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 = [