Merge pull request #5219 from xyflow/feat/provider-init-zoom
feat(provider): add minZoom, maxZoom and fitViewOptions
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@xyflow/react': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add initialMinZoom, initialMaxZoom and initialFitViewOptions to ReactFlowProvider
|
||||||
@@ -3,18 +3,49 @@ import { useState, type ReactNode } from 'react';
|
|||||||
import { Provider } from '../../contexts/StoreContext';
|
import { Provider } from '../../contexts/StoreContext';
|
||||||
import { createStore } from '../../store';
|
import { createStore } from '../../store';
|
||||||
import { BatchProvider } from '../BatchProvider';
|
import { BatchProvider } from '../BatchProvider';
|
||||||
import type { Node, Edge } from '../../types';
|
import type { Node, Edge, FitViewOptions } from '../../types';
|
||||||
import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
|
import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
|
||||||
|
|
||||||
export type ReactFlowProviderProps = {
|
export type ReactFlowProviderProps = {
|
||||||
|
/** These nodes are used to initialize the flow. They are not dynamic. */
|
||||||
initialNodes?: Node[];
|
initialNodes?: Node[];
|
||||||
|
/** These edges are used to initialize the flow. They are not dynamic. */
|
||||||
initialEdges?: Edge[];
|
initialEdges?: Edge[];
|
||||||
|
/** These nodes are used to initialize the flow. They are not dynamic. */
|
||||||
defaultNodes?: Node[];
|
defaultNodes?: Node[];
|
||||||
|
/** These edges are used to initialize the flow. They are not dynamic. */
|
||||||
defaultEdges?: Edge[];
|
defaultEdges?: Edge[];
|
||||||
|
/** The initial width is necessary to be able to use fitView on the server */
|
||||||
initialWidth?: number;
|
initialWidth?: number;
|
||||||
|
/** The initial height is necessary to be able to use fitView on the server */
|
||||||
initialHeight?: number;
|
initialHeight?: number;
|
||||||
|
/** When `true`, the flow will be zoomed and panned to fit all the nodes initially provided. */
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
|
/**
|
||||||
|
* You can provide an object of options to customize the initial fitView behavior.
|
||||||
|
*/
|
||||||
|
initialFitViewOptions?: FitViewOptions;
|
||||||
|
/** Initial minimum zoom level */
|
||||||
|
initialMinZoom?: number;
|
||||||
|
/** Initial maximum zoom level */
|
||||||
|
initialMaxZoom?: number;
|
||||||
|
/**
|
||||||
|
* The origin of the node to use when placing it in the flow or looking up its `x` and `y`
|
||||||
|
* position. An origin of `[0, 0]` means that a node's top left corner will be placed at the `x`
|
||||||
|
* and `y` position.
|
||||||
|
* @default [0, 0]
|
||||||
|
* @example
|
||||||
|
* [0, 0] // default, top left
|
||||||
|
* [0.5, 0.5] // center
|
||||||
|
* [1, 1] // bottom right
|
||||||
|
*/
|
||||||
nodeOrigin?: NodeOrigin;
|
nodeOrigin?: NodeOrigin;
|
||||||
|
/**
|
||||||
|
* By default, nodes can be placed on an infinite flow. You can use this prop to set a boundary.
|
||||||
|
*
|
||||||
|
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
||||||
|
* @example [[-1000, -10000], [1000, 1000]]
|
||||||
|
*/
|
||||||
nodeExtent?: CoordinateExtent;
|
nodeExtent?: CoordinateExtent;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
@@ -60,6 +91,9 @@ export function ReactFlowProvider({
|
|||||||
defaultEdges,
|
defaultEdges,
|
||||||
initialWidth: width,
|
initialWidth: width,
|
||||||
initialHeight: height,
|
initialHeight: height,
|
||||||
|
initialMinZoom: minZoom,
|
||||||
|
initialMaxZoom: maxZoom,
|
||||||
|
initialFitViewOptions: fitViewOptions,
|
||||||
fitView,
|
fitView,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
@@ -74,6 +108,9 @@ export function ReactFlowProvider({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fitView,
|
fitView,
|
||||||
|
minZoom,
|
||||||
|
maxZoom,
|
||||||
|
fitViewOptions,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useContext, type ReactNode } from 'react';
|
|||||||
|
|
||||||
import StoreContext from '../../contexts/StoreContext';
|
import StoreContext from '../../contexts/StoreContext';
|
||||||
import { ReactFlowProvider } from '../../components/ReactFlowProvider';
|
import { ReactFlowProvider } from '../../components/ReactFlowProvider';
|
||||||
import type { Node, Edge } from '../../types';
|
import type { Node, Edge, FitViewOptions } from '../../types';
|
||||||
import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
|
import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
|
||||||
|
|
||||||
export function Wrapper({
|
export function Wrapper({
|
||||||
@@ -14,6 +14,9 @@ export function Wrapper({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fitView,
|
fitView,
|
||||||
|
fitViewOptions,
|
||||||
|
minZoom,
|
||||||
|
maxZoom,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
}: {
|
}: {
|
||||||
@@ -25,6 +28,9 @@ export function Wrapper({
|
|||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
|
fitViewOptions?: FitViewOptions;
|
||||||
|
minZoom?: number;
|
||||||
|
maxZoom?: number;
|
||||||
nodeOrigin?: NodeOrigin;
|
nodeOrigin?: NodeOrigin;
|
||||||
nodeExtent?: CoordinateExtent;
|
nodeExtent?: CoordinateExtent;
|
||||||
}) {
|
}) {
|
||||||
@@ -47,6 +53,9 @@ export function Wrapper({
|
|||||||
initialWidth={width}
|
initialWidth={width}
|
||||||
initialHeight={height}
|
initialHeight={height}
|
||||||
fitView={fitView}
|
fitView={fitView}
|
||||||
|
initialFitViewOptions={fitViewOptions}
|
||||||
|
initialMinZoom={minZoom}
|
||||||
|
initialMaxZoom={maxZoom}
|
||||||
nodeOrigin={nodeOrigin}
|
nodeOrigin={nodeOrigin}
|
||||||
nodeExtent={nodeExtent}
|
nodeExtent={nodeExtent}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -177,6 +177,9 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
|||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
fitView={fitView}
|
fitView={fitView}
|
||||||
|
fitViewOptions={fitViewOptions}
|
||||||
|
minZoom={minZoom}
|
||||||
|
maxZoom={maxZoom}
|
||||||
nodeOrigin={nodeOrigin}
|
nodeOrigin={nodeOrigin}
|
||||||
nodeExtent={nodeExtent}
|
nodeExtent={nodeExtent}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
|
|
||||||
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||||
import getInitialState from './initialState';
|
import getInitialState from './initialState';
|
||||||
import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams } from '../types';
|
import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams, FitViewOptions } from '../types';
|
||||||
|
|
||||||
const createStore = ({
|
const createStore = ({
|
||||||
nodes,
|
nodes,
|
||||||
@@ -28,6 +28,9 @@ const createStore = ({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fitView,
|
fitView,
|
||||||
|
fitViewOptions,
|
||||||
|
minZoom,
|
||||||
|
maxZoom,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
}: {
|
}: {
|
||||||
@@ -38,6 +41,9 @@ const createStore = ({
|
|||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
|
fitViewOptions?: FitViewOptions;
|
||||||
|
minZoom?: number;
|
||||||
|
maxZoom?: number;
|
||||||
nodeOrigin?: NodeOrigin;
|
nodeOrigin?: NodeOrigin;
|
||||||
nodeExtent?: CoordinateExtent;
|
nodeExtent?: CoordinateExtent;
|
||||||
}) =>
|
}) =>
|
||||||
@@ -70,7 +76,20 @@ const createStore = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
...getInitialState({
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
fitView,
|
||||||
|
fitViewOptions,
|
||||||
|
minZoom,
|
||||||
|
maxZoom,
|
||||||
|
nodeOrigin,
|
||||||
|
nodeExtent,
|
||||||
|
defaultNodes,
|
||||||
|
defaultEdges,
|
||||||
|
}),
|
||||||
setNodes: (nodes: Node[]) => {
|
setNodes: (nodes: Node[]) => {
|
||||||
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued } = get();
|
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued } = get();
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { Edge, InternalNode, Node, ReactFlowStore } from '../types';
|
import type { Edge, FitViewOptions, InternalNode, Node, ReactFlowStore } from '../types';
|
||||||
|
|
||||||
const getInitialState = ({
|
const getInitialState = ({
|
||||||
nodes,
|
nodes,
|
||||||
@@ -22,6 +22,9 @@ const getInitialState = ({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fitView,
|
fitView,
|
||||||
|
fitViewOptions,
|
||||||
|
minZoom = 0.5,
|
||||||
|
maxZoom = 2,
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
}: {
|
}: {
|
||||||
@@ -32,6 +35,9 @@ const getInitialState = ({
|
|||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
|
fitViewOptions?: FitViewOptions;
|
||||||
|
minZoom?: number;
|
||||||
|
maxZoom?: number;
|
||||||
nodeOrigin?: NodeOrigin;
|
nodeOrigin?: NodeOrigin;
|
||||||
nodeExtent?: CoordinateExtent;
|
nodeExtent?: CoordinateExtent;
|
||||||
} = {}): ReactFlowStore => {
|
} = {}): ReactFlowStore => {
|
||||||
@@ -59,7 +65,14 @@ const getInitialState = ({
|
|||||||
filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)),
|
filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
|
const { x, y, zoom } = getViewportForBounds(
|
||||||
|
bounds,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
minZoom,
|
||||||
|
maxZoom,
|
||||||
|
fitViewOptions?.padding ?? 0.1
|
||||||
|
);
|
||||||
transform = [x, y, zoom];
|
transform = [x, y, zoom];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +93,8 @@ const getInitialState = ({
|
|||||||
hasDefaultNodes: defaultNodes !== undefined,
|
hasDefaultNodes: defaultNodes !== undefined,
|
||||||
hasDefaultEdges: defaultEdges !== undefined,
|
hasDefaultEdges: defaultEdges !== undefined,
|
||||||
panZoom: null,
|
panZoom: null,
|
||||||
minZoom: 0.5,
|
minZoom,
|
||||||
maxZoom: 2,
|
maxZoom,
|
||||||
translateExtent: infiniteExtent,
|
translateExtent: infiniteExtent,
|
||||||
nodeExtent: storeNodeExtent,
|
nodeExtent: storeNodeExtent,
|
||||||
nodesSelectionActive: false,
|
nodesSelectionActive: false,
|
||||||
@@ -110,7 +123,7 @@ const getInitialState = ({
|
|||||||
multiSelectionActive: false,
|
multiSelectionActive: false,
|
||||||
|
|
||||||
fitViewQueued: fitView ?? false,
|
fitViewQueued: fitView ?? false,
|
||||||
fitViewOptions: undefined,
|
fitViewOptions,
|
||||||
fitViewResolver: null,
|
fitViewResolver: null,
|
||||||
|
|
||||||
connection: { ...initialConnection },
|
connection: { ...initialConnection },
|
||||||
|
|||||||
Reference in New Issue
Block a user