UNPKG

@progress/kendo-react-dropdowns

Version:

React DropDowns offer an interface for users to select different items from a list and more. KendoReact Dropdowns package

506 lines (505 loc) 21.3 kB
/** * @license *------------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the package root for more information *------------------------------------------------------------------------------------------- */ import * as c from "react"; import g from "prop-types"; import N from "../common/SearchBar.mjs"; import R from "../common/ListContainer.mjs"; import z from "../common/List.mjs"; import C from "../common/DropDownBase.mjs"; import L from "../common/GroupStickyHeader.mjs"; import { getFilteredData as b, getItemValue as y, areSame as S, isPresent as H, getFocusedItem as M, itemIndexStartsWith as A } from "../common/utils.mjs"; import { classNames as w, Keys as m, canUseDOM as D, IconWrap as K, createPropsContext as q, withIdHOC as W, withPropsContext as G, withAdaptiveModeContext as U, kendoThemeMaps as $ } from "@progress/kendo-react-common"; import { FloatingLabel as j } from "@progress/kendo-react-labels"; import J from "../common/ClearButton.mjs"; import { AdaptiveMode as Q } from "../common/AdaptiveMode.mjs"; import E from "../common/withCustomComponent.mjs"; import { ActionSheetContent as X } from "@progress/kendo-react-layout"; import Y from "../common/ListFilter.mjs"; const Z = "Please enter a valid value!", { sizeMap: O, roundedMap: ee } = $, I = class I extends c.Component { constructor() { super(...arguments), this.state = {}, this.base = new C(this), this._element = null, this._suggested = "", this._input = null, this._adaptiveInput = null, this._skipFocusEvent = !1, this._isScrolling = !1, this.itemHeight = 0, this.focus = () => { this._input && this._input.focus(); }, this.handleItemSelect = (e, t) => { const s = b(this.props), n = y(s[e], this.props.textField); this.state.text && !this.mobileMode && (this.state.text && (t.data.text = ""), this.base.filterChanged("", t)), this._adaptiveInput && this._adaptiveInput.blur(), this.triggerOnChange(n, t); }, this.itemFocus = (e, t) => { const { textField: s } = this.props, i = b(this.props)[e]; S(this.state.focusedItem, i, s) || (t.data.focusedItem = i); }, this.togglePopup = (e) => { this.base.togglePopup(e); }, this.setValidity = () => { this._input && this._input.setCustomValidity && this._input.setCustomValidity( this.validity.valid ? "" : this.props.validationMessage || Z ); }, this.renderAdaptiveListContainer = () => { const { adaptiveTitle: e } = this.props, { windowWidth: t = 0 } = this.state, s = /* @__PURE__ */ c.createElement( Y, { value: this.value, ref: (i) => this._adaptiveInput = i && i.element, onChange: this.onChangeHandler, onKeyDown: this.onInputKeyDown, size: this.props.size, rounded: this.props.rounded, fillMode: this.props.fillMode } ), n = { title: e, expand: this.opened, onClose: (i) => this.onCancel(i), windowWidth: t, mobileFilter: s }; return /* @__PURE__ */ c.createElement(Q, { ...n }, /* @__PURE__ */ c.createElement(X, { overflowHidden: !0 }, /* @__PURE__ */ c.createElement("div", { className: "k-list-container" }, this.listContainerContent()))); }, this.onCancel = (e) => { const t = this.base.initState(); t.syntheticEvent = e, e.stopPropagation(), this.opened && this.base.togglePopup(t), t.events.push({ type: "onCancel" }); const s = this.state.text; H(s) && s !== "" && this.base.filterChanged("", t), this.state.text && (t.data.text = ""), this.applyState(t); }, this.listContainerContent = () => { const { header: e, footer: t, size: s, groupStickyHeaderItemRender: n, groupField: i, list: a } = this.props, d = b(this.props); let { group: o } = this.state; return o === void 0 && i !== void 0 && (o = y(d[0], i)), /* @__PURE__ */ c.createElement(c.Fragment, null, e && /* @__PURE__ */ c.createElement("div", { className: "k-list-header" }, e), /* @__PURE__ */ c.createElement( "div", { className: w("k-list", { [`k-list-${this.mobileMode ? "lg" : O[s] || s}`]: s }) }, !a && o && d.length !== 0 && /* @__PURE__ */ c.createElement(L, { group: o, groupMode: "modern", render: n }), this.renderList() ), t && /* @__PURE__ */ c.createElement("div", { className: "k-list-footer" }, t)); }, this.onScroll = (e) => { this._isScrolling = !0; const { list: t } = this.base, { groupField: s } = this.props; let n = b(this.props); if (!s || !n.length) return; const i = this.itemHeight || (t ? t.children[0].offsetHeight : 0), d = e.target.scrollTop; s && (n = this.base.getGroupedDataModernMode(n, s)); let o = n[0][s]; for (let r = 1; r < n.length && !(i * r > d); r++) n[r] && n[r][s] && (o = n[r][s]); o !== this.state.group && this.setState({ group: o }); }, this.handleItemClick = (e, t) => { this.base.handleItemClick(e, t), this._valueDuringOnChange = void 0; }, this.onChangeHandler = (e) => { const s = this.base.initState(), n = this.mobileMode ? e.target.element : e.currentTarget, i = n.value, a = n.selectionEnd === i.length; s.syntheticEvent = e; const d = this._suggested, o = this.value, r = o && o.substring(0, o.length - d.length), l = r && r === i, h = r && r.length > i.length, { suggest: v } = this.props, p = this.props.opened !== void 0 ? this.props.opened : this.state.opened; if (v !== void 0 && v !== !1) { l || h || !a ? this._suggested = "" : this.suggestValue(i); const u = i + this._suggested, f = { userInput: i, value: this._suggested }; this.triggerOnChange(u, s, { suggestion: f }); } else this._suggested = "", this.triggerOnChange(i, s); (!p && i || p && !i) && this.togglePopup(s), s.data.focusedItem = void 0, this.applyState(s), this.setState({ group: void 0 }); }, this.clearButtonClick = (e) => { const s = this.base.initState(), n = this.props.opened !== void 0 ? this.props.opened : this.state.opened; s.syntheticEvent = e; const i = ""; this._suggested = "", this.triggerOnChange(i, s), this.state.focusedItem !== void 0 && (s.data.focusedItem = void 0), n && this.togglePopup(s), this.applyState(s); }, this.onInputKeyDown = (e) => { const { skipDisabledItems: t, groupField: s, textField: n } = this.props, i = b(this.props); this._isScrolling && (this._isScrolling = !1); const a = this.focusedIndex(), d = i[a], o = e.keyCode, r = e.altKey, l = this.props.opened !== void 0 ? this.props.opened : this.state.opened, h = this.base.initState(); h.syntheticEvent = e; const v = () => { l && e.preventDefault(); }; if (r && o === m.down) this.setState({ opened: !0 }); else if (r && o === m.up) this.setState({ opened: !1 }); else if (l && o === m.pageUp) v(), this.base.scrollPopupByPageSize(-1); else if (l && o === m.pageDown) v(), this.base.scrollPopupByPageSize(1); else if (l && (o === m.enter || o === m.esc)) v(), t === !1 && d && d.disabled ? (l && this.togglePopup(h), this.applyState(h)) : this.applyInputValue(e.currentTarget.value, h, e.keyCode); else if (!l && o === m.esc) { const p = ""; this._suggested = "", this.triggerOnChange(p, h), this.state.focusedItem !== void 0 && (h.data.focusedItem = void 0), this.applyState(h); } else if (o === m.up || o === m.down) { if (s !== "" && n) if (!this.props.skipDisabledItems && l) this.onNavigate(h, o); else { let p = 0; if (o === m.down || o === m.right) { const u = i.slice(a + 1).find((f) => !f.disabled && f[n]); p = u && i.findIndex((f) => f[n] === u[n]); } else if (o === m.up || o === m.left) { let u; if (a === -1) u = i, p = i.findIndex((f) => !f.disabled && f[n]); else { u = i.slice(0, a); let f = u.pop(); for (; f && f.disabled; ) f = u.pop(); p = f && i.findIndex((x) => x[n] === f[n]); } } if (p !== void 0) { const u = p - a; this.onNavigate(h, o, u); } else p === void 0 && i.findIndex((u) => u[n]) === i.length - 1 && this.onNavigate(h, o); } else if (!this.props.skipDisabledItems && l) this.onNavigate(h, o); else { let p = null; if (o === m.down || o === m.right) p = i.slice(a + 1).find((u) => !u.disabled); else if (o === m.up || o === m.left) { const u = i.slice(0, a); for (p = u.pop(); p && p.disabled; ) p = u.pop(); } if (p) { const u = p.id - a - 1; this.onNavigate(h, o, u); } else this.onNavigate(h, o); } this.applyState(h), v(); } }, this.handleFocus = (e) => { this._skipFocusEvent || this.base.handleFocus(e); }, this.handleBlur = (e) => { const t = this.base.initState(); !this.state.focused || this._skipFocusEvent || (t.syntheticEvent = e, t.data.focused = !1, t.events.push({ type: "onBlur" }), this.opened && !this.mobileMode && (this.state.opened && (t.data.opened = !1), t.events.push({ type: "onClose" })), this.applyState(t)); }, this.handleWrapperClick = (e) => { const t = this._input; !this.opened && t && this.focusElement(t); const s = this.base.initState(); s.syntheticEvent = e, !this.state.focused && !this.mobileMode && (s.events.push({ type: "onFocus" }), s.data.focused = !0), this.mobileMode && window.setTimeout(() => this._adaptiveInput && this._adaptiveInput.focus(), 300), this.base.togglePopup(s), this.applyState(s); }; } get _inputId() { return this.props.id + "-accessibility-id"; } get document() { if (D) return this.element && this.element.ownerDocument || document; } /** * @hidden */ get element() { return this._element; } /** * The value of the AutoComplete. */ get value() { return this._valueDuringOnChange !== void 0 ? this._valueDuringOnChange : this.props.value !== void 0 ? this.props.value : this.state.value !== void 0 ? this.state.value : this.props.defaultValue !== void 0 ? this.props.defaultValue : ""; } /** * Gets the `name` property of the AutoComplete. */ get name() { return this.props.name; } /** * Represents the validity state into which the AutoComplete is set. */ get validity() { const e = this.props.validationMessage !== void 0, t = !this.required || this.value !== "", s = this.props.valid !== void 0 ? this.props.valid : t; return { customError: e, valid: s, valueMissing: this.value === null }; } /** @hidden */ get opened() { return !!(this.props.opened !== void 0 ? this.props.opened : this.state.opened); } /** * The mobile mode of the AutoComplete. */ get mobileMode() { var t; return !!(this.state.windowWidth && this.props._adaptiveMode && this.state.windowWidth <= ((t = this.props._adaptiveMode) == null ? void 0 : t.medium) && this.props.adaptive); } /** * @hidden */ get validityStyles() { return this.props.validityStyles !== void 0 ? this.props.validityStyles : I.defaultProps.validityStyles; } /** * @hidden */ get required() { return this.props.required !== void 0 ? this.props.required : I.defaultProps.required; } /** * @hidden */ componentDidUpdate(e, t) { var p; const { groupField: s = "" } = this.props, n = b(this.props), { data: i = [] } = e, a = this.focusedIndex(), d = n[a], o = i !== n, r = d !== void 0 && t.focusedItem !== d, l = this.props.opened !== void 0 ? this.props.opened : this.state.opened, h = e.opened !== void 0 ? e.opened : t.opened, v = !h && l; if (s === "") (l && (r || o) || v) && this.base.scrollToItem(a); else if (!this._isScrolling) { const u = (p = this.base.getGroupedDataModernMode(n, s)) == null ? void 0 : p.indexOf(d); v && (n && n.length !== 0 && this.base.resetGroupStickyHeader(n[0][s], this), this.base.scrollToItem(u)), l && h && r && this.base.scrollToItem(u); } this.setValidity(); } /** * @hidden */ componentDidMount() { var e; this.observerResize = D && window.ResizeObserver && new window.ResizeObserver(this.calculateMedia.bind(this)), this.base.didMount(), this.setValidity(), (e = this.document) != null && e.body && this.observerResize && this.observerResize.observe(this.document.body); } /** * @hidden */ componentWillUnmount() { this.observerResize && this.observerResize.disconnect(); } /** * @hidden */ render() { const { dir: e, disabled: t, label: s, className: n, style: i, loading: a, suggest: d, size: o, rounded: r, fillMode: l } = this.props, h = !this.validityStyles || this.validity.valid, v = this.base, p = this.value, u = this.props.clearButton !== !1 && !a && !!p, f = this.props.id || this._inputId, x = this.state.focused; typeof d == "string" && (this._suggested = d); const [P, B] = E(this.props.prefix || c.Fragment), [T, V] = E(this.props.suffix || c.Fragment), _ = /* @__PURE__ */ c.createElement(c.Fragment, null, /* @__PURE__ */ c.createElement( "span", { className: w("k-autocomplete k-input", n, { [`k-input-${O[o] || o}`]: o, [`k-rounded-${ee[r] || r}`]: r, [`k-input-${l}`]: l, "k-invalid": !h, "k-focus": x && !t, "k-loading": a, "k-required": this.required, "k-disabled": t }), ref: (F) => { this._element = F, v.wrapper = F; }, style: s ? { ...i, width: void 0 } : i, dir: e, onFocus: this.handleFocus, onBlur: this.handleBlur, onClick: this.handleWrapperClick }, this.props.prefix && /* @__PURE__ */ c.createElement(P, { ...B }), this.renderSearchBar(p || "", f), a && /* @__PURE__ */ c.createElement(K, { className: "k-input-loading-icon", name: "loading" }), u && !a && /* @__PURE__ */ c.createElement(J, { onClick: this.clearButtonClick, key: "clearbutton" }), this.props.suffix && /* @__PURE__ */ c.createElement(T, { ...V }), !this.mobileMode && this.renderListContainer() ), this.mobileMode && this.renderAdaptiveListContainer()); return s ? /* @__PURE__ */ c.createElement( j, { label: s, editorId: f, editorValue: p, editorValid: h, editorDisabled: t, style: { width: i ? i.width : void 0 }, children: _ } ) : _; } /** * @hidden */ onNavigate(e, t, s) { const n = this.value, { textField: i, focusedItemIndex: a } = this.props, d = b(this.props), o = this.state.focusedItem !== void 0 ? d.findIndex((l) => S(l, this.state.focusedItem, i)) : a ? a(d, n, i) : d.indexOf(M(d, n, i)), r = this.base.navigation.navigate({ keyCode: t, current: o, max: d.length - 1, min: 0, skipItems: s || void 0 }); r !== void 0 && this.itemFocus(r, e), this.applyState(e); } /** * @hidden */ applyInputValue(e, t, s) { const n = this.props.opened !== void 0 ? this.props.opened : this.state.opened, { textField: i } = this.props, a = b(this.props), d = this.focusedIndex(), o = a[d]; if (this._suggested = "", n && s === m.enter && o && !o.disabled) { const r = y(a[this.focusedIndex(e)], i); this.triggerOnChange(r, t); } n && this.togglePopup(t), this.applyState(t); } renderSearchBar(e, t) { const s = this.base, { placeholder: n, tabIndex: i, disabled: a, readonly: d, inputAttributes: o } = this.props, { focused: r } = this.state, l = this.props.opened !== void 0 ? this.props.opened : this.state.opened; return /* @__PURE__ */ c.createElement( N, { id: t, placeholder: n, tabIndex: i, accessKey: this.props.accessKey, value: e, suggestedText: this._suggested, focused: r, name: this.props.name, ref: (h) => this._input = h && h.input, onKeyDown: this.onInputKeyDown, onChange: this.onChangeHandler, onFocus: s.handleFocus, onBlur: this.handleBlur, disabled: a, readOnly: d, expanded: l, owns: s.listBoxId, activedescendant: "option-" + s.guid + "-" + this.focusedIndex(), role: "combobox", ariaLabelledBy: this.props.ariaLabelledBy, ariaDescribedBy: this.props.ariaDescribedBy, ariaRequired: this.required, render: this.props.valueRender, inputAttributes: o } ); } renderListContainer() { const e = this.base, { dir: t, groupField: s } = this.props, n = b(this.props), i = e.getPopupSettings(), a = this.props.opened !== void 0 ? this.props.opened : this.state.opened, d = i.width !== void 0 ? i.width : e.popupWidth; let { group: o } = this.state; return o === void 0 && s !== void 0 && (o = y(n[0], s)), /* @__PURE__ */ c.createElement( R, { width: d, popupSettings: { ...i, anchor: i.anchor || this.element, show: a, popupClass: w(i.popupClass, "k-list-container", "k-autocomplete-popup") }, dir: t !== void 0 ? t : this.base.dirCalculated, itemsCount: [n.length] }, this.listContainerContent() ); } renderList() { const e = this.base, t = e.getPopupSettings(), { textField: s, listNoDataRender: n, itemRender: i, groupHeaderItemRender: a } = this.props, d = b(this.props), o = this.value, r = this.props.opened !== void 0 ? this.props.opened : this.state.opened; return /* @__PURE__ */ c.createElement( z, { id: e.listBoxId, show: r, data: d.slice(), focusedIndex: this.focusedIndex(), value: o, textField: s, valueField: s, highlightSelected: !1, optionsGuid: e.guid, groupField: this.props.groupField, groupMode: "modern", listRef: (l) => e.list = l, wrapperStyle: this.mobileMode ? {} : { maxHeight: t.height }, wrapperCssClass: "k-list-content", onClick: this.handleItemClick, itemRender: i, groupHeaderItemRender: a, noDataRender: n, onMouseDown: (l) => l.preventDefault(), onScroll: this.onScroll } ); } triggerOnChange(e, t, s) { this.value === e && !s || (t.data.value = e, this._valueDuringOnChange = e, t.events.push({ type: "onChange", ...s || {} })); } focusElement(e) { this._skipFocusEvent = !0, e.focus(), window.setTimeout(() => this._skipFocusEvent = !1, 0); } applyState(e) { this.base.applyState(e), this._valueDuringOnChange = void 0; } suggestValue(e) { if (this._suggested = "", e) { const { textField: t } = this.props, s = b(this.props), n = s[A(s, e, t)]; if (n) { const i = y(n, t); e.toLowerCase() !== i.toLowerCase() && (this._suggested = i.substring(e.length)); } } } focusedIndex(e) { const { textField: t, focusedItemIndex: s, skipDisabledItems: n } = this.props, i = b(this.props), a = e !== void 0 ? e : this.value; if (this.state.focusedItem !== void 0) return i.findIndex((o) => S(o, this.state.focusedItem, t)); if (s) return s(i, a, t); const d = i.indexOf(M(i, a, t)); return n && t && d === -1 ? i.findIndex((o) => !o.disabled && o[t]) : Math.max(0, d); } calculateMedia(e) { for (const t of e) this.setState({ windowWidth: t.target.clientWidth }); } }; I.displayName = "AutoComplete", I.propTypes = { ...C.basicPropTypes, size: g.oneOf([null, "small", "medium", "large"]), rounded: g.oneOf([null, "small", "medium", "large", "full"]), fillMode: g.oneOf([null, "solid", "flat", "outline"]), groupField: g.string, suggest: g.oneOfType([g.bool, g.string]), placeholder: g.string, value: g.string, defaultValue: g.string, validationMessage: g.string, required: g.bool, readonly: g.bool, clearButton: g.bool, valueRender: g.func, id: g.string, ariaLabelledBy: g.string, ariaDescribedBy: g.string, list: g.any, adaptive: g.bool, adaptiveTitle: g.string, onCancel: g.func, skipDisabledItems: g.bool, inputAttributes: g.object }, I.defaultProps = { ...C.defaultProps, size: "medium", rounded: "medium", fillMode: "solid", skipDisabledItems: !0, prefix: void 0, suffix: void 0 }; let k = I; const te = q(), se = W( G( te, U(k) ) ); se.displayName = "KendoReactAutoComplete"; export { se as AutoComplete, te as AutoCompletePropsContext, k as AutoCompleteWithoutContext };