@xyflow/react 12.0.0-next.8 → 12.0.0-next.9

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 (73) hide show
  1. package/dist/base.css +19 -1
  2. package/dist/esm/additional-components/MiniMap/MiniMap.d.ts +1 -1
  3. package/dist/esm/additional-components/MiniMap/MiniMap.d.ts.map +1 -1
  4. package/dist/esm/additional-components/MiniMap/types.d.ts +2 -0
  5. package/dist/esm/additional-components/MiniMap/types.d.ts.map +1 -1
  6. package/dist/esm/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -1
  7. package/dist/esm/components/NodeWrapper/index.d.ts.map +1 -1
  8. package/dist/esm/components/NodesSelection/index.d.ts.map +1 -1
  9. package/dist/esm/components/ReactFlowProvider/index.d.ts +3 -1
  10. package/dist/esm/components/ReactFlowProvider/index.d.ts.map +1 -1
  11. package/dist/esm/components/StoreUpdater/index.d.ts.map +1 -1
  12. package/dist/esm/container/GraphView/index.d.ts +1 -1
  13. package/dist/esm/container/GraphView/index.d.ts.map +1 -1
  14. package/dist/esm/container/ReactFlow/Wrapper.d.ts +3 -1
  15. package/dist/esm/container/ReactFlow/Wrapper.d.ts.map +1 -1
  16. package/dist/esm/container/ReactFlow/index.d.ts +0 -2
  17. package/dist/esm/container/ReactFlow/index.d.ts.map +1 -1
  18. package/dist/esm/container/ReactFlow/init-values.d.ts +4 -0
  19. package/dist/esm/container/ReactFlow/init-values.d.ts.map +1 -0
  20. package/dist/esm/hooks/useHandleConnections.d.ts +3 -3
  21. package/dist/esm/hooks/useHandleConnections.d.ts.map +1 -1
  22. package/dist/esm/hooks/useKeyPress.d.ts.map +1 -1
  23. package/dist/esm/hooks/useMoveSelectedNodes.d.ts +12 -0
  24. package/dist/esm/hooks/useMoveSelectedNodes.d.ts.map +1 -0
  25. package/dist/esm/hooks/useReactFlow.d.ts.map +1 -1
  26. package/dist/esm/hooks/useUpdateNodePositions.d.ts.map +1 -1
  27. package/dist/esm/index.js +217 -241
  28. package/dist/esm/index.mjs +217 -241
  29. package/dist/esm/store/index.d.ts +3 -1
  30. package/dist/esm/store/index.d.ts.map +1 -1
  31. package/dist/esm/store/initialState.d.ts +3 -1
  32. package/dist/esm/store/initialState.d.ts.map +1 -1
  33. package/dist/esm/types/component-props.d.ts +2 -2
  34. package/dist/esm/types/store.d.ts +4 -3
  35. package/dist/esm/types/store.d.ts.map +1 -1
  36. package/dist/esm/utils/changes.d.ts +1 -1
  37. package/dist/esm/utils/changes.d.ts.map +1 -1
  38. package/dist/style.css +19 -1
  39. package/dist/umd/additional-components/MiniMap/MiniMap.d.ts +1 -1
  40. package/dist/umd/additional-components/MiniMap/MiniMap.d.ts.map +1 -1
  41. package/dist/umd/additional-components/MiniMap/types.d.ts +2 -0
  42. package/dist/umd/additional-components/MiniMap/types.d.ts.map +1 -1
  43. package/dist/umd/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -1
  44. package/dist/umd/components/NodeWrapper/index.d.ts.map +1 -1
  45. package/dist/umd/components/NodesSelection/index.d.ts.map +1 -1
  46. package/dist/umd/components/ReactFlowProvider/index.d.ts +3 -1
  47. package/dist/umd/components/ReactFlowProvider/index.d.ts.map +1 -1
  48. package/dist/umd/components/StoreUpdater/index.d.ts.map +1 -1
  49. package/dist/umd/container/GraphView/index.d.ts +1 -1
  50. package/dist/umd/container/GraphView/index.d.ts.map +1 -1
  51. package/dist/umd/container/ReactFlow/Wrapper.d.ts +3 -1
  52. package/dist/umd/container/ReactFlow/Wrapper.d.ts.map +1 -1
  53. package/dist/umd/container/ReactFlow/index.d.ts +0 -2
  54. package/dist/umd/container/ReactFlow/index.d.ts.map +1 -1
  55. package/dist/umd/container/ReactFlow/init-values.d.ts +4 -0
  56. package/dist/umd/container/ReactFlow/init-values.d.ts.map +1 -0
  57. package/dist/umd/hooks/useHandleConnections.d.ts +3 -3
  58. package/dist/umd/hooks/useHandleConnections.d.ts.map +1 -1
  59. package/dist/umd/hooks/useKeyPress.d.ts.map +1 -1
  60. package/dist/umd/hooks/useMoveSelectedNodes.d.ts +12 -0
  61. package/dist/umd/hooks/useMoveSelectedNodes.d.ts.map +1 -0
  62. package/dist/umd/hooks/useReactFlow.d.ts.map +1 -1
  63. package/dist/umd/index.js +2 -2
  64. package/dist/umd/store/index.d.ts +3 -1
  65. package/dist/umd/store/index.d.ts.map +1 -1
  66. package/dist/umd/store/initialState.d.ts +3 -1
  67. package/dist/umd/store/initialState.d.ts.map +1 -1
  68. package/dist/umd/types/component-props.d.ts +2 -2
  69. package/dist/umd/types/store.d.ts +4 -3
  70. package/dist/umd/types/store.d.ts.map +1 -1
  71. package/dist/umd/utils/changes.d.ts +1 -1
  72. package/dist/umd/utils/changes.d.ts.map +1 -1
  73. package/package.json +4 -4
package/dist/esm/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
- import { createContext, useContext, useMemo, useEffect, useRef, useState, useCallback, forwardRef, memo } from 'react';
3
+ import { createContext, useContext, useMemo, useEffect, useRef, useState, useCallback, useLayoutEffect, forwardRef, memo } from 'react';
4
4
  import cc from 'classcat';
5
5
  import { errorMessages, infiniteExtent, isInputDOMNode, fitView, getViewportForBounds, pointToRendererPoint, rendererPointToPoint, isNodeBase, isEdgeBase, getElementsToRemove, isRectObject, nodeToRect, getOverlappingArea, getDimensions, XYPanZoom, PanOnScrollMode, SelectionMode, getEventPosition, getNodesInside, XYDrag, snapPosition, calculateNodePosition, Position, isMouseEvent, XYHandle, getHostForElement, addEdge, getNodesBounds, clampPosition, internalsSymbol, getPositionWithOrigin, elementSelectionKeys, isEdgeVisible, MarkerType, createMarkerIds, isNumeric, getBezierEdgeCenter, getSmoothStepPath, getStraightPath, getBezierPath, getEdgePosition, getElevatedEdgeZIndex, getMarkerId, ConnectionMode, ConnectionLineType, updateConnectionLookup, adoptUserProvidedNodes, devWarn, updateNodeDimensions, updateAbsolutePositions, panBy, isMacOs, areConnectionMapsEqual, handleConnectionChange, getNodePositionWithOrigin, XYMinimap, getBoundsOfRects, ResizeControlVariant, XYResizer, XY_RESIZER_LINE_POSITIONS, XY_RESIZER_HANDLE_POSITIONS, getNodeToolbarTransform } from '@xyflow/system';
6
6
  export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, SelectionMode, addEdge, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, internalsSymbol, updateEdge } from '@xyflow/system';
@@ -106,6 +106,9 @@ function SelectionListener({ onSelectionChange }) {
106
106
  return null;
107
107
  }
108
108
 
109
+ const defaultNodeOrigin = [0, 0];
110
+ const defaultViewport = { x: 0, y: 0, zoom: 1 };
111
+
109
112
  /*
110
113
  * This component helps us to update the store with the vlues coming from the user.
111
114
  * We distinguish between values we can update directly with `useDirectStoreUpdater` (like `snapGrid`)
@@ -171,35 +174,37 @@ const fieldsToTrack = [...reactFlowFieldsToTrack, 'rfId'];
171
174
  const selector$n = (s) => ({
172
175
  setNodes: s.setNodes,
173
176
  setEdges: s.setEdges,
174
- setDefaultNodesAndEdges: s.setDefaultNodesAndEdges,
175
177
  setMinZoom: s.setMinZoom,
176
178
  setMaxZoom: s.setMaxZoom,
177
179
  setTranslateExtent: s.setTranslateExtent,
178
180
  setNodeExtent: s.setNodeExtent,
179
181
  reset: s.reset,
182
+ setDefaultNodesAndEdges: s.setDefaultNodesAndEdges,
180
183
  });
184
+ const initPrevValues = {
185
+ // these are values that are also passed directly to other components
186
+ // than the StoreUpdater. We can reduce the number of setStore calls
187
+ // by setting the same values here as prev fields.
188
+ translateExtent: infiniteExtent,
189
+ nodeOrigin: defaultNodeOrigin,
190
+ minZoom: 0.5,
191
+ maxZoom: 2,
192
+ elementsSelectable: true,
193
+ noPanClassName: 'nopan',
194
+ rfId: '1',
195
+ };
181
196
  function StoreUpdater(props) {
182
- const { setNodes, setEdges, setDefaultNodesAndEdges, setMinZoom, setMaxZoom, setTranslateExtent, setNodeExtent, reset, } = useStore(selector$n, shallow);
197
+ const { setNodes, setEdges, setMinZoom, setMaxZoom, setTranslateExtent, setNodeExtent, reset, setDefaultNodesAndEdges, } = useStore(selector$n, shallow);
183
198
  const store = useStoreApi();
184
199
  useEffect(() => {
185
- const edgesWithDefaults = props.defaultEdges?.map((e) => ({ ...e, ...props.defaultEdgeOptions }));
186
- setDefaultNodesAndEdges(props.defaultNodes, edgesWithDefaults);
200
+ setDefaultNodesAndEdges(props.defaultNodes, props.defaultEdges);
187
201
  return () => {
202
+ // when we reset the store we also need to reset the previous fields
203
+ previousFields.current = initPrevValues;
188
204
  reset();
189
205
  };
190
206
  }, []);
191
- const previousFields = useRef({
192
- // these are values that are also passed directly to other components
193
- // than the StoreUpdater. We can reduce the number of setStore calls
194
- // by setting the same values here as prev fields.
195
- translateExtent: infiniteExtent,
196
- nodeOrigin: initNodeOrigin,
197
- minZoom: 0.5,
198
- maxZoom: 2,
199
- elementsSelectable: true,
200
- noPanClassName: 'nopan',
201
- rfId: '1',
202
- });
207
+ const previousFields = useRef(initPrevValues);
203
208
  useEffect(() => {
204
209
  for (const fieldName of fieldsToTrack) {
205
210
  const fieldValue = props[fieldName];
@@ -333,6 +338,10 @@ keyCode = null, options = { target: defaultDoc, actInsideInputWithModifier: true
333
338
  else {
334
339
  pressedKeys.current.delete(event[keyOrCode]);
335
340
  }
341
+ // fix for Mac: when cmd key is pressed, keyup is not triggered for any other key, see: https://stackoverflow.com/questions/27380018/when-cmd-key-is-kept-pressed-keyup-is-not-triggered-for-any-other-key
342
+ if (event.key === 'Meta') {
343
+ pressedKeys.current.clear();
344
+ }
336
345
  modifierPressed.current = false;
337
346
  };
338
347
  const resetHandler = () => {
@@ -636,11 +645,13 @@ function applyNodeChanges(changes, nodes) {
636
645
  function applyEdgeChanges(changes, edges) {
637
646
  return applyChanges(changes, edges);
638
647
  }
639
- const createSelectionChange = (id, selected) => ({
640
- id,
641
- type: 'select',
642
- selected,
643
- });
648
+ function createSelectionChange(id, selected) {
649
+ return {
650
+ id,
651
+ type: 'select',
652
+ selected,
653
+ };
654
+ }
644
655
  function getSelectionChanges(items, selectedIds = new Set(), mutateItem = false) {
645
656
  const changes = [];
646
657
  for (const item of items) {
@@ -719,73 +730,86 @@ function useReactFlow() {
719
730
  const { edges = [] } = store.getState();
720
731
  return edges.find((e) => e.id === id);
721
732
  }, []);
722
- // this is used to handle multiple syncronous setNodes calls
723
- const setNodesData = useRef();
724
- const setNodesTimeout = useRef();
725
- const setNodes = useCallback((payload) => {
726
- const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
727
- const nextNodes = typeof payload === 'function' ? payload(setNodesData.current || nodes) : payload;
728
- setNodesData.current = nextNodes;
729
- if (setNodesTimeout.current) {
730
- clearTimeout(setNodesTimeout.current);
731
- }
732
- // if there are multiple synchronous setNodes calls, we only want to call onNodesChange once
733
- // for this, we use a timeout to wait for the last call and store updated nodes in setNodesData
734
- // this is not perfect, but should work in most cases
735
- setNodesTimeout.current = setTimeout(() => {
733
+ // A reference of all the batched updates to process before the next render. We
734
+ // want a mutable reference here so multiple synchronous calls to `setNodes` etc
735
+ // can be batched together.
736
+ const setElementsQueue = useRef({ nodes: [], edges: [] });
737
+ // Because we're using a ref above, we need some way to let React know when to
738
+ // actually process the queue. We flip this bit of state to `true` any time we
739
+ // mutate the queue and then flip it back to `false` after flushing the queue.
740
+ const [shouldFlushQueue, setShouldFlushQueue] = useState(false);
741
+ // Layout effects are guaranteed to run before the next render which means we
742
+ // shouldn't run into any issues with stale state or weird issues that come from
743
+ // rendering things one frame later than expected (we used to use `setTimeout`).
744
+ useLayoutEffect(() => {
745
+ // Because we need to flip the state back to false after flushing, this should
746
+ // trigger the hook again (!). If the hook is being run again we know that any
747
+ // updates should have been processed by now and we can safely clear the queue
748
+ // and bail early.
749
+ if (!shouldFlushQueue) {
750
+ setElementsQueue.current = { nodes: [], edges: [] };
751
+ return;
752
+ }
753
+ if (setElementsQueue.current.nodes.length) {
754
+ const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
755
+ // This is essentially an `Array.reduce` in imperative clothing. Processing
756
+ // this queue is a relatively hot path so we'd like to avoid the overhead of
757
+ // array methods where we can.
758
+ let next = nodes;
759
+ for (const payload of setElementsQueue.current.nodes) {
760
+ next = typeof payload === 'function' ? payload(next) : payload;
761
+ }
736
762
  if (hasDefaultNodes) {
737
- setNodes(nextNodes);
763
+ setNodes(next);
738
764
  }
739
765
  else if (onNodesChange) {
740
- const changes = getElementsDiffChanges({ items: setNodesData.current, lookup: nodeLookup });
741
- onNodesChange(changes);
766
+ onNodesChange(getElementsDiffChanges({
767
+ items: next,
768
+ lookup: nodeLookup,
769
+ }));
742
770
  }
743
- setNodesData.current = undefined;
744
- }, 0);
745
- }, []);
746
- // this is used to handle multiple syncronous setEdges calls
747
- const setEdgesData = useRef();
748
- const setEdgesTimeout = useRef();
749
- const setEdges = useCallback((payload) => {
750
- const { edges = [], setEdges, hasDefaultEdges, onEdgesChange, edgeLookup } = store.getState();
751
- const nextEdges = typeof payload === 'function' ? payload(setEdgesData.current || edges) : payload;
752
- setEdgesData.current = nextEdges;
753
- if (setEdgesTimeout.current) {
754
- clearTimeout(setEdgesTimeout.current);
771
+ setElementsQueue.current.nodes = [];
755
772
  }
756
- setEdgesTimeout.current = setTimeout(() => {
773
+ if (setElementsQueue.current.edges.length) {
774
+ const { edges = [], setEdges, hasDefaultEdges, onEdgesChange, edgeLookup } = store.getState();
775
+ let next = edges;
776
+ for (const payload of setElementsQueue.current.edges) {
777
+ next = typeof payload === 'function' ? payload(next) : payload;
778
+ }
757
779
  if (hasDefaultEdges) {
758
- setEdges(nextEdges);
780
+ setEdges(next);
759
781
  }
760
782
  else if (onEdgesChange) {
761
- const changes = getElementsDiffChanges({ items: nextEdges, lookup: edgeLookup });
762
- onEdgesChange(changes);
783
+ onEdgesChange(getElementsDiffChanges({
784
+ items: next,
785
+ lookup: edgeLookup,
786
+ }));
763
787
  }
764
- setEdgesData.current = undefined;
765
- }, 0);
788
+ setElementsQueue.current.edges = [];
789
+ }
790
+ // Beacuse we're using reactive state to trigger this effect, we need to flip
791
+ // it back to false.
792
+ setShouldFlushQueue(false);
793
+ }, [shouldFlushQueue]);
794
+ const setNodes = useCallback((payload) => {
795
+ setElementsQueue.current.nodes.push(payload);
796
+ setShouldFlushQueue(true);
797
+ }, []);
798
+ const setEdges = useCallback((payload) => {
799
+ setElementsQueue.current.edges.push(payload);
800
+ setShouldFlushQueue(true);
766
801
  }, []);
767
802
  const addNodes = useCallback((payload) => {
768
- const nodes = Array.isArray(payload) ? payload : [payload];
769
- const { nodes: currentNodes, hasDefaultNodes, onNodesChange, setNodes } = store.getState();
770
- if (hasDefaultNodes) {
771
- const nextNodes = [...currentNodes, ...nodes];
772
- setNodes(nextNodes);
773
- }
774
- else if (onNodesChange) {
775
- const changes = nodes.map((node) => ({ item: node, type: 'add' }));
776
- onNodesChange(changes);
777
- }
803
+ const newNodes = Array.isArray(payload) ? payload : [payload];
804
+ // Queueing a functional update means that we won't worry about other calls
805
+ // to `setNodes` that might happen elsewhere.
806
+ setElementsQueue.current.nodes.push((nodes) => [...nodes, ...newNodes]);
807
+ setShouldFlushQueue(true);
778
808
  }, []);
779
809
  const addEdges = useCallback((payload) => {
780
- const nextEdges = Array.isArray(payload) ? payload : [payload];
781
- const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
782
- if (hasDefaultEdges) {
783
- setEdges([...edges, ...nextEdges]);
784
- }
785
- else if (onEdgesChange) {
786
- const changes = nextEdges.map((edge) => ({ item: edge, type: 'add' }));
787
- onEdgesChange(changes);
788
- }
810
+ const newEdges = Array.isArray(payload) ? payload : [payload];
811
+ setElementsQueue.current.edges.push((edges) => [...edges, ...newEdges]);
812
+ setShouldFlushQueue(true);
789
813
  }, []);
790
814
  const toObject = useCallback(() => {
791
815
  const { nodes = [], edges = [], transform } = store.getState();
@@ -1296,23 +1320,22 @@ function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, n
1296
1320
 
1297
1321
  const selectedAndDraggable = (nodesDraggable) => (n) => n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'));
1298
1322
  /**
1299
- * Hook for updating node positions.
1323
+ * Hook for updating node positions by passing a direction and factor
1300
1324
  *
1301
1325
  * @internal
1302
1326
  * @returns function for updating node positions
1303
1327
  */
1304
- function useUpdateNodePositions() {
1328
+ function useMoveSelectedNodes() {
1305
1329
  const store = useStoreApi();
1306
- const updatePositions = useCallback((params) => {
1330
+ const moveSelectedNodes = useCallback((params) => {
1307
1331
  const { nodeExtent, nodes, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions, nodeLookup, nodeOrigin, } = store.getState();
1308
1332
  const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
1309
- // by default a node moves 5px on each key press, or 20px if shift is pressed
1310
- // if snap grid is enabled, we use that for the velocity.
1333
+ // by default a node moves 5px on each key press
1334
+ // if snap grid is enabled, we use that for the velocity
1311
1335
  const xVelo = snapToGrid ? snapGrid[0] : 5;
1312
1336
  const yVelo = snapToGrid ? snapGrid[1] : 5;
1313
- const factor = params.isShiftPressed ? 4 : 1;
1314
- const xDiff = params.x * xVelo * factor;
1315
- const yDiff = params.y * yVelo * factor;
1337
+ const xDiff = params.direction.x * xVelo * params.factor;
1338
+ const yDiff = params.direction.y * yVelo * params.factor;
1316
1339
  const nodeUpdates = selectedNodes.map((node) => {
1317
1340
  if (node.computed?.positionAbsolute) {
1318
1341
  let nextPosition = {
@@ -1335,9 +1358,9 @@ function useUpdateNodePositions() {
1335
1358
  }
1336
1359
  return node;
1337
1360
  });
1338
- updateNodePositions(nodeUpdates, true, false);
1361
+ updateNodePositions(nodeUpdates);
1339
1362
  }, []);
1340
- return updatePositions;
1363
+ return moveSelectedNodes;
1341
1364
  }
1342
1365
 
1343
1366
  const NodeIdContext = createContext(null);
@@ -1519,7 +1542,7 @@ const selector$h = (s) => {
1519
1542
  function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboardA11y }) {
1520
1543
  const store = useStoreApi();
1521
1544
  const { width, height, transformString, userSelectionActive } = useStore(selector$h, shallow);
1522
- const updatePositions = useUpdateNodePositions();
1545
+ const moveSelectedNodes = useMoveSelectedNodes();
1523
1546
  const nodeRef = useRef(null);
1524
1547
  useEffect(() => {
1525
1548
  if (!disableKeyboardA11y) {
@@ -1542,10 +1565,9 @@ function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboar
1542
1565
  : undefined;
1543
1566
  const onKeyDown = (event) => {
1544
1567
  if (Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) {
1545
- updatePositions({
1546
- x: arrowKeyDiffs[event.key].x,
1547
- y: arrowKeyDiffs[event.key].y,
1548
- isShiftPressed: event.shiftKey,
1568
+ moveSelectedNodes({
1569
+ direction: arrowKeyDiffs[event.key],
1570
+ factor: event.shiftKey ? 4 : 1,
1549
1571
  });
1550
1572
  }
1551
1573
  };
@@ -1653,14 +1675,29 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1653
1675
  const prevSourcePosition = useRef(node.sourcePosition);
1654
1676
  const prevTargetPosition = useRef(node.targetPosition);
1655
1677
  const prevType = useRef(nodeType);
1656
- const updatePositions = useUpdateNodePositions();
1678
+ const width = node.width ?? undefined;
1679
+ const height = node.height ?? undefined;
1680
+ const computedWidth = node.computed?.width;
1681
+ const computedHeight = node.computed?.height;
1682
+ const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
1683
+ const hasHandleBounds = !!node[internalsSymbol]?.handleBounds;
1684
+ const moveSelectedNodes = useMoveSelectedNodes();
1685
+ useEffect(() => {
1686
+ return () => {
1687
+ if (nodeRef.current) {
1688
+ resizeObserver?.unobserve(nodeRef.current);
1689
+ }
1690
+ };
1691
+ }, []);
1657
1692
  useEffect(() => {
1658
1693
  if (nodeRef.current && !node.hidden) {
1659
1694
  const currNode = nodeRef.current;
1660
- resizeObserver?.observe(currNode);
1661
- return () => resizeObserver?.unobserve(currNode);
1695
+ if (!initialized || !hasHandleBounds) {
1696
+ resizeObserver?.unobserve(currNode);
1697
+ resizeObserver?.observe(currNode);
1698
+ }
1662
1699
  }
1663
- }, [node.hidden]);
1700
+ }, [node.hidden, initialized, hasHandleBounds]);
1664
1701
  useEffect(() => {
1665
1702
  // when the user programmatically changes the source or handle position, we re-initialize the node
1666
1703
  const typeChanged = prevType.current !== nodeType;
@@ -1690,10 +1727,6 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1690
1727
  if (node.hidden) {
1691
1728
  return null;
1692
1729
  }
1693
- const width = node.width ?? undefined;
1694
- const height = node.height ?? undefined;
1695
- const computedWidth = node.computed?.width;
1696
- const computedHeight = node.computed?.height;
1697
1730
  const positionAbsoluteOrigin = getPositionWithOrigin({
1698
1731
  x: positionAbsoluteX,
1699
1732
  y: positionAbsoluteY,
@@ -1701,7 +1734,6 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1701
1734
  height: computedHeight ?? height ?? 0,
1702
1735
  origin: node.origin || nodeOrigin,
1703
1736
  });
1704
- const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
1705
1737
  const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
1706
1738
  const onMouseEnterHandler = onMouseEnter ? (event) => onMouseEnter(event, { ...node }) : undefined;
1707
1739
  const onMouseMoveHandler = onMouseMove ? (event) => onMouseMove(event, { ...node }) : undefined;
@@ -1745,10 +1777,9 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1745
1777
  .replace('Arrow', '')
1746
1778
  .toLowerCase()}. New position, x: ${~~positionAbsoluteX}, y: ${~~positionAbsoluteY}`,
1747
1779
  });
1748
- updatePositions({
1749
- x: arrowKeyDiffs[event.key].x,
1750
- y: arrowKeyDiffs[event.key].y,
1751
- isShiftPressed: event.shiftKey,
1780
+ moveSelectedNodes({
1781
+ direction: arrowKeyDiffs[event.key],
1782
+ factor: event.shiftKey ? 4 : 1,
1752
1783
  });
1753
1784
  }
1754
1785
  };
@@ -2200,8 +2231,8 @@ function EdgeWrapper({ id, edgesFocusable, edgesUpdatable, elementsSelectable, o
2200
2231
  ...(edgePosition || nullPosition),
2201
2232
  };
2202
2233
  }, [edge.source, edge.target, edge.sourceHandle, edge.targetHandle, edge.selected, edge.zIndex]), shallow);
2203
- const markerStartUrl = useMemo(() => (edge.markerStart ? `url(#${getMarkerId(edge.markerStart, rfId)})` : undefined), [edge.markerStart, rfId]);
2204
- const markerEndUrl = useMemo(() => (edge.markerEnd ? `url(#${getMarkerId(edge.markerEnd, rfId)})` : undefined), [edge.markerEnd, rfId]);
2234
+ const markerStartUrl = useMemo(() => (edge.markerStart ? `url('#${getMarkerId(edge.markerStart, rfId)}')` : undefined), [edge.markerStart, rfId]);
2235
+ const markerEndUrl = useMemo(() => (edge.markerEnd ? `url('#${getMarkerId(edge.markerEnd, rfId)}')` : undefined), [edge.markerEnd, rfId]);
2205
2236
  if (edge.hidden || !sourceX || !sourceY || !targetX || !targetY) {
2206
2237
  return null;
2207
2238
  }
@@ -2442,37 +2473,14 @@ function GraphViewComponent({ nodeTypes, edgeTypes, onInit, onNodeClick, onEdgeC
2442
2473
  GraphViewComponent.displayName = 'GraphView';
2443
2474
  const GraphView = memo(GraphViewComponent);
2444
2475
 
2445
- function handleControlledSelectionChange(changes, items) {
2446
- return items.map((item) => {
2447
- const change = changes.find((change) => change.id === item.id);
2448
- if (change) {
2449
- item.selected = change.selected;
2450
- }
2451
- return item;
2452
- });
2453
- }
2454
- function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get, set }) {
2455
- const { nodes, edges, onNodesChange, onEdgesChange, hasDefaultNodes, hasDefaultEdges } = get();
2456
- if (changedNodes?.length) {
2457
- if (hasDefaultNodes) {
2458
- set({ nodes: handleControlledSelectionChange(changedNodes, nodes) });
2459
- }
2460
- onNodesChange?.(changedNodes);
2461
- }
2462
- if (changedEdges?.length) {
2463
- if (hasDefaultEdges) {
2464
- set({ edges: handleControlledSelectionChange(changedEdges, edges) });
2465
- }
2466
- onEdgesChange?.(changedEdges);
2467
- }
2468
- }
2469
-
2470
- const getInitialState = ({ nodes = [], edges = [], width, height, fitView, } = {}) => {
2476
+ const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, height, fitView, } = {}) => {
2471
2477
  const nodeLookup = new Map();
2472
2478
  const connectionLookup = new Map();
2473
2479
  const edgeLookup = new Map();
2474
- updateConnectionLookup(connectionLookup, edgeLookup, edges);
2475
- const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, {
2480
+ const storeEdges = defaultEdges ?? edges ?? [];
2481
+ const storeNodes = defaultNodes ?? nodes ?? [];
2482
+ updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
2483
+ const nextNodes = adoptUserProvidedNodes(storeNodes, nodeLookup, {
2476
2484
  nodeOrigin: [0, 0],
2477
2485
  elevateNodesOnSelect: false,
2478
2486
  });
@@ -2491,13 +2499,13 @@ const getInitialState = ({ nodes = [], edges = [], width, height, fitView, } = {
2491
2499
  transform,
2492
2500
  nodes: nextNodes,
2493
2501
  nodeLookup,
2494
- edges,
2502
+ edges: storeEdges,
2495
2503
  edgeLookup,
2496
2504
  connectionLookup,
2497
2505
  onNodesChange: null,
2498
2506
  onEdgesChange: null,
2499
- hasDefaultNodes: false,
2500
- hasDefaultEdges: false,
2507
+ hasDefaultNodes: defaultNodes !== undefined,
2508
+ hasDefaultEdges: defaultEdges !== undefined,
2501
2509
  panZoom: null,
2502
2510
  minZoom: 0.5,
2503
2511
  maxZoom: 2,
@@ -2544,8 +2552,8 @@ const getInitialState = ({ nodes = [], edges = [], width, height, fitView, } = {
2544
2552
  };
2545
2553
  };
2546
2554
 
2547
- const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) => createWithEqualityFn((set, get) => ({
2548
- ...getInitialState({ nodes, edges, width, height, fitView: fitView$1 }),
2555
+ const createRFStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height, fitView: fitView$1, }) => createWithEqualityFn((set, get) => ({
2556
+ ...getInitialState({ nodes, edges, width, height, fitView: fitView$1, defaultNodes, defaultEdges }),
2549
2557
  setNodes: (nodes) => {
2550
2558
  const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
2551
2559
  // setNodes() is called exclusively in response to user actions:
@@ -2554,7 +2562,6 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2554
2562
  //
2555
2563
  // When this happens, we take the note objects passed by the user and extend them with fields
2556
2564
  // relevant for internal React Flow operations.
2557
- // TODO: consider updating the types to reflect the distinction between user-provided nodes and internal nodes.
2558
2565
  const nodesWithInternalData = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
2559
2566
  set({ nodes: nodesWithInternalData });
2560
2567
  },
@@ -2563,28 +2570,17 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2563
2570
  updateConnectionLookup(connectionLookup, edgeLookup, edges);
2564
2571
  set({ edges });
2565
2572
  },
2566
- // when the user works with an uncontrolled flow,
2567
- // we set a flag `hasDefaultNodes` / `hasDefaultEdges`
2568
2573
  setDefaultNodesAndEdges: (nodes, edges) => {
2569
- const hasDefaultNodes = typeof nodes !== 'undefined';
2570
- const hasDefaultEdges = typeof edges !== 'undefined';
2571
- const nextState = {
2572
- hasDefaultNodes,
2573
- hasDefaultEdges,
2574
- };
2575
- if (hasDefaultNodes) {
2576
- const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
2577
- nextState.nodes = adoptUserProvidedNodes(nodes, nodeLookup, {
2578
- nodeOrigin,
2579
- elevateNodesOnSelect,
2580
- });
2574
+ if (nodes) {
2575
+ const { setNodes } = get();
2576
+ setNodes(nodes);
2577
+ set({ hasDefaultNodes: true });
2581
2578
  }
2582
- if (hasDefaultEdges) {
2583
- const { connectionLookup, edgeLookup } = get();
2584
- updateConnectionLookup(connectionLookup, edgeLookup, edges);
2585
- nextState.edges = edges;
2579
+ if (edges) {
2580
+ const { setEdges } = get();
2581
+ setEdges(edges);
2582
+ set({ hasDefaultEdges: true });
2586
2583
  }
2587
- set(nextState);
2588
2584
  },
2589
2585
  // Every node gets registerd at a ResizeObserver. Whenever a node
2590
2586
  // changes its dimensions, this function is called to measure the
@@ -2621,86 +2617,70 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2621
2617
  onNodesChange?.(changes);
2622
2618
  }
2623
2619
  },
2624
- updateNodePositions: (nodeDragItems, positionChanged = true, dragging = false) => {
2620
+ updateNodePositions: (nodeDragItems, dragging = false) => {
2625
2621
  const changes = nodeDragItems.map((node) => {
2626
2622
  const change = {
2627
2623
  id: node.id,
2628
2624
  type: 'position',
2625
+ position: node.position,
2626
+ positionAbsolute: node.computed?.positionAbsolute,
2629
2627
  dragging,
2630
2628
  };
2631
- if (positionChanged) {
2632
- change.positionAbsolute = node.computed?.positionAbsolute;
2633
- change.position = node.position;
2634
- }
2635
2629
  return change;
2636
2630
  });
2637
2631
  get().triggerNodeChanges(changes);
2638
2632
  },
2639
2633
  triggerNodeChanges: (changes) => {
2640
- const { onNodesChange, nodeLookup, nodes, hasDefaultNodes, nodeOrigin, elevateNodesOnSelect } = get();
2634
+ const { onNodesChange, setNodes, nodes, hasDefaultNodes } = get();
2641
2635
  if (changes?.length) {
2642
2636
  if (hasDefaultNodes) {
2643
2637
  const updatedNodes = applyNodeChanges(changes, nodes);
2644
- const nextNodes = adoptUserProvidedNodes(updatedNodes, nodeLookup, {
2645
- nodeOrigin,
2646
- elevateNodesOnSelect,
2647
- });
2648
- set({ nodes: nextNodes });
2638
+ setNodes(updatedNodes);
2649
2639
  }
2650
2640
  onNodesChange?.(changes);
2651
2641
  }
2652
2642
  },
2643
+ triggerEdgeChanges: (changes) => {
2644
+ const { onEdgesChange, setEdges, edges, hasDefaultEdges } = get();
2645
+ if (changes?.length) {
2646
+ if (hasDefaultEdges) {
2647
+ const updatedEdges = applyEdgeChanges(changes, edges);
2648
+ setEdges(updatedEdges);
2649
+ }
2650
+ onEdgesChange?.(changes);
2651
+ }
2652
+ },
2653
2653
  addSelectedNodes: (selectedNodeIds) => {
2654
- const { multiSelectionActive, edges, nodes } = get();
2655
- let changedNodes;
2656
- let changedEdges = null;
2654
+ const { multiSelectionActive, edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
2657
2655
  if (multiSelectionActive) {
2658
- changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
2656
+ const nodeChanges = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
2657
+ triggerNodeChanges(nodeChanges);
2658
+ return;
2659
2659
  }
2660
- else {
2661
- changedNodes = getSelectionChanges(nodes, new Set([...selectedNodeIds]), true);
2662
- changedEdges = getSelectionChanges(edges);
2663
- }
2664
- updateNodesAndEdgesSelections({
2665
- changedNodes,
2666
- changedEdges,
2667
- get,
2668
- set,
2669
- });
2660
+ triggerNodeChanges(getSelectionChanges(nodes, new Set([...selectedNodeIds]), true));
2661
+ triggerEdgeChanges(getSelectionChanges(edges));
2670
2662
  },
2671
2663
  addSelectedEdges: (selectedEdgeIds) => {
2672
- const { multiSelectionActive, edges, nodes } = get();
2673
- let changedEdges;
2674
- let changedNodes = null;
2664
+ const { multiSelectionActive, edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
2675
2665
  if (multiSelectionActive) {
2676
- changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
2666
+ const changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
2667
+ triggerEdgeChanges(changedEdges);
2668
+ return;
2677
2669
  }
2678
- else {
2679
- changedEdges = getSelectionChanges(edges, new Set([...selectedEdgeIds]));
2680
- changedNodes = getSelectionChanges(nodes, new Set(), true);
2681
- }
2682
- updateNodesAndEdgesSelections({
2683
- changedNodes,
2684
- changedEdges,
2685
- get,
2686
- set,
2687
- });
2670
+ triggerEdgeChanges(getSelectionChanges(edges, new Set([...selectedEdgeIds])));
2671
+ triggerNodeChanges(getSelectionChanges(nodes, new Set(), true));
2688
2672
  },
2689
2673
  unselectNodesAndEdges: ({ nodes, edges } = {}) => {
2690
- const { edges: storeEdges, nodes: storeNodes } = get();
2674
+ const { edges: storeEdges, nodes: storeNodes, triggerNodeChanges, triggerEdgeChanges } = get();
2691
2675
  const nodesToUnselect = nodes ? nodes : storeNodes;
2692
2676
  const edgesToUnselect = edges ? edges : storeEdges;
2693
- const changedNodes = nodesToUnselect.map((n) => {
2677
+ const nodeChanges = nodesToUnselect.map((n) => {
2694
2678
  n.selected = false;
2695
2679
  return createSelectionChange(n.id, false);
2696
2680
  });
2697
- const changedEdges = edgesToUnselect.map((edge) => createSelectionChange(edge.id, false));
2698
- updateNodesAndEdgesSelections({
2699
- changedNodes,
2700
- changedEdges,
2701
- get,
2702
- set,
2703
- });
2681
+ const edgeChanges = edgesToUnselect.map((edge) => createSelectionChange(edge.id, false));
2682
+ triggerNodeChanges(nodeChanges);
2683
+ triggerEdgeChanges(edgeChanges);
2704
2684
  },
2705
2685
  setMinZoom: (minZoom) => {
2706
2686
  const { panZoom, maxZoom } = get();
@@ -2717,19 +2697,11 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2717
2697
  set({ translateExtent });
2718
2698
  },
2719
2699
  resetSelectedElements: () => {
2720
- const { edges, nodes } = get();
2721
- const nodesToUnselect = nodes
2722
- .filter((e) => e.selected)
2723
- .map((n) => createSelectionChange(n.id, false));
2724
- const edgesToUnselect = edges
2725
- .filter((e) => e.selected)
2726
- .map((e) => createSelectionChange(e.id, false));
2727
- updateNodesAndEdgesSelections({
2728
- changedNodes: nodesToUnselect,
2729
- changedEdges: edgesToUnselect,
2730
- get,
2731
- set,
2732
- });
2700
+ const { edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
2701
+ const nodeChanges = nodes.reduce((res, node) => (node.selected ? [...res, createSelectionChange(node.id, false)] : res), []);
2702
+ const edgeChanges = edges.reduce((res, edge) => (edge.selected ? [...res, createSelectionChange(edge.id, false)] : res), []);
2703
+ triggerNodeChanges(nodeChanges);
2704
+ triggerEdgeChanges(edgeChanges);
2733
2705
  },
2734
2706
  setNodeExtent: (nodeExtent) => {
2735
2707
  const { nodes } = get();
@@ -2781,21 +2753,17 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2781
2753
  };
2782
2754
  set(currentConnection);
2783
2755
  },
2784
- reset: () => {
2785
- // @todo: what should we do about this? Do we still need it?
2786
- // if you are on a SPA with multiple flows, we want to make sure that the store gets resetted
2787
- // when you switch pages. Does this reset solves this? Currently it always gets called. This
2788
- // leads to an emtpy nodes array at the beginning.
2789
- // set({ ...getInitialState() });
2790
- },
2756
+ reset: () => set({ ...getInitialState() }),
2791
2757
  }), Object.is);
2792
2758
 
2793
- function ReactFlowProvider({ children, initialNodes, initialEdges, initialWidth, initialHeight, fitView, }) {
2759
+ function ReactFlowProvider({ children, initialNodes, initialEdges, defaultNodes, defaultEdges, initialWidth, initialHeight, fitView, }) {
2794
2760
  const storeRef = useRef(null);
2795
2761
  if (!storeRef.current) {
2796
2762
  storeRef.current = createRFStore({
2797
2763
  nodes: initialNodes,
2798
2764
  edges: initialEdges,
2765
+ defaultNodes,
2766
+ defaultEdges,
2799
2767
  width: initialWidth,
2800
2768
  height: initialHeight,
2801
2769
  fitView,
@@ -2804,18 +2772,16 @@ function ReactFlowProvider({ children, initialNodes, initialEdges, initialWidth,
2804
2772
  return jsx(Provider$1, { value: storeRef.current, children: children });
2805
2773
  }
2806
2774
 
2807
- function Wrapper({ children, nodes, edges, width, height, fitView, }) {
2775
+ function Wrapper({ children, nodes, edges, defaultNodes, defaultEdges, width, height, fitView, }) {
2808
2776
  const isWrapped = useContext(StoreContext);
2809
2777
  if (isWrapped) {
2810
2778
  // we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
2811
2779
  // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
2812
2780
  return jsx(Fragment, { children: children });
2813
2781
  }
2814
- return (jsx(ReactFlowProvider, { initialNodes: nodes, initialEdges: edges, initialWidth: width, initialHeight: height, fitView: fitView, children: children }));
2782
+ return (jsx(ReactFlowProvider, { initialNodes: nodes, initialEdges: edges, defaultNodes: defaultNodes, defaultEdges: defaultEdges, initialWidth: width, initialHeight: height, fitView: fitView, children: children }));
2815
2783
  }
2816
2784
 
2817
- const initNodeOrigin = [0, 0];
2818
- const initDefaultViewport = { x: 0, y: 0, zoom: 1 };
2819
2785
  const wrapperStyle = {
2820
2786
  width: '100%',
2821
2787
  height: '100%',
@@ -2823,10 +2789,10 @@ const wrapperStyle = {
2823
2789
  position: 'relative',
2824
2790
  zIndex: 0,
2825
2791
  };
2826
- const ReactFlow = forwardRef(({ nodes, edges, defaultNodes, defaultEdges, className, nodeTypes, edgeTypes, onNodeClick, onEdgeClick, onInit, onMove, onMoveStart, onMoveEnd, onConnect, onConnectStart, onConnectEnd, onClickConnectStart, onClickConnectEnd, onNodeMouseEnter, onNodeMouseMove, onNodeMouseLeave, onNodeContextMenu, onNodeDoubleClick, onNodeDragStart, onNodeDrag, onNodeDragStop, onNodesDelete, onEdgesDelete, onDelete, onSelectionChange, onSelectionDragStart, onSelectionDrag, onSelectionDragStop, onSelectionContextMenu, onSelectionStart, onSelectionEnd, onBeforeDelete, connectionMode, connectionLineType = ConnectionLineType.Bezier, connectionLineStyle, connectionLineComponent, connectionLineContainerStyle, deleteKeyCode = 'Backspace', selectionKeyCode = 'Shift', selectionOnDrag = false, selectionMode = SelectionMode.Full, panActivationKeyCode = 'Space', multiSelectionKeyCode = isMacOs() ? 'Meta' : 'Control', zoomActivationKeyCode = isMacOs() ? 'Meta' : 'Control', snapToGrid, snapGrid, onlyRenderVisibleElements = false, selectNodesOnDrag, nodesDraggable, nodesConnectable, nodesFocusable, nodeOrigin = initNodeOrigin, edgesFocusable, edgesUpdatable, elementsSelectable = true, defaultViewport = initDefaultViewport, minZoom = 0.5, maxZoom = 2, translateExtent = infiniteExtent, preventScrolling = true, nodeExtent, defaultMarkerColor = '#b1b1b7', zoomOnScroll = true, zoomOnPinch = true, panOnScroll = false, panOnScrollSpeed = 0.5, panOnScrollMode = PanOnScrollMode.Free, zoomOnDoubleClick = true, panOnDrag = true, onPaneClick, onPaneMouseEnter, onPaneMouseMove, onPaneMouseLeave, onPaneScroll, onPaneContextMenu, children, onEdgeUpdate, onEdgeContextMenu, onEdgeDoubleClick, onEdgeMouseEnter, onEdgeMouseMove, onEdgeMouseLeave, onEdgeUpdateStart, onEdgeUpdateEnd, edgeUpdaterRadius = 10, onNodesChange, onEdgesChange, noDragClassName = 'nodrag', noWheelClassName = 'nowheel', noPanClassName = 'nopan', fitView, fitViewOptions, connectOnClick, attributionPosition, proOptions, defaultEdgeOptions, elevateNodesOnSelect, elevateEdgesOnSelect, disableKeyboardA11y = false, autoPanOnConnect, autoPanOnNodeDrag, connectionRadius, isValidConnection, onError, style, id, nodeDragThreshold, viewport, onViewportChange, width, height, colorMode = 'light', ...rest }, ref) => {
2792
+ const ReactFlow = forwardRef(({ nodes, edges, defaultNodes, defaultEdges, className, nodeTypes, edgeTypes, onNodeClick, onEdgeClick, onInit, onMove, onMoveStart, onMoveEnd, onConnect, onConnectStart, onConnectEnd, onClickConnectStart, onClickConnectEnd, onNodeMouseEnter, onNodeMouseMove, onNodeMouseLeave, onNodeContextMenu, onNodeDoubleClick, onNodeDragStart, onNodeDrag, onNodeDragStop, onNodesDelete, onEdgesDelete, onDelete, onSelectionChange, onSelectionDragStart, onSelectionDrag, onSelectionDragStop, onSelectionContextMenu, onSelectionStart, onSelectionEnd, onBeforeDelete, connectionMode, connectionLineType = ConnectionLineType.Bezier, connectionLineStyle, connectionLineComponent, connectionLineContainerStyle, deleteKeyCode = 'Backspace', selectionKeyCode = 'Shift', selectionOnDrag = false, selectionMode = SelectionMode.Full, panActivationKeyCode = 'Space', multiSelectionKeyCode = isMacOs() ? 'Meta' : 'Control', zoomActivationKeyCode = isMacOs() ? 'Meta' : 'Control', snapToGrid, snapGrid, onlyRenderVisibleElements = false, selectNodesOnDrag, nodesDraggable, nodesConnectable, nodesFocusable, nodeOrigin = defaultNodeOrigin, edgesFocusable, edgesUpdatable, elementsSelectable = true, defaultViewport: defaultViewport$1 = defaultViewport, minZoom = 0.5, maxZoom = 2, translateExtent = infiniteExtent, preventScrolling = true, nodeExtent, defaultMarkerColor = '#b1b1b7', zoomOnScroll = true, zoomOnPinch = true, panOnScroll = false, panOnScrollSpeed = 0.5, panOnScrollMode = PanOnScrollMode.Free, zoomOnDoubleClick = true, panOnDrag = true, onPaneClick, onPaneMouseEnter, onPaneMouseMove, onPaneMouseLeave, onPaneScroll, onPaneContextMenu, children, onEdgeUpdate, onEdgeContextMenu, onEdgeDoubleClick, onEdgeMouseEnter, onEdgeMouseMove, onEdgeMouseLeave, onEdgeUpdateStart, onEdgeUpdateEnd, edgeUpdaterRadius = 10, onNodesChange, onEdgesChange, noDragClassName = 'nodrag', noWheelClassName = 'nowheel', noPanClassName = 'nopan', fitView, fitViewOptions, connectOnClick, attributionPosition, proOptions, defaultEdgeOptions, elevateNodesOnSelect, elevateEdgesOnSelect, disableKeyboardA11y = false, autoPanOnConnect, autoPanOnNodeDrag, connectionRadius, isValidConnection, onError, style, id, nodeDragThreshold, viewport, onViewportChange, width, height, colorMode = 'light', ...rest }, ref) => {
2827
2793
  const rfId = id || '1';
2828
2794
  const colorModeClassName = useColorModeClass(colorMode);
2829
- return (jsx("div", { ...rest, style: { ...style, ...wrapperStyle }, ref: ref, className: cc(['react-flow', className, colorModeClassName]), "data-testid": "rf__wrapper", id: id, children: jsxs(Wrapper, { nodes: nodes, edges: edges, width: width, height: height, fitView: fitView, children: [jsx(GraphView, { onInit: onInit, onNodeClick: onNodeClick, onEdgeClick: onEdgeClick, onNodeMouseEnter: onNodeMouseEnter, onNodeMouseMove: onNodeMouseMove, onNodeMouseLeave: onNodeMouseLeave, onNodeContextMenu: onNodeContextMenu, onNodeDoubleClick: onNodeDoubleClick, nodeTypes: nodeTypes, edgeTypes: edgeTypes, connectionLineType: connectionLineType, connectionLineStyle: connectionLineStyle, connectionLineComponent: connectionLineComponent, connectionLineContainerStyle: connectionLineContainerStyle, selectionKeyCode: selectionKeyCode, selectionOnDrag: selectionOnDrag, selectionMode: selectionMode, deleteKeyCode: deleteKeyCode, multiSelectionKeyCode: multiSelectionKeyCode, panActivationKeyCode: panActivationKeyCode, zoomActivationKeyCode: zoomActivationKeyCode, onlyRenderVisibleElements: onlyRenderVisibleElements, defaultViewport: defaultViewport, translateExtent: translateExtent, minZoom: minZoom, maxZoom: maxZoom, preventScrolling: preventScrolling, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, zoomOnDoubleClick: zoomOnDoubleClick, panOnScroll: panOnScroll, panOnScrollSpeed: panOnScrollSpeed, panOnScrollMode: panOnScrollMode, panOnDrag: panOnDrag, onPaneClick: onPaneClick, onPaneMouseEnter: onPaneMouseEnter, onPaneMouseMove: onPaneMouseMove, onPaneMouseLeave: onPaneMouseLeave, onPaneScroll: onPaneScroll, onPaneContextMenu: onPaneContextMenu, onSelectionContextMenu: onSelectionContextMenu, onSelectionStart: onSelectionStart, onSelectionEnd: onSelectionEnd, onEdgeUpdate: onEdgeUpdate, onEdgeContextMenu: onEdgeContextMenu, onEdgeDoubleClick: onEdgeDoubleClick, onEdgeMouseEnter: onEdgeMouseEnter, onEdgeMouseMove: onEdgeMouseMove, onEdgeMouseLeave: onEdgeMouseLeave, onEdgeUpdateStart: onEdgeUpdateStart, onEdgeUpdateEnd: onEdgeUpdateEnd, edgeUpdaterRadius: edgeUpdaterRadius, defaultMarkerColor: defaultMarkerColor, noDragClassName: noDragClassName, noWheelClassName: noWheelClassName, noPanClassName: noPanClassName, rfId: rfId, disableKeyboardA11y: disableKeyboardA11y, nodeOrigin: nodeOrigin, nodeExtent: nodeExtent, viewport: viewport, onViewportChange: onViewportChange }), jsx(StoreUpdater, { nodes: nodes, edges: edges, defaultNodes: defaultNodes, defaultEdges: defaultEdges, onConnect: onConnect, onConnectStart: onConnectStart, onConnectEnd: onConnectEnd, onClickConnectStart: onClickConnectStart, onClickConnectEnd: onClickConnectEnd, nodesDraggable: nodesDraggable, nodesConnectable: nodesConnectable, nodesFocusable: nodesFocusable, edgesFocusable: edgesFocusable, edgesUpdatable: edgesUpdatable, elementsSelectable: elementsSelectable, elevateNodesOnSelect: elevateNodesOnSelect, elevateEdgesOnSelect: elevateEdgesOnSelect, minZoom: minZoom, maxZoom: maxZoom, nodeExtent: nodeExtent, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, snapToGrid: snapToGrid, snapGrid: snapGrid, connectionMode: connectionMode, translateExtent: translateExtent, connectOnClick: connectOnClick, defaultEdgeOptions: defaultEdgeOptions, fitView: fitView, fitViewOptions: fitViewOptions, onNodesDelete: onNodesDelete, onEdgesDelete: onEdgesDelete, onDelete: onDelete, onNodeDragStart: onNodeDragStart, onNodeDrag: onNodeDrag, onNodeDragStop: onNodeDragStop, onSelectionDrag: onSelectionDrag, onSelectionDragStart: onSelectionDragStart, onSelectionDragStop: onSelectionDragStop, onMove: onMove, onMoveStart: onMoveStart, onMoveEnd: onMoveEnd, noPanClassName: noPanClassName, nodeOrigin: nodeOrigin, rfId: rfId, autoPanOnConnect: autoPanOnConnect, autoPanOnNodeDrag: autoPanOnNodeDrag, onError: onError, connectionRadius: connectionRadius, isValidConnection: isValidConnection, selectNodesOnDrag: selectNodesOnDrag, nodeDragThreshold: nodeDragThreshold, onBeforeDelete: onBeforeDelete }), jsx(SelectionListener, { onSelectionChange: onSelectionChange }), children, jsx(Attribution, { proOptions: proOptions, position: attributionPosition }), jsx(A11yDescriptions, { rfId: rfId, disableKeyboardA11y: disableKeyboardA11y })] }) }));
2795
+ return (jsx("div", { ...rest, style: { ...style, ...wrapperStyle }, ref: ref, className: cc(['react-flow', className, colorModeClassName]), "data-testid": "rf__wrapper", id: id, children: jsxs(Wrapper, { nodes: nodes, edges: edges, defaultNodes: defaultNodes, defaultEdges: defaultEdges, width: width, height: height, fitView: fitView, children: [jsx(GraphView, { onInit: onInit, onNodeClick: onNodeClick, onEdgeClick: onEdgeClick, onNodeMouseEnter: onNodeMouseEnter, onNodeMouseMove: onNodeMouseMove, onNodeMouseLeave: onNodeMouseLeave, onNodeContextMenu: onNodeContextMenu, onNodeDoubleClick: onNodeDoubleClick, nodeTypes: nodeTypes, edgeTypes: edgeTypes, connectionLineType: connectionLineType, connectionLineStyle: connectionLineStyle, connectionLineComponent: connectionLineComponent, connectionLineContainerStyle: connectionLineContainerStyle, selectionKeyCode: selectionKeyCode, selectionOnDrag: selectionOnDrag, selectionMode: selectionMode, deleteKeyCode: deleteKeyCode, multiSelectionKeyCode: multiSelectionKeyCode, panActivationKeyCode: panActivationKeyCode, zoomActivationKeyCode: zoomActivationKeyCode, onlyRenderVisibleElements: onlyRenderVisibleElements, defaultViewport: defaultViewport$1, translateExtent: translateExtent, minZoom: minZoom, maxZoom: maxZoom, preventScrolling: preventScrolling, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, zoomOnDoubleClick: zoomOnDoubleClick, panOnScroll: panOnScroll, panOnScrollSpeed: panOnScrollSpeed, panOnScrollMode: panOnScrollMode, panOnDrag: panOnDrag, onPaneClick: onPaneClick, onPaneMouseEnter: onPaneMouseEnter, onPaneMouseMove: onPaneMouseMove, onPaneMouseLeave: onPaneMouseLeave, onPaneScroll: onPaneScroll, onPaneContextMenu: onPaneContextMenu, onSelectionContextMenu: onSelectionContextMenu, onSelectionStart: onSelectionStart, onSelectionEnd: onSelectionEnd, onEdgeUpdate: onEdgeUpdate, onEdgeContextMenu: onEdgeContextMenu, onEdgeDoubleClick: onEdgeDoubleClick, onEdgeMouseEnter: onEdgeMouseEnter, onEdgeMouseMove: onEdgeMouseMove, onEdgeMouseLeave: onEdgeMouseLeave, onEdgeUpdateStart: onEdgeUpdateStart, onEdgeUpdateEnd: onEdgeUpdateEnd, edgeUpdaterRadius: edgeUpdaterRadius, defaultMarkerColor: defaultMarkerColor, noDragClassName: noDragClassName, noWheelClassName: noWheelClassName, noPanClassName: noPanClassName, rfId: rfId, disableKeyboardA11y: disableKeyboardA11y, nodeOrigin: nodeOrigin, nodeExtent: nodeExtent, viewport: viewport, onViewportChange: onViewportChange }), jsx(StoreUpdater, { nodes: nodes, edges: edges, defaultNodes: defaultNodes, defaultEdges: defaultEdges, onConnect: onConnect, onConnectStart: onConnectStart, onConnectEnd: onConnectEnd, onClickConnectStart: onClickConnectStart, onClickConnectEnd: onClickConnectEnd, nodesDraggable: nodesDraggable, nodesConnectable: nodesConnectable, nodesFocusable: nodesFocusable, edgesFocusable: edgesFocusable, edgesUpdatable: edgesUpdatable, elementsSelectable: elementsSelectable, elevateNodesOnSelect: elevateNodesOnSelect, elevateEdgesOnSelect: elevateEdgesOnSelect, minZoom: minZoom, maxZoom: maxZoom, nodeExtent: nodeExtent, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, snapToGrid: snapToGrid, snapGrid: snapGrid, connectionMode: connectionMode, translateExtent: translateExtent, connectOnClick: connectOnClick, defaultEdgeOptions: defaultEdgeOptions, fitView: fitView, fitViewOptions: fitViewOptions, onNodesDelete: onNodesDelete, onEdgesDelete: onEdgesDelete, onDelete: onDelete, onNodeDragStart: onNodeDragStart, onNodeDrag: onNodeDrag, onNodeDragStop: onNodeDragStop, onSelectionDrag: onSelectionDrag, onSelectionDragStart: onSelectionDragStart, onSelectionDragStop: onSelectionDragStop, onMove: onMove, onMoveStart: onMoveStart, onMoveEnd: onMoveEnd, noPanClassName: noPanClassName, nodeOrigin: nodeOrigin, rfId: rfId, autoPanOnConnect: autoPanOnConnect, autoPanOnNodeDrag: autoPanOnNodeDrag, onError: onError, connectionRadius: connectionRadius, isValidConnection: isValidConnection, selectNodesOnDrag: selectNodesOnDrag, nodeDragThreshold: nodeDragThreshold, onBeforeDelete: onBeforeDelete }), jsx(SelectionListener, { onSelectionChange: onSelectionChange }), children, jsx(Attribution, { proOptions: proOptions, position: attributionPosition }), jsx(A11yDescriptions, { rfId: rfId, disableKeyboardA11y: disableKeyboardA11y })] }) }));
2830
2796
  });
2831
2797
  ReactFlow.displayName = 'ReactFlow';
2832
2798
 
@@ -3011,12 +2977,12 @@ function useNodesInitialized(options = defaultOptions) {
3011
2977
  * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type)
3012
2978
  * @param param.onConnect - gets called when a connection is established
3013
2979
  * @param param.onDisconnect - gets called when a connection is removed
3014
- * @returns an array with connections
2980
+ * @returns an array with handle connections
3015
2981
  */
3016
2982
  function useHandleConnections({ type, id = null, nodeId, onConnect, onDisconnect, }) {
3017
2983
  const _nodeId = useNodeId();
2984
+ const currentNodeId = nodeId ?? _nodeId;
3018
2985
  const prevConnections = useRef(null);
3019
- const currentNodeId = nodeId || _nodeId;
3020
2986
  const connections = useStore((state) => state.connectionLookup.get(`${currentNodeId}-${type}-${id}`), areConnectionMapsEqual);
3021
2987
  useEffect(() => {
3022
2988
  // @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts
@@ -3244,7 +3210,7 @@ const ARIA_LABEL_KEY = 'react-flow__minimap-desc';
3244
3210
  function MiniMapComponent({ style, className, nodeStrokeColor, nodeColor, nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth,
3245
3211
  // We need to rename the prop to be `CapitalCase` so that JSX will render it as
3246
3212
  // a component properly.
3247
- nodeComponent, maskColor, maskStrokeColor = 'none', maskStrokeWidth = 1, position = 'bottom-right', onClick, onNodeClick, pannable = false, zoomable = false, ariaLabel = 'React Flow mini map', inversePan, zoomStep = 10, offsetScale = 5, }) {
3213
+ nodeComponent, bgColor, maskColor, maskStrokeColor, maskStrokeWidth, position = 'bottom-right', onClick, onNodeClick, pannable = false, zoomable = false, ariaLabel = 'React Flow mini map', inversePan, zoomStep = 10, offsetScale = 5, }) {
3248
3214
  const store = useStoreApi();
3249
3215
  const svg = useRef(null);
3250
3216
  const { boundingRect, viewBB, rfId, panZoom, translateExtent, flowWidth, flowHeight } = useStore(selector$1, shallow);
@@ -3302,12 +3268,15 @@ nodeComponent, maskColor, maskStrokeColor = 'none', maskStrokeWidth = 1, positio
3302
3268
  : undefined;
3303
3269
  return (jsx(Panel, { position: position, style: {
3304
3270
  ...style,
3305
- '--xy-minimap-mask-color-props': typeof maskColor === 'string' ? maskColor : undefined,
3271
+ '--xy-minimap-background-color-props': typeof bgColor === 'string' ? bgColor : undefined,
3272
+ '--xy-minimap-mask-background-color-props': typeof maskColor === 'string' ? maskColor : undefined,
3273
+ '--xy-minimap-mask-stroke-color-props': typeof maskStrokeColor === 'string' ? maskStrokeColor : undefined,
3274
+ '--xy-minimap-mask-stroke-width-props': typeof maskStrokeWidth === 'number' ? maskStrokeWidth * viewScale : undefined,
3306
3275
  '--xy-minimap-node-background-color-props': typeof nodeColor === 'string' ? nodeColor : undefined,
3307
3276
  '--xy-minimap-node-stroke-color-props': typeof nodeStrokeColor === 'string' ? nodeStrokeColor : undefined,
3308
3277
  '--xy-minimap-node-stroke-width-props': typeof nodeStrokeWidth === 'string' ? nodeStrokeWidth : undefined,
3309
- }, className: cc(['react-flow__minimap', className]), "data-testid": "rf__minimap", children: jsxs("svg", { width: elementWidth, height: elementHeight, viewBox: `${x} ${y} ${width} ${height}`, role: "img", "aria-labelledby": labelledBy, ref: svg, onClick: onSvgClick, children: [ariaLabel && jsx("title", { id: labelledBy, children: ariaLabel }), jsx(MiniMapNodes$1, { onClick: onSvgNodeClick, nodeColor: nodeColor, nodeStrokeColor: nodeStrokeColor, nodeBorderRadius: nodeBorderRadius, nodeClassName: nodeClassName, nodeStrokeWidth: nodeStrokeWidth, nodeComponent: nodeComponent }), jsx("path", { className: "react-flow__minimap-mask", d: `M${x - offset},${y - offset}h${width + offset * 2}v${height + offset * 2}h${-width - offset * 2}z
3310
- M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`, fillRule: "evenodd", stroke: maskStrokeColor, strokeWidth: maskStrokeWidth, pointerEvents: "none" })] }) }));
3278
+ }, className: cc(['react-flow__minimap', className]), "data-testid": "rf__minimap", children: jsxs("svg", { width: elementWidth, height: elementHeight, viewBox: `${x} ${y} ${width} ${height}`, className: "react-flow__minimap-svg", role: "img", "aria-labelledby": labelledBy, ref: svg, onClick: onSvgClick, children: [ariaLabel && jsx("title", { id: labelledBy, children: ariaLabel }), jsx(MiniMapNodes$1, { onClick: onSvgNodeClick, nodeColor: nodeColor, nodeStrokeColor: nodeStrokeColor, nodeBorderRadius: nodeBorderRadius, nodeClassName: nodeClassName, nodeStrokeWidth: nodeStrokeWidth, nodeComponent: nodeComponent }), jsx("path", { className: "react-flow__minimap-mask", d: `M${x - offset},${y - offset}h${width + offset * 2}v${height + offset * 2}h${-width - offset * 2}z
3279
+ M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`, fillRule: "evenodd", pointerEvents: "none" })] }) }));
3311
3280
  }
3312
3281
  MiniMapComponent.displayName = 'MiniMap';
3313
3282
  const MiniMap = memo(MiniMapComponent);
@@ -3337,7 +3306,7 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
3337
3306
  snapToGrid,
3338
3307
  };
3339
3308
  },
3340
- onChange: (change) => {
3309
+ onChange: (change, childChanges) => {
3341
3310
  const { triggerNodeChanges } = store.getState();
3342
3311
  const changes = [];
3343
3312
  if (change.isXPosChange || change.isYPosChange) {
@@ -3363,6 +3332,13 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
3363
3332
  };
3364
3333
  changes.push(dimensionChange);
3365
3334
  }
3335
+ for (const childChange of childChanges) {
3336
+ const positionChange = {
3337
+ ...childChange,
3338
+ type: 'position',
3339
+ };
3340
+ changes.push(positionChange);
3341
+ }
3366
3342
  triggerNodeChanges(changes);
3367
3343
  },
3368
3344
  onEnd: () => {