From d8ab3bf0bd8dc79a15dc8b31167f4b194275bffc Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 11 Feb 2025 13:59:17 +0100 Subject: [PATCH 01/11] chore(hooks): tsdoc update --- packages/react/package.json | 2 +- .../Background/Background.tsx | 53 ++++++ .../additional-components/Background/types.ts | 3 +- .../additional-components/Controls/types.ts | 3 +- .../additional-components/MiniMap/MiniMap.tsx | 18 +- .../MiniMap/MiniMapNodes.tsx | 18 +- .../additional-components/MiniMap/types.ts | 3 +- .../NodeResizer/NodeResizeControl.tsx | 6 +- .../NodeResizer/types.ts | 9 +- .../NodeToolbar/types.ts | 6 +- .../src/components/BatchProvider/index.tsx | 8 +- .../src/components/BatchProvider/useQueue.ts | 28 +-- .../src/components/EdgeWrapper/index.tsx | 20 +-- packages/react/src/components/Edges/index.ts | 8 +- .../react/src/components/Handle/index.tsx | 6 +- .../src/components/NodeWrapper/index.tsx | 6 +- .../components/NodeWrapper/useNodeObserver.ts | 6 +- .../src/components/NodeWrapper/utils.tsx | 6 +- packages/react/src/components/Nodes/utils.ts | 10 +- .../src/components/NodesSelection/index.tsx | 6 +- packages/react/src/components/Panel/index.tsx | 3 +- .../src/components/StoreUpdater/index.tsx | 8 +- .../EdgeRenderer/MarkerDefinitions.tsx | 8 +- .../src/container/NodeRenderer/index.tsx | 48 ++--- packages/react/src/container/Pane/index.tsx | 12 +- .../react/src/container/ReactFlow/Wrapper.tsx | 6 +- packages/react/src/contexts/NodeIdContext.ts | 28 +++ packages/react/src/hooks/useConnection.ts | 21 ++- packages/react/src/hooks/useEdges.ts | 14 +- packages/react/src/hooks/useInternalNode.ts | 22 ++- packages/react/src/hooks/useKeyPress.ts | 66 +++++-- .../react/src/hooks/useMoveSelectedNodes.ts | 6 +- .../react/src/hooks/useNodeConnections.ts | 18 +- packages/react/src/hooks/useNodes.ts | 15 +- packages/react/src/hooks/useNodesData.ts | 16 +- .../react/src/hooks/useNodesEdgesState.ts | 64 ++++++- .../react/src/hooks/useNodesInitialized.ts | 41 ++++- .../react/src/hooks/useOnSelectionChange.ts | 34 +++- .../react/src/hooks/useOnViewportChange.ts | 20 ++- packages/react/src/hooks/useReactFlow.ts | 25 ++- packages/react/src/hooks/useStore.ts | 25 ++- .../react/src/hooks/useUpdateNodeInternals.ts | 40 ++++- packages/react/src/hooks/useViewport.ts | 25 ++- packages/react/src/hooks/useVisibleNodeIds.ts | 4 +- packages/react/src/store/index.ts | 52 +++--- packages/react/src/types/component-props.ts | 165 ++++++++++++------ packages/react/src/utils/changes.ts | 94 +++++----- pnpm-lock.yaml | 48 +++++ tooling/eslint-config/package.json | 9 +- tooling/eslint-config/src/index.js | 4 +- 50 files changed, 895 insertions(+), 271 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index 1cdf428c..af284012 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -57,7 +57,7 @@ "build": "rollup --config node:@xyflow/rollup-config --environment NODE_ENV:production && npm run css", "css": "postcss src/styles/{base,style}.css --config ./../../tooling/postcss-config/ --dir dist ", "css-watch": "pnpm css --watch", - "lint": "eslint --ext .js,.jsx,.ts,.tsx src", + "lint": "eslint --ext .js,.jsx,.ts,.tsx src --fix", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/packages/react/src/additional-components/Background/Background.tsx b/packages/react/src/additional-components/Background/Background.tsx index e4470f10..ee89624d 100644 --- a/packages/react/src/additional-components/Background/Background.tsx +++ b/packages/react/src/additional-components/Background/Background.tsx @@ -90,4 +90,57 @@ function BackgroundComponent({ BackgroundComponent.displayName = 'Background'; +/** + * The `` component makes it convenient to render different types of backgrounds common in node-based UIs. It comes with three variants: lines, dots and cross. + * + * @example + * + * A simple example of how to use the Background component. + * + * ```tsx + * import { useState } from 'react'; + * import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react'; + * + * export default function Flow() { + * return ( + * + * + * + * ); + * } + * ``` + * + * @example + * + * In this example you can see how to combine multiple backgrounds + * + * ```tsx + * import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react'; + * import '@xyflow/react/dist/style.css'; + * + * export default function Flow() { + * return ( + * + * + * + * + * ); + * } + * ``` + * + * @remarks + * + * When combining multiple components it’s important to give each of them a unique id prop! + * + */ export const Background = memo(BackgroundComponent); diff --git a/packages/react/src/additional-components/Background/types.ts b/packages/react/src/additional-components/Background/types.ts index 0a035aae..9d1cd67b 100644 --- a/packages/react/src/additional-components/Background/types.ts +++ b/packages/react/src/additional-components/Background/types.ts @@ -24,7 +24,8 @@ export type BackgroundProps = { offset?: number | [number, number]; /** Line width of the Line pattern */ lineWidth?: number; - /** Variant of the pattern + /** + * Variant of the pattern * @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross * 'lines', 'dots', 'cross' */ diff --git a/packages/react/src/additional-components/Controls/types.ts b/packages/react/src/additional-components/Controls/types.ts index f6ea0f8b..c36275d9 100644 --- a/packages/react/src/additional-components/Controls/types.ts +++ b/packages/react/src/additional-components/Controls/types.ts @@ -20,7 +20,8 @@ export type ControlProps = { onFitView?: () => void; /** Callback when interactivity is toggled */ onInteractiveChange?: (interactiveStatus: boolean) => void; - /** Position of the controls on the pane + /** + * Position of the controls on the pane * @example PanelPosition.TopLeft, PanelPosition.TopRight, * PanelPosition.BottomLeft, PanelPosition.BottomRight */ diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index bd7d6634..2f749395 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -44,8 +44,10 @@ function MiniMapComponent({ nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth, - // We need to rename the prop to be `CapitalCase` so that JSX will render it as - // a component properly. + /* + * We need to rename the prop to be `CapitalCase` so that JSX will render it as + * a component properly. + */ nodeComponent, bgColor, maskColor, @@ -111,16 +113,16 @@ function MiniMapComponent({ const onSvgClick = onClick ? (event: MouseEvent) => { - const [x, y] = minimapInstance.current?.pointer(event) || [0, 0]; - onClick(event, { x, y }); - } + const [x, y] = minimapInstance.current?.pointer(event) || [0, 0]; + onClick(event, { x, y }); + } : undefined; const onSvgNodeClick = onNodeClick ? useCallback((event: MouseEvent, nodeId: string) => { - const node = store.getState().nodeLookup.get(nodeId)!; - onNodeClick(event, node); - }, []) + const node = store.getState().nodeLookup.get(nodeId)!; + onNodeClick(event, node); + }, []) : undefined; return ( diff --git a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx index 87fd21e5..2c314289 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx @@ -21,8 +21,10 @@ function MiniMapNodes({ nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth, - // We need to rename the prop to be `CapitalCase` so that JSX will render it as - // a component properly. + /* + * We need to rename the prop to be `CapitalCase` so that JSX will render it as + * a component properly. + */ nodeComponent: NodeComponent = MiniMapNode, onClick, }: MiniMapNodesProps) { @@ -36,11 +38,13 @@ function MiniMapNodes({ return ( <> {nodeIds.map((nodeId) => ( - // The split of responsibilities between MiniMapNodes and - // NodeComponentWrapper may appear weird. However, it’s designed to - // minimize the cost of updates when individual nodes change. - // - // For more details, see a similar commit in `NodeRenderer/index.tsx`. + /* + * The split of responsibilities between MiniMapNodes and + * NodeComponentWrapper may appear weird. However, it’s designed to + * minimize the cost of updates when individual nodes change. + * + * For more details, see a similar commit in `NodeRenderer/index.tsx`. + */ key={nodeId} id={nodeId} diff --git a/packages/react/src/additional-components/MiniMap/types.ts b/packages/react/src/additional-components/MiniMap/types.ts index 4bfd1a51..c6d11ba8 100644 --- a/packages/react/src/additional-components/MiniMap/types.ts +++ b/packages/react/src/additional-components/MiniMap/types.ts @@ -27,7 +27,8 @@ export type MiniMapProps = Omit & { - /** Position of the control + /** + * Position of the control * @example ControlPosition.TopLeft, ControlPosition.TopRight, * ControlPosition.BottomLeft, ControlPosition.BottomRight */ position?: ControlPosition; - /** Variant of the control + /** + * Variant of the control * @example ResizeControlVariant.Handle, ResizeControlVariant.Line */ variant?: ResizeControlVariant; diff --git a/packages/react/src/additional-components/NodeToolbar/types.ts b/packages/react/src/additional-components/NodeToolbar/types.ts index 4de679bd..00bec37c 100644 --- a/packages/react/src/additional-components/NodeToolbar/types.ts +++ b/packages/react/src/additional-components/NodeToolbar/types.ts @@ -6,14 +6,16 @@ export type NodeToolbarProps = HTMLAttributes & { nodeId?: string | string[]; /** If true, node toolbar is visible even if node is not selected */ isVisible?: boolean; - /** Position of the toolbar relative to the node + /** + * Position of the toolbar relative to the node * @example Position.TopLeft, Position.TopRight, * Position.BottomLeft, Position.BottomRight */ position?: Position; /** Offset the toolbar from the node */ offset?: number; - /** Align the toolbar relative to the node + /** + * Align the toolbar relative to the node * @example Align.Start, Align.Center, Align.End */ align?: Align; diff --git a/packages/react/src/components/BatchProvider/index.tsx b/packages/react/src/components/BatchProvider/index.tsx index 1dbab718..5a5d1ed3 100644 --- a/packages/react/src/components/BatchProvider/index.tsx +++ b/packages/react/src/components/BatchProvider/index.tsx @@ -30,9 +30,11 @@ export function BatchProvider[]) => { const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState(); - // This is essentially an `Array.reduce` in imperative clothing. Processing - // this queue is a relatively hot path so we'd like to avoid the overhead of - // array methods where we can. + /* + * This is essentially an `Array.reduce` in imperative clothing. Processing + * this queue is a relatively hot path so we'd like to avoid the overhead of + * array methods where we can. + */ let next = nodes as NodeType[]; for (const payload of queueItems) { next = typeof payload === 'function' ? payload(next) : payload; diff --git a/packages/react/src/components/BatchProvider/useQueue.ts b/packages/react/src/components/BatchProvider/useQueue.ts index c33208d7..b1f9f793 100644 --- a/packages/react/src/components/BatchProvider/useQueue.ts +++ b/packages/react/src/components/BatchProvider/useQueue.ts @@ -12,21 +12,27 @@ import { Queue, QueueItem } from './types'; * @returns a Queue object */ export function useQueue(runQueue: (items: QueueItem[]) => void) { - // Because we're using a ref above, we need some way to let React know when to - // actually process the queue. We increment this number any time we mutate the - // queue, creating a new state to trigger the layout effect below. - // Using a boolean dirty flag here instead would lead to issues related to - // automatic batching. (https://github.com/xyflow/xyflow/issues/4779) + /* + * Because we're using a ref above, we need some way to let React know when to + * actually process the queue. We increment this number any time we mutate the + * queue, creating a new state to trigger the layout effect below. + * Using a boolean dirty flag here instead would lead to issues related to + * automatic batching. (https://github.com/xyflow/xyflow/issues/4779) + */ const [serial, setSerial] = useState(BigInt(0)); - // A reference of all the batched updates to process before the next render. We - // want a reference here so multiple synchronous calls to `setNodes` etc can be - // batched together. + /* + * A reference of all the batched updates to process before the next render. We + * want a reference here so multiple synchronous calls to `setNodes` etc can be + * batched together. + */ const [queue] = useState(() => createQueue(() => setSerial(n => n + BigInt(1)))); - // Layout effects are guaranteed to run before the next render which means we - // shouldn't run into any issues with stale state or weird issues that come from - // rendering things one frame later than expected (we used to use `setTimeout`). + /* + * Layout effects are guaranteed to run before the next render which means we + * shouldn't run into any issues with stale state or weird issues that come from + * rendering things one frame later than expected (we used to use `setTimeout`). + */ useIsomorphicLayoutEffect(() => { const queueItems = queue.get(); diff --git a/packages/react/src/components/EdgeWrapper/index.tsx b/packages/react/src/components/EdgeWrapper/index.tsx index f50ed4b9..2d3754fd 100644 --- a/packages/react/src/components/EdgeWrapper/index.tsx +++ b/packages/react/src/components/EdgeWrapper/index.tsx @@ -136,28 +136,28 @@ export function EdgeWrapper({ const onEdgeDoubleClick = onDoubleClick ? (event: React.MouseEvent) => { - onDoubleClick(event, { ...edge }); - } + onDoubleClick(event, { ...edge }); + } : undefined; const onEdgeContextMenu = onContextMenu ? (event: React.MouseEvent) => { - onContextMenu(event, { ...edge }); - } + onContextMenu(event, { ...edge }); + } : undefined; const onEdgeMouseEnter = onMouseEnter ? (event: React.MouseEvent) => { - onMouseEnter(event, { ...edge }); - } + onMouseEnter(event, { ...edge }); + } : undefined; const onEdgeMouseMove = onMouseMove ? (event: React.MouseEvent) => { - onMouseMove(event, { ...edge }); - } + onMouseMove(event, { ...edge }); + } : undefined; const onEdgeMouseLeave = onMouseLeave ? (event: React.MouseEvent) => { - onMouseLeave(event, { ...edge }); - } + onMouseLeave(event, { ...edge }); + } : undefined; const onKeyDown = (event: KeyboardEvent) => { diff --git a/packages/react/src/components/Edges/index.ts b/packages/react/src/components/Edges/index.ts index 21af9667..88b274eb 100644 --- a/packages/react/src/components/Edges/index.ts +++ b/packages/react/src/components/Edges/index.ts @@ -1,6 +1,8 @@ -// We distinguish between internal and exported edges -// The internal edges are used directly like custom edges and always get an id, source and target props -// If you import an edge from the library, the id is optional and source and target are not used at all +/* + * We distinguish between internal and exported edges + * The internal edges are used directly like custom edges and always get an id, source and target props + * If you import an edge from the library, the id is optional and source and target are not used at all + */ export { SimpleBezierEdge, SimpleBezierEdgeInternal } from './SimpleBezierEdge'; export { SmoothStepEdge, SmoothStepEdgeInternal } from './SmoothStepEdge'; diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index cc86e189..70e52dbe 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -228,8 +228,10 @@ function HandleComponent( connectingfrom: connectingFrom, connectingto: connectingTo, valid, - // shows where you can start a connection from - // and where you can end it while connecting + /* + * shows where you can start a connection from + * and where you can end it while connecting + */ connectionindicator: isConnectable && (!connectionInProcess || isPossibleEndHandle) && diff --git a/packages/react/src/components/NodeWrapper/index.tsx b/packages/react/src/components/NodeWrapper/index.tsx index bb136161..5c0ce08b 100644 --- a/packages/react/src/components/NodeWrapper/index.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -108,8 +108,10 @@ export function NodeWrapper({ const { selectNodesOnDrag, nodeDragThreshold } = store.getState(); if (isSelectable && (!selectNodesOnDrag || !isDraggable || nodeDragThreshold > 0)) { - // this handler gets called by XYDrag on drag start when selectNodesOnDrag=true - // here we only need to call it when selectNodesOnDrag=false + /* + * this handler gets called by XYDrag on drag start when selectNodesOnDrag=true + * here we only need to call it when selectNodesOnDrag=false + */ handleNodeClick({ id, store, diff --git a/packages/react/src/components/NodeWrapper/useNodeObserver.ts b/packages/react/src/components/NodeWrapper/useNodeObserver.ts index 2c2910fd..de1d31e7 100644 --- a/packages/react/src/components/NodeWrapper/useNodeObserver.ts +++ b/packages/react/src/components/NodeWrapper/useNodeObserver.ts @@ -49,8 +49,10 @@ export function useNodeObserver({ useEffect(() => { if (nodeRef.current) { - // when the user programmatically changes the source or handle position, we need to update the internals - // to make sure the edges are updated correctly + /* + * when the user programmatically changes the source or handle position, we need to update the internals + * to make sure the edges are updated correctly + */ const typeChanged = prevType.current !== nodeType; const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition; const targetPosChanged = prevTargetPosition.current !== node.targetPosition; diff --git a/packages/react/src/components/NodeWrapper/utils.tsx b/packages/react/src/components/NodeWrapper/utils.tsx index c8c7d5c7..8c699c64 100644 --- a/packages/react/src/components/NodeWrapper/utils.tsx +++ b/packages/react/src/components/NodeWrapper/utils.tsx @@ -23,9 +23,9 @@ export const builtinNodeTypes: NodeTypes = { export function getNodeInlineStyleDimensions( node: InternalNode ): { - width: number | string | undefined; - height: number | string | undefined; -} { + width: number | string | undefined; + height: number | string | undefined; + } { if (node.internals.handleBounds === undefined) { return { width: node.width ?? node.initialWidth ?? node.style?.width, diff --git a/packages/react/src/components/Nodes/utils.ts b/packages/react/src/components/Nodes/utils.ts index 81939bbc..146e05f8 100644 --- a/packages/react/src/components/Nodes/utils.ts +++ b/packages/react/src/components/Nodes/utils.ts @@ -4,10 +4,12 @@ import { errorMessages } from '@xyflow/system'; import type { ReactFlowState } from '../../types'; -// this handler is called by -// 1. the click handler when node is not draggable or selectNodesOnDrag = false -// or -// 2. the on drag start handler when node is draggable and selectNodesOnDrag = true +/* + * this handler is called by + * 1. the click handler when node is not draggable or selectNodesOnDrag = false + * or + * 2. the on drag start handler when node is draggable and selectNodesOnDrag = true + */ export function handleNodeClick({ id, store, diff --git a/packages/react/src/components/NodesSelection/index.tsx b/packages/react/src/components/NodesSelection/index.tsx index ecaef17b..ae587b56 100644 --- a/packages/react/src/components/NodesSelection/index.tsx +++ b/packages/react/src/components/NodesSelection/index.tsx @@ -61,9 +61,9 @@ export function NodesSelection({ const onContextMenu = onSelectionContextMenu ? (event: MouseEvent) => { - const selectedNodes = store.getState().nodes.filter((n) => n.selected); - onSelectionContextMenu(event, selectedNodes); - } + const selectedNodes = store.getState().nodes.filter((n) => n.selected); + onSelectionContextMenu(event, selectedNodes); + } : undefined; const onKeyDown = (event: KeyboardEvent) => { diff --git a/packages/react/src/components/Panel/index.tsx b/packages/react/src/components/Panel/index.tsx index e6dd9edb..63695928 100644 --- a/packages/react/src/components/Panel/index.tsx +++ b/packages/react/src/components/Panel/index.tsx @@ -6,7 +6,8 @@ import { useStore } from '../../hooks/useStore'; import type { ReactFlowState } from '../../types'; export type PanelProps = HTMLAttributes & { - /** Set position of the panel + /** + * Set position of the panel * @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' */ position?: PanelPosition; diff --git a/packages/react/src/components/StoreUpdater/index.tsx b/packages/react/src/components/StoreUpdater/index.tsx index cac8df0e..e3a61ab2 100644 --- a/packages/react/src/components/StoreUpdater/index.tsx +++ b/packages/react/src/components/StoreUpdater/index.tsx @@ -94,9 +94,11 @@ const selector = (s: ReactFlowState) => ({ }); const initPrevValues = { - // these are values that are also passed directly to other components - // than the StoreUpdater. We can reduce the number of setStore calls - // by setting the same values here as prev fields. + /* + * these are values that are also passed directly to other components + * than the StoreUpdater. We can reduce the number of setStore calls + * by setting the same values here as prev fields. + */ translateExtent: infiniteExtent, nodeOrigin: defaultNodeOrigin, minZoom: 0.5, diff --git a/packages/react/src/container/EdgeRenderer/MarkerDefinitions.tsx b/packages/react/src/container/EdgeRenderer/MarkerDefinitions.tsx index 5b6194c5..17b5950c 100644 --- a/packages/react/src/container/EdgeRenderer/MarkerDefinitions.tsx +++ b/packages/react/src/container/EdgeRenderer/MarkerDefinitions.tsx @@ -42,9 +42,11 @@ const Marker = ({ ); }; -// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore -// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper -// that we can then use for creating our unique marker ids +/* + * when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore + * when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper + * that we can then use for creating our unique marker ids + */ const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => { const edges = useStore((s) => s.edges); const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions); diff --git a/packages/react/src/container/NodeRenderer/index.tsx b/packages/react/src/container/NodeRenderer/index.tsx index 7b0bd131..02126d73 100644 --- a/packages/react/src/container/NodeRenderer/index.tsx +++ b/packages/react/src/container/NodeRenderer/index.tsx @@ -44,29 +44,31 @@ function NodeRendererComponent(props: NodeRendererProps {nodeIds.map((nodeId) => { return ( - // The split of responsibilities between NodeRenderer and - // NodeComponentWrapper may appear weird. However, it’s designed to - // minimize the cost of updates when individual nodes change. - // - // For example, when you’re dragging a single node, that node gets - // updated multiple times per second. If `NodeRenderer` were to update - // every time, it would have to re-run the `nodes.map()` loop every - // time. This gets pricey with hundreds of nodes, especially if every - // loop cycle does more than just rendering a JSX element! - // - // As a result of this choice, we took the following implementation - // decisions: - // - NodeRenderer subscribes *only* to node IDs – and therefore - // rerender *only* when visible nodes are added or removed. - // - NodeRenderer performs all operations the result of which can be - // shared between nodes (such as creating the `ResizeObserver` - // instance, or subscribing to `selector`). This means extra prop - // drilling into `NodeComponentWrapper`, but it means we need to run - // these operations only once – instead of once per node. - // - Any operations that you’d normally write inside `nodes.map` are - // moved into `NodeComponentWrapper`. This ensures they are - // memorized – so if `NodeRenderer` *has* to rerender, it only - // needs to regenerate the list of nodes, nothing else. + /* + * The split of responsibilities between NodeRenderer and + * NodeComponentWrapper may appear weird. However, it’s designed to + * minimize the cost of updates when individual nodes change. + * + * For example, when you’re dragging a single node, that node gets + * updated multiple times per second. If `NodeRenderer` were to update + * every time, it would have to re-run the `nodes.map()` loop every + * time. This gets pricey with hundreds of nodes, especially if every + * loop cycle does more than just rendering a JSX element! + * + * As a result of this choice, we took the following implementation + * decisions: + * - NodeRenderer subscribes *only* to node IDs – and therefore + * rerender *only* when visible nodes are added or removed. + * - NodeRenderer performs all operations the result of which can be + * shared between nodes (such as creating the `ResizeObserver` + * instance, or subscribing to `selector`). This means extra prop + * drilling into `NodeComponentWrapper`, but it means we need to run + * these operations only once – instead of once per node. + * - Any operations that you’d normally write inside `nodes.map` are + * moved into `NodeComponentWrapper`. This ensures they are + * memorized – so if `NodeRenderer` *has* to rerender, it only + * needs to regenerate the list of nodes, nothing else. + */ key={nodeId} id={nodeId} diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx index aeadde03..91459539 100644 --- a/packages/react/src/container/Pane/index.tsx +++ b/packages/react/src/container/Pane/index.tsx @@ -233,8 +233,10 @@ export function Pane({ (event.target as Partial)?.releasePointerCapture?.(event.pointerId); const { userSelectionRect } = store.getState(); - // We only want to trigger click functions when in selection mode if - // the user did not move the mouse. + /* + * We only want to trigger click functions when in selection mode if + * the user did not move the mouse. + */ if (!userSelectionActive && userSelectionRect && event.target === container.current) { onClick?.(event); } @@ -246,8 +248,10 @@ export function Pane({ }); onSelectionEnd?.(event); - // If the user kept holding the selectionKey during the selection, - // we need to reset the selectionInProgress, so the next click event is not prevented + /* + * If the user kept holding the selectionKey during the selection, + * we need to reset the selectionInProgress, so the next click event is not prevented + */ if (selectionKeyPressed || selectionOnDrag) { selectionInProgress.current = false; } diff --git a/packages/react/src/container/ReactFlow/Wrapper.tsx b/packages/react/src/container/ReactFlow/Wrapper.tsx index 5856809a..71a84316 100644 --- a/packages/react/src/container/ReactFlow/Wrapper.tsx +++ b/packages/react/src/container/ReactFlow/Wrapper.tsx @@ -31,8 +31,10 @@ export function Wrapper({ const isWrapped = useContext(StoreContext); if (isWrapped) { - // we need to wrap it with a fragment because it's not allowed for children to be a ReactNode - // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051 + /* + * we need to wrap it with a fragment because it's not allowed for children to be a ReactNode + * https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051 + */ return <>{children}; } diff --git a/packages/react/src/contexts/NodeIdContext.ts b/packages/react/src/contexts/NodeIdContext.ts index 28a14b78..b5811ad6 100644 --- a/packages/react/src/contexts/NodeIdContext.ts +++ b/packages/react/src/contexts/NodeIdContext.ts @@ -4,6 +4,34 @@ export const NodeIdContext = createContext(null); export const Provider = NodeIdContext.Provider; export const Consumer = NodeIdContext.Consumer; +/** + * You can use this hook to get the id of the node it is used inside. It is useful + *if you need the node's id deeper in the render tree but don't want to manually + *drill down the id as a prop. + * + * @public + * @returns id of the node + * + * @example + *```jsx + *import { useNodeId } from '@xyflow/react'; + * + *export default function CustomNode() { + * return ( + *
+ * This node has an id of + * + *
+ * ); + *} + * + *function NodeIdDisplay() { + * const nodeId = useNodeId(); + * + * return {nodeId}; + *} + *``` + */ export const useNodeId = (): string | null => { const nodeId = useContext(NodeIdContext); return nodeId; diff --git a/packages/react/src/hooks/useConnection.ts b/packages/react/src/hooks/useConnection.ts index 94db5140..8992d767 100644 --- a/packages/react/src/hooks/useConnection.ts +++ b/packages/react/src/hooks/useConnection.ts @@ -24,9 +24,28 @@ function getSelector {connection ? `Someone is trying to make a connection from ${connection.fromNode} to this one.` : 'There are currently no incoming connections!'} + * + * + * ); + * } + * ``` + * * @returns ConnectionState */ export function useConnection>>( diff --git a/packages/react/src/hooks/useEdges.ts b/packages/react/src/hooks/useEdges.ts index 3ed9de39..731bf845 100644 --- a/packages/react/src/hooks/useEdges.ts +++ b/packages/react/src/hooks/useEdges.ts @@ -6,10 +6,22 @@ import type { Edge, ReactFlowState } from '../types'; const edgesSelector = (state: ReactFlowState) => state.edges; /** - * Hook for getting the current edges from the store. + * This hook returns an array of the current edges. Components that use this hook + *will re-render **whenever any edge changes**. * * @public * @returns An array of edges + * + * @example + * ```tsx + *import { useEdges } from '@xyflow/react'; + * + *export default function () { + * const edges = useEdges(); + * + * return
There are currently {edges.length} edges!
; + *} + *``` */ export function useEdges(): EdgeType[] { const edges = useStore(edgesSelector, shallow) as EdgeType[]; diff --git a/packages/react/src/hooks/useInternalNode.ts b/packages/react/src/hooks/useInternalNode.ts index d16d697b..bfdc74f9 100644 --- a/packages/react/src/hooks/useInternalNode.ts +++ b/packages/react/src/hooks/useInternalNode.ts @@ -5,11 +5,31 @@ import { useStore } from './useStore'; import type { InternalNode, Node } from '../types'; /** - * Hook for getting an internal node by id + * This hook returns the internal representation of a specific node. Components that use this hook + *will re-render **whenever the node changes**, including when a node is selected + *or moved. * * @public * @param id - id of the node * @returns array with visible node ids + * + * @example + * ```tsx + *import { useInternalNode } from '@xyflow/react'; + * + *export default function () { + * const internalNode = useInternalNode('node-1'); + * const absolutePosition = internalNode.internals.positionAbsolute; + * + * return ( + *
+ * The absolute position of the node is at: + *

x: {absolutePosition.x}

+ *

y: {absolutePosition.y}

+ *
+ * ); + *} + *``` */ export function useInternalNode(id: string): InternalNode | undefined { const node = useStore( diff --git a/packages/react/src/hooks/useKeyPress.ts b/packages/react/src/hooks/useKeyPress.ts index 8bce0564..6f2d01c3 100644 --- a/packages/react/src/hooks/useKeyPress.ts +++ b/packages/react/src/hooks/useKeyPress.ts @@ -13,18 +13,38 @@ export type UseKeyPressOptions = { const defaultDoc = typeof document !== 'undefined' ? document : null; /** - * Hook for handling key events. + * This hook lets you listen for specific key codes and tells you whether they are + *currently pressed or not. * * @public * @param param.keyCode - The key code (string or array of strings) to use * @param param.options - Options * @returns boolean + * + * @example + * ```tsx + *import { useKeyPress } from '@xyflow/react'; + * + *export default function () { + * const spacePressed = useKeyPress('Space'); + * const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s']); + * + * return ( + *
+ * {spacePressed &&

Space pressed!

} + * {cmdAndSPressed &&

Cmd + S pressed!

} + *
+ * ); + *} + *``` */ export function useKeyPress( - // 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' + /* + * 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 { @@ -36,20 +56,24 @@ export function useKeyPress( // we need to remember the pressed keys in order to support combinations const pressedKeys = useRef(new Set([])); - // keyCodes = array with single keys [['a']] or key combinations [['a', 's']] - // keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft'] - // used to check if we store event.code or event.key. When the code is in the list of keysToWatch - // we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft" - // and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when - // we can't find it in the list of keysToWatch. + /* + * keyCodes = array with single keys [['a']] or key combinations [['a', 's']] + * keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft'] + * used to check if we store event.code or event.key. When the code is in the list of keysToWatch + * we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft" + * and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when + * we can't find it in the list of keysToWatch. + */ const [keyCodes, keysToWatch] = useMemo<[Array, Keys]>(() => { if (keyCode !== null) { const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode]; const keys = keyCodeArr .filter((kc) => typeof kc === 'string') - // we first replace all '+' with '\n' which we will use to split the keys on - // then we replace '\n\n' with '\n+', this way we can also support the combination 'key++' - // in the end we simply split on '\n' to get the key array + /* + * we first replace all '+' with '\n' which we will use to split the keys on + * then we replace '\n\n' with '\n+', this way we can also support the combination 'key++' + * in the end we simply split on '\n' to get the key array + */ .map((kc) => kc.replace('+', '\n').replace('\n\n', '\n+').split('\n')); const keysFlat = keys.reduce((res: Keys, item) => res.concat(...item), []); @@ -133,12 +157,16 @@ export function useKeyPress( function isMatchingKey(keyCodes: Array, pressedKeys: PressedKeys, isUp: boolean): boolean { return ( keyCodes - // we only want to compare same sizes of keyCode definitions - // and pressed keys. When the user specified 'Meta' as a key somewhere - // this would also be truthy without this filter when user presses 'Meta' + 'r' + /* + * we only want to compare same sizes of keyCode definitions + * and pressed keys. When the user specified 'Meta' as a key somewhere + * this would also be truthy without this filter when user presses 'Meta' + 'r' + */ .filter((keys) => isUp || keys.length === pressedKeys.size) - // since we want to support multiple possibilities only one of the - // combinations need to be part of the pressed keys + /* + * since we want to support multiple possibilities only one of the + * combinations need to be part of the pressed keys + */ .some((keys) => keys.every((k) => pressedKeys.has(k))) ); } diff --git a/packages/react/src/hooks/useMoveSelectedNodes.ts b/packages/react/src/hooks/useMoveSelectedNodes.ts index 52503c04..0b2dacc9 100644 --- a/packages/react/src/hooks/useMoveSelectedNodes.ts +++ b/packages/react/src/hooks/useMoveSelectedNodes.ts @@ -22,8 +22,10 @@ export function useMoveSelectedNodes() { const nodeUpdates = new Map(); const isSelected = selectedAndDraggable(nodesDraggable); - // by default a node moves 5px on each key press - // if snap grid is enabled, we use that for the velocity + /* + * by default a node moves 5px on each key press + * if snap grid is enabled, we use that for the velocity + */ const xVelo = snapToGrid ? snapGrid[0] : 5; const yVelo = snapToGrid ? snapGrid[1] : 5; diff --git a/packages/react/src/hooks/useNodeConnections.ts b/packages/react/src/hooks/useNodeConnections.ts index c83a7366..60ba894c 100644 --- a/packages/react/src/hooks/useNodeConnections.ts +++ b/packages/react/src/hooks/useNodeConnections.ts @@ -22,7 +22,7 @@ type UseNodeConnectionsParams = { }; /** - * Hook to retrieve all edges connected to a node. Can be filtered by handle type and id. + * This hook returns an array of connections on a specific node, handle type ('source', 'target') or handle ID. * * @public * @param param.id - node id - optional if called inside a custom node @@ -31,6 +31,22 @@ type UseNodeConnectionsParams = { * @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 + * + * @example + * ```jsx + *import { useNodeConnections } from '@xyflow/react'; + * + *export default function () { + * const connections = useNodeConnections({ + * type: 'target', + * handleId: 'my-handle', + * }); + * + * return ( + *
There are currently {connections.length} incoming connections!
+ * ); + *} + *``` */ export function useNodeConnections({ id, diff --git a/packages/react/src/hooks/useNodes.ts b/packages/react/src/hooks/useNodes.ts index 5e558e90..c64f7fb1 100644 --- a/packages/react/src/hooks/useNodes.ts +++ b/packages/react/src/hooks/useNodes.ts @@ -6,10 +6,23 @@ import type { Node, ReactFlowState } from '../types'; const nodesSelector = (state: ReactFlowState) => state.nodes; /** - * Hook for getting the current nodes from the store. + * This hook returns an array of the current nodes. Components that use this hook + *will re-render **whenever any node changes**, including when a node is selected + *or moved. * * @public * @returns An array of nodes + * + * @example + * ```jsx + *import { useNodes } from '@xyflow/react'; + * + *export default function() { + * const nodes = useNodes(); + * + * return
There are currently {nodes.length} nodes!
; + *} + *``` */ export 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 7472cbf7..1d2aa37e 100644 --- a/packages/react/src/hooks/useNodesData.ts +++ b/packages/react/src/hooks/useNodesData.ts @@ -5,12 +5,24 @@ import { useStore } from '../hooks/useStore'; import type { Node } from '../types'; /** - * Hook for receiving data of one or multiple nodes + * This hook lets you subscribe to changes of a specific nodes `data` object. * * @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 object (or array of object) with {id, type, data} representing each node + * + * @example + * + *```jsx + *import { useNodesData } from '@xyflow/react'; + * + *export default function() { + * const nodeData = useNodesData('nodeId-1'); + * const nodesData = useNodesData(['nodeId-1', 'nodeId-2']); + * + * return null; + *} + *``` */ export function useNodesData( nodeId: string diff --git a/packages/react/src/hooks/useNodesEdgesState.ts b/packages/react/src/hooks/useNodesEdgesState.ts index fae87c02..b9722d80 100644 --- a/packages/react/src/hooks/useNodesEdgesState.ts +++ b/packages/react/src/hooks/useNodesEdgesState.ts @@ -4,11 +4,41 @@ import { applyNodeChanges, applyEdgeChanges } from '../utils/changes'; import type { Node, Edge, OnNodesChange, OnEdgesChange } from '../types'; /** - * Hook for managing the state of nodes - should only be used for prototyping / simple use cases. + * This hook makes it easy to prototype a controlled flow where you manage the + *state of nodes and edges outside the `ReactFlowInstance`. You can think of it + *like React's `useState` hook with an additional helper callback. * * @public * @param initialNodes * @returns an array [nodes, setNodes, onNodesChange] + * @example + * + *```tsx + *import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react'; + * + *const initialNodes = []; + *const initialEdges = []; + * + *export default function () { + * const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + * const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + * + * return ( + * + * ); + *} + *``` + * + *@remarks This hook was created to make prototyping easier and our documentation + *examples clearer. Although it is OK to use this hook in production, in + *practice you may want to use a more sophisticated state management solution + *like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead. + * */ export function useNodesState( initialNodes: NodeType[] @@ -23,11 +53,41 @@ export function useNodesState( } /** - * Hook for managing the state of edges - should only be used for prototyping / simple use cases. + * This hook makes it easy to prototype a controlled flow where you manage the + *state of nodes and edges outside the `ReactFlowInstance`. You can think of it + *like React's `useState` hook with an additional helper callback. * * @public * @param initialEdges * @returns an array [edges, setEdges, onEdgesChange] + * @example + * + *```tsx + *import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react'; + * + *const initialNodes = []; + *const initialEdges = []; + * + *export default function () { + * const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + * const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + * + * return ( + * + * ); + *} + *``` + * + * @remarks This hook was created to make prototyping easier and our documentation + *examples clearer. Although it is OK to use this hook in production, in + *practice you may want to use a more sophisticated state management solution + *like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead. + * */ export function useEdgesState( initialEdges: EdgeType[] diff --git a/packages/react/src/hooks/useNodesInitialized.ts b/packages/react/src/hooks/useNodesInitialized.ts index 98bfa6b7..38f15a60 100644 --- a/packages/react/src/hooks/useNodesInitialized.ts +++ b/packages/react/src/hooks/useNodesInitialized.ts @@ -1,6 +1,7 @@ +import { nodeHasDimensions } from '@xyflow/system'; + import { useStore } from './useStore'; import type { ReactFlowState } from '../types'; -import { nodeHasDimensions } from '@xyflow/system'; export type UseNodesInitializedOptions = { includeHiddenNodes?: boolean; @@ -22,18 +23,44 @@ const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) => return true; }; -const defaultOptions = { - includeHiddenNodes: false, -}; - /** - * Hook which returns true when all nodes are initialized. + * This hook tells you whether all the nodes in a flow have been measured and given + *a width and height. When you add a node to the flow, this hook will return + *`false` and then `true` again once the node has been measured. * * @public * @param options.includeHiddenNodes - defaults to false * @returns boolean indicating whether all nodes are initialized + * + * @example + * ```jsx + *import { useReactFlow, useNodesInitialized } from '@xyflow/react'; + *import { useEffect, useState } from 'react'; + * + *const options = { + * includeHiddenNodes: false, + *}; + * + *export default function useLayout() { + * const { getNodes } = useReactFlow(); + * const nodesInitialized = useNodesInitialized(options); + * const [layoutedNodes, setLayoutedNodes] = useState(getNodes()); + * + * useEffect(() => { + * if (nodesInitialized) { + * setLayoutedNodes(yourLayoutingFunction(getNodes())); + * } + * }, [nodesInitialized]); + * + * return layoutedNodes; + *} + *``` */ -export function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean { +export function useNodesInitialized( + options: UseNodesInitializedOptions = { + includeHiddenNodes: false, + } +): boolean { const initialized = useStore(selector(options)); return initialized; diff --git a/packages/react/src/hooks/useOnSelectionChange.ts b/packages/react/src/hooks/useOnSelectionChange.ts index 8e08191b..65cad6fd 100644 --- a/packages/react/src/hooks/useOnSelectionChange.ts +++ b/packages/react/src/hooks/useOnSelectionChange.ts @@ -8,10 +8,42 @@ export type UseOnSelectionChangeOptions = { }; /** - * Hook for registering an onSelectionChange handler. + * This hook lets you listen for changes to both node and edge selection. As the + *name implies, the callback you provide will be called whenever the selection of + *_either_ nodes or edges changes. * * @public * @param params.onChange - The handler to register + * + * @example + * ```jsx + *import { useState } from 'react'; + *import { ReactFlow, useOnSelectionChange } from '@xyflow/react'; + * + *function SelectionDisplay() { + * const [selectedNodes, setSelectedNodes] = useState([]); + * const [selectedEdges, setSelectedEdges] = useState([]); + * + * // the passed handler has to be memoized, otherwise the hook will not work correctly + * const onChange = useCallback(({ nodes, edges }) => { + * setSelectedNodes(nodes.map((node) => node.id)); + * setSelectedEdges(edges.map((edge) => edge.id)); + * }, []); + * + * useOnSelectionChange({ + * onChange, + * }); + * + * return ( + *
+ *

Selected nodes: {selectedNodes.join(', ')}

+ *

Selected edges: {selectedEdges.join(', ')}

+ *
+ * ); + *} + *``` + * + * @remarks You need to memoize the passed `onChange` handler, otherwise the hook will not work correctly. */ export function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) { const store = useStoreApi(); diff --git a/packages/react/src/hooks/useOnViewportChange.ts b/packages/react/src/hooks/useOnViewportChange.ts index 61e4d64f..c82eb344 100644 --- a/packages/react/src/hooks/useOnViewportChange.ts +++ b/packages/react/src/hooks/useOnViewportChange.ts @@ -10,12 +10,30 @@ export type UseOnViewportChangeOptions = { }; /** - * Hook for registering an onViewportChange handler. + * The `useOnViewportChange` hook lets you listen for changes to the viewport such + *as panning and zooming. You can provide a callback for each phase of a viewport + *change: `onStart`, `onChange`, and `onEnd`. * * @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 + * + * @example + * ```jsx + *import { useCallback } from 'react'; + *import { useOnViewportChange } from '@xyflow/react'; + * + *function ViewportChangeLogger() { + * useOnViewportChange({ + * onStart: (viewport: Viewport) => console.log('start', viewport), + * onChange: (viewport: Viewport) => console.log('change', viewport), + * onEnd: (viewport: Viewport) => console.log('end', viewport), + * }); + * + * return null; + *} + *``` */ export 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 3d857cfa..57b2e001 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -20,10 +20,33 @@ import type { ReactFlowInstance, Node, Edge, InternalNode, ReactFlowState, Gener const selector = (s: ReactFlowState) => !!s.panZoom; /** - * Hook for accessing the ReactFlow instance. + * This hook returns a ReactFlowInstance that can be used to update nodes and edges, manipulate the viewport, or query the current state of the flow. * * @public * @returns ReactFlowInstance + * + * @example + * ```jsx + *import { useCallback, useState } from 'react'; + *import { useReactFlow } from '@xyflow/react'; + * + *export function NodeCounter() { + * const reactFlow = useReactFlow(); + * const [count, setCount] = useState(0); + * const countNodes = useCallback(() => { + * setCount(reactFlow.getNodes().length); + * // you need to pass it as a dependency if you are using it with useEffect or useCallback + * // because at the first render, it's not initialized yet and some functions might not work. + * }, [reactFlow]); + * + * return ( + *
+ * + *

There are {count} nodes in the flow.

+ *
+ * ); + *} + *``` */ export function useReactFlow(): ReactFlowInstance< NodeType, diff --git a/packages/react/src/hooks/useStore.ts b/packages/react/src/hooks/useStore.ts index 88c1abad..e34e2c36 100644 --- a/packages/react/src/hooks/useStore.ts +++ b/packages/react/src/hooks/useStore.ts @@ -9,7 +9,9 @@ import type { Edge, Node, ReactFlowState } from '../types'; const zustandErrorMessage = errorMessages['error001'](); /** - * Hook for accessing the internal store. Should only be used in rare cases. + * This hook can be used to subscribe to internal state changes of the React Flow + *component. The `useStore` hook is re-exported from the [Zustand](https://github.com/pmndrs/zustand) + *state management library, so you should check out their docs for more details. * * @public * @param selector @@ -17,8 +19,13 @@ const zustandErrorMessage = errorMessages['error001'](); * @returns The selected state slice * * @example - * const nodes = useStore((state: ReactFlowState) => state.nodes); + * ```ts + * const nodes = useStore((state) => state.nodes); + * ``` * + * @remarks This hook should only be used if there is no other way to access the internal + *state. For many of the common use cases, there are dedicated hooks available + *such as {@link useReactFlow}, {@link useViewport}, etc. */ function useStore( selector: (state: ReactFlowState) => StateSlice, @@ -33,6 +40,20 @@ function useStore( return useZustandStore(store, selector, equalityFn); } +/** + * In some cases, you might need to access the store directly. This hook returns the store object which can be used on demand to access the state or dispatch actions. + * + * @returns The store object + * + * @example + * ```ts + * const store = useStoreApi(); + * ``` + * + * @remarks This hook should only be used if there is no other way to access the internal + *state. For many of the common use cases, there are dedicated hooks available + *such as {@link useReactFlow}, {@link useViewport}, etc. + */ function useStoreApi() { const store = useContext(StoreContext) as UseBoundStoreWithEqualityFn< StoreApi> diff --git a/packages/react/src/hooks/useUpdateNodeInternals.ts b/packages/react/src/hooks/useUpdateNodeInternals.ts index 6486afd8..4463ed2a 100644 --- a/packages/react/src/hooks/useUpdateNodeInternals.ts +++ b/packages/react/src/hooks/useUpdateNodeInternals.ts @@ -4,10 +4,48 @@ import type { UpdateNodeInternals, InternalNodeUpdate } from '@xyflow/system'; import { useStoreApi } from '../hooks/useStore'; /** - * Hook for updating node internals. + * When you programmatically add or remove handles to a node or update a node's + *handle position, you need to let React Flow know about it using this hook. This + *will update the internal dimensions of the node and properly reposition handles + *on the canvas if necessary. * * @public * @returns function for updating node internals + * + * @example + * ```jsx + *import { useCallback, useState } from 'react'; + *import { Handle, useUpdateNodeInternals } from '@xyflow/react'; + * + *export default function RandomHandleNode({ id }) { + * const updateNodeInternals = useUpdateNodeInternals(); + * const [handleCount, setHandleCount] = useState(0); + * const randomizeHandleCount = useCallback(() => { + * setHandleCount(Math.floor(Math.random() * 10)); + * updateNodeInternals(id); + * }, [id, updateNodeInternals]); + * + * return ( + * <> + * {Array.from({ length: handleCount }).map((_, index) => ( + * + * ))} + * + *
+ * + *

There are {handleCount} handles on this node.

+ *
+ * + * ); + *} + *``` + * @remarks This hook can only be used in a component that is a child of a + *{@link ReactFlowProvider} or a {@link ReactFlow} component. */ export function useUpdateNodeInternals(): UpdateNodeInternals { const store = useStoreApi(); diff --git a/packages/react/src/hooks/useViewport.ts b/packages/react/src/hooks/useViewport.ts index 0b9758c1..a892e5a8 100644 --- a/packages/react/src/hooks/useViewport.ts +++ b/packages/react/src/hooks/useViewport.ts @@ -11,10 +11,33 @@ const viewportSelector = (state: ReactFlowState) => ({ }); /** - * Hook for getting the current viewport from the store. + * The `useViewport` hook is a convenient way to read the current state of the + *{@link Viewport} in a component. Components that use this hook + *will re-render **whenever the viewport changes**. * * @public * @returns The current viewport + * + * @example + * + *```jsx + *import { useViewport } from '@xyflow/react'; + * + *export default function ViewportDisplay() { + * const { x, y, zoom } = useViewport(); + * + * return ( + *
+ *

+ * The viewport is currently at ({x}, {y}) and zoomed to {zoom}. + *

+ *
+ * ); + *} + *``` + * + * @remarks This hook can only be used in a component that is a child of a + *{@link ReactFlowProvider} or a {@link ReactFlow} component. */ export function useViewport(): Viewport { const viewport = useStore(viewportSelector, shallow); diff --git a/packages/react/src/hooks/useVisibleNodeIds.ts b/packages/react/src/hooks/useVisibleNodeIds.ts index e8d14c8e..a0006940 100644 --- a/packages/react/src/hooks/useVisibleNodeIds.ts +++ b/packages/react/src/hooks/useVisibleNodeIds.ts @@ -8,8 +8,8 @@ import type { Node, ReactFlowState } from '../types'; const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => { return onlyRenderVisible ? getNodesInside(s.nodeLookup, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true).map( - (node) => node.id - ) + (node) => node.id + ) : Array.from(s.nodeLookup.keys()); }; diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 2466baa5..0f07d0f1 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -47,12 +47,14 @@ const createStore = ({ ...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }), setNodes: (nodes: Node[]) => { const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get(); - // setNodes() is called exclusively in response to user actions: - // - either when the `` prop is updated in the controlled ReactFlow setup, - // - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup. - // - // When this happens, we take the note objects passed by the user and extend them with fields - // relevant for internal React Flow operations. + /* + * setNodes() is called exclusively in response to user actions: + * - either when the `` prop is updated in the controlled ReactFlow setup, + * - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup. + * + * When this happens, we take the note objects passed by the user and extend them with fields + * relevant for internal React Flow operations. + */ adoptUserNodes(nodes, nodeLookup, parentLookup, { nodeOrigin, nodeExtent, @@ -81,9 +83,11 @@ const createStore = ({ set({ hasDefaultEdges: true }); } }, - // Every node gets registerd at a ResizeObserver. Whenever a node - // changes its dimensions, this function is called to measure the - // new dimensions and update the nodes. + /* + * Every node gets registerd at a ResizeObserver. Whenever a node + * changes its dimensions, this function is called to measure the + * new dimensions and update the nodes. + */ updateNodeInternals: (updates, params = { triggerFitView: true }) => { const { triggerNodeChanges, @@ -125,11 +129,13 @@ const createStore = ({ }); } - // here we are cirmumventing the onNodesChange handler - // in order to be able to display nodes even if the user - // has not provided an onNodesChange handler. - // Nodes are only rendered if they have a width and height - // attribute which they get from this handler. + /* + * here we are cirmumventing the onNodesChange handler + * in order to be able to display nodes even if the user + * has not provided an onNodesChange handler. + * Nodes are only rendered if they have a width and height + * attribute which they get from this handler. + */ set({ fitViewDone: nextFitViewDone }); } else { // we always want to trigger useStore calls whenever updateNodeInternals is called @@ -155,9 +161,9 @@ const createStore = ({ type: 'position', position: expandParent ? { - x: Math.max(0, dragItem.position.x), - y: Math.max(0, dragItem.position.y), - } + x: Math.max(0, dragItem.position.x), + y: Math.max(0, dragItem.position.y), + } : dragItem.position, dragging, }; @@ -248,8 +254,10 @@ const createStore = ({ const nodeChanges = nodesToUnselect.map((n) => { const internalNode = nodeLookup.get(n.id); if (internalNode) { - // we need to unselect the internal node that was selected previously before we - // send the change to the user to prevent it to be selected while dragging the new node + /* + * we need to unselect the internal node that was selected previously before we + * send the change to the user to prevent it to be selected while dragging the new node + */ internalNode.selected = false; } @@ -342,8 +350,10 @@ const createStore = ({ options ); }, - // we can't call an asnychronous function in updateNodeInternals - // for that we created this sync version of fitView + /* + * we can't call an asnychronous function in updateNodeInternals + * for that we created this sync version of fitView + */ fitViewSync: (options?: FitViewOptions): boolean => { const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get(); diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index d04875d1..2f318174 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -52,7 +52,8 @@ import type { */ export interface ReactFlowProps extends Omit, 'onError'> { - /** An array of nodes to render in a controlled flow. + /** + * An array of nodes to render in a controlled flow. * @example * const nodes = [ * { @@ -64,7 +65,8 @@ export interface ReactFlowProps; onReconnectStart?: (event: ReactMouseEvent, edge: EdgeType, handleType: HandleType) => void; onReconnectEnd?: (event: MouseEvent | TouchEvent, edge: EdgeType, handleType: HandleType) => void; - /** This event handler is called when a Node is updated + /** + * This event handler is called when a Node is updated * @example // Use NodesState hook to create edges and get onNodesChange handler * import ReactFlow, { useNodesState } from '@xyflow/react'; * const [edges, setNodes, onNodesChange] = useNodesState(initialNodes); @@ -149,7 +153,8 @@ export interface ReactFlowProps) */ onNodesChange?: OnNodesChange; - /** This event handler is called when a Edge is updated + /** + * This event handler is called when a Edge is updated * @example // Use EdgesState hook to create edges and get onEdgesChange handler * import ReactFlow, { useEdgesState } from '@xyflow/react'; * const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); @@ -181,7 +186,8 @@ export interface ReactFlowProps void; onSelectionEnd?: (event: ReactMouseEvent) => void; onSelectionContextMenu?: (event: ReactMouseEvent, nodes: NodeType[]) => void; - /** When a connection line is completed and two nodes are connected by the user, this event fires with the new connection. + /** + * When a connection line is completed and two nodes are connected by the user, this event fires with the new connection. * * You can use the addEdge utility to convert the connection to a complete edge. * @example // Use helper function to update edges onConnect @@ -223,17 +229,20 @@ export interface ReactFlowProps void; /** This event handler gets called when mouse leaves the pane */ onPaneMouseLeave?: (event: ReactMouseEvent) => void; - /** Distance that the mouse can move between mousedown/up that will trigger a click + /** + * Distance that the mouse can move between mousedown/up that will trigger a click * @default 0 */ paneClickDistance?: number; - /** Distance that the mouse can move between mousedown/up that will trigger a click + /** + * Distance that the mouse can move between mousedown/up that will trigger a click * @default 0 */ nodeClickDistance?: number; /** This handler gets called before the user deletes nodes or edges and provides a way to abort the deletion by returning false. */ onBeforeDelete?: OnBeforeDelete; - /** Custom node types to be available in a flow. + /** + * Custom node types to be available in a flow. * * React Flow matches a node's type to a component in the nodeTypes object. * @example @@ -242,7 +251,8 @@ export interface ReactFlowProps void; - /** By default the viewport extends infinitely. You can use this prop to set a boundary. + /** + * By default the viewport extends infinitely. You can use this prop to set a boundary. * * The first pair of coordinates is the top left boundary and the second pair is the bottom right. * @example [[-1000, -10000], [1000, 1000]] */ translateExtent?: CoordinateExtent; - /** Disabling this prop will allow the user to scroll the page even when their pointer is over the flow. + /** + * Disabling this prop will allow the user to scroll the page even when their pointer is over the flow. * @default true */ preventScrolling?: boolean; - /** By default nodes can be placed on an infinite flow. You can use this prop to set a boundary. + /** + * By default nodes can be placed on an infinite flow. You can use this prop to set a boundary. * * The first pair of coordinates is the top left boundary and the second pair is the bottom right. * @example [[-1000, -10000], [1000, 1000]] */ nodeExtent?: CoordinateExtent; - /** Color of edge markers + /** + * Color of edge markers * @example "#b1b1b7" */ defaultMarkerColor?: string; @@ -402,17 +439,20 @@ export interface ReactFlowProps true */ isValidConnection?: IsValidConnection; - /** With a threshold greater than zero you can control the distinction between node drag and click events. + /** + * With a threshold greater than zero you can control the distinction between node drag and click events. * * If threshold equals 1, you need to drag the node 1 pixel before a drag event is fired. * @default 1 @@ -510,12 +563,14 @@ export interface ReactFlowProps(); const addItemChanges: any[] = []; @@ -26,15 +30,19 @@ function applyChanges(changes: any[], elements: any[]): any[] { addItemChanges.push(change); continue; } else if (change.type === 'remove' || change.type === 'replace') { - // For a 'remove' change we can safely ignore any other changes queued for - // the same element, it's going to be removed anyway! + /* + * For a 'remove' change we can safely ignore any other changes queued for + * the same element, it's going to be removed anyway! + */ changesMap.set(change.id, [change]); } else { const elementChanges = changesMap.get(change.id); if (elementChanges) { - // If we have some changes queued already, we can do a mutable update of - // that array and save ourselves some copying. + /* + * If we have some changes queued already, we can do a mutable update of + * that array and save ourselves some copying. + */ elementChanges.push(change); } else { changesMap.set(change.id, [change]); @@ -45,8 +53,10 @@ function applyChanges(changes: any[], elements: any[]): any[] { for (const element of elements) { const changes = changesMap.get(element.id); - // When there are no changes for an element we can just push it unmodified, - // no need to copy it. + /* + * When there are no changes for an element we can just push it unmodified, + * no need to copy it. + */ if (!changes) { updatedElements.push(element); continue; @@ -62,9 +72,11 @@ function applyChanges(changes: any[], elements: any[]): any[] { continue; } - // For other types of changes, we want to start with a shallow copy of the - // object so React knows this element has changed. Sequential changes will - /// each _mutate_ this object, so there's only ever one copy. + /** + * For other types of changes, we want to start with a shallow copy of the + * object so React knows this element has changed. Sequential changes will + * each _mutate_ this object, so there's only ever one copy. + */ const updatedElement = { ...element }; for (const change of changes) { @@ -74,8 +86,10 @@ function applyChanges(changes: any[], elements: any[]): any[] { updatedElements.push(updatedElement); } - // we need to wait for all changes to be applied before adding new items - // to be able to add them at the correct index + /* + * we need to wait for all changes to be applied before adding new items + * to be able to add them at the correct index + */ if (addItemChanges.length) { addItemChanges.forEach((change) => { if (change.index !== undefined) { @@ -134,21 +148,21 @@ function applyChange(change: any, element: any): any { * 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. + *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 ( - - ); + * (changes) => { + * setNodes((oldNodes) => applyNodeChanges(changes, oldNodes)); + * }, + * [setNodes], + * ); + * + * return ( + * + * ); */ export function applyNodeChanges( changes: NodeChange[], @@ -161,21 +175,21 @@ export function applyNodeChanges( * 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. + *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 ( - - ); + * (changes) => { + * setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges)); + * }, + * [setEdges], + * ); + * + * return ( + * + * ); */ export function applyEdgeChanges( changes: EdgeChange[], @@ -205,9 +219,11 @@ export function getSelectionChanges( // we don't want to set all items to selected=false on the first selection if (!(item.selected === undefined && !willBeSelected) && item.selected !== willBeSelected) { if (mutateItem) { - // this hack is needed for nodes. When the user dragged a node, it's selected. - // When another node gets dragged, we need to deselect the previous one, - // in order to have only one selected node at a time - the onNodesChange callback comes too late here :/ + /* + * this hack is needed for nodes. When the user dragged a node, it's selected. + * When another node gets dragged, we need to deselect the previous one, + * in order to have only one selected node at a time - the onNodesChange callback comes too late here :/ + */ item.selected = willBeSelected; } changes.push(createSelectionChange(item.id, willBeSelected)); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22541966..6ae9a41c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -428,6 +428,9 @@ importers: tooling/eslint-config: devDependencies: + '@stylistic/eslint-plugin': + specifier: ^3.1.0 + version: 3.1.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/eslint-plugin': specifier: ^8.23.0 version: 8.23.0(@typescript-eslint/parser@8.23.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) @@ -1706,6 +1709,12 @@ packages: resolution: {integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==} engines: {node: '>=18'} + '@stylistic/eslint-plugin@3.1.0': + resolution: {integrity: sha512-pA6VOrOqk0+S8toJYhQGv2MWpQQR0QpeUo9AhNkC49Y26nxBQ/nH1rta9bUU1rPw2fJ1zZEMV5oCX5AazT7J2g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.40.0' + '@svelte-put/shortcut@3.1.1': resolution: {integrity: sha512-2L5EYTZXiaKvbEelVkg5znxqvfZGZai3m97+cAiUBhLZwXnGtviTDpHxOoZBsqz41szlfRMcamW/8o0+fbW3ZQ==} peerDependencies: @@ -2217,6 +2226,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -3290,6 +3304,10 @@ packages: esm-env@1.0.0: resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4776,6 +4794,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -7959,6 +7981,18 @@ snapshots: '@sindresorhus/merge-streams@1.0.0': {} + '@stylistic/eslint-plugin@3.1.0(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@typescript-eslint/utils': 8.23.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + estraverse: 5.3.0 + picomatch: 4.0.2 + transitivePeerDependencies: + - supports-color + - typescript + '@svelte-put/shortcut@3.1.1(svelte@4.2.12)': dependencies: svelte: 4.2.12 @@ -8613,12 +8647,18 @@ snapshots: dependencies: acorn: 8.10.0 + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + acorn@8.10.0: {} acorn@8.11.2: {} acorn@8.11.3: {} + acorn@8.14.0: {} + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -10257,6 +10297,12 @@ snapshots: esm-env@1.0.0: {} + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + espree@9.6.1: dependencies: acorn: 8.10.0 @@ -11961,6 +12007,8 @@ snapshots: picomatch@2.3.1: {} + picomatch@4.0.2: {} + pify@2.3.0: {} pify@4.0.1: {} diff --git a/tooling/eslint-config/package.json b/tooling/eslint-config/package.json index 00da90da..2c664276 100644 --- a/tooling/eslint-config/package.json +++ b/tooling/eslint-config/package.json @@ -5,11 +5,12 @@ "license": "MIT", "main": "src/index.js", "devDependencies": { - "eslint-config-prettier": "^10.0.1", - "eslint-config-turbo": "^2.4.0", - "eslint-plugin-react": "^7.37.4", + "@stylistic/eslint-plugin": "^3.1.0", "@typescript-eslint/eslint-plugin": "^8.23.0", "@typescript-eslint/parser": "^8.23.0", - "eslint-plugin-prettier": "^4.2.1" + "eslint-config-prettier": "^10.0.1", + "eslint-config-turbo": "^2.4.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.37.4" } } diff --git a/tooling/eslint-config/src/index.js b/tooling/eslint-config/src/index.js index f2ce34a0..7bd69b5a 100644 --- a/tooling/eslint-config/src/index.js +++ b/tooling/eslint-config/src/index.js @@ -13,7 +13,7 @@ module.exports = { 'turbo', 'prettier', ], - plugins: ['react', '@typescript-eslint'], + plugins: ['react', '@typescript-eslint', '@stylistic'], parserOptions: { ecmaFeatures: { jsx: true, @@ -28,5 +28,7 @@ module.exports = { }, rules: { '@typescript-eslint/no-non-null-assertion': 'off', + '@stylistic/indent': ['error', 2], + '@stylistic/multiline-comment-style': ['error', 'starred-block'], }, }; From 6c937546e49cb876886e6a633075516600a1d449 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 11 Feb 2025 14:19:33 +0100 Subject: [PATCH 02/11] chore(comps): tsdoc update --- .../Controls/ControlButton.tsx | 23 +++++++++++ .../Controls/Controls.tsx | 22 ++++++++++ .../additional-components/MiniMap/MiniMap.tsx | 20 +++++++++ .../NodeResizer/NodeResizeControl.tsx | 5 +++ .../NodeResizer/NodeResizer.tsx | 24 +++++++++++ .../NodeToolbar/NodeToolbar.tsx | 36 ++++++++++++++++ .../components/EdgeLabelRenderer/index.tsx | 41 +++++++++++++++++++ .../react/src/components/Edges/BaseEdge.tsx | 27 ++++++++++++ .../react/src/components/Edges/EdgeText.tsx | 26 ++++++++++++ .../react/src/components/Handle/index.tsx | 24 ++++++++++- packages/react/src/components/Panel/index.tsx | 30 ++++++++++++-- .../src/components/ViewportPortal/index.tsx | 24 +++++++++++ 12 files changed, 298 insertions(+), 4 deletions(-) diff --git a/packages/react/src/additional-components/Controls/ControlButton.tsx b/packages/react/src/additional-components/Controls/ControlButton.tsx index 744f46d4..12c58663 100644 --- a/packages/react/src/additional-components/Controls/ControlButton.tsx +++ b/packages/react/src/additional-components/Controls/ControlButton.tsx @@ -2,6 +2,29 @@ import cc from 'classcat'; import type { ControlButtonProps } from './types'; +/** + * You can add buttons to the control panel by using the `` component + *and pass it as a child to the [``](/api-reference/components/controls) component. + * + * @public + * @example + *```jsx + *import { MagicWand } from '@radix-ui/react-icons' + *import { ReactFlow, Controls, ControlButton } from '@xyflow/react' + * + *export default function Flow() { + * return ( + * + * + * alert('Something magical just happened. ✨')}> + * + * + * + * + * ) + *} + *``` + */ export function ControlButton({ children, className, ...rest }: ControlButtonProps) { return ( + * + * + * + * + *
+ * {data.label} + *
+ * + * + * + * + * ); + *}; + * + *export default memo(CustomNode); + *``` + * @remarks By default, the toolbar is only visible when a node is selected. If multiple + *nodes are selected it will not be visible to prevent overlapping toolbars or + *clutter. You can override this behavior by setting the `isVisible` prop to + *`true`. + */ export function NodeToolbar({ nodeId, children, diff --git a/packages/react/src/components/EdgeLabelRenderer/index.tsx b/packages/react/src/components/EdgeLabelRenderer/index.tsx index c66b70ae..b3aa1a91 100644 --- a/packages/react/src/components/EdgeLabelRenderer/index.tsx +++ b/packages/react/src/components/EdgeLabelRenderer/index.tsx @@ -6,6 +6,47 @@ import type { ReactFlowState } from '../../types'; const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__edgelabel-renderer'); +/** + * Edges are SVG-based. If you want to render more complex labels you can use the + *`` component to access a div based renderer. This component + *is a portal that renders the label in a `
` that is positioned on top of + *the edges. You can see an example usage of the component in the [edge label renderer](/examples/edges/edge-label-renderer) + *example. + * @public + * + * @example + *```jsx + *import React from 'react'; + *import { getBezierPath, EdgeLabelRenderer, BaseEdge } from '@xyflow/react'; + * + *export function CustomEdge({ id, data, ...props }) { + * const [edgePath, labelX, labelY] = getBezierPath(props); + * + * return ( + * <> + * + * + *
+ * {data.label} + *
+ *
+ * + * ); + *}; + *``` + * + *@remarks The `` has no pointer events by default. If you want to + *add mouse interactions you need to set the style `pointerEvents: all` and add + *the `nopan` class on the label or the element you want to interact with. + */ export function EdgeLabelRenderer({ children }: { children: ReactNode }) { const edgeLabelRenderer = useStore(selector); diff --git a/packages/react/src/components/Edges/BaseEdge.tsx b/packages/react/src/components/Edges/BaseEdge.tsx index 3d951796..e19412f2 100644 --- a/packages/react/src/components/Edges/BaseEdge.tsx +++ b/packages/react/src/components/Edges/BaseEdge.tsx @@ -4,6 +4,33 @@ import cc from 'classcat'; import { EdgeText } from './EdgeText'; import type { BaseEdgeProps } from '../../types'; +/** + * The `` component gets used internally for all the edges. It can be + *used inside a custom edge and handles the invisible helper edge and the edge label + *for you. + * + * @public + * @example + * ```jsx + *import { BaseEdge } from '@xyflow/react'; + * + *export function CustomEdge({ sourceX, sourceY, targetX, targetY, ...props }) { + * const [edgePath] = getStraightPath({ + * sourceX, + * sourceY, + * targetX, + * targetY, + * }); + * + * return ; + *} + *``` + * + * @remarks If you want to use an edge marker with the [``](/api-reference/components/base-edge) component, + *you can pass the `markerStart` or `markerEnd` props passed to your custom edge + *through to the [``](/api-reference/components/base-edge) component. You can see all the props + *passed to a custom edge by looking at the [`EdgeProps`](/api-reference/types/edge-props) type. + */ export function BaseEdge({ path, labelX, diff --git a/packages/react/src/components/Edges/EdgeText.tsx b/packages/react/src/components/Edges/EdgeText.tsx index 2e3ad041..e2d162b0 100644 --- a/packages/react/src/components/Edges/EdgeText.tsx +++ b/packages/react/src/components/Edges/EdgeText.tsx @@ -73,4 +73,30 @@ function EdgeTextComponent({ EdgeTextComponent.displayName = 'EdgeText'; +/** + * You can use the `` component as a helper component to display text + *within your custom edges. + * + *@public + * + *@example + *```jsx + *import { EdgeText } from '@xyflow/react'; + * + *export function CustomEdgeLabel({ label }) { + * return ( + * + * ); + *} + *``` + */ export const EdgeText = memo(EdgeTextComponent); diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 70e52dbe..07d18378 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -250,6 +250,28 @@ function HandleComponent( } /** - * The Handle component is a UI element that is used to connect nodes. + * The `` component is used in your [custom nodes](/learn/customization/custom-nodes) + *to define connection points. + * + *@public + * + *@example + * + *```jsx + *import { Handle, Position } from '@xyflow/react'; + * + *export function CustomNode({ data }) { + * return ( + * <> + *
+ * {data.label} + *
+ * + * + * + * + * ); + *}; + *``` */ export const Handle = memo(fixedForwardRef(HandleComponent)); diff --git a/packages/react/src/components/Panel/index.tsx b/packages/react/src/components/Panel/index.tsx index 63695928..1859b034 100644 --- a/packages/react/src/components/Panel/index.tsx +++ b/packages/react/src/components/Panel/index.tsx @@ -5,10 +5,34 @@ import type { PanelPosition } from '@xyflow/system'; import { useStore } from '../../hooks/useStore'; import type { ReactFlowState } from '../../types'; +/** + * The `` component helps you position content above the viewport. It is + *used internally by the [``](/api-reference/components/minimap) and [``](/api-reference/components/controls) + *components. + * + * @public + * + * @example + * ```jsx + *import { ReactFlow, Background, Panel } from '@xyflow/react'; + * + *export default function Flow() { + * return ( + * + * top-left + * top-center + * top-right + * bottom-left + * bottom-center + * bottom-right + * + * ); + *} + *``` + */ export type PanelProps = HTMLAttributes & { /** - * Set position of the panel - * @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' + * The position of the panel */ position?: PanelPosition; children: ReactNode; @@ -34,4 +58,4 @@ export const Panel = forwardRef( } ); -Panel.displayName = 'Panel' +Panel.displayName = 'Panel'; diff --git a/packages/react/src/components/ViewportPortal/index.tsx b/packages/react/src/components/ViewportPortal/index.tsx index c1b9b406..ea306935 100644 --- a/packages/react/src/components/ViewportPortal/index.tsx +++ b/packages/react/src/components/ViewportPortal/index.tsx @@ -6,6 +6,30 @@ import type { ReactFlowState } from '../../types'; const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__viewport-portal'); +/** + * The `` component can be used to add components to the same viewport of the flow where nodes and edges are rendered. + *This is useful when you want to render your own components that are adhere to the same coordinate system as the nodes & edges and are also + *affected by zooming and panning + * @public + * @example + * + * ```jsx + *import React from 'react'; + *import { ViewportPortal } from '@xyflow/react'; + * + *export default function () { + * return ( + * + *
+ * This div is positioned at [100, 100] on the flow. + *
+ *
+ * ); + *} + *``` + */ export function ViewportPortal({ children }: { children: ReactNode }) { const viewPortalDiv = useStore(selector); From fb63462be3be780c83c24f472546b47ff24630a6 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 11 Feb 2025 15:18:41 +0100 Subject: [PATCH 03/11] chore(utils): tsdoc update --- .../examples/UseOnSelectionChange/index.tsx | 1 - .../src/components/Edges/SimpleBezierEdge.tsx | 5 + packages/react/src/utils/changes.ts | 23 ++++- packages/react/src/utils/general.ts | 26 ++++- packages/system/package.json | 2 +- packages/system/src/types/edges.ts | 12 ++- packages/system/src/types/handles.ts | 12 ++- packages/system/src/types/nodes.ts | 21 ++-- packages/system/src/utils/dom.ts | 8 +- .../system/src/utils/edges/bezier-edge.ts | 35 ++++--- packages/system/src/utils/edges/general.ts | 14 ++- .../system/src/utils/edges/smoothstep-edge.ts | 50 ++++++---- .../system/src/utils/edges/straight-edge.ts | 26 ++--- packages/system/src/utils/general.ts | 4 +- packages/system/src/utils/graph.ts | 95 +++++++++++++++++-- packages/system/src/utils/node-toolbar.ts | 6 +- packages/system/src/utils/store.ts | 14 ++- packages/system/src/xydrag/XYDrag.ts | 12 ++- packages/system/src/xydrag/utils.ts | 16 ++-- packages/system/src/xyhandle/XYHandle.ts | 18 ++-- packages/system/src/xyhandle/utils.ts | 6 +- packages/system/src/xypanzoom/XYPanZoom.ts | 38 ++++---- packages/system/src/xypanzoom/eventhandler.ts | 14 ++- packages/system/src/xyresizer/XYResizer.ts | 12 ++- 24 files changed, 336 insertions(+), 134 deletions(-) diff --git a/examples/react/src/examples/UseOnSelectionChange/index.tsx b/examples/react/src/examples/UseOnSelectionChange/index.tsx index b9795951..fdbe124a 100644 --- a/examples/react/src/examples/UseOnSelectionChange/index.tsx +++ b/examples/react/src/examples/UseOnSelectionChange/index.tsx @@ -10,7 +10,6 @@ import { useEdgesState, useOnSelectionChange, OnSelectionChangeParams, - OnSelectionChangeFunc, } from '@xyflow/react'; const initialNodes: Node[] = [ diff --git a/packages/react/src/components/Edges/SimpleBezierEdge.tsx b/packages/react/src/components/Edges/SimpleBezierEdge.tsx index b3378c48..102c163e 100644 --- a/packages/react/src/components/Edges/SimpleBezierEdge.tsx +++ b/packages/react/src/components/Edges/SimpleBezierEdge.tsx @@ -29,6 +29,11 @@ function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number] return [x1, 0.5 * (y1 + y2)]; } +/** + * The `getSimpleBezierPath` util returns everything you need to render a simple +bezier edge between two nodes. + * @public + */ export function getSimpleBezierPath({ sourceX, sourceY, diff --git a/packages/react/src/utils/changes.ts b/packages/react/src/utils/changes.ts index eac8b9d3..ea0a9140 100644 --- a/packages/react/src/utils/changes.ts +++ b/packages/react/src/utils/changes.ts @@ -153,7 +153,14 @@ function applyChange(change: any, element: any): any { * @param nodes - Array of nodes to apply the changes to * @returns Array of updated nodes * @example - * const onNodesChange = useCallback( + *```tsx + *import { useState, useCallback } from 'react'; + *import { ReactFlow, applyNodeChanges, type Node, type Edge, type OnNodesChange } from '@xyflow/react'; + * + *export default function Flow() { + * const [nodes, setNodes] = useState([]); + * const [edges, setEdges] = useState([]); + * const onNodesChange: OnNodesChange = useCallback( * (changes) => { * setNodes((oldNodes) => applyNodeChanges(changes, oldNodes)); * }, @@ -161,8 +168,10 @@ function applyChange(change: any, element: any): any { * ); * * return ( - * + * * ); + *} + *``` */ export function applyNodeChanges( changes: NodeChange[], @@ -180,6 +189,14 @@ export function applyNodeChanges( * @param edges - Array of edge to apply the changes to * @returns Array of updated edges * @example + * + * ```tsx + *import { useState, useCallback } from 'react'; + *import { ReactFlow, applyEdgeChanges } from '@xyflow/react'; + * + *export default function Flow() { + * const [nodes, setNodes] = useState([]); + * const [edges, setEdges] = useState([]); * const onEdgesChange = useCallback( * (changes) => { * setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges)); @@ -190,6 +207,8 @@ export function applyNodeChanges( * return ( * * ); + *} + *``` */ export function applyEdgeChanges( changes: EdgeChange[], diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index 04608f74..6ac2e3c5 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -4,21 +4,43 @@ import { isNodeBase, isEdgeBase } from '@xyflow/system'; import type { Edge, Node } from '../types'; /** - * Test whether an object is useable as a Node + * Test whether an object is useable as an [`Node`](/api-reference/types/node). In TypeScript + *this is a type guard that will narrow the type of whatever you pass in to + *[`Node`](/api-reference/types/node) if it returns `true`. * @public * @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 + * + * @example + * ```js + *import { isNode } from '@xyflow/react'; + * + *if (isNode(node)) { + * // .. + *} + *``` */ export const isNode = (element: unknown): element is NodeType => isNodeBase(element); /** - * Test whether an object is useable as an Edge + * Test whether an object is useable as an [`Edge`](/api-reference/types/edge). In TypeScript + *this is a type guard that will narrow the type of whatever you pass in to + *[`Edge`](/api-reference/types/edge) if it returns `true`. * @public * @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 + * + * @example + * ```js + *import { isEdge } from '@xyflow/react'; + * + *if (isEdge(edge)) { + * // .. + *} + *``` */ export const isEdge = (element: unknown): element is EdgeType => isEdgeBase(element); diff --git a/packages/system/package.json b/packages/system/package.json index c7b1ee03..dfb81e1c 100644 --- a/packages/system/package.json +++ b/packages/system/package.json @@ -46,7 +46,7 @@ "scripts": { "dev": "concurrently \"rollup --config node:@xyflow/rollup-config --watch\"", "build": "rollup --config node:@xyflow/rollup-config --environment NODE_ENV:production", - "lint": "eslint --ext .js,.ts src", + "lint": "eslint --ext .js,.ts src --fix", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index 39dc97bd..990729c3 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -12,11 +12,13 @@ export type EdgeBase< source: string; /** Id of target node */ target: string; - /** Id of source handle + /** + * Id of source handle * only needed if there are multiple handles per node */ sourceHandle?: string | null; - /** Id of target handle + /** + * Id of target handle * only needed if there are multiple handles per node */ targetHandle?: string | null; @@ -27,11 +29,13 @@ export type EdgeBase< /** Arbitrary data passed to an edge */ data?: EdgeData; selected?: boolean; - /** Set the marker on the beginning of an edge + /** + * Set the marker on the beginning of an edge * @example 'arrow', 'arrowclosed' or custom marker */ markerStart?: EdgeMarkerType; - /** Set the marker on the end of an edge + /** + * Set the marker on the end of an edge * @example 'arrow', 'arrowclosed' or custom marker */ markerEnd?: EdgeMarkerType; diff --git a/packages/system/src/types/handles.ts b/packages/system/src/types/handles.ts index 9cf2a1dd..ef67d373 100644 --- a/packages/system/src/types/handles.ts +++ b/packages/system/src/types/handles.ts @@ -14,11 +14,13 @@ export type Handle = { }; export type HandleProps = { - /** Type of the handle + /** + * Type of the handle * @example HandleType.Source, HandleType.Target */ type: HandleType; - /** Position of the handle + /** + * Position of the handle * @example Position.TopLeft, Position.TopRight, * Position.BottomLeft, Position.BottomRight */ @@ -29,11 +31,13 @@ export type HandleProps = { isConnectableStart?: boolean; /** Should you be able to connect to this handle */ isConnectableEnd?: boolean; - /** Callback if connection is valid + /** + * Callback if connection is valid * @remarks connection becomes an edge if isValidConnection returns true */ isValidConnection?: IsValidConnection; - /** Id of the handle + /** + * Id of the handle * @remarks optional if there is only one handle of this type */ id?: string | null; diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts index 92261205..f1babd12 100644 --- a/packages/system/src/types/nodes.ts +++ b/packages/system/src/types/nodes.ts @@ -13,7 +13,8 @@ export type NodeBase< > = { /** Unique id of a node */ id: string; - /** Position of a node on the pane + /** + * Position of a node on the pane * @example { x: 0, y: 0 } */ position: XYPosition; @@ -21,11 +22,13 @@ export type NodeBase< data: NodeData; /** Type of node defined in nodeTypes */ type?: NodeType; - /** Only relevant for default, source, target nodeType. controls source position + /** + * Only relevant for default, source, target nodeType. controls source position * @example 'right', 'left', 'top', 'bottom' */ sourcePosition?: Position; - /** Only relevant for default, source, target nodeType. controls target position + /** + * Only relevant for default, source, target nodeType. controls target position * @example 'right', 'left', 'top', 'bottom' */ targetPosition?: Position; @@ -45,13 +48,15 @@ export type NodeBase< /** Parent node id, used for creating sub-flows */ parentId?: string; zIndex?: number; - /** Boundary a node can be moved in + /** + * Boundary a node can be moved in * @example 'parent' or [[0, 0], [100, 100]] */ extent?: 'parent' | CoordinateExtent; expandParent?: boolean; ariaLabel?: string; - /** Origin of the node relative to it's position + /** + * Origin of the node relative to it's position * @example * [0.5, 0.5] // centers the node * [0, 0] // top left @@ -73,8 +78,10 @@ export type InternalNodeBase = NodeType & internals: { positionAbsolute: XYPosition; z: number; - /** Holds a reference to the original node object provided by the user. - * Used as an optimization to avoid certain operations. */ + /** + * Holds a reference to the original node object provided by the user. + * Used as an optimization to avoid certain operations. + */ userNode: NodeType; handleBounds?: NodeHandleBounds; bounds?: NodeBounds; diff --git a/packages/system/src/utils/dom.ts b/packages/system/src/utils/dom.ts index 12188e9b..2311153f 100644 --- a/packages/system/src/utils/dom.ts +++ b/packages/system/src/utils/dom.ts @@ -61,9 +61,11 @@ export const getEventPosition = (event: MouseEvent | TouchEvent, bounds?: DOMRec }; }; -// The handle bounds are calculated relative to the node element. -// We store them in the internals object of the node in order to avoid -// unnecessary recalculations. +/* + * The handle bounds are calculated relative to the node element. + * We store them in the internals object of the node in order to avoid + * unnecessary recalculations. + */ export const getHandleBounds = ( type: 'source' | 'target', nodeElement: HTMLDivElement, diff --git a/packages/system/src/utils/edges/bezier-edge.ts b/packages/system/src/utils/edges/bezier-edge.ts index 640deffa..3b373938 100644 --- a/packages/system/src/utils/edges/bezier-edge.ts +++ b/packages/system/src/utils/edges/bezier-edge.ts @@ -38,8 +38,10 @@ export function getBezierEdgeCenter({ targetControlX: number; targetControlY: number; }): [number, number, number, number] { - // cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate - // https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve + /* + * cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate + * https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve + */ const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125; const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125; const offsetX = Math.abs(centerX - sourceX); @@ -70,7 +72,9 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva } /** - * Get a bezier path from source to target handle + * The `getBezierPath` util returns everything you need to render a bezier edge + *between two nodes. + * @public * @param params.sourceX - The x position of the source handle * @param params.sourceY - The y position of the source handle * @param params.sourcePosition - The position of the source handle (default: Position.Bottom) @@ -80,17 +84,22 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva * @param params.curvature - The curvature of the bezier edge * @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 + * ```js * 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, -}); + * 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, + *}); + *``` + * + * @remarks This function returns a tuple (aka a fixed-size array) to make it easier to + *work with multiple edge paths at once. */ export function getBezierPath({ sourceX, diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index 7dfa4b44..2a6fd7a5 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -90,12 +90,16 @@ const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => { }; /** - * This util is a convenience function to add a new Edge to an array of edges - * @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one. + * This util is a convenience function to add a new Edge to an array of edges. It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one. * @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 + * + * @remarks If an edge with the same `target` and `source` already exists (and the same + *`targetHandle` and `sourceHandle` if those are set), then this util won't add + *a new edge even if the `id` property is different. + * */ export const addEdge = ( edgeParams: EdgeType | Connection, @@ -137,12 +141,16 @@ export type ReconnectEdgeOptions = { }; /** - * A handy utility to reconnect an existing edge with new properties + * A handy utility to update an existing [`Edge`](/api-reference/types/edge) with new properties. + *This searches your edge array for an edge with a matching `id` and updates its + *properties with the connection you provide. * @param oldEdge - The edge you want to update * @param 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 + * + * @public */ export const reconnectEdge = ( oldEdge: EdgeType, diff --git a/packages/system/src/utils/edges/smoothstep-edge.ts b/packages/system/src/utils/edges/smoothstep-edge.ts index 5e57efcd..0771f357 100644 --- a/packages/system/src/utils/edges/smoothstep-edge.ts +++ b/packages/system/src/utils/edges/smoothstep-edge.ts @@ -38,8 +38,10 @@ const getDirection = ({ const distance = (a: XYPosition, b: XYPosition) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2)); -// ith this function we try to mimic a orthogonal edge routing behaviour -// It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges +/* + * ith this function we try to mimic a orthogonal edge routing behaviour + * It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges + */ function getPoints({ source, sourcePosition = Position.Bottom, @@ -83,16 +85,20 @@ function getPoints({ if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) { centerX = center.x ?? defaultCenterX; centerY = center.y ?? defaultCenterY; - // ---> - // | - // >--- + /* + * ---> + * | + * >--- + */ const verticalSplit: XYPosition[] = [ { x: centerX, y: sourceGapped.y }, { x: centerX, y: targetGapped.y }, ]; - // | - // --- - // | + /* + * | + * --- + * | + */ const horizontalSplit: XYPosition[] = [ { x: sourceGapped.x, y: centerY }, { x: targetGapped.x, y: centerY }, @@ -191,7 +197,10 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str } /** - * Get a smooth step path from source to target handle + * The `getSmoothStepPath` util returns everything you need to render a stepped path + *between two nodes. The `borderRadius` property can be used to choose how rounded + *the corners of those steps are. + * @public * @param params.sourceX - The x position of the source handle * @param params.sourceY - The y position of the source handle * @param params.sourcePosition - The position of the source handle (default: Position.Bottom) @@ -200,17 +209,20 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str * @param params.targetPosition - The position of the target handle (default: Position.Top) * @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 + * ```js * 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, - }); + * 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, + * }); + * ``` + * @remarks This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once. */ export function getSmoothStepPath({ sourceX, diff --git a/packages/system/src/utils/edges/straight-edge.ts b/packages/system/src/utils/edges/straight-edge.ts index 9940bfea..baa7093b 100644 --- a/packages/system/src/utils/edges/straight-edge.ts +++ b/packages/system/src/utils/edges/straight-edge.ts @@ -8,24 +8,28 @@ export type GetStraightPathParams = { }; /** - * Get a straight path from source to target handle + * Calculates the straight line path between two points. + * @public * @param params.sourceX - The x position of the source handle * @param params.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 + * ```js * 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, - }); + * 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, + * }); + * ``` + * @remarks This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once. */ export function getStraightPath({ sourceX, diff --git a/packages/system/src/utils/general.ts b/packages/system/src/utils/general.ts index 7f631a59..32c339e5 100644 --- a/packages/system/src/utils/general.ts +++ b/packages/system/src/utils/general.ts @@ -186,8 +186,8 @@ export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Tra * @returns A transforned {@link Viewport} that encloses the given bounds which you can pass to e.g. {@link setViewport} * @example * const { x, y, zoom } = getViewportForBounds( - { x: 0, y: 0, width: 100, height: 100}, - 1200, 800, 0.5, 2); + *{ x: 0, y: 0, width: 100, height: 100}, + *1200, 800, 0.5, 2); */ export const getViewportForBounds = ( bounds: Rect, diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts index ddec917c..1bb318d4 100644 --- a/packages/system/src/utils/graph.ts +++ b/packages/system/src/utils/graph.ts @@ -54,12 +54,27 @@ export const isInternalNodeBase = 'id' in element && 'internals' in element && !('source' in element) && !('target' in element); /** - * Pass in a node, and get connected nodes where edge.source === node.id + * This util is used to tell you what nodes, if any, are connected to the given node + *as the _target_ of an edge. * @public * @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 + * + * @example + * ```ts + *import { getOutgoers } from '@xyflow/react'; + * + *const nodes = []; + *const edges = []; + * + *const outgoers = getOutgoers( + * { id: '1', position: { x: 0, y: 0 }, data: { label: 'node' } }, + * nodes, + * edges, + *); + *``` */ export const getOutgoers = ( node: NodeType | { id: string }, @@ -81,12 +96,27 @@ export const getOutgoers = ( node: NodeType | { id: string }, @@ -124,12 +154,40 @@ export type GetNodesBoundsParams = { }; /** - * Internal function for determining a bounding box that contains all given nodes in an array. + * Returns the bounding box that contains all the given nodes in an array. This can + *be useful when combined with [`getViewportForBounds`](/api-reference/utils/get-viewport-for-bounds) + *to calculate the correct transform to fit the given nodes in a viewport. * @public * @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 params.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center * @returns Bounding box enclosing all nodes + * + * @remarks This function was previously called `getRectOfNodes` + * + * @example + * ```js + *import { getNodesBounds } from '@xyflow/react'; + * + *const nodes = [ + * { + * id: 'a', + * position: { x: 0, y: 0 }, + * data: { label: 'a' }, + * width: 50, + * height: 25, + * }, + * { + * id: 'b', + * position: { x: 100, y: 100 }, + * data: { label: 'b' }, + * width: 50, + * height: 25, + * }, + *]; + * + *const bounds = getNodesBounds(nodes); + *``` */ export const getNodesBounds = ( nodes: (NodeType | InternalNodeBase | string)[], @@ -154,8 +212,8 @@ export const getNodesBounds = ( currentNode = isId ? params.nodeLookup.get(nodeOrId) : !isInternalNodeBase(nodeOrId) - ? params.nodeLookup.get(nodeOrId.id) - : nodeOrId; + ? params.nodeLookup.get(nodeOrId.id) + : nodeOrId; } const nodeBox = currentNode ? nodeToBox(currentNode, params.nodeOrigin) : { x: 0, y: 0, x2: 0, y2: 0 }; @@ -238,10 +296,29 @@ export const getNodesInside = ( }; /** - * Get all connecting edges for a given set of nodes + * This utility filters an array of edges, keeping only those where either the source or target node is present in the given array of nodes. + * @public * @param nodes - Nodes you want to get the connected edges for * @param edges - All edges * @returns Array of edges that connect any of the given nodes with each other + * + * @example + * ```js + *import { getConnectedEdges } from '@xyflow/react'; + * + *const nodes = [ + * { id: 'a', position: { x: 0, y: 0 } }, + * { id: 'b', position: { x: 100, y: 0 } }, + *]; + * + *const edges = [ + * { id: 'a->c', source: 'a', target: 'c' }, + * { id: 'c->d', source: 'c', target: 'd' }, + *]; + * + *const connectedEdges = getConnectedEdges(nodes, edges); + * // => [{ id: 'a->c', source: 'a', target: 'c' }] + *``` */ export const getConnectedEdges = ( nodes: NodeType[], @@ -382,9 +459,9 @@ export async function getElementsToRemove; }): Promise<{ - nodes: NodeType[]; - edges: EdgeType[]; -}> { + nodes: NodeType[]; + edges: EdgeType[]; + }> { const nodeIds = new Set(nodesToRemove.map((node) => node.id)); const matchingNodes: NodeType[] = []; diff --git a/packages/system/src/utils/node-toolbar.ts b/packages/system/src/utils/node-toolbar.ts index 1813513c..f5246bcc 100644 --- a/packages/system/src/utils/node-toolbar.ts +++ b/packages/system/src/utils/node-toolbar.ts @@ -15,8 +15,10 @@ export function getNodeToolbarTransform( alignmentOffset = 1; } - // position === Position.Top - // we set the x any y position of the toolbar based on the nodes position + /* + * position === Position.Top + * we set the x any y position of the toolbar based on the nodes position + */ let pos = [ (nodeRect.x + nodeRect.width * alignmentOffset) * viewport.zoom + viewport.x, nodeRect.y * viewport.zoom + viewport.y - offset, diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index 77c5b369..a9d13316 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -276,8 +276,10 @@ export function handleExpandParent( }, }); - // We move all child nodes in the oppsite direction - // so the x,y changes of the parent do not move the children + /* + * We move all child nodes in the oppsite direction + * so the x,y changes of the parent do not move the children + */ parentLookup.get(parentId)?.forEach((childNode) => { if (!children.some((child) => child.id === childNode.id)) { changes.push({ @@ -472,9 +474,11 @@ function addConnectionToLookup( nodeId: string, handleId: string | null ) { - // We add the connection to the connectionLookup at the following keys - // 1. nodeId, 2. nodeId-type, 3. nodeId-type-handleId - // If the key already exists, we add the connection to the existing map + /* + * We add the connection to the connectionLookup at the following keys + * 1. nodeId, 2. nodeId-type, 3. nodeId-type-handleId + * If the key already exists, we add the connection to the existing map + */ let key = nodeId; const nodeMap = connectionLookup.get(key) || new Map(); connectionLookup.set(key, nodeMap.set(connectionKey, connection)); diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index eb2ea80a..be0fa93a 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -140,8 +140,10 @@ export function XYDrag voi for (const [id, dragItem] of dragItems) { if (!nodeLookup.has(id)) { - // if the node is not in the nodeLookup anymore, it was probably deleted while dragging - // and we don't need to update it anymore + /* + * if the node is not in the nodeLookup anymore, it was probably deleted while dragging + * and we don't need to update it anymore + */ continue; } @@ -150,8 +152,10 @@ export function XYDrag voi nextPosition = snapPosition(nextPosition, snapGrid); } - // if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node - // based on its position so that the node stays at it's position relative to the selection. + /* + * if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node + * based on its position so that the node stays at it's position relative to the selection. + */ let adjustedNodeExtent: CoordinateExtent = [ [nodeExtent[0][0], nodeExtent[0][1]], [nodeExtent[1][0], nodeExtent[1][1]], diff --git a/packages/system/src/xydrag/utils.ts b/packages/system/src/xydrag/utils.ts index 5c0eda0a..dc980281 100644 --- a/packages/system/src/xydrag/utils.ts +++ b/packages/system/src/xydrag/utils.ts @@ -74,9 +74,11 @@ export function getDragItems( return dragItems; } -// returns two params: -// 1. the dragged node (or the first of the list, if we are dragging a node selection) -// 2. array of selected nodes (for multi selections) +/* + * returns two params: + * 1. the dragged node (or the first of the list, if we are dragging a node selection) + * 2. array of selected nodes (for multi selections) + */ export function getEventHandlerParams({ nodeId, dragItems, @@ -112,10 +114,10 @@ export function getEventHandlerParams({ !node ? nodesFromDragItems[0] : { - ...node, - position: dragItems.get(nodeId)?.position || node.position, - dragging, - }, + ...node, + position: dragItems.get(nodeId)?.position || node.position, + dragging, + }, nodesFromDragItems, ]; } diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index 5f4fc610..9c2af456 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -165,8 +165,10 @@ function onPointerDown( toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId)! : null, }; - // we don't want to trigger an update when the connection - // is snapped to the same handle as before + /* + * we don't want to trigger an update when the connection + * is snapped to the same handle as before + */ if ( isValid && closestHandle && @@ -190,8 +192,10 @@ function onPointerDown( onConnect?.(connection); } - // it's important to get a fresh reference from the store here - // in order to get the latest state of onConnectEnd + /* + * it's important to get a fresh reference from the store here + * in order to get the latest state of onConnectEnd + */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const { inProgress, ...connectionState } = previousConnection; const finalConnectionState = { @@ -248,8 +252,10 @@ function isValidHandle( const { x, y } = getEventPosition(event); const handleBelow = doc.elementFromPoint(x, y); - // we always want to prioritize the handle below the mouse cursor over the closest distance handle, - // because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor + /* + * we always want to prioritize the handle below the mouse cursor over the closest distance handle, + * because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor + */ const handleToCheck = handleBelow?.classList.contains(`${lib}-flow__handle`) ? handleBelow : handleDomNode; const result: Result = { diff --git a/packages/system/src/xyhandle/utils.ts b/packages/system/src/xyhandle/utils.ts index ecc9b1cc..4650f192 100644 --- a/packages/system/src/xyhandle/utils.ts +++ b/packages/system/src/xyhandle/utils.ts @@ -19,8 +19,10 @@ function getNodesWithinDistance(position: XYPosition, nodeLookup: NodeLookup, di return nodes; } -// this distance is used for the area around the user pointer -// while doing a connection for finding the closest nodes +/* + * this distance is used for the area around the user pointer + * while doing a connection for finding the closest nodes + */ const ADDITIONAL_DISTANCE = 250; export function getClosestHandle( diff --git a/packages/system/src/xypanzoom/XYPanZoom.ts b/packages/system/src/xypanzoom/XYPanZoom.ts index 54b97e30..93bc79f8 100644 --- a/packages/system/src/xypanzoom/XYPanZoom.ts +++ b/packages/system/src/xypanzoom/XYPanZoom.ts @@ -114,22 +114,22 @@ export function XYPanZoom({ const wheelHandler = isPanOnScroll ? createPanOnScrollHandler({ - zoomPanValues, - noWheelClassName, - d3Selection, - d3Zoom: d3ZoomInstance, - panOnScrollMode, - panOnScrollSpeed, - zoomOnPinch, - onPanZoomStart, - onPanZoom, - onPanZoomEnd, - }) + zoomPanValues, + noWheelClassName, + d3Selection, + d3Zoom: d3ZoomInstance, + panOnScrollMode, + panOnScrollSpeed, + zoomOnPinch, + onPanZoomStart, + onPanZoom, + onPanZoomEnd, + }) : createZoomOnScrollHandler({ - noWheelClassName, - preventScrolling, - d3ZoomHandler, - }); + noWheelClassName, + preventScrolling, + d3ZoomHandler, + }); d3Selection.on('wheel.zoom', wheelHandler, { passive: false }); @@ -178,9 +178,11 @@ export function XYPanZoom({ }); d3ZoomInstance.filter(filter); - // We cannot add zoomOnDoubleClick to the filter above because - // double tapping on touch screens circumvents the filter and - // dblclick.zoom is fired on the selection directly + /* + * We cannot add zoomOnDoubleClick to the filter above because + * double tapping on touch screens circumvents the filter and + * dblclick.zoom is fired on the selection directly + */ if (zoomOnDoubleClick) { d3Selection.on('dblclick.zoom', d3DblClickZoomHandler); } else { diff --git a/packages/system/src/xypanzoom/eventhandler.ts b/packages/system/src/xypanzoom/eventhandler.ts index 2e78b90a..751a31c5 100644 --- a/packages/system/src/xypanzoom/eventhandler.ts +++ b/packages/system/src/xypanzoom/eventhandler.ts @@ -90,8 +90,10 @@ export function createPanOnScrollHandler({ return; } - // increase scroll speed in firefox - // firefox: deltaMode === 1; chrome: deltaMode === 0 + /* + * increase scroll speed in firefox + * firefox: deltaMode === 1; chrome: deltaMode === 0 + */ const deltaNormalize = event.deltaMode === 1 ? 20 : 1; let deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize; let deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize; @@ -114,9 +116,11 @@ export function createPanOnScrollHandler({ clearTimeout(zoomPanValues.panScrollTimeout); - // for pan on scroll we need to handle the event calls on our own - // we can't use the start, zoom and end events from d3-zoom - // because start and move gets called on every scroll event and not once at the beginning + /* + * for pan on scroll we need to handle the event calls on our own + * we can't use the start, zoom and end events from d3-zoom + * because start and move gets called on every scroll event and not once at the beginning + */ if (!zoomPanValues.isPanScrolling) { zoomPanValues.isPanScrolling = true; diff --git a/packages/system/src/xyresizer/XYResizer.ts b/packages/system/src/xyresizer/XYResizer.ts index 1f4ba321..44dd1ac6 100644 --- a/packages/system/src/xyresizer/XYResizer.ts +++ b/packages/system/src/xyresizer/XYResizer.ts @@ -154,8 +154,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X parentExtent = parentNode && node.extent === 'parent' ? nodeToParentExtent(parentNode) : undefined; } - // Collect all child nodes to correct their relative positions when top/left changes - // Determine largest minimal extent the parent node is allowed to resize to + /* + * Collect all child nodes to correct their relative positions when top/left changes + * Determine largest minimal extent the parent node is allowed to resize to + */ childNodes = []; childExtent = undefined; @@ -230,8 +232,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X prevValues.x = change.x; prevValues.y = change.y; - // when top/left changes, correct the relative positions of child nodes - // so that they stay in the same position + /* + * when top/left changes, correct the relative positions of child nodes + * so that they stay in the same position + */ if (childNodes.length > 0) { const xChange = x - prevX; const yChange = y - prevY; From 0848bc936701193c5ac122e8fd45da11d0670341 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 11 Feb 2025 15:31:19 +0100 Subject: [PATCH 04/11] chore(components): add tsdocs for rf and provider --- .../src/components/Edges/SimpleBezierEdge.tsx | 2 +- .../components/ReactFlowProvider/index.tsx | 35 +++++++++++++++++++ .../react/src/container/ReactFlow/index.tsx | 20 +++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/packages/react/src/components/Edges/SimpleBezierEdge.tsx b/packages/react/src/components/Edges/SimpleBezierEdge.tsx index 102c163e..1c8c2697 100644 --- a/packages/react/src/components/Edges/SimpleBezierEdge.tsx +++ b/packages/react/src/components/Edges/SimpleBezierEdge.tsx @@ -31,7 +31,7 @@ function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number] /** * The `getSimpleBezierPath` util returns everything you need to render a simple -bezier edge between two nodes. + *bezier edge between two nodes. * @public */ export function getSimpleBezierPath({ diff --git a/packages/react/src/components/ReactFlowProvider/index.tsx b/packages/react/src/components/ReactFlowProvider/index.tsx index b29f5f17..b8b4bf40 100644 --- a/packages/react/src/components/ReactFlowProvider/index.tsx +++ b/packages/react/src/components/ReactFlowProvider/index.tsx @@ -19,6 +19,41 @@ export type ReactFlowProviderProps = { children: ReactNode; }; +/** + * The `` component is a + *[context provider](https://react.dev/learn/passing-data-deeply-with-context#) that + *makes it possible to access a flow's internal state outside of the + *[``](/api-reference/react-flow) component. Many of the hooks we + *provide rely on this component to work. + * @public + * + * @example + * ```tsx + *import { ReactFlow, ReactFlowProvider, useNodes } from '@xyflow/react' + * + *export default function Flow() { + * return ( + * + * + * + * + * ); + *} + * + *function Sidebar() { + * // This hook will only work if the component it's used in is a child of a + * // . + * const nodes = useNodes() + * + * return ; + *} + *``` + * + * @remarks If you're using a router and want your flow's state to persist across routes, + *it's vital that you place the `` component _outside_ of + *your router. If you have multiple flows on the same page you will need to use a separate + *`` for each flow. + */ export function ReactFlowProvider({ initialNodes: nodes, initialEdges: edges, diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index 82206fc8..c9b504aa 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -301,4 +301,24 @@ function ReactFlow( ); } +/** + * The `` component is the heart of your React Flow application. It + *renders your nodes and edges and handles user interaction + * + * @public + * + * @example + * ```tsx + *import { ReactFlow } from '@xyflow/react' + * + *export default function Flow() { + * return (); + *} + *``` + */ export default fixedForwardRef(ReactFlow); From 7b0f96f0177c004a21bd59e346c1ef228d7087f7 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 11 Feb 2025 15:47:56 +0100 Subject: [PATCH 05/11] chore(types): add tsdocs --- packages/system/src/types/changes.ts | 13 ++++++++++++- packages/system/src/types/edges.ts | 20 ++++++++++++++++++++ packages/system/src/types/general.ts | 13 +++++++++++++ packages/system/src/types/utils.ts | 10 ++++++++++ packages/system/src/utils/edges/general.ts | 9 +++++++-- 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/packages/system/src/types/changes.ts b/packages/system/src/types/changes.ts index 9b232733..8ab81a37 100644 --- a/packages/system/src/types/changes.ts +++ b/packages/system/src/types/changes.ts @@ -42,7 +42,10 @@ export type NodeReplaceChange = { }; /** - * Union type of all possible node changes. + * The [`onNodesChange`](/api-reference/react-flow#on-nodes-change) callback takes + *an array of `NodeChange` objects that you should use to update your flow's state. + *The `NodeChange` type is a union of six different object types that represent that + *various ways an node can change in a flow. * @public */ export type NodeChange = @@ -67,6 +70,14 @@ export type EdgeReplaceChange = { type: 'replace'; }; +/** + * The [`onEdgesChange`](/api-reference/react-flow#on-edges-change) callback takes + *an array of `EdgeChange` objects that you should use to update your flow's state. + *The `EdgeChange` type is a union of four different object types that represent that + *various ways an edge can change in a flow. + * + * @public + */ export type EdgeChange = | EdgeSelectionChange | EdgeRemoveChange diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index 990729c3..abe7c8f2 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -58,11 +58,24 @@ export type BezierPathOptions = { curvature?: number; }; +/** + * @inline + */ export type DefaultEdgeOptionsBase = Omit< EdgeType, 'id' | 'source' | 'target' | 'sourceHandle' | 'targetHandle' | 'selected' >; +/** + * If you set the `connectionLineType` prop on your [``](/api-reference/react-flow#connection-connectionLineType) + *component, it will dictate the style of connection line rendered when creating + *new edges. + * + * @public + * + * @remarks If you choose to render a custom connection line component, this value will be + *passed to your component as part of its [`ConnectionLineComponentProps`](/api-reference/types/connection-line-component-props). + */ export enum ConnectionLineType { Bezier = 'default', Straight = 'straight', @@ -71,6 +84,13 @@ export enum ConnectionLineType { SimpleBezier = 'simplebezier', } +/** + * Edges can optionally have markers at the start and end of an edge. The `EdgeMarker` + *type is used to configure those markers! Check the docs for [`MarkerType`](/api-reference/types/marker-type) + *for details on what types of edge marker are available. + * + * @public + */ export type EdgeMarker = { type: MarkerType; color?: string; diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index 3c6eed52..485bb4f5 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -26,6 +26,13 @@ export type SetViewport = (viewport: Viewport, options?: ViewportHelperFunctionO export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => Promise; export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => Promise; +/** + * The `Connection` type is the basic minimal description of an [`Edge`](/api-reference/types/edge) + *between two nodes. The [`addEdge`](/api-reference/utils/add-edge) util can be used to upgrade + *a `Connection` to an [`Edge`](/api-reference/types/edge). + * + * @public + */ export type Connection = { source: string; target: string; @@ -174,6 +181,12 @@ export type ConnectionInProgress = | ConnectionInProgress | NoConnection; diff --git a/packages/system/src/types/utils.ts b/packages/system/src/types/utils.ts index b06786a7..61d55698 100644 --- a/packages/system/src/types/utils.ts +++ b/packages/system/src/types/utils.ts @@ -33,4 +33,14 @@ export type Box = XYPosition & { export type Transform = [number, number, number]; +/** + * A coordinate extent represents two points in a coordinate system: one in the top + *left corner and one in the bottom right corner. It is used to represent the + *bounds of nodes in the flow or the bounds of the viewport. + * + * @public + * + * @remarks Props that expect a `CoordinateExtent` usually default to `[[-∞, -∞], [+∞, +∞]]` + *to represent an unbounded extent. + */ export type CoordinateExtent = [[number, number], [number, number]]; diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index 2a6fd7a5..7f98d00f 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -144,13 +144,18 @@ export type ReconnectEdgeOptions = { * A handy utility to update an existing [`Edge`](/api-reference/types/edge) with new properties. *This searches your edge array for an edge with a matching `id` and updates its *properties with the connection you provide. + * @public * @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 - * - * @public + * + * @example + * ```js + *const onReconnect = useCallback( + * (oldEdge: Edge, newConnection: Connection) => setEdges((els) => reconnectEdge(oldEdge, newConnection, els)),[]); + *``` */ export const reconnectEdge = ( oldEdge: EdgeType, From 381ed2a5bf01bde4cf44b34faa2e464fbc0b6305 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 11 Feb 2025 17:57:21 +0100 Subject: [PATCH 06/11] chore(types): add tsdocs --- .../additional-components/Background/types.ts | 9 +++ .../Controls/ControlButton.tsx | 2 +- .../Controls/Controls.tsx | 5 +- .../additional-components/MiniMap/MiniMap.tsx | 16 ++--- .../additional-components/MiniMap/types.ts | 5 ++ .../NodeResizer/NodeResizer.tsx | 2 +- .../NodeToolbar/NodeToolbar.tsx | 7 +-- .../components/EdgeLabelRenderer/index.tsx | 13 ++--- .../react/src/components/Edges/BaseEdge.tsx | 10 ++-- .../react/src/components/Edges/EdgeText.tsx | 2 +- .../src/components/Edges/SimpleBezierEdge.tsx | 4 +- .../react/src/components/Handle/index.tsx | 2 +- packages/react/src/components/Panel/index.tsx | 6 +- .../components/ReactFlowProvider/index.tsx | 15 +++-- .../src/components/ViewportPortal/index.tsx | 7 ++- .../react/src/container/ReactFlow/index.tsx | 4 +- packages/react/src/contexts/NodeIdContext.ts | 4 +- packages/react/src/hooks/useConnection.ts | 6 +- packages/react/src/hooks/useEdges.ts | 2 +- packages/react/src/hooks/useInternalNode.ts | 6 +- packages/react/src/hooks/useKeyPress.ts | 2 +- packages/react/src/hooks/useNodes.ts | 4 +- .../react/src/hooks/useNodesEdgesState.ts | 22 +++---- packages/react/src/hooks/useStore.ts | 12 ++-- packages/react/src/types/edges.ts | 23 +++++++- packages/react/src/types/general.ts | 11 ++++ packages/react/src/types/instance.ts | 8 +++ packages/react/src/types/nodes.ts | 38 ++++++++++-- packages/react/src/utils/general.ts | 14 +++-- packages/svelte/src/lib/types/edges.ts | 4 +- packages/system/src/types/edges.ts | 6 ++ packages/system/src/types/general.ts | 58 +++++++++++++++++-- packages/system/src/types/nodes.ts | 17 +++++- packages/system/src/types/utils.ts | 18 +++++- 34 files changed, 264 insertions(+), 100 deletions(-) diff --git a/packages/react/src/additional-components/Background/types.ts b/packages/react/src/additional-components/Background/types.ts index 9d1cd67b..50219e2d 100644 --- a/packages/react/src/additional-components/Background/types.ts +++ b/packages/react/src/additional-components/Background/types.ts @@ -1,11 +1,20 @@ import { CSSProperties } from 'react'; +/** + * The three variants are exported as an enum for convenience. You can either import + * the enum and use it like `BackgroundVariant.Lines` or you can use the raw string + * value directly. + * @public + */ export enum BackgroundVariant { Lines = 'lines', Dots = 'dots', Cross = 'cross', } +/** + * @expand + */ export type BackgroundProps = { id?: string; /** Color of the pattern */ diff --git a/packages/react/src/additional-components/Controls/ControlButton.tsx b/packages/react/src/additional-components/Controls/ControlButton.tsx index 12c58663..a3cc81ad 100644 --- a/packages/react/src/additional-components/Controls/ControlButton.tsx +++ b/packages/react/src/additional-components/Controls/ControlButton.tsx @@ -4,7 +4,7 @@ import type { ControlButtonProps } from './types'; /** * You can add buttons to the control panel by using the `` component - *and pass it as a child to the [``](/api-reference/components/controls) component. + * and pass it as a child to the [``](/api-reference/components/controls) component. * * @public * @example diff --git a/packages/react/src/additional-components/Controls/Controls.tsx b/packages/react/src/additional-components/Controls/Controls.tsx index 1b0beae3..84337a28 100644 --- a/packages/react/src/additional-components/Controls/Controls.tsx +++ b/packages/react/src/additional-components/Controls/Controls.tsx @@ -127,7 +127,7 @@ ControlsComponent.displayName = 'Controls'; /** * The `` component renders a small panel that contains convenient - *buttons to zoom in, zoom out, fit the view, and lock the viewport. + * buttons to zoom in, zoom out, fit the view, and lock the viewport. * * @public * @example @@ -143,8 +143,7 @@ ControlsComponent.displayName = 'Controls'; *} *``` * - * @remarks To extend or customise the controls, you can use the [``](/api-reference/components/control-button) - *component + * @remarks To extend or customise the controls, you can use the [``](/api-reference/components/control-button) component * */ export const Controls = memo(ControlsComponent); diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index c5e8d719..be6dc453 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -113,16 +113,16 @@ function MiniMapComponent({ const onSvgClick = onClick ? (event: MouseEvent) => { - const [x, y] = minimapInstance.current?.pointer(event) || [0, 0]; - onClick(event, { x, y }); - } + const [x, y] = minimapInstance.current?.pointer(event) || [0, 0]; + onClick(event, { x, y }); + } : undefined; const onSvgNodeClick = onNodeClick ? useCallback((event: MouseEvent, nodeId: string) => { - const node = store.getState().nodeLookup.get(nodeId)!; - onNodeClick(event, node); - }, []) + const node = store.getState().nodeLookup.get(nodeId)!; + onNodeClick(event, node); + }, []) : undefined; return ( @@ -180,8 +180,8 @@ MiniMapComponent.displayName = 'MiniMap'; /** * The `` component can be used to render an overview of your flow. It - *renders each node as an SVG element and visualizes where the current viewport is - *in relation to the rest of the flow. + * renders each node as an SVG element and visualizes where the current viewport is + * in relation to the rest of the flow. * * @public * @example diff --git a/packages/react/src/additional-components/MiniMap/types.ts b/packages/react/src/additional-components/MiniMap/types.ts index c6d11ba8..351395f9 100644 --- a/packages/react/src/additional-components/MiniMap/types.ts +++ b/packages/react/src/additional-components/MiniMap/types.ts @@ -58,6 +58,11 @@ export type MiniMapNodes = Pick< onClick?: (event: MouseEvent, nodeId: string) => void; }; +/** + * The props that are passed to the MiniMapNode component + * + * @public + */ export type MiniMapNodeProps = { id: string; x: number; diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizer.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizer.tsx index f3e6cf8a..434be4b7 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizer.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizer.tsx @@ -5,7 +5,7 @@ import type { NodeResizerProps } from './types'; /** * The `` component can be used to add a resize functionality to your - *nodes. It renders draggable controls around the node to resize in all directions. + * nodes. It renders draggable controls around the node to resize in all directions. * @public * * @example diff --git a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx index 4e486425..0248b394 100644 --- a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx +++ b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx @@ -40,7 +40,7 @@ const storeSelector = (state: ReactFlowState) => ({ /** * This component can render a toolbar or tooltip to one side of a custom node. This - *toolbar doesn't scale with the viewport so that the content is always visible. + * toolbar doesn't scale with the viewport so that the content is always visible. * * @public * @example @@ -70,9 +70,8 @@ const storeSelector = (state: ReactFlowState) => ({ *export default memo(CustomNode); *``` * @remarks By default, the toolbar is only visible when a node is selected. If multiple - *nodes are selected it will not be visible to prevent overlapping toolbars or - *clutter. You can override this behavior by setting the `isVisible` prop to - *`true`. + * nodes are selected it will not be visible to prevent overlapping toolbars or + * clutter. You can override this behavior by setting the `isVisible` prop to `true`. */ export function NodeToolbar({ nodeId, diff --git a/packages/react/src/components/EdgeLabelRenderer/index.tsx b/packages/react/src/components/EdgeLabelRenderer/index.tsx index b3aa1a91..647811cb 100644 --- a/packages/react/src/components/EdgeLabelRenderer/index.tsx +++ b/packages/react/src/components/EdgeLabelRenderer/index.tsx @@ -8,10 +8,9 @@ const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__e /** * Edges are SVG-based. If you want to render more complex labels you can use the - *`` component to access a div based renderer. This component - *is a portal that renders the label in a `
` that is positioned on top of - *the edges. You can see an example usage of the component in the [edge label renderer](/examples/edges/edge-label-renderer) - *example. + * `` component to access a div based renderer. This component + * is a portal that renders the label in a `
` that is positioned on top of + * the edges. You can see an example usage of the component in the [edge label renderer](/examples/edges/edge-label-renderer) example. * @public * * @example @@ -43,9 +42,9 @@ const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__e *}; *``` * - *@remarks The `` has no pointer events by default. If you want to - *add mouse interactions you need to set the style `pointerEvents: all` and add - *the `nopan` class on the label or the element you want to interact with. + * @remarks The `` has no pointer events by default. If you want to + * add mouse interactions you need to set the style `pointerEvents: all` and add + * the `nopan` class on the label or the element you want to interact with. */ export function EdgeLabelRenderer({ children }: { children: ReactNode }) { const edgeLabelRenderer = useStore(selector); diff --git a/packages/react/src/components/Edges/BaseEdge.tsx b/packages/react/src/components/Edges/BaseEdge.tsx index e19412f2..4d1ac1da 100644 --- a/packages/react/src/components/Edges/BaseEdge.tsx +++ b/packages/react/src/components/Edges/BaseEdge.tsx @@ -6,8 +6,8 @@ import type { BaseEdgeProps } from '../../types'; /** * The `` component gets used internally for all the edges. It can be - *used inside a custom edge and handles the invisible helper edge and the edge label - *for you. + * used inside a custom edge and handles the invisible helper edge and the edge label + * for you. * * @public * @example @@ -27,9 +27,9 @@ import type { BaseEdgeProps } from '../../types'; *``` * * @remarks If you want to use an edge marker with the [``](/api-reference/components/base-edge) component, - *you can pass the `markerStart` or `markerEnd` props passed to your custom edge - *through to the [``](/api-reference/components/base-edge) component. You can see all the props - *passed to a custom edge by looking at the [`EdgeProps`](/api-reference/types/edge-props) type. + * you can pass the `markerStart` or `markerEnd` props passed to your custom edge + * through to the [``](/api-reference/components/base-edge) component. + * You can see all the props passed to a custom edge by looking at the [`EdgeProps`](/api-reference/types/edge-props) type. */ export function BaseEdge({ path, diff --git a/packages/react/src/components/Edges/EdgeText.tsx b/packages/react/src/components/Edges/EdgeText.tsx index e2d162b0..2c79f718 100644 --- a/packages/react/src/components/Edges/EdgeText.tsx +++ b/packages/react/src/components/Edges/EdgeText.tsx @@ -75,7 +75,7 @@ EdgeTextComponent.displayName = 'EdgeText'; /** * You can use the `` component as a helper component to display text - *within your custom edges. + * within your custom edges. * *@public * diff --git a/packages/react/src/components/Edges/SimpleBezierEdge.tsx b/packages/react/src/components/Edges/SimpleBezierEdge.tsx index 1c8c2697..a11cbc1d 100644 --- a/packages/react/src/components/Edges/SimpleBezierEdge.tsx +++ b/packages/react/src/components/Edges/SimpleBezierEdge.tsx @@ -31,8 +31,8 @@ function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number] /** * The `getSimpleBezierPath` util returns everything you need to render a simple - *bezier edge between two nodes. - * @public + * bezier edge between two nodes. + * @public */ export function getSimpleBezierPath({ sourceX, diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 07d18378..72e7ad21 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -251,7 +251,7 @@ function HandleComponent( /** * The `` component is used in your [custom nodes](/learn/customization/custom-nodes) - *to define connection points. + * to define connection points. * *@public * diff --git a/packages/react/src/components/Panel/index.tsx b/packages/react/src/components/Panel/index.tsx index 1859b034..461cc1ba 100644 --- a/packages/react/src/components/Panel/index.tsx +++ b/packages/react/src/components/Panel/index.tsx @@ -6,9 +6,9 @@ import { useStore } from '../../hooks/useStore'; import type { ReactFlowState } from '../../types'; /** - * The `` component helps you position content above the viewport. It is - *used internally by the [``](/api-reference/components/minimap) and [``](/api-reference/components/controls) - *components. + * The `` component helps you position content above the viewport. + * It is used internally by the [``](/api-reference/components/minimap) + * and [``](/api-reference/components/controls) components. * * @public * diff --git a/packages/react/src/components/ReactFlowProvider/index.tsx b/packages/react/src/components/ReactFlowProvider/index.tsx index b8b4bf40..1492ca3f 100644 --- a/packages/react/src/components/ReactFlowProvider/index.tsx +++ b/packages/react/src/components/ReactFlowProvider/index.tsx @@ -20,11 +20,10 @@ export type ReactFlowProviderProps = { }; /** - * The `` component is a - *[context provider](https://react.dev/learn/passing-data-deeply-with-context#) that - *makes it possible to access a flow's internal state outside of the - *[``](/api-reference/react-flow) component. Many of the hooks we - *provide rely on this component to work. + * The `` component is a [context provider](https://react.dev/learn/passing-data-deeply-with-context#) + * that makes it possible to access a flow's internal state outside of the + * [``](/api-reference/react-flow) component. Many of the hooks we + * provide rely on this component to work. * @public * * @example @@ -50,9 +49,9 @@ export type ReactFlowProviderProps = { *``` * * @remarks If you're using a router and want your flow's state to persist across routes, - *it's vital that you place the `` component _outside_ of - *your router. If you have multiple flows on the same page you will need to use a separate - *`` for each flow. + * it's vital that you place the `` component _outside_ of + * your router. If you have multiple flows on the same page you will need to use a separate + * `` for each flow. */ export function ReactFlowProvider({ initialNodes: nodes, diff --git a/packages/react/src/components/ViewportPortal/index.tsx b/packages/react/src/components/ViewportPortal/index.tsx index ea306935..1040d796 100644 --- a/packages/react/src/components/ViewportPortal/index.tsx +++ b/packages/react/src/components/ViewportPortal/index.tsx @@ -7,9 +7,10 @@ import type { ReactFlowState } from '../../types'; const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__viewport-portal'); /** - * The `` component can be used to add components to the same viewport of the flow where nodes and edges are rendered. - *This is useful when you want to render your own components that are adhere to the same coordinate system as the nodes & edges and are also - *affected by zooming and panning + * The `` component can be used to add components to the same viewport + * of the flow where nodes and edges are rendered. This is useful when you want to render + * your own components that are adhere to the same coordinate system as the nodes & edges + * and are also affected by zooming and panning * @public * @example * diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index c9b504aa..439a84e6 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -302,8 +302,8 @@ function ReactFlow( } /** - * The `` component is the heart of your React Flow application. It - *renders your nodes and edges and handles user interaction + * The `` component is the heart of your React Flow application. + * It renders your nodes and edges and handles user interaction * * @public * diff --git a/packages/react/src/contexts/NodeIdContext.ts b/packages/react/src/contexts/NodeIdContext.ts index b5811ad6..5458c98c 100644 --- a/packages/react/src/contexts/NodeIdContext.ts +++ b/packages/react/src/contexts/NodeIdContext.ts @@ -6,8 +6,8 @@ export const Consumer = NodeIdContext.Consumer; /** * You can use this hook to get the id of the node it is used inside. It is useful - *if you need the node's id deeper in the render tree but don't want to manually - *drill down the id as a prop. + * if you need the node's id deeper in the render tree but don't want to manually + * drill down the id as a prop. * * @public * @returns id of the node diff --git a/packages/react/src/hooks/useConnection.ts b/packages/react/src/hooks/useConnection.ts index 8992d767..9ed14b67 100644 --- a/packages/react/src/hooks/useConnection.ts +++ b/packages/react/src/hooks/useConnection.ts @@ -25,9 +25,9 @@ function getSelector state.edges; /** * This hook returns an array of the current edges. Components that use this hook - *will re-render **whenever any edge changes**. + * will re-render **whenever any edge changes**. * * @public * @returns An array of edges diff --git a/packages/react/src/hooks/useInternalNode.ts b/packages/react/src/hooks/useInternalNode.ts index bfdc74f9..ecd83897 100644 --- a/packages/react/src/hooks/useInternalNode.ts +++ b/packages/react/src/hooks/useInternalNode.ts @@ -5,9 +5,9 @@ import { useStore } from './useStore'; import type { InternalNode, Node } from '../types'; /** - * This hook returns the internal representation of a specific node. Components that use this hook - *will re-render **whenever the node changes**, including when a node is selected - *or moved. + * This hook returns the internal representation of a specific node. + * Components that use this hook will re-render **whenever the node changes**, + * including when a node is selected or moved. * * @public * @param id - id of the node diff --git a/packages/react/src/hooks/useKeyPress.ts b/packages/react/src/hooks/useKeyPress.ts index 6f2d01c3..00cd012b 100644 --- a/packages/react/src/hooks/useKeyPress.ts +++ b/packages/react/src/hooks/useKeyPress.ts @@ -14,7 +14,7 @@ const defaultDoc = typeof document !== 'undefined' ? document : null; /** * This hook lets you listen for specific key codes and tells you whether they are - *currently pressed or not. + * currently pressed or not. * * @public * @param param.keyCode - The key code (string or array of strings) to use diff --git a/packages/react/src/hooks/useNodes.ts b/packages/react/src/hooks/useNodes.ts index c64f7fb1..9fa14bcb 100644 --- a/packages/react/src/hooks/useNodes.ts +++ b/packages/react/src/hooks/useNodes.ts @@ -7,8 +7,8 @@ const nodesSelector = (state: ReactFlowState) => state.nodes; /** * This hook returns an array of the current nodes. Components that use this hook - *will re-render **whenever any node changes**, including when a node is selected - *or moved. + * will re-render **whenever any node changes**, including when a node is selected + * or moved. * * @public * @returns An array of nodes diff --git a/packages/react/src/hooks/useNodesEdgesState.ts b/packages/react/src/hooks/useNodesEdgesState.ts index b9722d80..b5919e5e 100644 --- a/packages/react/src/hooks/useNodesEdgesState.ts +++ b/packages/react/src/hooks/useNodesEdgesState.ts @@ -5,8 +5,8 @@ import type { Node, Edge, OnNodesChange, OnEdgesChange } from '../types'; /** * This hook makes it easy to prototype a controlled flow where you manage the - *state of nodes and edges outside the `ReactFlowInstance`. You can think of it - *like React's `useState` hook with an additional helper callback. + * state of nodes and edges outside the `ReactFlowInstance`. You can think of it + * like React's `useState` hook with an additional helper callback. * * @public * @param initialNodes @@ -34,10 +34,10 @@ import type { Node, Edge, OnNodesChange, OnEdgesChange } from '../types'; *} *``` * - *@remarks This hook was created to make prototyping easier and our documentation - *examples clearer. Although it is OK to use this hook in production, in - *practice you may want to use a more sophisticated state management solution - *like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead. + * @remarks This hook was created to make prototyping easier and our documentation + * examples clearer. Although it is OK to use this hook in production, in + * practice you may want to use a more sophisticated state management solution + * like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead. * */ export function useNodesState( @@ -54,8 +54,8 @@ export function useNodesState( /** * This hook makes it easy to prototype a controlled flow where you manage the - *state of nodes and edges outside the `ReactFlowInstance`. You can think of it - *like React's `useState` hook with an additional helper callback. + * state of nodes and edges outside the `ReactFlowInstance`. You can think of it + * like React's `useState` hook with an additional helper callback. * * @public * @param initialEdges @@ -84,9 +84,9 @@ export function useNodesState( *``` * * @remarks This hook was created to make prototyping easier and our documentation - *examples clearer. Although it is OK to use this hook in production, in - *practice you may want to use a more sophisticated state management solution - *like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead. + * examples clearer. Although it is OK to use this hook in production, in + * practice you may want to use a more sophisticated state management solution + * like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead. * */ export function useEdgesState( diff --git a/packages/react/src/hooks/useStore.ts b/packages/react/src/hooks/useStore.ts index e34e2c36..3b0a2778 100644 --- a/packages/react/src/hooks/useStore.ts +++ b/packages/react/src/hooks/useStore.ts @@ -10,8 +10,8 @@ const zustandErrorMessage = errorMessages['error001'](); /** * This hook can be used to subscribe to internal state changes of the React Flow - *component. The `useStore` hook is re-exported from the [Zustand](https://github.com/pmndrs/zustand) - *state management library, so you should check out their docs for more details. + * component. The `useStore` hook is re-exported from the [Zustand](https://github.com/pmndrs/zustand) + * state management library, so you should check out their docs for more details. * * @public * @param selector @@ -24,8 +24,8 @@ const zustandErrorMessage = errorMessages['error001'](); * ``` * * @remarks This hook should only be used if there is no other way to access the internal - *state. For many of the common use cases, there are dedicated hooks available - *such as {@link useReactFlow}, {@link useViewport}, etc. + * state. For many of the common use cases, there are dedicated hooks available + * such as {@link useReactFlow}, {@link useViewport}, etc. */ function useStore( selector: (state: ReactFlowState) => StateSlice, @@ -51,8 +51,8 @@ function useStore( * ``` * * @remarks This hook should only be used if there is no other way to access the internal - *state. For many of the common use cases, there are dedicated hooks available - *such as {@link useReactFlow}, {@link useViewport}, etc. + * state. For many of the common use cases, there are dedicated hooks available + * such as {@link useReactFlow}, {@link useViewport}, etc. */ function useStoreApi() { const store = useContext(StoreContext) as UseBoundStoreWithEqualityFn< diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index ce21e84f..1a6de7c2 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -19,6 +19,9 @@ import type { import { EdgeTypes, InternalNode, Node } from '.'; +/** + * @inline + */ export type EdgeLabelOptions = { label?: string | ReactNode; labelStyle?: CSSProperties; @@ -29,7 +32,8 @@ export type EdgeLabelOptions = { }; /** - * The Edge type is mainly used for the `edges` that get passed to the ReactFlow component + * An `Edge` is the complete description with everything React Flow needs + *to know in order to render it. * @public */ export type Edge< @@ -91,6 +95,12 @@ export type EdgeWrapperProps = { disableKeyboardA11y?: boolean; }; +/** + * Many properties on an [`Edge`](/api-reference/types/edge) are optional. When a new edge is created, + *the properties that are not provided will be filled in with the default values + *passed to the `defaultEdgeOptions` prop of the [``](/api-reference/react-flow#defaultedgeoptions) + *component. + */ export type DefaultEdgeOptions = DefaultEdgeOptionsBase; export type EdgeTextProps = SVGAttributes & @@ -100,8 +110,10 @@ export type EdgeTextProps = SVGAttributes & }; /** - * Custom edge component props + * When you implement a custom edge it is wrapped in a component that enables some + *basic functionality. The `EdgeProps` type is the props that are passed to this. * @public + * @expand */ export type EdgeProps = Pick< EdgeType, @@ -185,6 +197,13 @@ export type SimpleBezierEdgeProps = EdgeComponentProps; export type OnReconnect = (oldEdge: EdgeType, newConnection: Connection) => void; +/** + * If you want to render a custom component for connection lines, you can set the + *`connectionLineComponent` prop on the [``](/api-reference/react-flow#connection-connectionLineComponent) + *component. The `ConnectionLineComponentProps` are passed to your custom component. + * + * @public + */ export type ConnectionLineComponentProps = { connectionLineStyle?: CSSProperties; connectionLineType: ConnectionLineType; diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index 48e5b2fe..249cf01b 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -64,12 +64,23 @@ export type OnSelectionChangeParams = { export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void; export type FitViewParams = FitViewParamsBase; + +/** + * When calling [`fitView`](/api-reference/types/react-flow-instance#fitview) these options + * can be used to customize the behaviour. For example, the `duration` option can be used to + * transform the viewport smoothly over a given amount of time. + * + * @public + */ export type FitViewOptions = FitViewOptionsBase; export type FitView = (fitViewOptions?: FitViewOptions) => Promise; export type OnInit = ( reactFlowInstance: ReactFlowInstance ) => void; +/** + * @inline + */ export type ViewportHelperFunctions = { /** * Zooms viewport in by 1.2. diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index 992655a4..fbfa39ce 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -217,6 +217,14 @@ export type GeneralHelpers NodeConnection[]; }; +/** + * The `ReactFlowInstance` provides a collection of methods to query and manipulate + *the internal state of your flow. You can get an instance by using the + *[`useReactFlow`](/api-reference/hooks/use-react-flow) hook or attaching a listener to the + *[`onInit`](/api-reference/react-flow#event-oninit) event. + * + * @public + */ export type ReactFlowInstance = GeneralHelpers< NodeType, EdgeType diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 0d3924e4..cfdefb13 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -4,7 +4,9 @@ import type { CoordinateExtent, NodeBase, OnError, NodeProps as NodePropsBase, I import { NodeTypes } from './general'; /** - * The node data structure that gets used for the nodes prop. + * The `Node` type represents everything React Flow needs to know about a given node. + * Many of these properties can be manipulated both by React Flow or by you, but + * some such as `width` and `height` should be considered read-only. * @public */ export type Node< @@ -18,9 +20,10 @@ export type Node< }; /** - * The node data structure that gets used for internal nodes. - * There are some data structures added under node.internal - * that are needed for tracking some properties + * The `InternalNode` type is identical to the base [`Node`](/api-references/types/node) + * type but is extended with some additional properties used internall by React + * Flow. Some functions and callbacks that return nodes may return an `InternalNode`. + * * @public */ export type InternalNode = InternalNodeBase; @@ -60,4 +63,31 @@ export type BuiltInNode = | Node<{ label: string }, 'input' | 'output' | 'default'> | Node, 'group'>; +/** + * When you implement a [custom node](/learn/customization/custom-nodes) it is + * wrapped in a component that enables basic functionality like selection and + * dragging. Your custom node receives `NodeProps` as props. + * + * @public + * @example + * ```tsx + *import { useState } from 'react'; + *import { NodeProps, Node } from '@xyflow/react'; + * + *export type CounterNode = Node<{ initialCount?: number }, 'counter'>; + * + *export default function CounterNode(props: NodeProps) { + * const [count, setCount] = useState(props.data?.initialCount ?? 0); + * + * return ( + *
+ *

Count: {count}

+ * + *
+ * ); + *} + *``` + */ export type NodeProps = NodePropsBase; diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index 6ac2e3c5..e21bdfb6 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -4,9 +4,10 @@ import { isNodeBase, isEdgeBase } from '@xyflow/system'; import type { Edge, Node } from '../types'; /** - * Test whether an object is useable as an [`Node`](/api-reference/types/node). In TypeScript - *this is a type guard that will narrow the type of whatever you pass in to - *[`Node`](/api-reference/types/node) if it returns `true`. + * Test whether an object is useable as an [`Node`](/api-reference/types/node). + * In TypeScript this is a type guard that will narrow the type of whatever you pass in to + * [`Node`](/api-reference/types/node) if it returns `true`. + * * @public * @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 @@ -25,9 +26,10 @@ export const isNode = (element: unknown): element isNodeBase(element); /** - * Test whether an object is useable as an [`Edge`](/api-reference/types/edge). In TypeScript - *this is a type guard that will narrow the type of whatever you pass in to - *[`Edge`](/api-reference/types/edge) if it returns `true`. + * Test whether an object is useable as an [`Edge`](/api-reference/types/edge). + * In TypeScript this is a type guard that will narrow the type of whatever you pass in to + * [`Edge`](/api-reference/types/edge) if it returns `true`. + * * @public * @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 diff --git a/packages/svelte/src/lib/types/edges.ts b/packages/svelte/src/lib/types/edges.ts index de1def91..b2f6ba7e 100644 --- a/packages/svelte/src/lib/types/edges.ts +++ b/packages/svelte/src/lib/types/edges.ts @@ -11,7 +11,9 @@ import type { import type { Node } from '$lib/types'; /** - * The Edge type is mainly used for the `edges` that get passed to the SvelteFlow component. + * An `Edge` is the complete description with everything React Flow needs + *to know in order to render it. + * @public */ export type Edge< EdgeData extends Record = Record, diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index abe7c8f2..9f52b5a3 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -103,6 +103,12 @@ export type EdgeMarker = { export type EdgeMarkerType = string | EdgeMarker; +/** + * Edges may optionally have a marker on either end. The MarkerType type enumerates + * the options available to you when configuring a given marker. + * + * @public + */ export enum MarkerType { Arrow = 'arrow', ArrowClosed = 'arrowclosed', diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index 485bb4f5..8ac3f9e9 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -28,8 +28,8 @@ export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => Promise boolean; +/** + * @inline + */ export type FitViewParamsBase = { nodes: Map>; width: number; @@ -75,6 +91,9 @@ export type FitViewParamsBase = { maxZoom: number; }; +/** + * @inline + */ export type FitViewOptionsBase = { padding?: number; includeHiddenNodes?: boolean; @@ -84,6 +103,17 @@ export type FitViewOptionsBase = { nodes?: (NodeType | { id: string })[]; }; +/** + * Internally, React Flow maintains a coordinate system that is independent of the + * rest of the page. The `Viewport` type tells you where in that system your flow + * is currently being display at and how zoomed in or out it is. + * + * @public + * @remarks A `Transform` has the same properties as the viewport, but they represent + * different things. Make sure you don't get them muddled up or things will start + * to look weird! + * + */ export type Viewport = { x: number; y: number; @@ -94,12 +124,23 @@ export type KeyCode = string | Array; export type SnapGrid = [number, number]; +/** + * This enum is used to set the different modes of panning the viewport when the + * user scrolls. The `Free` mode allows the user to pan in any direction by scrolling + * with a device like a trackpad. The `Vertical` and `Horizontal` modes restrict + * scroll panning to only the vertical or horizontal axis, respectively. + * + * @public + */ export enum PanOnScrollMode { Free = 'free', Vertical = 'vertical', Horizontal = 'horizontal', } +/** + * @inline + */ export type ViewportHelperFunctionOptions = { duration?: number; }; @@ -120,6 +161,14 @@ export type D3ZoomHandler = (this: Element, event: any, d: unknown) => void; export type UpdateNodeInternals = (nodeId: string | string[]) => void; +/** + * This type is mostly used to help position things on top of the flow viewport. For + * example both the [``](/api-reference/components/minimap) and + * [``](/api-reference/components/controls) components take a `position` + * prop of this type. + * + * @public + */ export type PanelPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'; export type ProOptions = { @@ -183,7 +232,8 @@ export type ConnectionInProgress = NodeType & z: number; /** * Holds a reference to the original node object provided by the user. - * Used as an optimization to avoid certain operations. + * Used as an optimization to avoid certain operations. */ userNode: NodeType; handleBounds?: NodeHandleBounds; @@ -89,7 +90,7 @@ export type InternalNodeBase = NodeType & }; /** - * The node data structure that gets used for the nodes prop. + * The node data structure that gets used for the custom nodes props. * * @public */ @@ -141,10 +142,22 @@ export type NodeDragItem = { expandParent?: boolean; }; +/** + * The origin of a Node determines how it is placed relative to its own coordinates. + * `[0, 0]` places it at the top left corner, `[0.5, 0.5]` right in the center and + * `[1, 1]` at the bottom right of its position. + * + * @public + */ export type NodeOrigin = [number, number]; export type OnSelectionDrag = (event: MouseEvent, nodes: NodeBase[]) => void; +/** + * Type for the handles of a node + * + * @public + */ export type NodeHandle = Omit, 'nodeId'>; export type Align = 'center' | 'start' | 'end'; diff --git a/packages/system/src/types/utils.ts b/packages/system/src/types/utils.ts index 61d55698..274bfe55 100644 --- a/packages/system/src/types/utils.ts +++ b/packages/system/src/types/utils.ts @@ -1,3 +1,10 @@ +/** + * While [`PanelPosition`](/api-reference/types/panel-position) can be used to place a + * component in the corners of a container, the `Position` enum is less precise and used + * primarily in relation to edges and handles. + * + * @public + */ export enum Position { Left = 'left', Top = 'top', @@ -12,6 +19,11 @@ export const oppositePosition = { [Position.Bottom]: Position.Top, }; +/** + * All positions are stored in an object with x and y coordinates. + * + * @public + */ export type XYPosition = { x: number; y: number; @@ -35,12 +47,12 @@ export type Transform = [number, number, number]; /** * A coordinate extent represents two points in a coordinate system: one in the top - *left corner and one in the bottom right corner. It is used to represent the - *bounds of nodes in the flow or the bounds of the viewport. + * left corner and one in the bottom right corner. It is used to represent the + * bounds of nodes in the flow or the bounds of the viewport. * * @public * * @remarks Props that expect a `CoordinateExtent` usually default to `[[-∞, -∞], [+∞, +∞]]` - *to represent an unbounded extent. + * to represent an unbounded extent. */ export type CoordinateExtent = [[number, number], [number, number]]; From a541799654799cd46bd3f93eb0aa879708279f3e Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 12 Feb 2025 12:26:22 +0100 Subject: [PATCH 07/11] chore(types): add tsdocs --- .../additional-components/MiniMap/MiniMap.tsx | 12 +++---- .../NodeToolbar/NodeToolbar.tsx | 2 +- packages/react/src/types/edges.ts | 13 ++++--- packages/react/src/types/general.ts | 34 +++++++++++++++++++ packages/react/src/types/instance.ts | 9 +++-- packages/react/src/types/nodes.ts | 20 ++++++++--- packages/react/src/utils/changes.ts | 13 ++++--- 7 files changed, 77 insertions(+), 26 deletions(-) diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index be6dc453..c7f1fdce 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -113,16 +113,16 @@ function MiniMapComponent({ const onSvgClick = onClick ? (event: MouseEvent) => { - const [x, y] = minimapInstance.current?.pointer(event) || [0, 0]; - onClick(event, { x, y }); - } + const [x, y] = minimapInstance.current?.pointer(event) || [0, 0]; + onClick(event, { x, y }); + } : undefined; const onSvgNodeClick = onNodeClick ? useCallback((event: MouseEvent, nodeId: string) => { - const node = store.getState().nodeLookup.get(nodeId)!; - onNodeClick(event, node); - }, []) + const node = store.getState().nodeLookup.get(nodeId)!; + onNodeClick(event, node); + }, []) : undefined; return ( diff --git a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx index 0248b394..3fddceaa 100644 --- a/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx +++ b/packages/react/src/additional-components/NodeToolbar/NodeToolbar.tsx @@ -109,7 +109,7 @@ export function NodeToolbar({ const isActive = typeof isVisible === 'boolean' ? isVisible - : nodes.size === 1 && nodes.values().next().value.selected && selectedNodesCount === 1; + : nodes.size === 1 && nodes.values().next().value?.selected && selectedNodesCount === 1; if (!isActive || !nodes.size) { return null; diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 1a6de7c2..e25fdeb8 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -33,7 +33,7 @@ export type EdgeLabelOptions = { /** * An `Edge` is the complete description with everything React Flow needs - *to know in order to render it. + * to know in order to render it. * @public */ export type Edge< @@ -97,9 +97,8 @@ export type EdgeWrapperProps = { /** * Many properties on an [`Edge`](/api-reference/types/edge) are optional. When a new edge is created, - *the properties that are not provided will be filled in with the default values - *passed to the `defaultEdgeOptions` prop of the [``](/api-reference/react-flow#defaultedgeoptions) - *component. + * the properties that are not provided will be filled in with the default values + * passed to the `defaultEdgeOptions` prop of the [``](/api-reference/react-flow#defaultedgeoptions) component. */ export type DefaultEdgeOptions = DefaultEdgeOptionsBase; @@ -111,7 +110,7 @@ export type EdgeTextProps = SVGAttributes & /** * When you implement a custom edge it is wrapped in a component that enables some - *basic functionality. The `EdgeProps` type is the props that are passed to this. + * basic functionality. The `EdgeProps` type is the props that are passed to this. * @public * @expand */ @@ -199,8 +198,8 @@ export type OnReconnect = (oldEdge: EdgeType, newC /** * If you want to render a custom component for connection lines, you can set the - *`connectionLineComponent` prop on the [``](/api-reference/react-flow#connection-connectionLineComponent) - *component. The `ConnectionLineComponentProps` are passed to your custom component. + * `connectionLineComponent` prop on the [``](/api-reference/react-flow#connection-connectionLineComponent) + * component. The `ConnectionLineComponentProps` are passed to your custom component. * * @public */ diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index 249cf01b..913ec634 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -18,11 +18,44 @@ import { import type { Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.'; +/** + * This type can be used to type the `onNodesChange` function with a custom node type. + * + * @public + * + * @example + * + * ```ts + * const onNodesChange: OnNodesChange = useCallback((changes) => { + * setNodes((nodes) => applyNodeChanges(nodes, changes)); + * },[]); + * ``` + */ export type OnNodesChange = (changes: NodeChange[]) => void; + +/** + * This type can be used to type the `onEdgesChange` function with a custom edge type. + * + * @public + * + * @example + * + * ```ts + * const onEdgesChange: OnEdgesChange = useCallback((changes) => { + * setEdges((edges) => applyEdgeChanges(edges, changes)); + * },[]); + * ``` + */ export type OnEdgesChange = (changes: EdgeChange[]) => void; export type OnNodesDelete = (nodes: NodeType[]) => void; export type OnEdgesDelete = (edges: EdgeType[]) => void; + +/** + * This type can be used to type the `onDelete` function with a custom node and edge type. + * + * @public + */ export type OnDelete = (params: { nodes: NodeType[]; edges: EdgeType[]; @@ -39,6 +72,7 @@ export type NodeTypes = Record< } > >; + export type EdgeTypes = Record< string, ComponentType< diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts index fbfa39ce..85d66651 100644 --- a/packages/react/src/types/instance.ts +++ b/packages/react/src/types/instance.ts @@ -13,6 +13,9 @@ export type DeleteElementsOptions = { edges?: (Edge | { id: Edge['id'] })[]; }; +/** + * @inline + */ export type GeneralHelpers = { /** * Returns nodes. @@ -219,9 +222,9 @@ export type GeneralHelpers = { nodeClickDistance?: number; }; +/** + * The `BuiltInNode` type represents the built-in node types that are available in React Flow. + * You can use this type to extend your custom node type if you still want ot use the built-in ones. + * + * @public + * @example + * ```ts + * type CustomNode = Node<{ value: number }, 'custom'>; + * type MyAppNode = CustomNode | BuiltInNode; + * ``` + */ export type BuiltInNode = | Node<{ label: string }, 'input' | 'output' | 'default'> | Node, 'group'>; diff --git a/packages/react/src/utils/changes.ts b/packages/react/src/utils/changes.ts index ea0a9140..0bc30e46 100644 --- a/packages/react/src/utils/changes.ts +++ b/packages/react/src/utils/changes.ts @@ -147,8 +147,6 @@ function applyChange(change: any, element: any): any { /** * 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 @@ -172,6 +170,10 @@ function applyChange(change: any, element: any): any { * ); *} *``` + * @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. */ export function applyNodeChanges( changes: NodeChange[], @@ -183,13 +185,10 @@ export function applyNodeChanges( /** * 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 - * * ```tsx *import { useState, useCallback } from 'react'; *import { ReactFlow, applyEdgeChanges } from '@xyflow/react'; @@ -209,6 +208,10 @@ export function applyNodeChanges( * ); *} *``` + * @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. */ export function applyEdgeChanges( changes: EdgeChange[], From c3dc6d3851749925d63012c331b77deacea32f49 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 12 Feb 2025 14:29:20 +0100 Subject: [PATCH 08/11] chore(tsdocs): cleanup --- packages/system/src/utils/graph.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts index 1bb318d4..b2b1e6e5 100644 --- a/packages/system/src/utils/graph.ts +++ b/packages/system/src/utils/graph.ts @@ -55,7 +55,7 @@ export const isInternalNodeBase = = { /** * Returns the bounding box that contains all the given nodes in an array. This can - *be useful when combined with [`getViewportForBounds`](/api-reference/utils/get-viewport-for-bounds) - *to calculate the correct transform to fit the given nodes in a viewport. + * be useful when combined with [`getViewportForBounds`](/api-reference/utils/get-viewport-for-bounds) + * to calculate the correct transform to fit the given nodes in a viewport. * @public * @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 @@ -212,8 +212,8 @@ export const getNodesBounds = ( currentNode = isId ? params.nodeLookup.get(nodeOrId) : !isInternalNodeBase(nodeOrId) - ? params.nodeLookup.get(nodeOrId.id) - : nodeOrId; + ? params.nodeLookup.get(nodeOrId.id) + : nodeOrId; } const nodeBox = currentNode ? nodeToBox(currentNode, params.nodeOrigin) : { x: 0, y: 0, x2: 0, y2: 0 }; @@ -296,7 +296,8 @@ export const getNodesInside = ( }; /** - * This utility filters an array of edges, keeping only those where either the source or target node is present in the given array of nodes. + * This utility filters an array of edges, keeping only those where either the source or target + * node is present in the given array of nodes. * @public * @param nodes - Nodes you want to get the connected edges for * @param edges - All edges @@ -459,9 +460,9 @@ export async function getElementsToRemove; }): Promise<{ - nodes: NodeType[]; - edges: EdgeType[]; - }> { + nodes: NodeType[]; + edges: EdgeType[]; +}> { const nodeIds = new Set(nodesToRemove.map((node) => node.id)); const matchingNodes: NodeType[] = []; From 0b06491ef9c139d13c8ea73a26c1b18070fe0381 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 12 Feb 2025 14:42:12 +0100 Subject: [PATCH 09/11] chore(tsdocs): cleanup --- .../additional-components/Controls/types.ts | 6 +++++ .../additional-components/MiniMap/types.ts | 4 ++++ .../NodeResizer/types.ts | 9 ++++++++ .../NodeToolbar/types.ts | 3 +++ .../react/src/components/Handle/index.tsx | 12 ++++++---- packages/react/src/components/Panel/index.tsx | 23 +++++++++++-------- packages/react/src/types/edges.ts | 7 ++++++ packages/system/src/types/edges.ts | 3 +++ packages/system/src/xyresizer/types.ts | 15 ++++++++++++ 9 files changed, 68 insertions(+), 14 deletions(-) diff --git a/packages/react/src/additional-components/Controls/types.ts b/packages/react/src/additional-components/Controls/types.ts index c36275d9..bccd1117 100644 --- a/packages/react/src/additional-components/Controls/types.ts +++ b/packages/react/src/additional-components/Controls/types.ts @@ -3,6 +3,9 @@ import type { PanelPosition } from '@xyflow/system'; import type { FitViewOptions } from '../../types'; +/** + * @expand + */ export type ControlProps = { /** Show button for zoom in/out */ showZoom?: boolean; @@ -35,4 +38,7 @@ export type ControlProps = { orientation?: 'horizontal' | 'vertical'; }; +/** + * @expand + */ export type ControlButtonProps = ButtonHTMLAttributes; diff --git a/packages/react/src/additional-components/MiniMap/types.ts b/packages/react/src/additional-components/MiniMap/types.ts index 351395f9..8416f835 100644 --- a/packages/react/src/additional-components/MiniMap/types.ts +++ b/packages/react/src/additional-components/MiniMap/types.ts @@ -6,6 +6,9 @@ import type { Node } from '../../types'; export type GetMiniMapNodeAttribute = (node: NodeType) => string; +/** + * @expand + */ export type MiniMapProps = Omit, 'onClick'> & { /** Color of nodes on minimap */ nodeColor?: string | GetMiniMapNodeAttribute; @@ -62,6 +65,7 @@ export type MiniMapNodes = Pick< * The props that are passed to the MiniMapNode component * * @public + * @expand */ export type MiniMapNodeProps = { id: string; diff --git a/packages/react/src/additional-components/NodeResizer/types.ts b/packages/react/src/additional-components/NodeResizer/types.ts index 2d1a7c15..1295440f 100644 --- a/packages/react/src/additional-components/NodeResizer/types.ts +++ b/packages/react/src/additional-components/NodeResizer/types.ts @@ -9,6 +9,9 @@ import type { OnResizeEnd, } from '@xyflow/system'; +/** + * @expand + */ export type NodeResizerProps = { /** * Id of the node it is resizing @@ -47,6 +50,9 @@ export type NodeResizerProps = { onResizeEnd?: OnResizeEnd; }; +/** + * @expand + */ export type ResizeControlProps = Pick< NodeResizerProps, | 'nodeId' @@ -77,6 +83,9 @@ export type ResizeControlProps = Pick< children?: ReactNode; }; +/** + * @expand + */ export type ResizeControlLineProps = ResizeControlProps & { position?: ControlLinePosition; }; diff --git a/packages/react/src/additional-components/NodeToolbar/types.ts b/packages/react/src/additional-components/NodeToolbar/types.ts index 00bec37c..91a717da 100644 --- a/packages/react/src/additional-components/NodeToolbar/types.ts +++ b/packages/react/src/additional-components/NodeToolbar/types.ts @@ -1,6 +1,9 @@ import type { HTMLAttributes } from 'react'; import type { Position, Align } from '@xyflow/system'; +/** + * @expand + */ export type NodeToolbarProps = HTMLAttributes & { /** Id of the node, or array of ids the toolbar should be displayed at */ nodeId?: string | string[]; diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 72e7ad21..df7b428b 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -28,10 +28,14 @@ import { useNodeId } from '../../contexts/NodeIdContext'; import { type ReactFlowState } from '../../types'; import { fixedForwardRef } from '../../utils'; -export interface HandleProps extends HandlePropsSystem, Omit, 'id'> { - /** Callback called when connection is made */ - onConnect?: OnConnect; -} +/** + * @expand + */ +export type HandleProps = HandlePropsSystem & + Omit, 'id'> & { + /** Callback called when connection is made */ + onConnect?: OnConnect; + }; const selector = (s: ReactFlowState) => ({ connectOnClick: s.connectOnClick, diff --git a/packages/react/src/components/Panel/index.tsx b/packages/react/src/components/Panel/index.tsx index 461cc1ba..8d854b91 100644 --- a/packages/react/src/components/Panel/index.tsx +++ b/packages/react/src/components/Panel/index.tsx @@ -5,6 +5,19 @@ import type { PanelPosition } from '@xyflow/system'; import { useStore } from '../../hooks/useStore'; import type { ReactFlowState } from '../../types'; +/** + * @expand + */ +export type PanelProps = HTMLAttributes & { + /** + * The position of the panel + */ + position?: PanelPosition; + children: ReactNode; +}; + +const selector = (s: ReactFlowState) => (s.userSelectionActive ? 'none' : 'all'); + /** * The `` component helps you position content above the viewport. * It is used internally by the [``](/api-reference/components/minimap) @@ -30,16 +43,6 @@ import type { ReactFlowState } from '../../types'; *} *``` */ -export type PanelProps = HTMLAttributes & { - /** - * The position of the panel - */ - position?: PanelPosition; - children: ReactNode; -}; - -const selector = (s: ReactFlowState) => (s.userSelectionActive ? 'none' : 'all'); - export const Panel = forwardRef( ({ position = 'top-left', children, className, style, ...rest }, ref) => { const pointerEvents = useStore(selector); diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index e25fdeb8..4d1e3f04 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -132,6 +132,7 @@ export type EdgeProps = Pick< /** * BaseEdge component props * @public + * @expand */ export type BaseEdgeProps = Omit, 'd'> & EdgeLabelOptions & { @@ -148,6 +149,7 @@ export type BaseEdgeProps = Omit, 'd'> & /** * Helper type for edge components that get exported by the library * @public + * @expand */ export type EdgeComponentProps = EdgePosition & EdgeLabelOptions & { @@ -167,30 +169,35 @@ export type EdgeComponentWithPathOptions = EdgeComponentProps & { /** * BezierEdge component props * @public + * @expand */ export type BezierEdgeProps = EdgeComponentWithPathOptions; /** * SmoothStepEdge component props * @public + * @expand */ export type SmoothStepEdgeProps = EdgeComponentWithPathOptions; /** * StepEdge component props * @public + * @expand */ export type StepEdgeProps = EdgeComponentWithPathOptions; /** * StraightEdge component props * @public + * @expand */ export type StraightEdgeProps = Omit; /** * SimpleBezier component props * @public + * @expand */ export type SimpleBezierEdgeProps = EdgeComponentProps; diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index 9f52b5a3..76382e73 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -118,6 +118,9 @@ export type MarkerProps = EdgeMarker & { id: string; }; +/** + * @inline + */ export type EdgePosition = { sourceX: number; sourceY: number; diff --git a/packages/system/src/xyresizer/types.ts b/packages/system/src/xyresizer/types.ts index 99480512..34cad9e8 100644 --- a/packages/system/src/xyresizer/types.ts +++ b/packages/system/src/xyresizer/types.ts @@ -11,10 +11,25 @@ export type ResizeParamsWithDirection = ResizeParams & { direction: number[]; }; +/** + * Used to determine the control line position of the NodeResizer + * + * @public + */ export type ControlLinePosition = 'top' | 'bottom' | 'left' | 'right'; +/** + * Used to determine the control position of the NodeResizer + * + * @public + */ export type ControlPosition = ControlLinePosition | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; +/** + * Used to determine the variant of the resize control + * + * @public + */ export enum ResizeControlVariant { Line = 'line', Handle = 'handle', From f51f80db412bf835fe3fe95853a154fe1d02df22 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 12 Feb 2025 16:31:16 +0100 Subject: [PATCH 10/11] chore(tsdocs): cleanup --- packages/react/package.json | 2 +- packages/system/package.json | 2 +- pnpm-lock.yaml | 48 ------------------------------ tooling/eslint-config/package.json | 1 - tooling/eslint-config/src/index.js | 4 +-- 5 files changed, 3 insertions(+), 54 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index af284012..1cdf428c 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -57,7 +57,7 @@ "build": "rollup --config node:@xyflow/rollup-config --environment NODE_ENV:production && npm run css", "css": "postcss src/styles/{base,style}.css --config ./../../tooling/postcss-config/ --dir dist ", "css-watch": "pnpm css --watch", - "lint": "eslint --ext .js,.jsx,.ts,.tsx src --fix", + "lint": "eslint --ext .js,.jsx,.ts,.tsx src", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/packages/system/package.json b/packages/system/package.json index dfb81e1c..c7b1ee03 100644 --- a/packages/system/package.json +++ b/packages/system/package.json @@ -46,7 +46,7 @@ "scripts": { "dev": "concurrently \"rollup --config node:@xyflow/rollup-config --watch\"", "build": "rollup --config node:@xyflow/rollup-config --environment NODE_ENV:production", - "lint": "eslint --ext .js,.ts src --fix", + "lint": "eslint --ext .js,.ts src", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ae9a41c..22541966 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -428,9 +428,6 @@ importers: tooling/eslint-config: devDependencies: - '@stylistic/eslint-plugin': - specifier: ^3.1.0 - version: 3.1.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/eslint-plugin': specifier: ^8.23.0 version: 8.23.0(@typescript-eslint/parser@8.23.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) @@ -1709,12 +1706,6 @@ packages: resolution: {integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==} engines: {node: '>=18'} - '@stylistic/eslint-plugin@3.1.0': - resolution: {integrity: sha512-pA6VOrOqk0+S8toJYhQGv2MWpQQR0QpeUo9AhNkC49Y26nxBQ/nH1rta9bUU1rPw2fJ1zZEMV5oCX5AazT7J2g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: '>=8.40.0' - '@svelte-put/shortcut@3.1.1': resolution: {integrity: sha512-2L5EYTZXiaKvbEelVkg5znxqvfZGZai3m97+cAiUBhLZwXnGtviTDpHxOoZBsqz41szlfRMcamW/8o0+fbW3ZQ==} peerDependencies: @@ -2226,11 +2217,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -3304,10 +3290,6 @@ packages: esm-env@1.0.0: resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4794,10 +4776,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -7981,18 +7959,6 @@ snapshots: '@sindresorhus/merge-streams@1.0.0': {} - '@stylistic/eslint-plugin@3.1.0(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@typescript-eslint/utils': 8.23.0(eslint@8.57.0)(typescript@5.4.5) - eslint: 8.57.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 - estraverse: 5.3.0 - picomatch: 4.0.2 - transitivePeerDependencies: - - supports-color - - typescript - '@svelte-put/shortcut@3.1.1(svelte@4.2.12)': dependencies: svelte: 4.2.12 @@ -8647,18 +8613,12 @@ snapshots: dependencies: acorn: 8.10.0 - acorn-jsx@5.3.2(acorn@8.14.0): - dependencies: - acorn: 8.14.0 - acorn@8.10.0: {} acorn@8.11.2: {} acorn@8.11.3: {} - acorn@8.14.0: {} - aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -10297,12 +10257,6 @@ snapshots: esm-env@1.0.0: {} - espree@10.3.0: - dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 4.2.0 - espree@9.6.1: dependencies: acorn: 8.10.0 @@ -12007,8 +11961,6 @@ snapshots: picomatch@2.3.1: {} - picomatch@4.0.2: {} - pify@2.3.0: {} pify@4.0.1: {} diff --git a/tooling/eslint-config/package.json b/tooling/eslint-config/package.json index 2c664276..d4aa78ee 100644 --- a/tooling/eslint-config/package.json +++ b/tooling/eslint-config/package.json @@ -5,7 +5,6 @@ "license": "MIT", "main": "src/index.js", "devDependencies": { - "@stylistic/eslint-plugin": "^3.1.0", "@typescript-eslint/eslint-plugin": "^8.23.0", "@typescript-eslint/parser": "^8.23.0", "eslint-config-prettier": "^10.0.1", diff --git a/tooling/eslint-config/src/index.js b/tooling/eslint-config/src/index.js index 7bd69b5a..f2ce34a0 100644 --- a/tooling/eslint-config/src/index.js +++ b/tooling/eslint-config/src/index.js @@ -13,7 +13,7 @@ module.exports = { 'turbo', 'prettier', ], - plugins: ['react', '@typescript-eslint', '@stylistic'], + plugins: ['react', '@typescript-eslint'], parserOptions: { ecmaFeatures: { jsx: true, @@ -28,7 +28,5 @@ module.exports = { }, rules: { '@typescript-eslint/no-non-null-assertion': 'off', - '@stylistic/indent': ['error', 2], - '@stylistic/multiline-comment-style': ['error', 'starred-block'], }, }; From 6c121d427fea9a11e86a85f95d2c12ba8af34919 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 12 Feb 2025 16:33:33 +0100 Subject: [PATCH 11/11] chore(changeset): add --- .changeset/brown-apples-deliver.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/brown-apples-deliver.md diff --git a/.changeset/brown-apples-deliver.md b/.changeset/brown-apples-deliver.md new file mode 100644 index 00000000..cf1754ad --- /dev/null +++ b/.changeset/brown-apples-deliver.md @@ -0,0 +1,7 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +'@xyflow/system': patch +--- + +Add more TSDocs to components, hooks, utils funcs and types