New
This commit is contained in:
126
frontend/js/components/customSelect.js
Normal file
126
frontend/js/components/customSelect.js
Normal file
@@ -0,0 +1,126 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user