import { getGlobalStates } from '@wener/utils'; import { createStore, useStore } from 'zustand'; import { mutative } from 'zustand-mutative'; export interface UserAgentPreferences { colorTheme: 'light' | 'dark'; contrast: 'no-preference' | 'more' | 'less' | 'custom'; reducedMotion: 'no-preference' | 'reduce'; reducedData: 'no-preference' | 'reduce'; reducedTransparency: 'no-preference' | 'reduce'; devicePixelRatio: number; // zooming, screen resolution } export function getUserAgentPreferenceStore() { return getGlobalStates('UserAgentPreferenceStore', () => { return createUserAgentPreferenceStore(); }); } export function getUserAgentPreferences(): UserAgentPreferences { return getUserAgentPreferenceStore().getState(); } export function useUserAgentPreferences() { return useStore(getUserAgentPreferenceStore()); } function createUserAgentPreferenceStore({ watch = true }: { watch?: boolean } = {}) { return createStore( mutative< UserAgentPreferences & { close: () => void; reset: () => void; } >((setState, getState, store) => { let initial: UserAgentPreferences = { colorTheme: 'light', contrast: 'no-preference', reducedMotion: 'no-preference', reducedData: 'no-preference', reducedTransparency: 'no-preference', devicePixelRatio: globalThis.devicePixelRatio ?? 1, }; const closer: (() => void)[] = []; let reset: () => void = () => undefined; if (typeof window === 'undefined' || typeof window.matchMedia === 'undefined') { } else { let resolution = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); let prefersColorScheme = window.matchMedia('(prefers-color-scheme: dark)'); let prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); let prefersReducedData = window.matchMedia('(prefers-reduced-data: reduce)'); let prefersReducedTransparency = window.matchMedia('(prefers-reduced-transparency: reduce)'); let prefersContrast = window.matchMedia('(prefers-contrast: more)'); let set = (out: UserAgentPreferences) => { out.colorTheme = prefersColorScheme.matches ? 'dark' : 'light'; out.reducedMotion = prefersReducedMotion.matches ? 'reduce' : 'no-preference'; out.reducedData = prefersReducedData.matches ? 'reduce' : 'no-preference'; out.reducedTransparency = prefersReducedTransparency.matches ? 'reduce' : 'no-preference'; out.contrast = prefersContrast.matches ? 'more' : 'no-preference'; out.devicePixelRatio = globalThis.devicePixelRatio ?? 1; }; // initial set(initial); const allQuery = [ prefersColorScheme, prefersReducedMotion, prefersReducedData, prefersReducedTransparency, prefersContrast, resolution, ]; const onChange = () => { setState((s) => { set(s); return s; }); }; reset = onChange; if (watch) { for (let query of allQuery) { query.addEventListener('change', onChange); closer.push(() => query.removeEventListener('change', onChange)); } } } return { ...initial, close: () => { for (let c of closer) { c(); } closer.splice(0, closer.length); }, reset, }; }), ); }