@xyflow/react 12.0.0-next.13 → 12.0.0-next.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/base.css +2 -0
  2. package/dist/esm/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -1
  3. package/dist/esm/components/NodeWrapper/index.d.ts.map +1 -1
  4. package/dist/esm/components/NodeWrapper/useNodeObservation.d.ts +9 -0
  5. package/dist/esm/components/NodeWrapper/useNodeObservation.d.ts.map +1 -0
  6. package/dist/esm/components/NodeWrapper/useNodeObserver.d.ts +15 -0
  7. package/dist/esm/components/NodeWrapper/useNodeObserver.d.ts.map +1 -0
  8. package/dist/esm/components/NodesSelection/index.d.ts.map +1 -1
  9. package/dist/esm/components/ReactFlowProvider/index.d.ts +4 -3
  10. package/dist/esm/components/ReactFlowProvider/index.d.ts.map +1 -1
  11. package/dist/esm/components/SelectionListener/index.d.ts.map +1 -1
  12. package/dist/esm/container/NodeRenderer/useResizeObserver.d.ts.map +1 -1
  13. package/dist/esm/container/Pane/index.d.ts.map +1 -1
  14. package/dist/esm/contexts/RFStoreContext.d.ts +2 -2
  15. package/dist/esm/contexts/RFStoreContext.d.ts.map +1 -1
  16. package/dist/esm/contexts/StoreContext.d.ts +5 -0
  17. package/dist/esm/contexts/StoreContext.d.ts.map +1 -0
  18. package/dist/esm/hooks/useReactFlow.d.ts.map +1 -1
  19. package/dist/esm/hooks/useStore.d.ts +0 -1
  20. package/dist/esm/hooks/useStore.d.ts.map +1 -1
  21. package/dist/esm/hooks/useUpdateNodeInternals.d.ts.map +1 -1
  22. package/dist/esm/index.d.ts +1 -1
  23. package/dist/esm/index.d.ts.map +1 -1
  24. package/dist/esm/index.js +217 -167
  25. package/dist/esm/index.mjs +217 -167
  26. package/dist/esm/store/index.d.ts +2 -2
  27. package/dist/esm/store/index.d.ts.map +1 -1
  28. package/dist/esm/store/initialState.d.ts.map +1 -1
  29. package/dist/esm/types/nodes.d.ts +6 -0
  30. package/dist/esm/types/nodes.d.ts.map +1 -1
  31. package/dist/esm/types/store.d.ts +3 -2
  32. package/dist/esm/types/store.d.ts.map +1 -1
  33. package/dist/style.css +2 -0
  34. package/dist/umd/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -1
  35. package/dist/umd/components/NodeWrapper/index.d.ts.map +1 -1
  36. package/dist/umd/components/NodeWrapper/useNodeObserver.d.ts +15 -0
  37. package/dist/umd/components/NodeWrapper/useNodeObserver.d.ts.map +1 -0
  38. package/dist/umd/components/NodesSelection/index.d.ts.map +1 -1
  39. package/dist/umd/components/ReactFlowProvider/index.d.ts +4 -3
  40. package/dist/umd/components/ReactFlowProvider/index.d.ts.map +1 -1
  41. package/dist/umd/components/SelectionListener/index.d.ts.map +1 -1
  42. package/dist/umd/container/NodeRenderer/useResizeObserver.d.ts.map +1 -1
  43. package/dist/umd/container/Pane/index.d.ts.map +1 -1
  44. package/dist/umd/contexts/StoreContext.d.ts +5 -0
  45. package/dist/umd/contexts/StoreContext.d.ts.map +1 -0
  46. package/dist/umd/hooks/useReactFlow.d.ts.map +1 -1
  47. package/dist/umd/hooks/useStore.d.ts +0 -1
  48. package/dist/umd/hooks/useStore.d.ts.map +1 -1
  49. package/dist/umd/hooks/useUpdateNodeInternals.d.ts.map +1 -1
  50. package/dist/umd/index.d.ts +1 -1
  51. package/dist/umd/index.d.ts.map +1 -1
  52. package/dist/umd/index.js +2 -2
  53. package/dist/umd/store/index.d.ts +2 -2
  54. package/dist/umd/store/index.d.ts.map +1 -1
  55. package/dist/umd/store/initialState.d.ts.map +1 -1
  56. package/dist/umd/types/nodes.d.ts +6 -0
  57. package/dist/umd/types/nodes.d.ts.map +1 -1
  58. package/dist/umd/types/store.d.ts +3 -2
  59. package/dist/umd/types/store.d.ts.map +1 -1
  60. package/package.json +2 -2
package/dist/esm/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
3
  import cc from 'classcat';
4
- import { errorMessages, infiniteExtent, isInputDOMNode, fitView, getViewportForBounds, pointToRendererPoint, rendererPointToPoint, isNodeBase, isEdgeBase, getElementsToRemove, nodeToRect, isRectObject, getOverlappingArea, getDimensions, XYPanZoom, PanOnScrollMode, SelectionMode, getEventPosition, getNodesInside, XYDrag, snapPosition, calculateNodePosition, Position, ConnectionMode, isMouseEvent, XYHandle, getHostForElement, addEdge, getNodesBounds, clampPosition, getNodeDimensions, nodeHasDimensions, getPositionWithOrigin, elementSelectionKeys, isEdgeVisible, MarkerType, createMarkerIds, isNumeric, getBezierEdgeCenter, getSmoothStepPath, getStraightPath, getBezierPath, getEdgePosition, getElevatedEdgeZIndex, getMarkerId, ConnectionLineType, updateConnectionLookup, adoptUserNodes, devWarn, updateNodeDimensions, updateAbsolutePositions, handleParentExpand, panBy, isMacOs, areConnectionMapsEqual, handleConnectionChange, shallowNodeData, getNodePositionWithOrigin, XYMinimap, getBoundsOfRects, getInternalNodesBounds, ResizeControlVariant, XYResizer, XY_RESIZER_LINE_POSITIONS, XY_RESIZER_HANDLE_POSITIONS, getNodeToolbarTransform } from '@xyflow/system';
4
+ import { errorMessages, infiniteExtent, isInputDOMNode, fitView, getViewportForBounds, pointToRendererPoint, rendererPointToPoint, isNodeBase, isEdgeBase, getElementsToRemove, evaluateAbsolutePosition, nodeToRect, isRectObject, getOverlappingArea, getDimensions, XYPanZoom, PanOnScrollMode, SelectionMode, getEventPosition, getNodesInside, XYDrag, snapPosition, calculateNodePosition, Position, ConnectionMode, isMouseEvent, XYHandle, getHostForElement, addEdge, getInternalNodesBounds, nodeHasDimensions, getNodeDimensions, clampPosition, getPositionWithOrigin, elementSelectionKeys, isEdgeVisible, MarkerType, createMarkerIds, isNumeric, getBezierEdgeCenter, getSmoothStepPath, getStraightPath, getBezierPath, getEdgePosition, getElevatedEdgeZIndex, getMarkerId, ConnectionLineType, updateConnectionLookup, adoptUserNodes, devWarn, updateNodeInternals, updateAbsolutePositions, handleExpandParent, panBy, isMacOs, areConnectionMapsEqual, handleConnectionChange, shallowNodeData, getNodePositionWithOrigin, XYMinimap, getBoundsOfRects, ResizeControlVariant, XYResizer, XY_RESIZER_LINE_POSITIONS, XY_RESIZER_HANDLE_POSITIONS, getNodesBounds, getNodeToolbarTransform } from '@xyflow/system';
5
5
  export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, SelectionMode, addEdge, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, updateEdge } from '@xyflow/system';
6
6
  import { createContext, useContext, useMemo, useEffect, useRef, useState, forwardRef, useLayoutEffect, useCallback, memo } from 'react';
7
7
  import { useStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional';
@@ -40,7 +40,6 @@ function useStoreApi() {
40
40
  getState: store.getState,
41
41
  setState: store.setState,
42
42
  subscribe: store.subscribe,
43
- destroy: store.destroy,
44
43
  }), [store]);
45
44
  }
46
45
 
@@ -82,10 +81,21 @@ function Attribution({ proOptions, position = 'bottom-right' }) {
82
81
  return (jsx(Panel, { position: position, className: "react-flow__attribution", "data-message": "Please only hide this attribution when you are subscribed to React Flow Pro: https://pro.reactflow.dev", children: jsx("a", { href: "https://reactflow.dev", target: "_blank", rel: "noopener noreferrer", "aria-label": "React Flow attribution", children: "React Flow" }) }));
83
82
  }
84
83
 
85
- const selector$o = (s) => ({
86
- selectedNodes: Array.from(s.nodeLookup.values()).filter((n) => n.selected),
87
- selectedEdges: s.edges.filter((e) => e.selected),
88
- });
84
+ const selector$o = (s) => {
85
+ const selectedNodes = [];
86
+ const selectedEdges = [];
87
+ for (const [, node] of s.nodeLookup) {
88
+ if (node.selected) {
89
+ selectedNodes.push(node.internals.userNode);
90
+ }
91
+ }
92
+ for (const [, edge] of s.edgeLookup) {
93
+ if (edge.selected) {
94
+ selectedEdges.push(edge);
95
+ }
96
+ }
97
+ return { selectedNodes, selectedEdges };
98
+ };
89
99
  const selectId = (obj) => obj.id;
90
100
  function areEqual(a, b) {
91
101
  return (shallow(a.selectedNodes.map(selectId), b.selectedNodes.map(selectId)) &&
@@ -548,7 +558,7 @@ function applyChange(change, element) {
548
558
  element.measured ??= {};
549
559
  element.measured.width = change.dimensions.width;
550
560
  element.measured.height = change.dimensions.height;
551
- if (change.resizing) {
561
+ if (change.setAttributes) {
552
562
  element.width = change.dimensions.width;
553
563
  element.height = change.dimensions.height;
554
564
  }
@@ -615,8 +625,8 @@ function createSelectionChange(id, selected) {
615
625
  }
616
626
  function getSelectionChanges(items, selectedIds = new Set(), mutateItem = false) {
617
627
  const changes = [];
618
- for (const [, item] of items) {
619
- const willBeSelected = selectedIds.has(item.id);
628
+ for (const [id, item] of items) {
629
+ const willBeSelected = selectedIds.has(id);
620
630
  // we don't want to set all items to selected=false on the first selection
621
631
  if (!(item.selected === undefined && !willBeSelected) && item.selected !== willBeSelected) {
622
632
  if (mutateItem) {
@@ -692,10 +702,7 @@ function useReactFlow() {
692
702
  const { edges = [] } = store.getState();
693
703
  return edges.map((e) => ({ ...e }));
694
704
  }, []);
695
- const getEdge = useCallback((id) => {
696
- const { edges = [] } = store.getState();
697
- return edges.find((e) => e.id === id);
698
- }, []);
705
+ const getEdge = useCallback((id) => store.getState().edgeLookup.get(id), []);
699
706
  // A reference of all the batched updates to process before the next render. We
700
707
  // want a mutable reference here so multiple synchronous calls to `setNodes` etc
701
708
  // can be batched together.
@@ -825,13 +832,25 @@ function useReactFlow() {
825
832
  }
826
833
  return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
827
834
  }, []);
828
- const getNodeRect = useCallback(({ id }) => {
829
- const internalNode = store.getState().nodeLookup.get(id);
830
- return internalNode ? nodeToRect(internalNode) : null;
835
+ const getNodeRect = useCallback((node) => {
836
+ const { nodeLookup, nodeOrigin } = store.getState();
837
+ const nodeToUse = isNode(node) ? node : nodeLookup.get(node.id);
838
+ const position = nodeToUse.parentId
839
+ ? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.parentId, nodeLookup, nodeOrigin)
840
+ : nodeToUse.position;
841
+ const nodeWithPosition = {
842
+ id: nodeToUse.id,
843
+ position,
844
+ width: nodeToUse.measured?.width ?? nodeToUse.width,
845
+ height: nodeToUse.measured?.height ?? nodeToUse.height,
846
+ data: nodeToUse.data,
847
+ };
848
+ return nodeToRect(nodeWithPosition);
831
849
  }, []);
832
850
  const getIntersectingNodes = useCallback((nodeOrRect, partially = true, nodes) => {
833
851
  const isRect = isRectObject(nodeOrRect);
834
852
  const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
853
+ const hasNodesOption = nodes !== undefined;
835
854
  if (!nodeRect) {
836
855
  return [];
837
856
  }
@@ -840,7 +859,7 @@ function useReactFlow() {
840
859
  if (internalNode && !isRect && (n.id === nodeOrRect.id || !internalNode.internals.positionAbsolute)) {
841
860
  return false;
842
861
  }
843
- const currNodeRect = nodeToRect(n);
862
+ const currNodeRect = nodeToRect(hasNodesOption ? n : internalNode);
844
863
  const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
845
864
  const partiallyVisible = partially && overlappingArea > 0;
846
865
  return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
@@ -856,7 +875,7 @@ function useReactFlow() {
856
875
  const partiallyVisible = partially && overlappingArea > 0;
857
876
  return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
858
877
  }, []);
859
- const updateNode = useCallback((id, nodeUpdate, options = { replace: true }) => {
878
+ const updateNode = useCallback((id, nodeUpdate, options = { replace: false }) => {
860
879
  setNodes((prevNodes) => prevNodes.map((node) => {
861
880
  if (node.id === id) {
862
881
  const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node) : nodeUpdate;
@@ -1100,6 +1119,7 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1100
1119
  const prevSelectedNodesCount = useRef(0);
1101
1120
  const prevSelectedEdgesCount = useRef(0);
1102
1121
  const containerBounds = useRef();
1122
+ const edgeIdLookup = useRef(new Map());
1103
1123
  const { userSelectionActive, elementsSelectable, dragging } = useStore(selector$j, shallow);
1104
1124
  const resetUserSelection = () => {
1105
1125
  store.setState({ userSelectionActive: false, userSelectionRect: null });
@@ -1120,7 +1140,7 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1120
1140
  };
1121
1141
  const onWheel = onPaneScroll ? (event) => onPaneScroll(event) : undefined;
1122
1142
  const onMouseDown = (event) => {
1123
- const { resetSelectedElements, domNode } = store.getState();
1143
+ const { resetSelectedElements, domNode, edgeLookup } = store.getState();
1124
1144
  containerBounds.current = domNode?.getBoundingClientRect();
1125
1145
  if (!elementsSelectable ||
1126
1146
  !isSelecting ||
@@ -1129,6 +1149,11 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1129
1149
  !containerBounds.current) {
1130
1150
  return;
1131
1151
  }
1152
+ edgeIdLookup.current = new Map();
1153
+ for (const [id, edge] of edgeLookup) {
1154
+ edgeIdLookup.current.set(edge.source, edgeIdLookup.current.get(edge.source)?.add(id) || new Set([id]));
1155
+ edgeIdLookup.current.set(edge.target, edgeIdLookup.current.get(edge.target)?.add(id) || new Set([id]));
1156
+ }
1132
1157
  const { x, y } = getEventPosition(event.nativeEvent, containerBounds.current);
1133
1158
  resetSelectedElements();
1134
1159
  store.setState({
@@ -1148,24 +1173,24 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1148
1173
  if (!isSelecting || !containerBounds.current || !userSelectionRect) {
1149
1174
  return;
1150
1175
  }
1151
- store.setState({ userSelectionActive: true, nodesSelectionActive: false });
1152
- const mousePos = getEventPosition(event.nativeEvent, containerBounds.current);
1153
- const startX = userSelectionRect.startX ?? 0;
1154
- const startY = userSelectionRect.startY ?? 0;
1176
+ const { x: mouseX, y: mouseY } = getEventPosition(event.nativeEvent, containerBounds.current);
1177
+ const { startX, startY } = userSelectionRect;
1155
1178
  const nextUserSelectRect = {
1156
- ...userSelectionRect,
1157
- x: mousePos.x < startX ? mousePos.x : startX,
1158
- y: mousePos.y < startY ? mousePos.y : startY,
1159
- width: Math.abs(mousePos.x - startX),
1160
- height: Math.abs(mousePos.y - startY),
1179
+ startX,
1180
+ startY,
1181
+ x: mouseX < startX ? mouseX : startX,
1182
+ y: mouseY < startY ? mouseY : startY,
1183
+ width: Math.abs(mouseX - startX),
1184
+ height: Math.abs(mouseY - startY),
1161
1185
  };
1162
1186
  const selectedNodes = getNodesInside(nodeLookup, nextUserSelectRect, transform, selectionMode === SelectionMode.Partial, true, nodeOrigin);
1163
1187
  const selectedEdgeIds = new Set();
1164
1188
  const selectedNodeIds = new Set();
1165
1189
  for (const selectedNode of selectedNodes) {
1166
1190
  selectedNodeIds.add(selectedNode.id);
1167
- for (const [edgeId, edge] of edgeLookup) {
1168
- if (edge.source === selectedNode.id || edge.target === selectedNode.id) {
1191
+ const edgeIds = edgeIdLookup.current.get(selectedNode.id);
1192
+ if (edgeIds) {
1193
+ for (const edgeId of edgeIds) {
1169
1194
  selectedEdgeIds.add(edgeId);
1170
1195
  }
1171
1196
  }
@@ -1182,6 +1207,8 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1182
1207
  }
1183
1208
  store.setState({
1184
1209
  userSelectionRect: nextUserSelectRect,
1210
+ userSelectionActive: true,
1211
+ nodesSelectionActive: false,
1185
1212
  });
1186
1213
  };
1187
1214
  const onMouseUp = (event) => {
@@ -1288,7 +1315,7 @@ function useMoveSelectedNodes() {
1288
1315
  const store = useStoreApi();
1289
1316
  const moveSelectedNodes = useCallback((params) => {
1290
1317
  const { nodeExtent, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions, nodeLookup, nodeOrigin } = store.getState();
1291
- const nodeUpdates = [];
1318
+ const nodeUpdates = new Map();
1292
1319
  const isSelected = selectedAndDraggable(nodesDraggable);
1293
1320
  // by default a node moves 5px on each key press
1294
1321
  // if snap grid is enabled, we use that for the velocity
@@ -1317,7 +1344,7 @@ function useMoveSelectedNodes() {
1317
1344
  });
1318
1345
  node.position = position;
1319
1346
  node.internals.positionAbsolute = positionAbsolute;
1320
- nodeUpdates.push(node);
1347
+ nodeUpdates.set(node.id, node);
1321
1348
  }
1322
1349
  updateNodePositions(nodeUpdates);
1323
1350
  }, []);
@@ -1515,13 +1542,10 @@ function getNodeInlineStyleDimensions(node) {
1515
1542
  }
1516
1543
 
1517
1544
  const selector$h = (s) => {
1518
- const selectedNodes = [];
1519
- for (const [, node] of s.nodeLookup) {
1520
- if (node.selected) {
1521
- selectedNodes.push(node);
1522
- }
1523
- }
1524
- const { width, height, x, y } = getNodesBounds(selectedNodes, { nodeOrigin: s.nodeOrigin });
1545
+ const { width, height, x, y } = getInternalNodesBounds(s.nodeLookup, {
1546
+ nodeOrigin: s.nodeOrigin,
1547
+ filter: (node) => !!node.selected,
1548
+ });
1525
1549
  return {
1526
1550
  width,
1527
1551
  height,
@@ -1602,15 +1626,14 @@ function useVisibleNodeIds(onlyRenderVisible) {
1602
1626
  return nodeIds;
1603
1627
  }
1604
1628
 
1605
- const selector$e = (s) => s.updateNodeDimensions;
1629
+ const selector$e = (s) => s.updateNodeInternals;
1606
1630
  function useResizeObserver() {
1607
- const updateNodeDimensions = useStore(selector$e);
1608
- const resizeObserverRef = useRef();
1609
- const resizeObserver = useMemo(() => {
1631
+ const updateNodeInternals = useStore(selector$e);
1632
+ const [resizeObserver] = useState(() => {
1610
1633
  if (typeof ResizeObserver === 'undefined') {
1611
1634
  return null;
1612
1635
  }
1613
- const observer = new ResizeObserver((entries) => {
1636
+ return new ResizeObserver((entries) => {
1614
1637
  const updates = new Map();
1615
1638
  entries.forEach((entry) => {
1616
1639
  const id = entry.target.getAttribute('data-id');
@@ -1619,33 +1642,76 @@ function useResizeObserver() {
1619
1642
  nodeElement: entry.target,
1620
1643
  });
1621
1644
  });
1622
- updateNodeDimensions(updates);
1645
+ updateNodeInternals(updates);
1623
1646
  });
1624
- resizeObserverRef.current = observer;
1625
- return observer;
1626
- }, []);
1647
+ });
1627
1648
  useEffect(() => {
1628
1649
  return () => {
1629
- resizeObserverRef?.current?.disconnect();
1650
+ resizeObserver?.disconnect();
1630
1651
  };
1631
- }, []);
1652
+ }, [resizeObserver]);
1632
1653
  return resizeObserver;
1633
1654
  }
1634
1655
 
1656
+ /**
1657
+ * Hook to handle the resize observation + internal updates for the passed node.
1658
+ *
1659
+ * @internal
1660
+ * @returns nodeRef - reference to the node element
1661
+ */
1662
+ function useNodeObserver({ node, nodeType, hasDimensions, resizeObserver, }) {
1663
+ const store = useStoreApi();
1664
+ const nodeRef = useRef(null);
1665
+ const observedNode = useRef(null);
1666
+ const prevSourcePosition = useRef(node.sourcePosition);
1667
+ const prevTargetPosition = useRef(node.targetPosition);
1668
+ const prevType = useRef(nodeType);
1669
+ const isInitialized = hasDimensions && !!node.internals.handleBounds && !node.hidden;
1670
+ useEffect(() => {
1671
+ if (nodeRef.current && (!isInitialized || observedNode.current !== nodeRef.current)) {
1672
+ if (observedNode.current) {
1673
+ resizeObserver?.unobserve(observedNode.current);
1674
+ }
1675
+ resizeObserver?.observe(nodeRef.current);
1676
+ observedNode.current = nodeRef.current;
1677
+ }
1678
+ }, [isInitialized]);
1679
+ useEffect(() => {
1680
+ return () => {
1681
+ if (observedNode.current) {
1682
+ resizeObserver?.unobserve(observedNode.current);
1683
+ observedNode.current = null;
1684
+ }
1685
+ };
1686
+ }, []);
1687
+ useEffect(() => {
1688
+ if (nodeRef.current) {
1689
+ // when the user programmatically changes the source or handle position, we need to update the internals
1690
+ // to make sure the edges are updated correctly
1691
+ const typeChanged = prevType.current !== nodeType;
1692
+ const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
1693
+ const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
1694
+ if (typeChanged || sourcePosChanged || targetPosChanged) {
1695
+ prevType.current = nodeType;
1696
+ prevSourcePosition.current = node.sourcePosition;
1697
+ prevTargetPosition.current = node.targetPosition;
1698
+ store
1699
+ .getState()
1700
+ .updateNodeInternals(new Map([[node.id, { id: node.id, nodeElement: nodeRef.current, force: true }]]));
1701
+ }
1702
+ }
1703
+ }, [node.id, nodeType, node.sourcePosition, node.targetPosition]);
1704
+ return nodeRef;
1705
+ }
1706
+
1635
1707
  function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onContextMenu, onDoubleClick, nodesDraggable, elementsSelectable, nodesConnectable, nodesFocusable, resizeObserver, noDragClassName, noPanClassName, disableKeyboardA11y, rfId, nodeTypes, nodeExtent, nodeOrigin, onError, }) {
1636
- const { node, positionAbsoluteX, positionAbsoluteY, zIndex, isParent } = useStore((s) => {
1708
+ const { node, internals, isParent } = useStore((s) => {
1637
1709
  const node = s.nodeLookup.get(id);
1638
- const positionAbsolute = nodeExtent
1639
- ? clampPosition(node.internals.positionAbsolute, nodeExtent)
1640
- : node.internals.positionAbsolute || { x: 0, y: 0 };
1710
+ const isParent = s.parentLookup.has(id);
1641
1711
  return {
1642
1712
  node,
1643
- // we are mutating positionAbsolute, z and isParent attributes for sub flows
1644
- // so we we need to force a re-render when some change
1645
- positionAbsoluteX: positionAbsolute.x,
1646
- positionAbsoluteY: positionAbsolute.y,
1647
- zIndex: node.internals.z,
1648
- isParent: node.internals.isParent,
1713
+ internals: node.internals,
1714
+ isParent,
1649
1715
  };
1650
1716
  }, shallow);
1651
1717
  let nodeType = node.type || 'default';
@@ -1660,50 +1726,8 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1660
1726
  const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined'));
1661
1727
  const isFocusable = !!(node.focusable || (nodesFocusable && typeof node.focusable === 'undefined'));
1662
1728
  const store = useStoreApi();
1663
- const nodeRef = useRef(null);
1664
- const prevSourcePosition = useRef(node.sourcePosition);
1665
- const prevTargetPosition = useRef(node.targetPosition);
1666
- const prevType = useRef(nodeType);
1667
- const nodeDimensions = getNodeDimensions(node);
1668
- const inlineDimensions = getNodeInlineStyleDimensions(node);
1669
- const initialized = nodeHasDimensions(node);
1670
- const hasHandleBounds = !!node.internals.handleBounds;
1671
- const moveSelectedNodes = useMoveSelectedNodes();
1672
- useEffect(() => {
1673
- const currNode = nodeRef.current;
1674
- return () => {
1675
- if (currNode) {
1676
- resizeObserver?.unobserve(currNode);
1677
- }
1678
- };
1679
- }, []);
1680
- useEffect(() => {
1681
- if (nodeRef.current && !node.hidden) {
1682
- const currNode = nodeRef.current;
1683
- if (!initialized || !hasHandleBounds) {
1684
- resizeObserver?.unobserve(currNode);
1685
- resizeObserver?.observe(currNode);
1686
- }
1687
- }
1688
- }, [node.hidden, initialized, hasHandleBounds]);
1689
- useEffect(() => {
1690
- // when the user programmatically changes the source or handle position, we re-initialize the node
1691
- const typeChanged = prevType.current !== nodeType;
1692
- const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
1693
- const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
1694
- if (nodeRef.current && (typeChanged || sourcePosChanged || targetPosChanged)) {
1695
- if (typeChanged) {
1696
- prevType.current = nodeType;
1697
- }
1698
- if (sourcePosChanged) {
1699
- prevSourcePosition.current = node.sourcePosition;
1700
- }
1701
- if (targetPosChanged) {
1702
- prevTargetPosition.current = node.targetPosition;
1703
- }
1704
- store.getState().updateNodeDimensions(new Map([[id, { id, nodeElement: nodeRef.current, force: true }]]));
1705
- }
1706
- }, [id, nodeType, node.sourcePosition, node.targetPosition]);
1729
+ const hasDimensions = nodeHasDimensions(node);
1730
+ const nodeRef = useNodeObserver({ node, nodeType, hasDimensions, resizeObserver });
1707
1731
  const dragging = useDrag({
1708
1732
  nodeRef,
1709
1733
  disabled: node.hidden || !isDraggable,
@@ -1712,12 +1736,17 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1712
1736
  nodeId: id,
1713
1737
  isSelectable,
1714
1738
  });
1739
+ const moveSelectedNodes = useMoveSelectedNodes();
1715
1740
  if (node.hidden) {
1716
1741
  return null;
1717
1742
  }
1718
- const positionAbsoluteOrigin = getPositionWithOrigin({
1719
- x: positionAbsoluteX,
1720
- y: positionAbsoluteY,
1743
+ const nodeDimensions = getNodeDimensions(node);
1744
+ const inlineDimensions = getNodeInlineStyleDimensions(node);
1745
+ const clampedPosition = nodeExtent
1746
+ ? clampPosition(internals.positionAbsolute, nodeExtent)
1747
+ : internals.positionAbsolute;
1748
+ const positionWithOrigin = getPositionWithOrigin({
1749
+ ...clampedPosition,
1721
1750
  ...nodeDimensions,
1722
1751
  origin: node.origin || nodeOrigin,
1723
1752
  });
@@ -1759,7 +1788,7 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1759
1788
  store.setState({
1760
1789
  ariaLiveMessage: `Moved selected node ${event.key
1761
1790
  .replace('Arrow', '')
1762
- .toLowerCase()}. New position, x: ${~~positionAbsoluteX}, y: ${~~positionAbsoluteY}`,
1791
+ .toLowerCase()}. New position, x: ${~~clampedPosition.x}, y: ${~~clampedPosition.y}`,
1763
1792
  });
1764
1793
  moveSelectedNodes({
1765
1794
  direction: arrowKeyDiffs[event.key],
@@ -1783,13 +1812,13 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1783
1812
  dragging,
1784
1813
  },
1785
1814
  ]), ref: nodeRef, style: {
1786
- zIndex,
1787
- transform: `translate(${positionAbsoluteOrigin.x}px,${positionAbsoluteOrigin.y}px)`,
1815
+ zIndex: internals.z,
1816
+ transform: `translate(${positionWithOrigin.x}px,${positionWithOrigin.y}px)`,
1788
1817
  pointerEvents: hasPointerEvents ? 'all' : 'none',
1789
- visibility: initialized ? 'visible' : 'hidden',
1818
+ visibility: hasDimensions ? 'visible' : 'hidden',
1790
1819
  ...node.style,
1791
1820
  ...inlineDimensions,
1792
- }, "data-id": id, "data-testid": `rf__node-${id}`, onMouseEnter: onMouseEnterHandler, onMouseMove: onMouseMoveHandler, onMouseLeave: onMouseLeaveHandler, onContextMenu: onContextMenuHandler, onClick: onSelectNodeHandler, onDoubleClick: onDoubleClickHandler, onKeyDown: isFocusable ? onKeyDown : undefined, tabIndex: isFocusable ? 0 : undefined, role: isFocusable ? 'button' : undefined, "aria-describedby": disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`, "aria-label": node.ariaLabel, children: jsx(Provider, { value: id, children: jsx(NodeComponent, { id: id, data: node.data, type: nodeType, positionAbsoluteX: positionAbsoluteX, positionAbsoluteY: positionAbsoluteY, selected: node.selected, isConnectable: isConnectable, sourcePosition: node.sourcePosition, targetPosition: node.targetPosition, dragging: dragging, dragHandle: node.dragHandle, zIndex: zIndex, ...nodeDimensions }) }) }));
1821
+ }, "data-id": id, "data-testid": `rf__node-${id}`, onMouseEnter: onMouseEnterHandler, onMouseMove: onMouseMoveHandler, onMouseLeave: onMouseLeaveHandler, onContextMenu: onContextMenuHandler, onClick: onSelectNodeHandler, onDoubleClick: onDoubleClickHandler, onKeyDown: isFocusable ? onKeyDown : undefined, tabIndex: isFocusable ? 0 : undefined, role: isFocusable ? 'button' : undefined, "aria-describedby": disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`, "aria-label": node.ariaLabel, children: jsx(Provider, { value: id, children: jsx(NodeComponent, { id: id, data: node.data, type: nodeType, positionAbsoluteX: clampedPosition.x, positionAbsoluteY: clampedPosition.y, selected: node.selected, isConnectable: isConnectable, sourcePosition: node.sourcePosition, targetPosition: node.targetPosition, dragging: dragging, dragHandle: node.dragHandle, zIndex: internals.z, ...nodeDimensions }) }) }));
1793
1822
  }
1794
1823
 
1795
1824
  const selector$d = (s) => ({
@@ -2364,7 +2393,7 @@ const ConnectionLine = ({ nodeId, handleType, style, type = ConnectionLineType.B
2364
2393
  toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
2365
2394
  connectionMode: s.connectionMode,
2366
2395
  }), [nodeId]), shallow);
2367
- const fromHandleBounds = fromNode?.internals?.handleBounds;
2396
+ const fromHandleBounds = fromNode?.internals.handleBounds;
2368
2397
  let handleBounds = fromHandleBounds?.[handleType];
2369
2398
  if (connectionMode === ConnectionMode.Loose) {
2370
2399
  handleBounds = handleBounds ? handleBounds : fromHandleBounds?.[handleType === 'source' ? 'target' : 'source'];
@@ -2375,8 +2404,8 @@ const ConnectionLine = ({ nodeId, handleType, style, type = ConnectionLineType.B
2375
2404
  const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
2376
2405
  const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.measured.width ?? 0) / 2;
2377
2406
  const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.measured.height ?? 0;
2378
- const fromX = (fromNode.internals.positionAbsolute.x ?? 0) + fromHandleX;
2379
- const fromY = (fromNode.internals.positionAbsolute.y ?? 0) + fromHandleY;
2407
+ const fromX = fromNode.internals.positionAbsolute.x + fromHandleX;
2408
+ const fromY = fromNode.internals.positionAbsolute.y + fromHandleY;
2380
2409
  const fromPosition = fromHandle?.position;
2381
2410
  const toPosition = fromPosition ? oppositePosition[fromPosition] : null;
2382
2411
  if (!fromPosition || !toPosition) {
@@ -2460,20 +2489,23 @@ const GraphView = memo(GraphViewComponent);
2460
2489
 
2461
2490
  const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, height, fitView, } = {}) => {
2462
2491
  const nodeLookup = new Map();
2492
+ const parentLookup = new Map();
2463
2493
  const connectionLookup = new Map();
2464
2494
  const edgeLookup = new Map();
2465
2495
  const storeEdges = defaultEdges ?? edges ?? [];
2466
2496
  const storeNodes = defaultNodes ?? nodes ?? [];
2467
2497
  updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
2468
- adoptUserNodes(storeNodes, nodeLookup, {
2498
+ adoptUserNodes(storeNodes, nodeLookup, parentLookup, {
2469
2499
  nodeOrigin: [0, 0],
2470
2500
  elevateNodesOnSelect: false,
2471
2501
  });
2472
2502
  let transform = [0, 0, 1];
2473
2503
  if (fitView && width && height) {
2474
- const nodesWithDimensions = storeNodes.filter((node) => (node.width || node.initialWidth) && (node.height || node.initialHeight));
2475
2504
  // @todo users nodeOrigin should be used here
2476
- const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] });
2505
+ const bounds = getInternalNodesBounds(nodeLookup, {
2506
+ nodeOrigin: [0, 0],
2507
+ filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)),
2508
+ });
2477
2509
  const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
2478
2510
  transform = [x, y, zoom];
2479
2511
  }
@@ -2484,6 +2516,7 @@ const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, heig
2484
2516
  transform,
2485
2517
  nodes: storeNodes,
2486
2518
  nodeLookup,
2519
+ parentLookup,
2487
2520
  edges: storeEdges,
2488
2521
  edgeLookup,
2489
2522
  connectionLookup,
@@ -2538,17 +2571,17 @@ const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, heig
2538
2571
  };
2539
2572
  };
2540
2573
 
2541
- const createRFStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height, fitView: fitView$1, }) => createWithEqualityFn((set, get) => ({
2574
+ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height, fitView: fitView$1, }) => createWithEqualityFn((set, get) => ({
2542
2575
  ...getInitialState({ nodes, edges, width, height, fitView: fitView$1, defaultNodes, defaultEdges }),
2543
2576
  setNodes: (nodes) => {
2544
- const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
2577
+ const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
2545
2578
  // setNodes() is called exclusively in response to user actions:
2546
2579
  // - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
2547
2580
  // - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
2548
2581
  //
2549
2582
  // When this happens, we take the note objects passed by the user and extend them with fields
2550
2583
  // relevant for internal React Flow operations.
2551
- adoptUserNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
2584
+ adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin, elevateNodesOnSelect, checkEquality: true });
2552
2585
  set({ nodes });
2553
2586
  },
2554
2587
  setEdges: (edges) => {
@@ -2571,10 +2604,10 @@ const createRFStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height
2571
2604
  // Every node gets registerd at a ResizeObserver. Whenever a node
2572
2605
  // changes its dimensions, this function is called to measure the
2573
2606
  // new dimensions and update the nodes.
2574
- updateNodeDimensions: (updates) => {
2575
- const { onNodesChange, fitView, nodeLookup, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin, debug, } = get();
2576
- const changes = updateNodeDimensions(updates, nodeLookup, domNode, nodeOrigin);
2577
- if (changes.length === 0) {
2607
+ updateNodeInternals: (updates) => {
2608
+ const { onNodesChange, fitView, nodeLookup, parentLookup, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin, debug, } = get();
2609
+ const { changes, updatedInternals } = updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOrigin);
2610
+ if (!updatedInternals) {
2578
2611
  return;
2579
2612
  }
2580
2613
  updateAbsolutePositions(nodeLookup, { nodeOrigin });
@@ -2600,33 +2633,34 @@ const createRFStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height
2600
2633
  }
2601
2634
  },
2602
2635
  updateNodePositions: (nodeDragItems, dragging = false) => {
2603
- const { nodeLookup } = get();
2604
- const triggerChangeNodes = [];
2605
- const changes = nodeDragItems.map((node) => {
2636
+ const parentExpandChildren = [];
2637
+ const changes = [];
2638
+ for (const [id, dragItem] of nodeDragItems) {
2606
2639
  // @todo add expandParent to drag item so that we can get rid of the look up here
2607
- const internalNode = nodeLookup.get(node.id);
2608
2640
  const change = {
2609
- id: node.id,
2641
+ id,
2610
2642
  type: 'position',
2611
- position: node.position,
2643
+ position: dragItem.position,
2612
2644
  dragging,
2613
2645
  };
2614
- if (internalNode?.expandParent && change.position) {
2615
- triggerChangeNodes.push({
2616
- ...internalNode,
2617
- position: change.position,
2618
- internals: {
2619
- ...internalNode.internals,
2620
- positionAbsolute: node.internals.positionAbsolute,
2646
+ if (dragItem?.expandParent && dragItem?.parentId && change.position) {
2647
+ parentExpandChildren.push({
2648
+ id,
2649
+ parentId: dragItem.parentId,
2650
+ rect: {
2651
+ ...dragItem.internals.positionAbsolute,
2652
+ width: dragItem.measured.width,
2653
+ height: dragItem.measured.height,
2621
2654
  },
2622
2655
  });
2623
2656
  change.position.x = Math.max(0, change.position.x);
2624
2657
  change.position.y = Math.max(0, change.position.y);
2625
2658
  }
2626
- return change;
2627
- });
2628
- if (triggerChangeNodes.length > 0) {
2629
- const parentExpandChanges = handleParentExpand(triggerChangeNodes, nodeLookup);
2659
+ changes.push(change);
2660
+ }
2661
+ if (parentExpandChildren.length > 0) {
2662
+ const { nodeLookup, parentLookup } = get();
2663
+ const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup);
2630
2664
  changes.push(...parentExpandChanges);
2631
2665
  }
2632
2666
  get().triggerNodeChanges(changes);
@@ -2761,20 +2795,17 @@ const createRFStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height
2761
2795
  reset: () => set({ ...getInitialState() }),
2762
2796
  }), Object.is);
2763
2797
 
2764
- function ReactFlowProvider({ children, initialNodes, initialEdges, defaultNodes, defaultEdges, initialWidth, initialHeight, fitView, }) {
2765
- const storeRef = useRef(null);
2766
- if (!storeRef.current) {
2767
- storeRef.current = createRFStore({
2768
- nodes: initialNodes,
2769
- edges: initialEdges,
2770
- defaultNodes,
2771
- defaultEdges,
2772
- width: initialWidth,
2773
- height: initialHeight,
2774
- fitView,
2775
- });
2776
- }
2777
- return jsx(Provider$1, { value: storeRef.current, children: children });
2798
+ function ReactFlowProvider({ initialNodes: nodes, initialEdges: edges, defaultNodes, defaultEdges, initialWidth: width, initialHeight: height, fitView, children, }) {
2799
+ const [store] = useState(() => createStore({
2800
+ nodes,
2801
+ edges,
2802
+ defaultNodes,
2803
+ defaultEdges,
2804
+ width,
2805
+ height,
2806
+ fitView,
2807
+ }));
2808
+ return jsx(Provider$1, { value: store, children: children });
2778
2809
  }
2779
2810
 
2780
2811
  function Wrapper({ children, nodes, edges, defaultNodes, defaultEdges, width, height, fitView, }) {
@@ -2828,7 +2859,7 @@ function ViewportPortal({ children }) {
2828
2859
  function useUpdateNodeInternals() {
2829
2860
  const store = useStoreApi();
2830
2861
  return useCallback((id) => {
2831
- const { domNode, updateNodeDimensions } = store.getState();
2862
+ const { domNode, updateNodeInternals } = store.getState();
2832
2863
  const updateIds = Array.isArray(id) ? id : [id];
2833
2864
  const updates = new Map();
2834
2865
  updateIds.forEach((updateId) => {
@@ -2837,7 +2868,7 @@ function useUpdateNodeInternals() {
2837
2868
  updates.set(updateId, { id: updateId, nodeElement, force: true });
2838
2869
  }
2839
2870
  });
2840
- requestAnimationFrame(() => updateNodeDimensions(updates));
2871
+ requestAnimationFrame(() => updateNodeInternals(updates));
2841
2872
  }, []);
2842
2873
  }
2843
2874
 
@@ -3193,7 +3224,7 @@ nodeComponent: NodeComponent = MiniMapNode, onClick, }) {
3193
3224
  function NodeComponentWrapperInner({ id, nodeOrigin, nodeColorFunc, nodeStrokeColorFunc, nodeClassNameFunc, nodeBorderRadius, nodeStrokeWidth, shapeRendering, NodeComponent, onClick, }) {
3194
3225
  const { node, x, y } = useStore((s) => {
3195
3226
  const node = s.nodeLookup.get(id);
3196
- const { x, y } = getNodePositionWithOrigin(node, node?.origin || nodeOrigin).positionAbsolute;
3227
+ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
3197
3228
  return {
3198
3229
  node,
3199
3230
  x,
@@ -3333,24 +3364,43 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
3333
3364
  };
3334
3365
  },
3335
3366
  onChange: (change, childChanges) => {
3336
- const { triggerNodeChanges } = store.getState();
3367
+ const { triggerNodeChanges, nodeLookup, parentLookup, nodeOrigin } = store.getState();
3337
3368
  const changes = [];
3338
- if (change.isXPosChange || change.isYPosChange) {
3369
+ const nextPosition = { x: change.x, y: change.y };
3370
+ const node = nodeLookup.get(id);
3371
+ if (node && node.expandParent && node.parentId) {
3372
+ const child = {
3373
+ id: node.id,
3374
+ parentId: node.parentId,
3375
+ rect: {
3376
+ width: change.width ?? node.measured.width,
3377
+ height: change.height ?? node.measured.height,
3378
+ ...evaluateAbsolutePosition({
3379
+ x: change.x ?? node.position.x,
3380
+ y: change.y ?? node.position.y,
3381
+ }, node.parentId, nodeLookup, node.origin ?? nodeOrigin),
3382
+ },
3383
+ };
3384
+ const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
3385
+ changes.push(...parentExpandChanges);
3386
+ // when the parent was expanded by the child node, its position will be clamped at 0,0
3387
+ nextPosition.x = change.x ? Math.max(0, change.x) : undefined;
3388
+ nextPosition.y = change.y ? Math.max(0, change.y) : undefined;
3389
+ }
3390
+ if (nextPosition.x !== undefined && nextPosition.y !== undefined) {
3339
3391
  const positionChange = {
3340
3392
  id,
3341
3393
  type: 'position',
3342
- position: {
3343
- x: change.x,
3344
- y: change.y,
3345
- },
3394
+ position: { ...nextPosition },
3346
3395
  };
3347
3396
  changes.push(positionChange);
3348
3397
  }
3349
- if (change.isWidthChange || change.isHeightChange) {
3398
+ if (change.width !== undefined && change.height !== undefined) {
3350
3399
  const dimensionChange = {
3351
3400
  id,
3352
3401
  type: 'dimensions',
3353
3402
  resizing: true,
3403
+ setAttributes: true,
3354
3404
  dimensions: {
3355
3405
  width: change.width,
3356
3406
  height: change.height,
@@ -3470,7 +3520,7 @@ function NodeToolbar({ nodeId, children, className, style, isVisible, position =
3470
3520
  return null;
3471
3521
  }
3472
3522
  const nodeRect = getNodesBounds(nodes, { nodeOrigin });
3473
- const zIndex = Math.max(...nodes.map((node) => (node.internals?.z || 1) + 1));
3523
+ const zIndex = Math.max(...nodes.map((node) => node.internals.z + 1));
3474
3524
  const wrapperStyle = {
3475
3525
  position: 'absolute',
3476
3526
  transform: getNodeToolbarTransform(nodeRect, viewport, position, offset, align),