@xyflow/system 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.js +2176 -0
- package/dist/esm/index.mjs +322 -137
- package/dist/esm/types/edges.d.ts +5 -2
- package/dist/esm/types/edges.d.ts.map +1 -1
- package/dist/esm/types/general.d.ts +10 -2
- package/dist/esm/types/general.d.ts.map +1 -1
- package/dist/esm/types/nodes.d.ts +7 -2
- package/dist/esm/types/nodes.d.ts.map +1 -1
- package/dist/esm/types/utils.d.ts.map +1 -1
- package/dist/esm/utils/connections.d.ts +12 -0
- package/dist/esm/utils/connections.d.ts.map +1 -0
- package/dist/esm/utils/edges/bezier-edge.d.ts +23 -0
- package/dist/esm/utils/edges/bezier-edge.d.ts.map +1 -1
- package/dist/esm/utils/edges/general.d.ts +23 -5
- package/dist/esm/utils/edges/general.d.ts.map +1 -1
- package/dist/esm/utils/edges/positions.d.ts.map +1 -1
- package/dist/esm/utils/edges/smoothstep-edge.d.ts +22 -0
- package/dist/esm/utils/edges/smoothstep-edge.d.ts.map +1 -1
- package/dist/esm/utils/edges/straight-edge.d.ts +20 -0
- package/dist/esm/utils/edges/straight-edge.d.ts.map +1 -1
- package/dist/esm/utils/general.d.ts +19 -2
- package/dist/esm/utils/general.d.ts.map +1 -1
- package/dist/esm/utils/graph.d.ts +63 -8
- package/dist/esm/utils/graph.d.ts.map +1 -1
- package/dist/esm/utils/index.d.ts +2 -0
- package/dist/esm/utils/index.d.ts.map +1 -1
- package/dist/esm/utils/store.d.ts +4 -3
- package/dist/esm/utils/store.d.ts.map +1 -1
- package/dist/esm/xyhandle/XYHandle.d.ts +1 -1
- package/dist/esm/xyhandle/XYHandle.d.ts.map +1 -1
- package/dist/esm/xyminimap/index.d.ts.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/types/edges.d.ts +5 -2
- package/dist/umd/types/edges.d.ts.map +1 -1
- package/dist/umd/types/general.d.ts +10 -2
- package/dist/umd/types/general.d.ts.map +1 -1
- package/dist/umd/types/nodes.d.ts +7 -2
- package/dist/umd/types/nodes.d.ts.map +1 -1
- package/dist/umd/types/utils.d.ts.map +1 -1
- package/dist/umd/utils/connections.d.ts +12 -0
- package/dist/umd/utils/connections.d.ts.map +1 -0
- package/dist/umd/utils/edges/bezier-edge.d.ts +23 -0
- package/dist/umd/utils/edges/bezier-edge.d.ts.map +1 -1
- package/dist/umd/utils/edges/general.d.ts +23 -5
- package/dist/umd/utils/edges/general.d.ts.map +1 -1
- package/dist/umd/utils/edges/positions.d.ts.map +1 -1
- package/dist/umd/utils/edges/smoothstep-edge.d.ts +22 -0
- package/dist/umd/utils/edges/smoothstep-edge.d.ts.map +1 -1
- package/dist/umd/utils/edges/straight-edge.d.ts +20 -0
- package/dist/umd/utils/edges/straight-edge.d.ts.map +1 -1
- package/dist/umd/utils/general.d.ts +19 -2
- package/dist/umd/utils/general.d.ts.map +1 -1
- package/dist/umd/utils/graph.d.ts +63 -8
- package/dist/umd/utils/graph.d.ts.map +1 -1
- package/dist/umd/utils/index.d.ts +2 -0
- package/dist/umd/utils/index.d.ts.map +1 -1
- package/dist/umd/utils/store.d.ts +4 -3
- package/dist/umd/utils/store.d.ts.map +1 -1
- package/dist/umd/xyhandle/XYHandle.d.ts +1 -1
- package/dist/umd/xyhandle/XYHandle.d.ts.map +1 -1
- package/dist/umd/xyminimap/index.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/esm/index.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use client"
|
|
2
1
|
import { drag } from 'd3-drag';
|
|
3
2
|
import { select, pointer } from 'd3-selection';
|
|
4
3
|
import { zoom, zoomIdentity, zoomTransform } from 'd3-zoom';
|
|
@@ -64,9 +63,71 @@ var Position;
|
|
|
64
63
|
Position["Bottom"] = "bottom";
|
|
65
64
|
})(Position || (Position = {}));
|
|
66
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
function areConnectionMapsEqual(a, b) {
|
|
70
|
+
if (!a && !b) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (!a || !b || a.size !== b.size) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (!a.size && !b.size) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
for (const key of a.keys()) {
|
|
80
|
+
if (!b.has(key)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* We call the callback for all connections in a that are not in b
|
|
88
|
+
*
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
function handleConnectionChange(a, b, cb) {
|
|
92
|
+
if (!cb) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const diff = [];
|
|
96
|
+
a.forEach((connection, key) => {
|
|
97
|
+
if (!b?.has(key)) {
|
|
98
|
+
diff.push(connection);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
if (diff.length) {
|
|
102
|
+
cb(diff);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
67
106
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
107
|
+
/**
|
|
108
|
+
* Test whether an object is useable as an Edge
|
|
109
|
+
* @public
|
|
110
|
+
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true
|
|
111
|
+
* @param element - The element to test
|
|
112
|
+
* @returns A boolean indicating whether the element is an Edge
|
|
113
|
+
*/
|
|
68
114
|
const isEdgeBase = (element) => 'id' in element && 'source' in element && 'target' in element;
|
|
115
|
+
/**
|
|
116
|
+
* Test whether an object is useable as a Node
|
|
117
|
+
* @public
|
|
118
|
+
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true
|
|
119
|
+
* @param element - The element to test
|
|
120
|
+
* @returns A boolean indicating whether the element is an Node
|
|
121
|
+
*/
|
|
69
122
|
const isNodeBase = (element) => 'id' in element && !('source' in element) && !('target' in element);
|
|
123
|
+
/**
|
|
124
|
+
* Pass in a node, and get connected nodes where edge.source === node.id
|
|
125
|
+
* @public
|
|
126
|
+
* @param node - The node to get the connected nodes from
|
|
127
|
+
* @param nodes - The array of all nodes
|
|
128
|
+
* @param edges - The array of all edges
|
|
129
|
+
* @returns An array of nodes that are connected over eges where the source is the given node
|
|
130
|
+
*/
|
|
70
131
|
const getOutgoersBase = (node, nodes, edges) => {
|
|
71
132
|
if (!node.id) {
|
|
72
133
|
return [];
|
|
@@ -79,6 +140,14 @@ const getOutgoersBase = (node, nodes, edges) => {
|
|
|
79
140
|
});
|
|
80
141
|
return nodes.filter((n) => outgoerIds.has(n.id));
|
|
81
142
|
};
|
|
143
|
+
/**
|
|
144
|
+
* Pass in a node, and get connected nodes where edge.target === node.id
|
|
145
|
+
* @public
|
|
146
|
+
* @param node - The node to get the connected nodes from
|
|
147
|
+
* @param nodes - The array of all nodes
|
|
148
|
+
* @param edges - The array of all edges
|
|
149
|
+
* @returns An array of nodes that are connected over eges where the target is the given node
|
|
150
|
+
*/
|
|
82
151
|
const getIncomersBase = (node, nodes, edges) => {
|
|
83
152
|
if (!node.id) {
|
|
84
153
|
return [];
|
|
@@ -118,6 +187,14 @@ const getNodePositionWithOrigin = (node, nodeOrigin = [0, 0]) => {
|
|
|
118
187
|
: position,
|
|
119
188
|
};
|
|
120
189
|
};
|
|
190
|
+
/**
|
|
191
|
+
* Determines a bounding box that contains all given nodes in an array
|
|
192
|
+
* @public
|
|
193
|
+
* @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport.
|
|
194
|
+
* @param nodes - Nodes to calculate the bounds for
|
|
195
|
+
* @param nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
|
|
196
|
+
* @returns Bounding box enclosing all nodes
|
|
197
|
+
*/
|
|
121
198
|
const getNodesBounds = (nodes, nodeOrigin = [0, 0]) => {
|
|
122
199
|
if (nodes.length === 0) {
|
|
123
200
|
return { x: 0, y: 0, width: 0, height: 0 };
|
|
@@ -160,6 +237,12 @@ excludeNonSelectableNodes = false, nodeOrigin = [0, 0]) => {
|
|
|
160
237
|
}, []);
|
|
161
238
|
return visibleNodes;
|
|
162
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* Get all connecting edges for a given set of nodes
|
|
242
|
+
* @param nodes - Nodes you want to get the connected edges for
|
|
243
|
+
* @param edges - All edges
|
|
244
|
+
* @returns Array of edges that connect any of the given nodes with each other
|
|
245
|
+
*/
|
|
163
246
|
const getConnectedEdgesBase = (nodes, edges) => {
|
|
164
247
|
const nodeIds = new Set();
|
|
165
248
|
nodes.forEach((node) => {
|
|
@@ -238,33 +321,53 @@ function calcNextPosition(node, nextPosition, nodes, nodeExtent, nodeOrigin = [0
|
|
|
238
321
|
positionAbsolute,
|
|
239
322
|
};
|
|
240
323
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
324
|
+
/**
|
|
325
|
+
* Pass in nodes & edges to delete, get arrays of nodes and edges that actually can be deleted
|
|
326
|
+
* @internal
|
|
327
|
+
* @param param.nodesToRemove - The nodes to remove
|
|
328
|
+
* @param param.edgesToRemove - The edges to remove
|
|
329
|
+
* @param param.nodes - All nodes
|
|
330
|
+
* @param param.edges - All edges
|
|
331
|
+
* @param param.onBeforeDelete - Callback to check which nodes and edges can be deleted
|
|
332
|
+
* @returns nodes: nodes that can be deleted, edges: edges that can be deleted
|
|
333
|
+
*/
|
|
334
|
+
async function getElementsToRemove({ nodesToRemove = [], edgesToRemove = [], nodes, edges, onBeforeDelete, }) {
|
|
245
335
|
const nodeIds = nodesToRemove.map((node) => node.id);
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (deletable && (nodeIds.includes(node.id) || parentHit)) {
|
|
251
|
-
res.push(node);
|
|
336
|
+
const matchingNodes = [];
|
|
337
|
+
for (const node of nodes) {
|
|
338
|
+
if (node.deletable === false) {
|
|
339
|
+
continue;
|
|
252
340
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
341
|
+
const isIncluded = nodeIds.includes(node.id);
|
|
342
|
+
const parentHit = !isIncluded && node.parentNode && matchingNodes.find((n) => n.id === node.parentNode);
|
|
343
|
+
if (isIncluded || parentHit) {
|
|
344
|
+
matchingNodes.push(node);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const edgeIds = edgesToRemove.map((edge) => edge.id);
|
|
348
|
+
const deletableEdges = edges.filter((edge) => edge.deletable !== false);
|
|
257
349
|
const connectedEdges = getConnectedEdgesBase(matchingNodes, deletableEdges);
|
|
258
|
-
const matchingEdges = connectedEdges
|
|
259
|
-
|
|
260
|
-
|
|
350
|
+
const matchingEdges = connectedEdges;
|
|
351
|
+
for (const edge of deletableEdges) {
|
|
352
|
+
const isIncluded = edgeIds.includes(edge.id);
|
|
353
|
+
if (isIncluded && !matchingEdges.find((e) => e.id === edge.id)) {
|
|
354
|
+
matchingEdges.push(edge);
|
|
261
355
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
356
|
+
}
|
|
357
|
+
if (!onBeforeDelete) {
|
|
358
|
+
return {
|
|
359
|
+
edges: matchingEdges,
|
|
360
|
+
nodes: matchingNodes,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const onBeforeDeleteResult = await onBeforeDelete({
|
|
364
|
+
nodes: matchingNodes,
|
|
365
|
+
edges: matchingEdges,
|
|
366
|
+
});
|
|
367
|
+
if (typeof onBeforeDeleteResult === 'boolean') {
|
|
368
|
+
return onBeforeDeleteResult ? { edges: matchingEdges, nodes: matchingNodes } : { edges: [], nodes: [] };
|
|
369
|
+
}
|
|
370
|
+
return onBeforeDeleteResult;
|
|
268
371
|
}
|
|
269
372
|
|
|
270
373
|
const clamp = (val, min = 0, max = 1) => Math.min(Math.max(val, min), max);
|
|
@@ -272,8 +375,14 @@ const clampPosition = (position = { x: 0, y: 0 }, extent) => ({
|
|
|
272
375
|
x: clamp(position.x, extent[0][0], extent[1][0]),
|
|
273
376
|
y: clamp(position.y, extent[0][1], extent[1][1]),
|
|
274
377
|
});
|
|
275
|
-
|
|
276
|
-
|
|
378
|
+
/**
|
|
379
|
+
* Calculates the velocity of panning when the mouse is close to the edge of the canvas
|
|
380
|
+
* @internal
|
|
381
|
+
* @param value - One dimensional poition of the mouse (x or y)
|
|
382
|
+
* @param min - Minimal position on canvas before panning starts
|
|
383
|
+
* @param max - Maximal position on canvas before panning starts
|
|
384
|
+
* @returns - A number between 0 and 1 that represents the velocity of panning
|
|
385
|
+
*/
|
|
277
386
|
const calcAutoPanVelocity = (value, min, max) => {
|
|
278
387
|
if (value < min) {
|
|
279
388
|
return clamp(Math.abs(value - min), 1, 50) / 50;
|
|
@@ -347,12 +456,12 @@ const getPositionWithOrigin = ({ x, y, width, height, origin = [0, 0], }) => {
|
|
|
347
456
|
y: y - height * origin[1],
|
|
348
457
|
};
|
|
349
458
|
};
|
|
350
|
-
|
|
459
|
+
const snapPosition = (position, snapGrid = [1, 1]) => {
|
|
351
460
|
return {
|
|
352
461
|
x: snapGrid[0] * Math.round(position.x / snapGrid[0]),
|
|
353
462
|
y: snapGrid[1] * Math.round(position.y / snapGrid[1]),
|
|
354
463
|
};
|
|
355
|
-
}
|
|
464
|
+
};
|
|
356
465
|
const pointToRendererPoint = ({ x, y }, [tx, ty, tScale], snapToGrid = false, snapGrid = [1, 1]) => {
|
|
357
466
|
const position = {
|
|
358
467
|
x: (x - tx) / tScale,
|
|
@@ -366,6 +475,22 @@ const rendererPointToPoint = ({ x, y }, [tx, ty, tScale]) => {
|
|
|
366
475
|
y: y * tScale + ty,
|
|
367
476
|
};
|
|
368
477
|
};
|
|
478
|
+
/**
|
|
479
|
+
* Returns a viewport that encloses the given bounds with optional padding.
|
|
480
|
+
* @public
|
|
481
|
+
* @remarks You can determine bounds of nodes with {@link getNodesBounds} and {@link getBoundsOfRects}
|
|
482
|
+
* @param bounds - Bounds to fit inside viewport
|
|
483
|
+
* @param width - Width of the viewport
|
|
484
|
+
* @param height - Height of the viewport
|
|
485
|
+
* @param minZoom - Minimum zoom level of the resulting viewport
|
|
486
|
+
* @param maxZoom - Maximum zoom level of the resulting viewport
|
|
487
|
+
* @param padding - Optional padding around the bounds
|
|
488
|
+
* @returns A transforned {@link Viewport} that encloses the given bounds which you can pass to e.g. {@link setViewport}
|
|
489
|
+
* @example
|
|
490
|
+
* const { x, y, zoom } = getViewportForBounds(
|
|
491
|
+
{ x: 0, y: 0, width: 100, height: 100},
|
|
492
|
+
1200, 800, 0.5, 2);
|
|
493
|
+
*/
|
|
369
494
|
const getViewportForBounds = (bounds, width, height, minZoom, maxZoom, padding) => {
|
|
370
495
|
const xZoom = width / (bounds.width * (1 + padding));
|
|
371
496
|
const yZoom = height / (bounds.height * (1 + padding));
|
|
@@ -469,6 +594,29 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }) {
|
|
|
469
594
|
return [x1, y1 + calculateControlOffset(y2 - y1, c)];
|
|
470
595
|
}
|
|
471
596
|
}
|
|
597
|
+
/**
|
|
598
|
+
* Get a bezier path from source to target handle
|
|
599
|
+
* @param params.sourceX - The x position of the source handle
|
|
600
|
+
* @param params.sourceY - The y position of the source handle
|
|
601
|
+
* @param params.sourcePosition - The position of the source handle (default: Position.Bottom)
|
|
602
|
+
* @param params.targetX - The x position of the target handle
|
|
603
|
+
* @param params.targetY - The y position of the target handle
|
|
604
|
+
* @param params.targetPosition - The position of the target handle (default: Position.Top)
|
|
605
|
+
* @param params.curvature - The curvature of the bezier edge
|
|
606
|
+
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
|
|
607
|
+
* @example
|
|
608
|
+
* const source = { x: 0, y: 20 };
|
|
609
|
+
const target = { x: 150, y: 100 };
|
|
610
|
+
|
|
611
|
+
const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({
|
|
612
|
+
sourceX: source.x,
|
|
613
|
+
sourceY: source.y,
|
|
614
|
+
sourcePosition: Position.Right,
|
|
615
|
+
targetX: target.x,
|
|
616
|
+
targetY: target.y,
|
|
617
|
+
targetPosition: Position.Left,
|
|
618
|
+
});
|
|
619
|
+
*/
|
|
472
620
|
function getBezierPath({ sourceX, sourceY, sourcePosition = Position.Bottom, targetX, targetY, targetPosition = Position.Top, curvature = 0.25, }) {
|
|
473
621
|
const [sourceControlX, sourceControlY] = getControlWithCurvature({
|
|
474
622
|
pos: sourcePosition,
|
|
@@ -513,40 +661,13 @@ function getEdgeCenter({ sourceX, sourceY, targetX, targetY, }) {
|
|
|
513
661
|
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
|
514
662
|
return [centerX, centerY, xOffset, yOffset];
|
|
515
663
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const levelLookup = edges.reduce((tree, edge) => {
|
|
520
|
-
const hasZIndex = isNumeric(edge.zIndex);
|
|
521
|
-
let z = hasZIndex ? edge.zIndex : 0;
|
|
522
|
-
if (elevateEdgesOnSelect) {
|
|
523
|
-
const targetNode = nodeLookup.get(edge.target);
|
|
524
|
-
const sourceNode = nodeLookup.get(edge.source);
|
|
525
|
-
const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected;
|
|
526
|
-
const selectedZIndex = Math.max(sourceNode?.[internalsSymbol]?.z || 0, targetNode?.[internalsSymbol]?.z || 0, 1000);
|
|
527
|
-
z = (hasZIndex ? edge.zIndex : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0);
|
|
528
|
-
}
|
|
529
|
-
if (tree[z]) {
|
|
530
|
-
tree[z].push(edge);
|
|
531
|
-
}
|
|
532
|
-
else {
|
|
533
|
-
tree[z] = [edge];
|
|
534
|
-
}
|
|
535
|
-
maxLevel = z > maxLevel ? z : maxLevel;
|
|
536
|
-
return tree;
|
|
537
|
-
}, {});
|
|
538
|
-
const edgeTree = Object.entries(levelLookup).map(([key, edges]) => {
|
|
539
|
-
const level = +key;
|
|
540
|
-
return {
|
|
541
|
-
edges,
|
|
542
|
-
level,
|
|
543
|
-
isMaxLevel: level === maxLevel,
|
|
544
|
-
};
|
|
545
|
-
});
|
|
546
|
-
if (edgeTree.length === 0) {
|
|
547
|
-
return defaultEdgeTree;
|
|
664
|
+
function getElevatedEdgeZIndex({ sourceNode, targetNode, selected = false, zIndex = 0, elevateOnSelect = false, }) {
|
|
665
|
+
if (!elevateOnSelect) {
|
|
666
|
+
return zIndex;
|
|
548
667
|
}
|
|
549
|
-
|
|
668
|
+
const edgeOrConnectedNodeSelected = selected || targetNode.selected || sourceNode.selected;
|
|
669
|
+
const selectedZIndex = Math.max(sourceNode[internalsSymbol]?.z || 0, targetNode[internalsSymbol]?.z || 0, 1000);
|
|
670
|
+
return zIndex + (edgeOrConnectedNodeSelected ? selectedZIndex : 0);
|
|
550
671
|
}
|
|
551
672
|
function isEdgeVisible({ sourceNode, targetNode, width, height, transform }) {
|
|
552
673
|
const edgeBox = getBoundsOfBoxes(nodeToBox(sourceNode), nodeToBox(targetNode));
|
|
@@ -564,13 +685,21 @@ function isEdgeVisible({ sourceNode, targetNode, width, height, transform }) {
|
|
|
564
685
|
};
|
|
565
686
|
return getOverlappingArea(viewRect, boxToRect(edgeBox)) > 0;
|
|
566
687
|
}
|
|
567
|
-
const getEdgeId = ({ source, sourceHandle, target, targetHandle }) => `
|
|
688
|
+
const getEdgeId = ({ source, sourceHandle, target, targetHandle }) => `xy-edge__${source}${sourceHandle || ''}-${target}${targetHandle || ''}`;
|
|
568
689
|
const connectionExists = (edge, edges) => {
|
|
569
690
|
return edges.some((el) => el.source === edge.source &&
|
|
570
691
|
el.target === edge.target &&
|
|
571
692
|
(el.sourceHandle === edge.sourceHandle || (!el.sourceHandle && !edge.sourceHandle)) &&
|
|
572
693
|
(el.targetHandle === edge.targetHandle || (!el.targetHandle && !edge.targetHandle)));
|
|
573
694
|
};
|
|
695
|
+
/**
|
|
696
|
+
* This util is a convenience function to add a new Edge to an array of edges
|
|
697
|
+
* @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one.
|
|
698
|
+
* @public
|
|
699
|
+
* @param edgeParams - Either an Edge or a Connection you want to add
|
|
700
|
+
* @param edges - The array of all current edges
|
|
701
|
+
* @returns A new array of edges with the new edge added
|
|
702
|
+
*/
|
|
574
703
|
const addEdgeBase = (edgeParams, edges) => {
|
|
575
704
|
if (!edgeParams.source || !edgeParams.target) {
|
|
576
705
|
devWarn('006', errorMessages['error006']());
|
|
@@ -589,8 +718,22 @@ const addEdgeBase = (edgeParams, edges) => {
|
|
|
589
718
|
if (connectionExists(edge, edges)) {
|
|
590
719
|
return edges;
|
|
591
720
|
}
|
|
721
|
+
if (edge.sourceHandle === null) {
|
|
722
|
+
delete edge.sourceHandle;
|
|
723
|
+
}
|
|
724
|
+
if (edge.targetHandle === null) {
|
|
725
|
+
delete edge.targetHandle;
|
|
726
|
+
}
|
|
592
727
|
return edges.concat(edge);
|
|
593
728
|
};
|
|
729
|
+
/**
|
|
730
|
+
* A handy utility to update an existing Edge with new properties
|
|
731
|
+
* @param oldEdge - The edge you want to update
|
|
732
|
+
* @param newConnection - The new connection you want to update the edge with
|
|
733
|
+
* @param edges - The array of all current edges
|
|
734
|
+
* @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id
|
|
735
|
+
* @returns the updated edges array
|
|
736
|
+
*/
|
|
594
737
|
const updateEdgeBase = (oldEdge, newConnection, edges, options = { shouldReplaceId: true }) => {
|
|
595
738
|
const { id: oldEdgeId, ...rest } = oldEdge;
|
|
596
739
|
if (!newConnection.source || !newConnection.target) {
|
|
@@ -614,6 +757,26 @@ const updateEdgeBase = (oldEdge, newConnection, edges, options = { shouldReplace
|
|
|
614
757
|
return edges.filter((e) => e.id !== oldEdgeId).concat(edge);
|
|
615
758
|
};
|
|
616
759
|
|
|
760
|
+
/**
|
|
761
|
+
* Get a straight path from source to target handle
|
|
762
|
+
* @param params.sourceX - The x position of the source handle
|
|
763
|
+
* @param params.sourceY - The y position of the source handle
|
|
764
|
+
* @param params.targetX - The x position of the target handle
|
|
765
|
+
* @param params.targetY - The y position of the target handle
|
|
766
|
+
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
|
|
767
|
+
* @example
|
|
768
|
+
* const source = { x: 0, y: 20 };
|
|
769
|
+
const target = { x: 150, y: 100 };
|
|
770
|
+
|
|
771
|
+
const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({
|
|
772
|
+
sourceX: source.x,
|
|
773
|
+
sourceY: source.y,
|
|
774
|
+
sourcePosition: Position.Right,
|
|
775
|
+
targetX: target.x,
|
|
776
|
+
targetY: target.y,
|
|
777
|
+
targetPosition: Position.Left,
|
|
778
|
+
});
|
|
779
|
+
*/
|
|
617
780
|
function getStraightPath({ sourceX, sourceY, targetX, targetY, }) {
|
|
618
781
|
const [labelX, labelY, offsetX, offsetY] = getEdgeCenter({
|
|
619
782
|
sourceX,
|
|
@@ -762,6 +925,28 @@ function getBend(a, b, c, size) {
|
|
|
762
925
|
const yDir = a.y < c.y ? -1 : 1;
|
|
763
926
|
return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}`;
|
|
764
927
|
}
|
|
928
|
+
/**
|
|
929
|
+
* Get a smooth step path from source to target handle
|
|
930
|
+
* @param params.sourceX - The x position of the source handle
|
|
931
|
+
* @param params.sourceY - The y position of the source handle
|
|
932
|
+
* @param params.sourcePosition - The position of the source handle (default: Position.Bottom)
|
|
933
|
+
* @param params.targetX - The x position of the target handle
|
|
934
|
+
* @param params.targetY - The y position of the target handle
|
|
935
|
+
* @param params.targetPosition - The position of the target handle (default: Position.Top)
|
|
936
|
+
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
|
|
937
|
+
* @example
|
|
938
|
+
* const source = { x: 0, y: 20 };
|
|
939
|
+
const target = { x: 150, y: 100 };
|
|
940
|
+
|
|
941
|
+
const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({
|
|
942
|
+
sourceX: source.x,
|
|
943
|
+
sourceY: source.y,
|
|
944
|
+
sourcePosition: Position.Right,
|
|
945
|
+
targetX: target.x,
|
|
946
|
+
targetY: target.y,
|
|
947
|
+
targetPosition: Position.Left,
|
|
948
|
+
});
|
|
949
|
+
*/
|
|
765
950
|
function getSmoothStepPath({ sourceX, sourceY, sourcePosition = Position.Bottom, targetX, targetY, targetPosition = Position.Top, borderRadius = 5, centerX, centerY, offset = 20, }) {
|
|
766
951
|
const [points, labelX, labelY, offsetX, offsetY] = getPoints({
|
|
767
952
|
source: { x: sourceX, y: sourceY },
|
|
@@ -785,18 +970,22 @@ function getSmoothStepPath({ sourceX, sourceY, sourcePosition = Position.Bottom,
|
|
|
785
970
|
return [path, labelX, labelY, offsetX, offsetY];
|
|
786
971
|
}
|
|
787
972
|
|
|
973
|
+
function isNodeInitialized(node) {
|
|
974
|
+
return !!(node?.[internalsSymbol]?.handleBounds || node?.handles?.length) && !!(node?.computed?.width || node?.width);
|
|
975
|
+
}
|
|
788
976
|
function getEdgePosition(params) {
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
if (!isSourceValid || !isTargetValid) {
|
|
977
|
+
const { sourceNode, targetNode } = params;
|
|
978
|
+
if (!isNodeInitialized(sourceNode) || !isNodeInitialized(targetNode)) {
|
|
792
979
|
return null;
|
|
793
980
|
}
|
|
981
|
+
const sourceHandleBounds = sourceNode[internalsSymbol]?.handleBounds || toHandleBounds(sourceNode.handles);
|
|
982
|
+
const targetHandleBounds = targetNode[internalsSymbol]?.handleBounds || toHandleBounds(targetNode.handles);
|
|
983
|
+
const sourceHandle = getHandle(sourceHandleBounds?.source ?? [], params.sourceHandle);
|
|
984
|
+
const targetHandle = getHandle(
|
|
794
985
|
// when connection type is loose we can define all handles as sources and connect source -> source
|
|
795
|
-
|
|
796
|
-
? targetHandleBounds
|
|
797
|
-
: (targetHandleBounds
|
|
798
|
-
const sourceHandle = getHandle(sourceHandleBounds.source, params.sourceHandle);
|
|
799
|
-
const targetHandle = getHandle(targetNodeHandles, params.targetHandle);
|
|
986
|
+
params.connectionMode === ConnectionMode.Strict
|
|
987
|
+
? targetHandleBounds?.target ?? []
|
|
988
|
+
: (targetHandleBounds?.target ?? []).concat(targetHandleBounds?.source ?? []), params.targetHandle);
|
|
800
989
|
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
|
801
990
|
const targetPosition = targetHandle?.position || Position.Top;
|
|
802
991
|
if (!sourceHandle || !targetHandle) {
|
|
@@ -807,8 +996,8 @@ function getEdgePosition(params) {
|
|
|
807
996
|
}));
|
|
808
997
|
return null;
|
|
809
998
|
}
|
|
810
|
-
const
|
|
811
|
-
const
|
|
999
|
+
const [sourceX, sourceY] = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
|
|
1000
|
+
const [targetX, targetY] = getHandlePosition(targetPosition, targetNode, targetHandle);
|
|
812
1001
|
return {
|
|
813
1002
|
sourceX,
|
|
814
1003
|
sourceY,
|
|
@@ -822,67 +1011,37 @@ function toHandleBounds(handles) {
|
|
|
822
1011
|
if (!handles) {
|
|
823
1012
|
return null;
|
|
824
1013
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1014
|
+
const source = [];
|
|
1015
|
+
const target = [];
|
|
1016
|
+
for (const handle of handles) {
|
|
1017
|
+
handle.width = handle.width || 1;
|
|
1018
|
+
handle.height = handle.height || 1;
|
|
1019
|
+
if (handle.type === 'source') {
|
|
1020
|
+
source.push(handle);
|
|
830
1021
|
}
|
|
831
|
-
if (
|
|
832
|
-
|
|
1022
|
+
else if (handle.type === 'target') {
|
|
1023
|
+
target.push(handle);
|
|
833
1024
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
source
|
|
837
|
-
target
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
function getHandleDataByNode(node) {
|
|
841
|
-
const handleBounds = node?.[internalsSymbol]?.handleBounds || toHandleBounds(node?.handles) || null;
|
|
842
|
-
const nodeWidth = node?.computed?.width || node?.width;
|
|
843
|
-
const nodeHeight = node?.computed?.height || node?.height;
|
|
844
|
-
const isValid = handleBounds &&
|
|
845
|
-
nodeWidth &&
|
|
846
|
-
nodeHeight &&
|
|
847
|
-
typeof node?.computed?.positionAbsolute?.x !== 'undefined' &&
|
|
848
|
-
typeof node?.computed?.positionAbsolute?.y !== 'undefined';
|
|
849
|
-
return [
|
|
850
|
-
{
|
|
851
|
-
x: node?.computed?.positionAbsolute?.x || 0,
|
|
852
|
-
y: node?.computed?.positionAbsolute?.y || 0,
|
|
853
|
-
width: nodeWidth || 0,
|
|
854
|
-
height: nodeHeight || 0,
|
|
855
|
-
},
|
|
856
|
-
handleBounds,
|
|
857
|
-
!!isValid,
|
|
858
|
-
];
|
|
1025
|
+
}
|
|
1026
|
+
return {
|
|
1027
|
+
source,
|
|
1028
|
+
target,
|
|
1029
|
+
};
|
|
859
1030
|
}
|
|
860
|
-
function getHandlePosition(position,
|
|
861
|
-
const x = (handle?.x
|
|
862
|
-
const y = (handle?.y
|
|
863
|
-
const width = handle?.width ||
|
|
864
|
-
const height = handle?.height ||
|
|
1031
|
+
function getHandlePosition(position, node, handle = null) {
|
|
1032
|
+
const x = (handle?.x ?? 0) + (node.computed?.positionAbsolute?.x ?? 0);
|
|
1033
|
+
const y = (handle?.y ?? 0) + (node.computed?.positionAbsolute?.y ?? 0);
|
|
1034
|
+
const width = handle?.width || (node?.computed?.width ?? node?.width ?? 0);
|
|
1035
|
+
const height = handle?.height || (node?.computed?.height ?? node?.height ?? 0);
|
|
865
1036
|
switch (position) {
|
|
866
1037
|
case Position.Top:
|
|
867
|
-
return
|
|
868
|
-
x: x + width / 2,
|
|
869
|
-
y,
|
|
870
|
-
};
|
|
1038
|
+
return [x + width / 2, y];
|
|
871
1039
|
case Position.Right:
|
|
872
|
-
return
|
|
873
|
-
x: x + width,
|
|
874
|
-
y: y + height / 2,
|
|
875
|
-
};
|
|
1040
|
+
return [x + width, y + height / 2];
|
|
876
1041
|
case Position.Bottom:
|
|
877
|
-
return
|
|
878
|
-
x: x + width / 2,
|
|
879
|
-
y: y + height,
|
|
880
|
-
};
|
|
1042
|
+
return [x + width / 2, y + height];
|
|
881
1043
|
case Position.Left:
|
|
882
|
-
return
|
|
883
|
-
x,
|
|
884
|
-
y: y + height / 2,
|
|
885
|
-
};
|
|
1044
|
+
return [x, y + height / 2];
|
|
886
1045
|
}
|
|
887
1046
|
}
|
|
888
1047
|
function getHandle(bounds, handleId) {
|
|
@@ -979,10 +1138,13 @@ function updateAbsolutePositions(nodes, nodeLookup, nodeOrigin = [0, 0], parentN
|
|
|
979
1138
|
...node.position,
|
|
980
1139
|
z: node[internalsSymbol]?.z ?? 0,
|
|
981
1140
|
}, parentNode?.origin || nodeOrigin);
|
|
982
|
-
node.computed
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1141
|
+
const positionChanged = x !== node.computed?.positionAbsolute?.x || y !== node.computed?.positionAbsolute?.y;
|
|
1142
|
+
node.computed.positionAbsolute = positionChanged
|
|
1143
|
+
? {
|
|
1144
|
+
x,
|
|
1145
|
+
y,
|
|
1146
|
+
}
|
|
1147
|
+
: node.computed?.positionAbsolute;
|
|
986
1148
|
node[internalsSymbol].z = z;
|
|
987
1149
|
if (parentNodes?.[node.id]) {
|
|
988
1150
|
node[internalsSymbol].isParent = true;
|
|
@@ -991,15 +1153,21 @@ function updateAbsolutePositions(nodes, nodeLookup, nodeOrigin = [0, 0], parentN
|
|
|
991
1153
|
return node;
|
|
992
1154
|
});
|
|
993
1155
|
}
|
|
994
|
-
function
|
|
1156
|
+
function adoptUserProvidedNodes(nodes, nodeLookup, options = {
|
|
995
1157
|
nodeOrigin: [0, 0],
|
|
996
1158
|
elevateNodesOnSelect: true,
|
|
997
1159
|
defaults: {},
|
|
998
1160
|
}) {
|
|
1161
|
+
const tmpLookup = new Map(nodeLookup);
|
|
1162
|
+
nodeLookup.clear();
|
|
999
1163
|
const parentNodes = {};
|
|
1000
1164
|
const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
|
|
1001
1165
|
const nextNodes = nodes.map((n) => {
|
|
1002
|
-
const currentStoreNode =
|
|
1166
|
+
const currentStoreNode = tmpLookup.get(n.id);
|
|
1167
|
+
if (n === currentStoreNode?.[internalsSymbol]?.userProvidedNode) {
|
|
1168
|
+
nodeLookup.set(n.id, currentStoreNode);
|
|
1169
|
+
return currentStoreNode;
|
|
1170
|
+
}
|
|
1003
1171
|
const node = {
|
|
1004
1172
|
...options.defaults,
|
|
1005
1173
|
...n,
|
|
@@ -1019,6 +1187,7 @@ function updateNodes(nodes, nodeLookup, options = {
|
|
|
1019
1187
|
value: {
|
|
1020
1188
|
handleBounds: currInternals?.handleBounds,
|
|
1021
1189
|
z,
|
|
1190
|
+
userProvidedNode: n,
|
|
1022
1191
|
},
|
|
1023
1192
|
});
|
|
1024
1193
|
nodeLookup.set(node.id, node);
|
|
@@ -1093,6 +1262,21 @@ function panBy({ delta, panZoom, transform, translateExtent, width, height, }) {
|
|
|
1093
1262
|
(nextViewport.x !== transform[0] || nextViewport.y !== transform[1] || nextViewport.k !== transform[2]);
|
|
1094
1263
|
return transformChanged;
|
|
1095
1264
|
}
|
|
1265
|
+
function updateConnectionLookup(connectionLookup, edgeLookup, edges) {
|
|
1266
|
+
connectionLookup.clear();
|
|
1267
|
+
edgeLookup.clear();
|
|
1268
|
+
for (const edge of edges) {
|
|
1269
|
+
const { source, target, sourceHandle = null, targetHandle = null } = edge;
|
|
1270
|
+
const sourceKey = `${source}-source-${sourceHandle}`;
|
|
1271
|
+
const targetKey = `${target}-target-${targetHandle}`;
|
|
1272
|
+
const prevSource = connectionLookup.get(sourceKey) || new Map();
|
|
1273
|
+
const prevTarget = connectionLookup.get(targetKey) || new Map();
|
|
1274
|
+
const connection = { source, target, sourceHandle, targetHandle };
|
|
1275
|
+
edgeLookup.set(edge.id, edge);
|
|
1276
|
+
connectionLookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
|
|
1277
|
+
connectionLookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1096
1280
|
|
|
1097
1281
|
function wrapSelectionDragFunc(selectionFunc) {
|
|
1098
1282
|
return (event, _, nodes) => selectionFunc?.(event, nodes);
|
|
@@ -1424,7 +1608,6 @@ function getConnectionStatus(isInsideConnectionRadius, isHandleValid) {
|
|
|
1424
1608
|
return connectionStatus;
|
|
1425
1609
|
}
|
|
1426
1610
|
|
|
1427
|
-
const nullConnection = { source: null, target: null, sourceHandle: null, targetHandle: null };
|
|
1428
1611
|
const alwaysValid = () => true;
|
|
1429
1612
|
let connectionStartHandle = null;
|
|
1430
1613
|
function onPointerDown(event, { connectionMode, connectionRadius, handleId, nodeId, edgeUpdaterType, isTarget, domNode, nodes, lib, autoPanOnConnect, panBy, cancelConnection, onConnectStart, onConnect, onConnectEnd, isValidConnection = alwaysValid, onEdgeUpdateEnd, updateConnection, getTransform, }) {
|
|
@@ -1509,7 +1692,7 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
|
|
|
1509
1692
|
if (!closestHandle && !isValid && !handleDomNode) {
|
|
1510
1693
|
return resetRecentHandle(prevActiveHandle, lib);
|
|
1511
1694
|
}
|
|
1512
|
-
if (connection
|
|
1695
|
+
if (connection?.source !== connection?.target && handleDomNode) {
|
|
1513
1696
|
resetRecentHandle(prevActiveHandle, lib);
|
|
1514
1697
|
prevActiveHandle = handleDomNode;
|
|
1515
1698
|
handleDomNode.classList.add('connecting', `${lib}-flow__handle-connecting`);
|
|
@@ -1557,7 +1740,7 @@ function isValidHandle(event, { handle, connectionMode, fromNodeId, fromHandleId
|
|
|
1557
1740
|
const result = {
|
|
1558
1741
|
handleDomNode: handleToCheck,
|
|
1559
1742
|
isValid: false,
|
|
1560
|
-
connection:
|
|
1743
|
+
connection: null,
|
|
1561
1744
|
endHandle: null,
|
|
1562
1745
|
};
|
|
1563
1746
|
if (handleToCheck) {
|
|
@@ -1566,6 +1749,9 @@ function isValidHandle(event, { handle, connectionMode, fromNodeId, fromHandleId
|
|
|
1566
1749
|
const handleId = handleToCheck.getAttribute('data-handleid');
|
|
1567
1750
|
const connectable = handleToCheck.classList.contains('connectable');
|
|
1568
1751
|
const connectableEnd = handleToCheck.classList.contains('connectableend');
|
|
1752
|
+
if (!handleNodeId) {
|
|
1753
|
+
return result;
|
|
1754
|
+
}
|
|
1569
1755
|
const connection = {
|
|
1570
1756
|
source: isTarget ? handleNodeId : fromNodeId,
|
|
1571
1757
|
sourceHandle: isTarget ? handleId : fromHandleId,
|
|
@@ -1614,8 +1800,7 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
|
|
|
1614
1800
|
if (event.sourceEvent.type !== 'mousemove' || !panZoom) {
|
|
1615
1801
|
return;
|
|
1616
1802
|
}
|
|
1617
|
-
|
|
1618
|
-
const moveScale = getViewScale() * Math.max(1, transform[2]) * (inversePan ? -1 : 1);
|
|
1803
|
+
const moveScale = getViewScale() * Math.max(transform[2], Math.log(transform[2])) * (inversePan ? -1 : 1);
|
|
1619
1804
|
const position = {
|
|
1620
1805
|
x: transform[0] - event.sourceEvent.movementX * moveScale,
|
|
1621
1806
|
y: transform[1] - event.sourceEvent.movementY * moveScale,
|
|
@@ -1988,4 +2173,4 @@ function XYPanZoom({ domNode, minZoom, maxZoom, translateExtent, viewport, onPan
|
|
|
1988
2173
|
};
|
|
1989
2174
|
}
|
|
1990
2175
|
|
|
1991
|
-
export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, SelectionMode, XYDrag, XYHandle, XYMinimap, XYPanZoom, addEdgeBase, boxToRect, calcAutoPan, calcNextPosition, clamp, clampPosition, createMarkerIds, devWarn, elementSelectionKeys, errorMessages, fitView, getBezierEdgeCenter, getBezierPath, getBoundsOfBoxes, getBoundsOfRects, getConnectedEdgesBase, getDimensions, getEdgeCenter, getEdgePosition, getElementsToRemove, getEventPosition, getHandleBounds, getHostForElement, getIncomersBase, getMarkerId, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesInside, getOutgoersBase, getOverlappingArea, getPointerPosition, getPositionWithOrigin, getSmoothStepPath, getStraightPath, getViewportForBounds,
|
|
2176
|
+
export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, SelectionMode, XYDrag, XYHandle, XYMinimap, XYPanZoom, addEdgeBase, adoptUserProvidedNodes, areConnectionMapsEqual, boxToRect, calcAutoPan, calcNextPosition, clamp, clampPosition, createMarkerIds, devWarn, elementSelectionKeys, errorMessages, fitView, getBezierEdgeCenter, getBezierPath, getBoundsOfBoxes, getBoundsOfRects, getConnectedEdgesBase, getDimensions, getEdgeCenter, getEdgePosition, getElementsToRemove, getElevatedEdgeZIndex, getEventPosition, getHandleBounds, getHostForElement, getIncomersBase, getMarkerId, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesInside, getOutgoersBase, getOverlappingArea, getPointerPosition, getPositionWithOrigin, getSmoothStepPath, getStraightPath, getViewportForBounds, handleConnectionChange, infiniteExtent, internalsSymbol, isEdgeBase, isEdgeVisible, isInputDOMNode, isMacOs, isMouseEvent, isNodeBase, isNumeric, isRectObject, nodeToBox, nodeToRect, panBy, pointToRendererPoint, rectToBox, rendererPointToPoint, snapPosition, updateAbsolutePositions, updateConnectionLookup, updateEdgeBase, updateNodeDimensions };
|