release: v0.4.0 — React 适配器、loading链式id稳定、dragThreshold、RTL适配、dedupe插件
- React 适配器子包:@metona-team/metona-toast/react — useToast() hook + 声明式 <Toast /> 组件,组件卸载自动清理;react 为 optional peerDependency,主包保持零依赖 - loading 链式转换改原地 update:id 稳定不重建 DOM,转换后 duration 恢复默认自动关闭;update() 支持 duration 变更动态重启计时器 - _emit 幽灵实例修复:beforeShow 拦截后的 toast 不再注册进 _toasts - dragThreshold 配置项(默认 120px,原硬编码);RTL 容器 dir 属性 + 进度条/side 条/色条镜像 - dedupe 预设插件:相同 type+message 自动去重,支持 uninstall - jest 环境隔离测试独立成 tests/ssr.test.ts(resetModules 不再污染共享模块状态);新增 tests/setup.ts 补 TextEncoder - 文档:README/docs.html 补 React 适配器、dedupe、dragThreshold;CHANGELOG 更新;328 测试全过
This commit is contained in:
@@ -2,6 +2,19 @@
|
|||||||
|
|
||||||
All notable changes to MetonaToast will be documented in this file.
|
All notable changes to MetonaToast will be documented in this file.
|
||||||
|
|
||||||
|
## [0.4.0] - 2026-08-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **React 适配器子包**:`@metona-team/metona-toast/react` — `useToast()` hook(组件卸载时自动移除本组件创建的 Toast)+ 声明式 `<Toast />` 组件(props 变化更新、卸载自动清理)。主包保持零依赖,`react` 为 optional peerDependency
|
||||||
|
- **`dedupe` 预设插件**:相同 type+message 的 Toast 自动去重(更新已有实例而非重复弹出),支持 uninstall
|
||||||
|
- **`dragThreshold` 配置项**:拖拽关闭阈值可配置(默认 120px,原为硬编码)
|
||||||
|
- **RTL 完整适配**:容器设置 `dir="rtl"`,进度条/side 条/类型色条/圆角正确镜像
|
||||||
|
- **`update({ duration })` 支持**:动态变更显示时长并自动重启/停止计时器(此前 update 忽略 duration)
|
||||||
|
- **幽灵实例修复**:`_emit` 检查 `create()` 结果,beforeShow 拦截后的 toast 不再注册到 `_toasts`(此前会残留幽灵记录,影响 count 与去重类插件)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **loading 链式转换 id 稳定**:`loading.success/error/info/warning` 由"关闭旧 + 新建"改为原地 `update()` 同一实例,返回的 Toast id 与 loading id 一致;转换后 duration 自动恢复默认值(自动关闭)。注:转换不再产生新的入场动画,改为平滑更新;关闭按钮/进度条保持 loading 配置(如需默认外观,请通过 opts 覆盖)
|
||||||
|
|
||||||
## [0.3.0] - 2026-08-08
|
## [0.3.0] - 2026-08-08
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
- **11 种动画** — slide/fade/scale/bounce/flip/rotate/zoom/slideUp/slideDown/slideLeft/slideRight,每种效果明显不同
|
- **11 种动画** — slide/fade/scale/bounce/flip/rotate/zoom/slideUp/slideDown/slideLeft/slideRight,每种效果明显不同
|
||||||
- **主题系统** — light/dark/auto/warm 四种内置主题,支持 `registerTheme()` 注册自定义主题
|
- **主题系统** — light/dark/auto/warm 四种内置主题,支持 `registerTheme()` 注册自定义主题
|
||||||
- **国际化** — 内置 zh-CN / en-US 完整翻译(去重优化),可通过 `addTranslations()` 扩展
|
- **国际化** — 内置 zh-CN / en-US 完整翻译(去重优化),可通过 `addTranslations()` 扩展
|
||||||
- **插件系统** — 3 款预设插件 (keyboard/persistence/accessibility) + 自定义插件
|
- **插件系统** — 4 款预设插件 (keyboard/persistence/accessibility/dedupe) + 自定义插件
|
||||||
- **可拖拽关闭** — 拖动 Toast 任意方向即可关闭
|
- **可拖拽关闭** — 拖动 Toast 任意方向即可关闭,阈值可配置
|
||||||
- **TypeScript** — 严格模式,源码级类型安全,类型从源码自动生成无手动维护
|
- **TypeScript** — 严格模式,源码级类型安全,类型从源码自动生成无手动维护
|
||||||
- **全局错误回调** — `onError` 捕获所有钩子异常和定时器错误,方便接入监控系统
|
- **全局错误回调** — `onError` 捕获所有钩子异常和定时器错误,方便接入监控系统
|
||||||
|
|
||||||
@@ -82,9 +82,11 @@ MeToast.info('系统维护中');
|
|||||||
// 带标题
|
// 带标题
|
||||||
MeToast.success({ title: '保存成功', message: '文件已同步到云端' });
|
MeToast.success({ title: '保存成功', message: '文件已同步到云端' });
|
||||||
|
|
||||||
// 加载 → 成功链式转换
|
// 加载 → 成功链式转换(原地更新同一实例,id 稳定)
|
||||||
const loading = MeToast.loading('正在提交...');
|
const loading = MeToast.loading('正在提交...');
|
||||||
setTimeout(() => loading.success('提交成功!'), 2000);
|
const toast = await wait();
|
||||||
|
loading.success('提交成功!');
|
||||||
|
// toast.id === loading.id
|
||||||
|
|
||||||
// Promise 风格
|
// Promise 风格
|
||||||
await MeToast.promise(fetch('/api/data'), {
|
await MeToast.promise(fetch('/api/data'), {
|
||||||
@@ -105,6 +107,8 @@ MeToast.configure({
|
|||||||
animation: 'slide', // slide | fade | scale | bounce | flip | rotate | zoom | slideUp | slideDown | slideLeft | slideRight
|
animation: 'slide', // slide | fade | scale | bounce | flip | rotate | zoom | slideUp | slideDown | slideLeft | slideRight
|
||||||
pauseOnHover: true, // 悬停暂停计时
|
pauseOnHover: true, // 悬停暂停计时
|
||||||
closeOnClick: true, // 点击关闭
|
closeOnClick: true, // 点击关闭
|
||||||
|
draggable: true, // 允许拖拽关闭
|
||||||
|
dragThreshold: 120, // 拖拽关闭阈值(px)
|
||||||
showProgress: true, // 显示进度条
|
showProgress: true, // 显示进度条
|
||||||
draggable: true, // 允许拖拽关闭
|
draggable: true, // 允许拖拽关闭
|
||||||
locale: 'zh-CN', // 语言
|
locale: 'zh-CN', // 语言
|
||||||
@@ -197,6 +201,7 @@ MeToast.stack(['消息1', '消息2', '消息3'], { stagger: 150 });
|
|||||||
MeToast.use('keyboard'); // ESC 关闭所有 Toast
|
MeToast.use('keyboard'); // ESC 关闭所有 Toast
|
||||||
MeToast.use('persistence'); // 配置持久化到 localStorage
|
MeToast.use('persistence'); // 配置持久化到 localStorage
|
||||||
MeToast.use('accessibility'); // 屏幕阅读器朗读公告
|
MeToast.use('accessibility'); // 屏幕阅读器朗读公告
|
||||||
|
MeToast.use('dedupe'); // 相同 type+message 自动去重(更新已有实例)
|
||||||
|
|
||||||
// 自定义插件(通过 install 中注册 Toast 生命周期钩子)
|
// 自定义插件(通过 install 中注册 Toast 生命周期钩子)
|
||||||
import MeToast, { Toast } from '@metona-team/metona-toast';
|
import MeToast, { Toast } from '@metona-team/metona-toast';
|
||||||
@@ -262,6 +267,40 @@ MeToast.configure({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## React 适配器
|
||||||
|
|
||||||
|
主包保持零依赖;React 适配器通过子路径导入,`react` 为 optional peerDependency。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @metona-team/metona-toast react
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useToast, Toast } from '@metona-team/metona-toast/react';
|
||||||
|
|
||||||
|
function SubmitButton() {
|
||||||
|
// 本组件创建的 Toast 会在组件卸载时自动移除
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
const loading = toast.loading('正在提交...');
|
||||||
|
try {
|
||||||
|
await api.submit();
|
||||||
|
loading.success('提交成功!');
|
||||||
|
} catch (e) {
|
||||||
|
loading.error('提交失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <button onClick={submit}>提交</button>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声明式 Toast — props 变化时更新,组件卸载时自动移除
|
||||||
|
function SaveIndicator({ saving }: { saving: boolean }) {
|
||||||
|
return saving ? <Toast type="info" message="正在保存..." /> : null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
Vendored
+107
-19
@@ -5,7 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Utils — 工具函数
|
* MetonaToast Utils — 工具函数
|
||||||
* @module utils
|
* @module utils
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 生成唯一ID
|
* 生成唯一ID
|
||||||
@@ -42,7 +42,7 @@ const prefersDark = () => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Icons — 图标SVG定义
|
* MetonaToast Icons — 图标SVG定义
|
||||||
* @module icons
|
* @module icons
|
||||||
* @version 0.3.0
|
* @version 0.4.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.3.0
|
* @version 0.4.0
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
const LOCALES = {
|
const LOCALES = {
|
||||||
@@ -1026,12 +1026,12 @@ const LOCALES = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants — 常量定义
|
* MetonaToast Constants — 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 版本号 — 唯一来源,发布时只需修改此处
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
*/
|
*/
|
||||||
const VERSION = '0.3.0';
|
const VERSION = '0.4.0';
|
||||||
/**
|
/**
|
||||||
* 默认配置
|
* 默认配置
|
||||||
*/
|
*/
|
||||||
@@ -1044,6 +1044,7 @@ const DEFAULTS = Object.freeze({
|
|||||||
pauseOnHover: true,
|
pauseOnHover: true,
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
|
dragThreshold: 120,
|
||||||
showProgress: true,
|
showProgress: true,
|
||||||
progressDirection: 'horizontal',
|
progressDirection: 'horizontal',
|
||||||
icon: true,
|
icon: true,
|
||||||
@@ -1297,7 +1298,7 @@ const ANIMATION_TYPES = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', '
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Styles — 样式管理
|
* MetonaToast Styles — 样式管理
|
||||||
* @module styles
|
* @module styles
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
// 样式缓存
|
// 样式缓存
|
||||||
let styleElement = null;
|
let styleElement = null;
|
||||||
@@ -1655,6 +1656,41 @@ const generateCSS = () => {
|
|||||||
transition: transform 0.1s linear;
|
transition: transform 0.1s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RTL 镜像:进度条从右向左收缩、side 条移到右侧、类型色条翻转到右边 */
|
||||||
|
[dir="rtl"] .met-bar {
|
||||||
|
transform-origin: right;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-side {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-progress-v {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-success {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #10b981;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-error {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #ef4444;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-warning {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #f59e0b;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-info {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #3b82f6;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-loading {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
|
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||||
.met-container::-webkit-scrollbar-track { background: transparent; }
|
.met-container::-webkit-scrollbar-track { background: transparent; }
|
||||||
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
|
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
|
||||||
@@ -1751,7 +1787,7 @@ const injectStyles = () => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Themes — 主题管理
|
* MetonaToast Themes — 主题管理
|
||||||
* @module themes
|
* @module themes
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
// 当前主题状态
|
// 当前主题状态
|
||||||
let currentTheme = 'auto';
|
let currentTheme = 'auto';
|
||||||
@@ -2095,7 +2131,7 @@ const themeUtils = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast i18n — 国际化管理
|
* MetonaToast i18n — 国际化管理
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
// 当前语言状态
|
// 当前语言状态
|
||||||
let currentLocale = 'zh-CN';
|
let currentLocale = 'zh-CN';
|
||||||
@@ -2770,7 +2806,7 @@ const animationUtils = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Toast 类 — 核心通知组件
|
* Toast 类 — 核心通知组件
|
||||||
@@ -2931,6 +2967,9 @@ class Toast {
|
|||||||
el.className = `met-container ${position}`;
|
el.className = `met-container ${position}`;
|
||||||
el.setAttribute('aria-label', 'Notifications');
|
el.setAttribute('aria-label', 'Notifications');
|
||||||
el.setAttribute('role', 'region');
|
el.setAttribute('role', 'region');
|
||||||
|
// RTL 语言设置容器文字方向,使内容/关闭按钮/进度条正确镜像
|
||||||
|
if (isRTL)
|
||||||
|
el.setAttribute('dir', 'rtl');
|
||||||
const posStyles = {
|
const posStyles = {
|
||||||
'top-left': 'top:0;left:0;align-items:flex-start',
|
'top-left': 'top:0;left:0;align-items:flex-start',
|
||||||
'top-center': 'top:0;left:0;right:0;align-items:center',
|
'top-center': 'top:0;left:0;right:0;align-items:center',
|
||||||
@@ -3122,7 +3161,8 @@ class Toast {
|
|||||||
dragging = false;
|
dragging = false;
|
||||||
el.releasePointerCapture(e.pointerId);
|
el.releasePointerCapture(e.pointerId);
|
||||||
el.style.transition = '';
|
el.style.transition = '';
|
||||||
if (Math.abs(dx) > 120) {
|
const threshold = this.config.dragThreshold ?? 120;
|
||||||
|
if (Math.abs(dx) > threshold) {
|
||||||
el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
|
el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
|
||||||
el.style.opacity = '0';
|
el.style.opacity = '0';
|
||||||
setTimeout(() => this.close(true), 250);
|
setTimeout(() => this.close(true), 250);
|
||||||
@@ -3240,6 +3280,18 @@ class Toast {
|
|||||||
: (this.message ? `<div class="met-message">${escapeHTML(this.message)}</div>` : '');
|
: (this.message ? `<div class="met-message">${escapeHTML(this.message)}</div>` : '');
|
||||||
content.innerHTML = `${safeTitle}${safeMessage}`;
|
content.innerHTML = `${safeTitle}${safeMessage}`;
|
||||||
}
|
}
|
||||||
|
// duration 变更:重启或停止计时器(loading 0 → N 转换、动态调整时长等场景)
|
||||||
|
if (partial.duration !== undefined && partial.duration !== this.config.duration) {
|
||||||
|
this.config.duration = partial.duration;
|
||||||
|
if (this.rafId !== null)
|
||||||
|
cancelAnimationFrame(this.rafId);
|
||||||
|
this.rafId = null;
|
||||||
|
this.remaining = Math.max(0, partial.duration);
|
||||||
|
if (partial.duration > 0 && !this.paused) {
|
||||||
|
this.startedAt = Date.now();
|
||||||
|
this._startTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
// resetTimerOnUpdate: 更新内容后重置计时器
|
// resetTimerOnUpdate: 更新内容后重置计时器
|
||||||
if (this.config.resetTimerOnUpdate && (this.config.duration || 0) > 0) {
|
if (this.config.resetTimerOnUpdate && (this.config.duration || 0) > 0) {
|
||||||
if (this.rafId !== null)
|
if (this.rafId !== null)
|
||||||
@@ -3386,7 +3438,7 @@ const _containerCache = new Map();
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Templates — HTML 模板辅助函数
|
* MetonaToast Templates — HTML 模板辅助函数
|
||||||
* @module templates
|
* @module templates
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
* @description confirm / prompt / progress / action 的 DOM 模板
|
* @description confirm / prompt / progress / action 的 DOM 模板
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
@@ -3458,7 +3510,7 @@ const actionHTML = (actions) => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Plugins — 插件系统
|
* MetonaToast Plugins — 插件系统
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 插件管理器
|
* 插件管理器
|
||||||
@@ -3636,6 +3688,32 @@ const presetPlugins = {
|
|||||||
el.parentNode.removeChild(el); }, 3000);
|
el.parentNode.removeChild(el); }, 3000);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 去重插件 — 相同 type + message 的 toast 自动合并(更新已有的一条)
|
||||||
|
*/
|
||||||
|
dedupe: {
|
||||||
|
name: 'dedupe',
|
||||||
|
version: '1.0.0',
|
||||||
|
description: '相同类型和消息的 Toast 自动去重,更新已有实例而非重复弹出',
|
||||||
|
_off: null,
|
||||||
|
install() {
|
||||||
|
const off = Toast.on('beforeShow', (toast) => {
|
||||||
|
const existing = Array.from(Toast._registry.values()).find(t => !t.closing && t.type === toast.type && t.message === toast.message);
|
||||||
|
if (existing) {
|
||||||
|
existing.update({ title: toast.title || existing.title });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
this._off = off;
|
||||||
|
},
|
||||||
|
uninstall() {
|
||||||
|
const off = this._off;
|
||||||
|
if (off)
|
||||||
|
off();
|
||||||
|
this._off = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 插件工具
|
* 插件工具
|
||||||
@@ -3678,7 +3756,7 @@ const defaultPluginManager = new PluginManager();
|
|||||||
/**
|
/**
|
||||||
* MetonaToast API — meToast 核心 API 对象
|
* MetonaToast API — meToast 核心 API 对象
|
||||||
* @module api
|
* @module api
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 参数标准化工具
|
* 参数标准化工具
|
||||||
@@ -3729,7 +3807,10 @@ const meToast = {
|
|||||||
merged.message = merged.content;
|
merged.message = merged.content;
|
||||||
const t = new Toast(merged);
|
const t = new Toast(merged);
|
||||||
t.create();
|
t.create();
|
||||||
|
// beforeShow/onBeforeShow 拦截(返回 false)时 el 为 null,不注册幽灵实例
|
||||||
|
if (t.el !== null) {
|
||||||
this._toasts.set(t.id, t);
|
this._toasts.set(t.id, t);
|
||||||
|
}
|
||||||
return t;
|
return t;
|
||||||
},
|
},
|
||||||
_remove(id) {
|
_remove(id) {
|
||||||
@@ -3795,14 +3876,21 @@ const meToast = {
|
|||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* loading 链式转换 — 原地 update 同一实例(id 稳定,不重建 DOM)
|
||||||
|
* duration 从 0 恢复为默认值,使转换后的 toast 自动关闭
|
||||||
|
*/
|
||||||
_resolve(loadingToast, type, message, opts) {
|
_resolve(loadingToast, type, message, opts) {
|
||||||
const id = loadingToast.id;
|
const old = this._toasts.get(loadingToast.id);
|
||||||
const old = this._toasts.get(id);
|
|
||||||
if (!old)
|
if (!old)
|
||||||
return null;
|
return null;
|
||||||
const position = old.config.position;
|
old.update({
|
||||||
old.close();
|
...opts,
|
||||||
return this._emit({ ...opts, type, message, position });
|
type,
|
||||||
|
message,
|
||||||
|
duration: opts.duration ?? (old.config.duration || this._config.duration),
|
||||||
|
});
|
||||||
|
return old;
|
||||||
},
|
},
|
||||||
confirm(message, opts = {}) {
|
confirm(message, opts = {}) {
|
||||||
if (typeof message !== 'string') {
|
if (typeof message !== 'string') {
|
||||||
@@ -4340,7 +4428,7 @@ Toast.setCallbacks({
|
|||||||
/**
|
/**
|
||||||
* MetonaToast — 轻量级Toast通知库
|
* MetonaToast — 轻量级Toast通知库
|
||||||
* @module metona-toast
|
* @module metona-toast
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+5
-4
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast — 核心类型定义
|
* MetonaToast — 核心类型定义
|
||||||
* @module types
|
* @module types
|
||||||
* @version 0.3.0
|
* @version 0.4.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';
|
||||||
@@ -26,6 +26,7 @@ interface ToastConfig {
|
|||||||
pauseOnHover?: boolean;
|
pauseOnHover?: boolean;
|
||||||
closeOnClick?: boolean;
|
closeOnClick?: boolean;
|
||||||
draggable?: boolean;
|
draggable?: boolean;
|
||||||
|
dragThreshold?: number;
|
||||||
showProgress?: boolean;
|
showProgress?: boolean;
|
||||||
progressDirection?: ToastProgressDirection;
|
progressDirection?: ToastProgressDirection;
|
||||||
icon?: boolean;
|
icon?: boolean;
|
||||||
@@ -417,7 +418,7 @@ declare global {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -491,13 +492,13 @@ declare class Toast implements ToastInstance {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants — 常量定义
|
* MetonaToast Constants — 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本号 — 唯一来源,发布时只需修改此处
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
*/
|
*/
|
||||||
declare const VERSION = "0.3.0";
|
declare const VERSION = "0.4.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
||||||
|
|||||||
Vendored
+107
-19
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Utils — 工具函数
|
* MetonaToast Utils — 工具函数
|
||||||
* @module utils
|
* @module utils
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 生成唯一ID
|
* 生成唯一ID
|
||||||
@@ -38,7 +38,7 @@ const prefersDark = () => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Icons — 图标SVG定义
|
* MetonaToast Icons — 图标SVG定义
|
||||||
* @module icons
|
* @module icons
|
||||||
* @version 0.3.0
|
* @version 0.4.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.3.0
|
* @version 0.4.0
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
const LOCALES = {
|
const LOCALES = {
|
||||||
@@ -1022,12 +1022,12 @@ const LOCALES = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants — 常量定义
|
* MetonaToast Constants — 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 版本号 — 唯一来源,发布时只需修改此处
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
*/
|
*/
|
||||||
const VERSION = '0.3.0';
|
const VERSION = '0.4.0';
|
||||||
/**
|
/**
|
||||||
* 默认配置
|
* 默认配置
|
||||||
*/
|
*/
|
||||||
@@ -1040,6 +1040,7 @@ const DEFAULTS = Object.freeze({
|
|||||||
pauseOnHover: true,
|
pauseOnHover: true,
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
|
dragThreshold: 120,
|
||||||
showProgress: true,
|
showProgress: true,
|
||||||
progressDirection: 'horizontal',
|
progressDirection: 'horizontal',
|
||||||
icon: true,
|
icon: true,
|
||||||
@@ -1293,7 +1294,7 @@ const ANIMATION_TYPES = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', '
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Styles — 样式管理
|
* MetonaToast Styles — 样式管理
|
||||||
* @module styles
|
* @module styles
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
// 样式缓存
|
// 样式缓存
|
||||||
let styleElement = null;
|
let styleElement = null;
|
||||||
@@ -1651,6 +1652,41 @@ const generateCSS = () => {
|
|||||||
transition: transform 0.1s linear;
|
transition: transform 0.1s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RTL 镜像:进度条从右向左收缩、side 条移到右侧、类型色条翻转到右边 */
|
||||||
|
[dir="rtl"] .met-bar {
|
||||||
|
transform-origin: right;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-side {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-progress-v {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-success {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #10b981;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-error {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #ef4444;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-warning {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #f59e0b;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-info {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #3b82f6;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-loading {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
|
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||||
.met-container::-webkit-scrollbar-track { background: transparent; }
|
.met-container::-webkit-scrollbar-track { background: transparent; }
|
||||||
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
|
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
|
||||||
@@ -1747,7 +1783,7 @@ const injectStyles = () => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Themes — 主题管理
|
* MetonaToast Themes — 主题管理
|
||||||
* @module themes
|
* @module themes
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
// 当前主题状态
|
// 当前主题状态
|
||||||
let currentTheme = 'auto';
|
let currentTheme = 'auto';
|
||||||
@@ -2091,7 +2127,7 @@ const themeUtils = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast i18n — 国际化管理
|
* MetonaToast i18n — 国际化管理
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
// 当前语言状态
|
// 当前语言状态
|
||||||
let currentLocale = 'zh-CN';
|
let currentLocale = 'zh-CN';
|
||||||
@@ -2766,7 +2802,7 @@ const animationUtils = {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Toast 类 — 核心通知组件
|
* Toast 类 — 核心通知组件
|
||||||
@@ -2927,6 +2963,9 @@ class Toast {
|
|||||||
el.className = `met-container ${position}`;
|
el.className = `met-container ${position}`;
|
||||||
el.setAttribute('aria-label', 'Notifications');
|
el.setAttribute('aria-label', 'Notifications');
|
||||||
el.setAttribute('role', 'region');
|
el.setAttribute('role', 'region');
|
||||||
|
// RTL 语言设置容器文字方向,使内容/关闭按钮/进度条正确镜像
|
||||||
|
if (isRTL)
|
||||||
|
el.setAttribute('dir', 'rtl');
|
||||||
const posStyles = {
|
const posStyles = {
|
||||||
'top-left': 'top:0;left:0;align-items:flex-start',
|
'top-left': 'top:0;left:0;align-items:flex-start',
|
||||||
'top-center': 'top:0;left:0;right:0;align-items:center',
|
'top-center': 'top:0;left:0;right:0;align-items:center',
|
||||||
@@ -3118,7 +3157,8 @@ class Toast {
|
|||||||
dragging = false;
|
dragging = false;
|
||||||
el.releasePointerCapture(e.pointerId);
|
el.releasePointerCapture(e.pointerId);
|
||||||
el.style.transition = '';
|
el.style.transition = '';
|
||||||
if (Math.abs(dx) > 120) {
|
const threshold = this.config.dragThreshold ?? 120;
|
||||||
|
if (Math.abs(dx) > threshold) {
|
||||||
el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
|
el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
|
||||||
el.style.opacity = '0';
|
el.style.opacity = '0';
|
||||||
setTimeout(() => this.close(true), 250);
|
setTimeout(() => this.close(true), 250);
|
||||||
@@ -3236,6 +3276,18 @@ class Toast {
|
|||||||
: (this.message ? `<div class="met-message">${escapeHTML(this.message)}</div>` : '');
|
: (this.message ? `<div class="met-message">${escapeHTML(this.message)}</div>` : '');
|
||||||
content.innerHTML = `${safeTitle}${safeMessage}`;
|
content.innerHTML = `${safeTitle}${safeMessage}`;
|
||||||
}
|
}
|
||||||
|
// duration 变更:重启或停止计时器(loading 0 → N 转换、动态调整时长等场景)
|
||||||
|
if (partial.duration !== undefined && partial.duration !== this.config.duration) {
|
||||||
|
this.config.duration = partial.duration;
|
||||||
|
if (this.rafId !== null)
|
||||||
|
cancelAnimationFrame(this.rafId);
|
||||||
|
this.rafId = null;
|
||||||
|
this.remaining = Math.max(0, partial.duration);
|
||||||
|
if (partial.duration > 0 && !this.paused) {
|
||||||
|
this.startedAt = Date.now();
|
||||||
|
this._startTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
// resetTimerOnUpdate: 更新内容后重置计时器
|
// resetTimerOnUpdate: 更新内容后重置计时器
|
||||||
if (this.config.resetTimerOnUpdate && (this.config.duration || 0) > 0) {
|
if (this.config.resetTimerOnUpdate && (this.config.duration || 0) > 0) {
|
||||||
if (this.rafId !== null)
|
if (this.rafId !== null)
|
||||||
@@ -3382,7 +3434,7 @@ const _containerCache = new Map();
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Templates — HTML 模板辅助函数
|
* MetonaToast Templates — HTML 模板辅助函数
|
||||||
* @module templates
|
* @module templates
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
* @description confirm / prompt / progress / action 的 DOM 模板
|
* @description confirm / prompt / progress / action 的 DOM 模板
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
@@ -3454,7 +3506,7 @@ const actionHTML = (actions) => {
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Plugins — 插件系统
|
* MetonaToast Plugins — 插件系统
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 插件管理器
|
* 插件管理器
|
||||||
@@ -3632,6 +3684,32 @@ const presetPlugins = {
|
|||||||
el.parentNode.removeChild(el); }, 3000);
|
el.parentNode.removeChild(el); }, 3000);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 去重插件 — 相同 type + message 的 toast 自动合并(更新已有的一条)
|
||||||
|
*/
|
||||||
|
dedupe: {
|
||||||
|
name: 'dedupe',
|
||||||
|
version: '1.0.0',
|
||||||
|
description: '相同类型和消息的 Toast 自动去重,更新已有实例而非重复弹出',
|
||||||
|
_off: null,
|
||||||
|
install() {
|
||||||
|
const off = Toast.on('beforeShow', (toast) => {
|
||||||
|
const existing = Array.from(Toast._registry.values()).find(t => !t.closing && t.type === toast.type && t.message === toast.message);
|
||||||
|
if (existing) {
|
||||||
|
existing.update({ title: toast.title || existing.title });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
this._off = off;
|
||||||
|
},
|
||||||
|
uninstall() {
|
||||||
|
const off = this._off;
|
||||||
|
if (off)
|
||||||
|
off();
|
||||||
|
this._off = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 插件工具
|
* 插件工具
|
||||||
@@ -3674,7 +3752,7 @@ const defaultPluginManager = new PluginManager();
|
|||||||
/**
|
/**
|
||||||
* MetonaToast API — meToast 核心 API 对象
|
* MetonaToast API — meToast 核心 API 对象
|
||||||
* @module api
|
* @module api
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 参数标准化工具
|
* 参数标准化工具
|
||||||
@@ -3725,7 +3803,10 @@ const meToast = {
|
|||||||
merged.message = merged.content;
|
merged.message = merged.content;
|
||||||
const t = new Toast(merged);
|
const t = new Toast(merged);
|
||||||
t.create();
|
t.create();
|
||||||
|
// beforeShow/onBeforeShow 拦截(返回 false)时 el 为 null,不注册幽灵实例
|
||||||
|
if (t.el !== null) {
|
||||||
this._toasts.set(t.id, t);
|
this._toasts.set(t.id, t);
|
||||||
|
}
|
||||||
return t;
|
return t;
|
||||||
},
|
},
|
||||||
_remove(id) {
|
_remove(id) {
|
||||||
@@ -3791,14 +3872,21 @@ const meToast = {
|
|||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* loading 链式转换 — 原地 update 同一实例(id 稳定,不重建 DOM)
|
||||||
|
* duration 从 0 恢复为默认值,使转换后的 toast 自动关闭
|
||||||
|
*/
|
||||||
_resolve(loadingToast, type, message, opts) {
|
_resolve(loadingToast, type, message, opts) {
|
||||||
const id = loadingToast.id;
|
const old = this._toasts.get(loadingToast.id);
|
||||||
const old = this._toasts.get(id);
|
|
||||||
if (!old)
|
if (!old)
|
||||||
return null;
|
return null;
|
||||||
const position = old.config.position;
|
old.update({
|
||||||
old.close();
|
...opts,
|
||||||
return this._emit({ ...opts, type, message, position });
|
type,
|
||||||
|
message,
|
||||||
|
duration: opts.duration ?? (old.config.duration || this._config.duration),
|
||||||
|
});
|
||||||
|
return old;
|
||||||
},
|
},
|
||||||
confirm(message, opts = {}) {
|
confirm(message, opts = {}) {
|
||||||
if (typeof message !== 'string') {
|
if (typeof message !== 'string') {
|
||||||
@@ -4336,7 +4424,7 @@ Toast.setCallbacks({
|
|||||||
/**
|
/**
|
||||||
* MetonaToast — 轻量级Toast通知库
|
* MetonaToast — 轻量级Toast通知库
|
||||||
* @module metona-toast
|
* @module metona-toast
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+107
-19
@@ -7,7 +7,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Utils — 工具函数
|
* MetonaToast Utils — 工具函数
|
||||||
* @module utils
|
* @module utils
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 生成唯一ID
|
* 生成唯一ID
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Icons — 图标SVG定义
|
* MetonaToast Icons — 图标SVG定义
|
||||||
* @module icons
|
* @module icons
|
||||||
* @version 0.3.0
|
* @version 0.4.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.3.0
|
* @version 0.4.0
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
const LOCALES = {
|
const LOCALES = {
|
||||||
@@ -1028,12 +1028,12 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants — 常量定义
|
* MetonaToast Constants — 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 版本号 — 唯一来源,发布时只需修改此处
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
*/
|
*/
|
||||||
const VERSION = '0.3.0';
|
const VERSION = '0.4.0';
|
||||||
/**
|
/**
|
||||||
* 默认配置
|
* 默认配置
|
||||||
*/
|
*/
|
||||||
@@ -1046,6 +1046,7 @@
|
|||||||
pauseOnHover: true,
|
pauseOnHover: true,
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
|
dragThreshold: 120,
|
||||||
showProgress: true,
|
showProgress: true,
|
||||||
progressDirection: 'horizontal',
|
progressDirection: 'horizontal',
|
||||||
icon: true,
|
icon: true,
|
||||||
@@ -1299,7 +1300,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Styles — 样式管理
|
* MetonaToast Styles — 样式管理
|
||||||
* @module styles
|
* @module styles
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
// 样式缓存
|
// 样式缓存
|
||||||
let styleElement = null;
|
let styleElement = null;
|
||||||
@@ -1657,6 +1658,41 @@
|
|||||||
transition: transform 0.1s linear;
|
transition: transform 0.1s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RTL 镜像:进度条从右向左收缩、side 条移到右侧、类型色条翻转到右边 */
|
||||||
|
[dir="rtl"] .met-bar {
|
||||||
|
transform-origin: right;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-side {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-progress-v {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-success {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #10b981;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-error {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #ef4444;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-warning {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #f59e0b;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-info {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #3b82f6;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-loading {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
|
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||||
.met-container::-webkit-scrollbar-track { background: transparent; }
|
.met-container::-webkit-scrollbar-track { background: transparent; }
|
||||||
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
|
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
|
||||||
@@ -1753,7 +1789,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Themes — 主题管理
|
* MetonaToast Themes — 主题管理
|
||||||
* @module themes
|
* @module themes
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
// 当前主题状态
|
// 当前主题状态
|
||||||
let currentTheme = 'auto';
|
let currentTheme = 'auto';
|
||||||
@@ -2097,7 +2133,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast i18n — 国际化管理
|
* MetonaToast i18n — 国际化管理
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
// 当前语言状态
|
// 当前语言状态
|
||||||
let currentLocale = 'zh-CN';
|
let currentLocale = 'zh-CN';
|
||||||
@@ -2772,7 +2808,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Toast 类 — 核心通知组件
|
* Toast 类 — 核心通知组件
|
||||||
@@ -2933,6 +2969,9 @@
|
|||||||
el.className = `met-container ${position}`;
|
el.className = `met-container ${position}`;
|
||||||
el.setAttribute('aria-label', 'Notifications');
|
el.setAttribute('aria-label', 'Notifications');
|
||||||
el.setAttribute('role', 'region');
|
el.setAttribute('role', 'region');
|
||||||
|
// RTL 语言设置容器文字方向,使内容/关闭按钮/进度条正确镜像
|
||||||
|
if (isRTL)
|
||||||
|
el.setAttribute('dir', 'rtl');
|
||||||
const posStyles = {
|
const posStyles = {
|
||||||
'top-left': 'top:0;left:0;align-items:flex-start',
|
'top-left': 'top:0;left:0;align-items:flex-start',
|
||||||
'top-center': 'top:0;left:0;right:0;align-items:center',
|
'top-center': 'top:0;left:0;right:0;align-items:center',
|
||||||
@@ -3124,7 +3163,8 @@
|
|||||||
dragging = false;
|
dragging = false;
|
||||||
el.releasePointerCapture(e.pointerId);
|
el.releasePointerCapture(e.pointerId);
|
||||||
el.style.transition = '';
|
el.style.transition = '';
|
||||||
if (Math.abs(dx) > 120) {
|
const threshold = this.config.dragThreshold ?? 120;
|
||||||
|
if (Math.abs(dx) > threshold) {
|
||||||
el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
|
el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
|
||||||
el.style.opacity = '0';
|
el.style.opacity = '0';
|
||||||
setTimeout(() => this.close(true), 250);
|
setTimeout(() => this.close(true), 250);
|
||||||
@@ -3242,6 +3282,18 @@
|
|||||||
: (this.message ? `<div class="met-message">${escapeHTML(this.message)}</div>` : '');
|
: (this.message ? `<div class="met-message">${escapeHTML(this.message)}</div>` : '');
|
||||||
content.innerHTML = `${safeTitle}${safeMessage}`;
|
content.innerHTML = `${safeTitle}${safeMessage}`;
|
||||||
}
|
}
|
||||||
|
// duration 变更:重启或停止计时器(loading 0 → N 转换、动态调整时长等场景)
|
||||||
|
if (partial.duration !== undefined && partial.duration !== this.config.duration) {
|
||||||
|
this.config.duration = partial.duration;
|
||||||
|
if (this.rafId !== null)
|
||||||
|
cancelAnimationFrame(this.rafId);
|
||||||
|
this.rafId = null;
|
||||||
|
this.remaining = Math.max(0, partial.duration);
|
||||||
|
if (partial.duration > 0 && !this.paused) {
|
||||||
|
this.startedAt = Date.now();
|
||||||
|
this._startTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
// resetTimerOnUpdate: 更新内容后重置计时器
|
// resetTimerOnUpdate: 更新内容后重置计时器
|
||||||
if (this.config.resetTimerOnUpdate && (this.config.duration || 0) > 0) {
|
if (this.config.resetTimerOnUpdate && (this.config.duration || 0) > 0) {
|
||||||
if (this.rafId !== null)
|
if (this.rafId !== null)
|
||||||
@@ -3388,7 +3440,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Templates — HTML 模板辅助函数
|
* MetonaToast Templates — HTML 模板辅助函数
|
||||||
* @module templates
|
* @module templates
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
* @description confirm / prompt / progress / action 的 DOM 模板
|
* @description confirm / prompt / progress / action 的 DOM 模板
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
@@ -3460,7 +3512,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Plugins — 插件系统
|
* MetonaToast Plugins — 插件系统
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 插件管理器
|
* 插件管理器
|
||||||
@@ -3638,6 +3690,32 @@
|
|||||||
el.parentNode.removeChild(el); }, 3000);
|
el.parentNode.removeChild(el); }, 3000);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 去重插件 — 相同 type + message 的 toast 自动合并(更新已有的一条)
|
||||||
|
*/
|
||||||
|
dedupe: {
|
||||||
|
name: 'dedupe',
|
||||||
|
version: '1.0.0',
|
||||||
|
description: '相同类型和消息的 Toast 自动去重,更新已有实例而非重复弹出',
|
||||||
|
_off: null,
|
||||||
|
install() {
|
||||||
|
const off = Toast.on('beforeShow', (toast) => {
|
||||||
|
const existing = Array.from(Toast._registry.values()).find(t => !t.closing && t.type === toast.type && t.message === toast.message);
|
||||||
|
if (existing) {
|
||||||
|
existing.update({ title: toast.title || existing.title });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
this._off = off;
|
||||||
|
},
|
||||||
|
uninstall() {
|
||||||
|
const off = this._off;
|
||||||
|
if (off)
|
||||||
|
off();
|
||||||
|
this._off = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 插件工具
|
* 插件工具
|
||||||
@@ -3680,7 +3758,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast API — meToast 核心 API 对象
|
* MetonaToast API — meToast 核心 API 对象
|
||||||
* @module api
|
* @module api
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* 参数标准化工具
|
* 参数标准化工具
|
||||||
@@ -3731,7 +3809,10 @@
|
|||||||
merged.message = merged.content;
|
merged.message = merged.content;
|
||||||
const t = new Toast(merged);
|
const t = new Toast(merged);
|
||||||
t.create();
|
t.create();
|
||||||
|
// beforeShow/onBeforeShow 拦截(返回 false)时 el 为 null,不注册幽灵实例
|
||||||
|
if (t.el !== null) {
|
||||||
this._toasts.set(t.id, t);
|
this._toasts.set(t.id, t);
|
||||||
|
}
|
||||||
return t;
|
return t;
|
||||||
},
|
},
|
||||||
_remove(id) {
|
_remove(id) {
|
||||||
@@ -3797,14 +3878,21 @@
|
|||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* loading 链式转换 — 原地 update 同一实例(id 稳定,不重建 DOM)
|
||||||
|
* duration 从 0 恢复为默认值,使转换后的 toast 自动关闭
|
||||||
|
*/
|
||||||
_resolve(loadingToast, type, message, opts) {
|
_resolve(loadingToast, type, message, opts) {
|
||||||
const id = loadingToast.id;
|
const old = this._toasts.get(loadingToast.id);
|
||||||
const old = this._toasts.get(id);
|
|
||||||
if (!old)
|
if (!old)
|
||||||
return null;
|
return null;
|
||||||
const position = old.config.position;
|
old.update({
|
||||||
old.close();
|
...opts,
|
||||||
return this._emit({ ...opts, type, message, position });
|
type,
|
||||||
|
message,
|
||||||
|
duration: opts.duration ?? (old.config.duration || this._config.duration),
|
||||||
|
});
|
||||||
|
return old;
|
||||||
},
|
},
|
||||||
confirm(message, opts = {}) {
|
confirm(message, opts = {}) {
|
||||||
if (typeof message !== 'string') {
|
if (typeof message !== 'string') {
|
||||||
@@ -4342,7 +4430,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast — 轻量级Toast通知库
|
* MetonaToast — 轻量级Toast通知库
|
||||||
* @module metona-toast
|
* @module metona-toast
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|||||||
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
Vendored
+4582
File diff suppressed because it is too large
Load Diff
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+509
@@ -0,0 +1,509 @@
|
|||||||
|
/**
|
||||||
|
* MetonaToast — 核心类型定义
|
||||||
|
* @module types
|
||||||
|
* @version 0.4.0
|
||||||
|
*/
|
||||||
|
/** 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';
|
||||||
|
/** Toast 显示位置 */
|
||||||
|
type ToastPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
||||||
|
/** Toast 主题 */
|
||||||
|
type ToastTheme = 'light' | 'dark' | 'auto' | 'warm' | (string & {});
|
||||||
|
/** Toast 动画类型 */
|
||||||
|
type ToastAnimation = 'slide' | 'fade' | 'scale' | 'bounce' | 'flip' | 'rotate' | 'zoom' | 'slideUp' | 'slideDown' | 'slideLeft' | 'slideRight' | (string & {});
|
||||||
|
/** 进度条方向 */
|
||||||
|
type ToastProgressDirection = 'horizontal' | 'vertical';
|
||||||
|
interface TypeColor {
|
||||||
|
fg: string;
|
||||||
|
bg: string;
|
||||||
|
}
|
||||||
|
interface ToastConfig {
|
||||||
|
position?: ToastPosition;
|
||||||
|
duration?: number;
|
||||||
|
max?: number;
|
||||||
|
gap?: number;
|
||||||
|
offset?: number;
|
||||||
|
pauseOnHover?: boolean;
|
||||||
|
closeOnClick?: boolean;
|
||||||
|
draggable?: boolean;
|
||||||
|
dragThreshold?: number;
|
||||||
|
showProgress?: boolean;
|
||||||
|
progressDirection?: ToastProgressDirection;
|
||||||
|
icon?: boolean;
|
||||||
|
closeButton?: boolean;
|
||||||
|
theme?: ToastTheme;
|
||||||
|
animation?: ToastAnimation;
|
||||||
|
zIndex?: number;
|
||||||
|
width?: number | string;
|
||||||
|
className?: string;
|
||||||
|
style?: Record<string, string>;
|
||||||
|
onShow?: ((toast: ToastInstance) => void) | null;
|
||||||
|
onBeforeShow?: ((toast: ToastInstance) => boolean | void) | null;
|
||||||
|
onClose?: ((toast: ToastInstance) => void) | null;
|
||||||
|
onClick?: ((toast: ToastInstance) => void) | null;
|
||||||
|
onUpdate?: ((toast: ToastInstance) => void) | null;
|
||||||
|
render?: ((toast: ToastInstance) => string) | null;
|
||||||
|
resetTimerOnUpdate?: boolean;
|
||||||
|
notifyWhenHidden?: boolean;
|
||||||
|
onError?: ((errorInfo: ErrorInfo) => void) | null;
|
||||||
|
group?: string | null;
|
||||||
|
locale?: string;
|
||||||
|
plugins?: Array<string | Plugin>;
|
||||||
|
}
|
||||||
|
interface ErrorInfo {
|
||||||
|
hook?: string;
|
||||||
|
source?: string;
|
||||||
|
error: Error;
|
||||||
|
toast?: ToastInstance;
|
||||||
|
}
|
||||||
|
interface ToastOptions extends Partial<ToastConfig> {
|
||||||
|
type?: string;
|
||||||
|
title?: string;
|
||||||
|
message?: string;
|
||||||
|
content?: string;
|
||||||
|
html?: string;
|
||||||
|
iconHTML?: string;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
interface ToastInstance {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
html: string;
|
||||||
|
iconHTML: string;
|
||||||
|
config: ToastConfig;
|
||||||
|
el: HTMLElement | null;
|
||||||
|
barEl: HTMLElement | null;
|
||||||
|
rafId: number | null;
|
||||||
|
remaining: number;
|
||||||
|
startedAt: number;
|
||||||
|
paused: boolean;
|
||||||
|
closing: boolean;
|
||||||
|
group: string | null;
|
||||||
|
_cleanups: Array<() => void>;
|
||||||
|
_palette(): {
|
||||||
|
theme: string;
|
||||||
|
c: TypeColor;
|
||||||
|
t: Partial<ThemeConfig>;
|
||||||
|
};
|
||||||
|
create(): this;
|
||||||
|
update(partial: Partial<ToastOptions>): this;
|
||||||
|
updatePosition(position: string): this;
|
||||||
|
remove(): void;
|
||||||
|
close(immediate?: boolean): void;
|
||||||
|
_pause(): void;
|
||||||
|
_resume(): void;
|
||||||
|
_destroy(): void;
|
||||||
|
}
|
||||||
|
interface LoadingControl {
|
||||||
|
id: string;
|
||||||
|
success(message?: string, opts?: ToastOptions): ToastInstance | null;
|
||||||
|
error(message?: string, opts?: ToastOptions): ToastInstance | null;
|
||||||
|
info(message?: string, opts?: ToastOptions): ToastInstance | null;
|
||||||
|
warning(message?: string, opts?: ToastOptions): ToastInstance | null;
|
||||||
|
update(partial: Partial<ToastOptions>): unknown;
|
||||||
|
dismiss(): void;
|
||||||
|
}
|
||||||
|
interface ProgressControl {
|
||||||
|
id: string;
|
||||||
|
setProgress(percent: number): void;
|
||||||
|
complete(message?: string): void;
|
||||||
|
error(message?: string): void;
|
||||||
|
dismiss(): void;
|
||||||
|
}
|
||||||
|
interface CountdownControl {
|
||||||
|
id: string;
|
||||||
|
cancel(): void;
|
||||||
|
pause(): void;
|
||||||
|
resume(): void;
|
||||||
|
}
|
||||||
|
interface ActionControl {
|
||||||
|
id: string;
|
||||||
|
toast: ToastInstance;
|
||||||
|
dismiss(): void;
|
||||||
|
}
|
||||||
|
interface QueueControl {
|
||||||
|
then<TResult1 = void, TResult2 = never>(onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
||||||
|
catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null): Promise<void | TResult>;
|
||||||
|
cancel(): void;
|
||||||
|
}
|
||||||
|
interface GroupAPI {
|
||||||
|
_group: string;
|
||||||
|
show: (...args: unknown[]) => ToastInstance;
|
||||||
|
success: (...args: unknown[]) => ToastInstance;
|
||||||
|
error: (...args: unknown[]) => ToastInstance;
|
||||||
|
warning: (...args: unknown[]) => ToastInstance;
|
||||||
|
info: (...args: unknown[]) => ToastInstance;
|
||||||
|
loading: (...args: unknown[]) => LoadingControl;
|
||||||
|
action: (...args: unknown[]) => ActionControl;
|
||||||
|
dismiss(): void;
|
||||||
|
count(): number;
|
||||||
|
}
|
||||||
|
interface ActionButton {
|
||||||
|
text: string;
|
||||||
|
onClick: (toast: ToastInstance) => void;
|
||||||
|
color?: string;
|
||||||
|
style?: Record<string, string>;
|
||||||
|
close?: boolean;
|
||||||
|
}
|
||||||
|
interface PromiseOptions extends ToastOptions {
|
||||||
|
loading?: string;
|
||||||
|
success?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
interface ConfirmOptions extends ToastOptions {
|
||||||
|
confirmText?: string;
|
||||||
|
confirmColor?: string;
|
||||||
|
cancelText?: string;
|
||||||
|
cancelColor?: string;
|
||||||
|
}
|
||||||
|
interface PromptOptions extends ToastOptions {
|
||||||
|
inputType?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
submitText?: string;
|
||||||
|
submitColor?: string;
|
||||||
|
cancelText?: string;
|
||||||
|
cancelColor?: string;
|
||||||
|
}
|
||||||
|
interface ProgressOptions extends ToastOptions {
|
||||||
|
progressColor?: string;
|
||||||
|
}
|
||||||
|
interface CountdownOptions extends ToastOptions {
|
||||||
|
onComplete?: (() => void) | null;
|
||||||
|
}
|
||||||
|
interface QueueOptions extends ToastOptions {
|
||||||
|
delay?: number;
|
||||||
|
}
|
||||||
|
interface StackOptions extends ToastOptions {
|
||||||
|
stagger?: number;
|
||||||
|
}
|
||||||
|
interface InitOptions {
|
||||||
|
config?: Partial<ToastConfig>;
|
||||||
|
theme?: ToastTheme;
|
||||||
|
locale?: string;
|
||||||
|
plugins?: Array<string | Plugin>;
|
||||||
|
}
|
||||||
|
interface StatusInfo {
|
||||||
|
version: string;
|
||||||
|
toasts: number;
|
||||||
|
theme: string;
|
||||||
|
locale: string;
|
||||||
|
plugins: string[];
|
||||||
|
animations: number;
|
||||||
|
}
|
||||||
|
interface AnimationConfig {
|
||||||
|
enter: Record<string, string | number>;
|
||||||
|
leave: Record<string, string | number>;
|
||||||
|
duration: number;
|
||||||
|
easing: string;
|
||||||
|
name?: string;
|
||||||
|
delay?: number;
|
||||||
|
iterations?: number;
|
||||||
|
direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';
|
||||||
|
fillMode?: 'none' | 'forwards' | 'backwards' | 'both';
|
||||||
|
}
|
||||||
|
interface AnimationUtils {
|
||||||
|
register(name: string, config: Partial<AnimationConfig>): void;
|
||||||
|
unregister(name: string): void;
|
||||||
|
get(name: string): AnimationConfig | null;
|
||||||
|
getAnimationNames(): string[];
|
||||||
|
getActiveCount(): number;
|
||||||
|
cancelAll(): void;
|
||||||
|
reset(): void;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
interface ThemeConfig {
|
||||||
|
bg: string;
|
||||||
|
text: string;
|
||||||
|
border: string;
|
||||||
|
shadow: string;
|
||||||
|
hoverShadow: string;
|
||||||
|
progressBg: string;
|
||||||
|
closeHoverBg: string;
|
||||||
|
backdropFilter?: string;
|
||||||
|
}
|
||||||
|
interface ThemePreview {
|
||||||
|
name: string;
|
||||||
|
resolved: string;
|
||||||
|
colors: {
|
||||||
|
background: string;
|
||||||
|
text: string;
|
||||||
|
border: string;
|
||||||
|
};
|
||||||
|
isDark: boolean;
|
||||||
|
isLight: boolean;
|
||||||
|
isAuto: boolean;
|
||||||
|
}
|
||||||
|
interface ThemeUtils {
|
||||||
|
getSystemTheme(): 'light' | 'dark';
|
||||||
|
resolveTheme(theme: string): string;
|
||||||
|
getThemeConfig(theme: string): ThemeConfig;
|
||||||
|
applyTheme(theme: string): void;
|
||||||
|
getCurrentTheme(): string;
|
||||||
|
getResolvedTheme(): string;
|
||||||
|
switchTheme(theme: string): void;
|
||||||
|
toggleTheme(): void;
|
||||||
|
resetToAuto(): void;
|
||||||
|
initTheme(): void;
|
||||||
|
watchSystemTheme(): void;
|
||||||
|
unwatchSystemTheme(): void;
|
||||||
|
addThemeListener(listener: (theme: string, resolved: string) => void): () => void;
|
||||||
|
removeThemeListener(listener: (theme: string, resolved: string) => void): void;
|
||||||
|
clearThemeListeners(): void;
|
||||||
|
registerTheme(name: string, config: ThemeConfig): void;
|
||||||
|
unregisterTheme(name: string): void;
|
||||||
|
getAllThemes(): Record<string, ThemeConfig>;
|
||||||
|
getThemeNames(): string[];
|
||||||
|
hasTheme(name: string): boolean;
|
||||||
|
getThemePreview(theme: string): ThemePreview;
|
||||||
|
generateThemeCSS(theme: string): string;
|
||||||
|
applyThemeCSS(theme: string): void;
|
||||||
|
removeThemeCSS(): void;
|
||||||
|
saveTheme(theme: string): void;
|
||||||
|
loadTheme(): string;
|
||||||
|
}
|
||||||
|
interface LocaleInfo {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
direction: 'ltr' | 'rtl';
|
||||||
|
isSupported: boolean;
|
||||||
|
isCurrent: boolean;
|
||||||
|
isFallback: boolean;
|
||||||
|
}
|
||||||
|
interface I18nUtils {
|
||||||
|
t(key: string, params?: Record<string, string | number>): string;
|
||||||
|
plural(key: string, count: number, params?: Record<string, string | number>): string;
|
||||||
|
getCurrentLocale(): string;
|
||||||
|
setCurrentLocale(locale: string): void;
|
||||||
|
switchLocale(locale: string): void;
|
||||||
|
getFallbackLocale(): string;
|
||||||
|
setFallbackLocale(locale: string): void;
|
||||||
|
hasTranslation(key: string): boolean;
|
||||||
|
getTranslations(locale: string): Record<string, unknown>;
|
||||||
|
addTranslations(locale: string, translations: Record<string, unknown>): void;
|
||||||
|
removeTranslation(locale: string, key: string): void;
|
||||||
|
clearTranslations(locale: string): void;
|
||||||
|
getSupportedLocales(): string[];
|
||||||
|
isLocaleSupported(locale: string): boolean;
|
||||||
|
getLocaleName(locale: string): string;
|
||||||
|
getLocaleDirection(locale: string): 'ltr' | 'rtl';
|
||||||
|
getLocaleInfo(locale: string): LocaleInfo;
|
||||||
|
getAllLocaleInfo(): LocaleInfo[];
|
||||||
|
formatNumber(number: number, options?: Intl.NumberFormatOptions): string;
|
||||||
|
formatCurrency(amount: number, currency?: string, options?: Intl.NumberFormatOptions): string;
|
||||||
|
formatPercent(value: number, options?: Intl.NumberFormatOptions): string;
|
||||||
|
formatDate(date: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
|
||||||
|
formatTime(date: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
|
||||||
|
formatRelativeTime(date: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string;
|
||||||
|
formatList(list: string[], options?: Record<string, unknown>): string;
|
||||||
|
formatPlural(count: number, options?: Intl.PluralRulesOptions): string;
|
||||||
|
addLocaleListener(listener: (locale: string) => void): () => void;
|
||||||
|
removeLocaleListener(listener: (locale: string) => void): void;
|
||||||
|
clearLocaleListeners(): void;
|
||||||
|
initI18n(): void;
|
||||||
|
saveLocale(locale: string): void;
|
||||||
|
loadLocale(): string;
|
||||||
|
getDefaultLocale(): string;
|
||||||
|
}
|
||||||
|
interface Plugin {
|
||||||
|
name: string;
|
||||||
|
version?: string;
|
||||||
|
description?: string;
|
||||||
|
hooks?: Record<string, (...args: unknown[]) => void>;
|
||||||
|
middleware?: (...args: unknown[]) => unknown;
|
||||||
|
install?: (manager: PluginManager) => void | Record<string, unknown> | null;
|
||||||
|
uninstall?: (manager: PluginManager) => void;
|
||||||
|
init?: (manager: PluginManager) => void;
|
||||||
|
destroy?: (manager: PluginManager) => void;
|
||||||
|
installed?: boolean;
|
||||||
|
enabled?: boolean;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
interface PluginManager {
|
||||||
|
register(name: string, plugin: Plugin): PluginManager;
|
||||||
|
unregister(name: string): PluginManager;
|
||||||
|
get(name: string): Plugin | null;
|
||||||
|
has(name: string): boolean;
|
||||||
|
getAll(): Plugin[];
|
||||||
|
getNames(): string[];
|
||||||
|
enable(name: string): PluginManager;
|
||||||
|
disable(name: string): PluginManager;
|
||||||
|
isEnabled(name: string): boolean;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
interface PluginUtils {
|
||||||
|
createManager(): PluginManager;
|
||||||
|
register(name: string, plugin: Plugin): PluginManager;
|
||||||
|
unregister(name: string): PluginManager;
|
||||||
|
get(name: string): Plugin | null;
|
||||||
|
has(name: string): boolean;
|
||||||
|
getAll(): Plugin[];
|
||||||
|
getNames(): string[];
|
||||||
|
enable(name: string): PluginManager;
|
||||||
|
disable(name: string): PluginManager;
|
||||||
|
isEnabled(name: string): boolean;
|
||||||
|
getPreset(name: string): Plugin | null;
|
||||||
|
getAllPresets(): Record<string, Plugin>;
|
||||||
|
createPlugin(config: Partial<Plugin>): Plugin;
|
||||||
|
validatePlugin(plugin: Partial<Plugin>): {
|
||||||
|
valid: boolean;
|
||||||
|
errors: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
interface MeToast {
|
||||||
|
version: string;
|
||||||
|
_toasts: Map<string, ToastInstance>;
|
||||||
|
_config: ToastConfig;
|
||||||
|
_destroyed?: boolean;
|
||||||
|
_emit(opts: ToastOptions): ToastInstance;
|
||||||
|
_remove(id: string): void;
|
||||||
|
_resolve(loadingToast: ToastInstance, type: string, message: string, opts: ToastOptions): ToastInstance | null;
|
||||||
|
_groupCount(name: string): number;
|
||||||
|
configure(opts: Partial<ToastConfig>): MeToast;
|
||||||
|
init(options?: InitOptions): MeToast;
|
||||||
|
destroy(): void;
|
||||||
|
getStatus(): StatusInfo;
|
||||||
|
getConfig(): ToastConfig;
|
||||||
|
updateConfig(config: Partial<ToastConfig>): MeToast;
|
||||||
|
resetConfig(): MeToast;
|
||||||
|
show(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
success(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
error(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
warning(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
info(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
loading(messageOrOpts: string | ToastOptions, opts?: ToastOptions): LoadingControl;
|
||||||
|
promise<T>(promise: Promise<T>, opts?: PromiseOptions): Promise<T>;
|
||||||
|
confirm(message: string, opts?: ConfirmOptions): Promise<boolean>;
|
||||||
|
prompt(message: string, opts?: PromptOptions): Promise<string | null>;
|
||||||
|
progress(messageOrOpts: string | ToastOptions, opts?: ProgressOptions): ProgressControl;
|
||||||
|
countdown(message: string, seconds?: number, opts?: CountdownOptions): CountdownControl;
|
||||||
|
action(messageOrOpts: string | ToastOptions, actions?: ActionButton[], opts?: ToastOptions): ActionControl;
|
||||||
|
queue(messages: Array<string | ToastOptions>, opts?: QueueOptions): QueueControl;
|
||||||
|
stack(messages: Array<string | ToastOptions>, opts?: StackOptions): void;
|
||||||
|
group(name: string): GroupAPI;
|
||||||
|
dismissGroup(name: string): void;
|
||||||
|
find(id: string): ToastInstance | undefined;
|
||||||
|
dismiss(id?: string): void;
|
||||||
|
removeToast(id: string): void;
|
||||||
|
clear(position?: ToastPosition): void;
|
||||||
|
getToasts(): ToastInstance[];
|
||||||
|
count(): number;
|
||||||
|
hasToasts(): boolean;
|
||||||
|
getToast(id: string): ToastInstance | null;
|
||||||
|
closeAll(): void;
|
||||||
|
clearAll(): void;
|
||||||
|
pauseAll(): void;
|
||||||
|
resumeAll(): void;
|
||||||
|
updateAll(partial: Partial<ToastOptions>): void;
|
||||||
|
findToasts(predicate: (toast: ToastInstance) => boolean): ToastInstance[];
|
||||||
|
findByType(type: ToastType): ToastInstance[];
|
||||||
|
findByPosition(position: ToastPosition): ToastInstance[];
|
||||||
|
getAll(): Map<string, ToastInstance>;
|
||||||
|
use(plugin: string | Plugin, options?: Record<string, unknown>): MeToast;
|
||||||
|
animations: AnimationUtils;
|
||||||
|
themes: ThemeUtils;
|
||||||
|
i18n: I18nUtils;
|
||||||
|
plugins: PluginUtils;
|
||||||
|
presetPlugins: Record<string, Plugin>;
|
||||||
|
}
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
MeToast: MeToast;
|
||||||
|
Met: MeToast;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增强的 MeToast 对象 — 在 meToast 基础上注入子模块
|
||||||
|
*/
|
||||||
|
declare const enhancedMeToast: {
|
||||||
|
version: string;
|
||||||
|
animations: AnimationUtils;
|
||||||
|
themes: ThemeUtils;
|
||||||
|
i18n: I18nUtils;
|
||||||
|
plugins: PluginUtils;
|
||||||
|
presetPlugins: Record<string, Plugin>;
|
||||||
|
_toasts: Map<string, ToastInstance>;
|
||||||
|
_config: ToastConfig;
|
||||||
|
_destroyed?: boolean;
|
||||||
|
_emit(opts: ToastOptions): ToastInstance;
|
||||||
|
_remove(id: string): void;
|
||||||
|
_resolve(loadingToast: ToastInstance, type: string, message: string, opts: ToastOptions): ToastInstance | null;
|
||||||
|
_groupCount(name: string): number;
|
||||||
|
configure(opts: Partial<ToastConfig>): MeToast;
|
||||||
|
init(options?: InitOptions): MeToast;
|
||||||
|
destroy(): void;
|
||||||
|
getStatus(): StatusInfo;
|
||||||
|
getConfig(): ToastConfig;
|
||||||
|
updateConfig(config: Partial<ToastConfig>): MeToast;
|
||||||
|
resetConfig(): MeToast;
|
||||||
|
show(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
success(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
error(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
warning(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
info(messageOrOpts: string | ToastOptions, opts?: ToastOptions): ToastInstance;
|
||||||
|
loading(messageOrOpts: string | ToastOptions, opts?: ToastOptions): LoadingControl;
|
||||||
|
promise<T>(promise: Promise<T>, opts?: PromiseOptions): Promise<T>;
|
||||||
|
confirm(message: string, opts?: ConfirmOptions): Promise<boolean>;
|
||||||
|
prompt(message: string, opts?: PromptOptions): Promise<string | null>;
|
||||||
|
progress(messageOrOpts: string | ToastOptions, opts?: ProgressOptions): ProgressControl;
|
||||||
|
countdown(message: string, seconds?: number, opts?: CountdownOptions): CountdownControl;
|
||||||
|
action(messageOrOpts: string | ToastOptions, actions?: ActionButton[], opts?: ToastOptions): ActionControl;
|
||||||
|
queue(messages: Array<string | ToastOptions>, opts?: QueueOptions): QueueControl;
|
||||||
|
stack(messages: Array<string | ToastOptions>, opts?: StackOptions): void;
|
||||||
|
group(name: string): GroupAPI;
|
||||||
|
dismissGroup(name: string): void;
|
||||||
|
find(id: string): ToastInstance | undefined;
|
||||||
|
dismiss(id?: string): void;
|
||||||
|
removeToast(id: string): void;
|
||||||
|
clear(position?: ToastPosition): void;
|
||||||
|
getToasts(): ToastInstance[];
|
||||||
|
count(): number;
|
||||||
|
hasToasts(): boolean;
|
||||||
|
getToast(id: string): ToastInstance | null;
|
||||||
|
closeAll(): void;
|
||||||
|
clearAll(): void;
|
||||||
|
pauseAll(): void;
|
||||||
|
resumeAll(): void;
|
||||||
|
updateAll(partial: Partial<ToastOptions>): void;
|
||||||
|
findToasts(predicate: (toast: ToastInstance) => boolean): ToastInstance[];
|
||||||
|
findByType(type: ToastType): ToastInstance[];
|
||||||
|
findByPosition(position: ToastPosition): ToastInstance[];
|
||||||
|
getAll(): Map<string, ToastInstance>;
|
||||||
|
use(plugin: string | Plugin, options?: Record<string, unknown>): MeToast;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MetonaToast React — React 适配器
|
||||||
|
* @module react
|
||||||
|
* @version 0.4.0
|
||||||
|
* @description useToast hook + 声明式 <Toast /> 组件。主包保持零依赖,React 为 optional peerDependency
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** 支持声明式渲染的 Toast 类型 */
|
||||||
|
type ReactToastType = 'success' | 'error' | 'warning' | 'info' | 'show' | (string & {});
|
||||||
|
interface ToastProps extends Omit<ToastOptions, 'message' | 'type'> {
|
||||||
|
message: string;
|
||||||
|
type?: ReactToastType;
|
||||||
|
/** 组件卸载时自动移除该 Toast(默认 true)。设为 false 则 Toast 独立于组件生命周期 */
|
||||||
|
autoClose?: boolean;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 创建绑定清理集合的 API — 组件卸载时自动移除本组件创建的 Toast。
|
||||||
|
* 纯函数,便于独立测试与扩展。
|
||||||
|
*/
|
||||||
|
declare const createBoundApi: (cleanup: Set<string>) => Record<string, unknown>;
|
||||||
|
/**
|
||||||
|
* useToast — 返回 MeToast API,但本组件创建的 Toast 会在组件卸载时自动移除。
|
||||||
|
* 适合"通知随组件生命周期"的场景(如表单提交、异步任务页)。
|
||||||
|
*/
|
||||||
|
declare const useToast: () => Record<string, unknown>;
|
||||||
|
/**
|
||||||
|
* Toast — 声明式 Toast 组件。props 变化时更新内容(重新创建实例),
|
||||||
|
* 组件卸载时自动移除(autoClose 默认 true)。
|
||||||
|
*/
|
||||||
|
declare const Toast: (props: ToastProps) => null;
|
||||||
|
|
||||||
|
export { enhancedMeToast as MeToast, ReactToastType, Toast, ToastProps, createBoundApi, useToast };
|
||||||
Vendored
+4577
File diff suppressed because it is too large
Load Diff
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
testEnvironment: 'jsdom',
|
testEnvironment: 'jsdom',
|
||||||
|
setupFiles: ['<rootDir>/tests/setup.ts'],
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.ts$': 'babel-jest',
|
'^.+\\.ts$': 'babel-jest',
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+99
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@metona-team/metona-toast",
|
"name": "@metona-team/metona-toast",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@metona-team/metona-toast",
|
"name": "@metona-team/metona-toast",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.22.0",
|
"@babel/core": "^7.22.0",
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
"@rollup/plugin-terser": "^0.4.0",
|
"@rollup/plugin-terser": "^0.4.0",
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.0",
|
||||||
|
"@types/react": "^18.3.31",
|
||||||
|
"@types/react-dom": "^18.3.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"babel-jest": "^29.5.0",
|
"babel-jest": "^29.5.0",
|
||||||
@@ -25,6 +27,8 @@
|
|||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-environment-jsdom": "^29.5.0",
|
"jest-environment-jsdom": "^29.5.0",
|
||||||
"prettier": "^2.8.0",
|
"prettier": "^2.8.0",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
"rollup": "^3.20.0",
|
"rollup": "^3.20.0",
|
||||||
"rollup-plugin-dts": "^5.3.0",
|
"rollup-plugin-dts": "^5.3.0",
|
||||||
"rollup-plugin-livereload": "^2.0.5",
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
@@ -34,6 +38,14 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=17.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -2722,6 +2734,34 @@
|
|||||||
"undici-types": ">=7.24.0 <7.24.7"
|
"undici-types": ">=7.24.0 <7.24.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/prop-types": {
|
||||||
|
"version": "15.7.15",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
|
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/react": {
|
||||||
|
"version": "18.3.31",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.31.tgz",
|
||||||
|
"integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/prop-types": "*",
|
||||||
|
"csstype": "^3.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "18.3.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||||
|
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/resolve": {
|
"node_modules/@types/resolve": {
|
||||||
"version": "1.20.2",
|
"version": "1.20.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/resolve/-/resolve-1.20.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/resolve/-/resolve-1.20.2.tgz",
|
||||||
@@ -3810,6 +3850,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/data-urls": {
|
"node_modules/data-urls": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-3.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-3.0.2.tgz",
|
||||||
@@ -6103,6 +6150,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"loose-envify": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@@ -6713,6 +6773,33 @@
|
|||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "18.3.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz",
|
||||||
|
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "18.3.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
|
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"scheduler": "^0.23.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz",
|
||||||
@@ -7055,6 +7142,16 @@
|
|||||||
"node": ">=v12.22.7"
|
"node": ">=v12.22.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.23.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz",
|
||||||
|
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
|
||||||
|
|||||||
+18
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@metona-team/metona-toast",
|
"name": "@metona-team/metona-toast",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"description": "轻量、零依赖、精致美观的Toast通知库。TypeScript源码,开箱即用。",
|
"description": "轻量、零依赖、精致美观的Toast通知库。TypeScript源码,开箱即用。",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/metona-toast.cjs.js",
|
"main": "dist/metona-toast.cjs.js",
|
||||||
@@ -13,6 +13,19 @@
|
|||||||
"import": "./dist/metona-toast.esm.js",
|
"import": "./dist/metona-toast.esm.js",
|
||||||
"require": "./dist/metona-toast.cjs.js",
|
"require": "./dist/metona-toast.cjs.js",
|
||||||
"types": "./dist/metona-toast.d.ts"
|
"types": "./dist/metona-toast.d.ts"
|
||||||
|
},
|
||||||
|
"./react": {
|
||||||
|
"import": "./dist/react.js",
|
||||||
|
"require": "./dist/react.cjs.js",
|
||||||
|
"types": "./dist/react.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=17.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@@ -64,6 +77,8 @@
|
|||||||
"@rollup/plugin-terser": "^0.4.0",
|
"@rollup/plugin-terser": "^0.4.0",
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.0",
|
||||||
|
"@types/react": "^18.3.31",
|
||||||
|
"@types/react-dom": "^18.3.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"babel-jest": "^29.5.0",
|
"babel-jest": "^29.5.0",
|
||||||
@@ -71,6 +86,8 @@
|
|||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-environment-jsdom": "^29.5.0",
|
"jest-environment-jsdom": "^29.5.0",
|
||||||
"prettier": "^2.8.0",
|
"prettier": "^2.8.0",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
"rollup": "^3.20.0",
|
"rollup": "^3.20.0",
|
||||||
"rollup-plugin-dts": "^5.3.0",
|
"rollup-plugin-dts": "^5.3.0",
|
||||||
"rollup-plugin-livereload": "^2.0.5",
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
|
|||||||
@@ -103,5 +103,34 @@ export default [
|
|||||||
},
|
},
|
||||||
plugins: [dts()],
|
plugins: [dts()],
|
||||||
},
|
},
|
||||||
|
// React 适配器(ESM + CJS),react 为 optional peer 外部依赖
|
||||||
|
{
|
||||||
|
input: 'src/react.ts',
|
||||||
|
external: ['react'],
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
file: 'dist/react.js',
|
||||||
|
format: 'es',
|
||||||
|
exports: 'named',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'dist/react.cjs.js',
|
||||||
|
format: 'cjs',
|
||||||
|
exports: 'named',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
plugins: basePlugins,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'src/react.ts',
|
||||||
|
external: ['react'],
|
||||||
|
output: {
|
||||||
|
file: 'dist/react.d.ts',
|
||||||
|
format: 'es',
|
||||||
|
},
|
||||||
|
plugins: [dts()],
|
||||||
|
},
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|||||||
+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.3.0</span></div>
|
<div class="label">🛡️ onError 错误捕获 <span style="color:#34d399;font-size:10px;">v0.4.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.3.0 · MIT · <a href="index.html">首页</a> · <a href="docs.html">文档</a> · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a>
|
MetonaToast v0.4.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>
|
||||||
|
|||||||
+31
-4
@@ -79,6 +79,7 @@
|
|||||||
<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>
|
||||||
|
<a href="#react">React 适配器</a>
|
||||||
<div class="section">配置参考</div>
|
<div class="section">配置参考</div>
|
||||||
<a href="#config">全部配置项</a>
|
<a href="#config">全部配置项</a>
|
||||||
<a href="#callbacks">回调函数</a>
|
<a href="#callbacks">回调函数</a>
|
||||||
@@ -89,7 +90,7 @@
|
|||||||
<main class="main">
|
<main class="main">
|
||||||
|
|
||||||
<h1>API 文档</h1>
|
<h1>API 文档</h1>
|
||||||
<p>MetonaToast v0.3.0 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 <a href="#config">配置项</a> 作为可选第二参数。</p>
|
<p>MetonaToast v0.4.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>
|
||||||
@@ -422,13 +423,38 @@ MeToast.<span class="fn">removeToast</span>(t.id); <span class="cm">// 或按
|
|||||||
</table>
|
</table>
|
||||||
<pre>MeToast.<span class="fn">use</span>(<span class="s">'keyboard'</span>); <span class="cm">// ESC 关闭所有 Toast</span>
|
<pre>MeToast.<span class="fn">use</span>(<span class="s">'keyboard'</span>); <span class="cm">// ESC 关闭所有 Toast</span>
|
||||||
MeToast.<span class="fn">use</span>(<span class="s">'persistence'</span>); <span class="cm">// 配置自动保存到 localStorage</span>
|
MeToast.<span class="fn">use</span>(<span class="s">'persistence'</span>); <span class="cm">// 配置自动保存到 localStorage</span>
|
||||||
MeToast.<span class="fn">use</span>(<span class="s">'accessibility'</span>); <span class="cm">// 屏幕阅读器实时朗读 toast 内容</span></pre>
|
MeToast.<span class="fn">use</span>(<span class="s">'accessibility'</span>); <span class="cm">// 屏幕阅读器实时朗读 toast 内容</span>
|
||||||
<p class="hint">内置插件详情:keyboard 监听 keydown Escape 键;persistence 将配置写入 localStorage 并在页面加载时恢复;accessibility 在 afterShow/afterUpdate 钩子中通过 aria-live 区域朗读 toast 内容。</p>
|
MeToast.<span class="fn">use</span>(<span class="s">'dedupe'</span>); <span class="cm">// 相同 type+message 自动去重</span></pre>
|
||||||
|
<p class="hint">内置插件详情:keyboard 监听 keydown Escape 键;persistence 将配置写入 localStorage 并在页面加载时恢复;accessibility 在 afterShow/afterUpdate 钩子中通过 aria-live 区域朗读 toast 内容;dedupe 在 beforeShow 钩子中检测相同 type+message 的已有 toast,更新它并阻止重复弹出。</p>
|
||||||
|
|
||||||
<h2 id="destroy">destroy()</h2>
|
<h2 id="destroy">destroy()</h2>
|
||||||
<p>完全销毁。关闭所有 Toast、移除所有 DOM 容器和注入的样式标签、清空内存缓存。</p>
|
<p>完全销毁。关闭所有 Toast、移除所有 DOM 容器和注入的样式标签、清空内存缓存。</p>
|
||||||
<pre>MeToast.<span class="fn">destroy</span>();</pre>
|
<pre>MeToast.<span class="fn">destroy</span>();</pre>
|
||||||
|
|
||||||
|
<!-- ===== React 适配器 ===== -->
|
||||||
|
<h2 id="react">React 适配器</h2>
|
||||||
|
<p>主包保持零依赖。React 适配器通过子路径 <code>@metona-team/metona-toast/react</code> 导入,<code>react</code> 为 optional peerDependency,未使用 React 的项目不受影响。</p>
|
||||||
|
<pre><span class="cm">// 安装</span>
|
||||||
|
npm install @metona-team/metona-toast react
|
||||||
|
|
||||||
|
<span class="cm">// useToast — 组件卸载时自动移除本组件创建的 Toast</span>
|
||||||
|
<span class="kw">import</span> { <span class="fn">useToast</span>, <span class="fn">Toast</span> } <span class="kw">from</span> <span class="s">'@metona-team/metona-toast/react'</span>;
|
||||||
|
|
||||||
|
<span class="kw">function</span> <span class="fn">SubmitButton</span>() {
|
||||||
|
<span class="kw">const</span> toast = <span class="fn">useToast</span>();
|
||||||
|
<span class="kw">const</span> submit = <span class="kw">async</span> () => {
|
||||||
|
<span class="kw">const</span> loading = toast.<span class="fn">loading</span>(<span class="s">'正在提交...'</span>);
|
||||||
|
<span class="kw">try</span> { <span class="kw">await</span> <span class="fn">api</span>(); loading.<span class="fn">success</span>(<span class="s">'提交成功!'</span>); }
|
||||||
|
<span class="kw">catch</span> (e) { loading.<span class="fn">error</span>(<span class="s">'提交失败'</span>); }
|
||||||
|
};
|
||||||
|
<span class="kw">return</span> <button onClick={submit}>提交</button>;
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="cm">// Toast 组件 — props 变化时更新,卸载时自动移除(autoClose 默认 true)</span>
|
||||||
|
<span class="kw">function</span> <span class="fn">SaveIndicator</span>({ saving }) {
|
||||||
|
<span class="kw">return</span> saving ? <<span class="fn">Toast</span> type=<span class="s">"info"</span> message=<span class="s">"正在保存..."</span> /> : null;
|
||||||
|
}</pre>
|
||||||
|
|
||||||
<!-- ===== 配置参考 ===== -->
|
<!-- ===== 配置参考 ===== -->
|
||||||
<h2 id="config">全部配置项</h2>
|
<h2 id="config">全部配置项</h2>
|
||||||
<p>以下配置可用于 <code>configure()</code>、<code>init()</code> 或单个 Toast 方法的 opts 参数。</p>
|
<p>以下配置可用于 <code>configure()</code>、<code>init()</code> 或单个 Toast 方法的 opts 参数。</p>
|
||||||
@@ -441,7 +467,8 @@ MeToast.<span class="fn">use</span>(<span class="s">'accessibility'</span>); <sp
|
|||||||
<tr><td>offset</td><td>number</td><td>24</td><td>容器到屏幕边缘的距离(px)</td></tr>
|
<tr><td>offset</td><td>number</td><td>24</td><td>容器到屏幕边缘的距离(px)</td></tr>
|
||||||
<tr><td>pauseOnHover</td><td>boolean</td><td>true</td><td>鼠标悬停时暂停 duration 倒计时和进度条</td></tr>
|
<tr><td>pauseOnHover</td><td>boolean</td><td>true</td><td>鼠标悬停时暂停 duration 倒计时和进度条</td></tr>
|
||||||
<tr><td>closeOnClick</td><td>boolean</td><td>true</td><td>点击 Toast 任意位置关闭。关闭按钮始终触发关闭</td></tr>
|
<tr><td>closeOnClick</td><td>boolean</td><td>true</td><td>点击 Toast 任意位置关闭。关闭按钮始终触发关闭</td></tr>
|
||||||
<tr><td>draggable</td><td>boolean</td><td>true</td><td>允许拖拽关闭。拖拽超过 120px 触发关闭</td></tr>
|
<tr><td>draggable</td><td>boolean</td><td>true</td><td>允许拖拽关闭。拖拽超过 dragThreshold 触发关闭</td></tr>
|
||||||
|
<tr><td>dragThreshold</td><td>number</td><td>120</td><td>拖拽关闭阈值(px),位移超过该值松手即关闭</td></tr>
|
||||||
<tr><td>showProgress</td><td>boolean</td><td>true</td><td>显示 duration 倒计时进度条</td></tr>
|
<tr><td>showProgress</td><td>boolean</td><td>true</td><td>显示 duration 倒计时进度条</td></tr>
|
||||||
<tr><td>progressDirection</td><td>string</td><td>'horizontal'</td><td>进度条方向:horizontal(底部水平) / vertical(右侧垂直)</td></tr>
|
<tr><td>progressDirection</td><td>string</td><td>'horizontal'</td><td>进度条方向:horizontal(底部水平) / vertical(右侧垂直)</td></tr>
|
||||||
<tr><td>icon</td><td>boolean</td><td>true</td><td>显示类型对应图标(success→对勾等)</td></tr>
|
<tr><td>icon</td><td>boolean</td><td>true</td><td>显示类型对应图标(success→对勾等)</td></tr>
|
||||||
|
|||||||
+1
-1
@@ -247,7 +247,7 @@ orderGroup.<span class="fn">dismiss</span>(); <span class="cm">// 一键关闭
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>MetonaToast v0.3.0 · MIT License · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a></p>
|
<p>MetonaToast v0.4.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>
|
||||||
|
|||||||
+16
-6
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast API — meToast 核心 API 对象
|
* MetonaToast API — meToast 核心 API 对象
|
||||||
* @module api
|
* @module api
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Toast, _containerCache } from './toast.js';
|
import { Toast, _containerCache } from './toast.js';
|
||||||
@@ -78,7 +78,10 @@ const meToast: MeToast = {
|
|||||||
|
|
||||||
const t = new Toast(merged);
|
const t = new Toast(merged);
|
||||||
t.create();
|
t.create();
|
||||||
|
// beforeShow/onBeforeShow 拦截(返回 false)时 el 为 null,不注册幽灵实例
|
||||||
|
if (t.el !== null) {
|
||||||
this._toasts.set(t.id, t);
|
this._toasts.set(t.id, t);
|
||||||
|
}
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
},
|
},
|
||||||
@@ -159,15 +162,22 @@ const meToast: MeToast = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* loading 链式转换 — 原地 update 同一实例(id 稳定,不重建 DOM)
|
||||||
|
* duration 从 0 恢复为默认值,使转换后的 toast 自动关闭
|
||||||
|
*/
|
||||||
_resolve(loadingToast: ToastInstance, type: string, message: string, opts: ToastOptions): ToastInstance | null {
|
_resolve(loadingToast: ToastInstance, type: string, message: string, opts: ToastOptions): ToastInstance | null {
|
||||||
const id = loadingToast.id;
|
const old = this._toasts.get(loadingToast.id);
|
||||||
const old = this._toasts.get(id);
|
|
||||||
if (!old) return null;
|
if (!old) return null;
|
||||||
|
|
||||||
const position = old.config.position;
|
old.update({
|
||||||
old.close();
|
...opts,
|
||||||
|
type,
|
||||||
|
message,
|
||||||
|
duration: opts.duration ?? (old.config.duration || this._config.duration),
|
||||||
|
} as ToastOptions);
|
||||||
|
|
||||||
return this._emit({ ...opts, type, message, position } as ToastOptions);
|
return old;
|
||||||
},
|
},
|
||||||
|
|
||||||
confirm(message: string, opts: ConfirmOptions = {}): Promise<boolean> {
|
confirm(message: string, opts: ConfirmOptions = {}): Promise<boolean> {
|
||||||
|
|||||||
+3
-2
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Constants — 常量定义
|
* MetonaToast Constants — 常量定义
|
||||||
* @module constants
|
* @module constants
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ICONS } from './icons.js';
|
import { ICONS } from './icons.js';
|
||||||
@@ -12,7 +12,7 @@ export { ICONS, LOCALES };
|
|||||||
/**
|
/**
|
||||||
* 版本号 — 唯一来源,发布时只需修改此处
|
* 版本号 — 唯一来源,发布时只需修改此处
|
||||||
*/
|
*/
|
||||||
export const VERSION = '0.3.0';
|
export const VERSION = '0.4.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认配置
|
* 默认配置
|
||||||
@@ -26,6 +26,7 @@ export const DEFAULTS = Object.freeze({
|
|||||||
pauseOnHover: true,
|
pauseOnHover: true,
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
|
dragThreshold: 120,
|
||||||
showProgress: true,
|
showProgress: true,
|
||||||
progressDirection: 'horizontal' as const,
|
progressDirection: 'horizontal' as const,
|
||||||
icon: true,
|
icon: true,
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast i18n — 国际化管理
|
* MetonaToast i18n — 国际化管理
|
||||||
* @module i18n
|
* @module i18n
|
||||||
* @version 0.3.0
|
* @version 0.4.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.3.0
|
* @version 0.4.0
|
||||||
* @description 107 个内置 SVG 图标
|
* @description 107 个内置 SVG 图标
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast — 轻量级Toast通知库
|
* MetonaToast — 轻量级Toast通知库
|
||||||
* @module metona-toast
|
* @module metona-toast
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
* @author thzxx
|
* @author thzxx
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Locales — 国际化翻译数据
|
* MetonaToast Locales — 国际化翻译数据
|
||||||
* @module locales
|
* @module locales
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
* @description 内置 zh-CN / en-US 完整翻译
|
* @description 内置 zh-CN / en-US 完整翻译
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+33
-2
@@ -1,11 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Plugins — 插件系统
|
* MetonaToast Plugins — 插件系统
|
||||||
* @module plugins
|
* @module plugins
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { t } from './i18n.js';
|
import { t } from './i18n.js';
|
||||||
import type { Plugin, PluginManager as IPluginManager, PluginUtils } from './types.js';
|
import { Toast } from './toast.js';
|
||||||
|
import type { Plugin, PluginManager as IPluginManager, PluginUtils, ToastInstance } from './types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件管理器
|
* 插件管理器
|
||||||
@@ -176,6 +177,36 @@ const presetPlugins: Record<string, Plugin> = {
|
|||||||
setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 3000);
|
setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 3000);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 去重插件 — 相同 type + message 的 toast 自动合并(更新已有的一条)
|
||||||
|
*/
|
||||||
|
dedupe: {
|
||||||
|
name: 'dedupe',
|
||||||
|
version: '1.0.0',
|
||||||
|
description: '相同类型和消息的 Toast 自动去重,更新已有实例而非重复弹出',
|
||||||
|
_off: null as (() => void) | null,
|
||||||
|
|
||||||
|
install(this: Plugin) {
|
||||||
|
const off = Toast.on('beforeShow', (toast: ToastInstance) => {
|
||||||
|
const existing = Array.from(Toast._registry.values()).find(
|
||||||
|
t => !t.closing && t.type === toast.type && t.message === toast.message
|
||||||
|
);
|
||||||
|
if (existing) {
|
||||||
|
existing.update({ title: toast.title || existing.title });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
(this as Record<string, unknown>)._off = off;
|
||||||
|
},
|
||||||
|
|
||||||
|
uninstall(this: Plugin) {
|
||||||
|
const off = (this as Record<string, unknown>)._off as (() => void) | null;
|
||||||
|
if (off) off();
|
||||||
|
(this as Record<string, unknown>)._off = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* MetonaToast React — React 适配器
|
||||||
|
* @module react
|
||||||
|
* @version 0.4.0
|
||||||
|
* @description useToast hook + 声明式 <Toast /> 组件。主包保持零依赖,React 为 optional peerDependency
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import MeToast from './index.js';
|
||||||
|
import type { ToastInstance, ToastOptions } from './types.js';
|
||||||
|
|
||||||
|
/** 支持声明式渲染的 Toast 类型 */
|
||||||
|
export type ReactToastType = 'success' | 'error' | 'warning' | 'info' | 'show' | (string & {});
|
||||||
|
|
||||||
|
export interface ToastProps extends Omit<ToastOptions, 'message' | 'type'> {
|
||||||
|
message: string;
|
||||||
|
type?: ReactToastType;
|
||||||
|
/** 组件卸载时自动移除该 Toast(默认 true)。设为 false 则 Toast 独立于组件生命周期 */
|
||||||
|
autoClose?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SHOW_METHODS = ['success', 'error', 'warning', 'info', 'show'] as const;
|
||||||
|
type ShowMethod = (typeof SHOW_METHODS)[number];
|
||||||
|
|
||||||
|
/** 有 id 的可追踪对象(ToastInstance / LoadingControl) */
|
||||||
|
interface Tracked {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const showToast = (method: ShowMethod, message: string, opts: Omit<ToastProps, 'message' | 'type' | 'autoClose'>): ToastInstance => {
|
||||||
|
switch (method) {
|
||||||
|
case 'success': return MeToast.success(message, opts);
|
||||||
|
case 'error': return MeToast.error(message, opts);
|
||||||
|
case 'warning': return MeToast.warning(message, opts);
|
||||||
|
case 'info': return MeToast.info(message, opts);
|
||||||
|
default: return MeToast.show(message, opts);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建绑定清理集合的 API — 组件卸载时自动移除本组件创建的 Toast。
|
||||||
|
* 纯函数,便于独立测试与扩展。
|
||||||
|
*/
|
||||||
|
export const createBoundApi = (cleanup: Set<string>): Record<string, unknown> => {
|
||||||
|
const track = <T extends Tracked | null>(fn: () => T): T => {
|
||||||
|
const result = fn();
|
||||||
|
if (result && result.id) cleanup.add(result.id);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bound: Record<string, unknown> = {
|
||||||
|
...MeToast,
|
||||||
|
|
||||||
|
success: (messageOrOpts: string | ToastOptions, opts?: ToastOptions) =>
|
||||||
|
track(() => MeToast.success(messageOrOpts, opts)),
|
||||||
|
error: (messageOrOpts: string | ToastOptions, opts?: ToastOptions) =>
|
||||||
|
track(() => MeToast.error(messageOrOpts, opts)),
|
||||||
|
warning: (messageOrOpts: string | ToastOptions, opts?: ToastOptions) =>
|
||||||
|
track(() => MeToast.warning(messageOrOpts, opts)),
|
||||||
|
info: (messageOrOpts: string | ToastOptions, opts?: ToastOptions) =>
|
||||||
|
track(() => MeToast.info(messageOrOpts, opts)),
|
||||||
|
show: (messageOrOpts: string | ToastOptions, opts?: ToastOptions) =>
|
||||||
|
track(() => MeToast.show(messageOrOpts, opts)),
|
||||||
|
loading: (messageOrOpts: string | ToastOptions, opts?: ToastOptions) =>
|
||||||
|
track(() => MeToast.loading(messageOrOpts, opts)),
|
||||||
|
|
||||||
|
// 通过 id 关闭时同步清理集合,避免卸载时重复移除
|
||||||
|
dismiss: (id?: string) => {
|
||||||
|
if (id) cleanup.delete(id);
|
||||||
|
return MeToast.dismiss(id);
|
||||||
|
},
|
||||||
|
removeToast: (id: string) => {
|
||||||
|
cleanup.delete(id);
|
||||||
|
return MeToast.removeToast(id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return bound;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useToast — 返回 MeToast API,但本组件创建的 Toast 会在组件卸载时自动移除。
|
||||||
|
* 适合"通知随组件生命周期"的场景(如表单提交、异步任务页)。
|
||||||
|
*/
|
||||||
|
export const useToast = (): Record<string, unknown> => {
|
||||||
|
const cleanupRef = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const set = cleanupRef.current;
|
||||||
|
return () => {
|
||||||
|
set.forEach(id => MeToast.removeToast(id));
|
||||||
|
set.clear();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return useMemo(() => createBoundApi(cleanupRef.current), []);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toast — 声明式 Toast 组件。props 变化时更新内容(重新创建实例),
|
||||||
|
* 组件卸载时自动移除(autoClose 默认 true)。
|
||||||
|
*/
|
||||||
|
export const Toast = (props: ToastProps): null => {
|
||||||
|
const { message, type, autoClose = true, ...rest } = props;
|
||||||
|
const toastRef = useRef<ToastInstance | null>(null);
|
||||||
|
const restKey = JSON.stringify(rest);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const method: ShowMethod = SHOW_METHODS.includes(type as ShowMethod) ? (type as ShowMethod) : 'show';
|
||||||
|
toastRef.current = showToast(method, message, rest);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (autoClose) toastRef.current?.remove();
|
||||||
|
toastRef.current = null;
|
||||||
|
};
|
||||||
|
// autoClose 不参与重建依赖:它只影响卸载时的清理行为
|
||||||
|
}, [message, type, restKey]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { MeToast };
|
||||||
+36
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Styles — 样式管理
|
* MetonaToast Styles — 样式管理
|
||||||
* @module styles
|
* @module styles
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ThemeConfig } from './types.js';
|
import type { ThemeConfig } from './types.js';
|
||||||
@@ -363,6 +363,41 @@ const generateCSS = (): string => {
|
|||||||
transition: transform 0.1s linear;
|
transition: transform 0.1s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RTL 镜像:进度条从右向左收缩、side 条移到右侧、类型色条翻转到右边 */
|
||||||
|
[dir="rtl"] .met-bar {
|
||||||
|
transform-origin: right;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-side {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-progress-v {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-success {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #10b981;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-error {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #ef4444;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-warning {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #f59e0b;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-info {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #3b82f6;
|
||||||
|
}
|
||||||
|
[dir="rtl"] .met-toast.met-loading {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: 4px solid #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
|
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||||
.met-container::-webkit-scrollbar-track { background: transparent; }
|
.met-container::-webkit-scrollbar-track { background: transparent; }
|
||||||
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
|
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Templates — HTML 模板辅助函数
|
* MetonaToast Templates — HTML 模板辅助函数
|
||||||
* @module templates
|
* @module templates
|
||||||
* @version 0.3.0
|
* @version 0.4.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.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { THEMES } from './constants.js';
|
import { THEMES } from './constants.js';
|
||||||
|
|||||||
+17
-2
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Toast — Toast 类
|
* MetonaToast Toast — Toast 类
|
||||||
* @module toast
|
* @module toast
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { generateId, escapeHTML } from './utils.js';
|
import { generateId, escapeHTML } from './utils.js';
|
||||||
@@ -204,6 +204,8 @@ export class Toast implements ToastInstance {
|
|||||||
el.className = `met-container ${position}`;
|
el.className = `met-container ${position}`;
|
||||||
el.setAttribute('aria-label', 'Notifications');
|
el.setAttribute('aria-label', 'Notifications');
|
||||||
el.setAttribute('role', 'region');
|
el.setAttribute('role', 'region');
|
||||||
|
// RTL 语言设置容器文字方向,使内容/关闭按钮/进度条正确镜像
|
||||||
|
if (isRTL) el.setAttribute('dir', 'rtl');
|
||||||
|
|
||||||
const posStyles: Record<string, string> = {
|
const posStyles: Record<string, string> = {
|
||||||
'top-left': 'top:0;left:0;align-items:flex-start',
|
'top-left': 'top:0;left:0;align-items:flex-start',
|
||||||
@@ -417,7 +419,8 @@ export class Toast implements ToastInstance {
|
|||||||
el.releasePointerCapture(e.pointerId);
|
el.releasePointerCapture(e.pointerId);
|
||||||
el.style.transition = '';
|
el.style.transition = '';
|
||||||
|
|
||||||
if (Math.abs(dx) > 120) {
|
const threshold = this.config.dragThreshold ?? 120;
|
||||||
|
if (Math.abs(dx) > threshold) {
|
||||||
el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
|
el.style.transform = `translate(${dx * 2}px, ${dy}px) rotate(${dx * 0.2}deg)`;
|
||||||
el.style.opacity = '0';
|
el.style.opacity = '0';
|
||||||
setTimeout(() => this.close(true), 250);
|
setTimeout(() => this.close(true), 250);
|
||||||
@@ -535,6 +538,18 @@ export class Toast implements ToastInstance {
|
|||||||
content.innerHTML = `${safeTitle}${safeMessage}`;
|
content.innerHTML = `${safeTitle}${safeMessage}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// duration 变更:重启或停止计时器(loading 0 → N 转换、动态调整时长等场景)
|
||||||
|
if (partial.duration !== undefined && partial.duration !== this.config.duration) {
|
||||||
|
this.config.duration = partial.duration;
|
||||||
|
if (this.rafId !== null) cancelAnimationFrame(this.rafId);
|
||||||
|
this.rafId = null;
|
||||||
|
this.remaining = Math.max(0, partial.duration);
|
||||||
|
if (partial.duration > 0 && !this.paused) {
|
||||||
|
this.startedAt = Date.now();
|
||||||
|
this._startTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// resetTimerOnUpdate: 更新内容后重置计时器
|
// resetTimerOnUpdate: 更新内容后重置计时器
|
||||||
if (this.config.resetTimerOnUpdate && (this.config.duration || 0) > 0) {
|
if (this.config.resetTimerOnUpdate && (this.config.duration || 0) > 0) {
|
||||||
if (this.rafId !== null) cancelAnimationFrame(this.rafId);
|
if (this.rafId !== null) cancelAnimationFrame(this.rafId);
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast — 核心类型定义
|
* MetonaToast — 核心类型定义
|
||||||
* @module types
|
* @module types
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ========== 基础类型 ==========
|
// ========== 基础类型 ==========
|
||||||
@@ -72,6 +72,7 @@ export interface ToastConfig {
|
|||||||
pauseOnHover?: boolean;
|
pauseOnHover?: boolean;
|
||||||
closeOnClick?: boolean;
|
closeOnClick?: boolean;
|
||||||
draggable?: boolean;
|
draggable?: boolean;
|
||||||
|
dragThreshold?: number;
|
||||||
showProgress?: boolean;
|
showProgress?: boolean;
|
||||||
progressDirection?: ToastProgressDirection;
|
progressDirection?: ToastProgressDirection;
|
||||||
icon?: boolean;
|
icon?: boolean;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast Utils — 工具函数
|
* MetonaToast Utils — 工具函数
|
||||||
* @module utils
|
* @module utils
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+126
-51
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast 覆盖率补充测试 — 目标 95%+
|
* MetonaToast 覆盖率补充测试 — 目标 95%+
|
||||||
* @module tests
|
* @module tests
|
||||||
* @version 0.3.0
|
* @version 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MeToast, { Toast, VERSION } from '../src/index';
|
import MeToast, { Toast, VERSION } from '../src/index';
|
||||||
@@ -809,7 +809,7 @@ describe('api.ts 覆盖率', () => {
|
|||||||
|
|
||||||
test('getStatus 返回完整状态', () => {
|
test('getStatus 返回完整状态', () => {
|
||||||
const status = MeToast.getStatus();
|
const status = MeToast.getStatus();
|
||||||
expect(status.version).toBe('0.3.0');
|
expect(status.version).toBe('0.4.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();
|
||||||
@@ -1089,32 +1089,8 @@ describe('styles.ts 覆盖率', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ==================================================================
|
// ==================================================================
|
||||||
// 9. utils.ts 覆盖率补充
|
// 9. utils.ts 覆盖率补充(SSR 分支见 tests/ssr.test.ts — 独立环境隔离)
|
||||||
// ==================================================================
|
// ==================================================================
|
||||||
describe('utils.ts 覆盖率', () => {
|
|
||||||
test('escapeHTML SSR 路径(无 document)', () => {
|
|
||||||
// 需要绕过 mock document
|
|
||||||
const originalDoc = (global as Record<string, unknown>).document;
|
|
||||||
(global as Record<string, unknown>).document = undefined;
|
|
||||||
jest.resetModules();
|
|
||||||
const { escapeHTML } = require('../src/utils.js');
|
|
||||||
const result = escapeHTML('<script>alert("xss")</script>');
|
|
||||||
expect(result).not.toContain('<script>');
|
|
||||||
expect(result).toContain('<script>');
|
|
||||||
(global as Record<string, unknown>).document = originalDoc;
|
|
||||||
jest.resetModules();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prefersDark 无 window 时返回 false', () => {
|
|
||||||
const originalWindow = (global as Record<string, unknown>).window;
|
|
||||||
(global as Record<string, unknown>).window = undefined;
|
|
||||||
jest.resetModules();
|
|
||||||
const { prefersDark } = require('../src/utils.js');
|
|
||||||
expect(prefersDark()).toBe(false);
|
|
||||||
(global as Record<string, unknown>).window = originalWindow;
|
|
||||||
jest.resetModules();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==================================================================
|
// ==================================================================
|
||||||
// 10. 最终补漏 — 针对性覆盖剩余未覆盖行
|
// 10. 最终补漏 — 针对性覆盖剩余未覆盖行
|
||||||
@@ -1277,17 +1253,7 @@ describe('最终补漏', () => {
|
|||||||
unsub();
|
unsub();
|
||||||
});
|
});
|
||||||
|
|
||||||
// === themes.ts localStorage 异常路径 ===
|
// === themes.ts localStorage 异常路径(详见 tests/ssr.test.ts) ===
|
||||||
test('saveTheme localStorage 异常被捕获', () => {
|
|
||||||
const orig = (global as Record<string, unknown>).localStorage;
|
|
||||||
(global as Record<string, unknown>).localStorage = undefined;
|
|
||||||
jest.resetModules();
|
|
||||||
const { saveTheme } = require('../src/themes.js');
|
|
||||||
expect(() => saveTheme('dark')).not.toThrow();
|
|
||||||
(global as Record<string, unknown>).localStorage = orig;
|
|
||||||
jest.resetModules();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('loadTheme/loadLocale 异常路径不抛异常', () => {
|
test('loadTheme/loadLocale 异常路径不抛异常', () => {
|
||||||
const { loadTheme } = require('../src/themes.js');
|
const { loadTheme } = require('../src/themes.js');
|
||||||
const { loadLocale } = require('../src/i18n.js');
|
const { loadLocale } = require('../src/i18n.js');
|
||||||
@@ -1295,19 +1261,6 @@ describe('最终补漏', () => {
|
|||||||
expect(typeof loadLocale()).toBe('string');
|
expect(typeof loadLocale()).toBe('string');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loadLocale localStorage 异常返回 default', () => {
|
|
||||||
const i18n = require('../src/i18n.js');
|
|
||||||
const orig = (global as Record<string, unknown>).localStorage;
|
|
||||||
(global as Record<string, unknown>).localStorage = {
|
|
||||||
getItem: () => { throw new Error('quota'); },
|
|
||||||
};
|
|
||||||
jest.resetModules();
|
|
||||||
const { loadLocale } = require('../src/i18n.js');
|
|
||||||
expect(typeof loadLocale()).toBe('string');
|
|
||||||
(global as Record<string, unknown>).localStorage = orig;
|
|
||||||
jest.resetModules();
|
|
||||||
});
|
|
||||||
|
|
||||||
// === styles.ts 剩余 ===
|
// === styles.ts 剩余 ===
|
||||||
test('autoApplySystemTheme 内部逻辑不抛异常', () => {
|
test('autoApplySystemTheme 内部逻辑不抛异常', () => {
|
||||||
const styles = require('../src/styles.js');
|
const styles = require('../src/styles.js');
|
||||||
@@ -1493,3 +1446,125 @@ describe('v0.3.0 修复验证', () => {
|
|||||||
}, 120);
|
}, 120);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ==================================================================
|
||||||
|
// 12. v0.4.0 能力增强验证
|
||||||
|
// ==================================================================
|
||||||
|
describe('v0.4.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();
|
||||||
|
MeToast.animations.destroy();
|
||||||
|
MeToast.animations.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loading 链式转换 id 稳定(原地 update)', () => {
|
||||||
|
const l = MeToast.loading('正在加载');
|
||||||
|
const idBefore = l.id;
|
||||||
|
const toast = l.success('加载完成');
|
||||||
|
expect(toast).not.toBeNull();
|
||||||
|
expect(toast!.id).toBe(idBefore);
|
||||||
|
expect(toast!.type).toBe('success');
|
||||||
|
expect(toast!.message).toBe('加载完成');
|
||||||
|
expect(MeToast.count()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loading 转换后 duration 恢复默认自动关闭', () => {
|
||||||
|
MeToast.configure({ duration: 4000 });
|
||||||
|
const l = MeToast.loading('loading');
|
||||||
|
const toast = l.error('失败');
|
||||||
|
expect(toast!.config.duration).toBe(4000);
|
||||||
|
MeToast.resetConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loading 转换支持 opts 覆盖 duration', () => {
|
||||||
|
const l = MeToast.loading('loading');
|
||||||
|
const toast = l.warning('警告', { duration: 8000 });
|
||||||
|
expect(toast!.config.duration).toBe(8000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update duration 变更重启计时器', () => {
|
||||||
|
const t = MeToast.info('test', { duration: 0 });
|
||||||
|
expect(t.rafId).toBeNull();
|
||||||
|
t.update({ duration: 5000 });
|
||||||
|
expect(t.config.duration).toBe(5000);
|
||||||
|
expect(t.rafId).not.toBeNull();
|
||||||
|
t.update({ duration: 0 });
|
||||||
|
expect(t.config.duration).toBe(0);
|
||||||
|
expect(t.rafId).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dragThreshold 配置生效且默认 120', () => {
|
||||||
|
expect(MeToast.getConfig().dragThreshold).toBe(120);
|
||||||
|
const t = MeToast.info('x', { dragThreshold: 200 });
|
||||||
|
expect(t.config.dragThreshold).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('RTL 容器设置 dir 属性', () => {
|
||||||
|
const { getCurrentLocale, setCurrentLocale, addTranslations } = require('../src/i18n.js');
|
||||||
|
const originalLocale = getCurrentLocale();
|
||||||
|
// 'ar' 需先注册翻译,否则 setCurrentLocale fallback 回默认语言
|
||||||
|
addTranslations('ar', { close: 'إغلاق' });
|
||||||
|
setCurrentLocale('ar');
|
||||||
|
try {
|
||||||
|
const toast = new Toast({ message: 'test', position: 'top-left' });
|
||||||
|
const container = toast._getContainer();
|
||||||
|
expect(container.getAttribute('dir')).toBe('rtl');
|
||||||
|
toast.close(true);
|
||||||
|
} finally {
|
||||||
|
setCurrentLocale(originalLocale);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('LTR 容器不设置 dir 属性', () => {
|
||||||
|
const { getCurrentLocale, setCurrentLocale } = require('../src/i18n.js');
|
||||||
|
const originalLocale = getCurrentLocale();
|
||||||
|
setCurrentLocale('zh-CN');
|
||||||
|
try {
|
||||||
|
const toast = new Toast({ message: 'test' });
|
||||||
|
const container = toast._getContainer();
|
||||||
|
expect(container.getAttribute('dir')).toBeNull();
|
||||||
|
toast.close(true);
|
||||||
|
} finally {
|
||||||
|
setCurrentLocale(originalLocale);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dedupe 插件同消息去重', () => {
|
||||||
|
MeToast.use('dedupe');
|
||||||
|
MeToast.info('same message');
|
||||||
|
MeToast.info('same message');
|
||||||
|
expect(MeToast.count()).toBe(1);
|
||||||
|
MeToast.info('different message');
|
||||||
|
expect(MeToast.count()).toBe(2);
|
||||||
|
MeToast.plugins.unregister('dedupe');
|
||||||
|
// 卸载后不再去重
|
||||||
|
MeToast._toasts.clear();
|
||||||
|
MeToast.info('same message');
|
||||||
|
MeToast.info('same message');
|
||||||
|
expect(MeToast.count()).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dedupe 插件不同 type 不去重', () => {
|
||||||
|
MeToast.use('dedupe');
|
||||||
|
MeToast.info('msg');
|
||||||
|
MeToast.success('msg');
|
||||||
|
expect(MeToast.count()).toBe(2);
|
||||||
|
MeToast.plugins.unregister('dedupe');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dedupe 插件 uninstall 移除钩子', () => {
|
||||||
|
MeToast.use('dedupe');
|
||||||
|
MeToast.plugins.unregister('dedupe');
|
||||||
|
MeToast._toasts.clear();
|
||||||
|
MeToast.info('m');
|
||||||
|
MeToast.info('m');
|
||||||
|
expect(MeToast.count()).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+6
-6
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* MetonaToast 单元测试
|
* MetonaToast 单元测试
|
||||||
* @module tests
|
* @module tests
|
||||||
* @version 0.3.0
|
* @version 0.4.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.3.0');
|
expect(VERSION).toBe('0.4.0');
|
||||||
expect(MeToast.version).toBe('0.3.0');
|
expect(MeToast.version).toBe('0.4.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.3.0');
|
expect(status.version).toBe('0.4.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.3.0 新增功能测试 ==========
|
// ========== v0.4.0 新增功能测试 ==========
|
||||||
|
|
||||||
describe('v0.3.0 新增功能', () => {
|
describe('v0.4.0 新增功能', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MeToast._toasts.clear();
|
MeToast._toasts.clear();
|
||||||
const { _containerCache } = require('../src/toast.js');
|
const { _containerCache } = require('../src/toast.js');
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* MetonaToast React 适配器测试
|
||||||
|
* @module tests
|
||||||
|
* @version 0.4.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { renderToString } from 'react-dom/server';
|
||||||
|
import MeToast, { Toast } from '../src/index';
|
||||||
|
import { createBoundApi, Toast as ToastComponent } from '../src/react';
|
||||||
|
import type { ToastInstance } from '../src/types';
|
||||||
|
|
||||||
|
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();
|
||||||
|
MeToast.animations.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('React 适配器 — createBoundApi', () => {
|
||||||
|
test('跟踪创建的 toast id', () => {
|
||||||
|
const cleanup = new Set<string>();
|
||||||
|
const api = createBoundApi(cleanup);
|
||||||
|
(api.info as (m: string) => ToastInstance)('hello');
|
||||||
|
expect(cleanup.size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('success/error/warning/show 均被跟踪', () => {
|
||||||
|
const cleanup = new Set<string>();
|
||||||
|
const api = createBoundApi(cleanup);
|
||||||
|
(api.success as (m: string) => ToastInstance)('a');
|
||||||
|
(api.error as (m: string) => ToastInstance)('b');
|
||||||
|
(api.warning as (m: string) => ToastInstance)('c');
|
||||||
|
(api.show as (m: string) => ToastInstance)('d');
|
||||||
|
expect(cleanup.size).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loading 控制对象也被跟踪', () => {
|
||||||
|
const cleanup = new Set<string>();
|
||||||
|
const api = createBoundApi(cleanup);
|
||||||
|
(api.loading as (m: string) => { id: string })('loading...');
|
||||||
|
expect(cleanup.size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dismiss 同步清理集合', () => {
|
||||||
|
const cleanup = new Set<string>();
|
||||||
|
const api = createBoundApi(cleanup);
|
||||||
|
const t = (api.success as (m: string) => ToastInstance)('hi');
|
||||||
|
expect(cleanup.has(t.id)).toBe(true);
|
||||||
|
(api.dismiss as (id?: string) => void)(t.id);
|
||||||
|
expect(cleanup.has(t.id)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removeToast 同步清理集合', () => {
|
||||||
|
const cleanup = new Set<string>();
|
||||||
|
const api = createBoundApi(cleanup);
|
||||||
|
const t = (api.info as (m: string) => ToastInstance)('hi');
|
||||||
|
(api.removeToast as (id: string) => void)(t.id);
|
||||||
|
expect(cleanup.size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('未跟踪的方法直接透传', () => {
|
||||||
|
const cleanup = new Set<string>();
|
||||||
|
const api = createBoundApi(cleanup);
|
||||||
|
expect(typeof (api as Record<string, unknown>).count).toBe('function');
|
||||||
|
expect((api as { count(): number }).count()).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('React 适配器 — Toast 组件', () => {
|
||||||
|
test('SSR 渲染为空(effect 不执行)', () => {
|
||||||
|
const html = renderToString(React.createElement(ToastComponent, { message: 'ssr message' }));
|
||||||
|
expect(html).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SSR 带 type 不抛异常', () => {
|
||||||
|
const html = renderToString(React.createElement(ToastComponent, { message: 'x', type: 'success' }));
|
||||||
|
expect(html).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SSR 未知 type fallback 到 show 不抛异常', () => {
|
||||||
|
const html = renderToString(React.createElement(ToastComponent, { message: 'y', type: 'custom-type' }));
|
||||||
|
expect(html).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Jest 环境补丁 — jsdom 未暴露 Node 全局对象
|
||||||
|
*/
|
||||||
|
import { TextEncoder, TextDecoder } from 'util';
|
||||||
|
|
||||||
|
const g = globalThis as Record<string, unknown>;
|
||||||
|
|
||||||
|
if (typeof g.TextEncoder === 'undefined') {
|
||||||
|
g.TextEncoder = TextEncoder;
|
||||||
|
}
|
||||||
|
if (typeof g.TextDecoder === 'undefined') {
|
||||||
|
g.TextDecoder = TextDecoder;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* 环境隔离测试 — 需要切换 document/window/localStorage 环境并调用 jest.resetModules()。
|
||||||
|
* 独立成文件:resetModules 会清空模块缓存,若与其他测试同文件会导致
|
||||||
|
* 后续 require 拿到新模块实例、与 import 时绑定的实例状态分裂。
|
||||||
|
* @module tests
|
||||||
|
* @version 0.4.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('环境隔离(SSR / localStorage 异常路径)', () => {
|
||||||
|
test('escapeHTML SSR 路径(无 document)', () => {
|
||||||
|
const originalDoc = (global as Record<string, unknown>).document;
|
||||||
|
(global as Record<string, unknown>).document = undefined;
|
||||||
|
jest.resetModules();
|
||||||
|
const { escapeHTML } = require('../src/utils.js');
|
||||||
|
const result = escapeHTML('<script>alert("xss")</script>');
|
||||||
|
expect(result).not.toContain('<script>');
|
||||||
|
expect(result).toContain('<script>');
|
||||||
|
(global as Record<string, unknown>).document = originalDoc;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prefersDark 无 window 时返回 false', () => {
|
||||||
|
const originalWindow = (global as Record<string, unknown>).window;
|
||||||
|
(global as Record<string, unknown>).window = undefined;
|
||||||
|
jest.resetModules();
|
||||||
|
const { prefersDark } = require('../src/utils.js');
|
||||||
|
expect(prefersDark()).toBe(false);
|
||||||
|
(global as Record<string, unknown>).window = originalWindow;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('saveTheme localStorage 异常被捕获', () => {
|
||||||
|
const orig = (global as Record<string, unknown>).localStorage;
|
||||||
|
(global as Record<string, unknown>).localStorage = undefined;
|
||||||
|
jest.resetModules();
|
||||||
|
const { saveTheme } = require('../src/themes.js');
|
||||||
|
expect(() => saveTheme('dark')).not.toThrow();
|
||||||
|
(global as Record<string, unknown>).localStorage = orig;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loadLocale localStorage 异常返回 default', () => {
|
||||||
|
const orig = (global as Record<string, unknown>).localStorage;
|
||||||
|
(global as Record<string, unknown>).localStorage = {
|
||||||
|
getItem: () => { throw new Error('quota'); },
|
||||||
|
};
|
||||||
|
jest.resetModules();
|
||||||
|
const { loadLocale } = require('../src/i18n.js');
|
||||||
|
expect(typeof loadLocale()).toBe('string');
|
||||||
|
(global as Record<string, unknown>).localStorage = orig;
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user