Files
MetonaToast/README.md
T
tianhao dd061025e0 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 渲染、图标网格/卡片/代码块完整
2026-08-08 15:11:04 +08:00

373 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MetonaToast
轻量、零依赖、精致美观的 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)
[![license](https://img.shields.io/badge/license-MIT-green.svg)](https://git.metona.cn/MetonaTeam/MetonaToast)
[![coverage](https://img.shields.io/badge/coverage-96%25-brightgreen.svg)](https://git.metona.cn/MetonaTeam/MetonaToast)
## 特性
- **零依赖** — TypeScript 严格模式源码,gzip 后不到 10KB,无任何运行时依赖
- **107 种图标类型** — success/error/warning/info/loading 及 100+ 扩展类型,覆盖所有常见场景
- **11 种动画 + 自定义** — slide/fade/scale/bounce/flip/rotate/zoom/slideUp/slideDown/slideLeft/slideRight`animations.register()` 可注册任意自定义动画(动态注入 keyframes,真实生效)
- **主题系统** — light/dark/auto/warm 四种内置主题,`registerTheme()` 注册自定义主题,跟随系统主题自动切换
- **国际化** — 内置 zh-CN / en-US 完整翻译,`addTranslations()` 扩展任意语言,RTL 语言完整适配
- **插件系统** — 4 款预设插件 (keyboard / persistence / accessibility / dedupe) + 自定义插件
- **完整钩子系统** — 17 个生命周期与事件钩子,`beforeShow` / `beforeClose` / `beforeUpdate` 支持返回 `false` 拦截
- **可拖拽关闭** — 拖动 Toast 任意方向即可关闭,阈值 `dragThreshold` 可配置
- **React 适配器** — `@metona-team/metona-toast/react` 子路径导入,`useToast()` hook + 声明式 `<Toast />` 组件,主包保持零依赖
- **全局错误回调** — `onError` 捕获所有钩子异常和定时器错误,方便接入 Sentry、DataDog 等监控系统
- **工程质量** — 385 个测试全过,行覆盖率 96.42%,lint 零警告,CI 含覆盖率门禁(≥95%)
## 安装
```bash
npm install @metona-team/metona-toast
```
### 引用方式
```javascript
// ES Module
import MeToast from '@metona-team/metona-toast';
// CommonJS
const MeToast = require('@metona-team/metona-toast');
```
```html
<!-- CDN UMD 开发版 -->
<script src="https://git.metona.cn/MetonaTeam/MetonaToast/raw/branch/master/dist/metona-toast.js"></script>
<!-- CDN 压缩版(生产环境推荐) -->
<script src="https://git.metona.cn/MetonaTeam/MetonaToast/raw/branch/master/dist/metona-toast.min.js"></script>
<!-- CDN ES Module -->
<script type="module">
import MeToast from 'https://git.metona.cn/MetonaTeam/MetonaToast/raw/branch/master/dist/metona-toast.esm.js';
</script>
```
## 环境与版本支持
| 环境 | 最低版本 |
|------|---------|
| Chrome | 64+ |
| Firefox | 63+ |
| Safari | 11+ |
| Edge | 79+ |
| Opera | 51+ |
| iOS Safari | 11+ |
| Android Chrome | 64+ |
| Node.js | 16+ESM & CJS 双格式) |
| TypeScript | 4.5+(完整类型声明,类型从源码自动生成) |
核心依赖:Web Animations APIChrome 64+, Firefox 63+, Safari 11+)、Pointer Events、CSS Grid、CSS Custom Properties。
## 快速开始
浏览器 `window` 上注册两个全局变量:`MeToast``Met`,完全等价。
```javascript
import MeToast from '@metona-team/metona-toast';
// 也可以使用 Met — 与 MeToast 完全等价
Met.success('使用 Met 同样可用');
// 基础用法
MeToast.success('操作成功!');
MeToast.error('操作失败!');
MeToast.warning('请注意检查');
MeToast.info('系统维护中');
// 带标题
MeToast.success({ title: '保存成功', message: '文件已同步到云端' });
// 加载 → 链式转换(原地更新同一实例,id 稳定)
const loading = MeToast.loading('正在提交...');
setTimeout(() => loading.success('提交成功!'), 2000);
// Promise 风格 — 自动 loading → success/error
await MeToast.promise(fetch('/api/data'), {
loading: '加载中...',
success: '加载完成!',
error: '加载失败',
});
```
## 全局配置
```javascript
MeToast.configure({
position: 'top-right', // top-left | top-center | top-right | bottom-left | bottom-center | bottom-right
duration: 4000, // 显示时长(ms),0 为不自动关闭
max: 6, // 同一位置最多显示条数,超出关闭最早的
gap: 12, // Toast 间距(px)
offset: 24, // 容器距屏幕边缘(px)
theme: 'auto', // light | dark | auto(跟随系统)| warm
animation: 'slide', // 11 种内置动画或自定义动画名
pauseOnHover: true, // 悬停暂停计时
closeOnClick: true, // 点击关闭
draggable: true, // 允许拖拽关闭
dragThreshold: 120, // 拖拽关闭阈值(px)
showProgress: true, // 显示进度条
progressDirection: 'horizontal', // horizontal | vertical
zIndex: 9999,
width: 360,
locale: 'zh-CN', // 语言
onError: ({ hook, source, error, toast }) => {
// 钩子/定时器异常全局捕获,可接入监控系统
console.error('Toast error:', hook, error);
},
});
```
## 动画效果
11 种内置动画,每种感官差异明显:
| 动画 | 效果描述 | 时长 |
|------|----------|------|
| `slide` | 从右侧滑入 + 轻微过冲回弹 | 400ms |
| `fade` | 纯淡入(blur→清晰),从容优雅 | 500ms |
| `scale` | 弹性放大(0.55→1.07→1.0 | 450ms |
| `bounce` | 从天而降四段弹跳 | 650ms |
| `flip` | 3D 翻转入场 + 回摆 | 500ms |
| `rotate` | 旋转摇摆进入 | 500ms |
| `zoom` | 从中心爆发式弹出 | 500ms |
| `slideUp` | 从下方弹入 | 400ms |
| `slideDown` | 从上方弹入 | 400ms |
| `slideLeft` | 从左侧滑入 | 400ms |
| `slideRight` | 从右侧滑入 | 400ms |
```javascript
MeToast.success('弹跳动画', { animation: 'bounce' });
// 注册自定义动画 — 动态注入 keyframes,真实生效
MeToast.animations.register('myAnim', {
enter: { transform: 'rotate(-30deg) scale(0.5)', opacity: 0 },
leave: { transform: 'rotate(0) scale(1)', opacity: 1 },
duration: 500,
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
});
MeToast.success('自定义动画', { animation: 'myAnim' });
```
## 主题
```javascript
// 切换主题(自动持久化到 localStorage
MeToast.themes.switchTheme('dark');
MeToast.themes.switchTheme('warm');
MeToast.themes.toggleTheme(); // light ↔ dark
// 注册自定义主题
MeToast.themes.registerTheme('ocean', {
bg: 'rgba(240, 249, 255, 0.96)',
text: '#0c4a6e',
border: 'rgba(14, 165, 233, 0.2)',
shadow: '0 10px 36px -10px rgba(14, 165, 233, 0.18)',
hoverShadow: '0 14px 48px -10px rgba(14, 165, 233, 0.22)',
progressBg: 'rgba(14, 165, 233, 0.1)',
closeHoverBg: 'rgba(14, 165, 233, 0.1)',
});
MeToast.themes.switchTheme('ocean');
```
## 高级功能
```javascript
// 确认对话框 → Promise<boolean>
const ok = await MeToast.confirm('确定删除?', { confirmText: '删除', confirmColor: '#ef4444' });
// 输入对话框 → Promise<string|null>
const name = await MeToast.prompt('请输入姓名', { placeholder: '请输入...' });
// 进度条
const p = MeToast.progress('上传中...');
p.setProgress(60);
p.complete('上传完成!');
// 倒计时({seconds} 自动替换)
MeToast.countdown('操作将在 {seconds} 秒后执行', 5, {
onComplete: () => MeToast.success('已执行'),
});
// 队列展示(顺序逐个)
await MeToast.queue(['步骤一', '步骤二', '步骤三'], { delay: 800 });
// 堆叠展示(同时错峰)
MeToast.stack(['消息1', '消息2', '消息3'], { stagger: 150 });
// Action Toast — 内嵌操作按钮(close:false 可多次点击不关闭)
MeToast.action('文件已删除', [
{ text: '撤销', onClick: () => restore(), color: '#3b82f6' },
{ text: '查看', onClick: () => open(), color: '#10b981', close: false },
]);
// 分组管理
const orders = MeToast.group('orders');
orders.success('订单已创建');
orders.count(); // 该组 toast 数量
orders.dismiss(); // 一键关闭整组
```
## 插件
```javascript
// 安装预设插件
MeToast.use('keyboard'); // ESC 关闭所有 Toast
MeToast.use('persistence'); // 配置持久化到 localStorage
MeToast.use('accessibility'); // 屏幕阅读器朗读公告
MeToast.use('dedupe'); // 相同 type+message 自动去重(更新已有实例)
// 自定义插件(install 中注册生命周期钩子)
import MeToast, { Toast } from '@metona-team/metona-toast';
MeToast.use({
name: 'my-plugin',
install() {
Toast.on('afterShow', (toast) => {
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;
});
```
## 国际化
```javascript
MeToast.i18n.switchLocale('en-US');
// 添加语言(嵌套结构支持深度合并)
MeToast.i18n.addTranslations('ja', {
success: '成功',
error: 'エラー',
confirm: '確認',
cancel: 'キャンセル',
});
// 格式化工具(基于 Intl
MeToast.i18n.formatNumber(1234567); // "1,234,567"
MeToast.i18n.formatCurrency(99, 'CNY'); // "¥99.00"
MeToast.i18n.formatDate(new Date());
MeToast.i18n.formatRelativeTime(Date.now() + 3600000); // "1 小时后"
```
## Toast 管理
```javascript
MeToast.count(); // 当前数量
MeToast.getAll(); // 获取原生 Map<id, ToastInstance>
MeToast.getToasts(); // 获取所有实例数组
MeToast.find(id); // 查找实例(不存在返回 undefined)
MeToast.findByType(type); // 按类型过滤
MeToast.findByPosition(pos);// 按位置过滤
MeToast.dismiss(); // 关闭所有
MeToast.dismiss(id); // 关闭指定
MeToast.removeToast(id); // 立即移除(无离场动画)
MeToast.clear(position?); // 按位置清除
MeToast.pauseAll(); // 暂停所有计时
MeToast.resumeAll(); // 恢复所有计时
MeToast.updateAll(partial); // 批量更新
MeToast.destroy(); // 完全销毁(支持重复调用,init() 可恢复)
```
## 回调
```javascript
MeToast.success({
message: '操作成功',
onBeforeShow: (toast) => true, // 返回 false 阻止显示
onShow: (toast) => console.log('显示:', toast.id),
onClose: (toast) => console.log('关闭:', toast.id),
onClick: (toast) => console.log('点击:', toast.id),
onUpdate: (toast) => console.log('更新:', toast.id),
});
// 全局错误回调 — 捕获钩子异常和定时器错误
MeToast.configure({
onError: ({ hook, source, error, toast }) => {
reportError(error, { hook, source, toastId: toast?.id });
},
});
```
## React 适配器
主包保持零依赖;React 适配器通过子路径导入,`react` 为 optional peerDependency,未使用 React 的项目不受影响。
```bash
npm install @metona-team/metona-toast react
```
```tsx
import { useToast, Toast } from '@metona-team/metona-toast/react';
function SubmitButton() {
// 本组件创建的 Toast 会在组件卸载时自动移除
const toast = useToast();
const submit = async () => {
const loading = toast.loading('正在提交...');
try {
await api.submit();
loading.success('提交成功!');
} catch (e) {
loading.error('提交失败');
}
};
return <button onClick={submit}>提交</button>;
}
// 声明式 Toast — props 变化时更新内容,组件卸载时自动移除(autoClose 默认 true
function SaveIndicator({ saving }: { saving: boolean }) {
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
MIT