@auronui/vue 1.0.12 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/cjs/index.cjs +785 -711
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/components/autocomplete/Autocomplete.js.map +1 -1
  4. package/dist/components/autocomplete/Autocomplete.vue_vue_type_script_setup_true_lang.js +22 -3
  5. package/dist/components/autocomplete/Autocomplete.vue_vue_type_script_setup_true_lang.js.map +1 -1
  6. package/dist/components/combo-box/ComboBox.js.map +1 -1
  7. package/dist/components/combo-box/ComboBox.vue_vue_type_script_setup_true_lang.js +20 -2
  8. package/dist/components/combo-box/ComboBox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  9. package/dist/components/select/Select.context.js.map +1 -1
  10. package/dist/components/select/Select.js.map +1 -1
  11. package/dist/components/select/Select.vue_vue_type_script_setup_true_lang.js +33 -5
  12. package/dist/components/select/Select.vue_vue_type_script_setup_true_lang.js.map +1 -1
  13. package/dist/components/select/SelectItem.js.map +1 -1
  14. package/dist/components/select/SelectItem.vue_vue_type_script_setup_true_lang.js +2 -2
  15. package/dist/components/select/SelectItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  16. package/dist/components/select/SelectOverflowChips.js.map +1 -1
  17. package/dist/components/select/SelectOverflowChips.vue_vue_type_script_setup_true_lang.js.map +1 -1
  18. package/dist/components/select/SelectValue.js.map +1 -1
  19. package/dist/components/select/SelectValue.vue_vue_type_script_setup_true_lang.js +1 -1
  20. package/dist/components/select/SelectValue.vue_vue_type_script_setup_true_lang.js.map +1 -1
  21. package/dist/index.d.ts +49 -20
  22. package/dist/index.js +3 -3
  23. package/dist/utils/hasSlotComponent.js +26 -0
  24. package/dist/utils/hasSlotComponent.js.map +1 -0
  25. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"Autocomplete.js","names":[],"sources":["../../../src/components/autocomplete/Autocomplete.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, ref, toRef, useAttrs, useId, watch } from 'vue'\nimport { AutocompleteRoot } from 'reka-ui'\nimport { autocompleteVariants, type AutocompleteVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useAutocompleteProvide } from './Autocomplete.context'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'flat',\n size: 'md',\n color: 'default',\n labelPlacement: 'inside',\n fullWidth: false,\n isInvalid: false,\n isDisabled: false,\n isReadonly: false,\n isRequired: false,\n multiple: false,\n multipleOverflow: 'wrap',\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n loadItems: undefined,\n debounceMs: 200,\n filterOnOpen: false,\n truncateItems: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | string[]]\n 'update:open': [value: boolean]\n /** Fired when the user creates a new value via `creatable` or `<AutocompleteCreateItem>`. */\n 'create': [value: string]\n}>()\n\nexport interface AutocompleteItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\ntype Props = {\n /** Visual style of the field. @default 'flat' */\n variant?: AutocompleteVariants['variant']\n /** Field height. @default 'md' */\n size?: AutocompleteVariants['size']\n /** Accent color applied to focus ring + floating label. @default 'default' */\n color?: AutocompleteVariants['color']\n /**\n * Where the `label` is rendered relative to the field.\n * - `inside`: floats above the trigger (shrinks when focused/filled)\n * - `outside`: sits above the field, static\n * - `outside-left`: sits to the left, horizontal layout\n * @default 'inside'\n */\n labelPlacement?: AutocompleteVariants['labelPlacement']\n /** Stretches root wrapper to 100% width. @default false */\n fullWidth?: boolean\n /** Marks the field as invalid. Triggers danger styling and enables `errorMessage`. @default false */\n isInvalid?: boolean\n /** Disables the field. @default false */\n isDisabled?: boolean\n /** Makes the field read-only. @default false */\n isReadonly?: boolean\n /** Adds a required asterisk next to the label. @default false */\n isRequired?: boolean\n /** Placeholder shown when empty. */\n placeholder?: string\n /** Form field name, for native form submission. */\n name?: string\n /** Field label. When omitted, the floating-label behavior is skipped. */\n label?: string\n /** Helper text displayed below the field. Suppressed when `isInvalid && errorMessage` is shown. */\n description?: string\n /** Error text displayed below the field. Only rendered when `isInvalid` is also true. */\n errorMessage?: string\n /** Extra classes merged onto the root wrapper via `composeClassName`. */\n class?: string\n\n /* ─── Autocomplete-specific ─────────────────────────────────────── */\n /** Two-way bound selected value. string in single mode, string[] in multiple mode. */\n modelValue?: string | string[]\n /** Initial selected value (uncontrolled). */\n defaultValue?: string | string[]\n /** Allow selecting multiple values. modelValue becomes string[]. @default false */\n multiple?: boolean\n /**\n * Controls how chips overflow in multiple mode.\n * - `wrap`: trigger grows in height, chips wrap to new lines (default)\n * - `collapse`: fixed height, overflowing chips are hidden behind \"+N more\"\n * @default 'wrap'\n */\n multipleOverflow?: 'wrap' | 'collapse'\n /** Controls open state of the dropdown. */\n open?: boolean\n /** Initial open state of the dropdown (uncontrolled). */\n defaultOpen?: boolean\n /** Static items list — used when no loadItems is provided. */\n items?: AutocompleteItem[]\n /** Async data source: called on every query change. */\n loadItems?: (query: string) => Promise<AutocompleteItem[]>\n /** Debounce delay for loadItems calls (ms). 0 = no debounce. */\n debounceMs?: number\n /** Apply filter immediately on open (default: false — show all items until user types). */\n filterOnOpen?: boolean\n /**\n * Truncate item text with an ellipsis when it overflows the dropdown width.\n * Set to `false` to show full text — the dropdown will widen to fit.\n * @default true\n */\n truncateItems?: boolean\n}\n\nconst attrs = useAttrs()\nconst generatedId = useId()\nconst inputId = computed(() => (attrs.id as string | undefined) ?? generatedId)\n\nconst hasLabel = computed(() => !!props.label)\n\n// Registry for slot-rendered items: value → label (populated by AutocompleteItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Internal async state\nconst isLoading = ref(false)\nconst internalItems = ref<AutocompleteItem[]>([...props.items])\n\n// ── Multiple-mode state ────────────────────────────────────────────────────\n// Tracks selected values as an array. Only meaningful when props.multiple=true.\nconst selectedValues = ref<string[]>(\n props.multiple && Array.isArray(props.modelValue) ? [...props.modelValue] : [],\n)\n\n// Controlled open state used in multiple mode to prevent the dropdown closing\n// after each item selection (Reka would normally close on selection).\nconst internalOpen = ref(props.defaultOpen ?? false)\n\n// Flag set by onMultipleSelect so handleOpenChange can distinguish item\n// selection from Escape/outside-click close.\nlet selectingItem = false\n\n// ── Open-state tracking ────────────────────────────────────────────────────\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\n// Controlled open state for single mode (mirrors multiple mode's internalOpen).\n// Driving open ourselves lets handleOpenChange gate spurious reopens — e.g. the\n// focus bounce caused when a create handler mutates the items list.\nconst singleOpen = ref(props.open ?? props.defaultOpen ?? false)\n// When true, handleOpenChange ignores open=true requests. Set briefly after a\n// single-mode create so the post-create re-render can't reopen the menu.\nlet blockReopen = false\nlet blockReopenTimer: ReturnType<typeof setTimeout> | undefined\nconst effectiveIgnoreFilter = computed(() => {\n if (props.loadItems) return true\n if (!props.filterOnOpen && isOpen.value && !isUserTyping.value) return true\n return false\n})\n\n// ── Label/value bridge ─────────────────────────────────────────────────────\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (value == null || value === '') return ''\n const match = internalItems.value.find((i) => i.value === value)\n if (match) return match.label ?? match.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\nfunction valueFor(displayed: string): string {\n if (!displayed) return ''\n const match = internalItems.value.find(\n (i) => (i.label ?? i.textValue ?? i.value) === displayed,\n )\n if (match) return match.value\n for (const [value, label] of slotItemRegistry.value) {\n if (label === displayed) return value\n }\n return displayed\n}\n\nconst singleModelValue = computed(() =>\n props.multiple ? undefined : (props.modelValue as string | undefined),\n)\n\nconst searchTerm = ref(labelFor(singleModelValue.value))\n\nconst isFilled = computed(() =>\n props.multiple\n ? selectedValues.value.length > 0 || !!searchTerm.value\n : !!searchTerm.value,\n)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\nconst selectedLabels = computed(() =>\n selectedValues.value.map(v => ({ value: v, label: labelFor(v) || v })),\n)\n\n// ── Helpers ────────────────────────────────────────────────────────────────\nconst descriptionId = computed(() => `${inputId.value}-description`)\nconst errorMessageId = computed(() => `${inputId.value}-error`)\nconst showError = computed(() => props.isInvalid && !!props.errorMessage)\nconst showDescription = computed(() => !!props.description && !showError.value)\nconst hasHelper = computed(() => showError.value || showDescription.value)\nconst ariaDescribedBy = computed(() => {\n if (showError.value) return errorMessageId.value\n if (showDescription.value) return descriptionId.value\n return undefined\n})\n\n// ── Watchers ───────────────────────────────────────────────────────────────\n\n// Parent → internal: sync controlled modelValue into local state\nwatch(() => props.modelValue, (val) => {\n if (props.multiple) {\n if (Array.isArray(val)) selectedValues.value = [...val]\n } else {\n const next = labelFor(val as string | undefined)\n if (searchTerm.value !== next) searchTerm.value = next\n }\n})\n\n// Parent → internal: sync consumer-controlled open into our single-mode state\nwatch(() => props.open, (val) => {\n if (!props.multiple && val !== undefined) singleOpen.value = val\n})\n\n// Internal → parent: single mode only — multiple mode emits inside onMultipleSelect\nwatch(searchTerm, (displayed) => {\n if (props.multiple) {\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n return\n }\n const next = valueFor(displayed)\n if (next !== (singleModelValue.value ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n})\n\nfunction handleOpenChange(val: boolean) {\n if (props.multiple) {\n isOpen.value = val\n // Suppress close when triggered by item selection; allow Escape/outside-click\n internalOpen.value = (!val && selectingItem) ? true : val\n selectingItem = false\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n return\n }\n\n // Single mode: ignore spurious reopen requests right after a create — the\n // create handler's re-render bounces focus back to the input, which would\n // otherwise reopen the menu via openOnFocus.\n if (val && blockReopen) return\n\n isOpen.value = val\n singleOpen.value = val\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n}\n\n// ── Multiple-mode actions ──────────────────────────────────────────────────\n\nfunction onMultipleSelect(value: string) {\n selectingItem = true\n const idx = selectedValues.value.indexOf(value)\n selectedValues.value = idx === -1\n ? [...selectedValues.value, value]\n : selectedValues.value.filter((_, i) => i !== idx)\n // Clear the input and drop out of \"typing\" mode so the filter is ignored and\n // the full list shows again (effectiveIgnoreFilter depends on !isUserTyping).\n searchTerm.value = ''\n isUserTyping.value = false\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction removeValue(value: string) {\n selectedValues.value = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction clearAll() {\n selectedValues.value = []\n emit('update:modelValue', [])\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\n// ── Creatable ──────────────────────────────────────────────────────────────\n\nconst hasExactMatch = computed(() => {\n const term = searchTerm.value.trim().toLowerCase()\n if (!term) return false\n const inItems = internalItems.value.some(\n i => (i.label ?? i.textValue ?? i.value).toLowerCase() === term,\n )\n if (inItems) return true\n for (const label of slotItemRegistry.value.values()) {\n if (label.toLowerCase() === term) return true\n }\n return false\n})\n\nfunction onCreateValue(value: string) {\n const trimmed = value.trim()\n if (!trimmed) return\n if (props.multiple) {\n // We own selection state; add the value, then reset input + filter for the\n // next entry (dropdown is kept open and refocused by AutocompleteCreateItem).\n if (!selectedValues.value.includes(trimmed)) {\n selectingItem = true\n selectedValues.value = [...selectedValues.value, trimmed]\n emit('update:modelValue', selectedValues.value)\n }\n searchTerm.value = ''\n isUserTyping.value = false\n } else {\n // Single mode: set the value ourselves and close the (controlled) dropdown.\n searchTerm.value = trimmed\n emit('update:modelValue', trimmed)\n isOpen.value = false\n singleOpen.value = false\n // The create handler (parent @create) typically mutates the items list, which\n // re-renders and bounces focus back to the input — that fires openOnFocus and\n // would reopen the menu. Block reopen requests briefly so the close sticks.\n blockReopen = true\n clearTimeout(blockReopenTimer)\n blockReopenTimer = setTimeout(() => { blockReopen = false }, 300)\n }\n emit('create', trimmed)\n}\n\n// ── Async loading ──────────────────────────────────────────────────────────\nlet debounceTimer: ReturnType<typeof setTimeout> | undefined\n\nasync function runLoadItems(query: string) {\n if (!props.loadItems) return\n isLoading.value = true\n try {\n internalItems.value = await props.loadItems(query)\n } finally {\n isLoading.value = false\n }\n}\n\nfunction scheduleLoad(query: string) {\n if (!props.loadItems) return\n clearTimeout(debounceTimer)\n if (props.debounceMs === 0) {\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\nonMounted(() => {\n if (props.loadItems) void runLoadItems(searchTerm.value)\n})\n\nwatch(searchTerm, (q) => {\n if (props.loadItems) scheduleLoad(q)\n})\n\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) internalItems.value = [...newItems]\n})\n\nwatch(internalItems, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\nwatch(slotItemRegistry, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\n// ── Styles / context ───────────────────────────────────────────────────────\n\nconst slotFns = computed(() =>\n autocompleteVariants({\n variant: props.variant,\n size: props.size,\n color: props.color,\n fullWidth: props.fullWidth,\n isInvalid: props.isInvalid,\n isDisabled: props.isDisabled,\n isReadonly: props.isReadonly,\n hasLabel: hasLabel.value,\n labelPlacement: props.labelPlacement,\n }),\n)\n\nconst showOutsideLabel = computed(\n () => hasLabel.value && props.labelPlacement !== 'inside',\n)\n\nuseAutocompleteProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n isReadonly: toRef(props, 'isReadonly'),\n isRequired: toRef(props, 'isRequired'),\n isLoading,\n isFilled,\n fullWidth: toRef(props, 'fullWidth'),\n hasLabel,\n labelPlacement: toRef(props, 'labelPlacement'),\n inputId,\n label: toRef(props, 'label'),\n ariaDescribedBy,\n truncateItems: toRef(props, 'truncateItems'),\n hasItems,\n slots: slotFns,\n multiple: toRef(props, 'multiple'),\n multipleOverflow: toRef(props, 'multipleOverflow'),\n selectedValues,\n selectedLabels,\n onMultipleSelect,\n removeValue,\n clearAll,\n isSelected,\n registerItem,\n unregisterItem,\n searchTerm,\n hasExactMatch,\n onCreateValue,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :data-invalid=\"isInvalid || undefined\"\n :data-disabled=\"isDisabled || undefined\"\n :data-readonly=\"isReadonly || undefined\"\n :data-required=\"isRequired || undefined\"\n :data-has-label=\"hasLabel || undefined\"\n :data-has-helper=\"hasHelper || undefined\"\n >\n <label\n v-if=\"showOutsideLabel\"\n :for=\"inputId\"\n :class=\"slotFns.label()\"\n >{{ label }}<span\n v-if=\"isRequired\"\n aria-hidden=\"true\"\n > *</span></label>\n\n <div :class=\"slotFns.mainWrapper()\">\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.multiple ? internalOpen : singleOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :ignore-filter=\"effectiveIgnoreFilter\"\n :open-on-focus=\"true\"\n @update:open=\"handleOpenChange\"\n >\n <slot\n :is-loading=\"isLoading\"\n :items=\"internalItems\"\n />\n </AutocompleteRoot>\n\n <div\n v-if=\"hasHelper\"\n :class=\"slotFns.helperWrapper()\"\n >\n <div\n v-if=\"showError\"\n :id=\"errorMessageId\"\n :class=\"slotFns.errorMessage()\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"showDescription\"\n :id=\"descriptionId\"\n :class=\"slotFns.description()\"\n >\n {{ description }}\n </div>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"Autocomplete.js","names":[],"sources":["../../../src/components/autocomplete/Autocomplete.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, ref, toRef, useAttrs, useId, watch, useSlots } from 'vue'\nimport { AutocompleteRoot } from 'reka-ui'\nimport { autocompleteVariants, type AutocompleteVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useAutocompleteProvide } from './Autocomplete.context'\nimport { hasSlotComponent } from '../../utils/hasSlotComponent'\nimport AutocompleteInput from './AutocompleteInput.vue'\nimport AutocompleteContent from './AutocompleteContent.vue'\nimport AutocompleteItem from './AutocompleteItem.vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'flat',\n size: 'md',\n color: 'default',\n labelPlacement: 'inside',\n fullWidth: false,\n isInvalid: false,\n isDisabled: false,\n isReadonly: false,\n isRequired: false,\n multiple: false,\n multipleOverflow: 'wrap',\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n loadItems: undefined,\n debounceMs: 200,\n filterOnOpen: false,\n truncateItems: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | string[]]\n 'update:open': [value: boolean]\n /** Fired when the user creates a new value via `creatable` or `<AutocompleteCreateItem>`. */\n 'create': [value: string]\n}>()\n\nexport interface AutocompleteItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\ntype Props = {\n /** Visual style of the field. @default 'flat' */\n variant?: AutocompleteVariants['variant']\n /** Field height. @default 'md' */\n size?: AutocompleteVariants['size']\n /** Accent color applied to focus ring + floating label. @default 'default' */\n color?: AutocompleteVariants['color']\n /**\n * Where the `label` is rendered relative to the field.\n * - `inside`: floats above the trigger (shrinks when focused/filled)\n * - `outside`: sits above the field, static\n * - `outside-left`: sits to the left, horizontal layout\n * @default 'inside'\n */\n labelPlacement?: AutocompleteVariants['labelPlacement']\n /** Stretches root wrapper to 100% width. @default false */\n fullWidth?: boolean\n /** Marks the field as invalid. Triggers danger styling and enables `errorMessage`. @default false */\n isInvalid?: boolean\n /** Disables the field. @default false */\n isDisabled?: boolean\n /** Makes the field read-only. @default false */\n isReadonly?: boolean\n /** Adds a required asterisk next to the label. @default false */\n isRequired?: boolean\n /** Placeholder shown when empty. */\n placeholder?: string\n /** Form field name, for native form submission. */\n name?: string\n /** Field label. When omitted, the floating-label behavior is skipped. */\n label?: string\n /** Helper text displayed below the field. Suppressed when `isInvalid && errorMessage` is shown. */\n description?: string\n /** Error text displayed below the field. Only rendered when `isInvalid` is also true. */\n errorMessage?: string\n /** Extra classes merged onto the root wrapper via `composeClassName`. */\n class?: string\n\n /* ─── Autocomplete-specific ─────────────────────────────────────── */\n /** Two-way bound selected value. string in single mode, string[] in multiple mode. */\n modelValue?: string | string[]\n /** Initial selected value (uncontrolled). */\n defaultValue?: string | string[]\n /** Allow selecting multiple values. modelValue becomes string[]. @default false */\n multiple?: boolean\n /**\n * Controls how chips overflow in multiple mode.\n * - `wrap`: trigger grows in height, chips wrap to new lines (default)\n * - `collapse`: fixed height, overflowing chips are hidden behind \"+N more\"\n * @default 'wrap'\n */\n multipleOverflow?: 'wrap' | 'collapse'\n /** Controls open state of the dropdown. */\n open?: boolean\n /** Initial open state of the dropdown (uncontrolled). */\n defaultOpen?: boolean\n /** Static items list — used when no loadItems is provided. */\n items?: AutocompleteItem[]\n /** Async data source: called on every query change. */\n loadItems?: (query: string) => Promise<AutocompleteItem[]>\n /** Debounce delay for loadItems calls (ms). 0 = no debounce. */\n debounceMs?: number\n /** Apply filter immediately on open (default: false — show all items until user types). */\n filterOnOpen?: boolean\n /**\n * Truncate item text with an ellipsis when it overflows the dropdown width.\n * Set to `false` to show full text — the dropdown will widen to fit.\n * @default true\n */\n truncateItems?: boolean\n}\n\nconst attrs = useAttrs()\nconst generatedId = useId()\nconst inputId = computed(() => (attrs.id as string | undefined) ?? generatedId)\n\nconst hasLabel = computed(() => !!props.label)\n\nconst slots = useSlots()\n// Compound chrome present → pass slot through (advanced). Otherwise render the\n// input/content/items internally (short-form).\nconst usesCustomChrome = computed(() =>\n hasSlotComponent(slots.default?.(), [AutocompleteInput, AutocompleteContent]),\n)\n\n// Registry for slot-rendered items: value → label (populated by AutocompleteItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Internal async state\nconst isLoading = ref(false)\nconst internalItems = ref<AutocompleteItem[]>([...props.items])\n\n// ── Multiple-mode state ────────────────────────────────────────────────────\n// Tracks selected values as an array. Only meaningful when props.multiple=true.\nconst selectedValues = ref<string[]>(\n props.multiple && Array.isArray(props.modelValue) ? [...props.modelValue] : [],\n)\n\n// Controlled open state used in multiple mode to prevent the dropdown closing\n// after each item selection (Reka would normally close on selection).\nconst internalOpen = ref(props.defaultOpen ?? false)\n\n// Flag set by onMultipleSelect so handleOpenChange can distinguish item\n// selection from Escape/outside-click close.\nlet selectingItem = false\n\n// ── Open-state tracking ────────────────────────────────────────────────────\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\n// Controlled open state for single mode (mirrors multiple mode's internalOpen).\n// Driving open ourselves lets handleOpenChange gate spurious reopens — e.g. the\n// focus bounce caused when a create handler mutates the items list.\nconst singleOpen = ref(props.open ?? props.defaultOpen ?? false)\n// When true, handleOpenChange ignores open=true requests. Set briefly after a\n// single-mode create so the post-create re-render can't reopen the menu.\nlet blockReopen = false\nlet blockReopenTimer: ReturnType<typeof setTimeout> | undefined\nconst effectiveIgnoreFilter = computed(() => {\n if (props.loadItems) return true\n if (!props.filterOnOpen && isOpen.value && !isUserTyping.value) return true\n return false\n})\n\n// ── Label/value bridge ─────────────────────────────────────────────────────\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (value == null || value === '') return ''\n const match = internalItems.value.find((i) => i.value === value)\n if (match) return match.label ?? match.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\nfunction valueFor(displayed: string): string {\n if (!displayed) return ''\n const match = internalItems.value.find(\n (i) => (i.label ?? i.textValue ?? i.value) === displayed,\n )\n if (match) return match.value\n for (const [value, label] of slotItemRegistry.value) {\n if (label === displayed) return value\n }\n return displayed\n}\n\nconst singleModelValue = computed(() =>\n props.multiple ? undefined : (props.modelValue as string | undefined),\n)\n\nconst searchTerm = ref(labelFor(singleModelValue.value))\n\nconst isFilled = computed(() =>\n props.multiple\n ? selectedValues.value.length > 0 || !!searchTerm.value\n : !!searchTerm.value,\n)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\nconst selectedLabels = computed(() =>\n selectedValues.value.map(v => ({ value: v, label: labelFor(v) || v })),\n)\n\n// ── Helpers ────────────────────────────────────────────────────────────────\nconst descriptionId = computed(() => `${inputId.value}-description`)\nconst errorMessageId = computed(() => `${inputId.value}-error`)\nconst showError = computed(() => props.isInvalid && !!props.errorMessage)\nconst showDescription = computed(() => !!props.description && !showError.value)\nconst hasHelper = computed(() => showError.value || showDescription.value)\nconst ariaDescribedBy = computed(() => {\n if (showError.value) return errorMessageId.value\n if (showDescription.value) return descriptionId.value\n return undefined\n})\n\n// ── Watchers ───────────────────────────────────────────────────────────────\n\n// Parent → internal: sync controlled modelValue into local state\nwatch(() => props.modelValue, (val) => {\n if (props.multiple) {\n if (Array.isArray(val)) selectedValues.value = [...val]\n } else {\n const next = labelFor(val as string | undefined)\n if (searchTerm.value !== next) searchTerm.value = next\n }\n})\n\n// Parent → internal: sync consumer-controlled open into our single-mode state\nwatch(() => props.open, (val) => {\n if (!props.multiple && val !== undefined) singleOpen.value = val\n})\n\n// Internal → parent: single mode only — multiple mode emits inside onMultipleSelect\nwatch(searchTerm, (displayed) => {\n if (props.multiple) {\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n return\n }\n const next = valueFor(displayed)\n if (next !== (singleModelValue.value ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n})\n\nfunction handleOpenChange(val: boolean) {\n if (props.multiple) {\n isOpen.value = val\n // Suppress close when triggered by item selection; allow Escape/outside-click\n internalOpen.value = (!val && selectingItem) ? true : val\n selectingItem = false\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n return\n }\n\n // Single mode: ignore spurious reopen requests right after a create — the\n // create handler's re-render bounces focus back to the input, which would\n // otherwise reopen the menu via openOnFocus.\n if (val && blockReopen) return\n\n isOpen.value = val\n singleOpen.value = val\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n}\n\n// ── Multiple-mode actions ──────────────────────────────────────────────────\n\nfunction onMultipleSelect(value: string) {\n selectingItem = true\n const idx = selectedValues.value.indexOf(value)\n selectedValues.value = idx === -1\n ? [...selectedValues.value, value]\n : selectedValues.value.filter((_, i) => i !== idx)\n // Clear the input and drop out of \"typing\" mode so the filter is ignored and\n // the full list shows again (effectiveIgnoreFilter depends on !isUserTyping).\n searchTerm.value = ''\n isUserTyping.value = false\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction removeValue(value: string) {\n selectedValues.value = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction clearAll() {\n selectedValues.value = []\n emit('update:modelValue', [])\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\n// ── Creatable ──────────────────────────────────────────────────────────────\n\nconst hasExactMatch = computed(() => {\n const term = searchTerm.value.trim().toLowerCase()\n if (!term) return false\n const inItems = internalItems.value.some(\n i => (i.label ?? i.textValue ?? i.value).toLowerCase() === term,\n )\n if (inItems) return true\n for (const label of slotItemRegistry.value.values()) {\n if (label.toLowerCase() === term) return true\n }\n return false\n})\n\nfunction onCreateValue(value: string) {\n const trimmed = value.trim()\n if (!trimmed) return\n if (props.multiple) {\n // We own selection state; add the value, then reset input + filter for the\n // next entry (dropdown is kept open and refocused by AutocompleteCreateItem).\n if (!selectedValues.value.includes(trimmed)) {\n selectingItem = true\n selectedValues.value = [...selectedValues.value, trimmed]\n emit('update:modelValue', selectedValues.value)\n }\n searchTerm.value = ''\n isUserTyping.value = false\n } else {\n // Single mode: set the value ourselves and close the (controlled) dropdown.\n searchTerm.value = trimmed\n emit('update:modelValue', trimmed)\n isOpen.value = false\n singleOpen.value = false\n // The create handler (parent @create) typically mutates the items list, which\n // re-renders and bounces focus back to the input — that fires openOnFocus and\n // would reopen the menu. Block reopen requests briefly so the close sticks.\n blockReopen = true\n clearTimeout(blockReopenTimer)\n blockReopenTimer = setTimeout(() => { blockReopen = false }, 300)\n }\n emit('create', trimmed)\n}\n\n// ── Async loading ──────────────────────────────────────────────────────────\nlet debounceTimer: ReturnType<typeof setTimeout> | undefined\n\nasync function runLoadItems(query: string) {\n if (!props.loadItems) return\n isLoading.value = true\n try {\n internalItems.value = await props.loadItems(query)\n } finally {\n isLoading.value = false\n }\n}\n\nfunction scheduleLoad(query: string) {\n if (!props.loadItems) return\n clearTimeout(debounceTimer)\n if (props.debounceMs === 0) {\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\nonMounted(() => {\n if (props.loadItems) void runLoadItems(searchTerm.value)\n})\n\nwatch(searchTerm, (q) => {\n if (props.loadItems) scheduleLoad(q)\n})\n\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) internalItems.value = [...newItems]\n})\n\nwatch(internalItems, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\nwatch(slotItemRegistry, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\n// ── Styles / context ───────────────────────────────────────────────────────\n\nconst slotFns = computed(() =>\n autocompleteVariants({\n variant: props.variant,\n size: props.size,\n color: props.color,\n fullWidth: props.fullWidth,\n isInvalid: props.isInvalid,\n isDisabled: props.isDisabled,\n isReadonly: props.isReadonly,\n hasLabel: hasLabel.value,\n labelPlacement: props.labelPlacement,\n }),\n)\n\nconst showOutsideLabel = computed(\n () => hasLabel.value && props.labelPlacement !== 'inside',\n)\n\nuseAutocompleteProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n isReadonly: toRef(props, 'isReadonly'),\n isRequired: toRef(props, 'isRequired'),\n isLoading,\n isFilled,\n fullWidth: toRef(props, 'fullWidth'),\n hasLabel,\n labelPlacement: toRef(props, 'labelPlacement'),\n inputId,\n label: toRef(props, 'label'),\n ariaDescribedBy,\n truncateItems: toRef(props, 'truncateItems'),\n hasItems,\n slots: slotFns,\n multiple: toRef(props, 'multiple'),\n multipleOverflow: toRef(props, 'multipleOverflow'),\n selectedValues,\n selectedLabels,\n onMultipleSelect,\n removeValue,\n clearAll,\n isSelected,\n registerItem,\n unregisterItem,\n searchTerm,\n hasExactMatch,\n onCreateValue,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :data-invalid=\"isInvalid || undefined\"\n :data-disabled=\"isDisabled || undefined\"\n :data-readonly=\"isReadonly || undefined\"\n :data-required=\"isRequired || undefined\"\n :data-has-label=\"hasLabel || undefined\"\n :data-has-helper=\"hasHelper || undefined\"\n >\n <label\n v-if=\"showOutsideLabel\"\n :for=\"inputId\"\n :class=\"slotFns.label()\"\n >{{ label }}<span\n v-if=\"isRequired\"\n aria-hidden=\"true\"\n > *</span></label>\n\n <div :class=\"slotFns.mainWrapper()\">\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.multiple ? internalOpen : singleOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :ignore-filter=\"effectiveIgnoreFilter\"\n :open-on-focus=\"true\"\n @update:open=\"handleOpenChange\"\n >\n <slot\n v-if=\"usesCustomChrome\"\n :is-loading=\"isLoading\"\n :items=\"internalItems\"\n />\n <template v-else>\n <AutocompleteInput :placeholder=\"props.placeholder\" />\n <AutocompleteContent>\n <AutocompleteItem\n v-for=\"item in internalItems\"\n :key=\"item.value\"\n :value=\"item.value\"\n :is-disabled=\"item.isDisabled\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n >{{ item.label ?? item.textValue ?? item.value }}</slot>\n </AutocompleteItem>\n </AutocompleteContent>\n </template>\n </AutocompleteRoot>\n\n <div\n v-if=\"hasHelper\"\n :class=\"slotFns.helperWrapper()\"\n >\n <div\n v-if=\"showError\"\n :id=\"errorMessageId\"\n :class=\"slotFns.errorMessage()\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"showDescription\"\n :id=\"descriptionId\"\n :class=\"slotFns.description()\"\n >\n {{ description }}\n </div>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -1,6 +1,10 @@
1
1
  import { composeClassName } from "../../utils/composeClassName.js";
2
+ import { hasSlotComponent } from "../../utils/hasSlotComponent.js";
2
3
  import { useAutocompleteProvide } from "./Autocomplete.context.js";
3
- import { computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, onMounted, openBlock, ref, renderSlot, toDisplayString, toRef, unref, useAttrs, useId, watch, withCtx } from "vue";
4
+ import AutocompleteInput_default from "./AutocompleteInput.js";
5
+ import AutocompleteContent_default from "./AutocompleteContent.js";
6
+ import AutocompleteItem_default from "./AutocompleteItem.js";
7
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, onMounted, openBlock, ref, renderList, renderSlot, toDisplayString, toRef, unref, useAttrs, useId, useSlots, watch, withCtx } from "vue";
4
8
  import { autocompleteVariants } from "@auronui/styles";
5
9
  import { AutocompleteRoot } from "reka-ui";
6
10
  //#region src/components/autocomplete/Autocomplete.vue?vue&type=script&setup=true&lang.ts
@@ -95,6 +99,8 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
95
99
  const generatedId = useId();
96
100
  const inputId = computed(() => attrs.id ?? generatedId);
97
101
  const hasLabel = computed(() => !!props.label);
102
+ const slots = useSlots();
103
+ const usesCustomChrome = computed(() => hasSlotComponent(slots.default?.(), [AutocompleteInput_default, AutocompleteContent_default]));
98
104
  const slotItemRegistry = ref(/* @__PURE__ */ new Map());
99
105
  function registerItem(value, label) {
100
106
  const next = new Map(slotItemRegistry.value);
@@ -343,10 +349,23 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
343
349
  "open-on-focus": true,
344
350
  "onUpdate:open": handleOpenChange
345
351
  }, {
346
- default: withCtx(() => [renderSlot(_ctx.$slots, "default", {
352
+ default: withCtx(() => [usesCustomChrome.value ? renderSlot(_ctx.$slots, "default", {
353
+ key: 0,
347
354
  isLoading: isLoading.value,
348
355
  items: internalItems.value
349
- })]),
356
+ }) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createVNode(AutocompleteInput_default, { placeholder: props.placeholder }, null, 8, ["placeholder"]), createVNode(AutocompleteContent_default, null, {
357
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(internalItems.value, (item) => {
358
+ return openBlock(), createBlock(AutocompleteItem_default, {
359
+ key: item.value,
360
+ value: item.value,
361
+ "is-disabled": item.isDisabled
362
+ }, {
363
+ default: withCtx(() => [renderSlot(_ctx.$slots, "item", { item }, () => [createTextVNode(toDisplayString(item.label ?? item.textValue ?? item.value), 1)])]),
364
+ _: 2
365
+ }, 1032, ["value", "is-disabled"]);
366
+ }), 128))]),
367
+ _: 3
368
+ })], 64))]),
350
369
  _: 3
351
370
  }, 8, [
352
371
  "model-value",
@@ -1 +1 @@
1
- {"version":3,"file":"Autocomplete.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/autocomplete/Autocomplete.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, ref, toRef, useAttrs, useId, watch } from 'vue'\nimport { AutocompleteRoot } from 'reka-ui'\nimport { autocompleteVariants, type AutocompleteVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useAutocompleteProvide } from './Autocomplete.context'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'flat',\n size: 'md',\n color: 'default',\n labelPlacement: 'inside',\n fullWidth: false,\n isInvalid: false,\n isDisabled: false,\n isReadonly: false,\n isRequired: false,\n multiple: false,\n multipleOverflow: 'wrap',\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n loadItems: undefined,\n debounceMs: 200,\n filterOnOpen: false,\n truncateItems: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | string[]]\n 'update:open': [value: boolean]\n /** Fired when the user creates a new value via `creatable` or `<AutocompleteCreateItem>`. */\n 'create': [value: string]\n}>()\n\nexport interface AutocompleteItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\ntype Props = {\n /** Visual style of the field. @default 'flat' */\n variant?: AutocompleteVariants['variant']\n /** Field height. @default 'md' */\n size?: AutocompleteVariants['size']\n /** Accent color applied to focus ring + floating label. @default 'default' */\n color?: AutocompleteVariants['color']\n /**\n * Where the `label` is rendered relative to the field.\n * - `inside`: floats above the trigger (shrinks when focused/filled)\n * - `outside`: sits above the field, static\n * - `outside-left`: sits to the left, horizontal layout\n * @default 'inside'\n */\n labelPlacement?: AutocompleteVariants['labelPlacement']\n /** Stretches root wrapper to 100% width. @default false */\n fullWidth?: boolean\n /** Marks the field as invalid. Triggers danger styling and enables `errorMessage`. @default false */\n isInvalid?: boolean\n /** Disables the field. @default false */\n isDisabled?: boolean\n /** Makes the field read-only. @default false */\n isReadonly?: boolean\n /** Adds a required asterisk next to the label. @default false */\n isRequired?: boolean\n /** Placeholder shown when empty. */\n placeholder?: string\n /** Form field name, for native form submission. */\n name?: string\n /** Field label. When omitted, the floating-label behavior is skipped. */\n label?: string\n /** Helper text displayed below the field. Suppressed when `isInvalid && errorMessage` is shown. */\n description?: string\n /** Error text displayed below the field. Only rendered when `isInvalid` is also true. */\n errorMessage?: string\n /** Extra classes merged onto the root wrapper via `composeClassName`. */\n class?: string\n\n /* ─── Autocomplete-specific ─────────────────────────────────────── */\n /** Two-way bound selected value. string in single mode, string[] in multiple mode. */\n modelValue?: string | string[]\n /** Initial selected value (uncontrolled). */\n defaultValue?: string | string[]\n /** Allow selecting multiple values. modelValue becomes string[]. @default false */\n multiple?: boolean\n /**\n * Controls how chips overflow in multiple mode.\n * - `wrap`: trigger grows in height, chips wrap to new lines (default)\n * - `collapse`: fixed height, overflowing chips are hidden behind \"+N more\"\n * @default 'wrap'\n */\n multipleOverflow?: 'wrap' | 'collapse'\n /** Controls open state of the dropdown. */\n open?: boolean\n /** Initial open state of the dropdown (uncontrolled). */\n defaultOpen?: boolean\n /** Static items list — used when no loadItems is provided. */\n items?: AutocompleteItem[]\n /** Async data source: called on every query change. */\n loadItems?: (query: string) => Promise<AutocompleteItem[]>\n /** Debounce delay for loadItems calls (ms). 0 = no debounce. */\n debounceMs?: number\n /** Apply filter immediately on open (default: false — show all items until user types). */\n filterOnOpen?: boolean\n /**\n * Truncate item text with an ellipsis when it overflows the dropdown width.\n * Set to `false` to show full text — the dropdown will widen to fit.\n * @default true\n */\n truncateItems?: boolean\n}\n\nconst attrs = useAttrs()\nconst generatedId = useId()\nconst inputId = computed(() => (attrs.id as string | undefined) ?? generatedId)\n\nconst hasLabel = computed(() => !!props.label)\n\n// Registry for slot-rendered items: value → label (populated by AutocompleteItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Internal async state\nconst isLoading = ref(false)\nconst internalItems = ref<AutocompleteItem[]>([...props.items])\n\n// ── Multiple-mode state ────────────────────────────────────────────────────\n// Tracks selected values as an array. Only meaningful when props.multiple=true.\nconst selectedValues = ref<string[]>(\n props.multiple && Array.isArray(props.modelValue) ? [...props.modelValue] : [],\n)\n\n// Controlled open state used in multiple mode to prevent the dropdown closing\n// after each item selection (Reka would normally close on selection).\nconst internalOpen = ref(props.defaultOpen ?? false)\n\n// Flag set by onMultipleSelect so handleOpenChange can distinguish item\n// selection from Escape/outside-click close.\nlet selectingItem = false\n\n// ── Open-state tracking ────────────────────────────────────────────────────\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\n// Controlled open state for single mode (mirrors multiple mode's internalOpen).\n// Driving open ourselves lets handleOpenChange gate spurious reopens — e.g. the\n// focus bounce caused when a create handler mutates the items list.\nconst singleOpen = ref(props.open ?? props.defaultOpen ?? false)\n// When true, handleOpenChange ignores open=true requests. Set briefly after a\n// single-mode create so the post-create re-render can't reopen the menu.\nlet blockReopen = false\nlet blockReopenTimer: ReturnType<typeof setTimeout> | undefined\nconst effectiveIgnoreFilter = computed(() => {\n if (props.loadItems) return true\n if (!props.filterOnOpen && isOpen.value && !isUserTyping.value) return true\n return false\n})\n\n// ── Label/value bridge ─────────────────────────────────────────────────────\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (value == null || value === '') return ''\n const match = internalItems.value.find((i) => i.value === value)\n if (match) return match.label ?? match.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\nfunction valueFor(displayed: string): string {\n if (!displayed) return ''\n const match = internalItems.value.find(\n (i) => (i.label ?? i.textValue ?? i.value) === displayed,\n )\n if (match) return match.value\n for (const [value, label] of slotItemRegistry.value) {\n if (label === displayed) return value\n }\n return displayed\n}\n\nconst singleModelValue = computed(() =>\n props.multiple ? undefined : (props.modelValue as string | undefined),\n)\n\nconst searchTerm = ref(labelFor(singleModelValue.value))\n\nconst isFilled = computed(() =>\n props.multiple\n ? selectedValues.value.length > 0 || !!searchTerm.value\n : !!searchTerm.value,\n)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\nconst selectedLabels = computed(() =>\n selectedValues.value.map(v => ({ value: v, label: labelFor(v) || v })),\n)\n\n// ── Helpers ────────────────────────────────────────────────────────────────\nconst descriptionId = computed(() => `${inputId.value}-description`)\nconst errorMessageId = computed(() => `${inputId.value}-error`)\nconst showError = computed(() => props.isInvalid && !!props.errorMessage)\nconst showDescription = computed(() => !!props.description && !showError.value)\nconst hasHelper = computed(() => showError.value || showDescription.value)\nconst ariaDescribedBy = computed(() => {\n if (showError.value) return errorMessageId.value\n if (showDescription.value) return descriptionId.value\n return undefined\n})\n\n// ── Watchers ───────────────────────────────────────────────────────────────\n\n// Parent → internal: sync controlled modelValue into local state\nwatch(() => props.modelValue, (val) => {\n if (props.multiple) {\n if (Array.isArray(val)) selectedValues.value = [...val]\n } else {\n const next = labelFor(val as string | undefined)\n if (searchTerm.value !== next) searchTerm.value = next\n }\n})\n\n// Parent → internal: sync consumer-controlled open into our single-mode state\nwatch(() => props.open, (val) => {\n if (!props.multiple && val !== undefined) singleOpen.value = val\n})\n\n// Internal → parent: single mode only — multiple mode emits inside onMultipleSelect\nwatch(searchTerm, (displayed) => {\n if (props.multiple) {\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n return\n }\n const next = valueFor(displayed)\n if (next !== (singleModelValue.value ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n})\n\nfunction handleOpenChange(val: boolean) {\n if (props.multiple) {\n isOpen.value = val\n // Suppress close when triggered by item selection; allow Escape/outside-click\n internalOpen.value = (!val && selectingItem) ? true : val\n selectingItem = false\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n return\n }\n\n // Single mode: ignore spurious reopen requests right after a create — the\n // create handler's re-render bounces focus back to the input, which would\n // otherwise reopen the menu via openOnFocus.\n if (val && blockReopen) return\n\n isOpen.value = val\n singleOpen.value = val\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n}\n\n// ── Multiple-mode actions ──────────────────────────────────────────────────\n\nfunction onMultipleSelect(value: string) {\n selectingItem = true\n const idx = selectedValues.value.indexOf(value)\n selectedValues.value = idx === -1\n ? [...selectedValues.value, value]\n : selectedValues.value.filter((_, i) => i !== idx)\n // Clear the input and drop out of \"typing\" mode so the filter is ignored and\n // the full list shows again (effectiveIgnoreFilter depends on !isUserTyping).\n searchTerm.value = ''\n isUserTyping.value = false\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction removeValue(value: string) {\n selectedValues.value = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction clearAll() {\n selectedValues.value = []\n emit('update:modelValue', [])\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\n// ── Creatable ──────────────────────────────────────────────────────────────\n\nconst hasExactMatch = computed(() => {\n const term = searchTerm.value.trim().toLowerCase()\n if (!term) return false\n const inItems = internalItems.value.some(\n i => (i.label ?? i.textValue ?? i.value).toLowerCase() === term,\n )\n if (inItems) return true\n for (const label of slotItemRegistry.value.values()) {\n if (label.toLowerCase() === term) return true\n }\n return false\n})\n\nfunction onCreateValue(value: string) {\n const trimmed = value.trim()\n if (!trimmed) return\n if (props.multiple) {\n // We own selection state; add the value, then reset input + filter for the\n // next entry (dropdown is kept open and refocused by AutocompleteCreateItem).\n if (!selectedValues.value.includes(trimmed)) {\n selectingItem = true\n selectedValues.value = [...selectedValues.value, trimmed]\n emit('update:modelValue', selectedValues.value)\n }\n searchTerm.value = ''\n isUserTyping.value = false\n } else {\n // Single mode: set the value ourselves and close the (controlled) dropdown.\n searchTerm.value = trimmed\n emit('update:modelValue', trimmed)\n isOpen.value = false\n singleOpen.value = false\n // The create handler (parent @create) typically mutates the items list, which\n // re-renders and bounces focus back to the input — that fires openOnFocus and\n // would reopen the menu. Block reopen requests briefly so the close sticks.\n blockReopen = true\n clearTimeout(blockReopenTimer)\n blockReopenTimer = setTimeout(() => { blockReopen = false }, 300)\n }\n emit('create', trimmed)\n}\n\n// ── Async loading ──────────────────────────────────────────────────────────\nlet debounceTimer: ReturnType<typeof setTimeout> | undefined\n\nasync function runLoadItems(query: string) {\n if (!props.loadItems) return\n isLoading.value = true\n try {\n internalItems.value = await props.loadItems(query)\n } finally {\n isLoading.value = false\n }\n}\n\nfunction scheduleLoad(query: string) {\n if (!props.loadItems) return\n clearTimeout(debounceTimer)\n if (props.debounceMs === 0) {\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\nonMounted(() => {\n if (props.loadItems) void runLoadItems(searchTerm.value)\n})\n\nwatch(searchTerm, (q) => {\n if (props.loadItems) scheduleLoad(q)\n})\n\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) internalItems.value = [...newItems]\n})\n\nwatch(internalItems, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\nwatch(slotItemRegistry, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\n// ── Styles / context ───────────────────────────────────────────────────────\n\nconst slotFns = computed(() =>\n autocompleteVariants({\n variant: props.variant,\n size: props.size,\n color: props.color,\n fullWidth: props.fullWidth,\n isInvalid: props.isInvalid,\n isDisabled: props.isDisabled,\n isReadonly: props.isReadonly,\n hasLabel: hasLabel.value,\n labelPlacement: props.labelPlacement,\n }),\n)\n\nconst showOutsideLabel = computed(\n () => hasLabel.value && props.labelPlacement !== 'inside',\n)\n\nuseAutocompleteProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n isReadonly: toRef(props, 'isReadonly'),\n isRequired: toRef(props, 'isRequired'),\n isLoading,\n isFilled,\n fullWidth: toRef(props, 'fullWidth'),\n hasLabel,\n labelPlacement: toRef(props, 'labelPlacement'),\n inputId,\n label: toRef(props, 'label'),\n ariaDescribedBy,\n truncateItems: toRef(props, 'truncateItems'),\n hasItems,\n slots: slotFns,\n multiple: toRef(props, 'multiple'),\n multipleOverflow: toRef(props, 'multipleOverflow'),\n selectedValues,\n selectedLabels,\n onMultipleSelect,\n removeValue,\n clearAll,\n isSelected,\n registerItem,\n unregisterItem,\n searchTerm,\n hasExactMatch,\n onCreateValue,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :data-invalid=\"isInvalid || undefined\"\n :data-disabled=\"isDisabled || undefined\"\n :data-readonly=\"isReadonly || undefined\"\n :data-required=\"isRequired || undefined\"\n :data-has-label=\"hasLabel || undefined\"\n :data-has-helper=\"hasHelper || undefined\"\n >\n <label\n v-if=\"showOutsideLabel\"\n :for=\"inputId\"\n :class=\"slotFns.label()\"\n >{{ label }}<span\n v-if=\"isRequired\"\n aria-hidden=\"true\"\n > *</span></label>\n\n <div :class=\"slotFns.mainWrapper()\">\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.multiple ? internalOpen : singleOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :ignore-filter=\"effectiveIgnoreFilter\"\n :open-on-focus=\"true\"\n @update:open=\"handleOpenChange\"\n >\n <slot\n :is-loading=\"isLoading\"\n :items=\"internalItems\"\n />\n </AutocompleteRoot>\n\n <div\n v-if=\"hasHelper\"\n :class=\"slotFns.helperWrapper()\"\n >\n <div\n v-if=\"showError\"\n :id=\"errorMessageId\"\n :class=\"slotFns.errorMessage()\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"showDescription\"\n :id=\"descriptionId\"\n :class=\"slotFns.description()\"\n >\n {{ description }}\n </div>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"Autocomplete.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/autocomplete/Autocomplete.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, ref, toRef, useAttrs, useId, watch, useSlots } from 'vue'\nimport { AutocompleteRoot } from 'reka-ui'\nimport { autocompleteVariants, type AutocompleteVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useAutocompleteProvide } from './Autocomplete.context'\nimport { hasSlotComponent } from '../../utils/hasSlotComponent'\nimport AutocompleteInput from './AutocompleteInput.vue'\nimport AutocompleteContent from './AutocompleteContent.vue'\nimport AutocompleteItem from './AutocompleteItem.vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'flat',\n size: 'md',\n color: 'default',\n labelPlacement: 'inside',\n fullWidth: false,\n isInvalid: false,\n isDisabled: false,\n isReadonly: false,\n isRequired: false,\n multiple: false,\n multipleOverflow: 'wrap',\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n loadItems: undefined,\n debounceMs: 200,\n filterOnOpen: false,\n truncateItems: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | string[]]\n 'update:open': [value: boolean]\n /** Fired when the user creates a new value via `creatable` or `<AutocompleteCreateItem>`. */\n 'create': [value: string]\n}>()\n\nexport interface AutocompleteItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\ntype Props = {\n /** Visual style of the field. @default 'flat' */\n variant?: AutocompleteVariants['variant']\n /** Field height. @default 'md' */\n size?: AutocompleteVariants['size']\n /** Accent color applied to focus ring + floating label. @default 'default' */\n color?: AutocompleteVariants['color']\n /**\n * Where the `label` is rendered relative to the field.\n * - `inside`: floats above the trigger (shrinks when focused/filled)\n * - `outside`: sits above the field, static\n * - `outside-left`: sits to the left, horizontal layout\n * @default 'inside'\n */\n labelPlacement?: AutocompleteVariants['labelPlacement']\n /** Stretches root wrapper to 100% width. @default false */\n fullWidth?: boolean\n /** Marks the field as invalid. Triggers danger styling and enables `errorMessage`. @default false */\n isInvalid?: boolean\n /** Disables the field. @default false */\n isDisabled?: boolean\n /** Makes the field read-only. @default false */\n isReadonly?: boolean\n /** Adds a required asterisk next to the label. @default false */\n isRequired?: boolean\n /** Placeholder shown when empty. */\n placeholder?: string\n /** Form field name, for native form submission. */\n name?: string\n /** Field label. When omitted, the floating-label behavior is skipped. */\n label?: string\n /** Helper text displayed below the field. Suppressed when `isInvalid && errorMessage` is shown. */\n description?: string\n /** Error text displayed below the field. Only rendered when `isInvalid` is also true. */\n errorMessage?: string\n /** Extra classes merged onto the root wrapper via `composeClassName`. */\n class?: string\n\n /* ─── Autocomplete-specific ─────────────────────────────────────── */\n /** Two-way bound selected value. string in single mode, string[] in multiple mode. */\n modelValue?: string | string[]\n /** Initial selected value (uncontrolled). */\n defaultValue?: string | string[]\n /** Allow selecting multiple values. modelValue becomes string[]. @default false */\n multiple?: boolean\n /**\n * Controls how chips overflow in multiple mode.\n * - `wrap`: trigger grows in height, chips wrap to new lines (default)\n * - `collapse`: fixed height, overflowing chips are hidden behind \"+N more\"\n * @default 'wrap'\n */\n multipleOverflow?: 'wrap' | 'collapse'\n /** Controls open state of the dropdown. */\n open?: boolean\n /** Initial open state of the dropdown (uncontrolled). */\n defaultOpen?: boolean\n /** Static items list — used when no loadItems is provided. */\n items?: AutocompleteItem[]\n /** Async data source: called on every query change. */\n loadItems?: (query: string) => Promise<AutocompleteItem[]>\n /** Debounce delay for loadItems calls (ms). 0 = no debounce. */\n debounceMs?: number\n /** Apply filter immediately on open (default: false — show all items until user types). */\n filterOnOpen?: boolean\n /**\n * Truncate item text with an ellipsis when it overflows the dropdown width.\n * Set to `false` to show full text — the dropdown will widen to fit.\n * @default true\n */\n truncateItems?: boolean\n}\n\nconst attrs = useAttrs()\nconst generatedId = useId()\nconst inputId = computed(() => (attrs.id as string | undefined) ?? generatedId)\n\nconst hasLabel = computed(() => !!props.label)\n\nconst slots = useSlots()\n// Compound chrome present → pass slot through (advanced). Otherwise render the\n// input/content/items internally (short-form).\nconst usesCustomChrome = computed(() =>\n hasSlotComponent(slots.default?.(), [AutocompleteInput, AutocompleteContent]),\n)\n\n// Registry for slot-rendered items: value → label (populated by AutocompleteItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Internal async state\nconst isLoading = ref(false)\nconst internalItems = ref<AutocompleteItem[]>([...props.items])\n\n// ── Multiple-mode state ────────────────────────────────────────────────────\n// Tracks selected values as an array. Only meaningful when props.multiple=true.\nconst selectedValues = ref<string[]>(\n props.multiple && Array.isArray(props.modelValue) ? [...props.modelValue] : [],\n)\n\n// Controlled open state used in multiple mode to prevent the dropdown closing\n// after each item selection (Reka would normally close on selection).\nconst internalOpen = ref(props.defaultOpen ?? false)\n\n// Flag set by onMultipleSelect so handleOpenChange can distinguish item\n// selection from Escape/outside-click close.\nlet selectingItem = false\n\n// ── Open-state tracking ────────────────────────────────────────────────────\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\n// Controlled open state for single mode (mirrors multiple mode's internalOpen).\n// Driving open ourselves lets handleOpenChange gate spurious reopens — e.g. the\n// focus bounce caused when a create handler mutates the items list.\nconst singleOpen = ref(props.open ?? props.defaultOpen ?? false)\n// When true, handleOpenChange ignores open=true requests. Set briefly after a\n// single-mode create so the post-create re-render can't reopen the menu.\nlet blockReopen = false\nlet blockReopenTimer: ReturnType<typeof setTimeout> | undefined\nconst effectiveIgnoreFilter = computed(() => {\n if (props.loadItems) return true\n if (!props.filterOnOpen && isOpen.value && !isUserTyping.value) return true\n return false\n})\n\n// ── Label/value bridge ─────────────────────────────────────────────────────\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (value == null || value === '') return ''\n const match = internalItems.value.find((i) => i.value === value)\n if (match) return match.label ?? match.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\nfunction valueFor(displayed: string): string {\n if (!displayed) return ''\n const match = internalItems.value.find(\n (i) => (i.label ?? i.textValue ?? i.value) === displayed,\n )\n if (match) return match.value\n for (const [value, label] of slotItemRegistry.value) {\n if (label === displayed) return value\n }\n return displayed\n}\n\nconst singleModelValue = computed(() =>\n props.multiple ? undefined : (props.modelValue as string | undefined),\n)\n\nconst searchTerm = ref(labelFor(singleModelValue.value))\n\nconst isFilled = computed(() =>\n props.multiple\n ? selectedValues.value.length > 0 || !!searchTerm.value\n : !!searchTerm.value,\n)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\nconst selectedLabels = computed(() =>\n selectedValues.value.map(v => ({ value: v, label: labelFor(v) || v })),\n)\n\n// ── Helpers ────────────────────────────────────────────────────────────────\nconst descriptionId = computed(() => `${inputId.value}-description`)\nconst errorMessageId = computed(() => `${inputId.value}-error`)\nconst showError = computed(() => props.isInvalid && !!props.errorMessage)\nconst showDescription = computed(() => !!props.description && !showError.value)\nconst hasHelper = computed(() => showError.value || showDescription.value)\nconst ariaDescribedBy = computed(() => {\n if (showError.value) return errorMessageId.value\n if (showDescription.value) return descriptionId.value\n return undefined\n})\n\n// ── Watchers ───────────────────────────────────────────────────────────────\n\n// Parent → internal: sync controlled modelValue into local state\nwatch(() => props.modelValue, (val) => {\n if (props.multiple) {\n if (Array.isArray(val)) selectedValues.value = [...val]\n } else {\n const next = labelFor(val as string | undefined)\n if (searchTerm.value !== next) searchTerm.value = next\n }\n})\n\n// Parent → internal: sync consumer-controlled open into our single-mode state\nwatch(() => props.open, (val) => {\n if (!props.multiple && val !== undefined) singleOpen.value = val\n})\n\n// Internal → parent: single mode only — multiple mode emits inside onMultipleSelect\nwatch(searchTerm, (displayed) => {\n if (props.multiple) {\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n return\n }\n const next = valueFor(displayed)\n if (next !== (singleModelValue.value ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n})\n\nfunction handleOpenChange(val: boolean) {\n if (props.multiple) {\n isOpen.value = val\n // Suppress close when triggered by item selection; allow Escape/outside-click\n internalOpen.value = (!val && selectingItem) ? true : val\n selectingItem = false\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n return\n }\n\n // Single mode: ignore spurious reopen requests right after a create — the\n // create handler's re-render bounces focus back to the input, which would\n // otherwise reopen the menu via openOnFocus.\n if (val && blockReopen) return\n\n isOpen.value = val\n singleOpen.value = val\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n}\n\n// ── Multiple-mode actions ──────────────────────────────────────────────────\n\nfunction onMultipleSelect(value: string) {\n selectingItem = true\n const idx = selectedValues.value.indexOf(value)\n selectedValues.value = idx === -1\n ? [...selectedValues.value, value]\n : selectedValues.value.filter((_, i) => i !== idx)\n // Clear the input and drop out of \"typing\" mode so the filter is ignored and\n // the full list shows again (effectiveIgnoreFilter depends on !isUserTyping).\n searchTerm.value = ''\n isUserTyping.value = false\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction removeValue(value: string) {\n selectedValues.value = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction clearAll() {\n selectedValues.value = []\n emit('update:modelValue', [])\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\n// ── Creatable ──────────────────────────────────────────────────────────────\n\nconst hasExactMatch = computed(() => {\n const term = searchTerm.value.trim().toLowerCase()\n if (!term) return false\n const inItems = internalItems.value.some(\n i => (i.label ?? i.textValue ?? i.value).toLowerCase() === term,\n )\n if (inItems) return true\n for (const label of slotItemRegistry.value.values()) {\n if (label.toLowerCase() === term) return true\n }\n return false\n})\n\nfunction onCreateValue(value: string) {\n const trimmed = value.trim()\n if (!trimmed) return\n if (props.multiple) {\n // We own selection state; add the value, then reset input + filter for the\n // next entry (dropdown is kept open and refocused by AutocompleteCreateItem).\n if (!selectedValues.value.includes(trimmed)) {\n selectingItem = true\n selectedValues.value = [...selectedValues.value, trimmed]\n emit('update:modelValue', selectedValues.value)\n }\n searchTerm.value = ''\n isUserTyping.value = false\n } else {\n // Single mode: set the value ourselves and close the (controlled) dropdown.\n searchTerm.value = trimmed\n emit('update:modelValue', trimmed)\n isOpen.value = false\n singleOpen.value = false\n // The create handler (parent @create) typically mutates the items list, which\n // re-renders and bounces focus back to the input — that fires openOnFocus and\n // would reopen the menu. Block reopen requests briefly so the close sticks.\n blockReopen = true\n clearTimeout(blockReopenTimer)\n blockReopenTimer = setTimeout(() => { blockReopen = false }, 300)\n }\n emit('create', trimmed)\n}\n\n// ── Async loading ──────────────────────────────────────────────────────────\nlet debounceTimer: ReturnType<typeof setTimeout> | undefined\n\nasync function runLoadItems(query: string) {\n if (!props.loadItems) return\n isLoading.value = true\n try {\n internalItems.value = await props.loadItems(query)\n } finally {\n isLoading.value = false\n }\n}\n\nfunction scheduleLoad(query: string) {\n if (!props.loadItems) return\n clearTimeout(debounceTimer)\n if (props.debounceMs === 0) {\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\nonMounted(() => {\n if (props.loadItems) void runLoadItems(searchTerm.value)\n})\n\nwatch(searchTerm, (q) => {\n if (props.loadItems) scheduleLoad(q)\n})\n\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) internalItems.value = [...newItems]\n})\n\nwatch(internalItems, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\nwatch(slotItemRegistry, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\n// ── Styles / context ───────────────────────────────────────────────────────\n\nconst slotFns = computed(() =>\n autocompleteVariants({\n variant: props.variant,\n size: props.size,\n color: props.color,\n fullWidth: props.fullWidth,\n isInvalid: props.isInvalid,\n isDisabled: props.isDisabled,\n isReadonly: props.isReadonly,\n hasLabel: hasLabel.value,\n labelPlacement: props.labelPlacement,\n }),\n)\n\nconst showOutsideLabel = computed(\n () => hasLabel.value && props.labelPlacement !== 'inside',\n)\n\nuseAutocompleteProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n isReadonly: toRef(props, 'isReadonly'),\n isRequired: toRef(props, 'isRequired'),\n isLoading,\n isFilled,\n fullWidth: toRef(props, 'fullWidth'),\n hasLabel,\n labelPlacement: toRef(props, 'labelPlacement'),\n inputId,\n label: toRef(props, 'label'),\n ariaDescribedBy,\n truncateItems: toRef(props, 'truncateItems'),\n hasItems,\n slots: slotFns,\n multiple: toRef(props, 'multiple'),\n multipleOverflow: toRef(props, 'multipleOverflow'),\n selectedValues,\n selectedLabels,\n onMultipleSelect,\n removeValue,\n clearAll,\n isSelected,\n registerItem,\n unregisterItem,\n searchTerm,\n hasExactMatch,\n onCreateValue,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :data-invalid=\"isInvalid || undefined\"\n :data-disabled=\"isDisabled || undefined\"\n :data-readonly=\"isReadonly || undefined\"\n :data-required=\"isRequired || undefined\"\n :data-has-label=\"hasLabel || undefined\"\n :data-has-helper=\"hasHelper || undefined\"\n >\n <label\n v-if=\"showOutsideLabel\"\n :for=\"inputId\"\n :class=\"slotFns.label()\"\n >{{ label }}<span\n v-if=\"isRequired\"\n aria-hidden=\"true\"\n > *</span></label>\n\n <div :class=\"slotFns.mainWrapper()\">\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.multiple ? internalOpen : singleOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :ignore-filter=\"effectiveIgnoreFilter\"\n :open-on-focus=\"true\"\n @update:open=\"handleOpenChange\"\n >\n <slot\n v-if=\"usesCustomChrome\"\n :is-loading=\"isLoading\"\n :items=\"internalItems\"\n />\n <template v-else>\n <AutocompleteInput :placeholder=\"props.placeholder\" />\n <AutocompleteContent>\n <AutocompleteItem\n v-for=\"item in internalItems\"\n :key=\"item.value\"\n :value=\"item.value\"\n :is-disabled=\"item.isDisabled\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n >{{ item.label ?? item.textValue ?? item.value }}</slot>\n </AutocompleteItem>\n </AutocompleteContent>\n </template>\n </AutocompleteRoot>\n\n <div\n v-if=\"hasHelper\"\n :class=\"slotFns.helperWrapper()\"\n >\n <div\n v-if=\"showError\"\n :id=\"errorMessageId\"\n :class=\"slotFns.errorMessage()\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"showDescription\"\n :id=\"descriptionId\"\n :class=\"slotFns.description()\"\n >\n {{ description }}\n </div>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAaA,MAAM,QAAQ;EAuBd,MAAM,OAAO;EAsFb,MAAM,QAAQ,UAAS;EACvB,MAAM,cAAc,OAAM;EAC1B,MAAM,UAAU,eAAgB,MAAM,MAA6B,YAAW;EAE9E,MAAM,WAAW,eAAe,CAAC,CAAC,MAAM,MAAK;EAE7C,MAAM,QAAQ,UAAS;EAGvB,MAAM,mBAAmB,eACvB,iBAAiB,MAAM,WAAW,EAAE,CAAC,2BAAmB,4BAAoB,CAAC,CAC/E;EAIA,MAAM,mBAAmB,oBAAI,IAAI,KAAqB,CAAA;EAEtD,SAAS,aAAa,OAAe,OAAe;GAClD,MAAM,OAAO,IAAI,IAAI,iBAAiB,MAAK;AAC3C,QAAK,IAAI,OAAO,MAAK;AACrB,oBAAiB,QAAQ;;EAG3B,SAAS,eAAe,OAAe;GACrC,MAAM,OAAO,IAAI,IAAI,iBAAiB,MAAK;AAC3C,QAAK,OAAO,MAAK;AACjB,oBAAiB,QAAQ;;EAI3B,MAAM,YAAY,IAAI,MAAK;EAC3B,MAAM,gBAAgB,IAAwB,CAAC,GAAG,MAAM,MAAM,CAAA;EAI9D,MAAM,iBAAiB,IACrB,MAAM,YAAY,MAAM,QAAQ,MAAM,WAAW,GAAG,CAAC,GAAG,MAAM,WAAW,GAAG,EAAE,CAChF;EAIA,MAAM,eAAe,IAAI,MAAM,eAAe,MAAK;EAInD,IAAI,gBAAgB;EAGpB,MAAM,SAAS,IAAI,MAAM,eAAe,MAAK;EAC7C,MAAM,aAAa,IAAI,GAAE;EACzB,MAAM,eAAe,IAAI,MAAK;EAI9B,MAAM,aAAa,IAAI,MAAM,QAAQ,MAAM,eAAe,MAAK;EAG/D,IAAI,cAAc;EAClB,IAAI;EACJ,MAAM,wBAAwB,eAAe;AAC3C,OAAI,MAAM,UAAW,QAAO;AAC5B,OAAI,CAAC,MAAM,gBAAgB,OAAO,SAAS,CAAC,aAAa,MAAO,QAAO;AACvE,UAAO;IACR;EAID,SAAS,SAAS,OAAmC;AACnD,OAAI,SAAS,QAAQ,UAAU,GAAI,QAAO;GAC1C,MAAM,QAAQ,cAAc,MAAM,MAAM,MAAM,EAAE,UAAU,MAAK;AAC/D,OAAI,MAAO,QAAO,MAAM,SAAS,MAAM,aAAa;AACpD,UAAO,iBAAiB,MAAM,IAAI,MAAM,IAAI;;EAE9C,SAAS,SAAS,WAA2B;AAC3C,OAAI,CAAC,UAAW,QAAO;GACvB,MAAM,QAAQ,cAAc,MAAM,MAC/B,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,UACjD;AACA,OAAI,MAAO,QAAO,MAAM;AACxB,QAAK,MAAM,CAAC,OAAO,UAAU,iBAAiB,MAC5C,KAAI,UAAU,UAAW,QAAO;AAElC,UAAO;;EAGT,MAAM,mBAAmB,eACvB,MAAM,WAAW,KAAA,IAAa,MAAM,WACtC;EAEA,MAAM,aAAa,IAAI,SAAS,iBAAiB,MAAM,CAAA;EAEvD,MAAM,WAAW,eACf,MAAM,WACF,eAAe,MAAM,SAAS,KAAK,CAAC,CAAC,WAAW,QAChD,CAAC,CAAC,WAAW,MACnB;EACA,MAAM,WAAW,eAAe,cAAc,MAAM,SAAS,EAAC;EAE9D,MAAM,iBAAiB,eACrB,eAAe,MAAM,KAAI,OAAM;GAAE,OAAO;GAAG,OAAO,SAAS,EAAE,IAAI;GAAG,EAAE,CACxE;EAGA,MAAM,gBAAgB,eAAe,GAAG,QAAQ,MAAM,cAAa;EACnE,MAAM,iBAAiB,eAAe,GAAG,QAAQ,MAAM,QAAO;EAC9D,MAAM,YAAY,eAAe,MAAM,aAAa,CAAC,CAAC,MAAM,aAAY;EACxE,MAAM,kBAAkB,eAAe,CAAC,CAAC,MAAM,eAAe,CAAC,UAAU,MAAK;EAC9E,MAAM,YAAY,eAAe,UAAU,SAAS,gBAAgB,MAAK;EACzE,MAAM,kBAAkB,eAAe;AACrC,OAAI,UAAU,MAAO,QAAO,eAAe;AAC3C,OAAI,gBAAgB,MAAO,QAAO,cAAc;IAEjD;AAKD,cAAY,MAAM,aAAa,QAAQ;AACrC,OAAI,MAAM;QACJ,MAAM,QAAQ,IAAI,CAAE,gBAAe,QAAQ,CAAC,GAAG,IAAG;UACjD;IACL,MAAM,OAAO,SAAS,IAAyB;AAC/C,QAAI,WAAW,UAAU,KAAM,YAAW,QAAQ;;IAErD;AAGD,cAAY,MAAM,OAAO,QAAQ;AAC/B,OAAI,CAAC,MAAM,YAAY,QAAQ,KAAA,EAAW,YAAW,QAAQ;IAC9D;AAGD,QAAM,aAAa,cAAc;AAC/B,OAAI,MAAM,UAAU;AAClB,QAAI,OAAO,SAAS,cAAc,WAAW,MAAO,cAAa,QAAQ;AACzE;;GAEF,MAAM,OAAO,SAAS,UAAS;AAC/B,OAAI,UAAU,iBAAiB,SAAS,IAAK,MAAK,qBAAqB,KAAI;AAC3E,OAAI,OAAO,SAAS,cAAc,WAAW,MAAO,cAAa,QAAQ;IAC1E;EAED,SAAS,iBAAiB,KAAc;AACtC,OAAI,MAAM,UAAU;AAClB,WAAO,QAAQ;AAEf,iBAAa,QAAS,CAAC,OAAO,gBAAiB,OAAO;AACtD,oBAAgB;AAChB,QAAI,KAAK;AAAE,gBAAW,QAAQ,WAAW;AAAO,kBAAa,QAAQ;UAC9D,cAAa,QAAQ;AAC5B,SAAK,eAAe,IAAG;AACvB;;AAMF,OAAI,OAAO,YAAa;AAExB,UAAO,QAAQ;AACf,cAAW,QAAQ;AACnB,OAAI,KAAK;AAAE,eAAW,QAAQ,WAAW;AAAO,iBAAa,QAAQ;SAC9D,cAAa,QAAQ;AAC5B,QAAK,eAAe,IAAG;;EAKzB,SAAS,iBAAiB,OAAe;AACvC,mBAAgB;GAChB,MAAM,MAAM,eAAe,MAAM,QAAQ,MAAK;AAC9C,kBAAe,QAAQ,QAAQ,KAC3B,CAAC,GAAG,eAAe,OAAO,MAAK,GAC/B,eAAe,MAAM,QAAQ,GAAG,MAAM,MAAM,IAAG;AAGnD,cAAW,QAAQ;AACnB,gBAAa,QAAQ;AACrB,QAAK,qBAAqB,eAAe,MAAK;;EAGhD,SAAS,YAAY,OAAe;AAClC,kBAAe,QAAQ,eAAe,MAAM,QAAO,MAAK,MAAM,MAAK;AACnE,QAAK,qBAAqB,eAAe,MAAK;;EAGhD,SAAS,WAAW;AAClB,kBAAe,QAAQ,EAAC;AACxB,QAAK,qBAAqB,EAAE,CAAA;;EAG9B,SAAS,WAAW,OAAwB;AAC1C,UAAO,eAAe,MAAM,SAAS,MAAK;;EAK5C,MAAM,gBAAgB,eAAe;GACnC,MAAM,OAAO,WAAW,MAAM,MAAM,CAAC,aAAY;AACjD,OAAI,CAAC,KAAM,QAAO;AAIlB,OAHgB,cAAc,MAAM,MAClC,OAAM,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,aAAa,KAAK,KAC7D,CACa,QAAO;AACpB,QAAK,MAAM,SAAS,iBAAiB,MAAM,QAAQ,CACjD,KAAI,MAAM,aAAa,KAAK,KAAM,QAAO;AAE3C,UAAO;IACR;EAED,SAAS,cAAc,OAAe;GACpC,MAAM,UAAU,MAAM,MAAK;AAC3B,OAAI,CAAC,QAAS;AACd,OAAI,MAAM,UAAU;AAGlB,QAAI,CAAC,eAAe,MAAM,SAAS,QAAQ,EAAE;AAC3C,qBAAgB;AAChB,oBAAe,QAAQ,CAAC,GAAG,eAAe,OAAO,QAAO;AACxD,UAAK,qBAAqB,eAAe,MAAK;;AAEhD,eAAW,QAAQ;AACnB,iBAAa,QAAQ;UAChB;AAEL,eAAW,QAAQ;AACnB,SAAK,qBAAqB,QAAO;AACjC,WAAO,QAAQ;AACf,eAAW,QAAQ;AAInB,kBAAc;AACd,iBAAa,iBAAgB;AAC7B,uBAAmB,iBAAiB;AAAE,mBAAc;OAAS,IAAG;;AAElE,QAAK,UAAU,QAAO;;EAIxB,IAAI;EAEJ,eAAe,aAAa,OAAe;AACzC,OAAI,CAAC,MAAM,UAAW;AACtB,aAAU,QAAQ;AAClB,OAAI;AACF,kBAAc,QAAQ,MAAM,MAAM,UAAU,MAAK;aACzC;AACR,cAAU,QAAQ;;;EAItB,SAAS,aAAa,OAAe;AACnC,OAAI,CAAC,MAAM,UAAW;AACtB,gBAAa,cAAa;AAC1B,OAAI,MAAM,eAAe,EAClB,cAAa,MAAK;OAEvB,iBAAgB,iBAAiB,KAAK,aAAa,MAAM,EAAE,MAAM,WAAU;;AAI/E,kBAAgB;AACd,OAAI,MAAM,UAAgB,cAAa,WAAW,MAAK;IACxD;AAED,QAAM,aAAa,MAAM;AACvB,OAAI,MAAM,UAAW,cAAa,EAAC;IACpC;AAED,cAAY,MAAM,QAAQ,aAAa;AACrC,OAAI,CAAC,MAAM,UAAW,eAAc,QAAQ,CAAC,GAAG,SAAQ;IACzD;AAED,QAAM,qBAAqB;AACzB,OAAI,MAAM,SAAU;GACpB,MAAM,OAAO,SAAS,iBAAiB,MAAK;AAC5C,OAAI,QAAQ,WAAW,UAAU,QAAQ,SAAS,WAAW,MAAM,MAAM,iBAAiB,SAAS,IACjG,YAAW,QAAQ;IAEtB;AAED,QAAM,wBAAwB;AAC5B,OAAI,MAAM,SAAU;GACpB,MAAM,OAAO,SAAS,iBAAiB,MAAK;AAC5C,OAAI,QAAQ,WAAW,UAAU,QAAQ,SAAS,WAAW,MAAM,MAAM,iBAAiB,SAAS,IACjG,YAAW,QAAQ;IAEtB;EAID,MAAM,UAAU,eACd,qBAAqB;GACnB,SAAS,MAAM;GACf,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,WAAW,MAAM;GACjB,WAAW,MAAM;GACjB,YAAY,MAAM;GAClB,YAAY,MAAM;GAClB,UAAU,SAAS;GACnB,gBAAgB,MAAM;GACvB,CAAC,CACJ;EAEA,MAAM,mBAAmB,eACjB,SAAS,SAAS,MAAM,mBAAmB,SACnD;AAEA,yBAAuB;GACrB,YAAY,MAAM,OAAO,aAAa;GACtC,WAAW,MAAM,OAAO,YAAY;GACpC,YAAY,MAAM,OAAO,aAAa;GACtC,YAAY,MAAM,OAAO,aAAa;GACtC;GACA;GACA,WAAW,MAAM,OAAO,YAAY;GACpC;GACA,gBAAgB,MAAM,OAAO,iBAAiB;GAC9C;GACA,OAAO,MAAM,OAAO,QAAQ;GAC5B;GACA,eAAe,MAAM,OAAO,gBAAgB;GAC5C;GACA,OAAO;GACP,UAAU,MAAM,OAAO,WAAW;GAClC,kBAAkB,MAAM,OAAO,mBAAmB;GAClD;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAA;;uBAIC,mBAuEM,OAAA;IAtEH,OAAK,eAAE,MAAA,iBAAgB,CAAC,QAAA,MAAQ,MAAI,EAAI,MAAM,MAAK,CAAA;IACnD,gBAAc,QAAA,aAAa,KAAA;IAC3B,iBAAe,QAAA,cAAc,KAAA;IAC7B,iBAAe,QAAA,cAAc,KAAA;IAC7B,iBAAe,QAAA,cAAc,KAAA;IAC7B,kBAAgB,SAAA,SAAY,KAAA;IAC5B,mBAAiB,UAAA,SAAa,KAAA;OAGvB,iBAAA,SAAA,WAAA,EADR,mBAOkB,SAAA;;IALf,KAAK,QAAA;IACL,OAAK,eAAE,QAAA,MAAQ,OAAK,CAAA;uCACnB,QAAA,MAAK,EAAA,EAAA,EACD,QAAA,cAAA,WAAA,EADI,mBAGF,QAHE,YAGX,KAAE,IAAA,mBAAA,IAAA,KAAA,CAAA,EAAA,IAAA,WAAA,IAAA,mBAAA,IAAA,KAAA,EAEH,mBAoDM,OAAA,EApDA,OAAK,eAAE,QAAA,MAAQ,aAAW,CAAA,EAAA,EAAA,CAC9B,YA8BmB,MAAA,iBAAA,EAAA;IA7BT,eAAa,WAAA;4EAAU,QAAA;IAC9B,MAAM,MAAM,WAAW,aAAA,QAAe,WAAA;IACtC,UAAU,MAAM;IAChB,UAAU,MAAM;IAChB,iBAAe,sBAAA;IACf,iBAAe;IACf,iBAAa;;2BAMZ,CAHM,iBAAA,QADR,WAIE,KAAA,QAAA,WAAA;;KAFC,WAAY,UAAA;KACZ,OAAO,cAAA;uBAEV,mBAeW,UAAA,EAAA,KAAA,GAAA,EAAA,CAdT,YAAsD,2BAAA,EAAlC,aAAa,MAAM,aAAA,EAAA,MAAA,GAAA,CAAA,cAAA,CAAA,EACvC,YAYsB,6BAAA,MAAA;4BAVW,EAAA,UAAA,KAAA,EAD/B,mBAUmB,UAAA,MAAA,WATF,cAAA,QAAR,SAAI;0BADb,YAUmB,0BAAA;OARhB,KAAK,KAAK;OACV,OAAO,KAAK;OACZ,eAAa,KAAK;;8BAKqC,CAHxD,WAGwD,KAAA,QAAA,QAAA,EAD/C,MAAI,QAC2C,CAAA,gBAAA,gBAApD,KAAK,SAAS,KAAK,aAAa,KAAK,MAAK,EAAA,EAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;;OAO9C,UAAA,SAAA,WAAA,EADR,mBAkBM,OAAA;;IAhBH,OAAK,eAAE,QAAA,MAAQ,eAAa,CAAA;OAGrB,UAAA,SAAA,WAAA,EADR,mBAMM,OAAA;;IAJH,IAAI,eAAA;IACJ,OAAK,eAAE,QAAA,MAAQ,cAAY,CAAA;sBAEzB,QAAA,aAAY,EAAA,IAAA,WAAA,IAGJ,gBAAA,SAAA,WAAA,EADb,mBAMM,OAAA;;IAJH,IAAI,cAAA;IACJ,OAAK,eAAE,QAAA,MAAQ,aAAW,CAAA;sBAExB,QAAA,YAAW,EAAA,IAAA,WAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,EAAA,EAAA,CAAA,EAAA,IAAA,WAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ComboBox.js","names":[],"sources":["../../../src/components/combo-box/ComboBox.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, toRef, useId, watch } from 'vue'\nimport { ComboboxRoot } from 'reka-ui'\nimport { comboBoxVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useComboBoxProvide } from './ComboBox.context'\n\nexport interface ComboBoxItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\nconst props = withDefaults(defineProps<{\n modelValue?: string\n defaultValue?: string\n open?: boolean\n defaultOpen?: boolean\n items?: ComboBoxItem[]\n label?: string\n placeholder?: string\n description?: string\n errorMessage?: string\n isInvalid?: boolean\n isDisabled?: boolean\n isRequired?: boolean\n allowsCustomValue?: boolean\n fullWidth?: boolean\n /** Custom filter function: return true to include item */\n filterFunction?: (item: string, searchTerm: string) => boolean\n class?: string\n}>(), {\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n label: undefined,\n placeholder: undefined,\n description: undefined,\n errorMessage: undefined,\n isInvalid: false,\n isDisabled: false,\n isRequired: false,\n allowsCustomValue: false,\n fullWidth: false,\n filterFunction: undefined,\n class: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n 'update:open': [value: boolean]\n}>()\n\nconst labelId = useId()\n\nconst slotFns = computed(() =>\n comboBoxVariants({\n fullWidth: props.fullWidth,\n })\n)\n\n// Default filter: case-insensitive substring match\nconst effectiveFilter = computed(() => {\n if (props.filterFunction) return props.filterFunction\n return (itemText: string, searchTerm: string): boolean =>\n itemText.toLowerCase().includes(searchTerm.toLowerCase())\n})\n\n// Registry for slot-rendered items: value → label (populated by ComboBoxItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Resolve a user-facing value (\"us\") to the label text used internally by Reka.\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (!value) return ''\n const item = props.items.find(i => i.value === value)\n if (item) return item.label ?? item.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\n\n// Resolve a Reka-internal label text back to the user-facing value.\nfunction valueFor(label: string): string {\n if (!label) return ''\n // Check items prop first\n const item = props.items.find(i => (i.label ?? i.textValue ?? i.value) === label)\n if (item) return item.value\n // Check slot registry\n for (const [value, lbl] of slotItemRegistry.value) {\n if (lbl === label) return value\n }\n return label\n}\n\n// internalValue holds the label text that Reka sees as its modelValue.\n// This lets Reka write the label directly into the input without a displayValue function.\nconst internalValue = ref(labelFor(props.modelValue))\n\n// Map a stored value back to its human-readable label for the input display.\n// Used as a no-op pass-through since internalValue already holds the label.\nconst displayValue = computed(() => (val: string): string => val)\n\n// Parent → internal: when the user's v-model changes, resolve to label text\nwatch(() => props.modelValue, (val) => {\n const next = labelFor(val)\n if (internalValue.value !== next) internalValue.value = next\n})\n\n// Internal → parent: when Reka emits a label text (after selection), translate to real value\nfunction handleModelValueUpdate(emitted: string) {\n internalValue.value = emitted\n emit('update:modelValue', valueFor(emitted))\n}\n\n// When slot items register (children mount after parent), re-resolve internalValue.\n// This covers the case where modelValue is set before children have mounted.\nwatch(slotItemRegistry, () => {\n const next = labelFor(props.modelValue)\n if (next !== internalValue.value && valueFor(internalValue.value) === (props.modelValue ?? '')) {\n internalValue.value = next\n }\n})\n\nuseComboBoxProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n fullWidth: toRef(props, 'fullWidth'),\n slots: slotFns,\n displayValue,\n registerItem,\n unregisterItem,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :aria-invalid=\"props.isInvalid || undefined\"\n data-slot=\"combo-box\"\n >\n <label\n v-if=\"props.label\"\n :id=\"labelId\"\n data-slot=\"label\"\n >\n {{ props.label }}\n <span\n v-if=\"props.isRequired\"\n aria-hidden=\"true\"\n > *</span>\n </label>\n\n <ComboboxRoot\n v-model=\"internalValue\"\n :default-value=\"props.defaultValue ? labelFor(props.defaultValue) : undefined\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :filter-function=\"effectiveFilter\"\n @update:model-value=\"handleModelValueUpdate($event)\"\n @update:open=\"emit('update:open', $event)\"\n >\n <slot />\n </ComboboxRoot>\n\n <div\n v-if=\"props.description || (props.isInvalid && props.errorMessage)\"\n data-slot=\"helper-wrapper\"\n >\n <p\n v-if=\"props.isInvalid && props.errorMessage\"\n data-slot=\"error-message\"\n aria-live=\"polite\"\n >\n {{ props.errorMessage }}\n </p>\n <p\n v-else-if=\"props.description\"\n data-slot=\"description\"\n >\n {{ props.description }}\n </p>\n </div>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ComboBox.js","names":[],"sources":["../../../src/components/combo-box/ComboBox.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, toRef, useId, useSlots, watch } from 'vue'\nimport { ComboboxRoot } from 'reka-ui'\nimport { comboBoxVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useComboBoxProvide } from './ComboBox.context'\nimport { hasSlotComponent } from '../../utils/hasSlotComponent'\nimport ComboBoxInput from './ComboBoxInput.vue'\nimport ComboBoxContent from './ComboBoxContent.vue'\nimport ComboBoxItem from './ComboBoxItem.vue'\n\nexport interface ComboBoxItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\nconst props = withDefaults(defineProps<{\n modelValue?: string\n defaultValue?: string\n open?: boolean\n defaultOpen?: boolean\n items?: ComboBoxItem[]\n label?: string\n placeholder?: string\n description?: string\n errorMessage?: string\n isInvalid?: boolean\n isDisabled?: boolean\n isRequired?: boolean\n allowsCustomValue?: boolean\n fullWidth?: boolean\n /** Custom filter function: return true to include item */\n filterFunction?: (item: string, searchTerm: string) => boolean\n class?: string\n}>(), {\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n label: undefined,\n placeholder: undefined,\n description: undefined,\n errorMessage: undefined,\n isInvalid: false,\n isDisabled: false,\n isRequired: false,\n allowsCustomValue: false,\n fullWidth: false,\n filterFunction: undefined,\n class: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n 'update:open': [value: boolean]\n}>()\n\nconst labelId = useId()\n\nconst slots = useSlots()\n// Compound chrome present → pass slot through (advanced). Otherwise render the\n// input/content/items internally (short-form).\nconst usesCustomChrome = computed(() =>\n hasSlotComponent(slots.default?.(), [ComboBoxInput, ComboBoxContent]),\n)\n\nconst slotFns = computed(() =>\n comboBoxVariants({\n fullWidth: props.fullWidth,\n })\n)\n\n// Default filter: case-insensitive substring match\nconst effectiveFilter = computed(() => {\n if (props.filterFunction) return props.filterFunction\n return (itemText: string, searchTerm: string): boolean =>\n itemText.toLowerCase().includes(searchTerm.toLowerCase())\n})\n\n// Registry for slot-rendered items: value → label (populated by ComboBoxItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Resolve a user-facing value (\"us\") to the label text used internally by Reka.\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (!value) return ''\n const item = props.items.find(i => i.value === value)\n if (item) return item.label ?? item.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\n\n// Resolve a Reka-internal label text back to the user-facing value.\nfunction valueFor(label: string): string {\n if (!label) return ''\n // Check items prop first\n const item = props.items.find(i => (i.label ?? i.textValue ?? i.value) === label)\n if (item) return item.value\n // Check slot registry\n for (const [value, lbl] of slotItemRegistry.value) {\n if (lbl === label) return value\n }\n return label\n}\n\n// internalValue holds the label text that Reka sees as its modelValue.\n// This lets Reka write the label directly into the input without a displayValue function.\nconst internalValue = ref(labelFor(props.modelValue))\n\n// Map a stored value back to its human-readable label for the input display.\n// Used as a no-op pass-through since internalValue already holds the label.\nconst displayValue = computed(() => (val: string): string => val)\n\n// Parent → internal: when the user's v-model changes, resolve to label text\nwatch(() => props.modelValue, (val) => {\n const next = labelFor(val)\n if (internalValue.value !== next) internalValue.value = next\n})\n\n// Internal → parent: when Reka emits a label text (after selection), translate to real value\nfunction handleModelValueUpdate(emitted: string) {\n internalValue.value = emitted\n emit('update:modelValue', valueFor(emitted))\n}\n\n// When slot items register (children mount after parent), re-resolve internalValue.\n// This covers the case where modelValue is set before children have mounted.\nwatch(slotItemRegistry, () => {\n const next = labelFor(props.modelValue)\n if (next !== internalValue.value && valueFor(internalValue.value) === (props.modelValue ?? '')) {\n internalValue.value = next\n }\n})\n\nuseComboBoxProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n fullWidth: toRef(props, 'fullWidth'),\n slots: slotFns,\n displayValue,\n registerItem,\n unregisterItem,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :aria-invalid=\"props.isInvalid || undefined\"\n data-slot=\"combo-box\"\n >\n <label\n v-if=\"props.label\"\n :id=\"labelId\"\n data-slot=\"label\"\n >\n {{ props.label }}\n <span\n v-if=\"props.isRequired\"\n aria-hidden=\"true\"\n > *</span>\n </label>\n\n <ComboboxRoot\n v-model=\"internalValue\"\n :default-value=\"props.defaultValue ? labelFor(props.defaultValue) : undefined\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :filter-function=\"effectiveFilter\"\n @update:model-value=\"handleModelValueUpdate($event)\"\n @update:open=\"emit('update:open', $event)\"\n >\n <slot v-if=\"usesCustomChrome\" />\n <template v-else>\n <ComboBoxInput :placeholder=\"props.placeholder\" />\n <ComboBoxContent>\n <ComboBoxItem\n v-for=\"item in props.items\"\n :key=\"item.value\"\n :value=\"item.value\"\n :is-disabled=\"item.isDisabled\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n >{{ item.label ?? item.textValue ?? item.value }}</slot>\n </ComboBoxItem>\n </ComboBoxContent>\n </template>\n </ComboboxRoot>\n\n <div\n v-if=\"props.description || (props.isInvalid && props.errorMessage)\"\n data-slot=\"helper-wrapper\"\n >\n <p\n v-if=\"props.isInvalid && props.errorMessage\"\n data-slot=\"error-message\"\n aria-live=\"polite\"\n >\n {{ props.errorMessage }}\n </p>\n <p\n v-else-if=\"props.description\"\n data-slot=\"description\"\n >\n {{ props.description }}\n </p>\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -1,6 +1,10 @@
1
1
  import { composeClassName } from "../../utils/composeClassName.js";
2
+ import { hasSlotComponent } from "../../utils/hasSlotComponent.js";
2
3
  import { useComboBoxProvide } from "./ComboBox.context.js";
3
- import { computed, createCommentVNode, createElementBlock, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, renderSlot, toDisplayString, toRef, unref, useId, watch, withCtx } from "vue";
4
+ import ComboBoxInput_default from "./ComboBoxInput.js";
5
+ import ComboBoxContent_default from "./ComboBoxContent.js";
6
+ import ComboBoxItem_default from "./ComboBoxItem.js";
7
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, renderList, renderSlot, toDisplayString, toRef, unref, useId, useSlots, watch, withCtx } from "vue";
4
8
  import { comboBoxVariants } from "@auronui/styles";
5
9
  import { ComboboxRoot } from "reka-ui";
6
10
  //#region src/components/combo-box/ComboBox.vue?vue&type=script&setup=true&lang.ts
@@ -72,6 +76,8 @@ var ComboBox_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defin
72
76
  const props = __props;
73
77
  const emit = __emit;
74
78
  const labelId = useId();
79
+ const slots = useSlots();
80
+ const usesCustomChrome = computed(() => hasSlotComponent(slots.default?.(), [ComboBoxInput_default, ComboBoxContent_default]));
75
81
  const slotFns = computed(() => comboBoxVariants({ fullWidth: props.fullWidth }));
76
82
  const effectiveFilter = computed(() => {
77
83
  if (props.filterFunction) return props.filterFunction;
@@ -146,7 +152,19 @@ var ComboBox_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defin
146
152
  "filter-function": effectiveFilter.value,
147
153
  "onUpdate:open": _cache[2] || (_cache[2] = ($event) => emit("update:open", $event))
148
154
  }, {
149
- default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
155
+ default: withCtx(() => [usesCustomChrome.value ? renderSlot(_ctx.$slots, "default", { key: 0 }) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createVNode(ComboBoxInput_default, { placeholder: props.placeholder }, null, 8, ["placeholder"]), createVNode(ComboBoxContent_default, null, {
156
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(props.items, (item) => {
157
+ return openBlock(), createBlock(ComboBoxItem_default, {
158
+ key: item.value,
159
+ value: item.value,
160
+ "is-disabled": item.isDisabled
161
+ }, {
162
+ default: withCtx(() => [renderSlot(_ctx.$slots, "item", { item }, () => [createTextVNode(toDisplayString(item.label ?? item.textValue ?? item.value), 1)])]),
163
+ _: 2
164
+ }, 1032, ["value", "is-disabled"]);
165
+ }), 128))]),
166
+ _: 3
167
+ })], 64))]),
150
168
  _: 3
151
169
  }, 8, [
152
170
  "modelValue",
@@ -1 +1 @@
1
- {"version":3,"file":"ComboBox.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/combo-box/ComboBox.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, toRef, useId, watch } from 'vue'\nimport { ComboboxRoot } from 'reka-ui'\nimport { comboBoxVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useComboBoxProvide } from './ComboBox.context'\n\nexport interface ComboBoxItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\nconst props = withDefaults(defineProps<{\n modelValue?: string\n defaultValue?: string\n open?: boolean\n defaultOpen?: boolean\n items?: ComboBoxItem[]\n label?: string\n placeholder?: string\n description?: string\n errorMessage?: string\n isInvalid?: boolean\n isDisabled?: boolean\n isRequired?: boolean\n allowsCustomValue?: boolean\n fullWidth?: boolean\n /** Custom filter function: return true to include item */\n filterFunction?: (item: string, searchTerm: string) => boolean\n class?: string\n}>(), {\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n label: undefined,\n placeholder: undefined,\n description: undefined,\n errorMessage: undefined,\n isInvalid: false,\n isDisabled: false,\n isRequired: false,\n allowsCustomValue: false,\n fullWidth: false,\n filterFunction: undefined,\n class: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n 'update:open': [value: boolean]\n}>()\n\nconst labelId = useId()\n\nconst slotFns = computed(() =>\n comboBoxVariants({\n fullWidth: props.fullWidth,\n })\n)\n\n// Default filter: case-insensitive substring match\nconst effectiveFilter = computed(() => {\n if (props.filterFunction) return props.filterFunction\n return (itemText: string, searchTerm: string): boolean =>\n itemText.toLowerCase().includes(searchTerm.toLowerCase())\n})\n\n// Registry for slot-rendered items: value → label (populated by ComboBoxItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Resolve a user-facing value (\"us\") to the label text used internally by Reka.\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (!value) return ''\n const item = props.items.find(i => i.value === value)\n if (item) return item.label ?? item.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\n\n// Resolve a Reka-internal label text back to the user-facing value.\nfunction valueFor(label: string): string {\n if (!label) return ''\n // Check items prop first\n const item = props.items.find(i => (i.label ?? i.textValue ?? i.value) === label)\n if (item) return item.value\n // Check slot registry\n for (const [value, lbl] of slotItemRegistry.value) {\n if (lbl === label) return value\n }\n return label\n}\n\n// internalValue holds the label text that Reka sees as its modelValue.\n// This lets Reka write the label directly into the input without a displayValue function.\nconst internalValue = ref(labelFor(props.modelValue))\n\n// Map a stored value back to its human-readable label for the input display.\n// Used as a no-op pass-through since internalValue already holds the label.\nconst displayValue = computed(() => (val: string): string => val)\n\n// Parent → internal: when the user's v-model changes, resolve to label text\nwatch(() => props.modelValue, (val) => {\n const next = labelFor(val)\n if (internalValue.value !== next) internalValue.value = next\n})\n\n// Internal → parent: when Reka emits a label text (after selection), translate to real value\nfunction handleModelValueUpdate(emitted: string) {\n internalValue.value = emitted\n emit('update:modelValue', valueFor(emitted))\n}\n\n// When slot items register (children mount after parent), re-resolve internalValue.\n// This covers the case where modelValue is set before children have mounted.\nwatch(slotItemRegistry, () => {\n const next = labelFor(props.modelValue)\n if (next !== internalValue.value && valueFor(internalValue.value) === (props.modelValue ?? '')) {\n internalValue.value = next\n }\n})\n\nuseComboBoxProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n fullWidth: toRef(props, 'fullWidth'),\n slots: slotFns,\n displayValue,\n registerItem,\n unregisterItem,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :aria-invalid=\"props.isInvalid || undefined\"\n data-slot=\"combo-box\"\n >\n <label\n v-if=\"props.label\"\n :id=\"labelId\"\n data-slot=\"label\"\n >\n {{ props.label }}\n <span\n v-if=\"props.isRequired\"\n aria-hidden=\"true\"\n > *</span>\n </label>\n\n <ComboboxRoot\n v-model=\"internalValue\"\n :default-value=\"props.defaultValue ? labelFor(props.defaultValue) : undefined\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :filter-function=\"effectiveFilter\"\n @update:model-value=\"handleModelValueUpdate($event)\"\n @update:open=\"emit('update:open', $event)\"\n >\n <slot />\n </ComboboxRoot>\n\n <div\n v-if=\"props.description || (props.isInvalid && props.errorMessage)\"\n data-slot=\"helper-wrapper\"\n >\n <p\n v-if=\"props.isInvalid && props.errorMessage\"\n data-slot=\"error-message\"\n aria-live=\"polite\"\n >\n {{ props.errorMessage }}\n </p>\n <p\n v-else-if=\"props.description\"\n data-slot=\"description\"\n >\n {{ props.description }}\n </p>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ComboBox.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/combo-box/ComboBox.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, toRef, useId, useSlots, watch } from 'vue'\nimport { ComboboxRoot } from 'reka-ui'\nimport { comboBoxVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useComboBoxProvide } from './ComboBox.context'\nimport { hasSlotComponent } from '../../utils/hasSlotComponent'\nimport ComboBoxInput from './ComboBoxInput.vue'\nimport ComboBoxContent from './ComboBoxContent.vue'\nimport ComboBoxItem from './ComboBoxItem.vue'\n\nexport interface ComboBoxItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\nconst props = withDefaults(defineProps<{\n modelValue?: string\n defaultValue?: string\n open?: boolean\n defaultOpen?: boolean\n items?: ComboBoxItem[]\n label?: string\n placeholder?: string\n description?: string\n errorMessage?: string\n isInvalid?: boolean\n isDisabled?: boolean\n isRequired?: boolean\n allowsCustomValue?: boolean\n fullWidth?: boolean\n /** Custom filter function: return true to include item */\n filterFunction?: (item: string, searchTerm: string) => boolean\n class?: string\n}>(), {\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n label: undefined,\n placeholder: undefined,\n description: undefined,\n errorMessage: undefined,\n isInvalid: false,\n isDisabled: false,\n isRequired: false,\n allowsCustomValue: false,\n fullWidth: false,\n filterFunction: undefined,\n class: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n 'update:open': [value: boolean]\n}>()\n\nconst labelId = useId()\n\nconst slots = useSlots()\n// Compound chrome present → pass slot through (advanced). Otherwise render the\n// input/content/items internally (short-form).\nconst usesCustomChrome = computed(() =>\n hasSlotComponent(slots.default?.(), [ComboBoxInput, ComboBoxContent]),\n)\n\nconst slotFns = computed(() =>\n comboBoxVariants({\n fullWidth: props.fullWidth,\n })\n)\n\n// Default filter: case-insensitive substring match\nconst effectiveFilter = computed(() => {\n if (props.filterFunction) return props.filterFunction\n return (itemText: string, searchTerm: string): boolean =>\n itemText.toLowerCase().includes(searchTerm.toLowerCase())\n})\n\n// Registry for slot-rendered items: value → label (populated by ComboBoxItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Resolve a user-facing value (\"us\") to the label text used internally by Reka.\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (!value) return ''\n const item = props.items.find(i => i.value === value)\n if (item) return item.label ?? item.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\n\n// Resolve a Reka-internal label text back to the user-facing value.\nfunction valueFor(label: string): string {\n if (!label) return ''\n // Check items prop first\n const item = props.items.find(i => (i.label ?? i.textValue ?? i.value) === label)\n if (item) return item.value\n // Check slot registry\n for (const [value, lbl] of slotItemRegistry.value) {\n if (lbl === label) return value\n }\n return label\n}\n\n// internalValue holds the label text that Reka sees as its modelValue.\n// This lets Reka write the label directly into the input without a displayValue function.\nconst internalValue = ref(labelFor(props.modelValue))\n\n// Map a stored value back to its human-readable label for the input display.\n// Used as a no-op pass-through since internalValue already holds the label.\nconst displayValue = computed(() => (val: string): string => val)\n\n// Parent → internal: when the user's v-model changes, resolve to label text\nwatch(() => props.modelValue, (val) => {\n const next = labelFor(val)\n if (internalValue.value !== next) internalValue.value = next\n})\n\n// Internal → parent: when Reka emits a label text (after selection), translate to real value\nfunction handleModelValueUpdate(emitted: string) {\n internalValue.value = emitted\n emit('update:modelValue', valueFor(emitted))\n}\n\n// When slot items register (children mount after parent), re-resolve internalValue.\n// This covers the case where modelValue is set before children have mounted.\nwatch(slotItemRegistry, () => {\n const next = labelFor(props.modelValue)\n if (next !== internalValue.value && valueFor(internalValue.value) === (props.modelValue ?? '')) {\n internalValue.value = next\n }\n})\n\nuseComboBoxProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n fullWidth: toRef(props, 'fullWidth'),\n slots: slotFns,\n displayValue,\n registerItem,\n unregisterItem,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :aria-invalid=\"props.isInvalid || undefined\"\n data-slot=\"combo-box\"\n >\n <label\n v-if=\"props.label\"\n :id=\"labelId\"\n data-slot=\"label\"\n >\n {{ props.label }}\n <span\n v-if=\"props.isRequired\"\n aria-hidden=\"true\"\n > *</span>\n </label>\n\n <ComboboxRoot\n v-model=\"internalValue\"\n :default-value=\"props.defaultValue ? labelFor(props.defaultValue) : undefined\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :filter-function=\"effectiveFilter\"\n @update:model-value=\"handleModelValueUpdate($event)\"\n @update:open=\"emit('update:open', $event)\"\n >\n <slot v-if=\"usesCustomChrome\" />\n <template v-else>\n <ComboBoxInput :placeholder=\"props.placeholder\" />\n <ComboBoxContent>\n <ComboBoxItem\n v-for=\"item in props.items\"\n :key=\"item.value\"\n :value=\"item.value\"\n :is-disabled=\"item.isDisabled\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n >{{ item.label ?? item.textValue ?? item.value }}</slot>\n </ComboBoxItem>\n </ComboBoxContent>\n </template>\n </ComboboxRoot>\n\n <div\n v-if=\"props.description || (props.isInvalid && props.errorMessage)\"\n data-slot=\"helper-wrapper\"\n >\n <p\n v-if=\"props.isInvalid && props.errorMessage\"\n data-slot=\"error-message\"\n aria-live=\"polite\"\n >\n {{ props.errorMessage }}\n </p>\n <p\n v-else-if=\"props.description\"\n data-slot=\"description\"\n >\n {{ props.description }}\n </p>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Select.context.js","names":[],"sources":["../../../src/components/select/Select.context.ts"],"sourcesContent":["import { createContext } from '../../utils/context'\nimport type { ComputedRef, Ref } from 'vue'\nimport type { selectVariants } from '@auronui/styles'\n\nexport interface SelectContext {\n isDisabled: Ref<boolean>\n isInvalid: Ref<boolean>\n isReadonly: Ref<boolean>\n isRequired: Ref<boolean>\n fullWidth: Ref<boolean>\n hasLabel: Ref<boolean>\n labelPlacement: Ref<'inside' | 'outside' | 'outside-left'>\n triggerId: Ref<string>\n label: Ref<string | undefined>\n ariaDescribedBy: Ref<string | undefined>\n slots: ComputedRef<ReturnType<typeof selectVariants>>\n multiple: Ref<boolean>\n /**\n * Persistent registry of item value → display label. SelectItem registers\n * once on first mount and never unregisters — survives SelectContent\n * unmount so SelectValue can show the selected label while the popover\n * is closed (Reka clears its own optionsSet on unmount).\n */\n registerItem: (value: string, label: string) => void\n itemLabel: (value: string | string[] | undefined | null) => string\n removeValue: (value: string) => void\n}\n\nexport const {\n useProvide: useSelectProvide,\n useInject: useSelectInject,\n key: selectContextKey,\n} = createContext<SelectContext>('Select')\n"],"mappings":";;AA4BA,IAAa,EACX,YAAY,kBACZ,WAAW,iBACX,KAAK,qBACH,cAA6B,SAAS"}
1
+ {"version":3,"file":"Select.context.js","names":[],"sources":["../../../src/components/select/Select.context.ts"],"sourcesContent":["import { createContext } from '../../utils/context'\nimport type { ComputedRef, Ref } from 'vue'\nimport type { selectVariants } from '@auronui/styles'\n\n/**\n * Acceptable value for a Select item / model. Numeric values are legitimate\n * (e.g. entity IDs) and are preserved end-to-end — they are only coerced to a\n * string where the underlying contract requires one (Reka `text-value`,\n * registry display labels).\n */\nexport type SelectItemValue = string | number\n\n/**\n * Data-driven item shape for the terse `items` prop. `value` accepts numeric\n * keys (see SelectItemValue). `label` is the display text (falls back to\n * String(value)); `textValue` overrides the type-ahead / pre-set label.\n */\nexport interface SelectItemData {\n value: SelectItemValue\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\nexport interface SelectContext {\n isDisabled: Ref<boolean>\n isInvalid: Ref<boolean>\n isReadonly: Ref<boolean>\n isRequired: Ref<boolean>\n fullWidth: Ref<boolean>\n hasLabel: Ref<boolean>\n labelPlacement: Ref<'inside' | 'outside' | 'outside-left'>\n triggerId: Ref<string>\n label: Ref<string | undefined>\n ariaDescribedBy: Ref<string | undefined>\n slots: ComputedRef<ReturnType<typeof selectVariants>>\n multiple: Ref<boolean>\n /**\n * Persistent registry of item value → display label. SelectItem registers\n * once on first mount and never unregisters — survives SelectContent\n * unmount so SelectValue can show the selected label while the popover\n * is closed (Reka clears its own optionsSet on unmount).\n */\n registerItem: (value: SelectItemValue, label: string) => void\n itemLabel: (value: SelectItemValue | SelectItemValue[] | undefined | null) => string\n removeValue: (value: SelectItemValue) => void\n}\n\nexport const {\n useProvide: useSelectProvide,\n useInject: useSelectInject,\n key: selectContextKey,\n} = createContext<SelectContext>('Select')\n"],"mappings":";;AAgDA,IAAa,EACX,YAAY,kBACZ,WAAW,iBACX,KAAK,qBACH,cAA6B,SAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/select/Select.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, reactive, toRef, useAttrs, useId } from 'vue'\nimport { SelectRoot } from 'reka-ui'\nimport { selectVariants, type SelectVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useSelectProvide } from './Select.context'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'flat',\n size: 'md',\n color: 'default',\n labelPlacement: 'inside',\n fullWidth: false,\n isInvalid: false,\n isDisabled: false,\n isReadonly: false,\n isRequired: false,\n multiple: false,\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | string[]]\n 'update:open': [value: boolean]\n}>()\n\ntype Props = {\n /** Visual style of the field. @default 'flat' */\n variant?: SelectVariants['variant']\n /** Field height. @default 'md' */\n size?: SelectVariants['size']\n /** Accent color applied to focus ring + floating label. @default 'default' */\n color?: SelectVariants['color']\n /**\n * Where the `label` is rendered relative to the field.\n * - `inside`: floats above the trigger (shrinks when focused/filled)\n * - `outside`: sits above the field, static\n * - `outside-left`: sits to the left, horizontal layout\n * @default 'inside'\n */\n labelPlacement?: SelectVariants['labelPlacement']\n /** Stretches root wrapper to 100% width. @default false */\n fullWidth?: boolean\n /** Marks the field as invalid. Triggers danger styling and enables `errorMessage`. @default false */\n isInvalid?: boolean\n /** Disables the field. @default false */\n isDisabled?: boolean\n /** Makes the field read-only. @default false */\n isReadonly?: boolean\n /** Adds a required asterisk next to the label. @default false */\n isRequired?: boolean\n /** Placeholder shown when no value is selected. */\n placeholder?: string\n /** Form field name, for native form submission. */\n name?: string\n /** Field label. When omitted, the floating-label behavior is skipped. */\n label?: string\n /** Helper text displayed below the field. Suppressed when `isInvalid && errorMessage` is shown. */\n description?: string\n /** Error text displayed below the field. Only rendered when `isInvalid` is also true. */\n errorMessage?: string\n /** Extra classes merged onto the root wrapper via `composeClassName`. */\n class?: string\n\n /* ─── Select-specific ─────────────────────────────────────── */\n /** Two-way bound selected value. */\n modelValue?: string | string[]\n /** Initial selected value (uncontrolled). */\n defaultValue?: string | string[]\n /** Allow selecting multiple values. modelValue becomes string[]. @default false */\n multiple?: boolean\n /** Controls open state of the dropdown. */\n open?: boolean\n /** Initial open state of the dropdown (uncontrolled). */\n defaultOpen?: boolean\n}\n\nconst attrs = useAttrs()\nconst generatedId = useId()\nconst triggerId = computed(() => (attrs.id as string | undefined) ?? generatedId)\n\nconst hasLabel = computed(() => !!props.label)\n\n// Helper IDs / aria wiring\nconst descriptionId = computed(() => `${triggerId.value}-description`)\nconst errorMessageId = computed(() => `${triggerId.value}-error`)\nconst showError = computed(() => props.isInvalid && !!props.errorMessage)\nconst showDescription = computed(() => !!props.description && !showError.value)\nconst hasHelper = computed(() => showError.value || showDescription.value)\nconst ariaDescribedBy = computed(() => {\n if (showError.value) return errorMessageId.value\n if (showDescription.value) return descriptionId.value\n return undefined\n})\n\nconst slotFns = computed(() =>\n selectVariants({\n variant: props.variant,\n size: props.size,\n color: props.color,\n fullWidth: props.fullWidth,\n isInvalid: props.isInvalid,\n isDisabled: props.isDisabled,\n isReadonly: props.isReadonly,\n hasLabel: hasLabel.value,\n labelPlacement: props.labelPlacement,\n }),\n)\n\nconst showOutsideLabel = computed(\n () => hasLabel.value && props.labelPlacement !== 'inside',\n)\n\n// Persistent item registry. SelectItem populates on first mount; entries\n// survive SelectContent unmount so SelectValue can render the label while\n// the popover is closed.\nconst itemRegistry = reactive(new Map<string, string>())\nconst registerItem = (value: string, label: string) => {\n itemRegistry.set(value, label)\n}\nconst itemLabel = (value: string | string[] | undefined | null): string => {\n if (value == null) return ''\n if (Array.isArray(value)) {\n return value.map(v => itemRegistry.get(v) ?? v).filter(Boolean).join(', ')\n }\n return itemRegistry.get(value) ?? value\n}\n\nfunction removeValue(value: string) {\n const current = Array.isArray(props.modelValue) ? props.modelValue : []\n emit('update:modelValue', current.filter(v => v !== value))\n}\n\nuseSelectProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n isReadonly: toRef(props, 'isReadonly'),\n isRequired: toRef(props, 'isRequired'),\n fullWidth: toRef(props, 'fullWidth'),\n hasLabel,\n labelPlacement: toRef(props, 'labelPlacement'),\n triggerId,\n label: toRef(props, 'label'),\n ariaDescribedBy,\n slots: slotFns,\n multiple: toRef(props, 'multiple'),\n registerItem,\n itemLabel,\n removeValue,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :data-invalid=\"isInvalid || undefined\"\n :data-disabled=\"isDisabled || undefined\"\n :data-readonly=\"isReadonly || undefined\"\n :data-required=\"isRequired || undefined\"\n :data-has-label=\"hasLabel || undefined\"\n :data-has-helper=\"hasHelper || undefined\"\n >\n <label\n v-if=\"showOutsideLabel\"\n :for=\"triggerId\"\n :class=\"slotFns.label()\"\n >{{ label }}<span\n v-if=\"isRequired\"\n aria-hidden=\"true\"\n > *</span></label>\n\n <div :class=\"slotFns.mainWrapper()\">\n <SelectRoot\n :model-value=\"props.modelValue\"\n :default-value=\"props.defaultValue\"\n :multiple=\"props.multiple\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :name=\"props.name\"\n @update:model-value=\"emit('update:modelValue', $event as string | string[])\"\n @update:open=\"emit('update:open', $event)\"\n >\n <slot />\n </SelectRoot>\n\n <div\n v-if=\"hasHelper\"\n :class=\"slotFns.helperWrapper()\"\n >\n <div\n v-if=\"showError\"\n :id=\"errorMessageId\"\n :class=\"slotFns.errorMessage()\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"showDescription\"\n :id=\"descriptionId\"\n :class=\"slotFns.description()\"\n >\n {{ description }}\n </div>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/select/Select.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, reactive, toRef, useAttrs, useId, useSlots } from 'vue'\nimport { SelectRoot } from 'reka-ui'\nimport { selectVariants, type SelectVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useSelectProvide, type SelectItemValue, type SelectItemData } from './Select.context'\nimport { hasSlotComponent } from '../../utils/hasSlotComponent'\nimport SelectTrigger from './SelectTrigger.vue'\nimport SelectValue from './SelectValue.vue'\nimport SelectContent from './SelectContent.vue'\nimport SelectItem from './SelectItem.vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'flat',\n size: 'md',\n color: 'default',\n labelPlacement: 'inside',\n fullWidth: false,\n isInvalid: false,\n isDisabled: false,\n isReadonly: false,\n isRequired: false,\n multiple: false,\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: SelectItemValue | SelectItemValue[]]\n 'update:open': [value: boolean]\n}>()\n\ntype Props = {\n /** Visual style of the field. @default 'flat' */\n variant?: SelectVariants['variant']\n /** Field height. @default 'md' */\n size?: SelectVariants['size']\n /** Accent color applied to focus ring + floating label. @default 'default' */\n color?: SelectVariants['color']\n /**\n * Where the `label` is rendered relative to the field.\n * - `inside`: floats above the trigger (shrinks when focused/filled)\n * - `outside`: sits above the field, static\n * - `outside-left`: sits to the left, horizontal layout\n * @default 'inside'\n */\n labelPlacement?: SelectVariants['labelPlacement']\n /** Stretches root wrapper to 100% width. @default false */\n fullWidth?: boolean\n /** Marks the field as invalid. Triggers danger styling and enables `errorMessage`. @default false */\n isInvalid?: boolean\n /** Disables the field. @default false */\n isDisabled?: boolean\n /** Makes the field read-only. @default false */\n isReadonly?: boolean\n /** Adds a required asterisk next to the label. @default false */\n isRequired?: boolean\n /** Placeholder shown when no value is selected. */\n placeholder?: string\n /** Form field name, for native form submission. */\n name?: string\n /** Field label. When omitted, the floating-label behavior is skipped. */\n label?: string\n /** Helper text displayed below the field. Suppressed when `isInvalid && errorMessage` is shown. */\n description?: string\n /** Error text displayed below the field. Only rendered when `isInvalid` is also true. */\n errorMessage?: string\n /** Extra classes merged onto the root wrapper via `composeClassName`. */\n class?: string\n\n /* ─── Select-specific ─────────────────────────────────────── */\n /** Two-way bound selected value. Accepts string or numeric keys. */\n modelValue?: SelectItemValue | SelectItemValue[]\n /** Initial selected value (uncontrolled). Accepts string or numeric keys. */\n defaultValue?: SelectItemValue | SelectItemValue[]\n /** Allow selecting multiple values. modelValue becomes string[]. @default false */\n multiple?: boolean\n /** Controls open state of the dropdown. */\n open?: boolean\n /** Initial open state of the dropdown (uncontrolled). */\n defaultOpen?: boolean\n /**\n * Data-driven items for the terse API. When provided (and no SelectTrigger /\n * SelectContent is passed as a child), the trigger, value, and popover are\n * rendered internally. Use the `#item` slot to customize per-item rendering.\n */\n items?: SelectItemData[]\n}\n\nconst attrs = useAttrs()\nconst generatedId = useId()\nconst triggerId = computed(() => (attrs.id as string | undefined) ?? generatedId)\n\nconst hasLabel = computed(() => !!props.label)\n\nconst slots = useSlots()\n// Tier 3 (advanced): consumer supplied explicit compound chrome → pass through.\n// Tier 1/2 (terse): render trigger/value/content internally.\nconst usesCustomChrome = computed(() =>\n hasSlotComponent(slots.default?.(), [SelectTrigger, SelectContent]),\n)\n\n// Helper IDs / aria wiring\nconst descriptionId = computed(() => `${triggerId.value}-description`)\nconst errorMessageId = computed(() => `${triggerId.value}-error`)\nconst showError = computed(() => props.isInvalid && !!props.errorMessage)\nconst showDescription = computed(() => !!props.description && !showError.value)\nconst hasHelper = computed(() => showError.value || showDescription.value)\nconst ariaDescribedBy = computed(() => {\n if (showError.value) return errorMessageId.value\n if (showDescription.value) return descriptionId.value\n return undefined\n})\n\nconst slotFns = computed(() =>\n selectVariants({\n variant: props.variant,\n size: props.size,\n color: props.color,\n fullWidth: props.fullWidth,\n isInvalid: props.isInvalid,\n isDisabled: props.isDisabled,\n isReadonly: props.isReadonly,\n hasLabel: hasLabel.value,\n labelPlacement: props.labelPlacement,\n }),\n)\n\nconst showOutsideLabel = computed(\n () => hasLabel.value && props.labelPlacement !== 'inside',\n)\n\n// Persistent item registry. SelectItem populates on first mount; entries\n// survive SelectContent unmount so SelectValue can render the label while\n// the popover is closed.\nconst itemRegistry = reactive(new Map<SelectItemValue, string>())\nconst registerItem = (value: SelectItemValue, label: string) => {\n itemRegistry.set(value, label)\n}\nconst itemLabel = (value: SelectItemValue | SelectItemValue[] | undefined | null): string => {\n if (value == null) return ''\n if (Array.isArray(value)) {\n // Fall back to the stringified value (handles numeric keys) when no label is\n // registered. Filter empty strings only — never use filter(Boolean), which\n // would drop a registered label for the numeric key 0.\n return value\n .map(v => String(itemRegistry.get(v) ?? v))\n .filter(s => s.length > 0)\n .join(', ')\n }\n return itemRegistry.get(value) ?? String(value)\n}\n\nfunction removeValue(value: SelectItemValue) {\n const current = Array.isArray(props.modelValue) ? props.modelValue : []\n emit('update:modelValue', current.filter(v => v !== value))\n}\n\nuseSelectProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n isReadonly: toRef(props, 'isReadonly'),\n isRequired: toRef(props, 'isRequired'),\n fullWidth: toRef(props, 'fullWidth'),\n hasLabel,\n labelPlacement: toRef(props, 'labelPlacement'),\n triggerId,\n label: toRef(props, 'label'),\n ariaDescribedBy,\n slots: slotFns,\n multiple: toRef(props, 'multiple'),\n registerItem,\n itemLabel,\n removeValue,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :data-invalid=\"isInvalid || undefined\"\n :data-disabled=\"isDisabled || undefined\"\n :data-readonly=\"isReadonly || undefined\"\n :data-required=\"isRequired || undefined\"\n :data-has-label=\"hasLabel || undefined\"\n :data-has-helper=\"hasHelper || undefined\"\n >\n <label\n v-if=\"showOutsideLabel\"\n :for=\"triggerId\"\n :class=\"slotFns.label()\"\n >{{ label }}<span\n v-if=\"isRequired\"\n aria-hidden=\"true\"\n > *</span></label>\n\n <div :class=\"slotFns.mainWrapper()\">\n <SelectRoot\n :model-value=\"props.modelValue\"\n :default-value=\"props.defaultValue\"\n :multiple=\"props.multiple\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :name=\"props.name\"\n @update:model-value=\"emit('update:modelValue', $event as SelectItemValue | SelectItemValue[])\"\n @update:open=\"emit('update:open', $event)\"\n >\n <!-- Tier 3: consumer-provided compound chrome -->\n <slot v-if=\"usesCustomChrome\" />\n <!-- Tier 1/2: internally rendered chrome -->\n <template v-else>\n <SelectTrigger>\n <SelectValue :placeholder=\"props.placeholder\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem\n v-for=\"item in props.items\"\n :key=\"item.value\"\n :value=\"item.value\"\n :text-value=\"item.textValue ?? item.label\"\n :is-disabled=\"item.isDisabled\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n >{{ item.label ?? String(item.value) }}</slot>\n </SelectItem>\n <slot />\n </SelectContent>\n </template>\n </SelectRoot>\n\n <div\n v-if=\"hasHelper\"\n :class=\"slotFns.helperWrapper()\"\n >\n <div\n v-if=\"showError\"\n :id=\"errorMessageId\"\n :class=\"slotFns.errorMessage()\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"showDescription\"\n :id=\"descriptionId\"\n :class=\"slotFns.description()\"\n >\n {{ description }}\n </div>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -1,6 +1,11 @@
1
1
  import { composeClassName } from "../../utils/composeClassName.js";
2
2
  import { useSelectProvide } from "./Select.context.js";
3
- import { computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, reactive, renderSlot, toDisplayString, toRef, unref, useAttrs, useId, withCtx } from "vue";
3
+ import { hasSlotComponent } from "../../utils/hasSlotComponent.js";
4
+ import SelectTrigger_default from "./SelectTrigger.js";
5
+ import SelectValue_default from "./SelectValue.js";
6
+ import SelectContent_default from "./SelectContent.js";
7
+ import SelectItem_default from "./SelectItem.js";
8
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, reactive, renderList, renderSlot, toDisplayString, toRef, unref, useAttrs, useId, useSlots, withCtx } from "vue";
4
9
  import { selectVariants } from "@auronui/styles";
5
10
  import { SelectRoot } from "reka-ui";
6
11
  //#region src/components/select/Select.vue?vue&type=script&setup=true&lang.ts
@@ -66,7 +71,8 @@ var Select_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
66
71
  defaultOpen: {
67
72
  type: Boolean,
68
73
  default: void 0
69
- }
74
+ },
75
+ items: { default: () => [] }
70
76
  },
71
77
  emits: ["update:modelValue", "update:open"],
72
78
  setup(__props, { emit: __emit }) {
@@ -76,6 +82,8 @@ var Select_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
76
82
  const generatedId = useId();
77
83
  const triggerId = computed(() => attrs.id ?? generatedId);
78
84
  const hasLabel = computed(() => !!props.label);
85
+ const slots = useSlots();
86
+ const usesCustomChrome = computed(() => hasSlotComponent(slots.default?.(), [SelectTrigger_default, SelectContent_default]));
79
87
  const descriptionId = computed(() => `${triggerId.value}-description`);
80
88
  const errorMessageId = computed(() => `${triggerId.value}-error`);
81
89
  const showError = computed(() => props.isInvalid && !!props.errorMessage);
@@ -103,8 +111,8 @@ var Select_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
103
111
  };
104
112
  const itemLabel = (value) => {
105
113
  if (value == null) return "";
106
- if (Array.isArray(value)) return value.map((v) => itemRegistry.get(v) ?? v).filter(Boolean).join(", ");
107
- return itemRegistry.get(value) ?? value;
114
+ if (Array.isArray(value)) return value.map((v) => String(itemRegistry.get(v) ?? v)).filter((s) => s.length > 0).join(", ");
115
+ return itemRegistry.get(value) ?? String(value);
108
116
  };
109
117
  function removeValue(value) {
110
118
  emit("update:modelValue", (Array.isArray(props.modelValue) ? props.modelValue : []).filter((v) => v !== value));
@@ -151,7 +159,27 @@ var Select_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
151
159
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => emit("update:modelValue", $event)),
152
160
  "onUpdate:open": _cache[1] || (_cache[1] = ($event) => emit("update:open", $event))
153
161
  }, {
154
- default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
162
+ default: withCtx(() => [usesCustomChrome.value ? renderSlot(_ctx.$slots, "default", { key: 0 }) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createVNode(SelectTrigger_default, null, {
163
+ default: withCtx(() => [createVNode(SelectValue_default, { placeholder: props.placeholder }, null, 8, ["placeholder"])]),
164
+ _: 1
165
+ }), createVNode(SelectContent_default, null, {
166
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(props.items, (item) => {
167
+ return openBlock(), createBlock(SelectItem_default, {
168
+ key: item.value,
169
+ value: item.value,
170
+ "text-value": item.textValue ?? item.label,
171
+ "is-disabled": item.isDisabled
172
+ }, {
173
+ default: withCtx(() => [renderSlot(_ctx.$slots, "item", { item }, () => [createTextVNode(toDisplayString(item.label ?? String(item.value)), 1)])]),
174
+ _: 2
175
+ }, 1032, [
176
+ "value",
177
+ "text-value",
178
+ "is-disabled"
179
+ ]);
180
+ }), 128)), renderSlot(_ctx.$slots, "default")]),
181
+ _: 3
182
+ })], 64))]),
155
183
  _: 3
156
184
  }, 8, [
157
185
  "model-value",