refactor(nodes): rename computed to measured, add helpers

This commit is contained in:
moklick
2024-04-03 14:54:24 +02:00
parent f45f50671d
commit ea18e46a1a
32 changed files with 159 additions and 226 deletions
@@ -15,8 +15,7 @@ const initialNodes: Node[] = nodes.map((n) => ({
const expectedNodes: Node[] = initialNodes.map((n) => ({ const expectedNodes: Node[] = initialNodes.map((n) => ({
...n, ...n,
computed: { measured: {
positionAbsolute: n.position,
...nodeDimensions, ...nodeDimensions,
}, },
})); }));
@@ -48,7 +48,7 @@ describe('<ReactFlow />: onNodesChange', () => {
id: '1', id: '1',
item: { item: {
...nodes[0], ...nodes[0],
computed: { positionAbsolute: nodes[0].position, width: 200, height: 100 }, measured: { width: 200, height: 100 },
style: { width: 200, height: 100 }, style: { width: 200, height: 100 },
}, },
}, },
@@ -136,8 +136,8 @@ describe('applyChanges Testing', () => {
]; ];
const nextNodes = applyNodeChanges(nodeChanges, nodes); const nextNodes = applyNodeChanges(nodeChanges, nodes);
expect(nodes[0].computed).to.be.undefined; expect(nodes[0].measured).to.be.undefined;
expect(nextNodes[0].computed).to.be.deep.equal({ width: newWidth, height: newHeight }); expect(nextNodes[0].measured).to.be.deep.equal({ width: newWidth, height: newHeight });
expect(nextNodes[0].width).to.be.undefined; expect(nextNodes[0].width).to.be.undefined;
expect(nextNodes[0].height).to.be.undefined; expect(nextNodes[0].height).to.be.undefined;
}); });
@@ -153,7 +153,7 @@ describe('applyChanges Testing', () => {
const nextNodes = applyNodeChanges(nodeChanges, nodes); const nextNodes = applyNodeChanges(nodeChanges, nodes);
expect(nextNodes[0].position).to.be.deep.equal(newPosition); expect(nextNodes[0].position).to.be.deep.equal(newPosition);
expect(nextNodes[0].computed).to.be.deep.equal({ width: newWidth, height: newHeight }); expect(nextNodes[0].measured).to.be.deep.equal({ width: newWidth, height: newHeight });
}); });
it('replaces nodes/edges', () => { it('replaces nodes/edges', () => {
@@ -9,8 +9,8 @@ function getNodeIntersection(intersectionNode: Node, targetNode: Node) {
width: intersectionNodeWidth, width: intersectionNodeWidth,
height: intersectionNodeHeight, height: intersectionNodeHeight,
positionAbsolute: intersectionNodePosition, positionAbsolute: intersectionNodePosition,
} = intersectionNode.computed || {}; } = intersectionNode.measured || {};
const targetPosition = targetNode.computed?.positionAbsolute!; const targetPosition = targetNode.measured?.positionAbsolute!;
const w = intersectionNodeWidth! / 2; const w = intersectionNodeWidth! / 2;
const h = intersectionNodeHeight! / 2; const h = intersectionNodeHeight! / 2;
@@ -33,7 +33,7 @@ function getNodeIntersection(intersectionNode: Node, targetNode: Node) {
// returns the position (top,right,bottom or right) passed node compared to the intersection point // returns the position (top,right,bottom or right) passed node compared to the intersection point
function getEdgePosition(node: Node, intersectionPoint: XYPosition) { function getEdgePosition(node: Node, intersectionPoint: XYPosition) {
const n = { ...node.computed?.positionAbsolute, ...node }; const n = { ...node.measured?.positionAbsolute, ...node };
const nx = Math.round(n.x!); const nx = Math.round(n.x!);
const ny = Math.round(n.y!); const ny = Math.round(n.y!);
const px = Math.round(intersectionPoint.x); const px = Math.round(intersectionPoint.x);
@@ -42,13 +42,13 @@ function getEdgePosition(node: Node, intersectionPoint: XYPosition) {
if (px <= nx + 1) { if (px <= nx + 1) {
return Position.Left; return Position.Left;
} }
if (px >= nx + n.computed?.width! - 1) { if (px >= nx + n.measured?.width! - 1) {
return Position.Right; return Position.Right;
} }
if (py <= ny + 1) { if (py <= ny + 1) {
return Position.Top; return Position.Top;
} }
if (py >= n.y! + n.computed?.height! - 1) { if (py >= n.y! + n.measured?.height! - 1) {
return Position.Bottom; return Position.Bottom;
} }
@@ -6,7 +6,7 @@ function getNodeIntersection(intersectionNode: Node, targetNode: Node): XYPositi
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a // https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
const { position: intersectionNodePosition } = intersectionNode; const { position: intersectionNodePosition } = intersectionNode;
const { width: intersectionNodeWidth, height: intersectionNodeHeight } = intersectionNode.computed ?? { const { width: intersectionNodeWidth, height: intersectionNodeHeight } = intersectionNode.measured ?? {
width: 0, width: 0,
height: 0, height: 0,
}; };
@@ -42,13 +42,13 @@ function getEdgePosition(node: Node, intersectionPoint: XYPosition) {
if (px <= nx + 1) { if (px <= nx + 1) {
return Position.Left; return Position.Left;
} }
if (px >= nx + (n.computed?.width ?? 0) - 1) { if (px >= nx + (n.measured?.width ?? 0) - 1) {
return Position.Right; return Position.Right;
} }
if (py <= ny + 1) { if (py <= ny + 1) {
return Position.Top; return Position.Top;
} }
if (py >= n.y + (n.computed?.height ?? 0) - 1) { if (py >= n.y + (n.measured?.height ?? 0) - 1) {
return Position.Bottom; return Position.Bottom;
} }
@@ -6,7 +6,7 @@ import { shallow } from 'zustand/shallow';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { MiniMapNode } from './MiniMapNode'; import { MiniMapNode } from './MiniMapNode';
import type { ReactFlowState, Node } from '../../types'; import type { ReactFlowState, Node, InternalNode } from '../../types';
import type { MiniMapNodes as MiniMapNodesProps, GetMiniMapNodeAttribute, MiniMapNodeProps } from './types'; import type { MiniMapNodes as MiniMapNodesProps, GetMiniMapNodeAttribute, MiniMapNodeProps } from './types';
declare const window: any; declare const window: any;
@@ -85,7 +85,7 @@ function NodeComponentWrapperInner<NodeType extends Node>({
shapeRendering: string; shapeRendering: string;
}) { }) {
const { node, x, y } = useStore((s) => { const { node, x, y } = useStore((s) => {
const node = s.nodeLookup.get(id) as NodeType; const node = s.nodeLookup.get(id) as InternalNode<NodeType>;
const { x, y } = getNodePositionWithOrigin(node, node?.origin || nodeOrigin).positionAbsolute; const { x, y } = getNodePositionWithOrigin(node, node?.origin || nodeOrigin).positionAbsolute;
return { return {
@@ -5,12 +5,14 @@ import {
ResizeControlVariant, ResizeControlVariant,
type XYResizerInstance, type XYResizerInstance,
type XYResizerChange, type XYResizerChange,
XYResizerChildChange, type XYResizerChildChange,
type NodeChange,
type NodeDimensionChange,
type NodePositionChange,
} from '@xyflow/system'; } from '@xyflow/system';
import { useStoreApi } from '../../hooks/useStore'; import { useStoreApi } from '../../hooks/useStore';
import { useNodeId } from '../../contexts/NodeIdContext'; import { useNodeId } from '../../contexts/NodeIdContext';
import type { NodeChange, NodeDimensionChange, NodePositionChange } from '../../types';
import type { ResizeControlProps, ResizeControlLineProps } from './types'; import type { ResizeControlProps, ResizeControlLineProps } from './types';
function ResizeControl({ function ResizeControl({
@@ -12,8 +12,8 @@ import type { NodeToolbarProps } from './types';
const nodeEqualityFn = (a?: InternalNode, b?: InternalNode) => const nodeEqualityFn = (a?: InternalNode, b?: InternalNode) =>
a?.internals.positionAbsolute.x !== b?.internals.positionAbsolute.x || a?.internals.positionAbsolute.x !== b?.internals.positionAbsolute.x ||
a?.internals.positionAbsolute.y !== b?.internals.positionAbsolute.y || a?.internals.positionAbsolute.y !== b?.internals.positionAbsolute.y ||
a?.computed.width !== b?.computed.width || a?.measured.width !== b?.measured.width ||
a?.computed.height !== b?.computed.height || a?.measured.height !== b?.measured.height ||
a?.selected !== b?.selected || a?.selected !== b?.selected ||
a?.internals.z !== b?.internals.z; a?.internals.z !== b?.internals.z;
@@ -64,8 +64,8 @@ const ConnectionLine = ({
} }
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0]; const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0];
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.computed.width ?? 0) / 2; const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.measured.width ?? 0) / 2;
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.computed.height ?? 0; const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.measured.height ?? 0;
const fromX = (fromNode.internals.positionAbsolute.x ?? 0) + fromHandleX; const fromX = (fromNode.internals.positionAbsolute.x ?? 0) + fromHandleX;
const fromY = (fromNode.internals.positionAbsolute.y ?? 0) + fromHandleY; const fromY = (fromNode.internals.positionAbsolute.y ?? 0) + fromHandleY;
const fromPosition = fromHandle?.position; const fromPosition = fromHandle?.position;
+2 -2
View File
@@ -5,13 +5,13 @@
import { useRef, type MouseEvent as ReactMouseEvent, type ReactNode } from 'react'; import { useRef, type MouseEvent as ReactMouseEvent, type ReactNode } from 'react';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import cc from 'classcat'; import cc from 'classcat';
import { getNodesInside, getEventPosition, SelectionMode } from '@xyflow/system'; import { getNodesInside, getEventPosition, SelectionMode, type NodeChange, type EdgeChange } from '@xyflow/system';
import { UserSelection } from '../../components/UserSelection'; import { UserSelection } from '../../components/UserSelection';
import { containerStyle } from '../../styles/utils'; import { containerStyle } from '../../styles/utils';
import { useStore, useStoreApi } from '../../hooks/useStore'; import { useStore, useStoreApi } from '../../hooks/useStore';
import { getSelectionChanges } from '../../utils'; import { getSelectionChanges } from '../../utils';
import type { ReactFlowProps, ReactFlowState, NodeChange, EdgeChange } from '../../types'; import type { ReactFlowProps, ReactFlowState } from '../../types';
type PaneProps = { type PaneProps = {
isSelecting: boolean; isSelecting: boolean;
@@ -0,0 +1,21 @@
import { useCallback } from 'react';
import { shallow } from 'zustand/shallow';
import { useStore } from './useStore';
import type { InternalNode, Node } from '../types';
/**
* Hook for getting an internal node by id
*
* @public
* @param id - id of the node
* @returns array with visible node ids
*/
export function useInternalNode<NodeType extends Node = Node>(id: string): InternalNode<NodeType> | undefined {
const node = useStore(
useCallback((s) => s.nodeLookup.get(id) as InternalNode<NodeType> | undefined, [id]),
shallow
);
return node;
}
+5 -16
View File
@@ -1,12 +1,5 @@
import { useCallback, useMemo, useRef, useState } from 'react'; import { useCallback, useMemo, useRef, useState } from 'react';
import { import { getElementsToRemove, getOverlappingArea, isRectObject, nodeToRect, type Rect } from '@xyflow/system';
getElementsToRemove,
getOverlappingArea,
isRectObject,
nodeHasDimensions,
nodeToRect,
type Rect,
} from '@xyflow/system';
import useViewportHelper from './useViewportHelper'; import useViewportHelper from './useViewportHelper';
import { useStoreApi } from './useStore'; import { useStoreApi } from './useStore';
@@ -32,7 +25,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
}, []); }, []);
const getNode = useCallback<Instance.GetNode<NodeType>>((id) => { const getNode = useCallback<Instance.GetNode<NodeType>>((id) => {
return store.getState().nodeLookup.get(id)?.internals.userProvidedNode as NodeType; return store.getState().nodeLookup.get(id)?.internals.userNode as NodeType;
}, []); }, []);
const getEdges = useCallback<Instance.GetEdges<EdgeType>>(() => { const getEdges = useCallback<Instance.GetEdges<EdgeType>>(() => {
@@ -223,13 +216,9 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
[] []
); );
const getNodeRect = useCallback((nodeOrRect: NodeType | { id: NodeType['id'] }): Rect | null => { const getNodeRect = useCallback(({ id }: { id: string }): Rect | null => {
const node = const internalNode = store.getState().nodeLookup.get(id);
isNode(nodeOrRect) && nodeHasDimensions(nodeOrRect) return internalNode ? nodeToRect(internalNode) : null;
? nodeOrRect
: (store.getState().nodeLookup.get(nodeOrRect.id)?.internals.userProvidedNode as NodeType);
return node ? nodeToRect(node) : null;
}, []); }, []);
const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>( const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>(
@@ -48,12 +48,12 @@ const useViewportHelper = (): ViewportHelperFunctions => {
return { x, y, zoom }; return { x, y, zoom };
}, },
fitView: (options) => { fitView: (options) => {
const { nodes, width, height, nodeOrigin, minZoom, maxZoom, panZoom } = store.getState(); const { nodeLookup, width, height, nodeOrigin, minZoom, maxZoom, panZoom } = store.getState();
return panZoom return panZoom
? fitView( ? fitView(
{ {
nodes, nodeLookup,
width, width,
height, height,
nodeOrigin, nodeOrigin,
@@ -1,13 +1,13 @@
import { getNodesInside } from '@xyflow/system'; import { useCallback } from 'react';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { getNodesInside } from '@xyflow/system';
import { useStore } from './useStore'; import { useStore } from './useStore';
import type { Node, ReactFlowState } from '../types'; import type { Node, ReactFlowState } from '../types';
import { useCallback } from 'react';
const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => { const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
return onlyRenderVisible return onlyRenderVisible
? getNodesInside<Node>(s.nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true).map( ? getNodesInside<Node>(s.nodeLookup, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true).map(
(node) => node.id (node) => node.id
) )
: Array.from(s.nodeLookup.keys()); : Array.from(s.nodeLookup.keys());
+1
View File
@@ -26,6 +26,7 @@ export { useNodesInitialized, type UseNodesInitializedOptions } from './hooks/us
export { useHandleConnections } from './hooks/useHandleConnections'; export { useHandleConnections } from './hooks/useHandleConnections';
export { useNodesData } from './hooks/useNodesData'; export { useNodesData } from './hooks/useNodesData';
export { useConnection } from './hooks/useConnection'; export { useConnection } from './hooks/useConnection';
export { useInternalNode } from './hooks/useInternalNode';
export { useNodeId } from './contexts/NodeIdContext'; export { useNodeId } from './contexts/NodeIdContext';
export { applyNodeChanges, applyEdgeChanges } from './utils/changes'; export { applyNodeChanges, applyEdgeChanges } from './utils/changes';
+18 -24
View File
@@ -2,27 +2,20 @@ import { createWithEqualityFn } from 'zustand/traditional';
import { import {
clampPosition, clampPosition,
fitView as fitViewSystem, fitView as fitViewSystem,
adoptUserProvidedNodes, adoptUserNodes,
updateAbsolutePositions, updateAbsolutePositions,
panBy as panBySystem, panBy as panBySystem,
updateNodeDimensions as updateNodeDimensionsSystem, updateNodeDimensions as updateNodeDimensionsSystem,
updateConnectionLookup, updateConnectionLookup,
handleParentExpand, handleParentExpand,
NodeChange, NodeChange,
EdgeSelectionChange,
NodeSelectionChange,
} 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 { import type { ReactFlowState, Node, Edge, UnselectNodesAndEdgesParams, FitViewOptions, InternalNode } from '../types';
ReactFlowState,
Node,
Edge,
EdgeSelectionChange,
NodeSelectionChange,
UnselectNodesAndEdgesParams,
FitViewOptions,
InternalNode,
} from '../types';
const createRFStore = ({ const createRFStore = ({
nodes, nodes,
@@ -52,7 +45,7 @@ const createRFStore = ({
// //
// 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.
adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect }); adoptUserNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
set({ nodes }); set({ nodes });
}, },
@@ -263,21 +256,22 @@ const createRFStore = ({
triggerEdgeChanges(edgeChanges); triggerEdgeChanges(edgeChanges);
}, },
setNodeExtent: (nodeExtent) => { setNodeExtent: (nodeExtent) => {
const { nodes } = get(); const { nodeLookup } = get();
for (const [, node] of nodeLookup) {
const positionAbsolute = clampPosition(node.position, nodeExtent);
nodeLookup.set(node.id, {
...node,
internals: {
...node.internals,
positionAbsolute,
},
});
}
set({ set({
nodeExtent, nodeExtent,
nodes: nodes.map((node) => {
const positionAbsolute = clampPosition(node.position, nodeExtent);
return {
...node,
computed: {
...node.computed,
positionAbsolute,
},
};
}),
}); });
}, },
panBy: (delta): boolean => { panBy: (delta): boolean => {
+2 -2
View File
@@ -1,7 +1,7 @@
import { import {
infiniteExtent, infiniteExtent,
ConnectionMode, ConnectionMode,
adoptUserProvidedNodes, adoptUserNodes,
getNodesBounds, getNodesBounds,
getViewportForBounds, getViewportForBounds,
Transform, Transform,
@@ -35,7 +35,7 @@ const getInitialState = ({
const storeNodes = defaultNodes ?? nodes ?? []; const storeNodes = defaultNodes ?? nodes ?? [];
updateConnectionLookup(connectionLookup, edgeLookup, storeEdges); updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
adoptUserProvidedNodes(storeNodes, nodeLookup, { adoptUserNodes(storeNodes, nodeLookup, {
nodeOrigin: [0, 0], nodeOrigin: [0, 0],
elevateNodesOnSelect: false, elevateNodesOnSelect: false,
}); });
-71
View File
@@ -1,71 +0,0 @@
import type { XYPosition, Dimensions } from '@xyflow/system';
import type { Node, Edge } from '.';
export type NodeDimensionChange = {
id: string;
type: 'dimensions';
dimensions?: Dimensions;
resizing?: boolean;
};
export type NodePositionChange = {
id: string;
type: 'position';
position?: XYPosition;
positionAbsolute?: XYPosition;
dragging?: boolean;
};
export type NodeSelectionChange = {
id: string;
type: 'select';
selected: boolean;
};
export type NodeRemoveChange = {
id: string;
type: 'remove';
};
export type NodeAddChange<NodeType extends Node = Node> = {
item: NodeType;
type: 'add';
};
export type NodeReplaceChange<NodeType extends Node = Node> = {
id: string;
item: NodeType;
type: 'replace';
};
/**
* Union type of all possible node changes.
* @public
*/
export type NodeChange<NodeType extends Node = Node> =
| NodeDimensionChange
| NodePositionChange
| NodeSelectionChange
| NodeRemoveChange
| NodeAddChange<NodeType>
| NodeReplaceChange<NodeType>;
export type EdgeSelectionChange = NodeSelectionChange;
export type EdgeRemoveChange = NodeRemoveChange;
export type EdgeAddChange<EdgeType extends Edge = Edge> = {
item: EdgeType;
type: 'add';
};
export type EdgeReplaceChange<EdgeType extends Edge = Edge> = {
id: string;
item: EdgeType;
type: 'replace';
};
export type EdgeChange<EdgeType extends Edge = Edge> =
| EdgeSelectionChange
| EdgeRemoveChange
| EdgeAddChange<EdgeType>
| EdgeReplaceChange<EdgeType>;
+3 -1
View File
@@ -12,9 +12,11 @@ import {
XYPosition, XYPosition,
OnBeforeDeleteBase, OnBeforeDeleteBase,
Connection, Connection,
NodeChange,
EdgeChange,
} from '@xyflow/system'; } from '@xyflow/system';
import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.'; import type { Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.';
export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void; export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void;
export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void; export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void;
-1
View File
@@ -1,6 +1,5 @@
export * from './nodes'; export * from './nodes';
export * from './edges'; export * from './edges';
export * from './changes';
export * from './component-props'; export * from './component-props';
export * from './general'; export * from './general';
export * from './store'; export * from './store';
+2 -2
View File
@@ -25,12 +25,13 @@ import {
type EdgeLookup, type EdgeLookup,
type ConnectionLookup, type ConnectionLookup,
type NodeLookup, type NodeLookup,
NodeChange,
EdgeChange,
} from '@xyflow/system'; } from '@xyflow/system';
import type { import type {
Edge, Edge,
Node, Node,
NodeChange,
OnNodesChange, OnNodesChange,
OnEdgesChange, OnEdgesChange,
DefaultEdgeOptions, DefaultEdgeOptions,
@@ -43,7 +44,6 @@ import type {
OnNodeDrag, OnNodeDrag,
OnBeforeDelete, OnBeforeDelete,
IsValidConnection, IsValidConnection,
EdgeChange,
InternalNode, InternalNode,
} from '.'; } from '.';
+8 -9
View File
@@ -1,14 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { EdgeLookup, NodeLookup } from '@xyflow/system'; import {
import type { EdgeLookup,
Node, NodeLookup,
Edge,
EdgeChange, EdgeChange,
NodeChange, NodeChange,
NodeSelectionChange, NodeSelectionChange,
EdgeSelectionChange, EdgeSelectionChange,
InternalNode, } from '@xyflow/system';
} from '../types'; import type { Node, Edge, InternalNode } from '../types';
// This function applies changes to nodes or edges that are triggered by React Flow internally. // This function applies changes to nodes or edges that are triggered by React Flow internally.
// When you drag a node for example, React Flow will send a position change update. // When you drag a node for example, React Flow will send a position change update.
@@ -97,9 +96,9 @@ function applyChange(change: any, element: any): any {
case 'dimensions': { case 'dimensions': {
if (typeof change.dimensions !== 'undefined') { if (typeof change.dimensions !== 'undefined') {
element.computed ??= {}; element.measured ??= {};
element.computed.width = change.dimensions.width; element.measured.width = change.dimensions.width;
element.computed.height = change.dimensions.height; element.measured.height = change.dimensions.height;
if (change.resizing) { if (change.resizing) {
element.width = change.dimensions.width; element.width = change.dimensions.width;
+2 -2
View File
@@ -7,7 +7,7 @@ import {
type Writable type Writable
} from 'svelte/store'; } from 'svelte/store';
import { import {
adoptUserProvidedNodes, adoptUserNodes,
updateConnectionLookup, updateConnectionLookup,
type Viewport, type Viewport,
type PanZoomInstance, type PanZoomInstance,
@@ -141,7 +141,7 @@ export const createNodesStore = (
let elevateNodesOnSelect = true; let elevateNodesOnSelect = true;
const _set = (nds: Node[]): Node[] => { const _set = (nds: Node[]): Node[] => {
const nextNodes = adoptUserProvidedNodes(nds, nodeLookup, { const nextNodes = adoptUserNodes(nds, nodeLookup, {
elevateNodesOnSelect, elevateNodesOnSelect,
defaults defaults
}); });
+1 -1
View File
@@ -127,7 +127,7 @@ export type SelectionRect = Rect & {
export type OnError = (id: string, message: string) => void; export type OnError = (id: string, message: string) => void;
export type UpdateNodePositions = (dragItems: NodeDragItem[] | NodeBase[], dragging?: boolean) => void; export type UpdateNodePositions = (dragItems: NodeDragItem[] | InternalNodeBase[], dragging?: boolean) => void;
export type PanBy = (delta: XYPosition) => boolean; export type PanBy = (delta: XYPosition) => boolean;
export type UpdateConnection = (params: { export type UpdateConnection = (params: {
+6 -8
View File
@@ -59,27 +59,25 @@ export type NodeBase<
*/ */
origin?: NodeOrigin; origin?: NodeOrigin;
handles?: NodeHandle[]; handles?: NodeHandle[];
computed?: { measured?: {
width?: number; width?: number;
height?: number; height?: number;
}; };
}; };
export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType & { export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType & {
computed: { measured: {
width?: number; width?: number;
height?: number; height?: number;
}; };
// Only used internally
internals: { internals: {
positionAbsolute: XYPosition; positionAbsolute: XYPosition;
z: number; z: number;
// @todo should we rename this to "handles" and use same type as node.handles? // @todo should we rename this to "handles" and use same type as node.handles?
isParent: boolean; isParent: boolean;
/** Holds a reference to the original node object provided by the user /** Holds a reference to the original node object provided by the user.
* (which may lack some fields, like `computed` or `[internalSymbol]`. Used * Used as an optimization to avoid certain operations. */
* as an optimization to avoid certain operations. */ userNode: NodeType;
userProvidedNode: NodeType;
handleBounds?: NodeHandleBounds; handleBounds?: NodeHandleBounds;
}; };
}; };
@@ -123,7 +121,7 @@ export type NodeDragItem = {
position: XYPosition; position: XYPosition;
// distance from the mouse cursor to the node when start dragging // distance from the mouse cursor to the node when start dragging
distance: XYPosition; distance: XYPosition;
computed: { measured: {
width: number | null; width: number | null;
height: number | null; height: number | null;
}; };
+1 -1
View File
@@ -20,7 +20,7 @@ function isNodeInitialized(node: InternalNodeBase): boolean {
return ( return (
node && node &&
!!(node.internals.handleBounds || node.handles?.length) && !!(node.internals.handleBounds || node.handles?.length) &&
!!(node.computed.width || node.width || node.initialWidth) !!(node.measured.width || node.width || node.initialWidth)
); );
} }
+10 -10
View File
@@ -66,23 +66,23 @@ export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({
height: y2 - y, height: y2 - y,
}); });
export const nodeToRect = (node: InternalNodeBase, nodeOrigin: NodeOrigin = [0, 0]): Rect => { export const nodeToRect = (node: InternalNodeBase | NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Rect => {
const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin); const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
return { return {
...positionAbsolute, ...positionAbsolute,
width: node.computed?.width ?? node.width ?? 0, width: node.measured?.width ?? node.width ?? 0,
height: node.computed?.height ?? node.height ?? 0, height: node.measured?.height ?? node.height ?? 0,
}; };
}; };
export const nodeToBox = (node: InternalNodeBase, nodeOrigin: NodeOrigin = [0, 0]): Box => { export const nodeToBox = (node: InternalNodeBase | NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Box => {
const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin); const { positionAbsolute } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
return { return {
...positionAbsolute, ...positionAbsolute,
x2: positionAbsolute.x + (node.computed?.width ?? node.width ?? 0), x2: positionAbsolute.x + (node.measured?.width ?? node.width ?? 0),
y2: positionAbsolute.y + (node.computed?.height ?? node.height ?? 0), y2: positionAbsolute.y + (node.measured?.height ?? node.height ?? 0),
}; };
}; };
@@ -208,14 +208,14 @@ export function getNodeDimensions<NodeType extends NodeBase = NodeBase>(
node: NodeType node: NodeType
): { width: number; height: number } { ): { width: number; height: number } {
return { return {
width: node.computed?.width ?? node.width ?? node.initialWidth ?? 0, width: node.measured?.width ?? node.width ?? node.initialWidth ?? 0,
height: node.computed?.height ?? node.height ?? node.initialHeight ?? 0, height: node.measured?.height ?? node.height ?? node.initialHeight ?? 0,
}; };
} }
export function nodeHasDimensions<NodeType extends NodeBase = NodeBase>(node: NodeType): boolean { export function nodeHasDimensions<NodeType extends NodeBase = NodeBase>(node: NodeType): boolean {
return ( return (
(node.computed?.width ?? node.width ?? node.initialWidth) !== undefined && (node.measured?.width ?? node.width ?? node.initialWidth) !== undefined &&
(node.computed?.height ?? node.height ?? node.initialHeight) !== undefined (node.measured?.height ?? node.height ?? node.initialHeight) !== undefined
); );
} }
+18 -16
View File
@@ -50,7 +50,7 @@ export const isNodeBase = <NodeType extends NodeBase = NodeBase>(element: any):
export const isInternalNodeBase = <NodeType extends InternalNodeBase = InternalNodeBase>( export const isInternalNodeBase = <NodeType extends InternalNodeBase = InternalNodeBase>(
element: any element: any
): element is NodeType => 'id' in element && 'computed' in element && !('source' in element) && !('target' in element); ): element is NodeType => 'id' in element && 'internals' in element && !('source' in element) && !('target' in element);
/** /**
* Pass in a node, and get connected nodes where edge.source === node.id * Pass in a node, and get connected nodes where edge.source === node.id
@@ -106,7 +106,7 @@ export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType exten
}; };
export const getNodePositionWithOrigin = ( export const getNodePositionWithOrigin = (
node: InternalNodeBase | undefined, node: InternalNodeBase | NodeBase | undefined,
nodeOrigin: NodeOrigin = [0, 0] nodeOrigin: NodeOrigin = [0, 0]
): { position: XYPosition; positionAbsolute: XYPosition } => { ): { position: XYPosition; positionAbsolute: XYPosition } => {
if (!node) { if (!node) {
@@ -133,10 +133,13 @@ export const getNodePositionWithOrigin = (
return { return {
position, position,
positionAbsolute: { positionAbsolute:
x: node.internals.positionAbsolute.x - offsetX, 'internals' in node
y: node.internals.positionAbsolute.y - offsetY, ? {
}, x: node.internals.positionAbsolute.x - offsetX,
y: node.internals.positionAbsolute.y - offsetY,
}
: position,
}; };
}; };
@@ -165,7 +168,6 @@ export const getNodesBounds = (
const box = nodes.reduce( const box = nodes.reduce(
(currBox, node) => { (currBox, node) => {
// @ts-expect-error
const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin); const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin);
return getBoundsOfBoxes( return getBoundsOfBoxes(
currBox, currBox,
@@ -238,9 +240,9 @@ export const getNodesInside = <NodeType extends NodeBase = NodeBase>(
const visibleNodes: NodeType[] = []; const visibleNodes: NodeType[] = [];
for (const [, node] of nodeLookup) { for (const [, node] of nodeLookup) {
const { computed, selectable = true, hidden = false } = node; const { measured, selectable = true, hidden = false } = node;
const width = computed?.width ?? node.width ?? node.initialWidth ?? null; const width = measured.width ?? node.width ?? node.initialWidth ?? null;
const height = computed?.height ?? node.height ?? node.initialHeight ?? null; const height = measured.height ?? node.height ?? node.initialHeight ?? null;
if ((excludeNonSelectableNodes && !selectable) || hidden) { if ((excludeNonSelectableNodes && !selectable) || hidden) {
continue; continue;
@@ -286,7 +288,7 @@ export function fitView<Params extends FitViewParamsBase<NodeBase>, Options exte
const filteredNodes: InternalNodeBase[] = []; const filteredNodes: InternalNodeBase[] = [];
nodeLookup.forEach((n) => { nodeLookup.forEach((n) => {
const isVisible = n.computed?.width && n.computed?.height && (options?.includeHiddenNodes || !n.hidden); const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden);
if ( if (
isVisible && isVisible &&
@@ -331,7 +333,7 @@ function clampNodeExtent<NodeType extends NodeBase>(
if (!extent || extent === 'parent') { if (!extent || extent === 'parent') {
return extent; return extent;
} }
return [extent[0], [extent[1][0] - (node.computed?.width ?? 0), extent[1][1] - (node.computed?.height ?? 0)]]; return [extent[0], [extent[1][0] - (node.measured?.width ?? 0), extent[1][1] - (node.measured?.height ?? 0)]];
} }
/** /**
@@ -367,10 +369,10 @@ export function calculateNodePosition<NodeType extends NodeBase>({
if (!parentNode) { if (!parentNode) {
onError?.('005', errorMessages['error005']()); onError?.('005', errorMessages['error005']());
} else { } else {
const nodeWidth = node.computed.width; const nodeWidth = node.measured.width;
const nodeHeight = node.computed.height; const nodeHeight = node.measured.height;
const parentWidth = parentNode.computed.width; const parentWidth = parentNode.measured.width;
const parentHeight = parentNode.computed.height; const parentHeight = parentNode.measured.height;
if (nodeWidth && nodeHeight && parentWidth && parentHeight) { if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
const currNodeOrigin = node.origin || nodeOrigin; const currNodeOrigin = node.origin || nodeOrigin;
+19 -21
View File
@@ -70,7 +70,7 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
defaults?: Partial<NodeType>; defaults?: Partial<NodeType>;
}; };
export function adoptUserProvidedNodes<NodeType extends NodeBase>( export function adoptUserNodes<NodeType extends NodeBase>(
nodes: NodeType[], nodes: NodeType[],
nodeLookup: Map<string, InternalNodeBase<NodeType>>, nodeLookup: Map<string, InternalNodeBase<NodeType>>,
options: UpdateNodesOptions<NodeType> = { options: UpdateNodesOptions<NodeType> = {
@@ -84,33 +84,31 @@ export function adoptUserProvidedNodes<NodeType extends NodeBase>(
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0; const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
const parentNodeIds = new Set<string>(); const parentNodeIds = new Set<string>();
nodes.forEach((n) => { nodes.forEach((userNode) => {
const currentStoreNode = tmpLookup.get(n.id); const currentStoreNode = tmpLookup.get(userNode.id);
if (n.parentNode) { if (userNode.parentNode) {
parentNodeIds.add(n.parentNode); parentNodeIds.add(userNode.parentNode);
} }
if (n === currentStoreNode?.internals.userProvidedNode) { if (userNode === currentStoreNode?.internals.userNode) {
nodeLookup.set(n.id, currentStoreNode); nodeLookup.set(userNode.id, currentStoreNode);
} else { } else {
const node: InternalNodeBase<NodeType> = { nodeLookup.set(userNode.id, {
...options.defaults, ...options.defaults,
...n, ...userNode,
computed: { measured: {
width: n.computed?.width, width: userNode.measured?.width,
height: n.computed?.height, height: userNode.measured?.height,
}, },
internals: { internals: {
positionAbsolute: n.position, positionAbsolute: userNode.position,
handleBounds: currentStoreNode?.internals?.handleBounds, handleBounds: currentStoreNode?.internals?.handleBounds,
z: (isNumeric(n.zIndex) ? n.zIndex : 0) + (n.selected ? selectedNodeZ : 0), z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0),
userProvidedNode: n, userNode,
isParent: false, isParent: false,
}, },
}; });
nodeLookup.set(node.id, node);
} }
}); });
@@ -241,14 +239,14 @@ export function updateNodeDimensions<NodeType extends InternalNodeBase>(
const doUpdate = !!( const doUpdate = !!(
dimensions.width && dimensions.width &&
dimensions.height && dimensions.height &&
(node.computed?.width !== dimensions.width || node.computed?.height !== dimensions.height || update.force) (node.measured?.width !== dimensions.width || node.measured?.height !== dimensions.height || update.force)
); );
if (doUpdate) { if (doUpdate) {
const newNode = { const newNode = {
...node, ...node,
computed: { measured: {
...node.computed, ...node.measured,
...dimensions, ...dimensions,
}, },
internals: { internals: {
+2 -2
View File
@@ -139,10 +139,10 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
if (dragItems.length > 1 && nodeExtent && !n.extent) { if (dragItems.length > 1 && nodeExtent && !n.extent) {
const { positionAbsolute } = n.internals; const { positionAbsolute } = n.internals;
const x1 = positionAbsolute.x - nodesBox.x + nodeExtent[0][0]; const x1 = positionAbsolute.x - nodesBox.x + nodeExtent[0][0];
const x2 = positionAbsolute.x + (n.computed?.width ?? 0) - nodesBox.x2 + nodeExtent[1][0]; const x2 = positionAbsolute.x + (n.measured?.width ?? 0) - nodesBox.x2 + nodeExtent[1][0];
const y1 = positionAbsolute.y - nodesBox.y + nodeExtent[0][1]; const y1 = positionAbsolute.y - nodesBox.y + nodeExtent[0][1];
const y2 = positionAbsolute.y + (n.computed?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1]; const y2 = positionAbsolute.y + (n.measured?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
adjustedNodeExtent = [ adjustedNodeExtent = [
[x1, y1], [x1, y1],
+5 -5
View File
@@ -65,9 +65,9 @@ export function getDragItems<NodeType extends NodeBase>(
internals: { internals: {
positionAbsolute: internalNode.internals.positionAbsolute || { x: 0, y: 0 }, positionAbsolute: internalNode.internals.positionAbsolute || { x: 0, y: 0 },
}, },
computed: { measured: {
width: internalNode.computed.width || 0, width: internalNode.measured.width || 0,
height: internalNode.computed.height || 0, height: internalNode.measured.height || 0,
}, },
}); });
} }
@@ -93,8 +93,8 @@ export function getEventHandlerParams<NodeType extends NodeBase>({
return { return {
...node, ...node,
position: n.position, position: n.position,
computed: { measured: {
...n.computed, ...n.measured,
}, },
}; };
}); });
+5 -5
View File
@@ -71,15 +71,15 @@ export type XYResizerInstance = {
function nodeToParentExtent(node: NodeBase): CoordinateExtent { function nodeToParentExtent(node: NodeBase): CoordinateExtent {
return [ return [
[0, 0], [0, 0],
[node.computed!.width!, node.computed!.height!], [node.measured!.width!, node.measured!.height!],
]; ];
} }
function nodeToChildExtent(child: NodeBase, parent: NodeBase, nodeOrigin: NodeOrigin): CoordinateExtent { function nodeToChildExtent(child: NodeBase, parent: NodeBase, nodeOrigin: NodeOrigin): CoordinateExtent {
const x = parent.position.x + child.position.x; const x = parent.position.x + child.position.x;
const y = parent.position.y + child.position.y; const y = parent.position.y + child.position.y;
const width = child.computed!.width! ?? 0; const width = child.measured!.width! ?? 0;
const height = child.computed!.height! ?? 0; const height = child.measured!.height! ?? 0;
const originOffsetX = nodeOrigin[0] * width; const originOffsetX = nodeOrigin[0] * width;
const originOffsetY = nodeOrigin[1] * height; const originOffsetY = nodeOrigin[1] * height;
@@ -121,8 +121,8 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
prevValues = { prevValues = {
width: node.computed?.width ?? 0, width: node.measured?.width ?? 0,
height: node.computed?.height ?? 0, height: node.measured?.height ?? 0,
x: node.position.x ?? 0, x: node.position.x ?? 0,
y: node.position.y ?? 0, y: node.position.y ?? 0,
}; };