修复预览手势取消后的点击抑制

取消、失焦、树切换和无效几何统一清理点击抑制状态

补充 pointercancel 后点击状态回归测试

同步 UI 编辑器预览交互技术方案
This commit is contained in:
2026-09-03 14:45:58 +08:00
parent d225fdae01
commit 3deebf0e90
3 changed files with 9 additions and 3 deletions
@@ -140,6 +140,7 @@ export function useNodeTransformInteraction({
const gesture = activeGestureRef.current;
if (gesture) releasePointer(gesture.target, gesture.pointerId);
activeGestureRef.current = null;
suppressNextNodeClickRef.current = false;
}, []);
useEffect(() => cancelGesture, [cancelGesture]);
@@ -250,12 +251,14 @@ export function useNodeTransformInteraction({
const gesture = activeGestureRef.current;
if (!gesture || !acceptsGestureEvent(event)) return;
event.stopPropagation();
if (gesture.hasMoved) suppressNextNodeClickRef.current = true;
if (!gesture.hasMoved && isInsidePreview(event, previewRef)) {
const cleanPointerUpInside =
!gesture.hasMoved && isInsidePreview(event, previewRef);
const suppressClick = gesture.hasMoved || cleanPointerUpInside;
if (cleanPointerUpInside) {
canvas.selectNode(gesture.hitNodeId);
suppressNextNodeClickRef.current = true;
}
cancelGesture();
if (suppressClick) suppressNextNodeClickRef.current = true;
},
[acceptsGestureEvent, cancelGesture, canvas, previewRef],
);
@@ -188,6 +188,7 @@ describe('useNodeTransformInteraction', () => {
result.current.onNodePointerCancel(pointerEvent(target, 2, 0, 0));
});
expect(canvasProjection.selectNode).not.toHaveBeenCalled();
expect(result.current.consumeNodeClick()).toBe(false);
});
it('does not select when a clean pointerup lands outside the preview', () => {
@@ -43,4 +43,6 @@ Container 专属数据是互斥 tagged unionHBox/VBox 存 `alignment + separa
Preview 的节点选择采用“松开确认”语义:节点或调整手柄按下时只建立指针手势,不立即改变选择;沿用现有 `2px` 屏幕位移阈值(达到阈值即视为拖动),未达到阈值且在预览区域内松开时才选择按下命中的节点,发生拖动、取消、失焦或在区域外松开均不选择。发生拖动时,Transform 更新作用于按下前已选中的节点;没有当前选择时才回退到按下节点。拖动外层节点时,嵌套 child 产生的后续 click 也不得重新选择;未发生拖动时,嵌套 child 仍按实际命中节点选择。
取消、失焦、树切换或无效几何导致的手势终止会同时清理下一次 click 抑制状态;只有正常 pointerup 才会根据拖动结果抑制后续 click。
预览节点层禁止浏览器原生文字选择;拖动手势开始时阻止默认行为,避免 pointer drag 被浏览器解释为文本选取。