refactor(store): connection data
This commit is contained in:
@@ -43,7 +43,7 @@ const ConnectionLine = ({
|
||||
useCallback(
|
||||
(s: ReactFlowStore) => ({
|
||||
fromNode: s.nodeInternals.get(nodeId),
|
||||
handleId: s.connectionHandleId,
|
||||
handleId: s.connectionStartHandle?.handleId,
|
||||
toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
|
||||
toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
|
||||
connectionMode: s.connectionMode,
|
||||
@@ -133,8 +133,8 @@ type ConnectionLineWrapperProps = {
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
nodeId: s.connectionNodeId,
|
||||
handleType: s.connectionHandleType,
|
||||
nodeId: s.connectionStartHandle?.nodeId,
|
||||
handleType: s.connectionStartHandle?.type,
|
||||
nodesConnectable: s.nodesConnectable,
|
||||
connectionStatus: s.connectionStatus,
|
||||
width: s.width,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { memo, useState, useMemo, useRef, type ComponentType, type KeyboardEvent } from 'react';
|
||||
import cc from 'classcat';
|
||||
import { getMarkerId, elementSelectionKeys } from '@reactflow/utils';
|
||||
import { getMarkerId, elementSelectionKeys, XYHandle } from '@reactflow/utils';
|
||||
import type { Connection } from '@reactflow/system';
|
||||
|
||||
import { useStoreApi } from '../../hooks/useStore';
|
||||
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
|
||||
import { handlePointerDown } from '../Handle/handler';
|
||||
import { EdgeAnchor } from './EdgeAnchor';
|
||||
import { getMouseHandler } from './utils';
|
||||
import type { EdgeProps, WrapEdgeProps } from '../../types';
|
||||
@@ -96,7 +95,22 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const { edges, isValidConnection: isValidConnectionStore } = store.getState();
|
||||
const {
|
||||
autoPanOnConnect,
|
||||
domNode,
|
||||
edges,
|
||||
isValidConnection: isValidConnectionStore,
|
||||
connectionMode,
|
||||
connectionRadius,
|
||||
transform,
|
||||
lib,
|
||||
onConnectStart,
|
||||
onConnectEnd,
|
||||
cancelConnection,
|
||||
getNodes,
|
||||
panBy,
|
||||
updateConnection,
|
||||
} = store.getState();
|
||||
const nodeId = isSourceHandle ? target : source;
|
||||
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
||||
const handleType = isSourceHandle ? 'target' : 'source';
|
||||
@@ -104,6 +118,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
|
||||
const isTarget = isSourceHandle;
|
||||
const edge = edges.find((e) => e.id === id)!;
|
||||
const nodes = getNodes();
|
||||
|
||||
setUpdating(true);
|
||||
onEdgeUpdateStart?.(event, edge, handleType);
|
||||
@@ -115,17 +130,26 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
|
||||
const onConnectEdge = (connection: Connection) => onEdgeUpdate?.(edge, connection);
|
||||
|
||||
handlePointerDown({
|
||||
event,
|
||||
XYHandle.onPointerDown(event.nativeEvent, {
|
||||
autoPanOnConnect,
|
||||
connectionMode,
|
||||
connectionRadius,
|
||||
domNode,
|
||||
handleId,
|
||||
nodeId,
|
||||
onConnect: onConnectEdge,
|
||||
nodes,
|
||||
isTarget,
|
||||
getState: store.getState,
|
||||
setState: store.setState,
|
||||
isValidConnection,
|
||||
edgeUpdaterType: handleType,
|
||||
transform,
|
||||
lib,
|
||||
cancelConnection,
|
||||
panBy,
|
||||
isValidConnection,
|
||||
onConnect: onConnectEdge,
|
||||
onConnectStart,
|
||||
onConnectEnd,
|
||||
onEdgeUpdateEnd: _onEdgeUpdateEnd,
|
||||
updateConnection,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
import type { MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
|
||||
import { StoreApi } from 'zustand';
|
||||
import {
|
||||
getHostForElement,
|
||||
calcAutoPan,
|
||||
getEventPosition,
|
||||
pointToRendererPoint,
|
||||
rendererPointToPoint,
|
||||
} from '@reactflow/utils';
|
||||
import type { OnConnect, HandleType, Connection } from '@reactflow/system';
|
||||
|
||||
import {
|
||||
ConnectionHandle,
|
||||
getClosestHandle,
|
||||
getConnectionStatus,
|
||||
getHandleLookup,
|
||||
getHandleType,
|
||||
isValidHandle,
|
||||
resetRecentHandle,
|
||||
ValidConnectionFunc,
|
||||
} from './utils';
|
||||
import type { ReactFlowState } from '../../types';
|
||||
|
||||
export function handlePointerDown({
|
||||
event,
|
||||
handleId,
|
||||
nodeId,
|
||||
onConnect,
|
||||
isTarget,
|
||||
getState,
|
||||
setState,
|
||||
isValidConnection,
|
||||
edgeUpdaterType,
|
||||
onEdgeUpdateEnd,
|
||||
}: {
|
||||
event: ReactMouseEvent | ReactTouchEvent;
|
||||
handleId: string | null;
|
||||
nodeId: string;
|
||||
onConnect: OnConnect;
|
||||
isTarget: boolean;
|
||||
getState: StoreApi<ReactFlowState>['getState'];
|
||||
setState: StoreApi<ReactFlowState>['setState'];
|
||||
isValidConnection: ValidConnectionFunc;
|
||||
edgeUpdaterType?: HandleType;
|
||||
onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void;
|
||||
}): void {
|
||||
// when react-flow is used inside a shadow root we can't use document
|
||||
const doc = getHostForElement(event.target as HTMLElement);
|
||||
const {
|
||||
connectionMode,
|
||||
domNode,
|
||||
autoPanOnConnect,
|
||||
connectionRadius,
|
||||
onConnectStart,
|
||||
panBy,
|
||||
getNodes,
|
||||
cancelConnection,
|
||||
} = getState();
|
||||
let autoPanId = 0;
|
||||
let closestHandle: ConnectionHandle | null;
|
||||
|
||||
const { x, y } = getEventPosition(event.nativeEvent);
|
||||
const clickedHandle = doc?.elementFromPoint(x, y);
|
||||
const handleType = getHandleType(edgeUpdaterType, clickedHandle);
|
||||
const containerBounds = domNode?.getBoundingClientRect();
|
||||
|
||||
if (!containerBounds || !handleType) {
|
||||
return;
|
||||
}
|
||||
|
||||
let prevActiveHandle: Element;
|
||||
let connectionPosition = getEventPosition(event.nativeEvent, containerBounds);
|
||||
let autoPanStarted = false;
|
||||
let connection: Connection | null = null;
|
||||
let isValid = false;
|
||||
let handleDomNode: Element | null = null;
|
||||
|
||||
const handleLookup = getHandleLookup({
|
||||
nodes: getNodes(),
|
||||
nodeId,
|
||||
handleId,
|
||||
handleType,
|
||||
});
|
||||
|
||||
// when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas
|
||||
const autoPan = (): void => {
|
||||
if (!autoPanOnConnect) {
|
||||
return;
|
||||
}
|
||||
const [xMovement, yMovement] = calcAutoPan(connectionPosition, containerBounds);
|
||||
|
||||
panBy({ x: xMovement, y: yMovement });
|
||||
autoPanId = requestAnimationFrame(autoPan);
|
||||
};
|
||||
|
||||
setState({
|
||||
connectionPosition,
|
||||
connectionStatus: null,
|
||||
// connectionNodeId etc will be removed in the next major in favor of connectionStartHandle
|
||||
connectionNodeId: nodeId,
|
||||
connectionHandleId: handleId,
|
||||
connectionHandleType: handleType,
|
||||
connectionStartHandle: {
|
||||
nodeId,
|
||||
handleId,
|
||||
type: handleType,
|
||||
},
|
||||
connectionEndHandle: null,
|
||||
});
|
||||
|
||||
onConnectStart?.(event, { nodeId, handleId, handleType });
|
||||
|
||||
function onPointerMove(event: MouseEvent | TouchEvent) {
|
||||
const { transform } = getState();
|
||||
|
||||
connectionPosition = getEventPosition(event, containerBounds);
|
||||
closestHandle = getClosestHandle(
|
||||
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
|
||||
connectionRadius,
|
||||
handleLookup
|
||||
);
|
||||
|
||||
if (!autoPanStarted) {
|
||||
autoPan();
|
||||
autoPanStarted = true;
|
||||
}
|
||||
|
||||
const result = isValidHandle(
|
||||
event,
|
||||
closestHandle,
|
||||
connectionMode,
|
||||
nodeId,
|
||||
handleId,
|
||||
isTarget ? 'target' : 'source',
|
||||
isValidConnection,
|
||||
doc
|
||||
);
|
||||
|
||||
handleDomNode = result.handleDomNode;
|
||||
connection = result.connection;
|
||||
isValid = result.isValid;
|
||||
|
||||
setState({
|
||||
connectionPosition:
|
||||
closestHandle && isValid
|
||||
? rendererPointToPoint(
|
||||
{
|
||||
x: closestHandle.x,
|
||||
y: closestHandle.y,
|
||||
},
|
||||
transform
|
||||
)
|
||||
: connectionPosition,
|
||||
connectionStatus: getConnectionStatus(!!closestHandle, isValid),
|
||||
connectionEndHandle: result.endHandle,
|
||||
});
|
||||
|
||||
if (!closestHandle && !isValid && !handleDomNode) {
|
||||
return resetRecentHandle(prevActiveHandle);
|
||||
}
|
||||
|
||||
if (connection.source !== connection.target && handleDomNode) {
|
||||
resetRecentHandle(prevActiveHandle);
|
||||
prevActiveHandle = handleDomNode;
|
||||
// @todo: remove the old class names "react-flow__handle-" in the next major version
|
||||
handleDomNode.classList.add('connecting', 'react-flow__handle-connecting');
|
||||
handleDomNode.classList.toggle('valid', isValid);
|
||||
handleDomNode.classList.toggle('react-flow__handle-valid', isValid);
|
||||
}
|
||||
}
|
||||
|
||||
function onPointerUp(event: MouseEvent | TouchEvent) {
|
||||
if ((closestHandle || handleDomNode) && connection && isValid) {
|
||||
onConnect?.(connection);
|
||||
}
|
||||
|
||||
// it's important to get a fresh reference from the store here
|
||||
// in order to get the latest state of onConnectEnd
|
||||
getState().onConnectEnd?.(event);
|
||||
|
||||
if (edgeUpdaterType) {
|
||||
onEdgeUpdateEnd?.(event);
|
||||
}
|
||||
|
||||
resetRecentHandle(prevActiveHandle);
|
||||
cancelConnection();
|
||||
cancelAnimationFrame(autoPanId);
|
||||
autoPanStarted = false;
|
||||
isValid = false;
|
||||
connection = null;
|
||||
handleDomNode = null;
|
||||
|
||||
doc.removeEventListener('mousemove', onPointerMove as EventListener);
|
||||
doc.removeEventListener('mouseup', onPointerUp as EventListener);
|
||||
|
||||
doc.removeEventListener('touchmove', onPointerMove as EventListener);
|
||||
doc.removeEventListener('touchend', onPointerUp as EventListener);
|
||||
}
|
||||
|
||||
doc.addEventListener('mousemove', onPointerMove as EventListener);
|
||||
doc.addEventListener('mouseup', onPointerUp as EventListener);
|
||||
|
||||
doc.addEventListener('touchmove', onPointerMove as EventListener);
|
||||
doc.addEventListener('touchend', onPointerUp as EventListener);
|
||||
}
|
||||
@@ -2,16 +2,12 @@ import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent, TouchE
|
||||
import cc from 'classcat';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { errorMessages, Position, type HandleProps, type Connection, type HandleType } from '@reactflow/system';
|
||||
import { getHostForElement, isMouseEvent } from '@reactflow/utils';
|
||||
import { XYHandle, getHostForElement, isMouseEvent } from '@reactflow/utils';
|
||||
|
||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||
import { useNodeId } from '../../contexts/NodeIdContext';
|
||||
import { handlePointerDown } from './handler';
|
||||
import { addEdge } from '../../utils/';
|
||||
import { type ReactFlowState } from '../../types';
|
||||
import { isValidHandle } from './utils';
|
||||
|
||||
const alwaysValid = () => true;
|
||||
|
||||
export type HandleComponentProps = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'>;
|
||||
|
||||
@@ -95,15 +91,26 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
isConnectableStart &&
|
||||
((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered)
|
||||
) {
|
||||
handlePointerDown({
|
||||
event,
|
||||
const currentStore = store.getState();
|
||||
|
||||
XYHandle.onPointerDown(event.nativeEvent, {
|
||||
autoPanOnConnect: currentStore.autoPanOnConnect,
|
||||
connectionMode: currentStore.connectionMode,
|
||||
connectionRadius: currentStore.connectionRadius,
|
||||
domNode: currentStore.domNode,
|
||||
nodes: currentStore.getNodes(),
|
||||
transform: currentStore.transform,
|
||||
lib: currentStore.lib,
|
||||
isTarget,
|
||||
handleId,
|
||||
nodeId,
|
||||
panBy: currentStore.panBy,
|
||||
cancelConnection: currentStore.cancelConnection,
|
||||
onConnectStart: currentStore.onConnectStart,
|
||||
onConnectEnd: currentStore.onConnectEnd,
|
||||
updateConnection: currentStore.updateConnection,
|
||||
onConnect: onConnectExtended,
|
||||
isTarget,
|
||||
getState: store.getState,
|
||||
setState: store.setState,
|
||||
isValidConnection: isValidConnection || store.getState().isValidConnection || alwaysValid,
|
||||
isValidConnection: isValidConnection || currentStore.isValidConnection,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -121,6 +128,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
connectionClickStartHandle,
|
||||
connectionMode,
|
||||
isValidConnection: isValidConnectionStore,
|
||||
lib,
|
||||
} = store.getState();
|
||||
|
||||
if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) {
|
||||
@@ -128,27 +136,27 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
||||
}
|
||||
|
||||
if (!connectionClickStartHandle) {
|
||||
onClickConnectStart?.(event, { nodeId, handleId, handleType: type });
|
||||
onClickConnectStart?.(event.nativeEvent, { nodeId, handleId, handleType: type });
|
||||
store.setState({ connectionClickStartHandle: { nodeId, type, handleId } });
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = getHostForElement(event.target as HTMLElement);
|
||||
const isValidConnectionHandler = isValidConnection || isValidConnectionStore || alwaysValid;
|
||||
const { connection, isValid } = isValidHandle(
|
||||
event.nativeEvent,
|
||||
{
|
||||
const isValidConnectionHandler = isValidConnection || isValidConnectionStore;
|
||||
const { connection, isValid } = XYHandle.isValid(event.nativeEvent, {
|
||||
handle: {
|
||||
nodeId,
|
||||
id: handleId,
|
||||
type,
|
||||
},
|
||||
connectionMode,
|
||||
connectionClickStartHandle.nodeId,
|
||||
connectionClickStartHandle.handleId || null,
|
||||
connectionClickStartHandle.type,
|
||||
isValidConnectionHandler,
|
||||
doc
|
||||
);
|
||||
fromNodeId: connectionClickStartHandle.nodeId,
|
||||
fromHandleId: connectionClickStartHandle.handleId || null,
|
||||
fromType: connectionClickStartHandle.type,
|
||||
isValidConnection: isValidConnectionHandler,
|
||||
doc,
|
||||
lib,
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
onConnectExtended(connection);
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
import {
|
||||
internalsSymbol,
|
||||
ConnectionMode,
|
||||
ConnectionStatus,
|
||||
type ConnectingHandle,
|
||||
type Connection,
|
||||
type HandleType,
|
||||
type NodeHandleBounds,
|
||||
type XYPosition,
|
||||
} from '@reactflow/system';
|
||||
import { getEventPosition } from '@reactflow/utils';
|
||||
|
||||
import type { Node } from '../../types';
|
||||
|
||||
export type ConnectionHandle = {
|
||||
id: string | null;
|
||||
type: HandleType;
|
||||
nodeId: string;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export type ValidConnectionFunc = (connection: Connection) => boolean;
|
||||
|
||||
// this functions collects all handles and adds an absolute position
|
||||
// so that we can later find the closest handle to the mouse position
|
||||
export function getHandles(
|
||||
node: Node,
|
||||
handleBounds: NodeHandleBounds,
|
||||
type: HandleType,
|
||||
currentHandle: string
|
||||
): ConnectionHandle[] {
|
||||
return (handleBounds[type] || []).reduce<ConnectionHandle[]>((res, h) => {
|
||||
if (`${node.id}-${h.id}-${type}` !== currentHandle) {
|
||||
res.push({
|
||||
id: h.id || null,
|
||||
type,
|
||||
nodeId: node.id,
|
||||
x: (node.positionAbsolute?.x ?? 0) + h.x + h.width / 2,
|
||||
y: (node.positionAbsolute?.y ?? 0) + h.y + h.height / 2,
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function getClosestHandle(
|
||||
pos: XYPosition,
|
||||
connectionRadius: number,
|
||||
handles: ConnectionHandle[]
|
||||
): ConnectionHandle | null {
|
||||
let closestHandles: ConnectionHandle[] = [];
|
||||
let minDistance = Infinity;
|
||||
|
||||
handles.forEach((handle) => {
|
||||
const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2));
|
||||
if (distance <= connectionRadius) {
|
||||
if (distance < minDistance) {
|
||||
closestHandles = [handle];
|
||||
} else if (distance === minDistance) {
|
||||
// when multiple handles are on the same distance we collect all of them
|
||||
closestHandles.push(handle);
|
||||
}
|
||||
minDistance = distance;
|
||||
}
|
||||
});
|
||||
|
||||
if (!closestHandles.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return closestHandles.length === 1
|
||||
? closestHandles[0]
|
||||
: // if multiple handles are layouted on top of each other we take the one with type = target because it's more likely that the user wants to connect to this one
|
||||
closestHandles.find((handle) => handle.type === 'target') || closestHandles[0];
|
||||
}
|
||||
|
||||
type Result = {
|
||||
handleDomNode: Element | null;
|
||||
isValid: boolean;
|
||||
connection: Connection;
|
||||
endHandle: ConnectingHandle | null;
|
||||
};
|
||||
|
||||
const nullConnection: Connection = { source: null, target: null, sourceHandle: null, targetHandle: null };
|
||||
|
||||
// checks if and returns connection in fom of an object { source: 123, target: 312 }
|
||||
export function isValidHandle(
|
||||
event: MouseEvent | TouchEvent,
|
||||
handle: Pick<ConnectionHandle, 'nodeId' | 'id' | 'type'> | null,
|
||||
connectionMode: ConnectionMode,
|
||||
fromNodeId: string,
|
||||
fromHandleId: string | null,
|
||||
fromType: HandleType,
|
||||
isValidConnection: ValidConnectionFunc,
|
||||
doc: Document | ShadowRoot
|
||||
) {
|
||||
const isTarget = fromType === 'target';
|
||||
const handleDomNode = doc.querySelector(
|
||||
`.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
|
||||
);
|
||||
const { x, y } = getEventPosition(event);
|
||||
const handleBelow = doc.elementFromPoint(x, y);
|
||||
// we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
||||
// because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor
|
||||
const handleToCheck = handleBelow?.classList.contains('react-flow__handle') ? handleBelow : handleDomNode;
|
||||
|
||||
const result: Result = {
|
||||
handleDomNode: handleToCheck,
|
||||
isValid: false,
|
||||
connection: nullConnection,
|
||||
endHandle: null,
|
||||
};
|
||||
|
||||
if (handleToCheck) {
|
||||
const handleType = getHandleType(undefined, handleToCheck);
|
||||
const handleNodeId = handleToCheck.getAttribute('data-nodeid');
|
||||
const handleId = handleToCheck.getAttribute('data-handleid');
|
||||
const connectable = handleToCheck.classList.contains('connectable');
|
||||
const connectableEnd = handleToCheck.classList.contains('connectableend');
|
||||
|
||||
const connection: Connection = {
|
||||
source: isTarget ? handleNodeId : fromNodeId,
|
||||
sourceHandle: isTarget ? handleId : fromHandleId,
|
||||
target: isTarget ? fromNodeId : handleNodeId,
|
||||
targetHandle: isTarget ? fromHandleId : handleId,
|
||||
};
|
||||
|
||||
result.connection = connection;
|
||||
|
||||
const isConnectable = connectable && connectableEnd;
|
||||
// in strict mode we don't allow target to target or source to source connections
|
||||
const isValid =
|
||||
isConnectable &&
|
||||
(connectionMode === ConnectionMode.Strict
|
||||
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
|
||||
: handleNodeId !== fromNodeId || handleId !== fromHandleId);
|
||||
|
||||
if (isValid) {
|
||||
result.endHandle = {
|
||||
nodeId: handleNodeId as string,
|
||||
handleId,
|
||||
type: handleType as HandleType,
|
||||
};
|
||||
|
||||
result.isValid = isValidConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
type GetHandleLookupParams = {
|
||||
nodes: Node[];
|
||||
nodeId: string;
|
||||
handleId: string | null;
|
||||
handleType: string;
|
||||
};
|
||||
|
||||
export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHandleLookupParams) {
|
||||
return nodes.reduce<ConnectionHandle[]>((res, node) => {
|
||||
if (node[internalsSymbol]) {
|
||||
const { handleBounds } = node[internalsSymbol];
|
||||
let sourceHandles: ConnectionHandle[] = [];
|
||||
let targetHandles: ConnectionHandle[] = [];
|
||||
|
||||
if (handleBounds) {
|
||||
sourceHandles = getHandles(node, handleBounds, 'source', `${nodeId}-${handleId}-${handleType}`);
|
||||
targetHandles = getHandles(node, handleBounds, 'target', `${nodeId}-${handleId}-${handleType}`);
|
||||
}
|
||||
|
||||
res.push(...sourceHandles, ...targetHandles);
|
||||
}
|
||||
return res;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function getHandleType(
|
||||
edgeUpdaterType: HandleType | undefined,
|
||||
handleDomNode: Element | null
|
||||
): HandleType | null {
|
||||
if (edgeUpdaterType) {
|
||||
return edgeUpdaterType;
|
||||
} else if (handleDomNode?.classList.contains('target')) {
|
||||
return 'target';
|
||||
} else if (handleDomNode?.classList.contains('source')) {
|
||||
return 'source';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resetRecentHandle(handleDomNode: Element): void {
|
||||
handleDomNode?.classList.remove('valid', 'connecting', 'react-flow__handle-valid', 'react-flow__handle-connecting');
|
||||
}
|
||||
|
||||
export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
|
||||
let connectionStatus = null;
|
||||
|
||||
if (isHandleValid) {
|
||||
connectionStatus = 'valid';
|
||||
} else if (isInsideConnectionRadius && !isHandleValid) {
|
||||
connectionStatus = 'invalid';
|
||||
}
|
||||
|
||||
return connectionStatus as ConnectionStatus;
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
import { createStore } from 'zustand';
|
||||
import { clampPosition, getDimensions, fitView, getHandleBounds } from '@reactflow/utils';
|
||||
import {
|
||||
internalsSymbol,
|
||||
type NodeDimensionUpdate,
|
||||
type CoordinateExtent,
|
||||
type NodeDragItem,
|
||||
type XYPosition,
|
||||
} from '@reactflow/system';
|
||||
import { internalsSymbol, type CoordinateExtent } from '@reactflow/system';
|
||||
|
||||
import { applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||
import { createNodeInternals, updateAbsoluteNodePositions, updateNodesAndEdgesSelections } from './utils';
|
||||
@@ -20,7 +14,6 @@ import type {
|
||||
NodeSelectionChange,
|
||||
NodePositionChange,
|
||||
UnselectNodesAndEdgesParams,
|
||||
NodeChange,
|
||||
} from '../types';
|
||||
|
||||
const createRFStore = () =>
|
||||
@@ -48,7 +41,7 @@ const createRFStore = () =>
|
||||
|
||||
set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges });
|
||||
},
|
||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
|
||||
updateNodeDimensions: (updates) => {
|
||||
const {
|
||||
onNodesChange,
|
||||
nodeInternals,
|
||||
@@ -132,7 +125,7 @@ const createRFStore = () =>
|
||||
onNodesChange?.(changes);
|
||||
}
|
||||
},
|
||||
updateNodePositions: (nodeDragItems: NodeDragItem[] | Node[], positionChanged = true, dragging = false) => {
|
||||
updateNodePositions: (nodeDragItems, positionChanged = true, dragging = false) => {
|
||||
const { triggerNodeChanges } = get();
|
||||
|
||||
const changes = nodeDragItems.map((node) => {
|
||||
@@ -153,7 +146,7 @@ const createRFStore = () =>
|
||||
triggerNodeChanges(changes);
|
||||
},
|
||||
|
||||
triggerNodeChanges: (changes: NodeChange[]) => {
|
||||
triggerNodeChanges: (changes) => {
|
||||
const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin, getNodes, elevateNodesOnSelect } = get();
|
||||
|
||||
if (changes?.length) {
|
||||
@@ -167,7 +160,7 @@ const createRFStore = () =>
|
||||
}
|
||||
},
|
||||
|
||||
addSelectedNodes: (selectedNodeIds: string[]) => {
|
||||
addSelectedNodes: (selectedNodeIds) => {
|
||||
const { multiSelectionActive, edges, getNodes } = get();
|
||||
let changedNodes: NodeSelectionChange[];
|
||||
let changedEdges: EdgeSelectionChange[] | null = null;
|
||||
@@ -186,7 +179,7 @@ const createRFStore = () =>
|
||||
set,
|
||||
});
|
||||
},
|
||||
addSelectedEdges: (selectedEdgeIds: string[]) => {
|
||||
addSelectedEdges: (selectedEdgeIds) => {
|
||||
const { multiSelectionActive, edges, getNodes } = get();
|
||||
let changedEdges: EdgeSelectionChange[];
|
||||
let changedNodes: NodeSelectionChange[] | null = null;
|
||||
@@ -225,19 +218,19 @@ const createRFStore = () =>
|
||||
set,
|
||||
});
|
||||
},
|
||||
setMinZoom: (minZoom: number) => {
|
||||
setMinZoom: (minZoom) => {
|
||||
const { panZoom, maxZoom } = get();
|
||||
panZoom?.setScaleExtent([minZoom, maxZoom]);
|
||||
|
||||
set({ minZoom });
|
||||
},
|
||||
setMaxZoom: (maxZoom: number) => {
|
||||
setMaxZoom: (maxZoom) => {
|
||||
const { panZoom, minZoom } = get();
|
||||
panZoom?.setScaleExtent([minZoom, maxZoom]);
|
||||
|
||||
set({ maxZoom });
|
||||
},
|
||||
setTranslateExtent: (translateExtent: CoordinateExtent) => {
|
||||
setTranslateExtent: (translateExtent) => {
|
||||
get().panZoom?.setTranslateExtent(translateExtent);
|
||||
|
||||
set({ translateExtent });
|
||||
@@ -260,7 +253,7 @@ const createRFStore = () =>
|
||||
set,
|
||||
});
|
||||
},
|
||||
setNodeExtent: (nodeExtent: CoordinateExtent) => {
|
||||
setNodeExtent: (nodeExtent) => {
|
||||
const { nodeInternals } = get();
|
||||
|
||||
nodeInternals.forEach((node) => {
|
||||
@@ -272,7 +265,7 @@ const createRFStore = () =>
|
||||
nodeInternals: new Map(nodeInternals),
|
||||
});
|
||||
},
|
||||
panBy: (delta: XYPosition): boolean => {
|
||||
panBy: (delta): boolean => {
|
||||
const { transform, width, height, panZoom, translateExtent } = get();
|
||||
|
||||
if (!panZoom || (!delta.x && !delta.y)) {
|
||||
@@ -300,13 +293,22 @@ const createRFStore = () =>
|
||||
},
|
||||
cancelConnection: () =>
|
||||
set({
|
||||
connectionNodeId: initialState.connectionNodeId,
|
||||
connectionHandleId: initialState.connectionHandleId,
|
||||
connectionHandleType: initialState.connectionHandleType,
|
||||
connectionStatus: initialState.connectionStatus,
|
||||
connectionStartHandle: initialState.connectionStartHandle,
|
||||
connectionEndHandle: initialState.connectionEndHandle,
|
||||
}),
|
||||
updateConnection: (params) => {
|
||||
const { connectionStatus, connectionStartHandle, connectionEndHandle, connectionPosition } = get();
|
||||
|
||||
const currentConnection = {
|
||||
connectionPosition: params.connectionPosition ?? connectionPosition,
|
||||
connectionStatus: params.connectionStatus ?? connectionStatus,
|
||||
connectionStartHandle: params.connectionStartHandle ?? connectionStartHandle,
|
||||
connectionEndHandle: params.connectionEndHandle ?? connectionEndHandle,
|
||||
};
|
||||
|
||||
set(currentConnection);
|
||||
},
|
||||
reset: () => set({ ...initialState }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -22,9 +22,6 @@ const initialState: ReactFlowStore = {
|
||||
nodesSelectionActive: false,
|
||||
userSelectionActive: false,
|
||||
userSelectionRect: null,
|
||||
connectionNodeId: null,
|
||||
connectionHandleId: null,
|
||||
connectionHandleType: 'source',
|
||||
connectionPosition: { x: 0, y: 0 },
|
||||
connectionStatus: null,
|
||||
connectionMode: ConnectionMode.Strict,
|
||||
@@ -61,6 +58,8 @@ const initialState: ReactFlowStore = {
|
||||
connectionRadius: 20,
|
||||
onError: devWarn,
|
||||
isValidConnection: undefined,
|
||||
|
||||
lib: 'react',
|
||||
};
|
||||
|
||||
export default initialState;
|
||||
|
||||
@@ -3,6 +3,8 @@ import type {
|
||||
ConnectionMode,
|
||||
ConnectionLineType,
|
||||
OnConnect,
|
||||
OnConnectStart,
|
||||
OnConnectEnd,
|
||||
CoordinateExtent,
|
||||
KeyCode,
|
||||
PanOnScrollMode,
|
||||
@@ -16,6 +18,7 @@ import type {
|
||||
HandleType,
|
||||
SelectionMode,
|
||||
OnError,
|
||||
IsValidConnection,
|
||||
} from '@reactflow/system';
|
||||
|
||||
import type {
|
||||
@@ -25,8 +28,6 @@ import type {
|
||||
Node,
|
||||
Edge,
|
||||
ConnectionLineComponent,
|
||||
OnConnectStart,
|
||||
OnConnectEnd,
|
||||
OnEdgeUpdateFunc,
|
||||
OnInit,
|
||||
DefaultEdgeOptions,
|
||||
@@ -40,7 +41,6 @@ import type {
|
||||
SelectionDragHandler,
|
||||
EdgeMouseHandler,
|
||||
} from '.';
|
||||
import { ValidConnectionFunc } from '../components/Handle/utils';
|
||||
|
||||
export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
||||
nodes?: Node[];
|
||||
@@ -148,7 +148,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
||||
autoPanOnConnect?: boolean;
|
||||
connectionRadius?: number;
|
||||
onError?: OnError;
|
||||
isValidConnection?: ValidConnectionFunc;
|
||||
isValidConnection?: IsValidConnection;
|
||||
};
|
||||
|
||||
export type ReactFlowRefType = HTMLDivElement;
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type {
|
||||
MouseEvent as ReactMouseEvent,
|
||||
TouchEvent as ReactTouchEvent,
|
||||
ComponentType,
|
||||
MemoExoticComponent,
|
||||
} from 'react';
|
||||
import type { ComponentType, MemoExoticComponent } from 'react';
|
||||
import {
|
||||
FitViewParamsBase,
|
||||
FitViewOptionsBase,
|
||||
NodeProps,
|
||||
OnConnectStartParams,
|
||||
ZoomInOut,
|
||||
ZoomTo,
|
||||
SetViewport,
|
||||
@@ -18,7 +12,6 @@ import {
|
||||
SetCenter,
|
||||
FitBounds,
|
||||
Project,
|
||||
Connection,
|
||||
} from '@reactflow/system';
|
||||
|
||||
import type { NodeChange, EdgeChange, Node, WrapNodeProps, Edge, EdgeProps, WrapEdgeProps, ReactFlowInstance } from '.';
|
||||
@@ -46,16 +39,11 @@ export type OnSelectionChangeParams = {
|
||||
|
||||
export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void;
|
||||
|
||||
export type OnConnectStart = (event: ReactMouseEvent | ReactTouchEvent, params: OnConnectStartParams) => void;
|
||||
export type OnConnectEnd = (event: MouseEvent | TouchEvent) => void;
|
||||
|
||||
export type FitViewParams = FitViewParamsBase<Node>;
|
||||
export type FitViewOptions = FitViewOptionsBase<Node>;
|
||||
export type FitView = (fitViewOptions?: FitViewOptions) => boolean;
|
||||
export type OnInit<NodeData = any, EdgeData = any> = (reactFlowInstance: ReactFlowInstance<NodeData, EdgeData>) => void;
|
||||
|
||||
export type IsValidConnection = (edge: Edge | Connection) => boolean;
|
||||
|
||||
export type ViewportHelperFunctions = {
|
||||
zoomIn: ZoomInOut;
|
||||
zoomOut: ZoomInOut;
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
ConnectionMode,
|
||||
type ConnectionStatus,
|
||||
type CoordinateExtent,
|
||||
type HandleType,
|
||||
type NodeDimensionUpdate,
|
||||
type UpdateNodePositions,
|
||||
type NodeOrigin,
|
||||
@@ -16,11 +15,15 @@ import {
|
||||
type XYPosition,
|
||||
type PanZoomInstance,
|
||||
type PanBy,
|
||||
OnNodeDrag,
|
||||
OnSelectionDrag,
|
||||
OnMoveStart,
|
||||
OnMove,
|
||||
OnMoveEnd,
|
||||
type OnConnectStart,
|
||||
type OnConnectEnd,
|
||||
type OnNodeDrag,
|
||||
type OnSelectionDrag,
|
||||
type OnMoveStart,
|
||||
type OnMove,
|
||||
type OnMoveEnd,
|
||||
type IsValidConnection,
|
||||
type UpdateConnection,
|
||||
} from '@reactflow/system';
|
||||
|
||||
import type {
|
||||
@@ -30,15 +33,12 @@ import type {
|
||||
OnNodesChange,
|
||||
OnEdgesChange,
|
||||
NodeInternals,
|
||||
OnConnectStart,
|
||||
OnConnectEnd,
|
||||
DefaultEdgeOptions,
|
||||
FitViewOptions,
|
||||
OnNodesDelete,
|
||||
OnEdgesDelete,
|
||||
OnSelectionChangeFunc,
|
||||
UnselectNodesAndEdgesParams,
|
||||
IsValidConnection,
|
||||
} from '.';
|
||||
|
||||
export type ReactFlowStore = {
|
||||
@@ -67,9 +67,6 @@ export type ReactFlowStore = {
|
||||
userSelectionActive: boolean;
|
||||
userSelectionRect: SelectionRect | null;
|
||||
|
||||
connectionNodeId: string | null;
|
||||
connectionHandleId: string | null;
|
||||
connectionHandleType: HandleType | null;
|
||||
connectionPosition: XYPosition;
|
||||
connectionStatus: ConnectionStatus | null;
|
||||
connectionMode: ConnectionMode;
|
||||
@@ -135,6 +132,8 @@ export type ReactFlowStore = {
|
||||
connectionRadius: number;
|
||||
|
||||
isValidConnection?: IsValidConnection;
|
||||
|
||||
lib: string;
|
||||
};
|
||||
|
||||
export type ReactFlowActions = {
|
||||
@@ -153,6 +152,8 @@ export type ReactFlowActions = {
|
||||
setTranslateExtent: (translateExtent: CoordinateExtent) => void;
|
||||
setNodeExtent: (nodeExtent: CoordinateExtent) => void;
|
||||
cancelConnection: () => void;
|
||||
// @todo can this be reused by system?
|
||||
updateConnection: UpdateConnection;
|
||||
reset: () => void;
|
||||
triggerNodeChanges: (changes: NodeChange[]) => void;
|
||||
panBy: PanBy;
|
||||
|
||||
Reference in New Issue
Block a user