diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index faf9d31f..b793bfe0 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -1,11 +1,14 @@ import { useMemo } from 'react'; import { + boxToRect, EdgeRemoveChange, evaluateAbsolutePosition, + getBoundsOfBoxes, getElementsToRemove, getOverlappingArea, isRectObject, NodeRemoveChange, + nodeToBox, nodeToRect, type Rect, } from '@xyflow/system'; @@ -230,6 +233,26 @@ export function useReactFlow { + if (nodes.length === 0) { + return { x: 0, y: 0, width: 0, height: 0 }; + } + + const { nodeLookup, nodeOrigin } = store.getState(); + + const box = nodes.reduce( + (currBox, node) => { + const internalNode = + typeof node === 'string' ? nodeLookup.get(node) : node.parentId ? nodeLookup.get(node.id) : node; + + const nodeBox = internalNode ? nodeToBox(internalNode, nodeOrigin) : { x: 0, y: 0, x2: 0, y2: 0 }; + return getBoundsOfBoxes(currBox, nodeBox); + }, + { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } + ); + + return boxToRect(box); + }, }; }, []); diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index 5f4aa3b3..d545771e 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -173,6 +173,14 @@ export type GeneralHelpers | ((edge: EdgeType) => Partial), options?: { replace: boolean } ) => void; + /** + * Returns the bounds of the given nodes or node ids. + * + * @param nodes - the nodes or node ids to calculate the bounds for + * + * @returns the bounds of the given nodes + */ + getNodesBounds: (nodes: (NodeType | string)[]) => Rect; }; export type ReactFlowInstance = GeneralHelpers< diff --git a/packages/svelte/src/lib/hooks/useSvelteFlow.ts b/packages/svelte/src/lib/hooks/useSvelteFlow.ts index f46c1757..2de1e393 100644 --- a/packages/svelte/src/lib/hooks/useSvelteFlow.ts +++ b/packages/svelte/src/lib/hooks/useSvelteFlow.ts @@ -14,7 +14,10 @@ import { getViewportForBounds, getElementsToRemove, rendererPointToPoint, - nodeHasDimensions + nodeHasDimensions, + nodeToBox, + getBoundsOfBoxes, + boxToRect } from '@xyflow/system'; import { useStore } from '$lib/store'; @@ -230,6 +233,14 @@ export function useSvelteFlow(): { * @returns the nodes, edges and the viewport as a JSON object */ toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport }; + /** + * Returns the bounds of the given nodes or node ids. + * + * @param nodes - the nodes or node ids to calculate the bounds for + * + * @returns the bounds of the given nodes + */ + getNodesBounds: (nodes: (Node | string)[]) => Rect; } { const { zoomIn, @@ -247,6 +258,7 @@ export function useSvelteFlow(): { edges, domNode, nodeLookup, + nodeOrigin, edgeLookup } = useStore(); @@ -506,7 +518,34 @@ export function useSvelteFlow(): { nodes.update((nds) => nds); }, - viewport + viewport, + getNodesBounds: (nodes) => { + if (nodes.length === 0) { + return { x: 0, y: 0, width: 0, height: 0 }; + } + + const _nodeLookup = get(nodeLookup); + const _nodeOrigin = get(nodeOrigin); + + const box = nodes.reduce( + (currBox, node) => { + const internalNode = + typeof node === 'string' + ? _nodeLookup.get(node) + : node.parentId + ? _nodeLookup.get(node.id) + : node; + + const nodeBox = internalNode + ? nodeToBox(internalNode, _nodeOrigin) + : { x: 0, y: 0, x2: 0, y2: 0 }; + return getBoundsOfBoxes(currBox, nodeBox); + }, + { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } + ); + + return boxToRect(box); + } }; } function getElements(lookup: Map, ids: string[]): Node[];