@xyflow/system 0.0.22 → 0.0.24

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 (48) hide show
  1. package/dist/esm/index.js +360 -321
  2. package/dist/esm/index.mjs +360 -321
  3. package/dist/esm/types/changes.d.ts +1 -0
  4. package/dist/esm/types/changes.d.ts.map +1 -1
  5. package/dist/esm/types/general.d.ts +1 -1
  6. package/dist/esm/types/general.d.ts.map +1 -1
  7. package/dist/esm/types/nodes.d.ts +4 -4
  8. package/dist/esm/types/nodes.d.ts.map +1 -1
  9. package/dist/esm/utils/general.d.ts +22 -2
  10. package/dist/esm/utils/general.d.ts.map +1 -1
  11. package/dist/esm/utils/graph.d.ts +6 -8
  12. package/dist/esm/utils/graph.d.ts.map +1 -1
  13. package/dist/esm/utils/store.d.ts +10 -5
  14. package/dist/esm/utils/store.d.ts.map +1 -1
  15. package/dist/esm/utils/types.d.ts +6 -0
  16. package/dist/esm/utils/types.d.ts.map +1 -1
  17. package/dist/esm/xydrag/XYDrag.d.ts +1 -1
  18. package/dist/esm/xydrag/XYDrag.d.ts.map +1 -1
  19. package/dist/esm/xydrag/utils.d.ts +2 -3
  20. package/dist/esm/xydrag/utils.d.ts.map +1 -1
  21. package/dist/esm/xyminimap/index.d.ts.map +1 -1
  22. package/dist/esm/xypanzoom/eventhandler.d.ts.map +1 -1
  23. package/dist/esm/xyresizer/XYResizer.d.ts +6 -11
  24. package/dist/esm/xyresizer/XYResizer.d.ts.map +1 -1
  25. package/dist/umd/index.js +1 -1
  26. package/dist/umd/types/changes.d.ts +1 -0
  27. package/dist/umd/types/changes.d.ts.map +1 -1
  28. package/dist/umd/types/general.d.ts +1 -1
  29. package/dist/umd/types/general.d.ts.map +1 -1
  30. package/dist/umd/types/nodes.d.ts +4 -4
  31. package/dist/umd/types/nodes.d.ts.map +1 -1
  32. package/dist/umd/utils/general.d.ts +22 -2
  33. package/dist/umd/utils/general.d.ts.map +1 -1
  34. package/dist/umd/utils/graph.d.ts +6 -8
  35. package/dist/umd/utils/graph.d.ts.map +1 -1
  36. package/dist/umd/utils/store.d.ts +10 -5
  37. package/dist/umd/utils/store.d.ts.map +1 -1
  38. package/dist/umd/utils/types.d.ts +6 -0
  39. package/dist/umd/utils/types.d.ts.map +1 -1
  40. package/dist/umd/xydrag/XYDrag.d.ts +1 -1
  41. package/dist/umd/xydrag/XYDrag.d.ts.map +1 -1
  42. package/dist/umd/xydrag/utils.d.ts +2 -3
  43. package/dist/umd/xydrag/utils.d.ts.map +1 -1
  44. package/dist/umd/xyminimap/index.d.ts.map +1 -1
  45. package/dist/umd/xypanzoom/eventhandler.d.ts.map +1 -1
  46. package/dist/umd/xyresizer/XYResizer.d.ts +6 -11
  47. package/dist/umd/xyresizer/XYResizer.d.ts.map +1 -1
  48. package/package.json +3 -3
package/dist/esm/index.js CHANGED
@@ -161,33 +161,20 @@ const getIncomers = (node, nodes, edges) => {
161
161
  return nodes.filter((n) => incomersIds.has(n.id));
162
162
  };
163
163
  const getNodePositionWithOrigin = (node, nodeOrigin = [0, 0]) => {
164
- if (!node) {
165
- return {
166
- position: {
167
- x: 0,
168
- y: 0,
169
- },
170
- positionAbsolute: {
171
- x: 0,
172
- y: 0,
173
- },
174
- };
175
- }
176
164
  const { width, height } = getNodeDimensions(node);
177
- const offsetX = width * nodeOrigin[0];
178
- const offsetY = height * nodeOrigin[1];
179
- const position = {
180
- x: node.position.x - offsetX,
181
- y: node.position.y - offsetY,
182
- };
165
+ const positionAbsolute = 'internals' in node ? node.internals.positionAbsolute : node.position;
166
+ const origin = node.origin || nodeOrigin;
167
+ const offsetX = width * origin[0];
168
+ const offsetY = height * origin[1];
183
169
  return {
184
- position,
185
- positionAbsolute: 'internals' in node
186
- ? {
187
- x: node.internals.positionAbsolute.x - offsetX,
188
- y: node.internals.positionAbsolute.y - offsetY,
189
- }
190
- : position,
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
+ },
191
178
  };
192
179
  };
193
180
  /**
@@ -196,20 +183,15 @@ const getNodePositionWithOrigin = (node, nodeOrigin = [0, 0]) => {
196
183
  * @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport.
197
184
  * @param nodes - Nodes to calculate the bounds for
198
185
  * @param params.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
199
- * @param params.useRelativePosition - Whether to use the relative or absolute node positions
200
186
  * @returns Bounding box enclosing all nodes
201
187
  */
202
- // @todo how to handle this if users do not have absolute positions?
203
- const getNodesBounds = (nodes, params = { nodeOrigin: [0, 0], useRelativePosition: false }) => {
188
+ const getNodesBounds = (nodes, params = { nodeOrigin: [0, 0] }) => {
204
189
  if (nodes.length === 0) {
205
190
  return { x: 0, y: 0, width: 0, height: 0 };
206
191
  }
207
192
  const box = nodes.reduce((currBox, node) => {
208
- const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin);
209
- return getBoundsOfBoxes(currBox, rectToBox({
210
- ...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'],
211
- ...getNodeDimensions(node),
212
- }));
193
+ const nodeBox = nodeToBox(node, params.nodeOrigin);
194
+ return getBoundsOfBoxes(currBox, nodeBox);
213
195
  }, { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity });
214
196
  return boxToRect(box);
215
197
  };
@@ -219,7 +201,6 @@ const getNodesBounds = (nodes, params = { nodeOrigin: [0, 0], useRelativePositio
219
201
  */
220
202
  const getInternalNodesBounds = (nodeLookup, params = {
221
203
  nodeOrigin: [0, 0],
222
- useRelativePosition: false,
223
204
  }) => {
224
205
  if (nodeLookup.size === 0) {
225
206
  return { x: 0, y: 0, width: 0, height: 0 };
@@ -227,16 +208,13 @@ const getInternalNodesBounds = (nodeLookup, params = {
227
208
  let box = { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity };
228
209
  nodeLookup.forEach((node) => {
229
210
  if (params.filter == undefined || params.filter(node)) {
230
- const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin);
231
- box = getBoundsOfBoxes(box, rectToBox({
232
- ...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'],
233
- ...getNodeDimensions(node),
234
- }));
211
+ const nodeBox = nodeToBox(node, params.nodeOrigin);
212
+ box = getBoundsOfBoxes(box, nodeBox);
235
213
  }
236
214
  });
237
215
  return boxToRect(box);
238
216
  };
239
- const getNodesInside = (nodeLookup, rect, [tx, ty, tScale] = [0, 0, 1], partially = false,
217
+ const getNodesInside = (nodes, rect, [tx, ty, tScale] = [0, 0, 1], partially = false,
240
218
  // set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute
241
219
  excludeNonSelectableNodes = false, nodeOrigin = [0, 0]) => {
242
220
  const paneRect = {
@@ -245,7 +223,7 @@ excludeNonSelectableNodes = false, nodeOrigin = [0, 0]) => {
245
223
  height: rect.height / tScale,
246
224
  };
247
225
  const visibleNodes = [];
248
- for (const [, node] of nodeLookup) {
226
+ for (const [, node] of nodes) {
249
227
  const { measured, selectable = true, hidden = false } = node;
250
228
  const width = measured.width ?? node.width ?? node.initialWidth ?? null;
251
229
  const height = measured.height ?? node.height ?? node.initialHeight ?? null;
@@ -278,10 +256,10 @@ const getConnectedEdges = (nodes, edges) => {
278
256
  };
279
257
  function fitView({ nodeLookup, width, height, panZoom, minZoom, maxZoom, nodeOrigin = [0, 0] }, options) {
280
258
  const filteredNodes = [];
259
+ const optionNodeIds = options?.nodes ? new Set(options.nodes.map((node) => node.id)) : null;
281
260
  nodeLookup.forEach((n) => {
282
261
  const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden);
283
- if (isVisible &&
284
- (!options?.nodes || (options?.nodes.length && options?.nodes.some((optionNode) => optionNode.id === n.id)))) {
262
+ if (isVisible && (!optionNodeIds || optionNodeIds.has(n.id))) {
285
263
  filteredNodes.push(n);
286
264
  }
287
265
  });
@@ -452,19 +430,21 @@ const boxToRect = ({ x, y, x2, y2 }) => ({
452
430
  height: y2 - y,
453
431
  });
454
432
  const nodeToRect = (node, nodeOrigin = [0, 0]) => {
455
- const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
433
+ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
456
434
  return {
457
- ...positionAbsolute,
435
+ x,
436
+ y,
458
437
  width: node.measured?.width ?? node.width ?? 0,
459
438
  height: node.measured?.height ?? node.height ?? 0,
460
439
  };
461
440
  };
462
441
  const nodeToBox = (node, nodeOrigin = [0, 0]) => {
463
- const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
442
+ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
464
443
  return {
465
- ...positionAbsolute,
466
- x2: positionAbsolute.x + (node.measured?.width ?? node.width ?? 0),
467
- y2: positionAbsolute.y + (node.measured?.height ?? node.height ?? 0),
444
+ x,
445
+ y,
446
+ x2: x + (node.measured?.width ?? node.width ?? 0),
447
+ y2: y + (node.measured?.height ?? node.height ?? 0),
468
448
  };
469
449
  };
470
450
  const getBoundsOfRects = (rect1, rect2) => boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2)));
@@ -552,6 +532,32 @@ function nodeHasDimensions(node) {
552
532
  return ((node.measured?.width ?? node.width ?? node.initialWidth) !== undefined &&
553
533
  (node.measured?.height ?? node.height ?? node.initialHeight) !== undefined);
554
534
  }
535
+ /**
536
+ * Convert child position to aboslute position
537
+ *
538
+ * @internal
539
+ * @param position
540
+ * @param parentId
541
+ * @param nodeLookup
542
+ * @param nodeOrigin
543
+ * @returns an internal node with an absolute position
544
+ */
545
+ function evaluateAbsolutePosition(position, parentId, nodeLookup, nodeOrigin = [0, 0]) {
546
+ let nextParentId = parentId;
547
+ const positionAbsolute = { ...position };
548
+ while (nextParentId) {
549
+ const parent = nodeLookup.get(parentId);
550
+ nextParentId = parent?.parentId;
551
+ if (parent) {
552
+ 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
+ }
558
+ }
559
+ return positionAbsolute;
560
+ }
555
561
 
556
562
  function getPointerPosition(event, { snapGrid = [0, 0], snapToGrid = false, transform }) {
557
563
  const { x, y } = getEventPosition(event);
@@ -1078,8 +1084,8 @@ function toHandleBounds(handles) {
1078
1084
  };
1079
1085
  }
1080
1086
  function getHandlePosition(position, node, handle = null) {
1081
- const x = (handle?.x ?? 0) + (node.internals.positionAbsolute?.x ?? 0);
1082
- const y = (handle?.y ?? 0) + (node.internals.positionAbsolute?.y ?? 0);
1087
+ const x = (handle?.x ?? 0) + node.internals.positionAbsolute.x;
1088
+ const y = (handle?.y ?? 0) + node.internals.positionAbsolute.y;
1083
1089
  const { width, height } = handle ?? getNodeDimensions(node);
1084
1090
  switch (position) {
1085
1091
  case Position.Top:
@@ -1174,49 +1180,49 @@ function updateAbsolutePositions(nodeLookup, options = {
1174
1180
  nodeOrigin: [0, 0],
1175
1181
  elevateNodesOnSelect: true,
1176
1182
  defaults: {},
1177
- }, parentNodeIds) {
1183
+ }) {
1178
1184
  const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1179
- for (const [id, node] of nodeLookup) {
1185
+ for (const [, node] of nodeLookup) {
1180
1186
  const parentId = node.parentId;
1181
- if (parentId && !nodeLookup.has(parentId)) {
1187
+ if (!parentId) {
1188
+ continue;
1189
+ }
1190
+ if (!nodeLookup.has(parentId)) {
1182
1191
  throw new Error(`Parent node ${parentId} not found`);
1183
1192
  }
1184
- if (parentId || node.internals.isParent || parentNodeIds?.has(id)) {
1185
- const parentNode = parentId ? nodeLookup.get(parentId) : null;
1186
- const { x, y, z } = calculateXYZPosition(node, nodeLookup, {
1187
- ...node.position,
1188
- z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0),
1189
- }, parentNode?.origin || options.nodeOrigin);
1190
- const currPosition = node.internals.positionAbsolute;
1191
- const positionChanged = x !== currPosition.x || y !== currPosition.y;
1192
- node.internals.positionAbsolute = positionChanged ? { x, y } : currPosition;
1193
- node.internals.z = z;
1194
- if (parentNodeIds !== undefined) {
1195
- node.internals.isParent = !!parentNodeIds?.has(id);
1196
- }
1197
- nodeLookup.set(id, node);
1193
+ const parentNode = nodeLookup.get(parentId);
1194
+ const { x, y, z } = calculateXYZPosition(node, nodeLookup, {
1195
+ ...node.position,
1196
+ z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0),
1197
+ }, parentNode?.origin ?? options.nodeOrigin);
1198
+ const currPosition = node.internals.positionAbsolute;
1199
+ const positionChanged = x !== currPosition.x || y !== currPosition.y;
1200
+ if (positionChanged || z !== node.internals.z) {
1201
+ node.internals = {
1202
+ ...node.internals,
1203
+ positionAbsolute: positionChanged ? { x, y } : currPosition,
1204
+ z,
1205
+ };
1198
1206
  }
1199
1207
  }
1200
1208
  }
1201
- function adoptUserNodes(nodes, nodeLookup, options = {
1209
+ function adoptUserNodes(nodes, nodeLookup, parentLookup, options = {
1202
1210
  nodeOrigin: [0, 0],
1203
1211
  elevateNodesOnSelect: true,
1204
1212
  defaults: {},
1213
+ checkEquality: true,
1205
1214
  }) {
1206
1215
  const tmpLookup = new Map(nodeLookup);
1207
1216
  nodeLookup.clear();
1217
+ parentLookup.clear();
1208
1218
  const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1209
- const parentNodeIds = new Set();
1210
1219
  nodes.forEach((userNode) => {
1211
- const currentStoreNode = tmpLookup.get(userNode.id);
1212
- if (userNode.parentId) {
1213
- parentNodeIds.add(userNode.parentId);
1214
- }
1215
- if (userNode === currentStoreNode?.internals.userNode) {
1216
- nodeLookup.set(userNode.id, currentStoreNode);
1220
+ let internalNode = tmpLookup.get(userNode.id);
1221
+ if (options.checkEquality && userNode === internalNode?.internals.userNode) {
1222
+ nodeLookup.set(userNode.id, internalNode);
1217
1223
  }
1218
1224
  else {
1219
- nodeLookup.set(userNode.id, {
1225
+ internalNode = {
1220
1226
  ...options.defaults,
1221
1227
  ...userNode,
1222
1228
  measured: {
@@ -1225,79 +1231,93 @@ function adoptUserNodes(nodes, nodeLookup, options = {
1225
1231
  },
1226
1232
  internals: {
1227
1233
  positionAbsolute: userNode.position,
1228
- handleBounds: currentStoreNode?.internals?.handleBounds,
1234
+ handleBounds: internalNode?.internals.handleBounds,
1229
1235
  z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0),
1230
1236
  userNode,
1231
- isParent: false,
1232
1237
  },
1233
- });
1238
+ };
1239
+ nodeLookup.set(userNode.id, internalNode);
1240
+ }
1241
+ if (userNode.parentId) {
1242
+ const childNodes = parentLookup.get(userNode.parentId);
1243
+ if (childNodes) {
1244
+ childNodes.push(internalNode);
1245
+ }
1246
+ else {
1247
+ parentLookup.set(userNode.parentId, [internalNode]);
1248
+ }
1234
1249
  }
1235
1250
  });
1236
- if (parentNodeIds.size > 0) {
1237
- updateAbsolutePositions(nodeLookup, options, parentNodeIds);
1251
+ if (parentLookup.size > 0) {
1252
+ updateAbsolutePositions(nodeLookup, options);
1238
1253
  }
1239
1254
  }
1240
1255
  function calculateXYZPosition(node, nodeLookup, result, nodeOrigin = [0, 0]) {
1241
1256
  if (!node.parentId) {
1242
1257
  return result;
1243
1258
  }
1244
- const parentNode = nodeLookup.get(node.parentId);
1245
- const { position: parentNodePosition } = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
1246
- return calculateXYZPosition(parentNode, nodeLookup, {
1247
- x: (result.x ?? 0) + parentNodePosition.x,
1248
- y: (result.y ?? 0) + parentNodePosition.y,
1249
- z: (parentNode.internals.z ?? 0) > (result.z ?? 0) ? parentNode.internals.z ?? 0 : result.z ?? 0,
1250
- }, parentNode.origin || nodeOrigin);
1251
- }
1252
- function handleParentExpand(nodes, nodeLookup) {
1259
+ const parent = nodeLookup.get(node.parentId);
1260
+ const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin).position;
1261
+ return calculateXYZPosition(parent, nodeLookup, {
1262
+ x: (result.x ?? 0) + parentPosition.x,
1263
+ y: (result.y ?? 0) + parentPosition.y,
1264
+ z: (parent.internals.z ?? 0) > (result.z ?? 0) ? parent.internals.z ?? 0 : result.z ?? 0,
1265
+ }, parent.origin || nodeOrigin);
1266
+ }
1267
+ function handleExpandParent(children, nodeLookup, parentLookup, nodeOrigin) {
1253
1268
  const changes = [];
1254
- const chilNodeRects = new Map();
1255
- nodes.forEach((node) => {
1256
- const parentId = node.parentId;
1257
- if (node.expandParent && parentId) {
1258
- const parentNode = nodeLookup.get(parentId);
1259
- if (parentNode) {
1260
- const parentRect = chilNodeRects.get(parentId) || nodeToRect(parentNode, node.origin);
1261
- const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin));
1262
- chilNodeRects.set(parentId, expandedRect);
1263
- }
1269
+ const parentExpansions = new Map();
1270
+ // determine the expanded rectangle the child nodes would take for each parent
1271
+ for (const child of children) {
1272
+ const parent = nodeLookup.get(child.parentId);
1273
+ if (!parent) {
1274
+ continue;
1264
1275
  }
1265
- });
1266
- if (chilNodeRects.size > 0) {
1267
- chilNodeRects.forEach((rect, id) => {
1268
- const origParent = nodeLookup.get(id);
1269
- const { position } = getNodePositionWithOrigin(origParent, origParent.origin);
1270
- const dimensions = getNodeDimensions(origParent);
1271
- if (rect.x < position.x || rect.y < position.y) {
1272
- const xChange = Math.round(Math.abs(position.x - rect.x));
1273
- const yChange = Math.round(Math.abs(position.y - rect.y));
1276
+ const parentRect = parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent, parent.origin ?? nodeOrigin);
1277
+ const expandedRect = getBoundsOfRects(parentRect, child.rect);
1278
+ parentExpansions.set(child.parentId, { expandedRect, parent });
1279
+ }
1280
+ if (parentExpansions.size > 0) {
1281
+ parentExpansions.forEach(({ expandedRect, parent }, parentId) => {
1282
+ // determine the position & dimensions of the parent
1283
+ const { position } = getNodePositionWithOrigin(parent, parent.origin);
1284
+ const dimensions = getNodeDimensions(parent);
1285
+ // determine how much the parent expands by moving the position
1286
+ const xChange = expandedRect.x < position.x ? Math.round(Math.abs(position.x - expandedRect.x)) : 0;
1287
+ const yChange = expandedRect.y < position.y ? Math.round(Math.abs(position.y - expandedRect.y)) : 0;
1288
+ if (xChange > 0 || yChange > 0) {
1274
1289
  changes.push({
1275
- id,
1290
+ id: parentId,
1276
1291
  type: 'position',
1277
1292
  position: {
1278
1293
  x: position.x - xChange,
1279
1294
  y: position.y - yChange,
1280
1295
  },
1281
1296
  });
1282
- changes.push({
1283
- id,
1284
- type: 'dimensions',
1285
- resizing: true,
1286
- dimensions: {
1287
- width: dimensions.width + xChange,
1288
- height: dimensions.height + yChange,
1289
- },
1297
+ // We move all child nodes in the oppsite direction
1298
+ // so the x,y changes of the parent do not move the children
1299
+ const childNodes = parentLookup.get(parentId);
1300
+ childNodes?.forEach((childNode) => {
1301
+ if (!children.some((child) => child.id === childNode.id)) {
1302
+ changes.push({
1303
+ id: childNode.id,
1304
+ type: 'position',
1305
+ position: {
1306
+ x: childNode.position.x + xChange,
1307
+ y: childNode.position.y + yChange,
1308
+ },
1309
+ });
1310
+ }
1290
1311
  });
1291
- // @todo we need to reset child node positions if < 0
1292
1312
  }
1293
- else if (dimensions.width < rect.width || dimensions.height < rect.height) {
1313
+ if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height) {
1294
1314
  changes.push({
1295
- id,
1315
+ id: parentId,
1296
1316
  type: 'dimensions',
1297
- resizing: true,
1317
+ setAttributes: true,
1298
1318
  dimensions: {
1299
- width: Math.max(dimensions.width, rect.width),
1300
- height: Math.max(dimensions.height, rect.height),
1319
+ width: Math.max(dimensions.width, Math.round(expandedRect.width)),
1320
+ height: Math.max(dimensions.height, Math.round(expandedRect.height)),
1301
1321
  },
1302
1322
  });
1303
1323
  }
@@ -1305,16 +1325,17 @@ function handleParentExpand(nodes, nodeLookup) {
1305
1325
  }
1306
1326
  return changes;
1307
1327
  }
1308
- function updateNodeDimensions(updates, nodeLookup, domNode, nodeOrigin) {
1328
+ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOrigin) {
1309
1329
  const viewportNode = domNode?.querySelector('.xyflow__viewport');
1330
+ let updatedInternals = false;
1310
1331
  if (!viewportNode) {
1311
- return [];
1332
+ return { changes: [], updatedInternals };
1312
1333
  }
1313
1334
  const changes = [];
1314
1335
  const style = window.getComputedStyle(viewportNode);
1315
1336
  const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
1316
1337
  // in this array we collect nodes, that might trigger changes (like expanding parent)
1317
- const triggerChangeNodes = [];
1338
+ const parentExpandChildren = [];
1318
1339
  updates.forEach((update) => {
1319
1340
  const node = nodeLookup.get(update.id);
1320
1341
  if (node?.hidden) {
@@ -1325,19 +1346,18 @@ function updateNodeDimensions(updates, nodeLookup, domNode, nodeOrigin) {
1325
1346
  handleBounds: undefined,
1326
1347
  },
1327
1348
  });
1349
+ updatedInternals = true;
1328
1350
  }
1329
1351
  else if (node) {
1330
1352
  const dimensions = getDimensions(update.nodeElement);
1353
+ const dimensionChanged = node.measured.width !== dimensions.width || node.measured.height !== dimensions.height;
1331
1354
  const doUpdate = !!(dimensions.width &&
1332
1355
  dimensions.height &&
1333
- (node.measured?.width !== dimensions.width || node.measured?.height !== dimensions.height || update.force));
1356
+ (dimensionChanged || !node.internals.handleBounds || update.force));
1334
1357
  if (doUpdate) {
1335
1358
  const newNode = {
1336
1359
  ...node,
1337
- measured: {
1338
- ...node.measured,
1339
- ...dimensions,
1340
- },
1360
+ measured: dimensions,
1341
1361
  internals: {
1342
1362
  ...node.internals,
1343
1363
  handleBounds: {
@@ -1347,22 +1367,29 @@ function updateNodeDimensions(updates, nodeLookup, domNode, nodeOrigin) {
1347
1367
  },
1348
1368
  };
1349
1369
  nodeLookup.set(node.id, newNode);
1350
- changes.push({
1351
- id: newNode.id,
1352
- type: 'dimensions',
1353
- dimensions,
1354
- });
1355
- if (newNode.expandParent) {
1356
- triggerChangeNodes.push(newNode);
1370
+ updatedInternals = true;
1371
+ if (dimensionChanged) {
1372
+ changes.push({
1373
+ id: newNode.id,
1374
+ type: 'dimensions',
1375
+ dimensions,
1376
+ });
1377
+ if (newNode.expandParent && newNode.parentId) {
1378
+ parentExpandChildren.push({
1379
+ id: newNode.id,
1380
+ parentId: newNode.parentId,
1381
+ rect: nodeToRect(newNode, newNode.origin || nodeOrigin),
1382
+ });
1383
+ }
1357
1384
  }
1358
1385
  }
1359
1386
  }
1360
1387
  });
1361
- if (triggerChangeNodes.length > 0) {
1362
- const parentExpandChanges = handleParentExpand(triggerChangeNodes, nodeLookup);
1388
+ if (parentExpandChildren.length > 0) {
1389
+ const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
1363
1390
  changes.push(...parentExpandChanges);
1364
1391
  }
1365
- return changes;
1392
+ return { changes, updatedInternals };
1366
1393
  }
1367
1394
  function panBy({ delta, panZoom, transform, translateExtent, width, height, }) {
1368
1395
  if (!panZoom || (!delta.x && !delta.y)) {
@@ -1413,9 +1440,6 @@ function shallowNodeData(a, b) {
1413
1440
  return true;
1414
1441
  }
1415
1442
 
1416
- function wrapSelectionDragFunc(selectionFunc) {
1417
- return (event, _, nodes) => selectionFunc?.(event, nodes);
1418
- }
1419
1443
  function isParentSelected(node, nodeLookup) {
1420
1444
  if (!node.parentId) {
1421
1445
  return false;
@@ -1442,31 +1466,33 @@ function hasSelector(target, selector, domNode) {
1442
1466
  }
1443
1467
  // looks for all selected nodes and created a NodeDragItem for each of them
1444
1468
  function getDragItems(nodeLookup, nodesDraggable, mousePos, nodeId) {
1445
- const dragItems = [];
1469
+ const dragItems = new Map();
1446
1470
  for (const [id, node] of nodeLookup) {
1447
1471
  if ((node.selected || node.id === nodeId) &&
1448
1472
  (!node.parentId || !isParentSelected(node, nodeLookup)) &&
1449
1473
  (node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'))) {
1450
1474
  const internalNode = nodeLookup.get(id);
1451
- dragItems.push({
1452
- id: internalNode.id,
1453
- position: internalNode.position || { x: 0, y: 0 },
1454
- distance: {
1455
- x: mousePos.x - (internalNode.internals.positionAbsolute?.x ?? 0),
1456
- y: mousePos.y - (internalNode.internals.positionAbsolute?.y ?? 0),
1457
- },
1458
- extent: internalNode.extent,
1459
- parentId: internalNode.parentId,
1460
- origin: internalNode.origin,
1461
- expandParent: internalNode.expandParent,
1462
- internals: {
1463
- positionAbsolute: internalNode.internals.positionAbsolute || { x: 0, y: 0 },
1464
- },
1465
- measured: {
1466
- width: internalNode.measured.width || 0,
1467
- height: internalNode.measured.height || 0,
1468
- },
1469
- });
1475
+ if (internalNode) {
1476
+ dragItems.set(id, {
1477
+ id,
1478
+ position: internalNode.position || { x: 0, y: 0 },
1479
+ distance: {
1480
+ x: mousePos.x - internalNode.internals.positionAbsolute.x,
1481
+ y: mousePos.y - internalNode.internals.positionAbsolute.y,
1482
+ },
1483
+ extent: internalNode.extent,
1484
+ parentId: internalNode.parentId,
1485
+ origin: internalNode.origin,
1486
+ expandParent: internalNode.expandParent,
1487
+ internals: {
1488
+ positionAbsolute: internalNode.internals.positionAbsolute || { x: 0, y: 0 },
1489
+ },
1490
+ measured: {
1491
+ width: internalNode.measured.width ?? 0,
1492
+ height: internalNode.measured.height ?? 0,
1493
+ },
1494
+ });
1495
+ }
1470
1496
  }
1471
1497
  }
1472
1498
  return dragItems;
@@ -1475,24 +1501,34 @@ function getDragItems(nodeLookup, nodesDraggable, mousePos, nodeId) {
1475
1501
  // 1. the dragged node (or the first of the list, if we are dragging a node selection)
1476
1502
  // 2. array of selected nodes (for multi selections)
1477
1503
  function getEventHandlerParams({ nodeId, dragItems, nodeLookup, }) {
1478
- const nodesFromDragItems = dragItems.map((n) => {
1479
- const node = nodeLookup.get(n.id);
1480
- return {
1504
+ const nodesFromDragItems = [];
1505
+ for (const [id, dragItem] of dragItems) {
1506
+ const node = nodeLookup.get(id);
1507
+ if (node) {
1508
+ nodesFromDragItems.push({
1509
+ ...node,
1510
+ position: dragItem.position,
1511
+ });
1512
+ }
1513
+ }
1514
+ if (!nodeId) {
1515
+ return [nodesFromDragItems[0], nodesFromDragItems];
1516
+ }
1517
+ const node = nodeLookup.get(nodeId);
1518
+ return [
1519
+ {
1481
1520
  ...node,
1482
- position: n.position,
1483
- measured: {
1484
- ...n.measured,
1485
- },
1486
- };
1487
- });
1488
- return [nodeId ? nodesFromDragItems.find((n) => n.id === nodeId) : nodesFromDragItems[0], nodesFromDragItems];
1521
+ position: dragItems.get(nodeId)?.position || node.position,
1522
+ },
1523
+ nodesFromDragItems,
1524
+ ];
1489
1525
  }
1490
1526
 
1491
1527
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1492
1528
  function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragStop, }) {
1493
1529
  let lastPos = { x: null, y: null };
1494
1530
  let autoPanId = 0;
1495
- let dragItems = [];
1531
+ let dragItems = new Map();
1496
1532
  let autoPanStarted = false;
1497
1533
  let mousePosition = { x: 0, y: 0 };
1498
1534
  let containerBounds = null;
@@ -1506,12 +1542,12 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1506
1542
  lastPos = { x, y };
1507
1543
  let hasChange = false;
1508
1544
  let nodesBox = { x: 0, y: 0, x2: 0, y2: 0 };
1509
- if (dragItems.length > 1 && nodeExtent) {
1510
- const rect = getNodesBounds(dragItems, { nodeOrigin });
1545
+ if (dragItems.size > 1 && nodeExtent) {
1546
+ const rect = getInternalNodesBounds(dragItems, { nodeOrigin });
1511
1547
  nodesBox = rectToBox(rect);
1512
1548
  }
1513
- dragItems = dragItems.map((n) => {
1514
- let nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
1549
+ for (const [id, dragItem] of dragItems) {
1550
+ let nextPosition = { x: x - dragItem.distance.x, y: y - dragItem.distance.y };
1515
1551
  if (snapToGrid) {
1516
1552
  nextPosition = snapPosition(nextPosition, snapGrid);
1517
1553
  }
@@ -1521,19 +1557,19 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1521
1557
  [nodeExtent[0][0], nodeExtent[0][1]],
1522
1558
  [nodeExtent[1][0], nodeExtent[1][1]],
1523
1559
  ];
1524
- if (dragItems.length > 1 && nodeExtent && !n.extent) {
1525
- const { positionAbsolute } = n.internals;
1560
+ if (dragItems.size > 1 && nodeExtent && !dragItem.extent) {
1561
+ const { positionAbsolute } = dragItem.internals;
1526
1562
  const x1 = positionAbsolute.x - nodesBox.x + nodeExtent[0][0];
1527
- const x2 = positionAbsolute.x + (n.measured?.width ?? 0) - nodesBox.x2 + nodeExtent[1][0];
1563
+ const x2 = positionAbsolute.x + dragItem.measured.width - nodesBox.x2 + nodeExtent[1][0];
1528
1564
  const y1 = positionAbsolute.y - nodesBox.y + nodeExtent[0][1];
1529
- const y2 = positionAbsolute.y + (n.measured?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
1565
+ const y2 = positionAbsolute.y + dragItem.measured.height - nodesBox.y2 + nodeExtent[1][1];
1530
1566
  adjustedNodeExtent = [
1531
1567
  [x1, y1],
1532
1568
  [x2, y2],
1533
1569
  ];
1534
1570
  }
1535
1571
  const { position, positionAbsolute } = calculateNodePosition({
1536
- nodeId: n.id,
1572
+ nodeId: id,
1537
1573
  nextPosition,
1538
1574
  nodeLookup,
1539
1575
  nodeExtent: adjustedNodeExtent,
@@ -1541,11 +1577,10 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1541
1577
  onError,
1542
1578
  });
1543
1579
  // we want to make sure that we only fire a change event when there is a change
1544
- hasChange = hasChange || n.position.x !== position.x || n.position.y !== position.y;
1545
- n.position = position;
1546
- n.internals.positionAbsolute = positionAbsolute;
1547
- return n;
1548
- });
1580
+ hasChange = hasChange || dragItem.position.x !== position.x || dragItem.position.y !== position.y;
1581
+ dragItem.position = position;
1582
+ dragItem.internals.positionAbsolute = positionAbsolute;
1583
+ }
1549
1584
  if (!hasChange) {
1550
1585
  return;
1551
1586
  }
@@ -1559,8 +1594,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1559
1594
  onDrag?.(dragEvent, dragItems, currentNode, currentNodes);
1560
1595
  onNodeDrag?.(dragEvent, currentNode, currentNodes);
1561
1596
  if (!nodeId) {
1562
- const _onSelectionDrag = wrapSelectionDragFunc(onSelectionDrag);
1563
- _onSelectionDrag(dragEvent, currentNode, currentNodes);
1597
+ onSelectionDrag?.(dragEvent, currentNodes);
1564
1598
  }
1565
1599
  }
1566
1600
  }
@@ -1594,7 +1628,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1594
1628
  const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
1595
1629
  lastPos = pointerPos;
1596
1630
  dragItems = getDragItems(nodeLookup, nodesDraggable, pointerPos, nodeId);
1597
- if (dragItems.length > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) {
1631
+ if (dragItems.size > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) {
1598
1632
  const [currentNode, currentNodes] = getEventHandlerParams({
1599
1633
  nodeId,
1600
1634
  dragItems,
@@ -1603,8 +1637,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1603
1637
  onDragStart?.(event.sourceEvent, dragItems, currentNode, currentNodes);
1604
1638
  onNodeDragStart?.(event.sourceEvent, currentNode, currentNodes);
1605
1639
  if (!nodeId) {
1606
- const _onSelectionDragStart = wrapSelectionDragFunc(onSelectionDragStart);
1607
- _onSelectionDragStart(event.sourceEvent, currentNode, currentNodes);
1640
+ onSelectionDragStart?.(event.sourceEvent, currentNodes);
1608
1641
  }
1609
1642
  }
1610
1643
  }
@@ -1648,7 +1681,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1648
1681
  autoPanStarted = false;
1649
1682
  dragStarted = false;
1650
1683
  cancelAnimationFrame(autoPanId);
1651
- if (dragItems.length > 0) {
1684
+ if (dragItems.size > 0) {
1652
1685
  const { nodeLookup, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems();
1653
1686
  updateNodePositions(dragItems, false);
1654
1687
  if (onDragStop || onNodeDragStop || (!nodeId && onSelectionDragStop)) {
@@ -1660,8 +1693,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1660
1693
  onDragStop?.(event.sourceEvent, dragItems, currentNode, currentNodes);
1661
1694
  onNodeDragStop?.(event.sourceEvent, currentNode, currentNodes);
1662
1695
  if (!nodeId) {
1663
- const _onSelectionDragStop = wrapSelectionDragFunc(onSelectionDragStop);
1664
- _onSelectionDragStop(event.sourceEvent, currentNode, currentNodes);
1696
+ onSelectionDragStop?.(event.sourceEvent, currentNodes);
1665
1697
  }
1666
1698
  }
1667
1699
  }
@@ -1693,8 +1725,8 @@ function getHandles(node, handleBounds, type, currentHandle) {
1693
1725
  id: h.id || null,
1694
1726
  type,
1695
1727
  nodeId: node.id,
1696
- x: (node.internals.positionAbsolute.x ?? 0) + h.x + h.width / 2,
1697
- y: (node.internals.positionAbsolute.y ?? 0) + h.y + h.height / 2,
1728
+ x: node.internals.positionAbsolute.x + h.x + h.width / 2,
1729
+ y: node.internals.positionAbsolute.y + h.y + h.height / 2,
1698
1730
  });
1699
1731
  }
1700
1732
  return res;
@@ -1940,15 +1972,30 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
1940
1972
  const nextZoom = transform[2] * Math.pow(2, pinchDelta);
1941
1973
  panZoom.scaleTo(nextZoom);
1942
1974
  };
1975
+ let panStart = [0, 0];
1976
+ const panStartHandler = (event) => {
1977
+ if (event.sourceEvent.type === 'mousedown' || event.sourceEvent.type === 'touchstart') {
1978
+ panStart = [
1979
+ event.sourceEvent.clientX ?? event.sourceEvent.touches[0].clientX,
1980
+ event.sourceEvent.clientY ?? event.sourceEvent.touches[0].clientY,
1981
+ ];
1982
+ }
1983
+ };
1943
1984
  const panHandler = (event) => {
1944
1985
  const transform = getTransform();
1945
- if (event.sourceEvent.type !== 'mousemove' || !panZoom) {
1986
+ if ((event.sourceEvent.type !== 'mousemove' && event.sourceEvent.type !== 'touchmove') || !panZoom) {
1946
1987
  return;
1947
1988
  }
1989
+ const panCurrent = [
1990
+ event.sourceEvent.clientX ?? event.sourceEvent.touches[0].clientX,
1991
+ event.sourceEvent.clientY ?? event.sourceEvent.touches[0].clientY,
1992
+ ];
1993
+ const panDelta = [panCurrent[0] - panStart[0], panCurrent[1] - panStart[1]];
1994
+ panStart = panCurrent;
1948
1995
  const moveScale = getViewScale() * Math.max(transform[2], Math.log(transform[2])) * (inversePan ? -1 : 1);
1949
1996
  const position = {
1950
- x: transform[0] - event.sourceEvent.movementX * moveScale,
1951
- y: transform[1] - event.sourceEvent.movementY * moveScale,
1997
+ x: transform[0] - panDelta[0] * moveScale,
1998
+ y: transform[1] - panDelta[1] * moveScale,
1952
1999
  };
1953
2000
  const extent = [
1954
2001
  [0, 0],
@@ -1961,6 +2008,7 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
1961
2008
  }, extent, translateExtent);
1962
2009
  };
1963
2010
  const zoomAndPanHandler = zoom()
2011
+ .on('start', panStartHandler)
1964
2012
  // @ts-ignore
1965
2013
  .on('zoom', pannable ? panHandler : null)
1966
2014
  // @ts-ignore
@@ -2043,7 +2091,9 @@ function createPanOnScrollHandler({ zoomPanValues, noWheelClassName, d3Selection
2043
2091
  }
2044
2092
  function createZoomOnScrollHandler({ noWheelClassName, preventScrolling, d3ZoomHandler }) {
2045
2093
  return function (event, d) {
2046
- if (!preventScrolling || isWrappedWithClass(event, noWheelClassName)) {
2094
+ // we still want to enable pinch zooming even if preventScrolling is set to false
2095
+ const preventZoom = !preventScrolling && event.type === 'wheel' && !event.ctrlKey;
2096
+ if (preventZoom || isWrappedWithClass(event, noWheelClassName)) {
2047
2097
  return null;
2048
2098
  }
2049
2099
  event.preventDefault();
@@ -2558,16 +2608,6 @@ const initStartValues = {
2558
2608
  pointerY: 0,
2559
2609
  aspectRatio: 1,
2560
2610
  };
2561
- const initChange = {
2562
- x: 0,
2563
- y: 0,
2564
- width: 0,
2565
- height: 0,
2566
- isXPosChange: false,
2567
- isYPosChange: false,
2568
- isWidthChange: false,
2569
- isHeightChange: false,
2570
- };
2571
2611
  function nodeToParentExtent(node) {
2572
2612
  return [
2573
2613
  [0, 0],
@@ -2586,7 +2626,7 @@ function nodeToChildExtent(child, parent, nodeOrigin) {
2586
2626
  [x + width - originOffsetX, y + height - originOffsetY],
2587
2627
  ];
2588
2628
  }
2589
- function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2629
+ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2590
2630
  const selection = select(domNode);
2591
2631
  function update({ controlPosition, boundaries, keepAspectRatio, onResizeStart, onResize, onResizeEnd, shouldResize, }) {
2592
2632
  let prevValues = { ...initPrevValues };
@@ -2601,128 +2641,127 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2601
2641
  .on('start', (event) => {
2602
2642
  const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin } = getStoreItems();
2603
2643
  node = nodeLookup.get(nodeId);
2604
- if (node) {
2605
- const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2606
- prevValues = {
2607
- width: node.measured?.width ?? 0,
2608
- height: node.measured?.height ?? 0,
2609
- x: node.position.x ?? 0,
2610
- y: node.position.y ?? 0,
2611
- };
2612
- startValues = {
2613
- ...prevValues,
2614
- pointerX: xSnapped,
2615
- pointerY: ySnapped,
2616
- aspectRatio: prevValues.width / prevValues.height,
2617
- };
2618
- parentNode = undefined;
2619
- if (node.extent === 'parent' || node.expandParent) {
2620
- parentNode = nodeLookup.get(node.parentId);
2621
- if (parentNode && node.extent === 'parent') {
2622
- parentExtent = nodeToParentExtent(parentNode);
2623
- }
2644
+ if (!node) {
2645
+ return;
2646
+ }
2647
+ const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2648
+ prevValues = {
2649
+ width: node.measured?.width ?? 0,
2650
+ height: node.measured?.height ?? 0,
2651
+ x: node.position.x ?? 0,
2652
+ y: node.position.y ?? 0,
2653
+ };
2654
+ startValues = {
2655
+ ...prevValues,
2656
+ pointerX: xSnapped,
2657
+ pointerY: ySnapped,
2658
+ aspectRatio: prevValues.width / prevValues.height,
2659
+ };
2660
+ parentNode = undefined;
2661
+ if (node.extent === 'parent' || node.expandParent) {
2662
+ parentNode = nodeLookup.get(node.parentId);
2663
+ if (parentNode && node.extent === 'parent') {
2664
+ parentExtent = nodeToParentExtent(parentNode);
2624
2665
  }
2625
- // Collect all child nodes to correct their relative positions when top/left changes
2626
- // Determine largest minimal extent the parent node is allowed to resize to
2627
- childNodes = [];
2628
- childExtent = undefined;
2629
- for (const [childId, child] of nodeLookup) {
2630
- if (child.parentId === nodeId) {
2631
- childNodes.push({
2632
- id: childId,
2633
- position: { ...child.position },
2634
- extent: child.extent,
2635
- });
2636
- if (child.extent === 'parent' || child.expandParent) {
2637
- const extent = nodeToChildExtent(child, node, child.origin ?? nodeOrigin);
2638
- if (childExtent) {
2639
- childExtent = [
2640
- [Math.min(extent[0][0], childExtent[0][0]), Math.min(extent[0][1], childExtent[0][1])],
2641
- [Math.max(extent[1][0], childExtent[1][0]), Math.max(extent[1][1], childExtent[1][1])],
2642
- ];
2643
- }
2644
- else {
2645
- childExtent = extent;
2646
- }
2666
+ }
2667
+ // Collect all child nodes to correct their relative positions when top/left changes
2668
+ // Determine largest minimal extent the parent node is allowed to resize to
2669
+ childNodes = [];
2670
+ childExtent = undefined;
2671
+ for (const [childId, child] of nodeLookup) {
2672
+ if (child.parentId === nodeId) {
2673
+ childNodes.push({
2674
+ id: childId,
2675
+ position: { ...child.position },
2676
+ extent: child.extent,
2677
+ });
2678
+ if (child.extent === 'parent' || child.expandParent) {
2679
+ const extent = nodeToChildExtent(child, node, child.origin ?? nodeOrigin);
2680
+ if (childExtent) {
2681
+ childExtent = [
2682
+ [Math.min(extent[0][0], childExtent[0][0]), Math.min(extent[0][1], childExtent[0][1])],
2683
+ [Math.max(extent[1][0], childExtent[1][0]), Math.max(extent[1][1], childExtent[1][1])],
2684
+ ];
2685
+ }
2686
+ else {
2687
+ childExtent = extent;
2647
2688
  }
2648
2689
  }
2649
2690
  }
2650
- onResizeStart?.(event, { ...prevValues });
2651
2691
  }
2692
+ onResizeStart?.(event, { ...prevValues });
2652
2693
  })
2653
2694
  .on('drag', (event) => {
2654
2695
  const { transform, snapGrid, snapToGrid, nodeOrigin: storeNodeOrigin } = getStoreItems();
2655
2696
  const pointerPosition = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2656
2697
  const childChanges = [];
2657
- if (node) {
2658
- const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues;
2659
- const change = { ...initChange };
2660
- const nodeOrigin = node.origin ?? storeNodeOrigin;
2661
- const { width, height, x, y } = getDimensionsAfterResize(startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio, nodeOrigin, parentExtent, childExtent);
2662
- const isWidthChange = width !== prevWidth;
2663
- const isHeightChange = height !== prevHeight;
2664
- const isXPosChange = x !== prevX && isWidthChange;
2665
- const isYPosChange = y !== prevY && isHeightChange;
2666
- if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] == 1) {
2667
- change.isXPosChange = isXPosChange;
2668
- change.isYPosChange = isYPosChange;
2669
- change.x = isXPosChange ? x : prevX;
2670
- change.y = isYPosChange ? y : prevY;
2671
- prevValues.x = change.x;
2672
- prevValues.y = change.y;
2673
- // Fix expandParent when resizing from top/left
2674
- if (parentNode && node.expandParent) {
2675
- if (change.x < 0) {
2676
- prevValues.x = 0;
2677
- startValues.x = startValues.x - change.x;
2678
- }
2679
- if (change.y < 0) {
2680
- prevValues.y = 0;
2681
- startValues.y = startValues.y - change.y;
2682
- }
2698
+ if (!node) {
2699
+ return;
2700
+ }
2701
+ const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues;
2702
+ const change = {};
2703
+ const nodeOrigin = node.origin ?? storeNodeOrigin;
2704
+ const { width, height, x, y } = getDimensionsAfterResize(startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio, nodeOrigin, parentExtent, childExtent);
2705
+ const isWidthChange = width !== prevWidth;
2706
+ const isHeightChange = height !== prevHeight;
2707
+ const isXPosChange = x !== prevX && isWidthChange;
2708
+ const isYPosChange = y !== prevY && isHeightChange;
2709
+ if (!isXPosChange && !isYPosChange && !isWidthChange && !isHeightChange) {
2710
+ return;
2711
+ }
2712
+ if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] == 1) {
2713
+ change.x = isXPosChange ? x : prevValues.x;
2714
+ change.y = isYPosChange ? y : prevValues.y;
2715
+ prevValues.x = change.x;
2716
+ prevValues.y = change.y;
2717
+ // Fix expandParent when resizing from top/left
2718
+ if (parentNode && node.expandParent) {
2719
+ if (change.x && change.x < 0) {
2720
+ prevValues.x = 0;
2721
+ startValues.x = startValues.x - change.x;
2683
2722
  }
2684
- if (childNodes.length > 0) {
2685
- const xChange = x - prevX;
2686
- const yChange = y - prevY;
2687
- for (const childNode of childNodes) {
2688
- childNode.position = {
2689
- x: childNode.position.x - xChange + nodeOrigin[0] * (width - prevWidth),
2690
- y: childNode.position.y - yChange + nodeOrigin[1] * (height - prevHeight),
2691
- };
2692
- childChanges.push(childNode);
2693
- }
2723
+ if (change.y && change.y < 0) {
2724
+ prevValues.y = 0;
2725
+ startValues.y = startValues.y - change.y;
2694
2726
  }
2695
2727
  }
2696
- if (isWidthChange || isHeightChange) {
2697
- change.isWidthChange = isWidthChange;
2698
- change.isHeightChange = isHeightChange;
2699
- change.width = width;
2700
- change.height = height;
2701
- prevValues.width = change.width;
2702
- prevValues.height = change.height;
2703
- }
2704
- if (!change.isXPosChange && !change.isYPosChange && !isWidthChange && !isHeightChange) {
2705
- return;
2706
- }
2707
- const direction = getResizeDirection({
2708
- width: prevValues.width,
2709
- prevWidth,
2710
- height: prevValues.height,
2711
- prevHeight,
2712
- affectsX: controlDirection.affectsX,
2713
- affectsY: controlDirection.affectsY,
2714
- });
2715
- const nextValues = { ...prevValues, direction };
2716
- const callResize = shouldResize?.(event, nextValues);
2717
- if (callResize === false) {
2718
- return;
2728
+ if (childNodes.length > 0) {
2729
+ const xChange = x - prevX;
2730
+ const yChange = y - prevY;
2731
+ for (const childNode of childNodes) {
2732
+ childNode.position = {
2733
+ x: childNode.position.x - xChange + nodeOrigin[0] * (width - prevWidth),
2734
+ y: childNode.position.y - yChange + nodeOrigin[1] * (height - prevHeight),
2735
+ };
2736
+ childChanges.push(childNode);
2737
+ }
2719
2738
  }
2720
- onResize?.(event, nextValues);
2721
- onChange(change, childChanges);
2722
2739
  }
2740
+ if (isWidthChange || isHeightChange) {
2741
+ change.width = isWidthChange ? width : prevValues.width;
2742
+ change.height = isHeightChange ? height : prevValues.height;
2743
+ prevValues.width = change.width;
2744
+ prevValues.height = change.height;
2745
+ }
2746
+ const direction = getResizeDirection({
2747
+ width: prevValues.width,
2748
+ prevWidth,
2749
+ height: prevValues.height,
2750
+ prevHeight,
2751
+ affectsX: controlDirection.affectsX,
2752
+ affectsY: controlDirection.affectsY,
2753
+ });
2754
+ const nextValues = { ...prevValues, direction };
2755
+ const callResize = shouldResize?.(event, nextValues);
2756
+ if (callResize === false) {
2757
+ return;
2758
+ }
2759
+ onResize?.(event, nextValues);
2760
+ onChange(change, childChanges);
2723
2761
  })
2724
2762
  .on('end', (event) => {
2725
2763
  onResizeEnd?.(event, { ...prevValues });
2764
+ onEnd?.();
2726
2765
  });
2727
2766
  selection.call(dragHandler);
2728
2767
  }
@@ -2735,4 +2774,4 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2735
2774
  };
2736
2775
  }
2737
2776
 
2738
- 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, 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, handleParentExpand, infiniteExtent, isCoordinateExtent, isEdgeBase, isEdgeVisible, isInputDOMNode, isInternalNodeBase, isMacOs, isMouseEvent, isNodeBase, isNumeric, isRectObject, nodeHasDimensions, nodeToBox, nodeToRect, panBy, pointToRendererPoint, rectToBox, rendererPointToPoint, shallowNodeData, snapPosition, updateAbsolutePositions, updateConnectionLookup, updateEdge, updateNodeDimensions };
2777
+ 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 };