← All @molecule/* packages · App templates
@molecule/app-flow-canvas-reactFeature · flow-canvas · App (browser) · v1.0.1 · Apache-2.0
React node-and-edge flow canvas — drag nodes, connect ports, pan + zoom, build DAGs / workflows visually
npm install @molecule/app-flow-canvas-react@molecule/app-flow-canvas-react is a ready-made flow-canvas feature for the app (browser) side. It composes the core interfaces it needs, so it works with whichever providers your app has bonded.
import { FlowCanvas, type FlowNode, type FlowEdge } from '@molecule/app-flow-canvas-react'
function Builder() {
const [nodes, setNodes] = useState<FlowNode[]>(initialNodes)
const [edges, setEdges] = useState<FlowEdge[]>(initialEdges)
return (
<FlowCanvas
nodes={nodes}
edges={edges}
onChange={({ nodes, edges }) => {
setNodes(nodes)
setEdges(edges)
}}
nodeRenderers={{
task: (n) => <strong>{(n.data as { label: string }).label}</strong>,
}}
/>
)
}Works with: @molecule/app-react, @molecule/app-ui
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
React flow / DAG canvas primitives.
Exports:
<FlowCanvas> — top-level node-and-edge editor with drag, connect,
pan, zoom, select, and delete behaviors built in.FlowNode, FlowEdge, FlowPoint, FlowChange, FlowSelection,
FlowViewport, FlowNodeRenderer, FlowNodeRenderers types.bezierPath, defaultSourcePort,
defaultTargetPort, addEdge, removeEdge, removeNode,
moveNode, translateNode, clientToWorld).Used by ai-chatbot-builder, ai-workflow-automator, and ai-agent-playground for visual graph composition.
import { FlowCanvas, type FlowNode, type FlowEdge } from '@molecule/app-flow-canvas-react'
function Builder() {
const [nodes, setNodes] = useState<FlowNode[]>(initialNodes)
const [edges, setEdges] = useState<FlowEdge[]>(initialEdges)
return (
<FlowCanvas
nodes={nodes}
edges={edges}
onChange={({ nodes, edges }) => {
setNodes(nodes)
setEdges(edges)
}}
nodeRenderers={{
task: (n) => <strong>{(n.data as { label: string }).label}</strong>,
}}
/>
)
}
feature
npm install @molecule/app-flow-canvas-react @molecule/app-react @molecule/app-ui react
npm install -D @types/react
FlowCanvasProps<FlowCanvas> props.
interface FlowCanvasProps {
/** Nodes (controlled) or initial nodes (uncontrolled). */
nodes: FlowNode[]
/** Edges (controlled) or initial edges (uncontrolled). */
edges: FlowEdge[]
/**
* If provided, the canvas runs in **controlled** mode: built-in
* interactions (drag, connect, delete) call `onChange` instead of
* mutating internal state.
*/
onChange?: (next: FlowChange) => void
/** Map of node `type` → renderer. */
nodeRenderers?: FlowNodeRenderers
/** Fallback children rendered inside every node when `nodeRenderers` has no match. */
children?: ReactNode
/** Fired when the selection changes. */
onSelectionChange?: (selection: FlowSelection) => void
/** Fired when an edge is clicked. */
onEdgeClick?: (edge: FlowEdge) => void
/** Fired when a node is clicked. */
onNodeClick?: (node: FlowNode) => void
/** Disables the built-in delete-key shortcut. */
disableDeleteShortcut?: boolean
/** Disables panning of the canvas. */
disablePan?: boolean
/** Disables zooming via mouse wheel. */
disableZoom?: boolean
/** Show the dotted background grid. Defaults to true. */
showGrid?: boolean
/** Initial viewport (uncontrolled only). */
initialViewport?: FlowViewport
/** Extra classes merged onto the outer wrapper. */
className?: string
}
FlowChangeChange payload emitted by <FlowCanvas> when the graph is mutated by
a built-in interaction (drag, connect, delete).
interface FlowChange {
/** Updated node array (always the full new array). */
nodes: FlowNode[]
/** Updated edge array (always the full new array). */
edges: FlowEdge[]
}
FlowEdgeEdge connecting two nodes.
sourceHandle / targetHandle are optional handle ids — when omitted
the edge connects the right edge of the source to the left edge of the
target (the default port positions for a left-to-right DAG).
interface FlowEdge {
/** Unique edge id. */
id: string
/** Source node id. */
source: string
/** Optional source-side handle id. */
sourceHandle?: string
/** Target node id. */
target: string
/** Optional target-side handle id. */
targetHandle?: string
}
FlowNodeSingle node on the canvas.
data is opaque to the canvas — it's passed back to the renderer the
consumer registers for the matching type.
interface FlowNode<T = unknown> {
/** Unique node id. */
id: string
/** Discriminator the consumer uses to pick a renderer. */
type: string
/** World-space top-left of the node. */
position: FlowPoint
/** Free-form payload echoed back to the renderer. */
data?: T
/** Optional explicit width (in world units). Defaults to 180. */
width?: number
/** Optional explicit height (in world units). Defaults to 80. */
height?: number
}
FlowPointA 2-D point in canvas (world) coordinates.
interface FlowPoint {
/** X coordinate. */
x: number
/** Y coordinate. */
y: number
}
FlowSelectionSelection state held internally by <FlowCanvas> and surfaced through
the onSelectionChange callback.
interface FlowSelection {
/** Selected node ids. */
nodeIds: string[]
/** Selected edge ids. */
edgeIds: string[]
}
FlowViewportViewport (pan + zoom) state.
interface FlowViewport {
/** Horizontal pan offset, in screen pixels. */
x: number
/** Vertical pan offset, in screen pixels. */
y: number
/** Zoom factor; 1 = 100%. */
zoom: number
}
FlowNodeRendererRenderer for a single node type.
type FlowNodeRenderer<T = unknown> = (node: FlowNode<T>) => ReactNode
FlowNodeRenderersMap of node type → renderer.
type FlowNodeRenderers = Record<string, FlowNodeRenderer>
addEdge(edges, edge)Append a new edge — guarding against self-loops and exact duplicates.
function addEdge(edges: FlowEdge[], edge: FlowEdge): FlowEdge[]
edges — Current edge list.edge — Candidate edge.Returns: New edge array; returns edges unchanged if the edge would self-loop or duplicate an existing source/target/handle pair.
bezierPath(a, b)Build a smooth horizontal cubic-Bezier SVG path between two world points, with handles tugged horizontally for a "flow chart" feel.
function bezierPath(a: FlowPoint, b: FlowPoint): string
a — Start point (source).b — End point (target).Returns: SVG d attribute string.
clientToWorld(clientX, clientY, rect, viewport)Convert a screen-space pointer event coordinate into world-space (canvas-local) coordinates given the current viewport pan + zoom and the canvas element's bounding rect.
function clientToWorld(
clientX: number,
clientY: number,
rect: { left: number; top: number },
viewport: { x: number; y: number; zoom: number },
): FlowPoint
clientX — Pointer event clientX.clientY — Pointer event clientY.rect — The canvas root's getBoundingClientRect().viewport — Current pan + zoom.Returns: World-space point.
defaultSourcePort(node)World-space coordinates of a node's default source port (right edge,
vertically centred). Used when edge.sourceHandle is unset.
function defaultSourcePort(node: FlowNode<unknown>): FlowPoint
node — The source node.Returns: Centre-right anchor point in world coordinates.
defaultTargetPort(node)World-space coordinates of a node's default target port (left edge,
vertically centred). Used when edge.targetHandle is unset.
function defaultTargetPort(node: FlowNode<unknown>): FlowPoint
node — The target node.Returns: Centre-left anchor point in world coordinates.
FlowCanvas(props)Visual flow / DAG editor.
Renders nodes as absolutely-positioned <div> slots and edges as a
single <svg> overlay containing one <path> per edge. Built-in
interactions:
Style is driven entirely by getClassMap(). Inline styles are reserved
for geometry — node transform: translate(...), viewport pan/zoom,
SVG attributes — which classes can't express.
function FlowCanvas(props: FlowCanvasProps): JSX.Element
props — Component props.Returns: The flow canvas element.
moveNode(nodes, id, position)Move a single node to a new absolute position (immutably).
function moveNode(nodes: FlowNode<unknown>[], id: string, position: FlowPoint): FlowNode<unknown>[]
nodes — Current node list.id — Node id to move.position — New world-space top-left.Returns: New node array (same length, with the matching node updated).
removeEdge(edges, id)Remove a single edge by id (immutably).
function removeEdge(edges: FlowEdge[], id: string): FlowEdge[]
edges — Current edge list.id — Edge id to drop.Returns: Filtered edge array.
removeNode(nodes, edges, id)Remove a node and any edges connected to it.
function removeNode(
nodes: FlowNode<unknown>[],
edges: FlowEdge[],
id: string,
): { nodes: FlowNode[]; edges: FlowEdge[] }
nodes — Current node list.edges — Current edge list.id — Node id to remove.Returns: Pruned { nodes, edges }.
translateNode(nodes, id, dx, dy)Apply a delta (in world units) to a node's position.
function translateNode(
nodes: FlowNode<unknown>[],
id: string,
dx: number,
dy: number,
): FlowNode<unknown>[]
nodes — Current node list.id — Node id to translate.dx — Horizontal delta.dy — Vertical delta.Returns: New node array.
DEFAULT_NODE_HEIGHTDefault node height when node.height is unset.
const DEFAULT_NODE_HEIGHT: 80
DEFAULT_NODE_WIDTHDefault node width when node.width is unset.
const DEFAULT_NODE_WIDTH: 180
Peer dependencies:
@molecule/app-react ^1.0.1@molecule/app-ui ^1.0.1react ^18.0.0 || ^19.0.0@molecule/app-react@molecule/app-uireactThe canvas fills 100% of its parent — give the PARENT an explicit height (fixed-height panel, flex/grid track) or the canvas renders zero-tall and appears blank.
Passing onChange puts the canvas in controlled mode: built-in edits
(node drag, connect, Backspace/Delete) call onChange with the full
next { nodes, edges } and the caller re-renders with them. Without
onChange the canvas manages internal copies and nodes / edges
act as initial values only.
Translation strings are provided by @molecule/app-locales-flow-canvas.