docs: 重写 README 与 site 站点 — 精美酷炫全新视觉

- 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 渲染、图标网格/卡片/代码块完整
This commit is contained in:
tianhao
2026-08-08 15:11:04 +08:00
parent 1f718c4ef8
commit dd061025e0
4 changed files with 1019 additions and 688 deletions
+110 -44
View File
@@ -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) [![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) [![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) [![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 - **零依赖** — TypeScript 严格模式源码,gzip 后不到 10KB,无任何运行时依赖
- **107 种图标类型** — success/error/warning/info/loading 及更多扩展类型,覆盖所有常见场景 - **107 种图标类型** — success/error/warning/info/loading 及 100+ 扩展类型,覆盖所有常见场景
- **11 种动画** — slide/fade/scale/bounce/flip/rotate/zoom/slideUp/slideDown/slideLeft/slideRight每种效果明显不同 - **11 种动画 + 自定义** — slide/fade/scale/bounce/flip/rotate/zoom/slideUp/slideDown/slideLeft/slideRight`animations.register()` 可注册任意自定义动画(动态注入 keyframes,真实生效)
- **主题系统** — light/dark/auto/warm 四种内置主题,支持 `registerTheme()` 注册自定义主题 - **主题系统** — light/dark/auto/warm 四种内置主题,`registerTheme()` 注册自定义主题,跟随系统主题自动切换
- **国际化** — 内置 zh-CN / en-US 完整翻译(去重优化),可通过 `addTranslations()` 扩展 - **国际化** — 内置 zh-CN / en-US 完整翻译`addTranslations()` 扩展任意语言,RTL 语言完整适配
- **插件系统** — 4 款预设插件 (keyboard/persistence/accessibility/dedupe) + 自定义插件 - **插件系统** — 4 款预设插件 (keyboard / persistence / accessibility / dedupe) + 自定义插件
- **可拖拽关闭** — 拖动 Toast 任意方向即可关闭,阈值可配置 - **完整钩子系统** — 17 个生命周期与事件钩子,`beforeShow` / `beforeClose` / `beforeUpdate` 支持返回 `false` 拦截
- **TypeScript** — 严格模式,源码级类型安全,类型从源码自动生成无手动维护 - **可拖拽关闭** — 拖动 Toast 任意方向即可关闭,阈值 `dragThreshold` 可配置
- **全局错误回调** — `onError` 捕获所有钩子异常和定时器错误,方便接入监控系统 - **React 适配器** — `@metona-team/metona-toast/react` 子路径导入,`useToast()` hook + 声明式 `<Toast />` 组件,主包保持零依赖
- **全局错误回调** — `onError` 捕获所有钩子异常和定时器错误,方便接入 Sentry、DataDog 等监控系统
- **工程质量** — 385 个测试全过,行覆盖率 96.42%,lint 零警告,CI 含覆盖率门禁(≥95%)
## 安装 ## 安装
@@ -59,13 +62,13 @@ const MeToast = require('@metona-team/metona-toast');
| iOS Safari | 11+ | | iOS Safari | 11+ |
| Android Chrome | 64+ | | Android Chrome | 64+ |
| Node.js | 16+ESM & CJS 双格式) | | Node.js | 16+ESM & CJS 双格式) |
| TypeScript | 4.5+(完整类型声明) | | TypeScript | 4.5+(完整类型声明,类型从源码自动生成 |
核心依赖:Web Animations APIChrome 64+, Firefox 63+, Safari 11+)、Pointer Events、CSS Grid、CSS Custom Properties。 核心依赖:Web Animations APIChrome 64+, Firefox 63+, Safari 11+)、Pointer Events、CSS Grid、CSS Custom Properties。
## 快速开始 ## 快速开始
浏览器 `window`注册两个全局变量:`MeToast``Met`两者完全等价,任选其一使用 浏览器 `window` 上注册两个全局变量:`MeToast``Met`,完全等价。
```javascript ```javascript
import MeToast from '@metona-team/metona-toast'; import MeToast from '@metona-team/metona-toast';
@@ -82,13 +85,11 @@ MeToast.info('系统维护中');
// 带标题 // 带标题
MeToast.success({ title: '保存成功', message: '文件已同步到云端' }); MeToast.success({ title: '保存成功', message: '文件已同步到云端' });
// 加载 → 成功链式转换(原地更新同一实例,id 稳定) // 加载 → 链式转换(原地更新同一实例,id 稳定)
const loading = MeToast.loading('正在提交...'); const loading = MeToast.loading('正在提交...');
const toast = await wait(); setTimeout(() => loading.success('提交成功!'), 2000);
loading.success('提交成功!');
// toast.id === loading.id
// Promise 风格 // Promise 风格 — 自动 loading → success/error
await MeToast.promise(fetch('/api/data'), { await MeToast.promise(fetch('/api/data'), {
loading: '加载中...', loading: '加载中...',
success: '加载完成!', success: '加载完成!',
@@ -102,17 +103,22 @@ await MeToast.promise(fetch('/api/data'), {
MeToast.configure({ MeToast.configure({
position: 'top-right', // top-left | top-center | top-right | bottom-left | bottom-center | bottom-right position: 'top-right', // top-left | top-center | top-right | bottom-left | bottom-center | bottom-right
duration: 4000, // 显示时长(ms),0 为不自动关闭 duration: 4000, // 显示时长(ms),0 为不自动关闭
max: 6, // 同最多显示条数 max: 6, // 同一位置最多显示条数,超出关闭最早的
theme: 'auto', // light | dark | auto | warm gap: 12, // Toast 间距(px)
animation: 'slide', // slide | fade | scale | bounce | flip | rotate | zoom | slideUp | slideDown | slideLeft | slideRight offset: 24, // 容器距屏幕边缘(px)
theme: 'auto', // light | dark | auto(跟随系统)| warm
animation: 'slide', // 11 种内置动画或自定义动画名
pauseOnHover: true, // 悬停暂停计时 pauseOnHover: true, // 悬停暂停计时
closeOnClick: true, // 点击关闭 closeOnClick: true, // 点击关闭
draggable: true, // 允许拖拽关闭 draggable: true, // 允许拖拽关闭
dragThreshold: 120, // 拖拽关闭阈值(px) dragThreshold: 120, // 拖拽关闭阈值(px)
showProgress: true, // 显示进度条 showProgress: true, // 显示进度条
draggable: true, // 允许拖拽关闭 progressDirection: 'horizontal', // horizontal | vertical
zIndex: 9999,
width: 360,
locale: 'zh-CN', // 语言 locale: 'zh-CN', // 语言
onError: ({ hook, error, toast }) => { // 全局错误回调(钩子/定时器异常) onError: ({ hook, source, error, toast }) => {
// 钩子/定时器异常全局捕获,可接入监控系统
console.error('Toast error:', hook, error); console.error('Toast error:', hook, error);
}, },
}); });
@@ -120,40 +126,42 @@ MeToast.configure({
## 动画效果 ## 动画效果
11 种动画,每种感官差异明显: 11 种内置动画,每种感官差异明显:
| 动画 | 效果描述 | | 动画 | 效果描述 | 时长 |
|------|----------| |------|----------|------|
| `slide` | 从右侧滑入 + 轻微过冲回弹 | | `slide` | 从右侧滑入 + 轻微过冲回弹 | 400ms |
| `fade` | 纯淡入(blur→清晰),从容优雅 | | `fade` | 纯淡入(blur→清晰),从容优雅 | 500ms |
| `scale` | 弹性放大(0.55→1.07→1.0 | | `scale` | 弹性放大(0.55→1.07→1.0 | 450ms |
| `bounce` | 从天而降四段弹跳 | | `bounce` | 从天而降四段弹跳 | 650ms |
| `flip` | 3D 翻转入场 + 回摆 | | `flip` | 3D 翻转入场 + 回摆 | 500ms |
| `rotate` | 旋转摇摆进入 | | `rotate` | 旋转摇摆进入 | 500ms |
| `zoom` | 从中心爆发式弹出 | | `zoom` | 从中心爆发式弹出 | 500ms |
| `slideUp` | 从下方弹入 | | `slideUp` | 从下方弹入 | 400ms |
| `slideDown` | 从上方弹入 | | `slideDown` | 从上方弹入 | 400ms |
| `slideLeft` | 从左侧滑入 | | `slideLeft` | 从左侧滑入 | 400ms |
| `slideRight` | 从右侧滑入 | | `slideRight` | 从右侧滑入 | 400ms |
```javascript ```javascript
MeToast.success('弹跳动画', { animation: 'bounce' }); MeToast.success('弹跳动画', { animation: 'bounce' });
// 注册自定义动画 // 注册自定义动画 — 动态注入 keyframes,真实生效
MeToast.animations.register('myAnim', { MeToast.animations.register('myAnim', {
enter: { transform: 'rotate(-30deg) scale(0.5)', opacity: 0 }, enter: { transform: 'rotate(-30deg) scale(0.5)', opacity: 0 },
leave: { transform: 'rotate(0) scale(1)', opacity: 1 }, leave: { transform: 'rotate(0) scale(1)', opacity: 1 },
duration: 500, duration: 500,
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)', easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
}); });
MeToast.success('自定义动画', { animation: 'myAnim' });
``` ```
## 主题 ## 主题
```javascript ```javascript
// 切换主题 // 切换主题(自动持久化到 localStorage
MeToast.themes.switchTheme('dark'); MeToast.themes.switchTheme('dark');
MeToast.themes.switchTheme('warm'); MeToast.themes.switchTheme('warm');
MeToast.themes.toggleTheme(); // light ↔ dark
// 注册自定义主题 // 注册自定义主题
MeToast.themes.registerTheme('ocean', { MeToast.themes.registerTheme('ocean', {
@@ -192,6 +200,18 @@ await MeToast.queue(['步骤一', '步骤二', '步骤三'], { delay: 800 });
// 堆叠展示(同时错峰) // 堆叠展示(同时错峰)
MeToast.stack(['消息1', '消息2', '消息3'], { stagger: 150 }); 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('accessibility'); // 屏幕阅读器朗读公告
MeToast.use('dedupe'); // 相同 type+message 自动去重(更新已有实例) MeToast.use('dedupe'); // 相同 type+message 自动去重(更新已有实例)
// 自定义插件(通过 install 中注册 Toast 生命周期钩子) // 自定义插件(install 中注册生命周期钩子)
import MeToast, { Toast } from '@metona-team/metona-toast'; import MeToast, { Toast } from '@metona-team/metona-toast';
MeToast.use({ MeToast.use({
@@ -213,6 +233,36 @@ MeToast.use({
console.log('Toast shown:', toast.message); 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 ```javascript
MeToast.i18n.switchLocale('en-US'); MeToast.i18n.switchLocale('en-US');
// 添加语言 // 添加语言(嵌套结构支持深度合并)
MeToast.i18n.addTranslations('ja', { MeToast.i18n.addTranslations('ja', {
success: '成功', success: '成功',
error: 'エラー', error: 'エラー',
@@ -229,9 +279,11 @@ MeToast.i18n.addTranslations('ja', {
cancel: 'キャンセル', cancel: 'キャンセル',
}); });
// 格式化工具 // 格式化工具(基于 Intl
MeToast.i18n.formatNumber(1234567); // "1,234,567" MeToast.i18n.formatNumber(1234567); // "1,234,567"
MeToast.i18n.formatCurrency(99, 'CNY'); // "¥99.00" MeToast.i18n.formatCurrency(99, 'CNY'); // "¥99.00"
MeToast.i18n.formatDate(new Date());
MeToast.i18n.formatRelativeTime(Date.now() + 3600000); // "1 小时后"
``` ```
## Toast 管理 ## Toast 管理
@@ -240,11 +292,17 @@ MeToast.i18n.formatCurrency(99, 'CNY'); // "¥99.00"
MeToast.count(); // 当前数量 MeToast.count(); // 当前数量
MeToast.getAll(); // 获取原生 Map<id, ToastInstance> MeToast.getAll(); // 获取原生 Map<id, ToastInstance>
MeToast.getToasts(); // 获取所有实例数组 MeToast.getToasts(); // 获取所有实例数组
MeToast.find(id); // 查找实例(不存在返回 undefined)
MeToast.findByType(type); // 按类型过滤
MeToast.findByPosition(pos);// 按位置过滤
MeToast.dismiss(); // 关闭所有 MeToast.dismiss(); // 关闭所有
MeToast.dismiss(id); // 关闭指定 MeToast.dismiss(id); // 关闭指定
MeToast.removeToast(id); // 立即移除(无离场动画)
MeToast.clear(position?); // 按位置清除
MeToast.pauseAll(); // 暂停所有计时 MeToast.pauseAll(); // 暂停所有计时
MeToast.resumeAll(); // 恢复所有计时 MeToast.resumeAll(); // 恢复所有计时
MeToast.destroy(); // 完全销毁(支持重复调用) MeToast.updateAll(partial); // 批量更新
MeToast.destroy(); // 完全销毁(支持重复调用,init() 可恢复)
``` ```
## 回调 ## 回调
@@ -252,6 +310,7 @@ MeToast.destroy(); // 完全销毁(支持重复调用)
```javascript ```javascript
MeToast.success({ MeToast.success({
message: '操作成功', message: '操作成功',
onBeforeShow: (toast) => true, // 返回 false 阻止显示
onShow: (toast) => console.log('显示:', toast.id), onShow: (toast) => console.log('显示:', toast.id),
onClose: (toast) => console.log('关闭:', toast.id), onClose: (toast) => console.log('关闭:', toast.id),
onClick: (toast) => console.log('点击:', toast.id), onClick: (toast) => console.log('点击:', toast.id),
@@ -261,7 +320,6 @@ MeToast.success({
// 全局错误回调 — 捕获钩子异常和定时器错误 // 全局错误回调 — 捕获钩子异常和定时器错误
MeToast.configure({ MeToast.configure({
onError: ({ hook, source, error, toast }) => { onError: ({ hook, source, error, toast }) => {
// 接入监控系统 (Sentry, DataDog 等)
reportError(error, { hook, source, toastId: toast?.id }); reportError(error, { hook, source, toastId: toast?.id });
}, },
}); });
@@ -269,7 +327,7 @@ MeToast.configure({
## React 适配器 ## React 适配器
主包保持零依赖;React 适配器通过子路径导入,`react` 为 optional peerDependency。 主包保持零依赖;React 适配器通过子路径导入,`react` 为 optional peerDependency,未使用 React 的项目不受影响
```bash ```bash
npm install @metona-team/metona-toast react npm install @metona-team/metona-toast react
@@ -295,12 +353,20 @@ function SubmitButton() {
return <button onClick={submit}></button>; return <button onClick={submit}></button>;
} }
// 声明式 Toast — props 变化时更新,组件卸载时自动移除 // 声明式 Toast — props 变化时更新内容,组件卸载时自动移除autoClose 默认 true
function SaveIndicator({ saving }: { saving: boolean }) { function SaveIndicator({ saving }: { saving: boolean }) {
return saving ? <Toast type="info" message="正在保存..." /> : null; return saving ? <Toast type="info" message="正在保存..." /> : 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 ## License
MIT MIT
+215 -114
View File
@@ -7,51 +7,78 @@
<title>在线演示 — MetonaToast</title> <title>在线演示 — MetonaToast</title>
<style> <style>
:root { :root {
--bg: #0b1121; --surface: #151d33; --border: #1e2d4d; --bg: #070b14; --surface: rgba(255,255,255,0.03); --surface2: rgba(255,255,255,0.05);
--text: #e2e8f0; --muted: #64748b; --accent: #6366f1; --accent2: #818cf8; --border: rgba(255,255,255,0.08); --text: #e8ecf4; --muted: #8b94a7;
--accent: #a5b4fc; --grad: linear-gradient(135deg, #818cf8, #c084fc, #f472b6);
--radius: 14px; --radius: 14px;
} }
* { margin:0; padding:0; box-sizing:border-box; } * { margin:0; padding:0; box-sizing:border-box; }
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif; background:var(--bg); color:var(--text); line-height:1.6; min-height:100vh; } body {
a { color:var(--accent2); text-decoration:none; } font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;
code { background:rgba(99,102,241,0.12); color:#a5b4fc; padding:1px 6px; border-radius:4px; font-size:0.85em; } background:var(--bg); color:var(--text); line-height:1.65; min-height:100vh;
.nav { position:sticky; top:0; z-index:100; background:rgba(11,17,33,0.88); backdrop-filter:blur(14px); border-bottom:1px solid var(--border); } background-image:
.nav-inner { max-width:1300px; margin:0 auto; display:flex; align-items:center; justify-content:space-between; padding:0 20px; height:56px; } radial-gradient(ellipse 70% 45% at 50% -10%, rgba(99,102,241,0.12), transparent),
.nav-logo { font-size:18px; font-weight:700; background:linear-gradient(135deg,#818cf8,#c084fc,#f472b6); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; } radial-gradient(ellipse 50% 40% at 90% 20%, rgba(168,85,247,0.07), transparent);
.nav-links { display:flex; gap:20px; } }
.nav-links a { color:var(--muted); font-size:13px; font-weight:500; transition:.2s; } a { color:var(--accent); text-decoration:none; }
.nav-links a:hover { color:var(--accent2); } code { background:rgba(99,102,241,0.15); color:#a5b4fc; padding:1px 6px; border-radius:5px; font-size:.85em; }
.hero { text-align:center; padding:40px 20px 30px; border-bottom:1px solid var(--border); background:radial-gradient(ellipse at 50% 0%,rgba(99,102,241,0.12) 0%,transparent 50%); } .grad-text { background:var(--grad); -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; color:transparent; }
.hero h1 { font-size:36px; font-weight:800; }
.hero h1 span { background:linear-gradient(135deg,#818cf8,#c084fc,#f472b6); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; } .nav { position:sticky; top:0; z-index:100; background:rgba(7,11,20,0.72); backdrop-filter:blur(16px); border-bottom:1px solid var(--border); }
.nav-inner { max-width:1320px; margin:0 auto; display:flex; align-items:center; justify-content:space-between; padding:0 22px; height:58px; }
.nav-logo { font-size:18px; font-weight:800; }
.nav-links { display:flex; gap:22px; }
.nav-links a { color:var(--muted); font-size:13.5px; font-weight:500; transition:.2s; }
.nav-links a:hover { color:var(--accent); }
.hero { text-align:center; padding:38px 20px 28px; }
.hero h1 { font-size:34px; font-weight:800; }
.hero p { color:var(--muted); font-size:14px; margin-top:6px; } .hero p { color:var(--muted); font-size:14px; margin-top:6px; }
.container { max-width:1300px; margin:0 auto; padding:24px 20px; display:grid; grid-template-columns:repeat(auto-fill,minmax(400px,1fr)); gap:18px; } .container { max-width:1320px; margin:0 auto; padding:20px; display:grid; grid-template-columns:repeat(auto-fill,minmax(400px,1fr)); gap:16px; }
.card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:20px; } .card {
.card h2 { font-size:15px; margin-bottom:2px; } background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
.card > p { color:var(--muted); font-size:11px; margin-bottom:12px; } padding:20px; transition:border-color .2s, transform .2s; position:relative; overflow:hidden;
}
.card:hover { border-color:rgba(129,140,248,.35); }
.card::before {
content:''; position:absolute; top:0; left:0; right:0; height:2px;
background:var(--grad); opacity:0; transition:opacity .25s;
}
.card:hover::before { opacity:.8; }
.card h2 { font-size:14.5px; margin-bottom:2px; font-weight:700; }
.card > p { color:var(--muted); font-size:11.5px; margin-bottom:12px; }
.btn-row { display:flex; flex-wrap:wrap; gap:6px; } .btn-row { display:flex; flex-wrap:wrap; gap:6px; }
.btn { padding:8px 14px; border:none; border-radius:7px; font-size:12px; font-weight:500; cursor:pointer; transition:.15s; color:#fff; } .btn {
.btn:hover { transform:translateY(-1px); filter:brightness(1.15); } padding:8px 14px; border:none; border-radius:8px; font-size:12px; font-weight:600;
.btn-sm { padding:5px 10px; font-size:11px; border-radius:5px; } cursor:pointer; transition:transform .15s, filter .15s, box-shadow .15s; color:#fff;
}
.btn:hover { transform:translateY(-1.5px); filter:brightness(1.15); box-shadow:0 6px 18px -6px rgba(0,0,0,.5); }
.btn-sm { padding:5.5px 11px; font-size:11px; border-radius:6px; }
.btn-tiny { padding:4px 9px; font-size:10px; border-radius:5px; }
.btn-outline { background:transparent; border:1px solid var(--border); color:var(--text); } .btn-outline { background:transparent; border:1px solid var(--border); color:var(--text); }
.btn-outline:hover { border-color:var(--accent); background:rgba(99,102,241,0.06); } .btn-outline:hover { border-color:rgba(129,140,248,.5); background:rgba(129,140,248,.08); }
.btn-tiny { padding:4px 9px; font-size:10px; border-radius:4px; }
.c-s { background:#10b981; } .c-e { background:#ef4444; } .c-w { background:#f59e0b; color:#1e293b; } .c-s { background:#10b981; } .c-e { background:#ef4444; } .c-w { background:#f59e0b; color:#1e293b; }
.c-i { background:#3b82f6; } .c-p { background:#6366f1; } .c-pp { background:#8b5cf6; } .c-i { background:#3b82f6; } .c-p { background:#6366f1; } .c-pp { background:#8b5cf6; }
.c-pk { background:#ec4899; } .c-t { background:#14b8a6; } .c-o { background:#f97316; } .c-pk { background:#ec4899; } .c-t { background:#14b8a6; } .c-o { background:#f97316; }
.c-sl { background:#475569; } .c-r { background:#f43f5e; } .c-g { background:#22c55e; } .c-sl { background:#475569; } .c-r { background:#f43f5e; } .c-g { background:#22c55e; }
.label { font-size:10px; text-transform:uppercase; letter-spacing:0.8px; color:var(--muted); margin-bottom:10px; font-weight:600; } .c-grad { background:var(--grad); }
.demo-section { border-top:1px solid var(--border); padding-top:8px; margin-top:8px; } .label {
font-size:10px; text-transform:uppercase; letter-spacing:1px; color:var(--muted);
margin-bottom:10px; font-weight:700;
}
.demo-section { border-top:1px solid var(--border); padding-top:10px; margin-top:10px; }
.status-line { font-size:11.5px; color:#34d399; margin-top:10px; font-family:monospace; min-height:18px; }
footer { text-align:center; padding:30px 20px; color:var(--muted); font-size:12px; border-top:1px solid var(--border); } footer { text-align:center; padding:30px 20px; color:var(--muted); font-size:12px; border-top:1px solid var(--border); }
@media (max-width:768px) { .container { grid-template-columns:1fr; } }
@media (max-width:820px) { .container { grid-template-columns:1fr; } }
</style> </style>
</head> </head>
<body> <body>
<nav class="nav"> <nav class="nav">
<div class="nav-inner"> <div class="nav-inner">
<div class="nav-logo">MetonaToast</div> <div class="nav-logo"><span class="grad-text">MetonaToast</span></div>
<div class="nav-links"> <div class="nav-links">
<a href="index.html">首页</a> <a href="index.html">首页</a>
<a href="docs.html">文档</a> <a href="docs.html">文档</a>
@@ -62,7 +89,7 @@
</nav> </nav>
<header class="hero"> <header class="hero">
<h1><span>在线演示</span></h1> <h1><span class="grad-text">在线演示</span></h1>
<p>覆盖项目全部功能的交互演示 — 点击即可体验</p> <p>覆盖项目全部功能的交互演示 — 点击即可体验</p>
</header> </header>
@@ -81,10 +108,10 @@
<div class="demo-section"> <div class="demo-section">
<div class="btn-row"> <div class="btn-row">
<button class="btn btn-outline btn-sm" onclick="MeToast.success({title:'保存成功',message:'文件已同步到云端'})">带标题</button> <button class="btn btn-outline btn-sm" onclick="MeToast.success({title:'保存成功',message:'文件已同步到云端'})">带标题</button>
<button class="btn btn-outline btn-sm" onclick="MeToast.info('持续10秒',{duration:10000})">长时10s</button> <button class="btn btn-outline btn-sm" onclick="MeToast.info('持续 10 秒',{duration:10000})">长时 10s</button>
<button class="btn btn-outline btn-sm" onclick="MeToast.warning('手动关闭',{duration:0,closeButton:true})">手动关闭</button> <button class="btn btn-outline btn-sm" onclick="MeToast.warning('手动关闭',{duration:0,closeButton:true})">手动关闭</button>
<button class="btn btn-outline btn-sm" onclick="MeToast.info('',{icon:false})">无图标</button> <button class="btn btn-outline btn-sm" onclick="MeToast.info('',{icon:false})">无图标</button>
<button class="btn btn-outline btn-sm" onclick="MeToast.info('无按钮',{closeButton:false})">关闭按钮</button> <button class="btn btn-outline btn-sm" onclick="MeToast.info('无关闭按钮',{closeButton:false})">无按钮</button>
<button class="btn btn-outline btn-sm" onclick="demoCustomIcon()">自定义图标</button> <button class="btn btn-outline btn-sm" onclick="demoCustomIcon()">自定义图标</button>
</div> </div>
</div> </div>
@@ -92,16 +119,17 @@
<!-- 2. Loading 链式 --> <!-- 2. Loading 链式 -->
<div class="card"> <div class="card">
<div class="label">🔄 Loading 链式转换</div> <div class="label">🔄 Loading 链式转换 <span style="color:#34d399">id 稳定</span></div>
<div class="btn-row"> <div class="btn-row">
<button class="btn c-s" onclick="demoLoading()">提交成功</button> <button class="btn c-s" onclick="demoLoading('success')">提交成功</button>
<button class="btn c-e" onclick="demoLoadingFail()">提交失败</button> <button class="btn c-e" onclick="demoLoading('error')">提交失败</button>
<button class="btn c-w" onclick="demoLoadingWarn()">转为警告</button> <button class="btn c-w" onclick="demoLoading('warning')">转为警告</button>
<button class="btn c-i" onclick="demoLoadingUpdate()">原地更新</button> <button class="btn c-i" onclick="demoLoading('update')">原地更新</button>
</div> </div>
<p class="status-line" id="loading-status">转换后返回的 toast 与 loading 是同一个实例(id 不变)</p>
</div> </div>
<!-- 3. Promise + confirm + prompt --> <!-- 3. 对话框 -->
<div class="card"> <div class="card">
<div class="label">💬 对话框</div> <div class="label">💬 对话框</div>
<div class="btn-row"> <div class="btn-row">
@@ -116,8 +144,8 @@
<div class="card"> <div class="card">
<div class="label">📊 进度 & 倒计时</div> <div class="label">📊 进度 & 倒计时</div>
<div class="btn-row"> <div class="btn-row">
<button class="btn c-o" onclick="demoProgress()">进度条(水平)</button> <button class="btn c-o" onclick="demoProgress('horizontal')">进度条(水平)</button>
<button class="btn c-g" onclick="demoProgressV()">进度条(垂直)</button> <button class="btn c-g" onclick="demoProgress('vertical')">进度条(垂直)</button>
<button class="btn c-pk" onclick="demoCountdown()">倒计时 5s</button> <button class="btn c-pk" onclick="demoCountdown()">倒计时 5s</button>
<button class="btn c-r" onclick="demoCountdownPause()">倒计时(可暂停)</button> <button class="btn c-r" onclick="demoCountdownPause()">倒计时(可暂停)</button>
</div> </div>
@@ -136,7 +164,7 @@
<!-- 6. Action Toast --> <!-- 6. Action Toast -->
<div class="card"> <div class="card">
<div class="label">⚡ Action Toast</div> <div class="label">⚡ Action Toast <span style="color:#34d399">close:false 不关闭</span></div>
<div class="btn-row"> <div class="btn-row">
<button class="btn c-e" onclick="demoActionDelete()">删除+撤销</button> <button class="btn c-e" onclick="demoActionDelete()">删除+撤销</button>
<button class="btn c-i" onclick="demoActionMulti()">多按钮</button> <button class="btn c-i" onclick="demoActionMulti()">多按钮</button>
@@ -146,7 +174,7 @@
<!-- 7. 分组管理 --> <!-- 7. 分组管理 -->
<div class="card"> <div class="card">
<div class="label">📁 分组管理</div> <div class="label">📁 分组管理</div>
<div class="btn-row"> <div class="btn-row">
<button class="btn c-p" onclick="demoGroup()">创建分组消息</button> <button class="btn c-p" onclick="demoGroup()">创建分组消息</button>
<button class="btn c-e" onclick="dismissGroup()">关闭分组</button> <button class="btn c-e" onclick="dismissGroup()">关闭分组</button>
@@ -157,7 +185,7 @@
<!-- 8. 动画效果 --> <!-- 8. 动画效果 -->
<div class="card"> <div class="card">
<div class="label">🎭 11种动画效果</div> <div class="label">🎭 11 种动画效果</div>
<div class="btn-row"> <div class="btn-row">
<button class="btn btn-outline btn-tiny" onclick="anim('slide')">slide</button> <button class="btn btn-outline btn-tiny" onclick="anim('slide')">slide</button>
<button class="btn btn-outline btn-tiny" onclick="anim('fade')">fade</button> <button class="btn btn-outline btn-tiny" onclick="anim('fade')">fade</button>
@@ -171,9 +199,14 @@
<button class="btn btn-outline btn-tiny" onclick="anim('slideLeft')">slideLeft</button> <button class="btn btn-outline btn-tiny" onclick="anim('slideLeft')">slideLeft</button>
<button class="btn btn-outline btn-tiny" onclick="anim('slideRight')">slideRight</button> <button class="btn btn-outline btn-tiny" onclick="anim('slideRight')">slideRight</button>
</div> </div>
<div class="demo-section">
<div class="btn-row">
<button class="btn c-grad" onclick="demoCustomAnim()">✨ 注册自定义动画</button>
</div>
</div>
</div> </div>
<!-- 9. 6种位置 --> <!-- 9. 位置 -->
<div class="card"> <div class="card">
<div class="label">📍 全部位置</div> <div class="label">📍 全部位置</div>
<div class="btn-row"> <div class="btn-row">
@@ -186,7 +219,7 @@
</div> </div>
</div> </div>
<!-- 10. 主题切换 + 自定义 --> <!-- 10. 主题 -->
<div class="card"> <div class="card">
<div class="label">🎨 主题切换 & 自定义</div> <div class="label">🎨 主题切换 & 自定义</div>
<div class="btn-row"> <div class="btn-row">
@@ -194,24 +227,43 @@
<button class="btn btn-outline btn-sm" onclick="switchTheme('dark')">🌙 Dark</button> <button class="btn btn-outline btn-sm" onclick="switchTheme('dark')">🌙 Dark</button>
<button class="btn btn-outline btn-sm" onclick="switchTheme('auto')">🔄 Auto</button> <button class="btn btn-outline btn-sm" onclick="switchTheme('auto')">🔄 Auto</button>
<button class="btn btn-outline btn-sm" onclick="switchTheme('warm')">🔥 Warm</button> <button class="btn btn-outline btn-sm" onclick="switchTheme('warm')">🔥 Warm</button>
<button class="btn btn-outline btn-sm" onclick="demoRegisterTheme()">注册自定义主题</button> <button class="btn btn-outline btn-sm" onclick="demoRegisterTheme()">注册自定义</button>
</div> </div>
</div> </div>
<!-- 11. 插件系统 --> <!-- 11. 插件 -->
<div class="card"> <div class="card">
<div class="label">🔌 插件系统</div> <div class="label">🔌 插件系统 <span style="color:#34d399">4 款预设</span></div>
<div class="btn-row"> <div class="btn-row">
<button class="btn btn-outline btn-sm" onclick="MeToast.use('keyboard');MeToast.info('已安装 — 按 ESC 关闭所有 Toast')">keyboard ESC关闭</button> <button class="btn btn-outline btn-sm" onclick="MeToast.use('keyboard');MeToast.info('已安装 — 按 ESC 关闭所有 Toast')">keyboard</button>
<button class="btn btn-outline btn-sm" onclick="MeToast.use('persistence');MeToast.info('已安装 — 配置持久化到 localStorage')">persistence 持久化</button> <button class="btn btn-outline btn-sm" onclick="MeToast.use('persistence');MeToast.info('已安装 — 配置持久化')">persistence</button>
<button class="btn btn-outline btn-sm" onclick="MeToast.use('accessibility');MeToast.info('已安装 — 屏幕阅读器可用')">accessibility 无障碍</button> <button class="btn btn-outline btn-sm" onclick="MeToast.use('accessibility');MeToast.info('已安装 — 屏幕阅读器')">accessibility</button>
<button class="btn btn-outline btn-sm" onclick="MeToast.use('dedupe');MeToast.info('已安装 — 同消息去重')">dedupe</button>
<button class="btn btn-outline btn-sm" onclick="demoCustomPlugin()">自定义插件</button> <button class="btn btn-outline btn-sm" onclick="demoCustomPlugin()">自定义插件</button>
</div> </div>
<div class="demo-section">
<div class="btn-row">
<button class="btn c-t btn-sm" onclick="demoDedupe()">去重演示</button>
</div>
</div>
</div> </div>
<!-- 12. 高级特性 --> <!-- 12. 钩子系统 -->
<div class="card"> <div class="card">
<div class="label">🔥 高级特性 <span style="color:#34d399;font-size:10px;">resetTimer · onUpdate · render · notify</span></div> <div class="label">🔗 钩子系统 <span style="color:#34d399">beforeShow 拦截演示</span></div>
<div class="btn-row">
<button class="btn c-p" onclick="demoHookBlock()">开启拦截 error</button>
<button class="btn c-e" onclick="MeToast.error('这条会被拦截')">触发被拦 error</button>
<button class="btn c-s" onclick="MeToast.success('这条不受影响')">正常 success</button>
<button class="btn btn-outline btn-sm" onclick="demoHookUnblock()">关闭拦截</button>
<button class="btn btn-outline btn-sm" onclick="demoHookCount()">钩子统计</button>
</div>
<p class="status-line" id="hook-status">钩子:未注册</p>
</div>
<!-- 13. 高级特性 -->
<div class="card">
<div class="label">🔥 高级特性</div>
<div class="btn-row"> <div class="btn-row">
<button class="btn c-i" onclick="demoResetTimer()">resetTimerOnUpdate</button> <button class="btn c-i" onclick="demoResetTimer()">resetTimerOnUpdate</button>
<button class="btn c-p" onclick="demoOnUpdate()">onUpdate 回调</button> <button class="btn c-p" onclick="demoOnUpdate()">onUpdate 回调</button>
@@ -220,7 +272,7 @@
</div> </div>
</div> </div>
<!-- 13. Toast 管理 --> <!-- 14. Toast 管理 -->
<div class="card"> <div class="card">
<div class="label">🔧 Toast 管理</div> <div class="label">🔧 Toast 管理</div>
<div class="btn-row"> <div class="btn-row">
@@ -230,7 +282,7 @@
<button class="btn c-sl" onclick="MeToast.pauseAll();MeToast.info('已暂停全部')">暂停全部</button> <button class="btn c-sl" onclick="MeToast.pauseAll();MeToast.info('已暂停全部')">暂停全部</button>
<button class="btn c-sl" onclick="MeToast.resumeAll();MeToast.info('已恢复全部')">恢复全部</button> <button class="btn c-sl" onclick="MeToast.resumeAll();MeToast.info('已恢复全部')">恢复全部</button>
<button class="btn c-e" onclick="MeToast.dismiss()">关闭全部</button> <button class="btn c-e" onclick="MeToast.dismiss()">关闭全部</button>
<button class="btn c-r" onclick="MeToast.destroy();MeToast.info('已销毁')">销毁</button> <button class="btn c-r" onclick="MeToast.destroy();MeToast.init({});MeToast.info('已销毁并重建')">销毁重建</button>
</div> </div>
<div class="demo-section"> <div class="demo-section">
<div class="btn-row"> <div class="btn-row">
@@ -238,32 +290,34 @@
<button class="btn btn-outline btn-tiny" onclick="demoGetAll()">getAll</button> <button class="btn btn-outline btn-tiny" onclick="demoGetAll()">getAll</button>
<button class="btn btn-outline btn-tiny" onclick="demoClearByPos()">clear(位置)</button> <button class="btn btn-outline btn-tiny" onclick="demoClearByPos()">clear(位置)</button>
<button class="btn btn-outline btn-tiny" onclick="demoGetConfig()">查看配置</button> <button class="btn btn-outline btn-tiny" onclick="demoGetConfig()">查看配置</button>
<button class="btn btn-outline btn-tiny" onclick="demoRemove()">removeToast 立即移除</button>
</div> </div>
</div> </div>
</div> </div>
<!-- 14. 错误回调 --> <!-- 15. onError -->
<div class="card"> <div class="card">
<div class="label">🛡️ onError 错误捕获 <span style="color:#34d399;font-size:10px;">v0.5.0</span></div> <div class="label">🛡️ onError 错误捕获</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>
</div> </div>
</div> </div>
<!-- 15. 交互特性 --> <!-- 16. 交互特性 -->
<div class="card"> <div class="card">
<div class="label">✋ 交互特性</div> <div class="label">✋ 交互特性</div>
<div class="btn-row"> <div class="btn-row">
<button class="btn c-i" onclick="demoDraggable()">拖拽关闭</button> <button class="btn c-i" onclick="demoDraggable()">拖拽关闭</button>
<button class="btn c-p" onclick="demoPauseHover()">悬停暂停</button> <button class="btn c-p" onclick="demoDragThreshold()">拖拽阈值 300px</button>
<button class="btn c-w" onclick="demoCloseOnClick()">点击关闭</button> <button class="btn c-w" onclick="demoPauseHover()">悬停暂停</button>
<button class="btn c-pp" onclick="demoWidth()">自定义宽度</button> <button class="btn c-pp" onclick="demoCloseOnClick()">点击关闭</button>
<button class="btn c-t" onclick="demoClassName()">自定义className</button> <button class="btn c-t" onclick="demoWidth()">自定义宽度</button>
<button class="btn c-sl" onclick="demoClassName()">自定义 className</button>
</div> </div>
</div> </div>
<!-- 16. 国际化切换 --> <!-- 17. 国际化 -->
<div class="card"> <div class="card">
<div class="label">🌍 国际化 & 格式化</div> <div class="label">🌍 国际化 & 格式化</div>
<div class="btn-row"> <div class="btn-row">
@@ -273,16 +327,19 @@
</div> </div>
</div> </div>
<!-- 17. 107种预设图标 --> <!-- 18. 图标网格 -->
<div class="card" style="grid-column:1/-1;"> <div class="card" style="grid-column:1/-1;">
<div class="label">🎨 107种预设图标 — 点击任意图标发送Toast</div> <div class="label">🎨 107 种预设图标 — 点击任意图标发送 Toast</div>
<div class="btn-row" id="icon-grid" style="max-height:300px;overflow-y:auto;padding:4px 0;"></div> <div class="btn-row" id="icon-grid" style="max-height:320px;overflow-y:auto;padding:4px 0;"></div>
</div> </div>
</main> </main>
<footer> <footer>
MetonaToast v0.5.0 · MIT · <a href="index.html">首页</a> · <a href="docs.html">文档</a> · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a> MetonaToast v0.5.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>
@@ -290,27 +347,41 @@
MeToast.configure({ position:'top-right', duration:4000, theme:'dark', animation:'slide', pauseOnHover:true, showProgress:true }); MeToast.configure({ position:'top-right', duration:4000, theme:'dark', animation:'slide', pauseOnHover:true, showProgress:true });
// === Loading === // === Loading ===
function demoLoading() { const l=MeToast.loading('正在提交...'); setTimeout(()=>l.success('提交成功!'),2000); } function demoLoading(mode) {
function demoLoadingFail() { const l=MeToast.loading('正在处理...'); setTimeout(()=>l.error('处理失败'),2000); } const l = MeToast.loading('正在处理...');
function demoLoadingWarn() { const l=MeToast.loading('检查中...'); setTimeout(()=>l.warning('发现潜在问题'),2000); } const showId = () => document.getElementById('loading-status').textContent = 'loading id: ' + l.id;
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); } 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 === // === Promise / confirm / prompt ===
async function demoPromise() { async function demoPromise() {
const p=new Promise((r,e)=>setTimeout(()=>Math.random()>0.3?r('ok'):e('fail'),2000)); 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(_){} 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 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 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('密码已输入'); } async function demoPromptPassword() { const v=await MeToast.prompt('请输入密码',{placeholder:'密码',inputType:'password',submitText:'登录'}); if(v) MeToast.info('密码已输入'); }
// === Progress & Countdown === // === 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 demoProgress(dir) {
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); } 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 demoCountdown() { MeToast.countdown('{seconds} 秒后执行',5,{onComplete:()=>MeToast.success('已执行')}); }
function demoCountdownPause() { function demoCountdownPause() {
const c=MeToast.countdown('{seconds}s 可暂停',10,{type:'info',onComplete:()=>MeToast.success('倒计时结束')}); 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); setTimeout(()=>{c.resume();MeToast.info('已恢复倒计时')},7000);
} }
@@ -321,14 +392,15 @@
setTimeout(()=>{q.cancel();MeToast.warning('队列已取消',{duration:2000});},3500); setTimeout(()=>{q.cancel();MeToast.warning('队列已取消',{duration:2000});},3500);
} }
function demoStack() { MeToast.stack(['消息1','消息2','消息3','消息4','消息5'],{stagger:120,type:'info'}); } function demoStack() { MeToast.stack(['消息1','消息2','消息3','消息4','消息5'],{stagger:120,type:'info'}); }
function demoStackMixed() { function demoStackMixed() { MeToast.stack(['普通消息',{message:'警告消息',type:'warning',duration:5000},{message:'错误消息',type:'error'}],{stagger:150}); }
MeToast.stack(['普通消息',{message:'警告消息',type:'warning',duration:5000},{message:'错误消息',type:'error'}],{stagger:150});
}
// === Action === // === Action ===
function demoActionDelete() { MeToast.action('文件 data.json 已删除',[{text:'撤销',onClick:()=>MeToast.success('已撤销'),color:'#3b82f6'}]); } 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 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 === // === Group ===
const group = MeToast.group('demo-group'); const group = MeToast.group('demo-group');
@@ -342,32 +414,71 @@
// === Animation === // === Animation ===
function anim(name) { MeToast.success('动画: '+name,{animation:name,duration:3000,showProgress:false}); } 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 === // === Theme ===
function switchTheme(t) { MeToast.themes.switchTheme(t); MeToast.configure({theme:t}); MeToast.info('主题: '+t,{theme:t}); } function switchTheme(t) { MeToast.themes.switchTheme(t); MeToast.configure({theme:t}); MeToast.info('主题: '+t,{theme:t}); }
function demoRegisterTheme() { function demoRegisterTheme() {
try { 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.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.themes.switchTheme('sunset'); MeToast.configure({theme:'sunset'});
MeToast.configure({theme:'sunset'});
MeToast.success('已切换到 sunset 主题',{theme:'sunset'}); MeToast.success('已切换到 sunset 主题',{theme:'sunset'});
} catch(_) { MeToast.warning('主题已注册,已切换'); } } catch(_) { MeToast.warning('主题已注册,已切换'); }
} }
// === Plugins === // === Plugins ===
function demoCustomPlugin() { 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('自定义插件已安装(消息后追加计数)'); 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() { function demoResetTimer() {
const t=MeToast.info('这条消息不会自动消失(resetTimer',{duration:3000,resetTimerOnUpdate:true,closeButton:true}); 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+' — 每次更新重置3秒'});},1000); 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() { function demoCustomRender() {
MeToast.show({render:toast=>'<div style="background:#111;color:#0f0;padding:16px 20px;border-radius:10px;border:1px solid #0f0;font-family:monospace">🟢 <b>'+toast.message+'</b><br><span style="font-size:11px;opacity:0.6"># custom render by thzxx</span></div>',message:'自定义渲染内容',duration:5000}); MeToast.show({render:toast=>'<div style="background:#111;color:#0f0;padding:16px 20px;border-radius:10px;border:1px solid #0f0;font-family:monospace">🟢 <b>'+toast.message+'</b><br><span style="font-size:11px;opacity:0.6"># custom render</span></div>',message:'自定义渲染内容',duration:5000});
} }
function demoNotify() { function demoNotify() {
if(typeof Notification==='undefined'){MeToast.error('浏览器不支持 Notification');return;} if(typeof Notification==='undefined'){MeToast.error('浏览器不支持 Notification');return;}
@@ -382,48 +493,38 @@
} }
function demoGetAll() { const all=MeToast.getAll(); MeToast.info('当前共 '+all.size+' 条 toast'); } 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 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 方法');} } 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); }
// === 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: '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/></svg>'
});
}
// === onError === // === onError ===
function demoOnError() { function demoOnError() {
MeToast.configure({ MeToast.configure({ onError: function(info) { MeToast.error('⚠️ 捕获: ' + (info.hook || info.source), { duration: 3000, position: 'bottom-center' }); } });
onError: function(info) { MeToast.info('已启用 onError 监控', { duration: 3000 });
MeToast.error('⚠️ 捕获错误: ' + (info.hook || info.source), { duration: 3000, position: 'bottom-center' });
}
});
MeToast.info('已启用 onError 监控(钩子/定时器异常将被捕获)', { duration: 3000 });
// 触发一个钩子错误来演示
var badFn = function() { throw new Error('demo-error'); }; var badFn = function() { throw new Error('demo-error'); };
Toast.on('afterShow', badFn); Toast.on('afterShow', badFn);
MeToast.success('这条消息会触发错误', { duration: 2000 }); MeToast.success('这条会触发错误回调', { duration: 2000 });
setTimeout(function() { Toast.off('afterShow', badFn); }, 3000); setTimeout(function() { Toast.off('afterShow', badFn); }, 3000);
} }
function demoOnErrorReset() { function demoOnErrorReset() { MeToast.configure({ onError: null }); MeToast.info('onError 已重置'); }
MeToast.configure({ onError: null });
MeToast.info('onError 已重置为 null'); // === 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: '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/></svg>' });
} }
// === i18n === // === i18n ===
function switchLocale(loc) { function switchLocale(loc) {
try{MeToast.i18n.switchLocale(loc);MeToast.configure({locale:loc});MeToast.success(loc==='zh-CN'?'已切换到中文':'Switched to English',{theme:'dark'});} MeToast.i18n.switchLocale(loc); MeToast.configure({locale:loc});
catch(e){MeToast.info('已切换语言');} MeToast.success(loc==='zh-CN'?'已切换到中文':'Switched to English');
} }
function demoFormat() { function demoFormat() {
try{ MeToast.info('数字: '+MeToast.i18n.formatNumber(1234567)+' | 货币: '+MeToast.i18n.formatCurrency(99.5,'CNY')+' | 日期: '+MeToast.i18n.formatDate(new Date()),{duration:8000});
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');}
} }
// === 图标网格 === // === 图标网格 ===
+339 -384
View File
@@ -7,499 +7,442 @@
<title>API 文档 — MetonaToast</title> <title>API 文档 — MetonaToast</title>
<style> <style>
:root { :root {
--bg: #0b1121; --surface: #1a2332; --border: #1e2d4d; --bg: #070b14; --surface: rgba(255,255,255,0.03); --border: rgba(255,255,255,0.08);
--text: #e2e8f0; --muted: #64748b; --accent: #6366f1; --accent2: #818cf8; --text: #e8ecf4; --muted: #8b94a7; --accent: #a5b4fc; --accent2: #c7d2fe;
--radius: 10px; --grad: linear-gradient(135deg, #818cf8, #c084fc, #f472b6);
--radius: 12px;
} }
* { margin:0; padding:0; box-sizing:border-box; } * { margin:0; padding:0; box-sizing:border-box; }
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif; background:var(--bg); color:var(--text); line-height:1.7; display:flex; min-height:100vh; } html { scroll-behavior:smooth; }
code { background:rgba(99,102,241,0.12); color:#a5b4fc; padding:1px 6px; border-radius:4px; font-size:0.9em; white-space:nowrap; } body {
pre { background:#0a0f1a; border:1px solid var(--border); border-radius:8px; padding:14px 18px; overflow-x:auto; font:13px/1.7 'SF Mono','Cascadia Code','Fira Code',Consolas,monospace; color:#cbd5e1; margin:10px 0; } font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;
pre .kw { color:#c084fc; } pre .fn { color:#60a5fa; } pre .s { color:#34d399; } pre .cm { color:#475569; } background:var(--bg); color:var(--text); line-height:1.75; display:flex; min-height:100vh;
a { color:var(--accent2); text-decoration:none; } background-image:
radial-gradient(ellipse 70% 40% at 80% -10%, rgba(99,102,241,0.12), transparent),
radial-gradient(ellipse 50% 40% at 0% 20%, rgba(236,72,153,0.05), transparent);
}
code {
background:rgba(99,102,241,0.15); color:#a5b4fc; padding:1px 6px;
border-radius:5px; font-size:.88em; white-space:nowrap;
}
a { color:var(--accent); text-decoration:none; }
a:hover { color:var(--accent2); }
.sidebar { width:260px; min-width:260px; background:var(--surface); border-right:1px solid var(--border); padding:24px 0; position:sticky; top:0; height:100vh; overflow-y:auto; } /* ===== 侧边栏 ===== */
.sidebar h2 { font-size:15px; padding:0 24px; margin-bottom:16px; color:var(--accent2); } .sidebar {
.sidebar a { display:block; padding:7px 24px; font-size:13px; color:var(--muted); transition:.15s; border-left:2px solid transparent; } width:264px; min-width:264px; background:rgba(10,15,26,.8);
.sidebar a:hover, .sidebar a.active { color:var(--accent2); border-left-color:var(--accent); background:rgba(99,102,241,0.06); } border-right:1px solid var(--border); padding:22px 0;
.sidebar .section { font-size:11px; text-transform:uppercase; letter-spacing:1px; padding:16px 24px 6px; color:var(--muted); font-weight:600; } position:sticky; top:0; height:100vh; overflow-y:auto;
backdrop-filter:blur(14px);
}
.sidebar::-webkit-scrollbar { width:5px; }
.sidebar::-webkit-scrollbar-thumb { background:rgba(255,255,255,.12); border-radius:3px; }
.sidebar-logo { font-size:16px; font-weight:800; padding:0 24px; margin-bottom:4px; }
.grad-text { background:var(--grad); -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; color:transparent; }
.sidebar .back { display:block; font-size:12.5px; padding:6px 24px; color:var(--muted); }
.sidebar .back:hover { color:var(--accent2); }
.sidebar a.snav {
display:block; padding:6.5px 24px; font-size:13px; color:var(--muted);
transition:.15s; border-left:2px solid transparent;
}
.sidebar a.snav:hover { color:var(--accent2); }
.sidebar a.snav.active { color:var(--accent2); border-left-color:#a855f7; background:rgba(139,92,246,.08); }
.sidebar .section {
font-size:11px; text-transform:uppercase; letter-spacing:1.2px;
padding:18px 24px 6px; color:var(--muted); font-weight:700; opacity:.75;
}
.main { flex:1; padding:40px 48px; max-width:950px; } /* ===== 主内容 ===== */
.main h1 { font-size:30px; font-weight:700; margin-bottom:8px; } .main { flex:1; padding:44px 52px; max-width:980px; min-width:0; }
.main h2 { font-size:22px; font-weight:700; margin:44px 0 14px; padding-top:22px; border-top:1px solid var(--border); } .main h1 { font-size:32px; font-weight:800; margin-bottom:6px; letter-spacing:-.5px; }
.main h3 { font-size:17px; font-weight:600; margin:28px 0 10px; color:var(--accent2); } .main .h1-desc { color:var(--muted); font-size:14px; margin-bottom:8px; }
.main h2 {
font-size:22px; font-weight:700; margin:52px 0 12px; padding-top:22px;
border-top:1px solid var(--border); scroll-margin-top:24px;
}
.main h2::before { content:'#'; margin-right:8px; background:var(--grad); -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; }
.main h3 { font-size:16.5px; font-weight:700; margin:26px 0 10px; color:var(--accent2); }
.main p { margin:8px 0; color:var(--muted); font-size:14px; } .main p { margin:8px 0; color:var(--muted); font-size:14px; }
.main table { width:100%; border-collapse:collapse; margin:14px 0 20px; font-size:13px; } .main p code { font-size:.9em; }
.main table { width:100%; border-collapse:collapse; margin:14px 0 22px; font-size:13px; }
.main th, .main td { text-align:left; padding:9px 14px; border-bottom:1px solid var(--border); vertical-align:top; } .main th, .main td { text-align:left; padding:9px 14px; border-bottom:1px solid var(--border); vertical-align:top; }
.main th { color:var(--muted); font-weight:500; font-size:11px; text-transform:uppercase; letter-spacing:0.5px; } .main th { color:var(--muted); font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:.8px; }
.main td:first-child { color:var(--accent2); font-family:monospace; font-size:12px; white-space:nowrap; } .main td:first-child { color:var(--accent); font-family:monospace; font-size:12px; white-space:nowrap; }
.main td:nth-child(3) { font-family:monospace; font-size:11px; color:var(--muted); } .main td:nth-child(3) { font-family:monospace; font-size:11.5px; color:var(--muted); }
.badge { display:inline-block; padding:1px 7px; border-radius:3px; font-size:10px; font-weight:700; margin-left:6px; vertical-align:middle; } .badge {
.badge-new { background:rgba(16,185,129,0.2); color:#34d399; } display:inline-block; padding:1px 8px; border-radius:4px; font-size:10px;
.hint { font-size:12px; color:var(--muted); margin-top:2px; } font-weight:700; margin-left:6px; vertical-align:middle;
.return-box { background:rgba(16,185,129,0.08); border:1px solid rgba(16,185,129,0.2); border-radius:8px; padding:14px 18px; margin:12px 0; } }
.return-box h4 { font-size:12px; color:#34d399; margin-bottom:6px; text-transform:uppercase; letter-spacing:0.5px; } .badge-new { background:rgba(16,185,129,.18); color:#34d399; border:1px solid rgba(16,185,129,.3); }
.badge-v5 { background:rgba(168,85,247,.2); color:#d8b4fe; border:1px solid rgba(168,85,247,.4); }
.hint { font-size:12.5px; color:var(--muted); margin-top:2px; }
.return-box {
background:rgba(16,185,129,.06); border:1px solid rgba(16,185,129,.2);
border-radius:10px; padding:14px 18px; margin:14px 0;
}
.return-box h4 { font-size:11.5px; color:#34d399; margin-bottom:8px; text-transform:uppercase; letter-spacing:.8px; }
@media (max-width:768px) { .terminal {
background:#0a0f1c; border:1px solid var(--border); border-radius:10px;
overflow:hidden; margin:12px 0 20px;
}
.terminal-bar { display:flex; align-items:center; gap:7px; padding:9px 14px; background:rgba(255,255,255,0.03); border-bottom:1px solid var(--border); }
.terminal-bar .t-dot { width:10px; height:10px; border-radius:50%; }
.terminal-bar .t-dot:nth-child(1){ background:#ff5f57; } .terminal-bar .t-dot:nth-child(2){ background:#febc2e; } .terminal-bar .t-dot:nth-child(3){ background:#28c840; }
.terminal-bar .t-title { margin-left:10px; font-size:11px; color:var(--muted); }
.terminal pre { padding:16px 18px; overflow-x:auto; font:12.5px/1.75 'SF Mono','Cascadia Code','Fira Code',Consolas,monospace; color:#cbd5e1; }
.terminal pre .kw { color:#c084fc; } .terminal pre .fn { color:#60a5fa; } .terminal pre .s { color:#34d399; } .terminal pre .cm { color:#475569; }
@media (max-width:860px) {
body { flex-direction:column; } body { flex-direction:column; }
.sidebar { width:100%; min-width:0; height:auto; position:static; border-right:none; border-bottom:1px solid var(--border); padding:12px 16px; } .sidebar { width:100%; min-width:0; height:auto; position:static; border-right:none; border-bottom:1px solid var(--border); padding:12px 16px; }
.sidebar a { display:inline-block; padding:4px 10px; border-left:none; } .sidebar .section, .sidebar .back { padding:4px 8px; }
.main { padding:24px 20px; } .sidebar a.snav { display:inline-block; padding:4px 10px; border-left:none; font-size:12.5px; }
.sidebar a.snav.active { border-left:none; border-radius:6px; }
.main { padding:26px 20px; }
} }
</style> </style>
</head> </head>
<body> <body>
<nav class="sidebar"> <nav class="sidebar">
<h2>📘 MetonaToast 文档</h2> <div class="sidebar-logo"><span class="grad-text">MetonaToast 文档</span></div>
<a href="index.html">← 返回首页</a> <a class="back" href="index.html">← 返回首页</a>
<a href="demo.html">🎮 在线演示</a> <a class="back" href="demo.html">🎮 在线演示</a>
<div class="section">基础通知</div> <div class="section">基础通知</div>
<a href="#show">show()</a> <a class="snav" href="#show">show()</a>
<a href="#success">success()</a> <a class="snav" href="#success">success()</a>
<a href="#error">error()</a> <a class="snav" href="#error">error()</a>
<a href="#warning">warning()</a> <a class="snav" href="#warning">warning()</a>
<a href="#info">info()</a> <a class="snav" href="#info">info()</a>
<a href="#loading">loading()</a> <a class="snav" href="#loading">loading()</a>
<div class="section">高级功能</div> <div class="section">高级功能</div>
<a href="#promise">promise()</a> <a class="snav" href="#promise">promise()</a>
<a href="#confirm">confirm()</a> <a class="snav" href="#confirm">confirm()</a>
<a href="#prompt">prompt()</a> <a class="snav" href="#prompt">prompt()</a>
<a href="#progress">progress()</a> <a class="snav" href="#progress">progress()</a>
<a href="#countdown">countdown()</a> <a class="snav" href="#countdown">countdown()</a>
<a href="#queue">queue()</a> <a class="snav" href="#queue">queue()</a>
<a href="#stack">stack()</a> <a class="snav" href="#stack">stack()</a>
<a href="#action">action()</a> <a class="snav" href="#action">action()</a>
<div class="section">管理方法</div> <div class="section">管理方法</div>
<a href="#group">group()</a> <a class="snav" href="#group">group()</a>
<a href="#dismiss">dismiss()</a> <a class="snav" href="#dismiss">dismiss()</a>
<a href="#clear">clear()</a> <a class="snav" href="#clear">clear()</a>
<a href="#updatePosition">updatePosition()</a> <a class="snav" href="#updatePosition">updatePosition()</a>
<a href="#remove">remove() / removeToast()</a> <a class="snav" href="#remove">remove() / removeToast()</a>
<a href="#configure">configure()</a> <a class="snav" href="#configure">configure()</a>
<a href="#use">use()</a> <a class="snav" href="#use">use()</a>
<a href="#destroy">destroy()</a> <a class="snav" href="#destroy">destroy()</a>
<a href="#react">React 适配器</a> <div class="section">扩展</div>
<div class="section">配置参考</div> <a class="snav" href="#hooks">钩子系统</a>
<a href="#config">全部配置项</a> <a class="snav" href="#react">React 适配器</a>
<a href="#callbacks">回调函数</a> <a class="snav" href="#config">全部配置项</a>
<a href="#animations">动画列表</a> <a class="snav" href="#callbacks">回调函数</a>
<a href="#themes">主题参考</a> <a class="snav" href="#animations">动画列表</a>
<a class="snav" href="#themes">主题参考</a>
</nav> </nav>
<main class="main"> <main class="main">
<h1>API 文档</h1> <h1>API 文档</h1>
<p>MetonaToast v0.5.0 完整 API 参考。所有基础通知方法show/success/error/warning/info/loading支持两种调用形式,均可传入任何 <a href="#config">配置项</a> 作为可选第二参数。</p> <p class="h1-desc">MetonaToast v0.5.0 完整 API 参考。所有基础通知方法支持字符串与对象两种调用形式,均可传入任何<a href="#config">配置项</a>作为可选参数。</p>
<!-- ===== 基础通知 ===== --> <!-- ===== 基础通知 ===== -->
<h2 id="show">show(message, opts?)</h2> <h2 id="show">show(message, opts?)</h2>
<p>显示默认类型 Toast 通知,无特定颜色图标。</p> <p>显示默认类型 Toast,无特定颜色图标。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">show.js</span></div>
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
<tr><td>message</td><td>string | object</td><td></td><td>消息字符串,或包含 title/message 等属性的配置对象</td></tr>
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置对象,覆盖 <a href="#config">全部配置项</a>。仅当 message 为字符串时有效</td></tr>
</table>
<pre><span class="cm">// 字符串形式</span> <pre><span class="cm">// 字符串形式</span>
MeToast.<span class="fn">show</span>(<span class="s">'默认消息'</span>); MeToast.<span class="fn">show</span>(<span class="s">'默认消息'</span>);
MeToast.<span class="fn">show</span>(<span class="s">'自定义'</span>, { <span class="fn">duration</span>: 2000, <span class="fn">position</span>: <span class="s">'bottom-center'</span> }); MeToast.<span class="fn">show</span>(<span class="s">'自定义'</span>, { <span class="fn">duration</span>: <span class="num">2000</span>, <span class="fn">position</span>: <span class="s">'bottom-center'</span> });
<span class="cm">// 对象形式(所有 opts 作为一级属性)</span> <span class="cm">// 对象形式</span>
MeToast.<span class="fn">show</span>({ <span class="fn">title</span>: <span class="s">'标题'</span>, <span class="fn">message</span>: <span class="s">'内容'</span>, <span class="fn">duration</span>: 3000 });</pre> MeToast.<span class="fn">show</span>({ <span class="fn">title</span>: <span class="s">'标题'</span>, <span class="fn">message</span>: <span class="s">'内容'</span>, <span class="fn">duration</span>: <span class="num">3000</span> });</pre></div>
<h2 id="success">success(message, opts?)</h2> <h2 id="success">success(message, opts?)</h2>
<p>显示成功通知。绿色对勾图标 <code>#10b981</code>type = success。</p> <p>绿色对勾图标 <code>#10b981</code>type = success。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">success.js</span></div>
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
<tr><td>message</td><td>string | object</td><td></td><td>消息字符串或配置对象</td></tr>
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖。type 固定为 success</td></tr>
</table>
<pre>MeToast.<span class="fn">success</span>(<span class="s">'保存成功!'</span>); <pre>MeToast.<span class="fn">success</span>(<span class="s">'保存成功!'</span>);
MeToast.<span class="fn">success</span>({ <span class="fn">title</span>: <span class="s">'已保存'</span>, <span class="fn">message</span>: <span class="s">'数据已同步'</span> }); MeToast.<span class="fn">success</span>({ <span class="fn">title</span>: <span class="s">'已保存'</span>, <span class="fn">message</span>: <span class="s">'数据已同步'</span> });
Met.<span class="fn">success</span>(<span class="s">'MeToast和Met等价'</span>);</pre> Met.<span class="fn">success</span>(<span class="s">'Met 与 MeToast 等价'</span>);</pre></div>
<h2 id="error">error(message, opts?)</h2> <h2 id="error">error(message, opts?)</h2>
<p>显示错误通知。红色叉号图标 <code>#ef4444</code>type = error。aria-live 设为 assertive。</p> <p>红色叉号图标 <code>#ef4444</code>type = error。aria-live = assertive。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">error.js</span></div>
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
<tr><td>message</td><td>string | object</td><td></td><td>消息字符串或配置对象</td></tr>
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖。type 固定为 error</td></tr>
</table>
<pre>MeToast.<span class="fn">error</span>(<span class="s">'网络错误,请重试'</span>); <pre>MeToast.<span class="fn">error</span>(<span class="s">'网络错误,请重试'</span>);
MeToast.<span class="fn">error</span>({ <span class="fn">title</span>: <span class="s">'提交失败'</span>, <span class="fn">message</span>: <span class="s">'服务器不可达'</span> });</pre> MeToast.<span class="fn">error</span>({ <span class="fn">title</span>: <span class="s">'提交失败'</span>, <span class="fn">message</span>: <span class="s">'服务器不可达'</span> });</pre></div>
<h2 id="warning">warning(message, opts?)</h2> <h2 id="warning">warning(message, opts?)</h2>
<p>显示警告通知。黄色三角图标 <code>#f59e0b</code>type = warning。</p> <p>黄色三角图标 <code>#f59e0b</code>type = warning。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">warning.js</span></div>
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr> <pre>MeToast.<span class="fn">warning</span>(<span class="s">'请注意检查输入内容'</span>);</pre></div>
<tr><td>message</td><td>string | object</td><td></td><td>消息字符串或配置对象</td></tr>
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖。type 固定为 warning</td></tr>
</table>
<pre>MeToast.<span class="fn">warning</span>(<span class="s">'请注意检查输入内容'</span>);</pre>
<h2 id="info">info(message, opts?)</h2> <h2 id="info">info(message, opts?)</h2>
<p>显示信息通知。蓝色圆形图标 <code>#3b82f6</code>type = info。</p> <p>蓝色圆形图标 <code>#3b82f6</code>type = info。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">info.js</span></div>
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr> <pre>MeToast.<span class="fn">info</span>(<span class="s">'系统将于 22:00 维护'</span>);</pre></div>
<tr><td>message</td><td>string | object</td><td></td><td>消息字符串或配置对象</td></tr>
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖。type 固定为 info</td></tr>
</table>
<pre>MeToast.<span class="fn">info</span>(<span class="s">'系统将于 22:00 维护'</span>);</pre>
<h2 id="loading">loading(message, opts?)</h2> <h2 id="loading">loading(message, opts?)</h2>
<p>显示加载状态。type = loadingduration 强制 0(不自动关闭)closeButton 和 showProgress 强制为 false。返回 LoadingControl 对象</p> <p>type = loadingduration 强制 0(不自动关闭)。返回 LoadingControl。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">loading.js</span></div>
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr>
<tr><td>message</td><td>string | object</td><td></td><td>加载提示文本或配置对象</td></tr>
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置覆盖</td></tr>
</table>
<pre><span class="kw">const</span> loading = MeToast.<span class="fn">loading</span>(<span class="s">'正在提交...'</span>); <pre><span class="kw">const</span> loading = MeToast.<span class="fn">loading</span>(<span class="s">'正在提交...'</span>);
<span class="fn">setTimeout</span>(() => loading.<span class="fn">success</span>(<span class="s">'提交成功!'</span>), 2000);</pre> <span class="fn">setTimeout</span>(() => loading.<span class="fn">success</span>(<span class="s">'提交成功!'</span>), <span class="num">2000</span>);</pre></div>
<div class="return-box"> <div class="return-box">
<h4>返回 LoadingControl</h4> <h4>LoadingControl(链式转换 id 稳定)</h4>
<table> <table>
<tr><th>方法</th><th>签名</th><th>说明</th></tr> <tr><th>方法</th><th>说明</th></tr>
<tr><td>success</td><td>(msg, opts?) => ToastInstance</td><td>关闭加载 toast,原地替换为 success 类型</td></tr> <tr><td>success/error/info/warning</td><td>原地更新为对应类型(同一实例,id 不变),duration 自动恢复默认值</td></tr>
<tr><td>error</td><td>(msg, opts?) => ToastInstance</td><td>替换为 error 类型</td></tr> <tr><td>update(partial)</td><td>原地更新内容,不改变类型</td></tr>
<tr><td>info</td><td>(msg, opts?) => ToastInstance</td><td>替换为 info 类型</td></tr> <tr><td>dismiss()</td><td>直接关闭,不转换</td></tr>
<tr><td>warning</td><td>(msg, opts?) => ToastInstance</td><td>替换为 warning 类型</td></tr>
<tr><td>update</td><td>(partial) => MeToast</td><td>不关闭加载 toast,原地更新其内容(支持 resetTimerOnUpdate</td></tr>
<tr><td>dismiss</td><td>()</td><td>直接关闭加载 toast 不替换</td></tr>
</table> </table>
</div> </div>
<!-- ===== 高级功能 ===== --> <!-- ===== 高级功能 ===== -->
<h2 id="promise">promise(promise, opts)</h2> <h2 id="promise">promise(promise, opts)</h2>
<p>监听 Promise 生命周期自动显示 loading → 根据 resolve/reject 自动切换 success/error返回原 Promise,支持 await 获取结果</p> <p>监听 Promise 生命周期自动 loading → resolve/reject 切换 success/error返回原 Promise。</p>
<table> <table>
<tr><th>参数</th><th>类型</th><th>默认</th><th>说明</th></tr> <tr><th>参数</th><th>类型</th><th>默认</th><th>说明</th></tr>
<tr><td>promise</td><td>Promise</td><td></td><td>要监听的 Promise 对象。非 Promise 打印错误并返回 rejected Promise</td></tr> <tr><td>promise</td><td>Promise</td><td></td><td>要监听的 Promise。非 Promise 打印错误并返回 rejected Promise</td></tr>
<tr><td>opts.loading</td><td>string</td><td>"加载中..."</td><td>加载中显示的文本</td></tr> <tr><td>opts.loading / success / error</td><td>string</td><td>加载中... / 操作成功 / 操作失败</td><td>各阶段文本</td></tr>
<tr><td>opts.success</td><td>string</td><td>"操作成功"</td><td>resolve 后显示的文本</td></tr>
<tr><td>opts.error</td><td>string</td><td>"操作失败"</td><td>reject 后显示的文本</td></tr>
</table> </table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">promise.js</span></div>
<pre><span class="kw">try</span> { <pre><span class="kw">try</span> {
<span class="kw">await</span> MeToast.<span class="fn">promise</span>(<span class="fn">fetch</span>(<span class="s">'/api/data'</span>), { <span class="kw">await</span> MeToast.<span class="fn">promise</span>(<span class="fn">fetch</span>(<span class="s">'/api/data'</span>), {
<span class="fn">loading</span>: <span class="s">'加载中...'</span>, <span class="fn">loading</span>: <span class="s">'加载中...'</span>, <span class="fn">success</span>: <span class="s">'完成!'</span>, <span class="fn">error</span>: <span class="s">'失败'</span>,
<span class="fn">success</span>: <span class="s">'加载完成!'</span>,
<span class="fn">error</span>: <span class="s">'加载失败'</span>,
}); });
} <span class="kw">catch</span> (<span class="fn">e</span>) { <span class="cm">/* promise reject 会继续抛出 */</span> }</pre> } <span class="kw">catch</span> (e) { <span class="cm">/* reject 会继续抛出 */</span> }</pre></div>
<h2 id="confirm">confirm(message, opts?)</h2> <h2 id="confirm">confirm(message, opts?)</h2>
<p>确认对话框返回 <code>Promise&lt;boolean&gt;</code>内置 10 秒安全超时,超时自动 resolve(false)。</p> <p>确认对话框返回 <code>Promise&lt;boolean&gt;</code>内置 10 秒安全超时自动 resolve(false)。</p>
<table> <table>
<tr><th>参数</th><th>类型</th><th>默认</th><th>说明</th></tr> <tr><th>参数</th><th>默认</th><th>说明</th></tr>
<tr><td>message</td><td>string</td><td></td><td>对话框消息。非字符串会打印错误并 resolve(false)</td></tr> <tr><td>confirmText / confirmColor</td><td>确认 / #10b981</td><td>确认按钮文字与颜色</td></tr>
<tr><td>opts.confirmText</td><td>string</td><td>"确认"</td><td>确认按钮文字</td></tr> <tr><td>cancelText / cancelColor</td><td>取消 / #6b7280</td><td>取消按钮文字与颜色</td></tr>
<tr><td>opts.confirmColor</td><td>string</td><td>"#10b981"</td><td>确认按钮背景色</td></tr> <tr><td>type</td><td>warning</td><td>Toast 类型</td></tr>
<tr><td>opts.cancelText</td><td>string</td><td>"取消"</td><td>取消按钮文字</td></tr>
<tr><td>opts.cancelColor</td><td>string</td><td>"#6b7280"</td><td>取消按钮背景色</td></tr>
<tr><td>opts.type</td><td>string</td><td>"warning"</td><td>Toast 类型(影响图标和颜色)</td></tr>
</table> </table>
<pre><span class="kw">const</span> ok = <span class="kw">await</span> MeToast.<span class="fn">confirm</span>(<span class="s">'确定删除?'</span>, { <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">confirm.js</span></div>
<span class="fn">confirmText</span>: <span class="s">'删除'</span>, <pre><span class="kw">const</span> ok = <span class="kw">await</span> MeToast.<span class="fn">confirm</span>(<span class="s">'确定删除?'</span>, { <span class="fn">confirmText</span>: <span class="s">'删除'</span>, <span class="fn">confirmColor</span>: <span class="s">'#ef4444'</span> });
<span class="fn">confirmColor</span>: <span class="s">'#ef4444'</span>, <span class="kw">if</span> (ok) MeToast.<span class="fn">success</span>(<span class="s">'已删除'</span>);</pre></div>
<span class="fn">cancelText</span>: <span class="s">'保留'</span>,
});
<span class="kw">if</span> (ok) MeToast.<span class="fn">success</span>(<span class="s">'已删除'</span>);</pre>
<h2 id="prompt">prompt(message, opts?)</h2> <h2 id="prompt">prompt(message, opts?)</h2>
<p>输入对话框返回 <code>Promise&lt;string|null&gt;</code>Enter 或点击提交按钮返回输入值,取消返回 null。内置 10 秒安全超时。</p> <p>输入对话框返回 <code>Promise&lt;string|null&gt;</code>。Enter / 提交返回输入值,取消返回 null10 秒超时。</p>
<table> <table>
<tr><th>参数</th><th>类型</th><th>默认</th><th>说明</th></tr> <tr><th>参数</th><th>默认</th><th>说明</th></tr>
<tr><td>message</td><td>string</td><td></td><td>对话框消息。非字符串会打印错误并 resolve(null)</td></tr> <tr><td>placeholder / defaultValue</td><td>""</td><td>输入框占位与默认值</td></tr>
<tr><td>opts.placeholder</td><td>string</td><td>""</td><td>输入框占位文字</td></tr> <tr><td>inputType</td><td>text</td><td>input 的 typepassword/email/number 等)</td></tr>
<tr><td>opts.defaultValue</td><td>string</td><td>""</td><td>输入框默认值</td></tr> <tr><td>submitText / submitColor</td><td>确认 / #3b82f6</td><td>提交按钮</td></tr>
<tr><td>opts.inputType</td><td>string</td><td>"text"</td><td>input 标签 type 属性(如 password/email/number</td></tr> <tr><td>cancelText / cancelColor</td><td>取消 / #6b7280</td><td>取消按钮</td></tr>
<tr><td>opts.submitText</td><td>string</td><td>"确认"</td><td>提交按钮文字</td></tr>
<tr><td>opts.submitColor</td><td>string</td><td>"#3b82f6"</td><td>提交按钮背景色</td></tr>
<tr><td>opts.cancelText</td><td>string</td><td>"取消"</td><td>取消按钮文字</td></tr>
<tr><td>opts.cancelColor</td><td>string</td><td>"#6b7280"</td><td>取消按钮背景色</td></tr>
<tr><td>opts.type</td><td>string</td><td>"info"</td><td>Toast 类型</td></tr>
</table> </table>
<pre><span class="kw">const</span> name = <span class="kw">await</span> MeToast.<span class="fn">prompt</span>(<span class="s">'请输入姓名'</span>, { <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">prompt.js</span></div>
<span class="fn">placeholder</span>: <span class="s">'请输入...'</span>, <pre><span class="kw">const</span> name = <span class="kw">await</span> MeToast.<span class="fn">prompt</span>(<span class="s">'请输入姓名'</span>, { <span class="fn">placeholder</span>: <span class="s">'请输入...'</span> });
<span class="fn">defaultValue</span>: <span class="s">'张三'</span>, <span class="kw">if</span> (name) MeToast.<span class="fn">info</span>(<span class="s">'你好,'</span> + name);</pre></div>
<span class="fn">submitText</span>: <span class="s">'确定'</span>,
});
<span class="kw">if</span> (name) MeToast.<span class="fn">info</span>(<span class="s">'你好,'</span> + name);</pre>
<h2 id="progress">progress(message, opts?)</h2> <h2 id="progress">progress(message, opts?)</h2>
<p>进度条通知不自动关闭。返回 ProgressControl 对象以手动更新进度</p> <p>进度条通知不自动关闭。返回 ProgressControl。</p>
<table> <table>
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr> <tr><th>方法</th><th>说明</th></tr>
<tr><td>message</td><td>string | object</td><td></td><td>进度提示文本或配置对象</td></tr> <tr><td>setProgress(percent)</td><td>设置进度 0~100,自动 clamp</td></tr>
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置。type 默认 info</td></tr> <tr><td>complete(message?)</td><td>跳 100%300ms 后转 success1s 后自动关闭</td></tr>
<tr><td>opts.progressColor</td><td>string</td><td>"#3b82f6"</td><td>进度条填充颜色</td></tr> <tr><td>error(message?)</td><td>转 error2s 后自动关闭</td></tr>
<tr><td>dismiss()</td><td>直接关闭</td></tr>
</table> </table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">progress.js</span></div>
<pre><span class="kw">const</span> p = MeToast.<span class="fn">progress</span>(<span class="s">'上传中...'</span>, { <span class="fn">progressColor</span>: <span class="s">'#10b981'</span> }); <pre><span class="kw">const</span> p = MeToast.<span class="fn">progress</span>(<span class="s">'上传中...'</span>, { <span class="fn">progressColor</span>: <span class="s">'#10b981'</span> });
p.<span class="fn">setProgress</span>(45); <span class="cm">// → 45%</span> p.<span class="fn">setProgress</span>(<span class="num">45</span>);
p.<span class="fn">setProgress</span>(90); <span class="cm">// → 90%</span> p.<span class="fn">complete</span>(<span class="s">'上传完成!'</span>);</pre></div>
p.<span class="fn">complete</span>(<span class="s">'上传完成!'</span>); <span class="cm">// → 100% → success</span>
<span class="cm">// 或</span>
p.<span class="fn">error</span>(<span class="s">'上传失败'</span>);
p.<span class="fn">dismiss</span>();</pre>
<div class="return-box">
<h4>返回 ProgressControl</h4>
<table>
<tr><th>方法</th><th>签名</th><th>说明</th></tr>
<tr><td>setProgress</td><td>(percent: number)</td><td>设置进度 0~100,自动 clamp。更新进度条宽度和百分比文字</td></tr>
<tr><td>complete</td><td>(message?: string)</td><td>跳到 100%300ms 后替换为 success toast1s 后自动关闭</td></tr>
<tr><td>error</td><td>(message?: string)</td><td>替换为 error toast2s 后自动关闭</td></tr>
<tr><td>dismiss</td><td>()</td><td>直接关闭</td></tr>
</table>
</div>
<h2 id="countdown">countdown(message, seconds, opts?)</h2> <h2 id="countdown">countdown(message, seconds, opts?)</h2>
<p>倒计时 Toast<code>{seconds}</code> 占位符每秒自动替换为剩余秒数</p> <p>倒计时 Toast<code>{seconds}</code> 占位符每秒自动替换。</p>
<table> <table>
<tr><th>参数</th><th>类型</th><th>默认</th><th>说明</th></tr> <tr><th>参数</th><th>默认</th><th>说明</th></tr>
<tr><td>message</td><td>string</td><td></td><td>消息文本。支持 <code>{seconds}</code> 占位符。非字符串打印错误并返回空控制对象</td></tr> <tr><td>seconds</td><td>10</td><td>倒计时秒数,最小 1</td></tr>
<tr><td>seconds</td><td>number</td><td>10</td><td>倒计时秒数,最小 1</td></tr> <tr><td>opts.onComplete</td><td></td><td>归零回调</td></tr>
<tr><td>opts.onComplete</td><td>function</td><td></td><td>倒计时归零时的回调</td></tr>
<tr><td>opts.type</td><td>string</td><td>"warning"</td><td>Toast 类型</td></tr>
</table> </table>
<pre>MeToast.<span class="fn">countdown</span>(<span class="s">'{seconds} 秒后执行'</span>, 5, { <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">countdown.js</span></div>
<span class="fn">onComplete</span>: () => MeToast.<span class="fn">success</span>(<span class="s">'已执行'</span>), <pre>MeToast.<span class="fn">countdown</span>(<span class="s">'{seconds} 秒后执行'</span>, <span class="num">5</span>, { <span class="fn">onComplete</span>: () => MeToast.<span class="fn">success</span>(<span class="s">'已执行'</span>) });
});</pre> <span class="cm">// 返回 { cancel(), pause(), resume() }</span></pre></div>
<div class="return-box">
<h4>返回 CountdownControl</h4>
<table>
<tr><th>方法</th><th>说明</th></tr>
<tr><td>cancel()</td><td>清除计时器并关闭 toast</td></tr>
<tr><td>pause()</td><td>暂停倒计时</td></tr>
<tr><td>resume()</td><td>恢复倒计时</td></tr>
</table>
</div>
<h2 id="queue">queue(messages, opts?)</h2> <h2 id="queue">queue(messages, opts?)</h2>
<p>顺序逐个显示消息队列。前一条关闭后延时显示下一条。返回 QueueControlthenable + cancel</p> <p>顺序逐个显示消息队列,返回 thenable + cancel。支持 await</p>
<table> <table>
<tr><th>参数</th><th>类型</th><th>默认</th><th>说明</th></tr> <tr><th>参数</th><th>默认</th><th>说明</th></tr>
<tr><td>messages</td><td>Array&lt;string|object&gt;</td><td></td><td>消息数组。可为字符串或带 message/type/duration/onClose 的对象。非数组打印错误并返回空控制对象</td></tr> <tr><td>opts.delay</td><td>1000</td><td>每条关闭后到下一条的间隔(ms)</td></tr>
<tr><td>opts.delay</td><td>number</td><td>1000</td><td>每条消息关闭后到显示下一条的间隔(ms)</td></tr> <tr><td>opts.duration</td><td>3000</td><td>每条显示时长,可被消息级 duration 覆盖</td></tr>
<tr><td>opts.duration</td><td>number</td><td>3000</td><td>每条消息显示时长(ms),可被消息级 duration 覆盖</td></tr>
<tr><td>opts.onClose</td><td>function</td><td></td><td>全部队列完成后的回调。消息级 onClose 和队列级 onClose 都会依次调用</td></tr>
<tr><td>opts.type</td><td>string</td><td></td><td>默认类型</td></tr>
</table> </table>
<pre><span class="kw">const</span> q = MeToast.<span class="fn">queue</span>([ <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">queue.js</span></div>
<span class="s">'步骤一'</span>, <pre><span class="kw">const</span> q = MeToast.<span class="fn">queue</span>([<span class="s">'步骤一'</span>, <span class="s">'步骤二'</span>, <span class="s">'步骤三'</span>], { <span class="fn">delay</span>: <span class="num">800</span>, <span class="fn">duration</span>: <span class="num">2000</span> });
{ <span class="fn">message</span>: <span class="s">'步骤二'</span>, <span class="fn">duration</span>: 5000, <span class="fn">type</span>: <span class="s">'warning'</span> }, q.<span class="fn">cancel</span>(); <span class="cm">// 中途取消</span>
<span class="s">'步骤三'</span>, <span class="kw">await</span> q; <span class="cm">// 等待完成</span></pre></div>
], { <span class="fn">delay</span>: 800, <span class="fn">duration</span>: 2000, <span class="fn">type</span>: <span class="s">'info'</span> });
q.<span class="fn">cancel</span>(); <span class="cm">// 中途取消</span>
<span class="kw">await</span> q; <span class="cm">// 等待完成(thenable 支持 await</span></pre>
<div class="return-box">
<h4>返回 QueueControl (thenable)</h4>
<table>
<tr><th>方法</th><th>说明</th></tr>
<tr><td>.then(fn, rj)</td><td>Promise.then 代理,支持 await</td></tr>
<tr><td>.catch(rj)</td><td>Promise.catch 代理</td></tr>
<tr><td>.cancel()</td><td>设置取消标志,不再显示下一条消息</td></tr>
</table>
</div>
<h2 id="stack">stack(messages, opts?)</h2> <h2 id="stack">stack(messages, opts?)</h2>
<p>同时错峰显示多条消息。每条间隔 stagger 毫秒依次出现,全部叠加在屏幕上。</p> <p>同时错峰显示多条消息。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">stack.js</span></div>
<tr><th>参数</th><th>类型</th><th>默认值</th><th>说明</th></tr> <pre>MeToast.<span class="fn">stack</span>([<span class="s">'消息1'</span>, <span class="s">'消息2'</span>, { <span class="fn">message</span>: <span class="s">'警告'</span>, <span class="fn">type</span>: <span class="s">'warning'</span> }], { <span class="fn">stagger</span>: <span class="num">150</span> });</pre></div>
<tr><td>messages</td><td>Array&lt;string|object&gt;</td><td></td><td>消息数组。可为字符串或带 message/type/duration 的对象。非数组打印错误</td></tr>
<tr><td>opts.stagger</td><td>number</td><td>100</td><td>每条消息之间的显示间隔(ms)</td></tr>
<tr><td>opts.type</td><td>string</td><td></td><td>默认类型</td></tr>
</table>
<pre>MeToast.<span class="fn">stack</span>([<span class="s">'消息1'</span>, <span class="s">'消息2'</span>, { <span class="fn">message</span>: <span class="s">'警告'</span>, <span class="fn">type</span>: <span class="s">'warning'</span> }], {
<span class="fn">stagger</span>: 150,
<span class="fn">type</span>: <span class="s">'info'</span>,
});</pre>
<h2 id="action">action(message, actions, opts?)</h2> <h2 id="action">action(message, actions, opts?)</h2>
<p>Action Toast内嵌操作按钮。默认 duration=0 不自动关闭closeButton=true</p> <p>内嵌操作按钮。默认 duration=0 不自动关闭。点击按钮不会误触 closeOnClick(已隔离冒泡)</p>
<table> <table>
<tr><th>参数</th><th>类型</th><th>默认</th><th>说明</th></tr> <tr><th>ActionButton</th><th>默认</th><th>说明</th></tr>
<tr><td>message</td><td>string | object</td><td></td><td>消息字符串或配置对象</td></tr> <tr><td>text</td><td>(必填)</td><td>按钮文字</td></tr>
<tr><td>actions</td><td>ActionButton[]</td><td>[]</td><td>按钮数组。每个按钮可配 text/onClick/color/style/close</td></tr> <tr><td>onClick</td><td>—(必填)</td><td>点击回调,参数为当前 toast</td></tr>
<tr><td>opts</td><td>object</td><td>{}</td><td>可选配置。duration 默认 0closeButton 默认 true</td></tr> <tr><td>color / style</td><td>#6366f1</td><td>背景色 / 内联样式(style.background 优先)</td></tr>
</table> <tr><td>close</td><td>true</td><td>点击后是否关闭。false 可多次点击</td></tr>
<table>
<tr><th>ActionButton 字段</th><th>类型</th><th>默认值</th><th>说明</th></tr>
<tr><td>text</td><td>string</td><td>—(必填)</td><td>按钮显示文字</td></tr>
<tr><td>onClick</td><td>(toast) => void</td><td>—(必填)</td><td>点击回调函数,参数为当前 toast 实例</td></tr>
<tr><td>color</td><td>string</td><td>"#6366f1"</td><td>按钮背景色</td></tr>
<tr><td>style</td><td>object</td><td>{}</td><td>按钮内联样式,可覆盖 colorstyle.background 优先)</td></tr>
<tr><td>close</td><td>boolean</td><td>true</td><td>点击后是否自动关闭 toast。设为 false 可多次点击</td></tr>
</table> </table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">action.js</span></div>
<pre>MeToast.<span class="fn">action</span>(<span class="s">'文件已删除'</span>, [ <pre>MeToast.<span class="fn">action</span>(<span class="s">'文件已删除'</span>, [
{ <span class="fn">text</span>: <span class="s">'撤销'</span>, <span class="fn">onClick</span>: () => <span class="fn">restore</span>(), <span class="fn">color</span>: <span class="s">'#3b82f6'</span> }, { <span class="fn">text</span>: <span class="s">'撤销'</span>, <span class="fn">onClick</span>: () => <span class="fn">restore</span>(), <span class="fn">color</span>: <span class="s">'#3b82f6'</span> },
{ <span class="fn">text</span>: <span class="s">'查看详情'</span>, <span class="fn">onClick</span>: (t) => <span class="fn">openFile</span>(), <span class="fn">color</span>: <span class="s">'#10b981'</span>, <span class="fn">close</span>: false }, { <span class="fn">text</span>: <span class="s">'查看'</span>, <span class="fn">onClick</span>: () => <span class="fn">open</span>(), <span class="fn">color</span>: <span class="s">'#10b981'</span>, <span class="fn">close</span>: <span class="kw">false</span> },
]);</pre> ]);</pre></div>
<!-- ===== 管理方法 ===== --> <!-- ===== 管理方法 ===== -->
<h2 id="group">group(name)</h2> <h2 id="group">group(name)</h2>
<p>创建 Toast 分组,返回 GroupAPI 对象。该对象所有方法自动<code>group: name</code>,按组管理</p> <p>创建分组,返回 GroupAPI所有方法自动<code>group: name</code></p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">group.js</span></div>
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>name</td><td>string</td><td>分组名称。GroupAPI 和 dismissGroup 通过此名称关联</td></tr>
</table>
<pre><span class="kw">const</span> orders = MeToast.<span class="fn">group</span>(<span class="s">'orders'</span>); <pre><span class="kw">const</span> orders = MeToast.<span class="fn">group</span>(<span class="s">'orders'</span>);
orders.<span class="fn">success</span>(<span class="s">'订单已创建'</span>); orders.<span class="fn">success</span>(<span class="s">'订单已创建'</span>);
orders.<span class="fn">error</span>(<span class="s">'支付失败'</span>, { <span class="fn">duration</span>: 5000 }); orders.<span class="fn">count</span>(); <span class="cm">// 该组数量</span>
orders.<span class="fn">count</span>(); <span class="cm">// 该组当前 toast 数量</span> orders.<span class="fn">dismiss</span>(); <span class="cm">// 关闭整组</span>
orders.<span class="fn">dismiss</span>(); <span class="cm">// 关闭该组全部 toast</span> MeToast.<span class="fn">dismissGroup</span>(<span class="s">'orders'</span>);</pre></div>
<span class="cm">// 也支持主对象关闭</span>
MeToast.<span class="fn">dismissGroup</span>(<span class="s">'orders'</span>);</pre>
<div class="return-box">
<h4>返回 GroupAPI</h4>
<table>
<tr><th>方法</th><th>说明</th></tr>
<tr><td>show(msg, opts?)</td><td>等价 MeToast.show,自动注入 group</td></tr>
<tr><td>success(msg, opts?)</td><td>等价 MeToast.success</td></tr>
<tr><td>error(msg, opts?)</td><td>等价 MeToast.error</td></tr>
<tr><td>warning(msg, opts?)</td><td>等价 MeToast.warning</td></tr>
<tr><td>info(msg, opts?)</td><td>等价 MeToast.info</td></tr>
<tr><td>loading(msg, opts?)</td><td>等价 MeToast.loading</td></tr>
<tr><td>action(msg, actions, opts?)</td><td>等价 MeToast.action</td></tr>
<tr><td>dismiss()</td><td>关闭该组所有 toast</td></tr>
<tr><td>count()</td><td>返回该组当前 toast 数量</td></tr>
</table>
</div>
<h2 id="dismiss">dismiss(id?)</h2> <h2 id="dismiss">dismiss(id?)</h2>
<p>关闭 Toast。无参数关闭全部。</p> <p>关闭 Toast。无参数关闭全部,传入 id 关闭指定</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">dismiss.js</span></div>
<tr><th>参数</th><th>类型</th><th>说明</th></tr> <pre>MeToast.<span class="fn">dismiss</span>(); <span class="cm">// 全部</span>
<tr><td>id</td><td>string</td><td>可选。Toast 的 id,不传则关闭所有</td></tr> MeToast.<span class="fn">dismiss</span>(toast.id); <span class="cm">// 指定</span></pre></div>
</table>
<pre>MeToast.<span class="fn">dismiss</span>(); <span class="cm">// 关闭所有</span>
MeToast.<span class="fn">dismiss</span>(toast.id); <span class="cm">// 关闭指定</span></pre>
<h2 id="clear">clear(position?)</h2> <h2 id="clear">clear(position?)</h2>
<p>按位置清除 Toast。不传参数清除所有位置</p> <p>按位置清除。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">clear.js</span></div>
<tr><th>参数</th><th>类型</th><th>说明</th></tr> <pre>MeToast.<span class="fn">clear</span>(); <span class="cm">// 全部</span>
<tr><td>position</td><td>string</td><td>可选。位置如 'top-right',不传则清除全部</td></tr> MeToast.<span class="fn">clear</span>(<span class="s">'bottom-right'</span>); <span class="cm">// 仅右下角</span></pre></div>
</table>
<pre>MeToast.<span class="fn">clear</span>(); <span class="cm">// 全部</span>
MeToast.<span class="fn">clear</span>(<span class="s">'bottom-right'</span>); <span class="cm">// 仅右下角</span></pre>
<h2 id="updatePosition">updatePosition(position)</h2> <h2 id="updatePosition">updatePosition(position) <span class="badge badge-v5">v0.3</span></h2>
<p>运行时将 Toast 移动到新位置容器立即生效,不重新播放入场动画</p> <p>运行时将 Toast 移动到新位置容器立即生效。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">position.js</span></div>
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>position</td><td>string</td><td>目标位置:top-left / top-center / top-right / bottom-left / bottom-center / bottom-right</td></tr>
</table>
<pre><span class="kw">const</span> t = MeToast.<span class="fn">info</span>(<span class="s">'可移动的 Toast'</span>); <pre><span class="kw">const</span> t = MeToast.<span class="fn">info</span>(<span class="s">'可移动的 Toast'</span>);
t.<span class="fn">updatePosition</span>(<span class="s">'bottom-left'</span>); <span class="cm">// 移动到左下角</span></pre> t.<span class="fn">updatePosition</span>(<span class="s">'bottom-left'</span>);</pre></div>
<h2 id="remove">remove() / removeToast(id)</h2> <h2 id="remove">remove() / removeToast(id) <span class="badge badge-new">立即移除</span></h2>
<p>立即从 DOM 和内存移除 Toast<b>不触发离场动画</b>。适用于需要无动画快速清除的场景(区别于 <code>dismiss()</code> 的优雅关闭)</p> <p>立即从 DOM 和内存移除,<b>不触发离场动画</b>。适用于无动画快速清除。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">remove.js</span></div>
<tr><th>方法</th><th>签名</th><th>说明</th></tr> <pre>t.<span class="fn">remove</span>(); <span class="cm">// 实例方法</span>
<tr><td>remove</td><td>toast.remove()</td><td>Toast 实例方法,立即移除自身</td></tr> MeToast.<span class="fn">removeToast</span>(t.id); <span class="cm">// 按 id(等价)</span></pre></div>
<tr><td>removeToast</td><td>MeToast.removeToast(id)</td><td>按 id 立即移除。id 不存在时静默忽略</td></tr>
</table>
<pre><span class="kw">const</span> t = MeToast.<span class="fn">warning</span>(<span class="s">'临时消息'</span>);
t.<span class="fn">remove</span>(); <span class="cm">// 实例方法:立即移除</span>
MeToast.<span class="fn">removeToast</span>(t.id); <span class="cm">// 或按 id 移除(等价)</span></pre>
<h2 id="configure">configure(opts)</h2> <h2 id="configure">configure(opts)</h2>
<p>全局配置,影响后续所有 Toast。themelocale 变化触发相应副作用(应用主题 CSS / 切换语言)</p> <p>全局配置,影响后续所有 Toast。theme/locale 变化触发副作用。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">configure.js</span></div>
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>opts</td><td>object</td><td>包含任意 <a href="#config">配置项</a> 的对象</td></tr>
</table>
<pre>MeToast.<span class="fn">configure</span>({ <pre>MeToast.<span class="fn">configure</span>({
<span class="fn">position</span>: <span class="s">'top-right'</span>, <span class="fn">position</span>: <span class="s">'top-right'</span>, <span class="fn">duration</span>: <span class="num">4000</span>, <span class="fn">theme</span>: <span class="s">'dark'</span>,
<span class="fn">duration</span>: 4000, <span class="fn">animation</span>: <span class="s">'slide'</span>, <span class="fn">locale</span>: <span class="s">'zh-CN'</span>,
<span class="fn">theme</span>: <span class="s">'dark'</span>, });</pre></div>
<span class="fn">animation</span>: <span class="s">'slide'</span>,
<span class="fn">locale</span>: <span class="s">'zh-CN'</span>,
});</pre>
<h2 id="use">use(plugin)</h2> <h2 id="use">use(plugin)</h2>
<p>安装插件。支持字符串(内置预设名或插件对象。已被拒绝注册的无效插件会打印错误。</p> <p>安装插件:字符串预设名或插件对象。</p>
<table> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">use.js</span></div>
<tr><th>参数</th><th>类型</th><th>说明</th></tr> <pre>MeToast.<span class="fn">use</span>(<span class="s">'keyboard'</span>); <span class="cm">// ESC 关闭所有</span>
<tr><td>plugin</td><td>string | object</td><td>预设名 'keyboard'/'persistence'/'accessibility' 或自定义插件对象 { name, version?, install, uninstall? }</td></tr> MeToast.<span class="fn">use</span>(<span class="s">'persistence'</span>); <span class="cm">// 配置持久化 localStorage</span>
</table> MeToast.<span class="fn">use</span>(<span class="s">'accessibility'</span>); <span class="cm">// 屏幕阅读器朗读</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">'dedupe'</span>); <span class="cm">// 相同 type+message 自动去重</span></pre></div>
MeToast.<span class="fn">use</span>(<span class="s">'persistence'</span>); <span class="cm">// 配置自动保存到 localStorage</span> <p class="hint">dedupe:检测已有相同 toast,更新它并阻止重复弹出(beforeShow 钩子实现,支持 uninstall 卸载钩子)。</p>
MeToast.<span class="fn">use</span>(<span class="s">'accessibility'</span>); <span class="cm">// 屏幕阅读器实时朗读 toast 内容</span>
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 容器注入样式、清空内存缓存与钩子。支持重复调用,<code>init()</code> 可恢复</p>
<pre>MeToast.<span class="fn">destroy</span>();</pre> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">destroy.js</span></div>
<pre>MeToast.<span class="fn">destroy</span>();
MeToast.<span class="fn">init</span>({ <span class="fn">config</span>: { <span class="fn">duration</span>: <span class="num">3000</span> } }); <span class="cm">// 恢复</span></pre></div>
<!-- ===== React 适配器 ===== --> <!-- ===== 钩子系统 ===== -->
<h2 id="react">React 适配器</h2> <h2 id="hooks">钩子系统 <span class="badge badge-v5">v0.3</span></h2>
<p>主包保持零依赖。React 适配器通过子路径 <code>@metona-team/metona-toast/react</code> 导入,<code>react</code> 为 optional peerDependency,未使用 React 的项目不受影响</p> <p><code>Toast.on(name, handler)</code> 注册钩子并返回取消函数。<code>beforeShow</code> / <code>beforeClose</code> / <code>beforeUpdate</code> 的 handler 返回 <code>false</code> 可拦截对应操作</p>
<pre><span class="cm">// 安装</span> <table>
npm install @metona-team/metona-toast react <tr><th>钩子</th><th>触发时机</th><th>拦截</th></tr>
<tr><td>beforeInit / afterInit</td><td>init() 前后</td><td></td></tr>
<tr><td>beforeDestroy / afterDestroy</td><td>destroy() 前后</td><td></td></tr>
<tr><td>beforeShow / afterShow</td><td>显示前后</td><td>✅ 返回 false 阻止</td></tr>
<tr><td>beforeClose / afterClose</td><td>关闭前后</td><td>✅ 返回 false 阻止</td></tr>
<tr><td>beforeUpdate / afterUpdate</td><td>update() 前后</td><td>✅ 返回 false 阻止</td></tr>
<tr><td>configChange</td><td>configure / updateConfig / resetConfig</td><td></td></tr>
<tr><td>themeChange / localeChange</td><td>主题 / 语言切换</td><td></td></tr>
<tr><td>click / hover</td><td>点击 / 悬停进出</td><td></td></tr>
<tr><td>dragStart / dragEnd</td><td>拖拽开始 / 结束</td><td></td></tr>
<tr><td>animationStart / animationEnd</td><td>入场动画开始 / 结束</td><td></td></tr>
<tr><td>progressStart / progressEnd</td><td>倒计时开始 / 归零</td><td></td></tr>
</table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">hooks.js</span></div>
<pre><span class="kw">import</span> MeToast, { <span class="fn">Toast</span> } <span class="kw">from</span> <span class="s">'@metona-team/metona-toast'</span>;
<span class="cm">// useToast — 组件卸载时自动移除本组件创建的 Toast</span> <span class="cm">// 拦截:非允许时段禁止错误提示</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">const</span> off = <span class="fn">Toast</span>.<span class="fn">on</span>(<span class="s">'beforeShow'</span>, (toast) =&gt; {
<span class="kw">if</span> (toast.type === <span class="s">'error'</span> &amp;&amp; !isAllowed) <span class="kw">return</span> <span class="kw">false</span>;
});
<span class="cm">// ...</span>
off(); <span class="cm">// 取消注册</span></pre></div>
<!-- ===== React ===== -->
<h2 id="react">React 适配器 <span class="badge badge-v5">v0.4</span></h2>
<p>主包保持零依赖。通过子路径 <code>@metona-team/metona-toast/react</code> 导入,<code>react</code> 为 optional peerDependency。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">react.tsx</span></div>
<pre><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">function</span> <span class="fn">SubmitButton</span>() {
<span class="kw">const</span> toast = <span class="fn">useToast</span>(); <span class="kw">const</span> toast = <span class="fn">useToast</span>(); <span class="cm">// 组件卸载自动清理本组件创建的 Toast</span>
<span class="kw">const</span> submit = <span class="kw">async</span> () =&gt; { <span class="kw">const</span> submit = <span class="kw">async</span> () =&gt; {
<span class="kw">const</span> loading = toast.<span class="fn">loading</span>(<span class="s">'正在提交...'</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">try</span> { <span class="kw">await</span> api(); 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">catch</span> (e) { loading.<span class="fn">error</span>(<span class="s">'失败'</span>); }
}; };
<span class="kw">return</span> &lt;button onClick={submit}&gt;提交&lt;/button&gt;; <span class="kw">return</span> &lt;button onClick={submit}&gt;提交&lt;/button&gt;;
} }
<span class="cm">// Toast 组件 — props 变化更新,卸载自动移除(autoClose 默认 true</span> <span class="cm">// 声明式 Toastprops 变化更新,卸载自动移除(autoClose 默认 true</span>
<span class="kw">function</span> <span class="fn">SaveIndicator</span>({ saving }) { <span class="kw">function</span> <span class="fn">SaveIndicator</span>({ saving }) {
<span class="kw">return</span> saving ? &lt;<span class="fn">Toast</span> type=<span class="s">"info"</span> message=<span class="s">"正在保存..."</span> /&gt; : null; <span class="kw">return</span> saving ? &lt;<span class="fn">Toast</span> type=<span class="s">"info"</span> message=<span class="s">"正在保存..."</span> /&gt; : null;
}</pre> }</pre></div>
<!-- ===== 配置参考 ===== --> <!-- ===== 配置参考 ===== -->
<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>
<table> <table>
<tr><th>配置项</th><th>类型</th><th>默认值</th><th>说明</th></tr> <tr><th>配置项</th><th>类型</th><th>默认值</th><th>说明</th></tr>
<tr><td>position</td><td>string</td><td>'top-right'</td><td>位置:top-left / top-center / top-right / bottom-left / bottom-center / bottom-right</td></tr> <tr><td>position</td><td>string</td><td>'top-right'</td><td>6 个位置之一(RTL 自动翻转)</td></tr>
<tr><td>duration</td><td>number</td><td>4000</td><td>显示时长(ms)0=不自动关闭</td></tr> <tr><td>duration</td><td>number</td><td>4000</td><td>显示时长(ms)0 = 不自动关闭</td></tr>
<tr><td>max</td><td>number</td><td>6</td><td>位置最多同时显示条数,超出关闭最早的</td></tr> <tr><td>max</td><td>number</td><td>6</td><td>同位置最多条数,超出真正关闭最早的</td></tr>
<tr><td>gap</td><td>number</td><td>12</td><td>Toast 之间的间距(px)</td></tr> <tr><td>gap</td><td>number</td><td>12</td><td>Toast 间距(px)</td></tr>
<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>悬停暂停倒计时</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>点击关闭</td></tr>
<tr><td>draggable</td><td>boolean</td><td>true</td><td>允许拖拽关闭。拖拽超过 dragThreshold 触发关闭</td></tr> <tr><td>draggable</td><td>boolean</td><td>true</td><td>允许拖拽关闭</td></tr>
<tr><td>dragThreshold</td><td>number</td><td>120</td><td>拖拽关闭阈值(px),位移超过该值松手即关闭</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>显示倒计时进度条</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>显示类型图标</td></tr>
<tr><td>closeButton</td><td>boolean</td><td>true</td><td>显示右上角关闭 × 按钮</td></tr> <tr><td>closeButton</td><td>boolean</td><td>true</td><td>显示关闭 × 按钮</td></tr>
<tr><td>theme</td><td>string</td><td>'auto'</td><td>主题:light / dark / auto(跟随系统)/ warm / 自定义注册</td></tr> <tr><td>theme</td><td>string</td><td>'auto'</td><td>light / dark / auto / warm / 自定义名</td></tr>
<tr><td>animation</td><td>string</td><td>'slide'</td><td>入场动画名称,支持 CSS 动画列表见下</td></tr> <tr><td>animation</td><td>string</td><td>'slide'</td><td>11 种内置或自定义动画名</td></tr>
<tr><td>zIndex</td><td>number</td><td>9999</td><td>容器 CSS z-index</td></tr> <tr><td>zIndex</td><td>number</td><td>9999</td><td>容器 z-index</td></tr>
<tr><td>width</td><td>number|string</td><td>360</td><td>Toast 宽度数字表示 px</td></tr> <tr><td>width</td><td>number|string</td><td>360</td><td>宽度数字表示 px</td></tr>
<tr><td>className</td><td>string</td><td>''</td><td>附加到 Toast 元素上的 CSS 类名</td></tr> <tr><td>className</td><td>string</td><td>''</td><td>附加 CSS 类名</td></tr>
<tr><td>style</td><td>object</td><td>{}</td><td>附加到 Toast 元素上的内联样式对象</td></tr> <tr><td>style</td><td>object</td><td>{}</td><td>附加内联样式</td></tr>
<tr><td>locale</td><td>string</td><td>'zh-CN'</td><td>语言代码zh-CN / en-US 等)</td></tr> <tr><td>locale</td><td>string</td><td>'zh-CN'</td><td>语言代码</td></tr>
<tr><td>resetTimerOnUpdate</td><td>boolean</td><td>false</td><td>调用 update() 时重置 duration 倒计时</td></tr> <tr><td>resetTimerOnUpdate</td><td>boolean</td><td>false</td><td>update() 时重置倒计时</td></tr>
<tr><td>notifyWhenHidden</td><td>boolean</td><td>false</td><td>页面不可见时自动通过 Notification API 发送系统通知</td></tr> <tr><td>notifyWhenHidden</td><td>boolean</td><td>false</td><td>页面不可见时系统通知</td></tr>
<tr><td>render</td><td>function</td><td></td><td>自定义渲染函数 <code>(toast) => htmlString</code>,完全接管 DOM 构建</td></tr> <tr><td>render</td><td>function</td><td></td><td>自定义渲染函数,完全接管 DOM</td></tr>
<tr><td>onBeforeShow</td><td>function</td><td></td><td>显示前回调 <code>(toast) => boolean | void</code>返回 false 阻止该 Toast 显示</td></tr> <tr><td>onBeforeShow</td><td>function</td><td></td><td>返回 false 阻止显示</td></tr>
<tr><td>onError</td><td>function</td><td></td><td>全局错误回调 <code>({ hook, source, error, toast }) => void</code>,钩子异常或定时器错误时触发</td></tr> <tr><td>onError</td><td>function</td><td></td><td>钩子/定时器异常全局回调</td></tr>
</table> </table>
<h2 id="callbacks">回调函数</h2> <h2 id="callbacks">回调函数</h2>
<table> <table>
<tr><th>回调</th><th>签名</th><th>触发时机</th></tr> <tr><th>回调</th><th>签名</th><th>触发时机</th></tr>
<tr><td>onBeforeShow</td><td>(toast: ToastInstance) => boolean | void</td><td>Toast DOM 创建前返回 false 阻止显示</td></tr> <tr><td>onBeforeShow</td><td>(toast) =&gt; boolean | void</td><td>DOM 创建前返回 false 阻止显示</td></tr>
<tr><td>onShow</td><td>(toast: ToastInstance) => void</td><td>Toast DOM 创建并播放入场动画后</td></tr> <tr><td>onShow</td><td>(toast) =&gt; void</td><td>创建并播放入场动画后</td></tr>
<tr><td>onClose</td><td>(toast: ToastInstance) => void</td><td>Toast DOM 移除后(离场动画完成</td></tr> <tr><td>onClose</td><td>(toast) =&gt; void</td><td>DOM 移除后(离场完成)</td></tr>
<tr><td>onClick</td><td>(toast: ToastInstance) => void</td><td>Toast 被点击时(closeOnClicktrue 时还会自动关闭)</td></tr> <tr><td>onClick</td><td>(toast) =&gt; void</td><td>点击时(closeOnClick=true 时还会关闭)</td></tr>
<tr><td>onUpdate</td><td>(toast: ToastInstance) => void</td><td>Toast 内容通过 update() 更新</td></tr> <tr><td>onUpdate</td><td>(toast) =&gt; void</td><td>update() 后</td></tr>
<tr><td>onError</td><td>({ hook, source, error, toast }) => void</td><td>钩子回调或定时器发生异常时(全局配置,适用于接入错误监控)</td></tr> <tr><td>onError</td><td>({ hook, source, error, toast }) =&gt; void</td><td>钩子/定时器异常时</td></tr>
</table> </table>
<h2 id="animations">动画列表</h2> <h2 id="animations">动画列表</h2>
<p>配置 <code>animation</code> 的值,支持以下 CSS 动画。未在此列表中的值将 fallback 到 slide。</p> <p>未注册的动画名自动 fallback 到 slide。</p>
<table> <table>
<tr><th>名称</th><th>效果</th><th>时长</th></tr> <tr><th>名称</th><th>效果</th><th>时长</th></tr>
<tr><td>slide</td><td>从右侧滑入 + 回弹</td><td>400ms</td></tr> <tr><td>slide</td><td>从右侧滑入 + 回弹</td><td>400ms</td></tr>
@@ -509,20 +452,13 @@ npm install @metona-team/metona-toast react
<tr><td>flip</td><td>3D 翻转入场 + 回摆</td><td>500ms</td></tr> <tr><td>flip</td><td>3D 翻转入场 + 回摆</td><td>500ms</td></tr>
<tr><td>rotate</td><td>旋转摇摆进入</td><td>500ms</td></tr> <tr><td>rotate</td><td>旋转摇摆进入</td><td>500ms</td></tr>
<tr><td>zoom</td><td>从中心爆发式弹出</td><td>500ms</td></tr> <tr><td>zoom</td><td>从中心爆发式弹出</td><td>500ms</td></tr>
<tr><td>slideUp</td><td>从下方弹入</td><td>400ms</td></tr> <tr><td>slideUp / slideDown</td><td>从下方 / 上方弹入</td><td>400ms</td></tr>
<tr><td>slideDown</td><td>上方弹</td><td>400ms</td></tr> <tr><td>slideLeft / slideRight</td><td>左侧 / 右侧滑</td><td>400ms</td></tr>
<tr><td>slideLeft</td><td>从左侧滑入</td><td>400ms</td></tr>
<tr><td>slideRight</td><td>从右侧滑入</td><td>400ms</td></tr>
</table> </table>
<h2 id="themes">主题参考</h2> <h2 id="themes">主题参考</h2>
<pre><span class="cm">// 内置主题</span> <div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">theme.js</span></div>
light <span class="cm">— 白色玻璃质感</span> <pre><span class="cm">// 内置主题:light · dark · auto(跟随系统)· warm</span>
dark <span class="cm">— 深色玻璃质感</span>
auto <span class="cm">— 跟随系统主题设置</span>
warm <span class="cm">— 暖色调</span>
<span class="cm">// 注册自定义主题</span>
MeToast.themes.<span class="fn">registerTheme</span>(<span class="s">'ocean'</span>, { MeToast.themes.<span class="fn">registerTheme</span>(<span class="s">'ocean'</span>, {
<span class="fn">bg</span>: <span class="s">'rgba(240,249,255,0.96)'</span>, <span class="fn">bg</span>: <span class="s">'rgba(240,249,255,0.96)'</span>,
<span class="fn">text</span>: <span class="s">'#0c4a6e'</span>, <span class="fn">text</span>: <span class="s">'#0c4a6e'</span>,
@@ -532,19 +468,38 @@ MeToast.themes.<span class="fn">registerTheme</span>(<span class="s">'ocean'</sp
<span class="fn">progressBg</span>: <span class="s">'rgba(14,165,233,0.1)'</span>, <span class="fn">progressBg</span>: <span class="s">'rgba(14,165,233,0.1)'</span>,
<span class="fn">closeHoverBg</span>: <span class="s">'rgba(14,165,233,0.1)'</span>, <span class="fn">closeHoverBg</span>: <span class="s">'rgba(14,165,233,0.1)'</span>,
}); });
MeToast.themes.<span class="fn">switchTheme</span>(<span class="s">'ocean'</span>);</pre> MeToast.themes.<span class="fn">switchTheme</span>(<span class="s">'ocean'</span>);</pre></div>
</main> </main>
<script src="../dist/metona-toast.js"></script> <script src="../dist/metona-toast.js"></script>
<script> <script>
MeToast.configure({ position: 'top-right', duration: 4000, theme: 'dark', animation: 'slide' }); MeToast.configure({ position: 'top-right', duration: 4000, theme: 'dark', animation: 'slide' });
document.querySelectorAll('.sidebar a[href^="#"]').forEach(a => {
a.addEventListener('click', () => { // 侧边栏滚动高亮
document.querySelectorAll('.sidebar a').forEach(x => x.classList.remove('active')); const links = document.querySelectorAll('.sidebar a.snav');
a.classList.add('active'); const sections = [];
}); links.forEach(l => {
const id = l.getAttribute('href').slice(1);
const el = document.getElementById(id);
if (el) sections.push({ id, el, link: l });
}); });
const spy = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
links.forEach(l => l.classList.remove('active'));
const hit = sections.find(s => s.el === e.target);
if (hit) hit.link.classList.add('active');
}
});
}, { rootMargin: '-15% 0px -70% 0px' });
sections.forEach(s => spy.observe(s.el));
// 点击关闭高亮
links.forEach(l => l.addEventListener('click', () => {
links.forEach(x => x.classList.remove('active'));
l.classList.add('active');
}));
</script> </script>
</body> </body>
</html> </html>
+355 -146
View File
@@ -5,62 +5,231 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<title>MetonaToast — 轻量精致的 Toast 通知库</title> <title>MetonaToast — 轻量精致的 Toast 通知库</title>
<meta name="description" content="MetonaToast 是轻量、零依赖、精致美观的 Toast 通知库。107 种图标类型、11 种动画、主题系统、国际化、插件架构。"> <meta name="description" content="MetonaToast 是轻量、零依赖、精致美观的 Toast 通知库。107 种图标、11 种动画、主题系统、国际化、插件架构、React 适配器。">
<style> <style>
:root { :root {
--bg: #0b1121; --bg2: #111827; --surface: #1a2332; --bg: #070b14;
--border: #1e2d4d; --text: #e2e8f0; --muted: #64748b; --surface: rgba(255,255,255,0.03);
--accent: #6366f1; --accent2: #818cf8; --radius: 14px; --surface-hover: rgba(255,255,255,0.06);
--border: rgba(255,255,255,0.08);
--text: #e8ecf4;
--muted: #8b94a7;
--accent1: #6366f1;
--accent2: #a855f7;
--accent3: #ec4899;
--grad: linear-gradient(135deg, #818cf8, #c084fc, #f472b6);
--radius: 16px;
} }
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin:0; padding:0; box-sizing:border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; background: var(--bg); color: var(--text); line-height: 1.7; } html { scroll-behavior:smooth; }
a { color: var(--accent2); text-decoration: none; } body {
code { background: rgba(99,102,241,0.12); color: #a5b4fc; padding: 2px 7px; border-radius: 5px; font-size: 0.9em; } font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;
pre { background: #0a0f1a; border: 1px solid var(--border); border-radius: 10px; padding: 18px 22px; overflow-x: auto; font: 13px/1.8 'SF Mono','Cascadia Code','Fira Code',Consolas,monospace; color: #cbd5e1; } background:var(--bg); color:var(--text); line-height:1.7; overflow-x:hidden;
pre .kw { color: #c084fc; } pre .fn { color: #60a5fa; } pre .s { color: #34d399; } pre .cm { color: #475569; } background-image:
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(99,102,241,0.15), transparent),
radial-gradient(ellipse 60% 40% at 90% 10%, rgba(168,85,247,0.08), transparent),
radial-gradient(ellipse 50% 40% at 10% 30%, rgba(236,72,153,0.06), transparent);
}
/* 背景网格 */
body::before {
content:''; position:fixed; inset:0; pointer-events:none; z-index:0;
background-image:
linear-gradient(rgba(255,255,255,0.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.025) 1px, transparent 1px);
background-size:56px 56px;
mask-image: radial-gradient(ellipse 90% 70% at 50% 0%, black 40%, transparent 100%);
-webkit-mask-image: radial-gradient(ellipse 90% 70% at 50% 0%, black 40%, transparent 100%);
}
a { color:#a5b4fc; text-decoration:none; transition:color .2s; }
a:hover { color:#c7d2fe; }
::selection { background:rgba(139,92,246,.4); }
.nav { position: sticky; top: 0; z-index: 100; background: rgba(11,17,33,0.88); backdrop-filter: blur(14px); border-bottom: 1px solid var(--border); } .grad-text {
.nav-inner { max-width: 1200px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; height: 56px; } background:var(--grad);
.nav-logo { font-size: 18px; font-weight: 700; background: linear-gradient(135deg, #818cf8, #c084fc, #f472b6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } -webkit-background-clip:text; background-clip:text;
.nav-links { display: flex; gap: 24px; } -webkit-text-fill-color:transparent; color:transparent;
.nav-links a { color: var(--muted); font-size: 14px; font-weight: 500; transition: .2s; } }
.nav-links a:hover { color: var(--accent2); }
.hero { text-align: center; padding: 100px 20px 70px; background: radial-gradient(ellipse at 50% 0%, rgba(99,102,241,0.18) 0%, transparent 60%), linear-gradient(180deg, #0f172a 0%, var(--bg) 100%); border-bottom: 1px solid var(--border); } /* ===== Nav ===== */
.hero h1 { font-size: 52px; font-weight: 800; } .nav {
.hero h1 span { background: linear-gradient(135deg, #818cf8, #c084fc, #f472b6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } position:sticky; top:0; z-index:100;
.hero p { color: var(--muted); font-size: 17px; margin-top: 12px; max-width: 600px; margin-left: auto; margin-right: auto; } background:rgba(7,11,20,0.7);
.hero .badges { display: flex; gap: 12px; justify-content: center; margin-top: 24px; flex-wrap: wrap; } backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px);
.hero .badges span { background: var(--surface); border: 1px solid var(--border); border-radius: 20px; padding: 6px 18px; font-size: 13px; color: var(--accent2); } border-bottom:1px solid var(--border);
.hero-actions { margin-top: 32px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; } }
.btn { padding: 11px 28px; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer; transition: .2s; border: none; } .nav-inner {
.btn-primary { background: var(--accent); color: #fff; } max-width:1180px; margin:0 auto; display:flex; align-items:center;
.btn-primary:hover { background: var(--accent2); transform: translateY(-1px); } justify-content:space-between; padding:0 28px; height:60px;
.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); } }
.btn-outline:hover { border-color: var(--accent); color: var(--accent2); } .nav-logo { font-size:19px; font-weight:800; letter-spacing:.2px; cursor:pointer; }
.nav-links { display:flex; gap:26px; }
.nav-links a { color:var(--muted); font-size:14px; font-weight:500; position:relative; }
.nav-links a::after {
content:''; position:absolute; left:0; right:100%; bottom:-6px; height:2px;
background:var(--grad); border-radius:2px; transition:right .25s;
}
.nav-links a:hover::after { right:0; }
.nav-links a:hover { color:var(--text); }
.container { max-width: 1100px; margin: 0 auto; padding: 60px 24px; } /* ===== Hero ===== */
.section-title { font-size: 28px; font-weight: 700; text-align: center; margin-bottom: 8px; } .hero { text-align:center; padding:110px 24px 60px; position:relative; z-index:1; }
.section-desc { color: var(--muted); text-align: center; margin-bottom: 40px; font-size: 15px; } .hero-badge {
display:inline-flex; align-items:center; gap:8px;
background:rgba(99,102,241,0.1); border:1px solid rgba(99,102,241,0.3);
color:#a5b4fc; font-size:12px; font-weight:600; letter-spacing:1px;
padding:6px 16px; border-radius:999px; margin-bottom:26px;
}
.hero-badge .dot {
width:7px; height:7px; border-radius:50%; background:#34d399;
box-shadow:0 0 12px #34d399; animation:pulse 2s infinite;
}
@keyframes pulse { 50% { opacity:.4; } }
.hero h1 { font-size:clamp(42px, 7vw, 76px); font-weight:800; letter-spacing:-1px; line-height:1.1; }
.hero h1 .grad-text { animation:shimmer 6s linear infinite; }
@keyframes shimmer {
0%,100% { filter:hue-rotate(0deg); }
50% { filter:hue-rotate(30deg); }
}
.hero-sub {
color:var(--muted); font-size:clamp(15px, 2.2vw, 18px);
max-width:640px; margin:18px auto 0;
}
.hero-badges { display:flex; gap:10px; justify-content:center; margin-top:26px; flex-wrap:wrap; }
.hero-badges span {
background:var(--surface); border:1px solid var(--border); border-radius:999px;
padding:5px 14px; font-size:12.5px; color:#cbd5e1;
transition:border-color .2s, transform .2s;
}
.hero-badges span:hover { border-color:rgba(129,140,248,.5); transform:translateY(-2px); }
.hero-actions { margin-top:36px; display:flex; gap:14px; justify-content:center; flex-wrap:wrap; }
.btn {
padding:12px 30px; border-radius:12px; font-size:14.5px; font-weight:600;
cursor:pointer; transition:transform .2s, box-shadow .2s, border-color .2s;
border:1px solid transparent;
}
.btn-primary {
background:var(--grad); color:#fff;
box-shadow:0 8px 30px -8px rgba(139,92,246,.6);
}
.btn-primary:hover { transform:translateY(-2px); box-shadow:0 12px 40px -8px rgba(139,92,246,.8); }
.btn-ghost { background:var(--surface); border-color:var(--border); color:var(--text); }
.btn-ghost:hover { border-color:rgba(129,140,248,.5); transform:translateY(-2px); background:var(--surface-hover); }
.features { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 18px; } /* 浮动光球 */
.feature { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 28px; transition: .2s; } .orb { position:absolute; border-radius:50%; filter:blur(70px); pointer-events:none; z-index:0; }
.feature:hover { border-color: var(--accent); } .orb-1 { width:340px; height:340px; background:rgba(99,102,241,.25); top:-80px; left:12%; animation:float 12s ease-in-out infinite; }
.feature-icon { font-size: 28px; margin-bottom: 12px; } .orb-2 { width:260px; height:260px; background:rgba(236,72,153,.18); top:40px; right:10%; animation:float 15s ease-in-out infinite reverse; }
.feature h3 { font-size: 16px; margin-bottom: 6px; } .orb-3 { width:200px; height:200px; background:rgba(168,85,247,.2); bottom:-40px; left:40%; animation:float 18s ease-in-out infinite 2s; }
.feature p { color: var(--muted); font-size: 13px; line-height: 1.6; } @keyframes float { 0%,100%{transform:translateY(0) translateX(0);} 50%{transform:translateY(-30px) translateX(20px);} }
.code-block { max-width: 750px; margin: 0 auto; } /* ===== 数据统计 ===== */
.stats { position:relative; z-index:1; max-width:1000px; margin:10px auto 0; padding:0 24px; }
.stats-grid {
display:grid; grid-template-columns:repeat(auto-fit, minmax(150px, 1fr)); gap:1px;
background:var(--border); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden;
}
.stat { background:rgba(10,15,26,.85); padding:22px 12px; text-align:center; backdrop-filter:blur(8px); }
.stat-num { font-size:26px; font-weight:800; }
.stat-label { font-size:12px; color:var(--muted); margin-top:2px; letter-spacing:.5px; }
.theme-demo { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin: 24px 0; } /* ===== 通用 Section ===== */
.theme-btn { padding: 10px 20px; border-radius: 8px; border: 1px solid var(--border); cursor: pointer; background: var(--surface); color: var(--text); font-size: 13px; font-weight: 500; transition: .2s; } .container { position:relative; z-index:1; max-width:1100px; margin:0 auto; padding:80px 24px; }
.theme-btn:hover { border-color: var(--accent); } .section-title { font-size:30px; font-weight:800; text-align:center; letter-spacing:-.5px; }
.section-desc { color:var(--muted); text-align:center; margin-top:8px; font-size:15px; }
footer { text-align: center; padding: 40px 24px; color: var(--muted); font-size: 13px; border-top: 1px solid var(--border); } /* ===== 特性卡片 ===== */
.features { display:grid; grid-template-columns:repeat(auto-fill, minmax(300px, 1fr)); gap:16px; margin-top:44px; }
.feature {
background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
padding:26px 24px; transition:transform .25s, border-color .25s, background .25s;
position:relative; overflow:hidden;
}
.feature::before {
content:''; position:absolute; top:0; left:0; right:0; height:2px;
background:var(--grad); opacity:0; transition:opacity .25s;
}
.feature:hover { transform:translateY(-4px); border-color:rgba(129,140,248,.4); background:var(--surface-hover); }
.feature:hover::before { opacity:1; }
.feature-icon { font-size:26px; margin-bottom:12px; display:inline-block; }
.feature h3 { font-size:15.5px; margin-bottom:6px; font-weight:700; }
.feature p { color:var(--muted); font-size:13px; line-height:1.65; }
.feature code {
background:rgba(99,102,241,0.15); color:#a5b4fc;
padding:1px 6px; border-radius:5px; font-size:12px;
}
@media (max-width: 768px) { /* ===== 代码块 ===== */
.hero h1 { font-size: 32px; } .code-block { max-width:760px; margin:36px auto 0; }
.nav-links { gap: 14px; } .terminal {
background:#0a0f1c; border:1px solid var(--border); border-radius:12px; overflow:hidden;
box-shadow:0 20px 60px -20px rgba(0,0,0,.6);
}
.terminal-bar {
display:flex; align-items:center; gap:7px; padding:11px 16px;
background:rgba(255,255,255,0.03); border-bottom:1px solid var(--border);
}
.terminal-bar .t-dot { width:11px; height:11px; border-radius:50%; }
.terminal-bar .t-dot:nth-child(1){ background:#ff5f57; } .terminal-bar .t-dot:nth-child(2){ background:#febc2e; } .terminal-bar .t-dot:nth-child(3){ background:#28c840; }
.terminal-bar .t-title { margin-left:10px; font-size:11.5px; color:var(--muted); letter-spacing:.5px; }
.terminal pre {
padding:20px 22px; overflow-x:auto;
font:13px/1.8 'SF Mono','Cascadia Code','Fira Code',Consolas,monospace;
color:#cbd5e1;
}
.terminal pre .kw { color:#c084fc; } .terminal pre .fn { color:#60a5fa; }
.terminal pre .s { color:#34d399; } .terminal pre .cm { color:#475569; }
.terminal pre .num { color:#f472b6; }
/* ===== 主题预览 ===== */
.theme-demo { display:flex; gap:12px; justify-content:center; flex-wrap:wrap; margin-top:36px; }
.theme-btn {
padding:11px 22px; border-radius:10px; border:1px solid var(--border);
cursor:pointer; background:var(--surface); color:var(--text);
font-size:13.5px; font-weight:500; transition:all .2s;
}
.theme-btn:hover { border-color:rgba(129,140,248,.5); transform:translateY(-2px); }
.theme-btn.active {
background:var(--grad); color:#fff; border-color:transparent;
box-shadow:0 6px 24px -6px rgba(139,92,246,.6);
}
/* ===== 环境支持 ===== */
.env-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(160px, 1fr)); gap:12px; margin-top:40px; }
.env-card {
background:var(--surface); border:1px solid var(--border); border-radius:12px;
padding:20px 14px; text-align:center; transition:border-color .2s, transform .2s;
}
.env-card:hover { border-color:rgba(129,140,248,.4); transform:translateY(-3px); }
.env-card .e-ico { font-size:26px; }
.env-card h3 { font-size:14px; margin:8px 0 2px; }
.env-card p { font-size:12.5px; color:var(--muted); }
/* ===== CTA ===== */
.cta {
max-width:760px; margin:0 auto; text-align:center;
background:var(--grad); border-radius:20px; padding:56px 32px;
position:relative; overflow:hidden;
}
.cta::before {
content:''; position:absolute; inset:0;
background:radial-gradient(ellipse 60% 60% at 50% 120%, rgba(255,255,255,.25), transparent);
}
.cta h2 { font-size:30px; font-weight:800; color:#fff; position:relative; }
.cta p { color:rgba(255,255,255,.85); margin-top:10px; position:relative; }
.cta .btn { margin-top:26px; background:#fff; color:#6d28d9; position:relative; }
.cta .btn:hover { transform:translateY(-2px); }
footer { text-align:center; padding:44px 24px; color:var(--muted); font-size:13px; border-top:1px solid var(--border); position:relative; z-index:1; }
footer a { color:#a5b4fc; }
/* 滚动淡入 */
.reveal { opacity:0; transform:translateY(24px); transition:opacity .6s ease, transform .6s ease; }
.reveal.visible { opacity:1; transform:none; }
@media (max-width:768px) {
.hero { padding:80px 20px 44px; }
.nav-links { gap:16px; }
.container { padding:56px 20px; }
} }
</style> </style>
</head> </head>
@@ -68,7 +237,7 @@
<nav class="nav"> <nav class="nav">
<div class="nav-inner"> <div class="nav-inner">
<div class="nav-logo">MetonaToast</div> <div class="nav-logo"><span class="grad-text">MetonaToast</span></div>
<div class="nav-links"> <div class="nav-links">
<a href="index.html">首页</a> <a href="index.html">首页</a>
<a href="docs.html">文档</a> <a href="docs.html">文档</a>
@@ -79,117 +248,117 @@
</nav> </nav>
<header class="hero"> <header class="hero">
<h1><span>MetonaToast</span></h1> <div class="orb orb-1"></div>
<p>轻量 · 零依赖 · 精致美观的 Toast 通知库。TypeScript 严格模式源码,gzip 不到 10KB。</p> <div class="orb orb-2"></div>
<div class="badges"> <div class="orb orb-3"></div>
<span>📦 &lt;10KB gzip</span><span>🎨 107 图标</span><span>🎭 11 动画</span><span>🌍 国际化</span><span>🔌 插件</span><span>📘 TypeScript</span> <div class="hero-badge"><span class="dot"></span> v0.5.0 · 385 测试 · 覆盖率 96%</div>
<h1><span class="grad-text">MetonaToast</span></h1>
<p class="hero-sub">轻量 · 零依赖 · 精致美观的 Toast 通知库。TypeScript 严格模式,gzip 不到 10KB,一切开箱即用。</p>
<div class="hero-badges">
<span>📦 &lt;10KB gzip</span>
<span>🎨 107 图标</span>
<span>🎭 11 动画</span>
<span>🧩 4 插件</span>
<span>🔗 17 钩子</span>
<span>⚛️ React 适配器</span>
<span>🌍 国际化</span>
</div> </div>
<div class="hero-actions"> <div class="hero-actions">
<button class="btn btn-primary" onclick="demoQuickStart()">快速预览</button> <button class="btn btn-primary" onclick="demoQuickStart()">快速预览</button>
<button class="btn btn-outline" onclick="location.href='docs.html'">查看文档</button> <button class="btn btn-ghost" onclick="location.href='docs.html'">📖 查看文档</button>
<button class="btn btn-ghost" onclick="location.href='demo.html'">🎮 在线演示</button>
</div> </div>
</header> </header>
<section class="stats reveal">
<div class="stats-grid">
<div class="stat"><div class="stat-num grad-text">&lt;10KB</div><div class="stat-label">gzip 体积</div></div>
<div class="stat"><div class="stat-num grad-text">107</div><div class="stat-label">内置图标</div></div>
<div class="stat"><div class="stat-num grad-text">11</div><div class="stat-label">动画效果</div></div>
<div class="stat"><div class="stat-num grad-text">17</div><div class="stat-label">生命周期钩子</div></div>
<div class="stat"><div class="stat-num grad-text">385</div><div class="stat-label">测试用例</div></div>
<div class="stat"><div class="stat-num grad-text">96%</div><div class="stat-label">行覆盖率</div></div>
</div>
</section>
<main class="container"> <main class="container">
<h2 class="section-title">核心特性</h2> <h2 class="section-title reveal">核心特性</h2>
<p class="section-desc">一切你需要的,都在一个不到 10KB 的包中</p> <p class="section-desc reveal">你需要的一切,都在一个不到 10KB 的包中</p>
<div class="features"> <div class="features">
<div class="feature"> <div class="feature reveal">
<div class="feature-icon"></div>
<h3>零依赖</h3>
<p>TypeScript 严格模式,纯原生实现,无任何运行时依赖。兼容所有现代浏览器。</p>
</div>
<div class="feature reveal">
<div class="feature-icon">🎨</div> <div class="feature-icon">🎨</div>
<h3>107 种图标类型</h3> <h3>107 种图标类型</h3>
<p>success / error / warning / info / loading 及 100+ 扩展类型,覆盖所有常见场景。</p> <p>success / error / warning / info / loading 及 100+ 扩展类型,覆盖所有常见场景。</p>
</div> </div>
<div class="feature"> <div class="feature reveal">
<div class="feature-icon">🎭</div> <div class="feature-icon">🎭</div>
<h3>11 种动画效果</h3> <h3>11 种动画 + 自定义</h3>
<p>slide / fade / scale / bounce / flip / rotate / zoom / slideUp / slideDown / slideLeft / slideRight,每种感官差异明显</p> <p>每种感官差异明显,<code>animations.register()</code> 注册自定义动画,动态注入 keyframes 真实生效</p>
</div> </div>
<div class="feature"> <div class="feature reveal">
<div class="feature-icon">🖌️</div> <div class="feature-icon">🖌️</div>
<h3>主题系统</h3> <h3>主题系统</h3>
<p>内置 light / dark / auto / warm 四种主题,支持 <code>registerTheme()</code> 自定义注册任意主题</p> <p>light / dark / auto / warm 内置主题,<code>registerTheme()</code> 自定义,跟随系统自动切换</p>
</div> </div>
<div class="feature"> <div class="feature reveal">
<div class="feature-icon">🌍</div> <div class="feature-icon">🌍</div>
<h3>国际化</h3> <h3>国际化</h3>
<p>内置 zh-CN en-US 完整翻译,400+ 词汇。支持 <code>addTranslations()</code> 扩展任意语言。</p> <p>zh-CN / en-US 完整翻译,<code>addTranslations()</code> 扩展任意语言RTL 完整适配</p>
</div> </div>
<div class="feature"> <div class="feature reveal">
<div class="feature-icon">🔌</div> <div class="feature-icon">🧩</div>
<h3>插件系统</h3> <h3>插件系统</h3>
<p>3 款预设插件 + 自定义插件架构。keyboard / persistence / accessibility 开箱即用</p> <p>4 款预设keyboard / persistence / accessibility / dedupe,支持自定义插件架构</p>
</div> </div>
<div class="feature"> <div class="feature reveal">
<div class="feature-icon">🔗</div>
<h3>17 个钩子</h3>
<p>完整生命周期钩子,<code>beforeShow</code> / <code>beforeClose</code> / <code>beforeUpdate</code> 支持拦截。</p>
</div>
<div class="feature reveal">
<div class="feature-icon"></div> <div class="feature-icon"></div>
<h3>拖拽关闭</h3> <h3>拖拽关闭</h3>
<p>任意方向拖动 Toast 即可关闭。支持 Pointer Events,移动端和桌面端体验一致。</p> <p>任意方向拖动即关,阈值 <code>dragThreshold</code> 可配置。Pointer Events 全端一致。</p>
</div> </div>
<div class="feature"> <div class="feature reveal">
<div class="feature-icon"></div> <div class="feature-icon">⚛️</div>
<h3>零依赖</h3> <h3>React 适配器</h3>
<p>TypeScript 严格模式,纯原生实现,无需任何运行时依赖。兼容所有现代浏览器</p> <p><code>@metona-team/metona-toast/react</code> — useToast hook + 声明式 Toast 组件,主包保持零依赖</p>
</div> </div>
<div class="feature"> <div class="feature reveal">
<div class="feature-icon">🛡️</div> <div class="feature-icon">🛡️</div>
<h3>错误捕获</h3> <h3>错误捕获</h3>
<p>全局 <code>onError</code> 回调捕获钩子异常和定时器错误,方便接入 Sentry、DataDog 等监控系统</p> <p>全局 <code>onError</code> 回调捕获钩子定时器异常,方便接入 Sentry、DataDog。</p>
</div> </div>
<div class="feature"> <div class="feature reveal">
<div class="feature-icon">⏱️</div> <div class="feature-icon">⏱️</div>
<h3>暂停恢复</h3> <h3>暂停恢复</h3>
<p>悬停自动暂停、拖拽临时暂停、手动 pause/resume,剩余时间精确保持不丢失进度</p> <p>悬停暂停、拖拽暂停、手动 pause/resume,剩余时间精确保持不丢失。</p>
</div> </div>
<div class="feature"> <div class="feature reveal">
<div class="feature-icon">📘</div> <div class="feature-icon">📘</div>
<h3>TypeScript</h3> <h3>类型安全</h3>
<p>严格模式源码,类型从源码自动生成。strictNullChecks、noImplicitAny 全开,零类型漂移</p> <p>严格模式源码,类型自动生成零漂移。加载态转换 id 稳定,原地更新</p>
</div> </div>
</div> </div>
</main> </main>
<section class="container"> <section class="container" style="padding-top:0">
<h2 class="section-title">环境支持</h2> <h2 class="section-title reveal">快速开始</h2>
<p class="section-desc">所有现代浏览器原生支持,无需 polyfill</p> <p class="section-desc reveal">浏览器上仅注册两个全局变量:<code>MeToast</code><code>Met</code>,完全等价</p>
<div class="env-grid" style="display:grid; grid-template-columns:repeat(auto-fill,minmax(165px,1fr)); gap:12px;"> <div class="code-block reveal">
<div class="feature" style="text-align:center;padding:20px 16px;"> <div class="terminal">
<div style="font-size:28px;margin-bottom:6px;">🌐</div> <div class="terminal-bar">
<h3>Chrome</h3> <span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span>
<p>64+</p> <span class="t-title">quickstart.js</span>
</div> </div>
<div class="feature" style="text-align:center;padding:20px 16px;"> <pre><span class="cm"># 安装</span>
<div style="font-size:28px;margin-bottom:6px;">🦊</div>
<h3>Firefox</h3>
<p>63+</p>
</div>
<div class="feature" style="text-align:center;padding:20px 16px;">
<div style="font-size:28px;margin-bottom:6px;">🧭</div>
<h3>Safari</h3>
<p>11+</p>
</div>
<div class="feature" style="text-align:center;padding:20px 16px;">
<div style="font-size:28px;margin-bottom:6px;">🪟</div>
<h3>Edge</h3>
<p>79+</p>
</div>
<div class="feature" style="text-align:center;padding:20px 16px;">
<div style="font-size:28px;margin-bottom:6px;">📦</div>
<h3>Node.js</h3>
<p>16+</p>
</div>
<div class="feature" style="text-align:center;padding:20px 16px;">
<div style="font-size:28px;margin-bottom:6px;">📘</div>
<h3>TypeScript</h3>
<p>4.5+</p>
</div>
</div>
<p style="text-align:center;color:var(--muted);font-size:13px;margin-top:16px">核心依赖:Web Animations API · Pointer Events · CSS Grid · CSS Custom Properties</p>
</section>
<section class="container">
<h2 class="section-title">快速开始</h2>
<p class="section-desc">浏览器 window 上仅注册两个全局变量:<code>MeToast</code><code>Met</code>,完全等价</p>
<div class="code-block">
<pre><span class="cm"># npm 安装</span>
npm install @metona-team/metona-toast npm install @metona-team/metona-toast
<span class="cm">// 导入</span> <span class="cm">// 导入</span>
@@ -202,43 +371,45 @@ MeToast.<span class="fn">loading</span>(<span class="s">'加载中...'</span>);
<span class="cm">// CDN 直接引用</span> <span class="cm">// CDN 直接引用</span>
<span class="kw">&lt;script</span> <span class="fn">src</span>=<span class="s">"https://git.metona.cn/MetonaTeam/MetonaToast/raw/branch/master/dist/metona-toast.min.js"</span><span class="kw">&gt;&lt;/script&gt;</span></pre> <span class="kw">&lt;script</span> <span class="fn">src</span>=<span class="s">"https://git.metona.cn/MetonaTeam/MetonaToast/raw/branch/master/dist/metona-toast.min.js"</span><span class="kw">&gt;&lt;/script&gt;</span></pre>
</div>
</div> </div>
</section> </section>
<section class="container"> <section class="container" style="padding-top:0">
<h2 class="section-title">高级功能速览</h2> <h2 class="section-title reveal">高级功能速览</h2>
<p class="section-desc">不仅仅是一个 Toast——确认框、输入框、进度条、倒计时、队列,应有尽有</p> <p class="section-desc reveal">不仅仅是 Toast确认框、输入框、进度条、倒计时、队列、Action,应有尽有</p>
<div class="code-block"> <div class="code-block reveal">
<pre><span class="cm">// Promise 风格 — 自动 loading → success/error</span> <div class="terminal">
<div class="terminal-bar">
<span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span>
<span class="t-title">advanced.js</span>
</div>
<pre><span class="cm">// Promise 风格 — 自动 loading → success/error</span>
<span class="kw">await</span> MeToast.<span class="fn">promise</span>(<span class="fn">fetch</span>(<span class="s">'/api/data'</span>), { <span class="kw">await</span> MeToast.<span class="fn">promise</span>(<span class="fn">fetch</span>(<span class="s">'/api/data'</span>), {
<span class="fn">loading</span>: <span class="s">'加载中...'</span>, <span class="fn">loading</span>: <span class="s">'加载中...'</span>, <span class="fn">success</span>: <span class="s">'完成'</span>, <span class="fn">error</span>: <span class="s">'失败'</span>
<span class="fn">success</span>: <span class="s">'加载完成!'</span>,
<span class="fn">error</span>: <span class="s">'加载失败'</span>,
}); });
<span class="cm">// 确认对话框 → Promise&lt;boolean&gt;</span> <span class="cm">// 确认 / 输入对话框</span>
<span class="kw">const</span> ok = <span class="kw">await</span> MeToast.<span class="fn">confirm</span>(<span class="s">'确定删除'</span>, { <span class="fn">confirmText</span>: <span class="s">'删除'</span> }); <span class="kw">const</span> ok = <span class="kw">await</span> MeToast.<span class="fn">confirm</span>(<span class="s">'确定删除?'</span>, { <span class="fn">confirmText</span>: <span class="s">'删除'</span> });
<span class="cm">// 输入对话框 → Promise&lt;string|null&gt;</span>
<span class="kw">const</span> name = <span class="kw">await</span> MeToast.<span class="fn">prompt</span>(<span class="s">'请输入姓名'</span>); <span class="kw">const</span> name = <span class="kw">await</span> MeToast.<span class="fn">prompt</span>(<span class="s">'请输入姓名'</span>);
<span class="cm">// Action Toast — 内嵌操作按钮</span> <span class="cm">// Action Toast — 内嵌操作按钮</span>
MeToast.<span class="fn">action</span>(<span class="s">'文件已删除'</span>, [ MeToast.<span class="fn">action</span>(<span class="s">'文件已删除'</span>, [
{ <span class="fn">text</span>: <span class="s">'撤销'</span>, <span class="fn">onClick</span>: () => <span class="fn">restore</span>() }, { <span class="fn">text</span>: <span class="s">'撤销'</span>, <span class="fn">onClick</span>: () => <span class="fn">restore</span>() },
{ <span class="fn">text</span>: <span class="s">'查看'</span>, <span class="fn">onClick</span>: () => <span class="fn">open</span>() }, { <span class="fn">text</span>: <span class="s">'查看'</span>, <span class="fn">onClick</span>: () => <span class="fn">open</span>(), <span class="fn">close</span>: <span class="kw">false</span> },
]); ]);
<span class="cm">// Toast 分组管理</span> <span class="cm">// 加载 → 链式转换(id 稳定)</span>
<span class="kw">const</span> orderGroup = MeToast.<span class="fn">group</span>(<span class="s">'orders'</span>); <span class="kw">const</span> loading = MeToast.<span class="fn">loading</span>(<span class="s">'正在提交...'</span>);
orderGroup.<span class="fn">success</span>(<span class="s">'订单已创建'</span>); <span class="fn">setTimeout</span>(() => loading.<span class="fn">success</span>(<span class="s">'提交成功!'</span>), <span class="num">2000</span>);</pre>
orderGroup.<span class="fn">dismiss</span>(); <span class="cm">// 一键关闭整组</span></pre> </div>
</div> </div>
</section> </section>
<section class="container"> <section class="container" style="padding-top:0">
<h2 class="section-title">主题切换</h2> <h2 class="section-title reveal">主题切换</h2>
<p class="section-desc">点击按钮实时预览四种内置主题</p> <p class="section-desc reveal">点击按钮实时预览内置主题,切换结果自动持久化</p>
<div class="theme-demo"> <div class="theme-demo reveal">
<button class="theme-btn" onclick="switchTheme('light')">☀️ 浅色 Light</button> <button class="theme-btn" onclick="switchTheme('light')">☀️ 浅色 Light</button>
<button class="theme-btn" onclick="switchTheme('dark')">🌙 深色 Dark</button> <button class="theme-btn" onclick="switchTheme('dark')">🌙 深色 Dark</button>
<button class="theme-btn" onclick="switchTheme('auto')">🔄 自动 Auto</button> <button class="theme-btn" onclick="switchTheme('auto')">🔄 自动 Auto</button>
@@ -246,8 +417,34 @@ orderGroup.<span class="fn">dismiss</span>(); <span class="cm">// 一键关闭
</div> </div>
</section> </section>
<section class="container" style="padding-top:0">
<h2 class="section-title reveal">环境支持</h2>
<p class="section-desc reveal">所有现代浏览器原生支持,无需 polyfill</p>
<div class="env-grid reveal">
<div class="env-card"><div class="e-ico">🌐</div><h3>Chrome</h3><p>64+</p></div>
<div class="env-card"><div class="e-ico">🦊</div><h3>Firefox</h3><p>63+</p></div>
<div class="env-card"><div class="e-ico">🧭</div><h3>Safari</h3><p>11+</p></div>
<div class="env-card"><div class="e-ico">🪟</div><h3>Edge</h3><p>79+</p></div>
<div class="env-card"><div class="e-ico">📱</div><h3>iOS Safari</h3><p>11+</p></div>
<div class="env-card"><div class="e-ico">🤖</div><h3>Android</h3><p>64+</p></div>
<div class="env-card"><div class="e-ico">📦</div><h3>Node.js</h3><p>16+</p></div>
<div class="env-card"><div class="e-ico">📘</div><h3>TypeScript</h3><p>4.5+</p></div>
</div>
</section>
<section class="container" style="padding-top:0">
<div class="cta reveal">
<h2>开始使用 MetonaToast</h2>
<p>一条命令安装,立即体验精致优雅的 Toast 通知</p>
<button class="btn" onclick="location.href='demo.html'">🎮 打开在线演示</button>
</div>
</section>
<footer> <footer>
<p>MetonaToast v0.5.0 · MIT License · <a href="https://git.metona.cn/MetonaTeam/MetonaToast">Gitea</a></p> MetonaToast v0.5.0 · MIT License ·
<a href="https://git.metona.cn/MetonaTeam/MetonaToast" target="_blank">Gitea</a> ·
<a href="docs.html">文档</a> ·
<a href="demo.html">演示</a>
</footer> </footer>
<script src="../dist/metona-toast.js"></script> <script src="../dist/metona-toast.js"></script>
@@ -256,16 +453,28 @@ orderGroup.<span class="fn">dismiss</span>(); <span class="cm">// 一键关闭
function demoQuickStart() { function demoQuickStart() {
MeToast.success('✨ 欢迎使用 MetonaToast'); MeToast.success('✨ 欢迎使用 MetonaToast');
setTimeout(() => MeToast.info('💡 试试点击右上角的 Toast 来关闭'), 800); setTimeout(() => MeToast.info('💡 点击 Toast 或点击 × 即可关闭'), 800);
setTimeout(() => MeToast.warning('⚠️ 你也可以拖动 Toast 关闭'), 1600); setTimeout(() => MeToast.warning('⚠️ 拖动 Toast 也能关闭', { draggable: true }), 1600);
setTimeout(() => MeToast.success('🎭 试试 bounce 动画', { animation: 'bounce' }), 2400);
} }
function switchTheme(t) { function switchTheme(t) {
MeToast.themes.switchTheme(t); MeToast.themes.switchTheme(t);
MeToast.configure({ theme: 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: '暖色' }; const names = { light: '浅色', dark: '深色', auto: '自动', warm: '暖色' };
MeToast.info('已切换到「' + names[t] + '」主题', { theme: t }); 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);
</script> </script>
</body> </body>
</html> </html>