49 lines
2.6 KiB
TypeScript
49 lines
2.6 KiB
TypeScript
import { h } from '@ui-arch/frame';
|
||
import type { ToastProps } from './types.js';
|
||
|
||
let host: HTMLElement | null = null; // 全局右上角堆叠容器:首条消息出现时懒创建
|
||
let seq = 0; // 自增序号:为每条 toast 生成唯一 id,辅助无障碍定位
|
||
const MAX_STACK = 4; // 同屏最多条数:超出即挤掉最早一条,避免刷屏淹页面
|
||
|
||
/*** 确保堆叠容器存在:fixed 挂在 body 末尾,后续消息依次向上堆叠 ***/
|
||
function ensureHost(): HTMLElement {
|
||
if (host) return host;
|
||
host = h('div', { class: 'ua-toast-host' });
|
||
document.body.appendChild(host);
|
||
return host;
|
||
}
|
||
|
||
/*** 退场并移除:先摘 ua-open 触发过渡,transitionend 与 320ms 超时兜底二选一 ***/
|
||
function dismiss(item: HTMLElement): void {
|
||
item.classList.remove('ua-open');
|
||
item.addEventListener('transitionend', () => item.remove(), { once: true });
|
||
window.setTimeout(() => item.isConnected && item.remove(), 320); // reduced-motion 下 transition 不触发
|
||
}
|
||
|
||
/*** 弹出一条 toast:语义色由 variant 决定,duration 毫秒后自动淡出并移除节点 ***/
|
||
export function showToast(props: ToastProps): HTMLElement {
|
||
// —— 状态集中声明 ——
|
||
const duration = props.duration ?? 3000; // 停留时长毫秒
|
||
const item = h(
|
||
'div',
|
||
{ class: `ua-toast ua-toast-${props.variant ?? 'info'}${props.closable ? ' is-closable' : ''}`, role: 'status', id: `ua-toast-${++seq}` },
|
||
h('span', { class: 'ua-toast-msg' }, props.message),
|
||
props.closable ? h('button', { type: 'button', class: 'ua-toast-close', 'aria-label': '关闭提示', onClick: () => dismiss(item) }, '×') : null,
|
||
);
|
||
const stack = ensureHost();
|
||
if (stack.childElementCount >= MAX_STACK) stack.firstElementChild?.remove(); // 超过上限直接挤掉最早一条
|
||
|
||
stack.appendChild(item);
|
||
requestAnimationFrame(() => item.classList.add('ua-open')); // 下一帧入场,保证过渡生效
|
||
window.setTimeout(() => dismiss(item), duration);
|
||
return item;
|
||
}
|
||
|
||
/*** toast 语义快捷工厂:toast.info / success / warning / error(message, duration?) ***/
|
||
export const toast = {
|
||
info: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'info' }), // 中性信息
|
||
success: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'success' }), // 成功反馈
|
||
warning: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'warning' }), // 警告
|
||
error: (message: string, duration?: number): HTMLElement => showToast({ message, duration, variant: 'danger' }), // 错误
|
||
};
|