@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
JavaScript
/**
* @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
};