63 lines
2.0 KiB
JavaScript
63 lines
2.0 KiB
JavaScript
// Dark / light theme, interchangeable with web_template's lt.theme (devdesk#31):
|
|||
|
|
// same localStorage key (lt_theme), same data-theme attribute on <html>, the OS
|
||
|
|
// preference while nothing is saved, and tabs follow each other via `storage`.
|
||
|
|
// The inline script in index.template.html applies it before first paint.
|
||
|
|
|
||
|
|
const KEY = 'lt_theme';
|
||
|
|
const prefersLight = window.matchMedia('(prefers-color-scheme: light)');
|
||
|
|
const listeners = new Set();
|
||
|
|
|
||
|
|
function saved() {
|
||
|
|
try {
|
||
|
|
const value = localStorage.getItem(KEY);
|
||
|
|
return value === 'light' || value === 'dark' ? value : null;
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getTheme() {
|
||
|
|
return document.documentElement.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
|
||
|
|
}
|
||
|
|
|
||
|
|
function apply(theme) {
|
||
|
|
const root = document.documentElement;
|
||
|
|
root.setAttribute('data-theme', theme);
|
||
|
|
root.style.colorScheme = theme;
|
||
|
|
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', theme === 'light' ? '#edf0f5' : '#030508');
|
||
|
|
const next = theme === 'light' ? 'dark' : 'light';
|
||
|
|
for (const button of document.querySelectorAll('.lt-theme-btn')) {
|
||
|
|
button.textContent = theme === 'light' ? '◐' : '☀';
|
||
|
|
button.setAttribute('aria-label', `Switch to ${next} mode`);
|
||
|
|
button.title = `Switch to ${next} mode`;
|
||
|
|
}
|
||
|
|
for (const listener of listeners) listener(theme);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function setTheme(theme) {
|
||
|
|
try {
|
||
|
|
localStorage.setItem(KEY, theme);
|
||
|
|
} catch {
|
||
|
|
// Private mode: the choice lasts until the tab closes.
|
||
|
|
}
|
||
|
|
apply(theme);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function toggleTheme() {
|
||
|
|
setTheme(getTheme() === 'light' ? 'dark' : 'light');
|
||
|
|
}
|
||
|
|
|
||
|
|
export function onThemeChange(listener) {
|
||
|
|
listeners.add(listener);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function initTheme() {
|
||
|
|
apply(saved() ?? (prefersLight.matches ? 'light' : 'dark'));
|
||
|
|
prefersLight.addEventListener('change', (event) => {
|
||
|
|
if (!saved()) apply(event.matches ? 'light' : 'dark');
|
||
|
|
});
|
||
|
|
window.addEventListener('storage', (event) => {
|
||
|
|
if (event.key === KEY) apply(saved() ?? (prefersLight.matches ? 'light' : 'dark'));
|
||
|
|
});
|
||
|
|
}
|