refactor(updates): only trigger dim updates when dim changed

This commit is contained in:
moklick
2024-04-11 12:31:14 +02:00
parent 63aa0dd713
commit 4fa3a87241
14 changed files with 57 additions and 53 deletions
@@ -19,6 +19,7 @@ import {
import initialItems from './initial-elements'; import initialItems from './initial-elements';
import styles from './layouting.module.css'; import styles from './layouting.module.css';
import ReactFlowDevTools from '../DevTools/DevTools';
const dagreGraph = new dagre.graphlib.Graph(); const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({})); dagreGraph.setDefaultEdgeLabel(() => ({}));
@@ -98,6 +99,7 @@ const LayoutFlow = () => {
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
> >
<Controls /> <Controls />
<ReactFlowDevTools />
</ReactFlow> </ReactFlow>
<Panel position="top-right"> <Panel position="top-right">
<button onClick={() => onLayout('TB')}>vertical layout</button> <button onClick={() => onLayout('TB')}>vertical layout</button>
+3
View File
@@ -5,6 +5,9 @@
## Patch changes ## Patch changes
- fix hidden nodes - fix hidden nodes
- use `direction=ltr` for outer wrapper to support rtl sites
- allow pinch zoom even if `preventScrolling=false`
- export node and edge change related types
## 12.0.0-next.13 ## 12.0.0-next.13
@@ -123,7 +123,7 @@ export function NodeWrapper<NodeType extends Node>({
if (targetPosChanged) { if (targetPosChanged) {
prevTargetPosition.current = node.targetPosition; prevTargetPosition.current = node.targetPosition;
} }
store.getState().updateNodeDimensions(new Map([[id, { id, nodeElement: nodeRef.current, force: true }]])); store.getState().updateNodeInternals(new Map([[id, { id, nodeElement: nodeRef.current, force: true }]]));
} }
}, [id, nodeType, node.sourcePosition, node.targetPosition]); }, [id, nodeType, node.sourcePosition, node.targetPosition]);
@@ -2,12 +2,12 @@ import { useEffect, useMemo, useRef } from 'react';
import { ReactFlowState } from '../../types'; import { ReactFlowState } from '../../types';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { NodeDimensionUpdate } from '@xyflow/system'; import { InternalNodeUpdate } from '@xyflow/system';
const selector = (s: ReactFlowState) => s.updateNodeDimensions; const selector = (s: ReactFlowState) => s.updateNodeInternals;
export function useResizeObserver() { export function useResizeObserver() {
const updateNodeDimensions = useStore(selector); const updateNodeInternals = useStore(selector);
const resizeObserverRef = useRef<ResizeObserver>(); const resizeObserverRef = useRef<ResizeObserver>();
const resizeObserver = useMemo(() => { const resizeObserver = useMemo(() => {
@@ -16,7 +16,7 @@ export function useResizeObserver() {
} }
const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
const updates = new Map<string, NodeDimensionUpdate>(); const updates = new Map<string, InternalNodeUpdate>();
entries.forEach((entry: ResizeObserverEntry) => { entries.forEach((entry: ResizeObserverEntry) => {
const id = entry.target.getAttribute('data-id') as string; const id = entry.target.getAttribute('data-id') as string;
@@ -26,7 +26,7 @@ export function useResizeObserver() {
}); });
}); });
updateNodeDimensions(updates); updateNodeInternals(updates);
}); });
resizeObserverRef.current = observer; resizeObserverRef.current = observer;
@@ -1,5 +1,5 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { UpdateNodeInternals, NodeDimensionUpdate } from '@xyflow/system'; import type { UpdateNodeInternals, InternalNodeUpdate } from '@xyflow/system';
import { useStoreApi } from '../hooks/useStore'; import { useStoreApi } from '../hooks/useStore';
@@ -13,9 +13,9 @@ export function useUpdateNodeInternals(): UpdateNodeInternals {
const store = useStoreApi(); const store = useStoreApi();
return useCallback<UpdateNodeInternals>((id: string | string[]) => { return useCallback<UpdateNodeInternals>((id: string | string[]) => {
const { domNode, updateNodeDimensions } = store.getState(); const { domNode, updateNodeInternals } = store.getState();
const updateIds = Array.isArray(id) ? id : [id]; const updateIds = Array.isArray(id) ? id : [id];
const updates = new Map<string, NodeDimensionUpdate>(); const updates = new Map<string, InternalNodeUpdate>();
updateIds.forEach((updateId) => { updateIds.forEach((updateId) => {
const nodeElement = domNode?.querySelector(`.react-flow__node[data-id="${updateId}"]`) as HTMLDivElement; const nodeElement = domNode?.querySelector(`.react-flow__node[data-id="${updateId}"]`) as HTMLDivElement;
@@ -25,6 +25,6 @@ export function useUpdateNodeInternals(): UpdateNodeInternals {
} }
}); });
requestAnimationFrame(() => updateNodeDimensions(updates)); requestAnimationFrame(() => updateNodeInternals(updates));
}, []); }, []);
} }
+4 -4
View File
@@ -5,7 +5,7 @@ import {
adoptUserNodes, adoptUserNodes,
updateAbsolutePositions, updateAbsolutePositions,
panBy as panBySystem, panBy as panBySystem,
updateNodeDimensions as updateNodeDimensionsSystem, updateNodeInternals as updateNodeInternalsSystem,
updateConnectionLookup, updateConnectionLookup,
handleParentExpand, handleParentExpand,
NodeChange, NodeChange,
@@ -71,7 +71,7 @@ const createRFStore = ({
// Every node gets registerd at a ResizeObserver. Whenever a node // Every node gets registerd at a ResizeObserver. Whenever a node
// 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.
updateNodeDimensions: (updates) => { updateNodeInternals: (updates) => {
const { const {
onNodesChange, onNodesChange,
fitView, fitView,
@@ -84,9 +84,9 @@ const createRFStore = ({
debug, debug,
} = get(); } = get();
const changes = updateNodeDimensionsSystem(updates, nodeLookup, domNode, nodeOrigin); const { changes, updatedInternals } = updateNodeInternalsSystem(updates, nodeLookup, domNode, nodeOrigin);
if (changes.length === 0) { if (!updatedInternals) {
return; return;
} }
+2 -2
View File
@@ -2,7 +2,7 @@ import {
ConnectionMode, ConnectionMode,
type ConnectionStatus, type ConnectionStatus,
type CoordinateExtent, type CoordinateExtent,
type NodeDimensionUpdate, type InternalNodeUpdate,
type UpdateNodePositions, type UpdateNodePositions,
type NodeOrigin, type NodeOrigin,
type OnConnect, type OnConnect,
@@ -154,7 +154,7 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
setNodes: (nodes: NodeType[]) => void; setNodes: (nodes: NodeType[]) => void;
setEdges: (edges: EdgeType[]) => void; setEdges: (edges: EdgeType[]) => void;
setDefaultNodesAndEdges: (nodes?: NodeType[], edges?: EdgeType[]) => void; setDefaultNodesAndEdges: (nodes?: NodeType[], edges?: EdgeType[]) => void;
updateNodeDimensions: (updates: Map<string, NodeDimensionUpdate>) => void; updateNodeInternals: (updates: Map<string, InternalNodeUpdate>) => void;
updateNodePositions: UpdateNodePositions; updateNodePositions: UpdateNodePositions;
resetSelectedElements: () => void; resetSelectedElements: () => void;
unselectNodesAndEdges: (params?: UnselectNodesAndEdgesParams) => void; unselectNodesAndEdges: (params?: UnselectNodesAndEdgesParams) => void;
@@ -52,7 +52,7 @@
nodeDragThreshold, nodeDragThreshold,
selectNodesOnDrag, selectNodesOnDrag,
handleNodeSelection, handleNodeSelection,
updateNodeDimensions updateNodeInternals
} = store; } = store;
let nodeRef: HTMLDivElement; let nodeRef: HTMLDivElement;
@@ -97,7 +97,7 @@
if (doUpdate) { if (doUpdate) {
requestAnimationFrame(() => requestAnimationFrame(() =>
updateNodeDimensions( updateNodeInternals(
new Map([ new Map([
[ [
id, id,
@@ -10,7 +10,7 @@
nodesDraggable, nodesDraggable,
nodesConnectable, nodesConnectable,
elementsSelectable, elementsSelectable,
updateNodeDimensions updateNodeInternals
} = useStore(); } = useStore();
const resizeObserver: ResizeObserver | null = const resizeObserver: ResizeObserver | null =
@@ -29,7 +29,7 @@
}); });
}); });
updateNodeDimensions(updates); updateNodeInternals(updates);
}); });
onDestroy(() => { onDestroy(() => {
@@ -10,7 +10,7 @@ import { useStore } from '$lib/store';
* @returns function for updating node internals * @returns function for updating node internals
*/ */
export function useUpdateNodeInternals(): UpdateNodeInternals { export function useUpdateNodeInternals(): UpdateNodeInternals {
const { domNode, updateNodeDimensions } = useStore(); const { domNode, updateNodeInternals } = useStore();
// @todo: do we want to add this to system? // @todo: do we want to add this to system?
const updateInternals = (id: string | string[]) => { const updateInternals = (id: string | string[]) => {
@@ -27,7 +27,7 @@ export function useUpdateNodeInternals(): UpdateNodeInternals {
} }
}); });
requestAnimationFrame(() => updateNodeDimensions(updates)); requestAnimationFrame(() => updateNodeInternals(updates));
}; };
return updateInternals; return updateInternals;
+6 -6
View File
@@ -5,10 +5,10 @@ import {
fitView as fitViewUtil, fitView as fitViewUtil,
getElementsToRemove, getElementsToRemove,
panBy as panBySystem, panBy as panBySystem,
updateNodeDimensions as updateNodeDimensionsSystem, updateNodeInternals as updateNodeInternalsSystem,
addEdge as addEdgeUtil, addEdge as addEdgeUtil,
type UpdateNodePositions, type UpdateNodePositions,
type NodeDimensionUpdate, type InternalNodeUpdate,
type ViewportHelperFunctionOptions, type ViewportHelperFunctionOptions,
type Connection, type Connection,
type XYPosition, type XYPosition,
@@ -78,16 +78,16 @@ export function createStore({
store.nodes.update((nds) => nds); store.nodes.update((nds) => nds);
}; };
function updateNodeDimensions(updates: Map<string, NodeDimensionUpdate>) { function updateNodeInternals(updates: Map<string, InternalNodeUpdate>) {
const nodeLookup = get(store.nodeLookup); const nodeLookup = get(store.nodeLookup);
const changes = updateNodeDimensionsSystem( const { changes, updatedInternals } = updateNodeInternalsSystem(
updates, updates,
nodeLookup, nodeLookup,
get(store.domNode), get(store.domNode),
get(store.nodeOrigin) get(store.nodeOrigin)
); );
if (!changes) { if (!updatedInternals) {
return; return;
} }
@@ -400,7 +400,7 @@ export function createStore({
setEdgeTypes, setEdgeTypes,
addEdge, addEdge,
updateNodePositions, updateNodePositions,
updateNodeDimensions, updateNodeInternals,
zoomIn, zoomIn,
zoomOut, zoomOut,
fitView: (options?: FitViewOptions) => fitView(options), fitView: (options?: FitViewOptions) => fitView(options),
+2 -2
View File
@@ -1,6 +1,6 @@
import type { Writable } from 'svelte/store'; import type { Writable } from 'svelte/store';
import type { import type {
NodeDimensionUpdate, InternalNodeUpdate,
XYPosition, XYPosition,
ViewportHelperFunctionOptions, ViewportHelperFunctionOptions,
Connection, Connection,
@@ -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: Map<string, NodeDimensionUpdate>) => void; updateNodeInternals: (updates: Map<string, InternalNodeUpdate>) => 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;
+1 -1
View File
@@ -105,7 +105,7 @@ export type NodeHandleBounds = {
target: HandleElement[] | null; target: HandleElement[] | null;
}; };
export type NodeDimensionUpdate = { export type InternalNodeUpdate = {
id: string; id: string;
nodeElement: HTMLDivElement; nodeElement: HTMLDivElement;
force?: boolean; force?: boolean;
+21 -22
View File
@@ -1,7 +1,7 @@
import { import {
NodeBase, NodeBase,
CoordinateExtent, CoordinateExtent,
NodeDimensionUpdate, InternalNodeUpdate,
NodeOrigin, NodeOrigin,
PanZoomInstance, PanZoomInstance,
Transform, Transform,
@@ -11,7 +11,6 @@ import {
EdgeBase, EdgeBase,
EdgeLookup, EdgeLookup,
InternalNodeBase, InternalNodeBase,
NodeChange,
NodeLookup, NodeLookup,
Rect, Rect,
NodeDimensionChange, NodeDimensionChange,
@@ -213,16 +212,17 @@ export function handleParentExpand(
return changes; return changes;
} }
export function updateNodeDimensions<NodeType extends InternalNodeBase>( export function updateNodeInternals<NodeType extends InternalNodeBase>(
updates: Map<string, NodeDimensionUpdate>, updates: Map<string, InternalNodeUpdate>,
nodeLookup: Map<string, NodeType>, nodeLookup: Map<string, NodeType>,
domNode: HTMLElement | null, domNode: HTMLElement | null,
nodeOrigin?: NodeOrigin nodeOrigin?: NodeOrigin
): (NodeDimensionChange | NodePositionChange)[] { ): { changes: (NodeDimensionChange | NodePositionChange)[]; updatedInternals: boolean } {
const viewportNode = domNode?.querySelector('.xyflow__viewport'); const viewportNode = domNode?.querySelector('.xyflow__viewport');
let updatedInternals = false;
if (!viewportNode) { if (!viewportNode) {
return []; return { changes: [], updatedInternals };
} }
const changes: (NodeDimensionChange | NodePositionChange)[] = []; const changes: (NodeDimensionChange | NodePositionChange)[] = [];
@@ -242,24 +242,20 @@ export function updateNodeDimensions<NodeType extends InternalNodeBase>(
handleBounds: undefined, handleBounds: undefined,
}, },
}); });
updatedInternals = true;
} else if (node) { } else if (node) {
const dimensions = getDimensions(update.nodeElement); const dimensions = getDimensions(update.nodeElement);
const dimensionChanged = node.measured.width !== dimensions.width || node.measured.height !== dimensions.height;
const doUpdate = !!( const doUpdate = !!(
dimensions.width && dimensions.width &&
dimensions.height && dimensions.height &&
(node.measured?.width !== dimensions.width || (dimensionChanged || !node.internals.handleBounds || update.force)
node.measured?.height !== dimensions.height ||
!node.internals.handleBounds ||
update.force)
); );
if (doUpdate) { if (doUpdate) {
const newNode = { const newNode = {
...node, ...node,
measured: { measured: dimensions,
...node.measured,
...dimensions,
},
internals: { internals: {
...node.internals, ...node.internals,
handleBounds: { handleBounds: {
@@ -270,15 +266,18 @@ export function updateNodeDimensions<NodeType extends InternalNodeBase>(
}; };
nodeLookup.set(node.id, newNode); nodeLookup.set(node.id, newNode);
updatedInternals = true;
changes.push({ if (dimensionChanged) {
id: newNode.id, changes.push({
type: 'dimensions', id: newNode.id,
dimensions, type: 'dimensions',
}); dimensions,
});
if (newNode.expandParent) { if (newNode.expandParent) {
triggerChangeNodes.push(newNode); triggerChangeNodes.push(newNode);
}
} }
} }
} }
@@ -289,7 +288,7 @@ export function updateNodeDimensions<NodeType extends InternalNodeBase>(
changes.push(...parentExpandChanges); changes.push(...parentExpandChanges);
} }
return changes; return { changes, updatedInternals };
} }
export function panBy({ export function panBy({