release: v0.3.0 — 自定义动画真实生效、max超限修复、钩子系统完整化、destroy全量清理
- 自定义动画:animations.register() 注册的动画动态注入 keyframes 真实生效(此前仅存 Map 实际 fallback slide) - max 超限:通过实例注册表真正 close() 最早的 toast(此前仅删内存记录,DOM 残留) - 钩子收敛:beforeClose/beforeUpdate 支持返回 false 拦截;接入 click/hover/dragStart/dragEnd/animationStart/animationEnd/progressStart/progressEnd/configChange/themeChange/localeChange/beforeInit/afterInit/beforeDestroy/afterDestroy;HOOK_NAMES 移除永不触发的声明 - destroy() 全量清理:hooks、实例注册表、全部 metona-toast-* 样式标签;动画注册表真正清空 - applyThemeVariables 重复注入修复;init() 支持插件对象并复活 _destroyed;AnimationConfig 类型放宽 - VERSION 统一到 constants.ts 唯一维护;删除失效 scripts;新增 .gitattributes 统一 LF - docs.html 补 v0.2.1 新 API 文档;CHANGELOG 更新;测试 309 用例全过
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
# 统一文本文件行尾为 LF(跨平台协作)
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# 二进制文件不进行行尾转换
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.ico binary
|
||||||
|
*.gif binary
|
||||||
|
*.svg text
|
||||||
@@ -2,6 +2,28 @@
|
|||||||
|
|
||||||
All notable changes to MetonaToast will be documented in this file.
|
All notable changes to MetonaToast will be documented in this file.
|
||||||
|
|
||||||
|
## [0.3.0] - 2026-08-08
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **自定义动画真实生效**:`animations.register()` 注册的动画此前仅写入内存 Map,实际渲染始终 fallback 到 slide。现在注册表驱动 `_buildClassName` 并动态注入对应 `@keyframes` + 类规则,README 的注册教程名实相符
|
||||||
|
- **`max` 超限真正关闭最早的 toast**:此前超限时仅从内存 Map 删除记录,DOM 上的旧 toast 依然显示。现在通过实例注册表找到最早的 toast 并真正 `close()`(触发离场动画与生命周期回调)
|
||||||
|
- **`destroy()` 全量清理**:新增清理 `Toast._hooks` 钩子、`Toast._registry` 实例注册表、`metona-toast-theme-css` / `metona-toast-custom-styles` / `metona-toast-anim-styles` 样式标签;动画注册表改为真正清空
|
||||||
|
- **`applyThemeVariables` 重复注入**:修复多次调用追加相同 id 的 style 标签问题(改为先移除旧元素)
|
||||||
|
- **`init()` 复活**:`destroy()` 后调用 `init()` 重置 `_destroyed` 标志,恢复可初始化状态
|
||||||
|
- **失效脚本清理**:删除 `docs`(typedoc 未安装)、`example`(examples/ 目录不存在)脚本,新增 `serve` 脚本
|
||||||
|
- **`AnimationConfig.enter/leave` 类型放宽**:支持 `opacity: 0` 等数值属性(此前仅 `string`,README 示例在 TS 严格模式下报错)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **钩子系统完整化**:此前 `HOOK_NAMES` 声明 20+ 钩子但多数永不触发。现在全部声明均有触发点:
|
||||||
|
- 拦截语义:`beforeClose` / `beforeUpdate` 返回 `false` 可分别阻止关闭、更新
|
||||||
|
- 新触发点:`click` / `hover` / `dragStart` / `dragEnd` / `animationStart` / `animationEnd` / `progressStart` / `progressEnd` / `configChange` / `themeChange` / `localeChange` / `beforeInit` / `afterInit` / `beforeDestroy` / `afterDestroy`
|
||||||
|
- **`init({ plugins })` 支持插件对象**:与 `use()` 一致,可传 `string | Plugin`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **VERSION 唯一来源**:版本号统一在 `src/constants.ts` 维护,`api.ts` / `index.ts` 引用,消除三处硬编码漂移
|
||||||
|
- **`HOOK_NAMES` 收敛**:移除永不触发的 `progressUpdate` / `custom` 声明
|
||||||
|
- 版本升级至 **v0.3.0**(含行为增强,按 semver minor 处理)
|
||||||
|
|
||||||
## [0.2.1] - 2026-07-25
|
## [0.2.1] - 2026-07-25
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
Vendored
+212
-99
@@ -5,7 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Utils — 工具函数
|
* MetonaToast Utils — 工具函数
|
||||||
* @module utils
|
* @module utils
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 生成唯一ID
|
* 生成唯一ID
|
||||||
@@ -42,7 +42,7 @@ const prefersDark = () => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Icons — 图标SVG定义
|
* MetonaToast Icons — 图标SVG定义
|
||||||
* @module icons
|
* @module icons
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description 107 个内置 SVG 图标
|
* @description 107 个内置 SVG 图标
|
||||||
*/
|
*/
|
||||||
const ICONS = {
|
const ICONS = {
|
||||||
@@ -539,7 +539,7 @@ const ICONS = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Locales — 国际化翻译数据
|
* MetonaToast Locales — 国际化翻译数据
|
||||||
* @module locales
|
* @module locales
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
const LOCALES = {
|
const LOCALES = {
|
||||||
@@ -1026,8 +1026,12 @@ const LOCALES = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants — 常量定义
|
* MetonaToast Constants — 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
|
*/
|
||||||
|
const VERSION = '0.3.0';
|
||||||
/**
|
/**
|
||||||
* 默认配置
|
* 默认配置
|
||||||
*/
|
*/
|
||||||
@@ -1285,11 +1289,15 @@ const THEMES = {
|
|||||||
closeHoverBg: 'rgba(245, 158, 11, 0.1)',
|
closeHoverBg: 'rgba(245, 158, 11, 0.1)',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 动画类型
|
||||||
|
*/
|
||||||
|
const ANIMATION_TYPES = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom', 'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MetonaToast Styles — 样式管理
|
* MetonaToast Styles — 样式管理
|
||||||
* @module styles
|
* @module styles
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
// 样式缓存
|
// 样式缓存
|
||||||
let styleElement = null;
|
let styleElement = null;
|
||||||
@@ -1743,7 +1751,7 @@ const injectStyles = () => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Themes — 主题管理
|
* MetonaToast Themes — 主题管理
|
||||||
* @module themes
|
* @module themes
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
// 当前主题状态
|
// 当前主题状态
|
||||||
let currentTheme = 'auto';
|
let currentTheme = 'auto';
|
||||||
@@ -2087,7 +2095,7 @@ const themeUtils = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast i18n — 国际化管理
|
* MetonaToast i18n — 国际化管理
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
// 当前语言状态
|
// 当前语言状态
|
||||||
let currentLocale = 'zh-CN';
|
let currentLocale = 'zh-CN';
|
||||||
@@ -2643,10 +2651,126 @@ const i18nUtils = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MetonaToast Animations — 动画管理
|
||||||
|
* @module animations
|
||||||
|
* @version 0.2.1
|
||||||
|
*/
|
||||||
|
// 动画缓存
|
||||||
|
const animationMap = new Map();
|
||||||
|
// 内置动画(keyframes 由 styles.ts 注入,无需动态生成)
|
||||||
|
const BUILTIN_ANIMATIONS = new Set(ANIMATION_TYPES);
|
||||||
|
// 动态注入的自定义动画样式(惰性创建)
|
||||||
|
let animStyleElement = null;
|
||||||
|
const injectedAnimations = new Set();
|
||||||
|
/**
|
||||||
|
* 将 CSS 属性对象转换为内联 CSS 字符串
|
||||||
|
*/
|
||||||
|
const toCss = (props) => Object.entries(props)
|
||||||
|
.map(([key, value]) => `${key}:${value}`)
|
||||||
|
.join(';');
|
||||||
|
/**
|
||||||
|
* 为自定义动画生成入场 keyframes + 类规则
|
||||||
|
*/
|
||||||
|
const buildAnimationCSS = (name, config) => {
|
||||||
|
const enter = toCss(config.enter);
|
||||||
|
return `
|
||||||
|
@keyframes met-${name}-in {
|
||||||
|
from { ${enter}; }
|
||||||
|
}
|
||||||
|
.met-anim-${name}.met-toast { opacity: 0; }
|
||||||
|
.met-anim-${name}.met-toast.met-show {
|
||||||
|
animation: met-${name}-in ${config.duration}ms ${config.easing} forwards;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 确保自定义动画的 CSS 已注入文档(幂等,内置动画自动跳过)
|
||||||
|
*/
|
||||||
|
const ensureAnimationCSS = (name) => {
|
||||||
|
if (typeof document === 'undefined')
|
||||||
|
return;
|
||||||
|
if (BUILTIN_ANIMATIONS.has(name))
|
||||||
|
return;
|
||||||
|
const config = animationMap.get(name);
|
||||||
|
if (!config || injectedAnimations.has(name))
|
||||||
|
return;
|
||||||
|
if (!animStyleElement) {
|
||||||
|
animStyleElement = document.getElementById('metona-toast-anim-styles');
|
||||||
|
if (!animStyleElement) {
|
||||||
|
animStyleElement = document.createElement('style');
|
||||||
|
animStyleElement.id = 'metona-toast-anim-styles';
|
||||||
|
document.head.appendChild(animStyleElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animStyleElement.textContent += buildAnimationCSS(name, config);
|
||||||
|
injectedAnimations.add(name);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 检查动画是否存在(内置或自定义)
|
||||||
|
*/
|
||||||
|
const hasAnimation = (name) => animationMap.has(name);
|
||||||
|
// 注册默认动画
|
||||||
|
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||||
|
animationMap.set(name, {
|
||||||
|
name,
|
||||||
|
enter: config.enter,
|
||||||
|
leave: config.leave,
|
||||||
|
duration: config.duration,
|
||||||
|
easing: config.easing,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* 动画工具函数
|
||||||
|
*/
|
||||||
|
const animationUtils = {
|
||||||
|
register(name, config) {
|
||||||
|
animationMap.set(name, {
|
||||||
|
name,
|
||||||
|
enter: config.enter || {},
|
||||||
|
leave: config.leave || {},
|
||||||
|
duration: config.duration || 300,
|
||||||
|
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unregister(name) {
|
||||||
|
animationMap.delete(name);
|
||||||
|
},
|
||||||
|
get(name) {
|
||||||
|
return animationMap.get(name) || null;
|
||||||
|
},
|
||||||
|
getAnimationNames() {
|
||||||
|
return Array.from(animationMap.keys());
|
||||||
|
},
|
||||||
|
getActiveCount() {
|
||||||
|
return animationMap.size;
|
||||||
|
},
|
||||||
|
cancelAll() {
|
||||||
|
// CSS动画由浏览器原生管理,无需手动取消
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
animationMap.clear();
|
||||||
|
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||||
|
animationMap.set(name, {
|
||||||
|
name,
|
||||||
|
enter: config.enter,
|
||||||
|
leave: config.leave,
|
||||||
|
duration: config.duration,
|
||||||
|
easing: config.easing,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
animationMap.clear();
|
||||||
|
injectedAnimations.clear();
|
||||||
|
animStyleElement = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Toast 类 — 核心通知组件
|
* Toast 类 — 核心通知组件
|
||||||
@@ -2674,8 +2798,9 @@ class Toast {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
||||||
|
* toast 参数可选:全局事件(configChange/themeChange 等)不关联具体 toast
|
||||||
*/
|
*/
|
||||||
static trigger(name, toast) {
|
static trigger(name, toast = null) {
|
||||||
const list = this._hooks.get(name);
|
const list = this._hooks.get(name);
|
||||||
if (!list || list.length === 0)
|
if (!list || list.length === 0)
|
||||||
return true;
|
return true;
|
||||||
@@ -2690,7 +2815,7 @@ class Toast {
|
|||||||
console.error('Hook error:', name, e);
|
console.error('Hook error:', name, e);
|
||||||
if (Toast._onError) {
|
if (Toast._onError) {
|
||||||
try {
|
try {
|
||||||
Toast._onError({ hook: name, error: e, toast });
|
Toast._onError({ hook: name, error: e, toast: toast });
|
||||||
}
|
}
|
||||||
catch (_e) { /* noop */ }
|
catch (_e) { /* noop */ }
|
||||||
}
|
}
|
||||||
@@ -2745,6 +2870,7 @@ class Toast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
injectStyles();
|
injectStyles();
|
||||||
|
ensureAnimationCSS(this.config.animation || 'slide');
|
||||||
const container = this._getContainer();
|
const container = this._getContainer();
|
||||||
const { theme, c, t } = this._palette();
|
const { theme, c, t } = this._palette();
|
||||||
this._limitToasts(container);
|
this._limitToasts(container);
|
||||||
@@ -2764,6 +2890,7 @@ class Toast {
|
|||||||
else {
|
else {
|
||||||
container.appendChild(el);
|
container.appendChild(el);
|
||||||
}
|
}
|
||||||
|
Toast._registry.set(this.id, this);
|
||||||
this._bindEvents(el);
|
this._bindEvents(el);
|
||||||
this._startTimer();
|
this._startTimer();
|
||||||
// 进入动画
|
// 进入动画
|
||||||
@@ -2831,18 +2958,22 @@ class Toast {
|
|||||||
_limitToasts(container) {
|
_limitToasts(container) {
|
||||||
const max = this.config.max || 6;
|
const max = this.config.max || 6;
|
||||||
const list = Array.from(container.querySelectorAll('.met-toast'));
|
const list = Array.from(container.querySelectorAll('.met-toast'));
|
||||||
if (list.length >= max) {
|
while (list.length >= max) {
|
||||||
const first = list[0];
|
const el = list.shift();
|
||||||
const id = first?.dataset.id;
|
const id = el?.dataset.id;
|
||||||
if (id && Toast._removeToast) {
|
if (!id)
|
||||||
|
continue;
|
||||||
|
const existing = Toast._registry.get(id);
|
||||||
|
if (existing) {
|
||||||
|
existing.close(true);
|
||||||
|
}
|
||||||
|
else if (Toast._removeToast) {
|
||||||
Toast._removeToast(id);
|
Toast._removeToast(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_buildClassName(theme) {
|
_buildClassName(theme) {
|
||||||
const CSS_ANIMS = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom',
|
const anim = hasAnimation(this.config.animation || '') ? this.config.animation : 'slide';
|
||||||
'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
|
|
||||||
const anim = CSS_ANIMS.includes(this.config.animation || '') ? this.config.animation : 'slide';
|
|
||||||
return [
|
return [
|
||||||
'met-toast',
|
'met-toast',
|
||||||
`met-${this.type}`,
|
`met-${this.type}`,
|
||||||
@@ -2919,6 +3050,7 @@ class Toast {
|
|||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Toast.trigger('click', this);
|
||||||
if (this.config.closeOnClick && !target.closest('.met-close')) {
|
if (this.config.closeOnClick && !target.closest('.met-close')) {
|
||||||
if (typeof this.config.onClick === 'function') {
|
if (typeof this.config.onClick === 'function') {
|
||||||
try {
|
try {
|
||||||
@@ -2935,8 +3067,14 @@ class Toast {
|
|||||||
el.addEventListener('click', eventHandler);
|
el.addEventListener('click', eventHandler);
|
||||||
this._cleanups.push(() => el.removeEventListener('click', eventHandler));
|
this._cleanups.push(() => el.removeEventListener('click', eventHandler));
|
||||||
if (this.config.pauseOnHover && (this.config.duration || 0) > 0) {
|
if (this.config.pauseOnHover && (this.config.duration || 0) > 0) {
|
||||||
const mouseEnter = () => this._pause();
|
const mouseEnter = () => {
|
||||||
const mouseLeave = () => this._resume();
|
Toast.trigger('hover', this);
|
||||||
|
this._pause();
|
||||||
|
};
|
||||||
|
const mouseLeave = () => {
|
||||||
|
Toast.trigger('hover', this);
|
||||||
|
this._resume();
|
||||||
|
};
|
||||||
el.addEventListener('mouseenter', mouseEnter);
|
el.addEventListener('mouseenter', mouseEnter);
|
||||||
el.addEventListener('mouseleave', mouseLeave);
|
el.addEventListener('mouseleave', mouseLeave);
|
||||||
this._cleanups.push(() => {
|
this._cleanups.push(() => {
|
||||||
@@ -2944,6 +3082,15 @@ class Toast {
|
|||||||
el.removeEventListener('mouseleave', mouseLeave);
|
el.removeEventListener('mouseleave', mouseLeave);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 入场动画生命周期钩子(离场为过渡/内联样式,不产生 animation 事件)
|
||||||
|
const animStart = () => Toast.trigger('animationStart', this);
|
||||||
|
const animEnd = () => Toast.trigger('animationEnd', this);
|
||||||
|
el.addEventListener('animationstart', animStart);
|
||||||
|
el.addEventListener('animationend', animEnd);
|
||||||
|
this._cleanups.push(() => {
|
||||||
|
el.removeEventListener('animationstart', animStart);
|
||||||
|
el.removeEventListener('animationend', animEnd);
|
||||||
|
});
|
||||||
if (this.config.draggable) {
|
if (this.config.draggable) {
|
||||||
this._bindDrag(el);
|
this._bindDrag(el);
|
||||||
}
|
}
|
||||||
@@ -2959,6 +3106,7 @@ class Toast {
|
|||||||
el.setPointerCapture(e.pointerId);
|
el.setPointerCapture(e.pointerId);
|
||||||
el.style.transition = 'none';
|
el.style.transition = 'none';
|
||||||
this._pause();
|
this._pause();
|
||||||
|
Toast.trigger('dragStart', this);
|
||||||
};
|
};
|
||||||
const move = (e) => {
|
const move = (e) => {
|
||||||
if (!dragging)
|
if (!dragging)
|
||||||
@@ -2985,6 +3133,7 @@ class Toast {
|
|||||||
this._resume();
|
this._resume();
|
||||||
}
|
}
|
||||||
dx = dy = 0;
|
dx = dy = 0;
|
||||||
|
Toast.trigger('dragEnd', this);
|
||||||
};
|
};
|
||||||
el.addEventListener('pointerdown', down);
|
el.addEventListener('pointerdown', down);
|
||||||
el.addEventListener('pointermove', move);
|
el.addEventListener('pointermove', move);
|
||||||
@@ -3003,6 +3152,7 @@ class Toast {
|
|||||||
if (!resuming) {
|
if (!resuming) {
|
||||||
this.startedAt = Date.now();
|
this.startedAt = Date.now();
|
||||||
this.remaining = this.config.duration || 0;
|
this.remaining = this.config.duration || 0;
|
||||||
|
Toast.trigger('progressStart', this);
|
||||||
}
|
}
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
if (this.paused || this.closing)
|
if (this.paused || this.closing)
|
||||||
@@ -3017,6 +3167,7 @@ class Toast {
|
|||||||
this.barEl.style.transform = t;
|
this.barEl.style.transform = t;
|
||||||
}
|
}
|
||||||
if (this.remaining <= 0) {
|
if (this.remaining <= 0) {
|
||||||
|
Toast.trigger('progressEnd', this);
|
||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3050,7 +3201,9 @@ class Toast {
|
|||||||
this._startTimer(true);
|
this._startTimer(true);
|
||||||
}
|
}
|
||||||
update(partial) {
|
update(partial) {
|
||||||
Toast.trigger('beforeUpdate', this);
|
// beforeUpdate 钩子 — 返回 false 可阻止更新
|
||||||
|
if (!Toast.trigger('beforeUpdate', this))
|
||||||
|
return this;
|
||||||
const typeChanged = partial.type && partial.type !== this.type;
|
const typeChanged = partial.type && partial.type !== this.type;
|
||||||
if (partial.type)
|
if (partial.type)
|
||||||
this.type = partial.type;
|
this.type = partial.type;
|
||||||
@@ -3147,14 +3300,17 @@ class Toast {
|
|||||||
this.el.parentNode.removeChild(this.el);
|
this.el.parentNode.removeChild(this.el);
|
||||||
}
|
}
|
||||||
this.el = null;
|
this.el = null;
|
||||||
|
Toast._registry.delete(this.id);
|
||||||
if (Toast._removeToast)
|
if (Toast._removeToast)
|
||||||
Toast._removeToast(this.id);
|
Toast._removeToast(this.id);
|
||||||
}
|
}
|
||||||
close(immediate = false) {
|
close(immediate = false) {
|
||||||
if (this.closing)
|
if (this.closing)
|
||||||
return;
|
return;
|
||||||
|
// beforeClose 钩子 — 返回 false 可阻止关闭
|
||||||
|
if (!Toast.trigger('beforeClose', this))
|
||||||
|
return;
|
||||||
this.closing = true;
|
this.closing = true;
|
||||||
Toast.trigger('beforeClose', this);
|
|
||||||
if (this.rafId !== null)
|
if (this.rafId !== null)
|
||||||
cancelAnimationFrame(this.rafId);
|
cancelAnimationFrame(this.rafId);
|
||||||
this._cleanups.forEach(fn => { try {
|
this._cleanups.forEach(fn => { try {
|
||||||
@@ -3210,12 +3366,15 @@ class Toast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Toast.trigger('afterClose', this);
|
Toast.trigger('afterClose', this);
|
||||||
|
Toast._registry.delete(this.id);
|
||||||
if (Toast._removeToast)
|
if (Toast._removeToast)
|
||||||
Toast._removeToast(this.id);
|
Toast._removeToast(this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 静态钩子系统
|
// 静态钩子系统
|
||||||
Toast._hooks = new Map();
|
Toast._hooks = new Map();
|
||||||
|
// 实例注册表 — 用于通过 id 反查实例(max 超限移除、外部接管等)
|
||||||
|
Toast._registry = new Map();
|
||||||
// 由 api.ts 注入的回调,通过 setCallbacks() 设置
|
// 由 api.ts 注入的回调,通过 setCallbacks() 设置
|
||||||
Toast._onError = null;
|
Toast._onError = null;
|
||||||
Toast._removeToast = null;
|
Toast._removeToast = null;
|
||||||
@@ -3227,7 +3386,7 @@ const _containerCache = new Map();
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Templates — HTML 模板辅助函数
|
* MetonaToast Templates — HTML 模板辅助函数
|
||||||
* @module templates
|
* @module templates
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description confirm / prompt / progress / action 的 DOM 模板
|
* @description confirm / prompt / progress / action 的 DOM 模板
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
@@ -3299,7 +3458,7 @@ const actionHTML = (actions) => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Plugins — 插件系统
|
* MetonaToast Plugins — 插件系统
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 插件管理器
|
* 插件管理器
|
||||||
@@ -3519,7 +3678,7 @@ const defaultPluginManager = new PluginManager();
|
|||||||
/**
|
/**
|
||||||
* MetonaToast API — meToast 核心 API 对象
|
* MetonaToast API — meToast 核心 API 对象
|
||||||
* @module api
|
* @module api
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 参数标准化工具
|
* 参数标准化工具
|
||||||
@@ -3544,15 +3703,13 @@ const normalizeArgs = (args, defaultType = 'default') => {
|
|||||||
message: '',
|
message: '',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** 版本号常量 */
|
|
||||||
const VERSION$1 = '0.2.1';
|
|
||||||
/**
|
/**
|
||||||
* meToast API 对象
|
* meToast API 对象
|
||||||
*/
|
*/
|
||||||
const meToast = {
|
const meToast = {
|
||||||
_toasts: new Map(),
|
_toasts: new Map(),
|
||||||
_config: { ...DEFAULTS },
|
_config: { ...DEFAULTS },
|
||||||
version: '0.2.1',
|
version: VERSION,
|
||||||
configure(opts) {
|
configure(opts) {
|
||||||
if (!opts || typeof opts !== 'object')
|
if (!opts || typeof opts !== 'object')
|
||||||
return this;
|
return this;
|
||||||
@@ -3563,6 +3720,7 @@ const meToast = {
|
|||||||
if (opts.locale) {
|
if (opts.locale) {
|
||||||
setCurrentLocale(opts.locale);
|
setCurrentLocale(opts.locale);
|
||||||
}
|
}
|
||||||
|
Toast.trigger('configChange');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
_emit(opts) {
|
_emit(opts) {
|
||||||
@@ -4034,6 +4192,8 @@ const meToast = {
|
|||||||
},
|
},
|
||||||
// ====== 以下方法依赖子模块(themes/i18n/plugins/animations),由 index.ts 注入 ======
|
// ====== 以下方法依赖子模块(themes/i18n/plugins/animations),由 index.ts 注入 ======
|
||||||
init(options = {}) {
|
init(options = {}) {
|
||||||
|
Toast.trigger('beforeInit');
|
||||||
|
this._destroyed = false;
|
||||||
if (options.config) {
|
if (options.config) {
|
||||||
this.configure(options.config);
|
this.configure(options.config);
|
||||||
}
|
}
|
||||||
@@ -4048,11 +4208,12 @@ const meToast = {
|
|||||||
this.use(p);
|
this.use(p);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Toast.trigger('afterInit');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
getStatus() {
|
getStatus() {
|
||||||
return {
|
return {
|
||||||
version: VERSION$1,
|
version: VERSION,
|
||||||
toasts: this._toasts.size,
|
toasts: this._toasts.size,
|
||||||
theme: this.themes?.getCurrentTheme?.() || 'auto',
|
theme: this.themes?.getCurrentTheme?.() || 'auto',
|
||||||
locale: this.i18n?.getCurrentLocale?.() || 'zh-CN',
|
locale: this.i18n?.getCurrentLocale?.() || 'zh-CN',
|
||||||
@@ -4066,6 +4227,7 @@ const meToast = {
|
|||||||
updateConfig(config) {
|
updateConfig(config) {
|
||||||
if (config && typeof config === 'object') {
|
if (config && typeof config === 'object') {
|
||||||
Object.assign(this._config, config);
|
Object.assign(this._config, config);
|
||||||
|
Toast.trigger('configChange');
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
@@ -4073,6 +4235,7 @@ const meToast = {
|
|||||||
const keys = Object.keys(this._config);
|
const keys = Object.keys(this._config);
|
||||||
keys.forEach(k => delete this._config[k]);
|
keys.forEach(k => delete this._config[k]);
|
||||||
Object.assign(this._config, DEFAULTS);
|
Object.assign(this._config, DEFAULTS);
|
||||||
|
Toast.trigger('configChange');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
use(plugin, options = {}) {
|
use(plugin, options = {}) {
|
||||||
@@ -4113,6 +4276,7 @@ const meToast = {
|
|||||||
if (this._destroyed)
|
if (this._destroyed)
|
||||||
return;
|
return;
|
||||||
this._destroyed = true;
|
this._destroyed = true;
|
||||||
|
Toast.trigger('beforeDestroy');
|
||||||
this.dismiss();
|
this.dismiss();
|
||||||
// 清理子模块
|
// 清理子模块
|
||||||
if (this.plugins && typeof this.plugins.destroy === 'function') {
|
if (this.plugins && typeof this.plugins.destroy === 'function') {
|
||||||
@@ -4129,20 +4293,26 @@ const meToast = {
|
|||||||
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
|
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
|
||||||
this.i18n.clearLocaleListeners();
|
this.i18n.clearLocaleListeners();
|
||||||
}
|
}
|
||||||
if (this.animations && typeof this.animations.cancelAll === 'function') {
|
if (this.animations && typeof this.animations.destroy === 'function') {
|
||||||
this.animations.cancelAll();
|
this.animations.destroy();
|
||||||
}
|
}
|
||||||
// 清理 DOM
|
// 清理 DOM 与静态资源
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
const containers = document.querySelectorAll('.met-container');
|
const containers = document.querySelectorAll('.met-container');
|
||||||
containers.forEach((c) => { if (c.parentNode)
|
containers.forEach((c) => { if (c.parentNode)
|
||||||
c.parentNode.removeChild(c); });
|
c.parentNode.removeChild(c); });
|
||||||
const style = document.getElementById('metona-toast-styles');
|
['metona-toast-styles', 'metona-toast-theme-css', 'metona-toast-custom-styles', 'metona-toast-anim-styles'].forEach((id) => {
|
||||||
if (style && style.parentNode)
|
const el = document.getElementById(id);
|
||||||
style.parentNode.removeChild(style);
|
if (el && el.parentNode)
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this._toasts.clear();
|
this._toasts.clear();
|
||||||
_containerCache.clear();
|
_containerCache.clear();
|
||||||
|
Toast._registry.clear();
|
||||||
|
// afterDestroy 必须在清理 hooks 之前触发
|
||||||
|
Toast.trigger('afterDestroy');
|
||||||
|
Toast._hooks.clear();
|
||||||
},
|
},
|
||||||
// 子模块引用(由 index.ts 注入实际实现)
|
// 子模块引用(由 index.ts 注入实际实现)
|
||||||
animations: null,
|
animations: null,
|
||||||
@@ -4167,77 +4337,13 @@ Toast.setCallbacks({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* MetonaToast Animations — 动画管理
|
|
||||||
* @module animations
|
|
||||||
* @version 0.2.1
|
|
||||||
*/
|
|
||||||
// 动画缓存
|
|
||||||
const animationMap = new Map();
|
|
||||||
// 注册默认动画
|
|
||||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
|
||||||
animationMap.set(name, {
|
|
||||||
name,
|
|
||||||
enter: config.enter,
|
|
||||||
leave: config.leave,
|
|
||||||
duration: config.duration,
|
|
||||||
easing: config.easing,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* 动画工具函数
|
|
||||||
*/
|
|
||||||
const animationUtils = {
|
|
||||||
register(name, config) {
|
|
||||||
animationMap.set(name, {
|
|
||||||
name,
|
|
||||||
enter: config.enter || {},
|
|
||||||
leave: config.leave || {},
|
|
||||||
duration: config.duration || 300,
|
|
||||||
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
unregister(name) {
|
|
||||||
animationMap.delete(name);
|
|
||||||
},
|
|
||||||
get(name) {
|
|
||||||
return animationMap.get(name) || null;
|
|
||||||
},
|
|
||||||
getAnimationNames() {
|
|
||||||
return Array.from(animationMap.keys());
|
|
||||||
},
|
|
||||||
getActiveCount() {
|
|
||||||
return animationMap.size;
|
|
||||||
},
|
|
||||||
cancelAll() {
|
|
||||||
// CSS动画由浏览器原生管理,无需手动取消
|
|
||||||
},
|
|
||||||
reset() {
|
|
||||||
animationMap.clear();
|
|
||||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
|
||||||
animationMap.set(name, {
|
|
||||||
name,
|
|
||||||
enter: config.enter,
|
|
||||||
leave: config.leave,
|
|
||||||
duration: config.duration,
|
|
||||||
easing: config.easing,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
animationMap.clear();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MetonaToast — 轻量级Toast通知库
|
* MetonaToast — 轻量级Toast通知库
|
||||||
* @module metona-toast
|
* @module metona-toast
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
// 版本信息
|
|
||||||
const VERSION = '0.2.1';
|
|
||||||
/**
|
/**
|
||||||
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
||||||
*/
|
*/
|
||||||
@@ -4257,6 +4363,13 @@ if (typeof themeUtils.initTheme === 'function') {
|
|||||||
if (typeof i18nUtils.initI18n === 'function') {
|
if (typeof i18nUtils.initI18n === 'function') {
|
||||||
i18nUtils.initI18n();
|
i18nUtils.initI18n();
|
||||||
}
|
}
|
||||||
|
// 全局钩子 — 主题/语言变化转发到 Toast 钩子系统
|
||||||
|
if (typeof themeUtils.addThemeListener === 'function') {
|
||||||
|
themeUtils.addThemeListener(() => Toast.trigger('themeChange'));
|
||||||
|
}
|
||||||
|
if (typeof i18nUtils.addLocaleListener === 'function') {
|
||||||
|
i18nUtils.addLocaleListener(() => Toast.trigger('localeChange'));
|
||||||
|
}
|
||||||
// 浏览器环境全局注册
|
// 浏览器环境全局注册
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.MeToast = enhancedMeToast;
|
window.MeToast = enhancedMeToast;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+18
-6
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast — 核心类型定义
|
* MetonaToast — 核心类型定义
|
||||||
* @module types
|
* @module types
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/** Toast 通知类型(107种内置图标类型) */
|
/** Toast 通知类型(107种内置图标类型) */
|
||||||
type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading' | 'check' | 'x' | 'alert' | 'question' | 'star' | 'heart' | 'bell' | 'mail' | 'settings' | 'user' | 'home' | 'search' | 'plus' | 'minus' | 'edit' | 'trash' | 'download' | 'upload' | 'share' | 'link' | 'external' | 'clock' | 'calendar' | 'map' | 'compass' | 'globe' | 'wifi' | 'cloud' | 'sun' | 'moon' | 'zap' | 'activity' | 'cpu' | 'database' | 'server' | 'terminal' | 'code' | 'git' | 'package' | 'layers' | 'grid' | 'list' | 'filter' | 'sort' | 'refresh' | 'sync' | 'power' | 'battery' | 'bluetooth' | 'volume' | 'mic' | 'camera' | 'image' | 'video' | 'music' | 'file' | 'folder' | 'clipboard' | 'save' | 'print' | 'eye' | 'eyeOff' | 'lock' | 'unlock' | 'shield' | 'key' | 'flag' | 'bookmark' | 'tag' | 'gift' | 'award' | 'target' | 'crosshair' | 'move' | 'maximize' | 'minimize' | 'copy' | 'cut' | 'paste' | 'rotateCw' | 'rotateCcw' | 'zoomIn' | 'zoomOut' | 'crop' | 'sliders' | 'toggleLeft' | 'toggleRight' | 'checkCircle' | 'xCircle' | 'alertCircle' | 'infoCircle' | 'helpCircle' | 'alertTriangle' | 'checkSquare' | 'square' | 'circle' | 'triangle' | 'hexagon' | 'octagon' | 'pentagon' | 'diamond';
|
type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading' | 'check' | 'x' | 'alert' | 'question' | 'star' | 'heart' | 'bell' | 'mail' | 'settings' | 'user' | 'home' | 'search' | 'plus' | 'minus' | 'edit' | 'trash' | 'download' | 'upload' | 'share' | 'link' | 'external' | 'clock' | 'calendar' | 'map' | 'compass' | 'globe' | 'wifi' | 'cloud' | 'sun' | 'moon' | 'zap' | 'activity' | 'cpu' | 'database' | 'server' | 'terminal' | 'code' | 'git' | 'package' | 'layers' | 'grid' | 'list' | 'filter' | 'sort' | 'refresh' | 'sync' | 'power' | 'battery' | 'bluetooth' | 'volume' | 'mic' | 'camera' | 'image' | 'video' | 'music' | 'file' | 'folder' | 'clipboard' | 'save' | 'print' | 'eye' | 'eyeOff' | 'lock' | 'unlock' | 'shield' | 'key' | 'flag' | 'bookmark' | 'tag' | 'gift' | 'award' | 'target' | 'crosshair' | 'move' | 'maximize' | 'minimize' | 'copy' | 'cut' | 'paste' | 'rotateCw' | 'rotateCcw' | 'zoomIn' | 'zoomOut' | 'crop' | 'sliders' | 'toggleLeft' | 'toggleRight' | 'checkCircle' | 'xCircle' | 'alertCircle' | 'infoCircle' | 'helpCircle' | 'alertTriangle' | 'checkSquare' | 'square' | 'circle' | 'triangle' | 'hexagon' | 'octagon' | 'pentagon' | 'diamond';
|
||||||
@@ -193,8 +193,8 @@ interface StatusInfo {
|
|||||||
animations: number;
|
animations: number;
|
||||||
}
|
}
|
||||||
interface AnimationConfig {
|
interface AnimationConfig {
|
||||||
enter: Record<string, string>;
|
enter: Record<string, string | number>;
|
||||||
leave: Record<string, string>;
|
leave: Record<string, string | number>;
|
||||||
duration: number;
|
duration: number;
|
||||||
easing: string;
|
easing: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -417,7 +417,7 @@ declare global {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -425,6 +425,7 @@ declare global {
|
|||||||
*/
|
*/
|
||||||
declare class Toast implements ToastInstance {
|
declare class Toast implements ToastInstance {
|
||||||
static _hooks: Map<string, Array<(toast: Toast) => boolean | void>>;
|
static _hooks: Map<string, Array<(toast: Toast) => boolean | void>>;
|
||||||
|
static _registry: Map<string, Toast>;
|
||||||
private static _onError;
|
private static _onError;
|
||||||
private static _removeToast;
|
private static _removeToast;
|
||||||
/**
|
/**
|
||||||
@@ -438,8 +439,9 @@ declare class Toast implements ToastInstance {
|
|||||||
static off(name: string, fn: (toast: Toast) => boolean | void): void;
|
static off(name: string, fn: (toast: Toast) => boolean | void): void;
|
||||||
/**
|
/**
|
||||||
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
||||||
|
* toast 参数可选:全局事件(configChange/themeChange 等)不关联具体 toast
|
||||||
*/
|
*/
|
||||||
static trigger(name: string, toast: Toast): boolean;
|
static trigger(name: string, toast?: Toast | null): boolean;
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -486,7 +488,17 @@ declare class Toast implements ToastInstance {
|
|||||||
_destroy(): void;
|
_destroy(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const VERSION = "0.2.1";
|
/**
|
||||||
|
* MetonaToast Constants — 常量定义
|
||||||
|
* @module constants
|
||||||
|
* @version 0.3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
|
*/
|
||||||
|
declare const VERSION = "0.3.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
||||||
*/
|
*/
|
||||||
|
|||||||
Vendored
+212
-99
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Utils — 工具函数
|
* MetonaToast Utils — 工具函数
|
||||||
* @module utils
|
* @module utils
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 生成唯一ID
|
* 生成唯一ID
|
||||||
@@ -38,7 +38,7 @@ const prefersDark = () => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Icons — 图标SVG定义
|
* MetonaToast Icons — 图标SVG定义
|
||||||
* @module icons
|
* @module icons
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description 107 个内置 SVG 图标
|
* @description 107 个内置 SVG 图标
|
||||||
*/
|
*/
|
||||||
const ICONS = {
|
const ICONS = {
|
||||||
@@ -535,7 +535,7 @@ const ICONS = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Locales — 国际化翻译数据
|
* MetonaToast Locales — 国际化翻译数据
|
||||||
* @module locales
|
* @module locales
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
const LOCALES = {
|
const LOCALES = {
|
||||||
@@ -1022,8 +1022,12 @@ const LOCALES = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants — 常量定义
|
* MetonaToast Constants — 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
|
*/
|
||||||
|
const VERSION = '0.3.0';
|
||||||
/**
|
/**
|
||||||
* 默认配置
|
* 默认配置
|
||||||
*/
|
*/
|
||||||
@@ -1281,11 +1285,15 @@ const THEMES = {
|
|||||||
closeHoverBg: 'rgba(245, 158, 11, 0.1)',
|
closeHoverBg: 'rgba(245, 158, 11, 0.1)',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 动画类型
|
||||||
|
*/
|
||||||
|
const ANIMATION_TYPES = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom', 'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MetonaToast Styles — 样式管理
|
* MetonaToast Styles — 样式管理
|
||||||
* @module styles
|
* @module styles
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
// 样式缓存
|
// 样式缓存
|
||||||
let styleElement = null;
|
let styleElement = null;
|
||||||
@@ -1739,7 +1747,7 @@ const injectStyles = () => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Themes — 主题管理
|
* MetonaToast Themes — 主题管理
|
||||||
* @module themes
|
* @module themes
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
// 当前主题状态
|
// 当前主题状态
|
||||||
let currentTheme = 'auto';
|
let currentTheme = 'auto';
|
||||||
@@ -2083,7 +2091,7 @@ const themeUtils = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast i18n — 国际化管理
|
* MetonaToast i18n — 国际化管理
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
// 当前语言状态
|
// 当前语言状态
|
||||||
let currentLocale = 'zh-CN';
|
let currentLocale = 'zh-CN';
|
||||||
@@ -2639,10 +2647,126 @@ const i18nUtils = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MetonaToast Animations — 动画管理
|
||||||
|
* @module animations
|
||||||
|
* @version 0.2.1
|
||||||
|
*/
|
||||||
|
// 动画缓存
|
||||||
|
const animationMap = new Map();
|
||||||
|
// 内置动画(keyframes 由 styles.ts 注入,无需动态生成)
|
||||||
|
const BUILTIN_ANIMATIONS = new Set(ANIMATION_TYPES);
|
||||||
|
// 动态注入的自定义动画样式(惰性创建)
|
||||||
|
let animStyleElement = null;
|
||||||
|
const injectedAnimations = new Set();
|
||||||
|
/**
|
||||||
|
* 将 CSS 属性对象转换为内联 CSS 字符串
|
||||||
|
*/
|
||||||
|
const toCss = (props) => Object.entries(props)
|
||||||
|
.map(([key, value]) => `${key}:${value}`)
|
||||||
|
.join(';');
|
||||||
|
/**
|
||||||
|
* 为自定义动画生成入场 keyframes + 类规则
|
||||||
|
*/
|
||||||
|
const buildAnimationCSS = (name, config) => {
|
||||||
|
const enter = toCss(config.enter);
|
||||||
|
return `
|
||||||
|
@keyframes met-${name}-in {
|
||||||
|
from { ${enter}; }
|
||||||
|
}
|
||||||
|
.met-anim-${name}.met-toast { opacity: 0; }
|
||||||
|
.met-anim-${name}.met-toast.met-show {
|
||||||
|
animation: met-${name}-in ${config.duration}ms ${config.easing} forwards;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 确保自定义动画的 CSS 已注入文档(幂等,内置动画自动跳过)
|
||||||
|
*/
|
||||||
|
const ensureAnimationCSS = (name) => {
|
||||||
|
if (typeof document === 'undefined')
|
||||||
|
return;
|
||||||
|
if (BUILTIN_ANIMATIONS.has(name))
|
||||||
|
return;
|
||||||
|
const config = animationMap.get(name);
|
||||||
|
if (!config || injectedAnimations.has(name))
|
||||||
|
return;
|
||||||
|
if (!animStyleElement) {
|
||||||
|
animStyleElement = document.getElementById('metona-toast-anim-styles');
|
||||||
|
if (!animStyleElement) {
|
||||||
|
animStyleElement = document.createElement('style');
|
||||||
|
animStyleElement.id = 'metona-toast-anim-styles';
|
||||||
|
document.head.appendChild(animStyleElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animStyleElement.textContent += buildAnimationCSS(name, config);
|
||||||
|
injectedAnimations.add(name);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 检查动画是否存在(内置或自定义)
|
||||||
|
*/
|
||||||
|
const hasAnimation = (name) => animationMap.has(name);
|
||||||
|
// 注册默认动画
|
||||||
|
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||||
|
animationMap.set(name, {
|
||||||
|
name,
|
||||||
|
enter: config.enter,
|
||||||
|
leave: config.leave,
|
||||||
|
duration: config.duration,
|
||||||
|
easing: config.easing,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* 动画工具函数
|
||||||
|
*/
|
||||||
|
const animationUtils = {
|
||||||
|
register(name, config) {
|
||||||
|
animationMap.set(name, {
|
||||||
|
name,
|
||||||
|
enter: config.enter || {},
|
||||||
|
leave: config.leave || {},
|
||||||
|
duration: config.duration || 300,
|
||||||
|
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unregister(name) {
|
||||||
|
animationMap.delete(name);
|
||||||
|
},
|
||||||
|
get(name) {
|
||||||
|
return animationMap.get(name) || null;
|
||||||
|
},
|
||||||
|
getAnimationNames() {
|
||||||
|
return Array.from(animationMap.keys());
|
||||||
|
},
|
||||||
|
getActiveCount() {
|
||||||
|
return animationMap.size;
|
||||||
|
},
|
||||||
|
cancelAll() {
|
||||||
|
// CSS动画由浏览器原生管理,无需手动取消
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
animationMap.clear();
|
||||||
|
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||||
|
animationMap.set(name, {
|
||||||
|
name,
|
||||||
|
enter: config.enter,
|
||||||
|
leave: config.leave,
|
||||||
|
duration: config.duration,
|
||||||
|
easing: config.easing,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
animationMap.clear();
|
||||||
|
injectedAnimations.clear();
|
||||||
|
animStyleElement = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Toast 类 — 核心通知组件
|
* Toast 类 — 核心通知组件
|
||||||
@@ -2670,8 +2794,9 @@ class Toast {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
||||||
|
* toast 参数可选:全局事件(configChange/themeChange 等)不关联具体 toast
|
||||||
*/
|
*/
|
||||||
static trigger(name, toast) {
|
static trigger(name, toast = null) {
|
||||||
const list = this._hooks.get(name);
|
const list = this._hooks.get(name);
|
||||||
if (!list || list.length === 0)
|
if (!list || list.length === 0)
|
||||||
return true;
|
return true;
|
||||||
@@ -2686,7 +2811,7 @@ class Toast {
|
|||||||
console.error('Hook error:', name, e);
|
console.error('Hook error:', name, e);
|
||||||
if (Toast._onError) {
|
if (Toast._onError) {
|
||||||
try {
|
try {
|
||||||
Toast._onError({ hook: name, error: e, toast });
|
Toast._onError({ hook: name, error: e, toast: toast });
|
||||||
}
|
}
|
||||||
catch (_e) { /* noop */ }
|
catch (_e) { /* noop */ }
|
||||||
}
|
}
|
||||||
@@ -2741,6 +2866,7 @@ class Toast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
injectStyles();
|
injectStyles();
|
||||||
|
ensureAnimationCSS(this.config.animation || 'slide');
|
||||||
const container = this._getContainer();
|
const container = this._getContainer();
|
||||||
const { theme, c, t } = this._palette();
|
const { theme, c, t } = this._palette();
|
||||||
this._limitToasts(container);
|
this._limitToasts(container);
|
||||||
@@ -2760,6 +2886,7 @@ class Toast {
|
|||||||
else {
|
else {
|
||||||
container.appendChild(el);
|
container.appendChild(el);
|
||||||
}
|
}
|
||||||
|
Toast._registry.set(this.id, this);
|
||||||
this._bindEvents(el);
|
this._bindEvents(el);
|
||||||
this._startTimer();
|
this._startTimer();
|
||||||
// 进入动画
|
// 进入动画
|
||||||
@@ -2827,18 +2954,22 @@ class Toast {
|
|||||||
_limitToasts(container) {
|
_limitToasts(container) {
|
||||||
const max = this.config.max || 6;
|
const max = this.config.max || 6;
|
||||||
const list = Array.from(container.querySelectorAll('.met-toast'));
|
const list = Array.from(container.querySelectorAll('.met-toast'));
|
||||||
if (list.length >= max) {
|
while (list.length >= max) {
|
||||||
const first = list[0];
|
const el = list.shift();
|
||||||
const id = first?.dataset.id;
|
const id = el?.dataset.id;
|
||||||
if (id && Toast._removeToast) {
|
if (!id)
|
||||||
|
continue;
|
||||||
|
const existing = Toast._registry.get(id);
|
||||||
|
if (existing) {
|
||||||
|
existing.close(true);
|
||||||
|
}
|
||||||
|
else if (Toast._removeToast) {
|
||||||
Toast._removeToast(id);
|
Toast._removeToast(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_buildClassName(theme) {
|
_buildClassName(theme) {
|
||||||
const CSS_ANIMS = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom',
|
const anim = hasAnimation(this.config.animation || '') ? this.config.animation : 'slide';
|
||||||
'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
|
|
||||||
const anim = CSS_ANIMS.includes(this.config.animation || '') ? this.config.animation : 'slide';
|
|
||||||
return [
|
return [
|
||||||
'met-toast',
|
'met-toast',
|
||||||
`met-${this.type}`,
|
`met-${this.type}`,
|
||||||
@@ -2915,6 +3046,7 @@ class Toast {
|
|||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Toast.trigger('click', this);
|
||||||
if (this.config.closeOnClick && !target.closest('.met-close')) {
|
if (this.config.closeOnClick && !target.closest('.met-close')) {
|
||||||
if (typeof this.config.onClick === 'function') {
|
if (typeof this.config.onClick === 'function') {
|
||||||
try {
|
try {
|
||||||
@@ -2931,8 +3063,14 @@ class Toast {
|
|||||||
el.addEventListener('click', eventHandler);
|
el.addEventListener('click', eventHandler);
|
||||||
this._cleanups.push(() => el.removeEventListener('click', eventHandler));
|
this._cleanups.push(() => el.removeEventListener('click', eventHandler));
|
||||||
if (this.config.pauseOnHover && (this.config.duration || 0) > 0) {
|
if (this.config.pauseOnHover && (this.config.duration || 0) > 0) {
|
||||||
const mouseEnter = () => this._pause();
|
const mouseEnter = () => {
|
||||||
const mouseLeave = () => this._resume();
|
Toast.trigger('hover', this);
|
||||||
|
this._pause();
|
||||||
|
};
|
||||||
|
const mouseLeave = () => {
|
||||||
|
Toast.trigger('hover', this);
|
||||||
|
this._resume();
|
||||||
|
};
|
||||||
el.addEventListener('mouseenter', mouseEnter);
|
el.addEventListener('mouseenter', mouseEnter);
|
||||||
el.addEventListener('mouseleave', mouseLeave);
|
el.addEventListener('mouseleave', mouseLeave);
|
||||||
this._cleanups.push(() => {
|
this._cleanups.push(() => {
|
||||||
@@ -2940,6 +3078,15 @@ class Toast {
|
|||||||
el.removeEventListener('mouseleave', mouseLeave);
|
el.removeEventListener('mouseleave', mouseLeave);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 入场动画生命周期钩子(离场为过渡/内联样式,不产生 animation 事件)
|
||||||
|
const animStart = () => Toast.trigger('animationStart', this);
|
||||||
|
const animEnd = () => Toast.trigger('animationEnd', this);
|
||||||
|
el.addEventListener('animationstart', animStart);
|
||||||
|
el.addEventListener('animationend', animEnd);
|
||||||
|
this._cleanups.push(() => {
|
||||||
|
el.removeEventListener('animationstart', animStart);
|
||||||
|
el.removeEventListener('animationend', animEnd);
|
||||||
|
});
|
||||||
if (this.config.draggable) {
|
if (this.config.draggable) {
|
||||||
this._bindDrag(el);
|
this._bindDrag(el);
|
||||||
}
|
}
|
||||||
@@ -2955,6 +3102,7 @@ class Toast {
|
|||||||
el.setPointerCapture(e.pointerId);
|
el.setPointerCapture(e.pointerId);
|
||||||
el.style.transition = 'none';
|
el.style.transition = 'none';
|
||||||
this._pause();
|
this._pause();
|
||||||
|
Toast.trigger('dragStart', this);
|
||||||
};
|
};
|
||||||
const move = (e) => {
|
const move = (e) => {
|
||||||
if (!dragging)
|
if (!dragging)
|
||||||
@@ -2981,6 +3129,7 @@ class Toast {
|
|||||||
this._resume();
|
this._resume();
|
||||||
}
|
}
|
||||||
dx = dy = 0;
|
dx = dy = 0;
|
||||||
|
Toast.trigger('dragEnd', this);
|
||||||
};
|
};
|
||||||
el.addEventListener('pointerdown', down);
|
el.addEventListener('pointerdown', down);
|
||||||
el.addEventListener('pointermove', move);
|
el.addEventListener('pointermove', move);
|
||||||
@@ -2999,6 +3148,7 @@ class Toast {
|
|||||||
if (!resuming) {
|
if (!resuming) {
|
||||||
this.startedAt = Date.now();
|
this.startedAt = Date.now();
|
||||||
this.remaining = this.config.duration || 0;
|
this.remaining = this.config.duration || 0;
|
||||||
|
Toast.trigger('progressStart', this);
|
||||||
}
|
}
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
if (this.paused || this.closing)
|
if (this.paused || this.closing)
|
||||||
@@ -3013,6 +3163,7 @@ class Toast {
|
|||||||
this.barEl.style.transform = t;
|
this.barEl.style.transform = t;
|
||||||
}
|
}
|
||||||
if (this.remaining <= 0) {
|
if (this.remaining <= 0) {
|
||||||
|
Toast.trigger('progressEnd', this);
|
||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3046,7 +3197,9 @@ class Toast {
|
|||||||
this._startTimer(true);
|
this._startTimer(true);
|
||||||
}
|
}
|
||||||
update(partial) {
|
update(partial) {
|
||||||
Toast.trigger('beforeUpdate', this);
|
// beforeUpdate 钩子 — 返回 false 可阻止更新
|
||||||
|
if (!Toast.trigger('beforeUpdate', this))
|
||||||
|
return this;
|
||||||
const typeChanged = partial.type && partial.type !== this.type;
|
const typeChanged = partial.type && partial.type !== this.type;
|
||||||
if (partial.type)
|
if (partial.type)
|
||||||
this.type = partial.type;
|
this.type = partial.type;
|
||||||
@@ -3143,14 +3296,17 @@ class Toast {
|
|||||||
this.el.parentNode.removeChild(this.el);
|
this.el.parentNode.removeChild(this.el);
|
||||||
}
|
}
|
||||||
this.el = null;
|
this.el = null;
|
||||||
|
Toast._registry.delete(this.id);
|
||||||
if (Toast._removeToast)
|
if (Toast._removeToast)
|
||||||
Toast._removeToast(this.id);
|
Toast._removeToast(this.id);
|
||||||
}
|
}
|
||||||
close(immediate = false) {
|
close(immediate = false) {
|
||||||
if (this.closing)
|
if (this.closing)
|
||||||
return;
|
return;
|
||||||
|
// beforeClose 钩子 — 返回 false 可阻止关闭
|
||||||
|
if (!Toast.trigger('beforeClose', this))
|
||||||
|
return;
|
||||||
this.closing = true;
|
this.closing = true;
|
||||||
Toast.trigger('beforeClose', this);
|
|
||||||
if (this.rafId !== null)
|
if (this.rafId !== null)
|
||||||
cancelAnimationFrame(this.rafId);
|
cancelAnimationFrame(this.rafId);
|
||||||
this._cleanups.forEach(fn => { try {
|
this._cleanups.forEach(fn => { try {
|
||||||
@@ -3206,12 +3362,15 @@ class Toast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Toast.trigger('afterClose', this);
|
Toast.trigger('afterClose', this);
|
||||||
|
Toast._registry.delete(this.id);
|
||||||
if (Toast._removeToast)
|
if (Toast._removeToast)
|
||||||
Toast._removeToast(this.id);
|
Toast._removeToast(this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 静态钩子系统
|
// 静态钩子系统
|
||||||
Toast._hooks = new Map();
|
Toast._hooks = new Map();
|
||||||
|
// 实例注册表 — 用于通过 id 反查实例(max 超限移除、外部接管等)
|
||||||
|
Toast._registry = new Map();
|
||||||
// 由 api.ts 注入的回调,通过 setCallbacks() 设置
|
// 由 api.ts 注入的回调,通过 setCallbacks() 设置
|
||||||
Toast._onError = null;
|
Toast._onError = null;
|
||||||
Toast._removeToast = null;
|
Toast._removeToast = null;
|
||||||
@@ -3223,7 +3382,7 @@ const _containerCache = new Map();
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Templates — HTML 模板辅助函数
|
* MetonaToast Templates — HTML 模板辅助函数
|
||||||
* @module templates
|
* @module templates
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description confirm / prompt / progress / action 的 DOM 模板
|
* @description confirm / prompt / progress / action 的 DOM 模板
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
@@ -3295,7 +3454,7 @@ const actionHTML = (actions) => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Plugins — 插件系统
|
* MetonaToast Plugins — 插件系统
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 插件管理器
|
* 插件管理器
|
||||||
@@ -3515,7 +3674,7 @@ const defaultPluginManager = new PluginManager();
|
|||||||
/**
|
/**
|
||||||
* MetonaToast API — meToast 核心 API 对象
|
* MetonaToast API — meToast 核心 API 对象
|
||||||
* @module api
|
* @module api
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 参数标准化工具
|
* 参数标准化工具
|
||||||
@@ -3540,15 +3699,13 @@ const normalizeArgs = (args, defaultType = 'default') => {
|
|||||||
message: '',
|
message: '',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** 版本号常量 */
|
|
||||||
const VERSION$1 = '0.2.1';
|
|
||||||
/**
|
/**
|
||||||
* meToast API 对象
|
* meToast API 对象
|
||||||
*/
|
*/
|
||||||
const meToast = {
|
const meToast = {
|
||||||
_toasts: new Map(),
|
_toasts: new Map(),
|
||||||
_config: { ...DEFAULTS },
|
_config: { ...DEFAULTS },
|
||||||
version: '0.2.1',
|
version: VERSION,
|
||||||
configure(opts) {
|
configure(opts) {
|
||||||
if (!opts || typeof opts !== 'object')
|
if (!opts || typeof opts !== 'object')
|
||||||
return this;
|
return this;
|
||||||
@@ -3559,6 +3716,7 @@ const meToast = {
|
|||||||
if (opts.locale) {
|
if (opts.locale) {
|
||||||
setCurrentLocale(opts.locale);
|
setCurrentLocale(opts.locale);
|
||||||
}
|
}
|
||||||
|
Toast.trigger('configChange');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
_emit(opts) {
|
_emit(opts) {
|
||||||
@@ -4030,6 +4188,8 @@ const meToast = {
|
|||||||
},
|
},
|
||||||
// ====== 以下方法依赖子模块(themes/i18n/plugins/animations),由 index.ts 注入 ======
|
// ====== 以下方法依赖子模块(themes/i18n/plugins/animations),由 index.ts 注入 ======
|
||||||
init(options = {}) {
|
init(options = {}) {
|
||||||
|
Toast.trigger('beforeInit');
|
||||||
|
this._destroyed = false;
|
||||||
if (options.config) {
|
if (options.config) {
|
||||||
this.configure(options.config);
|
this.configure(options.config);
|
||||||
}
|
}
|
||||||
@@ -4044,11 +4204,12 @@ const meToast = {
|
|||||||
this.use(p);
|
this.use(p);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Toast.trigger('afterInit');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
getStatus() {
|
getStatus() {
|
||||||
return {
|
return {
|
||||||
version: VERSION$1,
|
version: VERSION,
|
||||||
toasts: this._toasts.size,
|
toasts: this._toasts.size,
|
||||||
theme: this.themes?.getCurrentTheme?.() || 'auto',
|
theme: this.themes?.getCurrentTheme?.() || 'auto',
|
||||||
locale: this.i18n?.getCurrentLocale?.() || 'zh-CN',
|
locale: this.i18n?.getCurrentLocale?.() || 'zh-CN',
|
||||||
@@ -4062,6 +4223,7 @@ const meToast = {
|
|||||||
updateConfig(config) {
|
updateConfig(config) {
|
||||||
if (config && typeof config === 'object') {
|
if (config && typeof config === 'object') {
|
||||||
Object.assign(this._config, config);
|
Object.assign(this._config, config);
|
||||||
|
Toast.trigger('configChange');
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
@@ -4069,6 +4231,7 @@ const meToast = {
|
|||||||
const keys = Object.keys(this._config);
|
const keys = Object.keys(this._config);
|
||||||
keys.forEach(k => delete this._config[k]);
|
keys.forEach(k => delete this._config[k]);
|
||||||
Object.assign(this._config, DEFAULTS);
|
Object.assign(this._config, DEFAULTS);
|
||||||
|
Toast.trigger('configChange');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
use(plugin, options = {}) {
|
use(plugin, options = {}) {
|
||||||
@@ -4109,6 +4272,7 @@ const meToast = {
|
|||||||
if (this._destroyed)
|
if (this._destroyed)
|
||||||
return;
|
return;
|
||||||
this._destroyed = true;
|
this._destroyed = true;
|
||||||
|
Toast.trigger('beforeDestroy');
|
||||||
this.dismiss();
|
this.dismiss();
|
||||||
// 清理子模块
|
// 清理子模块
|
||||||
if (this.plugins && typeof this.plugins.destroy === 'function') {
|
if (this.plugins && typeof this.plugins.destroy === 'function') {
|
||||||
@@ -4125,20 +4289,26 @@ const meToast = {
|
|||||||
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
|
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
|
||||||
this.i18n.clearLocaleListeners();
|
this.i18n.clearLocaleListeners();
|
||||||
}
|
}
|
||||||
if (this.animations && typeof this.animations.cancelAll === 'function') {
|
if (this.animations && typeof this.animations.destroy === 'function') {
|
||||||
this.animations.cancelAll();
|
this.animations.destroy();
|
||||||
}
|
}
|
||||||
// 清理 DOM
|
// 清理 DOM 与静态资源
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
const containers = document.querySelectorAll('.met-container');
|
const containers = document.querySelectorAll('.met-container');
|
||||||
containers.forEach((c) => { if (c.parentNode)
|
containers.forEach((c) => { if (c.parentNode)
|
||||||
c.parentNode.removeChild(c); });
|
c.parentNode.removeChild(c); });
|
||||||
const style = document.getElementById('metona-toast-styles');
|
['metona-toast-styles', 'metona-toast-theme-css', 'metona-toast-custom-styles', 'metona-toast-anim-styles'].forEach((id) => {
|
||||||
if (style && style.parentNode)
|
const el = document.getElementById(id);
|
||||||
style.parentNode.removeChild(style);
|
if (el && el.parentNode)
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this._toasts.clear();
|
this._toasts.clear();
|
||||||
_containerCache.clear();
|
_containerCache.clear();
|
||||||
|
Toast._registry.clear();
|
||||||
|
// afterDestroy 必须在清理 hooks 之前触发
|
||||||
|
Toast.trigger('afterDestroy');
|
||||||
|
Toast._hooks.clear();
|
||||||
},
|
},
|
||||||
// 子模块引用(由 index.ts 注入实际实现)
|
// 子模块引用(由 index.ts 注入实际实现)
|
||||||
animations: null,
|
animations: null,
|
||||||
@@ -4163,77 +4333,13 @@ Toast.setCallbacks({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* MetonaToast Animations — 动画管理
|
|
||||||
* @module animations
|
|
||||||
* @version 0.2.1
|
|
||||||
*/
|
|
||||||
// 动画缓存
|
|
||||||
const animationMap = new Map();
|
|
||||||
// 注册默认动画
|
|
||||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
|
||||||
animationMap.set(name, {
|
|
||||||
name,
|
|
||||||
enter: config.enter,
|
|
||||||
leave: config.leave,
|
|
||||||
duration: config.duration,
|
|
||||||
easing: config.easing,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* 动画工具函数
|
|
||||||
*/
|
|
||||||
const animationUtils = {
|
|
||||||
register(name, config) {
|
|
||||||
animationMap.set(name, {
|
|
||||||
name,
|
|
||||||
enter: config.enter || {},
|
|
||||||
leave: config.leave || {},
|
|
||||||
duration: config.duration || 300,
|
|
||||||
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
unregister(name) {
|
|
||||||
animationMap.delete(name);
|
|
||||||
},
|
|
||||||
get(name) {
|
|
||||||
return animationMap.get(name) || null;
|
|
||||||
},
|
|
||||||
getAnimationNames() {
|
|
||||||
return Array.from(animationMap.keys());
|
|
||||||
},
|
|
||||||
getActiveCount() {
|
|
||||||
return animationMap.size;
|
|
||||||
},
|
|
||||||
cancelAll() {
|
|
||||||
// CSS动画由浏览器原生管理,无需手动取消
|
|
||||||
},
|
|
||||||
reset() {
|
|
||||||
animationMap.clear();
|
|
||||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
|
||||||
animationMap.set(name, {
|
|
||||||
name,
|
|
||||||
enter: config.enter,
|
|
||||||
leave: config.leave,
|
|
||||||
duration: config.duration,
|
|
||||||
easing: config.easing,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
animationMap.clear();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MetonaToast — 轻量级Toast通知库
|
* MetonaToast — 轻量级Toast通知库
|
||||||
* @module metona-toast
|
* @module metona-toast
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
// 版本信息
|
|
||||||
const VERSION = '0.2.1';
|
|
||||||
/**
|
/**
|
||||||
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
||||||
*/
|
*/
|
||||||
@@ -4253,6 +4359,13 @@ if (typeof themeUtils.initTheme === 'function') {
|
|||||||
if (typeof i18nUtils.initI18n === 'function') {
|
if (typeof i18nUtils.initI18n === 'function') {
|
||||||
i18nUtils.initI18n();
|
i18nUtils.initI18n();
|
||||||
}
|
}
|
||||||
|
// 全局钩子 — 主题/语言变化转发到 Toast 钩子系统
|
||||||
|
if (typeof themeUtils.addThemeListener === 'function') {
|
||||||
|
themeUtils.addThemeListener(() => Toast.trigger('themeChange'));
|
||||||
|
}
|
||||||
|
if (typeof i18nUtils.addLocaleListener === 'function') {
|
||||||
|
i18nUtils.addLocaleListener(() => Toast.trigger('localeChange'));
|
||||||
|
}
|
||||||
// 浏览器环境全局注册
|
// 浏览器环境全局注册
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.MeToast = enhancedMeToast;
|
window.MeToast = enhancedMeToast;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+212
-99
@@ -7,7 +7,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Utils — 工具函数
|
* MetonaToast Utils — 工具函数
|
||||||
* @module utils
|
* @module utils
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 生成唯一ID
|
* 生成唯一ID
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Icons — 图标SVG定义
|
* MetonaToast Icons — 图标SVG定义
|
||||||
* @module icons
|
* @module icons
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description 107 个内置 SVG 图标
|
* @description 107 个内置 SVG 图标
|
||||||
*/
|
*/
|
||||||
const ICONS = {
|
const ICONS = {
|
||||||
@@ -541,7 +541,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Locales — 国际化翻译数据
|
* MetonaToast Locales — 国际化翻译数据
|
||||||
* @module locales
|
* @module locales
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
const LOCALES = {
|
const LOCALES = {
|
||||||
@@ -1028,8 +1028,12 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants — 常量定义
|
* MetonaToast Constants — 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
|
*/
|
||||||
|
const VERSION = '0.3.0';
|
||||||
/**
|
/**
|
||||||
* 默认配置
|
* 默认配置
|
||||||
*/
|
*/
|
||||||
@@ -1287,11 +1291,15 @@
|
|||||||
closeHoverBg: 'rgba(245, 158, 11, 0.1)',
|
closeHoverBg: 'rgba(245, 158, 11, 0.1)',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 动画类型
|
||||||
|
*/
|
||||||
|
const ANIMATION_TYPES = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom', 'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MetonaToast Styles — 样式管理
|
* MetonaToast Styles — 样式管理
|
||||||
* @module styles
|
* @module styles
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
// 样式缓存
|
// 样式缓存
|
||||||
let styleElement = null;
|
let styleElement = null;
|
||||||
@@ -1745,7 +1753,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Themes — 主题管理
|
* MetonaToast Themes — 主题管理
|
||||||
* @module themes
|
* @module themes
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
// 当前主题状态
|
// 当前主题状态
|
||||||
let currentTheme = 'auto';
|
let currentTheme = 'auto';
|
||||||
@@ -2089,7 +2097,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast i18n — 国际化管理
|
* MetonaToast i18n — 国际化管理
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
// 当前语言状态
|
// 当前语言状态
|
||||||
let currentLocale = 'zh-CN';
|
let currentLocale = 'zh-CN';
|
||||||
@@ -2645,10 +2653,126 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MetonaToast Animations — 动画管理
|
||||||
|
* @module animations
|
||||||
|
* @version 0.2.1
|
||||||
|
*/
|
||||||
|
// 动画缓存
|
||||||
|
const animationMap = new Map();
|
||||||
|
// 内置动画(keyframes 由 styles.ts 注入,无需动态生成)
|
||||||
|
const BUILTIN_ANIMATIONS = new Set(ANIMATION_TYPES);
|
||||||
|
// 动态注入的自定义动画样式(惰性创建)
|
||||||
|
let animStyleElement = null;
|
||||||
|
const injectedAnimations = new Set();
|
||||||
|
/**
|
||||||
|
* 将 CSS 属性对象转换为内联 CSS 字符串
|
||||||
|
*/
|
||||||
|
const toCss = (props) => Object.entries(props)
|
||||||
|
.map(([key, value]) => `${key}:${value}`)
|
||||||
|
.join(';');
|
||||||
|
/**
|
||||||
|
* 为自定义动画生成入场 keyframes + 类规则
|
||||||
|
*/
|
||||||
|
const buildAnimationCSS = (name, config) => {
|
||||||
|
const enter = toCss(config.enter);
|
||||||
|
return `
|
||||||
|
@keyframes met-${name}-in {
|
||||||
|
from { ${enter}; }
|
||||||
|
}
|
||||||
|
.met-anim-${name}.met-toast { opacity: 0; }
|
||||||
|
.met-anim-${name}.met-toast.met-show {
|
||||||
|
animation: met-${name}-in ${config.duration}ms ${config.easing} forwards;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 确保自定义动画的 CSS 已注入文档(幂等,内置动画自动跳过)
|
||||||
|
*/
|
||||||
|
const ensureAnimationCSS = (name) => {
|
||||||
|
if (typeof document === 'undefined')
|
||||||
|
return;
|
||||||
|
if (BUILTIN_ANIMATIONS.has(name))
|
||||||
|
return;
|
||||||
|
const config = animationMap.get(name);
|
||||||
|
if (!config || injectedAnimations.has(name))
|
||||||
|
return;
|
||||||
|
if (!animStyleElement) {
|
||||||
|
animStyleElement = document.getElementById('metona-toast-anim-styles');
|
||||||
|
if (!animStyleElement) {
|
||||||
|
animStyleElement = document.createElement('style');
|
||||||
|
animStyleElement.id = 'metona-toast-anim-styles';
|
||||||
|
document.head.appendChild(animStyleElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animStyleElement.textContent += buildAnimationCSS(name, config);
|
||||||
|
injectedAnimations.add(name);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 检查动画是否存在(内置或自定义)
|
||||||
|
*/
|
||||||
|
const hasAnimation = (name) => animationMap.has(name);
|
||||||
|
// 注册默认动画
|
||||||
|
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||||
|
animationMap.set(name, {
|
||||||
|
name,
|
||||||
|
enter: config.enter,
|
||||||
|
leave: config.leave,
|
||||||
|
duration: config.duration,
|
||||||
|
easing: config.easing,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* 动画工具函数
|
||||||
|
*/
|
||||||
|
const animationUtils = {
|
||||||
|
register(name, config) {
|
||||||
|
animationMap.set(name, {
|
||||||
|
name,
|
||||||
|
enter: config.enter || {},
|
||||||
|
leave: config.leave || {},
|
||||||
|
duration: config.duration || 300,
|
||||||
|
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unregister(name) {
|
||||||
|
animationMap.delete(name);
|
||||||
|
},
|
||||||
|
get(name) {
|
||||||
|
return animationMap.get(name) || null;
|
||||||
|
},
|
||||||
|
getAnimationNames() {
|
||||||
|
return Array.from(animationMap.keys());
|
||||||
|
},
|
||||||
|
getActiveCount() {
|
||||||
|
return animationMap.size;
|
||||||
|
},
|
||||||
|
cancelAll() {
|
||||||
|
// CSS动画由浏览器原生管理,无需手动取消
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
animationMap.clear();
|
||||||
|
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||||
|
animationMap.set(name, {
|
||||||
|
name,
|
||||||
|
enter: config.enter,
|
||||||
|
leave: config.leave,
|
||||||
|
duration: config.duration,
|
||||||
|
easing: config.easing,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
animationMap.clear();
|
||||||
|
injectedAnimations.clear();
|
||||||
|
animStyleElement = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Toast 类 — 核心通知组件
|
* Toast 类 — 核心通知组件
|
||||||
@@ -2676,8 +2800,9 @@
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
||||||
|
* toast 参数可选:全局事件(configChange/themeChange 等)不关联具体 toast
|
||||||
*/
|
*/
|
||||||
static trigger(name, toast) {
|
static trigger(name, toast = null) {
|
||||||
const list = this._hooks.get(name);
|
const list = this._hooks.get(name);
|
||||||
if (!list || list.length === 0)
|
if (!list || list.length === 0)
|
||||||
return true;
|
return true;
|
||||||
@@ -2692,7 +2817,7 @@
|
|||||||
console.error('Hook error:', name, e);
|
console.error('Hook error:', name, e);
|
||||||
if (Toast._onError) {
|
if (Toast._onError) {
|
||||||
try {
|
try {
|
||||||
Toast._onError({ hook: name, error: e, toast });
|
Toast._onError({ hook: name, error: e, toast: toast });
|
||||||
}
|
}
|
||||||
catch (_e) { /* noop */ }
|
catch (_e) { /* noop */ }
|
||||||
}
|
}
|
||||||
@@ -2747,6 +2872,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
injectStyles();
|
injectStyles();
|
||||||
|
ensureAnimationCSS(this.config.animation || 'slide');
|
||||||
const container = this._getContainer();
|
const container = this._getContainer();
|
||||||
const { theme, c, t } = this._palette();
|
const { theme, c, t } = this._palette();
|
||||||
this._limitToasts(container);
|
this._limitToasts(container);
|
||||||
@@ -2766,6 +2892,7 @@
|
|||||||
else {
|
else {
|
||||||
container.appendChild(el);
|
container.appendChild(el);
|
||||||
}
|
}
|
||||||
|
Toast._registry.set(this.id, this);
|
||||||
this._bindEvents(el);
|
this._bindEvents(el);
|
||||||
this._startTimer();
|
this._startTimer();
|
||||||
// 进入动画
|
// 进入动画
|
||||||
@@ -2833,18 +2960,22 @@
|
|||||||
_limitToasts(container) {
|
_limitToasts(container) {
|
||||||
const max = this.config.max || 6;
|
const max = this.config.max || 6;
|
||||||
const list = Array.from(container.querySelectorAll('.met-toast'));
|
const list = Array.from(container.querySelectorAll('.met-toast'));
|
||||||
if (list.length >= max) {
|
while (list.length >= max) {
|
||||||
const first = list[0];
|
const el = list.shift();
|
||||||
const id = first?.dataset.id;
|
const id = el?.dataset.id;
|
||||||
if (id && Toast._removeToast) {
|
if (!id)
|
||||||
|
continue;
|
||||||
|
const existing = Toast._registry.get(id);
|
||||||
|
if (existing) {
|
||||||
|
existing.close(true);
|
||||||
|
}
|
||||||
|
else if (Toast._removeToast) {
|
||||||
Toast._removeToast(id);
|
Toast._removeToast(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_buildClassName(theme) {
|
_buildClassName(theme) {
|
||||||
const CSS_ANIMS = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom',
|
const anim = hasAnimation(this.config.animation || '') ? this.config.animation : 'slide';
|
||||||
'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
|
|
||||||
const anim = CSS_ANIMS.includes(this.config.animation || '') ? this.config.animation : 'slide';
|
|
||||||
return [
|
return [
|
||||||
'met-toast',
|
'met-toast',
|
||||||
`met-${this.type}`,
|
`met-${this.type}`,
|
||||||
@@ -2921,6 +3052,7 @@
|
|||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Toast.trigger('click', this);
|
||||||
if (this.config.closeOnClick && !target.closest('.met-close')) {
|
if (this.config.closeOnClick && !target.closest('.met-close')) {
|
||||||
if (typeof this.config.onClick === 'function') {
|
if (typeof this.config.onClick === 'function') {
|
||||||
try {
|
try {
|
||||||
@@ -2937,8 +3069,14 @@
|
|||||||
el.addEventListener('click', eventHandler);
|
el.addEventListener('click', eventHandler);
|
||||||
this._cleanups.push(() => el.removeEventListener('click', eventHandler));
|
this._cleanups.push(() => el.removeEventListener('click', eventHandler));
|
||||||
if (this.config.pauseOnHover && (this.config.duration || 0) > 0) {
|
if (this.config.pauseOnHover && (this.config.duration || 0) > 0) {
|
||||||
const mouseEnter = () => this._pause();
|
const mouseEnter = () => {
|
||||||
const mouseLeave = () => this._resume();
|
Toast.trigger('hover', this);
|
||||||
|
this._pause();
|
||||||
|
};
|
||||||
|
const mouseLeave = () => {
|
||||||
|
Toast.trigger('hover', this);
|
||||||
|
this._resume();
|
||||||
|
};
|
||||||
el.addEventListener('mouseenter', mouseEnter);
|
el.addEventListener('mouseenter', mouseEnter);
|
||||||
el.addEventListener('mouseleave', mouseLeave);
|
el.addEventListener('mouseleave', mouseLeave);
|
||||||
this._cleanups.push(() => {
|
this._cleanups.push(() => {
|
||||||
@@ -2946,6 +3084,15 @@
|
|||||||
el.removeEventListener('mouseleave', mouseLeave);
|
el.removeEventListener('mouseleave', mouseLeave);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 入场动画生命周期钩子(离场为过渡/内联样式,不产生 animation 事件)
|
||||||
|
const animStart = () => Toast.trigger('animationStart', this);
|
||||||
|
const animEnd = () => Toast.trigger('animationEnd', this);
|
||||||
|
el.addEventListener('animationstart', animStart);
|
||||||
|
el.addEventListener('animationend', animEnd);
|
||||||
|
this._cleanups.push(() => {
|
||||||
|
el.removeEventListener('animationstart', animStart);
|
||||||
|
el.removeEventListener('animationend', animEnd);
|
||||||
|
});
|
||||||
if (this.config.draggable) {
|
if (this.config.draggable) {
|
||||||
this._bindDrag(el);
|
this._bindDrag(el);
|
||||||
}
|
}
|
||||||
@@ -2961,6 +3108,7 @@
|
|||||||
el.setPointerCapture(e.pointerId);
|
el.setPointerCapture(e.pointerId);
|
||||||
el.style.transition = 'none';
|
el.style.transition = 'none';
|
||||||
this._pause();
|
this._pause();
|
||||||
|
Toast.trigger('dragStart', this);
|
||||||
};
|
};
|
||||||
const move = (e) => {
|
const move = (e) => {
|
||||||
if (!dragging)
|
if (!dragging)
|
||||||
@@ -2987,6 +3135,7 @@
|
|||||||
this._resume();
|
this._resume();
|
||||||
}
|
}
|
||||||
dx = dy = 0;
|
dx = dy = 0;
|
||||||
|
Toast.trigger('dragEnd', this);
|
||||||
};
|
};
|
||||||
el.addEventListener('pointerdown', down);
|
el.addEventListener('pointerdown', down);
|
||||||
el.addEventListener('pointermove', move);
|
el.addEventListener('pointermove', move);
|
||||||
@@ -3005,6 +3154,7 @@
|
|||||||
if (!resuming) {
|
if (!resuming) {
|
||||||
this.startedAt = Date.now();
|
this.startedAt = Date.now();
|
||||||
this.remaining = this.config.duration || 0;
|
this.remaining = this.config.duration || 0;
|
||||||
|
Toast.trigger('progressStart', this);
|
||||||
}
|
}
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
if (this.paused || this.closing)
|
if (this.paused || this.closing)
|
||||||
@@ -3019,6 +3169,7 @@
|
|||||||
this.barEl.style.transform = t;
|
this.barEl.style.transform = t;
|
||||||
}
|
}
|
||||||
if (this.remaining <= 0) {
|
if (this.remaining <= 0) {
|
||||||
|
Toast.trigger('progressEnd', this);
|
||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3052,7 +3203,9 @@
|
|||||||
this._startTimer(true);
|
this._startTimer(true);
|
||||||
}
|
}
|
||||||
update(partial) {
|
update(partial) {
|
||||||
Toast.trigger('beforeUpdate', this);
|
// beforeUpdate 钩子 — 返回 false 可阻止更新
|
||||||
|
if (!Toast.trigger('beforeUpdate', this))
|
||||||
|
return this;
|
||||||
const typeChanged = partial.type && partial.type !== this.type;
|
const typeChanged = partial.type && partial.type !== this.type;
|
||||||
if (partial.type)
|
if (partial.type)
|
||||||
this.type = partial.type;
|
this.type = partial.type;
|
||||||
@@ -3149,14 +3302,17 @@
|
|||||||
this.el.parentNode.removeChild(this.el);
|
this.el.parentNode.removeChild(this.el);
|
||||||
}
|
}
|
||||||
this.el = null;
|
this.el = null;
|
||||||
|
Toast._registry.delete(this.id);
|
||||||
if (Toast._removeToast)
|
if (Toast._removeToast)
|
||||||
Toast._removeToast(this.id);
|
Toast._removeToast(this.id);
|
||||||
}
|
}
|
||||||
close(immediate = false) {
|
close(immediate = false) {
|
||||||
if (this.closing)
|
if (this.closing)
|
||||||
return;
|
return;
|
||||||
|
// beforeClose 钩子 — 返回 false 可阻止关闭
|
||||||
|
if (!Toast.trigger('beforeClose', this))
|
||||||
|
return;
|
||||||
this.closing = true;
|
this.closing = true;
|
||||||
Toast.trigger('beforeClose', this);
|
|
||||||
if (this.rafId !== null)
|
if (this.rafId !== null)
|
||||||
cancelAnimationFrame(this.rafId);
|
cancelAnimationFrame(this.rafId);
|
||||||
this._cleanups.forEach(fn => { try {
|
this._cleanups.forEach(fn => { try {
|
||||||
@@ -3212,12 +3368,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Toast.trigger('afterClose', this);
|
Toast.trigger('afterClose', this);
|
||||||
|
Toast._registry.delete(this.id);
|
||||||
if (Toast._removeToast)
|
if (Toast._removeToast)
|
||||||
Toast._removeToast(this.id);
|
Toast._removeToast(this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 静态钩子系统
|
// 静态钩子系统
|
||||||
Toast._hooks = new Map();
|
Toast._hooks = new Map();
|
||||||
|
// 实例注册表 — 用于通过 id 反查实例(max 超限移除、外部接管等)
|
||||||
|
Toast._registry = new Map();
|
||||||
// 由 api.ts 注入的回调,通过 setCallbacks() 设置
|
// 由 api.ts 注入的回调,通过 setCallbacks() 设置
|
||||||
Toast._onError = null;
|
Toast._onError = null;
|
||||||
Toast._removeToast = null;
|
Toast._removeToast = null;
|
||||||
@@ -3229,7 +3388,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Templates — HTML 模板辅助函数
|
* MetonaToast Templates — HTML 模板辅助函数
|
||||||
* @module templates
|
* @module templates
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description confirm / prompt / progress / action 的 DOM 模板
|
* @description confirm / prompt / progress / action 的 DOM 模板
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
@@ -3301,7 +3460,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Plugins — 插件系统
|
* MetonaToast Plugins — 插件系统
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 插件管理器
|
* 插件管理器
|
||||||
@@ -3521,7 +3680,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast API — meToast 核心 API 对象
|
* MetonaToast API — meToast 核心 API 对象
|
||||||
* @module api
|
* @module api
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 参数标准化工具
|
* 参数标准化工具
|
||||||
@@ -3546,15 +3705,13 @@
|
|||||||
message: '',
|
message: '',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** 版本号常量 */
|
|
||||||
const VERSION$1 = '0.2.1';
|
|
||||||
/**
|
/**
|
||||||
* meToast API 对象
|
* meToast API 对象
|
||||||
*/
|
*/
|
||||||
const meToast = {
|
const meToast = {
|
||||||
_toasts: new Map(),
|
_toasts: new Map(),
|
||||||
_config: { ...DEFAULTS },
|
_config: { ...DEFAULTS },
|
||||||
version: '0.2.1',
|
version: VERSION,
|
||||||
configure(opts) {
|
configure(opts) {
|
||||||
if (!opts || typeof opts !== 'object')
|
if (!opts || typeof opts !== 'object')
|
||||||
return this;
|
return this;
|
||||||
@@ -3565,6 +3722,7 @@
|
|||||||
if (opts.locale) {
|
if (opts.locale) {
|
||||||
setCurrentLocale(opts.locale);
|
setCurrentLocale(opts.locale);
|
||||||
}
|
}
|
||||||
|
Toast.trigger('configChange');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
_emit(opts) {
|
_emit(opts) {
|
||||||
@@ -4036,6 +4194,8 @@
|
|||||||
},
|
},
|
||||||
// ====== 以下方法依赖子模块(themes/i18n/plugins/animations),由 index.ts 注入 ======
|
// ====== 以下方法依赖子模块(themes/i18n/plugins/animations),由 index.ts 注入 ======
|
||||||
init(options = {}) {
|
init(options = {}) {
|
||||||
|
Toast.trigger('beforeInit');
|
||||||
|
this._destroyed = false;
|
||||||
if (options.config) {
|
if (options.config) {
|
||||||
this.configure(options.config);
|
this.configure(options.config);
|
||||||
}
|
}
|
||||||
@@ -4050,11 +4210,12 @@
|
|||||||
this.use(p);
|
this.use(p);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Toast.trigger('afterInit');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
getStatus() {
|
getStatus() {
|
||||||
return {
|
return {
|
||||||
version: VERSION$1,
|
version: VERSION,
|
||||||
toasts: this._toasts.size,
|
toasts: this._toasts.size,
|
||||||
theme: this.themes?.getCurrentTheme?.() || 'auto',
|
theme: this.themes?.getCurrentTheme?.() || 'auto',
|
||||||
locale: this.i18n?.getCurrentLocale?.() || 'zh-CN',
|
locale: this.i18n?.getCurrentLocale?.() || 'zh-CN',
|
||||||
@@ -4068,6 +4229,7 @@
|
|||||||
updateConfig(config) {
|
updateConfig(config) {
|
||||||
if (config && typeof config === 'object') {
|
if (config && typeof config === 'object') {
|
||||||
Object.assign(this._config, config);
|
Object.assign(this._config, config);
|
||||||
|
Toast.trigger('configChange');
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
@@ -4075,6 +4237,7 @@
|
|||||||
const keys = Object.keys(this._config);
|
const keys = Object.keys(this._config);
|
||||||
keys.forEach(k => delete this._config[k]);
|
keys.forEach(k => delete this._config[k]);
|
||||||
Object.assign(this._config, DEFAULTS);
|
Object.assign(this._config, DEFAULTS);
|
||||||
|
Toast.trigger('configChange');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
use(plugin, options = {}) {
|
use(plugin, options = {}) {
|
||||||
@@ -4115,6 +4278,7 @@
|
|||||||
if (this._destroyed)
|
if (this._destroyed)
|
||||||
return;
|
return;
|
||||||
this._destroyed = true;
|
this._destroyed = true;
|
||||||
|
Toast.trigger('beforeDestroy');
|
||||||
this.dismiss();
|
this.dismiss();
|
||||||
// 清理子模块
|
// 清理子模块
|
||||||
if (this.plugins && typeof this.plugins.destroy === 'function') {
|
if (this.plugins && typeof this.plugins.destroy === 'function') {
|
||||||
@@ -4131,20 +4295,26 @@
|
|||||||
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
|
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
|
||||||
this.i18n.clearLocaleListeners();
|
this.i18n.clearLocaleListeners();
|
||||||
}
|
}
|
||||||
if (this.animations && typeof this.animations.cancelAll === 'function') {
|
if (this.animations && typeof this.animations.destroy === 'function') {
|
||||||
this.animations.cancelAll();
|
this.animations.destroy();
|
||||||
}
|
}
|
||||||
// 清理 DOM
|
// 清理 DOM 与静态资源
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
const containers = document.querySelectorAll('.met-container');
|
const containers = document.querySelectorAll('.met-container');
|
||||||
containers.forEach((c) => { if (c.parentNode)
|
containers.forEach((c) => { if (c.parentNode)
|
||||||
c.parentNode.removeChild(c); });
|
c.parentNode.removeChild(c); });
|
||||||
const style = document.getElementById('metona-toast-styles');
|
['metona-toast-styles', 'metona-toast-theme-css', 'metona-toast-custom-styles', 'metona-toast-anim-styles'].forEach((id) => {
|
||||||
if (style && style.parentNode)
|
const el = document.getElementById(id);
|
||||||
style.parentNode.removeChild(style);
|
if (el && el.parentNode)
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this._toasts.clear();
|
this._toasts.clear();
|
||||||
_containerCache.clear();
|
_containerCache.clear();
|
||||||
|
Toast._registry.clear();
|
||||||
|
// afterDestroy 必须在清理 hooks 之前触发
|
||||||
|
Toast.trigger('afterDestroy');
|
||||||
|
Toast._hooks.clear();
|
||||||
},
|
},
|
||||||
// 子模块引用(由 index.ts 注入实际实现)
|
// 子模块引用(由 index.ts 注入实际实现)
|
||||||
animations: null,
|
animations: null,
|
||||||
@@ -4169,77 +4339,13 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* MetonaToast Animations — 动画管理
|
|
||||||
* @module animations
|
|
||||||
* @version 0.2.1
|
|
||||||
*/
|
|
||||||
// 动画缓存
|
|
||||||
const animationMap = new Map();
|
|
||||||
// 注册默认动画
|
|
||||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
|
||||||
animationMap.set(name, {
|
|
||||||
name,
|
|
||||||
enter: config.enter,
|
|
||||||
leave: config.leave,
|
|
||||||
duration: config.duration,
|
|
||||||
easing: config.easing,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* 动画工具函数
|
|
||||||
*/
|
|
||||||
const animationUtils = {
|
|
||||||
register(name, config) {
|
|
||||||
animationMap.set(name, {
|
|
||||||
name,
|
|
||||||
enter: config.enter || {},
|
|
||||||
leave: config.leave || {},
|
|
||||||
duration: config.duration || 300,
|
|
||||||
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
unregister(name) {
|
|
||||||
animationMap.delete(name);
|
|
||||||
},
|
|
||||||
get(name) {
|
|
||||||
return animationMap.get(name) || null;
|
|
||||||
},
|
|
||||||
getAnimationNames() {
|
|
||||||
return Array.from(animationMap.keys());
|
|
||||||
},
|
|
||||||
getActiveCount() {
|
|
||||||
return animationMap.size;
|
|
||||||
},
|
|
||||||
cancelAll() {
|
|
||||||
// CSS动画由浏览器原生管理,无需手动取消
|
|
||||||
},
|
|
||||||
reset() {
|
|
||||||
animationMap.clear();
|
|
||||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
|
||||||
animationMap.set(name, {
|
|
||||||
name,
|
|
||||||
enter: config.enter,
|
|
||||||
leave: config.leave,
|
|
||||||
duration: config.duration,
|
|
||||||
easing: config.easing,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
animationMap.clear();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MetonaToast — 轻量级Toast通知库
|
* MetonaToast — 轻量级Toast通知库
|
||||||
* @module metona-toast
|
* @module metona-toast
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
// 版本信息
|
|
||||||
const VERSION = '0.2.1';
|
|
||||||
/**
|
/**
|
||||||
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
||||||
*/
|
*/
|
||||||
@@ -4259,6 +4365,13 @@
|
|||||||
if (typeof i18nUtils.initI18n === 'function') {
|
if (typeof i18nUtils.initI18n === 'function') {
|
||||||
i18nUtils.initI18n();
|
i18nUtils.initI18n();
|
||||||
}
|
}
|
||||||
|
// 全局钩子 — 主题/语言变化转发到 Toast 钩子系统
|
||||||
|
if (typeof themeUtils.addThemeListener === 'function') {
|
||||||
|
themeUtils.addThemeListener(() => Toast.trigger('themeChange'));
|
||||||
|
}
|
||||||
|
if (typeof i18nUtils.addLocaleListener === 'function') {
|
||||||
|
i18nUtils.addLocaleListener(() => Toast.trigger('localeChange'));
|
||||||
|
}
|
||||||
// 浏览器环境全局注册
|
// 浏览器环境全局注册
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.MeToast = enhancedMeToast;
|
window.MeToast = enhancedMeToast;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+2
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@metona-team/metona-toast",
|
"name": "@metona-team/metona-toast",
|
||||||
"version": "0.2.1",
|
"version": "0.3.0",
|
||||||
"description": "轻量、零依赖、精致美观的Toast通知库。TypeScript源码,开箱即用。",
|
"description": "轻量、零依赖、精致美观的Toast通知库。TypeScript源码,开箱即用。",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/metona-toast.cjs.js",
|
"main": "dist/metona-toast.cjs.js",
|
||||||
@@ -31,8 +31,7 @@
|
|||||||
"format": "prettier --write src/**/*.ts tests/**/*.ts",
|
"format": "prettier --write src/**/*.ts tests/**/*.ts",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"prepublishOnly": "npm run typecheck && npm run lint && npm test && npm run build",
|
"prepublishOnly": "npm run typecheck && npm run lint && npm test && npm run build",
|
||||||
"docs": "typedoc src/ --out docs",
|
"serve": "bash serve.sh"
|
||||||
"example": "serve examples/"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
+2
-2
@@ -244,7 +244,7 @@
|
|||||||
|
|
||||||
<!-- 14. 错误回调 -->
|
<!-- 14. 错误回调 -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="label">🛡️ onError 错误捕获 <span style="color:#34d399;font-size:10px;">v0.2.1</span></div>
|
<div class="label">🛡️ onError 错误捕获 <span style="color:#34d399;font-size:10px;">v0.3.0</span></div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn c-e" onclick="demoOnError()">演示 onError</button>
|
<button class="btn c-e" onclick="demoOnError()">演示 onError</button>
|
||||||
<button class="btn c-w" onclick="demoOnErrorReset()">重置回调</button>
|
<button class="btn c-w" onclick="demoOnErrorReset()">重置回调</button>
|
||||||
@@ -282,7 +282,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
MetonaToast v0.2.1 · MIT · <a href="index.html">首页</a> · <a href="docs.html">文档</a> · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a>
|
MetonaToast v0.3.0 · MIT · <a href="index.html">首页</a> · <a href="docs.html">文档</a> · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="../dist/metona-toast.js"></script>
|
<script src="../dist/metona-toast.js"></script>
|
||||||
|
|||||||
+25
-1
@@ -74,6 +74,8 @@
|
|||||||
<a href="#group">group()</a>
|
<a href="#group">group()</a>
|
||||||
<a href="#dismiss">dismiss()</a>
|
<a href="#dismiss">dismiss()</a>
|
||||||
<a href="#clear">clear()</a>
|
<a href="#clear">clear()</a>
|
||||||
|
<a href="#updatePosition">updatePosition()</a>
|
||||||
|
<a href="#remove">remove() / removeToast()</a>
|
||||||
<a href="#configure">configure()</a>
|
<a href="#configure">configure()</a>
|
||||||
<a href="#use">use()</a>
|
<a href="#use">use()</a>
|
||||||
<a href="#destroy">destroy()</a>
|
<a href="#destroy">destroy()</a>
|
||||||
@@ -87,7 +89,7 @@
|
|||||||
<main class="main">
|
<main class="main">
|
||||||
|
|
||||||
<h1>API 文档</h1>
|
<h1>API 文档</h1>
|
||||||
<p>MetonaToast v0.2.1 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 <a href="#config">配置项</a> 作为可选第二参数。</p>
|
<p>MetonaToast v0.3.0 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 <a href="#config">配置项</a> 作为可选第二参数。</p>
|
||||||
|
|
||||||
<!-- ===== 基础通知 ===== -->
|
<!-- ===== 基础通知 ===== -->
|
||||||
<h2 id="show">show(message, opts?)</h2>
|
<h2 id="show">show(message, opts?)</h2>
|
||||||
@@ -378,6 +380,26 @@ MeToast.<span class="fn">dismiss</span>(toast.id); <span class="cm">// 关闭
|
|||||||
<pre>MeToast.<span class="fn">clear</span>(); <span class="cm">// 全部</span>
|
<pre>MeToast.<span class="fn">clear</span>(); <span class="cm">// 全部</span>
|
||||||
MeToast.<span class="fn">clear</span>(<span class="s">'bottom-right'</span>); <span class="cm">// 仅右下角</span></pre>
|
MeToast.<span class="fn">clear</span>(<span class="s">'bottom-right'</span>); <span class="cm">// 仅右下角</span></pre>
|
||||||
|
|
||||||
|
<h2 id="updatePosition">updatePosition(position)</h2>
|
||||||
|
<p>运行时将 Toast 移动到新的位置容器。立即生效,不重新播放入场动画。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
|
||||||
|
<tr><td>position</td><td>string</td><td>目标位置:top-left / top-center / top-right / bottom-left / bottom-center / bottom-right</td></tr>
|
||||||
|
</table>
|
||||||
|
<pre><span class="kw">const</span> t = MeToast.<span class="fn">info</span>(<span class="s">'可移动的 Toast'</span>);
|
||||||
|
t.<span class="fn">updatePosition</span>(<span class="s">'bottom-left'</span>); <span class="cm">// 移动到左下角</span></pre>
|
||||||
|
|
||||||
|
<h2 id="remove">remove() / removeToast(id)</h2>
|
||||||
|
<p>立即从 DOM 和内存中移除 Toast,<b>不触发离场动画</b>。适用于需要无动画快速清除的场景(区别于 <code>dismiss()</code> 的优雅关闭)。</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>方法</th><th>签名</th><th>说明</th></tr>
|
||||||
|
<tr><td>remove</td><td>toast.remove()</td><td>Toast 实例方法,立即移除自身</td></tr>
|
||||||
|
<tr><td>removeToast</td><td>MeToast.removeToast(id)</td><td>按 id 立即移除。id 不存在时静默忽略</td></tr>
|
||||||
|
</table>
|
||||||
|
<pre><span class="kw">const</span> t = MeToast.<span class="fn">warning</span>(<span class="s">'临时消息'</span>);
|
||||||
|
t.<span class="fn">remove</span>(); <span class="cm">// 实例方法:立即移除</span>
|
||||||
|
MeToast.<span class="fn">removeToast</span>(t.id); <span class="cm">// 或按 id 移除(等价)</span></pre>
|
||||||
|
|
||||||
<h2 id="configure">configure(opts)</h2>
|
<h2 id="configure">configure(opts)</h2>
|
||||||
<p>全局配置,影响后续所有 Toast。theme 和 locale 变化会触发相应副作用(应用主题 CSS / 切换语言)。</p>
|
<p>全局配置,影响后续所有 Toast。theme 和 locale 变化会触发相应副作用(应用主题 CSS / 切换语言)。</p>
|
||||||
<table>
|
<table>
|
||||||
@@ -434,12 +456,14 @@ MeToast.<span class="fn">use</span>(<span class="s">'accessibility'</span>); <sp
|
|||||||
<tr><td>resetTimerOnUpdate</td><td>boolean</td><td>false</td><td>调用 update() 时重置 duration 倒计时</td></tr>
|
<tr><td>resetTimerOnUpdate</td><td>boolean</td><td>false</td><td>调用 update() 时重置 duration 倒计时</td></tr>
|
||||||
<tr><td>notifyWhenHidden</td><td>boolean</td><td>false</td><td>页面不可见时自动通过 Notification API 发送系统通知</td></tr>
|
<tr><td>notifyWhenHidden</td><td>boolean</td><td>false</td><td>页面不可见时自动通过 Notification API 发送系统通知</td></tr>
|
||||||
<tr><td>render</td><td>function</td><td>—</td><td>自定义渲染函数 <code>(toast) => htmlString</code>,完全接管 DOM 构建</td></tr>
|
<tr><td>render</td><td>function</td><td>—</td><td>自定义渲染函数 <code>(toast) => htmlString</code>,完全接管 DOM 构建</td></tr>
|
||||||
|
<tr><td>onBeforeShow</td><td>function</td><td>—</td><td>显示前回调 <code>(toast) => boolean | void</code>,返回 false 可阻止该 Toast 显示</td></tr>
|
||||||
<tr><td>onError</td><td>function</td><td>—</td><td>全局错误回调 <code>({ hook, source, error, toast }) => void</code>,钩子异常或定时器错误时触发</td></tr>
|
<tr><td>onError</td><td>function</td><td>—</td><td>全局错误回调 <code>({ hook, source, error, toast }) => void</code>,钩子异常或定时器错误时触发</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h2 id="callbacks">回调函数</h2>
|
<h2 id="callbacks">回调函数</h2>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>回调</th><th>签名</th><th>触发时机</th></tr>
|
<tr><th>回调</th><th>签名</th><th>触发时机</th></tr>
|
||||||
|
<tr><td>onBeforeShow</td><td>(toast: ToastInstance) => boolean | void</td><td>Toast DOM 创建前。返回 false 阻止显示</td></tr>
|
||||||
<tr><td>onShow</td><td>(toast: ToastInstance) => void</td><td>Toast DOM 创建并播放入场动画后</td></tr>
|
<tr><td>onShow</td><td>(toast: ToastInstance) => void</td><td>Toast DOM 创建并播放入场动画后</td></tr>
|
||||||
<tr><td>onClose</td><td>(toast: ToastInstance) => void</td><td>Toast DOM 被移除后(离场动画完成时)</td></tr>
|
<tr><td>onClose</td><td>(toast: ToastInstance) => void</td><td>Toast DOM 被移除后(离场动画完成时)</td></tr>
|
||||||
<tr><td>onClick</td><td>(toast: ToastInstance) => void</td><td>Toast 被点击时(closeOnClick 为 true 时还会自动关闭)</td></tr>
|
<tr><td>onClick</td><td>(toast: ToastInstance) => void</td><td>Toast 被点击时(closeOnClick 为 true 时还会自动关闭)</td></tr>
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<title>MetonaToast — 轻量精致的 Toast 通知库</title>
|
<title>MetonaToast — 轻量精致的 Toast 通知库</title>
|
||||||
<meta name="description" content="MetonaToast 是轻量、零依赖、精致美观的 Toast 通知库。80+ 图标类型、11 种动画、主题系统、国际化、插件架构。">
|
<meta name="description" content="MetonaToast 是轻量、零依赖、精致美观的 Toast 通知库。107 种图标类型、11 种动画、主题系统、国际化、插件架构。">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg: #0b1121; --bg2: #111827; --surface: #1a2332;
|
--bg: #0b1121; --bg2: #111827; --surface: #1a2332;
|
||||||
@@ -247,7 +247,7 @@ orderGroup.<span class="fn">dismiss</span>(); <span class="cm">// 一键关闭
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>MetonaToast v0.2.1 · MIT License · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a></p>
|
<p>MetonaToast v0.3.0 · MIT License · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="../dist/metona-toast.js"></script>
|
<script src="../dist/metona-toast.js"></script>
|
||||||
|
|||||||
+62
-1
@@ -4,12 +4,71 @@
|
|||||||
* @version 0.2.1
|
* @version 0.2.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ANIMATIONS } from './constants.js';
|
import { ANIMATIONS, ANIMATION_TYPES } from './constants.js';
|
||||||
import type { AnimationConfig, AnimationUtils } from './types.js';
|
import type { AnimationConfig, AnimationUtils } from './types.js';
|
||||||
|
|
||||||
// 动画缓存
|
// 动画缓存
|
||||||
const animationMap: Map<string, AnimationConfig> = new Map();
|
const animationMap: Map<string, AnimationConfig> = new Map();
|
||||||
|
|
||||||
|
// 内置动画(keyframes 由 styles.ts 注入,无需动态生成)
|
||||||
|
const BUILTIN_ANIMATIONS: Set<string> = new Set(ANIMATION_TYPES);
|
||||||
|
|
||||||
|
// 动态注入的自定义动画样式(惰性创建)
|
||||||
|
let animStyleElement: HTMLStyleElement | null = null;
|
||||||
|
const injectedAnimations: Set<string> = new Set();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 CSS 属性对象转换为内联 CSS 字符串
|
||||||
|
*/
|
||||||
|
const toCss = (props: Record<string, string | number>): string =>
|
||||||
|
Object.entries(props)
|
||||||
|
.map(([key, value]) => `${key}:${value}`)
|
||||||
|
.join(';');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为自定义动画生成入场 keyframes + 类规则
|
||||||
|
*/
|
||||||
|
const buildAnimationCSS = (name: string, config: AnimationConfig): string => {
|
||||||
|
const enter = toCss(config.enter);
|
||||||
|
return `
|
||||||
|
@keyframes met-${name}-in {
|
||||||
|
from { ${enter}; }
|
||||||
|
}
|
||||||
|
.met-anim-${name}.met-toast { opacity: 0; }
|
||||||
|
.met-anim-${name}.met-toast.met-show {
|
||||||
|
animation: met-${name}-in ${config.duration}ms ${config.easing} forwards;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保自定义动画的 CSS 已注入文档(幂等,内置动画自动跳过)
|
||||||
|
*/
|
||||||
|
export const ensureAnimationCSS = (name: string): void => {
|
||||||
|
if (typeof document === 'undefined') return;
|
||||||
|
if (BUILTIN_ANIMATIONS.has(name)) return;
|
||||||
|
|
||||||
|
const config = animationMap.get(name);
|
||||||
|
if (!config || injectedAnimations.has(name)) return;
|
||||||
|
|
||||||
|
if (!animStyleElement) {
|
||||||
|
animStyleElement = document.getElementById('metona-toast-anim-styles') as HTMLStyleElement | null;
|
||||||
|
if (!animStyleElement) {
|
||||||
|
animStyleElement = document.createElement('style');
|
||||||
|
animStyleElement.id = 'metona-toast-anim-styles';
|
||||||
|
document.head.appendChild(animStyleElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animStyleElement.textContent += buildAnimationCSS(name, config);
|
||||||
|
injectedAnimations.add(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查动画是否存在(内置或自定义)
|
||||||
|
*/
|
||||||
|
export const hasAnimation = (name: string): boolean => animationMap.has(name);
|
||||||
|
|
||||||
// 注册默认动画
|
// 注册默认动画
|
||||||
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
Object.entries(ANIMATIONS).forEach(([name, config]) => {
|
||||||
animationMap.set(name, {
|
animationMap.set(name, {
|
||||||
@@ -70,6 +129,8 @@ export const animationUtils: AnimationUtils = {
|
|||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
animationMap.clear();
|
animationMap.clear();
|
||||||
|
injectedAnimations.clear();
|
||||||
|
animStyleElement = null;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+26
-12
@@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast API — meToast 核心 API 对象
|
* MetonaToast API — meToast 核心 API 对象
|
||||||
* @module api
|
* @module api
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Toast, _containerCache } from './toast.js';
|
import { Toast, _containerCache } from './toast.js';
|
||||||
import { DEFAULTS } from './constants.js';
|
import { DEFAULTS, VERSION } from './constants.js';
|
||||||
import { escapeHTML } from './utils.js';
|
import { escapeHTML } from './utils.js';
|
||||||
import { t, setCurrentLocale } from './i18n.js';
|
import { t, setCurrentLocale } from './i18n.js';
|
||||||
import { applyTheme } from './themes.js';
|
import { applyTheme } from './themes.js';
|
||||||
@@ -48,16 +48,13 @@ const normalizeArgs = (args: unknown[], defaultType = 'default'): ToastOptions =
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 版本号常量 */
|
|
||||||
const VERSION = '0.2.1';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* meToast API 对象
|
* meToast API 对象
|
||||||
*/
|
*/
|
||||||
const meToast: MeToast = {
|
const meToast: MeToast = {
|
||||||
_toasts: new Map<string, ToastInstance>(),
|
_toasts: new Map<string, ToastInstance>(),
|
||||||
_config: { ...DEFAULTS } as unknown as ToastConfig,
|
_config: { ...DEFAULTS } as unknown as ToastConfig,
|
||||||
version: '0.2.1',
|
version: VERSION,
|
||||||
|
|
||||||
configure(opts: Partial<ToastConfig>): MeToast {
|
configure(opts: Partial<ToastConfig>): MeToast {
|
||||||
if (!opts || typeof opts !== 'object') return this;
|
if (!opts || typeof opts !== 'object') return this;
|
||||||
@@ -71,6 +68,7 @@ const meToast: MeToast = {
|
|||||||
setCurrentLocale(opts.locale);
|
setCurrentLocale(opts.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toast.trigger('configChange');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -588,6 +586,9 @@ const meToast: MeToast = {
|
|||||||
// ====== 以下方法依赖子模块(themes/i18n/plugins/animations),由 index.ts 注入 ======
|
// ====== 以下方法依赖子模块(themes/i18n/plugins/animations),由 index.ts 注入 ======
|
||||||
|
|
||||||
init(options: InitOptions = {}): MeToast {
|
init(options: InitOptions = {}): MeToast {
|
||||||
|
Toast.trigger('beforeInit');
|
||||||
|
this._destroyed = false;
|
||||||
|
|
||||||
if (options.config) {
|
if (options.config) {
|
||||||
this.configure(options.config);
|
this.configure(options.config);
|
||||||
}
|
}
|
||||||
@@ -599,9 +600,11 @@ const meToast: MeToast = {
|
|||||||
}
|
}
|
||||||
if (options.plugins && Array.isArray(options.plugins)) {
|
if (options.plugins && Array.isArray(options.plugins)) {
|
||||||
options.plugins.forEach((p) => {
|
options.plugins.forEach((p) => {
|
||||||
this.use(p as string);
|
this.use(p);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toast.trigger('afterInit');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -623,6 +626,7 @@ const meToast: MeToast = {
|
|||||||
updateConfig(config: Partial<ToastConfig>): MeToast {
|
updateConfig(config: Partial<ToastConfig>): MeToast {
|
||||||
if (config && typeof config === 'object') {
|
if (config && typeof config === 'object') {
|
||||||
Object.assign(this._config, config);
|
Object.assign(this._config, config);
|
||||||
|
Toast.trigger('configChange');
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
@@ -631,6 +635,7 @@ const meToast: MeToast = {
|
|||||||
const keys = Object.keys(this._config);
|
const keys = Object.keys(this._config);
|
||||||
keys.forEach(k => delete (this._config as Record<string, unknown>)[k]);
|
keys.forEach(k => delete (this._config as Record<string, unknown>)[k]);
|
||||||
Object.assign(this._config, DEFAULTS);
|
Object.assign(this._config, DEFAULTS);
|
||||||
|
Toast.trigger('configChange');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -670,6 +675,8 @@ const meToast: MeToast = {
|
|||||||
destroy(): void {
|
destroy(): void {
|
||||||
if (this._destroyed) return;
|
if (this._destroyed) return;
|
||||||
this._destroyed = true;
|
this._destroyed = true;
|
||||||
|
|
||||||
|
Toast.trigger('beforeDestroy');
|
||||||
this.dismiss();
|
this.dismiss();
|
||||||
|
|
||||||
// 清理子模块
|
// 清理子模块
|
||||||
@@ -687,21 +694,28 @@ const meToast: MeToast = {
|
|||||||
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
|
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
|
||||||
this.i18n.clearLocaleListeners();
|
this.i18n.clearLocaleListeners();
|
||||||
}
|
}
|
||||||
if (this.animations && typeof this.animations.cancelAll === 'function') {
|
if (this.animations && typeof this.animations.destroy === 'function') {
|
||||||
this.animations.cancelAll();
|
this.animations.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理 DOM
|
// 清理 DOM 与静态资源
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
const containers = document.querySelectorAll('.met-container');
|
const containers = document.querySelectorAll('.met-container');
|
||||||
containers.forEach((c) => { if (c.parentNode) c.parentNode.removeChild(c); });
|
containers.forEach((c) => { if (c.parentNode) c.parentNode.removeChild(c); });
|
||||||
|
|
||||||
const style = document.getElementById('metona-toast-styles');
|
['metona-toast-styles', 'metona-toast-theme-css', 'metona-toast-custom-styles', 'metona-toast-anim-styles'].forEach((id) => {
|
||||||
if (style && style.parentNode) style.parentNode.removeChild(style);
|
const el = document.getElementById(id);
|
||||||
|
if (el && el.parentNode) el.parentNode.removeChild(el);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this._toasts.clear();
|
this._toasts.clear();
|
||||||
_containerCache.clear();
|
_containerCache.clear();
|
||||||
|
Toast._registry.clear();
|
||||||
|
|
||||||
|
// afterDestroy 必须在清理 hooks 之前触发
|
||||||
|
Toast.trigger('afterDestroy');
|
||||||
|
Toast._hooks.clear();
|
||||||
},
|
},
|
||||||
|
|
||||||
// 子模块引用(由 index.ts 注入实际实现)
|
// 子模块引用(由 index.ts 注入实际实现)
|
||||||
|
|||||||
+6
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants — 常量定义
|
* MetonaToast Constants — 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ICONS } from './icons.js';
|
import { ICONS } from './icons.js';
|
||||||
@@ -9,6 +9,11 @@ import { LOCALES } from './locales.js';
|
|||||||
|
|
||||||
export { ICONS, LOCALES };
|
export { ICONS, LOCALES };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
|
*/
|
||||||
|
export const VERSION = '0.3.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认配置
|
* 默认配置
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast i18n — 国际化管理
|
* MetonaToast i18n — 国际化管理
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { LOCALES } from './constants.js';
|
import { LOCALES } from './constants.js';
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Icons — 图标SVG定义
|
* MetonaToast Icons — 图标SVG定义
|
||||||
* @module icons
|
* @module icons
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description 107 个内置 SVG 图标
|
* @description 107 个内置 SVG 图标
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -1,22 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast — 轻量级Toast通知库
|
* MetonaToast — 轻量级Toast通知库
|
||||||
* @module metona-toast
|
* @module metona-toast
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { meToast } from './api.js';
|
import { meToast } from './api.js';
|
||||||
import { Toast, _containerCache } from './toast.js';
|
import { Toast } from './toast.js';
|
||||||
import { animationUtils } from './animations.js';
|
import { animationUtils } from './animations.js';
|
||||||
import { themeUtils } from './themes.js';
|
import { themeUtils } from './themes.js';
|
||||||
import { i18nUtils } from './i18n.js';
|
import { i18nUtils } from './i18n.js';
|
||||||
import { pluginUtils, presetPlugins } from './plugins.js';
|
import { pluginUtils, presetPlugins } from './plugins.js';
|
||||||
|
import { VERSION } from './constants.js';
|
||||||
import type { ToastInstance } from './types.js';
|
import type { ToastInstance } from './types.js';
|
||||||
|
|
||||||
// 版本信息
|
|
||||||
const VERSION = '0.2.1';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
||||||
*/
|
*/
|
||||||
@@ -40,6 +38,14 @@ if (typeof i18nUtils.initI18n === 'function') {
|
|||||||
i18nUtils.initI18n();
|
i18nUtils.initI18n();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全局钩子 — 主题/语言变化转发到 Toast 钩子系统
|
||||||
|
if (typeof themeUtils.addThemeListener === 'function') {
|
||||||
|
themeUtils.addThemeListener(() => Toast.trigger('themeChange'));
|
||||||
|
}
|
||||||
|
if (typeof i18nUtils.addLocaleListener === 'function') {
|
||||||
|
i18nUtils.addLocaleListener(() => Toast.trigger('localeChange'));
|
||||||
|
}
|
||||||
|
|
||||||
// 浏览器环境全局注册
|
// 浏览器环境全局注册
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.MeToast = enhancedMeToast;
|
window.MeToast = enhancedMeToast;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Locales — 国际化翻译数据
|
* MetonaToast Locales — 国际化翻译数据
|
||||||
* @module locales
|
* @module locales
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Plugins — 插件系统
|
* MetonaToast Plugins — 插件系统
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { t } from './i18n.js';
|
import { t } from './i18n.js';
|
||||||
|
|||||||
+6
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Styles — 样式管理
|
* MetonaToast Styles — 样式管理
|
||||||
* @module styles
|
* @module styles
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ThemeConfig } from './types.js';
|
import type { ThemeConfig } from './types.js';
|
||||||
@@ -506,6 +506,11 @@ export const generateThemeVariables = (theme: ThemeConfig): string => {
|
|||||||
export const applyThemeVariables = (theme: ThemeConfig): void => {
|
export const applyThemeVariables = (theme: ThemeConfig): void => {
|
||||||
if (typeof document === 'undefined') return;
|
if (typeof document === 'undefined') return;
|
||||||
|
|
||||||
|
const oldStyle = document.getElementById('metona-toast-custom-styles');
|
||||||
|
if (oldStyle && oldStyle.parentNode) {
|
||||||
|
oldStyle.parentNode.removeChild(oldStyle);
|
||||||
|
}
|
||||||
|
|
||||||
const css = generateThemeVariables(theme);
|
const css = generateThemeVariables(theme);
|
||||||
const customStyle = document.createElement('style');
|
const customStyle = document.createElement('style');
|
||||||
customStyle.id = 'metona-toast-custom-styles';
|
customStyle.id = 'metona-toast-custom-styles';
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Templates — HTML 模板辅助函数
|
* MetonaToast Templates — HTML 模板辅助函数
|
||||||
* @module templates
|
* @module templates
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
* @description confirm / prompt / progress / action 的 DOM 模板
|
* @description confirm / prompt / progress / action 的 DOM 模板
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Themes — 主题管理
|
* MetonaToast Themes — 主题管理
|
||||||
* @module themes
|
* @module themes
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { THEMES } from './constants.js';
|
import { THEMES } from './constants.js';
|
||||||
|
|||||||
+54
-16
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { generateId, escapeHTML } from './utils.js';
|
import { generateId, escapeHTML } from './utils.js';
|
||||||
@@ -9,6 +9,7 @@ import { DEFAULTS, ICONS, TYPE_COLORS, THEMES } from './constants.js';
|
|||||||
import { injectStyles } from './styles.js';
|
import { injectStyles } from './styles.js';
|
||||||
import { getTheme } from './themes.js';
|
import { getTheme } from './themes.js';
|
||||||
import { t, getLocaleDirection, getCurrentLocale } from './i18n.js';
|
import { t, getLocaleDirection, getCurrentLocale } from './i18n.js';
|
||||||
|
import { hasAnimation, ensureAnimationCSS } from './animations.js';
|
||||||
import type { ToastConfig, ToastOptions, ToastInstance, ErrorInfo, TypeColor, ThemeConfig } from './types.js';
|
import type { ToastConfig, ToastOptions, ToastInstance, ErrorInfo, TypeColor, ThemeConfig } from './types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,6 +19,9 @@ export class Toast implements ToastInstance {
|
|||||||
// 静态钩子系统
|
// 静态钩子系统
|
||||||
static _hooks: Map<string, Array<(toast: Toast) => boolean | void>> = new Map();
|
static _hooks: Map<string, Array<(toast: Toast) => boolean | void>> = new Map();
|
||||||
|
|
||||||
|
// 实例注册表 — 用于通过 id 反查实例(max 超限移除、外部接管等)
|
||||||
|
static _registry: Map<string, Toast> = new Map();
|
||||||
|
|
||||||
// 由 api.ts 注入的回调,通过 setCallbacks() 设置
|
// 由 api.ts 注入的回调,通过 setCallbacks() 设置
|
||||||
private static _onError: ((errorInfo: ErrorInfo) => void) | null = null;
|
private static _onError: ((errorInfo: ErrorInfo) => void) | null = null;
|
||||||
private static _removeToast: ((id: string) => void) | null = null;
|
private static _removeToast: ((id: string) => void) | null = null;
|
||||||
@@ -46,21 +50,22 @@ export class Toast implements ToastInstance {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
* 触发钩子 — 如果任意钩子返回 false 则整体返回 false
|
||||||
|
* toast 参数可选:全局事件(configChange/themeChange 等)不关联具体 toast
|
||||||
*/
|
*/
|
||||||
static trigger(name: string, toast: Toast): boolean {
|
static trigger(name: string, toast: Toast | null = null): boolean {
|
||||||
const list = this._hooks.get(name);
|
const list = this._hooks.get(name);
|
||||||
if (!list || list.length === 0) return true;
|
if (!list || list.length === 0) return true;
|
||||||
|
|
||||||
let allow = true;
|
let allow = true;
|
||||||
list.forEach(fn => {
|
list.forEach(fn => {
|
||||||
try {
|
try {
|
||||||
const result = fn(toast);
|
const result = fn(toast as Toast);
|
||||||
if (result === false) allow = false;
|
if (result === false) allow = false;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error('Hook error:', name, e);
|
console.error('Hook error:', name, e);
|
||||||
if (Toast._onError) {
|
if (Toast._onError) {
|
||||||
try { Toast._onError({ hook: name, error: e as Error, toast }); } catch (_e) { /* noop */ }
|
try { Toast._onError({ hook: name, error: e as Error, toast: toast as ToastInstance }); } catch (_e) { /* noop */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -124,6 +129,7 @@ export class Toast implements ToastInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
injectStyles();
|
injectStyles();
|
||||||
|
ensureAnimationCSS(this.config.animation || 'slide');
|
||||||
|
|
||||||
const container = this._getContainer();
|
const container = this._getContainer();
|
||||||
const { theme, c, t } = this._palette();
|
const { theme, c, t } = this._palette();
|
||||||
@@ -148,6 +154,8 @@ export class Toast implements ToastInstance {
|
|||||||
container.appendChild(el);
|
container.appendChild(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toast._registry.set(this.id, this);
|
||||||
|
|
||||||
this._bindEvents(el);
|
this._bindEvents(el);
|
||||||
this._startTimer();
|
this._startTimer();
|
||||||
|
|
||||||
@@ -227,19 +235,21 @@ export class Toast implements ToastInstance {
|
|||||||
_limitToasts(container: HTMLElement): void {
|
_limitToasts(container: HTMLElement): void {
|
||||||
const max = this.config.max || 6;
|
const max = this.config.max || 6;
|
||||||
const list = Array.from(container.querySelectorAll('.met-toast'));
|
const list = Array.from(container.querySelectorAll('.met-toast'));
|
||||||
if (list.length >= max) {
|
while (list.length >= max) {
|
||||||
const first = list[0] as HTMLElement;
|
const el = list.shift() as HTMLElement;
|
||||||
const id = first?.dataset.id;
|
const id = el?.dataset.id;
|
||||||
if (id && Toast._removeToast) {
|
if (!id) continue;
|
||||||
|
const existing = Toast._registry.get(id);
|
||||||
|
if (existing) {
|
||||||
|
existing.close(true);
|
||||||
|
} else if (Toast._removeToast) {
|
||||||
Toast._removeToast(id);
|
Toast._removeToast(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildClassName(theme: string): string {
|
_buildClassName(theme: string): string {
|
||||||
const CSS_ANIMS = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom',
|
const anim = hasAnimation(this.config.animation || '') ? (this.config.animation as string) : 'slide';
|
||||||
'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
|
|
||||||
const anim = CSS_ANIMS.includes(this.config.animation || '') ? this.config.animation : 'slide';
|
|
||||||
return [
|
return [
|
||||||
'met-toast',
|
'met-toast',
|
||||||
`met-${this.type}`,
|
`met-${this.type}`,
|
||||||
@@ -327,6 +337,8 @@ export class Toast implements ToastInstance {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toast.trigger('click', this);
|
||||||
|
|
||||||
if (this.config.closeOnClick && !target.closest('.met-close')) {
|
if (this.config.closeOnClick && !target.closest('.met-close')) {
|
||||||
if (typeof this.config.onClick === 'function') {
|
if (typeof this.config.onClick === 'function') {
|
||||||
try {
|
try {
|
||||||
@@ -344,8 +356,14 @@ export class Toast implements ToastInstance {
|
|||||||
this._cleanups.push(() => el.removeEventListener('click', eventHandler));
|
this._cleanups.push(() => el.removeEventListener('click', eventHandler));
|
||||||
|
|
||||||
if (this.config.pauseOnHover && (this.config.duration || 0) > 0) {
|
if (this.config.pauseOnHover && (this.config.duration || 0) > 0) {
|
||||||
const mouseEnter = () => this._pause();
|
const mouseEnter = () => {
|
||||||
const mouseLeave = () => this._resume();
|
Toast.trigger('hover', this);
|
||||||
|
this._pause();
|
||||||
|
};
|
||||||
|
const mouseLeave = () => {
|
||||||
|
Toast.trigger('hover', this);
|
||||||
|
this._resume();
|
||||||
|
};
|
||||||
|
|
||||||
el.addEventListener('mouseenter', mouseEnter);
|
el.addEventListener('mouseenter', mouseEnter);
|
||||||
el.addEventListener('mouseleave', mouseLeave);
|
el.addEventListener('mouseleave', mouseLeave);
|
||||||
@@ -356,6 +374,16 @@ export class Toast implements ToastInstance {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 入场动画生命周期钩子(离场为过渡/内联样式,不产生 animation 事件)
|
||||||
|
const animStart = () => Toast.trigger('animationStart', this);
|
||||||
|
const animEnd = () => Toast.trigger('animationEnd', this);
|
||||||
|
el.addEventListener('animationstart', animStart);
|
||||||
|
el.addEventListener('animationend', animEnd);
|
||||||
|
this._cleanups.push(() => {
|
||||||
|
el.removeEventListener('animationstart', animStart);
|
||||||
|
el.removeEventListener('animationend', animEnd);
|
||||||
|
});
|
||||||
|
|
||||||
if (this.config.draggable) {
|
if (this.config.draggable) {
|
||||||
this._bindDrag(el);
|
this._bindDrag(el);
|
||||||
}
|
}
|
||||||
@@ -372,6 +400,7 @@ export class Toast implements ToastInstance {
|
|||||||
el.setPointerCapture(e.pointerId);
|
el.setPointerCapture(e.pointerId);
|
||||||
el.style.transition = 'none';
|
el.style.transition = 'none';
|
||||||
this._pause();
|
this._pause();
|
||||||
|
Toast.trigger('dragStart', this);
|
||||||
};
|
};
|
||||||
|
|
||||||
const move = (e: PointerEvent) => {
|
const move = (e: PointerEvent) => {
|
||||||
@@ -398,6 +427,7 @@ export class Toast implements ToastInstance {
|
|||||||
this._resume();
|
this._resume();
|
||||||
}
|
}
|
||||||
dx = dy = 0;
|
dx = dy = 0;
|
||||||
|
Toast.trigger('dragEnd', this);
|
||||||
};
|
};
|
||||||
|
|
||||||
el.addEventListener('pointerdown', down);
|
el.addEventListener('pointerdown', down);
|
||||||
@@ -419,6 +449,7 @@ export class Toast implements ToastInstance {
|
|||||||
if (!resuming) {
|
if (!resuming) {
|
||||||
this.startedAt = Date.now();
|
this.startedAt = Date.now();
|
||||||
this.remaining = this.config.duration || 0;
|
this.remaining = this.config.duration || 0;
|
||||||
|
Toast.trigger('progressStart', this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tick = (): void => {
|
const tick = (): void => {
|
||||||
@@ -436,6 +467,7 @@ export class Toast implements ToastInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.remaining <= 0) {
|
if (this.remaining <= 0) {
|
||||||
|
Toast.trigger('progressEnd', this);
|
||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -467,7 +499,9 @@ export class Toast implements ToastInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(partial: Partial<ToastOptions>): this {
|
update(partial: Partial<ToastOptions>): this {
|
||||||
Toast.trigger('beforeUpdate', this);
|
// beforeUpdate 钩子 — 返回 false 可阻止更新
|
||||||
|
if (!Toast.trigger('beforeUpdate', this)) return this;
|
||||||
|
|
||||||
const typeChanged = partial.type && partial.type !== this.type;
|
const typeChanged = partial.type && partial.type !== this.type;
|
||||||
if (partial.type) this.type = partial.type;
|
if (partial.type) this.type = partial.type;
|
||||||
if (partial.title !== undefined) this.title = partial.title;
|
if (partial.title !== undefined) this.title = partial.title;
|
||||||
@@ -559,14 +593,17 @@ export class Toast implements ToastInstance {
|
|||||||
}
|
}
|
||||||
this.el = null;
|
this.el = null;
|
||||||
|
|
||||||
|
Toast._registry.delete(this.id);
|
||||||
if (Toast._removeToast) Toast._removeToast(this.id);
|
if (Toast._removeToast) Toast._removeToast(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
close(immediate = false): void {
|
close(immediate = false): void {
|
||||||
if (this.closing) return;
|
if (this.closing) return;
|
||||||
this.closing = true;
|
|
||||||
|
|
||||||
Toast.trigger('beforeClose', this);
|
// beforeClose 钩子 — 返回 false 可阻止关闭
|
||||||
|
if (!Toast.trigger('beforeClose', this)) return;
|
||||||
|
|
||||||
|
this.closing = true;
|
||||||
if (this.rafId !== null) cancelAnimationFrame(this.rafId);
|
if (this.rafId !== null) cancelAnimationFrame(this.rafId);
|
||||||
this._cleanups.forEach(fn => { try { fn(); } catch (_e) { /* noop */ } });
|
this._cleanups.forEach(fn => { try { fn(); } catch (_e) { /* noop */ } });
|
||||||
this._cleanups = [];
|
this._cleanups = [];
|
||||||
@@ -622,6 +659,7 @@ export class Toast implements ToastInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Toast.trigger('afterClose', this);
|
Toast.trigger('afterClose', this);
|
||||||
|
Toast._registry.delete(this.id);
|
||||||
if (Toast._removeToast) Toast._removeToast(this.id);
|
if (Toast._removeToast) Toast._removeToast(this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-5
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast — 核心类型定义
|
* MetonaToast — 核心类型定义
|
||||||
* @module types
|
* @module types
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ========== 基础类型 ==========
|
// ========== 基础类型 ==========
|
||||||
@@ -271,8 +271,8 @@ export interface StatusInfo {
|
|||||||
// ========== 动画 ==========
|
// ========== 动画 ==========
|
||||||
|
|
||||||
export interface AnimationConfig {
|
export interface AnimationConfig {
|
||||||
enter: Record<string, string>;
|
enter: Record<string, string | number>;
|
||||||
leave: Record<string, string>;
|
leave: Record<string, string | number>;
|
||||||
duration: number;
|
duration: number;
|
||||||
easing: string;
|
easing: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -538,10 +538,8 @@ export const HOOK_NAMES = {
|
|||||||
ANIMATION_START: 'animationStart',
|
ANIMATION_START: 'animationStart',
|
||||||
ANIMATION_END: 'animationEnd',
|
ANIMATION_END: 'animationEnd',
|
||||||
PROGRESS_START: 'progressStart',
|
PROGRESS_START: 'progressStart',
|
||||||
PROGRESS_UPDATE: 'progressUpdate',
|
|
||||||
PROGRESS_END: 'progressEnd',
|
PROGRESS_END: 'progressEnd',
|
||||||
ERROR: 'error',
|
ERROR: 'error',
|
||||||
CUSTOM: 'custom',
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type HookName = (typeof HOOK_NAMES)[keyof typeof HOOK_NAMES];
|
export type HookName = (typeof HOOK_NAMES)[keyof typeof HOOK_NAMES];
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Utils — 工具函数
|
* MetonaToast Utils — 工具函数
|
||||||
* @module utils
|
* @module utils
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+196
-2
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast 覆盖率补充测试 — 目标 95%+
|
* MetonaToast 覆盖率补充测试 — 目标 95%+
|
||||||
* @module tests
|
* @module tests
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MeToast, { Toast, VERSION } from '../src/index';
|
import MeToast, { Toast, VERSION } from '../src/index';
|
||||||
@@ -139,6 +139,70 @@ describe('animations.ts 覆盖率', () => {
|
|||||||
test('getActiveCount 返回动画数量', () => {
|
test('getActiveCount 返回动画数量', () => {
|
||||||
expect(MeToast.animations.getActiveCount()).toBeGreaterThan(0);
|
expect(MeToast.animations.getActiveCount()).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ensureAnimationCSS 内置动画不注入', () => {
|
||||||
|
const { ensureAnimationCSS } = require('../src/animations.js');
|
||||||
|
// jsdom 真实 DOM:内置动画由 styles.ts 统一注入,不应产生独立 style 标签
|
||||||
|
ensureAnimationCSS('slide');
|
||||||
|
expect(document.getElementById('metona-toast-anim-styles')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ensureAnimationCSS 自定义动画注入样式(幂等)', () => {
|
||||||
|
const { ensureAnimationCSS } = require('../src/animations.js');
|
||||||
|
MeToast.animations.destroy(); // 清理模块注入状态,保证测试独立
|
||||||
|
MeToast.animations.register('my-anim', {
|
||||||
|
enter: { opacity: 0, transform: 'scale(0.5)' },
|
||||||
|
leave: { opacity: 1 },
|
||||||
|
duration: 400,
|
||||||
|
easing: 'ease',
|
||||||
|
});
|
||||||
|
ensureAnimationCSS('my-anim');
|
||||||
|
const styleEl = document.getElementById('metona-toast-anim-styles');
|
||||||
|
expect(styleEl).not.toBeNull();
|
||||||
|
expect(styleEl!.textContent).toContain('@keyframes met-my-anim-in');
|
||||||
|
expect(styleEl!.textContent).toContain('.met-anim-my-anim.met-toast.met-show');
|
||||||
|
// 幂等:二次调用不重复追加
|
||||||
|
const before = styleEl!.textContent!.length;
|
||||||
|
ensureAnimationCSS('my-anim');
|
||||||
|
expect(styleEl!.textContent!.length).toBe(before);
|
||||||
|
MeToast.animations.unregister('my-anim');
|
||||||
|
MeToast.animations.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ensureAnimationCSS 未注册动画不注入', () => {
|
||||||
|
const { ensureAnimationCSS } = require('../src/animations.js');
|
||||||
|
// 清理 jsdom 中可能残留的动态 style 元素
|
||||||
|
document.getElementById('metona-toast-anim-styles')?.remove();
|
||||||
|
ensureAnimationCSS('not-registered');
|
||||||
|
expect(document.getElementById('metona-toast-anim-styles')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasAnimation 检查内置与自定义动画', () => {
|
||||||
|
const { hasAnimation } = require('../src/animations.js');
|
||||||
|
expect(hasAnimation('slide')).toBe(true);
|
||||||
|
expect(hasAnimation('nonexistent')).toBe(false);
|
||||||
|
MeToast.animations.register('has-anim-test', { enter: {}, leave: {}, duration: 300 });
|
||||||
|
expect(hasAnimation('has-anim-test')).toBe(true);
|
||||||
|
MeToast.animations.unregister('has-anim-test');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('自定义动画注册后 _buildClassName 使用该动画', () => {
|
||||||
|
MeToast.animations.register('custom-anim', {
|
||||||
|
enter: { opacity: 0 },
|
||||||
|
leave: { opacity: 1 },
|
||||||
|
duration: 400,
|
||||||
|
easing: 'ease',
|
||||||
|
});
|
||||||
|
const t = MeToast.info({ message: 'custom', animation: 'custom-anim' });
|
||||||
|
expect(t.el?.className).toContain('met-anim-custom-anim');
|
||||||
|
MeToast.animations.unregister('custom-anim');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('未注册动画名 fallback 到 slide', () => {
|
||||||
|
const t = new Toast({ message: 'test', animation: 'unknown-anim' });
|
||||||
|
t.create();
|
||||||
|
expect(t.el?.className).toContain('met-anim-slide');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ==================================================================
|
// ==================================================================
|
||||||
@@ -745,7 +809,7 @@ describe('api.ts 覆盖率', () => {
|
|||||||
|
|
||||||
test('getStatus 返回完整状态', () => {
|
test('getStatus 返回完整状态', () => {
|
||||||
const status = MeToast.getStatus();
|
const status = MeToast.getStatus();
|
||||||
expect(status.version).toBe('0.2.1');
|
expect(status.version).toBe('0.3.0');
|
||||||
expect(status.toasts).toBeGreaterThanOrEqual(0);
|
expect(status.toasts).toBeGreaterThanOrEqual(0);
|
||||||
expect(status.theme).toBeDefined();
|
expect(status.theme).toBeDefined();
|
||||||
expect(status.locale).toBeDefined();
|
expect(status.locale).toBeDefined();
|
||||||
@@ -1299,3 +1363,133 @@ describe('最终补漏', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ==================================================================
|
||||||
|
// 11. v0.3.0 修复验证
|
||||||
|
// ==================================================================
|
||||||
|
describe('v0.3.0 修复验证', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
MeToast._toasts.clear();
|
||||||
|
MeToast.resetConfig();
|
||||||
|
(MeToast as unknown as { _destroyed?: boolean })._destroyed = false;
|
||||||
|
Toast._hooks.clear();
|
||||||
|
Toast._registry.clear();
|
||||||
|
const { _containerCache } = require('../src/toast.js');
|
||||||
|
_containerCache.clear();
|
||||||
|
// 清理动画模块状态(injected 集合 + 动态 style 引用),再恢复默认动画
|
||||||
|
MeToast.animations.destroy();
|
||||||
|
MeToast.animations.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('_limitToasts 超出 max 时真正关闭最早的 toast', () => {
|
||||||
|
const t1 = MeToast.info('m1');
|
||||||
|
const t2 = MeToast.info('m2');
|
||||||
|
const el = createMockElement() as unknown as HTMLElement;
|
||||||
|
(el as unknown as { querySelectorAll: unknown }).querySelectorAll = jest.fn(() => [
|
||||||
|
{ dataset: { id: t1.id } },
|
||||||
|
{ dataset: { id: t2.id } },
|
||||||
|
]);
|
||||||
|
const newToast = new Toast({ message: 'm3', max: 2 });
|
||||||
|
newToast._limitToasts(el as unknown as HTMLElement);
|
||||||
|
expect(t1.closing).toBe(true);
|
||||||
|
expect(t2.closing).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('beforeClose 钩子返回 false 阻止关闭', () => {
|
||||||
|
const t = MeToast.info('test');
|
||||||
|
const blocker = () => false;
|
||||||
|
Toast.on('beforeClose', blocker);
|
||||||
|
t.close();
|
||||||
|
expect(t.closing).toBe(false);
|
||||||
|
Toast.off('beforeClose', blocker);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('beforeUpdate 钩子返回 false 阻止更新', () => {
|
||||||
|
const t = MeToast.info('原始');
|
||||||
|
const blocker = () => false;
|
||||||
|
Toast.on('beforeUpdate', blocker);
|
||||||
|
t.update({ message: '新消息' });
|
||||||
|
expect(t.message).toBe('原始');
|
||||||
|
Toast.off('beforeUpdate', blocker);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('configure/updateConfig/resetConfig 触发 configChange 钩子', () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
Toast.on('configChange', fn);
|
||||||
|
MeToast.configure({ duration: 2500 });
|
||||||
|
MeToast.updateConfig({ gap: 8 });
|
||||||
|
MeToast.resetConfig();
|
||||||
|
expect(fn).toHaveBeenCalledTimes(3);
|
||||||
|
Toast.off('configChange', fn);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('init 触发 beforeInit/afterInit 钩子', () => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
const before = jest.fn(() => calls.push('beforeInit'));
|
||||||
|
const after = jest.fn(() => calls.push('afterInit'));
|
||||||
|
Toast.on('beforeInit', before);
|
||||||
|
Toast.on('afterInit', after);
|
||||||
|
MeToast.init();
|
||||||
|
expect(calls).toEqual(['beforeInit', 'afterInit']);
|
||||||
|
Toast.off('beforeInit', before);
|
||||||
|
Toast.off('afterInit', after);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 触发 beforeDestroy/afterDestroy 钩子', () => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
const before = jest.fn(() => calls.push('beforeDestroy'));
|
||||||
|
const after = jest.fn(() => calls.push('afterDestroy'));
|
||||||
|
Toast.on('beforeDestroy', before);
|
||||||
|
Toast.on('afterDestroy', after);
|
||||||
|
MeToast.destroy();
|
||||||
|
expect(calls).toEqual(['beforeDestroy', 'afterDestroy']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 清理全部钩子', () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
Toast.on('afterShow', fn);
|
||||||
|
MeToast.destroy();
|
||||||
|
MeToast.success('after-destroy');
|
||||||
|
expect(fn).not.toHaveBeenCalled();
|
||||||
|
MeToast.animations.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 清理实例注册表', () => {
|
||||||
|
MeToast.info('m1');
|
||||||
|
expect(Toast._registry.size).toBe(1);
|
||||||
|
MeToast.destroy();
|
||||||
|
expect(Toast._registry.size).toBe(0);
|
||||||
|
MeToast.animations.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 后 init 恢复可用', () => {
|
||||||
|
MeToast.destroy();
|
||||||
|
MeToast.init({});
|
||||||
|
const t = MeToast.success('revived');
|
||||||
|
expect(t).toBeDefined();
|
||||||
|
MeToast.animations.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('init plugins 支持插件对象', () => {
|
||||||
|
const install = jest.fn();
|
||||||
|
MeToast.init({ plugins: [{ name: 'obj-plugin', install }] });
|
||||||
|
expect(MeToast.plugins.has('obj-plugin')).toBe(true);
|
||||||
|
expect(install).toHaveBeenCalled();
|
||||||
|
MeToast.plugins.unregister('obj-plugin');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('animate 生命周期钩子 progressStart/progressEnd 触发', (done) => {
|
||||||
|
const start = jest.fn();
|
||||||
|
const end = jest.fn();
|
||||||
|
Toast.on('progressStart', start);
|
||||||
|
Toast.on('progressEnd', end);
|
||||||
|
const t = MeToast.info({ message: 'timed', duration: 30 });
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(start).toHaveBeenCalled();
|
||||||
|
Toast.off('progressStart', start);
|
||||||
|
Toast.off('progressEnd', end);
|
||||||
|
done();
|
||||||
|
}, 120);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+6
-6
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast 单元测试
|
* MetonaToast 单元测试
|
||||||
* @module tests
|
* @module tests
|
||||||
* @version 0.2.1
|
* @version 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MeToast, { Toast, VERSION } from '../src/index';
|
import MeToast, { Toast, VERSION } from '../src/index';
|
||||||
@@ -105,8 +105,8 @@ const mockWindow = {
|
|||||||
|
|
||||||
describe('版本信息', () => {
|
describe('版本信息', () => {
|
||||||
test('应该有正确的版本号', () => {
|
test('应该有正确的版本号', () => {
|
||||||
expect(VERSION).toBe('0.2.1');
|
expect(VERSION).toBe('0.3.0');
|
||||||
expect(MeToast.version).toBe('0.2.1');
|
expect(MeToast.version).toBe('0.3.0');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -526,7 +526,7 @@ const mockWindow = {
|
|||||||
test('应该能够获取状态信息', () => {
|
test('应该能够获取状态信息', () => {
|
||||||
MeToast.success('消息');
|
MeToast.success('消息');
|
||||||
const status = MeToast.getStatus();
|
const status = MeToast.getStatus();
|
||||||
expect(status.version).toBe('0.2.1');
|
expect(status.version).toBe('0.3.0');
|
||||||
expect(status.toasts).toBeGreaterThanOrEqual(0);
|
expect(status.toasts).toBeGreaterThanOrEqual(0);
|
||||||
expect(status.theme).toBeDefined();
|
expect(status.theme).toBeDefined();
|
||||||
expect(status.locale).toBeDefined();
|
expect(status.locale).toBeDefined();
|
||||||
@@ -1213,9 +1213,9 @@ describe('新增功能', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========== v0.2.1 新增功能测试 ==========
|
// ========== v0.3.0 新增功能测试 ==========
|
||||||
|
|
||||||
describe('v0.2.1 新增功能', () => {
|
describe('v0.3.0 新增功能', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MeToast._toasts.clear();
|
MeToast._toasts.clear();
|
||||||
const { _containerCache } = require('../src/toast.js');
|
const { _containerCache } = require('../src/toast.js');
|
||||||
|
|||||||
Reference in New Issue
Block a user