Vue.component('tt-select', { props: { options: {type: Array, required: true}, label: {type: String, default: null}, required: {type: Boolean, default: false}, value: {type: [String, Number, Array], default: null}, disabled: {type: Boolean, default: false}, suffix: {type: String, default: null}, sm: {type: Boolean, default: false}, row: {type: Boolean, default: false}, multiple: {type: Boolean, default: false}, searchable: {type: Boolean, default: true} }, data() { return { open: false, searchQuery: '', selected: this.multiple ? (Array.isArray(this.value) ? [...this.value] : []) : this.value }; }, watch: { value(newVal) { this.selected = this.multiple ? (Array.isArray(newVal) ? [...newVal] : []) : newVal; }, open(isOpen) { if (!isOpen) { this.searchQuery = ''; // Reset search on close return; } this.$nextTick(() => { if (this.searchable && this.$refs.searchInput) { this.$refs.searchInput.focus(); } if (this.$refs.optionsList) { this.$refs.optionsList.scrollTop = 0; } }); } }, mounted() { document.addEventListener('click', this.onClickOutside); document.addEventListener('tt-select-open', this.handleGlobalOpen); }, beforeDestroy() { document.removeEventListener('click', this.onClickOutside); document.removeEventListener('tt-select-open', this.handleGlobalOpen); }, computed: { normalizedOptions() { return this.options.map(opt => (opt !== null && typeof opt === 'object') ? {value: opt.value, text: opt.text, disabled: !!opt.disabled} : {value: opt, text: String(opt), disabled: false} ); }, filteredOptions() { const displayLimit = 100; if (!this.searchable || !this.searchQuery) { return this.normalizedOptions.slice(0, displayLimit); } const q = this.searchQuery.toLowerCase(); return this.normalizedOptions .filter(option => option.text.toLowerCase().includes(q)) .slice(0, displayLimit); }, displayText() { const addSuffix = text => text ? `${text}${this.suffix ? ` ${this.suffix}` : ''}` : ''; if (!this.multiple) { const opt = this.normalizedOptions.find(o => o.value === this.selected); return opt ? addSuffix(opt.text) : 'Auswählen...'; } const count = Array.isArray(this.selected) ? this.selected.length : 0; if (count === 0) return 'Auswählen...'; if (count === 1) { const opt = this.normalizedOptions.find(o => o.value === this.selected[0]); return opt ? addSuffix(opt.text) : ''; } return `${count} ausgewählt`; }, hasSelection() { if (this.multiple) { return Array.isArray(this.selected) && this.selected.length > 0; } return this.selected !== null && this.selected !== undefined && this.selected !== ''; } }, methods: { toggleDropdown() { if (this.disabled) return; if (!this.open) { this.$emit('focus'); // Dispatch event that this select is opening document.dispatchEvent(new CustomEvent('tt-select-open', { detail: { id: this._uid } })); } this.open = !this.open; }, onClickOutside(e) { if (this.$el && !this.$el.contains(e.target)) { this.open = false; } }, handleGlobalOpen(event) { if (event.detail.id !== this._uid) { this.open = false; } }, isSelected(opt) { return this.multiple ? Array.isArray(this.selected) && this.selected.includes(opt.value) : this.selected === opt.value; }, selectOption(opt) { if (opt.disabled) return; if (this.multiple) { const newSelection = Array.isArray(this.selected) ? [...this.selected] : []; const index = newSelection.indexOf(opt.value); if (index > -1) { newSelection.splice(index, 1); } else { newSelection.push(opt.value); } this.selected = newSelection; } else { this.selected = opt.value; this.open = false; // Close on selection for single mode } this.$emit('input', this.selected); }, handleEnter() { if (this.searchQuery && this.filteredOptions.length === 1) { this.selectOption(this.filteredOptions[0]); if (!this.multiple) this.open = false; } } }, template: `