@toclocoinc/lattice-grid 1.27.0 → 1.29.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.
@@ -437,7 +437,7 @@
437
437
  <div class="shell">
438
438
  <aside class="rail">
439
439
  <p class="rail__brand">Lattice Grid</p>
440
- <p class="rail__sub">Developer guide · v1.27.0</p>
440
+ <p class="rail__sub">Developer guide · v1.29.0</p>
441
441
  <nav>
442
442
  <div class="rail__group">
443
443
  <span class="rail__label">Start here</span>
@@ -1378,7 +1378,7 @@ LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid
1378
1378
  <thead><tr><th>Family</th><th>Types</th></tr></thead>
1379
1379
  <tbody>
1380
1380
  <tr><td class="name">Core</td><td class="desc"><code>text</code>, <code>number</code>, <code>boolean</code>, <code>date</code>, <code>dateString</code>, <code>object</code>, <code>lookup</code></td></tr>
1381
- <tr><td class="name">Temporal</td><td class="desc"><code>datetime</code>, <code>time</code>, <code>duration</code></td></tr>
1381
+ <tr><td class="name">Temporal</td><td class="desc"><code>datetime</code>, <code>timestamp</code>, <code>time</code>, <code>duration</code></td></tr>
1382
1382
  <tr><td class="name">Network</td><td class="desc"><code>ipv4</code>, <code>ipv6</code>, <code>cidr</code>, <code>mac</code></td></tr>
1383
1383
  <tr><td class="name">Numeric bases</td><td class="desc"><code>hex</code>, <code>hex8</code>, <code>hex16</code>, <code>hex32</code>, <code>binary</code>, <code>binary8</code>, <code>octal</code></td></tr>
1384
1384
  <tr><td class="name">Units: computing</td><td class="desc"><code>bytes</code>, <code>megabytes</code>, <code>gigabytes</code>, <code>bitrate</code>, <code>gigabits</code></td></tr>
@@ -1537,8 +1537,36 @@ dataTypes: {
1537
1537
  convert.</p>
1538
1538
  <p>It is also faster and smaller: ISO 8601 sorts lexicographically in the same order it sorts
1539
1539
  chronologically, so a date column sorts as text, and repeated dates dictionary-encode well.</p>
1540
- <p>When you genuinely mean an instant (a log timestamp) use <code>datetime</code> and set
1541
- <code>format.timeZone</code>.</p>
1540
+ <p>When you genuinely mean an instant (a log timestamp, an audit time) use
1541
+ <code>timestamp</code> — see below. <code>datetime</code> stays a wall clock, deliberately.</p>
1542
+ </div>
1543
+
1544
+ <h3 id="timestamp-type">timestamp — an instant, stored UTC, shown in a chosen zone</h3>
1545
+ <p class="lead-in">
1546
+ <code>type: 'timestamp'</code> is the sibling to <code>datetime</code> for data that is one
1547
+ genuine moment everywhere — an audit time, an event <code>created_at</code>, a cross-region log
1548
+ line — rather than a wall clock.
1549
+ </p>
1550
+ <div class="why">
1551
+ <p><strong>Stored as an instant.</strong> A value ingests from epoch-millis, a <code>Date</code>,
1552
+ or a zone-bearing ISO string (<code>…Z</code> / <code>…+01:00</code>) and is stored as
1553
+ epoch-millis UTC. Because storage is numeric, sort, filter and compare operate on the
1554
+ <em>instant</em>, never on rendered text — two rows from different origin zones order by true
1555
+ chronology, and changing the display zone never reorders them.</p>
1556
+ <p><strong>Shown in a display zone you control.</strong> The cell renders in the zone resolved
1557
+ by precedence: the column's <code>typeOptions.timeZone</code>, then the grid's
1558
+ <code>config.timeZone</code>, then the viewer's local zone. The resolved zone is nameable
1559
+ (e.g. <code>Europe/London (BST)</code>) so a reader always knows which clock they are reading;
1560
+ set <code>typeOptions.showOrigin: true</code> to also show the origin zone when it differs, and
1561
+ when no origin was recorded the cell says so rather than assuming local.</p>
1562
+ <p><strong>Grouped by civil day in the display zone.</strong> Grouping a <code>timestamp</code>
1563
+ column buckets by civil day by default — <code>group: { granularity: 'week' | 'month' | 'instant', weekStart: 1 }</code>
1564
+ chooses week (Monday-start by default), month, or the exact instant. Buckets are computed by
1565
+ projecting the instant to a civil date in the display zone, so a 23- or 25-hour daylight-saving
1566
+ day still collapses to one bucket rather than splitting.</p>
1567
+ <p><strong>Excel export.</strong> Excel has no zone, so the display-zone wall clock is written
1568
+ as a plain numeric datetime serial (<code>yyyy-mm-dd hh:mm</code>) — what you saw on screen,
1569
+ with the zone named in the docs and column header, never shifted silently to UTC.</p>
1542
1570
  </div>
1543
1571
 
1544
1572
  <h3>Formats</h3>
@@ -2369,6 +2397,34 @@ grid.setPinnedRows([], { edge: 'top' }); <span class="cmt">// clear</span
2369
2397
  No second, empty row is rendered underneath it.
2370
2398
  </p>
2371
2399
 
2400
+ <h3 id="banded-header-grouping">Forming banded headers at runtime (BACKLOG-0000739)</h3>
2401
+ <p class="lead-in">
2402
+ Banded headers can be declared in config (<code>columnGroups</code>) and now also formed,
2403
+ renamed, moved and dissolved at runtime through <code>grid.columns</code>, with a keyboard
2404
+ equivalent for every action. The model is the single source of truth: a band made by
2405
+ interaction is the same <code>ColumnGroup</code> tree config drives, and it round-trips through
2406
+ a saved view.
2407
+ </p>
2408
+ <div class="why">
2409
+ <p><strong>The API.</strong> <code>groupColumns(ids, { title, groupId })</code> wraps columns
2410
+ in a new band or adds them to an existing one; <code>ungroupColumn(id)</code> takes a column
2411
+ out (dissolving a band it empties); <code>renameGroup(id, title)</code>,
2412
+ <code>dissolveGroup(id)</code> and <code>moveGroup(id, to)</code> do the rest. Each emits
2413
+ <code>columngroup:changed</code>. A band's columns are always contiguous, and a nested band
2414
+ dissolves into its parent, not the root.</p>
2415
+ <p><strong>The keyboard (WCAG 2.1.1).</strong> From a focused header cell:
2416
+ <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>&larr;</kbd>/<kbd>&rarr;</kbd> groups the column with its
2417
+ neighbour on that side (joining an adjacent band, or forming a new one);
2418
+ <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>&uarr;</kbd> takes it out of its band; and
2419
+ <kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>&larr;</kbd>/<kbd>&rarr;</kbd> moves the whole band as a
2420
+ unit. Every action is announced through the live region, and a refusal — "not in a band",
2421
+ "cannot be moved there" — is announced too, never silent.</p>
2422
+ <p><strong>Pinning and visibility.</strong> A band lives in one pin region and draws over its
2423
+ visible columns there: hiding a column shrinks the band's span without changing the band
2424
+ definition, and hiding the last visible column hides the band. A band is exposed to assistive
2425
+ technology as one <code>role="columnheader"</code> cell with an <code>aria-colspan</code>.</p>
2426
+ </div>
2427
+
2372
2428
  <h2 id="row-reorder">Row reorder</h2>
2373
2429
  <p class="lead-in">
2374
2430
  <code>rowReorder: true</code> puts a drag handle in the first visible column and lets a user
@@ -6179,6 +6235,7 @@ grid.state.apply(savedView.state);
6179
6235
  <tr><td class="name">chartRange</td><td class="desc">Chart a selected cell range — the spreadsheet gesture. Derives the chart from the range’s shape (a leading text column is the categories, the numeric columns the measures), respects hidden and unreadable columns, and returns the live chart or null when there is nothing to measure.</td></tr>
6180
6236
  <tr><td class="name">canChartRange</td><td class="desc">Whether <code>chartRange</code> would draw something for the grid’s current selection — the question a menu asks before offering the item.</td></tr>
6181
6237
  <tr><td class="name">deriveRangeSpec</td><td class="desc">Decide what a chart of a range should be without drawing it: the type, the category column, the measures, and a spec ready for <code>createChart</code>.</td></tr>
6238
+ <tr><td class="name">regressionPlots</td><td class="desc">Turn a fitted regression model into diagnostic chart specs ready for <code>createChart</code>: the fit line with its confidence band, residuals-vs-fitted, a QQ plot of the residuals, and a multicollinearity correlogram with the model’s VIF. The plots that need a per-row or per-coefficient quantity the grid has no column for (scale-location, residuals-vs-leverage, the coefficient forest) are returned as a null spec carrying the reason rather than dropped.</td></tr>
6182
6239
  <tr><td class="name">createDevtools</td><td class="desc">Mount the devtools panel against a grid, including its accessibility checks.</td></tr>
6183
6240
  <tr><td class="name">createLatticeGridElement</td><td class="desc">Build the element class without registering it, for a custom registry.</td></tr>
6184
6241
  <tr><td class="name">createMessages</td><td class="desc">Build a message catalogue. A partial set lays over the built-in British English one.</td></tr>
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.27.0, type declarations
2
+ * Lattice Grid 1.29.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -20,7 +20,7 @@ export type TypeName =
20
20
  | 'text' | 'number' | 'boolean' | 'date' | 'dateString' | 'object' | 'lookup'
21
21
  | 'image'
22
22
  // Extended catalogue. Never inferred, a column asks for these by name.
23
- | 'time' | 'datetime' | 'duration'
23
+ | 'time' | 'datetime' | 'duration' | 'timestamp'
24
24
  | 'ipv4' | 'ipv6' | 'cidr'
25
25
  | 'json' | 'secret'
26
26
  | 'hex' | 'hex8' | 'hex16' | 'hex32' | 'binary' | 'binary8' | 'octal'
@@ -551,9 +551,19 @@ export interface ColumnLayoutSpec {
551
551
  }
552
552
 
553
553
  export interface ColumnHeaderSpec {
554
+ /** Not read by the header renderer; use `render` to draw a custom heading. */
554
555
  template?: string;
556
+ /**
557
+ * A custom heading renderer: a function, or a component (a class with a
558
+ * `render` method). A string names a registered renderer. Either form draws
559
+ * the same two ways and they are interchangeable — it may append to the passed
560
+ * label element itself and return nothing, or return an `Element` (attached
561
+ * for you) or a `string` (used as the heading text).
562
+ */
555
563
  render?: string | RendererCtor;
564
+ /** Props passed to `render` as `params.props`. */
556
565
  props?: Record<string, unknown>;
566
+ /** A class, or classes, added to the heading cell. */
557
567
  class?: string | string[];
558
568
  tooltip?: string;
559
569
  align?: Align;
@@ -607,8 +617,17 @@ export interface Column {
607
617
  * Row grouping by this column. `index` fixes its place among several;
608
618
  * `explode` gives a multi-value cell one group per value rather than one
609
619
  * group for the combination.
610
- */
611
- group?: { enabled?: boolean; index?: number; explode?: boolean } | boolean;
620
+ *
621
+ * `granularity` and `weekStart` apply to a `timestamp` column: it buckets by
622
+ * civil `day` (the default), `week` or `month` in the display zone, or
623
+ * `instant` for one group per exact moment. `weekStart` is the first weekday,
624
+ * 1=Monday (default) to 7=Sunday.
625
+ */
626
+ group?: {
627
+ enabled?: boolean; index?: number; explode?: boolean;
628
+ granularity?: 'day' | 'week' | 'month' | 'instant';
629
+ weekStart?: number;
630
+ } | boolean;
612
631
  /** Use this column as a pivot dimension, and where it sits among several. */
613
632
  pivot?: { enabled?: boolean; index?: number } | boolean;
614
633
  /** The reduction shown in the totals row and in group footers. */
@@ -659,6 +678,49 @@ export interface Column {
659
678
  upper?: number;
660
679
  warnLower?: number;
661
680
  warnUpper?: number;
681
+ /**
682
+ * For a rolling time-series kind (`rollingSum`/`rollingAvg`/`rollingMin`/
683
+ * `rollingMax`/`windowCoverage`/`cumulativeToDate`/`periodOverPeriod`,
684
+ * BACKLOG-0000748), the column whose order defines the series — dates,
685
+ * sequence numbers, timestamps. **Required**: the screen sort is never used,
686
+ * because a rolling figure would then change on every header click, so a
687
+ * rolling column with no `orderBy` reports null and warns.
688
+ */
689
+ orderBy?: string;
690
+ /**
691
+ * For the rolling window kinds, the window to aggregate over: the last `span`
692
+ * rows (`count`), the last `span` ms — or `minutes` — of the `orderBy` axis
693
+ * (`time`), or everything so far (`session`). The first rows of a series
694
+ * carry a partial window, stamped by a `windowCoverage` companion rather than
695
+ * dressed as full.
696
+ */
697
+ window?: {
698
+ kind: 'count' | 'time' | 'session';
699
+ span?: number;
700
+ minutes?: number;
701
+ };
702
+ /**
703
+ * For a rolling kind, whether the series is computed per group (`'group'`,
704
+ * the default — partitioned by the grid's active grouping) or across the
705
+ * whole dataset (`'all'`).
706
+ */
707
+ within?: 'group' | 'all';
708
+ /**
709
+ * For `kind: 'rollingQuantile'` (and its `windowApproximate` companion), the
710
+ * quantile in `[0, 1]`, defaulting to the median (`0.5`). Exact while the
711
+ * window is small; past an internal span cap, and for a session window, the
712
+ * value comes from a sketch and is stamped by a `windowApproximate` column.
713
+ */
714
+ q?: number;
715
+ /**
716
+ * For a `fit*` kind (BACKLOG-0000812), the regression model the shadow reads
717
+ * — predictors, response, method and confidence. Its predictors/response may
718
+ * also be given directly on this object.
719
+ */
720
+ model?: RegressionSpec;
721
+ predictors?: string[];
722
+ response?: string;
723
+ method?: 'ols' | 'wls' | 'robust' | 'quantile';
662
724
  };
663
725
  /**
664
726
  * A running total down the grid **as it is currently ordered**.
@@ -719,7 +781,7 @@ export interface ResolvedColumn {
719
781
  edit: ColumnEditSpec;
720
782
  sort: ColumnSortSpec;
721
783
  filter: ColumnFilterSpec;
722
- group: { enabled: boolean; index: number; explode: boolean };
784
+ group: { enabled: boolean; index: number; explode: boolean; granularity?: 'day' | 'week' | 'month' | 'instant'; weekStart?: number };
723
785
  pivot: { enabled: boolean; index: number };
724
786
  total: TotalName | TotalFn | null;
725
787
  /**
@@ -1928,6 +1990,14 @@ export interface GridConfig {
1928
1990
  actions?: false | (RailActionName | '-' | RailAction)[];
1929
1991
  /** File name for the export action, without the extension. */
1930
1992
  exportName?: string;
1993
+ /**
1994
+ * Put the native annotation tools — pen, arrow, rectangle, highlighter — on
1995
+ * the rail. Off by default; each is a real toggle button that shows pressed
1996
+ * while it is the tool in use and turns off when pressed again. The tools
1997
+ * also appear automatically for the duration of a presentation, so this is
1998
+ * only needed to keep them available outside one.
1999
+ */
2000
+ annotate?: boolean;
1931
2001
  };
1932
2002
  /**
1933
2003
  * A drag-and-drop group-by strip above the column header — the pattern AG
@@ -2092,10 +2162,25 @@ export interface ColumnState {
2092
2162
  variant?: VariantSpec | null;
2093
2163
  }
2094
2164
 
2165
+ /**
2166
+ * A persisted banded-header node (§15, BACKLOG-0000739): a band with a `columns`
2167
+ * list whose members are leaf ids or nested bands. This is what round-trips a
2168
+ * drag-created group through a saved view.
2169
+ */
2170
+ export interface ColumnGroupState {
2171
+ id: string;
2172
+ title: string;
2173
+ collapsible: boolean;
2174
+ openByDefault: boolean;
2175
+ columns: Array<string | ColumnGroupState>;
2176
+ }
2177
+
2095
2178
  export interface GridState {
2096
2179
  version: number;
2097
2180
  columns?: ColumnState[];
2098
2181
  columnOrder?: string[];
2182
+ /** The banded-header tree, when the grid has one (BACKLOG-0000739). */
2183
+ columnGroups?: ColumnGroupState[];
2099
2184
  filters?: FilterSet;
2100
2185
  quick?: string;
2101
2186
  sort?: SortEntry[];
@@ -2392,6 +2477,14 @@ export interface StatisticsApi {
2392
2477
  */
2393
2478
  shadow(colId: string, kind: ShadowKind, rowKey: string,
2394
2479
  scope?: 'all' | 'filtered', spec?: object): unknown;
2480
+ /**
2481
+ * One regression shadow value for a row, by key (BACKLOG-0000812): the
2482
+ * predicted value, residual, or Cook's-distance influence flag from the fitted
2483
+ * model, over the filtered rows. Null for a row outside the fit.
2484
+ */
2485
+ fitShadow(kind: 'fitPredicted' | 'fitResidual' | 'fitInfluence'
2486
+ | 'fitStdResidual' | 'fitLeverage' | 'fitCooksD',
2487
+ rowKey: string, spec: RegressionSpec): number | boolean | null;
2395
2488
  /** A running total at one row, down the grid as it is currently ordered. */
2396
2489
  running(colId: string, kind: 'total' | 'percent', rowKey: string): number | null;
2397
2490
  /** Make the current values the new baseline: "mark all". */
@@ -2452,6 +2545,16 @@ export interface StatisticsApi {
2452
2545
  covariance(a: string, b: string, opts?: { population?: boolean }): number | null;
2453
2546
  /** Least-squares fit of `b` on `a`: in finance, beta and alpha. */
2454
2547
  regression(a: string, b: string): RegressionFit | null;
2548
+ /**
2549
+ * Fit a multi-predictor linear model over the filtered rows and return the
2550
+ * full diagnostic set — coefficients with standard errors, t and p; R² and
2551
+ * adjusted R²; per-row fitted values, residuals, leverage and Cook's D; VIF
2552
+ * per predictor; a Breusch–Pagan heteroscedasticity flag; and, for a single
2553
+ * predictor, a pointwise confidence band. `method` is `ols`, `wls` (needs a
2554
+ * `weights` column) or `robust`; `quantile` is reserved and the regularised
2555
+ * families refuse. Null on degenerate input (BACKLOG-0000792).
2556
+ */
2557
+ regressionModel(spec: RegressionSpec): RegressionModel | null;
2455
2558
  /** Spearman's rank correlation, which one outlier cannot drag. */
2456
2559
  spearman(a: string, b: string): number | null;
2457
2560
  /** Kendall's tau-b. Null past 5,000 rows: it is quadratic. */
@@ -2679,7 +2782,47 @@ export type ShadowKind =
2679
2782
  * `{lower, upper}` (and optional inner `{warnLower, warnUpper}`) off the
2680
2783
  * shadow declaration; centred-target ± tolerance is a deliberate follow-up.
2681
2784
  */
2682
- | 'specStatus';
2785
+ | 'specStatus'
2786
+ /**
2787
+ * Rolling time-series aggregates over a stated `orderBy` (BACKLOG-0000748,
2788
+ * Phase 1), computed in one ordered pass the grid caches by row key and never
2789
+ * over the screen sort. `rollingSum`/`rollingAvg`/`rollingMin`/`rollingMax`
2790
+ * reduce the `window`; `windowCoverage` reports how much of the requested
2791
+ * window a row actually covers (so a partial window is never dressed as full);
2792
+ * `cumulativeToDate` is the running total to the row; `periodOverPeriod` is the
2793
+ * change on the previous period (lag-1 in Phase 1). Sortable, filterable,
2794
+ * groupable and exportable like any cell.
2795
+ */
2796
+ | 'rollingSum' | 'rollingAvg' | 'rollingMin' | 'rollingMax'
2797
+ | 'windowCoverage' | 'cumulativeToDate' | 'periodOverPeriod'
2798
+ /**
2799
+ * A rolling quantile over the `orderBy` window (BACKLOG-0000748) — a trailing
2800
+ * median or p95, the quantile set by `q`. Exact while the window is small;
2801
+ * past an internal span cap, and for a session window, it comes from a KLL
2802
+ * sketch and `windowApproximate` reports which rows are approximate, so a
2803
+ * sketched quantile is never presented as exact.
2804
+ */
2805
+ | 'rollingQuantile' | 'windowApproximate'
2806
+ /**
2807
+ * Model-backed regression shadows (BACKLOG-0000812): the predicted value, the
2808
+ * residual, and a Cook's-distance influence flag for the row, read from the
2809
+ * fitted model named on the shadow declaration (`shadow: { kind:
2810
+ * 'fitResidual', model: { predictors, response, method } }`). They follow the
2811
+ * grid's filters — the model refits over the filtered rows — and are
2812
+ * sortable, filterable, groupable and exportable like any cell. Null for a row
2813
+ * outside the fit. `fitInfluence` flags Cook's D > 4/n by default (overridable
2814
+ * via `threshold`); "not influential" (`false`) and "cannot tell" (`null`)
2815
+ * stay distinct.
2816
+ *
2817
+ * `fitStdResidual`, `fitLeverage` and `fitCooksD` (BACKLOG-0000872) surface
2818
+ * the diagnostics the engine already computes — the internally studentised
2819
+ * residual `eᵢ/(s·√(1−hᵢ))`, the hat-matrix leverage `hᵢ`, and Cook's distance
2820
+ * — as their own numeric columns, so the scale-location and
2821
+ * residuals-vs-leverage plots bind to real columns. Null where there is no
2822
+ * spread to standardise against.
2823
+ */
2824
+ | 'fitPredicted' | 'fitResidual' | 'fitInfluence'
2825
+ | 'fitStdResidual' | 'fitLeverage' | 'fitCooksD';
2683
2826
 
2684
2827
  /** The three verdicts a `specStatus` shadow can report. */
2685
2828
  export type SpecStatus = 'PASS' | 'WARN' | 'FAIL';
@@ -2695,6 +2838,83 @@ export interface RegressionFit {
2695
2838
  n: number;
2696
2839
  }
2697
2840
 
2841
+ /** The specification of a multi-predictor model (BACKLOG-0000792). */
2842
+ export interface RegressionSpec {
2843
+ /** The predictor column ids. */
2844
+ predictors: string[];
2845
+ /** The response column id. */
2846
+ response: string;
2847
+ /** `ols` (default), `wls` or `robust`. `quantile` is reserved (coming next). */
2848
+ method?: 'ols' | 'wls' | 'robust' | 'quantile';
2849
+ /** A weights column id, required for `wls`. */
2850
+ weights?: string;
2851
+ /** The confidence level for the band; 0.95 by default. */
2852
+ confidence?: number;
2853
+ }
2854
+
2855
+ /** One fitted coefficient, with the uncertainty around it. */
2856
+ export interface RegressionCoefficient {
2857
+ /** `(intercept)` or the predictor's column id. */
2858
+ name: string;
2859
+ estimate: number;
2860
+ stdError: number;
2861
+ /** estimate ÷ standard error. */
2862
+ t: number;
2863
+ /** Two-sided Student-t p-value; a number with a documented method, not a verdict. */
2864
+ p: number;
2865
+ /**
2866
+ * The Wald confidence interval at the model's confidence level
2867
+ * (BACKLOG-0000872) — the whiskers a coefficient forest plot draws. Null when
2868
+ * there is no residual degree of freedom to form a critical value.
2869
+ */
2870
+ lower: number | null;
2871
+ upper: number | null;
2872
+ }
2873
+
2874
+ /** A pointwise confidence band for the mean response of a single-predictor fit. */
2875
+ export interface RegressionBand {
2876
+ confidence: number;
2877
+ points: { x: number; yhat: number; lower: number; upper: number }[];
2878
+ }
2879
+
2880
+ /** The Breusch–Pagan heteroscedasticity test result. */
2881
+ export interface Heteroscedasticity {
2882
+ statistic: number;
2883
+ df: number;
2884
+ p: number;
2885
+ /** True when the test rejects homoscedasticity at the 0.05 level. */
2886
+ heteroscedastic: boolean;
2887
+ }
2888
+
2889
+ /** A fitted multi-predictor linear model and its diagnostics (BACKLOG-0000792). */
2890
+ export interface RegressionModel {
2891
+ method: string;
2892
+ coefficients: RegressionCoefficient[];
2893
+ r2: number;
2894
+ adjR2: number;
2895
+ n: number;
2896
+ /** Residual degrees of freedom, n − p. */
2897
+ df: number;
2898
+ /** Residual variance, RSS ÷ df. */
2899
+ sigma2: number;
2900
+ fitted: number[];
2901
+ residuals: number[];
2902
+ /** Hat-diagonal leverage per row. */
2903
+ leverage: number[];
2904
+ /** Cook's distance per row; null where it cannot be computed. */
2905
+ cooksD: (number | null)[];
2906
+ /** Variance-inflation factor per predictor; Infinity when exactly collinear. */
2907
+ vif: number[];
2908
+ heteroscedasticity: Heteroscedasticity | null;
2909
+ band: RegressionBand | null;
2910
+ /** Per-row weights actually used (robust/WLS), or null for OLS. */
2911
+ weights: number[] | null;
2912
+ predictors: string[];
2913
+ response: string;
2914
+ /** The physical rows the diagnostics are aligned to, in order. */
2915
+ rows: number[];
2916
+ }
2917
+
2698
2918
  export interface ProcessCapability {
2699
2919
  n: number;
2700
2920
  mean: number;
@@ -3084,7 +3304,7 @@ export type EventName =
3084
3304
  | 'column:moved' | 'column:resized' | 'column:visible' | 'column:pinned'
3085
3305
  | 'column:grouped' | 'column:pivoted' | 'column:filter:open' | 'column:menu:open'
3086
3306
  | 'pivot:drill'
3087
- | 'columns:changed' | 'columns:tagged' | 'header:contextmenu'
3307
+ | 'columns:changed' | 'columns:tagged' | 'columngroup:changed' | 'header:contextmenu'
3088
3308
  /* Selection and view */
3089
3309
  | 'selection:changed' | 'range:changed' | 'clipboard:copy'
3090
3310
  | 'page:changed' | 'scroll' | 'scroll:end' | 'size:changed'
@@ -3314,6 +3534,22 @@ export interface ColumnsApi {
3314
3534
  show(ids: string | string[]): void;
3315
3535
  hide(ids: string | string[]): void;
3316
3536
  move(id: string, to: number): void;
3537
+ /**
3538
+ * Wrap leaf columns in a banded header, or add them to an existing band
3539
+ * (BACKLOG-0000739). Header banding, not row grouping (see {@link group}); the
3540
+ * band is a {@link ColumnGroup} node so a drag-, keyboard- or config-built band
3541
+ * is the same tree, and it round-trips through a saved view. Emits
3542
+ * `columngroup:changed`.
3543
+ */
3544
+ groupColumns(ids: string | string[], opts?: { title?: string; at?: number; groupId?: string }): string | null;
3545
+ /** Take a leaf out of its band; a band emptied by the move is dissolved. */
3546
+ ungroupColumn(id: string): void;
3547
+ /** Rename a banded header. */
3548
+ renameGroup(groupId: string, title: string): void;
3549
+ /** Dissolve a band, returning its columns to the enclosing level in place. */
3550
+ dissolveGroup(groupId: string): void;
3551
+ /** Move a whole band among its siblings, its columns travelling as a block. */
3552
+ moveGroup(groupId: string, to: number): void;
3317
3553
  pin(id: string, side: 'start' | 'end' | null): void;
3318
3554
  resize(id: string, px: number): void;
3319
3555
  /**
@@ -4218,7 +4454,10 @@ export interface ColumnMenuParams {
4218
4454
  /** The rail's built-in action names, plus `'-'` for a divider. */
4219
4455
  export type RailActionName =
4220
4456
  | 'undo' | 'redo' | 'pause' | 'restore' | 'maximise'
4221
- | 'export' | 'excel' | 'clipboard' | 'print';
4457
+ | 'export' | 'excel' | 'clipboard' | 'print'
4458
+ // The native annotation tools, on the rail when `toolPanel.annotate` is set
4459
+ // or while a presentation runs. Each is toggleable (see `RailAction.active`).
4460
+ | 'pen' | 'arrow' | 'rect' | 'highlight';
4222
4461
 
4223
4462
  /** What a host rail action's `run` is handed. */
4224
4463
  export interface RailActionParams {
@@ -4233,6 +4472,14 @@ export interface RailAction {
4233
4472
  icon?: string | (() => string);
4234
4473
  run(params: RailActionParams): void;
4235
4474
  enabled?(): boolean;
4475
+ /**
4476
+ * Marks the action as a toggle and reports whether it is currently on. When
4477
+ * present the rail renders `aria-pressed` and a pressed style, re-read on
4478
+ * every repaint; a one-shot action omits it and is unchanged. This is the
4479
+ * hook the native annotation tools use, and it is available to a host button
4480
+ * that is itself a toggle.
4481
+ */
4482
+ active?(): boolean;
4236
4483
  }
4237
4484
 
4238
4485
  /** The result of evaluating a formula a user typed into a cell (spec 8.11). */
@@ -5081,7 +5328,7 @@ export function resolveCatalogue(tag?: string): Record<string, unknown> | null;
5081
5328
  export type ChartType =
5082
5329
  | 'line' | 'step' | 'area' | 'rangeArea'
5083
5330
  | 'bar' | 'horizontalBar' | 'waterfall'
5084
- | 'scatter' | 'bubble'
5331
+ | 'scatter' | 'bubble' | 'forest'
5085
5332
  | 'combo' | 'pareto'
5086
5333
  | 'histogram' | 'boxplot' | 'heatmap'
5087
5334
  | 'qq' | 'ecdf' | 'lorenz' | 'correlogram' | 'control' | 'capability' | 'movingRange'
@@ -5240,6 +5487,29 @@ export interface ChartSpec {
5240
5487
  * through the order they happened to be listed in.
5241
5488
  */
5242
5489
  fit?: boolean | 'line';
5490
+ /**
5491
+ * A pointwise confidence band, drawn as a varying-width ribbon beneath the fit
5492
+ * line (BACKLOG-0000812). Fed by a fitted model's own interval — the `band`
5493
+ * from {@link StatisticsApi.regressionModel}, or as produced by
5494
+ * {@link regressionPlots} — so the ribbon and the diagnostics report the one
5495
+ * computation rather than a slope redrawn here. `line: false` suppresses the
5496
+ * band's own centre line, for a chart that already draws the fit with `fit`.
5497
+ *
5498
+ * Only where the x axis is numeric, for the same reason `fit` is.
5499
+ */
5500
+ band?: (RegressionBand & { line?: boolean }) | null;
5501
+ /**
5502
+ * An explicit point set, bypassing the by-column binder (BACKLOG-0000872): a
5503
+ * cartesian chart whose values are not a grid column — a scale-location plot's
5504
+ * √|standardised residual|, a coefficient forest's per-coefficient estimate —
5505
+ * hands its points in directly. Each is `{x, y}` with an optional `label`,
5506
+ * `size` (a bubble's third channel) and `lower`/`upper` (interval bounds the
5507
+ * error-bar primitive reads). Numeric `x` throughout gives a continuous axis.
5508
+ */
5509
+ points?: {
5510
+ x: number | string; y?: number; label?: string;
5511
+ size?: number; lower?: number; upper?: number; key?: string;
5512
+ }[];
5243
5513
  /**
5244
5514
  * Whiskers showing the uncertainty in each mark. `true` computes a confidence
5245
5515
  * interval from the readings behind the mark; `of` takes a symmetric margin
@@ -5323,8 +5593,22 @@ export interface ChartSpec {
5323
5593
  * The events a chart raises.
5324
5594
  *
5325
5595
  * A chart's own, not the grid's: `grid.on` takes {@link EventName} and knows
5326
- * nothing about these. `point:click` is the one most callers want, it is how a
5327
- * click on a mark becomes a filter on the grid.
5596
+ * nothing about these. There is no `point:click`, `point:hover` or
5597
+ * `series:toggle`; the events are the flat names below and `click` is the one
5598
+ * most callers want, it is how a click on a mark becomes a filter on the grid.
5599
+ *
5600
+ * `click` and `hover` carry a **flat** payload — there is no `point` wrapper:
5601
+ * `{ label, category, column, value, series, rowKeys, native, preventDefault }`.
5602
+ * `column` is the grid column the mark filters on and `category` the value to
5603
+ * filter it to; `value` is the measure when a single series sits under the mark,
5604
+ * otherwise null with the per-series numbers in `series`; `rowKeys` are the
5605
+ * source rows behind the mark; `native` is the DOM event.
5606
+ *
5607
+ * `click` fires whether or not the spec sets `filterOnClick`, and it fires
5608
+ * *before* any filter is applied: call `preventDefault()` on the payload to stop
5609
+ * the chart filtering the grid and take the click over yourself. With
5610
+ * `filterOnClick: true` in the spec the chart filters the grid itself on the
5611
+ * clicked mark's `column`/`category` unless a handler prevented it.
5328
5612
  */
5329
5613
  export type ChartEventName =
5330
5614
  | 'click' | 'hover' | 'leave' | 'focus'
@@ -5389,6 +5673,43 @@ declare module 'lattice-grid/modules/charts' {
5389
5673
  columns: string[];
5390
5674
  reason: string | null;
5391
5675
  };
5676
+ /**
5677
+ * Turn a fitted regression model into diagnostic chart specs ready for
5678
+ * `createChart` (BACKLOG-0000812). Pass a precomputed `model`, or a `spec` to
5679
+ * fit one over the grid, and the `fitted` and `residual` fit-shadow column ids
5680
+ * the residual and QQ plots draw over.
5681
+ *
5682
+ * The presets that map onto grid columns come back as drawable specs: `fit`
5683
+ * (the fit line with its confidence band), `residualsFitted`, `qq`, and
5684
+ * `multicollinearity` (a correlogram over the predictors, with the model's
5685
+ * `vif` alongside). The three that need a per-row or per-coefficient quantity
5686
+ * the grid has no column for — `scaleLocation`, `residualsLeverage`,
5687
+ * `coefficientForest` — come back with a null `spec` and a stable `reason`,
5688
+ * rather than silently dropped.
5689
+ */
5690
+ export function regressionPlots(
5691
+ grid: Grid,
5692
+ opts?: {
5693
+ model?: RegressionModel;
5694
+ spec?: RegressionSpec;
5695
+ fitted?: string;
5696
+ residual?: string;
5697
+ rows?: object[] | ((grid: Grid) => object[]);
5698
+ confidence?: number;
5699
+ },
5700
+ ): {
5701
+ model: RegressionModel | null;
5702
+ plots: Record<
5703
+ 'fit' | 'residualsFitted' | 'qq' | 'multicollinearity'
5704
+ | 'scaleLocation' | 'residualsLeverage' | 'coefficientForest',
5705
+ {
5706
+ spec: ChartSpec | null;
5707
+ reason: string | null;
5708
+ vif?: number[] | null;
5709
+ coefficients?: RegressionCoefficient[] | null;
5710
+ }
5711
+ >;
5712
+ };
5392
5713
  export function registerScheme(name: string, colours: readonly string[]): void;
5393
5714
  export function resolveScheme(spec?: object): object;
5394
5715
  export function schemeNames(): string[];
@@ -5453,6 +5774,31 @@ declare module 'lattice-grid/modules/svelte' {
5453
5774
  export default createLatticeAction;
5454
5775
  }
5455
5776
 
5777
+ declare module 'lattice-grid/modules/angular' {
5778
+ /**
5779
+ * Build the Angular standalone component and directive from one shared
5780
+ * controller (BACKLOG-0000805).
5781
+ *
5782
+ * The Angular core namespace and `createGrid` are passed in, for the same
5783
+ * reason as every other adapter: the package ships no dependencies and cannot
5784
+ * import `@angular/core` or the grid. Pass `@angular/common`'s
5785
+ * `isPlatformBrowser` too for an explicit SSR guard; without it the adapter
5786
+ * guards on the presence of a `document`.
5787
+ *
5788
+ * The returned `LatticeGridComponent` (`<lattice-grid [config]="…">`) and
5789
+ * `LatticeGridDirective` (`<div [latticeGrid]="…">`) each expose the live grid
5790
+ * through a `grid` getter — the same `Grid` the vanilla `createGrid` returns,
5791
+ * or null before build — at parity with React's `ref.current.grid`. Grid
5792
+ * events are `@Output`s aliased to their dashed names (`(cell-changed)`).
5793
+ */
5794
+ export function createLatticeGrid(
5795
+ deps: { ng: unknown; createGrid: unknown; isPlatformBrowser?: (id: unknown) => boolean },
5796
+ ): { LatticeGridComponent: unknown; LatticeGridDirective: unknown };
5797
+ export const EVENT_NAMES: readonly string[];
5798
+ export function dashedName(event: string): string;
5799
+ export default createLatticeGrid;
5800
+ }
5801
+
5456
5802
  declare module 'lattice-grid/modules/webcomponent' {
5457
5803
  /**
5458
5804
  * Register `<lattice-grid>`.