@sparklingopinions/sparkling-ui 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/aliases.d.ts +47 -10
  2. package/dist/components/Calendar/Calendar.js.map +1 -1
  3. package/dist/components/Calendar/Calendar.vue.d.ts +2 -2
  4. package/dist/components/Calendar/Calendar.vue_vue_type_script_setup_true_lang.js +6 -14
  5. package/dist/components/Calendar/Calendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  6. package/dist/components/Dialog/Dialog.js.map +1 -1
  7. package/dist/components/Dialog/Dialog.vue_vue_type_script_setup_true_lang.js +26 -29
  8. package/dist/components/Dialog/Dialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  9. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  10. package/dist/components/DropdownMenu/DropdownMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  11. package/dist/components/DropdownMenu/DropdownMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  12. package/dist/components/Filters/FilterChip.js.map +1 -1
  13. package/dist/components/Filters/FilterChip.vue_vue_type_script_setup_true_lang.js +2 -2
  14. package/dist/components/Filters/FilterChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  15. package/dist/components/Filters/FilterMenu.js.map +1 -1
  16. package/dist/components/Filters/FilterMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  17. package/dist/components/Filters/FilterMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  18. package/dist/components/Filters/SortMenu.js.map +1 -1
  19. package/dist/components/Filters/SortMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  20. package/dist/components/Filters/SortMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  21. package/dist/components/Input/Input.js.map +1 -1
  22. package/dist/components/Input/Input.vue_vue_type_script_setup_true_lang.js +1 -1
  23. package/dist/components/Input/Input.vue_vue_type_script_setup_true_lang.js.map +1 -1
  24. package/dist/components/Popover/Popover.js.map +1 -1
  25. package/dist/components/Popover/Popover.vue.d.ts +3 -0
  26. package/dist/components/Popover/Popover.vue_vue_type_script_setup_true_lang.js +38 -30
  27. package/dist/components/Popover/Popover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  28. package/dist/components/PropertyList/Editable.js.map +1 -1
  29. package/dist/components/PropertyList/Editable.vue_vue_type_script_setup_true_lang.js +6 -4
  30. package/dist/components/PropertyList/Editable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  31. package/dist/components/PropertyList/PropertyList.js.map +1 -1
  32. package/dist/components/PropertyList/PropertyList.vue.d.ts +17 -2
  33. package/dist/components/PropertyList/PropertyList.vue_vue_type_script_setup_true_lang.js +19 -14
  34. package/dist/components/PropertyList/PropertyList.vue_vue_type_script_setup_true_lang.js.map +1 -1
  35. package/dist/components/PropertyRow/Collapsible.js.map +1 -1
  36. package/dist/components/PropertyRow/Collapsible.vue.d.ts +1 -1
  37. package/dist/components/PropertyRow/Collapsible.vue_vue_type_script_setup_true_lang.js +12 -12
  38. package/dist/components/PropertyRow/Collapsible.vue_vue_type_script_setup_true_lang.js.map +1 -1
  39. package/dist/components/Radio/Radio.js.map +1 -1
  40. package/dist/components/Radio/Radio.vue_vue_type_script_setup_true_lang.js +1 -1
  41. package/dist/components/Radio/Radio.vue_vue_type_script_setup_true_lang.js.map +1 -1
  42. package/dist/components/Select/Select.js.map +1 -1
  43. package/dist/components/Select/Select.vue.d.ts +10 -1
  44. package/dist/components/Select/Select.vue_vue_type_script_setup_true_lang.js +125 -101
  45. package/dist/components/Select/Select.vue_vue_type_script_setup_true_lang.js.map +1 -1
  46. package/dist/components/Textarea/Textarea.js.map +1 -1
  47. package/dist/components/Textarea/Textarea.vue_vue_type_script_setup_true_lang.js +1 -1
  48. package/dist/components/Textarea/Textarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/styles/tailwind.css +4 -2
@@ -1 +1 @@
1
- {"version":3,"file":"Select.js","names":["_sfc_main","Select_vue_vue_type_script_setup_true_lang_default","Select_default"],"sources":["../../../src/components/Select/Select.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends SelectValue\">\nimport { computed, nextTick, ref, useAttrs } from 'vue'\n\nimport {\n ListboxContent,\n ListboxFilter,\n ListboxItem,\n ListboxRoot,\n ListboxVirtualizer,\n PopoverAnchor,\n PopoverContent,\n PopoverPortal,\n PopoverRoot\n} from 'reka-ui'\n\nimport type { Hue } from '../../utils/hues'\nimport { useFieldControl } from '../FormField/context'\nimport Spinner from '../Spinner/Spinner.vue'\nimport Tag from '../Tag/Tag.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport type { SelectOption, SelectValue } from './types'\n\nexport interface SelectProps<T extends SelectValue> {\n options: SelectOption<T>[]\n multiple?: boolean\n /** A filter row at the top of the list; on by default for `multiple`, `canCreate` or more than 8 options. */\n searchable?: boolean\n /** Offers to create what was typed, through `createOption`. */\n canCreate?: boolean\n /** Makes the created option; by default its value is its label. */\n createOption?: (draft: { label: string; color?: Hue }) => SelectOption<T> | Promise<SelectOption<T>>\n /** Creates with a hue, shown as a tag. */\n createColored?: boolean\n loading?: boolean\n /** 36px for forms, 28px for table feet and toolbars. */\n size?: 'md' | 'sm'\n disabled?: boolean\n invalid?: boolean\n placeholder?: string\n searchPlaceholder?: string\n emptyLabel?: string\n createLabel?: string\n removeLabel?: string\n /** Where the list opens; it flips when there's no room. */\n side?: 'bottom' | 'top'\n align?: 'start' | 'end'\n /** The list's width, when it should differ from the trigger's, e.g. `20rem`. */\n panelWidth?: string\n}\n\n// Attributes (aria-label, class for its width) go to the trigger, the one element in the page's flow.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<SelectProps<T>>(), {\n searchable: undefined,\n createOption: undefined,\n createColored: true,\n size: 'md',\n placeholder: 'Select an option',\n searchPlaceholder: 'Type to filter',\n emptyLabel: 'No results found',\n createLabel: 'Create',\n removeLabel: 'Remove',\n side: 'bottom',\n align: 'start',\n panelWidth: undefined\n})\n\nconst emit = defineEmits<{ 'query-change': [query: string] }>()\n\n// An aria-label would hide the trigger's visible value from its name; it's said before the value instead.\nconst attrs = useAttrs()\nconst label = computed(() => attrs['aria-label'] as string | undefined)\nconst triggerAttrs = computed(() => Object.fromEntries(Object.entries(attrs).filter(([key]) => key !== 'aria-label')))\n\nconst model = defineModel<T | T[] | null>({ default: null })\n\nconst open = ref(false)\nconst query = ref('')\nconst keyboard = ref(false)\n\nconst values = computed<T[]>(() =>\n Array.isArray(model.value) ? model.value : model.value === null || model.value === undefined ? [] : [model.value]\n)\nconst selected = computed(() =>\n values.value.map((value) => props.options.find((option) => option.value === value)).filter((option) => !!option)\n)\nconst search = computed(() => props.searchable ?? (props.multiple || props.canCreate || props.options.length > 8))\nconst listed = computed(() => {\n const text = query.value.trim().toLowerCase()\n return props.options\n .filter((option) => !(props.multiple && search.value && values.value.includes(option.value)))\n .filter((option) => !text || option.label.toLowerCase().includes(text))\n})\nconst showCreate = computed(() => {\n const text = query.value.trim().toLowerCase()\n return props.canCreate && !!text && !props.options.some((option) => option.label.toLowerCase() === text)\n})\n// More than this and the list renders only the rows in view.\nconst virtual = computed(() => listed.value.length > 20)\n\nconst createHues: Hue[] = ['gray', 'purple', 'red', 'green', 'orange', 'pink', 'blue', 'slate']\nconst newHue = computed(() => createHues[query.value.length % createHues.length])\nconst CREATE = { create: true } as const\n\nconst { attrs: fieldAttrs, invalid: isInvalid } = useFieldControl(\n () => undefined,\n () => props.invalid\n)\n\nfunction setQuery(text: string) {\n query.value = text\n emit('query-change', text)\n}\n\nfunction show(viaKeyboard: boolean) {\n if (props.disabled) return\n keyboard.value = viaKeyboard\n open.value = true\n}\n\nfunction close() {\n open.value = false\n if (query.value) setQuery('')\n}\n\nfunction toggle(event?: Event) {\n if (open.value) close()\n // A click from the keyboard (Enter, Space) has no pointer position.\n else show(!!event && (event as MouseEvent).detail === 0)\n}\n\nfunction choose(option: SelectOption<T>) {\n if (props.multiple) {\n model.value = values.value.includes(option.value)\n ? values.value.filter((value) => value !== option.value)\n : [...values.value, option.value]\n setQuery('')\n } else {\n model.value = option.value\n close()\n }\n}\n\nasync function create() {\n const label = query.value.trim()\n const draft = props.createColored ? { label, color: newHue.value } : { label }\n const option = props.createOption\n ? await props.createOption(draft)\n : ({ value: label as T, label, color: draft.color } as SelectOption<T>)\n choose(option)\n}\n\nfunction onSelect(event: CustomEvent, row: SelectOption<T> | typeof CREATE) {\n // What's chosen lives in the model; the listbox keeps no selection of its own.\n event.preventDefault()\n if ('create' in row) create()\n else if (!row.disabled) choose(row)\n}\n\nfunction remove(value: T) {\n model.value = values.value.filter((chosen) => chosen !== value)\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) keyboard.value = true\n if (event.key === 'Backspace' && props.multiple && !query.value && values.value.length)\n remove(values.value[values.value.length - 1]!)\n}\n\nfunction onTriggerKeydown(event: KeyboardEvent) {\n if (!open.value && ['ArrowDown', 'ArrowUp'].includes(event.key)) {\n event.preventDefault()\n show(true)\n }\n}\n\n// The filter takes focus when there is one, or else the list, so the arrow keys work at once.\nfunction onOpenAutoFocus(event: Event) {\n event.preventDefault()\n nextTick(() =>\n (event.target as HTMLElement | null)?.querySelector<HTMLElement>('[data-select-filter], [role=\"listbox\"]')?.focus()\n )\n}\n\n// Back to the trigger, which Reka can't find: it's our anchor, not its trigger.\nconst anchor = ref<{ $el: HTMLElement }>()\nfunction returnFocus(event: Event) {\n event.preventDefault()\n anchor.value?.$el?.focus()\n}\n\n// Reka highlights the first option as the list mounts; only the chosen one, or the keyboard, should be.\nconst listbox = ref()\nfunction onHighlight(payload: { value: unknown } | undefined) {\n if (keyboard.value || !payload) return\n if (values.value.includes(payload.value as T)) return\n nextTick(() => listbox.value && !keyboard.value && (listbox.value.highlightedElement = null))\n}\n\nconst triggerSizes = { md: 'min-h-9 pr-2.5 pl-3 text-sm', sm: 'h-7 pr-1.5 pl-2 text-13' }\nconst optionClass =\n 'flex min-h-8 cursor-pointer items-center justify-between gap-3 rounded-lg py-1.5 pr-2 pl-2.5 text-13 whitespace-nowrap text-sui-ink outline-hidden select-none data-disabled:cursor-default data-disabled:opacity-50 data-highlighted:bg-sui-sunken data-[state=checked]:font-medium data-[state=checked]:text-sui-accent group-data-keyboard/list:data-highlighted:sui-ring-inset'\n</script>\n\n<!-- One select for every choice: a short fixed list, a searchable one, several values as tags (`multiple`), or creating\n what was typed (`canCreate`). The `trigger` slot draws your own trigger and gets `open`, `toggle` and `selected`. -->\n<template>\n <PopoverRoot :open=\"open\" @update:open=\"(value) => (value ? show(false) : close())\">\n <PopoverAnchor ref=\"anchor\" as-child>\n <slot name=\"trigger\" :open=\"open\" :selected=\"selected\" :toggle=\"toggle\">\n <button\n v-bind=\"{ ...fieldAttrs, ...triggerAttrs }\"\n :aria-expanded=\"open\"\n aria-haspopup=\"listbox\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n class=\"group inline-flex items-center justify-between gap-1.5 rounded-(--sui-radius-button) border bg-sui-surface text-left text-sui-ink transition-colors duration-150 focus-visible:sui-ring-field disabled:pointer-events-none disabled:opacity-60\"\n :class=\"[\n triggerSizes[size],\n isInvalid ? 'border-sui-danger-mid' : 'border-sui-line-control hover:border-sui-ink-muted'\n ]\"\n :disabled=\"disabled\"\n type=\"button\"\n @click=\"toggle\"\n @keydown=\"onTriggerKeydown\"\n >\n <span v-if=\"label\" class=\"sr-only\">{{ label }}: </span>\n <span v-if=\"!selected.length\" class=\"text-sui-ink-faint\">{{ placeholder }}</span>\n <span v-else-if=\"multiple\" class=\"flex flex-wrap gap-1 py-1.25\">\n <Tag\n v-for=\"option in selected\"\n :key=\"option.value\"\n :color=\"option.color ?? undefined\"\n :name=\"option.label\"\n />\n </span>\n <Tag v-else-if=\"selected[0]!.color\" :color=\"selected[0]!.color\" :name=\"selected[0]!.label\" />\n <span v-else class=\"min-w-0 truncate whitespace-nowrap\">{{ selected[0]!.label }}</span>\n <svg\n aria-hidden=\"true\"\n class=\"size-4 shrink-0 fill-none stroke-sui-ink-muted transition-transform duration-150 group-aria-expanded:rotate-180\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M4.5 6.5 8 10l3.5-3.5\" />\n </svg>\n </button>\n </slot>\n </PopoverAnchor>\n <PopoverPortal>\n <PopoverContent\n :align=\"align\"\n :aria-label=\"label ?? placeholder\"\n class=\"z-[1000] overflow-hidden rounded-xl border border-sui-line-subtle bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-menu-in\"\n :class=\"\n search ? 'min-w-[max(var(--reka-popover-trigger-width),15rem)]' : 'min-w-(--reka-popover-trigger-width)'\n \"\n :side=\"side\"\n :side-offset=\"4\"\n :style=\"panelWidth ? { width: panelWidth } : undefined\"\n @close-auto-focus=\"returnFocus\"\n @open-auto-focus=\"onOpenAutoFocus\"\n >\n <ListboxRoot\n ref=\"listbox\"\n :highlight-on-hover=\"true\"\n :model-value=\"multiple ? values : (values[0] ?? undefined)\"\n :multiple=\"multiple\"\n @highlight=\"onHighlight\"\n @keydown=\"onKeydown\"\n @pointermove=\"keyboard = false\"\n >\n <div\n v-if=\"search\"\n class=\"flex min-h-11 flex-wrap items-center gap-1 border-b border-sui-line-subtle px-3 py-2.5\"\n >\n <template v-if=\"multiple\">\n <Tag\n v-for=\"option in selected\"\n :key=\"option.value\"\n :color=\"option.color ?? undefined\"\n :name=\"option.label\"\n removable\n :remove-label=\"removeLabel\"\n @remove=\"remove(option.value)\"\n />\n </template>\n <!-- focus: classes too: an app's forms plugin rings focused text fields, and outranks plain utilities. -->\n <ListboxFilter\n :aria-label=\"label ?? placeholder\"\n class=\"h-5 min-w-16 flex-1 rounded-none border-none bg-transparent p-0 text-13 text-sui-ink shadow-none outline-hidden placeholder:text-sui-ink-faint focus:border-none focus:shadow-none focus:ring-0 focus:outline-hidden\"\n data-select-filter\n :model-value=\"query\"\n :placeholder=\"multiple && selected.length ? '' : searchPlaceholder\"\n @update:model-value=\"setQuery\"\n />\n </div>\n <div v-if=\"loading\" class=\"flex justify-center py-6\"><Spinner size=\"sm\" /></div>\n <ListboxContent\n v-else-if=\"listed.length || showCreate\"\n :aria-label=\"label ?? placeholder\"\n class=\"group/list max-h-60 overflow-auto p-1 outline-hidden\"\n :data-keyboard=\"keyboard || undefined\"\n >\n <ListboxVirtualizer\n v-if=\"virtual\"\n v-slot=\"{ option }\"\n :estimate-size=\"32\"\n :options=\"listed\"\n :text-content=\"(option) => option.label\"\n >\n <ListboxItem\n :class=\"optionClass\"\n :disabled=\"option.disabled\"\n :value=\"option.value\"\n @select=\"onSelect($event, option as SelectOption<T>)\"\n >\n <span class=\"inline-flex min-w-0 items-center gap-1.5 truncate\">\n <slot name=\"option\" :option=\"option as SelectOption<T>\">\n <Tag v-if=\"option.color\" :color=\"option.color\" :name=\"option.label\" />\n <template v-else>{{ option.label }}</template>\n </slot>\n </span>\n <CheckIcon v-if=\"values.includes(option.value)\" />\n </ListboxItem>\n </ListboxVirtualizer>\n <template v-else>\n <ListboxItem\n v-for=\"option in listed\"\n :key=\"option.value\"\n :class=\"optionClass\"\n :disabled=\"option.disabled\"\n :value=\"option.value\"\n @select=\"onSelect($event, option)\"\n >\n <span class=\"inline-flex min-w-0 items-center gap-1.5 truncate\">\n <slot name=\"option\" :option=\"option\">\n <Tag v-if=\"option.color\" :color=\"option.color\" :name=\"option.label\" />\n <template v-else>{{ option.label }}</template>\n </slot>\n </span>\n <CheckIcon v-if=\"values.includes(option.value)\" />\n </ListboxItem>\n </template>\n <ListboxItem v-if=\"showCreate\" :class=\"optionClass\" :value=\"CREATE\" @select=\"onSelect($event, CREATE)\">\n <span class=\"inline-flex min-w-0 items-center gap-1.5\">\n {{ createLabel }}\n <Tag v-if=\"createColored\" :color=\"newHue\" :name=\"query.trim()\" />\n <strong v-else class=\"font-medium\">“{{ query.trim() }}”</strong>\n </span>\n </ListboxItem>\n </ListboxContent>\n <div v-else class=\"px-3.5 py-3 text-13 text-sui-ink-muted\">{{ emptyLabel }}</div>\n </ListboxRoot>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"Select.js","names":["_sfc_main","Select_vue_vue_type_script_setup_true_lang_default","Select_default"],"sources":["../../../src/components/Select/Select.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends SelectValue\">\nimport { computed, nextTick, ref, shallowRef, useAttrs } from 'vue'\n\nimport {\n ListboxContent,\n ListboxFilter,\n ListboxItem,\n ListboxRoot,\n ListboxVirtualizer,\n PopoverAnchor,\n PopoverContent,\n PopoverPortal,\n PopoverRoot\n} from 'reka-ui'\n\nimport type { Hue } from '../../utils/hues'\nimport { useFieldControl } from '../FormField/context'\nimport Spinner from '../Spinner/Spinner.vue'\nimport Tag from '../Tag/Tag.vue'\nimport CheckIcon from '../internal/CheckIcon.vue'\nimport type { SelectOption, SelectValue } from './types'\n\nexport interface SelectProps<T extends SelectValue> {\n options: SelectOption<T>[]\n /** The options for chosen values that aren't in `options`, such as a saved choice while `options` holds search\n * results. Options picked from the list are remembered without it. */\n selectedOptions?: SelectOption<T>[]\n multiple?: boolean\n /** Lets a single choice be removed, as a tag in the filter row or with Backspace there. */\n clearable?: boolean\n /** A filter row at the top of the list; on by default for `multiple`, `canCreate` or more than 8 options. */\n searchable?: boolean\n /** Narrows `options` to what's typed: by label, or by your function. `false` when `options` are already the results\n * for the query, such as from a server search through `query-change`. */\n filter?: boolean | ((option: SelectOption<T>, query: string) => boolean)\n /** Offers to create what was typed, through `createOption`. */\n canCreate?: boolean\n /** Makes the created option; by default its value is its label. */\n createOption?: (draft: { label: string; color?: Hue }) => SelectOption<T> | Promise<SelectOption<T>>\n /** Creates with a hue, shown as a tag. */\n createColored?: boolean\n loading?: boolean\n /** 36px for forms, 28px for table feet and toolbars. */\n size?: 'md' | 'sm'\n disabled?: boolean\n invalid?: boolean\n placeholder?: string\n searchPlaceholder?: string\n emptyLabel?: string\n createLabel?: string\n removeLabel?: string\n /** Where the list opens; it flips when there's no room. */\n side?: 'bottom' | 'top'\n align?: 'start' | 'end'\n /** The list's width, when it should differ from the trigger's, e.g. `20rem`. */\n panelWidth?: string\n}\n\n// Attributes (aria-label, class for its width) go to the trigger, the one element in the page's flow.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<SelectProps<T>>(), {\n selectedOptions: () => [],\n searchable: undefined,\n filter: true,\n createOption: undefined,\n createColored: true,\n size: 'md',\n placeholder: 'Select an option',\n searchPlaceholder: 'Type to filter',\n emptyLabel: 'No results found',\n createLabel: 'Create',\n removeLabel: 'Remove',\n side: 'bottom',\n align: 'start',\n panelWidth: undefined\n})\n\nconst emit = defineEmits<{ 'query-change': [query: string]; 'create-error': [error: unknown] }>()\n\n// An aria-label would hide the trigger's visible value from its name; it's said before the value instead.\nconst attrs = useAttrs()\nconst label = computed(() => attrs['aria-label'] as string | undefined)\nconst triggerAttrs = computed(() => Object.fromEntries(Object.entries(attrs).filter(([key]) => key !== 'aria-label')))\n\nconst model = defineModel<T | T[] | null>({ default: null })\n\nconst open = ref(false)\nconst query = ref('')\nconst keyboard = ref(false)\n\nconst values = computed<T[]>(() =>\n Array.isArray(model.value) ? model.value : model.value === null || model.value === undefined ? [] : [model.value]\n)\n// Options picked from earlier results, which `options` may no longer hold.\nconst picked = shallowRef<SelectOption<T>[]>([])\nconst selected = computed(() =>\n values.value\n .map((value) =>\n [props.options, props.selectedOptions, picked.value]\n .map((list) => list.find((option) => option.value === value))\n .find((option) => !!option)\n )\n .filter((option) => !!option)\n)\nconst search = computed(\n () =>\n props.searchable ??\n (props.multiple || props.canCreate || props.clearable || props.filter === false || props.options.length > 8)\n)\nconst removable = computed(() => props.multiple || props.clearable)\nconst matches = (option: SelectOption<T>, text: string) =>\n typeof props.filter === 'function'\n ? props.filter(option, text)\n : option.label.toLowerCase().includes(text.toLowerCase())\nconst listed = computed(() => {\n const text = query.value.trim()\n return props.options\n .filter((option) => !(props.multiple && search.value && values.value.includes(option.value)))\n .filter((option) => !text || props.filter === false || matches(option, text))\n})\nconst showCreate = computed(() => {\n const text = query.value.trim().toLowerCase()\n return props.canCreate && !!text && !props.options.some((option) => option.label.toLowerCase() === text)\n})\n// More than this and the list renders only the rows in view.\nconst virtual = computed(() => listed.value.length > 20)\n\nconst createHues: Hue[] = ['gray', 'purple', 'red', 'green', 'orange', 'pink', 'blue', 'slate']\nconst newHue = computed(() => createHues[query.value.length % createHues.length])\nconst CREATE = { create: true } as const\n\nconst { attrs: fieldAttrs, invalid: isInvalid } = useFieldControl(\n () => undefined,\n () => props.invalid\n)\n\nfunction setQuery(text: string) {\n query.value = text\n emit('query-change', text)\n}\n\nfunction show(viaKeyboard: boolean) {\n if (props.disabled) return\n keyboard.value = viaKeyboard\n open.value = true\n}\n\nfunction close() {\n open.value = false\n if (query.value) setQuery('')\n}\n\nfunction toggle(event?: Event) {\n if (open.value) close()\n // A click from the keyboard (Enter, Space) has no pointer position.\n else show(!!event && (event as MouseEvent).detail === 0)\n}\n\nfunction choose(option: SelectOption<T>) {\n picked.value = [option, ...picked.value.filter((known) => known.value !== option.value)]\n if (props.multiple) {\n model.value = values.value.includes(option.value)\n ? values.value.filter((value) => value !== option.value)\n : [...values.value, option.value]\n setQuery('')\n } else {\n model.value = option.value\n close()\n }\n}\n\nconst creating = ref(false)\n\n// A failed createOption keeps what was typed, so it can be tried again.\nasync function create() {\n if (creating.value) return\n const label = query.value.trim()\n const draft = props.createColored ? { label, color: newHue.value } : { label }\n creating.value = true\n try {\n const option = props.createOption\n ? await props.createOption(draft)\n : ({ value: label as T, label, color: draft.color } as SelectOption<T>)\n choose(option)\n } catch (error) {\n emit('create-error', error)\n } finally {\n creating.value = false\n }\n}\n\nfunction onSelect(event: CustomEvent, row: SelectOption<T> | typeof CREATE) {\n // What's chosen lives in the model; the listbox keeps no selection of its own.\n event.preventDefault()\n if ('create' in row) create()\n else if (!row.disabled) choose(row)\n}\n\nfunction remove(value: T) {\n model.value = props.multiple ? values.value.filter((chosen) => chosen !== value) : null\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) keyboard.value = true\n if (event.key === 'Backspace' && removable.value && !query.value && values.value.length)\n remove(values.value[values.value.length - 1]!)\n}\n\nfunction onTriggerKeydown(event: KeyboardEvent) {\n if (!open.value && ['ArrowDown', 'ArrowUp'].includes(event.key)) {\n event.preventDefault()\n show(true)\n }\n}\n\n// The filter takes focus when there is one, or else the list, so the arrow keys work at once.\nfunction onOpenAutoFocus(event: Event) {\n event.preventDefault()\n nextTick(() =>\n (event.target as HTMLElement | null)?.querySelector<HTMLElement>('[data-select-filter], [role=\"listbox\"]')?.focus()\n )\n}\n\n// Back to the trigger, which Reka can't find: it's our anchor, not its trigger.\nconst anchor = ref<{ $el: HTMLElement }>()\n\n// Nor does Reka know a press on it isn't outside: closing there would let the click reopen the list.\nfunction onPointerDownOutside(event: CustomEvent<{ originalEvent: PointerEvent }>) {\n const target = event.detail.originalEvent.target\n if (target instanceof Node && anchor.value?.$el?.contains(target)) event.preventDefault()\n}\nfunction returnFocus(event: Event) {\n event.preventDefault()\n anchor.value?.$el?.focus()\n}\n\n// Reka highlights the first option as the list mounts; only the chosen one, or the keyboard, should be.\nconst listbox = ref()\nfunction onHighlight(payload: { value: unknown } | undefined) {\n if (keyboard.value || !payload) return\n if (values.value.includes(payload.value as T)) return\n nextTick(() => listbox.value && !keyboard.value && (listbox.value.highlightedElement = null))\n}\n\nconst triggerSizes = { md: 'min-h-9 pr-2.5 pl-3 text-sm', sm: 'h-7 pr-1.5 pl-2 text-13' }\nconst optionClass =\n 'flex min-h-8 cursor-pointer items-center justify-between gap-3 rounded-lg py-1.5 pr-2 pl-2.5 text-13 whitespace-nowrap text-sui-ink outline-hidden select-none data-disabled:cursor-default data-disabled:opacity-50 data-highlighted:bg-sui-sunken data-[state=checked]:font-medium data-[state=checked]:text-sui-accent group-data-keyboard/list:data-highlighted:sui-ring-inset'\n</script>\n\n<!-- One select for every choice: a short fixed list, a searchable one, several values as tags (`multiple`), or creating\n what was typed (`canCreate`). For a server search, set `:filter=\"false\"` and replace `options` on `query-change`. The `trigger` slot draws your own trigger and gets `open`, `toggle` and `selected`. -->\n<template>\n <PopoverRoot :open=\"open\" @update:open=\"(value) => (value ? show(false) : close())\">\n <PopoverAnchor ref=\"anchor\" as-child>\n <slot name=\"trigger\" :open=\"open\" :selected=\"selected\" :toggle=\"toggle\">\n <button\n v-bind=\"{ ...fieldAttrs, ...triggerAttrs }\"\n :aria-expanded=\"open\"\n aria-haspopup=\"listbox\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n class=\"group inline-flex items-center justify-between gap-1.5 rounded-(--sui-radius-button) border bg-sui-surface text-left text-sui-ink transition-[border-color,box-shadow] duration-150 focus-visible:border-sui-accent focus-visible:sui-ring-field disabled:pointer-events-none disabled:opacity-60\"\n :class=\"[\n triggerSizes[size],\n isInvalid ? 'border-sui-danger-mid' : 'border-sui-line-strong hover:border-sui-ink-muted'\n ]\"\n :disabled=\"disabled\"\n type=\"button\"\n @click=\"toggle\"\n @keydown=\"onTriggerKeydown\"\n >\n <span v-if=\"label\" class=\"sr-only\">{{ label }}: </span>\n <span v-if=\"!selected.length\" class=\"text-sui-ink-faint\">{{ placeholder }}</span>\n <span v-else-if=\"multiple\" class=\"flex flex-wrap gap-1 py-1.25\">\n <Tag\n v-for=\"option in selected\"\n :key=\"option.value\"\n :color=\"option.color ?? undefined\"\n :name=\"option.label\"\n />\n </span>\n <Tag v-else-if=\"selected[0]!.color\" :color=\"selected[0]!.color\" :name=\"selected[0]!.label\" />\n <span v-else class=\"min-w-0 truncate whitespace-nowrap\">{{ selected[0]!.label }}</span>\n <svg\n aria-hidden=\"true\"\n class=\"size-4 shrink-0 fill-none stroke-sui-ink-muted transition-transform duration-150 group-aria-expanded:rotate-180\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.5\"\n viewBox=\"0 0 16 16\"\n >\n <path d=\"M4.5 6.5 8 10l3.5-3.5\" />\n </svg>\n </button>\n </slot>\n </PopoverAnchor>\n <PopoverPortal>\n <PopoverContent\n :align=\"align\"\n :aria-label=\"label ?? placeholder\"\n class=\"z-[1100] overflow-hidden rounded-xl border border-sui-line-subtle bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-menu-in\"\n :class=\"\n search ? 'min-w-[max(var(--reka-popover-trigger-width),15rem)]' : 'min-w-(--reka-popover-trigger-width)'\n \"\n :side=\"side\"\n :side-offset=\"4\"\n :style=\"panelWidth ? { width: panelWidth } : undefined\"\n @close-auto-focus=\"returnFocus\"\n @open-auto-focus=\"onOpenAutoFocus\"\n @pointer-down-outside=\"onPointerDownOutside\"\n >\n <ListboxRoot\n ref=\"listbox\"\n :highlight-on-hover=\"true\"\n :model-value=\"multiple ? values : (values[0] ?? undefined)\"\n :multiple=\"multiple\"\n @highlight=\"onHighlight\"\n @keydown=\"onKeydown\"\n @pointermove=\"keyboard = false\"\n >\n <div\n v-if=\"search\"\n class=\"flex min-h-11 flex-wrap items-center gap-1 border-b border-sui-line-subtle px-3 py-2.5\"\n >\n <template v-if=\"removable\">\n <Tag\n v-for=\"option in selected\"\n :key=\"option.value\"\n :color=\"option.color ?? undefined\"\n :name=\"option.label\"\n removable\n :remove-label=\"removeLabel\"\n @remove=\"remove(option.value)\"\n />\n </template>\n <!-- focus: classes too: an app's forms plugin rings focused text fields, and outranks plain utilities. -->\n <ListboxFilter\n :aria-label=\"label ?? placeholder\"\n class=\"h-5 min-w-16 flex-1 rounded-none border-none bg-transparent p-0 text-13 text-sui-ink shadow-none outline-hidden placeholder:text-sui-ink-faint focus:border-none focus:shadow-none focus:ring-0 focus:outline-hidden\"\n data-select-filter\n :model-value=\"query\"\n :placeholder=\"removable && selected.length ? '' : searchPlaceholder\"\n @update:model-value=\"setQuery\"\n />\n </div>\n <div v-if=\"loading\" class=\"flex justify-center py-6\"><Spinner size=\"sm\" /></div>\n <ListboxContent\n v-else-if=\"listed.length || showCreate\"\n :aria-label=\"label ?? placeholder\"\n class=\"group/list max-h-60 overflow-auto p-1 outline-hidden\"\n :data-keyboard=\"keyboard || undefined\"\n >\n <ListboxVirtualizer\n v-if=\"virtual\"\n v-slot=\"{ option }\"\n :estimate-size=\"32\"\n :options=\"listed\"\n :text-content=\"(option) => option.label\"\n >\n <ListboxItem\n :class=\"optionClass\"\n :disabled=\"option.disabled\"\n :value=\"option.value\"\n @select=\"onSelect($event, option as SelectOption<T>)\"\n >\n <span class=\"inline-flex min-w-0 items-center gap-1.5 truncate\">\n <slot name=\"option\" :option=\"option as SelectOption<T>\">\n <Tag v-if=\"option.color\" :color=\"option.color\" :name=\"option.label\" />\n <template v-else>{{ option.label }}</template>\n </slot>\n </span>\n <CheckIcon v-if=\"values.includes(option.value)\" />\n </ListboxItem>\n </ListboxVirtualizer>\n <template v-else>\n <ListboxItem\n v-for=\"option in listed\"\n :key=\"option.value\"\n :class=\"optionClass\"\n :disabled=\"option.disabled\"\n :value=\"option.value\"\n @select=\"onSelect($event, option)\"\n >\n <span class=\"inline-flex min-w-0 items-center gap-1.5 truncate\">\n <slot name=\"option\" :option=\"option\">\n <Tag v-if=\"option.color\" :color=\"option.color\" :name=\"option.label\" />\n <template v-else>{{ option.label }}</template>\n </slot>\n </span>\n <CheckIcon v-if=\"values.includes(option.value)\" />\n </ListboxItem>\n </template>\n <ListboxItem v-if=\"showCreate\" :class=\"optionClass\" :value=\"CREATE\" @select=\"onSelect($event, CREATE)\">\n <span class=\"inline-flex min-w-0 items-center gap-1.5\">\n {{ createLabel }}\n <Tag v-if=\"createColored\" :color=\"newHue\" :name=\"query.trim()\" />\n <strong v-else class=\"font-medium\">“{{ query.trim() }}”</strong>\n </span>\n </ListboxItem>\n </ListboxContent>\n <div v-else class=\"px-3.5 py-3 text-13 text-sui-ink-muted\">{{ emptyLabel }}</div>\n </ListboxRoot>\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n"],"mappings":""}
@@ -2,9 +2,17 @@ import type { Hue } from '../../utils/hues';
2
2
  import type { SelectOption, SelectValue } from './types';
3
3
  export interface SelectProps<T extends SelectValue> {
4
4
  options: SelectOption<T>[];
5
+ /** The options for chosen values that aren't in `options`, such as a saved choice while `options` holds search
6
+ * results. Options picked from the list are remembered without it. */
7
+ selectedOptions?: SelectOption<T>[];
5
8
  multiple?: boolean;
9
+ /** Lets a single choice be removed, as a tag in the filter row or with Backspace there. */
10
+ clearable?: boolean;
6
11
  /** A filter row at the top of the list; on by default for `multiple`, `canCreate` or more than 8 options. */
7
12
  searchable?: boolean;
13
+ /** Narrows `options` to what's typed: by label, or by your function. `false` when `options` are already the results
14
+ * for the query, such as from a server search through `query-change`. */
15
+ filter?: boolean | ((option: SelectOption<T>, query: string) => boolean);
8
16
  /** Offers to create what was typed, through `createOption`. */
9
17
  canCreate?: boolean;
10
18
  /** Makes the created option; by default its value is its label. */
@@ -36,6 +44,7 @@ declare const __VLS_export: <T extends SelectValue>(__VLS_props: NonNullable<Awa
36
44
  }) & {
37
45
  "onUpdate:modelValue"?: ((value: T | T[] | null) => any) | undefined;
38
46
  "onQuery-change"?: ((query: string) => any) | undefined;
47
+ "onCreate-error"?: ((error: unknown) => any) | undefined;
39
48
  }> & (typeof globalThis extends {
40
49
  __VLS_PROPS_FALLBACK: infer P;
41
50
  } ? P : {});
@@ -56,7 +65,7 @@ declare const __VLS_export: <T extends SelectValue>(__VLS_props: NonNullable<Awa
56
65
  option: SelectOption<T>;
57
66
  }) => any;
58
67
  };
59
- emit: ((evt: "query-change", query: string) => void) & ((event: "update:modelValue", value: T | T[] | null) => void);
68
+ emit: (((evt: "query-change", query: string) => void) & ((evt: "create-error", error: unknown) => void)) & ((event: "update:modelValue", value: T | T[] | null) => void);
60
69
  }>) => import("vue").VNode & {
61
70
  __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
62
71
  };
@@ -2,47 +2,53 @@ import { useFieldControl as e } from "../FormField/context.js";
2
2
  import t from "../internal/CheckIcon.js";
3
3
  import n from "../Spinner/Spinner.js";
4
4
  import r from "../Tag/Tag.js";
5
- import { Fragment as i, computed as a, createBlock as o, createCommentVNode as s, createElementBlock as c, createElementVNode as l, createTextVNode as u, createVNode as d, defineComponent as f, mergeModels as p, mergeProps as ee, nextTick as m, normalizeClass as h, normalizeStyle as te, openBlock as g, ref as _, renderList as v, renderSlot as y, toDisplayString as b, unref as x, useAttrs as ne, useModel as re, withCtx as S } from "vue";
6
- import { ListboxContent as C, ListboxFilter as w, ListboxItem as T, ListboxRoot as ie, ListboxVirtualizer as ae, PopoverAnchor as oe, PopoverContent as se, PopoverPortal as ce, PopoverRoot as le } from "reka-ui";
5
+ import { Fragment as i, computed as a, createBlock as o, createCommentVNode as s, createElementBlock as c, createElementVNode as l, createTextVNode as u, createVNode as d, defineComponent as f, mergeModels as p, mergeProps as ee, nextTick as m, normalizeClass as h, normalizeStyle as te, openBlock as g, ref as _, renderList as v, renderSlot as y, shallowRef as ne, toDisplayString as b, unref as x, useAttrs as re, useModel as ie, withCtx as S } from "vue";
6
+ import { ListboxContent as ae, ListboxFilter as oe, ListboxItem as C, ListboxRoot as se, ListboxVirtualizer as ce, PopoverAnchor as le, PopoverContent as ue, PopoverPortal as de, PopoverRoot as fe } from "reka-ui";
7
7
  //#region src/components/Select/Select.vue?vue&type=script&setup=true&lang.ts
8
- var ue = [
8
+ var pe = [
9
9
  "aria-expanded",
10
10
  "aria-invalid",
11
11
  "disabled"
12
- ], de = {
12
+ ], me = {
13
13
  key: 0,
14
14
  class: "sr-only"
15
- }, fe = {
15
+ }, he = {
16
16
  key: 1,
17
17
  class: "text-sui-ink-faint"
18
- }, pe = {
18
+ }, ge = {
19
19
  key: 2,
20
20
  class: "flex flex-wrap gap-1 py-1.25"
21
- }, me = {
21
+ }, _e = {
22
22
  key: 4,
23
23
  class: "min-w-0 truncate whitespace-nowrap"
24
- }, he = {
24
+ }, ve = {
25
25
  key: 0,
26
26
  class: "flex min-h-11 flex-wrap items-center gap-1 border-b border-sui-line-subtle px-3 py-2.5"
27
- }, ge = {
27
+ }, ye = {
28
28
  key: 1,
29
29
  class: "flex justify-center py-6"
30
- }, _e = { class: "inline-flex min-w-0 items-center gap-1.5 truncate" }, ve = { class: "inline-flex min-w-0 items-center gap-1.5 truncate" }, ye = { class: "inline-flex min-w-0 items-center gap-1.5" }, be = {
30
+ }, be = { class: "inline-flex min-w-0 items-center gap-1.5 truncate" }, xe = { class: "inline-flex min-w-0 items-center gap-1.5 truncate" }, Se = { class: "inline-flex min-w-0 items-center gap-1.5" }, Ce = {
31
31
  key: 1,
32
32
  class: "font-medium"
33
- }, xe = {
33
+ }, we = {
34
34
  key: 3,
35
35
  class: "px-3.5 py-3 text-13 text-sui-ink-muted"
36
- }, E = "flex min-h-8 cursor-pointer items-center justify-between gap-3 rounded-lg py-1.5 pr-2 pl-2.5 text-13 whitespace-nowrap text-sui-ink outline-hidden select-none data-disabled:cursor-default data-disabled:opacity-50 data-highlighted:bg-sui-sunken data-[state=checked]:font-medium data-[state=checked]:text-sui-accent group-data-keyboard/list:data-highlighted:sui-ring-inset", D = /*@__PURE__*/ f({
36
+ }, w = "flex min-h-8 cursor-pointer items-center justify-between gap-3 rounded-lg py-1.5 pr-2 pl-2.5 text-13 whitespace-nowrap text-sui-ink outline-hidden select-none data-disabled:cursor-default data-disabled:opacity-50 data-highlighted:bg-sui-sunken data-[state=checked]:font-medium data-[state=checked]:text-sui-accent group-data-keyboard/list:data-highlighted:sui-ring-inset", T = /*@__PURE__*/ f({
37
37
  inheritAttrs: !1,
38
38
  __name: "Select",
39
39
  props: /*@__PURE__*/ p({
40
40
  options: {},
41
+ selectedOptions: { default: () => [] },
41
42
  multiple: { type: Boolean },
43
+ clearable: { type: Boolean },
42
44
  searchable: {
43
45
  type: Boolean,
44
46
  default: void 0
45
47
  },
48
+ filter: {
49
+ type: [Boolean, Function],
50
+ default: !0
51
+ },
46
52
  canCreate: { type: Boolean },
47
53
  createOption: {
48
54
  type: Function,
@@ -68,15 +74,19 @@ var ue = [
68
74
  modelValue: { default: null },
69
75
  modelModifiers: {}
70
76
  }),
71
- emits: /*@__PURE__*/ p(["query-change"], ["update:modelValue"]),
77
+ emits: /*@__PURE__*/ p(["query-change", "create-error"], ["update:modelValue"]),
72
78
  setup(f, { emit: p }) {
73
- let D = f, Se = p, O = ne(), k = a(() => O["aria-label"]), A = a(() => Object.fromEntries(Object.entries(O).filter(([e]) => e !== "aria-label"))), j = re(f, "modelValue"), M = _(!1), N = _(""), P = _(!1), F = a(() => Array.isArray(j.value) ? j.value : j.value === null || j.value === void 0 ? [] : [j.value]), I = a(() => F.value.map((e) => D.options.find((t) => t.value === e)).filter((e) => !!e)), L = a(() => D.searchable ?? (D.multiple || D.canCreate || D.options.length > 8)), R = a(() => {
74
- let e = N.value.trim().toLowerCase();
75
- return D.options.filter((e) => !(D.multiple && L.value && F.value.includes(e.value))).filter((t) => !e || t.label.toLowerCase().includes(e));
79
+ let T = f, E = p, D = re(), O = a(() => D["aria-label"]), Te = a(() => Object.fromEntries(Object.entries(D).filter(([e]) => e !== "aria-label"))), k = ie(f, "modelValue"), A = _(!1), j = _(""), M = _(!1), N = a(() => Array.isArray(k.value) ? k.value : k.value === null || k.value === void 0 ? [] : [k.value]), P = ne([]), F = a(() => N.value.map((e) => [
80
+ T.options,
81
+ T.selectedOptions,
82
+ P.value
83
+ ].map((t) => t.find((t) => t.value === e)).find((e) => !!e)).filter((e) => !!e)), I = a(() => T.searchable ?? (T.multiple || T.canCreate || T.clearable || T.filter === !1 || T.options.length > 8)), L = a(() => T.multiple || T.clearable), Ee = (e, t) => typeof T.filter == "function" ? T.filter(e, t) : e.label.toLowerCase().includes(t.toLowerCase()), R = a(() => {
84
+ let e = j.value.trim();
85
+ return T.options.filter((e) => !(T.multiple && I.value && N.value.includes(e.value))).filter((t) => !e || T.filter === !1 || Ee(t, e));
76
86
  }), z = a(() => {
77
- let e = N.value.trim().toLowerCase();
78
- return D.canCreate && !!e && !D.options.some((t) => t.label.toLowerCase() === e);
79
- }), Ce = a(() => R.value.length > 20), B = [
87
+ let e = j.value.trim().toLowerCase();
88
+ return T.canCreate && !!e && !T.options.some((t) => t.label.toLowerCase() === e);
89
+ }), De = a(() => R.value.length > 20), B = [
80
90
  "gray",
81
91
  "purple",
82
92
  "red",
@@ -85,101 +95,114 @@ var ue = [
85
95
  "pink",
86
96
  "blue",
87
97
  "slate"
88
- ], V = a(() => B[N.value.length % B.length]), H = { create: !0 }, { attrs: we, invalid: U } = e(() => void 0, () => D.invalid);
98
+ ], V = a(() => B[j.value.length % B.length]), H = { create: !0 }, { attrs: Oe, invalid: U } = e(() => void 0, () => T.invalid);
89
99
  function W(e) {
90
- N.value = e, Se("query-change", e);
100
+ j.value = e, E("query-change", e);
91
101
  }
92
102
  function G(e) {
93
- D.disabled || (P.value = e, M.value = !0);
103
+ T.disabled || (M.value = e, A.value = !0);
94
104
  }
95
105
  function K() {
96
- M.value = !1, N.value && W("");
106
+ A.value = !1, j.value && W("");
97
107
  }
98
108
  function q(e) {
99
- M.value ? K() : G(!!e && e.detail === 0);
109
+ A.value ? K() : G(!!e && e.detail === 0);
100
110
  }
101
111
  function J(e) {
102
- D.multiple ? (j.value = F.value.includes(e.value) ? F.value.filter((t) => t !== e.value) : [...F.value, e.value], W("")) : (j.value = e.value, K());
112
+ P.value = [e, ...P.value.filter((t) => t.value !== e.value)], T.multiple ? (k.value = N.value.includes(e.value) ? N.value.filter((t) => t !== e.value) : [...N.value, e.value], W("")) : (k.value = e.value, K());
103
113
  }
104
- async function Te() {
105
- let e = N.value.trim(), t = D.createColored ? {
114
+ let Y = _(!1);
115
+ async function ke() {
116
+ if (Y.value) return;
117
+ let e = j.value.trim(), t = T.createColored ? {
106
118
  label: e,
107
119
  color: V.value
108
120
  } : { label: e };
109
- J(D.createOption ? await D.createOption(t) : {
110
- value: e,
111
- label: e,
112
- color: t.color
113
- });
114
- }
115
- function Y(e, t) {
116
- e.preventDefault(), "create" in t ? Te() : t.disabled || J(t);
121
+ Y.value = !0;
122
+ try {
123
+ J(T.createOption ? await T.createOption(t) : {
124
+ value: e,
125
+ label: e,
126
+ color: t.color
127
+ });
128
+ } catch (e) {
129
+ E("create-error", e);
130
+ } finally {
131
+ Y.value = !1;
132
+ }
117
133
  }
118
- function X(e) {
119
- j.value = F.value.filter((t) => t !== e);
134
+ function X(e, t) {
135
+ e.preventDefault(), "create" in t ? ke() : t.disabled || J(t);
120
136
  }
121
137
  function Z(e) {
138
+ k.value = T.multiple ? N.value.filter((t) => t !== e) : null;
139
+ }
140
+ function Ae(e) {
122
141
  [
123
142
  "ArrowUp",
124
143
  "ArrowDown",
125
144
  "Home",
126
145
  "End"
127
- ].includes(e.key) && (P.value = !0), e.key === "Backspace" && D.multiple && !N.value && F.value.length && X(F.value[F.value.length - 1]);
146
+ ].includes(e.key) && (M.value = !0), e.key === "Backspace" && L.value && !j.value && N.value.length && Z(N.value[N.value.length - 1]);
128
147
  }
129
- function Ee(e) {
130
- !M.value && ["ArrowDown", "ArrowUp"].includes(e.key) && (e.preventDefault(), G(!0));
148
+ function je(e) {
149
+ !A.value && ["ArrowDown", "ArrowUp"].includes(e.key) && (e.preventDefault(), G(!0));
131
150
  }
132
- function De(e) {
151
+ function Me(e) {
133
152
  e.preventDefault(), m(() => e.target?.querySelector("[data-select-filter], [role=\"listbox\"]")?.focus());
134
153
  }
135
154
  let Q = _();
136
- function Oe(e) {
155
+ function Ne(e) {
156
+ let t = e.detail.originalEvent.target;
157
+ t instanceof Node && Q.value?.$el?.contains(t) && e.preventDefault();
158
+ }
159
+ function Pe(e) {
137
160
  e.preventDefault(), Q.value?.$el?.focus();
138
161
  }
139
162
  let $ = _();
140
- function ke(e) {
141
- !P.value && e && (F.value.includes(e.value) || m(() => $.value && !P.value && ($.value.highlightedElement = null)));
163
+ function Fe(e) {
164
+ !M.value && e && (N.value.includes(e.value) || m(() => $.value && !M.value && ($.value.highlightedElement = null)));
142
165
  }
143
- let Ae = {
166
+ let Ie = {
144
167
  md: "min-h-9 pr-2.5 pl-3 text-sm",
145
168
  sm: "h-7 pr-1.5 pl-2 text-13"
146
169
  };
147
- return (e, a) => (g(), o(x(le), {
148
- open: M.value,
170
+ return (e, a) => (g(), o(x(fe), {
171
+ open: A.value,
149
172
  "onUpdate:open": a[2] ||= (e) => e ? G(!1) : K()
150
173
  }, {
151
- default: S(() => [d(x(oe), {
174
+ default: S(() => [d(x(le), {
152
175
  ref_key: "anchor",
153
176
  ref: Q,
154
177
  "as-child": ""
155
178
  }, {
156
179
  default: S(() => [y(e.$slots, "trigger", {
157
- open: M.value,
158
- selected: I.value,
180
+ open: A.value,
181
+ selected: F.value,
159
182
  toggle: q
160
183
  }, () => [l("button", ee({
161
- ...x(we),
162
- ...A.value
184
+ ...x(Oe),
185
+ ...Te.value
163
186
  }, {
164
- "aria-expanded": M.value,
187
+ "aria-expanded": A.value,
165
188
  "aria-haspopup": "listbox",
166
189
  "aria-invalid": x(U) ? "true" : void 0,
167
- class: ["group inline-flex items-center justify-between gap-1.5 rounded-(--sui-radius-button) border bg-sui-surface text-left text-sui-ink transition-colors duration-150 focus-visible:sui-ring-field disabled:pointer-events-none disabled:opacity-60", [Ae[f.size], x(U) ? "border-sui-danger-mid" : "border-sui-line-control hover:border-sui-ink-muted"]],
190
+ class: ["group inline-flex items-center justify-between gap-1.5 rounded-(--sui-radius-button) border bg-sui-surface text-left text-sui-ink transition-[border-color,box-shadow] duration-150 focus-visible:border-sui-accent focus-visible:sui-ring-field disabled:pointer-events-none disabled:opacity-60", [Ie[f.size], x(U) ? "border-sui-danger-mid" : "border-sui-line-strong hover:border-sui-ink-muted"]],
168
191
  disabled: f.disabled,
169
192
  type: "button",
170
193
  onClick: q,
171
- onKeydown: Ee
194
+ onKeydown: je
172
195
  }), [
173
- k.value ? (g(), c("span", de, b(k.value) + ": ", 1)) : s("", !0),
174
- I.value.length ? f.multiple ? (g(), c("span", pe, [(g(!0), c(i, null, v(I.value, (e) => (g(), o(r, {
196
+ O.value ? (g(), c("span", me, b(O.value) + ": ", 1)) : s("", !0),
197
+ F.value.length ? f.multiple ? (g(), c("span", ge, [(g(!0), c(i, null, v(F.value, (e) => (g(), o(r, {
175
198
  key: e.value,
176
199
  color: e.color ?? void 0,
177
200
  name: e.label
178
- }, null, 8, ["color", "name"]))), 128))])) : I.value[0].color ? (g(), o(r, {
201
+ }, null, 8, ["color", "name"]))), 128))])) : F.value[0].color ? (g(), o(r, {
179
202
  key: 3,
180
- color: I.value[0].color,
181
- name: I.value[0].label
182
- }, null, 8, ["color", "name"])) : (g(), c("span", me, b(I.value[0].label), 1)) : (g(), c("span", fe, b(f.placeholder), 1)),
203
+ color: F.value[0].color,
204
+ name: F.value[0].label
205
+ }, null, 8, ["color", "name"])) : (g(), c("span", _e, b(F.value[0].label), 1)) : (g(), c("span", he, b(f.placeholder), 1)),
183
206
  a[3] ||= l("svg", {
184
207
  "aria-hidden": "true",
185
208
  class: "size-4 shrink-0 fill-none stroke-sui-ink-muted transition-transform duration-150 group-aria-expanded:rotate-180",
@@ -188,75 +211,76 @@ var ue = [
188
211
  "stroke-width": "1.5",
189
212
  viewBox: "0 0 16 16"
190
213
  }, [l("path", { d: "M4.5 6.5 8 10l3.5-3.5" })], -1)
191
- ], 16, ue)])]),
214
+ ], 16, pe)])]),
192
215
  _: 3
193
- }, 512), d(x(ce), null, {
194
- default: S(() => [d(x(se), {
216
+ }, 512), d(x(de), null, {
217
+ default: S(() => [d(x(ue), {
195
218
  align: f.align,
196
- "aria-label": k.value ?? f.placeholder,
197
- class: h(["z-[1000] overflow-hidden rounded-xl border border-sui-line-subtle bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-menu-in", L.value ? "min-w-[max(var(--reka-popover-trigger-width),15rem)]" : "min-w-(--reka-popover-trigger-width)"]),
219
+ "aria-label": O.value ?? f.placeholder,
220
+ class: h(["z-[1100] overflow-hidden rounded-xl border border-sui-line-subtle bg-sui-surface text-sui-ink shadow-lg data-[state=open]:animate-sui-menu-in", I.value ? "min-w-[max(var(--reka-popover-trigger-width),15rem)]" : "min-w-(--reka-popover-trigger-width)"]),
198
221
  side: f.side,
199
222
  "side-offset": 4,
200
223
  style: te(f.panelWidth ? { width: f.panelWidth } : void 0),
201
- onCloseAutoFocus: Oe,
202
- onOpenAutoFocus: De
224
+ onCloseAutoFocus: Pe,
225
+ onOpenAutoFocus: Me,
226
+ onPointerDownOutside: Ne
203
227
  }, {
204
- default: S(() => [d(x(ie), {
228
+ default: S(() => [d(x(se), {
205
229
  ref_key: "listbox",
206
230
  ref: $,
207
231
  "highlight-on-hover": !0,
208
- "model-value": f.multiple ? F.value : F.value[0] ?? void 0,
232
+ "model-value": f.multiple ? N.value : N.value[0] ?? void 0,
209
233
  multiple: f.multiple,
210
- onHighlight: ke,
211
- onKeydown: Z,
212
- onPointermove: a[1] ||= (e) => P.value = !1
234
+ onHighlight: Fe,
235
+ onKeydown: Ae,
236
+ onPointermove: a[1] ||= (e) => M.value = !1
213
237
  }, {
214
- default: S(() => [L.value ? (g(), c("div", he, [f.multiple ? (g(!0), c(i, { key: 0 }, v(I.value, (e) => (g(), o(r, {
238
+ default: S(() => [I.value ? (g(), c("div", ve, [L.value ? (g(!0), c(i, { key: 0 }, v(F.value, (e) => (g(), o(r, {
215
239
  key: e.value,
216
240
  color: e.color ?? void 0,
217
241
  name: e.label,
218
242
  removable: "",
219
243
  "remove-label": f.removeLabel,
220
- onRemove: (t) => X(e.value)
244
+ onRemove: (t) => Z(e.value)
221
245
  }, null, 8, [
222
246
  "color",
223
247
  "name",
224
248
  "remove-label",
225
249
  "onRemove"
226
- ]))), 128)) : s("", !0), d(x(w), {
227
- "aria-label": k.value ?? f.placeholder,
250
+ ]))), 128)) : s("", !0), d(x(oe), {
251
+ "aria-label": O.value ?? f.placeholder,
228
252
  class: "h-5 min-w-16 flex-1 rounded-none border-none bg-transparent p-0 text-13 text-sui-ink shadow-none outline-hidden placeholder:text-sui-ink-faint focus:border-none focus:shadow-none focus:ring-0 focus:outline-hidden",
229
253
  "data-select-filter": "",
230
- "model-value": N.value,
231
- placeholder: f.multiple && I.value.length ? "" : f.searchPlaceholder,
254
+ "model-value": j.value,
255
+ placeholder: L.value && F.value.length ? "" : f.searchPlaceholder,
232
256
  "onUpdate:modelValue": W
233
257
  }, null, 8, [
234
258
  "aria-label",
235
259
  "model-value",
236
260
  "placeholder"
237
- ])])) : s("", !0), f.loading ? (g(), c("div", ge, [d(n, { size: "sm" })])) : R.value.length || z.value ? (g(), o(x(C), {
261
+ ])])) : s("", !0), f.loading ? (g(), c("div", ye, [d(n, { size: "sm" })])) : R.value.length || z.value ? (g(), o(x(ae), {
238
262
  key: 2,
239
- "aria-label": k.value ?? f.placeholder,
263
+ "aria-label": O.value ?? f.placeholder,
240
264
  class: "group/list max-h-60 overflow-auto p-1 outline-hidden",
241
- "data-keyboard": P.value || void 0
265
+ "data-keyboard": M.value || void 0
242
266
  }, {
243
- default: S(() => [Ce.value ? (g(), o(x(ae), {
267
+ default: S(() => [De.value ? (g(), o(x(ce), {
244
268
  key: 0,
245
269
  "estimate-size": 32,
246
270
  options: R.value,
247
271
  "text-content": (e) => e.label
248
272
  }, {
249
- default: S(({ option: n }) => [d(x(T), {
250
- class: h(E),
273
+ default: S(({ option: n }) => [d(x(C), {
274
+ class: h(w),
251
275
  disabled: n.disabled,
252
276
  value: n.value,
253
- onSelect: (e) => Y(e, n)
277
+ onSelect: (e) => X(e, n)
254
278
  }, {
255
- default: S(() => [l("span", _e, [y(e.$slots, "option", { option: n }, () => [n.color ? (g(), o(r, {
279
+ default: S(() => [l("span", be, [y(e.$slots, "option", { option: n }, () => [n.color ? (g(), o(r, {
256
280
  key: 0,
257
281
  color: n.color,
258
282
  name: n.label
259
- }, null, 8, ["color", "name"])) : (g(), c(i, { key: 1 }, [u(b(n.label), 1)], 64))])]), F.value.includes(n.value) ? (g(), o(t, { key: 0 })) : s("", !0)]),
283
+ }, null, 8, ["color", "name"])) : (g(), c(i, { key: 1 }, [u(b(n.label), 1)], 64))])]), N.value.includes(n.value) ? (g(), o(t, { key: 0 })) : s("", !0)]),
260
284
  _: 2
261
285
  }, 1032, [
262
286
  "disabled",
@@ -264,38 +288,38 @@ var ue = [
264
288
  "onSelect"
265
289
  ])]),
266
290
  _: 3
267
- }, 8, ["options", "text-content"])) : (g(!0), c(i, { key: 1 }, v(R.value, (n) => (g(), o(x(T), {
291
+ }, 8, ["options", "text-content"])) : (g(!0), c(i, { key: 1 }, v(R.value, (n) => (g(), o(x(C), {
268
292
  key: n.value,
269
- class: h(E),
293
+ class: h(w),
270
294
  disabled: n.disabled,
271
295
  value: n.value,
272
- onSelect: (e) => Y(e, n)
296
+ onSelect: (e) => X(e, n)
273
297
  }, {
274
- default: S(() => [l("span", ve, [y(e.$slots, "option", { option: n }, () => [n.color ? (g(), o(r, {
298
+ default: S(() => [l("span", xe, [y(e.$slots, "option", { option: n }, () => [n.color ? (g(), o(r, {
275
299
  key: 0,
276
300
  color: n.color,
277
301
  name: n.label
278
- }, null, 8, ["color", "name"])) : (g(), c(i, { key: 1 }, [u(b(n.label), 1)], 64))])]), F.value.includes(n.value) ? (g(), o(t, { key: 0 })) : s("", !0)]),
302
+ }, null, 8, ["color", "name"])) : (g(), c(i, { key: 1 }, [u(b(n.label), 1)], 64))])]), N.value.includes(n.value) ? (g(), o(t, { key: 0 })) : s("", !0)]),
279
303
  _: 2
280
304
  }, 1032, [
281
305
  "disabled",
282
306
  "value",
283
307
  "onSelect"
284
- ]))), 128)), z.value ? (g(), o(x(T), {
308
+ ]))), 128)), z.value ? (g(), o(x(C), {
285
309
  key: 2,
286
- class: h(E),
310
+ class: h(w),
287
311
  value: H,
288
- onSelect: a[0] ||= (e) => Y(e, H)
312
+ onSelect: a[0] ||= (e) => X(e, H)
289
313
  }, {
290
- default: S(() => [l("span", ye, [u(b(f.createLabel) + " ", 1), f.createColored ? (g(), o(r, {
314
+ default: S(() => [l("span", Se, [u(b(f.createLabel) + " ", 1), f.createColored ? (g(), o(r, {
291
315
  key: 0,
292
316
  color: V.value,
293
- name: N.value.trim()
294
- }, null, 8, ["color", "name"])) : (g(), c("strong", be, "“" + b(N.value.trim()) + "”", 1))])]),
317
+ name: j.value.trim()
318
+ }, null, 8, ["color", "name"])) : (g(), c("strong", Ce, "“" + b(j.value.trim()) + "”", 1))])]),
295
319
  _: 1
296
320
  })) : s("", !0)]),
297
321
  _: 3
298
- }, 8, ["aria-label", "data-keyboard"])) : (g(), c("div", xe, b(f.emptyLabel), 1))]),
322
+ }, 8, ["aria-label", "data-keyboard"])) : (g(), c("div", we, b(f.emptyLabel), 1))]),
299
323
  _: 3
300
324
  }, 8, ["model-value", "multiple"])]),
301
325
  _: 3
@@ -313,6 +337,6 @@ var ue = [
313
337
  }
314
338
  });
315
339
  //#endregion
316
- export { D as default };
340
+ export { T as default };
317
341
 
318
342
  //# sourceMappingURL=Select.vue_vue_type_script_setup_true_lang.js.map