implemented new fitView logic
This commit is contained in:
@@ -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,
|
||||
@@ -42,25 +41,60 @@ const createStore = ({
|
||||
nodeOrigin?: NodeOrigin;
|
||||
nodeExtent?: CoordinateExtent;
|
||||
}) =>
|
||||
createWithEqualityFn<ReactFlowState>(
|
||||
(set, get) => ({
|
||||
createWithEqualityFn<ReactFlowState>((set, get) => {
|
||||
async function resolveFitView() {
|
||||
const { nodeLookup, panZoom, fitViewOptions, fitViewResolver, width, height, minZoom, maxZoom } = get();
|
||||
|
||||
if (!panZoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
await fitViewport(
|
||||
{
|
||||
nodes: nodeLookup,
|
||||
width,
|
||||
height,
|
||||
panZoom,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
},
|
||||
fitViewOptions
|
||||
);
|
||||
|
||||
fitViewResolver?.resolve(true);
|
||||
/**
|
||||
* wait for the fitViewport to resolve before deleting the resolver,
|
||||
* we want to reuse the old resolver if the user calls fitView again in the mean time
|
||||
*/
|
||||
set({ fitViewResolver: null });
|
||||
}
|
||||
|
||||
return {
|
||||
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
||||
setNodes: (nodes: Node[]) => {
|
||||
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
||||
// setNodes() is called exclusively in response to user actions:
|
||||
// - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
||||
// - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
|
||||
//
|
||||
// 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 { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued } = get();
|
||||
/*
|
||||
* setNodes() is called exclusively in response to user actions:
|
||||
* - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
||||
* - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
|
||||
*
|
||||
* When this happens, we take the note objects passed by the user and extend them with fields
|
||||
* relevant for internal React Flow operations.
|
||||
*/
|
||||
|
||||
const nodesInitialized = adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
elevateNodesOnSelect,
|
||||
checkEquality: true,
|
||||
});
|
||||
|
||||
set({ nodes });
|
||||
if (fitViewQueued && nodesInitialized) {
|
||||
resolveFitView();
|
||||
set({ nodes, fitViewQueued: false, fitViewOptions: undefined });
|
||||
} else {
|
||||
set({ nodes });
|
||||
}
|
||||
},
|
||||
setEdges: (edges: Edge[]) => {
|
||||
const { connectionLookup, edgeLookup } = get();
|
||||
@@ -81,23 +115,14 @@ const createStore = ({
|
||||
set({ hasDefaultEdges: true });
|
||||
}
|
||||
},
|
||||
// Every node gets registerd at a ResizeObserver. Whenever a node
|
||||
// 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();
|
||||
/*
|
||||
* Every node gets registerd at a ResizeObserver. Whenever a node
|
||||
* changes its dimensions, this function is called to measure the
|
||||
* new dimensions and update the nodes.
|
||||
*/
|
||||
updateNodeInternals: (updates) => {
|
||||
const { triggerNodeChanges, nodeLookup, parentLookup, domNode, nodeOrigin, nodeExtent, debug, fitViewQueued } =
|
||||
get();
|
||||
|
||||
const { changes, updatedInternals } = updateNodeInternalsSystem(
|
||||
updates,
|
||||
@@ -114,23 +139,9 @@ 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 });
|
||||
if (fitViewQueued) {
|
||||
resolveFitView();
|
||||
set({ fitViewQueued: false, fitViewOptions: undefined });
|
||||
} else {
|
||||
// we always want to trigger useStore calls whenever updateNodeInternals is called
|
||||
set({});
|
||||
@@ -146,9 +157,12 @@ const createStore = ({
|
||||
updateNodePositions: (nodeDragItems, dragging = false) => {
|
||||
const parentExpandChildren: ParentExpandChild[] = [];
|
||||
const changes = [];
|
||||
const { nodeLookup, triggerNodeChanges } = get();
|
||||
|
||||
for (const [id, dragItem] of nodeDragItems) {
|
||||
const expandParent = !!(dragItem?.expandParent && dragItem?.parentId && dragItem?.position);
|
||||
// we are using the nodelookup to be sure to use the current expandParent and parentId value
|
||||
const node = nodeLookup.get(id);
|
||||
const expandParent = !!(node?.expandParent && node?.parentId && dragItem?.position);
|
||||
|
||||
const change: NodeChange = {
|
||||
id,
|
||||
@@ -162,14 +176,14 @@ const createStore = ({
|
||||
dragging,
|
||||
};
|
||||
|
||||
if (expandParent) {
|
||||
if (expandParent && node.parentId) {
|
||||
parentExpandChildren.push({
|
||||
id,
|
||||
parentId: dragItem.parentId!,
|
||||
parentId: node.parentId,
|
||||
rect: {
|
||||
...dragItem.internals.positionAbsolute,
|
||||
width: dragItem.measured.width!,
|
||||
height: dragItem.measured.height!,
|
||||
width: dragItem.measured.width ?? 0,
|
||||
height: dragItem.measured.height ?? 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -178,12 +192,12 @@ const createStore = ({
|
||||
}
|
||||
|
||||
if (parentExpandChildren.length > 0) {
|
||||
const { nodeLookup, parentLookup, nodeOrigin } = get();
|
||||
const { parentLookup, nodeOrigin } = get();
|
||||
const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
|
||||
changes.push(...parentExpandChanges);
|
||||
}
|
||||
|
||||
get().triggerNodeChanges(changes);
|
||||
triggerNodeChanges(changes);
|
||||
},
|
||||
triggerNodeChanges: (changes) => {
|
||||
const { onNodesChange, setNodes, nodes, hasDefaultNodes, debug } = get();
|
||||
@@ -222,7 +236,7 @@ const createStore = ({
|
||||
|
||||
if (multiSelectionActive) {
|
||||
const nodeChanges = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
|
||||
triggerNodeChanges(nodeChanges as NodeSelectionChange[]);
|
||||
triggerNodeChanges(nodeChanges);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -234,7 +248,7 @@ const createStore = ({
|
||||
|
||||
if (multiSelectionActive) {
|
||||
const changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
|
||||
triggerEdgeChanges(changedEdges as EdgeSelectionChange[]);
|
||||
triggerEdgeChanges(changedEdges);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,8 +262,10 @@ const createStore = ({
|
||||
const nodeChanges = nodesToUnselect.map((n) => {
|
||||
const internalNode = nodeLookup.get(n.id);
|
||||
if (internalNode) {
|
||||
// we need to unselect the internal node that was selected previously before we
|
||||
// send the change to the user to prevent it to be selected while dragging the new node
|
||||
/*
|
||||
* we need to unselect the internal node that was selected previously before we
|
||||
* send the change to the user to prevent it to be selected while dragging the new node
|
||||
*/
|
||||
internalNode.selected = false;
|
||||
}
|
||||
|
||||
@@ -257,8 +273,8 @@ const createStore = ({
|
||||
});
|
||||
const edgeChanges = edgesToUnselect.map((edge) => createSelectionChange(edge.id, false));
|
||||
|
||||
triggerNodeChanges(nodeChanges as NodeSelectionChange[]);
|
||||
triggerEdgeChanges(edgeChanges as EdgeSelectionChange[]);
|
||||
triggerNodeChanges(nodeChanges);
|
||||
triggerEdgeChanges(edgeChanges);
|
||||
},
|
||||
setMinZoom: (minZoom) => {
|
||||
const { panZoom, maxZoom } = get();
|
||||
@@ -284,11 +300,11 @@ const createStore = ({
|
||||
const { edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
|
||||
|
||||
const nodeChanges = nodes.reduce<NodeSelectionChange[]>(
|
||||
(res, node) => (node.selected ? [...res, createSelectionChange(node.id, false) as NodeSelectionChange] : res),
|
||||
(res, node) => (node.selected ? [...res, createSelectionChange(node.id, false)] : res),
|
||||
[]
|
||||
);
|
||||
const edgeChanges = edges.reduce<EdgeSelectionChange[]>(
|
||||
(res, edge) => (edge.selected ? [...res, createSelectionChange(edge.id, false) as EdgeSelectionChange] : res),
|
||||
(res, edge) => (edge.selected ? [...res, createSelectionChange(edge.id, false)] : res),
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -321,52 +337,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 },
|
||||
@@ -377,8 +347,7 @@ const createStore = ({
|
||||
},
|
||||
|
||||
reset: () => set({ ...getInitialState() }),
|
||||
}),
|
||||
Object.is
|
||||
);
|
||||
};
|
||||
}, Object.is);
|
||||
|
||||
export { createStore };
|
||||
|
||||
@@ -104,13 +104,14 @@ const getInitialState = ({
|
||||
elementsSelectable: true,
|
||||
elevateNodesOnSelect: true,
|
||||
elevateEdgesOnSelect: false,
|
||||
fitViewOnInit: false,
|
||||
fitViewDone: false,
|
||||
fitViewOnInitOptions: undefined,
|
||||
selectNodesOnDrag: true,
|
||||
|
||||
multiSelectionActive: false,
|
||||
|
||||
fitViewQueued: fitView ?? false,
|
||||
fitViewOptions: undefined,
|
||||
fitViewResolver: null,
|
||||
|
||||
connection: { ...initialConnection },
|
||||
connectionClickStartHandle: null,
|
||||
connectOnClick: true,
|
||||
|
||||
Reference in New Issue
Block a user