# @grafloria/react

React bindings for Grafloria Diagrams and Grafloria Dashboards — an MIT diagram and dashboard engine: routing, auto-layout, undo, collaboration and dashboard layouts, native in React.

## Install

```bash
npm install @grafloria/react @grafloria/engine @grafloria/renderer react react-dom @grafloria/element
```

It expects these alongside it:

- `@grafloria/engine` ^0.3.0
- `@grafloria/renderer` ^0.4.16
- `react` ^17.0.0 || ^18.0.0 || ^19.0.0
- `react-dom` ^17.0.0 || ^18.0.0 || ^19.0.0
- `@grafloria/element` ^0.4.3

## Functions

### `createGrafloriaStore`

```ts
function createGrafloriaStore(): GrafloriaStore
```

### `GrafloriaCommentPanel`

```ts
function GrafloriaCommentPanel(props: GrafloriaCommentPanelProps)
```

**Props**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `store` | `CommentStore` |  |  |
| `options?` | `CommentPanelOptions` |  |  |
| `onSelect?` | `(threadId: string) => void` |  |  |
| `className?` | `string` |  |  |
| `style?` | `CSSProperties` |  |  |

**Events**

- `onSelect`

### `GrafloriaDashboard`

```ts
function GrafloriaDashboard(props: GrafloriaDashboardProps)
```

**Props**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `views?` | `DashboardViewSpec[]` |  | Multi-view (tabbed) board. Mutually exclusive with `widgets`. |
| `widgets?` | `DashboardWidgetSpec[]` |  | Single-view shorthand. |
| `options?` | `Partial<DashboardOptions>` |  | Board options: columns, gap, sizing, rtl, responsive, binder… |
| `widgetTypes?` | `WidgetTypes` |  | Maps a widget `kind` to the React component that renders it. |
| `activeView?` | `string` |  | The visible view (the tab pattern). Omit for kit-managed. |
| `layout?` | `"split" \| "grid"` |  | LIVE SWITCHES — the toolbar toggles as props. Each is applied at mount |
| `sizing?` | `"fit" \| "grow"` |  |  |
| `static?` | `boolean` |  | Static board: the viewer's mode — no drag, no resize, no handles. |
| `onReady?` | `(handle: DashboardHandle) => void` |  | The typed handle, once the board is live. |
| `onLayoutChange?` | `(change: { viewId: string; widgets: DashboardWidgetSpec[]; }) => void` |  | Mirrors the kit's committed gestures (drag, resize, add, remove). |
| `className?` | `string` |  |  |
| `style?` | `CSSProperties` |  |  |
| `children?` | `ReactNode` |  |  |

**Events**

- `onReady` — The typed handle, once the board is live.
- `onLayoutChange` — Mirrors the kit's committed gestures (drag, resize, add, remove).

### `GrafloriaDiagram`

```ts
function GrafloriaDiagram(props: GrafloriaDiagramProps)
```

**Props**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `spec` | `RenderSpec` |  | Any kit spec — `erDiagram(...)`, `umlDiagram(...)`, `dashboard(...)`, or DSL text. |
| `options?` | `RenderOptions` |  | Options passed through to the underlying `createDiagram`. |
| `onReady?` | `(instance: DiagramInstance) => void` |  |  |
| `className?` | `string` |  |  |
| `style?` | `CSSProperties` |  |  |

**Events**

- `onReady`

### `GrafloriaFlow`

```ts
function GrafloriaFlow(props: GrafloriaFlowProps)
```

**Props**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `nodes?` | `NodeSpec[]` |  | Controlled nodes. Provide with `onNodesChange` (see `useNodesState`). |
| `edges?` | `EdgeSpec[]` |  | Controlled edges. |
| `groups?` | `(GroupModel \| GroupSpec)[]` |  | Controlled groups — zones around some nodes (a spec's `groups`, or the live |
| `defaultNodes?` | `NodeSpec[]` |  | Uncontrolled nodes — the instance owns them from here on. |
| `defaultEdges?` | `EdgeSpec[]` |  |  |
| `defaultGroups?` | `(GroupModel \| GroupSpec)[]` |  |  |
| `onNodesChange?` | `(nodes: NodeModel[]) => void` |  |  |
| `onEdgesChange?` | `(edges: LinkModel[]) => void` |  |  |
| `onSelectionChange?` | `(change: { nodes: NodeModel[]; edges: LinkModel[]; }) => void` |  |  |
| `onConnect?` | `(change: { link: LinkModel; }) => void` |  |  |
| `onNodeClick?` | `(change: { node: NodeModel; world: { x: number; y: number; }; }) => void` |  |  |
| `onEdgeClick?` | `(change: { edge: LinkModel; world: { x: number; y: number; }; }) => void` |  |  |
| `onInit?` | `(instance: DiagramInstance) => void` |  |  |
| `nodeTypes?` | `NodeTypes` |  | Custom node components, keyed by node `type`. |
| `theme?` | `Theme` |  |  |
| `fitView?` | `boolean` |  |  |
| `enablePan?` | `boolean` |  |  |
| `enableZoom?` | `boolean` |  |  |
| `zoomSensitivity?` | `number` |  |  |
| `dragThreshold?` | `number` |  |  |
| `readonly?` | `boolean` |  |  |
| `minZoom?` | `number` |  |  |
| `maxZoom?` | `number` |  |  |
| `ssr?` | `{ html: string; snapshot: HydrationSnapshot; }` |  | The `renderToStaticSVG()` result. Renders server-side, hydrates client-side. |
| `layout?` | `string \| { name: string; options?: Record<string, unknown>; }` |  | Declarative auto-layout — any engine registry name ('elk', 'dagre', |
| `onLayoutDone?` | `(result: unknown) => void` |  | Fires after each declarative layout completes. |
| `plugins?` | `boolean \| CanvasPluginOptions` |  | Canvas plugins — `true` mounts minimap + zoom/fit controls + background |
| `collab?` | `GrafloriaCollabOptions` |  | Real-time collaboration: hand in a transport (BroadcastChannelTransport, |
| `onCollabReady?` | `(session: SyncAdapter) => void` |  | The live SyncAdapter, right after `join()`. |
| `comments?` | `boolean \| CommentStore` |  | Anchored comment threads — `true` creates a store, or pass a shared |
| `commentsViewer?` | `string` |  | Viewer id for a `comments: true`-created store. |
| `rendererConfig?` | `Record<string, unknown>` |  | Renderer config passthrough (parallelLinks, parallelSpacing, jump styles, …). |
| `interaction?` | `Record<string, unknown>` |  | Interaction config passthrough (portVisibility, enableHelperLines, …). |
| `tokenBridge?` | `unknown` |  | Design-token bridge — adopt the app's shadcn / MUI / Tailwind CSS variables. |
| `highlighterConfig?` | `boolean \| Partial<HighlighterConfig>` |  | The outline layer Angular's canvas draws: outlines around the hovered node, |
| `highlightConnected?` | `boolean \| HighlightConnectedOptions` |  | Bring the selected nodes' lines forward and fade the rest: `true`, or |
| `className?` | `string` |  |  |
| `style?` | `CSSProperties` |  |  |
| `children?` | `ReactNode` |  | Overlays (toolbars, panels). Rendered as siblings of the canvas. |

**Events**

- `onNodesChange`
- `onEdgesChange`
- `onSelectionChange`
- `onConnect`
- `onNodeClick`
- `onEdgeClick`
- `onInit`
- `onLayoutDone` — Fires after each declarative layout completes.
- `onCollabReady` — The live SyncAdapter, right after `join()`.

### `GrafloriaProvider`

Wrap anything that needs `useGrafloria()` outside of `<GrafloriaFlow>`'s subtree.

```tsx
<GrafloriaProvider>
  <Toolbar />           // useGrafloria() works here…
  <GrafloriaFlow … />       // …because the flow publishes its instance to the store
</GrafloriaProvider>
```

`<GrafloriaFlow>` also creates its own store when there is no provider, so the
simple single-canvas case needs no wrapper at all.

```ts
function GrafloriaProvider({ children }: GrafloriaProviderProps)
```

**Props**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `children?` | `ReactNode` |  |  |

### `useEdgesState`

Controlled edge state. Mirrors {@link useNodesState}.

```ts
function useEdgesState(initial: EdgeSpec[] = []): EdgesState
```

### `useGrafloria`

The live `DiagramInstance`, or `null` until `<GrafloriaFlow>` has mounted.

Works from anywhere inside an `<GrafloriaProvider>` (a toolbar, a minimap, a
sidebar) and from inside `<GrafloriaFlow>`'s own children.

```tsx
const grafloria = useGrafloria();
<button onClick={() => grafloria?.fitView()}>Fit</button>
```

```ts
function useGrafloria(): DiagramInstance | null
```

### `useGrafloriaStore`

The nearest store, or null when there is no provider above us.

```ts
function useGrafloriaStore(): GrafloriaStore | null
```

### `useNodesState`

Controlled node state — the React Flow tuple everyone already knows:

```tsx
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
<GrafloriaFlow nodes={nodes} onNodesChange={onNodesChange} … />
```

`onNodesChange` is what closes the loop: the user drags a node, the ENGINE
moves it, the instance emits `nodes:change`, `<GrafloriaFlow>` calls this, and
React state catches up. Without it a controlled `<GrafloriaFlow>` would snap the
node back on the next render — the classic controlled-component trap.

```ts
function useNodesState(initial: NodeSpec[] = []): NodesState
```

### `useOnSelectionChange`

Fire a callback whenever the selection changes.

```tsx
useOnSelectionChange(({ nodes }) => setInspected(nodes[0] ?? null));
```

The handler is held in a ref, so passing an inline arrow (the common case)
does NOT re-subscribe on every render.

```ts
function useOnSelectionChange(handler: (change: SelectionChange) => void): void
```

### `useSelection`

The current selection as state (for rendering an inspector panel).

```ts
function useSelection(): SelectionChange
```

### `useViewport`

The live camera (zoom + world rect) as state — for a minimap or a zoom badge.

```ts
function useViewport(): { zoom: number; x: number; y: number }
```

## Constants

### `GrafloriaContext`

```ts
const GrafloriaContext: any
```

## Interfaces

### `GrafloriaCommentPanelProps`

```ts
interface GrafloriaCommentPanelProps
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `store` | `CommentStore` |  |  |
| `options?` | `CommentPanelOptions` |  |  |
| `onSelect?` | `(threadId: string \| null) => void` |  |  |
| `className?` | `string` |  |  |
| `style?` | `CSSProperties` |  |  |

### `GrafloriaDashboardProps`

```ts
interface GrafloriaDashboardProps
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `views?` | `DashboardViewSpec[]` |  | Multi-view (tabbed) board. Mutually exclusive with `widgets`. |
| `widgets?` | `DashboardWidgetSpec[]` |  | Single-view shorthand. |
| `options?` | `Partial<DashboardOptions>` |  | Board options: columns, gap, sizing, rtl, responsive, binder… |
| `widgetTypes?` | `WidgetTypes` |  | Maps a widget `kind` to the React component that renders it. |
| `activeView?` | `string` |  | The visible view (the tab pattern). Omit for kit-managed. |
| `layout?` | `'grid' \| 'split'` |  | LIVE SWITCHES — the toolbar toggles as props. Each is applied at mount (over `options`) and, when it changes afterwards, through the handle (`setLayout` / `setSizing` / `setStatic`) — no remount, like `activeView`. 'split' is the DevExpress splitter tree; 'grid' the cell grid. |
| `sizing?` | `'fit' \| 'grow'` |  |  |
| `static?` | `boolean` |  | Static board: the viewer's mode — no drag, no resize, no handles. |
| `onReady?` | `(handle: DashboardHandle) => void` |  | The typed handle, once the board is live. |
| `onLayoutChange?` | `(change: { viewId: string; widgets: DashboardWidgetSpec[] }) => void` |  | Mirrors the kit's committed gestures (drag, resize, add, remove). |
| `className?` | `string` |  |  |
| `style?` | `CSSProperties` |  |  |
| `children?` | `ReactNode` |  |  |

### `GrafloriaDiagramProps`

```ts
interface GrafloriaDiagramProps
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `spec` | `RenderSpec` |  | Any kit spec — `erDiagram(...)`, `umlDiagram(...)`, `dashboard(...)`, or DSL text. |
| `options?` | `RenderOptions` |  | Options passed through to the underlying `createDiagram`. |
| `onReady?` | `(instance: DiagramInstance) => void` |  |  |
| `className?` | `string` |  |  |
| `style?` | `CSSProperties` |  |  |

### `GrafloriaFlowProps`

```ts
interface GrafloriaFlowProps
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `nodes?` | `NodeSpec[]` |  | Controlled nodes. Provide with `onNodesChange` (see `useNodesState`). |
| `edges?` | `EdgeSpec[]` |  | Controlled edges. |
| `groups?` | `Array<GroupSpec \| GroupModel>` |  | Controlled groups — zones around some nodes (a spec's `groups`, or the live GroupModels of a loaded document). Reconciled like `nodes`. |
| `defaultNodes?` | `NodeSpec[]` |  | Uncontrolled nodes — the instance owns them from here on. |
| `defaultEdges?` | `EdgeSpec[]` |  |  |
| `defaultGroups?` | `Array<GroupSpec \| GroupModel>` |  |  |
| `onNodesChange?` | `(nodes: NodeModel[]) => void` |  |  |
| `onEdgesChange?` | `(edges: LinkModel[]) => void` |  |  |
| `onSelectionChange?` | `(change: { nodes: NodeModel[]; edges: LinkModel[] }) => void` |  |  |
| `onConnect?` | `(change: { link: LinkModel }) => void` |  |  |
| `onNodeClick?` | `(change: { node: NodeModel; world: { x: number; y: number } }) => void` |  |  |
| `onEdgeClick?` | `(change: { edge: LinkModel; world: { x: number; y: number } }) => void` |  |  |
| `onInit?` | `(instance: DiagramInstance) => void` |  |  |
| `nodeTypes?` | `NodeTypes` |  | Custom node components, keyed by node `type`. |
| `theme?` | `Theme` |  |  |
| `fitView?` | `boolean` |  |  |
| `enablePan?` | `boolean` |  |  |
| `enableZoom?` | `boolean` |  |  |
| `zoomSensitivity?` | `number` |  |  |
| `dragThreshold?` | `number` |  |  |
| `readonly?` | `boolean` |  |  |
| `minZoom?` | `number` |  |  |
| `maxZoom?` | `number` |  |  |
| `ssr?` | `{ html: string; snapshot: HydrationSnapshot }` |  | The `renderToStaticSVG()` result. Renders server-side, hydrates client-side. |
| `layout?` | `string \| { name: string; options?: Record<string, unknown> }` |  | Declarative auto-layout — any engine registry name ('elk', 'dagre', 'force', 'tree', 'grid', 'auto', …) or `{ name, options }`. Re-runs when the prop VALUE changes, never when node data changes. |
| `onLayoutDone?` | `(result: unknown) => void` |  | Fires after each declarative layout completes. |
| `plugins?` | `boolean \| CanvasPluginOptions` |  | Canvas plugins — `true` mounts minimap + zoom/fit controls + background grid with defaults; an object picks and configures them. |
| `collab?` | `GrafloriaCollabOptions` |  | Real-time collaboration: hand in a transport (BroadcastChannelTransport, WebSocketTransport, MemoryTransport, …) and an actor id — the flow joins a CRDT sync session at mount and leaves on unmount. Fixed for the life of the instance. |
| `onCollabReady?` | `(session: SyncAdapter) => void` |  | The live SyncAdapter, right after `join()`. |
| `comments?` | `boolean \| CommentStore` |  | Anchored comment threads — `true` creates a store, or pass a shared `CommentStore`. Read it back with `useGrafloria()?.getCommentStore()`. |
| `commentsViewer?` | `string` |  | Viewer id for a `comments: true`-created store. |
| `rendererConfig?` | `Record<string, unknown>` |  | Renderer config passthrough (parallelLinks, parallelSpacing, jump styles, …). |
| `interaction?` | `Record<string, unknown>` |  | Interaction config passthrough (portVisibility, enableHelperLines, …). |
| `tokenBridge?` | `unknown` |  | Design-token bridge — adopt the app's shadcn / MUI / Tailwind CSS variables. |
| `highlighterConfig?` | `boolean \| Partial<HighlighterConfig>` |  | The outline layer Angular's canvas draws: outlines around the hovered node, the selected node, nodes with a validation issue, and valid connection targets. `true` turns every kind on; an object turns kinds on or off one by one. Off when unset. Live: follows the prop by value. |
| `highlightConnected?` | `boolean \| HighlightConnectedOptions` |  | Bring the selected nodes' lines forward and fade the rest: `true`, or options (depth, stroke, outgoing, dimOpacity). Off when unset. Live: follows the prop by value. |
| `className?` | `string` |  |  |
| `style?` | `CSSProperties` |  |  |
| `children?` | `ReactNode` |  | Overlays (toolbars, panels). Rendered as siblings of the canvas. |

### `GrafloriaProviderProps`

```ts
interface GrafloriaProviderProps
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `children?` | `ReactNode` |  |  |

### `GrafloriaStore`

The provider + the store behind `useGrafloria()`.

React Flow's ergonomics come from exactly this shape: a `<ReactFlowProvider>`
that lets a toolbar, a sidebar or a minimap — components that are SIBLINGS of
the canvas, not children of it — reach the live instance. We keep that shape,
but the thing being shared is our framework-agnostic `DiagramInstance`, so the
provider is a 40-line store and NOT a re-implementation of the diagram.

Why a hand-rolled store rather than `useSyncExternalStore`: that hook is React
18+, and this package supports React 17–19. Subscribe + `useState` costs one
extra render on attach and works everywhere.

```ts
interface GrafloriaStore
```

**Members**

- `get(): DiagramInstance | null` — The live instance, or null before `<GrafloriaFlow>` has mounted.
- `set(instance: DiagramInstance | null): void` — Called by `<GrafloriaFlow>` on mount/unmount.
- `subscribe(listener: (instance: DiagramInstance | null) => void): () => void` — Notified whenever the instance is attached or detached.

### `NodeProps`

Props a custom node component receives. Deliberately React-Flow-shaped.

```ts
interface NodeProps<TData = Record<string, unknown>>
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` |  |  |
| `data` | `TData` |  |  |
| `selected` | `boolean` |  |  |
| `node` | `NodeModel` |  | The live engine model — the escape hatch. |

### `SelectionChange`

```ts
interface SelectionChange
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `nodes` | `NodeModel[]` |  |  |
| `edges` | `LinkModel[]` |  |  |

### `WidgetProps`

Props a widget component receives — the `NodeProps` twin for boards.

```ts
interface WidgetProps<TData = Record<string, unknown>>
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `widget` | `DashboardWidgetSpec` |  |  |
| `data` | `TData` |  |  |

## Types

### `EdgesState`

Also has every member of `Array`, listed on its own entry.

```ts
type EdgesState = [
  EdgeSpec[],
  Dispatch<SetStateAction<EdgeSpec[]>>,
  (edges: LinkModel[]) => void,
];
```

### `NodesState`

Also has every member of `Array`, listed on its own entry.

What `useNodesState` hands back — React Flow's tuple, with our types.

```ts
type NodesState = [
  NodeSpec[],
  Dispatch<SetStateAction<NodeSpec[]>>,
  (nodes: NodeModel[]) => void,
];
```

### `NodeTypes`

`nodeTypes` maps a node's `type` to the component that renders it.

```ts
type NodeTypes = Record<string, ComponentType<NodeProps<never>>>;
```

### `WidgetTypes`

```ts
type WidgetTypes = Record<string, ComponentType<WidgetProps>>;
```

## Also exported from here

These names are documented with the package that defines them, and can be imported from this one too.

- From [@grafloria/renderer](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-overview): `DARK_THEME`, `DiagramInstance`, `EdgeSpec`, `HydrationSnapshot`, `LIGHT_THEME`, `NodeSpec`, `PortSpec`, `renderToStaticSVG`, `StaticRenderOptions`, `StaticRenderResult`, `Theme`
