@xyflow/react 12.0.0-next.5 → 12.0.0-next.6

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.
@@ -1,6 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import type { ControlProps } from './types';
3
- declare function ControlsComponent({ style, showZoom, showFitView, showInteractive, fitViewOptions, onZoomIn, onZoomOut, onFitView, onInteractiveChange, className, children, position, }: ControlProps): import("react/jsx-runtime").JSX.Element;
3
+ declare function ControlsComponent({ style, showZoom, showFitView, showInteractive, fitViewOptions, onZoomIn, onZoomOut, onFitView, onInteractiveChange, className, children, position, 'aria-label': ariaLabel, }: ControlProps): import("react/jsx-runtime").JSX.Element;
4
4
  declare namespace ControlsComponent {
5
5
  var displayName: string;
6
6
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Controls.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/additional-components/Controls/Controls.tsx"],"names":[],"mappings":";AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAQ5C,iBAAS,iBAAiB,CAAC,EACzB,KAAK,EACL,QAAe,EACf,WAAkB,EAClB,eAAsB,EACtB,cAAc,EACd,QAAQ,EACR,SAAS,EACT,SAAS,EACT,mBAAmB,EACnB,SAAS,EACT,QAAQ,EACR,QAAwB,GACzB,EAAE,YAAY,2CAkFd;kBA/FQ,iBAAiB;;;AAmG1B,eAAO,MAAM,QAAQ,+DAA0B,CAAC"}
1
+ {"version":3,"file":"Controls.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/additional-components/Controls/Controls.tsx"],"names":[],"mappings":";AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAQ5C,iBAAS,iBAAiB,CAAC,EACzB,KAAK,EACL,QAAe,EACf,WAAkB,EAClB,eAAsB,EACtB,cAAc,EACd,QAAQ,EACR,SAAS,EACT,SAAS,EACT,mBAAmB,EACnB,SAAS,EACT,QAAQ,EACR,QAAwB,EACxB,YAAY,EAAE,SAAiC,GAChD,EAAE,YAAY,2CAmFd;kBAjGQ,iBAAiB;;;AAqG1B,eAAO,MAAM,QAAQ,+DAA0B,CAAC"}
@@ -1,7 +1,7 @@
1
- import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from 'react';
1
+ import type { ButtonHTMLAttributes, ReactNode } from 'react';
2
2
  import type { PanelPosition } from '@xyflow/system';
3
3
  import type { FitViewOptions } from '../../types';
4
- export type ControlProps = HTMLAttributes<HTMLDivElement> & {
4
+ export type ControlProps = {
5
5
  showZoom?: boolean;
6
6
  showFitView?: boolean;
7
7
  showInteractive?: boolean;
@@ -12,6 +12,9 @@ export type ControlProps = HTMLAttributes<HTMLDivElement> & {
12
12
  onInteractiveChange?: (interactiveStatus: boolean) => void;
13
13
  position?: PanelPosition;
14
14
  children?: ReactNode;
15
+ style?: React.CSSProperties;
16
+ className?: string;
17
+ 'aria-label'?: string;
15
18
  };
16
19
  export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
17
20
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/additional-components/Controls/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,cAAc,CAAC,GAAG;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,mBAAmB,CAAC,EAAE,CAAC,iBAAiB,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3D,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,iBAAiB,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../packages/react/src/additional-components/Controls/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,mBAAmB,CAAC,EAAE,CAAC,iBAAiB,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3D,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,iBAAiB,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useReactFlow.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/hooks/useReactFlow.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,iBAAiB,EAQjB,IAAI,EACJ,IAAI,EACL,MAAM,UAAU,CAAC;AAGlB;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,SAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,CAC3G,QAAQ,EACR,QAAQ,CACT,CA+QA"}
1
+ {"version":3,"file":"useReactFlow.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/hooks/useReactFlow.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,iBAAiB,EAIjB,IAAI,EACJ,IAAI,EAGL,MAAM,UAAU,CAAC;AAGlB;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,SAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,SAAS,IAAI,GAAG,IAAI,KAAK,iBAAiB,CAC3G,QAAQ,EACR,QAAQ,CACT,CAqSA"}
package/dist/esm/index.js CHANGED
@@ -496,97 +496,102 @@ function handleParentExpand(updatedElements, updateItem) {
496
496
  // When you drag a node for example, React Flow will send a position change update.
497
497
  // This function then applies the changes and returns the updated elements.
498
498
  function applyChanges(changes, elements) {
499
- // we need this hack to handle the setNodes and setEdges function of the useReactFlow hook for controlled flows
500
- if (changes.some((c) => c.type === 'reset')) {
501
- return changes.filter((c) => c.type === 'reset').map((c) => c.item);
502
- }
503
- let remainingChanges = [];
504
499
  const updatedElements = [];
500
+ // By storing a map of changes for each element, we can a quick lookup as we
501
+ // iterate over the elements array!
502
+ const changesMap = new Map();
505
503
  for (const change of changes) {
506
504
  if (change.type === 'add') {
507
505
  updatedElements.push(change.item);
506
+ continue;
508
507
  }
509
- else {
510
- remainingChanges.push(change);
508
+ else if (change.type === 'remove' || change.type === 'replace') {
509
+ // For a 'remove' change we can safely ignore any other changes queued for
510
+ // the same element, it's going to be removed anyway!
511
+ changesMap.set(change.id, [change]);
511
512
  }
512
- }
513
- for (const item of elements) {
514
- const nextChanges = [];
515
- const _remainingChanges = [];
516
- for (const change of remainingChanges) {
517
- if (change.id === item.id) {
518
- nextChanges.push(change);
513
+ else {
514
+ const elementChanges = changesMap.get(change.id);
515
+ if (elementChanges) {
516
+ // If we have some changes queued already, we can do a mutable update of
517
+ // that array and save ourselves some copying.
518
+ elementChanges.push(change);
519
519
  }
520
520
  else {
521
- _remainingChanges.push(change);
521
+ changesMap.set(change.id, [change]);
522
522
  }
523
523
  }
524
- remainingChanges = _remainingChanges;
525
- if (nextChanges.length === 0) {
526
- updatedElements.push(item);
524
+ }
525
+ for (const element of elements) {
526
+ const changes = changesMap.get(element.id);
527
+ // When there are no changes for an element we can just push it unmodified,
528
+ // no need to copy it.
529
+ if (!changes) {
530
+ updatedElements.push(element);
527
531
  continue;
528
532
  }
529
- const updateItem = { ...item };
530
- let isDeletion = false;
531
- for (const currentChange of nextChanges) {
532
- if (currentChange) {
533
- switch (currentChange.type) {
534
- case 'select': {
535
- updateItem.selected = currentChange.selected;
536
- break;
537
- }
538
- case 'position': {
539
- if (typeof currentChange.position !== 'undefined') {
540
- updateItem.position = currentChange.position;
541
- }
542
- if (typeof currentChange.positionAbsolute !== 'undefined') {
543
- if (!updateItem.computed) {
544
- updateItem.computed = {};
545
- }
546
- updateItem.computed.positionAbsolute = currentChange.positionAbsolute;
547
- }
548
- if (typeof currentChange.dragging !== 'undefined') {
549
- updateItem.dragging = currentChange.dragging;
550
- }
551
- if (updateItem.expandParent) {
552
- handleParentExpand(updatedElements, updateItem);
553
- }
554
- break;
555
- }
556
- case 'dimensions': {
557
- if (typeof currentChange.dimensions !== 'undefined') {
558
- if (!updateItem.computed) {
559
- updateItem.computed = {};
560
- }
561
- updateItem.computed.width = currentChange.dimensions.width;
562
- updateItem.computed.height = currentChange.dimensions.height;
563
- // this is needed for the node resizer to work
564
- if (currentChange.resizing) {
565
- updateItem.width = currentChange.dimensions.width;
566
- updateItem.height = currentChange.dimensions.height;
567
- }
568
- }
569
- if (typeof currentChange.resizing === 'boolean') {
570
- updateItem.resizing = currentChange.resizing;
571
- }
572
- if (updateItem.expandParent) {
573
- handleParentExpand(updatedElements, updateItem);
574
- }
575
- break;
576
- }
577
- case 'remove': {
578
- isDeletion = true;
579
- continue;
580
- }
581
- }
582
- }
533
+ // If we have a 'remove' change queued, it'll be the only change in the array
534
+ if (changes[0].type === 'remove') {
535
+ continue;
536
+ }
537
+ if (changes[0].type === 'replace') {
538
+ updatedElements.push({ ...changes[0].item });
539
+ continue;
583
540
  }
584
- if (!isDeletion) {
585
- updatedElements.push(updateItem);
541
+ // For other types of changes, we want to start with a shallow copy of the
542
+ // object so React knows this element has changed. Sequential changes will
543
+ /// each _mutate_ this object, so there's only ever one copy.
544
+ const updatedElement = { ...element };
545
+ for (const change of changes) {
546
+ applyChange(change, updatedElement, elements);
586
547
  }
548
+ updatedElements.push(updatedElement);
587
549
  }
588
550
  return updatedElements;
589
551
  }
552
+ // Applies a single change to an element. This is a *mutable* update.
553
+ function applyChange(change, element, elements = []) {
554
+ switch (change.type) {
555
+ case 'select': {
556
+ element.selected = change.selected;
557
+ break;
558
+ }
559
+ case 'position': {
560
+ if (typeof change.position !== 'undefined') {
561
+ element.position = change.position;
562
+ }
563
+ if (typeof change.positionAbsolute !== 'undefined') {
564
+ element.computed ??= {};
565
+ element.computed.positionAbsolute = change.positionAbsolute;
566
+ }
567
+ if (typeof change.dragging !== 'undefined') {
568
+ element.dragging = change.dragging;
569
+ }
570
+ if (element.expandParent) {
571
+ handleParentExpand(elements, element);
572
+ }
573
+ break;
574
+ }
575
+ case 'dimensions': {
576
+ if (typeof change.dimensions !== 'undefined') {
577
+ element.computed ??= {};
578
+ element.computed.width = change.dimensions.width;
579
+ element.computed.height = change.dimensions.height;
580
+ if (change.resizing) {
581
+ element.width = change.dimensions.width;
582
+ element.height = change.dimensions.height;
583
+ }
584
+ }
585
+ if (typeof change.resizing === 'boolean') {
586
+ element.resizing = change.resizing;
587
+ }
588
+ if (element.expandParent) {
589
+ handleParentExpand(elements, element);
590
+ }
591
+ break;
592
+ }
593
+ }
594
+ }
590
595
  /**
591
596
  * Drop in function that applies node changes to an array of nodes.
592
597
  * @public
@@ -655,6 +660,26 @@ function getSelectionChanges(items, selectedIds = new Set(), mutateItem = false)
655
660
  }
656
661
  return changes;
657
662
  }
663
+ function getElementsDiffChanges({ items = [], lookup, }) {
664
+ const changes = [];
665
+ const itemsLookup = new Map(items.map((item) => [item.id, item]));
666
+ for (const item of items) {
667
+ const storeItem = lookup.get(item.id);
668
+ if (storeItem !== undefined && storeItem !== item) {
669
+ changes.push({ id: item.id, item: item, type: 'replace' });
670
+ }
671
+ if (storeItem === undefined) {
672
+ changes.push({ item: item, type: 'add' });
673
+ }
674
+ }
675
+ for (const [id] of lookup) {
676
+ const nextNode = itemsLookup.get(id);
677
+ if (nextNode === undefined) {
678
+ changes.push({ id, type: 'remove' });
679
+ }
680
+ }
681
+ return changes;
682
+ }
658
683
 
659
684
  /**
660
685
  * Test whether an object is useable as a Node
@@ -739,31 +764,50 @@ function useReactFlow() {
739
764
  const { edges = [] } = store.getState();
740
765
  return edges.find((e) => e.id === id);
741
766
  }, []);
767
+ // this is used to handle multiple syncronous setNodes calls
768
+ const setNodesData = useRef();
769
+ const setNodesTimeout = useRef();
742
770
  const setNodes = useCallback((payload) => {
743
- const { nodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState();
744
- const nextNodes = typeof payload === 'function' ? payload(nodes) : payload;
745
- if (hasDefaultNodes) {
746
- setNodes(nextNodes);
747
- }
748
- else if (onNodesChange) {
749
- const changes = nextNodes.length === 0
750
- ? nodes.map((node) => ({ type: 'remove', id: node.id }))
751
- : nextNodes.map((node) => ({ item: node, type: 'reset' }));
752
- onNodesChange(changes);
753
- }
771
+ const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
772
+ const nextNodes = typeof payload === 'function' ? payload(setNodesData.current || nodes) : payload;
773
+ setNodesData.current = nextNodes;
774
+ if (setNodesTimeout.current) {
775
+ clearTimeout(setNodesTimeout.current);
776
+ }
777
+ // if there are multiple synchronous setNodes calls, we only want to call onNodesChange once
778
+ // for this, we use a timeout to wait for the last call and store updated nodes in setNodesData
779
+ // this is not perfect, but should work in most cases
780
+ setNodesTimeout.current = setTimeout(() => {
781
+ if (hasDefaultNodes) {
782
+ setNodes(nextNodes);
783
+ }
784
+ else if (onNodesChange) {
785
+ const changes = getElementsDiffChanges({ items: setNodesData.current, lookup: nodeLookup });
786
+ onNodesChange(changes);
787
+ }
788
+ setNodesData.current = undefined;
789
+ }, 0);
754
790
  }, []);
791
+ // this is used to handle multiple syncronous setEdges calls
792
+ const setEdgesData = useRef();
793
+ const setEdgesTimeout = useRef();
755
794
  const setEdges = useCallback((payload) => {
756
- const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
757
- const nextEdges = typeof payload === 'function' ? payload(edges) : payload;
758
- if (hasDefaultEdges) {
759
- setEdges(nextEdges);
760
- }
761
- else if (onEdgesChange) {
762
- const changes = nextEdges.length === 0
763
- ? edges.map((edge) => ({ type: 'remove', id: edge.id }))
764
- : nextEdges.map((edge) => ({ item: edge, type: 'reset' }));
765
- onEdgesChange(changes);
795
+ const { edges = [], setEdges, hasDefaultEdges, onEdgesChange, edgeLookup } = store.getState();
796
+ const nextEdges = typeof payload === 'function' ? payload(setEdgesData.current || edges) : payload;
797
+ setEdgesData.current = nextEdges;
798
+ if (setEdgesTimeout.current) {
799
+ clearTimeout(setEdgesTimeout.current);
766
800
  }
801
+ setEdgesTimeout.current = setTimeout(() => {
802
+ if (hasDefaultEdges) {
803
+ setEdges(nextEdges);
804
+ }
805
+ else if (onEdgesChange) {
806
+ const changes = getElementsDiffChanges({ items: nextEdges, lookup: edgeLookup });
807
+ onEdgesChange(changes);
808
+ }
809
+ setEdgesData.current = undefined;
810
+ }, 0);
767
811
  }, []);
768
812
  const addNodes = useCallback((payload) => {
769
813
  const nodes = Array.isArray(payload) ? payload : [payload];
@@ -801,8 +845,8 @@ function useReactFlow() {
801
845
  },
802
846
  };
803
847
  }, []);
804
- const deleteElements = useCallback(async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [], onBeforeDelete }) => {
805
- const { nodes, edges, hasDefaultNodes, hasDefaultEdges, onNodesDelete, onEdgesDelete, onNodesChange, onEdgesChange, onDelete, } = store.getState();
848
+ const deleteElements = useCallback(async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [] }) => {
849
+ const { nodes, edges, hasDefaultNodes, hasDefaultEdges, onNodesDelete, onEdgesDelete, onNodesChange, onEdgesChange, onDelete, onBeforeDelete, } = store.getState();
806
850
  const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
807
851
  nodesToRemove,
808
852
  edgesToRemove,
@@ -814,9 +858,8 @@ function useReactFlow() {
814
858
  const hasMatchingNodes = matchingNodes.length > 0;
815
859
  if (hasMatchingEdges) {
816
860
  if (hasDefaultEdges) {
817
- store.setState({
818
- edges: edges.filter((e) => !matchingEdges.some((mE) => mE.id === e.id)),
819
- });
861
+ const nextEdges = edges.filter((e) => !matchingEdges.some((mE) => mE.id === e.id));
862
+ store.getState().setEdges(nextEdges);
820
863
  }
821
864
  onEdgesDelete?.(matchingEdges);
822
865
  onEdgesChange?.(matchingEdges.map((edge) => ({
@@ -826,9 +869,8 @@ function useReactFlow() {
826
869
  }
827
870
  if (hasMatchingNodes) {
828
871
  if (hasDefaultNodes) {
829
- store.setState({
830
- nodes: nodes.filter((n) => !matchingNodes.some((mN) => mN.id === n.id)),
831
- });
872
+ const nextNodes = nodes.filter((n) => !matchingNodes.some((mN) => mN.id === n.id));
873
+ store.getState().setNodes(nextNodes);
832
874
  }
833
875
  onNodesDelete?.(matchingNodes);
834
876
  onNodesChange?.(matchingNodes.map((node) => ({ id: node.id, type: 'remove' })));
@@ -934,8 +976,8 @@ function useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode, }) {
934
976
  const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
935
977
  useEffect(() => {
936
978
  if (deleteKeyPressed) {
937
- const { edges, nodes, onBeforeDelete } = store.getState();
938
- deleteElements({ nodes: nodes.filter(selected), edges: edges.filter(selected), onBeforeDelete });
979
+ const { edges, nodes } = store.getState();
980
+ deleteElements({ nodes: nodes.filter(selected), edges: edges.filter(selected) });
939
981
  store.setState({ nodesSelectionActive: false });
940
982
  }
941
983
  }, [deleteKeyPressed]);
@@ -3120,7 +3162,7 @@ const selector$3 = (s) => ({
3120
3162
  minZoomReached: s.transform[2] <= s.minZoom,
3121
3163
  maxZoomReached: s.transform[2] >= s.maxZoom,
3122
3164
  });
3123
- function ControlsComponent({ style, showZoom = true, showFitView = true, showInteractive = true, fitViewOptions, onZoomIn, onZoomOut, onFitView, onInteractiveChange, className, children, position = 'bottom-left', }) {
3165
+ function ControlsComponent({ style, showZoom = true, showFitView = true, showInteractive = true, fitViewOptions, onZoomIn, onZoomOut, onFitView, onInteractiveChange, className, children, position = 'bottom-left', 'aria-label': ariaLabel = 'React Flow controls', }) {
3124
3166
  const store = useStoreApi();
3125
3167
  const { isInteractive, minZoomReached, maxZoomReached } = useStore(selector$3, shallow);
3126
3168
  const { zoomIn, zoomOut, fitView } = useReactFlow();
@@ -3144,7 +3186,7 @@ function ControlsComponent({ style, showZoom = true, showFitView = true, showInt
3144
3186
  });
3145
3187
  onInteractiveChange?.(!isInteractive);
3146
3188
  };
3147
- return (jsxs(Panel, { className: cc(['react-flow__controls', className]), position: position, style: style, "data-testid": "rf__controls", children: [showZoom && (jsxs(Fragment, { children: [jsx(ControlButton, { onClick: onZoomInHandler, className: "react-flow__controls-zoomin", title: "zoom in", "aria-label": "zoom in", disabled: maxZoomReached, children: jsx(PlusIcon, {}) }), jsx(ControlButton, { onClick: onZoomOutHandler, className: "react-flow__controls-zoomout", title: "zoom out", "aria-label": "zoom out", disabled: minZoomReached, children: jsx(MinusIcon, {}) })] })), showFitView && (jsx(ControlButton, { className: "react-flow__controls-fitview", onClick: onFitViewHandler, title: "fit view", "aria-label": "fit view", children: jsx(FitViewIcon, {}) })), showInteractive && (jsx(ControlButton, { className: "react-flow__controls-interactive", onClick: onToggleInteractivity, title: "toggle interactivity", "aria-label": "toggle interactivity", children: isInteractive ? jsx(UnlockIcon, {}) : jsx(LockIcon, {}) })), children] }));
3189
+ return (jsxs(Panel, { className: cc(['react-flow__controls', className]), position: position, style: style, "data-testid": "rf__controls", "aria-label": ariaLabel, children: [showZoom && (jsxs(Fragment, { children: [jsx(ControlButton, { onClick: onZoomInHandler, className: "react-flow__controls-zoomin", title: "zoom in", "aria-label": "zoom in", disabled: maxZoomReached, children: jsx(PlusIcon, {}) }), jsx(ControlButton, { onClick: onZoomOutHandler, className: "react-flow__controls-zoomout", title: "zoom out", "aria-label": "zoom out", disabled: minZoomReached, children: jsx(MinusIcon, {}) })] })), showFitView && (jsx(ControlButton, { className: "react-flow__controls-fitview", onClick: onFitViewHandler, title: "fit view", "aria-label": "fit view", children: jsx(FitViewIcon, {}) })), showInteractive && (jsx(ControlButton, { className: "react-flow__controls-interactive", onClick: onToggleInteractivity, title: "toggle interactivity", "aria-label": "toggle interactivity", children: isInteractive ? jsx(UnlockIcon, {}) : jsx(LockIcon, {}) })), children] }));
3148
3190
  }
3149
3191
  ControlsComponent.displayName = 'Controls';
3150
3192
  const Controls = memo(ControlsComponent);