API 文档
-MetonaToast v0.1.1 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 配置项 作为可选第二参数。
+MetonaToast v0.1.2 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 配置项 作为可选第二参数。
show(message, opts?)
@@ -434,6 +434,7 @@ MeToast.use('accessibility'); resetTimerOnUpdate | boolean | false | 调用 update() 时重置 duration 倒计时 |
| notifyWhenHidden | boolean | false | 页面不可见时自动通过 Notification API 发送系统通知 |
| render | function | — | 自定义渲染函数 (toast) => htmlString,完全接管 DOM 构建 |
+ | onError | function | — | 全局错误回调 ({ hook, source, error, toast }) => void,钩子异常或定时器错误时触发 |
回调函数
diff --git a/site/index.html b/site/index.html
index 78abea9..9dc3aa9 100644
--- a/site/index.html
+++ b/site/index.html
@@ -237,7 +237,7 @@ orderGroup.dismiss(); // 一键关闭
diff --git a/src/animations.js b/src/animations.js
index 8fb101a..f70242d 100644
--- a/src/animations.js
+++ b/src/animations.js
@@ -1,5 +1,5 @@
/**
- * MetonaToast Animations - 动画管理 v0.1.1
+ * MetonaToast Animations - 动画管理 v0.1.2
* @module animations
* @description 动画注册与管理,移除未使用的 Web Animations API dead code
*/
@@ -61,11 +61,11 @@ export const animationUtils = {
},
/**
- * 获取活动动画数量(兼容API,当前CSS动画不跟踪活动实例)
- * @returns {number} 动画数量
+ * 获取已注册动画总数
+ * @returns {number} 已注册动画数量
*/
getActiveCount() {
- return 0;
+ return animationMap.size;
},
/**
diff --git a/src/constants.js b/src/constants.js
index 3e13a20..57bf979 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -1,7 +1,7 @@
/**
* MetonaToast Constants - 常量定义
* @module constants
- * @version 0.1.1
+ * @version 0.1.2
* @description 默认配置、颜色、动画、主题等常量
*/
@@ -48,7 +48,10 @@ export const DEFAULTS = Object.freeze({
// 高级
resetTimerOnUpdate: false,
notifyWhenHidden: false,
-
+
+ // 错误处理
+ onError: null,
+
// 国际化
locale: 'zh-CN',
@@ -267,7 +270,15 @@ export const THEMES = {
progressBg: 'rgba(255, 255, 255, 0.08)',
closeHoverBg: 'rgba(255, 255, 255, 0.08)',
},
- auto: 'auto',
+ auto: {
+ bg: 'auto',
+ text: 'auto',
+ border: 'auto',
+ shadow: 'auto',
+ hoverShadow: 'auto',
+ progressBg: 'auto',
+ closeHoverBg: 'auto',
+ },
warm: {
bg: 'rgba(255, 251, 235, 0.96)',
text: '#78350f',
diff --git a/src/core.js b/src/core.js
index 4155d15..6a645cd 100644
--- a/src/core.js
+++ b/src/core.js
@@ -1,7 +1,7 @@
/**
* MetonaToast Core - 核心Toast逻辑
* @module core
- * @version 0.1.1
+ * @version 0.1.2
* @description 重构后的核心模块,包含Toast类的优化实现
*/
@@ -68,7 +68,15 @@ class Toast {
}
static trigger(name, toast) {
const list = this._hooks.get(name);
- if (list) list.forEach(fn => { try { fn(toast); } catch (e) { console.error('Hook error:', name, e); } });
+ if (list) list.forEach(fn => {
+ try { fn(toast); }
+ catch (e) {
+ console.error('Hook error:', name, e);
+ if (meToast._config.onError) {
+ try { meToast._config.onError({ hook: name, error: e, toast }); } catch (_) {}
+ }
+ }
+ });
}
constructor(opts) {
@@ -392,33 +400,43 @@ class Toast {
});
}
- _startTimer() {
+ _startTimer(resuming = false) {
if (this.config.duration <= 0) return;
-
- this.startedAt = Date.now();
- this.remaining = this.config.duration;
-
+
+ // 仅在首次启动时重置时间基准;resume 场景由 _resume() 预先调整 startedAt
+ if (!resuming) {
+ this.startedAt = Date.now();
+ this.remaining = this.config.duration;
+ }
+
const tick = () => {
if (this.paused || this.closing) return;
-
- const elapsed = Date.now() - this.startedAt;
- this.remaining = Math.max(0, this.config.duration - elapsed);
-
- if (this.barEl) {
- const ratio = this.remaining / this.config.duration;
- const t = this.config.progressDirection === 'vertical'
- ? `scaleY(${ratio})` : `scaleX(${ratio})`;
- this.barEl.style.transform = t;
+
+ try {
+ const elapsed = Date.now() - this.startedAt;
+ this.remaining = Math.max(0, this.config.duration - elapsed);
+
+ if (this.barEl) {
+ const ratio = this.remaining / this.config.duration;
+ const t = this.config.progressDirection === 'vertical'
+ ? `scaleY(${ratio})` : `scaleX(${ratio})`;
+ this.barEl.style.transform = t;
+ }
+
+ if (this.remaining <= 0) {
+ this.close();
+ return;
+ }
+ } catch (e) {
+ console.error('Timer tick error:', e);
+ if (meToast._config.onError) {
+ try { meToast._config.onError({ source: 'timer', error: e, toast: this }); } catch (_) {}
+ }
}
-
- if (this.remaining <= 0) {
- this.close();
- return;
- }
-
+
this.rafId = requestAnimationFrame(tick);
};
-
+
this.rafId = requestAnimationFrame(tick);
}
@@ -432,19 +450,37 @@ class Toast {
_resume() {
if (!this.paused) return;
this.paused = false;
+ // 校准时间基准,补偿暂停期间经过的时间
this.startedAt = Date.now() - (this.config.duration - this.remaining);
- this._startTimer();
+ this._startTimer(true);
}
update(partial) {
Toast.trigger('beforeUpdate', this);
+ const typeChanged = partial.type && partial.type !== this.type;
if (partial.type) this.type = partial.type;
if (partial.title !== undefined) this.title = partial.title;
if (partial.message !== undefined) this.message = partial.message;
if (partial.html !== undefined) this.html = partial.html;
-
+
if (!this.el) { Toast.trigger('afterUpdate', this); return this; }
-
+
+ // 类型变更时同步更新 DOM 类名、边框颜色和进度条颜色
+ if (typeChanged) {
+ const typeClasses = ['met-success', 'met-error', 'met-warning', 'met-info', 'met-loading', 'met-default'];
+ typeClasses.forEach(c => this.el.classList.remove(c));
+ this.el.classList.add(`met-${this.type}`);
+ if (this.barEl) {
+ const c = (TYPE_COLORS[this.type] || TYPE_COLORS.default);
+ this.barEl.style.background = c.fg;
+ }
+ const side = this.el.querySelector('.met-side');
+ if (side) {
+ const c = (TYPE_COLORS[this.type] || TYPE_COLORS.default);
+ side.style.background = c.fg;
+ }
+ }
+
const content = this.el.querySelector('.met-content');
if (content) {
const safeTitle = this.title ? `${escapeHTML(this.title)}
` : '';
@@ -453,7 +489,7 @@ class Toast {
: (this.message ? `${escapeHTML(this.message)}
` : '');
content.innerHTML = `${safeTitle}${safeMessage}`;
}
-
+
// resetTimerOnUpdate: 更新内容后重置计时器
if (this.config.resetTimerOnUpdate && this.config.duration > 0) {
cancelAnimationFrame(this.rafId);
@@ -461,11 +497,11 @@ class Toast {
this.remaining = this.config.duration;
this._startTimer();
}
-
+
if (typeof this.config.onUpdate === 'function') {
try { this.config.onUpdate(this); } catch (e) { console.error('onUpdate callback error:', e); }
}
-
+
Toast.trigger('afterUpdate', this);
return this;
}
@@ -621,11 +657,12 @@ const _actionHTML = (actions) => {
const meToast = {
_toasts: new Map(),
_config: { ...DEFAULTS },
- version: '0.1.1',
+ version: '0.1.2',
configure(opts) {
if (!opts || typeof opts !== 'object') return this;
- this._config = { ...this._config, ...opts };
+ // 原地更新,保持 _config 引用一致(index.js 的增强对象共享同一个 _config)
+ Object.assign(this._config, opts);
if (opts.theme) {
applyTheme(opts.theme);
diff --git a/src/i18n.js b/src/i18n.js
index fcd408c..fe7f8d9 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -1,7 +1,7 @@
/**
* MetonaToast i18n - 国际化管理
* @module i18n
- * @version 0.1.1
+ * @version 0.1.2
* @description 多语言支持、语言切换和翻译管理
*/
diff --git a/src/icons.js b/src/icons.js
index 70be2c1..e239388 100644
--- a/src/icons.js
+++ b/src/icons.js
@@ -1,7 +1,7 @@
/**
* MetonaToast Icons — 图标SVG定义
* @module icons
- * @version 0.1.1
+ * @version 0.1.2
* @description 80+ 内置SVG图标
*/
diff --git a/src/index.js b/src/index.js
index e645c88..3b6a879 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,7 +1,7 @@
/**
* MetonaToast - 轻量级Toast通知库
* @module metona-toast
- * @version 0.1.1
+ * @version 0.1.2
* @author thzxx
* @description 轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。
* @license MIT
@@ -15,7 +15,7 @@ import { pluginUtils, presetPlugins } from './plugins.js';
import { DEFAULTS } from './constants.js';
// 版本信息
-const VERSION = '0.1.1';
+const VERSION = '0.1.2';
/**
* 主对象增强
@@ -89,6 +89,8 @@ const enhancedMeToast = {
* 销毁
*/
destroy() {
+ if (this._destroyed) return;
+ this._destroyed = true;
this.dismiss();
if (this.plugins && typeof this.plugins.destroy === 'function') {
@@ -149,16 +151,19 @@ const enhancedMeToast = {
*/
updateConfig(config) {
if (config && typeof config === 'object') {
- this._config = { ...this._config, ...config };
+ Object.assign(this._config, config);
}
return this;
},
-
+
/**
* 重置配置
*/
resetConfig() {
- this._config = { ...DEFAULTS };
+ // 原地重置:先清除所有自有属性,再回填默认值
+ const keys = Object.keys(this._config);
+ keys.forEach(k => delete this._config[k]);
+ Object.assign(this._config, DEFAULTS);
return this;
},
diff --git a/src/locales.js b/src/locales.js
index 2cd98cf..38bc1f9 100644
--- a/src/locales.js
+++ b/src/locales.js
@@ -1,12 +1,13 @@
/**
* MetonaToast Locales — 国际化翻译数据
* @module locales
- * @version 0.1.1
- * @description 内置 zh-CN / en-US 完整翻译
+ * @version 0.1.2
+ * @description 内置 zh-CN / en-US 完整翻译(已去重优化)
*/
export const LOCALES = {
'zh-CN': {
+ // === 操作 ===
close: '关闭',
loading: '加载中...',
success: '操作成功',
@@ -87,6 +88,8 @@ export const LOCALES = {
hide: '隐藏',
visible: '可见',
hidden: '隐藏',
+
+ // === 状态 ===
enabled: '已启用',
disabled: '已禁用',
active: '活跃',
@@ -95,7 +98,6 @@ export const LOCALES = {
offline: '离线',
connected: '已连接',
disconnected: '已断开',
- loading: '加载中',
loaded: '已加载',
saving: '保存中',
saved: '已保存',
@@ -114,12 +116,9 @@ export const LOCALES = {
receiving: '接收中',
received: '已接收',
connecting: '连接中',
- error: '错误',
- warning: '警告',
- info: '信息',
- success: '成功',
+
+ // === 交互 ===
question: '问题',
- help: '帮助',
feedback: '反馈',
report: '报告',
bug: '错误',
@@ -133,7 +132,6 @@ export const LOCALES = {
complete: '完成',
incomplete: '未完成',
pending: '待处理',
- processing: '处理中',
failed: '失败',
cancelled: '已取消',
timeout: '超时',
@@ -142,8 +140,6 @@ export const LOCALES = {
valid: '有效',
required: '必填',
optional: '可选',
- enabled: '启用',
- disabled: '禁用',
allowed: '允许',
denied: '拒绝',
approved: '已批准',
@@ -158,6 +154,8 @@ export const LOCALES = {
unauthenticated: '未认证',
authorized: '已授权',
unauthorized: '未授权',
+
+ // === 范围 / 角色 ===
public: '公开',
private: '私有',
protected: '受保护',
@@ -181,6 +179,8 @@ export const LOCALES = {
following: '关注中',
friend: '朋友',
contact: '联系人',
+
+ // === 组织 ===
group: '群组',
team: '团队',
organization: '组织',
@@ -190,8 +190,6 @@ export const LOCALES = {
task: '任务',
issue: '问题',
ticket: '工单',
- request: '请求',
- response: '响应',
message: '消息',
notification: '通知',
alert: '警报',
@@ -199,7 +197,6 @@ export const LOCALES = {
event: '事件',
activity: '活动',
log: '日志',
- report: '报告',
analytics: '分析',
statistics: '统计',
metrics: '指标',
@@ -251,134 +248,10 @@ export const LOCALES = {
which: '哪个',
whose: '谁的',
whom: '谁',
- myself: '我自己',
- yourself: '你自己',
- himself: '他自己',
- herself: '她自己',
- itself: '它自己',
- ourselves: '我们自己',
- yourselves: '你们自己',
- themselves: '他们自己',
- mine: '我的',
- yours: '你的',
- his: '他的',
- hers: '她的',
- its: '它的',
- ours: '我们的',
- yours: '你们的',
- theirs: '他们的',
- me: '我',
- you: '你',
- him: '他',
- her: '她',
- it: '它',
- us: '我们',
- them: '他们',
- my: '我的',
- your: '你的',
- his: '他的',
- her: '她的',
- its: '它的',
- our: '我们的',
- your: '你们的',
- their: '他们的',
- i: '我',
- you: '你',
- he: '他',
- she: '她',
- it: '它',
- we: '我们',
- they: '他们',
- am: '是',
- is: '是',
- are: '是',
- was: '是',
- were: '是',
- be: '是',
- been: '是',
- being: '是',
- have: '有',
- has: '有',
- had: '有',
- having: '有',
- do: '做',
- does: '做',
- did: '做',
- doing: '做',
- will: '会',
- would: '会',
- shall: '将',
- should: '应该',
- may: '可以',
- might: '可能',
- can: '能',
- could: '能',
- must: '必须',
- need: '需要',
- dare: '敢',
- ought: '应该',
- used: '过去常常',
- to: '到',
- of: '的',
- in: '在',
- for: '为了',
- on: '在',
- with: '和',
- at: '在',
- by: '通过',
- from: '从',
- into: '进入',
- during: '在...期间',
- before: '在...之前',
- after: '在...之后',
- above: '在...上面',
- below: '在...下面',
- between: '在...之间',
- under: '在...下面',
- over: '在...上面',
- across: '穿过',
- through: '通过',
- into: '进入',
- towards: '朝向',
- upon: '在...上面',
- about: '关于',
- against: '反对',
- among: '在...之中',
- along: '沿着',
- around: '周围',
- beyond: '超出',
- but: '但是',
- despite: '尽管',
- except: '除了',
- inside: '里面',
- outside: '外面',
- since: '自从',
- until: '直到',
- unless: '除非',
- whether: '是否',
- while: '当...时候',
- although: '虽然',
- because: '因为',
- if: '如果',
- once: '一旦',
- since: '自从',
- so: '所以',
- that: '那个',
- though: '虽然',
- till: '直到',
- unless: '除非',
- until: '直到',
- when: '当...时候',
- whenever: '每当',
- where: '哪里',
- wherever: '无论哪里',
- whereas: '然而',
- wherever: '无论哪里',
- while: '当...时候',
- why: '为什么',
},
-
+
'en-US': {
+ // === Actions ===
close: 'Close',
loading: 'Loading...',
success: 'Success',
@@ -459,6 +332,8 @@ export const LOCALES = {
hide: 'Hide',
visible: 'Visible',
hidden: 'Hidden',
+
+ // === Status ===
enabled: 'Enabled',
disabled: 'Disabled',
active: 'Active',
@@ -467,7 +342,6 @@ export const LOCALES = {
offline: 'Offline',
connected: 'Connected',
disconnected: 'Disconnected',
- loading: 'Loading',
loaded: 'Loaded',
saving: 'Saving',
saved: 'Saved',
@@ -486,12 +360,9 @@ export const LOCALES = {
receiving: 'Receiving',
received: 'Received',
connecting: 'Connecting',
- error: 'Error',
- warning: 'Warning',
- info: 'Info',
- success: 'Success',
+
+ // === Interaction ===
question: 'Question',
- help: 'Help',
feedback: 'Feedback',
report: 'Report',
bug: 'Bug',
@@ -505,7 +376,6 @@ export const LOCALES = {
complete: 'Complete',
incomplete: 'Incomplete',
pending: 'Pending',
- processing: 'Processing',
failed: 'Failed',
cancelled: 'Cancelled',
timeout: 'Timeout',
@@ -514,8 +384,6 @@ export const LOCALES = {
valid: 'Valid',
required: 'Required',
optional: 'Optional',
- enabled: 'Enabled',
- disabled: 'Disabled',
allowed: 'Allowed',
denied: 'Denied',
approved: 'Approved',
@@ -530,6 +398,8 @@ export const LOCALES = {
unauthenticated: 'Unauthenticated',
authorized: 'Authorized',
unauthorized: 'Unauthorized',
+
+ // === Scope / Role ===
public: 'Public',
private: 'Private',
protected: 'Protected',
@@ -553,6 +423,8 @@ export const LOCALES = {
following: 'Following',
friend: 'Friend',
contact: 'Contact',
+
+ // === Organization ===
group: 'Group',
team: 'Team',
organization: 'Organization',
@@ -562,8 +434,6 @@ export const LOCALES = {
task: 'Task',
issue: 'Issue',
ticket: 'Ticket',
- request: 'Request',
- response: 'Response',
message: 'Message',
notification: 'Notification',
alert: 'Alert',
@@ -571,7 +441,6 @@ export const LOCALES = {
event: 'Event',
activity: 'Activity',
log: 'Log',
- report: 'Report',
analytics: 'Analytics',
statistics: 'Statistics',
metrics: 'Metrics',
@@ -623,130 +492,5 @@ export const LOCALES = {
which: 'Which',
whose: 'Whose',
whom: 'Whom',
- myself: 'Myself',
- yourself: 'Yourself',
- himself: 'Himself',
- herself: 'Herself',
- itself: 'Itself',
- ourselves: 'Ourselves',
- yourselves: 'Yourselves',
- themselves: 'Themselves',
- mine: 'Mine',
- yours: 'Yours',
- his: 'His',
- hers: 'Hers',
- its: 'Its',
- ours: 'Ours',
- yours: 'Yours',
- theirs: 'Theirs',
- me: 'Me',
- you: 'You',
- him: 'Him',
- her: 'Her',
- it: 'It',
- us: 'Us',
- them: 'Them',
- my: 'My',
- your: 'Your',
- his: 'His',
- her: 'Her',
- its: 'Its',
- our: 'Our',
- your: 'Your',
- their: 'Their',
- i: 'I',
- you: 'You',
- he: 'He',
- she: 'She',
- it: 'It',
- we: 'We',
- they: 'They',
- am: 'Am',
- is: 'Is',
- are: 'Are',
- was: 'Was',
- were: 'Were',
- be: 'Be',
- been: 'Been',
- being: 'Being',
- have: 'Have',
- has: 'Has',
- had: 'Had',
- having: 'Having',
- do: 'Do',
- does: 'Does',
- did: 'Did',
- doing: 'Doing',
- will: 'Will',
- would: 'Would',
- shall: 'Shall',
- should: 'Should',
- may: 'May',
- might: 'Might',
- can: 'Can',
- could: 'Could',
- must: 'Must',
- need: 'Need',
- dare: 'Dare',
- ought: 'Ought',
- used: 'Used',
- to: 'To',
- of: 'Of',
- in: 'In',
- for: 'For',
- on: 'On',
- with: 'With',
- at: 'At',
- by: 'By',
- from: 'From',
- into: 'Into',
- during: 'During',
- before: 'Before',
- after: 'After',
- above: 'Above',
- below: 'Below',
- between: 'Between',
- under: 'Under',
- over: 'Over',
- across: 'Across',
- through: 'Through',
- into: 'Into',
- towards: 'Towards',
- upon: 'Upon',
- about: 'About',
- against: 'Against',
- among: 'Among',
- along: 'Along',
- around: 'Around',
- beyond: 'Beyond',
- but: 'But',
- despite: 'Despite',
- except: 'Except',
- inside: 'Inside',
- outside: 'Outside',
- since: 'Since',
- until: 'Until',
- unless: 'Unless',
- whether: 'Whether',
- while: 'While',
- although: 'Although',
- because: 'Because',
- if: 'If',
- once: 'Once',
- since: 'Since',
- so: 'So',
- that: 'That',
- though: 'Though',
- till: 'Till',
- unless: 'Unless',
- until: 'Until',
- when: 'When',
- whenever: 'Whenever',
- where: 'Where',
- wherever: 'Wherever',
- whereas: 'Whereas',
- wherever: 'Wherever',
- while: 'While',
- why: 'Why',
},
};
diff --git a/src/plugins.js b/src/plugins.js
index 8aa1ec0..dc97a54 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -1,7 +1,7 @@
/**
* MetonaToast Plugins - 插件系统
* @module plugins
- * @version 0.1.1
+ * @version 0.1.2
* @description 插件管理器 + 3 款预设插件 (keyboard / persistence / accessibility)
*/
@@ -21,15 +21,19 @@ class PluginManager {
console.warn(`Plugin "${name}" is already registered`);
return this;
}
- if (!plugin || typeof plugin !== 'object' || (!plugin.name && !plugin.version)) {
- console.error(`Invalid plugin "${name}"`);
+ if (!plugin || typeof plugin !== 'object' || !plugin.name) {
+ console.error(`Invalid plugin "${name}": must be an object with a "name" property`);
return this;
}
- this.plugins.set(name, { name, ...plugin, installed: false, enabled: true });
+ // 先存储再 install,确保 install 内通过 this 设置的实例属性保留在存储对象上
+ // 保留插件自带的 name(若有),否则使用注册 key 作为 name
+ const stored = { ...plugin, installed: false, enabled: true };
+ if (!stored.name) stored.name = name;
+ this.plugins.set(name, stored);
- if (plugin.install) {
- try { plugin.install(this); this.plugins.get(name).installed = true; }
+ if (stored.install) {
+ try { stored.install(this); stored.installed = true; }
catch (e) { console.error(`Failed to install plugin "${name}":`, e); }
}
return this;
diff --git a/src/styles.js b/src/styles.js
index e506285..ee3f0c1 100644
--- a/src/styles.js
+++ b/src/styles.js
@@ -1,7 +1,7 @@
/**
- * MetonaToast Styles - 样式管理(精简版 v0.1.1)
+ * MetonaToast Styles - 样式管理(精简版 v0.1.2)
* @module styles
- * @version 0.1.1
+ * @version 0.1.2
* @description 样式注入与主题管理,移除未使用的组件样式
*/
diff --git a/src/themes.js b/src/themes.js
index 0545950..bfe8b94 100644
--- a/src/themes.js
+++ b/src/themes.js
@@ -1,7 +1,7 @@
/**
* MetonaToast Themes - 主题管理
* @module themes
- * @version 0.1.1
+ * @version 0.1.2
* @description 主题系统、自定义主题和主题切换
*/
@@ -458,84 +458,4 @@ export const themeUtils = {
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 };
diff --git a/src/utils.js b/src/utils.js
index 767484d..965e99d 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,7 +1,7 @@
/**
* MetonaToast Utils - 精简工具函数
* @module utils
- * @version 0.1.1
+ * @version 0.1.2
* @description 仅保留核心模块实际使用的工具函数
*/
diff --git a/tests/index.test.js b/tests/index.test.js
index 6c2cda1..bb7ce02 100644
--- a/tests/index.test.js
+++ b/tests/index.test.js
@@ -1,7 +1,7 @@
/**
* MetonaToast 单元测试
* @module tests
- * @version 0.1.1
+ * @version 0.1.2
*/
import MeToast, { Toast, VERSION } from '../src/index.js';
@@ -121,8 +121,8 @@ describe('MetonaToast', () => {
describe('版本信息', () => {
test('应该有正确的版本号', () => {
- expect(VERSION).toBe('0.1.1');
- expect(MeToast.version).toBe('0.1.1');
+ expect(VERSION).toBe('0.1.2');
+ expect(MeToast.version).toBe('0.1.2');
});
});
@@ -558,13 +558,52 @@ describe('MetonaToast', () => {
message: '消息',
duration: 5000,
});
-
+
toast._pause();
expect(toast.paused).toBe(true);
-
+
toast._resume();
expect(toast.paused).toBe(false);
});
+
+ test('暂停恢复应保持 remaining 时间连续性', () => {
+ const toast = MeToast.success({
+ message: '消息',
+ duration: 5000,
+ });
+
+ // 模拟计时开始
+ toast.config.duration = 5000;
+ // 直接设置内部状态模拟已运行 2 秒
+ toast.startedAt = Date.now() - 2000;
+ toast.remaining = 3000;
+ toast.paused = false;
+
+ const remBefore = toast.remaining;
+ toast._pause();
+ expect(toast.paused).toBe(true);
+ expect(toast.remaining).toBeLessThanOrEqual(remBefore);
+
+ const pausedRemaining = toast.remaining;
+ toast._resume();
+ expect(toast.paused).toBe(false);
+ // resume 后 remaining 应保持与暂停时一致(允许微小误差)
+ expect(toast.remaining).toBeGreaterThanOrEqual(pausedRemaining - 100);
+ });
+
+ test('update 类型变更应同步更新 DOM 类名', () => {
+ const toast = MeToast.info('test');
+ expect(toast.type).toBe('info');
+ toast.update({ type: 'error', message: 'changed' });
+ expect(toast.type).toBe('error');
+ // DOM 验证需要实际浏览器环境,此处验证状态变更
+ });
+
+ test('update 类型变更无 DOM 时不抛异常', () => {
+ const toast = new Toast({ message: 'test', type: 'info' });
+ expect(() => toast.update({ type: 'success', message: 'ok' })).not.toThrow();
+ expect(toast.type).toBe('success');
+ });
test('应该能够获取Toast配置', () => {
const toast = MeToast.success({
@@ -583,7 +622,7 @@ describe('MetonaToast', () => {
MeToast.success('消息');
const status = MeToast.getStatus();
- expect(status.version).toBe('0.1.1');
+ expect(status.version).toBe('0.1.2');
expect(status.toasts).toBeGreaterThanOrEqual(0);
expect(status.theme).toBeDefined();
expect(status.locale).toBeDefined();
@@ -1391,4 +1430,23 @@ describe('新增功能', () => {
});
expect(t.config.render).toBeInstanceOf(Function);
});
+
+ test('onError 回调在钩子异常时触发', () => {
+ const errors = [];
+ MeToast.configure({ onError: (e) => errors.push(e) });
+ const badHandler = () => { throw new Error('test-error'); };
+ Toast.on('afterShow', badHandler);
+ const toast = MeToast.success('test');
+ Toast.trigger('afterShow', toast);
+ expect(errors.length).toBeGreaterThanOrEqual(1);
+ expect(errors[0].error.message).toBe('test-error');
+ Toast.off('afterShow', badHandler);
+ MeToast.configure({ onError: null });
+ });
+
+ test('destroy 重复调用不抛异常', () => {
+ MeToast.success('test');
+ MeToast.destroy();
+ expect(() => MeToast.destroy()).not.toThrow();
+ });
});
diff --git a/types/index.d.ts b/types/index.d.ts
index a28695c..58dba7a 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -1,6 +1,6 @@
/**
* MetonaToast TypeScript 类型定义
- * @version 0.1.1
+ * @version 0.1.2
*/
// 基础类型
@@ -276,6 +276,8 @@ export interface ToastConfig {
resetTimerOnUpdate?: boolean;
/** 后台发送系统通知 */
notifyWhenHidden?: boolean;
+ /** 全局错误回调 */
+ onError?: (errorInfo: { hook?: string; source?: string; error: Error; toast?: ToastInstance }) => void;
/** 分组名称 */
group?: string;
/** 语言 */