@xyflow/react 12.0.0-next.14 → 12.0.0-next.16

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 (70) hide show
  1. package/dist/esm/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -1
  2. package/dist/esm/components/BatchProvider/index.d.ts +17 -0
  3. package/dist/esm/components/BatchProvider/index.d.ts.map +1 -0
  4. package/dist/esm/components/BatchProvider/types.d.ts +7 -0
  5. package/dist/esm/components/BatchProvider/types.d.ts.map +1 -0
  6. package/dist/esm/components/BatchProvider/useQueue.d.ts +11 -0
  7. package/dist/esm/components/BatchProvider/useQueue.d.ts.map +1 -0
  8. package/dist/esm/components/BatchProvider/utils.d.ts +3 -0
  9. package/dist/esm/components/BatchProvider/utils.d.ts.map +1 -0
  10. package/dist/esm/components/ElementBatcher/index.d.ts +3 -0
  11. package/dist/esm/components/ElementBatcher/index.d.ts.map +1 -0
  12. package/dist/esm/components/NodeWrapper/index.d.ts.map +1 -1
  13. package/dist/esm/components/NodeWrapper/useNodeObservation.d.ts +9 -0
  14. package/dist/esm/components/NodeWrapper/useNodeObservation.d.ts.map +1 -0
  15. package/dist/esm/components/NodeWrapper/useNodeObserver.d.ts +15 -0
  16. package/dist/esm/components/NodeWrapper/useNodeObserver.d.ts.map +1 -0
  17. package/dist/esm/components/NodesSelection/index.d.ts.map +1 -1
  18. package/dist/esm/components/ReactFlowProvider/index.d.ts +4 -3
  19. package/dist/esm/components/ReactFlowProvider/index.d.ts.map +1 -1
  20. package/dist/esm/components/SelectionListener/index.d.ts.map +1 -1
  21. package/dist/esm/container/NodeRenderer/useResizeObserver.d.ts.map +1 -1
  22. package/dist/esm/container/Pane/index.d.ts.map +1 -1
  23. package/dist/esm/contexts/RFStoreContext.d.ts +2 -2
  24. package/dist/esm/contexts/RFStoreContext.d.ts.map +1 -1
  25. package/dist/esm/contexts/StoreContext.d.ts +5 -0
  26. package/dist/esm/contexts/StoreContext.d.ts.map +1 -0
  27. package/dist/esm/hooks/useElementBatching.d.ts +3 -0
  28. package/dist/esm/hooks/useElementBatching.d.ts.map +1 -0
  29. package/dist/esm/hooks/useNodesInitialized.d.ts.map +1 -1
  30. package/dist/esm/hooks/useReactFlow.d.ts.map +1 -1
  31. package/dist/esm/hooks/useStore.d.ts +0 -1
  32. package/dist/esm/hooks/useStore.d.ts.map +1 -1
  33. package/dist/esm/index.js +324 -228
  34. package/dist/esm/index.mjs +324 -228
  35. package/dist/esm/store/index.d.ts +2 -2
  36. package/dist/esm/store/index.d.ts.map +1 -1
  37. package/dist/esm/store/initialState.d.ts.map +1 -1
  38. package/dist/esm/types/store.d.ts +1 -0
  39. package/dist/esm/types/store.d.ts.map +1 -1
  40. package/dist/umd/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -1
  41. package/dist/umd/components/BatchProvider/index.d.ts +17 -0
  42. package/dist/umd/components/BatchProvider/index.d.ts.map +1 -0
  43. package/dist/umd/components/BatchProvider/types.d.ts +7 -0
  44. package/dist/umd/components/BatchProvider/types.d.ts.map +1 -0
  45. package/dist/umd/components/BatchProvider/useQueue.d.ts +11 -0
  46. package/dist/umd/components/BatchProvider/useQueue.d.ts.map +1 -0
  47. package/dist/umd/components/ElementBatcher/index.d.ts +3 -0
  48. package/dist/umd/components/ElementBatcher/index.d.ts.map +1 -0
  49. package/dist/umd/components/NodeWrapper/index.d.ts.map +1 -1
  50. package/dist/umd/components/NodeWrapper/useNodeObserver.d.ts +15 -0
  51. package/dist/umd/components/NodeWrapper/useNodeObserver.d.ts.map +1 -0
  52. package/dist/umd/components/NodesSelection/index.d.ts.map +1 -1
  53. package/dist/umd/components/ReactFlowProvider/index.d.ts +4 -3
  54. package/dist/umd/components/ReactFlowProvider/index.d.ts.map +1 -1
  55. package/dist/umd/components/SelectionListener/index.d.ts.map +1 -1
  56. package/dist/umd/container/NodeRenderer/useResizeObserver.d.ts.map +1 -1
  57. package/dist/umd/container/Pane/index.d.ts.map +1 -1
  58. package/dist/umd/contexts/StoreContext.d.ts +5 -0
  59. package/dist/umd/contexts/StoreContext.d.ts.map +1 -0
  60. package/dist/umd/hooks/useNodesInitialized.d.ts.map +1 -1
  61. package/dist/umd/hooks/useReactFlow.d.ts.map +1 -1
  62. package/dist/umd/hooks/useStore.d.ts +0 -1
  63. package/dist/umd/hooks/useStore.d.ts.map +1 -1
  64. package/dist/umd/index.js +2 -2
  65. package/dist/umd/store/index.d.ts +2 -2
  66. package/dist/umd/store/index.d.ts.map +1 -1
  67. package/dist/umd/store/initialState.d.ts.map +1 -1
  68. package/dist/umd/types/store.d.ts +1 -0
  69. package/dist/umd/types/store.d.ts.map +1 -1
  70. package/package.json +2 -2
package/dist/esm/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
3
  import cc from 'classcat';
4
- import { errorMessages, infiniteExtent, isInputDOMNode, fitView, getViewportForBounds, pointToRendererPoint, rendererPointToPoint, isNodeBase, isEdgeBase, getElementsToRemove, nodeToRect, isRectObject, getOverlappingArea, getDimensions, XYPanZoom, PanOnScrollMode, SelectionMode, getEventPosition, getNodesInside, XYDrag, snapPosition, calculateNodePosition, Position, ConnectionMode, isMouseEvent, XYHandle, getHostForElement, addEdge, getNodesBounds, clampPosition, getNodeDimensions, nodeHasDimensions, getPositionWithOrigin, elementSelectionKeys, isEdgeVisible, MarkerType, createMarkerIds, isNumeric, getBezierEdgeCenter, getSmoothStepPath, getStraightPath, getBezierPath, getEdgePosition, getElevatedEdgeZIndex, getMarkerId, ConnectionLineType, updateConnectionLookup, adoptUserNodes, devWarn, updateNodeInternals, updateAbsolutePositions, handleParentExpand, panBy, isMacOs, areConnectionMapsEqual, handleConnectionChange, shallowNodeData, getNodePositionWithOrigin, XYMinimap, getBoundsOfRects, getInternalNodesBounds, ResizeControlVariant, XYResizer, XY_RESIZER_LINE_POSITIONS, XY_RESIZER_HANDLE_POSITIONS, getNodeToolbarTransform } from '@xyflow/system';
4
+ import { errorMessages, infiniteExtent, isInputDOMNode, fitView, getViewportForBounds, pointToRendererPoint, rendererPointToPoint, isNodeBase, isEdgeBase, getElementsToRemove, evaluateAbsolutePosition, nodeToRect, isRectObject, getOverlappingArea, getDimensions, XYPanZoom, PanOnScrollMode, SelectionMode, getEventPosition, getNodesInside, XYDrag, snapPosition, calculateNodePosition, Position, ConnectionMode, isMouseEvent, XYHandle, getHostForElement, addEdge, getInternalNodesBounds, nodeHasDimensions, getNodeDimensions, clampPosition, getPositionWithOrigin, elementSelectionKeys, isEdgeVisible, MarkerType, createMarkerIds, isNumeric, getBezierEdgeCenter, getSmoothStepPath, getStraightPath, getBezierPath, getEdgePosition, getElevatedEdgeZIndex, getMarkerId, ConnectionLineType, updateConnectionLookup, adoptUserNodes, devWarn, updateNodeInternals, updateAbsolutePositions, handleExpandParent, panBy, isMacOs, areConnectionMapsEqual, handleConnectionChange, shallowNodeData, getNodePositionWithOrigin, XYMinimap, getBoundsOfRects, ResizeControlVariant, XYResizer, XY_RESIZER_LINE_POSITIONS, XY_RESIZER_HANDLE_POSITIONS, getNodesBounds, getNodeToolbarTransform } from '@xyflow/system';
5
5
  export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, SelectionMode, addEdge, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, updateEdge } from '@xyflow/system';
6
6
  import { createContext, useContext, useMemo, useEffect, useRef, useState, forwardRef, useLayoutEffect, useCallback, memo } from 'react';
7
7
  import { useStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional';
@@ -40,7 +40,6 @@ function useStoreApi() {
40
40
  getState: store.getState,
41
41
  setState: store.setState,
42
42
  subscribe: store.subscribe,
43
- destroy: store.destroy,
44
43
  }), [store]);
45
44
  }
46
45
 
@@ -82,10 +81,21 @@ function Attribution({ proOptions, position = 'bottom-right' }) {
82
81
  return (jsx(Panel, { position: position, className: "react-flow__attribution", "data-message": "Please only hide this attribution when you are subscribed to React Flow Pro: https://pro.reactflow.dev", children: jsx("a", { href: "https://reactflow.dev", target: "_blank", rel: "noopener noreferrer", "aria-label": "React Flow attribution", children: "React Flow" }) }));
83
82
  }
84
83
 
85
- const selector$o = (s) => ({
86
- selectedNodes: Array.from(s.nodeLookup.values()).filter((n) => n.selected),
87
- selectedEdges: s.edges.filter((e) => e.selected),
88
- });
84
+ const selector$o = (s) => {
85
+ const selectedNodes = [];
86
+ const selectedEdges = [];
87
+ for (const [, node] of s.nodeLookup) {
88
+ if (node.selected) {
89
+ selectedNodes.push(node.internals.userNode);
90
+ }
91
+ }
92
+ for (const [, edge] of s.edgeLookup) {
93
+ if (edge.selected) {
94
+ selectedEdges.push(edge);
95
+ }
96
+ }
97
+ return { selectedNodes, selectedEdges };
98
+ };
89
99
  const selectId = (obj) => obj.id;
90
100
  function areEqual(a, b) {
91
101
  return (shallow(a.selectedNodes.map(selectId), b.selectedNodes.map(selectId)) &&
@@ -548,7 +558,7 @@ function applyChange(change, element) {
548
558
  element.measured ??= {};
549
559
  element.measured.width = change.dimensions.width;
550
560
  element.measured.height = change.dimensions.height;
551
- if (change.resizing) {
561
+ if (change.setAttributes) {
552
562
  element.width = change.dimensions.width;
553
563
  element.height = change.dimensions.height;
554
564
  }
@@ -615,8 +625,8 @@ function createSelectionChange(id, selected) {
615
625
  }
616
626
  function getSelectionChanges(items, selectedIds = new Set(), mutateItem = false) {
617
627
  const changes = [];
618
- for (const [, item] of items) {
619
- const willBeSelected = selectedIds.has(item.id);
628
+ for (const [id, item] of items) {
629
+ const willBeSelected = selectedIds.has(id);
620
630
  // we don't want to set all items to selected=false on the first selection
621
631
  if (!(item.selected === undefined && !willBeSelected) && item.selected !== willBeSelected) {
622
632
  if (mutateItem) {
@@ -634,7 +644,8 @@ function getElementsDiffChanges({ items = [], lookup, }) {
634
644
  const changes = [];
635
645
  const itemsLookup = new Map(items.map((item) => [item.id, item]));
636
646
  for (const item of items) {
637
- const storeItem = lookup.get(item.id);
647
+ const lookupItem = lookup.get(item.id);
648
+ const storeItem = lookupItem?.internals?.userNode ?? lookupItem;
638
649
  if (storeItem !== undefined && storeItem !== item) {
639
650
  changes.push({ id: item.id, item: item, type: 'replace' });
640
651
  }
@@ -677,33 +688,22 @@ function fixedForwardRef(render) {
677
688
  const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
678
689
 
679
690
  /**
680
- * Hook for accessing the ReactFlow instance.
691
+ * This hook returns a queue that can be used to batch updates.
681
692
  *
682
- * @public
683
- * @returns ReactFlowInstance
693
+ * @param runQueue - a function that gets called when the queue is flushed
694
+ * @internal
695
+ *
696
+ * @returns a Queue object
684
697
  */
685
- function useReactFlow() {
686
- const viewportHelper = useViewportHelper();
687
- const store = useStoreApi();
688
- const getNodes = useCallback(() => store.getState().nodes.map((n) => ({ ...n })), []);
689
- const getInternalNode = useCallback((id) => store.getState().nodeLookup.get(id), []);
690
- const getNode = useCallback((id) => getInternalNode(id)?.internals.userNode, [getInternalNode]);
691
- const getEdges = useCallback(() => {
692
- const { edges = [] } = store.getState();
693
- return edges.map((e) => ({ ...e }));
694
- }, []);
695
- const getEdge = useCallback((id) => {
696
- const { edges = [] } = store.getState();
697
- return edges.find((e) => e.id === id);
698
- }, []);
699
- // A reference of all the batched updates to process before the next render. We
700
- // want a mutable reference here so multiple synchronous calls to `setNodes` etc
701
- // can be batched together.
702
- const setElementsQueue = useRef({ nodes: [], edges: [] });
698
+ function useQueue(runQueue) {
703
699
  // Because we're using a ref above, we need some way to let React know when to
704
700
  // actually process the queue. We flip this bit of state to `true` any time we
705
701
  // mutate the queue and then flip it back to `false` after flushing the queue.
706
- const [shouldFlushQueue, setShouldFlushQueue] = useState(false);
702
+ const [shouldFlush, setShouldFlush] = useState(false);
703
+ // A reference of all the batched updates to process before the next render. We
704
+ // want a reference here so multiple synchronous calls to `setNodes` etc can be
705
+ // batched together.
706
+ const [queue] = useState(() => createQueue(() => setShouldFlush(true)));
707
707
  // Layout effects are guaranteed to run before the next render which means we
708
708
  // shouldn't run into any issues with stale state or weird issues that come from
709
709
  // rendering things one frame later than expected (we used to use `setTimeout`).
@@ -712,70 +712,123 @@ function useReactFlow() {
712
712
  // trigger the hook again (!). If the hook is being run again we know that any
713
713
  // updates should have been processed by now and we can safely clear the queue
714
714
  // and bail early.
715
- if (!shouldFlushQueue) {
716
- setElementsQueue.current = { nodes: [], edges: [] };
715
+ if (!shouldFlush) {
716
+ queue.reset();
717
717
  return;
718
718
  }
719
- if (setElementsQueue.current.nodes.length) {
720
- const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
721
- // This is essentially an `Array.reduce` in imperative clothing. Processing
722
- // this queue is a relatively hot path so we'd like to avoid the overhead of
723
- // array methods where we can.
724
- let next = nodes;
725
- for (const payload of setElementsQueue.current.nodes) {
726
- next = typeof payload === 'function' ? payload(next) : payload;
727
- }
728
- if (hasDefaultNodes) {
729
- setNodes(next);
730
- }
731
- else if (onNodesChange) {
732
- onNodesChange(getElementsDiffChanges({
733
- items: next,
734
- lookup: nodeLookup,
735
- }));
736
- }
737
- setElementsQueue.current.nodes = [];
738
- }
739
- if (setElementsQueue.current.edges.length) {
740
- const { edges = [], setEdges, hasDefaultEdges, onEdgesChange, edgeLookup } = store.getState();
741
- let next = edges;
742
- for (const payload of setElementsQueue.current.edges) {
743
- next = typeof payload === 'function' ? payload(next) : payload;
744
- }
745
- if (hasDefaultEdges) {
746
- setEdges(next);
747
- }
748
- else if (onEdgesChange) {
749
- onEdgesChange(getElementsDiffChanges({
750
- items: next,
751
- lookup: edgeLookup,
752
- }));
753
- }
754
- setElementsQueue.current.edges = [];
719
+ const queueItems = queue.get();
720
+ if (queueItems.length) {
721
+ runQueue(queueItems);
722
+ queue.reset();
755
723
  }
756
724
  // Beacuse we're using reactive state to trigger this effect, we need to flip
757
725
  // it back to false.
758
- setShouldFlushQueue(false);
759
- }, [shouldFlushQueue]);
726
+ setShouldFlush(false);
727
+ }, [shouldFlush]);
728
+ return queue;
729
+ }
730
+ function createQueue(cb) {
731
+ let queue = [];
732
+ return {
733
+ get: () => queue,
734
+ reset: () => {
735
+ queue = [];
736
+ },
737
+ push: (item) => {
738
+ queue.push(item);
739
+ cb();
740
+ },
741
+ };
742
+ }
743
+
744
+ const BatchContext = createContext(null);
745
+ /**
746
+ * This is a context provider that holds and processes the node and edge update queues
747
+ * that are needed to handle setNodes, addNodes, setEdges and addEdges.
748
+ *
749
+ * @internal
750
+ */
751
+ function BatchProvider({ children, }) {
752
+ const store = useStoreApi();
753
+ const nodeQueueHandler = useCallback((queueItems) => {
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 queueItems) {
760
+ next = typeof payload === 'function' ? payload(next) : payload;
761
+ }
762
+ if (hasDefaultNodes) {
763
+ setNodes(next);
764
+ }
765
+ else if (onNodesChange) {
766
+ onNodesChange(getElementsDiffChanges({
767
+ items: next,
768
+ lookup: nodeLookup,
769
+ }));
770
+ }
771
+ }, []);
772
+ const nodeQueue = useQueue(nodeQueueHandler);
773
+ const edgeQueueHandler = useCallback((queueItems) => {
774
+ const { edges = [], setEdges, hasDefaultEdges, onEdgesChange, edgeLookup } = store.getState();
775
+ let next = edges;
776
+ for (const payload of queueItems) {
777
+ next = typeof payload === 'function' ? payload(next) : payload;
778
+ }
779
+ if (hasDefaultEdges) {
780
+ setEdges(next);
781
+ }
782
+ else if (onEdgesChange) {
783
+ onEdgesChange(getElementsDiffChanges({
784
+ items: next,
785
+ lookup: edgeLookup,
786
+ }));
787
+ }
788
+ }, []);
789
+ const edgeQueue = useQueue(edgeQueueHandler);
790
+ const value = useMemo(() => ({ nodeQueue, edgeQueue }), []);
791
+ return jsx(BatchContext.Provider, { value: value, children: children });
792
+ }
793
+ function useBatchContext() {
794
+ const batchContext = useContext(BatchContext);
795
+ if (!batchContext) {
796
+ throw new Error('useBatchContext must be used within a BatchProvider');
797
+ }
798
+ return batchContext;
799
+ }
800
+
801
+ /**
802
+ * Hook for accessing the ReactFlow instance.
803
+ *
804
+ * @public
805
+ * @returns ReactFlowInstance
806
+ */
807
+ function useReactFlow() {
808
+ const viewportHelper = useViewportHelper();
809
+ const store = useStoreApi();
810
+ const batchContext = useBatchContext();
811
+ const getNodes = useCallback(() => store.getState().nodes.map((n) => ({ ...n })), []);
812
+ const getInternalNode = useCallback((id) => store.getState().nodeLookup.get(id), []);
813
+ const getNode = useCallback((id) => getInternalNode(id)?.internals.userNode, [getInternalNode]);
814
+ const getEdges = useCallback(() => {
815
+ const { edges = [] } = store.getState();
816
+ return edges.map((e) => ({ ...e }));
817
+ }, []);
818
+ const getEdge = useCallback((id) => store.getState().edgeLookup.get(id), []);
760
819
  const setNodes = useCallback((payload) => {
761
- setElementsQueue.current.nodes.push(payload);
762
- setShouldFlushQueue(true);
820
+ batchContext.nodeQueue.push(payload);
763
821
  }, []);
764
822
  const setEdges = useCallback((payload) => {
765
- setElementsQueue.current.edges.push(payload);
766
- setShouldFlushQueue(true);
823
+ batchContext.edgeQueue.push(payload);
767
824
  }, []);
768
825
  const addNodes = useCallback((payload) => {
769
826
  const newNodes = Array.isArray(payload) ? payload : [payload];
770
- // Queueing a functional update means that we won't worry about other calls
771
- // to `setNodes` that might happen elsewhere.
772
- setElementsQueue.current.nodes.push((nodes) => [...nodes, ...newNodes]);
773
- setShouldFlushQueue(true);
827
+ batchContext.nodeQueue.push((nodes) => [...nodes, ...newNodes]);
774
828
  }, []);
775
829
  const addEdges = useCallback((payload) => {
776
830
  const newEdges = Array.isArray(payload) ? payload : [payload];
777
- setElementsQueue.current.edges.push((edges) => [...edges, ...newEdges]);
778
- setShouldFlushQueue(true);
831
+ batchContext.edgeQueue.push((edges) => [...edges, ...newEdges]);
779
832
  }, []);
780
833
  const toObject = useCallback(() => {
781
834
  const { nodes = [], edges = [], transform } = store.getState();
@@ -825,13 +878,25 @@ function useReactFlow() {
825
878
  }
826
879
  return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
827
880
  }, []);
828
- const getNodeRect = useCallback(({ id }) => {
829
- const internalNode = store.getState().nodeLookup.get(id);
830
- return internalNode ? nodeToRect(internalNode) : null;
881
+ const getNodeRect = useCallback((node) => {
882
+ const { nodeLookup, nodeOrigin } = store.getState();
883
+ const nodeToUse = isNode(node) ? node : nodeLookup.get(node.id);
884
+ const position = nodeToUse.parentId
885
+ ? evaluateAbsolutePosition(nodeToUse.position, nodeToUse.parentId, nodeLookup, nodeOrigin)
886
+ : nodeToUse.position;
887
+ const nodeWithPosition = {
888
+ id: nodeToUse.id,
889
+ position,
890
+ width: nodeToUse.measured?.width ?? nodeToUse.width,
891
+ height: nodeToUse.measured?.height ?? nodeToUse.height,
892
+ data: nodeToUse.data,
893
+ };
894
+ return nodeToRect(nodeWithPosition);
831
895
  }, []);
832
896
  const getIntersectingNodes = useCallback((nodeOrRect, partially = true, nodes) => {
833
897
  const isRect = isRectObject(nodeOrRect);
834
898
  const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
899
+ const hasNodesOption = nodes !== undefined;
835
900
  if (!nodeRect) {
836
901
  return [];
837
902
  }
@@ -840,7 +905,7 @@ function useReactFlow() {
840
905
  if (internalNode && !isRect && (n.id === nodeOrRect.id || !internalNode.internals.positionAbsolute)) {
841
906
  return false;
842
907
  }
843
- const currNodeRect = nodeToRect(n);
908
+ const currNodeRect = nodeToRect(hasNodesOption ? n : internalNode);
844
909
  const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
845
910
  const partiallyVisible = partially && overlappingArea > 0;
846
911
  return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
@@ -856,7 +921,7 @@ function useReactFlow() {
856
921
  const partiallyVisible = partially && overlappingArea > 0;
857
922
  return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
858
923
  }, []);
859
- const updateNode = useCallback((id, nodeUpdate, options = { replace: true }) => {
924
+ const updateNode = useCallback((id, nodeUpdate, options = { replace: false }) => {
860
925
  setNodes((prevNodes) => prevNodes.map((node) => {
861
926
  if (node.id === id) {
862
927
  const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node) : nodeUpdate;
@@ -1100,6 +1165,7 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1100
1165
  const prevSelectedNodesCount = useRef(0);
1101
1166
  const prevSelectedEdgesCount = useRef(0);
1102
1167
  const containerBounds = useRef();
1168
+ const edgeIdLookup = useRef(new Map());
1103
1169
  const { userSelectionActive, elementsSelectable, dragging } = useStore(selector$j, shallow);
1104
1170
  const resetUserSelection = () => {
1105
1171
  store.setState({ userSelectionActive: false, userSelectionRect: null });
@@ -1120,7 +1186,7 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1120
1186
  };
1121
1187
  const onWheel = onPaneScroll ? (event) => onPaneScroll(event) : undefined;
1122
1188
  const onMouseDown = (event) => {
1123
- const { resetSelectedElements, domNode } = store.getState();
1189
+ const { resetSelectedElements, domNode, edgeLookup } = store.getState();
1124
1190
  containerBounds.current = domNode?.getBoundingClientRect();
1125
1191
  if (!elementsSelectable ||
1126
1192
  !isSelecting ||
@@ -1129,6 +1195,11 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1129
1195
  !containerBounds.current) {
1130
1196
  return;
1131
1197
  }
1198
+ edgeIdLookup.current = new Map();
1199
+ for (const [id, edge] of edgeLookup) {
1200
+ edgeIdLookup.current.set(edge.source, edgeIdLookup.current.get(edge.source)?.add(id) || new Set([id]));
1201
+ edgeIdLookup.current.set(edge.target, edgeIdLookup.current.get(edge.target)?.add(id) || new Set([id]));
1202
+ }
1132
1203
  const { x, y } = getEventPosition(event.nativeEvent, containerBounds.current);
1133
1204
  resetSelectedElements();
1134
1205
  store.setState({
@@ -1148,24 +1219,24 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1148
1219
  if (!isSelecting || !containerBounds.current || !userSelectionRect) {
1149
1220
  return;
1150
1221
  }
1151
- store.setState({ userSelectionActive: true, nodesSelectionActive: false });
1152
- const mousePos = getEventPosition(event.nativeEvent, containerBounds.current);
1153
- const startX = userSelectionRect.startX ?? 0;
1154
- const startY = userSelectionRect.startY ?? 0;
1222
+ const { x: mouseX, y: mouseY } = getEventPosition(event.nativeEvent, containerBounds.current);
1223
+ const { startX, startY } = userSelectionRect;
1155
1224
  const nextUserSelectRect = {
1156
- ...userSelectionRect,
1157
- x: mousePos.x < startX ? mousePos.x : startX,
1158
- y: mousePos.y < startY ? mousePos.y : startY,
1159
- width: Math.abs(mousePos.x - startX),
1160
- height: Math.abs(mousePos.y - startY),
1225
+ startX,
1226
+ startY,
1227
+ x: mouseX < startX ? mouseX : startX,
1228
+ y: mouseY < startY ? mouseY : startY,
1229
+ width: Math.abs(mouseX - startX),
1230
+ height: Math.abs(mouseY - startY),
1161
1231
  };
1162
1232
  const selectedNodes = getNodesInside(nodeLookup, nextUserSelectRect, transform, selectionMode === SelectionMode.Partial, true, nodeOrigin);
1163
1233
  const selectedEdgeIds = new Set();
1164
1234
  const selectedNodeIds = new Set();
1165
1235
  for (const selectedNode of selectedNodes) {
1166
1236
  selectedNodeIds.add(selectedNode.id);
1167
- for (const [edgeId, edge] of edgeLookup) {
1168
- if (edge.source === selectedNode.id || edge.target === selectedNode.id) {
1237
+ const edgeIds = edgeIdLookup.current.get(selectedNode.id);
1238
+ if (edgeIds) {
1239
+ for (const edgeId of edgeIds) {
1169
1240
  selectedEdgeIds.add(edgeId);
1170
1241
  }
1171
1242
  }
@@ -1182,6 +1253,8 @@ function Pane({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSe
1182
1253
  }
1183
1254
  store.setState({
1184
1255
  userSelectionRect: nextUserSelectRect,
1256
+ userSelectionActive: true,
1257
+ nodesSelectionActive: false,
1185
1258
  });
1186
1259
  };
1187
1260
  const onMouseUp = (event) => {
@@ -1288,7 +1361,7 @@ function useMoveSelectedNodes() {
1288
1361
  const store = useStoreApi();
1289
1362
  const moveSelectedNodes = useCallback((params) => {
1290
1363
  const { nodeExtent, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions, nodeLookup, nodeOrigin } = store.getState();
1291
- const nodeUpdates = [];
1364
+ const nodeUpdates = new Map();
1292
1365
  const isSelected = selectedAndDraggable(nodesDraggable);
1293
1366
  // by default a node moves 5px on each key press
1294
1367
  // if snap grid is enabled, we use that for the velocity
@@ -1317,7 +1390,7 @@ function useMoveSelectedNodes() {
1317
1390
  });
1318
1391
  node.position = position;
1319
1392
  node.internals.positionAbsolute = positionAbsolute;
1320
- nodeUpdates.push(node);
1393
+ nodeUpdates.set(node.id, node);
1321
1394
  }
1322
1395
  updateNodePositions(nodeUpdates);
1323
1396
  }, []);
@@ -1515,13 +1588,10 @@ function getNodeInlineStyleDimensions(node) {
1515
1588
  }
1516
1589
 
1517
1590
  const selector$h = (s) => {
1518
- const selectedNodes = [];
1519
- for (const [, node] of s.nodeLookup) {
1520
- if (node.selected) {
1521
- selectedNodes.push(node);
1522
- }
1523
- }
1524
- const { width, height, x, y } = getNodesBounds(selectedNodes, { nodeOrigin: s.nodeOrigin });
1591
+ const { width, height, x, y } = getInternalNodesBounds(s.nodeLookup, {
1592
+ nodeOrigin: s.nodeOrigin,
1593
+ filter: (node) => !!node.selected,
1594
+ });
1525
1595
  return {
1526
1596
  width,
1527
1597
  height,
@@ -1605,12 +1675,11 @@ function useVisibleNodeIds(onlyRenderVisible) {
1605
1675
  const selector$e = (s) => s.updateNodeInternals;
1606
1676
  function useResizeObserver() {
1607
1677
  const updateNodeInternals = useStore(selector$e);
1608
- const resizeObserverRef = useRef();
1609
- const resizeObserver = useMemo(() => {
1678
+ const [resizeObserver] = useState(() => {
1610
1679
  if (typeof ResizeObserver === 'undefined') {
1611
1680
  return null;
1612
1681
  }
1613
- const observer = new ResizeObserver((entries) => {
1682
+ return new ResizeObserver((entries) => {
1614
1683
  const updates = new Map();
1615
1684
  entries.forEach((entry) => {
1616
1685
  const id = entry.target.getAttribute('data-id');
@@ -1621,31 +1690,74 @@ function useResizeObserver() {
1621
1690
  });
1622
1691
  updateNodeInternals(updates);
1623
1692
  });
1624
- resizeObserverRef.current = observer;
1625
- return observer;
1626
- }, []);
1693
+ });
1627
1694
  useEffect(() => {
1628
1695
  return () => {
1629
- resizeObserverRef?.current?.disconnect();
1696
+ resizeObserver?.disconnect();
1630
1697
  };
1631
- }, []);
1698
+ }, [resizeObserver]);
1632
1699
  return resizeObserver;
1633
1700
  }
1634
1701
 
1702
+ /**
1703
+ * Hook to handle the resize observation + internal updates for the passed node.
1704
+ *
1705
+ * @internal
1706
+ * @returns nodeRef - reference to the node element
1707
+ */
1708
+ function useNodeObserver({ node, nodeType, hasDimensions, resizeObserver, }) {
1709
+ const store = useStoreApi();
1710
+ const nodeRef = useRef(null);
1711
+ const observedNode = useRef(null);
1712
+ const prevSourcePosition = useRef(node.sourcePosition);
1713
+ const prevTargetPosition = useRef(node.targetPosition);
1714
+ const prevType = useRef(nodeType);
1715
+ const isInitialized = hasDimensions && !!node.internals.handleBounds && !node.hidden;
1716
+ useEffect(() => {
1717
+ if (nodeRef.current && (!isInitialized || observedNode.current !== nodeRef.current)) {
1718
+ if (observedNode.current) {
1719
+ resizeObserver?.unobserve(observedNode.current);
1720
+ }
1721
+ resizeObserver?.observe(nodeRef.current);
1722
+ observedNode.current = nodeRef.current;
1723
+ }
1724
+ }, [isInitialized]);
1725
+ useEffect(() => {
1726
+ return () => {
1727
+ if (observedNode.current) {
1728
+ resizeObserver?.unobserve(observedNode.current);
1729
+ observedNode.current = null;
1730
+ }
1731
+ };
1732
+ }, []);
1733
+ useEffect(() => {
1734
+ if (nodeRef.current) {
1735
+ // when the user programmatically changes the source or handle position, we need to update the internals
1736
+ // to make sure the edges are updated correctly
1737
+ const typeChanged = prevType.current !== nodeType;
1738
+ const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
1739
+ const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
1740
+ if (typeChanged || sourcePosChanged || targetPosChanged) {
1741
+ prevType.current = nodeType;
1742
+ prevSourcePosition.current = node.sourcePosition;
1743
+ prevTargetPosition.current = node.targetPosition;
1744
+ store
1745
+ .getState()
1746
+ .updateNodeInternals(new Map([[node.id, { id: node.id, nodeElement: nodeRef.current, force: true }]]));
1747
+ }
1748
+ }
1749
+ }, [node.id, nodeType, node.sourcePosition, node.targetPosition]);
1750
+ return nodeRef;
1751
+ }
1752
+
1635
1753
  function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onContextMenu, onDoubleClick, nodesDraggable, elementsSelectable, nodesConnectable, nodesFocusable, resizeObserver, noDragClassName, noPanClassName, disableKeyboardA11y, rfId, nodeTypes, nodeExtent, nodeOrigin, onError, }) {
1636
- const { node, positionAbsoluteX, positionAbsoluteY, zIndex, isParent } = useStore((s) => {
1754
+ const { node, internals, isParent } = useStore((s) => {
1637
1755
  const node = s.nodeLookup.get(id);
1638
- const positionAbsolute = nodeExtent
1639
- ? clampPosition(node.internals.positionAbsolute, nodeExtent)
1640
- : node.internals.positionAbsolute || { x: 0, y: 0 };
1756
+ const isParent = s.parentLookup.has(id);
1641
1757
  return {
1642
1758
  node,
1643
- // we are mutating positionAbsolute, z and isParent attributes for sub flows
1644
- // so we we need to force a re-render when some change
1645
- positionAbsoluteX: positionAbsolute.x,
1646
- positionAbsoluteY: positionAbsolute.y,
1647
- zIndex: node.internals.z,
1648
- isParent: node.internals.isParent,
1759
+ internals: node.internals,
1760
+ isParent,
1649
1761
  };
1650
1762
  }, shallow);
1651
1763
  let nodeType = node.type || 'default';
@@ -1660,50 +1772,8 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1660
1772
  const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined'));
1661
1773
  const isFocusable = !!(node.focusable || (nodesFocusable && typeof node.focusable === 'undefined'));
1662
1774
  const store = useStoreApi();
1663
- const nodeRef = useRef(null);
1664
- const prevSourcePosition = useRef(node.sourcePosition);
1665
- const prevTargetPosition = useRef(node.targetPosition);
1666
- const prevType = useRef(nodeType);
1667
- const nodeDimensions = getNodeDimensions(node);
1668
- const inlineDimensions = getNodeInlineStyleDimensions(node);
1669
- const initialized = nodeHasDimensions(node);
1670
- const hasHandleBounds = !!node.internals.handleBounds;
1671
- const moveSelectedNodes = useMoveSelectedNodes();
1672
- useEffect(() => {
1673
- const currNode = nodeRef.current;
1674
- return () => {
1675
- if (currNode) {
1676
- resizeObserver?.unobserve(currNode);
1677
- }
1678
- };
1679
- }, []);
1680
- useEffect(() => {
1681
- if (nodeRef.current && !node.hidden) {
1682
- const currNode = nodeRef.current;
1683
- if (!initialized || !hasHandleBounds) {
1684
- resizeObserver?.unobserve(currNode);
1685
- resizeObserver?.observe(currNode);
1686
- }
1687
- }
1688
- }, [node.hidden, initialized, hasHandleBounds]);
1689
- useEffect(() => {
1690
- // when the user programmatically changes the source or handle position, we re-initialize the node
1691
- const typeChanged = prevType.current !== nodeType;
1692
- const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
1693
- const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
1694
- if (nodeRef.current && (typeChanged || sourcePosChanged || targetPosChanged)) {
1695
- if (typeChanged) {
1696
- prevType.current = nodeType;
1697
- }
1698
- if (sourcePosChanged) {
1699
- prevSourcePosition.current = node.sourcePosition;
1700
- }
1701
- if (targetPosChanged) {
1702
- prevTargetPosition.current = node.targetPosition;
1703
- }
1704
- store.getState().updateNodeInternals(new Map([[id, { id, nodeElement: nodeRef.current, force: true }]]));
1705
- }
1706
- }, [id, nodeType, node.sourcePosition, node.targetPosition]);
1775
+ const hasDimensions = nodeHasDimensions(node);
1776
+ const nodeRef = useNodeObserver({ node, nodeType, hasDimensions, resizeObserver });
1707
1777
  const dragging = useDrag({
1708
1778
  nodeRef,
1709
1779
  disabled: node.hidden || !isDraggable,
@@ -1712,12 +1782,17 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1712
1782
  nodeId: id,
1713
1783
  isSelectable,
1714
1784
  });
1785
+ const moveSelectedNodes = useMoveSelectedNodes();
1715
1786
  if (node.hidden) {
1716
1787
  return null;
1717
1788
  }
1718
- const positionAbsoluteOrigin = getPositionWithOrigin({
1719
- x: positionAbsoluteX,
1720
- y: positionAbsoluteY,
1789
+ const nodeDimensions = getNodeDimensions(node);
1790
+ const inlineDimensions = getNodeInlineStyleDimensions(node);
1791
+ const clampedPosition = nodeExtent
1792
+ ? clampPosition(internals.positionAbsolute, nodeExtent)
1793
+ : internals.positionAbsolute;
1794
+ const positionWithOrigin = getPositionWithOrigin({
1795
+ ...clampedPosition,
1721
1796
  ...nodeDimensions,
1722
1797
  origin: node.origin || nodeOrigin,
1723
1798
  });
@@ -1759,7 +1834,7 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1759
1834
  store.setState({
1760
1835
  ariaLiveMessage: `Moved selected node ${event.key
1761
1836
  .replace('Arrow', '')
1762
- .toLowerCase()}. New position, x: ${~~positionAbsoluteX}, y: ${~~positionAbsoluteY}`,
1837
+ .toLowerCase()}. New position, x: ${~~clampedPosition.x}, y: ${~~clampedPosition.y}`,
1763
1838
  });
1764
1839
  moveSelectedNodes({
1765
1840
  direction: arrowKeyDiffs[event.key],
@@ -1783,13 +1858,13 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1783
1858
  dragging,
1784
1859
  },
1785
1860
  ]), ref: nodeRef, style: {
1786
- zIndex,
1787
- transform: `translate(${positionAbsoluteOrigin.x}px,${positionAbsoluteOrigin.y}px)`,
1861
+ zIndex: internals.z,
1862
+ transform: `translate(${positionWithOrigin.x}px,${positionWithOrigin.y}px)`,
1788
1863
  pointerEvents: hasPointerEvents ? 'all' : 'none',
1789
- visibility: initialized ? 'visible' : 'hidden',
1864
+ visibility: hasDimensions ? 'visible' : 'hidden',
1790
1865
  ...node.style,
1791
1866
  ...inlineDimensions,
1792
- }, "data-id": id, "data-testid": `rf__node-${id}`, onMouseEnter: onMouseEnterHandler, onMouseMove: onMouseMoveHandler, onMouseLeave: onMouseLeaveHandler, onContextMenu: onContextMenuHandler, onClick: onSelectNodeHandler, onDoubleClick: onDoubleClickHandler, onKeyDown: isFocusable ? onKeyDown : undefined, tabIndex: isFocusable ? 0 : undefined, role: isFocusable ? 'button' : undefined, "aria-describedby": disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`, "aria-label": node.ariaLabel, children: jsx(Provider, { value: id, children: jsx(NodeComponent, { id: id, data: node.data, type: nodeType, positionAbsoluteX: positionAbsoluteX, positionAbsoluteY: positionAbsoluteY, selected: node.selected, isConnectable: isConnectable, sourcePosition: node.sourcePosition, targetPosition: node.targetPosition, dragging: dragging, dragHandle: node.dragHandle, zIndex: zIndex, ...nodeDimensions }) }) }));
1867
+ }, "data-id": id, "data-testid": `rf__node-${id}`, onMouseEnter: onMouseEnterHandler, onMouseMove: onMouseMoveHandler, onMouseLeave: onMouseLeaveHandler, onContextMenu: onContextMenuHandler, onClick: onSelectNodeHandler, onDoubleClick: onDoubleClickHandler, onKeyDown: isFocusable ? onKeyDown : undefined, tabIndex: isFocusable ? 0 : undefined, role: isFocusable ? 'button' : undefined, "aria-describedby": disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`, "aria-label": node.ariaLabel, children: jsx(Provider, { value: id, children: jsx(NodeComponent, { id: id, data: node.data, type: nodeType, positionAbsoluteX: clampedPosition.x, positionAbsoluteY: clampedPosition.y, selected: node.selected, isConnectable: isConnectable, sourcePosition: node.sourcePosition, targetPosition: node.targetPosition, dragging: dragging, dragHandle: node.dragHandle, zIndex: internals.z, ...nodeDimensions }) }) }));
1793
1868
  }
1794
1869
 
1795
1870
  const selector$d = (s) => ({
@@ -2460,20 +2535,23 @@ const GraphView = memo(GraphViewComponent);
2460
2535
 
2461
2536
  const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, height, fitView, } = {}) => {
2462
2537
  const nodeLookup = new Map();
2538
+ const parentLookup = new Map();
2463
2539
  const connectionLookup = new Map();
2464
2540
  const edgeLookup = new Map();
2465
2541
  const storeEdges = defaultEdges ?? edges ?? [];
2466
2542
  const storeNodes = defaultNodes ?? nodes ?? [];
2467
2543
  updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
2468
- adoptUserNodes(storeNodes, nodeLookup, {
2544
+ adoptUserNodes(storeNodes, nodeLookup, parentLookup, {
2469
2545
  nodeOrigin: [0, 0],
2470
2546
  elevateNodesOnSelect: false,
2471
2547
  });
2472
2548
  let transform = [0, 0, 1];
2473
2549
  if (fitView && width && height) {
2474
- const nodesWithDimensions = storeNodes.filter((node) => (node.width || node.initialWidth) && (node.height || node.initialHeight));
2475
2550
  // @todo users nodeOrigin should be used here
2476
- const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] });
2551
+ const bounds = getInternalNodesBounds(nodeLookup, {
2552
+ nodeOrigin: [0, 0],
2553
+ filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)),
2554
+ });
2477
2555
  const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
2478
2556
  transform = [x, y, zoom];
2479
2557
  }
@@ -2484,6 +2562,7 @@ const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, heig
2484
2562
  transform,
2485
2563
  nodes: storeNodes,
2486
2564
  nodeLookup,
2565
+ parentLookup,
2487
2566
  edges: storeEdges,
2488
2567
  edgeLookup,
2489
2568
  connectionLookup,
@@ -2538,17 +2617,17 @@ const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, heig
2538
2617
  };
2539
2618
  };
2540
2619
 
2541
- const createRFStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height, fitView: fitView$1, }) => createWithEqualityFn((set, get) => ({
2620
+ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height, fitView: fitView$1, }) => createWithEqualityFn((set, get) => ({
2542
2621
  ...getInitialState({ nodes, edges, width, height, fitView: fitView$1, defaultNodes, defaultEdges }),
2543
2622
  setNodes: (nodes) => {
2544
- const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
2623
+ const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
2545
2624
  // setNodes() is called exclusively in response to user actions:
2546
2625
  // - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
2547
2626
  // - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
2548
2627
  //
2549
2628
  // When this happens, we take the note objects passed by the user and extend them with fields
2550
2629
  // relevant for internal React Flow operations.
2551
- adoptUserNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
2630
+ adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin, elevateNodesOnSelect, checkEquality: true });
2552
2631
  set({ nodes });
2553
2632
  },
2554
2633
  setEdges: (edges) => {
@@ -2572,8 +2651,8 @@ const createRFStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height
2572
2651
  // changes its dimensions, this function is called to measure the
2573
2652
  // new dimensions and update the nodes.
2574
2653
  updateNodeInternals: (updates) => {
2575
- const { onNodesChange, fitView, nodeLookup, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin, debug, } = get();
2576
- const { changes, updatedInternals } = updateNodeInternals(updates, nodeLookup, domNode, nodeOrigin);
2654
+ const { onNodesChange, fitView, nodeLookup, parentLookup, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin, debug, } = get();
2655
+ const { changes, updatedInternals } = updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOrigin);
2577
2656
  if (!updatedInternals) {
2578
2657
  return;
2579
2658
  }
@@ -2600,33 +2679,34 @@ const createRFStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height
2600
2679
  }
2601
2680
  },
2602
2681
  updateNodePositions: (nodeDragItems, dragging = false) => {
2603
- const { nodeLookup } = get();
2604
- const triggerChangeNodes = [];
2605
- const changes = nodeDragItems.map((node) => {
2682
+ const parentExpandChildren = [];
2683
+ const changes = [];
2684
+ for (const [id, dragItem] of nodeDragItems) {
2606
2685
  // @todo add expandParent to drag item so that we can get rid of the look up here
2607
- const internalNode = nodeLookup.get(node.id);
2608
2686
  const change = {
2609
- id: node.id,
2687
+ id,
2610
2688
  type: 'position',
2611
- position: node.position,
2689
+ position: dragItem.position,
2612
2690
  dragging,
2613
2691
  };
2614
- if (internalNode?.expandParent && change.position) {
2615
- triggerChangeNodes.push({
2616
- ...internalNode,
2617
- position: change.position,
2618
- internals: {
2619
- ...internalNode.internals,
2620
- positionAbsolute: node.internals.positionAbsolute,
2692
+ if (dragItem?.expandParent && dragItem?.parentId && change.position) {
2693
+ parentExpandChildren.push({
2694
+ id,
2695
+ parentId: dragItem.parentId,
2696
+ rect: {
2697
+ ...dragItem.internals.positionAbsolute,
2698
+ width: dragItem.measured.width,
2699
+ height: dragItem.measured.height,
2621
2700
  },
2622
2701
  });
2623
2702
  change.position.x = Math.max(0, change.position.x);
2624
2703
  change.position.y = Math.max(0, change.position.y);
2625
2704
  }
2626
- return change;
2627
- });
2628
- if (triggerChangeNodes.length > 0) {
2629
- const parentExpandChanges = handleParentExpand(triggerChangeNodes, nodeLookup);
2705
+ changes.push(change);
2706
+ }
2707
+ if (parentExpandChildren.length > 0) {
2708
+ const { nodeLookup, parentLookup } = get();
2709
+ const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup);
2630
2710
  changes.push(...parentExpandChanges);
2631
2711
  }
2632
2712
  get().triggerNodeChanges(changes);
@@ -2761,20 +2841,17 @@ const createRFStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height
2761
2841
  reset: () => set({ ...getInitialState() }),
2762
2842
  }), Object.is);
2763
2843
 
2764
- function ReactFlowProvider({ children, initialNodes, initialEdges, defaultNodes, defaultEdges, initialWidth, initialHeight, fitView, }) {
2765
- const storeRef = useRef(null);
2766
- if (!storeRef.current) {
2767
- storeRef.current = createRFStore({
2768
- nodes: initialNodes,
2769
- edges: initialEdges,
2770
- defaultNodes,
2771
- defaultEdges,
2772
- width: initialWidth,
2773
- height: initialHeight,
2774
- fitView,
2775
- });
2776
- }
2777
- return jsx(Provider$1, { value: storeRef.current, children: children });
2844
+ function ReactFlowProvider({ initialNodes: nodes, initialEdges: edges, defaultNodes, defaultEdges, initialWidth: width, initialHeight: height, fitView, children, }) {
2845
+ const [store] = useState(() => createStore({
2846
+ nodes,
2847
+ edges,
2848
+ defaultNodes,
2849
+ defaultEdges,
2850
+ width,
2851
+ height,
2852
+ fitView,
2853
+ }));
2854
+ return (jsx(Provider$1, { value: store, children: jsx(BatchProvider, { children: children }) }));
2778
2855
  }
2779
2856
 
2780
2857
  function Wrapper({ children, nodes, edges, defaultNodes, defaultEdges, width, height, fitView, }) {
@@ -2949,9 +3026,9 @@ const selector$6 = (options) => (s) => {
2949
3026
  if (s.nodeLookup.size === 0) {
2950
3027
  return false;
2951
3028
  }
2952
- for (const [, node] of s.nodeLookup) {
2953
- if (options.includeHiddenNodes || !node.hidden) {
2954
- if (node.internals.handleBounds === undefined) {
3029
+ for (const [, { hidden, internals }] of s.nodeLookup) {
3030
+ if (options.includeHiddenNodes || !hidden) {
3031
+ if (internals.handleBounds === undefined || !nodeHasDimensions(internals.userNode)) {
2955
3032
  return false;
2956
3033
  }
2957
3034
  }
@@ -3193,7 +3270,7 @@ nodeComponent: NodeComponent = MiniMapNode, onClick, }) {
3193
3270
  function NodeComponentWrapperInner({ id, nodeOrigin, nodeColorFunc, nodeStrokeColorFunc, nodeClassNameFunc, nodeBorderRadius, nodeStrokeWidth, shapeRendering, NodeComponent, onClick, }) {
3194
3271
  const { node, x, y } = useStore((s) => {
3195
3272
  const node = s.nodeLookup.get(id);
3196
- const { x, y } = getNodePositionWithOrigin(node, node?.origin || nodeOrigin).positionAbsolute;
3273
+ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
3197
3274
  return {
3198
3275
  node,
3199
3276
  x,
@@ -3333,24 +3410,43 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
3333
3410
  };
3334
3411
  },
3335
3412
  onChange: (change, childChanges) => {
3336
- const { triggerNodeChanges } = store.getState();
3413
+ const { triggerNodeChanges, nodeLookup, parentLookup, nodeOrigin } = store.getState();
3337
3414
  const changes = [];
3338
- if (change.isXPosChange || change.isYPosChange) {
3415
+ const nextPosition = { x: change.x, y: change.y };
3416
+ const node = nodeLookup.get(id);
3417
+ if (node && node.expandParent && node.parentId) {
3418
+ const child = {
3419
+ id: node.id,
3420
+ parentId: node.parentId,
3421
+ rect: {
3422
+ width: change.width ?? node.measured.width,
3423
+ height: change.height ?? node.measured.height,
3424
+ ...evaluateAbsolutePosition({
3425
+ x: change.x ?? node.position.x,
3426
+ y: change.y ?? node.position.y,
3427
+ }, node.parentId, nodeLookup, node.origin ?? nodeOrigin),
3428
+ },
3429
+ };
3430
+ const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
3431
+ changes.push(...parentExpandChanges);
3432
+ // when the parent was expanded by the child node, its position will be clamped at 0,0
3433
+ nextPosition.x = change.x ? Math.max(0, change.x) : undefined;
3434
+ nextPosition.y = change.y ? Math.max(0, change.y) : undefined;
3435
+ }
3436
+ if (nextPosition.x !== undefined && nextPosition.y !== undefined) {
3339
3437
  const positionChange = {
3340
3438
  id,
3341
3439
  type: 'position',
3342
- position: {
3343
- x: change.x,
3344
- y: change.y,
3345
- },
3440
+ position: { ...nextPosition },
3346
3441
  };
3347
3442
  changes.push(positionChange);
3348
3443
  }
3349
- if (change.isWidthChange || change.isHeightChange) {
3444
+ if (change.width !== undefined && change.height !== undefined) {
3350
3445
  const dimensionChange = {
3351
3446
  id,
3352
3447
  type: 'dimensions',
3353
3448
  resizing: true,
3449
+ setAttributes: true,
3354
3450
  dimensions: {
3355
3451
  width: change.width,
3356
3452
  height: change.height,