让节点删除默认落到选中节点所在的树

src/view/ui-editor/useUiEditorPage.ts 的 deleteNode 默认 treeId 改为 treeForSelectedNode?.src_ui_design ?? activeImageId:选中节点可以来自非激活界面图,Inspector 删除按钮不给 treeId 时会静默删不掉
src/view/ui-editor/uiEditorKeyboardShortcuts.ts 与 view/ui-editor/index.tsx 去掉键盘删除强制传 activeImageId 的接线,treeId 改为可选,同时删掉已无消费者的 activeImageId 入参
tests/uiEditorPage.test.ts 补「非激活树里的节点默认也能删除」用例,uiEditorKeyboardShortcuts.test.ts 断言改为只传节点 id
This commit is contained in:
2026-09-24 16:04:29 +08:00
parent 12e8287f92
commit ef4601c555
5 changed files with 43 additions and 17 deletions
@@ -114,21 +114,19 @@ export default function UiEditorPage({
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(() => {
const onKeyDown = (event: KeyboardEvent) =>
handleUiEditorKeyDown(event, {
selectedNodeId,
activeImageId,
deleteNode,
historyUndo,
historyRedo,
});
window.addEventListener('keydown', onKeyDown);
return () => window.removeEventListener('keydown', onKeyDown);
}, [activeImageId, deleteNode, historyRedo, historyUndo, selectedNodeId]);
}, [deleteNode, historyRedo, historyUndo, selectedNodeId]);
async function save(afterReturn = false) {
const result = await session.save.save();
@@ -5,8 +5,8 @@ type DeleteResult = { ok: boolean } | undefined;
export type UiEditorKeyboardActions = {
selectedNodeId: NodeId | null;
activeImageId: UIDesignImageId | null;
deleteNode: (nodeId: NodeId, treeId: UIDesignImageId) => DeleteResult;
/** 省略 treeId 时由调用方按选中节点定位所在 UI 树。 */
deleteNode: (nodeId: NodeId, treeId?: UIDesignImageId | null) => DeleteResult;
historyUndo: () => boolean;
historyRedo: () => boolean;
};
@@ -50,11 +50,8 @@ export function handleUiEditorKeyDown(
!event.shiftKey &&
!isInteractiveTarget(event.target)
) {
if (actions.selectedNodeId && actions.activeImageId) {
const result = actions.deleteNode(
actions.selectedNodeId,
actions.activeImageId,
);
if (actions.selectedNodeId) {
const result = actions.deleteNode(actions.selectedNodeId);
if (result?.ok) {
event.preventDefault();
event.stopPropagation();
@@ -875,7 +875,12 @@ export function useUiEditorSession(
}
const deleteNode = useCallback(
(nodeId: NodeId, treeId = activeImageId) => {
(
nodeId: NodeId,
// 不给 treeId 时落到选中节点真正所在的树:选中节点可以来自非激活界面图,
// 按 activeImageId 找会静默删不掉。
treeId = treeForSelectedNode?.src_ui_design ?? activeImageId,
) => {
if (!treeId) return;
const tree = editorUiTrees.find(
(candidate) => candidate.src_ui_design === treeId,
@@ -892,7 +897,13 @@ export function useUiEditorSession(
}
return result;
},
[activeImageId, editorDeleteNode, editorUiTrees, selectedNodeId],
[
activeImageId,
editorDeleteNode,
editorUiTrees,
selectedNodeId,
treeForSelectedNode,
],
);
function selectSprite(id: SpriteAssetId) {
@@ -25,7 +25,6 @@ describe('ui editor keyboard shortcuts', () => {
const listener = (event: KeyboardEvent) =>
handleUiEditorKeyDown(event, {
selectedNodeId: null,
activeImageId: null,
deleteNode: vi.fn(),
historyUndo,
historyRedo,
@@ -47,7 +46,6 @@ describe('ui editor keyboard shortcuts', () => {
const listener = (event: KeyboardEvent) =>
handleUiEditorKeyDown(event, {
selectedNodeId: null,
activeImageId: null,
deleteNode: vi.fn(),
historyUndo,
historyRedo,
@@ -70,7 +68,6 @@ describe('ui editor keyboard shortcuts', () => {
const listener = (event: KeyboardEvent) =>
handleUiEditorKeyDown(event, {
selectedNodeId: 'node-1',
activeImageId: 'page-1',
deleteNode,
historyUndo: vi.fn(() => true),
historyRedo: vi.fn(() => true),
@@ -93,7 +90,6 @@ describe('ui editor keyboard shortcuts', () => {
const listener = (event: KeyboardEvent) =>
handleUiEditorKeyDown(event, {
selectedNodeId: 'node-1',
activeImageId: 'page-1',
deleteNode,
historyUndo: vi.fn(() => true),
historyRedo: vi.fn(() => true),
@@ -103,7 +99,7 @@ describe('ui editor keyboard shortcuts', () => {
fireEvent.click(zoomIn);
fireEvent.keyDown(window, { key: 'Delete' });
expect(deleteNode).toHaveBeenCalledWith('node-1', 'page-1');
expect(deleteNode).toHaveBeenCalledWith('node-1');
window.removeEventListener('keydown', listener);
});
});
@@ -596,6 +596,30 @@ describe('UiEditorPage', () => {
expect(result.current.canvas.hiddenNodeIds.size).toBe(0);
});
it('deletes an inspector node from its own tree without an explicit tree id', async () => {
const { result } = await renderLoadedSession(
stateWithPages(['page-a', 'page-b']),
);
let otherTreeNodeId: string | undefined;
act(() => {
const inserted = result.current.input.insertNode('page-b-root', 'page-b');
if (inserted?.ok) otherTreeNodeId = inserted.value;
});
expect(otherTreeNodeId).toBeTruthy();
// 选中非激活界面图里的节点:Inspector 删除按钮不给 treeId,必须落到它所在的树。
act(() => result.current.input.selectDesignImage('page-a'));
act(() => result.current.input.selectNode(otherTreeNodeId!));
act(() => result.current.inspector.deleteNode(otherTreeNodeId!));
const otherTree = result.current.canvas.uiTrees.find(
(tree) => tree.src_ui_design === 'page-b',
);
expect(otherTree?.root.children.map((child) => child.id)).not.toContain(
otherTreeNodeId,
);
});
it('clears selection when deleting a node removes the selected descendant', async () => {
const { result } = await renderLoadedSession(stateWithPages(['page']));
const rootId = 'page-root';