Files
MetonaToast/site/docs.html
T
tianhao 95f5652e9c fix: CJS 产物改 .cjs 扩展名并提升 default 导出 — 修复 require() 兼容性
- 包声明 type:module 时 .js 产物被 Node 按 ESM 解析:Node 16/18 下 require 报 ERR_REQUIRE_ESM,Node 20+ 拿到 namespace 对象(success/version 等属性 undefined)
- dist/metona-toast.cjs.js → .cjs,dist/react.cjs.js → .cjs
- 主包 CJS footer 将 default 提升为 module.exports:require() 直接返回 MeToast(兼容 .default / .MeToast / .Met / .Toast / .VERSION 所有用法)
- 版本 0.5.1(0.5.0 已发布带问题产物)
- 真实消费者验证:CJS/ESM/React 子包四路加载全部正常
2026-08-08 15:25:47 +08:00

506 lines
35 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<title>API 文档 — MetonaToast</title>
<style>
:root {
--bg: #070b14; --surface: rgba(255,255,255,0.03); --border: rgba(255,255,255,0.08);
--text: #e8ecf4; --muted: #8b94a7; --accent: #a5b4fc; --accent2: #c7d2fe;
--grad: linear-gradient(135deg, #818cf8, #c084fc, #f472b6);
--radius: 12px;
}
* { margin:0; padding:0; box-sizing:border-box; }
html { scroll-behavior:smooth; }
body {
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;
background:var(--bg); color:var(--text); line-height:1.75; display:flex; min-height:100vh;
background-image:
radial-gradient(ellipse 70% 40% at 80% -10%, rgba(99,102,241,0.12), transparent),
radial-gradient(ellipse 50% 40% at 0% 20%, rgba(236,72,153,0.05), transparent);
}
code {
background:rgba(99,102,241,0.15); color:#a5b4fc; padding:1px 6px;
border-radius:5px; font-size:.88em; white-space:nowrap;
}
a { color:var(--accent); text-decoration:none; }
a:hover { color:var(--accent2); }
/* ===== 侧边栏 ===== */
.sidebar {
width:264px; min-width:264px; background:rgba(10,15,26,.8);
border-right:1px solid var(--border); padding:22px 0;
position:sticky; top:0; height:100vh; overflow-y:auto;
backdrop-filter:blur(14px);
}
.sidebar::-webkit-scrollbar { width:5px; }
.sidebar::-webkit-scrollbar-thumb { background:rgba(255,255,255,.12); border-radius:3px; }
.sidebar-logo { font-size:16px; font-weight:800; padding:0 24px; margin-bottom:4px; }
.grad-text { background:var(--grad); -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; color:transparent; }
.sidebar .back { display:block; font-size:12.5px; padding:6px 24px; color:var(--muted); }
.sidebar .back:hover { color:var(--accent2); }
.sidebar a.snav {
display:block; padding:6.5px 24px; font-size:13px; color:var(--muted);
transition:.15s; border-left:2px solid transparent;
}
.sidebar a.snav:hover { color:var(--accent2); }
.sidebar a.snav.active { color:var(--accent2); border-left-color:#a855f7; background:rgba(139,92,246,.08); }
.sidebar .section {
font-size:11px; text-transform:uppercase; letter-spacing:1.2px;
padding:18px 24px 6px; color:var(--muted); font-weight:700; opacity:.75;
}
/* ===== 主内容 ===== */
.main { flex:1; padding:44px 52px; max-width:980px; min-width:0; }
.main h1 { font-size:32px; font-weight:800; margin-bottom:6px; letter-spacing:-.5px; }
.main .h1-desc { color:var(--muted); font-size:14px; margin-bottom:8px; }
.main h2 {
font-size:22px; font-weight:700; margin:52px 0 12px; padding-top:22px;
border-top:1px solid var(--border); scroll-margin-top:24px;
}
.main h2::before { content:'#'; margin-right:8px; background:var(--grad); -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; }
.main h3 { font-size:16.5px; font-weight:700; margin:26px 0 10px; color:var(--accent2); }
.main p { margin:8px 0; color:var(--muted); font-size:14px; }
.main p code { font-size:.9em; }
.main table { width:100%; border-collapse:collapse; margin:14px 0 22px; font-size:13px; }
.main th, .main td { text-align:left; padding:9px 14px; border-bottom:1px solid var(--border); vertical-align:top; }
.main th { color:var(--muted); font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:.8px; }
.main td:first-child { color:var(--accent); font-family:monospace; font-size:12px; white-space:nowrap; }
.main td:nth-child(3) { font-family:monospace; font-size:11.5px; color:var(--muted); }
.badge {
display:inline-block; padding:1px 8px; border-radius:4px; font-size:10px;
font-weight:700; margin-left:6px; vertical-align:middle;
}
.badge-new { background:rgba(16,185,129,.18); color:#34d399; border:1px solid rgba(16,185,129,.3); }
.badge-v5 { background:rgba(168,85,247,.2); color:#d8b4fe; border:1px solid rgba(168,85,247,.4); }
.hint { font-size:12.5px; color:var(--muted); margin-top:2px; }
.return-box {
background:rgba(16,185,129,.06); border:1px solid rgba(16,185,129,.2);
border-radius:10px; padding:14px 18px; margin:14px 0;
}
.return-box h4 { font-size:11.5px; color:#34d399; margin-bottom:8px; text-transform:uppercase; letter-spacing:.8px; }
.terminal {
background:#0a0f1c; border:1px solid var(--border); border-radius:10px;
overflow:hidden; margin:12px 0 20px;
}
.terminal-bar { display:flex; align-items:center; gap:7px; padding:9px 14px; background:rgba(255,255,255,0.03); border-bottom:1px solid var(--border); }
.terminal-bar .t-dot { width:10px; height:10px; border-radius:50%; }
.terminal-bar .t-dot:nth-child(1){ background:#ff5f57; } .terminal-bar .t-dot:nth-child(2){ background:#febc2e; } .terminal-bar .t-dot:nth-child(3){ background:#28c840; }
.terminal-bar .t-title { margin-left:10px; font-size:11px; color:var(--muted); }
.terminal pre { padding:16px 18px; overflow-x:auto; font:12.5px/1.75 'SF Mono','Cascadia Code','Fira Code',Consolas,monospace; color:#cbd5e1; }
.terminal pre .kw { color:#c084fc; } .terminal pre .fn { color:#60a5fa; } .terminal pre .s { color:#34d399; } .terminal pre .cm { color:#475569; }
@media (max-width:860px) {
body { flex-direction:column; }
.sidebar { width:100%; min-width:0; height:auto; position:static; border-right:none; border-bottom:1px solid var(--border); padding:12px 16px; }
.sidebar .section, .sidebar .back { padding:4px 8px; }
.sidebar a.snav { display:inline-block; padding:4px 10px; border-left:none; font-size:12.5px; }
.sidebar a.snav.active { border-left:none; border-radius:6px; }
.main { padding:26px 20px; }
}
</style>
</head>
<body>
<nav class="sidebar">
<div class="sidebar-logo"><span class="grad-text">MetonaToast 文档</span></div>
<a class="back" href="index.html">← 返回首页</a>
<a class="back" href="demo.html">🎮 在线演示</a>
<div class="section">基础通知</div>
<a class="snav" href="#show">show()</a>
<a class="snav" href="#success">success()</a>
<a class="snav" href="#error">error()</a>
<a class="snav" href="#warning">warning()</a>
<a class="snav" href="#info">info()</a>
<a class="snav" href="#loading">loading()</a>
<div class="section">高级功能</div>
<a class="snav" href="#promise">promise()</a>
<a class="snav" href="#confirm">confirm()</a>
<a class="snav" href="#prompt">prompt()</a>
<a class="snav" href="#progress">progress()</a>
<a class="snav" href="#countdown">countdown()</a>
<a class="snav" href="#queue">queue()</a>
<a class="snav" href="#stack">stack()</a>
<a class="snav" href="#action">action()</a>
<div class="section">管理方法</div>
<a class="snav" href="#group">group()</a>
<a class="snav" href="#dismiss">dismiss()</a>
<a class="snav" href="#clear">clear()</a>
<a class="snav" href="#updatePosition">updatePosition()</a>
<a class="snav" href="#remove">remove() / removeToast()</a>
<a class="snav" href="#configure">configure()</a>
<a class="snav" href="#use">use()</a>
<a class="snav" href="#destroy">destroy()</a>
<div class="section">扩展</div>
<a class="snav" href="#hooks">钩子系统</a>
<a class="snav" href="#react">React 适配器</a>
<a class="snav" href="#config">全部配置项</a>
<a class="snav" href="#callbacks">回调函数</a>
<a class="snav" href="#animations">动画列表</a>
<a class="snav" href="#themes">主题参考</a>
</nav>
<main class="main">
<h1>API 文档</h1>
<p class="h1-desc">MetonaToast v0.5.1 完整 API 参考。所有基础通知方法支持字符串与对象两种调用形式,均可传入任何<a href="#config">配置项</a>作为可选参数。</p>
<!-- ===== 基础通知 ===== -->
<h2 id="show">show(message, opts?)</h2>
<p>显示默认类型 Toast,无特定颜色与图标。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">show.js</span></div>
<pre><span class="cm">// 字符串形式</span>
MeToast.<span class="fn">show</span>(<span class="s">'默认消息'</span>);
MeToast.<span class="fn">show</span>(<span class="s">'自定义'</span>, { <span class="fn">duration</span>: <span class="num">2000</span>, <span class="fn">position</span>: <span class="s">'bottom-center'</span> });
<span class="cm">// 对象形式</span>
MeToast.<span class="fn">show</span>({ <span class="fn">title</span>: <span class="s">'标题'</span>, <span class="fn">message</span>: <span class="s">'内容'</span>, <span class="fn">duration</span>: <span class="num">3000</span> });</pre></div>
<h2 id="success">success(message, opts?)</h2>
<p>绿色对勾图标 <code>#10b981</code>type = success。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">success.js</span></div>
<pre>MeToast.<span class="fn">success</span>(<span class="s">'保存成功!'</span>);
MeToast.<span class="fn">success</span>({ <span class="fn">title</span>: <span class="s">'已保存'</span>, <span class="fn">message</span>: <span class="s">'数据已同步'</span> });
Met.<span class="fn">success</span>(<span class="s">'Met 与 MeToast 等价'</span>);</pre></div>
<h2 id="error">error(message, opts?)</h2>
<p>红色叉号图标 <code>#ef4444</code>type = error。aria-live = assertive。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">error.js</span></div>
<pre>MeToast.<span class="fn">error</span>(<span class="s">'网络错误,请重试'</span>);
MeToast.<span class="fn">error</span>({ <span class="fn">title</span>: <span class="s">'提交失败'</span>, <span class="fn">message</span>: <span class="s">'服务器不可达'</span> });</pre></div>
<h2 id="warning">warning(message, opts?)</h2>
<p>黄色三角图标 <code>#f59e0b</code>type = warning。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">warning.js</span></div>
<pre>MeToast.<span class="fn">warning</span>(<span class="s">'请注意检查输入内容'</span>);</pre></div>
<h2 id="info">info(message, opts?)</h2>
<p>蓝色圆形图标 <code>#3b82f6</code>type = info。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">info.js</span></div>
<pre>MeToast.<span class="fn">info</span>(<span class="s">'系统将于 22:00 维护'</span>);</pre></div>
<h2 id="loading">loading(message, opts?)</h2>
<p>type = loadingduration 强制 0(不自动关闭)。返回 LoadingControl。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">loading.js</span></div>
<pre><span class="kw">const</span> loading = MeToast.<span class="fn">loading</span>(<span class="s">'正在提交...'</span>);
<span class="fn">setTimeout</span>(() => loading.<span class="fn">success</span>(<span class="s">'提交成功!'</span>), <span class="num">2000</span>);</pre></div>
<div class="return-box">
<h4>LoadingControl(链式转换 id 稳定)</h4>
<table>
<tr><th>方法</th><th>说明</th></tr>
<tr><td>success/error/info/warning</td><td>原地更新为对应类型(同一实例,id 不变),duration 自动恢复默认值</td></tr>
<tr><td>update(partial)</td><td>原地更新内容,不改变类型</td></tr>
<tr><td>dismiss()</td><td>直接关闭,不转换</td></tr>
</table>
</div>
<!-- ===== 高级功能 ===== -->
<h2 id="promise">promise(promise, opts)</h2>
<p>监听 Promise 生命周期,自动 loading → resolve/reject 切换 success/error,返回原 Promise。</p>
<table>
<tr><th>参数</th><th>类型</th><th>默认</th><th>说明</th></tr>
<tr><td>promise</td><td>Promise</td><td></td><td>要监听的 Promise。非 Promise 打印错误并返回 rejected Promise</td></tr>
<tr><td>opts.loading / success / error</td><td>string</td><td>加载中... / 操作成功 / 操作失败</td><td>各阶段文本</td></tr>
</table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">promise.js</span></div>
<pre><span class="kw">try</span> {
<span class="kw">await</span> MeToast.<span class="fn">promise</span>(<span class="fn">fetch</span>(<span class="s">'/api/data'</span>), {
<span class="fn">loading</span>: <span class="s">'加载中...'</span>, <span class="fn">success</span>: <span class="s">'完成!'</span>, <span class="fn">error</span>: <span class="s">'失败'</span>,
});
} <span class="kw">catch</span> (e) { <span class="cm">/* reject 会继续抛出 */</span> }</pre></div>
<h2 id="confirm">confirm(message, opts?)</h2>
<p>确认对话框,返回 <code>Promise&lt;boolean&gt;</code>,内置 10 秒安全超时自动 resolve(false)。</p>
<table>
<tr><th>参数</th><th>默认</th><th>说明</th></tr>
<tr><td>confirmText / confirmColor</td><td>确认 / #10b981</td><td>确认按钮文字与颜色</td></tr>
<tr><td>cancelText / cancelColor</td><td>取消 / #6b7280</td><td>取消按钮文字与颜色</td></tr>
<tr><td>type</td><td>warning</td><td>Toast 类型</td></tr>
</table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">confirm.js</span></div>
<pre><span class="kw">const</span> ok = <span class="kw">await</span> MeToast.<span class="fn">confirm</span>(<span class="s">'确定删除?'</span>, { <span class="fn">confirmText</span>: <span class="s">'删除'</span>, <span class="fn">confirmColor</span>: <span class="s">'#ef4444'</span> });
<span class="kw">if</span> (ok) MeToast.<span class="fn">success</span>(<span class="s">'已删除'</span>);</pre></div>
<h2 id="prompt">prompt(message, opts?)</h2>
<p>输入对话框,返回 <code>Promise&lt;string|null&gt;</code>。Enter / 提交返回输入值,取消返回 null,10 秒超时。</p>
<table>
<tr><th>参数</th><th>默认</th><th>说明</th></tr>
<tr><td>placeholder / defaultValue</td><td>""</td><td>输入框占位与默认值</td></tr>
<tr><td>inputType</td><td>text</td><td>input 的 typepassword/email/number 等)</td></tr>
<tr><td>submitText / submitColor</td><td>确认 / #3b82f6</td><td>提交按钮</td></tr>
<tr><td>cancelText / cancelColor</td><td>取消 / #6b7280</td><td>取消按钮</td></tr>
</table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">prompt.js</span></div>
<pre><span class="kw">const</span> name = <span class="kw">await</span> MeToast.<span class="fn">prompt</span>(<span class="s">'请输入姓名'</span>, { <span class="fn">placeholder</span>: <span class="s">'请输入...'</span> });
<span class="kw">if</span> (name) MeToast.<span class="fn">info</span>(<span class="s">'你好,'</span> + name);</pre></div>
<h2 id="progress">progress(message, opts?)</h2>
<p>进度条通知,不自动关闭。返回 ProgressControl。</p>
<table>
<tr><th>方法</th><th>说明</th></tr>
<tr><td>setProgress(percent)</td><td>设置进度 0~100,自动 clamp</td></tr>
<tr><td>complete(message?)</td><td>跳 100%300ms 后转 success1s 后自动关闭</td></tr>
<tr><td>error(message?)</td><td>转 error2s 后自动关闭</td></tr>
<tr><td>dismiss()</td><td>直接关闭</td></tr>
</table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">progress.js</span></div>
<pre><span class="kw">const</span> p = MeToast.<span class="fn">progress</span>(<span class="s">'上传中...'</span>, { <span class="fn">progressColor</span>: <span class="s">'#10b981'</span> });
p.<span class="fn">setProgress</span>(<span class="num">45</span>);
p.<span class="fn">complete</span>(<span class="s">'上传完成!'</span>);</pre></div>
<h2 id="countdown">countdown(message, seconds, opts?)</h2>
<p>倒计时 Toast<code>{seconds}</code> 占位符每秒自动替换。</p>
<table>
<tr><th>参数</th><th>默认</th><th>说明</th></tr>
<tr><td>seconds</td><td>10</td><td>倒计时秒数,最小 1</td></tr>
<tr><td>opts.onComplete</td><td></td><td>归零回调</td></tr>
</table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">countdown.js</span></div>
<pre>MeToast.<span class="fn">countdown</span>(<span class="s">'{seconds} 秒后执行'</span>, <span class="num">5</span>, { <span class="fn">onComplete</span>: () => MeToast.<span class="fn">success</span>(<span class="s">'已执行'</span>) });
<span class="cm">// 返回 { cancel(), pause(), resume() }</span></pre></div>
<h2 id="queue">queue(messages, opts?)</h2>
<p>顺序逐个显示消息队列,返回 thenable + cancel。支持 await。</p>
<table>
<tr><th>参数</th><th>默认</th><th>说明</th></tr>
<tr><td>opts.delay</td><td>1000</td><td>每条关闭后到下一条的间隔(ms)</td></tr>
<tr><td>opts.duration</td><td>3000</td><td>每条显示时长,可被消息级 duration 覆盖</td></tr>
</table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">queue.js</span></div>
<pre><span class="kw">const</span> q = MeToast.<span class="fn">queue</span>([<span class="s">'步骤一'</span>, <span class="s">'步骤二'</span>, <span class="s">'步骤三'</span>], { <span class="fn">delay</span>: <span class="num">800</span>, <span class="fn">duration</span>: <span class="num">2000</span> });
q.<span class="fn">cancel</span>(); <span class="cm">// 中途取消</span>
<span class="kw">await</span> q; <span class="cm">// 等待完成</span></pre></div>
<h2 id="stack">stack(messages, opts?)</h2>
<p>同时错峰显示多条消息。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">stack.js</span></div>
<pre>MeToast.<span class="fn">stack</span>([<span class="s">'消息1'</span>, <span class="s">'消息2'</span>, { <span class="fn">message</span>: <span class="s">'警告'</span>, <span class="fn">type</span>: <span class="s">'warning'</span> }], { <span class="fn">stagger</span>: <span class="num">150</span> });</pre></div>
<h2 id="action">action(message, actions, opts?)</h2>
<p>内嵌操作按钮。默认 duration=0 不自动关闭。点击按钮不会误触 closeOnClick(已隔离冒泡)。</p>
<table>
<tr><th>ActionButton</th><th>默认</th><th>说明</th></tr>
<tr><td>text</td><td>—(必填)</td><td>按钮文字</td></tr>
<tr><td>onClick</td><td>—(必填)</td><td>点击回调,参数为当前 toast</td></tr>
<tr><td>color / style</td><td>#6366f1</td><td>背景色 / 内联样式(style.background 优先)</td></tr>
<tr><td>close</td><td>true</td><td>点击后是否关闭。false 可多次点击</td></tr>
</table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">action.js</span></div>
<pre>MeToast.<span class="fn">action</span>(<span class="s">'文件已删除'</span>, [
{ <span class="fn">text</span>: <span class="s">'撤销'</span>, <span class="fn">onClick</span>: () => <span class="fn">restore</span>(), <span class="fn">color</span>: <span class="s">'#3b82f6'</span> },
{ <span class="fn">text</span>: <span class="s">'查看'</span>, <span class="fn">onClick</span>: () => <span class="fn">open</span>(), <span class="fn">color</span>: <span class="s">'#10b981'</span>, <span class="fn">close</span>: <span class="kw">false</span> },
]);</pre></div>
<!-- ===== 管理方法 ===== -->
<h2 id="group">group(name)</h2>
<p>创建分组,返回 GroupAPI。所有方法自动注入 <code>group: name</code></p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">group.js</span></div>
<pre><span class="kw">const</span> orders = MeToast.<span class="fn">group</span>(<span class="s">'orders'</span>);
orders.<span class="fn">success</span>(<span class="s">'订单已创建'</span>);
orders.<span class="fn">count</span>(); <span class="cm">// 该组数量</span>
orders.<span class="fn">dismiss</span>(); <span class="cm">// 关闭整组</span>
MeToast.<span class="fn">dismissGroup</span>(<span class="s">'orders'</span>);</pre></div>
<h2 id="dismiss">dismiss(id?)</h2>
<p>关闭 Toast。无参数关闭全部,传入 id 关闭指定。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">dismiss.js</span></div>
<pre>MeToast.<span class="fn">dismiss</span>(); <span class="cm">// 全部</span>
MeToast.<span class="fn">dismiss</span>(toast.id); <span class="cm">// 指定</span></pre></div>
<h2 id="clear">clear(position?)</h2>
<p>按位置清除。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">clear.js</span></div>
<pre>MeToast.<span class="fn">clear</span>(); <span class="cm">// 全部</span>
MeToast.<span class="fn">clear</span>(<span class="s">'bottom-right'</span>); <span class="cm">// 仅右下角</span></pre></div>
<h2 id="updatePosition">updatePosition(position) <span class="badge badge-v5">v0.3</span></h2>
<p>运行时将 Toast 移动到新位置容器,立即生效。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">position.js</span></div>
<pre><span class="kw">const</span> t = MeToast.<span class="fn">info</span>(<span class="s">'可移动的 Toast'</span>);
t.<span class="fn">updatePosition</span>(<span class="s">'bottom-left'</span>);</pre></div>
<h2 id="remove">remove() / removeToast(id) <span class="badge badge-new">立即移除</span></h2>
<p>立即从 DOM 和内存移除,<b>不触发离场动画</b>。适用于无动画快速清除。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">remove.js</span></div>
<pre>t.<span class="fn">remove</span>(); <span class="cm">// 实例方法</span>
MeToast.<span class="fn">removeToast</span>(t.id); <span class="cm">// 按 id(等价)</span></pre></div>
<h2 id="configure">configure(opts)</h2>
<p>全局配置,影响后续所有 Toast。theme/locale 变化触发副作用。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">configure.js</span></div>
<pre>MeToast.<span class="fn">configure</span>({
<span class="fn">position</span>: <span class="s">'top-right'</span>, <span class="fn">duration</span>: <span class="num">4000</span>, <span class="fn">theme</span>: <span class="s">'dark'</span>,
<span class="fn">animation</span>: <span class="s">'slide'</span>, <span class="fn">locale</span>: <span class="s">'zh-CN'</span>,
});</pre></div>
<h2 id="use">use(plugin)</h2>
<p>安装插件:字符串预设名或插件对象。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">use.js</span></div>
<pre>MeToast.<span class="fn">use</span>(<span class="s">'keyboard'</span>); <span class="cm">// ESC 关闭所有</span>
MeToast.<span class="fn">use</span>(<span class="s">'persistence'</span>); <span class="cm">// 配置持久化 localStorage</span>
MeToast.<span class="fn">use</span>(<span class="s">'accessibility'</span>); <span class="cm">// 屏幕阅读器朗读</span>
MeToast.<span class="fn">use</span>(<span class="s">'dedupe'</span>); <span class="cm">// 相同 type+message 自动去重</span></pre></div>
<p class="hint">dedupe:检测已有相同 toast,更新它并阻止重复弹出(beforeShow 钩子实现,支持 uninstall 卸载钩子)。</p>
<h2 id="destroy">destroy()</h2>
<p>完全销毁:关闭所有 Toast、移除全部 DOM 容器与注入样式、清空内存缓存与钩子。支持重复调用,<code>init()</code> 可恢复。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">destroy.js</span></div>
<pre>MeToast.<span class="fn">destroy</span>();
MeToast.<span class="fn">init</span>({ <span class="fn">config</span>: { <span class="fn">duration</span>: <span class="num">3000</span> } }); <span class="cm">// 恢复</span></pre></div>
<!-- ===== 钩子系统 ===== -->
<h2 id="hooks">钩子系统 <span class="badge badge-v5">v0.3</span></h2>
<p><code>Toast.on(name, handler)</code> 注册钩子并返回取消函数。<code>beforeShow</code> / <code>beforeClose</code> / <code>beforeUpdate</code> 的 handler 返回 <code>false</code> 可拦截对应操作。</p>
<table>
<tr><th>钩子</th><th>触发时机</th><th>拦截</th></tr>
<tr><td>beforeInit / afterInit</td><td>init() 前后</td><td></td></tr>
<tr><td>beforeDestroy / afterDestroy</td><td>destroy() 前后</td><td></td></tr>
<tr><td>beforeShow / afterShow</td><td>显示前后</td><td>✅ 返回 false 阻止</td></tr>
<tr><td>beforeClose / afterClose</td><td>关闭前后</td><td>✅ 返回 false 阻止</td></tr>
<tr><td>beforeUpdate / afterUpdate</td><td>update() 前后</td><td>✅ 返回 false 阻止</td></tr>
<tr><td>configChange</td><td>configure / updateConfig / resetConfig</td><td></td></tr>
<tr><td>themeChange / localeChange</td><td>主题 / 语言切换</td><td></td></tr>
<tr><td>click / hover</td><td>点击 / 悬停进出</td><td></td></tr>
<tr><td>dragStart / dragEnd</td><td>拖拽开始 / 结束</td><td></td></tr>
<tr><td>animationStart / animationEnd</td><td>入场动画开始 / 结束</td><td></td></tr>
<tr><td>progressStart / progressEnd</td><td>倒计时开始 / 归零</td><td></td></tr>
</table>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">hooks.js</span></div>
<pre><span class="kw">import</span> MeToast, { <span class="fn">Toast</span> } <span class="kw">from</span> <span class="s">'@metona-team/metona-toast'</span>;
<span class="cm">// 拦截:非允许时段禁止错误提示</span>
<span class="kw">const</span> off = <span class="fn">Toast</span>.<span class="fn">on</span>(<span class="s">'beforeShow'</span>, (toast) =&gt; {
<span class="kw">if</span> (toast.type === <span class="s">'error'</span> &amp;&amp; !isAllowed) <span class="kw">return</span> <span class="kw">false</span>;
});
<span class="cm">// ...</span>
off(); <span class="cm">// 取消注册</span></pre></div>
<!-- ===== React ===== -->
<h2 id="react">React 适配器 <span class="badge badge-v5">v0.4</span></h2>
<p>主包保持零依赖。通过子路径 <code>@metona-team/metona-toast/react</code> 导入,<code>react</code> 为 optional peerDependency。</p>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">react.tsx</span></div>
<pre><span class="kw">import</span> { <span class="fn">useToast</span>, <span class="fn">Toast</span> } <span class="kw">from</span> <span class="s">'@metona-team/metona-toast/react'</span>;
<span class="kw">function</span> <span class="fn">SubmitButton</span>() {
<span class="kw">const</span> toast = <span class="fn">useToast</span>(); <span class="cm">// 组件卸载自动清理本组件创建的 Toast</span>
<span class="kw">const</span> submit = <span class="kw">async</span> () =&gt; {
<span class="kw">const</span> loading = toast.<span class="fn">loading</span>(<span class="s">'正在提交...'</span>);
<span class="kw">try</span> { <span class="kw">await</span> api(); loading.<span class="fn">success</span>(<span class="s">'完成!'</span>); }
<span class="kw">catch</span> (e) { loading.<span class="fn">error</span>(<span class="s">'失败'</span>); }
};
<span class="kw">return</span> &lt;button onClick={submit}&gt;提交&lt;/button&gt;;
}
<span class="cm">// 声明式 Toast:props 变化更新,卸载自动移除(autoClose 默认 true</span>
<span class="kw">function</span> <span class="fn">SaveIndicator</span>({ saving }) {
<span class="kw">return</span> saving ? &lt;<span class="fn">Toast</span> type=<span class="s">"info"</span> message=<span class="s">"正在保存..."</span> /&gt; : null;
}</pre></div>
<!-- ===== 配置参考 ===== -->
<h2 id="config">全部配置项</h2>
<p>可用于 <code>configure()</code><code>init()</code> 或单个 Toast 方法的 opts。</p>
<table>
<tr><th>配置项</th><th>类型</th><th>默认值</th><th>说明</th></tr>
<tr><td>position</td><td>string</td><td>'top-right'</td><td>6 个位置之一(RTL 自动翻转)</td></tr>
<tr><td>duration</td><td>number</td><td>4000</td><td>显示时长(ms)0 = 不自动关闭</td></tr>
<tr><td>max</td><td>number</td><td>6</td><td>同位置最多条数,超出真正关闭最早的</td></tr>
<tr><td>gap</td><td>number</td><td>12</td><td>Toast 间距(px)</td></tr>
<tr><td>offset</td><td>number</td><td>24</td><td>容器距屏幕边缘(px)</td></tr>
<tr><td>pauseOnHover</td><td>boolean</td><td>true</td><td>悬停暂停倒计时</td></tr>
<tr><td>closeOnClick</td><td>boolean</td><td>true</td><td>点击关闭</td></tr>
<tr><td>draggable</td><td>boolean</td><td>true</td><td>允许拖拽关闭</td></tr>
<tr><td>dragThreshold</td><td>number</td><td>120</td><td>拖拽关闭阈值(px)</td></tr>
<tr><td>showProgress</td><td>boolean</td><td>true</td><td>显示倒计时进度条</td></tr>
<tr><td>progressDirection</td><td>string</td><td>'horizontal'</td><td>horizontal / vertical</td></tr>
<tr><td>icon</td><td>boolean</td><td>true</td><td>显示类型图标</td></tr>
<tr><td>closeButton</td><td>boolean</td><td>true</td><td>显示关闭 × 按钮</td></tr>
<tr><td>theme</td><td>string</td><td>'auto'</td><td>light / dark / auto / warm / 自定义名</td></tr>
<tr><td>animation</td><td>string</td><td>'slide'</td><td>11 种内置或自定义动画名</td></tr>
<tr><td>zIndex</td><td>number</td><td>9999</td><td>容器 z-index</td></tr>
<tr><td>width</td><td>number|string</td><td>360</td><td>宽度,数字表示 px</td></tr>
<tr><td>className</td><td>string</td><td>''</td><td>附加 CSS 类名</td></tr>
<tr><td>style</td><td>object</td><td>{}</td><td>附加内联样式</td></tr>
<tr><td>locale</td><td>string</td><td>'zh-CN'</td><td>语言代码</td></tr>
<tr><td>resetTimerOnUpdate</td><td>boolean</td><td>false</td><td>update() 时重置倒计时</td></tr>
<tr><td>notifyWhenHidden</td><td>boolean</td><td>false</td><td>页面不可见时发系统通知</td></tr>
<tr><td>render</td><td>function</td><td></td><td>自定义渲染函数,完全接管 DOM</td></tr>
<tr><td>onBeforeShow</td><td>function</td><td></td><td>返回 false 阻止显示</td></tr>
<tr><td>onError</td><td>function</td><td></td><td>钩子/定时器异常全局回调</td></tr>
</table>
<h2 id="callbacks">回调函数</h2>
<table>
<tr><th>回调</th><th>签名</th><th>触发时机</th></tr>
<tr><td>onBeforeShow</td><td>(toast) =&gt; boolean | void</td><td>DOM 创建前,返回 false 阻止显示</td></tr>
<tr><td>onShow</td><td>(toast) =&gt; void</td><td>创建并播放入场动画后</td></tr>
<tr><td>onClose</td><td>(toast) =&gt; void</td><td>DOM 移除后(离场完成)</td></tr>
<tr><td>onClick</td><td>(toast) =&gt; void</td><td>点击时(closeOnClick=true 时还会关闭)</td></tr>
<tr><td>onUpdate</td><td>(toast) =&gt; void</td><td>update() 后</td></tr>
<tr><td>onError</td><td>({ hook, source, error, toast }) =&gt; void</td><td>钩子/定时器异常时</td></tr>
</table>
<h2 id="animations">动画列表</h2>
<p>未注册的动画名自动 fallback 到 slide。</p>
<table>
<tr><th>名称</th><th>效果</th><th>时长</th></tr>
<tr><td>slide</td><td>从右侧滑入 + 回弹</td><td>400ms</td></tr>
<tr><td>fade</td><td>纯淡入 + blur→清晰</td><td>500ms</td></tr>
<tr><td>scale</td><td>弹性放大 (0.55→1.07→1)</td><td>450ms</td></tr>
<tr><td>bounce</td><td>从天而降四段弹跳</td><td>650ms</td></tr>
<tr><td>flip</td><td>3D 翻转入场 + 回摆</td><td>500ms</td></tr>
<tr><td>rotate</td><td>旋转摇摆进入</td><td>500ms</td></tr>
<tr><td>zoom</td><td>从中心爆发式弹出</td><td>500ms</td></tr>
<tr><td>slideUp / slideDown</td><td>从下方 / 上方弹入</td><td>400ms</td></tr>
<tr><td>slideLeft / slideRight</td><td>从左侧 / 右侧滑入</td><td>400ms</td></tr>
</table>
<h2 id="themes">主题参考</h2>
<div class="terminal"><div class="terminal-bar"><span class="t-dot"></span><span class="t-dot"></span><span class="t-dot"></span><span class="t-title">theme.js</span></div>
<pre><span class="cm">// 内置主题:light · dark · auto(跟随系统)· warm</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>,
});
MeToast.themes.<span class="fn">switchTheme</span>(<span class="s">'ocean'</span>);</pre></div>
</main>
<script src="../dist/metona-toast.js"></script>
<script>
MeToast.configure({ position: 'top-right', duration: 4000, theme: 'dark', animation: 'slide' });
// 侧边栏滚动高亮
const links = document.querySelectorAll('.sidebar a.snav');
const sections = [];
links.forEach(l => {
const id = l.getAttribute('href').slice(1);
const el = document.getElementById(id);
if (el) sections.push({ id, el, link: l });
});
const spy = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
links.forEach(l => l.classList.remove('active'));
const hit = sections.find(s => s.el === e.target);
if (hit) hit.link.classList.add('active');
}
});
}, { rootMargin: '-15% 0px -70% 0px' });
sections.forEach(s => spy.observe(s.el));
// 点击关闭高亮
links.forEach(l => l.addEventListener('click', () => {
links.forEach(x => x.classList.remove('active'));
l.classList.add('active');
}));
</script>
</body>
</html>