// ============================================================================
// 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;