<template> <div :id="`custom-dropdown${identifier}`" :class="[ 'ui search selection dropdown', { 'active visible': isOpen }, { disabled }, ]" @click="toggleDropdown" > <input v-if="search" v-model="input" @input="isOpen = true" v-on:keyup.enter="select(0)" v-on:keyup.esc="toggleDropdown(false)" class="search" autocomplete="off" tabindex="0" :placeholder="placehold" ref="input" /> <div v-if="!input" class="default text"> {{ selected }} </div> <i :class="['dropdown icon', { clear: clearable && selected }]" @click="clear" ></i> <div :class="['menu', { 'visible transition': isOpen }]"> <div v-for="(option, index) in processedOptions || ['No results found.']" @click="select(index)" :key="option + index" :class="[ processedOptions ? 'item' : 'message', { 'active selected': option.name === selected }, ]" > <span v-if="option.name"> {{ option.name }} </span> <span v-else> {{ option }} </span> </div> </div> </div> </template> <script> export default { name: "Dropdown", props: [ "options", "selected", "disabled", "search", "placeholder", "clearable", ], computed: { processedOptions: function () { let options = this.options; if (this.search && this.input !== "") { options = this.searchOptions(this.options); } return options.length > 0 ? options : null; }, placehold() { return this.input ? "" : this.placeholder; }, }, data() { return { isOpen: false, input: "", identifier: 0, }; }, methods: { toggleDropdown(val) { if (this.isOpen) { this.input = ""; // * clear input field when closing dropdown } else if (this.search) { //* focus on input if is a search dropdown this.$refs.input.focus({ 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); this.$emit("update:selection", this.processedOptions[index]); this.input = ""; }, searchOptions(options) { return options.filter((el) => el.name ? el.name.toLowerCase().includes(this.input.toLowerCase()) : el.toLowerCase().includes(this.input.toLowerCase()) ); }, 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.toggleDropdown(false); }, }, created() { this.identifier = Math.floor(Math.random() * 10000); window.addEventListener("mousedown", this.clickOutsideDropdown); }, beforeDestroy() { window.removeEventListener("mousedown", this.clickOutsideDropdown); }, }; </script> <style scoped> .ui.selection.dropdown .menu > .item { white-space: nowrap; } </style>