Refactor fitView

This commit is contained in:
peterkogo
2025-03-05 13:45:28 +01:00
parent 8017792542
commit ee1ef205eb
11 changed files with 99 additions and 151 deletions
@@ -154,8 +154,8 @@ export function StoreUpdater<NodeType extends Node = Node, EdgeType extends Edge
else if (fieldName === 'nodeExtent') setNodeExtent(fieldValue as CoordinateExtent);
else if (fieldName === 'paneClickDistance') setPaneClickDistance(fieldValue as number);
// Renamed fields
else if (fieldName === 'fitView') store.setState({ fitViewOnInit: fieldValue as boolean });
else if (fieldName === 'fitViewOptions') store.setState({ fitViewOnInitOptions: fieldValue as FitViewOptions });
else if (fieldName === 'fitView') store.setState({ fitViewQueued: fieldValue as boolean });
else if (fieldName === 'fitViewOptions') store.setState({ fitViewOptions: fieldValue as FitViewOptions });
// General case
else store.setState({ [fieldName]: fieldValue });
}
+13 -1
View File
@@ -15,7 +15,15 @@ import useViewportHelper from './useViewportHelper';
import { useStore, useStoreApi } from './useStore';
import { useBatchContext } from '../components/BatchProvider';
import { elementToRemoveChange, isEdge, isNode } from '../utils';
import type { ReactFlowInstance, Node, Edge, InternalNode, ReactFlowState, GeneralHelpers } from '../types';
import type {
ReactFlowInstance,
Node,
Edge,
InternalNode,
ReactFlowState,
GeneralHelpers,
FitViewOptions,
} from '../types';
const selector = (s: ReactFlowState) => !!s.panZoom;
@@ -271,6 +279,10 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
.connectionLookup.get(`${nodeId}${type ? (handleId ? `-${type}-${handleId}` : `-${type}`) : ''}`)
?.values() ?? []
),
fitView: (options: FitViewOptions<NodeType> | undefined) => {
store.setState({ fitViewQueued: true, fitViewOptions: options });
batchContext.nodeQueue.push((nodes) => [...nodes]);
},
};
}, []);
@@ -2,11 +2,8 @@ import { useMemo } from 'react';
import {
pointToRendererPoint,
getViewportForBounds,
getFitViewNodes,
fitView,
type XYPosition,
rendererPointToPoint,
getDimensions,
SnapGrid,
} from '@xyflow/system';
@@ -65,28 +62,6 @@ const useViewportHelper = (): ViewportHelperFunctions => {
const [x, y, zoom] = store.getState().transform;
return { x, y, zoom };
},
fitView: (options) => {
const { nodeLookup, minZoom, maxZoom, panZoom, domNode } = store.getState();
if (!panZoom || !domNode) {
return Promise.resolve(false);
}
const fitViewNodes = getFitViewNodes(nodeLookup, options);
const { width, height } = getDimensions(domNode);
return fitView(
{
nodes: fitViewNodes,
width,
height,
minZoom,
maxZoom,
panZoom,
},
options
);
},
setCenter: async (x, y, options) => {
const { width, height, maxZoom, panZoom } = store.getState();
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
+30 -91
View File
@@ -1,7 +1,5 @@
import { createWithEqualityFn } from 'zustand/traditional';
import {
getFitViewNodes,
fitView as fitViewSystem,
adoptUserNodes,
updateAbsolutePositions,
panBy as panBySystem,
@@ -15,11 +13,12 @@ import {
initialConnection,
NodeOrigin,
CoordinateExtent,
fitViewport,
} from '@xyflow/system';
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
import getInitialState from './initialState';
import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams, FitViewOptions } from '../types';
import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams } from '../types';
const createStore = ({
nodes,
@@ -46,7 +45,7 @@ const createStore = ({
(set, get) => ({
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
setNodes: (nodes: Node[]) => {
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued, panZoom } = get();
/*
* setNodes() is called exclusively in response to user actions:
* - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
@@ -55,14 +54,35 @@ const createStore = ({
* When this happens, we take the note objects passed by the user and extend them with fields
* relevant for internal React Flow operations.
*/
adoptUserNodes(nodes, nodeLookup, parentLookup, {
const nodesInitialized = adoptUserNodes(nodes, nodeLookup, parentLookup, {
nodeOrigin,
nodeExtent,
elevateNodesOnSelect,
checkEquality: true,
});
set({ nodes });
let viewportFitted = false;
if (fitViewQueued && nodesInitialized && panZoom) {
const { fitViewOptions, width, height, minZoom, maxZoom } = get();
viewportFitted = fitViewport(
{
nodes: nodeLookup,
width,
height,
panZoom,
minZoom,
maxZoom,
},
fitViewOptions
);
}
if (viewportFitted) {
set({ nodes, fitViewQueued: false, fitViewOptions: undefined });
} else {
set({ nodes });
}
},
setEdges: (edges: Edge[]) => {
const { connectionLookup, edgeLookup } = get();
@@ -88,20 +108,8 @@ const createStore = ({
* changes its dimensions, this function is called to measure the
* new dimensions and update the nodes.
*/
updateNodeInternals: (updates, params = { triggerFitView: true }) => {
const {
triggerNodeChanges,
nodeLookup,
parentLookup,
fitViewOnInit,
fitViewDone,
fitViewOnInitOptions,
domNode,
nodeOrigin,
nodeExtent,
debug,
fitViewSync,
} = get();
updateNodeInternals: (updates) => {
const { triggerNodeChanges, nodeLookup, parentLookup, domNode, nodeOrigin, nodeExtent, debug } = get();
const { changes, updatedInternals } = updateNodeInternalsSystem(
updates,
@@ -118,29 +126,8 @@ const createStore = ({
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin, nodeExtent });
if (params.triggerFitView) {
// we call fitView once initially after all dimensions are set
let nextFitViewDone = fitViewDone;
if (!fitViewDone && fitViewOnInit) {
nextFitViewDone = fitViewSync({
...fitViewOnInitOptions,
nodes: fitViewOnInitOptions?.nodes,
});
}
/*
* here we are cirmumventing the onNodesChange handler
* in order to be able to display nodes even if the user
* has not provided an onNodesChange handler.
* Nodes are only rendered if they have a width and height
* attribute which they get from this handler.
*/
set({ fitViewDone: nextFitViewDone });
} else {
// we always want to trigger useStore calls whenever updateNodeInternals is called
set({});
}
// we always want to trigger useStore calls whenever updateNodeInternals is called
set({});
if (changes?.length > 0) {
if (debug) {
@@ -332,54 +319,6 @@ const createStore = ({
return panBySystem({ delta, panZoom, transform, translateExtent, width, height });
},
fitView: (options?: FitViewOptions): Promise<boolean> => {
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
if (!panZoom) {
return Promise.resolve(false);
}
const fitViewNodes = getFitViewNodes(nodeLookup, options);
return fitViewSystem(
{
nodes: fitViewNodes,
width,
height,
panZoom,
minZoom,
maxZoom,
},
options
);
},
/*
* we can't call an asnychronous function in updateNodeInternals
* for that we created this sync version of fitView
*/
fitViewSync: (options?: FitViewOptions): boolean => {
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
if (!panZoom) {
return false;
}
const fitViewNodes = getFitViewNodes(nodeLookup, options);
fitViewSystem(
{
nodes: fitViewNodes,
width,
height,
panZoom,
minZoom,
maxZoom,
},
options
);
return fitViewNodes.size > 0;
},
cancelConnection: () => {
set({
connection: { ...initialConnection },
+3 -3
View File
@@ -104,13 +104,13 @@ const getInitialState = ({
elementsSelectable: true,
elevateNodesOnSelect: true,
elevateEdgesOnSelect: false,
fitViewOnInit: false,
fitViewDone: false,
fitViewOnInitOptions: undefined,
selectNodesOnDrag: true,
multiSelectionActive: false,
fitViewQueued: false,
fitViewOptions: undefined,
connection: { ...initialConnection },
connectionClickStartHandle: null,
connectOnClick: true,
+1 -12
View File
@@ -109,7 +109,7 @@ export type FitViewParams<NodeType extends Node = Node> = FitViewParamsBase<Node
* @public
*/
export type FitViewOptions<NodeType extends Node = Node> = FitViewOptionsBase<NodeType>;
export type FitView = (fitViewOptions?: FitViewOptions) => Promise<boolean>;
export type FitView<NodeType extends Node = Node> = (fitViewOptions?: FitViewOptions<NodeType>) => void;
export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
) => void;
@@ -156,17 +156,6 @@ export type ViewportHelperFunctions = {
* @returns Viewport
*/
getViewport: GetViewport;
/**
* Fits the view.
*
* @param options.padding - optional padding
* @param options.includeHiddenNodes - optional includeHiddenNodes
* @param options.minZoom - optional minZoom
* @param options.maxZoom - optional maxZoom
* @param options.duration - optional duration. If set, a transition will be applied
* @param options.nodes - optional nodes to fit the view to
*/
fitView: FitView;
/**
* Sets the center of the view to the given position.
*
+12 -1
View File
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-namespace */
import type { HandleConnection, HandleType, NodeConnection, Rect, Viewport } from '@xyflow/system';
import type { Node, Edge, ViewportHelperFunctions, InternalNode } from '.';
import type { Node, Edge, ViewportHelperFunctions, InternalNode, FitView } from '.';
export type ReactFlowJsonObject<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
nodes: NodeType[];
@@ -217,6 +217,17 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
nodeId: string;
handleId?: string | null;
}) => NodeConnection[];
// /**
// * Fits the view.
// *
// * @param options.padding - optional padding
// * @param options.includeHiddenNodes - optional includeHiddenNodes
// * @param options.minZoom - optional minZoom
// * @param options.maxZoom - optional maxZoom
// * @param options.duration - optional duration. If set, a transition will be applied
// * @param options.nodes - optional nodes to fit the view to
// */
fitView: FitView<NodeType>;
};
/**
* The `ReactFlowInstance` provides a collection of methods to query and manipulate
+2 -5
View File
@@ -119,9 +119,8 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
connectOnClick: boolean;
defaultEdgeOptions?: DefaultEdgeOptions;
fitViewOnInit: boolean;
fitViewDone: boolean;
fitViewOnInitOptions: FitViewOptions | undefined;
fitViewQueued: boolean;
fitViewOptions: FitViewOptions | undefined;
onNodesDelete?: OnNodesDelete<NodeType>;
onEdgesDelete?: OnEdgesDelete<EdgeType>;
@@ -168,8 +167,6 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
panBy: PanBy;
fitView: (options?: FitViewOptions) => Promise<boolean>;
fitViewSync: (options?: FitViewOptions) => boolean;
setPaneClickDistance: (distance: number) => void;
};