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