// Filename: frontend/js/components/customSelect.js export default class CustomSelect { constructor(container) { this.container = container; this.trigger = this.container.querySelector('.custom-select-trigger'); this.panel = this.container.querySelector('.custom-select-panel'); if (!this.trigger || !this.panel) { console.warn('CustomSelect cannot initialize: missing .custom-select-trigger or .custom-select-panel.', this.container); return; } this.nativeSelect = this.container.querySelector('select'); this.triggerText = this.trigger.querySelector('span'); this.template = this.panel.querySelector('.custom-select-option-template'); if (typeof CustomSelect.openInstance === 'undefined') { CustomSelect.openInstance = null; CustomSelect.initGlobalListener(); } if (this.nativeSelect) { this.generateOptions(); this.updateTriggerText(); } this.bindEvents(); } static initGlobalListener() { document.addEventListener('click', (event) => { if (CustomSelect.openInstance && !CustomSelect.openInstance.container.contains(event.target)) { CustomSelect.openInstance.close(); } }); } generateOptions() { this.panel.querySelectorAll(':scope > *:not(.custom-select-option-template)').forEach(child => child.remove()); Array.from(this.nativeSelect.options).forEach(option => { let item; if (this.template) { item = this.template.cloneNode(true); item.classList.remove('custom-select-option-template'); item.removeAttribute('hidden'); } else { item = document.createElement('a'); item.href = '#'; item.className = 'block px-4 py-2 text-sm text-zinc-700 hover:bg-zinc-100 dark:text-zinc-200 dark:hover:bg-zinc-600'; } item.classList.add('custom-select-option'); item.textContent = option.textContent; item.dataset.value = option.value; if (option.selected) { item.classList.add('is-selected'); } this.panel.appendChild(item); }); } bindEvents() { this.trigger.addEventListener('click', (event) => { // [NEW] Guard clause: If the trigger is functionally disabled, do nothing. if (this.trigger.classList.contains('is-disabled')) { return; } event.stopPropagation(); if (CustomSelect.openInstance && CustomSelect.openInstance !== this) { CustomSelect.openInstance.close(); } this.toggle(); }); if (this.nativeSelect) { this.panel.addEventListener('click', (event) => { event.preventDefault(); const option = event.target.closest('.custom-select-option'); if (option) { this.selectOption(option); } }); } } selectOption(optionEl) { const selectedValue = optionEl.dataset.value; if (this.nativeSelect.value !== selectedValue) { this.nativeSelect.value = selectedValue; this.nativeSelect.dispatchEvent(new Event('change', { bubbles: true })); } this.updateTriggerText(); this.panel.querySelectorAll('.custom-select-option').forEach(el => el.classList.remove('is-selected')); optionEl.classList.add('is-selected'); this.close(); } updateTriggerText() { // [IMPROVEMENT] Guard against missing elements. if (!this.nativeSelect || !this.triggerText) return; const selectedOption = this.nativeSelect.options[this.nativeSelect.selectedIndex]; if (selectedOption) { this.triggerText.textContent = selectedOption.textContent; } } toggle() { this.panel.classList.toggle('hidden'); if (this.panel.classList.contains('hidden')) { if (CustomSelect.openInstance === this) { CustomSelect.openInstance = null; } } else { CustomSelect.openInstance = this; } } open() { this.panel.classList.remove('hidden'); CustomSelect.openInstance = this; } close() { this.panel.classList.add('hidden'); if (CustomSelect.openInstance === this) { CustomSelect.openInstance = null; } } }