@toclocoinc/lattice-grid 1.1.1 → 1.4.2

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.1.0 — type declarations
2
+ * Lattice Grid 1.4.0 — type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -18,11 +18,29 @@
18
18
 
19
19
  export type TypeName =
20
20
  | 'text' | 'number' | 'boolean' | 'date' | 'dateString' | 'object' | 'lookup'
21
+ | 'image'
21
22
  | (string & {});
22
23
 
23
- export type Align = 'start' | 'center' | 'end';
24
- export type Density = 'compact' | 'comfortable';
25
- export type Theme = 'light' | 'dark' | (string & {});
24
+ /**
25
+ * Cell and header alignment.
26
+ *
27
+ * `left` and `right` are accepted and normalised to `start` and `end`. The grid
28
+ * has no RTL handling today, so the two pairs are exact synonyms; if direction
29
+ * support lands, `start`/`end` will follow the text direction while
30
+ * `left`/`right` stay physical. `centre` is accepted alongside `center`.
31
+ */
32
+ export type Align = 'start' | 'center' | 'end' | 'left' | 'right' | 'centre';
33
+ /**
34
+ * A named preset, or a raw scale where 1 is `standard`. Row heights are
35
+ * 23.8 / 28 / 42 / 56px for the four presets; a number scales 28px.
36
+ */
37
+ export type Density = 'compact' | 'standard' | 'comfortable' | 'spacious' | number;
38
+ /**
39
+ * A shipped theme, or your own name — the value is written to `data-theme` on
40
+ * the grid's root, so `.lattice[data-theme="mine"]` is all a custom one needs.
41
+ * Unset follows the viewer's `prefers-color-scheme`.
42
+ */
43
+ export type Theme = 'light' | 'dark' | 'high-contrast' | 'terminal' | (string & {});
26
44
  export type ColumnRef = string;
27
45
  export type Comparator = (
28
46
  a: unknown, b: unknown, rowA?: Row, rowB?: Row, descending?: boolean,
@@ -68,10 +86,27 @@ export interface RowChange {
68
86
  remove?: unknown[] | string[];
69
87
  }
70
88
 
89
+ /** A row a change could not apply, and why. Reported, never thrown. */
90
+ export interface RejectedRow {
91
+ operation: 'add' | 'update' | 'remove';
92
+ id: string;
93
+ /**
94
+ * `unknown-id` — no row with that key. `duplicate-id` — a row with that key
95
+ * already exists; admitting a second would corrupt every structure that
96
+ * resolves one key to one row.
97
+ */
98
+ reason: 'unknown-id' | 'duplicate-id';
99
+ }
100
+
71
101
  export interface ChangeResult {
72
102
  added: Row[];
73
103
  updated: Row[];
74
104
  removed: string[];
105
+ /**
106
+ * Rows that could not be applied. A batch of a thousand containing three bad
107
+ * ones applies the other 997 and lists the three here.
108
+ */
109
+ rejected?: RejectedRow[];
75
110
  }
76
111
 
77
112
  // ---------------------------------------------------------------------------
@@ -175,6 +210,8 @@ export interface DataType {
175
210
  editor?: EditorName;
176
211
  total?: TotalName;
177
212
  align?: Align;
213
+ /** The cell renderer this type's values are drawn with by default. */
214
+ render?: RendererName;
178
215
  };
179
216
  storage?: 'float64' | 'int32' | 'bitset' | 'dictionary' | 'object';
180
217
  excel?: string;
@@ -270,7 +307,24 @@ export interface Editor {
270
307
  destroy?(): void;
271
308
  }
272
309
  export type EditorCtor = new () => Editor;
273
- export type EditorName = 'text' | 'textarea' | 'number' | 'date' | 'checkbox' | 'select' | 'multiSelect' | (string & {});
310
+ /** The built-in tool panels, addressable by name from configuration. */
311
+ export type ToolPanelName = 'columns' | 'filters' | 'views' | 'quick' | 'formatting' | (string & {});
312
+
313
+ /**
314
+ * The built-in cell renderers, addressable by name through `cell.render`.
315
+ * Anything registered through `components` is also valid here.
316
+ */
317
+ export type RendererName =
318
+ | 'area' | 'bullet' | 'checkbox' | 'colour' | 'column' | 'delta' | 'detailExpander'
319
+ | 'donut' | 'gauge' | 'group' | 'icon' | 'image' | 'line' | 'link' | 'pie' | 'pill'
320
+ | 'progress' | 'qrcode' | 'range' | 'rating' | 'skeleton' | 'stacked' | 'twoline'
321
+ | 'winloss' | (string & {});
322
+
323
+ export type EditorName =
324
+ | 'checkbox' | 'code' | 'colour' | 'date' | 'datetime' | 'duration' | 'iconPicker'
325
+ | 'ipaddress' | 'multiSelect' | 'number' | 'objectPicker' | 'password' | 'radix'
326
+ | 'rating' | 'segmented' | 'select' | 'slider' | 'text' | 'textarea' | 'time'
327
+ | 'treeSelect' | 'unit' | (string & {});
274
328
 
275
329
  export interface EditorParams extends CellParams {
276
330
  stop(cancel?: boolean): void;
@@ -424,6 +478,8 @@ export interface ColumnGroup {
424
478
  showWhen?: 'open' | 'closed' | 'always';
425
479
  marryChildren?: boolean;
426
480
  header?: { render?: string | RendererCtor; props?: Record<string, unknown>; class?: string | string[] };
481
+ /** This column's histogram. `true` turns it on with the grid's settings. */
482
+ facet?: ColumnFacetConfig | boolean;
427
483
  }
428
484
 
429
485
  /** A column after presets, type defaults and grid defaults are folded in. */
@@ -570,6 +626,15 @@ export interface StreamSourceConfig {
570
626
  context: unknown;
571
627
  signal: AbortSignal;
572
628
  }): AsyncIterable<Chunk>;
629
+ /**
630
+ * The most rows to keep. A stream has no end, so an unbounded grid dies
631
+ * overnight; this makes it a sliding window and the oldest rows are dropped.
632
+ * Omit for no limit.
633
+ *
634
+ * Set on the source, not passed to `open` — it bounds what the grid retains
635
+ * rather than what the producer sends.
636
+ */
637
+ maxRows?: number;
573
638
  promoteToMemoryBelow?: number;
574
639
  coalesceMs?: number;
575
640
  }
@@ -587,6 +652,10 @@ export interface TreeConfig {
587
652
  orphans?: 'root' | string;
588
653
  hasChildren?: (row: unknown) => boolean;
589
654
  loadChildren?: (row: Row, signal: AbortSignal) => Promise<unknown[]>;
655
+ /** Where the generated tree column takes its text from: a field or a function. */
656
+ label?: string | ((data: unknown, row: Row) => unknown);
657
+ /** The tree column's heading. Defaults to the label column's own title. */
658
+ title?: string;
590
659
  }
591
660
 
592
661
  export interface DetailConfig {
@@ -596,7 +665,22 @@ export interface DetailConfig {
596
665
  rows?: (row: Row) => unknown[] | Promise<unknown[]>;
597
666
  height?: number | 'auto' | ((row: Row) => number);
598
667
  cacheLimit?: number;
599
- isMaster?: (row: unknown) => boolean;
668
+ isMaster?: (data: unknown, row: Row) => boolean;
669
+ /**
670
+ * Render the detail into this element instead of into a row beneath its
671
+ * master. A selector or an element. Exactly one detail is open at a time in
672
+ * this placement (§13).
673
+ */
674
+ target?: string | HTMLElement;
675
+ /** Handed the nested grid as it is created, for whatever the forwarded events do not cover. */
676
+ onCreate?: (grid: Grid, masterRow: Row) => void;
677
+ /**
678
+ * The property of the master's record the detail rows live on, so an edit in
679
+ * the detail is reported as a path on the master — `ports.1.vlan`. Inferred
680
+ * by identity when `rows(row)` returns an array already on the record, which
681
+ * is the usual shape; set this when it does not (§13).
682
+ */
683
+ path?: string;
600
684
  }
601
685
 
602
686
  export interface SelectionConfig {
@@ -616,6 +700,28 @@ export interface EditConfig {
616
700
  start?: 'single' | 'double' | 'key';
617
701
  enterMovesDown?: boolean;
618
702
  undoDepth?: number;
703
+ commit?: (write: PendingWrite) => unknown;
704
+ confirm?: 'auto' | 'manual';
705
+ pendingTimeout?: number;
706
+ }
707
+
708
+ export interface PendingWrite {
709
+ id: string;
710
+ key: string;
711
+ colId: string;
712
+ value: unknown;
713
+ before: unknown;
714
+ row?: Row;
715
+ }
716
+
717
+ export interface OpenWrite {
718
+ id: string;
719
+ key: string;
720
+ colId: string;
721
+ value: unknown;
722
+ before: unknown;
723
+ state: 'pending' | 'superseded';
724
+ age: number;
619
725
  }
620
726
 
621
727
  export interface PaginationConfig {
@@ -633,6 +739,8 @@ export interface GridConfig {
633
739
  columnDefaults?: Column;
634
740
  columnPresets?: Record<string, Column>;
635
741
  dataTypes?: Record<string, DataType>;
742
+ /** Values sampled per undeclared column when inferring its type. Default 100. */
743
+ sampleSize?: number;
636
744
  components?: Record<string, RendererCtor | EditorCtor | FilterCtor>;
637
745
  pipes?: Record<string, (value: unknown, ...args: string[]) => string>;
638
746
  totalFns?: Record<string, TotalFn>;
@@ -653,24 +761,95 @@ export interface GridConfig {
653
761
  rowHeight?: number | ((row: Row) => number);
654
762
  headerHeight?: number;
655
763
  overscan?: number;
764
+ /**
765
+ * Size rows to their content rather than to the density token.
766
+ *
767
+ * Only rows that are actually rendered are ever measured, in both settings —
768
+ * the grid does not lay out rows you cannot see. The difference is what
769
+ * happens on a large grid: `true` gives up above ten thousand rows and falls
770
+ * back to fixed heights, because a cumulative offset array being patched as
771
+ * you scroll a million rows is not worth the result. `'visible'` keeps
772
+ * measuring at any size, accepting that the scrollbar shifts as rows are
773
+ * measured on the way past.
774
+ *
775
+ * The name is historical and reads as though it were about which rows are
776
+ * measured; it is about whether the ceiling applies.
777
+ */
656
778
  autoHeight?: boolean | 'visible';
657
779
  state?: GridState;
658
780
  licence?: string;
659
781
  maximise?: boolean;
782
+ /** Extra functions a formula may call, on top of the built-in library. */
783
+ formulaFunctions?: Record<string, (args: unknown[]) => unknown>;
660
784
  allowUnsafeTemplates?: boolean;
785
+ /**
786
+ * Caps on the change log behind `grid.updates` and `grid.timeline`.
787
+ *
788
+ * Two caps, because an entry is not a fixed size: `logLimit` bounds how many
789
+ * changes are kept (default 2000) and `logRows` bounds the rows they account
790
+ * for between them (default 100,000). A feed delivering large batches reaches
791
+ * the second long before the first, and without it the log is unbounded in
792
+ * bytes while looking bounded in entries.
793
+ */
794
+ updates?: {
795
+ logLimit?: number;
796
+ logRows?: number;
797
+ /**
798
+ * When a queued batch applies. `frame` (default) lands on a paint
799
+ * boundary, which is what makes one repaint per batch reliable;
800
+ * `microtask` at the end of the current task; `interval` on the coalescing
801
+ * window; `manual` only when you call `grid.updates.flush()`.
802
+ */
803
+ flush?: 'frame' | 'microtask' | 'interval' | 'manual';
804
+ /** Queued rows that force an early flush regardless of strategy. */
805
+ maxQueued?: number;
806
+ /** Milliseconds one flush may spend before deferring the rest. */
807
+ budgetMs?: number;
808
+ };
809
+ /**
810
+ * Threaded comments on individual cells (§16). Requires a stable
811
+ * `rowKey`: comments outlive the values they annotate, and index
812
+ * identity would reattach every thread on the next sort.
813
+ */
814
+ comments?: CommentConfig;
815
+ /**
816
+ * Collaborative presence (§18). A display feature over a transport the
817
+ * grid does not own; without a provider it is inert.
818
+ */
819
+ presence?: PresenceConfig;
820
+ /**
821
+ * Host environment for a support bundle. Supplied by the DOM layer;
822
+ * core cannot read `navigator` or `window` itself (§3.1).
823
+ */
824
+ environment?: () => Record<string, unknown>;
825
+ /**
826
+ * Column header histograms and the filters clicking them creates (§9.6).
827
+ *
828
+ * Off by default: the band roughly doubles header height, which is a cost
829
+ * no grid should pay without asking. Per-column settings layer over these.
830
+ */
831
+ facets?: FacetConfig | boolean;
661
832
  hostFilter?: { active(): boolean; passes(row: Row): boolean };
662
833
  context?: unknown;
834
+ /** Row count above which a column distribution is computed in a Worker. */
663
835
  workerThreshold?: number;
836
+ /**
837
+ * Compute column distributions off the main thread. Sorting, filtering and
838
+ * grouping run on the main thread; see §5.13 for why.
839
+ */
664
840
  useWorker?: boolean;
665
841
  workerUrl?: string;
666
842
  sharedMemory?: boolean;
667
843
  groupFooter?: boolean;
668
844
  /**
669
- * Where the grand total goes. `'bottom'` pins it below the rows; omitted
670
- * leaves it inline with the data. Only the string is recognised — `true`
671
- * does nothing.
845
+ * Where the grand total goes.
846
+ *
847
+ * `true` adds it as the last display row, counted by `rows.count()` like any
848
+ * other. `'bottom'` pins it beneath the viewport instead, so it stays in
849
+ * view while the rows scroll and is *not* part of `rows.count()`. Omitted or
850
+ * `false` means no grand total row.
672
851
  */
673
- grandTotalRow?: 'bottom';
852
+ grandTotalRow?: boolean | 'bottom';
674
853
  totalFilteredOnly?: boolean;
675
854
  totalOnlyChangedColumns?: boolean;
676
855
  showTotalInHeader?: boolean;
@@ -681,7 +860,7 @@ export interface GridConfig {
681
860
  * suppresses it entirely, which is what a read-only grid wants — the default
682
861
  * menu offers Paste, Clear and Fill down.
683
862
  */
684
- contextMenu?: boolean | ((p: CellParams) => MenuItem[]);
863
+ contextMenu?: boolean | ((p: CellMenuParams, defaults: MenuItem[]) => MenuItem[] | void);
685
864
  /** The header's 3-dot menu. `false` suppresses it. Default true. */
686
865
  columnMenu?: boolean;
687
866
  /**
@@ -695,13 +874,20 @@ export interface GridConfig {
695
874
  duration?: number;
696
875
  enabled?: boolean;
697
876
  };
877
+ /**
878
+ * Conditional formatting rules the grid holds as runtime state, keyed by
879
+ * column id or `'*'` for every column (spec 8.12). Seeds `grid.formatting`,
880
+ * which an end user can then change; the rules travel in saved views and
881
+ * undo like any other change. Config-time `cell.style` is unaffected.
882
+ */
883
+ formatting?: Record<string, FormattingRule[]>;
698
884
  /** A class, or classes, for every row. Re-evaluated on each repaint. */
699
885
  rowClass?: string | string[] | ((p: RowStyleParams) => string | string[]);
700
886
  /** Inline styles for every row. Camel-case or hyphenated property names. */
701
887
  rowStyle?: CellStyle | ((p: RowStyleParams) => CellStyle);
702
888
  toolPanel?: boolean | {
703
- /** Built-in names: `columns`, `filters`, `views`, `quick`. */
704
- panels?: string[];
889
+ /** Built-in names: `columns`, `filters`, `views`, `quick`, `formatting`. */
890
+ panels?: ToolPanelName[];
705
891
  openPanel?: string;
706
892
  /** Which edge to dock against. `left` is the icon rail; default `right`. */
707
893
  side?: 'left' | 'right';
@@ -711,7 +897,14 @@ export interface GridConfig {
711
897
  * Rail action buttons: `undo`, `redo`, `export`, `restore`. Defaults to all
712
898
  * four on the left rail and none on the right; `false` drops them.
713
899
  */
714
- actions?: false | ('undo' | 'redo' | 'export' | 'restore')[];
900
+ /**
901
+ * Which action buttons the rail offers, in order. `false` drops them.
902
+ *
903
+ * The built-in names are `undo`, `redo`, `pause`, `restore`, `maximise`,
904
+ * `export`, `excel`, `clipboard` and `print`, plus `'-'` for a divider.
905
+ * A {@link RailAction} object places one of your own among them.
906
+ */
907
+ actions?: false | (RailActionName | '-' | RailAction)[];
715
908
  /** File name for the export action, without the extension. */
716
909
  exportName?: string;
717
910
  };
@@ -723,7 +916,27 @@ export interface GridConfig {
723
916
  */
724
917
  permissions?: PermissionPolicy;
725
918
  /** Prior state for diff and audit mode (§12). */
726
- diff?: { snapshot?: unknown[] | Map<string, unknown>; strictNull?: boolean; addedColumns?: 'unchanged' | 'changed' };
919
+ diff?: {
920
+ snapshot?: unknown[] | Map<string, unknown>;
921
+ strictNull?: boolean;
922
+ addedColumns?: 'unchanged' | 'changed';
923
+ /**
924
+ * Whether a row present in the snapshot but gone from the data is shown,
925
+ * and whether it counts as data when it is.
926
+ *
927
+ * `false` — the default — leaves it out entirely. `'pinned'` shows it
928
+ * beneath the rows, struck through: visible history that is not part of the
929
+ * row set, so it is excluded from `rows.count()`, from exports and from
930
+ * selection. `'data'` appends it to the row set instead, so it *is*
931
+ * counted and exported.
932
+ *
933
+ * Neither is sorted or filtered among the live rows: a removed row's values
934
+ * are the snapshot's, and ordering yesterday's numbers among today's would
935
+ * present two data sets as one. Neither can be edited — there is nothing
936
+ * left to write to.
937
+ */
938
+ removedRows?: false | 'pinned' | 'data';
939
+ };
727
940
  /** Saved views (§15): a storage adapter and any pre-loaded views. */
728
941
  views?: { storage?: { read(): unknown[]; write(views: unknown[]): void }; saved?: unknown[] };
729
942
  /** The undo toolbar. `element` mounts it into the host's own chrome. */
@@ -733,13 +946,35 @@ export interface GridConfig {
733
946
  * host's, and owns the model, the key and the privacy decision.
734
947
  */
735
948
  ai?: {
736
- ask(p: { prompt: string; schema: unknown; context?: unknown }): Promise<unknown>;
949
+ ask(p: {
950
+ /** The full text to send: the schema description and the question together. */
951
+ prompt: string;
952
+ /** The grid's schema as data — columns, types and operators. No row values. */
953
+ schema: unknown;
954
+ /** The same schema rendered as text, which is what `prompt` embeds. */
955
+ schemaText: string;
956
+ /** What the user typed. */
957
+ message: string;
958
+ context?: unknown;
959
+ }): Promise<unknown>;
737
960
  schemaOptions?: object;
738
961
  context?: unknown;
739
962
  element?: HTMLElement;
740
963
  placeholder?: string;
741
964
  };
742
- pivot?: { enabled?: boolean; groupTotals?: 'before' | 'after' | false; maxColumns?: number; separator?: string };
965
+ pivot?: {
966
+ enabled?: boolean;
967
+ /**
968
+ * Add a column group totalling every value column across all pivot values —
969
+ * the grand total beside the pivoted ones. `'before'` places it at the near
970
+ * edge, `'after'` at the far edge. Omitted or `false` adds none.
971
+ */
972
+ groupTotals?: 'before' | 'after' | false;
973
+ /** Heading for that group. Defaults to `Total`. */
974
+ totalsLabel?: string;
975
+ maxColumns?: number;
976
+ separator?: string;
977
+ };
743
978
  }
744
979
 
745
980
  /**
@@ -795,6 +1030,7 @@ export interface GridState {
795
1030
  sort?: SortEntry[];
796
1031
  group?: string[];
797
1032
  pivot?: { enabled: boolean; columns: string[] };
1033
+ formatting?: Record<string, FormattingRule[]>;
798
1034
  expanded?: string[];
799
1035
  selection?: string[];
800
1036
  scroll?: { top: number; left: number };
@@ -806,13 +1042,65 @@ export interface StateApplyReport {
806
1042
  skipped: { key: string; reason: string }[];
807
1043
  }
808
1044
 
1045
+ // ---------------------------------------------------------------------------
1046
+ // Conditional formatting (spec 8.12)
1047
+ // ---------------------------------------------------------------------------
1048
+
1049
+ export interface FormattingCondition {
1050
+ op: Operator;
1051
+ value?: unknown;
1052
+ value2?: unknown;
1053
+ }
1054
+
1055
+ export interface FormattingScale {
1056
+ min: number;
1057
+ max: number;
1058
+ mid?: number;
1059
+ colours?: string[];
1060
+ }
1061
+
1062
+ /**
1063
+ * One rule. Either a condition and the styling it produces, or a colour scale.
1064
+ * A rule held as runtime state must be JSON, so `style` may not be a function
1065
+ * there — config-time `cell.style` still accepts one.
1066
+ */
1067
+ export interface FormattingRule {
1068
+ id?: string;
1069
+ when?: FormattingCondition;
1070
+ style?: CellStyle | ((p: CellParams) => CellStyle | null);
1071
+ scale?: FormattingScale;
1072
+ stopIfTrue?: boolean;
1073
+ enabled?: boolean;
1074
+ icon?: string;
1075
+ bar?: boolean;
1076
+ label?: string;
1077
+ }
1078
+
1079
+ /** A column id, or `'*'` for every column. */
1080
+ export type FormattingScope = string;
1081
+
1082
+ export interface FormattingApi {
1083
+ list(scope?: FormattingScope): FormattingRule[];
1084
+ all(): Record<FormattingScope, FormattingRule[]>;
1085
+ scopes(): FormattingScope[];
1086
+ add(scope: FormattingScope, rule: FormattingRule, opts?: { at?: number }): FormattingRule | null;
1087
+ remove(scope: FormattingScope, which: string | number): boolean;
1088
+ update(scope: FormattingScope, which: string | number, patch: FormattingRule): FormattingRule | null;
1089
+ move(scope: FormattingScope, which: string | number, to: number): boolean;
1090
+ set(scope: FormattingScope, rules: FormattingRule[]): FormattingRule[];
1091
+ replaceAll(rules: Record<FormattingScope, FormattingRule[]>): void;
1092
+ clear(scope?: FormattingScope): void;
1093
+ styleFor(colId: string, value: unknown): CellStyle | null;
1094
+ }
1095
+
809
1096
  // ---------------------------------------------------------------------------
810
1097
  // Events (spec 18.4)
811
1098
  // ---------------------------------------------------------------------------
812
1099
 
813
1100
  export type EventName =
814
1101
  | 'ready' | 'destroy' | 'render:first' | 'model:changed' | 'rows:changed' | 'rows:queued'
815
- | 'cell:changed' | 'cell:clicked' | 'cell:dblclicked' | 'cell:contextmenu'
1102
+ | 'cell:changed' | 'cell:pending' | 'cell:confirmed' | 'cell:reverted'
1103
+ | 'cell:clicked' | 'cell:dblclicked' | 'cell:contextmenu'
816
1104
  | 'cell:edit:start' | 'cell:edit:end' | 'row:edit:start' | 'row:edit:end'
817
1105
  | 'row:clicked' | 'row:dblclicked' | 'group:toggled'
818
1106
  | 'sort:changed' | 'filter:changed'
@@ -911,6 +1199,8 @@ export interface RowsApi {
911
1199
  count(): number;
912
1200
  /** Rows in the source before filtering; under pagination, across every page. */
913
1201
  totalCount(): number;
1202
+ /** Data rows matching the filters, excluding group, footer and total rows. */
1203
+ matchCount(): number;
914
1204
  data(): unknown[];
915
1205
  forEach(fn: (row: Row, index: number) => void): void;
916
1206
  value(key: string, colId: string): unknown;
@@ -941,15 +1231,35 @@ export interface ColumnsApi {
941
1231
  totals(ids: string | string[]): void;
942
1232
  }
943
1233
 
1234
+ export interface DetailApi {
1235
+ enabled(): boolean;
1236
+ isMaster(target: string | Row): boolean;
1237
+ isOpen(key: string): boolean;
1238
+ open(key: string): void;
1239
+ close(key: string): void;
1240
+ toggle(key: string): boolean;
1241
+ closeAll(): void;
1242
+ keys(): string[];
1243
+ active(): string | null;
1244
+ placement(): 'inline' | 'target' | null;
1245
+ config(): DetailConfig | null;
1246
+ }
1247
+
944
1248
  export interface SelectionApi {
945
1249
  rows(): Row[];
946
1250
  keys(): string[];
947
1251
  set(keys: string[]): void;
948
1252
  all(): void;
949
1253
  clear(): void;
1254
+ headerState(): boolean | 'partial';
950
1255
  cells(): { key: string; colId: string }[];
951
1256
  ranges(): CellRange[];
952
1257
  setRange(range: CellRange): void;
1258
+ addRange(range: CellRange): void;
1259
+ startRange(rowIndex: number, colId: string, opts?: { additive?: boolean }): void;
1260
+ extendRange(rowIndex: number, colId: string): void;
1261
+ corner(): { row: number; colId: string } | null;
1262
+ inRange(rowIndex: number, colId: string): boolean;
953
1263
  }
954
1264
 
955
1265
  export interface CellRange {
@@ -972,10 +1282,15 @@ export interface SortApi {
972
1282
  }
973
1283
 
974
1284
  export interface EditApi {
975
- start(key: string, colId: string): void;
1285
+ start(key: string, colId: string): boolean;
976
1286
  stop(cancel?: boolean): void;
977
1287
  undo(): void;
978
1288
  redo(): void;
1289
+ setCells(writes: { key: string; colId: string; value: unknown }[], type?: 'cell' | 'fill' | 'paste'): number;
1290
+ pasteInto(anchor: { key: string; colId: string }, text: string, extent?: { rows?: number; columns?: number }): number;
1291
+ settle(id: string, ok: boolean, reason?: string): boolean;
1292
+ pending(): OpenWrite[];
1293
+ status(key: string, colId: string): 'pending' | null;
979
1294
  }
980
1295
 
981
1296
  export interface ScrollApi {
@@ -1032,6 +1347,436 @@ export interface RowStyleParams {
1032
1347
  context: unknown;
1033
1348
  }
1034
1349
 
1350
+ /**
1351
+ * Presentation mode renders the grid for a room: full-screen, application
1352
+ * chrome hidden, and everything enlarged by a scale that multiplies the
1353
+ * configured density rather than replacing it. The data stays live and
1354
+ * interactive throughout.
1355
+ */
1356
+ /**
1357
+ * Rendering the grid to a still image. `scale` multiplies the pixel dimensions
1358
+ * — 2 for a retina still, 3 or 4 for a slide. `background` fills behind the
1359
+ * grid so a PNG dropped into a deck does not show it through.
1360
+ */
1361
+ export interface CaptureOptions {
1362
+ scale?: number;
1363
+ background?: string;
1364
+ download?: boolean;
1365
+ fileName?: string;
1366
+ }
1367
+
1368
+ /**
1369
+ * The presenter's drawing layer. Pixels over the grid — it never reads or
1370
+ * writes data, and it is inert until a tool is chosen, so scrolling and
1371
+ * selection pass straight through. Marks are held in content coordinates, so
1372
+ * they stay with the cells they annotate when the grid scrolls, and are
1373
+ * cleared when a presentation ends.
1374
+ */
1375
+ export interface AnnotationApi {
1376
+ readonly tool: 'pen' | 'arrow' | 'rect' | 'highlight' | null;
1377
+ readonly count: number;
1378
+ use(tool: 'pen' | 'arrow' | 'rect' | 'highlight' | null, opts?: { colour?: string }): string | null;
1379
+ undo(): number;
1380
+ clear(): void;
1381
+ redraw(): void;
1382
+ }
1383
+
1384
+ /**
1385
+ * Holding incoming updates, and the counters describing what they cost.
1386
+ * Pausing is explicit — a button, not a guess at whether the user is busy.
1387
+ */
1388
+ /** One thing the grid has flagged as probably a mistake. */
1389
+ export interface DiagnosticWarning {
1390
+ /** Stable identifier, nameable in a support conversation. */
1391
+ id: string;
1392
+ message: string;
1393
+ /** The specific values involved, so the warning is actionable. */
1394
+ values: Record<string, unknown>;
1395
+ count: number;
1396
+ first: number;
1397
+ last: number;
1398
+ /** `'check'` raised by a diagnostic check, `'reported'` from `warnOnce`. */
1399
+ source: 'check' | 'reported' | 'info';
1400
+ }
1401
+
1402
+ export interface DiagnosticsApi {
1403
+ snapshot(): Record<string, unknown>;
1404
+ /** `dom.cellWrites` is the figure a DOM-write assertion reads. */
1405
+ renders(): Record<string, unknown>;
1406
+ store(): Record<string, unknown>;
1407
+ operations(): Record<string, unknown>;
1408
+ providers(): Record<string, unknown>;
1409
+ events(): Record<string, number>;
1410
+ config(): { effective: Record<string, unknown>; supplied: string[]; defaulted: string[] };
1411
+ warnings(): DiagnosticWarning[];
1412
+ dismiss(id: string): void;
1413
+ /** Contains no row data, cell values or column values. */
1414
+ bundle(): Record<string, unknown>;
1415
+ checkOptions(options: unknown): boolean;
1416
+ record(kind: string, detail: { rows?: number; ms?: number; worker?: boolean }): void;
1417
+ render(cause: string, phases?: Record<string, number>): void;
1418
+ /** Off by default; recording times every emit. */
1419
+ recordEvents(on: boolean, limit?: number): void;
1420
+ eventLog(): Array<{ type: string; origin: string; listeners: number;
1421
+ payload: Record<string, string>; at: number; ms: number }>;
1422
+ clearEventLog(): void;
1423
+ /** Keep current store statistics so growth can be measured against them. */
1424
+ mark(): Record<string, unknown>;
1425
+ since(): Record<string, unknown> | null;
1426
+ reset(): void;
1427
+ }
1428
+
1429
+ /** One peer, as the grid holds them. */
1430
+ export interface Peer {
1431
+ id: string;
1432
+ name: string;
1433
+ /** Assigned deterministically from the id when the provider supplies none. */
1434
+ colour: string;
1435
+ avatarUrl?: string | null;
1436
+ initials?: string | null;
1437
+ /** Row key and column, never an index. */
1438
+ cursor: { rowId: string; colId: string } | null;
1439
+ ranges: Array<{ rowIds: string[]; columns: string[] }>;
1440
+ editing: { rowId: string; colId: string } | null;
1441
+ /** Local receipt time, not the sender's clock. */
1442
+ at: number;
1443
+ /** The sender's own timestamp, for inspection only. Nothing decides on it. */
1444
+ sentAt?: number | null;
1445
+ idle?: boolean;
1446
+ silentMs?: number;
1447
+ /** True when the peer's cursor is on a row this view is not showing. */
1448
+ hidden?: boolean;
1449
+ }
1450
+
1451
+ /**
1452
+ * Transport for presence. The grid never opens a connection: it subscribes to
1453
+ * what the provider delivers and hands it what changed locally.
1454
+ */
1455
+ export interface PresenceProvider {
1456
+ /** Returns an unsubscribe function, if it has one. */
1457
+ subscribe(onMessage: (message: Peer | Peer[]) => void): (() => void) | void;
1458
+ publish(state: Record<string, unknown>): void;
1459
+ }
1460
+
1461
+ export interface PresenceConfig {
1462
+ /** Without one the feature is inert and raises nothing. */
1463
+ provider?: PresenceProvider;
1464
+ /** The local identity, echoed in everything published. */
1465
+ me?: { id: string; name?: string; colour?: string; avatarUrl?: string; initials?: string };
1466
+ /** Milliseconds between published updates. Throttled, not debounced. */
1467
+ throttleMs?: number;
1468
+ /** Silence after which a peer is shown idle. */
1469
+ idleMs?: number;
1470
+ /** Silence after which a peer is dropped. */
1471
+ removeMs?: number;
1472
+ /** Silence after which a peer's edit claim is disregarded. */
1473
+ lockMs?: number;
1474
+ /**
1475
+ * Refuse local editing of a cell a peer is editing. Advisory only: the
1476
+ * authoritative resolution is the conditional write in `edit.commit`.
1477
+ */
1478
+ lock?: boolean;
1479
+ /** Override the peer colour palette. */
1480
+ palette?: string[];
1481
+ /** Suppress the roster, or place it. */
1482
+ roster?: boolean | { side?: 'start' | 'end' };
1483
+ /** Suppress join and leave announcements to assistive technology. */
1484
+ announce?: boolean;
1485
+ }
1486
+
1487
+ export interface PresenceApi {
1488
+ readonly enabled: boolean;
1489
+ readonly me: Record<string, unknown> | null;
1490
+ readonly publishing: boolean;
1491
+ peers(): Peer[];
1492
+ hiddenCount(): number;
1493
+ editorOf(rowId: string, colId: string): Peer | null;
1494
+ /** Advisory. Reduces collisions; does not eliminate them. */
1495
+ lockedBy(rowId: string, colId: string): Peer | null;
1496
+ jumpTo(peerId: string): boolean;
1497
+ publish(): void;
1498
+ setPublishing(on: boolean): void;
1499
+ setPaused(paused: boolean): void;
1500
+ connect(provider: PresenceProvider | null): void;
1501
+ stats(): Record<string, number>;
1502
+ }
1503
+
1504
+ /** One comment in a thread, as the provider returns it. */
1505
+ export interface Comment {
1506
+ id: string;
1507
+ body: string;
1508
+ /** Rendered as supplied. The grid does not know who the user is. */
1509
+ author?: { name?: string; avatarUrl?: string; initials?: string };
1510
+ at?: number;
1511
+ edited?: boolean;
1512
+ resolved?: boolean;
1513
+ parentId?: string | null;
1514
+ /** The cell's value when this was written, so a later reader is told it moved. */
1515
+ value?: unknown;
1516
+ /**
1517
+ * What the current user may do. Absent means the grid shows every affordance
1518
+ * and relies on the provider to refuse. Hiding a button is a convenience,
1519
+ * never a security control.
1520
+ */
1521
+ can?: { edit?: boolean; delete?: boolean; resolve?: boolean };
1522
+ }
1523
+
1524
+ /** Counts for one cell. Never bodies — this is consulted on every repaint. */
1525
+ export interface CommentDescriptor {
1526
+ count: number;
1527
+ unresolved: number;
1528
+ updated: number;
1529
+ }
1530
+
1531
+ /** What `loadIndex` returns per commented cell. */
1532
+ export interface CommentIndexEntry extends CommentDescriptor {
1533
+ cellKey?: string;
1534
+ rowId?: string;
1535
+ field?: string;
1536
+ }
1537
+
1538
+ /**
1539
+ * Storage for comments. Every method returns a promise; a rejection surfaces in
1540
+ * the panel without disturbing grid state.
1541
+ */
1542
+ export interface CommentProvider {
1543
+ loadIndex(rowIds: string[], fields: string[]): Promise<CommentIndexEntry[]>;
1544
+ loadThread(cellKey: string): Promise<Comment[]>;
1545
+ addComment(cellKey: string, body: string, parentId: string | null,
1546
+ context?: { value?: unknown }): Promise<Comment>;
1547
+ editComment(commentId: string, body: string): Promise<Comment>;
1548
+ deleteComment(commentId: string): Promise<void>;
1549
+ resolveThread(cellKey: string): Promise<void>;
1550
+ unresolveThread(cellKey: string): Promise<void>;
1551
+ }
1552
+
1553
+ export interface CommentConfig {
1554
+ /** Without one the feature is inert and no error is raised. */
1555
+ provider?: CommentProvider;
1556
+ /** Milliseconds a viewport change waits before the index is fetched. */
1557
+ debounce?: number;
1558
+ /** Cell descriptors held before the oldest are dropped. */
1559
+ indexLimit?: number;
1560
+ /** `'anchored'` floats beside the cell; `'docked'` uses a side panel. */
1561
+ mode?: 'anchored' | 'docked';
1562
+ /** Restricted markdown in bodies: emphasis, code and links only. */
1563
+ markdown?: boolean;
1564
+ /** Label for the row, so the panel says what is being commented on. */
1565
+ rowLabel?: (row: Row) => string;
1566
+ }
1567
+
1568
+ export interface CommentsApi {
1569
+ readonly enabled: boolean;
1570
+ readonly openKey: string | null;
1571
+ readonly thread: Comment[] | null;
1572
+ readonly loading: boolean;
1573
+ readonly complete: boolean;
1574
+ /** `'no-provider'`, `'no-row-identity'`, or null when available. */
1575
+ unavailable(): string | null;
1576
+ at(rowId: string, colId: string): CommentDescriptor | null;
1577
+ request(rowIds: string[], fields?: string[]): void;
1578
+ open(rowId: string, colId: string): Promise<Comment[] | null>;
1579
+ close(opts?: { reason?: string }): void;
1580
+ add(body: string, opts?: { parentId?: string; author?: object }): Promise<Comment | null>;
1581
+ edit(commentId: string, body: string): Promise<Comment | null>;
1582
+ remove(commentId: string): Promise<boolean>;
1583
+ resolve(): Promise<boolean>;
1584
+ unresolve(): Promise<boolean>;
1585
+ refresh(): void;
1586
+ loadAll(): Promise<boolean>;
1587
+ hiddenUnresolved(): number;
1588
+ filterToCommented(opts?: { unresolvedOnly?: boolean }): boolean;
1589
+ }
1590
+
1591
+ /** One bucket of a column's distribution. */
1592
+ export interface FacetBucket {
1593
+ /** Lower edge, for ordered columns. Half-open `[from, to)` except the last. */
1594
+ from?: number;
1595
+ /** Upper edge, for ordered columns. Inclusive on the last bucket only. */
1596
+ to?: number;
1597
+ /** The value, for categorical and boolean columns. */
1598
+ value?: unknown;
1599
+ /** True on the terminal bucket holding nulls, NaN and empty values. */
1600
+ null?: boolean;
1601
+ /** True on the aggregated tail bucket under `aboveLimit: 'topN'`. */
1602
+ remainder?: boolean;
1603
+ /** A ready-made label, where one is more useful than the raw value. */
1604
+ label?: string;
1605
+ }
1606
+
1607
+ /** Where a column's buckets are, and how they were chosen. */
1608
+ export interface FacetBounds {
1609
+ kind: 'numeric' | 'date' | 'category' | 'boolean' | 'none';
1610
+ buckets: FacetBucket[];
1611
+ /** Set when no histogram was drawn, naming why. */
1612
+ suppressed?: 'type' | 'cardinality' | 'rows' | 'streaming' | 'no-provider' | 'disabled';
1613
+ /** Distinct values, on categorical columns. */
1614
+ cardinality?: number;
1615
+ /** The time unit chosen, on date columns. */
1616
+ granularity?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
1617
+ /** The numeric strategy actually applied, which may differ from the request. */
1618
+ strategy?: 'equal' | 'quantile' | 'log';
1619
+ min?: number;
1620
+ max?: number;
1621
+ }
1622
+
1623
+ /** A column's computed distribution. */
1624
+ export interface FacetState {
1625
+ bounds: FacetBounds | null;
1626
+ /** Counts under every filter except this column's own. Aligned to `buckets`. */
1627
+ counts: Uint32Array | null;
1628
+ /** Counts with no filter applied, for the "40 of 200" reading. */
1629
+ unfiltered: Uint32Array | null;
1630
+ /** True while a recount is outstanding; draw the previous counts faded. */
1631
+ stale: boolean;
1632
+ suppressed: string | null;
1633
+ }
1634
+
1635
+ /** Per-column histogram settings, layered over the grid's. */
1636
+ export interface ColumnFacetConfig {
1637
+ enabled?: boolean;
1638
+ buckets?: number;
1639
+ strategy?: 'equal' | 'quantile' | 'log';
1640
+ granularity?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
1641
+ order?: 'count' | 'alpha';
1642
+ cardinalityLimit?: number;
1643
+ aboveLimit?: 'suppress' | 'topN';
1644
+ /** Replace the built-in bucketing entirely. */
1645
+ bucketFn?: (handle: unknown, indices: Uint32Array | null, count: number) => FacetBounds;
1646
+ /** Label a bucket for its tooltip and accessible name. */
1647
+ format?: (bucket: FacetBucket, count: number, unfiltered: number) => string;
1648
+ }
1649
+
1650
+ /** Grid-level histogram settings. */
1651
+ export interface FacetConfig extends ColumnFacetConfig {
1652
+ /** Off unless asked for: header space is tight and this doubles its height. */
1653
+ enabled?: boolean;
1654
+ /** Start as a one-line density strip that opens on hover or click. */
1655
+ collapsed?: boolean;
1656
+ /** Band height in pixels. */
1657
+ height?: number;
1658
+ /** Rows above which histograms are suppressed. */
1659
+ rowCeiling?: number;
1660
+ /** Milliseconds a filter change waits before charts recount. */
1661
+ debounce?: number;
1662
+ /** Whether a paused stream re-enables histograms. Defaults to true. */
1663
+ whilePaused?: boolean;
1664
+ /** Bucket counts for a source the client cannot compute over. */
1665
+ provider?: (request: {
1666
+ colId: string;
1667
+ column: unknown;
1668
+ filters: unknown;
1669
+ quick: string;
1670
+ bounds: FacetBounds | null;
1671
+ buckets: number;
1672
+ strategy: string;
1673
+ granularity?: string;
1674
+ }) => Promise<{ bounds?: FacetBounds; buckets?: FacetBucket[]; counts: ArrayLike<number>;
1675
+ unfiltered?: ArrayLike<number>; kind?: string }>;
1676
+ }
1677
+
1678
+ export interface FacetsApi {
1679
+ get(colId: string): FacetState | null;
1680
+ suppression(colId: string): string | null;
1681
+ config(colId?: string): FacetConfig;
1682
+ refresh(opts?: { immediate?: boolean }): void;
1683
+ isExpanded(colId: string): boolean;
1684
+ toggle(colId: string, open?: boolean): boolean;
1685
+ select(colId: string, from: number, to?: number,
1686
+ opts?: { additive?: boolean; gesture?: string }): boolean;
1687
+ clear(colId: string): boolean;
1688
+ selected(colId: string): number[];
1689
+ expanded(): string[];
1690
+ }
1691
+
1692
+ export interface UpdatesApi {
1693
+ readonly paused: boolean;
1694
+ pause(): boolean;
1695
+ resume(): ChangeResult;
1696
+ flush(): ChangeResult;
1697
+ stats(): {
1698
+ paused: boolean;
1699
+ pending: number;
1700
+ queued: number;
1701
+ coalesced: number;
1702
+ coalescedTotal: number;
1703
+ rows: number;
1704
+ dropped: number;
1705
+ held: number;
1706
+ heldLimit: number;
1707
+ flushes: number;
1708
+ strategy: string;
1709
+ deferrals: number;
1710
+ maxQueued: number;
1711
+ budgetMs: number;
1712
+ span: { from: number; to: number } | null;
1713
+ };
1714
+ log(opts?: { since?: number }): { at: number; change: RowChange; rows: number }[];
1715
+ }
1716
+
1717
+ /**
1718
+ * Moving the grid through recent data changes. Reads the change log rather
1719
+ * than the undo history: history records what the *user* did, and the question
1720
+ * on a live grid is what the *data* did. Nothing is scrubbable until
1721
+ * `attach()` — what a value used to be is not recoverable after the fact.
1722
+ */
1723
+ export interface TimelineApi {
1724
+ readonly attached: boolean;
1725
+ readonly live: boolean;
1726
+ readonly position: number;
1727
+ readonly depth: number;
1728
+ attach(): void;
1729
+ detach(): void;
1730
+ seek(steps: number): number;
1731
+ step(by: number): number;
1732
+ toLive(): number;
1733
+ at(): number | null;
1734
+ span(): { from: number; to: number } | null;
1735
+ }
1736
+
1737
+ export interface PresentationApi {
1738
+ readonly active: boolean;
1739
+ readonly scale: number;
1740
+ readonly options: { scale?: number; chrome?: string[]; views?: string[]; from?: number; autoAdvance?: number };
1741
+ readonly views: string[];
1742
+ readonly index: number;
1743
+ readonly viewId: string | null;
1744
+ start(options?: {
1745
+ scale?: number;
1746
+ chrome?: string[];
1747
+ /** Saved view ids to step through. Views are the slides. */
1748
+ views?: string[];
1749
+ /** Where in that sequence to begin. */
1750
+ from?: number;
1751
+ /** Milliseconds between automatic advances, for an unattended display. */
1752
+ autoAdvance?: number;
1753
+ }): boolean;
1754
+ stop(): boolean;
1755
+ setScale(value: number): number;
1756
+ nudge(steps?: number): number;
1757
+ step(by?: number): number;
1758
+ goTo(index: number): number;
1759
+ reset(): boolean;
1760
+ readonly spotlight: { keys: string[]; colIds: string[] } | null;
1761
+ setSpotlight(target?: { keys?: string[]; colIds?: string[] } | null): boolean;
1762
+ }
1763
+
1764
+ /**
1765
+ * Redaction obscures a column's values on screen. It is presentational: the
1766
+ * values stay in the model, the DOM, the clipboard and every export. Use
1767
+ * `permissions` with `writeOnly` for a value that must not be readable.
1768
+ */
1769
+ export interface RedactionApi {
1770
+ has(colId: string): boolean;
1771
+ list(): string[];
1772
+ toggle(colId: string): boolean;
1773
+ add(colId: string): void;
1774
+ remove(colId: string): void;
1775
+ set(ids: string[]): void;
1776
+ clear(): void;
1777
+ readonly active: boolean;
1778
+ }
1779
+
1035
1780
  export interface HighlightApi {
1036
1781
  /** Highlight a cell (`{key, colId}`), a row (`{key}`) or a column (`{colId}`). */
1037
1782
  (target: { key?: string; colId?: string } | string,
@@ -1059,8 +1804,20 @@ export interface OverlayApi {
1059
1804
  }
1060
1805
 
1061
1806
  export interface HistoryEntry {
1807
+ /** Monotonic sequence number, in the order actions were recorded. */
1808
+ seq: number;
1809
+ /** What kind of action it was, e.g. `'sort'`, `'column:pin'`, `'edit'`. */
1810
+ type: string;
1811
+ /** Human text for a button, e.g. `'sort by Region'`. */
1062
1812
  label: string;
1063
- kind: string;
1813
+ /** The column or row the action was aimed at, where there was one. */
1814
+ target: string | null;
1815
+ /** When it was recorded, on the high-resolution clock. */
1816
+ at: number;
1817
+ /** True when the edit model owns the undo rather than the history stack. */
1818
+ delegated: boolean;
1819
+ /** Set once the entry has been undone. */
1820
+ undone?: boolean;
1064
1821
  [key: string]: unknown;
1065
1822
  }
1066
1823
 
@@ -1136,7 +1893,14 @@ export interface AiApi {
1136
1893
  schema(opts?: { maxColumns?: number; maxRows?: number }): Record<string, unknown>;
1137
1894
  /** The same schema as a tool definition. */
1138
1895
  tool(opts?: { maxColumns?: number; maxRows?: number }): Record<string, unknown>;
1139
- prompt(text: string, opts?: Record<string, unknown>): string;
1896
+ /**
1897
+ * The prompt describing this grid — its columns, types and operators — for
1898
+ * sending to a model. It carries no row values.
1899
+ *
1900
+ * It does not take the user's question: compose that yourself alongside the
1901
+ * text this returns, which is what `ask` receives as `schemaText`.
1902
+ */
1903
+ prompt(opts?: Record<string, unknown>): string;
1140
1904
  buildPrompt(text: string, opts?: Record<string, unknown>): string;
1141
1905
  /** Parse what the model returned into a plan. */
1142
1906
  plan(reply: string | Record<string, unknown>, opts?: Record<string, unknown>): Record<string, unknown>;
@@ -1164,6 +1928,48 @@ export interface PaginationApi {
1164
1928
  * created with `createGrid` unless `maximise: false`; never on a headless grid,
1165
1929
  * which has no window to fill.
1166
1930
  */
1931
+ /** What a cell-menu builder and a host item's `action` are handed. */
1932
+ export interface CellMenuParams {
1933
+ key: string;
1934
+ colId: string;
1935
+ value: unknown;
1936
+ /** The row wrapper. */
1937
+ row: Row;
1938
+ /** Your original row object. */
1939
+ data: unknown;
1940
+ column: ResolvedColumn;
1941
+ index: number;
1942
+ grid: Grid;
1943
+ }
1944
+
1945
+ /** What a host rail action's `run` is handed. */
1946
+ /** The rail's built-in action names, plus `'-'` for a divider. */
1947
+ export type RailActionName =
1948
+ | 'undo' | 'redo' | 'pause' | 'restore' | 'maximise'
1949
+ | 'export' | 'excel' | 'clipboard' | 'print';
1950
+
1951
+ export interface RailActionParams {
1952
+ grid: Grid;
1953
+ keys: string[];
1954
+ cells: { key: string; colId: string }[];
1955
+ }
1956
+
1957
+ export interface RailAction {
1958
+ name: string;
1959
+ title: string | (() => string);
1960
+ icon?: string | (() => string);
1961
+ run(params: RailActionParams): void;
1962
+ enabled?(): boolean;
1963
+ }
1964
+
1965
+ /** The result of evaluating a formula a user typed into a cell (spec 8.11). */
1966
+ export type FormulaResult =
1967
+ | { ok: true; value: unknown; references: string[] }
1968
+ | { ok: false; error: string; at?: number };
1969
+
1970
+ export function evaluateFormula(text: string, params?: ParseParams): FormulaResult;
1971
+ export function looksLikeFormula(text: unknown): boolean;
1972
+
1167
1973
  export interface MaximiseApi {
1168
1974
  enter(): boolean;
1169
1975
  exit(): boolean;
@@ -1190,7 +1996,27 @@ export interface Grid {
1190
1996
  readonly licence: LicenceApi;
1191
1997
  readonly pagination: PaginationApi;
1192
1998
  readonly highlight: HighlightApi;
1999
+ readonly redaction: RedactionApi;
2000
+ capture?(opts?: CaptureOptions): Promise<Blob>;
2001
+ annotate?: AnnotationApi;
2002
+ readonly presentation: PresentationApi;
2003
+ readonly updates: UpdatesApi;
2004
+ readonly timeline: TimelineApi;
2005
+ readonly facets: FacetsApi;
2006
+ readonly detail: DetailApi;
2007
+ readonly comments: CommentsApi;
2008
+ readonly presence: PresenceApi;
2009
+ readonly diagnostics: DiagnosticsApi;
2010
+ readonly formatting: FormattingApi;
1193
2011
  readonly maximise?: MaximiseApi;
2012
+ /**
2013
+ * The element you passed to `createGrid`, not the grid's own root.
2014
+ *
2015
+ * The grid builds its `.lattice` root *inside* that element, so
2016
+ * `el.closest('.lattice')` never matches this, and a theme attribute set on
2017
+ * it has no effect — the theme is read from the root within. Use
2018
+ * `element.querySelector('.lattice')` for the grid's own root.
2019
+ */
1194
2020
  readonly element: HTMLElement | null;
1195
2021
  readonly destroyed: boolean;
1196
2022
  /** False until the first render has been laid out. */