Flow
13 partsRoot of the headless flow canvas. Owns node/edge/viewport state (two-way via
v-model:nodes / v-model:edges / v-model:viewport, or uncontrolled via
defaultNodes / defaultEdges / defaultViewport), reconciles the public
arrays into internal shallowRef Maps for O(1) reads, and provides
FlowContext to every part. It renders the standard FlowPane → FlowViewport → (edges, nodes) subtree and exposes the default slot for absolutely-
positioned chrome (Background / Controls / MiniMap / Panel). Customise node
and edge rendering with nodeTypes / edgeTypes component maps or the
#node-<type> / #edge-<type> scoped slots. Emits granular @nodes-change /
@edges-change alongside v-model, so consumers may own their data.
Demo
Anatomy
Import the parts and compose them. Each part forwards attributes to its underlying element.
import {
FlowBackground,
FlowConnectionLine,
FlowControls,
FlowEdgeRenderer,
FlowHandle,
FlowMiniMap,
FlowNodeRenderer,
FlowNodeResizer,
FlowNodeToolbar,
FlowPane,
FlowPanel,
FlowRoot,
FlowViewport,
} from '@robonen/primitives/canvas/flow';
<FlowRoot>
<FlowBackground />
<FlowConnectionLine />
<FlowControls />
<FlowEdgeRenderer />
<FlowHandle />
<FlowMiniMap />
<FlowNodeRenderer />
<FlowNodeResizer />
<FlowNodeToolbar />
<FlowPane />
<FlowPanel />
<FlowViewport />
</FlowRoot>API Reference
FlowBackground
BackgroundA grid background drawn as an SVG `<pattern>` that pans and zooms with the viewport (the pattern origin shifts by `viewport % (gap·zoom)` and its cell scales by `zoom`). Sits behind the viewport layer, ignores pointer events, and is fully styleable via `[data-flow-background]` / `currentColor`.
| Prop | Type | Description |
|---|---|---|
variant? | FlowBackgroundVariant | Pattern style. |
gap? | number | [number, number] | Grid spacing in flow units (single value or [x, y]). |
size? | number | Dot radius / line thickness in px. |
color? | string | Pattern colour. |
FlowConnectionLine
ConnectionLineNo props or events — renders its element and forwards attributes.
FlowControls
ControlsZoom-in / zoom-out / fit-view button cluster, hosted in a `FlowPanel`. Headless — every button is unstyled with a `data-flow-control` hook and an overridable icon slot (`#zoom-in`, `#zoom-out`, `#fit-view`); add more buttons via the default slot. Drives the canvas through `useFlow`.
| Prop | Type | Description |
|---|---|---|
position? | FlowPanelPosition | Panel anchor. |
ariaLabel? | string | Accessible group label. |
FlowEdgeRenderer
EdgeRendererThe single shared `<svg>` for all edges, living inside the viewport transform so edge coordinates are plain flow-space numbers. `overflow:visible` lets paths draw outside the nominal box; `pointer-events:none` here, re-enabled per edge on the fat interaction path. Markers are deduped into one `<defs>`. Rendered under the nodes (earlier in DOM) so nodes paint on top.
| Prop | Type | Description |
|---|---|---|
edgeTypes? | Record<string, Component> | — |
FlowHandle
HandleA connection anchor placed inside a custom node. Registers itself with the node sub-context (triggering a re-measure of handle geometry), positions itself on the given side by default, and starts a connection on pointerdown. The visual (size/colour) is the consumer's via `[data-flow-handle]`; only the side positioning is applied inline. `data-handleid` / `data-handletype` / `data-handlepos` drive measurement and styling hooks.
| Prop | Type | Description |
|---|---|---|
type | HandleType | Whether this handle starts (source) or ends (target) connections. |
position | Position | Side of the node the handle sits on. |
id? | string | null | Handle id; required when a node has multiple handles of one type. |
isConnectable? | boolean | Per-handle connect enable (defaults to the node's connectable). |
isValidConnection? | IsValidConnection | Per-handle connection validator (composed with the global one). |
FlowMiniMap
MiniMapA scaled overview of the graph with a viewport indicator. Auto-frames all nodes plus the current viewport, draws a `<rect>` per node, and (when `pannable`) recenters the viewport on click. Node rects expose `data-id` / `data-selected` for styling; size and colour are the consumer's.
| Prop | Type | Description |
|---|---|---|
position? | FlowPanelPosition | Panel anchor. |
width? | number | Map width in px. |
height? | number | Map height in px. |
pannable? | boolean | Click the map to recenter the viewport. |
ariaLabel? | string | Accessible label. |
FlowNodeRenderer
NodeRendererIterates the visible node ids and renders one `FlowNode` per id, keyed by id so virtualization re-inclusion patches in place. Forwards the `#node-<type>` slots and the `nodeTypes` component map down to each node. Renderless (returns a fragment of nodes directly into the viewport — no wrapper element).
| Prop | Type | Description |
|---|---|---|
nodeTypes? | Record<string, Component> | — |
FlowNodeResizer
NodeResizerIn-node resize handles (8 control points). Placed inside a custom node, it resizes that node by writing explicit `width`/`height` (and `position` for top/left edges, so the opposite edge stays fixed). Deltas are converted to flow space (`/zoom`), clamped to min/max, and committed through `updateNode`. Handles are unstyled — target `[data-flow-resize-handle][data-position]`.
| Prop | Type | Description |
|---|---|---|
minWidth? | number | Minimum width in flow units. |
maxWidth? | number | Maximum width in flow units. |
minHeight? | number | Minimum height in flow units. |
maxHeight? | number | Maximum height in flow units. |
keepAspectRatio? | boolean | Keep the node's aspect ratio while resizing. |
FlowNodeToolbar
NodeToolbarA contextual toolbar anchored to its node, teleported out of the transformed layer so it renders at a constant 1:1 scale regardless of zoom. Position is recomputed from the node's absolute rect via `flowToScreen` (fixed positioning). Visible when the node is selected by default; override with `isVisible`. Placed inside a custom node component. Implemented as a render function: a `<Teleport>` toggled by `v-if` at an SFC template root does not reliably re-subscribe to its visibility source, so the conditional teleport is expressed directly here.
| Prop | Type | Description |
|---|---|---|
isVisible? | boolean | Force visibility; defaults to "visible while the node is selected". |
position? | Position | Side of the node to anchor to. |
offset? | number | Gap from the node edge in px. |
to? | string | Teleport target. |
FlowPane
PaneThe interaction surface. Clips the canvas (`overflow:hidden`), disables native touch gestures (`touch-action:none`), reports its bounding rect into the context as the screen origin for coordinate math, and hosts the wheel/drag pan-zoom, marquee-selection, connection and keyboard layers. Background click clears the selection. Rendered by `FlowRoot`; not usually placed directly.
| Prop | Type | Description |
|---|---|---|
panOnDrag? | boolean | Drag the empty pane to pan. |
panOnScroll? | boolean | Wheel scroll pans instead of zooms. |
zoomOnScroll? | boolean | Wheel scroll zooms toward the pointer. |
zoomOnPinch? | boolean | Trackpad pinch zooms. |
zoomOnDoubleClick? | boolean | Double-click zooms in. |
FlowPanel
PanelAn absolutely-positioned overlay anchored to a corner/edge of the pane, for chrome like controls, legends, or toolbars. Stops pointer/wheel events from reaching the pane, so interacting with the panel never pans or zooms. Place inside `FlowRoot`'s default slot.
| Prop | Type | Description |
|---|---|---|
position? | FlowPanelPosition | Anchor position within the pane. |
FlowRoot
RootRoot of the headless flow canvas. Owns node/edge/viewport state (two-way via `v-model:nodes` / `v-model:edges` / `v-model:viewport`, or uncontrolled via `defaultNodes` / `defaultEdges` / `defaultViewport`), reconciles the public arrays into internal `shallowRef` Maps for O(1) reads, and provides `FlowContext` to every part. It renders the standard `FlowPane → FlowViewport → (edges, nodes)` subtree and exposes the default slot for absolutely- positioned chrome (Background / Controls / MiniMap / Panel). Customise node and edge rendering with `nodeTypes` / `edgeTypes` component maps or the `#node-<type>` / `#edge-<type>` scoped slots. Emits granular `@nodes-change` / `@edges-change` alongside `v-model`, so consumers may own their data.
| Prop | Type | Description |
|---|---|---|
defaultNodes? | FlowNode[] | Uncontrolled initial nodes (ignored when v-model:nodes is bound). |
defaultEdges? | FlowEdge[] | Uncontrolled initial edges. |
defaultViewport? | Viewport | Uncontrolled initial viewport. |
minZoom? | number | Minimum zoom level. |
maxZoom? | number | Maximum zoom level. |
nodesDraggable? | boolean | Global drag enable (per-node draggable overrides). |
nodesConnectable? | boolean | Global connect enable (per-node connectable overrides). |
elementsSelectable? | boolean | Global selection enable. |
snapToGrid? | boolean | Snap dragged nodes to a grid. |
snapGrid? | [number, number] | Grid spacing [x, y] for snapToGrid. |
connectionMode? | ConnectionMode | Connection validity model. |
connectionRadius? | number | Pixel radius for snapping a connection to a nearby handle. |
selectionMode? | SelectionMode | Marquee inclusion rule. |
elevateNodesOnSelect? | boolean | Raise z-index of selected nodes. |
nodeTypes? | Record<string, Component> | Component map keyed by node.type. Define module-level, never inline. |
edgeTypes? | Record<string, Component> | Component map keyed by edge.type. |
defaultEdgeType? | string | Default edge type when an edge has none. |
interactive? | boolean | Master interactivity switch (lock). |
disableKeyboardA11y? | boolean | Disable the keyboard a11y layer + role=application. |
isValidConnection? | IsValidConnection | Global connection validator, overridable per handle. |
onlyRenderVisibleElements? | boolean | Cull nodes/edges outside the viewport — for large graphs. |
virtualizationBuffer? | number | Extra px kept rendered around the viewport when virtualizing. |
nodes? | FlowNode[] | Two-way bound value (v-model:nodes). |
edges? | FlowEdge[] | Two-way bound value (v-model:edges). |
viewport? | Viewport | Two-way bound value (v-model:viewport). |
| Event | Payload |
|---|---|
update:nodes | [value: FlowNode[]] |
update:edges | [value: FlowEdge[]] |
update:viewport | [value: Viewport] |
FlowViewport
ViewportThe single transformed layer. Every node and the edge `<svg>` live inside it, so pan/zoom is one GPU-composited `transform` rather than a per-element restyle. `transform-origin:0 0` is required — the coordinate formulas assume top-left scaling. `will-change:transform` is toggled on ONLY while interacting (never permanently): a pinned hint locks the compositor's raster scale, so the cached texture is GPU-upscaled and the graph blurs at high zoom — toggling it lets the layer re-rasterise crisply once motion settles (see `useInteractionState`). Mirrors `FlowNode`'s per-node drag toggle.
No props or events — renders its element and forwards attributes.