diff --git a/.ua/backlog.json b/.ua/backlog.json index b84370d..cde3376 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -1140,6 +1140,14 @@ "acceptance": "npm run changelog 重新生成(267 提交),changelog:check 一致(提交本条后按约定再差一条);npm run check 全绿", "notes": "自补:发版前刷新步骤,收尾统一执行", "status": "done" + }, + { + "id": "g98", + "title": "弹窗与抽屉补遮罩关闭开关", + "motivation": "modal/drawer 点遮罩必关,表单类弹窗填到一半误触就丢内容;调用方没有任何开关能保留 × 与 Esc 的同时禁掉遮罩关闭", + "acceptance": "ModalProps / DrawerProps 新增 closeOnOverlay(缺省 true);传 false 时点遮罩不关闭,× 与 Esc 行为不变;文档参数表与演示(防误关弹窗实例)同步;npm run check 全绿", + "notes": "自补:可用性;遮罩点击处理改成显式卫语句,语义比一长串 && 更清楚", + "status": "open" } ] } diff --git a/.ua/index.json b/.ua/index.json index 4db4b5e..980cd77 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T05:14:42.470Z", + "generatedAt": "2026-10-26T05:15:56.401Z", "repoRoot": ".", "packages": [ { @@ -1578,6 +1578,11 @@ "t": "unknown", "d": "底部操作区内容(常见为按钮组,右对齐);缺省不渲染" }, + { + "n": "closeOnOverlay", + "t": "boolean", + "d": "点遮罩是否关闭弹窗;缺省 true,表单类可传 false 防误关(× 与 Esc 仍可用)" + }, { "n": "onClose", "t": "() => void", @@ -1634,6 +1639,11 @@ "t": "unknown", "d": "底部操作区内容;缺省不渲染" }, + { + "n": "closeOnOverlay", + "t": "boolean", + "d": "点遮罩是否关闭抽屉;缺省 true(× 与 Esc 始终可用)" + }, { "n": "onClose", "t": "() => void", diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 90be537..626b760 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -140,6 +140,7 @@ const name = signal(''); const agreed = signal(false); const nightMode = signal(true); const sheetOpen = signal(false); // 底部抽屉开合 +const stickyOpen = signal(false); // 防误关弹窗开合 const sound = signal(true); // 无可见文案的开关演示 const city = signal(''); const size = signal('m'); @@ -445,10 +446,21 @@ const content = h( section( 'sec-tabs-modal', '标签页与弹窗', - 'tabs 滑动指示条 + 方向键切换(←→),禁用页签置灰;modal 支持 Esc / 遮罩 / × 关闭,弹窗内的下拉展开时 Esc 先收下拉、再按才关弹窗(逐层关闭)。', + 'tabs 滑动指示条 + 方向键切换(←→),禁用页签置灰;modal 支持 Esc / 遮罩 / × 关闭(closeOnOverlay: false 时点遮罩不关、防填一半误触),弹窗内的下拉展开时 Esc 先收下拉、再按才关弹窗(逐层关闭)。', tabs({ tabs: [{ value: 'a', label: '概览' }, { value: 'b', label: '明细' }, { value: 'c', label: '设置' }, { value: 'd', label: '已停用', disabled: true }], active: activeTab }), h('span', { class: 'out' }, () => `当前页签:${activeTab()}`), button({ label: '打开弹窗', onClick: () => modalOpen(true) }), + button({ label: '打开防误关弹窗', variant: 'ghost', onClick: () => stickyOpen(true) }), + modal( + { + title: '填写中,点遮罩不关闭', + open: stickyOpen, + closeOnOverlay: false, + onClose: () => stickyOpen(false), + footer: h('div', { class: 'row' }, button({ label: '关闭', variant: 'ghost', onClick: () => stickyOpen(false) })), + }, + h('p', null, 'closeOnOverlay: false —— 点遮罩不会关闭,防止填到一半误触丢失内容;× 与 Esc 仍然有效。'), + ), modal( { title: '确认操作', diff --git a/llms-full.txt b/llms-full.txt index a196b6a..e79fed2 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T05:14:42.470Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T05:15:56.401Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -842,6 +842,7 @@ const page = signal(1); pagination({ page, pageSize: 3, total: 24, onChange: (p) - title(必填):string —— 标题 - size(默认 md):'sm' | 'md' | 'lg' —— 宽度档位:sm 340px / md 420px / lg 640px;缺省 md - footer:unknown —— 底部操作区内容(常见为按钮组,右对齐);缺省不渲染 +- closeOnOverlay:boolean —— 点遮罩是否关闭弹窗;缺省 true,表单类可传 false 防误关(× 与 Esc 仍可用) - onClose(回调):() => void —— 关闭回调(点遮罩、× 或 Esc 触发) - ...children:unknown[] —— 弹窗内容子节点 @@ -871,6 +872,7 @@ const open = signal(false); modal({ title: '确认操作', open, onClose: () => - placement(默认 right):'left' | 'right' | 'top' | 'bottom' —— 滑出方向;缺省 right - width(默认 320):number —— 面板尺寸(像素):左右滑出为宽度,上下滑出为高度;缺省 320 - footer:unknown —— 底部操作区内容;缺省不渲染 +- closeOnOverlay:boolean —— 点遮罩是否关闭抽屉;缺省 true(× 与 Esc 始终可用) - onClose(回调):() => void —— 关闭回调(点遮罩 / × / Esc 触发) - ...children:unknown[] —— 面板内容子节点 diff --git a/packages/skin/ui/src/drawer.ts b/packages/skin/ui/src/drawer.ts index e53f4ae..3c4f77e 100644 --- a/packages/skin/ui/src/drawer.ts +++ b/packages/skin/ui/src/drawer.ts @@ -15,7 +15,9 @@ export function drawer(props: DrawerProps, ...children: unknown[]): HTMLElement const overlay = h('div', { class: () => `ua-drawer-overlay${props.open() ? ' ua-open' : ''}`, onClick: (e: Event) => { - if (e.target === overlay) close(); // 卫语句:仅点遮罩本身关闭 + if (e.target !== overlay) return; // 卫语句:只处理点在遮罩本身的 + if (props.closeOnOverlay === false) return; // 显式禁止时防误关,× 与 Esc 仍可用 + close(); }, }); const panel = h( diff --git a/packages/skin/ui/src/modal.ts b/packages/skin/ui/src/modal.ts index a700e80..3abb770 100644 --- a/packages/skin/ui/src/modal.ts +++ b/packages/skin/ui/src/modal.ts @@ -15,7 +15,9 @@ export function modal(props: ModalProps, ...children: unknown[]): HTMLElement { const overlay = h('div', { class: 'ua-modal-overlay', onClick: (e: Event) => { - if (e.target === overlay) close(); // 卫语句:仅点遮罩本身关闭 + if (e.target !== overlay) return; // 卫语句:只处理点在遮罩本身的 + if (props.closeOnOverlay === false) return; // 显式禁止时防误关,× 与 Esc 仍可用 + close(); }, }); const box = h( diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 0f1f4a0..2d1a4ad 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -336,6 +336,7 @@ export interface ModalProps { open: Signal; // 开关信号:true 显示 / false 隐藏 size?: 'sm' | 'md' | 'lg'; // 宽度档位;缺省 md(420px),sm 340px / lg 640px footer?: unknown; // 底部操作区内容(常见为按钮组);缺省不渲染 + closeOnOverlay?: boolean; // 点遮罩是否关闭;缺省 true(表单类弹窗可传 false 防误关,× 与 Esc 仍可用) onClose?: () => void; // 关闭回调(点遮罩、× 或 Esc 触发) } @@ -346,6 +347,7 @@ export interface DrawerProps { placement?: 'left' | 'right' | 'top' | 'bottom'; // 滑出方向;缺省 right width?: number; // 面板尺寸像素:左右滑出为宽度,上下滑出为高度;缺省 320 footer?: unknown; // 底部操作区内容;缺省不渲染 + closeOnOverlay?: boolean; // 点遮罩是否关闭;缺省 true(× 与 Esc 始终可用) onClose?: () => void; // 关闭回调(遮罩 / × / Esc 触发) } diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 1e579b5..d14916b 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -1449,6 +1449,11 @@ "t": "unknown", "d": "底部操作区内容(常见为按钮组,右对齐);缺省不渲染" }, + { + "n": "closeOnOverlay", + "t": "boolean", + "d": "点遮罩是否关闭弹窗;缺省 true,表单类可传 false 防误关(× 与 Esc 仍可用)" + }, { "n": "onClose", "t": "() => void", @@ -1505,6 +1510,11 @@ "t": "unknown", "d": "底部操作区内容;缺省不渲染" }, + { + "n": "closeOnOverlay", + "t": "boolean", + "d": "点遮罩是否关闭抽屉;缺省 true(× 与 Esc 始终可用)" + }, { "n": "onClose", "t": "() => void",