import { defineComponent, toRefs, ref, watch, nextTick, createBlock, openBlock, unref, withCtx, renderSlot, createCommentVNode } from 'vue'; import { useVModel, createEventHook } from '@vueuse/core'; import { u as useCollection } from '../Collection/Collection.js'; import { c as compare } from './utils.js'; import { g as getFocusIntent } from '../RovingFocus/utils.js'; import '@floating-ui/vue'; import { c as createContext } from '../shared/createContext.js'; import { u as useTypeahead } from '../shared/useTypeahead.js'; import { u as usePrimitiveElement } from '../Primitive/usePrimitiveElement.js'; import { u as useDirection } from '../shared/useDirection.js'; import { u as useFormControl } from '../shared/useFormControl.js'; import { P as Primitive } from '../Primitive/Primitive.js'; import { _ as _sfc_main$1 } from '../VisuallyHidden/VisuallyHiddenInput.js'; import { u as useKbd } from '../shared/useKbd.js'; import { f as findValuesBetween } from '../shared/arrays.js'; const [injectListboxRootContext, provideListboxRootContext] = createContext("ListboxRoot"); const _sfc_main = /* @__PURE__ */ defineComponent({ __name: "ListboxRoot", props: { modelValue: {}, defaultValue: {}, multiple: { type: Boolean }, orientation: { default: "vertical" }, dir: {}, disabled: { type: Boolean }, selectionBehavior: { default: "toggle" }, highlightOnHover: { type: Boolean }, by: {}, asChild: { type: Boolean }, as: {}, name: {}, required: { type: Boolean } }, emits: ["update:modelValue", "highlight", "entryFocus", "leave"], setup(__props, { expose: __expose, emit: __emit }) { const props = __props; const emits = __emit; const { multiple, highlightOnHover, orientation, disabled, selectionBehavior, dir: propDir } = toRefs(props); const { getItems } = useCollection({ isProvider: true }); const { handleTypeaheadSearch } = useTypeahead(); const { primitiveElement, currentElement } = usePrimitiveElement(); const kbd = useKbd(); const dir = useDirection(propDir); const isFormControl = useFormControl(currentElement); const firstValue = ref(); const isUserAction = ref(false); const focusable = ref(true); const modelValue = useVModel(props, "modelValue", emits, { defaultValue: props.defaultValue ?? (multiple.value ? [] : void 0), passive: props.modelValue === void 0, deep: true }); function onValueChange(val) { isUserAction.value = true; if (props.multiple) { const modelArray = Array.isArray(modelValue.value) ? [...modelValue.value] : []; const index = modelArray.findIndex((i) => compare(i, val, props.by)); if (props.selectionBehavior === "toggle") { index === -1 ? modelArray.push(val) : modelArray.splice(index, 1); modelValue.value = modelArray; } else { modelValue.value = [val]; firstValue.value = val; } } else { if (props.selectionBehavior === "toggle") { if (compare(modelValue.value, val, props.by)) modelValue.value = void 0; else modelValue.value = val; } else { modelValue.value = val; } } setTimeout(() => { isUserAction.value = false; }, 1); } const highlightedElement = ref(null); const previousElement = ref(null); const isVirtual = ref(false); const isComposing = ref(false); const virtualFocusHook = createEventHook(); const virtualKeydownHook = createEventHook(); const virtualHighlightHook = createEventHook(); function getCollectionItem() { return getItems().map((i) => i.ref).filter((i) => i.dataset.disabled !== ""); } function changeHighlight(el, scrollIntoView = true) { if (!el) return; highlightedElement.value = el; if (focusable.value) highlightedElement.value.focus(); if (scrollIntoView) highlightedElement.value.scrollIntoView({ block: "nearest" }); const highlightedItem = getItems().find((i) => i.ref === el); emits("highlight", highlightedItem); } function highlightItem(value) { if (isVirtual.value) { virtualHighlightHook.trigger(value); } else { const item = getItems().find((i) => compare(i.value, value, props.by)); if (item) { highlightedElement.value = item.ref; changeHighlight(item.ref); } } } function onKeydownEnter(event) { if (highlightedElement.value && highlightedElement.value.isConnected) { event.preventDefault(); event.stopPropagation(); if (!isComposing.value) { highlightedElement.value.click(); } } } function onKeydownTypeAhead(event) { if (!focusable.value) return; isUserAction.value = true; if (isVirtual.value) { virtualKeydownHook.trigger(event); } else { const isMetaKey = event.altKey || event.ctrlKey || event.metaKey; if (isMetaKey && event.key === "a" && multiple.value) { const collection = getItems(); const values = collection.map((i) => i.value); modelValue.value = [...values]; event.preventDefault(); changeHighlight(collection[collection.length - 1].ref); } else if (!isMetaKey) { const el = handleTypeaheadSearch(event.key, getItems()); if (el) changeHighlight(el); } } setTimeout(() => { isUserAction.value = false; }, 1); } function onCompositionStart() { isComposing.value = true; } function onCompositionEnd() { requestAnimationFrame(() => { isComposing.value = false; }); } function highlightFirstItem() { nextTick(() => { const event = new KeyboardEvent("keydown", { key: "PageUp" }); onKeydownNavigation(event); }); } function onLeave(event) { const el = highlightedElement.value; if (el?.isConnected) { previousElement.value = el; } highlightedElement.value = null; emits("leave", event); } function onEnter(event) { const entryFocusEvent = new CustomEvent("listbox.entryFocus", { bubbles: false, cancelable: true }); event.currentTarget?.dispatchEvent(entryFocusEvent); emits("entryFocus", entryFocusEvent); if (entryFocusEvent.defaultPrevented) return; if (previousElement.value) { changeHighlight(previousElement.value); } else { const el = getCollectionItem()?.[0]; changeHighlight(el); } } function onKeydownNavigation(event) { const intent = getFocusIntent(event, orientation.value, dir.value); if (!intent) return; let collection = getCollectionItem(); if (highlightedElement.value) { if (intent === "last") { collection.reverse(); } else if (intent === "prev" || intent === "next") { if (intent === "prev") collection.reverse(); const currentIndex = collection.indexOf(highlightedElement.value); collection = collection.slice(currentIndex + 1); } handleMultipleReplace(event, collection[0]); } if (collection.length) { const index = !highlightedElement.value && intent === "prev" ? collection.length - 1 : 0; changeHighlight(collection[index]); } if (isVirtual.value) return virtualKeydownHook.trigger(event); } function handleMultipleReplace(event, targetEl) { if (isVirtual.value || props.selectionBehavior !== "replace" || !multiple.value || !Array.isArray(modelValue.value)) return; const isMetaKey = event.altKey || event.ctrlKey || event.metaKey; if (isMetaKey && !event.shiftKey) return; if (event.shiftKey) { const collection = getItems().filter((i) => i.ref.dataset.disabled !== ""); let lastValue = collection.find((i) => i.ref === targetEl)?.value; if (event.key === kbd.END) lastValue = collection[collection.length - 1].value; else if (event.key === kbd.HOME) lastValue = collection[0].value; if (!lastValue || !firstValue.value) return; const values = findValuesBetween(collection.map((i) => i.value), firstValue.value, lastValue); modelValue.value = values; } } async function highlightSelected(event) { await nextTick(); if (isVirtual.value) { virtualFocusHook.trigger(event); } else { const collection = getCollectionItem(); const item = collection.find((i) => i.dataset.state === "checked"); if (item) changeHighlight(item); else if (collection.length) changeHighlight(collection[0]); } } watch(modelValue, () => { if (!isUserAction.value) { nextTick(() => { highlightSelected(); }); } }, { immediate: true, deep: true }); __expose({ highlightedElement, highlightItem, highlightFirstItem, highlightSelected, getItems }); provideListboxRootContext({ modelValue, // @ts-expect-error ignoring onValueChange, multiple, orientation, dir, disabled, highlightOnHover, highlightedElement, isVirtual, virtualFocusHook, virtualKeydownHook, virtualHighlightHook, by: props.by, firstValue, selectionBehavior, focusable, onLeave, onEnter, changeHighlight, onKeydownEnter, onKeydownNavigation, onKeydownTypeAhead, onCompositionStart, onCompositionEnd, highlightFirstItem }); return (_ctx, _cache) => { return openBlock(), createBlock(unref(Primitive), { ref_key: "primitiveElement", ref: primitiveElement, as: _ctx.as, "as-child": _ctx.asChild, dir: unref(dir), "data-disabled": unref(disabled) ? "" : void 0, onPointerleave: onLeave, onFocusout: _cache[0] || (_cache[0] = async (event) => { const target = event.relatedTarget || event.target; await nextTick(); if (highlightedElement.value && unref(currentElement) && !unref(currentElement).contains(target)) { onLeave(event); } }) }, { default: withCtx(() => [ renderSlot(_ctx.$slots, "default", { modelValue: unref(modelValue) }), unref(isFormControl) && _ctx.name ? (openBlock(), createBlock(unref(_sfc_main$1), { key: 0, name: _ctx.name, value: unref(modelValue), disabled: unref(disabled), required: _ctx.required }, null, 8, ["name", "value", "disabled", "required"])) : createCommentVNode("", true) ]), _: 3 }, 8, ["as", "as-child", "dir", "data-disabled"]); }; } }); export { _sfc_main as _, injectListboxRootContext as i }; //# sourceMappingURL=ListboxRoot.js.map