// ============================================================================ // Stylescape | Toggle Switch Manager // ============================================================================ // Manages toggle switch UI elements with state persistence. // Supports data-ss-toggle attributes for declarative configuration. // ============================================================================ /** * Configuration options for ToggleSwitchManager */ export interface ToggleSwitchOptions { /** Initial state */ checked?: boolean; /** Storage key for persistence */ storageKey?: string; /** Use localStorage for persistence */ persist?: boolean; /** Callback when state changes */ onChange?: (isOn: boolean, element: HTMLInputElement) => void; /** CSS class for on state */ onClass?: string; /** CSS class for off state */ offClass?: string; /** Label for on state */ onLabel?: string; /** Label for off state */ offLabel?: string; } /** * Toggle switch manager with state persistence and callbacks. * * @example JavaScript * ```typescript * const toggle = new ToggleSwitchManager("#darkMode", { * persist: true, * storageKey: "dark-mode", * onChange: (isOn) => document.body.classList.toggle("dark", isOn) * }) * ``` * * @example HTML with data-ss * ```html * * * ``` */ export class ToggleSwitchManager { private element: HTMLInputElement | null; private options: Required; private labelElement: HTMLElement | null = null; constructor( selectorOrElement: string | HTMLInputElement, options: ToggleSwitchOptions = {}, ) { this.element = typeof selectorOrElement === "string" ? document.querySelector(selectorOrElement) : selectorOrElement; this.options = { checked: options.checked ?? false, storageKey: options.storageKey ?? this.element?.id ?? "toggle-state", persist: options.persist ?? false, onChange: options.onChange ?? (() => {}), onClass: options.onClass ?? "toggle--on", offClass: options.offClass ?? "toggle--off", onLabel: options.onLabel ?? "", offLabel: options.offLabel ?? "", }; if (!this.element) { console.warn("[Stylescape] ToggleSwitchManager element not found"); return; } this.init(); } // ======================================================================== // Public Properties // ======================================================================== /** * Get current state */ get isOn(): boolean { return this.element?.checked ?? false; } /** * Set current state */ set isOn(value: boolean) { if (!this.element) return; this.element.checked = value; this.handleChange(); } // ======================================================================== // Public Methods // ======================================================================== /** * Toggle the switch */ public toggle(): void { this.isOn = !this.isOn; } /** * Turn on */ public on(): void { this.isOn = true; } /** * Turn off */ public off(): void { this.isOn = false; } /** * Destroy the manager */ public destroy(): void { this.element?.removeEventListener("change", this.handleChange); this.element = null; } // ======================================================================== // Private Methods // ======================================================================== private init(): void { if (!this.element) return; // Load persisted state if (this.options.persist) { const stored = localStorage.getItem(this.options.storageKey); if (stored !== null) { this.element.checked = stored === "true"; } else { this.element.checked = this.options.checked; } } else { this.element.checked = this.options.checked; } // Find associated label if (this.element.id) { this.labelElement = document.querySelector( `label[for="${this.element.id}"]`, ); } // Set ARIA attributes this.element.setAttribute("role", "switch"); this.updateAriaState(); // Add event listener this.element.addEventListener("change", this.handleChange); // Initial state update this.updateUI(); } private handleChange = (): void => { if (!this.element) return; // Persist state if (this.options.persist) { localStorage.setItem( this.options.storageKey, String(this.element.checked), ); } this.updateUI(); this.updateAriaState(); this.options.onChange(this.element.checked, this.element); }; private updateUI(): void { if (!this.element) return; const parent = this.element.parentElement; // Update classes on parent wrapper parent?.classList.toggle(this.options.onClass, this.element.checked); parent?.classList.toggle(this.options.offClass, !this.element.checked); // Update label text if ( this.labelElement && (this.options.onLabel || this.options.offLabel) ) { const label = this.element.checked ? this.options.onLabel : this.options.offLabel; if (label) { this.labelElement.textContent = label; } } } private updateAriaState(): void { this.element?.setAttribute( "aria-checked", String(this.element?.checked ?? false), ); } } export default ToggleSwitchManager;