Refactor fitView
This commit is contained in:
@@ -56,8 +56,18 @@ const initialEdges: Edge[] = [
|
|||||||
const defaultEdgeOptions = {};
|
const defaultEdgeOptions = {};
|
||||||
|
|
||||||
const BasicFlow = () => {
|
const BasicFlow = () => {
|
||||||
const { addNodes, setNodes, getNodes, setEdges, getEdges, deleteElements, updateNodeData, toObject, setViewport } =
|
const {
|
||||||
useReactFlow();
|
addNodes,
|
||||||
|
setNodes,
|
||||||
|
getNodes,
|
||||||
|
setEdges,
|
||||||
|
getEdges,
|
||||||
|
deleteElements,
|
||||||
|
updateNodeData,
|
||||||
|
toObject,
|
||||||
|
setViewport,
|
||||||
|
fitView,
|
||||||
|
} = useReactFlow();
|
||||||
|
|
||||||
const updatePos = () => {
|
const updatePos = () => {
|
||||||
setNodes((nodes) =>
|
setNodes((nodes) =>
|
||||||
@@ -104,6 +114,7 @@ const BasicFlow = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
setEdges([{ id: 'a-b', source: 'a', target: 'b' }]);
|
setEdges([{ id: 'a-b', source: 'a', target: 'b' }]);
|
||||||
|
fitView();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateNode = () => {
|
const onUpdateNode = () => {
|
||||||
@@ -117,6 +128,7 @@ const BasicFlow = () => {
|
|||||||
position: { x: Math.random() * 300, y: Math.random() * 300 },
|
position: { x: Math.random() * 300, y: Math.random() * 300 },
|
||||||
className: 'light',
|
className: 'light',
|
||||||
});
|
});
|
||||||
|
fitView();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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 === 'nodeExtent') setNodeExtent(fieldValue as CoordinateExtent);
|
||||||
else if (fieldName === 'paneClickDistance') setPaneClickDistance(fieldValue as number);
|
else if (fieldName === 'paneClickDistance') setPaneClickDistance(fieldValue as number);
|
||||||
// Renamed fields
|
// Renamed fields
|
||||||
else if (fieldName === 'fitView') store.setState({ fitViewOnInit: fieldValue as boolean });
|
else if (fieldName === 'fitView') store.setState({ fitViewQueued: fieldValue as boolean });
|
||||||
else if (fieldName === 'fitViewOptions') store.setState({ fitViewOnInitOptions: fieldValue as FitViewOptions });
|
else if (fieldName === 'fitViewOptions') store.setState({ fitViewOptions: fieldValue as FitViewOptions });
|
||||||
// General case
|
// General case
|
||||||
else store.setState({ [fieldName]: fieldValue });
|
else store.setState({ [fieldName]: fieldValue });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,15 @@ import useViewportHelper from './useViewportHelper';
|
|||||||
import { useStore, useStoreApi } from './useStore';
|
import { useStore, useStoreApi } from './useStore';
|
||||||
import { useBatchContext } from '../components/BatchProvider';
|
import { useBatchContext } from '../components/BatchProvider';
|
||||||
import { elementToRemoveChange, isEdge, isNode } from '../utils';
|
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;
|
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}`) : ''}`)
|
.connectionLookup.get(`${nodeId}${type ? (handleId ? `-${type}-${handleId}` : `-${type}`) : ''}`)
|
||||||
?.values() ?? []
|
?.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 {
|
import {
|
||||||
pointToRendererPoint,
|
pointToRendererPoint,
|
||||||
getViewportForBounds,
|
getViewportForBounds,
|
||||||
getFitViewNodes,
|
|
||||||
fitView,
|
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
rendererPointToPoint,
|
rendererPointToPoint,
|
||||||
getDimensions,
|
|
||||||
SnapGrid,
|
SnapGrid,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
@@ -65,28 +62,6 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
|||||||
const [x, y, zoom] = store.getState().transform;
|
const [x, y, zoom] = store.getState().transform;
|
||||||
return { x, y, zoom };
|
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) => {
|
setCenter: async (x, y, options) => {
|
||||||
const { width, height, maxZoom, panZoom } = store.getState();
|
const { width, height, maxZoom, panZoom } = store.getState();
|
||||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
|
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { createWithEqualityFn } from 'zustand/traditional';
|
import { createWithEqualityFn } from 'zustand/traditional';
|
||||||
import {
|
import {
|
||||||
getFitViewNodes,
|
|
||||||
fitView as fitViewSystem,
|
|
||||||
adoptUserNodes,
|
adoptUserNodes,
|
||||||
updateAbsolutePositions,
|
updateAbsolutePositions,
|
||||||
panBy as panBySystem,
|
panBy as panBySystem,
|
||||||
@@ -15,11 +13,12 @@ import {
|
|||||||
initialConnection,
|
initialConnection,
|
||||||
NodeOrigin,
|
NodeOrigin,
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
|
fitViewport,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
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, FitViewOptions } from '../types';
|
import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams } from '../types';
|
||||||
|
|
||||||
const createStore = ({
|
const createStore = ({
|
||||||
nodes,
|
nodes,
|
||||||
@@ -46,7 +45,7 @@ const createStore = ({
|
|||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
||||||
setNodes: (nodes: Node[]) => {
|
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:
|
* setNodes() is called exclusively in response to user actions:
|
||||||
* - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
* - 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
|
* When this happens, we take the note objects passed by the user and extend them with fields
|
||||||
* relevant for internal React Flow operations.
|
* relevant for internal React Flow operations.
|
||||||
*/
|
*/
|
||||||
adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
|
||||||
|
const nodesInitialized = adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
elevateNodesOnSelect,
|
elevateNodesOnSelect,
|
||||||
checkEquality: true,
|
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[]) => {
|
setEdges: (edges: Edge[]) => {
|
||||||
const { connectionLookup, edgeLookup } = get();
|
const { connectionLookup, edgeLookup } = get();
|
||||||
@@ -88,20 +108,8 @@ const createStore = ({
|
|||||||
* changes its dimensions, this function is called to measure the
|
* changes its dimensions, this function is called to measure the
|
||||||
* new dimensions and update the nodes.
|
* new dimensions and update the nodes.
|
||||||
*/
|
*/
|
||||||
updateNodeInternals: (updates, params = { triggerFitView: true }) => {
|
updateNodeInternals: (updates) => {
|
||||||
const {
|
const { triggerNodeChanges, nodeLookup, parentLookup, domNode, nodeOrigin, nodeExtent, debug } = get();
|
||||||
triggerNodeChanges,
|
|
||||||
nodeLookup,
|
|
||||||
parentLookup,
|
|
||||||
fitViewOnInit,
|
|
||||||
fitViewDone,
|
|
||||||
fitViewOnInitOptions,
|
|
||||||
domNode,
|
|
||||||
nodeOrigin,
|
|
||||||
nodeExtent,
|
|
||||||
debug,
|
|
||||||
fitViewSync,
|
|
||||||
} = get();
|
|
||||||
|
|
||||||
const { changes, updatedInternals } = updateNodeInternalsSystem(
|
const { changes, updatedInternals } = updateNodeInternalsSystem(
|
||||||
updates,
|
updates,
|
||||||
@@ -118,29 +126,8 @@ const createStore = ({
|
|||||||
|
|
||||||
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin, nodeExtent });
|
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin, nodeExtent });
|
||||||
|
|
||||||
if (params.triggerFitView) {
|
// we always want to trigger useStore calls whenever updateNodeInternals is called
|
||||||
// we call fitView once initially after all dimensions are set
|
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({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changes?.length > 0) {
|
if (changes?.length > 0) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
@@ -332,54 +319,6 @@ const createStore = ({
|
|||||||
|
|
||||||
return panBySystem({ delta, panZoom, transform, translateExtent, width, height });
|
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: () => {
|
cancelConnection: () => {
|
||||||
set({
|
set({
|
||||||
connection: { ...initialConnection },
|
connection: { ...initialConnection },
|
||||||
|
|||||||
@@ -104,13 +104,13 @@ const getInitialState = ({
|
|||||||
elementsSelectable: true,
|
elementsSelectable: true,
|
||||||
elevateNodesOnSelect: true,
|
elevateNodesOnSelect: true,
|
||||||
elevateEdgesOnSelect: false,
|
elevateEdgesOnSelect: false,
|
||||||
fitViewOnInit: false,
|
|
||||||
fitViewDone: false,
|
|
||||||
fitViewOnInitOptions: undefined,
|
|
||||||
selectNodesOnDrag: true,
|
selectNodesOnDrag: true,
|
||||||
|
|
||||||
multiSelectionActive: false,
|
multiSelectionActive: false,
|
||||||
|
|
||||||
|
fitViewQueued: false,
|
||||||
|
fitViewOptions: undefined,
|
||||||
|
|
||||||
connection: { ...initialConnection },
|
connection: { ...initialConnection },
|
||||||
connectionClickStartHandle: null,
|
connectionClickStartHandle: null,
|
||||||
connectOnClick: true,
|
connectOnClick: true,
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export type FitViewParams<NodeType extends Node = Node> = FitViewParamsBase<Node
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type FitViewOptions<NodeType extends Node = Node> = FitViewOptionsBase<NodeType>;
|
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> = (
|
export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
|
||||||
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
|
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
|
||||||
) => void;
|
) => void;
|
||||||
@@ -156,17 +156,6 @@ export type ViewportHelperFunctions = {
|
|||||||
* @returns Viewport
|
* @returns Viewport
|
||||||
*/
|
*/
|
||||||
getViewport: GetViewport;
|
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.
|
* Sets the center of the view to the given position.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
import type { HandleConnection, HandleType, NodeConnection, Rect, Viewport } from '@xyflow/system';
|
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> = {
|
export type ReactFlowJsonObject<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||||
nodes: NodeType[];
|
nodes: NodeType[];
|
||||||
@@ -217,6 +217,17 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
nodeId: string;
|
nodeId: string;
|
||||||
handleId?: string | null;
|
handleId?: string | null;
|
||||||
}) => NodeConnection[];
|
}) => 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
|
* The `ReactFlowInstance` provides a collection of methods to query and manipulate
|
||||||
|
|||||||
@@ -119,9 +119,8 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
connectOnClick: boolean;
|
connectOnClick: boolean;
|
||||||
defaultEdgeOptions?: DefaultEdgeOptions;
|
defaultEdgeOptions?: DefaultEdgeOptions;
|
||||||
|
|
||||||
fitViewOnInit: boolean;
|
fitViewQueued: boolean;
|
||||||
fitViewDone: boolean;
|
fitViewOptions: FitViewOptions | undefined;
|
||||||
fitViewOnInitOptions: FitViewOptions | undefined;
|
|
||||||
|
|
||||||
onNodesDelete?: OnNodesDelete<NodeType>;
|
onNodesDelete?: OnNodesDelete<NodeType>;
|
||||||
onEdgesDelete?: OnEdgesDelete<EdgeType>;
|
onEdgesDelete?: OnEdgesDelete<EdgeType>;
|
||||||
@@ -168,8 +167,6 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
|||||||
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
|
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
|
||||||
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
|
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
|
||||||
panBy: PanBy;
|
panBy: PanBy;
|
||||||
fitView: (options?: FitViewOptions) => Promise<boolean>;
|
|
||||||
fitViewSync: (options?: FitViewOptions) => boolean;
|
|
||||||
setPaneClickDistance: (distance: number) => void;
|
setPaneClickDistance: (distance: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -333,10 +333,10 @@ export const getConnectedEdges = <NodeType extends NodeBase = NodeBase, EdgeType
|
|||||||
return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
|
return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getFitViewNodes<
|
function getFitViewNodes<
|
||||||
Params extends NodeLookup<InternalNodeBase<NodeBase>>,
|
Params extends NodeLookup<InternalNodeBase<NodeBase>>,
|
||||||
Options extends FitViewOptionsBase<NodeBase>
|
Options extends FitViewOptionsBase<NodeBase>
|
||||||
>(nodeLookup: Params, options?: Pick<Options, 'nodes' | 'includeHiddenNodes'>) {
|
>(nodeLookup: Params, options?: Options) {
|
||||||
const fitViewNodes: NodeLookup = new Map();
|
const fitViewNodes: NodeLookup = new Map();
|
||||||
const optionNodeIds = options?.nodes ? new Set(options.nodes.map((node) => node.id)) : null;
|
const optionNodeIds = options?.nodes ? new Set(options.nodes.map((node) => node.id)) : null;
|
||||||
|
|
||||||
@@ -351,15 +351,21 @@ export function getFitViewNodes<
|
|||||||
return fitViewNodes;
|
return fitViewNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fitView<Params extends FitViewParamsBase<NodeBase>, Options extends FitViewOptionsBase<NodeBase>>(
|
export function fitViewport<Params extends FitViewParamsBase<NodeBase>, Options extends FitViewOptionsBase<NodeBase>>(
|
||||||
{ nodes, width, height, panZoom, minZoom, maxZoom }: Params,
|
{ nodes, width, height, panZoom, minZoom, maxZoom }: Params,
|
||||||
options?: Omit<Options, 'nodes' | 'includeHiddenNodes'>
|
options?: Omit<Options, 'nodes' | 'includeHiddenNodes'>
|
||||||
): Promise<boolean> {
|
): boolean {
|
||||||
if (nodes.size === 0) {
|
if (nodes.size === 0) {
|
||||||
return Promise.resolve(false);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bounds = getInternalNodesBounds(nodes);
|
if (!panZoom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodesToFit = getFitViewNodes(nodes, options);
|
||||||
|
|
||||||
|
const bounds = getInternalNodesBounds(nodesToFit);
|
||||||
|
|
||||||
const viewport = getViewportForBounds(
|
const viewport = getViewportForBounds(
|
||||||
bounds,
|
bounds,
|
||||||
@@ -370,9 +376,9 @@ export async function fitView<Params extends FitViewParamsBase<NodeBase>, Option
|
|||||||
options?.padding ?? 0.1
|
options?.padding ?? 0.1
|
||||||
);
|
);
|
||||||
|
|
||||||
await panZoom.setViewport(viewport, { duration: options?.duration });
|
panZoom.setViewport(viewport, { duration: options?.duration });
|
||||||
|
|
||||||
return Promise.resolve(true);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -85,9 +85,10 @@ export function adoptUserNodes<NodeType extends NodeBase>(
|
|||||||
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
|
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
|
||||||
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
|
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
|
||||||
options?: UpdateNodesOptions<NodeType>
|
options?: UpdateNodesOptions<NodeType>
|
||||||
) {
|
): boolean {
|
||||||
const _options = mergeObjects(adoptUserNodesDefaultOptions, options);
|
const _options = mergeObjects(adoptUserNodesDefaultOptions, options);
|
||||||
|
|
||||||
|
let nodesInitialized = true;
|
||||||
const tmpLookup = new Map(nodeLookup);
|
const tmpLookup = new Map(nodeLookup);
|
||||||
const selectedNodeZ: number = _options?.elevateNodesOnSelect ? 1000 : 0;
|
const selectedNodeZ: number = _options?.elevateNodesOnSelect ? 1000 : 0;
|
||||||
|
|
||||||
@@ -123,10 +124,16 @@ export function adoptUserNodes<NodeType extends NodeBase>(
|
|||||||
nodeLookup.set(userNode.id, internalNode);
|
nodeLookup.set(userNode.id, internalNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!internalNode.measured || !internalNode.measured.width || !internalNode.measured.height) {
|
||||||
|
nodesInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (userNode.parentId) {
|
if (userNode.parentId) {
|
||||||
updateChildNode(internalNode, nodeLookup, parentLookup, options);
|
updateChildNode(internalNode, nodeLookup, parentLookup, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nodesInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateParentLookup<NodeType extends NodeBase>(
|
function updateParentLookup<NodeType extends NodeBase>(
|
||||||
|
|||||||
Reference in New Issue
Block a user