release: v0.1.5 — theme system usability, extensibility, external theme following

feat(themes): comprehensive theme system enhancement
- Fix resolveTheme('auto') always resolves to system theme (no stale override)
- Add per-instance theme isolation via createInstanceTheme()
- Add MarkdownEditor.setTheme()/getTheme()/getThemeContext() instance methods
- Add 'themeChange' instance event with { theme, resolved, config } payload
- Multi-instance independent themes (no cross-contamination)

feat(themes): external theme following (v0.1.5)
- followExternalTheme() — follow theme via data attr, CSS class, or callback
- adoptFromParent() — inherit theme from parent container
- watch() — watch external theme source (function or selector)

feat(themes): extensibility (v0.1.5)
- registerTheme() supports 'extends' option for theme inheritance
- exportCSSVars() — export all CSS variable values from element
- getCSSVariable() — query single CSS variable
- applyThemeToElement() — apply theme to any DOM element

chore: bump version 0.1.4 → 0.1.5 across all files
test(themes): 30 new tests covering all v0.1.5 theme APIs (499 total)
This commit is contained in:
2026-07-24 09:53:20 +08:00
parent f8b9f4a761
commit 517dc34435
16 changed files with 1087 additions and 277 deletions
+600 -251
View File
@@ -1,305 +1,609 @@
/**
* MetonaEditor Themes - 主题管理
* MetonaEditor Themes 主题系统(增强版)
* @module themes
* @version 0.1.4
* @description 主题系统、自定义主题和主题切换
* @version 0.1.5
* @description 全局+实例级主题管理、CSS 变量隔离、外部主题跟随、主题继承
*
* v0.1.5 增强:
* - 修复 resolveTheme('auto') 始终解析为系统主题
* - 实例级 setTheme() / getTheme(),多实例独立主题
* - 实例级 themeChange 事件通知
* - 外部主题跟随:syncWithElement() / adoptFromParent() / watch()
* - 主题继承:registerTheme(name, { extends: 'light', ... })
* - 导出 CSS 变量:exportCSSVars() / getCSSVariable()
* - 主题悬停/聚焦状态变量支持
* - 应用主题到指定元素(不污染 :root)
*/
import { THEMES } from './constants.js';
import { prefersDark } from './utils.js';
let currentTheme = 'auto';
let themeListeners = new Set();
// ============ 模块级状态(全局默认,向后兼容) ============
let globalCurrentTheme = 'auto';
let globalThemeListeners = new Set();
let systemThemeMediaQuery = null;
let systemThemeListener = null;
// ============ 系统主题 ============
/**
* 获取系统主题
* 获取系统主题偏好
* @returns {'light'|'dark'}
*/
export const getSystemTheme = () => {
const getSystemTheme = () => {
if (typeof window === 'undefined') return 'light';
return prefersDark() ? 'dark' : 'light';
};
/**
* 获取主题(别名
* 监听系统主题变化(内部回调版
* @param {(theme: 'light'|'dark') => void} callback
* @returns {() => void} 取消监听函数
*/
export const getTheme = (theme) => {
return resolveTheme(theme);
const _watchSystem = (callback) => {
if (typeof window === 'undefined' || !window.matchMedia) return () => {};
const mql = window.matchMedia('(prefers-color-scheme: dark)');
const handler = (e) => callback(e.matches ? 'dark' : 'light');
mql.addEventListener('change', handler);
return () => mql.removeEventListener('change', handler);
};
/**
* 解析主题
* 监听系统主题变化(全局单例模式,向后兼容)
* 自动在 auto 模式下跟随系统主题切换
*/
export const resolveTheme = (theme) => {
if (theme === 'auto') {
if (currentTheme && currentTheme !== 'auto') {
return currentTheme;
const watchSystemTheme = () => {
if (typeof window === 'undefined' || !window.matchMedia) return;
// 清理旧监听
if (_sysWatchUnsub) { _sysWatchUnsub(); _sysWatchUnsub = null; }
_sysWatchUnsub = _watchSystem((sysTheme) => {
if (globalCurrentTheme === 'auto') {
applyTheme('auto');
}
});
};
// ============ 主题解析 ============
/**
* 解析主题名为实际主题值
* 修复:auto 始终解析为系统当前主题,不再被上次手动设置污染
* @param {string} theme - 主题名
* @returns {string} 解析后的主题名
*/
const resolveTheme = (theme) => {
if (!theme || theme === 'auto') {
return getSystemTheme();
}
return theme;
};
/**
* 获取主题配置
* 获取主题配置对象
* @param {string} theme - 主题名
* @returns {Object} 主题配置
*/
export const getThemeConfig = (theme) => {
const getThemeConfig = (theme) => {
const resolved = resolveTheme(theme);
return THEMES[resolved] || THEMES.light;
};
/**
* 应用主题
*/
export const applyTheme = (theme) => {
currentTheme = theme;
const resolved = resolveTheme(theme);
if (typeof document !== 'undefined') {
document.documentElement.setAttribute('data-md-theme', resolved);
document.documentElement.classList.remove('md-theme-light', 'md-theme-dark', 'md-theme-auto');
document.documentElement.classList.add(`md-theme-${resolved}`);
const config = getThemeConfig(theme);
setThemeVariables(config);
}
notifyThemeListeners(theme, resolved);
};
// ============ CSS 变量操作 ============
/**
* 设置主题CSS变量
* 将主题配置写入 CSS 变量
* @param {Object} config - 主题配置对象
* @param {HTMLElement} [target] - 目标元素,不传则设置到 documentElement(全局默认
* @param {HTMLElement} [target] - 目标元素(默认 documentElement
*/
export const setThemeVariables = (config, target) => {
const setThemeVariables = (config, target) => {
if (typeof document === 'undefined' || !config || typeof config !== 'object') return;
const root = target || document.documentElement;
// 基础变量(保留用于全局与组件回退)
root.style.setProperty('--md-bg', config.bg);
root.style.setProperty('--md-text', config.text);
root.style.setProperty('--md-border', config.border);
root.style.setProperty('--md-shadow', config.shadow);
root.style.setProperty('--md-hover-shadow', config.hoverShadow);
// 编辑器专属变量
root.style.setProperty('--md-toolbar-bg', config.toolbarBg || config.bg);
root.style.setProperty('--md-textarea-bg', config.textareaBg || config.bg);
root.style.setProperty('--md-preview-bg', config.previewBg || config.bg);
root.style.setProperty('--md-code-bg', config.codeBg || 'rgba(127,127,127,0.1)');
root.style.setProperty('--md-code-text', config.codeText || config.text);
root.style.setProperty('--md-accent', config.accent || '#3b82f6');
root.style.setProperty('--md-muted', config.muted || '#9ca3af');
const vars = {
'--md-bg': config.bg,
'--md-text': config.text,
'--md-border': config.border,
'--md-shadow': config.shadow,
'--md-hover-shadow': config.hoverShadow,
'--md-toolbar-bg': config.toolbarBg || config.bg,
'--md-textarea-bg': config.textareaBg || config.bg,
'--md-preview-bg': config.previewBg || config.bg,
'--md-code-bg': config.codeBg || 'rgba(127,127,127,0.1)',
'--md-code-text': config.codeText || config.text,
'--md-accent': config.accent || '#3b82f6',
'--md-muted': config.muted || '#9ca3af',
};
Object.entries(vars).forEach(([k, v]) => {
if (v !== undefined && v !== null) {
root.style.setProperty(k, String(v));
}
});
// 兼容旧字段
if (config.progressBg) root.style.setProperty('--md-progress-bg', config.progressBg);
if (config.closeHoverBg) root.style.setProperty('--md-close-hover-bg', config.closeHoverBg);
};
/**
* 获取当前主题
* 导出当前 CSS 变量值(从指定元素读取)
* @param {HTMLElement} [el] - 读取的元素(默认 documentElement
* @returns {Object} CSS 变量键值对
*/
export const getCurrentTheme = () => {
return currentTheme;
const exportCSSVars = (el) => {
const root = el || document.documentElement;
if (typeof document === 'undefined') return {};
const style = getComputedStyle(root);
const varNames = [
'--md-bg', '--md-text', '--md-border', '--md-shadow',
'--md-hover-shadow', '--md-toolbar-bg', '--md-textarea-bg',
'--md-preview-bg', '--md-code-bg', '--md-code-text',
'--md-accent', '--md-muted',
];
const result = {};
varNames.forEach((name) => {
const val = style.getPropertyValue(name).trim();
if (val) result[name] = val;
});
return result;
};
/**
* 获取解析后的主题
* 获取单个 CSS 变量值
* @param {string} name - 变量名(自动补全 --md- 前缀)
* @param {HTMLElement} [el] - 读取的元素
* @returns {string} 变量值
*/
export const getResolvedTheme = () => {
return resolveTheme(currentTheme);
const getCSSVariable = (name, el) => {
const root = el || document.documentElement;
if (typeof document === 'undefined') return '';
const fullName = name.startsWith('--') ? name : `--md-${name}`;
return getComputedStyle(root).getPropertyValue(fullName).trim();
};
// ============ 主题切换与应用 ============
/**
* 应用主题到全局(向后兼容)
* @param {string} theme - 主题名
*/
const applyTheme = (theme) => {
globalCurrentTheme = theme;
const resolved = resolveTheme(theme);
if (typeof document !== 'undefined') {
document.documentElement.setAttribute('data-md-theme', resolved);
document.documentElement.classList.remove('md-theme-light', 'md-theme-dark', 'md-theme-auto', 'md-theme-warm');
document.documentElement.classList.add(`md-theme-${resolved}`);
const config = getThemeConfig(theme);
setThemeVariables(config);
}
notifyGlobalListeners(theme, resolved);
};
/**
* 切换主题
* 应用主题到指定元素(不改变全局状态)
* @param {string} theme - 主题名
* @param {HTMLElement} target - 目标 DOM 元素
*/
export const switchTheme = (theme) => {
const applyThemeToElement = (theme, target) => {
if (!target || typeof document === 'undefined') return;
const resolved = resolveTheme(theme);
const config = getThemeConfig(theme);
target.setAttribute('data-md-theme', resolved);
target.classList.remove('md-theme-light', 'md-theme-dark', 'md-theme-auto', 'md-theme-warm');
if (resolved !== 'auto') {
target.classList.add(`md-theme-${resolved}`);
}
setThemeVariables(config, target);
};
/**
* 切换主题并持久化
*/
const switchTheme = (theme) => {
applyTheme(theme);
saveTheme(theme);
};
/**
* 切换亮色/暗色主题
* 在 light/dark 间切换
*/
export const toggleTheme = () => {
const toggleTheme = () => {
const resolved = getResolvedTheme();
const newTheme = resolved === 'dark' ? 'light' : 'dark';
switchTheme(newTheme);
switchTheme(resolved === 'dark' ? 'light' : 'dark');
};
/**
* 重置为自动主题
*/
export const resetToAuto = () => {
const resetToAuto = () => {
switchTheme('auto');
};
/**
* 保存主题到本地存储
* 获取全局当前主题名
*/
export const saveTheme = (theme) => {
const getCurrentTheme = () => globalCurrentTheme;
/**
* 获取解析后的全局主题
*/
const getResolvedTheme = () => resolveTheme(globalCurrentTheme);
// ============ 主题持久化 ============
const saveTheme = (theme) => {
if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem('metona-editor-theme', theme);
} catch (e) {
console.warn('Failed to save theme:', e);
}
try { localStorage.setItem('metona-editor-theme', theme); } catch (_) {}
}
};
/**
* 从本地存储加载主题
*/
export const loadTheme = () => {
const loadTheme = () => {
if (typeof localStorage !== 'undefined') {
try {
return localStorage.getItem('metona-editor-theme') || 'auto';
} catch (e) {
console.warn('Failed to load theme:', e);
}
try { return localStorage.getItem('metona-editor-theme') || 'auto'; } catch (_) {}
}
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;
}
};
/**
* 添加主题监听器
*/
export const addThemeListener = (listener) => {
themeListeners.add(listener);
return () => {
themeListeners.delete(listener);
};
};
/**
* 移除主题监听器
*/
export const removeThemeListener = (listener) => {
themeListeners.delete(listener);
};
/**
* 通知主题监听器
*/
const notifyThemeListeners = (theme, resolved) => {
themeListeners.forEach((listener) => {
try {
listener(theme, resolved);
} catch (e) {
console.error('Theme listener error:', e);
}
const notifyGlobalListeners = (theme, resolved) => {
globalThemeListeners.forEach((fn) => {
try { fn(theme, resolved); } catch (e) { console.error('Theme listener error:', e); }
});
};
const addThemeListener = (fn) => {
globalThemeListeners.add(fn);
return () => globalThemeListeners.delete(fn);
};
const removeThemeListener = (fn) => {
globalThemeListeners.delete(fn);
};
const clearThemeListeners = () => {
globalThemeListeners.clear();
};
// ============ 系统主题监听 ============
let _sysWatchUnsub = null;
const startSystemWatch = () => {
watchSystemTheme(); // 使用兼容的全局单例
};
const stopSystemWatch = () => {
if (_sysWatchUnsub) { _sysWatchUnsub(); _sysWatchUnsub = null; }
};
// 保留旧 API 兼容
const unwatchSystemTheme = stopSystemWatch;
// ============ 初始化 ============
const initTheme = () => {
const savedTheme = loadTheme();
applyTheme(savedTheme);
startSystemWatch();
};
// ============ 外部主题跟随(v0.1.5 新增) ============
/**
* 清除所有主题监听器
* 跟随外部元素的主题
* 支持三种检测策略:
* 1. data-md-theme 属性(推荐,显式声明)
* 2. CSS 类名(如 theme-dark / theme-light
* 3. 自定义回调函数
*
* @param {Object} options
* @param {HTMLElement} [options.element] - 观察的元素(默认 documentElement
* @param {string} [options.attr='data-theme'] - 检测的属性名
* @param {string[]} [options.classMap] - 类名映射 { 'theme-dark': 'dark', 'theme-light': 'light' }
* @param {Function} [options.callback] - 自定义回调 (element) => themeName
* @param {Function} onThemeChange - 主题变化回调 (resolvedTheme) => void
* @returns {() => void} 取消跟随函数
*/
export const clearThemeListeners = () => {
themeListeners.clear();
const followExternalTheme = (options, onThemeChange) => {
if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') {
return () => {};
}
const {
element = document.documentElement,
attr = 'data-theme',
classMap = null,
callback = null,
} = options || {};
const detect = () => {
if (typeof callback === 'function') {
return callback(element);
}
// 策略1:属性检测
const attrVal = element.getAttribute(attr);
if (attrVal) return attrVal;
// 策略2classMap 检测
if (classMap) {
for (const [cls, theme] of Object.entries(classMap)) {
if (element.classList.contains(cls)) return theme;
}
}
// 策略3data-md-theme 回退
const mdTheme = element.getAttribute('data-md-theme');
if (mdTheme) return mdTheme;
return null;
};
let lastTheme = detect();
if (lastTheme) onThemeChange(lastTheme);
const observer = new MutationObserver(() => {
const current = detect();
if (current && current !== lastTheme) {
lastTheme = current;
onThemeChange(current);
}
});
observer.observe(element, {
attributes: true,
attributeFilter: [attr, 'class', 'data-md-theme'],
});
return () => observer.disconnect();
};
/**
* 注册自定义主题
* 从父容器继承主题(读取 CSS 变量 + data-md-theme
* 适用场景:编辑器嵌入已有主题体系的应用时自动适配
*
* @param {HTMLElement} container - 编辑器容器元素
* @param {(theme: string) => void} onThemeDetected - 检测到主题的回调
* @returns {() => void} 取消跟随函数
*/
export const registerTheme = (name, config) => {
const base = THEMES.light;
const adoptFromParent = (container, onThemeDetected) => {
if (typeof document === 'undefined' || !container) return () => {};
const detect = () => {
// 向上遍历 DOM 树寻找 data-md-theme 属性
let el = container.parentElement;
while (el) {
const t = el.getAttribute('data-md-theme');
if (t) return t;
// 也检测常见类名
if (el.classList.contains('theme-dark') || el.classList.contains('dark')) return 'dark';
if (el.classList.contains('theme-light') || el.classList.contains('light')) return 'light';
el = el.parentElement;
}
// 回退:读取父元素 computed style 中的 --md-bg 判断明暗
if (container.parentElement) {
const bg = getComputedStyle(container.parentElement).getPropertyValue('--md-bg').trim()
|| getComputedStyle(container.parentElement).backgroundColor;
if (bg) {
// 简单亮度判断
const rgb = bg.match(/\d+/g);
if (rgb && rgb.length >= 3) {
const brightness = (parseInt(rgb[0]) * 299 + parseInt(rgb[1]) * 587 + parseInt(rgb[2]) * 114) / 1000;
return brightness < 128 ? 'dark' : 'light';
}
}
}
return null;
};
const detected = detect();
if (detected) onThemeDetected(detected);
// 监听父元素的属性变化
const observer = new MutationObserver(() => {
const d = detect();
if (d) onThemeDetected(d);
});
let target = container.parentElement;
while (target) {
observer.observe(target, { attributes: true, attributeFilter: ['class', 'data-md-theme', 'data-theme'] });
target = target.parentElement;
// 只观察向上3层,避免性能问题
if (target === document.documentElement) break;
}
return () => observer.disconnect();
};
/**
* 创建一个主题观察者,监听外部主题源变化
* @param {Function|string} source - 回调函数返回主题名,或 CSS 选择器
* @param {(theme: string) => void} onChange - 主题变化回调
* @returns {() => void} 取消观察
*/
const watch = (source, onChange) => {
if (typeof source === 'function') {
const tick = () => {
try {
const theme = source();
if (theme) onChange(theme);
} catch (_) {}
};
tick();
const id = setInterval(tick, 500);
return () => clearInterval(id);
}
if (typeof source === 'string' && typeof document !== 'undefined') {
const el = document.querySelector(source);
if (!el) return () => {};
return followExternalTheme({ element: el, attr: 'data-theme' }, onChange);
}
return () => {};
};
// ============ 自定义主题注册(v0.1.5: 支持继承) ============
/**
* 注册自定义主题
* @param {string} name - 主题名
* @param {Object} config - 主题配置
* @param {string} [config.extends] - 继承的基础主题名(如 'light'
*/
const registerTheme = (name, config = {}) => {
const baseName = config.extends || 'light';
const base = THEMES[baseName] || THEMES.light;
THEMES[name] = {
bg: config.bg || base.bg,
text: config.text || base.text,
border: config.border || base.border,
shadow: config.shadow || base.shadow,
hoverShadow: config.hoverShadow || base.hoverShadow,
toolbarBg: config.toolbarBg || config.bg || base.toolbarBg,
textareaBg: config.textareaBg || config.bg || base.textareaBg,
previewBg: config.previewBg || config.bg || base.previewBg,
toolbarBg: config.toolbarBg || base.toolbarBg || base.bg,
textareaBg: config.textareaBg || base.textareaBg || base.bg,
previewBg: config.previewBg || base.previewBg || base.bg,
codeBg: config.codeBg || base.codeBg,
codeText: config.codeText || base.codeText,
accent: config.accent || base.accent,
muted: config.muted || base.muted,
// 兼容旧字段
progressBg: config.progressBg,
closeHoverBg: config.closeHoverBg,
progressBg: config.progressBg || base.progressBg,
closeHoverBg: config.closeHoverBg || base.closeHoverBg,
};
};
const unregisterTheme = (name) => {
if (name === 'light' || name === 'dark' || name === 'auto' || name === 'warm') {
console.warn('Cannot unregister built-in theme:', name);
return;
}
delete THEMES[name];
};
const getAllThemes = () => ({ ...THEMES });
const getThemeNames = () => Object.keys(THEMES);
const hasTheme = (name) => name in THEMES;
// ============ 实例级主题管理(v0.1.5 新增) ============
/**
* 为编辑器实例创建隔离的主题上下文
* 每个上下文独立跟踪主题,变更时自动同步 CSS 变量到实例 DOM
*
* @param {Object} editor - 编辑器实例(需有 el / config 属性)
* @returns {Object} 实例主题 API
*/
const createInstanceTheme = (editor) => {
let instanceTheme = editor.config?.theme || globalCurrentTheme || 'auto';
/** 应用主题到当前实例的 DOM */
const apply = (theme) => {
instanceTheme = theme;
const resolved = resolveTheme(theme);
const config = getThemeConfig(theme);
if (editor.el) {
// 更新 wrapper 类名
['me-theme-light', 'me-theme-dark', 'me-theme-warm'].forEach((c) => {
editor.el.classList.remove(c);
});
if (resolved !== 'auto') {
editor.el.classList.add(`me-theme-${resolved}`);
}
editor.el.setAttribute('data-md-theme', resolved);
// 设置实例级 CSS 变量(scope 到 wrapper
setThemeVariables(config, editor.el);
}
// 触发编辑器实例事件
if (typeof editor._emit === 'function') {
editor._emit('themeChange', { theme, resolved, config });
}
// 强制刷新预览(主题可能影响渲染)
if (typeof editor.refresh === 'function') {
editor.refresh();
}
return instanceTheme;
};
/** 获取实例当前主题名 */
const get = () => instanceTheme;
/** 设置实例主题 */
const set = (theme) => apply(theme);
/** 切换 light/dark */
const toggle = () => {
const resolved = resolveTheme(instanceTheme);
return apply(resolved === 'dark' ? 'light' : 'dark');
};
/** 获取解析后的主题名 */
const getResolved = () => resolveTheme(instanceTheme);
/** 获取实例主题配置 */
const getConfig = () => getThemeConfig(instanceTheme);
/** 获取实例 CSS 变量导出 */
const getVars = () => {
if (editor.el) return exportCSSVars(editor.el);
return {};
};
/** 外部主题跟随:同步到外部元素 */
const syncWithElement = (element, opts = {}) => {
return followExternalTheme(
{ element, attr: opts.attr || 'data-theme', classMap: opts.classMap, callback: opts.callback },
(detected) => apply(detected),
);
};
/** 从父容器继承主题 */
const adopt = () => {
if (editor.container) {
return adoptFromParent(editor.container, (detected) => apply(detected));
}
return () => {};
};
/** 监听外部主题变化 */
const watchExternal = (source) => {
return watch(source, (theme) => apply(theme));
};
// 初始应用
apply(instanceTheme);
return {
apply,
get,
set,
toggle,
getResolved,
getConfig,
getVars,
syncWithElement,
adopt,
watch: watchExternal,
};
};
/**
* 注销自定义主题
* 创建独立主题管理器(工厂函数,向后兼容)
* @returns {Object} 主题管理器
*/
export const unregisterTheme = (name) => {
if (name === 'light' || name === 'dark' || name === 'auto') {
console.warn('Cannot unregister built-in theme:', name);
return;
}
delete THEMES[name];
};
/**
* 获取所有主题
*/
export const getAllThemes = () => {
return { ...THEMES };
};
/**
* 获取主题名称列表
*/
export const getThemeNames = () => {
return Object.keys(THEMES);
};
/**
* 检查主题是否存在
*/
export const hasTheme = (name) => {
return name in THEMES;
};
/**
* 主题工具 — 代理层
*/
export const themeUtils = {
const createThemeManager = () => ({
getSystemTheme,
resolveTheme,
getThemeConfig,
@@ -322,69 +626,114 @@ export const themeUtils = {
hasTheme,
saveTheme,
loadTheme,
setThemeVariables,
exportCSSVars,
getCSSVariable,
applyThemeToElement,
followExternalTheme,
adoptFromParent,
watch,
createInstanceTheme,
});
// ============ 预设主题 ============
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 },
};
/**
* 预设主题
*/
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,
},
};
// 注册预设主题
// 初始化:确保 THEMES 中有预设
Object.entries(presetThemes).forEach(([name, theme]) => {
if (name !== 'auto' && theme.config !== 'auto') {
THEMES[name] = theme.config;
}
});
/**
* 创建主题管理器
*/
export const createThemeManager = () => {
return {
getSystemTheme,
resolveTheme,
getThemeConfig,
applyTheme,
getCurrentTheme,
getResolvedTheme,
switchTheme,
toggleTheme,
resetToAuto,
initTheme,
watchSystemTheme,
unwatchSystemTheme,
addThemeListener,
removeThemeListener,
clearThemeListeners,
registerTheme,
unregisterTheme,
getAllThemes,
getThemeNames,
hasTheme,
saveTheme,
loadTheme,
};
// ============ 工具代理层 ============
const themeUtils = {
// 系统
getSystemTheme,
resolveTheme,
getThemeConfig,
watchSystemTheme,
unwatchSystemTheme,
// 全局主题
applyTheme,
getCurrentTheme,
getResolvedTheme,
switchTheme,
toggleTheme,
resetToAuto,
initTheme,
// 持久化
saveTheme,
loadTheme,
// 监听
addThemeListener,
removeThemeListener,
clearThemeListeners,
// 注册
registerTheme,
unregisterTheme,
getAllThemes,
getThemeNames,
hasTheme,
// CSS 变量
setThemeVariables,
exportCSSVars,
getCSSVariable,
applyThemeToElement,
// 外部跟随(v0.1.5
followExternalTheme,
adoptFromParent,
watch,
// 实例主题(v0.1.5
createInstanceTheme,
};
export { themeUtils as default };
// ============ 具名导出 ============
export {
themeUtils,
themeUtils as default,
getSystemTheme,
resolveTheme,
getThemeConfig,
applyTheme,
getCurrentTheme,
getResolvedTheme,
switchTheme,
toggleTheme,
resetToAuto,
saveTheme,
loadTheme,
initTheme,
watchSystemTheme,
unwatchSystemTheme,
addThemeListener,
removeThemeListener,
clearThemeListeners,
registerTheme,
unregisterTheme,
getAllThemes,
getThemeNames,
hasTheme,
setThemeVariables,
exportCSSVars,
getCSSVariable,
applyThemeToElement,
followExternalTheme,
adoptFromParent,
watch,
createInstanceTheme,
createThemeManager,
presetThemes,
};
// 向后兼容别名
const getTheme = resolveTheme;
export { getTheme };