refactor(react/svelte): use node lookup for perf improvement

This commit is contained in:
moklick
2023-11-13 16:43:57 +01:00
parent 6e1e702cf3
commit a147a20f6e
22 changed files with 127 additions and 78 deletions
@@ -20,8 +20,6 @@ export function getNodesAndEdges(xElements = 10, yElements = 10): ElementsCollec
style: { width: 50, height: 30, fontSize: 11 }, style: { width: 50, height: 30, fontSize: 11 },
data, data,
position, position,
width: 50,
height: 30,
}; };
initialNodes.push(node); initialNodes.push(node);
+1 -1
View File
@@ -9,7 +9,7 @@
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"dev": "turbo run dev --parallel --concurrency 12", "dev": "turbo run dev --parallel --concurrency 12",
"dev:svelte": "turbo run dev --filter=svelte --filter=system", "dev:svelte": "turbo run dev --filter=svelte --filter=system",
"dev:react": "turbo run dev --filter=react", "dev:react": "turbo run dev --filter=react-examples ",
"test:svelte": "pnpm --filter=playwright run test:svelte", "test:svelte": "pnpm --filter=playwright run test:svelte",
"test:svelte:ui": "pnpm --filter=playwright run test:svelte:ui", "test:svelte:ui": "pnpm --filter=playwright run test:svelte:ui",
"test:react": "pnpm --filter=playwright run test:react", "test:react": "pnpm --filter=playwright run test:react",
@@ -65,8 +65,8 @@ function ResizeControl({
const dragHandler = drag<HTMLDivElement, unknown>() const dragHandler = drag<HTMLDivElement, unknown>()
.on('start', (event: ResizeDragEvent) => { .on('start', (event: ResizeDragEvent) => {
const { nodes, transform, snapGrid, snapToGrid } = store.getState(); const { nodesLookup, transform, snapGrid, snapToGrid } = store.getState();
const node = nodes.find((n) => n.id === id); const node = nodesLookup.get(id);
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
prevValues.current = { prevValues.current = {
@@ -86,9 +86,9 @@ function ResizeControl({
onResizeStart?.(event, { ...prevValues.current }); onResizeStart?.(event, { ...prevValues.current });
}) })
.on('drag', (event: ResizeDragEvent) => { .on('drag', (event: ResizeDragEvent) => {
const { nodes, transform, snapGrid, snapToGrid, triggerNodeChanges } = store.getState(); const { nodesLookup, transform, snapGrid, snapToGrid, triggerNodeChanges } = store.getState();
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
const node = nodes.find((n) => n.id === id); const node = nodesLookup.get(id);
if (node) { if (node) {
const changes: NodeChange[] = []; const changes: NodeChange[] = [];
@@ -43,7 +43,7 @@ const ConnectionLine = ({
const { fromNode, handleId, toX, toY, connectionMode } = useStore( const { fromNode, handleId, toX, toY, connectionMode } = useStore(
useCallback( useCallback(
(s: ReactFlowStore) => ({ (s: ReactFlowStore) => ({
fromNode: s.nodes.find((n) => n.id === nodeId), fromNode: s.nodesLookup.get(nodeId),
handleId: s.connectionStartHandle?.handleId, handleId: s.connectionStartHandle?.handleId,
toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2], toX: (s.connectionPosition.x - s.transform[0]) / s.transform[2],
toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2], toY: (s.connectionPosition.y - s.transform[1]) / s.transform[2],
@@ -53,9 +53,9 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
const [updateHover, setUpdateHover] = useState<boolean>(false); const [updateHover, setUpdateHover] = useState<boolean>(false);
const [updating, setUpdating] = useState<boolean>(false); const [updating, setUpdating] = useState<boolean>(false);
const store = useStoreApi(); const store = useStoreApi();
const edgePosition = useStore((state) => { const edgePosition = useStore(function edgeSelector(state) {
const sourceNode = state.nodes.find((n) => n.id === source); const sourceNode = state.nodesLookup.get(source);
const targetNode = state.nodes.find((n) => n.id === target); const targetNode = state.nodesLookup.get(target);
if (!sourceNode || !targetNode) { if (!sourceNode || !targetNode) {
return null; return null;
@@ -48,11 +48,16 @@ const NodeRenderer = (props: NodeRendererProps) => {
} }
const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
const updates = entries.map((entry: ResizeObserverEntry) => ({ const updates = new Map();
id: entry.target.getAttribute('data-id') as string,
nodeElement: entry.target as HTMLDivElement, entries.forEach((entry: ResizeObserverEntry) => {
forceUpdate: true, const id = entry.target.getAttribute('data-id') as string;
})); updates.set(id, {
id,
nodeElement: entry.target as HTMLDivElement,
forceUpdate: true,
});
});
updateNodeDimensions(updates); updateNodeDimensions(updates);
}); });
+3 -3
View File
@@ -12,8 +12,8 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole
const visibleEdges = const visibleEdges =
onlyRenderVisible && s.width && s.height onlyRenderVisible && s.width && s.height
? s.edges.filter((e) => { ? s.edges.filter((e) => {
const sourceNode = s.nodes.find((n) => n.id === e.source); const sourceNode = s.nodesLookup.get(e.source);
const targetNode = s.nodes.find((n) => n.id === e.target); const targetNode = s.nodesLookup.get(e.target);
return ( return (
sourceNode && sourceNode &&
@@ -29,7 +29,7 @@ function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boole
}) })
: s.edges; : s.edges;
return groupEdgesByZLevel(visibleEdges, s.nodes, elevateEdgesOnSelect); return groupEdgesByZLevel(visibleEdges, s.nodesLookup, elevateEdgesOnSelect);
}, },
[onlyRenderVisible, elevateEdgesOnSelect] [onlyRenderVisible, elevateEdgesOnSelect]
), ),
+18 -8
View File
@@ -41,8 +41,8 @@ const createRFStore = ({
(set, get) => ({ (set, get) => ({
...getInitialState({ nodes, edges, width, height, fitView }), ...getInitialState({ nodes, edges, width, height, fitView }),
setNodes: (nodes: Node[]) => { setNodes: (nodes: Node[]) => {
const { nodes: storeNodes, nodeOrigin, elevateNodesOnSelect } = get(); const { nodesLookup, nodeOrigin, elevateNodesOnSelect } = get();
const nextNodes = updateNodes(nodes, storeNodes, { nodeOrigin, elevateNodesOnSelect }); const nextNodes = updateNodes(nodes, nodesLookup, { nodeOrigin, elevateNodesOnSelect });
set({ nodes: nextNodes }); set({ nodes: nextNodes });
}, },
@@ -68,7 +68,7 @@ const createRFStore = ({
}; };
if (hasDefaultNodes) { if (hasDefaultNodes) {
nextState.nodes = updateNodes(nodes, [], { nextState.nodes = updateNodes(nodes, new Map(), {
nodeOrigin: get().nodeOrigin, nodeOrigin: get().nodeOrigin,
elevateNodesOnSelect: get().elevateNodesOnSelect, elevateNodesOnSelect: get().elevateNodesOnSelect,
}); });
@@ -80,13 +80,23 @@ const createRFStore = ({
set(nextState); set(nextState);
}, },
updateNodeDimensions: (updates) => { updateNodeDimensions: (updates) => {
const { onNodesChange, fitView, nodes, fitViewOnInit, fitViewDone, fitViewOnInitOptions, domNode, nodeOrigin } = const {
get(); onNodesChange,
fitView,
nodes,
nodesLookup,
fitViewOnInit,
fitViewDone,
fitViewOnInitOptions,
domNode,
nodeOrigin,
} = get();
const changes: NodeDimensionChange[] = []; const changes: NodeDimensionChange[] = [];
const updatedNodes = updateNodeDimensionsSystem( const updatedNodes = updateNodeDimensionsSystem(
updates, updates,
nodes, nodes,
nodesLookup,
domNode, domNode,
nodeOrigin, nodeOrigin,
(id: string, dimensions: Dimensions) => { (id: string, dimensions: Dimensions) => {
@@ -102,7 +112,7 @@ const createRFStore = ({
return; return;
} }
const nextNodes = updateAbsolutePositions(updatedNodes, nodeOrigin); const nextNodes = updateAbsolutePositions(updatedNodes, nodesLookup, nodeOrigin);
let nextFitViewDone = fitViewDone; let nextFitViewDone = fitViewDone;
if (!fitViewDone && fitViewOnInit) { if (!fitViewDone && fitViewOnInit) {
@@ -138,12 +148,12 @@ const createRFStore = ({
}, },
triggerNodeChanges: (changes) => { triggerNodeChanges: (changes) => {
const { onNodesChange, nodes, hasDefaultNodes, nodeOrigin, elevateNodesOnSelect } = get(); const { onNodesChange, nodesLookup, nodes, hasDefaultNodes, nodeOrigin, elevateNodesOnSelect } = get();
if (changes?.length) { if (changes?.length) {
if (hasDefaultNodes) { if (hasDefaultNodes) {
const updatedNodes = applyNodeChanges(changes, nodes); const updatedNodes = applyNodeChanges(changes, nodes);
const nextNodes = updateNodes(updatedNodes, nodes, { const nextNodes = updateNodes(updatedNodes, nodesLookup, {
nodeOrigin, nodeOrigin,
elevateNodesOnSelect, elevateNodesOnSelect,
}); });
+3 -1
View File
@@ -22,7 +22,8 @@ const getInitialState = ({
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
} = {}): ReactFlowStore => { } = {}): ReactFlowStore => {
const nextNodes = updateNodes(nodes, [], { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); const nodesLookup = new Map<string, Node>();
const nextNodes = updateNodes(nodes, nodesLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
let transform: Transform = [0, 0, 1]; let transform: Transform = [0, 0, 1];
@@ -43,6 +44,7 @@ const getInitialState = ({
height: 0, height: 0,
transform, transform,
nodes: nextNodes, nodes: nextNodes,
nodesLookup,
edges: edges, edges: edges,
onNodesChange: null, onNodesChange: null,
onEdgesChange: null, onEdgesChange: null,
+2 -1
View File
@@ -46,6 +46,7 @@ export type ReactFlowStore = {
height: number; height: number;
transform: Transform; transform: Transform;
nodes: Node[]; nodes: Node[];
nodesLookup: Map<string, Node>;
edges: Edge[]; edges: Edge[];
onNodesChange: OnNodesChange | null; onNodesChange: OnNodesChange | null;
onEdgesChange: OnEdgesChange | null; onEdgesChange: OnEdgesChange | null;
@@ -141,7 +142,7 @@ export type ReactFlowActions = {
getNodes: () => Node[]; getNodes: () => Node[];
setEdges: (edges: Edge[]) => void; setEdges: (edges: Edge[]) => void;
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void; setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => void;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void; updateNodeDimensions: (updates: Map<string, NodeDimensionUpdate>) => void;
updateNodePositions: UpdateNodePositions; updateNodePositions: UpdateNodePositions;
resetSelectedElements: () => void; resetSelectedElements: () => void;
unselectNodesAndEdges: (params?: UnselectNodesAndEdgesParams) => void; unselectNodesAndEdges: (params?: UnselectNodesAndEdgesParams) => void;
@@ -30,6 +30,7 @@ export default function drag(domNode: Element, params: UseDragParams) {
return { return {
nodes: get(store.nodes), nodes: get(store.nodes),
nodesLookup: get(store.nodesLookup),
edges: get(store.edges), edges: get(store.edges),
nodeExtent: get(store.nodeExtent), nodeExtent: get(store.nodeExtent),
snapGrid: snapGrid ? snapGrid : [0, 0], snapGrid: snapGrid ? snapGrid : [0, 0],
@@ -17,11 +17,18 @@
typeof ResizeObserver === 'undefined' typeof ResizeObserver === 'undefined'
? null ? null
: new ResizeObserver((entries: ResizeObserverEntry[]) => { : new ResizeObserver((entries: ResizeObserverEntry[]) => {
const updates = entries.map((entry: ResizeObserverEntry) => ({ const updates = new Map();
id: entry.target.getAttribute('data-id') as string,
nodeElement: entry.target as HTMLDivElement, entries.forEach((entry: ResizeObserverEntry) => {
forceUpdate: true const id = entry.target.getAttribute('data-id') as string;
}));
updates.set(id, {
id,
nodeElement: entry.target as HTMLDivElement,
forceUpdate: true
});
});
updateNodeDimensions(updates); updateNodeDimensions(updates);
}); });
@@ -56,15 +56,15 @@ export function getDerivedConnectionProps(
currentConnection, currentConnection,
store.connectionLineType, store.connectionLineType,
store.connectionMode, store.connectionMode,
store.nodes, store.nodesLookup,
store.viewport store.viewport
], ],
([connection, connectionLineType, connectionMode, nodes, viewport]) => { ([connection, connectionLineType, connectionMode, nodesLookup, viewport]) => {
if (!connection.connectionStartHandle?.nodeId) { if (!connection.connectionStartHandle?.nodeId) {
return initConnectionProps; return initConnectionProps;
} }
const fromNode = nodes.find((n) => n.id === connection.connectionStartHandle?.nodeId); const fromNode = nodesLookup.get(connection.connectionStartHandle?.nodeId);
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds; const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
const handleBoundsStrict = const handleBoundsStrict =
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || []; fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
+9 -8
View File
@@ -9,17 +9,18 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
[ [
store.edges, store.edges,
store.nodes, store.nodes,
store.nodesLookup,
store.onlyRenderVisibleElements, store.onlyRenderVisibleElements,
store.viewport, store.viewport,
store.width, store.width,
store.height store.height
], ],
([edges, nodes, onlyRenderVisibleElements, viewport, width, height]) => { ([edges, , nodesLookup, onlyRenderVisibleElements, viewport, width, height]) => {
const visibleEdges = const visibleEdges =
onlyRenderVisibleElements && width && height onlyRenderVisibleElements && width && height
? edges.filter((edge) => { ? edges.filter((edge) => {
const sourceNode = nodes.find((node) => node.id === edge.source); const sourceNode = nodesLookup.get(edge.source);
const targetNode = nodes.find((node) => node.id === edge.target); const targetNode = nodesLookup.get(edge.target);
return ( return (
sourceNode && sourceNode &&
@@ -40,11 +41,11 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
); );
return derived( return derived(
[visibleEdges, store.nodes, store.connectionMode, store.onError], [visibleEdges, store.nodes, store.nodesLookup, store.connectionMode, store.onError],
([visibleEdges, nodes, connectionMode, onError]) => { ([visibleEdges, , nodesLookup, connectionMode, onError]) => {
const layoutedEdges = visibleEdges.reduce<EdgeLayouted[]>((res, edge) => { const layoutedEdges = visibleEdges.reduce<EdgeLayouted[]>((res, edge) => {
const sourceNode = nodes.find((node) => node.id === edge.source); const sourceNode = nodesLookup.get(edge.source);
const targetNode = nodes.find((node) => node.id === edge.target); const targetNode = nodesLookup.get(edge.target);
if (!sourceNode || !targetNode) { if (!sourceNode || !targetNode) {
return res; return res;
@@ -70,7 +71,7 @@ export function getEdgeTree(store: SvelteFlowStoreState) {
return res; return res;
}, []); }, []);
const groupedEdges = groupEdgesByZLevel<EdgeLayouted>(layoutedEdges, nodes, false); const groupedEdges = groupEdgesByZLevel<EdgeLayouted>(layoutedEdges, nodesLookup, false);
return groupedEdges; return groupedEdges;
} }
+2 -1
View File
@@ -86,10 +86,11 @@ export function createStore({
}); });
}; };
function updateNodeDimensions(updates: NodeDimensionUpdate[]) { function updateNodeDimensions(updates: Map<string, NodeDimensionUpdate>) {
const nextNodes = updateNodeDimensionsSystem( const nextNodes = updateNodeDimensionsSystem(
updates, updates,
get(store.nodes), get(store.nodes),
get(store.nodesLookup),
get(store.domNode), get(store.domNode),
get(store.nodeOrigin) get(store.nodeOrigin)
); );
@@ -59,7 +59,11 @@ export const getInitialStore = ({
height?: number; height?: number;
fitView?: boolean; fitView?: boolean;
}) => { }) => {
const nextNodes = updateNodes(nodes, [], { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); const nodesLookup = new Map<string, Node>();
const nextNodes = updateNodes(nodes, nodesLookup, {
nodeOrigin: [0, 0],
elevateNodesOnSelect: false
});
let viewport: Viewport = { x: 0, y: 0, zoom: 1 }; let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
@@ -75,7 +79,8 @@ export const getInitialStore = ({
return { return {
flowId: writable<string | null>(null), flowId: writable<string | null>(null),
nodes: createNodesStore(nextNodes), nodes: createNodesStore(nextNodes, nodesLookup),
nodesLookup: readable<Map<string, Node>>(nodesLookup),
visibleNodes: readable<Node[]>([]), visibleNodes: readable<Node[]>([]),
edges: createEdgesStore(edges), edges: createEdgesStore(edges),
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]), edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
+1 -1
View File
@@ -27,7 +27,7 @@ export type SvelteFlowStoreActions = {
setTranslateExtent: (extent: CoordinateExtent) => void; setTranslateExtent: (extent: CoordinateExtent) => void;
fitView: (options?: FitViewOptions) => boolean; fitView: (options?: FitViewOptions) => boolean;
updateNodePositions: UpdateNodePositions; updateNodePositions: UpdateNodePositions;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void; updateNodeDimensions: (updates: Map<string, NodeDimensionUpdate>) => void;
unselectNodesAndEdges: (params?: { nodes?: Node[]; edges?: Edge[] }) => void; unselectNodesAndEdges: (params?: { nodes?: Node[]; edges?: Edge[] }) => void;
addSelectedNodes: (ids: string[]) => void; addSelectedNodes: (ids: string[]) => void;
addSelectedEdges: (ids: string[]) => void; addSelectedEdges: (ids: string[]) => void;
+3 -2
View File
@@ -111,7 +111,8 @@ export type NodeStoreOptions = {
// we are creating a custom store for the internals nodes in order to update the zIndex and positionAbsolute. // we are creating a custom store for the internals nodes in order to update the zIndex and positionAbsolute.
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes. // The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
export const createNodesStore = ( export const createNodesStore = (
nodes: Node[] nodes: Node[],
nodesLookup: Map<string, Node>
): { ): {
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber; subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
update: (this: void, updater: Updater<Node[]>) => void; update: (this: void, updater: Updater<Node[]>) => void;
@@ -125,7 +126,7 @@ export const createNodesStore = (
let elevateNodesOnSelect = true; let elevateNodesOnSelect = true;
const _set = (nds: Node[]): Node[] => { const _set = (nds: Node[]): Node[] => {
const nextNodes = updateNodes(nds, value, { const nextNodes = updateNodes(nds, nodesLookup, {
elevateNodesOnSelect, elevateNodesOnSelect,
defaults defaults
}); });
+3 -3
View File
@@ -33,7 +33,7 @@ export type GroupedEdges<EdgeType extends EdgeBase> = {
export function groupEdgesByZLevel<EdgeType extends EdgeBase>( export function groupEdgesByZLevel<EdgeType extends EdgeBase>(
edges: EdgeType[], edges: EdgeType[],
nodes: NodeBase[], nodesLookup: Map<string, NodeBase>,
elevateEdgesOnSelect = false elevateEdgesOnSelect = false
): GroupedEdges<EdgeType>[] { ): GroupedEdges<EdgeType>[] {
let maxLevel = -1; let maxLevel = -1;
@@ -43,8 +43,8 @@ export function groupEdgesByZLevel<EdgeType extends EdgeBase>(
let z = hasZIndex ? edge.zIndex! : 0; let z = hasZIndex ? edge.zIndex! : 0;
if (elevateEdgesOnSelect) { if (elevateEdgesOnSelect) {
const targetNode = nodes.find((n) => n.id === edge.target); const targetNode = nodesLookup.get(edge.target);
const sourceNode = nodes.find((n) => n.id === edge.source); const sourceNode = nodesLookup.get(edge.source);
const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected; const edgeOrConnectedNodeSelected = edge.selected || targetNode?.selected || sourceNode?.selected;
const selectedZIndex = Math.max( const selectedZIndex = Math.max(
sourceNode?.[internalsSymbol]?.z || 0, sourceNode?.[internalsSymbol]?.z || 0,
+21 -9
View File
@@ -18,19 +18,21 @@ type ParentNodes = Record<string, boolean>;
export function updateAbsolutePositions<NodeType extends NodeBase>( export function updateAbsolutePositions<NodeType extends NodeBase>(
nodes: NodeType[], nodes: NodeType[],
nodesLookup: Map<string, NodeType>,
nodeOrigin: NodeOrigin = [0, 0], nodeOrigin: NodeOrigin = [0, 0],
parentNodes?: ParentNodes parentNodes?: ParentNodes
) { ) {
return nodes.map((node) => { return nodes.map((node) => {
if (node.parentNode && !nodes.find((n) => n.id === node.parentNode)) { if (node.parentNode && !nodesLookup.has(node.parentNode)) {
throw new Error(`Parent node ${node.parentNode} not found`); throw new Error(`Parent node ${node.parentNode} not found`);
} }
if (node.parentNode || parentNodes?.[node.id]) { if (node.parentNode || parentNodes?.[node.id]) {
const parentNode = node.parentNode ? nodes.find((n) => n.id === node.parentNode) : null; const parentNode = node.parentNode ? nodesLookup.get(node.parentNode) : null;
const { x, y, z } = calculateXYZPosition( const { x, y, z } = calculateXYZPosition(
node, node,
nodes, nodes,
nodesLookup,
{ {
...node.position, ...node.position,
z: node[internalsSymbol]?.z ?? 0, z: node[internalsSymbol]?.z ?? 0,
@@ -62,7 +64,7 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
export function updateNodes<NodeType extends NodeBase>( export function updateNodes<NodeType extends NodeBase>(
nodes: NodeType[], nodes: NodeType[],
storeNodes: NodeType[], nodesLookup: Map<string, NodeType>,
options: UpdateNodesOptions<NodeType> = { options: UpdateNodesOptions<NodeType> = {
nodeOrigin: [0, 0] as NodeOrigin, nodeOrigin: [0, 0] as NodeOrigin,
elevateNodesOnSelect: true, elevateNodesOnSelect: true,
@@ -73,7 +75,7 @@ export function updateNodes<NodeType extends NodeBase>(
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0; const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
const nextNodes = nodes.map((n) => { const nextNodes = nodes.map((n) => {
const currentStoreNode = storeNodes.find((storeNode) => n.id === storeNode.id); const currentStoreNode = nodesLookup.get(n.id);
const node: NodeType = { const node: NodeType = {
...options.defaults, ...options.defaults,
...n, ...n,
@@ -96,10 +98,12 @@ export function updateNodes<NodeType extends NodeBase>(
}, },
}); });
nodesLookup.set(node.id, node);
return node; return node;
}); });
const nodesWithPositions = updateAbsolutePositions(nextNodes, options.nodeOrigin, parentNodes); const nodesWithPositions = updateAbsolutePositions(nextNodes, nodesLookup, options.nodeOrigin, parentNodes);
return nodesWithPositions; return nodesWithPositions;
} }
@@ -107,6 +111,7 @@ export function updateNodes<NodeType extends NodeBase>(
function calculateXYZPosition<NodeType extends NodeBase>( function calculateXYZPosition<NodeType extends NodeBase>(
node: NodeType, node: NodeType,
nodes: NodeType[], nodes: NodeType[],
nodesLookup: Map<string, NodeType>,
result: XYZPosition, result: XYZPosition,
nodeOrigin: NodeOrigin nodeOrigin: NodeOrigin
): XYZPosition { ): XYZPosition {
@@ -114,12 +119,13 @@ function calculateXYZPosition<NodeType extends NodeBase>(
return result; return result;
} }
const parentNode = nodes.find((n) => n.id === node.parentNode)!; const parentNode = nodesLookup.get(node.parentNode)!;
const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin); const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
return calculateXYZPosition( return calculateXYZPosition(
parentNode, parentNode,
nodes, nodes,
nodesLookup,
{ {
x: (result.x ?? 0) + parentNodePosition.x, x: (result.x ?? 0) + parentNodePosition.x,
y: (result.y ?? 0) + parentNodePosition.y, y: (result.y ?? 0) + parentNodePosition.y,
@@ -130,8 +136,9 @@ function calculateXYZPosition<NodeType extends NodeBase>(
} }
export function updateNodeDimensions( export function updateNodeDimensions(
updates: NodeDimensionUpdate[], updates: Map<string, NodeDimensionUpdate>,
nodes: NodeBase[], nodes: NodeBase[],
nodesLookup: Map<string, NodeBase>,
domNode: HTMLElement | null, domNode: HTMLElement | null,
nodeOrigin?: NodeOrigin, nodeOrigin?: NodeOrigin,
onUpdate?: (id: string, dimensions: Dimensions) => void onUpdate?: (id: string, dimensions: Dimensions) => void
@@ -146,7 +153,8 @@ export function updateNodeDimensions(
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform); const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
const nextNodes = nodes.map((node) => { const nextNodes = nodes.map((node) => {
const update = updates.find((u) => u.id === node.id); const update = updates.get(node.id);
if (update) { if (update) {
const dimensions = getDimensions(update.nodeElement); const dimensions = getDimensions(update.nodeElement);
const doUpdate = !!( const doUpdate = !!(
@@ -158,7 +166,7 @@ export function updateNodeDimensions(
if (doUpdate) { if (doUpdate) {
onUpdate?.(node.id, dimensions); onUpdate?.(node.id, dimensions);
return { const newNode = {
...node, ...node,
...dimensions, ...dimensions,
[internalsSymbol]: { [internalsSymbol]: {
@@ -169,6 +177,10 @@ export function updateNodeDimensions(
}, },
}, },
}; };
nodesLookup.set(node.id, newNode);
return newNode;
} }
} }
+17 -12
View File
@@ -33,6 +33,7 @@ export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBa
type StoreItems = { type StoreItems = {
nodes: NodeBase[]; nodes: NodeBase[];
nodesLookup: Map<string, NodeBase>;
edges: EdgeBase[]; edges: EdgeBase[];
nodeExtent: CoordinateExtent; nodeExtent: CoordinateExtent;
snapGrid: SnapGrid; snapGrid: SnapGrid;
@@ -103,6 +104,7 @@ export function XYDrag({
function updateNodes({ x, y }: XYPosition) { function updateNodes({ x, y }: XYPosition) {
const { const {
nodes, nodes,
nodesLookup,
nodeExtent, nodeExtent,
snapGrid, snapGrid,
snapToGrid, snapToGrid,
@@ -163,11 +165,11 @@ export function XYDrag({
updateNodePositions(dragItems, true, true); updateNodePositions(dragItems, true, true);
const onNodeOrSelectionDrag = nodeId ? onNodeDrag : wrapSelectionDragFunc(onSelectionDrag); const onNodeOrSelectionDrag = nodeId ? onNodeDrag : wrapSelectionDragFunc(onSelectionDrag);
if (dragEvent) { if (dragEvent && (onDrag || onNodeOrSelectionDrag)) {
const [currentNode, currentNodes] = getEventHandlerParams({ const [currentNode, currentNodes] = getEventHandlerParams({
nodeId, nodeId,
dragItems, dragItems,
nodes, nodesLookup,
}); });
onDrag?.(dragEvent as MouseEvent, dragItems, currentNode, currentNodes); onDrag?.(dragEvent as MouseEvent, dragItems, currentNode, currentNodes);
onNodeOrSelectionDrag?.(dragEvent as MouseEvent, currentNode, currentNodes); onNodeOrSelectionDrag?.(dragEvent as MouseEvent, currentNode, currentNodes);
@@ -197,6 +199,7 @@ export function XYDrag({
function startDrag(event: UseDragEvent) { function startDrag(event: UseDragEvent) {
const { const {
nodes, nodes,
nodesLookup,
multiSelectionActive, multiSelectionActive,
nodesDraggable, nodesDraggable,
transform, transform,
@@ -227,11 +230,11 @@ export function XYDrag({
const onNodeOrSelectionDragStart = nodeId ? onNodeDragStart : wrapSelectionDragFunc(onSelectionDragStart); const onNodeOrSelectionDragStart = nodeId ? onNodeDragStart : wrapSelectionDragFunc(onSelectionDragStart);
if (dragItems) { if (dragItems && (onDragStart || onNodeOrSelectionDragStart)) {
const [currentNode, currentNodes] = getEventHandlerParams({ const [currentNode, currentNodes] = getEventHandlerParams({
nodeId, nodeId,
dragItems, dragItems,
nodes, nodesLookup,
}); });
onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes); onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
onNodeOrSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); onNodeOrSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
@@ -288,18 +291,20 @@ export function XYDrag({
cancelAnimationFrame(autoPanId); cancelAnimationFrame(autoPanId);
if (dragItems) { if (dragItems) {
const { nodes, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems(); const { nodesLookup, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems();
const onNodeOrSelectionDragStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop); const onNodeOrSelectionDragStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop);
updateNodePositions(dragItems, false, false); updateNodePositions(dragItems, false, false);
const [currentNode, currentNodes] = getEventHandlerParams({ if (onDragStop || onNodeOrSelectionDragStop) {
nodeId, const [currentNode, currentNodes] = getEventHandlerParams({
dragItems, nodeId,
nodes, dragItems,
}); nodesLookup,
onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes); });
onNodeOrSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes); onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
onNodeOrSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
}
} }
}) })
.filter((event: MouseEvent) => { .filter((event: MouseEvent) => {
+3 -3
View File
@@ -75,14 +75,14 @@ export function getDragItems<NodeType extends NodeBase>(
export function getEventHandlerParams<NodeType extends NodeBase>({ export function getEventHandlerParams<NodeType extends NodeBase>({
nodeId, nodeId,
dragItems, dragItems,
nodes, nodesLookup,
}: { }: {
nodeId?: string; nodeId?: string;
dragItems: NodeDragItem[]; dragItems: NodeDragItem[];
nodes: NodeType[]; nodesLookup: Map<string, NodeType>;
}): [NodeType, NodeType[]] { }): [NodeType, NodeType[]] {
const extentedDragItems: NodeType[] = dragItems.map((n) => { const extentedDragItems: NodeType[] = dragItems.map((n) => {
const node = nodes.find((node) => node.id === n.id)!; const node = nodesLookup.get(n.id)!;
return { return {
...node, ...node,