Appearance
FlowCanvas — usage examples
FlowCanvas ships as six pre-compiled, per-framework packages from a single .rozie source — install only the one for your framework (no Rozie toolchain, no build-time compile step). Each carries its engine + framework peers as peer dependencies, so you control their versions. The snippets below are the same idiomatic consumption code shown in each package's README; switch the tab to your framework.
Usage
tsx
import { useState } from 'react';
import { FlowCanvas, NodeType, Port } from '@rozie-ui/rete-react';
export function Demo() {
const [graph, setGraph] = useState({
nodes: [
{ id: 'a', type: 'source', x: 0, y: 0, data: { label: 'Source' } },
{ id: 'b', type: 'merge', x: 280, y: 60, data: { label: 'Merge' } },
],
connections: [{ source: 'a', sourceOutput: 'num', target: 'b', targetInput: 'num' }],
});
const [zoom, setZoom] = useState(1);
return (
<div style={{ height: 400 }}>
<FlowCanvas
graph={graph}
onGraphChange={setGraph}
zoom={zoom}
onZoomChange={setZoom}
onConnectionCreated={(c) => console.log('connected', c)}
onNodeMoved={(e) => console.log('moved', e)}
>
<NodeType type="source" renderBody={({ node }) => <div>{node.data.label}</div>}>
<Port output="num" type="number" />
</NodeType>
<NodeType type="merge" renderBody={({ node }) => <div>{node.data.label}</div>}>
<Port input="num" type="number" multiple />
</NodeType>
</FlowCanvas>
</div>
);
}vue
<script setup lang="ts">
import { ref } from 'vue';
import FlowCanvas, { NodeType, Port } from '@rozie-ui/rete-vue';
const graph = ref({
nodes: [
{ id: 'a', type: 'source', x: 0, y: 0, data: { label: 'Source' } },
{ id: 'b', type: 'merge', x: 280, y: 60, data: { label: 'Merge' } },
],
connections: [{ source: 'a', sourceOutput: 'num', target: 'b', targetInput: 'num' }],
});
const zoom = ref(1);
</script>
<template>
<div style="height: 400px">
<FlowCanvas
v-model:graph="graph"
v-model:zoom="zoom"
@connection-created="(c) => console.log('connected', c)"
@node-moved="(e) => console.log('moved', e)"
>
<NodeType type="source">
<template #body="{ node }">{{ node.data.label }}</template>
<Port output="num" type="number" />
</NodeType>
<NodeType type="merge">
<template #body="{ node }">{{ node.data.label }}</template>
<Port input="num" type="number" multiple />
</NodeType>
</FlowCanvas>
</div>
</template>svelte
<script lang="ts">
import FlowCanvas, { NodeType, Port } from '@rozie-ui/rete-svelte';
let graph = $state({
nodes: [
{ id: 'a', type: 'source', x: 0, y: 0, data: { label: 'Source' } },
{ id: 'b', type: 'merge', x: 280, y: 60, data: { label: 'Merge' } },
],
connections: [{ source: 'a', sourceOutput: 'num', target: 'b', targetInput: 'num' }],
});
let zoom = $state(1);
</script>
<div style="height: 400px">
<FlowCanvas
bind:graph
bind:zoom
onconnectioncreated={(c) => console.log('connected', c)}
onnodemoved={(e) => console.log('moved', e)}
>
<NodeType type="source">
{#snippet body({ node })}<div>{node.data.label}</div>{/snippet}
<Port output="num" type="number" />
</NodeType>
<NodeType type="merge">
{#snippet body({ node })}<div>{node.data.label}</div>{/snippet}
<Port input="num" type="number" multiple />
</NodeType>
</FlowCanvas>
</div>ts
import { Component } from '@angular/core';
import { FlowCanvas, NodeType, Port } from '@rozie-ui/rete-angular';
@Component({
selector: 'app-demo',
standalone: true,
imports: [FlowCanvas, NodeType, Port],
template: `
<div style="height: 400px">
<rozie-flow-canvas
[(graph)]="graph"
[(zoom)]="zoom"
(connection-created)="onConnect($event)"
(node-moved)="onMoved($event)"
>
<rozie-node-type type="source">
<ng-template #body let-node="node">{{ node.data.label }}</ng-template>
<rozie-port output="num" type="number" />
</rozie-node-type>
<rozie-node-type type="merge">
<ng-template #body let-node="node">{{ node.data.label }}</ng-template>
<rozie-port input="num" type="number" multiple />
</rozie-node-type>
</rozie-flow-canvas>
</div>
`,
})
export class DemoComponent {
graph = {
nodes: [
{ id: 'a', type: 'source', x: 0, y: 0, data: { label: 'Source' } },
{ id: 'b', type: 'merge', x: 280, y: 60, data: { label: 'Merge' } },
],
connections: [{ source: 'a', sourceOutput: 'num', target: 'b', targetInput: 'num' }],
};
zoom = 1;
onConnect(c: any) { console.log('connected', c); }
onMoved(e: any) { console.log('moved', e); }
}tsx
import { createSignal } from 'solid-js';
import { FlowCanvas, NodeType, Port } from '@rozie-ui/rete-solid';
export function Demo() {
const [graph, setGraph] = createSignal({
nodes: [
{ id: 'a', type: 'source', x: 0, y: 0, data: { label: 'Source' } },
{ id: 'b', type: 'merge', x: 280, y: 60, data: { label: 'Merge' } },
],
connections: [{ source: 'a', sourceOutput: 'num', target: 'b', targetInput: 'num' }],
});
const [zoom, setZoom] = createSignal(1);
return (
<div style={{ height: '400px' }}>
<FlowCanvas
graph={graph()}
onGraphChange={setGraph}
zoom={zoom()}
onZoomChange={setZoom}
onConnectionCreated={(c) => console.log('connected', c)}
onNodeMoved={(e) => console.log('moved', e)}
>
{/* the #body scope arrives as an ACCESSOR on Solid — call it, don't destructure */}
<NodeType type="source" bodySlot={(ctx) => <div>{ctx().node.data.label}</div>}>
<Port output="num" type="number" />
</NodeType>
<NodeType type="merge" bodySlot={(ctx) => <div>{ctx().node.data.label}</div>}>
<Port input="num" type="number" multiple />
</NodeType>
</FlowCanvas>
</div>
);
}html
<!-- Node TYPE templates are light-DOM children; each body is a `slot="body"` element. -->
<rozie-flow-canvas id="flow" style="height: 400px">
<rozie-node-type type="source">
<div slot="body">Source</div>
<rozie-port output="num" type="number"></rozie-port>
</rozie-node-type>
<rozie-node-type type="merge">
<div slot="body">Merge</div>
<rozie-port input="num" type="number" multiple></rozie-port>
</rozie-node-type>
</rozie-flow-canvas>
<script type="module">
import '@rozie-ui/rete-lit';
// The custom elements own their own state — set `graph` as a PROPERTY and
// write it back from `graph-change` to keep the model two-way.
const el = document.querySelector('#flow');
el.graph = {
nodes: [
{ id: 'a', type: 'source', x: 0, y: 0, data: { label: 'Source' } },
{ id: 'b', type: 'merge', x: 280, y: 60, data: { label: 'Merge' } },
],
connections: [{ source: 'a', sourceOutput: 'num', target: 'b', targetInput: 'num' }],
};
el.zoom = 1;
el.addEventListener('graph-change', (e) => { el.graph = e.detail; });
el.addEventListener('zoom-change', (e) => { el.zoom = e.detail; });
el.addEventListener('connection-created', (e) => console.log('connected', e.detail));
</script>Imperative handle
Beyond props and events, FlowCanvas exposes imperative methods (declared once in the .rozie source via $expose). Grab a handle through your framework's native ref mechanism and call them directly:
tsx
import { useRef } from 'react';
import { FlowCanvas, type FlowCanvasHandle } from '@rozie-ui/rete-react';
const flow = useRef<FlowCanvasHandle>(null);
// <FlowCanvas ref={flow} ... />
// A node spec is { id, type, x, y, data? } — ports come from the TYPE's <Port>
// schema, and the label from data.label.
flow.current?.addNode({ id: 'c', type: 'merge', x: 100, y: 200, data: { label: 'New' } });
flow.current?.zoomToFit();
const editor = flow.current?.getEditor();vue
<script setup>
import { ref } from 'vue';
const flow = ref(); // template ref
</script>
<template>
<FlowCanvas ref="flow" />
<button @click="flow.zoomToFit()">Fit</button>
</template>svelte
<script>
let flow; // component instance via bind:this
</script>
<FlowCanvas bind:this={flow} />
<button onclick={() => flow.zoomToFit()}>Fit</button>ts
@Component({ /* ... */ })
export class DemoComponent {
@ViewChild(FlowCanvas) flow!: FlowCanvas; // or the viewChild() signal
fit() { this.flow.zoomToFit(); }
editor() { return this.flow.getEditor(); }
}tsx
import { FlowCanvas, type FlowCanvasHandle } from '@rozie-ui/rete-solid';
let handle: FlowCanvasHandle | undefined;
// The ref callback receives the HANDLE object (not the DOM node).
<FlowCanvas ref={(h) => (handle = h)} />;
handle?.zoomToFit();
const editor = handle?.getEditor();ts
// The custom element IS the handle — its exposed methods are public
// element methods.
const el = document.querySelector('rozie-flow-canvas');
el.zoomToFit();
const editor = el.getEditor();See also
- FlowCanvas — showcase & API — the full prop / event / slot / handle reference, and theming.
- FlowCanvas theming — the complete customizable-token table.
- FlowCanvas comparison — how it stacks up against the per-framework libraries.
- FlowCanvas — live demo — the real package running in the page, plus the one
.roziesource and all six generated outputs.