127 lines
4.5 KiB
JavaScript
127 lines
4.5 KiB
JavaScript
// 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;
|
|
}
|
|
}
|
|
}
|