iter(g80): 通知悬停与聚焦暂停自动关闭计时
This commit is contained in:
@@ -996,6 +996,14 @@
|
|||||||
"acceptance": "可排序 th 进 Tab 序(tabindex=0,不可排序的不抢焦点),Enter/空格 触发与点击相同的 toggleSort(空格不滚页),补 scope=col;悬停/聚焦视觉沿用既有 ua-sortable 与全局 focus-visible;dataTable 仍 ≤ 200 行;npm run check 全绿",
|
"acceptance": "可排序 th 进 Tab 序(tabindex=0,不可排序的不抢焦点),Enter/空格 触发与点击相同的 toggleSort(空格不滚页),补 scope=col;悬停/聚焦视觉沿用既有 ua-sortable 与全局 focus-visible;dataTable 仍 ≤ 200 行;npm run check 全绿",
|
||||||
"notes": "自补:无障碍;排序入口收敛为单个 toggle 闭包,鼠标与键盘同源,不改核心签名",
|
"notes": "自补:无障碍;排序入口收敛为单个 toggle 闭包,鼠标与键盘同源,不改核心签名",
|
||||||
"status": "done"
|
"status": "done"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g80",
|
||||||
|
"title": "通知悬停/聚焦暂停自动关闭计时",
|
||||||
|
"motivation": "duration 一设定就是定时器到点即退场,鼠标悬停正在读的卡片照样消失(WCAG 2.2.1「计时可调」);通知恰恰是长篇描述,读到一半被收走比不自动关更烦",
|
||||||
|
"acceptance": "pointerenter / focusin 暂停并扣减已流逝时间,pointerleave / focusout 按剩余毫秒续计;duration 为 0 时行为不变;关闭仍先清计时器;文档与演示描述同步;npm run check 全绿",
|
||||||
|
"notes": "自补:无障碍;不改签名,remaining 时间在组件内折算",
|
||||||
|
"status": "open"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-26T04:55:25.133Z",
|
"generatedAt": "2026-10-26T04:57:50.936Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -2163,7 +2163,7 @@
|
|||||||
{
|
{
|
||||||
"n": "duration",
|
"n": "duration",
|
||||||
"t": "number",
|
"t": "number",
|
||||||
"d": "自动关闭毫秒;缺省 0 = 常驻不自动关"
|
"d": "自动关闭毫秒;缺省 0 = 常驻不自动关,非 0 时悬停 / 聚焦暂停计时"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"n": "closable",
|
"n": "closable",
|
||||||
|
|||||||
+1
-1
@@ -738,7 +738,7 @@ const content = h(
|
|||||||
section(
|
section(
|
||||||
'sec-notification',
|
'sec-notification',
|
||||||
'通知卡片',
|
'通知卡片',
|
||||||
'notification:右下角堆叠(与右上角 toast 分区),标题 + 描述 + 操作区,缺省常驻需手动关闭;duration 传毫秒可自动消失。',
|
'notification:右下角堆叠(与右上角 toast 分区),标题 + 描述 + 操作区,缺省常驻需手动关闭;duration 传毫秒可自动消失,悬停或聚焦会暂停倒计时、离开按剩余时间续计。',
|
||||||
h(
|
h(
|
||||||
'div',
|
'div',
|
||||||
{ class: 'row' },
|
{ class: 'row' },
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-26T04:55:25.133Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-26T04:57:50.936Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -1184,7 +1184,7 @@ upload({ multiple: true, accept: 'image/*', hint: '拖拽图片到此处', onCha
|
|||||||
- title(必填):string —— 通知标题
|
- title(必填):string —— 通知标题
|
||||||
- description:string —— 补充说明;缺省不渲染
|
- description:string —— 补充说明;缺省不渲染
|
||||||
- variant:'info' | 'success' | 'warning' | 'danger' —— 语义色(左侧条与圆点);缺省 info
|
- variant:'info' | 'success' | 'warning' | 'danger' —— 语义色(左侧条与圆点);缺省 info
|
||||||
- duration:number —— 自动关闭毫秒;缺省 0 = 常驻不自动关
|
- duration:number —— 自动关闭毫秒;缺省 0 = 常驻不自动关,非 0 时悬停 / 聚焦暂停计时
|
||||||
- closable:boolean —— 是否显示关闭按钮;缺省 true
|
- closable:boolean —— 是否显示关闭按钮;缺省 true
|
||||||
- action:unknown —— 操作区内容(如按钮);缺省不渲染
|
- action:unknown —— 操作区内容(如按钮);缺省不渲染
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,15 @@ function dismiss(item: HTMLElement): void {
|
|||||||
window.setTimeout(() => item.isConnected && item.remove(), 320); // reduced-motion 下不触发 transition
|
window.setTimeout(() => item.isConnected && item.remove(), 320); // reduced-motion 下不触发 transition
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 通知卡片:标题 + 描述 + 可选操作区,返回 { el, close } 句柄;duration 为 0 表示常驻 ***/
|
/*** 通知卡片:标题 + 描述 + 可选操作区,返回 { el, close } 句柄;duration 为 0 表示常驻,非 0 时悬停 / 聚焦自动暂停计时 ***/
|
||||||
export function notification(props: NotificationProps): { el: HTMLElement; close: () => void } {
|
export function notification(props: NotificationProps): { el: HTMLElement; close: () => void } {
|
||||||
// —— 状态集中声明 ——
|
// —— 状态集中声明 ——
|
||||||
const duration = props.duration ?? 0; // 缺省常驻:通知通常需要用户读完
|
const duration = props.duration ?? 0; // 缺省常驻:通知通常需要用户读完
|
||||||
const closable = props.closable ?? true; // 缺省带关闭按钮
|
const closable = props.closable ?? true; // 缺省带关闭按钮
|
||||||
let timer = 0; // 自动关闭计时器句柄
|
let timer = 0; // 自动关闭计时器句柄
|
||||||
|
let remain = duration; // 剩余毫秒:暂停时扣减,续计时接着走
|
||||||
|
let startedAt = 0; // 本轮计时起点,用于折算暂停前已流逝的时间
|
||||||
|
let paused = false; // 是否因悬停 / 聚焦而暂停
|
||||||
const closeBtn = h('button', { type: 'button', class: 'ua-notice-close', 'aria-label': '关闭通知' }, '×') as HTMLButtonElement; // 关闭按钮,回调在下方绑定
|
const closeBtn = h('button', { type: 'button', class: 'ua-notice-close', 'aria-label': '关闭通知' }, '×') as HTMLButtonElement; // 关闭按钮,回调在下方绑定
|
||||||
const el = h(
|
const el = h(
|
||||||
'div',
|
'div',
|
||||||
@@ -47,10 +50,21 @@ export function notification(props: NotificationProps): { el: HTMLElement; close
|
|||||||
};
|
};
|
||||||
closeBtn.addEventListener('click', () => close());
|
closeBtn.addEventListener('click', () => close());
|
||||||
|
|
||||||
|
/*** 起表:按剩余毫秒设定时器 ***/
|
||||||
|
const arm = () => { if (remain <= 0) return; startedAt = Date.now(); timer = window.setTimeout(() => dismiss(el), remain); };
|
||||||
|
/*** 暂停计时:扣掉已流逝部分,剩余时间留给续计 ***/
|
||||||
|
const pause = () => { if (paused || remain <= 0) return; paused = true; window.clearTimeout(timer); remain -= Date.now() - startedAt; };
|
||||||
|
/*** 续计:离开后按剩余毫秒重新起表 ***/
|
||||||
|
const resume = () => { if (!paused) return; paused = false; arm(); };
|
||||||
|
el.addEventListener('pointerenter', pause); // 悬停阅读时不倒计时
|
||||||
|
el.addEventListener('pointerleave', resume);
|
||||||
|
el.addEventListener('focusin', pause); // 键盘聚焦到关闭/操作按钮同样暂停
|
||||||
|
el.addEventListener('focusout', resume);
|
||||||
|
|
||||||
const stack = ensureHost();
|
const stack = ensureHost();
|
||||||
if (stack.childElementCount >= MAX_STACK) stack.firstElementChild?.remove(); // 超出上限挤掉最早一条
|
if (stack.childElementCount >= MAX_STACK) stack.firstElementChild?.remove(); // 超出上限挤掉最早一条
|
||||||
stack.appendChild(el);
|
stack.appendChild(el);
|
||||||
requestAnimationFrame(() => el.classList.add('ua-open')); // 下一帧入场,保证过渡生效
|
requestAnimationFrame(() => el.classList.add('ua-open')); // 下一帧入场,保证过渡生效
|
||||||
if (duration > 0) timer = window.setTimeout(() => dismiss(el), duration); // 常驻(0)不设定时器
|
if (duration > 0) arm(); // 常驻(0)不设定时器
|
||||||
return { el, close };
|
return { el, close };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2034,7 +2034,7 @@
|
|||||||
{
|
{
|
||||||
"n": "duration",
|
"n": "duration",
|
||||||
"t": "number",
|
"t": "number",
|
||||||
"d": "自动关闭毫秒;缺省 0 = 常驻不自动关"
|
"d": "自动关闭毫秒;缺省 0 = 常驻不自动关,非 0 时悬停 / 聚焦暂停计时"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"n": "closable",
|
"n": "closable",
|
||||||
|
|||||||
Reference in New Issue
Block a user