diff --git a/packages/react/src/components/ConnectionLine/index.tsx b/packages/react/src/components/ConnectionLine/index.tsx
index 33b1ca50..56acd9ac 100644
--- a/packages/react/src/components/ConnectionLine/index.tsx
+++ b/packages/react/src/components/ConnectionLine/index.tsx
@@ -7,7 +7,6 @@ import {
ConnectionMode,
getBezierPath,
getSmoothStepPath,
- type ConnectionStatus,
type HandleType,
} from '@xyflow/system';
@@ -15,13 +14,21 @@ import { useStore } from '../../hooks/useStore';
import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge';
import type { ConnectionLineComponent, ReactFlowState, ReactFlowStore } from '../../types';
+function getConnectionStatus(isValid: boolean | null) {
+ if (isValid === null) {
+ return null;
+ }
+
+ return isValid ? 'valid' : 'invalid';
+}
+
type ConnectionLineProps = {
nodeId: string;
handleType: HandleType;
type: ConnectionLineType;
style?: CSSProperties;
CustomComponent?: ConnectionLineComponent;
- connectionStatus: ConnectionStatus | null;
+ isValid: boolean | null;
};
const oppositePosition = {
@@ -37,18 +44,17 @@ const ConnectionLine = ({
style,
type = ConnectionLineType.Bezier,
CustomComponent,
- connectionStatus,
+ isValid,
}: ConnectionLineProps) => {
- const { fromNode, handleId, toX, toY, connectionMode, endPosition, isValid } = useStore(
+ const { fromNode, startHandle, endHandle, toX, toY, connectionMode } = useStore(
useCallback(
(s: ReactFlowStore) => ({
fromNode: s.nodeLookup.get(nodeId),
- handleId: s.connectionStartHandle?.handleId,
- toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
- toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
+ startHandle: s.connection.fromHandle,
+ endHandle: s.connection.toHandle,
+ toX: (s.connection.position.x - s.transform[0]) / s.transform[2],
+ toY: (s.connection.position.y - s.transform[1]) / s.transform[2],
connectionMode: s.connectionMode,
- endPosition: s.connectionEndHandle?.position,
- isValid: s.connectionStatus === 'valid',
}),
[nodeId]
),
@@ -66,13 +72,15 @@ const ConnectionLine = ({
return null;
}
+ const handleId = startHandle?.handleId;
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.measured.width ?? 0) / 2;
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.measured.height ?? 0;
const fromX = fromNode.internals.positionAbsolute.x + fromHandleX;
const fromY = fromNode.internals.positionAbsolute.y + fromHandleY;
const fromPosition = fromHandle?.position;
- const toPosition = isValid && endPosition ? endPosition : fromPosition ? oppositePosition[fromPosition] : null;
+ const toPosition =
+ isValid && endHandle?.position ? endHandle.position : fromPosition ? oppositePosition[fromPosition] : null;
if (!fromPosition || !toPosition) {
return null;
@@ -83,7 +91,7 @@ const ConnectionLine = ({
);
}
@@ -136,19 +144,19 @@ type ConnectionLineWrapperProps = {
};
const selector = (s: ReactFlowState) => ({
- nodeId: s.connectionStartHandle?.nodeId,
- handleType: s.connectionStartHandle?.type,
+ nodeId: s.connection.fromHandle?.nodeId,
+ handleType: s.connection.fromHandle?.type,
nodesConnectable: s.nodesConnectable,
- connectionStatus: s.connectionStatus,
+ isValid: s.connection.isValid,
width: s.width,
height: s.height,
});
export function ConnectionLineWrapper({ containerStyle, style, type, component }: ConnectionLineWrapperProps) {
- const { nodeId, handleType, nodesConnectable, width, height, connectionStatus } = useStore(selector, shallow);
- const isValid = !!(nodeId && handleType && width && nodesConnectable);
+ const { nodeId, handleType, nodesConnectable, width, height, isValid } = useStore(selector, shallow);
+ const renderConnectionLine = !!(nodeId && handleType && width && nodesConnectable);
- if (!isValid) {
+ if (!renderConnectionLine) {
return null;
}
@@ -159,14 +167,14 @@ export function ConnectionLineWrapper({ containerStyle, style, type, component }
height={height}
className="react-flow__connectionline react-flow__container"
>
-
+
diff --git a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx
index d1cfd138..236592ee 100644
--- a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx
+++ b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx
@@ -96,7 +96,7 @@ export function EdgeUpdateAnchors({
onReconnectEnd: _onReconnectEnd,
updateConnection,
getTransform: () => store.getState().transform,
- getConnectionStartHandle: () => store.getState().connectionStartHandle,
+ getFromHandle: () => store.getState().connection.fromHandle,
});
};
diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx
index 760733a0..4fe59a96 100644
--- a/packages/react/src/components/Handle/index.tsx
+++ b/packages/react/src/components/Handle/index.tsx
@@ -39,28 +39,23 @@ const selector = (s: ReactFlowState) => ({
const connectingSelector =
(nodeId: string | null, handleId: string | null, type: HandleType) => (state: ReactFlowState) => {
- const {
- connectionStartHandle: startHandle,
- connectionEndHandle: endHandle,
- connectionClickStartHandle: clickHandle,
- connectionMode,
- connectionStatus,
- } = state;
+ const { connectionClickStartHandle: clickHandle, connectionMode, connection } = state;
- const connectingTo = endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type;
+ const { fromHandle, toHandle, isValid } = connection;
+
+ const connectingTo = toHandle?.nodeId === nodeId && toHandle?.handleId === handleId && toHandle?.type === type;
return {
- connectingFrom:
- startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type,
+ connectingFrom: fromHandle?.nodeId === nodeId && fromHandle?.handleId === handleId && fromHandle?.type === type,
connectingTo,
clickConnecting:
clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type,
isPossibleEndHandle:
connectionMode === ConnectionMode.Strict
- ? startHandle?.type !== type
- : nodeId !== startHandle?.nodeId || handleId !== startHandle?.handleId,
- connectionInProcess: !!startHandle,
- valid: connectingTo && connectionStatus === 'valid',
+ ? fromHandle?.type !== type
+ : nodeId !== fromHandle?.nodeId || handleId !== fromHandle?.handleId,
+ connectionInProcess: !!fromHandle,
+ valid: connectingTo && isValid,
};
};
@@ -144,7 +139,7 @@ function HandleComponent(
onConnect: onConnectExtended,
isValidConnection: isValidConnection || currentStore.isValidConnection,
getTransform: () => store.getState().transform,
- getConnectionStartHandle: () => store.getState().connectionStartHandle,
+ getFromHandle: () => store.getState().connection.fromHandle,
});
}
diff --git a/packages/react/src/hooks/useConnection.ts b/packages/react/src/hooks/useConnection.ts
index 9b458e87..d8f1bae4 100644
--- a/packages/react/src/hooks/useConnection.ts
+++ b/packages/react/src/hooks/useConnection.ts
@@ -2,33 +2,19 @@ import { shallow } from 'zustand/shallow';
import { useStore } from './useStore';
import type { ReactFlowStore } from '../types/store';
+import { ConnectionState } from '@xyflow/system';
const selector = (s: ReactFlowStore) => ({
- startHandle: s.connectionStartHandle,
- endHandle: s.connectionEndHandle,
- status: s.connectionStatus,
- position: s.connectionStartHandle ? s.connectionPosition : null,
+ ...s.connection,
+ inProgress: s.connection.fromHandle !== null,
});
-type UseConnectionResult = {
- /** The start handle where the user interaction started or null */
- startHandle: ReactFlowStore['connectionStartHandle'];
- /** The target handle that's inside the connection radius or null */
- endHandle: ReactFlowStore['connectionEndHandle'];
- /** The current connection status 'valid', 'invalid' or null*/
- status: ReactFlowStore['connectionStatus'];
- /** The current connection position or null */
- position: ReactFlowStore['connectionPosition'] | null;
-};
-
/**
* Hook for accessing the ongoing connection.
*
* @public
* @returns ongoing connection
*/
-export function useConnection(): UseConnectionResult {
- const ongoingConnection = useStore(selector, shallow);
-
- return ongoingConnection;
+export function useConnection(): ConnectionState & { inProgress: boolean } {
+ return useStore(selector, shallow);
}
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index e00cd0d7..e15d9ca2 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -49,7 +49,6 @@ export {
type OnMoveStart,
type OnMoveEnd,
type Connection,
- type ConnectionStatus,
ConnectionMode,
type OnConnectStartParams,
type OnConnectStart,
diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts
index ccc522d4..5ccd7070 100644
--- a/packages/react/src/store/index.ts
+++ b/packages/react/src/store/index.ts
@@ -309,18 +309,25 @@ const createStore = ({
options
);
},
- cancelConnection: () =>
+ cancelConnection: () => {
+ const { connection } = get();
set({
- connectionStatus: null,
- connectionStartHandle: null,
- connectionEndHandle: null,
- }),
+ connection: {
+ position: connection.position,
+ fromHandle: null,
+ toHandle: null,
+ isValid: null,
+ },
+ });
+ },
updateConnection: (params) => {
- const { connectionPosition } = get();
+ const { connection } = get();
const currentConnection = {
- ...params,
- connectionPosition: params.connectionPosition ?? connectionPosition,
+ connection: {
+ ...params,
+ position: params.position ?? connection.position,
+ },
};
set(currentConnection);
diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts
index 1b98ee68..495afd3d 100644
--- a/packages/react/src/store/initialState.ts
+++ b/packages/react/src/store/initialState.ts
@@ -79,8 +79,6 @@ const getInitialState = ({
nodesSelectionActive: false,
userSelectionActive: false,
userSelectionRect: null,
- connectionPosition: { x: 0, y: 0 },
- connectionStatus: null,
connectionMode: ConnectionMode.Strict,
domNode: null,
paneDragging: false,
@@ -106,8 +104,12 @@ const getInitialState = ({
multiSelectionActive: false,
- connectionStartHandle: null,
- connectionEndHandle: null,
+ connection: {
+ fromHandle: null,
+ toHandle: null,
+ position: { x: 0, y: 0 },
+ isValid: null,
+ },
connectionClickStartHandle: null,
connectOnClick: true,
diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts
index 23bec358..63a61adf 100644
--- a/packages/react/src/types/edges.ts
+++ b/packages/react/src/types/edges.ts
@@ -11,7 +11,6 @@ import type {
Connection,
ConnectionLineType,
HandleElement,
- ConnectionStatus,
EdgePosition,
StepPathOptions,
OnError,
@@ -202,7 +201,7 @@ export type ConnectionLineComponentProps = {
toY: number;
fromPosition: Position;
toPosition: Position;
- connectionStatus: ConnectionStatus | null;
+ connectionStatus: 'valid' | 'invalid' | null;
};
export type ConnectionLineComponent = ComponentType;
diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts
index 89e33748..ab99ad45 100644
--- a/packages/react/src/types/store.ts
+++ b/packages/react/src/types/store.ts
@@ -1,6 +1,6 @@
import {
ConnectionMode,
- type ConnectionStatus,
+ type ConnectionState,
type CoordinateExtent,
type InternalNodeUpdate,
type UpdateNodePositions,
@@ -12,7 +12,6 @@ import {
type SnapGrid,
type ConnectingHandle,
type Transform,
- type XYPosition,
type PanZoomInstance,
type PanBy,
type OnConnectStart,
@@ -25,9 +24,9 @@ import {
type EdgeLookup,
type ConnectionLookup,
type NodeLookup,
- NodeChange,
- EdgeChange,
- ParentLookup,
+ type NodeChange,
+ type EdgeChange,
+ type ParentLookup,
} from '@xyflow/system';
import type {
@@ -79,9 +78,9 @@ export type ReactFlowStore;
onNodeDrag?: OnNodeDrag;
onNodeDragStop?: OnNodeDrag;
diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte
index 472ca2c2..7ebfeb78 100644
--- a/packages/svelte/src/lib/components/Handle/Handle.svelte
+++ b/packages/svelte/src/lib/components/Handle/Handle.svelte
@@ -103,7 +103,7 @@
$onConnectEndAction?.(event);
},
getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom],
- getConnectionStartHandle: () => $connection.startHandle
+ getFromHandle: () => $connection.startHandle
});
}
}
diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts
index 9bc4ac94..b13a7d90 100644
--- a/packages/svelte/src/lib/index.ts
+++ b/packages/svelte/src/lib/index.ts
@@ -70,7 +70,6 @@ export {
type OnMoveStart,
type OnMoveEnd,
type Connection,
- type ConnectionStatus,
ConnectionMode,
type OnConnectStartParams,
type OnConnectStart,
diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts
index 606475dc..21d5cc0e 100644
--- a/packages/svelte/src/lib/store/index.ts
+++ b/packages/svelte/src/lib/store/index.ts
@@ -15,7 +15,8 @@ import {
type CoordinateExtent,
type UpdateConnection,
errorMessages,
- type NodeOrigin
+ type NodeOrigin,
+ type ConnectionState
} from '@xyflow/system';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } from '$lib/types';
@@ -350,12 +351,17 @@ export function createStore({
// by creating an internal, unexposed store and using a derived store
// we prevent using slow get() calls
const currentConnection = writable(initConnectionUpdateData);
- const updateConnection: UpdateConnection = (newConnection: ConnectionData) => {
- currentConnection.set(newConnection);
+ const updateConnection: UpdateConnection = (newConnection: ConnectionState) => {
+ currentConnection.set({
+ connectionStartHandle: newConnection.fromHandle,
+ connectionEndHandle: newConnection.toHandle,
+ connectionPosition: newConnection.position,
+ connectionStatus: newConnection.isValid ? 'valid' : 'invalid'
+ });
};
function cancelConnection() {
- updateConnection(initConnectionUpdateData);
+ currentConnection.set(initConnectionUpdateData);
}
function reset() {
diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts
index 5d2bb390..fed3a318 100644
--- a/packages/system/src/types/general.ts
+++ b/packages/system/src/types/general.ts
@@ -37,8 +37,6 @@ export type HandleConnection = Connection & {
edgeId: string;
};
-export type ConnectionStatus = 'valid' | 'invalid';
-
export enum ConnectionMode {
Strict = 'strict',
Loose = 'loose',
@@ -134,12 +132,23 @@ export type OnError = (id: string, message: string) => void;
export type UpdateNodePositions = (dragItems: Map, dragging?: boolean) => void;
export type PanBy = (delta: XYPosition) => boolean;
-export type UpdateConnection = (params: {
- connectionPosition: XYPosition | null;
- connectionStatus: ConnectionStatus | null;
- connectionStartHandle: ConnectingHandle | null;
- connectionEndHandle: ConnectingHandle | null;
-}) => void;
+export type NoConnectionInProgress = {
+ position: XYPosition;
+ isValid: null;
+ fromHandle: null;
+ toHandle: null;
+};
+
+export type ConnectionInProgress = {
+ position: XYPosition;
+ isValid: boolean | null;
+ fromHandle: ConnectingHandle;
+ toHandle: ConnectingHandle | null;
+};
+
+export type ConnectionState = ConnectionInProgress | NoConnectionInProgress;
+
+export type UpdateConnection = (params: ConnectionState) => void;
export type ColorModeClass = 'light' | 'dark';
export type ColorMode = ColorModeClass | 'system';
diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts
index c2dca7fe..bd4a866e 100644
--- a/packages/system/src/xyhandle/XYHandle.ts
+++ b/packages/system/src/xyhandle/XYHandle.ts
@@ -16,7 +16,7 @@ import {
Position,
} from '../types';
-import { getClosestHandle, getConnectionStatus, getHandleLookup, getHandleType } from './utils';
+import { getClosestHandle, isConnectionValid, getHandleLookup, getHandleType } from './utils';
export type OnPointerDownParams = {
autoPanOnConnect: boolean;
@@ -39,7 +39,7 @@ export type OnPointerDownParams = {
isValidConnection?: IsValidConnection;
onReconnectEnd?: (evt: MouseEvent | TouchEvent) => void;
getTransform: () => Transform;
- getConnectionStartHandle: () => ConnectingHandle | null;
+ getFromHandle: () => ConnectingHandle | null;
};
export type IsValidParams = {
@@ -63,12 +63,12 @@ type Result = {
handleDomNode: Element | null;
isValid: boolean;
connection: Connection | null;
- endHandle: ConnectingHandle | null;
+ toHandle: ConnectingHandle | null;
};
const alwaysValid = () => true;
-let connectionStartHandle: ConnectingHandle | null = null;
+let fromHandle: ConnectingHandle | null = null;
function onPointerDown(
event: MouseEvent | TouchEvent,
@@ -93,7 +93,7 @@ function onPointerDown(
onReconnectEnd,
updateConnection,
getTransform,
- getConnectionStartHandle,
+ getFromHandle,
}: OnPointerDownParams
) {
// when xyflow is used inside a shadow root we can't use document
@@ -110,7 +110,7 @@ function onPointerDown(
return;
}
- let connectionPosition = getEventPosition(event, containerBounds);
+ let position = getEventPosition(event, containerBounds);
let autoPanStarted = false;
let connection: Connection | null = null;
let isValid = false;
@@ -128,38 +128,39 @@ function onPointerDown(
if (!autoPanOnConnect || !containerBounds) {
return;
}
- const [x, y] = calcAutoPan(connectionPosition, containerBounds);
+ const [x, y] = calcAutoPan(position, containerBounds);
panBy({ x, y });
autoPanId = requestAnimationFrame(autoPan);
}
// Stays the same for all consecutive pointermove events
- connectionStartHandle = {
+ fromHandle = {
nodeId,
handleId,
type: handleType,
- position: (clickedHandle?.getAttribute('data-handlepos') as Position) || Position.Top,
+ position: (clickedHandle?.getAttribute('data-handlepos') as Position) ?? Position.Top,
};
updateConnection({
- connectionPosition,
- connectionStatus: null,
- connectionStartHandle,
- connectionEndHandle: null,
+ position,
+ isValid: null,
+ fromHandle,
+ toHandle: null,
});
onConnectStart?.(event, { nodeId, handleId, handleType });
function onPointerMove(event: MouseEvent | TouchEvent) {
- if (!getConnectionStartHandle()) {
+ if (!getFromHandle() || !fromHandle) {
onPointerUp(event);
+ return;
}
const transform = getTransform();
- connectionPosition = getEventPosition(event, containerBounds);
+ position = getEventPosition(event, containerBounds);
closestHandle = getClosestHandle(
- pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
+ pointToRendererPoint(position, transform, false, [1, 1]),
connectionRadius,
handleLookup
);
@@ -186,8 +187,8 @@ function onPointerDown(
isValid = result.isValid;
updateConnection({
- connectionStartHandle,
- connectionPosition:
+ fromHandle,
+ position:
closestHandle && isValid
? rendererPointToPoint(
{
@@ -196,9 +197,9 @@ function onPointerDown(
},
transform
)
- : connectionPosition,
- connectionStatus: getConnectionStatus(!!closestHandle, isValid),
- connectionEndHandle: result.endHandle,
+ : position,
+ isValid: isConnectionValid(!!closestHandle, isValid),
+ toHandle: result.toHandle,
});
}
@@ -221,7 +222,7 @@ function onPointerDown(
isValid = false;
connection = null;
handleDomNode = null;
- connectionStartHandle = null;
+ fromHandle = null;
doc.removeEventListener('mousemove', onPointerMove as EventListener);
doc.removeEventListener('mouseup', onPointerUp as EventListener);
@@ -267,7 +268,7 @@ function isValidHandle(
handleDomNode: handleToCheck,
isValid: false,
connection: null,
- endHandle: null,
+ toHandle: null,
};
if (handleToCheck) {
@@ -300,7 +301,7 @@ function isValidHandle(
result.isValid = isValid && isValidConnection(connection);
- result.endHandle = {
+ result.toHandle = {
nodeId: handleNodeId as string,
handleId,
type: handleType as HandleType,
diff --git a/packages/system/src/xyhandle/utils.ts b/packages/system/src/xyhandle/utils.ts
index 31dbfe61..b1b868ad 100644
--- a/packages/system/src/xyhandle/utils.ts
+++ b/packages/system/src/xyhandle/utils.ts
@@ -1,6 +1,5 @@
import { getHandlePosition } from '../utils';
import {
- ConnectionStatus,
type HandleType,
type NodeHandleBounds,
type XYPosition,
@@ -105,14 +104,14 @@ export function getHandleType(
return null;
}
-export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
- let connectionStatus = null;
+export function isConnectionValid(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
+ let isValid: boolean | null = null;
if (isHandleValid) {
- connectionStatus = 'valid';
+ isValid = true;
} else if (isInsideConnectionRadius && !isHandleValid) {
- connectionStatus = 'invalid';
+ isValid = false;
}
- return connectionStatus as ConnectionStatus;
+ return isValid;
}