From f0299920831d7137a2d27eb36208a7da63c9038d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 4 Sep 2026 18:04:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=B9=E7=AA=97=E5=86=85?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=BF=AB=E6=8D=B7=E9=94=AE=E8=AF=AF=E5=88=A0?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 排除 role=dialog 和 aria-modal 弹窗内的 Delete/Backspace 快捷键 补充弹窗焦点下节点不应被删除的回归测试 同步更新 UI 编辑器撤销重做规范 --- .../src/view/ui-editor/index.tsx | 2 +- .../tests/uiEditorPage.test.ts | 28 +++++++++++++++++++ docs/【UI编辑器】撤销重做规范-2026-09-03.md | 1 + 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx index de1f21527..16b53de8a 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx @@ -101,7 +101,7 @@ export default function UiEditorPage({ return Boolean( (target instanceof HTMLElement && target.isContentEditable) || element?.closest( - 'button, a, input, textarea, select, [contenteditable="true"], [role="button"]', + 'button, a, input, textarea, select, [contenteditable="true"], [role="button"], [role="dialog"], [aria-modal="true"]', ), ); }; diff --git a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts index e55cb9fcd..2c4206167 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts @@ -514,6 +514,34 @@ describe('UiEditorPage', () => { await waitFor(() => expect(screen.queryByText('page-child')).toBeNull()); }); + it('does not delete a selected node when Delete originates inside a dialog', async () => { + const state = stateWithPages(['page']); + state.ui_trees[0]!.root.children = [node('page-child')]; + const stateStore: IUiDesignStateStore = { + load: vi.fn().mockResolvedValue({ revision: 0, state }), + save: vi.fn(), + generateCode: vi.fn().mockRejectedValue(new Error('测试未配置代码生成')), + }; + + render( + createElement(UiEditorPage, { + projectPath: '/tmp/ui-editor-delete-dialog', + resourceId: 'ui-resource', + stateStore, + }), + ); + + const child = await screen.findByText('page-child'); + fireEvent.click(child); + const dialog = document.createElement('div'); + dialog.setAttribute('role', 'dialog'); + document.body.appendChild(dialog); + fireEvent.keyDown(dialog, { key: 'Delete' }); + + expect(screen.queryAllByText('page-child').length).toBeGreaterThan(0); + dialog.remove(); + }); + it('keeps Inspector status highlighting separate from node navigation', async () => { const { result } = await renderLoadedSession(stateWithPages(['page'])); const rootId = 'page-root'; diff --git a/docs/【UI编辑器】撤销重做规范-2026-09-03.md b/docs/【UI编辑器】撤销重做规范-2026-09-03.md index 9efab4673..bd4b6abaa 100644 --- a/docs/【UI编辑器】撤销重做规范-2026-09-03.md +++ b/docs/【UI编辑器】撤销重做规范-2026-09-03.md @@ -51,6 +51,7 @@ UI 编辑器支持撤销最近一次或多次作品编辑,并支持重做被 - `input`、`textarea`、`select`、`contenteditable` 等文本编辑控件交给浏览器原生行为,不拦截文本撤销;按钮/链接等非文本控件仍允许编辑器撤销/重做快捷键生效。 - UI Editor 页面内选中节点后,支持不带修饰键的 `Delete` 删除节点及其子节点;快捷键复用 Inspector、树面板和右键菜单共用的 `deleteNode` 命令,因此沿用根节点/锁定禁删、单条历史记录、dirty 标记和后续保存语义。 - `Delete` / `Backspace` 在 `input`、`textarea`、`select`、`contenteditable`、按钮和链接等交互控件聚焦时交给浏览器原生行为;无选中、根节点、锁定或目标不存在时不执行删除。长按重复事件不重复删除,成功处理后阻止默认行为和事件冒泡。 +- `Delete` / `Backspace` 在打开的对话框(`role="dialog"` 或 `aria-modal="true"`)内聚焦时交给对话框处理,不删除对话框背后的节点。 - 无可撤销或重做记录时按钮禁用,并提供可访问名称。 ## 脏状态与选择