/** * MetonaToast Themes - 主题管理 * @module themes * @version 2.0.0 * @description 主题系统、自定义主题和主题切换 */ import { THEMES } from './constants.js'; import { prefersDark } from './utils.js'; // 当前主题状态 let currentTheme = 'auto'; let themeListeners = new Set(); let systemThemeMediaQuery = null; let systemThemeListener = null; /** * 获取系统主题 * @returns {string} 系统主题 ('light' 或 'dark') */ export const getSystemTheme = () => { if (typeof window === 'undefined') return 'light'; return prefersDark() ? 'dark' : 'light'; }; /** * 获取主题(别名) * @param {string} theme - 主题名称 * @returns {string} 解析后的主题 */ export const getTheme = (theme) => { return resolveTheme(theme); }; /** * 解析主题 * @param {string} theme - 主题名称 * @returns {string} 解析后的主题 */ export const resolveTheme = (theme) => { if (theme === 'auto') { // 优先使用用户设置的全局主题,否则使用系统主题 if (currentTheme && currentTheme !== 'auto') { return currentTheme; } return getSystemTheme(); } return theme; }; /** * 获取主题配置 * @param {string} theme - 主题名称 * @returns {Object} 主题配置 */ export const getThemeConfig = (theme) => { const resolved = resolveTheme(theme); return THEMES[resolved] || THEMES.light; }; /** * 应用主题 * @param {string} theme - 主题名称 */ export const applyTheme = (theme) => { currentTheme = theme; const resolved = resolveTheme(theme); // 应用主题到文档 if (typeof document !== 'undefined') { document.documentElement.setAttribute('data-met-theme', resolved); document.documentElement.classList.remove('met-theme-light', 'met-theme-dark', 'met-theme-auto'); document.documentElement.classList.add(`met-theme-${resolved}`); // 设置CSS变量 const config = getThemeConfig(theme); setThemeVariables(config); } // 通知监听器 notifyThemeListeners(theme, resolved); }; /** * 设置主题CSS变量 * @param {Object} config - 主题配置 */ export const setThemeVariables = (config) => { if (typeof document === 'undefined') return; const root = document.documentElement; root.style.setProperty('--met-bg', config.bg); root.style.setProperty('--met-text', config.text); root.style.setProperty('--met-border', config.border); root.style.setProperty('--met-shadow', config.shadow); root.style.setProperty('--met-hover-shadow', config.hoverShadow); root.style.setProperty('--met-progress-bg', config.progressBg); root.style.setProperty('--met-close-hover-bg', config.closeHoverBg); // 设置颜色变量 root.style.setProperty('--met-success', '#10b981'); root.style.setProperty('--met-error', '#ef4444'); root.style.setProperty('--met-warning', '#f59e0b'); root.style.setProperty('--met-info', '#3b82f6'); root.style.setProperty('--met-loading', '#6366f1'); }; /** * 清除主题CSS变量 */ export const clearThemeVariables = () => { if (typeof document === 'undefined') return; const root = document.documentElement; const variables = [ '--met-bg', '--met-text', '--met-border', '--met-shadow', '--met-hover-shadow', '--met-progress-bg', '--met-close-hover-bg', '--met-success', '--met-error', '--met-warning', '--met-info', '--met-loading', ]; variables.forEach((variable) => { root.style.removeProperty(variable); }); }; /** * 获取当前主题 * @returns {string} 当前主题 */ export const getCurrentTheme = () => { return currentTheme; }; /** * 获取解析后的主题 * @returns {string} 解析后的主题 */ export const getResolvedTheme = () => { return resolveTheme(currentTheme); }; /** * 切换主题 * @param {string} theme - 新主题 */ export const switchTheme = (theme) => { applyTheme(theme); saveTheme(theme); }; /** * 切换亮色/暗色主题 */ export const toggleTheme = () => { const resolved = getResolvedTheme(); const newTheme = resolved === 'dark' ? 'light' : 'dark'; switchTheme(newTheme); }; /** * 重置为自动主题 */ export const resetToAuto = () => { switchTheme('auto'); }; /** * 保存主题到本地存储 * @param {string} theme - 主题名称 */ export const saveTheme = (theme) => { if (typeof localStorage !== 'undefined') { try { localStorage.setItem('metona-toast-theme', theme); } catch (e) { console.warn('Failed to save theme:', e); } } }; /** * 从本地存储加载主题 * @returns {string} 主题名称 */ export const loadTheme = () => { if (typeof localStorage !== 'undefined') { try { return localStorage.getItem('metona-toast-theme') || 'auto'; } catch (e) { console.warn('Failed to load theme:', e); } } return 'auto'; }; /** * 初始化主题系统 */ export const initTheme = () => { // 加载保存的主题 const savedTheme = loadTheme(); applyTheme(savedTheme); // 监听系统主题变化 watchSystemTheme(); }; /** * 监听系统主题变化 */ export const watchSystemTheme = () => { if (typeof window === 'undefined' || !window.matchMedia) return; // 移除旧的监听器 if (systemThemeMediaQuery && systemThemeListener) { systemThemeMediaQuery.removeEventListener('change', systemThemeListener); } // 创建新的监听器 systemThemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); systemThemeListener = (e) => { if (currentTheme === 'auto') { applyTheme('auto'); } }; systemThemeMediaQuery.addEventListener('change', systemThemeListener); }; /** * 停止监听系统主题变化 */ export const unwatchSystemTheme = () => { if (systemThemeMediaQuery && systemThemeListener) { systemThemeMediaQuery.removeEventListener('change', systemThemeListener); systemThemeMediaQuery = null; systemThemeListener = null; } }; /** * 添加主题监听器 * @param {Function} listener - 监听器函数 * @returns {Function} 移除监听器函数 */ export const addThemeListener = (listener) => { themeListeners.add(listener); return () => { themeListeners.delete(listener); }; }; /** * 移除主题监听器 * @param {Function} listener - 监听器函数 */ export const removeThemeListener = (listener) => { themeListeners.delete(listener); }; /** * 通知主题监听器 * @param {string} theme - 主题名称 * @param {string} resolved - 解析后的主题 */ const notifyThemeListeners = (theme, resolved) => { themeListeners.forEach((listener) => { try { listener(theme, resolved); } catch (e) { console.error('Theme listener error:', e); } }); }; /** * 清除所有主题监听器 */ export const clearThemeListeners = () => { themeListeners.clear(); }; /** * 注册自定义主题 * @param {string} name - 主题名称 * @param {Object} config - 主题配置 */ export const registerTheme = (name, config) => { THEMES[name] = { bg: config.bg || THEMES.light.bg, text: config.text || THEMES.light.text, border: config.border || THEMES.light.border, shadow: config.shadow || THEMES.light.shadow, hoverShadow: config.hoverShadow || THEMES.light.hoverShadow, progressBg: config.progressBg || THEMES.light.progressBg, closeHoverBg: config.closeHoverBg || THEMES.light.closeHoverBg, }; }; /** * 注销自定义主题 * @param {string} name - 主题名称 */ export const unregisterTheme = (name) => { if (name === 'light' || name === 'dark' || name === 'auto') { console.warn('Cannot unregister built-in theme:', name); return; } delete THEMES[name]; }; /** * 获取所有主题 * @returns {Object} 主题映射 */ export const getAllThemes = () => { return { ...THEMES }; }; /** * 获取主题名称列表 * @returns {Array} 主题名称数组 */ export const getThemeNames = () => { return Object.keys(THEMES); }; /** * 检查主题是否存在 * @param {string} name - 主题名称 * @returns {boolean} 是否存在 */ export const hasTheme = (name) => { return name in THEMES; }; /** * 获取主题预览 * @param {string} theme - 主题名称 * @returns {Object} 主题预览 */ export const getThemePreview = (theme) => { const config = getThemeConfig(theme); const resolved = resolveTheme(theme); return { name: theme, resolved, colors: { background: config.bg, text: config.text, border: config.border, }, isDark: resolved === 'dark', isLight: resolved === 'light', isAuto: theme === 'auto', }; }; /** * 生成主题CSS * @param {string} theme - 主题名称 * @returns {string} CSS字符串 */ export const generateThemeCSS = (theme) => { const config = getThemeConfig(theme); const resolved = resolveTheme(theme); return ` .met-theme-${resolved} { --met-bg: ${config.bg}; --met-text: ${config.text}; --met-border: ${config.border}; --met-shadow: ${config.shadow}; --met-hover-shadow: ${config.hoverShadow}; --met-progress-bg: ${config.progressBg}; --met-close-hover-bg: ${config.closeHoverBg}; } `; }; /** * 应用主题CSS * @param {string} theme - 主题名称 */ export const applyThemeCSS = (theme) => { if (typeof document === 'undefined') return; const css = generateThemeCSS(theme); const styleId = 'metona-toast-theme-css'; // 移除旧样式 const oldStyle = document.getElementById(styleId); if (oldStyle) { oldStyle.remove(); } // 添加新样式 const style = document.createElement('style'); style.id = styleId; style.textContent = css; document.head.appendChild(style); }; /** * 移除主题CSS */ export const removeThemeCSS = () => { if (typeof document === 'undefined') return; const styleId = 'metona-toast-theme-css'; const style = document.getElementById(styleId); if (style) { style.remove(); } }; /** * 主题工具 — 代理层,直接引用模块函数 */ export const themeUtils = { getSystemTheme, resolveTheme, getThemeConfig, applyTheme, getCurrentTheme, getResolvedTheme, switchTheme, toggleTheme, resetToAuto, initTheme, watchSystemTheme, unwatchSystemTheme, addThemeListener, removeThemeListener, clearThemeListeners, registerTheme, unregisterTheme, getAllThemes, getThemeNames, hasTheme, getThemePreview, generateThemeCSS, applyThemeCSS, removeThemeCSS, saveTheme, loadTheme, }; /** * 预设主题 */ export const presetThemes = { /** * 浅色主题 */ light: { name: '浅色', description: '明亮清晰的主题', config: THEMES.light, }, /** * 深色主题 */ dark: { name: '深色', description: '护眼舒适的暗色主题', config: THEMES.dark, }, /** * 自动主题 */ auto: { name: '自动', description: '跟随系统主题设置', config: 'auto', }, warm: { name: '暖色', description: '温馨舒适的暖色主题', config: THEMES.warm, }, }; // 注册预设主题 Object.entries(presetThemes).forEach(([name, theme]) => { if (name !== 'auto' && theme.config !== 'auto') { THEMES[name] = theme.config; } }); /** * 创建主题管理器 * @returns {Object} 主题管理器 */ export const createThemeManager = () => { return { getSystemTheme, resolveTheme, getThemeConfig, applyTheme, getCurrentTheme, getResolvedTheme, switchTheme, toggleTheme, resetToAuto, initTheme, watchSystemTheme, unwatchSystemTheme, addThemeListener, removeThemeListener, clearThemeListeners, registerTheme, unregisterTheme, getAllThemes, getThemeNames, hasTheme, getThemePreview, generateThemeCSS, applyThemeCSS, removeThemeCSS, saveTheme, loadTheme, }; }; export { themeUtils as default };