@yamada-ui/autocomplete
Version:
Yamada UI autocomplete component
1,350 lines (1,340 loc) • 42.5 kB
JavaScript
"use client"
;
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