refactor(fitView): use same fitView function for viewport helper and initial prop

This commit is contained in:
Christopher Möller
2022-01-27 13:00:03 +01:00
parent 0244c93cb9
commit af52bab2a5
40 changed files with 169 additions and 184 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ const BasicFlow = () => {
defaultZoom={1.5} defaultZoom={1.5}
minZoom={0.2} minZoom={0.2}
maxZoom={4} maxZoom={4}
fitViewOnInit fitView
> >
<Background variant={BackgroundVariant.Lines} /> <Background variant={BackgroundVariant.Lines} />
+1 -1
View File
@@ -69,7 +69,7 @@ const ControlledUncontrolled = () => {
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
defaultEdgeOptions={defaultEdgeOptions} defaultEdgeOptions={defaultEdgeOptions}
fitViewOnInit fitView
> >
<Background variant={BackgroundVariant.Lines} /> <Background variant={BackgroundVariant.Lines} />
+3 -3
View File
@@ -16,7 +16,7 @@ import ReactFlow, {
import ColorSelectorNode from './ColorSelectorNode'; import ColorSelectorNode from './ColorSelectorNode';
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => { const onInit = (reactFlowInstance: ReactFlowInstance) => {
console.log('flow loaded:', reactFlowInstance); console.log('flow loaded:', reactFlowInstance);
reactFlowInstance.fitView(); reactFlowInstance.fitView();
}; };
@@ -111,13 +111,13 @@ const CustomNodeFlow = () => {
onConnect={onConnect} onConnect={onConnect}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}
style={{ background: bgColor }} style={{ background: bgColor }}
onPaneReady={onPaneReady} onInit={onInit}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
snapToGrid={true} snapToGrid={true}
snapGrid={snapGrid} snapGrid={snapGrid}
defaultZoom={1.5} defaultZoom={1.5}
fitViewOnInit fitView
> >
<MiniMap <MiniMap
nodeStrokeColor={(n: Node): string => { nodeStrokeColor={(n: Node): string => {
+1 -6
View File
@@ -55,12 +55,7 @@ const DefaultNodes = () => {
}; };
return ( return (
<ReactFlow <ReactFlow defaultNodes={defaultNodes} defaultEdges={defaultEdges} defaultEdgeOptions={defaultEdgeOptions} fitView>
defaultNodes={defaultNodes}
defaultEdges={defaultEdges}
defaultEdgeOptions={defaultEdgeOptions}
fitViewOnInit
>
<Background variant={BackgroundVariant.Lines} /> <Background variant={BackgroundVariant.Lines} />
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}> <div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
+2 -2
View File
@@ -31,7 +31,7 @@ const DnDFlow = () => {
const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)); const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
const onPaneReady = (rfi: ReactFlowInstance) => setReactFlowInstance(rfi); const onInit = (rfi: ReactFlowInstance) => setReactFlowInstance(rfi);
const onDrop = (event: DragEvent) => { const onDrop = (event: DragEvent) => {
event.preventDefault(); event.preventDefault();
@@ -60,7 +60,7 @@ const DnDFlow = () => {
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onConnect={onConnect} onConnect={onConnect}
onPaneReady={onPaneReady} onInit={onInit}
onDrop={onDrop} onDrop={onDrop}
onDragOver={onDragOver} onDragOver={onDragOver}
> >
+2 -2
View File
@@ -15,7 +15,7 @@ import ReactFlow, {
} from 'react-flow-renderer'; } from 'react-flow-renderer';
import { getElements } from './utils'; import { getElements } from './utils';
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => { const onInit = (reactFlowInstance: ReactFlowInstance) => {
reactFlowInstance.fitView(); reactFlowInstance.fitView();
console.log(reactFlowInstance.getNodes()); console.log(reactFlowInstance.getNodes());
}; };
@@ -36,7 +36,7 @@ const EdgeTypesFlow = () => {
edges={edges} edges={edges}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onPaneReady={onPaneReady} onInit={onInit}
onConnect={onConnect} onConnect={onConnect}
minZoom={0.2} minZoom={0.2}
zoomOnScroll={false} zoomOnScroll={false}
+2 -2
View File
@@ -18,7 +18,7 @@ import ReactFlow, {
import CustomEdge from './CustomEdge'; import CustomEdge from './CustomEdge';
import CustomEdge2 from './CustomEdge2'; import CustomEdge2 from './CustomEdge2';
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView(); const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
@@ -121,7 +121,7 @@ const EdgesFlow = () => {
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
onConnect={onConnect} onConnect={onConnect}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}
onPaneReady={onPaneReady} onInit={onInit}
snapToGrid={true} snapToGrid={true}
edgeTypes={edgeTypes} edgeTypes={edgeTypes}
> >
+2 -2
View File
@@ -13,7 +13,7 @@ import ReactFlow, {
ReactFlowInstance, ReactFlowInstance,
} from 'react-flow-renderer'; } from 'react-flow-renderer';
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance); const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance);
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
@@ -38,7 +38,7 @@ const EmptyFlow = () => {
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
onPaneReady={onPaneReady} onInit={onInit}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
+2 -2
View File
@@ -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 onPaneReady = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView(); const onInit = (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}
onPaneReady={onPaneReady} onInit={onInit}
edgeTypes={edgeTypes} edgeTypes={edgeTypes}
connectionLineComponent={FloatingConnectionLine} connectionLineComponent={FloatingConnectionLine}
> >
+1 -1
View File
@@ -67,7 +67,7 @@ const LayoutFlow = () => {
edges={edges} edges={edges}
onConnect={onConnect} onConnect={onConnect}
nodeExtent={nodeExtent} nodeExtent={nodeExtent}
onPaneReady={() => onLayout('TB')} onInit={() => onLayout('TB')}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
> >
+2 -2
View File
@@ -91,7 +91,7 @@ const NestedFlow = () => {
const onConnect = useCallback((connection: Connection) => { const onConnect = useCallback((connection: Connection) => {
setEdges((eds) => addEdge(connection, eds)); setEdges((eds) => addEdge(connection, eds));
}, []); }, []);
const onPaneReady = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []); const onInit = 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}
onPaneReady={onPaneReady} onInit={onInit}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
+1 -1
View File
@@ -58,7 +58,7 @@ const NodeTypeChangeFlow = () => {
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onConnect={onConnect} onConnect={onConnect}
fitViewOnInit fitView
> >
<button onClick={changeType} style={buttonStyle}> <button onClick={changeType} style={buttonStyle}>
change type change type
+5 -3
View File
@@ -32,7 +32,7 @@ const onSelectionContextMenu = (event: MouseEvent, nodes: Node[]) => {
const onNodeClick = (_: MouseEvent, node: Node) => console.log('node click:', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('node click:', node);
const onSelectionChange = ({ nodes, edges }: OnSelectionChangeParams) => console.log('selection change', nodes, edges); const onSelectionChange = ({ nodes, edges }: OnSelectionChangeParams) => console.log('selection change', nodes, edges);
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => { const onInit = (reactFlowInstance: ReactFlowInstance) => {
console.log('pane ready:', reactFlowInstance); console.log('pane ready:', reactFlowInstance);
}; };
@@ -183,7 +183,7 @@ const OverviewFlow = () => {
onSelectionChange={onSelectionChange} onSelectionChange={onSelectionChange}
onMoveStart={onMoveStart} onMoveStart={onMoveStart}
onMoveEnd={onMoveEnd} onMoveEnd={onMoveEnd}
onPaneReady={onPaneReady} onInit={onInit}
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
snapToGrid={true} snapToGrid={true}
snapGrid={snapGrid} snapGrid={snapGrid}
@@ -192,8 +192,10 @@ const OverviewFlow = () => {
onEdgeMouseMove={onEdgeMouseMove} onEdgeMouseMove={onEdgeMouseMove}
onEdgeMouseLeave={onEdgeMouseLeave} onEdgeMouseLeave={onEdgeMouseLeave}
onEdgeDoubleClick={onEdgeDoubleClick} onEdgeDoubleClick={onEdgeDoubleClick}
fitViewOnInit fitView
fitViewOptions={{ padding: 0.2 }}
attributionPosition="top-right" attributionPosition="top-right"
maxZoom={Infinity}
> >
<MiniMap nodeStrokeColor={nodeStrokeColor} nodeColor={nodeColor} nodeBorderRadius={2} /> <MiniMap nodeStrokeColor={nodeStrokeColor} nodeColor={nodeColor} nodeBorderRadius={2} />
<Controls /> <Controls />
+2 -2
View File
@@ -17,7 +17,7 @@ import Sidebar from './Sidebar';
import './provider.css'; import './provider.css';
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
const onPaneReady = (reactFlowInstance: ReactFlowInstance) => console.log('pane ready:', reactFlowInstance); const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('pane ready:', reactFlowInstance);
const initialNodes: Node[] = [ const initialNodes: Node[] = [
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }, { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
@@ -48,7 +48,7 @@ const ProviderFlow = () => {
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
onConnect={onConnect} onConnect={onConnect}
onPaneReady={onPaneReady} onInit={onInit}
connectionMode={ConnectionMode.Loose} connectionMode={ConnectionMode.Loose}
> >
<Controls /> <Controls />
+1 -1
View File
@@ -35,7 +35,7 @@ const SaveRestore = () => {
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onConnect={onConnect} onConnect={onConnect}
onPaneReady={setRfInstance} onInit={setRfInstance}
> >
<Controls rfInstance={rfInstance} setNodes={setNodes} setEdges={setEdges} /> <Controls rfInstance={rfInstance} setNodes={setNodes} setEdges={setEdges} />
</ReactFlow> </ReactFlow>
+2 -8
View File
@@ -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 onPaneReady = (reactFlowInstance: ReactFlowInstance) => { const onInit = (reactFlowInstance: ReactFlowInstance) => {
reactFlowInstance.fitView(); reactFlowInstance.fitView();
console.log(reactFlowInstance.getNodes()); console.log(reactFlowInstance.getNodes());
}; };
@@ -55,13 +55,7 @@ const StressFlow = () => {
}, []); }, []);
return ( return (
<ReactFlow <ReactFlow nodes={nodes} edges={edges} onInit={onInit} onConnect={onConnect} onNodesChange={onNodesChange}>
nodes={nodes}
edges={edges}
onPaneReady={onPaneReady}
onConnect={onConnect}
onNodesChange={onNodesChange}
>
<MiniMap /> <MiniMap />
<Controls /> <Controls />
<Background /> <Background />
+3 -3
View File
@@ -113,7 +113,7 @@ const BasicFlow = () => {
const onConnect = useCallback((connection: Connection) => { const onConnect = useCallback((connection: Connection) => {
setEdges((eds) => addEdge(connection, eds)); setEdges((eds) => addEdge(connection, eds));
}, []); }, []);
const onPaneReady = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []); const onInit = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []);
const updatePos = () => { const updatePos = () => {
setNodes((nds) => { setNodes((nds) => {
@@ -155,7 +155,7 @@ const BasicFlow = () => {
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
onPaneReady={onPaneReady} onInit={onInit}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
@@ -168,7 +168,7 @@ const BasicFlow = () => {
maxZoom={4} maxZoom={4}
onlyRenderVisibleElements={false} onlyRenderVisibleElements={false}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
fitViewOnInit fitView
> >
<MiniMap /> <MiniMap />
<Controls /> <Controls />
+2 -2
View File
@@ -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 onPaneReady = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView(); const onInit = (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}
onPaneReady={onPaneReady} onInit={onInit}
snapToGrid={true} snapToGrid={true}
onEdgeUpdate={onEdgeUpdate} onEdgeUpdate={onEdgeUpdate}
onConnect={onConnect} onConnect={onConnect}
+6 -2
View File
@@ -33,7 +33,7 @@ const UseZoomPanHelperFlow = () => {
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 = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)); const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
const { project, setCenter, zoomIn, zoomOut } = useReactFlow(); const { project, setCenter, zoomIn, zoomOut, fitView } = useReactFlow();
const onPaneClick = useCallback( const onPaneClick = useCallback(
(evt) => { (evt) => {
@@ -55,7 +55,7 @@ const UseZoomPanHelperFlow = () => {
const onNodeClick = useCallback( const onNodeClick = useCallback(
(_, element) => { (_, element) => {
const { x, y } = element.position; const { x, y } = element.position;
setCenter(x, y, { zoom: 1 }); setCenter(x, y, { zoom: 1, duration: 1200 });
}, },
[setCenter] [setCenter]
); );
@@ -69,10 +69,14 @@ const UseZoomPanHelperFlow = () => {
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
onConnect={onConnect} onConnect={onConnect}
onPaneClick={onPaneClick} onPaneClick={onPaneClick}
fitView
fitViewOptions={{ duration: 1200, padding: 0.2 }}
maxZoom={Infinity}
> >
<div style={{ position: 'absolute', left: 0, top: 0, zIndex: 100 }}> <div style={{ position: 'absolute', left: 0, top: 0, zIndex: 100 }}>
<button onClick={() => zoomIn({ duration: 1200 })}>zoomIn</button> <button onClick={() => zoomIn({ duration: 1200 })}>zoomIn</button>
<button onClick={() => zoomOut({ duration: 0 })}>zoomOut</button> <button onClick={() => zoomOut({ duration: 0 })}>zoomOut</button>
<button onClick={() => fitView({ duration: 1200, padding: 0.3 })}>fitView</button>
</div> </div>
<Background /> <Background />
<MiniMap /> <MiniMap />
+1 -1
View File
@@ -70,7 +70,7 @@ const ValidationFlow = () => {
onConnectStart={onConnectStart} onConnectStart={onConnectStart}
onConnectStop={onConnectStop} onConnectStop={onConnectStop}
onConnectEnd={onConnectEnd} onConnectEnd={onConnectEnd}
fitViewOnInit fitView
/> />
); );
}; };
+1 -1
View File
@@ -14,7 +14,7 @@
"d3-selection": "^3.0.0", "d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0", "d3-zoom": "^3.0.0",
"react-draggable": "^4.4.4", "react-draggable": "^4.4.4",
"zustand": "^3.6.7" "zustand": "^3.6.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.16.5", "@babel/core": "^7.16.5",
+1 -1
View File
@@ -36,7 +36,7 @@
"d3-selection": "^3.0.0", "d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0", "d3-zoom": "^3.0.0",
"react-draggable": "^4.4.4", "react-draggable": "^4.4.4",
"zustand": "^3.6.7" "zustand": "^3.6.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.16.5", "@babel/core": "^7.16.5",
+3 -3
View File
@@ -25,7 +25,7 @@ const Controls: FC<ControlProps> = ({
showZoom = true, showZoom = true,
showFitView = true, showFitView = true,
showInteractive = true, showInteractive = true,
fitViewParams, fitViewOptions,
onZoomIn, onZoomIn,
onZoomOut, onZoomOut,
onFitView, onFitView,
@@ -51,9 +51,9 @@ const Controls: FC<ControlProps> = ({
}, [zoomOut, onZoomOut]); }, [zoomOut, onZoomOut]);
const onFitViewHandler = useCallback(() => { const onFitViewHandler = useCallback(() => {
fitView?.(fitViewParams); fitView?.(fitViewOptions);
onFitView?.(); onFitView?.();
}, [fitView, fitViewParams, onFitView]); }, [fitView, fitViewOptions, onFitView]);
const onInteractiveChangeHandler = useCallback(() => { const onInteractiveChangeHandler = useCallback(() => {
store.setState({ store.setState({
+2 -2
View File
@@ -5,7 +5,7 @@ import shallow from 'zustand/shallow';
import MiniMapNode from './MiniMapNode'; import MiniMapNode from './MiniMapNode';
import { useStore } from '../../store'; import { useStore } from '../../store';
import { getRectOfNodeInternals } from '../../utils/graph'; import { getRectOfNodes } from '../../utils/graph';
import { getBoundsofRects } from '../../utils'; import { getBoundsofRects } from '../../utils';
import { MiniMapProps, GetMiniMapNodeAttribute, ReactFlowState, Rect } from '../../types'; import { MiniMapProps, GetMiniMapNodeAttribute, ReactFlowState, Rect } from '../../types';
@@ -48,7 +48,7 @@ const MiniMap = ({
const hasNodes = nodeInternals && nodeInternals.size > 0; const hasNodes = nodeInternals && nodeInternals.size > 0;
// @TODO: work with nodeInternals instead of converting it to an array // @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(nodeInternals).map(([_, node]) => node); const nodes = Array.from(nodeInternals).map(([_, node]) => node);
const bb = getRectOfNodeInternals(nodes); const bb = getRectOfNodes(nodes);
const viewBB: Rect = { const viewBB: Rect = {
x: -tX / tScale, x: -tX / tScale,
y: -tY / tScale, y: -tY / tScale,
+3 -3
View File
@@ -4,12 +4,12 @@ import cc from 'classcat';
import { AttributionPosition, ProOptions } from '../../types'; import { AttributionPosition, ProOptions } from '../../types';
type AttributionProps = { type AttributionProps = {
pro?: ProOptions; proOptions?: ProOptions;
position?: AttributionPosition; position?: AttributionPosition;
}; };
function Attribution({ pro, position = 'bottom-right' }: AttributionProps) { function Attribution({ proOptions, position = 'bottom-right' }: AttributionProps) {
if (pro?.account === 'paid-subscription' && pro?.hideAttribution) { if (proOptions?.account === 'paid-subscription' && proOptions?.hideAttribution) {
return null; return null;
} }
+1 -4
View File
@@ -40,7 +40,6 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
sourcePosition, sourcePosition,
targetPosition, targetPosition,
hidden, hidden,
isInitialized,
snapToGrid, snapToGrid,
snapGrid, snapGrid,
dragging, dragging,
@@ -60,11 +59,9 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
zIndex, zIndex,
transform: `translate(${xPos}px,${yPos}px)`, transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents: hasPointerEvents ? 'all' : 'none', pointerEvents: hasPointerEvents ? 'all' : 'none',
// prevents jumping of nodes on start
opacity: isInitialized ? 1 : 0,
...style, ...style,
}), }),
[zIndex, xPos, yPos, hasPointerEvents, isInitialized, style] [zIndex, xPos, yPos, hasPointerEvents, style]
); );
const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]); const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
+2 -2
View File
@@ -10,7 +10,7 @@ import shallow from 'zustand/shallow';
import { useStore } from '../../store'; import { useStore } from '../../store';
import { Node, ReactFlowState } from '../../types'; import { Node, ReactFlowState } from '../../types';
import { getRectOfNodeInternals } from '../../utils/graph'; import { getRectOfNodes } from '../../utils/graph';
export interface NodesSelectionProps { export interface NodesSelectionProps {
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void; onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
@@ -55,7 +55,7 @@ function NodesSelection({
[tX, tY, tScale] [tX, tY, tScale]
); );
const selectedNodesBbox = useMemo(() => getRectOfNodeInternals(selectedNodes), [selectedNodes]); const selectedNodesBbox = useMemo(() => getRectOfNodes(selectedNodes), [selectedNodes]);
const innerStyle = useMemo( const innerStyle = useMemo(
() => ({ () => ({
+7 -3
View File
@@ -17,6 +17,7 @@ import {
ConnectionMode, ConnectionMode,
SnapGrid, SnapGrid,
DefaultEdgeOptions, DefaultEdgeOptions,
FitViewOptions,
} from '../../types'; } from '../../types';
interface StoreUpdaterProps { interface StoreUpdaterProps {
@@ -40,9 +41,10 @@ interface StoreUpdaterProps {
snapToGrid?: boolean; snapToGrid?: boolean;
snapGrid?: SnapGrid; snapGrid?: SnapGrid;
translateExtent?: CoordinateExtent; translateExtent?: CoordinateExtent;
fitViewOnInit: boolean;
connectOnClick: boolean; connectOnClick: boolean;
defaultEdgeOptions?: DefaultEdgeOptions; defaultEdgeOptions?: DefaultEdgeOptions;
fitView?: boolean;
fitViewOptions?: FitViewOptions;
} }
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
@@ -94,9 +96,10 @@ const StoreUpdater = ({
snapGrid, snapGrid,
snapToGrid, snapToGrid,
translateExtent, translateExtent,
fitViewOnInit,
connectOnClick, connectOnClick,
defaultEdgeOptions, defaultEdgeOptions,
fitView,
fitViewOptions,
}: StoreUpdaterProps) => { }: StoreUpdaterProps) => {
const { const {
setNodes, setNodes,
@@ -127,12 +130,13 @@ const StoreUpdater = ({
useDirectStoreUpdater('nodesDraggable', nodesDraggable, store.setState); useDirectStoreUpdater('nodesDraggable', nodesDraggable, store.setState);
useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState); useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState); useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
useDirectStoreUpdater('fitViewOnInit', fitViewOnInit, store.setState);
useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState); useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);
useDirectStoreUpdater('snapGrid', snapGrid, store.setState); useDirectStoreUpdater('snapGrid', snapGrid, store.setState);
useDirectStoreUpdater('onNodesChange', onNodesChange, store.setState); useDirectStoreUpdater('onNodesChange', onNodesChange, store.setState);
useDirectStoreUpdater('onEdgesChange', onEdgesChange, store.setState); useDirectStoreUpdater('onEdgesChange', onEdgesChange, store.setState);
useDirectStoreUpdater('connectOnClick', connectOnClick, store.setState); useDirectStoreUpdater('connectOnClick', connectOnClick, store.setState);
useDirectStoreUpdater('fitViewOnInit', fitView, store.setState);
useDirectStoreUpdater('fitViewOnInitOptions', fitViewOptions, store.setState);
useStoreUpdater<Node[]>(nodes, setNodes); useStoreUpdater<Node[]>(nodes, setNodes);
useStoreUpdater<Edge[]>(edges, setEdges); useStoreUpdater<Edge[]>(edges, setEdges);
+3 -3
View File
@@ -4,7 +4,7 @@ import FlowRenderer from '../FlowRenderer';
import NodeRenderer from '../NodeRenderer'; import NodeRenderer from '../NodeRenderer';
import EdgeRenderer from '../EdgeRenderer'; import EdgeRenderer from '../EdgeRenderer';
import Viewport from '../Viewport'; import Viewport from '../Viewport';
import useOnPaneReadyHandler from '../../hooks/useOnPaneReadyHandler'; import useOnInitHandler from '../../hooks/useOnInitHandler';
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode, ReactFlowProps } from '../../types'; import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode, ReactFlowProps } from '../../types';
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges'> { export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges'> {
@@ -30,7 +30,7 @@ const GraphView = ({
onMove, onMove,
onMoveStart, onMoveStart,
onMoveEnd, onMoveEnd,
onPaneReady, onInit,
onNodeClick, onNodeClick,
onEdgeClick, onEdgeClick,
onNodeDoubleClick, onNodeDoubleClick,
@@ -82,7 +82,7 @@ const GraphView = ({
noWheelClassName, noWheelClassName,
noPanClassName, noPanClassName,
}: GraphViewProps) => { }: GraphViewProps) => {
useOnPaneReadyHandler(onPaneReady); useOnInitHandler(onInit);
return ( return (
<FlowRenderer <FlowRenderer
+3 -6
View File
@@ -37,7 +37,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
const { scale, nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions, snapGrid, snapToGrid } = const { scale, nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions, snapGrid, snapToGrid } =
useStore(selector, shallow); useStore(selector, shallow);
const nodes = useVisibleNodes(props.onlyRenderVisibleElements); const nodes = useVisibleNodes(props.onlyRenderVisibleElements);
const reseizeObserverRef = useRef<ResizeObserver>(); const resizeObserverRef = useRef<ResizeObserver>();
const resizeObserver = useMemo(() => { const resizeObserver = useMemo(() => {
if (typeof ResizeObserver === 'undefined') { if (typeof ResizeObserver === 'undefined') {
@@ -54,14 +54,14 @@ const NodeRenderer = (props: NodeRendererProps) => {
updateNodeDimensions(updates); updateNodeDimensions(updates);
}); });
reseizeObserverRef.current = observer; resizeObserverRef.current = observer;
return observer; return observer;
}, []); }, []);
useEffect(() => { useEffect(() => {
return () => { return () => {
reseizeObserverRef?.current?.disconnect(); resizeObserverRef?.current?.disconnect();
}; };
}, []); }, []);
@@ -78,8 +78,6 @@ const NodeRenderer = (props: NodeRendererProps) => {
const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined')); const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'));
const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined')); const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined'));
const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined')); const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined'));
const isInitialized =
node.width && node.height && typeof node.width !== 'undefined' && typeof node.height !== 'undefined';
return ( return (
<NodeComponent <NodeComponent
@@ -95,7 +93,6 @@ const NodeRenderer = (props: NodeRendererProps) => {
xPos={node.positionAbsolute?.x ?? 0} xPos={node.positionAbsolute?.x ?? 0}
yPos={node.positionAbsolute?.y ?? 0} yPos={node.positionAbsolute?.y ?? 0}
dragging={!!node.dragging} dragging={!!node.dragging}
isInitialized={!!isInitialized}
snapGrid={snapGrid} snapGrid={snapGrid}
snapToGrid={snapToGrid} snapToGrid={snapToGrid}
selectNodesOnDrag={props.selectNodesOnDrag} selectNodesOnDrag={props.selectNodesOnDrag}
+8 -6
View File
@@ -46,7 +46,7 @@ const ReactFlow: FunctionComponent<ReactFlowProps> = forwardRef<ReactFlowRefType
edgeTypes = defaultEdgeTypes, edgeTypes = defaultEdgeTypes,
onNodeClick, onNodeClick,
onEdgeClick, onEdgeClick,
onPaneReady, onInit,
onMove, onMove,
onMoveStart, onMoveStart,
onMoveEnd, onMoveEnd,
@@ -115,10 +115,11 @@ const ReactFlow: FunctionComponent<ReactFlowProps> = forwardRef<ReactFlowRefType
noDragClassName = 'nodrag', noDragClassName = 'nodrag',
noWheelClassName = 'nowheel', noWheelClassName = 'nowheel',
noPanClassName = 'nopan', noPanClassName = 'nopan',
fitViewOnInit = false, fitView = false,
fitViewOptions,
connectOnClick = true, connectOnClick = true,
attributionPosition, attributionPosition,
pro, proOptions,
defaultEdgeOptions, defaultEdgeOptions,
...rest ...rest
}, },
@@ -132,7 +133,7 @@ const ReactFlow: FunctionComponent<ReactFlowProps> = forwardRef<ReactFlowRefType
<div {...rest} ref={ref} className={reactFlowClasses}> <div {...rest} ref={ref} className={reactFlowClasses}>
<Wrapper> <Wrapper>
<GraphView <GraphView
onPaneReady={onPaneReady} onInit={onInit}
onMove={onMove} onMove={onMove}
onMoveStart={onMoveStart} onMoveStart={onMoveStart}
onMoveEnd={onMoveEnd} onMoveEnd={onMoveEnd}
@@ -209,13 +210,14 @@ const ReactFlow: FunctionComponent<ReactFlowProps> = forwardRef<ReactFlowRefType
snapGrid={snapGrid} snapGrid={snapGrid}
connectionMode={connectionMode} connectionMode={connectionMode}
translateExtent={translateExtent} translateExtent={translateExtent}
fitViewOnInit={fitViewOnInit}
connectOnClick={connectOnClick} connectOnClick={connectOnClick}
defaultEdgeOptions={defaultEdgeOptions} defaultEdgeOptions={defaultEdgeOptions}
fitView={fitView}
fitViewOptions={fitViewOptions}
/> />
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />} {onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
{children} {children}
<Attribution pro={pro} position={attributionPosition} /> <Attribution proOptions={proOptions} position={attributionPosition} />
</Wrapper> </Wrapper>
</div> </div>
); );
@@ -1,19 +1,18 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import useReactFlow from './useReactFlow'; import useReactFlow from './useReactFlow';
import { OnPaneReady } from '../types'; import { OnInit } from '../types';
function useOnPaneReadyHandler(onPaneReady: OnPaneReady<any> | undefined) { function useOnInitHandler(onInit: OnInit<any> | undefined) {
const ReactFlowInstance = useReactFlow(); const ReactFlowInstance = useReactFlow();
const isInitialized = useRef<boolean>(false); const isInitialized = useRef<boolean>(false);
useEffect(() => { useEffect(() => {
if (!isInitialized.current && ReactFlowInstance.viewportInitialized && onPaneReady) { if (!isInitialized.current && ReactFlowInstance.viewportInitialized && onInit) {
onPaneReady(ReactFlowInstance); setTimeout(() => onInit(ReactFlowInstance), 1);
isInitialized.current = true; isInitialized.current = true;
} }
}, [onPaneReady, ReactFlowInstance]); }, [onInit, ReactFlowInstance.viewportInitialized]);
} }
export default useOnPaneReadyHandler; export default useOnInitHandler;
+12 -46
View File
@@ -1,11 +1,11 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { zoomIdentity } from 'd3-zoom'; import { zoomIdentity } from 'd3-zoom';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import { Selection as D3Selection } from 'd3';
import { useStoreApi, useStore } from '../store'; import { useStoreApi, useStore } from '../store';
import { getRectOfNodeInternals, pointToRendererPoint, getTransformForBounds } from '../utils/graph'; import { pointToRendererPoint, getTransformForBounds, getD3Transition } from '../utils/graph';
import { FitViewParams, Viewport, ViewportHelperFunctions, ReactFlowState, Rect, XYPosition } from '../types'; import { FitViewOptions, Viewport, ViewportHelperFunctions, ReactFlowState, Rect, XYPosition } from '../types';
import { fitView as fitViewStore } from '../store/utils';
const DEFAULT_PADDING = 0.1; const DEFAULT_PADDING = 0.1;
@@ -16,7 +16,7 @@ const initialViewportHelper: ViewportHelperFunctions = {
getZoom: () => 1, getZoom: () => 1,
setViewport: (_: Viewport) => {}, setViewport: (_: Viewport) => {},
getViewport: () => ({ x: 0, y: 0, zoom: 1 }), getViewport: () => ({ x: 0, y: 0, zoom: 1 }),
fitView: (_: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {}, fitView: (_: FitViewOptions = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {},
setCenter: (_: number, __: number) => {}, setCenter: (_: number, __: number) => {},
fitBounds: (_: Rect) => {}, fitBounds: (_: Rect) => {},
project: (position: XYPosition) => position, project: (position: XYPosition) => position,
@@ -28,10 +28,6 @@ const selector = (s: ReactFlowState) => ({
d3Selection: s.d3Selection, d3Selection: s.d3Selection,
}); });
const getTransition = (selection: D3Selection<Element, unknown, null, undefined>, duration: number = 0) => {
return selection.transition().duration(duration);
};
const useViewportHelper = (): ViewportHelperFunctions => { const useViewportHelper = (): ViewportHelperFunctions => {
const store = useStoreApi(); const store = useStoreApi();
const { d3Zoom, d3Selection } = useStore(selector, shallow); const { d3Zoom, d3Selection } = useStore(selector, shallow);
@@ -39,45 +35,22 @@ const useViewportHelper = (): ViewportHelperFunctions => {
const viewportHelperFunctions = useMemo<ViewportHelperFunctions>(() => { const viewportHelperFunctions = useMemo<ViewportHelperFunctions>(() => {
if (d3Selection && d3Zoom) { if (d3Selection && d3Zoom) {
return { return {
zoomIn: (options) => d3Zoom.scaleBy(getTransition(d3Selection, options?.duration), 1.2), zoomIn: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2),
zoomOut: (options) => d3Zoom.scaleBy(getTransition(d3Selection, options?.duration), 1 / 1.2), zoomOut: (options) => d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2),
zoomTo: (zoomLevel, options) => d3Zoom.scaleTo(getTransition(d3Selection, options?.duration), zoomLevel), zoomTo: (zoomLevel, options) => d3Zoom.scaleTo(getD3Transition(d3Selection, options?.duration), zoomLevel),
getZoom: () => { getZoom: () => {
const [, , zoom] = store.getState().transform; const [, , zoom] = store.getState().transform;
return zoom; return zoom;
}, },
setViewport: (transform, options) => { setViewport: (transform, options) => {
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom); const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
d3Zoom.transform(getTransition(d3Selection, options?.duration), nextTransform); d3Zoom.transform(getD3Transition(d3Selection, options?.duration), nextTransform);
}, },
getViewport: () => { getViewport: () => {
const [x, y, zoom] = store.getState().transform; const [x, y, zoom] = store.getState().transform;
return { x, y, zoom }; return { x, y, zoom };
}, },
fitView: (options) => { fitView: (options) => fitViewStore(store.getState, options),
const { nodeInternals, width, height, minZoom, maxZoom } = store.getState();
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
if (!nodes.length) {
return;
}
const bounds = getRectOfNodeInternals(
options?.includeHiddenNodes ? nodes : nodes.filter((node) => !node.hidden)
);
const [x, y, zoom] = getTransformForBounds(
bounds,
width,
height,
options?.minZoom ?? minZoom,
options?.maxZoom ?? maxZoom,
options?.padding ?? DEFAULT_PADDING
);
const transform = zoomIdentity.translate(x, y).scale(zoom);
d3Zoom.transform(getTransition(d3Selection, options?.duration), transform);
},
setCenter: (x, y, options) => { setCenter: (x, y, options) => {
const { width, height, maxZoom } = store.getState(); const { width, height, maxZoom } = store.getState();
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom; const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
@@ -85,21 +58,14 @@ const useViewportHelper = (): ViewportHelperFunctions => {
const centerY = height / 2 - y * nextZoom; const centerY = height / 2 - y * nextZoom;
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom); const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
d3Zoom.transform(getTransition(d3Selection, options?.duration), transform); d3Zoom.transform(getD3Transition(d3Selection, options?.duration), transform);
}, },
fitBounds: (bounds, options) => { fitBounds: (bounds, options) => {
const { width, height, minZoom, maxZoom } = store.getState(); const { width, height, minZoom, maxZoom } = store.getState();
const [x, y, zoom] = getTransformForBounds( const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, options?.padding ?? 0.1);
bounds,
width,
height,
minZoom,
maxZoom,
options?.padding ?? DEFAULT_PADDING
);
const transform = zoomIdentity.translate(x, y).scale(zoom); const transform = zoomIdentity.translate(x, y).scale(zoom);
d3Zoom.transform(getTransition(d3Selection, options?.duration), transform); d3Zoom.transform(getD3Transition(d3Selection, options?.duration), transform);
}, },
project: (position: XYPosition) => { project: (position: XYPosition) => {
const { transform, snapToGrid, snapGrid } = store.getState(); const { transform, snapToGrid, snapGrid } = store.getState();
+7 -4
View File
@@ -3,6 +3,7 @@ import createContext from 'zustand/context';
import { clampPosition, getDimensions } from '../utils'; import { clampPosition, getDimensions } from '../utils';
import { applyNodeChanges } from '../utils/changes'; import { applyNodeChanges } from '../utils/changes';
import { import {
ReactFlowState, ReactFlowState,
Node, Node,
@@ -20,10 +21,10 @@ import { createSelectionChange, getSelectionChanges } from '../utils/changes';
import { import {
createNodeInternals, createNodeInternals,
createPositionChange, createPositionChange,
fitView,
handleControlledEdgeSelectionChange, handleControlledEdgeSelectionChange,
handleControlledNodeSelectionChange, handleControlledNodeSelectionChange,
isParentSelected, isParentSelected,
fitView,
} from './utils'; } from './utils';
import initialState from './initialState'; import initialState from './initialState';
@@ -54,7 +55,7 @@ const createStore = () =>
set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges }); set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges });
}, },
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => { updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
const { onNodesChange, transform, nodeInternals, fitViewOnInit } = get(); const { onNodesChange, transform, nodeInternals, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions } = get();
const changes: NodeDimensionChange[] = updates.reduce<NodeDimensionChange[]>((res, update) => { const changes: NodeDimensionChange[] = updates.reduce<NodeDimensionChange[]>((res, update) => {
const node = nodeInternals.get(update.id); const node = nodeInternals.get(update.id);
@@ -86,8 +87,9 @@ const createStore = () =>
return res; return res;
}, []); }, []);
const fitViewOnInitDone = fitViewOnInit && fitView(get); const nextFitViewOnInitDone =
set({ nodeInternals: new Map(nodeInternals), fitViewOnInitDone }); fitViewOnInit && !fitViewOnInitDone && fitView(get, { initial: true, ...fitViewOnInitOptions });
set({ nodeInternals: new Map(nodeInternals), fitViewOnInitDone: nextFitViewOnInitDone });
if (changes?.length > 0) { if (changes?.length > 0) {
onNodesChange?.(changes); onNodesChange?.(changes);
@@ -186,6 +188,7 @@ const createStore = () =>
onEdgesChange?.(edgesToUnselect); onEdgesChange?.(edgesToUnselect);
} }
}, },
setMinZoom: (minZoom: number) => { setMinZoom: (minZoom: number) => {
const { d3Zoom, maxZoom } = get(); const { d3Zoom, maxZoom } = get();
d3Zoom?.scaleExtent([minZoom, maxZoom]); d3Zoom?.scaleExtent([minZoom, maxZoom]);
+3 -3
View File
@@ -37,14 +37,14 @@ const initialState: ReactFlowStore = {
nodesDraggable: true, nodesDraggable: true,
nodesConnectable: true, nodesConnectable: true,
elementsSelectable: true, elementsSelectable: true,
fitViewOnInit: false,
fitViewOnInitDone: false,
fitViewOnInitOptions: undefined,
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,
connectionStartHandle: null, connectionStartHandle: null,
connectOnClick: true, connectOnClick: true,
}; };
+35 -14
View File
@@ -2,7 +2,7 @@ import { zoomIdentity } from 'd3-zoom';
import { GetState } from 'zustand'; import { GetState } from 'zustand';
import { clampPosition, isNumeric } from '../utils'; import { clampPosition, isNumeric } from '../utils';
import { getRectOfNodes, getTransformForBounds } from '../utils/graph'; import { getD3Transition, getRectOfNodes, getTransformForBounds } from '../utils/graph';
import { import {
CoordinateExtent, CoordinateExtent,
Edge, Edge,
@@ -14,6 +14,7 @@ import {
ReactFlowState, ReactFlowState,
XYPosition, XYPosition,
XYZPosition, XYZPosition,
FitViewOptions,
} from '../types'; } from '../types';
type ParentNodes = Record<string, boolean>; type ParentNodes = Record<string, boolean>;
@@ -145,26 +146,46 @@ export function createPositionChange({
return change; return change;
} }
export function fitView(get: GetState<ReactFlowState>) { type InternalFitViewOptions = {
initial?: boolean;
} & FitViewOptions;
export function fitView(get: GetState<ReactFlowState>, options: InternalFitViewOptions = {}) {
let { nodeInternals, width, height, minZoom, maxZoom, d3Zoom, d3Selection, fitViewOnInitDone, fitViewOnInit } = get(); let { nodeInternals, width, height, minZoom, maxZoom, d3Zoom, d3Selection, fitViewOnInitDone, fitViewOnInit } = get();
if (fitViewOnInit && !fitViewOnInitDone && d3Zoom && d3Selection) { if ((options.initial && !fitViewOnInitDone && fitViewOnInit) || !options.initial) {
const rootNodes = Array.from(nodeInternals) if (d3Zoom && d3Selection) {
.filter(([_, n]) => !n.parentNode) const nodes = Array.from(nodeInternals.values()).filter((n) =>
.map(([_, n]) => n); options.includeHiddenNodes ? !n.parentNode && n.width && n.height : !n.parentNode && !n.hidden
const nodesInitialized = rootNodes.every((n) => n.width && n.height); );
if (rootNodes.length > 0 && nodesInitialized) { const nodesInitialized = nodes.every((n) => n.width && n.height);
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); if (nodes.length > 0 && nodesInitialized) {
d3Zoom.transform(d3Selection, nextTransform); const bounds = getRectOfNodes(nodes);
fitViewOnInitDone = true; const [x, y, zoom] = getTransformForBounds(
bounds,
width,
height,
options.minZoom ?? minZoom,
options.maxZoom ?? maxZoom,
options.padding ?? 0.1
);
const nextTransform = zoomIdentity.translate(x, y).scale(zoom);
if (typeof options.duration === 'number' && options.duration > 0) {
d3Zoom.transform(getD3Transition(d3Selection, options.duration), nextTransform);
} else {
d3Zoom.transform(d3Selection, nextTransform);
}
return true;
}
} }
} }
return fitViewOnInitDone; return false;
} }
export function handleControlledNodeSelectionChange(nodeChanges: NodeSelectionChange[], nodeInternals: NodeInternals) { export function handleControlledNodeSelectionChange(nodeChanges: NodeSelectionChange[], nodeInternals: NodeInternals) {
+8 -7
View File
@@ -20,15 +20,15 @@ import {
OnEdgeUpdateFunc, OnEdgeUpdateFunc,
NodeChange, NodeChange,
EdgeChange, EdgeChange,
OnPaneReady, OnInit,
ProOptions, ProOptions,
AttributionPosition, AttributionPosition,
DefaultEdgeOptions, DefaultEdgeOptions,
FitViewParams, FitViewOptions,
BackgroundVariant, BackgroundVariant,
} from '.'; } from '.';
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onPaneReady'> { export interface ReactFlowProps extends HTMLAttributes<HTMLDivElement> {
nodes?: Node[]; nodes?: Node[];
edges?: Edge[]; edges?: Edge[];
defaultNodes?: Node[]; defaultNodes?: Node[];
@@ -50,7 +50,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
onConnectStart?: OnConnectStart; onConnectStart?: OnConnectStart;
onConnectStop?: OnConnectStop; onConnectStop?: OnConnectStop;
onConnectEnd?: OnConnectEnd; onConnectEnd?: OnConnectEnd;
onPaneReady?: OnPaneReady; onInit?: OnInit;
onMove?: (viewport?: Viewport) => void; onMove?: (viewport?: Viewport) => void;
onMoveStart?: (viewport?: Viewport) => void; onMoveStart?: (viewport?: Viewport) => void;
onMoveEnd?: (viewport?: Viewport) => void; onMoveEnd?: (viewport?: Viewport) => void;
@@ -106,10 +106,11 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
noDragClassName?: string; noDragClassName?: string;
noWheelClassName?: string; noWheelClassName?: string;
noPanClassName?: string; noPanClassName?: string;
fitViewOnInit?: boolean; fitView?: boolean;
fitViewOptions?: FitViewOptions;
connectOnClick?: boolean; connectOnClick?: boolean;
attributionPosition?: AttributionPosition; attributionPosition?: AttributionPosition;
pro?: ProOptions; proOptions?: ProOptions;
} }
export type ReactFlowRefType = HTMLDivElement; export type ReactFlowRefType = HTMLDivElement;
@@ -129,7 +130,7 @@ export interface ControlProps extends HTMLAttributes<HTMLDivElement> {
showZoom?: boolean; showZoom?: boolean;
showFitView?: boolean; showFitView?: boolean;
showInteractive?: boolean; showInteractive?: boolean;
fitViewParams?: FitViewParams; fitViewOptions?: FitViewOptions;
onZoomIn?: () => void; onZoomIn?: () => void;
onZoomOut?: () => void; onZoomOut?: () => void;
onFitView?: () => void; onFitView?: () => void;
+7 -8
View File
@@ -12,7 +12,7 @@ import { ReactFlowInstance } from './instance';
export type NodeTypesType = { [key: string]: ReactNode }; export type NodeTypesType = { [key: string]: ReactNode };
export type EdgeTypesType = NodeTypesType; export type EdgeTypesType = NodeTypesType;
export type FitView = (fitViewOptions?: FitViewParams) => void; export type FitView = (fitViewOptions?: FitViewOptions) => void;
export type Project = (position: XYPosition) => XYPosition; export type Project = (position: XYPosition) => XYPosition;
@@ -28,9 +28,7 @@ export type SetViewport = (viewport: Viewport, options?: ViewportHelperFunctionO
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void; export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void;
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void; export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void;
export type OnPaneReady<NodeData = any, EdgeData = any> = ( export type OnInit<NodeData = any, EdgeData = any> = (reactFlowInstance: ReactFlowInstance<NodeData, EdgeData>) => void;
reactFlowInstance: ReactFlowInstance<NodeData, EdgeData>
) => void;
export interface Connection { export interface Connection {
source: string | null; source: string | null;
@@ -46,7 +44,7 @@ export enum ConnectionMode {
export type OnConnect = (connection: Connection) => void; export type OnConnect = (connection: Connection) => void;
export type FitViewParams = { export type FitViewOptions = {
padding?: number; padding?: number;
includeHiddenNodes?: boolean; includeHiddenNodes?: boolean;
minZoom?: number; minZoom?: number;
@@ -153,9 +151,6 @@ export type ReactFlowStore = {
reactFlowVersion: string; reactFlowVersion: string;
fitViewOnInit: boolean;
fitViewOnInitDone: boolean;
connectionStartHandle: StartHandle | null; connectionStartHandle: StartHandle | null;
onConnect?: OnConnect; onConnect?: OnConnect;
@@ -165,6 +160,10 @@ export type ReactFlowStore = {
connectOnClick: boolean; connectOnClick: boolean;
defaultEdgeOptions?: DefaultEdgeOptions; defaultEdgeOptions?: DefaultEdgeOptions;
fitViewOnInit: boolean;
fitViewOnInitDone: boolean;
fitViewOnInitOptions: FitViewOptions | undefined;
}; };
export type ReactFlowActions = { export type ReactFlowActions = {
-1
View File
@@ -81,7 +81,6 @@ export interface WrapNodeProps<T = any> {
sourcePosition?: Position; sourcePosition?: Position;
targetPosition?: Position; targetPosition?: Position;
hidden?: boolean; hidden?: boolean;
isInitialized?: boolean;
snapToGrid?: boolean; snapToGrid?: boolean;
snapGrid?: SnapGrid; snapGrid?: SnapGrid;
dragging: boolean; dragging: boolean;
+15 -13
View File
@@ -1,3 +1,4 @@
import { Selection as D3Selection } from 'd3';
import { boxToRect, clamp, getBoundsOfBoxes, rectToBox } from '../utils'; import { boxToRect, clamp, getBoundsOfBoxes, rectToBox } from '../utils';
import { Node, Edge, Connection, EdgeMarkerType, Transform, XYPosition, Rect, NodeInternals } from '../types'; import { Node, Edge, Connection, EdgeMarkerType, Transform, XYPosition, Rect, NodeInternals } from '../types';
@@ -124,21 +125,18 @@ export const pointToRendererPoint = (
return position; return position;
}; };
// @TODO: use one function for getRectOfNodes and getRectOfNodeInternals
export const getRectOfNodes = (nodes: Node[]): Rect => { export const getRectOfNodes = (nodes: Node[]): Rect => {
const box = nodes.reduce( const box = nodes.reduce(
(currBox, { position, width, height }) => (currBox, { positionAbsolute, position, width, height }) =>
getBoundsOfBoxes(currBox, rectToBox({ ...position, width: width || 0, height: height || 0 })), getBoundsOfBoxes(
{ x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } currBox,
); rectToBox({
x: positionAbsolute ? positionAbsolute.x : position.x,
return boxToRect(box); y: positionAbsolute ? positionAbsolute.y : position.y,
}; width: width || 0,
height: height || 0,
export const getRectOfNodeInternals = (nodes: Node[]): Rect => { })
const box = nodes.reduce( ),
(currBox, { positionAbsolute, width, height }) =>
getBoundsOfBoxes(currBox, rectToBox({ ...positionAbsolute!, width: width || 0, height: height || 0 })),
{ x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }
); );
@@ -213,3 +211,7 @@ export const getTransformForBounds = (
return [x, y, clampedZoom]; return [x, y, clampedZoom];
}; };
export const getD3Transition = (selection: D3Selection<Element, unknown, null, undefined>, duration: number = 0) => {
return selection.transition().duration(duration);
};