@auronui/vue 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +1080 -902
- 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 +141 -17
- package/dist/components/autocomplete/Autocomplete.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/autocomplete/AutocompleteCreateItem.js +7 -0
- package/dist/components/autocomplete/AutocompleteCreateItem.js.map +1 -0
- package/dist/components/autocomplete/AutocompleteCreateItem.vue_vue_type_script_setup_true_lang.js +57 -0
- package/dist/components/autocomplete/AutocompleteCreateItem.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/autocomplete/AutocompleteInput.js.map +1 -1
- package/dist/components/autocomplete/AutocompleteInput.vue_vue_type_script_setup_true_lang.js +40 -9
- package/dist/components/autocomplete/AutocompleteInput.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 +33 -5
- package/dist/components/autocomplete/AutocompleteItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/autocomplete/AutocompleteOverflowChips.js +7 -0
- package/dist/components/autocomplete/AutocompleteOverflowChips.js.map +1 -0
- package/dist/components/autocomplete/AutocompleteOverflowChips.vue_vue_type_script_setup_true_lang.js +88 -0
- package/dist/components/autocomplete/AutocompleteOverflowChips.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/combo-box/ComboBox.js.map +1 -1
- package/dist/components/combo-box/ComboBox.vue_vue_type_script_setup_true_lang.js +20 -2
- package/dist/components/combo-box/ComboBox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/select/Select.context.js.map +1 -1
- package/dist/components/select/Select.js.map +1 -1
- package/dist/components/select/Select.vue_vue_type_script_setup_true_lang.js +45 -6
- package/dist/components/select/Select.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 +2 -2
- package/dist/components/select/SelectItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/select/SelectOverflowChips.js +7 -0
- package/dist/components/select/SelectOverflowChips.js.map +1 -0
- package/dist/components/select/SelectOverflowChips.vue_vue_type_script_setup_true_lang.js +85 -0
- package/dist/components/select/SelectOverflowChips.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/select/SelectValue.js.map +1 -1
- package/dist/components/select/SelectValue.vue_vue_type_script_setup_true_lang.js +6 -1
- package/dist/components/select/SelectValue.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/index.d.ts +362 -432
- package/dist/index.js +5 -10
- package/dist/utils/hasSlotComponent.js +26 -0
- package/dist/utils/hasSlotComponent.js.map +1 -0
- package/package.json +2 -2
- package/dist/components/tag/Tag.js +0 -7
- package/dist/components/tag/Tag.js.map +0 -1
- package/dist/components/tag/Tag.vue_vue_type_script_setup_true_lang.js +0 -69
- package/dist/components/tag/Tag.vue_vue_type_script_setup_true_lang.js.map +0 -1
- package/dist/components/tag/TagDelete.js +0 -7
- package/dist/components/tag/TagDelete.js.map +0 -1
- package/dist/components/tag/TagDelete.vue_vue_type_script_setup_true_lang.js +0 -49
- package/dist/components/tag/TagDelete.vue_vue_type_script_setup_true_lang.js.map +0 -1
- package/dist/components/tag/TagText.js +0 -7
- package/dist/components/tag/TagText.js.map +0 -1
- package/dist/components/tag/TagText.vue_vue_type_script_setup_true_lang.js +0 -18
- package/dist/components/tag/TagText.vue_vue_type_script_setup_true_lang.js.map +0 -1
- package/dist/components/tag-group/TagGroup.context.js +0 -7
- package/dist/components/tag-group/TagGroup.context.js.map +0 -1
- package/dist/components/tag-group/TagGroup.js +0 -7
- package/dist/components/tag-group/TagGroup.js.map +0 -1
- package/dist/components/tag-group/TagGroup.vue_vue_type_script_setup_true_lang.js +0 -142
- package/dist/components/tag-group/TagGroup.vue_vue_type_script_setup_true_lang.js.map +0 -1
- package/dist/components/tag-group/TagGroupInput.js +0 -7
- package/dist/components/tag-group/TagGroupInput.js.map +0 -1
- package/dist/components/tag-group/TagGroupInput.vue_vue_type_script_setup_true_lang.js +0 -37
- package/dist/components/tag-group/TagGroupInput.vue_vue_type_script_setup_true_lang.js.map +0 -1
|
@@ -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 * 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":";;
|
|
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 /** Whether multiple values can be selected. */\n multiple: Ref<boolean>\n /**\n * How overflow chips are handled in multiple mode.\n * - `wrap`: trigger grows in height, chips wrap to new lines (default)\n * - `collapse`: fixed height, overflowing chips are hidden behind a \"+N more\" badge\n */\n multipleOverflow: Ref<'wrap' | 'collapse'>\n /** Currently selected values in multiple mode. */\n selectedValues: Ref<string[]>\n /** Selected value→label pairs for rendering chips. */\n selectedLabels: ComputedRef<Array<{ value: string; label: string }>>\n /** Toggle a value in the selectedValues array (multiple mode). */\n onMultipleSelect: (value: string) => void\n /** Remove a single value from selectedValues (multiple mode). */\n removeValue: (value: string) => void\n /** Clear all selected values and the search term (multiple mode). */\n clearAll: () => void\n /** Returns true if the given value is in selectedValues. */\n isSelected: (value: string) => boolean\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 /* ─── Creatable ─────────────────────────────────────────────────────── */\n /** Current search/filter term — exposed so AutocompleteCreateItem can read it. */\n searchTerm: Ref<string>\n /** True when the search term exactly matches an existing option (case-insensitive). */\n hasExactMatch: ComputedRef<boolean>\n /** Add the typed term as a new value. Handles both single and multiple mode. */\n onCreateValue: (value: string) => void\n}\n\nexport const {\n useProvide: useAutocompleteProvide,\n useInject: useAutocompleteInject,\n key: autocompleteContextKey,\n} = createContext<AutocompleteContext>('Autocomplete')\n"],"mappings":";;AA2DA,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// 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":""}
|
|
1
|
+
{"version":3,"file":"Autocomplete.js","names":[],"sources":["../../../src/components/autocomplete/Autocomplete.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, ref, toRef, useAttrs, useId, watch, useSlots } from 'vue'\nimport { AutocompleteRoot } from 'reka-ui'\nimport { autocompleteVariants, type AutocompleteVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useAutocompleteProvide } from './Autocomplete.context'\nimport { hasSlotComponent } from '../../utils/hasSlotComponent'\nimport AutocompleteInput from './AutocompleteInput.vue'\nimport AutocompleteContent from './AutocompleteContent.vue'\nimport AutocompleteItem from './AutocompleteItem.vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'flat',\n size: 'md',\n color: 'default',\n labelPlacement: 'inside',\n fullWidth: false,\n isInvalid: false,\n isDisabled: false,\n isReadonly: false,\n isRequired: false,\n multiple: false,\n multipleOverflow: 'wrap',\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n loadItems: undefined,\n debounceMs: 200,\n filterOnOpen: false,\n truncateItems: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | string[]]\n 'update:open': [value: boolean]\n /** Fired when the user creates a new value via `creatable` or `<AutocompleteCreateItem>`. */\n 'create': [value: string]\n}>()\n\nexport interface AutocompleteItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\ntype Props = {\n /** Visual style of the field. @default 'flat' */\n variant?: AutocompleteVariants['variant']\n /** Field height. @default 'md' */\n size?: AutocompleteVariants['size']\n /** Accent color applied to focus ring + floating label. @default 'default' */\n color?: AutocompleteVariants['color']\n /**\n * Where the `label` is rendered relative to the field.\n * - `inside`: floats above the trigger (shrinks when focused/filled)\n * - `outside`: sits above the field, static\n * - `outside-left`: sits to the left, horizontal layout\n * @default 'inside'\n */\n labelPlacement?: AutocompleteVariants['labelPlacement']\n /** Stretches root wrapper to 100% width. @default false */\n fullWidth?: boolean\n /** Marks the field as invalid. Triggers danger styling and enables `errorMessage`. @default false */\n isInvalid?: boolean\n /** Disables the field. @default false */\n isDisabled?: boolean\n /** Makes the field read-only. @default false */\n isReadonly?: boolean\n /** Adds a required asterisk next to the label. @default false */\n isRequired?: boolean\n /** Placeholder shown when empty. */\n placeholder?: string\n /** Form field name, for native form submission. */\n name?: string\n /** Field label. When omitted, the floating-label behavior is skipped. */\n label?: string\n /** Helper text displayed below the field. Suppressed when `isInvalid && errorMessage` is shown. */\n description?: string\n /** Error text displayed below the field. Only rendered when `isInvalid` is also true. */\n errorMessage?: string\n /** Extra classes merged onto the root wrapper via `composeClassName`. */\n class?: string\n\n /* ─── Autocomplete-specific ─────────────────────────────────────── */\n /** Two-way bound selected value. string in single mode, string[] in multiple mode. */\n modelValue?: string | string[]\n /** Initial selected value (uncontrolled). */\n defaultValue?: string | string[]\n /** Allow selecting multiple values. modelValue becomes string[]. @default false */\n multiple?: boolean\n /**\n * Controls how chips overflow in multiple mode.\n * - `wrap`: trigger grows in height, chips wrap to new lines (default)\n * - `collapse`: fixed height, overflowing chips are hidden behind \"+N more\"\n * @default 'wrap'\n */\n multipleOverflow?: 'wrap' | 'collapse'\n /** Controls open state of the dropdown. */\n open?: boolean\n /** Initial open state of the dropdown (uncontrolled). */\n defaultOpen?: boolean\n /** Static items list — used when no loadItems is provided. */\n items?: AutocompleteItem[]\n /** Async data source: called on every query change. */\n loadItems?: (query: string) => Promise<AutocompleteItem[]>\n /** Debounce delay for loadItems calls (ms). 0 = no debounce. */\n debounceMs?: number\n /** Apply filter immediately on open (default: false — show all items until user types). */\n filterOnOpen?: boolean\n /**\n * Truncate item text with an ellipsis when it overflows the dropdown width.\n * Set to `false` to show full text — the dropdown will widen to fit.\n * @default true\n */\n truncateItems?: boolean\n}\n\nconst attrs = useAttrs()\nconst generatedId = useId()\nconst inputId = computed(() => (attrs.id as string | undefined) ?? generatedId)\n\nconst hasLabel = computed(() => !!props.label)\n\nconst slots = useSlots()\n// Compound chrome present → pass slot through (advanced). Otherwise render the\n// input/content/items internally (short-form).\nconst usesCustomChrome = computed(() =>\n hasSlotComponent(slots.default?.(), [AutocompleteInput, AutocompleteContent]),\n)\n\n// Registry for slot-rendered items: value → label (populated by AutocompleteItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Internal async state\nconst isLoading = ref(false)\nconst internalItems = ref<AutocompleteItem[]>([...props.items])\n\n// ── Multiple-mode state ────────────────────────────────────────────────────\n// Tracks selected values as an array. Only meaningful when props.multiple=true.\nconst selectedValues = ref<string[]>(\n props.multiple && Array.isArray(props.modelValue) ? [...props.modelValue] : [],\n)\n\n// Controlled open state used in multiple mode to prevent the dropdown closing\n// after each item selection (Reka would normally close on selection).\nconst internalOpen = ref(props.defaultOpen ?? false)\n\n// Flag set by onMultipleSelect so handleOpenChange can distinguish item\n// selection from Escape/outside-click close.\nlet selectingItem = false\n\n// ── Open-state tracking ────────────────────────────────────────────────────\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\n// Controlled open state for single mode (mirrors multiple mode's internalOpen).\n// Driving open ourselves lets handleOpenChange gate spurious reopens — e.g. the\n// focus bounce caused when a create handler mutates the items list.\nconst singleOpen = ref(props.open ?? props.defaultOpen ?? false)\n// When true, handleOpenChange ignores open=true requests. Set briefly after a\n// single-mode create so the post-create re-render can't reopen the menu.\nlet blockReopen = false\nlet blockReopenTimer: ReturnType<typeof setTimeout> | undefined\nconst effectiveIgnoreFilter = computed(() => {\n if (props.loadItems) return true\n if (!props.filterOnOpen && isOpen.value && !isUserTyping.value) return true\n return false\n})\n\n// ── Label/value bridge ─────────────────────────────────────────────────────\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (value == null || value === '') return ''\n const match = internalItems.value.find((i) => i.value === value)\n if (match) return match.label ?? match.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\nfunction valueFor(displayed: string): string {\n if (!displayed) return ''\n const match = internalItems.value.find(\n (i) => (i.label ?? i.textValue ?? i.value) === displayed,\n )\n if (match) return match.value\n for (const [value, label] of slotItemRegistry.value) {\n if (label === displayed) return value\n }\n return displayed\n}\n\nconst singleModelValue = computed(() =>\n props.multiple ? undefined : (props.modelValue as string | undefined),\n)\n\nconst searchTerm = ref(labelFor(singleModelValue.value))\n\nconst isFilled = computed(() =>\n props.multiple\n ? selectedValues.value.length > 0 || !!searchTerm.value\n : !!searchTerm.value,\n)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\nconst selectedLabels = computed(() =>\n selectedValues.value.map(v => ({ value: v, label: labelFor(v) || v })),\n)\n\n// ── Helpers ────────────────────────────────────────────────────────────────\nconst descriptionId = computed(() => `${inputId.value}-description`)\nconst errorMessageId = computed(() => `${inputId.value}-error`)\nconst showError = computed(() => props.isInvalid && !!props.errorMessage)\nconst showDescription = computed(() => !!props.description && !showError.value)\nconst hasHelper = computed(() => showError.value || showDescription.value)\nconst ariaDescribedBy = computed(() => {\n if (showError.value) return errorMessageId.value\n if (showDescription.value) return descriptionId.value\n return undefined\n})\n\n// ── Watchers ───────────────────────────────────────────────────────────────\n\n// Parent → internal: sync controlled modelValue into local state\nwatch(() => props.modelValue, (val) => {\n if (props.multiple) {\n if (Array.isArray(val)) selectedValues.value = [...val]\n } else {\n const next = labelFor(val as string | undefined)\n if (searchTerm.value !== next) searchTerm.value = next\n }\n})\n\n// Parent → internal: sync consumer-controlled open into our single-mode state\nwatch(() => props.open, (val) => {\n if (!props.multiple && val !== undefined) singleOpen.value = val\n})\n\n// Internal → parent: single mode only — multiple mode emits inside onMultipleSelect\nwatch(searchTerm, (displayed) => {\n if (props.multiple) {\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n return\n }\n const next = valueFor(displayed)\n if (next !== (singleModelValue.value ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n})\n\nfunction handleOpenChange(val: boolean) {\n if (props.multiple) {\n isOpen.value = val\n // Suppress close when triggered by item selection; allow Escape/outside-click\n internalOpen.value = (!val && selectingItem) ? true : val\n selectingItem = false\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n return\n }\n\n // Single mode: ignore spurious reopen requests right after a create — the\n // create handler's re-render bounces focus back to the input, which would\n // otherwise reopen the menu via openOnFocus.\n if (val && blockReopen) return\n\n isOpen.value = val\n singleOpen.value = val\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n}\n\n// ── Multiple-mode actions ──────────────────────────────────────────────────\n\nfunction onMultipleSelect(value: string) {\n selectingItem = true\n const idx = selectedValues.value.indexOf(value)\n selectedValues.value = idx === -1\n ? [...selectedValues.value, value]\n : selectedValues.value.filter((_, i) => i !== idx)\n // Clear the input and drop out of \"typing\" mode so the filter is ignored and\n // the full list shows again (effectiveIgnoreFilter depends on !isUserTyping).\n searchTerm.value = ''\n isUserTyping.value = false\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction removeValue(value: string) {\n selectedValues.value = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction clearAll() {\n selectedValues.value = []\n emit('update:modelValue', [])\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\n// ── Creatable ──────────────────────────────────────────────────────────────\n\nconst hasExactMatch = computed(() => {\n const term = searchTerm.value.trim().toLowerCase()\n if (!term) return false\n const inItems = internalItems.value.some(\n i => (i.label ?? i.textValue ?? i.value).toLowerCase() === term,\n )\n if (inItems) return true\n for (const label of slotItemRegistry.value.values()) {\n if (label.toLowerCase() === term) return true\n }\n return false\n})\n\nfunction onCreateValue(value: string) {\n const trimmed = value.trim()\n if (!trimmed) return\n if (props.multiple) {\n // We own selection state; add the value, then reset input + filter for the\n // next entry (dropdown is kept open and refocused by AutocompleteCreateItem).\n if (!selectedValues.value.includes(trimmed)) {\n selectingItem = true\n selectedValues.value = [...selectedValues.value, trimmed]\n emit('update:modelValue', selectedValues.value)\n }\n searchTerm.value = ''\n isUserTyping.value = false\n } else {\n // Single mode: set the value ourselves and close the (controlled) dropdown.\n searchTerm.value = trimmed\n emit('update:modelValue', trimmed)\n isOpen.value = false\n singleOpen.value = false\n // The create handler (parent @create) typically mutates the items list, which\n // re-renders and bounces focus back to the input — that fires openOnFocus and\n // would reopen the menu. Block reopen requests briefly so the close sticks.\n blockReopen = true\n clearTimeout(blockReopenTimer)\n blockReopenTimer = setTimeout(() => { blockReopen = false }, 300)\n }\n emit('create', trimmed)\n}\n\n// ── Async loading ──────────────────────────────────────────────────────────\nlet debounceTimer: ReturnType<typeof setTimeout> | undefined\n\nasync function runLoadItems(query: string) {\n if (!props.loadItems) return\n isLoading.value = true\n try {\n internalItems.value = await props.loadItems(query)\n } finally {\n isLoading.value = false\n }\n}\n\nfunction scheduleLoad(query: string) {\n if (!props.loadItems) return\n clearTimeout(debounceTimer)\n if (props.debounceMs === 0) {\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\nonMounted(() => {\n if (props.loadItems) void runLoadItems(searchTerm.value)\n})\n\nwatch(searchTerm, (q) => {\n if (props.loadItems) scheduleLoad(q)\n})\n\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) internalItems.value = [...newItems]\n})\n\nwatch(internalItems, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\nwatch(slotItemRegistry, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\n// ── Styles / context ───────────────────────────────────────────────────────\n\nconst slotFns = computed(() =>\n autocompleteVariants({\n variant: props.variant,\n size: props.size,\n color: props.color,\n fullWidth: props.fullWidth,\n isInvalid: props.isInvalid,\n isDisabled: props.isDisabled,\n isReadonly: props.isReadonly,\n hasLabel: hasLabel.value,\n labelPlacement: props.labelPlacement,\n }),\n)\n\nconst showOutsideLabel = computed(\n () => hasLabel.value && props.labelPlacement !== 'inside',\n)\n\nuseAutocompleteProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n isReadonly: toRef(props, 'isReadonly'),\n isRequired: toRef(props, 'isRequired'),\n isLoading,\n isFilled,\n fullWidth: toRef(props, 'fullWidth'),\n hasLabel,\n labelPlacement: toRef(props, 'labelPlacement'),\n inputId,\n label: toRef(props, 'label'),\n ariaDescribedBy,\n truncateItems: toRef(props, 'truncateItems'),\n hasItems,\n slots: slotFns,\n multiple: toRef(props, 'multiple'),\n multipleOverflow: toRef(props, 'multipleOverflow'),\n selectedValues,\n selectedLabels,\n onMultipleSelect,\n removeValue,\n clearAll,\n isSelected,\n registerItem,\n unregisterItem,\n searchTerm,\n hasExactMatch,\n onCreateValue,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :data-invalid=\"isInvalid || undefined\"\n :data-disabled=\"isDisabled || undefined\"\n :data-readonly=\"isReadonly || undefined\"\n :data-required=\"isRequired || undefined\"\n :data-has-label=\"hasLabel || undefined\"\n :data-has-helper=\"hasHelper || undefined\"\n >\n <label\n v-if=\"showOutsideLabel\"\n :for=\"inputId\"\n :class=\"slotFns.label()\"\n >{{ label }}<span\n v-if=\"isRequired\"\n aria-hidden=\"true\"\n > *</span></label>\n\n <div :class=\"slotFns.mainWrapper()\">\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.multiple ? internalOpen : singleOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :ignore-filter=\"effectiveIgnoreFilter\"\n :open-on-focus=\"true\"\n @update:open=\"handleOpenChange\"\n >\n <slot\n v-if=\"usesCustomChrome\"\n :is-loading=\"isLoading\"\n :items=\"internalItems\"\n />\n <template v-else>\n <AutocompleteInput :placeholder=\"props.placeholder\" />\n <AutocompleteContent>\n <AutocompleteItem\n v-for=\"item in internalItems\"\n :key=\"item.value\"\n :value=\"item.value\"\n :is-disabled=\"item.isDisabled\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n >{{ item.label ?? item.textValue ?? item.value }}</slot>\n </AutocompleteItem>\n </AutocompleteContent>\n </template>\n </AutocompleteRoot>\n\n <div\n v-if=\"hasHelper\"\n :class=\"slotFns.helperWrapper()\"\n >\n <div\n v-if=\"showError\"\n :id=\"errorMessageId\"\n :class=\"slotFns.errorMessage()\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"showDescription\"\n :id=\"descriptionId\"\n :class=\"slotFns.description()\"\n >\n {{ description }}\n </div>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { composeClassName } from "../../utils/composeClassName.js";
|
|
2
|
+
import { hasSlotComponent } from "../../utils/hasSlotComponent.js";
|
|
2
3
|
import { useAutocompleteProvide } from "./Autocomplete.context.js";
|
|
3
|
-
import
|
|
4
|
+
import AutocompleteInput_default from "./AutocompleteInput.js";
|
|
5
|
+
import AutocompleteContent_default from "./AutocompleteContent.js";
|
|
6
|
+
import AutocompleteItem_default from "./AutocompleteItem.js";
|
|
7
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, onMounted, openBlock, ref, renderList, renderSlot, toDisplayString, toRef, unref, useAttrs, useId, useSlots, watch, withCtx } from "vue";
|
|
4
8
|
import { autocompleteVariants } from "@auronui/styles";
|
|
5
9
|
import { AutocompleteRoot } from "reka-ui";
|
|
6
10
|
//#region src/components/autocomplete/Autocomplete.vue?vue&type=script&setup=true&lang.ts
|
|
@@ -55,6 +59,11 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
55
59
|
class: {},
|
|
56
60
|
modelValue: { default: void 0 },
|
|
57
61
|
defaultValue: { default: void 0 },
|
|
62
|
+
multiple: {
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: false
|
|
65
|
+
},
|
|
66
|
+
multipleOverflow: { default: "wrap" },
|
|
58
67
|
open: {
|
|
59
68
|
type: Boolean,
|
|
60
69
|
default: void 0
|
|
@@ -78,7 +87,11 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
78
87
|
default: true
|
|
79
88
|
}
|
|
80
89
|
},
|
|
81
|
-
emits: [
|
|
90
|
+
emits: [
|
|
91
|
+
"update:modelValue",
|
|
92
|
+
"update:open",
|
|
93
|
+
"create"
|
|
94
|
+
],
|
|
82
95
|
setup(__props, { emit: __emit }) {
|
|
83
96
|
const props = __props;
|
|
84
97
|
const emit = __emit;
|
|
@@ -86,6 +99,8 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
86
99
|
const generatedId = useId();
|
|
87
100
|
const inputId = computed(() => attrs.id ?? generatedId);
|
|
88
101
|
const hasLabel = computed(() => !!props.label);
|
|
102
|
+
const slots = useSlots();
|
|
103
|
+
const usesCustomChrome = computed(() => hasSlotComponent(slots.default?.(), [AutocompleteInput_default, AutocompleteContent_default]));
|
|
89
104
|
const slotItemRegistry = ref(/* @__PURE__ */ new Map());
|
|
90
105
|
function registerItem(value, label) {
|
|
91
106
|
const next = new Map(slotItemRegistry.value);
|
|
@@ -99,9 +114,15 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
99
114
|
}
|
|
100
115
|
const isLoading = ref(false);
|
|
101
116
|
const internalItems = ref([...props.items]);
|
|
117
|
+
const selectedValues = ref(props.multiple && Array.isArray(props.modelValue) ? [...props.modelValue] : []);
|
|
118
|
+
const internalOpen = ref(props.defaultOpen ?? false);
|
|
119
|
+
let selectingItem = false;
|
|
102
120
|
const isOpen = ref(props.defaultOpen ?? false);
|
|
103
121
|
const termAtOpen = ref("");
|
|
104
122
|
const isUserTyping = ref(false);
|
|
123
|
+
const singleOpen = ref(props.open ?? props.defaultOpen ?? false);
|
|
124
|
+
let blockReopen = false;
|
|
125
|
+
let blockReopenTimer;
|
|
105
126
|
const effectiveIgnoreFilter = computed(() => {
|
|
106
127
|
if (props.loadItems) return true;
|
|
107
128
|
if (!props.filterOnOpen && isOpen.value && !isUserTyping.value) return true;
|
|
@@ -120,9 +141,14 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
120
141
|
for (const [value, label] of slotItemRegistry.value) if (label === displayed) return value;
|
|
121
142
|
return displayed;
|
|
122
143
|
}
|
|
123
|
-
const
|
|
124
|
-
const
|
|
144
|
+
const singleModelValue = computed(() => props.multiple ? void 0 : props.modelValue);
|
|
145
|
+
const searchTerm = ref(labelFor(singleModelValue.value));
|
|
146
|
+
const isFilled = computed(() => props.multiple ? selectedValues.value.length > 0 || !!searchTerm.value : !!searchTerm.value);
|
|
125
147
|
const hasItems = computed(() => internalItems.value.length > 0);
|
|
148
|
+
const selectedLabels = computed(() => selectedValues.value.map((v) => ({
|
|
149
|
+
value: v,
|
|
150
|
+
label: labelFor(v) || v
|
|
151
|
+
})));
|
|
126
152
|
const descriptionId = computed(() => `${inputId.value}-description`);
|
|
127
153
|
const errorMessageId = computed(() => `${inputId.value}-error`);
|
|
128
154
|
const showError = computed(() => props.isInvalid && !!props.errorMessage);
|
|
@@ -133,22 +159,96 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
133
159
|
if (showDescription.value) return descriptionId.value;
|
|
134
160
|
});
|
|
135
161
|
watch(() => props.modelValue, (val) => {
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
if (props.multiple) {
|
|
163
|
+
if (Array.isArray(val)) selectedValues.value = [...val];
|
|
164
|
+
} else {
|
|
165
|
+
const next = labelFor(val);
|
|
166
|
+
if (searchTerm.value !== next) searchTerm.value = next;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
watch(() => props.open, (val) => {
|
|
170
|
+
if (!props.multiple && val !== void 0) singleOpen.value = val;
|
|
138
171
|
});
|
|
139
172
|
watch(searchTerm, (displayed) => {
|
|
173
|
+
if (props.multiple) {
|
|
174
|
+
if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
140
177
|
const next = valueFor(displayed);
|
|
141
|
-
if (next !== (
|
|
178
|
+
if (next !== (singleModelValue.value ?? "")) emit("update:modelValue", next);
|
|
142
179
|
if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true;
|
|
143
180
|
});
|
|
144
181
|
function handleOpenChange(val) {
|
|
182
|
+
if (props.multiple) {
|
|
183
|
+
isOpen.value = val;
|
|
184
|
+
internalOpen.value = !val && selectingItem ? true : val;
|
|
185
|
+
selectingItem = false;
|
|
186
|
+
if (val) {
|
|
187
|
+
termAtOpen.value = searchTerm.value;
|
|
188
|
+
isUserTyping.value = false;
|
|
189
|
+
} else isUserTyping.value = false;
|
|
190
|
+
emit("update:open", val);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (val && blockReopen) return;
|
|
145
194
|
isOpen.value = val;
|
|
195
|
+
singleOpen.value = val;
|
|
146
196
|
if (val) {
|
|
147
197
|
termAtOpen.value = searchTerm.value;
|
|
148
198
|
isUserTyping.value = false;
|
|
149
199
|
} else isUserTyping.value = false;
|
|
150
200
|
emit("update:open", val);
|
|
151
201
|
}
|
|
202
|
+
function onMultipleSelect(value) {
|
|
203
|
+
selectingItem = true;
|
|
204
|
+
const idx = selectedValues.value.indexOf(value);
|
|
205
|
+
selectedValues.value = idx === -1 ? [...selectedValues.value, value] : selectedValues.value.filter((_, i) => i !== idx);
|
|
206
|
+
searchTerm.value = "";
|
|
207
|
+
isUserTyping.value = false;
|
|
208
|
+
emit("update:modelValue", selectedValues.value);
|
|
209
|
+
}
|
|
210
|
+
function removeValue(value) {
|
|
211
|
+
selectedValues.value = selectedValues.value.filter((v) => v !== value);
|
|
212
|
+
emit("update:modelValue", selectedValues.value);
|
|
213
|
+
}
|
|
214
|
+
function clearAll() {
|
|
215
|
+
selectedValues.value = [];
|
|
216
|
+
emit("update:modelValue", []);
|
|
217
|
+
}
|
|
218
|
+
function isSelected(value) {
|
|
219
|
+
return selectedValues.value.includes(value);
|
|
220
|
+
}
|
|
221
|
+
const hasExactMatch = computed(() => {
|
|
222
|
+
const term = searchTerm.value.trim().toLowerCase();
|
|
223
|
+
if (!term) return false;
|
|
224
|
+
if (internalItems.value.some((i) => (i.label ?? i.textValue ?? i.value).toLowerCase() === term)) return true;
|
|
225
|
+
for (const label of slotItemRegistry.value.values()) if (label.toLowerCase() === term) return true;
|
|
226
|
+
return false;
|
|
227
|
+
});
|
|
228
|
+
function onCreateValue(value) {
|
|
229
|
+
const trimmed = value.trim();
|
|
230
|
+
if (!trimmed) return;
|
|
231
|
+
if (props.multiple) {
|
|
232
|
+
if (!selectedValues.value.includes(trimmed)) {
|
|
233
|
+
selectingItem = true;
|
|
234
|
+
selectedValues.value = [...selectedValues.value, trimmed];
|
|
235
|
+
emit("update:modelValue", selectedValues.value);
|
|
236
|
+
}
|
|
237
|
+
searchTerm.value = "";
|
|
238
|
+
isUserTyping.value = false;
|
|
239
|
+
} else {
|
|
240
|
+
searchTerm.value = trimmed;
|
|
241
|
+
emit("update:modelValue", trimmed);
|
|
242
|
+
isOpen.value = false;
|
|
243
|
+
singleOpen.value = false;
|
|
244
|
+
blockReopen = true;
|
|
245
|
+
clearTimeout(blockReopenTimer);
|
|
246
|
+
blockReopenTimer = setTimeout(() => {
|
|
247
|
+
blockReopen = false;
|
|
248
|
+
}, 300);
|
|
249
|
+
}
|
|
250
|
+
emit("create", trimmed);
|
|
251
|
+
}
|
|
152
252
|
let debounceTimer;
|
|
153
253
|
async function runLoadItems(query) {
|
|
154
254
|
if (!props.loadItems) return;
|
|
@@ -175,12 +275,14 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
175
275
|
if (!props.loadItems) internalItems.value = [...newItems];
|
|
176
276
|
});
|
|
177
277
|
watch(internalItems, () => {
|
|
178
|
-
|
|
179
|
-
|
|
278
|
+
if (props.multiple) return;
|
|
279
|
+
const next = labelFor(singleModelValue.value);
|
|
280
|
+
if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? "")) searchTerm.value = next;
|
|
180
281
|
});
|
|
181
282
|
watch(slotItemRegistry, () => {
|
|
182
|
-
|
|
183
|
-
|
|
283
|
+
if (props.multiple) return;
|
|
284
|
+
const next = labelFor(singleModelValue.value);
|
|
285
|
+
if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? "")) searchTerm.value = next;
|
|
184
286
|
});
|
|
185
287
|
const slotFns = computed(() => autocompleteVariants({
|
|
186
288
|
variant: props.variant,
|
|
@@ -210,8 +312,19 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
210
312
|
truncateItems: toRef(props, "truncateItems"),
|
|
211
313
|
hasItems,
|
|
212
314
|
slots: slotFns,
|
|
315
|
+
multiple: toRef(props, "multiple"),
|
|
316
|
+
multipleOverflow: toRef(props, "multipleOverflow"),
|
|
317
|
+
selectedValues,
|
|
318
|
+
selectedLabels,
|
|
319
|
+
onMultipleSelect,
|
|
320
|
+
removeValue,
|
|
321
|
+
clearAll,
|
|
322
|
+
isSelected,
|
|
213
323
|
registerItem,
|
|
214
|
-
unregisterItem
|
|
324
|
+
unregisterItem,
|
|
325
|
+
searchTerm,
|
|
326
|
+
hasExactMatch,
|
|
327
|
+
onCreateValue
|
|
215
328
|
});
|
|
216
329
|
return (_ctx, _cache) => {
|
|
217
330
|
return openBlock(), createElementBlock("div", {
|
|
@@ -229,23 +342,34 @@ var Autocomplete_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ d
|
|
|
229
342
|
}, [createTextVNode(toDisplayString(__props.label), 1), __props.isRequired ? (openBlock(), createElementBlock("span", _hoisted_3, " *")) : createCommentVNode("", true)], 10, _hoisted_2)) : createCommentVNode("", true), createElementVNode("div", { class: normalizeClass(slotFns.value.mainWrapper()) }, [createVNode(unref(AutocompleteRoot), {
|
|
230
343
|
"model-value": searchTerm.value,
|
|
231
344
|
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchTerm.value = $event),
|
|
232
|
-
open: props.
|
|
233
|
-
"default-open": props.defaultOpen,
|
|
345
|
+
open: props.multiple ? internalOpen.value : singleOpen.value,
|
|
234
346
|
disabled: props.isDisabled,
|
|
235
347
|
required: props.isRequired,
|
|
236
348
|
"ignore-filter": effectiveIgnoreFilter.value,
|
|
237
349
|
"open-on-focus": true,
|
|
238
350
|
"onUpdate:open": handleOpenChange
|
|
239
351
|
}, {
|
|
240
|
-
default: withCtx(() => [renderSlot(_ctx.$slots, "default", {
|
|
352
|
+
default: withCtx(() => [usesCustomChrome.value ? renderSlot(_ctx.$slots, "default", {
|
|
353
|
+
key: 0,
|
|
241
354
|
isLoading: isLoading.value,
|
|
242
355
|
items: internalItems.value
|
|
243
|
-
})]),
|
|
356
|
+
}) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createVNode(AutocompleteInput_default, { placeholder: props.placeholder }, null, 8, ["placeholder"]), createVNode(AutocompleteContent_default, null, {
|
|
357
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(internalItems.value, (item) => {
|
|
358
|
+
return openBlock(), createBlock(AutocompleteItem_default, {
|
|
359
|
+
key: item.value,
|
|
360
|
+
value: item.value,
|
|
361
|
+
"is-disabled": item.isDisabled
|
|
362
|
+
}, {
|
|
363
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "item", { item }, () => [createTextVNode(toDisplayString(item.label ?? item.textValue ?? item.value), 1)])]),
|
|
364
|
+
_: 2
|
|
365
|
+
}, 1032, ["value", "is-disabled"]);
|
|
366
|
+
}), 128))]),
|
|
367
|
+
_: 3
|
|
368
|
+
})], 64))]),
|
|
244
369
|
_: 3
|
|
245
370
|
}, 8, [
|
|
246
371
|
"model-value",
|
|
247
372
|
"open",
|
|
248
|
-
"default-open",
|
|
249
373
|
"disabled",
|
|
250
374
|
"required",
|
|
251
375
|
"ignore-filter"
|
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// 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
|
+
{"version":3,"file":"Autocomplete.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/autocomplete/Autocomplete.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, ref, toRef, useAttrs, useId, watch, useSlots } from 'vue'\nimport { AutocompleteRoot } from 'reka-ui'\nimport { autocompleteVariants, type AutocompleteVariants } from '@auronui/styles'\nimport { composeClassName } from '../../utils/composeClassName'\nimport { useAutocompleteProvide } from './Autocomplete.context'\nimport { hasSlotComponent } from '../../utils/hasSlotComponent'\nimport AutocompleteInput from './AutocompleteInput.vue'\nimport AutocompleteContent from './AutocompleteContent.vue'\nimport AutocompleteItem from './AutocompleteItem.vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'flat',\n size: 'md',\n color: 'default',\n labelPlacement: 'inside',\n fullWidth: false,\n isInvalid: false,\n isDisabled: false,\n isReadonly: false,\n isRequired: false,\n multiple: false,\n multipleOverflow: 'wrap',\n modelValue: undefined,\n defaultValue: undefined,\n open: undefined,\n defaultOpen: undefined,\n items: () => [],\n loadItems: undefined,\n debounceMs: 200,\n filterOnOpen: false,\n truncateItems: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | string[]]\n 'update:open': [value: boolean]\n /** Fired when the user creates a new value via `creatable` or `<AutocompleteCreateItem>`. */\n 'create': [value: string]\n}>()\n\nexport interface AutocompleteItem {\n value: string\n label?: string\n textValue?: string\n isDisabled?: boolean\n}\n\ntype Props = {\n /** Visual style of the field. @default 'flat' */\n variant?: AutocompleteVariants['variant']\n /** Field height. @default 'md' */\n size?: AutocompleteVariants['size']\n /** Accent color applied to focus ring + floating label. @default 'default' */\n color?: AutocompleteVariants['color']\n /**\n * Where the `label` is rendered relative to the field.\n * - `inside`: floats above the trigger (shrinks when focused/filled)\n * - `outside`: sits above the field, static\n * - `outside-left`: sits to the left, horizontal layout\n * @default 'inside'\n */\n labelPlacement?: AutocompleteVariants['labelPlacement']\n /** Stretches root wrapper to 100% width. @default false */\n fullWidth?: boolean\n /** Marks the field as invalid. Triggers danger styling and enables `errorMessage`. @default false */\n isInvalid?: boolean\n /** Disables the field. @default false */\n isDisabled?: boolean\n /** Makes the field read-only. @default false */\n isReadonly?: boolean\n /** Adds a required asterisk next to the label. @default false */\n isRequired?: boolean\n /** Placeholder shown when empty. */\n placeholder?: string\n /** Form field name, for native form submission. */\n name?: string\n /** Field label. When omitted, the floating-label behavior is skipped. */\n label?: string\n /** Helper text displayed below the field. Suppressed when `isInvalid && errorMessage` is shown. */\n description?: string\n /** Error text displayed below the field. Only rendered when `isInvalid` is also true. */\n errorMessage?: string\n /** Extra classes merged onto the root wrapper via `composeClassName`. */\n class?: string\n\n /* ─── Autocomplete-specific ─────────────────────────────────────── */\n /** Two-way bound selected value. string in single mode, string[] in multiple mode. */\n modelValue?: string | string[]\n /** Initial selected value (uncontrolled). */\n defaultValue?: string | string[]\n /** Allow selecting multiple values. modelValue becomes string[]. @default false */\n multiple?: boolean\n /**\n * Controls how chips overflow in multiple mode.\n * - `wrap`: trigger grows in height, chips wrap to new lines (default)\n * - `collapse`: fixed height, overflowing chips are hidden behind \"+N more\"\n * @default 'wrap'\n */\n multipleOverflow?: 'wrap' | 'collapse'\n /** Controls open state of the dropdown. */\n open?: boolean\n /** Initial open state of the dropdown (uncontrolled). */\n defaultOpen?: boolean\n /** Static items list — used when no loadItems is provided. */\n items?: AutocompleteItem[]\n /** Async data source: called on every query change. */\n loadItems?: (query: string) => Promise<AutocompleteItem[]>\n /** Debounce delay for loadItems calls (ms). 0 = no debounce. */\n debounceMs?: number\n /** Apply filter immediately on open (default: false — show all items until user types). */\n filterOnOpen?: boolean\n /**\n * Truncate item text with an ellipsis when it overflows the dropdown width.\n * Set to `false` to show full text — the dropdown will widen to fit.\n * @default true\n */\n truncateItems?: boolean\n}\n\nconst attrs = useAttrs()\nconst generatedId = useId()\nconst inputId = computed(() => (attrs.id as string | undefined) ?? generatedId)\n\nconst hasLabel = computed(() => !!props.label)\n\nconst slots = useSlots()\n// Compound chrome present → pass slot through (advanced). Otherwise render the\n// input/content/items internally (short-form).\nconst usesCustomChrome = computed(() =>\n hasSlotComponent(slots.default?.(), [AutocompleteInput, AutocompleteContent]),\n)\n\n// Registry for slot-rendered items: value → label (populated by AutocompleteItem at mount).\n// Replaced with a new Map instance on each mutation so Vue's ref() reactivity tracks changes.\nconst slotItemRegistry = ref(new Map<string, string>())\n\nfunction registerItem(value: string, label: string) {\n const next = new Map(slotItemRegistry.value)\n next.set(value, label)\n slotItemRegistry.value = next\n}\n\nfunction unregisterItem(value: string) {\n const next = new Map(slotItemRegistry.value)\n next.delete(value)\n slotItemRegistry.value = next\n}\n\n// Internal async state\nconst isLoading = ref(false)\nconst internalItems = ref<AutocompleteItem[]>([...props.items])\n\n// ── Multiple-mode state ────────────────────────────────────────────────────\n// Tracks selected values as an array. Only meaningful when props.multiple=true.\nconst selectedValues = ref<string[]>(\n props.multiple && Array.isArray(props.modelValue) ? [...props.modelValue] : [],\n)\n\n// Controlled open state used in multiple mode to prevent the dropdown closing\n// after each item selection (Reka would normally close on selection).\nconst internalOpen = ref(props.defaultOpen ?? false)\n\n// Flag set by onMultipleSelect so handleOpenChange can distinguish item\n// selection from Escape/outside-click close.\nlet selectingItem = false\n\n// ── Open-state tracking ────────────────────────────────────────────────────\nconst isOpen = ref(props.defaultOpen ?? false)\nconst termAtOpen = ref('')\nconst isUserTyping = ref(false)\n// Controlled open state for single mode (mirrors multiple mode's internalOpen).\n// Driving open ourselves lets handleOpenChange gate spurious reopens — e.g. the\n// focus bounce caused when a create handler mutates the items list.\nconst singleOpen = ref(props.open ?? props.defaultOpen ?? false)\n// When true, handleOpenChange ignores open=true requests. Set briefly after a\n// single-mode create so the post-create re-render can't reopen the menu.\nlet blockReopen = false\nlet blockReopenTimer: ReturnType<typeof setTimeout> | undefined\nconst effectiveIgnoreFilter = computed(() => {\n if (props.loadItems) return true\n if (!props.filterOnOpen && isOpen.value && !isUserTyping.value) return true\n return false\n})\n\n// ── Label/value bridge ─────────────────────────────────────────────────────\n// Priority: items prop entry > slot registry > identity fallback\nfunction labelFor(value: string | undefined): string {\n if (value == null || value === '') return ''\n const match = internalItems.value.find((i) => i.value === value)\n if (match) return match.label ?? match.textValue ?? value\n return slotItemRegistry.value.get(value) ?? value\n}\nfunction valueFor(displayed: string): string {\n if (!displayed) return ''\n const match = internalItems.value.find(\n (i) => (i.label ?? i.textValue ?? i.value) === displayed,\n )\n if (match) return match.value\n for (const [value, label] of slotItemRegistry.value) {\n if (label === displayed) return value\n }\n return displayed\n}\n\nconst singleModelValue = computed(() =>\n props.multiple ? undefined : (props.modelValue as string | undefined),\n)\n\nconst searchTerm = ref(labelFor(singleModelValue.value))\n\nconst isFilled = computed(() =>\n props.multiple\n ? selectedValues.value.length > 0 || !!searchTerm.value\n : !!searchTerm.value,\n)\nconst hasItems = computed(() => internalItems.value.length > 0)\n\nconst selectedLabels = computed(() =>\n selectedValues.value.map(v => ({ value: v, label: labelFor(v) || v })),\n)\n\n// ── Helpers ────────────────────────────────────────────────────────────────\nconst descriptionId = computed(() => `${inputId.value}-description`)\nconst errorMessageId = computed(() => `${inputId.value}-error`)\nconst showError = computed(() => props.isInvalid && !!props.errorMessage)\nconst showDescription = computed(() => !!props.description && !showError.value)\nconst hasHelper = computed(() => showError.value || showDescription.value)\nconst ariaDescribedBy = computed(() => {\n if (showError.value) return errorMessageId.value\n if (showDescription.value) return descriptionId.value\n return undefined\n})\n\n// ── Watchers ───────────────────────────────────────────────────────────────\n\n// Parent → internal: sync controlled modelValue into local state\nwatch(() => props.modelValue, (val) => {\n if (props.multiple) {\n if (Array.isArray(val)) selectedValues.value = [...val]\n } else {\n const next = labelFor(val as string | undefined)\n if (searchTerm.value !== next) searchTerm.value = next\n }\n})\n\n// Parent → internal: sync consumer-controlled open into our single-mode state\nwatch(() => props.open, (val) => {\n if (!props.multiple && val !== undefined) singleOpen.value = val\n})\n\n// Internal → parent: single mode only — multiple mode emits inside onMultipleSelect\nwatch(searchTerm, (displayed) => {\n if (props.multiple) {\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n return\n }\n const next = valueFor(displayed)\n if (next !== (singleModelValue.value ?? '')) emit('update:modelValue', next)\n if (isOpen.value && displayed !== termAtOpen.value) isUserTyping.value = true\n})\n\nfunction handleOpenChange(val: boolean) {\n if (props.multiple) {\n isOpen.value = val\n // Suppress close when triggered by item selection; allow Escape/outside-click\n internalOpen.value = (!val && selectingItem) ? true : val\n selectingItem = false\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n return\n }\n\n // Single mode: ignore spurious reopen requests right after a create — the\n // create handler's re-render bounces focus back to the input, which would\n // otherwise reopen the menu via openOnFocus.\n if (val && blockReopen) return\n\n isOpen.value = val\n singleOpen.value = val\n if (val) { termAtOpen.value = searchTerm.value; isUserTyping.value = false }\n else { isUserTyping.value = false }\n emit('update:open', val)\n}\n\n// ── Multiple-mode actions ──────────────────────────────────────────────────\n\nfunction onMultipleSelect(value: string) {\n selectingItem = true\n const idx = selectedValues.value.indexOf(value)\n selectedValues.value = idx === -1\n ? [...selectedValues.value, value]\n : selectedValues.value.filter((_, i) => i !== idx)\n // Clear the input and drop out of \"typing\" mode so the filter is ignored and\n // the full list shows again (effectiveIgnoreFilter depends on !isUserTyping).\n searchTerm.value = ''\n isUserTyping.value = false\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction removeValue(value: string) {\n selectedValues.value = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', selectedValues.value)\n}\n\nfunction clearAll() {\n selectedValues.value = []\n emit('update:modelValue', [])\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\n// ── Creatable ──────────────────────────────────────────────────────────────\n\nconst hasExactMatch = computed(() => {\n const term = searchTerm.value.trim().toLowerCase()\n if (!term) return false\n const inItems = internalItems.value.some(\n i => (i.label ?? i.textValue ?? i.value).toLowerCase() === term,\n )\n if (inItems) return true\n for (const label of slotItemRegistry.value.values()) {\n if (label.toLowerCase() === term) return true\n }\n return false\n})\n\nfunction onCreateValue(value: string) {\n const trimmed = value.trim()\n if (!trimmed) return\n if (props.multiple) {\n // We own selection state; add the value, then reset input + filter for the\n // next entry (dropdown is kept open and refocused by AutocompleteCreateItem).\n if (!selectedValues.value.includes(trimmed)) {\n selectingItem = true\n selectedValues.value = [...selectedValues.value, trimmed]\n emit('update:modelValue', selectedValues.value)\n }\n searchTerm.value = ''\n isUserTyping.value = false\n } else {\n // Single mode: set the value ourselves and close the (controlled) dropdown.\n searchTerm.value = trimmed\n emit('update:modelValue', trimmed)\n isOpen.value = false\n singleOpen.value = false\n // The create handler (parent @create) typically mutates the items list, which\n // re-renders and bounces focus back to the input — that fires openOnFocus and\n // would reopen the menu. Block reopen requests briefly so the close sticks.\n blockReopen = true\n clearTimeout(blockReopenTimer)\n blockReopenTimer = setTimeout(() => { blockReopen = false }, 300)\n }\n emit('create', trimmed)\n}\n\n// ── Async loading ──────────────────────────────────────────────────────────\nlet debounceTimer: ReturnType<typeof setTimeout> | undefined\n\nasync function runLoadItems(query: string) {\n if (!props.loadItems) return\n isLoading.value = true\n try {\n internalItems.value = await props.loadItems(query)\n } finally {\n isLoading.value = false\n }\n}\n\nfunction scheduleLoad(query: string) {\n if (!props.loadItems) return\n clearTimeout(debounceTimer)\n if (props.debounceMs === 0) {\n void runLoadItems(query)\n } else {\n debounceTimer = setTimeout(() => void runLoadItems(query), props.debounceMs)\n }\n}\n\nonMounted(() => {\n if (props.loadItems) void runLoadItems(searchTerm.value)\n})\n\nwatch(searchTerm, (q) => {\n if (props.loadItems) scheduleLoad(q)\n})\n\nwatch(() => props.items, (newItems) => {\n if (!props.loadItems) internalItems.value = [...newItems]\n})\n\nwatch(internalItems, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\nwatch(slotItemRegistry, () => {\n if (props.multiple) return\n const next = labelFor(singleModelValue.value)\n if (next && searchTerm.value !== next && valueFor(searchTerm.value) === (singleModelValue.value ?? '')) {\n searchTerm.value = next\n }\n})\n\n// ── Styles / context ───────────────────────────────────────────────────────\n\nconst slotFns = computed(() =>\n autocompleteVariants({\n variant: props.variant,\n size: props.size,\n color: props.color,\n fullWidth: props.fullWidth,\n isInvalid: props.isInvalid,\n isDisabled: props.isDisabled,\n isReadonly: props.isReadonly,\n hasLabel: hasLabel.value,\n labelPlacement: props.labelPlacement,\n }),\n)\n\nconst showOutsideLabel = computed(\n () => hasLabel.value && props.labelPlacement !== 'inside',\n)\n\nuseAutocompleteProvide({\n isDisabled: toRef(props, 'isDisabled'),\n isInvalid: toRef(props, 'isInvalid'),\n isReadonly: toRef(props, 'isReadonly'),\n isRequired: toRef(props, 'isRequired'),\n isLoading,\n isFilled,\n fullWidth: toRef(props, 'fullWidth'),\n hasLabel,\n labelPlacement: toRef(props, 'labelPlacement'),\n inputId,\n label: toRef(props, 'label'),\n ariaDescribedBy,\n truncateItems: toRef(props, 'truncateItems'),\n hasItems,\n slots: slotFns,\n multiple: toRef(props, 'multiple'),\n multipleOverflow: toRef(props, 'multipleOverflow'),\n selectedValues,\n selectedLabels,\n onMultipleSelect,\n removeValue,\n clearAll,\n isSelected,\n registerItem,\n unregisterItem,\n searchTerm,\n hasExactMatch,\n onCreateValue,\n})\n</script>\n\n<template>\n <div\n :class=\"composeClassName(slotFns.base(), props.class)\"\n :data-invalid=\"isInvalid || undefined\"\n :data-disabled=\"isDisabled || undefined\"\n :data-readonly=\"isReadonly || undefined\"\n :data-required=\"isRequired || undefined\"\n :data-has-label=\"hasLabel || undefined\"\n :data-has-helper=\"hasHelper || undefined\"\n >\n <label\n v-if=\"showOutsideLabel\"\n :for=\"inputId\"\n :class=\"slotFns.label()\"\n >{{ label }}<span\n v-if=\"isRequired\"\n aria-hidden=\"true\"\n > *</span></label>\n\n <div :class=\"slotFns.mainWrapper()\">\n <AutocompleteRoot\n v-model:model-value=\"searchTerm\"\n :open=\"props.multiple ? internalOpen : singleOpen\"\n :disabled=\"props.isDisabled\"\n :required=\"props.isRequired\"\n :ignore-filter=\"effectiveIgnoreFilter\"\n :open-on-focus=\"true\"\n @update:open=\"handleOpenChange\"\n >\n <slot\n v-if=\"usesCustomChrome\"\n :is-loading=\"isLoading\"\n :items=\"internalItems\"\n />\n <template v-else>\n <AutocompleteInput :placeholder=\"props.placeholder\" />\n <AutocompleteContent>\n <AutocompleteItem\n v-for=\"item in internalItems\"\n :key=\"item.value\"\n :value=\"item.value\"\n :is-disabled=\"item.isDisabled\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n >{{ item.label ?? item.textValue ?? item.value }}</slot>\n </AutocompleteItem>\n </AutocompleteContent>\n </template>\n </AutocompleteRoot>\n\n <div\n v-if=\"hasHelper\"\n :class=\"slotFns.helperWrapper()\"\n >\n <div\n v-if=\"showError\"\n :id=\"errorMessageId\"\n :class=\"slotFns.errorMessage()\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"showDescription\"\n :id=\"descriptionId\"\n :class=\"slotFns.description()\"\n >\n {{ description }}\n </div>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import AutocompleteCreateItem_vue_vue_type_script_setup_true_lang_default from "./AutocompleteCreateItem.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
//#region src/components/autocomplete/AutocompleteCreateItem.vue
|
|
3
|
+
var AutocompleteCreateItem_default = AutocompleteCreateItem_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { AutocompleteCreateItem_default as default };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=AutocompleteCreateItem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AutocompleteCreateItem.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteCreateItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { AutocompleteItem as RekaAutocompleteItem, injectComboboxRootContext } from 'reka-ui'\nimport { useAutocompleteInject } from './Autocomplete.context'\n\nconst props = withDefaults(defineProps<{\n /**\n * Label for the create item. Accepts a static string or a function receiving\n * the current search term. Defaults to `Create \"${term}\"`.\n */\n label?: string | ((term: string) => string)\n class?: string\n}>(), {\n label: undefined,\n class: undefined,\n})\n\nconst ctx = useAutocompleteInject()\n// Reka's combobox context (provided by AutocompleteRoot) — used to drive the\n// open state and input focus directly, since this item's value equals the\n// current search term and Reka's native select path is unreliable for it.\nconst comboboxCtx = injectComboboxRootContext()\n\nconst term = computed(() => ctx.searchTerm.value.trim())\n\nconst isVisible = computed(() => !!term.value && !ctx.hasExactMatch.value)\n\nconst displayLabel = computed(() => {\n if (typeof props.label === 'function') return props.label(term.value)\n return props.label ?? `Create \"${term.value}\"`\n})\n\nfunction handleSelect(event: Event) {\n // Always manage the creation ourselves — this item's value equals the current\n // search term, so Reka's native select (set-value + close) is unreliable here.\n event.preventDefault()\n ctx.onCreateValue(term.value)\n if (ctx.multiple.value) {\n // Keep the dropdown open for the next entry and restore input focus.\n comboboxCtx.inputElement.value?.focus()\n } else {\n // Single mode: close the dropdown, like selecting a regular item.\n comboboxCtx.onOpenChange(false)\n }\n}\n</script>\n\n<template>\n <RekaAutocompleteItem\n v-if=\"isVisible\"\n :key=\"term\"\n :value=\"term\"\n :text-value=\"term\"\n :class=\"['list-box-item list-box-item--default', props.class]\"\n data-slot=\"list-box-item\"\n data-create-item\n @select=\"handleSelect\"\n >\n <slot :term=\"term\">\n <span\n class=\"autocomplete-item__text\"\n data-slot=\"item-text\"\n >\n {{ displayLabel }}\n </span>\n </slot>\n </RekaAutocompleteItem>\n</template>\n"],"mappings":""}
|
package/dist/components/autocomplete/AutocompleteCreateItem.vue_vue_type_script_setup_true_lang.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useAutocompleteInject } from "./Autocomplete.context.js";
|
|
2
|
+
import { computed, createBlock, createCommentVNode, createElementVNode, defineComponent, normalizeClass, openBlock, renderSlot, toDisplayString, unref, withCtx } from "vue";
|
|
3
|
+
import { AutocompleteItem, injectComboboxRootContext } from "reka-ui";
|
|
4
|
+
//#region src/components/autocomplete/AutocompleteCreateItem.vue?vue&type=script&setup=true&lang.ts
|
|
5
|
+
var _hoisted_1 = {
|
|
6
|
+
class: "autocomplete-item__text",
|
|
7
|
+
"data-slot": "item-text"
|
|
8
|
+
};
|
|
9
|
+
var AutocompleteCreateItem_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
10
|
+
__name: "AutocompleteCreateItem",
|
|
11
|
+
props: {
|
|
12
|
+
label: {
|
|
13
|
+
type: [String, Function],
|
|
14
|
+
default: void 0
|
|
15
|
+
},
|
|
16
|
+
class: { default: void 0 }
|
|
17
|
+
},
|
|
18
|
+
setup(__props) {
|
|
19
|
+
const props = __props;
|
|
20
|
+
const ctx = useAutocompleteInject();
|
|
21
|
+
const comboboxCtx = injectComboboxRootContext();
|
|
22
|
+
const term = computed(() => ctx.searchTerm.value.trim());
|
|
23
|
+
const isVisible = computed(() => !!term.value && !ctx.hasExactMatch.value);
|
|
24
|
+
const displayLabel = computed(() => {
|
|
25
|
+
if (typeof props.label === "function") return props.label(term.value);
|
|
26
|
+
return props.label ?? `Create "${term.value}"`;
|
|
27
|
+
});
|
|
28
|
+
function handleSelect(event) {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
ctx.onCreateValue(term.value);
|
|
31
|
+
if (ctx.multiple.value) comboboxCtx.inputElement.value?.focus();
|
|
32
|
+
else comboboxCtx.onOpenChange(false);
|
|
33
|
+
}
|
|
34
|
+
return (_ctx, _cache) => {
|
|
35
|
+
return isVisible.value ? (openBlock(), createBlock(unref(AutocompleteItem), {
|
|
36
|
+
key: term.value,
|
|
37
|
+
value: term.value,
|
|
38
|
+
"text-value": term.value,
|
|
39
|
+
class: normalizeClass(["list-box-item list-box-item--default", props.class]),
|
|
40
|
+
"data-slot": "list-box-item",
|
|
41
|
+
"data-create-item": "",
|
|
42
|
+
onSelect: handleSelect
|
|
43
|
+
}, {
|
|
44
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "default", { term: term.value }, () => [createElementVNode("span", _hoisted_1, toDisplayString(displayLabel.value), 1)])]),
|
|
45
|
+
_: 3
|
|
46
|
+
}, 8, [
|
|
47
|
+
"value",
|
|
48
|
+
"text-value",
|
|
49
|
+
"class"
|
|
50
|
+
])) : createCommentVNode("", true);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
//#endregion
|
|
55
|
+
export { AutocompleteCreateItem_vue_vue_type_script_setup_true_lang_default as default };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=AutocompleteCreateItem.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AutocompleteCreateItem.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteCreateItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { AutocompleteItem as RekaAutocompleteItem, injectComboboxRootContext } from 'reka-ui'\nimport { useAutocompleteInject } from './Autocomplete.context'\n\nconst props = withDefaults(defineProps<{\n /**\n * Label for the create item. Accepts a static string or a function receiving\n * the current search term. Defaults to `Create \"${term}\"`.\n */\n label?: string | ((term: string) => string)\n class?: string\n}>(), {\n label: undefined,\n class: undefined,\n})\n\nconst ctx = useAutocompleteInject()\n// Reka's combobox context (provided by AutocompleteRoot) — used to drive the\n// open state and input focus directly, since this item's value equals the\n// current search term and Reka's native select path is unreliable for it.\nconst comboboxCtx = injectComboboxRootContext()\n\nconst term = computed(() => ctx.searchTerm.value.trim())\n\nconst isVisible = computed(() => !!term.value && !ctx.hasExactMatch.value)\n\nconst displayLabel = computed(() => {\n if (typeof props.label === 'function') return props.label(term.value)\n return props.label ?? `Create \"${term.value}\"`\n})\n\nfunction handleSelect(event: Event) {\n // Always manage the creation ourselves — this item's value equals the current\n // search term, so Reka's native select (set-value + close) is unreliable here.\n event.preventDefault()\n ctx.onCreateValue(term.value)\n if (ctx.multiple.value) {\n // Keep the dropdown open for the next entry and restore input focus.\n comboboxCtx.inputElement.value?.focus()\n } else {\n // Single mode: close the dropdown, like selecting a regular item.\n comboboxCtx.onOpenChange(false)\n }\n}\n</script>\n\n<template>\n <RekaAutocompleteItem\n v-if=\"isVisible\"\n :key=\"term\"\n :value=\"term\"\n :text-value=\"term\"\n :class=\"['list-box-item list-box-item--default', props.class]\"\n data-slot=\"list-box-item\"\n data-create-item\n @select=\"handleSelect\"\n >\n <slot :term=\"term\">\n <span\n class=\"autocomplete-item__text\"\n data-slot=\"item-text\"\n >\n {{ displayLabel }}\n </span>\n </slot>\n </RekaAutocompleteItem>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;EAKA,MAAM,QAAQ;EAYd,MAAM,MAAM,uBAAsB;EAIlC,MAAM,cAAc,2BAA0B;EAE9C,MAAM,OAAO,eAAe,IAAI,WAAW,MAAM,MAAM,CAAA;EAEvD,MAAM,YAAY,eAAe,CAAC,CAAC,KAAK,SAAS,CAAC,IAAI,cAAc,MAAK;EAEzE,MAAM,eAAe,eAAe;AAClC,OAAI,OAAO,MAAM,UAAU,WAAY,QAAO,MAAM,MAAM,KAAK,MAAK;AACpE,UAAO,MAAM,SAAS,WAAW,KAAK,MAAM;IAC7C;EAED,SAAS,aAAa,OAAc;AAGlC,SAAM,gBAAe;AACrB,OAAI,cAAc,KAAK,MAAK;AAC5B,OAAI,IAAI,SAAS,MAEf,aAAY,aAAa,OAAO,OAAM;OAGtC,aAAY,aAAa,MAAK;;;UAOxB,UAAA,SAAA,WAAA,EADR,YAkBuB,MAAA,iBAAA,EAAA;IAhBpB,KAAK,KAAA;IACL,OAAO,KAAA;IACP,cAAY,KAAA;IACZ,OAAK,eAAA,CAAA,wCAA2C,MAAM,MAAK,CAAA;IAC5D,aAAU;IACV,oBAAA;IACC,UAAQ;;2BASF,CAPP,WAOO,KAAA,QAAA,WAAA,EAPA,MAAM,KAAA,OAAI,QAOV,CANL,mBAKO,QALP,YAKO,gBADF,aAAA,MAAY,EAAA,EAAA,CAAA,CAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteInput.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, useTemplateRef } from 'vue'\nimport { useFocusWithin } from '@vueuse/core'\nimport { AutocompleteInput, AutocompleteTrigger, AutocompleteCancel, AutocompleteAnchor } from 'reka-ui'\nimport { useAutocompleteInject } from './Autocomplete.context'\nimport Spinner from '../spinner/Spinner.vue'
|
|
1
|
+
{"version":3,"file":"AutocompleteInput.js","names":[],"sources":["../../../src/components/autocomplete/AutocompleteInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, useTemplateRef } from 'vue'\nimport { useFocusWithin } from '@vueuse/core'\nimport { AutocompleteInput, AutocompleteTrigger, AutocompleteCancel, AutocompleteAnchor } from 'reka-ui'\nimport { useAutocompleteInject } from './Autocomplete.context'\nimport Chip from '../chip/Chip.vue'\nimport Spinner from '../spinner/Spinner.vue'\nimport AutocompleteOverflowChips from './AutocompleteOverflowChips.vue'\n\nconst props = withDefaults(defineProps<{\n placeholder?: string\n class?: string\n}>(), {\n placeholder: undefined,\n class: undefined,\n})\n\nconst ctx = useAutocompleteInject()\n\n// Track focused state via useFocusWithin (NOT manual @focus/@blur listeners) — the\n// inner Reka <AutocompleteInput> wraps the native <input>; useFocusWithin observes\n// the actual DOM subtree of the anchor, so isFocused flips reliably regardless of\n// how Reka forwards refs/attrs.\nconst anchorRef = useTemplateRef<HTMLElement>('anchor')\nconst { focused: isFocused } = useFocusWithin(anchorRef)\n\n// Render the inside-label only when (a) a label is set and (b) labelPlacement is 'inside'\nconst showInsideLabel = computed(\n () => ctx.hasLabel.value && ctx.labelPlacement.value === 'inside',\n)\n\n// In multiple mode, hide placeholder once chips are present\nconst effectivePlaceholder = computed(() =>\n ctx.multiple.value && ctx.selectedValues.value.length > 0 ? undefined : props.placeholder,\n)\n\nconst getLabel = (v: string) => ctx.selectedLabels.value.find(l => l.value === v)?.label ?? v\n</script>\n\n<template>\n <AutocompleteAnchor\n ref=\"anchor\"\n :class=\"ctx.slots.value.trigger()\"\n :data-filled=\"ctx.hasLabel.value ? (ctx.isFilled.value || undefined) : undefined\"\n :data-focused=\"isFocused || undefined\"\n :data-invalid=\"ctx.isInvalid.value || undefined\"\n :data-disabled=\"ctx.isDisabled.value || undefined\"\n :data-readonly=\"ctx.isReadonly.value || undefined\"\n :data-multiple-overflow=\"ctx.multiple.value ? ctx.multipleOverflow.value : undefined\"\n data-slot=\"trigger\"\n >\n <label\n v-if=\"showInsideLabel\"\n :for=\"ctx.inputId.value\"\n :class=\"ctx.slots.value.label()\"\n >{{ ctx.label.value }}<span\n v-if=\"ctx.isRequired.value\"\n aria-hidden=\"true\"\n > *</span></label>\n <span\n v-if=\"$slots.startContent\"\n :class=\"ctx.slots.value.startContent()\"\n data-slot=\"start-content\"\n >\n <slot name=\"startContent\" />\n </span>\n <!-- Multiple/collapse: overflow chip area with truncation -->\n <AutocompleteOverflowChips\n v-if=\"ctx.multiple.value && ctx.multipleOverflow.value === 'collapse' && ctx.selectedValues.value.length > 0\"\n :values=\"ctx.selectedValues.value\"\n :get-label=\"getLabel\"\n :remove-value=\"ctx.removeValue\"\n />\n <!-- Multiple/wrap: chips that wrap onto new lines -->\n <template v-else-if=\"ctx.multiple.value && ctx.multipleOverflow.value === 'wrap'\">\n <Chip\n v-for=\"item in ctx.selectedLabels.value\"\n :key=\"item.value\"\n size=\"sm\"\n is-closable\n :close-aria-label=\"`Remove ${item.label}`\"\n data-slot=\"selected-chip\"\n @close.stop=\"ctx.removeValue(item.value)\"\n >\n {{ item.label }}\n </Chip>\n </template>\n <AutocompleteInput\n :id=\"ctx.inputId.value\"\n :placeholder=\"effectivePlaceholder\"\n :disabled=\"ctx.isDisabled.value\"\n :readonly=\"ctx.isReadonly.value\"\n :required=\"ctx.isRequired.value\"\n :aria-invalid=\"ctx.isInvalid.value || undefined\"\n :aria-describedby=\"ctx.ariaDescribedBy.value\"\n :class=\"ctx.slots.value.input()\"\n :style=\"ctx.multiple.value && ctx.multipleOverflow.value === 'collapse' && ctx.selectedValues.value.length > 0\n ? { flex: '0 0 auto', minWidth: '80px', width: 'auto' }\n : undefined\"\n data-slot=\"input\"\n autocomplete=\"off\"\n />\n <!-- Clear button: only shown when filled and not in a readonly/disabled state.\n Reka's AutocompleteCancel does not set data-empty itself, so we drive it here.\n In multiple mode also clears selectedValues via ctx.clearAll(). -->\n <AutocompleteCancel\n :class=\"ctx.slots.value.clearButton()\"\n :data-empty=\"(!ctx.isFilled.value || ctx.isReadonly.value || ctx.isDisabled.value) ? 'true' : undefined\"\n data-slot=\"clear-button\"\n aria-label=\"Clear\"\n @click=\"ctx.multiple.value ? ctx.clearAll() : undefined\"\n >\n <slot name=\"clearIcon\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n data-slot=\"autocomplete-clear-button-icon\"\n aria-hidden=\"true\"\n >\n <line\n x1=\"18\"\n y1=\"6\"\n x2=\"6\"\n y2=\"18\"\n />\n <line\n x1=\"6\"\n y1=\"6\"\n x2=\"18\"\n y2=\"18\"\n />\n </svg>\n </slot>\n </AutocompleteCancel>\n <!-- Inline loading spinner: replaces the dropdown indicator while loadItems is pending -->\n <span\n v-if=\"ctx.isLoading.value\"\n :class=\"ctx.slots.value.indicator()\"\n data-slot=\"autocomplete-loading-indicator\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label=\"Loading suggestions\"\n >\n <Spinner size=\"sm\" />\n </span>\n <!-- Dropdown trigger indicator (hidden while loading) -->\n <AutocompleteTrigger\n v-else\n :class=\"ctx.slots.value.indicator()\"\n data-slot=\"autocomplete-default-indicator\"\n aria-label=\"Toggle suggestions\"\n >\n <slot name=\"triggerIcon\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </slot>\n </AutocompleteTrigger>\n </AutocompleteAnchor>\n</template>\n"],"mappings":""}
|