UNPKG

@yamada-ui/autocomplete

Version:

Yamada UI autocomplete component

1,350 lines (1,340 loc) 42.5 kB
"use client" "use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/use-autocomplete.tsx var use_autocomplete_exports = {}; __export(use_autocomplete_exports, { useAutocomplete: () => useAutocomplete, useAutocompleteInput: () => useAutocompleteInput }); module.exports = __toCommonJS(use_autocomplete_exports); var import_core5 = require("@yamada-ui/core"); var import_form_control = require("@yamada-ui/form-control"); var import_use_controllable_state = require("@yamada-ui/use-controllable-state"); var import_use_disclosure = require("@yamada-ui/use-disclosure"); var import_use_outside_click = require("@yamada-ui/use-outside-click"); var import_utils7 = require("@yamada-ui/utils"); var import_react4 = require("react"); // src/autocomplete-context.ts var import_use_descendant = require("@yamada-ui/use-descendant"); var import_utils = require("@yamada-ui/utils"); var { DescendantsContextProvider: AutocompleteDescendantsContextProvider, useDescendant: useAutocompleteDescendant, useDescendants: useAutocompleteDescendants, useDescendantsContext: useAutocompleteDescendantsContext } = (0, import_use_descendant.createDescendant)(); var [AutocompleteProvider, useAutocompleteContext] = (0, import_utils.createContext)({ name: "AutocompleteContext", errorMessage: `useAutocompleteContext returned is 'undefined'. Seems you forgot to wrap the components in "<Autocomplete />" or "<MultiAutocomplete />"` }); // src/autocomplete-option.tsx var import_core2 = require("@yamada-ui/core"); var import_utils4 = require("@yamada-ui/utils"); // src/autocomplete-icon.tsx var import_core = require("@yamada-ui/core"); var import_icon = require("@yamada-ui/icon"); var import_use_clickable = require("@yamada-ui/use-clickable"); var import_utils2 = require("@yamada-ui/utils"); var import_react = require("react"); var import_jsx_runtime = require("react/jsx-runtime"); var AutocompleteIcon = (0, import_core.forwardRef)( ({ className, children, __css, ...rest }, ref) => { const { styles } = useAutocompleteContext(); const css = { alignItems: "center", cursor: "pointer", display: "inline-flex", justifyContent: "center", pointerEvents: "none", position: "absolute", top: "50%", transform: "translateY(-50%)", ...styles.icon, ...__css }; const validChildren = (0, import_utils2.getValidChildren)(children); const cloneChildren = validChildren.map( (child) => (0, import_react.cloneElement)(child, { style: { color: "currentColor", maxHeight: "1em", maxWidth: "1em" }, "aria-hidden": true, focusable: false }) ); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_core.ui.div, { ref, className: (0, import_utils2.cx)("ui-autocomplete__icon", className), __css: css, ...rest, children: (0, import_utils2.isValidElement)(children) ? cloneChildren : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icon.ChevronIcon, {}) } ); } ); AutocompleteIcon.displayName = "AutocompleteIcon"; AutocompleteIcon.__ui__ = "AutocompleteIcon"; var AutocompleteClearIcon = ({ className, children, ...props }) => { const ref = (0, import_react.useRef)(null); const { styles } = useAutocompleteContext(); const rest = (0, import_use_clickable.useClickable)({ ref, ...props }); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( AutocompleteIcon, { className: (0, import_utils2.cx)("ui-autocomplete__icon--clear", className), "aria-label": "Clear value", __css: styles.clearIcon, ...rest, children: children != null ? children : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icon.CloseIcon, { h: "0.5em", w: "0.5em" }) } ); }; AutocompleteClearIcon.displayName = "AutocompleteClearIcon"; AutocompleteClearIcon.__ui__ = "AutocompleteClearIcon"; var AutocompleteItemIcon = (0, import_core.forwardRef)(({ className, ...rest }, ref) => { const { styles } = useAutocompleteContext(); const css = { alignItems: "center", display: "inline-flex", flexShrink: 0, fontSize: "0.85em", justifyContent: "center", ...styles.itemIcon }; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_core.ui.span, { ref, className: (0, import_utils2.cx)("ui-autocomplete__item__icon", className), __css: css, ...rest } ); }); AutocompleteItemIcon.displayName = "AutocompleteItemIcon"; AutocompleteItemIcon.__ui__ = "AutocompleteItemIcon"; // src/use-autocomplete-option.ts var import_utils3 = require("@yamada-ui/utils"); var import_react2 = require("react"); var isTargetOption = (target) => { var _a; return (0, import_utils3.isHTMLElement)(target) && !!((_a = target.getAttribute("role")) == null ? void 0 : _a.startsWith("option")); }; var useAutocompleteOption = (props) => { var _a, _b; const { closeOnSelect, focusedIndex, inputRef, omitSelectedValues, setFocusedIndex, value: selectedValue, optionProps, onChange, onClose, onFocusNext } = useAutocompleteContext(); const uuid = (0, import_react2.useId)(); const itemRef = (0, import_react2.useRef)(null); const { id = uuid, children, closeOnSelect: customCloseOnSelect, isDisabled, disabled = isDisabled, isFocusable, focusable = isFocusable, icon: customIcon, value: optionValue, ...computedProps } = { ...optionProps, ...props }; const trulyDisabled = !!disabled && !focusable; const { descendants, index, register } = useAutocompleteDescendant({ disabled: trulyDisabled }); const values = descendants.values(); const frontValues = values.slice(0, index); const multi = (0, import_utils3.isArray)(selectedValue); const target = "target" in ((_b = (_a = itemRef.current) == null ? void 0 : _a.dataset) != null ? _b : {}); const focused = index === focusedIndex; const value = (0, import_react2.useMemo)(() => { let value2 = optionValue; if ((0, import_utils3.isUndefined)(optionValue)) { if ((0, import_utils3.isString)(children) || (0, import_utils3.isNumber)(children)) { value2 = children.toString(); } else { console.warn( `${!multi ? "Autocomplete" : "MultiAutocomplete"}: Cannot infer the option value of complex children. Pass a \`value\` prop or use a plain string as children to <Option>.` ); } } return value2 != null ? value2 : ""; }, [children, multi, optionValue]); const duplicated = frontValues.some( ({ node }) => node.dataset.value === value ); const selected = !duplicated && (!multi ? value === selectedValue : selectedValue.includes(value)); const onClick = (0, import_react2.useCallback)( (ev) => { ev.stopPropagation(); if (disabled || !isTargetOption(ev.currentTarget)) { if (inputRef.current) inputRef.current.focus(); return; } if (!duplicated) setFocusedIndex(index); onChange(value); if (inputRef.current) inputRef.current.focus(); if (customCloseOnSelect != null ? customCloseOnSelect : closeOnSelect) onClose(); if (omitSelectedValues) onFocusNext(index); }, [ duplicated, onFocusNext, omitSelectedValues, disabled, value, setFocusedIndex, index, onChange, customCloseOnSelect, closeOnSelect, onClose, inputRef ] ); const getOptionProps = (0, import_react2.useCallback)( (props2 = {}, ref = null) => { const hidden = !target || omitSelectedValues && selected; const style = { border: "0px", clip: "rect(0px, 0px, 0px, 0px)", height: "1px", margin: "-1px", overflow: "hidden", padding: "0px", position: "absolute", whiteSpace: "nowrap", width: "1px" }; return { ref: (0, import_utils3.mergeRefs)(itemRef, ref, register), role: "option", ...computedProps, ...props2, id, style: hidden ? style : void 0, "aria-disabled": (0, import_utils3.ariaAttr)(disabled), "aria-hidden": (0, import_utils3.ariaAttr)(hidden), "aria-selected": selected, "data-disabled": (0, import_utils3.dataAttr)(disabled), "data-duplicated": (0, import_utils3.dataAttr)(duplicated), "data-focus": (0, import_utils3.dataAttr)(focused), "data-target": (0, import_utils3.dataAttr)(true), "data-value": value, tabIndex: -1, onClick: (0, import_utils3.handlerAll)(computedProps.onClick, props2.onClick, onClick) }; }, [ id, duplicated, value, computedProps, disabled, focused, selected, target, omitSelectedValues, onClick, register ] ); return { target, children, customIcon, focused, omitSelectedValues, selected, getOptionProps }; }; // src/autocomplete-option.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); var AutocompleteOption = (0, import_core2.forwardRef)( ({ className, icon, ...rest }, ref) => { const { styles } = useAutocompleteContext(); const { children, customIcon, selected, getOptionProps } = useAutocompleteOption(rest); icon != null ? icon : icon = customIcon; const css = { alignItems: "center", color: "inherit", display: "flex", flex: "0 0 auto", gap: "0.75rem", outline: 0, textAlign: "start", textDecoration: "none", userSelect: "none", width: "100%", ...styles.item }; return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)( import_core2.ui.div, { className: (0, import_utils4.cx)("ui-autocomplete__item", className), __css: css, ...getOptionProps({}, ref), children: [ icon !== null ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AutocompleteItemIcon, { opacity: selected ? 1 : 0, children: icon || /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AutocompleteCheckIcon, {}) }) : null, /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_core2.ui.span, { style: { flex: 1 }, "data-label": true, children }) ] } ); } ); AutocompleteOption.displayName = "AutocompleteOption"; AutocompleteOption.__ui__ = "AutocompleteOption"; var AutocompleteCheckIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { height: "1em", viewBox: "0 0 14 14", width: "1em", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "polygon", { fill: "currentColor", points: "5.5 11.9993304 14 3.49933039 12.5 2 5.5 8.99933039 1.5 4.9968652 0 6.49933039" } ) }); AutocompleteCheckIcon.displayName = "AutocompleteCheckIcon"; AutocompleteCheckIcon.__ui__ = "AutocompleteCheckIcon"; // src/autocomplete-option-group.tsx var import_core4 = require("@yamada-ui/core"); var import_utils6 = require("@yamada-ui/utils"); // src/use-autocomplete-option-group.ts var import_core3 = require("@yamada-ui/core"); var import_utils5 = require("@yamada-ui/utils"); var import_react3 = require("react"); var useAutocompleteOptionGroup = ({ label, ...rest }) => { const { omitSelectedValues, value } = useAutocompleteContext(); const descendants = useAutocompleteDescendantsContext(); const labelRef = (0, import_react3.useRef)(null); const labelId = (0, import_react3.useId)(); const values = descendants.values(); const multi = (0, import_utils5.isArray)(value); const selectedValues = multi && omitSelectedValues ? descendants.values( ({ node }) => { var _a; return value.includes((_a = node.dataset.value) != null ? _a : ""); } ) : []; const selectedIndexes = selectedValues.map(({ index }) => index); const childValues = values.filter( ({ index, node }) => { var _a; return ((_a = node.parentElement) == null ? void 0 : _a.dataset.label) === label && !selectedIndexes.includes(index) && "target" in node.dataset; } ); const empty = !childValues.length; const [containerProps, groupProps] = (0, import_utils5.splitObject)(rest, import_core3.layoutStyleProperties); const getContainerProps = (0, import_react3.useCallback)( (props = {}, ref = null) => { var _a; const style = { border: "0px", clip: "rect(0px, 0px, 0px, 0px)", height: "1px", margin: "-1px", overflow: "hidden", padding: "0px", position: "absolute", whiteSpace: "nowrap", width: "1px" }; return { ref, "aria-labelledby": (_a = labelRef.current) == null ? void 0 : _a.id, role: "group", ...props, ...containerProps, style: empty ? style : void 0 }; }, [containerProps, empty] ); const getLabelProps = (0, import_react3.useCallback)( ({ id, ...props } = {}, ref = null) => { return { id: id != null ? id : labelId, ref: (0, import_utils5.mergeRefs)(ref, labelRef), role: "presentation", ...props }; }, [labelId] ); const getGroupProps = (0, import_react3.useCallback)( (props = {}, ref = null) => ({ ref, ...props, ...groupProps, "data-label": label }), [groupProps, label] ); return { label, getContainerProps, getGroupProps, getLabelProps }; }; // src/autocomplete-option-group.tsx var import_jsx_runtime3 = require("react/jsx-runtime"); var AutocompleteOptionGroup = (0, import_core4.forwardRef)( ({ className, children, color, h, height = h, minH, minHeight = minH, labelProps, ...rest }, ref) => { const { styles } = useAutocompleteContext(); const { label, getContainerProps, getGroupProps, getLabelProps } = useAutocompleteOptionGroup(rest); return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( import_core4.ui.section, { className: (0, import_utils6.cx)( "ui-autocomplete__item", "ui-autocomplete__item--group", className ), __css: { color, h: "fit-content", w: "100%" }, ...getContainerProps(), children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( import_core4.ui.header, { className: "ui-autocomplete__item__group-label", lineClamp: 1, __css: styles.groupLabel, ...getLabelProps(labelProps), children: label } ), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( import_core4.ui.div, { ...getGroupProps({}, ref), className: "ui-autocomplete__item__group", height, minHeight, __css: styles.group, children } ) ] } ); } ); AutocompleteOptionGroup.displayName = "AutocompleteOptionGroup"; AutocompleteOptionGroup.__ui__ = "AutocompleteOptionGroup"; // src/use-autocomplete.tsx var import_jsx_runtime4 = require("react/jsx-runtime"); var kanaMap = { "\uFF61": "\u3002", "\uFF62": "\u300C", "\uFF63": "\u300D", "\uFF64": "\u3001", "\uFF65": "\u30FB", \uFF66: "\u30F2", \uFF66\uFF9E: "\u30FA", \uFF67: "\u30A1", \uFF68: "\u30A3", \uFF69: "\u30A5", \uFF6A: "\u30A7", \uFF6B: "\u30A9", \uFF6C: "\u30E3", \uFF6D: "\u30E5", \uFF6E: "\u30E7", \uFF6F: "\u30C3", \uFF70: "\u30FC", \uFF71: "\u30A2", \uFF72: "\u30A4", \uFF73: "\u30A6", \uFF73\uFF9E: "\u30F4", \uFF74: "\u30A8", \uFF75: "\u30AA", \uFF76: "\u30AB", \uFF76\uFF9E: "\u30AC", \uFF77: "\u30AD", \uFF77\uFF9E: "\u30AE", \uFF78: "\u30AF", \uFF78\uFF9E: "\u30B0", \uFF79: "\u30B1", \uFF79\uFF9E: "\u30B2", \uFF7A: "\u30B3", \uFF7A\uFF9E: "\u30B4", \uFF7B: "\u30B5", \uFF7B\uFF9E: "\u30B6", \uFF7C: "\u30B7", \uFF7C\uFF9E: "\u30B8", \uFF7D: "\u30B9", \uFF7D\uFF9E: "\u30BA", \uFF7E: "\u30BB", \uFF7E\uFF9E: "\u30BC", \uFF7F: "\u30BD", \uFF7F\uFF9E: "\u30BE", \uFF80: "\u30BF", \uFF80\uFF9E: "\u30C0", \uFF81: "\u30C1", \uFF81\uFF9E: "\u30C2", \uFF82: "\u30C4", \uFF82\uFF9E: "\u30C5", \uFF83: "\u30C6", \uFF83\uFF9E: "\u30C7", \uFF84: "\u30C8", \uFF84\uFF9E: "\u30C9", \uFF85: "\u30CA", \uFF86: "\u30CB", \uFF87: "\u30CC", \uFF88: "\u30CD", \uFF89: "\u30CE", \uFF8A: "\u30CF", \uFF8A\uFF9E: "\u30D0", \uFF8A\uFF9F: "\u30D1", \uFF8B: "\u30D2", \uFF8B\uFF9E: "\u30D3", \uFF8B\uFF9F: "\u30D4", \uFF8C: "\u30D5", \uFF8C\uFF9E: "\u30D6", \uFF8C\uFF9F: "\u30D7", \uFF8D: "\u30D8", \uFF8D\uFF9E: "\u30D9", \uFF8D\uFF9F: "\u30DA", \uFF8E: "\u30DB", \uFF8E\uFF9E: "\u30DC", \uFF8E\uFF9F: "\u30DD", \uFF8F: "\u30DE", \uFF90: "\u30DF", \uFF91: "\u30E0", \uFF92: "\u30E1", \uFF93: "\u30E2", \uFF94: "\u30E4", \uFF95: "\u30E6", \uFF96: "\u30E8", \uFF97: "\u30E9", \uFF98: "\u30EA", \uFF99: "\u30EB", \uFF9A: "\u30EC", \uFF9B: "\u30ED", \uFF9C: "\u30EF", \uFF9C\uFF9E: "\u30F7", \uFF9D: "\u30F3" }; var defaultFormat = (value) => { value = value.replace( /[!-~]/g, (v) => String.fromCharCode(v.charCodeAt(0) - 65248) ); const reg = new RegExp("(" + Object.keys(kanaMap).join("|") + ")", "g"); value = value.replace(reg, (v) => kanaMap[v]).replace(/゙/g, "\u309B").replace(/゚/g, "\u309C"); value = value.toUpperCase(); return value; }; var flattenItems = (items) => { const filterItems = (items2) => items2.map((item) => { var _a; const { disabled, focusable } = item; const trulyDisabled = !!disabled && !focusable; if (trulyDisabled) return; if ("items" in item) { return filterItems((_a = item.items) != null ? _a : []); } else { return item; } }).filter(Boolean); return filterItems(items).flat(Infinity); }; var useAutocomplete = (props) => { var _a; const { allowCreate = false, allowFree = false, animation, boundary, children, closeDelay, closeOnBlur = true, closeOnEsc = true, closeOnSelect = true, defaultIsOpen, defaultOpen, defaultValue, duration = 0.2, emptyMessage = "No results found", eventListeners, flip, format = defaultFormat, gutter, insertPositionItem = "first", isLazy, isOpen, items, lazy = isLazy, lazyBehavior, matchWidth = true, maxSelectValues, modifiers, offset, omitSelectedValues = false, open: openProp, openDelay, placeholder, placement = "bottom-start", preventOverflow, strategy, value: valueProp, optionProps, onChange: onChangeProp, onClose: onCloseProp, onCreate: onCreateProp, onKeyDown: onKeyDownProp, onOpen: onOpenProp, onSearch: onSearchProp, ...rest } = (0, import_form_control.useFormControlProps)(props); const descendants = useAutocompleteDescendants(); const containerRef = (0, import_react4.useRef)(null); const inputRef = (0, import_react4.useRef)(null); const timeoutIds = (0, import_react4.useRef)(/* @__PURE__ */ new Set([])); const compositionRef = (0, import_react4.useRef)(false); const prevValue = (0, import_react4.useRef)(void 0); const [resolvedItems, setResolvedItems] = (0, import_react4.useState)(items ? JSON.parse(JSON.stringify(items)) : void 0); const [value, setValue] = (0, import_use_controllable_state.useControllableState)({ defaultValue, value: valueProp, onChange: onChangeProp }); const [label, setLabel] = (0, import_react4.useState)(void 0); const [inputValue, setInputValue] = (0, import_react4.useState)(""); const [focusedIndex, setFocusedIndex] = (0, import_react4.useState)(-1); const [allSelected, setAllSelected] = (0, import_react4.useState)(false); const [hit, setHit] = (0, import_react4.useState)(true); const { open, onClose, onOpen: onInternalOpen } = (0, import_use_disclosure.useDisclosure)({ defaultIsOpen, defaultOpen, isOpen, open: openProp, onClose: onCloseProp, onOpen: onOpenProp }); const { "aria-readonly": _ariaReadonly, onFocus: onFocusProp, ...formControlProps } = (0, import_utils7.pickObject)(rest, import_form_control.formControlProperties); const [containerProps, inputProps] = (0, import_utils7.splitObject)(rest, import_core5.layoutStyleProperties); const focused = focusedIndex > -1; const create = focusedIndex === -2 && allowCreate; const multi = (0, import_utils7.isArray)(value); const emptyValue = !multi ? !value : !value.length; const [firstInsertPositionItem, secondInsertPositionItem] = (0, import_react4.useMemo)(() => { if ((0, import_utils7.isArray)(insertPositionItem)) { return insertPositionItem; } else { return [insertPositionItem, "first"]; } }, [insertPositionItem]); if (allowCreate && !(0, import_utils7.isUndefined)(children)) { console.warn( `${!multi ? "Autocomplete" : "MultiAutocomplete"}: ${!multi ? "Autocomplete" : "MultiAutocomplete"} internally prefers 'children'. If 'allowCreate' is true, it will not be reflected correctly. If want to reflect, please set 'items' in props.` ); } const selectedValues = descendants.enabledValues( ({ node }) => { var _a2; return multi && value.includes((_a2 = node.dataset.value) != null ? _a2 : ""); } ); const selectedIndexes = selectedValues.map(({ index }) => index); const enabledValues = descendants.enabledValues( ({ index, node }) => "target" in node.dataset && !selectedIndexes.includes(index) ); const activedescendantId = (_a = descendants.value(focusedIndex)) == null ? void 0 : _a.node.id; const validChildren = (0, import_utils7.getValidChildren)(children); const computedChildren = (0, import_react4.useMemo)( () => resolvedItems == null ? void 0 : resolvedItems.map((item, index) => { if ("value" in item) { const { label: label2, value: value2, ...props2 } = item; return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AutocompleteOption, { value: value2, ...props2, children: label2 }, index); } else if ("items" in item) { const { items: items2 = [], label: label2, ...props2 } = item; return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( AutocompleteOptionGroup, { label: label2, ...props2, children: items2.map(({ label: label3, value: value2, ...props3 }, index2) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AutocompleteOption, { value: value2, ...props3, children: label3 }, index2)) }, index ); } }).filter(Boolean), [resolvedItems] ); const empty = !validChildren.length && !(computedChildren == null ? void 0 : computedChildren.length); const onOpen = (0, import_react4.useCallback)(() => { if (formControlProps.disabled || formControlProps.readOnly) return; if (!allowCreate && (empty || allSelected)) return; onInternalOpen(); if (inputRef.current) inputRef.current.focus(); }, [allowCreate, formControlProps, allSelected, empty, onInternalOpen]); const onFocusFirst = (0, import_react4.useCallback)(() => { const id = setTimeout(() => { var _a2; if (empty || allSelected) return; const first = descendants.enabledFirstValue( ({ node }) => "target" in node.dataset ); if (!first) return; if (!multi || !omitSelectedValues) { setFocusedIndex(first.index); } else { if (selectedIndexes.includes(first.index)) { const enabledFirst = enabledValues[0]; setFocusedIndex((_a2 = enabledFirst == null ? void 0 : enabledFirst.index) != null ? _a2 : -1); } else { setFocusedIndex(first.index); } } }); timeoutIds.current.add(id); }, [ descendants, enabledValues, allSelected, empty, multi, omitSelectedValues, selectedIndexes ]); const onFocusLast = (0, import_react4.useCallback)(() => { const id = setTimeout(() => { var _a2; if (empty || allSelected) return; const last = descendants.enabledLastValue( ({ node }) => "target" in node.dataset ); if (!last) return; if (!multi || !omitSelectedValues) { setFocusedIndex(last.index); } else { if (selectedIndexes.includes(last.index)) { const enabledLast = enabledValues.reverse()[0]; setFocusedIndex((_a2 = enabledLast == null ? void 0 : enabledLast.index) != null ? _a2 : -1); } else { setFocusedIndex(last.index); } } }); timeoutIds.current.add(id); }, [ descendants, enabledValues, allSelected, empty, multi, omitSelectedValues, selectedIndexes ]); const onFocusSelected = (0, import_react4.useCallback)(() => { const id = setTimeout(() => { const values = descendants.enabledValues(); const selected = values.find( ({ node }) => { var _a2; return !multi ? node.dataset.value === value : value.includes((_a2 = node.dataset.value) != null ? _a2 : ""); } ); if (selected) setFocusedIndex(selected.index); }); timeoutIds.current.add(id); }, [descendants, multi, value]); const onFocusNext = (0, import_react4.useCallback)( (index = focusedIndex) => { const id = setTimeout(() => { var _a2, _b; const next = descendants.enabledNextValue( index, ({ node }) => "target" in node.dataset ); if (!next) return; if (!multi || !omitSelectedValues) { setFocusedIndex(next.index); } else { if (selectedIndexes.includes(next.index)) { const enabledNext = (_a2 = enabledValues.find(({ index: index2 }) => next.index < index2)) != null ? _a2 : enabledValues[0]; setFocusedIndex((_b = enabledNext == null ? void 0 : enabledNext.index) != null ? _b : -1); } else { setFocusedIndex(next.index); } } }); timeoutIds.current.add(id); }, [ descendants, enabledValues, focusedIndex, multi, omitSelectedValues, selectedIndexes ] ); const onFocusPrev = (0, import_react4.useCallback)( (index = focusedIndex) => { const id = setTimeout(() => { var _a2, _b; const prev = descendants.enabledPrevValue( index, ({ node }) => "target" in node.dataset ); if (!prev) return; if (!multi || !omitSelectedValues) { setFocusedIndex(prev.index); } else { if (selectedIndexes.includes(prev.index)) { const enabledPrev = (_a2 = enabledValues.reverse().find(({ index: index2 }) => index2 < prev.index)) != null ? _a2 : enabledValues[0]; setFocusedIndex((_b = enabledPrev == null ? void 0 : enabledPrev.index) != null ? _b : -1); } else { setFocusedIndex(prev.index); } } }); timeoutIds.current.add(id); }, [ descendants, enabledValues, focusedIndex, multi, omitSelectedValues, selectedIndexes ] ); const onFocusFirstOrSelected = emptyValue || omitSelectedValues ? onFocusFirst : onFocusSelected; const onFocusLastOrSelected = emptyValue || omitSelectedValues ? onFocusLast : onFocusSelected; const pickOptions = (0, import_react4.useCallback)( (value2) => { const values = descendants.values(); let hit2 = false; let focused2 = false; values.forEach(({ index, node }) => { var _a2; if (format((_a2 = node.textContent) != null ? _a2 : "").includes(value2)) { hit2 = true; const disabled = "disabled" in node.dataset; node.dataset.target = ""; if (!focused2 && !disabled) { focused2 = true; setFocusedIndex(index); } } else { delete node.dataset.target; } }); setHit(hit2); }, [descendants, format] ); const rebirthOptions = (0, import_react4.useCallback)( (runFocus = true) => { const values = descendants.values(); values.forEach(({ node }) => { node.dataset.target = ""; }); if (runFocus) onFocusFirst(); setHit(true); }, [descendants, onFocusFirst] ); const onChangeLabel = (0, import_react4.useCallback)( (newValue) => { const values = descendants.values(); if (!values.length) return; const resolvedValues = (0, import_utils7.isArray)(newValue) ? newValue : [newValue]; const selectedLabel = resolvedValues.map((value2) => { var _a2, _b; const { node } = (_a2 = values.find(({ node: node2 }) => node2.dataset.value === value2)) != null ? _a2 : {}; if (node) { const { textContent } = (_b = Array.from(node.children).find( (child) => child.getAttribute("data-label") !== null )) != null ? _b : {}; return textContent; } else { return allowFree ? value2 : void 0; } }).filter((label2) => !(0, import_utils7.isUndefined)(label2)); setLabel(!multi ? selectedLabel[0] : selectedLabel); }, [allowFree, descendants, multi] ); const onChange = (0, import_react4.useCallback)( (newValue, runRebirth = true) => { setValue((prev) => { let next; if (!(0, import_utils7.isArray)(prev)) { next = newValue; } else { const selected = prev.includes(newValue); if (!selected) { next = [...prev, newValue]; } else { next = prev.filter((value2) => value2 !== newValue); } } prevValue.current = next; return next; }); setInputValue(""); if (multi && runRebirth) rebirthOptions(false); }, [multi, rebirthOptions, setValue] ); const onSelect = (0, import_react4.useCallback)(() => { var _a2, _b; let enabledValue = descendants.value(focusedIndex); if ("disabled" in ((_a2 = enabledValue == null ? void 0 : enabledValue.node.dataset) != null ? _a2 : {})) enabledValue = void 0; if (!enabledValue) return; const value2 = (_b = enabledValue.node.dataset.value) != null ? _b : ""; onChange(value2); if (closeOnSelect) onClose(); if (omitSelectedValues) onFocusNext(); }, [ closeOnSelect, descendants, focusedIndex, omitSelectedValues, onChange, onClose, onFocusNext ]); const onSearch = (0, import_react4.useCallback)( (ev) => { if (!open) onOpen(); onSearchProp == null ? void 0 : onSearchProp(ev); const value2 = ev.target.value; const computedValue = format(value2); if (computedValue) { pickOptions(computedValue); } else { rebirthOptions(); } setInputValue(value2); }, [open, onOpen, format, onSearchProp, pickOptions, rebirthOptions] ); const onCompositionStart = (0, import_react4.useCallback)(() => { compositionRef.current = true; }, []); const onCompositionEnd = (0, import_react4.useCallback)(() => { compositionRef.current = false; }, []); const onCreate = (0, import_react4.useCallback)(() => { var _a2, _b; const newItem = { label: inputValue, value: inputValue }; let newItems = []; if (resolvedItems) newItems = [...resolvedItems]; if (firstInsertPositionItem === "first") { newItems = [newItem, ...newItems]; } else if (firstInsertPositionItem === "last") { newItems = [...newItems, newItem]; } else { const i = newItems.findIndex( ({ label: label2 }) => label2 === firstInsertPositionItem ); const targetItem = newItems[i]; if (i !== -1 && targetItem && "items" in targetItem) { if (secondInsertPositionItem === "first") { targetItem.items = [newItem, ...(_a2 = targetItem.items) != null ? _a2 : []]; } else { targetItem.items = [...(_b = targetItem.items) != null ? _b : [], newItem]; } newItems[i] = targetItem; } else { console.warn( `${!multi ? "Autocomplete" : "MultiAutocomplete"}: '${firstInsertPositionItem}' specified in insertPositionItem does not exist in the option group.` ); } } setResolvedItems(newItems); onChange(inputValue); rebirthOptions(false); const index = flattenItems(newItems).findIndex( ({ value: value2 }) => value2 === inputValue ); setFocusedIndex(index); onCreateProp == null ? void 0 : onCreateProp(newItem, newItems); }, [ inputValue, resolvedItems, firstInsertPositionItem, onChange, rebirthOptions, onCreateProp, secondInsertPositionItem, multi ]); const onClick = (0, import_react4.useCallback)(() => { if (open) { if (inputRef.current) inputRef.current.focus(); } else { onOpen(); onFocusFirstOrSelected(); } }, [open, onFocusFirstOrSelected, onOpen]); const onFocus = (0, import_react4.useCallback)(() => { if (open) return; onOpen(); onFocusFirstOrSelected(); }, [open, onFocusFirstOrSelected, onOpen]); const onBlur = (0, import_react4.useCallback)( (ev) => { const relatedTarget = (0, import_utils7.getEventRelatedTarget)(ev); if ((0, import_utils7.isContains)(containerRef.current, relatedTarget)) return; if (!closeOnBlur && hit) return; if (allowFree && !!inputValue) onChange(inputValue, false); setInputValue(""); if (open) onClose(); }, [closeOnBlur, hit, open, inputValue, allowFree, onClose, onChange] ); const onDelete = (0, import_react4.useCallback)(() => { if (!multi) { onChange(""); } else { onChange(value[value.length - 1]); } if (!open) onFocus(); }, [multi, open, onChange, onFocus, value]); const onClear = (0, import_react4.useCallback)( (ev) => { ev.stopPropagation(); prevValue.current = []; setValue([]); setLabel(void 0); setInputValue(""); rebirthOptions(); if (open && inputRef.current) inputRef.current.focus(); }, [open, setLabel, setInputValue, setValue, rebirthOptions] ); const onKeyDown = (0, import_react4.useCallback)( (ev) => { if (ev.key === " ") ev.key = ev.code; if (formControlProps.disabled || formControlProps.readOnly) return; if (compositionRef.current) return; const enabledDelete = label === inputValue || !inputValue.length; const actions = { ArrowDown: focused ? () => onFocusNext() : !open ? (0, import_utils7.funcAll)(onOpen, onFocusFirstOrSelected) : void 0, ArrowUp: focused ? () => onFocusPrev() : !open ? (0, import_utils7.funcAll)(onOpen, onFocusLastOrSelected) : void 0, Backspace: !emptyValue && enabledDelete ? onDelete : void 0, End: open ? onFocusLast : void 0, Enter: create ? onCreate : focused ? onSelect : !open ? (0, import_utils7.funcAll)(onOpen, onFocusFirstOrSelected) : allowFree && multi ? () => { if (inputValue) onChange(inputValue); setFocusedIndex(0); } : void 0, Escape: closeOnEsc ? onClose : void 0, Home: open ? onFocusFirst : void 0, Space: create ? onCreate : focused ? onSelect : !open ? (0, import_utils7.funcAll)(onOpen, onFocusFirstOrSelected) : void 0 }; const action = actions[ev.key]; if (!action) return; ev.preventDefault(); ev.stopPropagation(); action(ev); }, [ allowFree, formControlProps, label, inputValue, onOpen, focused, multi, onFocusFirstOrSelected, onFocusNext, onFocusLastOrSelected, onFocusPrev, create, onCreate, onSelect, open, onFocusFirst, onFocusLast, closeOnEsc, onClose, emptyValue, onDelete, onChange ] ); (0, import_react4.useEffect)(() => { if (!multi) return; if (!omitSelectedValues && (0, import_utils7.isUndefined)(maxSelectValues)) return; const all = value.length > 0 && value.length === descendants.count(); const max = value.length === maxSelectValues; if (all || max) { onClose(); setAllSelected(true); } else { setAllSelected(false); } }, [omitSelectedValues, value, descendants, multi, onClose, maxSelectValues]); (0, import_utils7.useSafeLayoutEffect)(() => { onChangeLabel(value); }, [value]); (0, import_utils7.useUpdateEffect)(() => { if (open || allowFree) return; setFocusedIndex(-1); setInputValue(""); }, [open]); (0, import_utils7.useUpdateEffect)(() => { if (!hit) setFocusedIndex(-2); }, [hit]); (0, import_utils7.useUpdateEffect)(() => { setResolvedItems(items ? JSON.parse(JSON.stringify(items)) : void 0); }, [items]); (0, import_utils7.useUnmountEffect)(() => { timeoutIds.current.forEach((id) => clearTimeout(id)); timeoutIds.current.clear(); }); (0, import_use_outside_click.useOutsideClick)({ ref: containerRef, enabled: open && (closeOnBlur || !hit), handler: onClose }); const getPopoverProps = (0, import_react4.useCallback)( (props2) => ({ animation, boundary, closeDelay, closeOnBlur, duration, eventListeners, flip, gutter, lazy, lazyBehavior, matchWidth, modifiers, offset, openDelay, placement, preventOverflow, strategy, ...props2, closeOnButton: false, open, trigger: "never", onClose, onOpen }), [ closeOnBlur, openDelay, closeDelay, lazy, lazyBehavior, animation, duration, offset, gutter, preventOverflow, flip, matchWidth, boundary, eventListeners, strategy, placement, modifiers, open, onOpen, onClose ] ); const getContainerProps = (0, import_react4.useCallback)( (props2 = {}, ref = null) => ({ ref: (0, import_utils7.mergeRefs)(containerRef, ref), ...containerProps, ...props2, ...formControlProps, onBlur: (0, import_utils7.handlerAll)(props2.onBlur, rest.onBlur, onBlur), onClick: (0, import_utils7.handlerAll)(props2.onClick, rest.onClick, onClick) }), [containerProps, formControlProps, onBlur, onClick, rest] ); const getFieldProps = (0, import_react4.useCallback)( (props2 = {}, ref = null) => ({ ref, "aria-activedescendant": activedescendantId, "aria-autocomplete": "list", "aria-haspopup": "listbox", role: "combobox", tabIndex: -1, ...props2, ...formControlProps, "data-active": (0, import_utils7.dataAttr)(open), placeholder, onFocus: (0, import_utils7.handlerAll)(props2.onFocus, onFocusProp, onFocus), onKeyDown: (0, import_utils7.handlerAll)(props2.onKeyDown, onKeyDownProp, onKeyDown) }), [ activedescendantId, formControlProps, placeholder, open, onFocusProp, onFocus, onKeyDownProp, onKeyDown ] ); return { allowCreate, allowFree, allSelected, children: children != null ? children : computedChildren, closeOnSelect, descendants, empty, emptyMessage, focusedIndex, hit, inputRef, inputValue, label, omitSelectedValues, open, pickOptions, rebirthOptions, setFocusedIndex, value, formControlProps, getContainerProps, getFieldProps, getPopoverProps, inputProps, optionProps, onChange, onClear, onClose, onCompositionEnd, onCompositionStart, onCreate, onFocusFirst, onFocusLast, onFocusNext, onFocusPrev, onFocusSelected, onOpen, onSearch }; }; var useAutocompleteInput = () => { const { allSelected, inputRef, formControlProps, inputProps, onCompositionEnd, onCompositionStart, onSearch } = useAutocompleteContext(); (0, import_utils7.useUpdateEffect)(() => { if (allSelected && inputRef.current) inputRef.current.blur(); }, [allSelected]); const getInputProps = (0, import_react4.useCallback)( (props = {}, ref = null) => { return { ref: (0, import_utils7.mergeRefs)(inputRef, ref), ...formControlProps, autoCapitalize: "none", autoComplete: "off", spellCheck: "false", tabIndex: allSelected ? -1 : 0, ...inputProps, ...props, cursor: formControlProps.readOnly ? "default" : "text", pointerEvents: formControlProps.disabled || allSelected ? "none" : "auto", onChange: (0, import_utils7.handlerAll)(props.onChange, onSearch), onCompositionEnd: (0, import_utils7.handlerAll)( props.onCompositionEnd, inputProps.onCompositionEnd, onCompositionEnd ), onCompositionStart: (0, import_utils7.handlerAll)( props.onCompositionStart, inputProps.onCompositionStart, onCompositionStart ) }; }, [ inputProps, inputRef, formControlProps, allSelected, onSearch, onCompositionStart, onCompositionEnd ] ); return { getInputProps }; }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { useAutocomplete, useAutocompleteInput }); //# sourceMappingURL=use-autocomplete.js.map