<template> <div :id="`custom-dropdown${identifier}`" :class="[ 'ui search selection dropdown', { 'active visible': isOpen }, { disabled }, ]" @click="toggleDropdown" > <input v-if="search" ref="input" v-model="input" class="search" autocomplete="off" tabindex="0" :placeholder="placehold" @input="isOpen = true" @keyup.enter="select(0)" @keyup.esc="toggleDropdown(false)" > <div v-if="!input" class="default text" > <div v-if="Array.isArray(selected)"> <span v-if="selected[0]"> {{ selected[0] }} - </span> <span class="italic">{{ selected[1] }}</span> </div> <div v-else> {{ selected }} </div> </div> <i :class="['dropdown icon', { clear: clearable && selected }]" aria-hidden="true" @click="clear" /> <div :class="['menu', { 'visible transition': isOpen }]"> <div v-for="(option, index) in filteredOptions || ['No results found.']" :id="option.name && Array.isArray(option.name) ? option.name[0] : option.name || option" :key="option + index" :class="[ filteredOptions ? 'item' : 'message', { 'active selected': option.name === selected }, ]" @click="select(index)" > <div v-if="option.name && Array.isArray(option.name)"> <span v-if="option.name[0]"> {{ option.name[0] }} - </span> <span class="italic">{{ option.name[1] }}</span> </div> <span v-else-if="option.name"> {{ option.name }} </span> <span v-else> {{ option }} </span> </div> </div> </div> </template> <script> export default { name: 'Dropdown', props: { clearable: { type: Boolean, default: null, }, disabled: { type: Boolean, default: null, }, options: { type: Array, default: null, }, placeholder: { type: String, default: null, }, selected: { type: [String, Array], default: null, }, search: { type: Boolean, default: null, }, }, data() { return { isOpen: false, input: '', identifier: 0, }; }, computed: { filteredOptions: function () { let options = this.options; if (this.search && this.input !== '') { options = this.options.filter(this.matchInput); } return options.length > 0 ? options : null; }, placehold() { return this.input ? '' : this.placeholder; }, }, created() { const crypto = window.crypto || window.msCrypto; const array = new Uint32Array(1); this.identifier = Math.floor(crypto.getRandomValues(array) * 10000); window.addEventListener('mousedown', this.clickOutsideDropdown); }, beforeDestroy() { window.removeEventListener('mousedown', this.clickOutsideDropdown); }, methods: { toggleDropdown(val) { if (this.isOpen) { //* if dropdown is open : this.input = ''; // * -> clear input field when closing dropdown } else if (this.search) { //* if dropdown is closed is a search dropdown: this.$refs.input.focus({ //* -> focus on input field preventScroll: true, }); } else if (this.clearable && val.target && this.selected) { this.clear(); //* clear selected and input } this.isOpen = typeof val === 'boolean' ? val : !this.isOpen; }, select(index) { // * toggle dropdown is called several time, timeout delay this function to be the last setTimeout(() => { this.isOpen = false; }, 0); if (this.filteredOptions) { this.$emit('update:selection', this.filteredOptions[index]); } this.input = ''; }, matchInput(el) { let match; if (el.name && Array.isArray(el.name)) { match = el.name[0].toLowerCase().includes(this.input.toLowerCase()) || el.name[1].toLowerCase().includes(this.input.toLowerCase()); } else { match = el.name ? el.name.toLowerCase().includes(this.input.toLowerCase()) : el.toLowerCase().includes(this.input.toLowerCase()); } return match; }, clear() { if (this.clearable) { this.input = ''; this.$emit('update:selection', ''); if (this.isOpen) { this.toggleDropdown(false); } } }, clickOutsideDropdown(e) { if (!e.target.closest(`#custom-dropdown${this.identifier}`) && this.isOpen) { this.toggleDropdown(false); } }, }, }; </script> <style scoped> .ui.selection.dropdown .menu > .item { white-space: nowrap; } .italic { font-style: italic; } </style>