@xyflow/react 12.0.0-next.15 → 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 (30) hide show
  1. package/dist/esm/components/BatchProvider/index.d.ts +17 -0
  2. package/dist/esm/components/BatchProvider/index.d.ts.map +1 -0
  3. package/dist/esm/components/BatchProvider/types.d.ts +7 -0
  4. package/dist/esm/components/BatchProvider/types.d.ts.map +1 -0
  5. package/dist/esm/components/BatchProvider/useQueue.d.ts +11 -0
  6. package/dist/esm/components/BatchProvider/useQueue.d.ts.map +1 -0
  7. package/dist/esm/components/BatchProvider/utils.d.ts +3 -0
  8. package/dist/esm/components/BatchProvider/utils.d.ts.map +1 -0
  9. package/dist/esm/components/ElementBatcher/index.d.ts +3 -0
  10. package/dist/esm/components/ElementBatcher/index.d.ts.map +1 -0
  11. package/dist/esm/components/ReactFlowProvider/index.d.ts.map +1 -1
  12. package/dist/esm/hooks/useElementBatching.d.ts +3 -0
  13. package/dist/esm/hooks/useElementBatching.d.ts.map +1 -0
  14. package/dist/esm/hooks/useNodesInitialized.d.ts.map +1 -1
  15. package/dist/esm/hooks/useReactFlow.d.ts.map +1 -1
  16. package/dist/esm/index.js +120 -74
  17. package/dist/esm/index.mjs +120 -74
  18. package/dist/umd/components/BatchProvider/index.d.ts +17 -0
  19. package/dist/umd/components/BatchProvider/index.d.ts.map +1 -0
  20. package/dist/umd/components/BatchProvider/types.d.ts +7 -0
  21. package/dist/umd/components/BatchProvider/types.d.ts.map +1 -0
  22. package/dist/umd/components/BatchProvider/useQueue.d.ts +11 -0
  23. package/dist/umd/components/BatchProvider/useQueue.d.ts.map +1 -0
  24. package/dist/umd/components/ElementBatcher/index.d.ts +3 -0
  25. package/dist/umd/components/ElementBatcher/index.d.ts.map +1 -0
  26. package/dist/umd/components/ReactFlowProvider/index.d.ts.map +1 -1
  27. package/dist/umd/hooks/useNodesInitialized.d.ts.map +1 -1
  28. package/dist/umd/hooks/useReactFlow.d.ts.map +1 -1
  29. package/dist/umd/index.js +2 -2
  30. package/package.json +1 -1
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from 'react';
2
+ import { Queue } from './types';
3
+ import type { Edge, Node } from '../../types';
4
+ /**
5
+ * This is a context provider that holds and processes the node and edge update queues
6
+ * that are needed to handle setNodes, addNodes, setEdges and addEdges.
7
+ *
8
+ * @internal
9
+ */
10
+ export declare function BatchProvider<NodeType extends Node = Node, EdgeType extends Edge = Edge>({ children, }: {
11
+ children: ReactNode;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ export declare function useBatchContext(): {
14
+ nodeQueue: Queue<any>;
15
+ edgeQueue: Queue<any>;
16
+ };
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/BatchProvider/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,SAAS,EAAoC,MAAM,OAAO,CAAC;AAKnF,OAAO,EAAE,KAAK,EAAa,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAU9C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,EACxF,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;CACrB,2CAmDA;AAED,wBAAgB,eAAe;;;EAQ9B"}
@@ -0,0 +1,7 @@
1
+ export type QueueItem<T> = T[] | ((items: T[]) => T[]);
2
+ export type Queue<T> = {
3
+ get: () => QueueItem<T>[];
4
+ reset: () => void;
5
+ push: (item: QueueItem<T>) => void;
6
+ };
7
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/BatchProvider/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;AAEvD,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI;IACrB,GAAG,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACpC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { Queue, QueueItem } from './types';
2
+ /**
3
+ * This hook returns a queue that can be used to batch updates.
4
+ *
5
+ * @param runQueue - a function that gets called when the queue is flushed
6
+ * @internal
7
+ *
8
+ * @returns a Queue object
9
+ */
10
+ export declare function useQueue<T>(runQueue: (items: QueueItem<T>[]) => void): Queue<T>;
11
+ //# sourceMappingURL=useQueue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useQueue.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/BatchProvider/useQueue.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,YAsCpE"}
@@ -0,0 +1,3 @@
1
+ import type { Queue } from './types';
2
+ export declare function createQueue<T>(cb: () => void): Queue<T>;
3
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/BatchProvider/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,SAAS,CAAC;AAEhD,wBAAgB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAavD"}
@@ -0,0 +1,3 @@
1
+ import type { Edge, Node } from '../../types';
2
+ export declare function ElementBatcher<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): null;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/ElementBatcher/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAkB,MAAM,aAAa,CAAC;AAM9D,wBAAgB,cAAc,CAAC,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,SAAS,IAAI,GAAG,IAAI,UA6ExF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/ReactFlowProvider/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjD,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,YAAY,EAAE,KAAK,EACnB,YAAY,EAAE,KAAK,EACnB,YAAY,EACZ,YAAY,EACZ,YAAY,EAAE,KAAK,EACnB,aAAa,EAAE,MAAM,EACrB,OAAO,EACP,QAAQ,GACT,EAAE,sBAAsB,2CAcxB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/ReactFlowProvider/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKjD,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,YAAY,EAAE,KAAK,EACnB,YAAY,EAAE,KAAK,EACnB,YAAY,EACZ,YAAY,EACZ,YAAY,EAAE,KAAK,EACnB,aAAa,EAAE,MAAM,EACrB,OAAO,EACP,QAAQ,GACT,EAAE,sBAAsB,2CAkBxB"}
@@ -0,0 +1,3 @@
1
+ import type { Node, Edge } from '../types';
2
+ export declare function useElementBatching<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): void;
3
+ //# sourceMappingURL=useElementBatching.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useElementBatching.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/hooks/useElementBatching.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAI3C,wBAAgB,kBAAkB,CAAC,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,SAAS,IAAI,GAAG,IAAI,UAiF5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"useNodesInitialized.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/hooks/useNodesInitialized.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,0BAA0B,GAAG;IACvC,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAsBF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA2C,GAAG,OAAO,CAIjG"}
1
+ {"version":3,"file":"useNodesInitialized.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/hooks/useNodesInitialized.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,0BAA0B,GAAG;IACvC,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAsBF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA2C,GAAG,OAAO,CAIjG"}
@@ -1 +1 @@
1
- {"version":3,"file":"useReactFlow.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/hooks/useReactFlow.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,iBAAiB,EAAY,IAAI,EAAE,IAAI,EAAgB,MAAM,UAAU,CAAC;AAItF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,SAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,CAC3G,QAAQ,EACR,QAAQ,CACT,CA8UA"}
1
+ {"version":3,"file":"useReactFlow.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/hooks/useReactFlow.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,iBAAiB,EAAY,IAAI,EAAE,IAAI,EAAgB,MAAM,UAAU,CAAC;AAEtF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,SAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,CAC3G,QAAQ,EACR,QAAQ,CACT,CAwPA"}
package/dist/esm/index.js CHANGED
@@ -644,7 +644,8 @@ function getElementsDiffChanges({ items = [], lookup, }) {
644
644
  const changes = [];
645
645
  const itemsLookup = new Map(items.map((item) => [item.id, item]));
646
646
  for (const item of items) {
647
- const storeItem = lookup.get(item.id);
647
+ const lookupItem = lookup.get(item.id);
648
+ const storeItem = lookupItem?.internals?.userNode ?? lookupItem;
648
649
  if (storeItem !== undefined && storeItem !== item) {
649
650
  changes.push({ id: item.id, item: item, type: 'replace' });
650
651
  }
@@ -687,30 +688,22 @@ function fixedForwardRef(render) {
687
688
  const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
688
689
 
689
690
  /**
690
- * Hook for accessing the ReactFlow instance.
691
+ * This hook returns a queue that can be used to batch updates.
691
692
  *
692
- * @public
693
- * @returns ReactFlowInstance
693
+ * @param runQueue - a function that gets called when the queue is flushed
694
+ * @internal
695
+ *
696
+ * @returns a Queue object
694
697
  */
695
- function useReactFlow() {
696
- const viewportHelper = useViewportHelper();
697
- const store = useStoreApi();
698
- const getNodes = useCallback(() => store.getState().nodes.map((n) => ({ ...n })), []);
699
- const getInternalNode = useCallback((id) => store.getState().nodeLookup.get(id), []);
700
- const getNode = useCallback((id) => getInternalNode(id)?.internals.userNode, [getInternalNode]);
701
- const getEdges = useCallback(() => {
702
- const { edges = [] } = store.getState();
703
- return edges.map((e) => ({ ...e }));
704
- }, []);
705
- const getEdge = useCallback((id) => store.getState().edgeLookup.get(id), []);
706
- // A reference of all the batched updates to process before the next render. We
707
- // want a mutable reference here so multiple synchronous calls to `setNodes` etc
708
- // can be batched together.
709
- const setElementsQueue = useRef({ nodes: [], edges: [] });
698
+ function useQueue(runQueue) {
710
699
  // Because we're using a ref above, we need some way to let React know when to
711
700
  // actually process the queue. We flip this bit of state to `true` any time we
712
701
  // mutate the queue and then flip it back to `false` after flushing the queue.
713
- 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)));
714
707
  // Layout effects are guaranteed to run before the next render which means we
715
708
  // shouldn't run into any issues with stale state or weird issues that come from
716
709
  // rendering things one frame later than expected (we used to use `setTimeout`).
@@ -719,70 +712,123 @@ function useReactFlow() {
719
712
  // trigger the hook again (!). If the hook is being run again we know that any
720
713
  // updates should have been processed by now and we can safely clear the queue
721
714
  // and bail early.
722
- if (!shouldFlushQueue) {
723
- setElementsQueue.current = { nodes: [], edges: [] };
715
+ if (!shouldFlush) {
716
+ queue.reset();
724
717
  return;
725
718
  }
726
- if (setElementsQueue.current.nodes.length) {
727
- const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
728
- // This is essentially an `Array.reduce` in imperative clothing. Processing
729
- // this queue is a relatively hot path so we'd like to avoid the overhead of
730
- // array methods where we can.
731
- let next = nodes;
732
- for (const payload of setElementsQueue.current.nodes) {
733
- next = typeof payload === 'function' ? payload(next) : payload;
734
- }
735
- if (hasDefaultNodes) {
736
- setNodes(next);
737
- }
738
- else if (onNodesChange) {
739
- onNodesChange(getElementsDiffChanges({
740
- items: next,
741
- lookup: nodeLookup,
742
- }));
743
- }
744
- setElementsQueue.current.nodes = [];
745
- }
746
- if (setElementsQueue.current.edges.length) {
747
- const { edges = [], setEdges, hasDefaultEdges, onEdgesChange, edgeLookup } = store.getState();
748
- let next = edges;
749
- for (const payload of setElementsQueue.current.edges) {
750
- next = typeof payload === 'function' ? payload(next) : payload;
751
- }
752
- if (hasDefaultEdges) {
753
- setEdges(next);
754
- }
755
- else if (onEdgesChange) {
756
- onEdgesChange(getElementsDiffChanges({
757
- items: next,
758
- lookup: edgeLookup,
759
- }));
760
- }
761
- setElementsQueue.current.edges = [];
719
+ const queueItems = queue.get();
720
+ if (queueItems.length) {
721
+ runQueue(queueItems);
722
+ queue.reset();
762
723
  }
763
724
  // Beacuse we're using reactive state to trigger this effect, we need to flip
764
725
  // it back to false.
765
- setShouldFlushQueue(false);
766
- }, [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), []);
767
819
  const setNodes = useCallback((payload) => {
768
- setElementsQueue.current.nodes.push(payload);
769
- setShouldFlushQueue(true);
820
+ batchContext.nodeQueue.push(payload);
770
821
  }, []);
771
822
  const setEdges = useCallback((payload) => {
772
- setElementsQueue.current.edges.push(payload);
773
- setShouldFlushQueue(true);
823
+ batchContext.edgeQueue.push(payload);
774
824
  }, []);
775
825
  const addNodes = useCallback((payload) => {
776
826
  const newNodes = Array.isArray(payload) ? payload : [payload];
777
- // Queueing a functional update means that we won't worry about other calls
778
- // to `setNodes` that might happen elsewhere.
779
- setElementsQueue.current.nodes.push((nodes) => [...nodes, ...newNodes]);
780
- setShouldFlushQueue(true);
827
+ batchContext.nodeQueue.push((nodes) => [...nodes, ...newNodes]);
781
828
  }, []);
782
829
  const addEdges = useCallback((payload) => {
783
830
  const newEdges = Array.isArray(payload) ? payload : [payload];
784
- setElementsQueue.current.edges.push((edges) => [...edges, ...newEdges]);
785
- setShouldFlushQueue(true);
831
+ batchContext.edgeQueue.push((edges) => [...edges, ...newEdges]);
786
832
  }, []);
787
833
  const toObject = useCallback(() => {
788
834
  const { nodes = [], edges = [], transform } = store.getState();
@@ -2805,7 +2851,7 @@ function ReactFlowProvider({ initialNodes: nodes, initialEdges: edges, defaultNo
2805
2851
  height,
2806
2852
  fitView,
2807
2853
  }));
2808
- return jsx(Provider$1, { value: store, children: children });
2854
+ return (jsx(Provider$1, { value: store, children: jsx(BatchProvider, { children: children }) }));
2809
2855
  }
2810
2856
 
2811
2857
  function Wrapper({ children, nodes, edges, defaultNodes, defaultEdges, width, height, fitView, }) {
@@ -2980,9 +3026,9 @@ const selector$6 = (options) => (s) => {
2980
3026
  if (s.nodeLookup.size === 0) {
2981
3027
  return false;
2982
3028
  }
2983
- for (const [, node] of s.nodeLookup) {
2984
- if (options.includeHiddenNodes || !node.hidden) {
2985
- 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)) {
2986
3032
  return false;
2987
3033
  }
2988
3034
  }
@@ -644,7 +644,8 @@ function getElementsDiffChanges({ items = [], lookup, }) {
644
644
  const changes = [];
645
645
  const itemsLookup = new Map(items.map((item) => [item.id, item]));
646
646
  for (const item of items) {
647
- const storeItem = lookup.get(item.id);
647
+ const lookupItem = lookup.get(item.id);
648
+ const storeItem = lookupItem?.internals?.userNode ?? lookupItem;
648
649
  if (storeItem !== undefined && storeItem !== item) {
649
650
  changes.push({ id: item.id, item: item, type: 'replace' });
650
651
  }
@@ -687,30 +688,22 @@ function fixedForwardRef(render) {
687
688
  const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
688
689
 
689
690
  /**
690
- * Hook for accessing the ReactFlow instance.
691
+ * This hook returns a queue that can be used to batch updates.
691
692
  *
692
- * @public
693
- * @returns ReactFlowInstance
693
+ * @param runQueue - a function that gets called when the queue is flushed
694
+ * @internal
695
+ *
696
+ * @returns a Queue object
694
697
  */
695
- function useReactFlow() {
696
- const viewportHelper = useViewportHelper();
697
- const store = useStoreApi();
698
- const getNodes = useCallback(() => store.getState().nodes.map((n) => ({ ...n })), []);
699
- const getInternalNode = useCallback((id) => store.getState().nodeLookup.get(id), []);
700
- const getNode = useCallback((id) => getInternalNode(id)?.internals.userNode, [getInternalNode]);
701
- const getEdges = useCallback(() => {
702
- const { edges = [] } = store.getState();
703
- return edges.map((e) => ({ ...e }));
704
- }, []);
705
- const getEdge = useCallback((id) => store.getState().edgeLookup.get(id), []);
706
- // A reference of all the batched updates to process before the next render. We
707
- // want a mutable reference here so multiple synchronous calls to `setNodes` etc
708
- // can be batched together.
709
- const setElementsQueue = useRef({ nodes: [], edges: [] });
698
+ function useQueue(runQueue) {
710
699
  // Because we're using a ref above, we need some way to let React know when to
711
700
  // actually process the queue. We flip this bit of state to `true` any time we
712
701
  // mutate the queue and then flip it back to `false` after flushing the queue.
713
- 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)));
714
707
  // Layout effects are guaranteed to run before the next render which means we
715
708
  // shouldn't run into any issues with stale state or weird issues that come from
716
709
  // rendering things one frame later than expected (we used to use `setTimeout`).
@@ -719,70 +712,123 @@ function useReactFlow() {
719
712
  // trigger the hook again (!). If the hook is being run again we know that any
720
713
  // updates should have been processed by now and we can safely clear the queue
721
714
  // and bail early.
722
- if (!shouldFlushQueue) {
723
- setElementsQueue.current = { nodes: [], edges: [] };
715
+ if (!shouldFlush) {
716
+ queue.reset();
724
717
  return;
725
718
  }
726
- if (setElementsQueue.current.nodes.length) {
727
- const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
728
- // This is essentially an `Array.reduce` in imperative clothing. Processing
729
- // this queue is a relatively hot path so we'd like to avoid the overhead of
730
- // array methods where we can.
731
- let next = nodes;
732
- for (const payload of setElementsQueue.current.nodes) {
733
- next = typeof payload === 'function' ? payload(next) : payload;
734
- }
735
- if (hasDefaultNodes) {
736
- setNodes(next);
737
- }
738
- else if (onNodesChange) {
739
- onNodesChange(getElementsDiffChanges({
740
- items: next,
741
- lookup: nodeLookup,
742
- }));
743
- }
744
- setElementsQueue.current.nodes = [];
745
- }
746
- if (setElementsQueue.current.edges.length) {
747
- const { edges = [], setEdges, hasDefaultEdges, onEdgesChange, edgeLookup } = store.getState();
748
- let next = edges;
749
- for (const payload of setElementsQueue.current.edges) {
750
- next = typeof payload === 'function' ? payload(next) : payload;
751
- }
752
- if (hasDefaultEdges) {
753
- setEdges(next);
754
- }
755
- else if (onEdgesChange) {
756
- onEdgesChange(getElementsDiffChanges({
757
- items: next,
758
- lookup: edgeLookup,
759
- }));
760
- }
761
- setElementsQueue.current.edges = [];
719
+ const queueItems = queue.get();
720
+ if (queueItems.length) {
721
+ runQueue(queueItems);
722
+ queue.reset();
762
723
  }
763
724
  // Beacuse we're using reactive state to trigger this effect, we need to flip
764
725
  // it back to false.
765
- setShouldFlushQueue(false);
766
- }, [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), []);
767
819
  const setNodes = useCallback((payload) => {
768
- setElementsQueue.current.nodes.push(payload);
769
- setShouldFlushQueue(true);
820
+ batchContext.nodeQueue.push(payload);
770
821
  }, []);
771
822
  const setEdges = useCallback((payload) => {
772
- setElementsQueue.current.edges.push(payload);
773
- setShouldFlushQueue(true);
823
+ batchContext.edgeQueue.push(payload);
774
824
  }, []);
775
825
  const addNodes = useCallback((payload) => {
776
826
  const newNodes = Array.isArray(payload) ? payload : [payload];
777
- // Queueing a functional update means that we won't worry about other calls
778
- // to `setNodes` that might happen elsewhere.
779
- setElementsQueue.current.nodes.push((nodes) => [...nodes, ...newNodes]);
780
- setShouldFlushQueue(true);
827
+ batchContext.nodeQueue.push((nodes) => [...nodes, ...newNodes]);
781
828
  }, []);
782
829
  const addEdges = useCallback((payload) => {
783
830
  const newEdges = Array.isArray(payload) ? payload : [payload];
784
- setElementsQueue.current.edges.push((edges) => [...edges, ...newEdges]);
785
- setShouldFlushQueue(true);
831
+ batchContext.edgeQueue.push((edges) => [...edges, ...newEdges]);
786
832
  }, []);
787
833
  const toObject = useCallback(() => {
788
834
  const { nodes = [], edges = [], transform } = store.getState();
@@ -2805,7 +2851,7 @@ function ReactFlowProvider({ initialNodes: nodes, initialEdges: edges, defaultNo
2805
2851
  height,
2806
2852
  fitView,
2807
2853
  }));
2808
- return jsx(Provider$1, { value: store, children: children });
2854
+ return (jsx(Provider$1, { value: store, children: jsx(BatchProvider, { children: children }) }));
2809
2855
  }
2810
2856
 
2811
2857
  function Wrapper({ children, nodes, edges, defaultNodes, defaultEdges, width, height, fitView, }) {
@@ -2980,9 +3026,9 @@ const selector$6 = (options) => (s) => {
2980
3026
  if (s.nodeLookup.size === 0) {
2981
3027
  return false;
2982
3028
  }
2983
- for (const [, node] of s.nodeLookup) {
2984
- if (options.includeHiddenNodes || !node.hidden) {
2985
- 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)) {
2986
3032
  return false;
2987
3033
  }
2988
3034
  }
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from 'react';
2
+ import { Queue } from './types';
3
+ import type { Edge, Node } from '../../types';
4
+ /**
5
+ * This is a context provider that holds and processes the node and edge update queues
6
+ * that are needed to handle setNodes, addNodes, setEdges and addEdges.
7
+ *
8
+ * @internal
9
+ */
10
+ export declare function BatchProvider<NodeType extends Node = Node, EdgeType extends Edge = Edge>({ children, }: {
11
+ children: ReactNode;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ export declare function useBatchContext(): {
14
+ nodeQueue: Queue<any>;
15
+ edgeQueue: Queue<any>;
16
+ };
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/BatchProvider/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,SAAS,EAAoC,MAAM,OAAO,CAAC;AAKnF,OAAO,EAAE,KAAK,EAAa,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAU9C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,EACxF,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;CACrB,2CAmDA;AAED,wBAAgB,eAAe;;;EAQ9B"}
@@ -0,0 +1,7 @@
1
+ export type QueueItem<T> = T[] | ((items: T[]) => T[]);
2
+ export type Queue<T> = {
3
+ get: () => QueueItem<T>[];
4
+ reset: () => void;
5
+ push: (item: QueueItem<T>) => void;
6
+ };
7
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/BatchProvider/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;AAEvD,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI;IACrB,GAAG,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACpC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { Queue, QueueItem } from './types';
2
+ /**
3
+ * This hook returns a queue that can be used to batch updates.
4
+ *
5
+ * @param runQueue - a function that gets called when the queue is flushed
6
+ * @internal
7
+ *
8
+ * @returns a Queue object
9
+ */
10
+ export declare function useQueue<T>(runQueue: (items: QueueItem<T>[]) => void): Queue<T>;
11
+ //# sourceMappingURL=useQueue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useQueue.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/BatchProvider/useQueue.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,YAsCpE"}
@@ -0,0 +1,3 @@
1
+ import type { Edge, Node } from '../../types';
2
+ export declare function ElementBatcher<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): null;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/components/ElementBatcher/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAkB,MAAM,aAAa,CAAC;AAM9D,wBAAgB,cAAc,CAAC,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,SAAS,IAAI,GAAG,IAAI,UA6ExF"}