import { effect, signal } from '@ui-arch/frame'; import type { ThemeHandle, ThemeMode, ThemeOptions } from './types.js'; /*** 主题:把「URL 参数 > 本地记忆 > 跟随系统」的优先级收进一处,写 html[data-theme] 换整包令牌;接入方的二十行样板(读参数 / 读记忆 / 听系统 / 落盘)只剩一行调用 ***/ export function theme(options: ThemeOptions = {}): ThemeHandle { // —— 状态集中声明 —— const key = options.storageKey ?? 'ua-theme'; // 本地记忆键 const param = options.param ?? 'theme'; // URL 参数名,值 light / dark 时生效(iframe 联动入口) const darkName = options.dark ?? 'dark'; // 写进 data-theme 的值 const media = typeof matchMedia === 'function' ? matchMedia('(prefers-color-scheme: dark)') : null; const systemDark = signal(media?.matches ?? false); // 系统偏好落成信号,mode='system' 时它才是真相 const mode = signal(options.initial ?? readEnv(param, key)); // 初始选择:环境优先,兜底 system const dark = signal(resolveDark(mode(), systemDark())); // 实际生效是否暗色(写给出品方直接绑控件) /*** 系统偏好变化:只在跟随系统时影响视觉,显式选过的不被系统改回去 ***/ const onSystem = (e: MediaQueryListEvent): void => { systemDark(e.matches); }; media?.addEventListener('change', onSystem); effect(() => { const now = resolveDark(mode(), systemDark()); // 读两个依赖:选择变了或系统偏好变了都要重算 dark(now); document.documentElement.dataset.theme = now ? darkName : ''; // 亮色靠 :root 缺省,清掉属性即可 if (mode() !== 'system') localStorage.setItem(key, mode()); // 只记显式选择:跟随系统不该被冻成某一档 }); /*** 切到指定档:'system' 交还给系统偏好,其余落盘后刷新时仍生效 ***/ const set = (m: ThemeMode): void => { mode(m); }; /*** 明暗互换:控件只有一个开关时用它,system 态下按当前生效值反着切 ***/ const toggle = (): void => { mode(dark() ? 'light' : 'dark'); }; /*** 摘监听:单页应用卸载前调用,避免重复实例各挂一份 media 监听 ***/ const dispose = (): void => { media?.removeEventListener('change', onSystem); }; return { mode, dark, set, toggle, dispose }; } /*** 环境里的初值:URL 参数(联动入口)> 本地记忆 > 跟随系统 ***/ function readEnv(param: string, key: string): ThemeMode { const fromUrl = new URLSearchParams(location.search).get(param); if (fromUrl === 'dark' || fromUrl === 'light') return fromUrl; // 卫语句:参数是合法档位才认 const stored = localStorage.getItem(key); return stored === 'dark' || stored === 'light' ? stored : 'system'; // 记住的选择优先于系统偏好 } /*** 选择 + 系统偏好 → 是否暗色:显式档直接给答案,system 档才看系统 ***/ function resolveDark(mode: ThemeMode, systemDark: boolean): boolean { return mode === 'system' ? systemDark : mode === 'dark'; } /*** 读当前生效值(不建实例):只要一个「现在是不是暗色」的场景用得上,比如按主题切图表配色 ***/ export function currentThemeDark(darkName = 'dark'): boolean { return document.documentElement.dataset.theme === darkName; }