From 5898d3808322eba46473519af90237d401aa529f 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 11:35:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=20UI=20=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=99=A8=20Delete=20=E5=88=A0=E9=99=A4=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 复用现有 deleteNode 命令处理 Delete 快捷键 过滤交互控件、修饰键和重复按键事件 清理删除子树后的无效节点选区并补充页面测试 --- .../src/view/ui-editor/index.tsx | 36 +++++++++++++++---- .../src/view/ui-editor/useUiEditorPage.ts | 10 +++++- .../tests/uiEditorPage.test.ts | 17 +++++++++ 3 files changed, 56 insertions(+), 7 deletions(-) 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 241185424..3379750e2 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 @@ -79,24 +79,48 @@ export default function UiEditorPage({ session.save.isLocked; const historyUndo = session.history.undo; const historyRedo = session.history.redo; + const selectedNodeId = session.input.selectedNodeId; + const activeImageId = session.input.activeImageId; + const deleteNode = session.input.deleteNode; useEffect(() => { if (session.save.isDirty) setGenerateSuccess(null); }, [session.save.isDirty]); useEffect(() => { - const isEditableTarget = (target: EventTarget | null) => { - const element = target instanceof HTMLElement ? target : null; + const isInteractiveTarget = (target: EventTarget | null) => { + const element = target instanceof Element ? target : null; return Boolean( - element?.isContentEditable || - element?.closest('input, textarea, select, [contenteditable="true"]'), + (target instanceof HTMLElement && target.isContentEditable) || + element?.closest( + 'button, a, input, textarea, select, [contenteditable="true"], [role="button"]', + ), ); }; const onKeyDown = (event: KeyboardEvent) => { + if ( + event.key === 'Delete' && + !event.repeat && + !event.defaultPrevented && + !event.ctrlKey && + !event.metaKey && + !event.altKey && + !event.shiftKey && + !isInteractiveTarget(event.target) + ) { + if (selectedNodeId && activeImageId) { + const result = deleteNode(selectedNodeId, activeImageId); + if (result?.ok) { + event.preventDefault(); + event.stopPropagation(); + return; + } + } + } if ( event.repeat || event.defaultPrevented || - isEditableTarget(event.target) || + isInteractiveTarget(event.target) || (!event.ctrlKey && !event.metaKey) ) { return; @@ -113,7 +137,7 @@ export default function UiEditorPage({ }; window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); - }, [historyRedo, historyUndo]); + }, [activeImageId, deleteNode, historyRedo, historyUndo, selectedNodeId]); async function save(afterReturn = false) { if (await session.save.save()) { diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts index b2c98484b..25969e843 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts @@ -437,6 +437,9 @@ export function useUiEditorSession( ); return next.size === current.size ? current : next; }); + setSelectedNodeId((current) => + current && !validNodeIds.has(current) ? null : current, + ); }, [editor.state.ui_trees]); const isNodePreviewVisible = useCallback( @@ -881,12 +884,17 @@ export function useUiEditorSession( function deleteNode(nodeId: NodeId, treeId = activeImageId) { if (!treeId) return; + const tree = editor.state.ui_trees.find( + (candidate) => candidate.src_ui_design === treeId, + ); + const location = tree ? findUiNodeLocation(tree.root, nodeId) : null; + const deletedNodeIds = location ? collectUiNodeIds(location.node) : null; const result = editor.deleteNode(treeId, nodeId); if (!result.ok) { setStatus('无法删除该节点。'); return result; } - if (selectedNodeId === nodeId) setSelectedNodeId(null); + if (deletedNodeIds?.has(selectedNodeId ?? '')) setSelectedNodeId(null); return result; } diff --git a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts index 6c21bc416..7bd964430 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts @@ -473,6 +473,23 @@ describe('UiEditorPage', () => { expect(result.current.canvas.hiddenNodeIds.size).toBe(0); }); + it('clears selection when deleting a node removes the selected descendant', async () => { + const { result } = await renderLoadedSession(stateWithPages(['page'])); + const rootId = 'page-root'; + let parentId: string | undefined; + let childId: string | undefined; + act(() => { + parentId = result.current.input.insertNode(rootId, 'page')?.value; + childId = result.current.input.insertNode(parentId!, 'page')?.value; + result.current.input.selectNode(childId!); + }); + + act(() => result.current.input.deleteNode(parentId!, 'page')); + + expect(result.current.canvas.selectedNodeId).toBeNull(); + expect(result.current.history.canUndo).toBe(true); + }); + it('keeps Inspector status highlighting separate from node navigation', async () => { const { result } = await renderLoadedSession(stateWithPages(['page'])); const rootId = 'page-root';