# Events and interaction

Grafloria uses one event map beneath its framework bindings: the mounted [`DiagramInstance`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-diagraminstance#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.

```ts
import 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`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance#diagrameventname), and the payload handler shape by [`DiagramEventHandler`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance#diagrameventhandler). The returned cleanup function is [`Unsubscribe`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-core#unsubscribe).

```mermaid
flowchart 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`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-engine-engine#diagramengine) event bus:

```ts
import 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`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-interaction-interactioncontroller#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](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/how-grafloria-works) — the shared model, engine, and history.
- [The DiagramInstance](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-diagraminstance) — the complete instance surface.
- [Commands and shared history](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/commands-and-shared-history) — what a gesture becomes after it lands.
- [Ports and connection rules](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/ports-and-connection-rules) — how connection legality is enforced.
