Refactors select dropdown functionality to use custom implementation Adds color dot support for color selection Improves keyboard navigation and interaction for select dropdowns Modernizes UI components with more flexible and interactive controls
144 lines
4.4 KiB
TypeScript
144 lines
4.4 KiB
TypeScript
/**
|
|
* 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');
|
|
});
|
|
}
|
|
}
|