iter(g116): 抽屉支持非模态模式

This commit is contained in:
HIsheR
2026-10-26 14:10:17 +08:00
parent 9a118f662d
commit 2ab9406902
8 changed files with 56 additions and 7 deletions
+6 -3
View File
@@ -7,14 +7,16 @@ let seq = 0; // 实例序号:给标题生成唯一 id,供 aria-labelledby
/*** 抽屉:open 信号驱动侧滑面板,点遮罩 / × / Esc 关闭;Tab 焦点陷阱 + 关闭归还焦点,与 modal 的分工是不打断主视图、可从侧边保留上下文 ***/
export function drawer(props: DrawerProps, ...children: unknown[]): HTMLElement {
const titleId = `ua-drawer-title-${++seq}`; // 标题 id:抽屉的可访问名
const modal = props.modal !== false; // 是否模态:false 时无遮罩、不锁滚动、不困焦点,页面仍可操作
const close = (): void => {
if (!props.open()) return; // 卫语句:已关闭不重复触发回调
props.open(false);
props.onClose?.();
};
const overlay = h('div', {
class: () => `ua-drawer-overlay${props.open() ? ' ua-open' : ''}`,
class: () => `ua-drawer-overlay${props.open() ? ' ua-open' : ''}${modal ? '' : ' is-plain'}`,
onClick: (e: Event) => {
if (!modal) return; // 卫语句:非模态没有遮罩,点外面不算关闭
if (e.target !== overlay) return; // 卫语句:只处理点在遮罩本身的
if (props.closeOnOverlay === false) return; // 显式禁止时防误关,× 与 Esc 仍可用
close();
@@ -28,6 +30,7 @@ export function drawer(props: DrawerProps, ...children: unknown[]): HTMLElement
role: 'dialog',
'aria-labelledby': titleId,
'aria-hidden': () => String(!props.open()),
'aria-modal': modal ? 'true' : 'false',
},
h('div', { class: 'ua-drawer-head' }, h('h3', { class: 'ua-drawer-title', id: titleId }, props.title), h('button', { class: 'ua-drawer-close', 'aria-label': '关闭抽屉', onClick: close }, '×')),
h('div', { class: 'ua-drawer-body' }, ...children),
@@ -42,7 +45,7 @@ export function drawer(props: DrawerProps, ...children: unknown[]): HTMLElement
if (props.open()) {
lastFocus = document.activeElement as HTMLElement | null; // 记录打开前焦点
release ??= pushOverlay(close); // 入 Esc 栈:多弹层时只关最上层
releaseLock ??= lockScroll(); // 模态期锁背景滚动
if (modal) releaseLock ??= lockScroll(); // 仅模态期锁背景滚动
(panel.querySelector('button, input, textarea') as HTMLElement | null)?.focus(); // 打开时聚焦首个可交互元素
} else {
release?.(); // 出 Esc 栈
@@ -53,6 +56,6 @@ export function drawer(props: DrawerProps, ...children: unknown[]): HTMLElement
lastFocus = null;
}
});
trapFocus(panel, () => props.open()); // Tab 焦点陷阱:与 modal 共用同一份实现
if (modal) trapFocus(panel, () => props.open()); // 模态才困焦点:非模态要让用户能继续操作背后页面
return overlay;
}