Merge pull request #4105 from xyflow/refactor/internal-nodes

React Flow 12: separate user nodes and internal nodes
This commit is contained in:
Moritz Klack
2024-04-09 13:33:25 +02:00
committed by GitHub
57 changed files with 722 additions and 582 deletions
@@ -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;
}
@@ -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;
}
}
+23 -23
View File
@@ -1,16 +1,9 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import {
getElementsToRemove,
getOverlappingArea,
isRectObject,
nodeHasDimensions,
nodeToRect,
type Rect,
} from '@xyflow/system';
import { getElementsToRemove, getOverlappingArea, isRectObject, nodeToRect, type Rect } from '@xyflow/system';
import useViewportHelper from './useViewportHelper';
import { useStoreApi } from './useStore';
import type { ReactFlowInstance, Instance, Node, Edge } from '../types';
import type { ReactFlowInstance, Instance, Node, Edge, InternalNode } from '../types';
import { getElementsDiffChanges, isNode } from '../utils';
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
@@ -27,13 +20,20 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
const viewportHelper = useViewportHelper();
const store = useStoreApi();
const getNodes = useCallback<Instance.GetNodes<NodeType>>(() => {
return store.getState().nodes.map((n) => ({ ...n })) as NodeType[];
}, []);
const getNodes = useCallback<Instance.GetNodes<NodeType>>(
() => store.getState().nodes.map((n) => ({ ...n })) as NodeType[],
[]
);
const getNode = useCallback<Instance.GetNode<NodeType>>((id) => {
return store.getState().nodeLookup.get(id) as NodeType;
}, []);
const getInternalNode = useCallback<Instance.GetInternalNode<NodeType>>(
(id) => store.getState().nodeLookup.get(id) as InternalNode<NodeType>,
[]
);
const getNode = useCallback<Instance.GetNode<NodeType>>(
(id) => getInternalNode(id)?.internals.userNode as NodeType,
[getInternalNode]
);
const getEdges = useCallback<Instance.GetEdges<EdgeType>>(() => {
const { edges = [] } = store.getState();
@@ -223,13 +223,9 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
[]
);
const getNodeRect = useCallback((nodeOrRect: NodeType | { id: NodeType['id'] }): Rect | null => {
const node =
isNode(nodeOrRect) && nodeHasDimensions(nodeOrRect)
? nodeOrRect
: (store.getState().nodeLookup.get(nodeOrRect.id) as NodeType);
return node ? nodeToRect(node) : null;
const getNodeRect = useCallback(({ id }: { id: string }): Rect | null => {
const internalNode = store.getState().nodeLookup.get(id);
return internalNode ? nodeToRect(internalNode) : null;
}, []);
const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>(
@@ -242,7 +238,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;
}
@@ -308,6 +306,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
...viewportHelper,
getNodes,
getNode,
getInternalNode,
getEdges,
getEdge,
setNodes,
@@ -325,6 +324,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
viewportHelper,
getNodes,
getNode,
getInternalNode,
getEdges,
getEdge,
setNodes,
@@ -21,7 +21,7 @@ export function useUpdateNodeInternals(): UpdateNodeInternals {
const nodeElement = domNode?.querySelector(`.react-flow__node[data-id="${updateId}"]`) as HTMLDivElement;
if (nodeElement) {
updates.set(updateId, { id: updateId, nodeElement, forceUpdate: true });
updates.set(updateId, { id: updateId, nodeElement, force: true });
}
});
@@ -48,12 +48,12 @@ const useViewportHelper = (): ViewportHelperFunctions => {
return { x, y, zoom };
},
fitView: (options) => {
const { nodes, width, height, nodeOrigin, minZoom, maxZoom, panZoom } = store.getState();
const { nodeLookup, width, height, nodeOrigin, minZoom, maxZoom, panZoom } = store.getState();
return panZoom
? fitView(
{
nodes,
nodeLookup,
width,
height,
nodeOrigin,
@@ -1,13 +1,13 @@
import { getNodesInside } from '@xyflow/system';
import { useCallback } from 'react';
import { shallow } from 'zustand/shallow';
import { getNodesInside } from '@xyflow/system';
import { useStore } from './useStore';
import type { Node, ReactFlowState } from '../types';
import { useCallback } from 'react';
const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
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
)
: Array.from(s.nodeLookup.keys());