Store: don’t regenerate internal nodes if user-provided nodes haven’t changed
This commit is contained in:
@@ -2,7 +2,7 @@ import { createWithEqualityFn } from 'zustand/traditional';
|
|||||||
import {
|
import {
|
||||||
clampPosition,
|
clampPosition,
|
||||||
fitView as fitViewSystem,
|
fitView as fitViewSystem,
|
||||||
updateNodes,
|
adoptUserProvidedNodes,
|
||||||
updateAbsolutePositions,
|
updateAbsolutePositions,
|
||||||
panBy as panBySystem,
|
panBy as panBySystem,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
@@ -42,11 +42,16 @@ const createRFStore = ({
|
|||||||
...getInitialState({ nodes, edges, width, height, fitView }),
|
...getInitialState({ nodes, edges, width, height, fitView }),
|
||||||
setNodes: (nodes: Node[]) => {
|
setNodes: (nodes: Node[]) => {
|
||||||
const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
||||||
// Whenver new nodes are set, we need to calculate the absolute positions of the nodes
|
// setNodes() is called exclusively in response to user actions:
|
||||||
// and update the nodeLookup.
|
// - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
||||||
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
|
// - 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.
|
||||||
|
// TODO: consider updating the types to reflect the distinction between user-provided nodes and internal nodes.
|
||||||
|
const nodesWithInternalData = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
|
||||||
|
|
||||||
set({ nodes: nextNodes });
|
set({ nodes: nodesWithInternalData });
|
||||||
},
|
},
|
||||||
setEdges: (edges: Edge[]) => {
|
setEdges: (edges: Edge[]) => {
|
||||||
const { defaultEdgeOptions = {} } = get();
|
const { defaultEdgeOptions = {} } = get();
|
||||||
@@ -69,7 +74,7 @@ const createRFStore = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (hasDefaultNodes) {
|
if (hasDefaultNodes) {
|
||||||
nextState.nodes = updateNodes(nodes, new Map(), {
|
nextState.nodes = adoptUserProvidedNodes(nodes, new Map(), {
|
||||||
nodeOrigin: get().nodeOrigin,
|
nodeOrigin: get().nodeOrigin,
|
||||||
elevateNodesOnSelect: get().elevateNodesOnSelect,
|
elevateNodesOnSelect: get().elevateNodesOnSelect,
|
||||||
});
|
});
|
||||||
@@ -163,7 +168,7 @@ const createRFStore = ({
|
|||||||
if (changes?.length) {
|
if (changes?.length) {
|
||||||
if (hasDefaultNodes) {
|
if (hasDefaultNodes) {
|
||||||
const updatedNodes = applyNodeChanges(changes, nodes);
|
const updatedNodes = applyNodeChanges(changes, nodes);
|
||||||
const nextNodes = updateNodes(updatedNodes, nodeLookup, {
|
const nextNodes = adoptUserProvidedNodes(updatedNodes, nodeLookup, {
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
elevateNodesOnSelect,
|
elevateNodesOnSelect,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
infiniteExtent,
|
infiniteExtent,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
updateNodes,
|
adoptUserProvidedNodes,
|
||||||
getNodesBounds,
|
getNodesBounds,
|
||||||
getViewportForBounds,
|
getViewportForBounds,
|
||||||
Transform,
|
Transform,
|
||||||
@@ -23,7 +23,7 @@ const getInitialState = ({
|
|||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
} = {}): ReactFlowStore => {
|
} = {}): ReactFlowStore => {
|
||||||
const nodeLookup = new Map<string, Node>();
|
const nodeLookup = new Map<string, Node>();
|
||||||
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||||
|
|
||||||
let transform: Transform = [0, 0, 1];
|
let transform: Transform = [0, 0, 1];
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
type OnError,
|
type OnError,
|
||||||
devWarn,
|
devWarn,
|
||||||
type Viewport,
|
type Viewport,
|
||||||
updateNodes,
|
adoptUserProvidedNodes,
|
||||||
getNodesBounds,
|
getNodesBounds,
|
||||||
getViewportForBounds
|
getViewportForBounds
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
@@ -68,7 +68,7 @@ export const getInitialStore = ({
|
|||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const nodeLookup = new Map<string, Node>();
|
const nodeLookup = new Map<string, Node>();
|
||||||
const nextNodes = updateNodes(nodes, nodeLookup, {
|
const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, {
|
||||||
nodeOrigin: [0, 0],
|
nodeOrigin: [0, 0],
|
||||||
elevateNodesOnSelect: false
|
elevateNodesOnSelect: false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
type Writable,
|
type Writable,
|
||||||
get
|
get
|
||||||
} from 'svelte/store';
|
} from 'svelte/store';
|
||||||
import { updateNodes, type Viewport, type PanZoomInstance } from '@xyflow/system';
|
import { adoptUserProvidedNodes, type Viewport, type PanZoomInstance } from '@xyflow/system';
|
||||||
|
|
||||||
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ export const createNodesStore = (
|
|||||||
let elevateNodesOnSelect = true;
|
let elevateNodesOnSelect = true;
|
||||||
|
|
||||||
const _set = (nds: Node[]): Node[] => {
|
const _set = (nds: Node[]): Node[] => {
|
||||||
const nextNodes = updateNodes(nds, nodeLookup, {
|
const nextNodes = adoptUserProvidedNodes(nds, nodeLookup, {
|
||||||
elevateNodesOnSelect,
|
elevateNodesOnSelect,
|
||||||
defaults
|
defaults
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ export type NodeBase<T = any, U extends string | undefined = string | undefined>
|
|||||||
z?: number;
|
z?: number;
|
||||||
handleBounds?: NodeHandleBounds;
|
handleBounds?: NodeHandleBounds;
|
||||||
isParent?: boolean;
|
isParent?: boolean;
|
||||||
|
/** Holds a reference to the original node object provided by the user
|
||||||
|
* (which may lack some fields, like `computed` or `[internalSymbol]`. Used
|
||||||
|
* as an optimization to avoid certain operations. */
|
||||||
|
userProvidedNode: WeakRef<NodeBase>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
|
|||||||
defaults?: Partial<NodeType>;
|
defaults?: Partial<NodeType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function updateNodes<NodeType extends NodeBase>(
|
export function adoptUserProvidedNodes<NodeType extends NodeBase>(
|
||||||
nodes: NodeType[],
|
nodes: NodeType[],
|
||||||
nodeLookup: Map<string, NodeType>,
|
nodeLookup: Map<string, NodeType>,
|
||||||
options: UpdateNodesOptions<NodeType> = {
|
options: UpdateNodesOptions<NodeType> = {
|
||||||
@@ -76,6 +76,8 @@ export function updateNodes<NodeType extends NodeBase>(
|
|||||||
|
|
||||||
const nextNodes = nodes.map((n) => {
|
const nextNodes = nodes.map((n) => {
|
||||||
const currentStoreNode = nodeLookup.get(n.id);
|
const currentStoreNode = nodeLookup.get(n.id);
|
||||||
|
if (n === currentStoreNode?.[internalsSymbol]?.userProvidedNode.deref()) return currentStoreNode;
|
||||||
|
|
||||||
const node: NodeType = {
|
const node: NodeType = {
|
||||||
...options.defaults,
|
...options.defaults,
|
||||||
...n,
|
...n,
|
||||||
@@ -97,6 +99,7 @@ export function updateNodes<NodeType extends NodeBase>(
|
|||||||
value: {
|
value: {
|
||||||
handleBounds: currInternals?.handleBounds,
|
handleBounds: currInternals?.handleBounds,
|
||||||
z,
|
z,
|
||||||
|
userProvidedNode: new WeakRef(n),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user