init: MetonaToast v2.0.0 基线 — 已完成改进(模板抽象/constants拆分/测试增强)

This commit is contained in:
tianhao
2026-06-16 12:39:36 +08:00
commit ff9c7cd218
25 changed files with 20621 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
node_modules/
dist/
coverage/
.DS_Store
+1
View File
@@ -0,0 +1 @@
registry=https://registry.npmmirror.com
+232
View File
@@ -0,0 +1,232 @@
# MetonaToast
轻量、零依赖、精致美观的 Toast 通知库。
[![npm version](https://img.shields.io/npm/v/metona-toast.svg)](https://www.npmjs.com/package/metona-toast)
[![bundle size](https://img.shields.io/bundlephobia/minzip/metona-toast)](https://bundlephobia.com/package/metona-toast)
[![license](https://img.shields.io/npm/l/metona-toast.svg)](https://github.com/metona/metona-toast/blob/main/LICENSE)
## 特性
- **零依赖** — 纯原生 JavaScriptgzip 后不到 10KB
- **80+ 图标类型** — success/error/warning/info/loading 及更多扩展类型
- **11 种动画** — slide/fade/scale/bounce/flip/rotate/zoom/slideUp/slideDown/slideLeft/slideRight,每种效果明显不同
- **主题系统** — light/dark/auto/warm 四种内置主题,支持 `registerTheme()` 注册自定义主题
- **国际化** — 内置 zh-CN / en-US 完整翻译,可通过 `addTranslations()` 扩展
- **插件系统** — 3 款预设插件 (keyboard/persistence/accessibility) + 自定义插件
- **可拖拽关闭** — 拖动 Toast 任意方向即可关闭
- **TypeScript** — 完整类型定义
## 安装
```bash
npm install metona-toast
```
### 引用方式
```javascript
// ES Module
import MeToast from 'metona-toast';
// CommonJS
const MeToast = require('metona-toast');
```
```html
<!-- CDN UMD 开发版 -->
<script src="https://unpkg.com/metona-toast@2.0.0/dist/metona-toast.js"></script>
<!-- CDN 压缩版(生产环境推荐) -->
<script src="https://unpkg.com/metona-toast@2.0.0/dist/metona-toast.min.js"></script>
<!-- CDN ES Module -->
<script type="module">
import MeToast from 'https://unpkg.com/metona-toast@2.0.0/dist/metona-toast.esm.js';
</script>
```
## 快速开始
```javascript
import MeToast from 'metona-toast';
// 基础用法
MeToast.success('操作成功!');
MeToast.error('操作失败!');
MeToast.warning('请注意检查');
MeToast.info('系统维护中');
// 带标题
MeToast.success({ title: '保存成功', message: '文件已同步到云端' });
// 加载 → 成功链式转换
const loading = MeToast.loading('正在提交...');
setTimeout(() => loading.success('提交成功!'), 2000);
// Promise 风格
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, // 同时最多显示条数
theme: 'auto', // light | dark | auto | warm
animation: 'slide', // slide | fade | scale | bounce | flip | rotate | zoom | slideUp | slideDown | slideLeft | slideRight
pauseOnHover: true, // 悬停暂停计时
closeOnClick: true, // 点击关闭
showProgress: true, // 显示进度条
draggable: true, // 允许拖拽关闭
locale: 'zh-CN', // 语言
});
```
## 动画效果
11 种动画,每种感官差异明显:
| 动画 | 效果描述 |
|------|----------|
| `slide` | 从右侧滑入 + 轻微过冲回弹 |
| `fade` | 纯淡入(blur→清晰),从容优雅 |
| `scale` | 弹性放大(0.55→1.07→1.0 |
| `bounce` | 从天而降四段弹跳 |
| `flip` | 3D 翻转入场 + 回摆 |
| `rotate` | 旋转摇摆进入 |
| `zoom` | 从中心爆发式弹出 |
| `slideUp` | 从下方弹入 |
| `slideDown` | 从上方弹入 |
| `slideLeft` | 从左侧滑入 |
| `slideRight` | 从右侧滑入 |
```javascript
MeToast.success('弹跳动画', { animation: 'bounce' });
// 注册自定义动画
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)',
});
```
## 主题
```javascript
// 切换主题
MeToast.themes.switchTheme('dark');
MeToast.themes.switchTheme('warm');
// 注册自定义主题
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 });
```
## 插件
```javascript
// 安装预设插件
MeToast.use('keyboard'); // ESC 关闭所有 Toast
MeToast.use('persistence'); // 配置持久化到 localStorage
MeToast.use('accessibility'); // 屏幕阅读器朗读公告
// 自定义插件(通过 install 中注册 Toast 生命周期钩子)
import MeToast, { Toast } from 'metona-toast';
MeToast.use({
name: 'my-plugin',
install() {
Toast.on('afterShow', (toast) => {
console.log('Toast shown:', toast.message);
});
},
});
```
## 国际化
```javascript
MeToast.i18n.switchLocale('en-US');
// 添加语言
MeToast.i18n.addTranslations('ja', {
success: '成功',
error: 'エラー',
confirm: '確認',
cancel: 'キャンセル',
});
// 格式化工具
MeToast.i18n.formatNumber(1234567); // "1,234,567"
MeToast.i18n.formatCurrency(99, 'CNY'); // "¥99.00"
```
## Toast 管理
```javascript
MeToast.count(); // 当前数量
MeToast.getToasts(); // 获取所有实例
MeToast.dismiss(); // 关闭所有
MeToast.dismiss(id); // 关闭指定
MeToast.pauseAll(); // 暂停所有计时
MeToast.resumeAll(); // 恢复所有计时
MeToast.destroy(); // 完全销毁
```
## 回调
```javascript
MeToast.success({
message: '操作成功',
onShow: (toast) => console.log('显示:', toast.id),
onClose: (toast) => console.log('关闭:', toast.id),
onClick: (toast) => console.log('点击:', toast.id),
});
```
## License
MIT
+16
View File
@@ -0,0 +1,16 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
modules: 'commonjs',
},
],
],
plugins: [
'@babel/plugin-transform-modules-commonjs',
],
};
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# MetonaToast 构建脚本
echo "🍞 MetonaToast 构建脚本"
echo "========================"
# 检查Node.js
if ! command -v node &> /dev/null; then
echo "❌ 错误: 未找到Node.js"
exit 1
fi
# 检查npm
if ! command -v npm &> /dev/null; then
echo "❌ 错误: 未找到npm"
exit 1
fi
echo "✅ Node.js版本: $(node -v)"
echo "✅ npm版本: $(npm -v)"
# 安装依赖
echo ""
echo "📦 安装依赖..."
npm install
if [ $? -ne 0 ]; then
echo "❌ 依赖安装失败"
exit 1
fi
echo "✅ 依赖安装完成"
# 运行测试
echo ""
echo "🧪 运行测试..."
npm test
if [ $? -ne 0 ]; then
echo "❌ 测试失败"
exit 1
fi
echo "✅ 测试通过"
# 构建项目
echo ""
echo "🔨 构建项目..."
npm run build
if [ $? -ne 0 ]; then
echo "❌ 构建失败"
exit 1
fi
echo "✅ 构建完成"
# 显示构建结果
echo ""
echo "📊 构建结果:"
echo "========================"
ls -lh dist/
echo ""
echo "✅ 构建成功完成!"
echo ""
echo "📁 文件结构:"
echo " - dist/metona-toast.js (UMD格式)"
echo " - dist/metona-toast.esm.js (ES Module格式)"
echo " - dist/metona-toast.cjs.js (CommonJS格式)"
echo " - dist/metona-toast.min.js (压缩版本)"
echo " - dist/metona-toast.d.ts (TypeScript声明)"
echo ""
echo "🚀 使用方法:"
echo " 1. 浏览器: <script src='dist/metona-toast.js'></script>"
echo " 2. ES Module: import MeToast from 'dist/metona-toast.esm.js'"
echo " 3. CommonJS: const MeToast = require('dist/metona-toast.cjs.js')"
echo ""
echo "📝 示例:"
echo " 打开 examples/demo.html 查看完整示例"
echo ""
+395
View File
@@ -0,0 +1,395 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MetonaToast — 全功能演示</title>
<style>
:root {
--bg: #0b1121; --surface: #151d33; --border: #1e2d4d;
--text: #e2e8f0; --muted: #64748b; --accent: #6366f1; --accent-light: #818cf8;
--radius: 14px;
}
* { 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;
}
.hero {
text-align: center; padding: 70px 20px 50px;
background: radial-gradient(ellipse at 50% 0%, rgba(99,102,241,0.15) 0%, transparent 60%),
linear-gradient(180deg, #0f172a 0%, var(--bg) 100%);
border-bottom: 1px solid var(--border);
}
.hero h1 { font-size: 42px; 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;
}
.hero p { color: var(--muted); font-size: 15px; margin-top: 6px; }
.nav {
position: sticky; top: 0; z-index: 100;
background: rgba(11,17,33,0.85); backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
}
.nav-inner { max-width: 1300px; margin: 0 auto; display: flex; overflow-x: auto; padding: 0 20px; gap: 4px; }
.nav a {
padding: 14px 16px; color: var(--muted); text-decoration: none; font-size: 13px;
font-weight: 500; white-space: nowrap; border-bottom: 2px solid transparent; transition: 0.2s;
}
.nav a:hover, .nav a.active { color: var(--accent-light); border-bottom-color: var(--accent); }
.container { max-width: 1300px; margin: 0 auto; padding: 40px 20px; }
.section { margin-bottom: 56px; }
.section-head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.section-icon {
width: 42px; height: 42px; border-radius: 10px;
background: linear-gradient(135deg, var(--accent), #8b5cf6);
display: flex; align-items: center; justify-content: center; font-size: 20px;
}
.section-title { font-size: 24px; font-weight: 700; }
.section-desc { color: var(--muted); font-size: 14px; margin-bottom: 24px; }
.card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 24px; margin-bottom: 16px;
}
.card-label {
font-size: 12px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.8px; color: var(--muted); margin-bottom: 16px;
}
.btn-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.btn-grid-sm { grid-template-columns: repeat(auto-fill, minmax(95px, 1fr)); }
.btn {
padding: 10px 16px; border: none; border-radius: 8px; font-size: 13px;
font-weight: 500; cursor: pointer; transition: 0.2s; color: #fff;
display: flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap;
}
.btn:hover { transform: translateY(-1px); filter: brightness(1.15); }
.btn-sm { padding: 7px 12px; font-size: 12px; border-radius: 6px; }
.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); }
.btn-outline:hover { background: var(--border); }
.c-success { background: #10b981; } .c-error { background: #ef4444; }
.c-warning { background: #f59e0b; color: #1e293b; } .c-info { background: #3b82f6; }
.c-primary { background: #6366f1; } .c-purple { background: #8b5cf6; }
.c-pink { background: #ec4899; } .c-orange { background: #f97316; }
.c-teal { background: #14b8a6; } .c-cyan { background: #06b6d4; }
.c-rose { background: #f43f5e; } .c-slate { background: #475569; }
.c-indigo { background: #4f46e5; }
.theme-swatch {
display: inline-flex; align-items: center; gap: 8px;
padding: 8px 14px; border-radius: 8px; border: 1px solid var(--border);
cursor: pointer; transition: 0.2s; font-size: 13px; font-weight: 500;
}
.theme-swatch:hover { border-color: var(--accent); }
.swatch-dot { width: 16px; height: 16px; border-radius: 50%; border: 2px solid rgba(255,255,255,0.2); }
.theme-grid { display: flex; flex-wrap: wrap; gap: 10px; }
footer { text-align: center; color: var(--muted); font-size: 13px; padding: 40px 20px; border-top: 1px solid var(--border); }
footer a { color: var(--accent-light); text-decoration: none; }
@media (max-width: 768px) {
.hero h1 { font-size: 28px; }
.btn-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
}
</style>
</head>
<body>
<header class="hero">
<h1><span>MetonaToast</span></h1>
<p>轻量 · 零依赖 · 精致美观 — 全功能演示</p>
</header>
<nav class="nav">
<div class="nav-inner">
<a href="#types">图标类型</a>
<a href="#positions">位置</a>
<a href="#animations">动画</a>
<a href="#themes">主题</a>
<a href="#advanced">高级</a>
<a href="#plugins">插件</a>
<a href="#i18n">国际化</a>
<a href="#management">管理</a>
</div>
</nav>
<main class="container">
<!-- ===== TYPES ===== -->
<section id="types" class="section">
<div class="section-head"><div class="section-icon">🎨</div><div><h2 class="section-title">图标类型</h2><p class="section-desc">80+ 内置图标类型</p></div></div>
<div class="card">
<div class="card-label">常用</div>
<div class="btn-grid">
<button class="btn c-success" onclick="showType('success')">✓ success</button>
<button class="btn c-error" onclick="showType('error')">✕ error</button>
<button class="btn c-warning" onclick="showType('warning')">⚠ warning</button>
<button class="btn c-info" onclick="showType('info')"> info</button>
<button class="btn c-slate" onclick="showType('default')">— default</button>
<button class="btn c-purple" onclick="showType('loading')">⟳ loading</button>
</div>
</div>
<div class="card"><div class="card-label">操作</div>
<div class="btn-grid btn-grid-sm">
<button class="btn btn-outline btn-sm" onclick="showType('check')">check</button>
<button class="btn btn-outline btn-sm" onclick="showType('x')">x</button>
<button class="btn btn-outline btn-sm" onclick="showType('alert')">alert</button>
<button class="btn btn-outline btn-sm" onclick="showType('question')">question</button>
<button class="btn btn-outline btn-sm" onclick="showType('star')">star</button>
<button class="btn btn-outline btn-sm" onclick="showType('heart')">heart</button>
<button class="btn btn-outline btn-sm" onclick="showType('bell')">bell</button>
<button class="btn btn-outline btn-sm" onclick="showType('mail')">mail</button>
<button class="btn btn-outline btn-sm" onclick="showType('settings')">settings</button>
<button class="btn btn-outline btn-sm" onclick="showType('user')">user</button>
<button class="btn btn-outline btn-sm" onclick="showType('home')">home</button>
<button class="btn btn-outline btn-sm" onclick="showType('search')">search</button>
<button class="btn btn-outline btn-sm" onclick="showType('plus')">plus</button>
<button class="btn btn-outline btn-sm" onclick="showType('minus')">minus</button>
<button class="btn btn-outline btn-sm" onclick="showType('edit')">edit</button>
<button class="btn btn-outline btn-sm" onclick="showType('trash')">trash</button>
<button class="btn btn-outline btn-sm" onclick="showType('download')">download</button>
<button class="btn btn-outline btn-sm" onclick="showType('upload')">upload</button>
<button class="btn btn-outline btn-sm" onclick="showType('share')">share</button>
<button class="btn btn-outline btn-sm" onclick="showType('link')">link</button>
<button class="btn btn-outline btn-sm" onclick="showType('external')">external</button>
<button class="btn btn-outline btn-sm" onclick="showType('copy')">copy</button>
<button class="btn btn-outline btn-sm" onclick="showType('cut')">cut</button>
<button class="btn btn-outline btn-sm" onclick="showType('paste')">paste</button>
<button class="btn btn-outline btn-sm" onclick="showType('save')">save</button>
<button class="btn btn-outline btn-sm" onclick="showType('print')">print</button>
</div>
</div>
<div class="card"><div class="card-label">系统 & 设备</div>
<div class="btn-grid btn-grid-sm">
<button class="btn btn-outline btn-sm" onclick="showType('cpu')">cpu</button>
<button class="btn btn-outline btn-sm" onclick="showType('database')">database</button>
<button class="btn btn-outline btn-sm" onclick="showType('server')">server</button>
<button class="btn btn-outline btn-sm" onclick="showType('terminal')">terminal</button>
<button class="btn btn-outline btn-sm" onclick="showType('code')">code</button>
<button class="btn btn-outline btn-sm" onclick="showType('git')">git</button>
<button class="btn btn-outline btn-sm" onclick="showType('package')">package</button>
<button class="btn btn-outline btn-sm" onclick="showType('layers')">layers</button>
<button class="btn btn-outline btn-sm" onclick="showType('grid')">grid</button>
<button class="btn btn-outline btn-sm" onclick="showType('list')">list</button>
<button class="btn btn-outline btn-sm" onclick="showType('filter')">filter</button>
<button class="btn btn-outline btn-sm" onclick="showType('sort')">sort</button>
<button class="btn btn-outline btn-sm" onclick="showType('refresh')">refresh</button>
<button class="btn btn-outline btn-sm" onclick="showType('sync')">sync</button>
<button class="btn btn-outline btn-sm" onclick="showType('wifi')">wifi</button>
<button class="btn btn-outline btn-sm" onclick="showType('bluetooth')">bluetooth</button>
<button class="btn btn-outline btn-sm" onclick="showType('battery')">battery</button>
<button class="btn btn-outline btn-sm" onclick="showType('power')">power</button>
<button class="btn btn-outline btn-sm" onclick="showType('volume')">volume</button>
<button class="btn btn-outline btn-sm" onclick="showType('mic')">mic</button>
<button class="btn btn-outline btn-sm" onclick="showType('camera')">camera</button>
<button class="btn btn-outline btn-sm" onclick="showType('image')">image</button>
<button class="btn btn-outline btn-sm" onclick="showType('video')">video</button>
<button class="btn btn-outline btn-sm" onclick="showType('music')">music</button>
<button class="btn btn-outline btn-sm" onclick="showType('file')">file</button>
<button class="btn btn-outline btn-sm" onclick="showType('folder')">folder</button>
<button class="btn btn-outline btn-sm" onclick="showType('clipboard')">clipboard</button>
<button class="btn btn-outline btn-sm" onclick="showType('eye')">eye</button>
<button class="btn btn-outline btn-sm" onclick="showType('eyeOff')">eyeOff</button>
<button class="btn btn-outline btn-sm" onclick="showType('lock')">lock</button>
<button class="btn btn-outline btn-sm" onclick="showType('unlock')">unlock</button>
<button class="btn btn-outline btn-sm" onclick="showType('shield')">shield</button>
<button class="btn btn-outline btn-sm" onclick="showType('key')">key</button>
</div>
</div>
<div class="card"><div class="card-label">自然 & 几何</div>
<div class="btn-grid btn-grid-sm">
<button class="btn btn-outline btn-sm" onclick="showType('cloud')">cloud</button>
<button class="btn btn-outline btn-sm" onclick="showType('sun')">sun</button>
<button class="btn btn-outline btn-sm" onclick="showType('moon')">moon</button>
<button class="btn btn-outline btn-sm" onclick="showType('zap')">zap</button>
<button class="btn btn-outline btn-sm" onclick="showType('compass')">compass</button>
<button class="btn btn-outline btn-sm" onclick="showType('globe')">globe</button>
<button class="btn btn-outline btn-sm" onclick="showType('map')">map</button>
<button class="btn btn-outline btn-sm" onclick="showType('clock')">clock</button>
<button class="btn btn-outline btn-sm" onclick="showType('calendar')">calendar</button>
<button class="btn btn-outline btn-sm" onclick="showType('flag')">flag</button>
<button class="btn btn-outline btn-sm" onclick="showType('bookmark')">bookmark</button>
<button class="btn btn-outline btn-sm" onclick="showType('tag')">tag</button>
<button class="btn btn-outline btn-sm" onclick="showType('gift')">gift</button>
<button class="btn btn-outline btn-sm" onclick="showType('award')">award</button>
<button class="btn btn-outline btn-sm" onclick="showType('target')">target</button>
<button class="btn btn-outline btn-sm" onclick="showType('crosshair')">crosshair</button>
<button class="btn btn-outline btn-sm" onclick="showType('checkCircle')">checkCircle</button>
<button class="btn btn-outline btn-sm" onclick="showType('xCircle')">xCircle</button>
<button class="btn btn-outline btn-sm" onclick="showType('alertCircle')">alertCircle</button>
<button class="btn btn-outline btn-sm" onclick="showType('infoCircle')">infoCircle</button>
<button class="btn btn-outline btn-sm" onclick="showType('helpCircle')">helpCircle</button>
<button class="btn btn-outline btn-sm" onclick="showType('alertTriangle')">alertTriangle</button>
<button class="btn btn-outline btn-sm" onclick="showType('checkSquare')">checkSquare</button>
<button class="btn btn-outline btn-sm" onclick="showType('square')">square</button>
<button class="btn btn-outline btn-sm" onclick="showType('circle')">circle</button>
<button class="btn btn-outline btn-sm" onclick="showType('triangle')">triangle</button>
<button class="btn btn-outline btn-sm" onclick="showType('hexagon')">hexagon</button>
<button class="btn btn-outline btn-sm" onclick="showType('octagon')">octagon</button>
<button class="btn btn-outline btn-sm" onclick="showType('pentagon')">pentagon</button>
<button class="btn btn-outline btn-sm" onclick="showType('diamond')">diamond</button>
</div>
</div>
</section>
<!-- ===== POSITIONS ===== -->
<section id="positions" class="section">
<div class="section-head"><div class="section-icon">📍</div><div><h2 class="section-title">位置</h2><p class="section-desc">6 种预设位置</p></div></div>
<div class="card">
<div class="btn-grid">
<button class="btn c-slate" onclick="MeToast.success('左上角',{position:'top-left'})">↖ top-left</button>
<button class="btn c-slate" onclick="MeToast.success('顶部居中',{position:'top-center'})">↑ top-center</button>
<button class="btn c-slate" onclick="MeToast.success('右上角',{position:'top-right'})">↗ top-right</button>
<button class="btn c-slate" onclick="MeToast.success('左下角',{position:'bottom-left'})">↙ bottom-left</button>
<button class="btn c-slate" onclick="MeToast.success('底部居中',{position:'bottom-center'})">↓ bottom-center</button>
<button class="btn c-slate" onclick="MeToast.success('右下角',{position:'bottom-right'})">↘ bottom-right</button>
</div>
</div>
</section>
<!-- ===== ANIMATIONS ===== -->
<section id="animations" class="section">
<div class="section-head"><div class="section-icon">🎭</div><div><h2 class="section-title">动画效果</h2><p class="section-desc">11 种动画,每种效果明显不同</p></div></div>
<div class="card">
<div class="btn-grid">
<button class="btn c-primary" onclick="anim('slide')">slide 右侧滑入</button>
<button class="btn c-info" onclick="anim('fade')">fade 淡入模糊</button>
<button class="btn c-success" onclick="anim('scale')">scale 弹性放大</button>
<button class="btn c-warning" onclick="anim('bounce')">bounce 弹跳</button>
<button class="btn c-purple" onclick="anim('flip')">flip 3D翻开</button>
<button class="btn c-pink" onclick="anim('rotate')">rotate 旋转</button>
<button class="btn c-teal" onclick="anim('zoom')">zoom 爆发</button>
<button class="btn c-orange" onclick="anim('slideUp')">slideUp ↑</button>
<button class="btn c-cyan" onclick="anim('slideDown')">slideDown ↓</button>
<button class="btn c-rose" onclick="anim('slideLeft')">slideLeft ←</button>
<button class="btn c-indigo" onclick="anim('slideRight')">slideRight →</button>
</div>
</div>
</section>
<!-- ===== THEMES ===== -->
<section id="themes" class="section">
<div class="section-head"><div class="section-icon">🎨</div><div><h2 class="section-title">主题</h2><p class="section-desc">四种基础主题,支持 registerTheme() 自定义注册</p></div></div>
<div class="card">
<div class="card-label">基础主题</div>
<div class="theme-grid">
<div class="theme-swatch" onclick="switchTheme('light')"><span class="swatch-dot" style="background:#fff"></span>浅色 light</div>
<div class="theme-swatch" onclick="switchTheme('dark')"><span class="swatch-dot" style="background:#1c2028"></span>深色 dark</div>
<div class="theme-swatch" onclick="switchTheme('auto')"><span class="swatch-dot" style="background:linear-gradient(135deg,#fff,#1c2028)"></span>自动 auto</div>
<div class="theme-swatch" onclick="switchTheme('warm')"><span class="swatch-dot" style="background:#fffbeb"></span>暖色 warm</div>
</div>
</div>
</section>
<!-- ===== ADVANCED ===== -->
<section id="advanced" class="section">
<div class="section-head"><div class="section-icon"></div><div><h2 class="section-title">高级功能</h2><p class="section-desc">Promise / 确认框 / 输入框 / 进度条 / 倒计时 / 队列 / 堆叠</p></div></div>
<div class="card">
<div class="btn-grid">
<button class="btn c-purple" onclick="demoPromise()">Promise 风格</button>
<button class="btn c-pink" onclick="demoConfirm()">确认对话框</button>
<button class="btn c-teal" onclick="demoPrompt()">输入对话框</button>
<button class="btn c-orange" onclick="demoProgress()">进度条</button>
<button class="btn c-cyan" onclick="demoCountdown()">倒计时 5s</button>
<button class="btn c-primary" onclick="demoQueue()">队列展示</button>
<button class="btn c-indigo" onclick="demoStack()">堆叠展示</button>
</div>
</div>
</section>
<!-- ===== PLUGINS ===== -->
<section id="plugins" class="section">
<div class="section-head"><div class="section-icon">🔌</div><div><h2 class="section-title">插件系统</h2><p class="section-desc">3 款预设插件,按需安装</p></div></div>
<div class="card">
<div class="btn-grid">
<button class="btn c-purple" onclick="usePlugin('keyboard')">keyboard ESC关闭</button>
<button class="btn c-info" onclick="usePlugin('persistence')">persistence 持久化</button>
<button class="btn c-teal" onclick="usePlugin('accessibility')">accessibility 无障碍</button>
</div>
</div>
</section>
<!-- ===== I18N ===== -->
<section id="i18n" class="section">
<div class="section-head"><div class="section-icon">🌍</div><div><h2 class="section-title">国际化</h2><p class="section-desc">切换语言查看效果</p></div></div>
<div class="card">
<div class="btn-grid">
<button class="btn c-primary" onclick="switchLang('zh-CN')">简体中文</button>
<button class="btn c-info" onclick="switchLang('en-US')">English</button>
<button class="btn c-success" onclick="switchLang('ja')">日本語</button>
<button class="btn c-purple" onclick="switchLang('ko')">한국어</button>
<button class="btn c-pink" onclick="switchLang('fr')">Français</button>
<button class="btn c-teal" onclick="switchLang('de')">Deutsch</button>
<button class="btn c-orange" onclick="switchLang('es')">Español</button>
<button class="btn c-cyan" onclick="switchLang('ru')">Русский</button>
<button class="btn c-rose" onclick="switchLang('ar')">العربية</button>
</div>
</div>
</section>
<!-- ===== MANAGEMENT ===== -->
<section id="management" class="section">
<div class="section-head"><div class="section-icon">🔧</div><div><h2 class="section-title">Toast 管理</h2><p class="section-desc">统计、关闭、清除</p></div></div>
<div class="card">
<div class="btn-grid">
<button class="btn c-success" onclick="MeToast.success('消息 '+Date.now()%100)">发一条消息</button>
<button class="btn c-info" onclick="MeToast.info('消息 '+Date.now()%100)">再发一条</button>
<button class="btn c-warning" onclick="alert('当前: '+MeToast.count()+' 条')">查看数量</button>
<button class="btn c-error" onclick="MeToast.dismiss()">关闭全部</button>
<button class="btn c-slate" onclick="MeToast.clear()">清除全部</button>
<button class="btn c-purple" onclick="MeToast.pauseAll()">暂停全部</button>
<button class="btn c-teal" onclick="MeToast.resumeAll()">恢复全部</button>
</div>
</div>
</section>
</main>
<footer>
<p>MetonaToast v2.0.0 · <a href="index.html">← 代码示例</a></p>
</footer>
<script src="../dist/metona-toast.js"></script>
<script>
MeToast.configure({ position: 'top-right', duration: 4000, theme: 'dark', animation: 'slide', pauseOnHover: true, showProgress: true });
function showType(type) {
if (type === 'loading') { const l = MeToast.loading('加载中...'); setTimeout(() => l.success('完成!'), 2000); return; }
if (type === 'success') { MeToast.success(type); return; }
if (type === 'error') { MeToast.error(type); return; }
if (type === 'warning') { MeToast.warning(type); return; }
if (type === 'info') { MeToast.info(type); return; }
if (type === 'default') { MeToast.show('默认消息'); return; }
MeToast.show(type, { type });
}
function anim(name) { MeToast.success('动画: ' + name, { animation: name, duration: 3000 }); }
function switchTheme(t) { MeToast.themes.switchTheme(t); MeToast.configure({ theme: t }); MeToast.info('主题: ' + t, { theme: t }); }
function switchLang(l) { MeToast.i18n.switchLocale(l); MeToast.success(MeToast.i18n.getLocaleName(l) + ' (' + l + ')'); }
function usePlugin(n) { MeToast.use(n); MeToast.success('插件: ' + n); }
async function demoPromise() {
const p = new Promise((r, e) => setTimeout(() => Math.random() > 0.3 ? r('ok') : e('fail'), 2000));
try { await MeToast.promise(p, { loading: '处理中...', success: '成功!', error: '失败' }); } catch (_) {}
}
async function demoConfirm() {
const ok = await MeToast.confirm('确定删除吗?', { confirmText: '删除', confirmColor: '#ef4444' });
if (ok) MeToast.success('已删除'); else MeToast.info('已取消');
}
async function demoPrompt() {
const v = await MeToast.prompt('请输入姓名', { placeholder: '请输入...' });
if (v) MeToast.info('你好,' + v + '');
}
function demoProgress() {
const p = MeToast.progress('上传中...');
let v = 0;
const t = setInterval(() => { v += Math.random() * 28; if (v >= 100) { clearInterval(t); p.complete('完成!'); } else p.setProgress(v); }, 400);
}
function demoCountdown() {
MeToast.countdown('{seconds} 秒后执行', 5, { onComplete: () => MeToast.success('已执行') });
}
async function demoQueue() {
await MeToast.queue(['步骤一', '步骤二', '步骤三'], { delay: 800, duration: 2000, type: 'info' });
}
function demoStack() {
MeToast.stack(['消息 1', '消息 2', '消息 3', '消息 4'], { stagger: 150, type: 'info' });
}
</script>
</body>
</html>
+442
View File
@@ -0,0 +1,442 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MetonaToast — 基础功能与代码示例</title>
<style>
:root {
--bg: #0b1121; --surface: #151d33; --border: #1e2d4d;
--text: #e2e8f0; --muted: #64748b; --accent: #6366f1; --accent-light: #818cf8;
--radius: 14px;
}
* { 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;
}
.hero {
text-align: center; padding: 80px 20px 60px;
background: radial-gradient(ellipse at 50% 0%, rgba(99,102,241,0.15) 0%, transparent 60%),
linear-gradient(180deg, #0f172a 0%, var(--bg) 100%);
border-bottom: 1px solid var(--border);
}
.hero h1 { font-size: 48px; 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;
}
.hero p { color: var(--muted); font-size: 16px; margin-top: 8px; }
.hero .badges { display: flex; gap: 12px; justify-content: center; margin-top: 20px; flex-wrap: wrap; }
.hero .badges span {
background: var(--surface); border: 1px solid var(--border); border-radius: 20px;
padding: 5px 16px; font-size: 13px; color: var(--accent-light);
}
.container { max-width: 900px; margin: 0 auto; padding: 40px 20px; }
.section { margin-bottom: 48px; }
.section h2 { font-size: 22px; margin-bottom: 4px; }
.section > p { color: var(--muted); font-size: 14px; margin-bottom: 20px; }
.card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 24px; margin-bottom: 16px;
}
.card h3 { font-size: 14px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 14px; }
.btn-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.btn {
padding: 9px 18px; border: none; border-radius: 8px; font-size: 13px;
font-weight: 500; cursor: pointer; transition: 0.2s; color: #fff;
}
.btn:hover { transform: translateY(-1px); filter: brightness(1.15); }
.c-success { background: #10b981; } .c-error { background: #ef4444; }
.c-warning { background: #f59e0b; color: #1e293b; } .c-info { background: #3b82f6; }
.c-primary { background: #6366f1; } .c-purple { background: #8b5cf6; }
.c-pink { background: #ec4899; } .c-teal { background: #14b8a6; }
.c-orange { background: #f97316; } .c-slate { background: #475569; }
pre {
background: #0a0f1a; border: 1px solid var(--border); border-radius: 8px;
padding: 14px 18px; overflow-x: auto; margin: 0;
font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;
font-size: 12.5px; line-height: 1.75; color: #cbd5e1;
}
pre .kw { color: #c084fc; } pre .fn { color: #60a5fa; }
pre .s { color: #34d399; } pre .cm { color: #475569; }
.api-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.api-table th, .api-table td { text-align: left; padding: 7px 12px; border-bottom: 1px solid var(--border); }
.api-table th { color: var(--muted); font-weight: 500; font-size: 11px; text-transform: uppercase; }
.api-table code { background: rgba(99,102,241,0.15); color: #a5b4fc; padding: 1px 6px; border-radius: 4px; font-size: 12px; }
footer { text-align: center; color: var(--muted); font-size: 13px; padding: 40px 20px; border-top: 1px solid var(--border); }
footer a { color: var(--accent-light); text-decoration: none; }
</style>
</head>
<body>
<header class="hero">
<h1><span>MetonaToast</span></h1>
<p>轻量 · 零依赖 · 精致美观的 Toast 通知库</p>
<div class="badges">
<span>📦 &lt;10KB gzip</span><span>🎨 80+ 图标</span><span>🎭 11 动画</span><span>🌍 国际化</span><span>🔌 插件</span>
</div>
</header>
<main class="container">
<!-- 安装 -->
<section class="section">
<h2>📦 安装与引用</h2>
<div class="card">
<h3>npm</h3>
<pre><span class="cm"># 安装</span>
npm install metona-toast
<span class="cm"># ES Module</span>
<span class="kw">import</span> MeToast <span class="kw">from</span> <span class="s">'metona-toast'</span>;
<span class="cm"># CommonJS</span>
<span class="kw">const</span> MeToast = <span class="fn">require</span>(<span class="s">'metona-toast'</span>);</pre>
</div>
<div class="card">
<h3>CDN</h3>
<pre><span class="cm">&lt;!-- UMD 开发版 --&gt;</span>
<span class="kw">&lt;script</span> <span class="fn">src</span>=<span class="s">"https://unpkg.com/metona-toast@2.0.0/dist/metona-toast.js"</span><span class="kw">&gt;&lt;/script&gt;</span>
<span class="cm">&lt;!-- 压缩版(生产环境推荐)--&gt;</span>
<span class="kw">&lt;script</span> <span class="fn">src</span>=<span class="s">"https://unpkg.com/metona-toast@2.0.0/dist/metona-toast.min.js"</span><span class="kw">&gt;&lt;/script&gt;</span>
<span class="cm">&lt;!-- ES Module 直接引用 --&gt;</span>
<span class="kw">&lt;script</span> <span class="fn">type</span>=<span class="s">"module"</span><span class="kw">&gt;</span>
<span class="kw">import</span> MeToast <span class="kw">from</span> <span class="s">'https://unpkg.com/metona-toast@2.0.0/dist/metona-toast.esm.js'</span>;
<span class="kw">&lt;/script&gt;</span></pre>
</div>
</section>
<!-- 基础调用 -->
<section class="section">
<h2>🎯 基础调用</h2>
<p>五种核心类型,一行代码搞定</p>
<div class="card">
<div class="btn-row">
<button class="btn c-success" onclick="MeToast.success('操作成功!')">成功</button>
<button class="btn c-error" onclick="MeToast.error('操作失败!')">错误</button>
<button class="btn c-warning" onclick="MeToast.warning('请注意检查')">警告</button>
<button class="btn c-info" onclick="MeToast.info('系统维护中')">信息</button>
<button class="btn c-slate" onclick="MeToast.show('默认消息')">默认</button>
</div>
<pre><span class="cm">// 五种类型,一行搞定</span>
MeToast.<span class="fn">success</span>(<span class="s">'操作成功!'</span>);
MeToast.<span class="fn">error</span>(<span class="s">'操作失败!'</span>);
MeToast.<span class="fn">warning</span>(<span class="s">'请注意检查'</span>);
MeToast.<span class="fn">info</span>(<span class="s">'系统维护中'</span>);
MeToast.<span class="fn">show</span>(<span class="s">'默认消息'</span>);</pre>
</div>
</section>
<!-- 带配置 -->
<section class="section">
<h2>⚙️ 配置选项</h2>
<p>标题、位置、时长、主题、动画……灵活可控</p>
<div class="card">
<div class="btn-row">
<button class="btn c-success" onclick="MeToast.success({title:'保存成功',message:'文件已同步到云端'})">带标题</button>
<button class="btn c-pink" onclick="MeToast.info('显示10秒',{duration:10000})">长时10s</button>
<button class="btn c-purple" onclick="MeToast.warning('底部居中',{position:'bottom-center'})">底部居中</button>
<button class="btn c-teal" onclick="MeToast.info('手动关闭',{duration:0,closeButton:true})">手动关闭</button>
<button class="btn c-orange" onclick="MeToast.success('弹跳动画',{animation:'bounce'})">弹跳动画</button>
</div>
<pre><span class="cm">// 带标题</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> });
<span class="cm">// 自定义时长 — 显示10秒</span>
MeToast.<span class="fn">info</span>(<span class="s">'显示10秒'</span>, { <span class="fn">duration</span>: 10000 });
<span class="cm">// 自定义位置 — 底部居中</span>
MeToast.<span class="fn">warning</span>(<span class="s">'底部居中'</span>, { <span class="fn">position</span>: <span class="s">'bottom-center'</span> });
<span class="cm">// 手动关闭 — 不自动消失</span>
MeToast.<span class="fn">info</span>(<span class="s">'手动关闭'</span>, { <span class="fn">duration</span>: 0, <span class="fn">closeButton</span>: <span class="kw">true</span> });
<span class="cm">// 自定义动画 — 弹跳效果</span>
MeToast.<span class="fn">success</span>(<span class="s">'弹跳动画'</span>, { <span class="fn">animation</span>: <span class="s">'bounce'</span> });</pre>
</div>
</section>
<!-- Loading 链式 -->
<section class="section">
<h2>🔄 Loading 链式转换</h2>
<p>加载中 → 成功/失败/警告/信息,优雅切换</p>
<div class="card">
<div class="btn-row">
<button class="btn c-purple" onclick="demoLoading()">提交成功</button>
<button class="btn c-error" onclick="demoLoadingFail()">提交失败</button>
</div>
<pre><span class="cm">// loading → success</span>
<span class="kw">const</span> loading = MeToast.<span class="fn">loading</span>(<span class="s">'正在提交...'</span>);
<span class="kw">setTimeout</span>(() => loading.<span class="fn">success</span>(<span class="s">'提交成功!'</span>), 2000);
<span class="cm">// loading → error</span>
<span class="kw">const</span> loading = MeToast.<span class="fn">loading</span>(<span class="s">'正在提交...'</span>);
<span class="kw">setTimeout</span>(() => loading.<span class="fn">error</span>(<span class="s">'提交失败,请重试'</span>), 2000);</pre>
</div>
</section>
<!-- Promise -->
<section class="section">
<h2>⛓ Promise 封装</h2>
<p>一行代码自动处理 loading / success / error</p>
<div class="card">
<div class="btn-row">
<button class="btn c-primary" onclick="demoPromise()">模拟请求</button>
</div>
<pre><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">success</span>: <span class="s">'加载完成!'</span>,
<span class="fn">error</span>: <span class="s">'加载失败'</span>,
}
);</pre>
</div>
</section>
<!-- 对话框 -->
<section class="section">
<h2>💬 确认框 & 输入框</h2>
<p>内置对话框,返回 Promise</p>
<div class="card">
<div class="btn-row">
<button class="btn c-pink" onclick="demoConfirm()">确认对话框</button>
<button class="btn c-teal" onclick="demoPrompt()">输入对话框</button>
</div>
<pre><span class="cm">// 确认对话框 → Promise&lt;boolean&gt;</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="fn">confirmColor</span>: <span class="s">'#ef4444'</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="fn">placeholder</span>: <span class="s">'请输入...'</span>,
});</pre>
</div>
</section>
<!-- 进度 & 倒计时 -->
<section class="section">
<h2>📊 进度条 & 倒计时</h2>
<p>实时更新的进度通知和自动倒计时</p>
<div class="card">
<div class="btn-row">
<button class="btn c-orange" onclick="demoProgress()">进度条</button>
<button class="btn c-info" onclick="demoCountdown()">倒计时 5s</button>
</div>
<pre><span class="cm">// 进度条 — 实时更新</span>
<span class="kw">const</span> p = MeToast.<span class="fn">progress</span>(<span class="s">'上传中...'</span>);
<span class="kw">let</span> v = 0;
<span class="kw">const</span> t = <span class="fn">setInterval</span>(() => {
v += <span class="fn">Math</span>.random() * 28;
<span class="kw">if</span> (v >= 100) { <span class="fn">clearInterval</span>(t); p.<span class="fn">complete</span>(<span class="s">'上传完成!'</span>); }
<span class="kw">else</span> p.<span class="fn">setProgress</span>(v);
}, 400);
<span class="cm">// 倒计时 — {seconds} 自动替换</span>
MeToast.<span class="fn">countdown</span>(<span class="s">'{seconds} 秒后执行'</span>, 5, {
<span class="fn">onComplete</span>: () => MeToast.<span class="fn">success</span>(<span class="s">'已执行'</span>),
});</pre>
</div>
</section>
<!-- 队列 & 堆叠 -->
<section class="section">
<h2>📋 队列 & 堆叠</h2>
<p>顺序逐个展示 or 同时错峰展示</p>
<div class="card">
<div class="btn-row">
<button class="btn c-primary" onclick="demoQueue()">队列展示</button>
<button class="btn c-purple" onclick="demoStack()">堆叠展示</button>
</div>
<pre><span class="cm">// 队列 — 一个接一个</span>
<span class="kw">await</span> MeToast.<span class="fn">queue</span>([<span class="s">'步骤一'</span>, <span class="s">'步骤二'</span>, <span class="s">'步骤三'</span>], {
<span class="fn">delay</span>: 800,
<span class="fn">duration</span>: 2000,
<span class="fn">type</span>: <span class="s">'info'</span>,
});
<span class="cm">// 堆叠 — 同时错峰</span>
MeToast.<span class="fn">stack</span>([<span class="s">'消息 1'</span>, <span class="s">'消息 2'</span>, <span class="s">'消息 3'</span>, <span class="s">'消息 4'</span>], {
<span class="fn">stagger</span>: 150,
<span class="fn">type</span>: <span class="s">'info'</span>,
});</pre>
</div>
</section>
<!-- 插件 -->
<section class="section">
<h2>🔌 插件</h2>
<p>3 款预设插件,按需安装</p>
<div class="card">
<div class="btn-row">
<button class="btn c-purple" onclick="MeToast.use('keyboard');MeToast.info('已安装 keyboard — 试试按 ESC')">keyboard</button>
<button class="btn c-info" onclick="MeToast.use('persistence');MeToast.info('已安装 persistence — 配置将自动保存')">persistence</button>
<button class="btn c-teal" onclick="MeToast.use('accessibility');MeToast.info('已安装 accessibility — 屏幕阅读器可用')">accessibility</button>
</div>
<pre><span class="cm">// keyboard — ESC 关闭所有 Toast</span>
MeToast.<span class="fn">use</span>(<span class="s">'keyboard'</span>);
<span class="cm">// persistence — 配置持久化(自动保存/加载)</span>
MeToast.<span class="fn">use</span>(<span class="s">'persistence'</span>);
<span class="cm">// accessibility — 屏幕阅读器朗读公告</span>
MeToast.<span class="fn">use</span>(<span class="s">'accessibility'</span>);</pre>
</div>
</section>
<!-- 全局配置 -->
<section class="section">
<h2>🔧 全局配置</h2>
<p>一次配置,所有 Toast 自动继承</p>
<div class="card">
<pre>MeToast.<span class="fn">configure</span>({
<span class="fn">position</span>: <span class="s">'top-right'</span>, <span class="cm">// 默认位置</span>
<span class="fn">duration</span>: 4000, <span class="cm">// 默认显示时长(ms)</span>
<span class="fn">max</span>: 6, <span class="cm">// 同时最多显示条数</span>
<span class="fn">theme</span>: <span class="s">'auto'</span>, <span class="cm">// light | dark | auto | warm</span>
<span class="fn">animation</span>: <span class="s">'slide'</span>, <span class="cm">// 默认动画效果</span>
<span class="fn">pauseOnHover</span>: <span class="kw">true</span>, <span class="cm">// 悬停暂停计时</span>
<span class="fn">closeOnClick</span>: <span class="kw">true</span>, <span class="cm">// 点击关闭</span>
<span class="fn">showProgress</span>: <span class="kw">true</span>, <span class="cm">// 显示进度条</span>
<span class="fn">draggable</span>: <span class="kw">true</span>, <span class="cm">// 允许拖拽关闭</span>
<span class="fn">locale</span>: <span class="s">'zh-CN'</span>, <span class="cm">// 默认语言</span>
});</pre>
</div>
</section>
<!-- 主题 -->
<section class="section">
<h2>🎨 主题 & 国际化</h2>
<p>内置四种主题,支持自定义注册;内置中英文,可扩展</p>
<div class="card">
<div class="btn-row">
<button class="btn c-slate" onclick="MeToast.themes.switchTheme('light');MeToast.configure({theme:'light'});MeToast.success('浅色主题')">浅色</button>
<button class="btn c-slate" onclick="MeToast.themes.switchTheme('dark');MeToast.configure({theme:'dark'});MeToast.success('深色主题')">深色</button>
<button class="btn c-slate" onclick="MeToast.themes.switchTheme('warm');MeToast.configure({theme:'warm'});MeToast.success('暖色主题')">暖色</button>
<button class="btn c-info" onclick="MeToast.i18n.switchLocale('en-US');MeToast.success('Switched to English')">EN</button>
<button class="btn c-info" onclick="MeToast.i18n.switchLocale('zh-CN');MeToast.success('已切换为中文')">中文</button>
</div>
<pre><span class="cm">// 切换主题(switchTheme + configure 同步全局配置)</span>
MeToast.themes.<span class="fn">switchTheme</span>(<span class="s">'warm'</span>);
MeToast.<span class="fn">configure</span>({ <span class="fn">theme</span>: <span class="s">'warm'</span> });
<span class="cm">// 切换语言 — 英文</span>
MeToast.i18n.<span class="fn">switchLocale</span>(<span class="s">'en-US'</span>);
<span class="cm">// 切换回中文</span>
MeToast.i18n.<span class="fn">switchLocale</span>(<span class="s">'zh-CN'</span>);
<span class="cm">// 注册自定义主题</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">text</span>: <span class="s">'#0c4a6e'</span>,
<span class="fn">border</span>: <span class="s">'rgba(14,165,233,0.2)'</span>,
<span class="fn">shadow</span>: <span class="s">'0 10px 36px -10px rgba(14,165,233,0.18)'</span>,
<span class="fn">hoverShadow</span>: <span class="s">'0 14px 48px -10px rgba(14,165,233,0.22)'</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>,
});</pre>
</div>
</section>
<!-- Toast 管理 -->
<section class="section">
<h2>🔧 Toast 管理</h2>
<p>查找、关闭、清除、暂停、恢复</p>
<div class="card">
<div class="btn-row">
<button class="btn c-success" onclick="MeToast.success('消息 '+Date.now()%100)">发一条</button>
<button class="btn c-info" onclick="MeToast.info('消息 '+Date.now()%100)">再发一条</button>
<button class="btn c-warning" onclick="alert('当前: '+MeToast.count()+' 条')">查看数量</button>
<button class="btn c-slate" onclick="MeToast.pauseAll();MeToast.info('已暂停全部计时')">暂停全部</button>
<button class="btn c-slate" onclick="MeToast.resumeAll();MeToast.info('已恢复全部计时')">恢复全部</button>
<button class="btn c-error" onclick="MeToast.dismiss()">关闭全部</button>
</div>
<pre>MeToast.<span class="fn">count</span>(); <span class="cm">// 当前数量</span>
MeToast.<span class="fn">getToasts</span>(); <span class="cm">// 获取所有实例</span>
MeToast.<span class="fn">dismiss</span>(); <span class="cm">// 关闭全部</span>
MeToast.<span class="fn">dismiss</span>(id); <span class="cm">// 关闭指定</span>
MeToast.<span class="fn">pauseAll</span>(); <span class="cm">// 暂停全部计时</span>
MeToast.<span class="fn">resumeAll</span>(); <span class="cm">// 恢复全部计时</span>
MeToast.<span class="fn">destroy</span>(); <span class="cm">// 完全销毁</span></pre>
</div>
</section>
<!-- API 速览 -->
<section class="section">
<h2>📖 API 速览</h2>
<div class="card">
<table class="api-table">
<tr><th>方法</th><th>说明</th><th>示例</th></tr>
<tr><td><code>success(msg, opts?)</code></td><td>成功通知</td><td><code>MeToast.success('保存成功')</code></td></tr>
<tr><td><code>error(msg, opts?)</code></td><td>错误通知</td><td><code>MeToast.error('网络错误')</code></td></tr>
<tr><td><code>warning(msg, opts?)</code></td><td>警告通知</td><td><code>MeToast.warning('请注意')</code></td></tr>
<tr><td><code>info(msg, opts?)</code></td><td>信息通知</td><td><code>MeToast.info('系统维护中')</code></td></tr>
<tr><td><code>loading(msg, opts?)</code></td><td>加载状态</td><td><code>MeToast.loading('加载中...')</code></td></tr>
<tr><td><code>promise(p, opts)</code></td><td>Promise 自动状态</td><td><code>MeToast.promise(fetch(...), opts)</code></td></tr>
<tr><td><code>confirm(msg, opts?)</code></td><td>确认对话框 → boolean</td><td><code>await MeToast.confirm('删除?')</code></td></tr>
<tr><td><code>prompt(msg, opts?)</code></td><td>输入对话框 → string</td><td><code>await MeToast.prompt('姓名?')</code></td></tr>
<tr><td><code>progress(msg, opts?)</code></td><td>进度条通知</td><td><code>MeToast.progress('上传中...')</code></td></tr>
<tr><td><code>countdown(msg, s, opts?)</code></td><td>倒计时</td><td><code>MeToast.countdown('{seconds}s', 10)</code></td></tr>
<tr><td><code>queue(msgs, opts?)</code></td><td>队列顺序展示</td><td><code>MeToast.queue(['a','b','c'])</code></td></tr>
<tr><td><code>stack(msgs, opts?)</code></td><td>堆叠错峰展示</td><td><code>MeToast.stack(['a','b','c'])</code></td></tr>
<tr><td><code>configure(opts)</code></td><td>全局配置</td><td><code>MeToast.configure({duration:5000})</code></td></tr>
<tr><td><code>dismiss(id?)</code></td><td>关闭 toast</td><td><code>MeToast.dismiss()</code></td></tr>
</table>
</div>
</section>
</main>
<footer>
<p>MetonaToast v2.0.0 · <a href="demo.html">全功能演示 →</a></p>
</footer>
<script src="../dist/metona-toast.js"></script>
<script>
MeToast.configure({ position: 'top-right', duration: 4000, theme: 'dark', animation: 'slide', pauseOnHover: true, showProgress: true });
function demoLoading() {
const l = MeToast.loading('正在提交...');
setTimeout(() => l.success('提交成功!'), 2000);
}
function demoLoadingFail() {
const l = MeToast.loading('正在提交...');
setTimeout(() => l.error('提交失败,请重试'), 2000);
}
async function demoPromise() {
const p = new Promise((resolve, reject) => {
setTimeout(() => Math.random() > 0.3 ? resolve('ok') : reject('fail'), 2000);
});
try { await MeToast.promise(p, { loading: '加载中...', success: '加载完成!', error: '加载失败' }); } catch (_) {}
}
async function demoConfirm() {
const ok = await MeToast.confirm('确定删除吗?', { confirmText: '删除', confirmColor: '#ef4444' });
if (ok) MeToast.success('已删除'); else MeToast.info('已取消');
}
async function demoPrompt() {
const name = await MeToast.prompt('请输入姓名', { placeholder: '请输入...' });
if (name) MeToast.info(`你好,${name}`);
}
function demoProgress() {
const p = MeToast.progress('上传中...');
let v = 0;
const t = setInterval(() => { v += Math.random() * 28; if (v >= 100) { clearInterval(t); p.complete('上传完成!'); } else p.setProgress(v); }, 400);
}
function demoCountdown() {
MeToast.countdown('{seconds} 秒后执行', 5, { onComplete: () => MeToast.success('已执行') });
}
async function demoQueue() {
await MeToast.queue(['步骤一', '步骤二', '步骤三'], { delay: 800, duration: 2000, type: 'info' });
}
function demoStack() {
MeToast.stack(['消息 1', '消息 2', '消息 3', '消息 4'], { stagger: 150, type: 'info' });
}
</script>
</body>
</html>
+17
View File
@@ -0,0 +1,17 @@
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.js$': 'babel-jest',
},
transformIgnorePatterns: [
'/node_modules/(?!(@rollup)/)',
],
moduleFileExtensions: ['js', 'json'],
collectCoverageFrom: [
'src/**/*.js',
'!src/index.js',
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov'],
verbose: true,
};
+7444
View File
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
{
"name": "metona-toast",
"version": "2.0.0",
"description": "轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。",
"main": "dist/metona-toast.js",
"module": "src/index.js",
"types": "types/index.d.ts",
"exports": {
".": {
"import": "./src/index.js",
"require": "./dist/metona-toast.js",
"types": "./types/index.d.ts"
}
},
"files": [
"dist/",
"src/",
"types/",
"README.md",
"LICENSE"
],
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/",
"typecheck": "tsc --noEmit",
"prepublishOnly": "npm run build",
"docs": "jsdoc src/ -d docs",
"example": "serve examples/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/metona/metona-toast.git"
},
"keywords": [
"toast",
"notification",
"alert",
"message",
"snackbar",
"ui",
"component",
"lightweight",
"zero-dependency"
],
"author": "metona",
"license": "MIT",
"bugs": {
"url": "https://github.com/metona/metona-toast/issues"
},
"homepage": "https://github.com/metona/metona-toast#readme",
"devDependencies": {
"@babel/core": "^7.22.0",
"@babel/preset-env": "^7.22.0",
"@babel/plugin-transform-modules-commonjs": "^7.22.0",
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-node-resolve": "^15.0.0",
"@rollup/plugin-terser": "^0.4.0",
"@types/jest": "^29.5.0",
"babel-jest": "^29.5.0",
"eslint": "^8.40.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"prettier": "^2.8.0",
"rollup": "^3.20.0",
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-serve": "^2.0.1",
"typescript": "^5.0.0"
},
"engines": {
"node": ">=14.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}
+135
View File
@@ -0,0 +1,135 @@
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import dts from 'rollup-plugin-dts';
import serve from 'rollup-plugin-serve';
import livereload from 'rollup-plugin-livereload';
const isDev = process.env.ROLLUP_WATCH;
const isProd = process.env.NODE_ENV === 'production';
// 基础配置
const baseConfig = {
input: 'src/index.js',
plugins: [
resolve(),
commonjs(),
],
};
// 开发服务器配置
const devPlugins = isDev ? [
serve({
open: true,
contentBase: ['examples', 'dist'],
port: 3000,
}),
livereload({
watch: ['src', 'examples'],
}),
] : [];
// 生产环境配置
const prodPlugins = isProd ? [
terser({
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.warn'],
},
format: {
comments: false,
},
}),
] : [];
// 输出配置
export default [
// UMD格式(浏览器)
{
...baseConfig,
output: {
file: 'dist/metona-toast.js',
format: 'umd',
name: 'MeToast',
exports: 'named',
sourcemap: !isProd,
globals: {},
},
plugins: [
...baseConfig.plugins,
...devPlugins,
...prodPlugins,
],
},
// ESM格式(现代浏览器/打包工具)
{
...baseConfig,
output: {
file: 'dist/metona-toast.esm.js',
format: 'es',
exports: 'named',
sourcemap: !isProd,
},
plugins: [
...baseConfig.plugins,
...prodPlugins,
],
},
// CommonJS格式(Node.js
{
...baseConfig,
output: {
file: 'dist/metona-toast.cjs.js',
format: 'cjs',
exports: 'named',
sourcemap: !isProd,
},
plugins: [
...baseConfig.plugins,
...prodPlugins,
],
},
// 压缩版本(UMD
{
...baseConfig,
output: {
file: 'dist/metona-toast.min.js',
format: 'umd',
name: 'MeToast',
exports: 'named',
sourcemap: false,
globals: {},
},
plugins: [
...baseConfig.plugins,
terser({
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.warn'],
passes: 2,
},
format: {
comments: false,
},
mangle: {
toplevel: true,
},
}),
],
},
// TypeScript声明文件
{
input: 'types/index.d.ts',
output: {
file: 'dist/metona-toast.d.ts',
format: 'es',
},
plugins: [dts()],
},
];
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# MetonaToast 本地开发服务器
echo "🍞 MetonaToast 开发服务器"
echo "========================="
# 检查Python
if command -v python3 &> /dev/null; then
PYTHON=python3
elif command -v python &> /dev/null; then
PYTHON=python
else
echo "❌ 错误: 未找到Python"
exit 1
fi
PORT=${1:-3000}
echo "✅ Python: $($PYTHON --version 2>&1)"
echo "🌐 启动服务器..."
echo ""
echo "📁 访问地址:"
echo " http://localhost:$PORT/examples/demo.html"
echo " http://localhost:$PORT/examples/index.html"
echo ""
echo "按 Ctrl+C 停止服务器"
echo ""
cd "$(dirname "$0")"
$PYTHON -m http.server $PORT
+860
View File
@@ -0,0 +1,860 @@
/**
* MetonaToast Animations - 动画管理
* @module animations
* @version 2.0.0
* @description 动画效果管理和自定义
*/
import { ANIMATIONS } from './constants.js';
// 动画缓存
const animationCache = new Map();
/**
* 动画管理器类
*/
class AnimationManager {
constructor() {
this.animations = new Map();
this.activeAnimations = new Map();
this.animationId = 0;
// 注册默认动画
this._registerDefaults();
}
/**
* 注册默认动画
*/
_registerDefaults() {
Object.entries(ANIMATIONS).forEach(([name, config]) => {
this.register(name, config);
});
}
/**
* 注册动画
* @param {string} name - 动画名称
* @param {Object} config - 动画配置
*/
register(name, config) {
const animation = {
name,
enter: config.enter || {},
leave: config.leave || {},
duration: config.duration || 300,
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
delay: config.delay || 0,
iterations: config.iterations || 1,
direction: config.direction || 'normal',
fillMode: config.fillMode || 'forwards',
};
this.animations.set(name, animation);
animationCache.set(name, animation);
}
/**
* 注销动画
* @param {string} name - 动画名称
*/
unregister(name) {
this.animations.delete(name);
animationCache.delete(name);
}
/**
* 获取动画配置
* @param {string} name - 动画名称
* @returns {Object|null} 动画配置
*/
get(name) {
return this.animations.get(name) || ANIMATIONS[name] || null;
}
/**
* 应用动画
* @param {HTMLElement} element - 目标元素
* @param {string} animationName - 动画名称
* @param {Object} options - 额外选项
* @returns {Promise} 动画完成Promise
*/
apply(element, animationName, options = {}) {
return new Promise((resolve) => {
const animation = this.get(animationName);
if (!animation) {
resolve();
return;
}
const config = { ...animation, ...options };
const animId = ++this.animationId;
// 设置初始状态
Object.assign(element.style, config.enter);
// 强制重绘
element.offsetHeight;
// 创建动画
const keyframes = [
{ ...config.enter },
{ ...config.leave },
];
const animationOptions = {
duration: config.duration,
easing: config.easing,
delay: config.delay,
iterations: config.iterations,
direction: config.direction,
fill: config.fillMode,
};
// 应用动画
const anim = element.animate(keyframes, animationOptions);
// 存储活动动画
this.activeAnimations.set(animId, {
element,
animation: anim,
config,
});
// 动画完成处理
anim.onfinish = () => {
// 应用最终状态
Object.assign(element.style, config.leave);
// 清理
this.activeAnimations.delete(animId);
resolve();
};
// 动画取消处理
anim.oncancel = () => {
this.activeAnimations.delete(animId);
resolve();
};
});
}
/**
* 应用进入动画
* @param {HTMLElement} element - 目标元素
* @param {string} animationName - 动画名称
* @param {Object} options - 额外选项
* @returns {Promise} 动画完成Promise
*/
enter(element, animationName, options = {}) {
const animation = this.get(animationName);
if (!animation) {
return Promise.resolve();
}
return this.apply(element, animationName, {
...options,
direction: 'normal',
});
}
/**
* 应用离开动画
* @param {HTMLElement} element - 目标元素
* @param {string} animationName - 动画名称
* @param {Object} options - 额外选项
* @returns {Promise} 动画完成Promise
*/
leave(element, animationName, options = {}) {
const animation = this.get(animationName);
if (!animation) {
return Promise.resolve();
}
return this.apply(element, animationName, {
...options,
direction: 'reverse',
});
}
/**
* 取消所有动画
*/
cancelAll() {
this.activeAnimations.forEach(({ animation }) => {
animation.cancel();
});
this.activeAnimations.clear();
}
/**
* 取消元素动画
* @param {HTMLElement} element - 目标元素
*/
cancel(element) {
this.activeAnimations.forEach(({ element: el, animation }, id) => {
if (el === element) {
animation.cancel();
this.activeAnimations.delete(id);
}
});
}
/**
* 暂停所有动画
*/
pauseAll() {
this.activeAnimations.forEach(({ animation }) => {
animation.pause();
});
}
/**
* 恢复所有动画
*/
resumeAll() {
this.activeAnimations.forEach(({ animation }) => {
animation.play();
});
}
/**
* 暂停元素动画
* @param {HTMLElement} element - 目标元素
*/
pause(element) {
this.activeAnimations.forEach(({ element: el, animation }) => {
if (el === element) {
animation.pause();
}
});
}
/**
* 恢复元素动画
* @param {HTMLElement} element - 目标元素
*/
resume(element) {
this.activeAnimations.forEach(({ element: el, animation }) => {
if (el === element) {
animation.play();
}
});
}
/**
* 获取活动动画数量
* @returns {number} 动画数量
*/
getActiveCount() {
return this.activeAnimations.size;
}
/**
* 获取所有活动动画
* @returns {Map} 活动动画映射
*/
getActiveAnimations() {
return new Map(this.activeAnimations);
}
/**
* 检查元素是否有活动动画
* @param {HTMLElement} element - 目标元素
* @returns {boolean} 是否有活动动画
*/
hasActiveAnimation(element) {
for (const { element: el } of this.activeAnimations.values()) {
if (el === element) {
return true;
}
}
return false;
}
/**
* 获取动画配置
* @returns {Object} 动画配置
*/
getConfig() {
return {
animations: Array.from(this.animations.keys()),
activeCount: this.getActiveCount(),
};
}
/**
* 重置动画管理器
*/
reset() {
this.cancelAll();
this.animations.clear();
this._registerDefaults();
}
/**
* 销毁动画管理器
*/
destroy() {
this.cancelAll();
this.animations.clear();
this.activeAnimations.clear();
}
}
/**
* 预设动画效果
*/
const presetAnimations = {
bounce: {
enter: { transform: 'translateY(-80px)', opacity: 0 },
leave: { transform: 'translateY(20px)', opacity: 1 },
duration: 650,
easing: 'ease',
},
slideUp: {
enter: { transform: 'translateY(60px)', opacity: 0 },
leave: { transform: 'translateY(0)', opacity: 1 },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
slideDown: {
enter: { transform: 'translateY(-60px)', opacity: 0 },
leave: { transform: 'translateY(0)', opacity: 1 },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
slideLeft: {
enter: { transform: 'translateX(-90px)', opacity: 0 },
leave: { transform: 'translateX(0)', opacity: 1 },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
slideRight: {
enter: { transform: 'translateX(90px)', opacity: 0 },
leave: { transform: 'translateX(0)', opacity: 1 },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
};
// 注册预设动画
Object.entries(presetAnimations).forEach(([name, config]) => {
ANIMATIONS[name] = config;
});
/**
* 创建动画管理器实例
* @returns {AnimationManager} 动画管理器实例
*/
export const createAnimationManager = () => {
return new AnimationManager();
};
// 创建默认实例
const defaultAnimationManager = createAnimationManager();
/**
* 动画工具函数
*/
export const animationUtils = {
/**
* 应用动画
* @param {HTMLElement} element - 目标元素
* @param {string} animationName - 动画名称
* @param {Object} options - 额外选项
* @returns {Promise} 动画完成Promise
*/
apply(element, animationName, options = {}) {
return defaultAnimationManager.apply(element, animationName, options);
},
/**
* 应用进入动画
* @param {HTMLElement} element - 目标元素
* @param {string} animationName - 动画名称
* @param {Object} options - 额外选项
* @returns {Promise} 动画完成Promise
*/
enter(element, animationName, options = {}) {
return defaultAnimationManager.enter(element, animationName, options);
},
/**
* 应用离开动画
* @param {HTMLElement} element - 目标元素
* @param {string} animationName - 动画名称
* @param {Object} options - 额外选项
* @returns {Promise} 动画完成Promise
*/
leave(element, animationName, options = {}) {
return defaultAnimationManager.leave(element, animationName, options);
},
/**
* 取消所有动画
*/
cancelAll() {
defaultAnimationManager.cancelAll();
},
/**
* 取消元素动画
* @param {HTMLElement} element - 目标元素
*/
cancel(element) {
defaultAnimationManager.cancel(element);
},
/**
* 暂停所有动画
*/
pauseAll() {
defaultAnimationManager.pauseAll();
},
/**
* 恢复所有动画
*/
resumeAll() {
defaultAnimationManager.resumeAll();
},
/**
* 暂停元素动画
* @param {HTMLElement} element - 目标元素
*/
pause(element) {
defaultAnimationManager.pause(element);
},
/**
* 恢复元素动画
* @param {HTMLElement} element - 目标元素
*/
resume(element) {
defaultAnimationManager.resume(element);
},
/**
* 获取活动动画数量
* @returns {number} 动画数量
*/
getActiveCount() {
return defaultAnimationManager.getActiveCount();
},
/**
* 检查元素是否有活动动画
* @param {HTMLElement} element - 目标元素
* @returns {boolean} 是否有活动动画
*/
hasActiveAnimation(element) {
return defaultAnimationManager.hasActiveAnimation(element);
},
/**
* 注册动画
* @param {string} name - 动画名称
* @param {Object} config - 动画配置
*/
register(name, config) {
defaultAnimationManager.register(name, config);
},
/**
* 注销动画
* @param {string} name - 动画名称
*/
unregister(name) {
defaultAnimationManager.unregister(name);
},
/**
* 获取动画配置
* @param {string} name - 动画名称
* @returns {Object|null} 动画配置
*/
get(name) {
return defaultAnimationManager.get(name);
},
/**
* 获取所有动画名称
* @returns {Array} 动画名称数组
*/
getAnimationNames() {
return Array.from(defaultAnimationManager.animations.keys());
},
/**
* 获取动画配置
* @returns {Object} 动画配置
*/
getConfig() {
return defaultAnimationManager.getConfig();
},
/**
* 重置动画管理器
*/
reset() {
defaultAnimationManager.reset();
},
/**
* 销毁动画管理器
*/
destroy() {
defaultAnimationManager.destroy();
},
};
/**
* 动画预设
*/
export const animationPresets = presetAnimations;
/**
* 创建自定义动画
* @param {Object} config - 动画配置
* @returns {Object} 动画配置
*/
export const createAnimation = (config) => {
return {
enter: config.enter || {},
leave: config.leave || {},
duration: config.duration || 300,
easing: config.easing || 'cubic-bezier(0.4, 0, 0.2, 1)',
delay: config.delay || 0,
iterations: config.iterations || 1,
direction: config.direction || 'normal',
fillMode: config.fillMode || 'forwards',
};
};
/**
* 组合动画
* @param {Array} animations - 动画数组
* @returns {Object} 组合后的动画配置
*/
export const combineAnimations = (animations) => {
const combined = {
enter: {},
leave: {},
duration: 0,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
delay: 0,
iterations: 1,
direction: 'normal',
fillMode: 'forwards',
};
animations.forEach((anim) => {
if (anim.enter) {
Object.assign(combined.enter, anim.enter);
}
if (anim.leave) {
Object.assign(combined.leave, anim.leave);
}
if (anim.duration) {
combined.duration = Math.max(combined.duration, anim.duration);
}
if (anim.easing) {
combined.easing = anim.easing;
}
if (anim.delay) {
combined.delay = Math.max(combined.delay, anim.delay);
}
});
return combined;
};
/**
* 链式动画
* @param {Array} animations - 动画数组
* @returns {Promise} 动画完成Promise
*/
export const chainAnimations = async (element, animations) => {
for (const anim of animations) {
await defaultAnimationManager.apply(element, anim.name, anim.options);
}
};
/**
* 并行动画
* @param {Array} animations - 动画数组
* @returns {Promise} 动画完成Promise
*/
export const parallelAnimations = (element, animations) => {
return Promise.all(
animations.map((anim) =>
defaultAnimationManager.apply(element, anim.name, anim.options)
)
);
};
/**
* 延迟动画
* @param {number} delay - 延迟时间(毫秒)
* @returns {Promise} Promise对象
*/
export const delayAnimation = (delay) => {
return new Promise((resolve) => setTimeout(resolve, delay));
};
/**
* 动画队列
*/
export class AnimationQueue {
constructor() {
this.queue = [];
this.isProcessing = false;
}
/**
* 添加动画到队列
* @param {Function} animationFn - 动画函数
* @returns {Promise} 动画完成Promise
*/
add(animationFn) {
return new Promise((resolve, reject) => {
this.queue.push({
fn: animationFn,
resolve,
reject,
});
this._process();
});
}
/**
* 处理队列
*/
async _process() {
if (this.isProcessing || this.queue.length === 0) {
return;
}
this.isProcessing = true;
while (this.queue.length > 0) {
const { fn, resolve, reject } = this.queue.shift();
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
}
}
this.isProcessing = false;
}
/**
* 清空队列
*/
clear() {
this.queue = [];
}
/**
* 暂停队列
*/
pause() {
this.isProcessing = true;
}
/**
* 恢复队列
*/
resume() {
this.isProcessing = false;
this._process();
}
/**
* 获取队列长度
* @returns {number} 队列长度
*/
get length() {
return this.queue.length;
}
/**
* 检查是否正在处理
* @returns {boolean} 是否正在处理
*/
get processing() {
return this.isProcessing;
}
}
/**
* 创建动画队列
* @returns {AnimationQueue} 动画队列实例
*/
export const createAnimationQueue = () => {
return new AnimationQueue();
};
/**
* 动画性能监控
*/
export class AnimationPerformanceMonitor {
constructor() {
this.metrics = {
totalAnimations: 0,
activeAnimations: 0,
averageDuration: 0,
maxDuration: 0,
minDuration: Infinity,
};
this.history = [];
}
/**
* 记录动画开始
* @param {string} animationName - 动画名称
*/
recordStart(animationName) {
this.metrics.totalAnimations++;
this.metrics.activeAnimations++;
this.history.push({
name: animationName,
startTime: performance.now(),
endTime: null,
duration: null,
});
}
/**
* 记录动画结束
* @param {string} animationName - 动画名称
*/
recordEnd(animationName) {
this.metrics.activeAnimations--;
const entry = this.history.find(
(h) => h.name === animationName && h.endTime === null
);
if (entry) {
entry.endTime = performance.now();
entry.duration = entry.endTime - entry.startTime;
// 更新统计
this.metrics.maxDuration = Math.max(this.metrics.maxDuration, entry.duration);
this.metrics.minDuration = Math.min(this.metrics.minDuration, entry.duration);
// 计算平均值
const completed = this.history.filter((h) => h.duration !== null);
this.metrics.averageDuration =
completed.reduce((sum, h) => sum + h.duration, 0) / completed.length;
}
}
/**
* 获取指标
* @returns {Object} 性能指标
*/
getMetrics() {
return { ...this.metrics };
}
/**
* 获取历史记录
* @returns {Array} 历史记录
*/
getHistory() {
return [...this.history];
}
/**
* 重置监控
*/
reset() {
this.metrics = {
totalAnimations: 0,
activeAnimations: 0,
averageDuration: 0,
maxDuration: 0,
minDuration: Infinity,
};
this.history = [];
}
}
/**
* 创建性能监控实例
* @returns {AnimationPerformanceMonitor} 性能监控实例
*/
export const createPerformanceMonitor = () => {
return new AnimationPerformanceMonitor();
};
/**
* 动画缓存管理
*/
export const animationCacheManager = {
/**
* 缓存动画
* @param {string} key - 缓存键
* @param {Object} animation - 动画配置
*/
set(key, animation) {
animationCache.set(key, animation);
},
/**
* 获取缓存动画
* @param {string} key - 缓存键
* @returns {Object|null} 动画配置
*/
get(key) {
return animationCache.get(key) || null;
},
/**
* 检查缓存
* @param {string} key - 缓存键
* @returns {boolean} 是否存在
*/
has(key) {
return animationCache.has(key);
},
/**
* 删除缓存
* @param {string} key - 缓存键
*/
delete(key) {
animationCache.delete(key);
},
/**
* 清空缓存
*/
clear() {
animationCache.clear();
},
/**
* 获取缓存大小
* @returns {number} 缓存大小
*/
size() {
return animationCache.size;
},
};
export { AnimationManager, defaultAnimationManager };
+328
View File
@@ -0,0 +1,328 @@
/**
* MetonaToast Constants - 常量定义
* @module constants
* @version 2.0.0
* @description 默认配置、颜色、动画、主题等常量
*/
/**
* 默认配置
*/
export const DEFAULTS = Object.freeze({
// 基础配置
position: 'top-right',
duration: 4000,
max: 6,
gap: 12,
offset: 24,
// 交互配置
pauseOnHover: true,
closeOnClick: true,
draggable: true,
// 显示配置
showProgress: true,
progressDirection: 'horizontal',
icon: true,
closeButton: true,
// 主题配置
theme: 'auto',
animation: 'slide',
// 布局配置
zIndex: 9999,
width: 360,
// 自定义配置
className: '',
style: {},
// 回调函数
onShow: null,
onClose: null,
onClick: null,
// 国际化
locale: 'zh-CN',
// 插件
plugins: [],
});
// Icons — 从 icons.js 导入并重新导出(保持向后兼容)
import { ICONS } from './icons.js';
export { ICONS };
/**
* 类型颜色配置
*/
export const TYPE_COLORS = {
success: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
error: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
warning: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
info: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
loading: { fg: '#6366f1', bg: 'rgba(99, 102, 241, 0.1)' },
default: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
check: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
x: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
alert: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
question: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
star: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
heart: { fg: '#ec4899', bg: 'rgba(236, 72, 153, 0.1)' },
bell: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
mail: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
settings: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
user: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
home: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
search: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
plus: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
minus: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
edit: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
trash: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
download: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
upload: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
share: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
link: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
external: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
clock: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
calendar: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
map: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
compass: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
globe: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
wifi: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
cloud: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
sun: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
moon: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
zap: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
activity: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
cpu: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
database: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
server: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
terminal: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
code: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
git: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
package: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
layers: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
grid: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
list: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
filter: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
sort: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
refresh: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
sync: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
power: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
battery: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
bluetooth: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
volume: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
mic: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
camera: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
image: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
video: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
music: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
file: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
folder: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
clipboard: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
save: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
print: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
eye: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
eyeOff: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
lock: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
unlock: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
shield: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
key: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
flag: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
bookmark: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
tag: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
gift: { fg: '#ec4899', bg: 'rgba(236, 72, 153, 0.1)' },
award: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
target: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
crosshair: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
move: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
maximize: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
minimize: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
copy: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
cut: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
paste: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
rotateCw: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
rotateCcw: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
zoomIn: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
zoomOut: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
crop: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
sliders: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
toggleLeft: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
toggleRight: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
checkCircle: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
xCircle: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
alertCircle: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
infoCircle: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
helpCircle: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
alertTriangle: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
checkSquare: { fg: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
square: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
circle: { fg: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
triangle: { fg: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
hexagon: { fg: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
octagon: { fg: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
pentagon: { fg: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
diamond: { fg: '#ec4899', bg: 'rgba(236, 72, 153, 0.1)' },
};
/**
* 动画配置
*/
export const ANIMATIONS = {
slide: {
enter: { transform: 'translateX(80px)', opacity: 0 },
leave: { transform: 'translateX(120%)', opacity: 0 },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
fade: {
enter: { opacity: 0, filter: 'blur(3px)' },
leave: { opacity: 0, filter: 'blur(3px)' },
duration: 500,
easing: 'ease',
},
scale: {
enter: { transform: 'scale(0.55)', opacity: 0 },
leave: { transform: 'scale(0.55)', opacity: 0 },
duration: 450,
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
},
bounce: {
enter: { transform: 'translateY(-80px)', opacity: 0 },
leave: { transform: 'translateY(20px)', opacity: 0 },
duration: 650,
easing: 'ease',
},
flip: {
enter: { transform: 'perspective(500px) rotateX(-90deg)', opacity: 0 },
leave: { transform: 'perspective(500px) rotateX(90deg)', opacity: 0 },
duration: 500,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
rotate: {
enter: { transform: 'rotate(-25deg) scale(0.6)', opacity: 0 },
leave: { transform: 'rotate(25deg) scale(0.6)', opacity: 0 },
duration: 500,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
zoom: {
enter: { transform: 'scale(0.1)', opacity: 0 },
leave: { transform: 'scale(0.1)', opacity: 0 },
duration: 500,
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
},
slideUp: {
enter: { transform: 'translateY(60px)', opacity: 0 },
leave: { transform: 'translateY(-120%)', opacity: 0 },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
slideDown: {
enter: { transform: 'translateY(-60px)', opacity: 0 },
leave: { transform: 'translateY(120%)', opacity: 0 },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
slideLeft: {
enter: { transform: 'translateX(-90px)', opacity: 0 },
leave: { transform: 'translateX(120%)', opacity: 0 },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
slideRight: {
enter: { transform: 'translateX(90px)', opacity: 0 },
leave: { transform: 'translateX(-120%)', opacity: 0 },
duration: 400,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
},
};
/**
* 主题配置
*/
export const THEMES = {
light: {
bg: 'rgba(255, 255, 255, 0.96)',
text: '#1f2937',
border: 'rgba(0, 0, 0, 0.06)',
shadow: '0 10px 36px -10px rgba(0, 0, 0, 0.18), 0 4px 14px -4px rgba(0, 0, 0, 0.08)',
hoverShadow: '0 14px 48px -10px rgba(0, 0, 0, 0.22), 0 6px 18px -4px rgba(0, 0, 0, 0.10)',
progressBg: 'rgba(0, 0, 0, 0.06)',
closeHoverBg: 'rgba(0, 0, 0, 0.06)',
},
dark: {
bg: 'rgba(28, 32, 40, 0.94)',
text: '#e6e8eb',
border: 'rgba(255, 255, 255, 0.08)',
shadow: '0 10px 36px -10px rgba(0, 0, 0, 0.6), 0 4px 14px -4px rgba(0, 0, 0, 0.4)',
hoverShadow: '0 14px 48px -8px rgba(0, 0, 0, 0.6), 0 6px 18px -4px rgba(0, 0, 0, 0.4)',
progressBg: 'rgba(255, 255, 255, 0.08)',
closeHoverBg: 'rgba(255, 255, 255, 0.08)',
},
auto: 'auto',
warm: {
bg: 'rgba(255, 251, 235, 0.96)',
text: '#78350f',
border: 'rgba(245, 158, 11, 0.2)',
shadow: '0 10px 36px -10px rgba(245, 158, 11, 0.18), 0 4px 14px -4px rgba(245, 158, 11, 0.08)',
hoverShadow: '0 14px 48px -10px rgba(245, 158, 11, 0.22), 0 6px 18px -4px rgba(245, 158, 11, 0.10)',
progressBg: 'rgba(245, 158, 11, 0.1)',
closeHoverBg: 'rgba(245, 158, 11, 0.1)',
},
};
/**
* 位置配置
*/
export const POSITIONS = [
'top-left',
'top-center',
'top-right',
'bottom-left',
'bottom-center',
'bottom-right',
];
/**
* 类型配置
*/
export const TYPES = [
'default', 'success', 'error', 'warning', 'info', 'loading',
'check', 'x', 'alert', 'question', 'star', 'heart', 'bell', 'mail',
'settings', 'user', 'home', 'search', 'plus', 'minus', 'edit', 'trash',
'download', 'upload', 'share', 'link', 'external', 'clock', 'calendar',
'map', 'compass', 'globe', 'wifi', 'cloud', 'sun', 'moon', 'zap',
'activity', 'cpu', 'database', 'server', 'terminal', 'code', 'git',
'package', 'layers', 'grid', 'list', 'filter', 'sort', 'refresh', 'sync',
'power', 'battery', 'bluetooth', 'volume', 'mic', 'camera', 'image', 'video',
'music', 'file', 'folder', 'clipboard', 'save', 'print', 'eye', 'eyeOff',
'lock', 'unlock', 'shield', 'key', 'flag', 'bookmark', 'tag', 'gift',
'award', 'target', 'crosshair', 'move', 'maximize', 'minimize',
'copy', 'cut', 'paste', 'rotateCw', 'rotateCcw', 'zoomIn', 'zoomOut',
'crop', 'sliders', 'toggleLeft', 'toggleRight',
'checkCircle', 'xCircle', 'alertCircle', 'infoCircle', 'helpCircle',
'alertTriangle', 'checkSquare', 'square', 'circle', 'triangle',
'hexagon', 'octagon', 'pentagon', 'diamond',
];
// Locales — 从 locales.js 导入并重新导出(保持向后兼容)
import { LOCALES } from './locales.js';
export { LOCALES };
/**
* 进度条方向
*/
export const PROGRESS_DIRECTIONS = ['horizontal', 'vertical'];
/**
* 动画类型
*/
export const ANIMATION_TYPES = ['slide', 'fade', 'scale', 'bounce', 'flip', 'rotate', 'zoom', 'slideUp', 'slideDown', 'slideLeft', 'slideRight'];
/**
* 主题类型
*/
export const THEME_TYPES = ['light', 'dark', 'auto'];
+1070
View File
File diff suppressed because it is too large Load Diff
+1054
View File
File diff suppressed because it is too large Load Diff
+603
View File
@@ -0,0 +1,603 @@
/**
* MetonaToast Icons — 图标SVG定义
* @module icons
* @version 2.0.0
* @description 80+ 内置SVG图标
*/
export const ICONS = {
success: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"/>
</svg>`,
error: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<line x1="9" y1="9" x2="15" y2="15"/>
</svg>`,
warning: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
<line x1="12" y1="9" x2="12" y2="13"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>`,
info: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="16" x2="12" y2="12"/>
<line x1="12" y1="8" x2="12.01" y2="8"/>
</svg>`,
loading: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12a9 9 0 1 1-6.219-8.56" class="met-spin"/>
</svg>`,
close: `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>`,
check: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"/>
</svg>`,
x: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>`,
alert: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
<line x1="12" y1="9" x2="12" y2="13"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>`,
question: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>`,
star: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
</svg>`,
heart: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
</svg>`,
bell: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
</svg>`,
mail: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>`,
settings: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>`,
user: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>`,
home: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
<polyline points="9 22 9 12 15 12 15 22"/>
</svg>`,
search: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>`,
plus: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"/>
<line x1="5" y1="12" x2="19" y2="12"/>
</svg>`,
minus: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="12" x2="19" y2="12"/>
</svg>`,
edit: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>`,
trash: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<line x1="10" y1="11" x2="10" y2="17"/>
<line x1="14" y1="11" x2="14" y2="17"/>
</svg>`,
download: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>`,
upload: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
</svg>`,
share: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="18" cy="5" r="3"/>
<circle cx="6" cy="12" r="3"/>
<circle cx="18" cy="19" r="3"/>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
</svg>`,
link: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
</svg>`,
external: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
<polyline points="15 3 21 3 21 9"/>
<line x1="10" y1="14" x2="21" y2="3"/>
</svg>`,
clock: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>`,
calendar: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
</svg>`,
map: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/>
<line x1="8" y1="2" x2="8" y2="18"/>
<line x1="16" y1="6" x2="16" y2="22"/>
</svg>`,
compass: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/>
</svg>`,
globe: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="2" y1="12" x2="22" y2="12"/>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg>`,
wifi: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12.55a11 11 0 0 1 14.08 0"/>
<path d="M1.42 9a16 16 0 0 1 21.16 0"/>
<path d="M8.53 16.11a6 6 0 0 1 6.95 0"/>
<line x1="12" y1="20" x2="12.01" y2="20"/>
</svg>`,
cloud: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"/>
</svg>`,
sun: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>`,
moon: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>`,
zap: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</svg>`,
activity: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>`,
cpu: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="4" y="4" width="16" height="16" rx="2" ry="2"/>
<rect x="9" y="9" width="6" height="6"/>
<line x1="9" y1="1" x2="9" y2="4"/>
<line x1="15" y1="1" x2="15" y2="4"/>
<line x1="9" y1="20" x2="9" y2="23"/>
<line x1="15" y1="20" x2="15" y2="23"/>
<line x1="20" y1="9" x2="23" y2="9"/>
<line x1="20" y1="14" x2="23" y2="14"/>
<line x1="1" y1="9" x2="4" y2="9"/>
<line x1="1" y1="14" x2="4" y2="14"/>
</svg>`,
database: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3"/>
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
</svg>`,
server: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="2" width="20" height="8" rx="2" ry="2"/>
<rect x="2" y="14" width="20" height="8" rx="2" ry="2"/>
<line x1="6" y1="6" x2="6.01" y2="6"/>
<line x1="6" y1="18" x2="6.01" y2="18"/>
</svg>`,
terminal: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 17 10 11 4 5"/>
<line x1="12" y1="19" x2="20" y2="19"/>
</svg>`,
code: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="16 18 22 12 16 6"/>
<polyline points="8 6 2 12 8 18"/>
</svg>`,
git: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="18" cy="18" r="3"/>
<circle cx="6" cy="6" r="3"/>
<path d="M13 6h3a2 2 0 0 1 2 2v7"/>
<line x1="6" y1="9" x2="6" y2="21"/>
</svg>`,
package: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="16.5" y1="9.4" x2="7.5" y2="4.21"/>
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
<line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>`,
layers: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 2 7 12 12 22 7 12 2"/>
<polyline points="2 17 12 22 22 17"/>
<polyline points="2 12 12 17 22 12"/>
</svg>`,
grid: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"/>
<rect x="14" y="3" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/>
<rect x="3" y="14" width="7" height="7"/>
</svg>`,
list: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="8" y1="6" x2="21" y2="6"/>
<line x1="8" y1="12" x2="21" y2="12"/>
<line x1="8" y1="18" x2="21" y2="18"/>
<line x1="3" y1="6" x2="3.01" y2="6"/>
<line x1="3" y1="12" x2="3.01" y2="12"/>
<line x1="3" y1="18" x2="3.01" y2="18"/>
</svg>`,
filter: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>
</svg>`,
sort: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"/>
<polyline points="19 12 12 19 5 12"/>
</svg>`,
refresh: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"/>
<polyline points="1 20 1 14 7 14"/>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
</svg>`,
sync: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"/>
<polyline points="1 20 1 14 7 14"/>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
</svg>`,
power: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18.36 6.64a9 9 0 1 1-12.73 0"/>
<line x1="12" y1="2" x2="12" y2="12"/>
</svg>`,
battery: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="1" y="6" width="18" height="12" rx="2" ry="2"/>
<line x1="23" y1="13" x2="23" y2="11"/>
</svg>`,
bluetooth: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6.5 6.5 17.5 17.5 12 23 12 1 17.5 6.5 6.5 17.5"/>
</svg>`,
volume: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/>
</svg>`,
mic: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" y1="19" x2="12" y2="23"/>
<line x1="8" y1="23" x2="16" y2="23"/>
</svg>`,
camera: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
<circle cx="12" cy="13" r="4"/>
</svg>`,
image: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
<polyline points="21 15 16 10 5 21"/>
</svg>`,
video: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="23 7 16 12 23 17 23 7"/>
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
</svg>`,
music: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 18V5l12-2v13"/>
<circle cx="6" cy="18" r="3"/>
<circle cx="18" cy="16" r="3"/>
</svg>`,
file: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
<polyline points="13 2 13 9 20 9"/>
</svg>`,
folder: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>`,
clipboard: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
</svg>`,
save: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17 21 17 13 7 13 7 21"/>
<polyline points="7 3 7 8 15 8"/>
</svg>`,
print: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 6 2 18 2 18 9"/>
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/>
<rect x="6" y="14" width="12" height="8"/>
</svg>`,
eye: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
</svg>`,
eyeOff: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
<line x1="1" y1="1" x2="23" y2="23"/>
</svg>`,
lock: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>`,
unlock: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
<path d="M7 11V7a5 5 0 0 1 9.9-1"/>
</svg>`,
shield: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>`,
key: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/>
</svg>`,
flag: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/>
<line x1="4" y1="22" x2="4" y2="15"/>
</svg>`,
bookmark: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/>
</svg>`,
tag: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/>
<line x1="7" y1="7" x2="7.01" y2="7"/>
</svg>`,
gift: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 12 20 22 4 22 4 12"/>
<rect x="2" y="7" width="20" height="5"/>
<line x1="12" y1="22" x2="12" y2="7"/>
<path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"/>
<path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"/>
</svg>`,
award: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="7"/>
<polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"/>
</svg>`,
target: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<circle cx="12" cy="12" r="6"/>
<circle cx="12" cy="12" r="2"/>
</svg>`,
crosshair: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="22" y1="12" x2="18" y2="12"/>
<line x1="6" y1="12" x2="2" y2="12"/>
<line x1="12" y1="6" x2="12" y2="2"/>
<line x1="12" y1="22" x2="12" y2="18"/>
</svg>`,
move: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="5 9 2 12 5 15"/>
<polyline points="9 5 12 2 15 5"/>
<polyline points="15 19 12 22 9 19"/>
<polyline points="19 9 22 12 19 15"/>
<line x1="2" y1="12" x2="22" y2="12"/>
<line x1="12" y1="2" x2="12" y2="22"/>
</svg>`,
maximize: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
</svg>`,
minimize: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>
</svg>`,
copy: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</svg>`,
cut: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="6" cy="6" r="3"/>
<circle cx="6" cy="18" r="3"/>
<line x1="20" y1="4" x2="8.12" y2="15.88"/>
<line x1="14.47" y1="14.48" x2="20" y2="20"/>
<line x1="8.12" y1="8.12" x2="12" y2="12"/>
</svg>`,
paste: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
</svg>`,
rotateCw: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"/>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
</svg>`,
rotateCcw: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="1 4 1 10 7 10"/>
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
</svg>`,
zoomIn: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
<line x1="11" y1="8" x2="11" y2="14"/>
<line x1="8" y1="11" x2="14" y2="11"/>
</svg>`,
zoomOut: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
<line x1="8" y1="11" x2="14" y2="11"/>
</svg>`,
crop: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M6.13 1L6 16a2 2 0 0 0 2 2h15"/>
<path d="M1 6.13L16 6a2 2 0 0 1 2 2v15"/>
</svg>`,
sliders: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="4" y1="21" x2="4" y2="14"/>
<line x1="4" y1="10" x2="4" y2="3"/>
<line x1="12" y1="21" x2="12" y2="12"/>
<line x1="12" y1="8" x2="12" y2="3"/>
<line x1="20" y1="21" x2="20" y2="16"/>
<line x1="20" y1="12" x2="20" y2="3"/>
<line x1="1" y1="14" x2="7" y2="14"/>
<line x1="9" y1="8" x2="15" y2="8"/>
<line x1="17" y1="16" x2="23" y2="16"/>
</svg>`,
toggleLeft: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="1" y="5" width="22" height="14" rx="7" ry="7"/>
<circle cx="8" cy="12" r="3"/>
</svg>`,
toggleRight: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="1" y="5" width="22" height="14" rx="7" ry="7"/>
<circle cx="16" cy="12" r="3"/>
</svg>`,
checkCircle: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22 4 12 14.01 9 11.01"/>
</svg>`,
xCircle: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<line x1="9" y1="9" x2="15" y2="15"/>
</svg>`,
alertCircle: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="8" x2="12" y2="12"/>
<line x1="12" y1="16" x2="12.01" y2="16"/>
</svg>`,
infoCircle: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="16" x2="12" y2="12"/>
<line x1="12" y1="8" x2="12.01" y2="8"/>
</svg>`,
helpCircle: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>`,
alertTriangle: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
<line x1="12" y1="9" x2="12" y2="13"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>`,
checkSquare: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 11 12 14 22 4"/>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
</svg>`,
square: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
</svg>`,
circle: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
</svg>`,
triangle: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
</svg>`,
hexagon: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
</svg>`,
octagon: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"/>
</svg>`,
pentagon: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 22 8.5 19 20 5 20 2 8.5 12 2"/>
</svg>`,
diamond: `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="4.5" y="4.5" width="15" height="15" rx="1" transform="rotate(45 12 12)"/>
</svg>`,
};
+264
View File
@@ -0,0 +1,264 @@
/**
* MetonaToast - 轻量级Toast通知库
* @module metona-toast
* @version 2.0.0
* @author metona
* @description 轻量、零依赖、精致美观的Toast通知库。单文件,开箱即用。
* @license MIT
*/
import { meToast, Toast } from './core.js';
import { animationUtils } from './animations.js';
import { themeUtils } from './themes.js';
import { i18nUtils } from './i18n.js';
import { pluginUtils, presetPlugins } from './plugins.js';
import { DEFAULTS } from './constants.js';
// 版本信息
const VERSION = '2.0.0';
/**
* 主对象增强
*/
const enhancedMeToast = {
...meToast,
version: VERSION,
animations: animationUtils,
themes: themeUtils,
i18n: i18nUtils,
plugins: pluginUtils,
presetPlugins,
/**
* 安装插件,并连接必要的生命周期钩子
*/
use(plugin, options = {}) {
if (typeof plugin === 'string') {
const preset = presetPlugins[plugin];
if (!preset) {
console.warn(`Preset plugin "${plugin}" not found`);
return this;
}
this.plugins.register(plugin, { ...preset, ...options });
// 连接插件钩子
if (plugin === 'accessibility') {
Toast.on('afterShow', (toast) => preset.announce(toast));
}
if (plugin === 'persistence') {
const saved = preset.install();
if (saved) this.configure(saved);
Toast.on('afterClose', () => { preset.save(this.getConfig()); });
}
} else if (plugin && typeof plugin === 'object') {
const name = plugin.name || 'custom';
this.plugins.register(name, { ...plugin, ...options });
}
return this;
},
/**
* 初始化
*/
init(options = {}) {
if (options.config) {
this.configure(options.config);
}
if (options.theme) {
this.themes.switchTheme(options.theme);
}
if (options.locale) {
this.i18n.switchLocale(options.locale);
}
if (options.plugins && Array.isArray(options.plugins)) {
options.plugins.forEach((plugin) => {
this.use(plugin);
});
}
return this;
},
/**
* 销毁
*/
destroy() {
this.dismiss();
if (this.plugins && typeof this.plugins.destroy === 'function') {
this.plugins.destroy();
}
if (this.themes) {
if (typeof this.themes.clearThemeListeners === 'function') {
this.themes.clearThemeListeners();
}
if (typeof this.themes.unwatchSystemTheme === 'function') {
this.themes.unwatchSystemTheme();
}
}
if (this.i18n && typeof this.i18n.clearLocaleListeners === 'function') {
this.i18n.clearLocaleListeners();
}
if (this.animations && typeof this.animations.cancelAll === 'function') {
this.animations.cancelAll();
}
if (typeof document !== 'undefined') {
const containers = document.querySelectorAll('.met-container');
containers.forEach((c) => c.parentNode && c.parentNode.removeChild(c));
const style = document.getElementById('metona-toast-styles');
if (style) style.parentNode.removeChild(style);
}
this._toasts.clear();
},
/**
* 获取状态
*/
getStatus() {
return {
version: VERSION,
toasts: this._toasts.size,
theme: this.themes?.getCurrentTheme?.() || 'auto',
locale: this.i18n?.getCurrentLocale?.() || 'zh-CN',
plugins: this.plugins?.getNames?.() || [],
animations: this.animations?.getActiveCount?.() || 0,
};
},
/**
* 获取配置
*/
getConfig() {
return { ...this._config };
},
/**
* 更新配置
*/
updateConfig(config) {
if (config && typeof config === 'object') {
this._config = { ...this._config, ...config };
}
return this;
},
/**
* 重置配置
*/
resetConfig() {
this._config = { ...DEFAULTS };
return this;
},
/**
* 获取Toast列表
*/
getToasts() {
return Array.from(this._toasts.values());
},
/**
* 检查是否有Toast
*/
hasToasts() {
return this._toasts.size > 0;
},
/**
* 获取Toast
*/
getToast(id) {
if (!id) return null;
return this._toasts.get(id) || null;
},
/**
* 关闭所有Toast
*/
closeAll() {
this._toasts.forEach((t) => t.close());
},
/**
* 清除所有Toast
*/
clearAll() {
this._toasts.forEach((t) => t.close());
},
/**
* 暂停所有Toast
*/
pauseAll() {
this._toasts.forEach((t) => t._pause());
},
/**
* 恢复所有Toast
*/
resumeAll() {
this._toasts.forEach((t) => t._resume());
},
/**
* 更新所有Toast
*/
updateAll(partial) {
if (partial && typeof partial === 'object') {
this._toasts.forEach((t) => t.update(partial));
}
},
/**
* 查找Toast
*/
findToasts(predicate) {
if (typeof predicate !== 'function') return [];
return Array.from(this._toasts.values()).filter(predicate);
},
/**
* 按类型查找Toast
*/
findByType(type) {
return this.findToasts((t) => t.type === type);
},
/**
* 按位置查找Toast
*/
findByPosition(position) {
return this.findToasts((t) => t.config.position === position);
},
};
// 初始化主题和国际化
if (typeof themeUtils.initTheme === 'function') {
themeUtils.initTheme();
}
if (typeof i18nUtils.initI18n === 'function') {
i18nUtils.initI18n();
}
// 浏览器环境全局注册
if (typeof window !== 'undefined') {
window.MeToast = enhancedMeToast;
window.Met = enhancedMeToast;
window.metonaToast = enhancedMeToast;
}
// 导出
export default enhancedMeToast;
export { enhancedMeToast as meToast, enhancedMeToast as Met, enhancedMeToast as MeToast, Toast, VERSION };
+752
View File
@@ -0,0 +1,752 @@
/**
* MetonaToast Locales — 国际化翻译数据
* @module locales
* @version 2.0.0
* @description 内置 zh-CN / en-US 完整翻译
*/
export const LOCALES = {
'zh-CN': {
close: '关闭',
loading: '加载中...',
success: '操作成功',
error: '操作失败',
warning: '警告',
info: '信息',
confirm: '确认',
cancel: '取消',
yes: '是',
no: '否',
ok: '确定',
retry: '重试',
undo: '撤销',
redo: '重做',
save: '保存',
delete: '删除',
edit: '编辑',
add: '添加',
remove: '移除',
search: '搜索',
filter: '筛选',
sort: '排序',
refresh: '刷新',
sync: '同步',
upload: '上传',
download: '下载',
share: '分享',
copy: '复制',
cut: '剪切',
paste: '粘贴',
print: '打印',
export: '导出',
import: '导入',
settings: '设置',
help: '帮助',
about: '关于',
version: '版本',
language: '语言',
theme: '主题',
dark: '暗色',
light: '亮色',
auto: '自动',
notifications: '通知',
enable: '启用',
disable: '禁用',
on: '开启',
off: '关闭',
max: '最大',
min: '最小',
expand: '展开',
collapse: '折叠',
fullscreen: '全屏',
exitFullscreen: '退出全屏',
zoomIn: '放大',
zoomOut: '缩小',
reset: '重置',
apply: '应用',
submit: '提交',
send: '发送',
receive: '接收',
connect: '连接',
disconnect: '断开',
start: '开始',
stop: '停止',
pause: '暂停',
resume: '继续',
next: '下一个',
previous: '上一个',
first: '第一个',
last: '最后一个',
back: '返回',
forward: '前进',
home: '首页',
menu: '菜单',
more: '更多',
less: '更少',
show: '显示',
hide: '隐藏',
visible: '可见',
hidden: '隐藏',
enabled: '已启用',
disabled: '已禁用',
active: '活跃',
inactive: '非活跃',
online: '在线',
offline: '离线',
connected: '已连接',
disconnected: '已断开',
loading: '加载中',
loaded: '已加载',
saving: '保存中',
saved: '已保存',
deleting: '删除中',
deleted: '已删除',
uploading: '上传中',
uploaded: '已上传',
downloading: '下载中',
downloaded: '已下载',
processing: '处理中',
processed: '已处理',
syncing: '同步中',
synced: '已同步',
sending: '发送中',
sent: '已发送',
receiving: '接收中',
received: '已接收',
connecting: '连接中',
error: '错误',
warning: '警告',
info: '信息',
success: '成功',
question: '问题',
help: '帮助',
feedback: '反馈',
report: '报告',
bug: '错误',
feature: '功能',
improvement: '改进',
suggestion: '建议',
request: '请求',
response: '响应',
status: '状态',
progress: '进度',
complete: '完成',
incomplete: '未完成',
pending: '待处理',
processing: '处理中',
failed: '失败',
cancelled: '已取消',
timeout: '超时',
expired: '已过期',
invalid: '无效',
valid: '有效',
required: '必填',
optional: '可选',
enabled: '启用',
disabled: '禁用',
allowed: '允许',
denied: '拒绝',
approved: '已批准',
rejected: '已拒绝',
accepted: '已接受',
declined: '已拒绝',
confirmed: '已确认',
unconfirmed: '未确认',
verified: '已验证',
unverified: '未验证',
authenticated: '已认证',
unauthenticated: '未认证',
authorized: '已授权',
unauthorized: '未授权',
public: '公开',
private: '私有',
protected: '受保护',
internal: '内部',
external: '外部',
local: '本地',
remote: '远程',
global: '全局',
system: '系统',
user: '用户',
admin: '管理员',
moderator: '版主',
guest: '访客',
member: '成员',
owner: '所有者',
creator: '创建者',
editor: '编辑者',
viewer: '查看者',
subscriber: '订阅者',
follower: '关注者',
following: '关注中',
friend: '朋友',
contact: '联系人',
group: '群组',
team: '团队',
organization: '组织',
company: '公司',
department: '部门',
project: '项目',
task: '任务',
issue: '问题',
ticket: '工单',
request: '请求',
response: '响应',
message: '消息',
notification: '通知',
alert: '警报',
reminder: '提醒',
event: '事件',
activity: '活动',
log: '日志',
report: '报告',
analytics: '分析',
statistics: '统计',
metrics: '指标',
performance: '性能',
security: '安全',
privacy: '隐私',
compliance: '合规',
policy: '政策',
terms: '条款',
conditions: '条件',
agreement: '协议',
contract: '合同',
license: '许可证',
copyright: '版权',
trademark: '商标',
patent: '专利',
tradeSecret: '商业秘密',
confidential: '机密',
sensitive: '敏感',
critical: '关键',
important: '重要',
urgent: '紧急',
high: '高',
medium: '中',
low: '低',
none: '无',
all: '全部',
any: '任何',
some: '一些',
each: '每个',
every: '每个',
both: '两者',
neither: '两者都不',
either: '任一',
other: '其他',
another: '另一个',
this: '这个',
that: '那个',
these: '这些',
those: '那些',
here: '这里',
there: '那里',
where: '哪里',
when: '何时',
why: '为什么',
how: '如何',
what: '什么',
who: '谁',
which: '哪个',
whose: '谁的',
whom: '谁',
myself: '我自己',
yourself: '你自己',
himself: '他自己',
herself: '她自己',
itself: '它自己',
ourselves: '我们自己',
yourselves: '你们自己',
themselves: '他们自己',
mine: '我的',
yours: '你的',
his: '他的',
hers: '她的',
its: '它的',
ours: '我们的',
yours: '你们的',
theirs: '他们的',
me: '我',
you: '你',
him: '他',
her: '她',
it: '它',
us: '我们',
them: '他们',
my: '我的',
your: '你的',
his: '他的',
her: '她的',
its: '它的',
our: '我们的',
your: '你们的',
their: '他们的',
i: '我',
you: '你',
he: '他',
she: '她',
it: '它',
we: '我们',
they: '他们',
am: '是',
is: '是',
are: '是',
was: '是',
were: '是',
be: '是',
been: '是',
being: '是',
have: '有',
has: '有',
had: '有',
having: '有',
do: '做',
does: '做',
did: '做',
doing: '做',
will: '会',
would: '会',
shall: '将',
should: '应该',
may: '可以',
might: '可能',
can: '能',
could: '能',
must: '必须',
need: '需要',
dare: '敢',
ought: '应该',
used: '过去常常',
to: '到',
of: '的',
in: '在',
for: '为了',
on: '在',
with: '和',
at: '在',
by: '通过',
from: '从',
into: '进入',
during: '在...期间',
before: '在...之前',
after: '在...之后',
above: '在...上面',
below: '在...下面',
between: '在...之间',
under: '在...下面',
over: '在...上面',
across: '穿过',
through: '通过',
into: '进入',
towards: '朝向',
upon: '在...上面',
about: '关于',
against: '反对',
among: '在...之中',
along: '沿着',
around: '周围',
beyond: '超出',
but: '但是',
despite: '尽管',
except: '除了',
inside: '里面',
outside: '外面',
since: '自从',
until: '直到',
unless: '除非',
whether: '是否',
while: '当...时候',
although: '虽然',
because: '因为',
if: '如果',
once: '一旦',
since: '自从',
so: '所以',
that: '那个',
though: '虽然',
till: '直到',
unless: '除非',
until: '直到',
when: '当...时候',
whenever: '每当',
where: '哪里',
wherever: '无论哪里',
whereas: '然而',
wherever: '无论哪里',
while: '当...时候',
why: '为什么',
},
'en-US': {
close: 'Close',
loading: 'Loading...',
success: 'Success',
error: 'Error',
warning: 'Warning',
info: 'Info',
confirm: 'Confirm',
cancel: 'Cancel',
yes: 'Yes',
no: 'No',
ok: 'OK',
retry: 'Retry',
undo: 'Undo',
redo: 'Redo',
save: 'Save',
delete: 'Delete',
edit: 'Edit',
add: 'Add',
remove: 'Remove',
search: 'Search',
filter: 'Filter',
sort: 'Sort',
refresh: 'Refresh',
sync: 'Sync',
upload: 'Upload',
download: 'Download',
share: 'Share',
copy: 'Copy',
cut: 'Cut',
paste: 'Paste',
print: 'Print',
export: 'Export',
import: 'Import',
settings: 'Settings',
help: 'Help',
about: 'About',
version: 'Version',
language: 'Language',
theme: 'Theme',
dark: 'Dark',
light: 'Light',
auto: 'Auto',
notifications: 'Notifications',
enable: 'Enable',
disable: 'Disable',
on: 'On',
off: 'Off',
max: 'Max',
min: 'Min',
expand: 'Expand',
collapse: 'Collapse',
fullscreen: 'Fullscreen',
exitFullscreen: 'Exit Fullscreen',
zoomIn: 'Zoom In',
zoomOut: 'Zoom Out',
reset: 'Reset',
apply: 'Apply',
submit: 'Submit',
send: 'Send',
receive: 'Receive',
connect: 'Connect',
disconnect: 'Disconnect',
start: 'Start',
stop: 'Stop',
pause: 'Pause',
resume: 'Resume',
next: 'Next',
previous: 'Previous',
first: 'First',
last: 'Last',
back: 'Back',
forward: 'Forward',
home: 'Home',
menu: 'Menu',
more: 'More',
less: 'Less',
show: 'Show',
hide: 'Hide',
visible: 'Visible',
hidden: 'Hidden',
enabled: 'Enabled',
disabled: 'Disabled',
active: 'Active',
inactive: 'Inactive',
online: 'Online',
offline: 'Offline',
connected: 'Connected',
disconnected: 'Disconnected',
loading: 'Loading',
loaded: 'Loaded',
saving: 'Saving',
saved: 'Saved',
deleting: 'Deleting',
deleted: 'Deleted',
uploading: 'Uploading',
uploaded: 'Uploaded',
downloading: 'Downloading',
downloaded: 'Downloaded',
processing: 'Processing',
processed: 'Processed',
syncing: 'Syncing',
synced: 'Synced',
sending: 'Sending',
sent: 'Sent',
receiving: 'Receiving',
received: 'Received',
connecting: 'Connecting',
error: 'Error',
warning: 'Warning',
info: 'Info',
success: 'Success',
question: 'Question',
help: 'Help',
feedback: 'Feedback',
report: 'Report',
bug: 'Bug',
feature: 'Feature',
improvement: 'Improvement',
suggestion: 'Suggestion',
request: 'Request',
response: 'Response',
status: 'Status',
progress: 'Progress',
complete: 'Complete',
incomplete: 'Incomplete',
pending: 'Pending',
processing: 'Processing',
failed: 'Failed',
cancelled: 'Cancelled',
timeout: 'Timeout',
expired: 'Expired',
invalid: 'Invalid',
valid: 'Valid',
required: 'Required',
optional: 'Optional',
enabled: 'Enabled',
disabled: 'Disabled',
allowed: 'Allowed',
denied: 'Denied',
approved: 'Approved',
rejected: 'Rejected',
accepted: 'Accepted',
declined: 'Declined',
confirmed: 'Confirmed',
unconfirmed: 'Unconfirmed',
verified: 'Verified',
unverified: 'Unverified',
authenticated: 'Authenticated',
unauthenticated: 'Unauthenticated',
authorized: 'Authorized',
unauthorized: 'Unauthorized',
public: 'Public',
private: 'Private',
protected: 'Protected',
internal: 'Internal',
external: 'External',
local: 'Local',
remote: 'Remote',
global: 'Global',
system: 'System',
user: 'User',
admin: 'Admin',
moderator: 'Moderator',
guest: 'Guest',
member: 'Member',
owner: 'Owner',
creator: 'Creator',
editor: 'Editor',
viewer: 'Viewer',
subscriber: 'Subscriber',
follower: 'Follower',
following: 'Following',
friend: 'Friend',
contact: 'Contact',
group: 'Group',
team: 'Team',
organization: 'Organization',
company: 'Company',
department: 'Department',
project: 'Project',
task: 'Task',
issue: 'Issue',
ticket: 'Ticket',
request: 'Request',
response: 'Response',
message: 'Message',
notification: 'Notification',
alert: 'Alert',
reminder: 'Reminder',
event: 'Event',
activity: 'Activity',
log: 'Log',
report: 'Report',
analytics: 'Analytics',
statistics: 'Statistics',
metrics: 'Metrics',
performance: 'Performance',
security: 'Security',
privacy: 'Privacy',
compliance: 'Compliance',
policy: 'Policy',
terms: 'Terms',
conditions: 'Conditions',
agreement: 'Agreement',
contract: 'Contract',
license: 'License',
copyright: 'Copyright',
trademark: 'Trademark',
patent: 'Patent',
tradeSecret: 'Trade Secret',
confidential: 'Confidential',
sensitive: 'Sensitive',
critical: 'Critical',
important: 'Important',
urgent: 'Urgent',
high: 'High',
medium: 'Medium',
low: 'Low',
none: 'None',
all: 'All',
any: 'Any',
some: 'Some',
each: 'Each',
every: 'Every',
both: 'Both',
neither: 'Neither',
either: 'Either',
other: 'Other',
another: 'Another',
this: 'This',
that: 'That',
these: 'These',
those: 'Those',
here: 'Here',
there: 'There',
where: 'Where',
when: 'When',
why: 'Why',
how: 'How',
what: 'What',
who: 'Who',
which: 'Which',
whose: 'Whose',
whom: 'Whom',
myself: 'Myself',
yourself: 'Yourself',
himself: 'Himself',
herself: 'Herself',
itself: 'Itself',
ourselves: 'Ourselves',
yourselves: 'Yourselves',
themselves: 'Themselves',
mine: 'Mine',
yours: 'Yours',
his: 'His',
hers: 'Hers',
its: 'Its',
ours: 'Ours',
yours: 'Yours',
theirs: 'Theirs',
me: 'Me',
you: 'You',
him: 'Him',
her: 'Her',
it: 'It',
us: 'Us',
them: 'Them',
my: 'My',
your: 'Your',
his: 'His',
her: 'Her',
its: 'Its',
our: 'Our',
your: 'Your',
their: 'Their',
i: 'I',
you: 'You',
he: 'He',
she: 'She',
it: 'It',
we: 'We',
they: 'They',
am: 'Am',
is: 'Is',
are: 'Are',
was: 'Was',
were: 'Were',
be: 'Be',
been: 'Been',
being: 'Being',
have: 'Have',
has: 'Has',
had: 'Had',
having: 'Having',
do: 'Do',
does: 'Does',
did: 'Did',
doing: 'Doing',
will: 'Will',
would: 'Would',
shall: 'Shall',
should: 'Should',
may: 'May',
might: 'Might',
can: 'Can',
could: 'Could',
must: 'Must',
need: 'Need',
dare: 'Dare',
ought: 'Ought',
used: 'Used',
to: 'To',
of: 'Of',
in: 'In',
for: 'For',
on: 'On',
with: 'With',
at: 'At',
by: 'By',
from: 'From',
into: 'Into',
during: 'During',
before: 'Before',
after: 'After',
above: 'Above',
below: 'Below',
between: 'Between',
under: 'Under',
over: 'Over',
across: 'Across',
through: 'Through',
into: 'Into',
towards: 'Towards',
upon: 'Upon',
about: 'About',
against: 'Against',
among: 'Among',
along: 'Along',
around: 'Around',
beyond: 'Beyond',
but: 'But',
despite: 'Despite',
except: 'Except',
inside: 'Inside',
outside: 'Outside',
since: 'Since',
until: 'Until',
unless: 'Unless',
whether: 'Whether',
while: 'While',
although: 'Although',
because: 'Because',
if: 'If',
once: 'Once',
since: 'Since',
so: 'So',
that: 'That',
though: 'Though',
till: 'Till',
unless: 'Unless',
until: 'Until',
when: 'When',
whenever: 'Whenever',
where: 'Where',
wherever: 'Wherever',
whereas: 'Whereas',
wherever: 'Wherever',
while: 'While',
why: 'Why',
},
};
+204
View File
@@ -0,0 +1,204 @@
/**
* MetonaToast Plugins - 插件系统
* @module plugins
* @version 2.0.0
* @description 插件管理器 + 3 款预设插件 (keyboard / persistence / accessibility)
*/
/**
* 插件管理器
*/
class PluginManager {
constructor() {
this.plugins = new Map();
this.initialized = false;
}
register(name, plugin) {
if (this.plugins.has(name)) {
console.warn(`Plugin "${name}" is already registered`);
return this;
}
if (!plugin || typeof plugin !== 'object' || (!plugin.name && !plugin.version)) {
console.error(`Invalid plugin "${name}"`);
return this;
}
this.plugins.set(name, { name, ...plugin, installed: false, enabled: true });
if (plugin.install) {
try { plugin.install(this); this.plugins.get(name).installed = true; }
catch (e) { console.error(`Failed to install plugin "${name}":`, e); }
}
return this;
}
unregister(name) {
const plugin = this.plugins.get(name);
if (!plugin) return this;
if (plugin.uninstall) {
try { plugin.uninstall(this); } catch (e) { console.error(`Failed to uninstall plugin "${name}":`, e); }
}
this.plugins.delete(name);
return this;
}
get(name) { return this.plugins.get(name) || null; }
has(name) { return this.plugins.has(name); }
getAll() { return Array.from(this.plugins.values()); }
getNames() { return Array.from(this.plugins.keys()); }
enable(name) {
const p = this.plugins.get(name);
if (p) p.enabled = true;
return this;
}
disable(name) {
const p = this.plugins.get(name);
if (p) p.enabled = false;
return this;
}
isEnabled(name) {
const p = this.plugins.get(name);
return p ? p.enabled : false;
}
destroy() {
this.plugins.forEach((p, name) => {
if (p.destroy) { try { p.destroy(this); } catch (e) {} }
});
this.plugins.clear();
}
}
/**
* 预设插件
*/
const presetPlugins = {
/**
* 键盘快捷键插件 — ESC 关闭所有 Toast
*/
keyboard: {
name: 'keyboard',
version: '1.0.0',
description: 'ESC 关闭所有 Toast',
_handler: null,
install() {
if (typeof document === 'undefined') return;
this._handler = (e) => {
if (e.key === 'Escape') {
// 点击所有关闭按钮
document.querySelectorAll('.met-toast .met-close').forEach(btn => {
try { btn.click(); } catch (_) {}
});
}
};
document.addEventListener('keydown', this._handler);
},
uninstall() {
if (this._handler && typeof document !== 'undefined') {
document.removeEventListener('keydown', this._handler);
this._handler = null;
}
},
},
/**
* 持久化插件 — 自动保存/加载配置到 localStorage
*/
persistence: {
name: 'persistence',
version: '1.0.0',
description: '自动持久化配置到 localStorage',
storageKey: 'metona-toast-config',
install() {
// 加载已保存的配置
if (typeof localStorage !== 'undefined') {
try {
const saved = localStorage.getItem(this.storageKey);
return saved ? JSON.parse(saved) : null;
} catch (_) { return null; }
}
return null;
},
save(config) {
if (typeof localStorage !== 'undefined') {
try { localStorage.setItem(this.storageKey, JSON.stringify(config)); } catch (_) {}
}
},
uninstall() {
if (typeof localStorage !== 'undefined') {
try { localStorage.removeItem(this.storageKey); } catch (_) {}
}
},
},
/**
* 无障碍插件 — 屏幕阅读器公告
*/
accessibility: {
name: 'accessibility',
version: '1.0.0',
description: '通过屏幕阅读器朗读 Toast 内容',
announce(toast) {
if (typeof document === 'undefined') return;
const el = document.createElement('div');
el.setAttribute('aria-live', 'assertive');
el.setAttribute('aria-atomic', 'true');
el.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;';
const typeNames = { success:'成功', error:'错误', warning:'警告', info:'信息', loading:'加载中', default:'通知' };
el.textContent = `${typeNames[toast.type] || '通知'}: ${toast.title || ''} ${toast.message || ''}`;
document.body.appendChild(el);
setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 3000);
},
},
};
/**
* 插件工具
*/
const pluginUtils = {
createManager() { return new PluginManager(); },
register(name, plugin) { return defaultPluginManager.register(name, plugin); },
unregister(name) { return defaultPluginManager.unregister(name); },
get(name) { return defaultPluginManager.get(name); },
has(name) { return defaultPluginManager.has(name); },
getAll() { return defaultPluginManager.getAll(); },
getNames() { return defaultPluginManager.getNames(); },
enable(name) { return defaultPluginManager.enable(name); },
disable(name) { return defaultPluginManager.disable(name); },
isEnabled(name) { return defaultPluginManager.isEnabled(name); },
getPreset(name) { return presetPlugins[name] || null; },
getAllPresets() { return { ...presetPlugins }; },
createPlugin(config) {
return {
name: config.name || 'custom',
version: config.version || '1.0.0',
description: config.description || '',
hooks: config.hooks || {},
install: config.install || null,
uninstall: config.uninstall || null,
...config,
};
},
validatePlugin(plugin) {
const errors = [];
if (!plugin || typeof plugin !== 'object') errors.push('Plugin must be an object');
if (!plugin.name && !plugin.version) errors.push('Plugin must have a name or version');
return { valid: errors.length === 0, errors };
},
};
const defaultPluginManager = new PluginManager();
export { presetPlugins, pluginUtils, defaultPluginManager, PluginManager };
+1848
View File
File diff suppressed because it is too large Load Diff
+722
View File
@@ -0,0 +1,722 @@
/**
* MetonaToast Themes - 主题管理
* @module themes
* @version 2.0.0
* @description 主题系统、自定义主题和主题切换
*/
import { THEMES } from './constants.js';
import { prefersDark } from './utils.js';
// 当前主题状态
let currentTheme = 'auto';
let themeListeners = new Set();
let systemThemeMediaQuery = null;
let systemThemeListener = null;
/**
* 获取系统主题
* @returns {string} 系统主题 ('light' 或 'dark')
*/
export const getSystemTheme = () => {
if (typeof window === 'undefined') return 'light';
return prefersDark() ? 'dark' : 'light';
};
/**
* 获取主题(别名)
* @param {string} theme - 主题名称
* @returns {string} 解析后的主题
*/
export const getTheme = (theme) => {
return resolveTheme(theme);
};
/**
* 解析主题
* @param {string} theme - 主题名称
* @returns {string} 解析后的主题
*/
export const resolveTheme = (theme) => {
if (theme === 'auto') {
// 优先使用用户设置的全局主题,否则使用系统主题
if (currentTheme && currentTheme !== 'auto') {
return currentTheme;
}
return getSystemTheme();
}
return theme;
};
/**
* 获取主题配置
* @param {string} theme - 主题名称
* @returns {Object} 主题配置
*/
export const getThemeConfig = (theme) => {
const resolved = resolveTheme(theme);
return THEMES[resolved] || THEMES.light;
};
/**
* 应用主题
* @param {string} theme - 主题名称
*/
export const applyTheme = (theme) => {
currentTheme = theme;
const resolved = resolveTheme(theme);
// 应用主题到文档
if (typeof document !== 'undefined') {
document.documentElement.setAttribute('data-met-theme', resolved);
document.documentElement.classList.remove('met-theme-light', 'met-theme-dark', 'met-theme-auto');
document.documentElement.classList.add(`met-theme-${resolved}`);
// 设置CSS变量
const config = getThemeConfig(theme);
setThemeVariables(config);
}
// 通知监听器
notifyThemeListeners(theme, resolved);
};
/**
* 设置主题CSS变量
* @param {Object} config - 主题配置
*/
export const setThemeVariables = (config) => {
if (typeof document === 'undefined') return;
const root = document.documentElement;
root.style.setProperty('--met-bg', config.bg);
root.style.setProperty('--met-text', config.text);
root.style.setProperty('--met-border', config.border);
root.style.setProperty('--met-shadow', config.shadow);
root.style.setProperty('--met-hover-shadow', config.hoverShadow);
root.style.setProperty('--met-progress-bg', config.progressBg);
root.style.setProperty('--met-close-hover-bg', config.closeHoverBg);
// 设置颜色变量
root.style.setProperty('--met-success', '#10b981');
root.style.setProperty('--met-error', '#ef4444');
root.style.setProperty('--met-warning', '#f59e0b');
root.style.setProperty('--met-info', '#3b82f6');
root.style.setProperty('--met-loading', '#6366f1');
};
/**
* 清除主题CSS变量
*/
export const clearThemeVariables = () => {
if (typeof document === 'undefined') return;
const root = document.documentElement;
const variables = [
'--met-bg',
'--met-text',
'--met-border',
'--met-shadow',
'--met-hover-shadow',
'--met-progress-bg',
'--met-close-hover-bg',
'--met-success',
'--met-error',
'--met-warning',
'--met-info',
'--met-loading',
];
variables.forEach((variable) => {
root.style.removeProperty(variable);
});
};
/**
* 获取当前主题
* @returns {string} 当前主题
*/
export const getCurrentTheme = () => {
return currentTheme;
};
/**
* 获取解析后的主题
* @returns {string} 解析后的主题
*/
export const getResolvedTheme = () => {
return resolveTheme(currentTheme);
};
/**
* 切换主题
* @param {string} theme - 新主题
*/
export const switchTheme = (theme) => {
applyTheme(theme);
saveTheme(theme);
};
/**
* 切换亮色/暗色主题
*/
export const toggleTheme = () => {
const resolved = getResolvedTheme();
const newTheme = resolved === 'dark' ? 'light' : 'dark';
switchTheme(newTheme);
};
/**
* 重置为自动主题
*/
export const resetToAuto = () => {
switchTheme('auto');
};
/**
* 保存主题到本地存储
* @param {string} theme - 主题名称
*/
export const saveTheme = (theme) => {
if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem('metona-toast-theme', theme);
} catch (e) {
console.warn('Failed to save theme:', e);
}
}
};
/**
* 从本地存储加载主题
* @returns {string} 主题名称
*/
export const loadTheme = () => {
if (typeof localStorage !== 'undefined') {
try {
return localStorage.getItem('metona-toast-theme') || 'auto';
} catch (e) {
console.warn('Failed to load theme:', e);
}
}
return 'auto';
};
/**
* 初始化主题系统
*/
export const initTheme = () => {
// 加载保存的主题
const savedTheme = loadTheme();
applyTheme(savedTheme);
// 监听系统主题变化
watchSystemTheme();
};
/**
* 监听系统主题变化
*/
export const watchSystemTheme = () => {
if (typeof window === 'undefined' || !window.matchMedia) return;
// 移除旧的监听器
if (systemThemeMediaQuery && systemThemeListener) {
systemThemeMediaQuery.removeEventListener('change', systemThemeListener);
}
// 创建新的监听器
systemThemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
systemThemeListener = (e) => {
if (currentTheme === 'auto') {
applyTheme('auto');
}
};
systemThemeMediaQuery.addEventListener('change', systemThemeListener);
};
/**
* 停止监听系统主题变化
*/
export const unwatchSystemTheme = () => {
if (systemThemeMediaQuery && systemThemeListener) {
systemThemeMediaQuery.removeEventListener('change', systemThemeListener);
systemThemeMediaQuery = null;
systemThemeListener = null;
}
};
/**
* 添加主题监听器
* @param {Function} listener - 监听器函数
* @returns {Function} 移除监听器函数
*/
export const addThemeListener = (listener) => {
themeListeners.add(listener);
return () => {
themeListeners.delete(listener);
};
};
/**
* 移除主题监听器
* @param {Function} listener - 监听器函数
*/
export const removeThemeListener = (listener) => {
themeListeners.delete(listener);
};
/**
* 通知主题监听器
* @param {string} theme - 主题名称
* @param {string} resolved - 解析后的主题
*/
const notifyThemeListeners = (theme, resolved) => {
themeListeners.forEach((listener) => {
try {
listener(theme, resolved);
} catch (e) {
console.error('Theme listener error:', e);
}
});
};
/**
* 清除所有主题监听器
*/
export const clearThemeListeners = () => {
themeListeners.clear();
};
/**
* 注册自定义主题
* @param {string} name - 主题名称
* @param {Object} config - 主题配置
*/
export const registerTheme = (name, config) => {
THEMES[name] = {
bg: config.bg || THEMES.light.bg,
text: config.text || THEMES.light.text,
border: config.border || THEMES.light.border,
shadow: config.shadow || THEMES.light.shadow,
hoverShadow: config.hoverShadow || THEMES.light.hoverShadow,
progressBg: config.progressBg || THEMES.light.progressBg,
closeHoverBg: config.closeHoverBg || THEMES.light.closeHoverBg,
};
};
/**
* 注销自定义主题
* @param {string} name - 主题名称
*/
export const unregisterTheme = (name) => {
if (name === 'light' || name === 'dark' || name === 'auto') {
console.warn('Cannot unregister built-in theme:', name);
return;
}
delete THEMES[name];
};
/**
* 获取所有主题
* @returns {Object} 主题映射
*/
export const getAllThemes = () => {
return { ...THEMES };
};
/**
* 获取主题名称列表
* @returns {Array} 主题名称数组
*/
export const getThemeNames = () => {
return Object.keys(THEMES);
};
/**
* 检查主题是否存在
* @param {string} name - 主题名称
* @returns {boolean} 是否存在
*/
export const hasTheme = (name) => {
return name in THEMES;
};
/**
* 获取主题预览
* @param {string} theme - 主题名称
* @returns {Object} 主题预览
*/
export const getThemePreview = (theme) => {
const config = getThemeConfig(theme);
const resolved = resolveTheme(theme);
return {
name: theme,
resolved,
colors: {
background: config.bg,
text: config.text,
border: config.border,
},
isDark: resolved === 'dark',
isLight: resolved === 'light',
isAuto: theme === 'auto',
};
};
/**
* 生成主题CSS
* @param {string} theme - 主题名称
* @returns {string} CSS字符串
*/
export const generateThemeCSS = (theme) => {
const config = getThemeConfig(theme);
const resolved = resolveTheme(theme);
return `
.met-theme-${resolved} {
--met-bg: ${config.bg};
--met-text: ${config.text};
--met-border: ${config.border};
--met-shadow: ${config.shadow};
--met-hover-shadow: ${config.hoverShadow};
--met-progress-bg: ${config.progressBg};
--met-close-hover-bg: ${config.closeHoverBg};
}
`;
};
/**
* 应用主题CSS
* @param {string} theme - 主题名称
*/
export const applyThemeCSS = (theme) => {
if (typeof document === 'undefined') return;
const css = generateThemeCSS(theme);
const styleId = 'metona-toast-theme-css';
// 移除旧样式
const oldStyle = document.getElementById(styleId);
if (oldStyle) {
oldStyle.remove();
}
// 添加新样式
const style = document.createElement('style');
style.id = styleId;
style.textContent = css;
document.head.appendChild(style);
};
/**
* 移除主题CSS
*/
export const removeThemeCSS = () => {
if (typeof document === 'undefined') return;
const styleId = 'metona-toast-theme-css';
const style = document.getElementById(styleId);
if (style) {
style.remove();
}
};
/**
* 主题工具
*/
export const themeUtils = {
/**
* 获取系统主题
* @returns {string} 系统主题
*/
getSystemTheme() {
return getSystemTheme();
},
/**
* 解析主题
* @param {string} theme - 主题名称
* @returns {string} 解析后的主题
*/
resolveTheme(theme) {
return resolveTheme(theme);
},
/**
* 获取主题配置
* @param {string} theme - 主题名称
* @returns {Object} 主题配置
*/
getThemeConfig(theme) {
return getThemeConfig(theme);
},
/**
* 应用主题
* @param {string} theme - 主题名称
*/
applyTheme(theme) {
applyTheme(theme);
},
/**
* 获取当前主题
* @returns {string} 当前主题
*/
getCurrentTheme() {
return getCurrentTheme();
},
/**
* 获取解析后的主题
* @returns {string} 解析后的主题
*/
getResolvedTheme() {
return getResolvedTheme();
},
/**
* 切换主题
* @param {string} theme - 新主题
*/
switchTheme(theme) {
switchTheme(theme);
},
/**
* 切换亮色/暗色主题
*/
toggleTheme() {
toggleTheme();
},
/**
* 重置为自动主题
*/
resetToAuto() {
resetToAuto();
},
/**
* 初始化主题系统
*/
initTheme() {
initTheme();
},
/**
* 监听系统主题变化
*/
watchSystemTheme() {
watchSystemTheme();
},
/**
* 停止监听系统主题变化
*/
unwatchSystemTheme() {
unwatchSystemTheme();
},
/**
* 添加主题监听器
* @param {Function} listener - 监听器函数
* @returns {Function} 移除监听器函数
*/
addThemeListener(listener) {
return addThemeListener(listener);
},
/**
* 移除主题监听器
* @param {Function} listener - 监听器函数
*/
removeThemeListener(listener) {
removeThemeListener(listener);
},
/**
* 清除所有主题监听器
*/
clearThemeListeners() {
clearThemeListeners();
},
/**
* 注册自定义主题
* @param {string} name - 主题名称
* @param {Object} config - 主题配置
*/
registerTheme(name, config) {
registerTheme(name, config);
},
/**
* 注销自定义主题
* @param {string} name - 主题名称
*/
unregisterTheme(name) {
unregisterTheme(name);
},
/**
* 获取所有主题
* @returns {Object} 主题映射
*/
getAllThemes() {
return getAllThemes();
},
/**
* 获取主题名称列表
* @returns {Array} 主题名称数组
*/
getThemeNames() {
return getThemeNames();
},
/**
* 检查主题是否存在
* @param {string} name - 主题名称
* @returns {boolean} 是否存在
*/
hasTheme(name) {
return hasTheme(name);
},
/**
* 获取主题预览
* @param {string} theme - 主题名称
* @returns {Object} 主题预览
*/
getThemePreview(theme) {
return getThemePreview(theme);
},
/**
* 生成主题CSS
* @param {string} theme - 主题名称
* @returns {string} CSS字符串
*/
generateThemeCSS(theme) {
return generateThemeCSS(theme);
},
/**
* 应用主题CSS
* @param {string} theme - 主题名称
*/
applyThemeCSS(theme) {
applyThemeCSS(theme);
},
/**
* 移除主题CSS
*/
removeThemeCSS() {
removeThemeCSS();
},
/**
* 保存主题
* @param {string} theme - 主题名称
*/
saveTheme(theme) {
saveTheme(theme);
},
/**
* 加载主题
* @returns {string} 主题名称
*/
loadTheme() {
return loadTheme();
},
};
/**
* 预设主题
*/
export const presetThemes = {
/**
* 浅色主题
*/
light: {
name: '浅色',
description: '明亮清晰的主题',
config: THEMES.light,
},
/**
* 深色主题
*/
dark: {
name: '深色',
description: '护眼舒适的暗色主题',
config: THEMES.dark,
},
/**
* 自动主题
*/
auto: {
name: '自动',
description: '跟随系统主题设置',
config: 'auto',
},
warm: {
name: '暖色',
description: '温馨舒适的暖色主题',
config: THEMES.warm,
},
};
// 注册预设主题
Object.entries(presetThemes).forEach(([name, theme]) => {
if (name !== 'auto' && theme.config !== 'auto') {
THEMES[name] = theme.config;
}
});
/**
* 创建主题管理器
* @returns {Object} 主题管理器
*/
export const createThemeManager = () => {
return {
getSystemTheme,
resolveTheme,
getThemeConfig,
applyTheme,
getCurrentTheme,
getResolvedTheme,
switchTheme,
toggleTheme,
resetToAuto,
initTheme,
watchSystemTheme,
unwatchSystemTheme,
addThemeListener,
removeThemeListener,
clearThemeListeners,
registerTheme,
unregisterTheme,
getAllThemes,
getThemeNames,
hasTheme,
getThemePreview,
generateThemeCSS,
applyThemeCSS,
removeThemeCSS,
saveTheme,
loadTheme,
};
};
export { themeUtils as default };
+1062
View File
File diff suppressed because it is too large Load Diff
+1933
View File
File diff suppressed because it is too large Load Diff
+1037
View File
File diff suppressed because it is too large Load Diff