From f5da8c6b75bdc302901ec99d25be60ade409ef65 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 4 Dec 2023 18:06:01 +0100 Subject: [PATCH 01/10] TSDoc for getViewportForBounds --- packages/system/src/utils/general.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/system/src/utils/general.ts b/packages/system/src/utils/general.ts index 2db59322..7480a5e9 100644 --- a/packages/system/src/utils/general.ts +++ b/packages/system/src/utils/general.ts @@ -8,8 +8,8 @@ import type { NodeOrigin, SnapGrid, Transform, - Viewport, } from '../types'; +import { type Viewport } from '../types'; import { getNodePositionWithOrigin } from './graph'; export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max); @@ -155,6 +155,21 @@ export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Tra }; }; +/** + * Returns a viewport that encloses the given bounds with optional padding. + * @remarks You can determine bounds eg. with {@link getNodesBounds} and {@link getBoundsOfRects} + * @param bounds - Bounds to fit inside viewport + * @param width - Width of the viewport + * @param height - Height of the viewport + * @param minZoom - Minimum zoom level of the resulting viewport + * @param maxZoom - Maximum zoom level of the resulting viewport + * @param padding - Optional padding around the bounds + * @returns A {@link Viewport} that encloses the given bounds which you can pass to {@link setViewport} + * @example + * const { x, y, zoomn } = getViewportForBounds( + { x: 0, y: 0, width: 100, height: 100}, + 1200, 800, 0.5, 2); + */ export const getViewportForBounds = ( bounds: Rect, width: number, From 4accceb202a0ecc30ea2f527342b95e2626a497f Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 5 Dec 2023 12:12:21 +0100 Subject: [PATCH 02/10] Added TSDocs for several public util functions & converted comments to TSDocs for internal functions --- .../system/src/utils/edges/bezier-edge.ts | 11 ++++ packages/system/src/utils/general.ts | 21 +++++--- packages/system/src/utils/graph.ts | 50 +++++++++++++++++-- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/packages/system/src/utils/edges/bezier-edge.ts b/packages/system/src/utils/edges/bezier-edge.ts index 810b07d7..a8ecae31 100644 --- a/packages/system/src/utils/edges/bezier-edge.ts +++ b/packages/system/src/utils/edges/bezier-edge.ts @@ -69,6 +69,17 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva } } +/** + * Get a bezier path from source to target handle + * @param params.sourceX - The x position of the source handle + * @param params.sourceY - The y position of the source handle + * @param params.sourcePosition - The position of the source handle + * @param params.targetX - The x position of the target handle + * @param params.targetY - The y position of the target handle + * @param params.targetPosition - The position of the target handle + * @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 + */ export function getBezierPath({ sourceX, sourceY, diff --git a/packages/system/src/utils/general.ts b/packages/system/src/utils/general.ts index 7480a5e9..bc8f4dfe 100644 --- a/packages/system/src/utils/general.ts +++ b/packages/system/src/utils/general.ts @@ -19,8 +19,14 @@ export const clampPosition = (position: XYPosition = { x: 0, y: 0 }, extent: Coo y: clamp(position.y, extent[0][1], extent[1][1]), }); -// returns a number between 0 and 1 that represents the velocity of the movement -// when the mouse is close to the edge of the canvas +/** + * Calculates the velocity of panning when the mouse is close to the edge of the canvas + * @internal + * @param value - One dimensional poition of the mouse (x or y) + * @param min - Minimal position on canvas before panning starts + * @param max - Maximal position on canvas before panning starts + * @returns - A number between 0 and 1 that represents the velocity of panning + */ const calcAutoPanVelocity = (value: number, min: number, max: number): number => { if (value < min) { return clamp(Math.abs(value - min), 1, 50) / 50; @@ -127,12 +133,12 @@ export const getPositionWithOrigin = ({ }; }; -export function snapPosition(position: XYPosition, snapGrid: SnapGrid = [1, 1]): XYPosition { +export const snapPosition = (position: XYPosition, snapGrid: SnapGrid = [1, 1]): XYPosition => { return { x: snapGrid[0] * Math.round(position.x / snapGrid[0]), y: snapGrid[1] * Math.round(position.y / snapGrid[1]), }; -} +}; export const pointToRendererPoint = ( { x, y }: XYPosition, @@ -157,16 +163,17 @@ export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Tra /** * Returns a viewport that encloses the given bounds with optional padding. - * @remarks You can determine bounds eg. with {@link getNodesBounds} and {@link getBoundsOfRects} + * @public + * @remarks You can determine bounds of nodes with {@link getNodesBounds} and {@link getBoundsOfRects} * @param bounds - Bounds to fit inside viewport * @param width - Width of the viewport * @param height - Height of the viewport * @param minZoom - Minimum zoom level of the resulting viewport * @param maxZoom - Maximum zoom level of the resulting viewport * @param padding - Optional padding around the bounds - * @returns A {@link Viewport} that encloses the given bounds which you can pass to {@link setViewport} + * @returns A transforned {@link Viewport} that encloses the given bounds which you can pass to e.g. {@link setViewport} * @example - * const { x, y, zoomn } = getViewportForBounds( + * const { x, y, zoom } = getViewportForBounds( { x: 0, y: 0, width: 100, height: 100}, 1200, 800, 0.5, 2); */ diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts index 6a946a10..38d83dc2 100644 --- a/packages/system/src/utils/graph.ts +++ b/packages/system/src/utils/graph.ts @@ -26,14 +26,36 @@ import { } from '../types'; import { errorMessages } from '../constants'; +/** + * Test whether an object is useable as an Edge + * @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 + * @param element - The element to test + * @returns A boolean indicating whether the element is an Edge + */ export const isEdgeBase = ( element: NodeType | Connection | EdgeType ): element is EdgeType => 'id' in element && 'source' in element && 'target' in element; +/** + * Test whether an object is useable as a Node + * @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 + * @param element - The element to test + * @returns A boolean indicating whether the element is an Node + */ export const isNodeBase = ( element: NodeType | Connection | EdgeType ): element is NodeType => 'id' in element && !('source' in element) && !('target' in element); +/** + * Pass in a node, and get connected nodes where edge.source === node.id + * @public + * @param node - The node to get the connected nodes from + * @param nodes - The array of all nodes + * @param edges - The array of all edges + * @returns An array of nodes that are connected over eges where the source is the given node + */ export const getOutgoersBase = ( node: NodeType | { id: string }, nodes: NodeType[], @@ -53,6 +75,14 @@ export const getOutgoersBase = outgoerIds.has(n.id)); }; +/** + * Pass in a node, and get connected nodes where edge.target === node.id + * @public + * @param node - The node to get the connected nodes from + * @param nodes - The array of all nodes + * @param edges - The array of all edges + * @returns An array of nodes that are connected over eges where the target is the given node + */ export const getIncomersBase = ( node: NodeType | { id: string }, nodes: NodeType[], @@ -105,6 +135,14 @@ export const getNodePositionWithOrigin = ( }; }; +/** + * Determines a bounding box that contains all given nodes in an array + * @public + * @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 nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center + * @returns Bounding box enclosing all nodes + */ export const getNodesBounds = (nodes: NodeBase[], nodeOrigin: NodeOrigin = [0, 0]): Rect => { if (nodes.length === 0) { return { x: 0, y: 0, width: 0, height: 0 }; @@ -284,9 +322,15 @@ export function calcNextPosition( }; } -// helper function to get arrays of nodes and edges that can be deleted -// you can pass in a list of nodes and edges that should be deleted -// and the function only returns elements that are deletable and also handles connected nodes and child nodes +/** + * Pass in nodes & edges to delete, get arrays of nodes and edges that actually can be deleted + * @internal + * @param param.nodesToRemove - The nodes to remove + * @param param.edgesToRemove - The edges to remove + * @param param.nodes - All nodes + * @param param.edges - All edges + * @returns matchingNodes: nodes that can be deleted, matchingEdges: edges that can be deleted + */ export function getElementsToRemove({ nodesToRemove, edgesToRemove, From b264c11c549db15587cac751812a51127894c396 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 5 Dec 2023 13:35:12 +0100 Subject: [PATCH 03/10] Added some more TSDocs for util functions --- packages/react/src/utils/general.ts | 50 +++++++++++++++++++ .../system/src/utils/edges/bezier-edge.ts | 16 +++++- packages/system/src/utils/edges/general.ts | 16 ++++++ .../system/src/utils/edges/smoothstep-edge.ts | 22 ++++++++ .../system/src/utils/edges/straight-edge.ts | 20 ++++++++ packages/system/src/utils/graph.ts | 6 +++ 6 files changed, 128 insertions(+), 2 deletions(-) diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index e13247c1..af5443cd 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -10,10 +10,60 @@ import { import type { Edge, Node } from '../types'; +/** + * Test whether an object is useable as a Node + * @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 + * @param element - The element to test + * @returns A boolean indicating whether the element is an Node + */ export const isNode = isNodeBase; + +/** + * Test whether an object is useable as an Edge + * @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 + * @param element - The element to test + * @returns A boolean indicating whether the element is an Edge + */ export const isEdge = isEdgeBase; + +/** + * Pass in a node, and get connected nodes where edge.source === node.id + * @public + * @param node - The node to get the connected nodes from + * @param nodes - The array of all nodes + * @param edges - The array of all edges + * @returns An array of nodes that are connected over eges where the source is the given node + */ export const getOutgoers = getOutgoersBase; + +/** + * Pass in a node, and get connected nodes where edge.target === node.id + * @public + * @param node - The node to get the connected nodes from + * @param nodes - The array of all nodes + * @param edges - The array of all edges + * @returns An array of nodes that are connected over eges where the target is the given node + */ export const getIncomers = getIncomersBase; + +/** + * This util is a convenience function to add a new Edge to an array of edges + * @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one. + * @public + * @param edgeParams - Either an Edge or a Connection you want to add + * @param edges - The array of all current edges + * @returns A new array of edges with the new edge added + */ export const addEdge = addEdgeBase; + export const updateEdge = updateEdgeBase; + +/** + * Get all connecting edges for a given set of nodes + * @param nodes - Nodes you want to get the connected edges for + * @param edges - All edges + * @returns Array of edges that connect any of the given nodes with each other + */ export const getConnectedEdges = getConnectedEdgesBase; diff --git a/packages/system/src/utils/edges/bezier-edge.ts b/packages/system/src/utils/edges/bezier-edge.ts index a8ecae31..640deffa 100644 --- a/packages/system/src/utils/edges/bezier-edge.ts +++ b/packages/system/src/utils/edges/bezier-edge.ts @@ -73,12 +73,24 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva * Get a bezier path from source to target handle * @param params.sourceX - The x position of the source handle * @param params.sourceY - The y position of the source handle - * @param params.sourcePosition - The position of the source handle + * @param params.sourcePosition - The position of the source handle (default: Position.Bottom) * @param params.targetX - The x position of the target handle * @param params.targetY - The y position of the target handle - * @param params.targetPosition - The position of the target handle + * @param params.targetPosition - The position of the target handle (default: Position.Top) * @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 + * @example + * const source = { x: 0, y: 20 }; + const target = { x: 150, y: 100 }; + + const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({ + sourceX: source.x, + sourceY: source.y, + sourcePosition: Position.Right, + targetX: target.x, + targetY: target.y, + targetPosition: Position.Left, +}); */ export function getBezierPath({ sourceX, diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index 9ffd8e24..42baa0dc 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -124,6 +124,14 @@ const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => { ); }; +/** + * This util is a convenience function to add a new Edge to an array of edges + * @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one. + * @public + * @param edgeParams - Either an Edge or a Connection you want to add + * @param edges - The array of all current edges + * @returns A new array of edges with the new edge added + */ export const addEdgeBase = ( edgeParams: EdgeType | Connection, edges: EdgeType[] @@ -163,6 +171,14 @@ export type UpdateEdgeOptions = { shouldReplaceId?: boolean; }; +/** + * A handy utility to update an existing Edge with new properties + * @param oldEdge - The edge you want to update + * @param newConnection - The new Connection you want to update the edge with + * @param edges - The array of all current edges + * @param options.shouldReplaceId - + * @returns + */ export const updateEdgeBase = ( oldEdge: EdgeType, newConnection: Connection, diff --git a/packages/system/src/utils/edges/smoothstep-edge.ts b/packages/system/src/utils/edges/smoothstep-edge.ts index ba3e95a9..40954457 100644 --- a/packages/system/src/utils/edges/smoothstep-edge.ts +++ b/packages/system/src/utils/edges/smoothstep-edge.ts @@ -190,6 +190,28 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}`; } +/** + * Get a smooth step path from source to target handle + * @param params.sourceX - The x 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.targetX - The x position of the target handle + * @param params.targetY - The y position of the target handle + * @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 + * @example + * const source = { x: 0, y: 20 }; + const target = { x: 150, y: 100 }; + + const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({ + sourceX: source.x, + sourceY: source.y, + sourcePosition: Position.Right, + targetX: target.x, + targetY: target.y, + targetPosition: Position.Left, + }); + */ export function getSmoothStepPath({ sourceX, sourceY, diff --git a/packages/system/src/utils/edges/straight-edge.ts b/packages/system/src/utils/edges/straight-edge.ts index 9cc6d928..9940bfea 100644 --- a/packages/system/src/utils/edges/straight-edge.ts +++ b/packages/system/src/utils/edges/straight-edge.ts @@ -7,6 +7,26 @@ export type GetStraightPathParams = { targetY: number; }; +/** + * Get a straight path from source to target handle + * @param params.sourceX - The x 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.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 + * @example + * const source = { x: 0, y: 20 }; + const target = { x: 150, y: 100 }; + + const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({ + sourceX: source.x, + sourceY: source.y, + sourcePosition: Position.Right, + targetX: target.x, + targetY: target.y, + targetPosition: Position.Left, + }); + */ export function getStraightPath({ sourceX, sourceY, diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts index 38d83dc2..4732ff0b 100644 --- a/packages/system/src/utils/graph.ts +++ b/packages/system/src/utils/graph.ts @@ -208,6 +208,12 @@ export const getNodesInside = ( return visibleNodes; }; +/** + * Get all connecting edges for a given set of nodes + * @param nodes - Nodes you want to get the connected edges for + * @param edges - All edges + * @returns Array of edges that connect any of the given nodes with each other + */ export const getConnectedEdgesBase = ( nodes: NodeType[], edges: EdgeType[] From 95dfd1d6deef2a3ac29d800e73baeab8d8ec7915 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 5 Dec 2023 13:46:59 +0100 Subject: [PATCH 04/10] Finished TSDocs for util functions in React --- packages/react/src/utils/general.ts | 8 ++++++++ packages/system/src/utils/edges/general.ts | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index af5443cd..aa96ff92 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -58,6 +58,14 @@ export const getIncomers = getIncomersBase; */ export const addEdge = addEdgeBase; +/** + * A handy utility to update an existing Edge with new properties + * @param oldEdge - The edge you want to update + * @param newConnection - The new connection you want to update the edge with + * @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 + * @returns the updated edges array + */ export const updateEdge = updateEdgeBase; /** diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index 42baa0dc..bfadb743 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -174,10 +174,10 @@ export type UpdateEdgeOptions = { /** * A handy utility to update an existing Edge with new properties * @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 options.shouldReplaceId - - * @returns + * @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id + * @returns the updated edges array */ export const updateEdgeBase = ( oldEdge: EdgeType, From 8ee3fb64ed805b405c7c6c9476f8ba8f3a4a76c8 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 5 Dec 2023 13:48:49 +0100 Subject: [PATCH 05/10] Added TSDoc for util functions in svelte --- packages/svelte/src/lib/utils/index.ts | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/svelte/src/lib/utils/index.ts b/packages/svelte/src/lib/utils/index.ts index 23b29448..a44f471d 100644 --- a/packages/svelte/src/lib/utils/index.ts +++ b/packages/svelte/src/lib/utils/index.ts @@ -10,10 +10,68 @@ import { import type { Edge, Node } from '$lib/types'; +/** + * Test whether an object is useable as a Node + * @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 + * @param element - The element to test + * @returns A boolean indicating whether the element is an Node + */ export const isNode = isNodeBase; + +/** + * Test whether an object is useable as an Edge + * @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 + * @param element - The element to test + * @returns A boolean indicating whether the element is an Edge + */ export const isEdge = isEdgeBase; + +/** + * Pass in a node, and get connected nodes where edge.source === node.id + * @public + * @param node - The node to get the connected nodes from + * @param nodes - The array of all nodes + * @param edges - The array of all edges + * @returns An array of nodes that are connected over eges where the source is the given node + */ export const getOutgoers = getOutgoersBase; + +/** + * Pass in a node, and get connected nodes where edge.target === node.id + * @public + * @param node - The node to get the connected nodes from + * @param nodes - The array of all nodes + * @param edges - The array of all edges + * @returns An array of nodes that are connected over eges where the target is the given node + */ export const getIncomers = getIncomersBase; + +/** + * This util is a convenience function to add a new Edge to an array of edges + * @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one. + * @public + * @param edgeParams - Either an Edge or a Connection you want to add + * @param edges - The array of all current edges + * @returns A new array of edges with the new edge added + */ export const addEdge = addEdgeBase; + +/** + * A handy utility to update an existing Edge with new properties + * @param oldEdge - The edge you want to update + * @param newConnection - The new connection you want to update the edge with + * @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 + * @returns the updated edges array + */ export const updateEdge = updateEdgeBase; + +/** + * Get all connecting edges for a given set of nodes + * @param nodes - Nodes you want to get the connected edges for + * @param edges - All edges + * @returns Array of edges that connect any of the given nodes with each other + */ export const getConnectedEdges = getConnectedEdgesBase; From 46815e546ae03dc4b6f644cecf961fbb93c6b2cd Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 5 Dec 2023 14:26:55 +0100 Subject: [PATCH 06/10] TSDocs for applyNodeChanges & applyEdgeChanges --- packages/react/src/utils/changes.ts | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/react/src/utils/changes.ts b/packages/react/src/utils/changes.ts index c113ebe1..0cf87e6f 100644 --- a/packages/react/src/utils/changes.ts +++ b/packages/react/src/utils/changes.ts @@ -139,10 +139,50 @@ function applyChanges(changes: any[], elements: any[]): any[] { }, initElements); } +/** + * Drop in function that applies node changes to an array of nodes. + * @public + * @remarks Various events on the component can produce an {@link NodeChange} that describes how to update the edges of your flow in some way. + If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges. + * @param changes - Array of changes to apply + * @param nodes - Array of nodes to apply the changes to + * @returns Array of updated nodes + * @example + * const onNodesChange = useCallback( + (changes) => { + setNodes((oldNodes) => applyNodeChanges(changes, oldNodes)); + }, + [setNodes], + ); + + return ( + + ); + */ export function applyNodeChanges(changes: NodeChange[], nodes: Node[]): Node[] { return applyChanges(changes, nodes) as Node[]; } +/** + * Drop in function that applies edge changes to an array of edges. + * @public + * @remarks Various events on the component can produce an {@link EdgeChange} that describes how to update the edges of your flow in some way. + If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges. + * @param changes - Array of changes to apply + * @param edges - Array of edge to apply the changes to + * @returns Array of updated edges + * @example + * const onEdgesChange = useCallback( + (changes) => { + setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges)); + }, + [setEdges], + ); + + return ( + + ); + */ export function applyEdgeChanges(changes: EdgeChange[], edges: Edge[]): Edge[] { return applyChanges(changes, edges) as Edge[]; } From ba8ee0272ac937709068e5b53135ff8d3717562b Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 6 Dec 2023 15:56:17 +0100 Subject: [PATCH 07/10] refactor(types): cleanup edge types --- packages/react/src/types/component-props.ts | 3 +- packages/react/src/types/edges.ts | 75 ++++++++++++--------- packages/react/src/types/nodes.ts | 69 +++++++++++-------- packages/svelte/src/lib/types/edges.ts | 49 ++++++++------ packages/system/src/types/edges.ts | 3 +- 5 files changed, 117 insertions(+), 82 deletions(-) diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index 770432f5..09f9a582 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -20,6 +20,7 @@ import type { OnError, IsValidConnection, ColorMode, + SnapGrid, } from '@xyflow/system'; import type { @@ -110,7 +111,7 @@ export type ReactFlowProps = Omit, 'onError'> & { multiSelectionKeyCode?: KeyCode | null; zoomActivationKeyCode?: KeyCode | null; snapToGrid?: boolean; - snapGrid?: [number, number]; + snapGrid?: SnapGrid; onlyRenderVisibleElements?: boolean; nodesDraggable?: boolean; nodesConnectable?: boolean; diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 287c277d..fecd7c7b 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -13,7 +13,6 @@ import type { HandleElement, ConnectionStatus, EdgePosition, - Optional, StepPathOptions, } from '@xyflow/system'; @@ -30,13 +29,13 @@ export type EdgeLabelOptions = { export type EdgeUpdatable = boolean | HandleType; -export type DefaultEdge = EdgeBase & { - style?: CSSProperties; - className?: string; - sourceNode?: Node; - targetNode?: Node; - updatable?: EdgeUpdatable; -} & EdgeLabelOptions; +export type DefaultEdge = EdgeBase & + EdgeLabelOptions & { + style?: CSSProperties; + className?: string; + updatable?: EdgeUpdatable; + focusable?: boolean; + }; type SmoothStepEdgeType = DefaultEdge & { type: 'smoothstep'; @@ -53,6 +52,9 @@ type StepEdgeType = DefaultEdge & { pathOptions?: StepPathOptions; }; +/** + * The Edge type is mainly used for the `edges` that get passed to the ReactFlow component. + */ export type Edge = DefaultEdge | SmoothStepEdgeType | BezierEdgeType | StepEdgeType; export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void; @@ -85,45 +87,58 @@ export type EdgeTextProps = HTMLAttributes & y: number; }; -// props that get passed to a custom edge +/** + * Custom edge component props. + */ export type EdgeProps = Pick< Edge, 'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target' > & - Pick & EdgePosition & EdgeLabelOptions & { + interactionWidth?: number; + sourceHandleId?: string | null; + targetHandleId?: string | null; markerStart?: string; markerEnd?: string; // @TODO: how can we get better types for pathOptions? pathOptions?: any; }; -export type BaseEdgeProps = Pick & +/** + * BaseEdge component props. + */ +export type BaseEdgeProps = EdgeLabelOptions & { + id?: string; + interactionWidth?: number; + labelX?: number; + labelY?: number; + markerStart?: string; + markerEnd?: string; + path: string; + style?: CSSProperties; +}; + +export type EdgeComponentProps = EdgePosition & EdgeLabelOptions & { - id?: string; - labelX?: number; - labelY?: number; - path: string; + id?: EdgeProps['id']; + markerStart?: EdgeProps['markerStart']; + markerEnd?: EdgeProps['markerEnd']; + interactionWidth?: EdgeProps['interactionWidth']; + style?: EdgeProps['style']; + sourceHandleId?: EdgeProps['sourceHandleId']; + targetHandleId?: EdgeProps['targetHandleId']; }; -export type EdgeComponentProps = Optional, 'source' | 'target'>, 'id'>; - -export type StraightEdgeProps = Omit, 'sourcePosition' | 'targetPosition'>; - -export type SmoothStepEdgeProps = EdgeComponentProps & { - pathOptions?: SmoothStepPathOptions; +export type EdgeComponentWithPathOptions = EdgeComponentProps & { + pathOptions?: PathOptions; }; -export type BezierEdgeProps = EdgeComponentProps & { - pathOptions?: BezierPathOptions; -}; - -export type StepEdgeProps = EdgeComponentProps & { - pathOptions?: StepPathOptions; -}; - -export type SimpleBezierEdgeProps = EdgeComponentProps; +export type BezierEdgeProps = EdgeComponentWithPathOptions; +export type SmoothStepEdgeProps = EdgeComponentWithPathOptions; +export type StepEdgeProps = EdgeComponentWithPathOptions; +export type StraightEdgeProps = Omit; +export type SimpleBezierEdgeProps = EdgeComponentProps; export type OnEdgeUpdateFunc = (oldEdge: Edge, newConnection: Connection) => void; diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 29548666..4d10b76b 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -1,12 +1,25 @@ import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; import type { NodeBase, XYPosition } from '@xyflow/system'; +/** + * The node data structure that gets used for the nodes prop. + * @public + */ export type Node = NodeBase< NodeData, NodeType > & { + /** + * Inline style object + */ style?: CSSProperties; + /** + * Inline style object + */ className?: string; + /** + * Inline style object + */ resizing?: boolean; }; @@ -17,30 +30,32 @@ export type SelectionDragHandler = (event: ReactMouseEvent, nodes: Node[]) => vo export type WrapNodeProps = Pick< Node, 'id' | 'data' | 'style' | 'className' | 'dragHandle' | 'sourcePosition' | 'targetPosition' | 'hidden' | 'ariaLabel' -> & - Required, 'selected' | 'type' | 'zIndex'>> & { - isConnectable: boolean; - xPos: number; - yPos: number; - xPosOrigin: number; - yPosOrigin: number; - positionAbsolute: XYPosition; - initialized: boolean; - isSelectable: boolean; - isDraggable: boolean; - isFocusable: boolean; - onClick?: NodeMouseHandler; - onDoubleClick?: NodeMouseHandler; - onMouseEnter?: NodeMouseHandler; - onMouseMove?: NodeMouseHandler; - onMouseLeave?: NodeMouseHandler; - onContextMenu?: NodeMouseHandler; - resizeObserver: ResizeObserver | null; - isParent: boolean; - noDragClassName: string; - noPanClassName: string; - rfId: string; - disableKeyboardA11y: boolean; - width?: number; - height?: number; - }; +> & { + type: string; + selected: boolean; + zIndex: number; + isConnectable: boolean; + xPos: number; + yPos: number; + xPosOrigin: number; + yPosOrigin: number; + positionAbsolute: XYPosition; + initialized: boolean; + isSelectable: boolean; + isDraggable: boolean; + isFocusable: boolean; + onClick?: NodeMouseHandler; + onDoubleClick?: NodeMouseHandler; + onMouseEnter?: NodeMouseHandler; + onMouseMove?: NodeMouseHandler; + onMouseLeave?: NodeMouseHandler; + onContextMenu?: NodeMouseHandler; + resizeObserver: ResizeObserver | null; + isParent: boolean; + noDragClassName: string; + noPanClassName: string; + rfId: string; + disableKeyboardA11y: boolean; + width?: number; + height?: number; +}; diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index 3f1f0e8d..c1d3778d 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -6,13 +6,12 @@ import type { DefaultEdgeOptionsBase, EdgePosition, SmoothStepPathOptions, - Optional, StepPathOptions } from '@xyflow/system'; import type { Node } from '$lib/types'; -export type DefaultEdge = Omit, 'focusable'> & { +export type DefaultEdge = EdgeBase & { label?: string; labelStyle?: string; style?: string; @@ -34,12 +33,18 @@ type StepEdgeType = DefaultEdge & { pathOptions?: StepPathOptions; }; +/** + * The Edge type is mainly used for the `edges` that get passed to the SvelteFlow component. + */ export type Edge = | DefaultEdge | SmoothStepEdgeType | BezierEdgeType | StepEdgeType; +/** + * Custom edge component props. + */ export type EdgeProps = Omit, 'sourceHandle' | 'targetHandle' | 'type'> & EdgePosition & { markerStart?: string; @@ -48,30 +53,30 @@ export type EdgeProps = Omit, 'sourceHandle' | 'targetHandle' | targetHandleId?: string | null; }; -export type EdgeComponentProps = Optional< - Omit< - EdgeProps, - 'source' | 'target' | 'sourceHandleId' | 'targetHandleId' | 'animated' | 'selected' | 'data' - >, - 'id' ->; - -export type BezierEdgeProps = EdgeComponentProps & { - pathOptions?: BezierPathOptions; +export type EdgeComponentProps = EdgePosition & { + id?: EdgeProps['id']; + hidden?: EdgeProps['hidden']; + deletable?: EdgeProps['deletable']; + selectable?: EdgeProps['selectable']; + markerStart?: EdgeProps['markerStart']; + markerEnd?: EdgeProps['markerEnd']; + zIndex?: EdgeProps['zIndex']; + ariaLabel?: EdgeProps['ariaLabel']; + interactionWidth?: EdgeProps['interactionWidth']; + label?: EdgeProps['label']; + labelStyle?: EdgeProps['labelStyle']; + style?: EdgeProps['style']; + class?: EdgeProps['class']; }; -export type SmoothStepEdgeProps = EdgeComponentProps & { - pathOptions?: SmoothStepPathOptions; +export type EdgeComponentWithPathOptions = EdgeComponentProps & { + pathOptions?: PathOptions; }; -export type StepEdgeProps = EdgeComponentProps & { - pathOptions?: StepPathOptions; -}; - -export type StraightEdgeProps = Omit< - EdgeComponentProps, - 'sourcePosition' | 'targetPosition' ->; +export type BezierEdgeProps = EdgeComponentWithPathOptions; +export type SmoothStepEdgeProps = EdgeComponentWithPathOptions; +export type StepEdgeProps = EdgeComponentWithPathOptions; +export type StraightEdgeProps = Omit; export type EdgeTypes = Record>>; diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index 65c8e641..5455b57a 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -19,7 +19,6 @@ export type EdgeBase = { zIndex?: number; ariaLabel?: string; interactionWidth?: number; - focusable?: boolean; }; export type SmoothStepPathOptions = { @@ -37,7 +36,7 @@ export type BezierPathOptions = { export type DefaultEdgeOptionsBase = Omit< EdgeType, - 'id' | 'source' | 'target' | 'sourceHandle' | 'targetHandle' | 'sourceNode' | 'targetNode' + 'id' | 'source' | 'target' | 'sourceHandle' | 'targetHandle' >; export enum ConnectionLineType { From 10f31e79e66419ce0280113caa50be5adb07de86 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 7 Dec 2023 12:24:03 +0100 Subject: [PATCH 08/10] refactor(types): use custom node type as generic --- .../additional-components/MiniMap/types.ts | 16 ++--- packages/react/src/hooks/useNodes.ts | 4 +- .../react/src/hooks/useNodesEdgesState.ts | 12 ++-- packages/react/src/hooks/useReactFlow.ts | 59 ++++++++-------- packages/react/src/types/changes.ts | 16 ++--- packages/react/src/types/edges.ts | 34 +++++++++- packages/react/src/types/general.ts | 13 ++-- packages/react/src/types/instance.ts | 67 ++++++++++--------- packages/react/src/types/nodes.ts | 21 +++--- packages/react/src/utils/changes.ts | 8 +-- packages/svelte/src/lib/types/edges.ts | 18 +++++ packages/svelte/src/lib/types/nodes.ts | 8 +-- 12 files changed, 160 insertions(+), 116 deletions(-) diff --git a/packages/react/src/additional-components/MiniMap/types.ts b/packages/react/src/additional-components/MiniMap/types.ts index 5667f7a8..3b24b08d 100644 --- a/packages/react/src/additional-components/MiniMap/types.ts +++ b/packages/react/src/additional-components/MiniMap/types.ts @@ -4,12 +4,12 @@ import type { PanelPosition, XYPosition } from '@xyflow/system'; import type { Node } from '../../types'; -export type GetMiniMapNodeAttribute = (node: Node) => string; +export type GetMiniMapNodeAttribute = (node: NodeType) => string; -export type MiniMapProps = Omit, 'onClick'> & { - nodeColor?: string | GetMiniMapNodeAttribute; - nodeStrokeColor?: string | GetMiniMapNodeAttribute; - nodeClassName?: string | GetMiniMapNodeAttribute; +export type MiniMapProps = Omit, 'onClick'> & { + nodeColor?: string | GetMiniMapNodeAttribute; + nodeStrokeColor?: string | GetMiniMapNodeAttribute; + nodeClassName?: string | GetMiniMapNodeAttribute; nodeBorderRadius?: number; nodeStrokeWidth?: number; nodeComponent?: ComponentType; @@ -18,7 +18,7 @@ export type MiniMapProps = Omit, ' maskStrokeWidth?: number; position?: PanelPosition; onClick?: (event: MouseEvent, position: XYPosition) => void; - onNodeClick?: (event: MouseEvent, node: Node) => void; + onNodeClick?: (event: MouseEvent, node: NodeType) => void; pannable?: boolean; zoomable?: boolean; ariaLabel?: string | null; @@ -27,8 +27,8 @@ export type MiniMapProps = Omit, ' offsetScale?: number; }; -export type MiniMapNodes = Pick< - MiniMapProps, +export type MiniMapNodes = Pick< + MiniMapProps, 'nodeColor' | 'nodeStrokeColor' | 'nodeClassName' | 'nodeBorderRadius' | 'nodeStrokeWidth' | 'nodeComponent' > & { onClick?: (event: MouseEvent, nodeId: string) => void; diff --git a/packages/react/src/hooks/useNodes.ts b/packages/react/src/hooks/useNodes.ts index cc75f142..1ccd1d85 100644 --- a/packages/react/src/hooks/useNodes.ts +++ b/packages/react/src/hooks/useNodes.ts @@ -5,8 +5,8 @@ import type { Node, ReactFlowState } from '../types'; const nodesSelector = (state: ReactFlowState) => state.nodes; -function useNodes(): Node[] { - const nodes = useStore(nodesSelector, shallow); +function useNodes(): NodeType[] { + const nodes = useStore(nodesSelector, shallow) as NodeType[]; return nodes; } diff --git a/packages/react/src/hooks/useNodesEdgesState.ts b/packages/react/src/hooks/useNodesEdgesState.ts index 19754aa4..5255c7dd 100644 --- a/packages/react/src/hooks/useNodesEdgesState.ts +++ b/packages/react/src/hooks/useNodesEdgesState.ts @@ -11,14 +11,14 @@ type OnChange = (changes: ChangesType[]) => void; // const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); function createUseItemsState( applyChanges: ApplyChanges -): ( - initialItems: Node[] -) => [Node[], Dispatch[]>>, OnChange]; +): ( + initialItems: NodeType[] +) => [NodeType[], Dispatch>, OnChange]; function createUseItemsState( applyChanges: ApplyChanges -): ( - initialItems: Edge[] -) => [Edge[], Dispatch[]>>, OnChange]; +): ( + initialItems: EdgeType[] +) => [EdgeType[], Dispatch>, OnChange]; function createUseItemsState( applyChanges: ApplyChanges ): (initialItems: any[]) => [any[], Dispatch>, OnChange] { diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 44e29c14..2875ca03 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -26,31 +26,34 @@ import type { } from '../types'; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ -export default function useReactFlow(): ReactFlowInstance { +export default function useReactFlow(): ReactFlowInstance< + NodeType, + EdgeType +> { const viewportHelper = useViewportHelper(); const store = useStoreApi(); - const getNodes = useCallback>(() => { - return store.getState().nodes.map((n) => ({ ...n })); + const getNodes = useCallback>(() => { + return store.getState().nodes.map((n) => ({ ...n })) as NodeType[]; }, []); - const getNode = useCallback>((id) => { - return store.getState().nodeLookup.get(id); + const getNode = useCallback>((id) => { + return store.getState().nodeLookup.get(id) as NodeType; }, []); - const getEdges = useCallback>(() => { + const getEdges = useCallback>(() => { const { edges = [] } = store.getState(); - return edges.map((e) => ({ ...e })); + return edges.map((e) => ({ ...e })) as EdgeType[]; }, []); - const getEdge = useCallback>((id) => { + const getEdge = useCallback>((id) => { const { edges = [] } = store.getState(); - return edges.find((e) => e.id === id); + return edges.find((e) => e.id === id) as EdgeType; }, []); - const setNodes = useCallback>((payload) => { + const setNodes = useCallback>((payload) => { const { nodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); - const nextNodes = typeof payload === 'function' ? payload(nodes) : payload; + const nextNodes = typeof payload === 'function' ? payload(nodes as NodeType[]) : payload; if (hasDefaultNodes) { setNodes(nextNodes); @@ -58,14 +61,14 @@ export default function useReactFlow(): ReactFlo const changes = nextNodes.length === 0 ? nodes.map((node) => ({ type: 'remove', id: node.id } as NodeRemoveChange)) - : nextNodes.map((node) => ({ item: node, type: 'reset' } as NodeResetChange)); + : nextNodes.map((node) => ({ item: node, type: 'reset' } as NodeResetChange)); onNodesChange(changes); } }, []); - const setEdges = useCallback>((payload) => { + const setEdges = useCallback>((payload) => { const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState(); - const nextEdges = typeof payload === 'function' ? payload(edges) : payload; + const nextEdges = typeof payload === 'function' ? payload(edges as EdgeType[]) : payload; if (hasDefaultEdges) { setEdges(nextEdges); @@ -73,12 +76,12 @@ export default function useReactFlow(): ReactFlo const changes = nextEdges.length === 0 ? edges.map((edge) => ({ type: 'remove', id: edge.id } as EdgeRemoveChange)) - : nextEdges.map((edge) => ({ item: edge, type: 'reset' } as EdgeResetChange)); + : nextEdges.map((edge) => ({ item: edge, type: 'reset' } as EdgeResetChange)); onEdgesChange(changes); } }, []); - const addNodes = useCallback>((payload) => { + const addNodes = useCallback>((payload) => { const nodes = Array.isArray(payload) ? payload : [payload]; const { nodes: currentNodes, hasDefaultNodes, onNodesChange, setNodes } = store.getState(); @@ -86,29 +89,29 @@ export default function useReactFlow(): ReactFlo const nextNodes = [...currentNodes, ...nodes]; setNodes(nextNodes); } else if (onNodesChange) { - const changes = nodes.map((node) => ({ item: node, type: 'add' } as NodeAddChange)); + const changes = nodes.map((node) => ({ item: node, type: 'add' } as NodeAddChange)); onNodesChange(changes); } }, []); - const addEdges = useCallback>((payload) => { + const addEdges = useCallback>((payload) => { const nextEdges = Array.isArray(payload) ? payload : [payload]; const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState(); if (hasDefaultEdges) { setEdges([...edges, ...nextEdges]); } else if (onEdgesChange) { - const changes = nextEdges.map((edge) => ({ item: edge, type: 'add' } as EdgeAddChange)); + const changes = nextEdges.map((edge) => ({ item: edge, type: 'add' } as EdgeAddChange)); onEdgesChange(changes); } }, []); - const toObject = useCallback>(() => { + const toObject = useCallback>(() => { const { nodes = [], edges = [], transform } = store.getState(); const [x, y, zoom] = transform; return { - nodes: nodes.map((n) => ({ ...n })), - edges: edges.map((e) => ({ ...e })), + nodes: nodes.map((n) => ({ ...n })) as NodeType[], + edges: edges.map((e) => ({ ...e })) as EdgeType[], viewport: { x, y, @@ -180,11 +183,9 @@ export default function useReactFlow(): ReactFlo }, []); const getNodeRect = useCallback( - ( - nodeOrRect: Node | { id: Node['id'] } | Rect - ): [Rect | null, Node | null | undefined, boolean] => { + (nodeOrRect: NodeType | { id: Node['id'] } | Rect): [Rect | null, NodeType | null | undefined, boolean] => { const isRect = isRectObject(nodeOrRect); - const node = isRect ? null : store.getState().nodeLookup.get(nodeOrRect.id); + const node = isRect ? null : (store.getState().nodeLookup.get(nodeOrRect.id) as NodeType); if (!isRect && !node) { [null, null, isRect]; @@ -197,7 +198,7 @@ export default function useReactFlow(): ReactFlo [] ); - const getIntersectingNodes = useCallback>( + const getIntersectingNodes = useCallback>( (nodeOrRect, partially = true, nodes) => { const [nodeRect, node, isRect] = getNodeRect(nodeOrRect); @@ -215,12 +216,12 @@ export default function useReactFlow(): ReactFlo const partiallyVisible = partially && overlappingArea > 0; return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height; - }); + }) as NodeType[]; }, [] ); - const isNodeIntersecting = useCallback>( + const isNodeIntersecting = useCallback>( (nodeOrRect, area, partially = true) => { const [nodeRect] = getNodeRect(nodeOrRect); diff --git a/packages/react/src/types/changes.ts b/packages/react/src/types/changes.ts index 69ee0f1f..440a4c6b 100644 --- a/packages/react/src/types/changes.ts +++ b/packages/react/src/types/changes.ts @@ -30,13 +30,13 @@ export type NodeRemoveChange = { type: 'remove'; }; -export type NodeAddChange = { - item: Node; +export type NodeAddChange = { + item: NodeType; type: 'add'; }; -export type NodeResetChange = { - item: Node; +export type NodeResetChange = { + item: NodeType; type: 'reset'; }; @@ -50,12 +50,12 @@ export type NodeChange = export type EdgeSelectionChange = NodeSelectionChange; export type EdgeRemoveChange = NodeRemoveChange; -export type EdgeAddChange = { - item: Edge; +export type EdgeAddChange = { + item: EdgeType; type: 'add'; }; -export type EdgeResetChange = { - item: Edge; +export type EdgeResetChange = { + item: EdgeType; type: 'reset'; }; export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange | EdgeAddChange | EdgeResetChange; diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index fecd7c7b..836087fc 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -1,6 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { CSSProperties, HTMLAttributes, ReactNode, MouseEvent as ReactMouseEvent, ComponentType } from 'react'; +import type { + CSSProperties, + HTMLAttributes, + ReactNode, + MouseEvent as ReactMouseEvent, + ComponentType, + MemoExoticComponent, +} from 'react'; import type { EdgeBase, BezierPathOptions, @@ -119,6 +126,9 @@ export type BaseEdgeProps = EdgeLabelOptions & { style?: CSSProperties; }; +/** + * Helper type for edge components that get exported by the library. + */ export type EdgeComponentProps = EdgePosition & EdgeLabelOptions & { id?: EdgeProps['id']; @@ -134,10 +144,29 @@ export type EdgeComponentWithPathOptions = EdgeComponentProps & { pathOptions?: PathOptions; }; +/** + * BezierEdge component props + */ export type BezierEdgeProps = EdgeComponentWithPathOptions; + +/** + * SmoothStepEdge component props + */ export type SmoothStepEdgeProps = EdgeComponentWithPathOptions; + +/** + * StepEdge component props + */ export type StepEdgeProps = EdgeComponentWithPathOptions; + +/** + * StraightEdge component props + */ export type StraightEdgeProps = Omit; + +/** + * SimpleBezier component props + */ export type SimpleBezierEdgeProps = EdgeComponentProps; export type OnEdgeUpdateFunc = (oldEdge: Edge, newConnection: Connection) => void; @@ -157,3 +186,6 @@ export type ConnectionLineComponentProps = { }; export type ConnectionLineComponent = ComponentType; + +export type EdgeTypes = { [key: string]: ComponentType }; +export type EdgeTypesWrapped = { [key: string]: MemoExoticComponent> }; diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index b2e7098a..a212b057 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -1,9 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { ComponentType, MemoExoticComponent } from 'react'; import { FitViewParamsBase, FitViewOptionsBase, - NodeProps, ZoomInOut, ZoomTo, SetViewport, @@ -14,7 +12,7 @@ import { XYPosition, } from '@xyflow/system'; -import type { NodeChange, EdgeChange, Node, WrapNodeProps, Edge, EdgeProps, WrapEdgeProps, ReactFlowInstance } from '.'; +import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance } from '.'; export type OnNodesChange = (changes: NodeChange[]) => void; export type OnEdgesChange = (changes: EdgeChange[]) => void; @@ -23,11 +21,6 @@ export type OnNodesDelete = (nodes: Node[]) => void; export type OnEdgesDelete = (edges: Edge[]) => void; export type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void; -export type NodeTypes = { [key: string]: ComponentType }; -export type NodeTypesWrapped = { [key: string]: MemoExoticComponent> }; -export type EdgeTypes = { [key: string]: ComponentType }; -export type EdgeTypesWrapped = { [key: string]: MemoExoticComponent> }; - export type UnselectNodesAndEdgesParams = { nodes?: Node[]; edges?: Edge[]; @@ -43,7 +36,9 @@ export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void; export type FitViewParams = FitViewParamsBase; export type FitViewOptions = FitViewOptionsBase; export type FitView = (fitViewOptions?: FitViewOptions) => boolean; -export type OnInit = (reactFlowInstance: ReactFlowInstance) => void; +export type OnInit = ( + reactFlowInstance: ReactFlowInstance +) => void; export type ViewportHelperFunctions = { zoomIn: ZoomInOut; diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index f229ec87..6ac81e04 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -3,9 +3,9 @@ import type { Rect, Viewport } from '@xyflow/system'; import type { Node, Edge, ViewportHelperFunctions } from '.'; -export type ReactFlowJsonObject = { - nodes: Node[]; - edges: Edge[]; +export type ReactFlowJsonObject = { + nodes: NodeType[]; + edges: EdgeType[]; viewport: Viewport; }; @@ -15,30 +15,33 @@ export type DeleteElementsOptions = { }; export namespace Instance { - export type GetNodes = () => Node[]; - export type SetNodes = ( - payload: Node[] | ((nodes: Node[]) => Node[]) + export type GetNodes = () => NodeType[]; + export type SetNodes = ( + payload: NodeType[] | ((nodes: NodeType[]) => NodeType[]) ) => void; - export type AddNodes = (payload: Node[] | Node) => void; - export type GetNode = (id: string) => Node | undefined; - export type GetEdges = () => Edge[]; - export type SetEdges = ( - payload: Edge[] | ((edges: Edge[]) => Edge[]) + export type AddNodes = (payload: NodeType[] | NodeType) => void; + export type GetNode = (id: string) => NodeType | undefined; + export type GetEdges = () => EdgeType[]; + export type SetEdges = ( + payload: EdgeType[] | ((edges: EdgeType[]) => EdgeType[]) ) => void; - export type GetEdge = (id: string) => Edge | undefined; - export type AddEdges = (payload: Edge[] | Edge) => void; - export type ToObject = () => ReactFlowJsonObject; + export type GetEdge = (id: string) => EdgeType | undefined; + export type AddEdges = (payload: EdgeType[] | EdgeType) => void; + export type ToObject = () => ReactFlowJsonObject< + NodeType, + EdgeType + >; export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => { deletedNodes: Node[]; deletedEdges: Edge[]; }; - export type GetIntersectingNodes = ( - node: Node | { id: Node['id'] } | Rect, + export type GetIntersectingNodes = ( + node: NodeType | { id: Node['id'] } | Rect, partially?: boolean, - nodes?: Node[] - ) => Node[]; - export type IsNodeIntersecting = ( - node: Node | { id: Node['id'] } | Rect, + nodes?: NodeType[] + ) => NodeType[]; + export type IsNodeIntersecting = ( + node: NodeType | { id: Node['id'] } | Rect, area: Rect, partially?: boolean ) => boolean; @@ -47,18 +50,18 @@ export namespace Instance { export type getOutgoers = (node: string | Node | { id: Node['id'] }) => Node[]; } -export type ReactFlowInstance = { - getNodes: Instance.GetNodes; - setNodes: Instance.SetNodes; - addNodes: Instance.AddNodes; - getNode: Instance.GetNode; - getEdges: Instance.GetEdges; - setEdges: Instance.SetEdges; - addEdges: Instance.AddEdges; - getEdge: Instance.GetEdge; - toObject: Instance.ToObject; +export type ReactFlowInstance = { + getNodes: Instance.GetNodes; + setNodes: Instance.SetNodes; + addNodes: Instance.AddNodes; + getNode: Instance.GetNode; + getEdges: Instance.GetEdges; + setEdges: Instance.SetEdges; + addEdges: Instance.AddEdges; + getEdge: Instance.GetEdge; + toObject: Instance.ToObject; deleteElements: Instance.DeleteElements; - getIntersectingNodes: Instance.GetIntersectingNodes; - isNodeIntersecting: Instance.IsNodeIntersecting; + getIntersectingNodes: Instance.GetIntersectingNodes; + isNodeIntersecting: Instance.IsNodeIntersecting; viewportInitialized: boolean; } & Omit; diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 4d10b76b..7bccfe0a 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -1,5 +1,6 @@ -import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; -import type { NodeBase, XYPosition } from '@xyflow/system'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { CSSProperties, ComponentType, MemoExoticComponent, MouseEvent as ReactMouseEvent } from 'react'; +import type { NodeBase, NodeProps, XYPosition } from '@xyflow/system'; /** * The node data structure that gets used for the nodes prop. @@ -9,17 +10,8 @@ export type Node & { - /** - * Inline style object - */ style?: CSSProperties; - /** - * Inline style object - */ className?: string; - /** - * Inline style object - */ resizing?: boolean; }; @@ -27,8 +19,8 @@ export type NodeMouseHandler = (event: ReactMouseEvent, node: Node) => void; export type NodeDragHandler = (event: ReactMouseEvent, node: Node, nodes: Node[]) => void; export type SelectionDragHandler = (event: ReactMouseEvent, nodes: Node[]) => void; -export type WrapNodeProps = Pick< - Node, +export type WrapNodeProps = Pick< + NodeType, 'id' | 'data' | 'style' | 'className' | 'dragHandle' | 'sourcePosition' | 'targetPosition' | 'hidden' | 'ariaLabel' > & { type: string; @@ -59,3 +51,6 @@ export type WrapNodeProps = Pick< width?: number; height?: number; }; + +export type NodeTypes = { [key: string]: ComponentType }; +export type NodeTypesWrapped = { [key: string]: MemoExoticComponent> }; diff --git a/packages/react/src/utils/changes.ts b/packages/react/src/utils/changes.ts index 0cf87e6f..72e28b9e 100644 --- a/packages/react/src/utils/changes.ts +++ b/packages/react/src/utils/changes.ts @@ -159,8 +159,8 @@ function applyChanges(changes: any[], elements: any[]): any[] { ); */ -export function applyNodeChanges(changes: NodeChange[], nodes: Node[]): Node[] { - return applyChanges(changes, nodes) as Node[]; +export function applyNodeChanges(changes: NodeChange[], nodes: NodeType[]): NodeType[] { + return applyChanges(changes, nodes) as NodeType[]; } /** @@ -183,8 +183,8 @@ export function applyNodeChanges(changes: NodeChange[], nodes: N ); */ -export function applyEdgeChanges(changes: EdgeChange[], edges: Edge[]): Edge[] { - return applyChanges(changes, edges) as Edge[]; +export function applyEdgeChanges(changes: EdgeChange[], edges: EdgeType[]): EdgeType[] { + return applyChanges(changes, edges) as EdgeType[]; } export const createSelectionChange = (id: string, selected: boolean) => ({ diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index c1d3778d..218c7e6f 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -53,6 +53,9 @@ export type EdgeProps = Omit, 'sourceHandle' | 'targetHandle' | targetHandleId?: string | null; }; +/** + * Helper type for edge components that get exported by the library. + */ export type EdgeComponentProps = EdgePosition & { id?: EdgeProps['id']; hidden?: EdgeProps['hidden']; @@ -73,9 +76,24 @@ export type EdgeComponentWithPathOptions = EdgeComponentProps & { pathOptions?: PathOptions; }; +/** + * BezierEdge component props + */ export type BezierEdgeProps = EdgeComponentWithPathOptions; + +/** + * SmoothStepEdge component props + */ export type SmoothStepEdgeProps = EdgeComponentWithPathOptions; + +/** + * StepEdge component props + */ export type StepEdgeProps = EdgeComponentWithPathOptions; + +/** + * StraightEdge component props + */ export type StraightEdgeProps = Omit; export type EdgeTypes = Record>>; diff --git a/packages/svelte/src/lib/types/nodes.ts b/packages/svelte/src/lib/types/nodes.ts index 292ae4fa..4121754a 100644 --- a/packages/svelte/src/lib/types/nodes.ts +++ b/packages/svelte/src/lib/types/nodes.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ - import type { ComponentType, SvelteComponent } from 'svelte'; import type { NodeBase, NodeProps } from '@xyflow/system'; -// @todo: currently the helper function only like Node from '@reactflow/core' -// we need a base node type or helpes that accept Node like types -// eslint-disable-next-line @typescript-eslint/no-explicit-any +/** + * The node data structure that gets used for the nodes prop. + * @public + */ export type Node< NodeData = any, NodeType extends string | undefined = string | undefined From 2681ead50cbcf8d0081d0def87e8fa27449f4267 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 19 Dec 2023 12:36:49 +0100 Subject: [PATCH 09/10] chore(react/hooks): add docs --- packages/react/src/hooks/useColorModeClass.ts | 6 ++ packages/react/src/hooks/useDrag.ts | 5 ++ packages/react/src/hooks/useEdges.ts | 6 ++ .../react/src/hooks/useGlobalKeyHandler.ts | 5 ++ packages/react/src/hooks/useKeyPress.ts | 16 +++-- packages/react/src/hooks/useNodes.ts | 6 ++ packages/react/src/hooks/useNodesData.ts | 8 +++ .../react/src/hooks/useNodesEdgesState.ts | 17 +++++- .../react/src/hooks/useNodesInitialized.ts | 7 +++ packages/react/src/hooks/useOnInitHandler.ts | 5 ++ .../react/src/hooks/useOnSelectionChange.ts | 6 ++ .../react/src/hooks/useOnViewportChange.ts | 8 +++ packages/react/src/hooks/useReactFlow.ts | 61 +++---------------- packages/react/src/hooks/useResizeHandler.ts | 5 ++ packages/react/src/hooks/useStore.ts | 8 +++ .../react/src/hooks/useUpdateNodeInternals.ts | 6 ++ .../react/src/hooks/useUpdateNodePositions.ts | 6 ++ packages/react/src/hooks/useViewport.ts | 6 ++ packages/react/src/hooks/useViewportHelper.ts | 6 ++ packages/react/src/hooks/useViewportSync.ts | 6 ++ packages/react/src/hooks/useVisibleEdgeIds.ts | 7 +++ packages/react/src/hooks/useVisibleNodeIds.ts | 7 +++ packages/react/src/types/changes.ts | 4 ++ packages/react/src/types/component-props.ts | 4 ++ packages/react/src/types/edges.ts | 17 ++++-- packages/react/src/types/instance.ts | 2 +- 26 files changed, 175 insertions(+), 65 deletions(-) diff --git a/packages/react/src/hooks/useColorModeClass.ts b/packages/react/src/hooks/useColorModeClass.ts index b66f8cea..bcf8c6af 100644 --- a/packages/react/src/hooks/useColorModeClass.ts +++ b/packages/react/src/hooks/useColorModeClass.ts @@ -9,6 +9,12 @@ function getMediaQuery() { return window.matchMedia('(prefers-color-scheme: dark)'); } +/** + * Hook for receiving the current color mode class 'dark' or 'light'. + * + * @internal + * @param colorMode - The color mode to use ('dark', 'light' or 'system') + */ export default function useColorModeClass(colorMode: ColorMode): ColorModeClass { const [colorModeClass, setColorModeClass] = useState( colorMode === 'system' ? null : colorMode diff --git a/packages/react/src/hooks/useDrag.ts b/packages/react/src/hooks/useDrag.ts index 3b332d8a..8a4fab65 100644 --- a/packages/react/src/hooks/useDrag.ts +++ b/packages/react/src/hooks/useDrag.ts @@ -13,6 +13,11 @@ type UseDragParams = { isSelectable?: boolean; }; +/** + * Hook for calling XYDrag helper from @xyflow/system. + * + * @internal + */ function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, nodeId, isSelectable }: UseDragParams) { const store = useStoreApi(); const [dragging, setDragging] = useState(false); diff --git a/packages/react/src/hooks/useEdges.ts b/packages/react/src/hooks/useEdges.ts index 194f8805..94445ce4 100644 --- a/packages/react/src/hooks/useEdges.ts +++ b/packages/react/src/hooks/useEdges.ts @@ -5,6 +5,12 @@ import type { Edge, ReactFlowState } from '../types'; const edgesSelector = (state: ReactFlowState) => state.edges; +/** + * Hook for getting the current edges from the store. + * + * @public + * @returns An array of edges + */ function useEdges(): Edge[] { const edges = useStore(edgesSelector, shallow); diff --git a/packages/react/src/hooks/useGlobalKeyHandler.ts b/packages/react/src/hooks/useGlobalKeyHandler.ts index c243298d..786ed870 100644 --- a/packages/react/src/hooks/useGlobalKeyHandler.ts +++ b/packages/react/src/hooks/useGlobalKeyHandler.ts @@ -10,6 +10,11 @@ const selected = (item: Node | Edge) => item.selected; const deleteKeyOptions: UseKeyPressOptions = { actInsideInputWithModifier: false }; +/** + * Hook for handling global key events. + * + * @internal + */ export default ({ deleteKeyCode, multiSelectionKeyCode, diff --git a/packages/react/src/hooks/useKeyPress.ts b/packages/react/src/hooks/useKeyPress.ts index f897a6c4..98d460d0 100644 --- a/packages/react/src/hooks/useKeyPress.ts +++ b/packages/react/src/hooks/useKeyPress.ts @@ -12,11 +12,19 @@ export type UseKeyPressOptions = { const defaultDoc = typeof document !== 'undefined' ? document : null; -// the keycode can be a string 'a' or an array of strings ['a', 'a+d'] -// a string means a single key 'a' or a combination when '+' is used 'a+d' -// an array means different possibilites. Explainer: ['a', 'd+s'] here the -// user can use the single key 'a' or the combination 'd' + 's' +/** + * Hook for handling key events. + * + * @public + * @param param.keyCode - The key code (string or array of strings) to use + * @param param.options - Options + * @returns boolean + */ export default ( + // the keycode can be a string 'a' or an array of strings ['a', 'a+d'] + // a string means a single key 'a' or a combination when '+' is used 'a+d' + // an array means different possibilites. Explainer: ['a', 'd+s'] here the + // user can use the single key 'a' or the combination 'd' + 's' keyCode: KeyCode | null = null, options: UseKeyPressOptions = { target: defaultDoc, actInsideInputWithModifier: true } ): boolean => { diff --git a/packages/react/src/hooks/useNodes.ts b/packages/react/src/hooks/useNodes.ts index 1ccd1d85..bb54865d 100644 --- a/packages/react/src/hooks/useNodes.ts +++ b/packages/react/src/hooks/useNodes.ts @@ -5,6 +5,12 @@ import type { Node, ReactFlowState } from '../types'; const nodesSelector = (state: ReactFlowState) => state.nodes; +/** + * Hook for getting the current nodes from the store. + * + * @public + * @returns An array of nodes + */ function useNodes(): NodeType[] { const nodes = useStore(nodesSelector, shallow) as NodeType[]; diff --git a/packages/react/src/hooks/useNodesData.ts b/packages/react/src/hooks/useNodesData.ts index 4c242ad3..e209df22 100644 --- a/packages/react/src/hooks/useNodesData.ts +++ b/packages/react/src/hooks/useNodesData.ts @@ -4,6 +4,14 @@ import { shallow } from 'zustand/shallow'; import { useStore } from '../hooks/useStore'; import type { Node } from '../types'; +/** + * Hook for receiving data of one or multiple nodes + * + * @public + * @param nodeId - The id (or ids) of the node to get the data from + * @param guard - Optional guard function to narrow down the node type + * @returns An array data objects + */ export function useNodesData(nodeId: string): NodeType['data'] | null; export function useNodesData(nodeIds: string[]): NodeType['data'][]; export function useNodesData( diff --git a/packages/react/src/hooks/useNodesEdgesState.ts b/packages/react/src/hooks/useNodesEdgesState.ts index 5255c7dd..b0976b57 100644 --- a/packages/react/src/hooks/useNodesEdgesState.ts +++ b/packages/react/src/hooks/useNodesEdgesState.ts @@ -7,8 +7,6 @@ import type { Node, NodeChange, Edge, EdgeChange } from '../types'; type ApplyChanges = (changes: ChangesType[], items: ItemType[]) => ItemType[]; type OnChange = (changes: ChangesType[]) => void; -// returns a hook that can be used liked this: -// const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); function createUseItemsState( applyChanges: ApplyChanges ): ( @@ -31,5 +29,20 @@ function createUseItemsState( }; } +/** + * Hook for managing the state of nodes - should only be used for prototyping / simple use cases. + * + * @public + * @param initialNodes + * @returns an array [nodes, setNodes, onNodesChange] + */ export const useNodesState = createUseItemsState(applyNodeChanges); + +/** + * Hook for managing the state of edges - should only be used for prototyping / simple use cases. + * + * @public + * @param initialEdges + * @returns an array [edges, setEdges, onEdgesChange] + */ export const useEdgesState = createUseItemsState(applyEdgeChanges); diff --git a/packages/react/src/hooks/useNodesInitialized.ts b/packages/react/src/hooks/useNodesInitialized.ts index 3e869873..5a330706 100644 --- a/packages/react/src/hooks/useNodesInitialized.ts +++ b/packages/react/src/hooks/useNodesInitialized.ts @@ -21,6 +21,13 @@ const defaultOptions = { includeHiddenNodes: false, }; +/** + * Hook which returns true when all nodes are initialized. + * + * @public + * @param options.includeHiddenNodes - defaults to false + * @returns boolean indicating whether all nodes are initialized + */ function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean { const initialized = useStore(selector(options)); diff --git a/packages/react/src/hooks/useOnInitHandler.ts b/packages/react/src/hooks/useOnInitHandler.ts index 27de912b..e08e3b4c 100644 --- a/packages/react/src/hooks/useOnInitHandler.ts +++ b/packages/react/src/hooks/useOnInitHandler.ts @@ -3,6 +3,11 @@ import { useEffect, useRef } from 'react'; import useReactFlow from './useReactFlow'; import type { OnInit } from '../types'; +/** + * Hook for calling onInit handler. + * + * @internal + */ function useOnInitHandler(onInit: OnInit | undefined) { const rfInstance = useReactFlow(); const isInitialized = useRef(false); diff --git a/packages/react/src/hooks/useOnSelectionChange.ts b/packages/react/src/hooks/useOnSelectionChange.ts index 51df3ddb..bd3541d9 100644 --- a/packages/react/src/hooks/useOnSelectionChange.ts +++ b/packages/react/src/hooks/useOnSelectionChange.ts @@ -7,6 +7,12 @@ export type UseOnSelectionChangeOptions = { onChange: OnSelectionChangeFunc; }; +/** + * Hook for registering an onSelectionChange handler. + * + * @public + * @params params.onChange - The handler to register + */ function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) { const store = useStoreApi(); diff --git a/packages/react/src/hooks/useOnViewportChange.ts b/packages/react/src/hooks/useOnViewportChange.ts index 00ac15a7..13209d62 100644 --- a/packages/react/src/hooks/useOnViewportChange.ts +++ b/packages/react/src/hooks/useOnViewportChange.ts @@ -9,6 +9,14 @@ export type UseOnViewportChangeOptions = { onEnd?: OnViewportChange; }; +/** + * Hook for registering an onViewportChange handler. + * + * @public + * @param params.onStart - gets called when the viewport starts changing + * @param params.onChange - gets called when the viewport changes + * @param params.onEnd - gets called when the viewport stops changing + */ function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) { const store = useStoreApi(); diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index dfe399c6..aa043e99 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -1,13 +1,5 @@ import { useCallback, useMemo } from 'react'; -import { - getElementsToRemove, - getIncomersBase, - getOutgoersBase, - getOverlappingArea, - isRectObject, - nodeToRect, - type Rect, -} from '@xyflow/system'; +import { getElementsToRemove, getOverlappingArea, isRectObject, nodeToRect, type Rect } from '@xyflow/system'; import useViewportHelper from './useViewportHelper'; import { useStoreApi } from './useStore'; @@ -238,48 +230,13 @@ export default function useReactFlow((node) => { - const { edges } = store.getState(); - - const nodeIds = new Set(); - if (typeof node === 'string') { - nodeIds.add(node); - } else if (node.length >= 1) { - node.forEach((n) => { - nodeIds.add(n.id); - }); - } - - return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target)); - }, []); - - const getIncomers = useCallback((node) => { - const { nodes, edges } = store.getState(); - - if (typeof node === 'string') { - return getIncomersBase({ id: node }, nodes, edges); - } - - return getIncomersBase(node, nodes, edges); - }, []); - - const getOutgoers = useCallback((node) => { - const { nodes, edges } = store.getState(); - - if (typeof node == 'string') { - return getOutgoersBase({ id: node }, nodes, edges); - } - - return getOutgoersBase(node, nodes, edges); - }, []); - - const updateNode = useCallback( + const updateNode = useCallback>( (id, nodeUpdate, options = { replace: true }) => { setNodes((prevNodes) => prevNodes.map((node) => { if (node.id === id) { - const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as Node) : nodeUpdate; - return options.replace && isNode(nextNode) ? nextNode : { ...node, ...nextNode }; + const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as NodeType) : nodeUpdate; + return options.replace && isNode(nextNode) ? (nextNode as NodeType) : { ...node, ...nextNode }; } return node; @@ -289,7 +246,7 @@ export default function useReactFlow( + const updateNodeData = useCallback>( (id, dataUpdate, options = { replace: false }) => { updateNode( id, @@ -318,9 +275,6 @@ export default function useReactFlow): void { const store = useStoreApi(); diff --git a/packages/react/src/hooks/useStore.ts b/packages/react/src/hooks/useStore.ts index eb075d00..96587161 100644 --- a/packages/react/src/hooks/useStore.ts +++ b/packages/react/src/hooks/useStore.ts @@ -10,6 +10,14 @@ const zustandErrorMessage = errorMessages['error001'](); type ExtractState = StoreApi extends { getState: () => infer T } ? T : never; +/** + * Hook for accessing the internal store. Should only be used in rare cases. + * + * @public + * @param selector + * @param equalityFn + * @returns The selected state slice + */ function useStore( selector: (state: ReactFlowState) => StateSlice, equalityFn?: (a: StateSlice, b: StateSlice) => boolean diff --git a/packages/react/src/hooks/useUpdateNodeInternals.ts b/packages/react/src/hooks/useUpdateNodeInternals.ts index d14f35f3..ced202da 100644 --- a/packages/react/src/hooks/useUpdateNodeInternals.ts +++ b/packages/react/src/hooks/useUpdateNodeInternals.ts @@ -3,6 +3,12 @@ import type { UpdateNodeInternals, NodeDimensionUpdate } from '@xyflow/system'; import { useStoreApi } from '../hooks/useStore'; +/** + * Hook for updating node internals. + * + * @public + * @returns function for updating node internals + */ function useUpdateNodeInternals(): UpdateNodeInternals { const store = useStoreApi(); diff --git a/packages/react/src/hooks/useUpdateNodePositions.ts b/packages/react/src/hooks/useUpdateNodePositions.ts index 06577601..77def71f 100644 --- a/packages/react/src/hooks/useUpdateNodePositions.ts +++ b/packages/react/src/hooks/useUpdateNodePositions.ts @@ -7,6 +7,12 @@ import { useStoreApi } from '../hooks/useStore'; const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) => n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined')); +/** + * Hook for updating node positions. + * + * @internal + * @returns function for updating node positions + */ function useUpdateNodePositions() { const store = useStoreApi(); diff --git a/packages/react/src/hooks/useViewport.ts b/packages/react/src/hooks/useViewport.ts index c7153fac..4d48cf5c 100644 --- a/packages/react/src/hooks/useViewport.ts +++ b/packages/react/src/hooks/useViewport.ts @@ -10,6 +10,12 @@ const viewportSelector = (state: ReactFlowState) => ({ zoom: state.transform[2], }); +/** + * Hook for getting the current viewport from the store. + * + * @public + * @returns The current viewport + */ function useViewport(): Viewport { const viewport = useStore(viewportSelector, shallow); diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts index f1099b43..481bae15 100644 --- a/packages/react/src/hooks/useViewportHelper.ts +++ b/packages/react/src/hooks/useViewportHelper.ts @@ -12,6 +12,12 @@ import type { ViewportHelperFunctions, ReactFlowState } from '../types'; const selector = (s: ReactFlowState) => !!s.panZoom; +/** + * Hook for getting viewport helper functions. + * + * @internal + * @returns viewport helper functions + */ const useViewportHelper = (): ViewportHelperFunctions => { const store = useStoreApi(); const panZoomInitialized = useStore(selector); diff --git a/packages/react/src/hooks/useViewportSync.ts b/packages/react/src/hooks/useViewportSync.ts index a34cdcd5..f0a1a31e 100644 --- a/packages/react/src/hooks/useViewportSync.ts +++ b/packages/react/src/hooks/useViewportSync.ts @@ -6,6 +6,12 @@ import type { ReactFlowState } from '../types'; const selector = (state: ReactFlowState) => state.panZoom?.syncViewport; +/** + * Hook for syncing the viewport with the panzoom instance. + * + * @internal + * @param viewport + */ export default function useViewportSync(viewport?: Viewport) { const syncViewport = useStore(selector); const store = useStoreApi(); diff --git a/packages/react/src/hooks/useVisibleEdgeIds.ts b/packages/react/src/hooks/useVisibleEdgeIds.ts index eaa4eb2d..ceb40f31 100644 --- a/packages/react/src/hooks/useVisibleEdgeIds.ts +++ b/packages/react/src/hooks/useVisibleEdgeIds.ts @@ -5,6 +5,13 @@ import { isEdgeVisible } from '@xyflow/system'; import { useStore } from './useStore'; import { type ReactFlowState } from '../types'; +/** + * Hook for getting the visible edge ids from the store. + * + * @internal + * @param onlyRenderVisible + * @returns array with visible edge ids + */ function useVisibleEdgeIds(onlyRenderVisible: boolean): string[] { const edgeIds = useStore( useCallback( diff --git a/packages/react/src/hooks/useVisibleNodeIds.ts b/packages/react/src/hooks/useVisibleNodeIds.ts index 23b29096..803fde6d 100644 --- a/packages/react/src/hooks/useVisibleNodeIds.ts +++ b/packages/react/src/hooks/useVisibleNodeIds.ts @@ -13,6 +13,13 @@ const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => { : Array.from(s.nodeLookup.keys()); }; +/** + * Hook for getting the visible node ids from the store. + * + * @internal + * @param onlyRenderVisible + * @returns array with visible node ids + */ function useVisibleNodeIds(onlyRenderVisible: boolean) { const nodeIds = useStore(useCallback(selector(onlyRenderVisible), [onlyRenderVisible]), shallow); diff --git a/packages/react/src/types/changes.ts b/packages/react/src/types/changes.ts index 440a4c6b..f1282574 100644 --- a/packages/react/src/types/changes.ts +++ b/packages/react/src/types/changes.ts @@ -40,6 +40,10 @@ export type NodeResetChange = { type: 'reset'; }; +/** + * Union type of all possible node changes. + * @public + */ export type NodeChange = | NodeDimensionChange | NodePositionChange diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index 09f9a582..43dc5e90 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -45,6 +45,10 @@ import type { EdgeMouseHandler, } from '.'; +/** + * ReactFlow component props. + * @public + */ export type ReactFlowProps = Omit, 'onError'> & { nodes?: Node[]; edges?: Edge[]; diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index ec48db9d..faae7501 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -54,7 +54,8 @@ type StepEdgeType = DefaultEdge & { }; /** - * The Edge type is mainly used for the `edges` that get passed to the ReactFlow component. + * The Edge type is mainly used for the `edges` that get passed to the ReactFlow component + * @public */ export type Edge = DefaultEdge | SmoothStepEdgeType | BezierEdgeType | StepEdgeType; @@ -92,7 +93,8 @@ export type EdgeTextProps = HTMLAttributes & }; /** - * Custom edge component props. + * Custom edge component props + * @public */ export type EdgeProps = Pick< Edge, @@ -110,7 +112,8 @@ export type EdgeProps = Pick< }; /** - * BaseEdge component props. + * BaseEdge component props + * @public */ export type BaseEdgeProps = EdgeLabelOptions & { id?: string; @@ -124,7 +127,8 @@ export type BaseEdgeProps = EdgeLabelOptions & { }; /** - * Helper type for edge components that get exported by the library. + * Helper type for edge components that get exported by the library + * @public */ export type EdgeComponentProps = EdgePosition & EdgeLabelOptions & { @@ -143,26 +147,31 @@ export type EdgeComponentWithPathOptions = EdgeComponentProps & { /** * BezierEdge component props + * @public */ export type BezierEdgeProps = EdgeComponentWithPathOptions; /** * SmoothStepEdge component props + * @public */ export type SmoothStepEdgeProps = EdgeComponentWithPathOptions; /** * StepEdge component props + * @public */ export type StepEdgeProps = EdgeComponentWithPathOptions; /** * StraightEdge component props + * @public */ export type StraightEdgeProps = Omit; /** * SimpleBezier component props + * @public */ export type SimpleBezierEdgeProps = EdgeComponentProps; diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index 05eb1d4b..878e85a8 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -51,7 +51,7 @@ export namespace Instance { export type UpdateNode = ( id: string, - dataUpdate: Partial | ((node: NodeType) => Partial), + nodeUpdate: Partial | ((node: NodeType) => Partial), options?: { replace: boolean } ) => void; export type UpdateNodeData = ( From ac4ac77a854944621d030868adfd3486fd56eb47 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 19 Dec 2023 13:07:07 +0100 Subject: [PATCH 10/10] chore(svelte/hooks): add docs --- packages/react/src/hooks/useHandleConnections.ts | 2 +- packages/react/src/hooks/useNodesData.ts | 2 +- packages/react/src/hooks/useReactFlow.ts | 7 ++++++- packages/svelte/src/lib/hooks/useColorModeClass.ts | 6 ++++++ packages/svelte/src/lib/hooks/useConnection.ts | 6 ++++++ .../svelte/src/lib/hooks/useHandleConnections.ts | 9 +++++++++ packages/svelte/src/lib/hooks/useNodesData.ts | 8 ++++++++ packages/svelte/src/lib/hooks/useNodesEdges.ts | 12 ++++++++++++ packages/svelte/src/lib/hooks/useSvelteFlow.ts | 6 ++++++ .../svelte/src/lib/hooks/useUpdateNodeInternals.ts | 6 ++++++ 10 files changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/react/src/hooks/useHandleConnections.ts b/packages/react/src/hooks/useHandleConnections.ts index c9a026d9..7232cce4 100644 --- a/packages/react/src/hooks/useHandleConnections.ts +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -17,8 +17,8 @@ type useHandleConnectionsParams = { * * @public * @param param.type - handle type 'source' or 'target' - * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type) * @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used + * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type) * @param param.onConnect - gets called when a connection is established * @param param.onDisconnect - gets called when a connection is removed * @returns an array with connections diff --git a/packages/react/src/hooks/useNodesData.ts b/packages/react/src/hooks/useNodesData.ts index e209df22..10480caa 100644 --- a/packages/react/src/hooks/useNodesData.ts +++ b/packages/react/src/hooks/useNodesData.ts @@ -10,7 +10,7 @@ import type { Node } from '../types'; * @public * @param nodeId - The id (or ids) of the node to get the data from * @param guard - Optional guard function to narrow down the node type - * @returns An array data objects + * @returns An array od data objects */ export function useNodesData(nodeId: string): NodeType['data'] | null; export function useNodesData(nodeIds: string[]): NodeType['data'][]; diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index aa043e99..271e0ebf 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -18,7 +18,12 @@ import type { } from '../types'; import { isNode } from '../utils'; -/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ +/** + * Hook for accessing the ReactFlow instance. + * + * @public + * @returns ReactFlowInstance + */ export default function useReactFlow(): ReactFlowInstance< NodeType, EdgeType diff --git a/packages/svelte/src/lib/hooks/useColorModeClass.ts b/packages/svelte/src/lib/hooks/useColorModeClass.ts index ad163a3f..07a9a824 100644 --- a/packages/svelte/src/lib/hooks/useColorModeClass.ts +++ b/packages/svelte/src/lib/hooks/useColorModeClass.ts @@ -9,6 +9,12 @@ function getMediaQuery() { return window.matchMedia('(prefers-color-scheme: dark)'); } +/** + * Hook for receiving the current color mode class 'dark' or 'light'. + * + * @internal + * @param colorMode - The color mode to use ('dark', 'light' or 'system') + */ export function useColorModeClass(colorMode: ColorMode = 'light'): Readable { const colorModeClass = readable('light', (set) => { if (colorMode !== 'system') { diff --git a/packages/svelte/src/lib/hooks/useConnection.ts b/packages/svelte/src/lib/hooks/useConnection.ts index 6db9e735..967b4e51 100644 --- a/packages/svelte/src/lib/hooks/useConnection.ts +++ b/packages/svelte/src/lib/hooks/useConnection.ts @@ -3,6 +3,12 @@ import type { Readable } from 'svelte/store'; import { useStore } from '$lib/store'; import type { ConnectionProps } from '$lib/store/derived-connection-props'; +/** + * Hook for receiving the current connection. + * + * @public + * @returns current connection as a readable store + */ export function useConnection(): Readable { const { connection } = useStore(); diff --git a/packages/svelte/src/lib/hooks/useHandleConnections.ts b/packages/svelte/src/lib/hooks/useHandleConnections.ts index c8df0dae..27e61c11 100644 --- a/packages/svelte/src/lib/hooks/useHandleConnections.ts +++ b/packages/svelte/src/lib/hooks/useHandleConnections.ts @@ -11,6 +11,15 @@ export type useHandleConnectionsParams = { const initialConnections: Connection[] = []; +/** + * Hook to check if a is connected to another and get the connections. + * + * @public + * @param param.nodeId + * @param param.type - handle type 'source' or 'target' + * @param param.id - the handle id (this is only needed if the node has multiple handles of the same type) + * @returns an array with connections + */ export function useHandleConnections({ nodeId, type, id = null }: useHandleConnectionsParams) { const { edges, connectionLookup } = useStore(); let prevConnections: Map | undefined = undefined; diff --git a/packages/svelte/src/lib/hooks/useNodesData.ts b/packages/svelte/src/lib/hooks/useNodesData.ts index f50bc8b8..8e24645c 100644 --- a/packages/svelte/src/lib/hooks/useNodesData.ts +++ b/packages/svelte/src/lib/hooks/useNodesData.ts @@ -21,6 +21,14 @@ function areNodesDataEqual(a: Node['data'][] | null, b: Node['data'][] | null) { return true; } +/** + * Hook for receiving data of one or multiple nodes + * + * @public + * @param nodeId - The id (or ids) of the node to get the data from + * @param guard - Optional guard function to narrow down the node type + * @returns A readable store with an array of data objects + */ export function useNodesData( nodeId: string ): Readable; diff --git a/packages/svelte/src/lib/hooks/useNodesEdges.ts b/packages/svelte/src/lib/hooks/useNodesEdges.ts index 64e953c4..062f596d 100644 --- a/packages/svelte/src/lib/hooks/useNodesEdges.ts +++ b/packages/svelte/src/lib/hooks/useNodesEdges.ts @@ -1,10 +1,22 @@ import { useStore } from '$lib/store'; +/** + * Hook for getting the current nodes from the store. + * + * @public + * @returns store with an array of nodes + */ export function useNodes() { const { nodes } = useStore(); return nodes; } +/** + * Hook for getting the current edges from the store. + * + * @public + * @returns store with an array of edges + */ export function useEdges() { const { edges } = useStore(); return edges; diff --git a/packages/svelte/src/lib/hooks/useSvelteFlow.ts b/packages/svelte/src/lib/hooks/useSvelteFlow.ts index 6e66a998..74faa913 100644 --- a/packages/svelte/src/lib/hooks/useSvelteFlow.ts +++ b/packages/svelte/src/lib/hooks/useSvelteFlow.ts @@ -20,6 +20,12 @@ import { useStore } from '$lib/store'; import type { Edge, FitViewOptions, Node } from '$lib/types'; import { isNode } from '$lib/utils'; +/** + * Hook for accessing the ReactFlow instance. + * + * @public + * @returns helper functions + */ export function useSvelteFlow(): { zoomIn: ZoomInOut; zoomOut: ZoomInOut; diff --git a/packages/svelte/src/lib/hooks/useUpdateNodeInternals.ts b/packages/svelte/src/lib/hooks/useUpdateNodeInternals.ts index dad8ed88..45a203eb 100644 --- a/packages/svelte/src/lib/hooks/useUpdateNodeInternals.ts +++ b/packages/svelte/src/lib/hooks/useUpdateNodeInternals.ts @@ -3,6 +3,12 @@ import type { UpdateNodeInternals } from '@xyflow/system'; import { useStore } from '$lib/store'; +/** + * Hook for updating node internals. + * + * @public + * @returns function for updating node internals + */ export function useUpdateNodeInternals(): UpdateNodeInternals { const { domNode, updateNodeDimensions } = useStore();