@toclocoinc/lattice-grid 1.55.0 → 1.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +1 -1
  2. package/docs/API.html +389 -42
  3. package/docs/api-detail.html +373 -8
  4. package/lattice-grid.d.ts +525 -26
  5. package/lattice-grid.esm.min.js +1943 -920
  6. package/lattice-grid.min.cjs +1942 -920
  7. package/lattice-grid.min.css +1 -1
  8. package/lattice-grid.min.js +1942 -920
  9. package/modules/ai.esm.min.js +6 -4
  10. package/modules/ai.min.cjs +6 -4
  11. package/modules/ai.min.js +6 -4
  12. package/modules/angular.esm.min.js +7 -4
  13. package/modules/angular.min.cjs +7 -4
  14. package/modules/angular.min.js +7 -4
  15. package/modules/chart-alluvial.esm.min.js +1 -1
  16. package/modules/chart-arc.esm.min.js +1 -1
  17. package/modules/chart-bubblemap.esm.min.js +1 -1
  18. package/modules/chart-bump.esm.min.js +1 -1
  19. package/modules/chart-calendar.esm.min.js +1 -1
  20. package/modules/chart-decomposition.esm.min.js +1 -1
  21. package/modules/chart-diverging.esm.min.js +1 -1
  22. package/modules/chart-dumbbell.esm.min.js +1 -1
  23. package/modules/chart-fan.esm.min.js +1 -1
  24. package/modules/chart-hexbin.esm.min.js +1 -1
  25. package/modules/chart-hexmap.esm.min.js +1 -1
  26. package/modules/chart-icicle.esm.min.js +1 -1
  27. package/modules/chart-parallel.esm.min.js +1 -1
  28. package/modules/chart-ridgeline.esm.min.js +1 -1
  29. package/modules/chart-roc.esm.min.js +1 -1
  30. package/modules/chart-slope.esm.min.js +1 -1
  31. package/modules/chart-splom.esm.min.js +1 -1
  32. package/modules/chart-waffle.esm.min.js +1 -1
  33. package/modules/charts.esm.min.js +15 -10
  34. package/modules/charts.min.cjs +15 -10
  35. package/modules/charts.min.js +15 -10
  36. package/modules/data-router.esm.min.js +37 -4
  37. package/modules/data-router.min.cjs +37 -4
  38. package/modules/data-router.min.js +37 -4
  39. package/modules/devtools.esm.min.js +2 -2
  40. package/modules/devtools.min.cjs +2 -2
  41. package/modules/devtools.min.js +2 -2
  42. package/modules/dhtmlx-compat.esm.min.js +4 -4
  43. package/modules/dhtmlx-compat.min.cjs +4 -4
  44. package/modules/dhtmlx-compat.min.js +4 -4
  45. package/modules/gantt.esm.min.js +4 -4
  46. package/modules/gantt.min.cjs +4 -4
  47. package/modules/gantt.min.js +4 -4
  48. package/modules/htmx.esm.min.js +1939 -920
  49. package/modules/htmx.min.cjs +1939 -920
  50. package/modules/htmx.min.js +1939 -920
  51. package/modules/kanban.esm.min.js +106 -25
  52. package/modules/kanban.min.cjs +106 -25
  53. package/modules/kanban.min.js +106 -25
  54. package/modules/kpi.esm.min.js +44 -7
  55. package/modules/kpi.min.cjs +44 -7
  56. package/modules/kpi.min.js +44 -7
  57. package/modules/layout.esm.min.js +4 -4
  58. package/modules/layout.min.cjs +4 -4
  59. package/modules/layout.min.js +4 -4
  60. package/modules/mock-socket.esm.min.js +2 -2
  61. package/modules/mock-socket.min.cjs +2 -2
  62. package/modules/mock-socket.min.js +2 -2
  63. package/modules/react.esm.min.js +7 -4
  64. package/modules/react.min.cjs +7 -4
  65. package/modules/react.min.js +7 -4
  66. package/modules/svelte.esm.min.js +7 -4
  67. package/modules/svelte.min.cjs +7 -4
  68. package/modules/svelte.min.js +7 -4
  69. package/modules/tabs.esm.min.js +4 -4
  70. package/modules/tabs.min.cjs +4 -4
  71. package/modules/tabs.min.js +4 -4
  72. package/modules/vue.esm.min.js +7 -4
  73. package/modules/vue.min.cjs +7 -4
  74. package/modules/vue.min.js +7 -4
  75. package/modules/webcomponent.esm.min.js +1942 -920
  76. package/modules/webcomponent.min.cjs +1942 -920
  77. package/modules/webcomponent.min.js +1942 -920
  78. package/package.json +3 -2
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.55.0, type declarations
2
+ * Lattice Grid 1.57.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -340,7 +340,8 @@ export interface Option {
340
340
  label: string;
341
341
  disabled?: boolean;
342
342
  variant?: VariantName;
343
- icon?: string;
343
+ /** A glyph name from the icon registry (see {@link IconName}), shown before the label. */
344
+ icon?: IconName;
344
345
  group?: string;
345
346
  }
346
347
 
@@ -364,6 +365,32 @@ export interface LookupSpec {
364
365
  export type DecorationName = 'plain' | 'fill' | 'pill' | 'dot' | 'bar' | 'heat' | 'icon';
365
366
  export type VariantName = 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'accent' | 'none' | (string & {});
366
367
 
368
+ /**
369
+ * A glyph name from the icon sprite registry (`packages/dom/src/cell/icons.js`).
370
+ *
371
+ * The union below is every built-in name, generated from `iconNames()` so an
372
+ * editor can autocomplete and typo-check them — see
373
+ * `test/icon-name-type-drift.test.js`, which fails if this list and the
374
+ * registry ever disagree. It is deliberately **not closed**: the registry is
375
+ * extensible at runtime via `registerIcon`, `registerIcons`, `config.icons` and
376
+ * `grid.icons`, and `(string & {})` widens the type so a custom registered name
377
+ * still typechecks without losing autocomplete on the built-ins. A name the
378
+ * registry has never heard of — built-in or custom — draws a blank glyph and
379
+ * warns once at runtime; it is not a type error.
380
+ */
381
+ export type IconName =
382
+ | 'chevronRight' | 'chevronDown' | 'chevronUp' | 'chevronLeft'
383
+ | 'check' | 'dash' | 'close' | 'plus' | 'minus'
384
+ | 'info' | 'success' | 'warning' | 'danger' | 'clock' | 'lock'
385
+ | 'link' | 'external' | 'filter' | 'pause' | 'play' | 'chart' | 'palette'
386
+ | 'undo' | 'redo' | 'columns' | 'download' | 'restore' | 'spreadsheet' | 'print'
387
+ | 'maximise' | 'minimise' | 'views' | 'search' | 'pencil' | 'trash' | 'share'
388
+ | 'pin' | 'sortAsc' | 'sortDesc' | 'menu' | 'drag'
389
+ | 'star' | 'heart' | 'circleFilled' | 'square' | 'bolt' | 'flag'
390
+ | 'arrow' | 'highlight' | 'thumbUp' | 'eye' | 'eyeOff' | 'copy' | 'present'
391
+ | 'blank'
392
+ | (string & {});
393
+
367
394
  /** A built-in threshold icon set, mapping value bands to built-in glyphs. */
368
395
  export type IconSetName = 'trafficLights' | 'arrows' | 'trafficArrows' | 'ratings' | (string & {});
369
396
 
@@ -375,7 +402,8 @@ export type IconSetName = 'trafficLights' | 'arrows' | 'trafficArrows' | 'rating
375
402
  */
376
403
  export interface IconBand {
377
404
  min?: number;
378
- icon: string;
405
+ /** A glyph name from the icon registry (see {@link IconName}). */
406
+ icon: IconName;
379
407
  label?: string;
380
408
  variant?: VariantName;
381
409
  }
@@ -387,7 +415,12 @@ export interface DecorationSpec {
387
415
  outline?: boolean;
388
416
  edge?: boolean;
389
417
  position?: 'start' | 'end';
390
- name?: string | Record<string, string>;
418
+ /**
419
+ * `icon` decoration only: either a single glyph name (see {@link IconName})
420
+ * used for every value, or a value -> glyph name map for exact-value icons.
421
+ * Omit both `name` and `bands` to use `iconSet`/its default instead.
422
+ */
423
+ name?: IconName | Record<string, IconName>;
391
424
  /** icon only: a built-in threshold icon set, expanded to `bands`. */
392
425
  iconSet?: IconSetName;
393
426
  /** icon only: value bands mapped to glyphs, first match by descending `min`. */
@@ -626,7 +659,10 @@ export interface ColumnHeaderSpec {
626
659
  render?: string | RendererCtor;
627
660
  /** Props passed to `render` as `params.props`. */
628
661
  props?: Record<string, unknown>;
629
- /** A class, or classes, added to the heading cell. */
662
+ /**
663
+ * A class, or classes, added to the heading cell. A string may hold several
664
+ * space-separated tokens (`'a b'`), each applied individually.
665
+ */
630
666
  class?: string | string[];
631
667
  tooltip?: string;
632
668
  align?: Align;
@@ -1354,8 +1390,9 @@ export interface DerivedSourceConfig {
1354
1390
  * change on the parent re-derives the whole thing. `correlation` additionally
1355
1391
  * scans the rows once *per pair*, so N columns cost N·(N−1)/2 passes. Use
1356
1392
  * `refresh` (`'idle'` is the default; `'manual'` or a debounce in ms for an
1357
- * expensive analysis over a live feed) — see `docs/api-detail.html` for the
1358
- * measured figures.
1393
+ * expensive analysis over a live feed; under `'manual'` the host re-derives
1394
+ * by calling `rows.load()` on the derived grid) — see `docs/api-detail.html`
1395
+ * for the measured figures.
1359
1396
  *
1360
1397
  * Every row carries `n`, the rows the figure covered, because a derived
1361
1398
  * statistic travels into an export or a chart without its grid and "r = 0.98
@@ -1368,7 +1405,15 @@ export interface DerivedSourceConfig {
1368
1405
  */
1369
1406
  statistics?: DerivedStatistics;
1370
1407
 
1371
- /** When to re-derive. `idle` by default: coalesced to a frame. */
1408
+ /**
1409
+ * When to re-derive. `idle` by default: coalesced to a frame. A number
1410
+ * debounces by that many milliseconds; `live` re-derives on every change.
1411
+ * `manual` never re-derives on its own: the host triggers it by calling
1412
+ * `rows.load()`, with no argument, on the derived grid - from a Refresh
1413
+ * button, say. Each call re-reads `from` there and then and replaces the
1414
+ * rows; a derived grid takes its rows from `from`, so anything passed to
1415
+ * `load` is not used. Executed example: `docs/api-detail.html#derived-manual-refresh`.
1416
+ */
1372
1417
  refresh?: 'live' | 'idle' | 'manual' | number;
1373
1418
 
1374
1419
  /**
@@ -1566,9 +1611,20 @@ export interface DetailConfig {
1566
1611
  }
1567
1612
 
1568
1613
  export interface SelectionConfig {
1614
+ /** `'none'` also turns off `ranges` and `fillHandle` unless either is set explicitly alongside it. */
1569
1615
  mode?: 'none' | 'single' | 'multiple';
1570
1616
  checkbox?: boolean;
1571
1617
  headerCheckbox?: boolean;
1618
+ /**
1619
+ * Only the `checkbox` column may change row selection — a click anywhere
1620
+ * else in the row, and Space with focus anywhere but the checkbox, leave
1621
+ * selection untouched. Range and cell selection are unaffected either way.
1622
+ * For a host whose row click is bound to its own action (opening a record):
1623
+ * without this, that click also selects the row, so a later bulk action can
1624
+ * reach rows nobody chose. Off by default. `mode: 'none'` already refuses
1625
+ * every selection path regardless of this flag.
1626
+ */
1627
+ checkboxOnly?: boolean;
1572
1628
  groupSelectsChildren?: boolean;
1573
1629
  groupSelectsFiltered?: boolean;
1574
1630
  ranges?: boolean;
@@ -1703,8 +1759,13 @@ export interface GridConfig {
1703
1759
  * What identifies a row. Everything that survives a refresh (selection,
1704
1760
  * expansion, and edits in flight) is keyed on it, so it must be stable and
1705
1761
  * unique. A derived grid defaults to its own derived key.
1762
+ *
1763
+ * Three shapes: a field name (`'id'`, dot paths allowed); an array of field
1764
+ * names, joined into one composite key (`['tenantId', 'circuitId']`); or a
1765
+ * function of the row (`row => \`${row.tenantId}#${row.circuitId}\``),
1766
+ * itself allowed to return an array to the same effect.
1706
1767
  */
1707
- rowKey?: string | ((row: unknown) => string);
1768
+ rowKey?: string | string[] | ((row: unknown) => string | string[]);
1708
1769
  /** Where rows come from: memory, paged, remote, stream or derived. */
1709
1770
  source?: SourceConfig;
1710
1771
  /** How rows are ingested into the column store. */
@@ -1735,7 +1796,11 @@ export interface GridConfig {
1735
1796
  tree?: TreeConfig;
1736
1797
  /** The expandable panel beneath a row. */
1737
1798
  detail?: DetailConfig;
1738
- /** What the user may select, and how selection behaves across groups. */
1799
+ /**
1800
+ * What the user may select, and how selection behaves across groups.
1801
+ * The `'none'` shorthand is `{ mode: 'none' }` and behaves identically: no
1802
+ * row selection, and no cell ranges or fill handle either.
1803
+ */
1739
1804
  selection?: SelectionConfig | 'single' | 'multiple' | 'none';
1740
1805
  /** Editing, and how a change is committed and validated. */
1741
1806
  edit?: EditConfig | boolean;
@@ -2158,7 +2223,12 @@ export interface GridConfig {
2158
2223
  * measured; it is about whether the ceiling applies.
2159
2224
  */
2160
2225
  autoHeight?: boolean | 'visible';
2161
- /** Sort, filters, grouping, widths and the rest, restored at construction. */
2226
+ /**
2227
+ * Sort, filters, grouping, widths and the rest, restored at construction.
2228
+ * Takes precedence over a saved view flagged `isDefault`: when both are
2229
+ * present, this wins outright and the default view is never applied — the
2230
+ * active view id stays `null`.
2231
+ */
2162
2232
  state?: GridState;
2163
2233
  /** Your licence key. Without one the grid renders in full and watermarks off localhost. */
2164
2234
  licence?: string;
@@ -2664,6 +2734,15 @@ export interface GridState {
2664
2734
  /** The banded-header tree, when the grid has one (BACKLOG-0000739). */
2665
2735
  columnGroups?: ColumnGroupState[];
2666
2736
  filters?: FilterSet;
2737
+ /**
2738
+ * The `where` predicates that were in force, as names only (BACKLOG-0001202).
2739
+ * A predicate is host code: it cannot be serialised into a view or restored
2740
+ * from one. `apply` reconciles these against what the host has registered and
2741
+ * reports every name it cannot honour rather than restoring a view that
2742
+ * silently shows more rows than the one that was saved. Absent when none is
2743
+ * registered.
2744
+ */
2745
+ where?: string[];
2667
2746
  quick?: string;
2668
2747
  sort?: SortEntry[];
2669
2748
  group?: string[];
@@ -2692,6 +2771,27 @@ export interface StateApplyReport {
2692
2771
  skipped: { key: string; reason: string }[];
2693
2772
  }
2694
2773
 
2774
+ /**
2775
+ * Every top-level section of a {@link GridState} bar `version` — the
2776
+ * vocabulary `state.apply`'s `skip` list, `StateApplyReport.applied` and
2777
+ * `StateChangedEvent.sections` all speak, derived from `GridState` itself so a
2778
+ * new section cannot appear in one and be missing from the others.
2779
+ */
2780
+ export type StateSection = Exclude<keyof GridState, 'version'>;
2781
+
2782
+ /**
2783
+ * What caused a `state:changed` (BACKLOG-0001182).
2784
+ *
2785
+ * `'user'` is a change to one part of the view — a sort, a filter, a column
2786
+ * moved, resized, pinned or hidden, a grouping, a page — whether it arrived as
2787
+ * a gesture or as the equivalent API call. `'apply'` is `state.apply()`,
2788
+ * including the restore an undo performs and a `config.state` seed at
2789
+ * construction. `'reset'` is `state.reset()`, and is the one a persistence
2790
+ * layer skips: saving the reset arrangement writes the default straight back
2791
+ * over the view the user had just abandoned.
2792
+ */
2793
+ export type StateChangeCause = 'user' | 'apply' | 'reset';
2794
+
2695
2795
  // ---------------------------------------------------------------------------
2696
2796
  // Conditional formatting (spec 8.12)
2697
2797
  // ---------------------------------------------------------------------------
@@ -4089,6 +4189,11 @@ export type EventName =
4089
4189
  | 'model:changed' | 'rows:changed' | 'rows:queued' | 'rows:deferred'
4090
4190
  | 'rows:paused' | 'rows:resumed' | 'row:received' | 'row:sent' | 'row:copied'
4091
4191
  | 'row:moved' | 'source:error' | 'stream:chunk' | 'stream:end' | 'stream:evicted'
4192
+ /* The row-drag gesture as it happens (BACKLOG-0001224). Notifications only:
4193
+ * the drop is already vetoable by `beforeRowMove` and `beforeRowReceive`, and
4194
+ * a third veto on the same gesture would be a fourth place to look. All four
4195
+ * fire on the grid the drag started in and carry a {@link RowDragEvent}. */
4196
+ | 'rowDrag:started' | 'rowDrag:moved' | 'rowDrag:left' | 'rowDrag:ended'
4092
4197
  /* Cells and editing */
4093
4198
  | 'cell:changed' | 'cell:pending' | 'cell:confirmed' | 'cell:reverted' | 'cell:conflict'
4094
4199
  | 'cell:clicked' | 'cell:dblclicked' | 'cell:contextmenu'
@@ -4145,11 +4250,14 @@ export type EventName =
4145
4250
  | 'beforeEdit' | 'beforeSort' | 'beforeFilter'
4146
4251
  | 'beforeColumnMove' | 'beforeColumnResize' | 'beforeColumnHide'
4147
4252
  | 'beforeSelect' | 'beforeRowAdd' | 'beforeDelete' | 'beforeRowMove' | 'beforeGroup'
4253
+ /* A row dropped in from another grid, on the receiving grid (BACKLOG-0001225):
4254
+ * a {@link BeforeRowReceiveEvent}. */
4255
+ | 'beforeRowReceive'
4148
4256
  /* Their cancellation notifications (past-tense, non-cancellable). */
4149
4257
  | 'edit:cancelled' | 'sort:cancelled' | 'filter:cancelled'
4150
4258
  | 'columnMove:cancelled' | 'columnResize:cancelled' | 'columnHide:cancelled'
4151
4259
  | 'selection:cancelled' | 'rowAdd:cancelled' | 'delete:cancelled'
4152
- | 'rowMove:cancelled' | 'group:cancelled'
4260
+ | 'rowMove:cancelled' | 'group:cancelled' | 'rowReceive:cancelled'
4153
4261
  /* Every event at once, for logging and debugging. */
4154
4262
  | '*';
4155
4263
 
@@ -4191,6 +4299,195 @@ export interface BeforeEvent extends GridEvent {
4191
4299
  reason: string | null;
4192
4300
  }
4193
4301
 
4302
+ /**
4303
+ * The `beforeRowReceive` event (BACKLOG-0001225): a row dragged from another
4304
+ * grid is about to be inserted into this one. Fires on the **receiving** grid,
4305
+ * before the insert, with the row under the pointer named — so a drop that
4306
+ * means "assign this to that" can be recorded by the host and the insert
4307
+ * stopped with `preventDefault(reason)`.
4308
+ *
4309
+ * A veto leaves the source grid untouched: the row stays where it was, and
4310
+ * neither `row:sent` nor `row:copied` fires there. The source removes its row
4311
+ * only after the target has admitted it, and a veto is a refusal to admit.
4312
+ * The paired `rowReceive:cancelled` carries the same context plus the reason.
4313
+ *
4314
+ * Like every {@link BeforeEvent}, the handler may be `async`; the insert is
4315
+ * held until it settles, and is cancelled as `'stale'` (BACKLOG-0001242) if
4316
+ * the source row is gone by then, or if the row under the pointer is gone or
4317
+ * has moved to a different index — `at` names a slot as "before `overKey`",
4318
+ * and once that is no longer where `overKey`'s row sits, `at` is a stale index
4319
+ * into a list that changed while the handler was thinking, not the slot the
4320
+ * drop meant. `overKey: null` (the drop landed on no row) has no row to drift
4321
+ * against and is never stale on that account.
4322
+ */
4323
+ export interface BeforeRowReceiveEvent extends BeforeEvent {
4324
+ /**
4325
+ * The row about to be inserted: a shallow copy of the source row's data,
4326
+ * and the very object that is inserted if no handler vetoes, so a change
4327
+ * made to it here lands with the row.
4328
+ */
4329
+ data: Record<string, unknown>;
4330
+ /**
4331
+ * The display index the row would be inserted at: the index of the row
4332
+ * under the pointer, or `rows.count()` when the drop landed on no row. When
4333
+ * `overKey` names a row, this is guaranteed to still be that row's index at
4334
+ * the moment the insert actually runs — an async handler that leaves the
4335
+ * named row at a different index causes the drop to be cancelled as
4336
+ * `'stale'` (BACKLOG-0001242) rather than inserted at this index regardless.
4337
+ */
4338
+ at: number;
4339
+ /**
4340
+ * The key of the row under the pointer when the drop happened — the row the
4341
+ * user meant. Null when the drop landed past the last row, on empty space,
4342
+ * on the header, or on a pinned row: there is no row to name, and a nearest
4343
+ * guess would be wrong in a way that looks right.
4344
+ */
4345
+ overKey: string | null;
4346
+ /** The grid the row is being dragged from. */
4347
+ source: Grid;
4348
+ }
4349
+
4350
+ /**
4351
+ * The `rowReceive:cancelled` event (BACKLOG-0001225): a `beforeRowReceive`
4352
+ * was vetoed, or went stale during an async handler. Nothing was inserted and
4353
+ * the source grid is untouched.
4354
+ */
4355
+ export interface RowReceiveCancelledEvent extends GridEvent {
4356
+ /** The row that was not inserted, as the handler saw it. */
4357
+ data: Record<string, unknown>;
4358
+ /** The display index it would have taken. */
4359
+ at: number;
4360
+ /** The key of the row under the pointer, or null. */
4361
+ overKey: string | null;
4362
+ /** The grid the row would have come from; it still holds the row. */
4363
+ source: Grid;
4364
+ /**
4365
+ * The reason given to `preventDefault`, `'prevented'` when none was given,
4366
+ * or `'stale'` when the row under the pointer or the source row was gone by
4367
+ * the time an async handler settled.
4368
+ */
4369
+ reason: string;
4370
+ }
4371
+
4372
+ /**
4373
+ * The row-drag lifecycle events (BACKLOG-0001224): `rowDrag:started`,
4374
+ * `rowDrag:moved`, `rowDrag:left` and `rowDrag:ended`, which report a row drag
4375
+ * *as it happens* rather than once it has settled. Before them a host got the
4376
+ * handle the grid draws and then one settled event, with nothing in between to
4377
+ * highlight a candidate target, drive a custom drop indicator, or react when
4378
+ * the pointer left the grid.
4379
+ *
4380
+ * **All four fire on the grid the drag started in**, whether the row is being
4381
+ * reordered within that grid or dragged into another one. A drag is one gesture
4382
+ * with one owner, and the source grid is the only grid present for the whole of
4383
+ * it — the pointer may cross several others, or none. `over` names whichever
4384
+ * grid the event is about, so a single subscription can drive decoration on any
4385
+ * of them.
4386
+ *
4387
+ * **Notifications, not gates.** None of these is cancellable and none carries
4388
+ * `preventDefault`. The drop is already vetoable twice over — `beforeRowMove`
4389
+ * for a reorder, `beforeRowReceive` for a drop into another grid — and a third
4390
+ * veto on the same gesture would be a third place to look when a drop does not
4391
+ * happen.
4392
+ *
4393
+ * **What is safe to do in a handler.** Read, measure and draw: highlight a
4394
+ * candidate row, move an indicator, update a side panel. Do not mutate rows,
4395
+ * columns, sort, filters or grouping from one of these. The drag resolves where
4396
+ * it would land against the display order, so changing that order mid-gesture
4397
+ * moves the ground under the drop; and `data` is the source row's own object
4398
+ * rather than a copy, so writing to it edits the row that is still in the grid
4399
+ * without announcing it. Work that changes the grid belongs in
4400
+ * `beforeRowReceive`, which is asked before the insert, or in the settled
4401
+ * events afterwards.
4402
+ *
4403
+ * **`rowDrag:moved` is coalesced to one event per animation frame**, carrying
4404
+ * the latest pointer position of that frame, so a handler runs at the display's
4405
+ * rate rather than the pointer's several hundred events a second. The other
4406
+ * three fire on the transition itself.
4407
+ *
4408
+ * The sequence for any gesture is `rowDrag:started`, then `rowDrag:moved` and
4409
+ * `rowDrag:left` as the pointer travels, then exactly one `rowDrag:ended` —
4410
+ * including when the pointer is released outside every grid. No `rowDrag:moved`
4411
+ * is delivered after `rowDrag:ended`. A press that never passes the drag
4412
+ * threshold is a click and raises none of them; a grid destroyed mid-drag
4413
+ * raises no `rowDrag:ended`.
4414
+ */
4415
+ export interface RowDragEvent extends GridEvent {
4416
+ /** The key of the row being dragged. */
4417
+ key: string;
4418
+ /**
4419
+ * The dragged row's data as it stands in the source grid — that row's own
4420
+ * object, not a copy. Null if the row has left the source during the drag.
4421
+ */
4422
+ data: Record<string, unknown> | null;
4423
+ /**
4424
+ * The grid the event is about: the grid under the pointer for
4425
+ * `rowDrag:started`, `rowDrag:moved` and `rowDrag:ended`, and the grid just
4426
+ * left for `rowDrag:left`. Null when the pointer is over no grid at all.
4427
+ */
4428
+ over: Grid | null;
4429
+ /**
4430
+ * Where the row would land in `over`: the display index it would take. Null
4431
+ * when there is no candidate to report — the pointer is over no grid, over a
4432
+ * grid that will refuse the row, or over a header; and on `rowDrag:left`,
4433
+ * which is about a grid the pointer has already gone from.
4434
+ */
4435
+ at: number | null;
4436
+ /**
4437
+ * The key of the row under the pointer in `over`, or null where there is no
4438
+ * row to name: past the last row, on empty space, on a header, on a pinned
4439
+ * row, on a grid that will refuse the drop, or on `rowDrag:left`.
4440
+ */
4441
+ overKey: string | null;
4442
+ /**
4443
+ * `rowDrag:ended` only: whether the release is being acted on — a transfer
4444
+ * the target accepts, or a same-grid reorder that is a real move and is not
4445
+ * refused by a sort, filter or grouping. False when the row was released over
4446
+ * no grid, over a grid that refuses it, or back where it started. What became
4447
+ * of an acted-on drop is reported by `row:moved`, `row:sent`, `row:received`
4448
+ * and `rowReceive:cancelled`.
4449
+ */
4450
+ dropped?: boolean;
4451
+ }
4452
+
4453
+ /**
4454
+ * The `state:changed` event (BACKLOG-0001182).
4455
+ *
4456
+ * Fires once per logical state change, whether it began as a user gesture or
4457
+ * as a programmatic call, so view persistence is built on this one event
4458
+ * rather than on the ten individual ones — `reset()` raises those too, which
4459
+ * made a debounced save write the reset arrangement back.
4460
+ *
4461
+ * **Exactly one event per change.** A change that internally routes through
4462
+ * `state.apply()` — applying a saved view, an undo, a reset — announces itself
4463
+ * once, carrying the outermost cause rather than the inner mechanism's.
4464
+ *
4465
+ * A host predicate registered, replaced or removed through
4466
+ * `filters.where(name, fn)`, and a `filters.reapply()` that re-runs one, go
4467
+ * through the same tracked door as `sort` and `filters`: each fires this
4468
+ * event once, `cause: 'user'`, with `'where'` in `sections` (BACKLOG-0001235).
4469
+ */
4470
+ export interface StateChangedEvent extends GridEvent {
4471
+ /** Why the state changed. `'reset'` is the one a save should ignore. */
4472
+ cause: StateChangeCause;
4473
+ /**
4474
+ * Which sections moved, sorted and de-duplicated. For `'apply'` and
4475
+ * `'reset'` these are the sections the report applied; for `'user'`, the
4476
+ * sections the change touches.
4477
+ */
4478
+ sections: StateSection[];
4479
+ /**
4480
+ * The state that was applied — present for `'apply'` and `'reset'`, null for
4481
+ * `'user'`. A full capture on every gesture would put an unsanitised copy of
4482
+ * the state, hidden column ids and widths included, on the bus for every
4483
+ * listener; a host calls `grid.state.get()` when it decides to write, which
4484
+ * is permission-sanitised.
4485
+ */
4486
+ state: GridState | null;
4487
+ /** What an apply could not restore; null for `'user'`. */
4488
+ report: StateApplyReport | null;
4489
+ }
4490
+
4194
4491
  export type EventHandler = (e: GridEvent) => void;
4195
4492
  export type Unsubscribe = () => void;
4196
4493
 
@@ -4489,17 +4786,84 @@ export interface CellRange {
4489
4786
  columns: string[];
4490
4787
  }
4491
4788
 
4789
+ /**
4790
+ * How a `where` predicate is re-evaluated, whether `filters.clear()` may remove
4791
+ * it, and what the source may be told about it (BACKLOG-0001202).
4792
+ */
4793
+ export interface WhereOptions {
4794
+ /**
4795
+ * The columns the predicate reads, in the same spirit as `value.deps` on a
4796
+ * computed column (§8.4.2). Declared, the verdict is cached per row and
4797
+ * re-run only when one of these columns changes on that row. Omitted, the
4798
+ * predicate is treated as reading the whole row and is called on every pass —
4799
+ * never stale, and never skipped either.
4800
+ */
4801
+ deps?: string[];
4802
+ /**
4803
+ * Survive `filters.clear()`. For a predicate that is not the user's filter —
4804
+ * row-level permissions, tenant scoping — where a "clear filters" button must
4805
+ * never widen what the user can see.
4806
+ */
4807
+ pinned?: boolean;
4808
+ /**
4809
+ * A declarative twin of the predicate, pushed to the source while the function
4810
+ * stays as the residual. On a pushdown engine this narrows the fetch instead
4811
+ * of filtering a page client-side. It must be implied by the predicate: the
4812
+ * grid ANDs both, so a twin wider than the function costs only time, while one
4813
+ * narrower than it hides rows the function would have kept.
4814
+ */
4815
+ condition?: FilterSet;
4816
+ }
4817
+
4492
4818
  export interface FiltersApi {
4493
4819
  /** The quick filter's text and match mode, for restoring a control. */
4494
4820
  quickState(): { text: string; mode: string };
4495
4821
  get(): FilterSet;
4496
4822
  set(filters: FilterSet): void;
4823
+ /**
4824
+ * Drop the condition tree, the quick filter, and every `where` predicate that
4825
+ * was not registered `{ pinned: true }`.
4826
+ */
4497
4827
  clear(): void;
4498
4828
  quick(text: string): void;
4829
+ /** The names of the `where` predicates in force, in registration order. */
4830
+ where(): string[];
4831
+ /**
4832
+ * Register, replace or remove a named row predicate composed with the filter
4833
+ * set (BACKLOG-0001202).
4834
+ *
4835
+ * Registering *is* activating: there is no companion "a predicate is present"
4836
+ * flag to keep in sync, which is the failure mode this replaces. Several may
4837
+ * be in force at once under their own names, ANDed with each other and with
4838
+ * the declarative set, and removing one leaves the rest alone. The predicate
4839
+ * is handed the **data row**.
4840
+ *
4841
+ * grid.filters.where('visibleToMe', row => row.owner === me);
4842
+ * grid.filters.where('rateKnown', row => rates.has(row.ccy),
4843
+ * { deps: ['ccy'], pinned: true });
4844
+ * grid.filters.where('visibleToMe', null); // remove
4845
+ *
4846
+ * Only the names reach `filters.get()` and `state.get()`; the functions never
4847
+ * do.
4848
+ * @param name the name to register under
4849
+ * @param predicate the predicate, or null to remove it
4850
+ * @param opts re-evaluation, pinning, and the pushed-down twin
4851
+ */
4852
+ where(name: string, predicate: ((row: any) => boolean) | null, opts?: WhereOptions): void;
4853
+ /**
4854
+ * Re-run `where` predicates whose inputs changed where the grid could not see
4855
+ * it — a rate table that arrived late, a permission set that refreshed. The
4856
+ * out-of-band half of re-evaluation; `deps` is the half the grid observes for
4857
+ * itself. Together they replace the manual "filter again" call.
4858
+ * @param name the predicate to re-run; every one when omitted
4859
+ * @returns whether anything was re-run
4860
+ */
4861
+ reapply(name?: string): boolean;
4499
4862
  }
4500
4863
 
4501
4864
  export interface SortApi {
4502
4865
  get(): SortEntry[];
4866
+ /** Replace the sort model; an entry naming no known column is dropped with a warning. */
4503
4867
  set(entries: SortEntry[]): void;
4504
4868
  clear(): void;
4505
4869
  }
@@ -4743,6 +5107,11 @@ export interface SavedView {
4743
5107
  name: string;
4744
5108
  description: string;
4745
5109
  shared: boolean;
5110
+ /**
5111
+ * Applied on load when no `config.state` is given. `config.state` wins
5112
+ * outright over this flag: with both present, the default view is never
5113
+ * applied and the active view id stays `null`.
5114
+ */
4746
5115
  isDefault: boolean;
4747
5116
  /** Supplied in `config.views.saved`: listed apart, and not renamable or deletable. */
4748
5117
  builtin: boolean;
@@ -5375,7 +5744,11 @@ export interface FindApi {
5375
5744
  export interface StateApi {
5376
5745
  get(): GridState;
5377
5746
  apply(state: GridState, opts?: { skip?: (keyof GridState)[] }): StateApplyReport;
5378
- /** The state the grid started in, captured once after `config.state`. */
5747
+ /**
5748
+ * The grid as configured, without `config.state` — captured once, before
5749
+ * that seed is applied, so a view opened through `config.state` is never
5750
+ * itself mistaken for the default `reset()` returns to.
5751
+ */
5379
5752
  baseline(): GridState | null;
5380
5753
  /** Put the grid back the way it started, as one undoable step. */
5381
5754
  reset(): StateApplyReport | null;
@@ -5596,7 +5969,18 @@ export interface RailActionParams {
5596
5969
  export interface RailAction {
5597
5970
  name: string;
5598
5971
  title: string | (() => string);
5599
- icon?: string | (() => string);
5972
+ /**
5973
+ * A glyph name from the icon registry (see {@link IconName}) — a built-in
5974
+ * name, or one registered with `registerIcon`/`registerIcons`,
5975
+ * `config.icons` or `grid.icons`. A function form is re-read on every
5976
+ * repaint, the same as `title`, so a toggle can swap its glyph with its
5977
+ * state. When omitted, the rail tries `name` as the icon name instead (so an
5978
+ * action named after a built-in, e.g. `'undo'`, needs no separate `icon`);
5979
+ * an unrecognised name — from either `icon` or the `name` fallback — draws a
5980
+ * blank glyph, and only an explicitly-given unrecognised `icon` warns once
5981
+ * in the console.
5982
+ */
5983
+ icon?: IconName | (() => IconName);
5600
5984
  run(params: RailActionParams): void;
5601
5985
  enabled?(): boolean;
5602
5986
  /**
@@ -6357,6 +6741,28 @@ export function graphqlAdapter(options: {
6357
6741
  export function createGrid(element: HTMLElement, config?: GridConfig): Grid;
6358
6742
  export function createHeadlessGrid(config?: GridConfig): Grid;
6359
6743
 
6744
+ /**
6745
+ * House-wide defaults, merged beneath every grid built afterwards.
6746
+ *
6747
+ * For an application with many grids that should agree on theme, density or
6748
+ * row key. The exported factories cannot be wrapped in place — `createGrid` is
6749
+ * exported through a getter with no setter, so assigning over it is discarded
6750
+ * in a plain script and throws in a module — so this is the supported route.
6751
+ *
6752
+ * - **The per-grid config always wins.** Defaults sit *beneath* what
6753
+ * `createGrid`/`createHeadlessGrid` is passed; a key the grid names keeps the
6754
+ * grid's value, a key it omits takes the house value.
6755
+ * - **Plain objects deep-merge; arrays and everything else replace.** A house
6756
+ * `views: { storage }` and a grid's `views: { local: true }` both survive;
6757
+ * a grid's `columns` array replaces the house one rather than extending it.
6758
+ * - **Calling it again replaces the set, it does not accumulate.** Extend
6759
+ * explicitly with `defaults({ ...defaults(), density: 'compact' })`.
6760
+ * - **Never retroactive.** Grids already built are untouched.
6761
+ *
6762
+ * `defaults()` reads the current set; `defaults(null)` clears it.
6763
+ */
6764
+ export function defaults(config?: Partial<GridConfig> | null): Partial<GridConfig>;
6765
+
6360
6766
  /**
6361
6767
  * The library version, e.g. `'1.13.1'`.
6362
6768
  *
@@ -6440,6 +6846,7 @@ export function version(): string;
6440
6846
  export const LatticeGrid: {
6441
6847
  createGrid: typeof createGrid;
6442
6848
  createHeadlessGrid: typeof createHeadlessGrid;
6849
+ defaults: typeof defaults;
6443
6850
  registerModules: typeof registerModules;
6444
6851
  setLicence: typeof setLicence;
6445
6852
  version: typeof version;
@@ -7938,7 +8345,15 @@ declare module 'lattice-grid/modules/webcomponent' {
7938
8345
  * is disconnected.
7939
8346
  */
7940
8347
  export function defineLatticeGrid(tag?: string): void;
7941
- export function createLatticeGridElement(deps?: object): unknown;
8348
+ /**
8349
+ * Build the `<lattice-grid>` element class. The one argument is the grid
8350
+ * factory the element creates its grid with — `createGrid`-shaped, and
8351
+ * defaulting to it — injectable for tests. Returns the class, or `null`
8352
+ * where `HTMLElement` is undefined (a Node import, a server-side pass).
8353
+ */
8354
+ export function createLatticeGridElement(
8355
+ factory?: (element: Element, config: GridConfig) => Grid,
8356
+ ): typeof HTMLElement | null;
7942
8357
  export const TAG_NAME: string;
7943
8358
  export const EVENT_PREFIX: string;
7944
8359
  export const ATTRIBUTE_CONFIG: Readonly<Record<string, unknown>>;
@@ -7960,7 +8375,14 @@ declare module 'lattice-grid/modules/htmx' {
7960
8375
  */
7961
8376
  export function createGrid(element: Element, config: GridConfig): Grid;
7962
8377
  export function autoInit(root?: ParentNode): Grid[];
7963
- export function attach(element: Element, config?: GridConfig): Grid;
8378
+ /**
8379
+ * Wire the htmx lifecycle events on a document: grids are built in each
8380
+ * swapped-in fragment, released before htmx detaches one, and their view
8381
+ * state carried across history navigation. Called once on import against
8382
+ * the global `document`; call it again only for another document. Returns
8383
+ * the function that removes every listener it installed.
8384
+ */
8385
+ export function attach(doc?: Document): () => void;
7964
8386
  export function initWithin(root: ParentNode): Grid[];
7965
8387
  export function destroyWithin(root: ParentNode): void;
7966
8388
  export function gridElementsWithin(root: ParentNode): Element[];
@@ -7968,9 +8390,41 @@ declare module 'lattice-grid/modules/htmx' {
7968
8390
  export function readTable(table: Element): { columns: Column[]; rows: unknown[] };
7969
8391
  export function rowsFromFragment(fragment: ParentNode): unknown[];
7970
8392
  export function rowsFromJson(text: string): unknown[];
7971
- export function ingestResponse(grid: Grid, response: unknown): void;
7972
- export function driveServerMode(grid: Grid, opts?: object): () => void;
7973
- export function driveInfiniteScroll(grid: Grid, opts?: object): () => void;
8393
+ /**
8394
+ * Parse a response into rows by its content type: JSON through
8395
+ * `rowsFromJson`, anything else through `rowsFromFragment` against the
8396
+ * columns given. The fragment arrives already parsed; this never touches
8397
+ * `DOMParser` or `innerHTML`. Returns the rows and, when the body carried
8398
+ * one, the total.
8399
+ */
8400
+ export function ingestResponse(
8401
+ response: { contentType: string; text?: string; fragment?: ParentNode },
8402
+ columns: { field: string }[],
8403
+ ): { rows: unknown[]; total: number | undefined };
8404
+ /**
8405
+ * Drive server-side sort and filter through htmx. `trigger` is the element
8406
+ * carrying the htmx request attributes (`hx-get`, `hx-target`,
8407
+ * `hx-trigger="lattice:query-changed"`); the grid's query parameters are
8408
+ * merged into that element's request and its response ingested. Returns the
8409
+ * function that detaches everything this attached.
8410
+ */
8411
+ export function driveServerMode(
8412
+ grid: Grid,
8413
+ trigger: Element,
8414
+ opts?: { columns?: { field: string }[] },
8415
+ ): () => void;
8416
+ /**
8417
+ * Load rows in chunks as the user nears the end of what is loaded.
8418
+ * `sentinelEl` is the element carrying `hx-get` and
8419
+ * `hx-trigger="revealed, lattice:scroll-near-end"`; `threshold` is how many
8420
+ * rows from the end counts as near (default 20). Returns the function that
8421
+ * detaches everything this attached.
8422
+ */
8423
+ export function driveInfiniteScroll(
8424
+ grid: Grid,
8425
+ sentinelEl: Element,
8426
+ opts?: { columns?: { field: string }[]; threshold?: number },
8427
+ ): () => void;
7974
8428
  export function driveOobUpdates(grid: Grid, opts?: object): () => void;
7975
8429
  export function serialiseState(grid: Grid): string;
7976
8430
  export function restoreState(grid: Grid, state: string): void;
@@ -8034,7 +8488,12 @@ declare module 'lattice-grid/modules/devtools' {
8034
8488
  destroy(): void;
8035
8489
  };
8036
8490
  export function expose(grid: Grid, name?: string): void;
8037
- export const CONSOLE_ACTIVATION: string;
8491
+ /**
8492
+ * Whether the console entry point is compiled in. A build that replaces the
8493
+ * activation token with `false` removes the global entirely; in every other
8494
+ * build this is `true`.
8495
+ */
8496
+ export const CONSOLE_ACTIVATION: boolean;
8038
8497
  export default createDevtools;
8039
8498
  }
8040
8499
 
@@ -8408,8 +8867,12 @@ declare module 'lattice-grid/modules/kanban' {
8408
8867
  addCard?: boolean;
8409
8868
  /** Persist a standalone inline edit; return false or a rejected promise to revert. */
8410
8869
  onCardEdit?: (event: { card: KanbanCard; key: unknown; field: string; fieldPath: string; value: unknown }) => boolean | void | Promise<boolean | void>;
8411
- /** Create a card for a column on add-card; return the row to create (with its key), or nothing to auto-generate. */
8412
- onAddCard?: (columnId: string) => KanbanRow | void;
8870
+ /**
8871
+ * Create a card for a column on add-card; return the row to create (with
8872
+ * its key), a Promise of that row, or nothing to auto-generate. A rejected
8873
+ * Promise creates no card and leaves the board unchanged (BACKLOG-0001230).
8874
+ */
8875
+ onAddCard?: (columnId: string) => KanbanRow | Promise<KanbanRow> | void;
8413
8876
  /** A predicate filter over cards; only matching cards are shown. */
8414
8877
  filter?: (row: KanbanRow, card: KanbanCard) => boolean;
8415
8878
  /** Quick-filter text matched case-insensitively across card fields. */
@@ -8492,6 +8955,26 @@ declare module 'lattice-grid/modules/kanban' {
8492
8955
  readonly count: number;
8493
8956
  }
8494
8957
 
8958
+ /**
8959
+ * Named card predicates, composed with AND (BACKLOG-0001229), following the
8960
+ * grid's `filters.where` convention (BACKLOG-0001202). Several may be
8961
+ * registered under different names at once; each can be replaced or removed
8962
+ * without touching the others. `setFilter(fn)` is unchanged sugar for
8963
+ * `where(DEFAULT, fn)` / `where(DEFAULT, null)`.
8964
+ */
8965
+ interface KanbanFilters {
8966
+ /** The reserved name `board.setFilter` registers/removes under. */
8967
+ readonly DEFAULT: string;
8968
+ /** The registered names, in registration order. */
8969
+ where(): string[];
8970
+ /** Register or replace the predicate under `name`. */
8971
+ where(name: string, predicate: (row: KanbanRow, card: KanbanCard) => boolean): Kanban;
8972
+ /** Remove whatever is registered under `name`; a no-op if nothing was. */
8973
+ where(name: string, predicate: null): Kanban;
8974
+ /** Re-run every named predicate (or one, by name) and re-render. */
8975
+ reapply(name?: string): boolean;
8976
+ }
8977
+
8495
8978
  /**
8496
8979
  * A board instance: a kanban view of grid rows as cards grouped into columns.
8497
8980
  * It consumes data through the same keyed-diff `rows.apply` contract a grid
@@ -8540,9 +9023,11 @@ declare module 'lattice-grid/modules/kanban' {
8540
9023
  reorderLanes(order: string[]): Kanban;
8541
9024
  /** Move one swimlane before another (or to the end); emits `swimlane:reorder`. */
8542
9025
  moveLane(id: string, beforeId: string | null): Kanban;
8543
- /** Set a predicate filter over cards, or clear it with null. */
9026
+ /** Named card predicates, composed with AND (BACKLOG-0001229). See {@link KanbanFilters}. */
9027
+ filters: KanbanFilters;
9028
+ /** Set a predicate filter over cards, or clear it with null. Sugar for `filters.where(filters.DEFAULT, fn)`. */
8544
9029
  setFilter(fn: ((row: KanbanRow, card: KanbanCard) => boolean) | null): Kanban;
8545
- /** Set the quick-filter text matched across card fields. */
9030
+ /** Set the quick-filter text matched across card fields. Independent of every `filters.where` predicate. */
8546
9031
  setQuickFilter(text: string): Kanban;
8547
9032
  /** Distinct values of a property with card counts — the raw material for a facet control. */
8548
9033
  facets(property: string): { value: unknown; count: number }[];
@@ -8576,8 +9061,13 @@ declare module 'lattice-grid/modules/kanban' {
8576
9061
  editCard(key: unknown, name?: string): object | null;
8577
9062
  /** Commit an inline edit through the write-back path (grid.edit.setCells when bound); emits `card:edit`. */
8578
9063
  applyEdit(key: unknown, name: string, value: unknown): Promise<boolean>;
8579
- /** Add a card to a column and open it in inline edit; emits `card:add`. */
8580
- addCard(columnId: string, seed?: KanbanRow): unknown;
9064
+ /**
9065
+ * Add a card to a column and open it in inline edit; emits `card:add`.
9066
+ * Returns the new key directly, or a Promise of it when `onAddCard`
9067
+ * returns a Promise or a `beforeAdd` handler defers (BACKLOG-0001230); a
9068
+ * rejected `onAddCard` Promise resolves this to `null` with no card added.
9069
+ */
9070
+ addCard(columnId: string, seed?: KanbanRow): unknown | Promise<unknown>;
8581
9071
  /** Serialise the restorable state: collapsed columns/lanes, order, filter, sprint/epic, selection. */
8582
9072
  getState(): object;
8583
9073
  /** Restore a state snapshot from {@link Kanban#getState}. */
@@ -8587,6 +9077,15 @@ declare module 'lattice-grid/modules/kanban' {
8587
9077
  /** Set (or clear with null) an error state, rendered as a host-supplied message. */
8588
9078
  setError(message: string | null): Kanban;
8589
9079
  setRows(rows: KanbanRow[]): Kanban;
9080
+ /**
9081
+ * Replace the board's configured column set (BACKLOG-0001228). Keeps card
9082
+ * placement and interaction state (collapsed columns, column order, quick
9083
+ * filter, selection) for every column id that survives; a dropped id is
9084
+ * not specially handled — a card whose value has nowhere configured to go
9085
+ * re-derives an ad hoc column rather than becoming `unplaced` (the same
9086
+ * "never silently drop a card" rule an unconfigured value already gets).
9087
+ */
9088
+ setColumns(defs: KanbanColumnDef[]): Kanban;
8590
9089
  refresh(): Kanban;
8591
9090
  destroy(): void;
8592
9091
  }