# Layout — Grid Pack

Import these from `@grafloria/engine`.

## Classes

### `GridPackEngine`

```ts
class GridPackEngine
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `float` | `boolean` |  |  |
| `maxRows?` | `number` |  | Row bound (see GridPackOptions.maxRows). Undefined = unbounded. Changed through {@link setBound}. |
| `capacity?` | `number` |  | Row capacity (see GridPackOptions.capacity). Undefined = unbounded. |

**Methods**

- `get columns(): number` — Live column count. Change it only through {@link setColumns}.
- `constructor(items: GridPackItem[] = [], options: GridPackOptions = {})`
- `getItems(): readonly GridPackItem[]`
- `getItem(id: string): GridPackItem | undefined`
- `rows(): number` — Content height in rows: max(y+h) over all items (0 when empty).
- `hasOverlaps(): boolean` — True when any two items overlap — the invariant every op must preserve.
- `add(item: GridPackItem): GridPackItem | null` — Add an item. An explicit legal position is honoured verbatim. When the position collides — or the item asks for `autoPosition` — the tile AUTO-POSITIONS: a row-major scan for the first hole it fits, which is gridstack's `autoPosition` and NOT the same thing as gravity (gravity climbs one column; a hole at (3,0) under an occupied column is only reachable by the scan — the spec's first red proved it).
- `remove(id: string): void`
- `beginGesture(): void` — Begin a drag/resize gesture: fresh displaced-tile memory, and a full snapshot so `cancelGesture` (Escape) can restore every tile (gridstack `saveInitial` / `restoreInitial`).
- `endGesture(): void` — End a gesture: memory does NOT outlive it (E2, and the S4 swap lock).
- `cancelGesture(): void` — Escape: restore every tile to its gesture-start cell AND size, bring back the tiles removed during the gesture, drop the ones added, and restore the bound. Surviving tiles keep their object identity (a binder holds them).
- `setBound(rows: number): boolean` — Change the row bound — a container asking for rows on behalf of a child (tile first, step 1). Refused below the current content. Inside a gesture the change rides in the snapshot, so Escape gives the rows back.
- `moveCheck(id: string, x: number, y: number, options: MoveCheckOptions = {}): GridPackResult` — Try to put `id` at cell (x,y). Applies the full pipeline on acceptance: swap (three shapes) | push-down (+skip below locked) → settle (teleport memory + gravity). Refuses: out-of-gesture no-ops, cells intersecting a locked tile (E4b), and collisions under the anti-jitter coverage gate. `{ gate: false }` (see {@link MoveCheckOptions}) is first-placement mode: gate and swaps are skipped, push-down still applies, E4b still refuses.
- `resizeCheck(id: string, w: number, h: number, options: ResizeCheckOptions = {}): GridPackResult` — Resize `id` to w×h cells. Growth is CLAMPED so the tile never covers a locked tile (E4b applied to size); displaced neighbours push + settle, and return when the size shrinks back (E1/S2).
- `placeBeside(id: string, neighbourId: string, side: BesideSide, row?: number): PlaceBesideResult` — Put `id` NEXT TO `neighbourId` on `side`, at `row` (the pointer's row, clamped to the neighbour's rows; the neighbour's own row by default) — the one sideways primitive the tile-first drag model needs (step 1).
- `setColumns(next: number, layout: GridColumnLayout = 'moveScale'): boolean` — Change the board's COLUMN COUNT — gridstack's `column(n, layout)`, and the whole of responsive behaviour in one operation.
- `saveLayout(): { columns: number; items: GridPackItem[] }` — The layout to PERSIST — gridstack's `save()`, which serialises from the LARGEST cached column count rather than the live one. Saving while the board is narrow (a phone) therefore saves the DESKTOP layout: the cells a user authored at full width are the document, and the narrow arrangement is a projection of it.
- `cachedColumns(): number[]` — The column counts the cache currently holds a layout for (ascending).
- `getLayouts(): GridLayoutCache` — The cache as plain data — hand it to a rebuilt engine via {@link setLayouts}.
- `setLayouts(cache: GridLayoutCache): void` — Adopt a cache exported by {@link getLayouts}. The kit calls this after rebuilding its engine from the model (`sync()`), which would otherwise throw the wider layouts away on every undo, add or refresh.

## Interfaces

### `CachedCell`

One item's cached geometry at a column count. `h` is column-independent.

```ts
interface CachedCell
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `x` | `number` |  |  |
| `y` | `number` |  |  |
| `w` | `number` |  |  |

### `GridPackItem`

One tile, in integer grid cells.

```ts
interface GridPackItem
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` |  |  |
| `x` | `number` |  |  |
| `y` | `number` |  |  |
| `w` | `number` |  |  |
| `h` | `number` |  |  |
| `locked?` | `boolean` |  | Pinned: never pushed, never packed, refuses the mover outright (E4b). |
| `solid?` | `boolean` |  | SOLID (tile first, step 3): a container. Never pushed by a PASSING tile and never packed — a widget carried over it slides aside, so the board under the hand holds still and the container's zones stay reachable — but moved by INTENT: a mover that asks `pushSolid` (a moved section, a dock, a refused adoption), `placeBeside`, or a direct move of its own. |
| `minW?` | `number` |  | Per-item SIZE LIMITS in cells (gridstack's minW/maxW/minH/maxH). A resize clamps to them, and a column change scales a width only within them. They never move a tile: a push or a swap is not a resize. |
| `maxW?` | `number` |  |  |
| `minH?` | `number` |  |  |
| `maxH?` | `number` |  |  |
| `autoPosition?` | `boolean` |  | Ask add() to IGNORE x/y and scan row-major for the first free hole — gridstack's autoPosition (addWidget without coordinates). Distinct from gravity, which climbs one column: a hole at (3,0) under an occupied column is only reachable by the scan. Seeding/load honours explicit cells; palette-style adds pass this flag. |

### `GridPackOptions`

```ts
interface GridPackOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `columns?` | `number` |  | Column count of the board. Default 12. |
| `float?` | `boolean` |  | Float mode (gridstack `float: true`): tiles stay where placed and gaps are legal; gravity does not pack. Default false (gravity). |
| `maxRows?` | `number` |  | Row bound for a board whose DESIGN is a fixed strip (the nested KPI section: one row, always). Any op whose settled result would exceed it — a height resize, a width resize whose push spills a sibling down, a move displacing someone out of bounds — ROLLS BACK wholesale and reports `changed: false`; `add()` returns null when nothing can fit, so growing a first-row KPI can never push the strip past … |
| `capacity?` | `number` |  | Row CAPACITY of a board whose frame can hold only so many rows — the dashboard kit's bounded fit mode (a fit board never changes size; past its row floor it refuses rather than overflows). |

### `GridPackResult`

Result of a move/resize attempt.

```ts
interface GridPackResult
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `changed` | `boolean` |  | Whether the board accepted (and applied) the change. |
| `refusedBy?` | `GridPackRefusal` |  | Present on every refusal, absent on an accepted change. |

### `MoveCheckOptions`

Options for {@link GridPackEngine.moveCheck}.

```ts
interface MoveCheckOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `gate?` | `boolean` |  | The >50% anti-jitter coverage gate AND the swap heuristics it feeds. `gate: false` is the FIRST-PLACEMENT mode (a palette drag-in entering the board, or a dragged-out tile re-entering — gridstack's `dragInNode` behaviour): the cell is taken unconditionally — locked tiles still refuse (E4b), but any unlocked occupant is pushed down regardless of coverage, and no swap shape fires (an entering tile … |
| `pushSolid?` | `boolean` |  | Push SOLID tiles in the way as if they were plain — the mover means it (a moved section, a dock, a refused adoption). Default false. |

### `PlaceBesideResult`

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

Result of {@link GridPackEngine.placeBeside}.

```ts
interface PlaceBesideResult extends GridPackResult
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `how?` | `'placed' \| 'shifted' \| 'pushed'` |  | `placed`: the cell beside the neighbour was free (or only unlocked tiles were in it, pushed down); `shifted`: no room on that side of the board, the neighbour moved over by the mover's span and the mover took the edge; `pushed`: no room even shifted — the mover took the cell and the neighbour went down under it, the way any tile gives way. |

### `ResizeCheckOptions`

Options for {@link GridPackEngine.resizeCheck}.

```ts
interface ResizeCheckOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `pushSolid?` | `boolean` |  | Grow INTO solid tiles, pushing them, instead of clamping at them — a dock taking its band means it. Default false. |

## Types

### `BesideSide`

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

The side of a neighbour a tile is placed against.

```ts
type BesideSide = 'left' | 'right' | 'top' | 'bottom';
```

### `GridColumnLayout`

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

How a COLUMN-COUNT change re-lays the board out — gridstack's
`ColumnOptions`, recorded from its documented semantics:

'moveScale' (default) — scale both x and w by newColumns/oldColumns, so
               the board keeps its proportions at any width;
  'move'     — scale x only; widths survive verbatim (clamped to fit);
  'scale'    — scale w only; x survives verbatim (clamped to fit);
  'none'     — keep x and w exactly; clamp only what no longer fits.

Whatever the mode, `columns === 1` forces a SINGLE STACK (every tile x=0,
w=1) — the phone layout — and every mode re-places the result in reading
order afterwards, so the outcome can never overlap.

```ts
type GridColumnLayout = 'moveScale' | 'move' | 'scale' | 'none';
```

### `GridLayoutCache`

The PER-COLUMN LAYOUT CACHE, as plain data: column count → item id → cell. Exported/imported so a host that rebuilds its engine (the kit's `sync()`)
carries the wider layouts across the rebuild instead of losing them.

```ts
type GridLayoutCache = Record<number, Record<string, CachedCell>>;
```

### `GridPackRefusal`

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

Why a change was refused (tile first, step 1). `changed: false` alone can
also mean "already there", so every refusal names itself.

```ts
type GridPackRefusal = 'locked' | 'solid' | 'bound' | 'gate' | 'noop' | 'missing';
```
