Grafloria records edits through the engine's command path and reports the resulting model change through the instance event map and your framework binding. This keeps the canvas and your application state synchronized without a second undo stack.
One path from edit to state
The DiagramInstance is the handle shared by the framework wrappers. Its model stores nodes and edges; its DiagramEngine owns commands, validation, and history.
mermaidflowchart LR G["DiagramEngine.addNode()"] --> C["AddNodeCommand"] C --> H["Engine history"] H --> M["Diagram model"] M --> E["Instance event map"] E --> B["React callbacks, Vue emits, Angular outputs, or Qwik QRLs"] B --> S["Application state"]
Listen at the instance
Use instance.on() when you need the shared event map or when the event is not represented by a component callback. The call returns an unsubscribe function; call it when the owner of the listener is disposed.
tsimport type { DiagramInstance } from '@grafloria/renderer';
export function watchDiagram(instance: DiagramInstance) {
const offNodes = instance.on('nodes:change', ({ nodes }) => {
console.log('nodes:', nodes.length);
});
const offSelection = instance.on('selection:change', ({ nodes, edges }) => {
console.log('selected:', nodes.length, edges.length);
});
return () => {
offNodes();
offSelection();
};
}
nodes:change reports the live node models after nodes are added, removed, or changed. selection:change reports the selected node and edge models. Other instance events include edges:change, connect, reconnect, node:click, node:doubleclick, edge:click, and viewport:change; their payloads describe the completed interaction in diagram coordinates where applicable.
The same history in each framework
The event map stays the same; only the binding syntax changes.
Vue
The Vue GrafloriaFlow wrapper emits the same interactions in Vue form. Bind the controlled arrays with v-model and retain the instance from @init:
vue<script setup lang="ts"> import { ref } from 'vue'; import { GrafloriaFlow } from '@grafloria/vue'; import type { DiagramInstance, NodeSpec } from '@grafloria/renderer'; const nodes = ref<NodeSpec[]>([ { id: 'start', type: 'rect', label: 'Start', position: { x: 40, y: 80 }, size: { width: 140, height: 56 } }, { id: 'finish', type: 'rect', label: 'Finish', position: { x: 280, y: 80 }, size: { width: 140, height: 56 } }, ]); const instance = ref<DiagramInstance | null>(null); function onInit(ready: DiagramInstance) { instance.value = ready; } </script> <template> <div style="height: 320px"> <GrafloriaFlow v-model:nodes="nodes" @init="onInit" @selection-change="console.log" /> </div> <button type="button" @click="instance?.getEngine().undo()">Undo</button> <button type="button" @click="instance?.getEngine().redo()">Redo</button> </template>
v-model:nodes receives the reverted array after undo, so the displayed nodes and Vue state describe the same document.
Angular
Angular's DiagramCanvasComponent mirrors undo() and redo() as component methods. In controlled mode, two-way nodes and edges bindings receive model changes:
html<grafloria-diagram-canvas
#canvas
[(nodes)]="nodes"
[(edges)]="edges"
(modelChange)="persist($event)"
></grafloria-diagram-canvas>
<button type="button" (click)="canvas.undo()">Undo</button>
<button type="button" (click)="canvas.redo()">Redo</button>
Keep a template reference to the mounted component (#canvas) when using this form. For events that Angular does not expose as outputs, subscribe through the active engine's event bus rather than creating a separate history.
Qwik
Qwik's GrafloriaFlow uses the same instance and data model, with QRL callback props. Use onInit$ to retain the instance and onNodesChange$ for the model callback in a Qwik component. The callback receives the reconciled models after undo or redo.
Choose the edit path
Use the diagram model's direct operations for setup, import, and synchronization: those operations establish the document and do not represent a user's undoable action. Use the engine's command manager for an application-initiated edit that belongs in the user's history, such as a toolbar action or an assistant suggestion. A refused command does not enter history; in strict validation mode, an invalid result is reverted and is not recorded.
Do not call undo() on DiagramInstance; it is not an instance method. Call instance.getEngine().undo() instead. Angular's canvas component is the exception because it mirrors the method directly.
Was this page helpful?