@d2m/reactflow-core 11.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +10 -0
- package/dist/base.css +243 -0
- package/dist/esm/components/A11yDescriptions/index.d.ts +10 -0
- package/dist/esm/components/A11yDescriptions/index.d.ts.map +1 -0
- package/dist/esm/components/Attribution/index.d.ts +9 -0
- package/dist/esm/components/Attribution/index.d.ts.map +1 -0
- package/dist/esm/components/ConnectionLine/index.d.ts +12 -0
- package/dist/esm/components/ConnectionLine/index.d.ts.map +1 -0
- package/dist/esm/components/EdgeLabelRenderer/index.d.ts +6 -0
- package/dist/esm/components/EdgeLabelRenderer/index.d.ts.map +1 -0
- package/dist/esm/components/Edges/BaseEdge.d.ts +8 -0
- package/dist/esm/components/Edges/BaseEdge.d.ts.map +1 -0
- package/dist/esm/components/Edges/BezierEdge.d.ts +16 -0
- package/dist/esm/components/Edges/BezierEdge.d.ts.map +1 -0
- package/dist/esm/components/Edges/EdgeAnchor.d.ts +14 -0
- package/dist/esm/components/Edges/EdgeAnchor.d.ts.map +1 -0
- package/dist/esm/components/Edges/EdgeText.d.ts +5 -0
- package/dist/esm/components/Edges/EdgeText.d.ts.map +1 -0
- package/dist/esm/components/Edges/SimpleBezierEdge.d.ts +15 -0
- package/dist/esm/components/Edges/SimpleBezierEdge.d.ts.map +1 -0
- package/dist/esm/components/Edges/SmoothStepEdge.d.ts +19 -0
- package/dist/esm/components/Edges/SmoothStepEdge.d.ts.map +1 -0
- package/dist/esm/components/Edges/StepEdge.d.ts +5 -0
- package/dist/esm/components/Edges/StepEdge.d.ts.map +1 -0
- package/dist/esm/components/Edges/StraightEdge.d.ts +12 -0
- package/dist/esm/components/Edges/StraightEdge.d.ts.map +1 -0
- package/dist/esm/components/Edges/index.d.ts +6 -0
- package/dist/esm/components/Edges/index.d.ts.map +1 -0
- package/dist/esm/components/Edges/utils.d.ts +22 -0
- package/dist/esm/components/Edges/utils.d.ts.map +1 -0
- package/dist/esm/components/Edges/wrapEdge.d.ts +9 -0
- package/dist/esm/components/Edges/wrapEdge.d.ts.map +1 -0
- package/dist/esm/components/Handle/handler.d.ts +17 -0
- package/dist/esm/components/Handle/handler.d.ts.map +1 -0
- package/dist/esm/components/Handle/index.d.ts +6 -0
- package/dist/esm/components/Handle/index.d.ts.map +1 -0
- package/dist/esm/components/Handle/utils.d.ts +49 -0
- package/dist/esm/components/Handle/utils.d.ts.map +1 -0
- package/dist/esm/components/Nodes/DefaultNode.d.ts +8 -0
- package/dist/esm/components/Nodes/DefaultNode.d.ts.map +1 -0
- package/dist/esm/components/Nodes/GroupNode.d.ts +6 -0
- package/dist/esm/components/Nodes/GroupNode.d.ts.map +1 -0
- package/dist/esm/components/Nodes/InputNode.d.ts +8 -0
- package/dist/esm/components/Nodes/InputNode.d.ts.map +1 -0
- package/dist/esm/components/Nodes/OutputNode.d.ts +8 -0
- package/dist/esm/components/Nodes/OutputNode.d.ts.map +1 -0
- package/dist/esm/components/Nodes/utils.d.ts +15 -0
- package/dist/esm/components/Nodes/utils.d.ts.map +1 -0
- package/dist/esm/components/Nodes/wrapNode.d.ts +10 -0
- package/dist/esm/components/Nodes/wrapNode.d.ts.map +1 -0
- package/dist/esm/components/NodesSelection/index.d.ts +16 -0
- package/dist/esm/components/NodesSelection/index.d.ts.map +1 -0
- package/dist/esm/components/Panel/index.d.ts +9 -0
- package/dist/esm/components/Panel/index.d.ts.map +1 -0
- package/dist/esm/components/ReactFlowProvider/index.d.ts +4 -0
- package/dist/esm/components/ReactFlowProvider/index.d.ts.map +1 -0
- package/dist/esm/components/SelectionListener/index.d.ts +8 -0
- package/dist/esm/components/SelectionListener/index.d.ts.map +1 -0
- package/dist/esm/components/StoreUpdater/index.d.ts +7 -0
- package/dist/esm/components/StoreUpdater/index.d.ts.map +1 -0
- package/dist/esm/components/UserSelection/index.d.ts +4 -0
- package/dist/esm/components/UserSelection/index.d.ts.map +1 -0
- package/dist/esm/container/EdgeRenderer/MarkerDefinitions.d.ts +11 -0
- package/dist/esm/container/EdgeRenderer/MarkerDefinitions.d.ts.map +1 -0
- package/dist/esm/container/EdgeRenderer/MarkerSymbols.d.ts +11 -0
- package/dist/esm/container/EdgeRenderer/MarkerSymbols.d.ts.map +1 -0
- package/dist/esm/container/EdgeRenderer/index.d.ts +14 -0
- package/dist/esm/container/EdgeRenderer/index.d.ts.map +1 -0
- package/dist/esm/container/EdgeRenderer/utils.d.ts +28 -0
- package/dist/esm/container/EdgeRenderer/utils.d.ts.map +1 -0
- package/dist/esm/container/FlowRenderer/index.d.ts +12 -0
- package/dist/esm/container/FlowRenderer/index.d.ts.map +1 -0
- package/dist/esm/container/GraphView/index.d.ts +11 -0
- package/dist/esm/container/GraphView/index.d.ts.map +1 -0
- package/dist/esm/container/GraphView/utils.d.ts +6 -0
- package/dist/esm/container/GraphView/utils.d.ts.map +1 -0
- package/dist/esm/container/NodeRenderer/index.d.ts +12 -0
- package/dist/esm/container/NodeRenderer/index.d.ts.map +1 -0
- package/dist/esm/container/NodeRenderer/utils.d.ts +11 -0
- package/dist/esm/container/NodeRenderer/utils.d.ts.map +1 -0
- package/dist/esm/container/Pane/index.d.ts +12 -0
- package/dist/esm/container/Pane/index.d.ts.map +1 -0
- package/dist/esm/container/ReactFlow/Wrapper.d.ts +4 -0
- package/dist/esm/container/ReactFlow/Wrapper.d.ts.map +1 -0
- package/dist/esm/container/ReactFlow/index.d.ts +118 -0
- package/dist/esm/container/ReactFlow/index.d.ts.map +1 -0
- package/dist/esm/container/Viewport/index.d.ts +7 -0
- package/dist/esm/container/Viewport/index.d.ts.map +1 -0
- package/dist/esm/container/ZoomPane/index.d.ts +6 -0
- package/dist/esm/container/ZoomPane/index.d.ts.map +1 -0
- package/dist/esm/contants.d.ts +16 -0
- package/dist/esm/contants.d.ts.map +1 -0
- package/dist/esm/contexts/NodeIdContext.d.ts +7 -0
- package/dist/esm/contexts/NodeIdContext.d.ts.map +1 -0
- package/dist/esm/contexts/RFStoreContext.d.ts +5 -0
- package/dist/esm/contexts/RFStoreContext.d.ts.map +1 -0
- package/dist/esm/hooks/useDrag/index.d.ts +17 -0
- package/dist/esm/hooks/useDrag/index.d.ts.map +1 -0
- package/dist/esm/hooks/useDrag/utils.d.ts +15 -0
- package/dist/esm/hooks/useDrag/utils.d.ts.map +1 -0
- package/dist/esm/hooks/useEdges.d.ts +4 -0
- package/dist/esm/hooks/useEdges.d.ts.map +1 -0
- package/dist/esm/hooks/useGetPointerPosition.d.ts +9 -0
- package/dist/esm/hooks/useGetPointerPosition.d.ts.map +1 -0
- package/dist/esm/hooks/useGlobalKeyHandler.d.ts +8 -0
- package/dist/esm/hooks/useGlobalKeyHandler.d.ts.map +1 -0
- package/dist/esm/hooks/useKeyPress.d.ts +8 -0
- package/dist/esm/hooks/useKeyPress.d.ts.map +1 -0
- package/dist/esm/hooks/useNodes.d.ts +4 -0
- package/dist/esm/hooks/useNodes.d.ts.map +1 -0
- package/dist/esm/hooks/useNodesEdgesState.d.ts +7 -0
- package/dist/esm/hooks/useNodesEdgesState.d.ts.map +1 -0
- package/dist/esm/hooks/useNodesInitialized.d.ts +6 -0
- package/dist/esm/hooks/useNodesInitialized.d.ts.map +1 -0
- package/dist/esm/hooks/useOnInitHandler.d.ts +4 -0
- package/dist/esm/hooks/useOnInitHandler.d.ts.map +1 -0
- package/dist/esm/hooks/useOnSelectionChange.d.ts +7 -0
- package/dist/esm/hooks/useOnSelectionChange.d.ts.map +1 -0
- package/dist/esm/hooks/useOnViewportChange.d.ts +9 -0
- package/dist/esm/hooks/useOnViewportChange.d.ts.map +1 -0
- package/dist/esm/hooks/useReactFlow.d.ts +3 -0
- package/dist/esm/hooks/useReactFlow.d.ts.map +1 -0
- package/dist/esm/hooks/useResizeHandler.d.ts +4 -0
- package/dist/esm/hooks/useResizeHandler.d.ts.map +1 -0
- package/dist/esm/hooks/useStore.d.ts +14 -0
- package/dist/esm/hooks/useStore.d.ts.map +1 -0
- package/dist/esm/hooks/useUpdateNodeInternals.d.ts +4 -0
- package/dist/esm/hooks/useUpdateNodeInternals.d.ts.map +1 -0
- package/dist/esm/hooks/useUpdateNodePositions.d.ts +7 -0
- package/dist/esm/hooks/useUpdateNodePositions.d.ts.map +1 -0
- package/dist/esm/hooks/useViewport.d.ts +4 -0
- package/dist/esm/hooks/useViewport.d.ts.map +1 -0
- package/dist/esm/hooks/useViewportHelper.d.ts +4 -0
- package/dist/esm/hooks/useViewportHelper.d.ts.map +1 -0
- package/dist/esm/hooks/useVisibleEdges.d.ts +8 -0
- package/dist/esm/hooks/useVisibleEdges.d.ts.map +1 -0
- package/dist/esm/hooks/useVisibleNodes.d.ts +3 -0
- package/dist/esm/hooks/useVisibleNodes.d.ts.map +1 -0
- package/dist/esm/index.d.ts +31 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +4209 -0
- package/dist/esm/index.mjs +4209 -0
- package/dist/esm/store/index.d.ts +4 -0
- package/dist/esm/store/index.d.ts.map +1 -0
- package/dist/esm/store/initialState.d.ts +5 -0
- package/dist/esm/store/initialState.d.ts.map +1 -0
- package/dist/esm/store/utils.d.ts +20 -0
- package/dist/esm/store/utils.d.ts.map +1 -0
- package/dist/esm/styles/index.d.ts +3 -0
- package/dist/esm/styles/index.d.ts.map +1 -0
- package/dist/esm/types/changes.d.ts +47 -0
- package/dist/esm/types/changes.d.ts.map +1 -0
- package/dist/esm/types/component-props.d.ts +130 -0
- package/dist/esm/types/component-props.d.ts.map +1 -0
- package/dist/esm/types/edges.d.ts +143 -0
- package/dist/esm/types/edges.d.ts.map +1 -0
- package/dist/esm/types/general.d.ts +232 -0
- package/dist/esm/types/general.d.ts.map +1 -0
- package/dist/esm/types/handles.d.ts +22 -0
- package/dist/esm/types/handles.d.ts.map +1 -0
- package/dist/esm/types/index.d.ts +9 -0
- package/dist/esm/types/index.d.ts.map +1 -0
- package/dist/esm/types/instance.d.ts +48 -0
- package/dist/esm/types/instance.d.ts.map +1 -0
- package/dist/esm/types/nodes.d.ts +100 -0
- package/dist/esm/types/nodes.d.ts.map +1 -0
- package/dist/esm/types/utils.d.ts +26 -0
- package/dist/esm/types/utils.d.ts.map +1 -0
- package/dist/esm/utils/changes.d.ts +11 -0
- package/dist/esm/utils/changes.d.ts.map +1 -0
- package/dist/esm/utils/graph.d.ts +27 -0
- package/dist/esm/utils/graph.d.ts.map +1 -0
- package/dist/esm/utils/index.d.ts +29 -0
- package/dist/esm/utils/index.d.ts.map +1 -0
- package/dist/style.css +275 -0
- package/dist/umd/components/A11yDescriptions/index.d.ts +10 -0
- package/dist/umd/components/A11yDescriptions/index.d.ts.map +1 -0
- package/dist/umd/components/Attribution/index.d.ts +9 -0
- package/dist/umd/components/Attribution/index.d.ts.map +1 -0
- package/dist/umd/components/ConnectionLine/index.d.ts +12 -0
- package/dist/umd/components/ConnectionLine/index.d.ts.map +1 -0
- package/dist/umd/components/EdgeLabelRenderer/index.d.ts +6 -0
- package/dist/umd/components/EdgeLabelRenderer/index.d.ts.map +1 -0
- package/dist/umd/components/Edges/BaseEdge.d.ts +8 -0
- package/dist/umd/components/Edges/BaseEdge.d.ts.map +1 -0
- package/dist/umd/components/Edges/BezierEdge.d.ts +16 -0
- package/dist/umd/components/Edges/BezierEdge.d.ts.map +1 -0
- package/dist/umd/components/Edges/EdgeAnchor.d.ts +14 -0
- package/dist/umd/components/Edges/EdgeAnchor.d.ts.map +1 -0
- package/dist/umd/components/Edges/EdgeText.d.ts +5 -0
- package/dist/umd/components/Edges/EdgeText.d.ts.map +1 -0
- package/dist/umd/components/Edges/SimpleBezierEdge.d.ts +15 -0
- package/dist/umd/components/Edges/SimpleBezierEdge.d.ts.map +1 -0
- package/dist/umd/components/Edges/SmoothStepEdge.d.ts +19 -0
- package/dist/umd/components/Edges/SmoothStepEdge.d.ts.map +1 -0
- package/dist/umd/components/Edges/StepEdge.d.ts +5 -0
- package/dist/umd/components/Edges/StepEdge.d.ts.map +1 -0
- package/dist/umd/components/Edges/StraightEdge.d.ts +12 -0
- package/dist/umd/components/Edges/StraightEdge.d.ts.map +1 -0
- package/dist/umd/components/Edges/index.d.ts +6 -0
- package/dist/umd/components/Edges/index.d.ts.map +1 -0
- package/dist/umd/components/Edges/utils.d.ts +22 -0
- package/dist/umd/components/Edges/utils.d.ts.map +1 -0
- package/dist/umd/components/Edges/wrapEdge.d.ts +9 -0
- package/dist/umd/components/Edges/wrapEdge.d.ts.map +1 -0
- package/dist/umd/components/Handle/handler.d.ts +17 -0
- package/dist/umd/components/Handle/handler.d.ts.map +1 -0
- package/dist/umd/components/Handle/index.d.ts +6 -0
- package/dist/umd/components/Handle/index.d.ts.map +1 -0
- package/dist/umd/components/Handle/utils.d.ts +49 -0
- package/dist/umd/components/Handle/utils.d.ts.map +1 -0
- package/dist/umd/components/Nodes/DefaultNode.d.ts +8 -0
- package/dist/umd/components/Nodes/DefaultNode.d.ts.map +1 -0
- package/dist/umd/components/Nodes/GroupNode.d.ts +6 -0
- package/dist/umd/components/Nodes/GroupNode.d.ts.map +1 -0
- package/dist/umd/components/Nodes/InputNode.d.ts +8 -0
- package/dist/umd/components/Nodes/InputNode.d.ts.map +1 -0
- package/dist/umd/components/Nodes/OutputNode.d.ts +8 -0
- package/dist/umd/components/Nodes/OutputNode.d.ts.map +1 -0
- package/dist/umd/components/Nodes/utils.d.ts +15 -0
- package/dist/umd/components/Nodes/utils.d.ts.map +1 -0
- package/dist/umd/components/Nodes/wrapNode.d.ts +10 -0
- package/dist/umd/components/Nodes/wrapNode.d.ts.map +1 -0
- package/dist/umd/components/NodesSelection/index.d.ts +16 -0
- package/dist/umd/components/NodesSelection/index.d.ts.map +1 -0
- package/dist/umd/components/Panel/index.d.ts +9 -0
- package/dist/umd/components/Panel/index.d.ts.map +1 -0
- package/dist/umd/components/ReactFlowProvider/index.d.ts +4 -0
- package/dist/umd/components/ReactFlowProvider/index.d.ts.map +1 -0
- package/dist/umd/components/SelectionListener/index.d.ts +8 -0
- package/dist/umd/components/SelectionListener/index.d.ts.map +1 -0
- package/dist/umd/components/StoreUpdater/index.d.ts +7 -0
- package/dist/umd/components/StoreUpdater/index.d.ts.map +1 -0
- package/dist/umd/components/UserSelection/index.d.ts +4 -0
- package/dist/umd/components/UserSelection/index.d.ts.map +1 -0
- package/dist/umd/container/EdgeRenderer/MarkerDefinitions.d.ts +11 -0
- package/dist/umd/container/EdgeRenderer/MarkerDefinitions.d.ts.map +1 -0
- package/dist/umd/container/EdgeRenderer/MarkerSymbols.d.ts +11 -0
- package/dist/umd/container/EdgeRenderer/MarkerSymbols.d.ts.map +1 -0
- package/dist/umd/container/EdgeRenderer/index.d.ts +14 -0
- package/dist/umd/container/EdgeRenderer/index.d.ts.map +1 -0
- package/dist/umd/container/EdgeRenderer/utils.d.ts +28 -0
- package/dist/umd/container/EdgeRenderer/utils.d.ts.map +1 -0
- package/dist/umd/container/FlowRenderer/index.d.ts +12 -0
- package/dist/umd/container/FlowRenderer/index.d.ts.map +1 -0
- package/dist/umd/container/GraphView/index.d.ts +11 -0
- package/dist/umd/container/GraphView/index.d.ts.map +1 -0
- package/dist/umd/container/GraphView/utils.d.ts +6 -0
- package/dist/umd/container/GraphView/utils.d.ts.map +1 -0
- package/dist/umd/container/NodeRenderer/index.d.ts +12 -0
- package/dist/umd/container/NodeRenderer/index.d.ts.map +1 -0
- package/dist/umd/container/NodeRenderer/utils.d.ts +11 -0
- package/dist/umd/container/NodeRenderer/utils.d.ts.map +1 -0
- package/dist/umd/container/Pane/index.d.ts +12 -0
- package/dist/umd/container/Pane/index.d.ts.map +1 -0
- package/dist/umd/container/ReactFlow/Wrapper.d.ts +4 -0
- package/dist/umd/container/ReactFlow/Wrapper.d.ts.map +1 -0
- package/dist/umd/container/ReactFlow/index.d.ts +118 -0
- package/dist/umd/container/ReactFlow/index.d.ts.map +1 -0
- package/dist/umd/container/Viewport/index.d.ts +7 -0
- package/dist/umd/container/Viewport/index.d.ts.map +1 -0
- package/dist/umd/container/ZoomPane/index.d.ts +6 -0
- package/dist/umd/container/ZoomPane/index.d.ts.map +1 -0
- package/dist/umd/contants.d.ts +16 -0
- package/dist/umd/contants.d.ts.map +1 -0
- package/dist/umd/contexts/NodeIdContext.d.ts +7 -0
- package/dist/umd/contexts/NodeIdContext.d.ts.map +1 -0
- package/dist/umd/contexts/RFStoreContext.d.ts +5 -0
- package/dist/umd/contexts/RFStoreContext.d.ts.map +1 -0
- package/dist/umd/hooks/useDrag/index.d.ts +17 -0
- package/dist/umd/hooks/useDrag/index.d.ts.map +1 -0
- package/dist/umd/hooks/useDrag/utils.d.ts +15 -0
- package/dist/umd/hooks/useDrag/utils.d.ts.map +1 -0
- package/dist/umd/hooks/useEdges.d.ts +4 -0
- package/dist/umd/hooks/useEdges.d.ts.map +1 -0
- package/dist/umd/hooks/useGetPointerPosition.d.ts +9 -0
- package/dist/umd/hooks/useGetPointerPosition.d.ts.map +1 -0
- package/dist/umd/hooks/useGlobalKeyHandler.d.ts +8 -0
- package/dist/umd/hooks/useGlobalKeyHandler.d.ts.map +1 -0
- package/dist/umd/hooks/useKeyPress.d.ts +8 -0
- package/dist/umd/hooks/useKeyPress.d.ts.map +1 -0
- package/dist/umd/hooks/useNodes.d.ts +4 -0
- package/dist/umd/hooks/useNodes.d.ts.map +1 -0
- package/dist/umd/hooks/useNodesEdgesState.d.ts +7 -0
- package/dist/umd/hooks/useNodesEdgesState.d.ts.map +1 -0
- package/dist/umd/hooks/useNodesInitialized.d.ts +6 -0
- package/dist/umd/hooks/useNodesInitialized.d.ts.map +1 -0
- package/dist/umd/hooks/useOnInitHandler.d.ts +4 -0
- package/dist/umd/hooks/useOnInitHandler.d.ts.map +1 -0
- package/dist/umd/hooks/useOnSelectionChange.d.ts +7 -0
- package/dist/umd/hooks/useOnSelectionChange.d.ts.map +1 -0
- package/dist/umd/hooks/useOnViewportChange.d.ts +9 -0
- package/dist/umd/hooks/useOnViewportChange.d.ts.map +1 -0
- package/dist/umd/hooks/useReactFlow.d.ts +3 -0
- package/dist/umd/hooks/useReactFlow.d.ts.map +1 -0
- package/dist/umd/hooks/useResizeHandler.d.ts +4 -0
- package/dist/umd/hooks/useResizeHandler.d.ts.map +1 -0
- package/dist/umd/hooks/useStore.d.ts +14 -0
- package/dist/umd/hooks/useStore.d.ts.map +1 -0
- package/dist/umd/hooks/useUpdateNodeInternals.d.ts +4 -0
- package/dist/umd/hooks/useUpdateNodeInternals.d.ts.map +1 -0
- package/dist/umd/hooks/useUpdateNodePositions.d.ts +7 -0
- package/dist/umd/hooks/useUpdateNodePositions.d.ts.map +1 -0
- package/dist/umd/hooks/useViewport.d.ts +4 -0
- package/dist/umd/hooks/useViewport.d.ts.map +1 -0
- package/dist/umd/hooks/useViewportHelper.d.ts +4 -0
- package/dist/umd/hooks/useViewportHelper.d.ts.map +1 -0
- package/dist/umd/hooks/useVisibleEdges.d.ts +8 -0
- package/dist/umd/hooks/useVisibleEdges.d.ts.map +1 -0
- package/dist/umd/hooks/useVisibleNodes.d.ts +3 -0
- package/dist/umd/hooks/useVisibleNodes.d.ts.map +1 -0
- package/dist/umd/index.d.ts +31 -0
- package/dist/umd/index.d.ts.map +1 -0
- package/dist/umd/index.js +10 -0
- package/dist/umd/store/index.d.ts +4 -0
- package/dist/umd/store/index.d.ts.map +1 -0
- package/dist/umd/store/initialState.d.ts +5 -0
- package/dist/umd/store/initialState.d.ts.map +1 -0
- package/dist/umd/store/utils.d.ts +20 -0
- package/dist/umd/store/utils.d.ts.map +1 -0
- package/dist/umd/styles/index.d.ts +3 -0
- package/dist/umd/styles/index.d.ts.map +1 -0
- package/dist/umd/types/changes.d.ts +47 -0
- package/dist/umd/types/changes.d.ts.map +1 -0
- package/dist/umd/types/component-props.d.ts +130 -0
- package/dist/umd/types/component-props.d.ts.map +1 -0
- package/dist/umd/types/edges.d.ts +143 -0
- package/dist/umd/types/edges.d.ts.map +1 -0
- package/dist/umd/types/general.d.ts +232 -0
- package/dist/umd/types/general.d.ts.map +1 -0
- package/dist/umd/types/handles.d.ts +22 -0
- package/dist/umd/types/handles.d.ts.map +1 -0
- package/dist/umd/types/index.d.ts +9 -0
- package/dist/umd/types/index.d.ts.map +1 -0
- package/dist/umd/types/instance.d.ts +48 -0
- package/dist/umd/types/instance.d.ts.map +1 -0
- package/dist/umd/types/nodes.d.ts +100 -0
- package/dist/umd/types/nodes.d.ts.map +1 -0
- package/dist/umd/types/utils.d.ts +26 -0
- package/dist/umd/types/utils.d.ts.map +1 -0
- package/dist/umd/utils/changes.d.ts +11 -0
- package/dist/umd/utils/changes.d.ts.map +1 -0
- package/dist/umd/utils/graph.d.ts +27 -0
- package/dist/umd/utils/graph.d.ts.map +1 -0
- package/dist/umd/utils/index.d.ts +29 -0
- package/dist/umd/utils/index.d.ts.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,4209 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo, memo, useRef, useState, useEffect, forwardRef, useCallback } from 'react';
|
|
2
|
+
import cc from 'classcat';
|
|
3
|
+
import { useStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional';
|
|
4
|
+
import { shallow } from 'zustand/shallow';
|
|
5
|
+
import { zoomIdentity, zoom } from 'd3-zoom';
|
|
6
|
+
import { select, pointer } from 'd3-selection';
|
|
7
|
+
import { drag } from 'd3-drag';
|
|
8
|
+
import { createPortal } from 'react-dom';
|
|
9
|
+
|
|
10
|
+
const StoreContext = createContext(null);
|
|
11
|
+
const Provider$1 = StoreContext.Provider;
|
|
12
|
+
|
|
13
|
+
const errorMessages = {
|
|
14
|
+
error001: () => '[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#001',
|
|
15
|
+
error002: () => "It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",
|
|
16
|
+
error003: (nodeType) => `Node type "${nodeType}" not found. Using fallback type "default".`,
|
|
17
|
+
error004: () => 'The React Flow parent container needs a width and a height to render the graph.',
|
|
18
|
+
error005: () => 'Only child nodes can use a parent extent.',
|
|
19
|
+
error006: () => "Can't create edge. An edge needs a source and a target.",
|
|
20
|
+
error007: (id) => `The old edge with id=${id} does not exist.`,
|
|
21
|
+
error009: (type) => `Marker type "${type}" doesn't exist.`,
|
|
22
|
+
error008: (sourceHandle, edge) => `Couldn't create edge for ${!sourceHandle ? 'source' : 'target'} handle id: "${!sourceHandle ? edge.sourceHandle : edge.targetHandle}", edge id: ${edge.id}.`,
|
|
23
|
+
error010: () => 'Handle: No node id found. Make sure to only use a Handle inside a custom Node.',
|
|
24
|
+
error011: (edgeType) => `Edge type "${edgeType}" not found. Using fallback type "default".`,
|
|
25
|
+
error012: (id) => `Node with id "${id}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const zustandErrorMessage = errorMessages['error001']();
|
|
29
|
+
function useStore(selector, equalityFn) {
|
|
30
|
+
const store = useContext(StoreContext);
|
|
31
|
+
if (store === null) {
|
|
32
|
+
throw new Error(zustandErrorMessage);
|
|
33
|
+
}
|
|
34
|
+
return useStoreWithEqualityFn(store, selector, equalityFn);
|
|
35
|
+
}
|
|
36
|
+
const useStoreApi = () => {
|
|
37
|
+
const store = useContext(StoreContext);
|
|
38
|
+
if (store === null) {
|
|
39
|
+
throw new Error(zustandErrorMessage);
|
|
40
|
+
}
|
|
41
|
+
return useMemo(() => ({
|
|
42
|
+
getState: store.getState,
|
|
43
|
+
setState: store.setState,
|
|
44
|
+
subscribe: store.subscribe,
|
|
45
|
+
destroy: store.destroy,
|
|
46
|
+
}), [store]);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const selector$g = (s) => (s.userSelectionActive ? 'none' : 'all');
|
|
50
|
+
function Panel({ position, children, className, style, ...rest }) {
|
|
51
|
+
const pointerEvents = useStore(selector$g);
|
|
52
|
+
const positionClasses = `${position}`.split('-');
|
|
53
|
+
return (React.createElement("div", { className: cc(['react-flow__panel', className, ...positionClasses]), style: { ...style, pointerEvents }, ...rest }, children));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Attribution({ proOptions, position = 'bottom-right' }) {
|
|
57
|
+
if (proOptions?.hideAttribution) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return (React.createElement(Panel, { position: position, className: "react-flow__attribution", "data-message": "Please only hide this attribution when you are subscribed to React Flow Pro: https://reactflow.dev/pro" },
|
|
61
|
+
React.createElement("a", { href: "https://reactflow.dev", target: "_blank", rel: "noopener noreferrer", "aria-label": "React Flow attribution" }, "React Flow")));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const EdgeText = ({ x, y, label, labelStyle = {}, labelShowBg = true, labelBgStyle = {}, labelBgPadding = [2, 4], labelBgBorderRadius = 2, children, className, ...rest }) => {
|
|
65
|
+
const edgeRef = useRef(null);
|
|
66
|
+
const [edgeTextBbox, setEdgeTextBbox] = useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
67
|
+
const edgeTextClasses = cc(['react-flow__edge-textwrapper', className]);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (edgeRef.current) {
|
|
70
|
+
const textBbox = edgeRef.current.getBBox();
|
|
71
|
+
setEdgeTextBbox({
|
|
72
|
+
x: textBbox.x,
|
|
73
|
+
y: textBbox.y,
|
|
74
|
+
width: textBbox.width,
|
|
75
|
+
height: textBbox.height,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}, [label]);
|
|
79
|
+
if (typeof label === 'undefined' || !label) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return (React.createElement("g", { transform: `translate(${x - edgeTextBbox.width / 2} ${y - edgeTextBbox.height / 2})`, className: edgeTextClasses, visibility: edgeTextBbox.width ? 'visible' : 'hidden', ...rest },
|
|
83
|
+
labelShowBg && (React.createElement("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 })),
|
|
84
|
+
React.createElement("text", { className: "react-flow__edge-text", y: edgeTextBbox.height / 2, dy: "0.3em", ref: edgeRef, style: labelStyle }, label),
|
|
85
|
+
children));
|
|
86
|
+
};
|
|
87
|
+
var EdgeText$1 = memo(EdgeText);
|
|
88
|
+
|
|
89
|
+
const getDimensions = (node) => ({
|
|
90
|
+
width: node.offsetWidth,
|
|
91
|
+
height: node.offsetHeight,
|
|
92
|
+
});
|
|
93
|
+
const clamp = (val, min = 0, max = 1) => Math.min(Math.max(val, min), max);
|
|
94
|
+
const clampPosition = (position = { x: 0, y: 0 }, extent) => ({
|
|
95
|
+
x: clamp(position.x, extent[0][0], extent[1][0]),
|
|
96
|
+
y: clamp(position.y, extent[0][1], extent[1][1]),
|
|
97
|
+
});
|
|
98
|
+
// returns a number between 0 and 1 that represents the velocity of the movement
|
|
99
|
+
// when the mouse is close to the edge of the canvas
|
|
100
|
+
const calcAutoPanVelocity = (value, min, max) => {
|
|
101
|
+
if (value < min) {
|
|
102
|
+
return clamp(Math.abs(value - min), 1, 50) / 50;
|
|
103
|
+
}
|
|
104
|
+
else if (value > max) {
|
|
105
|
+
return -clamp(Math.abs(value - max), 1, 50) / 50;
|
|
106
|
+
}
|
|
107
|
+
return 0;
|
|
108
|
+
};
|
|
109
|
+
const calcAutoPan = (pos, bounds) => {
|
|
110
|
+
const xMovement = calcAutoPanVelocity(pos.x, 35, bounds.width - 35) * 20;
|
|
111
|
+
const yMovement = calcAutoPanVelocity(pos.y, 35, bounds.height - 35) * 20;
|
|
112
|
+
return [xMovement, yMovement];
|
|
113
|
+
};
|
|
114
|
+
const getHostForElement = (element) => element.getRootNode?.() || window?.document;
|
|
115
|
+
const getBoundsOfBoxes = (box1, box2) => ({
|
|
116
|
+
x: Math.min(box1.x, box2.x),
|
|
117
|
+
y: Math.min(box1.y, box2.y),
|
|
118
|
+
x2: Math.max(box1.x2, box2.x2),
|
|
119
|
+
y2: Math.max(box1.y2, box2.y2),
|
|
120
|
+
});
|
|
121
|
+
const rectToBox = ({ x, y, width, height }) => ({
|
|
122
|
+
x,
|
|
123
|
+
y,
|
|
124
|
+
x2: x + width,
|
|
125
|
+
y2: y + height,
|
|
126
|
+
});
|
|
127
|
+
const boxToRect = ({ x, y, x2, y2 }) => ({
|
|
128
|
+
x,
|
|
129
|
+
y,
|
|
130
|
+
width: x2 - x,
|
|
131
|
+
height: y2 - y,
|
|
132
|
+
});
|
|
133
|
+
const nodeToRect = (node) => ({
|
|
134
|
+
...(node.positionAbsolute || { x: 0, y: 0 }),
|
|
135
|
+
width: node.width || 0,
|
|
136
|
+
height: node.height || 0,
|
|
137
|
+
});
|
|
138
|
+
const getBoundsOfRects = (rect1, rect2) => boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2)));
|
|
139
|
+
const getOverlappingArea = (rectA, rectB) => {
|
|
140
|
+
const xOverlap = Math.max(0, Math.min(rectA.x + rectA.width, rectB.x + rectB.width) - Math.max(rectA.x, rectB.x));
|
|
141
|
+
const yOverlap = Math.max(0, Math.min(rectA.y + rectA.height, rectB.y + rectB.height) - Math.max(rectA.y, rectB.y));
|
|
142
|
+
return Math.ceil(xOverlap * yOverlap);
|
|
143
|
+
};
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
+
const isRectObject = (obj) => isNumeric(obj.width) && isNumeric(obj.height) && isNumeric(obj.x) && isNumeric(obj.y);
|
|
146
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
147
|
+
const isNumeric = (n) => !isNaN(n) && isFinite(n);
|
|
148
|
+
const internalsSymbol = Symbol.for('internals');
|
|
149
|
+
// used for a11y key board controls for nodes and edges
|
|
150
|
+
const elementSelectionKeys = ['Enter', ' ', 'Escape'];
|
|
151
|
+
const devWarn = (id, message) => {
|
|
152
|
+
if (process.env.NODE_ENV === 'development') {
|
|
153
|
+
console.warn(`[React Flow]: ${message} Help: https://reactflow.dev/error#${id}`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const isReactKeyboardEvent = (event) => 'nativeEvent' in event;
|
|
157
|
+
function isInputDOMNode(event) {
|
|
158
|
+
const kbEvent = isReactKeyboardEvent(event) ? event.nativeEvent : event;
|
|
159
|
+
// using composed path for handling shadow dom
|
|
160
|
+
const target = (kbEvent.composedPath?.()?.[0] || event.target);
|
|
161
|
+
const isInput = ['INPUT', 'SELECT', 'TEXTAREA'].includes(target?.nodeName) || target?.hasAttribute('contenteditable');
|
|
162
|
+
// when an input field is focused we don't want to trigger deletion or movement of nodes
|
|
163
|
+
return isInput || !!target?.closest('.nokey');
|
|
164
|
+
}
|
|
165
|
+
const isMouseEvent = (event) => 'clientX' in event;
|
|
166
|
+
const getEventPosition = (event, bounds) => {
|
|
167
|
+
const isMouseTriggered = isMouseEvent(event);
|
|
168
|
+
const evtX = isMouseTriggered ? event.clientX : event.touches?.[0].clientX;
|
|
169
|
+
const evtY = isMouseTriggered ? event.clientY : event.touches?.[0].clientY;
|
|
170
|
+
return {
|
|
171
|
+
x: evtX - (bounds?.left ?? 0),
|
|
172
|
+
y: evtY - (bounds?.top ?? 0),
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
const isMacOs = () => typeof navigator !== 'undefined' && navigator?.userAgent?.indexOf('Mac') >= 0;
|
|
176
|
+
|
|
177
|
+
const BaseEdge = ({ id, path, labelX, labelY, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, style, markerEnd, markerStart, interactionWidth = 20, }) => {
|
|
178
|
+
return (React.createElement(React.Fragment, null,
|
|
179
|
+
React.createElement("path", { id: id, style: style, d: path, fill: "none", className: "react-flow__edge-path", markerEnd: markerEnd, markerStart: markerStart }),
|
|
180
|
+
interactionWidth && (React.createElement("path", { d: path, fill: "none", strokeOpacity: 0, strokeWidth: interactionWidth, className: "react-flow__edge-interaction" })),
|
|
181
|
+
label && isNumeric(labelX) && isNumeric(labelY) ? (React.createElement(EdgeText$1, { x: labelX, y: labelY, label: label, labelStyle: labelStyle, labelShowBg: labelShowBg, labelBgStyle: labelBgStyle, labelBgPadding: labelBgPadding, labelBgBorderRadius: labelBgBorderRadius })) : null));
|
|
182
|
+
};
|
|
183
|
+
BaseEdge.displayName = 'BaseEdge';
|
|
184
|
+
|
|
185
|
+
const getMarkerEnd = (markerType, markerEndId) => {
|
|
186
|
+
if (typeof markerEndId !== 'undefined' && markerEndId) {
|
|
187
|
+
return `url(#${markerEndId})`;
|
|
188
|
+
}
|
|
189
|
+
return typeof markerType !== 'undefined' ? `url(#react-flow__${markerType})` : 'none';
|
|
190
|
+
};
|
|
191
|
+
function getMouseHandler$1(id, getState, handler) {
|
|
192
|
+
return handler === undefined
|
|
193
|
+
? handler
|
|
194
|
+
: (event) => {
|
|
195
|
+
const edge = getState().edges.find((e) => e.id === id);
|
|
196
|
+
if (edge) {
|
|
197
|
+
handler(event, { ...edge });
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// this is used for straight edges and simple smoothstep edges (LTR, RTL, BTT, TTB)
|
|
202
|
+
function getEdgeCenter({ sourceX, sourceY, targetX, targetY, }) {
|
|
203
|
+
const xOffset = Math.abs(targetX - sourceX) / 2;
|
|
204
|
+
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset;
|
|
205
|
+
const yOffset = Math.abs(targetY - sourceY) / 2;
|
|
206
|
+
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset;
|
|
207
|
+
return [centerX, centerY, xOffset, yOffset];
|
|
208
|
+
}
|
|
209
|
+
function getBezierEdgeCenter({ sourceX, sourceY, targetX, targetY, sourceControlX, sourceControlY, targetControlX, targetControlY, }) {
|
|
210
|
+
// cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
|
211
|
+
// https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
|
212
|
+
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125;
|
|
213
|
+
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125;
|
|
214
|
+
const offsetX = Math.abs(centerX - sourceX);
|
|
215
|
+
const offsetY = Math.abs(centerY - sourceY);
|
|
216
|
+
return [centerX, centerY, offsetX, offsetY];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
var ConnectionMode;
|
|
220
|
+
(function (ConnectionMode) {
|
|
221
|
+
ConnectionMode["Strict"] = "strict";
|
|
222
|
+
ConnectionMode["Loose"] = "loose";
|
|
223
|
+
})(ConnectionMode || (ConnectionMode = {}));
|
|
224
|
+
var PanOnScrollMode;
|
|
225
|
+
(function (PanOnScrollMode) {
|
|
226
|
+
PanOnScrollMode["Free"] = "free";
|
|
227
|
+
PanOnScrollMode["Vertical"] = "vertical";
|
|
228
|
+
PanOnScrollMode["Horizontal"] = "horizontal";
|
|
229
|
+
})(PanOnScrollMode || (PanOnScrollMode = {}));
|
|
230
|
+
var SelectionMode;
|
|
231
|
+
(function (SelectionMode) {
|
|
232
|
+
SelectionMode["Partial"] = "partial";
|
|
233
|
+
SelectionMode["Full"] = "full";
|
|
234
|
+
})(SelectionMode || (SelectionMode = {}));
|
|
235
|
+
|
|
236
|
+
var ConnectionLineType;
|
|
237
|
+
(function (ConnectionLineType) {
|
|
238
|
+
ConnectionLineType["Bezier"] = "default";
|
|
239
|
+
ConnectionLineType["Straight"] = "straight";
|
|
240
|
+
ConnectionLineType["Step"] = "step";
|
|
241
|
+
ConnectionLineType["SmoothStep"] = "smoothstep";
|
|
242
|
+
ConnectionLineType["SimpleBezier"] = "simplebezier";
|
|
243
|
+
})(ConnectionLineType || (ConnectionLineType = {}));
|
|
244
|
+
var MarkerType;
|
|
245
|
+
(function (MarkerType) {
|
|
246
|
+
MarkerType["Arrow"] = "arrow";
|
|
247
|
+
MarkerType["ArrowClosed"] = "arrowclosed";
|
|
248
|
+
})(MarkerType || (MarkerType = {}));
|
|
249
|
+
|
|
250
|
+
var Position;
|
|
251
|
+
(function (Position) {
|
|
252
|
+
Position["Left"] = "left";
|
|
253
|
+
Position["Top"] = "top";
|
|
254
|
+
Position["Right"] = "right";
|
|
255
|
+
Position["Bottom"] = "bottom";
|
|
256
|
+
})(Position || (Position = {}));
|
|
257
|
+
|
|
258
|
+
function getControl({ pos, x1, y1, x2, y2 }) {
|
|
259
|
+
if (pos === Position.Left || pos === Position.Right) {
|
|
260
|
+
return [0.5 * (x1 + x2), y1];
|
|
261
|
+
}
|
|
262
|
+
return [x1, 0.5 * (y1 + y2)];
|
|
263
|
+
}
|
|
264
|
+
function getSimpleBezierPath({ sourceX, sourceY, sourcePosition = Position.Bottom, targetX, targetY, targetPosition = Position.Top, }) {
|
|
265
|
+
const [sourceControlX, sourceControlY] = getControl({
|
|
266
|
+
pos: sourcePosition,
|
|
267
|
+
x1: sourceX,
|
|
268
|
+
y1: sourceY,
|
|
269
|
+
x2: targetX,
|
|
270
|
+
y2: targetY,
|
|
271
|
+
});
|
|
272
|
+
const [targetControlX, targetControlY] = getControl({
|
|
273
|
+
pos: targetPosition,
|
|
274
|
+
x1: targetX,
|
|
275
|
+
y1: targetY,
|
|
276
|
+
x2: sourceX,
|
|
277
|
+
y2: sourceY,
|
|
278
|
+
});
|
|
279
|
+
const [labelX, labelY, offsetX, offsetY] = getBezierEdgeCenter({
|
|
280
|
+
sourceX,
|
|
281
|
+
sourceY,
|
|
282
|
+
targetX,
|
|
283
|
+
targetY,
|
|
284
|
+
sourceControlX,
|
|
285
|
+
sourceControlY,
|
|
286
|
+
targetControlX,
|
|
287
|
+
targetControlY,
|
|
288
|
+
});
|
|
289
|
+
return [
|
|
290
|
+
`M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`,
|
|
291
|
+
labelX,
|
|
292
|
+
labelY,
|
|
293
|
+
offsetX,
|
|
294
|
+
offsetY,
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
const SimpleBezierEdge = memo(({ sourceX, sourceY, targetX, targetY, sourcePosition = Position.Bottom, targetPosition = Position.Top, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, style, markerEnd, markerStart, interactionWidth, }) => {
|
|
298
|
+
const [path, labelX, labelY] = getSimpleBezierPath({
|
|
299
|
+
sourceX,
|
|
300
|
+
sourceY,
|
|
301
|
+
sourcePosition,
|
|
302
|
+
targetX,
|
|
303
|
+
targetY,
|
|
304
|
+
targetPosition,
|
|
305
|
+
});
|
|
306
|
+
return (React.createElement(BaseEdge, { path: path, labelX: labelX, labelY: labelY, label: label, labelStyle: labelStyle, labelShowBg: labelShowBg, labelBgStyle: labelBgStyle, labelBgPadding: labelBgPadding, labelBgBorderRadius: labelBgBorderRadius, style: style, markerEnd: markerEnd, markerStart: markerStart, interactionWidth: interactionWidth }));
|
|
307
|
+
});
|
|
308
|
+
SimpleBezierEdge.displayName = 'SimpleBezierEdge';
|
|
309
|
+
|
|
310
|
+
const handleDirections = {
|
|
311
|
+
[Position.Left]: { x: -1, y: 0 },
|
|
312
|
+
[Position.Right]: { x: 1, y: 0 },
|
|
313
|
+
[Position.Top]: { x: 0, y: -1 },
|
|
314
|
+
[Position.Bottom]: { x: 0, y: 1 },
|
|
315
|
+
};
|
|
316
|
+
const getDirection = ({ source, sourcePosition = Position.Bottom, target, }) => {
|
|
317
|
+
if (sourcePosition === Position.Left || sourcePosition === Position.Right) {
|
|
318
|
+
return source.x < target.x ? { x: 1, y: 0 } : { x: -1, y: 0 };
|
|
319
|
+
}
|
|
320
|
+
return source.y < target.y ? { x: 0, y: 1 } : { x: 0, y: -1 };
|
|
321
|
+
};
|
|
322
|
+
const distance = (a, b) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
|
|
323
|
+
// ith this function we try to mimic a orthogonal edge routing behaviour
|
|
324
|
+
// It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges
|
|
325
|
+
function getPoints({ source, sourcePosition = Position.Bottom, target, targetPosition = Position.Top, center, offset, }) {
|
|
326
|
+
const sourceDir = handleDirections[sourcePosition];
|
|
327
|
+
const targetDir = handleDirections[targetPosition];
|
|
328
|
+
const sourceGapped = { x: source.x + sourceDir.x * offset, y: source.y + sourceDir.y * offset };
|
|
329
|
+
const targetGapped = { x: target.x + targetDir.x * offset, y: target.y + targetDir.y * offset };
|
|
330
|
+
const dir = getDirection({
|
|
331
|
+
source: sourceGapped,
|
|
332
|
+
sourcePosition,
|
|
333
|
+
target: targetGapped,
|
|
334
|
+
});
|
|
335
|
+
const dirAccessor = dir.x !== 0 ? 'x' : 'y';
|
|
336
|
+
const currDir = dir[dirAccessor];
|
|
337
|
+
let points = [];
|
|
338
|
+
let centerX, centerY;
|
|
339
|
+
const sourceGapOffset = { x: 0, y: 0 };
|
|
340
|
+
const targetGapOffset = { x: 0, y: 0 };
|
|
341
|
+
const [defaultCenterX, defaultCenterY, defaultOffsetX, defaultOffsetY] = getEdgeCenter({
|
|
342
|
+
sourceX: source.x,
|
|
343
|
+
sourceY: source.y,
|
|
344
|
+
targetX: target.x,
|
|
345
|
+
targetY: target.y,
|
|
346
|
+
});
|
|
347
|
+
// opposite handle positions, default case
|
|
348
|
+
if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
|
|
349
|
+
centerX = center.x ?? defaultCenterX;
|
|
350
|
+
centerY = center.y ?? defaultCenterY;
|
|
351
|
+
// --->
|
|
352
|
+
// |
|
|
353
|
+
// >---
|
|
354
|
+
const verticalSplit = [
|
|
355
|
+
{ x: centerX, y: sourceGapped.y },
|
|
356
|
+
{ x: centerX, y: targetGapped.y },
|
|
357
|
+
];
|
|
358
|
+
// |
|
|
359
|
+
// ---
|
|
360
|
+
// |
|
|
361
|
+
const horizontalSplit = [
|
|
362
|
+
{ x: sourceGapped.x, y: centerY },
|
|
363
|
+
{ x: targetGapped.x, y: centerY },
|
|
364
|
+
];
|
|
365
|
+
if (sourceDir[dirAccessor] === currDir) {
|
|
366
|
+
points = dirAccessor === 'x' ? verticalSplit : horizontalSplit;
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
points = dirAccessor === 'x' ? horizontalSplit : verticalSplit;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
// sourceTarget means we take x from source and y from target, targetSource is the opposite
|
|
374
|
+
const sourceTarget = [{ x: sourceGapped.x, y: targetGapped.y }];
|
|
375
|
+
const targetSource = [{ x: targetGapped.x, y: sourceGapped.y }];
|
|
376
|
+
// this handles edges with same handle positions
|
|
377
|
+
if (dirAccessor === 'x') {
|
|
378
|
+
points = sourceDir.x === currDir ? targetSource : sourceTarget;
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
points = sourceDir.y === currDir ? sourceTarget : targetSource;
|
|
382
|
+
}
|
|
383
|
+
if (sourcePosition === targetPosition) {
|
|
384
|
+
const diff = Math.abs(source[dirAccessor] - target[dirAccessor]);
|
|
385
|
+
// if an edge goes from right to right for example (sourcePosition === targetPosition) and the distance between source.x and target.x is less than the offset, the added point and the gapped source/target will overlap. This leads to a weird edge path. To avoid this we add a gapOffset to the source/target
|
|
386
|
+
if (diff <= offset) {
|
|
387
|
+
const gapOffset = Math.min(offset - 1, offset - diff);
|
|
388
|
+
if (sourceDir[dirAccessor] === currDir) {
|
|
389
|
+
sourceGapOffset[dirAccessor] = (sourceGapped[dirAccessor] > source[dirAccessor] ? -1 : 1) * gapOffset;
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
targetGapOffset[dirAccessor] = (targetGapped[dirAccessor] > target[dirAccessor] ? -1 : 1) * gapOffset;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// these are conditions for handling mixed handle positions like Right -> Bottom for example
|
|
397
|
+
if (sourcePosition !== targetPosition) {
|
|
398
|
+
const dirAccessorOpposite = dirAccessor === 'x' ? 'y' : 'x';
|
|
399
|
+
const isSameDir = sourceDir[dirAccessor] === targetDir[dirAccessorOpposite];
|
|
400
|
+
const sourceGtTargetOppo = sourceGapped[dirAccessorOpposite] > targetGapped[dirAccessorOpposite];
|
|
401
|
+
const sourceLtTargetOppo = sourceGapped[dirAccessorOpposite] < targetGapped[dirAccessorOpposite];
|
|
402
|
+
const flipSourceTarget = (sourceDir[dirAccessor] === 1 && ((!isSameDir && sourceGtTargetOppo) || (isSameDir && sourceLtTargetOppo))) ||
|
|
403
|
+
(sourceDir[dirAccessor] !== 1 && ((!isSameDir && sourceLtTargetOppo) || (isSameDir && sourceGtTargetOppo)));
|
|
404
|
+
if (flipSourceTarget) {
|
|
405
|
+
points = dirAccessor === 'x' ? sourceTarget : targetSource;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const sourceGapPoint = { x: sourceGapped.x + sourceGapOffset.x, y: sourceGapped.y + sourceGapOffset.y };
|
|
409
|
+
const targetGapPoint = { x: targetGapped.x + targetGapOffset.x, y: targetGapped.y + targetGapOffset.y };
|
|
410
|
+
const maxXDistance = Math.max(Math.abs(sourceGapPoint.x - points[0].x), Math.abs(targetGapPoint.x - points[0].x));
|
|
411
|
+
const maxYDistance = Math.max(Math.abs(sourceGapPoint.y - points[0].y), Math.abs(targetGapPoint.y - points[0].y));
|
|
412
|
+
// we want to place the label on the longest segment of the edge
|
|
413
|
+
if (maxXDistance >= maxYDistance) {
|
|
414
|
+
centerX = (sourceGapPoint.x + targetGapPoint.x) / 2;
|
|
415
|
+
centerY = points[0].y;
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
centerX = points[0].x;
|
|
419
|
+
centerY = (sourceGapPoint.y + targetGapPoint.y) / 2;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const pathPoints = [
|
|
423
|
+
source,
|
|
424
|
+
{ x: sourceGapped.x + sourceGapOffset.x, y: sourceGapped.y + sourceGapOffset.y },
|
|
425
|
+
...points,
|
|
426
|
+
{ x: targetGapped.x + targetGapOffset.x, y: targetGapped.y + targetGapOffset.y },
|
|
427
|
+
target,
|
|
428
|
+
];
|
|
429
|
+
return [pathPoints, centerX, centerY, defaultOffsetX, defaultOffsetY];
|
|
430
|
+
}
|
|
431
|
+
function getBend(a, b, c, size) {
|
|
432
|
+
const bendSize = Math.min(distance(a, b) / 2, distance(b, c) / 2, size);
|
|
433
|
+
const { x, y } = b;
|
|
434
|
+
// no bend
|
|
435
|
+
if ((a.x === x && x === c.x) || (a.y === y && y === c.y)) {
|
|
436
|
+
return `L${x} ${y}`;
|
|
437
|
+
}
|
|
438
|
+
// first segment is horizontal
|
|
439
|
+
if (a.y === y) {
|
|
440
|
+
const xDir = a.x < c.x ? -1 : 1;
|
|
441
|
+
const yDir = a.y < c.y ? 1 : -1;
|
|
442
|
+
return `L ${x + bendSize * xDir},${y}Q ${x},${y} ${x},${y + bendSize * yDir}`;
|
|
443
|
+
}
|
|
444
|
+
const xDir = a.x < c.x ? 1 : -1;
|
|
445
|
+
const yDir = a.y < c.y ? -1 : 1;
|
|
446
|
+
return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}`;
|
|
447
|
+
}
|
|
448
|
+
function getSmoothStepPath({ sourceX, sourceY, sourcePosition = Position.Bottom, targetX, targetY, targetPosition = Position.Top, borderRadius = 5, centerX, centerY, offset = 20, }) {
|
|
449
|
+
const [points, labelX, labelY, offsetX, offsetY] = getPoints({
|
|
450
|
+
source: { x: sourceX, y: sourceY },
|
|
451
|
+
sourcePosition,
|
|
452
|
+
target: { x: targetX, y: targetY },
|
|
453
|
+
targetPosition,
|
|
454
|
+
center: { x: centerX, y: centerY },
|
|
455
|
+
offset,
|
|
456
|
+
});
|
|
457
|
+
const path = points.reduce((res, p, i) => {
|
|
458
|
+
let segment = '';
|
|
459
|
+
if (i > 0 && i < points.length - 1) {
|
|
460
|
+
segment = getBend(points[i - 1], p, points[i + 1], borderRadius);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
segment = `${i === 0 ? 'M' : 'L'}${p.x} ${p.y}`;
|
|
464
|
+
}
|
|
465
|
+
res += segment;
|
|
466
|
+
return res;
|
|
467
|
+
}, '');
|
|
468
|
+
return [path, labelX, labelY, offsetX, offsetY];
|
|
469
|
+
}
|
|
470
|
+
const SmoothStepEdge = memo(({ sourceX, sourceY, targetX, targetY, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, style, sourcePosition = Position.Bottom, targetPosition = Position.Top, markerEnd, markerStart, pathOptions, interactionWidth, }) => {
|
|
471
|
+
const [path, labelX, labelY] = getSmoothStepPath({
|
|
472
|
+
sourceX,
|
|
473
|
+
sourceY,
|
|
474
|
+
sourcePosition,
|
|
475
|
+
targetX,
|
|
476
|
+
targetY,
|
|
477
|
+
targetPosition,
|
|
478
|
+
borderRadius: pathOptions?.borderRadius,
|
|
479
|
+
offset: pathOptions?.offset,
|
|
480
|
+
});
|
|
481
|
+
return (React.createElement(BaseEdge, { path: path, labelX: labelX, labelY: labelY, label: label, labelStyle: labelStyle, labelShowBg: labelShowBg, labelBgStyle: labelBgStyle, labelBgPadding: labelBgPadding, labelBgBorderRadius: labelBgBorderRadius, style: style, markerEnd: markerEnd, markerStart: markerStart, interactionWidth: interactionWidth }));
|
|
482
|
+
});
|
|
483
|
+
SmoothStepEdge.displayName = 'SmoothStepEdge';
|
|
484
|
+
|
|
485
|
+
const StepEdge = memo((props) => (React.createElement(SmoothStepEdge, { ...props, pathOptions: useMemo(() => ({ borderRadius: 0, offset: props.pathOptions?.offset }), [props.pathOptions?.offset]) })));
|
|
486
|
+
StepEdge.displayName = 'StepEdge';
|
|
487
|
+
|
|
488
|
+
function getStraightPath({ sourceX, sourceY, targetX, targetY, }) {
|
|
489
|
+
const [labelX, labelY, offsetX, offsetY] = getEdgeCenter({
|
|
490
|
+
sourceX,
|
|
491
|
+
sourceY,
|
|
492
|
+
targetX,
|
|
493
|
+
targetY,
|
|
494
|
+
});
|
|
495
|
+
return [`M ${sourceX},${sourceY}L ${targetX},${targetY}`, labelX, labelY, offsetX, offsetY];
|
|
496
|
+
}
|
|
497
|
+
const StraightEdge = memo(({ sourceX, sourceY, targetX, targetY, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, style, markerEnd, markerStart, interactionWidth, }) => {
|
|
498
|
+
const [path, labelX, labelY] = getStraightPath({ sourceX, sourceY, targetX, targetY });
|
|
499
|
+
return (React.createElement(BaseEdge, { path: path, labelX: labelX, labelY: labelY, label: label, labelStyle: labelStyle, labelShowBg: labelShowBg, labelBgStyle: labelBgStyle, labelBgPadding: labelBgPadding, labelBgBorderRadius: labelBgBorderRadius, style: style, markerEnd: markerEnd, markerStart: markerStart, interactionWidth: interactionWidth }));
|
|
500
|
+
});
|
|
501
|
+
StraightEdge.displayName = 'StraightEdge';
|
|
502
|
+
|
|
503
|
+
function calculateControlOffset(distance, curvature) {
|
|
504
|
+
if (distance >= 0) {
|
|
505
|
+
return 0.5 * distance;
|
|
506
|
+
}
|
|
507
|
+
return curvature * 25 * Math.sqrt(-distance);
|
|
508
|
+
}
|
|
509
|
+
function getControlWithCurvature({ pos, x1, y1, x2, y2, c }) {
|
|
510
|
+
switch (pos) {
|
|
511
|
+
case Position.Left:
|
|
512
|
+
return [x1 - calculateControlOffset(x1 - x2, c), y1];
|
|
513
|
+
case Position.Right:
|
|
514
|
+
return [x1 + calculateControlOffset(x2 - x1, c), y1];
|
|
515
|
+
case Position.Top:
|
|
516
|
+
return [x1, y1 - calculateControlOffset(y1 - y2, c)];
|
|
517
|
+
case Position.Bottom:
|
|
518
|
+
return [x1, y1 + calculateControlOffset(y2 - y1, c)];
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function getBezierPath({ sourceX, sourceY, sourcePosition = Position.Bottom, targetX, targetY, targetPosition = Position.Top, curvature = 0.25, }) {
|
|
522
|
+
const [sourceControlX, sourceControlY] = getControlWithCurvature({
|
|
523
|
+
pos: sourcePosition,
|
|
524
|
+
x1: sourceX,
|
|
525
|
+
y1: sourceY,
|
|
526
|
+
x2: targetX,
|
|
527
|
+
y2: targetY,
|
|
528
|
+
c: curvature,
|
|
529
|
+
});
|
|
530
|
+
const [targetControlX, targetControlY] = getControlWithCurvature({
|
|
531
|
+
pos: targetPosition,
|
|
532
|
+
x1: targetX,
|
|
533
|
+
y1: targetY,
|
|
534
|
+
x2: sourceX,
|
|
535
|
+
y2: sourceY,
|
|
536
|
+
c: curvature,
|
|
537
|
+
});
|
|
538
|
+
const [labelX, labelY, offsetX, offsetY] = getBezierEdgeCenter({
|
|
539
|
+
sourceX,
|
|
540
|
+
sourceY,
|
|
541
|
+
targetX,
|
|
542
|
+
targetY,
|
|
543
|
+
sourceControlX,
|
|
544
|
+
sourceControlY,
|
|
545
|
+
targetControlX,
|
|
546
|
+
targetControlY,
|
|
547
|
+
});
|
|
548
|
+
return [
|
|
549
|
+
`M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`,
|
|
550
|
+
labelX,
|
|
551
|
+
labelY,
|
|
552
|
+
offsetX,
|
|
553
|
+
offsetY,
|
|
554
|
+
];
|
|
555
|
+
}
|
|
556
|
+
const BezierEdge = memo(({ sourceX, sourceY, targetX, targetY, sourcePosition = Position.Bottom, targetPosition = Position.Top, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, style, markerEnd, markerStart, pathOptions, interactionWidth, }) => {
|
|
557
|
+
const [path, labelX, labelY] = getBezierPath({
|
|
558
|
+
sourceX,
|
|
559
|
+
sourceY,
|
|
560
|
+
sourcePosition,
|
|
561
|
+
targetX,
|
|
562
|
+
targetY,
|
|
563
|
+
targetPosition,
|
|
564
|
+
curvature: pathOptions?.curvature,
|
|
565
|
+
});
|
|
566
|
+
return (React.createElement(BaseEdge, { path: path, labelX: labelX, labelY: labelY, label: label, labelStyle: labelStyle, labelShowBg: labelShowBg, labelBgStyle: labelBgStyle, labelBgPadding: labelBgPadding, labelBgBorderRadius: labelBgBorderRadius, style: style, markerEnd: markerEnd, markerStart: markerStart, interactionWidth: interactionWidth }));
|
|
567
|
+
});
|
|
568
|
+
BezierEdge.displayName = 'BezierEdge';
|
|
569
|
+
|
|
570
|
+
const NodeIdContext = createContext(null);
|
|
571
|
+
const Provider = NodeIdContext.Provider;
|
|
572
|
+
NodeIdContext.Consumer;
|
|
573
|
+
const useNodeId = () => {
|
|
574
|
+
const nodeId = useContext(NodeIdContext);
|
|
575
|
+
return nodeId;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const isEdge = (element) => 'id' in element && 'source' in element && 'target' in element;
|
|
579
|
+
const isNode = (element) => 'id' in element && !('source' in element) && !('target' in element);
|
|
580
|
+
const getOutgoers = (node, nodes, edges) => {
|
|
581
|
+
if (!isNode(node)) {
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
const outgoerIds = edges.filter((e) => e.source === node.id).map((e) => e.target);
|
|
585
|
+
return nodes.filter((n) => outgoerIds.includes(n.id));
|
|
586
|
+
};
|
|
587
|
+
const getIncomers = (node, nodes, edges) => {
|
|
588
|
+
if (!isNode(node)) {
|
|
589
|
+
return [];
|
|
590
|
+
}
|
|
591
|
+
const incomersIds = edges.filter((e) => e.target === node.id).map((e) => e.source);
|
|
592
|
+
return nodes.filter((n) => incomersIds.includes(n.id));
|
|
593
|
+
};
|
|
594
|
+
const getEdgeId = ({ source, sourceHandle, target, targetHandle }) => `reactflow__edge-${source}${sourceHandle || ''}-${target}${targetHandle || ''}`;
|
|
595
|
+
const getMarkerId = (marker, rfId) => {
|
|
596
|
+
if (typeof marker === 'undefined') {
|
|
597
|
+
return '';
|
|
598
|
+
}
|
|
599
|
+
if (typeof marker === 'string') {
|
|
600
|
+
return marker;
|
|
601
|
+
}
|
|
602
|
+
const idPrefix = rfId ? `${rfId}__` : '';
|
|
603
|
+
return `${idPrefix}${Object.keys(marker)
|
|
604
|
+
.sort()
|
|
605
|
+
.map((key) => `${key}=${marker[key]}`)
|
|
606
|
+
.join('&')}`;
|
|
607
|
+
};
|
|
608
|
+
const connectionExists = (edge, edges) => {
|
|
609
|
+
return edges.some((el) => el.source === edge.source &&
|
|
610
|
+
el.target === edge.target &&
|
|
611
|
+
(el.sourceHandle === edge.sourceHandle || (!el.sourceHandle && !edge.sourceHandle)) &&
|
|
612
|
+
(el.targetHandle === edge.targetHandle || (!el.targetHandle && !edge.targetHandle)));
|
|
613
|
+
};
|
|
614
|
+
const addEdge = (edgeParams, edges) => {
|
|
615
|
+
if (!edgeParams.source || !edgeParams.target) {
|
|
616
|
+
devWarn('006', errorMessages['error006']());
|
|
617
|
+
return edges;
|
|
618
|
+
}
|
|
619
|
+
let edge;
|
|
620
|
+
if (isEdge(edgeParams)) {
|
|
621
|
+
edge = { ...edgeParams };
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
edge = {
|
|
625
|
+
...edgeParams,
|
|
626
|
+
id: getEdgeId(edgeParams),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
if (connectionExists(edge, edges)) {
|
|
630
|
+
return edges;
|
|
631
|
+
}
|
|
632
|
+
return edges.concat(edge);
|
|
633
|
+
};
|
|
634
|
+
const reconnectEdge = (oldEdge, newConnection, edges, options = { shouldReplaceId: true }) => {
|
|
635
|
+
const { id: oldEdgeId, ...rest } = oldEdge;
|
|
636
|
+
if (!newConnection.source || !newConnection.target) {
|
|
637
|
+
devWarn('006', errorMessages['error006']());
|
|
638
|
+
return edges;
|
|
639
|
+
}
|
|
640
|
+
const foundEdge = edges.find((e) => e.id === oldEdgeId);
|
|
641
|
+
if (!foundEdge) {
|
|
642
|
+
devWarn('007', errorMessages['error007'](oldEdgeId));
|
|
643
|
+
return edges;
|
|
644
|
+
}
|
|
645
|
+
// Remove old edge and create the new edge with parameters of old edge.
|
|
646
|
+
const edge = {
|
|
647
|
+
...rest,
|
|
648
|
+
id: options.shouldReplaceId ? getEdgeId(newConnection) : oldEdgeId,
|
|
649
|
+
source: newConnection.source,
|
|
650
|
+
target: newConnection.target,
|
|
651
|
+
sourceHandle: newConnection.sourceHandle,
|
|
652
|
+
targetHandle: newConnection.targetHandle,
|
|
653
|
+
};
|
|
654
|
+
return edges.filter((e) => e.id !== oldEdgeId).concat(edge);
|
|
655
|
+
};
|
|
656
|
+
/**
|
|
657
|
+
*
|
|
658
|
+
* @deprecated Use `reconnectEdge` instead.
|
|
659
|
+
*/
|
|
660
|
+
const updateEdge = (oldEdge, newConnection, edges, options = { shouldReplaceId: true }) => {
|
|
661
|
+
console.warn('[DEPRECATED] `updateEdge` is deprecated. Instead use `reconnectEdge` https://reactflow.dev/api-reference/utils/reconnect-edge');
|
|
662
|
+
return reconnectEdge(oldEdge, newConnection, edges, options);
|
|
663
|
+
};
|
|
664
|
+
const pointToRendererPoint = ({ x, y }, [tx, ty, tScale], snapToGrid, [snapX, snapY]) => {
|
|
665
|
+
const position = {
|
|
666
|
+
x: (x - tx) / tScale,
|
|
667
|
+
y: (y - ty) / tScale,
|
|
668
|
+
};
|
|
669
|
+
if (snapToGrid) {
|
|
670
|
+
return {
|
|
671
|
+
x: snapX * Math.round(position.x / snapX),
|
|
672
|
+
y: snapY * Math.round(position.y / snapY),
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
return position;
|
|
676
|
+
};
|
|
677
|
+
const rendererPointToPoint = ({ x, y }, [tx, ty, tScale]) => {
|
|
678
|
+
return {
|
|
679
|
+
x: x * tScale + tx,
|
|
680
|
+
y: y * tScale + ty,
|
|
681
|
+
};
|
|
682
|
+
};
|
|
683
|
+
const getNodePositionWithOrigin = (node, nodeOrigin = [0, 0]) => {
|
|
684
|
+
if (!node) {
|
|
685
|
+
return {
|
|
686
|
+
x: 0,
|
|
687
|
+
y: 0,
|
|
688
|
+
positionAbsolute: {
|
|
689
|
+
x: 0,
|
|
690
|
+
y: 0,
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const offsetX = (node.width ?? 0) * nodeOrigin[0];
|
|
695
|
+
const offsetY = (node.height ?? 0) * nodeOrigin[1];
|
|
696
|
+
const position = {
|
|
697
|
+
x: node.position.x - offsetX,
|
|
698
|
+
y: node.position.y - offsetY,
|
|
699
|
+
};
|
|
700
|
+
return {
|
|
701
|
+
...position,
|
|
702
|
+
positionAbsolute: node.positionAbsolute
|
|
703
|
+
? {
|
|
704
|
+
x: node.positionAbsolute.x - offsetX,
|
|
705
|
+
y: node.positionAbsolute.y - offsetY,
|
|
706
|
+
}
|
|
707
|
+
: position,
|
|
708
|
+
};
|
|
709
|
+
};
|
|
710
|
+
const getNodesBounds = (nodes, nodeOrigin = [0, 0]) => {
|
|
711
|
+
if (nodes.length === 0) {
|
|
712
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
713
|
+
}
|
|
714
|
+
const box = nodes.reduce((currBox, node) => {
|
|
715
|
+
const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
|
|
716
|
+
return getBoundsOfBoxes(currBox, rectToBox({
|
|
717
|
+
x,
|
|
718
|
+
y,
|
|
719
|
+
width: node.width || 0,
|
|
720
|
+
height: node.height || 0,
|
|
721
|
+
}));
|
|
722
|
+
}, { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity });
|
|
723
|
+
return boxToRect(box);
|
|
724
|
+
};
|
|
725
|
+
// @deprecated Use `getNodesBounds`.
|
|
726
|
+
const getRectOfNodes = (nodes, nodeOrigin = [0, 0]) => {
|
|
727
|
+
console.warn('[DEPRECATED] `getRectOfNodes` is deprecated. Instead use `getNodesBounds` https://reactflow.dev/api-reference/utils/get-nodes-bounds.');
|
|
728
|
+
return getNodesBounds(nodes, nodeOrigin);
|
|
729
|
+
};
|
|
730
|
+
const getNodesInside = (nodeInternals, rect, [tx, ty, tScale] = [0, 0, 1], partially = false,
|
|
731
|
+
// set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute
|
|
732
|
+
excludeNonSelectableNodes = false, nodeOrigin = [0, 0]) => {
|
|
733
|
+
const paneRect = {
|
|
734
|
+
x: (rect.x - tx) / tScale,
|
|
735
|
+
y: (rect.y - ty) / tScale,
|
|
736
|
+
width: rect.width / tScale,
|
|
737
|
+
height: rect.height / tScale,
|
|
738
|
+
};
|
|
739
|
+
const visibleNodes = [];
|
|
740
|
+
nodeInternals.forEach((node) => {
|
|
741
|
+
const { width, height, selectable = true, hidden = false } = node;
|
|
742
|
+
if ((excludeNonSelectableNodes && !selectable) || hidden) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
const { positionAbsolute } = getNodePositionWithOrigin(node, nodeOrigin);
|
|
746
|
+
const nodeRect = {
|
|
747
|
+
x: positionAbsolute.x,
|
|
748
|
+
y: positionAbsolute.y,
|
|
749
|
+
width: width || 0,
|
|
750
|
+
height: height || 0,
|
|
751
|
+
};
|
|
752
|
+
const overlappingArea = getOverlappingArea(paneRect, nodeRect);
|
|
753
|
+
const notInitialized = typeof width === 'undefined' || typeof height === 'undefined' || width === null || height === null;
|
|
754
|
+
const partiallyVisible = partially && overlappingArea > 0;
|
|
755
|
+
const area = (width || 0) * (height || 0);
|
|
756
|
+
const isVisible = notInitialized || partiallyVisible || overlappingArea >= area;
|
|
757
|
+
if (isVisible || node.dragging) {
|
|
758
|
+
visibleNodes.push(node);
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
return visibleNodes;
|
|
762
|
+
};
|
|
763
|
+
const getConnectedEdges = (nodes, edges) => {
|
|
764
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
765
|
+
return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target));
|
|
766
|
+
};
|
|
767
|
+
// @deprecated Use `getViewportForBounds`.
|
|
768
|
+
const getTransformForBounds = (bounds, width, height, minZoom, maxZoom, padding = 0.1) => {
|
|
769
|
+
const { x, y, zoom } = getViewportForBounds(bounds, width, height, minZoom, maxZoom, padding);
|
|
770
|
+
console.warn('[DEPRECATED] `getTransformForBounds` is deprecated. Instead use `getViewportForBounds`. Beware that the return value is type Viewport (`{ x: number, y: number, zoom: number }`) instead of Transform (`[number, number, number]`). https://reactflow.dev/api-reference/utils/get-viewport-for-bounds');
|
|
771
|
+
return [x, y, zoom];
|
|
772
|
+
};
|
|
773
|
+
const getViewportForBounds = (bounds, width, height, minZoom, maxZoom, padding = 0.1) => {
|
|
774
|
+
const xZoom = width / (bounds.width * (1 + padding));
|
|
775
|
+
const yZoom = height / (bounds.height * (1 + padding));
|
|
776
|
+
const zoom = Math.min(xZoom, yZoom);
|
|
777
|
+
const clampedZoom = clamp(zoom, minZoom, maxZoom);
|
|
778
|
+
const boundsCenterX = bounds.x + bounds.width / 2;
|
|
779
|
+
const boundsCenterY = bounds.y + bounds.height / 2;
|
|
780
|
+
const x = width / 2 - boundsCenterX * clampedZoom;
|
|
781
|
+
const y = height / 2 - boundsCenterY * clampedZoom;
|
|
782
|
+
return { x, y, zoom: clampedZoom };
|
|
783
|
+
};
|
|
784
|
+
const getD3Transition = (selection, duration = 0) => {
|
|
785
|
+
return selection.transition().duration(duration);
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// this functions collects all handles and adds an absolute position
|
|
789
|
+
// so that we can later find the closest handle to the mouse position
|
|
790
|
+
function getHandles(node, handleBounds, type, currentHandle) {
|
|
791
|
+
return (handleBounds[type] || []).reduce((res, h) => {
|
|
792
|
+
if (`${node.id}-${h.id}-${type}` !== currentHandle) {
|
|
793
|
+
res.push({
|
|
794
|
+
id: h.id || null,
|
|
795
|
+
type,
|
|
796
|
+
nodeId: node.id,
|
|
797
|
+
x: (node.positionAbsolute?.x ?? 0) + h.x + h.width / 2,
|
|
798
|
+
y: (node.positionAbsolute?.y ?? 0) + h.y + h.height / 2,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
return res;
|
|
802
|
+
}, []);
|
|
803
|
+
}
|
|
804
|
+
function getClosestHandle(event, doc, pos, connectionRadius, handles, validator) {
|
|
805
|
+
// we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
|
806
|
+
// because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor
|
|
807
|
+
const { x, y } = getEventPosition(event);
|
|
808
|
+
const domNodes = doc.elementsFromPoint(x, y);
|
|
809
|
+
const handleBelow = domNodes.find((el) => el.classList.contains('react-flow__handle'));
|
|
810
|
+
if (handleBelow) {
|
|
811
|
+
const handleNodeId = handleBelow.getAttribute('data-nodeid');
|
|
812
|
+
if (handleNodeId) {
|
|
813
|
+
const handleType = getHandleType(undefined, handleBelow);
|
|
814
|
+
const handleId = handleBelow.getAttribute('data-handleid');
|
|
815
|
+
const validHandleResult = validator({ nodeId: handleNodeId, id: handleId, type: handleType });
|
|
816
|
+
if (validHandleResult) {
|
|
817
|
+
const handle = handles.find((h) => h.nodeId === handleNodeId && h.type === handleType && h.id === handleId);
|
|
818
|
+
return {
|
|
819
|
+
handle: {
|
|
820
|
+
id: handleId,
|
|
821
|
+
type: handleType,
|
|
822
|
+
nodeId: handleNodeId,
|
|
823
|
+
x: handle?.x || pos.x,
|
|
824
|
+
y: handle?.y || pos.y,
|
|
825
|
+
},
|
|
826
|
+
validHandleResult,
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
// if we couldn't find a handle below the mouse cursor we look for the closest distance based on the connectionRadius
|
|
832
|
+
let closestHandles = [];
|
|
833
|
+
let minDistance = Infinity;
|
|
834
|
+
handles.forEach((handle) => {
|
|
835
|
+
const distance = Math.sqrt((handle.x - pos.x) ** 2 + (handle.y - pos.y) ** 2);
|
|
836
|
+
if (distance <= connectionRadius) {
|
|
837
|
+
const validHandleResult = validator(handle);
|
|
838
|
+
if (distance <= minDistance) {
|
|
839
|
+
if (distance < minDistance) {
|
|
840
|
+
closestHandles = [{ handle, validHandleResult }];
|
|
841
|
+
}
|
|
842
|
+
else if (distance === minDistance) {
|
|
843
|
+
// when multiple handles are on the same distance we collect all of them
|
|
844
|
+
closestHandles.push({
|
|
845
|
+
handle,
|
|
846
|
+
validHandleResult,
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
minDistance = distance;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
if (!closestHandles.length) {
|
|
854
|
+
return { handle: null, validHandleResult: defaultResult() };
|
|
855
|
+
}
|
|
856
|
+
if (closestHandles.length === 1) {
|
|
857
|
+
return closestHandles[0];
|
|
858
|
+
}
|
|
859
|
+
const hasValidHandle = closestHandles.some(({ validHandleResult }) => validHandleResult.isValid);
|
|
860
|
+
const hasTargetHandle = closestHandles.some(({ handle }) => handle.type === 'target');
|
|
861
|
+
// if multiple handles are layouted on top of each other we prefer the one with type = target and the one that is valid
|
|
862
|
+
return (closestHandles.find(({ handle, validHandleResult }) => hasTargetHandle ? handle.type === 'target' : (hasValidHandle ? validHandleResult.isValid : true)) || closestHandles[0]);
|
|
863
|
+
}
|
|
864
|
+
const nullConnection = { source: null, target: null, sourceHandle: null, targetHandle: null };
|
|
865
|
+
const defaultResult = () => ({
|
|
866
|
+
handleDomNode: null,
|
|
867
|
+
isValid: false,
|
|
868
|
+
connection: nullConnection,
|
|
869
|
+
endHandle: null,
|
|
870
|
+
});
|
|
871
|
+
// checks if and returns connection in fom of an object { source: 123, target: 312 }
|
|
872
|
+
function isValidHandle(handle, connectionMode, fromNodeId, fromHandleId, fromType, isValidConnection, doc) {
|
|
873
|
+
const isTarget = fromType === 'target';
|
|
874
|
+
const handleToCheck = doc.querySelector(`.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`);
|
|
875
|
+
const result = {
|
|
876
|
+
...defaultResult(),
|
|
877
|
+
handleDomNode: handleToCheck,
|
|
878
|
+
};
|
|
879
|
+
if (handleToCheck) {
|
|
880
|
+
const handleType = getHandleType(undefined, handleToCheck);
|
|
881
|
+
const handleNodeId = handleToCheck.getAttribute('data-nodeid');
|
|
882
|
+
const handleId = handleToCheck.getAttribute('data-handleid');
|
|
883
|
+
const connectable = handleToCheck.classList.contains('connectable');
|
|
884
|
+
const connectableEnd = handleToCheck.classList.contains('connectableend');
|
|
885
|
+
const connection = {
|
|
886
|
+
source: isTarget ? handleNodeId : fromNodeId,
|
|
887
|
+
sourceHandle: isTarget ? handleId : fromHandleId,
|
|
888
|
+
target: isTarget ? fromNodeId : handleNodeId,
|
|
889
|
+
targetHandle: isTarget ? fromHandleId : handleId,
|
|
890
|
+
};
|
|
891
|
+
result.connection = connection;
|
|
892
|
+
const isConnectable = connectable && connectableEnd;
|
|
893
|
+
// in strict mode we don't allow target to target or source to source connections
|
|
894
|
+
const isValid = isConnectable &&
|
|
895
|
+
(connectionMode === ConnectionMode.Strict
|
|
896
|
+
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
|
|
897
|
+
: handleNodeId !== fromNodeId || handleId !== fromHandleId);
|
|
898
|
+
if (isValid) {
|
|
899
|
+
result.endHandle = {
|
|
900
|
+
nodeId: handleNodeId,
|
|
901
|
+
handleId,
|
|
902
|
+
type: handleType,
|
|
903
|
+
};
|
|
904
|
+
result.isValid = isValidConnection(connection);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
return result;
|
|
908
|
+
}
|
|
909
|
+
function getHandleLookup({ nodes, nodeId, handleId, handleType }) {
|
|
910
|
+
return nodes.reduce((res, node) => {
|
|
911
|
+
if (node[internalsSymbol]) {
|
|
912
|
+
const { handleBounds } = node[internalsSymbol];
|
|
913
|
+
let sourceHandles = [];
|
|
914
|
+
let targetHandles = [];
|
|
915
|
+
if (handleBounds) {
|
|
916
|
+
sourceHandles = getHandles(node, handleBounds, 'source', `${nodeId}-${handleId}-${handleType}`);
|
|
917
|
+
targetHandles = getHandles(node, handleBounds, 'target', `${nodeId}-${handleId}-${handleType}`);
|
|
918
|
+
}
|
|
919
|
+
res.push(...sourceHandles, ...targetHandles);
|
|
920
|
+
}
|
|
921
|
+
return res;
|
|
922
|
+
}, []);
|
|
923
|
+
}
|
|
924
|
+
function getHandleType(edgeUpdaterType, handleDomNode) {
|
|
925
|
+
if (edgeUpdaterType) {
|
|
926
|
+
return edgeUpdaterType;
|
|
927
|
+
}
|
|
928
|
+
else if (handleDomNode?.classList.contains('target')) {
|
|
929
|
+
return 'target';
|
|
930
|
+
}
|
|
931
|
+
else if (handleDomNode?.classList.contains('source')) {
|
|
932
|
+
return 'source';
|
|
933
|
+
}
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
function resetRecentHandle(handleDomNode) {
|
|
937
|
+
handleDomNode?.classList.remove('valid', 'connecting', 'react-flow__handle-valid', 'react-flow__handle-connecting');
|
|
938
|
+
}
|
|
939
|
+
function getConnectionStatus(isInsideConnectionRadius, isHandleValid) {
|
|
940
|
+
let connectionStatus = null;
|
|
941
|
+
if (isHandleValid) {
|
|
942
|
+
connectionStatus = 'valid';
|
|
943
|
+
}
|
|
944
|
+
else if (isInsideConnectionRadius && !isHandleValid) {
|
|
945
|
+
connectionStatus = 'invalid';
|
|
946
|
+
}
|
|
947
|
+
return connectionStatus;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function handlePointerDown({ event, handleId, nodeId, onConnect, isTarget, getState, setState, isValidConnection, edgeUpdaterType, onReconnectEnd, }) {
|
|
951
|
+
// when react-flow is used inside a shadow root we can't use document
|
|
952
|
+
const doc = getHostForElement(event.target);
|
|
953
|
+
const { connectionMode, domNode, autoPanOnConnect, connectionRadius, onConnectStart, panBy, getNodes, cancelConnection, } = getState();
|
|
954
|
+
let autoPanId = 0;
|
|
955
|
+
let closestHandle;
|
|
956
|
+
const { x, y } = getEventPosition(event);
|
|
957
|
+
const clickedHandle = doc?.elementFromPoint(x, y);
|
|
958
|
+
const handleType = getHandleType(edgeUpdaterType, clickedHandle);
|
|
959
|
+
const containerBounds = domNode?.getBoundingClientRect();
|
|
960
|
+
if (!containerBounds || !handleType) {
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
let prevActiveHandle;
|
|
964
|
+
let connectionPosition = getEventPosition(event, containerBounds);
|
|
965
|
+
let autoPanStarted = false;
|
|
966
|
+
let connection = null;
|
|
967
|
+
let isValid = false;
|
|
968
|
+
let handleDomNode = null;
|
|
969
|
+
const handleLookup = getHandleLookup({
|
|
970
|
+
nodes: getNodes(),
|
|
971
|
+
nodeId,
|
|
972
|
+
handleId,
|
|
973
|
+
handleType,
|
|
974
|
+
});
|
|
975
|
+
// when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas
|
|
976
|
+
const autoPan = () => {
|
|
977
|
+
if (!autoPanOnConnect) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const [xMovement, yMovement] = calcAutoPan(connectionPosition, containerBounds);
|
|
981
|
+
panBy({ x: xMovement, y: yMovement });
|
|
982
|
+
autoPanId = requestAnimationFrame(autoPan);
|
|
983
|
+
};
|
|
984
|
+
setState({
|
|
985
|
+
connectionPosition,
|
|
986
|
+
connectionStatus: null,
|
|
987
|
+
// connectionNodeId etc will be removed in the next major in favor of connectionStartHandle
|
|
988
|
+
connectionNodeId: nodeId,
|
|
989
|
+
connectionHandleId: handleId,
|
|
990
|
+
connectionHandleType: handleType,
|
|
991
|
+
connectionStartHandle: {
|
|
992
|
+
nodeId,
|
|
993
|
+
handleId,
|
|
994
|
+
type: handleType,
|
|
995
|
+
},
|
|
996
|
+
connectionEndHandle: null,
|
|
997
|
+
});
|
|
998
|
+
onConnectStart?.(event, { nodeId, handleId, handleType });
|
|
999
|
+
function onPointerMove(event) {
|
|
1000
|
+
const { transform } = getState();
|
|
1001
|
+
connectionPosition = getEventPosition(event, containerBounds);
|
|
1002
|
+
const { handle, validHandleResult } = getClosestHandle(event, doc, pointToRendererPoint(connectionPosition, transform, false, [1, 1]), connectionRadius, handleLookup, (handle) => isValidHandle(handle, connectionMode, nodeId, handleId, isTarget ? 'target' : 'source', isValidConnection, doc));
|
|
1003
|
+
closestHandle = handle;
|
|
1004
|
+
if (!autoPanStarted) {
|
|
1005
|
+
autoPan();
|
|
1006
|
+
autoPanStarted = true;
|
|
1007
|
+
}
|
|
1008
|
+
handleDomNode = validHandleResult.handleDomNode;
|
|
1009
|
+
connection = validHandleResult.connection;
|
|
1010
|
+
isValid = validHandleResult.isValid;
|
|
1011
|
+
setState({
|
|
1012
|
+
connectionPosition: closestHandle && isValid
|
|
1013
|
+
? rendererPointToPoint({
|
|
1014
|
+
x: closestHandle.x,
|
|
1015
|
+
y: closestHandle.y,
|
|
1016
|
+
}, transform)
|
|
1017
|
+
: connectionPosition,
|
|
1018
|
+
connectionStatus: getConnectionStatus(!!closestHandle, isValid),
|
|
1019
|
+
connectionEndHandle: validHandleResult.endHandle,
|
|
1020
|
+
});
|
|
1021
|
+
if (!closestHandle && !isValid && !handleDomNode) {
|
|
1022
|
+
return resetRecentHandle(prevActiveHandle);
|
|
1023
|
+
}
|
|
1024
|
+
if (connection.source !== connection.target && handleDomNode) {
|
|
1025
|
+
resetRecentHandle(prevActiveHandle);
|
|
1026
|
+
prevActiveHandle = handleDomNode;
|
|
1027
|
+
// @todo: remove the old class names "react-flow__handle-" in the next major version
|
|
1028
|
+
handleDomNode.classList.add('connecting', 'react-flow__handle-connecting');
|
|
1029
|
+
handleDomNode.classList.toggle('valid', isValid);
|
|
1030
|
+
handleDomNode.classList.toggle('react-flow__handle-valid', isValid);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function onPointerUp(event) {
|
|
1034
|
+
if ((closestHandle || handleDomNode) && connection && isValid) {
|
|
1035
|
+
onConnect?.(connection);
|
|
1036
|
+
}
|
|
1037
|
+
// it's important to get a fresh reference from the store here
|
|
1038
|
+
// in order to get the latest state of onConnectEnd
|
|
1039
|
+
getState().onConnectEnd?.(event);
|
|
1040
|
+
if (edgeUpdaterType) {
|
|
1041
|
+
onReconnectEnd?.(event);
|
|
1042
|
+
}
|
|
1043
|
+
resetRecentHandle(prevActiveHandle);
|
|
1044
|
+
cancelConnection();
|
|
1045
|
+
cancelAnimationFrame(autoPanId);
|
|
1046
|
+
autoPanStarted = false;
|
|
1047
|
+
isValid = false;
|
|
1048
|
+
connection = null;
|
|
1049
|
+
handleDomNode = null;
|
|
1050
|
+
doc.removeEventListener('mousemove', onPointerMove);
|
|
1051
|
+
doc.removeEventListener('mouseup', onPointerUp);
|
|
1052
|
+
doc.removeEventListener('touchmove', onPointerMove);
|
|
1053
|
+
doc.removeEventListener('touchend', onPointerUp);
|
|
1054
|
+
}
|
|
1055
|
+
doc.addEventListener('mousemove', onPointerMove);
|
|
1056
|
+
doc.addEventListener('mouseup', onPointerUp);
|
|
1057
|
+
doc.addEventListener('touchmove', onPointerMove);
|
|
1058
|
+
doc.addEventListener('touchend', onPointerUp);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const alwaysValid = () => true;
|
|
1062
|
+
const selector$f = (s) => ({
|
|
1063
|
+
connectionStartHandle: s.connectionStartHandle,
|
|
1064
|
+
connectOnClick: s.connectOnClick,
|
|
1065
|
+
noPanClassName: s.noPanClassName,
|
|
1066
|
+
});
|
|
1067
|
+
const connectingSelector = (nodeId, handleId, type) => (state) => {
|
|
1068
|
+
const { connectionStartHandle: startHandle, connectionEndHandle: endHandle, connectionClickStartHandle: clickHandle, } = state;
|
|
1069
|
+
return {
|
|
1070
|
+
connecting: (startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type) ||
|
|
1071
|
+
(endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type),
|
|
1072
|
+
clickConnecting: clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type,
|
|
1073
|
+
};
|
|
1074
|
+
};
|
|
1075
|
+
const Handle = forwardRef(({ type = 'source', position = Position.Top, isValidConnection, isConnectable = true, isConnectableStart = true, isConnectableEnd = true, id, onConnect, children, className, onMouseDown, onTouchStart, ...rest }, ref) => {
|
|
1076
|
+
const handleId = id || null;
|
|
1077
|
+
const isTarget = type === 'target';
|
|
1078
|
+
const store = useStoreApi();
|
|
1079
|
+
const nodeId = useNodeId();
|
|
1080
|
+
const { connectOnClick, noPanClassName } = useStore(selector$f, shallow);
|
|
1081
|
+
const { connecting, clickConnecting } = useStore(connectingSelector(nodeId, handleId, type), shallow);
|
|
1082
|
+
if (!nodeId) {
|
|
1083
|
+
store.getState().onError?.('010', errorMessages['error010']());
|
|
1084
|
+
}
|
|
1085
|
+
const onConnectExtended = (params) => {
|
|
1086
|
+
const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState();
|
|
1087
|
+
const edgeParams = {
|
|
1088
|
+
...defaultEdgeOptions,
|
|
1089
|
+
...params,
|
|
1090
|
+
};
|
|
1091
|
+
if (hasDefaultEdges) {
|
|
1092
|
+
const { edges, setEdges } = store.getState();
|
|
1093
|
+
setEdges(addEdge(edgeParams, edges));
|
|
1094
|
+
}
|
|
1095
|
+
onConnectAction?.(edgeParams);
|
|
1096
|
+
onConnect?.(edgeParams);
|
|
1097
|
+
};
|
|
1098
|
+
const onPointerDown = (event) => {
|
|
1099
|
+
if (!nodeId) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const isMouseTriggered = isMouseEvent(event);
|
|
1103
|
+
if (isConnectableStart && ((isMouseTriggered && event.button === 0) || !isMouseTriggered)) {
|
|
1104
|
+
handlePointerDown({
|
|
1105
|
+
event,
|
|
1106
|
+
handleId,
|
|
1107
|
+
nodeId,
|
|
1108
|
+
onConnect: onConnectExtended,
|
|
1109
|
+
isTarget,
|
|
1110
|
+
getState: store.getState,
|
|
1111
|
+
setState: store.setState,
|
|
1112
|
+
isValidConnection: isValidConnection || store.getState().isValidConnection || alwaysValid,
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
if (isMouseTriggered) {
|
|
1116
|
+
onMouseDown?.(event);
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
onTouchStart?.(event);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
const onClick = (event) => {
|
|
1123
|
+
const { onClickConnectStart, onClickConnectEnd, connectionClickStartHandle, connectionMode, isValidConnection: isValidConnectionStore, } = store.getState();
|
|
1124
|
+
if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (!connectionClickStartHandle) {
|
|
1128
|
+
onClickConnectStart?.(event, { nodeId, handleId, handleType: type });
|
|
1129
|
+
store.setState({ connectionClickStartHandle: { nodeId, type, handleId } });
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const doc = getHostForElement(event.target);
|
|
1133
|
+
const isValidConnectionHandler = isValidConnection || isValidConnectionStore || alwaysValid;
|
|
1134
|
+
const { connection, isValid } = isValidHandle({
|
|
1135
|
+
nodeId,
|
|
1136
|
+
id: handleId,
|
|
1137
|
+
type,
|
|
1138
|
+
}, connectionMode, connectionClickStartHandle.nodeId, connectionClickStartHandle.handleId || null, connectionClickStartHandle.type, isValidConnectionHandler, doc);
|
|
1139
|
+
if (isValid) {
|
|
1140
|
+
onConnectExtended(connection);
|
|
1141
|
+
}
|
|
1142
|
+
onClickConnectEnd?.(event);
|
|
1143
|
+
store.setState({ connectionClickStartHandle: null });
|
|
1144
|
+
};
|
|
1145
|
+
return (React.createElement("div", { "data-handleid": handleId, "data-nodeid": nodeId, "data-handlepos": position, "data-id": `${nodeId}-${handleId}-${type}`, className: cc([
|
|
1146
|
+
'react-flow__handle',
|
|
1147
|
+
`react-flow__handle-${position}`,
|
|
1148
|
+
'nodrag',
|
|
1149
|
+
noPanClassName,
|
|
1150
|
+
className,
|
|
1151
|
+
{
|
|
1152
|
+
source: !isTarget,
|
|
1153
|
+
target: isTarget,
|
|
1154
|
+
connectable: isConnectable,
|
|
1155
|
+
connectablestart: isConnectableStart,
|
|
1156
|
+
connectableend: isConnectableEnd,
|
|
1157
|
+
connecting: clickConnecting,
|
|
1158
|
+
// this class is used to style the handle when the user is connecting
|
|
1159
|
+
connectionindicator: isConnectable && ((isConnectableStart && !connecting) || (isConnectableEnd && connecting)),
|
|
1160
|
+
},
|
|
1161
|
+
]), onMouseDown: onPointerDown, onTouchStart: onPointerDown, onClick: connectOnClick ? onClick : undefined, ref: ref, ...rest }, children));
|
|
1162
|
+
});
|
|
1163
|
+
Handle.displayName = 'Handle';
|
|
1164
|
+
var Handle$1 = memo(Handle);
|
|
1165
|
+
|
|
1166
|
+
const DefaultNode = ({ data, isConnectable, targetPosition = Position.Top, sourcePosition = Position.Bottom, }) => {
|
|
1167
|
+
return (React.createElement(React.Fragment, null,
|
|
1168
|
+
React.createElement(Handle$1, { type: "target", position: targetPosition, isConnectable: isConnectable }),
|
|
1169
|
+
data?.label,
|
|
1170
|
+
React.createElement(Handle$1, { type: "source", position: sourcePosition, isConnectable: isConnectable })));
|
|
1171
|
+
};
|
|
1172
|
+
DefaultNode.displayName = 'DefaultNode';
|
|
1173
|
+
var DefaultNode$1 = memo(DefaultNode);
|
|
1174
|
+
|
|
1175
|
+
const InputNode = ({ data, isConnectable, sourcePosition = Position.Bottom }) => (React.createElement(React.Fragment, null,
|
|
1176
|
+
data?.label,
|
|
1177
|
+
React.createElement(Handle$1, { type: "source", position: sourcePosition, isConnectable: isConnectable })));
|
|
1178
|
+
InputNode.displayName = 'InputNode';
|
|
1179
|
+
var InputNode$1 = memo(InputNode);
|
|
1180
|
+
|
|
1181
|
+
const OutputNode = ({ data, isConnectable, targetPosition = Position.Top }) => (React.createElement(React.Fragment, null,
|
|
1182
|
+
React.createElement(Handle$1, { type: "target", position: targetPosition, isConnectable: isConnectable }),
|
|
1183
|
+
data?.label));
|
|
1184
|
+
OutputNode.displayName = 'OutputNode';
|
|
1185
|
+
var OutputNode$1 = memo(OutputNode);
|
|
1186
|
+
|
|
1187
|
+
const GroupNode = () => null;
|
|
1188
|
+
GroupNode.displayName = 'GroupNode';
|
|
1189
|
+
|
|
1190
|
+
const selector$e = (s) => ({
|
|
1191
|
+
selectedNodes: s.getNodes().filter((n) => n.selected),
|
|
1192
|
+
selectedEdges: s.edges.filter((e) => e.selected).map((e) => ({ ...e })),
|
|
1193
|
+
});
|
|
1194
|
+
const selectId = (obj) => obj.id;
|
|
1195
|
+
function areEqual(a, b) {
|
|
1196
|
+
return (shallow(a.selectedNodes.map(selectId), b.selectedNodes.map(selectId)) &&
|
|
1197
|
+
shallow(a.selectedEdges.map(selectId), b.selectedEdges.map(selectId)));
|
|
1198
|
+
}
|
|
1199
|
+
// This is just a helper component for calling the onSelectionChange listener.
|
|
1200
|
+
// @TODO: Now that we have the onNodesChange and on EdgesChange listeners, do we still need this component?
|
|
1201
|
+
const SelectionListener = memo(({ onSelectionChange }) => {
|
|
1202
|
+
const store = useStoreApi();
|
|
1203
|
+
const { selectedNodes, selectedEdges } = useStore(selector$e, areEqual);
|
|
1204
|
+
useEffect(() => {
|
|
1205
|
+
const params = { nodes: selectedNodes, edges: selectedEdges };
|
|
1206
|
+
onSelectionChange?.(params);
|
|
1207
|
+
store.getState().onSelectionChange.forEach((fn) => fn(params));
|
|
1208
|
+
}, [selectedNodes, selectedEdges, onSelectionChange]);
|
|
1209
|
+
return null;
|
|
1210
|
+
});
|
|
1211
|
+
SelectionListener.displayName = 'SelectionListener';
|
|
1212
|
+
const changeSelector = (s) => !!s.onSelectionChange;
|
|
1213
|
+
function Wrapper$1({ onSelectionChange }) {
|
|
1214
|
+
const storeHasSelectionChange = useStore(changeSelector);
|
|
1215
|
+
if (onSelectionChange || storeHasSelectionChange) {
|
|
1216
|
+
return React.createElement(SelectionListener, { onSelectionChange: onSelectionChange });
|
|
1217
|
+
}
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
const selector$d = (s) => ({
|
|
1222
|
+
setNodes: s.setNodes,
|
|
1223
|
+
setEdges: s.setEdges,
|
|
1224
|
+
setDefaultNodesAndEdges: s.setDefaultNodesAndEdges,
|
|
1225
|
+
setMinZoom: s.setMinZoom,
|
|
1226
|
+
setMaxZoom: s.setMaxZoom,
|
|
1227
|
+
setTranslateExtent: s.setTranslateExtent,
|
|
1228
|
+
setNodeExtent: s.setNodeExtent,
|
|
1229
|
+
reset: s.reset,
|
|
1230
|
+
});
|
|
1231
|
+
function useStoreUpdater(value, setStoreState) {
|
|
1232
|
+
useEffect(() => {
|
|
1233
|
+
if (typeof value !== 'undefined') {
|
|
1234
|
+
setStoreState(value);
|
|
1235
|
+
}
|
|
1236
|
+
}, [value]);
|
|
1237
|
+
}
|
|
1238
|
+
// updates with values in store that don't have a dedicated setter function
|
|
1239
|
+
function useDirectStoreUpdater(key, value, setState) {
|
|
1240
|
+
useEffect(() => {
|
|
1241
|
+
if (typeof value !== 'undefined') {
|
|
1242
|
+
setState({ [key]: value });
|
|
1243
|
+
}
|
|
1244
|
+
}, [value]);
|
|
1245
|
+
}
|
|
1246
|
+
const StoreUpdater = ({ nodes, edges, defaultNodes, defaultEdges, onConnect, onConnectStart, onConnectEnd, onClickConnectStart, onClickConnectEnd, nodesDraggable, nodesConnectable, nodesFocusable, edgesFocusable, edgesUpdatable, elevateNodesOnSelect, minZoom, maxZoom, nodeExtent, onNodesChange, onEdgesChange, elementsSelectable, connectionMode, snapGrid, snapToGrid, translateExtent, connectOnClick, defaultEdgeOptions, fitView, fitViewOptions, onNodesDelete, onEdgesDelete, onNodeDrag, onNodeDragStart, onNodeDragStop, onSelectionDrag, onSelectionDragStart, onSelectionDragStop, noPanClassName, nodeOrigin, rfId, autoPanOnConnect, autoPanOnNodeDrag, onError, connectionRadius, isValidConnection, nodeDragThreshold, }) => {
|
|
1247
|
+
const { setNodes, setEdges, setDefaultNodesAndEdges, setMinZoom, setMaxZoom, setTranslateExtent, setNodeExtent, reset, } = useStore(selector$d, shallow);
|
|
1248
|
+
const store = useStoreApi();
|
|
1249
|
+
useEffect(() => {
|
|
1250
|
+
const edgesWithDefaults = defaultEdges?.map((e) => ({ ...e, ...defaultEdgeOptions }));
|
|
1251
|
+
setDefaultNodesAndEdges(defaultNodes, edgesWithDefaults);
|
|
1252
|
+
return () => {
|
|
1253
|
+
reset();
|
|
1254
|
+
};
|
|
1255
|
+
}, []);
|
|
1256
|
+
useDirectStoreUpdater('defaultEdgeOptions', defaultEdgeOptions, store.setState);
|
|
1257
|
+
useDirectStoreUpdater('connectionMode', connectionMode, store.setState);
|
|
1258
|
+
useDirectStoreUpdater('onConnect', onConnect, store.setState);
|
|
1259
|
+
useDirectStoreUpdater('onConnectStart', onConnectStart, store.setState);
|
|
1260
|
+
useDirectStoreUpdater('onConnectEnd', onConnectEnd, store.setState);
|
|
1261
|
+
useDirectStoreUpdater('onClickConnectStart', onClickConnectStart, store.setState);
|
|
1262
|
+
useDirectStoreUpdater('onClickConnectEnd', onClickConnectEnd, store.setState);
|
|
1263
|
+
useDirectStoreUpdater('nodesDraggable', nodesDraggable, store.setState);
|
|
1264
|
+
useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
|
|
1265
|
+
useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState);
|
|
1266
|
+
useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState);
|
|
1267
|
+
useDirectStoreUpdater('edgesUpdatable', edgesUpdatable, store.setState);
|
|
1268
|
+
useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
|
|
1269
|
+
useDirectStoreUpdater('elevateNodesOnSelect', elevateNodesOnSelect, store.setState);
|
|
1270
|
+
useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);
|
|
1271
|
+
useDirectStoreUpdater('snapGrid', snapGrid, store.setState);
|
|
1272
|
+
useDirectStoreUpdater('onNodesChange', onNodesChange, store.setState);
|
|
1273
|
+
useDirectStoreUpdater('onEdgesChange', onEdgesChange, store.setState);
|
|
1274
|
+
useDirectStoreUpdater('connectOnClick', connectOnClick, store.setState);
|
|
1275
|
+
useDirectStoreUpdater('fitViewOnInit', fitView, store.setState);
|
|
1276
|
+
useDirectStoreUpdater('fitViewOnInitOptions', fitViewOptions, store.setState);
|
|
1277
|
+
useDirectStoreUpdater('onNodesDelete', onNodesDelete, store.setState);
|
|
1278
|
+
useDirectStoreUpdater('onEdgesDelete', onEdgesDelete, store.setState);
|
|
1279
|
+
useDirectStoreUpdater('onNodeDrag', onNodeDrag, store.setState);
|
|
1280
|
+
useDirectStoreUpdater('onNodeDragStart', onNodeDragStart, store.setState);
|
|
1281
|
+
useDirectStoreUpdater('onNodeDragStop', onNodeDragStop, store.setState);
|
|
1282
|
+
useDirectStoreUpdater('onSelectionDrag', onSelectionDrag, store.setState);
|
|
1283
|
+
useDirectStoreUpdater('onSelectionDragStart', onSelectionDragStart, store.setState);
|
|
1284
|
+
useDirectStoreUpdater('onSelectionDragStop', onSelectionDragStop, store.setState);
|
|
1285
|
+
useDirectStoreUpdater('noPanClassName', noPanClassName, store.setState);
|
|
1286
|
+
useDirectStoreUpdater('nodeOrigin', nodeOrigin, store.setState);
|
|
1287
|
+
useDirectStoreUpdater('rfId', rfId, store.setState);
|
|
1288
|
+
useDirectStoreUpdater('autoPanOnConnect', autoPanOnConnect, store.setState);
|
|
1289
|
+
useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState);
|
|
1290
|
+
useDirectStoreUpdater('onError', onError, store.setState);
|
|
1291
|
+
useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState);
|
|
1292
|
+
useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState);
|
|
1293
|
+
useDirectStoreUpdater('nodeDragThreshold', nodeDragThreshold, store.setState);
|
|
1294
|
+
useStoreUpdater(nodes, setNodes);
|
|
1295
|
+
useStoreUpdater(edges, setEdges);
|
|
1296
|
+
useStoreUpdater(minZoom, setMinZoom);
|
|
1297
|
+
useStoreUpdater(maxZoom, setMaxZoom);
|
|
1298
|
+
useStoreUpdater(translateExtent, setTranslateExtent);
|
|
1299
|
+
useStoreUpdater(nodeExtent, setNodeExtent);
|
|
1300
|
+
return null;
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
const style = { display: 'none' };
|
|
1304
|
+
const ariaLiveStyle = {
|
|
1305
|
+
position: 'absolute',
|
|
1306
|
+
width: 1,
|
|
1307
|
+
height: 1,
|
|
1308
|
+
margin: -1,
|
|
1309
|
+
border: 0,
|
|
1310
|
+
padding: 0,
|
|
1311
|
+
overflow: 'hidden',
|
|
1312
|
+
clip: 'rect(0px, 0px, 0px, 0px)',
|
|
1313
|
+
clipPath: 'inset(100%)',
|
|
1314
|
+
};
|
|
1315
|
+
const ARIA_NODE_DESC_KEY = 'react-flow__node-desc';
|
|
1316
|
+
const ARIA_EDGE_DESC_KEY = 'react-flow__edge-desc';
|
|
1317
|
+
const ARIA_LIVE_MESSAGE = 'react-flow__aria-live';
|
|
1318
|
+
const selector$c = (s) => s.ariaLiveMessage;
|
|
1319
|
+
function AriaLiveMessage({ rfId }) {
|
|
1320
|
+
const ariaLiveMessage = useStore(selector$c);
|
|
1321
|
+
return (React.createElement("div", { id: `${ARIA_LIVE_MESSAGE}-${rfId}`, "aria-live": "assertive", "aria-atomic": "true", style: ariaLiveStyle }, ariaLiveMessage));
|
|
1322
|
+
}
|
|
1323
|
+
function A11yDescriptions({ rfId, disableKeyboardA11y }) {
|
|
1324
|
+
return (React.createElement(React.Fragment, null,
|
|
1325
|
+
React.createElement("div", { id: `${ARIA_NODE_DESC_KEY}-${rfId}`, style: style },
|
|
1326
|
+
"Press enter or space to select a node.",
|
|
1327
|
+
!disableKeyboardA11y && 'You can then use the arrow keys to move the node around.',
|
|
1328
|
+
" Press delete to remove it and escape to cancel.",
|
|
1329
|
+
' '),
|
|
1330
|
+
React.createElement("div", { id: `${ARIA_EDGE_DESC_KEY}-${rfId}`, style: style }, "Press enter or space to select an edge. You can then press delete to remove it or escape to cancel."),
|
|
1331
|
+
!disableKeyboardA11y && React.createElement(AriaLiveMessage, { rfId: rfId })));
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// the keycode can be a string 'a' or an array of strings ['a', 'a+d']
|
|
1335
|
+
// a string means a single key 'a' or a combination when '+' is used 'a+d'
|
|
1336
|
+
// an array means different possibilities. Explainer: ['a', 'd+s'] here the
|
|
1337
|
+
// user can use the single key 'a' or the combination 'd' + 's'
|
|
1338
|
+
var useKeyPress = (keyCode = null, options = { actInsideInputWithModifier: true }) => {
|
|
1339
|
+
const [keyPressed, setKeyPressed] = useState(false);
|
|
1340
|
+
// we need to remember if a modifier key is pressed in order to track it
|
|
1341
|
+
const modifierPressed = useRef(false);
|
|
1342
|
+
// we need to remember the pressed keys in order to support combinations
|
|
1343
|
+
const pressedKeys = useRef(new Set([]));
|
|
1344
|
+
// keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
|
1345
|
+
// keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
|
1346
|
+
// used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
|
1347
|
+
// we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
|
1348
|
+
// 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
|
|
1349
|
+
// we can't find it in the list of keysToWatch.
|
|
1350
|
+
const [keyCodes, keysToWatch] = useMemo(() => {
|
|
1351
|
+
if (keyCode !== null) {
|
|
1352
|
+
const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode];
|
|
1353
|
+
const keys = keyCodeArr.filter((kc) => typeof kc === 'string').map((kc) => kc.split('+'));
|
|
1354
|
+
const keysFlat = keys.reduce((res, item) => res.concat(...item), []);
|
|
1355
|
+
return [keys, keysFlat];
|
|
1356
|
+
}
|
|
1357
|
+
return [[], []];
|
|
1358
|
+
}, [keyCode]);
|
|
1359
|
+
useEffect(() => {
|
|
1360
|
+
const doc = typeof document !== 'undefined' ? document : null;
|
|
1361
|
+
const target = options?.target || doc;
|
|
1362
|
+
if (keyCode !== null) {
|
|
1363
|
+
const downHandler = (event) => {
|
|
1364
|
+
modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey;
|
|
1365
|
+
const preventAction = (!modifierPressed.current || (modifierPressed.current && !options.actInsideInputWithModifier)) &&
|
|
1366
|
+
isInputDOMNode(event);
|
|
1367
|
+
if (preventAction) {
|
|
1368
|
+
return false;
|
|
1369
|
+
}
|
|
1370
|
+
const keyOrCode = useKeyOrCode(event.code, keysToWatch);
|
|
1371
|
+
pressedKeys.current.add(event[keyOrCode]);
|
|
1372
|
+
if (isMatchingKey(keyCodes, pressedKeys.current, false)) {
|
|
1373
|
+
event.preventDefault();
|
|
1374
|
+
setKeyPressed(true);
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
const upHandler = (event) => {
|
|
1378
|
+
const preventAction = (!modifierPressed.current || (modifierPressed.current && !options.actInsideInputWithModifier)) &&
|
|
1379
|
+
isInputDOMNode(event);
|
|
1380
|
+
if (preventAction) {
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
const keyOrCode = useKeyOrCode(event.code, keysToWatch);
|
|
1384
|
+
if (isMatchingKey(keyCodes, pressedKeys.current, true)) {
|
|
1385
|
+
setKeyPressed(false);
|
|
1386
|
+
pressedKeys.current.clear();
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
pressedKeys.current.delete(event[keyOrCode]);
|
|
1390
|
+
}
|
|
1391
|
+
// fix for Mac: when cmd key is pressed, keyup is not triggered for any other key, see: https://stackoverflow.com/questions/27380018/when-cmd-key-is-kept-pressed-keyup-is-not-triggered-for-any-other-key
|
|
1392
|
+
if (event.key === 'Meta') {
|
|
1393
|
+
pressedKeys.current.clear();
|
|
1394
|
+
}
|
|
1395
|
+
modifierPressed.current = false;
|
|
1396
|
+
};
|
|
1397
|
+
const resetHandler = () => {
|
|
1398
|
+
pressedKeys.current.clear();
|
|
1399
|
+
setKeyPressed(false);
|
|
1400
|
+
};
|
|
1401
|
+
target?.addEventListener('keydown', downHandler);
|
|
1402
|
+
target?.addEventListener('keyup', upHandler);
|
|
1403
|
+
window.addEventListener('blur', resetHandler);
|
|
1404
|
+
return () => {
|
|
1405
|
+
target?.removeEventListener('keydown', downHandler);
|
|
1406
|
+
target?.removeEventListener('keyup', upHandler);
|
|
1407
|
+
window.removeEventListener('blur', resetHandler);
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
}, [keyCode, setKeyPressed]);
|
|
1411
|
+
return keyPressed;
|
|
1412
|
+
};
|
|
1413
|
+
// utils
|
|
1414
|
+
function isMatchingKey(keyCodes, pressedKeys, isUp) {
|
|
1415
|
+
return (keyCodes
|
|
1416
|
+
// we only want to compare same sizes of keyCode definitions
|
|
1417
|
+
// and pressed keys. When the user specified 'Meta' as a key somewhere
|
|
1418
|
+
// this would also be truthy without this filter when user presses 'Meta' + 'r'
|
|
1419
|
+
.filter((keys) => isUp || keys.length === pressedKeys.size)
|
|
1420
|
+
// since we want to support multiple possibilities only one of the
|
|
1421
|
+
// combinations need to be part of the pressed keys
|
|
1422
|
+
.some((keys) => keys.every((k) => pressedKeys.has(k))));
|
|
1423
|
+
}
|
|
1424
|
+
function useKeyOrCode(eventCode, keysToWatch) {
|
|
1425
|
+
return keysToWatch.includes(eventCode) ? 'code' : 'key';
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
function calculateXYZPosition(node, nodeInternals, result, nodeOrigin) {
|
|
1429
|
+
const parentId = node.parentNode || node.parentId;
|
|
1430
|
+
if (!parentId) {
|
|
1431
|
+
return result;
|
|
1432
|
+
}
|
|
1433
|
+
const parentNode = nodeInternals.get(parentId);
|
|
1434
|
+
const parentNodePosition = getNodePositionWithOrigin(parentNode, nodeOrigin);
|
|
1435
|
+
return calculateXYZPosition(parentNode, nodeInternals, {
|
|
1436
|
+
x: (result.x ?? 0) + parentNodePosition.x,
|
|
1437
|
+
y: (result.y ?? 0) + parentNodePosition.y,
|
|
1438
|
+
z: (parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0) ? parentNode[internalsSymbol]?.z ?? 0 : result.z ?? 0,
|
|
1439
|
+
}, nodeOrigin);
|
|
1440
|
+
}
|
|
1441
|
+
function updateAbsoluteNodePositions(nodeInternals, nodeOrigin, parentNodes) {
|
|
1442
|
+
nodeInternals.forEach((node) => {
|
|
1443
|
+
const parentId = node.parentNode || node.parentId;
|
|
1444
|
+
if (parentId && !nodeInternals.has(parentId)) {
|
|
1445
|
+
throw new Error(`Parent node ${parentId} not found`);
|
|
1446
|
+
}
|
|
1447
|
+
if (parentId || parentNodes?.[node.id]) {
|
|
1448
|
+
const { x, y, z } = calculateXYZPosition(node, nodeInternals, {
|
|
1449
|
+
...node.position,
|
|
1450
|
+
z: node[internalsSymbol]?.z ?? 0,
|
|
1451
|
+
}, nodeOrigin);
|
|
1452
|
+
node.positionAbsolute = {
|
|
1453
|
+
x,
|
|
1454
|
+
y,
|
|
1455
|
+
};
|
|
1456
|
+
node[internalsSymbol].z = z;
|
|
1457
|
+
if (parentNodes?.[node.id]) {
|
|
1458
|
+
node[internalsSymbol].isParent = true;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
function createNodeInternals(nodes, nodeInternals, nodeOrigin, elevateNodesOnSelect) {
|
|
1464
|
+
const nextNodeInternals = new Map();
|
|
1465
|
+
const parentNodes = {};
|
|
1466
|
+
const selectedNodeZ = elevateNodesOnSelect ? 1000 : 0;
|
|
1467
|
+
nodes.forEach((node) => {
|
|
1468
|
+
const z = (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0);
|
|
1469
|
+
const currInternals = nodeInternals.get(node.id);
|
|
1470
|
+
const internals = {
|
|
1471
|
+
...node,
|
|
1472
|
+
positionAbsolute: {
|
|
1473
|
+
x: node.position.x,
|
|
1474
|
+
y: node.position.y,
|
|
1475
|
+
},
|
|
1476
|
+
};
|
|
1477
|
+
const parentId = node.parentNode || node.parentId;
|
|
1478
|
+
if (parentId) {
|
|
1479
|
+
parentNodes[parentId] = true;
|
|
1480
|
+
}
|
|
1481
|
+
const resetHandleBounds = currInternals?.type && currInternals?.type !== node.type;
|
|
1482
|
+
Object.defineProperty(internals, internalsSymbol, {
|
|
1483
|
+
enumerable: false,
|
|
1484
|
+
value: {
|
|
1485
|
+
handleBounds: resetHandleBounds ? undefined : currInternals?.[internalsSymbol]?.handleBounds,
|
|
1486
|
+
z,
|
|
1487
|
+
},
|
|
1488
|
+
});
|
|
1489
|
+
nextNodeInternals.set(node.id, internals);
|
|
1490
|
+
});
|
|
1491
|
+
updateAbsoluteNodePositions(nextNodeInternals, nodeOrigin, parentNodes);
|
|
1492
|
+
return nextNodeInternals;
|
|
1493
|
+
}
|
|
1494
|
+
function fitView(get, options = {}) {
|
|
1495
|
+
const { getNodes, width, height, minZoom, maxZoom, d3Zoom, d3Selection, fitViewOnInitDone, fitViewOnInit, nodeOrigin, } = get();
|
|
1496
|
+
const isInitialFitView = options.initial && !fitViewOnInitDone && fitViewOnInit;
|
|
1497
|
+
const d3initialized = d3Zoom && d3Selection;
|
|
1498
|
+
if (d3initialized && (isInitialFitView || !options.initial)) {
|
|
1499
|
+
const nodes = getNodes().filter((n) => {
|
|
1500
|
+
const isVisible = options.includeHiddenNodes ? n.width && n.height : !n.hidden;
|
|
1501
|
+
if (options.nodes?.length) {
|
|
1502
|
+
return isVisible && options.nodes.some((optionNode) => optionNode.id === n.id);
|
|
1503
|
+
}
|
|
1504
|
+
return isVisible;
|
|
1505
|
+
});
|
|
1506
|
+
const nodesInitialized = nodes.every((n) => n.width && n.height);
|
|
1507
|
+
if (nodes.length > 0 && nodesInitialized) {
|
|
1508
|
+
const bounds = getNodesBounds(nodes, nodeOrigin);
|
|
1509
|
+
const { x, y, zoom } = getViewportForBounds(bounds, width, height, options.minZoom ?? minZoom, options.maxZoom ?? maxZoom, options.padding ?? 0.1);
|
|
1510
|
+
const nextTransform = zoomIdentity.translate(x, y).scale(zoom);
|
|
1511
|
+
if (typeof options.duration === 'number' && options.duration > 0) {
|
|
1512
|
+
d3Zoom.transform(getD3Transition(d3Selection, options.duration), nextTransform);
|
|
1513
|
+
}
|
|
1514
|
+
else {
|
|
1515
|
+
d3Zoom.transform(d3Selection, nextTransform);
|
|
1516
|
+
}
|
|
1517
|
+
return true;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return false;
|
|
1521
|
+
}
|
|
1522
|
+
function handleControlledNodeSelectionChange(nodeChanges, nodeInternals) {
|
|
1523
|
+
nodeChanges.forEach((change) => {
|
|
1524
|
+
const node = nodeInternals.get(change.id);
|
|
1525
|
+
if (node) {
|
|
1526
|
+
nodeInternals.set(node.id, {
|
|
1527
|
+
...node,
|
|
1528
|
+
[internalsSymbol]: node[internalsSymbol],
|
|
1529
|
+
selected: change.selected,
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1533
|
+
return new Map(nodeInternals);
|
|
1534
|
+
}
|
|
1535
|
+
function handleControlledEdgeSelectionChange(edgeChanges, edges) {
|
|
1536
|
+
return edges.map((e) => {
|
|
1537
|
+
const change = edgeChanges.find((change) => change.id === e.id);
|
|
1538
|
+
if (change) {
|
|
1539
|
+
e.selected = change.selected;
|
|
1540
|
+
}
|
|
1541
|
+
return e;
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get, set }) {
|
|
1545
|
+
const { nodeInternals, edges, onNodesChange, onEdgesChange, hasDefaultNodes, hasDefaultEdges } = get();
|
|
1546
|
+
if (changedNodes?.length) {
|
|
1547
|
+
if (hasDefaultNodes) {
|
|
1548
|
+
set({ nodeInternals: handleControlledNodeSelectionChange(changedNodes, nodeInternals) });
|
|
1549
|
+
}
|
|
1550
|
+
onNodesChange?.(changedNodes);
|
|
1551
|
+
}
|
|
1552
|
+
if (changedEdges?.length) {
|
|
1553
|
+
if (hasDefaultEdges) {
|
|
1554
|
+
set({ edges: handleControlledEdgeSelectionChange(changedEdges, edges) });
|
|
1555
|
+
}
|
|
1556
|
+
onEdgesChange?.(changedEdges);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
1561
|
+
const noop = () => { };
|
|
1562
|
+
const initialViewportHelper = {
|
|
1563
|
+
zoomIn: noop,
|
|
1564
|
+
zoomOut: noop,
|
|
1565
|
+
zoomTo: noop,
|
|
1566
|
+
getZoom: () => 1,
|
|
1567
|
+
setViewport: noop,
|
|
1568
|
+
getViewport: () => ({ x: 0, y: 0, zoom: 1 }),
|
|
1569
|
+
fitView: () => false,
|
|
1570
|
+
setCenter: noop,
|
|
1571
|
+
fitBounds: noop,
|
|
1572
|
+
project: (position) => position,
|
|
1573
|
+
screenToFlowPosition: (position) => position,
|
|
1574
|
+
flowToScreenPosition: (position) => position,
|
|
1575
|
+
viewportInitialized: false,
|
|
1576
|
+
};
|
|
1577
|
+
const selector$b = (s) => ({
|
|
1578
|
+
d3Zoom: s.d3Zoom,
|
|
1579
|
+
d3Selection: s.d3Selection,
|
|
1580
|
+
});
|
|
1581
|
+
const useViewportHelper = () => {
|
|
1582
|
+
const store = useStoreApi();
|
|
1583
|
+
const { d3Zoom, d3Selection } = useStore(selector$b, shallow);
|
|
1584
|
+
const viewportHelperFunctions = useMemo(() => {
|
|
1585
|
+
if (d3Selection && d3Zoom) {
|
|
1586
|
+
return {
|
|
1587
|
+
zoomIn: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2),
|
|
1588
|
+
zoomOut: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2),
|
|
1589
|
+
zoomTo: (zoomLevel, options) => d3Zoom.scaleTo(getD3Transition(d3Selection, options?.duration), zoomLevel),
|
|
1590
|
+
getZoom: () => store.getState().transform[2],
|
|
1591
|
+
setViewport: (transform, options) => {
|
|
1592
|
+
const [x, y, zoom] = store.getState().transform;
|
|
1593
|
+
const nextTransform = zoomIdentity
|
|
1594
|
+
.translate(transform.x ?? x, transform.y ?? y)
|
|
1595
|
+
.scale(transform.zoom ?? zoom);
|
|
1596
|
+
d3Zoom.transform(getD3Transition(d3Selection, options?.duration), nextTransform);
|
|
1597
|
+
},
|
|
1598
|
+
getViewport: () => {
|
|
1599
|
+
const [x, y, zoom] = store.getState().transform;
|
|
1600
|
+
return { x, y, zoom };
|
|
1601
|
+
},
|
|
1602
|
+
fitView: (options) => fitView(store.getState, options),
|
|
1603
|
+
setCenter: (x, y, options) => {
|
|
1604
|
+
const { width, height, maxZoom } = store.getState();
|
|
1605
|
+
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
|
|
1606
|
+
const centerX = width / 2 - x * nextZoom;
|
|
1607
|
+
const centerY = height / 2 - y * nextZoom;
|
|
1608
|
+
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
|
|
1609
|
+
d3Zoom.transform(getD3Transition(d3Selection, options?.duration), transform);
|
|
1610
|
+
},
|
|
1611
|
+
fitBounds: (bounds, options) => {
|
|
1612
|
+
const { width, height, minZoom, maxZoom } = store.getState();
|
|
1613
|
+
const { x, y, zoom } = getViewportForBounds(bounds, width, height, minZoom, maxZoom, options?.padding ?? 0.1);
|
|
1614
|
+
const transform = zoomIdentity.translate(x, y).scale(zoom);
|
|
1615
|
+
d3Zoom.transform(getD3Transition(d3Selection, options?.duration), transform);
|
|
1616
|
+
},
|
|
1617
|
+
// @deprecated Use `screenToFlowPosition`.
|
|
1618
|
+
project: (position) => {
|
|
1619
|
+
const { transform, snapToGrid, snapGrid } = store.getState();
|
|
1620
|
+
console.warn('[DEPRECATED] `project` is deprecated. Instead use `screenToFlowPosition`. There is no need to subtract the react flow bounds anymore! https://reactflow.dev/api-reference/types/react-flow-instance#screen-to-flow-position');
|
|
1621
|
+
return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
|
|
1622
|
+
},
|
|
1623
|
+
screenToFlowPosition: (position) => {
|
|
1624
|
+
const { transform, snapToGrid, snapGrid, domNode } = store.getState();
|
|
1625
|
+
if (!domNode) {
|
|
1626
|
+
return position;
|
|
1627
|
+
}
|
|
1628
|
+
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
|
1629
|
+
const relativePosition = {
|
|
1630
|
+
x: position.x - domX,
|
|
1631
|
+
y: position.y - domY,
|
|
1632
|
+
};
|
|
1633
|
+
return pointToRendererPoint(relativePosition, transform, snapToGrid, snapGrid);
|
|
1634
|
+
},
|
|
1635
|
+
flowToScreenPosition: (position) => {
|
|
1636
|
+
const { transform, domNode } = store.getState();
|
|
1637
|
+
if (!domNode) {
|
|
1638
|
+
return position;
|
|
1639
|
+
}
|
|
1640
|
+
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
|
1641
|
+
const rendererPosition = rendererPointToPoint(position, transform);
|
|
1642
|
+
return {
|
|
1643
|
+
x: rendererPosition.x + domX,
|
|
1644
|
+
y: rendererPosition.y + domY,
|
|
1645
|
+
};
|
|
1646
|
+
},
|
|
1647
|
+
viewportInitialized: true,
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
return initialViewportHelper;
|
|
1651
|
+
}, [d3Zoom, d3Selection]);
|
|
1652
|
+
return viewportHelperFunctions;
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
1656
|
+
function useReactFlow() {
|
|
1657
|
+
const viewportHelper = useViewportHelper();
|
|
1658
|
+
const store = useStoreApi();
|
|
1659
|
+
const getNodes = useCallback(() => {
|
|
1660
|
+
return store
|
|
1661
|
+
.getState()
|
|
1662
|
+
.getNodes()
|
|
1663
|
+
.map((n) => ({ ...n }));
|
|
1664
|
+
}, []);
|
|
1665
|
+
const getNode = useCallback((id) => {
|
|
1666
|
+
return store.getState().nodeInternals.get(id);
|
|
1667
|
+
}, []);
|
|
1668
|
+
const getEdges = useCallback(() => {
|
|
1669
|
+
const { edges = [] } = store.getState();
|
|
1670
|
+
return edges.map((e) => ({ ...e }));
|
|
1671
|
+
}, []);
|
|
1672
|
+
const getEdge = useCallback((id) => {
|
|
1673
|
+
const { edges = [] } = store.getState();
|
|
1674
|
+
return edges.find((e) => e.id === id);
|
|
1675
|
+
}, []);
|
|
1676
|
+
const setNodes = useCallback((payload) => {
|
|
1677
|
+
const { getNodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState();
|
|
1678
|
+
const nodes = getNodes();
|
|
1679
|
+
const nextNodes = typeof payload === 'function' ? payload(nodes) : payload;
|
|
1680
|
+
if (hasDefaultNodes) {
|
|
1681
|
+
setNodes(nextNodes);
|
|
1682
|
+
}
|
|
1683
|
+
else if (onNodesChange) {
|
|
1684
|
+
const changes = nextNodes.length === 0
|
|
1685
|
+
? nodes.map((node) => ({ type: 'remove', id: node.id }))
|
|
1686
|
+
: nextNodes.map((node) => ({ item: node, type: 'reset' }));
|
|
1687
|
+
onNodesChange(changes);
|
|
1688
|
+
}
|
|
1689
|
+
}, []);
|
|
1690
|
+
const setEdges = useCallback((payload) => {
|
|
1691
|
+
const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
|
|
1692
|
+
const nextEdges = typeof payload === 'function' ? payload(edges) : payload;
|
|
1693
|
+
if (hasDefaultEdges) {
|
|
1694
|
+
setEdges(nextEdges);
|
|
1695
|
+
}
|
|
1696
|
+
else if (onEdgesChange) {
|
|
1697
|
+
const changes = nextEdges.length === 0
|
|
1698
|
+
? edges.map((edge) => ({ type: 'remove', id: edge.id }))
|
|
1699
|
+
: nextEdges.map((edge) => ({ item: edge, type: 'reset' }));
|
|
1700
|
+
onEdgesChange(changes);
|
|
1701
|
+
}
|
|
1702
|
+
}, []);
|
|
1703
|
+
const addNodes = useCallback((payload) => {
|
|
1704
|
+
const nodes = Array.isArray(payload) ? payload : [payload];
|
|
1705
|
+
const { getNodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState();
|
|
1706
|
+
if (hasDefaultNodes) {
|
|
1707
|
+
const currentNodes = getNodes();
|
|
1708
|
+
const nextNodes = [...currentNodes, ...nodes];
|
|
1709
|
+
setNodes(nextNodes);
|
|
1710
|
+
}
|
|
1711
|
+
else if (onNodesChange) {
|
|
1712
|
+
const changes = nodes.map((node) => ({ item: node, type: 'add' }));
|
|
1713
|
+
onNodesChange(changes);
|
|
1714
|
+
}
|
|
1715
|
+
}, []);
|
|
1716
|
+
const addEdges = useCallback((payload) => {
|
|
1717
|
+
const nextEdges = Array.isArray(payload) ? payload : [payload];
|
|
1718
|
+
const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
|
|
1719
|
+
if (hasDefaultEdges) {
|
|
1720
|
+
setEdges([...edges, ...nextEdges]);
|
|
1721
|
+
}
|
|
1722
|
+
else if (onEdgesChange) {
|
|
1723
|
+
const changes = nextEdges.map((edge) => ({ item: edge, type: 'add' }));
|
|
1724
|
+
onEdgesChange(changes);
|
|
1725
|
+
}
|
|
1726
|
+
}, []);
|
|
1727
|
+
const toObject = useCallback(() => {
|
|
1728
|
+
const { getNodes, edges = [], transform } = store.getState();
|
|
1729
|
+
const [x, y, zoom] = transform;
|
|
1730
|
+
return {
|
|
1731
|
+
nodes: getNodes().map((n) => ({ ...n })),
|
|
1732
|
+
edges: edges.map((e) => ({ ...e })),
|
|
1733
|
+
viewport: {
|
|
1734
|
+
x,
|
|
1735
|
+
y,
|
|
1736
|
+
zoom,
|
|
1737
|
+
},
|
|
1738
|
+
};
|
|
1739
|
+
}, []);
|
|
1740
|
+
const deleteElements = useCallback(({ nodes: nodesDeleted, edges: edgesDeleted }) => {
|
|
1741
|
+
const { nodeInternals, getNodes, edges, hasDefaultNodes, hasDefaultEdges, onNodesDelete, onEdgesDelete, onNodesChange, onEdgesChange, } = store.getState();
|
|
1742
|
+
const nodeIds = (nodesDeleted || []).map((node) => node.id);
|
|
1743
|
+
const edgeIds = (edgesDeleted || []).map((edge) => edge.id);
|
|
1744
|
+
const nodesToRemove = getNodes().reduce((res, node) => {
|
|
1745
|
+
const parentId = node.parentNode || node.parentId;
|
|
1746
|
+
const parentHit = !nodeIds.includes(node.id) && parentId && res.find((n) => n.id === parentId);
|
|
1747
|
+
const deletable = typeof node.deletable === 'boolean' ? node.deletable : true;
|
|
1748
|
+
if (deletable && (nodeIds.includes(node.id) || parentHit)) {
|
|
1749
|
+
res.push(node);
|
|
1750
|
+
}
|
|
1751
|
+
return res;
|
|
1752
|
+
}, []);
|
|
1753
|
+
const deletableEdges = edges.filter((e) => (typeof e.deletable === 'boolean' ? e.deletable : true));
|
|
1754
|
+
const initialHitEdges = deletableEdges.filter((e) => edgeIds.includes(e.id));
|
|
1755
|
+
if (nodesToRemove || initialHitEdges) {
|
|
1756
|
+
const connectedEdges = getConnectedEdges(nodesToRemove, deletableEdges);
|
|
1757
|
+
const edgesToRemove = [...initialHitEdges, ...connectedEdges];
|
|
1758
|
+
const edgeIdsToRemove = edgesToRemove.reduce((res, edge) => {
|
|
1759
|
+
if (!res.includes(edge.id)) {
|
|
1760
|
+
res.push(edge.id);
|
|
1761
|
+
}
|
|
1762
|
+
return res;
|
|
1763
|
+
}, []);
|
|
1764
|
+
if (hasDefaultEdges || hasDefaultNodes) {
|
|
1765
|
+
if (hasDefaultEdges) {
|
|
1766
|
+
store.setState({
|
|
1767
|
+
edges: edges.filter((e) => !edgeIdsToRemove.includes(e.id)),
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
if (hasDefaultNodes) {
|
|
1771
|
+
nodesToRemove.forEach((node) => {
|
|
1772
|
+
nodeInternals.delete(node.id);
|
|
1773
|
+
});
|
|
1774
|
+
store.setState({
|
|
1775
|
+
nodeInternals: new Map(nodeInternals),
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
if (edgeIdsToRemove.length > 0) {
|
|
1780
|
+
onEdgesDelete?.(edgesToRemove);
|
|
1781
|
+
if (onEdgesChange) {
|
|
1782
|
+
onEdgesChange(edgeIdsToRemove.map((id) => ({
|
|
1783
|
+
id,
|
|
1784
|
+
type: 'remove',
|
|
1785
|
+
})));
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
if (nodesToRemove.length > 0) {
|
|
1789
|
+
onNodesDelete?.(nodesToRemove);
|
|
1790
|
+
if (onNodesChange) {
|
|
1791
|
+
const nodeChanges = nodesToRemove.map((n) => ({ id: n.id, type: 'remove' }));
|
|
1792
|
+
onNodesChange(nodeChanges);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}, []);
|
|
1797
|
+
const getNodeRect = useCallback((nodeOrRect) => {
|
|
1798
|
+
const isRect = isRectObject(nodeOrRect);
|
|
1799
|
+
const node = isRect ? null : store.getState().nodeInternals.get(nodeOrRect.id);
|
|
1800
|
+
if (!isRect && !node) {
|
|
1801
|
+
return [null, null, isRect];
|
|
1802
|
+
}
|
|
1803
|
+
const nodeRect = isRect ? nodeOrRect : nodeToRect(node);
|
|
1804
|
+
return [nodeRect, node, isRect];
|
|
1805
|
+
}, []);
|
|
1806
|
+
const getIntersectingNodes = useCallback((nodeOrRect, partially = true, nodes) => {
|
|
1807
|
+
const [nodeRect, node, isRect] = getNodeRect(nodeOrRect);
|
|
1808
|
+
if (!nodeRect) {
|
|
1809
|
+
return [];
|
|
1810
|
+
}
|
|
1811
|
+
return (nodes || store.getState().getNodes()).filter((n) => {
|
|
1812
|
+
if (!isRect && (n.id === node.id || !n.positionAbsolute)) {
|
|
1813
|
+
return false;
|
|
1814
|
+
}
|
|
1815
|
+
const currNodeRect = nodeToRect(n);
|
|
1816
|
+
const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
|
|
1817
|
+
const partiallyVisible = partially && overlappingArea > 0;
|
|
1818
|
+
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
|
|
1819
|
+
});
|
|
1820
|
+
}, []);
|
|
1821
|
+
const isNodeIntersecting = useCallback((nodeOrRect, area, partially = true) => {
|
|
1822
|
+
const [nodeRect] = getNodeRect(nodeOrRect);
|
|
1823
|
+
if (!nodeRect) {
|
|
1824
|
+
return false;
|
|
1825
|
+
}
|
|
1826
|
+
const overlappingArea = getOverlappingArea(nodeRect, area);
|
|
1827
|
+
const partiallyVisible = partially && overlappingArea > 0;
|
|
1828
|
+
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
|
|
1829
|
+
}, []);
|
|
1830
|
+
return useMemo(() => {
|
|
1831
|
+
return {
|
|
1832
|
+
...viewportHelper,
|
|
1833
|
+
getNodes,
|
|
1834
|
+
getNode,
|
|
1835
|
+
getEdges,
|
|
1836
|
+
getEdge,
|
|
1837
|
+
setNodes,
|
|
1838
|
+
setEdges,
|
|
1839
|
+
addNodes,
|
|
1840
|
+
addEdges,
|
|
1841
|
+
toObject,
|
|
1842
|
+
deleteElements,
|
|
1843
|
+
getIntersectingNodes,
|
|
1844
|
+
isNodeIntersecting,
|
|
1845
|
+
};
|
|
1846
|
+
}, [
|
|
1847
|
+
viewportHelper,
|
|
1848
|
+
getNodes,
|
|
1849
|
+
getNode,
|
|
1850
|
+
getEdges,
|
|
1851
|
+
getEdge,
|
|
1852
|
+
setNodes,
|
|
1853
|
+
setEdges,
|
|
1854
|
+
addNodes,
|
|
1855
|
+
addEdges,
|
|
1856
|
+
toObject,
|
|
1857
|
+
deleteElements,
|
|
1858
|
+
getIntersectingNodes,
|
|
1859
|
+
isNodeIntersecting,
|
|
1860
|
+
]);
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
const deleteKeyOptions = { actInsideInputWithModifier: false };
|
|
1864
|
+
var useGlobalKeyHandler = ({ deleteKeyCode, multiSelectionKeyCode }) => {
|
|
1865
|
+
const store = useStoreApi();
|
|
1866
|
+
const { deleteElements } = useReactFlow();
|
|
1867
|
+
const deleteKeyPressed = useKeyPress(deleteKeyCode, deleteKeyOptions);
|
|
1868
|
+
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
|
|
1869
|
+
useEffect(() => {
|
|
1870
|
+
if (deleteKeyPressed) {
|
|
1871
|
+
const { edges, getNodes } = store.getState();
|
|
1872
|
+
const selectedNodes = getNodes().filter((node) => node.selected);
|
|
1873
|
+
const selectedEdges = edges.filter((edge) => edge.selected);
|
|
1874
|
+
deleteElements({ nodes: selectedNodes, edges: selectedEdges });
|
|
1875
|
+
store.setState({ nodesSelectionActive: false });
|
|
1876
|
+
}
|
|
1877
|
+
}, [deleteKeyPressed]);
|
|
1878
|
+
useEffect(() => {
|
|
1879
|
+
store.setState({ multiSelectionActive: multiSelectionKeyPressed });
|
|
1880
|
+
}, [multiSelectionKeyPressed]);
|
|
1881
|
+
};
|
|
1882
|
+
|
|
1883
|
+
function useResizeHandler(rendererNode) {
|
|
1884
|
+
const store = useStoreApi();
|
|
1885
|
+
useEffect(() => {
|
|
1886
|
+
let resizeObserver;
|
|
1887
|
+
const updateDimensions = () => {
|
|
1888
|
+
if (!rendererNode.current) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
const size = getDimensions(rendererNode.current);
|
|
1892
|
+
if (size.height === 0 || size.width === 0) {
|
|
1893
|
+
store.getState().onError?.('004', errorMessages['error004']());
|
|
1894
|
+
}
|
|
1895
|
+
store.setState({ width: size.width || 500, height: size.height || 500 });
|
|
1896
|
+
};
|
|
1897
|
+
updateDimensions();
|
|
1898
|
+
window.addEventListener('resize', updateDimensions);
|
|
1899
|
+
if (rendererNode.current) {
|
|
1900
|
+
resizeObserver = new ResizeObserver(() => updateDimensions());
|
|
1901
|
+
resizeObserver.observe(rendererNode.current);
|
|
1902
|
+
}
|
|
1903
|
+
return () => {
|
|
1904
|
+
window.removeEventListener('resize', updateDimensions);
|
|
1905
|
+
if (resizeObserver && rendererNode.current) {
|
|
1906
|
+
resizeObserver.unobserve(rendererNode.current);
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
}, []);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
const containerStyle = {
|
|
1913
|
+
position: 'absolute',
|
|
1914
|
+
width: '100%',
|
|
1915
|
+
height: '100%',
|
|
1916
|
+
top: 0,
|
|
1917
|
+
left: 0,
|
|
1918
|
+
};
|
|
1919
|
+
|
|
1920
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
1921
|
+
const viewChanged = (prevViewport, eventTransform) => prevViewport.x !== eventTransform.x || prevViewport.y !== eventTransform.y || prevViewport.zoom !== eventTransform.k;
|
|
1922
|
+
const eventToFlowTransform = (eventTransform) => ({
|
|
1923
|
+
x: eventTransform.x,
|
|
1924
|
+
y: eventTransform.y,
|
|
1925
|
+
zoom: eventTransform.k,
|
|
1926
|
+
});
|
|
1927
|
+
const isWrappedWithClass = (event, className) => event.target.closest(`.${className}`);
|
|
1928
|
+
const isRightClickPan = (panOnDrag, usedButton) => usedButton === 2 && Array.isArray(panOnDrag) && panOnDrag.includes(2);
|
|
1929
|
+
const wheelDelta = (event) => {
|
|
1930
|
+
const factor = event.ctrlKey && isMacOs() ? 10 : 1;
|
|
1931
|
+
return -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * factor;
|
|
1932
|
+
};
|
|
1933
|
+
const selector$a = (s) => ({
|
|
1934
|
+
d3Zoom: s.d3Zoom,
|
|
1935
|
+
d3Selection: s.d3Selection,
|
|
1936
|
+
d3ZoomHandler: s.d3ZoomHandler,
|
|
1937
|
+
userSelectionActive: s.userSelectionActive,
|
|
1938
|
+
});
|
|
1939
|
+
const ZoomPane = ({ onMove, onMoveStart, onMoveEnd, onPaneContextMenu, zoomOnScroll = true, zoomOnPinch = true, panOnScroll = false, panOnScrollSpeed = 0.5, panOnScrollMode = PanOnScrollMode.Free, zoomOnDoubleClick = true, elementsSelectable, panOnDrag = true, defaultViewport, translateExtent, minZoom, maxZoom, zoomActivationKeyCode, preventScrolling = true, children, noWheelClassName, noPanClassName, }) => {
|
|
1940
|
+
const timerId = useRef();
|
|
1941
|
+
const store = useStoreApi();
|
|
1942
|
+
const isZoomingOrPanning = useRef(false);
|
|
1943
|
+
const zoomedWithRightMouseButton = useRef(false);
|
|
1944
|
+
const zoomPane = useRef(null);
|
|
1945
|
+
const prevTransform = useRef({ x: 0, y: 0, zoom: 0 });
|
|
1946
|
+
const { d3Zoom, d3Selection, d3ZoomHandler, userSelectionActive } = useStore(selector$a, shallow);
|
|
1947
|
+
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
|
|
1948
|
+
const mouseButton = useRef(0);
|
|
1949
|
+
const isPanScrolling = useRef(false);
|
|
1950
|
+
const panScrollTimeout = useRef();
|
|
1951
|
+
useResizeHandler(zoomPane);
|
|
1952
|
+
useEffect(() => {
|
|
1953
|
+
if (zoomPane.current) {
|
|
1954
|
+
const bbox = zoomPane.current.getBoundingClientRect();
|
|
1955
|
+
const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent);
|
|
1956
|
+
const selection = select(zoomPane.current).call(d3ZoomInstance);
|
|
1957
|
+
const updatedTransform = zoomIdentity
|
|
1958
|
+
.translate(defaultViewport.x, defaultViewport.y)
|
|
1959
|
+
.scale(clamp(defaultViewport.zoom, minZoom, maxZoom));
|
|
1960
|
+
const extent = [
|
|
1961
|
+
[0, 0],
|
|
1962
|
+
[bbox.width, bbox.height],
|
|
1963
|
+
];
|
|
1964
|
+
const constrainedTransform = d3ZoomInstance.constrain()(updatedTransform, extent, translateExtent);
|
|
1965
|
+
d3ZoomInstance.transform(selection, constrainedTransform);
|
|
1966
|
+
d3ZoomInstance.wheelDelta(wheelDelta);
|
|
1967
|
+
store.setState({
|
|
1968
|
+
d3Zoom: d3ZoomInstance,
|
|
1969
|
+
d3Selection: selection,
|
|
1970
|
+
d3ZoomHandler: selection.on('wheel.zoom'),
|
|
1971
|
+
// we need to pass transform because zoom handler is not registered when we set the initial transform
|
|
1972
|
+
transform: [constrainedTransform.x, constrainedTransform.y, constrainedTransform.k],
|
|
1973
|
+
domNode: zoomPane.current.closest('.react-flow'),
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
}, []);
|
|
1977
|
+
useEffect(() => {
|
|
1978
|
+
if (d3Selection && d3Zoom) {
|
|
1979
|
+
if (panOnScroll && !zoomActivationKeyPressed && !userSelectionActive) {
|
|
1980
|
+
d3Selection.on('wheel.zoom', (event) => {
|
|
1981
|
+
if (isWrappedWithClass(event, noWheelClassName)) {
|
|
1982
|
+
return false;
|
|
1983
|
+
}
|
|
1984
|
+
event.preventDefault();
|
|
1985
|
+
event.stopImmediatePropagation();
|
|
1986
|
+
const currentZoom = d3Selection.property('__zoom').k || 1;
|
|
1987
|
+
// macos and win set ctrlKey=true for pinch gesture on a trackpad
|
|
1988
|
+
if (event.ctrlKey && zoomOnPinch) {
|
|
1989
|
+
const point = pointer(event);
|
|
1990
|
+
const pinchDelta = wheelDelta(event);
|
|
1991
|
+
const zoom = currentZoom * Math.pow(2, pinchDelta);
|
|
1992
|
+
// @ts-ignore
|
|
1993
|
+
d3Zoom.scaleTo(d3Selection, zoom, point, event);
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
// increase scroll speed in firefox
|
|
1997
|
+
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
|
1998
|
+
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
|
|
1999
|
+
let deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
|
|
2000
|
+
let deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
|
|
2001
|
+
// this enables vertical scrolling with shift + scroll on windows
|
|
2002
|
+
if (!isMacOs() && event.shiftKey && panOnScrollMode !== PanOnScrollMode.Vertical) {
|
|
2003
|
+
deltaX = event.deltaY * deltaNormalize;
|
|
2004
|
+
deltaY = 0;
|
|
2005
|
+
}
|
|
2006
|
+
d3Zoom.translateBy(d3Selection, -(deltaX / currentZoom) * panOnScrollSpeed, -(deltaY / currentZoom) * panOnScrollSpeed,
|
|
2007
|
+
// @ts-ignore
|
|
2008
|
+
{ internal: true });
|
|
2009
|
+
const nextViewport = eventToFlowTransform(d3Selection.property('__zoom'));
|
|
2010
|
+
const { onViewportChangeStart, onViewportChange, onViewportChangeEnd } = store.getState();
|
|
2011
|
+
clearTimeout(panScrollTimeout.current);
|
|
2012
|
+
// for pan on scroll we need to handle the event calls on our own
|
|
2013
|
+
// we can't use the start, zoom and end events from d3-zoom
|
|
2014
|
+
// because start and move gets called on every scroll event and not once at the beginning
|
|
2015
|
+
if (!isPanScrolling.current) {
|
|
2016
|
+
isPanScrolling.current = true;
|
|
2017
|
+
onMoveStart?.(event, nextViewport);
|
|
2018
|
+
onViewportChangeStart?.(nextViewport);
|
|
2019
|
+
}
|
|
2020
|
+
if (isPanScrolling.current) {
|
|
2021
|
+
onMove?.(event, nextViewport);
|
|
2022
|
+
onViewportChange?.(nextViewport);
|
|
2023
|
+
panScrollTimeout.current = setTimeout(() => {
|
|
2024
|
+
onMoveEnd?.(event, nextViewport);
|
|
2025
|
+
onViewportChangeEnd?.(nextViewport);
|
|
2026
|
+
isPanScrolling.current = false;
|
|
2027
|
+
}, 150);
|
|
2028
|
+
}
|
|
2029
|
+
}, { passive: false });
|
|
2030
|
+
}
|
|
2031
|
+
else if (typeof d3ZoomHandler !== 'undefined') {
|
|
2032
|
+
d3Selection.on('wheel.zoom', function (event, d) {
|
|
2033
|
+
// we still want to enable pinch zooming even if preventScrolling is set to false
|
|
2034
|
+
const invalidEvent = !preventScrolling && event.type === 'wheel' && !event.ctrlKey;
|
|
2035
|
+
if (invalidEvent || isWrappedWithClass(event, noWheelClassName)) {
|
|
2036
|
+
return null;
|
|
2037
|
+
}
|
|
2038
|
+
event.preventDefault();
|
|
2039
|
+
d3ZoomHandler.call(this, event, d);
|
|
2040
|
+
}, { passive: false });
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
}, [
|
|
2044
|
+
userSelectionActive,
|
|
2045
|
+
panOnScroll,
|
|
2046
|
+
panOnScrollMode,
|
|
2047
|
+
d3Selection,
|
|
2048
|
+
d3Zoom,
|
|
2049
|
+
d3ZoomHandler,
|
|
2050
|
+
zoomActivationKeyPressed,
|
|
2051
|
+
zoomOnPinch,
|
|
2052
|
+
preventScrolling,
|
|
2053
|
+
noWheelClassName,
|
|
2054
|
+
onMoveStart,
|
|
2055
|
+
onMove,
|
|
2056
|
+
onMoveEnd,
|
|
2057
|
+
]);
|
|
2058
|
+
useEffect(() => {
|
|
2059
|
+
if (d3Zoom) {
|
|
2060
|
+
d3Zoom.on('start', (event) => {
|
|
2061
|
+
if (!event.sourceEvent || event.sourceEvent.internal) {
|
|
2062
|
+
return null;
|
|
2063
|
+
}
|
|
2064
|
+
// we need to remember it here, because it's always 0 in the "zoom" event
|
|
2065
|
+
mouseButton.current = event.sourceEvent?.button;
|
|
2066
|
+
const { onViewportChangeStart } = store.getState();
|
|
2067
|
+
const flowTransform = eventToFlowTransform(event.transform);
|
|
2068
|
+
isZoomingOrPanning.current = true;
|
|
2069
|
+
prevTransform.current = flowTransform;
|
|
2070
|
+
if (event.sourceEvent?.type === 'mousedown') {
|
|
2071
|
+
store.setState({ paneDragging: true });
|
|
2072
|
+
}
|
|
2073
|
+
onViewportChangeStart?.(flowTransform);
|
|
2074
|
+
onMoveStart?.(event.sourceEvent, flowTransform);
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
}, [d3Zoom, onMoveStart]);
|
|
2078
|
+
useEffect(() => {
|
|
2079
|
+
if (d3Zoom) {
|
|
2080
|
+
if (userSelectionActive && !isZoomingOrPanning.current) {
|
|
2081
|
+
d3Zoom.on('zoom', null);
|
|
2082
|
+
}
|
|
2083
|
+
else if (!userSelectionActive) {
|
|
2084
|
+
d3Zoom.on('zoom', (event) => {
|
|
2085
|
+
const { onViewportChange } = store.getState();
|
|
2086
|
+
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
|
|
2087
|
+
zoomedWithRightMouseButton.current = !!(onPaneContextMenu && isRightClickPan(panOnDrag, mouseButton.current ?? 0));
|
|
2088
|
+
if ((onMove || onViewportChange) && !event.sourceEvent?.internal) {
|
|
2089
|
+
const flowTransform = eventToFlowTransform(event.transform);
|
|
2090
|
+
onViewportChange?.(flowTransform);
|
|
2091
|
+
onMove?.(event.sourceEvent, flowTransform);
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}, [userSelectionActive, d3Zoom, onMove, panOnDrag, onPaneContextMenu]);
|
|
2097
|
+
useEffect(() => {
|
|
2098
|
+
if (d3Zoom) {
|
|
2099
|
+
d3Zoom.on('end', (event) => {
|
|
2100
|
+
if (!event.sourceEvent || event.sourceEvent.internal) {
|
|
2101
|
+
return null;
|
|
2102
|
+
}
|
|
2103
|
+
const { onViewportChangeEnd } = store.getState();
|
|
2104
|
+
isZoomingOrPanning.current = false;
|
|
2105
|
+
store.setState({ paneDragging: false });
|
|
2106
|
+
if (onPaneContextMenu &&
|
|
2107
|
+
isRightClickPan(panOnDrag, mouseButton.current ?? 0) &&
|
|
2108
|
+
!zoomedWithRightMouseButton.current) {
|
|
2109
|
+
onPaneContextMenu(event.sourceEvent);
|
|
2110
|
+
}
|
|
2111
|
+
zoomedWithRightMouseButton.current = false;
|
|
2112
|
+
if ((onMoveEnd || onViewportChangeEnd) && viewChanged(prevTransform.current, event.transform)) {
|
|
2113
|
+
const flowTransform = eventToFlowTransform(event.transform);
|
|
2114
|
+
prevTransform.current = flowTransform;
|
|
2115
|
+
clearTimeout(timerId.current);
|
|
2116
|
+
timerId.current = setTimeout(() => {
|
|
2117
|
+
onViewportChangeEnd?.(flowTransform);
|
|
2118
|
+
onMoveEnd?.(event.sourceEvent, flowTransform);
|
|
2119
|
+
}, panOnScroll ? 150 : 0);
|
|
2120
|
+
}
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
}, [d3Zoom, panOnScroll, panOnDrag, onMoveEnd, onPaneContextMenu]);
|
|
2124
|
+
useEffect(() => {
|
|
2125
|
+
if (d3Zoom) {
|
|
2126
|
+
d3Zoom.filter((event) => {
|
|
2127
|
+
const zoomScroll = zoomActivationKeyPressed || zoomOnScroll;
|
|
2128
|
+
const pinchZoom = zoomOnPinch && event.ctrlKey;
|
|
2129
|
+
if ((panOnDrag === true || (Array.isArray(panOnDrag) && panOnDrag.includes(1))) &&
|
|
2130
|
+
event.button === 1 &&
|
|
2131
|
+
event.type === 'mousedown' &&
|
|
2132
|
+
(isWrappedWithClass(event, 'react-flow__node') || isWrappedWithClass(event, 'react-flow__edge'))) {
|
|
2133
|
+
return true;
|
|
2134
|
+
}
|
|
2135
|
+
// if all interactions are disabled, we prevent all zoom events
|
|
2136
|
+
if (!panOnDrag && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
|
|
2137
|
+
return false;
|
|
2138
|
+
}
|
|
2139
|
+
// during a selection we prevent all other interactions
|
|
2140
|
+
if (userSelectionActive) {
|
|
2141
|
+
return false;
|
|
2142
|
+
}
|
|
2143
|
+
// if zoom on double click is disabled, we prevent the double click event
|
|
2144
|
+
if (!zoomOnDoubleClick && event.type === 'dblclick') {
|
|
2145
|
+
return false;
|
|
2146
|
+
}
|
|
2147
|
+
// if the target element is inside an element with the nowheel class, we prevent zooming
|
|
2148
|
+
if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
|
|
2149
|
+
return false;
|
|
2150
|
+
}
|
|
2151
|
+
// if the target element is inside an element with the nopan class, we prevent panning
|
|
2152
|
+
if (isWrappedWithClass(event, noPanClassName) &&
|
|
2153
|
+
(event.type !== 'wheel' || (panOnScroll && event.type === 'wheel' && !zoomActivationKeyPressed))) {
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
// when there is no scroll handling enabled, we prevent all wheel events
|
|
2160
|
+
if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
|
|
2161
|
+
return false;
|
|
2162
|
+
}
|
|
2163
|
+
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
|
|
2164
|
+
if (!panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) {
|
|
2165
|
+
return false;
|
|
2166
|
+
}
|
|
2167
|
+
// if the pane is only movable using allowed clicks
|
|
2168
|
+
if (Array.isArray(panOnDrag) && !panOnDrag.includes(event.button) && event.type === 'mousedown') {
|
|
2169
|
+
return false;
|
|
2170
|
+
}
|
|
2171
|
+
// We only allow right clicks if pan on drag is set to right click
|
|
2172
|
+
const buttonAllowed = (Array.isArray(panOnDrag) && panOnDrag.includes(event.button)) || !event.button || event.button <= 1;
|
|
2173
|
+
// default filter for d3-zoom
|
|
2174
|
+
return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed;
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
}, [
|
|
2178
|
+
userSelectionActive,
|
|
2179
|
+
d3Zoom,
|
|
2180
|
+
zoomOnScroll,
|
|
2181
|
+
zoomOnPinch,
|
|
2182
|
+
panOnScroll,
|
|
2183
|
+
zoomOnDoubleClick,
|
|
2184
|
+
panOnDrag,
|
|
2185
|
+
elementsSelectable,
|
|
2186
|
+
zoomActivationKeyPressed,
|
|
2187
|
+
]);
|
|
2188
|
+
return (React.createElement("div", { className: "react-flow__renderer", ref: zoomPane, style: containerStyle }, children));
|
|
2189
|
+
};
|
|
2190
|
+
|
|
2191
|
+
const selector$9 = (s) => ({
|
|
2192
|
+
userSelectionActive: s.userSelectionActive,
|
|
2193
|
+
userSelectionRect: s.userSelectionRect,
|
|
2194
|
+
});
|
|
2195
|
+
function UserSelection() {
|
|
2196
|
+
const { userSelectionActive, userSelectionRect } = useStore(selector$9, shallow);
|
|
2197
|
+
const isActive = userSelectionActive && userSelectionRect;
|
|
2198
|
+
if (!isActive) {
|
|
2199
|
+
return null;
|
|
2200
|
+
}
|
|
2201
|
+
return (React.createElement("div", { className: "react-flow__selection react-flow__container", style: {
|
|
2202
|
+
width: userSelectionRect.width,
|
|
2203
|
+
height: userSelectionRect.height,
|
|
2204
|
+
transform: `translate(${userSelectionRect.x}px, ${userSelectionRect.y}px)`,
|
|
2205
|
+
} }));
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
function handleParentExpand(res, updateItem) {
|
|
2209
|
+
const parentId = updateItem.parentNode || updateItem.parentId;
|
|
2210
|
+
const parent = res.find((e) => e.id === parentId);
|
|
2211
|
+
if (parent) {
|
|
2212
|
+
const extendWidth = updateItem.position.x + updateItem.width - parent.width;
|
|
2213
|
+
const extendHeight = updateItem.position.y + updateItem.height - parent.height;
|
|
2214
|
+
if (extendWidth > 0 || extendHeight > 0 || updateItem.position.x < 0 || updateItem.position.y < 0) {
|
|
2215
|
+
parent.style = { ...parent.style } || {};
|
|
2216
|
+
parent.style.width = parent.style.width ?? parent.width;
|
|
2217
|
+
parent.style.height = parent.style.height ?? parent.height;
|
|
2218
|
+
if (extendWidth > 0) {
|
|
2219
|
+
parent.style.width += extendWidth;
|
|
2220
|
+
}
|
|
2221
|
+
if (extendHeight > 0) {
|
|
2222
|
+
parent.style.height += extendHeight;
|
|
2223
|
+
}
|
|
2224
|
+
if (updateItem.position.x < 0) {
|
|
2225
|
+
const xDiff = Math.abs(updateItem.position.x);
|
|
2226
|
+
parent.position.x = parent.position.x - xDiff;
|
|
2227
|
+
parent.style.width += xDiff;
|
|
2228
|
+
updateItem.position.x = 0;
|
|
2229
|
+
}
|
|
2230
|
+
if (updateItem.position.y < 0) {
|
|
2231
|
+
const yDiff = Math.abs(updateItem.position.y);
|
|
2232
|
+
parent.position.y = parent.position.y - yDiff;
|
|
2233
|
+
parent.style.height += yDiff;
|
|
2234
|
+
updateItem.position.y = 0;
|
|
2235
|
+
}
|
|
2236
|
+
parent.width = parent.style.width;
|
|
2237
|
+
parent.height = parent.style.height;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
function applyChanges(changes, elements) {
|
|
2242
|
+
// we need this hack to handle the setNodes and setEdges function of the useReactFlow hook for controlled flows
|
|
2243
|
+
if (changes.some((c) => c.type === 'reset')) {
|
|
2244
|
+
return changes.filter((c) => c.type === 'reset').map((c) => c.item);
|
|
2245
|
+
}
|
|
2246
|
+
const initElements = changes.filter((c) => c.type === 'add').map((c) => c.item);
|
|
2247
|
+
return elements.reduce((res, item) => {
|
|
2248
|
+
const currentChanges = changes.filter((c) => c.id === item.id);
|
|
2249
|
+
if (currentChanges.length === 0) {
|
|
2250
|
+
res.push(item);
|
|
2251
|
+
return res;
|
|
2252
|
+
}
|
|
2253
|
+
const updateItem = { ...item };
|
|
2254
|
+
for (const currentChange of currentChanges) {
|
|
2255
|
+
if (currentChange) {
|
|
2256
|
+
switch (currentChange.type) {
|
|
2257
|
+
case 'select': {
|
|
2258
|
+
updateItem.selected = currentChange.selected;
|
|
2259
|
+
break;
|
|
2260
|
+
}
|
|
2261
|
+
case 'position': {
|
|
2262
|
+
if (typeof currentChange.position !== 'undefined') {
|
|
2263
|
+
updateItem.position = currentChange.position;
|
|
2264
|
+
}
|
|
2265
|
+
if (typeof currentChange.positionAbsolute !== 'undefined') {
|
|
2266
|
+
updateItem.positionAbsolute = currentChange.positionAbsolute;
|
|
2267
|
+
}
|
|
2268
|
+
if (typeof currentChange.dragging !== 'undefined') {
|
|
2269
|
+
updateItem.dragging = currentChange.dragging;
|
|
2270
|
+
}
|
|
2271
|
+
if (updateItem.expandParent) {
|
|
2272
|
+
handleParentExpand(res, updateItem);
|
|
2273
|
+
}
|
|
2274
|
+
break;
|
|
2275
|
+
}
|
|
2276
|
+
case 'dimensions': {
|
|
2277
|
+
if (typeof currentChange.dimensions !== 'undefined') {
|
|
2278
|
+
updateItem.width = currentChange.dimensions.width;
|
|
2279
|
+
updateItem.height = currentChange.dimensions.height;
|
|
2280
|
+
}
|
|
2281
|
+
if (typeof currentChange.updateStyle !== 'undefined') {
|
|
2282
|
+
updateItem.style = { ...(updateItem.style || {}), ...currentChange.dimensions };
|
|
2283
|
+
}
|
|
2284
|
+
if (typeof currentChange.resizing === 'boolean') {
|
|
2285
|
+
updateItem.resizing = currentChange.resizing;
|
|
2286
|
+
}
|
|
2287
|
+
if (updateItem.expandParent) {
|
|
2288
|
+
handleParentExpand(res, updateItem);
|
|
2289
|
+
}
|
|
2290
|
+
break;
|
|
2291
|
+
}
|
|
2292
|
+
case 'remove': {
|
|
2293
|
+
return res;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
res.push(updateItem);
|
|
2299
|
+
return res;
|
|
2300
|
+
}, initElements);
|
|
2301
|
+
}
|
|
2302
|
+
function applyNodeChanges(changes, nodes) {
|
|
2303
|
+
return applyChanges(changes, nodes);
|
|
2304
|
+
}
|
|
2305
|
+
function applyEdgeChanges(changes, edges) {
|
|
2306
|
+
return applyChanges(changes, edges);
|
|
2307
|
+
}
|
|
2308
|
+
const createSelectionChange = (id, selected) => ({
|
|
2309
|
+
id,
|
|
2310
|
+
type: 'select',
|
|
2311
|
+
selected,
|
|
2312
|
+
});
|
|
2313
|
+
function getSelectionChanges(items, selectedIds) {
|
|
2314
|
+
return items.reduce((res, item) => {
|
|
2315
|
+
const willBeSelected = selectedIds.includes(item.id);
|
|
2316
|
+
if (!item.selected && willBeSelected) {
|
|
2317
|
+
item.selected = true;
|
|
2318
|
+
res.push(createSelectionChange(item.id, true));
|
|
2319
|
+
}
|
|
2320
|
+
else if (item.selected && !willBeSelected) {
|
|
2321
|
+
item.selected = false;
|
|
2322
|
+
res.push(createSelectionChange(item.id, false));
|
|
2323
|
+
}
|
|
2324
|
+
return res;
|
|
2325
|
+
}, []);
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
/**
|
|
2329
|
+
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift
|
|
2330
|
+
*/
|
|
2331
|
+
const wrapHandler = (handler, containerRef) => {
|
|
2332
|
+
return (event) => {
|
|
2333
|
+
if (event.target !== containerRef.current) {
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
handler?.(event);
|
|
2337
|
+
};
|
|
2338
|
+
};
|
|
2339
|
+
const selector$8 = (s) => ({
|
|
2340
|
+
userSelectionActive: s.userSelectionActive,
|
|
2341
|
+
elementsSelectable: s.elementsSelectable,
|
|
2342
|
+
dragging: s.paneDragging,
|
|
2343
|
+
});
|
|
2344
|
+
const Pane = memo(({ isSelecting, selectionMode = SelectionMode.Full, panOnDrag, onSelectionStart, onSelectionEnd, onPaneClick, onPaneContextMenu, onPaneScroll, onPaneMouseEnter, onPaneMouseMove, onPaneMouseLeave, children, }) => {
|
|
2345
|
+
const container = useRef(null);
|
|
2346
|
+
const store = useStoreApi();
|
|
2347
|
+
const prevSelectedNodesCount = useRef(0);
|
|
2348
|
+
const prevSelectedEdgesCount = useRef(0);
|
|
2349
|
+
const containerBounds = useRef();
|
|
2350
|
+
const { userSelectionActive, elementsSelectable, dragging } = useStore(selector$8, shallow);
|
|
2351
|
+
const resetUserSelection = () => {
|
|
2352
|
+
store.setState({ userSelectionActive: false, userSelectionRect: null });
|
|
2353
|
+
prevSelectedNodesCount.current = 0;
|
|
2354
|
+
prevSelectedEdgesCount.current = 0;
|
|
2355
|
+
};
|
|
2356
|
+
const onClick = (event) => {
|
|
2357
|
+
onPaneClick?.(event);
|
|
2358
|
+
store.getState().resetSelectedElements();
|
|
2359
|
+
store.setState({ nodesSelectionActive: false });
|
|
2360
|
+
};
|
|
2361
|
+
const onContextMenu = (event) => {
|
|
2362
|
+
if (Array.isArray(panOnDrag) && panOnDrag?.includes(2)) {
|
|
2363
|
+
event.preventDefault();
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
onPaneContextMenu?.(event);
|
|
2367
|
+
};
|
|
2368
|
+
const onWheel = onPaneScroll ? (event) => onPaneScroll(event) : undefined;
|
|
2369
|
+
const onMouseDown = (event) => {
|
|
2370
|
+
const { resetSelectedElements, domNode } = store.getState();
|
|
2371
|
+
containerBounds.current = domNode?.getBoundingClientRect();
|
|
2372
|
+
if (!elementsSelectable ||
|
|
2373
|
+
!isSelecting ||
|
|
2374
|
+
event.button !== 0 ||
|
|
2375
|
+
event.target !== container.current ||
|
|
2376
|
+
!containerBounds.current) {
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
const { x, y } = getEventPosition(event, containerBounds.current);
|
|
2380
|
+
resetSelectedElements();
|
|
2381
|
+
store.setState({
|
|
2382
|
+
userSelectionRect: {
|
|
2383
|
+
width: 0,
|
|
2384
|
+
height: 0,
|
|
2385
|
+
startX: x,
|
|
2386
|
+
startY: y,
|
|
2387
|
+
x,
|
|
2388
|
+
y,
|
|
2389
|
+
},
|
|
2390
|
+
});
|
|
2391
|
+
onSelectionStart?.(event);
|
|
2392
|
+
};
|
|
2393
|
+
const onMouseMove = (event) => {
|
|
2394
|
+
const { userSelectionRect, nodeInternals, edges, transform, onNodesChange, onEdgesChange, nodeOrigin, getNodes } = store.getState();
|
|
2395
|
+
if (!isSelecting || !containerBounds.current || !userSelectionRect) {
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
store.setState({ userSelectionActive: true, nodesSelectionActive: false });
|
|
2399
|
+
const mousePos = getEventPosition(event, containerBounds.current);
|
|
2400
|
+
const startX = userSelectionRect.startX ?? 0;
|
|
2401
|
+
const startY = userSelectionRect.startY ?? 0;
|
|
2402
|
+
const nextUserSelectRect = {
|
|
2403
|
+
...userSelectionRect,
|
|
2404
|
+
x: mousePos.x < startX ? mousePos.x : startX,
|
|
2405
|
+
y: mousePos.y < startY ? mousePos.y : startY,
|
|
2406
|
+
width: Math.abs(mousePos.x - startX),
|
|
2407
|
+
height: Math.abs(mousePos.y - startY),
|
|
2408
|
+
};
|
|
2409
|
+
const nodes = getNodes();
|
|
2410
|
+
const selectedNodes = getNodesInside(nodeInternals, nextUserSelectRect, transform, selectionMode === SelectionMode.Partial, true, nodeOrigin);
|
|
2411
|
+
const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id);
|
|
2412
|
+
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
|
2413
|
+
if (prevSelectedNodesCount.current !== selectedNodeIds.length) {
|
|
2414
|
+
prevSelectedNodesCount.current = selectedNodeIds.length;
|
|
2415
|
+
const changes = getSelectionChanges(nodes, selectedNodeIds);
|
|
2416
|
+
if (changes.length) {
|
|
2417
|
+
onNodesChange?.(changes);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
if (prevSelectedEdgesCount.current !== selectedEdgeIds.length) {
|
|
2421
|
+
prevSelectedEdgesCount.current = selectedEdgeIds.length;
|
|
2422
|
+
const changes = getSelectionChanges(edges, selectedEdgeIds);
|
|
2423
|
+
if (changes.length) {
|
|
2424
|
+
onEdgesChange?.(changes);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
store.setState({
|
|
2428
|
+
userSelectionRect: nextUserSelectRect,
|
|
2429
|
+
});
|
|
2430
|
+
};
|
|
2431
|
+
const onMouseUp = (event) => {
|
|
2432
|
+
if (event.button !== 0) {
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
const { userSelectionRect } = store.getState();
|
|
2436
|
+
// We only want to trigger click functions when in selection mode if
|
|
2437
|
+
// the user did not move the mouse.
|
|
2438
|
+
if (!userSelectionActive && userSelectionRect && event.target === container.current) {
|
|
2439
|
+
onClick?.(event);
|
|
2440
|
+
}
|
|
2441
|
+
store.setState({ nodesSelectionActive: prevSelectedNodesCount.current > 0 });
|
|
2442
|
+
resetUserSelection();
|
|
2443
|
+
onSelectionEnd?.(event);
|
|
2444
|
+
};
|
|
2445
|
+
const onMouseLeave = (event) => {
|
|
2446
|
+
if (userSelectionActive) {
|
|
2447
|
+
store.setState({ nodesSelectionActive: prevSelectedNodesCount.current > 0 });
|
|
2448
|
+
onSelectionEnd?.(event);
|
|
2449
|
+
}
|
|
2450
|
+
resetUserSelection();
|
|
2451
|
+
};
|
|
2452
|
+
const hasActiveSelection = elementsSelectable && (isSelecting || userSelectionActive);
|
|
2453
|
+
return (React.createElement("div", { className: cc(['react-flow__pane', { dragging, selection: isSelecting }]), onClick: hasActiveSelection ? undefined : wrapHandler(onClick, container), onContextMenu: wrapHandler(onContextMenu, container), onWheel: wrapHandler(onWheel, container), onMouseEnter: hasActiveSelection ? undefined : onPaneMouseEnter, onMouseDown: hasActiveSelection ? onMouseDown : undefined, onMouseMove: hasActiveSelection ? onMouseMove : onPaneMouseMove, onMouseUp: hasActiveSelection ? onMouseUp : undefined, onMouseLeave: hasActiveSelection ? onMouseLeave : onPaneMouseLeave, ref: container, style: containerStyle },
|
|
2454
|
+
children,
|
|
2455
|
+
React.createElement(UserSelection, null)));
|
|
2456
|
+
});
|
|
2457
|
+
Pane.displayName = 'Pane';
|
|
2458
|
+
|
|
2459
|
+
function isParentSelected(node, nodeInternals) {
|
|
2460
|
+
const parentId = node.parentNode || node.parentId;
|
|
2461
|
+
if (!parentId) {
|
|
2462
|
+
return false;
|
|
2463
|
+
}
|
|
2464
|
+
const parentNode = nodeInternals.get(parentId);
|
|
2465
|
+
if (!parentNode) {
|
|
2466
|
+
return false;
|
|
2467
|
+
}
|
|
2468
|
+
if (parentNode.selected) {
|
|
2469
|
+
return true;
|
|
2470
|
+
}
|
|
2471
|
+
return isParentSelected(parentNode, nodeInternals);
|
|
2472
|
+
}
|
|
2473
|
+
function hasSelector(target, selector, nodeRef) {
|
|
2474
|
+
let current = target;
|
|
2475
|
+
do {
|
|
2476
|
+
if (current?.matches(selector))
|
|
2477
|
+
return true;
|
|
2478
|
+
if (current === nodeRef.current)
|
|
2479
|
+
return false;
|
|
2480
|
+
current = current.parentElement;
|
|
2481
|
+
} while (current);
|
|
2482
|
+
return false;
|
|
2483
|
+
}
|
|
2484
|
+
// looks for all selected nodes and created a NodeDragItem for each of them
|
|
2485
|
+
function getDragItems(nodeInternals, nodesDraggable, mousePos, nodeId) {
|
|
2486
|
+
return Array.from(nodeInternals.values())
|
|
2487
|
+
.filter((n) => (n.selected || n.id === nodeId) &&
|
|
2488
|
+
(!n.parentNode || n.parentId || !isParentSelected(n, nodeInternals)) &&
|
|
2489
|
+
(n.draggable || (nodesDraggable && typeof n.draggable === 'undefined')))
|
|
2490
|
+
.map((n) => ({
|
|
2491
|
+
id: n.id,
|
|
2492
|
+
position: n.position || { x: 0, y: 0 },
|
|
2493
|
+
positionAbsolute: n.positionAbsolute || { x: 0, y: 0 },
|
|
2494
|
+
distance: {
|
|
2495
|
+
x: mousePos.x - (n.positionAbsolute?.x ?? 0),
|
|
2496
|
+
y: mousePos.y - (n.positionAbsolute?.y ?? 0),
|
|
2497
|
+
},
|
|
2498
|
+
delta: {
|
|
2499
|
+
x: 0,
|
|
2500
|
+
y: 0,
|
|
2501
|
+
},
|
|
2502
|
+
extent: n.extent,
|
|
2503
|
+
parentNode: n.parentNode || n.parentId,
|
|
2504
|
+
parentId: n.parentNode || n.parentId,
|
|
2505
|
+
width: n.width,
|
|
2506
|
+
height: n.height,
|
|
2507
|
+
expandParent: n.expandParent,
|
|
2508
|
+
}));
|
|
2509
|
+
}
|
|
2510
|
+
function clampNodeExtent(node, extent) {
|
|
2511
|
+
if (!extent || extent === 'parent') {
|
|
2512
|
+
return extent;
|
|
2513
|
+
}
|
|
2514
|
+
return [extent[0], [extent[1][0] - (node.width || 0), extent[1][1] - (node.height || 0)]];
|
|
2515
|
+
}
|
|
2516
|
+
function calcNextPosition(node, nextPosition, nodeInternals, nodeExtent, nodeOrigin = [0, 0], onError) {
|
|
2517
|
+
const clampedNodeExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
|
2518
|
+
let currentExtent = clampedNodeExtent;
|
|
2519
|
+
const parentId = node.parentNode || node.parentId;
|
|
2520
|
+
if (node.extent === 'parent' && !node.expandParent) {
|
|
2521
|
+
if (parentId && node.width && node.height) {
|
|
2522
|
+
const parent = nodeInternals.get(parentId);
|
|
2523
|
+
const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, nodeOrigin).positionAbsolute;
|
|
2524
|
+
currentExtent =
|
|
2525
|
+
parent && isNumeric(parentX) && isNumeric(parentY) && isNumeric(parent.width) && isNumeric(parent.height)
|
|
2526
|
+
? [
|
|
2527
|
+
[parentX + node.width * nodeOrigin[0], parentY + node.height * nodeOrigin[1]],
|
|
2528
|
+
[
|
|
2529
|
+
parentX + parent.width - node.width + node.width * nodeOrigin[0],
|
|
2530
|
+
parentY + parent.height - node.height + node.height * nodeOrigin[1],
|
|
2531
|
+
],
|
|
2532
|
+
]
|
|
2533
|
+
: currentExtent;
|
|
2534
|
+
}
|
|
2535
|
+
else {
|
|
2536
|
+
onError?.('005', errorMessages['error005']());
|
|
2537
|
+
currentExtent = clampedNodeExtent;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
else if (node.extent && parentId && node.extent !== 'parent') {
|
|
2541
|
+
const parent = nodeInternals.get(parentId);
|
|
2542
|
+
const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, nodeOrigin).positionAbsolute;
|
|
2543
|
+
currentExtent = [
|
|
2544
|
+
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
|
|
2545
|
+
[node.extent[1][0] + parentX, node.extent[1][1] + parentY],
|
|
2546
|
+
];
|
|
2547
|
+
}
|
|
2548
|
+
let parentPosition = { x: 0, y: 0 };
|
|
2549
|
+
if (parentId) {
|
|
2550
|
+
const parentNode = nodeInternals.get(parentId);
|
|
2551
|
+
parentPosition = getNodePositionWithOrigin(parentNode, nodeOrigin).positionAbsolute;
|
|
2552
|
+
}
|
|
2553
|
+
const positionAbsolute = currentExtent && currentExtent !== 'parent'
|
|
2554
|
+
? clampPosition(nextPosition, currentExtent)
|
|
2555
|
+
: nextPosition;
|
|
2556
|
+
return {
|
|
2557
|
+
position: {
|
|
2558
|
+
x: positionAbsolute.x - parentPosition.x,
|
|
2559
|
+
y: positionAbsolute.y - parentPosition.y,
|
|
2560
|
+
},
|
|
2561
|
+
positionAbsolute,
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
// returns two params:
|
|
2565
|
+
// 1. the dragged node (or the first of the list, if we are dragging a node selection)
|
|
2566
|
+
// 2. array of selected nodes (for multi selections)
|
|
2567
|
+
function getEventHandlerParams({ nodeId, dragItems, nodeInternals, }) {
|
|
2568
|
+
const extentedDragItems = dragItems.map((n) => {
|
|
2569
|
+
const node = nodeInternals.get(n.id);
|
|
2570
|
+
return {
|
|
2571
|
+
...node,
|
|
2572
|
+
position: n.position,
|
|
2573
|
+
positionAbsolute: n.positionAbsolute,
|
|
2574
|
+
};
|
|
2575
|
+
});
|
|
2576
|
+
return [nodeId ? extentedDragItems.find((n) => n.id === nodeId) : extentedDragItems[0], extentedDragItems];
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
const getHandleBounds = (selector, nodeElement, zoom, nodeOrigin) => {
|
|
2580
|
+
const handles = nodeElement.querySelectorAll(selector);
|
|
2581
|
+
if (!handles || !handles.length) {
|
|
2582
|
+
return null;
|
|
2583
|
+
}
|
|
2584
|
+
const handlesArray = Array.from(handles);
|
|
2585
|
+
const nodeBounds = nodeElement.getBoundingClientRect();
|
|
2586
|
+
const nodeOffset = {
|
|
2587
|
+
x: nodeBounds.width * nodeOrigin[0],
|
|
2588
|
+
y: nodeBounds.height * nodeOrigin[1],
|
|
2589
|
+
};
|
|
2590
|
+
return handlesArray.map((handle) => {
|
|
2591
|
+
const handleBounds = handle.getBoundingClientRect();
|
|
2592
|
+
return {
|
|
2593
|
+
id: handle.getAttribute('data-handleid'),
|
|
2594
|
+
position: handle.getAttribute('data-handlepos'),
|
|
2595
|
+
x: (handleBounds.left - nodeBounds.left - nodeOffset.x) / zoom,
|
|
2596
|
+
y: (handleBounds.top - nodeBounds.top - nodeOffset.y) / zoom,
|
|
2597
|
+
...getDimensions(handle),
|
|
2598
|
+
};
|
|
2599
|
+
});
|
|
2600
|
+
};
|
|
2601
|
+
function getMouseHandler(id, getState, handler) {
|
|
2602
|
+
return handler === undefined
|
|
2603
|
+
? handler
|
|
2604
|
+
: (event) => {
|
|
2605
|
+
const node = getState().nodeInternals.get(id);
|
|
2606
|
+
if (node) {
|
|
2607
|
+
handler(event, { ...node });
|
|
2608
|
+
}
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
// this handler is called by
|
|
2612
|
+
// 1. the click handler when node is not draggable or selectNodesOnDrag = false
|
|
2613
|
+
// or
|
|
2614
|
+
// 2. the on drag start handler when node is draggable and selectNodesOnDrag = true
|
|
2615
|
+
function handleNodeClick({ id, store, unselect = false, nodeRef, }) {
|
|
2616
|
+
const { addSelectedNodes, unselectNodesAndEdges, multiSelectionActive, nodeInternals, onError } = store.getState();
|
|
2617
|
+
const node = nodeInternals.get(id);
|
|
2618
|
+
if (!node) {
|
|
2619
|
+
onError?.('012', errorMessages['error012'](id));
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
store.setState({ nodesSelectionActive: false });
|
|
2623
|
+
if (!node.selected) {
|
|
2624
|
+
addSelectedNodes([id]);
|
|
2625
|
+
}
|
|
2626
|
+
else if (unselect || (node.selected && multiSelectionActive)) {
|
|
2627
|
+
unselectNodesAndEdges({ nodes: [node], edges: [] });
|
|
2628
|
+
requestAnimationFrame(() => nodeRef?.current?.blur());
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
function useGetPointerPosition() {
|
|
2633
|
+
const store = useStoreApi();
|
|
2634
|
+
// returns the pointer position projected to the RF coordinate system
|
|
2635
|
+
const getPointerPosition = useCallback(({ sourceEvent }) => {
|
|
2636
|
+
const { transform, snapGrid, snapToGrid } = store.getState();
|
|
2637
|
+
const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX;
|
|
2638
|
+
const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY;
|
|
2639
|
+
const pointerPos = {
|
|
2640
|
+
x: (x - transform[0]) / transform[2],
|
|
2641
|
+
y: (y - transform[1]) / transform[2],
|
|
2642
|
+
};
|
|
2643
|
+
// we need the snapped position in order to be able to skip unnecessary drag events
|
|
2644
|
+
return {
|
|
2645
|
+
xSnapped: snapToGrid ? snapGrid[0] * Math.round(pointerPos.x / snapGrid[0]) : pointerPos.x,
|
|
2646
|
+
ySnapped: snapToGrid ? snapGrid[1] * Math.round(pointerPos.y / snapGrid[1]) : pointerPos.y,
|
|
2647
|
+
...pointerPos,
|
|
2648
|
+
};
|
|
2649
|
+
}, []);
|
|
2650
|
+
return getPointerPosition;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
function wrapSelectionDragFunc(selectionFunc) {
|
|
2654
|
+
return (event, _, nodes) => selectionFunc?.(event, nodes);
|
|
2655
|
+
}
|
|
2656
|
+
function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, nodeId, isSelectable, selectNodesOnDrag, }) {
|
|
2657
|
+
const store = useStoreApi();
|
|
2658
|
+
const [dragging, setDragging] = useState(false);
|
|
2659
|
+
const dragItems = useRef([]);
|
|
2660
|
+
const lastPos = useRef({ x: null, y: null });
|
|
2661
|
+
const autoPanId = useRef(0);
|
|
2662
|
+
const containerBounds = useRef(null);
|
|
2663
|
+
const mousePosition = useRef({ x: 0, y: 0 });
|
|
2664
|
+
const dragEvent = useRef(null);
|
|
2665
|
+
const autoPanStarted = useRef(false);
|
|
2666
|
+
const dragStarted = useRef(false);
|
|
2667
|
+
const abortDrag = useRef(false);
|
|
2668
|
+
const getPointerPosition = useGetPointerPosition();
|
|
2669
|
+
useEffect(() => {
|
|
2670
|
+
if (nodeRef?.current) {
|
|
2671
|
+
const selection = select(nodeRef.current);
|
|
2672
|
+
const updateNodes = ({ x, y }) => {
|
|
2673
|
+
const { nodeInternals, onNodeDrag, onSelectionDrag, updateNodePositions, nodeExtent, snapGrid, snapToGrid, nodeOrigin, onError, } = store.getState();
|
|
2674
|
+
lastPos.current = { x, y };
|
|
2675
|
+
let hasChange = false;
|
|
2676
|
+
let nodesBox = { x: 0, y: 0, x2: 0, y2: 0 };
|
|
2677
|
+
if (dragItems.current.length > 1 && nodeExtent) {
|
|
2678
|
+
const rect = getNodesBounds(dragItems.current, nodeOrigin);
|
|
2679
|
+
nodesBox = rectToBox(rect);
|
|
2680
|
+
}
|
|
2681
|
+
dragItems.current = dragItems.current.map((n) => {
|
|
2682
|
+
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
|
|
2683
|
+
if (snapToGrid) {
|
|
2684
|
+
nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]);
|
|
2685
|
+
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
|
|
2686
|
+
}
|
|
2687
|
+
// if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node
|
|
2688
|
+
// based on its position so that the node stays at it's position relative to the selection.
|
|
2689
|
+
const adjustedNodeExtent = [
|
|
2690
|
+
[nodeExtent[0][0], nodeExtent[0][1]],
|
|
2691
|
+
[nodeExtent[1][0], nodeExtent[1][1]],
|
|
2692
|
+
];
|
|
2693
|
+
if (dragItems.current.length > 1 && nodeExtent && !n.extent) {
|
|
2694
|
+
adjustedNodeExtent[0][0] = n.positionAbsolute.x - nodesBox.x + nodeExtent[0][0];
|
|
2695
|
+
adjustedNodeExtent[1][0] = n.positionAbsolute.x + (n.width ?? 0) - nodesBox.x2 + nodeExtent[1][0];
|
|
2696
|
+
adjustedNodeExtent[0][1] = n.positionAbsolute.y - nodesBox.y + nodeExtent[0][1];
|
|
2697
|
+
adjustedNodeExtent[1][1] = n.positionAbsolute.y + (n.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
|
|
2698
|
+
}
|
|
2699
|
+
const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, adjustedNodeExtent, nodeOrigin, onError);
|
|
2700
|
+
// we want to make sure that we only fire a change event when there is a change
|
|
2701
|
+
hasChange = hasChange || n.position.x !== updatedPos.position.x || n.position.y !== updatedPos.position.y;
|
|
2702
|
+
n.position = updatedPos.position;
|
|
2703
|
+
n.positionAbsolute = updatedPos.positionAbsolute;
|
|
2704
|
+
return n;
|
|
2705
|
+
});
|
|
2706
|
+
if (!hasChange) {
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
updateNodePositions(dragItems.current, true, true);
|
|
2710
|
+
setDragging(true);
|
|
2711
|
+
const onDrag = nodeId ? onNodeDrag : wrapSelectionDragFunc(onSelectionDrag);
|
|
2712
|
+
if (onDrag && dragEvent.current) {
|
|
2713
|
+
const [currentNode, nodes] = getEventHandlerParams({
|
|
2714
|
+
nodeId,
|
|
2715
|
+
dragItems: dragItems.current,
|
|
2716
|
+
nodeInternals,
|
|
2717
|
+
});
|
|
2718
|
+
onDrag(dragEvent.current, currentNode, nodes);
|
|
2719
|
+
}
|
|
2720
|
+
};
|
|
2721
|
+
const autoPan = () => {
|
|
2722
|
+
if (!containerBounds.current) {
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
const [xMovement, yMovement] = calcAutoPan(mousePosition.current, containerBounds.current);
|
|
2726
|
+
if (xMovement !== 0 || yMovement !== 0) {
|
|
2727
|
+
const { transform, panBy } = store.getState();
|
|
2728
|
+
lastPos.current.x = (lastPos.current.x ?? 0) - xMovement / transform[2];
|
|
2729
|
+
lastPos.current.y = (lastPos.current.y ?? 0) - yMovement / transform[2];
|
|
2730
|
+
if (panBy({ x: xMovement, y: yMovement })) {
|
|
2731
|
+
updateNodes(lastPos.current);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
autoPanId.current = requestAnimationFrame(autoPan);
|
|
2735
|
+
};
|
|
2736
|
+
const startDrag = (event) => {
|
|
2737
|
+
const { nodeInternals, multiSelectionActive, nodesDraggable, unselectNodesAndEdges, onNodeDragStart, onSelectionDragStart, } = store.getState();
|
|
2738
|
+
dragStarted.current = true;
|
|
2739
|
+
const onStart = nodeId ? onNodeDragStart : wrapSelectionDragFunc(onSelectionDragStart);
|
|
2740
|
+
if ((!selectNodesOnDrag || !isSelectable) && !multiSelectionActive && nodeId) {
|
|
2741
|
+
if (!nodeInternals.get(nodeId)?.selected) {
|
|
2742
|
+
// we need to reset selected nodes when selectNodesOnDrag=false
|
|
2743
|
+
unselectNodesAndEdges();
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
if (nodeId && isSelectable && selectNodesOnDrag) {
|
|
2747
|
+
handleNodeClick({
|
|
2748
|
+
id: nodeId,
|
|
2749
|
+
store,
|
|
2750
|
+
nodeRef: nodeRef,
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
const pointerPos = getPointerPosition(event);
|
|
2754
|
+
lastPos.current = pointerPos;
|
|
2755
|
+
dragItems.current = getDragItems(nodeInternals, nodesDraggable, pointerPos, nodeId);
|
|
2756
|
+
if (onStart && dragItems.current) {
|
|
2757
|
+
const [currentNode, nodes] = getEventHandlerParams({
|
|
2758
|
+
nodeId,
|
|
2759
|
+
dragItems: dragItems.current,
|
|
2760
|
+
nodeInternals,
|
|
2761
|
+
});
|
|
2762
|
+
onStart(event.sourceEvent, currentNode, nodes);
|
|
2763
|
+
}
|
|
2764
|
+
};
|
|
2765
|
+
if (disabled) {
|
|
2766
|
+
selection.on('.drag', null);
|
|
2767
|
+
}
|
|
2768
|
+
else {
|
|
2769
|
+
const dragHandler = drag()
|
|
2770
|
+
.on('start', (event) => {
|
|
2771
|
+
const { domNode, nodeDragThreshold } = store.getState();
|
|
2772
|
+
if (nodeDragThreshold === 0) {
|
|
2773
|
+
startDrag(event);
|
|
2774
|
+
}
|
|
2775
|
+
abortDrag.current = false;
|
|
2776
|
+
const pointerPos = getPointerPosition(event);
|
|
2777
|
+
lastPos.current = pointerPos;
|
|
2778
|
+
containerBounds.current = domNode?.getBoundingClientRect() || null;
|
|
2779
|
+
mousePosition.current = getEventPosition(event.sourceEvent, containerBounds.current);
|
|
2780
|
+
})
|
|
2781
|
+
.on('drag', (event) => {
|
|
2782
|
+
const pointerPos = getPointerPosition(event);
|
|
2783
|
+
const { autoPanOnNodeDrag, nodeDragThreshold } = store.getState();
|
|
2784
|
+
if (event.sourceEvent.type === 'touchmove' && event.sourceEvent.touches.length > 1) {
|
|
2785
|
+
abortDrag.current = true;
|
|
2786
|
+
}
|
|
2787
|
+
if (abortDrag.current) {
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
if (!autoPanStarted.current && dragStarted.current && autoPanOnNodeDrag) {
|
|
2791
|
+
autoPanStarted.current = true;
|
|
2792
|
+
autoPan();
|
|
2793
|
+
}
|
|
2794
|
+
if (!dragStarted.current) {
|
|
2795
|
+
const x = pointerPos.xSnapped - (lastPos?.current?.x ?? 0);
|
|
2796
|
+
const y = pointerPos.ySnapped - (lastPos?.current?.y ?? 0);
|
|
2797
|
+
const distance = Math.sqrt(x * x + y * y);
|
|
2798
|
+
if (distance > nodeDragThreshold) {
|
|
2799
|
+
startDrag(event);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
// skip events without movement
|
|
2803
|
+
if ((lastPos.current.x !== pointerPos.xSnapped || lastPos.current.y !== pointerPos.ySnapped) &&
|
|
2804
|
+
dragItems.current &&
|
|
2805
|
+
dragStarted.current) {
|
|
2806
|
+
dragEvent.current = event.sourceEvent;
|
|
2807
|
+
mousePosition.current = getEventPosition(event.sourceEvent, containerBounds.current);
|
|
2808
|
+
updateNodes(pointerPos);
|
|
2809
|
+
}
|
|
2810
|
+
})
|
|
2811
|
+
.on('end', (event) => {
|
|
2812
|
+
if (!dragStarted.current || abortDrag.current) {
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
setDragging(false);
|
|
2816
|
+
autoPanStarted.current = false;
|
|
2817
|
+
dragStarted.current = false;
|
|
2818
|
+
cancelAnimationFrame(autoPanId.current);
|
|
2819
|
+
if (dragItems.current) {
|
|
2820
|
+
const { updateNodePositions, nodeInternals, onNodeDragStop, onSelectionDragStop } = store.getState();
|
|
2821
|
+
const onStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop);
|
|
2822
|
+
updateNodePositions(dragItems.current, false, false);
|
|
2823
|
+
if (onStop) {
|
|
2824
|
+
const [currentNode, nodes] = getEventHandlerParams({
|
|
2825
|
+
nodeId,
|
|
2826
|
+
dragItems: dragItems.current,
|
|
2827
|
+
nodeInternals,
|
|
2828
|
+
});
|
|
2829
|
+
onStop(event.sourceEvent, currentNode, nodes);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
})
|
|
2833
|
+
.filter((event) => {
|
|
2834
|
+
const target = event.target;
|
|
2835
|
+
const isDraggable = !event.button &&
|
|
2836
|
+
(!noDragClassName || !hasSelector(target, `.${noDragClassName}`, nodeRef)) &&
|
|
2837
|
+
(!handleSelector || hasSelector(target, handleSelector, nodeRef));
|
|
2838
|
+
return isDraggable;
|
|
2839
|
+
});
|
|
2840
|
+
selection.call(dragHandler);
|
|
2841
|
+
return () => {
|
|
2842
|
+
selection.on('.drag', null);
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
}, [
|
|
2847
|
+
nodeRef,
|
|
2848
|
+
disabled,
|
|
2849
|
+
noDragClassName,
|
|
2850
|
+
handleSelector,
|
|
2851
|
+
isSelectable,
|
|
2852
|
+
store,
|
|
2853
|
+
nodeId,
|
|
2854
|
+
selectNodesOnDrag,
|
|
2855
|
+
getPointerPosition,
|
|
2856
|
+
]);
|
|
2857
|
+
return dragging;
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
function useUpdateNodePositions() {
|
|
2861
|
+
const store = useStoreApi();
|
|
2862
|
+
const updatePositions = useCallback((params) => {
|
|
2863
|
+
const { nodeInternals, nodeExtent, updateNodePositions, getNodes, snapToGrid, snapGrid, onError, nodesDraggable } = store.getState();
|
|
2864
|
+
const selectedNodes = getNodes().filter((n) => n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined')));
|
|
2865
|
+
// by default a node moves 5px on each key press, or 20px if shift is pressed
|
|
2866
|
+
// if snap grid is enabled, we use that for the velocity.
|
|
2867
|
+
const xVelo = snapToGrid ? snapGrid[0] : 5;
|
|
2868
|
+
const yVelo = snapToGrid ? snapGrid[1] : 5;
|
|
2869
|
+
const factor = params.isShiftPressed ? 4 : 1;
|
|
2870
|
+
const positionDiffX = params.x * xVelo * factor;
|
|
2871
|
+
const positionDiffY = params.y * yVelo * factor;
|
|
2872
|
+
const nodeUpdates = selectedNodes.map((n) => {
|
|
2873
|
+
if (n.positionAbsolute) {
|
|
2874
|
+
const nextPosition = { x: n.positionAbsolute.x + positionDiffX, y: n.positionAbsolute.y + positionDiffY };
|
|
2875
|
+
if (snapToGrid) {
|
|
2876
|
+
nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]);
|
|
2877
|
+
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
|
|
2878
|
+
}
|
|
2879
|
+
const { positionAbsolute, position } = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent, undefined, onError);
|
|
2880
|
+
n.position = position;
|
|
2881
|
+
n.positionAbsolute = positionAbsolute;
|
|
2882
|
+
}
|
|
2883
|
+
return n;
|
|
2884
|
+
});
|
|
2885
|
+
updateNodePositions(nodeUpdates, true, false);
|
|
2886
|
+
}, []);
|
|
2887
|
+
return updatePositions;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
const arrowKeyDiffs = {
|
|
2891
|
+
ArrowUp: { x: 0, y: -1 },
|
|
2892
|
+
ArrowDown: { x: 0, y: 1 },
|
|
2893
|
+
ArrowLeft: { x: -1, y: 0 },
|
|
2894
|
+
ArrowRight: { x: 1, y: 0 },
|
|
2895
|
+
};
|
|
2896
|
+
var wrapNode = (NodeComponent) => {
|
|
2897
|
+
const NodeWrapper = ({ id, type, data, xPos, yPos, xPosOrigin, yPosOrigin, selected, onClick, onMouseEnter, onMouseMove, onMouseLeave, onContextMenu, onDoubleClick, style, className, isDraggable, isSelectable, isConnectable, isFocusable, selectNodesOnDrag, sourcePosition, targetPosition, hidden, resizeObserver, dragHandle, zIndex, isParent, noDragClassName, noPanClassName, initialized, disableKeyboardA11y, ariaLabel, rfId, hasHandleBounds, }) => {
|
|
2898
|
+
const store = useStoreApi();
|
|
2899
|
+
const nodeRef = useRef(null);
|
|
2900
|
+
const prevNodeRef = useRef(null);
|
|
2901
|
+
const prevSourcePosition = useRef(sourcePosition);
|
|
2902
|
+
const prevTargetPosition = useRef(targetPosition);
|
|
2903
|
+
const prevType = useRef(type);
|
|
2904
|
+
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
|
2905
|
+
const updatePositions = useUpdateNodePositions();
|
|
2906
|
+
const onMouseEnterHandler = getMouseHandler(id, store.getState, onMouseEnter);
|
|
2907
|
+
const onMouseMoveHandler = getMouseHandler(id, store.getState, onMouseMove);
|
|
2908
|
+
const onMouseLeaveHandler = getMouseHandler(id, store.getState, onMouseLeave);
|
|
2909
|
+
const onContextMenuHandler = getMouseHandler(id, store.getState, onContextMenu);
|
|
2910
|
+
const onDoubleClickHandler = getMouseHandler(id, store.getState, onDoubleClick);
|
|
2911
|
+
const onSelectNodeHandler = (event) => {
|
|
2912
|
+
const { nodeDragThreshold } = store.getState();
|
|
2913
|
+
if (isSelectable && (!selectNodesOnDrag || !isDraggable || nodeDragThreshold > 0)) {
|
|
2914
|
+
// this handler gets called within the drag start event when selectNodesOnDrag=true
|
|
2915
|
+
handleNodeClick({
|
|
2916
|
+
id,
|
|
2917
|
+
store,
|
|
2918
|
+
nodeRef,
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2921
|
+
if (onClick) {
|
|
2922
|
+
const node = store.getState().nodeInternals.get(id);
|
|
2923
|
+
if (node) {
|
|
2924
|
+
onClick(event, { ...node });
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
};
|
|
2928
|
+
const onKeyDown = (event) => {
|
|
2929
|
+
if (isInputDOMNode(event)) {
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
if (disableKeyboardA11y) {
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
if (elementSelectionKeys.includes(event.key) && isSelectable) {
|
|
2936
|
+
const unselect = event.key === 'Escape';
|
|
2937
|
+
handleNodeClick({
|
|
2938
|
+
id,
|
|
2939
|
+
store,
|
|
2940
|
+
unselect,
|
|
2941
|
+
nodeRef,
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
else if (isDraggable && selected && Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) {
|
|
2945
|
+
store.setState({
|
|
2946
|
+
ariaLiveMessage: `Moved selected node ${event.key
|
|
2947
|
+
.replace('Arrow', '')
|
|
2948
|
+
.toLowerCase()}. New position, x: ${~~xPos}, y: ${~~yPos}`,
|
|
2949
|
+
});
|
|
2950
|
+
updatePositions({
|
|
2951
|
+
x: arrowKeyDiffs[event.key].x,
|
|
2952
|
+
y: arrowKeyDiffs[event.key].y,
|
|
2953
|
+
isShiftPressed: event.shiftKey,
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
};
|
|
2957
|
+
useEffect(() => {
|
|
2958
|
+
return () => {
|
|
2959
|
+
if (prevNodeRef.current) {
|
|
2960
|
+
resizeObserver?.unobserve(prevNodeRef.current);
|
|
2961
|
+
prevNodeRef.current = null;
|
|
2962
|
+
}
|
|
2963
|
+
};
|
|
2964
|
+
}, []);
|
|
2965
|
+
useEffect(() => {
|
|
2966
|
+
if (nodeRef.current && !hidden) {
|
|
2967
|
+
const currNode = nodeRef.current;
|
|
2968
|
+
if (!initialized || !hasHandleBounds || prevNodeRef.current !== currNode) {
|
|
2969
|
+
// At this point we always want to make sure that the node gets re-measured / re-initialized.
|
|
2970
|
+
// We need to unobserve it first in case it is still observed
|
|
2971
|
+
if (prevNodeRef.current) {
|
|
2972
|
+
resizeObserver?.unobserve(prevNodeRef.current);
|
|
2973
|
+
}
|
|
2974
|
+
resizeObserver?.observe(currNode);
|
|
2975
|
+
prevNodeRef.current = currNode;
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
}, [hidden, initialized, hasHandleBounds]);
|
|
2979
|
+
useEffect(() => {
|
|
2980
|
+
// when the user programmatically changes the source or handle position, we re-initialize the node
|
|
2981
|
+
const typeChanged = prevType.current !== type;
|
|
2982
|
+
const sourcePosChanged = prevSourcePosition.current !== sourcePosition;
|
|
2983
|
+
const targetPosChanged = prevTargetPosition.current !== targetPosition;
|
|
2984
|
+
if (nodeRef.current && (typeChanged || sourcePosChanged || targetPosChanged)) {
|
|
2985
|
+
if (typeChanged) {
|
|
2986
|
+
prevType.current = type;
|
|
2987
|
+
}
|
|
2988
|
+
if (sourcePosChanged) {
|
|
2989
|
+
prevSourcePosition.current = sourcePosition;
|
|
2990
|
+
}
|
|
2991
|
+
if (targetPosChanged) {
|
|
2992
|
+
prevTargetPosition.current = targetPosition;
|
|
2993
|
+
}
|
|
2994
|
+
store.getState().updateNodeDimensions([{ id, nodeElement: nodeRef.current, forceUpdate: true }]);
|
|
2995
|
+
}
|
|
2996
|
+
}, [id, type, sourcePosition, targetPosition]);
|
|
2997
|
+
const dragging = useDrag({
|
|
2998
|
+
nodeRef,
|
|
2999
|
+
disabled: hidden || !isDraggable,
|
|
3000
|
+
noDragClassName,
|
|
3001
|
+
handleSelector: dragHandle,
|
|
3002
|
+
nodeId: id,
|
|
3003
|
+
isSelectable,
|
|
3004
|
+
selectNodesOnDrag,
|
|
3005
|
+
});
|
|
3006
|
+
if (hidden) {
|
|
3007
|
+
return null;
|
|
3008
|
+
}
|
|
3009
|
+
return (React.createElement("div", { className: cc([
|
|
3010
|
+
'react-flow__node',
|
|
3011
|
+
`react-flow__node-${type}`,
|
|
3012
|
+
{
|
|
3013
|
+
// this is overwritable by passing `nopan` as a class name
|
|
3014
|
+
[noPanClassName]: isDraggable,
|
|
3015
|
+
},
|
|
3016
|
+
className,
|
|
3017
|
+
{
|
|
3018
|
+
selected,
|
|
3019
|
+
selectable: isSelectable,
|
|
3020
|
+
parent: isParent,
|
|
3021
|
+
dragging,
|
|
3022
|
+
},
|
|
3023
|
+
]), ref: nodeRef, style: {
|
|
3024
|
+
zIndex,
|
|
3025
|
+
transform: `translate(${xPosOrigin}px,${yPosOrigin}px)`,
|
|
3026
|
+
pointerEvents: hasPointerEvents ? 'all' : 'none',
|
|
3027
|
+
visibility: initialized ? 'visible' : 'hidden',
|
|
3028
|
+
...style,
|
|
3029
|
+
}, "data-id": id, "data-testid": `rf__node-${id}`, onMouseEnter: onMouseEnterHandler, onMouseMove: onMouseMoveHandler, onMouseLeave: onMouseLeaveHandler, onContextMenu: onContextMenuHandler, onClick: onSelectNodeHandler, onDoubleClick: onDoubleClickHandler, onKeyDown: isFocusable ? onKeyDown : undefined, tabIndex: isFocusable ? 0 : undefined, role: isFocusable ? 'button' : undefined, "aria-describedby": disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`, "aria-label": ariaLabel },
|
|
3030
|
+
React.createElement(Provider, { value: id },
|
|
3031
|
+
React.createElement(NodeComponent, { id: id, data: data, type: type, xPos: xPos, yPos: yPos, selected: selected, isConnectable: isConnectable, sourcePosition: sourcePosition, targetPosition: targetPosition, dragging: dragging, dragHandle: dragHandle, zIndex: zIndex }))));
|
|
3032
|
+
};
|
|
3033
|
+
NodeWrapper.displayName = 'NodeWrapper';
|
|
3034
|
+
return memo(NodeWrapper);
|
|
3035
|
+
};
|
|
3036
|
+
|
|
3037
|
+
/**
|
|
3038
|
+
* The nodes selection rectangle gets displayed when a user
|
|
3039
|
+
* made a selection with on or several nodes
|
|
3040
|
+
*/
|
|
3041
|
+
const selector$7 = (s) => {
|
|
3042
|
+
const selectedNodes = s.getNodes().filter((n) => n.selected);
|
|
3043
|
+
return {
|
|
3044
|
+
...getNodesBounds(selectedNodes, s.nodeOrigin),
|
|
3045
|
+
transformString: `translate(${s.transform[0]}px,${s.transform[1]}px) scale(${s.transform[2]})`,
|
|
3046
|
+
userSelectionActive: s.userSelectionActive,
|
|
3047
|
+
};
|
|
3048
|
+
};
|
|
3049
|
+
function NodesSelection({ onSelectionContextMenu, noPanClassName, disableKeyboardA11y }) {
|
|
3050
|
+
const store = useStoreApi();
|
|
3051
|
+
const { width, height, x: left, y: top, transformString, userSelectionActive } = useStore(selector$7, shallow);
|
|
3052
|
+
const updatePositions = useUpdateNodePositions();
|
|
3053
|
+
const nodeRef = useRef(null);
|
|
3054
|
+
useEffect(() => {
|
|
3055
|
+
if (!disableKeyboardA11y) {
|
|
3056
|
+
nodeRef.current?.focus({
|
|
3057
|
+
preventScroll: true,
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
}, [disableKeyboardA11y]);
|
|
3061
|
+
useDrag({
|
|
3062
|
+
nodeRef,
|
|
3063
|
+
});
|
|
3064
|
+
if (userSelectionActive || !width || !height) {
|
|
3065
|
+
return null;
|
|
3066
|
+
}
|
|
3067
|
+
const onContextMenu = onSelectionContextMenu
|
|
3068
|
+
? (event) => {
|
|
3069
|
+
const selectedNodes = store
|
|
3070
|
+
.getState()
|
|
3071
|
+
.getNodes()
|
|
3072
|
+
.filter((n) => n.selected);
|
|
3073
|
+
onSelectionContextMenu(event, selectedNodes);
|
|
3074
|
+
}
|
|
3075
|
+
: undefined;
|
|
3076
|
+
const onKeyDown = (event) => {
|
|
3077
|
+
if (Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) {
|
|
3078
|
+
updatePositions({
|
|
3079
|
+
x: arrowKeyDiffs[event.key].x,
|
|
3080
|
+
y: arrowKeyDiffs[event.key].y,
|
|
3081
|
+
isShiftPressed: event.shiftKey,
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
3085
|
+
return (React.createElement("div", { className: cc(['react-flow__nodesselection', 'react-flow__container', noPanClassName]), style: {
|
|
3086
|
+
transform: transformString,
|
|
3087
|
+
} },
|
|
3088
|
+
React.createElement("div", { ref: nodeRef, className: "react-flow__nodesselection-rect", onContextMenu: onContextMenu, tabIndex: disableKeyboardA11y ? undefined : -1, onKeyDown: disableKeyboardA11y ? undefined : onKeyDown, style: {
|
|
3089
|
+
width,
|
|
3090
|
+
height,
|
|
3091
|
+
top,
|
|
3092
|
+
left,
|
|
3093
|
+
} })));
|
|
3094
|
+
}
|
|
3095
|
+
var NodesSelection$1 = memo(NodesSelection);
|
|
3096
|
+
|
|
3097
|
+
const selector$6 = (s) => s.nodesSelectionActive;
|
|
3098
|
+
const FlowRenderer = ({ children, onPaneClick, onPaneMouseEnter, onPaneMouseMove, onPaneMouseLeave, onPaneContextMenu, onPaneScroll, deleteKeyCode, onMove, onMoveStart, onMoveEnd, selectionKeyCode, selectionOnDrag, selectionMode, onSelectionStart, onSelectionEnd, multiSelectionKeyCode, panActivationKeyCode, zoomActivationKeyCode, elementsSelectable, zoomOnScroll, zoomOnPinch, panOnScroll: _panOnScroll, panOnScrollSpeed, panOnScrollMode, zoomOnDoubleClick, panOnDrag: _panOnDrag, defaultViewport, translateExtent, minZoom, maxZoom, preventScrolling, onSelectionContextMenu, noWheelClassName, noPanClassName, disableKeyboardA11y, }) => {
|
|
3099
|
+
const nodesSelectionActive = useStore(selector$6);
|
|
3100
|
+
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
|
3101
|
+
const panActivationKeyPressed = useKeyPress(panActivationKeyCode);
|
|
3102
|
+
const panOnDrag = panActivationKeyPressed || _panOnDrag;
|
|
3103
|
+
const panOnScroll = panActivationKeyPressed || _panOnScroll;
|
|
3104
|
+
const isSelecting = selectionKeyPressed || (selectionOnDrag && panOnDrag !== true);
|
|
3105
|
+
useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode });
|
|
3106
|
+
return (React.createElement(ZoomPane, { onMove: onMove, onMoveStart: onMoveStart, onMoveEnd: onMoveEnd, onPaneContextMenu: onPaneContextMenu, elementsSelectable: elementsSelectable, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, panOnScroll: panOnScroll, panOnScrollSpeed: panOnScrollSpeed, panOnScrollMode: panOnScrollMode, zoomOnDoubleClick: zoomOnDoubleClick, panOnDrag: !selectionKeyPressed && panOnDrag, defaultViewport: defaultViewport, translateExtent: translateExtent, minZoom: minZoom, maxZoom: maxZoom, zoomActivationKeyCode: zoomActivationKeyCode, preventScrolling: preventScrolling, noWheelClassName: noWheelClassName, noPanClassName: noPanClassName },
|
|
3107
|
+
React.createElement(Pane, { onSelectionStart: onSelectionStart, onSelectionEnd: onSelectionEnd, onPaneClick: onPaneClick, onPaneMouseEnter: onPaneMouseEnter, onPaneMouseMove: onPaneMouseMove, onPaneMouseLeave: onPaneMouseLeave, onPaneContextMenu: onPaneContextMenu, onPaneScroll: onPaneScroll, panOnDrag: panOnDrag, isSelecting: !!isSelecting, selectionMode: selectionMode },
|
|
3108
|
+
children,
|
|
3109
|
+
nodesSelectionActive && (React.createElement(NodesSelection$1, { onSelectionContextMenu: onSelectionContextMenu, noPanClassName: noPanClassName, disableKeyboardA11y: disableKeyboardA11y })))));
|
|
3110
|
+
};
|
|
3111
|
+
FlowRenderer.displayName = 'FlowRenderer';
|
|
3112
|
+
var FlowRenderer$1 = memo(FlowRenderer);
|
|
3113
|
+
|
|
3114
|
+
function useVisibleNodes(onlyRenderVisible) {
|
|
3115
|
+
const nodes = useStore(useCallback((s) => onlyRenderVisible
|
|
3116
|
+
? getNodesInside(s.nodeInternals, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
|
|
3117
|
+
: s.getNodes(), [onlyRenderVisible]));
|
|
3118
|
+
return nodes;
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
function createNodeTypes(nodeTypes) {
|
|
3122
|
+
const standardTypes = {
|
|
3123
|
+
input: wrapNode((nodeTypes.input || InputNode$1)),
|
|
3124
|
+
default: wrapNode((nodeTypes.default || DefaultNode$1)),
|
|
3125
|
+
output: wrapNode((nodeTypes.output || OutputNode$1)),
|
|
3126
|
+
group: wrapNode((nodeTypes.group || GroupNode)),
|
|
3127
|
+
};
|
|
3128
|
+
const wrappedTypes = {};
|
|
3129
|
+
const specialTypes = Object.keys(nodeTypes)
|
|
3130
|
+
.filter((k) => !['input', 'default', 'output', 'group'].includes(k))
|
|
3131
|
+
.reduce((res, key) => {
|
|
3132
|
+
res[key] = wrapNode((nodeTypes[key] || DefaultNode$1));
|
|
3133
|
+
return res;
|
|
3134
|
+
}, wrappedTypes);
|
|
3135
|
+
return {
|
|
3136
|
+
...standardTypes,
|
|
3137
|
+
...specialTypes,
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
const getPositionWithOrigin = ({ x, y, width, height, origin, }) => {
|
|
3141
|
+
if (!width || !height) {
|
|
3142
|
+
return { x, y };
|
|
3143
|
+
}
|
|
3144
|
+
if (origin[0] < 0 || origin[1] < 0 || origin[0] > 1 || origin[1] > 1) {
|
|
3145
|
+
return { x, y };
|
|
3146
|
+
}
|
|
3147
|
+
return {
|
|
3148
|
+
x: x - width * origin[0],
|
|
3149
|
+
y: y - height * origin[1],
|
|
3150
|
+
};
|
|
3151
|
+
};
|
|
3152
|
+
|
|
3153
|
+
const selector$5 = (s) => ({
|
|
3154
|
+
nodesDraggable: s.nodesDraggable,
|
|
3155
|
+
nodesConnectable: s.nodesConnectable,
|
|
3156
|
+
nodesFocusable: s.nodesFocusable,
|
|
3157
|
+
elementsSelectable: s.elementsSelectable,
|
|
3158
|
+
updateNodeDimensions: s.updateNodeDimensions,
|
|
3159
|
+
onError: s.onError,
|
|
3160
|
+
});
|
|
3161
|
+
const NodeRenderer = (props) => {
|
|
3162
|
+
const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, updateNodeDimensions, onError } = useStore(selector$5, shallow);
|
|
3163
|
+
const nodes = useVisibleNodes(props.onlyRenderVisibleElements);
|
|
3164
|
+
const resizeObserverRef = useRef();
|
|
3165
|
+
const resizeObserver = useMemo(() => {
|
|
3166
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
3167
|
+
return null;
|
|
3168
|
+
}
|
|
3169
|
+
const observer = new ResizeObserver((entries) => {
|
|
3170
|
+
const updates = entries.map((entry) => ({
|
|
3171
|
+
id: entry.target.getAttribute('data-id'),
|
|
3172
|
+
nodeElement: entry.target,
|
|
3173
|
+
forceUpdate: true,
|
|
3174
|
+
}));
|
|
3175
|
+
updateNodeDimensions(updates);
|
|
3176
|
+
});
|
|
3177
|
+
resizeObserverRef.current = observer;
|
|
3178
|
+
return observer;
|
|
3179
|
+
}, []);
|
|
3180
|
+
useEffect(() => {
|
|
3181
|
+
return () => {
|
|
3182
|
+
resizeObserverRef?.current?.disconnect();
|
|
3183
|
+
};
|
|
3184
|
+
}, []);
|
|
3185
|
+
return (React.createElement("div", { className: "react-flow__nodes", style: containerStyle }, nodes.map((node) => {
|
|
3186
|
+
let nodeType = node.type || 'default';
|
|
3187
|
+
if (!props.nodeTypes[nodeType]) {
|
|
3188
|
+
onError?.('003', errorMessages['error003'](nodeType));
|
|
3189
|
+
nodeType = 'default';
|
|
3190
|
+
}
|
|
3191
|
+
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default);
|
|
3192
|
+
const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'));
|
|
3193
|
+
const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined'));
|
|
3194
|
+
const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined'));
|
|
3195
|
+
const isFocusable = !!(node.focusable || (nodesFocusable && typeof node.focusable === 'undefined'));
|
|
3196
|
+
const clampedPosition = props.nodeExtent
|
|
3197
|
+
? clampPosition(node.positionAbsolute, props.nodeExtent)
|
|
3198
|
+
: node.positionAbsolute;
|
|
3199
|
+
const posX = clampedPosition?.x ?? 0;
|
|
3200
|
+
const posY = clampedPosition?.y ?? 0;
|
|
3201
|
+
const posOrigin = getPositionWithOrigin({
|
|
3202
|
+
x: posX,
|
|
3203
|
+
y: posY,
|
|
3204
|
+
width: node.width ?? 0,
|
|
3205
|
+
height: node.height ?? 0,
|
|
3206
|
+
origin: props.nodeOrigin,
|
|
3207
|
+
});
|
|
3208
|
+
return (React.createElement(NodeComponent, { key: node.id, id: node.id, className: node.className, style: node.style, type: nodeType, data: node.data, sourcePosition: node.sourcePosition || Position.Bottom, targetPosition: node.targetPosition || Position.Top, hidden: node.hidden, xPos: posX, yPos: posY, xPosOrigin: posOrigin.x, yPosOrigin: posOrigin.y, selectNodesOnDrag: props.selectNodesOnDrag, onClick: props.onNodeClick, onMouseEnter: props.onNodeMouseEnter, onMouseMove: props.onNodeMouseMove, onMouseLeave: props.onNodeMouseLeave, onContextMenu: props.onNodeContextMenu, onDoubleClick: props.onNodeDoubleClick, selected: !!node.selected, isDraggable: isDraggable, isSelectable: isSelectable, isConnectable: isConnectable, isFocusable: isFocusable, resizeObserver: resizeObserver, dragHandle: node.dragHandle, zIndex: node[internalsSymbol]?.z ?? 0, isParent: !!node[internalsSymbol]?.isParent, noDragClassName: props.noDragClassName, noPanClassName: props.noPanClassName, initialized: !!node.width && !!node.height, rfId: props.rfId, disableKeyboardA11y: props.disableKeyboardA11y, ariaLabel: node.ariaLabel, hasHandleBounds: !!node[internalsSymbol]?.handleBounds }));
|
|
3209
|
+
})));
|
|
3210
|
+
};
|
|
3211
|
+
NodeRenderer.displayName = 'NodeRenderer';
|
|
3212
|
+
var NodeRenderer$1 = memo(NodeRenderer);
|
|
3213
|
+
|
|
3214
|
+
const shiftX = (x, shift, position) => {
|
|
3215
|
+
if (position === Position.Left)
|
|
3216
|
+
return x - shift;
|
|
3217
|
+
if (position === Position.Right)
|
|
3218
|
+
return x + shift;
|
|
3219
|
+
return x;
|
|
3220
|
+
};
|
|
3221
|
+
const shiftY = (y, shift, position) => {
|
|
3222
|
+
if (position === Position.Top)
|
|
3223
|
+
return y - shift;
|
|
3224
|
+
if (position === Position.Bottom)
|
|
3225
|
+
return y + shift;
|
|
3226
|
+
return y;
|
|
3227
|
+
};
|
|
3228
|
+
const EdgeUpdaterClassName = 'react-flow__edgeupdater';
|
|
3229
|
+
const EdgeAnchor = ({ position, centerX, centerY, radius = 10, onMouseDown, onMouseEnter, onMouseOut, type, }) => (React.createElement("circle", { onMouseDown: onMouseDown, onMouseEnter: onMouseEnter, onMouseOut: onMouseOut, className: cc([EdgeUpdaterClassName, `${EdgeUpdaterClassName}-${type}`]), cx: shiftX(centerX, radius, position), cy: shiftY(centerY, radius, position), r: radius, stroke: "transparent", fill: "transparent" }));
|
|
3230
|
+
|
|
3231
|
+
const alwaysValidConnection = () => true;
|
|
3232
|
+
var wrapEdge = (EdgeComponent) => {
|
|
3233
|
+
const EdgeWrapper = ({ id, className, type, data, onClick, onEdgeDoubleClick, selected, animated, label, labelStyle, labelShowBg, labelBgStyle, labelBgPadding, labelBgBorderRadius, style, source, target, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, elementsSelectable, hidden, sourceHandleId, targetHandleId, onContextMenu, onMouseEnter, onMouseMove, onMouseLeave, reconnectRadius, onReconnect, onReconnectStart, onReconnectEnd, markerEnd, markerStart, rfId, ariaLabel, isFocusable, isReconnectable, pathOptions, interactionWidth, disableKeyboardA11y, }) => {
|
|
3234
|
+
const edgeRef = useRef(null);
|
|
3235
|
+
const [updateHover, setUpdateHover] = useState(false);
|
|
3236
|
+
const [updating, setUpdating] = useState(false);
|
|
3237
|
+
const store = useStoreApi();
|
|
3238
|
+
const markerStartUrl = useMemo(() => `url('#${getMarkerId(markerStart, rfId)}')`, [markerStart, rfId]);
|
|
3239
|
+
const markerEndUrl = useMemo(() => `url('#${getMarkerId(markerEnd, rfId)}')`, [markerEnd, rfId]);
|
|
3240
|
+
if (hidden) {
|
|
3241
|
+
return null;
|
|
3242
|
+
}
|
|
3243
|
+
const onEdgeClick = (event) => {
|
|
3244
|
+
const { edges, addSelectedEdges, unselectNodesAndEdges, multiSelectionActive } = store.getState();
|
|
3245
|
+
const edge = edges.find((e) => e.id === id);
|
|
3246
|
+
if (!edge) {
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
if (elementsSelectable) {
|
|
3250
|
+
store.setState({ nodesSelectionActive: false });
|
|
3251
|
+
if (edge.selected && multiSelectionActive) {
|
|
3252
|
+
unselectNodesAndEdges({ nodes: [], edges: [edge] });
|
|
3253
|
+
edgeRef.current?.blur();
|
|
3254
|
+
}
|
|
3255
|
+
else {
|
|
3256
|
+
addSelectedEdges([id]);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
if (onClick) {
|
|
3260
|
+
onClick(event, edge);
|
|
3261
|
+
}
|
|
3262
|
+
};
|
|
3263
|
+
const onEdgeDoubleClickHandler = getMouseHandler$1(id, store.getState, onEdgeDoubleClick);
|
|
3264
|
+
const onEdgeContextMenu = getMouseHandler$1(id, store.getState, onContextMenu);
|
|
3265
|
+
const onEdgeMouseEnter = getMouseHandler$1(id, store.getState, onMouseEnter);
|
|
3266
|
+
const onEdgeMouseMove = getMouseHandler$1(id, store.getState, onMouseMove);
|
|
3267
|
+
const onEdgeMouseLeave = getMouseHandler$1(id, store.getState, onMouseLeave);
|
|
3268
|
+
const handleEdgeUpdater = (event, isSourceHandle) => {
|
|
3269
|
+
// avoid triggering edge updater if mouse btn is not left
|
|
3270
|
+
if (event.button !== 0) {
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
const { edges, isValidConnection: isValidConnectionStore } = store.getState();
|
|
3274
|
+
const nodeId = isSourceHandle ? target : source;
|
|
3275
|
+
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
|
3276
|
+
const handleType = isSourceHandle ? 'target' : 'source';
|
|
3277
|
+
const isValidConnection = isValidConnectionStore || alwaysValidConnection;
|
|
3278
|
+
const isTarget = isSourceHandle;
|
|
3279
|
+
const edge = edges.find((e) => e.id === id);
|
|
3280
|
+
setUpdating(true);
|
|
3281
|
+
onReconnectStart?.(event, edge, handleType);
|
|
3282
|
+
const _onReconnectEnd = (evt) => {
|
|
3283
|
+
setUpdating(false);
|
|
3284
|
+
onReconnectEnd?.(evt, edge, handleType);
|
|
3285
|
+
};
|
|
3286
|
+
const onConnectEdge = (connection) => onReconnect?.(edge, connection);
|
|
3287
|
+
handlePointerDown({
|
|
3288
|
+
event,
|
|
3289
|
+
handleId,
|
|
3290
|
+
nodeId,
|
|
3291
|
+
onConnect: onConnectEdge,
|
|
3292
|
+
isTarget,
|
|
3293
|
+
getState: store.getState,
|
|
3294
|
+
setState: store.setState,
|
|
3295
|
+
isValidConnection,
|
|
3296
|
+
edgeUpdaterType: handleType,
|
|
3297
|
+
onReconnectEnd: _onReconnectEnd,
|
|
3298
|
+
});
|
|
3299
|
+
};
|
|
3300
|
+
const onEdgeUpdaterSourceMouseDown = (event) => handleEdgeUpdater(event, true);
|
|
3301
|
+
const onEdgeUpdaterTargetMouseDown = (event) => handleEdgeUpdater(event, false);
|
|
3302
|
+
const onEdgeUpdaterMouseEnter = () => setUpdateHover(true);
|
|
3303
|
+
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
|
|
3304
|
+
const inactive = !elementsSelectable && !onClick;
|
|
3305
|
+
const onKeyDown = (event) => {
|
|
3306
|
+
if (!disableKeyboardA11y && elementSelectionKeys.includes(event.key) && elementsSelectable) {
|
|
3307
|
+
const { unselectNodesAndEdges, addSelectedEdges, edges } = store.getState();
|
|
3308
|
+
const unselect = event.key === 'Escape';
|
|
3309
|
+
if (unselect) {
|
|
3310
|
+
edgeRef.current?.blur();
|
|
3311
|
+
unselectNodesAndEdges({ edges: [edges.find((e) => e.id === id)] });
|
|
3312
|
+
}
|
|
3313
|
+
else {
|
|
3314
|
+
addSelectedEdges([id]);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
};
|
|
3318
|
+
return (React.createElement("g", { className: cc([
|
|
3319
|
+
'react-flow__edge',
|
|
3320
|
+
`react-flow__edge-${type}`,
|
|
3321
|
+
className,
|
|
3322
|
+
{ selected, animated, inactive, updating: updateHover },
|
|
3323
|
+
]), onClick: onEdgeClick, onDoubleClick: onEdgeDoubleClickHandler, onContextMenu: onEdgeContextMenu, onMouseEnter: onEdgeMouseEnter, onMouseMove: onEdgeMouseMove, onMouseLeave: onEdgeMouseLeave, onKeyDown: isFocusable ? onKeyDown : undefined, tabIndex: isFocusable ? 0 : undefined, role: isFocusable ? 'button' : 'img', "data-testid": `rf__edge-${id}`, "aria-label": ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`, "aria-describedby": isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined, ref: edgeRef },
|
|
3324
|
+
!updating && (React.createElement(EdgeComponent, { id: id, source: source, target: target, selected: selected, animated: animated, label: label, labelStyle: labelStyle, labelShowBg: labelShowBg, labelBgStyle: labelBgStyle, labelBgPadding: labelBgPadding, labelBgBorderRadius: labelBgBorderRadius, data: data, style: style, sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY, sourcePosition: sourcePosition, targetPosition: targetPosition, sourceHandleId: sourceHandleId, targetHandleId: targetHandleId, markerStart: markerStartUrl, markerEnd: markerEndUrl, pathOptions: pathOptions, interactionWidth: interactionWidth })),
|
|
3325
|
+
isReconnectable && (React.createElement(React.Fragment, null,
|
|
3326
|
+
(isReconnectable === 'source' || isReconnectable === true) && (React.createElement(EdgeAnchor, { position: sourcePosition, centerX: sourceX, centerY: sourceY, radius: reconnectRadius, onMouseDown: onEdgeUpdaterSourceMouseDown, onMouseEnter: onEdgeUpdaterMouseEnter, onMouseOut: onEdgeUpdaterMouseOut, type: "source" })),
|
|
3327
|
+
(isReconnectable === 'target' || isReconnectable === true) && (React.createElement(EdgeAnchor, { position: targetPosition, centerX: targetX, centerY: targetY, radius: reconnectRadius, onMouseDown: onEdgeUpdaterTargetMouseDown, onMouseEnter: onEdgeUpdaterMouseEnter, onMouseOut: onEdgeUpdaterMouseOut, type: "target" }))))));
|
|
3328
|
+
};
|
|
3329
|
+
EdgeWrapper.displayName = 'EdgeWrapper';
|
|
3330
|
+
return memo(EdgeWrapper);
|
|
3331
|
+
};
|
|
3332
|
+
|
|
3333
|
+
function createEdgeTypes(edgeTypes) {
|
|
3334
|
+
const standardTypes = {
|
|
3335
|
+
default: wrapEdge((edgeTypes.default || BezierEdge)),
|
|
3336
|
+
straight: wrapEdge((edgeTypes.bezier || StraightEdge)),
|
|
3337
|
+
step: wrapEdge((edgeTypes.step || StepEdge)),
|
|
3338
|
+
smoothstep: wrapEdge((edgeTypes.step || SmoothStepEdge)),
|
|
3339
|
+
simplebezier: wrapEdge((edgeTypes.simplebezier || SimpleBezierEdge)),
|
|
3340
|
+
};
|
|
3341
|
+
const wrappedTypes = {};
|
|
3342
|
+
const specialTypes = Object.keys(edgeTypes)
|
|
3343
|
+
.filter((k) => !['default', 'bezier'].includes(k))
|
|
3344
|
+
.reduce((res, key) => {
|
|
3345
|
+
res[key] = wrapEdge((edgeTypes[key] || BezierEdge));
|
|
3346
|
+
return res;
|
|
3347
|
+
}, wrappedTypes);
|
|
3348
|
+
return {
|
|
3349
|
+
...standardTypes,
|
|
3350
|
+
...specialTypes,
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
function getHandlePosition(position, nodeRect, handle = null) {
|
|
3354
|
+
const x = (handle?.x || 0) + nodeRect.x;
|
|
3355
|
+
const y = (handle?.y || 0) + nodeRect.y;
|
|
3356
|
+
const width = handle?.width || nodeRect.width;
|
|
3357
|
+
const height = handle?.height || nodeRect.height;
|
|
3358
|
+
switch (position) {
|
|
3359
|
+
case Position.Top:
|
|
3360
|
+
return {
|
|
3361
|
+
x: x + width / 2,
|
|
3362
|
+
y,
|
|
3363
|
+
};
|
|
3364
|
+
case Position.Right:
|
|
3365
|
+
return {
|
|
3366
|
+
x: x + width,
|
|
3367
|
+
y: y + height / 2,
|
|
3368
|
+
};
|
|
3369
|
+
case Position.Bottom:
|
|
3370
|
+
return {
|
|
3371
|
+
x: x + width / 2,
|
|
3372
|
+
y: y + height,
|
|
3373
|
+
};
|
|
3374
|
+
case Position.Left:
|
|
3375
|
+
return {
|
|
3376
|
+
x,
|
|
3377
|
+
y: y + height / 2,
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
function getHandle(bounds, handleId) {
|
|
3382
|
+
if (!bounds) {
|
|
3383
|
+
return null;
|
|
3384
|
+
}
|
|
3385
|
+
if (bounds.length === 1 || !handleId) {
|
|
3386
|
+
return bounds[0];
|
|
3387
|
+
}
|
|
3388
|
+
else if (handleId) {
|
|
3389
|
+
return bounds.find((d) => d.id === handleId) || null;
|
|
3390
|
+
}
|
|
3391
|
+
return null;
|
|
3392
|
+
}
|
|
3393
|
+
const getEdgePositions = (sourceNodeRect, sourceHandle, sourcePosition, targetNodeRect, targetHandle, targetPosition) => {
|
|
3394
|
+
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
|
|
3395
|
+
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
|
|
3396
|
+
return {
|
|
3397
|
+
sourceX: sourceHandlePos.x,
|
|
3398
|
+
sourceY: sourceHandlePos.y,
|
|
3399
|
+
targetX: targetHandlePos.x,
|
|
3400
|
+
targetY: targetHandlePos.y,
|
|
3401
|
+
};
|
|
3402
|
+
};
|
|
3403
|
+
function isEdgeVisible({ sourcePos, targetPos, sourceWidth, sourceHeight, targetWidth, targetHeight, width, height, transform, }) {
|
|
3404
|
+
const edgeBox = {
|
|
3405
|
+
x: Math.min(sourcePos.x, targetPos.x),
|
|
3406
|
+
y: Math.min(sourcePos.y, targetPos.y),
|
|
3407
|
+
x2: Math.max(sourcePos.x + sourceWidth, targetPos.x + targetWidth),
|
|
3408
|
+
y2: Math.max(sourcePos.y + sourceHeight, targetPos.y + targetHeight),
|
|
3409
|
+
};
|
|
3410
|
+
if (edgeBox.x === edgeBox.x2) {
|
|
3411
|
+
edgeBox.x2 += 1;
|
|
3412
|
+
}
|
|
3413
|
+
if (edgeBox.y === edgeBox.y2) {
|
|
3414
|
+
edgeBox.y2 += 1;
|
|
3415
|
+
}
|
|
3416
|
+
const viewBox = rectToBox({
|
|
3417
|
+
x: (0 - transform[0]) / transform[2],
|
|
3418
|
+
y: (0 - transform[1]) / transform[2],
|
|
3419
|
+
width: width / transform[2],
|
|
3420
|
+
height: height / transform[2],
|
|
3421
|
+
});
|
|
3422
|
+
const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x));
|
|
3423
|
+
const yOverlap = Math.max(0, Math.min(viewBox.y2, edgeBox.y2) - Math.max(viewBox.y, edgeBox.y));
|
|
3424
|
+
const overlappingArea = Math.ceil(xOverlap * yOverlap);
|
|
3425
|
+
return overlappingArea > 0;
|
|
3426
|
+
}
|
|
3427
|
+
function getNodeData(node) {
|
|
3428
|
+
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
|
|
3429
|
+
const isValid = handleBounds &&
|
|
3430
|
+
node?.width &&
|
|
3431
|
+
node?.height &&
|
|
3432
|
+
typeof node?.positionAbsolute?.x !== 'undefined' &&
|
|
3433
|
+
typeof node?.positionAbsolute?.y !== 'undefined';
|
|
3434
|
+
return [
|
|
3435
|
+
{
|
|
3436
|
+
x: node?.positionAbsolute?.x || 0,
|
|
3437
|
+
y: node?.positionAbsolute?.y || 0,
|
|
3438
|
+
width: node?.width || 0,
|
|
3439
|
+
height: node?.height || 0,
|
|
3440
|
+
},
|
|
3441
|
+
handleBounds,
|
|
3442
|
+
!!isValid,
|
|
3443
|
+
];
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }];
|
|
3447
|
+
function groupEdgesByZLevel(edges, nodeInternals, elevateEdgesOnSelect = false) {
|
|
3448
|
+
let maxLevel = -1;
|
|
3449
|
+
const levelLookup = edges.reduce((tree, edge) => {
|
|
3450
|
+
const hasZIndex = isNumeric(edge.zIndex);
|
|
3451
|
+
let z = hasZIndex ? edge.zIndex : 0;
|
|
3452
|
+
if (elevateEdgesOnSelect) {
|
|
3453
|
+
const targetNode = nodeInternals.get(edge.target);
|
|
3454
|
+
const sourceNode = nodeInternals.get(edge.source);
|
|
3455
|
+
const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected;
|
|
3456
|
+
const selectedZIndex = Math.max(sourceNode?.[internalsSymbol]?.z || 0, targetNode?.[internalsSymbol]?.z || 0, 1000);
|
|
3457
|
+
z = (hasZIndex ? edge.zIndex : 0) + (edgeOrConnectedNodeSelected ? selectedZIndex : 0);
|
|
3458
|
+
}
|
|
3459
|
+
if (tree[z]) {
|
|
3460
|
+
tree[z].push(edge);
|
|
3461
|
+
}
|
|
3462
|
+
else {
|
|
3463
|
+
tree[z] = [edge];
|
|
3464
|
+
}
|
|
3465
|
+
maxLevel = z > maxLevel ? z : maxLevel;
|
|
3466
|
+
return tree;
|
|
3467
|
+
}, {});
|
|
3468
|
+
const edgeTree = Object.entries(levelLookup).map(([key, edges]) => {
|
|
3469
|
+
const level = +key;
|
|
3470
|
+
return {
|
|
3471
|
+
edges,
|
|
3472
|
+
level,
|
|
3473
|
+
isMaxLevel: level === maxLevel,
|
|
3474
|
+
};
|
|
3475
|
+
});
|
|
3476
|
+
if (edgeTree.length === 0) {
|
|
3477
|
+
return defaultEdgeTree;
|
|
3478
|
+
}
|
|
3479
|
+
return edgeTree;
|
|
3480
|
+
}
|
|
3481
|
+
function useVisibleEdges(onlyRenderVisible, nodeInternals, elevateEdgesOnSelect) {
|
|
3482
|
+
const edges = useStore(useCallback((s) => {
|
|
3483
|
+
if (!onlyRenderVisible) {
|
|
3484
|
+
return s.edges;
|
|
3485
|
+
}
|
|
3486
|
+
return s.edges.filter((e) => {
|
|
3487
|
+
const sourceNode = nodeInternals.get(e.source);
|
|
3488
|
+
const targetNode = nodeInternals.get(e.target);
|
|
3489
|
+
return (sourceNode?.width &&
|
|
3490
|
+
sourceNode?.height &&
|
|
3491
|
+
targetNode?.width &&
|
|
3492
|
+
targetNode?.height &&
|
|
3493
|
+
isEdgeVisible({
|
|
3494
|
+
sourcePos: sourceNode.positionAbsolute || { x: 0, y: 0 },
|
|
3495
|
+
targetPos: targetNode.positionAbsolute || { x: 0, y: 0 },
|
|
3496
|
+
sourceWidth: sourceNode.width,
|
|
3497
|
+
sourceHeight: sourceNode.height,
|
|
3498
|
+
targetWidth: targetNode.width,
|
|
3499
|
+
targetHeight: targetNode.height,
|
|
3500
|
+
width: s.width,
|
|
3501
|
+
height: s.height,
|
|
3502
|
+
transform: s.transform,
|
|
3503
|
+
}));
|
|
3504
|
+
});
|
|
3505
|
+
}, [onlyRenderVisible, nodeInternals]));
|
|
3506
|
+
return groupEdgesByZLevel(edges, nodeInternals, elevateEdgesOnSelect);
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
const ArrowSymbol = ({ color = 'none', strokeWidth = 1 }) => {
|
|
3510
|
+
return (React.createElement("polyline", { style: {
|
|
3511
|
+
stroke: color,
|
|
3512
|
+
strokeWidth,
|
|
3513
|
+
}, strokeLinecap: "round", strokeLinejoin: "round", fill: "none", points: "-5,-4 0,0 -5,4" }));
|
|
3514
|
+
};
|
|
3515
|
+
const ArrowClosedSymbol = ({ color = 'none', strokeWidth = 1 }) => {
|
|
3516
|
+
return (React.createElement("polyline", { style: {
|
|
3517
|
+
stroke: color,
|
|
3518
|
+
fill: color,
|
|
3519
|
+
strokeWidth,
|
|
3520
|
+
}, strokeLinecap: "round", strokeLinejoin: "round", points: "-5,-4 0,0 -5,4 -5,-4" }));
|
|
3521
|
+
};
|
|
3522
|
+
const MarkerSymbols = {
|
|
3523
|
+
[MarkerType.Arrow]: ArrowSymbol,
|
|
3524
|
+
[MarkerType.ArrowClosed]: ArrowClosedSymbol,
|
|
3525
|
+
};
|
|
3526
|
+
function useMarkerSymbol(type) {
|
|
3527
|
+
const store = useStoreApi();
|
|
3528
|
+
const symbol = useMemo(() => {
|
|
3529
|
+
const symbolExists = Object.prototype.hasOwnProperty.call(MarkerSymbols, type);
|
|
3530
|
+
if (!symbolExists) {
|
|
3531
|
+
store.getState().onError?.('009', errorMessages['error009'](type));
|
|
3532
|
+
return null;
|
|
3533
|
+
}
|
|
3534
|
+
return MarkerSymbols[type];
|
|
3535
|
+
}, [type]);
|
|
3536
|
+
return symbol;
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
const Marker = ({ id, type, color, width = 12.5, height = 12.5, markerUnits = 'strokeWidth', strokeWidth, orient = 'auto-start-reverse', }) => {
|
|
3540
|
+
const Symbol = useMarkerSymbol(type);
|
|
3541
|
+
if (!Symbol) {
|
|
3542
|
+
return null;
|
|
3543
|
+
}
|
|
3544
|
+
return (React.createElement("marker", { className: "react-flow__arrowhead", id: id, markerWidth: `${width}`, markerHeight: `${height}`, viewBox: "-10 -10 20 20", markerUnits: markerUnits, orient: orient, refX: "0", refY: "0" },
|
|
3545
|
+
React.createElement(Symbol, { color: color, strokeWidth: strokeWidth })));
|
|
3546
|
+
};
|
|
3547
|
+
const markerSelector = ({ defaultColor, rfId }) => (s) => {
|
|
3548
|
+
const ids = [];
|
|
3549
|
+
return s.edges
|
|
3550
|
+
.reduce((markers, edge) => {
|
|
3551
|
+
[edge.markerStart, edge.markerEnd].forEach((marker) => {
|
|
3552
|
+
if (marker && typeof marker === 'object') {
|
|
3553
|
+
const markerId = getMarkerId(marker, rfId);
|
|
3554
|
+
if (!ids.includes(markerId)) {
|
|
3555
|
+
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
|
|
3556
|
+
ids.push(markerId);
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
});
|
|
3560
|
+
return markers;
|
|
3561
|
+
}, [])
|
|
3562
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
3563
|
+
};
|
|
3564
|
+
// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
|
3565
|
+
// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
|
3566
|
+
// that we can then use for creating our unique marker ids
|
|
3567
|
+
const MarkerDefinitions = ({ defaultColor, rfId }) => {
|
|
3568
|
+
const markers = useStore(useCallback(markerSelector({ defaultColor, rfId }), [defaultColor, rfId]),
|
|
3569
|
+
// the id includes all marker options, so we just need to look at that part of the marker
|
|
3570
|
+
(a, b) => !(a.length !== b.length || a.some((m, i) => m.id !== b[i].id)));
|
|
3571
|
+
return (React.createElement("defs", null, markers.map((marker) => (React.createElement(Marker, { id: marker.id, key: marker.id, type: marker.type, color: marker.color, width: marker.width, height: marker.height, markerUnits: marker.markerUnits, strokeWidth: marker.strokeWidth, orient: marker.orient })))));
|
|
3572
|
+
};
|
|
3573
|
+
MarkerDefinitions.displayName = 'MarkerDefinitions';
|
|
3574
|
+
var MarkerDefinitions$1 = memo(MarkerDefinitions);
|
|
3575
|
+
|
|
3576
|
+
const selector$4 = (s) => ({
|
|
3577
|
+
nodesConnectable: s.nodesConnectable,
|
|
3578
|
+
edgesFocusable: s.edgesFocusable,
|
|
3579
|
+
edgesUpdatable: s.edgesUpdatable,
|
|
3580
|
+
elementsSelectable: s.elementsSelectable,
|
|
3581
|
+
width: s.width,
|
|
3582
|
+
height: s.height,
|
|
3583
|
+
connectionMode: s.connectionMode,
|
|
3584
|
+
nodeInternals: s.nodeInternals,
|
|
3585
|
+
onError: s.onError,
|
|
3586
|
+
});
|
|
3587
|
+
const EdgeRenderer = ({ defaultMarkerColor, onlyRenderVisibleElements, elevateEdgesOnSelect, rfId, edgeTypes, noPanClassName, onEdgeContextMenu, onEdgeMouseEnter, onEdgeMouseMove, onEdgeMouseLeave, onEdgeClick, onEdgeDoubleClick, onReconnect, onReconnectStart, onReconnectEnd, reconnectRadius, children, disableKeyboardA11y, }) => {
|
|
3588
|
+
const { edgesFocusable, edgesUpdatable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = useStore(selector$4, shallow);
|
|
3589
|
+
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect);
|
|
3590
|
+
if (!width) {
|
|
3591
|
+
return null;
|
|
3592
|
+
}
|
|
3593
|
+
return (React.createElement(React.Fragment, null,
|
|
3594
|
+
edgeTree.map(({ level, edges, isMaxLevel }) => (React.createElement("svg", { key: level, style: { zIndex: level }, width: width, height: height, className: "react-flow__edges react-flow__container" },
|
|
3595
|
+
isMaxLevel && React.createElement(MarkerDefinitions$1, { defaultColor: defaultMarkerColor, rfId: rfId }),
|
|
3596
|
+
React.createElement("g", null, edges.map((edge) => {
|
|
3597
|
+
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(nodeInternals.get(edge.source));
|
|
3598
|
+
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(nodeInternals.get(edge.target));
|
|
3599
|
+
if (!sourceIsValid || !targetIsValid) {
|
|
3600
|
+
return null;
|
|
3601
|
+
}
|
|
3602
|
+
let edgeType = edge.type || 'default';
|
|
3603
|
+
if (!edgeTypes[edgeType]) {
|
|
3604
|
+
onError?.('011', errorMessages['error011'](edgeType));
|
|
3605
|
+
edgeType = 'default';
|
|
3606
|
+
}
|
|
3607
|
+
const EdgeComponent = edgeTypes[edgeType] || edgeTypes.default;
|
|
3608
|
+
// when connection type is loose we can define all handles as sources and connect source -> source
|
|
3609
|
+
const targetNodeHandles = connectionMode === ConnectionMode.Strict
|
|
3610
|
+
? targetHandleBounds.target
|
|
3611
|
+
: (targetHandleBounds.target ?? []).concat(targetHandleBounds.source ?? []);
|
|
3612
|
+
const sourceHandle = getHandle(sourceHandleBounds.source, edge.sourceHandle);
|
|
3613
|
+
const targetHandle = getHandle(targetNodeHandles, edge.targetHandle);
|
|
3614
|
+
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
|
3615
|
+
const targetPosition = targetHandle?.position || Position.Top;
|
|
3616
|
+
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
|
|
3617
|
+
const edgeReconnectable = edge.reconnectable || edge.updatable;
|
|
3618
|
+
const isReconnectable = typeof onReconnect !== 'undefined' &&
|
|
3619
|
+
(edgeReconnectable || (edgesUpdatable && typeof edgeReconnectable === 'undefined'));
|
|
3620
|
+
if (!sourceHandle || !targetHandle) {
|
|
3621
|
+
onError?.('008', errorMessages['error008'](sourceHandle, edge));
|
|
3622
|
+
return null;
|
|
3623
|
+
}
|
|
3624
|
+
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(sourceNodeRect, sourceHandle, sourcePosition, targetNodeRect, targetHandle, targetPosition);
|
|
3625
|
+
return (React.createElement(EdgeComponent, { key: edge.id, id: edge.id, className: cc([edge.className, noPanClassName]), type: edgeType, data: edge.data, selected: !!edge.selected, animated: !!edge.animated, hidden: !!edge.hidden, label: edge.label, labelStyle: edge.labelStyle, labelShowBg: edge.labelShowBg, labelBgStyle: edge.labelBgStyle, labelBgPadding: edge.labelBgPadding, labelBgBorderRadius: edge.labelBgBorderRadius, style: edge.style, source: edge.source, target: edge.target, sourceHandleId: edge.sourceHandle, targetHandleId: edge.targetHandle, markerEnd: edge.markerEnd, markerStart: edge.markerStart, sourceX: sourceX, sourceY: sourceY, targetX: targetX, targetY: targetY, sourcePosition: sourcePosition, targetPosition: targetPosition, elementsSelectable: elementsSelectable, onContextMenu: onEdgeContextMenu, onMouseEnter: onEdgeMouseEnter, onMouseMove: onEdgeMouseMove, onMouseLeave: onEdgeMouseLeave, onClick: onEdgeClick, onEdgeDoubleClick: onEdgeDoubleClick, onReconnect: onReconnect, onReconnectStart: onReconnectStart, onReconnectEnd: onReconnectEnd, reconnectRadius: reconnectRadius, rfId: rfId, ariaLabel: edge.ariaLabel, isFocusable: isFocusable, isReconnectable: isReconnectable, pathOptions: 'pathOptions' in edge ? edge.pathOptions : undefined, interactionWidth: edge.interactionWidth, disableKeyboardA11y: disableKeyboardA11y }));
|
|
3626
|
+
}))))),
|
|
3627
|
+
children));
|
|
3628
|
+
};
|
|
3629
|
+
EdgeRenderer.displayName = 'EdgeRenderer';
|
|
3630
|
+
var EdgeRenderer$1 = memo(EdgeRenderer);
|
|
3631
|
+
|
|
3632
|
+
const selector$3 = (s) => `translate(${s.transform[0]}px,${s.transform[1]}px) scale(${s.transform[2]})`;
|
|
3633
|
+
function Viewport({ children }) {
|
|
3634
|
+
const transform = useStore(selector$3);
|
|
3635
|
+
return (React.createElement("div", { className: "react-flow__viewport react-flow__container", style: { transform } }, children));
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
function useOnInitHandler(onInit) {
|
|
3639
|
+
const rfInstance = useReactFlow();
|
|
3640
|
+
const isInitialized = useRef(false);
|
|
3641
|
+
useEffect(() => {
|
|
3642
|
+
if (!isInitialized.current && rfInstance.viewportInitialized && onInit) {
|
|
3643
|
+
setTimeout(() => onInit(rfInstance), 1);
|
|
3644
|
+
isInitialized.current = true;
|
|
3645
|
+
}
|
|
3646
|
+
}, [onInit, rfInstance.viewportInitialized]);
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
const oppositePosition = {
|
|
3650
|
+
[Position.Left]: Position.Right,
|
|
3651
|
+
[Position.Right]: Position.Left,
|
|
3652
|
+
[Position.Top]: Position.Bottom,
|
|
3653
|
+
[Position.Bottom]: Position.Top,
|
|
3654
|
+
};
|
|
3655
|
+
const ConnectionLine = ({ nodeId, handleType, style, type = ConnectionLineType.Bezier, CustomComponent, connectionStatus, }) => {
|
|
3656
|
+
const { fromNode, handleId, toX, toY, connectionMode } = useStore(useCallback((s) => ({
|
|
3657
|
+
fromNode: s.nodeInternals.get(nodeId),
|
|
3658
|
+
handleId: s.connectionHandleId,
|
|
3659
|
+
toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
|
|
3660
|
+
toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
|
|
3661
|
+
connectionMode: s.connectionMode,
|
|
3662
|
+
}), [nodeId]), shallow);
|
|
3663
|
+
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
|
|
3664
|
+
let handleBounds = fromHandleBounds?.[handleType];
|
|
3665
|
+
if (connectionMode === ConnectionMode.Loose) {
|
|
3666
|
+
handleBounds = handleBounds ? handleBounds : fromHandleBounds?.[handleType === 'source' ? 'target' : 'source'];
|
|
3667
|
+
}
|
|
3668
|
+
if (!fromNode || !handleBounds) {
|
|
3669
|
+
return null;
|
|
3670
|
+
}
|
|
3671
|
+
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
|
|
3672
|
+
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.width ?? 0) / 2;
|
|
3673
|
+
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.height ?? 0;
|
|
3674
|
+
const fromX = (fromNode.positionAbsolute?.x ?? 0) + fromHandleX;
|
|
3675
|
+
const fromY = (fromNode.positionAbsolute?.y ?? 0) + fromHandleY;
|
|
3676
|
+
const fromPosition = fromHandle?.position;
|
|
3677
|
+
const toPosition = fromPosition ? oppositePosition[fromPosition] : null;
|
|
3678
|
+
if (!fromPosition || !toPosition) {
|
|
3679
|
+
return null;
|
|
3680
|
+
}
|
|
3681
|
+
if (CustomComponent) {
|
|
3682
|
+
return (React.createElement(CustomComponent, { connectionLineType: type, connectionLineStyle: style, fromNode: fromNode, fromHandle: fromHandle, fromX: fromX, fromY: fromY, toX: toX, toY: toY, fromPosition: fromPosition, toPosition: toPosition, connectionStatus: connectionStatus }));
|
|
3683
|
+
}
|
|
3684
|
+
let dAttr = '';
|
|
3685
|
+
const pathParams = {
|
|
3686
|
+
sourceX: fromX,
|
|
3687
|
+
sourceY: fromY,
|
|
3688
|
+
sourcePosition: fromPosition,
|
|
3689
|
+
targetX: toX,
|
|
3690
|
+
targetY: toY,
|
|
3691
|
+
targetPosition: toPosition,
|
|
3692
|
+
};
|
|
3693
|
+
if (type === ConnectionLineType.Bezier) {
|
|
3694
|
+
// we assume the destination position is opposite to the source position
|
|
3695
|
+
[dAttr] = getBezierPath(pathParams);
|
|
3696
|
+
}
|
|
3697
|
+
else if (type === ConnectionLineType.Step) {
|
|
3698
|
+
[dAttr] = getSmoothStepPath({
|
|
3699
|
+
...pathParams,
|
|
3700
|
+
borderRadius: 0,
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
else if (type === ConnectionLineType.SmoothStep) {
|
|
3704
|
+
[dAttr] = getSmoothStepPath(pathParams);
|
|
3705
|
+
}
|
|
3706
|
+
else if (type === ConnectionLineType.SimpleBezier) {
|
|
3707
|
+
[dAttr] = getSimpleBezierPath(pathParams);
|
|
3708
|
+
}
|
|
3709
|
+
else {
|
|
3710
|
+
dAttr = `M${fromX},${fromY} ${toX},${toY}`;
|
|
3711
|
+
}
|
|
3712
|
+
return React.createElement("path", { d: dAttr, fill: "none", className: "react-flow__connection-path", style: style });
|
|
3713
|
+
};
|
|
3714
|
+
ConnectionLine.displayName = 'ConnectionLine';
|
|
3715
|
+
const selector$2 = (s) => ({
|
|
3716
|
+
nodeId: s.connectionNodeId,
|
|
3717
|
+
handleType: s.connectionHandleType,
|
|
3718
|
+
nodesConnectable: s.nodesConnectable,
|
|
3719
|
+
connectionStatus: s.connectionStatus,
|
|
3720
|
+
width: s.width,
|
|
3721
|
+
height: s.height,
|
|
3722
|
+
});
|
|
3723
|
+
function ConnectionLineWrapper({ containerStyle, style, type, component }) {
|
|
3724
|
+
const { nodeId, handleType, nodesConnectable, width, height, connectionStatus } = useStore(selector$2, shallow);
|
|
3725
|
+
const isValid = !!(nodeId && handleType && width && nodesConnectable);
|
|
3726
|
+
if (!isValid) {
|
|
3727
|
+
return null;
|
|
3728
|
+
}
|
|
3729
|
+
return (React.createElement("svg", { style: containerStyle, width: width, height: height, className: "react-flow__edges react-flow__connectionline react-flow__container" },
|
|
3730
|
+
React.createElement("g", { className: cc(['react-flow__connection', connectionStatus]) },
|
|
3731
|
+
React.createElement(ConnectionLine, { nodeId: nodeId, handleType: handleType, style: style, type: type, CustomComponent: component, connectionStatus: connectionStatus }))));
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3735
|
+
function useNodeOrEdgeTypes(nodeOrEdgeTypes, createTypes) {
|
|
3736
|
+
const typesKeysRef = useRef(null);
|
|
3737
|
+
const store = useStoreApi();
|
|
3738
|
+
const typesParsed = useMemo(() => {
|
|
3739
|
+
if (process.env.NODE_ENV === 'development') {
|
|
3740
|
+
const typeKeys = Object.keys(nodeOrEdgeTypes);
|
|
3741
|
+
if (shallow(typesKeysRef.current, typeKeys)) {
|
|
3742
|
+
store.getState().onError?.('002', errorMessages['error002']());
|
|
3743
|
+
}
|
|
3744
|
+
typesKeysRef.current = typeKeys;
|
|
3745
|
+
}
|
|
3746
|
+
return createTypes(nodeOrEdgeTypes);
|
|
3747
|
+
}, [nodeOrEdgeTypes]);
|
|
3748
|
+
return typesParsed;
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
const GraphView = ({ nodeTypes, edgeTypes, onMove, onMoveStart, onMoveEnd, onInit, onNodeClick, onEdgeClick, onNodeDoubleClick, onEdgeDoubleClick, onNodeMouseEnter, onNodeMouseMove, onNodeMouseLeave, onNodeContextMenu, onSelectionContextMenu, onSelectionStart, onSelectionEnd, connectionLineType, connectionLineStyle, connectionLineComponent, connectionLineContainerStyle, selectionKeyCode, selectionOnDrag, selectionMode, multiSelectionKeyCode, panActivationKeyCode, zoomActivationKeyCode, deleteKeyCode, onlyRenderVisibleElements, elementsSelectable, selectNodesOnDrag, defaultViewport, translateExtent, minZoom, maxZoom, preventScrolling, defaultMarkerColor, zoomOnScroll, zoomOnPinch, panOnScroll, panOnScrollSpeed, panOnScrollMode, zoomOnDoubleClick, panOnDrag, onPaneClick, onPaneMouseEnter, onPaneMouseMove, onPaneMouseLeave, onPaneScroll, onPaneContextMenu, onEdgeContextMenu, onEdgeMouseEnter, onEdgeMouseMove, onEdgeMouseLeave, onReconnect, onReconnectStart, onReconnectEnd, reconnectRadius, noDragClassName, noWheelClassName, noPanClassName, elevateEdgesOnSelect, disableKeyboardA11y, nodeOrigin, nodeExtent, rfId, }) => {
|
|
3752
|
+
const nodeTypesWrapped = useNodeOrEdgeTypes(nodeTypes, createNodeTypes);
|
|
3753
|
+
const edgeTypesWrapped = useNodeOrEdgeTypes(edgeTypes, createEdgeTypes);
|
|
3754
|
+
useOnInitHandler(onInit);
|
|
3755
|
+
return (React.createElement(FlowRenderer$1, { onPaneClick: onPaneClick, onPaneMouseEnter: onPaneMouseEnter, onPaneMouseMove: onPaneMouseMove, onPaneMouseLeave: onPaneMouseLeave, onPaneContextMenu: onPaneContextMenu, onPaneScroll: onPaneScroll, deleteKeyCode: deleteKeyCode, selectionKeyCode: selectionKeyCode, selectionOnDrag: selectionOnDrag, selectionMode: selectionMode, onSelectionStart: onSelectionStart, onSelectionEnd: onSelectionEnd, multiSelectionKeyCode: multiSelectionKeyCode, panActivationKeyCode: panActivationKeyCode, zoomActivationKeyCode: zoomActivationKeyCode, elementsSelectable: elementsSelectable, onMove: onMove, onMoveStart: onMoveStart, onMoveEnd: onMoveEnd, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, zoomOnDoubleClick: zoomOnDoubleClick, panOnScroll: panOnScroll, panOnScrollSpeed: panOnScrollSpeed, panOnScrollMode: panOnScrollMode, panOnDrag: panOnDrag, defaultViewport: defaultViewport, translateExtent: translateExtent, minZoom: minZoom, maxZoom: maxZoom, onSelectionContextMenu: onSelectionContextMenu, preventScrolling: preventScrolling, noDragClassName: noDragClassName, noWheelClassName: noWheelClassName, noPanClassName: noPanClassName, disableKeyboardA11y: disableKeyboardA11y },
|
|
3756
|
+
React.createElement(Viewport, null,
|
|
3757
|
+
React.createElement(EdgeRenderer$1, { edgeTypes: edgeTypesWrapped, onEdgeClick: onEdgeClick, onEdgeDoubleClick: onEdgeDoubleClick, onlyRenderVisibleElements: onlyRenderVisibleElements, onEdgeContextMenu: onEdgeContextMenu, onEdgeMouseEnter: onEdgeMouseEnter, onEdgeMouseMove: onEdgeMouseMove, onEdgeMouseLeave: onEdgeMouseLeave, onReconnect: onReconnect, onReconnectStart: onReconnectStart, onReconnectEnd: onReconnectEnd, reconnectRadius: reconnectRadius, defaultMarkerColor: defaultMarkerColor, noPanClassName: noPanClassName, elevateEdgesOnSelect: !!elevateEdgesOnSelect, disableKeyboardA11y: disableKeyboardA11y, rfId: rfId },
|
|
3758
|
+
React.createElement(ConnectionLineWrapper, { style: connectionLineStyle, type: connectionLineType, component: connectionLineComponent, containerStyle: connectionLineContainerStyle })),
|
|
3759
|
+
React.createElement("div", { className: "react-flow__edgelabel-renderer" }),
|
|
3760
|
+
React.createElement(NodeRenderer$1, { nodeTypes: nodeTypesWrapped, onNodeClick: onNodeClick, onNodeDoubleClick: onNodeDoubleClick, onNodeMouseEnter: onNodeMouseEnter, onNodeMouseMove: onNodeMouseMove, onNodeMouseLeave: onNodeMouseLeave, onNodeContextMenu: onNodeContextMenu, selectNodesOnDrag: selectNodesOnDrag, onlyRenderVisibleElements: onlyRenderVisibleElements, noPanClassName: noPanClassName, noDragClassName: noDragClassName, disableKeyboardA11y: disableKeyboardA11y, nodeOrigin: nodeOrigin, nodeExtent: nodeExtent, rfId: rfId }))));
|
|
3761
|
+
};
|
|
3762
|
+
GraphView.displayName = 'GraphView';
|
|
3763
|
+
var GraphView$1 = memo(GraphView);
|
|
3764
|
+
|
|
3765
|
+
const infiniteExtent = [
|
|
3766
|
+
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
|
3767
|
+
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
|
3768
|
+
];
|
|
3769
|
+
const initialState = {
|
|
3770
|
+
rfId: '1',
|
|
3771
|
+
width: 0,
|
|
3772
|
+
height: 0,
|
|
3773
|
+
transform: [0, 0, 1],
|
|
3774
|
+
nodeInternals: new Map(),
|
|
3775
|
+
edges: [],
|
|
3776
|
+
onNodesChange: null,
|
|
3777
|
+
onEdgesChange: null,
|
|
3778
|
+
hasDefaultNodes: false,
|
|
3779
|
+
hasDefaultEdges: false,
|
|
3780
|
+
d3Zoom: null,
|
|
3781
|
+
d3Selection: null,
|
|
3782
|
+
d3ZoomHandler: undefined,
|
|
3783
|
+
minZoom: 0.5,
|
|
3784
|
+
maxZoom: 2,
|
|
3785
|
+
translateExtent: infiniteExtent,
|
|
3786
|
+
nodeExtent: infiniteExtent,
|
|
3787
|
+
nodesSelectionActive: false,
|
|
3788
|
+
userSelectionActive: false,
|
|
3789
|
+
userSelectionRect: null,
|
|
3790
|
+
connectionNodeId: null,
|
|
3791
|
+
connectionHandleId: null,
|
|
3792
|
+
connectionHandleType: 'source',
|
|
3793
|
+
connectionPosition: { x: 0, y: 0 },
|
|
3794
|
+
connectionStatus: null,
|
|
3795
|
+
connectionMode: ConnectionMode.Strict,
|
|
3796
|
+
domNode: null,
|
|
3797
|
+
paneDragging: false,
|
|
3798
|
+
noPanClassName: 'nopan',
|
|
3799
|
+
nodeOrigin: [0, 0],
|
|
3800
|
+
nodeDragThreshold: 0,
|
|
3801
|
+
snapGrid: [15, 15],
|
|
3802
|
+
snapToGrid: false,
|
|
3803
|
+
nodesDraggable: true,
|
|
3804
|
+
nodesConnectable: true,
|
|
3805
|
+
nodesFocusable: true,
|
|
3806
|
+
edgesFocusable: true,
|
|
3807
|
+
edgesUpdatable: true,
|
|
3808
|
+
elementsSelectable: true,
|
|
3809
|
+
elevateNodesOnSelect: true,
|
|
3810
|
+
fitViewOnInit: false,
|
|
3811
|
+
fitViewOnInitDone: false,
|
|
3812
|
+
fitViewOnInitOptions: undefined,
|
|
3813
|
+
onSelectionChange: [],
|
|
3814
|
+
multiSelectionActive: false,
|
|
3815
|
+
connectionStartHandle: null,
|
|
3816
|
+
connectionEndHandle: null,
|
|
3817
|
+
connectionClickStartHandle: null,
|
|
3818
|
+
connectOnClick: true,
|
|
3819
|
+
ariaLiveMessage: '',
|
|
3820
|
+
autoPanOnConnect: true,
|
|
3821
|
+
autoPanOnNodeDrag: true,
|
|
3822
|
+
connectionRadius: 20,
|
|
3823
|
+
onError: devWarn,
|
|
3824
|
+
isValidConnection: undefined,
|
|
3825
|
+
};
|
|
3826
|
+
|
|
3827
|
+
const createRFStore = () => createWithEqualityFn((set, get) => ({
|
|
3828
|
+
...initialState,
|
|
3829
|
+
setNodes: (nodes) => {
|
|
3830
|
+
const { nodeInternals, nodeOrigin, elevateNodesOnSelect } = get();
|
|
3831
|
+
set({ nodeInternals: createNodeInternals(nodes, nodeInternals, nodeOrigin, elevateNodesOnSelect) });
|
|
3832
|
+
},
|
|
3833
|
+
getNodes: () => {
|
|
3834
|
+
return Array.from(get().nodeInternals.values());
|
|
3835
|
+
},
|
|
3836
|
+
setEdges: (edges) => {
|
|
3837
|
+
const { defaultEdgeOptions = {} } = get();
|
|
3838
|
+
set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
|
|
3839
|
+
},
|
|
3840
|
+
setDefaultNodesAndEdges: (nodes, edges) => {
|
|
3841
|
+
const hasDefaultNodes = typeof nodes !== 'undefined';
|
|
3842
|
+
const hasDefaultEdges = typeof edges !== 'undefined';
|
|
3843
|
+
const nodeInternals = hasDefaultNodes
|
|
3844
|
+
? createNodeInternals(nodes, new Map(), get().nodeOrigin, get().elevateNodesOnSelect)
|
|
3845
|
+
: new Map();
|
|
3846
|
+
const nextEdges = hasDefaultEdges ? edges : [];
|
|
3847
|
+
set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges });
|
|
3848
|
+
},
|
|
3849
|
+
updateNodeDimensions: (updates) => {
|
|
3850
|
+
const { onNodesChange, nodeInternals, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions, domNode, nodeOrigin, } = get();
|
|
3851
|
+
const viewportNode = domNode?.querySelector('.react-flow__viewport');
|
|
3852
|
+
if (!viewportNode) {
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3855
|
+
const style = window.getComputedStyle(viewportNode);
|
|
3856
|
+
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
|
|
3857
|
+
const changes = updates.reduce((res, update) => {
|
|
3858
|
+
const node = nodeInternals.get(update.id);
|
|
3859
|
+
if (node?.hidden) {
|
|
3860
|
+
nodeInternals.set(node.id, {
|
|
3861
|
+
...node,
|
|
3862
|
+
[internalsSymbol]: {
|
|
3863
|
+
...node[internalsSymbol],
|
|
3864
|
+
// we need to reset the handle bounds when the node is hidden
|
|
3865
|
+
// in order to force a new observation when the node is shown again
|
|
3866
|
+
handleBounds: undefined,
|
|
3867
|
+
},
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
else if (node) {
|
|
3871
|
+
const dimensions = getDimensions(update.nodeElement);
|
|
3872
|
+
const doUpdate = !!(dimensions.width &&
|
|
3873
|
+
dimensions.height &&
|
|
3874
|
+
(node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate));
|
|
3875
|
+
if (doUpdate) {
|
|
3876
|
+
nodeInternals.set(node.id, {
|
|
3877
|
+
...node,
|
|
3878
|
+
[internalsSymbol]: {
|
|
3879
|
+
...node[internalsSymbol],
|
|
3880
|
+
handleBounds: {
|
|
3881
|
+
source: getHandleBounds('.source', update.nodeElement, zoom, nodeOrigin),
|
|
3882
|
+
target: getHandleBounds('.target', update.nodeElement, zoom, nodeOrigin),
|
|
3883
|
+
},
|
|
3884
|
+
},
|
|
3885
|
+
...dimensions,
|
|
3886
|
+
});
|
|
3887
|
+
res.push({
|
|
3888
|
+
id: node.id,
|
|
3889
|
+
type: 'dimensions',
|
|
3890
|
+
dimensions,
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
return res;
|
|
3895
|
+
}, []);
|
|
3896
|
+
updateAbsoluteNodePositions(nodeInternals, nodeOrigin);
|
|
3897
|
+
const nextFitViewOnInitDone = fitViewOnInitDone ||
|
|
3898
|
+
(fitViewOnInit && !fitViewOnInitDone && fitView(get, { initial: true, ...fitViewOnInitOptions }));
|
|
3899
|
+
set({ nodeInternals: new Map(nodeInternals), fitViewOnInitDone: nextFitViewOnInitDone });
|
|
3900
|
+
if (changes?.length > 0) {
|
|
3901
|
+
onNodesChange?.(changes);
|
|
3902
|
+
}
|
|
3903
|
+
},
|
|
3904
|
+
updateNodePositions: (nodeDragItems, positionChanged = true, dragging = false) => {
|
|
3905
|
+
const { triggerNodeChanges } = get();
|
|
3906
|
+
const changes = nodeDragItems.map((node) => {
|
|
3907
|
+
const change = {
|
|
3908
|
+
id: node.id,
|
|
3909
|
+
type: 'position',
|
|
3910
|
+
dragging,
|
|
3911
|
+
};
|
|
3912
|
+
if (positionChanged) {
|
|
3913
|
+
change.positionAbsolute = node.positionAbsolute;
|
|
3914
|
+
change.position = node.position;
|
|
3915
|
+
}
|
|
3916
|
+
return change;
|
|
3917
|
+
});
|
|
3918
|
+
triggerNodeChanges(changes);
|
|
3919
|
+
},
|
|
3920
|
+
triggerNodeChanges: (changes) => {
|
|
3921
|
+
const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin, getNodes, elevateNodesOnSelect } = get();
|
|
3922
|
+
if (changes?.length) {
|
|
3923
|
+
if (hasDefaultNodes) {
|
|
3924
|
+
const nodes = applyNodeChanges(changes, getNodes());
|
|
3925
|
+
const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin, elevateNodesOnSelect);
|
|
3926
|
+
set({ nodeInternals: nextNodeInternals });
|
|
3927
|
+
}
|
|
3928
|
+
onNodesChange?.(changes);
|
|
3929
|
+
}
|
|
3930
|
+
},
|
|
3931
|
+
addSelectedNodes: (selectedNodeIds) => {
|
|
3932
|
+
const { multiSelectionActive, edges, getNodes } = get();
|
|
3933
|
+
let changedNodes;
|
|
3934
|
+
let changedEdges = null;
|
|
3935
|
+
if (multiSelectionActive) {
|
|
3936
|
+
changedNodes = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
|
|
3937
|
+
}
|
|
3938
|
+
else {
|
|
3939
|
+
changedNodes = getSelectionChanges(getNodes(), selectedNodeIds);
|
|
3940
|
+
changedEdges = getSelectionChanges(edges, []);
|
|
3941
|
+
}
|
|
3942
|
+
updateNodesAndEdgesSelections({
|
|
3943
|
+
changedNodes,
|
|
3944
|
+
changedEdges,
|
|
3945
|
+
get,
|
|
3946
|
+
set,
|
|
3947
|
+
});
|
|
3948
|
+
},
|
|
3949
|
+
addSelectedEdges: (selectedEdgeIds) => {
|
|
3950
|
+
const { multiSelectionActive, edges, getNodes } = get();
|
|
3951
|
+
let changedEdges;
|
|
3952
|
+
let changedNodes = null;
|
|
3953
|
+
if (multiSelectionActive) {
|
|
3954
|
+
changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
|
|
3955
|
+
}
|
|
3956
|
+
else {
|
|
3957
|
+
changedEdges = getSelectionChanges(edges, selectedEdgeIds);
|
|
3958
|
+
changedNodes = getSelectionChanges(getNodes(), []);
|
|
3959
|
+
}
|
|
3960
|
+
updateNodesAndEdgesSelections({
|
|
3961
|
+
changedNodes,
|
|
3962
|
+
changedEdges,
|
|
3963
|
+
get,
|
|
3964
|
+
set,
|
|
3965
|
+
});
|
|
3966
|
+
},
|
|
3967
|
+
unselectNodesAndEdges: ({ nodes, edges } = {}) => {
|
|
3968
|
+
const { edges: storeEdges, getNodes } = get();
|
|
3969
|
+
const nodesToUnselect = nodes ? nodes : getNodes();
|
|
3970
|
+
const edgesToUnselect = edges ? edges : storeEdges;
|
|
3971
|
+
const changedNodes = nodesToUnselect.map((n) => {
|
|
3972
|
+
n.selected = false;
|
|
3973
|
+
return createSelectionChange(n.id, false);
|
|
3974
|
+
});
|
|
3975
|
+
const changedEdges = edgesToUnselect.map((edge) => createSelectionChange(edge.id, false));
|
|
3976
|
+
updateNodesAndEdgesSelections({
|
|
3977
|
+
changedNodes,
|
|
3978
|
+
changedEdges,
|
|
3979
|
+
get,
|
|
3980
|
+
set,
|
|
3981
|
+
});
|
|
3982
|
+
},
|
|
3983
|
+
setMinZoom: (minZoom) => {
|
|
3984
|
+
const { d3Zoom, maxZoom } = get();
|
|
3985
|
+
d3Zoom?.scaleExtent([minZoom, maxZoom]);
|
|
3986
|
+
set({ minZoom });
|
|
3987
|
+
},
|
|
3988
|
+
setMaxZoom: (maxZoom) => {
|
|
3989
|
+
const { d3Zoom, minZoom } = get();
|
|
3990
|
+
d3Zoom?.scaleExtent([minZoom, maxZoom]);
|
|
3991
|
+
set({ maxZoom });
|
|
3992
|
+
},
|
|
3993
|
+
setTranslateExtent: (translateExtent) => {
|
|
3994
|
+
get().d3Zoom?.translateExtent(translateExtent);
|
|
3995
|
+
set({ translateExtent });
|
|
3996
|
+
},
|
|
3997
|
+
resetSelectedElements: () => {
|
|
3998
|
+
const { edges, getNodes } = get();
|
|
3999
|
+
const nodes = getNodes();
|
|
4000
|
+
const nodesToUnselect = nodes
|
|
4001
|
+
.filter((e) => e.selected)
|
|
4002
|
+
.map((n) => createSelectionChange(n.id, false));
|
|
4003
|
+
const edgesToUnselect = edges
|
|
4004
|
+
.filter((e) => e.selected)
|
|
4005
|
+
.map((e) => createSelectionChange(e.id, false));
|
|
4006
|
+
updateNodesAndEdgesSelections({
|
|
4007
|
+
changedNodes: nodesToUnselect,
|
|
4008
|
+
changedEdges: edgesToUnselect,
|
|
4009
|
+
get,
|
|
4010
|
+
set,
|
|
4011
|
+
});
|
|
4012
|
+
},
|
|
4013
|
+
setNodeExtent: (nodeExtent) => {
|
|
4014
|
+
const { nodeInternals } = get();
|
|
4015
|
+
nodeInternals.forEach((node) => {
|
|
4016
|
+
node.positionAbsolute = clampPosition(node.position, nodeExtent);
|
|
4017
|
+
});
|
|
4018
|
+
set({
|
|
4019
|
+
nodeExtent,
|
|
4020
|
+
nodeInternals: new Map(nodeInternals),
|
|
4021
|
+
});
|
|
4022
|
+
},
|
|
4023
|
+
panBy: (delta) => {
|
|
4024
|
+
const { transform, width, height, d3Zoom, d3Selection, translateExtent } = get();
|
|
4025
|
+
if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) {
|
|
4026
|
+
return false;
|
|
4027
|
+
}
|
|
4028
|
+
const nextTransform = zoomIdentity
|
|
4029
|
+
.translate(transform[0] + delta.x, transform[1] + delta.y)
|
|
4030
|
+
.scale(transform[2]);
|
|
4031
|
+
const extent = [
|
|
4032
|
+
[0, 0],
|
|
4033
|
+
[width, height],
|
|
4034
|
+
];
|
|
4035
|
+
const constrainedTransform = d3Zoom?.constrain()(nextTransform, extent, translateExtent);
|
|
4036
|
+
d3Zoom.transform(d3Selection, constrainedTransform);
|
|
4037
|
+
const transformChanged = transform[0] !== constrainedTransform.x ||
|
|
4038
|
+
transform[1] !== constrainedTransform.y ||
|
|
4039
|
+
transform[2] !== constrainedTransform.k;
|
|
4040
|
+
return transformChanged;
|
|
4041
|
+
},
|
|
4042
|
+
cancelConnection: () => set({
|
|
4043
|
+
connectionNodeId: initialState.connectionNodeId,
|
|
4044
|
+
connectionHandleId: initialState.connectionHandleId,
|
|
4045
|
+
connectionHandleType: initialState.connectionHandleType,
|
|
4046
|
+
connectionStatus: initialState.connectionStatus,
|
|
4047
|
+
connectionStartHandle: initialState.connectionStartHandle,
|
|
4048
|
+
connectionEndHandle: initialState.connectionEndHandle,
|
|
4049
|
+
}),
|
|
4050
|
+
reset: () => set({ ...initialState }),
|
|
4051
|
+
}), Object.is);
|
|
4052
|
+
|
|
4053
|
+
const ReactFlowProvider = ({ children }) => {
|
|
4054
|
+
const storeRef = useRef(null);
|
|
4055
|
+
if (!storeRef.current) {
|
|
4056
|
+
storeRef.current = createRFStore();
|
|
4057
|
+
}
|
|
4058
|
+
return React.createElement(Provider$1, { value: storeRef.current }, children);
|
|
4059
|
+
};
|
|
4060
|
+
ReactFlowProvider.displayName = 'ReactFlowProvider';
|
|
4061
|
+
|
|
4062
|
+
const Wrapper = ({ children }) => {
|
|
4063
|
+
const isWrapped = useContext(StoreContext);
|
|
4064
|
+
if (isWrapped) {
|
|
4065
|
+
// we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
|
|
4066
|
+
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
|
|
4067
|
+
return React.createElement(React.Fragment, null, children);
|
|
4068
|
+
}
|
|
4069
|
+
return React.createElement(ReactFlowProvider, null, children);
|
|
4070
|
+
};
|
|
4071
|
+
Wrapper.displayName = 'ReactFlowWrapper';
|
|
4072
|
+
|
|
4073
|
+
const defaultNodeTypes = {
|
|
4074
|
+
input: InputNode$1,
|
|
4075
|
+
default: DefaultNode$1,
|
|
4076
|
+
output: OutputNode$1,
|
|
4077
|
+
group: GroupNode,
|
|
4078
|
+
};
|
|
4079
|
+
const defaultEdgeTypes = {
|
|
4080
|
+
default: BezierEdge,
|
|
4081
|
+
straight: StraightEdge,
|
|
4082
|
+
step: StepEdge,
|
|
4083
|
+
smoothstep: SmoothStepEdge,
|
|
4084
|
+
simplebezier: SimpleBezierEdge,
|
|
4085
|
+
};
|
|
4086
|
+
const initNodeOrigin = [0, 0];
|
|
4087
|
+
const initSnapGrid = [15, 15];
|
|
4088
|
+
const initDefaultViewport = { x: 0, y: 0, zoom: 1 };
|
|
4089
|
+
const wrapperStyle = {
|
|
4090
|
+
width: '100%',
|
|
4091
|
+
height: '100%',
|
|
4092
|
+
overflow: 'hidden',
|
|
4093
|
+
position: 'relative',
|
|
4094
|
+
zIndex: 0,
|
|
4095
|
+
};
|
|
4096
|
+
const ReactFlow = forwardRef(({ nodes, edges, defaultNodes, defaultEdges, className, nodeTypes = defaultNodeTypes, edgeTypes = defaultEdgeTypes, onNodeClick, onEdgeClick, onInit, onMove, onMoveStart, onMoveEnd, onConnect, onConnectStart, onConnectEnd, onClickConnectStart, onClickConnectEnd, onNodeMouseEnter, onNodeMouseMove, onNodeMouseLeave, onNodeContextMenu, onNodeDoubleClick, onNodeDragStart, onNodeDrag, onNodeDragStop, onNodesDelete, onEdgesDelete, onSelectionChange, onSelectionDragStart, onSelectionDrag, onSelectionDragStop, onSelectionContextMenu, onSelectionStart, onSelectionEnd, connectionMode = ConnectionMode.Strict, 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 = false, snapGrid = initSnapGrid, onlyRenderVisibleElements = false, selectNodesOnDrag = true, nodesDraggable, nodesConnectable, nodesFocusable, nodeOrigin = initNodeOrigin, edgesFocusable, edgesUpdatable, elementsSelectable, defaultViewport = initDefaultViewport, 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, children, onEdgeContextMenu, onEdgeDoubleClick, onEdgeMouseEnter, onEdgeMouseMove, onEdgeMouseLeave, onEdgeUpdate, onEdgeUpdateStart, onEdgeUpdateEnd, onReconnect, onReconnectStart, onReconnectEnd, reconnectRadius = 10, edgeUpdaterRadius = 10, onNodesChange, onEdgesChange, noDragClassName = 'nodrag', noWheelClassName = 'nowheel', noPanClassName = 'nopan', fitView = false, fitViewOptions, connectOnClick = true, attributionPosition, proOptions, defaultEdgeOptions, elevateNodesOnSelect = true, elevateEdgesOnSelect = false, disableKeyboardA11y = false, autoPanOnConnect = true, autoPanOnNodeDrag = true, connectionRadius = 20, isValidConnection, onError, style, id, nodeDragThreshold, ...rest }, ref) => {
|
|
4097
|
+
const rfId = id || '1';
|
|
4098
|
+
return (React.createElement("div", { ...rest, style: { ...style, ...wrapperStyle }, ref: ref, className: cc(['react-flow', className]), "data-testid": "rf__wrapper", id: id },
|
|
4099
|
+
React.createElement(Wrapper, null,
|
|
4100
|
+
React.createElement(GraphView$1, { onInit: onInit, onMove: onMove, onMoveStart: onMoveStart, onMoveEnd: onMoveEnd, 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, selectNodesOnDrag: selectNodesOnDrag, defaultViewport: defaultViewport, 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, onSelectionContextMenu: onSelectionContextMenu, onSelectionStart: onSelectionStart, onSelectionEnd: onSelectionEnd, onEdgeContextMenu: onEdgeContextMenu, onEdgeDoubleClick: onEdgeDoubleClick, onEdgeMouseEnter: onEdgeMouseEnter, onEdgeMouseMove: onEdgeMouseMove, onEdgeMouseLeave: onEdgeMouseLeave, onReconnect: onReconnect ?? onEdgeUpdate, onReconnectStart: onReconnectStart ?? onEdgeUpdateStart, onReconnectEnd: onReconnectEnd ?? onEdgeUpdateEnd, reconnectRadius: reconnectRadius ?? edgeUpdaterRadius, defaultMarkerColor: defaultMarkerColor, noDragClassName: noDragClassName, noWheelClassName: noWheelClassName, noPanClassName: noPanClassName, elevateEdgesOnSelect: elevateEdgesOnSelect, rfId: rfId, disableKeyboardA11y: disableKeyboardA11y, nodeOrigin: nodeOrigin, nodeExtent: nodeExtent }),
|
|
4101
|
+
React.createElement(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, edgesUpdatable: edgesUpdatable, elementsSelectable: elementsSelectable, elevateNodesOnSelect: elevateNodesOnSelect, 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, onNodeDragStart: onNodeDragStart, onNodeDrag: onNodeDrag, onNodeDragStop: onNodeDragStop, onSelectionDrag: onSelectionDrag, onSelectionDragStart: onSelectionDragStart, onSelectionDragStop: onSelectionDragStop, noPanClassName: noPanClassName, nodeOrigin: nodeOrigin, rfId: rfId, autoPanOnConnect: autoPanOnConnect, autoPanOnNodeDrag: autoPanOnNodeDrag, onError: onError, connectionRadius: connectionRadius, isValidConnection: isValidConnection, nodeDragThreshold: nodeDragThreshold }),
|
|
4102
|
+
React.createElement(Wrapper$1, { onSelectionChange: onSelectionChange }),
|
|
4103
|
+
children,
|
|
4104
|
+
React.createElement(Attribution, { proOptions: proOptions, position: attributionPosition }),
|
|
4105
|
+
React.createElement(A11yDescriptions, { rfId: rfId, disableKeyboardA11y: disableKeyboardA11y }))));
|
|
4106
|
+
});
|
|
4107
|
+
ReactFlow.displayName = 'ReactFlow';
|
|
4108
|
+
|
|
4109
|
+
const selector$1 = (s) => s.domNode?.querySelector('.react-flow__edgelabel-renderer');
|
|
4110
|
+
function EdgeLabelRenderer({ children }) {
|
|
4111
|
+
const edgeLabelRenderer = useStore(selector$1);
|
|
4112
|
+
if (!edgeLabelRenderer) {
|
|
4113
|
+
return null;
|
|
4114
|
+
}
|
|
4115
|
+
return createPortal(children, edgeLabelRenderer);
|
|
4116
|
+
}
|
|
4117
|
+
|
|
4118
|
+
function useUpdateNodeInternals() {
|
|
4119
|
+
const store = useStoreApi();
|
|
4120
|
+
return useCallback((id) => {
|
|
4121
|
+
const { domNode, updateNodeDimensions } = store.getState();
|
|
4122
|
+
const updateIds = Array.isArray(id) ? id : [id];
|
|
4123
|
+
const updates = updateIds.reduce((res, updateId) => {
|
|
4124
|
+
const nodeElement = domNode?.querySelector(`.react-flow__node[data-id="${updateId}"]`);
|
|
4125
|
+
if (nodeElement) {
|
|
4126
|
+
res.push({ id: updateId, nodeElement, forceUpdate: true });
|
|
4127
|
+
}
|
|
4128
|
+
return res;
|
|
4129
|
+
}, []);
|
|
4130
|
+
requestAnimationFrame(() => updateNodeDimensions(updates));
|
|
4131
|
+
}, []);
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
const nodesSelector = (state) => state.getNodes();
|
|
4135
|
+
function useNodes() {
|
|
4136
|
+
const nodes = useStore(nodesSelector, shallow);
|
|
4137
|
+
return nodes;
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
const edgesSelector = (state) => state.edges;
|
|
4141
|
+
function useEdges() {
|
|
4142
|
+
const edges = useStore(edgesSelector, shallow);
|
|
4143
|
+
return edges;
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
const viewportSelector = (state) => ({
|
|
4147
|
+
x: state.transform[0],
|
|
4148
|
+
y: state.transform[1],
|
|
4149
|
+
zoom: state.transform[2],
|
|
4150
|
+
});
|
|
4151
|
+
function useViewport() {
|
|
4152
|
+
const viewport = useStore(viewportSelector, shallow);
|
|
4153
|
+
return viewport;
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4157
|
+
function createUseItemsState(applyChanges) {
|
|
4158
|
+
return (initialItems) => {
|
|
4159
|
+
const [items, setItems] = useState(initialItems);
|
|
4160
|
+
const onItemsChange = useCallback((changes) => setItems((items) => applyChanges(changes, items)), []);
|
|
4161
|
+
return [items, setItems, onItemsChange];
|
|
4162
|
+
};
|
|
4163
|
+
}
|
|
4164
|
+
const useNodesState = createUseItemsState(applyNodeChanges);
|
|
4165
|
+
const useEdgesState = createUseItemsState(applyEdgeChanges);
|
|
4166
|
+
|
|
4167
|
+
function useOnViewportChange({ onStart, onChange, onEnd }) {
|
|
4168
|
+
const store = useStoreApi();
|
|
4169
|
+
useEffect(() => {
|
|
4170
|
+
store.setState({ onViewportChangeStart: onStart });
|
|
4171
|
+
}, [onStart]);
|
|
4172
|
+
useEffect(() => {
|
|
4173
|
+
store.setState({ onViewportChange: onChange });
|
|
4174
|
+
}, [onChange]);
|
|
4175
|
+
useEffect(() => {
|
|
4176
|
+
store.setState({ onViewportChangeEnd: onEnd });
|
|
4177
|
+
}, [onEnd]);
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
function useOnSelectionChange({ onChange }) {
|
|
4181
|
+
const store = useStoreApi();
|
|
4182
|
+
useEffect(() => {
|
|
4183
|
+
const nextSelectionChangeHandlers = [...store.getState().onSelectionChange, onChange];
|
|
4184
|
+
store.setState({ onSelectionChange: nextSelectionChangeHandlers });
|
|
4185
|
+
return () => {
|
|
4186
|
+
const nextHandlers = store.getState().onSelectionChange.filter((fn) => fn !== onChange);
|
|
4187
|
+
store.setState({ onSelectionChange: nextHandlers });
|
|
4188
|
+
};
|
|
4189
|
+
}, [onChange]);
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
const selector = (options) => (s) => {
|
|
4193
|
+
if (s.nodeInternals.size === 0) {
|
|
4194
|
+
return false;
|
|
4195
|
+
}
|
|
4196
|
+
return s
|
|
4197
|
+
.getNodes()
|
|
4198
|
+
.filter((n) => (options.includeHiddenNodes ? true : !n.hidden))
|
|
4199
|
+
.every((n) => n[internalsSymbol]?.handleBounds !== undefined);
|
|
4200
|
+
};
|
|
4201
|
+
const defaultOptions = {
|
|
4202
|
+
includeHiddenNodes: false,
|
|
4203
|
+
};
|
|
4204
|
+
function useNodesInitialized(options = defaultOptions) {
|
|
4205
|
+
const initialized = useStore(selector(options));
|
|
4206
|
+
return initialized;
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
export { BaseEdge, BezierEdge, ConnectionLineType, ConnectionMode, EdgeLabelRenderer, EdgeText$1 as EdgeText, Handle$1 as Handle, MarkerType, PanOnScrollMode, Panel, Position, ReactFlow, ReactFlowProvider, SelectionMode, SimpleBezierEdge, SmoothStepEdge, StepEdge, StraightEdge, addEdge, applyEdgeChanges, applyNodeChanges, boxToRect, clamp, getBezierPath, getBoundsOfRects, getConnectedEdges, getIncomers, getMarkerEnd, getNodePositionWithOrigin, getNodesBounds, getOutgoers, getRectOfNodes, getSimpleBezierPath, getSmoothStepPath, getStraightPath, getTransformForBounds, getViewportForBounds, handleParentExpand, internalsSymbol, isEdge, isNode, reconnectEdge, rectToBox, updateEdge, useEdges, useEdgesState, useGetPointerPosition, useKeyPress, useNodeId, useNodes, useNodesInitialized, useNodesState, useOnSelectionChange, useOnViewportChange, useReactFlow, useStore, useStoreApi, useUpdateNodeInternals, useViewport };
|