CanvasRenderer
Headless canvas renderer
CanvasRenderer
A headless component for rendering Canvases collection canvas data. Built on @xyflow/react, with fully customizable node renderers.
CanvasRenderer is imported from: @01.software/sdk/ui/canvas.
Installation
@xyflow/react is required as a peer dependency.
npm install @xyflow/reactYou must import the CSS:
import '@xyflow/react/dist/style.css'Basic Usage
import { CanvasRenderer } from '@01.software/sdk/ui/canvas'
import '@xyflow/react/dist/style.css'
function CanvasViewer({ canvas }) {
return (
<div style={{ width: '100%', height: 600 }}>
<CanvasRenderer data={canvas.canvas} />
</div>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | CanvasData | — | Canvas document's canvas field (required). Returns null if null/undefined |
className | string | — | Container CSS class |
style | CSSProperties | — | Container inline style |
nodeRenderers | Record<string, ComponentType<DynamicNodeSlotProps>> | — | Custom renderers by node type slug |
nodeTypeDefs | NodeTypeDef[] | — | Node type definitions (enables color badges, field-type-aware rendering) |
edgeTypeDefs | EdgeTypeDef[] | — | Edge type definitions for styled edges (color, stroke, markers, animation) |
background | boolean | false | Show background pattern |
interactive | boolean | false | Enable pan/zoom (default: read-only) |
fitView | boolean | true | Fit view on mount |
onNodeClick | (event, node: CanvasNode) => void | — | Node click handler |
onNodeDoubleClick | (event, node: CanvasNode) => void | — | Node double-click handler |
onNodeContextMenu | (event, node: CanvasNode) => void | — | Node right-click handler |
onNodeMouseEnter | (event, node: CanvasNode) => void | — | Node mouse enter handler |
onNodeMouseLeave | (event, node: CanvasNode) => void | — | Node mouse leave handler |
onEdgeClick | (event, edge: CanvasEdge) => void | — | Edge click handler |
frameRenderer | ComponentType<FrameNodeSlotProps> | — | Custom frame node renderer |
nodeWrapper | ComponentType<NodeWrapperSlotProps> | — | Wraps every dynamic node's rendered content |
edgeRenderers | Record<string, ComponentType<EdgeSlotProps>> | — | Custom edge renderers by edge type slug |
controls | boolean | false | Show zoom/fit controls |
minimap | boolean | false | Show minimap |
minimapNodeColor | (node: CanvasNode) => string | — | Custom minimap node color function |
children | ReactNode | — | Additional children rendered inside ReactFlow |
renderNode | (props: DynamicNodeSlotProps, content: ReactElement) => ReactElement | null | — | Global override for all dynamic nodes. content is the resolved rendering, props.defaultRender has original template/field rendering |
onViewportChange | (viewport: CanvasViewport) => void | — | Called when viewport changes (pan/zoom) |
defaultViewport | CanvasViewport | — | Default viewport (used when fitView is false) |
bounds | CanvasBounds | — | Focus on specific bounds |
clampBounds | CanvasBounds | — | Separate panning restriction bounds (no padding). Auto-enforces minZoom at cover zoom. Overrides bounds-based translateExtent |
focusPadding | number | 0.1 | Padding for focus bounds |
focusAnimation | boolean | number | true | Animate focus transition (true=300ms, number=custom ms, false=instant) |
focusMode | 'contain' | 'cover' | 'contain' | contain fits entire bounds, cover fills viewport (may crop) |
responsiveFit | boolean | true | Re-fit viewport on container resize (when bounds is set) |
translateExtent | [[number, number], [number, number]] | — | Override translateExtent directly for custom pan limits |
minZoom | number | — | Minimum zoom level. When clampBounds is set, auto-enforced as cover zoom |
maxZoom | number | — | Maximum zoom level |
Stable references required: nodeRenderers, frameRenderer, nodeWrapper, renderNode,
and edgeRenderers must be stable — defined outside the component or wrapped with useCallback/useMemo.
Unstable references recreate nodeTypes/edgeTypes on every render, causing React Flow to remount all nodes.
Node Types
CanvasRenderer supports two node types:
| Type | Description |
|---|---|
dynamic | Default renderer for all regular nodes. Distinguished by nodeTypeSlug |
frame | Container/grouping node (dashed border, background color) |
Built-in Node Types
Default node type definitions provided via BUILT_IN_NODE_TYPES:
| slug | Fields |
|---|---|
text | body (textarea) |
image | image (image), alt (text), caption (text) |
Built-in Edge Types
Default edge type definition provided via BUILT_IN_EDGE_TYPES:
| slug | Properties |
|---|---|
default | strokeWidth: 2, animated: false, lineStyle: default |
useCanvas Hook
Fetches a Canvas document, node type definitions, and edge type definitions in one call. Queries by slug or UUID.
import { useCanvas, CanvasRenderer } from '@01.software/sdk/ui/canvas'
function CanvasPage({ slug }) {
const { data, nodeTypeDefs, edgeTypeDefs, isLoading, error } = useCanvas({
client,
slug,
enabled: true,
})
if (isLoading) return <div>Loading...</div>
if (!data) return null
return (
<div style={{ width: '100%', height: 600 }}>
<CanvasRenderer data={data} nodeTypeDefs={nodeTypeDefs} />
</div>
)
}| Option | Type | Description |
|---|---|---|
client | Client | ServerClient | SDK client (required) |
slug | string | Canvas slug (query by slug) |
id | string | Canvas ID (query by UUID) |
enabled | boolean | Enable query (default: true) |
| Return | Type | Description |
|---|---|---|
data | CanvasData | undefined | Canvas data |
nodeTypeDefs | NodeTypeDef[] | Built-in + API node type definitions (merged) |
edgeTypeDefs | EdgeTypeDef[] | Built-in + API edge type definitions (merged) |
canvas | Record<string, unknown> | undefined | Full canvas document |
isLoading | boolean | Loading state |
error | Error | null | Error |
Custom Node Rendering
Use nodeRenderers to provide custom renderers by node type slug:
import {
CanvasRenderer,
type DynamicNodeSlotProps,
} from '@01.software/sdk/ui/canvas'
function ProductCard({ id, label, fields, nodeTypeDef }: DynamicNodeSlotProps) {
return (
<div className="product-card">
<h3>{label}</h3>
{fields.image && <img src={String(fields.image)} alt={label} />}
<p>{String(fields.price)}</p>
</div>
)
}
<CanvasRenderer
data={canvasData}
nodeTypeDefs={nodeTypeDefs}
nodeRenderers={{
'product-card': ProductCard,
}}
/>DynamicNodeSlotProps
| Prop | Type | Description |
|---|---|---|
id | string | Node ID |
nodeTypeSlug | string | Node type slug |
label | string | Node label |
fields | Record<string, unknown> | Node field data |
nodeTypeDef | NodeTypeDef | undefined | Node type definition (if available) |
selected | boolean | undefined | Whether this node is currently selected |
width | number | undefined | Measured node width (undefined before first measurement) |
height | number | undefined | Measured node height (undefined before first measurement) |
defaultRender | ReactElement | undefined | Default rendering (template or field-based). Allows wrapping/extending |
Rendering Priority
CanvasRenderer resolves dynamic node renderers in this order:
nodeRenderers[slug]— Custom renderer (highest priority)NodeTypeDef.template— Auto-compiled JSX template- Default field rendering — Type-aware display (image, text, etc.)
If you provide a nodeRenderer for a slug that also has a template, your
renderer completely replaces the built-in template handling — including
size calculation and CSS injection. Only use nodeRenderers when you need
full control over rendering.
Template Rendering
When NodeTypeDef.template is set, CanvasRenderer compiles JSX at runtime using sucrase and renders the component automatically. NodeTypeDef.customCSS is injected as a global <style> tag.
CodeComponentProps
Template components receive these props:
| Prop | Type | Description |
|---|---|---|
fields | Record<string, unknown> | Node field data |
label | string | Node label |
color | string | Node type color |
nodeTypeSlug | string | Node type slug |
width | number | Node width (measured or defaultSize) |
height | number | Node height (measured or defaultSize) |
Security
Templates are sandboxed: document, window, fetch, eval, import, localStorage, setTimeout, and other browser globals are blocked. Templates that contain these patterns will not compile.
Advanced Usage
import { compileTemplate, clearTemplateCache } from '@01.software/sdk/ui/canvas'
// Manually compile a template
const Component = compileTemplate(templateCode, 'my-slug')
// Clear all cached compiled templates
clearTemplateCache()Node Wrapper
Use nodeWrapper to wrap every dynamic node's rendered content. This is useful for adding card styling, selection indicators, or React Flow Handle components for edge connections.
CanvasRenderer does not include Handle components by default. If you need
visible edges connecting to nodes, use nodeWrapper to add them.
import { Handle, Position } from '@xyflow/react'
import {
CanvasRenderer,
type NodeWrapperSlotProps,
} from '@01.software/sdk/ui/canvas'
function NodeCard({ label, children }: NodeWrapperSlotProps) {
return (
<div className="rounded-lg border bg-white p-3 shadow-sm">
<Handle type="target" position={Position.Top} />
<div className="text-sm font-semibold">{label}</div>
{children}
<Handle type="source" position={Position.Bottom} />
</div>
)
}
<CanvasRenderer data={canvas} nodeWrapper={NodeCard} />NodeWrapperSlotProps
| Prop | Type | Description |
|---|---|---|
id | string | Node ID |
nodeTypeSlug | string | Node type slug |
label | string | Node label |
selected | boolean | undefined | Whether node is selected |
nodeTypeDef | NodeTypeDef | undefined | Node type definition |
children | ReactNode | Default rendered content |
Frame Renderer
Use frameRenderer to provide a custom frame node renderer. Frame nodes are container/grouping nodes that visually wrap other nodes.
import { CanvasRenderer, type FrameNodeSlotProps } from '@01.software/sdk/ui/canvas'
function CustomFrame({
id,
label,
color,
padding,
borderStyle,
opacity,
}: FrameNodeSlotProps) {
return (
<div style={{ backgroundColor: color, padding, opacity, borderStyle }}>
<span>{label}</span>
</div>
)
}
<CanvasRenderer data={canvas} frameRenderer={CustomFrame} />FrameNodeSlotProps
| Prop | Type | Description |
|---|---|---|
id | string | Node ID |
label | string | Frame label |
color | string | undefined | Background color |
padding | number | undefined | Inner padding |
borderStyle | 'dashed' | 'solid' | 'none' | undefined | Border style |
opacity | number | undefined | Background opacity |
width | number | undefined | Frame width |
height | number | undefined | Frame height |
children | ReactNode | undefined | Children (if any) |
Frame Navigation
Use getFrameData to extract a frame's content and render it as an isolated view. Returns filtered nodes, edges, and dual bounds for viewport control.
import { useCanvas, CanvasRenderer, getFrameData } from '@01.software/sdk/ui/canvas'
import '@xyflow/react/dist/style.css'
const FRAME_ID = '54ed896d-...'
function FrameViewer({ slug }) {
const { data, nodeTypeDefs, edgeTypeDefs } = useCanvas({ client, slug })
const frameData = useMemo(
() => (data ? getFrameData(data, FRAME_ID) : undefined),
[data],
)
if (!frameData) return null
return (
<div style={{ width: '100%', height: 600 }}>
<CanvasRenderer
data={frameData.data}
nodeTypeDefs={nodeTypeDefs}
edgeTypeDefs={edgeTypeDefs}
bounds={frameData.fitBounds}
clampBounds={frameData.clampBounds}
interactive
frameRenderer={() => null}
/>
</div>
)
}FrameData
| Field | Type | Description |
|---|---|---|
data | CanvasData | Filtered canvas (frame + descendants, draggable: false) |
fitBounds | CanvasBounds | Child content bounding box — use with bounds for viewport centering |
clampBounds | CanvasBounds | Frame area bounding box — use with clampBounds for panning restriction |
Use frameRenderer={() => null} to hide the frame node visually while keeping
its children visible. The frame node is included in the data to preserve
parent-child positioning.
Related Utilities
| Function | Description |
|---|---|
getFrameData(data, frameId) | Extract frame descendants with dual bounds |
getFrames(nodes) | Get all frame nodes with bounds (sorted top-left to bottom-right) |
getNodeBounds(nodes, nodeIds) | Calculate bounding box for given node IDs |
Edge Renderers
Use edgeRenderers to provide custom edge rendering by edge type slug. Each renderer receives the edge data and its type definition.
import { CanvasRenderer, type EdgeSlotProps } from '@01.software/sdk/ui/canvas'
function CustomEdge({ id, source, target, edgeTypeDef, style }: EdgeSlotProps) {
return (
<g>
<line
x1={0}
y1={0}
x2={100}
y2={100}
stroke={edgeTypeDef?.color ?? '#888'}
strokeWidth={edgeTypeDef?.strokeWidth ?? 2}
/>
</g>
)
}
<CanvasRenderer
data={canvas}
edgeTypeDefs={edgeTypeDefs}
edgeRenderers={{ 'custom-edge': CustomEdge }}
/>EdgeSlotProps
| Prop | Type | Description |
|---|---|---|
id | string | Edge ID |
edgeTypeSlug | string | undefined | Edge type slug |
source | string | Source node ID |
target | string | Target node ID |
label | string | undefined | Edge label |
fields | Record<string, unknown> | undefined | Edge field data |
edgeTypeDef | EdgeTypeDef | undefined | Edge type definition |
style | CSSProperties | undefined | Computed edge style |
useCanvasData Hook
A pure data transformation hook for headless or SSR usage. Extracts nodes, edges, and type definition maps from canvas data without rendering anything.
import { useCanvasData } from '@01.software/sdk/ui/canvas'
function CanvasAnalytics({ canvas, nodeTypeDefs, edgeTypeDefs }) {
const { nodes, edges, nodeTypeDefsMap, edgeTypeDefsMap } = useCanvasData({
data: canvas,
nodeTypeDefs,
edgeTypeDefs,
})
return (
<div>
<p>Nodes: {nodes.length}</p>
<p>Edges: {edges.length}</p>
<p>Node types: {nodeTypeDefsMap.size}</p>
</div>
)
}| Option | Type | Description |
|---|---|---|
data | CanvasData | undefined | Canvas data from Canvas document (required) |
nodeTypeDefs | NodeTypeDef[] | Node type definitions (defaults to built-in types) |
edgeTypeDefs | EdgeTypeDef[] | Edge type definitions (defaults to built-in types) |
| Return | Type | Description |
|---|---|---|
nodes | CanvasNode[] | Nodes from canvas data |
edges | Edge[] | Edges from canvas data (typed for @xyflow/react) |
nodeTypeDefsMap | Map<string, NodeTypeDef> | Map of node type slug to definition |
edgeTypeDefsMap | Map<string, EdgeTypeDef> | Map of edge type slug to definition |
NodeTypeDef
When nodeTypeDefs is provided, the default renderer is enhanced — field-type-aware rendering (image fields render as <img>, all other fields render as text) and template support.
interface NodeTypeDef {
slug: string
name: string
color: string
defaultSize: { width: number; height: number }
fields: NodeTypeFieldDef[]
transparentBackground?: boolean
template?: string | null
customCSS?: string | null
}
interface NodeTypeFieldDef {
name: string
label: string
fieldType:
| 'text'
| 'textarea'
| 'number'
| 'url'
| 'color'
| 'image'
| 'select'
| 'toggle'
options?: { label: string; value: string }[]
defaultValue?: string
required?: boolean
}EdgeTypeDef
Edge type definitions returned by useCanvas. Tenants define custom edge types with visual properties and custom fields.
interface EdgeTypeDef {
slug: string
name: string
color: string
strokeWidth: number
animated: boolean
lineStyle: string
markerStart: string
markerEnd: string
fields: NodeTypeFieldDef[]
}Exports
| Export | Description |
|---|---|
CanvasRenderer | Main component |
CanvasRendererProps | Props type |
useCanvas | Canvas document + node type fetch hook |
SDKClient | useCanvas client interface type |
UseCanvasOptions, UseCanvasResult | useCanvas types |
useCanvasData | Pure data transformation hook (headless/SSR) |
UseCanvasDataOptions, UseCanvasDataResult | useCanvasData types |
CanvasData | Canvas data ({ nodes, edges, viewport }) |
CanvasNode, CanvasEdge, CanvasViewport | React Flow entity types |
CanvasNodePosition, CanvasNodeData | Node position/data types |
DynamicNodeData, DynamicNodeSlotProps | Dynamic node data/slot props |
FrameNodeData, FrameNodeSlotProps | Frame node data/slot props |
EdgeSlotProps | Edge renderer slot props |
NodeWrapperSlotProps | Node wrapper slot props |
CanvasBounds | Viewport focus bounds type |
NodeTypeDef, NodeTypeFieldDef | Node type definitions |
EdgeTypeDef | Edge type definition |
isDynamicNode, isFrameNode | Type guards |
BUILT_IN_NODE_TYPES | Built-in node type definitions (text, image) |
BUILT_IN_EDGE_TYPES | Built-in edge type definitions (default) |
getNodeBounds, getFrames | Utility functions for viewport focus and frame navigation |
getFrameData | Extract frame descendants with dual bounds |
FrameData | getFrameData return type |
compileTemplate | Compile JSX template string to React component |
clearTemplateCache | Clear compiled template cache |
CodeComponentProps | Template component props type |