iter(g300): 修正 toast 悬停暂停从未生效

This commit is contained in:
HIsheR
2026-10-27 00:25:55 +08:00
parent a8ff91c0a8
commit c013998f88
7 changed files with 51 additions and 20 deletions
+16 -2
View File
@@ -4,6 +4,7 @@ import type { ToastProps } from './types.js';
let host: HTMLElement | null = null; // 全局右上角堆叠容器:首条消息出现时懒创建
let seq = 0; // 自增序号:为每条 toast 生成唯一 id,辅助无障碍定位
const MAX_STACK = 4; // 同屏最多条数:超出即挤掉最早一条,避免刷屏淹页面
const PAUSE_FLOOR = 400; // 恢复时的最短剩余毫秒:悬停前已近归零也不至于松手即消失
/*** 确保堆叠容器存在:fixed 挂在 body 末尾,后续消息依次向上堆叠 ***/
function ensureHost(): HTMLElement {
@@ -43,10 +44,23 @@ export function showToast(props: ToastProps): HTMLElement {
stack.appendChild(item);
requestAnimationFrame(() => item.classList.add('ua-open')); // 下一帧入场,保证过渡生效
const arm = (ms: number): void => { startedAt = Date.now(); timer = window.setTimeout(() => dismiss(item), ms); }; // 重新武装倒计时
/*** 暂停倒计时:扣掉本段已走时长,清空句柄(重复进入不会二次扣减) ***/
const pause = (): void => {
if (!timer) return; // 已暂停 / 常驻(duration 为 0)时无计时器可停
window.clearTimeout(timer);
timer = 0;
remain = Math.max(PAUSE_FLOOR, remain - (Date.now() - startedAt));
};
/*** 恢复倒计时:按剩余时长重新武装 ***/
const resume = (): void => { if (!timer && remain > 0) arm(remain); };
if (duration > 0) {
arm(duration);
item.addEventListener('pointerenter', () => { window.clearTimeout(timer); remain -= Date.now() - startedAt; }); // 悬停暂停:记下剩余时长
item.addEventListener('pointerleave', () => arm(remain)); // 移出后按剩余时长继续
item.addEventListener('pointerenter', pause); // 悬停停留阅读:倒计时暂停
item.addEventListener('pointerleave', resume); // 移出后按剩余时长继续
item.addEventListener('focusin', pause); // 键盘 Tab 进关闭 / 操作按钮时同样暂停
item.addEventListener('focusout', (e) => {
if (!item.contains(e.relatedTarget as Node | null)) resume(); // 焦点还在卡片内部(按钮间切换)时不恢复
});
}
return item;
}
+2 -1
View File
@@ -1128,7 +1128,7 @@
{
"n": "closable",
"t": "boolean",
"d": "显示关闭按钮,点 × 立即收起;缺省 false(仅可关闭的 toast 接收指针事件,其余保持穿透)",
"d": "显示关闭按钮,点 × 立即收起;缺省 false(任意 toast 都接收指针,悬停或聚焦即暂停倒计时)",
"df": "false"
},
{
@@ -1153,6 +1153,7 @@
"steps": [
"直接调用语义工厂:toast.info / success / warning / error(message, duration?),无需先建容器",
"不传 duration 时默认停留 3 秒后自动淡出并移除节点",
"鼠标悬停或键盘 Tab 进卡片时倒计时暂停,移出 / 焦点离开后按剩余时长续走(剩余不足 400ms 时补到 400ms,避免松手即消失);宿主容器仍穿透,只有卡片本体这一小块可命中",
"多条消息在右上角自动纵向堆叠,互不遮挡",
"需要完整入参(含 variant)时用 showToast(props);组件挂载容器由内部懒创建,样式随 ui.css 一起引入",
"同屏最多 4 条,超出自动挤掉最早一条;需要用户主动确认的消息用 showToast({ closable: true }) 给个 ×",
+2 -7
View File
@@ -1543,16 +1543,14 @@ button.ua-step-dot:hover {
font-size: var(--ua-font-size-md);
opacity: 0;
transform: translateX(var(--ua-translate-12));
/* 自己接指针:悬停可暂停倒计时(宿主仍穿透,只有卡片本体这一小块可命中) */
pointer-events: auto;
transition: opacity var(--ua-duration-base) var(--ua-ease-out), transform var(--ua-duration-base) var(--ua-ease-out);
}
.ua-toast.ua-open {
opacity: 1;
transform: translateX(0);
}
/* 仅带关闭按钮的 toast 接收指针,其余保持穿透不挡页面 */
.ua-toast.is-closable {
pointer-events: auto;
}
.ua-toast-close {
padding: 0 var(--ua-space-05);
border: none;
@@ -4219,9 +4217,6 @@ button.ua-step-dot:hover {
}
/* —— toast 操作按钮(撤销类) —— */
.ua-toast.is-action {
pointer-events: auto;
}
.ua-toast-action {
padding: 0;
border: none;