From 004574d4426be9431be1fd37dbbfdceeedd92d41 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:21:05 +0100 Subject: [PATCH] fix: allow drag-to-pan over diagram nodes and compounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously onPointerDown bailed out when the target was inside a node (data-node-id), blocking pan entirely over nodes and compound groups. Now panning always starts, and a didPan ref distinguishes drag from click — node click handlers skip selection when the user was dragging. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/components/ProcessDiagram/ProcessDiagram.tsx | 8 ++++++-- ui/src/components/ProcessDiagram/useZoomPan.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ui/src/components/ProcessDiagram/ProcessDiagram.tsx b/ui/src/components/ProcessDiagram/ProcessDiagram.tsx index 382c22fb..08cd07e1 100644 --- a/ui/src/components/ProcessDiagram/ProcessDiagram.tsx +++ b/ui/src/components/ProcessDiagram/ProcessDiagram.tsx @@ -152,8 +152,12 @@ export function ProcessDiagram({ ); const handleNodeClick = useCallback( - (nodeId: string) => { onNodeSelect?.(nodeId); }, - [onNodeSelect], + (nodeId: string) => { + // Suppress click if the pointer gesture was a drag (pan) + if (zoom.didPan.current) return; + onNodeSelect?.(nodeId); + }, + [onNodeSelect, zoom.didPan], ); const handleNodeDoubleClick = useCallback( diff --git a/ui/src/components/ProcessDiagram/useZoomPan.ts b/ui/src/components/ProcessDiagram/useZoomPan.ts index dcdb9dc5..555e0436 100644 --- a/ui/src/components/ProcessDiagram/useZoomPan.ts +++ b/ui/src/components/ProcessDiagram/useZoomPan.ts @@ -18,6 +18,7 @@ export function useZoomPan() { translateY: 0, }); const isPanning = useRef(false); + const didPan = useRef(false); const panStart = useRef({ x: 0, y: 0 }); const containerRef = useRef(null); const svgRef = useRef(null); @@ -57,8 +58,11 @@ export function useZoomPan() { const onPointerDown = useCallback( (e: React.PointerEvent) => { - if ((e.target as Element).closest('[data-node-id]')) return; + // Always allow drag-to-pan, even over nodes. Click vs drag is + // distinguished by didPan: if the pointer moved, it's a pan and + // node click handlers should be suppressed. isPanning.current = true; + didPan.current = false; panStart.current = { x: e.clientX - state.translateX, y: e.clientY - state.translateY }; (e.currentTarget as SVGSVGElement).setPointerCapture(e.pointerId); }, @@ -68,6 +72,7 @@ export function useZoomPan() { const onPointerMove = useCallback( (e: React.PointerEvent) => { if (!isPanning.current) return; + didPan.current = true; setState(prev => ({ ...prev, translateX: e.clientX - panStart.current.x, @@ -166,6 +171,8 @@ export function useZoomPan() { containerRef, svgRef, transform, + /** True if the last pointer gesture was a drag (not a click). Check in click handlers. */ + didPan, panTo, resetView, onPointerDown,