@auronui/vue 1.0.3 → 1.0.4
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.
- package/dist/cjs/index.cjs +440 -37
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/components/autocomplete/Autocomplete.context.js.map +1 -1
- package/dist/components/autocomplete/Autocomplete.js.map +1 -1
- package/dist/components/autocomplete/Autocomplete.vue_vue_type_script_setup_true_lang.js +24 -3
- package/dist/components/autocomplete/Autocomplete.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/autocomplete/AutocompleteContent.js.map +1 -1
- package/dist/components/autocomplete/AutocompleteContent.vue_vue_type_script_setup_true_lang.js +39 -1
- package/dist/components/autocomplete/AutocompleteContent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/autocomplete/AutocompleteItem.js.map +1 -1
- package/dist/components/autocomplete/AutocompleteItem.vue_vue_type_script_setup_true_lang.js +24 -4
- package/dist/components/autocomplete/AutocompleteItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/combo-box/ComboBox.context.js.map +1 -1
- package/dist/components/combo-box/ComboBox.js.map +1 -1
- package/dist/components/combo-box/ComboBox.vue_vue_type_script_setup_true_lang.js +46 -11
- package/dist/components/combo-box/ComboBox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/combo-box/ComboBoxContent.js.map +1 -1
- package/dist/components/combo-box/ComboBoxContent.vue_vue_type_script_setup_true_lang.js +35 -1
- package/dist/components/combo-box/ComboBoxContent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/combo-box/ComboBoxItem.js.map +1 -1
- package/dist/components/combo-box/ComboBoxItem.vue_vue_type_script_setup_true_lang.js +27 -5
- package/dist/components/combo-box/ComboBoxItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/date-time-picker/DateTimePicker.js.map +1 -1
- package/dist/components/date-time-picker/DateTimePicker.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/date-time-picker/DateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/form/Form.js +7 -0
- package/dist/components/form/Form.js.map +1 -0
- package/dist/components/form/Form.vue_vue_type_script_setup_true_lang.js +97 -0
- package/dist/components/form/Form.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/form/FormField.js +7 -0
- package/dist/components/form/FormField.js.map +1 -0
- package/dist/components/form/FormField.vue_vue_type_script_setup_true_lang.js +75 -0
- package/dist/components/form/FormField.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/form/form.context.js +15 -0
- package/dist/components/form/form.context.js.map +1 -0
- package/dist/components/form/validation.js +71 -0
- package/dist/components/form/validation.js.map +1 -0
- package/dist/components/select/SelectContent.js.map +1 -1
- package/dist/components/select/SelectContent.vue_vue_type_script_setup_true_lang.js +7 -14
- package/dist/components/select/SelectContent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/select/SelectItem.js.map +1 -1
- package/dist/components/select/SelectItem.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/components/select/SelectItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/select/SelectValue.js.map +1 -1
- package/dist/components/select/SelectValue.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/select/SelectValue.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/index.js +3 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Autocomplete.context.js","names":[],"sources":["../../../src/components/autocomplete/Autocomplete.context.ts"],"sourcesContent":["import { createContext } from '../../utils/context'\nimport type { ComputedRef, Ref } from 'vue'\nimport type { autocompleteVariants } from '@auronui/styles'\n\nexport interface AutocompleteContext {\n isDisabled: Ref<boolean>\n isInvalid: Ref<boolean>\n isReadonly: Ref<boolean>\n isRequired: Ref<boolean>\n isLoading: Ref<boolean>\n isFilled: Ref<boolean>\n fullWidth: Ref<boolean>\n hasLabel: Ref<boolean>\n labelPlacement: Ref<'inside' | 'outside' | 'outside-left'>\n inputId: Ref<string>\n label: Ref<string | undefined>\n ariaDescribedBy: Ref<string | undefined>\n truncateItems: Ref<boolean>\n hasItems: Ref<boolean>\n slots: ComputedRef<ReturnType<typeof autocompleteVariants>>\n}\n\nexport const {\n useProvide: useAutocompleteProvide,\n useInject: useAutocompleteInject,\n key: autocompleteContextKey,\n} = createContext<AutocompleteContext>('Autocomplete')\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"Autocomplete.context.js","names":[],"sources":["../../../src/components/autocomplete/Autocomplete.context.ts"],"sourcesContent":["import { createContext } from '../../utils/context'\nimport type { ComputedRef, Ref } from 'vue'\nimport type { autocompleteVariants } from '@auronui/styles'\n\nexport interface AutocompleteContext {\n isDisabled: Ref<boolean>\n isInvalid: Ref<boolean>\n isReadonly: Ref<boolean>\n isRequired: Ref<boolean>\n isLoading: Ref<boolean>\n isFilled: Ref<boolean>\n fullWidth: Ref<boolean>\n hasLabel: Ref<boolean>\n labelPlacement: Ref<'inside' | 'outside' | 'outside-left'>\n inputId: Ref<string>\n label: Ref<string | undefined>\n ariaDescribedBy: Ref<string | undefined>\n truncateItems: Ref<boolean>\n hasItems: Ref<boolean>\n slots: ComputedRef<ReturnType<typeof autocompleteVariants>>\n /**\n * Called by AutocompleteItem at mount time to register a value→label pair.\n * Used by the bridge's valueFor() when no `items` prop entry matches.\n */\n registerItem: (value: string, label: string) => void\n /**\n * Called by AutocompleteItem at unmount time to deregister.\n */\n unregisterItem: (value: string) => void\n}\n\nexport const {\n useProvide: useAutocompleteProvide,\n useInject: useAutocompleteInject,\n key: autocompleteContextKey,\n} = createContext<AutocompleteContext>('Autocomplete')\n"],"mappings":";;AA+BA,IAAa,EACX,YAAY,wBACZ,WAAW,uBACX,KAAK,2BACH,cAAmC,eAAe"}
|
|
@@ -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 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]\n 'update:open': [value: boolean]\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. */\n modelValue?: string\n /** Initial selected value (uncontrolled). */\n defaultValue?: string\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// Internal async state\nconst isLoading = ref(false)\nconst internalItems = ref<AutocompleteItem[]>([...props.items])\n\n// Open-state tracking so we can skip client-side filtering until the user types\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\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// Internal display text — bound to AutocompleteRoot's model-value.\n// Holds the LABEL (what the user reads), not the value. Bridged below.\nfunction labelFor(value: string | undefined): string {\n if (value == null || value === '') return ''\n const match = internalItems.value.find((i) => i.value === value)\n return match?.label ?? match?.textValue ?? 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 return match?.value ?? displayed\n}\n\nconst searchTerm = ref(labelFor(props.modelValue))\n\nconst isFilled = computed(() => !!searchTerm.value)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\n// Helper IDs / aria wiring\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// Parent → internal: when the v-model value changes, reflect its label\nwatch(() => props.modelValue, (val) => {\n const next = labelFor(val)\n if (searchTerm.value !== next) searchTerm.value = next\n})\n\n// Internal → parent: when the displayed label changes, emit the real value.\n// Also detect user typing (for open-time filtering suppression).\nwatch(searchTerm, (displayed) => {\n const next = valueFor(displayed)\n if (next !== (props.modelValue ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) {\n isUserTyping.value = true\n }\n})\n\nfunction handleOpenChange(val: boolean) {\n isOpen.value = val\n if (val) {\n termAtOpen.value = searchTerm.value\n isUserTyping.value = false\n } else {\n isUserTyping.value = false\n }\n emit('update:open', val)\n}\n\n// Debounce timer\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 // Run immediately (for tests and zero-debounce configs)\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\n// Initial load on mount\nonMounted(() => {\n if (props.loadItems) {\n void runLoadItems(searchTerm.value)\n }\n})\n\n// Watch searchTerm changes and invoke loadItems (debounced)\nwatch(searchTerm, (q) => {\n if (props.loadItems) {\n scheduleLoad(q)\n }\n})\n\n// Sync static items when they change\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) {\n internalItems.value = [...newItems]\n }\n})\n\n// When items arrive (async) or change, re-resolve the display label\nwatch(internalItems, () => {\n const next = labelFor(props.modelValue)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (props.modelValue ?? '')) {\n searchTerm.value = next\n }\n})\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})\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 is Reka UI's distinct autocomplete primitive.\n ignoreFilter=true ensures server-returned items are never filtered\n client-side (the server handles filtering). -->\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\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 } 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 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]\n 'update:open': [value: boolean]\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. */\n modelValue?: string\n /** Initial selected value (uncontrolled). */\n defaultValue?: string\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// Open-state tracking so we can skip client-side filtering until the user types\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\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// Internal display text — bound to AutocompleteRoot's model-value.\n// Holds the LABEL (what the user reads), not the value. Bridged below.\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 // Check items prop first\n const match = internalItems.value.find(\n (i) => (i.label ?? i.textValue ?? i.value) === displayed,\n )\n if (match) return match.value\n // Check slot registry: find value whose label equals displayed\n for (const [value, label] of slotItemRegistry.value) {\n if (label === displayed) return value\n }\n return displayed\n}\n\nconst searchTerm = ref(labelFor(props.modelValue))\n\nconst isFilled = computed(() => !!searchTerm.value)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\n// Helper IDs / aria wiring\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// Parent → internal: when the v-model value changes, reflect its label\nwatch(() => props.modelValue, (val) => {\n const next = labelFor(val)\n if (searchTerm.value !== next) searchTerm.value = next\n})\n\n// Internal → parent: when the displayed label changes, emit the real value.\n// Also detect user typing (for open-time filtering suppression).\nwatch(searchTerm, (displayed) => {\n const next = valueFor(displayed)\n if (next !== (props.modelValue ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) {\n isUserTyping.value = true\n }\n})\n\nfunction handleOpenChange(val: boolean) {\n isOpen.value = val\n if (val) {\n termAtOpen.value = searchTerm.value\n isUserTyping.value = false\n } else {\n isUserTyping.value = false\n }\n emit('update:open', val)\n}\n\n// Debounce timer\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 // Run immediately (for tests and zero-debounce configs)\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\n// Initial load on mount\nonMounted(() => {\n if (props.loadItems) {\n void runLoadItems(searchTerm.value)\n }\n})\n\n// Watch searchTerm changes and invoke loadItems (debounced)\nwatch(searchTerm, (q) => {\n if (props.loadItems) {\n scheduleLoad(q)\n }\n})\n\n// Sync static items when they change\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) {\n internalItems.value = [...newItems]\n }\n})\n\n// When items arrive (async) or change, re-resolve the display label\nwatch(internalItems, () => {\n const next = labelFor(props.modelValue)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (props.modelValue ?? '')) {\n searchTerm.value = next\n }\n})\n\n// When slot items register (children mount after parent), re-resolve the display label.\n// This covers the case where modelValue is set before children have mounted.\nwatch(slotItemRegistry, () => {\n const next = labelFor(props.modelValue)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (props.modelValue ?? '')) {\n searchTerm.value = next\n }\n})\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 registerItem,\n unregisterItem,\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 is Reka UI's distinct autocomplete primitive.\n ignoreFilter=true ensures server-returned items are never filtered\n client-side (the server handles filtering). -->\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\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":""}
|
|
@@ -86,6 +86,17 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
86
86
|
const generatedId = useId();
|
|
87
87
|
const inputId = computed(() => attrs.id ?? generatedId);
|
|
88
88
|
const hasLabel = computed(() => !!props.label);
|
|
89
|
+
const slotItemRegistry = ref(/* @__PURE__ */ new Map());
|
|
90
|
+
function registerItem(value, label) {
|
|
91
|
+
const next = new Map(slotItemRegistry.value);
|
|
92
|
+
next.set(value, label);
|
|
93
|
+
slotItemRegistry.value = next;
|
|
94
|
+
}
|
|
95
|
+
function unregisterItem(value) {
|
|
96
|
+
const next = new Map(slotItemRegistry.value);
|
|
97
|
+
next.delete(value);
|
|
98
|
+
slotItemRegistry.value = next;
|
|
99
|
+
}
|
|
89
100
|
const isLoading = ref(false);
|
|
90
101
|
const internalItems = ref([...props.items]);
|
|
91
102
|
const isOpen = ref(props.defaultOpen ?? false);
|
|
@@ -99,11 +110,15 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
99
110
|
function labelFor(value) {
|
|
100
111
|
if (value == null || value === "") return "";
|
|
101
112
|
const match = internalItems.value.find((i) => i.value === value);
|
|
102
|
-
return match
|
|
113
|
+
if (match) return match.label ?? match.textValue ?? value;
|
|
114
|
+
return slotItemRegistry.value.get(value) ?? value;
|
|
103
115
|
}
|
|
104
116
|
function valueFor(displayed) {
|
|
105
117
|
if (!displayed) return "";
|
|
106
|
-
|
|
118
|
+
const match = internalItems.value.find((i) => (i.label ?? i.textValue ?? i.value) === displayed);
|
|
119
|
+
if (match) return match.value;
|
|
120
|
+
for (const [value, label] of slotItemRegistry.value) if (label === displayed) return value;
|
|
121
|
+
return displayed;
|
|
107
122
|
}
|
|
108
123
|
const searchTerm = ref(labelFor(props.modelValue));
|
|
109
124
|
const isFilled = computed(() => !!searchTerm.value);
|
|
@@ -163,6 +178,10 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
163
178
|
const next = labelFor(props.modelValue);
|
|
164
179
|
if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (props.modelValue ?? "")) searchTerm.value = next;
|
|
165
180
|
});
|
|
181
|
+
watch(slotItemRegistry, () => {
|
|
182
|
+
const next = labelFor(props.modelValue);
|
|
183
|
+
if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (props.modelValue ?? "")) searchTerm.value = next;
|
|
184
|
+
});
|
|
166
185
|
const slotFns = computed(() => autocompleteVariants({
|
|
167
186
|
variant: props.variant,
|
|
168
187
|
size: props.size,
|
|
@@ -190,7 +209,9 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
190
209
|
ariaDescribedBy,
|
|
191
210
|
truncateItems: toRef(props, "truncateItems"),
|
|
192
211
|
hasItems,
|
|
193
|
-
slots: slotFns
|
|
212
|
+
slots: slotFns,
|
|
213
|
+
registerItem,
|
|
214
|
+
unregisterItem
|
|
194
215
|
});
|
|
195
216
|
return (_ctx, _cache) => {
|
|
196
217
|
return openBlock(), createElementBlock("div", {
|
package/dist/components/autocomplete/Autocomplete.vue_vue_type_script_setup_true_lang.js.map
CHANGED
|
@@ -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 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]\n 'update:open': [value: boolean]\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. */\n modelValue?: string\n /** Initial selected value (uncontrolled). */\n defaultValue?: string\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// Internal async state\nconst isLoading = ref(false)\nconst internalItems = ref<AutocompleteItem[]>([...props.items])\n\n// Open-state tracking so we can skip client-side filtering until the user types\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\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// Internal display text — bound to AutocompleteRoot's model-value.\n// Holds the LABEL (what the user reads), not the value. Bridged below.\nfunction labelFor(value: string | undefined): string {\n if (value == null || value === '') return ''\n const match = internalItems.value.find((i) => i.value === value)\n return match?.label ?? match?.textValue ?? 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 return match?.value ?? displayed\n}\n\nconst searchTerm = ref(labelFor(props.modelValue))\n\nconst isFilled = computed(() => !!searchTerm.value)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\n// Helper IDs / aria wiring\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// Parent → internal: when the v-model value changes, reflect its label\nwatch(() => props.modelValue, (val) => {\n const next = labelFor(val)\n if (searchTerm.value !== next) searchTerm.value = next\n})\n\n// Internal → parent: when the displayed label changes, emit the real value.\n// Also detect user typing (for open-time filtering suppression).\nwatch(searchTerm, (displayed) => {\n const next = valueFor(displayed)\n if (next !== (props.modelValue ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) {\n isUserTyping.value = true\n }\n})\n\nfunction handleOpenChange(val: boolean) {\n isOpen.value = val\n if (val) {\n termAtOpen.value = searchTerm.value\n isUserTyping.value = false\n } else {\n isUserTyping.value = false\n }\n emit('update:open', val)\n}\n\n// Debounce timer\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 // Run immediately (for tests and zero-debounce configs)\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\n// Initial load on mount\nonMounted(() => {\n if (props.loadItems) {\n void runLoadItems(searchTerm.value)\n }\n})\n\n// Watch searchTerm changes and invoke loadItems (debounced)\nwatch(searchTerm, (q) => {\n if (props.loadItems) {\n scheduleLoad(q)\n }\n})\n\n// Sync static items when they change\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) {\n internalItems.value = [...newItems]\n }\n})\n\n// When items arrive (async) or change, re-resolve the display label\nwatch(internalItems, () => {\n const next = labelFor(props.modelValue)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (props.modelValue ?? '')) {\n searchTerm.value = next\n }\n})\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})\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 is Reka UI's distinct autocomplete primitive.\n ignoreFilter=true ensures server-returned items are never filtered\n client-side (the server handles filtering). -->\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\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 } 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 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]\n 'update:open': [value: boolean]\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. */\n modelValue?: string\n /** Initial selected value (uncontrolled). */\n defaultValue?: string\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// Open-state tracking so we can skip client-side filtering until the user types\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\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// Internal display text — bound to AutocompleteRoot's model-value.\n// Holds the LABEL (what the user reads), not the value. Bridged below.\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 // Check items prop first\n const match = internalItems.value.find(\n (i) => (i.label ?? i.textValue ?? i.value) === displayed,\n )\n if (match) return match.value\n // Check slot registry: find value whose label equals displayed\n for (const [value, label] of slotItemRegistry.value) {\n if (label === displayed) return value\n }\n return displayed\n}\n\nconst searchTerm = ref(labelFor(props.modelValue))\n\nconst isFilled = computed(() => !!searchTerm.value)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\n// Helper IDs / aria wiring\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// Parent → internal: when the v-model value changes, reflect its label\nwatch(() => props.modelValue, (val) => {\n const next = labelFor(val)\n if (searchTerm.value !== next) searchTerm.value = next\n})\n\n// Internal → parent: when the displayed label changes, emit the real value.\n// Also detect user typing (for open-time filtering suppression).\nwatch(searchTerm, (displayed) => {\n const next = valueFor(displayed)\n if (next !== (props.modelValue ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) {\n isUserTyping.value = true\n }\n})\n\nfunction handleOpenChange(val: boolean) {\n isOpen.value = val\n if (val) {\n termAtOpen.value = searchTerm.value\n isUserTyping.value = false\n } else {\n isUserTyping.value = false\n }\n emit('update:open', val)\n}\n\n// Debounce timer\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 // Run immediately (for tests and zero-debounce configs)\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\n// Initial load on mount\nonMounted(() => {\n if (props.loadItems) {\n void runLoadItems(searchTerm.value)\n }\n})\n\n// Watch searchTerm changes and invoke loadItems (debounced)\nwatch(searchTerm, (q) => {\n if (props.loadItems) {\n scheduleLoad(q)\n }\n})\n\n// Sync static items when they change\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) {\n internalItems.value = [...newItems]\n }\n})\n\n// When items arrive (async) or change, re-resolve the display label\nwatch(internalItems, () => {\n const next = labelFor(props.modelValue)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (props.modelValue ?? '')) {\n searchTerm.value = next\n }\n})\n\n// When slot items register (children mount after parent), re-resolve the display label.\n// This covers the case where modelValue is set before children have mounted.\nwatch(slotItemRegistry, () => {\n const next = labelFor(props.modelValue)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (props.modelValue ?? '')) {\n searchTerm.value = next\n }\n})\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 registerItem,\n unregisterItem,\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 is Reka UI's distinct autocomplete primitive.\n ignoreFilter=true ensures server-returned items are never filtered\n client-side (the server handles filtering). -->\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.open\"\n :default-open=\"props.defaultOpen\"\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteContent.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteContent.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { AutocompletePortal, AutocompleteContent, AutocompleteViewport, AutocompleteEmpty, injectComboboxRootContext } from 'reka-ui'\nimport { motion, AnimatePresence } from 'motion-v'\nimport { useAutocompleteInject } from './Autocomplete.context'\n\nconst props = withDefaults(defineProps<{\n sideOffset?: number\n class?: string\n}>(), {\n sideOffset: 8,\n class: undefined,\n})\n\nconst ctx = useAutocompleteInject()\n// AutocompleteRoot internally provides the ComboboxRoot context\nconst rootContext = injectComboboxRootContext()\n</script>\n\n<template>\n <AutocompletePortal>\n <AnimatePresence>\n <AutocompleteContent\n v-if=\"rootContext.open.value && (ctx.hasItems.value || (ctx.isFilled.value && !ctx.isLoading.value))\"\n position=\"popper\"\n :side-offset=\"props.sideOffset\"\n as-child\n data-slot=\"popover\"\n >\n <motion.div\n :class=\"['autocomplete__popover', 'relative']\"\n :data-loading=\"ctx.isLoading.value ? '' : undefined\"\n :data-truncate-items=\"ctx.truncateItems.value ? undefined : 'false'\"\n :aria-busy=\"ctx.isLoading.value || undefined\"\n :initial=\"{ opacity: 0, scale: 0.95 }\"\n :animate=\"{ opacity: 1, scale: 1 }\"\n :exit=\"{ opacity: 0, scale: 0.95 }\"\n :transition=\"{ duration: 0.15 }\"\n >\n <div\n :class=\"[\n 'transition-opacity duration-150',\n ctx.isLoading.value\n ? 'pointer-events-none opacity-50 grayscale cursor-not-allowed select-none'\n : '',\n ]\"\n :inert=\"ctx.isLoading.value || undefined\"\n :aria-disabled=\"ctx.isLoading.value || undefined\"\n :data-disabled=\"ctx.isLoading.value ? '' : undefined\"\n data-slot=\"list-wrapper\"\n >\n <AutocompleteViewport\n data-slot=\"list-box\"\n >\n <slot />\n <!-- Empty state: only show when the user has typed a query -->\n <AutocompleteEmpty\n v-if=\"ctx.isFilled.value && !ctx.isLoading.value\"\n class=\"py-3 text-center text-sm text-default-400\"\n data-slot=\"empty-content\"\n >\n <slot name=\"empty\">\n No results found\n </slot>\n </AutocompleteEmpty>\n </AutocompleteViewport>\n </div>\n </motion.div>\n </AutocompleteContent>\n </AnimatePresence>\n </AutocompletePortal>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"AutocompleteContent.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteContent.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { AutocompletePortal, AutocompleteContent, AutocompleteViewport, AutocompleteEmpty, injectComboboxRootContext } from 'reka-ui'\nimport { motion, AnimatePresence } from 'motion-v'\nimport { useSlots, watchEffect, type VNode } from 'vue'\nimport { useAutocompleteInject } from './Autocomplete.context'\n\nconst props = withDefaults(defineProps<{\n sideOffset?: number\n class?: string\n}>(), {\n sideOffset: 8,\n class: undefined,\n})\n\nconst ctx = useAutocompleteInject()\n// AutocompleteRoot internally provides the ComboboxRoot context\nconst rootContext = injectComboboxRootContext()\n\n// Pre-walk slot VNodes to extract value→label pairs synchronously.\n// This runs before the portal opens so the bridge can resolve labels on initial render.\nconst slots = useSlots()\n\nfunction extractNodeText(nodes: VNode[]): string {\n return nodes.map(n => {\n if (typeof n.children === 'string') return n.children\n if (Array.isArray(n.children)) return extractNodeText(n.children as VNode[])\n return ''\n }).join('')\n}\n\nfunction walkAndRegister(nodes: VNode[]) {\n for (const node of nodes) {\n // AutocompleteItem VNodes have a `value` prop; extract their text children\n if (node.props && typeof node.props.value === 'string') {\n const value = node.props.value as string\n const label = node.props.label as string | undefined\n if (label) {\n ctx.registerItem(value, label)\n } else {\n // Extract text from the default slot children of this VNode\n const children = node.children\n if (children && typeof children === 'object' && 'default' in children) {\n const slotFn = (children as Record<string, () => VNode[]>).default\n if (typeof slotFn === 'function') {\n const text = extractNodeText(slotFn()).trim()\n if (text) ctx.registerItem(value, text)\n }\n } else if (typeof children === 'string') {\n const text = children.trim()\n if (text) ctx.registerItem(value, text)\n } else if (Array.isArray(children)) {\n const text = extractNodeText(children as VNode[]).trim()\n if (text) ctx.registerItem(value, text)\n }\n }\n }\n // Recurse into children\n if (Array.isArray(node.children)) {\n walkAndRegister(node.children as VNode[])\n }\n }\n}\n\n// Run synchronously at setup time and whenever the slot content changes\nwatchEffect(() => {\n const vnodes = slots.default?.()\n if (vnodes) walkAndRegister(vnodes)\n})\n</script>\n\n<template>\n <AutocompletePortal>\n <AnimatePresence>\n <AutocompleteContent\n v-if=\"rootContext.open.value && (ctx.hasItems.value || (ctx.isFilled.value && !ctx.isLoading.value))\"\n position=\"popper\"\n :side-offset=\"props.sideOffset\"\n as-child\n data-slot=\"popover\"\n >\n <motion.div\n :class=\"['autocomplete__popover', 'relative']\"\n :data-loading=\"ctx.isLoading.value ? '' : undefined\"\n :data-truncate-items=\"ctx.truncateItems.value ? undefined : 'false'\"\n :aria-busy=\"ctx.isLoading.value || undefined\"\n :initial=\"{ opacity: 0, scale: 0.95 }\"\n :animate=\"{ opacity: 1, scale: 1 }\"\n :exit=\"{ opacity: 0, scale: 0.95 }\"\n :transition=\"{ duration: 0.15 }\"\n >\n <div\n :class=\"[\n 'transition-opacity duration-150',\n ctx.isLoading.value\n ? 'pointer-events-none opacity-50 grayscale cursor-not-allowed select-none'\n : '',\n ]\"\n :inert=\"ctx.isLoading.value || undefined\"\n :aria-disabled=\"ctx.isLoading.value || undefined\"\n :data-disabled=\"ctx.isLoading.value ? '' : undefined\"\n data-slot=\"list-wrapper\"\n >\n <AutocompleteViewport\n data-slot=\"list-box\"\n >\n <slot />\n <!-- Empty state: only show when the user has typed a query -->\n <AutocompleteEmpty\n v-if=\"ctx.isFilled.value && !ctx.isLoading.value\"\n class=\"py-3 text-center text-sm text-default-400\"\n data-slot=\"empty-content\"\n >\n <slot name=\"empty\">\n No results found\n </slot>\n </AutocompleteEmpty>\n </AutocompleteViewport>\n </div>\n </motion.div>\n </AutocompleteContent>\n </AnimatePresence>\n </AutocompletePortal>\n</template>\n"],"mappings":""}
|
package/dist/components/autocomplete/AutocompleteContent.vue_vue_type_script_setup_true_lang.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { motion } from "../../node_modules/.pnpm/motion-v@2.2.1_@vueuse_core@14.2.1_vue@3.5.32_typescript@6.0.2___react-dom@19.2.5_react_8b28b23614a2152514812dba6ef76a55/node_modules/motion-v/dist/es/components/motion/index.js";
|
|
2
2
|
import { AnimatePresence_default } from "../../node_modules/.pnpm/motion-v@2.2.1_@vueuse_core@14.2.1_vue@3.5.32_typescript@6.0.2___react-dom@19.2.5_react_8b28b23614a2152514812dba6ef76a55/node_modules/motion-v/dist/es/components/animate-presence/AnimatePresence.js";
|
|
3
3
|
import { useAutocompleteInject } from "./Autocomplete.context.js";
|
|
4
|
-
import { createBlock, createCommentVNode, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, renderSlot, unref, withCtx } from "vue";
|
|
4
|
+
import { createBlock, createCommentVNode, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, renderSlot, unref, useSlots, watchEffect, withCtx } from "vue";
|
|
5
5
|
import { AutocompleteContent, AutocompleteEmpty, AutocompletePortal, AutocompleteViewport, injectComboboxRootContext } from "reka-ui";
|
|
6
6
|
//#region src/components/autocomplete/AutocompleteContent.vue?vue&type=script&setup=true&lang.ts
|
|
7
7
|
var _hoisted_1 = [
|
|
@@ -19,6 +19,44 @@ var AutocompleteContent_vue_vue_type_script_setup_true_lang_default = /* @__PURE
|
|
|
19
19
|
const props = __props;
|
|
20
20
|
const ctx = useAutocompleteInject();
|
|
21
21
|
const rootContext = injectComboboxRootContext();
|
|
22
|
+
const slots = useSlots();
|
|
23
|
+
function extractNodeText(nodes) {
|
|
24
|
+
return nodes.map((n) => {
|
|
25
|
+
if (typeof n.children === "string") return n.children;
|
|
26
|
+
if (Array.isArray(n.children)) return extractNodeText(n.children);
|
|
27
|
+
return "";
|
|
28
|
+
}).join("");
|
|
29
|
+
}
|
|
30
|
+
function walkAndRegister(nodes) {
|
|
31
|
+
for (const node of nodes) {
|
|
32
|
+
if (node.props && typeof node.props.value === "string") {
|
|
33
|
+
const value = node.props.value;
|
|
34
|
+
const label = node.props.label;
|
|
35
|
+
if (label) ctx.registerItem(value, label);
|
|
36
|
+
else {
|
|
37
|
+
const children = node.children;
|
|
38
|
+
if (children && typeof children === "object" && "default" in children) {
|
|
39
|
+
const slotFn = children.default;
|
|
40
|
+
if (typeof slotFn === "function") {
|
|
41
|
+
const text = extractNodeText(slotFn()).trim();
|
|
42
|
+
if (text) ctx.registerItem(value, text);
|
|
43
|
+
}
|
|
44
|
+
} else if (typeof children === "string") {
|
|
45
|
+
const text = children.trim();
|
|
46
|
+
if (text) ctx.registerItem(value, text);
|
|
47
|
+
} else if (Array.isArray(children)) {
|
|
48
|
+
const text = extractNodeText(children).trim();
|
|
49
|
+
if (text) ctx.registerItem(value, text);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(node.children)) walkAndRegister(node.children);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
watchEffect(() => {
|
|
57
|
+
const vnodes = slots.default?.();
|
|
58
|
+
if (vnodes) walkAndRegister(vnodes);
|
|
59
|
+
});
|
|
22
60
|
return (_ctx, _cache) => {
|
|
23
61
|
return openBlock(), createBlock(unref(AutocompletePortal), null, {
|
|
24
62
|
default: withCtx(() => [createVNode(unref(AnimatePresence_default), null, {
|
package/dist/components/autocomplete/AutocompleteContent.vue_vue_type_script_setup_true_lang.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteContent.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteContent.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { AutocompletePortal, AutocompleteContent, AutocompleteViewport, AutocompleteEmpty, injectComboboxRootContext } from 'reka-ui'\nimport { motion, AnimatePresence } from 'motion-v'\nimport { useAutocompleteInject } from './Autocomplete.context'\n\nconst props = withDefaults(defineProps<{\n sideOffset?: number\n class?: string\n}>(), {\n sideOffset: 8,\n class: undefined,\n})\n\nconst ctx = useAutocompleteInject()\n// AutocompleteRoot internally provides the ComboboxRoot context\nconst rootContext = injectComboboxRootContext()\n</script>\n\n<template>\n <AutocompletePortal>\n <AnimatePresence>\n <AutocompleteContent\n v-if=\"rootContext.open.value && (ctx.hasItems.value || (ctx.isFilled.value && !ctx.isLoading.value))\"\n position=\"popper\"\n :side-offset=\"props.sideOffset\"\n as-child\n data-slot=\"popover\"\n >\n <motion.div\n :class=\"['autocomplete__popover', 'relative']\"\n :data-loading=\"ctx.isLoading.value ? '' : undefined\"\n :data-truncate-items=\"ctx.truncateItems.value ? undefined : 'false'\"\n :aria-busy=\"ctx.isLoading.value || undefined\"\n :initial=\"{ opacity: 0, scale: 0.95 }\"\n :animate=\"{ opacity: 1, scale: 1 }\"\n :exit=\"{ opacity: 0, scale: 0.95 }\"\n :transition=\"{ duration: 0.15 }\"\n >\n <div\n :class=\"[\n 'transition-opacity duration-150',\n ctx.isLoading.value\n ? 'pointer-events-none opacity-50 grayscale cursor-not-allowed select-none'\n : '',\n ]\"\n :inert=\"ctx.isLoading.value || undefined\"\n :aria-disabled=\"ctx.isLoading.value || undefined\"\n :data-disabled=\"ctx.isLoading.value ? '' : undefined\"\n data-slot=\"list-wrapper\"\n >\n <AutocompleteViewport\n data-slot=\"list-box\"\n >\n <slot />\n <!-- Empty state: only show when the user has typed a query -->\n <AutocompleteEmpty\n v-if=\"ctx.isFilled.value && !ctx.isLoading.value\"\n class=\"py-3 text-center text-sm text-default-400\"\n data-slot=\"empty-content\"\n >\n <slot name=\"empty\">\n No results found\n </slot>\n </AutocompleteEmpty>\n </AutocompleteViewport>\n </div>\n </motion.div>\n </AutocompleteContent>\n </AnimatePresence>\n </AutocompletePortal>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"AutocompleteContent.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteContent.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { AutocompletePortal, AutocompleteContent, AutocompleteViewport, AutocompleteEmpty, injectComboboxRootContext } from 'reka-ui'\nimport { motion, AnimatePresence } from 'motion-v'\nimport { useSlots, watchEffect, type VNode } from 'vue'\nimport { useAutocompleteInject } from './Autocomplete.context'\n\nconst props = withDefaults(defineProps<{\n sideOffset?: number\n class?: string\n}>(), {\n sideOffset: 8,\n class: undefined,\n})\n\nconst ctx = useAutocompleteInject()\n// AutocompleteRoot internally provides the ComboboxRoot context\nconst rootContext = injectComboboxRootContext()\n\n// Pre-walk slot VNodes to extract value→label pairs synchronously.\n// This runs before the portal opens so the bridge can resolve labels on initial render.\nconst slots = useSlots()\n\nfunction extractNodeText(nodes: VNode[]): string {\n return nodes.map(n => {\n if (typeof n.children === 'string') return n.children\n if (Array.isArray(n.children)) return extractNodeText(n.children as VNode[])\n return ''\n }).join('')\n}\n\nfunction walkAndRegister(nodes: VNode[]) {\n for (const node of nodes) {\n // AutocompleteItem VNodes have a `value` prop; extract their text children\n if (node.props && typeof node.props.value === 'string') {\n const value = node.props.value as string\n const label = node.props.label as string | undefined\n if (label) {\n ctx.registerItem(value, label)\n } else {\n // Extract text from the default slot children of this VNode\n const children = node.children\n if (children && typeof children === 'object' && 'default' in children) {\n const slotFn = (children as Record<string, () => VNode[]>).default\n if (typeof slotFn === 'function') {\n const text = extractNodeText(slotFn()).trim()\n if (text) ctx.registerItem(value, text)\n }\n } else if (typeof children === 'string') {\n const text = children.trim()\n if (text) ctx.registerItem(value, text)\n } else if (Array.isArray(children)) {\n const text = extractNodeText(children as VNode[]).trim()\n if (text) ctx.registerItem(value, text)\n }\n }\n }\n // Recurse into children\n if (Array.isArray(node.children)) {\n walkAndRegister(node.children as VNode[])\n }\n }\n}\n\n// Run synchronously at setup time and whenever the slot content changes\nwatchEffect(() => {\n const vnodes = slots.default?.()\n if (vnodes) walkAndRegister(vnodes)\n})\n</script>\n\n<template>\n <AutocompletePortal>\n <AnimatePresence>\n <AutocompleteContent\n v-if=\"rootContext.open.value && (ctx.hasItems.value || (ctx.isFilled.value && !ctx.isLoading.value))\"\n position=\"popper\"\n :side-offset=\"props.sideOffset\"\n as-child\n data-slot=\"popover\"\n >\n <motion.div\n :class=\"['autocomplete__popover', 'relative']\"\n :data-loading=\"ctx.isLoading.value ? '' : undefined\"\n :data-truncate-items=\"ctx.truncateItems.value ? undefined : 'false'\"\n :aria-busy=\"ctx.isLoading.value || undefined\"\n :initial=\"{ opacity: 0, scale: 0.95 }\"\n :animate=\"{ opacity: 1, scale: 1 }\"\n :exit=\"{ opacity: 0, scale: 0.95 }\"\n :transition=\"{ duration: 0.15 }\"\n >\n <div\n :class=\"[\n 'transition-opacity duration-150',\n ctx.isLoading.value\n ? 'pointer-events-none opacity-50 grayscale cursor-not-allowed select-none'\n : '',\n ]\"\n :inert=\"ctx.isLoading.value || undefined\"\n :aria-disabled=\"ctx.isLoading.value || undefined\"\n :data-disabled=\"ctx.isLoading.value ? '' : undefined\"\n data-slot=\"list-wrapper\"\n >\n <AutocompleteViewport\n data-slot=\"list-box\"\n >\n <slot />\n <!-- Empty state: only show when the user has typed a query -->\n <AutocompleteEmpty\n v-if=\"ctx.isFilled.value && !ctx.isLoading.value\"\n class=\"py-3 text-center text-sm text-default-400\"\n data-slot=\"empty-content\"\n >\n <slot name=\"empty\">\n No results found\n </slot>\n </AutocompleteEmpty>\n </AutocompleteViewport>\n </div>\n </motion.div>\n </AutocompleteContent>\n </AnimatePresence>\n </AutocompletePortal>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteItem.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { AutocompleteItem, ComboboxItemIndicator } from 'reka-ui'\n\nconst props = withDefaults(defineProps<{\n value: string\n
|
|
1
|
+
{"version":3,"file":"AutocompleteItem.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted, useSlots, type VNode } from 'vue'\nimport { AutocompleteItem, ComboboxItemIndicator } from 'reka-ui'\nimport { useAutocompleteInject } from './Autocomplete.context'\n\nconst props = withDefaults(defineProps<{\n value: string\n isDisabled?: boolean\n class?: string\n}>(), {\n isDisabled: false,\n class: undefined,\n})\n\nconst slots = useSlots()\nconst ctx = useAutocompleteInject()\n\n// Extract plain text from default slot VNodes at render time.\nfunction extractText(nodes: VNode[]): string {\n return nodes.map(n => {\n if (typeof n.children === 'string') return n.children\n if (Array.isArray(n.children)) return extractText(n.children as VNode[])\n return ''\n }).join('')\n}\n\n// The display text Reka writes into the input when this item is selected.\n// Reads slot text content — no extra props needed.\nconst displayText = computed(() => {\n const vnodes = slots.default?.()\n if (!vnodes) return props.value\n return extractText(vnodes).trim() || props.value\n})\n\n// Register this item's value→label mapping with the parent Autocomplete bridge\n// so valueFor() can translate the display label back to the real value.\nonMounted(() => {\n ctx.registerItem(props.value, displayText.value)\n})\n\nonUnmounted(() => {\n ctx.unregisterItem(props.value)\n})\n</script>\n\n<template>\n <AutocompleteItem\n :value=\"displayText\"\n :text-value=\"displayText\"\n :disabled=\"props.isDisabled\"\n :data-item-value=\"props.value\"\n class=\"list-box-item list-box-item--default\"\n data-slot=\"list-box-item\"\n >\n <slot name=\"startContent\" />\n <span\n class=\"autocomplete-item__text\"\n data-slot=\"item-text\"\n ><slot /></span>\n <ComboboxItemIndicator\n class=\"list-box-item__indicator\"\n data-slot=\"list-box-item-indicator\"\n >\n <slot name=\"selectedIcon\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n data-slot=\"list-box-item-indicator--checkmark\"\n aria-hidden=\"true\"\n >\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n </slot>\n </ComboboxItemIndicator>\n <slot name=\"endContent\" />\n </AutocompleteItem>\n</template>\n"],"mappings":""}
|
package/dist/components/autocomplete/AutocompleteItem.vue_vue_type_script_setup_true_lang.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useAutocompleteInject } from "./Autocomplete.context.js";
|
|
2
|
+
import { computed, createBlock, createElementVNode, createVNode, defineComponent, onMounted, onUnmounted, openBlock, renderSlot, unref, useSlots, withCtx } from "vue";
|
|
2
3
|
import { AutocompleteItem, ComboboxItemIndicator } from "reka-ui";
|
|
3
4
|
//#region src/components/autocomplete/AutocompleteItem.vue?vue&type=script&setup=true&lang.ts
|
|
4
5
|
var _hoisted_1 = {
|
|
@@ -9,7 +10,6 @@ var AutocompleteItem_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
9
10
|
__name: "AutocompleteItem",
|
|
10
11
|
props: {
|
|
11
12
|
value: {},
|
|
12
|
-
textValue: { default: void 0 },
|
|
13
13
|
isDisabled: {
|
|
14
14
|
type: Boolean,
|
|
15
15
|
default: false
|
|
@@ -18,10 +18,30 @@ var AutocompleteItem_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
|
|
|
18
18
|
},
|
|
19
19
|
setup(__props) {
|
|
20
20
|
const props = __props;
|
|
21
|
+
const slots = useSlots();
|
|
22
|
+
const ctx = useAutocompleteInject();
|
|
23
|
+
function extractText(nodes) {
|
|
24
|
+
return nodes.map((n) => {
|
|
25
|
+
if (typeof n.children === "string") return n.children;
|
|
26
|
+
if (Array.isArray(n.children)) return extractText(n.children);
|
|
27
|
+
return "";
|
|
28
|
+
}).join("");
|
|
29
|
+
}
|
|
30
|
+
const displayText = computed(() => {
|
|
31
|
+
const vnodes = slots.default?.();
|
|
32
|
+
if (!vnodes) return props.value;
|
|
33
|
+
return extractText(vnodes).trim() || props.value;
|
|
34
|
+
});
|
|
35
|
+
onMounted(() => {
|
|
36
|
+
ctx.registerItem(props.value, displayText.value);
|
|
37
|
+
});
|
|
38
|
+
onUnmounted(() => {
|
|
39
|
+
ctx.unregisterItem(props.value);
|
|
40
|
+
});
|
|
21
41
|
return (_ctx, _cache) => {
|
|
22
42
|
return openBlock(), createBlock(unref(AutocompleteItem), {
|
|
23
|
-
value:
|
|
24
|
-
"text-value":
|
|
43
|
+
value: displayText.value,
|
|
44
|
+
"text-value": displayText.value,
|
|
25
45
|
disabled: props.isDisabled,
|
|
26
46
|
"data-item-value": props.value,
|
|
27
47
|
class: "list-box-item list-box-item--default",
|
package/dist/components/autocomplete/AutocompleteItem.vue_vue_type_script_setup_true_lang.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteItem.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { AutocompleteItem, ComboboxItemIndicator } from 'reka-ui'\n\nconst props = withDefaults(defineProps<{\n value: string\n
|
|
1
|
+
{"version":3,"file":"AutocompleteItem.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted, useSlots, type VNode } from 'vue'\nimport { AutocompleteItem, ComboboxItemIndicator } from 'reka-ui'\nimport { useAutocompleteInject } from './Autocomplete.context'\n\nconst props = withDefaults(defineProps<{\n value: string\n isDisabled?: boolean\n class?: string\n}>(), {\n isDisabled: false,\n class: undefined,\n})\n\nconst slots = useSlots()\nconst ctx = useAutocompleteInject()\n\n// Extract plain text from default slot VNodes at render time.\nfunction extractText(nodes: VNode[]): string {\n return nodes.map(n => {\n if (typeof n.children === 'string') return n.children\n if (Array.isArray(n.children)) return extractText(n.children as VNode[])\n return ''\n }).join('')\n}\n\n// The display text Reka writes into the input when this item is selected.\n// Reads slot text content — no extra props needed.\nconst displayText = computed(() => {\n const vnodes = slots.default?.()\n if (!vnodes) return props.value\n return extractText(vnodes).trim() || props.value\n})\n\n// Register this item's value→label mapping with the parent Autocomplete bridge\n// so valueFor() can translate the display label back to the real value.\nonMounted(() => {\n ctx.registerItem(props.value, displayText.value)\n})\n\nonUnmounted(() => {\n ctx.unregisterItem(props.value)\n})\n</script>\n\n<template>\n <AutocompleteItem\n :value=\"displayText\"\n :text-value=\"displayText\"\n :disabled=\"props.isDisabled\"\n :data-item-value=\"props.value\"\n class=\"list-box-item list-box-item--default\"\n data-slot=\"list-box-item\"\n >\n <slot name=\"startContent\" />\n <span\n class=\"autocomplete-item__text\"\n data-slot=\"item-text\"\n ><slot /></span>\n <ComboboxItemIndicator\n class=\"list-box-item__indicator\"\n data-slot=\"list-box-item-indicator\"\n >\n <slot name=\"selectedIcon\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n data-slot=\"list-box-item-indicator--checkmark\"\n aria-hidden=\"true\"\n >\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n </slot>\n </ComboboxItemIndicator>\n <slot name=\"endContent\" />\n </AutocompleteItem>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAKA,MAAM,QAAQ;EASd,MAAM,QAAQ,UAAS;EACvB,MAAM,MAAM,uBAAsB;EAGlC,SAAS,YAAY,OAAwB;AAC3C,UAAO,MAAM,KAAI,MAAK;AACpB,QAAI,OAAO,EAAE,aAAa,SAAU,QAAO,EAAE;AAC7C,QAAI,MAAM,QAAQ,EAAE,SAAS,CAAE,QAAO,YAAY,EAAE,SAAmB;AACvE,WAAO;KACP,CAAC,KAAK,GAAE;;EAKZ,MAAM,cAAc,eAAe;GACjC,MAAM,SAAS,MAAM,WAAU;AAC/B,OAAI,CAAC,OAAQ,QAAO,MAAM;AAC1B,UAAO,YAAY,OAAO,CAAC,MAAM,IAAI,MAAM;IAC5C;AAID,kBAAgB;AACd,OAAI,aAAa,MAAM,OAAO,YAAY,MAAK;IAChD;AAED,oBAAkB;AAChB,OAAI,eAAe,MAAM,MAAK;IAC/B;;uBAIC,YAoCmB,MAAA,iBAAA,EAAA;IAnChB,OAAO,YAAA;IACP,cAAY,YAAA;IACZ,UAAU,MAAM;IAChB,mBAAiB,MAAM;IACxB,OAAM;IACN,aAAU;;2BAEkB;KAA5B,WAA4B,KAAA,QAAA,eAAA;KAC5B,mBAGgB,QAHhB,YAGgB,CAAf,WAAQ,KAAA,QAAA,UAAA,CAAA,CAAA;KACT,YAqBwB,MAAA,sBAAA,EAAA;MApBtB,OAAM;MACN,aAAU;;6BAkBH,CAhBP,WAgBO,KAAA,QAAA,gBAAA,EAAA,QAAA,CAAA,OAAA,OAAA,OAAA,KAfL,mBAcM,OAAA;OAbJ,OAAM;OACN,OAAM;OACN,QAAO;OACP,SAAQ;OACR,MAAK;OACL,QAAO;OACP,gBAAa;OACb,kBAAe;OACf,mBAAgB;OAChB,aAAU;OACV,eAAY;UAEZ,mBAAoC,YAAA,EAA1B,QAAO,kBAAgB,CAAA,CAAA,EAAA,GAAA,EAAA,CAAA,CAAA,CAAA;;;KAIvC,WAA0B,KAAA,QAAA,aAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComboBox.context.js","names":[],"sources":["../../../src/components/combo-box/ComboBox.context.ts"],"sourcesContent":["import { createContext } from '../../utils/context'\nimport type { ComputedRef, Ref } from 'vue'\nimport type { comboBoxVariants } from '@auronui/styles'\n\nexport interface ComboBoxContext {\n isDisabled: Ref<boolean>\n isInvalid: Ref<boolean>\n fullWidth: Ref<boolean>\n slots: ComputedRef<ReturnType<typeof comboBoxVariants>>\n displayValue: ComputedRef<(value: string) => string>\n}\n\nexport const {\n useProvide: useComboBoxProvide,\n useInject: useComboBoxInject,\n key: comboBoxContextKey,\n} = createContext<ComboBoxContext>('ComboBox')\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"ComboBox.context.js","names":[],"sources":["../../../src/components/combo-box/ComboBox.context.ts"],"sourcesContent":["import { createContext } from '../../utils/context'\nimport type { ComputedRef, Ref } from 'vue'\nimport type { comboBoxVariants } from '@auronui/styles'\n\nexport interface ComboBoxContext {\n isDisabled: Ref<boolean>\n isInvalid: Ref<boolean>\n fullWidth: Ref<boolean>\n slots: ComputedRef<ReturnType<typeof comboBoxVariants>>\n displayValue: ComputedRef<(value: string) => string>\n /**\n * Called by ComboBoxItem at mount time to register a value→label pair.\n * Used by displayValue() and the modelValue bridge for slot-only usage.\n */\n registerItem: (value: string, label: string) => void\n /**\n * Called by ComboBoxItem at unmount time to deregister.\n */\n unregisterItem: (value: string) => void\n}\n\nexport const {\n useProvide: useComboBoxProvide,\n useInject: useComboBoxInject,\n key: comboBoxContextKey,\n} = createContext<ComboBoxContext>('ComboBox')\n"],"mappings":";;AAqBA,IAAa,EACX,YAAY,oBACZ,WAAW,mBACX,KAAK,uBACH,cAA+B,WAAW"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComboBox.js","names":[],"sources":["../../../src/components/combo-box/ComboBox.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, toRef, useId } 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//
|
|
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,6 +1,6 @@
|
|
|
1
1
|
import { composeClassName } from "../../utils/composeClassName.js";
|
|
2
2
|
import { useComboBoxProvide } from "./ComboBox.context.js";
|
|
3
|
-
import { computed, createCommentVNode, createElementBlock, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, renderSlot, toDisplayString, toRef, unref, useId, withCtx } from "vue";
|
|
3
|
+
import { computed, createCommentVNode, createElementBlock, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, renderSlot, toDisplayString, toRef, unref, useId, watch, withCtx } from "vue";
|
|
4
4
|
import { comboBoxVariants } from "@auronui/styles";
|
|
5
5
|
import { ComboboxRoot } from "reka-ui";
|
|
6
6
|
//#region src/components/combo-box/ComboBox.vue?vue&type=script&setup=true&lang.ts
|
|
@@ -77,17 +77,52 @@ var ComboBox_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defin
|
|
|
77
77
|
if (props.filterFunction) return props.filterFunction;
|
|
78
78
|
return (itemText, searchTerm) => itemText.toLowerCase().includes(searchTerm.toLowerCase());
|
|
79
79
|
});
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
80
|
+
const slotItemRegistry = ref(/* @__PURE__ */ new Map());
|
|
81
|
+
function registerItem(value, label) {
|
|
82
|
+
const next = new Map(slotItemRegistry.value);
|
|
83
|
+
next.set(value, label);
|
|
84
|
+
slotItemRegistry.value = next;
|
|
85
|
+
}
|
|
86
|
+
function unregisterItem(value) {
|
|
87
|
+
const next = new Map(slotItemRegistry.value);
|
|
88
|
+
next.delete(value);
|
|
89
|
+
slotItemRegistry.value = next;
|
|
90
|
+
}
|
|
91
|
+
function labelFor(value) {
|
|
92
|
+
if (!value) return "";
|
|
93
|
+
const item = props.items.find((i) => i.value === value);
|
|
94
|
+
if (item) return item.label ?? item.textValue ?? value;
|
|
95
|
+
return slotItemRegistry.value.get(value) ?? value;
|
|
96
|
+
}
|
|
97
|
+
function valueFor(label) {
|
|
98
|
+
if (!label) return "";
|
|
99
|
+
const item = props.items.find((i) => (i.label ?? i.textValue ?? i.value) === label);
|
|
100
|
+
if (item) return item.value;
|
|
101
|
+
for (const [value, lbl] of slotItemRegistry.value) if (lbl === label) return value;
|
|
102
|
+
return label;
|
|
103
|
+
}
|
|
104
|
+
const internalValue = ref(labelFor(props.modelValue));
|
|
105
|
+
const displayValue = computed(() => (val) => val);
|
|
106
|
+
watch(() => props.modelValue, (val) => {
|
|
107
|
+
const next = labelFor(val);
|
|
108
|
+
if (internalValue.value !== next) internalValue.value = next;
|
|
109
|
+
});
|
|
110
|
+
function handleModelValueUpdate(emitted) {
|
|
111
|
+
internalValue.value = emitted;
|
|
112
|
+
emit("update:modelValue", valueFor(emitted));
|
|
113
|
+
}
|
|
114
|
+
watch(slotItemRegistry, () => {
|
|
115
|
+
const next = labelFor(props.modelValue);
|
|
116
|
+
if (next !== internalValue.value && valueFor(internalValue.value) === (props.modelValue ?? "")) internalValue.value = next;
|
|
84
117
|
});
|
|
85
118
|
useComboBoxProvide({
|
|
86
119
|
isDisabled: toRef(props, "isDisabled"),
|
|
87
120
|
isInvalid: toRef(props, "isInvalid"),
|
|
88
121
|
fullWidth: toRef(props, "fullWidth"),
|
|
89
122
|
slots: slotFns,
|
|
90
|
-
displayValue
|
|
123
|
+
displayValue,
|
|
124
|
+
registerItem,
|
|
125
|
+
unregisterItem
|
|
91
126
|
});
|
|
92
127
|
return (_ctx, _cache) => {
|
|
93
128
|
return openBlock(), createElementBlock("div", {
|
|
@@ -101,20 +136,20 @@ var ComboBox_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defin
|
|
|
101
136
|
"data-slot": "label"
|
|
102
137
|
}, [createTextVNode(toDisplayString(props.label) + " ", 1), props.isRequired ? (openBlock(), createElementBlock("span", _hoisted_3, " *")) : createCommentVNode("", true)], 8, _hoisted_2)) : createCommentVNode("", true),
|
|
103
138
|
createVNode(unref(ComboboxRoot), {
|
|
104
|
-
|
|
105
|
-
"
|
|
139
|
+
modelValue: internalValue.value,
|
|
140
|
+
"onUpdate:modelValue": [_cache[0] || (_cache[0] = ($event) => internalValue.value = $event), _cache[1] || (_cache[1] = ($event) => handleModelValueUpdate($event))],
|
|
141
|
+
"default-value": props.defaultValue ? labelFor(props.defaultValue) : void 0,
|
|
106
142
|
open: props.open,
|
|
107
143
|
"default-open": props.defaultOpen,
|
|
108
144
|
disabled: props.isDisabled,
|
|
109
145
|
required: props.isRequired,
|
|
110
146
|
"filter-function": effectiveFilter.value,
|
|
111
|
-
"onUpdate:
|
|
112
|
-
"onUpdate:open": _cache[1] || (_cache[1] = ($event) => emit("update:open", $event))
|
|
147
|
+
"onUpdate:open": _cache[2] || (_cache[2] = ($event) => emit("update:open", $event))
|
|
113
148
|
}, {
|
|
114
149
|
default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
|
|
115
150
|
_: 3
|
|
116
151
|
}, 8, [
|
|
117
|
-
"
|
|
152
|
+
"modelValue",
|
|
118
153
|
"default-value",
|
|
119
154
|
"open",
|
|
120
155
|
"default-open",
|
|
@@ -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, toRef, useId } 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//
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComboBoxContent.js","names":[],"sources":["../../../src/components/combo-box/ComboBoxContent.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ComboboxPortal, ComboboxContent, ComboboxViewport, injectComboboxRootContext } from 'reka-ui'\nimport { motion, AnimatePresence } from 'motion-v'\nimport { useComboBoxInject } from './ComboBox.context'\n\nconst props = withDefaults(defineProps<{\n sideOffset?: number\n class?: string\n}>(), {\n sideOffset: 8,\n class: undefined,\n})\n\nconst ctx = useComboBoxInject()\nconst comboboxRootContext = injectComboboxRootContext()\n</script>\n\n<template>\n <ComboboxPortal>\n <AnimatePresence>\n <ComboboxContent\n v-if=\"comboboxRootContext.open.value\"\n position=\"popper\"\n :side-offset=\"props.sideOffset\"\n as-child\n data-slot=\"popover\"\n >\n <motion.div\n :class=\"ctx.slots.value.popover()\"\n :initial=\"{ opacity: 0, scale: 0.95 }\"\n :animate=\"{ opacity: 1, scale: 1 }\"\n :exit=\"{ opacity: 0, scale: 0.95 }\"\n :transition=\"{ duration: 0.15 }\"\n >\n <ComboboxViewport data-slot=\"list-box\">\n <slot />\n <slot name=\"empty\" />\n </ComboboxViewport>\n </motion.div>\n </ComboboxContent>\n </AnimatePresence>\n </ComboboxPortal>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"ComboBoxContent.js","names":[],"sources":["../../../src/components/combo-box/ComboBoxContent.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ComboboxPortal, ComboboxContent, ComboboxViewport, injectComboboxRootContext } from 'reka-ui'\nimport { motion, AnimatePresence } from 'motion-v'\nimport { useSlots, watchEffect, type VNode } from 'vue'\nimport { useComboBoxInject } from './ComboBox.context'\n\nconst props = withDefaults(defineProps<{\n sideOffset?: number\n class?: string\n}>(), {\n sideOffset: 8,\n class: undefined,\n})\n\nconst ctx = useComboBoxInject()\nconst comboboxRootContext = injectComboboxRootContext()\n\n// Pre-walk slot VNodes to extract value→label pairs synchronously.\n// This runs before the portal opens so the bridge can resolve labels on initial render.\nconst slots = useSlots()\n\nfunction extractNodeText(nodes: VNode[]): string {\n return nodes.map(n => {\n if (typeof n.children === 'string') return n.children\n if (Array.isArray(n.children)) return extractNodeText(n.children as VNode[])\n return ''\n }).join('')\n}\n\nfunction walkAndRegister(nodes: VNode[]) {\n for (const node of nodes) {\n // ComboBoxItem VNodes have a `value` prop; extract their text children\n if (node.props && typeof node.props.value === 'string') {\n const value = node.props.value as string\n const children = node.children\n if (children && typeof children === 'object' && 'default' in children) {\n const slotFn = (children as Record<string, () => VNode[]>).default\n if (typeof slotFn === 'function') {\n const text = extractNodeText(slotFn()).trim()\n if (text) ctx.registerItem(value, text)\n }\n } else if (typeof children === 'string') {\n const text = children.trim()\n if (text) ctx.registerItem(value, text)\n } else if (Array.isArray(children)) {\n const text = extractNodeText(children as VNode[]).trim()\n if (text) ctx.registerItem(value, text)\n }\n }\n // Recurse into children arrays\n if (Array.isArray(node.children)) {\n walkAndRegister(node.children as VNode[])\n }\n }\n}\n\n// Run synchronously at setup time and whenever the slot content changes\nwatchEffect(() => {\n const vnodes = slots.default?.()\n if (vnodes) walkAndRegister(vnodes)\n})\n</script>\n\n<template>\n <ComboboxPortal>\n <AnimatePresence>\n <ComboboxContent\n v-if=\"comboboxRootContext.open.value\"\n position=\"popper\"\n :side-offset=\"props.sideOffset\"\n as-child\n data-slot=\"popover\"\n >\n <motion.div\n :class=\"ctx.slots.value.popover()\"\n :initial=\"{ opacity: 0, scale: 0.95 }\"\n :animate=\"{ opacity: 1, scale: 1 }\"\n :exit=\"{ opacity: 0, scale: 0.95 }\"\n :transition=\"{ duration: 0.15 }\"\n >\n <ComboboxViewport data-slot=\"list-box\">\n <slot />\n <slot name=\"empty\" />\n </ComboboxViewport>\n </motion.div>\n </ComboboxContent>\n </AnimatePresence>\n </ComboboxPortal>\n</template>\n"],"mappings":""}
|