@toclocoinc/lattice-grid 1.26.0 → 1.28.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.
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.26.0, type declarations
2
+ * Lattice Grid 1.28.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -642,6 +642,12 @@ export interface Column {
642
642
  * `'filtered'` ranks within what the filters left.
643
643
  */
644
644
  scope?: 'all' | 'filtered';
645
+ /**
646
+ * For `kind: 'anomalyFlag'`, the modified-z score a row must clear to be
647
+ * flagged an anomaly. Default 3.5 (Iglewicz & Hoaglin). Ignored by
648
+ * `anomalyScore`, which reports the raw score, and by the other kinds.
649
+ */
650
+ threshold?: number;
645
651
  /**
646
652
  * For `kind: 'specStatus'`, the hard specification the row is judged
647
653
  * against. `lower`/`upper` are the pass limits (a value beyond either
@@ -1328,6 +1334,19 @@ export interface GridConfig {
1328
1334
  */
1329
1335
  columnTagFilter?: boolean | { multiple?: boolean; label?: string };
1330
1336
 
1337
+ /**
1338
+ * Show a small chip in the grid chrome that reads how many rows an anomaly
1339
+ * shadow column has flagged, and filters the grid to exactly those when it is
1340
+ * clicked (BACKLOG-0000799).
1341
+ *
1342
+ * Off by default, and it draws nothing unless a column declares a
1343
+ * `shadow: { kind: 'anomalyFlag' }`. The count and the filter both read that
1344
+ * one shadow column, so the number on the chip is the number of rows the
1345
+ * click reveals. `column` names the base column to summarise when more than
1346
+ * one anomaly-flag shadow is present; `label` overrides the chip's wording.
1347
+ */
1348
+ anomalySummary?: boolean | { column?: string; label?: string };
1349
+
1331
1350
  /**
1332
1351
  * Open a row on a form when it is double-clicked.
1333
1352
  *
@@ -1909,6 +1928,14 @@ export interface GridConfig {
1909
1928
  actions?: false | (RailActionName | '-' | RailAction)[];
1910
1929
  /** File name for the export action, without the extension. */
1911
1930
  exportName?: string;
1931
+ /**
1932
+ * Put the native annotation tools — pen, arrow, rectangle, highlighter — on
1933
+ * the rail. Off by default; each is a real toggle button that shows pressed
1934
+ * while it is the tool in use and turns off when pressed again. The tools
1935
+ * also appear automatically for the duration of a presentation, so this is
1936
+ * only needed to keep them available outside one.
1937
+ */
1938
+ annotate?: boolean;
1912
1939
  };
1913
1940
  /**
1914
1941
  * A drag-and-drop group-by strip above the column header — the pattern AG
@@ -1926,6 +1953,18 @@ export interface GridConfig {
1926
1953
  /** Placeholder shown while nothing is grouped. */
1927
1954
  hint?: string;
1928
1955
  };
1956
+ /**
1957
+ * A built-in KPI/stat strip: a labelled band of {@link createStat} tiles the
1958
+ * grid places for you, above the column header. Each entry is a stat spec —
1959
+ * the same fields {@link StatConfig} takes, minus `grid` and `container`,
1960
+ * which the grid supplies — so a strip tile and a hand-placed one are the same
1961
+ * object. The tiles follow the grid's filters, recomputing on every change
1962
+ * exactly as a stand-alone stat does.
1963
+ *
1964
+ * Off by default and non-breaking, matching `groupPanel`: no `kpis` means no
1965
+ * band and no cost. It reuses `createStat` and reimplements no compute.
1966
+ */
1967
+ kpis?: Array<Omit<StatConfig, 'grid' | 'container'>>;
1929
1968
  /** The quick filter's initial text. */
1930
1969
  quickFilterText?: string;
1931
1970
  /**
@@ -2371,6 +2410,17 @@ export interface StatisticsApi {
2371
2410
  reduce(colId: string, fn: string): unknown;
2372
2411
  /** Everything worth knowing about one column, in one pass each. */
2373
2412
  profile(colId: string): ColumnProfile | null;
2413
+ /**
2414
+ * The rows that do not belong (BACKLOG-0000749): anomaly detection over the
2415
+ * filtered rows by the robust modified z-score (`modifiedZScore`, the
2416
+ * default), Tukey's IQR fences (`iqr`), or multivariate Mahalanobis distance
2417
+ * over the chosen columns (`mahalanobis`). Every flagged row carries the score
2418
+ * behind it and the reason for it, so a flag is explainable rather than a
2419
+ * verdict from nowhere. Non-numeric columns are returned under `skipped`.
2420
+ */
2421
+ anomalies(opts?: { columns?: string[];
2422
+ method?: 'modifiedZScore' | 'iqr' | 'mahalanobis';
2423
+ threshold?: number; k?: number; p?: number }): AnomalyReport;
2374
2424
  /**
2375
2425
  * Which columns differ most between the filtered subset and the whole
2376
2426
  * population it was drawn from, ranked by effect size — never by a p-value.
@@ -2390,6 +2440,20 @@ export interface StatisticsApi {
2390
2440
  * on one side alone is returned under `unmatched`.
2391
2441
  */
2392
2442
  datasetVsDataset(other: Grid, opts?: { columns?: string[] }): DatasetComparison;
2443
+ /**
2444
+ * Is the difference between two groups real? A two-sample test returned as
2445
+ * data to interpret — never a verdict (BACKLOG-0000750). The significance
2446
+ * boundary the comparison story (653, 735) stopped short of: those rank by how
2447
+ * *much* columns differ and return no p-value; this answers *how sure* for one
2448
+ * chosen pair of groups and hands the p-value back as data. There is no
2449
+ * `significant` flag, no badge, and no multiple-comparison correction. The
2450
+ * rows are split by `opts.by`, the test is chosen by the column's family and
2451
+ * named in the result (overridable with `opts.test`): Welch's t or
2452
+ * Mann-Whitney U for a numeric column, chi-square for a categorical one. Every
2453
+ * result pairs a confidence interval on the difference with the effect size,
2454
+ * so it is always "how big and how sure".
2455
+ */
2456
+ compareGroups(colId: string, opts: TwoSampleSpec): GroupComparison | null;
2393
2457
  /** Pearson's correlation between two columns. */
2394
2458
  correlation(a: string, b: string): number | null;
2395
2459
  /** Covariance, a correlation before the scales are divided out. */
@@ -2558,12 +2622,64 @@ export function openWindow(
2558
2622
  now?: () => number,
2559
2623
  ): Window;
2560
2624
 
2625
+ /**
2626
+ * The anomaly-detection methods (BACKLOG-0000749): the robust univariate
2627
+ * modified z-score, Tukey's IQR fences, and multivariate Mahalanobis distance.
2628
+ * Interpretable statistics with written-down cuts, never a black box.
2629
+ */
2630
+ export const ANOMALY_METHODS: readonly ('modifiedZScore' | 'iqr' | 'mahalanobis')[];
2631
+
2632
+ /**
2633
+ * Per-row modified z-scores and flags for one column of readings — the robust
2634
+ * outlier score on the median and MAD (`0.6745·(x − median)/MAD`), flagged past
2635
+ * `threshold` (default 3.5). Robust to the outliers themselves: one wild reading
2636
+ * cannot inflate the spread and hide. A non-finite reading and a zero-MAD column
2637
+ * yield a null score and no flag rather than an invented one.
2638
+ */
2639
+ export function modifiedZScores(
2640
+ values: ArrayLike<number>,
2641
+ opts?: { threshold?: number },
2642
+ ): { median: number | null; mad: number | null; threshold: number;
2643
+ scores: (number | null)[]; flags: boolean[]; flagged: number };
2644
+
2645
+ /**
2646
+ * Tukey's fences for one column: `[Q1 − k·IQR, Q3 + k·IQR]` (default `k = 1.5`),
2647
+ * the same fence the box plot draws, with R type 7 quartiles. Null when there
2648
+ * are no readings.
2649
+ */
2650
+ export function iqrFences(
2651
+ values: ArrayLike<number>,
2652
+ opts?: { k?: number },
2653
+ ): { q1: number; q3: number; iqr: number; lower: number; upper: number; k: number } | null;
2654
+
2655
+ /**
2656
+ * Mahalanobis distance of every row from the joint centre, in the metric of the
2657
+ * data's own sample covariance, cut at a χ² quantile (default the 0.975 point).
2658
+ * Catches a row impossible only in combination, which a per-column scan misses.
2659
+ * A row with any missing coordinate gets a null distance; a singular covariance
2660
+ * is ridge-regularised and reported as `singular` rather than throwing.
2661
+ */
2662
+ export function mahalanobis(
2663
+ matrix: number[][],
2664
+ opts?: { p?: number; ridge?: number },
2665
+ ): { center: number[]; df: number; cutoff: number; singular: boolean; used: number;
2666
+ distances: (number | null)[]; squared: (number | null)[]; flags: boolean[];
2667
+ flagged: number } | null;
2668
+
2561
2669
  export type ShadowKind =
2562
2670
  | 'updates' | 'updatedAt' | 'sinceUpdate' | 'delta' | 'deltaPercent'
2563
2671
  | 'rate' | 'history' | 'firstValue' | 'streak'
2564
2672
  /** Where the row sits among the others, over every tracked row. */
2565
2673
  | 'rank' | 'rankAsc' | 'rankChange' | 'percentile' | 'quartile'
2566
2674
  | 'zScore' | 'shareOfTotal'
2675
+ /**
2676
+ * A robust outlier score and flag per row (BACKLOG-0000749): the modified
2677
+ * z-score on the median and MAD, and the boolean of whether it clears
2678
+ * `threshold` (default 3.5, read off the shadow declaration). Sortable,
2679
+ * filterable, groupable and exportable like any cell. Null where there is no
2680
+ * robust spread to score against.
2681
+ */
2682
+ | 'anomalyScore' | 'anomalyFlag'
2567
2683
  /**
2568
2684
  * The row's pass/fail verdict against a hard-limit spec, as a value:
2569
2685
  * `'PASS'`, `'WARN'` or `'FAIL'`. Sortable, filterable, groupable and
@@ -2712,6 +2828,69 @@ export interface SubsetComparison {
2712
2828
  measures: { numeric: string; categorical: string; common: string };
2713
2829
  }
2714
2830
 
2831
+ export interface AnomalyReason {
2832
+ /** The column that put this row over the line. */
2833
+ column: string;
2834
+ /** The column's display name, or its id. */
2835
+ name: string;
2836
+ /** The row's value in that column. */
2837
+ value: number;
2838
+ /** The modified z-score, for the `modifiedZScore` method. */
2839
+ score?: number;
2840
+ /** The lower fence, for the `iqr` method. */
2841
+ lower?: number;
2842
+ /** The upper fence, for the `iqr` method. */
2843
+ upper?: number;
2844
+ /** Which rule flagged it. */
2845
+ method?: 'modifiedZScore' | 'iqr';
2846
+ }
2847
+
2848
+ export interface AnomalyRow {
2849
+ /** The row key — stable across a sort or a feed, where the index is not. */
2850
+ rowKey: string | null;
2851
+ /** The physical row index at the time of the call. */
2852
+ index: number;
2853
+ /**
2854
+ * The row's headline score: its most extreme modified z-score across the
2855
+ * flagging columns (univariate), the Mahalanobis distance (multivariate), or
2856
+ * null for the IQR method, which has no single score.
2857
+ */
2858
+ score: number | null;
2859
+ /** The squared Mahalanobis distance, for the `mahalanobis` method. */
2860
+ squared?: number | null;
2861
+ /** Why this row was flagged: the columns and how far, so it is explainable. */
2862
+ why: AnomalyReason[];
2863
+ }
2864
+
2865
+ export interface AnomalyReport {
2866
+ /** Which rule produced the report. */
2867
+ method: 'modifiedZScore' | 'iqr' | 'mahalanobis';
2868
+ /** The IQR fence multiplier, for the `iqr` method. */
2869
+ k?: number;
2870
+ /** How many rows the scan ran over. */
2871
+ n: number;
2872
+ /** The flagged rows, worst first. */
2873
+ rows: AnomalyRow[];
2874
+ /** How many rows were flagged. */
2875
+ flagged: number;
2876
+ /** The column ids that were not numeric and so could not be scored. */
2877
+ skipped: string[];
2878
+ /** How many numeric columns were scored (univariate). */
2879
+ scored?: number;
2880
+ /** Per-column summaries (univariate): the centre, spread and fence per column. */
2881
+ columns?: unknown;
2882
+ /** The degrees of freedom of the χ² cut (multivariate). */
2883
+ df?: number;
2884
+ /** The χ² cut the squared distance is compared against (multivariate). */
2885
+ cutoff?: number | null;
2886
+ /** The joint centre the distances are measured from (multivariate). */
2887
+ center?: number[];
2888
+ /** How many complete rows defined the metric (multivariate). */
2889
+ used?: number;
2890
+ /** Whether the covariance was singular and had to be regularised (multivariate). */
2891
+ singular?: boolean;
2892
+ }
2893
+
2715
2894
  export interface DatasetColumnDifference {
2716
2895
  /** The column id, present on both grids. */
2717
2896
  column: string;
@@ -2753,6 +2932,92 @@ export interface DatasetComparison {
2753
2932
  measures: { numeric: string; categorical: string; common: string };
2754
2933
  }
2755
2934
 
2935
+ /** How {@link StatisticsApi.compareGroups} splits the rows and picks a test. */
2936
+ export interface TwoSampleSpec {
2937
+ /** The column whose values split the rows into groups. Required. */
2938
+ by: string;
2939
+ /** The two group values to compare. The two most frequent when omitted. */
2940
+ groups?: [unknown, unknown];
2941
+ /**
2942
+ * Force a test rather than choosing by column family. `auto` (the default)
2943
+ * picks Welch or Mann-Whitney for a numeric column and chi-square for a
2944
+ * categorical one; the choice is always named in the result.
2945
+ */
2946
+ test?: 'auto' | 'welch' | 'mannWhitney' | 'chiSquare';
2947
+ /** The confidence level for the interval, 0 to 1. 0.95 by default. */
2948
+ confidence?: number;
2949
+ /**
2950
+ * The focal category for a chi-square difference interval, when the column has
2951
+ * more than two categories. Without it, a multi-category comparison reports no
2952
+ * scalar interval, only the effect size.
2953
+ */
2954
+ category?: unknown;
2955
+ }
2956
+
2957
+ /** The effect size paired with a two-sample test — the "how big" half. */
2958
+ export interface GroupEffectSize {
2959
+ /**
2960
+ * The named measure: `pooledStandardMeanDifference` (Cohen's d) for the
2961
+ * numeric tests, `categoricalTotalVariation` for chi-square.
2962
+ */
2963
+ name: string;
2964
+ /** The effect size in its own terms, or null when it has no scale here. */
2965
+ value: number | null;
2966
+ }
2967
+
2968
+ /** A confidence interval on the difference a two-sample test measured. */
2969
+ export interface GroupDifferenceInterval {
2970
+ /** The point estimate of the difference the interval is around. */
2971
+ estimate: number;
2972
+ lower: number;
2973
+ upper: number;
2974
+ /** The level the bounds were computed at, 0 to 1. */
2975
+ confidence: number;
2976
+ /** The method, named for honesty: `welch-t`, `hodges-lehmann`, `newcombe`. */
2977
+ method: string;
2978
+ /** For a chi-square interval, which category's share the difference is of. */
2979
+ category?: unknown;
2980
+ }
2981
+
2982
+ /**
2983
+ * The result of {@link StatisticsApi.compareGroups}: how big *and* how sure, as
2984
+ * data to interpret. Carries no significance verdict — the p-value is a number,
2985
+ * never a flag or a badge.
2986
+ */
2987
+ export interface GroupComparison {
2988
+ /** The test used, named so it is never hidden. */
2989
+ test: 'welch' | 'mannWhitney' | 'chiSquare';
2990
+ /** Whether the test was chosen automatically or forced by the caller. */
2991
+ chosenBy: 'auto' | 'override';
2992
+ /** Why this test — the column family, a normality screen, or the override. */
2993
+ reason: string;
2994
+ /** The test statistic. */
2995
+ statistic: number;
2996
+ /** What the statistic is: `t`, `U`, or `chiSquare`. */
2997
+ statisticName: string;
2998
+ /** The degrees of freedom, where the test has them; null for Mann-Whitney. */
2999
+ df: number | null;
3000
+ /**
3001
+ * The two-sided p-value, returned as data for the caller to interpret. Never
3002
+ * thresholded into a verdict here.
3003
+ */
3004
+ pValue: number;
3005
+ /** The confidence interval on the difference, or null when there is none. */
3006
+ interval: GroupDifferenceInterval | null;
3007
+ /** The paired effect size, so the p-value is never read on its own. */
3008
+ effectSize: GroupEffectSize;
3009
+ /** How many rows the first group stood on. */
3010
+ nA: number;
3011
+ /** How many rows the second group stood on. */
3012
+ nB: number;
3013
+ /** The two group values compared, as keys. */
3014
+ groups: [unknown, unknown];
3015
+ /** False when either group is under the reliability floor. */
3016
+ reliable: boolean;
3017
+ /** The test's method, named per the reference-suite honesty rule. */
3018
+ method: string;
3019
+ }
3020
+
2756
3021
  export interface FormattingApi {
2757
3022
  list(scope?: FormattingScope): FormattingRule[];
2758
3023
  all(): Record<FormattingScope, FormattingRule[]>;
@@ -3148,6 +3413,20 @@ export interface EditApi {
3148
3413
  undo(): void;
3149
3414
  redo(): void;
3150
3415
  setCells(writes: { key: string; colId: string; value: unknown }[], type?: 'cell' | 'fill' | 'paste'): number;
3416
+ /**
3417
+ * Set one value across a block of cells as a single undoable step (§12, card
3418
+ * 740). Defaults to the selected range; read-only and non-editable cells are
3419
+ * skipped and every write runs the normal parse/validate path.
3420
+ */
3421
+ bulkSet(value: unknown, opts?: { cells?: { key: string; colId: string }[] }): number;
3422
+ /**
3423
+ * Fill a selected range from its leading edge as one undoable step (§12, card
3424
+ * 740). The default copies the anchor across the range (Excel's Ctrl+D and its
3425
+ * natural siblings); `series: true` extrapolates a numeric or date series from
3426
+ * the first one or two cells of each line, falling back to a copy for types
3427
+ * with no series. `direction` defaults to `'down'`.
3428
+ */
3429
+ fill(opts?: { direction?: 'down' | 'up' | 'left' | 'right'; series?: boolean; range?: CellRange }): number;
3151
3430
  pasteInto(anchor: { key: string; colId: string }, text: string, extent?: { rows?: number; columns?: number }): number;
3152
3431
  /** Whether a bulk paste is previewed before it commits (`edit.pastePreview`, §12). */
3153
3432
  readonly pastePreview: boolean;
@@ -3947,7 +4226,10 @@ export interface ColumnMenuParams {
3947
4226
  /** The rail's built-in action names, plus `'-'` for a divider. */
3948
4227
  export type RailActionName =
3949
4228
  | 'undo' | 'redo' | 'pause' | 'restore' | 'maximise'
3950
- | 'export' | 'excel' | 'clipboard' | 'print';
4229
+ | 'export' | 'excel' | 'clipboard' | 'print'
4230
+ // The native annotation tools, on the rail when `toolPanel.annotate` is set
4231
+ // or while a presentation runs. Each is toggleable (see `RailAction.active`).
4232
+ | 'pen' | 'arrow' | 'rect' | 'highlight';
3951
4233
 
3952
4234
  /** What a host rail action's `run` is handed. */
3953
4235
  export interface RailActionParams {
@@ -3962,6 +4244,14 @@ export interface RailAction {
3962
4244
  icon?: string | (() => string);
3963
4245
  run(params: RailActionParams): void;
3964
4246
  enabled?(): boolean;
4247
+ /**
4248
+ * Marks the action as a toggle and reports whether it is currently on. When
4249
+ * present the rail renders `aria-pressed` and a pressed style, re-read on
4250
+ * every repaint; a one-shot action omits it and is unchanged. This is the
4251
+ * hook the native annotation tools use, and it is available to a host button
4252
+ * that is itself a toggle.
4253
+ */
4254
+ active?(): boolean;
3965
4255
  }
3966
4256
 
3967
4257
  /** The result of evaluating a formula a user typed into a cell (spec 8.11). */
@@ -4423,15 +4713,18 @@ export function odataAdapter(options: {
4423
4713
  url: string; fetch?: typeof fetch; headers?: Record<string, string>;
4424
4714
  count?: boolean; search?: boolean;
4425
4715
  /**
4426
- * The key property a cell update targets in its entity-key URL segment
4427
- * (`/Orders(<key>)`). Write-back only (§7 OData, wave 1).
4716
+ * The key property every write addresses a row by in its entity-key URL
4717
+ * segment (`/Orders(<key>)`), and that an add-row is rekeyed to from the
4718
+ * created entity. Write-back only (§7 OData).
4428
4719
  */
4429
4720
  key?: string;
4430
4721
  /**
4431
- * Opt the adapter into cell write-back. `false` (the default) declares the
4432
- * source read-only; `true` advertises `mutate: { update: true, returning: 'row' }`
4433
- * so a committed cell edit is persisted with `PATCH`. Wave 1 wires `update`
4434
- * only; append and delete are deferred.
4722
+ * Opt the adapter into write-back. `false` (the default) declares the source
4723
+ * read-only; `true` advertises `mutate: { update: true, delete: true, append:
4724
+ * true, returning: 'row' }` so a committed cell edit is persisted with
4725
+ * `PATCH`, a row delete with `DELETE /EntitySet(key)`, and an add-row with
4726
+ * `POST /EntitySet` reading the created entity back (§7 OData,
4727
+ * BACKLOG-0000766, BACKLOG-0000795).
4435
4728
  */
4436
4729
  edit?: boolean;
4437
4730
  }): PushdownAdapter & { urlFor(query: RemoteRequest): string };
@@ -4447,18 +4740,27 @@ export function restAdapter(options: {
4447
4740
  encodeFilter?: (filters: object) => string;
4448
4741
  rows?: (body: unknown) => unknown[]; total?: (body: unknown, rows: unknown[]) => number;
4449
4742
  /**
4450
- * Opt the adapter into cell write-back. `false` (the default) declares the
4451
- * source read-only; `true` advertises `mutate: { update: true, delete: true, returning }`
4452
- * so a committed cell edit is persisted with `PATCH` and a row delete with
4453
- * `DELETE`. Append needs the row-keyed pending engine and is refused loudly.
4743
+ * Opt the adapter into write-back. `false` (the default) declares the source
4744
+ * read-only; `true` advertises `mutate: { update: true, delete: true, append:
4745
+ * true, returning }` so a committed cell edit is persisted with `PATCH`, a row
4746
+ * delete with `DELETE`, and an add-row with `POST` to the collection URL
4747
+ * (§7 REST, BACKLOG-0000769, BACKLOG-0000795).
4454
4748
  */
4455
4749
  edit?: boolean;
4456
4750
  /**
4457
4751
  * The reconcile contract for a successful write (§5.1). `'none'` (the default)
4458
4752
  * is last-write-wins — the optimistic value stands; `'row'` reads the server's
4459
- * authoritative row (via {@link writeRow}) back before confirm.
4753
+ * authoritative row (via {@link writeRow}) back before confirm; `'key'` reads
4754
+ * only the server-assigned key. An add-row needs `'row'` or `'key'` so the
4755
+ * temp row can be rekeyed to its server key.
4460
4756
  */
4461
- returning?: 'row' | 'none';
4757
+ returning?: 'row' | 'key' | 'none';
4758
+ /**
4759
+ * The property an add-row response carries the server-assigned key in, read
4760
+ * back (through {@link writeRow}) to rekey the optimistic row. Defaults to
4761
+ * `id`. Write-back only.
4762
+ */
4763
+ keyField?: string;
4462
4764
  /**
4463
4765
  * Full control of a mutation's HTTP shape, overriding the default verb map and
4464
4766
  * URL. Given the {@link MutationOp}, return the method, url and optional
@@ -4468,11 +4770,13 @@ export function restAdapter(options: {
4468
4770
  /**
4469
4771
  * The endpoint a single mutation targets, when the default `${url}/${key}` is
4470
4772
  * not what the service uses. Ignored when {@link encodeMutation} is supplied.
4773
+ * Addresses an existing row; an add-row POSTs to the collection `url` instead.
4471
4774
  */
4472
4775
  writeUrlFor?: (op: MutationOp) => string;
4473
4776
  /**
4474
- * Pull the authoritative row out of a write response when `returning: 'row'`.
4475
- * Tolerates the plain entity, a `{ row }` or a `{ data }` envelope by default.
4777
+ * Pull the authoritative row out of a write response when `returning: 'row'`,
4778
+ * and the created row an add-row reads its key from. Tolerates the plain
4779
+ * entity, a `{ row }` or a `{ data }` envelope by default.
4476
4780
  */
4477
4781
  writeRow?: (body: unknown) => unknown;
4478
4782
  }): PushdownAdapter & { urlFor(query: RemoteRequest): string };
@@ -4498,21 +4802,25 @@ export function duckdbAdapter(options: {
4498
4802
  /** Columns to select. Everything by default. */
4499
4803
  fields?: string[];
4500
4804
  /**
4501
- * The key column a cell update targets in its `WHERE`. Write-back is refused
4502
- * unless this names a real column, because an `UPDATE` without a unique key
4503
- * could touch more than one row (§7 DuckDB, wave 1).
4805
+ * The key column an update and a delete target in their `WHERE`, and that an
4806
+ * add-row is rekeyed by. Write-back is refused unless this names a real column,
4807
+ * because an `UPDATE`/`DELETE` without a unique key could touch more than one
4808
+ * row (§7 DuckDB). Defaults to `id`.
4504
4809
  */
4505
4810
  keyField?: string;
4506
4811
  /**
4507
- * Allow cell updates against a plain writable table. `false` (the default)
4508
- * keeps the source read-only, so a `from` that is a view or an expression can
4509
- * never be mutated by accident. Wave 1 wires `update` only.
4812
+ * Allow write-back against a plain writable table. `false` (the default) keeps
4813
+ * the source read-only, so a `from` that is a view or an expression can never
4814
+ * be mutated by accident. Enables `update`, `delete` and `append`
4815
+ * (BACKLOG-0000765, BACKLOG-0000795).
4510
4816
  */
4511
4817
  writable?: boolean;
4512
4818
  /**
4513
- * The reconcile contract for a successful update (§5.1). `'row'` (the default)
4819
+ * The reconcile contract for a successful write (§5.1). `'row'` (the default)
4514
4820
  * appends `RETURNING *` and reconciles server truth (computed columns,
4515
- * triggers); `'none'` keeps the optimistic value (last-write-wins).
4821
+ * triggers); `'none'` keeps the optimistic value (last-write-wins). An add-row
4822
+ * always `RETURNING`s at least the key column regardless, since it needs that
4823
+ * key to rekey the temp row.
4516
4824
  */
4517
4825
  returning?: 'row' | 'none';
4518
4826
  }): PushdownAdapter & { sqlFor(query: RemoteRequest): { sql: string; params: unknown[] } };
@@ -4557,6 +4865,58 @@ export function dfqlAdapter(options: {
4557
4865
  encodeCreate?: (row: unknown) => Record<string, unknown>;
4558
4866
  }): PushdownAdapter & { linesFor(query: RemoteRequest): object[] };
4559
4867
 
4868
+ /**
4869
+ * An adapter for a GraphQL endpoint (BACKLOG-0000741).
4870
+ *
4871
+ * GraphQL has no fixed query semantics — a filter, a sort and pagination are
4872
+ * whatever the schema defines — so this adapter is configured, not zero-config.
4873
+ * The caller supplies `buildQuery`, which turns the pushed plan into the
4874
+ * `{ query, variables }` body a GraphQL endpoint is POSTed, and `parseResponse`,
4875
+ * which reads the operation's `data` back into `{ rows, total }`. Sensible
4876
+ * defaults cover an offset/limit list with a `totalCount` and a Relay cursor
4877
+ * connection (`first`/`after` with `pageInfo`); either is replaced by passing
4878
+ * the hook.
4879
+ *
4880
+ * The default `buildQuery` pushes only the window and asks for the total, so the
4881
+ * default capabilities are `range` and `total` and nothing else: filter, sort
4882
+ * and quick are left absent and the grid finishes them over the window. Declare
4883
+ * `operators`/`capabilities` only alongside a `buildQuery` that genuinely emits
4884
+ * them, or the grid returns the wrong rows silently.
4885
+ *
4886
+ * A Relay cursor connection is forward-only: a deep window is reached by paging
4887
+ * forward to it, which costs round trips proportional to its offset. Offset
4888
+ * pagination jumps straight to the window. `buildMutation` opts the write path
4889
+ * in and is a declared follow-up (the write-back wave); `capabilities.mutate` is
4890
+ * `false` by declaration until it is wired.
4891
+ */
4892
+ export function graphqlAdapter(options: {
4893
+ /** The GraphQL endpoint, POSTed a `{ query, variables }` body. Required. */
4894
+ url: string;
4895
+ fetch?: typeof fetch; headers?: Record<string, string>;
4896
+ /** The root query field the default query selects from. `items` by default. */
4897
+ field?: string;
4898
+ /** Field names for the default query's selection set. */
4899
+ fields?: string[];
4900
+ /** A raw selection set (for nested fields), overriding `fields`. */
4901
+ selection?: string;
4902
+ /** `offset` (offset/limit list) or `cursor` (Relay connection). `offset` by default. */
4903
+ pagination?: 'offset' | 'cursor';
4904
+ /** The page size for the whole-result and forward-cursor walks. */
4905
+ pageSize?: number;
4906
+ /** Rename the pagination variables the adapter drives per page. */
4907
+ vars?: Partial<Record<'offset' | 'limit' | 'first' | 'after', string>>;
4908
+ capabilities?: PushdownCapabilities; operators?: string[];
4909
+ /** Turn the pushed plan into a GraphQL operation `{ query, variables }`, replacing the default. */
4910
+ buildQuery?: (request: RemoteRequest) => object;
4911
+ /** Read the operation's `data` into `{ rows, total, pageInfo? }`, replacing the default. */
4912
+ parseResponse?: (data: object) => object;
4913
+ /** Turn a mutation into a GraphQL operation (write-back follow-up). */
4914
+ buildMutation?: (op: object) => object;
4915
+ }): PushdownAdapter & {
4916
+ buildQuery(query: RemoteRequest): { query: string; variables: object };
4917
+ parseResponse(data: object): { rows: unknown[]; total: number };
4918
+ };
4919
+
4560
4920
  export function createGrid(element: HTMLElement, config?: GridConfig): Grid;
4561
4921
  export function createHeadlessGrid(config?: GridConfig): Grid;
4562
4922
 
@@ -4982,8 +5342,22 @@ export interface ChartSpec {
4982
5342
  * The events a chart raises.
4983
5343
  *
4984
5344
  * A chart's own, not the grid's: `grid.on` takes {@link EventName} and knows
4985
- * nothing about these. `point:click` is the one most callers want, it is how a
4986
- * click on a mark becomes a filter on the grid.
5345
+ * nothing about these. There is no `point:click`, `point:hover` or
5346
+ * `series:toggle`; the events are the flat names below and `click` is the one
5347
+ * most callers want, it is how a click on a mark becomes a filter on the grid.
5348
+ *
5349
+ * `click` and `hover` carry a **flat** payload — there is no `point` wrapper:
5350
+ * `{ label, category, column, value, series, rowKeys, native, preventDefault }`.
5351
+ * `column` is the grid column the mark filters on and `category` the value to
5352
+ * filter it to; `value` is the measure when a single series sits under the mark,
5353
+ * otherwise null with the per-series numbers in `series`; `rowKeys` are the
5354
+ * source rows behind the mark; `native` is the DOM event.
5355
+ *
5356
+ * `click` fires whether or not the spec sets `filterOnClick`, and it fires
5357
+ * *before* any filter is applied: call `preventDefault()` on the payload to stop
5358
+ * the chart filtering the grid and take the click over yourself. With
5359
+ * `filterOnClick: true` in the spec the chart filters the grid itself on the
5360
+ * clicked mark's `column`/`category` unless a handler prevented it.
4987
5361
  */
4988
5362
  export type ChartEventName =
4989
5363
  | 'click' | 'hover' | 'leave' | 'focus'
@@ -5063,6 +5437,9 @@ declare module 'lattice-grid/modules/react' {
5063
5437
  * React nor the grid: you pass both in. That is what keeps the package's
5064
5438
  * promise of no runtime dependencies, and what stops an adapter disagreeing
5065
5439
  * with the grid version already loaded.
5440
+ *
5441
+ * The live grid is reached through a forwarded ref: `ref.current.grid` is the
5442
+ * same `Grid` the vanilla `createGrid` returns, or null before mount.
5066
5443
  */
5067
5444
  export function createLatticeGrid(deps: { React: unknown; createGrid: unknown }): unknown;
5068
5445
  /** Every grid event, as the prop name a React caller writes. */
@@ -5072,14 +5449,37 @@ declare module 'lattice-grid/modules/react' {
5072
5449
  }
5073
5450
 
5074
5451
  declare module 'lattice-grid/modules/vue' {
5075
- export function createLatticeGrid(deps: { Vue?: unknown; createGrid: unknown }): unknown;
5452
+ /**
5453
+ * Build the Vue 3 component.
5454
+ *
5455
+ * The Vue runtime and `createGrid` are passed in, for the same reason as the
5456
+ * React adapter: the package ships no dependencies and cannot import either.
5457
+ * The dependency key is lowercase `vue` — `createLatticeGrid({ vue, createGrid })`.
5458
+ *
5459
+ * The live grid is reached through the component's exposed `grid()` method:
5460
+ * with `ref="grid"` on the element, `this.$refs.grid.grid()` returns the same
5461
+ * `Grid` the vanilla `createGrid` returns, or null before mount.
5462
+ */
5463
+ export function createLatticeGrid(deps: { vue: unknown; createGrid: unknown }): unknown;
5076
5464
  export const EVENT_NAMES: readonly string[];
5077
5465
  export function dashedName(event: string): string;
5078
5466
  export default createLatticeGrid;
5079
5467
  }
5080
5468
 
5081
5469
  declare module 'lattice-grid/modules/svelte' {
5082
- /** A Svelte action: `use:lattice={config}`. */
5470
+ /**
5471
+ * A Svelte action: `use:lattice={config}`.
5472
+ *
5473
+ * The action owns nothing but the node the caller already has, so the grid is
5474
+ * reached one of two ways. Pass an `onGrid` callback in the action params
5475
+ * (BACKLOG-0000785): `use:lattice={{ ...config, onGrid: (g) => (grid = g) }}`
5476
+ * calls it once with the live `Grid` the moment it is built — synchronously,
5477
+ * before `ready` fires — and again if you hand the action a different
5478
+ * `onGrid`. Or read it off an event: every grid event carries the grid on its
5479
+ * `detail`, so `on:ready={(e) => e.detail.grid}` hands you the same `Grid` a
5480
+ * turn after construction. Use `onGrid` when you need the instance during the
5481
+ * first render.
5482
+ */
5083
5483
  export function createLatticeAction(deps: { createGrid: unknown }): unknown;
5084
5484
  export const EVENT_NAMES: readonly string[];
5085
5485
  export function dashedName(event: string): string;
@@ -5093,6 +5493,10 @@ declare module 'lattice-grid/modules/webcomponent' {
5093
5493
  * This module carries the grid inside it. Use it *or* `createGrid` in one
5094
5494
  * page, never both: two copies keep separate registries, and a renderer
5095
5495
  * registered through one will not appear in the other.
5496
+ *
5497
+ * The live grid is reached through the element's `grid` getter: `el.grid` is
5498
+ * the same `Grid` the vanilla `createGrid` returns, or null while the element
5499
+ * is disconnected.
5096
5500
  */
5097
5501
  export function defineLatticeGrid(tag?: string): void;
5098
5502
  export function createLatticeGridElement(deps?: object): unknown;
@@ -5102,6 +5506,11 @@ declare module 'lattice-grid/modules/webcomponent' {
5102
5506
  export function observedAttributeNames(): string[];
5103
5507
  export function domEventName(event: string): string;
5104
5508
  export class GridElementController {}
5509
+ // Core factories re-exported from this module so they bind to the one engine
5510
+ // the element already carries: a type built with these here shares the
5511
+ // element's registry rather than a second copy's (BACKLOG-0000787). Typed by
5512
+ // reference to the base package.
5513
+ export { createCurrencyType, createUnitType, registerUnitSystem, createStat } from 'lattice-grid';
5105
5514
  export default defineLatticeGrid;
5106
5515
  }
5107
5516
 
@@ -5133,6 +5542,27 @@ declare module 'lattice-grid/modules/htmx' {
5133
5542
  export const QUERY_CHANGED_EVENT: string;
5134
5543
  export const SCROLL_NEAR_END_EVENT: string;
5135
5544
  export const HTML_ROW_WARNING_THRESHOLD: number;
5545
+ // The core factory surface this module re-exports, so an htmx page builds its
5546
+ // configured columns (a currency type, a unit type, a stat) from the one
5547
+ // engine it already carries rather than a second copy (BACKLOG-0000786).
5548
+ // Typed by reference to the base package; names the base package leaves
5549
+ // untyped stay untyped here too.
5550
+ export {
5551
+ createHeadlessGrid, version, getVersion, Grid, Registry, registerModules,
5552
+ createRadixType, createUnitType, registerUnitSystem, defineUnit, UNIT_SYSTEMS, parseUnit, formatUnit,
5553
+ createCurrencyType, parseMoney, formatMoney, convertMoney, rateFunction, MISSING_RATE,
5554
+ Messages, createMessages, auditCatalogue,
5555
+ EN_GB, MESSAGE_KEYS, DEFAULT_LOCALE, formatList, resolveLocale, LOCALES, resolveCatalogue,
5556
+ EN_US, FR_FR, FR_CA, IT_IT, ES_ES, PT_BR, DE_DE, NL_NL, SV_SE, DA_DK, NB_NO, FI_FI,
5557
+ PL_PL, CS_CZ, HU_HU, RO_RO, UK_UA, EL_GR, JA_JP, AR, AR_SA,
5558
+ Window, openWindow, WINDOW_KINDS,
5559
+ evaluateFormula, referencesOf, looksLikeFormula, compileRules, ingest, ingestSync,
5560
+ createPushdownSource, planQuery, splitFilters, applyResidual, capabilitiesOf, resolveMutate, NO_CAPABILITIES,
5561
+ odataAdapter, restAdapter, dfqlAdapter, duckdbAdapter,
5562
+ createStat, deltaOf, toneOf,
5563
+ } from 'lattice-grid';
5564
+ // American licence aliases mirror the base package (dom/index.js).
5565
+ export { setLicence as setLicense, licenceInfo as licenseInfo, licenceState as licenseState } from 'lattice-grid';
5136
5566
  }
5137
5567
 
5138
5568
  declare module 'lattice-grid/modules/dhtmlx-compat' {