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