/** * Controls Module * * Handles generic UI controls functionality: * - Toggle sliders (Ja/Nej switches) * - Select dropdowns */ /** * Controller for generic UI controls */ export class ControlsController { constructor() { this.initToggleSliders(); this.initSelectDropdowns(); } /** * Initialize all toggle sliders on the page * Toggle slider: Ja/Nej button switch with data-value attribute * Clicking anywhere on the slider toggles the value */ private initToggleSliders(): void { document.querySelectorAll('swp-toggle-slider').forEach(slider => { slider.addEventListener('click', () => { const el = slider as HTMLElement; const newValue = el.dataset.value === 'yes' ? 'no' : 'yes'; el.dataset.value = newValue; // Dispatch custom event for listeners slider.dispatchEvent(new CustomEvent('toggle', { bubbles: true, detail: { value: newValue } })); }); }); } /** * Initialize all select dropdowns on the page */ private initSelectDropdowns(): void { document.querySelectorAll('swp-select').forEach(select => { const trigger = select.querySelector('button'); const dropdown = select.querySelector('swp-select-dropdown'); const options = select.querySelectorAll('swp-select-option'); if (!trigger || !dropdown) return; // Toggle dropdown on button click trigger.addEventListener('click', (e) => { e.stopPropagation(); const isOpen = select.classList.toggle('open'); trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); }); // Handle option selection options.forEach(option => { option.addEventListener('click', () => { const value = (option as HTMLElement).dataset.value; const label = option.textContent?.trim() || ''; // Update selected state options.forEach(o => o.classList.remove('selected')); option.classList.add('selected'); // Update trigger display const valueEl = trigger.querySelector('swp-select-value'); if (valueEl) { valueEl.textContent = label; } // Update color dot if present (for color pickers) const triggerDot = trigger.querySelector('swp-color-dot'); if (triggerDot && value) { triggerDot.className = `is-${value}`; } // Update data-value on select element (select as HTMLElement).dataset.value = value; // Close dropdown select.classList.remove('open'); trigger.setAttribute('aria-expanded', 'false'); // Dispatch custom event select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value, label } })); }); }); }); // Click outside to close any open dropdown document.addEventListener('click', () => { this.closeAllSelects(); }); // Keyboard navigation document.addEventListener('keydown', (e) => { const openSelect = document.querySelector('swp-select.open'); if (!openSelect) return; // Escape to close if (e.key === 'Escape') { this.closeAllSelects(); return; } // Letter key to jump to option if (e.key.length === 1 && /[a-zA-ZæøåÆØÅ]/.test(e.key)) { const options = openSelect.querySelectorAll('swp-select-option'); const letter = e.key.toLowerCase(); for (const option of options) { const text = option.textContent?.trim().toLowerCase() || ''; if (text.startsWith(letter)) { // Scroll into view and highlight option.scrollIntoView({ block: 'nearest' }); options.forEach(o => o.classList.remove('highlighted')); option.classList.add('highlighted'); break; } } } // Enter to select highlighted option if (e.key === 'Enter') { const highlighted = openSelect.querySelector('swp-select-option.highlighted') as HTMLElement; if (highlighted) { highlighted.click(); } } }); } private closeAllSelects(): void { document.querySelectorAll('swp-select.open').forEach(select => { select.classList.remove('open'); select.querySelector('button')?.setAttribute('aria-expanded', 'false'); }); } }