UNPKG

@oruga-ui/oruga-next

Version:

UI components for Vue.js and CSS framework agnostic

1 lines 19.3 kB
{"version":3,"file":"taginput.cjs","sources":["../../src/components/taginput/Taginput.vue","../../src/components/taginput/index.ts"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T = string\">\nimport {\n computed,\n useAttrs,\n useTemplateRef,\n useId,\n watchEffect,\n ref,\n type Component,\n} from \"vue\";\n\nimport OAutocomplete from \"../autocomplete/Autocomplete.vue\";\nimport OTag from \"../tag/Tag.vue\";\n\nimport { getDefault } from \"@/utils/config\";\nimport {\n defineClasses,\n getActiveClasses,\n normalizeOptions,\n findOption,\n useInputHandler,\n useSequentialId,\n toOptionsGroup,\n type OptionsGroupItem,\n} from \"@/composables\";\n\nimport type { TaginputProps } from \"./props\";\n\n/**\n * A simple tag input field that can have autocomplete functionality.\n * @displayName Taginput\n * @style _taginput.scss\n */\ndefineOptions({\n isOruga: true,\n name: \"OTaginput\",\n configField: \"taginput\",\n inheritAttrs: false,\n});\n\ntype ModelValue = TaginputProps<T>[\"modelValue\"];\n\nconst props = withDefaults(defineProps<TaginputProps<T>>(), {\n override: undefined,\n modelValue: undefined,\n input: \"\",\n options: undefined,\n filter: undefined,\n size: () => getDefault(\"taginput.size\"),\n variant: () => getDefault(\"taginput.variant\"),\n maxitems: undefined,\n maxlength: undefined,\n counter: () => getDefault(\"taginput.counter\", true),\n openOnFocus: () => getDefault(\"taginput.openOnFocus\", true),\n keepOpen: () => getDefault(\"taginput.keepOpen\", false),\n placeholder: undefined,\n expanded: false,\n disabled: false,\n keepFirst: () => getDefault(\"taginput.keepFirst\", false),\n allowNew: () => getDefault(\"taginput.allowNew\", false),\n allowDuplicates: () => getDefault(\"taginput.allowDuplicates\", false),\n validateItem: () => true,\n createItem: (item: T | string) => item as T,\n checkScroll: () => getDefault(\"taginput.checkScroll\", false),\n closeable: () => getDefault(\"taginput.closeable\", true),\n iconPack: () => getDefault(\"taginput.iconPack\"),\n icon: () => getDefault(\"taginput.icon\"),\n closeIcon: () => getDefault(\"taginput.closeIcon\", \"close\"),\n ariaCloseLabel: () => getDefault(\"taginput.ariaCloseLabel\", \"Remove\"),\n autocomplete: () => getDefault(\"taginput.autocomplete\", \"off\"),\n useHtml5Validation: () => getDefault(\"useHtml5Validation\", true),\n customValidity: undefined,\n teleport: () => getDefault(\"taginput.teleport\", false),\n autocompleteClasses: () => getDefault(\"taginput.autocompleteClasses\", {}),\n});\n\nconst emits = defineEmits<{\n /**\n * modelValue prop two-way binding\n * @param value {string[] | number[] | object[]} updated modelValue prop\n */\n \"update:model-value\": [value: ModelValue[]];\n /**\n * input prop two-way binding\n * @param value {string} updated input prop\n */\n \"update:input\": [value: string];\n /**\n * on input change event\n * @param value {string} input value\n * @param event {Event} native event\n */\n input: [value: string, event: Event];\n /**\n * new item got added\n * @param value {string | number | object} added item\n */\n add: [value: T];\n /**\n * item got removed\n * @param value {string | number | object} removed item\n */\n remove: [value: T];\n /**\n * on input focus event\n * @param event {Event} native event\n */\n focus: [event: Event];\n /**\n * on input blur event\n * @param event {Event} native event\n */\n blur: [event: Event];\n /**\n * on input invalid event\n * @param event {Event} native event\n */\n invalid: [event: Event];\n /**\n * on icon click event\n * @param event {Event} native event\n */\n \"icon-click\": [event: Event];\n /**\n * on icon right click event\n * @param event {Event} native event\n */\n \"icon-right-click\": [event: Event];\n /** the list inside the dropdown reached the start */\n \"scroll-start\": [];\n /** the list inside the dropdown reached it's end */\n \"scroll-end\": [];\n}>();\n\n// define as Component to prevent docs memmory overload\nconst autocompleteRef = useTemplateRef<Component>(\"autocompleteComponent\");\n\n// use form input functionalities\nconst { checkHtml5Validity, setFocus, onFocus, onBlur, onInvalid } =\n useInputHandler(autocompleteRef, emits, props);\n\nconst isDropdownActive = ref(false);\n\n// the selected items, use v-model to make it two-way binding\nconst selectedItems = defineModel<ModelValue>({ default: undefined });\n\n// the value of the inner input, use v-model:input to make it two-way binding\nconst inputValue = defineModel<string>(\"input\", { default: \"\" });\n\nconst inputLength = computed(() => inputValue.value.trim().length);\nconst itemsLength = computed(() => selectedItems.value?.length || 0);\n\n// create a unique id sequence\nconst { nextSequence } = useSequentialId();\n\n/** normalized programamtic options */\nconst groupedOptions = computed<OptionsGroupItem<T>[]>(() => {\n const normalizedOptions = normalizeOptions<T>(props.options, nextSequence);\n const groupedOptions = toOptionsGroup<T>(normalizedOptions, nextSequence());\n return groupedOptions;\n});\n\n/** map the selected items into option items */\nconst selectedOptions = computed(() => {\n if (!selectedItems.value) return [];\n return selectedItems.value.map((value) => {\n const option = findOption<T>(groupedOptions, value);\n // return the found option or create a new option object\n if (option) return option;\n else return { label: String(value), value, key: useId() };\n });\n});\n\n/** show the input field if a maxitems hasn't been set or reached */\nconst hasInput = computed(\n () => props.maxitems == null || itemsLength.value < Number(props.maxitems),\n);\n\nwatchEffect(() => {\n // blur if input is empty\n if (!hasInput.value) onBlur(new Event(\"blur\"));\n});\n\nfunction addItem(item?: T | string): void {\n item = item || inputValue.value.trim();\n\n if (item) {\n const itemToAdd = props.createItem(item);\n\n if (!selectedItems.value?.length) {\n // Add the item input if not items are set yet\n if (props.validateItem(item)) {\n selectedItems.value = [itemToAdd];\n emits(\"add\", itemToAdd);\n }\n } else {\n // Add the item input if it is not blank\n // or previously added (if not allowDuplicates).\n const add = !props.allowDuplicates\n ? !selectedItems.value.includes(itemToAdd)\n : true;\n\n if (add && props.validateItem(item)) {\n selectedItems.value = [...selectedItems.value, itemToAdd];\n emits(\"add\", itemToAdd);\n }\n }\n }\n\n // after autocomplete events\n requestAnimationFrame(() => {\n inputValue.value = \"\";\n emits(\"input\", \"\", new Event(\"input\"));\n });\n}\n\nfunction removeItem(index: number, event?: Event): void {\n if (!selectedItems.value?.length) return;\n const item = selectedItems.value.at(index);\n if (!item) return;\n selectedItems.value = selectedItems.value.toSpliced(index, 1);\n emits(\"remove\", item);\n if (event) event.stopPropagation();\n if (props.openOnFocus && autocompleteRef.value) setFocus();\n}\n\n// --- Event Handler ---\n\nfunction onSelect(option: T | undefined): void {\n if (!option) return;\n addItem(option);\n}\n\nfunction onInput(value: string, event: Event): void {\n emits(\"input\", value?.trim(), event);\n}\n\nfunction onBackspace(): void {\n if (!inputValue.value?.length && itemsLength.value > 0)\n // remove last item\n removeItem(itemsLength.value - 1);\n}\n\nfunction onEnter(): void {\n // Add item if not select only and dropdown selection is closed\n if (props.allowNew && !isDropdownActive.value) addItem();\n}\n\n// --- Computed Component Classes ---\n\nconst rootClasses = defineClasses(\n [\"rootClass\", \"o-taginput\"],\n [\n \"sizeClass\",\n \"o-taginput--\",\n computed(() => props.size),\n computed(() => !!props.size),\n ],\n [\n \"variantClass\",\n \"o-taginput--\",\n computed(() => props.variant),\n computed(() => !!props.variant),\n ],\n [\n \"expandedClass\",\n \"o-taginput--expanded\",\n null,\n computed(() => props.expanded),\n ],\n [\n \"disabledClass\",\n \"o-taginput--disabled\",\n null,\n computed(() => props.disabled),\n ],\n);\n\nconst containerClasses = defineClasses([\n \"containerClass\",\n \"o-taginput__container\",\n]);\n\nconst itemClasses = defineClasses(\n [\"itemClass\", \"o-taginput__item\"],\n [\n \"variantClass\",\n \"o-taginput__item--\",\n computed(() => props.variant),\n computed(() => !!props.variant),\n ],\n);\n\nconst counterClasses = defineClasses([\"counterClass\", \"o-taginput__counter\"]);\n\nconst autocompleteRootClasses = defineClasses([\n \"autocompleteClasses.rootClass\",\n \"o-taginput__autocomplete\",\n]);\n\nconst autocompleteInputClasses = defineClasses([\n \"autocompleteClasses.inputClasses.inputClass\",\n \"o-taginput__input\",\n]);\n\nconst attrs = useAttrs();\n\nconst autocompleteBind = computed(() => ({\n ...attrs,\n \"root-class\": getActiveClasses(autocompleteRootClasses),\n \"input-classes\": {\n \"input-class\": getActiveClasses(autocompleteInputClasses),\n },\n ...props.autocompleteClasses,\n}));\n\n// --- Expose Public Functionalities ---\n\n/** expose functionalities for programmatic usage */\ndefineExpose({ checkHtml5Validity, focus: setFocus, value: selectedItems });\n</script>\n\n<template>\n <div data-oruga=\"taginput\" :class=\"rootClasses\">\n <div :class=\"containerClasses\" @focus=\"onFocus\" @blur=\"onBlur\">\n <!--\n @slot Override selected items\n @binding {(string, object)[]} items - selected items\n @binding {object[]} options - selected options\n @binding {(index, event): void} removeItem - remove item function\n -->\n <slot\n name=\"selected\"\n :items=\"selectedItems\"\n :options=\"selectedOptions\"\n :remove-item=\"removeItem\">\n <o-tag\n v-for=\"(option, index) in selectedOptions\"\n :key=\"option.key\"\n :label=\"option.label\"\n :class=\"itemClasses\"\n :closeable=\"closeable && !disabled\"\n :close-icon=\"closeIcon\"\n :close-icon-pack=\"iconPack\"\n :aria-close-label=\"ariaCloseLabel\"\n @close=\"removeItem(index, $event)\" />\n </slot>\n\n <o-autocomplete\n v-show=\"hasInput\"\n ref=\"autocompleteComponent\"\n v-model:active=\"isDropdownActive\"\n v-model:input=\"inputValue\"\n v-bind=\"autocompleteBind\"\n :options=\"options\"\n :filter=\"filter\"\n :placeholder=\"placeholder\"\n :icon=\"icon\"\n :icon-pack=\"iconPack\"\n :maxlength=\"maxlength\"\n :size=\"size\"\n :disabled=\"disabled\"\n :autocomplete=\"autocomplete\"\n :open-on-focus=\"openOnFocus\"\n :keep-first=\"keepFirst\"\n :keep-open=\"keepOpen\"\n :check-scroll=\"checkScroll\"\n :teleport=\"teleport\"\n :has-counter=\"false\"\n :use-html5-validation=\"false\"\n expanded\n @input=\"onInput\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n @invalid=\"onInvalid\"\n @keydown.enter=\"onEnter\"\n @keydown.tab=\"onEnter\"\n @keydown.backspace=\"onBackspace\"\n @select=\"onSelect\"\n @scroll-start=\"$emit('scroll-start')\"\n @scroll-end=\"$emit('scroll-end')\"\n @icon-click=\"$emit('icon-click', $event)\"\n @icon-right-click=\"$emit('icon-right-click', $event)\">\n <template v-if=\"$slots.header\" #header>\n <!--\n @slot Define an additional header\n -->\n <slot name=\"header\" />\n </template>\n\n <template\n v-if=\"$slots.default\"\n #default=\"{ option, index, value }\">\n <!--\n @slot Override the select option\n @binding {object} option - option object\n @binding {number} index - option index\n @binding {unknown} value - option value\n -->\n <slot :option=\"option\" :index=\"index\" :value=\"value\" />\n </template>\n\n <template v-if=\"$slots.empty\" #empty>\n <!--\n @slot Define content for empty state \n -->\n <slot name=\"empty\" />\n </template>\n\n <template v-if=\"$slots.footer\" #footer>\n <!--\n @slot Define an additional footer\n -->\n <slot name=\"footer\" />\n </template>\n </o-autocomplete>\n </div>\n\n <small\n v-if=\"counter && (maxitems || maxlength)\"\n :class=\"counterClasses\">\n <template v-if=\"maxlength && inputLength > 0\">\n <!--\n @slot Override the counter\n @binding {number} items - items count\n @binding {number} total - total count\n -->\n <slot name=\"counter\" :items=\"inputLength\" :total=\"maxlength\">\n {{ inputLength }} / {{ maxlength }}\n </slot>\n </template>\n\n <template v-else-if=\"maxitems\">\n <!--\n @slot Override the counter\n @binding {number} items - items count\n @binding {number} total - total count\n -->\n <slot name=\"counter\" :items=\"itemsLength\" :total=\"maxitems\">\n {{ itemsLength }} / {{ maxitems }}\n </slot>\n </template>\n </small>\n </div>\n</template>\n","import type { App, Plugin } from \"vue\";\n\nimport Taginput from \"./Taginput.vue\";\n\nimport { registerComponent } from \"@/utils/plugins\";\n\n/** export taginput plugin */\nexport default {\n install(Vue: App) {\n registerComponent(Vue, Taginput);\n },\n} as Plugin;\n\n/** export taginput components */\nexport { Taginput as OTaginput };\n"],"names":["useTemplateRef","useInputHandler","ref","_useModel","computed","useSequentialId","normalizeOptions","groupedOptions","toOptionsGroup","findOption","useId","watchEffect","index","defineClasses","useAttrs","getActiveClasses","registerComponent","Taginput"],"mappings":"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"}