From e352f3cdc493d4b991cfd8ce151bf749640b133f Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 25 Jan 2024 17:10:44 +0100 Subject: [PATCH] chore(ts): add annotations for useReactFlow/ useSvelteFlow --- .../examples/add-node-on-drop/Flow.svelte | 2 +- packages/react/src/hooks/useConnection.ts | 20 ++- .../react/src/hooks/useHandleConnections.ts | 2 +- .../react/src/hooks/useOnSelectionChange.ts | 2 +- packages/react/src/hooks/useViewportHelper.ts | 14 +- packages/react/src/types/general.ts | 78 ++++++++- packages/react/src/types/instance.ts | 93 ++++++++++ .../svelte/src/lib/hooks/useSvelteFlow.ts | 161 +++++++++++++++++- 8 files changed, 351 insertions(+), 21 deletions(-) diff --git a/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte b/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte index 809ae82c..e767e4ac 100644 --- a/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte +++ b/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte @@ -34,7 +34,7 @@ // See of connection landed inside the flow pane const targetIsPane = (event.target as HTMLDivElement)?.classList.contains('svelte-flow__pane'); - if (targetIsPane) { + if (targetIsPane && 'clientX' in event && 'clientY' in event) { const id = getId(); const position = { x: event.clientX, diff --git a/packages/react/src/hooks/useConnection.ts b/packages/react/src/hooks/useConnection.ts index 3544f31f..9b458e87 100644 --- a/packages/react/src/hooks/useConnection.ts +++ b/packages/react/src/hooks/useConnection.ts @@ -10,18 +10,24 @@ const selector = (s: ReactFlowStore) => ({ position: s.connectionStartHandle ? s.connectionPosition : null, }); +type UseConnectionResult = { + /** The start handle where the user interaction started or null */ + startHandle: ReactFlowStore['connectionStartHandle']; + /** The target handle that's inside the connection radius or null */ + endHandle: ReactFlowStore['connectionEndHandle']; + /** The current connection status 'valid', 'invalid' or null*/ + status: ReactFlowStore['connectionStatus']; + /** The current connection position or null */ + position: ReactFlowStore['connectionPosition'] | null; +}; + /** * Hook for accessing the ongoing connection. * * @public - * @returns ongoing connection: startHandle, endHandle, status, position + * @returns ongoing connection */ -export function useConnection(): { - startHandle: ReactFlowStore['connectionStartHandle']; - endHandle: ReactFlowStore['connectionEndHandle']; - status: ReactFlowStore['connectionStatus']; - position: ReactFlowStore['connectionPosition'] | null; -} { +export function useConnection(): UseConnectionResult { const ongoingConnection = useStore(selector, shallow); return ongoingConnection; diff --git a/packages/react/src/hooks/useHandleConnections.ts b/packages/react/src/hooks/useHandleConnections.ts index 7232cce4..4f62615e 100644 --- a/packages/react/src/hooks/useHandleConnections.ts +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -13,7 +13,7 @@ type useHandleConnectionsParams = { }; /** - * Hook to check if a is connected to another and get the connections. + * Hook to check if a is connected to another and get the connections. * * @public * @param param.type - handle type 'source' or 'target' diff --git a/packages/react/src/hooks/useOnSelectionChange.ts b/packages/react/src/hooks/useOnSelectionChange.ts index 53c48891..8e08191b 100644 --- a/packages/react/src/hooks/useOnSelectionChange.ts +++ b/packages/react/src/hooks/useOnSelectionChange.ts @@ -11,7 +11,7 @@ export type UseOnSelectionChangeOptions = { * Hook for registering an onSelectionChange handler. * * @public - * @params params.onChange - The handler to register + * @param params.onChange - The handler to register */ export function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) { const store = useStoreApi(); diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts index 39320459..0e27d933 100644 --- a/packages/react/src/hooks/useViewportHelper.ts +++ b/packages/react/src/hooks/useViewportHelper.ts @@ -86,31 +86,31 @@ const useViewportHelper = (): ViewportHelperFunctions => { panZoom?.setViewport(viewport, { duration: options?.duration }); }, - screenToFlowPosition: (position: XYPosition, options: { snapToGrid: boolean } = { snapToGrid: true }) => { + screenToFlowPosition: (clientPosition: XYPosition, options: { snapToGrid: boolean } = { snapToGrid: true }) => { const { transform, snapGrid, domNode } = store.getState(); if (!domNode) { - return position; + return clientPosition; } const { x: domX, y: domY } = domNode.getBoundingClientRect(); const correctedPosition = { - x: position.x - domX, - y: position.y - domY, + x: clientPosition.x - domX, + y: clientPosition.y - domY, }; return pointToRendererPoint(correctedPosition, transform, options.snapToGrid, snapGrid); }, - flowToScreenPosition: (position: XYPosition) => { + flowToScreenPosition: (flowPosition: XYPosition) => { const { transform, domNode } = store.getState(); if (!domNode) { - return position; + return flowPosition; } const { x: domX, y: domY } = domNode.getBoundingClientRect(); - const rendererPosition = rendererPointToPoint(position, transform); + const rendererPosition = rendererPointToPoint(flowPosition, transform); return { x: rendererPosition.x + domX, diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index 53d95d68..4f3f127a 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -47,17 +47,91 @@ export type OnInit = ) => void; export type ViewportHelperFunctions = { + /** + * Zooms viewport in by 1.2. + * + * @param options.duration - optional duration. If set, a transition will be applied + */ zoomIn: ZoomInOut; + /** + * Zooms viewport out by 1 / 1.2. + * + * @param options.duration - optional duration. If set, a transition will be applied + */ zoomOut: ZoomInOut; + /** + * Sets the current zoom level. + * + * @param zoomLevel - the zoom level to set + * @param options.duration - optional duration. If set, a transition will be applied + */ zoomTo: ZoomTo; + /** + * Returns the current zoom level. + * + * @returns current zoom as a number + */ getZoom: GetZoom; + /** + * Sets the current viewport. + * + * @param viewport - the viewport to set + * @param options.duration - optional duration. If set, a transition will be applied + */ setViewport: SetViewport; + /** + * Returns the current viewport. + * + * @returns Viewport + */ getViewport: GetViewport; + /** + * Fits the view. + * + * @param options.padding - optional padding + * @param options.includeHiddenNodes - optional includeHiddenNodes + * @param options.minZoom - optional minZoom + * @param options.maxZoom - optional maxZoom + * @param options.duration - optional duration. If set, a transition will be applied + * @param options.nodes - optional nodes to fit the view to + */ fitView: FitView; + /** + * Sets the center of the view to the given position. + * + * @param x - x position + * @param y - y position + * @param options.zoom - optional zoom + */ setCenter: SetCenter; + /** + * Fits the view to the given bounds . + * + * @param bounds - the bounds ({ x: number, y: number, width: number, height: number }) to fit the view to + * @param options.padding - optional padding + */ fitBounds: FitBounds; - screenToFlowPosition: (position: XYPosition, options?: { snapToGrid: boolean }) => XYPosition; - flowToScreenPosition: (position: XYPosition) => XYPosition; + /** + * Converts a screen / client position to a flow position. + * + * @param clientPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY + * @param options.snapToGrid - if true, the converted position will be snapped to the grid + * @returns position as { x: number, y: number } + * + * @example + * const flowPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY }) + */ + screenToFlowPosition: (clientPosition: XYPosition, options?: { snapToGrid: boolean }) => XYPosition; + /** + * Converts a flow position to a screen / client position. + * + * @param flowPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY + * @returns position as { x: number, y: number } + * + * @example + * const clientPosition = flowToScreenPosition({ x: node.position.x, y: node.position.y }) + */ + flowToScreenPosition: (flowPosition: XYPosition) => XYPosition; viewportInitialized: boolean; }; diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index 6b37fd14..d34c23dc 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -59,19 +59,112 @@ export namespace Instance { } export type ReactFlowInstance = { + /** + * Returns nodes. + * + * @returns nodes array + */ getNodes: Instance.GetNodes; + /** + * Sets nodes. + * + * @param payload - the nodes to set or a function that receives the current nodes and returns the new nodes + */ setNodes: Instance.SetNodes; + /** + * Adds nodes. + * + * @param payload - the nodes to add + */ addNodes: Instance.AddNodes; + /** + * Returns a node by id. + * + * @param id - the node id + * @returns the node or undefined if no node was found + */ getNode: Instance.GetNode; + /** + * Returns edges. + * + * @returns edges array + */ getEdges: Instance.GetEdges; + /** + * Sets edges. + * + * @param payload - the edges to set or a function that receives the current edges and returns the new edges + */ setEdges: Instance.SetEdges; + /** + * Adds edges. + * + * @param payload - the edges to add + */ addEdges: Instance.AddEdges; + /** + * Returns an edge by id. + * + * @param id - the edge id + * @returns the edge or undefined if no edge was found + */ getEdge: Instance.GetEdge; + /** + * Returns the nodes, edges and the viewport as a JSON object. + * + * @returns the nodes, edges and the viewport as a JSON object + */ toObject: Instance.ToObject; + /** + * Deletes nodes and edges. + * + * @param params.nodes - optional nodes array to delete + * @param params.edges - optional edges array to delete + * + * @returns a promise that resolves with the deleted nodes and edges + */ deleteElements: Instance.DeleteElements; + /** + * Returns all nodes that intersect with the given node or rect. + * + * @param node - the node or rect to check for intersections + * @param partially - if true, the node is considered to be intersecting if it partially overlaps with the passed node or rect + * @param nodes - optional nodes array to check for intersections + * + * @returns an array of intersecting nodes + */ getIntersectingNodes: Instance.GetIntersectingNodes; + /** + * Checks if the given node or rect intersects with the passed rect. + * + * @param node - the node or rect to check for intersections + * @param area - the rect to check for intersections + * @param partially - if true, the node is considered to be intersecting if it partially overlaps with the passed react + * + * @returns true if the node or rect intersects with the given area + */ isNodeIntersecting: Instance.IsNodeIntersecting; + /** + * Updates a node. + * + * @param id - id of the node to update + * @param nodeUpdate - the node update as an object or a function that receives the current node and returns the node update + * @param options.replace - if true, the node is replaced with the node update, otherwise the changes get merged + * + * @example + * updateNode('node-1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } })); + */ updateNode: Instance.UpdateNode; + /** + * Updates the data attribute of a node. + * + * @param id - id of the node to update + * @param dataUpdate - the data update as an object or a function that receives the current data and returns the data update + * @param options.replace - if true, the data is replaced with the data update, otherwise the changes get merged + * + * @example + * updateNodeData('node-1', { label: 'A new label' }); + */ updateNodeData: Instance.UpdateNodeData; viewportInitialized: boolean; } & Omit; diff --git a/packages/svelte/src/lib/hooks/useSvelteFlow.ts b/packages/svelte/src/lib/hooks/useSvelteFlow.ts index b6bb941e..c035a52a 100644 --- a/packages/svelte/src/lib/hooks/useSvelteFlow.ts +++ b/packages/svelte/src/lib/hooks/useSvelteFlow.ts @@ -24,32 +24,136 @@ import { isNode } from '$lib/utils'; * Hook for accessing the ReactFlow instance. * * @public + * * @returns helper functions */ export function useSvelteFlow(): { + /** + * Zooms viewport in by 1.2. + * + * @param options.duration - optional duration. If set, a transition will be applied + */ zoomIn: ZoomInOut; + /** + * Zooms viewport out by 1 / 1.2. + * + * @param options.duration - optional duration. If set, a transition will be applied + */ zoomOut: ZoomInOut; + /** + * Returns a node by id. + * + * @param id - the node id + * @returns the node or undefined if no node was found + */ getNode: (id: string) => Node | undefined; + /** + * Returns nodes. + * + * @returns nodes array + */ getNodes: (ids?: string[]) => Node[]; + /** + * Returns an edge by id. + * + * @param id - the edge id + * @returns the edge or undefined if no edge was found + */ getEdge: (id: string) => Edge | undefined; + /** + * Returns edges. + * + * @returns edges array + */ getEdges: (ids?: string[]) => Edge[]; + /** + * Sets the current zoom level. + * + * @param zoomLevel - the zoom level to set + * @param options.duration - optional duration. If set, a transition will be applied + */ setZoom: (zoomLevel: number, options?: ViewportHelperFunctionOptions) => void; + /** + * Returns the current zoom level. + * + * @returns current zoom as a number + */ getZoom: () => number; + /** + * Sets the center of the view to the given position. + * + * @param x - x position + * @param y - y position + * @param options.zoom - optional zoom + */ setCenter: (x: number, y: number, options?: SetCenterOptions) => void; + /** + * Sets the current viewport. + * + * @param viewport - the viewport to set + * @param options.duration - optional duration. If set, a transition will be applied + */ setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void; + /** + * Returns the current viewport. + * + * @returns Viewport + */ getViewport: () => Viewport; + /** + * Fits the view. + * + * @param options.padding - optional padding + * @param options.includeHiddenNodes - optional includeHiddenNodes + * @param options.minZoom - optional minZoom + * @param options.maxZoom - optional maxZoom + * @param options.duration - optional duration. If set, a transition will be applied + * @param options.nodes - optional nodes to fit the view to + */ fitView: (options?: FitViewOptions) => void; + /** + * Returns all nodes that intersect with the given node or rect. + * + * @param node - the node or rect to check for intersections + * @param partially - if true, the node is considered to be intersecting if it partially overlaps with the passed node or rect + * @param nodes - optional nodes array to check for intersections + * + * @returns an array of intersecting nodes + */ getIntersectingNodes: ( nodeOrRect: Node | { id: Node['id'] } | Rect, partially?: boolean, nodesToIntersect?: Node[] ) => Node[]; + /** + * Checks if the given node or rect intersects with the passed rect. + * + * @param node - the node or rect to check for intersections + * @param area - the rect to check for intersections + * @param partially - if true, the node is considered to be intersecting if it partially overlaps with the passed react + * + * @returns true if the node or rect intersects with the given area + */ isNodeIntersecting: ( nodeOrRect: Node | { id: Node['id'] } | Rect, area: Rect, partially?: boolean ) => boolean; + /** + * Fits the view to the given bounds . + * + * @param bounds - the bounds ({ x: number, y: number, width: number, height: number }) to fit the view to + * @param options.padding - optional padding + */ fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void; + /** + * Deletes nodes and edges. + * + * @param params.nodes - optional nodes array to delete + * @param params.edges - optional edges array to delete + * + * @returns a promise that resolves with the deleted nodes and edges + */ deleteElements: ({ nodes, edges @@ -57,19 +161,66 @@ export function useSvelteFlow(): { nodes?: (Node | { id: Node['id'] })[]; edges?: (Edge | { id: Edge['id'] })[]; }) => Promise<{ deletedNodes: Node[]; deletedEdges: Edge[] }>; - screenToFlowPosition: (position: XYPosition, options?: { snapToGrid: boolean }) => XYPosition; - flowToScreenPosition: (position: XYPosition) => XYPosition; + /** + * Converts a screen / client position to a flow position. + * + * @param clientPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY + * @param options.snapToGrid - if true, the converted position will be snapped to the grid + * @returns position as { x: number, y: number } + * + * @example + * const flowPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY }) + */ + screenToFlowPosition: ( + clientPosition: XYPosition, + options?: { snapToGrid: boolean } + ) => XYPosition; + /** + * Converts a flow position to a screen / client position. + * + * @param flowPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY + * @returns position as { x: number, y: number } + * + * @example + * const clientPosition = flowToScreenPosition({ x: node.position.x, y: node.position.y }) + */ + flowToScreenPosition: (flowPosition: XYPosition) => XYPosition; viewport: Writable; + /** + * Updates a node. + * + * @param id - id of the node to update + * @param nodeUpdate - the node update as an object or a function that receives the current node and returns the node update + * @param options.replace - if true, the node is replaced with the node update, otherwise the changes get merged + * + * @example + * updateNode('node-1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } })); + */ updateNode: ( id: string, nodeUpdate: Partial | ((node: Node) => Partial), options?: { replace: boolean } ) => void; + /** + * Updates the data attribute of a node. + * + * @param id - id of the node to update + * @param dataUpdate - the data update as an object or a function that receives the current data and returns the data update + * @param options.replace - if true, the data is replaced with the data update, otherwise the changes get merged + * + * @example + * updateNodeData('node-1', { label: 'A new label' }); + */ updateNodeData: ( id: string, dataUpdate: object | ((node: Node) => object), options?: { replace: boolean } ) => void; + /** + * Returns the nodes, edges and the viewport as a JSON object. + * + * @returns the nodes, edges and the viewport as a JSON object + */ toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport }; } { const { @@ -263,6 +414,11 @@ export function useSvelteFlow(): { _snapGrid || [1, 1] ); }, + /** + * + * @param position + * @returns + */ flowToScreenPosition: (position: XYPosition) => { const _domNode = get(domNode); @@ -279,6 +435,7 @@ export function useSvelteFlow(): { y: rendererPosition.y + domY }; }, + toObject: () => { return { nodes: get(nodes).map((node) => ({