refactor(react): separate user nodes and indernal nodes

This commit is contained in:
moklick
2024-03-27 11:22:43 +01:00
parent d775012e1b
commit 844d574c4f
33 changed files with 374 additions and 315 deletions
@@ -3,7 +3,7 @@
import { memo, useEffect, useRef, type MouseEvent, useCallback, CSSProperties } from 'react';
import cc from 'classcat';
import { shallow } from 'zustand/shallow';
import { getNodesBounds, getBoundsOfRects, XYMinimap, type Rect, type XYMinimapInstance } from '@xyflow/system';
import { getInternalNodesBounds, getBoundsOfRects, XYMinimap, type Rect, type XYMinimapInstance } from '@xyflow/system';
import { useStore, useStoreApi } from '../../hooks/useStore';
import { Panel } from '../../components/Panel';
@@ -26,7 +26,9 @@ const selector = (s: ReactFlowState) => {
return {
viewBB,
boundingRect:
s.nodes.length > 0 ? getBoundsOfRects(getNodesBounds(s.nodes, { nodeOrigin: s.nodeOrigin }), viewBB) : viewBB,
s.nodeLookup.size > 0
? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup, { nodeOrigin: s.nodeOrigin }), viewBB)
: viewBB,
rfId: s.rfId,
nodeOrigin: s.nodeOrigin,
panZoom: s.panZoom,
@@ -1,23 +1,23 @@
import { useCallback, CSSProperties } from 'react';
import cc from 'classcat';
import { shallow } from 'zustand/shallow';
import { getNodesBounds, Rect, Position, internalsSymbol, getNodeToolbarTransform } from '@xyflow/system';
import { Rect, Position, getNodeToolbarTransform, getNodesBounds } from '@xyflow/system';
import { Node, ReactFlowState } from '../../types';
import { InternalNode, ReactFlowState } from '../../types';
import { useStore } from '../../hooks/useStore';
import { useNodeId } from '../../contexts/NodeIdContext';
import { NodeToolbarPortal } from './NodeToolbarPortal';
import type { NodeToolbarProps } from './types';
const nodeEqualityFn = (a?: Node, b?: Node) =>
a?.computed?.positionAbsolute?.x !== b?.computed?.positionAbsolute?.x ||
a?.computed?.positionAbsolute?.y !== b?.computed?.positionAbsolute?.y ||
a?.computed?.width !== b?.computed?.width ||
a?.computed?.height !== b?.computed?.height ||
const nodeEqualityFn = (a?: InternalNode, b?: InternalNode) =>
a?.internals.positionAbsolute.x !== b?.internals.positionAbsolute.x ||
a?.internals.positionAbsolute.y !== b?.internals.positionAbsolute.y ||
a?.computed.width !== b?.computed.width ||
a?.computed.height !== b?.computed.height ||
a?.selected !== b?.selected ||
a?.[internalsSymbol]?.z !== b?.[internalsSymbol]?.z;
a?.internals.z !== b?.internals.z;
const nodesEqualityFn = (a: Node[], b: Node[]) => {
const nodesEqualityFn = (a: InternalNode[], b: InternalNode[]) => {
if (a.length !== b.length) {
return false;
}
@@ -49,10 +49,10 @@ export function NodeToolbar({
const contextNodeId = useNodeId();
const nodesSelector = useCallback(
(state: ReactFlowState): Node[] => {
(state: ReactFlowState): InternalNode[] => {
const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId || ''];
return nodeIds.reduce<Node[]>((acc, id) => {
return nodeIds.reduce<InternalNode[]>((acc, id) => {
const node = state.nodeLookup.get(id);
if (node) {
acc.push(node);
@@ -74,7 +74,7 @@ export function NodeToolbar({
}
const nodeRect: Rect = getNodesBounds(nodes, { nodeOrigin });
const zIndex: number = Math.max(...nodes.map((node) => (node[internalsSymbol]?.z || 1) + 1));
const zIndex: number = Math.max(...nodes.map((node) => (node.internals?.z || 1) + 1));
const wrapperStyle: CSSProperties = {
position: 'absolute',
@@ -2,7 +2,6 @@ import { CSSProperties, useCallback } from 'react';
import { shallow } from 'zustand/shallow';
import cc from 'classcat';
import {
internalsSymbol,
Position,
ConnectionLineType,
ConnectionMode,
@@ -53,7 +52,7 @@ const ConnectionLine = ({
),
shallow
);
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
const fromHandleBounds = fromNode?.internals?.handleBounds;
let handleBounds = fromHandleBounds?.[handleType];
if (connectionMode === ConnectionMode.Loose) {
@@ -65,10 +64,10 @@ const ConnectionLine = ({
}
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 fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.computed?.height ?? 0;
const fromX = (fromNode.computed?.positionAbsolute?.x ?? 0) + fromHandleX;
const fromY = (fromNode.computed?.positionAbsolute?.y ?? 0) + fromHandleY;
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.computed.width ?? 0) / 2;
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.computed.height ?? 0;
const fromX = (fromNode.internals.positionAbsolute.x ?? 0) + fromHandleX;
const fromY = (fromNode.internals.positionAbsolute.y ?? 0) + fromHandleY;
const fromPosition = fromHandle?.position;
const toPosition = fromPosition ? oppositePosition[fromPosition] : null;
@@ -54,7 +54,7 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
onConnectStart,
onConnectEnd,
cancelConnection,
nodes,
nodeLookup,
rfId: flowId,
panBy,
updateConnection,
@@ -82,7 +82,7 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
domNode,
handleId,
nodeId,
nodes,
nodeLookup,
isTarget,
edgeUpdaterType: handleType,
lib,
@@ -126,7 +126,7 @@ function HandleComponent(
connectionMode: currentStore.connectionMode,
connectionRadius: currentStore.connectionRadius,
domNode: currentStore.domNode,
nodes: currentStore.nodes,
nodeLookup: currentStore.nodeLookup,
lib: currentStore.lib,
isTarget,
handleId,
@@ -7,7 +7,6 @@ import {
errorMessages,
getNodeDimensions,
getPositionWithOrigin,
internalsSymbol,
isInputDOMNode,
nodeHasDimensions,
} from '@xyflow/system';
@@ -19,7 +18,7 @@ import { useDrag } from '../../hooks/useDrag';
import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes';
import { handleNodeClick } from '../Nodes/utils';
import { arrowKeyDiffs, builtinNodeTypes, getNodeInlineStyleDimensions } from './utils';
import type { Node, NodeWrapperProps } from '../../types';
import type { InternalNode, Node, NodeWrapperProps } from '../../types';
export function NodeWrapper<NodeType extends Node>({
id,
@@ -44,11 +43,11 @@ export function NodeWrapper<NodeType extends Node>({
onError,
}: NodeWrapperProps<NodeType>) {
const { node, positionAbsoluteX, positionAbsoluteY, zIndex, isParent } = useStore((s) => {
const node = s.nodeLookup.get(id)! as NodeType;
const node = s.nodeLookup.get(id)! as InternalNode<NodeType>;
const positionAbsolute = nodeExtent
? clampPosition(node.computed?.positionAbsolute, nodeExtent)
: node.computed?.positionAbsolute || { x: 0, y: 0 };
? clampPosition(node.internals.positionAbsolute, nodeExtent)
: node.internals.positionAbsolute || { x: 0, y: 0 };
return {
node,
@@ -56,8 +55,8 @@ export function NodeWrapper<NodeType extends Node>({
// so we we need to force a re-render when some change
positionAbsoluteX: positionAbsolute.x,
positionAbsoluteY: positionAbsolute.y,
zIndex: node[internalsSymbol]?.z ?? 0,
isParent: !!node[internalsSymbol]?.isParent,
zIndex: node.internals.z,
isParent: node.internals.isParent,
};
}, shallow);
@@ -84,7 +83,7 @@ export function NodeWrapper<NodeType extends Node>({
const nodeDimensions = getNodeDimensions(node);
const inlineDimensions = getNodeInlineStyleDimensions(node);
const initialized = nodeHasDimensions(node);
const hasHandleBounds = !!node[internalsSymbol]?.handleBounds;
const hasHandleBounds = !!node.internals.handleBounds;
const moveSelectedNodes = useMoveSelectedNodes();
@@ -4,7 +4,7 @@ import { InputNode } from '../Nodes/InputNode';
import { DefaultNode } from '../Nodes/DefaultNode';
import { GroupNode } from '../Nodes/GroupNode';
import { OutputNode } from '../Nodes/OutputNode';
import type { Node, NodeTypes } from '../../types';
import type { InternalNode, Node, NodeTypes } from '../../types';
export const arrowKeyDiffs: Record<string, XYPosition> = {
ArrowUp: { x: 0, y: -1 },
@@ -21,12 +21,12 @@ export const builtinNodeTypes: NodeTypes = {
};
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
node: NodeType
node: InternalNode<NodeType>
): {
width: number | string | undefined;
height: number | string | undefined;
} {
if (!node.computed) {
if (node.internals.handleBounds === undefined) {
return {
width: node.width ?? node.initialWidth ?? node.style?.width,
height: node.height ?? node.initialHeight ?? node.style?.height,
@@ -11,7 +11,7 @@ import { useStore, useStoreApi } from '../../hooks/useStore';
import { useDrag } from '../../hooks/useDrag';
import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes';
import { arrowKeyDiffs } from '../NodeWrapper/utils';
import type { Node, ReactFlowState } from '../../types';
import type { InternalNode, Node, ReactFlowState } from '../../types';
export type NodesSelectionProps<NodeType> = {
onSelectionContextMenu?: (event: MouseEvent, nodes: NodeType[]) => void;
@@ -20,7 +20,13 @@ export type NodesSelectionProps<NodeType> = {
};
const selector = (s: ReactFlowState) => {
const selectedNodes = s.nodes.filter((n) => n.selected);
const selectedNodes: InternalNode[] = [];
for (const [, node] of s.nodeLookup) {
if (node.selected) {
selectedNodes.push(node);
}
}
const { width, height, x, y } = getNodesBounds(selectedNodes, { nodeOrigin: s.nodeOrigin });
return {
@@ -15,7 +15,7 @@ type SelectionListenerProps = {
};
const selector = (s: ReactFlowState) => ({
selectedNodes: s.nodes.filter((n) => n.selected),
selectedNodes: Array.from(s.nodeLookup.values()).filter((n) => n.selected),
selectedEdges: s.edges.filter((e) => e.selected),
});
+6 -6
View File
@@ -128,7 +128,7 @@ export function Pane({
};
const onMouseMove = (event: ReactMouseEvent): void => {
const { userSelectionRect, edges, transform, nodeOrigin, nodes, triggerNodeChanges, triggerEdgeChanges } =
const { userSelectionRect, edgeLookup, transform, nodeOrigin, nodeLookup, triggerNodeChanges, triggerEdgeChanges } =
store.getState();
if (!isSelecting || !containerBounds.current || !userSelectionRect) {
return;
@@ -149,7 +149,7 @@ export function Pane({
};
const selectedNodes = getNodesInside(
nodes,
nodeLookup,
nextUserSelectRect,
transform,
selectionMode === SelectionMode.Partial,
@@ -163,22 +163,22 @@ export function Pane({
for (const selectedNode of selectedNodes) {
selectedNodeIds.add(selectedNode.id);
for (const edge of edges) {
for (const [edgeId, edge] of edgeLookup) {
if (edge.source === selectedNode.id || edge.target === selectedNode.id) {
selectedEdgeIds.add(edge.id);
selectedEdgeIds.add(edgeId);
}
}
}
if (prevSelectedNodesCount.current !== selectedNodeIds.size) {
prevSelectedNodesCount.current = selectedNodeIds.size;
const changes = getSelectionChanges(nodes, selectedNodeIds, true) as NodeChange[];
const changes = getSelectionChanges(nodeLookup, selectedNodeIds, true) as NodeChange[];
triggerNodeChanges(changes);
}
if (prevSelectedEdgesCount.current !== selectedEdgeIds.size) {
prevSelectedEdgesCount.current = selectedEdgeIds.size;
const changes = getSelectionChanges(edges, selectedEdgeIds) as EdgeChange[];
const changes = getSelectionChanges(edgeLookup, selectedEdgeIds) as EdgeChange[];
triggerEdgeChanges(changes);
}
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { calculateNodePosition, snapPosition, type XYPosition } from '@xyflow/system';
import { Node } from '../types';
import { type Node } from '../types';
import { useStoreApi } from './useStore';
const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) =>
@@ -17,18 +17,11 @@ export function useMoveSelectedNodes() {
const store = useStoreApi();
const moveSelectedNodes = useCallback((params: { direction: XYPosition; factor: number }) => {
const {
nodeExtent,
nodes,
snapToGrid,
snapGrid,
nodesDraggable,
onError,
updateNodePositions,
nodeLookup,
nodeOrigin,
} = store.getState();
const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
const { nodeExtent, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions, nodeLookup, nodeOrigin } =
store.getState();
const nodeUpdates = [];
const isSelected = selectedAndDraggable(nodesDraggable);
// by default a node moves 5px on each key press
// if snap grid is enabled, we use that for the velocity
const xVelo = snapToGrid ? snapGrid[0] : 5;
@@ -37,32 +30,34 @@ export function useMoveSelectedNodes() {
const xDiff = params.direction.x * xVelo * params.factor;
const yDiff = params.direction.y * yVelo * params.factor;
const nodeUpdates = selectedNodes.map((node) => {
if (node.computed?.positionAbsolute) {
let nextPosition = {
x: node.computed.positionAbsolute.x + xDiff,
y: node.computed.positionAbsolute.y + yDiff,
};
if (snapToGrid) {
nextPosition = snapPosition(nextPosition, snapGrid);
}
const { position, positionAbsolute } = calculateNodePosition({
nodeId: node.id,
nextPosition,
nodeLookup,
nodeExtent,
nodeOrigin,
onError,
});
node.position = position;
node.computed.positionAbsolute = positionAbsolute;
for (const [, node] of nodeLookup) {
if (!isSelected(node)) {
continue;
}
return node;
});
let nextPosition = {
x: node.internals.positionAbsolute.x + xDiff,
y: node.internals.positionAbsolute.y + yDiff,
};
if (snapToGrid) {
nextPosition = snapPosition(nextPosition, snapGrid);
}
const { position, positionAbsolute } = calculateNodePosition({
nodeId: node.id,
nextPosition,
nodeLookup,
nodeExtent,
nodeOrigin,
onError,
});
node.position = position;
node.internals.positionAbsolute = positionAbsolute;
nodeUpdates.push(node);
}
updateNodePositions(nodeUpdates);
}, []);
@@ -1,5 +1,3 @@
import { internalsSymbol } from '@xyflow/system';
import { useStore } from './useStore';
import type { ReactFlowState } from '../types';
@@ -8,13 +6,13 @@ export type UseNodesInitializedOptions = {
};
const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) => {
if (s.nodes.length === 0) {
if (s.nodeLookup.size === 0) {
return false;
}
for (const node of s.nodes) {
for (const [, node] of s.nodeLookup) {
if (options.includeHiddenNodes || !node.hidden) {
if (node[internalsSymbol]?.handleBounds === undefined) {
if (node.internals.handleBounds === undefined) {
return false;
}
}
+5 -3
View File
@@ -32,7 +32,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
}, []);
const getNode = useCallback<Instance.GetNode<NodeType>>((id) => {
return store.getState().nodeLookup.get(id) as NodeType;
return store.getState().nodeLookup.get(id)?.internals.userProvidedNode as NodeType;
}, []);
const getEdges = useCallback<Instance.GetEdges<EdgeType>>(() => {
@@ -227,7 +227,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
const node =
isNode(nodeOrRect) && nodeHasDimensions(nodeOrRect)
? nodeOrRect
: (store.getState().nodeLookup.get(nodeOrRect.id) as NodeType);
: (store.getState().nodeLookup.get(nodeOrRect.id)?.internals.userProvidedNode as NodeType);
return node ? nodeToRect(node) : null;
}, []);
@@ -242,7 +242,9 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
}
return (nodes || store.getState().nodes).filter((n) => {
if (!isRect && (n.id === nodeOrRect!.id || !n.computed?.positionAbsolute)) {
const internalNode = store.getState().nodeLookup.get(n.id);
if (internalNode && !isRect && (n.id === nodeOrRect!.id || !internalNode.internals.positionAbsolute)) {
return false;
}
-1
View File
@@ -103,5 +103,4 @@ export {
addEdge,
updateEdge,
getConnectedEdges,
internalsSymbol,
} from '@xyflow/system';
+16 -18
View File
@@ -52,9 +52,9 @@ const createRFStore = ({
//
// When this happens, we take the note objects passed by the user and extend them with fields
// relevant for internal React Flow operations.
const nodesWithInternalData = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
set({ nodes: nodesWithInternalData });
set({ nodes });
},
setEdges: (edges: Edge[]) => {
const { connectionLookup, edgeLookup } = get();
@@ -80,9 +80,9 @@ const createRFStore = ({
// new dimensions and update the nodes.
updateNodeDimensions: (updates) => {
const {
nodes,
onNodesChange,
fitView,
nodes,
nodeLookup,
fitViewOnInit,
fitViewDone,
@@ -95,7 +95,6 @@ const createRFStore = ({
const updatedNodes = updateNodeDimensionsSystem(
updates,
nodes,
nodeLookup,
domNode,
nodeOrigin,
@@ -112,14 +111,14 @@ const createRFStore = ({
return;
}
const nextNodes = updateAbsolutePositions(updatedNodes, nodeLookup, nodeOrigin);
updateAbsolutePositions(nodeLookup, nodeOrigin);
// we call fitView once initially after all dimensions are set
let nextFitViewDone = fitViewDone;
if (!fitViewDone && fitViewOnInit) {
nextFitViewDone = fitView(nextNodes, {
nextFitViewDone = fitView({
...fitViewOnInitOptions,
nodes: fitViewOnInitOptions?.nodes || nextNodes,
nodes: fitViewOnInitOptions?.nodes || nodes,
});
}
@@ -128,7 +127,7 @@ const createRFStore = ({
// has not provided an onNodesChange handler.
// Nodes are only rendered if they have a width and height
// attribute which they get from this handler.
set({ nodes: nextNodes, fitViewDone: nextFitViewDone });
set({ nodes, fitViewDone: nextFitViewDone });
if (changes?.length > 0) {
if (debug) {
@@ -143,7 +142,6 @@ const createRFStore = ({
id: node.id,
type: 'position',
position: node.position,
positionAbsolute: node.computed?.positionAbsolute,
dragging,
};
@@ -185,7 +183,7 @@ const createRFStore = ({
}
},
addSelectedNodes: (selectedNodeIds) => {
const { multiSelectionActive, edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
const { multiSelectionActive, edgeLookup, nodeLookup, triggerNodeChanges, triggerEdgeChanges } = get();
if (multiSelectionActive) {
const nodeChanges = selectedNodeIds.map((nodeId) => createSelectionChange(nodeId, true));
@@ -193,11 +191,11 @@ const createRFStore = ({
return;
}
triggerNodeChanges(getSelectionChanges(nodes, new Set([...selectedNodeIds]), true));
triggerEdgeChanges(getSelectionChanges(edges));
triggerNodeChanges(getSelectionChanges(nodeLookup, new Set([...selectedNodeIds]), true));
triggerEdgeChanges(getSelectionChanges(edgeLookup));
},
addSelectedEdges: (selectedEdgeIds) => {
const { multiSelectionActive, edges, nodes, triggerNodeChanges, triggerEdgeChanges } = get();
const { multiSelectionActive, edgeLookup, nodeLookup, triggerNodeChanges, triggerEdgeChanges } = get();
if (multiSelectionActive) {
const changedEdges = selectedEdgeIds.map((edgeId) => createSelectionChange(edgeId, true));
@@ -205,8 +203,8 @@ const createRFStore = ({
return;
}
triggerEdgeChanges(getSelectionChanges(edges, new Set([...selectedEdgeIds])));
triggerNodeChanges(getSelectionChanges(nodes, new Set(), true));
triggerEdgeChanges(getSelectionChanges(edgeLookup, new Set([...selectedEdgeIds])));
triggerNodeChanges(getSelectionChanges(nodeLookup, new Set(), true));
},
unselectNodesAndEdges: ({ nodes, edges }: UnselectNodesAndEdgesParams = {}) => {
const { edges: storeEdges, nodes: storeNodes, triggerNodeChanges, triggerEdgeChanges } = get();
@@ -276,8 +274,8 @@ const createRFStore = ({
const { transform, width, height, panZoom, translateExtent } = get();
return panBySystem({ delta, panZoom, transform, translateExtent, width, height });
},
fitView: (nodes: Node[], options?: FitViewOptions): boolean => {
const { panZoom, width, height, minZoom, maxZoom, nodeOrigin } = get();
fitView: (options?: FitViewOptions): boolean => {
const { panZoom, width, height, minZoom, maxZoom, nodeOrigin, nodeLookup } = get();
if (!panZoom) {
return false;
@@ -285,7 +283,7 @@ const createRFStore = ({
return fitViewSystem(
{
nodes,
nodeLookup,
width,
height,
panZoom,
+3 -3
View File
@@ -35,7 +35,7 @@ const getInitialState = ({
const storeNodes = defaultNodes ?? nodes ?? [];
updateConnectionLookup(connectionLookup, edgeLookup, storeEdges);
const nextNodes = adoptUserProvidedNodes(storeNodes, nodeLookup, {
adoptUserProvidedNodes(storeNodes, nodeLookup, {
nodeOrigin: [0, 0],
elevateNodesOnSelect: false,
});
@@ -43,7 +43,7 @@ const getInitialState = ({
let transform: Transform = [0, 0, 1];
if (fitView && width && height) {
const nodesWithDimensions = nextNodes.filter(
const nodesWithDimensions = storeNodes.filter(
(node) => (node.width || node.initialWidth) && (node.height || node.initialHeight)
);
// @todo users nodeOrigin should be used here
@@ -57,7 +57,7 @@ const getInitialState = ({
width: 0,
height: 0,
transform,
nodes: nextNodes,
nodes: storeNodes,
nodeLookup,
edges: storeEdges,
edgeLookup,
+10 -1
View File
@@ -1,5 +1,12 @@
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
import type { CoordinateExtent, NodeBase, NodeOrigin, OnError, NodeProps as NodePropsBase } from '@xyflow/system';
import type {
CoordinateExtent,
NodeBase,
NodeOrigin,
OnError,
NodeProps as NodePropsBase,
InternalNodeBase,
} from '@xyflow/system';
import { NodeTypes } from './general';
@@ -17,6 +24,8 @@ export type Node<
focusable?: boolean;
};
export type InternalNode<NodeType extends Node = Node> = InternalNodeBase<NodeType>;
export type NodeMouseHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, node: NodeType) => void;
export type SelectionDragHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, nodes: NodeType[]) => void;
export type OnNodeDrag<NodeType extends Node = Node> = (
+3 -2
View File
@@ -44,6 +44,7 @@ import type {
OnBeforeDelete,
IsValidConnection,
EdgeChange,
InternalNode,
} from '.';
export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
@@ -52,7 +53,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
height: number;
transform: Transform;
nodes: NodeType[];
nodeLookup: NodeLookup<NodeType>;
nodeLookup: NodeLookup<InternalNode<NodeType>>;
edges: Edge[];
edgeLookup: EdgeLookup<EdgeType>;
connectionLookup: ConnectionLookup;
@@ -169,7 +170,7 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
triggerNodeChanges: (changes: NodeChange<NodeType>[]) => void;
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
panBy: PanBy;
fitView: (nodes: NodeType[], options?: FitViewOptions) => boolean;
fitView: (options?: FitViewOptions) => boolean;
};
export type ReactFlowState<NodeType extends Node = Node, EdgeType extends Edge = Edge> = ReactFlowStore<
+12 -9
View File
@@ -1,6 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { EdgeLookup, NodeLookup } from '@xyflow/system';
import type { Node, Edge, EdgeChange, NodeChange, NodeSelectionChange, EdgeSelectionChange } from '../types';
import type {
Node,
Edge,
EdgeChange,
NodeChange,
NodeSelectionChange,
EdgeSelectionChange,
InternalNode,
} from '../types';
export function handleParentExpand(updatedElements: any[], updateItem: any) {
for (const [index, item] of updatedElements.entries()) {
@@ -125,11 +133,6 @@ function applyChange(change: any, element: any, elements: any[] = []): any {
element.position = change.position;
}
if (typeof change.positionAbsolute !== 'undefined') {
element.computed ??= {};
element.computed.positionAbsolute = change.positionAbsolute;
}
if (typeof change.dragging !== 'undefined') {
element.dragging = change.dragging;
}
@@ -228,13 +231,13 @@ export function createSelectionChange(id: string, selected: boolean): NodeSelect
}
export function getSelectionChanges(
items: any[],
items: Map<string, any>,
selectedIds: Set<string> = new Set(),
mutateItem = false
): NodeSelectionChange[] | EdgeSelectionChange[] {
const changes: NodeSelectionChange[] | EdgeSelectionChange[] = [];
for (const item of items) {
for (const [, item] of items) {
const willBeSelected = selectedIds.has(item.id);
// we don't want to set all items to selected=false on the first selection
@@ -266,7 +269,7 @@ export function getElementsDiffChanges({
lookup,
}: {
items: Node[] | undefined;
lookup: NodeLookup<Node>;
lookup: NodeLookup<InternalNode<Node>>;
}): NodeChange[];
export function getElementsDiffChanges({
items,