import type { CoreService, ThemeService } from '../core/tokens'
import { eventBus } from '../core/event-bus'
/**
* Theme module — color scheme switching and theme-color meta tag management.
*
* Responsibilities:
* - Toggle between light, dark, and auto color schemes.
* - Update `` based on current scheme.
* - Persist user preference to localStorage.
*/
export class ThemeModule implements ThemeService {
private readonly mql = window.matchMedia('(prefers-color-scheme: dark)')
constructor(private readonly core: CoreService) {}
/**
* Apply a theme mode and emit the `fixit:switch-theme` event.
* @param mode - `'auto'`, `'light'`, or `'dark'`.
* @param persist - Whether to save the choice to localStorage (default: `true`).
*/
setThemeMode(mode: string, persist = true) {
const prevIsDark = this.core.isDark
this.core.themeMode = mode
document.documentElement.dataset.themeMode = mode
this.core.isDark = mode === 'auto' ? this.mql.matches : mode === 'dark'
if (persist) {
window.localStorage?.setItem('theme-mode', mode)
}
eventBus.emit('fixit:switch-theme', {
isDark: this.core.isDark,
mode,
isChanged: prevIsDark !== this.core.isDark,
})
}
/** Sync the `` tag with the current color scheme. */
initThemeColor() {
const $meta = document.querySelector('[name="theme-color"]')
if (!$meta)
return
const applyThemeColor = (isDark: boolean) => {
$meta.content = isDark ? $meta.dataset.dark! : $meta.dataset.light!
}
eventBus.on('fixit:switch-theme', ({ detail }) => {
if (!detail.isChanged)
return
applyThemeColor(detail.isDark)
})
applyThemeColor(this.core.isDark)
}
/** Initialize the theme switch button cycle and system preference listener. */
initSwitchTheme() {
const modes = ['auto', 'light', 'dark'] as const
document.querySelectorAll('.theme-switch').forEach(($themeSwitch: Element) => {
$themeSwitch.addEventListener('click', () => {
const currentIndex = modes.indexOf(this.core.themeMode as typeof modes[number])
const nextMode = modes[(currentIndex + 1) % modes.length]
this.setThemeMode(nextMode)
}, false)
})
this.mql.addEventListener('change', (e: MediaQueryListEvent) => {
if (this.core.themeMode !== 'auto')
return
const prevIsDark = this.core.isDark
this.core.isDark = e.matches
eventBus.emit('fixit:switch-theme', {
isDark: this.core.isDark,
mode: 'auto',
isChanged: prevIsDark !== this.core.isDark,
})
})
}
/** Initialize theme color and switch handler. */
setup() {
this.initThemeColor()
this.initSwitchTheme()
}
}