@toclocoinc/lattice-grid 1.51.0 → 1.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +2 -1
  2. package/docs/API.html +259 -5
  3. package/docs/api-detail.html +121 -5
  4. package/lattice-grid.d.ts +501 -5
  5. package/lattice-grid.esm.min.js +288 -17
  6. package/lattice-grid.min.cjs +288 -17
  7. package/lattice-grid.min.js +288 -17
  8. package/modules/ai.esm.min.js +18 -4
  9. package/modules/ai.min.cjs +18 -4
  10. package/modules/ai.min.js +18 -4
  11. package/modules/angular.esm.min.js +2 -2
  12. package/modules/angular.min.cjs +2 -2
  13. package/modules/angular.min.js +2 -2
  14. package/modules/chart-alluvial.esm.min.js +1 -1
  15. package/modules/chart-arc.esm.min.js +1 -1
  16. package/modules/chart-bubblemap.esm.min.js +1 -1
  17. package/modules/chart-bump.esm.min.js +1 -1
  18. package/modules/chart-calendar.esm.min.js +1 -1
  19. package/modules/chart-decomposition.esm.min.js +1 -1
  20. package/modules/chart-diverging.esm.min.js +1 -1
  21. package/modules/chart-dumbbell.esm.min.js +1 -1
  22. package/modules/chart-fan.esm.min.js +1 -1
  23. package/modules/chart-hexbin.esm.min.js +1 -1
  24. package/modules/chart-hexmap.esm.min.js +1 -1
  25. package/modules/chart-icicle.esm.min.js +1 -1
  26. package/modules/chart-parallel.esm.min.js +1 -1
  27. package/modules/chart-ridgeline.esm.min.js +1 -1
  28. package/modules/chart-roc.esm.min.js +1 -1
  29. package/modules/chart-slope.esm.min.js +1 -1
  30. package/modules/chart-splom.esm.min.js +1 -1
  31. package/modules/chart-waffle.esm.min.js +1 -1
  32. package/modules/charts.esm.min.js +255 -83
  33. package/modules/charts.min.cjs +255 -83
  34. package/modules/charts.min.js +255 -83
  35. package/modules/data-router.esm.min.js +4 -4
  36. package/modules/data-router.min.cjs +4 -4
  37. package/modules/data-router.min.js +4 -4
  38. package/modules/devtools.esm.min.js +2 -2
  39. package/modules/devtools.min.cjs +2 -2
  40. package/modules/devtools.min.js +2 -2
  41. package/modules/dhtmlx-compat.esm.min.js +4 -4
  42. package/modules/dhtmlx-compat.min.cjs +4 -4
  43. package/modules/dhtmlx-compat.min.js +4 -4
  44. package/modules/gantt.esm.min.js +359 -62
  45. package/modules/gantt.min.cjs +359 -62
  46. package/modules/gantt.min.js +359 -62
  47. package/modules/htmx.esm.min.js +288 -17
  48. package/modules/htmx.min.cjs +288 -17
  49. package/modules/htmx.min.js +288 -17
  50. package/modules/kanban.esm.min.js +4 -4
  51. package/modules/kanban.min.cjs +4 -4
  52. package/modules/kanban.min.js +4 -4
  53. package/modules/kpi.esm.min.js +886 -41
  54. package/modules/kpi.min.cjs +886 -41
  55. package/modules/kpi.min.js +886 -41
  56. package/modules/layout.esm.min.js +1825 -0
  57. package/modules/layout.min.cjs +1828 -0
  58. package/modules/layout.min.js +1828 -0
  59. package/modules/mock-socket.esm.min.js +2 -2
  60. package/modules/mock-socket.min.cjs +2 -2
  61. package/modules/mock-socket.min.js +2 -2
  62. package/modules/react.esm.min.js +2 -2
  63. package/modules/react.min.cjs +2 -2
  64. package/modules/react.min.js +2 -2
  65. package/modules/svelte.esm.min.js +2 -2
  66. package/modules/svelte.min.cjs +2 -2
  67. package/modules/svelte.min.js +2 -2
  68. package/modules/tabs.esm.min.js +89 -14
  69. package/modules/tabs.min.cjs +89 -14
  70. package/modules/tabs.min.js +89 -14
  71. package/modules/vue.esm.min.js +2 -2
  72. package/modules/vue.min.cjs +2 -2
  73. package/modules/vue.min.js +2 -2
  74. package/modules/webcomponent.esm.min.js +288 -17
  75. package/modules/webcomponent.min.cjs +288 -17
  76. package/modules/webcomponent.min.js +288 -17
  77. package/package.json +1 -1
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.51.0, type declarations
2
+ * Lattice Grid 1.53.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -1327,6 +1327,52 @@ export interface DerivedSourceConfig {
1327
1327
  /** With `profile`, emit one row per statistic instead of one per column. */
1328
1328
  orient?: 'columns' | 'metrics';
1329
1329
 
1330
+ /**
1331
+ * Project a **relational** statistic into rows (BACKLOG-0001046): the figures
1332
+ * that need two or more columns, or a second grid, and so cannot be reached
1333
+ * through `select`.
1334
+ *
1335
+ * Every *single-column* statistic already has a route and this is not it —
1336
+ * the derived `select` reduces a group by any kernel the totals row uses, and
1337
+ * that table is a superset of the statistics one, so
1338
+ * `select: { p95: { of: 'amount', fn: 'p95' } }` (or `gini`, `stddev`,
1339
+ * `median`, `trimmedMean`, …) works today. Reach for `statistics` only when
1340
+ * the answer is a correlation, a series summary or a comparison against
1341
+ * another dataset.
1342
+ *
1343
+ * **A terminal producer, like `profile`, not a pipeline stage.** A
1344
+ * correlation is one row per column *pair*, a series summary one row per
1345
+ * *metric*, a comparison one row per compared *column* — none of which is one
1346
+ * row per group, so there is no position in
1347
+ * `unnest → where → bucket → groupBy → select → sort → limit` for it to
1348
+ * occupy. It replaces the pipeline, and those keys are ignored with a warning
1349
+ * naming them (BACKLOG-0001092) rather than silently discarded. Sort, filter
1350
+ * or limit the derived grid itself instead, or chain a second derived grid
1351
+ * whose `from` is this one.
1352
+ *
1353
+ * **`profile` and `statistics` are mutually exclusive** and declaring both is
1354
+ * refused, by name, when the source is built. **Not supported alongside a
1355
+ * union `from`** — a relational statistic reduces one grid's own columns and a
1356
+ * union has no single set of them; also refused by name.
1357
+ *
1358
+ * **Cost.** Like every terminal producer this never patches incrementally: a
1359
+ * change on the parent re-derives the whole thing. `correlation` additionally
1360
+ * scans the rows once *per pair*, so N columns cost N·(N−1)/2 passes. Use
1361
+ * `refresh` (`'idle'` is the default; `'manual'` or a debounce in ms for an
1362
+ * expensive analysis over a live feed) — see `docs/api-detail.html` for the
1363
+ * measured figures.
1364
+ *
1365
+ * Every row carries `n`, the rows the figure covered, because a derived
1366
+ * statistic travels into an export or a chart without its grid and "r = 0.98
1367
+ * over eleven rows" is a different claim from the same number over eleven
1368
+ * thousand. It does NOT carry a windowed/approximate flag: whether a source
1369
+ * held fewer rows than matched its filters is decided from the source's own
1370
+ * counters, which a derived source cannot reach, so that signal stays where
1371
+ * it already works - the `stat.windowed:*` console warning the parent grid
1372
+ * emits.
1373
+ */
1374
+ statistics?: DerivedStatistics;
1375
+
1330
1376
  /** When to re-derive. `idle` by default: coalesced to a frame. */
1331
1377
  refresh?: 'live' | 'idle' | 'manual' | number;
1332
1378
 
@@ -1337,6 +1383,86 @@ export interface DerivedSourceConfig {
1337
1383
  crossFilter?: boolean | string | { col?: string };
1338
1384
  }
1339
1385
 
1386
+ /**
1387
+ * Which relational statistic a derived source projects into rows, and how
1388
+ * (BACKLOG-0001046). See `DerivedSourceConfig.statistics`.
1389
+ *
1390
+ * A discriminated union on `fn`, so the relational statistics still deferred —
1391
+ * `regression`, `regressionModel`, `forecast`, `anomalies`, `adf`, `acf`,
1392
+ * `spearman`, `kendall`, `covariance`, `subsetVsPopulation`, `compareGroups`,
1393
+ * `capability`, `interval`, `windowed`, `weightedQuantile`, `weightedAverage` —
1394
+ * arrive as further arms of this one key rather than as a second mechanism.
1395
+ */
1396
+ export type DerivedStatistics =
1397
+ | DerivedCorrelation
1398
+ | DerivedSeries
1399
+ | DerivedDatasetComparison;
1400
+
1401
+ /**
1402
+ * Pearson's correlation across N columns, pairwise.
1403
+ *
1404
+ * Rows, `orient: 'pairs'` (the default): one per unordered pair,
1405
+ * `{ a, b, coefficient, n }` — the long form, because that is what
1406
+ * a grid sorts, filters and charts well, and "the three most correlated pairs"
1407
+ * is then a sort and a `limit` on the derived grid. Only the upper triangle is
1408
+ * emitted: r is symmetric, so `(a,b)` and `(b,a)` are one finding, and a column
1409
+ * against itself is 1 by definition.
1410
+ *
1411
+ * Rows, `orient: 'matrix'`: one per column, carrying a field per other column
1412
+ * plus `column` and `n` — the classic square, for a heat map.
1413
+ * The diagonal is 1 and both triangles are filled.
1414
+ */
1415
+ export interface DerivedCorrelation {
1416
+ fn: 'correlation';
1417
+ /** The columns to correlate pairwise. At least two, or the source is refused. */
1418
+ columns: string[];
1419
+ /** `pairs` (default) for one row per pair; `matrix` for the square. */
1420
+ orient?: 'pairs' | 'matrix';
1421
+ }
1422
+
1423
+ /**
1424
+ * A `grid.statistics.series` summary, as one row per metric:
1425
+ * `{ metric, value, n }`.
1426
+ *
1427
+ * One row per *metric*, not per point: `series` returns a `SeriesStats` summary
1428
+ * object — `n`, `first`, `last`, `change`, `changePercent`, `volatility`,
1429
+ * `annualisedVolatility`, `growth`, `maxDrawdown`, `maxDrawdownFrom`,
1430
+ * `maxDrawdownTo`, `autocorrelation`, `upDays`, `downDays` — and not a value
1431
+ * per row. The shape is deliberately the one `profile`'s `orient: 'metrics'`
1432
+ * already emits rather than a third convention for the same idea.
1433
+ */
1434
+ export interface DerivedSeries {
1435
+ fn: 'series';
1436
+ /** The column to summarise. */
1437
+ of: string;
1438
+ /** The column that orders it. Required and never guessed. */
1439
+ by: string;
1440
+ /** Annualise volatility and growth against this many periods per year. */
1441
+ periodsPerYear?: number;
1442
+ }
1443
+
1444
+ /**
1445
+ * How this grid differs from another, ranked by effect size, as rows:
1446
+ * `{ column, measure, magnitude, distance, direction, nA, nB, reliable,
1447
+ * unmatched }`, largest difference first.
1448
+ *
1449
+ * The two-grid shape: one grid is the data, a second *is* the analysis of it.
1450
+ * Both sides are read over their filtered rows, and the peer is watched — an
1451
+ * edit or a filter on it re-derives the comparison, because a comparison whose
1452
+ * other side has moved is wrong rather than merely late.
1453
+ *
1454
+ * A column present on only one side cannot be compared. It is still reported,
1455
+ * as a row with a null `magnitude` and `unmatched` set to `'A'` or `'B'`, so a
1456
+ * reader sees that it was skipped and why rather than finding it absent.
1457
+ */
1458
+ export interface DerivedDatasetComparison {
1459
+ fn: 'datasetVsDataset';
1460
+ /** The second grid to compare this one against. */
1461
+ with: Grid;
1462
+ /** Restrict the comparison to these columns. All shared columns by default. */
1463
+ columns?: string[];
1464
+ }
1465
+
1340
1466
  /**
1341
1467
  * One member of a union `from` (BACKLOG-0001045): a grid to combine with the
1342
1468
  * others, plus how to read it and reshape it before it joins the rest. A bare
@@ -7353,11 +7479,17 @@ declare module 'lattice-grid/modules/gantt' {
7353
7479
  /**
7354
7480
  * A typed dependency between two tasks (by id), with optional lag/lead. `type`
7355
7481
  * defaults to `'FS'`; either endpoint may be a leaf or a summary.
7482
+ *
7483
+ * `type` also accepts the MS Project string shorthand — `'FS+2'`, `'SS-1'`
7484
+ * (BACKLOG-0001072). It is normalised to the structured form on the way in, so
7485
+ * `gantt.dependencies` always reads back `{ type, lag }` and there is no second
7486
+ * internal representation. Giving both a shorthand lag and a conflicting `lag`
7487
+ * field warns; the explicit field wins.
7356
7488
  */
7357
7489
  export interface GanttDependency {
7358
7490
  from: string | number;
7359
7491
  to: string | number;
7360
- type?: GanttLinkType;
7492
+ type?: GanttLinkType | `${GanttLinkType}${'+' | '-'}${number}`;
7361
7493
  lag?: number;
7362
7494
  }
7363
7495
 
@@ -7600,7 +7732,15 @@ declare module 'lattice-grid/modules/gantt' {
7600
7732
  * milestones, progress). The view redraws when the schedule recomputes.
7601
7733
  */
7602
7734
  mount(container: unknown, options?: {
7603
- width?: number;
7735
+ /**
7736
+ * The plot width. `'container'` (the default) measures the element it was
7737
+ * mounted into and keeps following it, so a plan in a tab, drawer,
7738
+ * accordion or split pane fits without the host writing a
7739
+ * `ResizeObserver` (BACKLOG-0001079); a container with no box yet holds a
7740
+ * 720px fallback rather than drawing at zero. A number is honoured
7741
+ * exactly and installs no observer. Ignored under `zoom`, which warns.
7742
+ */
7743
+ width?: number | 'container';
7604
7744
  rowHeight?: number;
7605
7745
  labelWidth?: number;
7606
7746
  rowLabels?: boolean;
@@ -7608,7 +7748,23 @@ declare module 'lattice-grid/modules/gantt' {
7608
7748
  showCritical?: boolean;
7609
7749
  showProgress?: boolean;
7610
7750
  dateAxis?: boolean;
7611
- today?: number;
7751
+ /**
7752
+ * The today line, as a plan day-number or a calendar date. A date is
7753
+ * converted into plan space through `projectEpoch` (BACKLOG-0001079), so
7754
+ * "put the line on the real today" is expressible for a relative plan.
7755
+ */
7756
+ today?: number | string | Date;
7757
+ /**
7758
+ * The calendar date plan day 0 stands for (BACKLOG-0001079).
7759
+ *
7760
+ * Display-only: axis ticks, bar labels, tooltips, screen-reader text and
7761
+ * the built-in `'weekends'` shading move with it; the schedule, `getState`
7762
+ * and the CSV/MSPDI exports do not. Without it, the engine's contract makes
7763
+ * day 0 the Unix epoch, which is why a plan written as day offsets renders
7764
+ * as January 1970. A host-supplied `nonWorking` function still receives raw
7765
+ * plan days.
7766
+ */
7767
+ projectEpoch?: number | string | Date | null;
7612
7768
  nonWorking?: 'weekends' | ((day: number) => boolean);
7613
7769
  label?: 'name' | 'percent' | 'dates' | 'none' | ((task: GanttScheduledTask) => string);
7614
7770
  /** Whether bars can be dragged to move/resize (default true). */
@@ -8481,6 +8637,75 @@ declare module 'lattice-grid/modules/kpi' {
8481
8637
  sparkline?: KPISparkline | string;
8482
8638
  }
8483
8639
 
8640
+ /**
8641
+ * The hierarchy a KPI panel arranges its tiles into (BACKLOG-0001059): a rail
8642
+ * of top-level items that expand to the indicators beneath them, each parent
8643
+ * highlighted with the worst status below it.
8644
+ *
8645
+ * The shape is declared with `path` or `parentKey` — the same two shapes the
8646
+ * grid's tree data and the tree-select editor take — over the **tile specs**,
8647
+ * not the rows. With neither declared, one is derived by splitting the tile
8648
+ * ids on `separator`, so `system.compute.cpu` files itself under Compute
8649
+ * under System. A panel whose ids carry no separator stays flat, and `false`
8650
+ * keeps it flat whatever they look like.
8651
+ *
8652
+ * A tile's `field` is never a source: a dot there already means a nested
8653
+ * object property.
8654
+ */
8655
+ interface KPITreeConfig {
8656
+ /** The tile's own place in the hierarchy, its own segment last. */
8657
+ path?: (tile: KPITile) => (string | number)[];
8658
+ /** The id of the tile this one sits under, or a reader for it. */
8659
+ parentKey?: string | ((tile: KPITile) => unknown);
8660
+ /** The heading tiles whose parent is not in the panel are gathered under. */
8661
+ orphans?: 'root' | string;
8662
+ /** The separator a derived hierarchy splits a tile id on. Defaults to `.`. */
8663
+ separator?: string;
8664
+ /** Which branches start open: every one (`true`), or these node keys. */
8665
+ expanded?: true | string[];
8666
+ }
8667
+
8668
+ /**
8669
+ * One node of the rail.
8670
+ *
8671
+ * **No value rolls up.** `value` and `formatted` are the node's own tile's
8672
+ * reading, and are `null` on a level the hierarchy synthesised, because the
8673
+ * running accumulators cannot be composed without a rescan.
8674
+ *
8675
+ * **Severity does.** `rollup` is the worst status at or below the node, which
8676
+ * is what a collapsed branch reports. `unknown` is excluded from it on
8677
+ * purpose — ranking "nothing was measured" as the worst would hide a real
8678
+ * warning underneath it — and is surfaced as `unknown`, a count of the
8679
+ * descendants that measured nothing, so neither can pass unnoticed.
8680
+ */
8681
+ interface KPINodeModel {
8682
+ /** The node's stable identity: the tile id, or the path of a synthesised level. */
8683
+ key: string;
8684
+ /** The tile id, or null on a synthesised level. */
8685
+ id: string | null;
8686
+ label: string;
8687
+ /** Depth, 0 at the top level. */
8688
+ level: number;
8689
+ /** Its place among its siblings, from 1, and how many there are. */
8690
+ posinset: number;
8691
+ setsize: number;
8692
+ hasChildren: boolean;
8693
+ expanded: boolean;
8694
+ children: KPINodeModel[];
8695
+ /** The node's own tile, or null on a synthesised level. */
8696
+ tile: KPITileModel | null;
8697
+ value: unknown;
8698
+ formatted: string | null;
8699
+ /** The node's own status. */
8700
+ status: 'good' | 'warn' | 'critical' | 'unknown' | null;
8701
+ /** The worst status at or below the node. Never `unknown`. */
8702
+ rollup: 'good' | 'warn' | 'critical' | null;
8703
+ /** How many tiles at or below the node measured nothing. */
8704
+ unknown: number;
8705
+ /** How many tiles are at or below the node. */
8706
+ items: number;
8707
+ }
8708
+
8484
8709
  /** A computed tile, as it appears in the model. */
8485
8710
  interface KPITileModel {
8486
8711
  id: string;
@@ -8525,10 +8750,20 @@ declare module 'lattice-grid/modules/kpi' {
8525
8750
  columns?: number;
8526
8751
  ariaLabel?: string;
8527
8752
  nullText?: string;
8753
+ /** Arrange the tiles as a hierarchy; `false` keeps the panel flat. */
8754
+ tree?: KPITreeConfig | false;
8755
+ /**
8756
+ * The catalogue the panel's own text is read from. A panel routinely has no
8757
+ * grid to borrow one off — two of its three input modes have none — so this
8758
+ * is the first-class way to translate it. A grid's own `messages` satisfies
8759
+ * the shape; a key it does not carry falls back to English.
8760
+ */
8761
+ messages?: { t(key: string, params?: Record<string, unknown>): string };
8528
8762
  onTileClick?: (event: KPIEvent) => void;
8529
8763
  onTileDblClick?: (event: KPIEvent) => void;
8530
8764
  onTileContextMenu?: (event: KPIEvent) => void;
8531
- onChange?: (event: { model: { tiles: KPITileModel[] } }) => void;
8765
+ onNodeToggle?: (event: { key: string; expanded: boolean; node?: KPINodeModel }) => void;
8766
+ onChange?: (event: { model: { tiles: KPITileModel[]; nodes?: KPINodeModel[] } }) => void;
8532
8767
  }
8533
8768
 
8534
8769
  /** The keyed-diff consumer surface a KPI panel shares with a grid, so a Data Router routes to it directly. */
@@ -8547,10 +8782,21 @@ declare module 'lattice-grid/modules/kpi' {
8547
8782
  interface KPI {
8548
8783
  readonly el: unknown | null;
8549
8784
  readonly rowKey: string | ((row: KPIRow) => unknown);
8785
+ /** Whether the panel renders as a hierarchy rather than a flat tile grid. */
8786
+ readonly tree: boolean;
8550
8787
  rows: KPIRows;
8551
8788
  tiles(): KPITileModel[];
8552
8789
  tile(id: string): KPITileModel | undefined;
8553
8790
  value(id: string): unknown;
8791
+ /** The top-level nodes of the hierarchy. Empty on a flat panel. */
8792
+ nodes(): KPINodeModel[];
8793
+ /** One node by its key, at any depth. */
8794
+ node(key: string): KPINodeModel | undefined;
8795
+ /** The nodes on screen: the roots, plus the children of every open branch. */
8796
+ visibleNodes(): KPINodeModel[];
8797
+ expand(key: string): KPI;
8798
+ collapse(key: string): KPI;
8799
+ toggle(key: string): KPI;
8554
8800
  setRows(rows: KPIRow[]): KPI;
8555
8801
  refresh(): KPI;
8556
8802
  getState(): object;
@@ -9072,3 +9318,253 @@ declare module 'lattice-grid/modules/tabs' {
9072
9318
  export function createTabs(el: HTMLElement, config: TabsConfig): Tabs;
9073
9319
  export default createTabs;
9074
9320
  }
9321
+
9322
+ declare module 'lattice-grid/modules/layout' {
9323
+ /**
9324
+ * One window on the cell grid.
9325
+ *
9326
+ * Deliberately **not** named `WindowSpec`: that name is already taken by the
9327
+ * rolling-statistics window (`{ kind: 'count'|'time'|'session', span, size }`)
9328
+ * and reusing it would put `kind: 'session'` next to a dashboard pane.
9329
+ */
9330
+ interface LayoutWindow {
9331
+ /** A stable, unique id. Required. */
9332
+ id: string;
9333
+ /** The 1-based column the window starts in. Auto-placed when omitted. */
9334
+ xPos?: number;
9335
+ /** The 1-based row the window starts in. Auto-placed when omitted. */
9336
+ yPos?: number;
9337
+ /** How many columns it spans (default 1). */
9338
+ xSize?: number;
9339
+ /** How many rows it spans (default 1). */
9340
+ ySize?: number;
9341
+ /** The title shown in the chrome bar, and the name every control takes. */
9342
+ title?: string;
9343
+ /** Whether to draw the title bar (default `true`). */
9344
+ chrome?: boolean;
9345
+ /** Whether to offer a close button (default `false`). */
9346
+ closable?: boolean;
9347
+ /** Whether the window can be moved by drag or keyboard (default `false`). */
9348
+ movable?: boolean;
9349
+ /** Whether the window can be resized by drag or keyboard (default `false`). */
9350
+ resizable?: boolean;
9351
+ /** Padding inside the window; the layout's `padding` (default `'5px'`) otherwise. */
9352
+ padding?: number | string;
9353
+ /** The `id` given to the payload container (default `` `${id}-body` ``). */
9354
+ payloadId?: string;
9355
+ /** The window's accessible name, when the title alone is not enough context. */
9356
+ ariaLabel?: string;
9357
+ }
9358
+
9359
+ /**
9360
+ * The three capabilities a layout-level default and `setInteractive()` cover.
9361
+ *
9362
+ * These are the layout **defaults**, not the per-window resolution: a window
9363
+ * that declared `movable: false` stays pinned whatever these say.
9364
+ *
9365
+ * Three values, not two. `undefined` means no layout-level default is in force
9366
+ * and each window's own flag decides; `true` unlocks everything that did not
9367
+ * opt out; `false` is an active lock. Reporting `undefined` as `false` would
9368
+ * read correctly and round-trip wrongly, so it is reported as it is.
9369
+ */
9370
+ interface LayoutInteractive {
9371
+ movable: boolean | undefined;
9372
+ resizable: boolean | undefined;
9373
+ closable: boolean | undefined;
9374
+ }
9375
+
9376
+ /** The plain, JSON-safe arrangement `getLayout()` returns and `setLayout()` takes. */
9377
+ interface LayoutSnapshot {
9378
+ columns: number;
9379
+ rows: number;
9380
+ windows: { id: string; xPos: number; yPos: number; xSize: number; ySize: number }[];
9381
+ }
9382
+
9383
+ /** A cell placement, as carried on the move and resize events. */
9384
+ interface LayoutPlacement {
9385
+ xPos: number;
9386
+ yPos: number;
9387
+ xSize: number;
9388
+ ySize: number;
9389
+ }
9390
+
9391
+ /** The payload of `window:moved`, `beforeWindowMove`, `beforeWindowResize`. */
9392
+ interface LayoutMoveEvent {
9393
+ id: string;
9394
+ from: LayoutPlacement;
9395
+ /** Where the window was asked to go. */
9396
+ to: LayoutPlacement;
9397
+ /** Where it actually ended up, which under `compact: 'vertical'` may differ. */
9398
+ landed?: LayoutPlacement;
9399
+ origin?: 'api' | 'user' | 'init';
9400
+ reason?: string | null;
9401
+ /** Cancel the action (only meaningful on a `before*` event). */
9402
+ preventDefault?: (reason?: string) => void;
9403
+ defaultPrevented?: boolean;
9404
+ }
9405
+
9406
+ /**
9407
+ * The payload of `window:resized` — the measured **content box** of the
9408
+ * payload container, not a cell count. Emitted when the container genuinely
9409
+ * changes size, including on the opening frame; never with a zero box.
9410
+ */
9411
+ interface LayoutResizeEvent {
9412
+ id: string;
9413
+ payloadId: string;
9414
+ /** The payload container itself, so a host can act on it directly. */
9415
+ payload: HTMLElement;
9416
+ width: number;
9417
+ height: number;
9418
+ xPos: number;
9419
+ yPos: number;
9420
+ xSize: number;
9421
+ ySize: number;
9422
+ }
9423
+
9424
+ /** The payload of `window:closed` and `beforeWindowClose`. */
9425
+ interface LayoutCloseEvent {
9426
+ id: string;
9427
+ payloadId: string;
9428
+ /** The payload container, handed back so the host can destroy what it mounted. */
9429
+ payload?: HTMLElement;
9430
+ origin?: 'api' | 'user';
9431
+ reason?: string | null;
9432
+ preventDefault?: (reason?: string) => void;
9433
+ defaultPrevented?: boolean;
9434
+ }
9435
+
9436
+ /** The payload of `layout:changed`: the whole arrangement, plus what moved it. */
9437
+ interface LayoutChangedEvent extends LayoutSnapshot {
9438
+ cause: string;
9439
+ }
9440
+
9441
+ /** Dashboard layout configuration. */
9442
+ interface LayoutConfig {
9443
+ /** Cell columns across the mounted element (default 12). */
9444
+ columns?: number;
9445
+ /** Cell rows down the mounted element (default 6). */
9446
+ rows?: number;
9447
+ /** Horizontal overflow (default `'static'`). */
9448
+ overflowX?: 'static' | 'scroll';
9449
+ /** Vertical overflow (default `'static'`). */
9450
+ overflowY?: 'static' | 'scroll';
9451
+ /** Fixed column track size, used only when `overflowX` is `'scroll'` (default `'240px'`). */
9452
+ columnWidth?: number | string;
9453
+ /** Fixed row track size, used only when `overflowY` is `'scroll'` (default `'160px'`). */
9454
+ rowHeight?: number | string;
9455
+ /** The gap between cells (default `'8px'`). */
9456
+ gap?: number | string;
9457
+ /** The default padding inside a window (default `'5px'`). */
9458
+ padding?: number | string;
9459
+ /** Rearrangement (default `'vertical'`): push displaced windows down, then pull up. */
9460
+ compact?: 'vertical' | 'none';
9461
+ /**
9462
+ * The default `movable` for every window that does not declare its own
9463
+ * (default `false`). This states a default, so `false` takes nothing away
9464
+ * from a window that declared `movable: true`; `setInteractive(false)` is
9465
+ * the active lock that does.
9466
+ */
9467
+ movable?: boolean;
9468
+ /** The default `resizable` for windows that declare none (default `false`); see `movable`. */
9469
+ resizable?: boolean;
9470
+ /** The default `closable` for windows that declare none (default `false`); see `movable`. */
9471
+ closable?: boolean;
9472
+ /** The windows, in mount order. */
9473
+ windows?: LayoutWindow[];
9474
+ /** An arrangement to apply at mount, as produced by `getLayout()`. */
9475
+ layout?: LayoutSnapshot;
9476
+ /** The layout region's accessible name. */
9477
+ ariaLabel?: string;
9478
+ /** A message catalogue, e.g. `grid.messages`; built-in English seeds otherwise. */
9479
+ messages?: { t(key: string, params?: Record<string, unknown>): string };
9480
+ onWindowMoved?: (event: LayoutMoveEvent) => void;
9481
+ onWindowResized?: (event: LayoutResizeEvent) => void;
9482
+ onWindowClosed?: (event: LayoutCloseEvent) => void;
9483
+ onLayoutChanged?: (event: LayoutChangedEvent) => void;
9484
+ onBeforeWindowMove?: (event: LayoutMoveEvent) => boolean | void | Promise<boolean>;
9485
+ onBeforeWindowResize?: (event: LayoutMoveEvent) => boolean | void | Promise<boolean>;
9486
+ onBeforeWindowClose?: (event: LayoutCloseEvent) => boolean | void | Promise<boolean>;
9487
+ onWindowMoveCancelled?: (event: LayoutMoveEvent) => void;
9488
+ onWindowResizeCancelled?: (event: LayoutMoveEvent) => void;
9489
+ onWindowCloseCancelled?: (event: LayoutCloseEvent) => void;
9490
+ }
9491
+
9492
+ /**
9493
+ * A reconfigurable dashboard: a cell grid inside an element, and a set of
9494
+ * windows on it that a user can move, resize and close by pointer or by
9495
+ * keyboard (BACKLOG-0001108).
9496
+ *
9497
+ * The module is **payload-agnostic**: a window body is a container with an id,
9498
+ * which this module creates and sizes and never reads. It tells a payload it
9499
+ * was resized by emitting `window:resized`; it never calls into one, because it
9500
+ * cannot know what one is.
9501
+ */
9502
+ interface Layout {
9503
+ readonly el: HTMLElement;
9504
+ /** The window ids, in mount order. */
9505
+ windows(): string[];
9506
+ /** The payload container for a window, or `null`. */
9507
+ payload(id: string): HTMLElement | null;
9508
+ /** A copy of one window's current descriptor, or `null`. */
9509
+ window(id: string): LayoutWindow | null;
9510
+ /** Add a window after mount; returns its payload container. */
9511
+ add(spec: LayoutWindow): HTMLElement;
9512
+ /** Move or resize a window, through the same before-events the drag uses. */
9513
+ move(id: string, to: Partial<LayoutPlacement>): boolean | Promise<boolean>;
9514
+ /** Close a window through `beforeWindowClose`; the payload is not destroyed. */
9515
+ close(id: string): boolean | Promise<boolean>;
9516
+ /** The full current arrangement. */
9517
+ getLayout(): LayoutSnapshot;
9518
+ /** Restore an arrangement; never throws on garbage. */
9519
+ setLayout(incoming: LayoutSnapshot | LayoutWindow[]): number;
9520
+ /** A versioned snapshot, following core's and gantt's shape. */
9521
+ getState(): { version: number; layout: LayoutSnapshot };
9522
+ /** Restore a `getState()` snapshot; never throws on garbage. */
9523
+ setState(snapshot: unknown): number;
9524
+ /**
9525
+ * Lock or unlock the dashboard at runtime — the "Edit layout" button. A
9526
+ * boolean sets all three capabilities; an object sets only the keys it
9527
+ * carries. Nothing is destroyed, so every payload survives the toggle.
9528
+ *
9529
+ * The asymmetry is deliberate: **you can always take a capability away; you
9530
+ * can never grant one where the developer said no.** `setInteractive(false)`
9531
+ * locks every window, including one whose own spec says `movable: true`;
9532
+ * `setInteractive(true)` unlocks only the windows that never opted out.
9533
+ *
9534
+ * `config.movable: false` and `setInteractive(false)` are deliberately not
9535
+ * the same thing: the config states the *default* for windows that declare
9536
+ * nothing (and `false` is already that default, so it takes nothing away from
9537
+ * a window that opted in), while this is an *active lock*.
9538
+ *
9539
+ * A key carrying `undefined` is treated as absent, so
9540
+ * `setInteractive(getInteractive())` is a no-op in every state.
9541
+ *
9542
+ * A locked layout is not a read-only dashboard: this module never reads or
9543
+ * writes a payload, so a grid inside a window is made read-only with the
9544
+ * grid's own settings.
9545
+ */
9546
+ setInteractive(value: boolean | Partial<LayoutInteractive>): LayoutInteractive;
9547
+ /**
9548
+ * The layout-level interactivity now in force, as a copy — `undefined` where
9549
+ * no layout-level default is set, so the result round-trips through
9550
+ * `setInteractive`.
9551
+ */
9552
+ getInteractive(): LayoutInteractive;
9553
+ /** Re-measure every window and emit `window:resized` for those that changed. */
9554
+ refresh(): number;
9555
+ on(
9556
+ name: 'window:moved' | 'window:resized' | 'window:closed' | 'layout:changed'
9557
+ | 'beforeWindowMove' | 'beforeWindowResize' | 'beforeWindowClose'
9558
+ | 'windowMove:cancelled' | 'windowResize:cancelled' | 'windowClose:cancelled'
9559
+ | '*' | string,
9560
+ fn: (event: any) => unknown,
9561
+ ): () => void;
9562
+ off(name: string, fn: (event: any) => unknown): void;
9563
+ /** Tear the layout down; whatever the host mounted in a payload is the host's to destroy. */
9564
+ destroy(): void;
9565
+ }
9566
+
9567
+ /** Create a reconfigurable dashboard layout over a host element. */
9568
+ export function createLayout(el: HTMLElement, config?: LayoutConfig): Layout;
9569
+ export default createLayout;
9570
+ }