Grafloria separates what happened from how the host framework updates. The rendered
DiagramInstance reports model,
selection, connection, pointer, and viewport changes. The interaction configuration determines which
gestures the user can perform.
Start with the instance
Use render when application code needs the live instance from
the first line. The instance connects the rendered canvas to the model and engine, so subscribe to it
for application events rather than subscribing to a detached model.
tsimport { render } from '@grafloria/element';
import type { DiagramInstance } from '@grafloria/renderer';
const host = document.getElementById('diagram');
if (!host) {
throw new Error('Missing #diagram');
}
host.style.height = '400px';
const nodes = [
{ id: 'start', position: { x: 40, y: 80 }, size: { width: 140, height: 70 }, label: 'Start' },
{ id: 'finish', position: { x: 280, y: 80 }, size: { width: 140, height: 70 }, label: 'Finish' },
];
const edges = [{ id: 'start-finish', source: 'start', target: 'finish' }];
const instance: DiagramInstance = render({ nodes, edges }, host, {
interaction: { portVisibility: 'always' },
});
const stopSelection = instance.on('selection:change', ({ nodes: selectedNodes, edges: selectedEdges }) => {
console.log('selection', selectedNodes.length, selectedEdges.length);
});
instance.on('node:click', ({ node, world }) => {
console.log(`clicked ${node.id} at ${world.x},${world.y}`);
});
instance.on('viewport:change', ({ viewport, zoom }) => {
console.log('camera', viewport, zoom);
});
// Call this when the host no longer needs the listener.
stopSelection();
Give the mounted host a height; otherwise the canvas has no drawing area:
html<div id="diagram" style="height: 400px"></div>
The sample renders two nodes and a link. Selecting a node reports the current selection, clicking a
node reports diagram-world coordinates, and panning or zooming reports the camera. Every on() call
returns an unsubscribe function; retain it for the lifetime of the host and call it during teardown.
Read the event map
The instance event map is the same across bindings. The payloads are typed by the event name and
contain live LinkModel objects where shown:
| Event | Payload | Use it for |
|---|---|---|
nodes:change | { nodes: NodeModel[] } | Persist added or removed nodes. |
edges:change | { edges: LinkModel[] } | Persist added or removed links. |
selection:change | { nodes: NodeModel[]; edges: LinkModel[] } | Update an inspector or toolbar. |
connect | { link: LinkModel } | React to a completed connection. |
reconnect | { link: LinkModel; endpoint: 'source' | 'target' } | React to an endpoint move. |
node:click | { node: NodeModel; world: { x: number; y: number } } | Open node actions at the diagram position. |
node:doubleclick | { node: NodeModel; world: { x: number; y: number } } | Start a node-specific action. |
edge:click | { edge: LinkModel; world: { x: number; y: number } } | Open link actions. |
viewport:change | { viewport: Rectangle; zoom: number } | Save or mirror the camera. |
ready | void | Run work after the instance is ready. |
NodeModel and LinkModel in these payloads are
the live model objects. Query the model for document data; use the engine for behavior.
Follow the same map in each framework
Framework bindings translate the same instance events into their native surface:
- Plain JavaScript uses
instance.on(...). - React uses callback props such as
onConnect,onNodeClick,onSelectionChange, andonNodesChange. - Vue uses kebab-case emits such as
@connect,@node-click, and@selection-change; usev-model:nodesfor node data. - Angular uses model writes and outputs such as
[(nodes)],(viewportChanged), and(layoutDone); click handling uses the engine event bus. - Qwik uses the binding's
$callback form and supplies the same instance event payloads.
The element disposes its diagram when it disconnects; a render() instance is your responsibility to
dispose when its host is torn down.
Trace a connection gesture
Connection feedback belongs to the engine event bus. Get the DiagramEngine
from the live instance and subscribe while a guidance surface is mounted:
tsimport { render } from '@grafloria/element';
const host = document.getElementById('diagram');
if (!host) {
throw new Error('Missing #diagram');
}
host.style.height = '400px';
const instance = render({
nodes: [{ id: 'start', position: { x: 40, y: 80 }, label: 'Start' }],
edges: [],
}, host);
const bus = instance.getEngine().eventBus;
const names = [
'connection:start',
'connection:update',
'connection:port-enter',
'connection:port-leave',
'connection:complete',
'connection:cancel',
] as const;
const stops = names.map((name) => bus.on(name, (payload: object) => {
console.log(name, payload);
}));
function stopConnectionLogging(): void {
for (const stop of stops) {
stop();
}
}
These events describe the live wire gesture, including target entry and refusal guidance. The
instance's connect event is the completed document-level connection; use it when you only need the
resulting link.
Configure what users can do
Set common options when mounting, then use the engine for a runtime change:
tsimport { render } from '@grafloria/element';
const host = document.getElementById('diagram');
if (!host) {
throw new Error('Missing #diagram');
}
host.style.height = '400px';
const instance = render({
nodes: [{ id: 'start', position: { x: 40, y: 80 }, label: 'Start' }],
edges: [],
}, host);
const engine = instance.getEngine();
engine.setInteractionConfig({
showConnectionPreview: true,
highlightValidTargets: true,
enableLinkReconnection: true,
});
The interaction configuration includes these controls:
| Option | Effect |
|---|---|
mode | Chooses direct, deliberate, or smart interaction. |
dragThreshold | Sets the screen-pixel movement required before a click becomes a drag; the documented default is 4. |
portVisibility | Chooses when ports are visible. |
showConnectionPreview | Shows the wire preview during a connection drag. |
highlightValidTargets | Highlights legal connection targets during the drag. |
enableLinkReconnection | Allows link endpoints to be reconnected. |
enableWaypointEditing | Allows adding, moving, and removing link waypoints. |
enableControlPointEditing | Allows control-point editing on Bézier links. |
Runtime updates merge into the existing configuration and emit
config:interaction-changed. Framework-level switches also cover view-only and camera behavior:
readonly, enablePan, enableZoom, minZoom, maxZoom, and zoomSensitivity. Built-in keyboard
support handles history, deletion, arrow-key nudging, and focus-visible navigation without extra
event wiring.
Keep the layers distinct
Use the instance for rendering, reconciliation, events, viewport control, and export. Use
getModel() for nodes, links, groups, and document queries. Use getEngine() for interaction
configuration and behavior. The interaction controller is a lower-level framework-agnostic layer;
use it only when you are extending the interaction pipeline rather than responding to normal user
events.
See The DiagramInstance for the complete instance surface and Commands and undo for what a completed gesture becomes in history.
Was this page helpful?