# DashboardHandle

Import it from `@grafloria/element`.

The typed façade — the `erTable`/`umlClass` equivalent for dashboards.

```ts
interface DashboardHandle
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `views` | `string[]` |  | The view ids, in declaration order. |
| `activeView` | `string` |  | The currently shown view id. |

**Members**

- `showView(id: string): void` — Show a view (the others park off-camera) and frame it.
- `widget(id: string): WidgetHandle | undefined` — A widget handle by id (undefined when unknown).
- `focusWidget(id: string): boolean` — SELECT a widget and move keyboard focus to it — what a press on the widget
does. The selected widget shows its painted grip (`dragHandle: { grip }`)
and a quiet ring; a void click clears. False when the id is unknown.
- `selectWidget(id: string | undefined): boolean` — SELECT a widget WITHOUT moving keyboard focus — the ring and the grip,
nothing else; what a mouse press does. `undefined` clears the selection
on every view. False when the id is unknown.
- `getSelectedWidget(): string | undefined` — The selected widget, if any (across views: only the on-camera one can be).
- `widgetsOf(viewId?: string): WidgetHandle[]` — Every widget handle of a view (default: the active one).
- `setLayout(layout: 'grid' | 'split', viewId?: string): void` — Switch a view (default: the active one) between the cell grid and the
split tree, live and keeping the picture: cells → tree by guillotine cuts,
tree → cells by snapping to the columns. Persisted on the board, so a
saved document reopens in the layout it was left in.
- `getLayout(viewId?: string): 'grid' | 'split' | 'tabs'` — A container built with `layout: 'tabs'` reports 'tabs'; views never do.
- `setCaption(id: string, caption: SectionCaption): boolean` — Set a SECTION's caption live — `false` removes it, `true` is the title, a
string or the options. Repaints the band, gives the reserve back or takes
it, persists on the section, one undo step. False for anything that is
not a container.
- `getCaption(id: string): SectionCaption | undefined` — The caption as authored or last set; undefined when the section has none.
- `activateTab(containerId: string, pageId: string): boolean` — Show a PAGE of a tab container (`layout: 'tabs'`). False when the
container or the page is not one. Persisted, so a saved board reopens on
the page it was left on.
- `getActiveTab(containerId: string): string | undefined` — The page showing in a tab container.
- `moveToTab(widgetId: string, containerId: string, index?: number): Promise<boolean>` — Move a WIDGET into `containerId` as a NEW TAB at `index` (default: the
end): the widget becomes the only member of a fresh page named by its
title, and that page the active tab — what dropping a widget on a strip
does. One undoable step; the board it left re-packs, and a page it
emptied closes.
- `moveTab(containerId: string, pageId: string, index: number): boolean` — Reorder a page along its container's strip. One undoable step; the order is saved with the container.
- `setSizing(mode: 'fit' | 'grow'): void` — Live sizing/float switches — the two prototype toggles.
- `getSizing(): 'fit' | 'grow'`
- `setFloat(on: boolean): void`
- `getFloat(): boolean`
- `setColumns(n: number, layout?: GridColumnLayout, viewId?: string): void` — Set the COLUMN COUNT of every board (or one view), live. Goes through the
engine's per-column layout cache, so shrinking then growing back restores
the wide layout rather than re-deriving it. An explicit call PINS the
count — the width-driven `responsive` evaluator stops overriding it.
- `getColumns(viewId?: string): number` — The LIVE column count of a view (default: the active one).
- `setRtl(on: boolean): void` — RTL mirroring, live — pixels only, cells never change.
- `getRtl(): boolean`
- `setStatic(on: boolean): void` — Static (read-only for the pointer) mode, live — the viewer/designer switch.
- `getStatic(): boolean`
- `setDragHandle(v: DragHandleOption): void` — Drag-handle mode, live, every view: `true` = the caption strip, a selector = your own handle, `{ grip: true, … }` = a painted grip, `false` = the whole card.
- `getDragHandle(): DragHandleOption`
- `addWidget(spec: DashboardWidgetSpec, viewId?: string, opts?: { displaced?: Command[] }): WidgetHandle | undefined` — Add a widget to a view. CREATES the node (you do not pre-build one), wires
its metadata, and commits node + membership as ONE undoable step. Auto-positions when the spec names no cell. `opts.displaced`: the
commands a palette drop handed `onDropIn` for the tiles the placeholder
pushed aside — folded into the same step, so the widget lands on the cell
the drop showed and undo puts the pushed tiles back with it. Left out,
the board is re-read from the model and the push is forgotten: the new
widget then auto-positions into whatever hole is left (Quantia's "lands
on the cell it was aimed at", element 0.4.54).
- `refresh(): void` — Re-read every board from the model — call after undo/redo, or any
out-of-band mutation, so the grid and the projection agree again.
- `fit(viewId?: string): void` — Re-frame the camera on a view (default: the active one).
- `metrics(viewId?: string): ReturnType<DashboardGridHandle['metrics']> | undefined` — Live geometry of a view's board (columns, gap, rows, rowHeight, frame…).
- `toJSON(): DashboardSnapshot` — The whole board as plain data — feed it straight back to `dashboard()`:

```ts
dashboard({ ...handle.toJSON(), renderWidget });   // a true round trip
```

Everything `DashboardOptions` takes EXCEPT the function seams
(`renderWidget`, `onLayoutChange`), which cannot be written to a file and
must be supplied again on the way back in.

Values are read from the LIVE board, not from the authored literal, so a
mode or column count the user changed after mount is what you get back.

This used to return only `views`, which made the round-trip claim true of
the layout and false of the board: a board authored `grow` at a 10-column,
6px-gap geometry reloaded as a 12-column `fit` one. It is also what
`JSON.stringify(handle)` calls, so the partial answer was a permanent
footgun in a save API rather than merely an omission.
- `exportIds(viewId?: string): Set<string>` — The node ids ONE view occupies — pass straight to `includeIds` to export
just that board:

```ts
api.export('pdf', { includeIds: handle.exportIds() });
```

WHY THIS EXISTS. Tabs park the inactive views far off-camera, which is
invisible on screen and ruinous on export: `export()` frames the whole
MODEL, so a two-view board writes a ~21,000px document that is almost
entirely empty — with no warning, because nothing is technically wrong. Scoping was always possible; knowing WHAT to scope to was not.

The set includes the view's GROUP as well as its widgets. Rolling this by
hand from `toJSON()` looks equivalent and is not — it drops the group, and
the widgets export without the frame they sit in.
- `binderOf(viewId?: string): DashboardGridHandle | undefined` — THE DOCUMENTED ESCAPE HATCH: the view's own `bindDashboardGrid` handle
(default: the active view). Reach for it only for what this façade does
not cover yet — palette drag-in (`beginPaletteDrag`), board `metrics()`,
`cellRectOf`, `planRemoval`, and re-`sync()` after an external undo. Every
call site is a named gap in this API, not a normal way to drive a board.
- `dispose(): void`
