refactor(general): fit view in store to prevent flickering, rename onLoad => onPaneReady

This commit is contained in:
moklick
2021-12-09 07:24:45 +01:00
parent 3a52efbb1f
commit 88582a86cc
17 changed files with 96 additions and 100 deletions
+5 -4
View File
@@ -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 />
+5 -4
View File
@@ -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 -3
View File
@@ -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}
> >
+1 -5
View File
@@ -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}
> >
+4 -4
View File
@@ -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}
+9 -3
View File
@@ -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 />
+3 -3
View File
@@ -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}
+8
View File
@@ -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;
}; };
-51
View File
@@ -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;
+3 -3
View File
@@ -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
+6 -7
View File
@@ -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
View File
@@ -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 };
+3
View File
@@ -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;
+26
View File
@@ -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;
}
+6 -2
View File
@@ -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
View File
@@ -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;