From 134046f806ced659df8ac3aa4b68ae3c18a4fdcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 16:06:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=91=E6=A0=B9=E6=8B=96=E6=8B=BD=E7=9A=84?= =?UTF-8?q?=E6=8C=87=E9=92=88=E5=8F=96=E6=B6=88=E4=B8=8D=E5=86=8D=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E5=81=8F=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PreviewWorkspace.tsx 拆出 handleTreePointerCancel:pointercancel 只清拖拽状态与指针捕获,不再按抬手坐标算最终偏移,避免浏览器取消手势时把意外偏移写进文档 tests/previewRightPanDrag.test.tsx 补「抬起提交、取消不提交」用例,并给画布夹具补上 setTreeOffset 桩 --- .../components/preview/PreviewWorkspace.tsx | 17 +++++- .../tests/previewRightPanDrag.test.tsx | 59 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx index 91557dced..7939a74c0 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx @@ -588,6 +588,21 @@ export function PreviewWorkspace({ event.currentTarget.releasePointerCapture(event.pointerId); }; + /** 浏览器发起的指针取消不是正常抬起:只清拖拽状态与捕获,不写回树偏移。 */ + const handleTreePointerCancel = ( + event: ReactPointerEvent, + ) => { + const drag = treeDragRef.current; + if (!drag || drag.pointerId !== event.pointerId) return; + treeDragRef.current = null; + setPreviewTreeOffsets((current) => { + const next = new Map(current); + next.delete(drag.treeId); + return next; + }); + releasePointerCapture(event.currentTarget, event.pointerId); + }; + const handleWheel = (event: React.WheelEvent) => { event.preventDefault(); const rect = event.currentTarget.getBoundingClientRect(); @@ -702,7 +717,7 @@ export function PreviewWorkspace({ } onPointerMove={handleTreePointerMove} onPointerUp={handleTreePointerUp} - onPointerCancel={handleTreePointerUp} + onPointerCancel={handleTreePointerCancel} > {showOriginImage && previewUrls[item.src_ui_design] ? ( { vi.restoreAllMocks(); }); +describe('PreviewWorkspace tree drag', () => { + function renderTreeDrag() { + const rendered = renderPreview(); + const treeLayer = rendered.container.querySelector( + '[data-tree-id]', + ) as HTMLDivElement; + const rootNode = treeLayer.querySelector( + '[data-node-id="root"]', + ) as HTMLDivElement; + return { ...rendered, rootNode, treeLayer }; + } + + it('commits the tree offset on pointerup but not on pointercancel', () => { + const committed = renderTreeDrag(); + dispatchPointer(committed.rootNode, 'pointerdown', { + button: 0, + clientX: 10, + clientY: 10, + pointerId: 11, + }); + // 指针捕获在树容器上,后续指针事件由浏览器派发给捕获元素。 + dispatchPointer(committed.treeLayer, 'pointermove', { + clientX: 40, + clientY: 50, + pointerId: 11, + }); + dispatchPointer(committed.treeLayer, 'pointerup', { + button: 0, + clientX: 40, + clientY: 50, + pointerId: 11, + }); + expect(committed.canvas.setTreeOffset).toHaveBeenCalledTimes(1); + expect(committed.canvas.setTreeOffset.mock.calls[0]?.[0]).toBe('page-1'); + cleanup(); + + const cancelled = renderTreeDrag(); + dispatchPointer(cancelled.rootNode, 'pointerdown', { + button: 0, + clientX: 10, + clientY: 10, + pointerId: 12, + }); + dispatchPointer(cancelled.treeLayer, 'pointermove', { + clientX: 40, + clientY: 50, + pointerId: 12, + }); + dispatchPointer(cancelled.treeLayer, 'pointercancel', { + button: 0, + clientX: 40, + clientY: 50, + pointerId: 12, + }); + expect(cancelled.canvas.setTreeOffset).not.toHaveBeenCalled(); + }); +}); + describe('PreviewWorkspace right-button pan', () => { it('pans the viewport on a right drag and never opens the node menu', () => { const { canvas, child, container, preview } = renderPreview();