# Instance and bindings

Grafloria has one headless engine behind its JavaScript, React, Vue, Angular, Qwik, and custom-element surfaces. Choose the surface that matches your host, then use the shared [`DiagramInstance`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance) when you need the live diagram.

## Choose the layer for the job

The model owns diagram data. The engine owns behavior. The binding connects those layers to your framework's lifecycle and state.

```mermaid
flowchart TB
  UI["React / Vue / Angular / Qwik / HTML"] --> B["Framework binding"]
  B --> I["DiagramInstance"]
  I --> M["DiagramModel: nodes, links, groups, viewport"]
  I --> E["DiagramEngine: commands, layout, validation, undo"]
  M --> R["Renderer and layout: geometry and pixels"]
  E --> R
```

| Need | Use | Why |
|---|---|---|
| Supply or replace nodes and edges | The binding's data props, or `setNodes()` / `setEdges()` on the instance | Specs are reconciled into live models. Persistent IDs keep existing live models. |
| Query nodes, links, groups, or selection | `instance.getModel()` | The model is the document's live source of truth. |
| Layout, validation, commands, or undo | `instance.getEngine()` | Behavior belongs to the engine, not the instance's data API. |
| Paint after a mutation | `render()` or `renderNow()` | `render()` queues a coalesced repaint; `renderNow()` paints synchronously when you must measure immediately. |
| Listen for changes | `instance.on()` or the binding's event props | Every binding exposes the same event map in its native idiom. |
| Read or change the camera | `instance.viewport` and `fitView()` | Viewport data is separate from node data. |
| Read comments | `getCommentStore()` | It returns the enabled comment store, or `null` when comments are disabled. |

Do not put engine behavior into framework state. Keep application state for data that the rest of your application owns, such as a saved document or an inspector selection. Keep the live diagram in the instance and subscribe to the events or hooks that your UI needs.

## The same instance from each surface

For plain JavaScript, [`render`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-element-core#render) mounts a data spec and returns the instance immediately. Its target can be an element or a CSS selector; the spec is data, not Mermaid text.

```js
import { render } from '@grafloria/element';

const host = document.createElement('div');
host.id = 'diagram';
host.style.height = '240px';
document.body.append(host);

const api = render(
  {
    nodes: [
      { id: 'plan', position: { x: 40, y: 40 }, size: { width: 140, height: 56 }, label: 'Plan' },
      { id: 'ship', position: { x: 280, y: 40 }, size: { width: 140, height: 56 }, label: 'Ship' },
    ],
    edges: [{ id: 'plan-ship', source: 'plan', target: 'ship' }],
  },
  host,
  { fitView: true },
);

const stopListening = api.on('selection:change', ({ nodes }) => {
  console.log(`Selected ${nodes.length} node(s)`);
});

api.fitView(24);
```

The mounted host shows two boxes and a connecting edge. Selecting a box invokes the handler with the live selected models. `fitView()` frames both boxes; call `dispose()` from your host's teardown path rather than immediately after mounting in an application that still displays the diagram.

In React, [`GrafloriaFlow`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-react#grafloriaflow) is the component surface. Its `onInit` callback supplies the same instance that `render()` returns. Use [`GrafloriaProvider`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-react#grafloriaprovider) when a toolbar or inspector is outside the flow subtree, then read the handle with [`useGrafloria`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-react#usegrafloria). The subscription hooks expose framework state without copying the whole model into React.

```tsx
import {
  GrafloriaFlow,
  GrafloriaProvider,
  useGrafloria,
  useOnSelectionChange,
  useSelection,
  useViewport,
  type DiagramInstance,
  type EdgeSpec,
  type NodeSpec,
} from '@grafloria/react';
import { useState } from 'react';

const nodes: NodeSpec[] = [
  { id: 'plan', position: { x: 40, y: 40 }, size: { width: 140, height: 56 }, label: 'Plan' },
  { id: 'ship', position: { x: 280, y: 40 }, size: { width: 140, height: 56 }, label: 'Ship' },
];
const edges: EdgeSpec[] = [{ id: 'plan-ship', source: 'plan', target: 'ship' }];

function Toolbar() {
  const instance = useGrafloria();
  const selection = useSelection();
  const viewport = useViewport();
  const [lastSelection, setLastSelection] = useState(0);
  useOnSelectionChange(({ nodes: changedNodes }) => setLastSelection(changedNodes.length));
  return (
    <div>
      <button type="button" onClick={() => instance?.fitView(24)}>Fit view</button>
      <span> zoom {viewport.zoom.toFixed(2)}</span>
      <span> selected {selection.nodes.length} node(s)</span>
      <span> last change {lastSelection}</span>
    </div>
  );
}

export default function DiagramPage() {
  const [instance, setInstance] = useState<DiagramInstance | null>(null);
  return (
    <GrafloriaProvider>
      <Toolbar />
      <div style={{ height: 400 }}>
        <GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} fitView onInit={setInstance} />
      </div>
      <p>{instance ? 'Diagram ready' : 'Starting diagram'}</p>
    </GrafloriaProvider>
  );
}
```

The canvas renders inside the sized `div`; the toolbar reports the live zoom and selection, and the paragraph changes when the instance mounts. `defaultNodes` and `defaultEdges` seed an uncontrolled canvas. If application state must own the graph, pass `nodes` and `edges` together with `onNodesChange` and `onEdgesChange` instead. Do not mix controlled `nodes` with uncontrolled defaults: a stale controlled array can overwrite a user edit on the next render.

Vue exposes the same component-level idea through `GrafloriaFlow` props and emits, including `@selection-change` and `@connect`. Angular exposes controlled `[(nodes)]` / `[(edges)]` bindings and outputs such as `(viewportChanged)` and `(layoutDone)`. The custom element forwards the same instance events as bubbling `CustomEvent`s. The underlying model and engine do not change when the binding changes.

## Data, behavior, and rendering are different calls

Start with the instance, then descend only for the responsibility you need:

```ts
import type { DiagramInstance } from '@grafloria/renderer';

async function inspect(api: DiagramInstance): Promise<void> {
  const model = api.getModel();
  const selected = model.getSelectedNodes();
  const engine = api.getEngine();
  const validation = engine.validateDiagram();
  await engine.layout('elk');
  api.renderNow();
  console.log(selected, validation);
}
```

The first two calls query live data and behavior respectively; the layout call changes geometry through the engine; `renderNow()` makes the resulting pixels current before an immediate measurement. Undo is also an engine operation: call `api.getEngine().undo()`, not `api.undo()`.

For persistence, serialize the model rather than a framework's stale mirror. For text round-trips, use `exportText()` and `loadText()` on the instance. For a screenshot or document export, use the instance's export methods. These operations act on the mounted diagram, so they include edits made through gestures.

## Events and framework state

The instance event map is the common language:

```ts
import type { DiagramInstance } from '@grafloria/renderer';

function watch(api: DiagramInstance): () => void {
  const stopNode = api.on('node:click', ({ node, world }) => {
    console.log(node.id, world.x, world.y);
  });
  const stopViewport = api.on('viewport:change', ({ viewport, zoom }) => {
    console.log(viewport, zoom);
  });
  return () => {
    stopNode();
    stopViewport();
  };
}
```

`on()` returns an unsubscribe function. React callback props, Vue emits, Angular outputs, and element DOM events dress the same map in framework-native forms. Use the component event first when it exists; reach through `on()` when you need an instance event that the component does not expose.

For React UI, [`useSelection`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-react#useselection) is state for rendering an inspector, [`useViewport`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-react#useviewport) is state for a zoom badge or minimap, and [`useOnSelectionChange`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-react#useonselectionchange) is a callback for side effects. They subscribe to the live instance; they are not a second diagram model.

## A practical rule

Pass specs through the binding when the framework owns the document. Use the instance for the mounted diagram's events, camera, painting, export, and text conversion. Use `getModel()` for queries and `getEngine()` for commands, layout, validation, and undo. This keeps one document, one history, and one event map across every binding.
