refactor(general): fit view in store to prevent flickering, rename onLoad => onPaneReady
This commit is contained in:
@@ -7,7 +7,7 @@ import ReactFlow, {
|
|||||||
Controls,
|
Controls,
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
OnLoadParams,
|
ReactFlowInstance,
|
||||||
Connection,
|
Connection,
|
||||||
MarkerType,
|
MarkerType,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
@@ -105,7 +105,7 @@ const nodeTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BasicFlow = () => {
|
const BasicFlow = () => {
|
||||||
const [rfInstance, setRfInstance] = useState<OnLoadParams | null>(null);
|
const [rfInstance, setRfInstance] = useState<ReactFlowInstance | null>(null);
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ const BasicFlow = () => {
|
|||||||
return addEdge(connection, eds);
|
return addEdge(connection, eds);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
const onLoad = useCallback((reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance), []);
|
const onPaneReady = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []);
|
||||||
|
|
||||||
const updatePos = () => {
|
const updatePos = () => {
|
||||||
setNodes((nds) => {
|
setNodes((nds) => {
|
||||||
@@ -154,7 +154,7 @@ const BasicFlow = () => {
|
|||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onLoad={onLoad}
|
onPaneReady={onPaneReady}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onNodeClick={onNodeClick}
|
onNodeClick={onNodeClick}
|
||||||
@@ -167,6 +167,7 @@ const BasicFlow = () => {
|
|||||||
maxZoom={4}
|
maxZoom={4}
|
||||||
onlyRenderVisibleElements={false}
|
onlyRenderVisibleElements={false}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
|
fitViewOnInit
|
||||||
>
|
>
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
<Controls />
|
<Controls />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import ReactFlow, {
|
|||||||
MiniMap,
|
MiniMap,
|
||||||
Controls,
|
Controls,
|
||||||
Node,
|
Node,
|
||||||
OnLoadParams,
|
ReactFlowInstance,
|
||||||
Position,
|
Position,
|
||||||
SnapGrid,
|
SnapGrid,
|
||||||
Connection,
|
Connection,
|
||||||
@@ -16,7 +16,7 @@ import ReactFlow, {
|
|||||||
|
|
||||||
import ColorSelectorNode from './ColorSelectorNode';
|
import ColorSelectorNode from './ColorSelectorNode';
|
||||||
|
|
||||||
const onLoad = (reactFlowInstance: OnLoadParams) => {
|
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => {
|
||||||
console.log('flow loaded:', reactFlowInstance);
|
console.log('flow loaded:', reactFlowInstance);
|
||||||
reactFlowInstance.fitView();
|
reactFlowInstance.fitView();
|
||||||
};
|
};
|
||||||
@@ -111,12 +111,13 @@ const CustomNodeFlow = () => {
|
|||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
style={{ background: bgColor }}
|
style={{ background: bgColor }}
|
||||||
onLoad={onLoad}
|
onPaneReady={onPaneReady}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
connectionLineStyle={connectionLineStyle}
|
connectionLineStyle={connectionLineStyle}
|
||||||
snapToGrid={true}
|
snapToGrid={true}
|
||||||
snapGrid={snapGrid}
|
snapGrid={snapGrid}
|
||||||
defaultZoom={1.5}
|
defaultZoom={1.5}
|
||||||
|
fitViewOnInit
|
||||||
>
|
>
|
||||||
<MiniMap
|
<MiniMap
|
||||||
nodeStrokeColor={(n: Node): string => {
|
nodeStrokeColor={(n: Node): string => {
|
||||||
@@ -137,4 +138,4 @@ const CustomNodeFlow = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CustomNodeFlow;
|
export default CustomNodeFlow;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
|
|||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
addEdge,
|
||||||
Background,
|
Background,
|
||||||
OnLoadParams,
|
ReactFlowInstance,
|
||||||
EdgeTypesType,
|
EdgeTypesType,
|
||||||
Connection,
|
Connection,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
@@ -16,7 +16,7 @@ import FloatingEdge from './FloatingEdge';
|
|||||||
import FloatingConnectionLine from './FloatingConnectionLine';
|
import FloatingConnectionLine from './FloatingConnectionLine';
|
||||||
import { createElements } from './utils';
|
import { createElements } from './utils';
|
||||||
|
|
||||||
const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView();
|
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
||||||
|
|
||||||
const { nodes: initialNodes, edges: initialEdges } = createElements();
|
const { nodes: initialNodes, edges: initialEdges } = createElements();
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ const FloatingEdges = () => {
|
|||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onLoad={onLoad}
|
onPaneReady={onPaneReady}
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
connectionLineComponent={FloatingConnectionLine}
|
connectionLineComponent={FloatingConnectionLine}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import { useCallback } from 'react';
|
|||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
addEdge,
|
addEdge,
|
||||||
applyNodeChanges,
|
|
||||||
applyEdgeChanges,
|
|
||||||
Controls,
|
Controls,
|
||||||
NodeChange,
|
|
||||||
EdgeChange,
|
|
||||||
Connection,
|
Connection,
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
Position,
|
Position,
|
||||||
@@ -71,7 +67,7 @@ const LayoutFlow = () => {
|
|||||||
edges={edges}
|
edges={edges}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
nodeExtent={nodeExtent}
|
nodeExtent={nodeExtent}
|
||||||
onLoad={() => onLayout('TB')}
|
onPaneReady={() => onLayout('TB')}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import ReactFlow, {
|
|||||||
Controls,
|
Controls,
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
OnLoadParams,
|
ReactFlowInstance,
|
||||||
Connection,
|
Connection,
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
@@ -84,14 +84,14 @@ const initialEdges: Edge[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const NestedFlow = () => {
|
const NestedFlow = () => {
|
||||||
const [rfInstance, setRfInstance] = useState<OnLoadParams | null>(null);
|
const [rfInstance, setRfInstance] = useState<ReactFlowInstance | null>(null);
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
const onConnect = useCallback((connection: Connection) => {
|
const onConnect = useCallback((connection: Connection) => {
|
||||||
setEdges((eds) => addEdge(connection, eds));
|
setEdges((eds) => addEdge(connection, eds));
|
||||||
}, []);
|
}, []);
|
||||||
const onLoad = useCallback((reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance), []);
|
const onPaneReady = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []);
|
||||||
|
|
||||||
const updatePos = () => {
|
const updatePos = () => {
|
||||||
setNodes((nds) => {
|
setNodes((nds) => {
|
||||||
@@ -131,7 +131,7 @@ const NestedFlow = () => {
|
|||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onLoad={onLoad}
|
onPaneReady={onPaneReady}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onNodeClick={onNodeClick}
|
onNodeClick={onNodeClick}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import ReactFlow, {
|
|||||||
MiniMap,
|
MiniMap,
|
||||||
Controls,
|
Controls,
|
||||||
Background,
|
Background,
|
||||||
OnLoadParams,
|
ReactFlowInstance,
|
||||||
Edge,
|
Edge,
|
||||||
Node,
|
Node,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
@@ -16,7 +16,7 @@ import { getNodesAndEdges } from './utils';
|
|||||||
|
|
||||||
const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 4 };
|
const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 4 };
|
||||||
|
|
||||||
const onLoad = (reactFlowInstance: OnLoadParams) => {
|
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => {
|
||||||
reactFlowInstance.fitView();
|
reactFlowInstance.fitView();
|
||||||
console.log(reactFlowInstance.getNodes());
|
console.log(reactFlowInstance.getNodes());
|
||||||
};
|
};
|
||||||
@@ -55,7 +55,13 @@ const StressFlow = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow nodes={nodes} edges={edges} onLoad={onLoad} onConnect={onConnect} onNodesChange={onNodesChange}>
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onPaneReady={onPaneReady}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
>
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
<Controls />
|
<Controls />
|
||||||
<Background />
|
<Background />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ReactFlow, {
|
|||||||
addEdge,
|
addEdge,
|
||||||
applyNodeChanges,
|
applyNodeChanges,
|
||||||
applyEdgeChanges,
|
applyEdgeChanges,
|
||||||
OnLoadParams,
|
ReactFlowInstance,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
Node,
|
Node,
|
||||||
@@ -53,7 +53,7 @@ const initialNodes: Node[] = [
|
|||||||
|
|
||||||
const initialEdges = [{ id: 'e1-2', source: '1', target: '2', label: 'This is a draggable edge' }];
|
const initialEdges = [{ id: 'e1-2', source: '1', target: '2', label: 'This is a draggable edge' }];
|
||||||
|
|
||||||
const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView();
|
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
|
||||||
const onEdgeUpdateStart = (_: React.MouseEvent, edge: Edge) => console.log('start update', edge);
|
const onEdgeUpdateStart = (_: React.MouseEvent, edge: Edge) => console.log('start update', edge);
|
||||||
const onEdgeUpdateEnd = (_: MouseEvent, edge: Edge) => console.log('end update', edge);
|
const onEdgeUpdateEnd = (_: MouseEvent, edge: Edge) => console.log('end update', edge);
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ const UpdatableEdge = () => {
|
|||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onLoad={onLoad}
|
onPaneReady={onPaneReady}
|
||||||
snapToGrid={true}
|
snapToGrid={true}
|
||||||
onEdgeUpdate={onEdgeUpdate}
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ interface StoreUpdaterProps {
|
|||||||
snapToGrid?: boolean;
|
snapToGrid?: boolean;
|
||||||
snapGrid?: SnapGrid;
|
snapGrid?: SnapGrid;
|
||||||
translateExtent?: CoordinateExtent;
|
translateExtent?: CoordinateExtent;
|
||||||
|
fitViewOnInit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
@@ -58,6 +59,7 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
setOnNodesChange: s.setOnNodesChange,
|
setOnNodesChange: s.setOnNodesChange,
|
||||||
setOnEdgesChange: s.setOnEdgesChange,
|
setOnEdgesChange: s.setOnEdgesChange,
|
||||||
reset: s.reset,
|
reset: s.reset,
|
||||||
|
setFitViewOnInit: s.setFitViewOnInit,
|
||||||
});
|
});
|
||||||
|
|
||||||
const StoreUpdater = ({
|
const StoreUpdater = ({
|
||||||
@@ -79,6 +81,7 @@ const StoreUpdater = ({
|
|||||||
snapGrid,
|
snapGrid,
|
||||||
snapToGrid,
|
snapToGrid,
|
||||||
translateExtent,
|
translateExtent,
|
||||||
|
fitViewOnInit,
|
||||||
}: StoreUpdaterProps) => {
|
}: StoreUpdaterProps) => {
|
||||||
const {
|
const {
|
||||||
setNodes,
|
setNodes,
|
||||||
@@ -100,6 +103,7 @@ const StoreUpdater = ({
|
|||||||
setOnEdgesChange,
|
setOnEdgesChange,
|
||||||
setConnectionMode,
|
setConnectionMode,
|
||||||
reset,
|
reset,
|
||||||
|
setFitViewOnInit,
|
||||||
} = useStore(selector, shallow);
|
} = useStore(selector, shallow);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -212,6 +216,10 @@ const StoreUpdater = ({
|
|||||||
}
|
}
|
||||||
}, [onEdgesChange]);
|
}, [onEdgesChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFitViewOnInit(fitViewOnInit);
|
||||||
|
}, [fitViewOnInit]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import React, { MutableRefObject, useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
import { Node } from '../../types';
|
|
||||||
import { getRectOfNodes, getTransformForBounds } from '../../utils/graph';
|
|
||||||
import { useStoreApi } from '../../store';
|
|
||||||
import useZoomPanHelper from '../../hooks/useZoomPanHelper';
|
|
||||||
|
|
||||||
type ViewFitterInnerProps = {
|
|
||||||
nodes: Node[];
|
|
||||||
viewFitted: MutableRefObject<boolean>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ViewFitterInner({ nodes, viewFitted }: ViewFitterInnerProps) {
|
|
||||||
const store = useStoreApi();
|
|
||||||
const { setTransform } = useZoomPanHelper();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (nodes.length > 0 && !viewFitted.current) {
|
|
||||||
const nodesInitialized = nodes.every((n) => n.width && n.height);
|
|
||||||
|
|
||||||
if (nodesInitialized) {
|
|
||||||
const { width, height, minZoom, maxZoom } = store.getState();
|
|
||||||
const bounds = getRectOfNodes(nodes);
|
|
||||||
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom ?? 0.5, maxZoom ?? 2);
|
|
||||||
|
|
||||||
viewFitted.current = true;
|
|
||||||
setTransform({ x, y, zoom });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [nodes]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ViewFitterProps = {
|
|
||||||
nodes: Node[];
|
|
||||||
};
|
|
||||||
|
|
||||||
function ViewFitter({ nodes }: ViewFitterProps) {
|
|
||||||
// we are storing the viewFitted in this wrapper to get rid of the useEffect of the inner component
|
|
||||||
// that needs to check if the nodes changed after we fitted the view.
|
|
||||||
const viewFitted = useRef<boolean>(false);
|
|
||||||
|
|
||||||
if (viewFitted.current) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ViewFitterInner nodes={nodes} viewFitted={viewFitted} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ViewFitter;
|
|
||||||
@@ -5,7 +5,7 @@ import NodeRenderer from '../NodeRenderer';
|
|||||||
import EdgeRenderer from '../EdgeRenderer';
|
import EdgeRenderer from '../EdgeRenderer';
|
||||||
import Viewport from '../Viewport';
|
import Viewport from '../Viewport';
|
||||||
import { ReactFlowProps } from '../ReactFlow';
|
import { ReactFlowProps } from '../ReactFlow';
|
||||||
import useOnLoadHandler from '../../hooks/useOnLoadHandler';
|
import useOnPaneReadyHandler from '../../hooks/useOnPaneReadyHandler';
|
||||||
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode } from '../../types';
|
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode } from '../../types';
|
||||||
|
|
||||||
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges'> {
|
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges'> {
|
||||||
@@ -31,7 +31,7 @@ const GraphView = ({
|
|||||||
onMove,
|
onMove,
|
||||||
onMoveStart,
|
onMoveStart,
|
||||||
onMoveEnd,
|
onMoveEnd,
|
||||||
onLoad,
|
onPaneReady,
|
||||||
onNodeClick,
|
onNodeClick,
|
||||||
onEdgeClick,
|
onEdgeClick,
|
||||||
onNodeDoubleClick,
|
onNodeDoubleClick,
|
||||||
@@ -83,7 +83,7 @@ const GraphView = ({
|
|||||||
noWheelClassName,
|
noWheelClassName,
|
||||||
noPanClassName,
|
noPanClassName,
|
||||||
}: GraphViewProps) => {
|
}: GraphViewProps) => {
|
||||||
useOnLoadHandler(onLoad);
|
useOnPaneReadyHandler(onPaneReady);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlowRenderer
|
<FlowRenderer
|
||||||
|
|||||||
@@ -19,12 +19,10 @@ import SelectionListener from '../../components/SelectionListener';
|
|||||||
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
|
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
|
||||||
import { createEdgeTypes } from '../EdgeRenderer/utils';
|
import { createEdgeTypes } from '../EdgeRenderer/utils';
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
import ViewFitter from '../../components/ViewFitter';
|
|
||||||
import {
|
import {
|
||||||
OnSelectionChangeFunc,
|
OnSelectionChangeFunc,
|
||||||
NodeTypesType,
|
NodeTypesType,
|
||||||
EdgeTypesType,
|
EdgeTypesType,
|
||||||
OnLoad,
|
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
@@ -41,6 +39,7 @@ import {
|
|||||||
OnEdgeUpdateFunc,
|
OnEdgeUpdateFunc,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
EdgeChange,
|
EdgeChange,
|
||||||
|
OnPaneReady,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import '../../style.css';
|
import '../../style.css';
|
||||||
@@ -59,7 +58,7 @@ const defaultEdgeTypes = {
|
|||||||
smoothstep: SmoothStepEdge,
|
smoothstep: SmoothStepEdge,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
|
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onPaneReady'> {
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
onNodesChange?: (nodeChanges: NodeChange[]) => void;
|
onNodesChange?: (nodeChanges: NodeChange[]) => void;
|
||||||
@@ -78,7 +77,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|||||||
onConnectStart?: OnConnectStart;
|
onConnectStart?: OnConnectStart;
|
||||||
onConnectStop?: OnConnectStop;
|
onConnectStop?: OnConnectStop;
|
||||||
onConnectEnd?: OnConnectEnd;
|
onConnectEnd?: OnConnectEnd;
|
||||||
onLoad?: OnLoad;
|
onPaneReady?: OnPaneReady;
|
||||||
onMove?: (flowTransform?: FlowTransform) => void;
|
onMove?: (flowTransform?: FlowTransform) => void;
|
||||||
onMoveStart?: (flowTransform?: FlowTransform) => void;
|
onMoveStart?: (flowTransform?: FlowTransform) => void;
|
||||||
onMoveEnd?: (flowTransform?: FlowTransform) => void;
|
onMoveEnd?: (flowTransform?: FlowTransform) => void;
|
||||||
@@ -152,7 +151,7 @@ const ReactFlow: FunctionComponent<ReactFlowProps> = forwardRef<ReactFlowRefType
|
|||||||
edgeTypes = defaultEdgeTypes,
|
edgeTypes = defaultEdgeTypes,
|
||||||
onNodeClick,
|
onNodeClick,
|
||||||
onEdgeClick,
|
onEdgeClick,
|
||||||
onLoad,
|
onPaneReady,
|
||||||
onMove,
|
onMove,
|
||||||
onMoveStart,
|
onMoveStart,
|
||||||
onMoveEnd,
|
onMoveEnd,
|
||||||
@@ -234,7 +233,7 @@ const ReactFlow: FunctionComponent<ReactFlowProps> = forwardRef<ReactFlowRefType
|
|||||||
<div {...rest} ref={ref} className={reactFlowClasses}>
|
<div {...rest} ref={ref} className={reactFlowClasses}>
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<GraphView
|
<GraphView
|
||||||
onLoad={onLoad}
|
onPaneReady={onPaneReady}
|
||||||
onMove={onMove}
|
onMove={onMove}
|
||||||
onMoveStart={onMoveStart}
|
onMoveStart={onMoveStart}
|
||||||
onMoveEnd={onMoveEnd}
|
onMoveEnd={onMoveEnd}
|
||||||
@@ -309,8 +308,8 @@ const ReactFlow: FunctionComponent<ReactFlowProps> = forwardRef<ReactFlowRefType
|
|||||||
snapGrid={snapGrid}
|
snapGrid={snapGrid}
|
||||||
connectionMode={connectionMode}
|
connectionMode={connectionMode}
|
||||||
translateExtent={translateExtent}
|
translateExtent={translateExtent}
|
||||||
|
fitViewOnInit={fitViewOnInit}
|
||||||
/>
|
/>
|
||||||
{fitViewOnInit && <ViewFitter nodes={nodes} />}
|
|
||||||
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
||||||
{children}
|
{children}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { useEffect, useRef } from 'react';
|
|||||||
|
|
||||||
import { pointToRendererPoint } from '../utils/graph';
|
import { pointToRendererPoint } from '../utils/graph';
|
||||||
import { useStoreApi } from '../store';
|
import { useStoreApi } from '../store';
|
||||||
import useZoomPanHelper from '../hooks/useZoomPanHelper';
|
import useZoomPanHelper from './useZoomPanHelper';
|
||||||
import { OnLoad, XYPosition, Node, Edge, FlowExportObject } from '../types';
|
import { OnPaneReady, XYPosition, Node, Edge, FlowExportObject } from '../types';
|
||||||
|
|
||||||
function useOnLoadHandler(onLoad: OnLoad<any> | undefined) {
|
function useOnPaneReadyHandler(onPaneReady: OnPaneReady<any> | undefined) {
|
||||||
const isInitialized = useRef<boolean>(false);
|
const isInitialized = useRef<boolean>(false);
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const { zoomIn, zoomOut, zoomTo, getZoom, setTransform, getTransform, setCenter, fitView, initialized } =
|
const { zoomIn, zoomOut, zoomTo, getZoom, setTransform, getTransform, setCenter, fitView, initialized } =
|
||||||
@@ -13,7 +13,7 @@ function useOnLoadHandler(onLoad: OnLoad<any> | undefined) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isInitialized.current && initialized) {
|
if (!isInitialized.current && initialized) {
|
||||||
if (onLoad) {
|
if (onPaneReady) {
|
||||||
const project = (position: XYPosition): XYPosition => {
|
const project = (position: XYPosition): XYPosition => {
|
||||||
const { transform, snapToGrid, snapGrid } = store.getState();
|
const { transform, snapToGrid, snapGrid } = store.getState();
|
||||||
return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
|
return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
|
||||||
@@ -43,7 +43,7 @@ function useOnLoadHandler(onLoad: OnLoad<any> | undefined) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
onLoad({
|
onPaneReady({
|
||||||
fitView: (params = { padding: 0.1 }) => fitView(params),
|
fitView: (params = { padding: 0.1 }) => fitView(params),
|
||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
@@ -61,7 +61,7 @@ function useOnLoadHandler(onLoad: OnLoad<any> | undefined) {
|
|||||||
|
|
||||||
isInitialized.current = true;
|
isInitialized.current = true;
|
||||||
}
|
}
|
||||||
}, [onLoad, zoomIn, zoomOut, zoomTo, setTransform, fitView, initialized]);
|
}, [onPaneReady, zoomIn, zoomOut, zoomTo, setTransform, fitView, initialized]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useOnLoadHandler;
|
export default useOnPaneReadyHandler;
|
||||||
+6
-3
@@ -28,7 +28,7 @@ import {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
import { getHandleBounds } from '../components/Nodes/utils';
|
import { getHandleBounds } from '../components/Nodes/utils';
|
||||||
import { createSelectionChange, getSelectionChanges } from '../utils/changes';
|
import { createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||||
import { createNodeInternals, createPositionChange, isParentSelected } from './utils';
|
import { createNodeInternals, createPositionChange, fitView, isParentSelected } from './utils';
|
||||||
import initialState from './initialState';
|
import initialState from './initialState';
|
||||||
|
|
||||||
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
|
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
|
||||||
@@ -46,7 +46,7 @@ const createStore = () =>
|
|||||||
set({ edges });
|
set({ edges });
|
||||||
},
|
},
|
||||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
|
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
|
||||||
const { onNodesChange, transform, nodeInternals } = get();
|
const { onNodesChange, transform, nodeInternals, fitViewOnInit } = get();
|
||||||
|
|
||||||
const changes: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => {
|
const changes: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => {
|
||||||
const node = nodeInternals.get(update.id);
|
const node = nodeInternals.get(update.id);
|
||||||
@@ -78,7 +78,9 @@ const createStore = () =>
|
|||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
set({ nodeInternals: new Map(nodeInternals) });
|
const fitViewOnInitDone = fitViewOnInit && fitView(get);
|
||||||
|
|
||||||
|
set({ nodeInternals: new Map(nodeInternals), fitViewOnInitDone });
|
||||||
|
|
||||||
if (changes?.length > 0) {
|
if (changes?.length > 0) {
|
||||||
onNodesChange?.(changes);
|
onNodesChange?.(changes);
|
||||||
@@ -234,6 +236,7 @@ const createStore = () =>
|
|||||||
setOnNodesChange: (onNodesChange: OnNodesChange) => set({ onNodesChange }),
|
setOnNodesChange: (onNodesChange: OnNodesChange) => set({ onNodesChange }),
|
||||||
setOnEdgesChange: (onEdgesChange: OnEdgesChange) => set({ onEdgesChange }),
|
setOnEdgesChange: (onEdgesChange: OnEdgesChange) => set({ onEdgesChange }),
|
||||||
reset: () => set({ ...initialState }),
|
reset: () => set({ ...initialState }),
|
||||||
|
setFitViewOnInit: (fitViewOnInit: boolean) => set({ fitViewOnInit }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export { Provider, useStore, createStore, useStoreApi };
|
export { Provider, useStore, createStore, useStoreApi };
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ const initialState: ReactFlowStore = {
|
|||||||
multiSelectionActive: false,
|
multiSelectionActive: false,
|
||||||
|
|
||||||
reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
|
reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
|
||||||
|
|
||||||
|
fitViewOnInit: false,
|
||||||
|
fitViewOnInitDone: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default initialState;
|
export default initialState;
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
|
import { zoomIdentity } from 'd3-zoom';
|
||||||
|
import { GetState } from 'zustand';
|
||||||
import {
|
import {
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
Node,
|
Node,
|
||||||
NodeDimensionChange,
|
NodeDimensionChange,
|
||||||
NodeInternals,
|
NodeInternals,
|
||||||
NodeInternalsItem,
|
NodeInternalsItem,
|
||||||
|
ReactFlowState,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
XYZPosition,
|
XYZPosition,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { clampPosition, isNumeric } from '../utils';
|
import { clampPosition, isNumeric } from '../utils';
|
||||||
|
import { getRectOfNodes, getTransformForBounds } from '../utils/graph';
|
||||||
|
|
||||||
type ParentNodes = Record<string, boolean>;
|
type ParentNodes = Record<string, boolean>;
|
||||||
|
|
||||||
@@ -172,3 +176,25 @@ export function createPositionChange({
|
|||||||
|
|
||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fitView(get: GetState<ReactFlowState>) {
|
||||||
|
let { nodeInternals, width, height, minZoom, maxZoom, d3Zoom, d3Selection, fitViewOnInitDone, fitViewOnInit } = get();
|
||||||
|
|
||||||
|
if (fitViewOnInit && !fitViewOnInitDone && d3Zoom && d3Selection) {
|
||||||
|
const rootNodes = Array.from(nodeInternals)
|
||||||
|
.filter(([_, n]) => !n.parentNode)
|
||||||
|
.map(([_, n]) => n);
|
||||||
|
const nodesInitialized = rootNodes.every((n) => n.width && n.height);
|
||||||
|
|
||||||
|
if (rootNodes.length > 0 && nodesInitialized) {
|
||||||
|
const bounds = getRectOfNodes(rootNodes);
|
||||||
|
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom ?? 0.5, maxZoom ?? 2);
|
||||||
|
|
||||||
|
const nextTransform = zoomIdentity.translate(x, y).scale(zoom);
|
||||||
|
d3Zoom.transform(d3Selection, nextTransform);
|
||||||
|
fitViewOnInitDone = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fitViewOnInitDone;
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export type ZoomTo = (zoomLevel: number, options?: ZoomPanHelperFunctionOptions)
|
|||||||
export type SetTransform = (transform: FlowTransform, options?: ZoomPanHelperFunctionOptions) => void;
|
export type SetTransform = (transform: FlowTransform, options?: ZoomPanHelperFunctionOptions) => void;
|
||||||
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void;
|
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void;
|
||||||
|
|
||||||
export type OnLoadParams<T = any> = {
|
export type ReactFlowInstance<T = any> = {
|
||||||
zoomIn: ZoomInOut;
|
zoomIn: ZoomInOut;
|
||||||
zoomOut: ZoomInOut;
|
zoomOut: ZoomInOut;
|
||||||
zoomTo: ZoomTo;
|
zoomTo: ZoomTo;
|
||||||
@@ -44,7 +44,7 @@ export type OnLoadParams<T = any> = {
|
|||||||
toObject: ToObject<T>;
|
toObject: ToObject<T>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OnLoad<T = any> = (params: OnLoadParams<T>) => void;
|
export type OnPaneReady<T = any> = (reactFlowInstance: ReactFlowInstance<T>) => void;
|
||||||
|
|
||||||
export interface Connection {
|
export interface Connection {
|
||||||
source: string | null;
|
source: string | null;
|
||||||
@@ -183,6 +183,9 @@ export type ReactFlowStore = {
|
|||||||
multiSelectionActive: boolean;
|
multiSelectionActive: boolean;
|
||||||
|
|
||||||
reactFlowVersion: string;
|
reactFlowVersion: string;
|
||||||
|
|
||||||
|
fitViewOnInit: boolean;
|
||||||
|
fitViewOnInitDone: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowActions = {
|
export type ReactFlowActions = {
|
||||||
@@ -226,6 +229,7 @@ export type ReactFlowActions = {
|
|||||||
onConnectEnd?: OnConnectEnd;
|
onConnectEnd?: OnConnectEnd;
|
||||||
|
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
|
setFitViewOnInit: (fitViewOnInit: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowState = ReactFlowStore & ReactFlowActions;
|
export type ReactFlowState = ReactFlowStore & ReactFlowActions;
|
||||||
|
|||||||
+1
-1
@@ -8,8 +8,8 @@ import { HandleElement } from './handles';
|
|||||||
export interface Node<T = any> {
|
export interface Node<T = any> {
|
||||||
id: string;
|
id: string;
|
||||||
position: XYPosition;
|
position: XYPosition;
|
||||||
|
data: T;
|
||||||
type?: string;
|
type?: string;
|
||||||
data?: T;
|
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
targetPosition?: Position;
|
targetPosition?: Position;
|
||||||
|
|||||||
Reference in New Issue
Block a user