@dazl/color-scheme
Version:
Color scheme utilities and tools
90 lines (89 loc) • 2.94 kB
JavaScript
function initiateColorScheme({ saveConfig, loadConfig, cssClass }) {
const state = {
listeners: new Set(),
config: loadConfig(),
};
const isDarkQuery = window.matchMedia('(prefers-color-scheme: dark)');
const resolveSystem = () => (isDarkQuery.matches ? 'dark' : 'light');
const onSystemChange = () => {
if (state.config !== 'system')
return;
updateDocument();
};
const currentState = () => {
const config = state.config;
const resolved = config === 'system' ? resolveSystem() : config;
return { config, resolved };
};
const updateDocument = () => {
const current = currentState();
const root = document.documentElement;
root.classList.remove(cssClass.light, cssClass.dark);
root.classList.add(cssClass[current.resolved]);
root.style.colorScheme = current.resolved === 'dark' ? 'dark' : 'light';
state.listeners.forEach((listener) => listener(current));
};
// set initial color scheme and listen for system changes
updateDocument();
isDarkQuery.addEventListener('change', onSystemChange);
return {
get config() {
return state.config;
},
set config(config) {
if (config === state.config)
return;
state.config = config;
updateDocument();
saveConfig(config);
},
get currentState() {
return currentState();
},
get resolvedSystem() {
return resolveSystem();
},
getRootCssClass(resolved = currentState().resolved) {
return cssClass[resolved];
},
subscribe: (sub) => {
state.listeners.add(sub);
return () => {
state.listeners.delete(sub);
};
},
dispose: () => {
state.listeners.clear();
isDarkQuery.removeEventListener('change', onSystemChange);
},
};
}
const storageKey = 'color-scheme';
const scriptDataset = document.currentScript?.dataset;
const darkCssClass = scriptDataset?.darkClass || 'dark-theme';
const lightCssClass = scriptDataset?.lightClass || 'light-theme';
window.colorSchemeApi ??= initiateColorScheme({
loadConfig() {
try {
const config = localStorage.getItem(storageKey);
return config === 'light' || config === 'dark' ? config : 'system';
}
catch {
return 'system';
}
},
saveConfig(config) {
try {
if (config === 'system') {
localStorage.removeItem(storageKey);
}
else {
localStorage.setItem(storageKey, config);
}
}
catch {
// Fallback to no-op if localStorage is not available
}
},
cssClass: { light: lightCssClass, dark: darkCssClass },
});