Skip to content
D
Documentation

Events and interaction

concept
3 min readUpdated

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.

ts
import { 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:

EventPayloadUse 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.
readyvoidRun 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, and onNodesChange.
  • Vue uses kebab-case emits such as @connect, @node-click, and @selection-change; use v-model:nodes for 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:

ts
import { 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:

ts
import { 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:

OptionEffect
modeChooses direct, deliberate, or smart interaction.
dragThresholdSets the screen-pixel movement required before a click becomes a drag; the documented default is 4.
portVisibilityChooses when ports are visible.
showConnectionPreviewShows the wire preview during a connection drag.
highlightValidTargetsHighlights legal connection targets during the drag.
enableLinkReconnectionAllows link endpoints to be reconnected.
enableWaypointEditingAllows adding, moving, and removing link waypoints.
enableControlPointEditingAllows 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?