@ariakit/react-core
Version:
Ariakit React core
469 lines (429 loc) • 17.6 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
var _SIL3OXTPcjs = require('../__chunks/SIL3OXTP.cjs');
var _XQNVUUCMcjs = require('../__chunks/XQNVUUCM.cjs');
require('../__chunks/MIBORXQW.cjs');
var _PH2DFCJScjs = require('../__chunks/PH2DFCJS.cjs');
require('../__chunks/ZVJRPAXY.cjs');
require('../__chunks/YDPERDKF.cjs');
require('../__chunks/KVXNVDJK.cjs');
require('../__chunks/D6FV6EYS.cjs');
require('../__chunks/YUGKYIYY.cjs');
require('../__chunks/T3QB4FR3.cjs');
require('../__chunks/6HKL3JR2.cjs');
var _WULEED4Qcjs = require('../__chunks/WULEED4Q.cjs');
var _OZM4QA2Vcjs = require('../__chunks/OZM4QA2V.cjs');
require('../__chunks/FDRJDQ5Y.cjs');
var _7EQBAZ46cjs = require('../__chunks/7EQBAZ46.cjs');
// src/combobox/combobox.tsx
var _dom = require('@ariakit/core/utils/dom');
var _events = require('@ariakit/core/utils/events');
var _focus = require('@ariakit/core/utils/focus');
var _misc = require('@ariakit/core/utils/misc');
var _store = require('@ariakit/core/utils/store');
var _react = require('react');
var TagName = "input";
function isFirstItemAutoSelected(items, activeValue, autoSelect) {
if (!autoSelect) return false;
const firstItem = items.find((item) => !item.disabled && item.value);
return (firstItem == null ? void 0 : firstItem.value) === activeValue;
}
function hasCompletionString(value, activeValue) {
if (!activeValue) return false;
if (value == null) return false;
value = _misc.normalizeString.call(void 0, value);
return activeValue.length > value.length && activeValue.toLowerCase().indexOf(value.toLowerCase()) === 0;
}
function isInputEvent(event) {
return event.type === "input";
}
function isAriaAutoCompleteValue(value) {
return value === "inline" || value === "list" || value === "both" || value === "none";
}
function getDefaultAutoSelectId(items) {
const item = items.find((item2) => {
var _a;
if (item2.disabled) return false;
return ((_a = item2.element) == null ? void 0 : _a.getAttribute("role")) !== "tab";
});
return item == null ? void 0 : item.id;
}
var useCombobox = _WULEED4Qcjs.createHook.call(void 0,
function useCombobox2(_a) {
var _b = _a, {
store,
focusable = true,
autoSelect: autoSelectProp = false,
getAutoSelectId,
setValueOnChange,
showMinLength = 0,
showOnChange,
showOnMouseDown,
showOnClick = showOnMouseDown,
showOnKeyDown,
showOnKeyPress = showOnKeyDown,
blurActiveItemOnClick,
setValueOnClick = true,
moveOnKeyPress = true,
autoComplete = "list"
} = _b, props = _7EQBAZ46cjs.__objRest.call(void 0, _b, [
"store",
"focusable",
"autoSelect",
"getAutoSelectId",
"setValueOnChange",
"showMinLength",
"showOnChange",
"showOnMouseDown",
"showOnClick",
"showOnKeyDown",
"showOnKeyPress",
"blurActiveItemOnClick",
"setValueOnClick",
"moveOnKeyPress",
"autoComplete"
]);
const context = _PH2DFCJScjs.useComboboxProviderContext.call(void 0, );
store = store || context;
_misc.invariant.call(void 0,
store,
process.env.NODE_ENV !== "production" && "Combobox must receive a `store` prop or be wrapped in a ComboboxProvider component."
);
const ref = _react.useRef.call(void 0, null);
const [valueUpdated, forceValueUpdate] = _OZM4QA2Vcjs.useForceUpdate.call(void 0, );
const canAutoSelectRef = _react.useRef.call(void 0, false);
const composingRef = _react.useRef.call(void 0, false);
const autoSelect = store.useState(
(state) => state.virtualFocus && autoSelectProp
);
const inline = autoComplete === "inline" || autoComplete === "both";
const [canInline, setCanInline] = _react.useState.call(void 0, inline);
_OZM4QA2Vcjs.useUpdateLayoutEffect.call(void 0, () => {
if (!inline) return;
setCanInline(true);
}, [inline]);
const storeValue = store.useState("value");
const prevSelectedValueRef = _react.useRef.call(void 0, );
_react.useEffect.call(void 0, () => {
return _store.sync.call(void 0, store, ["selectedValue", "activeId"], (_, prev) => {
prevSelectedValueRef.current = prev.selectedValue;
});
}, []);
const inlineActiveValue = store.useState((state) => {
var _a2;
if (!inline) return;
if (!canInline) return;
if (state.activeValue && Array.isArray(state.selectedValue)) {
if (state.selectedValue.includes(state.activeValue)) return;
if ((_a2 = prevSelectedValueRef.current) == null ? void 0 : _a2.includes(state.activeValue)) return;
}
return state.activeValue;
});
const items = store.useState("renderedItems");
const open = store.useState("open");
const contentElement = store.useState("contentElement");
const value = _react.useMemo.call(void 0, () => {
if (!inline) return storeValue;
if (!canInline) return storeValue;
const firstItemAutoSelected = isFirstItemAutoSelected(
items,
inlineActiveValue,
autoSelect
);
if (firstItemAutoSelected) {
if (hasCompletionString(storeValue, inlineActiveValue)) {
const slice = (inlineActiveValue == null ? void 0 : inlineActiveValue.slice(storeValue.length)) || "";
return storeValue + slice;
}
return storeValue;
}
return inlineActiveValue || storeValue;
}, [inline, canInline, items, inlineActiveValue, autoSelect, storeValue]);
_react.useEffect.call(void 0, () => {
const element = ref.current;
if (!element) return;
const onCompositeItemMove = () => setCanInline(true);
element.addEventListener("combobox-item-move", onCompositeItemMove);
return () => {
element.removeEventListener("combobox-item-move", onCompositeItemMove);
};
}, []);
_react.useEffect.call(void 0, () => {
if (!inline) return;
if (!canInline) return;
if (!inlineActiveValue) return;
const firstItemAutoSelected = isFirstItemAutoSelected(
items,
inlineActiveValue,
autoSelect
);
if (!firstItemAutoSelected) return;
if (!hasCompletionString(storeValue, inlineActiveValue)) return;
let cleanup = _misc.noop;
queueMicrotask(() => {
const element = ref.current;
if (!element) return;
const { start: prevStart, end: prevEnd } = _dom.getTextboxSelection.call(void 0, element);
const nextStart = storeValue.length;
const nextEnd = inlineActiveValue.length;
_dom.setSelectionRange.call(void 0, element, nextStart, nextEnd);
cleanup = () => {
if (!_focus.hasFocus.call(void 0, element)) return;
const { start, end } = _dom.getTextboxSelection.call(void 0, element);
if (start !== nextStart) return;
if (end !== nextEnd) return;
_dom.setSelectionRange.call(void 0, element, prevStart, prevEnd);
};
});
return () => cleanup();
}, [
valueUpdated,
inline,
canInline,
inlineActiveValue,
items,
autoSelect,
storeValue
]);
const scrollingElementRef = _react.useRef.call(void 0, null);
const getAutoSelectIdProp = _OZM4QA2Vcjs.useEvent.call(void 0, getAutoSelectId);
const autoSelectIdRef = _react.useRef.call(void 0, null);
_react.useEffect.call(void 0, () => {
if (!open) return;
if (!contentElement) return;
const scrollingElement = _dom.getScrollingElement.call(void 0, contentElement);
if (!scrollingElement) return;
scrollingElementRef.current = scrollingElement;
const onUserScroll = () => {
canAutoSelectRef.current = false;
};
const onScroll = () => {
if (!store) return;
if (!canAutoSelectRef.current) return;
const { activeId } = store.getState();
if (activeId === null) return;
if (activeId === autoSelectIdRef.current) return;
canAutoSelectRef.current = false;
};
const options = { passive: true, capture: true };
scrollingElement.addEventListener("wheel", onUserScroll, options);
scrollingElement.addEventListener("touchmove", onUserScroll, options);
scrollingElement.addEventListener("scroll", onScroll, options);
return () => {
scrollingElement.removeEventListener("wheel", onUserScroll, true);
scrollingElement.removeEventListener("touchmove", onUserScroll, true);
scrollingElement.removeEventListener("scroll", onScroll, true);
};
}, [open, contentElement, store]);
_OZM4QA2Vcjs.useSafeLayoutEffect.call(void 0, () => {
if (!storeValue) return;
if (composingRef.current) return;
canAutoSelectRef.current = true;
}, [storeValue]);
_OZM4QA2Vcjs.useSafeLayoutEffect.call(void 0, () => {
if (autoSelect !== "always" && open) return;
canAutoSelectRef.current = open;
}, [autoSelect, open]);
const resetValueOnSelect = store.useState("resetValueOnSelect");
_OZM4QA2Vcjs.useUpdateEffect.call(void 0, () => {
var _a2, _b2;
const canAutoSelect = canAutoSelectRef.current;
if (!store) return;
if (!open) return;
if (!canAutoSelect && !resetValueOnSelect) return;
const { baseElement, contentElement: contentElement2, activeId } = store.getState();
if (baseElement && !_focus.hasFocus.call(void 0, baseElement)) return;
if (contentElement2 == null ? void 0 : contentElement2.hasAttribute("data-placing")) {
const observer = new MutationObserver(forceValueUpdate);
observer.observe(contentElement2, { attributeFilter: ["data-placing"] });
return () => observer.disconnect();
}
if (autoSelect && canAutoSelect) {
const userAutoSelectId = getAutoSelectIdProp(items);
const autoSelectId = userAutoSelectId !== void 0 ? userAutoSelectId : (_a2 = getDefaultAutoSelectId(items)) != null ? _a2 : store.first();
autoSelectIdRef.current = autoSelectId;
store.move(autoSelectId != null ? autoSelectId : null);
} else {
const element = (_b2 = store.item(activeId || store.first())) == null ? void 0 : _b2.element;
if (element && "scrollIntoView" in element) {
element.scrollIntoView({ block: "nearest", inline: "nearest" });
}
}
return;
}, [
store,
open,
valueUpdated,
storeValue,
autoSelect,
resetValueOnSelect,
getAutoSelectIdProp,
items
]);
_react.useEffect.call(void 0, () => {
if (!inline) return;
const combobox = ref.current;
if (!combobox) return;
const elements = [combobox, contentElement].filter(
(value2) => !!value2
);
const onBlur2 = (event) => {
if (elements.every((el) => _events.isFocusEventOutside.call(void 0, event, el))) {
store == null ? void 0 : store.setValue(value);
}
};
for (const element of elements) {
element.addEventListener("focusout", onBlur2);
}
return () => {
for (const element of elements) {
element.removeEventListener("focusout", onBlur2);
}
};
}, [inline, contentElement, store, value]);
const canShow = (event) => {
const currentTarget = event.currentTarget;
return currentTarget.value.length >= showMinLength;
};
const onChangeProp = props.onChange;
const showOnChangeProp = _OZM4QA2Vcjs.useBooleanEvent.call(void 0, showOnChange != null ? showOnChange : canShow);
const setValueOnChangeProp = _OZM4QA2Vcjs.useBooleanEvent.call(void 0,
// If the combobox is combined with tags, the value will be set by the tag
// input component.
setValueOnChange != null ? setValueOnChange : !store.tag
);
const onChange = _OZM4QA2Vcjs.useEvent.call(void 0, (event) => {
onChangeProp == null ? void 0 : onChangeProp(event);
if (event.defaultPrevented) return;
if (!store) return;
const currentTarget = event.currentTarget;
const { value: value2, selectionStart, selectionEnd } = currentTarget;
const nativeEvent = event.nativeEvent;
canAutoSelectRef.current = true;
if (isInputEvent(nativeEvent)) {
if (nativeEvent.isComposing) {
canAutoSelectRef.current = false;
composingRef.current = true;
}
if (inline) {
const textInserted = nativeEvent.inputType === "insertText" || nativeEvent.inputType === "insertCompositionText";
const caretAtEnd = selectionStart === value2.length;
setCanInline(textInserted && caretAtEnd);
}
}
if (setValueOnChangeProp(event)) {
const isSameValue = value2 === store.getState().value;
store.setValue(value2);
queueMicrotask(() => {
_dom.setSelectionRange.call(void 0, currentTarget, selectionStart, selectionEnd);
});
if (inline && autoSelect && isSameValue) {
forceValueUpdate();
}
}
if (showOnChangeProp(event)) {
store.show();
}
if (!autoSelect || !canAutoSelectRef.current) {
store.setActiveId(null);
}
});
const onCompositionEndProp = props.onCompositionEnd;
const onCompositionEnd = _OZM4QA2Vcjs.useEvent.call(void 0, (event) => {
canAutoSelectRef.current = true;
composingRef.current = false;
onCompositionEndProp == null ? void 0 : onCompositionEndProp(event);
if (event.defaultPrevented) return;
if (!autoSelect) return;
forceValueUpdate();
});
const onMouseDownProp = props.onMouseDown;
const blurActiveItemOnClickProp = _OZM4QA2Vcjs.useBooleanEvent.call(void 0,
blurActiveItemOnClick != null ? blurActiveItemOnClick : () => !!(store == null ? void 0 : store.getState().includesBaseElement)
);
const setValueOnClickProp = _OZM4QA2Vcjs.useBooleanEvent.call(void 0, setValueOnClick);
const showOnClickProp = _OZM4QA2Vcjs.useBooleanEvent.call(void 0, showOnClick != null ? showOnClick : canShow);
const onMouseDown = _OZM4QA2Vcjs.useEvent.call(void 0, (event) => {
onMouseDownProp == null ? void 0 : onMouseDownProp(event);
if (event.defaultPrevented) return;
if (event.button) return;
if (event.ctrlKey) return;
if (!store) return;
if (blurActiveItemOnClickProp(event)) {
store.setActiveId(null);
}
if (setValueOnClickProp(event)) {
store.setValue(value);
}
if (showOnClickProp(event)) {
_events.queueBeforeEvent.call(void 0, event.currentTarget, "mouseup", store.show);
}
});
const onKeyDownProp = props.onKeyDown;
const showOnKeyPressProp = _OZM4QA2Vcjs.useBooleanEvent.call(void 0, showOnKeyPress != null ? showOnKeyPress : canShow);
const onKeyDown = _OZM4QA2Vcjs.useEvent.call(void 0, (event) => {
onKeyDownProp == null ? void 0 : onKeyDownProp(event);
if (!event.repeat) {
canAutoSelectRef.current = false;
}
if (event.defaultPrevented) return;
if (event.ctrlKey) return;
if (event.altKey) return;
if (event.shiftKey) return;
if (event.metaKey) return;
if (!store) return;
const { open: open2 } = store.getState();
if (open2) return;
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
if (showOnKeyPressProp(event)) {
event.preventDefault();
store.show();
}
}
});
const onBlurProp = props.onBlur;
const onBlur = _OZM4QA2Vcjs.useEvent.call(void 0, (event) => {
canAutoSelectRef.current = false;
onBlurProp == null ? void 0 : onBlurProp(event);
if (event.defaultPrevented) return;
});
const id = _OZM4QA2Vcjs.useId.call(void 0, props.id);
const ariaAutoComplete = isAriaAutoCompleteValue(autoComplete) ? autoComplete : void 0;
const isActiveItem = store.useState((state) => state.activeId === null);
props = _7EQBAZ46cjs.__spreadProps.call(void 0, _7EQBAZ46cjs.__spreadValues.call(void 0, {
id,
role: "combobox",
"aria-autocomplete": ariaAutoComplete,
"aria-haspopup": _dom.getPopupRole.call(void 0, contentElement, "listbox"),
"aria-expanded": open,
"aria-controls": contentElement == null ? void 0 : contentElement.id,
"data-active-item": isActiveItem || void 0,
value
}, props), {
ref: _OZM4QA2Vcjs.useMergeRefs.call(void 0, ref, props.ref),
onChange,
onCompositionEnd,
onMouseDown,
onKeyDown,
onBlur
});
props = _SIL3OXTPcjs.useComposite.call(void 0, _7EQBAZ46cjs.__spreadProps.call(void 0, _7EQBAZ46cjs.__spreadValues.call(void 0, {
store,
focusable
}, props), {
// Enable inline autocomplete when the user moves from the combobox input
// to an item.
moveOnKeyPress: (event) => {
if (_misc.isFalsyBooleanCallback.call(void 0, moveOnKeyPress, event)) return false;
if (inline) setCanInline(true);
return true;
}
}));
props = _XQNVUUCMcjs.usePopoverAnchor.call(void 0, _7EQBAZ46cjs.__spreadValues.call(void 0, { store }, props));
return _7EQBAZ46cjs.__spreadValues.call(void 0, { autoComplete: "off" }, props);
}
);
var Combobox = _WULEED4Qcjs.forwardRef.call(void 0, function Combobox2(props) {
const htmlProps = useCombobox(props);
return _WULEED4Qcjs.createElement.call(void 0, TagName, htmlProps);
});
exports.Combobox = Combobox; exports.useCombobox = useCombobox;