Files
UI/packages/skin/ui/src/toast.ts
T
2026-10-23 16:53:44 +08:00

49 lines
2.6 KiB
TypeScript
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.
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' }), // 错误
};