UNPKG

@senka-ai/ui

Version:

A modern, type-safe Svelte 5 UI component library with full theme support, accessibility standards, and robust state management patterns

92 lines (91 loc) 2.87 kB
let currentTheme = 'light'; let initialized = false; let listeners = []; function getInitialTheme() { if (typeof window === 'undefined') return 'light'; const savedTheme = localStorage.getItem('theme'); if (savedTheme && (savedTheme === 'light' || savedTheme === 'dark')) { return savedTheme; } return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } // Initialize theme on first access function ensureInitialized() { if (!initialized && typeof window !== 'undefined') { currentTheme = getInitialTheme(); initialized = true; applyThemeToDOM(); setupSystemThemeListener(); } } // Apply theme to DOM function applyThemeToDOM() { if (typeof window !== 'undefined') { document.documentElement.setAttribute('data-theme', currentTheme); } } // Setup system theme change listener function setupSystemThemeListener() { if (typeof window !== 'undefined') { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleSystemThemeChange = (e) => { // Only auto-change if user hasn't manually set a preference if (!localStorage.getItem('theme')) { setTheme(e.matches ? 'dark' : 'light'); } }; mediaQuery.addEventListener('change', handleSystemThemeChange); } } // Notify listeners of theme change function notifyListeners() { listeners.forEach((listener) => listener(currentTheme)); } // Public API export function getTheme() { ensureInitialized(); return currentTheme; } export function setTheme(newTheme) { if (newTheme === 'light' || newTheme === 'dark') { ensureInitialized(); currentTheme = newTheme; if (typeof window !== 'undefined') { localStorage.setItem('theme', currentTheme); applyThemeToDOM(); } notifyListeners(); } } export function toggleTheme() { setTheme(currentTheme === 'light' ? 'dark' : 'light'); } // For reactive access in components - returns a reactive state export function useTheme() { ensureInitialized(); // Create reactive state inside the component context const theme = $state({ current: currentTheme }); // Subscribe to changes const unsubscribe = subscribe((newTheme) => { theme.current = newTheme; }); // Return reactive getter and cleanup return { get current() { return theme.current; }, destroy: unsubscribe, }; } // Subscribe to theme changes export function subscribe(listener) { listeners.push(listener); listener(currentTheme); // Call immediately with current value return () => { const index = listeners.indexOf(listener); if (index > -1) { listeners.splice(index, 1); } }; }