@molecule/app-flow-canvas-react
Feature · flow-canvas · App (browser) · v1.0.3 · 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-reactHow it works
@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
Reference
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,FlowNodeRendererstypes.- Pure geometry helpers (
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.
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.
Quick Start
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>,
}}
/>
)
}
Type
feature
Installation
npm install @molecule/app-flow-canvas-react @molecule/app-react @molecule/app-ui react
npm install -D @types/react
API
Interfaces
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
}
FlowPoint
A 2-D point in canvas (world) coordinates.
interface FlowPoint {
/** X coordinate. */
x: number
/** Y coordinate. */
y: number
}
FlowNode
Single 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
}
FlowEdge
Edge 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
}
FlowSelection
Selection state held internally by <FlowCanvas> and surfaced through
the onSelectionChange callback.
interface FlowSelection {
/** Selected node ids. */
nodeIds: string[]
/** Selected edge ids. */
edgeIds: string[]
}
FlowChange
Change 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[]
}
FlowViewport
Viewport (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
}
Types
FlowNodeRenderer
Renderer for a single node type.
type FlowNodeRenderer<T = unknown> = (node: FlowNode<T>) => ReactNode
FlowNodeRenderers
Map of node type → renderer.
type FlowNodeRenderers = Record<string, FlowNodeRenderer>
Functions
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:
- Drag a node — press on a node body and drag to reposition.
- Pan the canvas — press on empty space and drag.
- Zoom — mouse-wheel scroll (anchored on the cursor).
- Connect ports — press on a source handle (right edge of a node) and drag onto another node to create an edge.
- Select — click a node or edge; Backspace / Delete removes everything in the selection.
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.
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.
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.
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).
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.
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 }.
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.
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.
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'sgetBoundingClientRect().viewport— Current pan + zoom.
Returns: World-space point.
Constants
DEFAULT_NODE_WIDTH
Default node width when node.width is unset.
const DEFAULT_NODE_WIDTH: 180
DEFAULT_NODE_HEIGHT
Default node height when node.height is unset.
const DEFAULT_NODE_HEIGHT: 80
Injection Notes
Requirements
Peer dependencies:
@molecule/app-react^1.0.1@molecule/app-ui^1.0.1react^18.0.0 || ^19.0.0
Runtime Dependencies
@molecule/app-react@molecule/app-uireact
The 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.
Translations
Translation strings are provided by @molecule/app-locales-flow-canvas.