@xyflow/system 0.0.30 → 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 (60) hide show
  1. package/dist/esm/index.js +270 -225
  2. package/dist/esm/index.mjs +271 -225
  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 -16
  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/positions.d.ts +3 -3
  16. package/dist/esm/utils/edges/positions.d.ts.map +1 -1
  17. package/dist/esm/utils/general.d.ts +4 -8
  18. package/dist/esm/utils/general.d.ts.map +1 -1
  19. package/dist/esm/utils/graph.d.ts +3 -7
  20. package/dist/esm/utils/graph.d.ts.map +1 -1
  21. package/dist/esm/utils/store.d.ts +2 -2
  22. package/dist/esm/utils/store.d.ts.map +1 -1
  23. package/dist/esm/xydrag/XYDrag.d.ts.map +1 -1
  24. package/dist/esm/xyhandle/XYHandle.d.ts +1 -46
  25. package/dist/esm/xyhandle/XYHandle.d.ts.map +1 -1
  26. package/dist/esm/xyhandle/types.d.ts +47 -0
  27. package/dist/esm/xyhandle/types.d.ts.map +1 -0
  28. package/dist/esm/xyhandle/utils.d.ts +5 -6
  29. package/dist/esm/xyhandle/utils.d.ts.map +1 -1
  30. package/dist/esm/xyresizer/XYResizer.d.ts.map +1 -1
  31. package/dist/umd/index.js +1 -1
  32. package/dist/umd/types/general.d.ts +30 -11
  33. package/dist/umd/types/general.d.ts.map +1 -1
  34. package/dist/umd/types/handles.d.ts +4 -16
  35. package/dist/umd/types/handles.d.ts.map +1 -1
  36. package/dist/umd/types/nodes.d.ts +6 -5
  37. package/dist/umd/types/nodes.d.ts.map +1 -1
  38. package/dist/umd/types/utils.d.ts +6 -0
  39. package/dist/umd/types/utils.d.ts.map +1 -1
  40. package/dist/umd/utils/connections.d.ts +1 -0
  41. package/dist/umd/utils/connections.d.ts.map +1 -1
  42. package/dist/umd/utils/dom.d.ts +2 -2
  43. package/dist/umd/utils/dom.d.ts.map +1 -1
  44. package/dist/umd/utils/edges/positions.d.ts +3 -3
  45. package/dist/umd/utils/edges/positions.d.ts.map +1 -1
  46. package/dist/umd/utils/general.d.ts +4 -8
  47. package/dist/umd/utils/general.d.ts.map +1 -1
  48. package/dist/umd/utils/graph.d.ts +3 -7
  49. package/dist/umd/utils/graph.d.ts.map +1 -1
  50. package/dist/umd/utils/store.d.ts +2 -2
  51. package/dist/umd/utils/store.d.ts.map +1 -1
  52. package/dist/umd/xydrag/XYDrag.d.ts.map +1 -1
  53. package/dist/umd/xyhandle/XYHandle.d.ts +1 -46
  54. package/dist/umd/xyhandle/XYHandle.d.ts.map +1 -1
  55. package/dist/umd/xyhandle/types.d.ts +47 -0
  56. package/dist/umd/xyhandle/types.d.ts.map +1 -0
  57. package/dist/umd/xyhandle/utils.d.ts +5 -6
  58. package/dist/umd/xyhandle/utils.d.ts.map +1 -1
  59. package/dist/umd/xyresizer/XYResizer.d.ts.map +1 -1
  60. 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
  });
@@ -1049,13 +1048,13 @@ function getEdgePosition(params) {
1049
1048
  }
1050
1049
  const sourcePosition = sourceHandle?.position || Position.Bottom;
1051
1050
  const targetPosition = targetHandle?.position || Position.Top;
1052
- const [sourceX, sourceY] = getHandlePosition(sourceNode, sourceHandle, sourcePosition);
1053
- const [targetX, targetY] = getHandlePosition(targetNode, targetHandle, targetPosition);
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,20 +1080,23 @@ function toHandleBounds(handles) {
1081
1080
  target,
1082
1081
  };
1083
1082
  }
1084
- function getHandlePosition(node, handle, fallbackPosition = Position.Left) {
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
+ }
1088
1090
  const position = handle?.position ?? fallbackPosition;
1089
1091
  switch (position) {
1090
1092
  case Position.Top:
1091
- return [x + width / 2, y];
1093
+ return { x: x + width / 2, y };
1092
1094
  case Position.Right:
1093
- return [x + width, y + height / 2];
1095
+ return { x: x + width, y: y + height / 2 };
1094
1096
  case Position.Bottom:
1095
- return [x + width / 2, y + height];
1097
+ return { x: x + width / 2, y: y + height };
1096
1098
  case Position.Left:
1097
- return [x, y + height / 2];
1099
+ return { x, y: y + height / 2 };
1098
1100
  }
1099
1101
  }
1100
1102
  function getHandle(bounds, handleId) {
@@ -1175,95 +1177,98 @@ function getNodeToolbarTransform(nodeRect, viewport, position, offset, align) {
1175
1177
  return `translate(${pos[0]}px, ${pos[1]}px) translate(${shift[0]}%, ${shift[1]}%)`;
1176
1178
  }
1177
1179
 
1178
- function updateAbsolutePositions(nodeLookup, options = {
1180
+ const defaultOptions = {
1179
1181
  nodeOrigin: [0, 0],
1180
1182
  elevateNodesOnSelect: true,
1181
1183
  defaults: {},
1182
- }) {
1183
- const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1184
- for (const [, node] of nodeLookup) {
1185
- const parentId = node.parentId;
1186
- 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) {
1187
1193
  continue;
1188
1194
  }
1189
- if (!nodeLookup.has(parentId)) {
1190
- throw new Error(`Parent node ${parentId} not found`);
1191
- }
1192
- const parentNode = nodeLookup.get(parentId);
1193
- const { x, y, z } = calculateXYZPosition(node, nodeLookup, {
1194
- ...node.position,
1195
- z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0),
1196
- }, parentNode?.origin ?? options.nodeOrigin);
1197
- const currPosition = node.internals.positionAbsolute;
1198
- const positionChanged = x !== currPosition.x || y !== currPosition.y;
1199
- if (positionChanged || z !== node.internals.z) {
1200
- node.internals = {
1201
- ...node.internals,
1202
- positionAbsolute: positionChanged ? { x, y } : currPosition,
1203
- z,
1204
- };
1205
- }
1195
+ updateChildPosition(node, nodeLookup, parentLookup, _options);
1206
1196
  }
1207
1197
  }
1208
- function adoptUserNodes(nodes, nodeLookup, parentLookup, options = {
1209
- nodeOrigin: [0, 0],
1210
- elevateNodesOnSelect: true,
1211
- defaults: {},
1212
- checkEquality: true,
1213
- }) {
1198
+ function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
1199
+ const _options = { ...adoptUserNodesDefaultOptions, ...options };
1214
1200
  const tmpLookup = new Map(nodeLookup);
1215
1201
  nodeLookup.clear();
1216
1202
  parentLookup.clear();
1217
1203
  const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1218
- nodes.forEach((userNode) => {
1204
+ for (const userNode of nodes) {
1219
1205
  let internalNode = tmpLookup.get(userNode.id);
1220
- if (options.checkEquality && userNode === internalNode?.internals.userNode) {
1206
+ if (_options.checkEquality && userNode === internalNode?.internals.userNode) {
1221
1207
  nodeLookup.set(userNode.id, internalNode);
1222
1208
  }
1223
1209
  else {
1224
1210
  internalNode = {
1225
- ...options.defaults,
1211
+ ..._options.defaults,
1226
1212
  ...userNode,
1227
1213
  measured: {
1228
1214
  width: userNode.measured?.width,
1229
1215
  height: userNode.measured?.height,
1230
1216
  },
1231
1217
  internals: {
1232
- positionAbsolute: userNode.position,
1218
+ positionAbsolute: getNodePositionWithOrigin(userNode, _options.nodeOrigin),
1233
1219
  handleBounds: internalNode?.internals.handleBounds,
1234
- z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0),
1220
+ z: calculateZ(userNode, selectedNodeZ),
1235
1221
  userNode,
1236
1222
  },
1237
1223
  };
1238
1224
  nodeLookup.set(userNode.id, internalNode);
1239
1225
  }
1240
1226
  if (userNode.parentId) {
1241
- const childNodes = parentLookup.get(userNode.parentId);
1242
- if (childNodes) {
1243
- childNodes.push(internalNode);
1244
- }
1245
- else {
1246
- parentLookup.set(userNode.parentId, [internalNode]);
1247
- }
1227
+ updateChildPosition(internalNode, nodeLookup, parentLookup, options);
1248
1228
  }
1249
- });
1250
- if (parentLookup.size > 0) {
1251
- updateAbsolutePositions(nodeLookup, options);
1252
1229
  }
1253
1230
  }
1254
- function calculateXYZPosition(node, nodeLookup, result, nodeOrigin = [0, 0]) {
1255
- if (!node.parentId) {
1256
- 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
+ };
1257
1256
  }
1258
- const parent = nodeLookup.get(node.parentId);
1259
- const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin).position;
1260
- return calculateXYZPosition(parent, nodeLookup, {
1261
- x: (result.x ?? 0) + parentPosition.x,
1262
- y: (result.y ?? 0) + parentPosition.y,
1263
- z: (parent.internals.z ?? 0) > (result.z ?? 0) ? parent.internals.z ?? 0 : result.z ?? 0,
1264
- }, parent.origin || nodeOrigin);
1265
1257
  }
1266
- 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]) {
1267
1272
  const changes = [];
1268
1273
  const parentExpansions = new Map();
1269
1274
  // determine the expanded rectangle the child nodes would take for each parent
@@ -1272,31 +1277,36 @@ function handleExpandParent(children, nodeLookup, parentLookup, nodeOrigin) {
1272
1277
  if (!parent) {
1273
1278
  continue;
1274
1279
  }
1275
- const parentRect = parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent, parent.origin ?? nodeOrigin);
1280
+ const parentRect = parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent);
1276
1281
  const expandedRect = getBoundsOfRects(parentRect, child.rect);
1277
1282
  parentExpansions.set(child.parentId, { expandedRect, parent });
1278
1283
  }
1279
1284
  if (parentExpansions.size > 0) {
1280
1285
  parentExpansions.forEach(({ expandedRect, parent }, parentId) => {
1281
1286
  // determine the position & dimensions of the parent
1282
- const { position } = getNodePositionWithOrigin(parent, parent.origin);
1287
+ const positionAbsolute = parent.internals.positionAbsolute;
1283
1288
  const dimensions = getNodeDimensions(parent);
1284
- // determine how much the parent expands by moving the position
1285
- const xChange = expandedRect.x < position.x ? Math.round(Math.abs(position.x - expandedRect.x)) : 0;
1286
- const yChange = expandedRect.y < position.y ? Math.round(Math.abs(position.y - expandedRect.y)) : 0;
1287
- 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) {
1288
1299
  changes.push({
1289
1300
  id: parentId,
1290
1301
  type: 'position',
1291
1302
  position: {
1292
- x: position.x - xChange,
1293
- y: position.y - yChange,
1303
+ x: parent.position.x - xChange + widthChange,
1304
+ y: parent.position.y - yChange + heightChange,
1294
1305
  },
1295
1306
  });
1296
1307
  // We move all child nodes in the oppsite direction
1297
1308
  // so the x,y changes of the parent do not move the children
1298
- const childNodes = parentLookup.get(parentId);
1299
- childNodes?.forEach((childNode) => {
1309
+ parentLookup.get(parentId)?.forEach((childNode) => {
1300
1310
  if (!children.some((child) => child.id === childNode.id)) {
1301
1311
  changes.push({
1302
1312
  id: childNode.id,
@@ -1309,14 +1319,15 @@ function handleExpandParent(children, nodeLookup, parentLookup, nodeOrigin) {
1309
1319
  }
1310
1320
  });
1311
1321
  }
1312
- 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) {
1313
1324
  changes.push({
1314
1325
  id: parentId,
1315
1326
  type: 'dimensions',
1316
1327
  setAttributes: true,
1317
1328
  dimensions: {
1318
- width: Math.max(dimensions.width, Math.round(expandedRect.width)),
1319
- 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),
1320
1331
  },
1321
1332
  });
1322
1333
  }
@@ -1335,16 +1346,19 @@ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOri
1335
1346
  const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
1336
1347
  // in this array we collect nodes, that might trigger changes (like expanding parent)
1337
1348
  const parentExpandChildren = [];
1338
- updates.forEach((update) => {
1349
+ for (const update of updates.values()) {
1339
1350
  const node = nodeLookup.get(update.id);
1340
- if (node?.hidden) {
1351
+ if (!node) {
1352
+ continue;
1353
+ }
1354
+ if (node.hidden) {
1341
1355
  node.internals = {
1342
1356
  ...node.internals,
1343
1357
  handleBounds: undefined,
1344
1358
  };
1345
1359
  updatedInternals = true;
1346
1360
  }
1347
- else if (node) {
1361
+ else {
1348
1362
  const dimensions = getDimensions(update.nodeElement);
1349
1363
  const dimensionChanged = node.measured.width !== dimensions.width || node.measured.height !== dimensions.height;
1350
1364
  const doUpdate = !!(dimensions.width &&
@@ -1355,11 +1369,15 @@ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOri
1355
1369
  node.measured = dimensions;
1356
1370
  node.internals = {
1357
1371
  ...node.internals,
1372
+ positionAbsolute: getNodePositionWithOrigin(node, nodeOrigin),
1358
1373
  handleBounds: {
1359
- source: getHandleBounds('.source', update.nodeElement, nodeBounds, zoom, node.origin || nodeOrigin),
1360
- 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),
1361
1376
  },
1362
1377
  };
1378
+ if (node.parentId) {
1379
+ updateChildPosition(node, nodeLookup, parentLookup, { nodeOrigin });
1380
+ }
1363
1381
  updatedInternals = true;
1364
1382
  if (dimensionChanged) {
1365
1383
  changes.push({
@@ -1377,7 +1395,7 @@ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOri
1377
1395
  }
1378
1396
  }
1379
1397
  }
1380
- });
1398
+ }
1381
1399
  if (parentExpandChildren.length > 0) {
1382
1400
  const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
1383
1401
  changes.push(...parentExpandChanges);
@@ -1537,7 +1555,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1537
1555
  let hasChange = false;
1538
1556
  let nodesBox = { x: 0, y: 0, x2: 0, y2: 0 };
1539
1557
  if (dragItems.size > 1 && nodeExtent) {
1540
- const rect = getInternalNodesBounds(dragItems, { nodeOrigin });
1558
+ const rect = getInternalNodesBounds(dragItems);
1541
1559
  nodesBox = rectToBox(rect);
1542
1560
  }
1543
1561
  for (const [id, dragItem] of dragItems) {
@@ -1720,19 +1738,18 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1720
1738
  // this functions collects all handles and adds an absolute position
1721
1739
  // so that we can later find the closest handle to the mouse position
1722
1740
  function getHandles(node, handleBounds, type, currentHandle) {
1723
- return (handleBounds[type] || []).reduce((res, handle) => {
1724
- if (`${node.id}-${handle.id}-${type}` !== currentHandle) {
1725
- const [x, y] = getHandlePosition(node, handle);
1726
- res.push({
1727
- id: handle.id || null,
1728
- type,
1729
- nodeId: node.id,
1730
- x,
1731
- y,
1732
- });
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 });
1733
1749
  }
1734
1750
  return res;
1735
1751
  }, []);
1752
+ return [handles, excludedHandle];
1736
1753
  }
1737
1754
  function getClosestHandle(pos, connectionRadius, handles) {
1738
1755
  let closestHandles = [];
@@ -1760,15 +1777,17 @@ function getClosestHandle(pos, connectionRadius, handles) {
1760
1777
  }
1761
1778
  function getHandleLookup({ nodeLookup, nodeId, handleId, handleType, }) {
1762
1779
  const connectionHandles = [];
1763
- for (const [, node] of nodeLookup) {
1780
+ const currentHandle = { nodeId, handleId, handleType };
1781
+ let excludedHandle = null;
1782
+ for (const node of nodeLookup.values()) {
1764
1783
  if (node.internals.handleBounds) {
1765
- const id = `${nodeId}-${handleId}-${handleType}`;
1766
- const sourceHandles = getHandles(node, node.internals.handleBounds, 'source', id);
1767
- 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;
1768
1787
  connectionHandles.push(...sourceHandles, ...targetHandles);
1769
1788
  }
1770
1789
  }
1771
- return connectionHandles;
1790
+ return [connectionHandles, excludedHandle];
1772
1791
  }
1773
1792
  function getHandleType(edgeUpdaterType, handleDomNode) {
1774
1793
  if (edgeUpdaterType) {
@@ -1782,20 +1801,19 @@ function getHandleType(edgeUpdaterType, handleDomNode) {
1782
1801
  }
1783
1802
  return null;
1784
1803
  }
1785
- function getConnectionStatus(isInsideConnectionRadius, isHandleValid) {
1786
- let connectionStatus = null;
1804
+ function isConnectionValid(isInsideConnectionRadius, isHandleValid) {
1805
+ let isValid = null;
1787
1806
  if (isHandleValid) {
1788
- connectionStatus = 'valid';
1807
+ isValid = true;
1789
1808
  }
1790
1809
  else if (isInsideConnectionRadius && !isHandleValid) {
1791
- connectionStatus = 'invalid';
1810
+ isValid = false;
1792
1811
  }
1793
- return connectionStatus;
1812
+ return isValid;
1794
1813
  }
1795
1814
 
1796
1815
  const alwaysValid = () => true;
1797
- let connectionStartHandle = null;
1798
- function onPointerDown(event, { connectionMode, connectionRadius, handleId, nodeId, edgeUpdaterType, isTarget, domNode, nodeLookup, lib, autoPanOnConnect, flowId, panBy, cancelConnection, onConnectStart, onConnect, onConnectEnd, isValidConnection = alwaysValid, onReconnectEnd, 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, }) {
1799
1817
  // when xyflow is used inside a shadow root we can't use document
1800
1818
  const doc = getHostForElement(event.target);
1801
1819
  let autoPanId = 0;
@@ -1807,12 +1825,12 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1807
1825
  if (!containerBounds || !handleType) {
1808
1826
  return;
1809
1827
  }
1810
- let connectionPosition = getEventPosition(event, containerBounds);
1828
+ let position = getEventPosition(event, containerBounds);
1811
1829
  let autoPanStarted = false;
1812
1830
  let connection = null;
1813
1831
  let isValid = false;
1814
1832
  let handleDomNode = null;
1815
- const handleLookup = getHandleLookup({
1833
+ const [handleLookup, fromHandleInternal] = getHandleLookup({
1816
1834
  nodeLookup,
1817
1835
  nodeId,
1818
1836
  handleId,
@@ -1823,31 +1841,42 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1823
1841
  if (!autoPanOnConnect || !containerBounds) {
1824
1842
  return;
1825
1843
  }
1826
- const [x, y] = calcAutoPan(connectionPosition, containerBounds);
1844
+ const [x, y] = calcAutoPan(position, containerBounds);
1827
1845
  panBy({ x, y });
1828
1846
  autoPanId = requestAnimationFrame(autoPan);
1829
1847
  }
1830
1848
  // Stays the same for all consecutive pointermove events
1831
- connectionStartHandle = {
1849
+ const fromHandle = {
1850
+ ...fromHandleInternal,
1832
1851
  nodeId,
1833
- handleId,
1834
1852
  type: handleType,
1835
- position: clickedHandle?.getAttribute('data-handlepos') || Position.Top,
1853
+ position: fromHandleInternal.position,
1836
1854
  };
1837
- updateConnection({
1838
- connectionPosition,
1839
- connectionStatus: null,
1840
- connectionStartHandle,
1841
- connectionEndHandle: null,
1842
- });
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;
1843
1871
  onConnectStart?.(event, { nodeId, handleId, handleType });
1844
1872
  function onPointerMove(event) {
1845
- if (!getConnectionStartHandle()) {
1873
+ if (!getFromHandle() || !fromHandle) {
1846
1874
  onPointerUp(event);
1875
+ return;
1847
1876
  }
1848
1877
  const transform = getTransform();
1849
- connectionPosition = getEventPosition(event, containerBounds);
1850
- 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);
1851
1880
  if (!autoPanStarted) {
1852
1881
  autoPan();
1853
1882
  autoPanStarted = true;
@@ -1862,21 +1891,34 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1862
1891
  doc,
1863
1892
  lib,
1864
1893
  flowId,
1894
+ handleLookup,
1865
1895
  });
1866
1896
  handleDomNode = result.handleDomNode;
1867
1897
  connection = result.connection;
1868
- isValid = result.isValid;
1869
- updateConnection({
1870
- connectionStartHandle,
1871
- connectionPosition: closestHandle && isValid
1872
- ? rendererPointToPoint({
1873
- x: closestHandle.x,
1874
- y: closestHandle.y,
1875
- }, transform)
1876
- : connectionPosition,
1877
- connectionStatus: getConnectionStatus(!!closestHandle, isValid),
1878
- connectionEndHandle: result.endHandle,
1879
- });
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;
1880
1922
  }
1881
1923
  function onPointerUp(event) {
1882
1924
  if ((closestHandle || handleDomNode) && connection && isValid) {
@@ -1894,7 +1936,6 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1894
1936
  isValid = false;
1895
1937
  connection = null;
1896
1938
  handleDomNode = null;
1897
- connectionStartHandle = null;
1898
1939
  doc.removeEventListener('mousemove', onPointerMove);
1899
1940
  doc.removeEventListener('mouseup', onPointerUp);
1900
1941
  doc.removeEventListener('touchmove', onPointerMove);
@@ -1906,7 +1947,7 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1906
1947
  doc.addEventListener('touchend', onPointerUp);
1907
1948
  }
1908
1949
  // checks if and returns connection in fom of an object { source: 123, target: 312 }
1909
- 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, }) {
1910
1951
  const isTarget = fromType === 'target';
1911
1952
  const handleDomNode = handle
1912
1953
  ? doc.querySelector(`.${lib}-flow__handle[data-id="${flowId}-${handle?.nodeId}-${handle?.id}-${handle?.type}"]`)
@@ -1920,7 +1961,7 @@ function isValidHandle(event, { handle, connectionMode, fromNodeId, fromHandleId
1920
1961
  handleDomNode: handleToCheck,
1921
1962
  isValid: false,
1922
1963
  connection: null,
1923
- endHandle: null,
1964
+ toHandle: null,
1924
1965
  };
1925
1966
  if (handleToCheck) {
1926
1967
  const handleType = getHandleType(undefined, handleToCheck);
@@ -1945,12 +1986,14 @@ function isValidHandle(event, { handle, connectionMode, fromNodeId, fromHandleId
1945
1986
  ? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
1946
1987
  : handleNodeId !== fromNodeId || handleId !== fromHandleId);
1947
1988
  result.isValid = isValid && isValidConnection(connection);
1948
- result.endHandle = {
1949
- nodeId: handleNodeId,
1950
- handleId,
1951
- type: handleType,
1952
- position: handleToCheck.getAttribute('data-handlepos'),
1953
- };
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
+ }
1996
+ }
1954
1997
  }
1955
1998
  return result;
1956
1999
  }
@@ -2655,8 +2698,8 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2655
2698
  }
2656
2699
  const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2657
2700
  prevValues = {
2658
- width: node.measured?.width ?? 0,
2659
- height: node.measured?.height ?? 0,
2701
+ width: node.measured.width ?? 0,
2702
+ height: node.measured.height ?? 0,
2660
2703
  x: node.position.x ?? 0,
2661
2704
  y: node.position.y ?? 0,
2662
2705
  };
@@ -2667,11 +2710,9 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2667
2710
  aspectRatio: prevValues.width / prevValues.height,
2668
2711
  };
2669
2712
  parentNode = undefined;
2670
- if (node.extent === 'parent' || node.expandParent) {
2713
+ if (node.parentId && (node.extent === 'parent' || node.expandParent)) {
2671
2714
  parentNode = nodeLookup.get(node.parentId);
2672
- if (parentNode && node.extent === 'parent') {
2673
- parentExtent = nodeToParentExtent(parentNode);
2674
- }
2715
+ parentExtent = parentNode && node.extent === 'parent' ? nodeToParentExtent(parentNode) : undefined;
2675
2716
  }
2676
2717
  // Collect all child nodes to correct their relative positions when top/left changes
2677
2718
  // Determine largest minimal extent the parent node is allowed to resize to
@@ -2718,22 +2759,13 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2718
2759
  if (!isXPosChange && !isYPosChange && !isWidthChange && !isHeightChange) {
2719
2760
  return;
2720
2761
  }
2721
- if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] == 1) {
2762
+ if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] === 1) {
2722
2763
  change.x = isXPosChange ? x : prevValues.x;
2723
2764
  change.y = isYPosChange ? y : prevValues.y;
2724
2765
  prevValues.x = change.x;
2725
2766
  prevValues.y = change.y;
2726
- // Fix expandParent when resizing from top/left
2727
- if (parentNode && node.expandParent) {
2728
- if (change.x && change.x < 0) {
2729
- prevValues.x = 0;
2730
- startValues.x = startValues.x - change.x;
2731
- }
2732
- if (change.y && change.y < 0) {
2733
- prevValues.y = 0;
2734
- startValues.y = startValues.y - change.y;
2735
- }
2736
- }
2767
+ // when top/left changes, correct the relative positions of child nodes
2768
+ // so that they stay in the same position
2737
2769
  if (childNodes.length > 0) {
2738
2770
  const xChange = x - prevX;
2739
2771
  const yChange = y - prevY;
@@ -2752,6 +2784,19 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2752
2784
  prevValues.width = change.width;
2753
2785
  prevValues.height = change.height;
2754
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
+ }
2755
2800
  const direction = getResizeDirection({
2756
2801
  width: prevValues.width,
2757
2802
  prevWidth,
@@ -2783,4 +2828,4 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2783
2828
  };
2784
2829
  }
2785
2830
 
2786
- 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, getHandlePosition, 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, reconnectEdge, rectToBox, rendererPointToPoint, shallowNodeData, snapPosition, updateAbsolutePositions, updateConnectionLookup, 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 };