@xyflow/react 12.0.0-next.7 → 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 (113) hide show
  1. package/dist/base.css +21 -17
  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/EdgeWrapper/index.d.ts.map +1 -1
  8. package/dist/esm/components/Handle/index.d.ts +6 -2
  9. package/dist/esm/components/Handle/index.d.ts.map +1 -1
  10. package/dist/esm/components/NodeWrapper/index.d.ts.map +1 -1
  11. package/dist/esm/components/NodesSelection/index.d.ts.map +1 -1
  12. package/dist/esm/components/ReactFlowProvider/index.d.ts +3 -1
  13. package/dist/esm/components/ReactFlowProvider/index.d.ts.map +1 -1
  14. package/dist/esm/components/StoreUpdater/index.d.ts.map +1 -1
  15. package/dist/esm/container/EdgeRenderer/MarkerDefinitions.d.ts.map +1 -1
  16. package/dist/esm/container/FlowRenderer/index.d.ts.map +1 -1
  17. package/dist/esm/container/GraphView/index.d.ts +1 -1
  18. package/dist/esm/container/GraphView/index.d.ts.map +1 -1
  19. package/dist/esm/container/ReactFlow/Wrapper.d.ts +3 -1
  20. package/dist/esm/container/ReactFlow/Wrapper.d.ts.map +1 -1
  21. package/dist/esm/container/ReactFlow/index.d.ts +3 -118
  22. package/dist/esm/container/ReactFlow/index.d.ts.map +1 -1
  23. package/dist/esm/container/ReactFlow/init-values.d.ts +4 -0
  24. package/dist/esm/container/ReactFlow/init-values.d.ts.map +1 -0
  25. package/dist/esm/hooks/useConnection.d.ts +13 -7
  26. package/dist/esm/hooks/useConnection.d.ts.map +1 -1
  27. package/dist/esm/hooks/useDrag.d.ts +1 -1
  28. package/dist/esm/hooks/useDrag.d.ts.map +1 -1
  29. package/dist/esm/hooks/useHandleConnections.d.ts +4 -4
  30. package/dist/esm/hooks/useHandleConnections.d.ts.map +1 -1
  31. package/dist/esm/hooks/useKeyPress.d.ts.map +1 -1
  32. package/dist/esm/hooks/useMoveSelectedNodes.d.ts +12 -0
  33. package/dist/esm/hooks/useMoveSelectedNodes.d.ts.map +1 -0
  34. package/dist/esm/hooks/useNodesInitialized.d.ts.map +1 -1
  35. package/dist/esm/hooks/useOnSelectionChange.d.ts +1 -1
  36. package/dist/esm/hooks/useReactFlow.d.ts.map +1 -1
  37. package/dist/esm/index.d.ts +1 -1
  38. package/dist/esm/index.d.ts.map +1 -1
  39. package/dist/esm/index.js +276 -283
  40. package/dist/esm/index.mjs +276 -283
  41. package/dist/esm/store/index.d.ts +3 -1
  42. package/dist/esm/store/index.d.ts.map +1 -1
  43. package/dist/esm/store/initialState.d.ts +3 -1
  44. package/dist/esm/store/initialState.d.ts.map +1 -1
  45. package/dist/esm/types/component-props.d.ts +9 -9
  46. package/dist/esm/types/component-props.d.ts.map +1 -1
  47. package/dist/esm/types/general.d.ts +79 -4
  48. package/dist/esm/types/general.d.ts.map +1 -1
  49. package/dist/esm/types/instance.d.ts +93 -0
  50. package/dist/esm/types/instance.d.ts.map +1 -1
  51. package/dist/esm/types/nodes.d.ts +3 -3
  52. package/dist/esm/types/nodes.d.ts.map +1 -1
  53. package/dist/esm/types/store.d.ts +5 -4
  54. package/dist/esm/types/store.d.ts.map +1 -1
  55. package/dist/esm/utils/changes.d.ts +1 -1
  56. package/dist/esm/utils/changes.d.ts.map +1 -1
  57. package/dist/style.css +29 -13
  58. package/dist/umd/additional-components/MiniMap/MiniMap.d.ts +1 -1
  59. package/dist/umd/additional-components/MiniMap/MiniMap.d.ts.map +1 -1
  60. package/dist/umd/additional-components/MiniMap/types.d.ts +2 -0
  61. package/dist/umd/additional-components/MiniMap/types.d.ts.map +1 -1
  62. package/dist/umd/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -1
  63. package/dist/umd/components/EdgeWrapper/index.d.ts.map +1 -1
  64. package/dist/umd/components/Handle/index.d.ts +6 -2
  65. package/dist/umd/components/Handle/index.d.ts.map +1 -1
  66. package/dist/umd/components/NodeWrapper/index.d.ts.map +1 -1
  67. package/dist/umd/components/NodesSelection/index.d.ts.map +1 -1
  68. package/dist/umd/components/ReactFlowProvider/index.d.ts +3 -1
  69. package/dist/umd/components/ReactFlowProvider/index.d.ts.map +1 -1
  70. package/dist/umd/components/StoreUpdater/index.d.ts.map +1 -1
  71. package/dist/umd/container/EdgeRenderer/MarkerDefinitions.d.ts.map +1 -1
  72. package/dist/umd/container/FlowRenderer/index.d.ts.map +1 -1
  73. package/dist/umd/container/GraphView/index.d.ts +1 -1
  74. package/dist/umd/container/GraphView/index.d.ts.map +1 -1
  75. package/dist/umd/container/ReactFlow/Wrapper.d.ts +3 -1
  76. package/dist/umd/container/ReactFlow/Wrapper.d.ts.map +1 -1
  77. package/dist/umd/container/ReactFlow/index.d.ts +3 -118
  78. package/dist/umd/container/ReactFlow/index.d.ts.map +1 -1
  79. package/dist/umd/container/ReactFlow/init-values.d.ts +4 -0
  80. package/dist/umd/container/ReactFlow/init-values.d.ts.map +1 -0
  81. package/dist/umd/hooks/useConnection.d.ts +13 -7
  82. package/dist/umd/hooks/useConnection.d.ts.map +1 -1
  83. package/dist/umd/hooks/useDrag.d.ts +1 -1
  84. package/dist/umd/hooks/useDrag.d.ts.map +1 -1
  85. package/dist/umd/hooks/useHandleConnections.d.ts +4 -4
  86. package/dist/umd/hooks/useHandleConnections.d.ts.map +1 -1
  87. package/dist/umd/hooks/useKeyPress.d.ts.map +1 -1
  88. package/dist/umd/hooks/useMoveSelectedNodes.d.ts +12 -0
  89. package/dist/umd/hooks/useMoveSelectedNodes.d.ts.map +1 -0
  90. package/dist/umd/hooks/useNodesInitialized.d.ts.map +1 -1
  91. package/dist/umd/hooks/useOnSelectionChange.d.ts +1 -1
  92. package/dist/umd/hooks/useReactFlow.d.ts.map +1 -1
  93. package/dist/umd/hooks/useUpdateNodePositions.d.ts.map +1 -1
  94. package/dist/umd/index.d.ts +1 -1
  95. package/dist/umd/index.d.ts.map +1 -1
  96. package/dist/umd/index.js +2 -2
  97. package/dist/umd/store/index.d.ts +3 -1
  98. package/dist/umd/store/index.d.ts.map +1 -1
  99. package/dist/umd/store/initialState.d.ts +3 -1
  100. package/dist/umd/store/initialState.d.ts.map +1 -1
  101. package/dist/umd/types/component-props.d.ts +9 -9
  102. package/dist/umd/types/component-props.d.ts.map +1 -1
  103. package/dist/umd/types/general.d.ts +79 -4
  104. package/dist/umd/types/general.d.ts.map +1 -1
  105. package/dist/umd/types/instance.d.ts +93 -0
  106. package/dist/umd/types/instance.d.ts.map +1 -1
  107. package/dist/umd/types/nodes.d.ts +3 -3
  108. package/dist/umd/types/nodes.d.ts.map +1 -1
  109. package/dist/umd/types/store.d.ts +5 -4
  110. package/dist/umd/types/store.d.ts.map +1 -1
  111. package/dist/umd/utils/changes.d.ts +1 -1
  112. package/dist/umd/utils/changes.d.ts.map +1 -1
  113. package/package.json +2 -2
package/dist/esm/index.js CHANGED
@@ -1,8 +1,8 @@
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
- import { errorMessages, infiniteExtent, isInputDOMNode, fitView, getViewportForBounds, pointToRendererPoint, rendererPointToPoint, isNodeBase, isEdgeBase, getElementsToRemove, isRectObject, nodeToRect, getOverlappingArea, getDimensions, XYPanZoom, PanOnScrollMode, SelectionMode, getEventPosition, getNodesInside, XYDrag, snapPosition, calcNextPosition, 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, updateNodeDimensions, updateAbsolutePositions, panBy, isMacOs, areConnectionMapsEqual, handleConnectionChange, getNodePositionWithOrigin, XYMinimap, getBoundsOfRects, ResizeControlVariant, XYResizer, XY_RESIZER_LINE_POSITIONS, XY_RESIZER_HANDLE_POSITIONS, getNodeToolbarTransform } from '@xyflow/system';
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';
7
7
  import { useStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional';
8
8
  import { shallow } from 'zustand/shallow';
@@ -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 = () => {
@@ -424,25 +433,25 @@ const useViewportHelper = () => {
424
433
  const viewport = getViewportForBounds(bounds, width, height, minZoom, maxZoom, options?.padding ?? 0.1);
425
434
  panZoom?.setViewport(viewport, { duration: options?.duration });
426
435
  },
427
- screenToFlowPosition: (position, options = { snapToGrid: true }) => {
436
+ screenToFlowPosition: (clientPosition, options = { snapToGrid: true }) => {
428
437
  const { transform, snapGrid, domNode } = store.getState();
429
438
  if (!domNode) {
430
- return position;
439
+ return clientPosition;
431
440
  }
432
441
  const { x: domX, y: domY } = domNode.getBoundingClientRect();
433
442
  const correctedPosition = {
434
- x: position.x - domX,
435
- y: position.y - domY,
443
+ x: clientPosition.x - domX,
444
+ y: clientPosition.y - domY,
436
445
  };
437
446
  return pointToRendererPoint(correctedPosition, transform, options.snapToGrid, snapGrid);
438
447
  },
439
- flowToScreenPosition: (position) => {
448
+ flowToScreenPosition: (flowPosition) => {
440
449
  const { transform, domNode } = store.getState();
441
450
  if (!domNode) {
442
- return position;
451
+ return flowPosition;
443
452
  }
444
453
  const { x: domX, y: domY } = domNode.getBoundingClientRect();
445
- const rendererPosition = rendererPointToPoint(position, transform);
454
+ const rendererPosition = rendererPointToPoint(flowPosition, transform);
446
455
  return {
447
456
  x: rendererPosition.x + domX,
448
457
  y: rendererPosition.y + domY,
@@ -458,9 +467,7 @@ function handleParentExpand(updatedElements, updateItem) {
458
467
  for (const [index, item] of updatedElements.entries()) {
459
468
  if (item.id === updateItem.parentNode) {
460
469
  const parent = { ...item };
461
- if (!parent.computed) {
462
- parent.computed = {};
463
- }
470
+ parent.computed ??= {};
464
471
  const extendWidth = updateItem.position.x + updateItem.computed.width - parent.computed.width;
465
472
  const extendHeight = updateItem.position.y + updateItem.computed.height - parent.computed.height;
466
473
  if (extendWidth > 0 || extendHeight > 0 || updateItem.position.x < 0 || updateItem.position.y < 0) {
@@ -543,7 +550,7 @@ function applyChanges(changes, elements) {
543
550
  /// each _mutate_ this object, so there's only ever one copy.
544
551
  const updatedElement = { ...element };
545
552
  for (const change of changes) {
546
- applyChange(change, updatedElement, elements);
553
+ applyChange(change, updatedElement, updatedElements);
547
554
  }
548
555
  updatedElements.push(updatedElement);
549
556
  }
@@ -638,11 +645,13 @@ function applyNodeChanges(changes, nodes) {
638
645
  function applyEdgeChanges(changes, edges) {
639
646
  return applyChanges(changes, edges);
640
647
  }
641
- const createSelectionChange = (id, selected) => ({
642
- id,
643
- type: 'select',
644
- selected,
645
- });
648
+ function createSelectionChange(id, selected) {
649
+ return {
650
+ id,
651
+ type: 'select',
652
+ selected,
653
+ };
654
+ }
646
655
  function getSelectionChanges(items, selectedIds = new Set(), mutateItem = false) {
647
656
  const changes = [];
648
657
  for (const item of items) {
@@ -721,73 +730,86 @@ function useReactFlow() {
721
730
  const { edges = [] } = store.getState();
722
731
  return edges.find((e) => e.id === id);
723
732
  }, []);
724
- // this is used to handle multiple syncronous setNodes calls
725
- const setNodesData = useRef();
726
- const setNodesTimeout = useRef();
727
- const setNodes = useCallback((payload) => {
728
- const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
729
- const nextNodes = typeof payload === 'function' ? payload(setNodesData.current || nodes) : payload;
730
- setNodesData.current = nextNodes;
731
- if (setNodesTimeout.current) {
732
- clearTimeout(setNodesTimeout.current);
733
- }
734
- // if there are multiple synchronous setNodes calls, we only want to call onNodesChange once
735
- // for this, we use a timeout to wait for the last call and store updated nodes in setNodesData
736
- // this is not perfect, but should work in most cases
737
- 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
+ }
738
762
  if (hasDefaultNodes) {
739
- setNodes(nextNodes);
763
+ setNodes(next);
740
764
  }
741
765
  else if (onNodesChange) {
742
- const changes = getElementsDiffChanges({ items: setNodesData.current, lookup: nodeLookup });
743
- onNodesChange(changes);
766
+ onNodesChange(getElementsDiffChanges({
767
+ items: next,
768
+ lookup: nodeLookup,
769
+ }));
744
770
  }
745
- setNodesData.current = undefined;
746
- }, 0);
747
- }, []);
748
- // this is used to handle multiple syncronous setEdges calls
749
- const setEdgesData = useRef();
750
- const setEdgesTimeout = useRef();
751
- const setEdges = useCallback((payload) => {
752
- const { edges = [], setEdges, hasDefaultEdges, onEdgesChange, edgeLookup } = store.getState();
753
- const nextEdges = typeof payload === 'function' ? payload(setEdgesData.current || edges) : payload;
754
- setEdgesData.current = nextEdges;
755
- if (setEdgesTimeout.current) {
756
- clearTimeout(setEdgesTimeout.current);
771
+ setElementsQueue.current.nodes = [];
757
772
  }
758
- 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
+ }
759
779
  if (hasDefaultEdges) {
760
- setEdges(nextEdges);
780
+ setEdges(next);
761
781
  }
762
782
  else if (onEdgesChange) {
763
- const changes = getElementsDiffChanges({ items: nextEdges, lookup: edgeLookup });
764
- onEdgesChange(changes);
783
+ onEdgesChange(getElementsDiffChanges({
784
+ items: next,
785
+ lookup: edgeLookup,
786
+ }));
765
787
  }
766
- setEdgesData.current = undefined;
767
- }, 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);
768
801
  }, []);
769
802
  const addNodes = useCallback((payload) => {
770
- const nodes = Array.isArray(payload) ? payload : [payload];
771
- const { nodes: currentNodes, hasDefaultNodes, onNodesChange, setNodes } = store.getState();
772
- if (hasDefaultNodes) {
773
- const nextNodes = [...currentNodes, ...nodes];
774
- setNodes(nextNodes);
775
- }
776
- else if (onNodesChange) {
777
- const changes = nodes.map((node) => ({ item: node, type: 'add' }));
778
- onNodesChange(changes);
779
- }
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);
780
808
  }, []);
781
809
  const addEdges = useCallback((payload) => {
782
- const nextEdges = Array.isArray(payload) ? payload : [payload];
783
- const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
784
- if (hasDefaultEdges) {
785
- setEdges([...edges, ...nextEdges]);
786
- }
787
- else if (onEdgesChange) {
788
- const changes = nextEdges.map((edge) => ({ item: edge, type: 'add' }));
789
- onEdgesChange(changes);
790
- }
810
+ const newEdges = Array.isArray(payload) ? payload : [payload];
811
+ setElementsQueue.current.edges.push((edges) => [...edges, ...newEdges]);
812
+ setShouldFlushQueue(true);
791
813
  }, []);
792
814
  const toObject = useCallback(() => {
793
815
  const { nodes = [], edges = [], transform } = store.getState();
@@ -1264,7 +1286,7 @@ function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, n
1264
1286
  handleNodeClick({
1265
1287
  id,
1266
1288
  store,
1267
- nodeRef: nodeRef,
1289
+ nodeRef,
1268
1290
  });
1269
1291
  },
1270
1292
  onDragStart: () => {
@@ -1298,44 +1320,47 @@ function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, n
1298
1320
 
1299
1321
  const selectedAndDraggable = (nodesDraggable) => (n) => n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'));
1300
1322
  /**
1301
- * Hook for updating node positions.
1323
+ * Hook for updating node positions by passing a direction and factor
1302
1324
  *
1303
1325
  * @internal
1304
1326
  * @returns function for updating node positions
1305
1327
  */
1306
- function useUpdateNodePositions() {
1328
+ function useMoveSelectedNodes() {
1307
1329
  const store = useStoreApi();
1308
- const updatePositions = useCallback((params) => {
1309
- const { nodeExtent, nodes, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions } = store.getState();
1330
+ const moveSelectedNodes = useCallback((params) => {
1331
+ const { nodeExtent, nodes, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions, nodeLookup, nodeOrigin, } = store.getState();
1310
1332
  const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
1311
- // by default a node moves 5px on each key press, or 20px if shift is pressed
1312
- // 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
1313
1335
  const xVelo = snapToGrid ? snapGrid[0] : 5;
1314
1336
  const yVelo = snapToGrid ? snapGrid[1] : 5;
1315
- const factor = params.isShiftPressed ? 4 : 1;
1316
- const xDiff = params.x * xVelo * factor;
1317
- const yDiff = params.y * yVelo * factor;
1337
+ const xDiff = params.direction.x * xVelo * params.factor;
1338
+ const yDiff = params.direction.y * yVelo * params.factor;
1318
1339
  const nodeUpdates = selectedNodes.map((node) => {
1319
1340
  if (node.computed?.positionAbsolute) {
1320
1341
  let nextPosition = {
1321
- x: node.computed?.positionAbsolute.x + xDiff,
1322
- y: node.computed?.positionAbsolute.y + yDiff,
1342
+ x: node.computed.positionAbsolute.x + xDiff,
1343
+ y: node.computed.positionAbsolute.y + yDiff,
1323
1344
  };
1324
1345
  if (snapToGrid) {
1325
1346
  nextPosition = snapPosition(nextPosition, snapGrid);
1326
1347
  }
1327
- const { positionAbsolute, position } = calcNextPosition(node, nextPosition, nodes, nodeExtent, undefined, onError);
1348
+ const { position, positionAbsolute } = calculateNodePosition({
1349
+ nodeId: node.id,
1350
+ nextPosition,
1351
+ nodeLookup,
1352
+ nodeExtent,
1353
+ nodeOrigin,
1354
+ onError,
1355
+ });
1328
1356
  node.position = position;
1329
- if (!node.computed) {
1330
- node.computed = {};
1331
- }
1332
1357
  node.computed.positionAbsolute = positionAbsolute;
1333
1358
  }
1334
1359
  return node;
1335
1360
  });
1336
- updateNodePositions(nodeUpdates, true, false);
1361
+ updateNodePositions(nodeUpdates);
1337
1362
  }, []);
1338
- return updatePositions;
1363
+ return moveSelectedNodes;
1339
1364
  }
1340
1365
 
1341
1366
  const NodeIdContext = createContext(null);
@@ -1470,6 +1495,9 @@ const HandleComponent = forwardRef(({ type = 'source', position = Position.Top,
1470
1495
  ]), onMouseDown: onPointerDown, onTouchStart: onPointerDown, onClick: connectOnClick ? onClick : undefined, ref: ref, ...rest, children: children }));
1471
1496
  });
1472
1497
  HandleComponent.displayName = 'Handle';
1498
+ /**
1499
+ * The Handle component is the part of a node that can be used to connect nodes.
1500
+ */
1473
1501
  const Handle = memo(HandleComponent);
1474
1502
 
1475
1503
  function InputNode({ data, isConnectable, sourcePosition = Position.Bottom }) {
@@ -1503,7 +1531,7 @@ const builtinNodeTypes = {
1503
1531
 
1504
1532
  const selector$h = (s) => {
1505
1533
  const selectedNodes = s.nodes.filter((n) => n.selected);
1506
- const { width, height, x, y } = getNodesBounds(selectedNodes, s.nodeOrigin);
1534
+ const { width, height, x, y } = getNodesBounds(selectedNodes, { nodeOrigin: s.nodeOrigin });
1507
1535
  return {
1508
1536
  width,
1509
1537
  height,
@@ -1514,7 +1542,7 @@ const selector$h = (s) => {
1514
1542
  function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboardA11y }) {
1515
1543
  const store = useStoreApi();
1516
1544
  const { width, height, transformString, userSelectionActive } = useStore(selector$h, shallow);
1517
- const updatePositions = useUpdateNodePositions();
1545
+ const moveSelectedNodes = useMoveSelectedNodes();
1518
1546
  const nodeRef = useRef(null);
1519
1547
  useEffect(() => {
1520
1548
  if (!disableKeyboardA11y) {
@@ -1537,10 +1565,9 @@ function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboar
1537
1565
  : undefined;
1538
1566
  const onKeyDown = (event) => {
1539
1567
  if (Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) {
1540
- updatePositions({
1541
- x: arrowKeyDiffs[event.key].x,
1542
- y: arrowKeyDiffs[event.key].y,
1543
- isShiftPressed: event.shiftKey,
1568
+ moveSelectedNodes({
1569
+ direction: arrowKeyDiffs[event.key],
1570
+ factor: event.shiftKey ? 4 : 1,
1544
1571
  });
1545
1572
  }
1546
1573
  };
@@ -1552,14 +1579,16 @@ function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboar
1552
1579
  } }) }));
1553
1580
  }
1554
1581
 
1555
- const selector$g = (s) => s.nodesSelectionActive;
1582
+ const selector$g = (s) => {
1583
+ return { nodesSelectionActive: s.nodesSelectionActive, userSelectionActive: s.userSelectionActive };
1584
+ };
1556
1585
  const FlowRendererComponent = ({ children, onPaneClick, onPaneMouseEnter, onPaneMouseMove, onPaneMouseLeave, onPaneContextMenu, onPaneScroll, deleteKeyCode, selectionKeyCode, selectionOnDrag, selectionMode, onSelectionStart, onSelectionEnd, multiSelectionKeyCode, panActivationKeyCode, zoomActivationKeyCode, elementsSelectable, zoomOnScroll, zoomOnPinch, panOnScroll: _panOnScroll, panOnScrollSpeed, panOnScrollMode, zoomOnDoubleClick, panOnDrag: _panOnDrag, defaultViewport, translateExtent, minZoom, maxZoom, preventScrolling, onSelectionContextMenu, noWheelClassName, noPanClassName, disableKeyboardA11y, onViewportChange, isControlledViewport, }) => {
1557
- const nodesSelectionActive = useStore(selector$g);
1586
+ const { nodesSelectionActive, userSelectionActive } = useStore(selector$g);
1558
1587
  const selectionKeyPressed = useKeyPress(selectionKeyCode);
1559
1588
  const panActivationKeyPressed = useKeyPress(panActivationKeyCode);
1560
1589
  const panOnDrag = panActivationKeyPressed || _panOnDrag;
1561
1590
  const panOnScroll = panActivationKeyPressed || _panOnScroll;
1562
- const isSelecting = selectionKeyPressed || (selectionOnDrag && panOnDrag !== true);
1591
+ const isSelecting = selectionKeyPressed || userSelectionActive || (selectionOnDrag && panOnDrag !== true);
1563
1592
  useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode });
1564
1593
  return (jsx(ZoomPane, { onPaneContextMenu: onPaneContextMenu, elementsSelectable: elementsSelectable, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, panOnScroll: panOnScroll, panOnScrollSpeed: panOnScrollSpeed, panOnScrollMode: panOnScrollMode, zoomOnDoubleClick: zoomOnDoubleClick, panOnDrag: !selectionKeyPressed && panOnDrag, defaultViewport: defaultViewport, translateExtent: translateExtent, minZoom: minZoom, maxZoom: maxZoom, zoomActivationKeyCode: zoomActivationKeyCode, preventScrolling: preventScrolling, noWheelClassName: noWheelClassName, noPanClassName: noPanClassName, onViewportChange: onViewportChange, isControlledViewport: isControlledViewport, children: jsxs(Pane, { onSelectionStart: onSelectionStart, onSelectionEnd: onSelectionEnd, onPaneClick: onPaneClick, onPaneMouseEnter: onPaneMouseEnter, onPaneMouseMove: onPaneMouseMove, onPaneMouseLeave: onPaneMouseLeave, onPaneContextMenu: onPaneContextMenu, onPaneScroll: onPaneScroll, panOnDrag: panOnDrag, isSelecting: !!isSelecting, selectionMode: selectionMode, children: [children, nodesSelectionActive && (jsx(NodesSelection, { onSelectionContextMenu: onSelectionContextMenu, noPanClassName: noPanClassName, disableKeyboardA11y: disableKeyboardA11y }))] }) }));
1565
1594
  };
@@ -1646,14 +1675,29 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1646
1675
  const prevSourcePosition = useRef(node.sourcePosition);
1647
1676
  const prevTargetPosition = useRef(node.targetPosition);
1648
1677
  const prevType = useRef(nodeType);
1649
- 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
+ }, []);
1650
1692
  useEffect(() => {
1651
1693
  if (nodeRef.current && !node.hidden) {
1652
1694
  const currNode = nodeRef.current;
1653
- resizeObserver?.observe(currNode);
1654
- return () => resizeObserver?.unobserve(currNode);
1695
+ if (!initialized || !hasHandleBounds) {
1696
+ resizeObserver?.unobserve(currNode);
1697
+ resizeObserver?.observe(currNode);
1698
+ }
1655
1699
  }
1656
- }, [node.hidden]);
1700
+ }, [node.hidden, initialized, hasHandleBounds]);
1657
1701
  useEffect(() => {
1658
1702
  // when the user programmatically changes the source or handle position, we re-initialize the node
1659
1703
  const typeChanged = prevType.current !== nodeType;
@@ -1683,10 +1727,6 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1683
1727
  if (node.hidden) {
1684
1728
  return null;
1685
1729
  }
1686
- const width = node.width ?? undefined;
1687
- const height = node.height ?? undefined;
1688
- const computedWidth = node.computed?.width;
1689
- const computedHeight = node.computed?.height;
1690
1730
  const positionAbsoluteOrigin = getPositionWithOrigin({
1691
1731
  x: positionAbsoluteX,
1692
1732
  y: positionAbsoluteY,
@@ -1694,7 +1734,6 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1694
1734
  height: computedHeight ?? height ?? 0,
1695
1735
  origin: node.origin || nodeOrigin,
1696
1736
  });
1697
- const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
1698
1737
  const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
1699
1738
  const onMouseEnterHandler = onMouseEnter ? (event) => onMouseEnter(event, { ...node }) : undefined;
1700
1739
  const onMouseMoveHandler = onMouseMove ? (event) => onMouseMove(event, { ...node }) : undefined;
@@ -1738,10 +1777,9 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1738
1777
  .replace('Arrow', '')
1739
1778
  .toLowerCase()}. New position, x: ${~~positionAbsoluteX}, y: ${~~positionAbsoluteY}`,
1740
1779
  });
1741
- updatePositions({
1742
- x: arrowKeyDiffs[event.key].x,
1743
- y: arrowKeyDiffs[event.key].y,
1744
- isShiftPressed: event.shiftKey,
1780
+ moveSelectedNodes({
1781
+ direction: arrowKeyDiffs[event.key],
1782
+ factor: event.shiftKey ? 4 : 1,
1745
1783
  });
1746
1784
  }
1747
1785
  };
@@ -1884,18 +1922,21 @@ const Marker = ({ id, type, color, width = 12.5, height = 12.5, markerUnits = 's
1884
1922
  }
1885
1923
  return (jsx("marker", { className: "react-flow__arrowhead", id: id, markerWidth: `${width}`, markerHeight: `${height}`, viewBox: "-10 -10 20 20", markerUnits: markerUnits, orient: orient, refX: "0", refY: "0", children: jsx(Symbol, { color: color, strokeWidth: strokeWidth }) }));
1886
1924
  };
1887
- const markerSelector = ({ defaultColor, rfId }) => (s) => {
1888
- const markers = createMarkerIds(s.edges, { id: rfId, defaultColor });
1889
- return markers;
1890
- };
1891
- const markersEqual = (a, b) =>
1892
- // the id includes all marker options, so we just need to look at that part of the marker
1893
- !(a.length !== b.length || a.some((m, i) => m.id !== b[i].id));
1894
1925
  // when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
1895
1926
  // when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
1896
1927
  // that we can then use for creating our unique marker ids
1897
1928
  const MarkerDefinitions = ({ defaultColor, rfId }) => {
1898
- const markers = useStore(useCallback(markerSelector({ defaultColor, rfId }), [defaultColor, rfId]), markersEqual);
1929
+ const edges = useStore((s) => s.edges);
1930
+ const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions);
1931
+ const markers = useMemo(() => {
1932
+ const markers = createMarkerIds(edges, {
1933
+ id: rfId,
1934
+ defaultColor,
1935
+ defaultMarkerStart: defaultEdgeOptions?.markerStart,
1936
+ defaultMarkerEnd: defaultEdgeOptions?.markerEnd,
1937
+ });
1938
+ return markers;
1939
+ }, [edges, defaultEdgeOptions, rfId, defaultColor]);
1899
1940
  if (!markers.length) {
1900
1941
  return null;
1901
1942
  }
@@ -2190,8 +2231,8 @@ function EdgeWrapper({ id, edgesFocusable, edgesUpdatable, elementsSelectable, o
2190
2231
  ...(edgePosition || nullPosition),
2191
2232
  };
2192
2233
  }, [edge.source, edge.target, edge.sourceHandle, edge.targetHandle, edge.selected, edge.zIndex]), shallow);
2193
- const markerStartUrl = useMemo(() => (edge.markerStart ? `url(#${getMarkerId(edge.markerStart, rfId)})` : undefined), [edge.markerStart, rfId]);
2194
- 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]);
2195
2236
  if (edge.hidden || !sourceX || !sourceY || !targetX || !targetY) {
2196
2237
  return null;
2197
2238
  }
@@ -2259,6 +2300,7 @@ function EdgeWrapper({ id, edgesFocusable, edgesUpdatable, elementsSelectable, o
2259
2300
  animated: edge.animated,
2260
2301
  inactive: !isSelectable && !onClick,
2261
2302
  updating: updateHover,
2303
+ selectable: isSelectable,
2262
2304
  },
2263
2305
  ]), onClick: onEdgeClick, onDoubleClick: onEdgeDoubleClick, onContextMenu: onEdgeContextMenu, onMouseEnter: onEdgeMouseEnter, onMouseMove: onEdgeMouseMove, onMouseLeave: onEdgeMouseLeave, onKeyDown: isFocusable ? onKeyDown : undefined, tabIndex: isFocusable ? 0 : undefined, role: isFocusable ? 'button' : 'img', "data-id": id, "data-testid": `rf__edge-${id}`, "aria-label": edge.ariaLabel === null ? undefined : edge.ariaLabel || `Edge from ${edge.source} to ${edge.target}`, "aria-describedby": isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined, ref: edgeRef, children: [!updating && (jsx(EdgeComponent, { id: id, source: edge.source, target: edge.target, selected: edge.selected, animated: edge.animated, label: edge.label, labelStyle: edge.labelStyle, labelShowBg: edge.labelShowBg, labelBgStyle: edge.labelBgStyle, labelBgPadding: edge.labelBgPadding, labelBgBorderRadius: edge.labelBgBorderRadius, sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY, sourcePosition: sourcePosition, targetPosition: targetPosition, data: edge.data, style: edge.style, sourceHandleId: edge.sourceHandle, targetHandleId: edge.targetHandle, markerStart: markerStartUrl, markerEnd: markerEndUrl, pathOptions: 'pathOptions' in edge ? edge.pathOptions : undefined, interactionWidth: edge.interactionWidth })), isUpdatable && (jsx(EdgeUpdateAnchors, { edge: edge, isUpdatable: isUpdatable, edgeUpdaterRadius: edgeUpdaterRadius, onEdgeUpdate: onEdgeUpdate, onEdgeUpdateStart: onEdgeUpdateStart, onEdgeUpdateEnd: onEdgeUpdateEnd, sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY, sourcePosition: sourcePosition, targetPosition: targetPosition, setUpdateHover: setUpdateHover, setUpdating: setUpdating, sourceHandleId: edge.sourceHandle, targetHandleId: edge.targetHandle }))] }) }));
2264
2306
  }
@@ -2431,44 +2473,22 @@ function GraphViewComponent({ nodeTypes, edgeTypes, onInit, onNodeClick, onEdgeC
2431
2473
  GraphViewComponent.displayName = 'GraphView';
2432
2474
  const GraphView = memo(GraphViewComponent);
2433
2475
 
2434
- function handleControlledSelectionChange(changes, items) {
2435
- return items.map((item) => {
2436
- const change = changes.find((change) => change.id === item.id);
2437
- if (change) {
2438
- item.selected = change.selected;
2439
- }
2440
- return item;
2441
- });
2442
- }
2443
- function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get, set }) {
2444
- const { nodes, edges, onNodesChange, onEdgesChange, hasDefaultNodes, hasDefaultEdges } = get();
2445
- if (changedNodes?.length) {
2446
- if (hasDefaultNodes) {
2447
- set({ nodes: handleControlledSelectionChange(changedNodes, nodes) });
2448
- }
2449
- onNodesChange?.(changedNodes);
2450
- }
2451
- if (changedEdges?.length) {
2452
- if (hasDefaultEdges) {
2453
- set({ edges: handleControlledSelectionChange(changedEdges, edges) });
2454
- }
2455
- onEdgesChange?.(changedEdges);
2456
- }
2457
- }
2458
-
2459
- const getInitialState = ({ nodes = [], edges = [], width, height, fitView, } = {}) => {
2476
+ const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, height, fitView, } = {}) => {
2460
2477
  const nodeLookup = new Map();
2461
2478
  const connectionLookup = new Map();
2462
2479
  const edgeLookup = new Map();
2463
- updateConnectionLookup(connectionLookup, edgeLookup, edges);
2464
- 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, {
2465
2484
  nodeOrigin: [0, 0],
2466
2485
  elevateNodesOnSelect: false,
2467
2486
  });
2468
2487
  let transform = [0, 0, 1];
2469
2488
  if (fitView && width && height) {
2470
2489
  const nodesWithDimensions = nextNodes.filter((node) => node.width && node.height);
2471
- const bounds = getNodesBounds(nodesWithDimensions, [0, 0]);
2490
+ // @todo users nodeOrigin should be used here
2491
+ const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] });
2472
2492
  const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
2473
2493
  transform = [x, y, zoom];
2474
2494
  }
@@ -2479,13 +2499,13 @@ const getInitialState = ({ nodes = [], edges = [], width, height, fitView, } = {
2479
2499
  transform,
2480
2500
  nodes: nextNodes,
2481
2501
  nodeLookup,
2482
- edges,
2502
+ edges: storeEdges,
2483
2503
  edgeLookup,
2484
2504
  connectionLookup,
2485
2505
  onNodesChange: null,
2486
2506
  onEdgesChange: null,
2487
- hasDefaultNodes: false,
2488
- hasDefaultEdges: false,
2507
+ hasDefaultNodes: defaultNodes !== undefined,
2508
+ hasDefaultEdges: defaultEdges !== undefined,
2489
2509
  panZoom: null,
2490
2510
  minZoom: 0.5,
2491
2511
  maxZoom: 2,
@@ -2525,15 +2545,15 @@ const getInitialState = ({ nodes = [], edges = [], width, height, fitView, } = {
2525
2545
  autoPanOnConnect: true,
2526
2546
  autoPanOnNodeDrag: true,
2527
2547
  connectionRadius: 20,
2528
- onError: () => null,
2548
+ onError: devWarn,
2529
2549
  isValidConnection: undefined,
2530
2550
  onSelectionChangeHandlers: [],
2531
2551
  lib: 'react',
2532
2552
  };
2533
2553
  };
2534
2554
 
2535
- const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) => createWithEqualityFn((set, get) => ({
2536
- ...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 }),
2537
2557
  setNodes: (nodes) => {
2538
2558
  const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
2539
2559
  // setNodes() is called exclusively in response to user actions:
@@ -2542,7 +2562,6 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2542
2562
  //
2543
2563
  // When this happens, we take the note objects passed by the user and extend them with fields
2544
2564
  // relevant for internal React Flow operations.
2545
- // TODO: consider updating the types to reflect the distinction between user-provided nodes and internal nodes.
2546
2565
  const nodesWithInternalData = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
2547
2566
  set({ nodes: nodesWithInternalData });
2548
2567
  },
@@ -2551,28 +2570,17 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2551
2570
  updateConnectionLookup(connectionLookup, edgeLookup, edges);
2552
2571
  set({ edges });
2553
2572
  },
2554
- // when the user works with an uncontrolled flow,
2555
- // we set a flag `hasDefaultNodes` / `hasDefaultEdges`
2556
2573
  setDefaultNodesAndEdges: (nodes, edges) => {
2557
- const hasDefaultNodes = typeof nodes !== 'undefined';
2558
- const hasDefaultEdges = typeof edges !== 'undefined';
2559
- const nextState = {
2560
- hasDefaultNodes,
2561
- hasDefaultEdges,
2562
- };
2563
- if (hasDefaultNodes) {
2564
- const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
2565
- nextState.nodes = adoptUserProvidedNodes(nodes, nodeLookup, {
2566
- nodeOrigin,
2567
- elevateNodesOnSelect,
2568
- });
2574
+ if (nodes) {
2575
+ const { setNodes } = get();
2576
+ setNodes(nodes);
2577
+ set({ hasDefaultNodes: true });
2569
2578
  }
2570
- if (hasDefaultEdges) {
2571
- const { connectionLookup, edgeLookup } = get();
2572
- updateConnectionLookup(connectionLookup, edgeLookup, edges);
2573
- nextState.edges = edges;
2579
+ if (edges) {
2580
+ const { setEdges } = get();
2581
+ setEdges(edges);
2582
+ set({ hasDefaultEdges: true });
2574
2583
  }
2575
- set(nextState);
2576
2584
  },
2577
2585
  // Every node gets registerd at a ResizeObserver. Whenever a node
2578
2586
  // changes its dimensions, this function is called to measure the
@@ -2609,86 +2617,70 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2609
2617
  onNodesChange?.(changes);
2610
2618
  }
2611
2619
  },
2612
- updateNodePositions: (nodeDragItems, positionChanged = true, dragging = false) => {
2620
+ updateNodePositions: (nodeDragItems, dragging = false) => {
2613
2621
  const changes = nodeDragItems.map((node) => {
2614
2622
  const change = {
2615
2623
  id: node.id,
2616
2624
  type: 'position',
2625
+ position: node.position,
2626
+ positionAbsolute: node.computed?.positionAbsolute,
2617
2627
  dragging,
2618
2628
  };
2619
- if (positionChanged) {
2620
- change.positionAbsolute = node.computed?.positionAbsolute;
2621
- change.position = node.position;
2622
- }
2623
2629
  return change;
2624
2630
  });
2625
2631
  get().triggerNodeChanges(changes);
2626
2632
  },
2627
2633
  triggerNodeChanges: (changes) => {
2628
- const { onNodesChange, nodeLookup, nodes, hasDefaultNodes, nodeOrigin, elevateNodesOnSelect } = get();
2634
+ const { onNodesChange, setNodes, nodes, hasDefaultNodes } = get();
2629
2635
  if (changes?.length) {
2630
2636
  if (hasDefaultNodes) {
2631
2637
  const updatedNodes = applyNodeChanges(changes, nodes);
2632
- const nextNodes = adoptUserProvidedNodes(updatedNodes, nodeLookup, {
2633
- nodeOrigin,
2634
- elevateNodesOnSelect,
2635
- });
2636
- set({ nodes: nextNodes });
2638
+ setNodes(updatedNodes);
2637
2639
  }
2638
2640
  onNodesChange?.(changes);
2639
2641
  }
2640
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
+ },
2641
2653
  addSelectedNodes: (selectedNodeIds) => {
2642
- const { multiSelectionActive, edges, nodes } = get();
2643
- let changedNodes;
2644
- let changedEdges = null;
2654
+ const { multiSelectionActive, edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
2645
2655
  if (multiSelectionActive) {
2646
- changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
2656
+ const nodeChanges = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
2657
+ triggerNodeChanges(nodeChanges);
2658
+ return;
2647
2659
  }
2648
- else {
2649
- changedNodes = getSelectionChanges(nodes, new Set([...selectedNodeIds]), true);
2650
- changedEdges = getSelectionChanges(edges);
2651
- }
2652
- updateNodesAndEdgesSelections({
2653
- changedNodes,
2654
- changedEdges,
2655
- get,
2656
- set,
2657
- });
2660
+ triggerNodeChanges(getSelectionChanges(nodes, new Set([...selectedNodeIds]), true));
2661
+ triggerEdgeChanges(getSelectionChanges(edges));
2658
2662
  },
2659
2663
  addSelectedEdges: (selectedEdgeIds) => {
2660
- const { multiSelectionActive, edges, nodes } = get();
2661
- let changedEdges;
2662
- let changedNodes = null;
2664
+ const { multiSelectionActive, edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
2663
2665
  if (multiSelectionActive) {
2664
- changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
2666
+ const changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
2667
+ triggerEdgeChanges(changedEdges);
2668
+ return;
2665
2669
  }
2666
- else {
2667
- changedEdges = getSelectionChanges(edges, new Set([...selectedEdgeIds]));
2668
- changedNodes = getSelectionChanges(nodes, new Set(), true);
2669
- }
2670
- updateNodesAndEdgesSelections({
2671
- changedNodes,
2672
- changedEdges,
2673
- get,
2674
- set,
2675
- });
2670
+ triggerEdgeChanges(getSelectionChanges(edges, new Set([...selectedEdgeIds])));
2671
+ triggerNodeChanges(getSelectionChanges(nodes, new Set(), true));
2676
2672
  },
2677
2673
  unselectNodesAndEdges: ({ nodes, edges } = {}) => {
2678
- const { edges: storeEdges, nodes: storeNodes } = get();
2674
+ const { edges: storeEdges, nodes: storeNodes, triggerNodeChanges, triggerEdgeChanges } = get();
2679
2675
  const nodesToUnselect = nodes ? nodes : storeNodes;
2680
2676
  const edgesToUnselect = edges ? edges : storeEdges;
2681
- const changedNodes = nodesToUnselect.map((n) => {
2677
+ const nodeChanges = nodesToUnselect.map((n) => {
2682
2678
  n.selected = false;
2683
2679
  return createSelectionChange(n.id, false);
2684
2680
  });
2685
- const changedEdges = edgesToUnselect.map((edge) => createSelectionChange(edge.id, false));
2686
- updateNodesAndEdgesSelections({
2687
- changedNodes,
2688
- changedEdges,
2689
- get,
2690
- set,
2691
- });
2681
+ const edgeChanges = edgesToUnselect.map((edge) => createSelectionChange(edge.id, false));
2682
+ triggerNodeChanges(nodeChanges);
2683
+ triggerEdgeChanges(edgeChanges);
2692
2684
  },
2693
2685
  setMinZoom: (minZoom) => {
2694
2686
  const { panZoom, maxZoom } = get();
@@ -2705,19 +2697,11 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2705
2697
  set({ translateExtent });
2706
2698
  },
2707
2699
  resetSelectedElements: () => {
2708
- const { edges, nodes } = get();
2709
- const nodesToUnselect = nodes
2710
- .filter((e) => e.selected)
2711
- .map((n) => createSelectionChange(n.id, false));
2712
- const edgesToUnselect = edges
2713
- .filter((e) => e.selected)
2714
- .map((e) => createSelectionChange(e.id, false));
2715
- updateNodesAndEdgesSelections({
2716
- changedNodes: nodesToUnselect,
2717
- changedEdges: edgesToUnselect,
2718
- get,
2719
- set,
2720
- });
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);
2721
2705
  },
2722
2706
  setNodeExtent: (nodeExtent) => {
2723
2707
  const { nodes } = get();
@@ -2769,21 +2753,17 @@ const createRFStore = ({ nodes, edges, width, height, fitView: fitView$1, }) =>
2769
2753
  };
2770
2754
  set(currentConnection);
2771
2755
  },
2772
- reset: () => {
2773
- // @todo: what should we do about this? Do we still need it?
2774
- // if you are on a SPA with multiple flows, we want to make sure that the store gets resetted
2775
- // when you switch pages. Does this reset solves this? Currently it always gets called. This
2776
- // leads to an emtpy nodes array at the beginning.
2777
- // set({ ...getInitialState() });
2778
- },
2756
+ reset: () => set({ ...getInitialState() }),
2779
2757
  }), Object.is);
2780
2758
 
2781
- function ReactFlowProvider({ children, initialNodes, initialEdges, initialWidth, initialHeight, fitView, }) {
2759
+ function ReactFlowProvider({ children, initialNodes, initialEdges, defaultNodes, defaultEdges, initialWidth, initialHeight, fitView, }) {
2782
2760
  const storeRef = useRef(null);
2783
2761
  if (!storeRef.current) {
2784
2762
  storeRef.current = createRFStore({
2785
2763
  nodes: initialNodes,
2786
2764
  edges: initialEdges,
2765
+ defaultNodes,
2766
+ defaultEdges,
2787
2767
  width: initialWidth,
2788
2768
  height: initialHeight,
2789
2769
  fitView,
@@ -2792,18 +2772,16 @@ function ReactFlowProvider({ children, initialNodes, initialEdges, initialWidth,
2792
2772
  return jsx(Provider$1, { value: storeRef.current, children: children });
2793
2773
  }
2794
2774
 
2795
- function Wrapper({ children, nodes, edges, width, height, fitView, }) {
2775
+ function Wrapper({ children, nodes, edges, defaultNodes, defaultEdges, width, height, fitView, }) {
2796
2776
  const isWrapped = useContext(StoreContext);
2797
2777
  if (isWrapped) {
2798
2778
  // we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
2799
2779
  // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
2800
2780
  return jsx(Fragment, { children: children });
2801
2781
  }
2802
- 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 }));
2803
2783
  }
2804
2784
 
2805
- const initNodeOrigin = [0, 0];
2806
- const initDefaultViewport = { x: 0, y: 0, zoom: 1 };
2807
2785
  const wrapperStyle = {
2808
2786
  width: '100%',
2809
2787
  height: '100%',
@@ -2811,10 +2789,10 @@ const wrapperStyle = {
2811
2789
  position: 'relative',
2812
2790
  zIndex: 0,
2813
2791
  };
2814
- 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) => {
2815
2793
  const rfId = id || '1';
2816
2794
  const colorModeClassName = useColorModeClass(colorMode);
2817
- 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 })] }) }));
2818
2796
  });
2819
2797
  ReactFlow.displayName = 'ReactFlow';
2820
2798
 
@@ -2948,7 +2926,7 @@ function useOnViewportChange({ onStart, onChange, onEnd }) {
2948
2926
  * Hook for registering an onSelectionChange handler.
2949
2927
  *
2950
2928
  * @public
2951
- * @params params.onChange - The handler to register
2929
+ * @param params.onChange - The handler to register
2952
2930
  */
2953
2931
  function useOnSelectionChange({ onChange }) {
2954
2932
  const store = useStoreApi();
@@ -2966,9 +2944,14 @@ const selector$6 = (options) => (s) => {
2966
2944
  if (s.nodes.length === 0) {
2967
2945
  return false;
2968
2946
  }
2969
- return s.nodes
2970
- .filter((n) => (options.includeHiddenNodes ? true : !n.hidden))
2971
- .every((n) => n[internalsSymbol]?.handleBounds !== undefined);
2947
+ for (const node of s.nodes) {
2948
+ if (options.includeHiddenNodes || !node.hidden) {
2949
+ if (node[internalsSymbol]?.handleBounds === undefined) {
2950
+ return false;
2951
+ }
2952
+ }
2953
+ }
2954
+ return true;
2972
2955
  };
2973
2956
  const defaultOptions = {
2974
2957
  includeHiddenNodes: false,
@@ -2986,7 +2969,7 @@ function useNodesInitialized(options = defaultOptions) {
2986
2969
  }
2987
2970
 
2988
2971
  /**
2989
- * Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
2972
+ * Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
2990
2973
  *
2991
2974
  * @public
2992
2975
  * @param param.type - handle type 'source' or 'target'
@@ -2994,12 +2977,12 @@ function useNodesInitialized(options = defaultOptions) {
2994
2977
  * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type)
2995
2978
  * @param param.onConnect - gets called when a connection is established
2996
2979
  * @param param.onDisconnect - gets called when a connection is removed
2997
- * @returns an array with connections
2980
+ * @returns an array with handle connections
2998
2981
  */
2999
2982
  function useHandleConnections({ type, id = null, nodeId, onConnect, onDisconnect, }) {
3000
2983
  const _nodeId = useNodeId();
2984
+ const currentNodeId = nodeId ?? _nodeId;
3001
2985
  const prevConnections = useRef(null);
3002
- const currentNodeId = nodeId || _nodeId;
3003
2986
  const connections = useStore((state) => state.connectionLookup.get(`${currentNodeId}-${type}-${id}`), areConnectionMapsEqual);
3004
2987
  useEffect(() => {
3005
2988
  // @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts
@@ -3041,7 +3024,7 @@ const selector$5 = (s) => ({
3041
3024
  * Hook for accessing the ongoing connection.
3042
3025
  *
3043
3026
  * @public
3044
- * @returns ongoing connection: startHandle, endHandle, status, position
3027
+ * @returns ongoing connection
3045
3028
  */
3046
3029
  function useConnection() {
3047
3030
  const ongoingConnection = useStore(selector$5, shallow);
@@ -3214,7 +3197,7 @@ const selector$1 = (s) => {
3214
3197
  };
3215
3198
  return {
3216
3199
  viewBB,
3217
- boundingRect: s.nodes.length > 0 ? getBoundsOfRects(getNodesBounds(s.nodes, s.nodeOrigin), viewBB) : viewBB,
3200
+ boundingRect: s.nodes.length > 0 ? getBoundsOfRects(getNodesBounds(s.nodes, { nodeOrigin: s.nodeOrigin }), viewBB) : viewBB,
3218
3201
  rfId: s.rfId,
3219
3202
  nodeOrigin: s.nodeOrigin,
3220
3203
  panZoom: s.panZoom,
@@ -3227,7 +3210,7 @@ const ARIA_LABEL_KEY = 'react-flow__minimap-desc';
3227
3210
  function MiniMapComponent({ style, className, nodeStrokeColor, nodeColor, nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth,
3228
3211
  // We need to rename the prop to be `CapitalCase` so that JSX will render it as
3229
3212
  // a component properly.
3230
- 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, }) {
3231
3214
  const store = useStoreApi();
3232
3215
  const svg = useRef(null);
3233
3216
  const { boundingRect, viewBB, rfId, panZoom, translateExtent, flowWidth, flowHeight } = useStore(selector$1, shallow);
@@ -3285,12 +3268,15 @@ nodeComponent, maskColor, maskStrokeColor = 'none', maskStrokeWidth = 1, positio
3285
3268
  : undefined;
3286
3269
  return (jsx(Panel, { position: position, style: {
3287
3270
  ...style,
3288
- '--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,
3289
3275
  '--xy-minimap-node-background-color-props': typeof nodeColor === 'string' ? nodeColor : undefined,
3290
3276
  '--xy-minimap-node-stroke-color-props': typeof nodeStrokeColor === 'string' ? nodeStrokeColor : undefined,
3291
3277
  '--xy-minimap-node-stroke-width-props': typeof nodeStrokeWidth === 'string' ? nodeStrokeWidth : undefined,
3292
- }, 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
3293
- 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" })] }) }));
3294
3280
  }
3295
3281
  MiniMapComponent.displayName = 'MiniMap';
3296
3282
  const MiniMap = memo(MiniMapComponent);
@@ -3320,7 +3306,7 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
3320
3306
  snapToGrid,
3321
3307
  };
3322
3308
  },
3323
- onChange: (change) => {
3309
+ onChange: (change, childChanges) => {
3324
3310
  const { triggerNodeChanges } = store.getState();
3325
3311
  const changes = [];
3326
3312
  if (change.isXPosChange || change.isYPosChange) {
@@ -3346,6 +3332,13 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
3346
3332
  };
3347
3333
  changes.push(dimensionChange);
3348
3334
  }
3335
+ for (const childChange of childChanges) {
3336
+ const positionChange = {
3337
+ ...childChange,
3338
+ type: 'position',
3339
+ };
3340
+ changes.push(positionChange);
3341
+ }
3349
3342
  triggerNodeChanges(changes);
3350
3343
  },
3351
3344
  onEnd: () => {
@@ -3450,7 +3443,7 @@ function NodeToolbar({ nodeId, children, className, style, isVisible, position =
3450
3443
  if (!isActive || !nodes.length) {
3451
3444
  return null;
3452
3445
  }
3453
- const nodeRect = getNodesBounds(nodes, nodeOrigin);
3446
+ const nodeRect = getNodesBounds(nodes, { nodeOrigin });
3454
3447
  const zIndex = Math.max(...nodes.map((node) => (node[internalsSymbol]?.z || 1) + 1));
3455
3448
  const wrapperStyle = {
3456
3449
  position: 'absolute',