树根拖拽的指针取消不再提交偏移

PreviewWorkspace.tsx 拆出 handleTreePointerCancel:pointercancel 只清拖拽状态与指针捕获,不再按抬手坐标算最终偏移,避免浏览器取消手势时把意外偏移写进文档
tests/previewRightPanDrag.test.tsx 补「抬起提交、取消不提交」用例,并给画布夹具补上 setTreeOffset 桩
This commit is contained in:
2026-09-24 16:06:48 +08:00
parent 064c26d7c9
commit 134046f806
2 changed files with 75 additions and 1 deletions
@@ -588,6 +588,21 @@ export function PreviewWorkspace({
event.currentTarget.releasePointerCapture(event.pointerId);
};
/** 浏览器发起的指针取消不是正常抬起:只清拖拽状态与捕获,不写回树偏移。 */
const handleTreePointerCancel = (
event: ReactPointerEvent<HTMLDivElement>,
) => {
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<HTMLDivElement>) => {
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] ? (
<img
@@ -117,6 +117,7 @@ function createCanvas(
insertNode: vi.fn(),
insertNodeAfter: vi.fn(),
deleteNode: vi.fn(),
setTreeOffset: vi.fn(),
openClearDialog: vi.fn(),
...overrides,
};
@@ -206,6 +207,64 @@ afterEach(() => {
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();