57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
export type Theme = 'dark' | 'light'
|
|
|
|
const STORAGE_KEY = 'viewer.theme'
|
|
const listeners = new Set<(theme: Theme) => void>()
|
|
|
|
export function initTheme(): Theme {
|
|
const isAndroidShell =
|
|
typeof (window as unknown as { AndroidViewer?: unknown }).AndroidViewer !== 'undefined'
|
|
let theme: Theme = 'dark'
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY)
|
|
if (stored === 'light' || stored === 'dark') {
|
|
theme = stored
|
|
} else if (!isAndroidShell && window.matchMedia?.('(prefers-color-scheme: light)').matches) {
|
|
theme = 'light'
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
applyTheme(theme, false)
|
|
return theme
|
|
}
|
|
|
|
export function getTheme(): Theme {
|
|
return document.documentElement.dataset.theme === 'light' ? 'light' : 'dark'
|
|
}
|
|
|
|
export function setTheme(theme: Theme): void {
|
|
applyTheme(theme, true)
|
|
}
|
|
|
|
export function toggleTheme(): Theme {
|
|
const next: Theme = getTheme() === 'dark' ? 'light' : 'dark'
|
|
setTheme(next)
|
|
return next
|
|
}
|
|
|
|
function applyTheme(theme: Theme, persist: boolean): void {
|
|
document.documentElement.dataset.theme = theme
|
|
document.documentElement.style.colorScheme = theme
|
|
const meta = document.querySelector('meta[name="theme-color"]')
|
|
if (meta) meta.setAttribute('content', theme === 'dark' ? '#0b0e14' : '#f2f5fa')
|
|
if (persist) {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, theme)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
for (const listener of listeners) listener(theme)
|
|
}
|
|
|
|
export function onThemeChange(listener: (theme: Theme) => void): () => void {
|
|
listeners.add(listener)
|
|
return () => listeners.delete(listener)
|
|
}
|