init: MetonaToast v2.0.0 基线 — 已完成改进(模板抽象/constants拆分/测试增强)

This commit is contained in:
tianhao
2026-06-16 12:39:36 +08:00
commit ff9c7cd218
25 changed files with 20621 additions and 0 deletions
+204
View File
@@ -0,0 +1,204 @@
/**
* MetonaToast Plugins - 插件系统
* @module plugins
* @version 2.0.0
* @description 插件管理器 + 3 款预设插件 (keyboard / persistence / accessibility)
*/
/**
* 插件管理器
*/
class PluginManager {
constructor() {
this.plugins = new Map();
this.initialized = false;
}
register(name, plugin) {
if (this.plugins.has(name)) {
console.warn(`Plugin "${name}" is already registered`);
return this;
}
if (!plugin || typeof plugin !== 'object' || (!plugin.name && !plugin.version)) {
console.error(`Invalid plugin "${name}"`);
return this;
}
this.plugins.set(name, { name, ...plugin, installed: false, enabled: true });
if (plugin.install) {
try { plugin.install(this); this.plugins.get(name).installed = true; }
catch (e) { console.error(`Failed to install plugin "${name}":`, e); }
}
return this;
}
unregister(name) {
const plugin = this.plugins.get(name);
if (!plugin) return this;
if (plugin.uninstall) {
try { plugin.uninstall(this); } catch (e) { console.error(`Failed to uninstall plugin "${name}":`, e); }
}
this.plugins.delete(name);
return this;
}
get(name) { return this.plugins.get(name) || null; }
has(name) { return this.plugins.has(name); }
getAll() { return Array.from(this.plugins.values()); }
getNames() { return Array.from(this.plugins.keys()); }
enable(name) {
const p = this.plugins.get(name);
if (p) p.enabled = true;
return this;
}
disable(name) {
const p = this.plugins.get(name);
if (p) p.enabled = false;
return this;
}
isEnabled(name) {
const p = this.plugins.get(name);
return p ? p.enabled : false;
}
destroy() {
this.plugins.forEach((p, name) => {
if (p.destroy) { try { p.destroy(this); } catch (e) {} }
});
this.plugins.clear();
}
}
/**
* 预设插件
*/
const presetPlugins = {
/**
* 键盘快捷键插件 — ESC 关闭所有 Toast
*/
keyboard: {
name: 'keyboard',
version: '1.0.0',
description: 'ESC 关闭所有 Toast',
_handler: null,
install() {
if (typeof document === 'undefined') return;
this._handler = (e) => {
if (e.key === 'Escape') {
// 点击所有关闭按钮
document.querySelectorAll('.met-toast .met-close').forEach(btn => {
try { btn.click(); } catch (_) {}
});
}
};
document.addEventListener('keydown', this._handler);
},
uninstall() {
if (this._handler && typeof document !== 'undefined') {
document.removeEventListener('keydown', this._handler);
this._handler = null;
}
},
},
/**
* 持久化插件 — 自动保存/加载配置到 localStorage
*/
persistence: {
name: 'persistence',
version: '1.0.0',
description: '自动持久化配置到 localStorage',
storageKey: 'metona-toast-config',
install() {
// 加载已保存的配置
if (typeof localStorage !== 'undefined') {
try {
const saved = localStorage.getItem(this.storageKey);
return saved ? JSON.parse(saved) : null;
} catch (_) { return null; }
}
return null;
},
save(config) {
if (typeof localStorage !== 'undefined') {
try { localStorage.setItem(this.storageKey, JSON.stringify(config)); } catch (_) {}
}
},
uninstall() {
if (typeof localStorage !== 'undefined') {
try { localStorage.removeItem(this.storageKey); } catch (_) {}
}
},
},
/**
* 无障碍插件 — 屏幕阅读器公告
*/
accessibility: {
name: 'accessibility',
version: '1.0.0',
description: '通过屏幕阅读器朗读 Toast 内容',
announce(toast) {
if (typeof document === 'undefined') return;
const el = document.createElement('div');
el.setAttribute('aria-live', 'assertive');
el.setAttribute('aria-atomic', 'true');
el.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;';
const typeNames = { success:'成功', error:'错误', warning:'警告', info:'信息', loading:'加载中', default:'通知' };
el.textContent = `${typeNames[toast.type] || '通知'}: ${toast.title || ''} ${toast.message || ''}`;
document.body.appendChild(el);
setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 3000);
},
},
};
/**
* 插件工具
*/
const pluginUtils = {
createManager() { return new PluginManager(); },
register(name, plugin) { return defaultPluginManager.register(name, plugin); },
unregister(name) { return defaultPluginManager.unregister(name); },
get(name) { return defaultPluginManager.get(name); },
has(name) { return defaultPluginManager.has(name); },
getAll() { return defaultPluginManager.getAll(); },
getNames() { return defaultPluginManager.getNames(); },
enable(name) { return defaultPluginManager.enable(name); },
disable(name) { return defaultPluginManager.disable(name); },
isEnabled(name) { return defaultPluginManager.isEnabled(name); },
getPreset(name) { return presetPlugins[name] || null; },
getAllPresets() { return { ...presetPlugins }; },
createPlugin(config) {
return {
name: config.name || 'custom',
version: config.version || '1.0.0',
description: config.description || '',
hooks: config.hooks || {},
install: config.install || null,
uninstall: config.uninstall || null,
...config,
};
},
validatePlugin(plugin) {
const errors = [];
if (!plugin || typeof plugin !== 'object') errors.push('Plugin must be an object');
if (!plugin.name && !plugin.version) errors.push('Plugin must have a name or version');
return { valid: errors.length === 0, errors };
},
};
const defaultPluginManager = new PluginManager();
export { presetPlugins, pluginUtils, defaultPluginManager, PluginManager };