diff --git a/packages/core/src/components/Handle/handler.ts b/packages/core/src/components/Handle/handler.ts index 65fc28e5..b8493a8b 100644 --- a/packages/core/src/components/Handle/handler.ts +++ b/packages/core/src/components/Handle/handler.ts @@ -1,7 +1,7 @@ import type { MouseEvent as ReactMouseEvent } from 'react'; import { StoreApi } from 'zustand'; -import { clamp, getHostForElement, getVelocity } from '../../utils'; +import { getHostForElement, getVelocity } from '../../utils'; import { ConnectionMode } from '../../types'; import type { OnConnect, Connection, HandleType, ReactFlowState, XYPosition } from '../../types'; @@ -100,13 +100,13 @@ export function handleMouseDown({ }): void { // when react-flow is used inside a shadow root we can't use document const doc = getHostForElement(event.target as HTMLElement); - const { onConnectStart, connectionMode, domNode } = getState(); + const { onConnectStart, connectionMode, domNode, autoPanOnConnect } = getState(); let connectionPosition: XYPosition | null = null; let requestAnimationFrameId = 0; // when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas const updateViewport = (): void => { - if (!connectionPosition || !containerBounds) { + if (!connectionPosition || !containerBounds || !autoPanOnConnect) { return; } @@ -132,6 +132,7 @@ export function handleMouseDown({ const handleType = elementEdgeUpdaterType ? elementEdgeUpdaterType : elementBelowIsTarget ? 'target' : 'source'; const containerBounds = domNode.getBoundingClientRect(); let recentHoveredHandle: Element; + connectionPosition = { x: event.clientX - containerBounds.left, y: event.clientY - containerBounds.top, diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx index 2c9a3ec6..55af8fb4 100644 --- a/packages/core/src/components/StoreUpdater/index.tsx +++ b/packages/core/src/components/StoreUpdater/index.tsx @@ -45,6 +45,8 @@ type StoreUpdaterProps = Pick< | 'noPanClassName' | 'nodeOrigin' | 'elevateNodesOnSelect' + | 'autoPanOnConnect' + | 'autoPanOnNodeDrag' > & { rfId: string }; const selector = (s: ReactFlowState) => ({ @@ -119,6 +121,8 @@ const StoreUpdater = ({ noPanClassName, nodeOrigin, rfId, + autoPanOnConnect, + autoPanOnNodeDrag, }: StoreUpdaterProps) => { const { setNodes, @@ -172,6 +176,8 @@ const StoreUpdater = ({ useDirectStoreUpdater('noPanClassName', noPanClassName, store.setState); useDirectStoreUpdater('nodeOrigin', nodeOrigin, store.setState); useDirectStoreUpdater('rfId', rfId, store.setState); + useDirectStoreUpdater('autoPanOnConnect', autoPanOnConnect, store.setState); + useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState); useStoreUpdater(nodes, setNodes); useStoreUpdater(edges, setEdges); diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index 0afc251d..c19d6b81 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -160,6 +160,8 @@ const ReactFlow = forwardRef( elevateNodesOnSelect = true, elevateEdgesOnSelect = false, disableKeyboardA11y = false, + autoPanOnConnect = true, + autoPanOnNodeDrag = true, style, id, ...rest @@ -287,6 +289,8 @@ const ReactFlow = forwardRef( noPanClassName={noPanClassName} nodeOrigin={nodeOrigin} rfId={rfId} + autoPanOnConnect={autoPanOnConnect} + autoPanOnNodeDrag={autoPanOnNodeDrag} /> {children} diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts index ca8dc007..1333666d 100644 --- a/packages/core/src/hooks/useDrag/index.ts +++ b/packages/core/src/hooks/useDrag/index.ts @@ -114,8 +114,10 @@ function useDrag({ const yMovement = getVelocity(centerPosition.current.y, 35, containerBounds.current.height - 35) * 20; if (xMovement !== 0 || yMovement !== 0) { - lastPos.current.x = (lastPos.current.x ?? 0) + xMovement * -1; - lastPos.current.y = (lastPos.current.y ?? 0) + yMovement * -1; + const scale = store.getState().transform[2]; + + lastPos.current.x = (lastPos.current.x ?? 0) - xMovement / scale; + lastPos.current.y = (lastPos.current.y ?? 0) - yMovement / scale; updateNodes(lastPos.current as XYPosition); @@ -175,8 +177,9 @@ function useDrag({ }) .on('drag', (event: UseDragEvent) => { const pointerPos = getPointerPosition(event); + const { autoPanOnNodeDrag } = store.getState(); - if (!animationFrameStarted.current) { + if (!animationFrameStarted.current && autoPanOnNodeDrag) { animationFrameStarted.current = true; updateViewport(); } @@ -199,6 +202,7 @@ function useDrag({ setDragging(false); animationFrameStarted.current = false; cancelAnimationFrame(requestAnimationFrameId.current); + if (dragItems.current) { const { updateNodePositions, nodeInternals, onNodeDragStop, onSelectionDragStop } = store.getState(); const onStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop); diff --git a/packages/core/src/store/initialState.ts b/packages/core/src/store/initialState.ts index f8cc369d..91070d06 100644 --- a/packages/core/src/store/initialState.ts +++ b/packages/core/src/store/initialState.ts @@ -56,6 +56,8 @@ const initialState: ReactFlowStore = { connectOnClick: true, ariaLiveMessage: '', + autoPanOnConnect: true, + autoPanOnNodeDrag: true, }; export default initialState; diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts index 0441ecdf..eeac7ca7 100644 --- a/packages/core/src/types/component-props.ts +++ b/packages/core/src/types/component-props.ts @@ -139,6 +139,8 @@ export type ReactFlowProps = HTMLAttributes & { elevateNodesOnSelect?: boolean; elevateEdgesOnSelect?: boolean; disableKeyboardA11y?: boolean; + autoPanOnNodeDrag?: boolean; + autoPanOnConnect?: boolean; }; export type ReactFlowRefType = HTMLDivElement; diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index 6ec5e103..14a68b0d 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -210,6 +210,8 @@ export type ReactFlowStore = { onSelectionChange?: OnSelectionChangeFunc; ariaLiveMessage: string; + autoPanOnConnect: boolean; + autoPanOnNodeDrag: boolean; }; export type ReactFlowActions = {