diff --git a/examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte b/examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte
index 0a7f87b5..d315f572 100644
--- a/examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte
+++ b/examples/svelte/src/routes/examples/custom-connection-line/ConnectionLine.svelte
@@ -1,9 +1,24 @@
-{#if $connection.path}
-
+{#if $connection.inProgress}
+
{/if}
diff --git a/packages/react/src/components/ConnectionLine/index.tsx b/packages/react/src/components/ConnectionLine/index.tsx
index 56acd9ac..a5f7e1e2 100644
--- a/packages/react/src/components/ConnectionLine/index.tsx
+++ b/packages/react/src/components/ConnectionLine/index.tsx
@@ -1,140 +1,18 @@
-import { CSSProperties, useCallback } from 'react';
+import { CSSProperties } from 'react';
import { shallow } from 'zustand/shallow';
import cc from 'classcat';
import {
- Position,
ConnectionLineType,
- ConnectionMode,
getBezierPath,
getSmoothStepPath,
- type HandleType,
+ getConnectionStatus,
+ getStraightPath,
} from '@xyflow/system';
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;
- isValid: boolean | null;
-};
-
-const oppositePosition = {
- [Position.Left]: Position.Right,
- [Position.Right]: Position.Left,
- [Position.Top]: Position.Bottom,
- [Position.Bottom]: Position.Top,
-};
-
-const ConnectionLine = ({
- nodeId,
- handleType,
- style,
- type = ConnectionLineType.Bezier,
- CustomComponent,
- isValid,
-}: ConnectionLineProps) => {
- const { fromNode, startHandle, endHandle, toX, toY, connectionMode } = useStore(
- useCallback(
- (s: ReactFlowStore) => ({
- fromNode: s.nodeLookup.get(nodeId),
- 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,
- }),
- [nodeId]
- ),
- shallow
- );
-
- const fromHandleBounds = fromNode?.internals.handleBounds;
- let handleBounds = fromHandleBounds?.[handleType];
-
- if (connectionMode === ConnectionMode.Loose) {
- handleBounds = handleBounds ? handleBounds : fromHandleBounds?.[handleType === 'source' ? 'target' : 'source'];
- }
-
- if (!fromNode || !handleBounds) {
- 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 && endHandle?.position ? endHandle.position : fromPosition ? oppositePosition[fromPosition] : null;
-
- if (!fromPosition || !toPosition) {
- return null;
- }
-
- if (CustomComponent) {
- return (
-
- );
- }
-
- let dAttr = '';
-
- const pathParams = {
- sourceX: fromX,
- sourceY: fromY,
- sourcePosition: fromPosition,
- targetX: toX,
- targetY: toY,
- targetPosition: toPosition,
- };
-
- if (type === ConnectionLineType.Bezier) {
- // we assume the destination position is opposite to the source position
- [dAttr] = getBezierPath(pathParams);
- } else if (type === ConnectionLineType.Step) {
- [dAttr] = getSmoothStepPath({
- ...pathParams,
- borderRadius: 0,
- });
- } else if (type === ConnectionLineType.SmoothStep) {
- [dAttr] = getSmoothStepPath(pathParams);
- } else if (type === ConnectionLineType.SimpleBezier) {
- [dAttr] = getSimpleBezierPath(pathParams);
- } else {
- dAttr = `M${fromX},${fromY} ${toX},${toY}`;
- }
-
- return ;
-};
-
-ConnectionLine.displayName = 'ConnectionLine';
+import type { ConnectionLineComponent, ReactFlowState } from '../../types';
+import { useConnection } from '../../hooks/useConnection';
type ConnectionLineWrapperProps = {
type: ConnectionLineType;
@@ -144,19 +22,18 @@ type ConnectionLineWrapperProps = {
};
const selector = (s: ReactFlowState) => ({
- nodeId: s.connection.fromHandle?.nodeId,
- handleType: s.connection.fromHandle?.type,
nodesConnectable: s.nodesConnectable,
isValid: s.connection.isValid,
+ inProgress: s.connection.inProgress,
width: s.width,
height: s.height,
});
export function ConnectionLineWrapper({ containerStyle, style, type, component }: ConnectionLineWrapperProps) {
- const { nodeId, handleType, nodesConnectable, width, height, isValid } = useStore(selector, shallow);
- const renderConnectionLine = !!(nodeId && handleType && width && nodesConnectable);
+ const { nodesConnectable, width, height, isValid, inProgress } = useStore(selector, shallow);
+ const renderConnection = !!(width && nodesConnectable && inProgress);
- if (!renderConnectionLine) {
+ if (!renderConnection) {
return null;
}
@@ -168,15 +45,78 @@ export function ConnectionLineWrapper({ containerStyle, style, type, component }
className="react-flow__connectionline react-flow__container"
>
-
+
);
}
+
+type ConnectionLineProps = {
+ type: ConnectionLineType;
+ style?: CSSProperties;
+ CustomComponent?: ConnectionLineComponent;
+ isValid: boolean | null;
+};
+
+const ConnectionLine = ({ style, type = ConnectionLineType.Bezier, CustomComponent, isValid }: ConnectionLineProps) => {
+ const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition } = useConnection();
+
+ if (!inProgress) {
+ return;
+ }
+
+ if (CustomComponent) {
+ return (
+
+ );
+ }
+
+ let path = '';
+
+ const pathParams = {
+ sourceX: from.x,
+ sourceY: from.y,
+ sourcePosition: fromPosition,
+ targetX: to.x,
+ targetY: to.y,
+ targetPosition: toPosition,
+ };
+
+ switch (type) {
+ case ConnectionLineType.Bezier:
+ [path] = getBezierPath(pathParams);
+ break;
+ case ConnectionLineType.SimpleBezier:
+ [path] = getSimpleBezierPath(pathParams);
+ break;
+ case ConnectionLineType.Step:
+ [path] = getSmoothStepPath({
+ ...pathParams,
+ borderRadius: 0,
+ });
+ break;
+ case ConnectionLineType.SmoothStep:
+ [path] = getSmoothStepPath(pathParams);
+ break;
+ default:
+ [path] = getStraightPath(pathParams);
+ }
+
+ return ;
+};
+
+ConnectionLine.displayName = 'ConnectionLine';
diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx
index 4fe59a96..02ba9ced 100644
--- a/packages/react/src/components/Handle/index.tsx
+++ b/packages/react/src/components/Handle/index.tsx
@@ -43,17 +43,16 @@ const connectingSelector =
const { fromHandle, toHandle, isValid } = connection;
- const connectingTo = toHandle?.nodeId === nodeId && toHandle?.handleId === handleId && toHandle?.type === type;
+ const connectingTo = toHandle?.nodeId === nodeId && toHandle?.id === handleId && toHandle?.type === type;
return {
- connectingFrom: fromHandle?.nodeId === nodeId && fromHandle?.handleId === handleId && fromHandle?.type === type,
+ connectingFrom: fromHandle?.nodeId === nodeId && fromHandle?.id === handleId && fromHandle?.type === type,
connectingTo,
- clickConnecting:
- clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type,
+ clickConnecting: clickHandle?.nodeId === nodeId && clickHandle?.id === handleId && clickHandle?.type === type,
isPossibleEndHandle:
connectionMode === ConnectionMode.Strict
? fromHandle?.type !== type
- : nodeId !== fromHandle?.nodeId || handleId !== fromHandle?.handleId,
+ : nodeId !== fromHandle?.nodeId || handleId !== fromHandle?.id,
connectionInProcess: !!fromHandle,
valid: connectingTo && isValid,
};
@@ -167,7 +166,7 @@ function HandleComponent(
if (!connectionClickStartHandle) {
onClickConnectStart?.(event.nativeEvent, { nodeId, handleId, handleType: type });
- store.setState({ connectionClickStartHandle: { nodeId, type, handleId } });
+ store.setState({ connectionClickStartHandle: { nodeId, type, id: handleId } });
return;
}
@@ -181,7 +180,7 @@ function HandleComponent(
},
connectionMode,
fromNodeId: connectionClickStartHandle.nodeId,
- fromHandleId: connectionClickStartHandle.handleId || null,
+ fromHandleId: connectionClickStartHandle.id || null,
fromType: connectionClickStartHandle.type,
isValidConnection: isValidConnectionHandler,
flowId,
diff --git a/packages/react/src/hooks/useConnection.ts b/packages/react/src/hooks/useConnection.ts
index d8f1bae4..8a4b377b 100644
--- a/packages/react/src/hooks/useConnection.ts
+++ b/packages/react/src/hooks/useConnection.ts
@@ -2,19 +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) => ({
- ...s.connection,
- inProgress: s.connection.fromHandle !== null,
-});
+import { ConnectionState, pointToRendererPoint } from '@xyflow/system';
+const selector = (s: ReactFlowStore): ConnectionState => {
+ return s.connection.inProgress
+ ? { ...s.connection, to: pointToRendererPoint(s.connection.to, s.transform) }
+ : { ...s.connection };
+};
/**
- * Hook for accessing the ongoing connection.
+ * Hook for accessing the connection state.
*
* @public
- * @returns ongoing connection
+ * @returns ConnectionState
*/
-export function useConnection(): ConnectionState & { inProgress: boolean } {
+export function useConnection(): ConnectionState {
return useStore(selector, shallow);
}
diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts
index 5ccd7070..57beb3f9 100644
--- a/packages/react/src/store/index.ts
+++ b/packages/react/src/store/index.ts
@@ -12,6 +12,7 @@ import {
EdgeSelectionChange,
NodeSelectionChange,
ParentExpandChild,
+ initialConnection,
NodeOrigin,
} from '@xyflow/system';
@@ -310,27 +311,12 @@ const createStore = ({
);
},
cancelConnection: () => {
- const { connection } = get();
set({
- connection: {
- position: connection.position,
- fromHandle: null,
- toHandle: null,
- isValid: null,
- },
+ connection: { ...initialConnection },
});
},
- updateConnection: (params) => {
- const { connection } = get();
-
- const currentConnection = {
- connection: {
- ...params,
- position: params.position ?? connection.position,
- },
- };
-
- set(currentConnection);
+ updateConnection: (connection) => {
+ set({ connection });
},
reset: () => set({ ...getInitialState() }),
diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts
index 495afd3d..7d2789a2 100644
--- a/packages/react/src/store/initialState.ts
+++ b/packages/react/src/store/initialState.ts
@@ -8,6 +8,7 @@ import {
devWarn,
getInternalNodesBounds,
NodeOrigin,
+ initialConnection,
} from '@xyflow/system';
import type { Edge, InternalNode, Node, ReactFlowStore } from '../types';
@@ -104,12 +105,7 @@ const getInitialState = ({
multiSelectionActive: false,
- connection: {
- fromHandle: null,
- toHandle: null,
- position: { x: 0, y: 0 },
- isValid: null,
- },
+ connection: { ...initialConnection },
connectionClickStartHandle: null,
connectOnClick: true,
diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts
index 63a61adf..640366d7 100644
--- a/packages/react/src/types/edges.ts
+++ b/packages/react/src/types/edges.ts
@@ -10,7 +10,7 @@ import type {
HandleType,
Connection,
ConnectionLineType,
- HandleElement,
+ Handle,
EdgePosition,
StepPathOptions,
OnError,
@@ -193,8 +193,8 @@ export type OnReconnect = (oldEdge: EdgeType, newC
export type ConnectionLineComponentProps = {
connectionLineStyle?: CSSProperties;
connectionLineType: ConnectionLineType;
- fromNode?: Node;
- fromHandle?: HandleElement;
+ fromNode: Node;
+ fromHandle: Handle;
fromX: number;
fromY: number;
toX: number;
@@ -202,6 +202,8 @@ export type ConnectionLineComponentProps = {
fromPosition: Position;
toPosition: Position;
connectionStatus: 'valid' | 'invalid' | null;
+ toNode: Node | null;
+ toHandle: Handle | null;
};
export type ConnectionLineComponent = ComponentType;
diff --git a/packages/react/src/types/store.ts b/packages/react/src/types/store.ts
index ab99ad45..7ce5fabf 100644
--- a/packages/react/src/types/store.ts
+++ b/packages/react/src/types/store.ts
@@ -10,7 +10,7 @@ import {
type OnViewportChange,
type SelectionRect,
type SnapGrid,
- type ConnectingHandle,
+ type Handle,
type Transform,
type PanZoomInstance,
type PanBy,
@@ -80,7 +80,7 @@ export type ReactFlowStore & Required>) | null;
snapToGrid: boolean;
snapGrid: SnapGrid;
diff --git a/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte b/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte
index 904a45d3..71e3f2c9 100644
--- a/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte
+++ b/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte
@@ -2,21 +2,59 @@
import cc from 'classcat';
import { useStore } from '$lib/store';
+ import {
+ ConnectionLineType,
+ getBezierPath,
+ getConnectionStatus,
+ getSmoothStepPath,
+ getStraightPath
+ } from '@xyflow/system';
export let containerStyle: string = '';
export let style: string = '';
export let isCustomComponent: boolean = false;
- const { width, height, connection } = useStore();
+ const { width, height, connection, connectionLineType } = useStore();
+
+ let path: string | null = null;
+
+ $: if ($connection.inProgress && !isCustomComponent) {
+ const { from, to, fromPosition, toPosition } = $connection;
+ const pathParams = {
+ sourceX: from.x,
+ sourceY: from.y,
+ sourcePosition: fromPosition,
+ targetX: to.x,
+ targetY: to.y,
+ targetPosition: toPosition
+ };
+
+ switch ($connectionLineType) {
+ case ConnectionLineType.Bezier:
+ [path] = getBezierPath(pathParams);
+ break;
+ case ConnectionLineType.Step:
+ [path] = getSmoothStepPath({
+ ...pathParams,
+ borderRadius: 0
+ });
+ break;
+ case ConnectionLineType.SmoothStep:
+ [path] = getSmoothStepPath(pathParams);
+ break;
+ default:
+ [path] = getStraightPath(pathParams);
+ }
+ }
-{#if $connection.path}
+{#if $connection.inProgress}
diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte
index 7ebfeb78..90482aa1 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],
- getFromHandle: () => $connection.startHandle
+ getFromHandle: () => $connection.fromHandle
});
}
}
@@ -128,21 +128,20 @@
prevConnections = connections ?? new Map();
}
- $: connectionInProcess = !!$connection.startHandle;
+ $: connectionInProcess = !!$connection.fromHandle;
$: connectingFrom =
- $connection.startHandle?.nodeId === nodeId &&
- $connection.startHandle?.type === type &&
- $connection.startHandle?.handleId === handleId;
+ $connection.fromHandle?.nodeId === nodeId &&
+ $connection.fromHandle?.type === type &&
+ $connection.fromHandle?.id === handleId;
$: connectingTo =
- $connection.endHandle?.nodeId === nodeId &&
- $connection.endHandle?.type === type &&
- $connection.endHandle?.handleId === handleId;
+ $connection.toHandle?.nodeId === nodeId &&
+ $connection.toHandle?.type === type &&
+ $connection.toHandle?.id === handleId;
$: isPossibleEndHandle =
$connectionMode === ConnectionMode.Strict
- ? $connection.startHandle?.type !== type
- : nodeId !== $connection.startHandle?.nodeId ||
- handleId !== $connection.startHandle?.handleId;
- $: valid = connectingTo && $connection.status === 'valid';
+ ? $connection.fromHandle?.type !== type
+ : nodeId !== $connection.fromHandle?.nodeId || handleId !== $connection.fromHandle?.id;
+ $: valid = connectingTo && $connection.isValid;