chore(utils): tsdoc update

This commit is contained in:
moklick
2025-02-11 15:18:41 +01:00
parent 6c937546e4
commit fb63462be3
24 changed files with 336 additions and 134 deletions
@@ -10,7 +10,6 @@ import {
useEdgesState, useEdgesState,
useOnSelectionChange, useOnSelectionChange,
OnSelectionChangeParams, OnSelectionChangeParams,
OnSelectionChangeFunc,
} from '@xyflow/react'; } from '@xyflow/react';
const initialNodes: Node[] = [ const initialNodes: Node[] = [
@@ -29,6 +29,11 @@ function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number]
return [x1, 0.5 * (y1 + y2)]; return [x1, 0.5 * (y1 + y2)];
} }
/**
* The `getSimpleBezierPath` util returns everything you need to render a simple
bezier edge between two nodes.
* @public
*/
export function getSimpleBezierPath({ export function getSimpleBezierPath({
sourceX, sourceX,
sourceY, sourceY,
+21 -2
View File
@@ -153,7 +153,14 @@ function applyChange(change: any, element: any): any {
* @param nodes - Array of nodes to apply the changes to * @param nodes - Array of nodes to apply the changes to
* @returns Array of updated nodes * @returns Array of updated nodes
* @example * @example
* const onNodesChange = useCallback( *```tsx
*import { useState, useCallback } from 'react';
*import { ReactFlow, applyNodeChanges, type Node, type Edge, type OnNodesChange } from '@xyflow/react';
*
*export default function Flow() {
* const [nodes, setNodes] = useState<Node[]>([]);
* const [edges, setEdges] = useState<Edge[]>([]);
* const onNodesChange: OnNodesChange = useCallback(
* (changes) => { * (changes) => {
* setNodes((oldNodes) => applyNodeChanges(changes, oldNodes)); * setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
* }, * },
@@ -161,8 +168,10 @@ function applyChange(change: any, element: any): any {
* ); * );
* *
* return ( * return (
* <ReactFLow nodes={nodes} edges={edges} onNodesChange={onNodesChange} /> * <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
* ); * );
*}
*```
*/ */
export function applyNodeChanges<NodeType extends Node = Node>( export function applyNodeChanges<NodeType extends Node = Node>(
changes: NodeChange<NodeType>[], changes: NodeChange<NodeType>[],
@@ -180,6 +189,14 @@ export function applyNodeChanges<NodeType extends Node = Node>(
* @param edges - Array of edge to apply the changes to * @param edges - Array of edge to apply the changes to
* @returns Array of updated edges * @returns Array of updated edges
* @example * @example
*
* ```tsx
*import { useState, useCallback } from 'react';
*import { ReactFlow, applyEdgeChanges } from '@xyflow/react';
*
*export default function Flow() {
* const [nodes, setNodes] = useState([]);
* const [edges, setEdges] = useState([]);
* const onEdgesChange = useCallback( * const onEdgesChange = useCallback(
* (changes) => { * (changes) => {
* setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges)); * setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
@@ -190,6 +207,8 @@ export function applyNodeChanges<NodeType extends Node = Node>(
* return ( * return (
* <ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} /> * <ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
* ); * );
*}
*```
*/ */
export function applyEdgeChanges<EdgeType extends Edge = Edge>( export function applyEdgeChanges<EdgeType extends Edge = Edge>(
changes: EdgeChange<EdgeType>[], changes: EdgeChange<EdgeType>[],
+24 -2
View File
@@ -4,21 +4,43 @@ import { isNodeBase, isEdgeBase } from '@xyflow/system';
import type { Edge, Node } from '../types'; import type { Edge, Node } from '../types';
/** /**
* Test whether an object is useable as a Node * Test whether an object is useable as an [`Node`](/api-reference/types/node). In TypeScript
*this is a type guard that will narrow the type of whatever you pass in to
*[`Node`](/api-reference/types/node) if it returns `true`.
* @public * @public
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true * @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true
* @param element - The element to test * @param element - The element to test
* @returns A boolean indicating whether the element is an Node * @returns A boolean indicating whether the element is an Node
*
* @example
* ```js
*import { isNode } from '@xyflow/react';
*
*if (isNode(node)) {
* // ..
*}
*```
*/ */
export const isNode = <NodeType extends Node = Node>(element: unknown): element is NodeType => export const isNode = <NodeType extends Node = Node>(element: unknown): element is NodeType =>
isNodeBase<NodeType>(element); isNodeBase<NodeType>(element);
/** /**
* Test whether an object is useable as an Edge * Test whether an object is useable as an [`Edge`](/api-reference/types/edge). In TypeScript
*this is a type guard that will narrow the type of whatever you pass in to
*[`Edge`](/api-reference/types/edge) if it returns `true`.
* @public * @public
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true * @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true
* @param element - The element to test * @param element - The element to test
* @returns A boolean indicating whether the element is an Edge * @returns A boolean indicating whether the element is an Edge
*
* @example
* ```js
*import { isEdge } from '@xyflow/react';
*
*if (isEdge(edge)) {
* // ..
*}
*```
*/ */
export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType => export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType =>
isEdgeBase<EdgeType>(element); isEdgeBase<EdgeType>(element);
+1 -1
View File
@@ -46,7 +46,7 @@
"scripts": { "scripts": {
"dev": "concurrently \"rollup --config node:@xyflow/rollup-config --watch\"", "dev": "concurrently \"rollup --config node:@xyflow/rollup-config --watch\"",
"build": "rollup --config node:@xyflow/rollup-config --environment NODE_ENV:production", "build": "rollup --config node:@xyflow/rollup-config --environment NODE_ENV:production",
"lint": "eslint --ext .js,.ts src", "lint": "eslint --ext .js,.ts src --fix",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
+8 -4
View File
@@ -12,11 +12,13 @@ export type EdgeBase<
source: string; source: string;
/** Id of target node */ /** Id of target node */
target: string; target: string;
/** Id of source handle /**
* Id of source handle
* only needed if there are multiple handles per node * only needed if there are multiple handles per node
*/ */
sourceHandle?: string | null; sourceHandle?: string | null;
/** Id of target handle /**
* Id of target handle
* only needed if there are multiple handles per node * only needed if there are multiple handles per node
*/ */
targetHandle?: string | null; targetHandle?: string | null;
@@ -27,11 +29,13 @@ export type EdgeBase<
/** Arbitrary data passed to an edge */ /** Arbitrary data passed to an edge */
data?: EdgeData; data?: EdgeData;
selected?: boolean; selected?: boolean;
/** Set the marker on the beginning of an edge /**
* Set the marker on the beginning of an edge
* @example 'arrow', 'arrowclosed' or custom marker * @example 'arrow', 'arrowclosed' or custom marker
*/ */
markerStart?: EdgeMarkerType; markerStart?: EdgeMarkerType;
/** Set the marker on the end of an edge /**
* Set the marker on the end of an edge
* @example 'arrow', 'arrowclosed' or custom marker * @example 'arrow', 'arrowclosed' or custom marker
*/ */
markerEnd?: EdgeMarkerType; markerEnd?: EdgeMarkerType;
+8 -4
View File
@@ -14,11 +14,13 @@ export type Handle = {
}; };
export type HandleProps = { export type HandleProps = {
/** Type of the handle /**
* Type of the handle
* @example HandleType.Source, HandleType.Target * @example HandleType.Source, HandleType.Target
*/ */
type: HandleType; type: HandleType;
/** Position of the handle /**
* Position of the handle
* @example Position.TopLeft, Position.TopRight, * @example Position.TopLeft, Position.TopRight,
* Position.BottomLeft, Position.BottomRight * Position.BottomLeft, Position.BottomRight
*/ */
@@ -29,11 +31,13 @@ export type HandleProps = {
isConnectableStart?: boolean; isConnectableStart?: boolean;
/** Should you be able to connect to this handle */ /** Should you be able to connect to this handle */
isConnectableEnd?: boolean; isConnectableEnd?: boolean;
/** Callback if connection is valid /**
* Callback if connection is valid
* @remarks connection becomes an edge if isValidConnection returns true * @remarks connection becomes an edge if isValidConnection returns true
*/ */
isValidConnection?: IsValidConnection; isValidConnection?: IsValidConnection;
/** Id of the handle /**
* Id of the handle
* @remarks optional if there is only one handle of this type * @remarks optional if there is only one handle of this type
*/ */
id?: string | null; id?: string | null;
+14 -7
View File
@@ -13,7 +13,8 @@ export type NodeBase<
> = { > = {
/** Unique id of a node */ /** Unique id of a node */
id: string; id: string;
/** Position of a node on the pane /**
* Position of a node on the pane
* @example { x: 0, y: 0 } * @example { x: 0, y: 0 }
*/ */
position: XYPosition; position: XYPosition;
@@ -21,11 +22,13 @@ export type NodeBase<
data: NodeData; data: NodeData;
/** Type of node defined in nodeTypes */ /** Type of node defined in nodeTypes */
type?: NodeType; type?: NodeType;
/** Only relevant for default, source, target nodeType. controls source position /**
* Only relevant for default, source, target nodeType. controls source position
* @example 'right', 'left', 'top', 'bottom' * @example 'right', 'left', 'top', 'bottom'
*/ */
sourcePosition?: Position; sourcePosition?: Position;
/** Only relevant for default, source, target nodeType. controls target position /**
* Only relevant for default, source, target nodeType. controls target position
* @example 'right', 'left', 'top', 'bottom' * @example 'right', 'left', 'top', 'bottom'
*/ */
targetPosition?: Position; targetPosition?: Position;
@@ -45,13 +48,15 @@ export type NodeBase<
/** Parent node id, used for creating sub-flows */ /** Parent node id, used for creating sub-flows */
parentId?: string; parentId?: string;
zIndex?: number; zIndex?: number;
/** Boundary a node can be moved in /**
* Boundary a node can be moved in
* @example 'parent' or [[0, 0], [100, 100]] * @example 'parent' or [[0, 0], [100, 100]]
*/ */
extent?: 'parent' | CoordinateExtent; extent?: 'parent' | CoordinateExtent;
expandParent?: boolean; expandParent?: boolean;
ariaLabel?: string; ariaLabel?: string;
/** Origin of the node relative to it's position /**
* Origin of the node relative to it's position
* @example * @example
* [0.5, 0.5] // centers the node * [0.5, 0.5] // centers the node
* [0, 0] // top left * [0, 0] // top left
@@ -73,8 +78,10 @@ export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType &
internals: { internals: {
positionAbsolute: XYPosition; positionAbsolute: XYPosition;
z: number; z: number;
/** Holds a reference to the original node object provided by the user. /**
* Used as an optimization to avoid certain operations. */ * Holds a reference to the original node object provided by the user.
* Used as an optimization to avoid certain operations.
*/
userNode: NodeType; userNode: NodeType;
handleBounds?: NodeHandleBounds; handleBounds?: NodeHandleBounds;
bounds?: NodeBounds; bounds?: NodeBounds;
+5 -3
View File
@@ -61,9 +61,11 @@ export const getEventPosition = (event: MouseEvent | TouchEvent, bounds?: DOMRec
}; };
}; };
// The handle bounds are calculated relative to the node element. /*
// We store them in the internals object of the node in order to avoid * The handle bounds are calculated relative to the node element.
// unnecessary recalculations. * We store them in the internals object of the node in order to avoid
* unnecessary recalculations.
*/
export const getHandleBounds = ( export const getHandleBounds = (
type: 'source' | 'target', type: 'source' | 'target',
nodeElement: HTMLDivElement, nodeElement: HTMLDivElement,
+22 -13
View File
@@ -38,8 +38,10 @@ export function getBezierEdgeCenter({
targetControlX: number; targetControlX: number;
targetControlY: number; targetControlY: number;
}): [number, number, number, number] { }): [number, number, number, number] {
// cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate /*
// https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve * cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
* https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
*/
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125; const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125;
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125; const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125;
const offsetX = Math.abs(centerX - sourceX); const offsetX = Math.abs(centerX - sourceX);
@@ -70,7 +72,9 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva
} }
/** /**
* Get a bezier path from source to target handle * The `getBezierPath` util returns everything you need to render a bezier edge
*between two nodes.
* @public
* @param params.sourceX - The x position of the source handle * @param params.sourceX - The x position of the source handle
* @param params.sourceY - The y position of the source handle * @param params.sourceY - The y position of the source handle
* @param params.sourcePosition - The position of the source handle (default: Position.Bottom) * @param params.sourcePosition - The position of the source handle (default: Position.Bottom)
@@ -80,17 +84,22 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva
* @param params.curvature - The curvature of the bezier edge * @param params.curvature - The curvature of the bezier edge
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label * @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
* @example * @example
* ```js
* const source = { x: 0, y: 20 }; * const source = { x: 0, y: 20 };
const target = { x: 150, y: 100 }; * const target = { x: 150, y: 100 };
*
const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({ * const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({
sourceX: source.x, * sourceX: source.x,
sourceY: source.y, * sourceY: source.y,
sourcePosition: Position.Right, * sourcePosition: Position.Right,
targetX: target.x, * targetX: target.x,
targetY: target.y, * targetY: target.y,
targetPosition: Position.Left, * targetPosition: Position.Left,
}); *});
*```
*
* @remarks This function returns a tuple (aka a fixed-size array) to make it easier to
*work with multiple edge paths at once.
*/ */
export function getBezierPath({ export function getBezierPath({
sourceX, sourceX,
+11 -3
View File
@@ -90,12 +90,16 @@ const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => {
}; };
/** /**
* This util is a convenience function to add a new Edge to an array of edges * This util is a convenience function to add a new Edge to an array of edges. It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one.
* @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one.
* @public * @public
* @param edgeParams - Either an Edge or a Connection you want to add * @param edgeParams - Either an Edge or a Connection you want to add
* @param edges - The array of all current edges * @param edges - The array of all current edges
* @returns A new array of edges with the new edge added * @returns A new array of edges with the new edge added
*
* @remarks If an edge with the same `target` and `source` already exists (and the same
*`targetHandle` and `sourceHandle` if those are set), then this util won't add
*a new edge even if the `id` property is different.
*
*/ */
export const addEdge = <EdgeType extends EdgeBase>( export const addEdge = <EdgeType extends EdgeBase>(
edgeParams: EdgeType | Connection, edgeParams: EdgeType | Connection,
@@ -137,12 +141,16 @@ export type ReconnectEdgeOptions = {
}; };
/** /**
* A handy utility to reconnect an existing edge with new properties * A handy utility to update an existing [`Edge`](/api-reference/types/edge) with new properties.
*This searches your edge array for an edge with a matching `id` and updates its
*properties with the connection you provide.
* @param oldEdge - The edge you want to update * @param oldEdge - The edge you want to update
* @param newConnection - The new connection you want to update the edge with * @param newConnection - The new connection you want to update the edge with
* @param edges - The array of all current edges * @param edges - The array of all current edges
* @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id * @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id
* @returns the updated edges array * @returns the updated edges array
*
* @public
*/ */
export const reconnectEdge = <EdgeType extends EdgeBase>( export const reconnectEdge = <EdgeType extends EdgeBase>(
oldEdge: EdgeType, oldEdge: EdgeType,
@@ -38,8 +38,10 @@ const getDirection = ({
const distance = (a: XYPosition, b: XYPosition) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2)); const distance = (a: XYPosition, b: XYPosition) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
// ith this function we try to mimic a orthogonal edge routing behaviour /*
// It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges * ith this function we try to mimic a orthogonal edge routing behaviour
* It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges
*/
function getPoints({ function getPoints({
source, source,
sourcePosition = Position.Bottom, sourcePosition = Position.Bottom,
@@ -83,16 +85,20 @@ function getPoints({
if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) { if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
centerX = center.x ?? defaultCenterX; centerX = center.x ?? defaultCenterX;
centerY = center.y ?? defaultCenterY; centerY = center.y ?? defaultCenterY;
// ---> /*
// | * --->
// >--- * |
* >---
*/
const verticalSplit: XYPosition[] = [ const verticalSplit: XYPosition[] = [
{ x: centerX, y: sourceGapped.y }, { x: centerX, y: sourceGapped.y },
{ x: centerX, y: targetGapped.y }, { x: centerX, y: targetGapped.y },
]; ];
// | /*
// --- * |
// | * ---
* |
*/
const horizontalSplit: XYPosition[] = [ const horizontalSplit: XYPosition[] = [
{ x: sourceGapped.x, y: centerY }, { x: sourceGapped.x, y: centerY },
{ x: targetGapped.x, y: centerY }, { x: targetGapped.x, y: centerY },
@@ -191,7 +197,10 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str
} }
/** /**
* Get a smooth step path from source to target handle * The `getSmoothStepPath` util returns everything you need to render a stepped path
*between two nodes. The `borderRadius` property can be used to choose how rounded
*the corners of those steps are.
* @public
* @param params.sourceX - The x position of the source handle * @param params.sourceX - The x position of the source handle
* @param params.sourceY - The y position of the source handle * @param params.sourceY - The y position of the source handle
* @param params.sourcePosition - The position of the source handle (default: Position.Bottom) * @param params.sourcePosition - The position of the source handle (default: Position.Bottom)
@@ -200,17 +209,20 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str
* @param params.targetPosition - The position of the target handle (default: Position.Top) * @param params.targetPosition - The position of the target handle (default: Position.Top)
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label * @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
* @example * @example
* ```js
* const source = { x: 0, y: 20 }; * const source = { x: 0, y: 20 };
const target = { x: 150, y: 100 }; * const target = { x: 150, y: 100 };
*
const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({ * const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({
sourceX: source.x, * sourceX: source.x,
sourceY: source.y, * sourceY: source.y,
sourcePosition: Position.Right, * sourcePosition: Position.Right,
targetX: target.x, * targetX: target.x,
targetY: target.y, * targetY: target.y,
targetPosition: Position.Left, * targetPosition: Position.Left,
}); * });
* ```
* @remarks This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once.
*/ */
export function getSmoothStepPath({ export function getSmoothStepPath({
sourceX, sourceX,
@@ -8,24 +8,28 @@ export type GetStraightPathParams = {
}; };
/** /**
* Get a straight path from source to target handle * Calculates the straight line path between two points.
* @public
* @param params.sourceX - The x position of the source handle * @param params.sourceX - The x position of the source handle
* @param params.sourceY - The y position of the source handle * @param params.sourceY - The y position of the source handle
* @param params.targetX - The x position of the target handle * @param params.targetX - The x position of the target handle
* @param params.targetY - The y position of the target handle * @param params.targetY - The y position of the target handle
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label * @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
* @example * @example
* ```js
* const source = { x: 0, y: 20 }; * const source = { x: 0, y: 20 };
const target = { x: 150, y: 100 }; * const target = { x: 150, y: 100 };
*
const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({ * const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({
sourceX: source.x, * sourceX: source.x,
sourceY: source.y, * sourceY: source.y,
sourcePosition: Position.Right, * sourcePosition: Position.Right,
targetX: target.x, * targetX: target.x,
targetY: target.y, * targetY: target.y,
targetPosition: Position.Left, * targetPosition: Position.Left,
}); * });
* ```
* @remarks This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once.
*/ */
export function getStraightPath({ export function getStraightPath({
sourceX, sourceX,
+2 -2
View File
@@ -186,8 +186,8 @@ export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Tra
* @returns A transforned {@link Viewport} that encloses the given bounds which you can pass to e.g. {@link setViewport} * @returns A transforned {@link Viewport} that encloses the given bounds which you can pass to e.g. {@link setViewport}
* @example * @example
* const { x, y, zoom } = getViewportForBounds( * const { x, y, zoom } = getViewportForBounds(
{ x: 0, y: 0, width: 100, height: 100}, *{ x: 0, y: 0, width: 100, height: 100},
1200, 800, 0.5, 2); *1200, 800, 0.5, 2);
*/ */
export const getViewportForBounds = ( export const getViewportForBounds = (
bounds: Rect, bounds: Rect,
+86 -9
View File
@@ -54,12 +54,27 @@ export const isInternalNodeBase = <NodeType extends InternalNodeBase = InternalN
): element is NodeType => 'id' in element && 'internals' in element && !('source' in element) && !('target' in element); ): element is NodeType => 'id' in element && 'internals' in element && !('source' in element) && !('target' in element);
/** /**
* Pass in a node, and get connected nodes where edge.source === node.id * This util is used to tell you what nodes, if any, are connected to the given node
*as the _target_ of an edge.
* @public * @public
* @param node - The node to get the connected nodes from * @param node - The node to get the connected nodes from
* @param nodes - The array of all nodes * @param nodes - The array of all nodes
* @param edges - The array of all edges * @param edges - The array of all edges
* @returns An array of nodes that are connected over eges where the source is the given node * @returns An array of nodes that are connected over eges where the source is the given node
*
* @example
* ```ts
*import { getOutgoers } from '@xyflow/react';
*
*const nodes = [];
*const edges = [];
*
*const outgoers = getOutgoers(
* { id: '1', position: { x: 0, y: 0 }, data: { label: 'node' } },
* nodes,
* edges,
*);
*```
*/ */
export const getOutgoers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>( export const getOutgoers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
node: NodeType | { id: string }, node: NodeType | { id: string },
@@ -81,12 +96,27 @@ export const getOutgoers = <NodeType extends NodeBase = NodeBase, EdgeType exten
}; };
/** /**
* Pass in a node, and get connected nodes where edge.target === node.id * This util is used to tell you what nodes, if any, are connected to the given node
*as the _source_ of an edge.
* @public * @public
* @param node - The node to get the connected nodes from * @param node - The node to get the connected nodes from
* @param nodes - The array of all nodes * @param nodes - The array of all nodes
* @param edges - The array of all edges * @param edges - The array of all edges
* @returns An array of nodes that are connected over eges where the target is the given node * @returns An array of nodes that are connected over eges where the target is the given node
*
* @example
* ```ts
*import { getIncomers } from '@xyflow/react';
*
*const nodes = [];
*const edges = [];
*
*const incomers = getIncomers(
* { id: '1', position: { x: 0, y: 0 }, data: { label: 'node' } },
* nodes,
* edges,
*);
*```
*/ */
export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>( export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
node: NodeType | { id: string }, node: NodeType | { id: string },
@@ -124,12 +154,40 @@ export type GetNodesBoundsParams<NodeType extends NodeBase = NodeBase> = {
}; };
/** /**
* Internal function for determining a bounding box that contains all given nodes in an array. * Returns the bounding box that contains all the given nodes in an array. This can
*be useful when combined with [`getViewportForBounds`](/api-reference/utils/get-viewport-for-bounds)
*to calculate the correct transform to fit the given nodes in a viewport.
* @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
*
* @remarks This function was previously called `getRectOfNodes`
*
* @example
* ```js
*import { getNodesBounds } from '@xyflow/react';
*
*const nodes = [
* {
* id: 'a',
* position: { x: 0, y: 0 },
* data: { label: 'a' },
* width: 50,
* height: 25,
* },
* {
* id: 'b',
* position: { x: 100, y: 100 },
* data: { label: 'b' },
* width: 50,
* height: 25,
* },
*];
*
*const bounds = getNodesBounds(nodes);
*```
*/ */
export const getNodesBounds = <NodeType extends NodeBase = NodeBase>( export const getNodesBounds = <NodeType extends NodeBase = NodeBase>(
nodes: (NodeType | InternalNodeBase<NodeType> | string)[], nodes: (NodeType | InternalNodeBase<NodeType> | string)[],
@@ -154,8 +212,8 @@ export const getNodesBounds = <NodeType extends NodeBase = NodeBase>(
currentNode = isId currentNode = isId
? params.nodeLookup.get(nodeOrId) ? params.nodeLookup.get(nodeOrId)
: !isInternalNodeBase(nodeOrId) : !isInternalNodeBase(nodeOrId)
? params.nodeLookup.get(nodeOrId.id) ? params.nodeLookup.get(nodeOrId.id)
: nodeOrId; : nodeOrId;
} }
const nodeBox = currentNode ? nodeToBox(currentNode, params.nodeOrigin) : { x: 0, y: 0, x2: 0, y2: 0 }; const nodeBox = currentNode ? nodeToBox(currentNode, params.nodeOrigin) : { x: 0, y: 0, x2: 0, y2: 0 };
@@ -238,10 +296,29 @@ export const getNodesInside = <NodeType extends NodeBase = NodeBase>(
}; };
/** /**
* Get all connecting edges for a given set of nodes * This utility filters an array of edges, keeping only those where either the source or target node is present in the given array of nodes.
* @public
* @param nodes - Nodes you want to get the connected edges for * @param nodes - Nodes you want to get the connected edges for
* @param edges - All edges * @param edges - All edges
* @returns Array of edges that connect any of the given nodes with each other * @returns Array of edges that connect any of the given nodes with each other
*
* @example
* ```js
*import { getConnectedEdges } from '@xyflow/react';
*
*const nodes = [
* { id: 'a', position: { x: 0, y: 0 } },
* { id: 'b', position: { x: 100, y: 0 } },
*];
*
*const edges = [
* { id: 'a->c', source: 'a', target: 'c' },
* { id: 'c->d', source: 'c', target: 'd' },
*];
*
*const connectedEdges = getConnectedEdges(nodes, edges);
* // => [{ id: 'a->c', source: 'a', target: 'c' }]
*```
*/ */
export const getConnectedEdges = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>( export const getConnectedEdges = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
nodes: NodeType[], nodes: NodeType[],
@@ -382,9 +459,9 @@ export async function getElementsToRemove<NodeType extends NodeBase = NodeBase,
edges: EdgeType[]; edges: EdgeType[];
onBeforeDelete?: OnBeforeDeleteBase<NodeType, EdgeType>; onBeforeDelete?: OnBeforeDeleteBase<NodeType, EdgeType>;
}): Promise<{ }): Promise<{
nodes: NodeType[]; nodes: NodeType[];
edges: EdgeType[]; edges: EdgeType[];
}> { }> {
const nodeIds = new Set(nodesToRemove.map((node) => node.id)); const nodeIds = new Set(nodesToRemove.map((node) => node.id));
const matchingNodes: NodeType[] = []; const matchingNodes: NodeType[] = [];
+4 -2
View File
@@ -15,8 +15,10 @@ export function getNodeToolbarTransform(
alignmentOffset = 1; alignmentOffset = 1;
} }
// position === Position.Top /*
// we set the x any y position of the toolbar based on the nodes position * position === Position.Top
* we set the x any y position of the toolbar based on the nodes position
*/
let pos = [ let pos = [
(nodeRect.x + nodeRect.width * alignmentOffset) * viewport.zoom + viewport.x, (nodeRect.x + nodeRect.width * alignmentOffset) * viewport.zoom + viewport.x,
nodeRect.y * viewport.zoom + viewport.y - offset, nodeRect.y * viewport.zoom + viewport.y - offset,
+9 -5
View File
@@ -276,8 +276,10 @@ export function handleExpandParent(
}, },
}); });
// We move all child nodes in the oppsite direction /*
// so the x,y changes of the parent do not move the children * We move all child nodes in the oppsite direction
* so the x,y changes of the parent do not move the children
*/
parentLookup.get(parentId)?.forEach((childNode) => { parentLookup.get(parentId)?.forEach((childNode) => {
if (!children.some((child) => child.id === childNode.id)) { if (!children.some((child) => child.id === childNode.id)) {
changes.push({ changes.push({
@@ -472,9 +474,11 @@ function addConnectionToLookup(
nodeId: string, nodeId: string,
handleId: string | null handleId: string | null
) { ) {
// We add the connection to the connectionLookup at the following keys /*
// 1. nodeId, 2. nodeId-type, 3. nodeId-type-handleId * We add the connection to the connectionLookup at the following keys
// If the key already exists, we add the connection to the existing map * 1. nodeId, 2. nodeId-type, 3. nodeId-type-handleId
* If the key already exists, we add the connection to the existing map
*/
let key = nodeId; let key = nodeId;
const nodeMap = connectionLookup.get(key) || new Map(); const nodeMap = connectionLookup.get(key) || new Map();
connectionLookup.set(key, nodeMap.set(connectionKey, connection)); connectionLookup.set(key, nodeMap.set(connectionKey, connection));
+8 -4
View File
@@ -140,8 +140,10 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
for (const [id, dragItem] of dragItems) { for (const [id, dragItem] of dragItems) {
if (!nodeLookup.has(id)) { if (!nodeLookup.has(id)) {
// if the node is not in the nodeLookup anymore, it was probably deleted while dragging /*
// and we don't need to update it anymore * if the node is not in the nodeLookup anymore, it was probably deleted while dragging
* and we don't need to update it anymore
*/
continue; continue;
} }
@@ -150,8 +152,10 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
nextPosition = snapPosition(nextPosition, snapGrid); nextPosition = snapPosition(nextPosition, snapGrid);
} }
// if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node /*
// based on its position so that the node stays at it's position relative to the selection. * if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node
* based on its position so that the node stays at it's position relative to the selection.
*/
let adjustedNodeExtent: CoordinateExtent = [ let adjustedNodeExtent: CoordinateExtent = [
[nodeExtent[0][0], nodeExtent[0][1]], [nodeExtent[0][0], nodeExtent[0][1]],
[nodeExtent[1][0], nodeExtent[1][1]], [nodeExtent[1][0], nodeExtent[1][1]],
+9 -7
View File
@@ -74,9 +74,11 @@ export function getDragItems<NodeType extends NodeBase>(
return dragItems; return dragItems;
} }
// returns two params: /*
// 1. the dragged node (or the first of the list, if we are dragging a node selection) * returns two params:
// 2. array of selected nodes (for multi selections) * 1. the dragged node (or the first of the list, if we are dragging a node selection)
* 2. array of selected nodes (for multi selections)
*/
export function getEventHandlerParams<NodeType extends InternalNodeBase>({ export function getEventHandlerParams<NodeType extends InternalNodeBase>({
nodeId, nodeId,
dragItems, dragItems,
@@ -112,10 +114,10 @@ export function getEventHandlerParams<NodeType extends InternalNodeBase>({
!node !node
? nodesFromDragItems[0] ? nodesFromDragItems[0]
: { : {
...node, ...node,
position: dragItems.get(nodeId)?.position || node.position, position: dragItems.get(nodeId)?.position || node.position,
dragging, dragging,
}, },
nodesFromDragItems, nodesFromDragItems,
]; ];
} }
+12 -6
View File
@@ -165,8 +165,10 @@ function onPointerDown(
toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId)! : null, toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId)! : null,
}; };
// we don't want to trigger an update when the connection /*
// is snapped to the same handle as before * we don't want to trigger an update when the connection
* is snapped to the same handle as before
*/
if ( if (
isValid && isValid &&
closestHandle && closestHandle &&
@@ -190,8 +192,10 @@ function onPointerDown(
onConnect?.(connection); onConnect?.(connection);
} }
// it's important to get a fresh reference from the store here /*
// in order to get the latest state of onConnectEnd * it's important to get a fresh reference from the store here
* in order to get the latest state of onConnectEnd
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { inProgress, ...connectionState } = previousConnection; const { inProgress, ...connectionState } = previousConnection;
const finalConnectionState = { const finalConnectionState = {
@@ -248,8 +252,10 @@ function isValidHandle(
const { x, y } = getEventPosition(event); const { x, y } = getEventPosition(event);
const handleBelow = doc.elementFromPoint(x, y); const handleBelow = doc.elementFromPoint(x, y);
// we always want to prioritize the handle below the mouse cursor over the closest distance handle, /*
// because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor * we always want to prioritize the handle below the mouse cursor over the closest distance handle,
* because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor
*/
const handleToCheck = handleBelow?.classList.contains(`${lib}-flow__handle`) ? handleBelow : handleDomNode; const handleToCheck = handleBelow?.classList.contains(`${lib}-flow__handle`) ? handleBelow : handleDomNode;
const result: Result = { const result: Result = {
+4 -2
View File
@@ -19,8 +19,10 @@ function getNodesWithinDistance(position: XYPosition, nodeLookup: NodeLookup, di
return nodes; return nodes;
} }
// this distance is used for the area around the user pointer /*
// while doing a connection for finding the closest nodes * this distance is used for the area around the user pointer
* while doing a connection for finding the closest nodes
*/
const ADDITIONAL_DISTANCE = 250; const ADDITIONAL_DISTANCE = 250;
export function getClosestHandle( export function getClosestHandle(
+20 -18
View File
@@ -114,22 +114,22 @@ export function XYPanZoom({
const wheelHandler = isPanOnScroll const wheelHandler = isPanOnScroll
? createPanOnScrollHandler({ ? createPanOnScrollHandler({
zoomPanValues, zoomPanValues,
noWheelClassName, noWheelClassName,
d3Selection, d3Selection,
d3Zoom: d3ZoomInstance, d3Zoom: d3ZoomInstance,
panOnScrollMode, panOnScrollMode,
panOnScrollSpeed, panOnScrollSpeed,
zoomOnPinch, zoomOnPinch,
onPanZoomStart, onPanZoomStart,
onPanZoom, onPanZoom,
onPanZoomEnd, onPanZoomEnd,
}) })
: createZoomOnScrollHandler({ : createZoomOnScrollHandler({
noWheelClassName, noWheelClassName,
preventScrolling, preventScrolling,
d3ZoomHandler, d3ZoomHandler,
}); });
d3Selection.on('wheel.zoom', wheelHandler, { passive: false }); d3Selection.on('wheel.zoom', wheelHandler, { passive: false });
@@ -178,9 +178,11 @@ export function XYPanZoom({
}); });
d3ZoomInstance.filter(filter); d3ZoomInstance.filter(filter);
// We cannot add zoomOnDoubleClick to the filter above because /*
// double tapping on touch screens circumvents the filter and * We cannot add zoomOnDoubleClick to the filter above because
// dblclick.zoom is fired on the selection directly * double tapping on touch screens circumvents the filter and
* dblclick.zoom is fired on the selection directly
*/
if (zoomOnDoubleClick) { if (zoomOnDoubleClick) {
d3Selection.on('dblclick.zoom', d3DblClickZoomHandler); d3Selection.on('dblclick.zoom', d3DblClickZoomHandler);
} else { } else {
@@ -90,8 +90,10 @@ export function createPanOnScrollHandler({
return; return;
} }
// increase scroll speed in firefox /*
// firefox: deltaMode === 1; chrome: deltaMode === 0 * increase scroll speed in firefox
* firefox: deltaMode === 1; chrome: deltaMode === 0
*/
const deltaNormalize = event.deltaMode === 1 ? 20 : 1; const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
let deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize; let deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
let deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize; let deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
@@ -114,9 +116,11 @@ export function createPanOnScrollHandler({
clearTimeout(zoomPanValues.panScrollTimeout); clearTimeout(zoomPanValues.panScrollTimeout);
// for pan on scroll we need to handle the event calls on our own /*
// we can't use the start, zoom and end events from d3-zoom * for pan on scroll we need to handle the event calls on our own
// because start and move gets called on every scroll event and not once at the beginning * we can't use the start, zoom and end events from d3-zoom
* because start and move gets called on every scroll event and not once at the beginning
*/
if (!zoomPanValues.isPanScrolling) { if (!zoomPanValues.isPanScrolling) {
zoomPanValues.isPanScrolling = true; zoomPanValues.isPanScrolling = true;
+8 -4
View File
@@ -154,8 +154,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
parentExtent = parentNode && node.extent === 'parent' ? nodeToParentExtent(parentNode) : undefined; parentExtent = parentNode && node.extent === 'parent' ? nodeToParentExtent(parentNode) : undefined;
} }
// Collect all child nodes to correct their relative positions when top/left changes /*
// Determine largest minimal extent the parent node is allowed to resize to * Collect all child nodes to correct their relative positions when top/left changes
* Determine largest minimal extent the parent node is allowed to resize to
*/
childNodes = []; childNodes = [];
childExtent = undefined; childExtent = undefined;
@@ -230,8 +232,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
prevValues.x = change.x; prevValues.x = change.x;
prevValues.y = change.y; prevValues.y = change.y;
// when top/left changes, correct the relative positions of child nodes /*
// so that they stay in the same position * when top/left changes, correct the relative positions of child nodes
* so that they stay in the same position
*/
if (childNodes.length > 0) { if (childNodes.length > 0) {
const xChange = x - prevX; const xChange = x - prevX;
const yChange = y - prevY; const yChange = y - prevY;