@toclocoinc/lattice-grid 1.15.0 → 1.17.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.15.0, type declarations
2
+ * Lattice Grid 1.17.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -341,6 +341,22 @@ export interface LookupSpec {
341
341
  export type DecorationName = 'plain' | 'fill' | 'pill' | 'dot' | 'bar' | 'heat' | 'icon';
342
342
  export type VariantName = 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'accent' | 'none' | (string & {});
343
343
 
344
+ /** A built-in threshold icon set, mapping value bands to built-in glyphs. */
345
+ export type IconSetName = 'trafficLights' | 'arrows' | 'trafficArrows' | 'ratings' | (string & {});
346
+
347
+ /**
348
+ * One band of a threshold icon set. A value clears a band when it is at least
349
+ * `min`; the highest band it clears wins. Omit `min` on the last band to make
350
+ * it the catch-all. `label` is what assistive technology announces for the
351
+ * glyph, so a screen-reader user hears the band's meaning, not only the value.
352
+ */
353
+ export interface IconBand {
354
+ min?: number;
355
+ icon: string;
356
+ label?: string;
357
+ variant?: VariantName;
358
+ }
359
+
344
360
  export interface DecorationSpec {
345
361
  type: DecorationName;
346
362
  size?: 'sm' | 'md' | 'lg';
@@ -349,6 +365,10 @@ export interface DecorationSpec {
349
365
  edge?: boolean;
350
366
  position?: 'start' | 'end';
351
367
  name?: string | Record<string, string>;
368
+ /** icon only: a built-in threshold icon set, expanded to `bands`. */
369
+ iconSet?: IconSetName;
370
+ /** icon only: value bands mapped to glyphs, first match by descending `min`. */
371
+ bands?: IconBand[];
352
372
  min?: number;
353
373
  max?: number;
354
374
  origin?: number;
@@ -738,6 +758,94 @@ export interface Source {
738
758
 
739
759
  export interface MemorySourceConfig { mode: 'memory'; columnarBelow?: number }
740
760
 
761
+ /**
762
+ * How rows are ingested into the column store.
763
+ */
764
+ export interface IngestConfig {
765
+ /**
766
+ * Retain the caller's row objects by reference so identity round-trips.
767
+ * Default `true`, the historical behaviour: `rows.data()` returns the exact
768
+ * objects you supplied, `row === sourceObject` holds, and a custom renderer
769
+ * reading `row.sourceObject` works.
770
+ *
771
+ * Set `false` to keep only the packed columns and reconstruct a plain row
772
+ * object from them on demand. This drops roughly half the resident footprint,
773
+ * but changes three behaviours: `rows.data()` returns freshly reconstructed
774
+ * objects (new object each call, so `row === sourceObject` no longer holds),
775
+ * a custom renderer that reaches for `row.sourceObject` gets a reconstruction
776
+ * rather than the original, and equality against a row becomes value-based.
777
+ * The stored values are unchanged, so `get()`, `byKey()`, `value()` and
778
+ * `values()` are unaffected.
779
+ */
780
+ retainSource?: boolean;
781
+
782
+ /**
783
+ * Release the caller's row objects from the *source layer* once the column
784
+ * store has been built, so the columns become the sole resident copy of the
785
+ * data. Default `false`, which keeps today's behaviour.
786
+ *
787
+ * `retainSource:false` stops the {@link https://en.wikipedia.org/wiki/Column-oriented_DBMS column store}
788
+ * from holding the caller's objects, but the memory source and the grid config
789
+ * still retain the supplied array by reference — so the objects stay alive and
790
+ * the resident footprint does not actually fall. This flag closes that gap: it
791
+ * clears `MemorySource`'s retained array and drops the array from the grid
792
+ * config, leaving nothing on the heap but the packed columns. That is where
793
+ * the large reduction comes from (roughly an order of magnitude at a million
794
+ * rows), not from `retainSource` on its own.
795
+ *
796
+ * Implies `retainSource:false`: dropping the caller's objects while the store
797
+ * still expects to read through them would leave the source with no data at
798
+ * all, so setting this on forces the store to reconstruct rows from columns.
799
+ * Every read is therefore served from the columns — `at()`, `byKey()`,
800
+ * `get()`, `value()`, `values()`, filtering, sorting, grouping, totals and
801
+ * export are all unaffected in their values. What changes is the same three
802
+ * identity behaviours `retainSource:false` documents: `rows.data()` returns
803
+ * freshly reconstructed objects (so `row === sourceObject` no longer holds), a
804
+ * custom renderer reaching for `row.sourceObject` gets a reconstruction, and
805
+ * equality against a row becomes value-based.
806
+ *
807
+ * One consumer cannot be served from the columns: an *impure computed column*
808
+ * (a shadow, or a rank/positional column) is deliberately never materialised
809
+ * into the store, so its handle is built by reading the source objects. Under
810
+ * `dropSourceRows` those objects are gone, so such a column reduces over
811
+ * nothing and warns once rather than returning a silently wrong figure. Do not
812
+ * enable `dropSourceRows` on a grid that sorts, filters, groups or totals on a
813
+ * shadow or a positional column.
814
+ */
815
+ dropSourceRows?: boolean;
816
+
817
+ /**
818
+ * Columnize `stream`-source ingest on a Worker so a large load does not block
819
+ * the main thread. Default `false`. When on, an arriving chunk that clears
820
+ * {@link IngestConfig.workerThreshold} is packed into typed column buffers on
821
+ * the Worker; the main thread merges the finished buffers into the store and
822
+ * renders, without running the per-field extraction pass that otherwise
823
+ * dominates ingest.
824
+ *
825
+ * This makes **stream** ingest non-blocking (remote sources already are).
826
+ * Memory and paged sources cannot be made non-blocking this way — the main
827
+ * thread must read the caller's own row objects — and are unaffected. The
828
+ * effect composes with `retainSource: false`: with it off the source keeps no
829
+ * caller-object array on the main thread at all, so the load is both
830
+ * non-blocking and lighter on memory.
831
+ *
832
+ * A column that reads through a closure — a `date` column's storage
833
+ * conversion, or a computed column — cannot cross the Worker boundary, so a
834
+ * grid with any such column columnizes on the main thread and says so once.
835
+ * Falls back silently to the main thread wherever a Worker cannot be created.
836
+ */
837
+ useWorker?: boolean;
838
+
839
+ /**
840
+ * Row count in a single stream chunk at or above which columnization is
841
+ * offloaded to the Worker when {@link IngestConfig.useWorker} is on. Default
842
+ * `10000`. A smaller first chunk is packed on the main thread, where the
843
+ * cost is trivial and the postMessage round trip would only add latency to
844
+ * time-to-first-row.
845
+ */
846
+ workerThreshold?: number;
847
+ }
848
+
741
849
  export interface PagedSourceConfig {
742
850
  mode: 'paged';
743
851
  pageSize?: number;
@@ -968,6 +1076,15 @@ export interface EditConfig {
968
1076
  commit?: (write: PendingWrite) => unknown;
969
1077
  confirm?: 'auto' | 'manual';
970
1078
  pendingTimeout?: number;
1079
+ /**
1080
+ * Show a preview of what a bulk paste will change before it commits (§12),
1081
+ * with confirm/cancel. Off by default: a paste commits straight away, exactly
1082
+ * as it always has. When on, a paste into more than one cell first opens a
1083
+ * dialog listing every cell that changes (old → new) and every cell that would
1084
+ * be rejected (permission, data-type, read-only); confirm commits precisely
1085
+ * that set through the ordinary edit path, cancel commits nothing.
1086
+ */
1087
+ pastePreview?: boolean;
971
1088
  }
972
1089
 
973
1090
  export interface PendingWrite {
@@ -1010,6 +1127,8 @@ export interface GridConfig {
1010
1127
  rowKey?: string | ((row: unknown) => string);
1011
1128
  /** Where rows come from: memory, paged, remote, stream or derived. */
1012
1129
  source?: SourceConfig;
1130
+ /** How rows are ingested into the column store. */
1131
+ ingest?: IngestConfig;
1013
1132
  /** Applied to every column before its own settings. */
1014
1133
  columnDefaults?: Column;
1015
1134
  /** Named bundles of column settings, referenced by a column's `preset`. */
@@ -1074,6 +1193,21 @@ export interface GridConfig {
1074
1193
  */
1075
1194
  cornerRadius?: boolean | number | string;
1076
1195
 
1196
+ /**
1197
+ * Shade alternate data rows (zebra striping).
1198
+ *
1199
+ * Off by default, and strictly opt-in: an existing grid must look exactly the
1200
+ * same on upgrade. When `true`, every other data row takes the theme's
1201
+ * `--lattice-surface-alt` background, which every palette already defines, so
1202
+ * dark, high-contrast and terminal stripe correctly without extra work.
1203
+ *
1204
+ * Parity follows the row's *logical* index, not its position in the DOM, so a
1205
+ * row keeps its stripe across a scroll even though the rows are recycled.
1206
+ * Structural rows — group headings, group footers and the grand total — are
1207
+ * never striped, and both selection and hover still win over the stripe.
1208
+ */
1209
+ stripedRows?: boolean;
1210
+
1077
1211
  /**
1078
1212
  * Show a bar above the column headings for filtering columns by tag.
1079
1213
  *
@@ -1228,7 +1362,12 @@ export interface GridConfig {
1228
1362
  * only the sort, filter and menu controls inside them.
1229
1363
  */
1230
1364
  showHeader?: boolean;
1231
- /** Header height in pixels. */
1365
+ /**
1366
+ * Header height in pixels. Omitted, the header takes its height from the
1367
+ * density-scaled `--lattice-header-height` token, so `density` sizes the
1368
+ * header as it sizes the rows. A number names one explicitly and outranks the
1369
+ * token.
1370
+ */
1232
1371
  headerHeight?: number;
1233
1372
  /** How many rows to render beyond the viewport. More costs memory and
1234
1373
  * smooths fast scrolling; fewer is lighter and can show a gap. */
@@ -1374,6 +1513,14 @@ export interface GridConfig {
1374
1513
  totalOnlyChangedColumns?: boolean;
1375
1514
  /** Put the total in the header rather than a footer row. */
1376
1515
  showTotalInHeader?: boolean;
1516
+ /**
1517
+ * Let the user pick a column's reduction from the column menu. On, the
1518
+ * totalling entry becomes an "Aggregate" submenu offering the aggregates the
1519
+ * column's type says are meaningful (§9.4); off, the menu keeps its plain
1520
+ * "Total this column" toggle. Off by default, so an existing grid is
1521
+ * unchanged.
1522
+ */
1523
+ aggregateChooser?: boolean;
1377
1524
  /** Render only the visible columns once there are more than this many. */
1378
1525
  columnVirtualisationAbove?: number;
1379
1526
  /** The bar beneath the grid, and which panels it carries. */
@@ -1391,6 +1538,23 @@ export interface GridConfig {
1391
1538
  */
1392
1539
  columnMenu?: boolean | ((p: ColumnMenuParams, defaults: MenuItem[]) => MenuItem[] | void);
1393
1540
 
1541
+ /**
1542
+ * Chart a selected cell range — the spreadsheet "chart this selection"
1543
+ * gesture. Off by default, so a grid opts in.
1544
+ *
1545
+ * The DOM layer draws no charts itself — the charts module is optional and
1546
+ * loaded by the host — so this is where the host wires the two together: a
1547
+ * function, or an object carrying `onChart`, is called with the grid and the
1548
+ * selected range when the reader chooses "Chart selection" from the cell
1549
+ * menu. The handler typically calls `chartRange` from
1550
+ * `lattice-grid/modules/charts`. `true` offers the item and emits nothing
1551
+ * extra; supply a handler to have it actually draw.
1552
+ */
1553
+ rangeChart?:
1554
+ | boolean
1555
+ | ((grid: Grid, range: CellRange) => void)
1556
+ | { onChart?: (grid: Grid, range: CellRange) => void };
1557
+
1394
1558
  /**
1395
1559
  * The `?` keyboard shortcut overlay. `false` suppresses it, for a host
1396
1560
  * that wants `?` for itself. Default true.
@@ -1453,9 +1617,11 @@ export interface GridConfig {
1453
1617
  * Keep the enclosing group headings pinned above the viewport while
1454
1618
  * scrolling inside a group.
1455
1619
  *
1456
- * On by default, stacking at most two. `false` turns it off; a number, or
1457
- * `{ depth }`, sets how many may stack: each costs a row of viewport, so a
1458
- * deep grouping would otherwise spend the screen describing itself.
1620
+ * Off by default — a deliberate product default; sticky group headers are
1621
+ * opt-in. `true` turns it on, stacking at most two; a number, or `{ depth }`,
1622
+ * sets how many may stack: each costs a row of viewport, so a deep grouping
1623
+ * would otherwise spend the screen describing itself. `false` is off, the
1624
+ * same as leaving it unset.
1459
1625
  */
1460
1626
  stickyGroupHeaders?: boolean | number | { depth?: number };
1461
1627
  /**
@@ -1503,6 +1669,22 @@ export interface GridConfig {
1503
1669
  /** File name for the export action, without the extension. */
1504
1670
  exportName?: string;
1505
1671
  };
1672
+ /**
1673
+ * A drag-and-drop group-by strip above the column header — the pattern AG
1674
+ * Grid calls the row-group panel. Drag a column heading into it to group by
1675
+ * that column; the active groups show as removable, reorderable chips, and
1676
+ * reordering the chips changes the nesting order. It is keyboard-operable
1677
+ * (arrows navigate, Shift+arrow reorders, Delete ungroups, and an add control
1678
+ * groups any column), and every change is announced through the live region,
1679
+ * which is why it also addresses the drag-only complaint of BACKLOG-0000429.
1680
+ *
1681
+ * Off by default and non-breaking, matching `toolPanel`. It drives the same
1682
+ * grouping model as `grid.columns.group()`; it reimplements nothing.
1683
+ */
1684
+ groupPanel?: boolean | {
1685
+ /** Placeholder shown while nothing is grouped. */
1686
+ hint?: string;
1687
+ };
1506
1688
  /** The quick filter's initial text. */
1507
1689
  quickFilterText?: string;
1508
1690
  /**
@@ -1767,6 +1949,103 @@ export interface PushdownPlan {
1767
1949
  needsAll: boolean;
1768
1950
  /** Which parts could not be pushed: `filter`, `sort`, `quick`. */
1769
1951
  unpushed: string[];
1952
+ /**
1953
+ * Whether the whole result was fetched because `fullDataset` is on, rather
1954
+ * than only because residual work forced it. When true, totals and statistics
1955
+ * reduce over the whole matching set and the windowed-stat warning is silent.
1956
+ */
1957
+ full: boolean;
1958
+ /**
1959
+ * Per-aggregate provenance, present only when the last request computed
1960
+ * aggregates (BACKLOG-0000730 Part B): which statistics the engine computed
1961
+ * and which the client did, with the class the pushdown map assigned each.
1962
+ * Under grouping it also carries the `groupBy` the subtotals were computed
1963
+ * over. Build-time inspection, not a runtime per-figure marker.
1964
+ */
1965
+ aggregates?: {
1966
+ engine: AggregateProvenance[];
1967
+ client: AggregateProvenance[];
1968
+ groupBy?: string[];
1969
+ };
1970
+ }
1971
+
1972
+ /**
1973
+ * Opt-in, sticky full-dataset pull for a pushdown/remote source
1974
+ * (BACKLOG-0000730). Off by default. When enabled, the source materialises the
1975
+ * entire matching set client-side once per query signature and serves every
1976
+ * window, total and statistic from it, so those figures are computed over the
1977
+ * whole set rather than the loaded window. A set past either limit is refused
1978
+ * with a visible `source:error` — never silently truncated.
1979
+ */
1980
+ export interface PushdownFullDatasetConfig {
1981
+ /** Sticky: hold the whole matching set client-side. Default `false`. */
1982
+ enabled?: boolean;
1983
+ /** Refuse (visible error) past this many rows. Default `1_000_000`. */
1984
+ maxRows?: number;
1985
+ /** Refuse past this estimated heap cost, in bytes. Default `512 * 1024 * 1024`. */
1986
+ maxBytesEstimate?: number;
1987
+ }
1988
+
1989
+ /** How one requested aggregate should be computed. */
1990
+ export type AggregateMode = 'engine' | 'client' | 'engine-if-identical';
1991
+
1992
+ /**
1993
+ * Design-time aggregate-pushdown policy for a pushdown source
1994
+ * (BACKLOG-0000730 Part B, ungrouped). The developer chooses, at grid setup
1995
+ * before render, whether each statistic is computed by the engine (fast, over
1996
+ * the matching set) or client-side (the grid's exact definition, needs a
1997
+ * full-dataset pull). It is fixed for the life of the grid, never a runtime
1998
+ * toggle, and never surfaced to an end user.
1999
+ *
2000
+ * Absent, every aggregate is computed client-side — today's behaviour, so no
2001
+ * existing caller regresses. `engine-if-identical` is the recommended setting
2002
+ * for a windowed DuckDB source: it pushes only the statistics whose engine
2003
+ * result is verified identical to the grid kernel, keeping the documented
2004
+ * MAY-DIFFER stats (e.g. `mode`) client-side. The engine is used only when the
2005
+ * filter is fully pushed; a residual filter forces every aggregate client-side,
2006
+ * so an engine figure and a client figure never mix in one result set.
2007
+ */
2008
+ export interface PushdownAggregatesConfig {
2009
+ /**
2010
+ * The default policy for stats the engine can express. `'engine'` pushes
2011
+ * everything expressible (using the engine's method for MAY-DIFFER stats);
2012
+ * `'engine-if-identical'` pushes only the verified-identical ones; `'client'`
2013
+ * computes everything client-side. Default `'client'`.
2014
+ */
2015
+ default?: AggregateMode;
2016
+ /** Per-stat overrides, winning over `default`. A stat the engine cannot
2017
+ * express (`weightedQuantile`) is always client-side regardless. */
2018
+ overrides?: Record<string, 'engine' | 'client'>;
2019
+ }
2020
+
2021
+ /**
2022
+ * One aggregate the grid asks the source to compute over the matching set.
2023
+ * `params` carries e.g. `{ share: 0.1 }` so an adapter emits the matching SQL;
2024
+ * `weight` names the second column for a two-column stat like `correlation`.
2025
+ */
2026
+ export interface AggregateRequest {
2027
+ /** Keys the result back to the request. */
2028
+ id: string;
2029
+ /** The column to reduce. */
2030
+ col: string;
2031
+ /** The statistic name, as used in `total: '<name>'`. */
2032
+ fn: string;
2033
+ /** The second column, for a two-column statistic. */
2034
+ weight?: string;
2035
+ /** Parameters the statistic takes, e.g. a trim share. */
2036
+ params?: Record<string, unknown>;
2037
+ }
2038
+
2039
+ /** How one aggregate was routed, for `lastPlan()` provenance. */
2040
+ export interface AggregateProvenance {
2041
+ id: string;
2042
+ col: string;
2043
+ fn: string;
2044
+ /** How the engine result relates to the grid kernel. */
2045
+ class: 'identical' | 'may-differ' | 'fallback';
2046
+ /** Why it is client-side, when it is (config, fallback, or the guard). */
2047
+ reason?: string;
2048
+ weight?: string;
1770
2049
  }
1771
2050
 
1772
2051
  export interface PushdownSourceConfig {
@@ -1774,6 +2053,16 @@ export interface PushdownSourceConfig {
1774
2053
  /** The compute barrel, for applying whatever the engine could not. */
1775
2054
  compute?: object;
1776
2055
  pageSize?: number;
2056
+ /**
2057
+ * Opt-in full-dataset pull. Off unless `fullDataset.enabled` is set. See
2058
+ * {@link PushdownFullDatasetConfig}.
2059
+ */
2060
+ fullDataset?: PushdownFullDatasetConfig;
2061
+ /**
2062
+ * Design-time aggregate-pushdown policy. Absent = client-side (today's
2063
+ * behaviour). See {@link PushdownAggregatesConfig}.
2064
+ */
2065
+ aggregates?: PushdownAggregatesConfig;
1777
2066
  }
1778
2067
 
1779
2068
  export interface StatisticsApi {
@@ -2164,6 +2453,11 @@ export interface RowsApi {
2164
2453
  export interface ColumnsApi {
2165
2454
  /** Set or clear a column's totals-row reduction. */
2166
2455
  setTotal(id: string, fn: TotalName | TotalFn | null): void;
2456
+ /**
2457
+ * The aggregate names meaningful for a column, honouring its type's
2458
+ * `totals.supported` declaration (§9.4). What the aggregate chooser offers.
2459
+ */
2460
+ aggregates(id: string): TotalName[];
2167
2461
  /** Every distinct value in a column, from the dictionary where there is one. */
2168
2462
  distinct(id: string): unknown[];
2169
2463
  get(id: string): ResolvedColumn | undefined;
@@ -2186,6 +2480,13 @@ export interface ColumnsApi {
2186
2480
  move(id: string, to: number): void;
2187
2481
  pin(id: string, side: 'start' | 'end' | null): void;
2188
2482
  resize(id: string, px: number): void;
2483
+ /**
2484
+ * Set, change or clear a column's decoration at runtime (§8.7). Pass `null` to
2485
+ * clear it back to plain text. Presentation config: it is not on the undo
2486
+ * timeline and is not carried in a saved view — use `grid.formatting` for
2487
+ * durable, view-persisted conditional styling.
2488
+ */
2489
+ decorate(id: string, decoration: DecorationName | DecorationSpec | null, opts?: { variant?: VariantSpec }): void;
2189
2490
  autoSize(ids?: string | string[]): void;
2190
2491
  fit(): void;
2191
2492
  group(ids: string | string[]): void;
@@ -2269,6 +2570,18 @@ export interface EditApi {
2269
2570
  redo(): void;
2270
2571
  setCells(writes: { key: string; colId: string; value: unknown }[], type?: 'cell' | 'fill' | 'paste'): number;
2271
2572
  pasteInto(anchor: { key: string; colId: string }, text: string, extent?: { rows?: number; columns?: number }): number;
2573
+ /** Whether a bulk paste is previewed before it commits (`edit.pastePreview`, §12). */
2574
+ readonly pastePreview: boolean;
2575
+ /**
2576
+ * Compute what a paste would change, without committing (§12). The engine
2577
+ * behind `edit.pastePreview`: `changes` are the accepted writes with their old
2578
+ * and new values (and whether each actually differs), `rejected` are the cells
2579
+ * a commit would refuse, each with a reason.
2580
+ */
2581
+ previewPaste(anchor: { key: string; colId: string }, text: string, extent?: { rows?: number; columns?: number }): {
2582
+ changes: { key: string; colId: string; oldValue: unknown; newValue: unknown; changed: boolean }[];
2583
+ rejected: { key: string; colId: string; value: unknown; reason: 'permission' | 'readOnly' | 'validation' | 'locked' | 'missing' }[];
2584
+ };
2272
2585
  settle(id: string, ok: boolean, reason?: string): boolean;
2273
2586
  pending(): OpenWrite[];
2274
2587
  status(key: string, colId: string): 'pending' | null;
@@ -3265,7 +3578,51 @@ export function toneOf(direction: string, goodWhen: string): 'good' | 'bad' | 'f
3265
3578
  */
3266
3579
  export function createPushdownSource(
3267
3580
  config: PushdownSourceConfig,
3268
- ): SourceConfig & { lastPlan(): PushdownPlan | null };
3581
+ ): SourceConfig & {
3582
+ lastPlan(): PushdownPlan | null;
3583
+ /**
3584
+ * Compute a set of aggregates over the matching set, splitting them between
3585
+ * the engine and the client by the design-time `aggregates` config
3586
+ * (BACKLOG-0000730 Part B). Ungrouped, returns the engine-computed `values`
3587
+ * keyed by id. When the request carries a `groupBy`, returns `groups` instead:
3588
+ * one entry per subtotal level and the grand total (`level: 0`, produced by a
3589
+ * single `GROUP BY ROLLUP`), each with its key values and its aggregate values
3590
+ * keyed by id. The client list is what the caller computes itself over the
3591
+ * full set. Aggregates are pushed only when the filter is fully pushed and —
3592
+ * under grouping — every grouping key is a plain column the engine can group
3593
+ * by; a residual filter or an unpushable group key forces every aggregate
3594
+ * client-side (no mixed provenance).
3595
+ */
3596
+ aggregate(
3597
+ request: RemoteRequest,
3598
+ requested: AggregateRequest[],
3599
+ ): Promise<{
3600
+ values: Record<string, unknown>;
3601
+ groups?: Array<{
3602
+ keys: unknown[];
3603
+ grouping?: number[];
3604
+ level: number;
3605
+ values: Record<string, unknown>;
3606
+ }>;
3607
+ engine: AggregateProvenance[];
3608
+ client: AggregateProvenance[];
3609
+ }>;
3610
+ };
3611
+
3612
+ /**
3613
+ * The pushdown map (BACKLOG-0000730 Part B): one published record per statistic
3614
+ * giving whether the engine can express it, the DuckDB aggregate SQL it emits,
3615
+ * and whether that result is IDENTICAL to the grid's own kernel or MAY-DIFFER.
3616
+ * The single source of truth the push router, the docs and `lastPlan()` all read.
3617
+ */
3618
+ export const STAT_PUSHDOWN: Readonly<Record<string, {
3619
+ pushable: boolean;
3620
+ class: 'identical' | 'may-differ' | 'fallback';
3621
+ sql?: string;
3622
+ note?: string;
3623
+ twoColumn?: boolean;
3624
+ blankAware?: boolean;
3625
+ }>>;
3269
3626
 
3270
3627
  /**
3271
3628
  * The capability set an adapter that declares nothing is treated as having:
@@ -3623,6 +3980,12 @@ export interface ChartSpec {
3623
3980
  y?: string;
3624
3981
  /** Splits the measure into one series per distinct value. */
3625
3982
  series?: string;
3983
+ /**
3984
+ * The exact rows to chart, overriding the grid's own walk — an array, or a
3985
+ * function returning one at draw time. `chartRange` uses it to bind a chart
3986
+ * to the band of rows a selected range covers rather than the whole grid.
3987
+ */
3988
+ rows?: object[] | ((grid: Grid) => object[]);
3626
3989
  /** Several measures at once, for combo and candlestick. */
3627
3990
  measures?: ChartMeasure[];
3628
3991
  /** Endpoints, for sankey, chord and network. */
@@ -3753,6 +4116,39 @@ declare module 'lattice-grid/modules/charts' {
3753
4116
  export const SCHEMES: Readonly<Record<string, readonly string[]>>;
3754
4117
  export const PALETTE: readonly string[];
3755
4118
  export function createChart(spec: ChartSpec): Chart;
4119
+ /**
4120
+ * Chart a selected cell range. Derives the chart from the range's shape — a
4121
+ * leading text column becomes the categories, the numeric columns become the
4122
+ * measures — and returns the live chart, or null when the range has nothing
4123
+ * to measure. Respects hidden and unreadable columns. The type is a sensible
4124
+ * default the caller can change with `chart.update({ type })`.
4125
+ */
4126
+ export function chartRange(
4127
+ grid: Grid,
4128
+ opts: {
4129
+ container: Element | string;
4130
+ range?: CellRange;
4131
+ type?: ChartType;
4132
+ } & Partial<ChartSpec>,
4133
+ ): Chart | null;
4134
+ /** Would {@link chartRange} draw something for the grid's current selection? */
4135
+ export function canChartRange(grid: Grid, opts?: { range?: CellRange }): boolean;
4136
+ /**
4137
+ * Decide what a chart of a range should be, without drawing it: the type, the
4138
+ * category column, the measure columns, and a `spec` ready for `createChart`
4139
+ * — or a `reason` naming why the range cannot be charted.
4140
+ */
4141
+ export function deriveRangeSpec(
4142
+ grid: Grid,
4143
+ opts?: { range?: CellRange; type?: ChartType },
4144
+ ): {
4145
+ spec: ChartSpec | null;
4146
+ type: ChartType | null;
4147
+ x: string | null;
4148
+ measures: string[];
4149
+ columns: string[];
4150
+ reason: string | null;
4151
+ };
3756
4152
  export function registerScheme(name: string, colours: readonly string[]): void;
3757
4153
  export function resolveScheme(spec?: object): object;
3758
4154
  export function schemeNames(): string[];