@xyflow/react 12.4.2 → 12.4.4
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.
- package/dist/esm/additional-components/Background/Background.d.ts +53 -0
- package/dist/esm/additional-components/Background/Background.d.ts.map +1 -1
- package/dist/esm/additional-components/Background/types.d.ts +11 -1
- package/dist/esm/additional-components/Background/types.d.ts.map +1 -1
- package/dist/esm/additional-components/Controls/ControlButton.d.ts +23 -0
- package/dist/esm/additional-components/Controls/ControlButton.d.ts.map +1 -1
- package/dist/esm/additional-components/Controls/Controls.d.ts +21 -0
- package/dist/esm/additional-components/Controls/Controls.d.ts.map +1 -1
- package/dist/esm/additional-components/Controls/types.d.ts +8 -1
- package/dist/esm/additional-components/Controls/types.d.ts.map +1 -1
- package/dist/esm/additional-components/MiniMap/MiniMap.d.ts +20 -0
- package/dist/esm/additional-components/MiniMap/MiniMap.d.ts.map +1 -1
- package/dist/esm/additional-components/MiniMap/MiniMapNodes.d.ts.map +1 -1
- package/dist/esm/additional-components/MiniMap/types.d.ts +11 -1
- package/dist/esm/additional-components/MiniMap/types.d.ts.map +1 -1
- package/dist/esm/additional-components/NodeResizer/NodeResizeControl.d.ts +5 -0
- package/dist/esm/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -1
- package/dist/esm/additional-components/NodeResizer/NodeResizer.d.ts +24 -0
- package/dist/esm/additional-components/NodeResizer/NodeResizer.d.ts.map +1 -1
- package/dist/esm/additional-components/NodeResizer/types.d.ts +15 -3
- package/dist/esm/additional-components/NodeResizer/types.d.ts.map +1 -1
- package/dist/esm/additional-components/NodeToolbar/NodeToolbar.d.ts +35 -0
- package/dist/esm/additional-components/NodeToolbar/NodeToolbar.d.ts.map +1 -1
- package/dist/esm/additional-components/NodeToolbar/types.d.ts +7 -2
- package/dist/esm/additional-components/NodeToolbar/types.d.ts.map +1 -1
- package/dist/esm/components/BatchProvider/index.d.ts.map +1 -1
- package/dist/esm/components/BatchProvider/useQueue.d.ts.map +1 -1
- package/dist/esm/components/ConnectionLine/index.d.ts +4 -4
- package/dist/esm/components/ConnectionLine/index.d.ts.map +1 -1
- package/dist/esm/components/EdgeLabelRenderer/index.d.ts +40 -0
- package/dist/esm/components/EdgeLabelRenderer/index.d.ts.map +1 -1
- package/dist/esm/components/EdgeWrapper/index.d.ts +1 -1
- package/dist/esm/components/EdgeWrapper/index.d.ts.map +1 -1
- package/dist/esm/components/Edges/BaseEdge.d.ts +27 -0
- package/dist/esm/components/Edges/BaseEdge.d.ts.map +1 -1
- package/dist/esm/components/Edges/EdgeText.d.ts +26 -0
- package/dist/esm/components/Edges/EdgeText.d.ts.map +1 -1
- package/dist/esm/components/Edges/SimpleBezierEdge.d.ts +5 -0
- package/dist/esm/components/Edges/SimpleBezierEdge.d.ts.map +1 -1
- package/dist/esm/components/Edges/index.d.ts.map +1 -1
- package/dist/esm/components/Handle/index.d.ts +32 -4
- package/dist/esm/components/Handle/index.d.ts.map +1 -1
- package/dist/esm/components/NodeWrapper/index.d.ts +1 -1
- package/dist/esm/components/NodeWrapper/index.d.ts.map +1 -1
- package/dist/esm/components/NodeWrapper/useNodeObserver.d.ts.map +1 -1
- package/dist/esm/components/NodeWrapper/utils.d.ts.map +1 -1
- package/dist/esm/components/Nodes/utils.d.ts.map +1 -1
- package/dist/esm/components/Panel/index.d.ts +32 -4
- package/dist/esm/components/Panel/index.d.ts.map +1 -1
- package/dist/esm/components/ReactFlowProvider/index.d.ts +34 -0
- package/dist/esm/components/ReactFlowProvider/index.d.ts.map +1 -1
- package/dist/esm/components/SelectionListener/index.d.ts +4 -4
- package/dist/esm/components/SelectionListener/index.d.ts.map +1 -1
- package/dist/esm/components/StoreUpdater/index.d.ts.map +1 -1
- package/dist/esm/components/ViewportPortal/index.d.ts +25 -0
- package/dist/esm/components/ViewportPortal/index.d.ts.map +1 -1
- package/dist/esm/container/EdgeRenderer/MarkerDefinitions.d.ts.map +1 -1
- package/dist/esm/container/NodeRenderer/index.d.ts.map +1 -1
- package/dist/esm/container/Pane/index.d.ts.map +1 -1
- package/dist/esm/container/ReactFlow/Wrapper.d.ts.map +1 -1
- package/dist/esm/container/ReactFlow/index.d.ts +21 -1
- package/dist/esm/container/ReactFlow/index.d.ts.map +1 -1
- package/dist/esm/contexts/NodeIdContext.d.ts +28 -0
- package/dist/esm/contexts/NodeIdContext.d.ts.map +1 -1
- package/dist/esm/hooks/useConnection.d.ts +20 -1
- package/dist/esm/hooks/useConnection.d.ts.map +1 -1
- package/dist/esm/hooks/useEdges.d.ts +13 -1
- package/dist/esm/hooks/useEdges.d.ts.map +1 -1
- package/dist/esm/hooks/useInternalNode.d.ts +21 -1
- package/dist/esm/hooks/useInternalNode.d.ts.map +1 -1
- package/dist/esm/hooks/useKeyPress.d.ts +19 -1
- package/dist/esm/hooks/useKeyPress.d.ts.map +1 -1
- package/dist/esm/hooks/useMoveSelectedNodes.d.ts.map +1 -1
- package/dist/esm/hooks/useNodeConnections.d.ts +17 -1
- package/dist/esm/hooks/useNodeConnections.d.ts.map +1 -1
- package/dist/esm/hooks/useNodes.d.ts +14 -1
- package/dist/esm/hooks/useNodes.d.ts.map +1 -1
- package/dist/esm/hooks/useNodesData.d.ts +14 -2
- package/dist/esm/hooks/useNodesData.d.ts.map +1 -1
- package/dist/esm/hooks/useNodesEdgesState.d.ts +62 -2
- package/dist/esm/hooks/useNodesEdgesState.d.ts.map +1 -1
- package/dist/esm/hooks/useNodesInitialized.d.ts +27 -1
- package/dist/esm/hooks/useNodesInitialized.d.ts.map +1 -1
- package/dist/esm/hooks/useOnSelectionChange.d.ts +37 -5
- package/dist/esm/hooks/useOnSelectionChange.d.ts.map +1 -1
- package/dist/esm/hooks/useOnViewportChange.d.ts +19 -1
- package/dist/esm/hooks/useOnViewportChange.d.ts.map +1 -1
- package/dist/esm/hooks/useReactFlow.d.ts +24 -1
- package/dist/esm/hooks/useReactFlow.d.ts.map +1 -1
- package/dist/esm/hooks/useStore.d.ts +23 -2
- package/dist/esm/hooks/useStore.d.ts.map +1 -1
- package/dist/esm/hooks/useUpdateNodeInternals.d.ts +39 -1
- package/dist/esm/hooks/useUpdateNodeInternals.d.ts.map +1 -1
- package/dist/esm/hooks/useViewport.d.ts +24 -1
- package/dist/esm/hooks/useViewport.d.ts.map +1 -1
- package/dist/esm/hooks/useViewportHelper.d.ts.map +1 -1
- package/dist/esm/index.js +1107 -198
- package/dist/esm/index.mjs +1107 -198
- package/dist/esm/store/index.d.ts.map +1 -1
- package/dist/esm/types/component-props.d.ts +112 -57
- package/dist/esm/types/component-props.d.ts.map +1 -1
- package/dist/esm/types/edges.d.ts +27 -2
- package/dist/esm/types/edges.d.ts.map +1 -1
- package/dist/esm/types/general.d.ts +48 -7
- package/dist/esm/types/general.d.ts.map +1 -1
- package/dist/esm/types/instance.d.ts +11 -1
- package/dist/esm/types/instance.d.ts.map +1 -1
- package/dist/esm/types/nodes.d.ts +46 -4
- package/dist/esm/types/nodes.d.ts.map +1 -1
- package/dist/esm/types/store.d.ts +2 -2
- package/dist/esm/types/store.d.ts.map +1 -1
- package/dist/esm/utils/changes.d.ts +45 -23
- package/dist/esm/utils/changes.d.ts.map +1 -1
- package/dist/esm/utils/general.d.ts +27 -3
- package/dist/esm/utils/general.d.ts.map +1 -1
- package/dist/umd/additional-components/Background/Background.d.ts +53 -0
- package/dist/umd/additional-components/Background/Background.d.ts.map +1 -1
- package/dist/umd/additional-components/Background/types.d.ts +11 -1
- package/dist/umd/additional-components/Background/types.d.ts.map +1 -1
- package/dist/umd/additional-components/Controls/ControlButton.d.ts +23 -0
- package/dist/umd/additional-components/Controls/ControlButton.d.ts.map +1 -1
- package/dist/umd/additional-components/Controls/Controls.d.ts +21 -0
- package/dist/umd/additional-components/Controls/Controls.d.ts.map +1 -1
- package/dist/umd/additional-components/Controls/types.d.ts +8 -1
- package/dist/umd/additional-components/Controls/types.d.ts.map +1 -1
- package/dist/umd/additional-components/MiniMap/MiniMap.d.ts +20 -0
- package/dist/umd/additional-components/MiniMap/MiniMap.d.ts.map +1 -1
- package/dist/umd/additional-components/MiniMap/MiniMapNodes.d.ts.map +1 -1
- package/dist/umd/additional-components/MiniMap/types.d.ts +11 -1
- package/dist/umd/additional-components/MiniMap/types.d.ts.map +1 -1
- package/dist/umd/additional-components/NodeResizer/NodeResizeControl.d.ts +5 -0
- package/dist/umd/additional-components/NodeResizer/NodeResizeControl.d.ts.map +1 -1
- package/dist/umd/additional-components/NodeResizer/NodeResizer.d.ts +24 -0
- package/dist/umd/additional-components/NodeResizer/NodeResizer.d.ts.map +1 -1
- package/dist/umd/additional-components/NodeResizer/types.d.ts +15 -3
- package/dist/umd/additional-components/NodeResizer/types.d.ts.map +1 -1
- package/dist/umd/additional-components/NodeToolbar/NodeToolbar.d.ts +35 -0
- package/dist/umd/additional-components/NodeToolbar/NodeToolbar.d.ts.map +1 -1
- package/dist/umd/additional-components/NodeToolbar/types.d.ts +7 -2
- package/dist/umd/additional-components/NodeToolbar/types.d.ts.map +1 -1
- package/dist/umd/components/BatchProvider/index.d.ts.map +1 -1
- package/dist/umd/components/BatchProvider/useQueue.d.ts.map +1 -1
- package/dist/umd/components/ConnectionLine/index.d.ts +4 -4
- package/dist/umd/components/ConnectionLine/index.d.ts.map +1 -1
- package/dist/umd/components/EdgeLabelRenderer/index.d.ts +40 -0
- package/dist/umd/components/EdgeLabelRenderer/index.d.ts.map +1 -1
- package/dist/umd/components/EdgeWrapper/index.d.ts +1 -1
- package/dist/umd/components/EdgeWrapper/index.d.ts.map +1 -1
- package/dist/umd/components/Edges/BaseEdge.d.ts +27 -0
- package/dist/umd/components/Edges/BaseEdge.d.ts.map +1 -1
- package/dist/umd/components/Edges/EdgeText.d.ts +26 -0
- package/dist/umd/components/Edges/EdgeText.d.ts.map +1 -1
- package/dist/umd/components/Edges/SimpleBezierEdge.d.ts +5 -0
- package/dist/umd/components/Edges/SimpleBezierEdge.d.ts.map +1 -1
- package/dist/umd/components/Edges/index.d.ts.map +1 -1
- package/dist/umd/components/Handle/index.d.ts +32 -4
- package/dist/umd/components/Handle/index.d.ts.map +1 -1
- package/dist/umd/components/NodeWrapper/index.d.ts +1 -1
- package/dist/umd/components/NodeWrapper/index.d.ts.map +1 -1
- package/dist/umd/components/NodeWrapper/useNodeObserver.d.ts.map +1 -1
- package/dist/umd/components/NodeWrapper/utils.d.ts.map +1 -1
- package/dist/umd/components/Nodes/utils.d.ts.map +1 -1
- package/dist/umd/components/Panel/index.d.ts +32 -4
- package/dist/umd/components/Panel/index.d.ts.map +1 -1
- package/dist/umd/components/ReactFlowProvider/index.d.ts +34 -0
- package/dist/umd/components/ReactFlowProvider/index.d.ts.map +1 -1
- package/dist/umd/components/SelectionListener/index.d.ts +4 -4
- package/dist/umd/components/SelectionListener/index.d.ts.map +1 -1
- package/dist/umd/components/StoreUpdater/index.d.ts.map +1 -1
- package/dist/umd/components/ViewportPortal/index.d.ts +25 -0
- package/dist/umd/components/ViewportPortal/index.d.ts.map +1 -1
- package/dist/umd/container/EdgeRenderer/MarkerDefinitions.d.ts.map +1 -1
- package/dist/umd/container/NodeRenderer/index.d.ts.map +1 -1
- package/dist/umd/container/Pane/index.d.ts.map +1 -1
- package/dist/umd/container/ReactFlow/Wrapper.d.ts.map +1 -1
- package/dist/umd/container/ReactFlow/index.d.ts +21 -1
- package/dist/umd/container/ReactFlow/index.d.ts.map +1 -1
- package/dist/umd/contexts/NodeIdContext.d.ts +28 -0
- package/dist/umd/contexts/NodeIdContext.d.ts.map +1 -1
- package/dist/umd/hooks/useConnection.d.ts +20 -1
- package/dist/umd/hooks/useConnection.d.ts.map +1 -1
- package/dist/umd/hooks/useEdges.d.ts +13 -1
- package/dist/umd/hooks/useEdges.d.ts.map +1 -1
- package/dist/umd/hooks/useInternalNode.d.ts +21 -1
- package/dist/umd/hooks/useInternalNode.d.ts.map +1 -1
- package/dist/umd/hooks/useKeyPress.d.ts +19 -1
- package/dist/umd/hooks/useKeyPress.d.ts.map +1 -1
- package/dist/umd/hooks/useMoveSelectedNodes.d.ts.map +1 -1
- package/dist/umd/hooks/useNodeConnections.d.ts +17 -1
- package/dist/umd/hooks/useNodeConnections.d.ts.map +1 -1
- package/dist/umd/hooks/useNodes.d.ts +14 -1
- package/dist/umd/hooks/useNodes.d.ts.map +1 -1
- package/dist/umd/hooks/useNodesData.d.ts +14 -2
- package/dist/umd/hooks/useNodesData.d.ts.map +1 -1
- package/dist/umd/hooks/useNodesEdgesState.d.ts +62 -2
- package/dist/umd/hooks/useNodesEdgesState.d.ts.map +1 -1
- package/dist/umd/hooks/useNodesInitialized.d.ts +27 -1
- package/dist/umd/hooks/useNodesInitialized.d.ts.map +1 -1
- package/dist/umd/hooks/useOnSelectionChange.d.ts +37 -5
- package/dist/umd/hooks/useOnSelectionChange.d.ts.map +1 -1
- package/dist/umd/hooks/useOnViewportChange.d.ts +19 -1
- package/dist/umd/hooks/useOnViewportChange.d.ts.map +1 -1
- package/dist/umd/hooks/useReactFlow.d.ts +24 -1
- package/dist/umd/hooks/useReactFlow.d.ts.map +1 -1
- package/dist/umd/hooks/useStore.d.ts +23 -2
- package/dist/umd/hooks/useStore.d.ts.map +1 -1
- package/dist/umd/hooks/useUpdateNodeInternals.d.ts +39 -1
- package/dist/umd/hooks/useUpdateNodeInternals.d.ts.map +1 -1
- package/dist/umd/hooks/useViewport.d.ts +24 -1
- package/dist/umd/hooks/useViewport.d.ts.map +1 -1
- package/dist/umd/hooks/useViewportHelper.d.ts.map +1 -1
- package/dist/umd/index.js +2 -2
- package/dist/umd/store/index.d.ts.map +1 -1
- package/dist/umd/types/component-props.d.ts +112 -57
- package/dist/umd/types/component-props.d.ts.map +1 -1
- package/dist/umd/types/edges.d.ts +27 -2
- package/dist/umd/types/edges.d.ts.map +1 -1
- package/dist/umd/types/general.d.ts +48 -7
- package/dist/umd/types/general.d.ts.map +1 -1
- package/dist/umd/types/instance.d.ts +11 -1
- package/dist/umd/types/instance.d.ts.map +1 -1
- package/dist/umd/types/nodes.d.ts +46 -4
- package/dist/umd/types/nodes.d.ts.map +1 -1
- package/dist/umd/types/store.d.ts +2 -2
- package/dist/umd/types/store.d.ts.map +1 -1
- package/dist/umd/utils/changes.d.ts +45 -23
- package/dist/umd/utils/changes.d.ts.map +1 -1
- package/dist/umd/utils/general.d.ts +27 -3
- package/dist/umd/utils/general.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/esm/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { createContext, useContext, useMemo, forwardRef, useEffect, useRef, useState, useLayoutEffect, useCallback, memo } from 'react';
|
|
3
4
|
import cc from 'classcat';
|
|
4
5
|
import { errorMessages, infiniteExtent, isInputDOMNode, getFitViewNodes, getDimensions, fitView, getViewportForBounds, pointToRendererPoint, rendererPointToPoint, isNodeBase, isEdgeBase, getElementsToRemove, isRectObject, nodeToRect, getOverlappingArea, getNodesBounds, evaluateAbsolutePosition, XYPanZoom, PanOnScrollMode, SelectionMode, getEventPosition, getNodesInside, areSetsEqual, XYDrag, snapPosition, calculateNodePosition, Position, ConnectionMode, isMouseEvent, XYHandle, getHostForElement, addEdge, getInternalNodesBounds, isNumeric, nodeHasDimensions, getNodeDimensions, elementSelectionKeys, isEdgeVisible, MarkerType, createMarkerIds, getBezierEdgeCenter, getSmoothStepPath, getStraightPath, getBezierPath, getEdgePosition, getElevatedEdgeZIndex, getMarkerId, getConnectionStatus, ConnectionLineType, updateConnectionLookup, adoptUserNodes, initialConnection, devWarn, updateNodeInternals, updateAbsolutePositions, handleExpandParent, panBy, isMacOs, areConnectionMapsEqual, handleConnectionChange, shallowNodeData, XYMinimap, getBoundsOfRects, ResizeControlVariant, XYResizer, XY_RESIZER_LINE_POSITIONS, XY_RESIZER_HANDLE_POSITIONS, getNodeToolbarTransform } from '@xyflow/system';
|
|
5
6
|
export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, ResizeControlVariant, SelectionMode, addEdge, getBezierEdgeCenter, getBezierPath, getConnectedEdges, getEdgeCenter, getIncomers, getNodesBounds, getOutgoers, getSmoothStepPath, getStraightPath, getViewportForBounds, reconnectEdge } from '@xyflow/system';
|
|
6
|
-
import { createContext, useContext, useMemo, forwardRef, useEffect, useRef, useState, useLayoutEffect, useCallback, memo } from 'react';
|
|
7
7
|
import { useStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional';
|
|
8
8
|
import { shallow } from 'zustand/shallow';
|
|
9
9
|
import { createPortal } from 'react-dom';
|
|
@@ -13,7 +13,9 @@ const Provider$1 = StoreContext.Provider;
|
|
|
13
13
|
|
|
14
14
|
const zustandErrorMessage = errorMessages['error001']();
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* This hook can be used to subscribe to internal state changes of the React Flow
|
|
17
|
+
* component. The `useStore` hook is re-exported from the [Zustand](https://github.com/pmndrs/zustand)
|
|
18
|
+
* state management library, so you should check out their docs for more details.
|
|
17
19
|
*
|
|
18
20
|
* @public
|
|
19
21
|
* @param selector
|
|
@@ -21,8 +23,13 @@ const zustandErrorMessage = errorMessages['error001']();
|
|
|
21
23
|
* @returns The selected state slice
|
|
22
24
|
*
|
|
23
25
|
* @example
|
|
24
|
-
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* const nodes = useStore((state) => state.nodes);
|
|
28
|
+
* ```
|
|
25
29
|
*
|
|
30
|
+
* @remarks This hook should only be used if there is no other way to access the internal
|
|
31
|
+
* state. For many of the common use cases, there are dedicated hooks available
|
|
32
|
+
* such as {@link useReactFlow}, {@link useViewport}, etc.
|
|
26
33
|
*/
|
|
27
34
|
function useStore(selector, equalityFn) {
|
|
28
35
|
const store = useContext(StoreContext);
|
|
@@ -31,6 +38,20 @@ function useStore(selector, equalityFn) {
|
|
|
31
38
|
}
|
|
32
39
|
return useStoreWithEqualityFn(store, selector, equalityFn);
|
|
33
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* In some cases, you might need to access the store directly. This hook returns the store object which can be used on demand to access the state or dispatch actions.
|
|
43
|
+
*
|
|
44
|
+
* @returns The store object
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const store = useStoreApi();
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @remarks This hook should only be used if there is no other way to access the internal
|
|
52
|
+
* state. For many of the common use cases, there are dedicated hooks available
|
|
53
|
+
* such as {@link useReactFlow}, {@link useViewport}, etc.
|
|
54
|
+
*/
|
|
34
55
|
function useStoreApi() {
|
|
35
56
|
const store = useContext(StoreContext);
|
|
36
57
|
if (store === null) {
|
|
@@ -68,11 +89,37 @@ function A11yDescriptions({ rfId, disableKeyboardA11y }) {
|
|
|
68
89
|
}
|
|
69
90
|
|
|
70
91
|
const selector$n = (s) => (s.userSelectionActive ? 'none' : 'all');
|
|
92
|
+
/**
|
|
93
|
+
* The `<Panel />` component helps you position content above the viewport.
|
|
94
|
+
* It is used internally by the [`<MiniMap />`](/api-reference/components/minimap)
|
|
95
|
+
* and [`<Controls />`](/api-reference/components/controls) components.
|
|
96
|
+
*
|
|
97
|
+
* @public
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```jsx
|
|
101
|
+
*import { ReactFlow, Background, Panel } from '@xyflow/react';
|
|
102
|
+
*
|
|
103
|
+
*export default function Flow() {
|
|
104
|
+
* return (
|
|
105
|
+
* <ReactFlow nodes={[]} fitView>
|
|
106
|
+
* <Panel position="top-left">top-left</Panel>
|
|
107
|
+
* <Panel position="top-center">top-center</Panel>
|
|
108
|
+
* <Panel position="top-right">top-right</Panel>
|
|
109
|
+
* <Panel position="bottom-left">bottom-left</Panel>
|
|
110
|
+
* <Panel position="bottom-center">bottom-center</Panel>
|
|
111
|
+
* <Panel position="bottom-right">bottom-right</Panel>
|
|
112
|
+
* </ReactFlow>
|
|
113
|
+
* );
|
|
114
|
+
*}
|
|
115
|
+
*```
|
|
116
|
+
*/
|
|
71
117
|
const Panel = forwardRef(({ position = 'top-left', children, className, style, ...rest }, ref) => {
|
|
72
118
|
const pointerEvents = useStore(selector$n);
|
|
73
119
|
const positionClasses = `${position}`.split('-');
|
|
74
120
|
return (jsx("div", { className: cc(['react-flow__panel', className, ...positionClasses]), style: { ...style, pointerEvents }, ref: ref, ...rest, children: children }));
|
|
75
121
|
});
|
|
122
|
+
Panel.displayName = 'Panel';
|
|
76
123
|
|
|
77
124
|
function Attribution({ proOptions, position = 'bottom-right' }) {
|
|
78
125
|
if (proOptions?.hideAttribution) {
|
|
@@ -101,7 +148,7 @@ function areEqual(a, b) {
|
|
|
101
148
|
return (shallow(a.selectedNodes.map(selectId), b.selectedNodes.map(selectId)) &&
|
|
102
149
|
shallow(a.selectedEdges.map(selectId), b.selectedEdges.map(selectId)));
|
|
103
150
|
}
|
|
104
|
-
function SelectionListenerInner({ onSelectionChange }) {
|
|
151
|
+
function SelectionListenerInner({ onSelectionChange, }) {
|
|
105
152
|
const store = useStoreApi();
|
|
106
153
|
const { selectedNodes, selectedEdges } = useStore(selector$m, areEqual);
|
|
107
154
|
useEffect(() => {
|
|
@@ -112,7 +159,7 @@ function SelectionListenerInner({ onSelectionChange }) {
|
|
|
112
159
|
return null;
|
|
113
160
|
}
|
|
114
161
|
const changeSelector = (s) => !!s.onSelectionChangeHandlers;
|
|
115
|
-
function SelectionListener({ onSelectionChange }) {
|
|
162
|
+
function SelectionListener({ onSelectionChange, }) {
|
|
116
163
|
const storeHasSelectionChangeHandlers = useStore(changeSelector);
|
|
117
164
|
if (onSelectionChange || storeHasSelectionChangeHandlers) {
|
|
118
165
|
return jsx(SelectionListenerInner, { onSelectionChange: onSelectionChange });
|
|
@@ -200,9 +247,11 @@ const selector$l = (s) => ({
|
|
|
200
247
|
setPaneClickDistance: s.setPaneClickDistance,
|
|
201
248
|
});
|
|
202
249
|
const initPrevValues = {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
250
|
+
/*
|
|
251
|
+
* these are values that are also passed directly to other components
|
|
252
|
+
* than the StoreUpdater. We can reduce the number of setStore calls
|
|
253
|
+
* by setting the same values here as prev fields.
|
|
254
|
+
*/
|
|
206
255
|
translateExtent: infiniteExtent,
|
|
207
256
|
nodeOrigin: defaultNodeOrigin,
|
|
208
257
|
minZoom: 0.5,
|
|
@@ -295,38 +344,62 @@ function useColorModeClass(colorMode) {
|
|
|
295
344
|
|
|
296
345
|
const defaultDoc = typeof document !== 'undefined' ? document : null;
|
|
297
346
|
/**
|
|
298
|
-
*
|
|
347
|
+
* This hook lets you listen for specific key codes and tells you whether they are
|
|
348
|
+
* currently pressed or not.
|
|
299
349
|
*
|
|
300
350
|
* @public
|
|
301
351
|
* @param param.keyCode - The key code (string or array of strings) to use
|
|
302
352
|
* @param param.options - Options
|
|
303
353
|
* @returns boolean
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```tsx
|
|
357
|
+
*import { useKeyPress } from '@xyflow/react';
|
|
358
|
+
*
|
|
359
|
+
*export default function () {
|
|
360
|
+
* const spacePressed = useKeyPress('Space');
|
|
361
|
+
* const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s']);
|
|
362
|
+
*
|
|
363
|
+
* return (
|
|
364
|
+
* <div>
|
|
365
|
+
* {spacePressed && <p>Space pressed!</p>}
|
|
366
|
+
* {cmdAndSPressed && <p>Cmd + S pressed!</p>}
|
|
367
|
+
* </div>
|
|
368
|
+
* );
|
|
369
|
+
*}
|
|
370
|
+
*```
|
|
304
371
|
*/
|
|
305
372
|
function useKeyPress(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
373
|
+
/*
|
|
374
|
+
* the keycode can be a string 'a' or an array of strings ['a', 'a+d']
|
|
375
|
+
* a string means a single key 'a' or a combination when '+' is used 'a+d'
|
|
376
|
+
* an array means different possibilites. Explainer: ['a', 'd+s'] here the
|
|
377
|
+
* user can use the single key 'a' or the combination 'd' + 's'
|
|
378
|
+
*/
|
|
310
379
|
keyCode = null, options = { target: defaultDoc, actInsideInputWithModifier: true }) {
|
|
311
380
|
const [keyPressed, setKeyPressed] = useState(false);
|
|
312
381
|
// we need to remember if a modifier key is pressed in order to track it
|
|
313
382
|
const modifierPressed = useRef(false);
|
|
314
383
|
// we need to remember the pressed keys in order to support combinations
|
|
315
384
|
const pressedKeys = useRef(new Set([]));
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
385
|
+
/*
|
|
386
|
+
* keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
|
387
|
+
* keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
|
388
|
+
* used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
|
389
|
+
* we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
|
390
|
+
* and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
|
|
391
|
+
* we can't find it in the list of keysToWatch.
|
|
392
|
+
*/
|
|
322
393
|
const [keyCodes, keysToWatch] = useMemo(() => {
|
|
323
394
|
if (keyCode !== null) {
|
|
324
395
|
const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode];
|
|
325
396
|
const keys = keyCodeArr
|
|
326
397
|
.filter((kc) => typeof kc === 'string')
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
398
|
+
/*
|
|
399
|
+
* we first replace all '+' with '\n' which we will use to split the keys on
|
|
400
|
+
* then we replace '\n\n' with '\n+', this way we can also support the combination 'key++'
|
|
401
|
+
* in the end we simply split on '\n' to get the key array
|
|
402
|
+
*/
|
|
330
403
|
.map((kc) => kc.replace('+', '\n').replace('\n\n', '\n+').split('\n'));
|
|
331
404
|
const keysFlat = keys.reduce((res, item) => res.concat(...item), []);
|
|
332
405
|
return [keys, keysFlat];
|
|
@@ -391,12 +464,16 @@ keyCode = null, options = { target: defaultDoc, actInsideInputWithModifier: true
|
|
|
391
464
|
// utils
|
|
392
465
|
function isMatchingKey(keyCodes, pressedKeys, isUp) {
|
|
393
466
|
return (keyCodes
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
467
|
+
/*
|
|
468
|
+
* we only want to compare same sizes of keyCode definitions
|
|
469
|
+
* and pressed keys. When the user specified 'Meta' as a key somewhere
|
|
470
|
+
* this would also be truthy without this filter when user presses 'Meta' + 'r'
|
|
471
|
+
*/
|
|
397
472
|
.filter((keys) => isUp || keys.length === pressedKeys.size)
|
|
398
|
-
|
|
399
|
-
|
|
473
|
+
/*
|
|
474
|
+
* since we want to support multiple possibilities only one of the
|
|
475
|
+
* combinations need to be part of the pressed keys
|
|
476
|
+
*/
|
|
400
477
|
.some((keys) => keys.every((k) => pressedKeys.has(k))));
|
|
401
478
|
}
|
|
402
479
|
function useKeyOrCode(eventCode, keysToWatch) {
|
|
@@ -482,8 +559,8 @@ const useViewportHelper = () => {
|
|
|
482
559
|
await panZoom.setViewport(viewport, { duration: options?.duration });
|
|
483
560
|
return Promise.resolve(true);
|
|
484
561
|
},
|
|
485
|
-
screenToFlowPosition: (clientPosition, options = {
|
|
486
|
-
const { transform, snapGrid, domNode } = store.getState();
|
|
562
|
+
screenToFlowPosition: (clientPosition, options = {}) => {
|
|
563
|
+
const { transform, snapGrid, snapToGrid, domNode } = store.getState();
|
|
487
564
|
if (!domNode) {
|
|
488
565
|
return clientPosition;
|
|
489
566
|
}
|
|
@@ -492,7 +569,9 @@ const useViewportHelper = () => {
|
|
|
492
569
|
x: clientPosition.x - domX,
|
|
493
570
|
y: clientPosition.y - domY,
|
|
494
571
|
};
|
|
495
|
-
|
|
572
|
+
const _snapGrid = options.snapGrid ?? snapGrid;
|
|
573
|
+
const _snapToGrid = options.snapToGrid ?? snapToGrid;
|
|
574
|
+
return pointToRendererPoint(correctedPosition, transform, _snapToGrid, _snapGrid);
|
|
496
575
|
},
|
|
497
576
|
flowToScreenPosition: (flowPosition) => {
|
|
498
577
|
const { transform, domNode } = store.getState();
|
|
@@ -510,13 +589,17 @@ const useViewportHelper = () => {
|
|
|
510
589
|
}, []);
|
|
511
590
|
};
|
|
512
591
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
592
|
+
/*
|
|
593
|
+
* This function applies changes to nodes or edges that are triggered by React Flow internally.
|
|
594
|
+
* When you drag a node for example, React Flow will send a position change update.
|
|
595
|
+
* This function then applies the changes and returns the updated elements.
|
|
596
|
+
*/
|
|
516
597
|
function applyChanges(changes, elements) {
|
|
517
598
|
const updatedElements = [];
|
|
518
|
-
|
|
519
|
-
|
|
599
|
+
/*
|
|
600
|
+
* By storing a map of changes for each element, we can a quick lookup as we
|
|
601
|
+
* iterate over the elements array!
|
|
602
|
+
*/
|
|
520
603
|
const changesMap = new Map();
|
|
521
604
|
const addItemChanges = [];
|
|
522
605
|
for (const change of changes) {
|
|
@@ -525,15 +608,19 @@ function applyChanges(changes, elements) {
|
|
|
525
608
|
continue;
|
|
526
609
|
}
|
|
527
610
|
else if (change.type === 'remove' || change.type === 'replace') {
|
|
528
|
-
|
|
529
|
-
|
|
611
|
+
/*
|
|
612
|
+
* For a 'remove' change we can safely ignore any other changes queued for
|
|
613
|
+
* the same element, it's going to be removed anyway!
|
|
614
|
+
*/
|
|
530
615
|
changesMap.set(change.id, [change]);
|
|
531
616
|
}
|
|
532
617
|
else {
|
|
533
618
|
const elementChanges = changesMap.get(change.id);
|
|
534
619
|
if (elementChanges) {
|
|
535
|
-
|
|
536
|
-
|
|
620
|
+
/*
|
|
621
|
+
* If we have some changes queued already, we can do a mutable update of
|
|
622
|
+
* that array and save ourselves some copying.
|
|
623
|
+
*/
|
|
537
624
|
elementChanges.push(change);
|
|
538
625
|
}
|
|
539
626
|
else {
|
|
@@ -543,8 +630,10 @@ function applyChanges(changes, elements) {
|
|
|
543
630
|
}
|
|
544
631
|
for (const element of elements) {
|
|
545
632
|
const changes = changesMap.get(element.id);
|
|
546
|
-
|
|
547
|
-
|
|
633
|
+
/*
|
|
634
|
+
* When there are no changes for an element we can just push it unmodified,
|
|
635
|
+
* no need to copy it.
|
|
636
|
+
*/
|
|
548
637
|
if (!changes) {
|
|
549
638
|
updatedElements.push(element);
|
|
550
639
|
continue;
|
|
@@ -557,17 +646,21 @@ function applyChanges(changes, elements) {
|
|
|
557
646
|
updatedElements.push({ ...changes[0].item });
|
|
558
647
|
continue;
|
|
559
648
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
649
|
+
/**
|
|
650
|
+
* For other types of changes, we want to start with a shallow copy of the
|
|
651
|
+
* object so React knows this element has changed. Sequential changes will
|
|
652
|
+
* each _mutate_ this object, so there's only ever one copy.
|
|
653
|
+
*/
|
|
563
654
|
const updatedElement = { ...element };
|
|
564
655
|
for (const change of changes) {
|
|
565
656
|
applyChange(change, updatedElement);
|
|
566
657
|
}
|
|
567
658
|
updatedElements.push(updatedElement);
|
|
568
659
|
}
|
|
569
|
-
|
|
570
|
-
|
|
660
|
+
/*
|
|
661
|
+
* we need to wait for all changes to be applied before adding new items
|
|
662
|
+
* to be able to add them at the correct index
|
|
663
|
+
*/
|
|
571
664
|
if (addItemChanges.length) {
|
|
572
665
|
addItemChanges.forEach((change) => {
|
|
573
666
|
if (change.index !== undefined) {
|
|
@@ -616,22 +709,33 @@ function applyChange(change, element) {
|
|
|
616
709
|
/**
|
|
617
710
|
* Drop in function that applies node changes to an array of nodes.
|
|
618
711
|
* @public
|
|
619
|
-
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange} that describes how to update the edges of your flow in some way.
|
|
620
|
-
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
|
|
621
712
|
* @param changes - Array of changes to apply
|
|
622
713
|
* @param nodes - Array of nodes to apply the changes to
|
|
623
714
|
* @returns Array of updated nodes
|
|
624
715
|
* @example
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
716
|
+
*```tsx
|
|
717
|
+
*import { useState, useCallback } from 'react';
|
|
718
|
+
*import { ReactFlow, applyNodeChanges, type Node, type Edge, type OnNodesChange } from '@xyflow/react';
|
|
719
|
+
*
|
|
720
|
+
*export default function Flow() {
|
|
721
|
+
* const [nodes, setNodes] = useState<Node[]>([]);
|
|
722
|
+
* const [edges, setEdges] = useState<Edge[]>([]);
|
|
723
|
+
* const onNodesChange: OnNodesChange = useCallback(
|
|
724
|
+
* (changes) => {
|
|
725
|
+
* setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
|
|
726
|
+
* },
|
|
727
|
+
* [setNodes],
|
|
728
|
+
* );
|
|
729
|
+
*
|
|
730
|
+
* return (
|
|
731
|
+
* <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
|
|
732
|
+
* );
|
|
733
|
+
*}
|
|
734
|
+
*```
|
|
735
|
+
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange}
|
|
736
|
+
* that describes how to update the edges of your flow in some way.
|
|
737
|
+
* If you don't need any custom behaviour, this util can be used to take an array
|
|
738
|
+
* of these changes and apply them to your edges.
|
|
635
739
|
*/
|
|
636
740
|
function applyNodeChanges(changes, nodes) {
|
|
637
741
|
return applyChanges(changes, nodes);
|
|
@@ -639,22 +743,33 @@ function applyNodeChanges(changes, nodes) {
|
|
|
639
743
|
/**
|
|
640
744
|
* Drop in function that applies edge changes to an array of edges.
|
|
641
745
|
* @public
|
|
642
|
-
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange} that describes how to update the edges of your flow in some way.
|
|
643
|
-
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
|
|
644
746
|
* @param changes - Array of changes to apply
|
|
645
747
|
* @param edges - Array of edge to apply the changes to
|
|
646
748
|
* @returns Array of updated edges
|
|
647
749
|
* @example
|
|
750
|
+
* ```tsx
|
|
751
|
+
*import { useState, useCallback } from 'react';
|
|
752
|
+
*import { ReactFlow, applyEdgeChanges } from '@xyflow/react';
|
|
753
|
+
*
|
|
754
|
+
*export default function Flow() {
|
|
755
|
+
* const [nodes, setNodes] = useState([]);
|
|
756
|
+
* const [edges, setEdges] = useState([]);
|
|
648
757
|
* const onEdgesChange = useCallback(
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
758
|
+
* (changes) => {
|
|
759
|
+
* setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
|
|
760
|
+
* },
|
|
761
|
+
* [setEdges],
|
|
762
|
+
* );
|
|
763
|
+
*
|
|
764
|
+
* return (
|
|
765
|
+
* <ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
|
|
766
|
+
* );
|
|
767
|
+
*}
|
|
768
|
+
*```
|
|
769
|
+
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange}
|
|
770
|
+
* that describes how to update the edges of your flow in some way.
|
|
771
|
+
* If you don't need any custom behaviour, this util can be used to take an array
|
|
772
|
+
* of these changes and apply them to your edges.
|
|
658
773
|
*/
|
|
659
774
|
function applyEdgeChanges(changes, edges) {
|
|
660
775
|
return applyChanges(changes, edges);
|
|
@@ -673,9 +788,11 @@ function getSelectionChanges(items, selectedIds = new Set(), mutateItem = false)
|
|
|
673
788
|
// we don't want to set all items to selected=false on the first selection
|
|
674
789
|
if (!(item.selected === undefined && !willBeSelected) && item.selected !== willBeSelected) {
|
|
675
790
|
if (mutateItem) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
791
|
+
/*
|
|
792
|
+
* this hack is needed for nodes. When the user dragged a node, it's selected.
|
|
793
|
+
* When another node gets dragged, we need to deselect the previous one,
|
|
794
|
+
* in order to have only one selected node at a time - the onNodesChange callback comes too late here :/
|
|
795
|
+
*/
|
|
679
796
|
item.selected = willBeSelected;
|
|
680
797
|
}
|
|
681
798
|
changes.push(createSelectionChange(item.id, willBeSelected));
|
|
@@ -712,22 +829,46 @@ function elementToRemoveChange(item) {
|
|
|
712
829
|
}
|
|
713
830
|
|
|
714
831
|
/**
|
|
715
|
-
* Test whether an object is useable as
|
|
832
|
+
* Test whether an object is useable as an [`Node`](/api-reference/types/node).
|
|
833
|
+
* In TypeScript this is a type guard that will narrow the type of whatever you pass in to
|
|
834
|
+
* [`Node`](/api-reference/types/node) if it returns `true`.
|
|
835
|
+
*
|
|
716
836
|
* @public
|
|
717
837
|
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true
|
|
718
838
|
* @param element - The element to test
|
|
719
839
|
* @returns A boolean indicating whether the element is an Node
|
|
840
|
+
*
|
|
841
|
+
* @example
|
|
842
|
+
* ```js
|
|
843
|
+
*import { isNode } from '@xyflow/react';
|
|
844
|
+
*
|
|
845
|
+
*if (isNode(node)) {
|
|
846
|
+
* // ..
|
|
847
|
+
*}
|
|
848
|
+
*```
|
|
720
849
|
*/
|
|
721
850
|
const isNode = (element) => isNodeBase(element);
|
|
722
851
|
/**
|
|
723
|
-
* Test whether an object is useable as an Edge
|
|
852
|
+
* Test whether an object is useable as an [`Edge`](/api-reference/types/edge).
|
|
853
|
+
* In TypeScript this is a type guard that will narrow the type of whatever you pass in to
|
|
854
|
+
* [`Edge`](/api-reference/types/edge) if it returns `true`.
|
|
855
|
+
*
|
|
724
856
|
* @public
|
|
725
857
|
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true
|
|
726
858
|
* @param element - The element to test
|
|
727
859
|
* @returns A boolean indicating whether the element is an Edge
|
|
860
|
+
*
|
|
861
|
+
* @example
|
|
862
|
+
* ```js
|
|
863
|
+
*import { isEdge } from '@xyflow/react';
|
|
864
|
+
*
|
|
865
|
+
*if (isEdge(edge)) {
|
|
866
|
+
* // ..
|
|
867
|
+
*}
|
|
868
|
+
*```
|
|
728
869
|
*/
|
|
729
870
|
const isEdge = (element) => isEdgeBase(element);
|
|
730
|
-
// eslint-disable-next-line @typescript-eslint/
|
|
871
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
731
872
|
function fixedForwardRef(render) {
|
|
732
873
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
733
874
|
return forwardRef(render);
|
|
@@ -745,19 +886,25 @@ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffec
|
|
|
745
886
|
* @returns a Queue object
|
|
746
887
|
*/
|
|
747
888
|
function useQueue(runQueue) {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
889
|
+
/*
|
|
890
|
+
* Because we're using a ref above, we need some way to let React know when to
|
|
891
|
+
* actually process the queue. We increment this number any time we mutate the
|
|
892
|
+
* queue, creating a new state to trigger the layout effect below.
|
|
893
|
+
* Using a boolean dirty flag here instead would lead to issues related to
|
|
894
|
+
* automatic batching. (https://github.com/xyflow/xyflow/issues/4779)
|
|
895
|
+
*/
|
|
753
896
|
const [serial, setSerial] = useState(BigInt(0));
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
897
|
+
/*
|
|
898
|
+
* A reference of all the batched updates to process before the next render. We
|
|
899
|
+
* want a reference here so multiple synchronous calls to `setNodes` etc can be
|
|
900
|
+
* batched together.
|
|
901
|
+
*/
|
|
757
902
|
const [queue] = useState(() => createQueue(() => setSerial(n => n + BigInt(1))));
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
903
|
+
/*
|
|
904
|
+
* Layout effects are guaranteed to run before the next render which means we
|
|
905
|
+
* shouldn't run into any issues with stale state or weird issues that come from
|
|
906
|
+
* rendering things one frame later than expected (we used to use `setTimeout`).
|
|
907
|
+
*/
|
|
761
908
|
useIsomorphicLayoutEffect(() => {
|
|
762
909
|
const queueItems = queue.get();
|
|
763
910
|
if (queueItems.length) {
|
|
@@ -792,9 +939,11 @@ function BatchProvider({ children, }) {
|
|
|
792
939
|
const store = useStoreApi();
|
|
793
940
|
const nodeQueueHandler = useCallback((queueItems) => {
|
|
794
941
|
const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
942
|
+
/*
|
|
943
|
+
* This is essentially an `Array.reduce` in imperative clothing. Processing
|
|
944
|
+
* this queue is a relatively hot path so we'd like to avoid the overhead of
|
|
945
|
+
* array methods where we can.
|
|
946
|
+
*/
|
|
798
947
|
let next = nodes;
|
|
799
948
|
for (const payload of queueItems) {
|
|
800
949
|
next = typeof payload === 'function' ? payload(next) : payload;
|
|
@@ -840,10 +989,33 @@ function useBatchContext() {
|
|
|
840
989
|
|
|
841
990
|
const selector$k = (s) => !!s.panZoom;
|
|
842
991
|
/**
|
|
843
|
-
*
|
|
992
|
+
* This hook returns a ReactFlowInstance that can be used to update nodes and edges, manipulate the viewport, or query the current state of the flow.
|
|
844
993
|
*
|
|
845
994
|
* @public
|
|
846
995
|
* @returns ReactFlowInstance
|
|
996
|
+
*
|
|
997
|
+
* @example
|
|
998
|
+
* ```jsx
|
|
999
|
+
*import { useCallback, useState } from 'react';
|
|
1000
|
+
*import { useReactFlow } from '@xyflow/react';
|
|
1001
|
+
*
|
|
1002
|
+
*export function NodeCounter() {
|
|
1003
|
+
* const reactFlow = useReactFlow();
|
|
1004
|
+
* const [count, setCount] = useState(0);
|
|
1005
|
+
* const countNodes = useCallback(() => {
|
|
1006
|
+
* setCount(reactFlow.getNodes().length);
|
|
1007
|
+
* // you need to pass it as a dependency if you are using it with useEffect or useCallback
|
|
1008
|
+
* // because at the first render, it's not initialized yet and some functions might not work.
|
|
1009
|
+
* }, [reactFlow]);
|
|
1010
|
+
*
|
|
1011
|
+
* return (
|
|
1012
|
+
* <div>
|
|
1013
|
+
* <button onClick={countNodes}>Update count</button>
|
|
1014
|
+
* <p>There are {count} nodes in the flow.</p>
|
|
1015
|
+
* </div>
|
|
1016
|
+
* );
|
|
1017
|
+
*}
|
|
1018
|
+
*```
|
|
847
1019
|
*/
|
|
848
1020
|
function useReactFlow() {
|
|
849
1021
|
const viewportHelper = useViewportHelper();
|
|
@@ -1310,8 +1482,10 @@ function Pane({ isSelecting, selectionKeyPressed, selectionMode = SelectionMode.
|
|
|
1310
1482
|
}
|
|
1311
1483
|
event.target?.releasePointerCapture?.(event.pointerId);
|
|
1312
1484
|
const { userSelectionRect } = store.getState();
|
|
1313
|
-
|
|
1314
|
-
|
|
1485
|
+
/*
|
|
1486
|
+
* We only want to trigger click functions when in selection mode if
|
|
1487
|
+
* the user did not move the mouse.
|
|
1488
|
+
*/
|
|
1315
1489
|
if (!userSelectionActive && userSelectionRect && event.target === container.current) {
|
|
1316
1490
|
onClick?.(event);
|
|
1317
1491
|
}
|
|
@@ -1321,8 +1495,10 @@ function Pane({ isSelecting, selectionKeyPressed, selectionMode = SelectionMode.
|
|
|
1321
1495
|
nodesSelectionActive: selectedNodeIds.current.size > 0,
|
|
1322
1496
|
});
|
|
1323
1497
|
onSelectionEnd?.(event);
|
|
1324
|
-
|
|
1325
|
-
|
|
1498
|
+
/*
|
|
1499
|
+
* If the user kept holding the selectionKey during the selection,
|
|
1500
|
+
* we need to reset the selectionInProgress, so the next click event is not prevented
|
|
1501
|
+
*/
|
|
1326
1502
|
if (selectionKeyPressed || selectionOnDrag) {
|
|
1327
1503
|
selectionInProgress.current = false;
|
|
1328
1504
|
}
|
|
@@ -1332,10 +1508,12 @@ function Pane({ isSelecting, selectionKeyPressed, selectionMode = SelectionMode.
|
|
|
1332
1508
|
return (jsxs("div", { className: cc(['react-flow__pane', { draggable, dragging, selection: isSelecting }]), onClick: hasActiveSelection ? undefined : wrapHandler(onClick, container), onContextMenu: wrapHandler(onContextMenu, container), onWheel: wrapHandler(onWheel, container), onPointerEnter: hasActiveSelection ? undefined : onPaneMouseEnter, onPointerDown: hasActiveSelection ? onPointerDown : onPaneMouseMove, onPointerMove: hasActiveSelection ? onPointerMove : onPaneMouseMove, onPointerUp: hasActiveSelection ? onPointerUp : undefined, onPointerLeave: onPaneMouseLeave, ref: container, style: containerStyle, children: [children, jsx(UserSelection, {})] }));
|
|
1333
1509
|
}
|
|
1334
1510
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1511
|
+
/*
|
|
1512
|
+
* this handler is called by
|
|
1513
|
+
* 1. the click handler when node is not draggable or selectNodesOnDrag = false
|
|
1514
|
+
* or
|
|
1515
|
+
* 2. the on drag start handler when node is draggable and selectNodesOnDrag = true
|
|
1516
|
+
*/
|
|
1339
1517
|
function handleNodeClick({ id, store, unselect = false, nodeRef, }) {
|
|
1340
1518
|
const { addSelectedNodes, unselectNodesAndEdges, multiSelectionActive, nodeLookup, onError } = store.getState();
|
|
1341
1519
|
const node = nodeLookup.get(id);
|
|
@@ -1414,8 +1592,10 @@ function useMoveSelectedNodes() {
|
|
|
1414
1592
|
const { nodeExtent, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions, nodeLookup, nodeOrigin } = store.getState();
|
|
1415
1593
|
const nodeUpdates = new Map();
|
|
1416
1594
|
const isSelected = selectedAndDraggable(nodesDraggable);
|
|
1417
|
-
|
|
1418
|
-
|
|
1595
|
+
/*
|
|
1596
|
+
* by default a node moves 5px on each key press
|
|
1597
|
+
* if snap grid is enabled, we use that for the velocity
|
|
1598
|
+
*/
|
|
1419
1599
|
const xVelo = snapToGrid ? snapGrid[0] : 5;
|
|
1420
1600
|
const yVelo = snapToGrid ? snapGrid[1] : 5;
|
|
1421
1601
|
const xDiff = params.direction.x * xVelo * params.factor;
|
|
@@ -1451,6 +1631,34 @@ function useMoveSelectedNodes() {
|
|
|
1451
1631
|
const NodeIdContext = createContext(null);
|
|
1452
1632
|
const Provider = NodeIdContext.Provider;
|
|
1453
1633
|
NodeIdContext.Consumer;
|
|
1634
|
+
/**
|
|
1635
|
+
* You can use this hook to get the id of the node it is used inside. It is useful
|
|
1636
|
+
* if you need the node's id deeper in the render tree but don't want to manually
|
|
1637
|
+
* drill down the id as a prop.
|
|
1638
|
+
*
|
|
1639
|
+
* @public
|
|
1640
|
+
* @returns id of the node
|
|
1641
|
+
*
|
|
1642
|
+
* @example
|
|
1643
|
+
*```jsx
|
|
1644
|
+
*import { useNodeId } from '@xyflow/react';
|
|
1645
|
+
*
|
|
1646
|
+
*export default function CustomNode() {
|
|
1647
|
+
* return (
|
|
1648
|
+
* <div>
|
|
1649
|
+
* <span>This node has an id of </span>
|
|
1650
|
+
* <NodeIdDisplay />
|
|
1651
|
+
* </div>
|
|
1652
|
+
* );
|
|
1653
|
+
*}
|
|
1654
|
+
*
|
|
1655
|
+
*function NodeIdDisplay() {
|
|
1656
|
+
* const nodeId = useNodeId();
|
|
1657
|
+
*
|
|
1658
|
+
* return <span>{nodeId}</span>;
|
|
1659
|
+
*}
|
|
1660
|
+
*```
|
|
1661
|
+
*/
|
|
1454
1662
|
const useNodeId = () => {
|
|
1455
1663
|
const nodeId = useContext(NodeIdContext);
|
|
1456
1664
|
return nodeId;
|
|
@@ -1473,6 +1681,7 @@ const connectingSelector = (nodeId, handleId, type) => (state) => {
|
|
|
1473
1681
|
? fromHandle?.type !== type
|
|
1474
1682
|
: nodeId !== fromHandle?.nodeId || handleId !== fromHandle?.id,
|
|
1475
1683
|
connectionInProcess: !!fromHandle,
|
|
1684
|
+
clickConnectionInProcess: !!clickHandle,
|
|
1476
1685
|
valid: connectingTo && isValid,
|
|
1477
1686
|
};
|
|
1478
1687
|
};
|
|
@@ -1482,7 +1691,7 @@ function HandleComponent({ type = 'source', position = Position.Top, isValidConn
|
|
|
1482
1691
|
const store = useStoreApi();
|
|
1483
1692
|
const nodeId = useNodeId();
|
|
1484
1693
|
const { connectOnClick, noPanClassName, rfId } = useStore(selector$g, shallow);
|
|
1485
|
-
const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, valid } = useStore(connectingSelector(nodeId, handleId, type), shallow);
|
|
1694
|
+
const { connectingFrom, connectingTo, clickConnecting, isPossibleEndHandle, connectionInProcess, clickConnectionInProcess, valid, } = useStore(connectingSelector(nodeId, handleId, type), shallow);
|
|
1486
1695
|
if (!nodeId) {
|
|
1487
1696
|
store.getState().onError?.('010', errorMessages['error010']());
|
|
1488
1697
|
}
|
|
@@ -1590,16 +1799,40 @@ function HandleComponent({ type = 'source', position = Position.Top, isValidConn
|
|
|
1590
1799
|
connectingfrom: connectingFrom,
|
|
1591
1800
|
connectingto: connectingTo,
|
|
1592
1801
|
valid,
|
|
1593
|
-
|
|
1594
|
-
|
|
1802
|
+
/*
|
|
1803
|
+
* shows where you can start a connection from
|
|
1804
|
+
* and where you can end it while connecting
|
|
1805
|
+
*/
|
|
1595
1806
|
connectionindicator: isConnectable &&
|
|
1596
1807
|
(!connectionInProcess || isPossibleEndHandle) &&
|
|
1597
|
-
(connectionInProcess ? isConnectableEnd : isConnectableStart),
|
|
1808
|
+
(connectionInProcess || clickConnectionInProcess ? isConnectableEnd : isConnectableStart),
|
|
1598
1809
|
},
|
|
1599
1810
|
]), onMouseDown: onPointerDown, onTouchStart: onPointerDown, onClick: connectOnClick ? onClick : undefined, ref: ref, ...rest, children: children }));
|
|
1600
1811
|
}
|
|
1601
1812
|
/**
|
|
1602
|
-
* The Handle component is
|
|
1813
|
+
* The `<Handle />` component is used in your [custom nodes](/learn/customization/custom-nodes)
|
|
1814
|
+
* to define connection points.
|
|
1815
|
+
*
|
|
1816
|
+
*@public
|
|
1817
|
+
*
|
|
1818
|
+
*@example
|
|
1819
|
+
*
|
|
1820
|
+
*```jsx
|
|
1821
|
+
*import { Handle, Position } from '@xyflow/react';
|
|
1822
|
+
*
|
|
1823
|
+
*export function CustomNode({ data }) {
|
|
1824
|
+
* return (
|
|
1825
|
+
* <>
|
|
1826
|
+
* <div style={{ padding: '10px 20px' }}>
|
|
1827
|
+
* {data.label}
|
|
1828
|
+
* </div>
|
|
1829
|
+
*
|
|
1830
|
+
* <Handle type="target" position={Position.Left} />
|
|
1831
|
+
* <Handle type="source" position={Position.Right} />
|
|
1832
|
+
* </>
|
|
1833
|
+
* );
|
|
1834
|
+
*};
|
|
1835
|
+
*```
|
|
1603
1836
|
*/
|
|
1604
1837
|
const Handle = memo(fixedForwardRef(HandleComponent));
|
|
1605
1838
|
|
|
@@ -1792,8 +2025,10 @@ function useNodeObserver({ node, nodeType, hasDimensions, resizeObserver, }) {
|
|
|
1792
2025
|
}, []);
|
|
1793
2026
|
useEffect(() => {
|
|
1794
2027
|
if (nodeRef.current) {
|
|
1795
|
-
|
|
1796
|
-
|
|
2028
|
+
/*
|
|
2029
|
+
* when the user programmatically changes the source or handle position, we need to update the internals
|
|
2030
|
+
* to make sure the edges are updated correctly
|
|
2031
|
+
*/
|
|
1797
2032
|
const typeChanged = prevType.current !== nodeType;
|
|
1798
2033
|
const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
|
|
1799
2034
|
const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
|
|
@@ -1810,7 +2045,7 @@ function useNodeObserver({ node, nodeType, hasDimensions, resizeObserver, }) {
|
|
|
1810
2045
|
return nodeRef;
|
|
1811
2046
|
}
|
|
1812
2047
|
|
|
1813
|
-
function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onContextMenu, onDoubleClick, nodesDraggable, elementsSelectable, nodesConnectable, nodesFocusable, resizeObserver, noDragClassName, noPanClassName, disableKeyboardA11y, rfId, nodeTypes,
|
|
2048
|
+
function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onContextMenu, onDoubleClick, nodesDraggable, elementsSelectable, nodesConnectable, nodesFocusable, resizeObserver, noDragClassName, noPanClassName, disableKeyboardA11y, rfId, nodeTypes, nodeClickDistance, onError, }) {
|
|
1814
2049
|
const { node, internals, isParent } = useStore((s) => {
|
|
1815
2050
|
const node = s.nodeLookup.get(id);
|
|
1816
2051
|
const isParent = s.parentLookup.has(id);
|
|
@@ -1868,8 +2103,10 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
|
|
|
1868
2103
|
const onSelectNodeHandler = (event) => {
|
|
1869
2104
|
const { selectNodesOnDrag, nodeDragThreshold } = store.getState();
|
|
1870
2105
|
if (isSelectable && (!selectNodesOnDrag || !isDraggable || nodeDragThreshold > 0)) {
|
|
1871
|
-
|
|
1872
|
-
|
|
2106
|
+
/*
|
|
2107
|
+
* this handler gets called by XYDrag on drag start when selectNodesOnDrag=true
|
|
2108
|
+
* here we only need to call it when selectNodesOnDrag=false
|
|
2109
|
+
*/
|
|
1873
2110
|
handleNodeClick({
|
|
1874
2111
|
id,
|
|
1875
2112
|
store,
|
|
@@ -1945,29 +2182,31 @@ function NodeRendererComponent(props) {
|
|
|
1945
2182
|
const resizeObserver = useResizeObserver();
|
|
1946
2183
|
return (jsx("div", { className: "react-flow__nodes", style: containerStyle, children: nodeIds.map((nodeId) => {
|
|
1947
2184
|
return (
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2185
|
+
/*
|
|
2186
|
+
* The split of responsibilities between NodeRenderer and
|
|
2187
|
+
* NodeComponentWrapper may appear weird. However, it’s designed to
|
|
2188
|
+
* minimize the cost of updates when individual nodes change.
|
|
2189
|
+
*
|
|
2190
|
+
* For example, when you’re dragging a single node, that node gets
|
|
2191
|
+
* updated multiple times per second. If `NodeRenderer` were to update
|
|
2192
|
+
* every time, it would have to re-run the `nodes.map()` loop every
|
|
2193
|
+
* time. This gets pricey with hundreds of nodes, especially if every
|
|
2194
|
+
* loop cycle does more than just rendering a JSX element!
|
|
2195
|
+
*
|
|
2196
|
+
* As a result of this choice, we took the following implementation
|
|
2197
|
+
* decisions:
|
|
2198
|
+
* - NodeRenderer subscribes *only* to node IDs – and therefore
|
|
2199
|
+
* rerender *only* when visible nodes are added or removed.
|
|
2200
|
+
* - NodeRenderer performs all operations the result of which can be
|
|
2201
|
+
* shared between nodes (such as creating the `ResizeObserver`
|
|
2202
|
+
* instance, or subscribing to `selector`). This means extra prop
|
|
2203
|
+
* drilling into `NodeComponentWrapper`, but it means we need to run
|
|
2204
|
+
* these operations only once – instead of once per node.
|
|
2205
|
+
* - Any operations that you’d normally write inside `nodes.map` are
|
|
2206
|
+
* moved into `NodeComponentWrapper`. This ensures they are
|
|
2207
|
+
* memorized – so if `NodeRenderer` *has* to rerender, it only
|
|
2208
|
+
* needs to regenerate the list of nodes, nothing else.
|
|
2209
|
+
*/
|
|
1971
2210
|
jsx(NodeWrapper, { id: nodeId, nodeTypes: props.nodeTypes, nodeExtent: props.nodeExtent, onClick: props.onNodeClick, onMouseEnter: props.onNodeMouseEnter, onMouseMove: props.onNodeMouseMove, onMouseLeave: props.onNodeMouseLeave, onContextMenu: props.onNodeContextMenu, onDoubleClick: props.onNodeDoubleClick, noDragClassName: props.noDragClassName, noPanClassName: props.noPanClassName, rfId: props.rfId, disableKeyboardA11y: props.disableKeyboardA11y, resizeObserver: resizeObserver, nodesDraggable: nodesDraggable, nodesConnectable: nodesConnectable, nodesFocusable: nodesFocusable, elementsSelectable: elementsSelectable, nodeClickDistance: props.nodeClickDistance, onError: onError }, nodeId));
|
|
1972
2211
|
}) }));
|
|
1973
2212
|
}
|
|
@@ -2046,9 +2285,11 @@ const Marker = ({ id, type, color, width = 12.5, height = 12.5, markerUnits = 's
|
|
|
2046
2285
|
}
|
|
2047
2286
|
return (jsx("marker", { className: "react-flow__arrowhead", id: id, markerWidth: `${width}`, markerHeight: `${height}`, viewBox: "-10 -10 20 20", markerUnits: markerUnits, orient: orient, refX: "0", refY: "0", children: jsx(Symbol, { color: color, strokeWidth: strokeWidth }) }));
|
|
2048
2287
|
};
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2288
|
+
/*
|
|
2289
|
+
* when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
|
2290
|
+
* when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
|
2291
|
+
* that we can then use for creating our unique marker ids
|
|
2292
|
+
*/
|
|
2052
2293
|
const MarkerDefinitions = ({ defaultColor, rfId }) => {
|
|
2053
2294
|
const edges = useStore((s) => s.edges);
|
|
2054
2295
|
const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions);
|
|
@@ -2090,8 +2331,61 @@ function EdgeTextComponent({ x, y, label, labelStyle = {}, labelShowBg = true, l
|
|
|
2090
2331
|
return (jsxs("g", { transform: `translate(${x - edgeTextBbox.width / 2} ${y - edgeTextBbox.height / 2})`, className: edgeTextClasses, visibility: edgeTextBbox.width ? 'visible' : 'hidden', ...rest, children: [labelShowBg && (jsx("rect", { width: edgeTextBbox.width + 2 * labelBgPadding[0], x: -labelBgPadding[0], y: -labelBgPadding[1], height: edgeTextBbox.height + 2 * labelBgPadding[1], className: "react-flow__edge-textbg", style: labelBgStyle, rx: labelBgBorderRadius, ry: labelBgBorderRadius })), jsx("text", { className: "react-flow__edge-text", y: edgeTextBbox.height / 2, dy: "0.3em", ref: edgeTextRef, style: labelStyle, children: label }), children] }));
|
|
2091
2332
|
}
|
|
2092
2333
|
EdgeTextComponent.displayName = 'EdgeText';
|
|
2334
|
+
/**
|
|
2335
|
+
* You can use the `<EdgeText />` component as a helper component to display text
|
|
2336
|
+
* within your custom edges.
|
|
2337
|
+
*
|
|
2338
|
+
*@public
|
|
2339
|
+
*
|
|
2340
|
+
*@example
|
|
2341
|
+
*```jsx
|
|
2342
|
+
*import { EdgeText } from '@xyflow/react';
|
|
2343
|
+
*
|
|
2344
|
+
*export function CustomEdgeLabel({ label }) {
|
|
2345
|
+
* return (
|
|
2346
|
+
* <EdgeText
|
|
2347
|
+
* x={100}
|
|
2348
|
+
* y={100}
|
|
2349
|
+
* label={label}
|
|
2350
|
+
* labelStyle={{ fill: 'white' }}
|
|
2351
|
+
* labelShowBg
|
|
2352
|
+
* labelBgStyle={{ fill: 'red' }}
|
|
2353
|
+
* labelBgPadding={[2, 4]}
|
|
2354
|
+
* labelBgBorderRadius={2}
|
|
2355
|
+
* />
|
|
2356
|
+
* );
|
|
2357
|
+
*}
|
|
2358
|
+
*```
|
|
2359
|
+
*/
|
|
2093
2360
|
const EdgeText = memo(EdgeTextComponent);
|
|
2094
2361
|
|
|
2362
|
+
/**
|
|
2363
|
+
* The `<BaseEdge />` component gets used internally for all the edges. It can be
|
|
2364
|
+
* used inside a custom edge and handles the invisible helper edge and the edge label
|
|
2365
|
+
* for you.
|
|
2366
|
+
*
|
|
2367
|
+
* @public
|
|
2368
|
+
* @example
|
|
2369
|
+
* ```jsx
|
|
2370
|
+
*import { BaseEdge } from '@xyflow/react';
|
|
2371
|
+
*
|
|
2372
|
+
*export function CustomEdge({ sourceX, sourceY, targetX, targetY, ...props }) {
|
|
2373
|
+
* const [edgePath] = getStraightPath({
|
|
2374
|
+
* sourceX,
|
|
2375
|
+
* sourceY,
|
|
2376
|
+
* targetX,
|
|
2377
|
+
* targetY,
|
|
2378
|
+
* });
|
|
2379
|
+
*
|
|
2380
|
+
* return <BaseEdge path={edgePath} {...props} />;
|
|
2381
|
+
*}
|
|
2382
|
+
*```
|
|
2383
|
+
*
|
|
2384
|
+
* @remarks If you want to use an edge marker with the [`<BaseEdge />`](/api-reference/components/base-edge) component,
|
|
2385
|
+
* you can pass the `markerStart` or `markerEnd` props passed to your custom edge
|
|
2386
|
+
* through to the [`<BaseEdge />`](/api-reference/components/base-edge) component.
|
|
2387
|
+
* You can see all the props passed to a custom edge by looking at the [`EdgeProps`](/api-reference/types/edge-props) type.
|
|
2388
|
+
*/
|
|
2095
2389
|
function BaseEdge({ path, labelX, labelY, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, interactionWidth = 20, ...props }) {
|
|
2096
2390
|
return (jsxs(Fragment, { children: [jsx("path", { ...props, d: path, fill: "none", className: cc(['react-flow__edge-path', props.className]) }), interactionWidth && (jsx("path", { d: path, fill: "none", strokeOpacity: 0, strokeWidth: interactionWidth, className: "react-flow__edge-interaction" })), label && isNumeric(labelX) && isNumeric(labelY) ? (jsx(EdgeText, { x: labelX, y: labelY, label: label, labelStyle: labelStyle, labelShowBg: labelShowBg, labelBgStyle: labelBgStyle, labelBgPadding: labelBgPadding, labelBgBorderRadius: labelBgBorderRadius })) : null] }));
|
|
2097
2391
|
}
|
|
@@ -2102,6 +2396,11 @@ function getControl({ pos, x1, y1, x2, y2 }) {
|
|
|
2102
2396
|
}
|
|
2103
2397
|
return [x1, 0.5 * (y1 + y2)];
|
|
2104
2398
|
}
|
|
2399
|
+
/**
|
|
2400
|
+
* The `getSimpleBezierPath` util returns everything you need to render a simple
|
|
2401
|
+
* bezier edge between two nodes.
|
|
2402
|
+
* @public
|
|
2403
|
+
*/
|
|
2105
2404
|
function getSimpleBezierPath({ sourceX, sourceY, sourcePosition = Position.Bottom, targetX, targetY, targetPosition = Position.Top, }) {
|
|
2106
2405
|
const [sourceControlX, sourceControlY] = getControl({
|
|
2107
2406
|
pos: sourcePosition,
|
|
@@ -2502,9 +2801,28 @@ function getSelector(connectionSelector) {
|
|
|
2502
2801
|
return storeSelector$1;
|
|
2503
2802
|
}
|
|
2504
2803
|
/**
|
|
2505
|
-
*
|
|
2804
|
+
* The `useConnection` hook returns the current connection when there is an active
|
|
2805
|
+
* connection interaction. If no connection interaction is active, it returns null
|
|
2806
|
+
* for every property. A typical use case for this hook is to colorize handles
|
|
2807
|
+
* based on a certain condition (e.g. if the connection is valid or not).
|
|
2506
2808
|
*
|
|
2507
2809
|
* @public
|
|
2810
|
+
* @example
|
|
2811
|
+
*
|
|
2812
|
+
* ```tsx
|
|
2813
|
+
*import { useConnection } from '@xyflow/react';
|
|
2814
|
+
*
|
|
2815
|
+
*function App() {
|
|
2816
|
+
* const connection = useConnection();
|
|
2817
|
+
*
|
|
2818
|
+
* return (
|
|
2819
|
+
* <div> {connection ? `Someone is trying to make a connection from ${connection.fromNode} to this one.` : 'There are currently no incoming connections!'}
|
|
2820
|
+
*
|
|
2821
|
+
* </div>
|
|
2822
|
+
* );
|
|
2823
|
+
* }
|
|
2824
|
+
* ```
|
|
2825
|
+
*
|
|
2508
2826
|
* @returns ConnectionState
|
|
2509
2827
|
*/
|
|
2510
2828
|
function useConnection(connectionSelector) {
|
|
@@ -2519,7 +2837,7 @@ const selector$7 = (s) => ({
|
|
|
2519
2837
|
width: s.width,
|
|
2520
2838
|
height: s.height,
|
|
2521
2839
|
});
|
|
2522
|
-
function ConnectionLineWrapper({ containerStyle, style, type, component }) {
|
|
2840
|
+
function ConnectionLineWrapper({ containerStyle, style, type, component, }) {
|
|
2523
2841
|
const { nodesConnectable, width, height, isValid, inProgress } = useStore(selector$7, shallow);
|
|
2524
2842
|
const renderConnection = !!(width && nodesConnectable && inProgress);
|
|
2525
2843
|
if (!renderConnection) {
|
|
@@ -2527,7 +2845,7 @@ function ConnectionLineWrapper({ containerStyle, style, type, component }) {
|
|
|
2527
2845
|
}
|
|
2528
2846
|
return (jsx("svg", { style: containerStyle, width: width, height: height, className: "react-flow__connectionline react-flow__container", children: jsx("g", { className: cc(['react-flow__connection', getConnectionStatus(isValid)]), children: jsx(ConnectionLine, { style: style, type: type, CustomComponent: component, isValid: isValid }) }) }));
|
|
2529
2847
|
}
|
|
2530
|
-
const ConnectionLine = ({ style, type = ConnectionLineType.Bezier, CustomComponent, isValid }) => {
|
|
2848
|
+
const ConnectionLine = ({ style, type = ConnectionLineType.Bezier, CustomComponent, isValid, }) => {
|
|
2531
2849
|
const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition } = useConnection();
|
|
2532
2850
|
if (!inProgress) {
|
|
2533
2851
|
return;
|
|
@@ -2700,12 +3018,14 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2700
3018
|
...getInitialState({ nodes, edges, width, height, fitView: fitView$1, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
|
2701
3019
|
setNodes: (nodes) => {
|
|
2702
3020
|
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
3021
|
+
/*
|
|
3022
|
+
* setNodes() is called exclusively in response to user actions:
|
|
3023
|
+
* - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
|
3024
|
+
* - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
|
|
3025
|
+
*
|
|
3026
|
+
* When this happens, we take the note objects passed by the user and extend them with fields
|
|
3027
|
+
* relevant for internal React Flow operations.
|
|
3028
|
+
*/
|
|
2709
3029
|
adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
|
2710
3030
|
nodeOrigin,
|
|
2711
3031
|
nodeExtent,
|
|
@@ -2731,9 +3051,11 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2731
3051
|
set({ hasDefaultEdges: true });
|
|
2732
3052
|
}
|
|
2733
3053
|
},
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
3054
|
+
/*
|
|
3055
|
+
* Every node gets registerd at a ResizeObserver. Whenever a node
|
|
3056
|
+
* changes its dimensions, this function is called to measure the
|
|
3057
|
+
* new dimensions and update the nodes.
|
|
3058
|
+
*/
|
|
2737
3059
|
updateNodeInternals: (updates, params = { triggerFitView: true }) => {
|
|
2738
3060
|
const { triggerNodeChanges, nodeLookup, parentLookup, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin, nodeExtent, debug, fitViewSync, } = get();
|
|
2739
3061
|
const { changes, updatedInternals } = updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOrigin, nodeExtent);
|
|
@@ -2750,11 +3072,13 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2750
3072
|
nodes: fitViewOnInitOptions?.nodes,
|
|
2751
3073
|
});
|
|
2752
3074
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
3075
|
+
/*
|
|
3076
|
+
* here we are cirmumventing the onNodesChange handler
|
|
3077
|
+
* in order to be able to display nodes even if the user
|
|
3078
|
+
* has not provided an onNodesChange handler.
|
|
3079
|
+
* Nodes are only rendered if they have a width and height
|
|
3080
|
+
* attribute which they get from this handler.
|
|
3081
|
+
*/
|
|
2758
3082
|
set({ fitViewDone: nextFitViewDone });
|
|
2759
3083
|
}
|
|
2760
3084
|
else {
|
|
@@ -2771,8 +3095,11 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2771
3095
|
updateNodePositions: (nodeDragItems, dragging = false) => {
|
|
2772
3096
|
const parentExpandChildren = [];
|
|
2773
3097
|
const changes = [];
|
|
3098
|
+
const { nodeLookup, triggerNodeChanges } = get();
|
|
2774
3099
|
for (const [id, dragItem] of nodeDragItems) {
|
|
2775
|
-
|
|
3100
|
+
// we are using the nodelookup to be sure to use the current expandParent and parentId value
|
|
3101
|
+
const node = nodeLookup.get(id);
|
|
3102
|
+
const expandParent = !!(node?.expandParent && node?.parentId && dragItem?.position);
|
|
2776
3103
|
const change = {
|
|
2777
3104
|
id,
|
|
2778
3105
|
type: 'position',
|
|
@@ -2784,25 +3111,25 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2784
3111
|
: dragItem.position,
|
|
2785
3112
|
dragging,
|
|
2786
3113
|
};
|
|
2787
|
-
if (expandParent) {
|
|
3114
|
+
if (expandParent && node.parentId) {
|
|
2788
3115
|
parentExpandChildren.push({
|
|
2789
3116
|
id,
|
|
2790
|
-
parentId:
|
|
3117
|
+
parentId: node.parentId,
|
|
2791
3118
|
rect: {
|
|
2792
3119
|
...dragItem.internals.positionAbsolute,
|
|
2793
|
-
width: dragItem.measured.width,
|
|
2794
|
-
height: dragItem.measured.height,
|
|
3120
|
+
width: dragItem.measured.width ?? 0,
|
|
3121
|
+
height: dragItem.measured.height ?? 0,
|
|
2795
3122
|
},
|
|
2796
3123
|
});
|
|
2797
3124
|
}
|
|
2798
3125
|
changes.push(change);
|
|
2799
3126
|
}
|
|
2800
3127
|
if (parentExpandChildren.length > 0) {
|
|
2801
|
-
const {
|
|
3128
|
+
const { parentLookup, nodeOrigin } = get();
|
|
2802
3129
|
const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
|
|
2803
3130
|
changes.push(...parentExpandChanges);
|
|
2804
3131
|
}
|
|
2805
|
-
|
|
3132
|
+
triggerNodeChanges(changes);
|
|
2806
3133
|
},
|
|
2807
3134
|
triggerNodeChanges: (changes) => {
|
|
2808
3135
|
const { onNodesChange, setNodes, nodes, hasDefaultNodes, debug } = get();
|
|
@@ -2857,8 +3184,10 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2857
3184
|
const nodeChanges = nodesToUnselect.map((n) => {
|
|
2858
3185
|
const internalNode = nodeLookup.get(n.id);
|
|
2859
3186
|
if (internalNode) {
|
|
2860
|
-
|
|
2861
|
-
|
|
3187
|
+
/*
|
|
3188
|
+
* we need to unselect the internal node that was selected previously before we
|
|
3189
|
+
* send the change to the user to prevent it to be selected while dragging the new node
|
|
3190
|
+
*/
|
|
2862
3191
|
internalNode.selected = false;
|
|
2863
3192
|
}
|
|
2864
3193
|
return createSelectionChange(n.id, false);
|
|
@@ -2926,8 +3255,10 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2926
3255
|
maxZoom,
|
|
2927
3256
|
}, options);
|
|
2928
3257
|
},
|
|
2929
|
-
|
|
2930
|
-
|
|
3258
|
+
/*
|
|
3259
|
+
* we can't call an asnychronous function in updateNodeInternals
|
|
3260
|
+
* for that we created this sync version of fitView
|
|
3261
|
+
*/
|
|
2931
3262
|
fitViewSync: (options) => {
|
|
2932
3263
|
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
|
|
2933
3264
|
if (!panZoom) {
|
|
@@ -2955,6 +3286,40 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2955
3286
|
reset: () => set({ ...getInitialState() }),
|
|
2956
3287
|
}), Object.is);
|
|
2957
3288
|
|
|
3289
|
+
/**
|
|
3290
|
+
* The `<ReactFlowProvider />` component is a [context provider](https://react.dev/learn/passing-data-deeply-with-context#)
|
|
3291
|
+
* that makes it possible to access a flow's internal state outside of the
|
|
3292
|
+
* [`<ReactFlow />`](/api-reference/react-flow) component. Many of the hooks we
|
|
3293
|
+
* provide rely on this component to work.
|
|
3294
|
+
* @public
|
|
3295
|
+
*
|
|
3296
|
+
* @example
|
|
3297
|
+
* ```tsx
|
|
3298
|
+
*import { ReactFlow, ReactFlowProvider, useNodes } from '@xyflow/react'
|
|
3299
|
+
*
|
|
3300
|
+
*export default function Flow() {
|
|
3301
|
+
* return (
|
|
3302
|
+
* <ReactFlowProvider>
|
|
3303
|
+
* <ReactFlow nodes={...} edges={...} />
|
|
3304
|
+
* <Sidebar />
|
|
3305
|
+
* </ReactFlowProvider>
|
|
3306
|
+
* );
|
|
3307
|
+
*}
|
|
3308
|
+
*
|
|
3309
|
+
*function Sidebar() {
|
|
3310
|
+
* // This hook will only work if the component it's used in is a child of a
|
|
3311
|
+
* // <ReactFlowProvider />.
|
|
3312
|
+
* const nodes = useNodes()
|
|
3313
|
+
*
|
|
3314
|
+
* return <aside>do something with nodes</aside>;
|
|
3315
|
+
*}
|
|
3316
|
+
*```
|
|
3317
|
+
*
|
|
3318
|
+
* @remarks If you're using a router and want your flow's state to persist across routes,
|
|
3319
|
+
* it's vital that you place the `<ReactFlowProvider />` component _outside_ of
|
|
3320
|
+
* your router. If you have multiple flows on the same page you will need to use a separate
|
|
3321
|
+
* `<ReactFlowProvider />` for each flow.
|
|
3322
|
+
*/
|
|
2958
3323
|
function ReactFlowProvider({ initialNodes: nodes, initialEdges: edges, defaultNodes, defaultEdges, initialWidth: width, initialHeight: height, fitView, nodeOrigin, nodeExtent, children, }) {
|
|
2959
3324
|
const [store] = useState(() => createStore({
|
|
2960
3325
|
nodes,
|
|
@@ -2973,8 +3338,10 @@ function ReactFlowProvider({ initialNodes: nodes, initialEdges: edges, defaultNo
|
|
|
2973
3338
|
function Wrapper({ children, nodes, edges, defaultNodes, defaultEdges, width, height, fitView, nodeOrigin, nodeExtent, }) {
|
|
2974
3339
|
const isWrapped = useContext(StoreContext);
|
|
2975
3340
|
if (isWrapped) {
|
|
2976
|
-
|
|
2977
|
-
|
|
3341
|
+
/*
|
|
3342
|
+
* we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
|
|
3343
|
+
* https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
|
|
3344
|
+
*/
|
|
2978
3345
|
return jsx(Fragment, { children: children });
|
|
2979
3346
|
}
|
|
2980
3347
|
return (jsx(ReactFlowProvider, { initialNodes: nodes, initialEdges: edges, defaultNodes: defaultNodes, defaultEdges: defaultEdges, initialWidth: width, initialHeight: height, fitView: fitView, nodeOrigin: nodeOrigin, nodeExtent: nodeExtent, children: children }));
|
|
@@ -2987,14 +3354,79 @@ const wrapperStyle = {
|
|
|
2987
3354
|
position: 'relative',
|
|
2988
3355
|
zIndex: 0,
|
|
2989
3356
|
};
|
|
2990
|
-
function ReactFlow({ nodes, edges, defaultNodes, defaultEdges, className, nodeTypes, edgeTypes, onNodeClick, onEdgeClick, onInit, onMove, onMoveStart, onMoveEnd, onConnect, onConnectStart, onConnectEnd, onClickConnectStart, onClickConnectEnd, onNodeMouseEnter, onNodeMouseMove, onNodeMouseLeave, onNodeContextMenu, onNodeDoubleClick, onNodeDragStart, onNodeDrag, onNodeDragStop, onNodesDelete, onEdgesDelete, onDelete, onSelectionChange, onSelectionDragStart, onSelectionDrag, onSelectionDragStop, onSelectionContextMenu, onSelectionStart, onSelectionEnd, onBeforeDelete, connectionMode, connectionLineType = ConnectionLineType.Bezier, connectionLineStyle, connectionLineComponent, connectionLineContainerStyle, deleteKeyCode = 'Backspace', selectionKeyCode = 'Shift', selectionOnDrag = false, selectionMode = SelectionMode.Full, panActivationKeyCode = 'Space', multiSelectionKeyCode = isMacOs() ? 'Meta' : 'Control', zoomActivationKeyCode = isMacOs() ? 'Meta' : 'Control', snapToGrid, snapGrid, onlyRenderVisibleElements = false, selectNodesOnDrag, nodesDraggable, nodesConnectable, nodesFocusable, nodeOrigin = defaultNodeOrigin, edgesFocusable, edgesReconnectable, elementsSelectable = true, defaultViewport: defaultViewport$1 = defaultViewport, minZoom = 0.5, maxZoom = 2, translateExtent = infiniteExtent, preventScrolling = true, nodeExtent, defaultMarkerColor = '#b1b1b7', zoomOnScroll = true, zoomOnPinch = true, panOnScroll = false, panOnScrollSpeed = 0.5, panOnScrollMode = PanOnScrollMode.Free, zoomOnDoubleClick = true, panOnDrag = true, onPaneClick, onPaneMouseEnter, onPaneMouseMove, onPaneMouseLeave, onPaneScroll, onPaneContextMenu, paneClickDistance = 0, nodeClickDistance = 0, children, onReconnect, onReconnectStart, onReconnectEnd, onEdgeContextMenu, onEdgeDoubleClick, onEdgeMouseEnter, onEdgeMouseMove, onEdgeMouseLeave, reconnectRadius = 10, onNodesChange, onEdgesChange, noDragClassName = 'nodrag', noWheelClassName = 'nowheel', noPanClassName = 'nopan', fitView, fitViewOptions, connectOnClick, attributionPosition, proOptions, defaultEdgeOptions, elevateNodesOnSelect, elevateEdgesOnSelect, disableKeyboardA11y = false, autoPanOnConnect, autoPanOnNodeDrag, autoPanSpeed, connectionRadius, isValidConnection, onError, style, id, nodeDragThreshold, viewport, onViewportChange, width, height, colorMode = 'light', debug, ...rest }, ref) {
|
|
3357
|
+
function ReactFlow({ nodes, edges, defaultNodes, defaultEdges, className, nodeTypes, edgeTypes, onNodeClick, onEdgeClick, onInit, onMove, onMoveStart, onMoveEnd, onConnect, onConnectStart, onConnectEnd, onClickConnectStart, onClickConnectEnd, onNodeMouseEnter, onNodeMouseMove, onNodeMouseLeave, onNodeContextMenu, onNodeDoubleClick, onNodeDragStart, onNodeDrag, onNodeDragStop, onNodesDelete, onEdgesDelete, onDelete, onSelectionChange, onSelectionDragStart, onSelectionDrag, onSelectionDragStop, onSelectionContextMenu, onSelectionStart, onSelectionEnd, onBeforeDelete, connectionMode, connectionLineType = ConnectionLineType.Bezier, connectionLineStyle, connectionLineComponent, connectionLineContainerStyle, deleteKeyCode = 'Backspace', selectionKeyCode = 'Shift', selectionOnDrag = false, selectionMode = SelectionMode.Full, panActivationKeyCode = 'Space', multiSelectionKeyCode = isMacOs() ? 'Meta' : 'Control', zoomActivationKeyCode = isMacOs() ? 'Meta' : 'Control', snapToGrid, snapGrid, onlyRenderVisibleElements = false, selectNodesOnDrag, nodesDraggable, nodesConnectable, nodesFocusable, nodeOrigin = defaultNodeOrigin, edgesFocusable, edgesReconnectable, elementsSelectable = true, defaultViewport: defaultViewport$1 = defaultViewport, minZoom = 0.5, maxZoom = 2, translateExtent = infiniteExtent, preventScrolling = true, nodeExtent, defaultMarkerColor = '#b1b1b7', zoomOnScroll = true, zoomOnPinch = true, panOnScroll = false, panOnScrollSpeed = 0.5, panOnScrollMode = PanOnScrollMode.Free, zoomOnDoubleClick = true, panOnDrag = true, onPaneClick, onPaneMouseEnter, onPaneMouseMove, onPaneMouseLeave, onPaneScroll, onPaneContextMenu, paneClickDistance = 0, nodeClickDistance = 0, children, onReconnect, onReconnectStart, onReconnectEnd, onEdgeContextMenu, onEdgeDoubleClick, onEdgeMouseEnter, onEdgeMouseMove, onEdgeMouseLeave, reconnectRadius = 10, onNodesChange, onEdgesChange, noDragClassName = 'nodrag', noWheelClassName = 'nowheel', noPanClassName = 'nopan', fitView, fitViewOptions, connectOnClick, attributionPosition, proOptions, defaultEdgeOptions, elevateNodesOnSelect, elevateEdgesOnSelect, disableKeyboardA11y = false, autoPanOnConnect, autoPanOnNodeDrag, autoPanSpeed, connectionRadius, isValidConnection, onError, style, id, nodeDragThreshold, viewport, onViewportChange, width, height, colorMode = 'light', debug, onScroll, ...rest }, ref) {
|
|
2991
3358
|
const rfId = id || '1';
|
|
2992
3359
|
const colorModeClassName = useColorModeClass(colorMode);
|
|
2993
|
-
|
|
3360
|
+
// Undo scroll events, preventing viewport from shifting when nodes outside of it are focused
|
|
3361
|
+
const wrapperOnScroll = useCallback((e) => {
|
|
3362
|
+
e.currentTarget.scrollTo({ top: 0, left: 0, behavior: 'instant' });
|
|
3363
|
+
onScroll?.(e);
|
|
3364
|
+
}, [onScroll]);
|
|
3365
|
+
return (jsx("div", { "data-testid": "rf__wrapper", ...rest, onScroll: wrapperOnScroll, style: { ...style, ...wrapperStyle }, ref: ref, className: cc(['react-flow', className, colorModeClassName]), id: id, children: jsxs(Wrapper, { nodes: nodes, edges: edges, width: width, height: height, fitView: fitView, nodeOrigin: nodeOrigin, nodeExtent: nodeExtent, children: [jsx(GraphView, { onInit: onInit, onNodeClick: onNodeClick, onEdgeClick: onEdgeClick, onNodeMouseEnter: onNodeMouseEnter, onNodeMouseMove: onNodeMouseMove, onNodeMouseLeave: onNodeMouseLeave, onNodeContextMenu: onNodeContextMenu, onNodeDoubleClick: onNodeDoubleClick, nodeTypes: nodeTypes, edgeTypes: edgeTypes, connectionLineType: connectionLineType, connectionLineStyle: connectionLineStyle, connectionLineComponent: connectionLineComponent, connectionLineContainerStyle: connectionLineContainerStyle, selectionKeyCode: selectionKeyCode, selectionOnDrag: selectionOnDrag, selectionMode: selectionMode, deleteKeyCode: deleteKeyCode, multiSelectionKeyCode: multiSelectionKeyCode, panActivationKeyCode: panActivationKeyCode, zoomActivationKeyCode: zoomActivationKeyCode, onlyRenderVisibleElements: onlyRenderVisibleElements, defaultViewport: defaultViewport$1, translateExtent: translateExtent, minZoom: minZoom, maxZoom: maxZoom, preventScrolling: preventScrolling, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, zoomOnDoubleClick: zoomOnDoubleClick, panOnScroll: panOnScroll, panOnScrollSpeed: panOnScrollSpeed, panOnScrollMode: panOnScrollMode, panOnDrag: panOnDrag, onPaneClick: onPaneClick, onPaneMouseEnter: onPaneMouseEnter, onPaneMouseMove: onPaneMouseMove, onPaneMouseLeave: onPaneMouseLeave, onPaneScroll: onPaneScroll, onPaneContextMenu: onPaneContextMenu, paneClickDistance: paneClickDistance, nodeClickDistance: nodeClickDistance, onSelectionContextMenu: onSelectionContextMenu, onSelectionStart: onSelectionStart, onSelectionEnd: onSelectionEnd, onReconnect: onReconnect, onReconnectStart: onReconnectStart, onReconnectEnd: onReconnectEnd, onEdgeContextMenu: onEdgeContextMenu, onEdgeDoubleClick: onEdgeDoubleClick, onEdgeMouseEnter: onEdgeMouseEnter, onEdgeMouseMove: onEdgeMouseMove, onEdgeMouseLeave: onEdgeMouseLeave, reconnectRadius: reconnectRadius, defaultMarkerColor: defaultMarkerColor, noDragClassName: noDragClassName, noWheelClassName: noWheelClassName, noPanClassName: noPanClassName, rfId: rfId, disableKeyboardA11y: disableKeyboardA11y, nodeExtent: nodeExtent, viewport: viewport, onViewportChange: onViewportChange }), jsx(StoreUpdater, { nodes: nodes, edges: edges, defaultNodes: defaultNodes, defaultEdges: defaultEdges, onConnect: onConnect, onConnectStart: onConnectStart, onConnectEnd: onConnectEnd, onClickConnectStart: onClickConnectStart, onClickConnectEnd: onClickConnectEnd, nodesDraggable: nodesDraggable, nodesConnectable: nodesConnectable, nodesFocusable: nodesFocusable, edgesFocusable: edgesFocusable, edgesReconnectable: edgesReconnectable, elementsSelectable: elementsSelectable, elevateNodesOnSelect: elevateNodesOnSelect, elevateEdgesOnSelect: elevateEdgesOnSelect, minZoom: minZoom, maxZoom: maxZoom, nodeExtent: nodeExtent, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, snapToGrid: snapToGrid, snapGrid: snapGrid, connectionMode: connectionMode, translateExtent: translateExtent, connectOnClick: connectOnClick, defaultEdgeOptions: defaultEdgeOptions, fitView: fitView, fitViewOptions: fitViewOptions, onNodesDelete: onNodesDelete, onEdgesDelete: onEdgesDelete, onDelete: onDelete, onNodeDragStart: onNodeDragStart, onNodeDrag: onNodeDrag, onNodeDragStop: onNodeDragStop, onSelectionDrag: onSelectionDrag, onSelectionDragStart: onSelectionDragStart, onSelectionDragStop: onSelectionDragStop, onMove: onMove, onMoveStart: onMoveStart, onMoveEnd: onMoveEnd, noPanClassName: noPanClassName, nodeOrigin: nodeOrigin, rfId: rfId, autoPanOnConnect: autoPanOnConnect, autoPanOnNodeDrag: autoPanOnNodeDrag, autoPanSpeed: autoPanSpeed, onError: onError, connectionRadius: connectionRadius, isValidConnection: isValidConnection, selectNodesOnDrag: selectNodesOnDrag, nodeDragThreshold: nodeDragThreshold, onBeforeDelete: onBeforeDelete, paneClickDistance: paneClickDistance, debug: debug }), jsx(SelectionListener, { onSelectionChange: onSelectionChange }), children, jsx(Attribution, { proOptions: proOptions, position: attributionPosition }), jsx(A11yDescriptions, { rfId: rfId, disableKeyboardA11y: disableKeyboardA11y })] }) }));
|
|
2994
3366
|
}
|
|
3367
|
+
/**
|
|
3368
|
+
* The `<ReactFlow />` component is the heart of your React Flow application.
|
|
3369
|
+
* It renders your nodes and edges and handles user interaction
|
|
3370
|
+
*
|
|
3371
|
+
* @public
|
|
3372
|
+
*
|
|
3373
|
+
* @example
|
|
3374
|
+
* ```tsx
|
|
3375
|
+
*import { ReactFlow } from '@xyflow/react'
|
|
3376
|
+
*
|
|
3377
|
+
*export default function Flow() {
|
|
3378
|
+
* return (<ReactFlow
|
|
3379
|
+
* nodes={...}
|
|
3380
|
+
* edges={...}
|
|
3381
|
+
* onNodesChange={...}
|
|
3382
|
+
* ...
|
|
3383
|
+
* />);
|
|
3384
|
+
*}
|
|
3385
|
+
*```
|
|
3386
|
+
*/
|
|
2995
3387
|
var index = fixedForwardRef(ReactFlow);
|
|
2996
3388
|
|
|
2997
3389
|
const selector$6 = (s) => s.domNode?.querySelector('.react-flow__edgelabel-renderer');
|
|
3390
|
+
/**
|
|
3391
|
+
* Edges are SVG-based. If you want to render more complex labels you can use the
|
|
3392
|
+
* `<EdgeLabelRenderer />` component to access a div based renderer. This component
|
|
3393
|
+
* is a portal that renders the label in a `<div />` that is positioned on top of
|
|
3394
|
+
* the edges. You can see an example usage of the component in the [edge label renderer](/examples/edges/edge-label-renderer) example.
|
|
3395
|
+
* @public
|
|
3396
|
+
*
|
|
3397
|
+
* @example
|
|
3398
|
+
*```jsx
|
|
3399
|
+
*import React from 'react';
|
|
3400
|
+
*import { getBezierPath, EdgeLabelRenderer, BaseEdge } from '@xyflow/react';
|
|
3401
|
+
*
|
|
3402
|
+
*export function CustomEdge({ id, data, ...props }) {
|
|
3403
|
+
* const [edgePath, labelX, labelY] = getBezierPath(props);
|
|
3404
|
+
*
|
|
3405
|
+
* return (
|
|
3406
|
+
* <>
|
|
3407
|
+
* <BaseEdge id={id} path={edgePath} />
|
|
3408
|
+
* <EdgeLabelRenderer>
|
|
3409
|
+
* <div
|
|
3410
|
+
* style={{
|
|
3411
|
+
* position: 'absolute',
|
|
3412
|
+
* transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
3413
|
+
* background: '#ffcc00',
|
|
3414
|
+
* padding: 10,
|
|
3415
|
+
* }}
|
|
3416
|
+
* className="nodrag nopan"
|
|
3417
|
+
* >
|
|
3418
|
+
* {data.label}
|
|
3419
|
+
* </div>
|
|
3420
|
+
* </EdgeLabelRenderer>
|
|
3421
|
+
* </>
|
|
3422
|
+
* );
|
|
3423
|
+
*};
|
|
3424
|
+
*```
|
|
3425
|
+
*
|
|
3426
|
+
* @remarks The `<EdgeLabelRenderer />` has no pointer events by default. If you want to
|
|
3427
|
+
* add mouse interactions you need to set the style `pointerEvents: all` and add
|
|
3428
|
+
* the `nopan` class on the label or the element you want to interact with.
|
|
3429
|
+
*/
|
|
2998
3430
|
function EdgeLabelRenderer({ children }) {
|
|
2999
3431
|
const edgeLabelRenderer = useStore(selector$6);
|
|
3000
3432
|
if (!edgeLabelRenderer) {
|
|
@@ -3004,6 +3436,31 @@ function EdgeLabelRenderer({ children }) {
|
|
|
3004
3436
|
}
|
|
3005
3437
|
|
|
3006
3438
|
const selector$5 = (s) => s.domNode?.querySelector('.react-flow__viewport-portal');
|
|
3439
|
+
/**
|
|
3440
|
+
* The `<ViewportPortal />` component can be used to add components to the same viewport
|
|
3441
|
+
* of the flow where nodes and edges are rendered. This is useful when you want to render
|
|
3442
|
+
* your own components that are adhere to the same coordinate system as the nodes & edges
|
|
3443
|
+
* and are also affected by zooming and panning
|
|
3444
|
+
* @public
|
|
3445
|
+
* @example
|
|
3446
|
+
*
|
|
3447
|
+
* ```jsx
|
|
3448
|
+
*import React from 'react';
|
|
3449
|
+
*import { ViewportPortal } from '@xyflow/react';
|
|
3450
|
+
*
|
|
3451
|
+
*export default function () {
|
|
3452
|
+
* return (
|
|
3453
|
+
* <ViewportPortal>
|
|
3454
|
+
* <div
|
|
3455
|
+
* style={{ transform: 'translate(100px, 100px)', position: 'absolute' }}
|
|
3456
|
+
* >
|
|
3457
|
+
* This div is positioned at [100, 100] on the flow.
|
|
3458
|
+
* </div>
|
|
3459
|
+
* </ViewportPortal>
|
|
3460
|
+
* );
|
|
3461
|
+
*}
|
|
3462
|
+
*```
|
|
3463
|
+
*/
|
|
3007
3464
|
function ViewportPortal({ children }) {
|
|
3008
3465
|
const viewPortalDiv = useStore(selector$5);
|
|
3009
3466
|
if (!viewPortalDiv) {
|
|
@@ -3013,10 +3470,48 @@ function ViewportPortal({ children }) {
|
|
|
3013
3470
|
}
|
|
3014
3471
|
|
|
3015
3472
|
/**
|
|
3016
|
-
*
|
|
3473
|
+
* When you programmatically add or remove handles to a node or update a node's
|
|
3474
|
+
*handle position, you need to let React Flow know about it using this hook. This
|
|
3475
|
+
*will update the internal dimensions of the node and properly reposition handles
|
|
3476
|
+
*on the canvas if necessary.
|
|
3017
3477
|
*
|
|
3018
3478
|
* @public
|
|
3019
3479
|
* @returns function for updating node internals
|
|
3480
|
+
*
|
|
3481
|
+
* @example
|
|
3482
|
+
* ```jsx
|
|
3483
|
+
*import { useCallback, useState } from 'react';
|
|
3484
|
+
*import { Handle, useUpdateNodeInternals } from '@xyflow/react';
|
|
3485
|
+
*
|
|
3486
|
+
*export default function RandomHandleNode({ id }) {
|
|
3487
|
+
* const updateNodeInternals = useUpdateNodeInternals();
|
|
3488
|
+
* const [handleCount, setHandleCount] = useState(0);
|
|
3489
|
+
* const randomizeHandleCount = useCallback(() => {
|
|
3490
|
+
* setHandleCount(Math.floor(Math.random() * 10));
|
|
3491
|
+
* updateNodeInternals(id);
|
|
3492
|
+
* }, [id, updateNodeInternals]);
|
|
3493
|
+
*
|
|
3494
|
+
* return (
|
|
3495
|
+
* <>
|
|
3496
|
+
* {Array.from({ length: handleCount }).map((_, index) => (
|
|
3497
|
+
* <Handle
|
|
3498
|
+
* key={index}
|
|
3499
|
+
* type="target"
|
|
3500
|
+
* position="left"
|
|
3501
|
+
* id={`handle-${index}`}
|
|
3502
|
+
* />
|
|
3503
|
+
* ))}
|
|
3504
|
+
*
|
|
3505
|
+
* <div>
|
|
3506
|
+
* <button onClick={randomizeHandleCount}>Randomize handle count</button>
|
|
3507
|
+
* <p>There are {handleCount} handles on this node.</p>
|
|
3508
|
+
* </div>
|
|
3509
|
+
* </>
|
|
3510
|
+
* );
|
|
3511
|
+
*}
|
|
3512
|
+
*```
|
|
3513
|
+
* @remarks This hook can only be used in a component that is a child of a
|
|
3514
|
+
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
|
3020
3515
|
*/
|
|
3021
3516
|
function useUpdateNodeInternals() {
|
|
3022
3517
|
const store = useStoreApi();
|
|
@@ -3036,10 +3531,23 @@ function useUpdateNodeInternals() {
|
|
|
3036
3531
|
|
|
3037
3532
|
const nodesSelector = (state) => state.nodes;
|
|
3038
3533
|
/**
|
|
3039
|
-
*
|
|
3534
|
+
* This hook returns an array of the current nodes. Components that use this hook
|
|
3535
|
+
* will re-render **whenever any node changes**, including when a node is selected
|
|
3536
|
+
* or moved.
|
|
3040
3537
|
*
|
|
3041
3538
|
* @public
|
|
3042
3539
|
* @returns An array of nodes
|
|
3540
|
+
*
|
|
3541
|
+
* @example
|
|
3542
|
+
* ```jsx
|
|
3543
|
+
*import { useNodes } from '@xyflow/react';
|
|
3544
|
+
*
|
|
3545
|
+
*export default function() {
|
|
3546
|
+
* const nodes = useNodes();
|
|
3547
|
+
*
|
|
3548
|
+
* return <div>There are currently {nodes.length} nodes!</div>;
|
|
3549
|
+
*}
|
|
3550
|
+
*```
|
|
3043
3551
|
*/
|
|
3044
3552
|
function useNodes() {
|
|
3045
3553
|
const nodes = useStore(nodesSelector, shallow);
|
|
@@ -3048,10 +3556,22 @@ function useNodes() {
|
|
|
3048
3556
|
|
|
3049
3557
|
const edgesSelector = (state) => state.edges;
|
|
3050
3558
|
/**
|
|
3051
|
-
*
|
|
3559
|
+
* This hook returns an array of the current edges. Components that use this hook
|
|
3560
|
+
* will re-render **whenever any edge changes**.
|
|
3052
3561
|
*
|
|
3053
3562
|
* @public
|
|
3054
3563
|
* @returns An array of edges
|
|
3564
|
+
*
|
|
3565
|
+
* @example
|
|
3566
|
+
* ```tsx
|
|
3567
|
+
*import { useEdges } from '@xyflow/react';
|
|
3568
|
+
*
|
|
3569
|
+
*export default function () {
|
|
3570
|
+
* const edges = useEdges();
|
|
3571
|
+
*
|
|
3572
|
+
* return <div>There are currently {edges.length} edges!</div>;
|
|
3573
|
+
*}
|
|
3574
|
+
*```
|
|
3055
3575
|
*/
|
|
3056
3576
|
function useEdges() {
|
|
3057
3577
|
const edges = useStore(edgesSelector, shallow);
|
|
@@ -3064,10 +3584,33 @@ const viewportSelector = (state) => ({
|
|
|
3064
3584
|
zoom: state.transform[2],
|
|
3065
3585
|
});
|
|
3066
3586
|
/**
|
|
3067
|
-
*
|
|
3587
|
+
* The `useViewport` hook is a convenient way to read the current state of the
|
|
3588
|
+
*{@link Viewport} in a component. Components that use this hook
|
|
3589
|
+
*will re-render **whenever the viewport changes**.
|
|
3068
3590
|
*
|
|
3069
3591
|
* @public
|
|
3070
3592
|
* @returns The current viewport
|
|
3593
|
+
*
|
|
3594
|
+
* @example
|
|
3595
|
+
*
|
|
3596
|
+
*```jsx
|
|
3597
|
+
*import { useViewport } from '@xyflow/react';
|
|
3598
|
+
*
|
|
3599
|
+
*export default function ViewportDisplay() {
|
|
3600
|
+
* const { x, y, zoom } = useViewport();
|
|
3601
|
+
*
|
|
3602
|
+
* return (
|
|
3603
|
+
* <div>
|
|
3604
|
+
* <p>
|
|
3605
|
+
* The viewport is currently at ({x}, {y}) and zoomed to {zoom}.
|
|
3606
|
+
* </p>
|
|
3607
|
+
* </div>
|
|
3608
|
+
* );
|
|
3609
|
+
*}
|
|
3610
|
+
*```
|
|
3611
|
+
*
|
|
3612
|
+
* @remarks This hook can only be used in a component that is a child of a
|
|
3613
|
+
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
|
3071
3614
|
*/
|
|
3072
3615
|
function useViewport() {
|
|
3073
3616
|
const viewport = useStore(viewportSelector, shallow);
|
|
@@ -3075,11 +3618,41 @@ function useViewport() {
|
|
|
3075
3618
|
}
|
|
3076
3619
|
|
|
3077
3620
|
/**
|
|
3078
|
-
*
|
|
3621
|
+
* This hook makes it easy to prototype a controlled flow where you manage the
|
|
3622
|
+
* state of nodes and edges outside the `ReactFlowInstance`. You can think of it
|
|
3623
|
+
* like React's `useState` hook with an additional helper callback.
|
|
3079
3624
|
*
|
|
3080
3625
|
* @public
|
|
3081
3626
|
* @param initialNodes
|
|
3082
3627
|
* @returns an array [nodes, setNodes, onNodesChange]
|
|
3628
|
+
* @example
|
|
3629
|
+
*
|
|
3630
|
+
*```tsx
|
|
3631
|
+
*import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
|
|
3632
|
+
*
|
|
3633
|
+
*const initialNodes = [];
|
|
3634
|
+
*const initialEdges = [];
|
|
3635
|
+
*
|
|
3636
|
+
*export default function () {
|
|
3637
|
+
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
|
3638
|
+
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
3639
|
+
*
|
|
3640
|
+
* return (
|
|
3641
|
+
* <ReactFlow
|
|
3642
|
+
* nodes={nodes}
|
|
3643
|
+
* edges={edges}
|
|
3644
|
+
* onNodesChange={onNodesChange}
|
|
3645
|
+
* onEdgesChange={onEdgesChange}
|
|
3646
|
+
* />
|
|
3647
|
+
* );
|
|
3648
|
+
*}
|
|
3649
|
+
*```
|
|
3650
|
+
*
|
|
3651
|
+
* @remarks This hook was created to make prototyping easier and our documentation
|
|
3652
|
+
* examples clearer. Although it is OK to use this hook in production, in
|
|
3653
|
+
* practice you may want to use a more sophisticated state management solution
|
|
3654
|
+
* like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead.
|
|
3655
|
+
*
|
|
3083
3656
|
*/
|
|
3084
3657
|
function useNodesState(initialNodes) {
|
|
3085
3658
|
const [nodes, setNodes] = useState(initialNodes);
|
|
@@ -3087,11 +3660,41 @@ function useNodesState(initialNodes) {
|
|
|
3087
3660
|
return [nodes, setNodes, onNodesChange];
|
|
3088
3661
|
}
|
|
3089
3662
|
/**
|
|
3090
|
-
*
|
|
3663
|
+
* This hook makes it easy to prototype a controlled flow where you manage the
|
|
3664
|
+
* state of nodes and edges outside the `ReactFlowInstance`. You can think of it
|
|
3665
|
+
* like React's `useState` hook with an additional helper callback.
|
|
3091
3666
|
*
|
|
3092
3667
|
* @public
|
|
3093
3668
|
* @param initialEdges
|
|
3094
3669
|
* @returns an array [edges, setEdges, onEdgesChange]
|
|
3670
|
+
* @example
|
|
3671
|
+
*
|
|
3672
|
+
*```tsx
|
|
3673
|
+
*import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
|
|
3674
|
+
*
|
|
3675
|
+
*const initialNodes = [];
|
|
3676
|
+
*const initialEdges = [];
|
|
3677
|
+
*
|
|
3678
|
+
*export default function () {
|
|
3679
|
+
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
|
3680
|
+
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
3681
|
+
*
|
|
3682
|
+
* return (
|
|
3683
|
+
* <ReactFlow
|
|
3684
|
+
* nodes={nodes}
|
|
3685
|
+
* edges={edges}
|
|
3686
|
+
* onNodesChange={onNodesChange}
|
|
3687
|
+
* onEdgesChange={onEdgesChange}
|
|
3688
|
+
* />
|
|
3689
|
+
* );
|
|
3690
|
+
*}
|
|
3691
|
+
*```
|
|
3692
|
+
*
|
|
3693
|
+
* @remarks This hook was created to make prototyping easier and our documentation
|
|
3694
|
+
* examples clearer. Although it is OK to use this hook in production, in
|
|
3695
|
+
* practice you may want to use a more sophisticated state management solution
|
|
3696
|
+
* like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead.
|
|
3697
|
+
*
|
|
3095
3698
|
*/
|
|
3096
3699
|
function useEdgesState(initialEdges) {
|
|
3097
3700
|
const [edges, setEdges] = useState(initialEdges);
|
|
@@ -3100,12 +3703,30 @@ function useEdgesState(initialEdges) {
|
|
|
3100
3703
|
}
|
|
3101
3704
|
|
|
3102
3705
|
/**
|
|
3103
|
-
*
|
|
3706
|
+
* The `useOnViewportChange` hook lets you listen for changes to the viewport such
|
|
3707
|
+
*as panning and zooming. You can provide a callback for each phase of a viewport
|
|
3708
|
+
*change: `onStart`, `onChange`, and `onEnd`.
|
|
3104
3709
|
*
|
|
3105
3710
|
* @public
|
|
3106
3711
|
* @param params.onStart - gets called when the viewport starts changing
|
|
3107
3712
|
* @param params.onChange - gets called when the viewport changes
|
|
3108
3713
|
* @param params.onEnd - gets called when the viewport stops changing
|
|
3714
|
+
*
|
|
3715
|
+
* @example
|
|
3716
|
+
* ```jsx
|
|
3717
|
+
*import { useCallback } from 'react';
|
|
3718
|
+
*import { useOnViewportChange } from '@xyflow/react';
|
|
3719
|
+
*
|
|
3720
|
+
*function ViewportChangeLogger() {
|
|
3721
|
+
* useOnViewportChange({
|
|
3722
|
+
* onStart: (viewport: Viewport) => console.log('start', viewport),
|
|
3723
|
+
* onChange: (viewport: Viewport) => console.log('change', viewport),
|
|
3724
|
+
* onEnd: (viewport: Viewport) => console.log('end', viewport),
|
|
3725
|
+
* });
|
|
3726
|
+
*
|
|
3727
|
+
* return null;
|
|
3728
|
+
*}
|
|
3729
|
+
*```
|
|
3109
3730
|
*/
|
|
3110
3731
|
function useOnViewportChange({ onStart, onChange, onEnd }) {
|
|
3111
3732
|
const store = useStoreApi();
|
|
@@ -3121,12 +3742,44 @@ function useOnViewportChange({ onStart, onChange, onEnd }) {
|
|
|
3121
3742
|
}
|
|
3122
3743
|
|
|
3123
3744
|
/**
|
|
3124
|
-
*
|
|
3745
|
+
* This hook lets you listen for changes to both node and edge selection. As the
|
|
3746
|
+
*name implies, the callback you provide will be called whenever the selection of
|
|
3747
|
+
*_either_ nodes or edges changes.
|
|
3125
3748
|
*
|
|
3126
3749
|
* @public
|
|
3127
3750
|
* @param params.onChange - The handler to register
|
|
3751
|
+
*
|
|
3752
|
+
* @example
|
|
3753
|
+
* ```jsx
|
|
3754
|
+
*import { useState } from 'react';
|
|
3755
|
+
*import { ReactFlow, useOnSelectionChange } from '@xyflow/react';
|
|
3756
|
+
*
|
|
3757
|
+
*function SelectionDisplay() {
|
|
3758
|
+
* const [selectedNodes, setSelectedNodes] = useState([]);
|
|
3759
|
+
* const [selectedEdges, setSelectedEdges] = useState([]);
|
|
3760
|
+
*
|
|
3761
|
+
* // the passed handler has to be memoized, otherwise the hook will not work correctly
|
|
3762
|
+
* const onChange = useCallback(({ nodes, edges }) => {
|
|
3763
|
+
* setSelectedNodes(nodes.map((node) => node.id));
|
|
3764
|
+
* setSelectedEdges(edges.map((edge) => edge.id));
|
|
3765
|
+
* }, []);
|
|
3766
|
+
*
|
|
3767
|
+
* useOnSelectionChange({
|
|
3768
|
+
* onChange,
|
|
3769
|
+
* });
|
|
3770
|
+
*
|
|
3771
|
+
* return (
|
|
3772
|
+
* <div>
|
|
3773
|
+
* <p>Selected nodes: {selectedNodes.join(', ')}</p>
|
|
3774
|
+
* <p>Selected edges: {selectedEdges.join(', ')}</p>
|
|
3775
|
+
* </div>
|
|
3776
|
+
* );
|
|
3777
|
+
*}
|
|
3778
|
+
*```
|
|
3779
|
+
*
|
|
3780
|
+
* @remarks You need to memoize the passed `onChange` handler, otherwise the hook will not work correctly.
|
|
3128
3781
|
*/
|
|
3129
|
-
function useOnSelectionChange({ onChange }) {
|
|
3782
|
+
function useOnSelectionChange({ onChange, }) {
|
|
3130
3783
|
const store = useStoreApi();
|
|
3131
3784
|
useEffect(() => {
|
|
3132
3785
|
const nextOnSelectionChangeHandlers = [...store.getState().onSelectionChangeHandlers, onChange];
|
|
@@ -3151,17 +3804,42 @@ const selector$4 = (options) => (s) => {
|
|
|
3151
3804
|
}
|
|
3152
3805
|
return true;
|
|
3153
3806
|
};
|
|
3154
|
-
const defaultOptions = {
|
|
3155
|
-
includeHiddenNodes: false,
|
|
3156
|
-
};
|
|
3157
3807
|
/**
|
|
3158
|
-
*
|
|
3808
|
+
* This hook tells you whether all the nodes in a flow have been measured and given
|
|
3809
|
+
*a width and height. When you add a node to the flow, this hook will return
|
|
3810
|
+
*`false` and then `true` again once the node has been measured.
|
|
3159
3811
|
*
|
|
3160
3812
|
* @public
|
|
3161
3813
|
* @param options.includeHiddenNodes - defaults to false
|
|
3162
3814
|
* @returns boolean indicating whether all nodes are initialized
|
|
3815
|
+
*
|
|
3816
|
+
* @example
|
|
3817
|
+
* ```jsx
|
|
3818
|
+
*import { useReactFlow, useNodesInitialized } from '@xyflow/react';
|
|
3819
|
+
*import { useEffect, useState } from 'react';
|
|
3820
|
+
*
|
|
3821
|
+
*const options = {
|
|
3822
|
+
* includeHiddenNodes: false,
|
|
3823
|
+
*};
|
|
3824
|
+
*
|
|
3825
|
+
*export default function useLayout() {
|
|
3826
|
+
* const { getNodes } = useReactFlow();
|
|
3827
|
+
* const nodesInitialized = useNodesInitialized(options);
|
|
3828
|
+
* const [layoutedNodes, setLayoutedNodes] = useState(getNodes());
|
|
3829
|
+
*
|
|
3830
|
+
* useEffect(() => {
|
|
3831
|
+
* if (nodesInitialized) {
|
|
3832
|
+
* setLayoutedNodes(yourLayoutingFunction(getNodes()));
|
|
3833
|
+
* }
|
|
3834
|
+
* }, [nodesInitialized]);
|
|
3835
|
+
*
|
|
3836
|
+
* return layoutedNodes;
|
|
3837
|
+
*}
|
|
3838
|
+
*```
|
|
3163
3839
|
*/
|
|
3164
|
-
function useNodesInitialized(options =
|
|
3840
|
+
function useNodesInitialized(options = {
|
|
3841
|
+
includeHiddenNodes: false,
|
|
3842
|
+
}) {
|
|
3165
3843
|
const initialized = useStore(selector$4(options));
|
|
3166
3844
|
return initialized;
|
|
3167
3845
|
}
|
|
@@ -3198,7 +3876,7 @@ function useHandleConnections({ type, id, nodeId, onConnect, onDisconnect, }) {
|
|
|
3198
3876
|
|
|
3199
3877
|
const error014 = errorMessages['error014']();
|
|
3200
3878
|
/**
|
|
3201
|
-
*
|
|
3879
|
+
* This hook returns an array of connections on a specific node, handle type ('source', 'target') or handle ID.
|
|
3202
3880
|
*
|
|
3203
3881
|
* @public
|
|
3204
3882
|
* @param param.id - node id - optional if called inside a custom node
|
|
@@ -3207,6 +3885,22 @@ const error014 = errorMessages['error014']();
|
|
|
3207
3885
|
* @param param.onConnect - gets called when a connection is established
|
|
3208
3886
|
* @param param.onDisconnect - gets called when a connection is removed
|
|
3209
3887
|
* @returns an array with connections
|
|
3888
|
+
*
|
|
3889
|
+
* @example
|
|
3890
|
+
* ```jsx
|
|
3891
|
+
*import { useNodeConnections } from '@xyflow/react';
|
|
3892
|
+
*
|
|
3893
|
+
*export default function () {
|
|
3894
|
+
* const connections = useNodeConnections({
|
|
3895
|
+
* handleType: 'target',
|
|
3896
|
+
* handleId: 'my-handle',
|
|
3897
|
+
* });
|
|
3898
|
+
*
|
|
3899
|
+
* return (
|
|
3900
|
+
* <div>There are currently {connections.length} incoming connections!</div>
|
|
3901
|
+
* );
|
|
3902
|
+
*}
|
|
3903
|
+
*```
|
|
3210
3904
|
*/
|
|
3211
3905
|
function useNodeConnections({ id, handleType, handleId, onConnect, onDisconnect, } = {}) {
|
|
3212
3906
|
const nodeId = useNodeId();
|
|
@@ -3250,11 +3944,31 @@ function useNodesData(nodeIds) {
|
|
|
3250
3944
|
}
|
|
3251
3945
|
|
|
3252
3946
|
/**
|
|
3253
|
-
*
|
|
3947
|
+
* This hook returns the internal representation of a specific node.
|
|
3948
|
+
* Components that use this hook will re-render **whenever the node changes**,
|
|
3949
|
+
* including when a node is selected or moved.
|
|
3254
3950
|
*
|
|
3255
3951
|
* @public
|
|
3256
3952
|
* @param id - id of the node
|
|
3257
3953
|
* @returns array with visible node ids
|
|
3954
|
+
*
|
|
3955
|
+
* @example
|
|
3956
|
+
* ```tsx
|
|
3957
|
+
*import { useInternalNode } from '@xyflow/react';
|
|
3958
|
+
*
|
|
3959
|
+
*export default function () {
|
|
3960
|
+
* const internalNode = useInternalNode('node-1');
|
|
3961
|
+
* const absolutePosition = internalNode.internals.positionAbsolute;
|
|
3962
|
+
*
|
|
3963
|
+
* return (
|
|
3964
|
+
* <div>
|
|
3965
|
+
* The absolute position of the node is at:
|
|
3966
|
+
* <p>x: {absolutePosition.x}</p>
|
|
3967
|
+
* <p>y: {absolutePosition.y}</p>
|
|
3968
|
+
* </div>
|
|
3969
|
+
* );
|
|
3970
|
+
*}
|
|
3971
|
+
*```
|
|
3258
3972
|
*/
|
|
3259
3973
|
function useInternalNode(id) {
|
|
3260
3974
|
const node = useStore(useCallback((s) => s.nodeLookup.get(id), [id]), shallow);
|
|
@@ -3268,6 +3982,12 @@ function DotPattern({ radius, className }) {
|
|
|
3268
3982
|
return (jsx("circle", { cx: radius, cy: radius, r: radius, className: cc(['react-flow__background-pattern', 'dots', className]) }));
|
|
3269
3983
|
}
|
|
3270
3984
|
|
|
3985
|
+
/**
|
|
3986
|
+
* The three variants are exported as an enum for convenience. You can either import
|
|
3987
|
+
* the enum and use it like `BackgroundVariant.Lines` or you can use the raw string
|
|
3988
|
+
* value directly.
|
|
3989
|
+
* @public
|
|
3990
|
+
*/
|
|
3271
3991
|
var BackgroundVariant;
|
|
3272
3992
|
(function (BackgroundVariant) {
|
|
3273
3993
|
BackgroundVariant["Lines"] = "lines";
|
|
@@ -3309,6 +4029,59 @@ size, lineWidth = 1, offset = 0, color, bgColor, style, className, patternClassN
|
|
|
3309
4029
|
}, ref: ref, "data-testid": "rf__background", children: [jsx("pattern", { id: _patternId, x: transform[0] % scaledGap[0], y: transform[1] % scaledGap[1], width: scaledGap[0], height: scaledGap[1], patternUnits: "userSpaceOnUse", patternTransform: `translate(-${scaledOffset[0]},-${scaledOffset[1]})`, children: isDots ? (jsx(DotPattern, { radius: scaledSize / 2, className: patternClassName })) : (jsx(LinePattern, { dimensions: patternDimensions, lineWidth: lineWidth, variant: variant, className: patternClassName })) }), jsx("rect", { x: "0", y: "0", width: "100%", height: "100%", fill: `url(#${_patternId})` })] }));
|
|
3310
4030
|
}
|
|
3311
4031
|
BackgroundComponent.displayName = 'Background';
|
|
4032
|
+
/**
|
|
4033
|
+
* The `<Background />` component makes it convenient to render different types of backgrounds common in node-based UIs. It comes with three variants: lines, dots and cross.
|
|
4034
|
+
*
|
|
4035
|
+
* @example
|
|
4036
|
+
*
|
|
4037
|
+
* A simple example of how to use the Background component.
|
|
4038
|
+
*
|
|
4039
|
+
* ```tsx
|
|
4040
|
+
* import { useState } from 'react';
|
|
4041
|
+
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
|
|
4042
|
+
*
|
|
4043
|
+
* export default function Flow() {
|
|
4044
|
+
* return (
|
|
4045
|
+
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
|
|
4046
|
+
* <Background color="#ccc" variant={BackgroundVariant.Dots} />
|
|
4047
|
+
* </ReactFlow>
|
|
4048
|
+
* );
|
|
4049
|
+
* }
|
|
4050
|
+
* ```
|
|
4051
|
+
*
|
|
4052
|
+
* @example
|
|
4053
|
+
*
|
|
4054
|
+
* In this example you can see how to combine multiple backgrounds
|
|
4055
|
+
*
|
|
4056
|
+
* ```tsx
|
|
4057
|
+
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
|
|
4058
|
+
* import '@xyflow/react/dist/style.css';
|
|
4059
|
+
*
|
|
4060
|
+
* export default function Flow() {
|
|
4061
|
+
* return (
|
|
4062
|
+
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
|
|
4063
|
+
* <Background
|
|
4064
|
+
* id="1"
|
|
4065
|
+
* gap={10}
|
|
4066
|
+
* color="#f1f1f1"
|
|
4067
|
+
* variant={BackgroundVariant.Lines}
|
|
4068
|
+
* />
|
|
4069
|
+
* <Background
|
|
4070
|
+
* id="2"
|
|
4071
|
+
* gap={100}
|
|
4072
|
+
* color="#ccc"
|
|
4073
|
+
* variant={BackgroundVariant.Lines}
|
|
4074
|
+
* />
|
|
4075
|
+
* </ReactFlow>
|
|
4076
|
+
* );
|
|
4077
|
+
* }
|
|
4078
|
+
* ```
|
|
4079
|
+
*
|
|
4080
|
+
* @remarks
|
|
4081
|
+
*
|
|
4082
|
+
* When combining multiple <Background /> components it’s important to give each of them a unique id prop!
|
|
4083
|
+
*
|
|
4084
|
+
*/
|
|
3312
4085
|
const Background = memo(BackgroundComponent);
|
|
3313
4086
|
|
|
3314
4087
|
function PlusIcon() {
|
|
@@ -3331,6 +4104,29 @@ function UnlockIcon() {
|
|
|
3331
4104
|
return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 25 32", children: jsx("path", { d: "M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z" }) }));
|
|
3332
4105
|
}
|
|
3333
4106
|
|
|
4107
|
+
/**
|
|
4108
|
+
* You can add buttons to the control panel by using the `<ControlButton />` component
|
|
4109
|
+
* and pass it as a child to the [`<Controls />`](/api-reference/components/controls) component.
|
|
4110
|
+
*
|
|
4111
|
+
* @public
|
|
4112
|
+
* @example
|
|
4113
|
+
*```jsx
|
|
4114
|
+
*import { MagicWand } from '@radix-ui/react-icons'
|
|
4115
|
+
*import { ReactFlow, Controls, ControlButton } from '@xyflow/react'
|
|
4116
|
+
*
|
|
4117
|
+
*export default function Flow() {
|
|
4118
|
+
* return (
|
|
4119
|
+
* <ReactFlow nodes={[...]} edges={[...]}>
|
|
4120
|
+
* <Controls>
|
|
4121
|
+
* <ControlButton onClick={() => alert('Something magical just happened. ✨')}>
|
|
4122
|
+
* <MagicWand />
|
|
4123
|
+
* </ControlButton>
|
|
4124
|
+
* </Controls>
|
|
4125
|
+
* </ReactFlow>
|
|
4126
|
+
* )
|
|
4127
|
+
*}
|
|
4128
|
+
*```
|
|
4129
|
+
*/
|
|
3334
4130
|
function ControlButton({ children, className, ...rest }) {
|
|
3335
4131
|
return (jsx("button", { type: "button", className: cc(['react-flow__controls-button', className]), ...rest, children: children }));
|
|
3336
4132
|
}
|
|
@@ -3368,6 +4164,27 @@ function ControlsComponent({ style, showZoom = true, showFitView = true, showInt
|
|
|
3368
4164
|
return (jsxs(Panel, { className: cc(['react-flow__controls', orientationClass, 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] }));
|
|
3369
4165
|
}
|
|
3370
4166
|
ControlsComponent.displayName = 'Controls';
|
|
4167
|
+
/**
|
|
4168
|
+
* The `<Controls />` component renders a small panel that contains convenient
|
|
4169
|
+
* buttons to zoom in, zoom out, fit the view, and lock the viewport.
|
|
4170
|
+
*
|
|
4171
|
+
* @public
|
|
4172
|
+
* @example
|
|
4173
|
+
*```tsx
|
|
4174
|
+
*import { ReactFlow, Controls } from '@xyflow/react'
|
|
4175
|
+
*
|
|
4176
|
+
*export default function Flow() {
|
|
4177
|
+
* return (
|
|
4178
|
+
* <ReactFlow nodes={[...]} edges={[...]}>
|
|
4179
|
+
* <Controls />
|
|
4180
|
+
* </ReactFlow>
|
|
4181
|
+
* )
|
|
4182
|
+
*}
|
|
4183
|
+
*```
|
|
4184
|
+
*
|
|
4185
|
+
* @remarks To extend or customise the controls, you can use the [`<ControlButton />`](/api-reference/components/control-button) component
|
|
4186
|
+
*
|
|
4187
|
+
*/
|
|
3371
4188
|
const Controls = memo(ControlsComponent);
|
|
3372
4189
|
|
|
3373
4190
|
function MiniMapNodeComponent({ id, x, y, width, height, style, color, strokeColor, strokeWidth, className, borderRadius, shapeRendering, selected, onClick, }) {
|
|
@@ -3384,8 +4201,10 @@ const MiniMapNode = memo(MiniMapNodeComponent);
|
|
|
3384
4201
|
const selectorNodeIds = (s) => s.nodes.map((node) => node.id);
|
|
3385
4202
|
const getAttrFunction = (func) => func instanceof Function ? func : () => func;
|
|
3386
4203
|
function MiniMapNodes({ nodeStrokeColor, nodeColor, nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth,
|
|
3387
|
-
|
|
3388
|
-
|
|
4204
|
+
/*
|
|
4205
|
+
* We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
|
4206
|
+
* a component properly.
|
|
4207
|
+
*/
|
|
3389
4208
|
nodeComponent: NodeComponent = MiniMapNode, onClick, }) {
|
|
3390
4209
|
const nodeIds = useStore(selectorNodeIds, shallow);
|
|
3391
4210
|
const nodeColorFunc = getAttrFunction(nodeColor);
|
|
@@ -3393,11 +4212,13 @@ nodeComponent: NodeComponent = MiniMapNode, onClick, }) {
|
|
|
3393
4212
|
const nodeClassNameFunc = getAttrFunction(nodeClassName);
|
|
3394
4213
|
const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
|
|
3395
4214
|
return (jsx(Fragment, { children: nodeIds.map((nodeId) => (
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
4215
|
+
/*
|
|
4216
|
+
* The split of responsibilities between MiniMapNodes and
|
|
4217
|
+
* NodeComponentWrapper may appear weird. However, it’s designed to
|
|
4218
|
+
* minimize the cost of updates when individual nodes change.
|
|
4219
|
+
*
|
|
4220
|
+
* For more details, see a similar commit in `NodeRenderer/index.tsx`.
|
|
4221
|
+
*/
|
|
3401
4222
|
jsx(NodeComponentWrapper, { id: nodeId, nodeColorFunc: nodeColorFunc, nodeStrokeColorFunc: nodeStrokeColorFunc, nodeClassNameFunc: nodeClassNameFunc, nodeBorderRadius: nodeBorderRadius, nodeStrokeWidth: nodeStrokeWidth, NodeComponent: NodeComponent, onClick: onClick, shapeRendering: shapeRendering }, nodeId))) }));
|
|
3402
4223
|
}
|
|
3403
4224
|
function NodeComponentWrapperInner({ id, nodeColorFunc, nodeStrokeColorFunc, nodeClassNameFunc, nodeBorderRadius, nodeStrokeWidth, shapeRendering, NodeComponent, onClick, }) {
|
|
@@ -3442,8 +4263,10 @@ const selector$1 = (s) => {
|
|
|
3442
4263
|
};
|
|
3443
4264
|
const ARIA_LABEL_KEY = 'react-flow__minimap-desc';
|
|
3444
4265
|
function MiniMapComponent({ style, className, nodeStrokeColor, nodeColor, nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth,
|
|
3445
|
-
|
|
3446
|
-
|
|
4266
|
+
/*
|
|
4267
|
+
* We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
|
4268
|
+
* a component properly.
|
|
4269
|
+
*/
|
|
3447
4270
|
nodeComponent, bgColor, maskColor, maskStrokeColor, maskStrokeWidth, position = 'bottom-right', onClick, onNodeClick, pannable = false, zoomable = false, ariaLabel = 'React Flow mini map', inversePan, zoomStep = 10, offsetScale = 5, }) {
|
|
3448
4271
|
const store = useStoreApi();
|
|
3449
4272
|
const svg = useRef(null);
|
|
@@ -3513,6 +4336,26 @@ nodeComponent, bgColor, maskColor, maskStrokeColor, maskStrokeWidth, position =
|
|
|
3513
4336
|
M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`, fillRule: "evenodd", pointerEvents: "none" })] }) }));
|
|
3514
4337
|
}
|
|
3515
4338
|
MiniMapComponent.displayName = 'MiniMap';
|
|
4339
|
+
/**
|
|
4340
|
+
* The `<MiniMap />` component can be used to render an overview of your flow. It
|
|
4341
|
+
* renders each node as an SVG element and visualizes where the current viewport is
|
|
4342
|
+
* in relation to the rest of the flow.
|
|
4343
|
+
*
|
|
4344
|
+
* @public
|
|
4345
|
+
* @example
|
|
4346
|
+
*
|
|
4347
|
+
* ```jsx
|
|
4348
|
+
*import { ReactFlow, MiniMap } from '@xyflow/react';
|
|
4349
|
+
*
|
|
4350
|
+
*export default function Flow() {
|
|
4351
|
+
* return (
|
|
4352
|
+
* <ReactFlow nodes={[...]]} edges={[...]]}>
|
|
4353
|
+
* <MiniMap nodeStrokeWidth={3} />
|
|
4354
|
+
* </ReactFlow>
|
|
4355
|
+
* );
|
|
4356
|
+
*}
|
|
4357
|
+
*```
|
|
4358
|
+
*/
|
|
3516
4359
|
const MiniMap = memo(MiniMapComponent);
|
|
3517
4360
|
|
|
3518
4361
|
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, }) {
|
|
@@ -3549,8 +4392,8 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
|
|
|
3549
4392
|
const node = nodeLookup.get(id);
|
|
3550
4393
|
if (node && node.expandParent && node.parentId) {
|
|
3551
4394
|
const origin = node.origin ?? nodeOrigin;
|
|
3552
|
-
const width = change.width ?? node.measured.width;
|
|
3553
|
-
const height = change.height ?? node.measured.height;
|
|
4395
|
+
const width = change.width ?? node.measured.width ?? 0;
|
|
4396
|
+
const height = change.height ?? node.measured.height ?? 0;
|
|
3554
4397
|
const child = {
|
|
3555
4398
|
id: node.id,
|
|
3556
4399
|
parentId: node.parentId,
|
|
@@ -3565,8 +4408,10 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
|
|
|
3565
4408
|
};
|
|
3566
4409
|
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
|
|
3567
4410
|
changes.push(...parentExpandChanges);
|
|
3568
|
-
|
|
3569
|
-
|
|
4411
|
+
/*
|
|
4412
|
+
* when the parent was expanded by the child node, its position will be clamped at
|
|
4413
|
+
* 0,0 when node origin is 0,0 and to width, height if it's 1,1
|
|
4414
|
+
*/
|
|
3570
4415
|
nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : undefined;
|
|
3571
4416
|
nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : undefined;
|
|
3572
4417
|
}
|
|
@@ -3644,8 +4489,37 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
|
|
|
3644
4489
|
const controlStyle = color ? { ...style, [colorStyleProp]: color } : style;
|
|
3645
4490
|
return (jsx("div", { className: cc(['react-flow__resize-control', 'nodrag', ...positionClassNames, variant, className]), ref: resizeControlRef, style: controlStyle, children: children }));
|
|
3646
4491
|
}
|
|
4492
|
+
/**
|
|
4493
|
+
* To create your own resizing UI, you can use the `NodeResizeControl` component where you can pass children (such as icons).
|
|
4494
|
+
* @public
|
|
4495
|
+
*
|
|
4496
|
+
*/
|
|
3647
4497
|
const NodeResizeControl = memo(ResizeControl);
|
|
3648
4498
|
|
|
4499
|
+
/**
|
|
4500
|
+
* The `<NodeResizer />` component can be used to add a resize functionality to your
|
|
4501
|
+
* nodes. It renders draggable controls around the node to resize in all directions.
|
|
4502
|
+
* @public
|
|
4503
|
+
*
|
|
4504
|
+
* @example
|
|
4505
|
+
*```jsx
|
|
4506
|
+
*import { memo } from 'react';
|
|
4507
|
+
*import { Handle, Position, NodeResizer } from '@xyflow/react';
|
|
4508
|
+
*
|
|
4509
|
+
*function ResizableNode({ data }) {
|
|
4510
|
+
* return (
|
|
4511
|
+
* <>
|
|
4512
|
+
* <NodeResizer minWidth={100} minHeight={30} />
|
|
4513
|
+
* <Handle type="target" position={Position.Left} />
|
|
4514
|
+
* <div style={{ padding: 10 }}>{data.label}</div>
|
|
4515
|
+
* <Handle type="source" position={Position.Right} />
|
|
4516
|
+
* </>
|
|
4517
|
+
* );
|
|
4518
|
+
*};
|
|
4519
|
+
*
|
|
4520
|
+
*export default memo(ResizableNode);
|
|
4521
|
+
*```
|
|
4522
|
+
*/
|
|
3649
4523
|
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, }) {
|
|
3650
4524
|
if (!isVisible) {
|
|
3651
4525
|
return null;
|
|
@@ -3685,6 +4559,41 @@ const storeSelector = (state) => ({
|
|
|
3685
4559
|
zoom: state.transform[2],
|
|
3686
4560
|
selectedNodesCount: state.nodes.filter((node) => node.selected).length,
|
|
3687
4561
|
});
|
|
4562
|
+
/**
|
|
4563
|
+
* This component can render a toolbar or tooltip to one side of a custom node. This
|
|
4564
|
+
* toolbar doesn't scale with the viewport so that the content is always visible.
|
|
4565
|
+
*
|
|
4566
|
+
* @public
|
|
4567
|
+
* @example
|
|
4568
|
+
* ```jsx
|
|
4569
|
+
*import { memo } from 'react';
|
|
4570
|
+
*import { Handle, Position, NodeToolbar } from '@xyflow/react';
|
|
4571
|
+
*
|
|
4572
|
+
*function CustomNode({ data }) {
|
|
4573
|
+
* return (
|
|
4574
|
+
* <>
|
|
4575
|
+
* <NodeToolbar isVisible={data.toolbarVisible} position={data.toolbarPosition}>
|
|
4576
|
+
* <button>delete</button>
|
|
4577
|
+
* <button>copy</button>
|
|
4578
|
+
* <button>expand</button>
|
|
4579
|
+
* </NodeToolbar>
|
|
4580
|
+
*
|
|
4581
|
+
* <div style={{ padding: '10px 20px' }}>
|
|
4582
|
+
* {data.label}
|
|
4583
|
+
* </div>
|
|
4584
|
+
*
|
|
4585
|
+
* <Handle type="target" position={Position.Left} />
|
|
4586
|
+
* <Handle type="source" position={Position.Right} />
|
|
4587
|
+
* </>
|
|
4588
|
+
* );
|
|
4589
|
+
*};
|
|
4590
|
+
*
|
|
4591
|
+
*export default memo(CustomNode);
|
|
4592
|
+
*```
|
|
4593
|
+
* @remarks By default, the toolbar is only visible when a node is selected. If multiple
|
|
4594
|
+
* nodes are selected it will not be visible to prevent overlapping toolbars or
|
|
4595
|
+
* clutter. You can override this behavior by setting the `isVisible` prop to `true`.
|
|
4596
|
+
*/
|
|
3688
4597
|
function NodeToolbar({ nodeId, children, className, style, isVisible, position = Position.Top, offset = 10, align = 'center', ...rest }) {
|
|
3689
4598
|
const contextNodeId = useNodeId();
|
|
3690
4599
|
const nodesSelector = useCallback((state) => {
|
|
@@ -3703,7 +4612,7 @@ function NodeToolbar({ nodeId, children, className, style, isVisible, position =
|
|
|
3703
4612
|
// if isVisible is not set, we show the toolbar only if its node is selected and no other node is selected
|
|
3704
4613
|
const isActive = typeof isVisible === 'boolean'
|
|
3705
4614
|
? isVisible
|
|
3706
|
-
: nodes.size === 1 && nodes.values().next().value
|
|
4615
|
+
: nodes.size === 1 && nodes.values().next().value?.selected && selectedNodesCount === 1;
|
|
3707
4616
|
if (!isActive || !nodes.size) {
|
|
3708
4617
|
return null;
|
|
3709
4618
|
}
|