chore: v0.1.2 — 全面修复、优化与增强
修复: - core.js: _resume 计时器 BUG (startedAt 被 _startTimer 覆盖导致暂停恢复失效) - plugins.js: register 在 install 前展开导致 handler 实例丢失 - locales.js: zh-CN/en-US 大量重复 key 覆盖正确翻译值 - core.js: configure 改为原地更新避免 _config 引用断裂 优化: - core.js: update() 类型变更时同步更新 DOM 类名和进度条颜色 - animations.js: getActiveCount 返回已注册动画数量 - themes.js: 移除 presetThemes 死代码和重复注册循环 - constants.js: THEMES.auto 从字符串改为规范化对象 - index.js: destroy 添加防重复调用保护 增强: - core.js: 新增 onError 全局错误回调,钩子异常和定时器错误可被外部捕获 - styles.js: _resume 校准时间基准补偿注释 - 139 tests 全部通过
This commit is contained in:
+68
-31
@@ -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 ? `<div class="met-title">${escapeHTML(this.title)}</div>` : '';
|
||||
@@ -453,7 +489,7 @@ class Toast {
|
||||
: (this.message ? `<div class="met-message">${escapeHTML(this.message)}</div>` : '');
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user