Merge pull request #4477 from xyflow/nodesbounds

Added getNodesBounds function to useReactFlow/useSvelteFlow
This commit is contained in:
Moritz Klack
2024-09-05 16:32:06 +02:00
committed by GitHub
6 changed files with 69 additions and 12 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@xyflow/react': minor
'@xyflow/svelte': minor
'@xyflow/system': minor
---
Deprecate util function getNodesBounds and add alternative to useReactFlow/useSvelteFlow hook
+5
View File
@@ -3,6 +3,7 @@ import {
EdgeRemoveChange, EdgeRemoveChange,
evaluateAbsolutePosition, evaluateAbsolutePosition,
getElementsToRemove, getElementsToRemove,
getNodesBounds,
getOverlappingArea, getOverlappingArea,
isRectObject, isRectObject,
NodeRemoveChange, NodeRemoveChange,
@@ -230,6 +231,10 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
options options
); );
}, },
getNodesBounds: (nodes: (NodeType | InternalNode | string)[]): Rect => {
const { nodeLookup, nodeOrigin } = store.getState();
return getNodesBounds(nodes, { nodeLookup, nodeOrigin });
},
getHandleConnections: ({ type, id, nodeId }) => getHandleConnections: ({ type, id, nodeId }) =>
Array.from( Array.from(
store store
+8
View File
@@ -173,6 +173,14 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
dataUpdate: Partial<EdgeType['data']> | ((edge: EdgeType) => Partial<EdgeType['data']>), dataUpdate: Partial<EdgeType['data']> | ((edge: EdgeType) => Partial<EdgeType['data']>),
options?: { replace: boolean } options?: { replace: boolean }
) => void; ) => 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 | InternalNode | string)[]) => Rect;
/** /**
* Gets all connections for a given handle belonging to a specific node. * Gets all connections for a given handle belonging to a specific node.
* *
+18 -4
View File
@@ -16,7 +16,8 @@ import {
rendererPointToPoint, rendererPointToPoint,
evaluateAbsolutePosition, evaluateAbsolutePosition,
type HandleType, type HandleType,
type HandleConnection type HandleConnection,
getNodesBounds
} from '@xyflow/system'; } from '@xyflow/system';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
@@ -233,7 +234,14 @@ export function useSvelteFlow(): {
*/ */
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport }; toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
/** /**
* Gets all connections for a given handle belonging to a specific node. * 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 | InternalNode | string)[]) => Rect;
/** Gets all connections for a given handle belonging to a specific node.
* *
* @param type - handle type 'source' or 'target' * @param type - handle type 'source' or 'target'
* @param id - the handle id (this is only needed if you have multiple handles of the same type, meaning you have to provide a unique id for each handle) * @param id - the handle id (this is only needed if you have multiple handles of the same type, meaning you have to provide a unique id for each handle)
@@ -266,9 +274,9 @@ export function useSvelteFlow(): {
edges, edges,
domNode, domNode,
nodeLookup, nodeLookup,
nodeOrigin,
edgeLookup, edgeLookup,
connectionLookup, connectionLookup
nodeOrigin
} = useStore(); } = useStore();
const getNodeRect = (node: Node | { id: Node['id'] }): Rect | null => { const getNodeRect = (node: Node | { id: Node['id'] }): Rect | null => {
@@ -543,6 +551,12 @@ export function useSvelteFlow(): {
nodes.update((nds) => nds); nodes.update((nds) => nds);
}, },
getNodesBounds: (nodes) => {
const _nodeLookup = get(nodeLookup);
const _nodeOrigin = get(nodeOrigin);
return getNodesBounds(nodes, { nodeLookup: _nodeLookup, nodeOrigin: _nodeOrigin });
},
getHandleConnections: ({ type, id, nodeId }) => getHandleConnections: ({ type, id, nodeId }) =>
Array.from( Array.from(
get(connectionLookup) get(connectionLookup)
@@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { getNodesBounds, Position, getNodeToolbarTransform } from '@xyflow/system'; import { Position, getNodeToolbarTransform } from '@xyflow/system';
import portal from '$lib/actions/portal'; import portal from '$lib/actions/portal';
import type { InternalNode } from '$lib/types'; import type { InternalNode } from '$lib/types';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
import type { NodeToolbarProps } from './types'; import type { NodeToolbarProps } from './types';
import { useSvelteFlow } from '$lib/hooks/useSvelteFlow';
type $$Props = NodeToolbarProps; type $$Props = NodeToolbarProps;
@@ -15,7 +16,8 @@
export let offset: $$Props['offset'] = undefined; export let offset: $$Props['offset'] = undefined;
export let isVisible: $$Props['isVisible'] = undefined; export let isVisible: $$Props['isVisible'] = undefined;
const { domNode, viewport, nodeLookup, nodes, nodeOrigin } = useStore(); const { domNode, viewport, nodeLookup, nodes } = useStore();
const { getNodesBounds } = useSvelteFlow();
const contextNodeId = getContext<string>('svelteflow__node_id'); const contextNodeId = getContext<string>('svelteflow__node_id');
let transform: string; let transform: string;
@@ -42,7 +44,7 @@
} }
$: { $: {
const nodeRect = getNodesBounds(toolbarNodes, { nodeOrigin: $nodeOrigin }); const nodeRect = getNodesBounds(toolbarNodes);
if (nodeRect) { if (nodeRect) {
transform = getNodeToolbarTransform(nodeRect, $viewport, _position, _offset, _align); transform = getNodeToolbarTransform(nodeRect, $viewport, _position, _offset, _align);
+26 -5
View File
@@ -118,26 +118,47 @@ export const getNodePositionWithOrigin = (node: NodeBase, nodeOrigin: NodeOrigin
}; };
}; };
export type GetNodesBoundsParams = { export type GetNodesBoundsParams<NodeType extends NodeBase = NodeBase> = {
nodeOrigin?: NodeOrigin; nodeOrigin?: NodeOrigin;
nodeLookup?: NodeLookup<InternalNodeBase<NodeType>>;
}; };
/** /**
* Determines a bounding box that contains all given nodes in an array * Internal function for determining a bounding box that contains all given nodes in an array.
* @public * @public
* @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport. * @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 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.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
* @returns Bounding box enclosing all nodes * @returns Bounding box enclosing all nodes
*/ */
export const getNodesBounds = (nodes: NodeBase[], params: GetNodesBoundsParams = { nodeOrigin: [0, 0] }): Rect => { export const getNodesBounds = <NodeType extends NodeBase = NodeBase>(
nodes: (NodeType | InternalNodeBase<NodeType> | string)[],
params: GetNodesBoundsParams<NodeType> = { nodeOrigin: [0, 0], nodeLookup: undefined }
): Rect => {
if (process.env.NODE_ENV === 'development' && !params.nodeLookup) {
console.warn(
'Please use `getNodesBounds` from `useReactFlow`/`useSvelteFlow` hook to ensure correct values for sub flows. If not possible, you have to provide a nodeLookup to support sub flows.'
);
}
if (nodes.length === 0) { if (nodes.length === 0) {
return { x: 0, y: 0, width: 0, height: 0 }; return { x: 0, y: 0, width: 0, height: 0 };
} }
const box = nodes.reduce( const box = nodes.reduce(
(currBox, node) => { (currBox, nodeOrId) => {
const nodeBox = nodeToBox(node, params.nodeOrigin); const isId = typeof nodeOrId === 'string';
let currentNode = !params.nodeLookup && !isId ? nodeOrId : undefined;
if (params.nodeLookup) {
currentNode = isId
? params.nodeLookup.get(nodeOrId)
: !isInternalNodeBase(nodeOrId)
? params.nodeLookup.get(nodeOrId.id)
: nodeOrId;
}
const nodeBox = currentNode ? nodeToBox(currentNode, params.nodeOrigin) : { x: 0, y: 0, x2: 0, y2: 0 };
return getBoundsOfBoxes(currBox, nodeBox); return getBoundsOfBoxes(currBox, nodeBox);
}, },
{ x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }