From dd061025e07b1bd8ca3d20645b2ffb3dc266d7be Mon Sep 17 00:00:00 2001 From: tianhao Date: Sat, 8 Aug 2026 15:11:04 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E9=87=8D=E5=86=99=20README=20=E4=B8=8E?= =?UTF-8?q?=20site=20=E7=AB=99=E7=82=B9=20=E2=80=94=20=E7=B2=BE=E7=BE=8E?= =?UTF-8?q?=E9=85=B7=E7=82=AB=E5=85=A8=E6=96=B0=E8=A7=86=E8=A7=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README 全面更新:对齐 v0.5.0(钩子系统完整表、React 适配器、dragThreshold、工程质量章节) - site 三页全新设计:深色渐变 + 玻璃拟态 + 光球漂浮 + 网格背景 + 终端风格代码块 + 滚动淡入 - index.html:Hero 自动演示、数据统计条、12 特性卡、环境支持、CTA - docs.html:28 项 API 导航、滚动高亮、补全钩子系统/React/配置项/动画/主题章节 - demo.html:18 交互卡片、新增钩子拦截/dedupe 去重/自定义动画/拖拽阈值演示、107 图标网格 - jsdom 冒烟验证:三页 MeToast 全局注册、toast 渲染、图标网格/卡片/代码块完整 --- README.md | 154 ++++++++--- site/demo.html | 329 ++++++++++++++-------- site/docs.html | 723 +++++++++++++++++++++++------------------------- site/index.html | 501 +++++++++++++++++++++++---------- 4 files changed, 1019 insertions(+), 688 deletions(-) diff --git a/README.md b/README.md index 9e7696d..fede285 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,21 @@ [![npm version](https://img.shields.io/npm/v/@metona-team/metona-toast.svg)](https://www.npmjs.com/package/@metona-team/metona-toast) [![bundle size](https://img.shields.io/bundlephobia/minzip/@metona-team/metona-toast)](https://bundlephobia.com/package/@metona-team/metona-toast) [![license](https://img.shields.io/badge/license-MIT-green.svg)](https://git.metona.cn/MetonaTeam/MetonaToast) +[![coverage](https://img.shields.io/badge/coverage-96%25-brightgreen.svg)](https://git.metona.cn/MetonaTeam/MetonaToast) ## 特性 -- **零依赖** — TypeScript 严格模式源码,gzip 后不到 10KB -- **107 种图标类型** — success/error/warning/info/loading 及更多扩展类型,覆盖所有常见场景 -- **11 种动画** — slide/fade/scale/bounce/flip/rotate/zoom/slideUp/slideDown/slideLeft/slideRight,每种效果明显不同 -- **主题系统** — light/dark/auto/warm 四种内置主题,支持 `registerTheme()` 注册自定义主题 -- **国际化** — 内置 zh-CN / en-US 完整翻译(去重优化),可通过 `addTranslations()` 扩展 -- **插件系统** — 4 款预设插件 (keyboard/persistence/accessibility/dedupe) + 自定义插件 -- **可拖拽关闭** — 拖动 Toast 任意方向即可关闭,阈值可配置 -- **TypeScript** — 严格模式,源码级类型安全,类型从源码自动生成无手动维护 -- **全局错误回调** — `onError` 捕获所有钩子异常和定时器错误,方便接入监控系统 +- **零依赖** — TypeScript 严格模式源码,gzip 后不到 10KB,无任何运行时依赖 +- **107 种图标类型** — success/error/warning/info/loading 及 100+ 扩展类型,覆盖所有常见场景 +- **11 种动画 + 自定义** — slide/fade/scale/bounce/flip/rotate/zoom/slideUp/slideDown/slideLeft/slideRight,`animations.register()` 可注册任意自定义动画(动态注入 keyframes,真实生效) +- **主题系统** — light/dark/auto/warm 四种内置主题,`registerTheme()` 注册自定义主题,跟随系统主题自动切换 +- **国际化** — 内置 zh-CN / en-US 完整翻译,`addTranslations()` 扩展任意语言,RTL 语言完整适配 +- **插件系统** — 4 款预设插件 (keyboard / persistence / accessibility / dedupe) + 自定义插件 +- **完整钩子系统** — 17 个生命周期与事件钩子,`beforeShow` / `beforeClose` / `beforeUpdate` 支持返回 `false` 拦截 +- **可拖拽关闭** — 拖动 Toast 任意方向即可关闭,阈值 `dragThreshold` 可配置 +- **React 适配器** — `@metona-team/metona-toast/react` 子路径导入,`useToast()` hook + 声明式 `` 组件,主包保持零依赖 +- **全局错误回调** — `onError` 捕获所有钩子异常和定时器错误,方便接入 Sentry、DataDog 等监控系统 +- **工程质量** — 385 个测试全过,行覆盖率 96.42%,lint 零警告,CI 含覆盖率门禁(≥95%) ## 安装 @@ -59,13 +62,13 @@ const MeToast = require('@metona-team/metona-toast'); | iOS Safari | 11+ | | Android Chrome | 64+ | | Node.js | 16+(ESM & CJS 双格式) | -| TypeScript | 4.5+(完整类型声明) | +| TypeScript | 4.5+(完整类型声明,类型从源码自动生成) | 核心依赖:Web Animations API(Chrome 64+, Firefox 63+, Safari 11+)、Pointer Events、CSS Grid、CSS Custom Properties。 ## 快速开始 -浏览器中 `window` 上会注册两个全局变量:`MeToast` 和 `Met`,两者完全等价,任选其一使用。 +浏览器 `window` 上注册两个全局变量:`MeToast` 和 `Met`,完全等价。 ```javascript import MeToast from '@metona-team/metona-toast'; @@ -82,13 +85,11 @@ MeToast.info('系统维护中'); // 带标题 MeToast.success({ title: '保存成功', message: '文件已同步到云端' }); -// 加载 → 成功链式转换(原地更新同一实例,id 稳定) +// 加载 → 链式转换(原地更新同一实例,id 稳定) const loading = MeToast.loading('正在提交...'); -const toast = await wait(); -loading.success('提交成功!'); -// toast.id === loading.id +setTimeout(() => loading.success('提交成功!'), 2000); -// Promise 风格 +// Promise 风格 — 自动 loading → success/error await MeToast.promise(fetch('/api/data'), { loading: '加载中...', success: '加载完成!', @@ -102,17 +103,22 @@ await MeToast.promise(fetch('/api/data'), { MeToast.configure({ position: 'top-right', // top-left | top-center | top-right | bottom-left | bottom-center | bottom-right duration: 4000, // 显示时长(ms),0 为不自动关闭 - max: 6, // 同时最多显示条数 - theme: 'auto', // light | dark | auto | warm - animation: 'slide', // slide | fade | scale | bounce | flip | rotate | zoom | slideUp | slideDown | slideLeft | slideRight + max: 6, // 同一位置最多显示条数,超出关闭最早的 + gap: 12, // Toast 间距(px) + offset: 24, // 容器距屏幕边缘(px) + theme: 'auto', // light | dark | auto(跟随系统)| warm + animation: 'slide', // 11 种内置动画或自定义动画名 pauseOnHover: true, // 悬停暂停计时 closeOnClick: true, // 点击关闭 draggable: true, // 允许拖拽关闭 dragThreshold: 120, // 拖拽关闭阈值(px) showProgress: true, // 显示进度条 - draggable: true, // 允许拖拽关闭 + progressDirection: 'horizontal', // horizontal | vertical + zIndex: 9999, + width: 360, locale: 'zh-CN', // 语言 - onError: ({ hook, error, toast }) => { // 全局错误回调(钩子/定时器异常) + onError: ({ hook, source, error, toast }) => { + // 钩子/定时器异常全局捕获,可接入监控系统 console.error('Toast error:', hook, error); }, }); @@ -120,40 +126,42 @@ MeToast.configure({ ## 动画效果 -11 种动画,每种感官差异明显: +11 种内置动画,每种感官差异明显: -| 动画 | 效果描述 | -|------|----------| -| `slide` | 从右侧滑入 + 轻微过冲回弹 | -| `fade` | 纯淡入(blur→清晰),从容优雅 | -| `scale` | 弹性放大(0.55→1.07→1.0) | -| `bounce` | 从天而降四段弹跳 | -| `flip` | 3D 翻转入场 + 回摆 | -| `rotate` | 旋转摇摆进入 | -| `zoom` | 从中心爆发式弹出 | -| `slideUp` | 从下方弹入 | -| `slideDown` | 从上方弹入 | -| `slideLeft` | 从左侧滑入 | -| `slideRight` | 从右侧滑入 | +| 动画 | 效果描述 | 时长 | +|------|----------|------| +| `slide` | 从右侧滑入 + 轻微过冲回弹 | 400ms | +| `fade` | 纯淡入(blur→清晰),从容优雅 | 500ms | +| `scale` | 弹性放大(0.55→1.07→1.0) | 450ms | +| `bounce` | 从天而降四段弹跳 | 650ms | +| `flip` | 3D 翻转入场 + 回摆 | 500ms | +| `rotate` | 旋转摇摆进入 | 500ms | +| `zoom` | 从中心爆发式弹出 | 500ms | +| `slideUp` | 从下方弹入 | 400ms | +| `slideDown` | 从上方弹入 | 400ms | +| `slideLeft` | 从左侧滑入 | 400ms | +| `slideRight` | 从右侧滑入 | 400ms | ```javascript MeToast.success('弹跳动画', { animation: 'bounce' }); -// 注册自定义动画 +// 注册自定义动画 — 动态注入 keyframes,真实生效 MeToast.animations.register('myAnim', { enter: { transform: 'rotate(-30deg) scale(0.5)', opacity: 0 }, leave: { transform: 'rotate(0) scale(1)', opacity: 1 }, duration: 500, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)', }); +MeToast.success('自定义动画', { animation: 'myAnim' }); ``` ## 主题 ```javascript -// 切换主题 +// 切换主题(自动持久化到 localStorage) MeToast.themes.switchTheme('dark'); MeToast.themes.switchTheme('warm'); +MeToast.themes.toggleTheme(); // light ↔ dark // 注册自定义主题 MeToast.themes.registerTheme('ocean', { @@ -192,6 +200,18 @@ await MeToast.queue(['步骤一', '步骤二', '步骤三'], { delay: 800 }); // 堆叠展示(同时错峰) MeToast.stack(['消息1', '消息2', '消息3'], { stagger: 150 }); + +// Action Toast — 内嵌操作按钮(close:false 可多次点击不关闭) +MeToast.action('文件已删除', [ + { text: '撤销', onClick: () => restore(), color: '#3b82f6' }, + { text: '查看', onClick: () => open(), color: '#10b981', close: false }, +]); + +// 分组管理 +const orders = MeToast.group('orders'); +orders.success('订单已创建'); +orders.count(); // 该组 toast 数量 +orders.dismiss(); // 一键关闭整组 ``` ## 插件 @@ -203,7 +223,7 @@ MeToast.use('persistence'); // 配置持久化到 localStorage MeToast.use('accessibility'); // 屏幕阅读器朗读公告 MeToast.use('dedupe'); // 相同 type+message 自动去重(更新已有实例) -// 自定义插件(通过 install 中注册 Toast 生命周期钩子) +// 自定义插件(install 中注册生命周期钩子) import MeToast, { Toast } from '@metona-team/metona-toast'; MeToast.use({ @@ -213,6 +233,36 @@ MeToast.use({ console.log('Toast shown:', toast.message); }); }, + uninstall() { + console.log('plugin removed'); + }, +}); +``` + +## 钩子系统 + +`Toast.on(name, handler)` 注册钩子,返回取消函数。`beforeShow` / `beforeClose` / `beforeUpdate` 的 handler 返回 `false` 可拦截对应操作。 + +| 钩子 | 触发时机 | 拦截 | +|------|----------|------| +| `beforeInit` / `afterInit` | `init()` 前后 | — | +| `beforeDestroy` / `afterDestroy` | `destroy()` 前后 | — | +| `beforeShow` / `afterShow` | Toast 显示前后 | ✅ 返回 false 阻止显示 | +| `beforeClose` / `afterClose` | Toast 关闭前后 | ✅ 返回 false 阻止关闭 | +| `beforeUpdate` / `afterUpdate` | `update()` 前后 | ✅ 返回 false 阻止更新 | +| `configChange` | `configure`/`updateConfig`/`resetConfig` | — | +| `themeChange` | 主题切换 | — | +| `localeChange` | 语言切换 | — | +| `click` | 点击 Toast 主体 | — | +| `hover` | 鼠标悬停进出 | — | +| `dragStart` / `dragEnd` | 拖拽开始/结束 | — | +| `animationStart` / `animationEnd` | 入场动画开始/结束 | — | +| `progressStart` / `progressEnd` | 倒计时开始/归零 | — | + +```javascript +// 拦截示例:指定类型禁止显示 +Toast.on('beforeShow', (toast) => { + if (toast.type === 'error' && !isAllowed) return false; }); ``` @@ -221,7 +271,7 @@ MeToast.use({ ```javascript MeToast.i18n.switchLocale('en-US'); -// 添加语言 +// 添加语言(嵌套结构支持深度合并) MeToast.i18n.addTranslations('ja', { success: '成功', error: 'エラー', @@ -229,9 +279,11 @@ MeToast.i18n.addTranslations('ja', { cancel: 'キャンセル', }); -// 格式化工具 +// 格式化工具(基于 Intl) MeToast.i18n.formatNumber(1234567); // "1,234,567" MeToast.i18n.formatCurrency(99, 'CNY'); // "¥99.00" +MeToast.i18n.formatDate(new Date()); +MeToast.i18n.formatRelativeTime(Date.now() + 3600000); // "1 小时后" ``` ## Toast 管理 @@ -240,11 +292,17 @@ MeToast.i18n.formatCurrency(99, 'CNY'); // "¥99.00" MeToast.count(); // 当前数量 MeToast.getAll(); // 获取原生 Map MeToast.getToasts(); // 获取所有实例数组 +MeToast.find(id); // 查找实例(不存在返回 undefined) +MeToast.findByType(type); // 按类型过滤 +MeToast.findByPosition(pos);// 按位置过滤 MeToast.dismiss(); // 关闭所有 MeToast.dismiss(id); // 关闭指定 +MeToast.removeToast(id); // 立即移除(无离场动画) +MeToast.clear(position?); // 按位置清除 MeToast.pauseAll(); // 暂停所有计时 MeToast.resumeAll(); // 恢复所有计时 -MeToast.destroy(); // 完全销毁(支持重复调用) +MeToast.updateAll(partial); // 批量更新 +MeToast.destroy(); // 完全销毁(支持重复调用,init() 可恢复) ``` ## 回调 @@ -252,6 +310,7 @@ MeToast.destroy(); // 完全销毁(支持重复调用) ```javascript MeToast.success({ message: '操作成功', + onBeforeShow: (toast) => true, // 返回 false 阻止显示 onShow: (toast) => console.log('显示:', toast.id), onClose: (toast) => console.log('关闭:', toast.id), onClick: (toast) => console.log('点击:', toast.id), @@ -261,7 +320,6 @@ MeToast.success({ // 全局错误回调 — 捕获钩子异常和定时器错误 MeToast.configure({ onError: ({ hook, source, error, toast }) => { - // 接入监控系统 (Sentry, DataDog 等) reportError(error, { hook, source, toastId: toast?.id }); }, }); @@ -269,7 +327,7 @@ MeToast.configure({ ## React 适配器 -主包保持零依赖;React 适配器通过子路径导入,`react` 为 optional peerDependency。 +主包保持零依赖;React 适配器通过子路径导入,`react` 为 optional peerDependency,未使用 React 的项目不受影响。 ```bash npm install @metona-team/metona-toast react @@ -295,12 +353,20 @@ function SubmitButton() { return ; } -// 声明式 Toast — props 变化时更新,组件卸载时自动移除 +// 声明式 Toast — props 变化时更新内容,组件卸载时自动移除(autoClose 默认 true) function SaveIndicator({ saving }: { saving: boolean }) { return saving ? : null; } ``` +## 工程质量 + +- **385 个测试** 全部通过(单元 + DOM 交互 + React 渲染 + SSR 环境隔离) +- **行覆盖率 96.42%**,jest 覆盖率门禁 `lines >= 95%`,未达标构建即失败 +- **lint 零警告**(TypeScript ESLint 严格检查) +- **CI**(Gitea Actions):Node 18/20/22/24 矩阵 — typecheck → lint → 测试+覆盖率门禁 → 构建 → bundle size 检查 +- **自动发布**:推送 `v*` tag 触发测试、构建并发布到 Gitea npm registry + ## License MIT diff --git a/site/demo.html b/site/demo.html index b1eafdf..96a16f1 100644 --- a/site/demo.html +++ b/site/demo.html @@ -7,51 +7,78 @@ 在线演示 — MetonaToast
-

在线演示

+

在线演示

覆盖项目全部功能的交互演示 — 点击即可体验

@@ -81,10 +108,10 @@
- + - +
@@ -92,16 +119,17 @@
-
🔄 Loading 链式转换
+
🔄 Loading 链式转换 id 稳定
- - - - + + + +
+

转换后返回的 toast 与 loading 是同一个实例(id 不变)

- +
💬 对话框
@@ -116,8 +144,8 @@
📊 进度 & 倒计时
- - + +
@@ -136,7 +164,7 @@
-
⚡ Action Toast
+
⚡ Action Toast close:false 不关闭
@@ -146,7 +174,7 @@
-
📁 分组管理
+
📁 分组管理
@@ -157,7 +185,7 @@
-
🎭 11种动画效果
+
🎭 11 种动画效果
@@ -171,9 +199,14 @@
+
+
+ +
+
- +
📍 全部位置
@@ -186,7 +219,7 @@
- +
🎨 主题切换 & 自定义
@@ -194,24 +227,43 @@ - +
- +
-
🔌 插件系统
+
🔌 插件系统 4 款预设
- - - + + + +
+
+
+ +
+
- +
-
🔥 高级特性 resetTimer · onUpdate · render · notify
+
🔗 钩子系统 beforeShow 拦截演示
+
+ + + + + +
+

钩子:未注册

+
+ + +
+
🔥 高级特性
@@ -220,7 +272,7 @@
- +
🔧 Toast 管理
@@ -230,7 +282,7 @@ - +
@@ -238,32 +290,34 @@ +
- +
-
🛡️ onError 错误捕获 v0.5.0
+
🛡️ onError 错误捕获
- +
✋ 交互特性
- - - - + + + + +
- +
🌍 国际化 & 格式化
@@ -273,16 +327,19 @@
- +
-
🎨 107种预设图标 — 点击任意图标发送Toast
-
+
🎨 107 种预设图标 — 点击任意图标发送 Toast
+
@@ -290,27 +347,41 @@ MeToast.configure({ position:'top-right', duration:4000, theme:'dark', animation:'slide', pauseOnHover:true, showProgress:true }); // === Loading === - function demoLoading() { const l=MeToast.loading('正在提交...'); setTimeout(()=>l.success('提交成功!'),2000); } - function demoLoadingFail() { const l=MeToast.loading('正在处理...'); setTimeout(()=>l.error('处理失败'),2000); } - function demoLoadingWarn() { const l=MeToast.loading('检查中...'); setTimeout(()=>l.warning('发现潜在问题'),2000); } - function demoLoadingUpdate() { const l=MeToast.loading('处理中 0%'); let i=1; const t=setInterval(()=>{if(i>3){clearInterval(t);l.success('完成!');return;} l.update({message:'处理中 '+i*33+'%'}); i++;},600); } + function demoLoading(mode) { + const l = MeToast.loading('正在处理...'); + const showId = () => document.getElementById('loading-status').textContent = 'loading id: ' + l.id; + showId(); + setTimeout(() => { + const map = { success:['提交成功!','success'], error:['处理失败','error'], warning:['发现潜在问题','warning'], update:null }; + if (mode === 'update') { + l.update({ message: '进度 50%' }); + setTimeout(() => l.success('完成!'), 1000); + return; + } + const [msg, type] = map[mode]; + const t = l[type](msg); + if (t) document.getElementById('loading-status').textContent = '转换后 id: ' + t.id + '(' + (t.id === l.id ? '同一实例 ✓' : '不同实例 ✗') + ')'; + }, 1600); + } // === Promise / confirm / prompt === async function demoPromise() { - const p=new Promise((r,e)=>setTimeout(()=>Math.random()>0.3?r('ok'):e('fail'),2000)); - try{await MeToast.promise(p,{loading:'处理中...',success:'成功!',error:'失败'});}catch(_){} + const p = new Promise((r,e)=>setTimeout(()=>Math.random()>0.3?r('ok'):e('fail'),2000)); + try{ await MeToast.promise(p,{loading:'处理中...',success:'成功!',error:'失败'}); }catch(_){} } async function demoConfirm() { const ok=await MeToast.confirm('确定删除?',{confirmText:'删除',confirmColor:'#ef4444'}); if(ok)MeToast.success('已删除'); } async function demoPrompt() { const v=await MeToast.prompt('请输入姓名',{placeholder:'请输入...'}); if(v) MeToast.info('你好,'+v+'!'); } async function demoPromptPassword() { const v=await MeToast.prompt('请输入密码',{placeholder:'密码',inputType:'password',submitText:'登录'}); if(v) MeToast.info('密码已输入'); } // === Progress & Countdown === - function demoProgress() { const p=MeToast.progress('上传中...',{progressColor:'#3b82f6'}); let v=0; const t=setInterval(()=>{v+=Math.random()*25; if(v>=100){clearInterval(t);p.complete('上传完成!');}else p.setProgress(v);},400); } - function demoProgressV() { const p=MeToast.progress('上传中...',{progressColor:'#10b981',progressDirection:'vertical'}); let v=0; const t=setInterval(()=>{v+=Math.random()*25; if(v>=100){clearInterval(t);p.complete('完成!');}else p.setProgress(v);},400); } + function demoProgress(dir) { + const p=MeToast.progress('上传中...',{progressColor: dir==='vertical'?'#10b981':'#3b82f6', progressDirection: dir}); + let v=0; const t=setInterval(()=>{v+=Math.random()*25; if(v>=100){clearInterval(t);p.complete('上传完成!');}else p.setProgress(v);},400); + } function demoCountdown() { MeToast.countdown('{seconds} 秒后执行',5,{onComplete:()=>MeToast.success('已执行')}); } function demoCountdownPause() { const c=MeToast.countdown('{seconds}s 可暂停',10,{type:'info',onComplete:()=>MeToast.success('倒计时结束')}); - setTimeout(()=>{c.pause();MeToast.warning('已暂停3秒后恢复',{duration:3000})},4000); + setTimeout(()=>{c.pause();MeToast.warning('已暂停 3 秒后恢复',{duration:3000})},4000); setTimeout(()=>{c.resume();MeToast.info('已恢复倒计时')},7000); } @@ -321,14 +392,15 @@ setTimeout(()=>{q.cancel();MeToast.warning('队列已取消',{duration:2000});},3500); } function demoStack() { MeToast.stack(['消息1','消息2','消息3','消息4','消息5'],{stagger:120,type:'info'}); } - function demoStackMixed() { - MeToast.stack(['普通消息',{message:'警告消息',type:'warning',duration:5000},{message:'错误消息',type:'error'}],{stagger:150}); - } + function demoStackMixed() { MeToast.stack(['普通消息',{message:'警告消息',type:'warning',duration:5000},{message:'错误消息',type:'error'}],{stagger:150}); } // === Action === function demoActionDelete() { MeToast.action('文件 data.json 已删除',[{text:'撤销',onClick:()=>MeToast.success('已撤销'),color:'#3b82f6'}]); } function demoActionMulti() { MeToast.action('邮件已发送',[{text:'查看',onClick:()=>MeToast.info('打开邮件详情'),color:'#3b82f6'},{text:'撤回',onClick:()=>MeToast.warning('已撤回'),color:'#f59e0b'}]); } - function demoActionNoClose() { MeToast.action('完成后点击关闭',[{text:'我知道了',onClick:()=>{},color:'#10b981'}]); } + function demoActionNoClose() { + let n=0; + MeToast.action('点「+1」不会关闭,可连续点击',[{text:'+1',onClick:(t)=>{n++;t.update({message:'已点击 '+n+' 次(close:false 不关闭)'});},color:'#10b981',close:false}],{duration:8000}); + } // === Group === const group = MeToast.group('demo-group'); @@ -342,32 +414,71 @@ // === Animation === function anim(name) { MeToast.success('动画: '+name,{animation:name,duration:3000,showProgress:false}); } + function demoCustomAnim() { + MeToast.animations.register('swing', { + enter: { transform: 'rotate(-40deg) scale(0.3)', opacity: 0 }, + leave: { transform: 'rotate(20deg) scale(0.5)', opacity: 0 }, + duration: 600, + easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)', + }); + MeToast.success('自定义 swing 动画!', { animation: 'swing', duration: 3000 }); + } // === Theme === function switchTheme(t) { MeToast.themes.switchTheme(t); MeToast.configure({theme:t}); MeToast.info('主题: '+t,{theme:t}); } function demoRegisterTheme() { try { MeToast.themes.registerTheme('sunset',{bg:'rgba(255,248,240,0.96)',text:'#92400e',border:'rgba(249,115,22,0.2)',shadow:'0 10px 36px -10px rgba(249,115,22,0.2)',hoverShadow:'0 14px 48px -10px rgba(249,115,22,0.3)',progressBg:'rgba(249,115,22,0.1)',closeHoverBg:'rgba(249,115,22,0.1)'}); - MeToast.themes.switchTheme('sunset'); - MeToast.configure({theme:'sunset'}); + MeToast.themes.switchTheme('sunset'); MeToast.configure({theme:'sunset'}); MeToast.success('已切换到 sunset 主题',{theme:'sunset'}); } catch(_) { MeToast.warning('主题已注册,已切换'); } } // === Plugins === function demoCustomPlugin() { - MeToast.use({name:'click-counter',version:'1.0',install(){let c=0;Toast.on('afterShow',t=>{c++;setTimeout(()=>t.update({message:t.message.replace(/\\(\\d+\\)/,'')+' (总通知#'+c+')'}),1);});}}); + MeToast.use({name:'click-counter',version:'1.0',install(){let c=0;Toast.on('afterShow',t=>{c++;setTimeout(()=>t.update({message:t.message.replace(/\(\d+\)/,'')+' (总通知#'+c+')'}),1);});}}); MeToast.info('自定义插件已安装(消息后追加计数)'); } + function demoDedupe() { + if (!MeToast.plugins.has('dedupe')) MeToast.use('dedupe'); + MeToast.info('同一条消息'); + MeToast.info('同一条消息'); + MeToast.info('同一条消息'); + MeToast.info('当前仅 ' + MeToast.count() + ' 条 — 相同消息自动合并'); + } + + // === Hooks === + let hookOff = null; + function demoHookBlock() { + if (hookOff) hookOff(); + hookOff = Toast.on('beforeShow', (toast) => { + if (toast.type === 'error') { document.getElementById('hook-status').textContent = '钩子:error 已被拦截 ✋'; return false; } + }); + document.getElementById('hook-status').textContent = '钩子:已注册(error 类型将被拦截)'; + MeToast.info('拦截器已开启 — 试试触发 error'); + } + function demoHookUnblock() { + if (hookOff) { hookOff(); hookOff = null; } + document.getElementById('hook-status').textContent = '钩子:已移除'; + MeToast.info('拦截器已关闭'); + } + function demoHookCount() { + const stats = []; + ['afterShow','afterClose','configChange','themeChange'].forEach(name => { + const list = Toast._hooks.get(name); + stats.push(name + ':' + (list ? list.length : 0)); + }); + MeToast.info('钩子数量 — ' + stats.join(' '), { duration: 5000 }); + } // === 高级特性 === function demoResetTimer() { - const t=MeToast.info('这条消息不会自动消失(resetTimer)',{duration:3000,resetTimerOnUpdate:true,closeButton:true}); - let n=0; const iv=setInterval(()=>{n++;if(n>8){clearInterval(iv);t.close();return;}t.update({message:'重置计时器 #'+n+' — 每次更新重置3秒'});},1000); + const t=MeToast.info('这条不会自动消失(每次更新重置 3 秒)',{duration:3000,resetTimerOnUpdate:true,closeButton:true}); + let n=0; const iv=setInterval(()=>{n++;if(n>8){clearInterval(iv);t.close();return;}t.update({message:'重置计时器 #'+n});},1000); } - function demoOnUpdate() { const t=MeToast.success('onUpdate 演示',{onUpdate:t=>{console.log('更新了:',t.message);}}); setTimeout(()=>t.update({message:'已更新!'}),1000); setTimeout(()=>t.update({message:'再次更新!'}),2000); } + function demoOnUpdate() { const t=MeToast.success('onUpdate 演示',{onUpdate:t=>{console.log('更新了:',t.message);}}); setTimeout(()=>t.update({message:'已更新!'}),1000); } function demoCustomRender() { - MeToast.show({render:toast=>'
🟢 '+toast.message+'
# custom render by thzxx
',message:'自定义渲染内容',duration:5000}); + MeToast.show({render:toast=>'
🟢 '+toast.message+'
# custom render
',message:'自定义渲染内容',duration:5000}); } function demoNotify() { if(typeof Notification==='undefined'){MeToast.error('浏览器不支持 Notification');return;} @@ -382,48 +493,38 @@ } function demoGetAll() { const all=MeToast.getAll(); MeToast.info('当前共 '+all.size+' 条 toast'); } function demoClearByPos() { MeToast.error('右下角临时消息',{position:'bottom-right',duration:3000}); setTimeout(()=>{MeToast.clear('bottom-right');MeToast.info('右下角已清除');},500); } - function demoGetConfig() { try{const c=MeToast.getConfig();MeToast.info('配置: '+JSON.stringify({position:c.position,duration:c.duration,theme:c.theme,animation:c.animation,locale:c.locale}).replace(/"/g,''),{duration:5000});}catch(e){MeToast.error('无 getConfig 方法');} } - - // === Interaction === - function demoDraggable() { MeToast.info('← 试试拖动我来关闭',{draggable:true,duration:8000,closeButton:true}); } - function demoPauseHover() { MeToast.info('鼠标悬停在我上面,倒计时会暂停',{pauseOnHover:true,duration:8000}); } - function demoCloseOnClick() { MeToast.warning('点击我即可关闭',{closeOnClick:true,duration:10000}); } - function demoWidth() { MeToast.info('宽550px',{width:550}); setTimeout(()=>MeToast.info('宽280px(窄)',{width:280}),500); } - function demoClassName() { MeToast.success('带自定义 className',{className:'demo-custom-class'}); } - function demoCustomIcon() { - MeToast.info('通用通知', { - iconHTML: '' - }); - } + function demoGetConfig() { const c=MeToast.getConfig(); MeToast.info('配置: '+JSON.stringify({position:c.position,duration:c.duration,theme:c.theme,animation:c.animation,locale:c.locale}).replace(/"/g,''),{duration:5000}); } + function demoRemove() { const t=MeToast.warning('这条将被立即移除(无动画)'); setTimeout(()=>{MeToast.removeToast(t.id);MeToast.info('已立即移除');},800); } // === onError === function demoOnError() { - MeToast.configure({ - onError: function(info) { - MeToast.error('⚠️ 捕获错误: ' + (info.hook || info.source), { duration: 3000, position: 'bottom-center' }); - } - }); - MeToast.info('已启用 onError 监控(钩子/定时器异常将被捕获)', { duration: 3000 }); - // 触发一个钩子错误来演示 + MeToast.configure({ onError: function(info) { MeToast.error('⚠️ 捕获: ' + (info.hook || info.source), { duration: 3000, position: 'bottom-center' }); } }); + MeToast.info('已启用 onError 监控', { duration: 3000 }); var badFn = function() { throw new Error('demo-error'); }; Toast.on('afterShow', badFn); - MeToast.success('这条消息会触发错误', { duration: 2000 }); + MeToast.success('这条会触发错误回调', { duration: 2000 }); setTimeout(function() { Toast.off('afterShow', badFn); }, 3000); } - function demoOnErrorReset() { - MeToast.configure({ onError: null }); - MeToast.info('onError 已重置为 null'); + function demoOnErrorReset() { MeToast.configure({ onError: null }); MeToast.info('onError 已重置'); } + + // === Interaction === + function demoDraggable() { MeToast.info('← 拖动我超过 120px 关闭',{draggable:true,duration:8000,closeButton:true}); } + function demoDragThreshold() { MeToast.info('← 拖动阈值 300px(要拖更远)',{draggable:true,dragThreshold:300,duration:8000,closeButton:true}); } + function demoPauseHover() { MeToast.info('悬停时倒计时会暂停',{pauseOnHover:true,duration:8000}); } + function demoCloseOnClick() { MeToast.warning('点击我即可关闭',{closeOnClick:true,duration:10000}); } + function demoWidth() { MeToast.info('宽 550px',{width:550}); setTimeout(()=>MeToast.info('宽 280px(窄)',{width:280}),500); } + function demoClassName() { MeToast.success('带自定义 className',{className:'demo-custom-class'}); } + function demoCustomIcon() { + MeToast.info('通用通知', { iconHTML: '' }); } // === i18n === function switchLocale(loc) { - try{MeToast.i18n.switchLocale(loc);MeToast.configure({locale:loc});MeToast.success(loc==='zh-CN'?'已切换到中文':'Switched to English',{theme:'dark'});} - catch(e){MeToast.info('已切换语言');} + MeToast.i18n.switchLocale(loc); MeToast.configure({locale:loc}); + MeToast.success(loc==='zh-CN'?'已切换到中文':'Switched to English'); } function demoFormat() { - try{ - MeToast.info('数字: '+MeToast.i18n.formatNumber(1234567)+' | 货币: '+MeToast.i18n.formatCurrency(99.5,'CNY')+' | 日期: '+MeToast.i18n.formatDate(new Date()),{duration:8000}); - }catch(e){MeToast.info('格式化: 1234567 → 1,234,567');} + MeToast.info('数字: '+MeToast.i18n.formatNumber(1234567)+' | 货币: '+MeToast.i18n.formatCurrency(99.5,'CNY')+' | 日期: '+MeToast.i18n.formatDate(new Date()),{duration:8000}); } // === 图标网格 === diff --git a/site/docs.html b/site/docs.html index df53864..000e275 100644 --- a/site/docs.html +++ b/site/docs.html @@ -7,499 +7,442 @@ API 文档 — MetonaToast

API 文档

-

MetonaToast v0.5.0 完整 API 参考。所有基础通知方法(show/success/error/warning/info/loading)支持两种调用形式,均可传入任何 配置项 作为可选第二参数。

+

MetonaToast v0.5.0 完整 API 参考。所有基础通知方法支持字符串与对象两种调用形式,均可传入任何配置项作为可选参数。

show(message, opts?)

-

显示默认类型的 Toast 通知,无特定颜色和图标。

- - - - -
参数类型默认值说明
messagestring | object消息字符串,或包含 title/message 等属性的配置对象
optsobject{}可选配置对象,覆盖 全部配置项。仅当 message 为字符串时有效
+

显示默认类型 Toast,无特定颜色与图标。

+
show.js
// 字符串形式
 MeToast.show('默认消息');
-MeToast.show('自定义', { duration: 2000, position: 'bottom-center' });
-// 对象形式(所有 opts 作为一级属性)
-MeToast.show({ title: '标题', message: '内容', duration: 3000 });
+MeToast.show('自定义', { duration: 2000, position: 'bottom-center' }); +// 对象形式 +MeToast.show({ title: '标题', message: '内容', duration: 3000 });

success(message, opts?)

-

显示成功通知。绿色对勾图标 #10b981,type = success。

- - - - -
参数类型默认值说明
messagestring | object消息字符串或配置对象
optsobject{}可选配置覆盖。type 固定为 success
+

绿色对勾图标 #10b981,type = success。

+
success.js
MeToast.success('保存成功!');
 MeToast.success({ title: '已保存', message: '数据已同步' });
-Met.success('MeToast和Met等价');
+Met.success('Met 与 MeToast 等价');

error(message, opts?)

-

显示错误通知。红色叉号图标 #ef4444,type = error。aria-live 设为 assertive。

- - - - -
参数类型默认值说明
messagestring | object消息字符串或配置对象
optsobject{}可选配置覆盖。type 固定为 error
+

红色叉号图标 #ef4444,type = error。aria-live = assertive。

+
error.js
MeToast.error('网络错误,请重试');
-MeToast.error({ title: '提交失败', message: '服务器不可达' });
+MeToast.error({ title: '提交失败', message: '服务器不可达' });

warning(message, opts?)

-

显示警告通知。黄色三角图标 #f59e0b,type = warning。

- - - - -
参数类型默认值说明
messagestring | object消息字符串或配置对象
optsobject{}可选配置覆盖。type 固定为 warning
-
MeToast.warning('请注意检查输入内容');
+

黄色三角图标 #f59e0b,type = warning。

+
warning.js
+
MeToast.warning('请注意检查输入内容');

info(message, opts?)

-

显示信息通知。蓝色圆形图标 #3b82f6,type = info。

- - - - -
参数类型默认值说明
messagestring | object消息字符串或配置对象
optsobject{}可选配置覆盖。type 固定为 info
-
MeToast.info('系统将于 22:00 维护');
+

蓝色圆形图标 #3b82f6,type = info。

+
info.js
+
MeToast.info('系统将于 22:00 维护');

loading(message, opts?)

-

显示加载状态。type = loading,duration 强制为 0(不自动关闭),closeButton 和 showProgress 强制为 false。返回 LoadingControl 对象。

- - - - -
参数类型默认值说明
messagestring | object加载提示文本或配置对象
optsobject{}可选配置覆盖
+

type = loading,duration 强制 0(不自动关闭)。返回 LoadingControl。

+
loading.js
const loading = MeToast.loading('正在提交...');
-setTimeout(() => loading.success('提交成功!'), 2000);
+setTimeout(() => loading.success('提交成功!'), 2000);
-

返回 LoadingControl

+

LoadingControl(链式转换 id 稳定)

- - - - - - - + + + +
方法签名说明
success(msg, opts?) => ToastInstance关闭加载 toast,原地替换为 success 类型
error(msg, opts?) => ToastInstance替换为 error 类型
info(msg, opts?) => ToastInstance替换为 info 类型
warning(msg, opts?) => ToastInstance替换为 warning 类型
update(partial) => MeToast不关闭加载 toast,原地更新其内容(支持 resetTimerOnUpdate)
dismiss()直接关闭加载 toast 不替换
方法说明
success/error/info/warning原地更新为对应类型(同一实例,id 不变),duration 自动恢复默认值
update(partial)原地更新内容,不改变类型
dismiss()直接关闭,不转换

promise(promise, opts)

-

监听 Promise 生命周期。自动显示 loading → 根据 resolve/reject 自动切换 success/error。返回原 Promise,支持 await 获取结果。

+

监听 Promise 生命周期,自动 loading → resolve/reject 切换 success/error,返回原 Promise。

- - - - - + + +
参数类型默认值说明
promisePromise要监听的 Promise 对象。非 Promise 会打印错误并返回 rejected Promise
opts.loadingstring"加载中..."加载中显示的文本
opts.successstring"操作成功"resolve 后显示的文本
opts.errorstring"操作失败"reject 后显示的文本
参数类型默认说明
promisePromise要监听的 Promise。非 Promise 打印错误并返回 rejected Promise
opts.loading / success / errorstring加载中... / 操作成功 / 操作失败各阶段文本
+
promise.js
try {
   await MeToast.promise(fetch('/api/data'), {
-    loading: '加载中...',
-    success: '加载完成!',
-    error: '加载失败',
+    loading: '加载中...', success: '完成!', error: '失败',
   });
-} catch (e) { /* promise reject 会继续抛出 */ }
+} catch (e) { /* reject 会继续抛出 */ }

confirm(message, opts?)

-

确认对话框。返回 Promise<boolean>。内置 10 秒安全超时,超时自动 resolve(false)。

+

确认对话框,返回 Promise<boolean>,内置 10 秒安全超时自动 resolve(false)。

- - - - - - - + + + +
参数类型默认值说明
messagestring对话框消息。非字符串会打印错误并 resolve(false)
opts.confirmTextstring"确认"确认按钮文字
opts.confirmColorstring"#10b981"确认按钮背景色
opts.cancelTextstring"取消"取消按钮文字
opts.cancelColorstring"#6b7280"取消按钮背景色
opts.typestring"warning"Toast 类型(影响图标和颜色)
参数默认说明
confirmText / confirmColor确认 / #10b981确认按钮文字与颜色
cancelText / cancelColor取消 / #6b7280取消按钮文字与颜色
typewarningToast 类型
-
const ok = await MeToast.confirm('确定删除?', {
-  confirmText: '删除',
-  confirmColor: '#ef4444',
-  cancelText: '保留',
-});
-if (ok) MeToast.success('已删除');
+
confirm.js
+
const ok = await MeToast.confirm('确定删除?', { confirmText: '删除', confirmColor: '#ef4444' });
+if (ok) MeToast.success('已删除');

prompt(message, opts?)

-

输入对话框。返回 Promise<string|null>。按 Enter 或点击提交按钮返回输入值,取消返回 null。内置 10 秒安全超时。

+

输入对话框,返回 Promise<string|null>。Enter / 提交返回输入值,取消返回 null,10 秒超时。

- - - - - - - - - - + + + + +
参数类型默认值说明
messagestring对话框消息。非字符串会打印错误并 resolve(null)
opts.placeholderstring""输入框占位文字
opts.defaultValuestring""输入框默认值
opts.inputTypestring"text"input 标签 type 属性(如 password/email/number)
opts.submitTextstring"确认"提交按钮文字
opts.submitColorstring"#3b82f6"提交按钮背景色
opts.cancelTextstring"取消"取消按钮文字
opts.cancelColorstring"#6b7280"取消按钮背景色
opts.typestring"info"Toast 类型
参数默认说明
placeholder / defaultValue""输入框占位与默认值
inputTypetextinput 的 type(password/email/number 等)
submitText / submitColor确认 / #3b82f6提交按钮
cancelText / cancelColor取消 / #6b7280取消按钮
-
const name = await MeToast.prompt('请输入姓名', {
-  placeholder: '请输入...',
-  defaultValue: '张三',
-  submitText: '确定',
-});
-if (name) MeToast.info('你好,' + name);
+
prompt.js
+
const name = await MeToast.prompt('请输入姓名', { placeholder: '请输入...' });
+if (name) MeToast.info('你好,' + name);

progress(message, opts?)

-

进度条通知。不自动关闭。返回 ProgressControl 对象以手动更新进度。

+

进度条通知,不自动关闭。返回 ProgressControl。

- - - - + + + + +
参数类型默认值说明
messagestring | object进度提示文本或配置对象
optsobject{}可选配置。type 默认 info
opts.progressColorstring"#3b82f6"进度条填充颜色
方法说明
setProgress(percent)设置进度 0~100,自动 clamp
complete(message?)跳 100%,300ms 后转 success,1s 后自动关闭
error(message?)转 error,2s 后自动关闭
dismiss()直接关闭
+
progress.js
const p = MeToast.progress('上传中...', { progressColor: '#10b981' });
-p.setProgress(45); // → 45%
-p.setProgress(90); // → 90%
-p.complete('上传完成!'); // → 100% → success
-// 或
-p.error('上传失败');
-p.dismiss();
-
-

返回 ProgressControl

- - - - - - -
方法签名说明
setProgress(percent: number)设置进度 0~100,自动 clamp。更新进度条宽度和百分比文字
complete(message?: string)跳到 100%,300ms 后替换为 success toast,1s 后自动关闭
error(message?: string)替换为 error toast,2s 后自动关闭
dismiss()直接关闭
-
+p.setProgress(45); +p.complete('上传完成!');

countdown(message, seconds, opts?)

-

倒计时 Toast。{seconds} 占位符每秒自动替换为剩余秒数。

+

倒计时 Toast,{seconds} 占位符每秒自动替换。

- - - - - + + +
参数类型默认值说明
messagestring消息文本。支持 {seconds} 占位符。非字符串打印错误并返回空控制对象
secondsnumber10倒计时秒数,最小 1
opts.onCompletefunction倒计时归零时的回调
opts.typestring"warning"Toast 类型
参数默认说明
seconds10倒计时秒数,最小 1
opts.onComplete归零回调
-
MeToast.countdown('{seconds} 秒后执行', 5, {
-  onComplete: () => MeToast.success('已执行'),
-});
-
-

返回 CountdownControl

- - - - - -
方法说明
cancel()清除计时器并关闭 toast
pause()暂停倒计时
resume()恢复倒计时
-
+
countdown.js
+
MeToast.countdown('{seconds} 秒后执行', 5, { onComplete: () => MeToast.success('已执行') });
+// 返回 { cancel(), pause(), resume() }

queue(messages, opts?)

-

顺序逐个显示消息队列。前一条关闭后延时显示下一条。返回 QueueControl(thenable + cancel)。

+

顺序逐个显示消息队列,返回 thenable + cancel。支持 await。

- - - - - - + + +
参数类型默认值说明
messagesArray<string|object>消息数组。可为字符串或带 message/type/duration/onClose 的对象。非数组打印错误并返回空控制对象
opts.delaynumber1000每条消息关闭后到显示下一条的间隔(ms)
opts.durationnumber3000每条消息显示时长(ms),可被消息级 duration 覆盖
opts.onClosefunction全部队列完成后的回调。消息级 onClose 和队列级 onClose 都会依次调用
opts.typestring默认类型
参数默认说明
opts.delay1000每条关闭后到下一条的间隔(ms)
opts.duration3000每条显示时长,可被消息级 duration 覆盖
-
const q = MeToast.queue([
-  '步骤一',
-  { message: '步骤二', duration: 5000, type: 'warning' },
-  '步骤三',
-], { delay: 800, duration: 2000, type: 'info' });
-q.cancel(); // 中途取消
-await q;  // 等待完成(thenable 支持 await)
-
-

返回 QueueControl (thenable)

- - - - - -
方法说明
.then(fn, rj)Promise.then 代理,支持 await
.catch(rj)Promise.catch 代理
.cancel()设置取消标志,不再显示下一条消息
-
+
queue.js
+
const q = MeToast.queue(['步骤一', '步骤二', '步骤三'], { delay: 800, duration: 2000 });
+q.cancel();  // 中途取消
+await q;      // 等待完成

stack(messages, opts?)

-

同时错峰显示多条消息。每条间隔 stagger 毫秒依次出现,全部叠加在屏幕上。

- - - - - -
参数类型默认值说明
messagesArray<string|object>消息数组。可为字符串或带 message/type/duration 的对象。非数组打印错误
opts.staggernumber100每条消息之间的显示间隔(ms)
opts.typestring默认类型
-
MeToast.stack(['消息1', '消息2', { message: '警告', type: 'warning' }], {
-  stagger: 150,
-  type: 'info',
-});
+

同时错峰显示多条消息。

+
stack.js
+
MeToast.stack(['消息1', '消息2', { message: '警告', type: 'warning' }], { stagger: 150 });

action(message, actions, opts?)

-

Action Toast:内嵌操作按钮。默认 duration=0 不自动关闭,closeButton=true。

+

内嵌操作按钮。默认 duration=0 不自动关闭。点击按钮不会误触 closeOnClick(已隔离冒泡)。

- - - - -
参数类型默认值说明
messagestring | object消息字符串或配置对象
actionsActionButton[][]按钮数组。每个按钮可配 text/onClick/color/style/close
optsobject{}可选配置。duration 默认 0,closeButton 默认 true
- - - - - - - + + + + +
ActionButton 字段类型默认值说明
textstring—(必填)按钮显示文字
onClick(toast) => void—(必填)点击回调函数,参数为当前 toast 实例
colorstring"#6366f1"按钮背景色
styleobject{}按钮内联样式,可覆盖 color(style.background 优先)
closebooleantrue点击后是否自动关闭 toast。设为 false 可多次点击
ActionButton默认说明
text—(必填)按钮文字
onClick—(必填)点击回调,参数为当前 toast
color / style#6366f1背景色 / 内联样式(style.background 优先)
closetrue点击后是否关闭。false 可多次点击
+
action.js
MeToast.action('文件已删除', [
   { text: '撤销', onClick: () => restore(), color: '#3b82f6' },
-  { text: '查看详情', onClick: (t) => openFile(), color: '#10b981', close: false },
-]);
+ { text: '查看', onClick: () => open(), color: '#10b981', close: false }, +]);

group(name)

-

创建 Toast 分组,返回 GroupAPI 对象。该对象所有方法自动传入 group: name,按组管理。

- - - -
参数类型说明
namestring分组名称。GroupAPI 和 dismissGroup 通过此名称关联
+

创建分组,返回 GroupAPI。所有方法自动注入 group: name

+
group.js
const orders = MeToast.group('orders');
 orders.success('订单已创建');
-orders.error('支付失败', { duration: 5000 });
-orders.count();   // 该组当前 toast 数量
-orders.dismiss(); // 关闭该组全部 toast
-// 也支持主对象关闭
-MeToast.dismissGroup('orders');
-
-

返回 GroupAPI

- - - - - - - - - - - -
方法说明
show(msg, opts?)等价 MeToast.show,自动注入 group
success(msg, opts?)等价 MeToast.success
error(msg, opts?)等价 MeToast.error
warning(msg, opts?)等价 MeToast.warning
info(msg, opts?)等价 MeToast.info
loading(msg, opts?)等价 MeToast.loading
action(msg, actions, opts?)等价 MeToast.action
dismiss()关闭该组所有 toast
count()返回该组当前 toast 数量
-
+orders.count(); // 该组数量 +orders.dismiss(); // 关闭整组 +MeToast.dismissGroup('orders');

dismiss(id?)

-

关闭 Toast。无参数则关闭全部。

- - - -
参数类型说明
idstring可选。Toast 的 id,不传则关闭所有
-
MeToast.dismiss();          // 关闭所有
-MeToast.dismiss(toast.id);  // 关闭指定
+

关闭 Toast。无参数关闭全部,传入 id 关闭指定。

+
dismiss.js
+
MeToast.dismiss();         // 全部
+MeToast.dismiss(toast.id); // 指定

clear(position?)

-

按位置清除 Toast。不传参数清除所有位置。

- - - -
参数类型说明
positionstring可选。位置如 'top-right',不传则清除全部
-
MeToast.clear();                      // 全部
-MeToast.clear('bottom-right'); // 仅右下角
+

按位置清除。

+
clear.js
+
MeToast.clear();                    // 全部
+MeToast.clear('bottom-right'); // 仅右下角
-

updatePosition(position)

-

运行时将 Toast 移动到新的位置容器。立即生效,不重新播放入场动画。

- - - -
参数类型说明
positionstring目标位置:top-left / top-center / top-right / bottom-left / bottom-center / bottom-right
+

updatePosition(position) v0.3

+

运行时将 Toast 移动到新位置容器,立即生效。

+
position.js
const t = MeToast.info('可移动的 Toast');
-t.updatePosition('bottom-left'); // 移动到左下角
+t.updatePosition('bottom-left');
-

remove() / removeToast(id)

-

立即从 DOM 和内存中移除 Toast,不触发离场动画。适用于需要无动画快速清除的场景(区别于 dismiss() 的优雅关闭)。

- - - - -
方法签名说明
removetoast.remove()Toast 实例方法,立即移除自身
removeToastMeToast.removeToast(id)按 id 立即移除。id 不存在时静默忽略
-
const t = MeToast.warning('临时消息');
-t.remove();                    // 实例方法:立即移除
-MeToast.removeToast(t.id);    // 或按 id 移除(等价)
+

remove() / removeToast(id) 立即移除

+

立即从 DOM 和内存移除,不触发离场动画。适用于无动画快速清除。

+
remove.js
+
t.remove();                  // 实例方法
+MeToast.removeToast(t.id);    // 按 id(等价)

configure(opts)

-

全局配置,影响后续所有 Toast。theme 和 locale 变化会触发相应副作用(应用主题 CSS / 切换语言)。

- - - -
参数类型说明
optsobject包含任意 配置项 的对象
+

全局配置,影响后续所有 Toast。theme/locale 变化触发副作用。

+
configure.js
MeToast.configure({
-  position: 'top-right',
-  duration: 4000,
-  theme: 'dark',
-  animation: 'slide',
-  locale: 'zh-CN',
-});
+ position: 'top-right', duration: 4000, theme: 'dark', + animation: 'slide', locale: 'zh-CN', +});

use(plugin)

-

安装插件。支持字符串(内置预设名)或插件对象。已被拒绝注册的无效插件会打印错误。

- - - -
参数类型说明
pluginstring | object预设名 'keyboard'/'persistence'/'accessibility' 或自定义插件对象 { name, version?, install, uninstall? }
-
MeToast.use('keyboard');      // ESC 关闭所有 Toast
-MeToast.use('persistence');   // 配置自动保存到 localStorage
-MeToast.use('accessibility'); // 屏幕阅读器实时朗读 toast 内容
-MeToast.use('dedupe');        // 相同 type+message 自动去重
-

内置插件详情:keyboard 监听 keydown Escape 键;persistence 将配置写入 localStorage 并在页面加载时恢复;accessibility 在 afterShow/afterUpdate 钩子中通过 aria-live 区域朗读 toast 内容;dedupe 在 beforeShow 钩子中检测相同 type+message 的已有 toast,更新它并阻止重复弹出。

+

安装插件:字符串预设名或插件对象。

+
use.js
+
MeToast.use('keyboard');      // ESC 关闭所有
+MeToast.use('persistence');   // 配置持久化 localStorage
+MeToast.use('accessibility'); // 屏幕阅读器朗读
+MeToast.use('dedupe');        // 相同 type+message 自动去重
+

dedupe:检测已有相同 toast,更新它并阻止重复弹出(beforeShow 钩子实现,支持 uninstall 卸载钩子)。

destroy()

-

完全销毁。关闭所有 Toast、移除所有 DOM 容器和注入的样式标签、清空内存缓存。

-
MeToast.destroy();
+

完全销毁:关闭所有 Toast、移除全部 DOM 容器与注入样式、清空内存缓存与钩子。支持重复调用,init() 可恢复。

+
destroy.js
+
MeToast.destroy();
+MeToast.init({ config: { duration: 3000 } });  // 恢复
- -

React 适配器

-

主包保持零依赖。React 适配器通过子路径 @metona-team/metona-toast/react 导入,react 为 optional peerDependency,未使用 React 的项目不受影响。

-
// 安装
-npm install @metona-team/metona-toast react
+
+

钩子系统 v0.3

+

Toast.on(name, handler) 注册钩子并返回取消函数。beforeShow / beforeClose / beforeUpdate 的 handler 返回 false 可拦截对应操作。

+ + + + + + + + + + + + + +
钩子触发时机拦截
beforeInit / afterInitinit() 前后
beforeDestroy / afterDestroydestroy() 前后
beforeShow / afterShow显示前后✅ 返回 false 阻止
beforeClose / afterClose关闭前后✅ 返回 false 阻止
beforeUpdate / afterUpdateupdate() 前后✅ 返回 false 阻止
configChangeconfigure / updateConfig / resetConfig
themeChange / localeChange主题 / 语言切换
click / hover点击 / 悬停进出
dragStart / dragEnd拖拽开始 / 结束
animationStart / animationEnd入场动画开始 / 结束
progressStart / progressEnd倒计时开始 / 归零
+
hooks.js
+
import MeToast, { Toast } from '@metona-team/metona-toast';
 
-// useToast — 组件卸载时自动移除本组件创建的 Toast
-import { useToast, Toast } from '@metona-team/metona-toast/react';
+// 拦截:非允许时段禁止错误提示
+const off = Toast.on('beforeShow', (toast) => {
+  if (toast.type === 'error' && !isAllowed) return false;
+});
+// ...
+off();  // 取消注册
+ + +

React 适配器 v0.4

+

主包保持零依赖。通过子路径 @metona-team/metona-toast/react 导入,react 为 optional peerDependency。

+
react.tsx
+
import { useToast, Toast } from '@metona-team/metona-toast/react';
 
 function SubmitButton() {
-  const toast = useToast();
+  const toast = useToast();  // 组件卸载自动清理本组件创建的 Toast
   const submit = async () => {
     const loading = toast.loading('正在提交...');
-    try { await api(); loading.success('提交成功!'); }
-    catch (e) { loading.error('提交失败'); }
+    try { await api(); loading.success('完成!'); }
+    catch (e) { loading.error('失败'); }
   };
   return <button onClick={submit}>提交</button>;
 }
 
-// Toast 组件 — props 变化时更新,卸载时自动移除(autoClose 默认 true)
+// 声明式 Toast:props 变化更新,卸载自动移除(autoClose 默认 true)
 function SaveIndicator({ saving }) {
   return saving ? <Toast type="info" message="正在保存..." /> : null;
-}
+}

全部配置项

-

以下配置可用于 configure()init() 或单个 Toast 方法的 opts 参数。

+

可用于 configure()init() 或单个 Toast 方法的 opts。

- - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
配置项类型默认值说明
positionstring'top-right'位置:top-left / top-center / top-right / bottom-left / bottom-center / bottom-right
durationnumber4000显示时长(ms),0=不自动关闭
maxnumber6同一位置最多同时显示条数,超出则关闭最早的
gapnumber12Toast 之间的间距(px)
offsetnumber24容器到屏幕边缘的距离(px)
pauseOnHoverbooleantrue鼠标悬停时暂停 duration 倒计时和进度条
closeOnClickbooleantrue点击 Toast 任意位置关闭。关闭按钮始终触发关闭
draggablebooleantrue允许拖拽关闭。拖拽超过 dragThreshold 触发关闭
dragThresholdnumber120拖拽关闭阈值(px),位移超过该值松手即关闭
showProgressbooleantrue显示 duration 倒计时进度条
progressDirectionstring'horizontal'进度条方向:horizontal(底部水平) / vertical(右侧垂直)
iconbooleantrue显示类型对应图标(success→对勾等)
closeButtonbooleantrue显示右上角关闭 × 按钮
themestring'auto'主题:light / dark / auto(跟随系统)/ warm / 自定义注册名
animationstring'slide'入场动画名称,支持 CSS 动画列表见下
zIndexnumber9999容器 CSS z-index
widthnumber|string360Toast 宽度。数字表示 px
classNamestring''附加到 Toast 元素上的 CSS 类名
styleobject{}附加到 Toast 元素上的内联样式对象
localestring'zh-CN'语言代码(zh-CN / en-US 等)
resetTimerOnUpdatebooleanfalse调用 update() 时重置 duration 倒计时
notifyWhenHiddenbooleanfalse页面不可见时自动通过 Notification API 发送系统通知
renderfunction自定义渲染函数 (toast) => htmlString,完全接管 DOM 构建
onBeforeShowfunction显示前回调 (toast) => boolean | void,返回 false 可阻止该 Toast 显示
onErrorfunction全局错误回调 ({ hook, source, error, toast }) => void,钩子异常或定时器错误时触发
positionstring'top-right'6 个位置之一(RTL 自动翻转)
durationnumber4000显示时长(ms),0 = 不自动关闭
maxnumber6同位置最多条数,超出真正关闭最早的
gapnumber12Toast 间距(px)
offsetnumber24容器距屏幕边缘(px)
pauseOnHoverbooleantrue悬停暂停倒计时
closeOnClickbooleantrue点击关闭
draggablebooleantrue允许拖拽关闭
dragThresholdnumber120拖拽关闭阈值(px)
showProgressbooleantrue显示倒计时进度条
progressDirectionstring'horizontal'horizontal / vertical
iconbooleantrue显示类型图标
closeButtonbooleantrue显示关闭 × 按钮
themestring'auto'light / dark / auto / warm / 自定义名
animationstring'slide'11 种内置或自定义动画名
zIndexnumber9999容器 z-index
widthnumber|string360宽度,数字表示 px
classNamestring''附加 CSS 类名
styleobject{}附加内联样式
localestring'zh-CN'语言代码
resetTimerOnUpdatebooleanfalseupdate() 时重置倒计时
notifyWhenHiddenbooleanfalse页面不可见时发系统通知
renderfunction自定义渲染函数,完全接管 DOM
onBeforeShowfunction返回 false 阻止显示
onErrorfunction钩子/定时器异常全局回调

回调函数

- - - - - - + + + + + +
回调签名触发时机
onBeforeShow(toast: ToastInstance) => boolean | voidToast DOM 创建前。返回 false 阻止显示
onShow(toast: ToastInstance) => voidToast DOM 创建并播放入场动画后
onClose(toast: ToastInstance) => voidToast DOM 被移除后(离场动画完成时)
onClick(toast: ToastInstance) => voidToast 被点击时(closeOnClick 为 true 时还会自动关闭)
onUpdate(toast: ToastInstance) => voidToast 内容通过 update() 更新后
onError({ hook, source, error, toast }) => void钩子回调或定时器发生异常时(全局配置,适用于接入错误监控)
onBeforeShow(toast) => boolean | voidDOM 创建前,返回 false 阻止显示
onShow(toast) => void创建并播放入场动画后
onClose(toast) => voidDOM 移除后(离场完成)
onClick(toast) => void点击时(closeOnClick=true 时还会关闭)
onUpdate(toast) => voidupdate() 后
onError({ hook, source, error, toast }) => void钩子/定时器异常时

动画列表

-

配置 animation 的值,支持以下 CSS 动画。未在此列表中的值将 fallback 到 slide。

+

未注册的动画名自动 fallback 到 slide。

@@ -509,20 +452,13 @@ npm install @metona-team/metona-toast react - - - - + +
名称效果时长
slide从右侧滑入 + 回弹400ms
flip3D 翻转入场 + 回摆500ms
rotate旋转摇摆进入500ms
zoom从中心爆发式弹出500ms
slideUp从下方弹入400ms
slideDown从上方弹入400ms
slideLeft从左侧滑入400ms
slideRight从右侧滑入400ms
slideUp / slideDown从下方 / 上方弹入400ms
slideLeft / slideRight从左侧 / 右侧滑入400ms

主题参考

-
// 内置主题
-light   — 白色玻璃质感
-dark    — 深色玻璃质感
-auto    — 跟随系统主题设置
-warm    — 暖色调
-
-// 注册自定义主题
+
theme.js
+
// 内置主题:light · dark · auto(跟随系统)· warm
 MeToast.themes.registerTheme('ocean', {
   bg: 'rgba(240,249,255,0.96)',
   text: '#0c4a6e',
@@ -532,19 +468,38 @@ MeToast.themes.registerTheme('ocean'progressBg: 'rgba(14,165,233,0.1)',
   closeHoverBg: 'rgba(14,165,233,0.1)',
 });
-MeToast.themes.switchTheme('ocean');
+MeToast.themes.switchTheme('ocean');
- \ No newline at end of file + diff --git a/site/index.html b/site/index.html index 3ed1976..2b56480 100644 --- a/site/index.html +++ b/site/index.html @@ -5,62 +5,231 @@ MetonaToast — 轻量精致的 Toast 通知库 - + @@ -68,7 +237,7 @@
-

MetonaToast

-

轻量 · 零依赖 · 精致美观的 Toast 通知库。TypeScript 严格模式源码,gzip 不到 10KB。

-
- 📦 <10KB gzip🎨 107 图标🎭 11 动画🌍 国际化🔌 插件📘 TypeScript +
+
+
+
v0.5.0 · 385 测试 · 覆盖率 96%
+

MetonaToast

+

轻量 · 零依赖 · 精致美观的 Toast 通知库。TypeScript 严格模式,gzip 不到 10KB,一切开箱即用。

+
+ 📦 <10KB gzip + 🎨 107 图标 + 🎭 11 动画 + 🧩 4 插件 + 🔗 17 钩子 + ⚛️ React 适配器 + 🌍 国际化
- - + + +
+
+
+
<10KB
gzip 体积
+
107
内置图标
+
11
动画效果
+
17
生命周期钩子
+
385
测试用例
+
96%
行覆盖率
+
+
+
-

核心特性

-

一切你需要的,都在一个不到 10KB 的包中

+

核心特性

+

你需要的一切,都在一个不到 10KB 的包中

+
-
+
+
+

零依赖

+

TypeScript 严格模式,纯原生实现,无任何运行时依赖。兼容所有现代浏览器。

+
+
🎨

107 种图标类型

-

success / error / warning / info / loading 以及 100+ 扩展类型,覆盖所有常见场景。

+

success / error / warning / info / loading 及 100+ 扩展类型,覆盖所有常见场景。

-
+
🎭
-

11 种动画效果

-

slide / fade / scale / bounce / flip / rotate / zoom / slideUp / slideDown / slideLeft / slideRight,每种感官差异明显。

+

11 种动画 + 自定义

+

每种感官差异明显,animations.register() 注册自定义动画,动态注入 keyframes 真实生效。

-
+
🖌️

主题系统

-

内置 light / dark / auto / warm 四种主题,支持 registerTheme() 自定义注册任意主题。

+

light / dark / auto / warm 内置主题,registerTheme() 自定义,跟随系统自动切换。

-
+
🌍

国际化

-

内置 zh-CN 和 en-US 完整翻译,400+ 词汇。支持 addTranslations() 扩展任意语言。

+

zh-CN / en-US 完整翻译,addTranslations() 扩展任意语言,RTL 完整适配。

-
-
🔌
+
+
🧩

插件系统

-

3 款预设插件 + 自定义插件架构。keyboard / persistence / accessibility 开箱即用。

+

4 款预设:keyboard / persistence / accessibility / dedupe,支持自定义插件架构。

-
+
+
🔗
+

17 个钩子

+

完整生命周期钩子,beforeShow / beforeClose / beforeUpdate 支持拦截。

+
+

拖拽关闭

-

任意方向拖动 Toast 即可关闭。支持 Pointer Events,移动端和桌面端体验一致。

+

任意方向拖动即关,阈值 dragThreshold 可配置。Pointer Events 全端一致。

-
-
-

零依赖

-

TypeScript 严格模式,纯原生实现,无需任何运行时依赖。兼容所有现代浏览器。

+
+
⚛️
+

React 适配器

+

@metona-team/metona-toast/react — useToast hook + 声明式 Toast 组件,主包保持零依赖。

-
+
🛡️

错误捕获

-

全局 onError 回调可捕获钩子异常和定时器错误,方便接入 Sentry、DataDog 等监控系统。

+

全局 onError 回调捕获钩子与定时器异常,方便接入 Sentry、DataDog。

-
+
⏱️

暂停恢复

-

悬停自动暂停、拖拽临时暂停、手动 pause/resume,剩余时间精确保持,不丢失进度。

+

悬停暂停、拖拽暂停、手动 pause/resume,剩余时间精确保持不丢失。

-
+
📘
-

TypeScript

-

严格模式源码,类型从源码自动生成。strictNullChecks、noImplicitAny 全开,零类型漂移。

+

类型安全

+

严格模式源码,类型自动生成零漂移。加载态转换 id 稳定,原地更新。

-
-

环境支持

-

所有现代浏览器原生支持,无需 polyfill

-
-
-
🌐
-

Chrome

-

64+

-
-
-
🦊
-

Firefox

-

63+

-
-
-
🧭
-

Safari

-

11+

-
-
-
🪟
-

Edge

-

79+

-
-
-
📦
-

Node.js

-

16+

-
-
-
📘
-

TypeScript

-

4.5+

-
-
-

核心依赖:Web Animations API · Pointer Events · CSS Grid · CSS Custom Properties

-
- -
-

快速开始

-

浏览器 window 上仅注册两个全局变量:MeToastMet,完全等价

-
-
# npm 安装
+
+

快速开始

+

浏览器上仅注册两个全局变量:MeToastMet,完全等价

+
+
+
+ + quickstart.js +
+
# 安装
 npm install @metona-team/metona-toast
 
 // 导入
@@ -202,43 +371,45 @@ MeToast.loading('加载中...');
 
 // CDN 直接引用
 <script src="https://git.metona.cn/MetonaTeam/MetonaToast/raw/branch/master/dist/metona-toast.min.js"></script>
+
-
-

高级功能速览

-

不仅仅是一个 Toast——确认框、输入框、进度条、倒计时、队列,应有尽有

-
-
// Promise 风格 — 自动 loading → success/error
+
+

高级功能速览

+

不仅仅是 Toast — 确认框、输入框、进度条、倒计时、队列、Action,应有尽有

+
+
+
+ + advanced.js +
+
// Promise 风格 — 自动 loading → success/error
 await MeToast.promise(fetch('/api/data'), {
-  loading: '加载中...',
-  success: '加载完成!',
-  error: '加载失败',
+  loading: '加载中...', success: '完成', error: '失败'
 });
 
-// 确认对话框 → Promise<boolean>
-const ok = await MeToast.confirm('确定删除吗?', { confirmText: '删除' });
-
-// 输入对话框 → Promise<string|null>
+// 确认 / 输入对话框
+const ok = await MeToast.confirm('确定删除?', { confirmText: '删除' });
 const name = await MeToast.prompt('请输入姓名');
 
 // Action Toast — 内嵌操作按钮
 MeToast.action('文件已删除', [
   { text: '撤销', onClick: () => restore() },
-  { text: '查看', onClick: () => open() },
+  { text: '查看', onClick: () => open(), close: false },
 ]);
 
-// Toast 分组管理
-const orderGroup = MeToast.group('orders');
-orderGroup.success('订单已创建');
-orderGroup.dismiss(); // 一键关闭整组
+// 加载 → 链式转换(id 稳定) +const loading = MeToast.loading('正在提交...'); +setTimeout(() => loading.success('提交成功!'), 2000);
+
-
-

主题切换

-

点击按钮实时预览四种内置主题

-
+
+

主题切换

+

点击按钮实时预览内置主题,切换结果自动持久化

+
@@ -246,8 +417,34 @@ orderGroup.dismiss(); // 一键关闭
+
+

环境支持

+

所有现代浏览器原生支持,无需 polyfill

+
+
🌐

Chrome

64+

+
🦊

Firefox

63+

+
🧭

Safari

11+

+
🪟

Edge

79+

+
📱

iOS Safari

11+

+
🤖

Android

64+

+
📦

Node.js

16+

+
📘

TypeScript

4.5+

+
+
+ +
+
+

开始使用 MetonaToast

+

一条命令安装,立即体验精致优雅的 Toast 通知

+ +
+
+
-

MetonaToast v0.5.0 · MIT License · Gitea

+ MetonaToast v0.5.0 · MIT License · + Gitea · + 文档 · + 演示
@@ -256,16 +453,28 @@ orderGroup.dismiss(); // 一键关闭 function demoQuickStart() { MeToast.success('✨ 欢迎使用 MetonaToast!'); - setTimeout(() => MeToast.info('💡 试试点击右上角的 Toast 来关闭它'), 800); - setTimeout(() => MeToast.warning('⚠️ 你也可以拖动 Toast 来关闭'), 1600); + setTimeout(() => MeToast.info('💡 点击 Toast 或点击 × 即可关闭'), 800); + setTimeout(() => MeToast.warning('⚠️ 拖动 Toast 也能关闭', { draggable: true }), 1600); + setTimeout(() => MeToast.success('🎭 试试 bounce 动画', { animation: 'bounce' }), 2400); } function switchTheme(t) { MeToast.themes.switchTheme(t); MeToast.configure({ theme: t }); + document.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active')); + event.target.classList.add('active'); const names = { light: '浅色', dark: '深色', auto: '自动', warm: '暖色' }; MeToast.info('已切换到「' + names[t] + '」主题', { theme: t }); } + + // 滚动淡入 + const observer = new IntersectionObserver(entries => { + entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('visible'); observer.unobserve(e.target); } }); + }, { threshold: 0.12 }); + document.querySelectorAll('.reveal').forEach(el => observer.observe(el)); + + // 页面加载自动演示 + setTimeout(demoQuickStart, 600);