From a72798e065e63d600596c1cb237f1ad3eaea4af0 Mon Sep 17 00:00:00 2001 From: Alessandro Date: Thu, 15 Jan 2026 11:48:35 +0100 Subject: [PATCH 01/19] find solution and remove log --- packages/react/src/container/Pane/index.tsx | 42 ++++++++++++++++++++- packages/system/src/xypanzoom/XYPanZoom.ts | 5 ++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx index 32a0e6df37..90863f4637 100644 --- a/packages/react/src/container/Pane/index.tsx +++ b/packages/react/src/container/Pane/index.tsx @@ -9,13 +9,14 @@ import { } from 'react'; import { shallow } from 'zustand/shallow'; import cc from 'classcat'; -import { getNodesInside, getEventPosition, SelectionMode, areSetsEqual } from '@xyflow/system'; +import { getNodesInside, getEventPosition, SelectionMode, areSetsEqual, calcAutoPan, XYPosition } from '@xyflow/system'; import { UserSelection } from '../../components/UserSelection'; import { containerStyle } from '../../styles/utils'; import { useStore, useStoreApi } from '../../hooks/useStore'; import { getSelectionChanges } from '../../utils'; import type { ReactFlowProps, ReactFlowState } from '../../types'; +import { useReactFlow } from '../../hooks/useReactFlow'; type PaneProps = { isSelecting: boolean; @@ -56,6 +57,8 @@ const selector = (s: ReactFlowState) => ({ elementsSelectable: s.elementsSelectable, connectionInProgress: s.connection.inProgress, dragging: s.paneDragging, + panBy: s.panBy, + autoPanSpeed: s.autoPanSpeed, }); export function Pane({ @@ -75,8 +78,12 @@ export function Pane({ onPaneMouseLeave, children, }: PaneProps) { + let autoPanId = useRef(0); const store = useStoreApi(); - const { userSelectionActive, elementsSelectable, dragging, connectionInProgress } = useStore(selector, shallow); + const { userSelectionActive, elementsSelectable, dragging, connectionInProgress, panBy, autoPanSpeed } = useStore( + selector, + shallow + ); const isSelectionEnabled = elementsSelectable && (isSelecting || userSelectionActive); const container = useRef(null); @@ -87,6 +94,14 @@ export function Pane({ // Used to prevent click events when the user lets go of the selectionKey during a selection const selectionInProgress = useRef(false); + // Used for auto pan when approaching the edges of the container + // TODO pass as prop + const autoPanOnConnect = true; + const position = useRef({ x: 0, y: 0 }); + const autoPanStarted = useRef(false); + + const { setViewport, getViewport } = useReactFlow(); + const onClick = (event: ReactMouseEvent) => { // We prevent click events when the user let go of the selectionKey during a selection // We also prevent click events when a connection is in progress @@ -157,6 +172,19 @@ export function Pane({ } }; + function autoPan(): void { + if (!autoPanOnConnect || !containerBounds.current) { + return; + } + console.log('autoPan'); + const [x, y] = calcAutoPan(position.current, containerBounds.current, autoPanSpeed); + + console.log('x', x, 'y', y); + panBy({ x, y }); + + autoPanId.current = requestAnimationFrame(autoPan); + } + const onPointerMove = (event: ReactPointerEvent): void => { const { userSelectionRect, @@ -175,6 +203,7 @@ export function Pane({ } const { x: mouseX, y: mouseY } = getEventPosition(event.nativeEvent, containerBounds.current); + position.current = { x: mouseX, y: mouseY }; const { startX, startY } = userSelectionRect; if (!selectionInProgress.current) { @@ -198,6 +227,11 @@ export function Pane({ height: Math.abs(mouseY - startY), }; + if (!autoPanStarted.current) { + autoPan(); + autoPanStarted.current = true; + } + const prevSelectedNodeIds = selectedNodeIds.current; const prevSelectedEdgeIds = selectedEdgeIds.current; @@ -266,6 +300,10 @@ export function Pane({ nodesSelectionActive: selectedNodeIds.current.size > 0, }); } + + cancelAnimationFrame(autoPanId.current); + autoPanId.current = 0; + autoPanStarted.current = false; }; const draggable = panOnDrag === true || (Array.isArray(panOnDrag) && panOnDrag.includes(0)); diff --git a/packages/system/src/xypanzoom/XYPanZoom.ts b/packages/system/src/xypanzoom/XYPanZoom.ts index ee1a763e5a..3ff408a279 100644 --- a/packages/system/src/xypanzoom/XYPanZoom.ts +++ b/packages/system/src/xypanzoom/XYPanZoom.ts @@ -108,7 +108,7 @@ export function XYPanZoom({ paneClickDistance, selectionOnDrag, }: PanZoomUpdateOptions) { - if (userSelectionActive && !zoomPanValues.isZoomingOrPanning) { + if (!zoomPanValues.isZoomingOrPanning) { destroy(); } @@ -139,7 +139,8 @@ export function XYPanZoom({ d3Selection.on('wheel.zoom', wheelHandler, { passive: false }); - if (!userSelectionActive) { + // Used to be !userSelectionActive + if (true) { // pan zoom start const startHandler = createPanZoomStartHandler({ zoomPanValues, From 6535e5c02001c30c6eef21f751d83e63db94b104 Mon Sep 17 00:00:00 2001 From: Alessandro Date: Thu, 22 Jan 2026 17:39:06 +0100 Subject: [PATCH 02/19] Add prop for autopan on selection --- examples/react/src/examples/Basic/index.tsx | 1 + packages/react/src/container/FlowRenderer/index.tsx | 2 ++ packages/react/src/container/GraphView/index.tsx | 2 ++ packages/react/src/container/Pane/index.tsx | 10 ++++------ packages/react/src/container/ReactFlow/index.tsx | 2 ++ packages/react/src/types/component-props.ts | 6 ++++++ packages/system/src/xypanzoom/XYPanZoom.ts | 2 +- 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/examples/react/src/examples/Basic/index.tsx b/examples/react/src/examples/Basic/index.tsx index a24c908c89..7553c98f7d 100644 --- a/examples/react/src/examples/Basic/index.tsx +++ b/examples/react/src/examples/Basic/index.tsx @@ -162,6 +162,7 @@ const BasicFlow = () => { selectNodesOnDrag={false} elevateEdgesOnSelect elevateNodesOnSelect={false} + panOnSelection={true} nodeDragThreshold={0} > diff --git a/packages/react/src/container/FlowRenderer/index.tsx b/packages/react/src/container/FlowRenderer/index.tsx index 2198ce5eb2..d7145c2f59 100644 --- a/packages/react/src/container/FlowRenderer/index.tsx +++ b/packages/react/src/container/FlowRenderer/index.tsx @@ -61,6 +61,7 @@ function FlowRendererComponent({ panOnScrollMode, zoomOnDoubleClick, panOnDrag: _panOnDrag, + panOnSelection, defaultViewport, translateExtent, minZoom, @@ -118,6 +119,7 @@ function FlowRendererComponent({ onPaneContextMenu={onPaneContextMenu} onPaneScroll={onPaneScroll} panOnDrag={panOnDrag} + panOnSelection={panOnSelection} isSelecting={!!isSelecting} selectionMode={selectionMode} selectionKeyPressed={selectionKeyPressed} diff --git a/packages/react/src/container/GraphView/index.tsx b/packages/react/src/container/GraphView/index.tsx index fd0296f170..b94acc1c31 100644 --- a/packages/react/src/container/GraphView/index.tsx +++ b/packages/react/src/container/GraphView/index.tsx @@ -80,6 +80,7 @@ function GraphViewComponent(false); - // Used for auto pan when approaching the edges of the container - // TODO pass as prop - const autoPanOnConnect = true; + // Used for auto pan when approaching the edges of the container during selection const position = useRef({ x: 0, y: 0 }); const autoPanStarted = useRef(false); @@ -173,13 +173,11 @@ export function Pane({ }; function autoPan(): void { - if (!autoPanOnConnect || !containerBounds.current) { + if (!panOnSelection || !containerBounds.current) { return; } - console.log('autoPan'); const [x, y] = calcAutoPan(position.current, containerBounds.current, autoPanSpeed); - console.log('x', x, 'y', y); panBy({ x, y }); autoPanId.current = requestAnimationFrame(autoPan); diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index b04f295589..7bcb5a4d0e 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -132,6 +132,7 @@ function ReactFlow( disableKeyboardA11y = false, autoPanOnConnect, autoPanOnNodeDrag, + panOnSelection, autoPanSpeed, connectionRadius, isValidConnection, @@ -224,6 +225,7 @@ function ReactFlow( panOnScrollSpeed={panOnScrollSpeed} panOnScrollMode={panOnScrollMode} panOnDrag={panOnDrag} + panOnSelection={panOnSelection} onPaneClick={onPaneClick} onPaneMouseEnter={onPaneMouseEnter} onPaneMouseMove={onPaneMouseMove} diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index effc7ca66c..86cb6ff867 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -634,6 +634,12 @@ export interface ReactFlowProps Date: Mon, 26 Jan 2026 13:37:54 +0100 Subject: [PATCH 03/19] rename prop --- examples/react/src/examples/Basic/index.tsx | 2 +- packages/react/src/container/FlowRenderer/index.tsx | 4 ++-- packages/react/src/container/GraphView/index.tsx | 4 ++-- packages/react/src/container/Pane/index.tsx | 6 +++--- packages/react/src/container/ReactFlow/index.tsx | 4 ++-- packages/react/src/types/component-props.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/react/src/examples/Basic/index.tsx b/examples/react/src/examples/Basic/index.tsx index 7553c98f7d..9c18c31319 100644 --- a/examples/react/src/examples/Basic/index.tsx +++ b/examples/react/src/examples/Basic/index.tsx @@ -162,7 +162,7 @@ const BasicFlow = () => { selectNodesOnDrag={false} elevateEdgesOnSelect elevateNodesOnSelect={false} - panOnSelection={true} + autopanOnSelection={true} nodeDragThreshold={0} > diff --git a/packages/react/src/container/FlowRenderer/index.tsx b/packages/react/src/container/FlowRenderer/index.tsx index d7145c2f59..17dbc01498 100644 --- a/packages/react/src/container/FlowRenderer/index.tsx +++ b/packages/react/src/container/FlowRenderer/index.tsx @@ -61,7 +61,7 @@ function FlowRendererComponent({ panOnScrollMode, zoomOnDoubleClick, panOnDrag: _panOnDrag, - panOnSelection, + autopanOnSelection, defaultViewport, translateExtent, minZoom, @@ -119,7 +119,7 @@ function FlowRendererComponent({ onPaneContextMenu={onPaneContextMenu} onPaneScroll={onPaneScroll} panOnDrag={panOnDrag} - panOnSelection={panOnSelection} + autopanOnSelection={autopanOnSelection} isSelecting={!!isSelecting} selectionMode={selectionMode} selectionKeyPressed={selectionKeyPressed} diff --git a/packages/react/src/container/GraphView/index.tsx b/packages/react/src/container/GraphView/index.tsx index b94acc1c31..9cf777ef72 100644 --- a/packages/react/src/container/GraphView/index.tsx +++ b/packages/react/src/container/GraphView/index.tsx @@ -80,7 +80,7 @@ function GraphViewComponent( disableKeyboardA11y = false, autoPanOnConnect, autoPanOnNodeDrag, - panOnSelection, + autopanOnSelection, autoPanSpeed, connectionRadius, isValidConnection, @@ -225,7 +225,7 @@ function ReactFlow( panOnScrollSpeed={panOnScrollSpeed} panOnScrollMode={panOnScrollMode} panOnDrag={panOnDrag} - panOnSelection={panOnSelection} + autopanOnSelection={autopanOnSelection} onPaneClick={onPaneClick} onPaneMouseEnter={onPaneMouseEnter} onPaneMouseMove={onPaneMouseMove} diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index 86cb6ff867..742f40c3d8 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -639,7 +639,7 @@ export interface ReactFlowProps Date: Mon, 26 Jan 2026 16:22:21 +0100 Subject: [PATCH 04/19] svelte implementation --- .../svelte/src/lib/container/Pane/Pane.svelte | 30 ++++++++++++++++++- .../svelte/src/lib/container/Pane/types.ts | 1 + .../container/SvelteFlow/SvelteFlow.svelte | 2 ++ .../src/lib/container/SvelteFlow/types.ts | 6 ++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/lib/container/Pane/Pane.svelte b/packages/svelte/src/lib/container/Pane/Pane.svelte index b7de8cfac6..be4048ec57 100644 --- a/packages/svelte/src/lib/container/Pane/Pane.svelte +++ b/packages/svelte/src/lib/container/Pane/Pane.svelte @@ -39,7 +39,7 @@