@xyflow/react 12.4.2 → 12.4.3
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/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/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 +20 -0
- 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 +33 -1
- 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 +1089 -184
- package/dist/esm/index.mjs +1089 -184
- package/dist/esm/store/index.d.ts.map +1 -1
- package/dist/esm/types/component-props.d.ts +111 -56
- 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 +41 -0
- package/dist/esm/types/general.d.ts.map +1 -1
- package/dist/esm/types/instance.d.ts +11 -0
- 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/utils/changes.d.ts +45 -23
- package/dist/esm/utils/changes.d.ts.map +1 -1
- package/dist/esm/utils/general.d.ts +26 -2
- 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/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/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 +20 -0
- 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 +33 -1
- 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 +111 -56
- 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 +41 -0
- package/dist/umd/types/general.d.ts.map +1 -1
- package/dist/umd/types/instance.d.ts +11 -0
- 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/utils/changes.d.ts +45 -23
- package/dist/umd/utils/changes.d.ts.map +1 -1
- package/dist/umd/utils/general.d.ts +26 -2
- package/dist/umd/utils/general.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/esm/index.mjs
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) {
|
|
@@ -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;
|
|
@@ -1590,8 +1798,10 @@ function HandleComponent({ type = 'source', position = Position.Top, isValidConn
|
|
|
1590
1798
|
connectingfrom: connectingFrom,
|
|
1591
1799
|
connectingto: connectingTo,
|
|
1592
1800
|
valid,
|
|
1593
|
-
|
|
1594
|
-
|
|
1801
|
+
/*
|
|
1802
|
+
* shows where you can start a connection from
|
|
1803
|
+
* and where you can end it while connecting
|
|
1804
|
+
*/
|
|
1595
1805
|
connectionindicator: isConnectable &&
|
|
1596
1806
|
(!connectionInProcess || isPossibleEndHandle) &&
|
|
1597
1807
|
(connectionInProcess ? isConnectableEnd : isConnectableStart),
|
|
@@ -1599,7 +1809,29 @@ function HandleComponent({ type = 'source', position = Position.Top, isValidConn
|
|
|
1599
1809
|
]), onMouseDown: onPointerDown, onTouchStart: onPointerDown, onClick: connectOnClick ? onClick : undefined, ref: ref, ...rest, children: children }));
|
|
1600
1810
|
}
|
|
1601
1811
|
/**
|
|
1602
|
-
* The Handle component is
|
|
1812
|
+
* The `<Handle />` component is used in your [custom nodes](/learn/customization/custom-nodes)
|
|
1813
|
+
* to define connection points.
|
|
1814
|
+
*
|
|
1815
|
+
*@public
|
|
1816
|
+
*
|
|
1817
|
+
*@example
|
|
1818
|
+
*
|
|
1819
|
+
*```jsx
|
|
1820
|
+
*import { Handle, Position } from '@xyflow/react';
|
|
1821
|
+
*
|
|
1822
|
+
*export function CustomNode({ data }) {
|
|
1823
|
+
* return (
|
|
1824
|
+
* <>
|
|
1825
|
+
* <div style={{ padding: '10px 20px' }}>
|
|
1826
|
+
* {data.label}
|
|
1827
|
+
* </div>
|
|
1828
|
+
*
|
|
1829
|
+
* <Handle type="target" position={Position.Left} />
|
|
1830
|
+
* <Handle type="source" position={Position.Right} />
|
|
1831
|
+
* </>
|
|
1832
|
+
* );
|
|
1833
|
+
*};
|
|
1834
|
+
*```
|
|
1603
1835
|
*/
|
|
1604
1836
|
const Handle = memo(fixedForwardRef(HandleComponent));
|
|
1605
1837
|
|
|
@@ -1792,8 +2024,10 @@ function useNodeObserver({ node, nodeType, hasDimensions, resizeObserver, }) {
|
|
|
1792
2024
|
}, []);
|
|
1793
2025
|
useEffect(() => {
|
|
1794
2026
|
if (nodeRef.current) {
|
|
1795
|
-
|
|
1796
|
-
|
|
2027
|
+
/*
|
|
2028
|
+
* when the user programmatically changes the source or handle position, we need to update the internals
|
|
2029
|
+
* to make sure the edges are updated correctly
|
|
2030
|
+
*/
|
|
1797
2031
|
const typeChanged = prevType.current !== nodeType;
|
|
1798
2032
|
const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
|
|
1799
2033
|
const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
|
|
@@ -1810,7 +2044,7 @@ function useNodeObserver({ node, nodeType, hasDimensions, resizeObserver, }) {
|
|
|
1810
2044
|
return nodeRef;
|
|
1811
2045
|
}
|
|
1812
2046
|
|
|
1813
|
-
function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onContextMenu, onDoubleClick, nodesDraggable, elementsSelectable, nodesConnectable, nodesFocusable, resizeObserver, noDragClassName, noPanClassName, disableKeyboardA11y, rfId, nodeTypes,
|
|
2047
|
+
function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onContextMenu, onDoubleClick, nodesDraggable, elementsSelectable, nodesConnectable, nodesFocusable, resizeObserver, noDragClassName, noPanClassName, disableKeyboardA11y, rfId, nodeTypes, nodeClickDistance, onError, }) {
|
|
1814
2048
|
const { node, internals, isParent } = useStore((s) => {
|
|
1815
2049
|
const node = s.nodeLookup.get(id);
|
|
1816
2050
|
const isParent = s.parentLookup.has(id);
|
|
@@ -1868,8 +2102,10 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
|
|
|
1868
2102
|
const onSelectNodeHandler = (event) => {
|
|
1869
2103
|
const { selectNodesOnDrag, nodeDragThreshold } = store.getState();
|
|
1870
2104
|
if (isSelectable && (!selectNodesOnDrag || !isDraggable || nodeDragThreshold > 0)) {
|
|
1871
|
-
|
|
1872
|
-
|
|
2105
|
+
/*
|
|
2106
|
+
* this handler gets called by XYDrag on drag start when selectNodesOnDrag=true
|
|
2107
|
+
* here we only need to call it when selectNodesOnDrag=false
|
|
2108
|
+
*/
|
|
1873
2109
|
handleNodeClick({
|
|
1874
2110
|
id,
|
|
1875
2111
|
store,
|
|
@@ -1945,29 +2181,31 @@ function NodeRendererComponent(props) {
|
|
|
1945
2181
|
const resizeObserver = useResizeObserver();
|
|
1946
2182
|
return (jsx("div", { className: "react-flow__nodes", style: containerStyle, children: nodeIds.map((nodeId) => {
|
|
1947
2183
|
return (
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2184
|
+
/*
|
|
2185
|
+
* The split of responsibilities between NodeRenderer and
|
|
2186
|
+
* NodeComponentWrapper may appear weird. However, it’s designed to
|
|
2187
|
+
* minimize the cost of updates when individual nodes change.
|
|
2188
|
+
*
|
|
2189
|
+
* For example, when you’re dragging a single node, that node gets
|
|
2190
|
+
* updated multiple times per second. If `NodeRenderer` were to update
|
|
2191
|
+
* every time, it would have to re-run the `nodes.map()` loop every
|
|
2192
|
+
* time. This gets pricey with hundreds of nodes, especially if every
|
|
2193
|
+
* loop cycle does more than just rendering a JSX element!
|
|
2194
|
+
*
|
|
2195
|
+
* As a result of this choice, we took the following implementation
|
|
2196
|
+
* decisions:
|
|
2197
|
+
* - NodeRenderer subscribes *only* to node IDs – and therefore
|
|
2198
|
+
* rerender *only* when visible nodes are added or removed.
|
|
2199
|
+
* - NodeRenderer performs all operations the result of which can be
|
|
2200
|
+
* shared between nodes (such as creating the `ResizeObserver`
|
|
2201
|
+
* instance, or subscribing to `selector`). This means extra prop
|
|
2202
|
+
* drilling into `NodeComponentWrapper`, but it means we need to run
|
|
2203
|
+
* these operations only once – instead of once per node.
|
|
2204
|
+
* - Any operations that you’d normally write inside `nodes.map` are
|
|
2205
|
+
* moved into `NodeComponentWrapper`. This ensures they are
|
|
2206
|
+
* memorized – so if `NodeRenderer` *has* to rerender, it only
|
|
2207
|
+
* needs to regenerate the list of nodes, nothing else.
|
|
2208
|
+
*/
|
|
1971
2209
|
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
2210
|
}) }));
|
|
1973
2211
|
}
|
|
@@ -2046,9 +2284,11 @@ const Marker = ({ id, type, color, width = 12.5, height = 12.5, markerUnits = 's
|
|
|
2046
2284
|
}
|
|
2047
2285
|
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
2286
|
};
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2287
|
+
/*
|
|
2288
|
+
* when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
|
2289
|
+
* when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
|
2290
|
+
* that we can then use for creating our unique marker ids
|
|
2291
|
+
*/
|
|
2052
2292
|
const MarkerDefinitions = ({ defaultColor, rfId }) => {
|
|
2053
2293
|
const edges = useStore((s) => s.edges);
|
|
2054
2294
|
const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions);
|
|
@@ -2090,8 +2330,61 @@ function EdgeTextComponent({ x, y, label, labelStyle = {}, labelShowBg = true, l
|
|
|
2090
2330
|
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
2331
|
}
|
|
2092
2332
|
EdgeTextComponent.displayName = 'EdgeText';
|
|
2333
|
+
/**
|
|
2334
|
+
* You can use the `<EdgeText />` component as a helper component to display text
|
|
2335
|
+
* within your custom edges.
|
|
2336
|
+
*
|
|
2337
|
+
*@public
|
|
2338
|
+
*
|
|
2339
|
+
*@example
|
|
2340
|
+
*```jsx
|
|
2341
|
+
*import { EdgeText } from '@xyflow/react';
|
|
2342
|
+
*
|
|
2343
|
+
*export function CustomEdgeLabel({ label }) {
|
|
2344
|
+
* return (
|
|
2345
|
+
* <EdgeText
|
|
2346
|
+
* x={100}
|
|
2347
|
+
* y={100}
|
|
2348
|
+
* label={label}
|
|
2349
|
+
* labelStyle={{ fill: 'white' }}
|
|
2350
|
+
* labelShowBg
|
|
2351
|
+
* labelBgStyle={{ fill: 'red' }}
|
|
2352
|
+
* labelBgPadding={[2, 4]}
|
|
2353
|
+
* labelBgBorderRadius={2}
|
|
2354
|
+
* />
|
|
2355
|
+
* );
|
|
2356
|
+
*}
|
|
2357
|
+
*```
|
|
2358
|
+
*/
|
|
2093
2359
|
const EdgeText = memo(EdgeTextComponent);
|
|
2094
2360
|
|
|
2361
|
+
/**
|
|
2362
|
+
* The `<BaseEdge />` component gets used internally for all the edges. It can be
|
|
2363
|
+
* used inside a custom edge and handles the invisible helper edge and the edge label
|
|
2364
|
+
* for you.
|
|
2365
|
+
*
|
|
2366
|
+
* @public
|
|
2367
|
+
* @example
|
|
2368
|
+
* ```jsx
|
|
2369
|
+
*import { BaseEdge } from '@xyflow/react';
|
|
2370
|
+
*
|
|
2371
|
+
*export function CustomEdge({ sourceX, sourceY, targetX, targetY, ...props }) {
|
|
2372
|
+
* const [edgePath] = getStraightPath({
|
|
2373
|
+
* sourceX,
|
|
2374
|
+
* sourceY,
|
|
2375
|
+
* targetX,
|
|
2376
|
+
* targetY,
|
|
2377
|
+
* });
|
|
2378
|
+
*
|
|
2379
|
+
* return <BaseEdge path={edgePath} {...props} />;
|
|
2380
|
+
*}
|
|
2381
|
+
*```
|
|
2382
|
+
*
|
|
2383
|
+
* @remarks If you want to use an edge marker with the [`<BaseEdge />`](/api-reference/components/base-edge) component,
|
|
2384
|
+
* you can pass the `markerStart` or `markerEnd` props passed to your custom edge
|
|
2385
|
+
* through to the [`<BaseEdge />`](/api-reference/components/base-edge) component.
|
|
2386
|
+
* You can see all the props passed to a custom edge by looking at the [`EdgeProps`](/api-reference/types/edge-props) type.
|
|
2387
|
+
*/
|
|
2095
2388
|
function BaseEdge({ path, labelX, labelY, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, interactionWidth = 20, ...props }) {
|
|
2096
2389
|
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
2390
|
}
|
|
@@ -2102,6 +2395,11 @@ function getControl({ pos, x1, y1, x2, y2 }) {
|
|
|
2102
2395
|
}
|
|
2103
2396
|
return [x1, 0.5 * (y1 + y2)];
|
|
2104
2397
|
}
|
|
2398
|
+
/**
|
|
2399
|
+
* The `getSimpleBezierPath` util returns everything you need to render a simple
|
|
2400
|
+
* bezier edge between two nodes.
|
|
2401
|
+
* @public
|
|
2402
|
+
*/
|
|
2105
2403
|
function getSimpleBezierPath({ sourceX, sourceY, sourcePosition = Position.Bottom, targetX, targetY, targetPosition = Position.Top, }) {
|
|
2106
2404
|
const [sourceControlX, sourceControlY] = getControl({
|
|
2107
2405
|
pos: sourcePosition,
|
|
@@ -2502,9 +2800,28 @@ function getSelector(connectionSelector) {
|
|
|
2502
2800
|
return storeSelector$1;
|
|
2503
2801
|
}
|
|
2504
2802
|
/**
|
|
2505
|
-
*
|
|
2803
|
+
* The `useConnection` hook returns the current connection when there is an active
|
|
2804
|
+
* connection interaction. If no connection interaction is active, it returns null
|
|
2805
|
+
* for every property. A typical use case for this hook is to colorize handles
|
|
2806
|
+
* based on a certain condition (e.g. if the connection is valid or not).
|
|
2506
2807
|
*
|
|
2507
2808
|
* @public
|
|
2809
|
+
* @example
|
|
2810
|
+
*
|
|
2811
|
+
* ```tsx
|
|
2812
|
+
*import { useConnection } from '@xyflow/react';
|
|
2813
|
+
*
|
|
2814
|
+
*function App() {
|
|
2815
|
+
* const connection = useConnection();
|
|
2816
|
+
*
|
|
2817
|
+
* return (
|
|
2818
|
+
* <div> {connection ? `Someone is trying to make a connection from ${connection.fromNode} to this one.` : 'There are currently no incoming connections!'}
|
|
2819
|
+
*
|
|
2820
|
+
* </div>
|
|
2821
|
+
* );
|
|
2822
|
+
* }
|
|
2823
|
+
* ```
|
|
2824
|
+
*
|
|
2508
2825
|
* @returns ConnectionState
|
|
2509
2826
|
*/
|
|
2510
2827
|
function useConnection(connectionSelector) {
|
|
@@ -2519,7 +2836,7 @@ const selector$7 = (s) => ({
|
|
|
2519
2836
|
width: s.width,
|
|
2520
2837
|
height: s.height,
|
|
2521
2838
|
});
|
|
2522
|
-
function ConnectionLineWrapper({ containerStyle, style, type, component }) {
|
|
2839
|
+
function ConnectionLineWrapper({ containerStyle, style, type, component, }) {
|
|
2523
2840
|
const { nodesConnectable, width, height, isValid, inProgress } = useStore(selector$7, shallow);
|
|
2524
2841
|
const renderConnection = !!(width && nodesConnectable && inProgress);
|
|
2525
2842
|
if (!renderConnection) {
|
|
@@ -2527,7 +2844,7 @@ function ConnectionLineWrapper({ containerStyle, style, type, component }) {
|
|
|
2527
2844
|
}
|
|
2528
2845
|
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
2846
|
}
|
|
2530
|
-
const ConnectionLine = ({ style, type = ConnectionLineType.Bezier, CustomComponent, isValid }) => {
|
|
2847
|
+
const ConnectionLine = ({ style, type = ConnectionLineType.Bezier, CustomComponent, isValid, }) => {
|
|
2531
2848
|
const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition } = useConnection();
|
|
2532
2849
|
if (!inProgress) {
|
|
2533
2850
|
return;
|
|
@@ -2700,12 +3017,14 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2700
3017
|
...getInitialState({ nodes, edges, width, height, fitView: fitView$1, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
|
2701
3018
|
setNodes: (nodes) => {
|
|
2702
3019
|
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
3020
|
+
/*
|
|
3021
|
+
* setNodes() is called exclusively in response to user actions:
|
|
3022
|
+
* - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
|
3023
|
+
* - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
|
|
3024
|
+
*
|
|
3025
|
+
* When this happens, we take the note objects passed by the user and extend them with fields
|
|
3026
|
+
* relevant for internal React Flow operations.
|
|
3027
|
+
*/
|
|
2709
3028
|
adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
|
2710
3029
|
nodeOrigin,
|
|
2711
3030
|
nodeExtent,
|
|
@@ -2731,9 +3050,11 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2731
3050
|
set({ hasDefaultEdges: true });
|
|
2732
3051
|
}
|
|
2733
3052
|
},
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
3053
|
+
/*
|
|
3054
|
+
* Every node gets registerd at a ResizeObserver. Whenever a node
|
|
3055
|
+
* changes its dimensions, this function is called to measure the
|
|
3056
|
+
* new dimensions and update the nodes.
|
|
3057
|
+
*/
|
|
2737
3058
|
updateNodeInternals: (updates, params = { triggerFitView: true }) => {
|
|
2738
3059
|
const { triggerNodeChanges, nodeLookup, parentLookup, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin, nodeExtent, debug, fitViewSync, } = get();
|
|
2739
3060
|
const { changes, updatedInternals } = updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOrigin, nodeExtent);
|
|
@@ -2750,11 +3071,13 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2750
3071
|
nodes: fitViewOnInitOptions?.nodes,
|
|
2751
3072
|
});
|
|
2752
3073
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
3074
|
+
/*
|
|
3075
|
+
* here we are cirmumventing the onNodesChange handler
|
|
3076
|
+
* in order to be able to display nodes even if the user
|
|
3077
|
+
* has not provided an onNodesChange handler.
|
|
3078
|
+
* Nodes are only rendered if they have a width and height
|
|
3079
|
+
* attribute which they get from this handler.
|
|
3080
|
+
*/
|
|
2758
3081
|
set({ fitViewDone: nextFitViewDone });
|
|
2759
3082
|
}
|
|
2760
3083
|
else {
|
|
@@ -2857,8 +3180,10 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2857
3180
|
const nodeChanges = nodesToUnselect.map((n) => {
|
|
2858
3181
|
const internalNode = nodeLookup.get(n.id);
|
|
2859
3182
|
if (internalNode) {
|
|
2860
|
-
|
|
2861
|
-
|
|
3183
|
+
/*
|
|
3184
|
+
* we need to unselect the internal node that was selected previously before we
|
|
3185
|
+
* send the change to the user to prevent it to be selected while dragging the new node
|
|
3186
|
+
*/
|
|
2862
3187
|
internalNode.selected = false;
|
|
2863
3188
|
}
|
|
2864
3189
|
return createSelectionChange(n.id, false);
|
|
@@ -2926,8 +3251,10 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2926
3251
|
maxZoom,
|
|
2927
3252
|
}, options);
|
|
2928
3253
|
},
|
|
2929
|
-
|
|
2930
|
-
|
|
3254
|
+
/*
|
|
3255
|
+
* we can't call an asnychronous function in updateNodeInternals
|
|
3256
|
+
* for that we created this sync version of fitView
|
|
3257
|
+
*/
|
|
2931
3258
|
fitViewSync: (options) => {
|
|
2932
3259
|
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
|
|
2933
3260
|
if (!panZoom) {
|
|
@@ -2955,6 +3282,40 @@ const createStore = ({ nodes, edges, defaultNodes, defaultEdges, width, height,
|
|
|
2955
3282
|
reset: () => set({ ...getInitialState() }),
|
|
2956
3283
|
}), Object.is);
|
|
2957
3284
|
|
|
3285
|
+
/**
|
|
3286
|
+
* The `<ReactFlowProvider />` component is a [context provider](https://react.dev/learn/passing-data-deeply-with-context#)
|
|
3287
|
+
* that makes it possible to access a flow's internal state outside of the
|
|
3288
|
+
* [`<ReactFlow />`](/api-reference/react-flow) component. Many of the hooks we
|
|
3289
|
+
* provide rely on this component to work.
|
|
3290
|
+
* @public
|
|
3291
|
+
*
|
|
3292
|
+
* @example
|
|
3293
|
+
* ```tsx
|
|
3294
|
+
*import { ReactFlow, ReactFlowProvider, useNodes } from '@xyflow/react'
|
|
3295
|
+
*
|
|
3296
|
+
*export default function Flow() {
|
|
3297
|
+
* return (
|
|
3298
|
+
* <ReactFlowProvider>
|
|
3299
|
+
* <ReactFlow nodes={...} edges={...} />
|
|
3300
|
+
* <Sidebar />
|
|
3301
|
+
* </ReactFlowProvider>
|
|
3302
|
+
* );
|
|
3303
|
+
*}
|
|
3304
|
+
*
|
|
3305
|
+
*function Sidebar() {
|
|
3306
|
+
* // This hook will only work if the component it's used in is a child of a
|
|
3307
|
+
* // <ReactFlowProvider />.
|
|
3308
|
+
* const nodes = useNodes()
|
|
3309
|
+
*
|
|
3310
|
+
* return <aside>do something with nodes</aside>;
|
|
3311
|
+
*}
|
|
3312
|
+
*```
|
|
3313
|
+
*
|
|
3314
|
+
* @remarks If you're using a router and want your flow's state to persist across routes,
|
|
3315
|
+
* it's vital that you place the `<ReactFlowProvider />` component _outside_ of
|
|
3316
|
+
* your router. If you have multiple flows on the same page you will need to use a separate
|
|
3317
|
+
* `<ReactFlowProvider />` for each flow.
|
|
3318
|
+
*/
|
|
2958
3319
|
function ReactFlowProvider({ initialNodes: nodes, initialEdges: edges, defaultNodes, defaultEdges, initialWidth: width, initialHeight: height, fitView, nodeOrigin, nodeExtent, children, }) {
|
|
2959
3320
|
const [store] = useState(() => createStore({
|
|
2960
3321
|
nodes,
|
|
@@ -2973,8 +3334,10 @@ function ReactFlowProvider({ initialNodes: nodes, initialEdges: edges, defaultNo
|
|
|
2973
3334
|
function Wrapper({ children, nodes, edges, defaultNodes, defaultEdges, width, height, fitView, nodeOrigin, nodeExtent, }) {
|
|
2974
3335
|
const isWrapped = useContext(StoreContext);
|
|
2975
3336
|
if (isWrapped) {
|
|
2976
|
-
|
|
2977
|
-
|
|
3337
|
+
/*
|
|
3338
|
+
* we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
|
|
3339
|
+
* https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
|
|
3340
|
+
*/
|
|
2978
3341
|
return jsx(Fragment, { children: children });
|
|
2979
3342
|
}
|
|
2980
3343
|
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 +3350,79 @@ const wrapperStyle = {
|
|
|
2987
3350
|
position: 'relative',
|
|
2988
3351
|
zIndex: 0,
|
|
2989
3352
|
};
|
|
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) {
|
|
3353
|
+
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
3354
|
const rfId = id || '1';
|
|
2992
3355
|
const colorModeClassName = useColorModeClass(colorMode);
|
|
2993
|
-
|
|
3356
|
+
// Undo scroll events, preventing viewport from shifting when nodes outside of it are focused
|
|
3357
|
+
const wrapperOnScroll = useCallback((e) => {
|
|
3358
|
+
e.currentTarget.scrollTo({ top: 0, left: 0, behavior: 'instant' });
|
|
3359
|
+
onScroll?.(e);
|
|
3360
|
+
}, [onScroll]);
|
|
3361
|
+
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
3362
|
}
|
|
3363
|
+
/**
|
|
3364
|
+
* The `<ReactFlow />` component is the heart of your React Flow application.
|
|
3365
|
+
* It renders your nodes and edges and handles user interaction
|
|
3366
|
+
*
|
|
3367
|
+
* @public
|
|
3368
|
+
*
|
|
3369
|
+
* @example
|
|
3370
|
+
* ```tsx
|
|
3371
|
+
*import { ReactFlow } from '@xyflow/react'
|
|
3372
|
+
*
|
|
3373
|
+
*export default function Flow() {
|
|
3374
|
+
* return (<ReactFlow
|
|
3375
|
+
* nodes={...}
|
|
3376
|
+
* edges={...}
|
|
3377
|
+
* onNodesChange={...}
|
|
3378
|
+
* ...
|
|
3379
|
+
* />);
|
|
3380
|
+
*}
|
|
3381
|
+
*```
|
|
3382
|
+
*/
|
|
2995
3383
|
var index = fixedForwardRef(ReactFlow);
|
|
2996
3384
|
|
|
2997
3385
|
const selector$6 = (s) => s.domNode?.querySelector('.react-flow__edgelabel-renderer');
|
|
3386
|
+
/**
|
|
3387
|
+
* Edges are SVG-based. If you want to render more complex labels you can use the
|
|
3388
|
+
* `<EdgeLabelRenderer />` component to access a div based renderer. This component
|
|
3389
|
+
* is a portal that renders the label in a `<div />` that is positioned on top of
|
|
3390
|
+
* the edges. You can see an example usage of the component in the [edge label renderer](/examples/edges/edge-label-renderer) example.
|
|
3391
|
+
* @public
|
|
3392
|
+
*
|
|
3393
|
+
* @example
|
|
3394
|
+
*```jsx
|
|
3395
|
+
*import React from 'react';
|
|
3396
|
+
*import { getBezierPath, EdgeLabelRenderer, BaseEdge } from '@xyflow/react';
|
|
3397
|
+
*
|
|
3398
|
+
*export function CustomEdge({ id, data, ...props }) {
|
|
3399
|
+
* const [edgePath, labelX, labelY] = getBezierPath(props);
|
|
3400
|
+
*
|
|
3401
|
+
* return (
|
|
3402
|
+
* <>
|
|
3403
|
+
* <BaseEdge id={id} path={edgePath} />
|
|
3404
|
+
* <EdgeLabelRenderer>
|
|
3405
|
+
* <div
|
|
3406
|
+
* style={{
|
|
3407
|
+
* position: 'absolute',
|
|
3408
|
+
* transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
3409
|
+
* background: '#ffcc00',
|
|
3410
|
+
* padding: 10,
|
|
3411
|
+
* }}
|
|
3412
|
+
* className="nodrag nopan"
|
|
3413
|
+
* >
|
|
3414
|
+
* {data.label}
|
|
3415
|
+
* </div>
|
|
3416
|
+
* </EdgeLabelRenderer>
|
|
3417
|
+
* </>
|
|
3418
|
+
* );
|
|
3419
|
+
*};
|
|
3420
|
+
*```
|
|
3421
|
+
*
|
|
3422
|
+
* @remarks The `<EdgeLabelRenderer />` has no pointer events by default. If you want to
|
|
3423
|
+
* add mouse interactions you need to set the style `pointerEvents: all` and add
|
|
3424
|
+
* the `nopan` class on the label or the element you want to interact with.
|
|
3425
|
+
*/
|
|
2998
3426
|
function EdgeLabelRenderer({ children }) {
|
|
2999
3427
|
const edgeLabelRenderer = useStore(selector$6);
|
|
3000
3428
|
if (!edgeLabelRenderer) {
|
|
@@ -3004,6 +3432,31 @@ function EdgeLabelRenderer({ children }) {
|
|
|
3004
3432
|
}
|
|
3005
3433
|
|
|
3006
3434
|
const selector$5 = (s) => s.domNode?.querySelector('.react-flow__viewport-portal');
|
|
3435
|
+
/**
|
|
3436
|
+
* The `<ViewportPortal />` component can be used to add components to the same viewport
|
|
3437
|
+
* of the flow where nodes and edges are rendered. This is useful when you want to render
|
|
3438
|
+
* your own components that are adhere to the same coordinate system as the nodes & edges
|
|
3439
|
+
* and are also affected by zooming and panning
|
|
3440
|
+
* @public
|
|
3441
|
+
* @example
|
|
3442
|
+
*
|
|
3443
|
+
* ```jsx
|
|
3444
|
+
*import React from 'react';
|
|
3445
|
+
*import { ViewportPortal } from '@xyflow/react';
|
|
3446
|
+
*
|
|
3447
|
+
*export default function () {
|
|
3448
|
+
* return (
|
|
3449
|
+
* <ViewportPortal>
|
|
3450
|
+
* <div
|
|
3451
|
+
* style={{ transform: 'translate(100px, 100px)', position: 'absolute' }}
|
|
3452
|
+
* >
|
|
3453
|
+
* This div is positioned at [100, 100] on the flow.
|
|
3454
|
+
* </div>
|
|
3455
|
+
* </ViewportPortal>
|
|
3456
|
+
* );
|
|
3457
|
+
*}
|
|
3458
|
+
*```
|
|
3459
|
+
*/
|
|
3007
3460
|
function ViewportPortal({ children }) {
|
|
3008
3461
|
const viewPortalDiv = useStore(selector$5);
|
|
3009
3462
|
if (!viewPortalDiv) {
|
|
@@ -3013,10 +3466,48 @@ function ViewportPortal({ children }) {
|
|
|
3013
3466
|
}
|
|
3014
3467
|
|
|
3015
3468
|
/**
|
|
3016
|
-
*
|
|
3469
|
+
* When you programmatically add or remove handles to a node or update a node's
|
|
3470
|
+
*handle position, you need to let React Flow know about it using this hook. This
|
|
3471
|
+
*will update the internal dimensions of the node and properly reposition handles
|
|
3472
|
+
*on the canvas if necessary.
|
|
3017
3473
|
*
|
|
3018
3474
|
* @public
|
|
3019
3475
|
* @returns function for updating node internals
|
|
3476
|
+
*
|
|
3477
|
+
* @example
|
|
3478
|
+
* ```jsx
|
|
3479
|
+
*import { useCallback, useState } from 'react';
|
|
3480
|
+
*import { Handle, useUpdateNodeInternals } from '@xyflow/react';
|
|
3481
|
+
*
|
|
3482
|
+
*export default function RandomHandleNode({ id }) {
|
|
3483
|
+
* const updateNodeInternals = useUpdateNodeInternals();
|
|
3484
|
+
* const [handleCount, setHandleCount] = useState(0);
|
|
3485
|
+
* const randomizeHandleCount = useCallback(() => {
|
|
3486
|
+
* setHandleCount(Math.floor(Math.random() * 10));
|
|
3487
|
+
* updateNodeInternals(id);
|
|
3488
|
+
* }, [id, updateNodeInternals]);
|
|
3489
|
+
*
|
|
3490
|
+
* return (
|
|
3491
|
+
* <>
|
|
3492
|
+
* {Array.from({ length: handleCount }).map((_, index) => (
|
|
3493
|
+
* <Handle
|
|
3494
|
+
* key={index}
|
|
3495
|
+
* type="target"
|
|
3496
|
+
* position="left"
|
|
3497
|
+
* id={`handle-${index}`}
|
|
3498
|
+
* />
|
|
3499
|
+
* ))}
|
|
3500
|
+
*
|
|
3501
|
+
* <div>
|
|
3502
|
+
* <button onClick={randomizeHandleCount}>Randomize handle count</button>
|
|
3503
|
+
* <p>There are {handleCount} handles on this node.</p>
|
|
3504
|
+
* </div>
|
|
3505
|
+
* </>
|
|
3506
|
+
* );
|
|
3507
|
+
*}
|
|
3508
|
+
*```
|
|
3509
|
+
* @remarks This hook can only be used in a component that is a child of a
|
|
3510
|
+
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
|
3020
3511
|
*/
|
|
3021
3512
|
function useUpdateNodeInternals() {
|
|
3022
3513
|
const store = useStoreApi();
|
|
@@ -3036,10 +3527,23 @@ function useUpdateNodeInternals() {
|
|
|
3036
3527
|
|
|
3037
3528
|
const nodesSelector = (state) => state.nodes;
|
|
3038
3529
|
/**
|
|
3039
|
-
*
|
|
3530
|
+
* This hook returns an array of the current nodes. Components that use this hook
|
|
3531
|
+
* will re-render **whenever any node changes**, including when a node is selected
|
|
3532
|
+
* or moved.
|
|
3040
3533
|
*
|
|
3041
3534
|
* @public
|
|
3042
3535
|
* @returns An array of nodes
|
|
3536
|
+
*
|
|
3537
|
+
* @example
|
|
3538
|
+
* ```jsx
|
|
3539
|
+
*import { useNodes } from '@xyflow/react';
|
|
3540
|
+
*
|
|
3541
|
+
*export default function() {
|
|
3542
|
+
* const nodes = useNodes();
|
|
3543
|
+
*
|
|
3544
|
+
* return <div>There are currently {nodes.length} nodes!</div>;
|
|
3545
|
+
*}
|
|
3546
|
+
*```
|
|
3043
3547
|
*/
|
|
3044
3548
|
function useNodes() {
|
|
3045
3549
|
const nodes = useStore(nodesSelector, shallow);
|
|
@@ -3048,10 +3552,22 @@ function useNodes() {
|
|
|
3048
3552
|
|
|
3049
3553
|
const edgesSelector = (state) => state.edges;
|
|
3050
3554
|
/**
|
|
3051
|
-
*
|
|
3555
|
+
* This hook returns an array of the current edges. Components that use this hook
|
|
3556
|
+
* will re-render **whenever any edge changes**.
|
|
3052
3557
|
*
|
|
3053
3558
|
* @public
|
|
3054
3559
|
* @returns An array of edges
|
|
3560
|
+
*
|
|
3561
|
+
* @example
|
|
3562
|
+
* ```tsx
|
|
3563
|
+
*import { useEdges } from '@xyflow/react';
|
|
3564
|
+
*
|
|
3565
|
+
*export default function () {
|
|
3566
|
+
* const edges = useEdges();
|
|
3567
|
+
*
|
|
3568
|
+
* return <div>There are currently {edges.length} edges!</div>;
|
|
3569
|
+
*}
|
|
3570
|
+
*```
|
|
3055
3571
|
*/
|
|
3056
3572
|
function useEdges() {
|
|
3057
3573
|
const edges = useStore(edgesSelector, shallow);
|
|
@@ -3064,10 +3580,33 @@ const viewportSelector = (state) => ({
|
|
|
3064
3580
|
zoom: state.transform[2],
|
|
3065
3581
|
});
|
|
3066
3582
|
/**
|
|
3067
|
-
*
|
|
3583
|
+
* The `useViewport` hook is a convenient way to read the current state of the
|
|
3584
|
+
*{@link Viewport} in a component. Components that use this hook
|
|
3585
|
+
*will re-render **whenever the viewport changes**.
|
|
3068
3586
|
*
|
|
3069
3587
|
* @public
|
|
3070
3588
|
* @returns The current viewport
|
|
3589
|
+
*
|
|
3590
|
+
* @example
|
|
3591
|
+
*
|
|
3592
|
+
*```jsx
|
|
3593
|
+
*import { useViewport } from '@xyflow/react';
|
|
3594
|
+
*
|
|
3595
|
+
*export default function ViewportDisplay() {
|
|
3596
|
+
* const { x, y, zoom } = useViewport();
|
|
3597
|
+
*
|
|
3598
|
+
* return (
|
|
3599
|
+
* <div>
|
|
3600
|
+
* <p>
|
|
3601
|
+
* The viewport is currently at ({x}, {y}) and zoomed to {zoom}.
|
|
3602
|
+
* </p>
|
|
3603
|
+
* </div>
|
|
3604
|
+
* );
|
|
3605
|
+
*}
|
|
3606
|
+
*```
|
|
3607
|
+
*
|
|
3608
|
+
* @remarks This hook can only be used in a component that is a child of a
|
|
3609
|
+
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
|
3071
3610
|
*/
|
|
3072
3611
|
function useViewport() {
|
|
3073
3612
|
const viewport = useStore(viewportSelector, shallow);
|
|
@@ -3075,11 +3614,41 @@ function useViewport() {
|
|
|
3075
3614
|
}
|
|
3076
3615
|
|
|
3077
3616
|
/**
|
|
3078
|
-
*
|
|
3617
|
+
* This hook makes it easy to prototype a controlled flow where you manage the
|
|
3618
|
+
* state of nodes and edges outside the `ReactFlowInstance`. You can think of it
|
|
3619
|
+
* like React's `useState` hook with an additional helper callback.
|
|
3079
3620
|
*
|
|
3080
3621
|
* @public
|
|
3081
3622
|
* @param initialNodes
|
|
3082
3623
|
* @returns an array [nodes, setNodes, onNodesChange]
|
|
3624
|
+
* @example
|
|
3625
|
+
*
|
|
3626
|
+
*```tsx
|
|
3627
|
+
*import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
|
|
3628
|
+
*
|
|
3629
|
+
*const initialNodes = [];
|
|
3630
|
+
*const initialEdges = [];
|
|
3631
|
+
*
|
|
3632
|
+
*export default function () {
|
|
3633
|
+
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
|
3634
|
+
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
3635
|
+
*
|
|
3636
|
+
* return (
|
|
3637
|
+
* <ReactFlow
|
|
3638
|
+
* nodes={nodes}
|
|
3639
|
+
* edges={edges}
|
|
3640
|
+
* onNodesChange={onNodesChange}
|
|
3641
|
+
* onEdgesChange={onEdgesChange}
|
|
3642
|
+
* />
|
|
3643
|
+
* );
|
|
3644
|
+
*}
|
|
3645
|
+
*```
|
|
3646
|
+
*
|
|
3647
|
+
* @remarks This hook was created to make prototyping easier and our documentation
|
|
3648
|
+
* examples clearer. Although it is OK to use this hook in production, in
|
|
3649
|
+
* practice you may want to use a more sophisticated state management solution
|
|
3650
|
+
* like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead.
|
|
3651
|
+
*
|
|
3083
3652
|
*/
|
|
3084
3653
|
function useNodesState(initialNodes) {
|
|
3085
3654
|
const [nodes, setNodes] = useState(initialNodes);
|
|
@@ -3087,11 +3656,41 @@ function useNodesState(initialNodes) {
|
|
|
3087
3656
|
return [nodes, setNodes, onNodesChange];
|
|
3088
3657
|
}
|
|
3089
3658
|
/**
|
|
3090
|
-
*
|
|
3659
|
+
* This hook makes it easy to prototype a controlled flow where you manage the
|
|
3660
|
+
* state of nodes and edges outside the `ReactFlowInstance`. You can think of it
|
|
3661
|
+
* like React's `useState` hook with an additional helper callback.
|
|
3091
3662
|
*
|
|
3092
3663
|
* @public
|
|
3093
3664
|
* @param initialEdges
|
|
3094
3665
|
* @returns an array [edges, setEdges, onEdgesChange]
|
|
3666
|
+
* @example
|
|
3667
|
+
*
|
|
3668
|
+
*```tsx
|
|
3669
|
+
*import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
|
|
3670
|
+
*
|
|
3671
|
+
*const initialNodes = [];
|
|
3672
|
+
*const initialEdges = [];
|
|
3673
|
+
*
|
|
3674
|
+
*export default function () {
|
|
3675
|
+
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
|
3676
|
+
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
3677
|
+
*
|
|
3678
|
+
* return (
|
|
3679
|
+
* <ReactFlow
|
|
3680
|
+
* nodes={nodes}
|
|
3681
|
+
* edges={edges}
|
|
3682
|
+
* onNodesChange={onNodesChange}
|
|
3683
|
+
* onEdgesChange={onEdgesChange}
|
|
3684
|
+
* />
|
|
3685
|
+
* );
|
|
3686
|
+
*}
|
|
3687
|
+
*```
|
|
3688
|
+
*
|
|
3689
|
+
* @remarks This hook was created to make prototyping easier and our documentation
|
|
3690
|
+
* examples clearer. Although it is OK to use this hook in production, in
|
|
3691
|
+
* practice you may want to use a more sophisticated state management solution
|
|
3692
|
+
* like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead.
|
|
3693
|
+
*
|
|
3095
3694
|
*/
|
|
3096
3695
|
function useEdgesState(initialEdges) {
|
|
3097
3696
|
const [edges, setEdges] = useState(initialEdges);
|
|
@@ -3100,12 +3699,30 @@ function useEdgesState(initialEdges) {
|
|
|
3100
3699
|
}
|
|
3101
3700
|
|
|
3102
3701
|
/**
|
|
3103
|
-
*
|
|
3702
|
+
* The `useOnViewportChange` hook lets you listen for changes to the viewport such
|
|
3703
|
+
*as panning and zooming. You can provide a callback for each phase of a viewport
|
|
3704
|
+
*change: `onStart`, `onChange`, and `onEnd`.
|
|
3104
3705
|
*
|
|
3105
3706
|
* @public
|
|
3106
3707
|
* @param params.onStart - gets called when the viewport starts changing
|
|
3107
3708
|
* @param params.onChange - gets called when the viewport changes
|
|
3108
3709
|
* @param params.onEnd - gets called when the viewport stops changing
|
|
3710
|
+
*
|
|
3711
|
+
* @example
|
|
3712
|
+
* ```jsx
|
|
3713
|
+
*import { useCallback } from 'react';
|
|
3714
|
+
*import { useOnViewportChange } from '@xyflow/react';
|
|
3715
|
+
*
|
|
3716
|
+
*function ViewportChangeLogger() {
|
|
3717
|
+
* useOnViewportChange({
|
|
3718
|
+
* onStart: (viewport: Viewport) => console.log('start', viewport),
|
|
3719
|
+
* onChange: (viewport: Viewport) => console.log('change', viewport),
|
|
3720
|
+
* onEnd: (viewport: Viewport) => console.log('end', viewport),
|
|
3721
|
+
* });
|
|
3722
|
+
*
|
|
3723
|
+
* return null;
|
|
3724
|
+
*}
|
|
3725
|
+
*```
|
|
3109
3726
|
*/
|
|
3110
3727
|
function useOnViewportChange({ onStart, onChange, onEnd }) {
|
|
3111
3728
|
const store = useStoreApi();
|
|
@@ -3121,10 +3738,42 @@ function useOnViewportChange({ onStart, onChange, onEnd }) {
|
|
|
3121
3738
|
}
|
|
3122
3739
|
|
|
3123
3740
|
/**
|
|
3124
|
-
*
|
|
3741
|
+
* This hook lets you listen for changes to both node and edge selection. As the
|
|
3742
|
+
*name implies, the callback you provide will be called whenever the selection of
|
|
3743
|
+
*_either_ nodes or edges changes.
|
|
3125
3744
|
*
|
|
3126
3745
|
* @public
|
|
3127
3746
|
* @param params.onChange - The handler to register
|
|
3747
|
+
*
|
|
3748
|
+
* @example
|
|
3749
|
+
* ```jsx
|
|
3750
|
+
*import { useState } from 'react';
|
|
3751
|
+
*import { ReactFlow, useOnSelectionChange } from '@xyflow/react';
|
|
3752
|
+
*
|
|
3753
|
+
*function SelectionDisplay() {
|
|
3754
|
+
* const [selectedNodes, setSelectedNodes] = useState([]);
|
|
3755
|
+
* const [selectedEdges, setSelectedEdges] = useState([]);
|
|
3756
|
+
*
|
|
3757
|
+
* // the passed handler has to be memoized, otherwise the hook will not work correctly
|
|
3758
|
+
* const onChange = useCallback(({ nodes, edges }) => {
|
|
3759
|
+
* setSelectedNodes(nodes.map((node) => node.id));
|
|
3760
|
+
* setSelectedEdges(edges.map((edge) => edge.id));
|
|
3761
|
+
* }, []);
|
|
3762
|
+
*
|
|
3763
|
+
* useOnSelectionChange({
|
|
3764
|
+
* onChange,
|
|
3765
|
+
* });
|
|
3766
|
+
*
|
|
3767
|
+
* return (
|
|
3768
|
+
* <div>
|
|
3769
|
+
* <p>Selected nodes: {selectedNodes.join(', ')}</p>
|
|
3770
|
+
* <p>Selected edges: {selectedEdges.join(', ')}</p>
|
|
3771
|
+
* </div>
|
|
3772
|
+
* );
|
|
3773
|
+
*}
|
|
3774
|
+
*```
|
|
3775
|
+
*
|
|
3776
|
+
* @remarks You need to memoize the passed `onChange` handler, otherwise the hook will not work correctly.
|
|
3128
3777
|
*/
|
|
3129
3778
|
function useOnSelectionChange({ onChange }) {
|
|
3130
3779
|
const store = useStoreApi();
|
|
@@ -3151,17 +3800,42 @@ const selector$4 = (options) => (s) => {
|
|
|
3151
3800
|
}
|
|
3152
3801
|
return true;
|
|
3153
3802
|
};
|
|
3154
|
-
const defaultOptions = {
|
|
3155
|
-
includeHiddenNodes: false,
|
|
3156
|
-
};
|
|
3157
3803
|
/**
|
|
3158
|
-
*
|
|
3804
|
+
* This hook tells you whether all the nodes in a flow have been measured and given
|
|
3805
|
+
*a width and height. When you add a node to the flow, this hook will return
|
|
3806
|
+
*`false` and then `true` again once the node has been measured.
|
|
3159
3807
|
*
|
|
3160
3808
|
* @public
|
|
3161
3809
|
* @param options.includeHiddenNodes - defaults to false
|
|
3162
3810
|
* @returns boolean indicating whether all nodes are initialized
|
|
3811
|
+
*
|
|
3812
|
+
* @example
|
|
3813
|
+
* ```jsx
|
|
3814
|
+
*import { useReactFlow, useNodesInitialized } from '@xyflow/react';
|
|
3815
|
+
*import { useEffect, useState } from 'react';
|
|
3816
|
+
*
|
|
3817
|
+
*const options = {
|
|
3818
|
+
* includeHiddenNodes: false,
|
|
3819
|
+
*};
|
|
3820
|
+
*
|
|
3821
|
+
*export default function useLayout() {
|
|
3822
|
+
* const { getNodes } = useReactFlow();
|
|
3823
|
+
* const nodesInitialized = useNodesInitialized(options);
|
|
3824
|
+
* const [layoutedNodes, setLayoutedNodes] = useState(getNodes());
|
|
3825
|
+
*
|
|
3826
|
+
* useEffect(() => {
|
|
3827
|
+
* if (nodesInitialized) {
|
|
3828
|
+
* setLayoutedNodes(yourLayoutingFunction(getNodes()));
|
|
3829
|
+
* }
|
|
3830
|
+
* }, [nodesInitialized]);
|
|
3831
|
+
*
|
|
3832
|
+
* return layoutedNodes;
|
|
3833
|
+
*}
|
|
3834
|
+
*```
|
|
3163
3835
|
*/
|
|
3164
|
-
function useNodesInitialized(options =
|
|
3836
|
+
function useNodesInitialized(options = {
|
|
3837
|
+
includeHiddenNodes: false,
|
|
3838
|
+
}) {
|
|
3165
3839
|
const initialized = useStore(selector$4(options));
|
|
3166
3840
|
return initialized;
|
|
3167
3841
|
}
|
|
@@ -3198,7 +3872,7 @@ function useHandleConnections({ type, id, nodeId, onConnect, onDisconnect, }) {
|
|
|
3198
3872
|
|
|
3199
3873
|
const error014 = errorMessages['error014']();
|
|
3200
3874
|
/**
|
|
3201
|
-
*
|
|
3875
|
+
* This hook returns an array of connections on a specific node, handle type ('source', 'target') or handle ID.
|
|
3202
3876
|
*
|
|
3203
3877
|
* @public
|
|
3204
3878
|
* @param param.id - node id - optional if called inside a custom node
|
|
@@ -3207,6 +3881,22 @@ const error014 = errorMessages['error014']();
|
|
|
3207
3881
|
* @param param.onConnect - gets called when a connection is established
|
|
3208
3882
|
* @param param.onDisconnect - gets called when a connection is removed
|
|
3209
3883
|
* @returns an array with connections
|
|
3884
|
+
*
|
|
3885
|
+
* @example
|
|
3886
|
+
* ```jsx
|
|
3887
|
+
*import { useNodeConnections } from '@xyflow/react';
|
|
3888
|
+
*
|
|
3889
|
+
*export default function () {
|
|
3890
|
+
* const connections = useNodeConnections({
|
|
3891
|
+
* type: 'target',
|
|
3892
|
+
* handleId: 'my-handle',
|
|
3893
|
+
* });
|
|
3894
|
+
*
|
|
3895
|
+
* return (
|
|
3896
|
+
* <div>There are currently {connections.length} incoming connections!</div>
|
|
3897
|
+
* );
|
|
3898
|
+
*}
|
|
3899
|
+
*```
|
|
3210
3900
|
*/
|
|
3211
3901
|
function useNodeConnections({ id, handleType, handleId, onConnect, onDisconnect, } = {}) {
|
|
3212
3902
|
const nodeId = useNodeId();
|
|
@@ -3250,11 +3940,31 @@ function useNodesData(nodeIds) {
|
|
|
3250
3940
|
}
|
|
3251
3941
|
|
|
3252
3942
|
/**
|
|
3253
|
-
*
|
|
3943
|
+
* This hook returns the internal representation of a specific node.
|
|
3944
|
+
* Components that use this hook will re-render **whenever the node changes**,
|
|
3945
|
+
* including when a node is selected or moved.
|
|
3254
3946
|
*
|
|
3255
3947
|
* @public
|
|
3256
3948
|
* @param id - id of the node
|
|
3257
3949
|
* @returns array with visible node ids
|
|
3950
|
+
*
|
|
3951
|
+
* @example
|
|
3952
|
+
* ```tsx
|
|
3953
|
+
*import { useInternalNode } from '@xyflow/react';
|
|
3954
|
+
*
|
|
3955
|
+
*export default function () {
|
|
3956
|
+
* const internalNode = useInternalNode('node-1');
|
|
3957
|
+
* const absolutePosition = internalNode.internals.positionAbsolute;
|
|
3958
|
+
*
|
|
3959
|
+
* return (
|
|
3960
|
+
* <div>
|
|
3961
|
+
* The absolute position of the node is at:
|
|
3962
|
+
* <p>x: {absolutePosition.x}</p>
|
|
3963
|
+
* <p>y: {absolutePosition.y}</p>
|
|
3964
|
+
* </div>
|
|
3965
|
+
* );
|
|
3966
|
+
*}
|
|
3967
|
+
*```
|
|
3258
3968
|
*/
|
|
3259
3969
|
function useInternalNode(id) {
|
|
3260
3970
|
const node = useStore(useCallback((s) => s.nodeLookup.get(id), [id]), shallow);
|
|
@@ -3268,6 +3978,12 @@ function DotPattern({ radius, className }) {
|
|
|
3268
3978
|
return (jsx("circle", { cx: radius, cy: radius, r: radius, className: cc(['react-flow__background-pattern', 'dots', className]) }));
|
|
3269
3979
|
}
|
|
3270
3980
|
|
|
3981
|
+
/**
|
|
3982
|
+
* The three variants are exported as an enum for convenience. You can either import
|
|
3983
|
+
* the enum and use it like `BackgroundVariant.Lines` or you can use the raw string
|
|
3984
|
+
* value directly.
|
|
3985
|
+
* @public
|
|
3986
|
+
*/
|
|
3271
3987
|
var BackgroundVariant;
|
|
3272
3988
|
(function (BackgroundVariant) {
|
|
3273
3989
|
BackgroundVariant["Lines"] = "lines";
|
|
@@ -3309,6 +4025,59 @@ size, lineWidth = 1, offset = 0, color, bgColor, style, className, patternClassN
|
|
|
3309
4025
|
}, 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
4026
|
}
|
|
3311
4027
|
BackgroundComponent.displayName = 'Background';
|
|
4028
|
+
/**
|
|
4029
|
+
* 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.
|
|
4030
|
+
*
|
|
4031
|
+
* @example
|
|
4032
|
+
*
|
|
4033
|
+
* A simple example of how to use the Background component.
|
|
4034
|
+
*
|
|
4035
|
+
* ```tsx
|
|
4036
|
+
* import { useState } from 'react';
|
|
4037
|
+
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
|
|
4038
|
+
*
|
|
4039
|
+
* export default function Flow() {
|
|
4040
|
+
* return (
|
|
4041
|
+
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
|
|
4042
|
+
* <Background color="#ccc" variant={BackgroundVariant.Dots} />
|
|
4043
|
+
* </ReactFlow>
|
|
4044
|
+
* );
|
|
4045
|
+
* }
|
|
4046
|
+
* ```
|
|
4047
|
+
*
|
|
4048
|
+
* @example
|
|
4049
|
+
*
|
|
4050
|
+
* In this example you can see how to combine multiple backgrounds
|
|
4051
|
+
*
|
|
4052
|
+
* ```tsx
|
|
4053
|
+
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
|
|
4054
|
+
* import '@xyflow/react/dist/style.css';
|
|
4055
|
+
*
|
|
4056
|
+
* export default function Flow() {
|
|
4057
|
+
* return (
|
|
4058
|
+
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
|
|
4059
|
+
* <Background
|
|
4060
|
+
* id="1"
|
|
4061
|
+
* gap={10}
|
|
4062
|
+
* color="#f1f1f1"
|
|
4063
|
+
* variant={BackgroundVariant.Lines}
|
|
4064
|
+
* />
|
|
4065
|
+
* <Background
|
|
4066
|
+
* id="2"
|
|
4067
|
+
* gap={100}
|
|
4068
|
+
* color="#ccc"
|
|
4069
|
+
* variant={BackgroundVariant.Lines}
|
|
4070
|
+
* />
|
|
4071
|
+
* </ReactFlow>
|
|
4072
|
+
* );
|
|
4073
|
+
* }
|
|
4074
|
+
* ```
|
|
4075
|
+
*
|
|
4076
|
+
* @remarks
|
|
4077
|
+
*
|
|
4078
|
+
* When combining multiple <Background /> components it’s important to give each of them a unique id prop!
|
|
4079
|
+
*
|
|
4080
|
+
*/
|
|
3312
4081
|
const Background = memo(BackgroundComponent);
|
|
3313
4082
|
|
|
3314
4083
|
function PlusIcon() {
|
|
@@ -3331,6 +4100,29 @@ function UnlockIcon() {
|
|
|
3331
4100
|
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
4101
|
}
|
|
3333
4102
|
|
|
4103
|
+
/**
|
|
4104
|
+
* You can add buttons to the control panel by using the `<ControlButton />` component
|
|
4105
|
+
* and pass it as a child to the [`<Controls />`](/api-reference/components/controls) component.
|
|
4106
|
+
*
|
|
4107
|
+
* @public
|
|
4108
|
+
* @example
|
|
4109
|
+
*```jsx
|
|
4110
|
+
*import { MagicWand } from '@radix-ui/react-icons'
|
|
4111
|
+
*import { ReactFlow, Controls, ControlButton } from '@xyflow/react'
|
|
4112
|
+
*
|
|
4113
|
+
*export default function Flow() {
|
|
4114
|
+
* return (
|
|
4115
|
+
* <ReactFlow nodes={[...]} edges={[...]}>
|
|
4116
|
+
* <Controls>
|
|
4117
|
+
* <ControlButton onClick={() => alert('Something magical just happened. ✨')}>
|
|
4118
|
+
* <MagicWand />
|
|
4119
|
+
* </ControlButton>
|
|
4120
|
+
* </Controls>
|
|
4121
|
+
* </ReactFlow>
|
|
4122
|
+
* )
|
|
4123
|
+
*}
|
|
4124
|
+
*```
|
|
4125
|
+
*/
|
|
3334
4126
|
function ControlButton({ children, className, ...rest }) {
|
|
3335
4127
|
return (jsx("button", { type: "button", className: cc(['react-flow__controls-button', className]), ...rest, children: children }));
|
|
3336
4128
|
}
|
|
@@ -3368,6 +4160,27 @@ function ControlsComponent({ style, showZoom = true, showFitView = true, showInt
|
|
|
3368
4160
|
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
4161
|
}
|
|
3370
4162
|
ControlsComponent.displayName = 'Controls';
|
|
4163
|
+
/**
|
|
4164
|
+
* The `<Controls />` component renders a small panel that contains convenient
|
|
4165
|
+
* buttons to zoom in, zoom out, fit the view, and lock the viewport.
|
|
4166
|
+
*
|
|
4167
|
+
* @public
|
|
4168
|
+
* @example
|
|
4169
|
+
*```tsx
|
|
4170
|
+
*import { ReactFlow, Controls } from '@xyflow/react'
|
|
4171
|
+
*
|
|
4172
|
+
*export default function Flow() {
|
|
4173
|
+
* return (
|
|
4174
|
+
* <ReactFlow nodes={[...]} edges={[...]}>
|
|
4175
|
+
* <Controls />
|
|
4176
|
+
* </ReactFlow>
|
|
4177
|
+
* )
|
|
4178
|
+
*}
|
|
4179
|
+
*```
|
|
4180
|
+
*
|
|
4181
|
+
* @remarks To extend or customise the controls, you can use the [`<ControlButton />`](/api-reference/components/control-button) component
|
|
4182
|
+
*
|
|
4183
|
+
*/
|
|
3371
4184
|
const Controls = memo(ControlsComponent);
|
|
3372
4185
|
|
|
3373
4186
|
function MiniMapNodeComponent({ id, x, y, width, height, style, color, strokeColor, strokeWidth, className, borderRadius, shapeRendering, selected, onClick, }) {
|
|
@@ -3384,8 +4197,10 @@ const MiniMapNode = memo(MiniMapNodeComponent);
|
|
|
3384
4197
|
const selectorNodeIds = (s) => s.nodes.map((node) => node.id);
|
|
3385
4198
|
const getAttrFunction = (func) => func instanceof Function ? func : () => func;
|
|
3386
4199
|
function MiniMapNodes({ nodeStrokeColor, nodeColor, nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth,
|
|
3387
|
-
|
|
3388
|
-
|
|
4200
|
+
/*
|
|
4201
|
+
* We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
|
4202
|
+
* a component properly.
|
|
4203
|
+
*/
|
|
3389
4204
|
nodeComponent: NodeComponent = MiniMapNode, onClick, }) {
|
|
3390
4205
|
const nodeIds = useStore(selectorNodeIds, shallow);
|
|
3391
4206
|
const nodeColorFunc = getAttrFunction(nodeColor);
|
|
@@ -3393,11 +4208,13 @@ nodeComponent: NodeComponent = MiniMapNode, onClick, }) {
|
|
|
3393
4208
|
const nodeClassNameFunc = getAttrFunction(nodeClassName);
|
|
3394
4209
|
const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
|
|
3395
4210
|
return (jsx(Fragment, { children: nodeIds.map((nodeId) => (
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
4211
|
+
/*
|
|
4212
|
+
* The split of responsibilities between MiniMapNodes and
|
|
4213
|
+
* NodeComponentWrapper may appear weird. However, it’s designed to
|
|
4214
|
+
* minimize the cost of updates when individual nodes change.
|
|
4215
|
+
*
|
|
4216
|
+
* For more details, see a similar commit in `NodeRenderer/index.tsx`.
|
|
4217
|
+
*/
|
|
3401
4218
|
jsx(NodeComponentWrapper, { id: nodeId, nodeColorFunc: nodeColorFunc, nodeStrokeColorFunc: nodeStrokeColorFunc, nodeClassNameFunc: nodeClassNameFunc, nodeBorderRadius: nodeBorderRadius, nodeStrokeWidth: nodeStrokeWidth, NodeComponent: NodeComponent, onClick: onClick, shapeRendering: shapeRendering }, nodeId))) }));
|
|
3402
4219
|
}
|
|
3403
4220
|
function NodeComponentWrapperInner({ id, nodeColorFunc, nodeStrokeColorFunc, nodeClassNameFunc, nodeBorderRadius, nodeStrokeWidth, shapeRendering, NodeComponent, onClick, }) {
|
|
@@ -3442,8 +4259,10 @@ const selector$1 = (s) => {
|
|
|
3442
4259
|
};
|
|
3443
4260
|
const ARIA_LABEL_KEY = 'react-flow__minimap-desc';
|
|
3444
4261
|
function MiniMapComponent({ style, className, nodeStrokeColor, nodeColor, nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth,
|
|
3445
|
-
|
|
3446
|
-
|
|
4262
|
+
/*
|
|
4263
|
+
* We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
|
4264
|
+
* a component properly.
|
|
4265
|
+
*/
|
|
3447
4266
|
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
4267
|
const store = useStoreApi();
|
|
3449
4268
|
const svg = useRef(null);
|
|
@@ -3513,6 +4332,26 @@ nodeComponent, bgColor, maskColor, maskStrokeColor, maskStrokeWidth, position =
|
|
|
3513
4332
|
M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`, fillRule: "evenodd", pointerEvents: "none" })] }) }));
|
|
3514
4333
|
}
|
|
3515
4334
|
MiniMapComponent.displayName = 'MiniMap';
|
|
4335
|
+
/**
|
|
4336
|
+
* The `<MiniMap />` component can be used to render an overview of your flow. It
|
|
4337
|
+
* renders each node as an SVG element and visualizes where the current viewport is
|
|
4338
|
+
* in relation to the rest of the flow.
|
|
4339
|
+
*
|
|
4340
|
+
* @public
|
|
4341
|
+
* @example
|
|
4342
|
+
*
|
|
4343
|
+
* ```jsx
|
|
4344
|
+
*import { ReactFlow, MiniMap } from '@xyflow/react';
|
|
4345
|
+
*
|
|
4346
|
+
*export default function Flow() {
|
|
4347
|
+
* return (
|
|
4348
|
+
* <ReactFlow nodes={[...]]} edges={[...]]}>
|
|
4349
|
+
* <MiniMap nodeStrokeWidth={3} />
|
|
4350
|
+
* </ReactFlow>
|
|
4351
|
+
* );
|
|
4352
|
+
*}
|
|
4353
|
+
*```
|
|
4354
|
+
*/
|
|
3516
4355
|
const MiniMap = memo(MiniMapComponent);
|
|
3517
4356
|
|
|
3518
4357
|
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, }) {
|
|
@@ -3565,8 +4404,10 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
|
|
|
3565
4404
|
};
|
|
3566
4405
|
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
|
|
3567
4406
|
changes.push(...parentExpandChanges);
|
|
3568
|
-
|
|
3569
|
-
|
|
4407
|
+
/*
|
|
4408
|
+
* when the parent was expanded by the child node, its position will be clamped at
|
|
4409
|
+
* 0,0 when node origin is 0,0 and to width, height if it's 1,1
|
|
4410
|
+
*/
|
|
3570
4411
|
nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : undefined;
|
|
3571
4412
|
nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : undefined;
|
|
3572
4413
|
}
|
|
@@ -3644,8 +4485,37 @@ function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle
|
|
|
3644
4485
|
const controlStyle = color ? { ...style, [colorStyleProp]: color } : style;
|
|
3645
4486
|
return (jsx("div", { className: cc(['react-flow__resize-control', 'nodrag', ...positionClassNames, variant, className]), ref: resizeControlRef, style: controlStyle, children: children }));
|
|
3646
4487
|
}
|
|
4488
|
+
/**
|
|
4489
|
+
* To create your own resizing UI, you can use the `NodeResizeControl` component where you can pass children (such as icons).
|
|
4490
|
+
* @public
|
|
4491
|
+
*
|
|
4492
|
+
*/
|
|
3647
4493
|
const NodeResizeControl = memo(ResizeControl);
|
|
3648
4494
|
|
|
4495
|
+
/**
|
|
4496
|
+
* The `<NodeResizer />` component can be used to add a resize functionality to your
|
|
4497
|
+
* nodes. It renders draggable controls around the node to resize in all directions.
|
|
4498
|
+
* @public
|
|
4499
|
+
*
|
|
4500
|
+
* @example
|
|
4501
|
+
*```jsx
|
|
4502
|
+
*import { memo } from 'react';
|
|
4503
|
+
*import { Handle, Position, NodeResizer } from '@xyflow/react';
|
|
4504
|
+
*
|
|
4505
|
+
*function ResizableNode({ data }) {
|
|
4506
|
+
* return (
|
|
4507
|
+
* <>
|
|
4508
|
+
* <NodeResizer minWidth={100} minHeight={30} />
|
|
4509
|
+
* <Handle type="target" position={Position.Left} />
|
|
4510
|
+
* <div style={{ padding: 10 }}>{data.label}</div>
|
|
4511
|
+
* <Handle type="source" position={Position.Right} />
|
|
4512
|
+
* </>
|
|
4513
|
+
* );
|
|
4514
|
+
*};
|
|
4515
|
+
*
|
|
4516
|
+
*export default memo(ResizableNode);
|
|
4517
|
+
*```
|
|
4518
|
+
*/
|
|
3649
4519
|
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
4520
|
if (!isVisible) {
|
|
3651
4521
|
return null;
|
|
@@ -3685,6 +4555,41 @@ const storeSelector = (state) => ({
|
|
|
3685
4555
|
zoom: state.transform[2],
|
|
3686
4556
|
selectedNodesCount: state.nodes.filter((node) => node.selected).length,
|
|
3687
4557
|
});
|
|
4558
|
+
/**
|
|
4559
|
+
* This component can render a toolbar or tooltip to one side of a custom node. This
|
|
4560
|
+
* toolbar doesn't scale with the viewport so that the content is always visible.
|
|
4561
|
+
*
|
|
4562
|
+
* @public
|
|
4563
|
+
* @example
|
|
4564
|
+
* ```jsx
|
|
4565
|
+
*import { memo } from 'react';
|
|
4566
|
+
*import { Handle, Position, NodeToolbar } from '@xyflow/react';
|
|
4567
|
+
*
|
|
4568
|
+
*function CustomNode({ data }) {
|
|
4569
|
+
* return (
|
|
4570
|
+
* <>
|
|
4571
|
+
* <NodeToolbar isVisible={data.toolbarVisible} position={data.toolbarPosition}>
|
|
4572
|
+
* <button>delete</button>
|
|
4573
|
+
* <button>copy</button>
|
|
4574
|
+
* <button>expand</button>
|
|
4575
|
+
* </NodeToolbar>
|
|
4576
|
+
*
|
|
4577
|
+
* <div style={{ padding: '10px 20px' }}>
|
|
4578
|
+
* {data.label}
|
|
4579
|
+
* </div>
|
|
4580
|
+
*
|
|
4581
|
+
* <Handle type="target" position={Position.Left} />
|
|
4582
|
+
* <Handle type="source" position={Position.Right} />
|
|
4583
|
+
* </>
|
|
4584
|
+
* );
|
|
4585
|
+
*};
|
|
4586
|
+
*
|
|
4587
|
+
*export default memo(CustomNode);
|
|
4588
|
+
*```
|
|
4589
|
+
* @remarks By default, the toolbar is only visible when a node is selected. If multiple
|
|
4590
|
+
* nodes are selected it will not be visible to prevent overlapping toolbars or
|
|
4591
|
+
* clutter. You can override this behavior by setting the `isVisible` prop to `true`.
|
|
4592
|
+
*/
|
|
3688
4593
|
function NodeToolbar({ nodeId, children, className, style, isVisible, position = Position.Top, offset = 10, align = 'center', ...rest }) {
|
|
3689
4594
|
const contextNodeId = useNodeId();
|
|
3690
4595
|
const nodesSelector = useCallback((state) => {
|
|
@@ -3703,7 +4608,7 @@ function NodeToolbar({ nodeId, children, className, style, isVisible, position =
|
|
|
3703
4608
|
// if isVisible is not set, we show the toolbar only if its node is selected and no other node is selected
|
|
3704
4609
|
const isActive = typeof isVisible === 'boolean'
|
|
3705
4610
|
? isVisible
|
|
3706
|
-
: nodes.size === 1 && nodes.values().next().value
|
|
4611
|
+
: nodes.size === 1 && nodes.values().next().value?.selected && selectedNodesCount === 1;
|
|
3707
4612
|
if (!isActive || !nodes.size) {
|
|
3708
4613
|
return null;
|
|
3709
4614
|
}
|