@xyflow/system 0.0.29 → 0.0.31

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.
Files changed (66) hide show
  1. package/dist/esm/index.js +281 -230
  2. package/dist/esm/index.mjs +282 -230
  3. package/dist/esm/types/general.d.ts +30 -11
  4. package/dist/esm/types/general.d.ts.map +1 -1
  5. package/dist/esm/types/handles.d.ts +4 -15
  6. package/dist/esm/types/handles.d.ts.map +1 -1
  7. package/dist/esm/types/nodes.d.ts +6 -5
  8. package/dist/esm/types/nodes.d.ts.map +1 -1
  9. package/dist/esm/types/utils.d.ts +6 -0
  10. package/dist/esm/types/utils.d.ts.map +1 -1
  11. package/dist/esm/utils/connections.d.ts +1 -0
  12. package/dist/esm/utils/connections.d.ts.map +1 -1
  13. package/dist/esm/utils/dom.d.ts +2 -2
  14. package/dist/esm/utils/dom.d.ts.map +1 -1
  15. package/dist/esm/utils/edges/general.d.ts +3 -3
  16. package/dist/esm/utils/edges/general.d.ts.map +1 -1
  17. package/dist/esm/utils/edges/positions.d.ts +3 -0
  18. package/dist/esm/utils/edges/positions.d.ts.map +1 -1
  19. package/dist/esm/utils/general.d.ts +4 -8
  20. package/dist/esm/utils/general.d.ts.map +1 -1
  21. package/dist/esm/utils/graph.d.ts +3 -7
  22. package/dist/esm/utils/graph.d.ts.map +1 -1
  23. package/dist/esm/utils/store.d.ts +2 -2
  24. package/dist/esm/utils/store.d.ts.map +1 -1
  25. package/dist/esm/xydrag/XYDrag.d.ts.map +1 -1
  26. package/dist/esm/xyhandle/XYHandle.d.ts +1 -46
  27. package/dist/esm/xyhandle/XYHandle.d.ts.map +1 -1
  28. package/dist/esm/xyhandle/types.d.ts +47 -0
  29. package/dist/esm/xyhandle/types.d.ts.map +1 -0
  30. package/dist/esm/xyhandle/utils.d.ts +5 -6
  31. package/dist/esm/xyhandle/utils.d.ts.map +1 -1
  32. package/dist/esm/xyminimap/index.d.ts.map +1 -1
  33. package/dist/esm/xyresizer/XYResizer.d.ts.map +1 -1
  34. package/dist/umd/index.js +1 -1
  35. package/dist/umd/types/general.d.ts +30 -11
  36. package/dist/umd/types/general.d.ts.map +1 -1
  37. package/dist/umd/types/handles.d.ts +4 -15
  38. package/dist/umd/types/handles.d.ts.map +1 -1
  39. package/dist/umd/types/nodes.d.ts +6 -5
  40. package/dist/umd/types/nodes.d.ts.map +1 -1
  41. package/dist/umd/types/utils.d.ts +6 -0
  42. package/dist/umd/types/utils.d.ts.map +1 -1
  43. package/dist/umd/utils/connections.d.ts +1 -0
  44. package/dist/umd/utils/connections.d.ts.map +1 -1
  45. package/dist/umd/utils/dom.d.ts +2 -2
  46. package/dist/umd/utils/dom.d.ts.map +1 -1
  47. package/dist/umd/utils/edges/general.d.ts +3 -3
  48. package/dist/umd/utils/edges/general.d.ts.map +1 -1
  49. package/dist/umd/utils/edges/positions.d.ts +3 -0
  50. package/dist/umd/utils/edges/positions.d.ts.map +1 -1
  51. package/dist/umd/utils/general.d.ts +4 -8
  52. package/dist/umd/utils/general.d.ts.map +1 -1
  53. package/dist/umd/utils/graph.d.ts +3 -7
  54. package/dist/umd/utils/graph.d.ts.map +1 -1
  55. package/dist/umd/utils/store.d.ts +2 -2
  56. package/dist/umd/utils/store.d.ts.map +1 -1
  57. package/dist/umd/xydrag/XYDrag.d.ts.map +1 -1
  58. package/dist/umd/xyhandle/XYHandle.d.ts +1 -46
  59. package/dist/umd/xyhandle/XYHandle.d.ts.map +1 -1
  60. package/dist/umd/xyhandle/types.d.ts +47 -0
  61. package/dist/umd/xyhandle/types.d.ts.map +1 -0
  62. package/dist/umd/xyhandle/utils.d.ts +5 -6
  63. package/dist/umd/xyhandle/utils.d.ts.map +1 -1
  64. package/dist/umd/xyminimap/index.d.ts.map +1 -1
  65. package/dist/umd/xyresizer/XYResizer.d.ts.map +1 -1
  66. package/package.json +3 -3
package/dist/esm/index.js CHANGED
@@ -39,6 +39,18 @@ var SelectionMode;
39
39
  SelectionMode["Partial"] = "partial";
40
40
  SelectionMode["Full"] = "full";
41
41
  })(SelectionMode || (SelectionMode = {}));
42
+ const initialConnection = {
43
+ inProgress: false,
44
+ isValid: null,
45
+ from: null,
46
+ fromHandle: null,
47
+ fromPosition: null,
48
+ fromNode: null,
49
+ to: null,
50
+ toHandle: null,
51
+ toPosition: null,
52
+ toNode: null,
53
+ };
42
54
 
43
55
  var ConnectionLineType;
44
56
  (function (ConnectionLineType) {
@@ -61,6 +73,12 @@ var Position;
61
73
  Position["Right"] = "right";
62
74
  Position["Bottom"] = "bottom";
63
75
  })(Position || (Position = {}));
76
+ const oppositePosition = {
77
+ [Position.Left]: Position.Right,
78
+ [Position.Right]: Position.Left,
79
+ [Position.Top]: Position.Bottom,
80
+ [Position.Bottom]: Position.Top,
81
+ };
64
82
 
65
83
  /**
66
84
  * @internal
@@ -101,6 +119,9 @@ function handleConnectionChange(a, b, cb) {
101
119
  cb(diff);
102
120
  }
103
121
  }
122
+ function getConnectionStatus(isValid) {
123
+ return isValid === null ? null : isValid ? 'valid' : 'invalid';
124
+ }
104
125
 
105
126
  /* eslint-disable @typescript-eslint/no-explicit-any */
106
127
  /**
@@ -162,19 +183,12 @@ const getIncomers = (node, nodes, edges) => {
162
183
  };
163
184
  const getNodePositionWithOrigin = (node, nodeOrigin = [0, 0]) => {
164
185
  const { width, height } = getNodeDimensions(node);
165
- const positionAbsolute = 'internals' in node ? node.internals.positionAbsolute : node.position;
166
- const origin = node.origin || nodeOrigin;
186
+ const origin = node.origin ?? nodeOrigin;
167
187
  const offsetX = width * origin[0];
168
188
  const offsetY = height * origin[1];
169
189
  return {
170
- position: {
171
- x: node.position.x - offsetX,
172
- y: node.position.y - offsetY,
173
- },
174
- positionAbsolute: {
175
- x: positionAbsolute.x - offsetX,
176
- y: positionAbsolute.y - offsetY,
177
- },
190
+ x: node.position.x - offsetX,
191
+ y: node.position.y - offsetY,
178
192
  };
179
193
  };
180
194
  /**
@@ -199,16 +213,14 @@ const getNodesBounds = (nodes, params = { nodeOrigin: [0, 0] }) => {
199
213
  * Determines a bounding box that contains all given nodes in an array
200
214
  * @internal
201
215
  */
202
- const getInternalNodesBounds = (nodeLookup, params = {
203
- nodeOrigin: [0, 0],
204
- }) => {
216
+ const getInternalNodesBounds = (nodeLookup, params = {}) => {
205
217
  if (nodeLookup.size === 0) {
206
218
  return { x: 0, y: 0, width: 0, height: 0 };
207
219
  }
208
220
  let box = { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity };
209
221
  nodeLookup.forEach((node) => {
210
- if (params.filter == undefined || params.filter(node)) {
211
- const nodeBox = nodeToBox(node, params.nodeOrigin);
222
+ if (params.filter === undefined || params.filter(node)) {
223
+ const nodeBox = nodeToBox(node);
212
224
  box = getBoundsOfBoxes(box, nodeBox);
213
225
  }
214
226
  });
@@ -216,7 +228,7 @@ const getInternalNodesBounds = (nodeLookup, params = {
216
228
  };
217
229
  const getNodesInside = (nodes, rect, [tx, ty, tScale] = [0, 0, 1], partially = false,
218
230
  // set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute
219
- excludeNonSelectableNodes = false, nodeOrigin = [0, 0]) => {
231
+ excludeNonSelectableNodes = false) => {
220
232
  const paneRect = {
221
233
  ...pointToRendererPoint(rect, [tx, ty, tScale]),
222
234
  width: rect.width / tScale,
@@ -230,7 +242,7 @@ excludeNonSelectableNodes = false, nodeOrigin = [0, 0]) => {
230
242
  if ((excludeNonSelectableNodes && !selectable) || hidden) {
231
243
  continue;
232
244
  }
233
- const overlappingArea = getOverlappingArea(paneRect, nodeToRect(node, nodeOrigin));
245
+ const overlappingArea = getOverlappingArea(paneRect, nodeToRect(node));
234
246
  const notInitialized = width === null || height === null;
235
247
  const partiallyVisible = partially && overlappingArea > 0;
236
248
  const area = (width ?? 0) * (height ?? 0);
@@ -254,17 +266,17 @@ const getConnectedEdges = (nodes, edges) => {
254
266
  });
255
267
  return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
256
268
  };
257
- function fitView({ nodeLookup, width, height, panZoom, minZoom, maxZoom, nodeOrigin = [0, 0] }, options) {
258
- const filteredNodes = [];
269
+ function fitView({ nodeLookup, width, height, panZoom, minZoom, maxZoom }, options) {
270
+ const filteredNodes = new Map();
259
271
  const optionNodeIds = options?.nodes ? new Set(options.nodes.map((node) => node.id)) : null;
260
272
  nodeLookup.forEach((n) => {
261
273
  const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden);
262
274
  if (isVisible && (!optionNodeIds || optionNodeIds.has(n.id))) {
263
- filteredNodes.push(n);
275
+ filteredNodes.set(n.id, n);
264
276
  }
265
277
  });
266
- if (filteredNodes.length > 0) {
267
- const bounds = getNodesBounds(filteredNodes, { nodeOrigin });
278
+ if (filteredNodes.size > 0) {
279
+ const bounds = getInternalNodesBounds(filteredNodes);
268
280
  const viewport = getViewportForBounds(bounds, width, height, options?.minZoom ?? minZoom, options?.maxZoom ?? maxZoom, options?.padding ?? 0.1);
269
281
  panZoom.setViewport(viewport, { duration: options?.duration });
270
282
  return true;
@@ -294,9 +306,8 @@ function clampNodeExtent(node, extent) {
294
306
  function calculateNodePosition({ nodeId, nextPosition, nodeLookup, nodeOrigin = [0, 0], nodeExtent, onError, }) {
295
307
  const node = nodeLookup.get(nodeId);
296
308
  const parentNode = node.parentId ? nodeLookup.get(node.parentId) : undefined;
297
- const { x: parentX, y: parentY } = parentNode
298
- ? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
299
- : { x: 0, y: 0 };
309
+ const { x: parentX, y: parentY } = parentNode ? parentNode.internals.positionAbsolute : { x: 0, y: 0 };
310
+ const origin = node.origin ?? nodeOrigin;
300
311
  let currentExtent = clampNodeExtent(node, node.extent || nodeExtent);
301
312
  if (node.extent === 'parent' && !node.expandParent) {
302
313
  if (!parentNode) {
@@ -308,12 +319,9 @@ function calculateNodePosition({ nodeId, nextPosition, nodeLookup, nodeOrigin =
308
319
  const parentWidth = parentNode.measured.width;
309
320
  const parentHeight = parentNode.measured.height;
310
321
  if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
311
- const currNodeOrigin = node.origin || nodeOrigin;
312
- const extentX = parentX + nodeWidth * currNodeOrigin[0];
313
- const extentY = parentY + nodeHeight * currNodeOrigin[1];
314
322
  currentExtent = [
315
- [extentX, extentY],
316
- [extentX + parentWidth - nodeWidth, extentY + parentHeight - nodeHeight],
323
+ [parentX, parentY],
324
+ [parentX + parentWidth - nodeWidth, parentY + parentHeight - nodeHeight],
317
325
  ];
318
326
  }
319
327
  }
@@ -329,8 +337,9 @@ function calculateNodePosition({ nodeId, nextPosition, nodeLookup, nodeOrigin =
329
337
  : nextPosition;
330
338
  return {
331
339
  position: {
332
- x: positionAbsolute.x - parentX,
333
- y: positionAbsolute.y - parentY,
340
+ // TODO: is there a better way to do this?
341
+ x: positionAbsolute.x - parentX + node.measured.width * origin[0],
342
+ y: positionAbsolute.y - parentY + node.measured.height * origin[1],
334
343
  },
335
344
  positionAbsolute,
336
345
  };
@@ -430,21 +439,25 @@ const boxToRect = ({ x, y, x2, y2 }) => ({
430
439
  height: y2 - y,
431
440
  });
432
441
  const nodeToRect = (node, nodeOrigin = [0, 0]) => {
433
- const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
442
+ const { x, y } = isInternalNodeBase(node)
443
+ ? node.internals.positionAbsolute
444
+ : getNodePositionWithOrigin(node, nodeOrigin);
434
445
  return {
435
446
  x,
436
447
  y,
437
- width: node.measured?.width ?? node.width ?? 0,
438
- height: node.measured?.height ?? node.height ?? 0,
448
+ width: node.measured?.width ?? node.width ?? node.initialWidth ?? 0,
449
+ height: node.measured?.height ?? node.height ?? node.initialHeight ?? 0,
439
450
  };
440
451
  };
441
452
  const nodeToBox = (node, nodeOrigin = [0, 0]) => {
442
- const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
453
+ const { x, y } = isInternalNodeBase(node)
454
+ ? node.internals.positionAbsolute
455
+ : getNodePositionWithOrigin(node, nodeOrigin);
443
456
  return {
444
457
  x,
445
458
  y,
446
- x2: x + (node.measured?.width ?? node.width ?? 0),
447
- y2: y + (node.measured?.height ?? node.height ?? 0),
459
+ x2: x + (node.measured?.width ?? node.width ?? node.initialWidth ?? 0),
460
+ y2: y + (node.measured?.height ?? node.height ?? node.initialHeight ?? 0),
448
461
  };
449
462
  };
450
463
  const getBoundsOfRects = (rect1, rect2) => boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2)));
@@ -463,15 +476,6 @@ const devWarn = (id, message) => {
463
476
  console.warn(`[React Flow]: ${message} Help: https://reactflow.dev/error#${id}`);
464
477
  }
465
478
  };
466
- const getPositionWithOrigin = ({ x, y, width, height, origin = [0, 0], }) => {
467
- if (!width || !height || origin[0] < 0 || origin[1] < 0 || origin[0] > 1 || origin[1] > 1) {
468
- return { x, y };
469
- }
470
- return {
471
- x: x - width * origin[0],
472
- y: y - height * origin[1],
473
- };
474
- };
475
479
  const snapPosition = (position, snapGrid = [1, 1]) => {
476
480
  return {
477
481
  x: snapGrid[0] * Math.round(position.x / snapGrid[0]),
@@ -542,7 +546,7 @@ function nodeHasDimensions(node) {
542
546
  * @param nodeOrigin
543
547
  * @returns an internal node with an absolute position
544
548
  */
545
- function evaluateAbsolutePosition(position, parentId, nodeLookup, nodeOrigin = [0, 0]) {
549
+ function evaluateAbsolutePosition(position, dimensions = { width: 0, height: 0 }, parentId, nodeLookup, nodeOrigin) {
546
550
  let nextParentId = parentId;
547
551
  const positionAbsolute = { ...position };
548
552
  while (nextParentId) {
@@ -550,10 +554,8 @@ function evaluateAbsolutePosition(position, parentId, nodeLookup, nodeOrigin = [
550
554
  nextParentId = parent?.parentId;
551
555
  if (parent) {
552
556
  const origin = parent.origin || nodeOrigin;
553
- const xOffset = (parent.measured.width ?? 0) * origin[0];
554
- const yOffset = (parent.measured.height ?? 0) * origin[1];
555
- positionAbsolute.x += parent.position.x - xOffset;
556
- positionAbsolute.y += parent.position.y - yOffset;
557
+ positionAbsolute.x += parent.internals.positionAbsolute.x - (dimensions.width ?? 0) * origin[0];
558
+ positionAbsolute.y += parent.internals.positionAbsolute.y - (dimensions.height ?? 0) * origin[1];
557
559
  }
558
560
  }
559
561
  return positionAbsolute;
@@ -596,23 +598,20 @@ const getEventPosition = (event, bounds) => {
596
598
  // The handle bounds are calculated relative to the node element.
597
599
  // We store them in the internals object of the node in order to avoid
598
600
  // unnecessary recalculations.
599
- const getHandleBounds = (selector, nodeElement, nodeBounds, zoom, nodeOrigin = [0, 0]) => {
600
- const handles = nodeElement.querySelectorAll(selector);
601
+ const getHandleBounds = (type, nodeElement, nodeBounds, zoom, nodeId) => {
602
+ const handles = nodeElement.querySelectorAll(`.${type}`);
601
603
  if (!handles || !handles.length) {
602
604
  return null;
603
605
  }
604
- const handlesArray = Array.from(handles);
605
- const nodeOffset = {
606
- x: nodeBounds.left + nodeBounds.width * nodeOrigin[0],
607
- y: nodeBounds.top + nodeBounds.height * nodeOrigin[1],
608
- };
609
- return handlesArray.map((handle) => {
606
+ return Array.from(handles).map((handle) => {
610
607
  const handleBounds = handle.getBoundingClientRect();
611
608
  return {
612
609
  id: handle.getAttribute('data-handleid'),
610
+ type,
611
+ nodeId,
613
612
  position: handle.getAttribute('data-handlepos'),
614
- x: (handleBounds.left - nodeOffset.x) / zoom,
615
- y: (handleBounds.top - nodeOffset.y) / zoom,
613
+ x: (handleBounds.left - nodeBounds.left) / zoom,
614
+ y: (handleBounds.top - nodeBounds.top) / zoom,
616
615
  ...getDimensions(handle),
617
616
  };
618
617
  });
@@ -778,14 +777,14 @@ const addEdge = (edgeParams, edges) => {
778
777
  return edges.concat(edge);
779
778
  };
780
779
  /**
781
- * A handy utility to update an existing Edge with new properties
780
+ * A handy utility to reconnect an existing edge with new properties
782
781
  * @param oldEdge - The edge you want to update
783
782
  * @param newConnection - The new connection you want to update the edge with
784
783
  * @param edges - The array of all current edges
785
784
  * @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id
786
785
  * @returns the updated edges array
787
786
  */
788
- const updateEdge = (oldEdge, newConnection, edges, options = { shouldReplaceId: true }) => {
787
+ const reconnectEdge = (oldEdge, newConnection, edges, options = { shouldReplaceId: true }) => {
789
788
  const { id: oldEdgeId, ...rest } = oldEdge;
790
789
  if (!newConnection.source || !newConnection.target) {
791
790
  devWarn('006', errorMessages['error006']());
@@ -1039,8 +1038,6 @@ function getEdgePosition(params) {
1039
1038
  params.connectionMode === ConnectionMode.Strict
1040
1039
  ? targetHandleBounds?.target ?? []
1041
1040
  : (targetHandleBounds?.target ?? []).concat(targetHandleBounds?.source ?? []), params.targetHandle);
1042
- const sourcePosition = sourceHandle?.position || Position.Bottom;
1043
- const targetPosition = targetHandle?.position || Position.Top;
1044
1041
  if (!sourceHandle || !targetHandle) {
1045
1042
  params.onError?.('008', errorMessages['error008'](!sourceHandle ? 'source' : 'target', {
1046
1043
  id: params.id,
@@ -1049,13 +1046,15 @@ function getEdgePosition(params) {
1049
1046
  }));
1050
1047
  return null;
1051
1048
  }
1052
- const [sourceX, sourceY] = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
1053
- const [targetX, targetY] = getHandlePosition(targetPosition, targetNode, targetHandle);
1049
+ const sourcePosition = sourceHandle?.position || Position.Bottom;
1050
+ const targetPosition = targetHandle?.position || Position.Top;
1051
+ const source = getHandlePosition(sourceNode, sourceHandle, sourcePosition);
1052
+ const target = getHandlePosition(targetNode, targetHandle, targetPosition);
1054
1053
  return {
1055
- sourceX,
1056
- sourceY,
1057
- targetX,
1058
- targetY,
1054
+ sourceX: source.x,
1055
+ sourceY: source.y,
1056
+ targetX: target.x,
1057
+ targetY: target.y,
1059
1058
  sourcePosition,
1060
1059
  targetPosition,
1061
1060
  };
@@ -1081,19 +1080,23 @@ function toHandleBounds(handles) {
1081
1080
  target,
1082
1081
  };
1083
1082
  }
1084
- function getHandlePosition(position, node, handle = null) {
1083
+ function getHandlePosition(node, handle, fallbackPosition = Position.Left, center = false) {
1085
1084
  const x = (handle?.x ?? 0) + node.internals.positionAbsolute.x;
1086
1085
  const y = (handle?.y ?? 0) + node.internals.positionAbsolute.y;
1087
1086
  const { width, height } = handle ?? getNodeDimensions(node);
1087
+ if (center) {
1088
+ return { x: x + width / 2, y: y + height / 2 };
1089
+ }
1090
+ const position = handle?.position ?? fallbackPosition;
1088
1091
  switch (position) {
1089
1092
  case Position.Top:
1090
- return [x + width / 2, y];
1093
+ return { x: x + width / 2, y };
1091
1094
  case Position.Right:
1092
- return [x + width, y + height / 2];
1095
+ return { x: x + width, y: y + height / 2 };
1093
1096
  case Position.Bottom:
1094
- return [x + width / 2, y + height];
1097
+ return { x: x + width / 2, y: y + height };
1095
1098
  case Position.Left:
1096
- return [x, y + height / 2];
1099
+ return { x, y: y + height / 2 };
1097
1100
  }
1098
1101
  }
1099
1102
  function getHandle(bounds, handleId) {
@@ -1174,95 +1177,98 @@ function getNodeToolbarTransform(nodeRect, viewport, position, offset, align) {
1174
1177
  return `translate(${pos[0]}px, ${pos[1]}px) translate(${shift[0]}%, ${shift[1]}%)`;
1175
1178
  }
1176
1179
 
1177
- function updateAbsolutePositions(nodeLookup, options = {
1180
+ const defaultOptions = {
1178
1181
  nodeOrigin: [0, 0],
1179
1182
  elevateNodesOnSelect: true,
1180
1183
  defaults: {},
1181
- }) {
1182
- const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1183
- for (const [, node] of nodeLookup) {
1184
- const parentId = node.parentId;
1185
- if (!parentId) {
1184
+ };
1185
+ const adoptUserNodesDefaultOptions = {
1186
+ ...defaultOptions,
1187
+ checkEquality: true,
1188
+ };
1189
+ function updateAbsolutePositions(nodeLookup, parentLookup, options) {
1190
+ const _options = { ...defaultOptions, ...options };
1191
+ for (const node of nodeLookup.values()) {
1192
+ if (!node.parentId) {
1186
1193
  continue;
1187
1194
  }
1188
- if (!nodeLookup.has(parentId)) {
1189
- throw new Error(`Parent node ${parentId} not found`);
1190
- }
1191
- const parentNode = nodeLookup.get(parentId);
1192
- const { x, y, z } = calculateXYZPosition(node, nodeLookup, {
1193
- ...node.position,
1194
- z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0),
1195
- }, parentNode?.origin ?? options.nodeOrigin);
1196
- const currPosition = node.internals.positionAbsolute;
1197
- const positionChanged = x !== currPosition.x || y !== currPosition.y;
1198
- if (positionChanged || z !== node.internals.z) {
1199
- node.internals = {
1200
- ...node.internals,
1201
- positionAbsolute: positionChanged ? { x, y } : currPosition,
1202
- z,
1203
- };
1204
- }
1195
+ updateChildPosition(node, nodeLookup, parentLookup, _options);
1205
1196
  }
1206
1197
  }
1207
- function adoptUserNodes(nodes, nodeLookup, parentLookup, options = {
1208
- nodeOrigin: [0, 0],
1209
- elevateNodesOnSelect: true,
1210
- defaults: {},
1211
- checkEquality: true,
1212
- }) {
1198
+ function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
1199
+ const _options = { ...adoptUserNodesDefaultOptions, ...options };
1213
1200
  const tmpLookup = new Map(nodeLookup);
1214
1201
  nodeLookup.clear();
1215
1202
  parentLookup.clear();
1216
1203
  const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1217
- nodes.forEach((userNode) => {
1204
+ for (const userNode of nodes) {
1218
1205
  let internalNode = tmpLookup.get(userNode.id);
1219
- if (options.checkEquality && userNode === internalNode?.internals.userNode) {
1206
+ if (_options.checkEquality && userNode === internalNode?.internals.userNode) {
1220
1207
  nodeLookup.set(userNode.id, internalNode);
1221
1208
  }
1222
1209
  else {
1223
1210
  internalNode = {
1224
- ...options.defaults,
1211
+ ..._options.defaults,
1225
1212
  ...userNode,
1226
1213
  measured: {
1227
1214
  width: userNode.measured?.width,
1228
1215
  height: userNode.measured?.height,
1229
1216
  },
1230
1217
  internals: {
1231
- positionAbsolute: userNode.position,
1218
+ positionAbsolute: getNodePositionWithOrigin(userNode, _options.nodeOrigin),
1232
1219
  handleBounds: internalNode?.internals.handleBounds,
1233
- z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0),
1220
+ z: calculateZ(userNode, selectedNodeZ),
1234
1221
  userNode,
1235
1222
  },
1236
1223
  };
1237
1224
  nodeLookup.set(userNode.id, internalNode);
1238
1225
  }
1239
1226
  if (userNode.parentId) {
1240
- const childNodes = parentLookup.get(userNode.parentId);
1241
- if (childNodes) {
1242
- childNodes.push(internalNode);
1243
- }
1244
- else {
1245
- parentLookup.set(userNode.parentId, [internalNode]);
1246
- }
1227
+ updateChildPosition(internalNode, nodeLookup, parentLookup, options);
1247
1228
  }
1248
- });
1249
- if (parentLookup.size > 0) {
1250
- updateAbsolutePositions(nodeLookup, options);
1251
1229
  }
1252
1230
  }
1253
- function calculateXYZPosition(node, nodeLookup, result, nodeOrigin = [0, 0]) {
1254
- if (!node.parentId) {
1255
- return result;
1231
+ function updateChildPosition(node, nodeLookup, parentLookup, options) {
1232
+ const _options = { ...defaultOptions, ...options };
1233
+ const parentId = node.parentId;
1234
+ const parentNode = nodeLookup.get(parentId);
1235
+ if (!parentNode) {
1236
+ throw new Error(`Parent node ${parentId} not found`);
1237
+ }
1238
+ // update the parentLookup
1239
+ const childNodes = parentLookup.get(parentId);
1240
+ if (childNodes) {
1241
+ childNodes.set(node.id, node);
1242
+ }
1243
+ else {
1244
+ parentLookup.set(parentId, new Map([[node.id, node]]));
1245
+ }
1246
+ const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1247
+ const { x, y, z } = calculateChildXYZ(node, parentNode, _options.nodeOrigin, selectedNodeZ);
1248
+ const currPosition = node.internals.positionAbsolute;
1249
+ const positionChanged = x !== currPosition.x || y !== currPosition.y;
1250
+ if (positionChanged || z !== node.internals.z) {
1251
+ node.internals = {
1252
+ ...node.internals,
1253
+ positionAbsolute: positionChanged ? { x, y } : currPosition,
1254
+ z,
1255
+ };
1256
1256
  }
1257
- const parent = nodeLookup.get(node.parentId);
1258
- const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin).position;
1259
- return calculateXYZPosition(parent, nodeLookup, {
1260
- x: (result.x ?? 0) + parentPosition.x,
1261
- y: (result.y ?? 0) + parentPosition.y,
1262
- z: (parent.internals.z ?? 0) > (result.z ?? 0) ? parent.internals.z ?? 0 : result.z ?? 0,
1263
- }, parent.origin || nodeOrigin);
1264
1257
  }
1265
- function handleExpandParent(children, nodeLookup, parentLookup, nodeOrigin) {
1258
+ function calculateZ(node, selectedNodeZ) {
1259
+ return (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0);
1260
+ }
1261
+ function calculateChildXYZ(childNode, parentNode, nodeOrigin, selectedNodeZ) {
1262
+ const position = getNodePositionWithOrigin(childNode, nodeOrigin);
1263
+ const childZ = calculateZ(childNode, selectedNodeZ);
1264
+ const parentZ = parentNode.internals.z ?? 0;
1265
+ return {
1266
+ x: parentNode.internals.positionAbsolute.x + position.x,
1267
+ y: parentNode.internals.positionAbsolute.y + position.y,
1268
+ z: parentZ > childZ ? parentZ : childZ,
1269
+ };
1270
+ }
1271
+ function handleExpandParent(children, nodeLookup, parentLookup, nodeOrigin = [0, 0]) {
1266
1272
  const changes = [];
1267
1273
  const parentExpansions = new Map();
1268
1274
  // determine the expanded rectangle the child nodes would take for each parent
@@ -1271,31 +1277,36 @@ function handleExpandParent(children, nodeLookup, parentLookup, nodeOrigin) {
1271
1277
  if (!parent) {
1272
1278
  continue;
1273
1279
  }
1274
- const parentRect = parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent, parent.origin ?? nodeOrigin);
1280
+ const parentRect = parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent);
1275
1281
  const expandedRect = getBoundsOfRects(parentRect, child.rect);
1276
1282
  parentExpansions.set(child.parentId, { expandedRect, parent });
1277
1283
  }
1278
1284
  if (parentExpansions.size > 0) {
1279
1285
  parentExpansions.forEach(({ expandedRect, parent }, parentId) => {
1280
1286
  // determine the position & dimensions of the parent
1281
- const { position } = getNodePositionWithOrigin(parent, parent.origin);
1287
+ const positionAbsolute = parent.internals.positionAbsolute;
1282
1288
  const dimensions = getNodeDimensions(parent);
1283
- // determine how much the parent expands by moving the position
1284
- const xChange = expandedRect.x < position.x ? Math.round(Math.abs(position.x - expandedRect.x)) : 0;
1285
- const yChange = expandedRect.y < position.y ? Math.round(Math.abs(position.y - expandedRect.y)) : 0;
1286
- if (xChange > 0 || yChange > 0) {
1289
+ const origin = parent.origin ?? nodeOrigin;
1290
+ // determine how much the parent expands in width and position
1291
+ const xChange = expandedRect.x < positionAbsolute.x ? Math.round(Math.abs(positionAbsolute.x - expandedRect.x)) : 0;
1292
+ const yChange = expandedRect.y < positionAbsolute.y ? Math.round(Math.abs(positionAbsolute.y - expandedRect.y)) : 0;
1293
+ const newWidth = Math.max(dimensions.width, Math.round(expandedRect.width));
1294
+ const newHeight = Math.max(dimensions.height, Math.round(expandedRect.height));
1295
+ const widthChange = (newWidth - dimensions.width) * origin[0];
1296
+ const heightChange = (newHeight - dimensions.height) * origin[1];
1297
+ // We need to correct the position of the parent node if the origin is not [0,0]
1298
+ if (xChange > 0 || yChange > 0 || widthChange || heightChange) {
1287
1299
  changes.push({
1288
1300
  id: parentId,
1289
1301
  type: 'position',
1290
1302
  position: {
1291
- x: position.x - xChange,
1292
- y: position.y - yChange,
1303
+ x: parent.position.x - xChange + widthChange,
1304
+ y: parent.position.y - yChange + heightChange,
1293
1305
  },
1294
1306
  });
1295
1307
  // We move all child nodes in the oppsite direction
1296
1308
  // so the x,y changes of the parent do not move the children
1297
- const childNodes = parentLookup.get(parentId);
1298
- childNodes?.forEach((childNode) => {
1309
+ parentLookup.get(parentId)?.forEach((childNode) => {
1299
1310
  if (!children.some((child) => child.id === childNode.id)) {
1300
1311
  changes.push({
1301
1312
  id: childNode.id,
@@ -1308,14 +1319,15 @@ function handleExpandParent(children, nodeLookup, parentLookup, nodeOrigin) {
1308
1319
  }
1309
1320
  });
1310
1321
  }
1311
- if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height) {
1322
+ // We need to correct the dimensions of the parent node if the origin is not [0,0]
1323
+ if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height || xChange || yChange) {
1312
1324
  changes.push({
1313
1325
  id: parentId,
1314
1326
  type: 'dimensions',
1315
1327
  setAttributes: true,
1316
1328
  dimensions: {
1317
- width: Math.max(dimensions.width, Math.round(expandedRect.width)),
1318
- height: Math.max(dimensions.height, Math.round(expandedRect.height)),
1329
+ width: newWidth + (xChange ? origin[0] * xChange - widthChange : 0),
1330
+ height: newHeight + (yChange ? origin[1] * yChange - heightChange : 0),
1319
1331
  },
1320
1332
  });
1321
1333
  }
@@ -1334,16 +1346,19 @@ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOri
1334
1346
  const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
1335
1347
  // in this array we collect nodes, that might trigger changes (like expanding parent)
1336
1348
  const parentExpandChildren = [];
1337
- updates.forEach((update) => {
1349
+ for (const update of updates.values()) {
1338
1350
  const node = nodeLookup.get(update.id);
1339
- if (node?.hidden) {
1351
+ if (!node) {
1352
+ continue;
1353
+ }
1354
+ if (node.hidden) {
1340
1355
  node.internals = {
1341
1356
  ...node.internals,
1342
1357
  handleBounds: undefined,
1343
1358
  };
1344
1359
  updatedInternals = true;
1345
1360
  }
1346
- else if (node) {
1361
+ else {
1347
1362
  const dimensions = getDimensions(update.nodeElement);
1348
1363
  const dimensionChanged = node.measured.width !== dimensions.width || node.measured.height !== dimensions.height;
1349
1364
  const doUpdate = !!(dimensions.width &&
@@ -1354,11 +1369,15 @@ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOri
1354
1369
  node.measured = dimensions;
1355
1370
  node.internals = {
1356
1371
  ...node.internals,
1372
+ positionAbsolute: getNodePositionWithOrigin(node, nodeOrigin),
1357
1373
  handleBounds: {
1358
- source: getHandleBounds('.source', update.nodeElement, nodeBounds, zoom, node.origin || nodeOrigin),
1359
- target: getHandleBounds('.target', update.nodeElement, nodeBounds, zoom, node.origin || nodeOrigin),
1374
+ source: getHandleBounds('source', update.nodeElement, nodeBounds, zoom, node.id),
1375
+ target: getHandleBounds('target', update.nodeElement, nodeBounds, zoom, node.id),
1360
1376
  },
1361
1377
  };
1378
+ if (node.parentId) {
1379
+ updateChildPosition(node, nodeLookup, parentLookup, { nodeOrigin });
1380
+ }
1362
1381
  updatedInternals = true;
1363
1382
  if (dimensionChanged) {
1364
1383
  changes.push({
@@ -1376,7 +1395,7 @@ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOri
1376
1395
  }
1377
1396
  }
1378
1397
  }
1379
- });
1398
+ }
1380
1399
  if (parentExpandChildren.length > 0) {
1381
1400
  const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
1382
1401
  changes.push(...parentExpandChanges);
@@ -1536,7 +1555,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1536
1555
  let hasChange = false;
1537
1556
  let nodesBox = { x: 0, y: 0, x2: 0, y2: 0 };
1538
1557
  if (dragItems.size > 1 && nodeExtent) {
1539
- const rect = getInternalNodesBounds(dragItems, { nodeOrigin });
1558
+ const rect = getInternalNodesBounds(dragItems);
1540
1559
  nodesBox = rectToBox(rect);
1541
1560
  }
1542
1561
  for (const [id, dragItem] of dragItems) {
@@ -1719,18 +1738,18 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1719
1738
  // this functions collects all handles and adds an absolute position
1720
1739
  // so that we can later find the closest handle to the mouse position
1721
1740
  function getHandles(node, handleBounds, type, currentHandle) {
1722
- return (handleBounds[type] || []).reduce((res, h) => {
1723
- if (`${node.id}-${h.id}-${type}` !== currentHandle) {
1724
- res.push({
1725
- id: h.id || null,
1726
- type,
1727
- nodeId: node.id,
1728
- x: node.internals.positionAbsolute.x + h.x + h.width / 2,
1729
- y: node.internals.positionAbsolute.y + h.y + h.height / 2,
1730
- });
1741
+ let excludedHandle = null;
1742
+ const handles = (handleBounds[type] || []).reduce((res, handle) => {
1743
+ if (node.id === currentHandle.nodeId && type === currentHandle.handleType && handle.id === currentHandle.handleId) {
1744
+ excludedHandle = handle;
1745
+ }
1746
+ else {
1747
+ const handleXY = getHandlePosition(node, handle, handle.position, true);
1748
+ res.push({ ...handle, ...handleXY });
1731
1749
  }
1732
1750
  return res;
1733
1751
  }, []);
1752
+ return [handles, excludedHandle];
1734
1753
  }
1735
1754
  function getClosestHandle(pos, connectionRadius, handles) {
1736
1755
  let closestHandles = [];
@@ -1758,15 +1777,17 @@ function getClosestHandle(pos, connectionRadius, handles) {
1758
1777
  }
1759
1778
  function getHandleLookup({ nodeLookup, nodeId, handleId, handleType, }) {
1760
1779
  const connectionHandles = [];
1761
- for (const [, node] of nodeLookup) {
1780
+ const currentHandle = { nodeId, handleId, handleType };
1781
+ let excludedHandle = null;
1782
+ for (const node of nodeLookup.values()) {
1762
1783
  if (node.internals.handleBounds) {
1763
- const id = `${nodeId}-${handleId}-${handleType}`;
1764
- const sourceHandles = getHandles(node, node.internals.handleBounds, 'source', id);
1765
- const targetHandles = getHandles(node, node.internals.handleBounds, 'target', id);
1784
+ const [sourceHandles, excludedSource] = getHandles(node, node.internals.handleBounds, 'source', currentHandle);
1785
+ const [targetHandles, excludedTarget] = getHandles(node, node.internals.handleBounds, 'target', currentHandle);
1786
+ excludedHandle = excludedHandle ? excludedHandle : excludedSource ?? excludedTarget;
1766
1787
  connectionHandles.push(...sourceHandles, ...targetHandles);
1767
1788
  }
1768
1789
  }
1769
- return connectionHandles;
1790
+ return [connectionHandles, excludedHandle];
1770
1791
  }
1771
1792
  function getHandleType(edgeUpdaterType, handleDomNode) {
1772
1793
  if (edgeUpdaterType) {
@@ -1780,20 +1801,19 @@ function getHandleType(edgeUpdaterType, handleDomNode) {
1780
1801
  }
1781
1802
  return null;
1782
1803
  }
1783
- function getConnectionStatus(isInsideConnectionRadius, isHandleValid) {
1784
- let connectionStatus = null;
1804
+ function isConnectionValid(isInsideConnectionRadius, isHandleValid) {
1805
+ let isValid = null;
1785
1806
  if (isHandleValid) {
1786
- connectionStatus = 'valid';
1807
+ isValid = true;
1787
1808
  }
1788
1809
  else if (isInsideConnectionRadius && !isHandleValid) {
1789
- connectionStatus = 'invalid';
1810
+ isValid = false;
1790
1811
  }
1791
- return connectionStatus;
1812
+ return isValid;
1792
1813
  }
1793
1814
 
1794
1815
  const alwaysValid = () => true;
1795
- let connectionStartHandle = null;
1796
- function onPointerDown(event, { connectionMode, connectionRadius, handleId, nodeId, edgeUpdaterType, isTarget, domNode, nodeLookup, lib, autoPanOnConnect, flowId, panBy, cancelConnection, onConnectStart, onConnect, onConnectEnd, isValidConnection = alwaysValid, onEdgeUpdateEnd, updateConnection, getTransform, getConnectionStartHandle, }) {
1816
+ function onPointerDown(event, { connectionMode, connectionRadius, handleId, nodeId, edgeUpdaterType, isTarget, domNode, nodeLookup, lib, autoPanOnConnect, flowId, panBy, cancelConnection, onConnectStart, onConnect, onConnectEnd, isValidConnection = alwaysValid, onReconnectEnd, updateConnection, getTransform, getFromHandle, }) {
1797
1817
  // when xyflow is used inside a shadow root we can't use document
1798
1818
  const doc = getHostForElement(event.target);
1799
1819
  let autoPanId = 0;
@@ -1805,12 +1825,12 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1805
1825
  if (!containerBounds || !handleType) {
1806
1826
  return;
1807
1827
  }
1808
- let connectionPosition = getEventPosition(event, containerBounds);
1828
+ let position = getEventPosition(event, containerBounds);
1809
1829
  let autoPanStarted = false;
1810
1830
  let connection = null;
1811
1831
  let isValid = false;
1812
1832
  let handleDomNode = null;
1813
- const handleLookup = getHandleLookup({
1833
+ const [handleLookup, fromHandleInternal] = getHandleLookup({
1814
1834
  nodeLookup,
1815
1835
  nodeId,
1816
1836
  handleId,
@@ -1821,31 +1841,42 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1821
1841
  if (!autoPanOnConnect || !containerBounds) {
1822
1842
  return;
1823
1843
  }
1824
- const [x, y] = calcAutoPan(connectionPosition, containerBounds);
1844
+ const [x, y] = calcAutoPan(position, containerBounds);
1825
1845
  panBy({ x, y });
1826
1846
  autoPanId = requestAnimationFrame(autoPan);
1827
1847
  }
1828
1848
  // Stays the same for all consecutive pointermove events
1829
- connectionStartHandle = {
1849
+ const fromHandle = {
1850
+ ...fromHandleInternal,
1830
1851
  nodeId,
1831
- handleId,
1832
1852
  type: handleType,
1853
+ position: fromHandleInternal.position,
1833
1854
  };
1834
- updateConnection({
1835
- connectionPosition,
1836
- connectionStatus: null,
1837
- // connectionNodeId etc will be removed in the next major in favor of connectionStartHandle
1838
- connectionStartHandle,
1839
- connectionEndHandle: null,
1840
- });
1855
+ const fromNodeInternal = nodeLookup.get(nodeId);
1856
+ const from = getHandlePosition(fromNodeInternal, fromHandle, Position.Left, true);
1857
+ const newConnection = {
1858
+ inProgress: true,
1859
+ isValid: null,
1860
+ from,
1861
+ fromHandle,
1862
+ fromPosition: fromHandle.position,
1863
+ fromNode: fromNodeInternal.internals.userNode,
1864
+ to: position,
1865
+ toHandle: null,
1866
+ toPosition: oppositePosition[fromHandle.position],
1867
+ toNode: null,
1868
+ };
1869
+ updateConnection(newConnection);
1870
+ let previousConnection = newConnection;
1841
1871
  onConnectStart?.(event, { nodeId, handleId, handleType });
1842
1872
  function onPointerMove(event) {
1843
- if (!getConnectionStartHandle()) {
1873
+ if (!getFromHandle() || !fromHandle) {
1844
1874
  onPointerUp(event);
1875
+ return;
1845
1876
  }
1846
1877
  const transform = getTransform();
1847
- connectionPosition = getEventPosition(event, containerBounds);
1848
- closestHandle = getClosestHandle(pointToRendererPoint(connectionPosition, transform, false, [1, 1]), connectionRadius, handleLookup);
1878
+ position = getEventPosition(event, containerBounds);
1879
+ closestHandle = getClosestHandle(pointToRendererPoint(position, transform, false, [1, 1]), connectionRadius, handleLookup);
1849
1880
  if (!autoPanStarted) {
1850
1881
  autoPan();
1851
1882
  autoPanStarted = true;
@@ -1860,21 +1891,34 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1860
1891
  doc,
1861
1892
  lib,
1862
1893
  flowId,
1894
+ handleLookup,
1863
1895
  });
1864
1896
  handleDomNode = result.handleDomNode;
1865
1897
  connection = result.connection;
1866
- isValid = result.isValid;
1867
- updateConnection({
1868
- connectionStartHandle,
1869
- connectionPosition: closestHandle && isValid
1870
- ? rendererPointToPoint({
1871
- x: closestHandle.x,
1872
- y: closestHandle.y,
1873
- }, transform)
1874
- : connectionPosition,
1875
- connectionStatus: getConnectionStatus(!!closestHandle, isValid),
1876
- connectionEndHandle: result.endHandle,
1877
- });
1898
+ isValid = isConnectionValid(!!closestHandle, result.isValid);
1899
+ const newConnection = {
1900
+ // from stays the same
1901
+ ...previousConnection,
1902
+ isValid,
1903
+ to: closestHandle && isValid
1904
+ ? rendererPointToPoint({ x: closestHandle.x, y: closestHandle.y }, transform)
1905
+ : position,
1906
+ toHandle: result.toHandle,
1907
+ toPosition: isValid && result.toHandle ? result.toHandle.position : oppositePosition[fromHandle.position],
1908
+ toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId).internals.userNode : null,
1909
+ };
1910
+ // we don't want to trigger an update when the connection
1911
+ // is snapped to the same handle as before
1912
+ if (isValid &&
1913
+ closestHandle &&
1914
+ previousConnection.toHandle &&
1915
+ newConnection.toHandle &&
1916
+ previousConnection.toHandle.nodeId === newConnection.toHandle.nodeId &&
1917
+ previousConnection.toHandle.id === newConnection.toHandle.id) {
1918
+ return;
1919
+ }
1920
+ updateConnection(newConnection);
1921
+ previousConnection = newConnection;
1878
1922
  }
1879
1923
  function onPointerUp(event) {
1880
1924
  if ((closestHandle || handleDomNode) && connection && isValid) {
@@ -1884,7 +1928,7 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1884
1928
  // in order to get the latest state of onConnectEnd
1885
1929
  onConnectEnd?.(event);
1886
1930
  if (edgeUpdaterType) {
1887
- onEdgeUpdateEnd?.(event);
1931
+ onReconnectEnd?.(event);
1888
1932
  }
1889
1933
  cancelConnection();
1890
1934
  cancelAnimationFrame(autoPanId);
@@ -1892,7 +1936,6 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1892
1936
  isValid = false;
1893
1937
  connection = null;
1894
1938
  handleDomNode = null;
1895
- connectionStartHandle = null;
1896
1939
  doc.removeEventListener('mousemove', onPointerMove);
1897
1940
  doc.removeEventListener('mouseup', onPointerUp);
1898
1941
  doc.removeEventListener('touchmove', onPointerMove);
@@ -1904,7 +1947,7 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1904
1947
  doc.addEventListener('touchend', onPointerUp);
1905
1948
  }
1906
1949
  // checks if and returns connection in fom of an object { source: 123, target: 312 }
1907
- function isValidHandle(event, { handle, connectionMode, fromNodeId, fromHandleId, fromType, doc, lib, flowId, isValidConnection = alwaysValid, }) {
1950
+ function isValidHandle(event, { handle, connectionMode, fromNodeId, fromHandleId, fromType, doc, lib, flowId, isValidConnection = alwaysValid, handleLookup, }) {
1908
1951
  const isTarget = fromType === 'target';
1909
1952
  const handleDomNode = handle
1910
1953
  ? doc.querySelector(`.${lib}-flow__handle[data-id="${flowId}-${handle?.nodeId}-${handle?.id}-${handle?.type}"]`)
@@ -1918,7 +1961,7 @@ function isValidHandle(event, { handle, connectionMode, fromNodeId, fromHandleId
1918
1961
  handleDomNode: handleToCheck,
1919
1962
  isValid: false,
1920
1963
  connection: null,
1921
- endHandle: null,
1964
+ toHandle: null,
1922
1965
  };
1923
1966
  if (handleToCheck) {
1924
1967
  const handleType = getHandleType(undefined, handleToCheck);
@@ -1942,13 +1985,14 @@ function isValidHandle(event, { handle, connectionMode, fromNodeId, fromHandleId
1942
1985
  (connectionMode === ConnectionMode.Strict
1943
1986
  ? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
1944
1987
  : handleNodeId !== fromNodeId || handleId !== fromHandleId);
1945
- if (isValid) {
1946
- result.endHandle = {
1947
- nodeId: handleNodeId,
1948
- handleId,
1949
- type: handleType,
1950
- };
1951
- result.isValid = isValidConnection(connection);
1988
+ result.isValid = isValid && isValidConnection(connection);
1989
+ if (handleLookup) {
1990
+ const toHandle = handleLookup.find((h) => h.id === handleId && h.nodeId === handleNodeId && h.type === handleType);
1991
+ if (toHandle) {
1992
+ result.toHandle = {
1993
+ ...toHandle,
1994
+ };
1995
+ }
1952
1996
  }
1953
1997
  }
1954
1998
  return result;
@@ -1961,6 +2005,7 @@ const XYHandle = {
1961
2005
  function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
1962
2006
  const selection = select(domNode);
1963
2007
  function update({ translateExtent, width, height, zoomStep = 10, pannable = true, zoomable = true, inversePan = false, }) {
2008
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1964
2009
  const zoomHandler = (event) => {
1965
2010
  const transform = getTransform();
1966
2011
  if (event.sourceEvent.type !== 'wheel' || !panZoom) {
@@ -1973,6 +2018,7 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
1973
2018
  panZoom.scaleTo(nextZoom);
1974
2019
  };
1975
2020
  let panStart = [0, 0];
2021
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1976
2022
  const panStartHandler = (event) => {
1977
2023
  if (event.sourceEvent.type === 'mousedown' || event.sourceEvent.type === 'touchstart') {
1978
2024
  panStart = [
@@ -1981,6 +2027,7 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
1981
2027
  ];
1982
2028
  }
1983
2029
  };
2030
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1984
2031
  const panHandler = (event) => {
1985
2032
  const transform = getTransform();
1986
2033
  if ((event.sourceEvent.type !== 'mousemove' && event.sourceEvent.type !== 'touchmove') || !panZoom) {
@@ -2009,8 +2056,10 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
2009
2056
  };
2010
2057
  const zoomAndPanHandler = zoom()
2011
2058
  .on('start', panStartHandler)
2059
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2012
2060
  // @ts-ignore
2013
2061
  .on('zoom', pannable ? panHandler : null)
2062
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2014
2063
  // @ts-ignore
2015
2064
  .on('zoom.wheel', zoomable ? zoomHandler : null);
2016
2065
  selection.call(zoomAndPanHandler, {});
@@ -2649,8 +2698,8 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2649
2698
  }
2650
2699
  const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2651
2700
  prevValues = {
2652
- width: node.measured?.width ?? 0,
2653
- height: node.measured?.height ?? 0,
2701
+ width: node.measured.width ?? 0,
2702
+ height: node.measured.height ?? 0,
2654
2703
  x: node.position.x ?? 0,
2655
2704
  y: node.position.y ?? 0,
2656
2705
  };
@@ -2661,11 +2710,9 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2661
2710
  aspectRatio: prevValues.width / prevValues.height,
2662
2711
  };
2663
2712
  parentNode = undefined;
2664
- if (node.extent === 'parent' || node.expandParent) {
2713
+ if (node.parentId && (node.extent === 'parent' || node.expandParent)) {
2665
2714
  parentNode = nodeLookup.get(node.parentId);
2666
- if (parentNode && node.extent === 'parent') {
2667
- parentExtent = nodeToParentExtent(parentNode);
2668
- }
2715
+ parentExtent = parentNode && node.extent === 'parent' ? nodeToParentExtent(parentNode) : undefined;
2669
2716
  }
2670
2717
  // Collect all child nodes to correct their relative positions when top/left changes
2671
2718
  // Determine largest minimal extent the parent node is allowed to resize to
@@ -2712,22 +2759,13 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2712
2759
  if (!isXPosChange && !isYPosChange && !isWidthChange && !isHeightChange) {
2713
2760
  return;
2714
2761
  }
2715
- if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] == 1) {
2762
+ if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] === 1) {
2716
2763
  change.x = isXPosChange ? x : prevValues.x;
2717
2764
  change.y = isYPosChange ? y : prevValues.y;
2718
2765
  prevValues.x = change.x;
2719
2766
  prevValues.y = change.y;
2720
- // Fix expandParent when resizing from top/left
2721
- if (parentNode && node.expandParent) {
2722
- if (change.x && change.x < 0) {
2723
- prevValues.x = 0;
2724
- startValues.x = startValues.x - change.x;
2725
- }
2726
- if (change.y && change.y < 0) {
2727
- prevValues.y = 0;
2728
- startValues.y = startValues.y - change.y;
2729
- }
2730
- }
2767
+ // when top/left changes, correct the relative positions of child nodes
2768
+ // so that they stay in the same position
2731
2769
  if (childNodes.length > 0) {
2732
2770
  const xChange = x - prevX;
2733
2771
  const yChange = y - prevY;
@@ -2746,6 +2784,19 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2746
2784
  prevValues.width = change.width;
2747
2785
  prevValues.height = change.height;
2748
2786
  }
2787
+ // Fix expandParent when resizing from top/left
2788
+ if (parentNode && node.expandParent) {
2789
+ const xLimit = nodeOrigin[0] * (change.width ?? 0);
2790
+ if (change.x && change.x < xLimit) {
2791
+ prevValues.x = xLimit;
2792
+ startValues.x = startValues.x - (change.x - xLimit);
2793
+ }
2794
+ const yLimit = nodeOrigin[1] * (change.height ?? 0);
2795
+ if (change.y && change.y < yLimit) {
2796
+ prevValues.y = yLimit;
2797
+ startValues.y = startValues.y - (change.y - yLimit);
2798
+ }
2799
+ }
2749
2800
  const direction = getResizeDirection({
2750
2801
  width: prevValues.width,
2751
2802
  prevWidth,
@@ -2777,4 +2828,4 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2777
2828
  };
2778
2829
  }
2779
2830
 
2780
- export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, ResizeControlVariant, SelectionMode, XYDrag, XYHandle, XYMinimap, XYPanZoom, XYResizer, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSITIONS, addEdge, adoptUserNodes, areConnectionMapsEqual, boxToRect, calcAutoPan, calculateNodePosition, clamp, clampPosition, createMarkerIds, devWarn, elementSelectionKeys, errorMessages, evaluateAbsolutePosition, fitView, getBezierEdgeCenter, getBezierPath, getBoundsOfBoxes, getBoundsOfRects, getConnectedEdges, getDimensions, getEdgeCenter, getEdgePosition, getElementsToRemove, getElevatedEdgeZIndex, getEventPosition, getHandleBounds, getHostForElement, getIncomers, getInternalNodesBounds, getMarkerId, getNodeDimensions, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesInside, getOutgoers, getOverlappingArea, getPointerPosition, getPositionWithOrigin, getSmoothStepPath, getStraightPath, getViewportForBounds, handleConnectionChange, handleExpandParent, infiniteExtent, isCoordinateExtent, isEdgeBase, isEdgeVisible, isInputDOMNode, isInternalNodeBase, isMacOs, isMouseEvent, isNodeBase, isNumeric, isRectObject, nodeHasDimensions, nodeToBox, nodeToRect, panBy, pointToRendererPoint, rectToBox, rendererPointToPoint, shallowNodeData, snapPosition, updateAbsolutePositions, updateConnectionLookup, updateEdge, updateNodeInternals };
2831
+ export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, ResizeControlVariant, SelectionMode, XYDrag, XYHandle, XYMinimap, XYPanZoom, XYResizer, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSITIONS, addEdge, adoptUserNodes, areConnectionMapsEqual, boxToRect, calcAutoPan, calculateNodePosition, clamp, clampPosition, createMarkerIds, devWarn, elementSelectionKeys, errorMessages, evaluateAbsolutePosition, fitView, getBezierEdgeCenter, getBezierPath, getBoundsOfBoxes, getBoundsOfRects, getConnectedEdges, getConnectionStatus, getDimensions, getEdgeCenter, getEdgePosition, getElementsToRemove, getElevatedEdgeZIndex, getEventPosition, getHandleBounds, getHandlePosition, getHostForElement, getIncomers, getInternalNodesBounds, getMarkerId, getNodeDimensions, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesInside, getOutgoers, getOverlappingArea, getPointerPosition, getSmoothStepPath, getStraightPath, getViewportForBounds, handleConnectionChange, handleExpandParent, infiniteExtent, initialConnection, isCoordinateExtent, isEdgeBase, isEdgeVisible, isInputDOMNode, isInternalNodeBase, isMacOs, isMouseEvent, isNodeBase, isNumeric, isRectObject, nodeHasDimensions, nodeToBox, nodeToRect, oppositePosition, panBy, pointToRendererPoint, reconnectEdge, rectToBox, rendererPointToPoint, shallowNodeData, snapPosition, updateAbsolutePositions, updateConnectionLookup, updateNodeInternals };