From 0ae06ce5cbe658501c6c8332036c27f7c824d67b Mon Sep 17 00:00:00 2001 From: peterkogo Date: Thu, 10 Oct 2024 17:20:21 +0200 Subject: [PATCH 01/14] implemented more broader tracking of handleConnections --- .../react/src/hooks/useHandleConnections.ts | 6 ++-- .../src/lib/hooks/useHandleConnections.ts | 8 +++-- packages/system/src/utils/store.ts | 36 +++++++++++++------ 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/packages/react/src/hooks/useHandleConnections.ts b/packages/react/src/hooks/useHandleConnections.ts index 20b65a2d..ff20c909 100644 --- a/packages/react/src/hooks/useHandleConnections.ts +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -12,7 +12,7 @@ import { useNodeId } from '../contexts/NodeIdContext'; type useHandleConnectionsParams = { type: HandleType; - id?: string | null; + id?: string; nodeId?: string; onConnect?: (connections: Connection[]) => void; onDisconnect?: (connections: Connection[]) => void; @@ -31,7 +31,7 @@ type useHandleConnectionsParams = { */ export function useHandleConnections({ type, - id = null, + id, nodeId, onConnect, onDisconnect, @@ -42,7 +42,7 @@ export function useHandleConnections({ const prevConnections = useRef | null>(null); const connections = useStore( - (state) => state.connectionLookup.get(`${currentNodeId}-${type}-${id}`), + (state) => state.connectionLookup.get(`${currentNodeId}${type ? (id ? `-${type}-${id}` : `-${type}`) : ''}`), areConnectionMapsEqual ); diff --git a/packages/svelte/src/lib/hooks/useHandleConnections.ts b/packages/svelte/src/lib/hooks/useHandleConnections.ts index 7aec3858..909ecee6 100644 --- a/packages/svelte/src/lib/hooks/useHandleConnections.ts +++ b/packages/svelte/src/lib/hooks/useHandleConnections.ts @@ -7,7 +7,7 @@ import { getContext } from 'svelte'; export type useHandleConnectionsParams = { type: HandleType; nodeId?: string; - id?: string | null; + id?: string; }; const initialConnections: HandleConnection[] = []; @@ -21,7 +21,7 @@ const initialConnections: HandleConnection[] = []; * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type) * @returns an array with connections */ -export function useHandleConnections({ type, nodeId, id = null }: useHandleConnectionsParams) { +export function useHandleConnections({ type, nodeId, id }: useHandleConnectionsParams) { const { edges, connectionLookup } = useStore(); const _nodeId = getContext('svelteflow__node_id'); @@ -32,7 +32,9 @@ export function useHandleConnections({ type, nodeId, id = null }: useHandleConne return derived( [edges, connectionLookup], ([, connectionLookup], set) => { - const nextConnections = connectionLookup.get(`${currentNodeId}-${type}-${id || null}`); + const nextConnections = connectionLookup.get( + `${currentNodeId}${type ? (id ? `-${type}-${id}` : `-${type}`) : ''}` + ); if (!areConnectionMapsEqual(nextConnections, prevConnections)) { prevConnections = nextConnections; diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index ec55759a..d7282e96 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -1,4 +1,4 @@ -import { infiniteExtent } from '..'; +import { HandleConnection, infiniteExtent } from '..'; import { NodeBase, CoordinateExtent, @@ -42,7 +42,7 @@ const adoptUserNodesDefaultOptions = { checkEquality: true, }; -function mergeObjects>(base: T, incoming?: Partial): T { +function mergeObjects>(base: T, incoming?: Partial): T { const result = { ...base }; for (const key in incoming) { if (incoming[key] !== undefined) { @@ -439,22 +439,38 @@ export async function panBy({ return Promise.resolve(transformChanged); } +function addConnectionToLookup( + type: 'source' | 'target', + connection: HandleConnection, + connectionKey: string, + connectionLookup: ConnectionLookup, + nodeId: string, + handleId: string | null +) { + const keyFragments = [nodeId, type, handleId]; + let key = ''; + for (const keyFragment of keyFragments) { + key += keyFragment; + const prevMap = connectionLookup.get(key) || new Map(); + connectionLookup.set(key, prevMap.set(connectionKey, connection)); + key += '-'; + } +} + export function updateConnectionLookup(connectionLookup: ConnectionLookup, edgeLookup: EdgeLookup, edges: EdgeBase[]) { connectionLookup.clear(); edgeLookup.clear(); for (const edge of edges) { - const { source, target, sourceHandle = null, targetHandle = null } = edge; + const { source: sourceNode, target: targetNode, sourceHandle = null, targetHandle = null } = edge; - const sourceKey = `${source}-source-${sourceHandle}`; - const targetKey = `${target}-target-${targetHandle}`; + const connection = { edgeId: edge.id, source: sourceNode, target: targetNode, sourceHandle, targetHandle }; + const sourceKey = `${sourceNode}-${sourceHandle}`; + const targetKey = `${targetNode}-${targetHandle}`; - const prevSource = connectionLookup.get(sourceKey) || new Map(); - const prevTarget = connectionLookup.get(targetKey) || new Map(); - const connection = { edgeId: edge.id, source, target, sourceHandle, targetHandle }; + addConnectionToLookup('source', connection, targetKey, connectionLookup, sourceNode, sourceHandle); + addConnectionToLookup('target', connection, sourceKey, connectionLookup, targetNode, targetHandle); edgeLookup.set(edge.id, edge); - connectionLookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection)); - connectionLookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection)); } } From c622b5a5c266fb295f17b86fe0f9861bc1c4eddd Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 14 Oct 2024 11:16:41 +0200 Subject: [PATCH 02/14] added comments to new connection lookup logic --- packages/system/src/utils/store.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index d7282e96..4ac93c96 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -439,6 +439,16 @@ export async function panBy({ return Promise.resolve(transformChanged); } +/** + * this function adds the connection to the connectionLookup + * at the following keys: nodeId-type-handleId, nodeId-type and nodeId + * @param type type of the connection + * @param connection connection that should be added to the lookup + * @param connectionKey at which key the connection should be added + * @param connectionLookup reference to the connection lookup + * @param nodeId nodeId of the connection + * @param handleId handleId of the conneciton + */ function addConnectionToLookup( type: 'source' | 'target', connection: HandleConnection, @@ -447,12 +457,16 @@ function addConnectionToLookup( nodeId: string, handleId: string | null ) { + // create array of key fragments for easier iteration const keyFragments = [nodeId, type, handleId]; let key = ''; for (const keyFragment of keyFragments) { + // values for each iteration: nodeId, nodeId-type, nodeId-type-handleId key += keyFragment; + // create a new map if the key does not exist and add the connection const prevMap = connectionLookup.get(key) || new Map(); connectionLookup.set(key, prevMap.set(connectionKey, connection)); + // add - as seperator for next iteration key += '-'; } } From e10f53cf898a56f954783d6efcf6977a0d88f4a9 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 14 Oct 2024 11:21:11 +0200 Subject: [PATCH 03/14] added changeset; ; --- .changeset/silly-pots-yell.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/silly-pots-yell.md diff --git a/.changeset/silly-pots-yell.md b/.changeset/silly-pots-yell.md new file mode 100644 index 00000000..b9fc9458 --- /dev/null +++ b/.changeset/silly-pots-yell.md @@ -0,0 +1,7 @@ +--- +'@xyflow/react': minor +'@xyflow/svelte': minor +'@xyflow/system': patch +--- + +Improved tracking of edges connected to a node. The hook useHandleConnection can now not only return all connections to a specific handleId but also to a handleType or even a nodeId. From ed82fd2edac2427afb23910526b33345830f8af3 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 6 Jan 2025 11:19:29 +0100 Subject: [PATCH 04/14] added useNodeConnections --- .../react/src/hooks/useNodeConnections.ts | 69 +++++++++++++++++++ .../src/lib/hooks/useNodeConnections.ts | 62 +++++++++++++++++ packages/system/src/constants.ts | 2 + 3 files changed, 133 insertions(+) create mode 100644 packages/react/src/hooks/useNodeConnections.ts create mode 100644 packages/svelte/src/lib/hooks/useNodeConnections.ts diff --git a/packages/react/src/hooks/useNodeConnections.ts b/packages/react/src/hooks/useNodeConnections.ts new file mode 100644 index 00000000..339de64f --- /dev/null +++ b/packages/react/src/hooks/useNodeConnections.ts @@ -0,0 +1,69 @@ +import { useEffect, useMemo, useRef } from 'react'; +import { + Connection, + HandleConnection, + HandleType, + areConnectionMapsEqual, + handleConnectionChange, + errorMessages, +} from '@xyflow/system'; + +import { useStore } from './useStore'; +import { useNodeId } from '../contexts/NodeIdContext'; + +const error014 = errorMessages['error014'](); + +type UseNodeConnectionsParams = { + type?: HandleType; + handleId?: string; + nodeId?: string; + onConnect?: (connections: Connection[]) => void; + onDisconnect?: (connections: Connection[]) => void; +}; + +/** + * Hook to retrieve all edges connected to a node. Can be filtered by handle type and id. + * + * @public + * @param param.nodeId - node id - optional if called inside a custom node + * @param param.type - filter by handle type 'source' or 'target' + * @param param.handleId - filter by handle id (this is only needed if the node has multiple handles of the same type) + * @param param.onConnect - gets called when a connection is established + * @param param.onDisconnect - gets called when a connection is removed + * @returns an array with connections + */ +export function useHandleConnections({ + type, + handleId, + nodeId, + onConnect, + onDisconnect, +}: UseNodeConnectionsParams): HandleConnection[] { + const _nodeId = useNodeId(); + const currentNodeId = nodeId ?? _nodeId; + + if (!currentNodeId) { + throw new Error(error014); + } + + const prevConnections = useRef | null>(null); + + const connections = useStore( + (state) => + state.connectionLookup.get(`${currentNodeId}${type ? (handleId ? `-${type}-${handleId}` : `-${type}`) : ''}`), + areConnectionMapsEqual + ); + + useEffect(() => { + // @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts + if (prevConnections.current && prevConnections.current !== connections) { + const _connections = connections ?? new Map(); + handleConnectionChange(prevConnections.current, _connections, onDisconnect); + handleConnectionChange(_connections, prevConnections.current, onConnect); + } + + prevConnections.current = connections ?? new Map(); + }, [connections, onConnect, onDisconnect]); + + return useMemo(() => Array.from(connections?.values() ?? []), [connections]); +} diff --git a/packages/svelte/src/lib/hooks/useNodeConnections.ts b/packages/svelte/src/lib/hooks/useNodeConnections.ts new file mode 100644 index 00000000..29ef8f06 --- /dev/null +++ b/packages/svelte/src/lib/hooks/useNodeConnections.ts @@ -0,0 +1,62 @@ +import { derived } from 'svelte/store'; +import { + areConnectionMapsEqual, + errorMessages, + type HandleConnection, + type HandleType +} from '@xyflow/system'; + +import { useStore } from '$lib/store'; +import { getContext } from 'svelte'; + +const error014 = errorMessages['error014'](); + +type UseNodeConnectionsParams = { + type?: HandleType; + handleId?: string; + nodeId?: string; + // TODO: Svelte 5 + // onConnect?: (connections: Connection[]) => void; + // onDisconnect?: (connections: Connection[]) => void; +}; + +const initialConnections: HandleConnection[] = []; + +/** + * Hook to retrieve all edges connected to a node. Can be filtered by handle type and id. + * + * @public + * @param param.nodeId - node id - optional if called inside a custom node + * @param param.type - filter by handle type 'source' or 'target' + * @param param.handleId - filter by handle id (this is only needed if the node has multiple handles of the same type) + * @todo @param param.onConnect - gets called when a connection is established + * @todo @param param.onDisconnect - gets called when a connection is removed + * @returns an array with connections + */ +export function useHandleConnections({ type, nodeId, handleId }: UseNodeConnectionsParams) { + const { edges, connectionLookup } = useStore(); + + const _nodeId = getContext('svelteflow__node_id'); + const currentNodeId = nodeId ?? _nodeId; + + if (!currentNodeId) { + throw new Error(error014); + } + + let prevConnections: Map | undefined = undefined; + + return derived( + [edges, connectionLookup], + ([, connectionLookup], set) => { + const nextConnections = connectionLookup.get( + `${currentNodeId}${type ? (handleId ? `-${type}-${handleId}` : `-${type}`) : ''}` + ); + + if (!areConnectionMapsEqual(nextConnections, prevConnections)) { + prevConnections = nextConnections; + set(Array.from(prevConnections?.values() || [])); + } + }, + initialConnections + ); +} diff --git a/packages/system/src/constants.ts b/packages/system/src/constants.ts index e108b761..921189fa 100644 --- a/packages/system/src/constants.ts +++ b/packages/system/src/constants.ts @@ -24,6 +24,8 @@ export const errorMessages = { `Node with id "${id}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`, error013: (lib: string = 'react') => `It seems that you haven't loaded the styles. Please import '@xyflow/${lib}/dist/style.css' or base.css to make sure everything is working properly.`, + error014: () => + 'useNodeConnections: No node ID found. Call useNodeConnections inside a custom Node or provide a node ID.', }; export const infiniteExtent: CoordinateExtent = [ From dcf6a635958ca9d4a0f4456acc9146983748bc55 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 6 Jan 2025 12:22:28 +0100 Subject: [PATCH 05/14] reverted useHandleConnections --- packages/react/src/hooks/useHandleConnections.ts | 6 +++--- packages/svelte/src/lib/hooks/useHandleConnections.ts | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/react/src/hooks/useHandleConnections.ts b/packages/react/src/hooks/useHandleConnections.ts index ff20c909..20b65a2d 100644 --- a/packages/react/src/hooks/useHandleConnections.ts +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -12,7 +12,7 @@ import { useNodeId } from '../contexts/NodeIdContext'; type useHandleConnectionsParams = { type: HandleType; - id?: string; + id?: string | null; nodeId?: string; onConnect?: (connections: Connection[]) => void; onDisconnect?: (connections: Connection[]) => void; @@ -31,7 +31,7 @@ type useHandleConnectionsParams = { */ export function useHandleConnections({ type, - id, + id = null, nodeId, onConnect, onDisconnect, @@ -42,7 +42,7 @@ export function useHandleConnections({ const prevConnections = useRef | null>(null); const connections = useStore( - (state) => state.connectionLookup.get(`${currentNodeId}${type ? (id ? `-${type}-${id}` : `-${type}`) : ''}`), + (state) => state.connectionLookup.get(`${currentNodeId}-${type}-${id}`), areConnectionMapsEqual ); diff --git a/packages/svelte/src/lib/hooks/useHandleConnections.ts b/packages/svelte/src/lib/hooks/useHandleConnections.ts index 909ecee6..7aec3858 100644 --- a/packages/svelte/src/lib/hooks/useHandleConnections.ts +++ b/packages/svelte/src/lib/hooks/useHandleConnections.ts @@ -7,7 +7,7 @@ import { getContext } from 'svelte'; export type useHandleConnectionsParams = { type: HandleType; nodeId?: string; - id?: string; + id?: string | null; }; const initialConnections: HandleConnection[] = []; @@ -21,7 +21,7 @@ const initialConnections: HandleConnection[] = []; * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type) * @returns an array with connections */ -export function useHandleConnections({ type, nodeId, id }: useHandleConnectionsParams) { +export function useHandleConnections({ type, nodeId, id = null }: useHandleConnectionsParams) { const { edges, connectionLookup } = useStore(); const _nodeId = getContext('svelteflow__node_id'); @@ -32,9 +32,7 @@ export function useHandleConnections({ type, nodeId, id }: useHandleConnectionsP return derived( [edges, connectionLookup], ([, connectionLookup], set) => { - const nextConnections = connectionLookup.get( - `${currentNodeId}${type ? (id ? `-${type}-${id}` : `-${type}`) : ''}` - ); + const nextConnections = connectionLookup.get(`${currentNodeId}-${type}-${id || null}`); if (!areConnectionMapsEqual(nextConnections, prevConnections)) { prevConnections = nextConnections; From e9179017ba1ac133eb6186280f4d7e5b1b0f9cdc Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 6 Jan 2025 12:25:59 +0100 Subject: [PATCH 06/14] update changeset --- .changeset/silly-pots-yell.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/silly-pots-yell.md b/.changeset/silly-pots-yell.md index b9fc9458..73c0609f 100644 --- a/.changeset/silly-pots-yell.md +++ b/.changeset/silly-pots-yell.md @@ -4,4 +4,4 @@ '@xyflow/system': patch --- -Improved tracking of edges connected to a node. The hook useHandleConnection can now not only return all connections to a specific handleId but also to a handleType or even a nodeId. +Add useNodeConnections hook to track all connections to a node. Can be filtered by handleType and handleId. From 086355427157fb6ec1f291bdec8bfae98f546f74 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 7 Jan 2025 10:24:44 +0100 Subject: [PATCH 07/14] fixed naming & exportyts --- packages/react/src/hooks/useNodeConnections.ts | 2 +- packages/react/src/index.ts | 1 + packages/svelte/src/lib/hooks/useNodeConnections.ts | 2 +- packages/svelte/src/lib/index.ts | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react/src/hooks/useNodeConnections.ts b/packages/react/src/hooks/useNodeConnections.ts index 339de64f..02b82cd6 100644 --- a/packages/react/src/hooks/useNodeConnections.ts +++ b/packages/react/src/hooks/useNodeConnections.ts @@ -32,7 +32,7 @@ type UseNodeConnectionsParams = { * @param param.onDisconnect - gets called when a connection is removed * @returns an array with connections */ -export function useHandleConnections({ +export function useNodeConnections({ type, handleId, nodeId, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index cb36e305..4cbb393d 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -24,6 +24,7 @@ export { useOnViewportChange, type UseOnViewportChangeOptions } from './hooks/us export { useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange'; export { useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized'; export { useHandleConnections } from './hooks/useHandleConnections'; +export { useNodeConnections } from './hooks/useNodeConnections'; export { useNodesData } from './hooks/useNodesData'; export { useConnection } from './hooks/useConnection'; export { useInternalNode } from './hooks/useInternalNode'; diff --git a/packages/svelte/src/lib/hooks/useNodeConnections.ts b/packages/svelte/src/lib/hooks/useNodeConnections.ts index 29ef8f06..fc5c9933 100644 --- a/packages/svelte/src/lib/hooks/useNodeConnections.ts +++ b/packages/svelte/src/lib/hooks/useNodeConnections.ts @@ -33,7 +33,7 @@ const initialConnections: HandleConnection[] = []; * @todo @param param.onDisconnect - gets called when a connection is removed * @returns an array with connections */ -export function useHandleConnections({ type, nodeId, handleId }: UseNodeConnectionsParams) { +export function useNodeConnections({ type, nodeId, handleId }: UseNodeConnectionsParams) { const { edges, connectionLookup } = useStore(); const _nodeId = getContext('svelteflow__node_id'); diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index 112af9fa..d224055e 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -31,6 +31,7 @@ export * from '$lib/hooks/useUpdateNodeInternals'; export * from '$lib/hooks/useConnection'; export * from '$lib/hooks/useNodesEdges'; export * from '$lib/hooks/useHandleConnections'; +export * from '$lib/hooks/useNodeConnections'; export * from '$lib/hooks/useNodesData'; export * from '$lib/hooks/useInternalNode'; export { useInitialized, useNodesInitialized } from '$lib/hooks/useInitialized'; From 4a0b5e6c21f057411c6249a9aee519e45515b7b2 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 7 Jan 2025 10:29:22 +0100 Subject: [PATCH 08/14] deprecated useHandleConnections --- packages/react/src/hooks/useHandleConnections.ts | 5 +++++ packages/svelte/src/lib/hooks/useHandleConnections.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/packages/react/src/hooks/useHandleConnections.ts b/packages/react/src/hooks/useHandleConnections.ts index 20b65a2d..6d82403c 100644 --- a/packages/react/src/hooks/useHandleConnections.ts +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -22,6 +22,7 @@ type useHandleConnectionsParams = { * Hook to check if a is connected to another and get the connections. * * @public + * @deprecated Use `useNodeConnections` instead. * @param param.type - handle type 'source' or 'target' * @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type) @@ -36,6 +37,10 @@ export function useHandleConnections({ onConnect, onDisconnect, }: useHandleConnectionsParams): HandleConnection[] { + console.warn( + '[DEPRECATED] `useHandleConnections` is deprecated. Instead use `useNodeConnections` https://reactflow.dev/api-reference/hooks/useNodeConnections' + ); + const _nodeId = useNodeId(); const currentNodeId = nodeId ?? _nodeId; diff --git a/packages/svelte/src/lib/hooks/useHandleConnections.ts b/packages/svelte/src/lib/hooks/useHandleConnections.ts index 7aec3858..be331d8a 100644 --- a/packages/svelte/src/lib/hooks/useHandleConnections.ts +++ b/packages/svelte/src/lib/hooks/useHandleConnections.ts @@ -16,6 +16,7 @@ const initialConnections: HandleConnection[] = []; * Hook to check if a is connected to another and get the connections. * * @public + * @deprecated Use `useNodeConnections` instead. * @param param.nodeId * @param param.type - handle type 'source' or 'target' * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type) @@ -24,6 +25,10 @@ const initialConnections: HandleConnection[] = []; export function useHandleConnections({ type, nodeId, id = null }: useHandleConnectionsParams) { const { edges, connectionLookup } = useStore(); + console.warn( + '[DEPRECATED] `useHandleConnections` is deprecated. Instead use `useNodeConnections` https://svelteflow.dev/api-reference/hooks/useNodeConnections' + ); + const _nodeId = getContext('svelteflow__node_id'); const currentNodeId = nodeId ?? _nodeId; From 4fc8e081287828857e0620ee8473a6e553e3b0da Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 7 Jan 2025 11:57:33 +0100 Subject: [PATCH 09/14] fixed internal examples, made code more understandable --- examples/react/src/App/routes.ts | 8 +++--- .../MultiHandleNode.tsx | 6 ++--- .../SingleHandleNode.tsx | 6 ++--- .../index.tsx | 0 .../src/examples/UseNodesData/ResultNode.tsx | 4 +-- .../examples/UseNodesData/UppercaseNode.tsx | 4 +-- .../examples/usenodesdata/ResultNode.svelte | 4 +-- .../usenodesdata/UppercaseNode.svelte | 4 +-- packages/system/src/utils/store.ts | 26 ++++++++++--------- 9 files changed, 32 insertions(+), 30 deletions(-) rename examples/react/src/examples/{UseHandleConnections => UseNodeConnections}/MultiHandleNode.tsx (87%) rename examples/react/src/examples/{UseHandleConnections => UseNodeConnections}/SingleHandleNode.tsx (86%) rename examples/react/src/examples/{UseHandleConnections => UseNodeConnections}/index.tsx (100%) diff --git a/examples/react/src/App/routes.ts b/examples/react/src/App/routes.ts index 28c9e8cc..cb184b4a 100644 --- a/examples/react/src/App/routes.ts +++ b/examples/react/src/App/routes.ts @@ -49,7 +49,7 @@ import NodeToolbar from '../examples/NodeToolbar'; import UseConnection from '../examples/UseConnection'; import UseNodesInitialized from '../examples/UseNodesInit'; import UseNodesData from '../examples/UseNodesData'; -import UseHandleConnections from '../examples/UseHandleConnections'; +import UseNodeConnections from '../examples/UseNodeConnections'; import AddNodeOnEdgeDrop from '../examples/AddNodeOnEdgeDrop'; import DevTools from '../examples/DevTools'; import Redux from '../examples/Redux'; @@ -313,9 +313,9 @@ const routes: IRoute[] = [ component: UseReactFlow, }, { - name: 'useHandleConnections', - path: 'usehandleconnections', - component: UseHandleConnections, + name: 'useNodeConnections', + path: 'usenodeconnections', + component: UseNodeConnections, }, { name: 'useNodesData', diff --git a/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx b/examples/react/src/examples/UseNodeConnections/MultiHandleNode.tsx similarity index 87% rename from examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx rename to examples/react/src/examples/UseNodeConnections/MultiHandleNode.tsx index 4b8fd2c1..3397242a 100644 --- a/examples/react/src/examples/UseHandleConnections/MultiHandleNode.tsx +++ b/examples/react/src/examples/UseNodeConnections/MultiHandleNode.tsx @@ -1,5 +1,5 @@ import { memo, FC, useEffect, useCallback } from 'react'; -import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleProps } from '@xyflow/react'; +import { Handle, Position, NodeProps, useNodeConnections, Connection, HandleProps } from '@xyflow/react'; function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) { const onConnect = useCallback( @@ -11,9 +11,9 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string (connections: Connection[]) => console.log('onDisconnect handler, node id:', nodeId, connections), [nodeId] ); - const connections = useHandleConnections({ + const connections = useNodeConnections({ type: handleProps.type, - id: handleProps.id, + handleId: handleProps.id, onConnect, onDisconnect, }); diff --git a/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx b/examples/react/src/examples/UseNodeConnections/SingleHandleNode.tsx similarity index 86% rename from examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx rename to examples/react/src/examples/UseNodeConnections/SingleHandleNode.tsx index 1c13dcb0..070bbf2a 100644 --- a/examples/react/src/examples/UseHandleConnections/SingleHandleNode.tsx +++ b/examples/react/src/examples/UseNodeConnections/SingleHandleNode.tsx @@ -1,5 +1,5 @@ import { memo, FC, useEffect, useCallback } from 'react'; -import { Handle, Position, NodeProps, useHandleConnections, Connection, HandleProps } from '@xyflow/react'; +import { Handle, Position, NodeProps, useNodeConnections, Connection, HandleProps } from '@xyflow/react'; function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) { const onConnect = useCallback( @@ -14,9 +14,9 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }, [nodeId] ); - const connections = useHandleConnections({ + const connections = useNodeConnections({ type: handleProps.type, - id: handleProps.id, + handleId: handleProps.id, onConnect, onDisconnect, }); diff --git a/examples/react/src/examples/UseHandleConnections/index.tsx b/examples/react/src/examples/UseNodeConnections/index.tsx similarity index 100% rename from examples/react/src/examples/UseHandleConnections/index.tsx rename to examples/react/src/examples/UseNodeConnections/index.tsx diff --git a/examples/react/src/examples/UseNodesData/ResultNode.tsx b/examples/react/src/examples/UseNodesData/ResultNode.tsx index bf8f7a35..45ec4087 100644 --- a/examples/react/src/examples/UseNodesData/ResultNode.tsx +++ b/examples/react/src/examples/UseNodesData/ResultNode.tsx @@ -1,9 +1,9 @@ import { memo } from 'react'; -import { Handle, Position, useHandleConnections, useNodesData } from '@xyflow/react'; +import { Handle, Position, useNodeConnections, useNodesData } from '@xyflow/react'; import { isTextNode, type MyNode } from '.'; function ResultNode() { - const connections = useHandleConnections({ + const connections = useNodeConnections({ type: 'target', }); const nodesData = useNodesData(connections.map((connection) => connection.source)); diff --git a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx index 70c6079d..a6f0b67b 100644 --- a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx +++ b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx @@ -1,10 +1,10 @@ import { memo, useEffect } from 'react'; -import { Position, NodeProps, useReactFlow, Handle, useHandleConnections, useNodesData } from '@xyflow/react'; +import { Position, NodeProps, useReactFlow, Handle, useNodeConnections, useNodesData } from '@xyflow/react'; import { isTextNode, type TextNode, type MyNode } from '.'; function UppercaseNode({ id }: NodeProps) { const { updateNodeData } = useReactFlow(); - const connections = useHandleConnections({ + const connections = useNodeConnections({ type: 'target', }); const nodesData = useNodesData(connections[0]?.source); diff --git a/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte index 436d2109..9aa05fc2 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/ResultNode.svelte @@ -2,7 +2,7 @@ import { Handle, Position, - useHandleConnections, + useNodeConnections, useNodesData, type NodeProps } from '@xyflow/svelte'; @@ -13,7 +13,7 @@ export let id: $$Props['id']; $$restProps; - const connections = useHandleConnections({ + const connections = useNodeConnections({ nodeId: id, type: 'target' }); diff --git a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte index 55960cb5..5ca38032 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte @@ -2,7 +2,7 @@ import { Handle, Position, - useHandleConnections, + useNodeConnections, useNodesData, useSvelteFlow, type NodeProps @@ -16,7 +16,7 @@ $$restProps; const { updateNodeData } = useSvelteFlow(); - const connections = useHandleConnections({ + const connections = useNodeConnections({ nodeId: id, type: 'target' }); diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 4ac93c96..223772ea 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -457,18 +457,20 @@ function addConnectionToLookup( nodeId: string, handleId: string | null ) { - // create array of key fragments for easier iteration - const keyFragments = [nodeId, type, handleId]; - let key = ''; - for (const keyFragment of keyFragments) { - // values for each iteration: nodeId, nodeId-type, nodeId-type-handleId - key += keyFragment; - // create a new map if the key does not exist and add the connection - const prevMap = connectionLookup.get(key) || new Map(); - connectionLookup.set(key, prevMap.set(connectionKey, connection)); - // add - as seperator for next iteration - key += '-'; - } + // We add the connection to the connectionLookup at the following keys + // 1. nodeId, 2. nodeId-type, 3. nodeId-type-handleId + // If the key already exists, we add the connection to the existing map + let key = nodeId; + const nodeMap = connectionLookup.get(key) || new Map(); + connectionLookup.set(key, nodeMap.set(connectionKey, connection)); + + key = `${nodeId}-${type}`; + const typeMap = connectionLookup.get(key) || new Map(); + connectionLookup.set(key, typeMap.set(connectionKey, connection)); + + key = `${nodeId}-${type}-${handleId}`; + const handleMap = connectionLookup.get(key) || new Map(); + connectionLookup.set(key, handleMap.set(connectionKey, connection)); } export function updateConnectionLookup(connectionLookup: ConnectionLookup, edgeLookup: EdgeLookup, edges: EdgeBase[]) { From e37712bbb99344709c89f440aa0b49749c6cd2ff Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 7 Jan 2025 12:20:54 +0100 Subject: [PATCH 10/14] add NodeConnection --- packages/react/src/hooks/useNodeConnections.ts | 6 +++--- packages/svelte/src/lib/hooks/useNodeConnections.ts | 6 +++--- packages/system/src/types/general.ts | 5 +++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/react/src/hooks/useNodeConnections.ts b/packages/react/src/hooks/useNodeConnections.ts index 02b82cd6..038a70a2 100644 --- a/packages/react/src/hooks/useNodeConnections.ts +++ b/packages/react/src/hooks/useNodeConnections.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo, useRef } from 'react'; import { Connection, - HandleConnection, + NodeConnection, HandleType, areConnectionMapsEqual, handleConnectionChange, @@ -38,7 +38,7 @@ export function useNodeConnections({ nodeId, onConnect, onDisconnect, -}: UseNodeConnectionsParams): HandleConnection[] { +}: UseNodeConnectionsParams): NodeConnection[] { const _nodeId = useNodeId(); const currentNodeId = nodeId ?? _nodeId; @@ -46,7 +46,7 @@ export function useNodeConnections({ throw new Error(error014); } - const prevConnections = useRef | null>(null); + const prevConnections = useRef | null>(null); const connections = useStore( (state) => diff --git a/packages/svelte/src/lib/hooks/useNodeConnections.ts b/packages/svelte/src/lib/hooks/useNodeConnections.ts index fc5c9933..0393db21 100644 --- a/packages/svelte/src/lib/hooks/useNodeConnections.ts +++ b/packages/svelte/src/lib/hooks/useNodeConnections.ts @@ -2,7 +2,7 @@ import { derived } from 'svelte/store'; import { areConnectionMapsEqual, errorMessages, - type HandleConnection, + type NodeConnection, type HandleType } from '@xyflow/system'; @@ -20,7 +20,7 @@ type UseNodeConnectionsParams = { // onDisconnect?: (connections: Connection[]) => void; }; -const initialConnections: HandleConnection[] = []; +const initialConnections: NodeConnection[] = []; /** * Hook to retrieve all edges connected to a node. Can be filtered by handle type and id. @@ -43,7 +43,7 @@ export function useNodeConnections({ type, nodeId, handleId }: UseNodeConnection throw new Error(error014); } - let prevConnections: Map | undefined = undefined; + let prevConnections: Map | undefined = undefined; return derived( [edges, connectionLookup], diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index 98d8afb6..3c6eed52 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -33,10 +33,15 @@ export type Connection = { targetHandle: string | null; }; +// TODO: remove in next version export type HandleConnection = Connection & { edgeId: string; }; +export type NodeConnection = Connection & { + edgeId: string; +}; + export enum ConnectionMode { Strict = 'strict', Loose = 'loose', From b601d385f3a355d28ea7c0c6b1b7807be16c4254 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 7 Jan 2025 13:34:37 +0100 Subject: [PATCH 11/14] chore(useNodeConnections): add default param --- packages/react/src/hooks/useNodeConnections.ts | 2 +- packages/svelte/src/lib/hooks/useNodeConnections.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/hooks/useNodeConnections.ts b/packages/react/src/hooks/useNodeConnections.ts index 038a70a2..b0481851 100644 --- a/packages/react/src/hooks/useNodeConnections.ts +++ b/packages/react/src/hooks/useNodeConnections.ts @@ -38,7 +38,7 @@ export function useNodeConnections({ nodeId, onConnect, onDisconnect, -}: UseNodeConnectionsParams): NodeConnection[] { +}: UseNodeConnectionsParams = {}): NodeConnection[] { const _nodeId = useNodeId(); const currentNodeId = nodeId ?? _nodeId; diff --git a/packages/svelte/src/lib/hooks/useNodeConnections.ts b/packages/svelte/src/lib/hooks/useNodeConnections.ts index 0393db21..fbebdd92 100644 --- a/packages/svelte/src/lib/hooks/useNodeConnections.ts +++ b/packages/svelte/src/lib/hooks/useNodeConnections.ts @@ -33,7 +33,7 @@ const initialConnections: NodeConnection[] = []; * @todo @param param.onDisconnect - gets called when a connection is removed * @returns an array with connections */ -export function useNodeConnections({ type, nodeId, handleId }: UseNodeConnectionsParams) { +export function useNodeConnections({ type, nodeId, handleId }: UseNodeConnectionsParams = {}) { const { edges, connectionLookup } = useStore(); const _nodeId = getContext('svelteflow__node_id'); From b2d1844c0f626862576623063589993aaedaa8b0 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 7 Jan 2025 14:18:05 +0100 Subject: [PATCH 12/14] remove redundant connectionLookup entries --- .../react/src/hooks/useHandleConnections.ts | 4 ++-- packages/react/src/hooks/useReactFlow.ts | 9 +++++++- packages/react/src/types/instance.ts | 21 +++++++++++++++++-- .../src/lib/components/Handle/Handle.svelte | 2 +- .../src/lib/hooks/useHandleConnections.ts | 4 ++-- packages/system/src/utils/store.ts | 8 ++++--- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/packages/react/src/hooks/useHandleConnections.ts b/packages/react/src/hooks/useHandleConnections.ts index 6d82403c..cad87715 100644 --- a/packages/react/src/hooks/useHandleConnections.ts +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -32,7 +32,7 @@ type useHandleConnectionsParams = { */ export function useHandleConnections({ type, - id = null, + id, nodeId, onConnect, onDisconnect, @@ -47,7 +47,7 @@ export function useHandleConnections({ const prevConnections = useRef | null>(null); const connections = useStore( - (state) => state.connectionLookup.get(`${currentNodeId}-${type}-${id}`), + (state) => state.connectionLookup.get(`${currentNodeId}-${type}${id ? `-${id}` : ''}`), areConnectionMapsEqual ); diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index c4d81e2d..ab090820 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -239,7 +239,14 @@ export function useReactFlow + Array.from( + store + .getState() + .connectionLookup.get(`${nodeId}${type ? (handleId ? `-${type}-${handleId}` : `-${type}`) : ''}`) ?.values() ?? [] ), }; diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index fdece4dd..e0a38d67 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import type { HandleConnection, HandleType, Rect, Viewport } from '@xyflow/system'; +import type { HandleConnection, HandleType, NodeConnection, Rect, Viewport } from '@xyflow/system'; import type { Node, Edge, ViewportHelperFunctions, InternalNode } from '.'; export type ReactFlowJsonObject = { @@ -183,7 +183,7 @@ export type GeneralHelpers Rect; /** * Gets all connections for a given handle belonging to a specific node. - * + * @de * @param type - handle type 'source' or 'target' * @param id - the handle id (this is only needed if you have multiple handles of the same type, meaning you have to provide a unique id for each handle) * @param nodeId - the node id the handle belongs to @@ -198,6 +198,23 @@ export type GeneralHelpers HandleConnection[]; + /** + * Gets all connections to a node. Can be filtered by handle type and id. + * @deprecated use `getNodeConnections` instead + * @param type - handle type 'source' or 'target' + * @param handleId - the handle id (this is only needed if you have multiple handles of the same type, meaning you have to provide a unique id for each handle) + * @param nodeId - the node id the handle belongs to + * @returns an array with handle connections + */ + getNodeConnections: ({ + type, + handleId, + nodeId, + }: { + type?: HandleType; + nodeId: string; + handleId?: string | null; + }) => NodeConnection[]; }; export type ReactFlowInstance = GeneralHelpers< diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index 45f70fb2..aa93d868 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -116,7 +116,7 @@ $: if (onconnect || ondisconnect) { // connectionLookup is not reactive, so we use edges to get notified about updates $edges; - connections = $connectionLookup.get(`${nodeId}-${type}-${id || null}`); + connections = $connectionLookup.get(`${nodeId}-${type}${id ? `-${id}` : ''}`); } $: { diff --git a/packages/svelte/src/lib/hooks/useHandleConnections.ts b/packages/svelte/src/lib/hooks/useHandleConnections.ts index be331d8a..16f84f0c 100644 --- a/packages/svelte/src/lib/hooks/useHandleConnections.ts +++ b/packages/svelte/src/lib/hooks/useHandleConnections.ts @@ -22,7 +22,7 @@ const initialConnections: HandleConnection[] = []; * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type) * @returns an array with connections */ -export function useHandleConnections({ type, nodeId, id = null }: useHandleConnectionsParams) { +export function useHandleConnections({ type, nodeId, id }: useHandleConnectionsParams) { const { edges, connectionLookup } = useStore(); console.warn( @@ -37,7 +37,7 @@ export function useHandleConnections({ type, nodeId, id = null }: useHandleConne return derived( [edges, connectionLookup], ([, connectionLookup], set) => { - const nextConnections = connectionLookup.get(`${currentNodeId}-${type}-${id || null}`); + const nextConnections = connectionLookup.get(`${currentNodeId}-${type}${id ? `-${id}` : ''}`); if (!areConnectionMapsEqual(nextConnections, prevConnections)) { prevConnections = nextConnections; diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 223772ea..bccb115d 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -468,9 +468,11 @@ function addConnectionToLookup( const typeMap = connectionLookup.get(key) || new Map(); connectionLookup.set(key, typeMap.set(connectionKey, connection)); - key = `${nodeId}-${type}-${handleId}`; - const handleMap = connectionLookup.get(key) || new Map(); - connectionLookup.set(key, handleMap.set(connectionKey, connection)); + if (handleId) { + key = `${nodeId}-${type}-${handleId}`; + const handleMap = connectionLookup.get(key) || new Map(); + connectionLookup.set(key, handleMap.set(connectionKey, connection)); + } } export function updateConnectionLookup(connectionLookup: ConnectionLookup, edgeLookup: EdgeLookup, edges: EdgeBase[]) { From 3a5006e311526542bbf81751373973915a8a9aea Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 7 Jan 2025 14:30:07 +0100 Subject: [PATCH 13/14] type --- packages/react/src/types/instance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index e0a38d67..992655a4 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -183,7 +183,7 @@ export type GeneralHelpers Rect; /** * Gets all connections for a given handle belonging to a specific node. - * @de + * @deprecated * @param type - handle type 'source' or 'target' * @param id - the handle id (this is only needed if you have multiple handles of the same type, meaning you have to provide a unique id for each handle) * @param nodeId - the node id the handle belongs to From 9b159d505f963b5b2eeff1186e3e2aeaaff2203b Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Tue, 7 Jan 2025 15:17:39 +0100 Subject: [PATCH 14/14] chore(changesets): do patch release for svelte instead of minor --- .changeset/silly-pots-yell.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/silly-pots-yell.md b/.changeset/silly-pots-yell.md index 73c0609f..71848488 100644 --- a/.changeset/silly-pots-yell.md +++ b/.changeset/silly-pots-yell.md @@ -1,6 +1,6 @@ --- '@xyflow/react': minor -'@xyflow/svelte': minor +'@xyflow/svelte': patch '@xyflow/system': patch ---