@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
JavaScript
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);
}
};
}