@xyflow/system 0.0.23 → 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 (46) hide show
  1. package/dist/esm/index.js +337 -305
  2. package/dist/esm/index.mjs +337 -305
  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 +3 -3
  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 +6 -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/xyresizer/XYResizer.d.ts +6 -11
  23. package/dist/esm/xyresizer/XYResizer.d.ts.map +1 -1
  24. package/dist/umd/index.js +1 -1
  25. package/dist/umd/types/changes.d.ts +1 -0
  26. package/dist/umd/types/changes.d.ts.map +1 -1
  27. package/dist/umd/types/general.d.ts +1 -1
  28. package/dist/umd/types/general.d.ts.map +1 -1
  29. package/dist/umd/types/nodes.d.ts +3 -3
  30. package/dist/umd/types/nodes.d.ts.map +1 -1
  31. package/dist/umd/utils/general.d.ts +22 -2
  32. package/dist/umd/utils/general.d.ts.map +1 -1
  33. package/dist/umd/utils/graph.d.ts +6 -8
  34. package/dist/umd/utils/graph.d.ts.map +1 -1
  35. package/dist/umd/utils/store.d.ts +6 -5
  36. package/dist/umd/utils/store.d.ts.map +1 -1
  37. package/dist/umd/utils/types.d.ts +6 -0
  38. package/dist/umd/utils/types.d.ts.map +1 -1
  39. package/dist/umd/xydrag/XYDrag.d.ts +1 -1
  40. package/dist/umd/xydrag/XYDrag.d.ts.map +1 -1
  41. package/dist/umd/xydrag/utils.d.ts +2 -3
  42. package/dist/umd/xydrag/utils.d.ts.map +1 -1
  43. package/dist/umd/xyminimap/index.d.ts.map +1 -1
  44. package/dist/umd/xyresizer/XYResizer.d.ts +6 -11
  45. package/dist/umd/xyresizer/XYResizer.d.ts.map +1 -1
  46. package/package.json +3 -3
@@ -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,11 +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
- // TODO: this remove options.nodes.some with a Set
284
- if (isVisible &&
285
- (!options?.nodes || (options?.nodes.length && options?.nodes.some((optionNode) => optionNode.id === n.id)))) {
262
+ if (isVisible && (!optionNodeIds || optionNodeIds.has(n.id))) {
286
263
  filteredNodes.push(n);
287
264
  }
288
265
  });
@@ -453,19 +430,21 @@ const boxToRect = ({ x, y, x2, y2 }) => ({
453
430
  height: y2 - y,
454
431
  });
455
432
  const nodeToRect = (node, nodeOrigin = [0, 0]) => {
456
- const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
433
+ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
457
434
  return {
458
- ...positionAbsolute,
435
+ x,
436
+ y,
459
437
  width: node.measured?.width ?? node.width ?? 0,
460
438
  height: node.measured?.height ?? node.height ?? 0,
461
439
  };
462
440
  };
463
441
  const nodeToBox = (node, nodeOrigin = [0, 0]) => {
464
- const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
442
+ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
465
443
  return {
466
- ...positionAbsolute,
467
- x2: positionAbsolute.x + (node.measured?.width ?? node.width ?? 0),
468
- 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),
469
448
  };
470
449
  };
471
450
  const getBoundsOfRects = (rect1, rect2) => boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2)));
@@ -553,6 +532,32 @@ function nodeHasDimensions(node) {
553
532
  return ((node.measured?.width ?? node.width ?? node.initialWidth) !== undefined &&
554
533
  (node.measured?.height ?? node.height ?? node.initialHeight) !== undefined);
555
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
+ }
556
561
 
557
562
  function getPointerPosition(event, { snapGrid = [0, 0], snapToGrid = false, transform }) {
558
563
  const { x, y } = getEventPosition(event);
@@ -1175,31 +1180,33 @@ function updateAbsolutePositions(nodeLookup, options = {
1175
1180
  nodeOrigin: [0, 0],
1176
1181
  elevateNodesOnSelect: true,
1177
1182
  defaults: {},
1178
- }, parentNodeIds) {
1183
+ }) {
1179
1184
  const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1180
- for (const [id, node] of nodeLookup) {
1185
+ for (const [, node] of nodeLookup) {
1181
1186
  const parentId = node.parentId;
1182
- if (parentId && !nodeLookup.has(parentId)) {
1187
+ if (!parentId) {
1188
+ continue;
1189
+ }
1190
+ if (!nodeLookup.has(parentId)) {
1183
1191
  throw new Error(`Parent node ${parentId} not found`);
1184
1192
  }
1185
- if (parentId || node.internals.isParent || parentNodeIds?.has(id)) {
1186
- const parentNode = parentId ? nodeLookup.get(parentId) : null;
1187
- const { x, y, z } = calculateXYZPosition(node, nodeLookup, {
1188
- ...node.position,
1189
- z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0),
1190
- }, parentNode?.origin || options.nodeOrigin);
1191
- const currPosition = node.internals.positionAbsolute;
1192
- const positionChanged = x !== currPosition.x || y !== currPosition.y;
1193
- node.internals.positionAbsolute = positionChanged ? { x, y } : currPosition;
1194
- node.internals.z = z;
1195
- if (parentNodeIds !== undefined) {
1196
- node.internals.isParent = !!parentNodeIds?.has(id);
1197
- }
1198
- 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
+ };
1199
1206
  }
1200
1207
  }
1201
1208
  }
1202
- function adoptUserNodes(nodes, nodeLookup, options = {
1209
+ function adoptUserNodes(nodes, nodeLookup, parentLookup, options = {
1203
1210
  nodeOrigin: [0, 0],
1204
1211
  elevateNodesOnSelect: true,
1205
1212
  defaults: {},
@@ -1207,18 +1214,15 @@ function adoptUserNodes(nodes, nodeLookup, options = {
1207
1214
  }) {
1208
1215
  const tmpLookup = new Map(nodeLookup);
1209
1216
  nodeLookup.clear();
1217
+ parentLookup.clear();
1210
1218
  const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1211
- const parentNodeIds = new Set();
1212
1219
  nodes.forEach((userNode) => {
1213
- const currentStoreNode = tmpLookup.get(userNode.id);
1214
- if (userNode.parentId) {
1215
- parentNodeIds.add(userNode.parentId);
1216
- }
1217
- if (options.checkEquality && userNode === currentStoreNode?.internals.userNode) {
1218
- 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);
1219
1223
  }
1220
1224
  else {
1221
- nodeLookup.set(userNode.id, {
1225
+ internalNode = {
1222
1226
  ...options.defaults,
1223
1227
  ...userNode,
1224
1228
  measured: {
@@ -1227,79 +1231,93 @@ function adoptUserNodes(nodes, nodeLookup, options = {
1227
1231
  },
1228
1232
  internals: {
1229
1233
  positionAbsolute: userNode.position,
1230
- handleBounds: currentStoreNode?.internals.handleBounds,
1234
+ handleBounds: internalNode?.internals.handleBounds,
1231
1235
  z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0),
1232
1236
  userNode,
1233
- isParent: false,
1234
1237
  },
1235
- });
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
+ }
1236
1249
  }
1237
1250
  });
1238
- if (parentNodeIds.size > 0) {
1239
- updateAbsolutePositions(nodeLookup, options, parentNodeIds);
1251
+ if (parentLookup.size > 0) {
1252
+ updateAbsolutePositions(nodeLookup, options);
1240
1253
  }
1241
1254
  }
1242
1255
  function calculateXYZPosition(node, nodeLookup, result, nodeOrigin = [0, 0]) {
1243
1256
  if (!node.parentId) {
1244
1257
  return result;
1245
1258
  }
1246
- const parentNode = nodeLookup.get(node.parentId);
1247
- const { position: parentNodePosition } = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
1248
- return calculateXYZPosition(parentNode, nodeLookup, {
1249
- x: (result.x ?? 0) + parentNodePosition.x,
1250
- y: (result.y ?? 0) + parentNodePosition.y,
1251
- z: (parentNode.internals.z ?? 0) > (result.z ?? 0) ? parentNode.internals.z ?? 0 : result.z ?? 0,
1252
- }, parentNode.origin || nodeOrigin);
1253
- }
1254
- 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) {
1255
1268
  const changes = [];
1256
- const chilNodeRects = new Map();
1257
- nodes.forEach((node) => {
1258
- const parentId = node.parentId;
1259
- if (node.expandParent && parentId) {
1260
- const parentNode = nodeLookup.get(parentId);
1261
- if (parentNode) {
1262
- const parentRect = chilNodeRects.get(parentId) || nodeToRect(parentNode, node.origin);
1263
- const expandedRect = getBoundsOfRects(parentRect, nodeToRect(node, node.origin));
1264
- chilNodeRects.set(parentId, expandedRect);
1265
- }
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;
1266
1275
  }
1267
- });
1268
- if (chilNodeRects.size > 0) {
1269
- chilNodeRects.forEach((rect, id) => {
1270
- const origParent = nodeLookup.get(id);
1271
- const { position } = getNodePositionWithOrigin(origParent, origParent.origin);
1272
- const dimensions = getNodeDimensions(origParent);
1273
- if (rect.x < position.x || rect.y < position.y) {
1274
- const xChange = Math.round(Math.abs(position.x - rect.x));
1275
- 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) {
1276
1289
  changes.push({
1277
- id,
1290
+ id: parentId,
1278
1291
  type: 'position',
1279
1292
  position: {
1280
1293
  x: position.x - xChange,
1281
1294
  y: position.y - yChange,
1282
1295
  },
1283
1296
  });
1284
- changes.push({
1285
- id,
1286
- type: 'dimensions',
1287
- resizing: true,
1288
- dimensions: {
1289
- width: dimensions.width + xChange,
1290
- height: dimensions.height + yChange,
1291
- },
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
+ }
1292
1311
  });
1293
- // @todo we need to reset child node positions if < 0
1294
1312
  }
1295
- else if (dimensions.width < rect.width || dimensions.height < rect.height) {
1313
+ if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height) {
1296
1314
  changes.push({
1297
- id,
1315
+ id: parentId,
1298
1316
  type: 'dimensions',
1299
- resizing: true,
1317
+ setAttributes: true,
1300
1318
  dimensions: {
1301
- width: Math.max(dimensions.width, rect.width),
1302
- 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)),
1303
1321
  },
1304
1322
  });
1305
1323
  }
@@ -1307,7 +1325,7 @@ function handleParentExpand(nodes, nodeLookup) {
1307
1325
  }
1308
1326
  return changes;
1309
1327
  }
1310
- function updateNodeInternals(updates, nodeLookup, domNode, nodeOrigin) {
1328
+ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOrigin) {
1311
1329
  const viewportNode = domNode?.querySelector('.xyflow__viewport');
1312
1330
  let updatedInternals = false;
1313
1331
  if (!viewportNode) {
@@ -1317,7 +1335,7 @@ function updateNodeInternals(updates, nodeLookup, domNode, nodeOrigin) {
1317
1335
  const style = window.getComputedStyle(viewportNode);
1318
1336
  const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
1319
1337
  // in this array we collect nodes, that might trigger changes (like expanding parent)
1320
- const triggerChangeNodes = [];
1338
+ const parentExpandChildren = [];
1321
1339
  updates.forEach((update) => {
1322
1340
  const node = nodeLookup.get(update.id);
1323
1341
  if (node?.hidden) {
@@ -1356,15 +1374,19 @@ function updateNodeInternals(updates, nodeLookup, domNode, nodeOrigin) {
1356
1374
  type: 'dimensions',
1357
1375
  dimensions,
1358
1376
  });
1359
- if (newNode.expandParent) {
1360
- triggerChangeNodes.push(newNode);
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
+ });
1361
1383
  }
1362
1384
  }
1363
1385
  }
1364
1386
  }
1365
1387
  });
1366
- if (triggerChangeNodes.length > 0) {
1367
- const parentExpandChanges = handleParentExpand(triggerChangeNodes, nodeLookup);
1388
+ if (parentExpandChildren.length > 0) {
1389
+ const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
1368
1390
  changes.push(...parentExpandChanges);
1369
1391
  }
1370
1392
  return { changes, updatedInternals };
@@ -1418,9 +1440,6 @@ function shallowNodeData(a, b) {
1418
1440
  return true;
1419
1441
  }
1420
1442
 
1421
- function wrapSelectionDragFunc(selectionFunc) {
1422
- return (event, _, nodes) => selectionFunc?.(event, nodes);
1423
- }
1424
1443
  function isParentSelected(node, nodeLookup) {
1425
1444
  if (!node.parentId) {
1426
1445
  return false;
@@ -1447,31 +1466,33 @@ function hasSelector(target, selector, domNode) {
1447
1466
  }
1448
1467
  // looks for all selected nodes and created a NodeDragItem for each of them
1449
1468
  function getDragItems(nodeLookup, nodesDraggable, mousePos, nodeId) {
1450
- const dragItems = [];
1469
+ const dragItems = new Map();
1451
1470
  for (const [id, node] of nodeLookup) {
1452
1471
  if ((node.selected || node.id === nodeId) &&
1453
1472
  (!node.parentId || !isParentSelected(node, nodeLookup)) &&
1454
1473
  (node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'))) {
1455
1474
  const internalNode = nodeLookup.get(id);
1456
- dragItems.push({
1457
- id: internalNode.id,
1458
- position: internalNode.position || { x: 0, y: 0 },
1459
- distance: {
1460
- x: mousePos.x - internalNode.internals.positionAbsolute.x,
1461
- y: mousePos.y - internalNode.internals.positionAbsolute.y,
1462
- },
1463
- extent: internalNode.extent,
1464
- parentId: internalNode.parentId,
1465
- origin: internalNode.origin,
1466
- expandParent: internalNode.expandParent,
1467
- internals: {
1468
- positionAbsolute: internalNode.internals.positionAbsolute || { x: 0, y: 0 },
1469
- },
1470
- measured: {
1471
- width: internalNode.measured.width || 0,
1472
- height: internalNode.measured.height || 0,
1473
- },
1474
- });
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
+ }
1475
1496
  }
1476
1497
  }
1477
1498
  return dragItems;
@@ -1480,24 +1501,34 @@ function getDragItems(nodeLookup, nodesDraggable, mousePos, nodeId) {
1480
1501
  // 1. the dragged node (or the first of the list, if we are dragging a node selection)
1481
1502
  // 2. array of selected nodes (for multi selections)
1482
1503
  function getEventHandlerParams({ nodeId, dragItems, nodeLookup, }) {
1483
- const nodesFromDragItems = dragItems.map((n) => {
1484
- const node = nodeLookup.get(n.id);
1485
- 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
+ {
1486
1520
  ...node,
1487
- position: n.position,
1488
- measured: {
1489
- ...n.measured,
1490
- },
1491
- };
1492
- });
1493
- return [nodeId ? nodesFromDragItems.find((n) => n.id === nodeId) : nodesFromDragItems[0], nodesFromDragItems];
1521
+ position: dragItems.get(nodeId)?.position || node.position,
1522
+ },
1523
+ nodesFromDragItems,
1524
+ ];
1494
1525
  }
1495
1526
 
1496
1527
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1497
1528
  function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragStop, }) {
1498
1529
  let lastPos = { x: null, y: null };
1499
1530
  let autoPanId = 0;
1500
- let dragItems = [];
1531
+ let dragItems = new Map();
1501
1532
  let autoPanStarted = false;
1502
1533
  let mousePosition = { x: 0, y: 0 };
1503
1534
  let containerBounds = null;
@@ -1511,12 +1542,12 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1511
1542
  lastPos = { x, y };
1512
1543
  let hasChange = false;
1513
1544
  let nodesBox = { x: 0, y: 0, x2: 0, y2: 0 };
1514
- if (dragItems.length > 1 && nodeExtent) {
1515
- const rect = getNodesBounds(dragItems, { nodeOrigin });
1545
+ if (dragItems.size > 1 && nodeExtent) {
1546
+ const rect = getInternalNodesBounds(dragItems, { nodeOrigin });
1516
1547
  nodesBox = rectToBox(rect);
1517
1548
  }
1518
- dragItems = dragItems.map((n) => {
1519
- 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 };
1520
1551
  if (snapToGrid) {
1521
1552
  nextPosition = snapPosition(nextPosition, snapGrid);
1522
1553
  }
@@ -1526,19 +1557,19 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1526
1557
  [nodeExtent[0][0], nodeExtent[0][1]],
1527
1558
  [nodeExtent[1][0], nodeExtent[1][1]],
1528
1559
  ];
1529
- if (dragItems.length > 1 && nodeExtent && !n.extent) {
1530
- const { positionAbsolute } = n.internals;
1560
+ if (dragItems.size > 1 && nodeExtent && !dragItem.extent) {
1561
+ const { positionAbsolute } = dragItem.internals;
1531
1562
  const x1 = positionAbsolute.x - nodesBox.x + nodeExtent[0][0];
1532
- 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];
1533
1564
  const y1 = positionAbsolute.y - nodesBox.y + nodeExtent[0][1];
1534
- 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];
1535
1566
  adjustedNodeExtent = [
1536
1567
  [x1, y1],
1537
1568
  [x2, y2],
1538
1569
  ];
1539
1570
  }
1540
1571
  const { position, positionAbsolute } = calculateNodePosition({
1541
- nodeId: n.id,
1572
+ nodeId: id,
1542
1573
  nextPosition,
1543
1574
  nodeLookup,
1544
1575
  nodeExtent: adjustedNodeExtent,
@@ -1546,11 +1577,10 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1546
1577
  onError,
1547
1578
  });
1548
1579
  // we want to make sure that we only fire a change event when there is a change
1549
- hasChange = hasChange || n.position.x !== position.x || n.position.y !== position.y;
1550
- n.position = position;
1551
- n.internals.positionAbsolute = positionAbsolute;
1552
- return n;
1553
- });
1580
+ hasChange = hasChange || dragItem.position.x !== position.x || dragItem.position.y !== position.y;
1581
+ dragItem.position = position;
1582
+ dragItem.internals.positionAbsolute = positionAbsolute;
1583
+ }
1554
1584
  if (!hasChange) {
1555
1585
  return;
1556
1586
  }
@@ -1564,8 +1594,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1564
1594
  onDrag?.(dragEvent, dragItems, currentNode, currentNodes);
1565
1595
  onNodeDrag?.(dragEvent, currentNode, currentNodes);
1566
1596
  if (!nodeId) {
1567
- const _onSelectionDrag = wrapSelectionDragFunc(onSelectionDrag);
1568
- _onSelectionDrag(dragEvent, currentNode, currentNodes);
1597
+ onSelectionDrag?.(dragEvent, currentNodes);
1569
1598
  }
1570
1599
  }
1571
1600
  }
@@ -1599,7 +1628,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1599
1628
  const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
1600
1629
  lastPos = pointerPos;
1601
1630
  dragItems = getDragItems(nodeLookup, nodesDraggable, pointerPos, nodeId);
1602
- if (dragItems.length > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) {
1631
+ if (dragItems.size > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) {
1603
1632
  const [currentNode, currentNodes] = getEventHandlerParams({
1604
1633
  nodeId,
1605
1634
  dragItems,
@@ -1608,8 +1637,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1608
1637
  onDragStart?.(event.sourceEvent, dragItems, currentNode, currentNodes);
1609
1638
  onNodeDragStart?.(event.sourceEvent, currentNode, currentNodes);
1610
1639
  if (!nodeId) {
1611
- const _onSelectionDragStart = wrapSelectionDragFunc(onSelectionDragStart);
1612
- _onSelectionDragStart(event.sourceEvent, currentNode, currentNodes);
1640
+ onSelectionDragStart?.(event.sourceEvent, currentNodes);
1613
1641
  }
1614
1642
  }
1615
1643
  }
@@ -1653,7 +1681,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1653
1681
  autoPanStarted = false;
1654
1682
  dragStarted = false;
1655
1683
  cancelAnimationFrame(autoPanId);
1656
- if (dragItems.length > 0) {
1684
+ if (dragItems.size > 0) {
1657
1685
  const { nodeLookup, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems();
1658
1686
  updateNodePositions(dragItems, false);
1659
1687
  if (onDragStop || onNodeDragStop || (!nodeId && onSelectionDragStop)) {
@@ -1665,8 +1693,7 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
1665
1693
  onDragStop?.(event.sourceEvent, dragItems, currentNode, currentNodes);
1666
1694
  onNodeDragStop?.(event.sourceEvent, currentNode, currentNodes);
1667
1695
  if (!nodeId) {
1668
- const _onSelectionDragStop = wrapSelectionDragFunc(onSelectionDragStop);
1669
- _onSelectionDragStop(event.sourceEvent, currentNode, currentNodes);
1696
+ onSelectionDragStop?.(event.sourceEvent, currentNodes);
1670
1697
  }
1671
1698
  }
1672
1699
  }
@@ -1945,15 +1972,30 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
1945
1972
  const nextZoom = transform[2] * Math.pow(2, pinchDelta);
1946
1973
  panZoom.scaleTo(nextZoom);
1947
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
+ };
1948
1984
  const panHandler = (event) => {
1949
1985
  const transform = getTransform();
1950
- if (event.sourceEvent.type !== 'mousemove' || !panZoom) {
1986
+ if ((event.sourceEvent.type !== 'mousemove' && event.sourceEvent.type !== 'touchmove') || !panZoom) {
1951
1987
  return;
1952
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;
1953
1995
  const moveScale = getViewScale() * Math.max(transform[2], Math.log(transform[2])) * (inversePan ? -1 : 1);
1954
1996
  const position = {
1955
- x: transform[0] - event.sourceEvent.movementX * moveScale,
1956
- y: transform[1] - event.sourceEvent.movementY * moveScale,
1997
+ x: transform[0] - panDelta[0] * moveScale,
1998
+ y: transform[1] - panDelta[1] * moveScale,
1957
1999
  };
1958
2000
  const extent = [
1959
2001
  [0, 0],
@@ -1966,6 +2008,7 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
1966
2008
  }, extent, translateExtent);
1967
2009
  };
1968
2010
  const zoomAndPanHandler = zoom()
2011
+ .on('start', panStartHandler)
1969
2012
  // @ts-ignore
1970
2013
  .on('zoom', pannable ? panHandler : null)
1971
2014
  // @ts-ignore
@@ -2565,16 +2608,6 @@ const initStartValues = {
2565
2608
  pointerY: 0,
2566
2609
  aspectRatio: 1,
2567
2610
  };
2568
- const initChange = {
2569
- x: 0,
2570
- y: 0,
2571
- width: 0,
2572
- height: 0,
2573
- isXPosChange: false,
2574
- isYPosChange: false,
2575
- isWidthChange: false,
2576
- isHeightChange: false,
2577
- };
2578
2611
  function nodeToParentExtent(node) {
2579
2612
  return [
2580
2613
  [0, 0],
@@ -2593,7 +2626,7 @@ function nodeToChildExtent(child, parent, nodeOrigin) {
2593
2626
  [x + width - originOffsetX, y + height - originOffsetY],
2594
2627
  ];
2595
2628
  }
2596
- function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2629
+ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2597
2630
  const selection = select(domNode);
2598
2631
  function update({ controlPosition, boundaries, keepAspectRatio, onResizeStart, onResize, onResizeEnd, shouldResize, }) {
2599
2632
  let prevValues = { ...initPrevValues };
@@ -2608,128 +2641,127 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2608
2641
  .on('start', (event) => {
2609
2642
  const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin } = getStoreItems();
2610
2643
  node = nodeLookup.get(nodeId);
2611
- if (node) {
2612
- const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2613
- prevValues = {
2614
- width: node.measured?.width ?? 0,
2615
- height: node.measured?.height ?? 0,
2616
- x: node.position.x ?? 0,
2617
- y: node.position.y ?? 0,
2618
- };
2619
- startValues = {
2620
- ...prevValues,
2621
- pointerX: xSnapped,
2622
- pointerY: ySnapped,
2623
- aspectRatio: prevValues.width / prevValues.height,
2624
- };
2625
- parentNode = undefined;
2626
- if (node.extent === 'parent' || node.expandParent) {
2627
- parentNode = nodeLookup.get(node.parentId);
2628
- if (parentNode && node.extent === 'parent') {
2629
- parentExtent = nodeToParentExtent(parentNode);
2630
- }
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);
2631
2665
  }
2632
- // Collect all child nodes to correct their relative positions when top/left changes
2633
- // Determine largest minimal extent the parent node is allowed to resize to
2634
- childNodes = [];
2635
- childExtent = undefined;
2636
- for (const [childId, child] of nodeLookup) {
2637
- if (child.parentId === nodeId) {
2638
- childNodes.push({
2639
- id: childId,
2640
- position: { ...child.position },
2641
- extent: child.extent,
2642
- });
2643
- if (child.extent === 'parent' || child.expandParent) {
2644
- const extent = nodeToChildExtent(child, node, child.origin ?? nodeOrigin);
2645
- if (childExtent) {
2646
- childExtent = [
2647
- [Math.min(extent[0][0], childExtent[0][0]), Math.min(extent[0][1], childExtent[0][1])],
2648
- [Math.max(extent[1][0], childExtent[1][0]), Math.max(extent[1][1], childExtent[1][1])],
2649
- ];
2650
- }
2651
- else {
2652
- childExtent = extent;
2653
- }
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;
2654
2688
  }
2655
2689
  }
2656
2690
  }
2657
- onResizeStart?.(event, { ...prevValues });
2658
2691
  }
2692
+ onResizeStart?.(event, { ...prevValues });
2659
2693
  })
2660
2694
  .on('drag', (event) => {
2661
2695
  const { transform, snapGrid, snapToGrid, nodeOrigin: storeNodeOrigin } = getStoreItems();
2662
2696
  const pointerPosition = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2663
2697
  const childChanges = [];
2664
- if (node) {
2665
- const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues;
2666
- const change = { ...initChange };
2667
- const nodeOrigin = node.origin ?? storeNodeOrigin;
2668
- const { width, height, x, y } = getDimensionsAfterResize(startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio, nodeOrigin, parentExtent, childExtent);
2669
- const isWidthChange = width !== prevWidth;
2670
- const isHeightChange = height !== prevHeight;
2671
- const isXPosChange = x !== prevX && isWidthChange;
2672
- const isYPosChange = y !== prevY && isHeightChange;
2673
- if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] == 1) {
2674
- change.isXPosChange = isXPosChange;
2675
- change.isYPosChange = isYPosChange;
2676
- change.x = isXPosChange ? x : prevX;
2677
- change.y = isYPosChange ? y : prevY;
2678
- prevValues.x = change.x;
2679
- prevValues.y = change.y;
2680
- // Fix expandParent when resizing from top/left
2681
- if (parentNode && node.expandParent) {
2682
- if (change.x < 0) {
2683
- prevValues.x = 0;
2684
- startValues.x = startValues.x - change.x;
2685
- }
2686
- if (change.y < 0) {
2687
- prevValues.y = 0;
2688
- startValues.y = startValues.y - change.y;
2689
- }
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;
2690
2722
  }
2691
- if (childNodes.length > 0) {
2692
- const xChange = x - prevX;
2693
- const yChange = y - prevY;
2694
- for (const childNode of childNodes) {
2695
- childNode.position = {
2696
- x: childNode.position.x - xChange + nodeOrigin[0] * (width - prevWidth),
2697
- y: childNode.position.y - yChange + nodeOrigin[1] * (height - prevHeight),
2698
- };
2699
- childChanges.push(childNode);
2700
- }
2723
+ if (change.y && change.y < 0) {
2724
+ prevValues.y = 0;
2725
+ startValues.y = startValues.y - change.y;
2701
2726
  }
2702
2727
  }
2703
- if (isWidthChange || isHeightChange) {
2704
- change.isWidthChange = isWidthChange;
2705
- change.isHeightChange = isHeightChange;
2706
- change.width = width;
2707
- change.height = height;
2708
- prevValues.width = change.width;
2709
- prevValues.height = change.height;
2710
- }
2711
- if (!change.isXPosChange && !change.isYPosChange && !isWidthChange && !isHeightChange) {
2712
- return;
2713
- }
2714
- const direction = getResizeDirection({
2715
- width: prevValues.width,
2716
- prevWidth,
2717
- height: prevValues.height,
2718
- prevHeight,
2719
- affectsX: controlDirection.affectsX,
2720
- affectsY: controlDirection.affectsY,
2721
- });
2722
- const nextValues = { ...prevValues, direction };
2723
- const callResize = shouldResize?.(event, nextValues);
2724
- if (callResize === false) {
2725
- 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
+ }
2726
2738
  }
2727
- onResize?.(event, nextValues);
2728
- onChange(change, childChanges);
2729
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);
2730
2761
  })
2731
2762
  .on('end', (event) => {
2732
2763
  onResizeEnd?.(event, { ...prevValues });
2764
+ onEnd?.();
2733
2765
  });
2734
2766
  selection.call(dragHandler);
2735
2767
  }
@@ -2742,4 +2774,4 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2742
2774
  };
2743
2775
  }
2744
2776
 
2745
- 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, updateNodeInternals };
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 };