diff --git a/packages/react/src/components/NodesSelection/index.tsx b/packages/react/src/components/NodesSelection/index.tsx index e87f23af..ae0a84d1 100644 --- a/packages/react/src/components/NodesSelection/index.tsx +++ b/packages/react/src/components/NodesSelection/index.tsx @@ -5,13 +5,13 @@ import { useRef, useEffect, type MouseEvent, type KeyboardEvent } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; -import { getNodesBounds } from '@xyflow/system'; +import { getInternalNodesBounds } from '@xyflow/system'; import { useStore, useStoreApi } from '../../hooks/useStore'; import { useDrag } from '../../hooks/useDrag'; import { useMoveSelectedNodes } from '../../hooks/useMoveSelectedNodes'; import { arrowKeyDiffs } from '../NodeWrapper/utils'; -import type { InternalNode, Node, ReactFlowState } from '../../types'; +import type { Node, ReactFlowState } from '../../types'; export type NodesSelectionProps = { onSelectionContextMenu?: (event: MouseEvent, nodes: NodeType[]) => void; @@ -20,14 +20,10 @@ export type NodesSelectionProps = { }; const selector = (s: ReactFlowState) => { - 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 }); + const { width, height, x, y } = getInternalNodesBounds(s.nodeLookup, { + nodeOrigin: s.nodeOrigin, + filter: (node) => !!node.selected, + }); return { width, diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index d3812725..57844405 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -2,14 +2,14 @@ import { infiniteExtent, ConnectionMode, adoptUserNodes, - getNodesBounds, getViewportForBounds, Transform, updateConnectionLookup, devWarn, + getInternalNodesBounds, } from '@xyflow/system'; -import type { Edge, Node, ReactFlowStore } from '../types'; +import type { Edge, InternalNode, Node, ReactFlowStore } from '../types'; const getInitialState = ({ nodes, @@ -28,7 +28,7 @@ const getInitialState = ({ height?: number; fitView?: boolean; } = {}): ReactFlowStore => { - const nodeLookup = new Map(); + const nodeLookup = new Map(); const parentLookup = new Map(); const connectionLookup = new Map(); const edgeLookup = new Map(); @@ -44,11 +44,11 @@ const getInitialState = ({ let transform: Transform = [0, 0, 1]; if (fitView && width && height) { - const nodesWithDimensions = storeNodes.filter( - (node) => (node.width || node.initialWidth) && (node.height || node.initialHeight) - ); // @todo users nodeOrigin should be used here - const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] }); + const bounds = getInternalNodesBounds(nodeLookup, { + nodeOrigin: [0, 0], + filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight)), + }); const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1); transform = [x, y, zoom]; } diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts index c306f3e3..53592609 100644 --- a/packages/system/src/utils/graph.ts +++ b/packages/system/src/utils/graph.ts @@ -4,13 +4,12 @@ import { clampPosition, getBoundsOfBoxes, getOverlappingArea, - rectToBox, nodeToRect, pointToRendererPoint, getViewportForBounds, isCoordinateExtent, getNodeDimensions, - getPositionWithOrigin, + nodeToBox, } from './general'; import { type Transform, @@ -131,7 +130,6 @@ export const getNodePositionWithOrigin = ( export type GetNodesBoundsParams = { nodeOrigin?: NodeOrigin; - useRelativePosition?: boolean; }; /** @@ -140,28 +138,17 @@ export type GetNodesBoundsParams = { * @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport. * @param nodes - Nodes to calculate the bounds for * @param params.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center - * @param params.useRelativePosition - Whether to use the relative or absolute node positions * @returns Bounding box enclosing all nodes */ -// @todo how to handle this if users do not have absolute positions? -export const getNodesBounds = ( - nodes: NodeBase[], - params: GetNodesBoundsParams = { nodeOrigin: [0, 0], useRelativePosition: false } -): Rect => { +export const getNodesBounds = (nodes: NodeBase[], params: GetNodesBoundsParams = { nodeOrigin: [0, 0] }): Rect => { if (nodes.length === 0) { return { x: 0, y: 0, width: 0, height: 0 }; } const box = nodes.reduce( (currBox, node) => { - const nodePos = getNodePositionWithOrigin(node, params.nodeOrigin); - return getBoundsOfBoxes( - currBox, - rectToBox({ - ...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'], - ...getNodeDimensions(node), - }) - ); + const nodeBox = nodeToBox(node, params.nodeOrigin); + return getBoundsOfBoxes(currBox, nodeBox); }, { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } ); @@ -169,19 +156,19 @@ export const getNodesBounds = ( return boxToRect(box); }; -export type GetInternalNodesBoundsParams = { +export type GetInternalNodesBoundsParams = { nodeOrigin?: NodeOrigin; useRelativePosition?: boolean; - filter?: (node: NodeBase | NodeDragItem) => boolean; + filter?: (node: NodeType) => boolean; }; /** * Determines a bounding box that contains all given nodes in an array * @internal */ -export const getInternalNodesBounds = ( - nodeLookup: NodeLookup | Map, - params: GetInternalNodesBoundsParams = { +export const getInternalNodesBounds = ( + nodeLookup: Map, + params: GetInternalNodesBoundsParams = { nodeOrigin: [0, 0], } ): Rect => { @@ -193,24 +180,8 @@ export const getInternalNodesBounds = ( nodeLookup.forEach((node) => { if (params.filter == undefined || params.filter(node)) { - const { width, height } = getNodeDimensions(node); - const { x, y } = getPositionWithOrigin({ - x: node.internals.positionAbsolute.x, - y: node.internals.positionAbsolute.x, - width, - height, - origin: node.origin || params.nodeOrigin, - }); - - box = getBoundsOfBoxes( - box, - rectToBox({ - x, - y, - width, - height, - }) - ); + const nodeBox = nodeToBox(node as InternalNodeBase, params.nodeOrigin); + box = getBoundsOfBoxes(box, nodeBox); } }); @@ -218,7 +189,7 @@ export const getInternalNodesBounds = ( }; export const getNodesInside = ( - nodeLookup: Map>, + nodes: Map>, rect: Rect, [tx, ty, tScale]: Transform = [0, 0, 1], partially = false, @@ -234,7 +205,7 @@ export const getNodesInside = ( const visibleNodes: InternalNodeBase[] = []; - for (const [, node] of nodeLookup) { + for (const [, node] of nodes) { const { measured, selectable = true, hidden = false } = node; const width = measured.width ?? node.width ?? node.initialWidth ?? null; const height = measured.height ?? node.height ?? node.initialHeight ?? null; @@ -281,15 +252,12 @@ export function fitView, Options exte options?: Options ) { const filteredNodes: InternalNodeBase[] = []; + const optionNodeIds = options?.nodes ? new Set(options.nodes.map((node) => node.id)) : null; nodeLookup.forEach((n) => { const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden); - // TODO: this remove options.nodes.some with a Set - if ( - isVisible && - (!options?.nodes || (options?.nodes.length && options?.nodes.some((optionNode) => optionNode.id === n.id))) - ) { + if (isVisible && (!optionNodeIds || optionNodeIds.has(n.id))) { filteredNodes.push(n); } });