feat(autoPan): add props, handle scale
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import { StoreApi } from 'zustand';
|
import { StoreApi } from 'zustand';
|
||||||
|
|
||||||
import { clamp, getHostForElement, getVelocity } from '../../utils';
|
import { getHostForElement, getVelocity } from '../../utils';
|
||||||
import { ConnectionMode } from '../../types';
|
import { ConnectionMode } from '../../types';
|
||||||
import type { OnConnect, Connection, HandleType, ReactFlowState, XYPosition } from '../../types';
|
import type { OnConnect, Connection, HandleType, ReactFlowState, XYPosition } from '../../types';
|
||||||
|
|
||||||
@@ -100,13 +100,13 @@ export function handleMouseDown({
|
|||||||
}): void {
|
}): void {
|
||||||
// when react-flow is used inside a shadow root we can't use document
|
// when react-flow is used inside a shadow root we can't use document
|
||||||
const doc = getHostForElement(event.target as HTMLElement);
|
const doc = getHostForElement(event.target as HTMLElement);
|
||||||
const { onConnectStart, connectionMode, domNode } = getState();
|
const { onConnectStart, connectionMode, domNode, autoPanOnConnect } = getState();
|
||||||
let connectionPosition: XYPosition | null = null;
|
let connectionPosition: XYPosition | null = null;
|
||||||
let requestAnimationFrameId = 0;
|
let requestAnimationFrameId = 0;
|
||||||
|
|
||||||
// when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas
|
// when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas
|
||||||
const updateViewport = (): void => {
|
const updateViewport = (): void => {
|
||||||
if (!connectionPosition || !containerBounds) {
|
if (!connectionPosition || !containerBounds || !autoPanOnConnect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +132,7 @@ export function handleMouseDown({
|
|||||||
const handleType = elementEdgeUpdaterType ? elementEdgeUpdaterType : elementBelowIsTarget ? 'target' : 'source';
|
const handleType = elementEdgeUpdaterType ? elementEdgeUpdaterType : elementBelowIsTarget ? 'target' : 'source';
|
||||||
const containerBounds = domNode.getBoundingClientRect();
|
const containerBounds = domNode.getBoundingClientRect();
|
||||||
let recentHoveredHandle: Element;
|
let recentHoveredHandle: Element;
|
||||||
|
|
||||||
connectionPosition = {
|
connectionPosition = {
|
||||||
x: event.clientX - containerBounds.left,
|
x: event.clientX - containerBounds.left,
|
||||||
y: event.clientY - containerBounds.top,
|
y: event.clientY - containerBounds.top,
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ type StoreUpdaterProps = Pick<
|
|||||||
| 'noPanClassName'
|
| 'noPanClassName'
|
||||||
| 'nodeOrigin'
|
| 'nodeOrigin'
|
||||||
| 'elevateNodesOnSelect'
|
| 'elevateNodesOnSelect'
|
||||||
|
| 'autoPanOnConnect'
|
||||||
|
| 'autoPanOnNodeDrag'
|
||||||
> & { rfId: string };
|
> & { rfId: string };
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
@@ -119,6 +121,8 @@ const StoreUpdater = ({
|
|||||||
noPanClassName,
|
noPanClassName,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
rfId,
|
rfId,
|
||||||
|
autoPanOnConnect,
|
||||||
|
autoPanOnNodeDrag,
|
||||||
}: StoreUpdaterProps) => {
|
}: StoreUpdaterProps) => {
|
||||||
const {
|
const {
|
||||||
setNodes,
|
setNodes,
|
||||||
@@ -172,6 +176,8 @@ const StoreUpdater = ({
|
|||||||
useDirectStoreUpdater('noPanClassName', noPanClassName, store.setState);
|
useDirectStoreUpdater('noPanClassName', noPanClassName, store.setState);
|
||||||
useDirectStoreUpdater('nodeOrigin', nodeOrigin, store.setState);
|
useDirectStoreUpdater('nodeOrigin', nodeOrigin, store.setState);
|
||||||
useDirectStoreUpdater('rfId', rfId, store.setState);
|
useDirectStoreUpdater('rfId', rfId, store.setState);
|
||||||
|
useDirectStoreUpdater('autoPanOnConnect', autoPanOnConnect, store.setState);
|
||||||
|
useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState);
|
||||||
|
|
||||||
useStoreUpdater<Node[]>(nodes, setNodes);
|
useStoreUpdater<Node[]>(nodes, setNodes);
|
||||||
useStoreUpdater<Edge[]>(edges, setEdges);
|
useStoreUpdater<Edge[]>(edges, setEdges);
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
elevateNodesOnSelect = true,
|
elevateNodesOnSelect = true,
|
||||||
elevateEdgesOnSelect = false,
|
elevateEdgesOnSelect = false,
|
||||||
disableKeyboardA11y = false,
|
disableKeyboardA11y = false,
|
||||||
|
autoPanOnConnect = true,
|
||||||
|
autoPanOnNodeDrag = true,
|
||||||
style,
|
style,
|
||||||
id,
|
id,
|
||||||
...rest
|
...rest
|
||||||
@@ -287,6 +289,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
noPanClassName={noPanClassName}
|
noPanClassName={noPanClassName}
|
||||||
nodeOrigin={nodeOrigin}
|
nodeOrigin={nodeOrigin}
|
||||||
rfId={rfId}
|
rfId={rfId}
|
||||||
|
autoPanOnConnect={autoPanOnConnect}
|
||||||
|
autoPanOnNodeDrag={autoPanOnNodeDrag}
|
||||||
/>
|
/>
|
||||||
<SelectionListener onSelectionChange={onSelectionChange} />
|
<SelectionListener onSelectionChange={onSelectionChange} />
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -114,8 +114,10 @@ function useDrag({
|
|||||||
const yMovement = getVelocity(centerPosition.current.y, 35, containerBounds.current.height - 35) * 20;
|
const yMovement = getVelocity(centerPosition.current.y, 35, containerBounds.current.height - 35) * 20;
|
||||||
|
|
||||||
if (xMovement !== 0 || yMovement !== 0) {
|
if (xMovement !== 0 || yMovement !== 0) {
|
||||||
lastPos.current.x = (lastPos.current.x ?? 0) + xMovement * -1;
|
const scale = store.getState().transform[2];
|
||||||
lastPos.current.y = (lastPos.current.y ?? 0) + yMovement * -1;
|
|
||||||
|
lastPos.current.x = (lastPos.current.x ?? 0) - xMovement / scale;
|
||||||
|
lastPos.current.y = (lastPos.current.y ?? 0) - yMovement / scale;
|
||||||
|
|
||||||
updateNodes(lastPos.current as XYPosition);
|
updateNodes(lastPos.current as XYPosition);
|
||||||
|
|
||||||
@@ -175,8 +177,9 @@ function useDrag({
|
|||||||
})
|
})
|
||||||
.on('drag', (event: UseDragEvent) => {
|
.on('drag', (event: UseDragEvent) => {
|
||||||
const pointerPos = getPointerPosition(event);
|
const pointerPos = getPointerPosition(event);
|
||||||
|
const { autoPanOnNodeDrag } = store.getState();
|
||||||
|
|
||||||
if (!animationFrameStarted.current) {
|
if (!animationFrameStarted.current && autoPanOnNodeDrag) {
|
||||||
animationFrameStarted.current = true;
|
animationFrameStarted.current = true;
|
||||||
updateViewport();
|
updateViewport();
|
||||||
}
|
}
|
||||||
@@ -199,6 +202,7 @@ function useDrag({
|
|||||||
setDragging(false);
|
setDragging(false);
|
||||||
animationFrameStarted.current = false;
|
animationFrameStarted.current = false;
|
||||||
cancelAnimationFrame(requestAnimationFrameId.current);
|
cancelAnimationFrame(requestAnimationFrameId.current);
|
||||||
|
|
||||||
if (dragItems.current) {
|
if (dragItems.current) {
|
||||||
const { updateNodePositions, nodeInternals, onNodeDragStop, onSelectionDragStop } = store.getState();
|
const { updateNodePositions, nodeInternals, onNodeDragStop, onSelectionDragStop } = store.getState();
|
||||||
const onStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop);
|
const onStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop);
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ const initialState: ReactFlowStore = {
|
|||||||
connectOnClick: true,
|
connectOnClick: true,
|
||||||
|
|
||||||
ariaLiveMessage: '',
|
ariaLiveMessage: '',
|
||||||
|
autoPanOnConnect: true,
|
||||||
|
autoPanOnNodeDrag: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default initialState;
|
export default initialState;
|
||||||
|
|||||||
@@ -139,6 +139,8 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
|||||||
elevateNodesOnSelect?: boolean;
|
elevateNodesOnSelect?: boolean;
|
||||||
elevateEdgesOnSelect?: boolean;
|
elevateEdgesOnSelect?: boolean;
|
||||||
disableKeyboardA11y?: boolean;
|
disableKeyboardA11y?: boolean;
|
||||||
|
autoPanOnNodeDrag?: boolean;
|
||||||
|
autoPanOnConnect?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowRefType = HTMLDivElement;
|
export type ReactFlowRefType = HTMLDivElement;
|
||||||
|
|||||||
@@ -210,6 +210,8 @@ export type ReactFlowStore = {
|
|||||||
onSelectionChange?: OnSelectionChangeFunc;
|
onSelectionChange?: OnSelectionChangeFunc;
|
||||||
|
|
||||||
ariaLiveMessage: string;
|
ariaLiveMessage: string;
|
||||||
|
autoPanOnConnect: boolean;
|
||||||
|
autoPanOnNodeDrag: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowActions = {
|
export type ReactFlowActions = {
|
||||||
|
|||||||
Reference in New Issue
Block a user