merge useNodeConnections
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
'@xyflow/react': minor
|
||||||
|
'@xyflow/svelte': patch
|
||||||
|
'@xyflow/system': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add useNodeConnections hook to track all connections to a node. Can be filtered by handleType and handleId.
|
||||||
@@ -49,7 +49,7 @@ import NodeToolbar from '../examples/NodeToolbar';
|
|||||||
import UseConnection from '../examples/UseConnection';
|
import UseConnection from '../examples/UseConnection';
|
||||||
import UseNodesInitialized from '../examples/UseNodesInit';
|
import UseNodesInitialized from '../examples/UseNodesInit';
|
||||||
import UseNodesData from '../examples/UseNodesData';
|
import UseNodesData from '../examples/UseNodesData';
|
||||||
import UseHandleConnections from '../examples/UseHandleConnections';
|
import UseNodeConnections from '../examples/UseNodeConnections';
|
||||||
import AddNodeOnEdgeDrop from '../examples/AddNodeOnEdgeDrop';
|
import AddNodeOnEdgeDrop from '../examples/AddNodeOnEdgeDrop';
|
||||||
import DevTools from '../examples/DevTools';
|
import DevTools from '../examples/DevTools';
|
||||||
import Redux from '../examples/Redux';
|
import Redux from '../examples/Redux';
|
||||||
@@ -313,9 +313,9 @@ const routes: IRoute[] = [
|
|||||||
component: UseReactFlow,
|
component: UseReactFlow,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'useHandleConnections',
|
name: 'useNodeConnections',
|
||||||
path: 'usehandleconnections',
|
path: 'usenodeconnections',
|
||||||
component: UseHandleConnections,
|
component: UseNodeConnections,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'useNodesData',
|
name: 'useNodesData',
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
import { memo, FC, useEffect, useCallback } from 'react';
|
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 }) {
|
function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) {
|
||||||
const onConnect = useCallback(
|
const onConnect = useCallback(
|
||||||
@@ -11,9 +11,9 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string
|
|||||||
(connections: Connection[]) => console.log('onDisconnect handler, node id:', nodeId, connections),
|
(connections: Connection[]) => console.log('onDisconnect handler, node id:', nodeId, connections),
|
||||||
[nodeId]
|
[nodeId]
|
||||||
);
|
);
|
||||||
const connections = useHandleConnections({
|
const connections = useNodeConnections({
|
||||||
type: handleProps.type,
|
type: handleProps.type,
|
||||||
id: handleProps.id,
|
handleId: handleProps.id,
|
||||||
onConnect,
|
onConnect,
|
||||||
onDisconnect,
|
onDisconnect,
|
||||||
});
|
});
|
||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
import { memo, FC, useEffect, useCallback } from 'react';
|
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 }) {
|
function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string }) {
|
||||||
const onConnect = useCallback(
|
const onConnect = useCallback(
|
||||||
@@ -14,9 +14,9 @@ function CustomHandle({ nodeId, ...handleProps }: HandleProps & { nodeId: string
|
|||||||
},
|
},
|
||||||
[nodeId]
|
[nodeId]
|
||||||
);
|
);
|
||||||
const connections = useHandleConnections({
|
const connections = useNodeConnections({
|
||||||
type: handleProps.type,
|
type: handleProps.type,
|
||||||
id: handleProps.id,
|
handleId: handleProps.id,
|
||||||
onConnect,
|
onConnect,
|
||||||
onDisconnect,
|
onDisconnect,
|
||||||
});
|
});
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { memo } from 'react';
|
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 '.';
|
import { isTextNode, type MyNode } from '.';
|
||||||
|
|
||||||
function ResultNode() {
|
function ResultNode() {
|
||||||
const connections = useHandleConnections({
|
const connections = useNodeConnections({
|
||||||
type: 'target',
|
type: 'target',
|
||||||
});
|
});
|
||||||
const nodesData = useNodesData<MyNode>(connections.map((connection) => connection.source));
|
const nodesData = useNodesData<MyNode>(connections.map((connection) => connection.source));
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { memo, useEffect } from 'react';
|
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 '.';
|
import { isTextNode, type TextNode, type MyNode } from '.';
|
||||||
|
|
||||||
function UppercaseNode({ id }: NodeProps) {
|
function UppercaseNode({ id }: NodeProps) {
|
||||||
const { updateNodeData } = useReactFlow();
|
const { updateNodeData } = useReactFlow();
|
||||||
const connections = useHandleConnections({
|
const connections = useNodeConnections({
|
||||||
type: 'target',
|
type: 'target',
|
||||||
});
|
});
|
||||||
const nodesData = useNodesData<MyNode>(connections[0]?.source);
|
const nodesData = useNodesData<MyNode>(connections[0]?.source);
|
||||||
|
|||||||
@@ -147,6 +147,12 @@
|
|||||||
function hideUnhide() {
|
function hideUnhide() {
|
||||||
updateNode('hideunhide', (node) => ({ hidden: !node.hidden }));
|
updateNode('hideunhide', (node) => ({ hidden: !node.hidden }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $inspect(edges);
|
||||||
|
$effect(() => {
|
||||||
|
// console.log(edges.map((edge) => ({ id: edge.id, selected: edge.selected })));
|
||||||
|
edges.forEach((edge) => console.log({ id: edge.id, selected: edge.selected }));
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SvelteFlow
|
<SvelteFlow
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
default: DebugNode
|
default: DebugNode
|
||||||
};
|
};
|
||||||
|
|
||||||
let nodes = $state<Node[]>([
|
let nodes = $state.raw<Node[]>([
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let edges = $state<Edge[]>([
|
let edges = $state.raw<Edge[]>([
|
||||||
{
|
{
|
||||||
id: 'e1-2',
|
id: 'e1-2',
|
||||||
source: '1',
|
source: '1',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import {
|
import {
|
||||||
Handle,
|
Handle,
|
||||||
Position,
|
Position,
|
||||||
useHandleConnections,
|
useNodeConnections,
|
||||||
useNodesData,
|
useNodesData,
|
||||||
type NodeProps
|
type NodeProps
|
||||||
} from '@xyflow/svelte';
|
} from '@xyflow/svelte';
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
let { id }: NodeProps = $props();
|
let { id }: NodeProps = $props();
|
||||||
|
|
||||||
const connections = useHandleConnections({
|
const connections = useNodeConnections({
|
||||||
nodeId: id,
|
nodeId: id,
|
||||||
type: 'target'
|
type: 'target'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import {
|
import {
|
||||||
Handle,
|
Handle,
|
||||||
Position,
|
Position,
|
||||||
useHandleConnections,
|
useNodeConnections,
|
||||||
useNodesData,
|
useNodesData,
|
||||||
useSvelteFlow,
|
useSvelteFlow,
|
||||||
type Node,
|
type Node,
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
let { id, data }: NodeProps<Node<{ text: string }>> = $props();
|
let { id, data }: NodeProps<Node<{ text: string }>> = $props();
|
||||||
|
|
||||||
const { updateNodeData } = useSvelteFlow();
|
const { updateNodeData } = useSvelteFlow();
|
||||||
const connections = useHandleConnections({
|
const connections = useNodeConnections({
|
||||||
nodeId: id,
|
nodeId: id,
|
||||||
type: 'target'
|
type: 'target'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type useHandleConnectionsParams = {
|
|||||||
* Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
|
* Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
|
* @deprecated Use `useNodeConnections` instead.
|
||||||
* @param param.type - handle type 'source' or 'target'
|
* @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.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)
|
* @param param.id - the handle id (this is only needed if the node has multiple handles of the same type)
|
||||||
@@ -31,18 +32,22 @@ type useHandleConnectionsParams = {
|
|||||||
*/
|
*/
|
||||||
export function useHandleConnections({
|
export function useHandleConnections({
|
||||||
type,
|
type,
|
||||||
id = null,
|
id,
|
||||||
nodeId,
|
nodeId,
|
||||||
onConnect,
|
onConnect,
|
||||||
onDisconnect,
|
onDisconnect,
|
||||||
}: useHandleConnectionsParams): HandleConnection[] {
|
}: useHandleConnectionsParams): HandleConnection[] {
|
||||||
|
console.warn(
|
||||||
|
'[DEPRECATED] `useHandleConnections` is deprecated. Instead use `useNodeConnections` https://reactflow.dev/api-reference/hooks/useNodeConnections'
|
||||||
|
);
|
||||||
|
|
||||||
const _nodeId = useNodeId();
|
const _nodeId = useNodeId();
|
||||||
const currentNodeId = nodeId ?? _nodeId;
|
const currentNodeId = nodeId ?? _nodeId;
|
||||||
|
|
||||||
const prevConnections = useRef<Map<string, HandleConnection> | null>(null);
|
const prevConnections = useRef<Map<string, HandleConnection> | null>(null);
|
||||||
|
|
||||||
const connections = useStore(
|
const connections = useStore(
|
||||||
(state) => state.connectionLookup.get(`${currentNodeId}-${type}-${id}`),
|
(state) => state.connectionLookup.get(`${currentNodeId}-${type}${id ? `-${id}` : ''}`),
|
||||||
areConnectionMapsEqual
|
areConnectionMapsEqual
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
NodeConnection,
|
||||||
|
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 useNodeConnections({
|
||||||
|
type,
|
||||||
|
handleId,
|
||||||
|
nodeId,
|
||||||
|
onConnect,
|
||||||
|
onDisconnect,
|
||||||
|
}: UseNodeConnectionsParams = {}): NodeConnection[] {
|
||||||
|
const _nodeId = useNodeId();
|
||||||
|
const currentNodeId = nodeId ?? _nodeId;
|
||||||
|
|
||||||
|
if (!currentNodeId) {
|
||||||
|
throw new Error(error014);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevConnections = useRef<Map<string, NodeConnection> | 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]);
|
||||||
|
}
|
||||||
@@ -238,7 +238,14 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
|||||||
Array.from(
|
Array.from(
|
||||||
store
|
store
|
||||||
.getState()
|
.getState()
|
||||||
.connectionLookup.get(`${nodeId}-${type}-${id ?? null}`)
|
.connectionLookup.get(`${nodeId}-${type}${id ? `-${id}` : ''}`)
|
||||||
|
?.values() ?? []
|
||||||
|
),
|
||||||
|
getNodeConnections: ({ type, handleId, nodeId }) =>
|
||||||
|
Array.from(
|
||||||
|
store
|
||||||
|
.getState()
|
||||||
|
.connectionLookup.get(`${nodeId}${type ? (handleId ? `-${type}-${handleId}` : `-${type}`) : ''}`)
|
||||||
?.values() ?? []
|
?.values() ?? []
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export { useOnViewportChange, type UseOnViewportChangeOptions } from './hooks/us
|
|||||||
export { useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange';
|
export { useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange';
|
||||||
export { useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized';
|
export { useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized';
|
||||||
export { useHandleConnections } from './hooks/useHandleConnections';
|
export { useHandleConnections } from './hooks/useHandleConnections';
|
||||||
|
export { useNodeConnections } from './hooks/useNodeConnections';
|
||||||
export { useNodesData } from './hooks/useNodesData';
|
export { useNodesData } from './hooks/useNodesData';
|
||||||
export { useConnection } from './hooks/useConnection';
|
export { useConnection } from './hooks/useConnection';
|
||||||
export { useInternalNode } from './hooks/useInternalNode';
|
export { useInternalNode } from './hooks/useInternalNode';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* 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 '.';
|
import type { Node, Edge, ViewportHelperFunctions, InternalNode } from '.';
|
||||||
|
|
||||||
export type ReactFlowJsonObject<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
export type ReactFlowJsonObject<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||||
@@ -183,7 +183,7 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
getNodesBounds: (nodes: (NodeType | InternalNode | string)[]) => Rect;
|
getNodesBounds: (nodes: (NodeType | InternalNode | string)[]) => Rect;
|
||||||
/**
|
/**
|
||||||
* Gets all connections for a given handle belonging to a specific node.
|
* Gets all connections for a given handle belonging to a specific node.
|
||||||
*
|
* @deprecated
|
||||||
* @param type - handle type 'source' or 'target'
|
* @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 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
|
* @param nodeId - the node id the handle belongs to
|
||||||
@@ -198,6 +198,23 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
nodeId: string;
|
nodeId: string;
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
}) => HandleConnection[];
|
}) => 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<NodeType extends Node = Node, EdgeType extends Edge = Edge> = GeneralHelpers<
|
export type ReactFlowInstance<NodeType extends Node = Node, EdgeType extends Edge = Edge> = GeneralHelpers<
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @xyflow/svelte
|
# @xyflow/svelte
|
||||||
|
|
||||||
|
## 0.1.26
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#4897](https://github.com/xyflow/xyflow/pull/4897) [`c6890512`](https://github.com/xyflow/xyflow/commit/c6890512a685c8ec87310b3b003e62b0b976eca4) Thanks [@peterkogo](https://github.com/peterkogo)! - Freeze required @svelte-put/shortcut version to 3.1.1 to prevent breaking of key inputs.
|
||||||
|
|
||||||
## 0.1.25
|
## 0.1.25
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@xyflow/svelte",
|
"name": "@xyflow/svelte",
|
||||||
"version": "0.1.25",
|
"version": "0.1.26",
|
||||||
"description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.",
|
"description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"svelte",
|
"svelte",
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
store.edges;
|
store.edges;
|
||||||
if (onconnect || ondisconnect) {
|
if (onconnect || ondisconnect) {
|
||||||
let connections = store.connectionLookup.get(`${nodeId}-${type}-${handleId}`);
|
let connections = store.connectionLookup.get(`${nodeId}-${type}${id ? `-${id}` : ''}`);
|
||||||
|
|
||||||
if (prevConnections && !areConnectionMapsEqual(connections, prevConnections)) {
|
if (prevConnections && !areConnectionMapsEqual(connections, prevConnections)) {
|
||||||
const _connections = connections ?? new Map();
|
const _connections = connections ?? new Map();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
return (item: Item) => {
|
return (item: Item) => {
|
||||||
const isSelected = ids.has(item.id);
|
const isSelected = ids.has(item.id);
|
||||||
|
|
||||||
if (item.selected !== isSelected) {
|
if (!!item.selected !== isSelected) {
|
||||||
return { ...item, selected: isSelected };
|
return { ...item, selected: isSelected };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,8 +23,7 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: maybe replace with set.intersection?
|
function isSetEqual(a: Set<string>, b: Set<string>) {
|
||||||
function setEq(a: Set<string>, b: Set<string>) {
|
|
||||||
if (a.size !== b.size) {
|
if (a.size !== b.size) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -57,7 +56,6 @@
|
|||||||
// svelte-ignore non_reactive_update
|
// svelte-ignore non_reactive_update
|
||||||
let container: HTMLDivElement;
|
let container: HTMLDivElement;
|
||||||
let containerBounds: DOMRect | null = null;
|
let containerBounds: DOMRect | null = null;
|
||||||
// let selectedNodes: InternalNode[] = [];
|
|
||||||
|
|
||||||
let selectedNodeIds: Set<string> = new Set();
|
let selectedNodeIds: Set<string> = new Set();
|
||||||
let selectedEdgeIds: Set<string> = new Set();
|
let selectedEdgeIds: Set<string> = new Set();
|
||||||
@@ -149,25 +147,27 @@
|
|||||||
).map((n) => n.id)
|
).map((n) => n.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: replace with extended connectionLookup
|
|
||||||
let edgesSelectable = store.defaultEdgeOptions.selectable ?? true;
|
let edgesSelectable = store.defaultEdgeOptions.selectable ?? true;
|
||||||
selectedEdgeIds = new Set();
|
selectedEdgeIds = new Set();
|
||||||
store.edges.forEach((edge) => {
|
|
||||||
if (
|
// We look for all edges connected to the selected nodes
|
||||||
selectedNodeIds.has(edge.source) &&
|
for (let nodeId of selectedNodeIds) {
|
||||||
selectedNodeIds.has(edge.target) &&
|
let connections = store.connectionLookup.get(nodeId);
|
||||||
(edge.selectable ?? edgesSelectable)
|
if (!connections) continue;
|
||||||
) {
|
for (let { edgeId } of connections.values()) {
|
||||||
selectedEdgeIds.add(edge.id);
|
let edge = store.edgeLookup.get(edgeId);
|
||||||
|
if (edge && (edge.selectable ?? edgesSelectable)) {
|
||||||
|
selectedEdgeIds.add(edgeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// this prevents unnecessary updates while updating the selection rectangle
|
// this prevents unnecessary updates while updating the selection rectangle
|
||||||
if (setEq(prevSelectedNodeIds, selectedNodeIds)) {
|
if (isSetEqual(prevSelectedNodeIds, selectedNodeIds)) {
|
||||||
store.nodes = store.nodes.map(toggleSelected(selectedNodeIds));
|
store.nodes = store.nodes.map(toggleSelected(selectedNodeIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setEq(prevSelectedEdgeIds, selectedEdgeIds)) {
|
if (isSetEqual(prevSelectedEdgeIds, selectedEdgeIds)) {
|
||||||
store.edges = store.edges.map(toggleSelected(selectedEdgeIds));
|
store.edges = store.edges.map(toggleSelected(selectedEdgeIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { getContext } from 'svelte';
|
|||||||
* @param functionName - The name of the function that is being called
|
* @param functionName - The name of the function that is being called
|
||||||
* @param force - If true, the warning will be shown regardless if child of <SvelteFlowFlow />
|
* @param force - If true, the warning will be shown regardless if child of <SvelteFlowFlow />
|
||||||
*/
|
*/
|
||||||
export function derivedWarning(functionName: string, force?: boolean) {
|
export function derivedWarning(functionName: string) {
|
||||||
const storeContext = getContext<StoreContext>(key);
|
const storeContext = getContext<StoreContext>(key);
|
||||||
|
|
||||||
if (!storeContext) {
|
if (!storeContext) {
|
||||||
@@ -18,7 +18,7 @@ export function derivedWarning(functionName: string, force?: boolean) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((force || storeContext.provider) && !$effect.tracking()) {
|
if (storeContext.provider && typeof window === 'object' && !$effect.tracking()) {
|
||||||
console.warn(`Use $derived(${functionName}()) to receive updates when values change.`);
|
console.warn(`Use $derived(${functionName}()) to receive updates when values change.`);
|
||||||
console.trace(functionName);
|
console.trace(functionName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import { areConnectionMapsEqual, type HandleConnection, type HandleType } from '@xyflow/system';
|
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
|
||||||
import { getContext } from 'svelte';
|
|
||||||
|
|
||||||
export type useHandleConnectionsParams = {
|
|
||||||
type: HandleType;
|
|
||||||
nodeId?: string;
|
|
||||||
id?: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialConnections: HandleConnection[] = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
* @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)
|
|
||||||
* @returns an array with connections
|
|
||||||
*/
|
|
||||||
export function useHandleConnections({
|
|
||||||
type,
|
|
||||||
nodeId: _nodeId,
|
|
||||||
id = null
|
|
||||||
}: useHandleConnectionsParams) {
|
|
||||||
const { edges, connectionLookup } = $derived(useStore());
|
|
||||||
|
|
||||||
const contextNodeId = getContext<string>('svelteflow__node_id');
|
|
||||||
const nodeId = _nodeId ?? contextNodeId;
|
|
||||||
|
|
||||||
let prevConnections: Map<string, HandleConnection> | undefined = new Map();
|
|
||||||
let connectionsArray: HandleConnection[] = initialConnections;
|
|
||||||
|
|
||||||
const connections = $derived.by(() => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
||||||
edges;
|
|
||||||
const nextConnections = connectionLookup.get(`${nodeId}-${type}-${id || null}`);
|
|
||||||
if (!areConnectionMapsEqual(nextConnections, prevConnections)) {
|
|
||||||
prevConnections = nextConnections;
|
|
||||||
connectionsArray = Array.from(nextConnections?.values() || initialConnections);
|
|
||||||
}
|
|
||||||
return connectionsArray;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
get current() {
|
|
||||||
return connections;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { areConnectionMapsEqual, type NodeConnection, type HandleType } from '@xyflow/system';
|
||||||
|
|
||||||
|
import { useStore } from '$lib/store';
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
|
type UseNodeConnectionsParams = {
|
||||||
|
type?: HandleType;
|
||||||
|
handleId?: string;
|
||||||
|
nodeId?: string;
|
||||||
|
// TODO: Svelte 5
|
||||||
|
// onConnect?: (connections: Connection[]) => void;
|
||||||
|
// onDisconnect?: (connections: Connection[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialConnections: NodeConnection[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 useNodeConnections({
|
||||||
|
type,
|
||||||
|
nodeId: _nodeId,
|
||||||
|
handleId
|
||||||
|
}: UseNodeConnectionsParams = {}) {
|
||||||
|
const { edges, connectionLookup } = $derived(useStore());
|
||||||
|
|
||||||
|
const contextNodeId = getContext<string>('svelteflow__node_id');
|
||||||
|
const nodeId = _nodeId ?? contextNodeId;
|
||||||
|
|
||||||
|
let prevConnections: Map<string, NodeConnection> | undefined = new Map();
|
||||||
|
let connectionsArray: NodeConnection[] = initialConnections;
|
||||||
|
|
||||||
|
const connections = $derived.by(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
|
edges;
|
||||||
|
const nextConnections = connectionLookup.get(
|
||||||
|
`${nodeId}-${type}${handleId ? `-${handleId}` : ''}`
|
||||||
|
);
|
||||||
|
if (!areConnectionMapsEqual(nextConnections, prevConnections)) {
|
||||||
|
prevConnections = nextConnections;
|
||||||
|
connectionsArray = Array.from(nextConnections?.values() || initialConnections);
|
||||||
|
}
|
||||||
|
return connectionsArray;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
get current() {
|
||||||
|
return connections;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ export * from '$lib/hooks/useSvelteFlow.svelte';
|
|||||||
export * from '$lib/hooks/useUpdateNodeInternals.svelte';
|
export * from '$lib/hooks/useUpdateNodeInternals.svelte';
|
||||||
export * from '$lib/hooks/useConnection.svelte';
|
export * from '$lib/hooks/useConnection.svelte';
|
||||||
export * from '$lib/hooks/useNodesEdgesViewport.svelte';
|
export * from '$lib/hooks/useNodesEdgesViewport.svelte';
|
||||||
export * from '$lib/hooks/useHandleConnections.svelte';
|
export * from '$lib/hooks/useNodeConnections.svelte';
|
||||||
export * from '$lib/hooks/useNodesData.svelte';
|
export * from '$lib/hooks/useNodesData.svelte';
|
||||||
export * from '$lib/hooks/useInternalNode.svelte';
|
export * from '$lib/hooks/useInternalNode.svelte';
|
||||||
export { useInitialized, useNodesInitialized } from '$lib/hooks/useInitialized.svelte';
|
export { useInitialized, useNodesInitialized } from '$lib/hooks/useInitialized.svelte';
|
||||||
|
|||||||
@@ -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.`,
|
`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') =>
|
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.`,
|
`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 = [
|
export const infiniteExtent: CoordinateExtent = [
|
||||||
|
|||||||
@@ -33,10 +33,15 @@ export type Connection = {
|
|||||||
targetHandle: string | null;
|
targetHandle: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: remove in next version
|
||||||
export type HandleConnection = Connection & {
|
export type HandleConnection = Connection & {
|
||||||
edgeId: string;
|
edgeId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NodeConnection = Connection & {
|
||||||
|
edgeId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export enum ConnectionMode {
|
export enum ConnectionMode {
|
||||||
Strict = 'strict',
|
Strict = 'strict',
|
||||||
Loose = 'loose',
|
Loose = 'loose',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { infiniteExtent } from '..';
|
import { HandleConnection, infiniteExtent } from '..';
|
||||||
import {
|
import {
|
||||||
NodeBase,
|
NodeBase,
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
@@ -42,7 +42,7 @@ const adoptUserNodesDefaultOptions = {
|
|||||||
checkEquality: true,
|
checkEquality: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
function mergeObjects<T extends Record<string, any>>(base: T, incoming?: Partial<T>): T {
|
function mergeObjects<T extends Record<string, unknown>>(base: T, incoming?: Partial<T>): T {
|
||||||
const result = { ...base };
|
const result = { ...base };
|
||||||
for (const key in incoming) {
|
for (const key in incoming) {
|
||||||
if (incoming[key] !== undefined) {
|
if (incoming[key] !== undefined) {
|
||||||
@@ -450,22 +450,56 @@ export async function panBy({
|
|||||||
return Promise.resolve(transformChanged);
|
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,
|
||||||
|
connectionKey: string,
|
||||||
|
connectionLookup: ConnectionLookup,
|
||||||
|
nodeId: string,
|
||||||
|
handleId: string | null
|
||||||
|
) {
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
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[]) {
|
export function updateConnectionLookup(connectionLookup: ConnectionLookup, edgeLookup: EdgeLookup, edges: EdgeBase[]) {
|
||||||
connectionLookup.clear();
|
connectionLookup.clear();
|
||||||
edgeLookup.clear();
|
edgeLookup.clear();
|
||||||
|
|
||||||
for (const edge of edges) {
|
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 connection = { edgeId: edge.id, source: sourceNode, target: targetNode, sourceHandle, targetHandle };
|
||||||
const targetKey = `${target}-target-${targetHandle}`;
|
const sourceKey = `${sourceNode}-${sourceHandle}`;
|
||||||
|
const targetKey = `${targetNode}-${targetHandle}`;
|
||||||
|
|
||||||
const prevSource = connectionLookup.get(sourceKey) || new Map();
|
addConnectionToLookup('source', connection, targetKey, connectionLookup, sourceNode, sourceHandle);
|
||||||
const prevTarget = connectionLookup.get(targetKey) || new Map();
|
addConnectionToLookup('target', connection, sourceKey, connectionLookup, targetNode, targetHandle);
|
||||||
const connection = { edgeId: edge.id, source, target, sourceHandle, targetHandle };
|
|
||||||
|
|
||||||
edgeLookup.set(edge.id, edge);
|
edgeLookup.set(edge.id, edge);
|
||||||
connectionLookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
|
|
||||||
connectionLookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user