Grafloria uses one event map beneath its framework bindings: the mounted DiagramInstance reports model, selection, pointer, and viewport changes, while each framework exposes those changes in its own idiom.
One event map
The instance is the facade over the diagram model and engine. Subscribe to it when application code needs the same behavior in JavaScript, React, Vue, Angular, or Qwik.
tsimport type {
DiagramInstance,
Unsubscribe,
} from '@grafloria/renderer';
export function connectEventHandlers(instance: DiagramInstance): Unsubscribe[] {
const stopNodes = instance.on('nodes:change', ({ nodes }) => {
console.log('nodes changed', nodes);
});
const stopEdges = instance.on('edges:change', ({ edges }) => {
console.log('edges changed', edges);
});
const stopSelection = instance.on('selection:change', ({ nodes, edges }) => {
console.log('selection changed', nodes, edges);
});
const stopConnect = instance.on('connect', ({ link }) => {
console.log('connection completed', link);
});
const stopReconnect = instance.on('reconnect', ({ link, endpoint }) => {
console.log('connection endpoint moved', link, endpoint);
});
const stopNodeClick = instance.on('node:click', ({ node, world }) => {
console.log('node clicked', node, world);
});
const stopDoubleClick = instance.on('node:doubleclick', ({ node, world }) => {
console.log('node double-clicked', node, world);
});
const stopEdgeClick = instance.on('edge:click', ({ edge, world }) => {
console.log('edge clicked', edge, world);
});
const stopViewport = instance.on('viewport:change', ({ viewport, zoom }) => {
console.log('viewport changed', viewport, zoom);
});
return [
stopNodes,
stopEdges,
stopSelection,
stopConnect,
stopReconnect,
stopNodeClick,
stopDoubleClick,
stopEdgeClick,
stopViewport,
];
}
Each on() call returns an unsubscribe function. Keep those functions with the mounted component and call them when that component unmounts; off() is the alternative when you retain the original handler. The world value in pointer events uses diagram coordinates, not screen pixels.
The event names are represented by DiagramEventName, and the payload handler shape by DiagramEventHandler. The returned cleanup function is Unsubscribe.
mermaidflowchart LR A[User action] --> B[DiagramInstance event map] B --> C[Framework callback or output] B --> D[Application state or persistence] A --> E[DiagramEngine connection lifecycle] E --> F[Guidance UI]
Framework surfaces
The event meaning stays the same; only the binding surface changes.
| Surface | Use for user-facing events | Instance access |
|---|---|---|
| Plain JavaScript | api.on(...), or bubbling grafloria-* DOM events | The object returned by the JavaScript mount |
| React | onConnect, onNodeClick, onSelectionChange, and onNodesChange callback props | The instance supplied by the binding's initialization callback |
| Vue | @connect, @node-click, @selection-change, and v-model:nodes | The instance supplied by @init |
| Angular | [(nodes)], (viewportChanged), and (layoutDone); use the engine bus for clicks | The Angular canvas instance |
| Qwik | The binding's onInit$ callback receives the same instance | The DiagramInstance argument to onInit$ |
The Vue binding receives the instance through @init, while the Qwik binding receives it through the serializable onInit$ callback. Use the component's callback or output first when the binding provides one. Reach through the instance for events that the binding does not expose, such as reconnect and node:doubleclick.
Connection lifecycle
The instance's connect event reports a completed wire. For guidance while the user is dragging, use the DiagramEngine event bus:
tsimport type { DiagramInstance } from '@grafloria/renderer';
export function watchConnectionDrag(instance: DiagramInstance): () => void {
const bus = instance.getEngine().eventBus;
const names = [
'connection:start',
'connection:update',
'connection:port-enter',
'connection:port-leave',
'connection:complete',
'connection:cancel',
] as const;
const unsubscribers = names.map((name) => {
const handler = (payload: object): void => {
console.log(name, payload);
};
return bus.on(name, handler);
});
return (): void => {
for (const unsubscribe of unsubscribers) {
unsubscribe();
}
};
}
The lifecycle starts when a connection drag begins, updates as the pointer moves, announces port entry and exit, and ends as either connection:complete or connection:cancel. Use these notifications to tint legal targets or explain refusals; use connect when the completed link is the application result.
Interaction configuration
Interaction configuration controls what gestures the user may perform. Set common options at mount, or update engine-level interaction configuration at runtime with instance.getEngine().setInteractionConfig({ portVisibility: 'always' }).
Framework-level switches cover read-only mode, panning, zooming, zoom limits, zoom sensitivity, and fit-to-view. Angular also exposes switches for snapping, proximity connections, keyboard navigation, in-place editing, and canvas bounds. Built-in keyboard support handles history, deletion, arrow-key nudging, and focus-visible navigation without event wiring.
The renderer's InteractionController owns pointer and keyboard interaction logic, but it does not decide how a framework re-renders. Angular marks for check, React updates state or its external-store subscription, Vue touches a ref, and a vanilla host calls its own render path. Use the instance and its binding before reaching for this lower layer.
Related concepts
- How Grafloria works — the shared model, engine, and history.
- The DiagramInstance — the complete instance surface.
- Commands and shared history — what a gesture becomes after it lands.
- Ports and connection rules — how connection legality is enforced.
Was this page helpful?