Flow

13 parts

Root 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

Loading demo…

Anatomy

Import the parts and compose them. Each part forwards attributes to its underlying element.

vue
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

Background

A 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`.

PropTypeDescription
variant?FlowBackgroundVariantPattern style.
gap?number | [number, number]Grid spacing in flow units (single value or [x, y]).
size?numberDot radius / line thickness in px.
color?stringPattern colour.

FlowConnectionLine

ConnectionLine

No props or events — renders its element and forwards attributes.

FlowControls

Controls

Zoom-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`.

PropTypeDescription
position?FlowPanelPositionPanel anchor.
ariaLabel?stringAccessible group label.

FlowEdgeRenderer

EdgeRenderer

The 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.

PropTypeDescription
edgeTypes?Record<string, Component>

FlowHandle

Handle

A 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.

PropTypeDescription
typeHandleTypeWhether this handle starts (source) or ends (target) connections.
positionPositionSide of the node the handle sits on.
id?string | nullHandle id; required when a node has multiple handles of one type.
isConnectable?booleanPer-handle connect enable (defaults to the node's connectable).
isValidConnection?IsValidConnectionPer-handle connection validator (composed with the global one).

FlowMiniMap

MiniMap

A 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.

PropTypeDescription
position?FlowPanelPositionPanel anchor.
width?numberMap width in px.
height?numberMap height in px.
pannable?booleanClick the map to recenter the viewport.
ariaLabel?stringAccessible label.

FlowNodeRenderer

NodeRenderer

Iterates 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).

PropTypeDescription
nodeTypes?Record<string, Component>

FlowNodeResizer

NodeResizer

In-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]`.

PropTypeDescription
minWidth?numberMinimum width in flow units.
maxWidth?numberMaximum width in flow units.
minHeight?numberMinimum height in flow units.
maxHeight?numberMaximum height in flow units.
keepAspectRatio?booleanKeep the node's aspect ratio while resizing.

FlowNodeToolbar

NodeToolbar

A 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.

PropTypeDescription
isVisible?booleanForce visibility; defaults to "visible while the node is selected".
position?PositionSide of the node to anchor to.
offset?numberGap from the node edge in px.
to?stringTeleport target.

FlowPane

Pane

The 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.

PropTypeDescription
panOnDrag?booleanDrag the empty pane to pan.
panOnScroll?booleanWheel scroll pans instead of zooms.
zoomOnScroll?booleanWheel scroll zooms toward the pointer.
zoomOnPinch?booleanTrackpad pinch zooms.
zoomOnDoubleClick?booleanDouble-click zooms in.

FlowPanel

Panel

An 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.

PropTypeDescription
position?FlowPanelPositionAnchor position within the pane.

FlowRoot

Root

Root 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.

PropTypeDescription
defaultNodes?FlowNode[]Uncontrolled initial nodes (ignored when v-model:nodes is bound).
defaultEdges?FlowEdge[]Uncontrolled initial edges.
defaultViewport?ViewportUncontrolled initial viewport.
minZoom?numberMinimum zoom level.
maxZoom?numberMaximum zoom level.
nodesDraggable?booleanGlobal drag enable (per-node draggable overrides).
nodesConnectable?booleanGlobal connect enable (per-node connectable overrides).
elementsSelectable?booleanGlobal selection enable.
snapToGrid?booleanSnap dragged nodes to a grid.
snapGrid?[number, number]Grid spacing [x, y] for snapToGrid.
connectionMode?ConnectionModeConnection validity model.
connectionRadius?numberPixel radius for snapping a connection to a nearby handle.
selectionMode?SelectionModeMarquee inclusion rule.
elevateNodesOnSelect?booleanRaise 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?stringDefault edge type when an edge has none.
interactive?booleanMaster interactivity switch (lock).
disableKeyboardA11y?booleanDisable the keyboard a11y layer + role=application.
isValidConnection?IsValidConnectionGlobal connection validator, overridable per handle.
onlyRenderVisibleElements?booleanCull nodes/edges outside the viewport — for large graphs.
virtualizationBuffer?numberExtra 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?ViewportTwo-way bound value (v-model:viewport).
Emits
EventPayload
update:nodes[value: FlowNode[]]
update:edges[value: FlowEdge[]]
update:viewport[value: Viewport]

FlowViewport

Viewport

The 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.