UNPKG

vira

Version:

A simple and highly versatile design system using element-vir.

94 lines (93 loc) 3.71 kB
import { assert } from '@augment-vir/assert'; import { LocalStorageClient } from '@electrovir/local-storage-client'; import { defineShape, enumShape } from 'object-shape-tester'; import { applyColorThemeViaStyleElement } from 'theme-vir/dist/color-theme/apply-color-theme.js'; import { listenTo } from 'typed-event-target'; import { viraTheme, viraThemeDarkOverride } from '../styles/vira-color-theme.js'; /** * A user-facing selection of which theme to display. `Auto` follows the system color scheme; the * other values force a specific theme regardless of system preference. * * @category Internal */ export var ViraThemeSelection; (function (ViraThemeSelection) { ViraThemeSelection["Light"] = "light"; ViraThemeSelection["Dark"] = "dark"; ViraThemeSelection["Auto"] = "auto"; })(ViraThemeSelection || (ViraThemeSelection = {})); /** * Default implementation of {@link ApplyThemeCallback}, which simply applies Vira themes. * * @category Internal */ export const defaultApplyThemeCallback = ({ useDarkTheme }) => { applyColorThemeViaStyleElement(viraTheme, useDarkTheme ? viraThemeDarkOverride : undefined); }; const darkSchemeMediaQuery = '(prefers-color-scheme: dark)'; const themeStorageShapes = { selectedTheme: defineShape({ theme: enumShape(ViraThemeSelection), }), }; /** * Tracks the user's {@link ViraThemeSelection} and bridges it to a consumer-supplied `applyTheme` * callback. Persists the selection in LocalStorage via an internal `LocalStorageClient`, and * listens for system color-scheme changes to re-apply the theme in auto mode without persisting. * * The initial theme is applied during construction. * * @category Util */ export class ViraThemeClient { /** The callback that will be called to apply a new theme. */ applyThemeCallback = defaultApplyThemeCallback; /** Contains the user's last selected theme, saving and loading it to disk for persistence. */ localStorageClient; /** A callback to remove the global theme preference listener. */ removeThemePreferenceListener = listenTo(globalThis.matchMedia(darkSchemeMediaQuery), 'change', (event) => { assert.instanceOf(event, MediaQueryListEvent); if (this.currentTheme === ViraThemeSelection.Auto) { void this.applyThemeCallback({ useDarkTheme: event.matches, }); } }); constructor(params = {}) { if (params.applyTheme) { this.applyThemeCallback = params.applyTheme; } this.localStorageClient = new LocalStorageClient(themeStorageShapes, { storeName: params.storeName || 'vira-theme', }); this.applySelection(this.currentTheme); } /** * The currently selected theme. If you use multiple clients to set the same theme, this might * get out of sync. */ get currentTheme() { return this.localStorageClient.get.selectedTheme()?.theme || ViraThemeSelection.Auto; } /** Set the selected theme. */ setSelectedTheme(selection) { this.applySelection(selection); this.localStorageClient.set.selectedTheme({ theme: selection, }); } /** Cleanup internal state and listeners. */ destroy() { this.removeThemePreferenceListener(); this.localStorageClient.destroy(); } /** Apply the currently selected theme. */ applySelection(selection) { const useDarkTheme = selection === ViraThemeSelection.Dark || (selection === ViraThemeSelection.Auto && globalThis.matchMedia(darkSchemeMediaQuery).matches); void this.applyThemeCallback({ useDarkTheme, }); } }