@toclocoinc/lattice-grid 1.50.0 → 1.52.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 (74) hide show
  1. package/README.md +1 -1
  2. package/docs/API.html +477 -11
  3. package/docs/api-detail.html +348 -1
  4. package/lattice-grid.d.ts +399 -10
  5. package/lattice-grid.esm.min.js +619 -69
  6. package/lattice-grid.min.cjs +619 -69
  7. package/lattice-grid.min.js +619 -69
  8. package/modules/ai.esm.min.js +4 -4
  9. package/modules/ai.min.cjs +4 -4
  10. package/modules/ai.min.js +4 -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 +181 -87
  33. package/modules/charts.min.cjs +181 -87
  34. package/modules/charts.min.js +181 -87
  35. package/modules/data-router.esm.min.js +7 -5
  36. package/modules/data-router.min.cjs +7 -5
  37. package/modules/data-router.min.js +7 -5
  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 +300 -62
  45. package/modules/gantt.min.cjs +300 -62
  46. package/modules/gantt.min.js +300 -62
  47. package/modules/htmx.esm.min.js +619 -69
  48. package/modules/htmx.min.cjs +619 -69
  49. package/modules/htmx.min.js +619 -69
  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 +752 -35
  54. package/modules/kpi.min.cjs +752 -35
  55. package/modules/kpi.min.js +752 -35
  56. package/modules/mock-socket.esm.min.js +2 -2
  57. package/modules/mock-socket.min.cjs +2 -2
  58. package/modules/mock-socket.min.js +2 -2
  59. package/modules/react.esm.min.js +2 -2
  60. package/modules/react.min.cjs +2 -2
  61. package/modules/react.min.js +2 -2
  62. package/modules/svelte.esm.min.js +2 -2
  63. package/modules/svelte.min.cjs +2 -2
  64. package/modules/svelte.min.js +2 -2
  65. package/modules/tabs.esm.min.js +94 -12
  66. package/modules/tabs.min.cjs +94 -12
  67. package/modules/tabs.min.js +94 -12
  68. package/modules/vue.esm.min.js +2 -2
  69. package/modules/vue.min.cjs +2 -2
  70. package/modules/vue.min.js +2 -2
  71. package/modules/webcomponent.esm.min.js +619 -69
  72. package/modules/webcomponent.min.cjs +619 -69
  73. package/modules/webcomponent.min.js +619 -69
  74. package/package.json +1 -1
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.50.0, type declarations
2
+ * Lattice Grid 1.52.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -863,6 +863,24 @@ export interface Column {
863
863
  layout?: ColumnLayoutSpec | number;
864
864
  /** The header cell: its text, tooltip, menu and any header chart. */
865
865
  header?: ColumnHeaderSpec | string;
866
+ /**
867
+ * The cell right-click menu for this column alone (BACKLOG-0001068), in the
868
+ * same shapes the grid-level `contextMenu` takes plus a bare array for the
869
+ * common "just these items here" case.
870
+ *
871
+ * Declared where the column is declared rather than as another branch inside
872
+ * one grid-level callback: the menu logic for a column belongs beside the
873
+ * column it belongs to. It does not replace the grid-level menu — the three
874
+ * levels compose as a chain, built-in defaults then grid-level then this one,
875
+ * each handed the previous result as its `defaults`, so a column adding one
876
+ * item does not have to restate Paste, Clear and Fill down.
877
+ *
878
+ * `false` suppresses the menu on this column and leaves every other column
879
+ * alone: what a sensitive or read-only column wants. The more specific level
880
+ * wins, so a column may also declare a menu on a grid whose `contextMenu` is
881
+ * `false`.
882
+ */
883
+ contextMenu?: boolean | MenuItem[] | ((p: CellMenuParams, defaults: MenuItem[]) => MenuItem[] | void);
866
884
  /**
867
885
  * When this column's header controls — its sort arrow, filter funnel and menu
868
886
  * button — are shown, overriding the grid-level `headerControls` default for
@@ -939,6 +957,12 @@ export interface ResolvedColumn {
939
957
  grandTotal: TotalName | TotalFn | null;
940
958
  layout: ColumnLayoutSpec;
941
959
  header: ColumnHeaderSpec;
960
+ /**
961
+ * This column's own cell-menu declaration (BACKLOG-0001068), or null when it
962
+ * makes none and the grid-level menu stands alone. Carried onto the resolved
963
+ * column so a column preset or `columnDefaults` can supply one.
964
+ */
965
+ contextMenu: boolean | MenuItem[] | ((p: CellMenuParams, defaults: MenuItem[]) => MenuItem[] | void) | null;
942
966
  export: ColumnExportSpec;
943
967
  lookup: LookupSpec | null;
944
968
  allowGroup: boolean;
@@ -1220,9 +1244,57 @@ export interface DerivedSourceConfig {
1220
1244
  * key when nothing is grouped. `config.rowKey` defaults to it, so it need not
1221
1245
  * be set; an explicit `rowKey` still wins.
1222
1246
  */
1223
- /** The grid to read. */
1224
- from: Grid;
1225
- /** Which of its rows to read. `filtered` by default. */
1247
+ /**
1248
+ * The grid to read, or several to combine into one row set before the rest
1249
+ * of the pipeline runs (BACKLOG-0001045). A bare `Grid` is shorthand for a
1250
+ * `UnionSourceOptions` with no `label`/`follow`/`map` override, so an
1251
+ * existing `from: <grid>` keeps meaning exactly what it always has.
1252
+ *
1253
+ * Given an array, every source is read (each narrowed by its own `follow`,
1254
+ * defaulting to `'filtered'` as a lone `from` does today), concatenated in
1255
+ * **declaration order** — deterministic, not interleaved — and only then
1256
+ * does `unnest`/`join`/`where`/`bucket`/`groupBy`/`select`/`sort`/`limit`/
1257
+ * `limitPer`/`cumulative` run, over the combined set, so "the worst
1258
+ * performers across both" is one derivation rather than a hand-merge.
1259
+ *
1260
+ * The output carries the **union of the sources' fields**: a field present
1261
+ * on only one source is `undefined` on rows from the others. Sources are
1262
+ * **not** type-reconciled — if two disagree on what a field means or holds,
1263
+ * that is not resolved for you; give each source a `map` to project it into
1264
+ * a common shape first. Every row also carries `__source` (the entry's
1265
+ * `label`, or its declaration index when unlabelled), which is required —
1266
+ * not optional — because without it a combined list cannot be read, filtered
1267
+ * or grouped by where it came from; it is an ordinary field to `where`,
1268
+ * `groupBy` and `select`. And because the derived key (`__key`) would
1269
+ * otherwise collide across sources sharing the same identifiers, it is
1270
+ * namespaced by the same source tag when nothing is grouped (a grouped
1271
+ * union's `__key` is the group value, exactly as today, and rows from
1272
+ * different sources correctly land in the *same* group when their group
1273
+ * values agree — that merging is the point of grouping a union, not a
1274
+ * collision to guard against).
1275
+ *
1276
+ * This is **not** a join: there is no dedup or merge-on-key, and it draws no
1277
+ * UNION/UNION ALL distinction — overlapping rows from two sources simply
1278
+ * both appear. Reach for `join` when two sides share a key and you want them
1279
+ * matched rather than stacked.
1280
+ *
1281
+ * An empty source contributes nothing and the rest still combine; a source
1282
+ * that fails to read is named in a `warnOnce` and skipped for that pass
1283
+ * rather than silently dropped, because a silently missing source would
1284
+ * make "worst across both" quietly wrong. A source list that includes the
1285
+ * grid being derived, directly or through a chain, is refused when the
1286
+ * source is built (naming the offender) rather than recursed into.
1287
+ *
1288
+ * `crossFilter` has no single target once there is more than one parent, so
1289
+ * it is not supported alongside a union `from` (ignored, with a `warnOnce`,
1290
+ * rather than guessing which parent to push onto).
1291
+ */
1292
+ from: Grid | UnionSourceOptions[];
1293
+ /**
1294
+ * Which of its rows to read. `filtered` by default. Ignored — with a
1295
+ * `warnOnce` — when `from` is a union array: each entry there carries its
1296
+ * own `follow` instead (BACKLOG-0001045).
1297
+ */
1226
1298
  follow?: 'filtered' | 'all' | 'selected' | 'grouped';
1227
1299
 
1228
1300
  /** An array property to expand, one row per element, before anything else. */
@@ -1255,6 +1327,52 @@ export interface DerivedSourceConfig {
1255
1327
  /** With `profile`, emit one row per statistic instead of one per column. */
1256
1328
  orient?: 'columns' | 'metrics';
1257
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
+
1258
1376
  /** When to re-derive. `idle` by default: coalesced to a frame. */
1259
1377
  refresh?: 'live' | 'idle' | 'manual' | number;
1260
1378
 
@@ -1265,6 +1383,118 @@ export interface DerivedSourceConfig {
1265
1383
  crossFilter?: boolean | string | { col?: string };
1266
1384
  }
1267
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
+
1466
+ /**
1467
+ * One member of a union `from` (BACKLOG-0001045): a grid to combine with the
1468
+ * others, plus how to read it and reshape it before it joins the rest. A bare
1469
+ * `Grid` in the `from` array is shorthand for `{ grid }` with every other
1470
+ * field defaulted.
1471
+ */
1472
+ export interface UnionSourceOptions {
1473
+ /** The grid this source reads. */
1474
+ grid: Grid;
1475
+ /**
1476
+ * Identifies this source: it is what `__source` carries on every row this
1477
+ * source contributes, and what namespaces that row's `__key` so two sources
1478
+ * sharing the same identifiers do not collide. Defaults to the source's
1479
+ * position in the `from` array (`'0'`, `'1'`, …), as a string.
1480
+ */
1481
+ label?: string;
1482
+ /**
1483
+ * Which of this source's rows to read. `filtered` by default, exactly as a
1484
+ * lone `from` follows its grid today — set independently per source, so
1485
+ * filtering one narrows only its own contribution.
1486
+ */
1487
+ follow?: 'filtered' | 'all' | 'selected' | 'grouped';
1488
+ /**
1489
+ * Reshape this source's rows into the common shape before they join the
1490
+ * rest — typically a rename or a projection, for a field this source calls
1491
+ * something else. Not a type coercion: if a field means something different
1492
+ * on two sources, `map` is where you make them agree, because the union
1493
+ * itself does not guess.
1494
+ */
1495
+ map?: (row: unknown) => unknown;
1496
+ }
1497
+
1268
1498
  export interface DerivedJoin {
1269
1499
  /** The grid holding the other side. */
1270
1500
  with: Grid;
@@ -5952,6 +6182,18 @@ export function duckdbAdapter(options: {
5952
6182
  from: string;
5953
6183
  /** Columns to select. Everything by default. */
5954
6184
  fields?: string[];
6185
+ /**
6186
+ * Whether to count the matching set at all. `true` by default: the total is a
6187
+ * separate `count(*)` statement carrying the same `WHERE`, dispatched in the
6188
+ * same tick as the page query rather than serialised behind it
6189
+ * (BACKLOG-0001065). `false` issues no count statement, declares
6190
+ * `capabilities.total: false`, and leaves the result's `total` **absent** — so
6191
+ * the grid scrolls open-ended instead of being told the page length is the
6192
+ * whole set. Turn it off for a grid that never shows a count: an unfiltered
6193
+ * count is answered from Parquet metadata and a filtered one still has to
6194
+ * evaluate the predicate, so it is cheap rather than free.
6195
+ */
6196
+ count?: boolean;
5955
6197
  /**
5956
6198
  * The key column an update and a delete target in their `WHERE`, and that an
5957
6199
  * add-row is rekeyed by. Write-back is refused unless this names a real column,
@@ -5974,7 +6216,15 @@ export function duckdbAdapter(options: {
5974
6216
  * key to rekey the temp row.
5975
6217
  */
5976
6218
  returning?: 'row' | 'none';
5977
- }): PushdownAdapter & { sqlFor(query: RemoteRequest): { sql: string; params: unknown[] } };
6219
+ }): PushdownAdapter & {
6220
+ sqlFor(query: RemoteRequest): { sql: string; params: unknown[] };
6221
+ /**
6222
+ * The separate `count(*)` statement that reports the matching set's size, with
6223
+ * the same `WHERE` as {@link sqlFor} and no `ORDER BY` or `LIMIT`
6224
+ * (BACKLOG-0001065). `null` when the adapter was built with `count: false`.
6225
+ */
6226
+ countSqlFor(query: RemoteRequest): { sql: string; params: unknown[] } | null;
6227
+ };
5978
6228
 
5979
6229
  /**
5980
6230
  * An adapter for a DemandFlow entity, speaking `POST /v1/query`.
@@ -6054,6 +6304,15 @@ export function graphqlAdapter(options: {
6054
6304
  pagination?: 'offset' | 'cursor';
6055
6305
  /** The page size for the whole-result and forward-cursor walks. */
6056
6306
  pageSize?: number;
6307
+ /**
6308
+ * Whether the default query asks for `totalCount`. `true` by default.
6309
+ * `false` drops it from the selection set and declares
6310
+ * `capabilities.total: false`, so a grid that never shows a count does not
6311
+ * make the server compute one (BACKLOG-0001065). Unlike the DuckDB adapter the
6312
+ * count is not split into a second operation — that would cost an extra HTTP
6313
+ * round trip rather than saving one — so suppression is the only lever here.
6314
+ */
6315
+ count?: boolean;
6057
6316
  /** Rename the pagination variables the adapter drives per page. */
6058
6317
  vars?: Partial<Record<'offset' | 'limit' | 'first' | 'after', string>>;
6059
6318
  capabilities?: PushdownCapabilities; operators?: string[];
@@ -7220,11 +7479,17 @@ declare module 'lattice-grid/modules/gantt' {
7220
7479
  /**
7221
7480
  * A typed dependency between two tasks (by id), with optional lag/lead. `type`
7222
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.
7223
7488
  */
7224
7489
  export interface GanttDependency {
7225
7490
  from: string | number;
7226
7491
  to: string | number;
7227
- type?: GanttLinkType;
7492
+ type?: GanttLinkType | `${GanttLinkType}${'+' | '-'}${number}`;
7228
7493
  lag?: number;
7229
7494
  }
7230
7495
 
@@ -7467,7 +7732,15 @@ declare module 'lattice-grid/modules/gantt' {
7467
7732
  * milestones, progress). The view redraws when the schedule recomputes.
7468
7733
  */
7469
7734
  mount(container: unknown, options?: {
7470
- 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';
7471
7744
  rowHeight?: number;
7472
7745
  labelWidth?: number;
7473
7746
  rowLabels?: boolean;
@@ -7475,7 +7748,23 @@ declare module 'lattice-grid/modules/gantt' {
7475
7748
  showCritical?: boolean;
7476
7749
  showProgress?: boolean;
7477
7750
  dateAxis?: boolean;
7478
- 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;
7479
7768
  nonWorking?: 'weekends' | ((day: number) => boolean);
7480
7769
  label?: 'name' | 'percent' | 'dates' | 'none' | ((task: GanttScheduledTask) => string);
7481
7770
  /** Whether bars can be dragged to move/resize (default true). */
@@ -8348,6 +8637,75 @@ declare module 'lattice-grid/modules/kpi' {
8348
8637
  sparkline?: KPISparkline | string;
8349
8638
  }
8350
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
+
8351
8709
  /** A computed tile, as it appears in the model. */
8352
8710
  interface KPITileModel {
8353
8711
  id: string;
@@ -8356,7 +8714,17 @@ declare module 'lattice-grid/modules/kpi' {
8356
8714
  field?: string;
8357
8715
  value: unknown;
8358
8716
  formatted: string;
8359
- status: 'good' | 'warn' | 'critical' | null;
8717
+ /**
8718
+ * The tile's semantic band, or `unknown` when the panel holds no rows at
8719
+ * all. `unknown` is decided from data presence before any threshold is
8720
+ * consulted: an aggregation over nothing returns the identity of its
8721
+ * operation (`sum` and `count` return 0), and 0 is a number a threshold
8722
+ * grades, so without it an empty panel would report as a healthy one. A
8723
+ * tile whose `filter` matches none of the rows the panel *does* hold has
8724
+ * measured a real zero and is banded normally. `null` means the tile has no
8725
+ * thresholds or bands configured.
8726
+ */
8727
+ status: 'good' | 'warn' | 'critical' | 'unknown' | null;
8360
8728
  target?: number;
8361
8729
  baseline?: number;
8362
8730
  delta: number | null;
@@ -8382,10 +8750,20 @@ declare module 'lattice-grid/modules/kpi' {
8382
8750
  columns?: number;
8383
8751
  ariaLabel?: string;
8384
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 };
8385
8762
  onTileClick?: (event: KPIEvent) => void;
8386
8763
  onTileDblClick?: (event: KPIEvent) => void;
8387
8764
  onTileContextMenu?: (event: KPIEvent) => void;
8388
- 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;
8389
8767
  }
8390
8768
 
8391
8769
  /** The keyed-diff consumer surface a KPI panel shares with a grid, so a Data Router routes to it directly. */
@@ -8404,10 +8782,21 @@ declare module 'lattice-grid/modules/kpi' {
8404
8782
  interface KPI {
8405
8783
  readonly el: unknown | null;
8406
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;
8407
8787
  rows: KPIRows;
8408
8788
  tiles(): KPITileModel[];
8409
8789
  tile(id: string): KPITileModel | undefined;
8410
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;
8411
8800
  setRows(rows: KPIRow[]): KPI;
8412
8801
  refresh(): KPI;
8413
8802
  getState(): object;