/**
* Flow — a canvas of nodes joined by edges, that you pan, pinch and rearrange
* with a finger.
*
* ```tsx
*
*
*
* …
*
*
* …
*
*
*
*
* ```
*
* ## Where the positions live
*
* Every node's box is kept twice: on the UI thread, where a drag writes it
* every frame and the node's own transform reads it, and in React state, where
* the edges are rendered from it. A dragged node therefore never lags its own
* finger, and the edges attached to it redraw as it moves.
*
* The tempting design is one copy — everything on the UI thread, edges
* animating their own path strings, no renders at all. It does not draw. An
* animated SVG path in React Native reliably animates its `d` and nothing
* else, and only while nothing else about it is animated; anything more is
* dropped with no error, leaving a path that never receives its geometry. So
* the edges are ordinary elements and cost a render per drag frame. That is a
* real cost and it is the right trade.
*
* An `animated` edge keeps that arrangement and animates the one property the
* geometry does not own: the dash offset. Its `d` still arrives by re-render,
* so a dragged node reshapes the edge while the dashes keep marching.
*
* JavaScript is told a drag has finished through `onNodeDragEnd`. Positions
* are otherwise yours to leave alone: pass `position` once and the canvas
* takes it from there, or keep it in state and pass it back to drive nodes
* from outside.
*
* ## Two gestures, one canvas
*
* The pane pans and pinches; a node drags; a handle draws a new connection.
* They are nested gesture detectors rather than one gesture doing three jobs,
* so the innermost thing under the finger wins, which is what a finger expects.
*/
import { type ReactNode } from 'react';
import { type ViewProps } from 'react-native';
import { type FlowPoint, type FlowRect, type FlowSide } from './flow-paths.js';
import { type FlowEndpoint, type FlowEndpointReference } from './flow-identifiers.js';
export type FlowEdgeVariant = 'bezier' | 'smoothstep' | 'step' | 'straight';
export interface FlowViewport {
x: number;
y: number;
zoom: number;
}
export interface FlowNodePosition {
x: number;
y: number;
}
/** What `onConnect` is handed when a connection lands. */
export interface FlowConnection {
/** Node the drag started from. */
source: string;
/** Handle it started from, when it started from one. */
sourceHandle?: string;
/** Node it was dropped on. */
target: string;
/** Handle it was dropped on, when it landed on one. */
targetHandle?: string;
}
/**
* Which layer a part belongs to. Declared on the component as a static so the
* root can sort its children without depending on function identity.
*/
type FlowSlot = 'background' | 'overlay';
export interface FlowProps extends Omit {
className?: string;
/** Where the canvas starts. `zoom` of 1 is one graph point per screen point. */
defaultViewport?: FlowViewport;
/** Closest the canvas will zoom out. */
minZoom?: number;
/** Closest it will zoom in. */
maxZoom?: number;
/** Drag the empty canvas to move it. */
panOnDrag?: boolean;
/** Pinch to zoom. */
zoomOnPinch?: boolean;
/**
* Frame every node once they have all measured themselves. For a graph whose
* positions come from data and are not laid out against a known screen size.
*/
fitViewOnMount?: boolean;
/** Padding left around the graph when fitting, in screen points. */
fitViewPadding?: number;
/** The canvas has moved or zoomed. Fired as it happens, on the JS thread. */
onViewportChange?: (viewport: FlowViewport) => void;
/** A node was dropped somewhere new. The only time a drag reaches JavaScript. */
onNodeDragEnd?: (id: string, position: FlowNodePosition) => void;
/**
* A connection was drawn between two handles. The canvas never adds the edge
* itself — the graph is yours, so what a new connection means is yours too.
*/
onConnect?: (connection: FlowConnection) => void;
/** Refuse a connection before `onConnect` sees it. */
isValidConnection?: (connection: FlowConnection) => boolean;
children?: ReactNode;
}
declare function FlowRoot({ className, defaultViewport, minZoom, maxZoom, panOnDrag, zoomOnPinch, fitViewOnMount, fitViewPadding, onViewportChange, onNodeDragEnd, onConnect, isValidConnection, children, ...props }: FlowProps): import("react").JSX.Element;
export interface FlowBackgroundProps {
/** The mark repeated across the canvas. */
variant?: 'dots' | 'lines' | 'cross' | 'none';
/** Points between marks. */
gap?: number;
/** How big each mark is drawn. */
size?: number;
/** Mark colour. Defaults to a muted theme token. */
color?: string;
}
/**
* The grid behind everything. It lives inside the transformed layer, so it
* pans and scales with the graph — which is the whole point: a grid that
* stayed put would say the canvas was not moving.
*/
declare function FlowBackground({ variant, gap, size, color, }: FlowBackgroundProps): import("react").JSX.Element | null;
declare namespace FlowBackground {
var displayName: string;
var slot: FlowSlot;
}
export interface FlowNodeProps extends Omit {
/** Identifies the node to edges and to `onNodeDragEnd`. Must be unique. */
id: string;
/** Where it starts, in graph coordinates. */
position: FlowNodePosition;
className?: string;
/** Let a finger move it. */
draggable?: boolean;
/**
* Keep the node inside the `Flow.Group` it is drawn in. A drag stops at the
* container's edge instead of leaving it. Ignored outside a group — there is
* nothing to be kept inside of.
*/
confine?: boolean;
/**
* Hold the node still: it takes no drag of its own and moves only when its
* container does. For a diagram where the boxes are what you rearrange and
* their contents are a fixed part of them.
*/
pinned?: boolean;
/** Draw the selected ring. */
selected?: boolean;
/** Tapping the node — separate from dragging it. */
onPress?: () => void;
/**
* Delete the node when assistive technology requests the advertised Delete
* node action. No delete action is exposed when this is omitted.
*/
onDelete?: () => void;
/** Graph points covered by each Move up, right, down or left accessibility action. */
accessibilityMoveStep?: number;
/** Spoken name. Defaults to the node's id. */
accessibilityLabel?: string;
children?: ReactNode;
}
/**
* One box on the canvas. Its own content is whatever you put inside — a Frame
* is the usual answer, since a node is a titled card of rows more often than
* it is anything else.
*/
declare function FlowNode({ id, position, className, draggable, confine, pinned, selected, onPress, onDelete, accessibilityMoveStep, accessibilityLabel, accessibilityActions, onAccessibilityAction, children, ...props }: FlowNodeProps): import("react").JSX.Element;
declare namespace FlowNode {
var displayName: string;
}
export interface FlowHandleProps {
/** Names the handle to an edge, as `"nodeId.handleId"`. */
id?: string;
/** Which face it sits on. */
position?: FlowSide;
/**
* `source` starts connections, `target` receives them, `both` does either.
* A drag from a source can only land on a target, and the other way round.
*/
type?: 'source' | 'target' | 'both';
/** Where along the face, 0–1. For more than one handle on a side. */
offset?: number;
className?: string;
/**
* Spoken handle name used in the parent node's connection actions. Defaults
* to the handle's id.
*/
accessibilityLabel?: string;
/** Draw nothing. The handle still anchors edges and still accepts a drop. */
hidden?: boolean;
}
/**
* A port on a node — both the point an edge attaches to and the grip a new
* connection is dragged from.
*
* Its position is worked out from the node's box and the face it names, so it
* never has to measure itself. That matters: a handle that measured would be
* one frame behind the node it sits on, and the edge would trail its own port.
*/
declare function FlowHandle({ id, position, type, offset, className, accessibilityLabel, hidden, }: FlowHandleProps): import("react").JSX.Element | null;
declare namespace FlowHandle {
var displayName: string;
}
export interface FlowEdgeProps {
/** Source node or handle. Strings retain the `"nodeId.handleId"` shorthand. */
from: FlowEndpointReference;
/** Target node or handle, in the same shape. */
to: FlowEndpointReference;
/** How the edge is routed. */
variant?: FlowEdgeVariant;
/**
* Mark the edge as carrying something — a request, a build, a dependency
* that is live rather than declared. Draws it dashed and marches the dashes
* from source to target. Falls back to a still dashed edge when the
* operating system is set to reduce motion.
*/
animated?: boolean;
/** Draw it broken rather than solid. */
dashed?: boolean;
/**
* Stroke colour. Defaults to the muted-foreground token — an edge is content
* rather than chrome, and the border token it would otherwise share with the
* nodes is, by design, barely there.
*/
color?: string;
/** Stroke width in graph points. */
width?: number;
/** Put an arrowhead on the target end. */
arrow?: boolean;
/** Override the face it leaves from. Otherwise worked out from the layout. */
fromSide?: FlowSide;
/** Override the face it arrives at. */
toSide?: FlowSide;
/** Corner radius for `smoothstep`. */
radius?: number;
/** How far the edge steps clear of a node before turning. */
gap?: number;
/** Curve strength for `bezier`. */
curvature?: number;
}
/**
* A line between two nodes. It names them rather than coordinates, and works
* out its own geometry from wherever they currently are — including which face
* to use, which is why a graph the user rearranges does not need re-specifying.
*
* It draws nothing where it is written. Every edge in a canvas has to end up
* inside one `