@xyflow/system 0.0.16 → 0.0.18

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 (54) hide show
  1. package/dist/esm/index.js +331 -135
  2. package/dist/esm/index.mjs +331 -135
  3. package/dist/esm/types/edges.d.ts +2 -2
  4. package/dist/esm/types/edges.d.ts.map +1 -1
  5. package/dist/esm/types/general.d.ts +5 -2
  6. package/dist/esm/types/general.d.ts.map +1 -1
  7. package/dist/esm/types/nodes.d.ts +12 -11
  8. package/dist/esm/types/nodes.d.ts.map +1 -1
  9. package/dist/esm/utils/connections.d.ts +3 -3
  10. package/dist/esm/utils/connections.d.ts.map +1 -1
  11. package/dist/esm/utils/dom.d.ts.map +1 -1
  12. package/dist/esm/utils/edges/general.d.ts +2 -2
  13. package/dist/esm/utils/edges/general.d.ts.map +1 -1
  14. package/dist/esm/utils/edges/positions.d.ts.map +1 -1
  15. package/dist/esm/utils/general.d.ts +5 -0
  16. package/dist/esm/utils/general.d.ts.map +1 -1
  17. package/dist/esm/utils/graph.d.ts +6 -6
  18. package/dist/esm/utils/graph.d.ts.map +1 -1
  19. package/dist/esm/xydrag/XYDrag.d.ts +1 -2
  20. package/dist/esm/xydrag/XYDrag.d.ts.map +1 -1
  21. package/dist/esm/xyhandle/XYHandle.d.ts.map +1 -1
  22. package/dist/esm/xyhandle/utils.d.ts +0 -1
  23. package/dist/esm/xyhandle/utils.d.ts.map +1 -1
  24. package/dist/esm/xyresizer/XYResizer.d.ts +8 -2
  25. package/dist/esm/xyresizer/XYResizer.d.ts.map +1 -1
  26. package/dist/esm/xyresizer/utils.d.ts +14 -13
  27. package/dist/esm/xyresizer/utils.d.ts.map +1 -1
  28. package/dist/umd/index.js +1 -1
  29. package/dist/umd/types/edges.d.ts +2 -2
  30. package/dist/umd/types/edges.d.ts.map +1 -1
  31. package/dist/umd/types/general.d.ts +5 -2
  32. package/dist/umd/types/general.d.ts.map +1 -1
  33. package/dist/umd/types/nodes.d.ts +12 -11
  34. package/dist/umd/types/nodes.d.ts.map +1 -1
  35. package/dist/umd/utils/connections.d.ts +3 -3
  36. package/dist/umd/utils/connections.d.ts.map +1 -1
  37. package/dist/umd/utils/dom.d.ts.map +1 -1
  38. package/dist/umd/utils/edges/general.d.ts +2 -2
  39. package/dist/umd/utils/edges/general.d.ts.map +1 -1
  40. package/dist/umd/utils/edges/positions.d.ts.map +1 -1
  41. package/dist/umd/utils/general.d.ts +5 -0
  42. package/dist/umd/utils/general.d.ts.map +1 -1
  43. package/dist/umd/utils/graph.d.ts +6 -6
  44. package/dist/umd/utils/graph.d.ts.map +1 -1
  45. package/dist/umd/xydrag/XYDrag.d.ts +1 -2
  46. package/dist/umd/xydrag/XYDrag.d.ts.map +1 -1
  47. package/dist/umd/xyhandle/XYHandle.d.ts.map +1 -1
  48. package/dist/umd/xyhandle/utils.d.ts +0 -1
  49. package/dist/umd/xyhandle/utils.d.ts.map +1 -1
  50. package/dist/umd/xyresizer/XYResizer.d.ts +8 -2
  51. package/dist/umd/xyresizer/XYResizer.d.ts.map +1 -1
  52. package/dist/umd/xyresizer/utils.d.ts +14 -13
  53. package/dist/umd/xyresizer/utils.d.ts.map +1 -1
  54. package/package.json +3 -3
@@ -173,8 +173,9 @@ const getNodePositionWithOrigin = (node, nodeOrigin = [0, 0]) => {
173
173
  },
174
174
  };
175
175
  }
176
- const offsetX = (node.computed?.width ?? node.width ?? 0) * nodeOrigin[0];
177
- const offsetY = (node.computed?.height ?? node.height ?? 0) * nodeOrigin[1];
176
+ const { width, height } = getNodeDimensions(node);
177
+ const offsetX = width * nodeOrigin[0];
178
+ const offsetY = height * nodeOrigin[1];
178
179
  const position = {
179
180
  x: node.position.x - offsetX,
180
181
  y: node.position.y - offsetY,
@@ -206,8 +207,7 @@ const getNodesBounds = (nodes, params = { nodeOrigin: [0, 0], useRelativePositio
206
207
  const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin);
207
208
  return getBoundsOfBoxes(currBox, rectToBox({
208
209
  ...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'],
209
- width: node.computed?.width ?? node.width ?? 0,
210
- height: node.computed?.height ?? node.height ?? 0,
210
+ ...getNodeDimensions(node),
211
211
  }));
212
212
  }, { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity });
213
213
  return boxToRect(box);
@@ -222,8 +222,8 @@ excludeNonSelectableNodes = false, nodeOrigin = [0, 0]) => {
222
222
  };
223
223
  const visibleNodes = nodes.reduce((res, node) => {
224
224
  const { computed, selectable = true, hidden = false } = node;
225
- const width = computed?.width ?? node.width ?? null;
226
- const height = computed?.height ?? node.height ?? null;
225
+ const width = computed?.width ?? node.width ?? node.initialWidth ?? null;
226
+ const height = computed?.height ?? node.height ?? node.initialHeight ?? null;
227
227
  if ((excludeNonSelectableNodes && !selectable) || hidden) {
228
228
  return res;
229
229
  }
@@ -517,6 +517,16 @@ const isMacOs = () => typeof navigator !== 'undefined' && navigator?.userAgent?.
517
517
  function isCoordinateExtent(extent) {
518
518
  return extent !== undefined && extent !== 'parent';
519
519
  }
520
+ function getNodeDimensions(node) {
521
+ return {
522
+ width: node.computed?.width ?? node.width ?? node.initialWidth ?? 0,
523
+ height: node.computed?.height ?? node.height ?? node.initialHeight ?? 0,
524
+ };
525
+ }
526
+ function nodeHasDimensions(node) {
527
+ return ((node.computed?.width ?? node.width ?? node.initialWidth) !== undefined &&
528
+ (node.computed?.height ?? node.height ?? node.initialHeight) !== undefined);
529
+ }
520
530
 
521
531
  function getPointerPosition(event, { snapGrid = [0, 0], snapToGrid = false, transform }) {
522
532
  const { x, y } = getEventPosition(event);
@@ -539,10 +549,8 @@ function isInputDOMNode(event) {
539
549
  // using composed path for handling shadow dom
540
550
  const target = (event.composedPath?.()?.[0] || event.target);
541
551
  const isInput = inputTags.includes(target?.nodeName) || target?.hasAttribute('contenteditable');
542
- // we want to be able to do a multi selection event if we are in an input field
543
- const isModifierKey = event.ctrlKey || event.metaKey || event.shiftKey;
544
552
  // when an input field is focused we don't want to trigger deletion or movement of nodes
545
- return (isInput && !isModifierKey) || !!target?.closest('.nokey');
553
+ return isInput || !!target?.closest('.nokey');
546
554
  }
547
555
  const isMouseEvent = (event) => 'clientX' in event;
548
556
  const getEventPosition = (event, bounds) => {
@@ -840,8 +848,8 @@ function getPoints({ source, sourcePosition = Position.Bottom, target, targetPos
840
848
  });
841
849
  // opposite handle positions, default case
842
850
  if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
843
- centerX = center.x || defaultCenterX;
844
- centerY = center.y || defaultCenterY;
851
+ centerX = center.x ?? defaultCenterX;
852
+ centerY = center.y ?? defaultCenterY;
845
853
  // --->
846
854
  // |
847
855
  // >---
@@ -985,7 +993,8 @@ function getSmoothStepPath({ sourceX, sourceY, sourcePosition = Position.Bottom,
985
993
  }
986
994
 
987
995
  function isNodeInitialized(node) {
988
- return !!(node?.[internalsSymbol]?.handleBounds || node?.handles?.length) && !!(node?.computed?.width || node?.width);
996
+ return (!!(node?.[internalsSymbol]?.handleBounds || node?.handles?.length) &&
997
+ !!(node?.computed?.width || node?.width || node?.initialWidth));
989
998
  }
990
999
  function getEdgePosition(params) {
991
1000
  const { sourceNode, targetNode } = params;
@@ -1028,8 +1037,8 @@ function toHandleBounds(handles) {
1028
1037
  const source = [];
1029
1038
  const target = [];
1030
1039
  for (const handle of handles) {
1031
- handle.width = handle.width || 1;
1032
- handle.height = handle.height || 1;
1040
+ handle.width = handle.width ?? 1;
1041
+ handle.height = handle.height ?? 1;
1033
1042
  if (handle.type === 'source') {
1034
1043
  source.push(handle);
1035
1044
  }
@@ -1045,8 +1054,7 @@ function toHandleBounds(handles) {
1045
1054
  function getHandlePosition(position, node, handle = null) {
1046
1055
  const x = (handle?.x ?? 0) + (node.computed?.positionAbsolute?.x ?? 0);
1047
1056
  const y = (handle?.y ?? 0) + (node.computed?.positionAbsolute?.y ?? 0);
1048
- const width = handle?.width || (node?.computed?.width ?? node?.width ?? 0);
1049
- const height = handle?.height || (node?.computed?.height ?? node?.height ?? 0);
1057
+ const { width, height } = handle ?? getNodeDimensions(node);
1050
1058
  switch (position) {
1051
1059
  case Position.Top:
1052
1060
  return [x + width / 2, y];
@@ -1182,8 +1190,8 @@ function adoptUserProvidedNodes(nodes, nodeLookup, options = {
1182
1190
  ...n,
1183
1191
  computed: {
1184
1192
  positionAbsolute: n.position,
1185
- width: n.computed?.width || currentStoreNode?.computed?.width,
1186
- height: n.computed?.height || currentStoreNode?.computed?.height,
1193
+ width: n.computed?.width,
1194
+ height: n.computed?.height,
1187
1195
  },
1188
1196
  };
1189
1197
  const z = (isNumeric(n.zIndex) ? n.zIndex : 0) + (n.selected ? selectedNodeZ : 0);
@@ -1280,7 +1288,7 @@ function updateConnectionLookup(connectionLookup, edgeLookup, edges) {
1280
1288
  const targetKey = `${target}-target-${targetHandle}`;
1281
1289
  const prevSource = connectionLookup.get(sourceKey) || new Map();
1282
1290
  const prevTarget = connectionLookup.get(targetKey) || new Map();
1283
- const connection = { source, target, sourceHandle, targetHandle };
1291
+ const connection = { edgeId: edge.id, source, target, sourceHandle, targetHandle };
1284
1292
  edgeLookup.set(edge.id, edge);
1285
1293
  connectionLookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
1286
1294
  connectionLookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
@@ -1361,19 +1369,19 @@ function getEventHandlerParams({ nodeId, dragItems, nodeLookup, }) {
1361
1369
  }
1362
1370
 
1363
1371
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1364
- function XYDrag({ domNode, onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragStop, }) {
1372
+ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragStop, }) {
1365
1373
  let lastPos = { x: null, y: null };
1366
1374
  let autoPanId = 0;
1367
1375
  let dragItems = [];
1368
1376
  let autoPanStarted = false;
1369
1377
  let mousePosition = { x: 0, y: 0 };
1370
- let dragEvent = null;
1371
1378
  let containerBounds = null;
1372
1379
  let dragStarted = false;
1373
- const d3Selection = select(domNode);
1380
+ let d3Selection = null;
1374
1381
  // public functions
1375
1382
  function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }) {
1376
- function updateNodes({ x, y }) {
1383
+ d3Selection = select(domNode);
1384
+ function updateNodes({ x, y }, dragEvent) {
1377
1385
  const { nodeLookup, nodeExtent, snapGrid, snapToGrid, nodeOrigin, onNodeDrag, onSelectionDrag, onError, updateNodePositions, } = getStoreItems();
1378
1386
  lastPos = { x, y };
1379
1387
  let hasChange = false;
@@ -1418,16 +1426,19 @@ function XYDrag({ domNode, onNodeMouseDown, getStoreItems, onDragStart, onDrag,
1418
1426
  if (!hasChange) {
1419
1427
  return;
1420
1428
  }
1421
- updateNodePositions(dragItems, true, true);
1422
- const onNodeOrSelectionDrag = nodeId ? onNodeDrag : wrapSelectionDragFunc(onSelectionDrag);
1423
- if (dragEvent && (onDrag || onNodeOrSelectionDrag)) {
1429
+ updateNodePositions(dragItems, true);
1430
+ if (dragEvent && (onDrag || onNodeDrag || (!nodeId && onSelectionDrag))) {
1424
1431
  const [currentNode, currentNodes] = getEventHandlerParams({
1425
1432
  nodeId,
1426
1433
  dragItems,
1427
1434
  nodeLookup,
1428
1435
  });
1429
1436
  onDrag?.(dragEvent, dragItems, currentNode, currentNodes);
1430
- onNodeOrSelectionDrag?.(dragEvent, currentNode, currentNodes);
1437
+ onNodeDrag?.(dragEvent, currentNode, currentNodes);
1438
+ if (!nodeId) {
1439
+ const _onSelectionDrag = wrapSelectionDragFunc(onSelectionDrag);
1440
+ _onSelectionDrag(dragEvent, currentNode, currentNodes);
1441
+ }
1431
1442
  }
1432
1443
  }
1433
1444
  function autoPan() {
@@ -1440,7 +1451,7 @@ function XYDrag({ domNode, onNodeMouseDown, getStoreItems, onDragStart, onDrag,
1440
1451
  lastPos.x = (lastPos.x ?? 0) - xMovement / transform[2];
1441
1452
  lastPos.y = (lastPos.y ?? 0) - yMovement / transform[2];
1442
1453
  if (panBy({ x: xMovement, y: yMovement })) {
1443
- updateNodes(lastPos);
1454
+ updateNodes(lastPos, null);
1444
1455
  }
1445
1456
  }
1446
1457
  autoPanId = requestAnimationFrame(autoPan);
@@ -1460,15 +1471,18 @@ function XYDrag({ domNode, onNodeMouseDown, getStoreItems, onDragStart, onDrag,
1460
1471
  const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
1461
1472
  lastPos = pointerPos;
1462
1473
  dragItems = getDragItems(nodes, nodesDraggable, pointerPos, nodeId);
1463
- const onNodeOrSelectionDragStart = nodeId ? onNodeDragStart : wrapSelectionDragFunc(onSelectionDragStart);
1464
- if (dragItems && (onDragStart || onNodeOrSelectionDragStart)) {
1474
+ if (dragItems.length > 0 && (onDragStart || onNodeDragStart || (!nodeId && onSelectionDragStart))) {
1465
1475
  const [currentNode, currentNodes] = getEventHandlerParams({
1466
1476
  nodeId,
1467
1477
  dragItems,
1468
1478
  nodeLookup,
1469
1479
  });
1470
1480
  onDragStart?.(event.sourceEvent, dragItems, currentNode, currentNodes);
1471
- onNodeOrSelectionDragStart?.(event.sourceEvent, currentNode, currentNodes);
1481
+ onNodeDragStart?.(event.sourceEvent, currentNode, currentNodes);
1482
+ if (!nodeId) {
1483
+ const _onSelectionDragStart = wrapSelectionDragFunc(onSelectionDragStart);
1484
+ _onSelectionDragStart(event.sourceEvent, currentNode, currentNodes);
1485
+ }
1472
1486
  }
1473
1487
  }
1474
1488
  const d3DragInstance = drag()
@@ -1499,9 +1513,9 @@ function XYDrag({ domNode, onNodeMouseDown, getStoreItems, onDragStart, onDrag,
1499
1513
  }
1500
1514
  // skip events without movement
1501
1515
  if ((lastPos.x !== pointerPos.xSnapped || lastPos.y !== pointerPos.ySnapped) && dragItems && dragStarted) {
1502
- dragEvent = event.sourceEvent;
1516
+ // dragEvent = event.sourceEvent as MouseEvent;
1503
1517
  mousePosition = getEventPosition(event.sourceEvent, containerBounds);
1504
- updateNodes(pointerPos);
1518
+ updateNodes(pointerPos, event.sourceEvent);
1505
1519
  }
1506
1520
  })
1507
1521
  .on('end', (event) => {
@@ -1511,18 +1525,21 @@ function XYDrag({ domNode, onNodeMouseDown, getStoreItems, onDragStart, onDrag,
1511
1525
  autoPanStarted = false;
1512
1526
  dragStarted = false;
1513
1527
  cancelAnimationFrame(autoPanId);
1514
- if (dragItems) {
1528
+ if (dragItems.length > 0) {
1515
1529
  const { nodeLookup, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems();
1516
- const onNodeOrSelectionDragStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop);
1517
- updateNodePositions(dragItems, false, false);
1518
- if (onDragStop || onNodeOrSelectionDragStop) {
1530
+ updateNodePositions(dragItems, false);
1531
+ if (onDragStop || onNodeDragStop || (!nodeId && onSelectionDragStop)) {
1519
1532
  const [currentNode, currentNodes] = getEventHandlerParams({
1520
1533
  nodeId,
1521
1534
  dragItems,
1522
1535
  nodeLookup,
1523
1536
  });
1524
1537
  onDragStop?.(event.sourceEvent, dragItems, currentNode, currentNodes);
1525
- onNodeOrSelectionDragStop?.(event.sourceEvent, currentNode, currentNodes);
1538
+ onNodeDragStop?.(event.sourceEvent, currentNode, currentNodes);
1539
+ if (!nodeId) {
1540
+ const _onSelectionDragStop = wrapSelectionDragFunc(onSelectionDragStop);
1541
+ _onSelectionDragStop(event.sourceEvent, currentNode, currentNodes);
1542
+ }
1526
1543
  }
1527
1544
  }
1528
1545
  })
@@ -1536,7 +1553,7 @@ function XYDrag({ domNode, onNodeMouseDown, getStoreItems, onDragStart, onDrag,
1536
1553
  d3Selection.call(d3DragInstance);
1537
1554
  }
1538
1555
  function destroy() {
1539
- d3Selection.on('.drag', null);
1556
+ d3Selection?.on('.drag', null);
1540
1557
  }
1541
1558
  return {
1542
1559
  update,
@@ -1563,7 +1580,7 @@ function getHandles(node, handleBounds, type, currentHandle) {
1563
1580
  function getClosestHandle(pos, connectionRadius, handles) {
1564
1581
  let closestHandles = [];
1565
1582
  let minDistance = Infinity;
1566
- handles.forEach((handle) => {
1583
+ for (const handle of handles) {
1567
1584
  const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2));
1568
1585
  if (distance <= connectionRadius) {
1569
1586
  if (distance < minDistance) {
@@ -1575,7 +1592,7 @@ function getClosestHandle(pos, connectionRadius, handles) {
1575
1592
  }
1576
1593
  minDistance = distance;
1577
1594
  }
1578
- });
1595
+ }
1579
1596
  if (!closestHandles.length) {
1580
1597
  return null;
1581
1598
  }
@@ -1611,9 +1628,6 @@ function getHandleType(edgeUpdaterType, handleDomNode) {
1611
1628
  }
1612
1629
  return null;
1613
1630
  }
1614
- function resetRecentHandle(handleDomNode, lib) {
1615
- handleDomNode?.classList.remove('valid', 'connecting', `${lib}-flow__handle-valid`, `${lib}-flow__handle-connecting`);
1616
- }
1617
1631
  function getConnectionStatus(isInsideConnectionRadius, isHandleValid) {
1618
1632
  let connectionStatus = null;
1619
1633
  if (isHandleValid) {
@@ -1639,7 +1653,6 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1639
1653
  if (!containerBounds || !handleType) {
1640
1654
  return;
1641
1655
  }
1642
- let prevActiveHandle;
1643
1656
  let connectionPosition = getEventPosition(event, containerBounds);
1644
1657
  let autoPanStarted = false;
1645
1658
  let connection = null;
@@ -1707,16 +1720,6 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1707
1720
  connectionStatus: getConnectionStatus(!!closestHandle, isValid),
1708
1721
  connectionEndHandle: result.endHandle,
1709
1722
  });
1710
- if (!closestHandle && !isValid && !handleDomNode) {
1711
- return resetRecentHandle(prevActiveHandle, lib);
1712
- }
1713
- if (connection?.source !== connection?.target && handleDomNode) {
1714
- resetRecentHandle(prevActiveHandle, lib);
1715
- prevActiveHandle = handleDomNode;
1716
- handleDomNode.classList.add('connecting', `${lib}-flow__handle-connecting`);
1717
- handleDomNode.classList.toggle('valid', isValid);
1718
- handleDomNode.classList.toggle(`${lib}-flow__handle-valid`, isValid);
1719
- }
1720
1723
  }
1721
1724
  function onPointerUp(event) {
1722
1725
  if ((closestHandle || handleDomNode) && connection && isValid) {
@@ -1728,7 +1731,6 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
1728
1731
  if (edgeUpdaterType) {
1729
1732
  onEdgeUpdateEnd?.(event);
1730
1733
  }
1731
- resetRecentHandle(prevActiveHandle, lib);
1732
1734
  cancelConnection();
1733
1735
  cancelAnimationFrame(autoPanId);
1734
1736
  autoPanStarted = false;
@@ -2240,65 +2242,183 @@ function getControlDirection(controlPosition) {
2240
2242
  affectsY,
2241
2243
  };
2242
2244
  }
2245
+ function getLowerExtentClamp(lowerExtent, lowerBound) {
2246
+ return Math.max(0, lowerBound - lowerExtent);
2247
+ }
2248
+ function getUpperExtentClamp(upperExtent, upperBound) {
2249
+ return Math.max(0, upperExtent - upperBound);
2250
+ }
2251
+ function getSizeClamp(size, minSize, maxSize) {
2252
+ return Math.max(0, minSize - size, size - maxSize);
2253
+ }
2254
+ function xor(a, b) {
2255
+ return a ? !b : b;
2256
+ }
2243
2257
  /**
2244
- * Calculates new width & height of node after resize based on pointer position
2258
+ * Calculates new width & height and x & y of node after resize based on pointer position
2259
+ * @description - Buckle up, this is a chunky one... If you want to determine the new dimensions of a node after a resize,
2260
+ * you have to account for all possible restrictions: min/max width/height of the node, the maximum extent the node is allowed
2261
+ * to move in (in this case: resize into) determined by the parent node, the minimal extent determined by child nodes
2262
+ * with expandParent or extent: 'parent' set and oh yeah, these things also have to work with keepAspectRatio!
2263
+ * The way this is done is by determining how much each of these restricting actually restricts the resize and then applying the
2264
+ * strongest restriction. Because the resize affects x, y and width, height and width, height of a opposing side with keepAspectRatio,
2265
+ * the resize amount is always kept in distX & distY amount (the distance in mouse movement)
2266
+ * Instead of clamping each value, we first calculate the biggest 'clamp' (for the lack of a better name) and then apply it to all values.
2267
+ * To complicate things nodeOrigin has to be taken into account as well. This is done by offsetting the nodes as if their origin is [0, 0],
2268
+ * then calculating the restrictions as usual
2245
2269
  * @param startValues - starting values of resize
2246
2270
  * @param controlDirection - dimensions affected by the resize
2247
2271
  * @param pointerPosition - the current pointer position corrected for snapping
2248
2272
  * @param boundaries - minimum and maximum dimensions of the node
2249
2273
  * @param keepAspectRatio - prevent changes of asprect ratio
2250
- * @returns width: new width of node, height: new height of node
2274
+ * @returns x, y, width and height of the node after resize
2251
2275
  */
2252
- function getDimensionsAfterResize(startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio) {
2253
- const { isHorizontal, isVertical, affectsX, affectsY } = controlDirection;
2276
+ function getDimensionsAfterResize(startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio, nodeOrigin, extent, childExtent) {
2277
+ let { affectsX, affectsY } = controlDirection;
2278
+ const { isHorizontal, isVertical } = controlDirection;
2279
+ const isDiagonal = isHorizontal && isVertical;
2254
2280
  const { xSnapped, ySnapped } = pointerPosition;
2255
2281
  const { minWidth, maxWidth, minHeight, maxHeight } = boundaries;
2256
- const { pointerX: startX, pointerY: startY, width: startWidth, height: startHeight, aspectRatio } = startValues;
2257
- const distX = Math.floor(isHorizontal ? xSnapped - startX : 0);
2258
- const distY = Math.floor(isVertical ? ySnapped - startY : 0);
2259
- let width = clamp(startWidth + (affectsX ? -distX : distX), minWidth, maxWidth);
2260
- let height = clamp(startHeight + (affectsY ? -distY : distY), minHeight, maxHeight);
2282
+ const { x: startX, y: startY, width: startWidth, height: startHeight, aspectRatio } = startValues;
2283
+ let distX = Math.floor(isHorizontal ? xSnapped - startValues.pointerX : 0);
2284
+ let distY = Math.floor(isVertical ? ySnapped - startValues.pointerY : 0);
2285
+ const newWidth = startWidth + (affectsX ? -distX : distX);
2286
+ const newHeight = startHeight + (affectsY ? -distY : distY);
2287
+ const originOffsetX = -nodeOrigin[0] * startWidth;
2288
+ const originOffsetY = -nodeOrigin[1] * startHeight;
2289
+ // Check if maxWidth, minWWidth, maxHeight, minHeight are restricting the resize
2290
+ let clampX = getSizeClamp(newWidth, minWidth, maxWidth);
2291
+ let clampY = getSizeClamp(newHeight, minHeight, maxHeight);
2292
+ // Check if extent is restricting the resize
2293
+ if (extent) {
2294
+ let xExtentClamp = 0;
2295
+ let yExtentClamp = 0;
2296
+ if (affectsX && distX < 0) {
2297
+ xExtentClamp = getLowerExtentClamp(startX + distX + originOffsetX, extent[0][0]);
2298
+ }
2299
+ else if (!affectsX && distX > 0) {
2300
+ xExtentClamp = getUpperExtentClamp(startX + newWidth + originOffsetX, extent[1][0]);
2301
+ }
2302
+ if (affectsY && distY < 0) {
2303
+ yExtentClamp = getLowerExtentClamp(startY + distY + originOffsetY, extent[0][1]);
2304
+ }
2305
+ else if (!affectsY && distY > 0) {
2306
+ yExtentClamp = getUpperExtentClamp(startY + newHeight + originOffsetY, extent[1][1]);
2307
+ }
2308
+ clampX = Math.max(clampX, xExtentClamp);
2309
+ clampY = Math.max(clampY, yExtentClamp);
2310
+ }
2311
+ // Check if the child extent is restricting the resize
2312
+ if (childExtent) {
2313
+ let xExtentClamp = 0;
2314
+ let yExtentClamp = 0;
2315
+ if (affectsX && distX > 0) {
2316
+ xExtentClamp = getUpperExtentClamp(startX + distX, childExtent[0][0]);
2317
+ }
2318
+ else if (!affectsX && distX < 0) {
2319
+ xExtentClamp = getLowerExtentClamp(startX + newWidth, childExtent[1][0]);
2320
+ }
2321
+ if (affectsY && distY > 0) {
2322
+ yExtentClamp = getUpperExtentClamp(startY + distY, childExtent[0][1]);
2323
+ }
2324
+ else if (!affectsY && distY < 0) {
2325
+ yExtentClamp = getLowerExtentClamp(startY + newHeight, childExtent[1][1]);
2326
+ }
2327
+ clampX = Math.max(clampX, xExtentClamp);
2328
+ clampY = Math.max(clampY, yExtentClamp);
2329
+ }
2330
+ // Check if the aspect ratio resizing of the other side is restricting the resize
2261
2331
  if (keepAspectRatio) {
2262
- const nextAspectRatio = width / height;
2263
- const isDiagonal = isHorizontal && isVertical;
2264
- const isOnlyHorizontal = isHorizontal && !isVertical;
2265
- const isOnlyVertical = isVertical && !isHorizontal;
2266
- width = (nextAspectRatio <= aspectRatio && isDiagonal) || isOnlyVertical ? height * aspectRatio : width;
2267
- height = (nextAspectRatio > aspectRatio && isDiagonal) || isOnlyHorizontal ? width / aspectRatio : height;
2268
- if (width >= maxWidth) {
2269
- width = maxWidth;
2270
- height = maxWidth / aspectRatio;
2332
+ if (isHorizontal) {
2333
+ // Check if the max dimensions might be restricting the resize
2334
+ const aspectHeightClamp = getSizeClamp(newWidth / aspectRatio, minHeight, maxHeight) * aspectRatio;
2335
+ clampX = Math.max(clampX, aspectHeightClamp);
2336
+ // Check if the extent is restricting the resize
2337
+ if (extent) {
2338
+ let aspectExtentClamp = 0;
2339
+ if ((!affectsX && !affectsY) || (affectsX && !affectsY && isDiagonal)) {
2340
+ aspectExtentClamp =
2341
+ getUpperExtentClamp(startY + originOffsetY + newWidth / aspectRatio, extent[1][1]) * aspectRatio;
2342
+ }
2343
+ else {
2344
+ aspectExtentClamp =
2345
+ getLowerExtentClamp(startY + originOffsetY + (affectsX ? distX : -distX) / aspectRatio, extent[0][1]) *
2346
+ aspectRatio;
2347
+ }
2348
+ clampX = Math.max(clampX, aspectExtentClamp);
2349
+ }
2350
+ // Check if the child extent is restricting the resize
2351
+ if (childExtent) {
2352
+ let aspectExtentClamp = 0;
2353
+ if ((!affectsX && !affectsY) || (affectsX && !affectsY && isDiagonal)) {
2354
+ aspectExtentClamp = getLowerExtentClamp(startY + newWidth / aspectRatio, childExtent[1][1]) * aspectRatio;
2355
+ }
2356
+ else {
2357
+ aspectExtentClamp =
2358
+ getUpperExtentClamp(startY + (affectsX ? distX : -distX) / aspectRatio, childExtent[0][1]) * aspectRatio;
2359
+ }
2360
+ clampX = Math.max(clampX, aspectExtentClamp);
2361
+ }
2271
2362
  }
2272
- else if (width <= minWidth) {
2273
- width = minWidth;
2274
- height = minWidth / aspectRatio;
2363
+ // Do the same thing for vertical resizing
2364
+ if (isVertical) {
2365
+ const aspectWidthClamp = getSizeClamp(newHeight * aspectRatio, minWidth, maxWidth) / aspectRatio;
2366
+ clampY = Math.max(clampY, aspectWidthClamp);
2367
+ if (extent) {
2368
+ let aspectExtentClamp = 0;
2369
+ if ((!affectsX && !affectsY) || (affectsY && !affectsX && isDiagonal)) {
2370
+ aspectExtentClamp =
2371
+ getUpperExtentClamp(startX + newHeight * aspectRatio + originOffsetX, extent[1][0]) / aspectRatio;
2372
+ }
2373
+ else {
2374
+ aspectExtentClamp =
2375
+ getLowerExtentClamp(startX + (affectsY ? distY : -distY) * aspectRatio + originOffsetX, extent[0][0]) /
2376
+ aspectRatio;
2377
+ }
2378
+ clampY = Math.max(clampY, aspectExtentClamp);
2379
+ }
2380
+ if (childExtent) {
2381
+ let aspectExtentClamp = 0;
2382
+ if ((!affectsX && !affectsY) || (affectsY && !affectsX && isDiagonal)) {
2383
+ aspectExtentClamp = getLowerExtentClamp(startX + newHeight * aspectRatio, childExtent[1][0]) / aspectRatio;
2384
+ }
2385
+ else {
2386
+ aspectExtentClamp =
2387
+ getUpperExtentClamp(startX + (affectsY ? distY : -distY) * aspectRatio, childExtent[0][0]) / aspectRatio;
2388
+ }
2389
+ clampY = Math.max(clampY, aspectExtentClamp);
2390
+ }
2275
2391
  }
2276
- if (height >= maxHeight) {
2277
- height = maxHeight;
2278
- width = maxHeight * aspectRatio;
2392
+ }
2393
+ distY = distY + (distY < 0 ? clampY : -clampY);
2394
+ distX = distX + (distX < 0 ? clampX : -clampX);
2395
+ if (keepAspectRatio) {
2396
+ if (isDiagonal) {
2397
+ if (newWidth > newHeight * aspectRatio) {
2398
+ distY = (xor(affectsX, affectsY) ? -distX : distX) / aspectRatio;
2399
+ }
2400
+ else {
2401
+ distX = (xor(affectsX, affectsY) ? -distY : distY) * aspectRatio;
2402
+ }
2279
2403
  }
2280
- else if (height <= minHeight) {
2281
- height = minHeight;
2282
- width = minHeight * aspectRatio;
2404
+ else {
2405
+ if (isHorizontal) {
2406
+ distY = distX / aspectRatio;
2407
+ affectsY = affectsX;
2408
+ }
2409
+ else {
2410
+ distX = distY * aspectRatio;
2411
+ affectsX = affectsY;
2412
+ }
2283
2413
  }
2284
2414
  }
2415
+ const x = affectsX ? startX + distX : startX;
2416
+ const y = affectsY ? startY + distY : startY;
2285
2417
  return {
2286
- width,
2287
- height,
2288
- };
2289
- }
2290
- /**
2291
- * Determines new x & y position of node after resize based on new width & height
2292
- * @param startValues - starting values of resize
2293
- * @param controlDirection - dimensions affected by the resize
2294
- * @param width - new width of node
2295
- * @param height - new height of node
2296
- * @returns x: new x position of node, y: new y position of node
2297
- */
2298
- function getPositionAfterResize(startValues, controlDirection, width, height) {
2299
- return {
2300
- x: controlDirection.affectsX ? startValues.x - (width - startValues.width) : startValues.x,
2301
- y: controlDirection.affectsY ? startValues.y - (height - startValues.height) : startValues.y,
2418
+ width: startWidth + (affectsX ? -distX : distX),
2419
+ height: startHeight + (affectsY ? -distY : distY),
2420
+ x: nodeOrigin[0] * distX * (!affectsX ? 1 : -1) + x,
2421
+ y: nodeOrigin[1] * distY * (!affectsY ? 1 : -1) + y,
2302
2422
  };
2303
2423
  }
2304
2424
 
@@ -2319,53 +2439,129 @@ const initChange = {
2319
2439
  isWidthChange: false,
2320
2440
  isHeightChange: false,
2321
2441
  };
2442
+ function nodeToParentExtent(node) {
2443
+ return [
2444
+ [0, 0],
2445
+ [node.computed.width, node.computed.height],
2446
+ ];
2447
+ }
2448
+ function nodeToChildExtent(child, parent, nodeOrigin) {
2449
+ const x = parent.position.x + child.position.x;
2450
+ const y = parent.position.y + child.position.y;
2451
+ const width = child.computed.width ?? 0;
2452
+ const height = child.computed.height ?? 0;
2453
+ const originOffsetX = nodeOrigin[0] * width;
2454
+ const originOffsetY = nodeOrigin[1] * height;
2455
+ return [
2456
+ [x - originOffsetX, y - originOffsetY],
2457
+ [x + width - originOffsetX, y + height - originOffsetY],
2458
+ ];
2459
+ }
2322
2460
  function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2323
2461
  const selection = select(domNode);
2324
2462
  function update({ controlPosition, boundaries, keepAspectRatio, onResizeStart, onResize, onResizeEnd, shouldResize, }) {
2325
2463
  let prevValues = { ...initPrevValues };
2326
2464
  let startValues = { ...initStartValues };
2327
2465
  const controlDirection = getControlDirection(controlPosition);
2466
+ let node = undefined;
2467
+ let childNodes = [];
2468
+ let parentNode = undefined; // Needed to fix expandParent
2469
+ let parentExtent = undefined;
2470
+ let childExtent = undefined;
2328
2471
  const dragHandler = drag()
2329
2472
  .on('start', (event) => {
2330
- const { nodeLookup, transform, snapGrid, snapToGrid } = getStoreItems();
2331
- const node = nodeLookup.get(nodeId);
2332
- const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2333
- prevValues = {
2334
- width: node?.computed?.width ?? 0,
2335
- height: node?.computed?.height ?? 0,
2336
- x: node?.position.x ?? 0,
2337
- y: node?.position.y ?? 0,
2338
- };
2339
- startValues = {
2340
- ...prevValues,
2341
- pointerX: xSnapped,
2342
- pointerY: ySnapped,
2343
- aspectRatio: prevValues.width / prevValues.height,
2344
- };
2345
- onResizeStart?.(event, { ...prevValues });
2473
+ const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin } = getStoreItems();
2474
+ node = nodeLookup.get(nodeId);
2475
+ if (node) {
2476
+ const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2477
+ prevValues = {
2478
+ width: node.computed?.width ?? 0,
2479
+ height: node.computed?.height ?? 0,
2480
+ x: node.position.x ?? 0,
2481
+ y: node.position.y ?? 0,
2482
+ };
2483
+ startValues = {
2484
+ ...prevValues,
2485
+ pointerX: xSnapped,
2486
+ pointerY: ySnapped,
2487
+ aspectRatio: prevValues.width / prevValues.height,
2488
+ };
2489
+ parentNode = undefined;
2490
+ if (node.extent === 'parent' || node.expandParent) {
2491
+ parentNode = nodeLookup.get(node.parentNode);
2492
+ if (parentNode && node.extent === 'parent') {
2493
+ parentExtent = nodeToParentExtent(parentNode);
2494
+ }
2495
+ }
2496
+ // Collect all child nodes to correct their relative positions when top/left changes
2497
+ // Determine largest minimal extent the parent node is allowed to resize to
2498
+ childNodes = [];
2499
+ childExtent = undefined;
2500
+ for (const [childId, child] of nodeLookup) {
2501
+ if (child.parentNode === nodeId) {
2502
+ childNodes.push({
2503
+ id: childId,
2504
+ position: { ...child.position },
2505
+ extent: child.extent,
2506
+ });
2507
+ if (child.extent === 'parent' || child.expandParent) {
2508
+ const extent = nodeToChildExtent(child, node, child.origin ?? nodeOrigin);
2509
+ if (childExtent) {
2510
+ childExtent = [
2511
+ [Math.min(extent[0][0], childExtent[0][0]), Math.min(extent[0][1], childExtent[0][1])],
2512
+ [Math.max(extent[1][0], childExtent[1][0]), Math.max(extent[1][1], childExtent[1][1])],
2513
+ ];
2514
+ }
2515
+ else {
2516
+ childExtent = extent;
2517
+ }
2518
+ }
2519
+ }
2520
+ }
2521
+ onResizeStart?.(event, { ...prevValues });
2522
+ }
2346
2523
  })
2347
2524
  .on('drag', (event) => {
2348
- const { nodeLookup, transform, snapGrid, snapToGrid } = getStoreItems();
2525
+ const { transform, snapGrid, snapToGrid, nodeOrigin: storeNodeOrigin } = getStoreItems();
2349
2526
  const pointerPosition = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
2350
- const node = nodeLookup.get(nodeId);
2527
+ const childChanges = [];
2351
2528
  if (node) {
2352
- const change = { ...initChange };
2353
2529
  const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues;
2354
- const { width, height } = getDimensionsAfterResize(startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio);
2530
+ const change = { ...initChange };
2531
+ const nodeOrigin = node.origin ?? storeNodeOrigin;
2532
+ const { width, height, x, y } = getDimensionsAfterResize(startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio, nodeOrigin, parentExtent, childExtent);
2355
2533
  const isWidthChange = width !== prevWidth;
2356
2534
  const isHeightChange = height !== prevHeight;
2357
- if (controlDirection.affectsX || controlDirection.affectsY) {
2358
- const { x, y } = getPositionAfterResize(startValues, controlDirection, width, height);
2359
- // only transform the node if the width or height changes
2360
- const isXPosChange = x !== prevX && isWidthChange;
2361
- const isYPosChange = y !== prevY && isHeightChange;
2362
- if (isXPosChange || isYPosChange) {
2363
- change.isXPosChange = isXPosChange;
2364
- change.isYPosChange = isYPosChange;
2365
- change.x = isXPosChange ? x : prevX;
2366
- change.y = isYPosChange ? y : prevY;
2367
- prevValues.x = change.x;
2368
- prevValues.y = change.y;
2535
+ const isXPosChange = x !== prevX && isWidthChange;
2536
+ const isYPosChange = y !== prevY && isHeightChange;
2537
+ if (isXPosChange || isYPosChange || nodeOrigin[0] === 1 || nodeOrigin[1] == 1) {
2538
+ change.isXPosChange = isXPosChange;
2539
+ change.isYPosChange = isYPosChange;
2540
+ change.x = isXPosChange ? x : prevX;
2541
+ change.y = isYPosChange ? y : prevY;
2542
+ prevValues.x = change.x;
2543
+ prevValues.y = change.y;
2544
+ // Fix expandParent when resizing from top/left
2545
+ if (parentNode && node.expandParent) {
2546
+ if (change.x < 0) {
2547
+ prevValues.x = 0;
2548
+ startValues.x = startValues.x - change.x;
2549
+ }
2550
+ if (change.y < 0) {
2551
+ prevValues.y = 0;
2552
+ startValues.y = startValues.y - change.y;
2553
+ }
2554
+ }
2555
+ if (childNodes.length > 0) {
2556
+ const xChange = x - prevX;
2557
+ const yChange = y - prevY;
2558
+ for (const childNode of childNodes) {
2559
+ childNode.position = {
2560
+ x: childNode.position.x - xChange + nodeOrigin[0] * (width - prevWidth),
2561
+ y: childNode.position.y - yChange + nodeOrigin[1] * (height - prevHeight),
2562
+ };
2563
+ childChanges.push(childNode);
2564
+ }
2369
2565
  }
2370
2566
  }
2371
2567
  if (isWidthChange || isHeightChange) {
@@ -2373,8 +2569,8 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2373
2569
  change.isHeightChange = isHeightChange;
2374
2570
  change.width = width;
2375
2571
  change.height = height;
2376
- prevValues.width = width;
2377
- prevValues.height = height;
2572
+ prevValues.width = change.width;
2573
+ prevValues.height = change.height;
2378
2574
  }
2379
2575
  if (!change.isXPosChange && !change.isYPosChange && !isWidthChange && !isHeightChange) {
2380
2576
  return;
@@ -2393,7 +2589,7 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2393
2589
  return;
2394
2590
  }
2395
2591
  onResize?.(event, nextValues);
2396
- onChange(change);
2592
+ onChange(change, childChanges);
2397
2593
  }
2398
2594
  })
2399
2595
  .on('end', (event) => {
@@ -2410,4 +2606,4 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange }) {
2410
2606
  };
2411
2607
  }
2412
2608
 
2413
- export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, ResizeControlVariant, SelectionMode, XYDrag, XYHandle, XYMinimap, XYPanZoom, XYResizer, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSITIONS, addEdge, adoptUserProvidedNodes, 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, getMarkerId, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesInside, getOutgoers, getOverlappingArea, getPointerPosition, getPositionWithOrigin, getSmoothStepPath, getStraightPath, getViewportForBounds, handleConnectionChange, infiniteExtent, internalsSymbol, isCoordinateExtent, isEdgeBase, isEdgeVisible, isInputDOMNode, isMacOs, isMouseEvent, isNodeBase, isNumeric, isRectObject, nodeToBox, nodeToRect, panBy, pointToRendererPoint, rectToBox, rendererPointToPoint, snapPosition, updateAbsolutePositions, updateConnectionLookup, updateEdge, updateNodeDimensions };
2609
+ export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, ResizeControlVariant, SelectionMode, XYDrag, XYHandle, XYMinimap, XYPanZoom, XYResizer, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSITIONS, addEdge, adoptUserProvidedNodes, 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, getMarkerId, getNodeDimensions, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesInside, getOutgoers, getOverlappingArea, getPointerPosition, getPositionWithOrigin, getSmoothStepPath, getStraightPath, getViewportForBounds, handleConnectionChange, infiniteExtent, internalsSymbol, isCoordinateExtent, isEdgeBase, isEdgeVisible, isInputDOMNode, isMacOs, isMouseEvent, isNodeBase, isNumeric, isRectObject, nodeHasDimensions, nodeToBox, nodeToRect, panBy, pointToRendererPoint, rectToBox, rendererPointToPoint, snapPosition, updateAbsolutePositions, updateConnectionLookup, updateEdge, updateNodeDimensions };