修复预览拖动取消状态残留

按实际拖动目标清理父节点及子节点预览变换

补充保持子节点位置时的取消回归测试

恢复拖动手势阻止浏览器原生文字选择
This commit is contained in:
2026-09-04 13:55:30 +08:00
parent 101eb78ca3
commit 2ebc97ad71
2 changed files with 63 additions and 1 deletions
@@ -271,6 +271,8 @@ export function useNodeTransformInteraction({
selectedNodeId && tree ? findNodeById(tree.root, selectedNodeId) : null;
const dragNode =
selectedNode && selectedNode.id !== tree?.root.id ? selectedNode : node;
if (!isFiniteTransform(dragNode.layout.transform)) return;
event.preventDefault();
activeGestureRef.current = {
kind: 'drag',
treeId: activeImageId,
@@ -284,7 +286,7 @@ export function useNodeTransformInteraction({
hasMoved: false,
previewNodeIds: previewNodeIds(
tree,
node.id,
dragNode.id,
logicalSize,
keepChildrenUnchanged,
),
@@ -70,12 +70,14 @@ function pointerEvent(
pointerId: number,
clientX: number,
clientY: number,
eventTarget: EventTarget = target,
) {
return {
button: 0,
clientX,
clientY,
currentTarget: target,
target: eventTarget,
pointerId,
preventDefault: vi.fn(),
shiftKey: false,
@@ -96,6 +98,7 @@ function renderInteraction({
tree = pageTree,
keepChildrenUnchanged = false,
onPreviewTransform,
selectedNodeId = null,
}: {
activeImageId?: string | null;
scale?: number;
@@ -105,6 +108,7 @@ function renderInteraction({
nodeId: string,
transform: UiNode['layout']['transform'] | null,
) => void;
selectedNodeId?: string | null;
} = {}) {
const canvasProjection = canvas();
const viewportRef = { current: { scale } };
@@ -119,6 +123,7 @@ function renderInteraction({
viewportRef,
keepChildrenUnchanged,
onPreviewTransform,
selectedNodeId,
}),
{ initialProps: { imageId: activeImageId, currentTree: tree } },
);
@@ -126,6 +131,61 @@ function renderInteraction({
}
describe('useNodeTransformInteraction', () => {
it('prevents native text selection when starting a node drag', () => {
const { result } = renderInteraction();
const target = gestureTarget();
const event = pointerEvent(target, 1, 0, 0);
act(() => {
result.current.onNodePointerDown(event, child);
});
expect(event.preventDefault).toHaveBeenCalledTimes(1);
});
it('cleans preview transforms for the selected parent when dragging from a child', () => {
const nestedChild = transformedNode('child', {
anchor_min: [0, 0],
anchor_max: [0, 0],
offset_min: [10, 10],
offset_max: [60, 50],
});
const parent = transformedNode(
'parent',
{
anchor_min: [0, 0],
anchor_max: [0, 0],
offset_min: [20, 20],
offset_max: [120, 100],
},
[nestedChild],
);
const tree: UITree = {
src_ui_design: 'page',
root: node('root', [parent]),
};
const onPreviewTransform = vi.fn();
const { result } = renderInteraction({
tree,
keepChildrenUnchanged: true,
onPreviewTransform,
selectedNodeId: 'parent',
});
const target = gestureTarget();
act(() => {
result.current.onNodePointerDown(
pointerEvent(target, 1, 0, 0),
nestedChild,
);
result.current.onNodePointerMove(pointerEvent(target, 1, 10, 5));
result.current.onNodePointerCancel(pointerEvent(target, 1, 10, 5));
});
expect(onPreviewTransform).toHaveBeenCalledWith('parent', null);
expect(onPreviewTransform).toHaveBeenCalledWith('child', null);
});
it('previews stable child page rectangles when the parent moves', () => {
const nestedChild = transformedNode('child', {
anchor_min: [0, 0],