@vue-interface/searchable-select-field 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchable-select-field.js","sources":["../../../node_modules/.pnpm/@heroicons+vue@2.2.0_vue@3.5.26_typescript@5.9.3_/node_modules/@heroicons/vue/24/outline/esm/ChevronDownIcon.js","../../../node_modules/.pnpm/@heroicons+vue@2.2.0_vue@3.5.26_typescript@5.9.3_/node_modules/@heroicons/vue/24/outline/esm/XMarkIcon.js","../src/SearchableSelectField.vue"],"sourcesContent":["import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport default function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"none\",\n viewBox: \"0 0 24 24\",\n \"stroke-width\": \"1.5\",\n stroke: \"currentColor\",\n \"aria-hidden\": \"true\",\n \"data-slot\": \"icon\"\n }, [\n _createElementVNode(\"path\", {\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n d: \"m19.5 8.25-7.5 7.5-7.5-7.5\"\n })\n ]))\n}","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport default function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"none\",\n viewBox: \"0 0 24 24\",\n \"stroke-width\": \"1.5\",\n stroke: \"currentColor\",\n \"aria-hidden\": \"true\",\n \"data-slot\": \"icon\"\n }, [\n _createElementVNode(\"path\", {\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n d: \"M6 18 18 6M6 6l12 12\"\n })\n ]))\n}","<script setup lang=\"ts\" generic=\"T, Value\">\nimport { ChevronDownIcon, XMarkIcon } from '@heroicons/vue/24/outline';\nimport { ActivityIndicator, Pulse } from '@vue-interface/activity-indicator';\nimport type { FormControlEvents, FormControlProps, FormControlSlots } from '@vue-interface/form-control';\nimport { useFormControl } from '@vue-interface/form-control';\nimport { InputField } from '@vue-interface/input-field';\nimport Fuse, { IFuseOptions } from 'fuse.js';\nimport { InputHTMLAttributes, computed, nextTick, ref, useTemplateRef, watch, watchEffect } from 'vue';\n\nconst props = withDefaults(defineProps<SearchableSelectFieldProps<T,Value>>(), {\n formControlClass: 'form-control',\n labelClass: 'form-label',\n size: 'form-control-md',\n clearable: true,\n options: () => []\n});\n\nconst model = defineModel<T>();\nconst isInteractive = computed(() => !props.disabled && !props.readonly);\n\ndefineSlots<FormControlSlots<SearchableSelectFieldSizePrefix,T> & {\n default(props: { option: T; display?: (option: T) => string }): any;\n}>();\n\nconst emit = defineEmits<FormControlEvents>();\n\nconst {\n controlAttributes,\n formGroupClasses,\n listeners\n} = useFormControl<InputHTMLAttributes, SearchableSelectFieldSizePrefix, T|undefined, T>({ model, props, emit });\n\nwatchEffect(() => {\n if(props.value !== undefined) {\n model.value = props.value;\n }\n});\n\nconst input = ref<string>();\nconst field = useTemplateRef<any>('field');\nconst showOptions = ref(false);\nconst active = ref<number>();\nconst buttons = useTemplateRef<HTMLButtonElement[]>('buttons');\nconst optionsEl = useTemplateRef<HTMLDivElement>('optionsEl');\n\nconst keys = computed(() => {\n return typeof props.options === 'object' && props.options?.[0]\n ? Object.keys(props.options?.[0])\n : ['$'];\n});\n\nlet fuse: Fuse<T> = createFuse();\n\nfunction createFuse() {\n return new Fuse(props.options ?? [], props.fuseOptions ?? {\n includeScore: true,\n threshold: .45,\n keys: keys.value\n });\n}\n\nconst filtered = computed<T[]>(() => {\n if(!input.value) {\n return props.options ?? [];\n }\n\n const matches = fuse.search(input.value).map(({ item }) => item);\n\n if(props.allowCustom && !matches.length) {\n return props.options;\n }\n\n return matches;\n});\n\nwatch(() => props.options, () => {\n fuse = createFuse();\n});\n\nfunction scrollIntoView(child?: HTMLElement) {\n const parent = optionsEl.value;\n\n if(!parent || !child) {\n return;\n }\n\n const parentRect = parent.getBoundingClientRect();\n const childRect = child.getBoundingClientRect();\n\n const childTop = childRect.top - parentRect.top + parent.scrollTop;\n\n parent.scrollTop = childTop;\n};\n\nwatch([input, active], ([input, active]) => {\n if(input) {\n buttons.value?.[0]?.scrollIntoView({\n block: 'nearest',\n inline: 'nearest'\n });\n }\n else if(active !== undefined) {\n buttons.value?.[active]?.scrollIntoView({\n block: 'nearest',\n inline: 'nearest'\n });\n }\n});\n\nwatch(optionsEl, (value) => {\n if(!value || active.value === undefined) {\n return;\n }\n\n nextTick(() => {\n scrollIntoView(buttons.value?.[active.value as number]);\n });\n});\n\nfunction select(option?: T) {\n model.value = option;\n active.value = option && props.options.includes(option)\n ? props.options.indexOf(option)\n : undefined;\n input.value = undefined;\n showOptions.value = false;\n}\n\nfunction onInput(e: Event) {\n if (!isInteractive.value) return;\n\n showOptions.value = true;\n active.value = undefined;\n input.value = (e.target as HTMLInputElement)?.value;\n\n if(!input.value && !props.allowCustom) {\n model.value = undefined;\n }\n\n if(props.allowCustom) {\n model.value = input.value as T;\n }\n}\n\nfunction onKeypressEnter() {\n if (!isInteractive.value) return;\n\n if(!showOptions.value) {\n showOptions.value = true;\n return;\n }\n\n if(props.allowCustom && active.value === undefined) {\n select(input.value as T ?? model.value);\n }\n else if(active.value === undefined) {\n select(filtered.value[0]);\n }\n else if(filtered.value[active.value]) {\n select(filtered.value[active.value]);\n }\n else {\n select(undefined);\n }\n}\n\nfunction onKeydownUp() {\n if (!isInteractive.value) return;\n \n showOptions.value = true;\n\n if(!active.value) {\n active.value = filtered.value.length - 1;\n }\n else {\n active.value--;\n }\n}\n\nfunction onKeydownDown() {\n if (!isInteractive.value) return;\n\n showOptions.value = true;\n\n if(active.value === undefined || active.value === filtered.value.length - 1) {\n active.value = 0;\n }\n else {\n active.value++;\n }\n}\n\nfunction onBlur() {\n showOptions.value = false;\n input.value = undefined;\n}\n\nfunction onClickOption(option: T) {\n if (!isInteractive.value) return;\n select(option);\n}\n\nfunction clear() {\n if (!isInteractive.value) return;\n input.value = undefined;\n model.value = undefined;\n}\n\nfunction toggle() {\n if (!isInteractive.value) return;\n \n if(showOptions.value ) {\n showOptions.value = false;\n }\n else {\n showOptions.value = true;\n field.value?.$el?.querySelector('input')?.focus();\n }\n}\n\nconst canClear = computed(() => {\n return props.clearable && (!!input.value || !!model.value) && isInteractive.value;\n});\n</script>\n<script lang=\"ts\">\nexport type SearchableSelectFieldSizePrefix = 'form-control';\n\nexport type SearchableSelectFieldProps<ModelValue, Value> = FormControlProps<\n InputHTMLAttributes, \n SearchableSelectFieldSizePrefix, \n ModelValue, \n Value\n> & {\n options?: ModelValue[];\n fuseOptions?: IFuseOptions<ModelValue>;\n display?: (option: ModelValue) => string;\n allowCustom?: boolean;\n clearable?: boolean;\n};\n</script>\n\n\n<template>\n <div class=\"relative [&_.form-control]:pr-8\">\n <InputField\n ref=\"field\"\n class=\"searchable-select-field-input\"\n :class=\"{ 'has-clear-button': canClear, formGroupClasses }\"\n :size=\"size\"\n v-bind=\"{ ...$attrs, controlAttributes, listeners }\"\n :name=\"name\"\n :label=\"label\"\n :model-value=\"input ?? (model && props?.display ? props?.display?.(model) : model)\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :color=\"color\"\n :error=\"error\"\n :errors=\"errors\"\n :feedback=\"feedback\"\n :valid=\"valid\"\n :invalid=\"invalid\"\n @click=\"isInteractive && (showOptions = true)\"\n @focus=\"isInteractive && (showOptions = true)\"\n @blur=\"onBlur\"\n @keypress.enter.prevent=\"onKeypressEnter\"\n @keydown.up.prevent=\"onKeydownUp\"\n @keydown.down.prevent=\"onKeydownDown\"\n @keyup.escape=\"showOptions = false\"\n @input=\"onInput\">\n <template #icon v-if=\"$slots.icon\">\n <slot name=\"icon\" />\n </template>\n <template #activity>\n <slot\n name=\"activity\"\n v-bind=\"{ disabled, options, invalid, valid }\">\n <ActivityIndicator\n v-if=\"!disabled && !options\"\n :type=\"Pulse\"\n size=\"activity-indicator-sm\" />\n <button\n v-else-if=\"canClear\"\n type=\"button\"\n class=\"searchable-select-field-clear-button\"\n @click.stop=\"clear\">\n <XMarkIcon class=\"size-[1.25em]\" />\n </button>\n <button\n v-else-if=\"!invalid && !valid\"\n type=\"button\"\n @click.stop=\"toggle\">\n <ChevronDownIcon class=\"size-[1em]\" />\n </button>\n </slot>\n </template>\n </InputField>\n <div\n v-if=\"showOptions && filtered.length\"\n ref=\"optionsEl\"\n tabindex=\"-1\"\n class=\"searchable-select-field-dropdown\"\n :class=\"size\"\n @mousedown.prevent.stop>\n <button\n v-for=\"(option, i) in filtered\"\n ref=\"buttons\"\n :key=\"i\"\n type=\"button\"\n tabindex=\"-1\"\n :title=\"display?.(option) ?? String(option)\"\n :class=\"{\n ['bg-neutral-200 dark:bg-neutral-700']: active === i\n }\"\n @mousedown.prevent\n @click=\"onClickOption(option)\">\n <slot v-bind=\"{ option, display }\">\n <div class=\"truncate\">\n {{ display?.(option) ?? option }}\n </div>\n </slot>\n </button>\n </div>\n </div>\n</template>"],"names":["render","_ctx","_cache","_openBlock","_createElementBlock","_createElementVNode","props","__props","model","_useModel","isInteractive","computed","emit","__emit","controlAttributes","formGroupClasses","listeners","useFormControl","watchEffect","input","ref","field","useTemplateRef","showOptions","active","buttons","optionsEl","keys","fuse","createFuse","Fuse","filtered","matches","item","watch","scrollIntoView","child","parent","parentRect","childTop","value","nextTick","select","option","onInput","e","onKeypressEnter","onKeydownUp","onKeydownDown","onBlur","onClickOption","clear","toggle","canClear","_hoisted_1","_createVNode","_unref","_mergeProps","$attrs","$event","_renderSlot","_normalizeProps","_guardReactiveProps","_createBlock","ActivityIndicator","Pulse","XMarkIcon","ChevronDownIcon","$slots","_normalizeClass","_Fragment","_renderList","i","_hoisted_3","_toDisplayString"],"mappings":"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","x_google_ignoreList":[0,1]}
|
|
1
|
+
{"version":3,"file":"searchable-select-field.js","sources":["../../../node_modules/.pnpm/@heroicons+vue@2.2.0_vue@3.5.27_typescript@5.9.3_/node_modules/@heroicons/vue/24/outline/esm/ChevronDownIcon.js","../../../node_modules/.pnpm/@heroicons+vue@2.2.0_vue@3.5.27_typescript@5.9.3_/node_modules/@heroicons/vue/24/outline/esm/XMarkIcon.js","../src/SearchableSelectField.vue"],"sourcesContent":["import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport default function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"none\",\n viewBox: \"0 0 24 24\",\n \"stroke-width\": \"1.5\",\n stroke: \"currentColor\",\n \"aria-hidden\": \"true\",\n \"data-slot\": \"icon\"\n }, [\n _createElementVNode(\"path\", {\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n d: \"m19.5 8.25-7.5 7.5-7.5-7.5\"\n })\n ]))\n}","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport default function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"none\",\n viewBox: \"0 0 24 24\",\n \"stroke-width\": \"1.5\",\n stroke: \"currentColor\",\n \"aria-hidden\": \"true\",\n \"data-slot\": \"icon\"\n }, [\n _createElementVNode(\"path\", {\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n d: \"M6 18 18 6M6 6l12 12\"\n })\n ]))\n}","<script setup lang=\"ts\" generic=\"ModelValue, Value extends ModelValue\">\nimport { ChevronDownIcon, XMarkIcon } from '@heroicons/vue/24/outline';\nimport { ActivityIndicator, Pulse } from '@vue-interface/activity-indicator';\nimport type { FormControlEvents, FormControlProps, FormControlSlots } from '@vue-interface/form-control';\nimport { useFormControl } from '@vue-interface/form-control';\nimport { InputField } from '@vue-interface/input-field';\nimport Fuse, { IFuseOptions } from 'fuse.js';\nimport { type HTMLAttributes, computed, nextTick, ref, useTemplateRef, watch, watchEffect } from 'vue';\n\nconst props = withDefaults(defineProps<SearchableSelectFieldProps<ModelValue,Value>>(), {\n formControlClass: 'form-control',\n labelClass: 'form-label',\n size: 'form-control-md',\n clearable: true,\n options: () => []\n});\n\nconst model = defineModel<ModelValue>();\nconst isInteractive = computed(() => !props.disabled && !props.readonly);\n\ndefineSlots<FormControlSlots<SearchableSelectFieldSizePrefix,ModelValue> & {\n default(props: { option: ModelValue; display?: (option: ModelValue) => string }): any;\n}>();\n\nconst emit = defineEmits<FormControlEvents>();\n\nconst {\n controlAttributes,\n formGroupClasses,\n listeners\n} = useFormControl<HTMLAttributes, SearchableSelectFieldSizePrefix, ModelValue|undefined, ModelValue>({ model, props, emit });\n\nwatchEffect(() => {\n if(props.value !== undefined) {\n model.value = props.value;\n }\n});\n\nconst input = ref<string>();\nconst field = useTemplateRef<any>('field');\nconst showOptions = ref(false);\nconst active = ref<number>();\nconst buttons = useTemplateRef<HTMLButtonElement[]>('buttons');\nconst optionsEl = useTemplateRef<HTMLDivElement>('optionsEl');\n\nconst keys = computed(() => {\n return typeof props.options === 'object' && props.options?.[0]\n ? Object.keys(props.options?.[0])\n : ['$'];\n});\n\nlet fuse: Fuse<ModelValue> = createFuse();\n\nfunction createFuse() {\n return new Fuse(props.options ?? [], props.fuseOptions ?? {\n includeScore: true,\n threshold: .45,\n keys: keys.value\n });\n}\n\nconst filtered = computed<ModelValue[]>(() => {\n if(!input.value) {\n return props.options ?? [];\n }\n\n const matches = fuse.search(input.value).map(({ item }) => item);\n\n if(props.allowCustom && !matches.length) {\n return props.options;\n }\n\n return matches;\n});\n\nwatch(() => props.options, () => {\n fuse = createFuse();\n});\n\nfunction scrollIntoView(child?: HTMLElement) {\n const parent = optionsEl.value;\n\n if(!parent || !child) {\n return;\n }\n\n const parentRect = parent.getBoundingClientRect();\n const childRect = child.getBoundingClientRect();\n\n const childTop = childRect.top - parentRect.top + parent.scrollTop;\n\n parent.scrollTop = childTop;\n};\n\nwatch([input, active], ([input, active]) => {\n if(input) {\n buttons.value?.[0]?.scrollIntoView({\n block: 'nearest',\n inline: 'nearest'\n });\n }\n else if(active !== undefined) {\n buttons.value?.[active]?.scrollIntoView({\n block: 'nearest',\n inline: 'nearest'\n });\n }\n});\n\nwatch(optionsEl, (value) => {\n if(!value || active.value === undefined) {\n return;\n }\n\n nextTick(() => {\n scrollIntoView(buttons.value?.[active.value as number]);\n });\n});\n\nfunction select(option?: ModelValue) {\n model.value = option;\n active.value = option && props.options.includes(option)\n ? props.options.indexOf(option)\n : undefined;\n input.value = undefined;\n showOptions.value = false;\n}\n\nfunction onInput(e: Event) {\n if (!isInteractive.value) return;\n\n showOptions.value = true;\n active.value = undefined;\n input.value = (e.target as HTMLInputElement)?.value;\n\n if(!input.value && !props.allowCustom) {\n model.value = undefined;\n }\n\n if(props.allowCustom) {\n model.value = input.value as ModelValue;\n }\n}\n\nfunction onKeypressEnter() {\n if (!isInteractive.value) return;\n\n if(!showOptions.value) {\n showOptions.value = true;\n return;\n }\n\n if(props.allowCustom && active.value === undefined) {\n select(input.value as ModelValue ?? model.value);\n }\n else if(active.value === undefined) {\n select(filtered.value[0]);\n }\n else if(filtered.value[active.value]) {\n select(filtered.value[active.value]);\n }\n else {\n select(undefined);\n }\n}\n\nfunction onKeydownUp() {\n if (!isInteractive.value) return;\n \n showOptions.value = true;\n\n if(!active.value) {\n active.value = filtered.value.length - 1;\n }\n else {\n active.value--;\n }\n}\n\nfunction onKeydownDown() {\n if (!isInteractive.value) return;\n\n showOptions.value = true;\n\n if(active.value === undefined || active.value === filtered.value.length - 1) {\n active.value = 0;\n }\n else {\n active.value++;\n }\n}\n\nfunction onBlur() {\n showOptions.value = false;\n input.value = undefined;\n}\n\nfunction onClickOption(option: ModelValue) {\n if (!isInteractive.value) return;\n select(option);\n}\n\nfunction clear() {\n if (!isInteractive.value) return;\n input.value = undefined;\n model.value = undefined;\n}\n\nfunction toggle() {\n if (!isInteractive.value) return;\n \n if(showOptions.value ) {\n showOptions.value = false;\n }\n else {\n showOptions.value = true;\n field.value?.$el?.querySelector('input')?.focus();\n }\n}\n\nconst canClear = computed(() => {\n return props.clearable && (!!input.value || !!model.value) && isInteractive.value;\n});\n</script>\n<script lang=\"ts\">\nexport type SearchableSelectFieldSizePrefix = 'form-control';\n\nexport type SearchableSelectFieldProps<ModelValue, Value> = FormControlProps<\n HTMLAttributes, \n SearchableSelectFieldSizePrefix, \n ModelValue, \n Value\n> & {\n options?: ModelValue[];\n fuseOptions?: IFuseOptions<ModelValue>;\n display?: (option: ModelValue) => string;\n allowCustom?: boolean;\n clearable?: boolean;\n};\n</script>\n\n\n<template>\n <div class=\"relative [&_.form-control]:pr-8\">\n <InputField\n ref=\"field\"\n class=\"searchable-select-field-input\"\n :class=\"{ 'has-clear-button': canClear, formGroupClasses }\"\n :size=\"size\"\n v-bind=\"{ ...$attrs, controlAttributes, listeners }\"\n :name=\"name\"\n :label=\"label\"\n :model-value=\"input ?? (model && props?.display ? props?.display?.(model) : model)\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :color=\"color\"\n :error=\"error\"\n :errors=\"errors\"\n :feedback=\"feedback\"\n :valid=\"valid\"\n :invalid=\"invalid\"\n @click=\"isInteractive && (showOptions = true)\"\n @focus=\"isInteractive && (showOptions = true)\"\n @blur=\"onBlur\"\n @keypress.enter.prevent=\"onKeypressEnter\"\n @keydown.up.prevent=\"onKeydownUp\"\n @keydown.down.prevent=\"onKeydownDown\"\n @keyup.escape=\"showOptions = false\"\n @input=\"onInput\">\n <template #icon v-if=\"$slots.icon\">\n <slot name=\"icon\" />\n </template>\n <template #activity>\n <slot\n name=\"activity\"\n v-bind=\"{ disabled, options, invalid, valid }\">\n <ActivityIndicator\n v-if=\"!disabled && !options\"\n :type=\"Pulse\"\n size=\"activity-indicator-sm\" />\n <button\n v-else-if=\"canClear\"\n type=\"button\"\n class=\"searchable-select-field-clear-button\"\n @click.stop=\"clear\">\n <XMarkIcon class=\"size-[1.25em]\" />\n </button>\n <button\n v-else-if=\"!invalid && !valid\"\n type=\"button\"\n @click.stop=\"toggle\">\n <ChevronDownIcon class=\"size-[1em]\" />\n </button>\n </slot>\n </template>\n </InputField>\n <div\n v-if=\"showOptions && filtered.length\"\n ref=\"optionsEl\"\n tabindex=\"-1\"\n class=\"searchable-select-field-dropdown\"\n :class=\"size\"\n @mousedown.prevent.stop>\n <button\n v-for=\"(option, i) in filtered\"\n ref=\"buttons\"\n :key=\"i\"\n type=\"button\"\n tabindex=\"-1\"\n :title=\"display?.(option) ?? String(option)\"\n :class=\"{\n ['bg-neutral-200 dark:bg-neutral-700']: active === i\n }\"\n @mousedown.prevent\n @click=\"onClickOption(option)\">\n <slot v-bind=\"{ option, display }\">\n <div class=\"truncate\">\n {{ display?.(option) ?? option }}\n </div>\n </slot>\n </button>\n </div>\n </div>\n</template>"],"names":["render","_ctx","_cache","_openBlock","_createElementBlock","_createElementVNode","props","__props","model","_useModel","isInteractive","computed","emit","__emit","controlAttributes","formGroupClasses","listeners","useFormControl","watchEffect","input","ref","field","useTemplateRef","showOptions","active","buttons","optionsEl","keys","fuse","createFuse","Fuse","filtered","matches","item","watch","scrollIntoView","child","parent","parentRect","childTop","value","nextTick","select","option","onInput","e","onKeypressEnter","onKeydownUp","onKeydownDown","onBlur","onClickOption","clear","toggle","canClear","_hoisted_1","_createVNode","_unref","_mergeProps","$attrs","$event","_renderSlot","_normalizeProps","_guardReactiveProps","_createBlock","ActivityIndicator","Pulse","XMarkIcon","ChevronDownIcon","$slots","_normalizeClass","_Fragment","_renderList","i","_hoisted_3","_toDisplayString"],"mappings":"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","x_google_ignoreList":[0,1]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchable-select-field.umd.cjs","sources":["../../../node_modules/.pnpm/@heroicons+vue@2.2.0_vue@3.5.26_typescript@5.9.3_/node_modules/@heroicons/vue/24/outline/esm/ChevronDownIcon.js","../../../node_modules/.pnpm/@heroicons+vue@2.2.0_vue@3.5.26_typescript@5.9.3_/node_modules/@heroicons/vue/24/outline/esm/XMarkIcon.js","../src/SearchableSelectField.vue"],"sourcesContent":["import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport default function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"none\",\n viewBox: \"0 0 24 24\",\n \"stroke-width\": \"1.5\",\n stroke: \"currentColor\",\n \"aria-hidden\": \"true\",\n \"data-slot\": \"icon\"\n }, [\n _createElementVNode(\"path\", {\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n d: \"m19.5 8.25-7.5 7.5-7.5-7.5\"\n })\n ]))\n}","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport default function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"none\",\n viewBox: \"0 0 24 24\",\n \"stroke-width\": \"1.5\",\n stroke: \"currentColor\",\n \"aria-hidden\": \"true\",\n \"data-slot\": \"icon\"\n }, [\n _createElementVNode(\"path\", {\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n d: \"M6 18 18 6M6 6l12 12\"\n })\n ]))\n}","<script setup lang=\"ts\" generic=\"T, Value\">\nimport { ChevronDownIcon, XMarkIcon } from '@heroicons/vue/24/outline';\nimport { ActivityIndicator, Pulse } from '@vue-interface/activity-indicator';\nimport type { FormControlEvents, FormControlProps, FormControlSlots } from '@vue-interface/form-control';\nimport { useFormControl } from '@vue-interface/form-control';\nimport { InputField } from '@vue-interface/input-field';\nimport Fuse, { IFuseOptions } from 'fuse.js';\nimport { InputHTMLAttributes, computed, nextTick, ref, useTemplateRef, watch, watchEffect } from 'vue';\n\nconst props = withDefaults(defineProps<SearchableSelectFieldProps<T,Value>>(), {\n formControlClass: 'form-control',\n labelClass: 'form-label',\n size: 'form-control-md',\n clearable: true,\n options: () => []\n});\n\nconst model = defineModel<T>();\nconst isInteractive = computed(() => !props.disabled && !props.readonly);\n\ndefineSlots<FormControlSlots<SearchableSelectFieldSizePrefix,T> & {\n default(props: { option: T; display?: (option: T) => string }): any;\n}>();\n\nconst emit = defineEmits<FormControlEvents>();\n\nconst {\n controlAttributes,\n formGroupClasses,\n listeners\n} = useFormControl<InputHTMLAttributes, SearchableSelectFieldSizePrefix, T|undefined, T>({ model, props, emit });\n\nwatchEffect(() => {\n if(props.value !== undefined) {\n model.value = props.value;\n }\n});\n\nconst input = ref<string>();\nconst field = useTemplateRef<any>('field');\nconst showOptions = ref(false);\nconst active = ref<number>();\nconst buttons = useTemplateRef<HTMLButtonElement[]>('buttons');\nconst optionsEl = useTemplateRef<HTMLDivElement>('optionsEl');\n\nconst keys = computed(() => {\n return typeof props.options === 'object' && props.options?.[0]\n ? Object.keys(props.options?.[0])\n : ['$'];\n});\n\nlet fuse: Fuse<T> = createFuse();\n\nfunction createFuse() {\n return new Fuse(props.options ?? [], props.fuseOptions ?? {\n includeScore: true,\n threshold: .45,\n keys: keys.value\n });\n}\n\nconst filtered = computed<T[]>(() => {\n if(!input.value) {\n return props.options ?? [];\n }\n\n const matches = fuse.search(input.value).map(({ item }) => item);\n\n if(props.allowCustom && !matches.length) {\n return props.options;\n }\n\n return matches;\n});\n\nwatch(() => props.options, () => {\n fuse = createFuse();\n});\n\nfunction scrollIntoView(child?: HTMLElement) {\n const parent = optionsEl.value;\n\n if(!parent || !child) {\n return;\n }\n\n const parentRect = parent.getBoundingClientRect();\n const childRect = child.getBoundingClientRect();\n\n const childTop = childRect.top - parentRect.top + parent.scrollTop;\n\n parent.scrollTop = childTop;\n};\n\nwatch([input, active], ([input, active]) => {\n if(input) {\n buttons.value?.[0]?.scrollIntoView({\n block: 'nearest',\n inline: 'nearest'\n });\n }\n else if(active !== undefined) {\n buttons.value?.[active]?.scrollIntoView({\n block: 'nearest',\n inline: 'nearest'\n });\n }\n});\n\nwatch(optionsEl, (value) => {\n if(!value || active.value === undefined) {\n return;\n }\n\n nextTick(() => {\n scrollIntoView(buttons.value?.[active.value as number]);\n });\n});\n\nfunction select(option?: T) {\n model.value = option;\n active.value = option && props.options.includes(option)\n ? props.options.indexOf(option)\n : undefined;\n input.value = undefined;\n showOptions.value = false;\n}\n\nfunction onInput(e: Event) {\n if (!isInteractive.value) return;\n\n showOptions.value = true;\n active.value = undefined;\n input.value = (e.target as HTMLInputElement)?.value;\n\n if(!input.value && !props.allowCustom) {\n model.value = undefined;\n }\n\n if(props.allowCustom) {\n model.value = input.value as T;\n }\n}\n\nfunction onKeypressEnter() {\n if (!isInteractive.value) return;\n\n if(!showOptions.value) {\n showOptions.value = true;\n return;\n }\n\n if(props.allowCustom && active.value === undefined) {\n select(input.value as T ?? model.value);\n }\n else if(active.value === undefined) {\n select(filtered.value[0]);\n }\n else if(filtered.value[active.value]) {\n select(filtered.value[active.value]);\n }\n else {\n select(undefined);\n }\n}\n\nfunction onKeydownUp() {\n if (!isInteractive.value) return;\n \n showOptions.value = true;\n\n if(!active.value) {\n active.value = filtered.value.length - 1;\n }\n else {\n active.value--;\n }\n}\n\nfunction onKeydownDown() {\n if (!isInteractive.value) return;\n\n showOptions.value = true;\n\n if(active.value === undefined || active.value === filtered.value.length - 1) {\n active.value = 0;\n }\n else {\n active.value++;\n }\n}\n\nfunction onBlur() {\n showOptions.value = false;\n input.value = undefined;\n}\n\nfunction onClickOption(option: T) {\n if (!isInteractive.value) return;\n select(option);\n}\n\nfunction clear() {\n if (!isInteractive.value) return;\n input.value = undefined;\n model.value = undefined;\n}\n\nfunction toggle() {\n if (!isInteractive.value) return;\n \n if(showOptions.value ) {\n showOptions.value = false;\n }\n else {\n showOptions.value = true;\n field.value?.$el?.querySelector('input')?.focus();\n }\n}\n\nconst canClear = computed(() => {\n return props.clearable && (!!input.value || !!model.value) && isInteractive.value;\n});\n</script>\n<script lang=\"ts\">\nexport type SearchableSelectFieldSizePrefix = 'form-control';\n\nexport type SearchableSelectFieldProps<ModelValue, Value> = FormControlProps<\n InputHTMLAttributes, \n SearchableSelectFieldSizePrefix, \n ModelValue, \n Value\n> & {\n options?: ModelValue[];\n fuseOptions?: IFuseOptions<ModelValue>;\n display?: (option: ModelValue) => string;\n allowCustom?: boolean;\n clearable?: boolean;\n};\n</script>\n\n\n<template>\n <div class=\"relative [&_.form-control]:pr-8\">\n <InputField\n ref=\"field\"\n class=\"searchable-select-field-input\"\n :class=\"{ 'has-clear-button': canClear, formGroupClasses }\"\n :size=\"size\"\n v-bind=\"{ ...$attrs, controlAttributes, listeners }\"\n :name=\"name\"\n :label=\"label\"\n :model-value=\"input ?? (model && props?.display ? props?.display?.(model) : model)\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :color=\"color\"\n :error=\"error\"\n :errors=\"errors\"\n :feedback=\"feedback\"\n :valid=\"valid\"\n :invalid=\"invalid\"\n @click=\"isInteractive && (showOptions = true)\"\n @focus=\"isInteractive && (showOptions = true)\"\n @blur=\"onBlur\"\n @keypress.enter.prevent=\"onKeypressEnter\"\n @keydown.up.prevent=\"onKeydownUp\"\n @keydown.down.prevent=\"onKeydownDown\"\n @keyup.escape=\"showOptions = false\"\n @input=\"onInput\">\n <template #icon v-if=\"$slots.icon\">\n <slot name=\"icon\" />\n </template>\n <template #activity>\n <slot\n name=\"activity\"\n v-bind=\"{ disabled, options, invalid, valid }\">\n <ActivityIndicator\n v-if=\"!disabled && !options\"\n :type=\"Pulse\"\n size=\"activity-indicator-sm\" />\n <button\n v-else-if=\"canClear\"\n type=\"button\"\n class=\"searchable-select-field-clear-button\"\n @click.stop=\"clear\">\n <XMarkIcon class=\"size-[1.25em]\" />\n </button>\n <button\n v-else-if=\"!invalid && !valid\"\n type=\"button\"\n @click.stop=\"toggle\">\n <ChevronDownIcon class=\"size-[1em]\" />\n </button>\n </slot>\n </template>\n </InputField>\n <div\n v-if=\"showOptions && filtered.length\"\n ref=\"optionsEl\"\n tabindex=\"-1\"\n class=\"searchable-select-field-dropdown\"\n :class=\"size\"\n @mousedown.prevent.stop>\n <button\n v-for=\"(option, i) in filtered\"\n ref=\"buttons\"\n :key=\"i\"\n type=\"button\"\n tabindex=\"-1\"\n :title=\"display?.(option) ?? String(option)\"\n :class=\"{\n ['bg-neutral-200 dark:bg-neutral-700']: active === i\n }\"\n @mousedown.prevent\n @click=\"onClickOption(option)\">\n <slot v-bind=\"{ option, display }\">\n <div class=\"truncate\">\n {{ display?.(option) ?? option }}\n </div>\n </slot>\n </button>\n </div>\n </div>\n</template>"],"names":["render","_ctx","_cache","_openBlock","_createElementBlock","_createElementVNode","props","__props","model","_useModel","isInteractive","computed","emit","__emit","controlAttributes","formGroupClasses","listeners","useFormControl","watchEffect","input","ref","field","useTemplateRef","showOptions","active","buttons","optionsEl","keys","fuse","createFuse","Fuse","filtered","matches","item","watch","scrollIntoView","child","parent","parentRect","childTop","value","nextTick","select","option","onInput","e","onKeypressEnter","onKeydownUp","onKeydownDown","onBlur","onClickOption","clear","toggle","canClear","_hoisted_1","_createVNode","_unref","_mergeProps","$attrs","$event","_renderSlot","_normalizeProps","_guardReactiveProps","_createBlock","ActivityIndicator","Pulse","XMarkIcon","ChevronDownIcon","$slots","_normalizeClass","_Fragment","_renderList","i","_hoisted_3","_toDisplayString"],"mappings":"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","x_google_ignoreList":[0,1]}
|
|
1
|
+
{"version":3,"file":"searchable-select-field.umd.cjs","sources":["../../../node_modules/.pnpm/@heroicons+vue@2.2.0_vue@3.5.27_typescript@5.9.3_/node_modules/@heroicons/vue/24/outline/esm/ChevronDownIcon.js","../../../node_modules/.pnpm/@heroicons+vue@2.2.0_vue@3.5.27_typescript@5.9.3_/node_modules/@heroicons/vue/24/outline/esm/XMarkIcon.js","../src/SearchableSelectField.vue"],"sourcesContent":["import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport default function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"none\",\n viewBox: \"0 0 24 24\",\n \"stroke-width\": \"1.5\",\n stroke: \"currentColor\",\n \"aria-hidden\": \"true\",\n \"data-slot\": \"icon\"\n }, [\n _createElementVNode(\"path\", {\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n d: \"m19.5 8.25-7.5 7.5-7.5-7.5\"\n })\n ]))\n}","import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \"vue\"\n\nexport default function render(_ctx, _cache) {\n return (_openBlock(), _createElementBlock(\"svg\", {\n xmlns: \"http://www.w3.org/2000/svg\",\n fill: \"none\",\n viewBox: \"0 0 24 24\",\n \"stroke-width\": \"1.5\",\n stroke: \"currentColor\",\n \"aria-hidden\": \"true\",\n \"data-slot\": \"icon\"\n }, [\n _createElementVNode(\"path\", {\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n d: \"M6 18 18 6M6 6l12 12\"\n })\n ]))\n}","<script setup lang=\"ts\" generic=\"ModelValue, Value extends ModelValue\">\nimport { ChevronDownIcon, XMarkIcon } from '@heroicons/vue/24/outline';\nimport { ActivityIndicator, Pulse } from '@vue-interface/activity-indicator';\nimport type { FormControlEvents, FormControlProps, FormControlSlots } from '@vue-interface/form-control';\nimport { useFormControl } from '@vue-interface/form-control';\nimport { InputField } from '@vue-interface/input-field';\nimport Fuse, { IFuseOptions } from 'fuse.js';\nimport { type HTMLAttributes, computed, nextTick, ref, useTemplateRef, watch, watchEffect } from 'vue';\n\nconst props = withDefaults(defineProps<SearchableSelectFieldProps<ModelValue,Value>>(), {\n formControlClass: 'form-control',\n labelClass: 'form-label',\n size: 'form-control-md',\n clearable: true,\n options: () => []\n});\n\nconst model = defineModel<ModelValue>();\nconst isInteractive = computed(() => !props.disabled && !props.readonly);\n\ndefineSlots<FormControlSlots<SearchableSelectFieldSizePrefix,ModelValue> & {\n default(props: { option: ModelValue; display?: (option: ModelValue) => string }): any;\n}>();\n\nconst emit = defineEmits<FormControlEvents>();\n\nconst {\n controlAttributes,\n formGroupClasses,\n listeners\n} = useFormControl<HTMLAttributes, SearchableSelectFieldSizePrefix, ModelValue|undefined, ModelValue>({ model, props, emit });\n\nwatchEffect(() => {\n if(props.value !== undefined) {\n model.value = props.value;\n }\n});\n\nconst input = ref<string>();\nconst field = useTemplateRef<any>('field');\nconst showOptions = ref(false);\nconst active = ref<number>();\nconst buttons = useTemplateRef<HTMLButtonElement[]>('buttons');\nconst optionsEl = useTemplateRef<HTMLDivElement>('optionsEl');\n\nconst keys = computed(() => {\n return typeof props.options === 'object' && props.options?.[0]\n ? Object.keys(props.options?.[0])\n : ['$'];\n});\n\nlet fuse: Fuse<ModelValue> = createFuse();\n\nfunction createFuse() {\n return new Fuse(props.options ?? [], props.fuseOptions ?? {\n includeScore: true,\n threshold: .45,\n keys: keys.value\n });\n}\n\nconst filtered = computed<ModelValue[]>(() => {\n if(!input.value) {\n return props.options ?? [];\n }\n\n const matches = fuse.search(input.value).map(({ item }) => item);\n\n if(props.allowCustom && !matches.length) {\n return props.options;\n }\n\n return matches;\n});\n\nwatch(() => props.options, () => {\n fuse = createFuse();\n});\n\nfunction scrollIntoView(child?: HTMLElement) {\n const parent = optionsEl.value;\n\n if(!parent || !child) {\n return;\n }\n\n const parentRect = parent.getBoundingClientRect();\n const childRect = child.getBoundingClientRect();\n\n const childTop = childRect.top - parentRect.top + parent.scrollTop;\n\n parent.scrollTop = childTop;\n};\n\nwatch([input, active], ([input, active]) => {\n if(input) {\n buttons.value?.[0]?.scrollIntoView({\n block: 'nearest',\n inline: 'nearest'\n });\n }\n else if(active !== undefined) {\n buttons.value?.[active]?.scrollIntoView({\n block: 'nearest',\n inline: 'nearest'\n });\n }\n});\n\nwatch(optionsEl, (value) => {\n if(!value || active.value === undefined) {\n return;\n }\n\n nextTick(() => {\n scrollIntoView(buttons.value?.[active.value as number]);\n });\n});\n\nfunction select(option?: ModelValue) {\n model.value = option;\n active.value = option && props.options.includes(option)\n ? props.options.indexOf(option)\n : undefined;\n input.value = undefined;\n showOptions.value = false;\n}\n\nfunction onInput(e: Event) {\n if (!isInteractive.value) return;\n\n showOptions.value = true;\n active.value = undefined;\n input.value = (e.target as HTMLInputElement)?.value;\n\n if(!input.value && !props.allowCustom) {\n model.value = undefined;\n }\n\n if(props.allowCustom) {\n model.value = input.value as ModelValue;\n }\n}\n\nfunction onKeypressEnter() {\n if (!isInteractive.value) return;\n\n if(!showOptions.value) {\n showOptions.value = true;\n return;\n }\n\n if(props.allowCustom && active.value === undefined) {\n select(input.value as ModelValue ?? model.value);\n }\n else if(active.value === undefined) {\n select(filtered.value[0]);\n }\n else if(filtered.value[active.value]) {\n select(filtered.value[active.value]);\n }\n else {\n select(undefined);\n }\n}\n\nfunction onKeydownUp() {\n if (!isInteractive.value) return;\n \n showOptions.value = true;\n\n if(!active.value) {\n active.value = filtered.value.length - 1;\n }\n else {\n active.value--;\n }\n}\n\nfunction onKeydownDown() {\n if (!isInteractive.value) return;\n\n showOptions.value = true;\n\n if(active.value === undefined || active.value === filtered.value.length - 1) {\n active.value = 0;\n }\n else {\n active.value++;\n }\n}\n\nfunction onBlur() {\n showOptions.value = false;\n input.value = undefined;\n}\n\nfunction onClickOption(option: ModelValue) {\n if (!isInteractive.value) return;\n select(option);\n}\n\nfunction clear() {\n if (!isInteractive.value) return;\n input.value = undefined;\n model.value = undefined;\n}\n\nfunction toggle() {\n if (!isInteractive.value) return;\n \n if(showOptions.value ) {\n showOptions.value = false;\n }\n else {\n showOptions.value = true;\n field.value?.$el?.querySelector('input')?.focus();\n }\n}\n\nconst canClear = computed(() => {\n return props.clearable && (!!input.value || !!model.value) && isInteractive.value;\n});\n</script>\n<script lang=\"ts\">\nexport type SearchableSelectFieldSizePrefix = 'form-control';\n\nexport type SearchableSelectFieldProps<ModelValue, Value> = FormControlProps<\n HTMLAttributes, \n SearchableSelectFieldSizePrefix, \n ModelValue, \n Value\n> & {\n options?: ModelValue[];\n fuseOptions?: IFuseOptions<ModelValue>;\n display?: (option: ModelValue) => string;\n allowCustom?: boolean;\n clearable?: boolean;\n};\n</script>\n\n\n<template>\n <div class=\"relative [&_.form-control]:pr-8\">\n <InputField\n ref=\"field\"\n class=\"searchable-select-field-input\"\n :class=\"{ 'has-clear-button': canClear, formGroupClasses }\"\n :size=\"size\"\n v-bind=\"{ ...$attrs, controlAttributes, listeners }\"\n :name=\"name\"\n :label=\"label\"\n :model-value=\"input ?? (model && props?.display ? props?.display?.(model) : model)\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :color=\"color\"\n :error=\"error\"\n :errors=\"errors\"\n :feedback=\"feedback\"\n :valid=\"valid\"\n :invalid=\"invalid\"\n @click=\"isInteractive && (showOptions = true)\"\n @focus=\"isInteractive && (showOptions = true)\"\n @blur=\"onBlur\"\n @keypress.enter.prevent=\"onKeypressEnter\"\n @keydown.up.prevent=\"onKeydownUp\"\n @keydown.down.prevent=\"onKeydownDown\"\n @keyup.escape=\"showOptions = false\"\n @input=\"onInput\">\n <template #icon v-if=\"$slots.icon\">\n <slot name=\"icon\" />\n </template>\n <template #activity>\n <slot\n name=\"activity\"\n v-bind=\"{ disabled, options, invalid, valid }\">\n <ActivityIndicator\n v-if=\"!disabled && !options\"\n :type=\"Pulse\"\n size=\"activity-indicator-sm\" />\n <button\n v-else-if=\"canClear\"\n type=\"button\"\n class=\"searchable-select-field-clear-button\"\n @click.stop=\"clear\">\n <XMarkIcon class=\"size-[1.25em]\" />\n </button>\n <button\n v-else-if=\"!invalid && !valid\"\n type=\"button\"\n @click.stop=\"toggle\">\n <ChevronDownIcon class=\"size-[1em]\" />\n </button>\n </slot>\n </template>\n </InputField>\n <div\n v-if=\"showOptions && filtered.length\"\n ref=\"optionsEl\"\n tabindex=\"-1\"\n class=\"searchable-select-field-dropdown\"\n :class=\"size\"\n @mousedown.prevent.stop>\n <button\n v-for=\"(option, i) in filtered\"\n ref=\"buttons\"\n :key=\"i\"\n type=\"button\"\n tabindex=\"-1\"\n :title=\"display?.(option) ?? String(option)\"\n :class=\"{\n ['bg-neutral-200 dark:bg-neutral-700']: active === i\n }\"\n @mousedown.prevent\n @click=\"onClickOption(option)\">\n <slot v-bind=\"{ option, display }\">\n <div class=\"truncate\">\n {{ display?.(option) ?? option }}\n </div>\n </slot>\n </button>\n </div>\n </div>\n</template>"],"names":["render","_ctx","_cache","_openBlock","_createElementBlock","_createElementVNode","props","__props","model","_useModel","isInteractive","computed","emit","__emit","controlAttributes","formGroupClasses","listeners","useFormControl","watchEffect","input","ref","field","useTemplateRef","showOptions","active","buttons","optionsEl","keys","fuse","createFuse","Fuse","filtered","matches","item","watch","scrollIntoView","child","parent","parentRect","childTop","value","nextTick","select","option","onInput","e","onKeypressEnter","onKeydownUp","onKeydownDown","onBlur","onClickOption","clear","toggle","canClear","_hoisted_1","_createVNode","_unref","_mergeProps","$attrs","$event","_renderSlot","_normalizeProps","_guardReactiveProps","_createBlock","ActivityIndicator","Pulse","XMarkIcon","ChevronDownIcon","$slots","_normalizeClass","_Fragment","_renderList","i","_hoisted_3","_toDisplayString"],"mappings":"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","x_google_ignoreList":[0,1]}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { FormControlProps, FormControlSlots } from '@vue-interface/form-control';
|
|
2
2
|
import { IFuseOptions } from 'fuse.js';
|
|
3
|
-
import {
|
|
3
|
+
import { HTMLAttributes } from 'vue';
|
|
4
4
|
export type SearchableSelectFieldSizePrefix = 'form-control';
|
|
5
|
-
export type SearchableSelectFieldProps<ModelValue, Value> = FormControlProps<
|
|
5
|
+
export type SearchableSelectFieldProps<ModelValue, Value> = FormControlProps<HTMLAttributes, SearchableSelectFieldSizePrefix, ModelValue, Value> & {
|
|
6
6
|
options?: ModelValue[];
|
|
7
7
|
fuseOptions?: IFuseOptions<ModelValue>;
|
|
8
8
|
display?: (option: ModelValue) => string;
|
|
9
9
|
allowCustom?: boolean;
|
|
10
10
|
clearable?: boolean;
|
|
11
11
|
};
|
|
12
|
-
declare const _default: <
|
|
12
|
+
declare const _default: <ModelValue, Value extends ModelValue>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
13
13
|
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
14
14
|
readonly onInput?: ((event: Event) => any) | undefined;
|
|
15
15
|
readonly onSelect?: ((event: Event) => any) | undefined;
|
|
@@ -45,9 +45,9 @@ declare const _default: <T, Value>(__VLS_props: NonNullable<Awaited<typeof __VLS
|
|
|
45
45
|
readonly onTouchcancel?: ((event: TouchEvent) => any) | undefined;
|
|
46
46
|
readonly onChange?: ((event: Event) => any) | undefined;
|
|
47
47
|
readonly onBeforeinput?: ((event: Event) => any) | undefined;
|
|
48
|
-
readonly "onUpdate:modelValue"?: ((value:
|
|
48
|
+
readonly "onUpdate:modelValue"?: ((value: ModelValue) => any) | undefined;
|
|
49
49
|
} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onCopy" | "onCut" | "onPaste" | "onFocus" | "onFocusin" | "onFocusout" | "onBlur" | "onChange" | "onBeforeinput" | "onInput" | "onReset" | "onSubmit" | "onInvalid" | "onKeydown" | "onKeypress" | "onKeyup" | "onMousedown" | "onMouseenter" | "onMouseleave" | "onMousemove" | "onMouseout" | "onMouseover" | "onMouseup" | "onSelect" | "onScroll" | "onTouchcancel" | "onTouchend" | "onTouchmove" | "onTouchstart" | "onClick" | "onContextmenu" | "onWheel" | "onDoubleclick" | "onSelectionchange" | "onUpdate:modelValue"> & ({
|
|
50
|
-
modelValue?:
|
|
50
|
+
modelValue?: ModelValue;
|
|
51
51
|
} & {
|
|
52
52
|
activity?: boolean;
|
|
53
53
|
disabled?: boolean;
|
|
@@ -62,7 +62,7 @@ declare const _default: <T, Value>(__VLS_props: NonNullable<Awaited<typeof __VLS
|
|
|
62
62
|
invalid?: boolean;
|
|
63
63
|
label?: string;
|
|
64
64
|
labelClass?: string;
|
|
65
|
-
modelValue?:
|
|
65
|
+
modelValue?: ModelValue | undefined;
|
|
66
66
|
name?: string;
|
|
67
67
|
plaintext?: boolean;
|
|
68
68
|
size?: import('@vue-interface/form-control').FormControlSize<"form-control"> | undefined;
|
|
@@ -70,27 +70,27 @@ declare const _default: <T, Value>(__VLS_props: NonNullable<Awaited<typeof __VLS
|
|
|
70
70
|
readonly?: boolean;
|
|
71
71
|
valid?: boolean;
|
|
72
72
|
value?: Value | undefined;
|
|
73
|
-
} & Omit<
|
|
74
|
-
options?:
|
|
75
|
-
fuseOptions?: IFuseOptions<
|
|
76
|
-
display?: ((option:
|
|
73
|
+
} & Omit<HTMLAttributes, "size"> & {
|
|
74
|
+
options?: ModelValue[] | undefined;
|
|
75
|
+
fuseOptions?: IFuseOptions<ModelValue> | undefined;
|
|
76
|
+
display?: ((option: ModelValue) => string) | undefined;
|
|
77
77
|
allowCustom?: boolean;
|
|
78
78
|
clearable?: boolean;
|
|
79
79
|
}) & Partial<{}>> & import('vue').PublicProps;
|
|
80
80
|
expose(exposed: import('vue').ShallowUnwrapRef<{}>): void;
|
|
81
81
|
attrs: any;
|
|
82
|
-
slots: Readonly<FormControlSlots<"form-control",
|
|
82
|
+
slots: Readonly<FormControlSlots<"form-control", ModelValue> & {
|
|
83
83
|
default(props: {
|
|
84
|
-
option:
|
|
85
|
-
display?: (option:
|
|
84
|
+
option: ModelValue;
|
|
85
|
+
display?: (option: ModelValue) => string;
|
|
86
86
|
}): any;
|
|
87
|
-
}> & FormControlSlots<"form-control",
|
|
87
|
+
}> & FormControlSlots<"form-control", ModelValue> & {
|
|
88
88
|
default(props: {
|
|
89
|
-
option:
|
|
90
|
-
display?: (option:
|
|
89
|
+
option: ModelValue;
|
|
90
|
+
display?: (option: ModelValue) => string;
|
|
91
91
|
}): any;
|
|
92
92
|
};
|
|
93
|
-
emit: (((evt: "input", event: Event) => void) & ((evt: "select", event: Event) => void) & ((evt: "blur", event: FocusEvent) => void) & ((evt: "focus", event: FocusEvent) => void) & ((evt: "focusin", event: FocusEvent) => void) & ((evt: "focusout", event: FocusEvent) => void) & ((evt: "click", event: MouseEvent) => void) & ((evt: "doubleclick", event: MouseEvent) => void) & ((evt: "contextmenu", event: MouseEvent) => void) & ((evt: "mousedown", event: MouseEvent) => void) & ((evt: "mouseup", event: MouseEvent) => void) & ((evt: "mouseover", event: MouseEvent) => void) & ((evt: "mouseout", event: MouseEvent) => void) & ((evt: "mouseenter", event: MouseEvent) => void) & ((evt: "mouseleave", event: MouseEvent) => void) & ((evt: "mousemove", event: MouseEvent) => void) & ((evt: "keydown", event: KeyboardEvent) => void) & ((evt: "keyup", event: KeyboardEvent) => void) & ((evt: "keypress", event: KeyboardEvent) => void) & ((evt: "selectionchange", event: Event) => void) & ((evt: "invalid", event: Event) => void) & ((evt: "submit", event: Event) => void) & ((evt: "reset", event: Event) => void) & ((evt: "scroll", event: Event) => void) & ((evt: "wheel", event: WheelEvent) => void) & ((evt: "copy", event: ClipboardEvent) => void) & ((evt: "cut", event: ClipboardEvent) => void) & ((evt: "paste", event: ClipboardEvent) => void) & ((evt: "touchstart", event: TouchEvent) => void) & ((evt: "touchend", event: TouchEvent) => void) & ((evt: "touchmove", event: TouchEvent) => void) & ((evt: "touchcancel", event: TouchEvent) => void) & ((evt: "change", event: Event) => void) & ((evt: "beforeinput", event: Event) => void)) & ((evt: "update:modelValue", value:
|
|
93
|
+
emit: (((evt: "input", event: Event) => void) & ((evt: "select", event: Event) => void) & ((evt: "blur", event: FocusEvent) => void) & ((evt: "focus", event: FocusEvent) => void) & ((evt: "focusin", event: FocusEvent) => void) & ((evt: "focusout", event: FocusEvent) => void) & ((evt: "click", event: MouseEvent) => void) & ((evt: "doubleclick", event: MouseEvent) => void) & ((evt: "contextmenu", event: MouseEvent) => void) & ((evt: "mousedown", event: MouseEvent) => void) & ((evt: "mouseup", event: MouseEvent) => void) & ((evt: "mouseover", event: MouseEvent) => void) & ((evt: "mouseout", event: MouseEvent) => void) & ((evt: "mouseenter", event: MouseEvent) => void) & ((evt: "mouseleave", event: MouseEvent) => void) & ((evt: "mousemove", event: MouseEvent) => void) & ((evt: "keydown", event: KeyboardEvent) => void) & ((evt: "keyup", event: KeyboardEvent) => void) & ((evt: "keypress", event: KeyboardEvent) => void) & ((evt: "selectionchange", event: Event) => void) & ((evt: "invalid", event: Event) => void) & ((evt: "submit", event: Event) => void) & ((evt: "reset", event: Event) => void) & ((evt: "scroll", event: Event) => void) & ((evt: "wheel", event: WheelEvent) => void) & ((evt: "copy", event: ClipboardEvent) => void) & ((evt: "cut", event: ClipboardEvent) => void) & ((evt: "paste", event: ClipboardEvent) => void) & ((evt: "touchstart", event: TouchEvent) => void) & ((evt: "touchend", event: TouchEvent) => void) & ((evt: "touchmove", event: TouchEvent) => void) & ((evt: "touchcancel", event: TouchEvent) => void) & ((evt: "change", event: Event) => void) & ((evt: "beforeinput", event: Event) => void)) & ((evt: "update:modelValue", value: ModelValue) => void);
|
|
94
94
|
}>) => import('vue').VNode & {
|
|
95
95
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
96
96
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vue-interface/searchable-select-field",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "A Vue searchable select field component.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/searchable-select-field.umd.cjs",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"fuse.js": "^7.1.0",
|
|
49
49
|
"vue": "^3.3.4",
|
|
50
50
|
"@vue-interface/activity-indicator": "3.0.8",
|
|
51
|
-
"@vue-interface/form-control": "2.0.
|
|
52
|
-
"@vue-interface/input-field": "3.0.
|
|
51
|
+
"@vue-interface/form-control": "2.0.23",
|
|
52
|
+
"@vue-interface/input-field": "3.0.4",
|
|
53
53
|
"@vue-interface/sizeable": "2.0.0"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<script setup lang="ts" generic="
|
|
1
|
+
<script setup lang="ts" generic="ModelValue, Value extends ModelValue">
|
|
2
2
|
import { ChevronDownIcon, XMarkIcon } from '@heroicons/vue/24/outline';
|
|
3
3
|
import { ActivityIndicator, Pulse } from '@vue-interface/activity-indicator';
|
|
4
4
|
import type { FormControlEvents, FormControlProps, FormControlSlots } from '@vue-interface/form-control';
|
|
5
5
|
import { useFormControl } from '@vue-interface/form-control';
|
|
6
6
|
import { InputField } from '@vue-interface/input-field';
|
|
7
7
|
import Fuse, { IFuseOptions } from 'fuse.js';
|
|
8
|
-
import {
|
|
8
|
+
import { type HTMLAttributes, computed, nextTick, ref, useTemplateRef, watch, watchEffect } from 'vue';
|
|
9
9
|
|
|
10
|
-
const props = withDefaults(defineProps<SearchableSelectFieldProps<
|
|
10
|
+
const props = withDefaults(defineProps<SearchableSelectFieldProps<ModelValue,Value>>(), {
|
|
11
11
|
formControlClass: 'form-control',
|
|
12
12
|
labelClass: 'form-label',
|
|
13
13
|
size: 'form-control-md',
|
|
@@ -15,11 +15,11 @@ const props = withDefaults(defineProps<SearchableSelectFieldProps<T,Value>>(), {
|
|
|
15
15
|
options: () => []
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
const model = defineModel<
|
|
18
|
+
const model = defineModel<ModelValue>();
|
|
19
19
|
const isInteractive = computed(() => !props.disabled && !props.readonly);
|
|
20
20
|
|
|
21
|
-
defineSlots<FormControlSlots<SearchableSelectFieldSizePrefix,
|
|
22
|
-
default(props: { option:
|
|
21
|
+
defineSlots<FormControlSlots<SearchableSelectFieldSizePrefix,ModelValue> & {
|
|
22
|
+
default(props: { option: ModelValue; display?: (option: ModelValue) => string }): any;
|
|
23
23
|
}>();
|
|
24
24
|
|
|
25
25
|
const emit = defineEmits<FormControlEvents>();
|
|
@@ -28,7 +28,7 @@ const {
|
|
|
28
28
|
controlAttributes,
|
|
29
29
|
formGroupClasses,
|
|
30
30
|
listeners
|
|
31
|
-
} = useFormControl<
|
|
31
|
+
} = useFormControl<HTMLAttributes, SearchableSelectFieldSizePrefix, ModelValue|undefined, ModelValue>({ model, props, emit });
|
|
32
32
|
|
|
33
33
|
watchEffect(() => {
|
|
34
34
|
if(props.value !== undefined) {
|
|
@@ -49,7 +49,7 @@ const keys = computed(() => {
|
|
|
49
49
|
: ['$'];
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
let fuse: Fuse<
|
|
52
|
+
let fuse: Fuse<ModelValue> = createFuse();
|
|
53
53
|
|
|
54
54
|
function createFuse() {
|
|
55
55
|
return new Fuse(props.options ?? [], props.fuseOptions ?? {
|
|
@@ -59,7 +59,7 @@ function createFuse() {
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const filtered = computed<
|
|
62
|
+
const filtered = computed<ModelValue[]>(() => {
|
|
63
63
|
if(!input.value) {
|
|
64
64
|
return props.options ?? [];
|
|
65
65
|
}
|
|
@@ -117,7 +117,7 @@ watch(optionsEl, (value) => {
|
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
function select(option?:
|
|
120
|
+
function select(option?: ModelValue) {
|
|
121
121
|
model.value = option;
|
|
122
122
|
active.value = option && props.options.includes(option)
|
|
123
123
|
? props.options.indexOf(option)
|
|
@@ -138,7 +138,7 @@ function onInput(e: Event) {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
if(props.allowCustom) {
|
|
141
|
-
model.value = input.value as
|
|
141
|
+
model.value = input.value as ModelValue;
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -151,7 +151,7 @@ function onKeypressEnter() {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
if(props.allowCustom && active.value === undefined) {
|
|
154
|
-
select(input.value as
|
|
154
|
+
select(input.value as ModelValue ?? model.value);
|
|
155
155
|
}
|
|
156
156
|
else if(active.value === undefined) {
|
|
157
157
|
select(filtered.value[0]);
|
|
@@ -195,7 +195,7 @@ function onBlur() {
|
|
|
195
195
|
input.value = undefined;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
function onClickOption(option:
|
|
198
|
+
function onClickOption(option: ModelValue) {
|
|
199
199
|
if (!isInteractive.value) return;
|
|
200
200
|
select(option);
|
|
201
201
|
}
|
|
@@ -226,7 +226,7 @@ const canClear = computed(() => {
|
|
|
226
226
|
export type SearchableSelectFieldSizePrefix = 'form-control';
|
|
227
227
|
|
|
228
228
|
export type SearchableSelectFieldProps<ModelValue, Value> = FormControlProps<
|
|
229
|
-
|
|
229
|
+
HTMLAttributes,
|
|
230
230
|
SearchableSelectFieldSizePrefix,
|
|
231
231
|
ModelValue,
|
|
232
232
|
Value
|