@xyflow/react 12.0.0-next.4 → 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.
Files changed (53) hide show
  1. package/dist/esm/additional-components/Controls/Controls.d.ts +1 -1
  2. package/dist/esm/additional-components/Controls/Controls.d.ts.map +1 -1
  3. package/dist/esm/additional-components/Controls/types.d.ts +5 -2
  4. package/dist/esm/additional-components/Controls/types.d.ts.map +1 -1
  5. package/dist/esm/additional-components/NodeResizer/NodeResizeControl.d.ts +7 -0
  6. package/dist/esm/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -0
  7. package/dist/esm/additional-components/NodeResizer/NodeResizer.d.ts +2 -2
  8. package/dist/esm/additional-components/NodeResizer/NodeResizer.d.ts.map +1 -1
  9. package/dist/esm/additional-components/NodeResizer/ResizeControl.d.ts.map +1 -1
  10. package/dist/esm/additional-components/NodeResizer/index.d.ts +2 -2
  11. package/dist/esm/additional-components/NodeResizer/index.d.ts.map +1 -1
  12. package/dist/esm/additional-components/NodeResizer/types.d.ts +1 -23
  13. package/dist/esm/additional-components/NodeResizer/types.d.ts.map +1 -1
  14. package/dist/esm/container/ReactFlow/index.d.ts +1 -1
  15. package/dist/esm/hooks/useReactFlow.d.ts.map +1 -1
  16. package/dist/esm/index.d.ts +1 -1
  17. package/dist/esm/index.d.ts.map +1 -1
  18. package/dist/esm/index.js +245 -289
  19. package/dist/esm/index.mjs +245 -289
  20. package/dist/esm/types/changes.d.ts +8 -7
  21. package/dist/esm/types/changes.d.ts.map +1 -1
  22. package/dist/esm/types/component-props.d.ts +1 -1
  23. package/dist/esm/types/instance.d.ts +1 -2
  24. package/dist/esm/types/instance.d.ts.map +1 -1
  25. package/dist/esm/utils/changes.d.ts +20 -2
  26. package/dist/esm/utils/changes.d.ts.map +1 -1
  27. package/dist/style.css +3 -2
  28. package/dist/umd/additional-components/Controls/Controls.d.ts +1 -1
  29. package/dist/umd/additional-components/Controls/Controls.d.ts.map +1 -1
  30. package/dist/umd/additional-components/Controls/types.d.ts +5 -2
  31. package/dist/umd/additional-components/Controls/types.d.ts.map +1 -1
  32. package/dist/umd/additional-components/NodeResizer/NodeResizeControl.d.ts +7 -0
  33. package/dist/umd/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -0
  34. package/dist/umd/additional-components/NodeResizer/NodeResizer.d.ts +2 -2
  35. package/dist/umd/additional-components/NodeResizer/NodeResizer.d.ts.map +1 -1
  36. package/dist/umd/additional-components/NodeResizer/ResizeControl.d.ts.map +1 -1
  37. package/dist/umd/additional-components/NodeResizer/index.d.ts +2 -2
  38. package/dist/umd/additional-components/NodeResizer/index.d.ts.map +1 -1
  39. package/dist/umd/additional-components/NodeResizer/types.d.ts +1 -23
  40. package/dist/umd/additional-components/NodeResizer/types.d.ts.map +1 -1
  41. package/dist/umd/container/ReactFlow/index.d.ts +1 -1
  42. package/dist/umd/hooks/useReactFlow.d.ts.map +1 -1
  43. package/dist/umd/index.d.ts +1 -1
  44. package/dist/umd/index.d.ts.map +1 -1
  45. package/dist/umd/index.js +2 -2
  46. package/dist/umd/types/changes.d.ts +8 -7
  47. package/dist/umd/types/changes.d.ts.map +1 -1
  48. package/dist/umd/types/component-props.d.ts +1 -1
  49. package/dist/umd/types/instance.d.ts +1 -2
  50. package/dist/umd/types/instance.d.ts.map +1 -1
  51. package/dist/umd/utils/changes.d.ts +20 -2
  52. package/dist/umd/utils/changes.d.ts.map +1 -1
  53. package/package.json +4 -11
package/dist/esm/index.js CHANGED
@@ -2,13 +2,11 @@
2
2
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
3
  import { createContext, useContext, useMemo, useEffect, useRef, useState, useCallback, forwardRef, memo } from 'react';
4
4
  import cc from 'classcat';
5
- import { errorMessages, infiniteExtent, isInputDOMNode, fitView, getViewportForBounds, pointToRendererPoint, rendererPointToPoint, isNodeBase, isEdgeBase, getOutgoersBase, getIncomersBase, addEdgeBase, updateEdgeBase, getConnectedEdgesBase, getElementsToRemove, isRectObject, nodeToRect, getOverlappingArea, getDimensions, XYPanZoom, PanOnScrollMode, SelectionMode, getEventPosition, getNodesInside, XYDrag, snapPosition, calcNextPosition, Position, isMouseEvent, XYHandle, getHostForElement, getNodesBounds, clampPosition, internalsSymbol, getPositionWithOrigin, elementSelectionKeys, isEdgeVisible, MarkerType, createMarkerIds, isNumeric, getBezierEdgeCenter, getSmoothStepPath, getStraightPath, getBezierPath, getEdgePosition, getElevatedEdgeZIndex, getMarkerId, ConnectionMode, ConnectionLineType, updateConnectionLookup, adoptUserProvidedNodes, updateNodeDimensions, updateAbsolutePositions, panBy, isMacOs, areConnectionMapsEqual, handleConnectionChange, getNodePositionWithOrigin, XYMinimap, getBoundsOfRects, getPointerPosition, clamp, getNodeToolbarTransform } from '@xyflow/system';
5
+ import { errorMessages, infiniteExtent, isInputDOMNode, fitView, getViewportForBounds, pointToRendererPoint, rendererPointToPoint, isNodeBase, isEdgeBase, getOutgoersBase, getIncomersBase, addEdgeBase, updateEdgeBase, getConnectedEdgesBase, getElementsToRemove, isRectObject, nodeToRect, getOverlappingArea, getDimensions, XYPanZoom, PanOnScrollMode, SelectionMode, getEventPosition, getNodesInside, XYDrag, snapPosition, calcNextPosition, Position, isMouseEvent, XYHandle, getHostForElement, getNodesBounds, clampPosition, internalsSymbol, getPositionWithOrigin, elementSelectionKeys, isEdgeVisible, MarkerType, createMarkerIds, isNumeric, getBezierEdgeCenter, getSmoothStepPath, getStraightPath, getBezierPath, getEdgePosition, getElevatedEdgeZIndex, getMarkerId, ConnectionMode, ConnectionLineType, updateConnectionLookup, adoptUserProvidedNodes, updateNodeDimensions, updateAbsolutePositions, panBy, isMacOs, areConnectionMapsEqual, handleConnectionChange, getNodePositionWithOrigin, XYMinimap, getBoundsOfRects, ResizeControlVariant, XYResizer, XY_RESIZER_LINE_POSITIONS, XY_RESIZER_HANDLE_POSITIONS, getNodeToolbarTransform } from '@xyflow/system';
6
6
  export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, SelectionMode, getBezierEdgeCenter, getBezierPath, getEdgeCenter, getNodesBounds, getSmoothStepPath, getStraightPath, getViewportForBounds, internalsSymbol } from '@xyflow/system';
7
7
  import { useStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional';
8
8
  import { shallow } from 'zustand/shallow';
9
9
  import { createPortal } from 'react-dom';
10
- import { drag } from 'd3-drag';
11
- import { select } from 'd3-selection';
12
10
 
13
11
  const StoreContext = createContext(null);
14
12
  const Provider$1 = StoreContext.Provider;
@@ -456,38 +454,41 @@ const useViewportHelper = () => {
456
454
  return viewportHelperFunctions;
457
455
  };
458
456
 
459
- function handleParentExpand(res, updateItem) {
460
- const parent = res.find((e) => e.id === updateItem.parentNode);
461
- if (parent) {
462
- if (!parent.computed) {
463
- parent.computed = {};
464
- }
465
- const extendWidth = updateItem.position.x + updateItem.computed.width - parent.computed.width;
466
- const extendHeight = updateItem.position.y + updateItem.computed.height - parent.computed.height;
467
- if (extendWidth > 0 || extendHeight > 0 || updateItem.position.x < 0 || updateItem.position.y < 0) {
468
- parent.style = { ...parent.style } || {};
469
- parent.style.width = parent.style.width ?? parent.computed.width;
470
- parent.style.height = parent.style.height ?? parent.computed.height;
471
- if (extendWidth > 0) {
472
- parent.style.width += extendWidth;
457
+ function handleParentExpand(updatedElements, updateItem) {
458
+ for (const [index, item] of updatedElements.entries()) {
459
+ if (item.id === updateItem.parentNode) {
460
+ const parent = { ...item };
461
+ if (!parent.computed) {
462
+ parent.computed = {};
473
463
  }
474
- if (extendHeight > 0) {
475
- parent.style.height += extendHeight;
476
- }
477
- if (updateItem.position.x < 0) {
478
- const xDiff = Math.abs(updateItem.position.x);
479
- parent.position.x = parent.position.x - xDiff;
480
- parent.style.width += xDiff;
481
- updateItem.position.x = 0;
482
- }
483
- if (updateItem.position.y < 0) {
484
- const yDiff = Math.abs(updateItem.position.y);
485
- parent.position.y = parent.position.y - yDiff;
486
- parent.style.height += yDiff;
487
- updateItem.position.y = 0;
464
+ const extendWidth = updateItem.position.x + updateItem.computed.width - parent.computed.width;
465
+ const extendHeight = updateItem.position.y + updateItem.computed.height - parent.computed.height;
466
+ if (extendWidth > 0 || extendHeight > 0 || updateItem.position.x < 0 || updateItem.position.y < 0) {
467
+ parent.width = parent.width ?? parent.computed.width;
468
+ parent.height = parent.height ?? parent.computed.height;
469
+ if (extendWidth > 0) {
470
+ parent.width += extendWidth;
471
+ }
472
+ if (extendHeight > 0) {
473
+ parent.height += extendHeight;
474
+ }
475
+ if (updateItem.position.x < 0) {
476
+ const xDiff = Math.abs(updateItem.position.x);
477
+ parent.position.x = parent.position.x - xDiff;
478
+ parent.width += xDiff;
479
+ updateItem.position.x = 0;
480
+ }
481
+ if (updateItem.position.y < 0) {
482
+ const yDiff = Math.abs(updateItem.position.y);
483
+ parent.position.y = parent.position.y - yDiff;
484
+ parent.height += yDiff;
485
+ updateItem.position.y = 0;
486
+ }
487
+ parent.computed.width = parent.width;
488
+ parent.computed.height = parent.height;
489
+ updatedElements[index] = parent;
488
490
  }
489
- parent.computed.width = parent.style.width;
490
- parent.computed.height = parent.style.height;
491
+ break;
491
492
  }
492
493
  }
493
494
  }
@@ -495,90 +496,101 @@ function handleParentExpand(res, updateItem) {
495
496
  // When you drag a node for example, React Flow will send a position change update.
496
497
  // This function then applies the changes and returns the updated elements.
497
498
  function applyChanges(changes, elements) {
498
- // we need this hack to handle the setNodes and setEdges function of the useReactFlow hook for controlled flows
499
- if (changes.some((c) => c.type === 'reset')) {
500
- return changes.filter((c) => c.type === 'reset').map((c) => c.item);
501
- }
502
- let remainingChanges = [];
503
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();
504
503
  for (const change of changes) {
505
504
  if (change.type === 'add') {
506
505
  updatedElements.push(change.item);
506
+ continue;
507
507
  }
508
- else {
509
- 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]);
510
512
  }
511
- }
512
- for (const item of elements) {
513
- const nextChanges = [];
514
- const _remainingChanges = [];
515
- for (const change of remainingChanges) {
516
- if (change.id === item.id) {
517
- 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);
518
519
  }
519
520
  else {
520
- _remainingChanges.push(change);
521
+ changesMap.set(change.id, [change]);
521
522
  }
522
523
  }
523
- remainingChanges = _remainingChanges;
524
- if (nextChanges.length === 0) {
525
- 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);
531
+ continue;
532
+ }
533
+ // If we have a 'remove' change queued, it'll be the only change in the array
534
+ if (changes[0].type === 'remove') {
526
535
  continue;
527
536
  }
528
- const updateItem = { ...item };
529
- for (const currentChange of nextChanges) {
530
- if (currentChange) {
531
- switch (currentChange.type) {
532
- case 'select': {
533
- updateItem.selected = currentChange.selected;
534
- break;
535
- }
536
- case 'position': {
537
- if (typeof currentChange.position !== 'undefined') {
538
- updateItem.position = currentChange.position;
539
- }
540
- if (typeof currentChange.positionAbsolute !== 'undefined') {
541
- if (!updateItem.computed) {
542
- updateItem.computed = {};
543
- }
544
- updateItem.computed.positionAbsolute = currentChange.positionAbsolute;
545
- }
546
- if (typeof currentChange.dragging !== 'undefined') {
547
- updateItem.dragging = currentChange.dragging;
548
- }
549
- if (updateItem.expandParent) {
550
- handleParentExpand(updatedElements, updateItem);
551
- }
552
- break;
553
- }
554
- case 'dimensions': {
555
- if (typeof currentChange.dimensions !== 'undefined') {
556
- if (!updateItem.computed) {
557
- updateItem.computed = {};
558
- }
559
- updateItem.computed.width = currentChange.dimensions.width;
560
- updateItem.computed.height = currentChange.dimensions.height;
561
- }
562
- if (typeof currentChange.updateStyle !== 'undefined') {
563
- updateItem.style = { ...(updateItem.style || {}), ...currentChange.dimensions };
564
- }
565
- if (typeof currentChange.resizing === 'boolean') {
566
- updateItem.resizing = currentChange.resizing;
567
- }
568
- if (updateItem.expandParent) {
569
- handleParentExpand(updatedElements, updateItem);
570
- }
571
- break;
572
- }
573
- case 'remove': {
574
- continue;
575
- }
537
+ if (changes[0].type === 'replace') {
538
+ updatedElements.push({ ...changes[0].item });
539
+ continue;
540
+ }
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);
547
+ }
548
+ updatedElements.push(updatedElement);
549
+ }
550
+ return updatedElements;
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;
576
583
  }
577
584
  }
578
- updatedElements.push(updateItem);
585
+ if (typeof change.resizing === 'boolean') {
586
+ element.resizing = change.resizing;
587
+ }
588
+ if (element.expandParent) {
589
+ handleParentExpand(elements, element);
590
+ }
591
+ break;
579
592
  }
580
593
  }
581
- return updatedElements;
582
594
  }
583
595
  /**
584
596
  * Drop in function that applies node changes to an array of nodes.
@@ -620,7 +632,7 @@ function applyNodeChanges(changes, nodes) {
620
632
  );
621
633
 
622
634
  return (
623
- <ReactFLow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
635
+ <ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
624
636
  );
625
637
  */
626
638
  function applyEdgeChanges(changes, edges) {
@@ -648,6 +660,26 @@ function getSelectionChanges(items, selectedIds = new Set(), mutateItem = false)
648
660
  }
649
661
  return changes;
650
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
+ }
651
683
 
652
684
  /**
653
685
  * Test whether an object is useable as a Node
@@ -732,31 +764,50 @@ function useReactFlow() {
732
764
  const { edges = [] } = store.getState();
733
765
  return edges.find((e) => e.id === id);
734
766
  }, []);
767
+ // this is used to handle multiple syncronous setNodes calls
768
+ const setNodesData = useRef();
769
+ const setNodesTimeout = useRef();
735
770
  const setNodes = useCallback((payload) => {
736
- const { nodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState();
737
- const nextNodes = typeof payload === 'function' ? payload(nodes) : payload;
738
- if (hasDefaultNodes) {
739
- setNodes(nextNodes);
740
- }
741
- else if (onNodesChange) {
742
- const changes = nextNodes.length === 0
743
- ? nodes.map((node) => ({ type: 'remove', id: node.id }))
744
- : nextNodes.map((node) => ({ item: node, type: 'reset' }));
745
- onNodesChange(changes);
746
- }
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);
747
790
  }, []);
791
+ // this is used to handle multiple syncronous setEdges calls
792
+ const setEdgesData = useRef();
793
+ const setEdgesTimeout = useRef();
748
794
  const setEdges = useCallback((payload) => {
749
- const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
750
- const nextEdges = typeof payload === 'function' ? payload(edges) : payload;
751
- if (hasDefaultEdges) {
752
- setEdges(nextEdges);
753
- }
754
- else if (onEdgesChange) {
755
- const changes = nextEdges.length === 0
756
- ? edges.map((edge) => ({ type: 'remove', id: edge.id }))
757
- : nextEdges.map((edge) => ({ item: edge, type: 'reset' }));
758
- 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);
759
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);
760
811
  }, []);
761
812
  const addNodes = useCallback((payload) => {
762
813
  const nodes = Array.isArray(payload) ? payload : [payload];
@@ -794,8 +845,8 @@ function useReactFlow() {
794
845
  },
795
846
  };
796
847
  }, []);
797
- const deleteElements = useCallback(async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [], onBeforeDelete }) => {
798
- 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();
799
850
  const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
800
851
  nodesToRemove,
801
852
  edgesToRemove,
@@ -807,9 +858,8 @@ function useReactFlow() {
807
858
  const hasMatchingNodes = matchingNodes.length > 0;
808
859
  if (hasMatchingEdges) {
809
860
  if (hasDefaultEdges) {
810
- store.setState({
811
- edges: edges.filter((e) => !matchingEdges.some((mE) => mE.id === e.id)),
812
- });
861
+ const nextEdges = edges.filter((e) => !matchingEdges.some((mE) => mE.id === e.id));
862
+ store.getState().setEdges(nextEdges);
813
863
  }
814
864
  onEdgesDelete?.(matchingEdges);
815
865
  onEdgesChange?.(matchingEdges.map((edge) => ({
@@ -819,9 +869,8 @@ function useReactFlow() {
819
869
  }
820
870
  if (hasMatchingNodes) {
821
871
  if (hasDefaultNodes) {
822
- store.setState({
823
- nodes: nodes.filter((n) => !matchingNodes.some((mN) => mN.id === n.id)),
824
- });
872
+ const nextNodes = nodes.filter((n) => !matchingNodes.some((mN) => mN.id === n.id));
873
+ store.getState().setNodes(nextNodes);
825
874
  }
826
875
  onNodesDelete?.(matchingNodes);
827
876
  onNodesChange?.(matchingNodes.map((node) => ({ id: node.id, type: 'remove' })));
@@ -927,8 +976,8 @@ function useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode, }) {
927
976
  const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
928
977
  useEffect(() => {
929
978
  if (deleteKeyPressed) {
930
- const { edges, nodes, onBeforeDelete } = store.getState();
931
- 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) });
932
981
  store.setState({ nodesSelectionActive: false });
933
982
  }
934
983
  }, [deleteKeyPressed]);
@@ -1753,9 +1802,9 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
1753
1802
  transform: `translate(${positionAbsoluteOrigin.x}px,${positionAbsoluteOrigin.y}px)`,
1754
1803
  pointerEvents: hasPointerEvents ? 'all' : 'none',
1755
1804
  visibility: initialized ? 'visible' : 'hidden',
1756
- width,
1757
- height,
1758
1805
  ...node.style,
1806
+ width: width ?? node.style?.width,
1807
+ height: height ?? node.style?.height,
1759
1808
  }, "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, width: computedWidth, height: computedHeight, positionAbsoluteX: positionAbsoluteX, positionAbsoluteY: positionAbsoluteY, selected: node.selected, isConnectable: isConnectable, sourcePosition: node.sourcePosition, targetPosition: node.targetPosition, dragging: dragging, dragHandle: node.dragHandle, zIndex: zIndex }) }) }));
1760
1809
  }
1761
1810
 
@@ -3113,7 +3162,7 @@ const selector$3 = (s) => ({
3113
3162
  minZoomReached: s.transform[2] <= s.minZoom,
3114
3163
  maxZoomReached: s.transform[2] >= s.maxZoom,
3115
3164
  });
3116
- 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', }) {
3117
3166
  const store = useStoreApi();
3118
3167
  const { isInteractive, minZoomReached, maxZoomReached } = useStore(selector$3, shallow);
3119
3168
  const { zoomIn, zoomOut, fitView } = useReactFlow();
@@ -3137,7 +3186,7 @@ function ControlsComponent({ style, showZoom = true, showFitView = true, showInt
3137
3186
  });
3138
3187
  onInteractiveChange?.(!isInteractive);
3139
3188
  };
3140
- 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] }));
3141
3190
  }
3142
3191
  ControlsComponent.displayName = 'Controls';
3143
3192
  const Controls = memo(ControlsComponent);
@@ -3283,179 +3332,87 @@ nodeComponent, maskColor, maskStrokeColor = 'none', maskStrokeWidth = 1, positio
3283
3332
  MiniMapComponent.displayName = 'MiniMap';
3284
3333
  const MiniMap = memo(MiniMapComponent);
3285
3334
 
3286
- var ResizeControlVariant;
3287
- (function (ResizeControlVariant) {
3288
- ResizeControlVariant["Line"] = "line";
3289
- ResizeControlVariant["Handle"] = "handle";
3290
- })(ResizeControlVariant || (ResizeControlVariant = {}));
3291
-
3292
- // returns an array of two numbers (0, 1 or -1) representing the direction of the resize
3293
- // 0 = no change, 1 = increase, -1 = decrease
3294
- function getDirection({ width, prevWidth, height, prevHeight, invertX, invertY }) {
3295
- const deltaWidth = width - prevWidth;
3296
- const deltaHeight = height - prevHeight;
3297
- const direction = [deltaWidth > 0 ? 1 : deltaWidth < 0 ? -1 : 0, deltaHeight > 0 ? 1 : deltaHeight < 0 ? -1 : 0];
3298
- if (deltaWidth && invertX) {
3299
- direction[0] = direction[0] * -1;
3300
- }
3301
- if (deltaHeight && invertY) {
3302
- direction[1] = direction[1] * -1;
3303
- }
3304
- return direction;
3305
- }
3306
-
3307
- const initPrevValues = { width: 0, height: 0, x: 0, y: 0 };
3308
- const initStartValues = {
3309
- ...initPrevValues,
3310
- pointerX: 0,
3311
- pointerY: 0,
3312
- aspectRatio: 1,
3313
- };
3314
3335
  function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle, className, style = {}, children, color, minWidth = 10, minHeight = 10, maxWidth = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE, keepAspectRatio = false, shouldResize, onResizeStart, onResize, onResizeEnd, }) {
3315
3336
  const contextNodeId = useNodeId();
3316
3337
  const id = typeof nodeId === 'string' ? nodeId : contextNodeId;
3317
3338
  const store = useStoreApi();
3318
3339
  const resizeControlRef = useRef(null);
3319
- const startValues = useRef(initStartValues);
3320
- const prevValues = useRef(initPrevValues);
3321
3340
  const defaultPosition = variant === ResizeControlVariant.Line ? 'right' : 'bottom-right';
3322
3341
  const controlPosition = position ?? defaultPosition;
3342
+ const resizer = useRef(null);
3323
3343
  useEffect(() => {
3324
3344
  if (!resizeControlRef.current || !id) {
3325
3345
  return;
3326
3346
  }
3327
- const selection = select(resizeControlRef.current);
3328
- const enableX = controlPosition.includes('right') || controlPosition.includes('left');
3329
- const enableY = controlPosition.includes('bottom') || controlPosition.includes('top');
3330
- const invertX = controlPosition.includes('left');
3331
- const invertY = controlPosition.includes('top');
3332
- const dragHandler = drag()
3333
- .on('start', (event) => {
3334
- const { nodeLookup, transform, snapGrid, snapToGrid } = store.getState();
3335
- const node = nodeLookup.get(id);
3336
- const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
3337
- prevValues.current = {
3338
- width: node?.computed?.width ?? 0,
3339
- height: node?.computed?.height ?? 0,
3340
- x: node?.position.x ?? 0,
3341
- y: node?.position.y ?? 0,
3342
- };
3343
- startValues.current = {
3344
- ...prevValues.current,
3345
- pointerX: xSnapped,
3346
- pointerY: ySnapped,
3347
- aspectRatio: prevValues.current.width / prevValues.current.height,
3348
- };
3349
- onResizeStart?.(event, { ...prevValues.current });
3350
- })
3351
- .on('drag', (event) => {
3352
- const { nodeLookup, transform, snapGrid, snapToGrid, triggerNodeChanges } = store.getState();
3353
- const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
3354
- const node = nodeLookup.get(id);
3355
- if (node) {
3356
- const changes = [];
3357
- const { pointerX: startX, pointerY: startY, width: startWidth, height: startHeight, x: startNodeX, y: startNodeY, aspectRatio, } = startValues.current;
3358
- const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues.current;
3359
- const distX = Math.floor(enableX ? xSnapped - startX : 0);
3360
- const distY = Math.floor(enableY ? ySnapped - startY : 0);
3361
- let width = clamp(startWidth + (invertX ? -distX : distX), minWidth, maxWidth);
3362
- let height = clamp(startHeight + (invertY ? -distY : distY), minHeight, maxHeight);
3363
- if (keepAspectRatio) {
3364
- const nextAspectRatio = width / height;
3365
- const isDiagonal = enableX && enableY;
3366
- const isHorizontal = enableX && !enableY;
3367
- const isVertical = enableY && !enableX;
3368
- width = (nextAspectRatio <= aspectRatio && isDiagonal) || isVertical ? height * aspectRatio : width;
3369
- height = (nextAspectRatio > aspectRatio && isDiagonal) || isHorizontal ? width / aspectRatio : height;
3370
- if (width >= maxWidth) {
3371
- width = maxWidth;
3372
- height = maxWidth / aspectRatio;
3373
- }
3374
- else if (width <= minWidth) {
3375
- width = minWidth;
3376
- height = minWidth / aspectRatio;
3377
- }
3378
- if (height >= maxHeight) {
3379
- height = maxHeight;
3380
- width = maxHeight * aspectRatio;
3381
- }
3382
- else if (height <= minHeight) {
3383
- height = minHeight;
3384
- width = minHeight * aspectRatio;
3385
- }
3386
- }
3387
- const isWidthChange = width !== prevWidth;
3388
- const isHeightChange = height !== prevHeight;
3389
- if (invertX || invertY) {
3390
- const x = invertX ? startNodeX - (width - startWidth) : startNodeX;
3391
- const y = invertY ? startNodeY - (height - startHeight) : startNodeY;
3392
- // only transform the node if the width or height changes
3393
- const isXPosChange = x !== prevX && isWidthChange;
3394
- const isYPosChange = y !== prevY && isHeightChange;
3395
- if (isXPosChange || isYPosChange) {
3347
+ if (!resizer.current) {
3348
+ resizer.current = XYResizer({
3349
+ domNode: resizeControlRef.current,
3350
+ nodeId: id,
3351
+ getStoreItems: () => {
3352
+ const { nodeLookup, transform, snapGrid, snapToGrid } = store.getState();
3353
+ return {
3354
+ nodeLookup,
3355
+ transform,
3356
+ snapGrid,
3357
+ snapToGrid,
3358
+ };
3359
+ },
3360
+ onChange: (change) => {
3361
+ const { triggerNodeChanges } = store.getState();
3362
+ const changes = [];
3363
+ if (change.isXPosChange || change.isYPosChange) {
3396
3364
  const positionChange = {
3397
- id: node.id,
3365
+ id,
3398
3366
  type: 'position',
3399
3367
  position: {
3400
- x: isXPosChange ? x : prevX,
3401
- y: isYPosChange ? y : prevY,
3368
+ x: change.x,
3369
+ y: change.y,
3402
3370
  },
3403
3371
  };
3404
3372
  changes.push(positionChange);
3405
- prevValues.current.x = positionChange.position.x;
3406
- prevValues.current.y = positionChange.position.y;
3407
3373
  }
3408
- }
3409
- if (isWidthChange || isHeightChange) {
3374
+ if (change.isWidthChange || change.isHeightChange) {
3375
+ const dimensionChange = {
3376
+ id,
3377
+ type: 'dimensions',
3378
+ resizing: true,
3379
+ dimensions: {
3380
+ width: change.width,
3381
+ height: change.height,
3382
+ },
3383
+ };
3384
+ changes.push(dimensionChange);
3385
+ }
3386
+ triggerNodeChanges(changes);
3387
+ },
3388
+ onEnd: () => {
3410
3389
  const dimensionChange = {
3411
3390
  id: id,
3412
3391
  type: 'dimensions',
3413
- updateStyle: true,
3414
- resizing: true,
3415
- dimensions: {
3416
- width: width,
3417
- height: height,
3418
- },
3392
+ resizing: false,
3419
3393
  };
3420
- changes.push(dimensionChange);
3421
- prevValues.current.width = width;
3422
- prevValues.current.height = height;
3423
- }
3424
- if (changes.length === 0) {
3425
- return;
3426
- }
3427
- const direction = getDirection({
3428
- width: prevValues.current.width,
3429
- prevWidth,
3430
- height: prevValues.current.height,
3431
- prevHeight,
3432
- invertX,
3433
- invertY,
3434
- });
3435
- const nextValues = { ...prevValues.current, direction };
3436
- const callResize = shouldResize?.(event, nextValues);
3437
- if (callResize === false) {
3438
- return;
3439
- }
3440
- onResize?.(event, nextValues);
3441
- triggerNodeChanges(changes);
3442
- }
3443
- })
3444
- .on('end', (event) => {
3445
- const dimensionChange = {
3446
- id: id,
3447
- type: 'dimensions',
3448
- resizing: false,
3449
- };
3450
- onResizeEnd?.(event, { ...prevValues.current });
3451
- store.getState().triggerNodeChanges([dimensionChange]);
3394
+ store.getState().triggerNodeChanges([dimensionChange]);
3395
+ },
3396
+ });
3397
+ }
3398
+ resizer.current.update({
3399
+ controlPosition,
3400
+ boundaries: {
3401
+ minWidth,
3402
+ minHeight,
3403
+ maxWidth,
3404
+ maxHeight,
3405
+ },
3406
+ keepAspectRatio,
3407
+ onResizeStart,
3408
+ onResize,
3409
+ onResizeEnd,
3410
+ shouldResize,
3452
3411
  });
3453
- selection.call(dragHandler);
3454
3412
  return () => {
3455
- selection.on('.drag', null);
3413
+ resizer.current?.destroy();
3456
3414
  };
3457
3415
  }, [
3458
- id,
3459
3416
  controlPosition,
3460
3417
  minWidth,
3461
3418
  minHeight,
@@ -3465,21 +3422,20 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
3465
3422
  onResizeStart,
3466
3423
  onResize,
3467
3424
  onResizeEnd,
3425
+ shouldResize,
3468
3426
  ]);
3469
3427
  const positionClassNames = controlPosition.split('-');
3470
3428
  const colorStyleProp = variant === ResizeControlVariant.Line ? 'borderColor' : 'backgroundColor';
3471
3429
  const controlStyle = color ? { ...style, [colorStyleProp]: color } : style;
3472
3430
  return (jsx("div", { className: cc(['react-flow__resize-control', 'nodrag', ...positionClassNames, variant, className]), ref: resizeControlRef, style: controlStyle, children: children }));
3473
3431
  }
3474
- var ResizeControl$1 = memo(ResizeControl);
3432
+ const NodeResizeControl = memo(ResizeControl);
3475
3433
 
3476
- const handleControls = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
3477
- const lineControls = ['top', 'right', 'bottom', 'left'];
3478
3434
  function NodeResizer({ nodeId, isVisible = true, handleClassName, handleStyle, lineClassName, lineStyle, color, minWidth = 10, minHeight = 10, maxWidth = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE, keepAspectRatio = false, shouldResize, onResizeStart, onResize, onResizeEnd, }) {
3479
3435
  if (!isVisible) {
3480
3436
  return null;
3481
3437
  }
3482
- return (jsxs(Fragment, { children: [lineControls.map((c) => (jsx(ResizeControl$1, { className: lineClassName, style: lineStyle, nodeId: nodeId, position: c, variant: ResizeControlVariant.Line, color: color, minWidth: minWidth, minHeight: minHeight, maxWidth: maxWidth, maxHeight: maxHeight, onResizeStart: onResizeStart, keepAspectRatio: keepAspectRatio, shouldResize: shouldResize, onResize: onResize, onResizeEnd: onResizeEnd }, c))), handleControls.map((c) => (jsx(ResizeControl$1, { className: handleClassName, style: handleStyle, nodeId: nodeId, position: c, color: color, minWidth: minWidth, minHeight: minHeight, maxWidth: maxWidth, maxHeight: maxHeight, onResizeStart: onResizeStart, keepAspectRatio: keepAspectRatio, shouldResize: shouldResize, onResize: onResize, onResizeEnd: onResizeEnd }, c)))] }));
3438
+ return (jsxs(Fragment, { children: [XY_RESIZER_LINE_POSITIONS.map((position) => (jsx(NodeResizeControl, { className: lineClassName, style: lineStyle, nodeId: nodeId, position: position, variant: ResizeControlVariant.Line, color: color, minWidth: minWidth, minHeight: minHeight, maxWidth: maxWidth, maxHeight: maxHeight, onResizeStart: onResizeStart, keepAspectRatio: keepAspectRatio, shouldResize: shouldResize, onResize: onResize, onResizeEnd: onResizeEnd }, position))), XY_RESIZER_HANDLE_POSITIONS.map((position) => (jsx(NodeResizeControl, { className: handleClassName, style: handleStyle, nodeId: nodeId, position: position, color: color, minWidth: minWidth, minHeight: minHeight, maxWidth: maxWidth, maxHeight: maxHeight, onResizeStart: onResizeStart, keepAspectRatio: keepAspectRatio, shouldResize: shouldResize, onResize: onResize, onResizeEnd: onResizeEnd }, position)))] }));
3483
3439
  }
3484
3440
 
3485
3441
  const selector = (state) => state.domNode?.querySelector('.react-flow__renderer');
@@ -3542,4 +3498,4 @@ function NodeToolbar({ nodeId, children, className, style, isVisible, position =
3542
3498
  return (jsx(NodeToolbarPortal, { children: jsx("div", { style: wrapperStyle, className: cc(['react-flow__node-toolbar', className]), ...rest, "data-id": nodes.reduce((acc, node) => `${acc}${node.id} `, '').trim(), children: children }) }));
3543
3499
  }
3544
3500
 
3545
- export { Background, BackgroundVariant, BaseEdge, BezierEdge, ControlButton, Controls, EdgeLabelRenderer, EdgeText, Handle, MiniMap, ResizeControl$1 as NodeResizeControl, NodeResizer, NodeToolbar, Panel, ReactFlow, ReactFlowProvider, ResizeControlVariant, SimpleBezierEdge, SmoothStepEdge, StepEdge, StraightEdge, ViewportPortal, addEdge, applyEdgeChanges, applyNodeChanges, getConnectedEdges, getIncomers, getOutgoers, getSimpleBezierPath, handleParentExpand, isEdge, isNode, updateEdge, useConnection, useEdges, useEdgesState, useHandleConnections, useKeyPress, useNodeId, useNodes, useNodesData, useNodesInitialized, useNodesState, useOnSelectionChange, useOnViewportChange, useReactFlow, useStore, useStoreApi, useUpdateNodeInternals, useViewport };
3501
+ export { Background, BackgroundVariant, BaseEdge, BezierEdge, ControlButton, Controls, EdgeLabelRenderer, EdgeText, Handle, MiniMap, NodeResizeControl, NodeResizer, NodeToolbar, Panel, ReactFlow, ReactFlowProvider, SimpleBezierEdge, SmoothStepEdge, StepEdge, StraightEdge, ViewportPortal, addEdge, applyEdgeChanges, applyNodeChanges, getConnectedEdges, getIncomers, getOutgoers, getSimpleBezierPath, handleParentExpand, isEdge, isNode, updateEdge, useConnection, useEdges, useEdgesState, useHandleConnections, useKeyPress, useNodeId, useNodes, useNodesData, useNodesInitialized, useNodesState, useOnSelectionChange, useOnViewportChange, useReactFlow, useStore, useStoreApi, useUpdateNodeInternals, useViewport };