@toclocoinc/lattice-grid 1.58.0 → 1.60.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 (113) hide show
  1. package/README.md +3 -3
  2. package/docs/API.html +1773 -73
  3. package/docs/api-detail.html +321 -5
  4. package/lattice-grid.d.ts +281 -2842
  5. package/lattice-grid.esm.min.js +879 -148
  6. package/lattice-grid.min.cjs +879 -148
  7. package/lattice-grid.min.css +1 -1
  8. package/lattice-grid.min.js +879 -148
  9. package/modules/ai.d.ts +401 -0
  10. package/modules/ai.esm.min.js +25 -6
  11. package/modules/ai.min.cjs +25 -6
  12. package/modules/ai.min.js +25 -6
  13. package/modules/angular.d.ts +31 -0
  14. package/modules/angular.esm.min.js +3 -2
  15. package/modules/angular.min.cjs +3 -2
  16. package/modules/angular.min.js +3 -2
  17. package/modules/chart-alluvial.d.ts +18 -0
  18. package/modules/chart-alluvial.esm.min.js +1 -1
  19. package/modules/chart-arc.d.ts +18 -0
  20. package/modules/chart-arc.esm.min.js +1 -1
  21. package/modules/chart-bubblemap.d.ts +18 -0
  22. package/modules/chart-bubblemap.esm.min.js +1 -1
  23. package/modules/chart-bump.d.ts +12 -0
  24. package/modules/chart-bump.esm.min.js +1 -1
  25. package/modules/chart-calendar.d.ts +12 -0
  26. package/modules/chart-calendar.esm.min.js +1 -1
  27. package/modules/chart-decomposition.d.ts +20 -0
  28. package/modules/chart-decomposition.esm.min.js +1 -1
  29. package/modules/chart-diverging.d.ts +12 -0
  30. package/modules/chart-diverging.esm.min.js +1 -1
  31. package/modules/chart-dumbbell.d.ts +18 -0
  32. package/modules/chart-dumbbell.esm.min.js +1 -1
  33. package/modules/chart-fan.d.ts +18 -0
  34. package/modules/chart-fan.esm.min.js +1 -1
  35. package/modules/chart-hexbin.d.ts +18 -0
  36. package/modules/chart-hexbin.esm.min.js +1 -1
  37. package/modules/chart-hexmap.d.ts +18 -0
  38. package/modules/chart-hexmap.esm.min.js +1 -1
  39. package/modules/chart-icicle.d.ts +12 -0
  40. package/modules/chart-icicle.esm.min.js +1 -1
  41. package/modules/chart-parallel.d.ts +19 -0
  42. package/modules/chart-parallel.esm.min.js +1 -1
  43. package/modules/chart-ridgeline.d.ts +14 -0
  44. package/modules/chart-ridgeline.esm.min.js +1 -1
  45. package/modules/chart-roc.d.ts +20 -0
  46. package/modules/chart-roc.esm.min.js +1 -1
  47. package/modules/chart-slope.d.ts +12 -0
  48. package/modules/chart-slope.esm.min.js +1 -1
  49. package/modules/chart-splom.d.ts +19 -0
  50. package/modules/chart-splom.esm.min.js +1 -1
  51. package/modules/chart-waffle.d.ts +12 -0
  52. package/modules/chart-waffle.esm.min.js +1 -1
  53. package/modules/charts.d.ts +122 -0
  54. package/modules/charts.esm.min.js +4 -4
  55. package/modules/charts.min.cjs +4 -4
  56. package/modules/charts.min.js +4 -4
  57. package/modules/data-router.d.ts +91 -0
  58. package/modules/data-router.esm.min.js +109 -17
  59. package/modules/data-router.min.cjs +109 -17
  60. package/modules/data-router.min.js +109 -17
  61. package/modules/devtools.d.ts +28 -0
  62. package/modules/devtools.esm.min.js +2 -2
  63. package/modules/devtools.min.cjs +2 -2
  64. package/modules/devtools.min.js +2 -2
  65. package/modules/dhtmlx-compat.d.ts +19 -0
  66. package/modules/dhtmlx-compat.esm.min.js +4 -4
  67. package/modules/dhtmlx-compat.min.cjs +4 -4
  68. package/modules/dhtmlx-compat.min.js +4 -4
  69. package/modules/gantt.d.ts +515 -0
  70. package/modules/gantt.esm.min.js +109 -33
  71. package/modules/gantt.min.cjs +109 -33
  72. package/modules/gantt.min.js +109 -33
  73. package/modules/htmx.d.ts +176 -0
  74. package/modules/htmx.esm.min.js +879 -148
  75. package/modules/htmx.min.cjs +879 -148
  76. package/modules/htmx.min.js +879 -148
  77. package/modules/kanban.d.ts +492 -0
  78. package/modules/kanban.esm.min.js +4 -4
  79. package/modules/kanban.min.cjs +4 -4
  80. package/modules/kanban.min.js +4 -4
  81. package/modules/kpi.d.ts +255 -0
  82. package/modules/kpi.esm.min.js +40 -7
  83. package/modules/kpi.min.cjs +40 -7
  84. package/modules/kpi.min.js +40 -7
  85. package/modules/layout.d.ts +332 -0
  86. package/modules/layout.esm.min.js +59 -6
  87. package/modules/layout.min.cjs +59 -6
  88. package/modules/layout.min.js +59 -6
  89. package/modules/mock-socket.d.ts +114 -0
  90. package/modules/mock-socket.esm.min.js +2 -2
  91. package/modules/mock-socket.min.cjs +2 -2
  92. package/modules/mock-socket.min.js +2 -2
  93. package/modules/react.d.ts +25 -0
  94. package/modules/react.esm.min.js +3 -2
  95. package/modules/react.min.cjs +3 -2
  96. package/modules/react.min.js +3 -2
  97. package/modules/svelte.d.ts +26 -0
  98. package/modules/svelte.esm.min.js +3 -2
  99. package/modules/svelte.min.cjs +3 -2
  100. package/modules/svelte.min.js +3 -2
  101. package/modules/tabs.d.ts +133 -0
  102. package/modules/tabs.esm.min.js +411 -9
  103. package/modules/tabs.min.cjs +411 -9
  104. package/modules/tabs.min.js +411 -9
  105. package/modules/vue.d.ts +24 -0
  106. package/modules/vue.esm.min.js +3 -2
  107. package/modules/vue.min.cjs +3 -2
  108. package/modules/vue.min.js +3 -2
  109. package/modules/webcomponent.d.ts +47 -0
  110. package/modules/webcomponent.esm.min.js +879 -148
  111. package/modules/webcomponent.min.cjs +879 -148
  112. package/modules/webcomponent.min.js +879 -148
  113. package/package.json +2 -2
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.58.0, type declarations
2
+ * Lattice Grid 1.60.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -541,6 +541,125 @@ export interface ColumnValueSpec {
541
541
  quickFilterText?: (p: ValueParams) => string;
542
542
  }
543
543
 
544
+ /**
545
+ * One label/value line in a {@link TooltipSpec}.
546
+ *
547
+ * Both halves are written as text by the grid, whatever they contain.
548
+ */
549
+ export interface TooltipRow {
550
+ /** The line's label, drawn on the leading edge. */
551
+ label?: unknown;
552
+ /** The line's value, drawn on the trailing edge. */
553
+ value?: unknown;
554
+ }
555
+
556
+ /**
557
+ * Structured tooltip content the grid renders for you (BACKLOG-0001204): a
558
+ * heading, a list of label/value lines, and a closing note.
559
+ *
560
+ * Every field is written as **text**, never as markup, so a spec built out of
561
+ * row values needs no escaping and cannot become HTML by accident. Return
562
+ * `{ html }` from `render` when markup is genuinely wanted.
563
+ */
564
+ export interface TooltipSpec {
565
+ /** A heading for the tooltip. */
566
+ title?: unknown;
567
+ /** Label/value lines, in order. */
568
+ rows?: TooltipRow[];
569
+ /** A closing note under the lines, drawn quieter than them. */
570
+ note?: unknown;
571
+ }
572
+
573
+ /**
574
+ * What a tooltip's `render` and `mount` are given: the same identification
575
+ * `cell:clicked` carries, plus the cell element itself and the grid.
576
+ *
577
+ * Resolved from the DOM at the moment the tooltip opens rather than when the
578
+ * pointer arrived, so a pooled row re-used in between names the row it is
579
+ * showing now.
580
+ */
581
+ export interface TooltipParams {
582
+ /** The row under the pointer or the keyboard cursor. */
583
+ row: Row;
584
+ /** That row's key. */
585
+ key: string;
586
+ /** Its display index. */
587
+ index: number;
588
+ /** The column the cell belongs to. */
589
+ colId: string;
590
+ /** The resolved column. */
591
+ column: Column;
592
+ /** The cell's value. */
593
+ value: unknown;
594
+ /** The cell's formatted text. */
595
+ text: string;
596
+ /** The cell element the tooltip is anchored to. */
597
+ cell: HTMLElement;
598
+ /** The grid. */
599
+ grid: Grid;
600
+ }
601
+
602
+ /**
603
+ * A rich, keyboard-accessible tooltip for a column's cells (BACKLOG-0001204) —
604
+ * the object form of `cell.tooltip`, drawn by the grid rather than handed to
605
+ * the browser as a native `title`.
606
+ *
607
+ * Shown after a delay (`tooltip.delay`, 400ms by default) on hover *and* on
608
+ * keyboard focus; the cell points at it with `aria-describedby`; it can be
609
+ * hovered without closing and Escape dismisses it (WCAG 2.2 AA, 1.4.13). It
610
+ * closes on scroll, because rows are pooled and a bubble left open would be
611
+ * anchored to a node that is now showing a different row.
612
+ */
613
+ export interface ColumnTooltipSpec {
614
+ /**
615
+ * Produce the content. Four shapes, and the difference between the last two
616
+ * is a security property rather than a style choice:
617
+ *
618
+ * - an **element** — your own DOM, attached as it is;
619
+ * - a **{@link TooltipSpec}** — `{ title, rows, note }`, rendered as text;
620
+ * - **`{ html }`** — the only wrapper that inserts markup, scrubbed of script
621
+ * the same way `allowUnsafeTemplates` output is;
622
+ * - a **string** — *always* text, never markup.
623
+ *
624
+ * The last rule is what makes `render: (p) => p.value` safe: a value comes
625
+ * from row data, and data must not be able to promote itself to HTML.
626
+ */
627
+ render?: (params: TooltipParams) => HTMLElement | TooltipSpec | { html: string } | string | null | undefined;
628
+ /**
629
+ * Put live content in the tooltip — a sparkline, a KPI tile — by calling into
630
+ * a module bundle your application loaded. The grid core never imports a
631
+ * module, so anything live is mounted here by you.
632
+ */
633
+ mount?: (el: HTMLElement, params: TooltipParams) => void;
634
+ /**
635
+ * Tear down whatever `mount` built. Called every time the tooltip closes, so
636
+ * nothing keeps running behind a hidden box.
637
+ */
638
+ unmount?: (el: HTMLElement) => void;
639
+ }
640
+
641
+ /**
642
+ * Grid-level defaults for the rich cell tooltip (BACKLOG-0001204), set once for
643
+ * every column rather than repeated on each.
644
+ *
645
+ * Defaults only: it switches nothing on. A tooltip exists because a column
646
+ * declares `cell.tooltip`, and a grid whose columns declare none has no
647
+ * tooltips whatever is set here.
648
+ */
649
+ export interface TooltipConfig {
650
+ /**
651
+ * How long the pointer or the keyboard cursor must rest on a cell before the
652
+ * tooltip is built, in milliseconds. 400 by default.
653
+ *
654
+ * The delay is why a pointer sweeping across the grid mounts nothing: a
655
+ * tooltip that built a chart on every cell it crossed would be unusable, and
656
+ * `0` asks for exactly that.
657
+ */
658
+ delay?: number;
659
+ /** How wide the tooltip may grow. A number is pixels; a string is used as written. */
660
+ maxWidth?: number | string;
661
+ }
662
+
544
663
  export interface ColumnCellSpec {
545
664
  decoration?: DecorationName | DecorationSpec;
546
665
  variant?: VariantSpec;
@@ -551,7 +670,15 @@ export interface ColumnCellSpec {
551
670
  class?: string | string[] | ((p: CellParams) => string | string[]);
552
671
  classWhen?: Record<string, string | ((p: CellParams) => boolean)>;
553
672
  style?: CellStyle | ((p: CellParams) => CellStyle);
554
- tooltip?: string | ((p: CellParams) => string);
673
+ /**
674
+ * A tooltip for this column's cells.
675
+ *
676
+ * A string or a function is the plain-text case and becomes the browser's own
677
+ * `title`. An object is a {@link ColumnTooltipSpec}: a tooltip the grid draws,
678
+ * which can carry structure, markup or live content and which a keyboard user
679
+ * can reach (BACKLOG-0001204).
680
+ */
681
+ tooltip?: string | ((p: CellParams) => string) | ColumnTooltipSpec;
555
682
  align?: Align;
556
683
  /**
557
684
  * Vertical alignment of this column's cell content, overriding the grid-level
@@ -635,6 +762,31 @@ export interface ColumnLayoutSpec {
635
762
  * the grid only when nothing else is fixed.
636
763
  */
637
764
  width?: number | string;
765
+ /**
766
+ * `'content'` sizes the column to what it is actually showing, the way
767
+ * `columns.autoSize()` does, and keeps doing it: on the first paint, and
768
+ * again whenever the rows change, the columns are shown, hidden, reordered
769
+ * or pinned, or the grid is resized. It is the declarative form of the
770
+ * imperative call, so a host no longer has to re-issue `autoSize()` after
771
+ * every data change.
772
+ *
773
+ * Sized to the *visible* content, not to the widest value in the dataset:
774
+ * the measurement reads the rows the renderer has mounted, because measuring
775
+ * a million rows is not a plan. It measures the heading too, so a column
776
+ * whose title is longer than its values widens to show the title.
777
+ *
778
+ * **Anything the caller states outranks it.** A declared `width` wins, and
779
+ * so does a width the user drags to — a resize is recorded as a `width`, so
780
+ * from that moment the column is that wide and the fit no longer touches it.
781
+ * `min` and `max` clamp the fitted width as they clamp any other. `flex` is
782
+ * resolved before this and wins, the two being contradictory instructions:
783
+ * `flex` fits the column to the *grid*, this fits it to the *content*.
784
+ *
785
+ * Not re-measured on scroll, deliberately: different rows mount as the grid
786
+ * scrolls, and re-fitting against them would make the columns jitter under
787
+ * the reader.
788
+ */
789
+ fit?: 'content';
638
790
  min?: number;
639
791
  max?: number;
640
792
  flex?: number;
@@ -928,6 +1080,16 @@ export interface Column {
928
1080
  * the way `align` is. Omitted, the column follows the grid default.
929
1081
  */
930
1082
  verticalAlign?: VAlign;
1083
+ /**
1084
+ * When this leaf column is shown, the same union `ColumnGroup` declares
1085
+ * (BACKLOG-0001279). A leaf reads its own `showWhen` exactly as a group
1086
+ * reads its own — `open`/`closed` tie the leaf to an ancestor group's
1087
+ * collapsed state, `always` (the default) shows it regardless — so tying a
1088
+ * leaf's visibility to a group's open/closed state does not require
1089
+ * wrapping it in a `ColumnGroup` of its own just to hold this setting; a
1090
+ * wrapper is for grouping columns, not for this.
1091
+ */
1092
+ showWhen?: 'open' | 'closed' | 'always';
931
1093
  /** How the column leaves the grid, where that differs from how it is shown. */
932
1094
  export?: ColumnExportSpec;
933
1095
  /** Whether the user may group by this column from the interface. */
@@ -1177,6 +1339,38 @@ export interface RemoteRequest {
1177
1339
  sort: SortEntry[];
1178
1340
  context: unknown;
1179
1341
  signal: AbortSignal;
1342
+ /**
1343
+ * The `where` predicates in force, as a runtime the source can evaluate but
1344
+ * not mutate (BACKLOG-0001268). Present **only when at least one predicate is
1345
+ * registered**, so a grid that does not use `where` sends the request it
1346
+ * always sent, field for field.
1347
+ *
1348
+ * A host `fetch` may ignore it, and every existing one does: it is a host
1349
+ * function, so there is nothing to serialise and no engine can evaluate it —
1350
+ * `passes` is dropped by `JSON.stringify` the way `signal` already is. It is
1351
+ * carried for the one reader that can act on it, `createPushdownSource`,
1352
+ * which runs it as the residual over the matching set when that set is under
1353
+ * `whereRowLimit`. The `{ condition }` twin remains the route that narrows
1354
+ * the fetch itself, at any size.
1355
+ */
1356
+ where?: WhereRuntime;
1357
+ }
1358
+
1359
+ /**
1360
+ * The `where` predicates in force, as a source sees them (BACKLOG-0001268).
1361
+ *
1362
+ * A snapshot rather than the model, so a source can evaluate the predicates but
1363
+ * cannot register or remove one through it.
1364
+ */
1365
+ export interface WhereRuntime {
1366
+ /** Whether any predicate is registered at all. */
1367
+ active: boolean;
1368
+ /** The registered names, in registration order — for diagnostics. */
1369
+ names: string[];
1370
+ /** Bumped on every registration or removal, so a cache key can track it. */
1371
+ version: number;
1372
+ /** Does this row survive every registered predicate? */
1373
+ passes(row: unknown, key?: string): boolean;
1180
1374
  }
1181
1375
 
1182
1376
  export interface RemoteResult {
@@ -1874,6 +2068,16 @@ export interface GridConfig {
1874
2068
  */
1875
2069
  verticalAlign?: VAlign;
1876
2070
 
2071
+ /**
2072
+ * Defaults for the rich cell tooltip (BACKLOG-0001204).
2073
+ *
2074
+ * The tooltip itself is declared per column, on `cell.tooltip`; this only
2075
+ * carries the settings that are a house style rather than a per-column
2076
+ * decision. It switches nothing on: a column with no `cell.tooltip` has no
2077
+ * tooltip whatever is set here.
2078
+ */
2079
+ tooltip?: TooltipConfig;
2080
+
1877
2081
  /**
1878
2082
  * Keep the scroll viewport's scrollbars visible (BACKLOG-0000990).
1879
2083
  *
@@ -2330,7 +2534,7 @@ export interface GridConfig {
2330
2534
  * markup `data-lat-group-toggle` and a click on it expands or collapses the
2331
2535
  * group, or call `params.toggle()` from a node you built yourself.
2332
2536
  */
2333
- groupRenderer?(params: GroupRowParams): string | Node | void;
2537
+ groupRenderer?: (params: GroupRowParams) => string | Node | void;
2334
2538
  /**
2335
2539
  * Which groups start expanded, before anyone has opened or closed one.
2336
2540
  *
@@ -2376,13 +2580,13 @@ export interface GridConfig {
2376
2580
  * should *not* be part of the data, use `pinnedTopRows`.
2377
2581
  */
2378
2582
  fullWidth?: {
2379
- when(row: Row): boolean;
2583
+ when: (row: Row) => boolean;
2380
2584
  /**
2381
2585
  * Return a string for text, or a node for content. Return nothing and
2382
2586
  * write into `params.element` yourself. An HTML string is deliberately not
2383
2587
  * accepted: see `allowUnsafeTemplates` for that decision elsewhere.
2384
2588
  */
2385
- render(params: FullWidthParams): string | Node | void;
2589
+ render: (params: FullWidthParams) => string | Node | void;
2386
2590
  };
2387
2591
  /** Total what the filters left rather than the whole set. */
2388
2592
  totalFilteredOnly?: boolean;
@@ -2574,10 +2778,12 @@ export interface GridConfig {
2574
2778
  exportName?: string;
2575
2779
  /**
2576
2780
  * Put the native annotation tools — pen, arrow, rectangle, highlighter — on
2577
- * the rail. Off by default; each is a real toggle button that shows pressed
2578
- * while it is the tool in use and turns off when pressed again. The tools
2579
- * also appear automatically for the duration of a presentation, so this is
2580
- * only needed to keep them available outside one.
2781
+ * the rail. Each is a real toggle button that shows pressed while it is the
2782
+ * tool in use and turns off when pressed again. Three states, not two:
2783
+ * `true` opts in and keeps the tools on the rail always, presentation or
2784
+ * not; `false` opts OUT and the tools are never added, not even for a
2785
+ * presentation; omitted keeps the default, where the tools are off until a
2786
+ * presentation starts, appear for its duration, and leave when it ends.
2581
2787
  */
2582
2788
  annotate?: boolean;
2583
2789
  };
@@ -2648,7 +2854,7 @@ export interface GridConfig {
2648
2854
  * host's, and owns the model, the key and the privacy decision.
2649
2855
  */
2650
2856
  ai?: {
2651
- ask(p: {
2857
+ ask: (p: {
2652
2858
  /** The full text to send: the schema description and the question together. */
2653
2859
  prompt: string;
2654
2860
  /** The grid's schema as data: columns, types and operators. No row values. */
@@ -2658,7 +2864,7 @@ export interface GridConfig {
2658
2864
  /** What the user typed. */
2659
2865
  message: string;
2660
2866
  context?: unknown;
2661
- }): Promise<unknown>;
2867
+ }) => Promise<unknown>;
2662
2868
  schemaOptions?: object;
2663
2869
  context?: unknown;
2664
2870
  element?: HTMLElement;
@@ -3011,11 +3217,31 @@ export interface PushdownAdapter {
3011
3217
  export interface PushdownPlan {
3012
3218
  /** The query the adapter was given. */
3013
3219
  pushed: RemoteRequest;
3014
- /** What the grid applied afterwards. */
3015
- residual: { filters: object | null; sort: SortEntry[] | null; quick: string };
3220
+ /**
3221
+ * What the grid applied afterwards. `where` is the host predicate runtime
3222
+ * when one survived the `whereRowLimit` gate, and `null` when none was
3223
+ * registered or the gate refused it (BACKLOG-0001268).
3224
+ */
3225
+ residual: {
3226
+ filters: object | null;
3227
+ sort: SortEntry[] | null;
3228
+ quick: string;
3229
+ where: WhereRuntime | null;
3230
+ /**
3231
+ * Whether the rows the residual runs over are the whole matching set rather
3232
+ * than a fetched fraction (BACKLOG-0001268). Set by the source when it hands
3233
+ * the residual to `applyResidual`; absent on the plan `lastPlan()` reports,
3234
+ * because it is a property of one fetch's result, not of the plan.
3235
+ *
3236
+ * When true the counts the residual produces are whole-dataset counts, so
3237
+ * the page-relative `where` warning is suppressed. Absent counts as not
3238
+ * whole: silence has to be earned.
3239
+ */
3240
+ whole?: boolean;
3241
+ };
3016
3242
  /** Whether the whole result had to be fetched rather than a window. */
3017
3243
  needsAll: boolean;
3018
- /** Which parts could not be pushed: `filter`, `sort`, `quick`. */
3244
+ /** Which parts could not be pushed: `filter`, `sort`, `quick`, `where`. */
3019
3245
  unpushed: string[];
3020
3246
  /**
3021
3247
  * Whether the whole result was fetched because `fullDataset` is on, rather
@@ -3146,6 +3372,26 @@ export interface PushdownSourceConfig {
3146
3372
  * no-residual short-return warning.
3147
3373
  */
3148
3374
  allowPartialResults?: boolean;
3375
+ /**
3376
+ * The most rows the source will fetch and hold in order to run a twinless
3377
+ * `where` predicate as the residual (BACKLOG-0001268). Defaults to `50_000`,
3378
+ * the same anchor as the grid's `workerThreshold` — the size at which this
3379
+ * codebase already judges a dataset big enough to need different handling.
3380
+ *
3381
+ * A `where` predicate is a host function no engine can evaluate, so the only
3382
+ * way to honour one is to fetch every matching row and filter here. That
3383
+ * silently turns a windowed grid into a whole-dataset download, which is the
3384
+ * thing a pushdown source exists to avoid. So it is a gate, not a free
3385
+ * upgrade: at or past this many matching rows the predicate is **refused and
3386
+ * warned about** — the rows it would exclude stay on screen — rather than the
3387
+ * download being taken on the host's behalf. An adapter that reports no row
3388
+ * total counts as over the limit, because guessing the other way is guessing
3389
+ * your way into the download.
3390
+ *
3391
+ * Raise it when you want that download; the `{ condition }` twin is the route
3392
+ * that narrows the fetch itself and works at any size.
3393
+ */
3394
+ whereRowLimit?: number;
3149
3395
  }
3150
3396
 
3151
3397
  export interface StatisticsApi {
@@ -4227,6 +4473,20 @@ export type EventName =
4227
4473
  /* Cells and editing */
4228
4474
  | 'cell:changed' | 'cell:pending' | 'cell:confirmed' | 'cell:reverted' | 'cell:conflict'
4229
4475
  | 'cell:clicked' | 'cell:dblclicked' | 'cell:contextmenu'
4476
+ /* The pointer entering and leaving a cell (BACKLOG-0001203). Announcements
4477
+ * only, carrying what `cell:clicked` carries plus the cell element as
4478
+ * `target`. A host cannot wire these itself: rows and cells are pooled and
4479
+ * re-used as the grid scrolls, so a listener bound to a cell node fires for
4480
+ * whichever row occupies it next. Nothing in the grid is gated on hover, so
4481
+ * a keyboard user reaches everything a pointer does. */
4482
+ | 'cell:mouseover' | 'cell:mouseout'
4483
+ /* A pointer press and release on a cell (BACKLOG-0001272), the same
4484
+ * convention as the hover pair above: announcements only, carrying what
4485
+ * `cell:clicked` carries plus the cell element as `target`. A host cannot
4486
+ * wire these itself for the same reason it cannot wire the hover pair —
4487
+ * rows and cells are pooled and re-used as the grid scrolls, so a listener
4488
+ * bound to a cell node fires for whichever row occupies it next. */
4489
+ | 'cell:mousedown' | 'cell:mouseup'
4230
4490
  | 'cell:edit:start' | 'cell:edit:end' | 'row:edit:start' | 'row:edit:end'
4231
4491
  | 'row:clicked' | 'row:dblclicked'
4232
4492
  | 'row:pending' | 'row:confirmed' | 'row:reverted' | 'row:conflict'
@@ -4848,6 +5108,13 @@ export interface WhereOptions {
4848
5108
  * of filtering a page client-side. It must be implied by the predicate: the
4849
5109
  * grid ANDs both, so a twin wider than the function costs only time, while one
4850
5110
  * narrower than it hides rows the function would have kept.
5111
+ *
5112
+ * **The twin is what works at any size.** Without one, a pushdown source can
5113
+ * still run the function — but only as the residual over the whole matching
5114
+ * set, so it does so only while that set is under `whereRowLimit` (default
5115
+ * `50_000`) and refuses loudly past it (BACKLOG-0001268). A paged or remote
5116
+ * source cannot run it at all and warns at registration. The twin is pushed
5117
+ * to the engine, so it narrows the fetch itself and none of that applies.
4851
5118
  */
4852
5119
  condition?: FilterSet;
4853
5120
  }
@@ -5869,8 +6136,6 @@ export interface DiffApi {
5869
6136
  report(): Record<string, unknown>;
5870
6137
  }
5871
6138
 
5872
- export type PermissionLevel = 'hidden' | 'read' | 'write' | 'writeOnly';
5873
-
5874
6139
  export interface PermissionsApi {
5875
6140
  levelOf(column: string | ResolvedColumn): PermissionLevel;
5876
6141
  isHidden(column: string | ResolvedColumn): boolean;
@@ -7432,2829 +7697,3 @@ export interface Chart {
7432
7697
  toCSV(): string;
7433
7698
  destroy(): void;
7434
7699
  }
7435
-
7436
- declare module 'lattice-grid/modules/charts' {
7437
- /** Every type name `createChart` accepts. */
7438
- export const TYPES: readonly ChartType[];
7439
- /** The built-in colour schemes, by name. */
7440
- export const SCHEMES: Readonly<Record<string, readonly string[]>>;
7441
- export const PALETTE: readonly string[];
7442
- export function createChart(spec: ChartSpec): Chart;
7443
- /**
7444
- * Chart a selected cell range. Derives the chart from the range's shape — a
7445
- * leading text column becomes the categories, the numeric columns become the
7446
- * measures — and returns the live chart, or null when the range has nothing
7447
- * to measure. Respects hidden and unreadable columns. The type is a sensible
7448
- * default the caller can change with `chart.update({ type })`.
7449
- */
7450
- export function chartRange(
7451
- grid: Grid,
7452
- opts: {
7453
- container: Element | string;
7454
- range?: CellRange;
7455
- type?: ChartType;
7456
- } & Partial<ChartSpec>,
7457
- ): Chart | null;
7458
- /** Would {@link chartRange} draw something for the grid's current selection? */
7459
- export function canChartRange(grid: Grid, opts?: { range?: CellRange }): boolean;
7460
- /**
7461
- * Decide what a chart of a range should be, without drawing it: the type, the
7462
- * category column, the measure columns, and a `spec` ready for `createChart`
7463
- * — or a `reason` naming why the range cannot be charted.
7464
- */
7465
- export function deriveRangeSpec(
7466
- grid: Grid,
7467
- opts?: { range?: CellRange; type?: ChartType },
7468
- ): {
7469
- spec: ChartSpec | null;
7470
- type: ChartType | null;
7471
- x: string | null;
7472
- measures: string[];
7473
- columns: string[];
7474
- reason: string | null;
7475
- };
7476
- /**
7477
- * Turn a fitted regression model into diagnostic chart specs ready for
7478
- * `createChart` (BACKLOG-0000812). Pass a precomputed `model`, or a `spec` to
7479
- * fit one over the grid, and the `fitted` and `residual` fit-shadow column ids
7480
- * the residual and QQ plots draw over.
7481
- *
7482
- * The presets that map onto grid columns come back as drawable specs: `fit`
7483
- * (the fit line with its confidence band), `residualsFitted`, `qq`, and
7484
- * `multicollinearity` (a correlogram over the predictors, with the model's
7485
- * `vif` alongside). The three that need a per-row or per-coefficient quantity
7486
- * the grid has no column for — `scaleLocation`, `residualsLeverage`,
7487
- * `coefficientForest` — come back with a null `spec` and a stable `reason`,
7488
- * rather than silently dropped.
7489
- */
7490
- export function regressionPlots(
7491
- grid: Grid,
7492
- opts?: {
7493
- model?: RegressionModel;
7494
- spec?: RegressionSpec;
7495
- fitted?: string;
7496
- residual?: string;
7497
- rows?: object[] | ((grid: Grid) => object[]);
7498
- confidence?: number;
7499
- },
7500
- ): {
7501
- model: RegressionModel | null;
7502
- plots: Record<
7503
- 'fit' | 'residualsFitted' | 'qq' | 'multicollinearity'
7504
- | 'scaleLocation' | 'residualsLeverage' | 'coefficientForest',
7505
- {
7506
- spec: ChartSpec | null;
7507
- reason: string | null;
7508
- vif?: number[] | null;
7509
- coefficients?: RegressionCoefficient[] | null;
7510
- }
7511
- >;
7512
- };
7513
- export function registerScheme(name: string, colours: readonly string[]): void;
7514
- export function resolveScheme(spec?: object): object;
7515
- export function schemeNames(): string[];
7516
- export function setDefaultScheme(name: string): void;
7517
- /**
7518
- * The definition an extension chart type registers (BACKLOG-0000886). `draw`
7519
- * receives the base drawing context — `plot`, `bound`, `groups`, `scheme`,
7520
- * `typography`, `fontSize`, `labels`, `grid`, `spec`, `doc` — plus
7521
- * `ctx.helpers`, the base's own toolkit of primitives (element factory, scales,
7522
- * axes, mark pool, distribution kernels), and appends its marks to the layer
7523
- * groups. `bind` optionally supplies the bound data (default: the by-series
7524
- * binder); `freeform` lays the chart out without axis gutters; `labelled`
7525
- * declares that `labels` applies.
7526
- */
7527
- interface ChartTypeDefinition {
7528
- draw: (ctx: object) => object;
7529
- bind?: (grid: Grid, spec: ChartSpec) => object;
7530
- freeform?: boolean;
7531
- labelled?: boolean;
7532
- }
7533
- /**
7534
- * Register an extension chart type so `createChart({ type })` can draw it
7535
- * (BACKLOG-0000886). Extension types ship as their own opt-in modules, so the
7536
- * base charts bundle does not grow for a type a caller never imports — you pay
7537
- * only for the charts you use.
7538
- */
7539
- export function registerChartType(name: string, def: ChartTypeDefinition): void;
7540
- /** Every registered extension chart-type name, in registration order. */
7541
- export function registeredChartTypes(): string[];
7542
- export { Chart };
7543
- }
7544
-
7545
- declare module 'lattice-grid/modules/chart-ridgeline' {
7546
- /**
7547
- * The ridgeline (joy plot) extension chart type (BACKLOG-0000886). Importing
7548
- * this module registers `ridgeline` with the base charts module; the base
7549
- * bundle does not include it unless a caller imports it. Draws one
7550
- * kernel-density ridge per category (`x`), stacked and overlapping, over the
7551
- * distribution of a measure (`y`); `spec.overlap` sets the vertical overlap.
7552
- */
7553
- export function drawRidgeline(ctx: object): object;
7554
- export default drawRidgeline;
7555
- }
7556
-
7557
- declare module 'lattice-grid/modules/chart-calendar' {
7558
- /**
7559
- * The calendar-heatmap extension chart type (BACKLOG-0000886). Importing this
7560
- * module registers `calendar`. Draws value-by-day as a GitHub-style grid: `x`
7561
- * is a date column, `y` the measure summed per day.
7562
- */
7563
- export function drawCalendar(ctx: object): object;
7564
- export default drawCalendar;
7565
- }
7566
-
7567
- declare module 'lattice-grid/modules/chart-splom' {
7568
- /**
7569
- * The scatter-plot-matrix (SPLOM) extension chart type (BACKLOG-0000886).
7570
- * Importing this module registers `splom`. Crosses every pair of the numeric
7571
- * `columns` (2–6) as a matrix of scatters, naming each variable on the
7572
- * diagonal.
7573
- */
7574
- export function drawSplom(ctx: object): object;
7575
- /** The SPLOM binding: reads the numeric `columns` off the grid's visible rows. */
7576
- export function bindSplom(grid: Grid, spec: object): object;
7577
- export default drawSplom;
7578
- }
7579
-
7580
- declare module 'lattice-grid/modules/chart-hexbin' {
7581
- /**
7582
- * The hexbin / 2D-density extension chart type (BACKLOG-0000886). Importing
7583
- * this module registers `hexbin`. Bins `x`/`y` points into hexagons shaded by
7584
- * count, so a large scatter reads as a density field rather than overplotting.
7585
- */
7586
- export function drawHexbin(ctx: object): object;
7587
- /** The hexbin binding: reads the numeric `x` and `y` columns off the grid's rows. */
7588
- export function bindHexbin(grid: Grid, spec: object): object;
7589
- export default drawHexbin;
7590
- }
7591
-
7592
- declare module 'lattice-grid/modules/chart-roc' {
7593
- /**
7594
- * The ROC / PR / calibration extension chart type (BACKLOG-0000886). Importing
7595
- * this module registers `roc`. `spec.curve` chooses `'roc'` (default, with the
7596
- * chance diagonal and AUC), `'pr'`, or `'calibration'`; `label` is the outcome
7597
- * column (positive when truthy or equal to `spec.positive`), `score` the model
7598
- * score.
7599
- */
7600
- export function drawRoc(ctx: object): object;
7601
- /** The ROC binding: reads the outcome and score off the grid's rows. */
7602
- export function bindRoc(grid: Grid, spec: object): object;
7603
- export default drawRoc;
7604
- }
7605
-
7606
- declare module 'lattice-grid/modules/chart-fan' {
7607
- /**
7608
- * The fan / forecast extension chart type (BACKLOG-0000886). Importing this
7609
- * module registers `fan`. Draws `y` (history) as a solid line, `forecast` as a
7610
- * dashed continuation, and the `lower`/`upper` interval as a widening band.
7611
- */
7612
- export function drawFan(ctx: object): object;
7613
- /** The fan binding: reads the history, forecast and interval columns in row order. */
7614
- export function bindFan(grid: Grid, spec: object): object;
7615
- export default drawFan;
7616
- }
7617
-
7618
- declare module 'lattice-grid/modules/chart-decomposition' {
7619
- /**
7620
- * The seasonal-decomposition panel extension chart type (BACKLOG-0000886),
7621
- * companion to the `tsTrend`/`tsSeasonal`/`tsResidual` shadow columns.
7622
- * Importing this module registers `decomposition`. Draws a stacked panel per
7623
- * named component column (`observed`/`trend`/`seasonal`/`residual`) sharing one
7624
- * x axis.
7625
- */
7626
- export function drawDecomposition(ctx: object): object;
7627
- /** The decomposition binding: reads the named component columns in row order. */
7628
- export function bindDecomposition(grid: Grid, spec: object): object;
7629
- export default drawDecomposition;
7630
- }
7631
-
7632
- declare module 'lattice-grid/modules/chart-slope' {
7633
- /**
7634
- * The slope-chart extension type (BACKLOG-0000886). Importing this module
7635
- * registers `slope`. One line per `series` connecting its `y` across the `x`
7636
- * periods — before/after comparison read from the slopes.
7637
- */
7638
- export function drawSlope(ctx: object): object;
7639
- export default drawSlope;
7640
- }
7641
-
7642
- declare module 'lattice-grid/modules/chart-dumbbell' {
7643
- /**
7644
- * The dumbbell / connected-dot extension type (BACKLOG-0000886). Importing
7645
- * this module registers `dumbbell`. Two dots (`start`, `end`) joined by a bar
7646
- * per `x` category — the gap is the bar's length.
7647
- */
7648
- export function drawDumbbell(ctx: object): object;
7649
- /** The dumbbell binding: reads the category and its two numeric columns. */
7650
- export function bindDumbbell(grid: Grid, spec: object): object;
7651
- export default drawDumbbell;
7652
- }
7653
-
7654
- declare module 'lattice-grid/modules/chart-bump' {
7655
- /**
7656
- * The bump-chart extension type (BACKLOG-0000886). Importing this module
7657
- * registers `bump`. One line per `series` plotted by its rank of `y` within
7658
- * each `x` period — rank-over-time, where crossings are the story.
7659
- */
7660
- export function drawBump(ctx: object): object;
7661
- export default drawBump;
7662
- }
7663
-
7664
- declare module 'lattice-grid/modules/chart-diverging' {
7665
- /**
7666
- * The diverging-bar extension type (BACKLOG-0000886). Importing this module
7667
- * registers `diverging`. Horizontal bars growing left/right from a central
7668
- * zero over a signed `y`, on a symmetric scale.
7669
- */
7670
- export function drawDiverging(ctx: object): object;
7671
- export default drawDiverging;
7672
- }
7673
-
7674
- declare module 'lattice-grid/modules/chart-parallel' {
7675
- /**
7676
- * The parallel-coordinates extension type (BACKLOG-0000886). Importing this
7677
- * module registers `parallel`. One polyline per row across the numeric
7678
- * `columns`, each a vertical axis with its own scale; `spec.colourBy` colours
7679
- * by a category.
7680
- */
7681
- export function drawParallel(ctx: object): object;
7682
- /** The parallel-coordinates binding: reads the dimension columns off the rows. */
7683
- export function bindParallel(grid: Grid, spec: object): object;
7684
- export default drawParallel;
7685
- }
7686
-
7687
- declare module 'lattice-grid/modules/chart-icicle' {
7688
- /**
7689
- * The icicle extension type (BACKLOG-0000886). Importing this module registers
7690
- * `icicle`. A hierarchy (the grid's group tree) as nested rectangles in rows,
7691
- * sized by `y`; drills like the built-in hierarchical types.
7692
- */
7693
- export function drawIcicle(ctx: object): object;
7694
- export default drawIcicle;
7695
- }
7696
-
7697
- declare module 'lattice-grid/modules/chart-waffle' {
7698
- /**
7699
- * The waffle / dot-matrix extension type (BACKLOG-0000886). Importing this
7700
- * module registers `waffle`. Proportion as counted squares (default 100), one
7701
- * colour per `x` category sized by `y`.
7702
- */
7703
- export function drawWaffle(ctx: object): object;
7704
- export default drawWaffle;
7705
- }
7706
-
7707
- declare module 'lattice-grid/modules/chart-alluvial' {
7708
- /**
7709
- * The alluvial extension type (BACKLOG-0000886). Importing this module
7710
- * registers `alluvial`. Ribbons from `source` categories to `target`
7711
- * categories sized by `value` — categorical flow between two dimensions.
7712
- */
7713
- export function drawAlluvial(ctx: object): object;
7714
- /** The alluvial binding: aggregates source→target flows off the grid's rows. */
7715
- export function bindAlluvial(grid: Grid, spec: object): object;
7716
- export default drawAlluvial;
7717
- }
7718
-
7719
- declare module 'lattice-grid/modules/chart-arc' {
7720
- /**
7721
- * The arc-diagram extension type (BACKLOG-0000886). Importing this module
7722
- * registers `arc`. Nodes on a baseline with `source`→`target` relationships as
7723
- * semicircular arcs, thickness by `value`.
7724
- */
7725
- export function drawArc(ctx: object): object;
7726
- /** The arc-diagram binding: collects nodes and edges off the grid's rows. */
7727
- export function bindArc(grid: Grid, spec: object): object;
7728
- export default drawArc;
7729
- }
7730
-
7731
- declare module 'lattice-grid/modules/chart-bubblemap' {
7732
- /**
7733
- * The symbol / bubble-map extension type (BACKLOG-0000886). Importing this
7734
- * module registers `bubblemap`. Points placed by `lon`/`lat`, each a bubble
7735
- * with a square-root radius from `size`; needs no outlines and fetches nothing.
7736
- */
7737
- export function drawBubbleMap(ctx: object): object;
7738
- /** The bubble-map binding: reads the coordinate and size columns off the rows. */
7739
- export function bindBubbleMap(grid: Grid, spec: object): object;
7740
- export default drawBubbleMap;
7741
- }
7742
-
7743
- declare module 'lattice-grid/modules/chart-hexmap' {
7744
- /**
7745
- * The hexbin-map extension type (BACKLOG-0000886). Importing this module
7746
- * registers `hexmap`. `lon`/`lat` points binned into hexagons shaded by count,
7747
- * so a geographic density reads without overplotting or outlines.
7748
- */
7749
- export function drawHexMap(ctx: object): object;
7750
- /** The hexbin-map binding: reads the coordinate columns off the grid's rows. */
7751
- export function bindHexMap(grid: Grid, spec: object): object;
7752
- export default drawHexMap;
7753
- }
7754
-
7755
- declare module 'lattice-grid/modules/react' {
7756
- /**
7757
- * Build the React component.
7758
- *
7759
- * A factory rather than a component, because the adapter imports neither
7760
- * React nor the grid: you pass both in. That is what keeps the package's
7761
- * promise of no runtime dependencies, and what stops an adapter disagreeing
7762
- * with the grid version already loaded.
7763
- *
7764
- * The live grid is reached through a forwarded ref: `ref.current.grid` is the
7765
- * same `Grid` the vanilla `createGrid` returns, or null before mount.
7766
- */
7767
- export function createLatticeGrid(deps: { React: unknown; createGrid: unknown }): unknown;
7768
- /** Every grid event, as the prop name a React caller writes. */
7769
- export const EVENT_NAMES: readonly string[];
7770
- export function handlerName(event: string): string;
7771
- export default createLatticeGrid;
7772
- }
7773
-
7774
- declare module 'lattice-grid/modules/vue' {
7775
- /**
7776
- * Build the Vue 3 component.
7777
- *
7778
- * The Vue runtime and `createGrid` are passed in, for the same reason as the
7779
- * React adapter: the package ships no dependencies and cannot import either.
7780
- * The dependency key is lowercase `vue` — `createLatticeGrid({ vue, createGrid })`.
7781
- *
7782
- * The live grid is reached through the component's exposed `grid()` method:
7783
- * with `ref="grid"` on the element, `this.$refs.grid.grid()` returns the same
7784
- * `Grid` the vanilla `createGrid` returns, or null before mount.
7785
- */
7786
- export function createLatticeGrid(deps: { vue: unknown; createGrid: unknown }): unknown;
7787
- export const EVENT_NAMES: readonly string[];
7788
- export function dashedName(event: string): string;
7789
- export default createLatticeGrid;
7790
- }
7791
-
7792
- declare module 'lattice-grid/modules/svelte' {
7793
- /**
7794
- * A Svelte action: `use:lattice={config}`.
7795
- *
7796
- * The action owns nothing but the node the caller already has, so the grid is
7797
- * reached one of two ways. Pass an `onGrid` callback in the action params
7798
- * (BACKLOG-0000785): `use:lattice={{ ...config, onGrid: (g) => (grid = g) }}`
7799
- * calls it once with the live `Grid` the moment it is built — synchronously,
7800
- * before `ready` fires — and again if you hand the action a different
7801
- * `onGrid`. Or read it off an event: every grid event carries the grid on its
7802
- * `detail`, so `on:ready={(e) => e.detail.grid}` hands you the same `Grid` a
7803
- * turn after construction. Use `onGrid` when you need the instance during the
7804
- * first render.
7805
- */
7806
- export function createLatticeAction(deps: { createGrid: unknown }): unknown;
7807
- export const EVENT_NAMES: readonly string[];
7808
- export function dashedName(event: string): string;
7809
- export default createLatticeAction;
7810
- }
7811
-
7812
- declare module 'lattice-grid/modules/angular' {
7813
- /**
7814
- * Build the Angular standalone component and directive from one shared
7815
- * controller (BACKLOG-0000805).
7816
- *
7817
- * The Angular core namespace and `createGrid` are passed in, for the same
7818
- * reason as every other adapter: the package ships no dependencies and cannot
7819
- * import `@angular/core` or the grid. Pass `@angular/common`'s
7820
- * `isPlatformBrowser` too for an explicit SSR guard; without it the adapter
7821
- * guards on the presence of a `document`.
7822
- *
7823
- * The returned `LatticeGridComponent` (`<lattice-grid [config]="…">`) and
7824
- * `LatticeGridDirective` (`<div [latticeGrid]="…">`) each expose the live grid
7825
- * through a `grid` getter — the same `Grid` the vanilla `createGrid` returns,
7826
- * or null before build — at parity with React's `ref.current.grid`. Grid
7827
- * events are `@Output`s aliased to their dashed names (`(cell-changed)`).
7828
- */
7829
- export function createLatticeGrid(
7830
- deps: { ng: unknown; createGrid: unknown; isPlatformBrowser?: (id: unknown) => boolean },
7831
- ): { LatticeGridComponent: unknown; LatticeGridDirective: unknown };
7832
- export const EVENT_NAMES: readonly string[];
7833
- export function dashedName(event: string): string;
7834
- export default createLatticeGrid;
7835
- }
7836
-
7837
- declare module 'lattice-grid/modules/data-router' {
7838
- /**
7839
- * A record routed through a data router: any object. Its partition comes from
7840
- * the router's `key` and its identity within a grid from `rowKey`.
7841
- */
7842
- type RouterRecord = Record<string, unknown>;
7843
-
7844
- /** A per-route diff summary returned by `load`. */
7845
- interface RouteDiff { added: number; updated: number; removed: number }
7846
-
7847
- /** A predicate: a property value (`row[key] === value`) or a `fn(row)`. */
7848
- type RoutePredicate = unknown | ((row: RouterRecord) => boolean);
7849
-
7850
- /**
7851
- * Per-route reshaping options (v3, BACKLOG-0000887): `transform` maps/renames/
7852
- * derives each row before the grid sees it; `filter` gives the grid only the
7853
- * rows it admits; `sort` (a comparator or `{ key, dir }`) orders what the grid
7854
- * receives. `rowKey` overrides the router default. All optional.
7855
- */
7856
- interface RouteOptions {
7857
- rowKey?: (string | ((row: RouterRecord) => unknown));
7858
- transform?: (row: RouterRecord) => RouterRecord;
7859
- filter?: (row: RouterRecord) => boolean;
7860
- sort?: (((a: RouterRecord, b: RouterRecord) => number) | { key: string; dir?: 'asc' | 'desc' });
7861
- }
7862
-
7863
- /**
7864
- * A cross-grid selection relation (v2, BACKLOG-0000880): a key map (target
7865
- * rows whose `to` value is among the selected source rows' `from` values — an
7866
- * IN set), or a function handed the selected source rows that returns a
7867
- * target-row predicate.
7868
- */
7869
- type SelectionRelation =
7870
- | { from: string; to: string }
7871
- | ((selected: RouterRecord[]) => ((row: RouterRecord) => boolean));
7872
-
7873
- /**
7874
- * A data router: one arriving stream, partitioned by a property (or composite
7875
- * predicate), fanned out to a grid per partition (BACKLOG-0000879). Each grid
7876
- * sees only its slice, updated by keyed diff through the public
7877
- * `grid.rows.apply` path — no grid-core change, no cross-references between
7878
- * grids. Snapshots apply keyed diffs (unchanged rows never repaint); deltas add,
7879
- * update or remove in place by `rowKey`, preserving selection and scroll.
7880
- */
7881
- interface DataRouter {
7882
- /** Attach a grid behind a predicate; `opts` may reshape/filter/sort the route (v3). */
7883
- attach(grid: unknown, predicate: RoutePredicate, opts?: RouteOptions): DataRouter;
7884
- /** Attach the "rest" sink for records no explicit route matched. */
7885
- attachDefault(grid: unknown, opts?: RouteOptions): DataRouter;
7886
- /** Detach a grid; the host still owns and destroys it. */
7887
- detach(grid: unknown): DataRouter;
7888
- /** Apply a full snapshot as a keyed diff per grid; returns per-route counts. */
7889
- load(snapshot: RouterRecord[]): RouteDiff[];
7890
- /** Apply incremental deltas, routed and applied in place by `rowKey`. */
7891
- apply(deltas: { op: 'upsert' | 'delete'; row: RouterRecord }[]): void;
7892
- /**
7893
- * Link a source grid's selection to what a target grid receives (v2,
7894
- * BACKLOG-0000880): the target shows the subset of its partition the
7895
- * `relation` admits, re-pushed through the keyed-diff path. No selection
7896
- * shows the full partition; changes are debounced.
7897
- */
7898
- link(source: unknown, target: unknown, relation: SelectionRelation): DataRouter;
7899
- /** Apply any debounced selection refilter synchronously (for tests/determinism). */
7900
- flush(): DataRouter;
7901
- /** How many records matched no route. */
7902
- readonly unrouted: number;
7903
- /** Detach every grid and drop every link (the host destroys the grids themselves). */
7904
- destroy(): void;
7905
- }
7906
-
7907
- /**
7908
- * Create a data router that partitions one stream to many grids.
7909
- *
7910
- * `key` is the partition property or `fn(row)`; `rowKey` is the identity within
7911
- * a grid; `overlap` fans a record to every matching route (default: first match
7912
- * wins); `onUnrouted` receives records that match none; `selectionDebounce` is
7913
- * the debounce in ms for cross-grid selection refilters (default 16; `0` is
7914
- * synchronous).
7915
- */
7916
- export function createDataRouter(opts: {
7917
- key: (string | ((row: RouterRecord) => unknown));
7918
- rowKey?: (string | ((row: RouterRecord) => unknown));
7919
- overlap?: boolean;
7920
- onUnrouted?: (item: unknown) => void;
7921
- selectionDebounce?: number;
7922
- }): DataRouter;
7923
- export default createDataRouter;
7924
- }
7925
-
7926
- declare module 'lattice-grid/modules/gantt' {
7927
- /** One of the four dependency link types (finish-to-start, start-to-start, finish-to-finish, start-to-finish). */
7928
- export type GanttLinkType = 'FS' | 'SS' | 'FF' | 'SF';
7929
-
7930
- /** A scheduling constraint: pin the start, pin the finish, or schedule as late as possible. */
7931
- export type GanttConstraintType =
7932
- | 'must-start-on' | 'must-finish-on' | 'as-late-as-possible' | 'MSO' | 'MFO' | 'ALAP';
7933
-
7934
- /** A working-time calendar: a Monday–Friday preset, or explicit working weekdays and holidays. */
7935
- export type GanttCalendar =
7936
- | 'weekends'
7937
- | { workdays?: number[]; holidays?: Array<string | number | Date> };
7938
-
7939
- /**
7940
- * A task in a Gantt plan. Give a `duration` or a `start`+`end` (a day-number,
7941
- * ISO date string or `Date`; one is derived from the other). `milestone: true`
7942
- * (or `duration: 0`) is a zero-duration point. `parent` nests a task under a
7943
- * summary, whose window and progress are DERIVED from its children.
7944
- * `baselineStart`/`baselineEnd` (host-stored) drive planned-vs-actual variance;
7945
- * `constraint` pins or pulls the task; `assignee` and `height` feed the split
7946
- * view's grid panel.
7947
- */
7948
- export interface GanttTask {
7949
- id: string | number;
7950
- name?: string;
7951
- start?: number | string | Date;
7952
- end?: number | string | Date;
7953
- duration?: number;
7954
- percentComplete?: number;
7955
- milestone?: boolean;
7956
- parent?: string | number;
7957
- baselineStart?: number | string | Date;
7958
- baselineEnd?: number | string | Date;
7959
- baseline?: { start?: number | string | Date; end?: number | string | Date };
7960
- constraint?: GanttConstraintType;
7961
- constraintDate?: number | string | Date;
7962
- assignee?: string | string[];
7963
- assignees?: string[];
7964
- owner?: string;
7965
- /**
7966
- * Explicit resource assignments with fractional units (BACKLOG-0000948):
7967
- * `units` is a multiplier where 1 is a full-time booking. Use this when a
7968
- * task books a resource at less (or more) than 100%; a bare `assignee` is
7969
- * `units: 1`.
7970
- */
7971
- assignments?: Array<{ resource?: string; name?: string; id?: string; units?: number }>;
7972
- /** Leveling priority: a higher value is delayed last (default 0). */
7973
- priority?: number;
7974
- /** An explicit row height (px) for the split view; applied to both panels. */
7975
- height?: number;
7976
- /**
7977
- * The budgeted cost (BAC) for earned-value analysis (BACKLOG-0000958). When
7978
- * omitted the task's duration is used as the budget, giving schedule-only EVM.
7979
- */
7980
- cost?: number;
7981
- /**
7982
- * The actual cost incurred (ACWP) for earned-value analysis
7983
- * (BACKLOG-0000958). Left out, the task's cost variance/CPI are `null`.
7984
- */
7985
- actualCost?: number;
7986
- }
7987
-
7988
- /**
7989
- * Resource capacities for over-allocation detection and leveling
7990
- * (BACKLOG-0000948): either a list of resources with a capacity (max
7991
- * concurrent units, default 1) or a name→capacity map.
7992
- */
7993
- export type GanttResourceSpec =
7994
- | Array<{ id?: string; name?: string; resource?: string; capacity?: number; maxUnits?: number; max?: number; units?: number }>
7995
- | Record<string, number>;
7996
-
7997
- /**
7998
- * A typed dependency between two tasks (by id), with optional lag/lead. `type`
7999
- * defaults to `'FS'`; either endpoint may be a leaf or a summary.
8000
- *
8001
- * `type` also accepts the MS Project string shorthand — `'FS+2'`, `'SS-1'`
8002
- * (BACKLOG-0001072). It is normalised to the structured form on the way in, so
8003
- * `gantt.dependencies` always reads back `{ type, lag }` and there is no second
8004
- * internal representation. Giving both a shorthand lag and a conflicting `lag`
8005
- * field warns; the explicit field wins.
8006
- */
8007
- export interface GanttDependency {
8008
- from: string | number;
8009
- to: string | number;
8010
- type?: GanttLinkType | `${GanttLinkType}${'+' | '-'}${number}`;
8011
- lag?: number;
8012
- }
8013
-
8014
- /** The computed CPM values for one task (a leaf is scheduled, a summary derived). */
8015
- interface GanttScheduledTask {
8016
- id: string;
8017
- name: string;
8018
- duration: number;
8019
- es: number;
8020
- ef: number;
8021
- ls: number;
8022
- lf: number;
8023
- totalFloat: number;
8024
- critical: boolean;
8025
- percentComplete: number | null;
8026
- parent: string | null;
8027
- isSummary: boolean;
8028
- isMilestone: boolean;
8029
- children: string[];
8030
- /** The planned (baseline) window, present only when the task carries a baseline. */
8031
- baselineStart?: number | null;
8032
- baselineEnd?: number | null;
8033
- /** Variance vs the baseline (actual − planned, day-numbers); a positive value is a slip. */
8034
- startVariance?: number | null;
8035
- finishVariance?: number | null;
8036
- durationVariance?: number | null;
8037
- }
8038
-
8039
- /** An unhonourable scheduling constraint, reported rather than obeyed. */
8040
- interface GanttConflict {
8041
- id: string;
8042
- type: string;
8043
- at: number | null;
8044
- earliestFeasible: number;
8045
- }
8046
-
8047
- /** A CPM schedule result: per-task dates/float and the critical path, or an error. */
8048
- interface GanttSchedule {
8049
- ok: boolean;
8050
- error?: { code: string; message: string; cycle?: string[] };
8051
- tasks?: Map<string, GanttScheduledTask>;
8052
- order?: string[];
8053
- critical?: string[];
8054
- criticalPaths?: string[][];
8055
- projectStart?: number;
8056
- projectFinish?: number;
8057
- projectDuration?: number;
8058
- /** Constraints a predecessor made infeasible (empty when all are satisfied). */
8059
- conflicts?: GanttConflict[];
8060
- /** Whether a working-time calendar was applied. */
8061
- calendar?: boolean;
8062
- /** The resource over-allocations for this schedule (BACKLOG-0000948). */
8063
- overAllocations?: GanttOverAllocation[];
8064
- /** The full resource-load report for this schedule (BACKLOG-0000948). */
8065
- resourceLoad?: GanttResourceLoad;
8066
- }
8067
-
8068
- /** One contiguous load segment for a resource: how many units are booked over a span. */
8069
- interface GanttResourceSegment {
8070
- start: number;
8071
- end: number;
8072
- load: number;
8073
- taskIds: string[];
8074
- }
8075
-
8076
- /** A resource booked beyond its capacity across concurrent tasks (BACKLOG-0000948). */
8077
- interface GanttOverAllocation {
8078
- resource: string;
8079
- capacity: number;
8080
- start: number;
8081
- end: number;
8082
- load: number;
8083
- taskIds: string[];
8084
- }
8085
-
8086
- /** The per-resource load and the over-allocations across a schedule (BACKLOG-0000948). */
8087
- interface GanttResourceLoad {
8088
- ok: boolean;
8089
- resources: Array<{ resource: string; capacity: number; peak: number; segments: GanttResourceSegment[] }>;
8090
- overAllocations: GanttOverAllocation[];
8091
- byResource: Map<string, { capacity: number; peak: number; segments: GanttResourceSegment[] }>;
8092
- }
8093
-
8094
- /** The result of resource leveling: the shifted tasks and what moved (BACKLOG-0000948). */
8095
- interface GanttLevelResult {
8096
- ok: boolean;
8097
- resolved?: boolean;
8098
- tasks?: GanttTask[];
8099
- schedule?: GanttSchedule;
8100
- moves?: Array<{ id: string; from: number; to: number; delay: number }>;
8101
- remaining?: GanttOverAllocation[];
8102
- error?: { code: string; message: string };
8103
- }
8104
-
8105
- /** A placement violation flagged by `findViolations`. */
8106
- interface GanttViolation {
8107
- id: string;
8108
- placedStart: number;
8109
- earliestStart: number;
8110
- by: number;
8111
- }
8112
-
8113
- /** The four link types, in documented order. */
8114
- export const LINK_TYPES: readonly GanttLinkType[];
8115
-
8116
- /** Error codes the scheduler reports (rather than throwing) on bad input. */
8117
- export const SCHEDULE_ERROR: Record<string, string>;
8118
-
8119
- /**
8120
- * Compute the CPM schedule for a set of tasks and dependencies: forward and
8121
- * backward passes over the leaf tasks honouring FS/SS/FF/SF + lag, slack/float
8122
- * and the zero-float critical path, with summaries derived from their children,
8123
- * milestones scheduled as points, and dependency cycles refused (never looped).
8124
- */
8125
- export function computeSchedule(tasks: GanttTask[], deps?: GanttDependency[], options?: { projectStart?: number | string | Date; deadline?: number | string | Date; calendar?: GanttCalendar | null }): GanttSchedule;
8126
-
8127
- /** The tasks placed earlier than their earliest feasible start (manual validation). */
8128
- export function findViolations(tasks: GanttTask[], schedule: GanttSchedule): GanttViolation[];
8129
-
8130
- /** Format an engine day-number as an ISO calendar date (`YYYY-MM-DD`, UTC). */
8131
- export function toISODate(day: number): string | null;
8132
-
8133
- /** Earned-value metrics for one task or the whole project (BACKLOG-0000958). */
8134
- interface GanttEarnedValueRow {
8135
- id: string;
8136
- name: string;
8137
- isSummary: boolean;
8138
- isMilestone: boolean;
8139
- percentComplete: number | null;
8140
- /** Whether a baseline (not the fallback scheduled window) drove PV. */
8141
- hasBaseline: boolean;
8142
- /** Whether any actual cost fed AC (else AC/CV/CPI are null). */
8143
- hasActualCost: boolean;
8144
- /** Budget at completion (the task's cost, or its duration when no cost). */
8145
- bac: number;
8146
- /** Planned Value (BCWS): budgeted cost of the work scheduled by the status date. */
8147
- pv: number;
8148
- /** Earned Value (BCWP): budgeted cost of the work performed (BAC × %complete). */
8149
- ev: number;
8150
- /** Actual Cost (ACWP): what the work performed actually cost, or null. */
8151
- ac: number | null;
8152
- /** Schedule Variance (EV − PV); positive is ahead of schedule. */
8153
- sv: number;
8154
- /** Cost Variance (EV − AC); positive is under budget; null without AC. */
8155
- cv: number | null;
8156
- /** Schedule Performance Index (EV / PV); null when PV is zero. */
8157
- spi: number | null;
8158
- /** Cost Performance Index (EV / AC); null without AC or when AC is zero. */
8159
- cpi: number | null;
8160
- }
8161
-
8162
- /** The earned-value result at a status date (BACKLOG-0000958). */
8163
- interface GanttEarnedValue {
8164
- ok: boolean;
8165
- error?: { code: string; message: string };
8166
- /** The status date the metrics were evaluated at (day-number). */
8167
- statusDate?: number;
8168
- /** Every task keyed by id (leaf, summary and derived). */
8169
- byTask?: Map<string, GanttEarnedValueRow>;
8170
- /** The same rows in schedule order. */
8171
- rows?: GanttEarnedValueRow[];
8172
- /** The project total, rolled up as money sums of the leaves. */
8173
- project?: GanttEarnedValueRow;
8174
- }
8175
-
8176
- /**
8177
- * Compute earned-value management (EVM) metrics for a scheduled plan at a
8178
- * status date (BACKLOG-0000958): PV/BCWS from the baseline, EV/BCWP from
8179
- * %complete, AC/ACWP from the per-task `actualCost`, and the derived SV/CV and
8180
- * SPI/CPI — per leaf, rolled up to summaries and the project. The math is
8181
- * implemented locally in the module (no core-compute dependency).
8182
- */
8183
- export function computeEarnedValue(
8184
- tasks: GanttTask[],
8185
- schedule: GanttSchedule,
8186
- options?: { statusDate?: number | string | Date; costField?: string; actualCostField?: string },
8187
- ): GanttEarnedValue;
8188
-
8189
- /** A headless Gantt controller: holds the model, recomputes on edits, emits changes. */
8190
- interface Gantt {
8191
- readonly tasks: GanttTask[];
8192
- readonly dependencies: GanttDependency[];
8193
- readonly schedule: GanttSchedule | null;
8194
- readonly critical: string[];
8195
- /** Constraints the latest schedule could not honour (empty when all are satisfied). */
8196
- readonly conflicts: GanttConflict[];
8197
- readonly autoSchedule: boolean;
8198
- readonly grid: unknown;
8199
- /** The over-allocations from the latest schedule (BACKLOG-0000948). */
8200
- readonly overAllocations: GanttOverAllocation[];
8201
- /** The latest resource-load report, or null before a successful schedule (BACKLOG-0000948). */
8202
- readonly resourceLoad: GanttResourceLoad | null;
8203
- setTasks(tasks: GanttTask[]): GanttSchedule;
8204
- setDependencies(deps: GanttDependency[]): GanttSchedule;
8205
- applyEdit(patch: { id: string | number; start?: number; end?: number; duration?: number }, editOpts?: { writeBack?: boolean }): GanttSchedule;
8206
- compute(): GanttSchedule;
8207
- findViolations(): GanttViolation[];
8208
- /**
8209
- * Compute the resource load and over-allocations on demand (BACKLOG-0000948),
8210
- * optionally overriding the capacities for this call.
8211
- */
8212
- resources(loadOpts?: { resources?: GanttResourceSpec; defaultCapacity?: number }): GanttResourceLoad;
8213
- /**
8214
- * Resolve resource over-allocation by shifting tasks later — resource
8215
- * leveling (BACKLOG-0000948). Honours the CPM dependencies and the
8216
- * working-time calendar. Mutates the model unless `{ dryRun: true }`; with
8217
- * `{ writeBack: true }` and a bound grid the moved tasks are pushed through
8218
- * the grid's edit surface.
8219
- */
8220
- level(levelOpts?: {
8221
- dryRun?: boolean;
8222
- writeBack?: boolean;
8223
- priorityField?: string;
8224
- maxIterations?: number;
8225
- resources?: GanttResourceSpec;
8226
- defaultCapacity?: number;
8227
- }): GanttLevelResult;
8228
- /** Export the scheduled tasks as CSV; `{ dates: true }` writes ISO dates. */
8229
- toCSV(csvOpts?: { dates?: boolean }): string;
8230
- /**
8231
- * Export the current plan as Microsoft Project (MSPDI) XML (BACKLOG-0000950):
8232
- * tasks, dependencies, constraints, baseline, resources and assignments, plus
8233
- * the working-time calendar, serialised with the computed schedule.
8234
- */
8235
- toMSPDI(xmlOpts?: { hoursPerDay?: number; projectName?: string }): string;
8236
- /**
8237
- * The live consumer surface, mirroring `grid.rows.apply`, so a Data Router
8238
- * can drive the Gantt like any other view. Keyed by the controller's rowKey.
8239
- */
8240
- readonly rows: {
8241
- apply(change: { add?: GanttTask[]; update?: GanttTask[]; remove?: Array<string | GanttTask> }): {
8242
- added: GanttTask[]; updated: GanttTask[]; removed: string[];
8243
- };
8244
- };
8245
- on(event: 'schedule' | 'error', fn: (payload: unknown) => void): () => void;
8246
- off(event: 'schedule' | 'error', fn: (payload: unknown) => void): void;
8247
- /**
8248
- * Render the plan into a container as an SVG timeline (bars, dependency
8249
- * arrows, critical-path highlight, today line, non-working shading,
8250
- * milestones, progress). The view redraws when the schedule recomputes.
8251
- */
8252
- mount(container: unknown, options?: {
8253
- /**
8254
- * The plot width. `'container'` (the default) measures the element it was
8255
- * mounted into and keeps following it, so a plan in a tab, drawer,
8256
- * accordion or split pane fits without the host writing a
8257
- * `ResizeObserver` (BACKLOG-0001079); a container with no box yet holds a
8258
- * 720px fallback rather than drawing at zero. A number is honoured
8259
- * exactly and installs no observer. Ignored under `zoom`, which warns.
8260
- */
8261
- width?: number | 'container';
8262
- rowHeight?: number;
8263
- labelWidth?: number;
8264
- rowLabels?: boolean;
8265
- showArrows?: boolean;
8266
- showCritical?: boolean;
8267
- showProgress?: boolean;
8268
- dateAxis?: boolean;
8269
- /**
8270
- * The today line, as a plan day-number or a calendar date. A date is
8271
- * converted into plan space through `projectEpoch` (BACKLOG-0001079), so
8272
- * "put the line on the real today" is expressible for a relative plan.
8273
- */
8274
- today?: number | string | Date;
8275
- /**
8276
- * The calendar date plan day 0 stands for (BACKLOG-0001079).
8277
- *
8278
- * Display-only: axis ticks, bar labels, tooltips, screen-reader text and
8279
- * the built-in `'weekends'` shading move with it; the schedule, `getState`
8280
- * and the CSV/MSPDI exports do not. Without it, the engine's contract makes
8281
- * day 0 the Unix epoch, which is why a plan written as day offsets renders
8282
- * as January 1970. A host-supplied `nonWorking` function still receives raw
8283
- * plan days.
8284
- */
8285
- projectEpoch?: number | string | Date | null;
8286
- nonWorking?: 'weekends' | ((day: number) => boolean);
8287
- label?: 'name' | 'percent' | 'dates' | 'none' | ((task: GanttScheduledTask) => string);
8288
- /** Whether bars can be dragged to move/resize (default true). */
8289
- editable?: boolean;
8290
- /** Pixels from a bar's right edge that begin a resize rather than a move. */
8291
- resizeZone?: number;
8292
- /** Time-scale zoom: a level, or raw pixels-per-day. Omit to fit the width. */
8293
- zoom?: 'day' | 'week' | 'month' | 'quarter' | number;
8294
- /** Scroll so the today line is in view after drawing. */
8295
- scrollToToday?: boolean;
8296
- /** Show a hover tooltip (dates/duration/%/slack); default true. */
8297
- tooltip?: boolean;
8298
- /** Group tasks into swimlanes by a task property name or `fn(task)`. */
8299
- groupBy?: string | ((task: GanttTask) => unknown);
8300
- /** Keyboard editing + focusable bars + ARIA announcements (default true). */
8301
- keyboard?: boolean;
8302
- /** Days a keyboard arrow moves/resizes a task (default 1). */
8303
- moveStep?: number;
8304
- }): unknown;
8305
- /**
8306
- * Mount the JOINED split view (BACKLOG-0000938): one continuous, row-aligned
8307
- * surface with a left task-grid panel (Task Name tree with expand/collapse,
8308
- * assignee avatars, a circular % ring, plus any host columns) and the right
8309
- * timeline, sharing a single vertical scroll so every grid row lines up
8310
- * exactly with its bar row. The timeline scrolls horizontally on its own.
8311
- * Composes the controller's schedule; makes no change to grid core.
8312
- */
8313
- mountSplit(container: unknown, options?: {
8314
- height?: number;
8315
- rowHeight?: number;
8316
- headerHeight?: number;
8317
- gridWidth?: number;
8318
- indent?: number;
8319
- zoom?: 'day' | 'week' | 'month' | 'quarter' | number;
8320
- today?: number;
8321
- nonWorking?: 'weekends' | ((day: number) => boolean);
8322
- calendar?: GanttCalendar | null;
8323
- showArrows?: boolean;
8324
- showProgress?: boolean;
8325
- showBaseline?: boolean;
8326
- barLabel?: 'name' | 'percent' | 'dates' | 'none' | ((task: GanttScheduledTask) => string);
8327
- /**
8328
- * Surface earned-value metrics in `kind: 'evm'` columns (BACKLOG-0000958).
8329
- * `true` computes EVM at the today line (or the project finish); an object
8330
- * overrides the status date and the cost field names.
8331
- */
8332
- evm?: boolean | { statusDate?: number | string | Date; costField?: string; actualCostField?: string };
8333
- columns?: Array<{ key: string; title?: string; width?: number; kind?: 'name' | 'assignee' | 'progress' | 'evm'; metric?: 'bac' | 'pv' | 'ev' | 'ac' | 'sv' | 'cv' | 'spi' | 'cpi'; digits?: number; render?: (task: GanttScheduledTask, ctx: { rawTask: GanttTask; depth: number }) => unknown }>;
8334
- }): unknown;
8335
- /**
8336
- * Capture a baseline (planned) snapshot of the current schedule as HOST data
8337
- * (this does not mutate the tasks). Store it and feed it back as
8338
- * `baselineStart`/`baselineEnd` task fields to get variance and ghost bars.
8339
- */
8340
- captureBaseline(): Array<{ id: string; baselineStart: number; baselineEnd: number; baselineDuration: number }>;
8341
- /**
8342
- * Compute earned-value (EVM) metrics for the current plan at a status date
8343
- * (BACKLOG-0000958): PV/EV/AC and the derived SV/CV/SPI/CPI per task, rolled
8344
- * up to summaries and the project. Budget (BAC) is the task's `cost`, or its
8345
- * duration when no cost is given; AC comes from `actualCost`.
8346
- */
8347
- earnedValue(evmOpts?: { statusDate?: number | string | Date; costField?: string; actualCostField?: string }): GanttEarnedValue;
8348
- /** Detach the mounted view, if any. The host still owns the container. */
8349
- unmount(): void;
8350
- /** The mounted view, or null. */
8351
- readonly view: unknown;
8352
- destroy(): void;
8353
- }
8354
-
8355
- /**
8356
- * Create a Gantt controller over a task list and a dependency list. Computes
8357
- * the CPM schedule immediately and again on every `setTasks`/`setDependencies`/
8358
- * `applyEdit`, emitting `schedule` on success and `error` on a cycle or bad
8359
- * input. `grid` is stored for the write-back binding; `autoSchedule` requests
8360
- * dependent cascading.
8361
- */
8362
- export function createGantt(opts?: {
8363
- tasks?: GanttTask[];
8364
- dependencies?: GanttDependency[];
8365
- /** The schedule anchor: a day-number, ISO date string or Date. It only sets the floor a task with no predecessor starts on; it does not change how the schedule is computed. */
8366
- projectStart?: number | string | Date;
8367
- /** A project deadline (a day-number, ISO string or Date); tasks that cannot meet it get negative float. */
8368
- deadline?: number | string | Date;
8369
- /** A working-time calendar: skip weekends/holidays, durations in working days. */
8370
- calendar?: GanttCalendar | null;
8371
- /** Resource capacities for over-allocation detection and leveling (BACKLOG-0000948). */
8372
- resources?: GanttResourceSpec;
8373
- /** The capacity for a resource with none stated (default 1 = one full-time booking). */
8374
- defaultCapacity?: number;
8375
- autoSchedule?: boolean;
8376
- grid?: unknown;
8377
- /** Map task fields to grid column ids to enable drag write-back. */
8378
- columns?: { start?: string; end?: string; duration?: string };
8379
- /** Task identity for the live `rows.apply` surface (a field or fn); default 'id'. */
8380
- rowKey?: string | ((row: GanttTask) => unknown);
8381
- /** Auto-mount into this element at construction. */
8382
- element?: unknown;
8383
- }): Gantt;
8384
- export default createGantt;
8385
-
8386
- /** The model {@link importMSPDI} returns and {@link exportMSPDI} takes. */
8387
- interface GanttMSPDIModel {
8388
- tasks: GanttTask[];
8389
- dependencies?: GanttDependency[];
8390
- resources?: GanttResourceSpec;
8391
- projectStart?: number | string | Date;
8392
- calendar?: GanttCalendar | null;
8393
- schedule?: GanttSchedule;
8394
- }
8395
-
8396
- /**
8397
- * Import a Microsoft Project (MSPDI) XML document (BACKLOG-0000950) into the
8398
- * module's model: the task tree, typed dependencies with lag, constraints,
8399
- * baseline, %complete, resources with capacity, the resource assignments, and
8400
- * the working-time calendar. The result is ready to pass to {@link createGantt}.
8401
- */
8402
- export function importMSPDI(xml: string, opts?: { hoursPerDay?: number }): {
8403
- ok: boolean;
8404
- error?: string;
8405
- tasks: GanttTask[];
8406
- dependencies: GanttDependency[];
8407
- resources: Array<{ id: string; name: string; capacity: number }>;
8408
- projectStart?: number;
8409
- calendar?: null | { workdays: number[]; holidays: number[] };
8410
- };
8411
-
8412
- /**
8413
- * Export a Gantt model to Microsoft Project (MSPDI) XML (BACKLOG-0000950). A
8414
- * scheduled model may be passed so start/finish dates are the computed ones.
8415
- */
8416
- export function exportMSPDI(model: GanttMSPDIModel, opts?: { hoursPerDay?: number; projectName?: string }): string;
8417
- }
8418
-
8419
- declare module 'lattice-grid/modules/webcomponent' {
8420
- /**
8421
- * Register `<lattice-grid>`.
8422
- *
8423
- * This module carries the grid inside it. Use it *or* `createGrid` in one
8424
- * page, never both: two copies keep separate registries, and a renderer
8425
- * registered through one will not appear in the other.
8426
- *
8427
- * The live grid is reached through the element's `grid` getter: `el.grid` is
8428
- * the same `Grid` the vanilla `createGrid` returns, or null while the element
8429
- * is disconnected.
8430
- */
8431
- export function defineLatticeGrid(tag?: string): void;
8432
- /**
8433
- * Build the `<lattice-grid>` element class. The one argument is the grid
8434
- * factory the element creates its grid with — `createGrid`-shaped, and
8435
- * defaulting to it — injectable for tests. Returns the class, or `null`
8436
- * where `HTMLElement` is undefined (a Node import, a server-side pass).
8437
- */
8438
- export function createLatticeGridElement(
8439
- factory?: (element: Element, config: GridConfig) => Grid,
8440
- ): typeof HTMLElement | null;
8441
- export const TAG_NAME: string;
8442
- export const EVENT_PREFIX: string;
8443
- export const ATTRIBUTE_CONFIG: Readonly<Record<string, unknown>>;
8444
- export function observedAttributeNames(): string[];
8445
- export function domEventName(event: string): string;
8446
- export class GridElementController {}
8447
- // Core factories re-exported from this module so they bind to the one engine
8448
- // the element already carries: a type built with these here shares the
8449
- // element's registry rather than a second copy's (BACKLOG-0000787). Typed by
8450
- // reference to the base package.
8451
- export { createCurrencyType, createUnitType, registerUnitSystem, createStat } from 'lattice-grid';
8452
- export default defineLatticeGrid;
8453
- }
8454
-
8455
- declare module 'lattice-grid/modules/htmx' {
8456
- /**
8457
- * The htmx integration, which re-exports the base API alongside its own,
8458
- * a page using it imports this and never the base package as well.
8459
- */
8460
- export function createGrid(element: Element, config: GridConfig): Grid;
8461
- export function autoInit(root?: ParentNode): Grid[];
8462
- /**
8463
- * Wire the htmx lifecycle events on a document: grids are built in each
8464
- * swapped-in fragment, released before htmx detaches one, and their view
8465
- * state carried across history navigation. Called once on import against
8466
- * the global `document`; call it again only for another document. Returns
8467
- * the function that removes every listener it installed.
8468
- */
8469
- export function attach(doc?: Document): () => void;
8470
- export function initWithin(root: ParentNode): Grid[];
8471
- export function destroyWithin(root: ParentNode): void;
8472
- export function gridElementsWithin(root: ParentNode): Element[];
8473
- export function hydrateTable(table: Element, config?: GridConfig): Grid;
8474
- export function readTable(table: Element): { columns: Column[]; rows: unknown[] };
8475
- export function rowsFromFragment(fragment: ParentNode): unknown[];
8476
- export function rowsFromJson(text: string): unknown[];
8477
- /**
8478
- * Parse a response into rows by its content type: JSON through
8479
- * `rowsFromJson`, anything else through `rowsFromFragment` against the
8480
- * columns given. The fragment arrives already parsed; this never touches
8481
- * `DOMParser` or `innerHTML`. Returns the rows and, when the body carried
8482
- * one, the total.
8483
- */
8484
- export function ingestResponse(
8485
- response: { contentType: string; text?: string; fragment?: ParentNode },
8486
- columns: { field: string }[],
8487
- ): { rows: unknown[]; total: number | undefined };
8488
- /**
8489
- * Drive server-side sort and filter through htmx. `trigger` is the element
8490
- * carrying the htmx request attributes (`hx-get`, `hx-target`,
8491
- * `hx-trigger="lattice:query-changed"`); the grid's query parameters are
8492
- * merged into that element's request and its response ingested. Returns the
8493
- * function that detaches everything this attached.
8494
- */
8495
- export function driveServerMode(
8496
- grid: Grid,
8497
- trigger: Element,
8498
- opts?: { columns?: { field: string }[] },
8499
- ): () => void;
8500
- /**
8501
- * Load rows in chunks as the user nears the end of what is loaded.
8502
- * `sentinelEl` is the element carrying `hx-get` and
8503
- * `hx-trigger="revealed, lattice:scroll-near-end"`; `threshold` is how many
8504
- * rows from the end counts as near (default 20). Returns the function that
8505
- * detaches everything this attached.
8506
- */
8507
- export function driveInfiniteScroll(
8508
- grid: Grid,
8509
- sentinelEl: Element,
8510
- opts?: { columns?: { field: string }[]; threshold?: number },
8511
- ): () => void;
8512
- export function driveOobUpdates(grid: Grid, opts?: object): () => void;
8513
- export function serialiseState(grid: Grid): string;
8514
- export function restoreState(grid: Grid, state: string): void;
8515
- export function saveStateWithin(root: ParentNode): void;
8516
- export function restoreStateWithin(root: ParentNode): void;
8517
- export function queryParams(grid: Grid): Record<string, string>;
8518
- export function warnIfLargeHtmlPayload(rows: number): void;
8519
- export const QUERY_CHANGED_EVENT: string;
8520
- export const SCROLL_NEAR_END_EVENT: string;
8521
- export const HTML_ROW_WARNING_THRESHOLD: number;
8522
- // The core factory surface this module re-exports, so an htmx page builds its
8523
- // configured columns (a currency type, a unit type, a stat) from the one
8524
- // engine it already carries rather than a second copy (BACKLOG-0000786).
8525
- // Typed by reference to the base package; names the base package leaves
8526
- // untyped stay untyped here too.
8527
- export {
8528
- createHeadlessGrid, version, getVersion, Grid, Registry, registerModules,
8529
- createRadixType, createUnitType, registerUnitSystem, defineUnit, UNIT_SYSTEMS, parseUnit, formatUnit,
8530
- createCurrencyType, parseMoney, formatMoney, convertMoney, rateFunction, MISSING_RATE,
8531
- Messages, createMessages, auditCatalogue,
8532
- EN_GB, MESSAGE_KEYS, DEFAULT_LOCALE, formatList, resolveLocale, LOCALES, resolveCatalogue,
8533
- EN_US, FR_FR, FR_CA, IT_IT, ES_ES, PT_BR, DE_DE, NL_NL, SV_SE, DA_DK, NB_NO, FI_FI,
8534
- PL_PL, CS_CZ, HU_HU, RO_RO, UK_UA, EL_GR, JA_JP, AR, AR_SA,
8535
- Window, openWindow, WINDOW_KINDS,
8536
- evaluateFormula, referencesOf, looksLikeFormula, compileRules, ingest, ingestSync,
8537
- createPushdownSource, planQuery, splitFilters, applyResidual, capabilitiesOf, resolveMutate, NO_CAPABILITIES,
8538
- odataAdapter, restAdapter, dfqlAdapter, duckdbAdapter,
8539
- createStat, deltaOf, toneOf,
8540
- } from 'lattice-grid';
8541
- // American licence aliases mirror the base package (dom/index.js).
8542
- export { setLicence as setLicense, licenceInfo as licenseInfo, licenceState as licenseState } from 'lattice-grid';
8543
- }
8544
-
8545
- declare module 'lattice-grid/modules/dhtmlx-compat' {
8546
- /**
8547
- * A dhtmlx Grid-shaped API over Lattice, for migrating a piece at a time.
8548
- *
8549
- * The module shares the page's one core rather than bundling its own: the
8550
- * grid it builds comes from the `lattice-grid` package the app already loads
8551
- * (or the `LatticeGrid` global a script tag publishes), so a licence set on
8552
- * that core applies to these grids too. Load the core alongside this module —
8553
- * a bundler wires the peer import for you; a `<script src>` page loads the
8554
- * global build first.
8555
- */
8556
- export class Grid {
8557
- constructor(container: Element | string, config?: object);
8558
- }
8559
- export default Grid;
8560
- }
8561
-
8562
- declare module 'lattice-grid/modules/devtools' {
8563
- /**
8564
- * The devtools panel, including the accessibility checks.
8565
- *
8566
- * The grid is handed in rather than imported: a module may depend on nothing
8567
- * in core, or the bundler inlines the whole grid into it.
8568
- */
8569
- export function createDevtools(opts: { grid: Grid; container?: Element }): {
8570
- element: Element;
8571
- refresh(): void;
8572
- destroy(): void;
8573
- };
8574
- export function expose(grid: Grid, name?: string): void;
8575
- /**
8576
- * Whether the console entry point is compiled in. A build that replaces the
8577
- * activation token with `false` removes the global entirely; in every other
8578
- * build this is `true`.
8579
- */
8580
- export const CONSOLE_ACTIVATION: boolean;
8581
- export default createDevtools;
8582
- }
8583
-
8584
- declare module 'lattice-grid/modules/mock-socket' {
8585
- /** One record on a feed: any object. Its partition comes from a property and its identity from `rowKey`. */
8586
- type FeedRow = Record<string, unknown>;
8587
-
8588
- /** One change in a delta batch, in the shape the data router applies. */
8589
- interface FeedChange { op: 'upsert' | 'delete'; row: FeedRow }
8590
-
8591
- /**
8592
- * A message on the wire. A snapshot carries the full opening set; a delta
8593
- * carries the changes since. The reader parses `event.data` and switches on
8594
- * `kind`, exactly as against a real feed that framed its messages the same way.
8595
- */
8596
- interface FeedMessage {
8597
- kind: 'snapshot' | 'delta';
8598
- /** Present on a snapshot: the full opening set of rows. */
8599
- rows?: FeedRow[];
8600
- /** Present on a delta: the changes to apply. */
8601
- changes?: FeedChange[];
8602
- }
8603
-
8604
- /** A feed: any iterator that yields a snapshot first, then deltas forever. */
8605
- type Feed = Iterator<FeedMessage>;
8606
-
8607
- /**
8608
- * A serverless stand-in for a live `WebSocket`. It presents the same surface
8609
- * as the browser's `WebSocket` — `readyState` and the state constants,
8610
- * `onopen`/`onmessage`/`onclose`/`onerror`, `addEventListener`, `send` and
8611
- * `close` — so the code that reads it does not change when it is swapped for a
8612
- * real socket. It opens after a short delay, emits the feed's first value as a
8613
- * snapshot, then pumps one value per tick as a delta.
8614
- */
8615
- export class MockWebSocket {
8616
- static readonly CONNECTING: 0;
8617
- static readonly OPEN: 1;
8618
- static readonly CLOSING: 2;
8619
- static readonly CLOSED: 3;
8620
- readonly CONNECTING: 0;
8621
- readonly OPEN: 1;
8622
- readonly CLOSING: 2;
8623
- readonly CLOSED: 3;
8624
- readyState: number;
8625
- url: string;
8626
- onopen: ((event: { type: string }) => void) | null;
8627
- onmessage: ((event: { type: string; data: string }) => void) | null;
8628
- onclose: ((event: { type: string; code: number; reason: string; wasClean: boolean }) => void) | null;
8629
- onerror: ((event: { type: string; error: unknown }) => void) | null;
8630
- /**
8631
- * @param init the feed and its timing: `feed` (snapshot first, then deltas);
8632
- * `rate` ms between deltas (default 1000); `jitter` random plus-or-minus ms
8633
- * per gap (default 0); `seed` for that jitter (default 1); `snapshotDelay`
8634
- * ms before opening (default 60); `pauseWhenHidden` stops while the tab is
8635
- * hidden (default true); `url` a cosmetic address.
8636
- */
8637
- constructor(init: {
8638
- feed: Feed;
8639
- rate?: number;
8640
- jitter?: number;
8641
- seed?: number;
8642
- snapshotDelay?: number;
8643
- pauseWhenHidden?: boolean;
8644
- url?: string;
8645
- });
8646
- addEventListener(type: string, fn: (event: unknown) => void): void;
8647
- removeEventListener(type: string, fn: (event: unknown) => void): void;
8648
- /** A real socket sends upstream; here it is accepted and ignored. */
8649
- send(data?: unknown): void;
8650
- /** Stop the feed until `resume()`; the socket stays open (a demo/test affordance). */
8651
- pause(): void;
8652
- /** Resume a paused feed. */
8653
- resume(): void;
8654
- /** Close the socket, stop the feed and emit a clean `close`. */
8655
- close(): void;
8656
- }
8657
-
8658
- /**
8659
- * mulberry32: a small seeded pseudo-random generator, so a custom feed can be
8660
- * seeded the same way the shipped ones are. The same seed yields the same
8661
- * sequence of values in `[0, 1)`.
8662
- */
8663
- export function rng(seed: number): () => number;
8664
-
8665
- /**
8666
- * A mixed operations feed — orders, shipments and incidents across three
8667
- * regions plus a throughput rollup — the Data Router tutorial partitions
8668
- * across several grids and a chart from one source. Yields a snapshot, then
8669
- * deltas forever. Seedable for a repeatable stream.
8670
- */
8671
- export function opsFeed(options?: {
8672
- seed?: number;
8673
- orders?: number;
8674
- shipments?: number;
8675
- incidents?: number;
8676
- batch?: number;
8677
- }): Generator<FeedMessage>;
8678
-
8679
- /**
8680
- * A market-data feed: instruments whose prices random-walk each tick, each
8681
- * record carrying `type: 'price'`, `symbol`, `last`, `chg` and a bid/ask. The
8682
- * price/random-walk feed behind the trading-terminal tutorial. Yields a
8683
- * snapshot, then deltas forever. Seedable for a repeatable stream.
8684
- */
8685
- export function priceFeed(options?: {
8686
- seed?: number;
8687
- symbols?: { symbol: string; last: number }[];
8688
- move?: number;
8689
- batch?: number;
8690
- spread?: number;
8691
- }): Generator<FeedMessage>;
8692
-
8693
- export default MockWebSocket;
8694
- }
8695
-
8696
- declare module 'lattice-grid/modules/kanban' {
8697
- /** A row backing a card: any object. Its column comes from `columnProperty` and its identity from `rowKey`. */
8698
- type KanbanRow = Record<string, unknown>;
8699
-
8700
- /**
8701
- * A card model — one row as it appears on the board. `fields` holds the
8702
- * resolved display text for each mapped card field; `columnId` is the column
8703
- * the card sits in; `points` is the numeric points value (0 when absent).
8704
- * `swimlane`/`sprint`/`epic`/`order` are read from their configured properties
8705
- * and carried for the later cycles that render them.
8706
- */
8707
- interface KanbanCard {
8708
- key: unknown;
8709
- row: KanbanRow;
8710
- columnId: string | null;
8711
- points: number;
8712
- hasPoints: boolean;
8713
- order?: unknown;
8714
- swimlane?: unknown;
8715
- sprint?: unknown;
8716
- epic?: unknown;
8717
- fields: Record<string, string>;
8718
- }
8719
-
8720
- /** A column with its cards and aggregates. `over` is true when `count` exceeds `wipLimit`. */
8721
- interface KanbanColumn {
8722
- id: string;
8723
- title: string;
8724
- color: string | null;
8725
- wipLimit: number | null;
8726
- collapsed: boolean;
8727
- cards: KanbanCard[];
8728
- count: number;
8729
- points: number;
8730
- over: boolean;
8731
- }
8732
-
8733
- /** A column definition: an id string, or an object configuring one column. */
8734
- type KanbanColumnDef = string | {
8735
- id: string;
8736
- title?: string;
8737
- color?: string;
8738
- wipLimit?: number;
8739
- collapsed?: boolean;
8740
- /**
8741
- * A per-column SLA override (BACKLOG-0000960): a lone threshold read as the
8742
- * breach level, or a `{ warn, breach }` pair. Overrides the global `sla`
8743
- * thresholds for cards in this column (precedence: lane → column → global).
8744
- */
8745
- sla?: KanbanSlaThreshold | { warn?: KanbanSlaThreshold; breach?: KanbanSlaThreshold };
8746
- /** A per-column warn threshold — the shorthand for `sla: { warn }`. */
8747
- slaWarn?: KanbanSlaThreshold;
8748
- /** A per-column breach threshold — the shorthand for `sla: { breach }`. */
8749
- slaBreach?: KanbanSlaThreshold;
8750
- };
8751
-
8752
- /** A card field editor handle returned by a host editor factory. */
8753
- interface KanbanEditor {
8754
- el: HTMLElement;
8755
- focus?: () => void;
8756
- destroy?: () => void;
8757
- }
8758
-
8759
- /** A card field mapping: a property path, a function, or an object opting into inline edit. */
8760
- type KanbanFieldMap = string | ((row: KanbanRow) => unknown) | {
8761
- field: string;
8762
- edit?: boolean;
8763
- editor?: (ctx: { card: KanbanCard; field: string; value: string; commit: (value: unknown) => void; cancel: () => void }) => KanbanEditor;
8764
- };
8765
-
8766
- /** The field-to-property mapping that drives the card template. */
8767
- interface KanbanCardMap {
8768
- title?: KanbanFieldMap;
8769
- subtitle?: KanbanFieldMap;
8770
- labels?: KanbanFieldMap;
8771
- assignee?: KanbanFieldMap;
8772
- due?: KanbanFieldMap;
8773
- cover?: KanbanFieldMap;
8774
- progress?: KanbanFieldMap;
8775
- badges?: KanbanFieldMap;
8776
- accent?: KanbanFieldMap;
8777
- [field: string]: KanbanFieldMap | undefined;
8778
- }
8779
-
8780
- /** Granular readonly: the whole board, or selectively by column id and card key. */
8781
- type KanbanReadonly = boolean | {
8782
- board?: boolean;
8783
- columns?: Record<string, boolean>;
8784
- cards?: Record<string, boolean>;
8785
- };
8786
-
8787
- /** The payload every board event carries. */
8788
- interface KanbanEvent {
8789
- card: KanbanCard;
8790
- column: string | null;
8791
- el?: unknown;
8792
- originalEvent?: unknown;
8793
- }
8794
-
8795
- /**
8796
- * A card-aging / SLA threshold (BACKLOG-0000960): a raw millisecond count, or
8797
- * a `{ weeks, days, hours, minutes, seconds, ms }` spec whose fields are summed
8798
- * (`{ days: 3, hours: 12 }` → 3.5 days). A negative or non-finite value means
8799
- * "no threshold at this level".
8800
- */
8801
- type KanbanSlaThreshold = number | {
8802
- weeks?: number; week?: number; w?: number;
8803
- days?: number; day?: number; d?: number;
8804
- hours?: number; hour?: number; h?: number;
8805
- minutes?: number; minute?: number; m?: number; min?: number;
8806
- seconds?: number; second?: number; s?: number; sec?: number;
8807
- ms?: number; milliseconds?: number;
8808
- };
8809
-
8810
- /**
8811
- * Card-aging / SLA configuration (BACKLOG-0000960). A card is measured against a
8812
- * `warn` and a `breach` threshold; the view puts an age chip on aged cards and a
8813
- * highlight on breached ones, and a rising crossing fires the `card:sla` event
8814
- * and the matching `onWarn`/`onBreach` callback (signature `(level, rows)`, the
8815
- * Data Router alert handler's). Thresholds resolve most-specific-first:
8816
- * lane → column → global. Reached at runtime as {@link Kanban#sla}.
8817
- */
8818
- interface KanbanSlaConfig {
8819
- /** The global warn threshold. */
8820
- warn?: KanbanSlaThreshold;
8821
- /** The global breach threshold. */
8822
- breach?: KanbanSlaThreshold;
8823
- /** Per-column overrides by column id (each a threshold or a `{ warn, breach }` pair). */
8824
- columns?: Record<string, KanbanSlaThreshold | { warn?: KanbanSlaThreshold; breach?: KanbanSlaThreshold }>;
8825
- /** Per-swimlane overrides by lane id (each a threshold or a `{ warn, breach }` pair). */
8826
- lanes?: Record<string, KanbanSlaThreshold | { warn?: KanbanSlaThreshold; breach?: KanbanSlaThreshold }>;
8827
- /**
8828
- * Where the ageing clock starts: `'column'` (default) measures time in the
8829
- * card's current column; `'board'` measures age since the card arrived/was
8830
- * created.
8831
- */
8832
- basis?: 'column' | 'board';
8833
- /** A row property holding the wall-clock time the card entered its column. */
8834
- enteredProperty?: string;
8835
- /** A row property holding the wall-clock time the card was created. */
8836
- createdProperty?: string;
8837
- /** Whether cards in a done column are exempt from ageing (default true). */
8838
- ignoreDone?: boolean;
8839
- /** Whether the flow transition log drives the ageing basis when present (default true). */
8840
- useTransitionLog?: boolean;
8841
- /** Show the age chip on every aged card (`'always'`), or only on warn/breach (`'threshold'`, default). */
8842
- showAge?: 'always' | 'threshold';
8843
- /** A wall-clock epoch clock, injectable for deterministic tests (default `Date.now`). */
8844
- now?: () => number;
8845
- /** A re-check interval in ms so a card breaching by sitting still still lights up (0 = off). */
8846
- tick?: number;
8847
- /** Called on a rising crossing to warn level, `(level, rows)` — the router alert handler's shape. */
8848
- onWarn?: (level: 'warn' | 'breach', rows: KanbanRow[]) => void;
8849
- /** Called on a rising crossing to breach level, `(level, rows)` — the router alert handler's shape. */
8850
- onBreach?: (level: 'warn' | 'breach', rows: KanbanRow[]) => void;
8851
- }
8852
-
8853
- /** The computed SLA state of one card (BACKLOG-0000960). */
8854
- interface KanbanSlaState {
8855
- key: unknown;
8856
- columnId: string | null;
8857
- lane?: unknown;
8858
- /** The ageing-clock start epoch (ms), or null when no time source could be resolved. */
8859
- start: number | null;
8860
- /** The card's age in ms, or null when unknown. */
8861
- ageMs: number | null;
8862
- /** A short human age label (`2d`, `5h`, …), '' when unknown. */
8863
- ageText: string;
8864
- /** The resolved warn threshold in ms, or null. */
8865
- warnMs: number | null;
8866
- /** The resolved breach threshold in ms, or null. */
8867
- breachMs: number | null;
8868
- /** The classified level, or null when the card cannot be aged. */
8869
- level: 'ok' | 'warn' | 'breach' | null;
8870
- /** True when `level` is `'breach'`. */
8871
- breached: boolean;
8872
- }
8873
-
8874
- /**
8875
- * The card-aging / SLA monitor (BACKLOG-0000960), reached as {@link Kanban#sla}
8876
- * when a `sla` config is supplied. Pure and DOM-free: it computes each card's
8877
- * ageing state from the board's card model and the flow transition log, and the
8878
- * view paints it.
8879
- */
8880
- interface KanbanSla {
8881
- /** The normalised SLA config (read-only). */
8882
- readonly config: object;
8883
- /** Recompute every card's SLA state without emitting anything. */
8884
- sync(): KanbanSla;
8885
- /** Recompute and fire `card:sla`/`onWarn`/`onBreach` on each rising crossing. */
8886
- evaluate(opts?: { emit?: boolean }): KanbanSlaState[];
8887
- /** Establish the baseline, notify on the current state, and start the optional tick. */
8888
- start(): KanbanSla;
8889
- /** The SLA state of one card (by card model or key), or null when unknown. */
8890
- stateFor(cardOrKey: KanbanCard | unknown): KanbanSlaState | null;
8891
- /** Every card's current SLA state. */
8892
- states(): KanbanSlaState[];
8893
- /** The cards currently at breach level. */
8894
- breaches(): KanbanSlaState[];
8895
- /** The cards currently at warn level (not yet breached). */
8896
- warnings(): KanbanSlaState[];
8897
- /** Stop the tick and drop the board subscriptions. */
8898
- destroy(): void;
8899
- }
8900
-
8901
- /**
8902
- * Kanban configuration. Every structural property is named here so the same
8903
- * board maps DemandFlow (a status field, `points`, `sprint`, `epic`, a
8904
- * swimlane property) and any customer schema without code change.
8905
- */
8906
- interface KanbanConfig {
8907
- rows?: KanbanRow[];
8908
- grid?: unknown;
8909
- rowKey?: string | ((row: KanbanRow) => unknown);
8910
- columnProperty?: string;
8911
- columns?: KanbanColumnDef[];
8912
- columnOrder?: string[];
8913
- pointsProperty?: string;
8914
- showPoints?: boolean;
8915
- orderProperty?: string;
8916
- swimlaneProperty?: string;
8917
- /** Render the 2D swimlane layout using `swimlaneProperty` (default false). */
8918
- swimlanes?: boolean;
8919
- /** Explicit lane definitions; otherwise lanes come from the distinct swimlane values. */
8920
- lanes?: (string | { id: string; title?: string })[];
8921
- /** An explicit lane order by id (also set by a lane-header-drag reorder). */
8922
- laneOrder?: string[];
8923
- /** Enforce `wipLimit` as a hard gate: a move that would exceed it is refused (default false). */
8924
- enforceWip?: boolean;
8925
- /** A custom card template: return an HTML string or a DOM node to own the whole card body. */
8926
- cardRenderer?: (card: KanbanCard, ctx: { column: KanbanColumn; readonly: boolean; el: HTMLElement; doc: Document }) => string | Node | void;
8927
- sprintProperty?: string;
8928
- epicProperty?: string;
8929
- /** A configurable sprint dataset: the canonical sprint list (order + titles), shown even when empty. */
8930
- sprints?: (string | { id: unknown; title?: string })[];
8931
- /** The initially selected sprint id, `Kanban.BACKLOG`, or undefined for all. */
8932
- sprint?: unknown;
8933
- /** The initially selected epic id, or undefined for all. */
8934
- epic?: unknown;
8935
- /** Column ids that count as "done" for a rollup's progress (also a column def's `done: true`). */
8936
- doneColumns?: string[];
8937
- /** Card pop-out: a nested child grid or board (master-detail by composition). */
8938
- children?: KanbanChildren;
8939
- /** Card virtualization for tall columns: true, or `{ rowHeight, overscan, threshold, viewport }`. */
8940
- virtualize?: boolean | { rowHeight?: number; overscan?: number; threshold?: number; viewport?: number };
8941
- /**
8942
- * Card aging / SLA highlighting (BACKLOG-0000960): warn/breach thresholds
8943
- * (globally, per column and/or per lane) that age each card and fire
8944
- * `card:sla` on a rising crossing. Opt-in; reached at runtime as
8945
- * {@link Kanban#sla}. See {@link KanbanSlaConfig}.
8946
- */
8947
- sla?: KanbanSlaConfig;
8948
- /** A saved board state (from `getState`) to restore on construction. */
8949
- state?: object;
8950
- /** Show a per-column add-card affordance. */
8951
- addCard?: boolean;
8952
- /** Persist a standalone inline edit; return false or a rejected promise to revert. */
8953
- onCardEdit?: (event: { card: KanbanCard; key: unknown; field: string; fieldPath: string; value: unknown }) => boolean | void | Promise<boolean | void>;
8954
- /**
8955
- * Create a card for a column on add-card; return the row to create (with
8956
- * its key), a Promise of that row, or nothing to auto-generate. A rejected
8957
- * Promise creates no card and leaves the board unchanged (BACKLOG-0001230).
8958
- */
8959
- onAddCard?: (columnId: string) => KanbanRow | Promise<KanbanRow> | void;
8960
- /** A predicate filter over cards; only matching cards are shown. */
8961
- filter?: (row: KanbanRow, card: KanbanCard) => boolean;
8962
- /** Quick-filter text matched case-insensitively across card fields. */
8963
- quickFilter?: string;
8964
- card?: KanbanCardMap;
8965
- readonly?: KanbanReadonly;
8966
- ariaLabel?: string;
8967
- emptyText?: string;
8968
- /** Whether card selection is enabled (default true). */
8969
- selectable?: boolean;
8970
- /** Host-localised words for the move announcements (grabbed/moved/dropped/reverted/cancelled). */
8971
- labels?: Record<string, string>;
8972
- /**
8973
- * Veto/confirm a move before any write. Return `false` (or a promise of it)
8974
- * to refuse; `from`/`to` are column ids, `index` the target position.
8975
- */
8976
- onBeforeMove?: (card: KanbanCard, from: string | null, to: string, index: number | null) => boolean | Promise<boolean>;
8977
- /**
8978
- * Persist a move on a standalone (non-grid) board. Return `false` or a
8979
- * rejected promise to revert the optimistic move. On a grid-bound board the
8980
- * grid's write-back pipeline persists instead and this is not called.
8981
- */
8982
- onCardMove?: (event: KanbanMoveEvent) => boolean | void | Promise<boolean | void>;
8983
- /** A per-card context menu: items, or `fn(card, selectedCards)` returning items. Suppresses `card:contextmenu`. */
8984
- contextMenu?: KanbanMenuItem[] | ((card: KanbanCard, selected: KanbanCard[]) => KanbanMenuItem[]);
8985
- onCardClick?: (event: KanbanEvent) => void;
8986
- onCardDblClick?: (event: KanbanEvent) => void;
8987
- onCardContextMenu?: (event: KanbanEvent) => void;
8988
- }
8989
-
8990
- /**
8991
- * Card pop-out configuration. The child view is a full composed grid (via
8992
- * `factory`, a `createGrid`), a nested board (`asBoard`), or a custom `render`.
8993
- * The child set is the rows whose `property` equals the card key, or the
8994
- * `load(card)` result. Recursion falls out: a nested board can pop its own
8995
- * children.
8996
- */
8997
- interface KanbanChildren {
8998
- /** Parent-id property linking child rows to a card within the same dataset. */
8999
- property?: string;
9000
- /** Per-card child rows, sync or async — an alternative (or addition) to `property`. */
9001
- load?: (card: KanbanCard) => KanbanRow[] | Promise<KanbanRow[]>;
9002
- /** Whether a card can be expanded, overriding the property/load inference. */
9003
- hasChildren?: (card: KanbanCard) => boolean;
9004
- /** Where the pop-out appears (default `drawer`). */
9005
- present?: 'drawer' | 'modal' | 'inline';
9006
- /** The grid factory (a `createGrid`) that builds the child grid. */
9007
- factory?: (container: HTMLElement, options: object) => { destroy?: () => void };
9008
- /** Make the child a nested board (recursive) instead of a grid. */
9009
- asBoard?: boolean;
9010
- /** Options for the child grid/board — an object or `fn(card)`. */
9011
- gridOptions?: object | ((card: KanbanCard) => object);
9012
- /** Fully custom child render; returns a cleanup function. */
9013
- render?: (container: HTMLElement, ctx: { card: KanbanCard; rows: KanbanRow[]; board: Kanban; depth: number }) => (void | (() => void));
9014
- /** The pop-out title (default the card title). */
9015
- title?: (card: KanbanCard) => string;
9016
- }
9017
-
9018
- /** One context-menu item. `action` receives the card, the selected cards, and the board. */
9019
- interface KanbanMenuItem {
9020
- label: string;
9021
- action?: (ctx: { card: KanbanCard; cards: KanbanCard[]; board: Kanban }) => void;
9022
- disabled?: boolean;
9023
- }
9024
-
9025
- /** The payload of a `card:move` (and `card:reverted`) event. */
9026
- interface KanbanMoveEvent {
9027
- keys: unknown[];
9028
- cards: KanbanCard[];
9029
- from: (string | null)[];
9030
- to: string;
9031
- index: number | null;
9032
- orders: number[] | null;
9033
- }
9034
-
9035
- /** The keyed-diff consumer surface a board shares with a grid, so a Data Router routes to it directly. */
9036
- interface KanbanRows {
9037
- apply(change: { add?: KanbanRow[]; update?: KanbanRow[]; remove?: unknown[] }): void;
9038
- forEach(fn: (row: KanbanRow, key: unknown) => void): void;
9039
- readonly count: number;
9040
- }
9041
-
9042
- /**
9043
- * Named card predicates, composed with AND (BACKLOG-0001229), following the
9044
- * grid's `filters.where` convention (BACKLOG-0001202). Several may be
9045
- * registered under different names at once; each can be replaced or removed
9046
- * without touching the others. `setFilter(fn)` is unchanged sugar for
9047
- * `where(DEFAULT, fn)` / `where(DEFAULT, null)`.
9048
- */
9049
- interface KanbanFilters {
9050
- /** The reserved name `board.setFilter` registers/removes under. */
9051
- readonly DEFAULT: string;
9052
- /** The registered names, in registration order. */
9053
- where(): string[];
9054
- /** Register or replace the predicate under `name`. */
9055
- where(name: string, predicate: (row: KanbanRow, card: KanbanCard) => boolean): Kanban;
9056
- /** Remove whatever is registered under `name`; a no-op if nothing was. */
9057
- where(name: string, predicate: null): Kanban;
9058
- /** Re-run every named predicate (or one, by name) and re-render. */
9059
- reapply(name?: string): boolean;
9060
- }
9061
-
9062
- /**
9063
- * A board instance: a kanban view of grid rows as cards grouped into columns.
9064
- * It consumes data through the same keyed-diff `rows.apply` contract a grid
9065
- * exposes, so `dataRouter.attach(value, board)` drives it like any other
9066
- * viewer.
9067
- */
9068
- interface Kanban {
9069
- readonly el: unknown | null;
9070
- readonly rowKey: string | ((row: KanbanRow) => unknown);
9071
- rows: KanbanRows;
9072
- /** The card-aging / SLA monitor, present only when a `sla` config was supplied (BACKLOG-0000960). */
9073
- sla?: KanbanSla;
9074
- columns(): KanbanColumn[];
9075
- column(id: string): KanbanColumn | undefined;
9076
- count(id: string): number;
9077
- points(id: string): number;
9078
- cards(): KanbanCard[];
9079
- card(key: unknown): KanbanCard | undefined;
9080
- on(name: string, fn: (event: KanbanEvent) => void): () => void;
9081
- off(name: string, fn: (event: KanbanEvent) => void): void;
9082
- readonly(scope?: { column?: string; card?: unknown }): boolean;
9083
- /**
9084
- * Move one or more cards to a column (and, with an order property, to a
9085
- * position within it), through the `onBeforeMove` veto and the grid's
9086
- * shipped write-back path. The single entry point behind drag-and-drop and
9087
- * keyboard move.
9088
- */
9089
- move(keys: unknown | unknown[], toColumn: string, toIndex?: number | null, toLane?: string): Promise<{ moved: unknown[]; reverted: boolean }>;
9090
- /** The selected card keys. */
9091
- selection(): unknown[];
9092
- /** Whether a card is selected. */
9093
- isSelected(key: unknown): boolean;
9094
- /** Change the selection: `set` (replace), `add`, `toggle` or `remove`. */
9095
- select(keys: unknown | unknown[], mode?: 'set' | 'add' | 'toggle' | 'remove'): Kanban;
9096
- /** Clear the selection. */
9097
- clearSelection(): Kanban;
9098
- /** Collapse, expand or toggle a column (emits `column:collapse`). */
9099
- collapseColumn(id: string, collapsed?: boolean): Kanban;
9100
- /** Collapse, expand or toggle a swimlane (emits `swimlane:collapse`). */
9101
- collapseLane(id: string, collapsed?: boolean): Kanban;
9102
- /** Reorder the columns to the given id order (emits `column:reorder`). */
9103
- reorderColumns(order: string[]): Kanban;
9104
- /** Move one column before another (or to the end); emits `column:reorder`. */
9105
- moveColumn(id: string, beforeId: string | null): Kanban;
9106
- /** Reorder the swimlanes to the given id order (emits `swimlane:reorder`). */
9107
- reorderLanes(order: string[]): Kanban;
9108
- /** Move one swimlane before another (or to the end); emits `swimlane:reorder`. */
9109
- moveLane(id: string, beforeId: string | null): Kanban;
9110
- /** Named card predicates, composed with AND (BACKLOG-0001229). See {@link KanbanFilters}. */
9111
- filters: KanbanFilters;
9112
- /** Set a predicate filter over cards, or clear it with null. Sugar for `filters.where(filters.DEFAULT, fn)`. */
9113
- setFilter(fn: ((row: KanbanRow, card: KanbanCard) => boolean) | null): Kanban;
9114
- /** Set the quick-filter text matched across card fields. Independent of every `filters.where` predicate. */
9115
- setQuickFilter(text: string): Kanban;
9116
- /** Distinct values of a property with card counts — the raw material for a facet control. */
9117
- facets(property: string): { value: unknown; count: number }[];
9118
- /** The sentinel `setSprint` value that selects the backlog (cards with no sprint). */
9119
- readonly BACKLOG: unknown;
9120
- /** Select the shown sprint (`BACKLOG` for the backlog, undefined for all); emits `sprint:changed`. */
9121
- setSprint(sprint: unknown): Kanban;
9122
- /** Show only the backlog (cards with no sprint). */
9123
- showBacklog(): Kanban;
9124
- /** Select the shown epic (undefined for all); emits `epic:changed`. */
9125
- setEpic(epic: unknown): Kanban;
9126
- /** The distinct sprint values (the switcher's options); a configured `sprints` dataset pins the order. */
9127
- sprints(): unknown[];
9128
- /** The sprint dataset as `{ id, title }` descriptors — the configured list plus any data-only sprint. */
9129
- sprintDefs(): { id: unknown; title: string }[];
9130
- /** The distinct epic values. */
9131
- epics(): unknown[];
9132
- /** Roll rows up by a property: per-bucket count, points, done and progress. */
9133
- rollup(property: string): { value: unknown; count: number; points: number; doneCount: number; donePoints: number; progress: number }[];
9134
- /** The epic rollup (empty when no epic property is configured). */
9135
- epicRollup(): { value: unknown; count: number; points: number; doneCount: number; donePoints: number; progress: number }[];
9136
- /** Whether a card can be expanded to a child pop-out. */
9137
- canExpand(card: KanbanCard): boolean;
9138
- /** Open a card's children in a pop-out (drawer/modal/inline); emits `card:expand`/`card:drill`. */
9139
- expand(key: unknown): Promise<object | null>;
9140
- /** Close any open card pop-out. */
9141
- closeDetail(): Kanban;
9142
- /** Whether a mapped card field is opted into inline edit and writable. */
9143
- isFieldEditable(name: string): boolean;
9144
- /** Start inline editing a card's field (the grid's own field editor when bound); no-op headless. */
9145
- editCard(key: unknown, name?: string): object | null;
9146
- /** Commit an inline edit through the write-back path (grid.edit.setCells when bound); emits `card:edit`. */
9147
- applyEdit(key: unknown, name: string, value: unknown): Promise<boolean>;
9148
- /**
9149
- * Add a card to a column and open it in inline edit; emits `card:add`.
9150
- * Returns the new key directly, or a Promise of it when `onAddCard`
9151
- * returns a Promise or a `beforeAdd` handler defers (BACKLOG-0001230); a
9152
- * rejected `onAddCard` Promise resolves this to `null` with no card added.
9153
- */
9154
- addCard(columnId: string, seed?: KanbanRow): unknown | Promise<unknown>;
9155
- /** Serialise the restorable state: collapsed columns/lanes, order, filter, sprint/epic, selection. */
9156
- getState(): object;
9157
- /** Restore a state snapshot from {@link Kanban#getState}. */
9158
- setState(snapshot: object): Kanban;
9159
- /** Mark the board loading (renders a host-localised loading state). */
9160
- setLoading(loading: boolean): Kanban;
9161
- /** Set (or clear with null) an error state, rendered as a host-supplied message. */
9162
- setError(message: string | null): Kanban;
9163
- setRows(rows: KanbanRow[]): Kanban;
9164
- /**
9165
- * Replace the board's configured column set (BACKLOG-0001228). Keeps card
9166
- * placement and interaction state (collapsed columns, column order, quick
9167
- * filter, selection) for every column id that survives; a dropped id is
9168
- * not specially handled — a card whose value has nowhere configured to go
9169
- * re-derives an ad hoc column rather than becoming `unplaced` (the same
9170
- * "never silently drop a card" rule an unconfigured value already gets).
9171
- */
9172
- setColumns(defs: KanbanColumnDef[]): Kanban;
9173
- refresh(): Kanban;
9174
- destroy(): void;
9175
- }
9176
-
9177
- /**
9178
- * Create a board (kanban) view of rows, grouped into columns by a configurable
9179
- * property. Pass a DOM element to render into, or `null` for a headless board
9180
- * that computes the same column/card model without a DOM.
9181
- */
9182
- export function createKanban(el: HTMLElement | null, config?: KanbanConfig): Kanban;
9183
- export default createKanban;
9184
- }
9185
-
9186
- declare module 'lattice-grid/modules/kpi' {
9187
- /** A row backing a KPI aggregate: any object. Its identity comes from `rowKey`. */
9188
- type KPIRow = Record<string, unknown>;
9189
-
9190
- /** The aggregation kinds a tile can compute. `custom` is a host reducer over the rows. */
9191
- type KPIAggregation = 'sum' | 'avg' | 'min' | 'max' | 'count' | 'countDistinct' | 'custom';
9192
-
9193
- /** Number formatting for a tile value. `percent` treats the value as a ratio (0.42 → 42%). */
9194
- type KPIFormat =
9195
- | 'number' | 'currency' | 'percent' | 'compact'
9196
- | { type?: 'number' | 'currency' | 'percent' | 'compact'; decimals?: number; currency?: string; locale?: string };
9197
-
9198
- /**
9199
- * A semantic threshold: two cut points and a direction. `higherIsBetter` (the
9200
- * default) makes a value at/above `warn` good, at/above `critical` a warning,
9201
- * below it critical; `lowerIsBetter` mirrors it. Colour is a host concern.
9202
- */
9203
- interface KPIThresholds {
9204
- warn: number;
9205
- critical: number;
9206
- direction?: 'higherIsBetter' | 'lowerIsBetter';
9207
- }
9208
-
9209
- /** An explicit band: the `status` of the first band whose half-open `[min, max)` contains the value. */
9210
- interface KPIBand {
9211
- min?: number;
9212
- max?: number;
9213
- status: 'good' | 'warn' | 'critical';
9214
- }
9215
-
9216
- /** An optional sparkline series: the `y` field plotted in order of the `x` field (or insertion). */
9217
- interface KPISparkline {
9218
- x?: string;
9219
- y: string | ((row: KPIRow) => unknown);
9220
- }
9221
-
9222
- /** One tile: an aggregate over the routed rows, with optional filter, format, threshold and trend. */
9223
- interface KPITile {
9224
- /** A stable identity for the tile (defaults to the label, then the index). */
9225
- id?: string;
9226
- /** The tile's accessible label. */
9227
- label?: string;
9228
- /** The aggregation kind, or a reducer `(rows, tile) => value` for a custom tile. */
9229
- aggregation?: KPIAggregation | ((rows: KPIRow[], tile: object) => unknown);
9230
- /** The reducer for a `custom` aggregation, when `aggregation` is the string `'custom'`. */
9231
- compute?: (rows: KPIRow[], tile: object) => unknown;
9232
- /** The field the aggregation reads (a path or accessor). Ignored by `count`. */
9233
- field?: string | ((row: KPIRow) => unknown);
9234
- /** A predicate limiting the rows this tile aggregates. */
9235
- filter?: (row: KPIRow) => boolean;
9236
- /** Value formatting. */
9237
- format?: KPIFormat;
9238
- /** A comparison target rendered alongside the value. */
9239
- target?: number;
9240
- /** A baseline the tile's delta is measured against. */
9241
- baseline?: number;
9242
- /** Threshold bands, either two cut points or an explicit band list. */
9243
- thresholds?: KPIThresholds;
9244
- /** Explicit status bands (an alternative to `thresholds`). */
9245
- bands?: KPIBand[];
9246
- /** A trend sparkline series. */
9247
- sparkline?: KPISparkline | string;
9248
- }
9249
-
9250
- /**
9251
- * The hierarchy a KPI panel arranges its tiles into (BACKLOG-0001059): a rail
9252
- * of top-level items that expand to the indicators beneath them, each parent
9253
- * highlighted with the worst status below it.
9254
- *
9255
- * The shape is declared with `path` or `parentKey` — the same two shapes the
9256
- * grid's tree data and the tree-select editor take — over the **tile specs**,
9257
- * not the rows. With neither declared, one is derived by splitting the tile
9258
- * ids on `separator`, so `system.compute.cpu` files itself under Compute
9259
- * under System. A panel whose ids carry no separator stays flat, and `false`
9260
- * keeps it flat whatever they look like.
9261
- *
9262
- * A tile's `field` is never a source: a dot there already means a nested
9263
- * object property.
9264
- */
9265
- interface KPITreeConfig {
9266
- /** The tile's own place in the hierarchy, its own segment last. */
9267
- path?: (tile: KPITile) => (string | number)[];
9268
- /** The id of the tile this one sits under, or a reader for it. */
9269
- parentKey?: string | ((tile: KPITile) => unknown);
9270
- /** The heading tiles whose parent is not in the panel are gathered under. */
9271
- orphans?: 'root' | string;
9272
- /** The separator a derived hierarchy splits a tile id on. Defaults to `.`. */
9273
- separator?: string;
9274
- /** Which branches start open: every one (`true`), or these node keys. */
9275
- expanded?: true | string[];
9276
- }
9277
-
9278
- /**
9279
- * One node of the rail.
9280
- *
9281
- * **No value rolls up.** `value` and `formatted` are the node's own tile's
9282
- * reading, and are `null` on a level the hierarchy synthesised, because the
9283
- * running accumulators cannot be composed without a rescan.
9284
- *
9285
- * **Severity does.** `rollup` is the worst status at or below the node, which
9286
- * is what a collapsed branch reports. `unknown` is excluded from it on
9287
- * purpose — ranking "nothing was measured" as the worst would hide a real
9288
- * warning underneath it — and is surfaced as `unknown`, a count of the
9289
- * descendants that measured nothing, so neither can pass unnoticed.
9290
- */
9291
- interface KPINodeModel {
9292
- /** The node's stable identity: the tile id, or the path of a synthesised level. */
9293
- key: string;
9294
- /** The tile id, or null on a synthesised level. */
9295
- id: string | null;
9296
- label: string;
9297
- /** Depth, 0 at the top level. */
9298
- level: number;
9299
- /** Its place among its siblings, from 1, and how many there are. */
9300
- posinset: number;
9301
- setsize: number;
9302
- hasChildren: boolean;
9303
- expanded: boolean;
9304
- children: KPINodeModel[];
9305
- /** The node's own tile, or null on a synthesised level. */
9306
- tile: KPITileModel | null;
9307
- value: unknown;
9308
- formatted: string | null;
9309
- /** The node's own status. */
9310
- status: 'good' | 'warn' | 'critical' | 'unknown' | null;
9311
- /** The worst status at or below the node. Never `unknown`. */
9312
- rollup: 'good' | 'warn' | 'critical' | null;
9313
- /** How many tiles at or below the node measured nothing. */
9314
- unknown: number;
9315
- /** How many tiles are at or below the node. */
9316
- items: number;
9317
- }
9318
-
9319
- /** A computed tile, as it appears in the model. */
9320
- interface KPITileModel {
9321
- id: string;
9322
- label: string;
9323
- aggregation: string;
9324
- field?: string;
9325
- value: unknown;
9326
- formatted: string;
9327
- /**
9328
- * The tile's semantic band, or `unknown` when the tile measured nothing.
9329
- * `unknown` is decided from data presence before any threshold is
9330
- * consulted: an aggregation over nothing returns the identity of its
9331
- * operation (`sum` and `count` return 0), and 0 is a number a threshold
9332
- * grades, so without it an empty panel would report as a healthy one.
9333
- *
9334
- * Two things make a tile `unknown`: the panel holds no rows at all, or the
9335
- * tile's `field` names no column on the bound grid, so it never read a cell
9336
- * to reduce over. A tile whose `filter` matches none of the rows the panel
9337
- * *does* hold is neither — it has measured a real zero and is banded
9338
- * normally. `null` means the tile has no thresholds or bands configured.
9339
- */
9340
- status: 'good' | 'warn' | 'critical' | 'unknown' | null;
9341
- target?: number;
9342
- baseline?: number;
9343
- delta: number | null;
9344
- deltaPercent: number | null;
9345
- deltaFormatted?: string;
9346
- count: number;
9347
- sparkline: number[] | null;
9348
- }
9349
-
9350
- /** The payload every tile event carries. */
9351
- interface KPIEvent {
9352
- tile: KPITileModel;
9353
- id: string;
9354
- originalEvent?: unknown;
9355
- }
9356
-
9357
- /** KPI panel configuration. */
9358
- interface KPIConfig {
9359
- rows?: KPIRow[];
9360
- grid?: unknown;
9361
- rowKey?: string | ((row: KPIRow) => unknown);
9362
- /**
9363
- * Extra columns of the bound `grid` to project onto the rows a tile `filter`
9364
- * sees, beyond the fields the tiles themselves declare. A grid-bound panel
9365
- * hands a filter a projection, not a whole grid row, so a filter over a
9366
- * column no tile names would otherwise read `undefined` and report a
9367
- * confident zero. Ignored on a panel over a plain `rows` array.
9368
- */
9369
- fields?: string[];
9370
- tiles?: KPITile[];
9371
- columns?: number;
9372
- ariaLabel?: string;
9373
- nullText?: string;
9374
- /** Arrange the tiles as a hierarchy; `false` keeps the panel flat. */
9375
- tree?: KPITreeConfig | false;
9376
- /**
9377
- * The catalogue the panel's own text is read from. A panel routinely has no
9378
- * grid to borrow one off — two of its three input modes have none — so this
9379
- * is the first-class way to translate it. A grid's own `messages` satisfies
9380
- * the shape; a key it does not carry falls back to English.
9381
- */
9382
- messages?: { t(key: string, params?: Record<string, unknown>): string };
9383
- onTileClick?: (event: KPIEvent) => void;
9384
- onTileDblClick?: (event: KPIEvent) => void;
9385
- onTileContextMenu?: (event: KPIEvent) => void;
9386
- onNodeToggle?: (event: { key: string; expanded: boolean; node?: KPINodeModel }) => void;
9387
- onChange?: (event: { model: { tiles: KPITileModel[]; nodes?: KPINodeModel[] } }) => void;
9388
- }
9389
-
9390
- /** The keyed-diff consumer surface a KPI panel shares with a grid, so a Data Router routes to it directly. */
9391
- interface KPIRows {
9392
- apply(change: { add?: KPIRow[]; update?: KPIRow[]; remove?: unknown[] }): void;
9393
- forEach(fn: (row: KPIRow, key: unknown) => void): void;
9394
- readonly count: number;
9395
- }
9396
-
9397
- /**
9398
- * A KPI / stat-tile panel: a grid of aggregate tiles over a dataset. It
9399
- * consumes data through the same keyed-diff `rows.apply` contract a grid
9400
- * exposes, so `dataRouter.attach(value, kpi)` drives it like any other viewer,
9401
- * updating each tile incrementally from the routed delta.
9402
- */
9403
- interface KPI {
9404
- readonly el: unknown | null;
9405
- readonly rowKey: string | ((row: KPIRow) => unknown);
9406
- /** Whether the panel renders as a hierarchy rather than a flat tile grid. */
9407
- readonly tree: boolean;
9408
- rows: KPIRows;
9409
- tiles(): KPITileModel[];
9410
- tile(id: string): KPITileModel | undefined;
9411
- value(id: string): unknown;
9412
- /** The top-level nodes of the hierarchy. Empty on a flat panel. */
9413
- nodes(): KPINodeModel[];
9414
- /** One node by its key, at any depth. */
9415
- node(key: string): KPINodeModel | undefined;
9416
- /** The nodes on screen: the roots, plus the children of every open branch. */
9417
- visibleNodes(): KPINodeModel[];
9418
- expand(key: string): KPI;
9419
- collapse(key: string): KPI;
9420
- toggle(key: string): KPI;
9421
- setRows(rows: KPIRow[]): KPI;
9422
- refresh(): KPI;
9423
- getState(): object;
9424
- setState(snapshot: object): KPI;
9425
- on(name: string, fn: (event: KPIEvent) => void): () => void;
9426
- off(name: string, fn: (event: KPIEvent) => void): void;
9427
- destroy(): void;
9428
- }
9429
-
9430
- /**
9431
- * Create a KPI / stat-tile panel over rows or a bound grid. Pass a DOM element
9432
- * to render into, or `null` for a headless panel that computes the same tile
9433
- * model without a DOM.
9434
- */
9435
- export function createKPI(el: HTMLElement | null, config?: KPIConfig): KPI;
9436
- export default createKPI;
9437
- }
9438
-
9439
- declare module 'lattice-grid/modules/ai' {
9440
- /**
9441
- * The provider-agnostic model callback the host supplies (BACKLOG-0000965).
9442
- * The module never imports a provider SDK, reads a key, or makes a network
9443
- * call — it builds this payload and awaits the host's reply. A host may wrap a
9444
- * chat provider (`{ text }`), a completion (a bare string), a tool-calling turn
9445
- * (`{ toolCalls }`), or a structured provider (`{ structured }`).
9446
- */
9447
- type AIAsk = (payload: {
9448
- /** The narrate-only system instruction. */
9449
- system: string;
9450
- /** The single user message: the facts block and the ask. */
9451
- message: string;
9452
- /** System and message joined, for a completion-shaped provider. */
9453
- prompt: string;
9454
- /** The running chat, including any tool results, for a chat-shaped provider. */
9455
- messages: Array<{ role: string; content: string; [k: string]: unknown }>;
9456
- /** The read-only tool definitions, present only on the tool-use path. */
9457
- tools?: object[];
9458
- /** The grid's generated schema (no row values). */
9459
- schema?: unknown;
9460
- /** An abort signal the host should honour. */
9461
- signal?: AbortSignal;
9462
- }) => Promise<
9463
- | string
9464
- | { text?: string; content?: string; toolCalls?: object[]; structured?: unknown }
9465
- >;
9466
-
9467
- /** A single computed figure a narrative is grounded on. */
9468
- interface AIFact {
9469
- id: string;
9470
- label: string;
9471
- /** The raw numeric value, or null for a context-only fact. */
9472
- value: number | null;
9473
- /** The pre-formatted display string the model is told to use verbatim. */
9474
- display: string;
9475
- kind: string;
9476
- colId?: string;
9477
- }
9478
-
9479
- /**
9480
- * A narrative target. `view` narrates the current filtered view; `column`
9481
- * narrates one column's profile; `forecast` adds its projection; `kpi`/`chart`
9482
- * narrate figures the caller passes through in `facts`; `risk` assembles a
9483
- * project RISK SUMMARY from the separate Gantt / Kanban modules' public outputs
9484
- * (BACKLOG-0000979).
9485
- */
9486
- interface AITarget {
9487
- kind?: 'view' | 'column' | 'forecast' | 'kpi' | 'chart' | 'risk';
9488
- colId?: string;
9489
- /** Forecast options, for `kind: 'forecast'`. */
9490
- options?: object;
9491
- /** Caller-supplied figures for a KPI/chart Explain, grounded like the rest. */
9492
- facts?: Array<{ id?: string; label: string; value: unknown; display?: string; kind?: string; colId?: string }>;
9493
- /**
9494
- * For `kind: 'risk'`: a Gantt instance (from `createGantt`). Read duck-typed
9495
- * for `earnedValue()` (SPI/CPI/variances) and `schedule` (critical path,
9496
- * float). The AI bundle never imports the Gantt module.
9497
- */
9498
- gantt?: unknown;
9499
- /**
9500
- * For `kind: 'risk'`: a Kanban board (from `createKanban`). Read for its
9501
- * `board.sla` monitor (breach / warning counts). The AI bundle never imports
9502
- * the Kanban module.
9503
- */
9504
- board?: unknown;
9505
- /** For `kind: 'risk'`: an SLA monitor, if not reached through `board`. */
9506
- sla?: unknown;
9507
- /** For `kind: 'risk'`: a precomputed `gantt.earnedValue()` result. */
9508
- earnedValue?: object;
9509
- /** For `kind: 'risk'`: a precomputed `gantt.schedule` result. */
9510
- schedule?: object;
9511
- /** For `kind: 'risk'`: precomputed SLA breach states. */
9512
- breaches?: object[];
9513
- /** For `kind: 'risk'`: precomputed SLA warning states. */
9514
- warnings?: object[];
9515
- /** For `kind: 'risk'`: options passed to `gantt.earnedValue()`. */
9516
- evmOptions?: object;
9517
- /**
9518
- * For `kind: 'risk'`: expose the at-risk task NAMES (off by default — a risk
9519
- * summary carries aggregates only unless the host opts in).
9520
- */
9521
- includeTaskNames?: boolean;
9522
- /**
9523
- * For `kind: 'risk'`: expose the money figures BAC/PV/EV/AC (off by default).
9524
- */
9525
- includeCost?: boolean;
9526
- /** For `kind: 'risk'`: cap on named at-risk tasks (default 10). */
9527
- maxTasks?: number;
9528
- }
9529
-
9530
- /** The facts packet a narrative grounds on. */
9531
- interface AIFactsPacket {
9532
- target: AITarget;
9533
- facts: AIFact[];
9534
- /** The numeric values seeding the reconciliation registry. */
9535
- groundedValues: number[];
9536
- meta: {
9537
- kind: string; filtered: boolean; factCount: number; redacted?: boolean; colId?: string;
9538
- /** For `kind: 'risk'`: which module sources resolved. */
9539
- sources?: { schedule: boolean; earnedValue: boolean; sla: boolean };
9540
- /** For `kind: 'risk'`: which opt-in exposures were honoured. */
9541
- exposed?: { taskNames: boolean; cost: boolean };
9542
- };
9543
- }
9544
-
9545
- /**
9546
- * The risk facts a board / Gantt risk summary grounds on (BACKLOG-0000979),
9547
- * from {@link buildRiskFacts}: the facts plus which module sources resolved and
9548
- * which opt-in exposures (task names, cost) were honoured.
9549
- */
9550
- interface AIRiskFacts {
9551
- facts: AIFact[];
9552
- meta: {
9553
- kind: 'risk';
9554
- sources: { schedule: boolean; earnedValue: boolean; sla: boolean };
9555
- exposed: { taskNames: boolean; cost: boolean };
9556
- };
9557
- }
9558
-
9559
- /** The result of a narrative: reconciled prose plus what grounded and what did not. */
9560
- interface AINarrative {
9561
- /** The narrative, with every ungrounded figure stripped (or flagged). */
9562
- text: string;
9563
- facts: AIFact[];
9564
- /** The figures that reconciled against a computed value. */
9565
- grounded: string[];
9566
- /** The figures removed as ungrounded. */
9567
- flagged: string[];
9568
- packet: AIFactsPacket;
9569
- /** How many ask() rounds ran (>1 only on the tool-use path). */
9570
- rounds: number;
9571
- mode: 'tools' | 'packet';
9572
- }
9573
-
9574
- /** AI module configuration. */
9575
- interface AIConfig {
9576
- /** The host's model callback. Falls back to the grid's `ai.ask` when omitted. */
9577
- ask?: AIAsk;
9578
- /** Opt into specific features: `'narrative'`, `'insights'`, `'query'`/`'ask'`. All on when omitted. */
9579
- enable?: string[];
9580
- /**
9581
- * Ask-your-data: apply a safe (read-only) query result without a confirm
9582
- * step. Off by default — the resolved query is shown and waits for Apply.
9583
- */
9584
- autoApply?: boolean;
9585
- /**
9586
- * A Data Router instance; on applying a query the answer rows are fanned to
9587
- * its attached viewers (grid + chart + KPI together) via `load()`.
9588
- */
9589
- router?: unknown;
9590
- /** Budgets passed to the schema builder for ask-your-data. */
9591
- schemaOptions?: object;
9592
- /** Extra context passed through to `ask()`. */
9593
- context?: unknown;
9594
- /** Called with each ask-your-data result. */
9595
- onQuery?: (result: AIQueryResult) => void;
9596
- /** Called with each governed-actor proposal (Play C), before any approval. */
9597
- onProposal?: (result: AIProposal) => void;
9598
- /**
9599
- * A Kanban board (from `createKanban`) the governed actor writes moves
9600
- * through: an NL card move applies via the board's own `beforeMove` gate
9601
- * (BACKLOG-0000967), never a kanban-specific write bypass.
9602
- */
9603
- board?: unknown;
9604
- /** Cap on rows any tool result carries to `ask()`. */
9605
- maxRows?: number;
9606
- /** Columns whose values must never leave the browser. */
9607
- redact?: string | string[] | ((colId: string) => boolean);
9608
- /** Force tool-use on or off; auto-detected from how `ask` was supplied otherwise. */
9609
- tools?: boolean;
9610
- /** Locale for figure formatting. */
9611
- locale?: string;
9612
- /** Column cap for a view summary. */
9613
- maxColumns?: number;
9614
- /** What to do with an ungrounded figure: `'strip'` (default) or `'flag'`. */
9615
- reconcile?: 'strip' | 'flag';
9616
- /** An element to mount the insights panel into. */
9617
- element?: HTMLElement;
9618
- /** Called when a narrative is produced. */
9619
- onNarrative?: (result: AINarrative) => void;
9620
- /** Called when `ask()` errors; the grid stays usable. */
9621
- onError?: (error: { error: unknown; target: AITarget }) => void;
9622
- }
9623
-
9624
- /** The report from applying an ask-your-data query. */
9625
- interface AIApplyReport {
9626
- ok: boolean;
9627
- /** The action types that were applied. */
9628
- applied: string[];
9629
- /** Actions that threw while applying. */
9630
- failed: Array<{ type: string; reason: string }>;
9631
- /** Actions refused by the read-only gate — a mutation is never applied. */
9632
- refused: Array<{ type: string; reason: string }>;
9633
- /** How many answer rows were fanned to a router's viewers. */
9634
- fannedOut: number;
9635
- }
9636
-
9637
- /**
9638
- * The result of an ask-your-data question (BACKLOG-0000966): a validated,
9639
- * READ-ONLY query spec — never rows — that the host reviews before applying.
9640
- */
9641
- interface AIQueryResult {
9642
- /** True when the spec is safe to apply: at least one read, nothing unsafe. */
9643
- ok: boolean;
9644
- /** The user's question. */
9645
- question: string;
9646
- /** The core plan (from `grid.ai.plan`). */
9647
- plan: Record<string, unknown>;
9648
- /** The read-only actions that will run — the validated query spec. */
9649
- actions: object[];
9650
- /** Actions refused as not read-only (a mutation the model asked for). */
9651
- unsafe: Array<{ type: string; reason: string }>;
9652
- /** Parts the core validator dropped (unknown column, bad operator, …). */
9653
- rejected: Array<{ at: string; what: string; reason: string }>;
9654
- /** The model's own one-line summary, if any. */
9655
- explain: string;
9656
- /** The validated query spec as data. */
9657
- spec: { actions: object[] };
9658
- /** The apply report once applied, or null. */
9659
- applied: AIApplyReport | null;
9660
- /** The resolved query in one human sentence, from the validated spec. */
9661
- describe(): string;
9662
- /** Apply the query (re-gated), fanning the answer to a router if configured. */
9663
- apply(opts?: { router?: unknown; onResult?: (rows: object[]) => void }): AIApplyReport;
9664
- }
9665
-
9666
- /** One before/after change in a governed-actor proposal (BACKLOG-0000967). */
9667
- interface AIDiffEntry {
9668
- /** The target row key. */
9669
- key: string;
9670
- /** A human label identifying the row (a name-like column, else the key). */
9671
- rowLabel: string;
9672
- /** The target column id. */
9673
- colId: string;
9674
- /** The column's title, for the diff header. */
9675
- colTitle: string;
9676
- /** The current stored value. */
9677
- oldValue: unknown;
9678
- /** The current value as shown (a lookup id mapped to its label). */
9679
- oldDisplay: string;
9680
- /** The proposed stored value (a label resolved to its option id). */
9681
- newValue: unknown;
9682
- /** The proposed value as shown. */
9683
- newDisplay: string;
9684
- }
9685
-
9686
- /**
9687
- * A governed-actor proposal (Play C, BACKLOG-0000967): the model's structured
9688
- * edits, VALIDATED and resolved against the current view — never written until
9689
- * a human approves. `apply()` writes ONLY through the grid's own gate.
9690
- */
9691
- interface AIProposal {
9692
- /** True when there is at least one applicable change and nothing needs a pick first. */
9693
- ok: boolean;
9694
- /** The user's instruction. */
9695
- instruction: string;
9696
- /** `'view'` (the filtered set, the default) or `'all'` (an opted-in widen). */
9697
- scope: 'view' | 'all';
9698
- /** How many rows the scope covers. */
9699
- scopeCount: number;
9700
- /** The scope in words, always stated in the confirm/diff. */
9701
- scopeText: string;
9702
- /** Whether any proposal was a bulk (`scope:'view'`) edit. */
9703
- bulk: boolean;
9704
- /** The before/after diff — exactly what would change. Nothing is written yet. */
9705
- diff: AIDiffEntry[];
9706
- /** Proposals refused before apply (unknown column, unknown label, bad type/range, no match). */
9707
- rejected: Array<{ reason: string; [k: string]: unknown }>;
9708
- /** Matches needing a human pick (>1 row for one phrase), with candidates. */
9709
- ambiguous: Array<{ reason: string; candidates: Array<{ key: string; label: string }>; [k: string]: unknown }>;
9710
- /** Named targets found only outside the view, offered for an opt-in widen. */
9711
- outOfView: Array<{ reason: string; candidates: Array<{ key: string; label: string }>; [k: string]: unknown }>;
9712
- /** Matches whose value already equals the ask (nothing to change). */
9713
- noops: Array<{ reason: string; [k: string]: unknown }>;
9714
- /** The apply report once applied, or null. */
9715
- applied: AIProposalReport | null;
9716
- /** The proposal in one human sentence, always stating the scope. */
9717
- describe(): string;
9718
- /** Apply the approved diff through the gate (`beforeEdit`, or `beforeMove` for a board). */
9719
- apply(opts?: { board?: unknown }): Promise<AIProposalReport>;
9720
- }
9721
-
9722
- /** The report from applying a governed-actor proposal. */
9723
- interface AIProposalReport {
9724
- /** True when at least one edit landed. */
9725
- ok: boolean;
9726
- /** How many edits landed through the gate. */
9727
- applied: number;
9728
- /** How many edits were attempted. */
9729
- requested: number;
9730
- /** How many were stopped by a before-handler veto. */
9731
- vetoed: number;
9732
- /** Which gated path applied them: `'setCells'`, `'board.move'`, or `'none'`. */
9733
- via: string;
9734
- }
9735
-
9736
- /**
9737
- * An AI controller over a live grid. It explains the grid's computed figures
9738
- * (Play A), answers questions with validated read-only query specs (Play B),
9739
- * and PROPOSES governed edits a human approves and the grid's own gate applies
9740
- * (Play C). `grid.ai` (in core) is the complementary intent/plan skill layer
9741
- * this consumes.
9742
- */
9743
- interface AI {
9744
- /** The mounted insights panel element, or null. */
9745
- readonly el: HTMLElement | null;
9746
- /** Whether a usable `ask()` is configured. */
9747
- readonly ready: boolean;
9748
- /** Produce a grounded, reconciled narrative for a target. */
9749
- explain(target?: AITarget, opts?: object): Promise<AINarrative>;
9750
- /** An alias for {@link AI.explain}. */
9751
- narrate(target?: AITarget, opts?: object): Promise<AINarrative>;
9752
- /**
9753
- * Produce a grounded, reconciled board / Gantt RISK SUMMARY
9754
- * (BACKLOG-0000979): a plain-language reading like "3 tasks at risk on the
9755
- * critical path, SPI 0.67, 2 SLA breaches". A convenience over
9756
- * `explain({ kind: 'risk', ... })`; the module sources go in `sources`
9757
- * (`gantt`, `board`/`sla`, or precomputed outputs). Every figure runs through
9758
- * the same reconciliation guard as {@link AI.explain}.
9759
- */
9760
- riskSummary(sources?: {
9761
- gantt?: unknown; board?: unknown; sla?: unknown;
9762
- earnedValue?: object; schedule?: object; breaches?: object[]; warnings?: object[];
9763
- includeTaskNames?: boolean; includeCost?: boolean; maxTasks?: number; evmOptions?: object;
9764
- }, opts?: object): Promise<AINarrative>;
9765
- /** Mount (or re-target) the insights panel into an element. */
9766
- insights(el?: HTMLElement, opts?: object): AI;
9767
- /** Build an "Explain" button bound to a target. */
9768
- attachExplain(target: AITarget, opts?: object): HTMLElement | null;
9769
- /** Build the facts packet for a target without calling `ask()`. */
9770
- facts(target?: AITarget, opts?: object): AIFactsPacket;
9771
- /**
9772
- * Ask-your-data: turn a question into a validated, read-only query spec, run
9773
- * it in the engine, and (on apply) fan the answer to router-attached viewers.
9774
- * Returns a result the host reviews; `autoApply` applies a safe read for you.
9775
- */
9776
- query(question: string, opts?: {
9777
- autoApply?: boolean; router?: unknown; schemaOptions?: object;
9778
- context?: unknown; tools?: boolean; signal?: AbortSignal;
9779
- onResult?: (rows: object[]) => void;
9780
- }): Promise<AIQueryResult>;
9781
- /** Apply a reviewed query result (the confirm path); re-gated at the seam. */
9782
- applyQuery(result: AIQueryResult, opts?: { router?: unknown; onResult?: (rows: object[]) => void }): AIApplyReport;
9783
- /** Mount the ask-your-data bar (input, Ask, auto-apply toggle, preview, Apply/Discard). */
9784
- askBar(el?: HTMLElement, opts?: object): AI;
9785
- /**
9786
- * Governed actor (Play C): ask the model for structured edit PROPOSALS over
9787
- * the current view, validate and resolve them (label -> stored value, locate
9788
- * a named row, reject unknown columns/labels/out-of-range), and return a
9789
- * reviewable {@link AIProposal} with a before/after diff. NOTHING is written
9790
- * — the model proposes; a human approves.
9791
- */
9792
- propose(instruction: string, opts?: {
9793
- widen?: boolean; board?: unknown; schemaOptions?: object; maxRows?: number;
9794
- context?: unknown; redact?: string | string[] | ((colId: string) => boolean);
9795
- signal?: AbortSignal;
9796
- }): Promise<AIProposal>;
9797
- /**
9798
- * Apply an approved proposal — the human-approval step. Writes ONLY through
9799
- * the gate: a grid cell edit via `grid.edit.setCells({ origin: 'ai' })` (the
9800
- * `beforeEdit` veto), a kanban move via `board.move({ origin: 'ai' })` (the
9801
- * `beforeMove` veto). A vetoing host handler stops the write.
9802
- */
9803
- applyProposal(result: AIProposal, opts?: { board?: unknown }): Promise<AIProposalReport>;
9804
- /**
9805
- * Mount the governed-actor bar: an instruction input, Propose, a before/after
9806
- * diff preview stating the scope, and Approve/Discard. Approve applies
9807
- * through the gate.
9808
- */
9809
- actorBar(el?: HTMLElement, opts?: object): AI;
9810
- on(name: 'narrative' | 'query' | 'proposal' | 'error' | string, fn: (payload: object) => void): () => void;
9811
- off(name: string, fn: (payload: object) => void): void;
9812
- destroy(): void;
9813
- }
9814
-
9815
- /**
9816
- * Create an AI narrative / insights controller over a live grid. The grid may
9817
- * be headless or rendered; the module grounds every figure on the grid's
9818
- * engine and calls only the host's `ask()`.
9819
- */
9820
- export function createAI(grid: unknown, config?: AIConfig): AI;
9821
-
9822
- /**
9823
- * Build the RISK-SUMMARY facts packet (BACKLOG-0000979) from the separate
9824
- * Gantt / Kanban modules' public outputs — SPI/CPI and variances from
9825
- * `gantt.earnedValue()`, tasks at risk / on the critical path from
9826
- * `gantt.schedule`, and SLA breaches from `board.sla`. Reads the module
9827
- * instances (or their precomputed outputs) duck-typed off `target`; the AI
9828
- * bundle imports neither module. This is the exact grounded set
9829
- * `explain({ kind: 'risk' })` would use, exposed for preview and testing.
9830
- */
9831
- export function buildRiskFacts(target: AITarget, opts?: {
9832
- locale?: string; fmt?: (value: number) => string;
9833
- }): AIRiskFacts;
9834
-
9835
- export default createAI;
9836
- }
9837
-
9838
- declare module 'lattice-grid/modules/tabs' {
9839
- /**
9840
- * One tab: an id, a display label, a grid config, and — for a derived tab —
9841
- * the parent tab id plus the narrowing forwarded onto the derived source
9842
- * built for it (`source: { mode: 'derived', from: <parent's grid>, ... }`).
9843
- * The derivation keys are the ones `packages/core/src/source/derive.js`
9844
- * already understands; this module invents none of its own.
9845
- */
9846
- interface TabDescriptor {
9847
- /** A stable, unique id. Required. */
9848
- id: string;
9849
- /** The tab button's text. Defaults to `id`. */
9850
- label?: string;
9851
- /** The grid config passed to `createGrid` for this tab (merged with the derived `source`, when `from` is set). */
9852
- config?: object;
9853
- /** The parent tab id to derive from. When set, `config.source` is built for you and any of your own is replaced (with a warning). */
9854
- from?: string;
9855
- /** Row predicate forwarded to the derived source. */
9856
- where?: (row: unknown) => boolean;
9857
- /** Group-by forwarded to the derived source. */
9858
- group?: unknown;
9859
- groupBy?: unknown;
9860
- /** Time-bucketing forwarded to the derived source. */
9861
- bucket?: unknown;
9862
- /** Join spec forwarded to the derived source. */
9863
- join?: unknown;
9864
- /** Array-field unnesting forwarded to the derived source. */
9865
- unnest?: unknown;
9866
- /** `'live' | 'idle' | 'manual' | number` forwarded to the derived source. */
9867
- refresh?: 'live' | 'idle' | 'manual' | number;
9868
- /** Cross-filter wiring forwarded to the derived source. */
9869
- crossFilter?: unknown;
9870
- /** Which slice of the parent's rows to derive from: `'filtered' | 'all' | 'selected' | 'grouped'`. */
9871
- follow?: 'filtered' | 'all' | 'selected' | 'grouped';
9872
- /** Row limit forwarded to the derived source. */
9873
- limit?: number;
9874
- /** Sort forwarded to the derived source. */
9875
- sort?: unknown;
9876
- /** Statistical-profile derivation, forwarded to the derived source. */
9877
- profile?: unknown;
9878
- /** This tab's panel's own `aria-label`, when the label alone is not enough context. */
9879
- ariaLabel?: string;
9880
- }
9881
-
9882
- /** The payload every tab-change event carries. */
9883
- interface TabChangeEvent {
9884
- id: string;
9885
- previousId: string | null;
9886
- origin?: 'api' | 'user' | 'init';
9887
- reason?: string | null;
9888
- /** Cancel the switch (only meaningful on `beforeTabChange`). */
9889
- preventDefault?: (reason?: string) => void;
9890
- defaultPrevented?: boolean;
9891
- }
9892
-
9893
- /** Tabbed-grid configuration. */
9894
- interface TabsConfig {
9895
- /** The grid factory to mount each tab with, e.g. `import { createGrid } from 'lattice-grid'`. Required. */
9896
- createGrid: (el: HTMLElement, config: object) => unknown;
9897
- /** The tabs, in display order. Required, at least one. */
9898
- tabs: TabDescriptor[];
9899
- /** The initially active tab id. Defaults to the first tab. */
9900
- active?: string;
9901
- /** The tablist landmark's accessible name. */
9902
- ariaLabel?: string;
9903
- /** An explicit message-catalogue override; otherwise a mounted tab's own `grid.messages` is used. */
9904
- messages?: { t(key: string, params?: Record<string, unknown>): string };
9905
- onTabChange?: (event: TabChangeEvent) => void;
9906
- onBeforeTabChange?: (event: TabChangeEvent) => boolean | void | Promise<boolean>;
9907
- onTabChangeCancelled?: (event: TabChangeEvent) => void;
9908
- }
9909
-
9910
- /**
9911
- * A tabbed grid: a `role="tablist"` strip above a stack of `role="tabpanel"`
9912
- * regions, each hosting its own, independently-configured grid instance
9913
- * (BACKLOG-0001039). A tab's grid mounts on first activation and is kept
9914
- * alive, hidden, until `destroy()`.
9915
- */
9916
- interface Tabs {
9917
- readonly el: HTMLElement;
9918
- /** The currently active tab id. */
9919
- readonly activeId: string;
9920
- /** The configured tab ids, in order. */
9921
- tabs(): string[];
9922
- /** The live grid instance for a tab, or `null` before it has been materialised. */
9923
- tab(id: string): unknown | null;
9924
- /** Whether a tab's grid has been created yet. */
9925
- isMounted(id: string): boolean;
9926
- /** Switch the active tab, gated by `beforeTabChange`. */
9927
- activate(id: string, opts?: { origin?: 'api' | 'user' }): boolean | Promise<boolean>;
9928
- on(name: 'beforeTabChange' | 'tab:changed' | 'tabChange:cancelled' | string, fn: (event: TabChangeEvent) => void): () => void;
9929
- off(name: string, fn: (event: TabChangeEvent) => void): void;
9930
- /** Tear the whole strip down; destroys every mounted tab's grid. */
9931
- destroy(): void;
9932
- }
9933
-
9934
- /**
9935
- * Create a tabbed grid over a host element. Each tab is a full,
9936
- * independently-configured grid instance; a tab may derive from another via
9937
- * `from`, reusing the shipped `source: { mode: 'derived' }` mechanism.
9938
- */
9939
- export function createTabs(el: HTMLElement, config: TabsConfig): Tabs;
9940
- export default createTabs;
9941
- }
9942
-
9943
- declare module 'lattice-grid/modules/layout' {
9944
- /**
9945
- * One window on the cell grid.
9946
- *
9947
- * Deliberately **not** named `WindowSpec`: that name is already taken by the
9948
- * rolling-statistics window (`{ kind: 'count'|'time'|'session', span, size }`)
9949
- * and reusing it would put `kind: 'session'` next to a dashboard pane.
9950
- */
9951
- interface LayoutWindow {
9952
- /** A stable, unique id. Required. */
9953
- id: string;
9954
- /** The 1-based column the window starts in. Auto-placed when omitted. */
9955
- xPos?: number;
9956
- /** The 1-based row the window starts in. Auto-placed when omitted. */
9957
- yPos?: number;
9958
- /** How many columns it spans (default 1). */
9959
- xSize?: number;
9960
- /** How many rows it spans (default 1). */
9961
- ySize?: number;
9962
- /** The title shown in the chrome bar, and the name every control takes. */
9963
- title?: string;
9964
- /** Whether to draw the title bar (default `true`). */
9965
- chrome?: boolean;
9966
- /** Whether to offer a close button (default `false`). */
9967
- closable?: boolean;
9968
- /** Whether the window can be moved by drag or keyboard (default `false`). */
9969
- movable?: boolean;
9970
- /** Whether the window can be resized by drag or keyboard (default `false`). */
9971
- resizable?: boolean;
9972
- /**
9973
- * Whether to offer a maximise control in the chrome (default `false`).
9974
- *
9975
- * Maximising fills the **layout host**, not the browser window, and hides
9976
- * every other window for the duration. Escape restores it, unless a payload
9977
- * has already claimed the key.
9978
- */
9979
- maximisable?: boolean;
9980
- /**
9981
- * Whether to offer a minimise control in the chrome (default `false`).
9982
- *
9983
- * A window with `chrome: false` cannot be minimised whatever this says:
9984
- * there would be nothing left on screen to restore it with.
9985
- */
9986
- minimisable?: boolean;
9987
- /** Padding inside the window; the layout's `padding` (default `'5px'`) otherwise. */
9988
- padding?: number | string;
9989
- /** The `id` given to the payload container (default `` `${id}-body` ``). */
9990
- payloadId?: string;
9991
- /** The window's accessible name, when the title alone is not enough context. */
9992
- ariaLabel?: string;
9993
- }
9994
-
9995
- /**
9996
- * The three capabilities a layout-level default and `setInteractive()` cover.
9997
- *
9998
- * These are the layout **defaults**, not the per-window resolution: a window
9999
- * that declared `movable: false` stays pinned whatever these say.
10000
- *
10001
- * Three values, not two. `undefined` means no layout-level default is in force
10002
- * and each window's own flag decides; `true` unlocks everything that did not
10003
- * opt out; `false` is an active lock. Reporting `undefined` as `false` would
10004
- * read correctly and round-trip wrongly, so it is reported as it is.
10005
- */
10006
- interface LayoutInteractive {
10007
- movable: boolean | undefined;
10008
- resizable: boolean | undefined;
10009
- closable: boolean | undefined;
10010
- }
10011
-
10012
- /** The plain, JSON-safe arrangement `getLayout()` returns and `setLayout()` takes. */
10013
- interface LayoutSnapshot {
10014
- columns: number;
10015
- rows: number;
10016
- windows: { id: string; xPos: number; yPos: number; xSize: number; ySize: number }[];
10017
- }
10018
-
10019
- /** A cell placement, as carried on the move and resize events. */
10020
- interface LayoutPlacement {
10021
- xPos: number;
10022
- yPos: number;
10023
- xSize: number;
10024
- ySize: number;
10025
- }
10026
-
10027
- /** The payload of `window:moved`, `beforeWindowMove`, `beforeWindowResize`. */
10028
- interface LayoutMoveEvent {
10029
- id: string;
10030
- from: LayoutPlacement;
10031
- /** Where the window was asked to go. */
10032
- to: LayoutPlacement;
10033
- /** Where it actually ended up, which under `compact: 'vertical'` may differ. */
10034
- landed?: LayoutPlacement;
10035
- origin?: 'api' | 'user' | 'init';
10036
- reason?: string | null;
10037
- /** Cancel the action (only meaningful on a `before*` event). */
10038
- preventDefault?: (reason?: string) => void;
10039
- defaultPrevented?: boolean;
10040
- }
10041
-
10042
- /**
10043
- * The payload of `window:resized` — the measured **content box** of the
10044
- * payload container, not a cell count. Emitted when the container genuinely
10045
- * changes size, including on the opening frame; never with a zero box.
10046
- */
10047
- interface LayoutResizeEvent {
10048
- id: string;
10049
- payloadId: string;
10050
- /** The payload container itself, so a host can act on it directly. */
10051
- payload: HTMLElement;
10052
- width: number;
10053
- height: number;
10054
- xPos: number;
10055
- yPos: number;
10056
- xSize: number;
10057
- ySize: number;
10058
- }
10059
-
10060
- /** The payload of `window:closed` and `beforeWindowClose`. */
10061
- interface LayoutCloseEvent {
10062
- id: string;
10063
- payloadId: string;
10064
- /** The payload container, handed back so the host can destroy what it mounted. */
10065
- payload?: HTMLElement;
10066
- origin?: 'api' | 'user';
10067
- reason?: string | null;
10068
- preventDefault?: (reason?: string) => void;
10069
- defaultPrevented?: boolean;
10070
- }
10071
-
10072
- /** The payload of `layout:changed`: the whole arrangement, plus what moved it. */
10073
- interface LayoutChangedEvent extends LayoutSnapshot {
10074
- cause: string;
10075
- }
10076
-
10077
- /** Dashboard layout configuration. */
10078
- interface LayoutConfig {
10079
- /** Cell columns across the mounted element (default 12). */
10080
- columns?: number;
10081
- /** Cell rows down the mounted element (default 6). */
10082
- rows?: number;
10083
- /** Horizontal overflow (default `'static'`). */
10084
- overflowX?: 'static' | 'scroll';
10085
- /** Vertical overflow (default `'static'`). */
10086
- overflowY?: 'static' | 'scroll';
10087
- /** Fixed column track size, used only when `overflowX` is `'scroll'` (default `'240px'`). */
10088
- columnWidth?: number | string;
10089
- /** Fixed row track size, used only when `overflowY` is `'scroll'` (default `'160px'`). */
10090
- rowHeight?: number | string;
10091
- /** The gap between cells (default `'8px'`). */
10092
- gap?: number | string;
10093
- /** The default padding inside a window (default `'5px'`). */
10094
- padding?: number | string;
10095
- /** Rearrangement (default `'vertical'`): push displaced windows down, then pull up. */
10096
- compact?: 'vertical' | 'none';
10097
- /**
10098
- * The default `movable` for every window that does not declare its own
10099
- * (default `false`). This states a default, so `false` takes nothing away
10100
- * from a window that declared `movable: true`; `setInteractive(false)` is
10101
- * the active lock that does.
10102
- */
10103
- movable?: boolean;
10104
- /** The default `resizable` for windows that declare none (default `false`); see `movable`. */
10105
- resizable?: boolean;
10106
- /** The default `closable` for windows that declare none (default `false`); see `movable`. */
10107
- closable?: boolean;
10108
- /**
10109
- * The default `maximisable` for windows that declare none (default `false`).
10110
- *
10111
- * Not touched by `setInteractive()`: a display mode neither moves nor resizes
10112
- * a window in the arrangement, so a locked dashboard can still be blown up
10113
- * to read.
10114
- */
10115
- maximisable?: boolean;
10116
- /** The default `minimisable` for windows that declare none (default `false`); see `maximisable`. */
10117
- minimisable?: boolean;
10118
- /** The windows, in mount order. */
10119
- windows?: LayoutWindow[];
10120
- /** An arrangement to apply at mount, as produced by `getLayout()`. */
10121
- layout?: LayoutSnapshot;
10122
- /** The layout region's accessible name. */
10123
- ariaLabel?: string;
10124
- /** A message catalogue, e.g. `grid.messages`; built-in English seeds otherwise. */
10125
- messages?: { t(key: string, params?: Record<string, unknown>): string };
10126
- onWindowMoved?: (event: LayoutMoveEvent) => void;
10127
- onWindowResized?: (event: LayoutResizeEvent) => void;
10128
- onWindowClosed?: (event: LayoutCloseEvent) => void;
10129
- onLayoutChanged?: (event: LayoutChangedEvent) => void;
10130
- onBeforeWindowMove?: (event: LayoutMoveEvent) => boolean | void | Promise<boolean>;
10131
- onBeforeWindowResize?: (event: LayoutMoveEvent) => boolean | void | Promise<boolean>;
10132
- onBeforeWindowClose?: (event: LayoutCloseEvent) => boolean | void | Promise<boolean>;
10133
- onWindowMoveCancelled?: (event: LayoutMoveEvent) => void;
10134
- onWindowResizeCancelled?: (event: LayoutMoveEvent) => void;
10135
- onWindowCloseCancelled?: (event: LayoutCloseEvent) => void;
10136
- }
10137
-
10138
- /**
10139
- * A reconfigurable dashboard: a cell grid inside an element, and a set of
10140
- * windows on it that a user can move, resize and close by pointer or by
10141
- * keyboard (BACKLOG-0001108).
10142
- *
10143
- * The module is **payload-agnostic**: a window body is a container with an id,
10144
- * which this module creates and sizes and never reads. It tells a payload it
10145
- * was resized by emitting `window:resized`; it never calls into one, because it
10146
- * cannot know what one is.
10147
- */
10148
- interface Layout {
10149
- readonly el: HTMLElement;
10150
- /** The window ids, in mount order. */
10151
- windows(): string[];
10152
- /** The payload container for a window, or `null`. */
10153
- payload(id: string): HTMLElement | null;
10154
- /** A copy of one window's current descriptor, or `null`. */
10155
- window(id: string): LayoutWindow | null;
10156
- /** Add a window after mount; returns its payload container. */
10157
- add(spec: LayoutWindow): HTMLElement;
10158
- /** Move or resize a window, through the same before-events the drag uses. */
10159
- move(id: string, to: Partial<LayoutPlacement>): boolean | Promise<boolean>;
10160
- /** Close a window through `beforeWindowClose`; the payload is not destroyed. */
10161
- close(id: string): boolean | Promise<boolean>;
10162
- /**
10163
- * Blow one window up to fill the layout host, hiding the rest.
10164
- *
10165
- * It fills the **host element**, not the browser window, so there is no
10166
- * `position: fixed` (whose containing block is the nearest ancestor carrying
10167
- * a `transform` or a `contain`, which is why the same rule fills the screen
10168
- * on one page and lands in a 300px box on the next), no reparenting and
10169
- * nothing that can disturb the page around the dashboard.
10170
- *
10171
- * **Nothing moves**: no compaction runs, no placement changes, and the
10172
- * payload container is the same DOM node throughout. **Escape restores it**,
10173
- * from anywhere inside the layout — a focused grid body cell or column
10174
- * heading included — unless a payload has already claimed the key: an open
10175
- * cell editor, filter menu or column menu closes first, and the next Escape
10176
- * restores the window. Afterwards focus lands on the window's maximise
10177
- * control. A minimised window is expanded first, and maximising a second
10178
- * window restores the first.
10179
- */
10180
- maximise(id: string): boolean;
10181
- /**
10182
- * Collapse one window to a single row: its payload is hidden and its chrome
10183
- * stays, carrying the control that brings it back.
10184
- *
10185
- * On screen it becomes one row and the windows below pull up into the space
10186
- * under `compact: 'vertical'`. In the arrangement nothing moves at all — the
10187
- * collapse is a projection of it — so `restore()` gives back exactly the
10188
- * arrangement that was there, in **any** order and with any number of other
10189
- * windows still collapsed.
10190
- *
10191
- * A window with `chrome: false` is refused, with a warning naming it.
10192
- */
10193
- minimise(id: string): boolean;
10194
- /** Leave whichever display mode a window is in; `false` when it was in none. */
10195
- restore(id: string): boolean;
10196
- /** The id of the window filling the host, or `null`. At most one. */
10197
- maximised(): string | null;
10198
- /** The ids of every currently minimised window, in mount order. */
10199
- minimised(): string[];
10200
- /**
10201
- * The full current arrangement.
10202
- *
10203
- * **A mode is not an arrangement**: this reports the *underlying* placement
10204
- * of a maximised or minimised window — where it will be when restored — never
10205
- * the geometry it is drawn at.
10206
- */
10207
- getLayout(): LayoutSnapshot;
10208
- /** Restore an arrangement; never throws on garbage. */
10209
- setLayout(incoming: LayoutSnapshot | LayoutWindow[]): number;
10210
- /** A versioned snapshot, following core's and gantt's shape. */
10211
- getState(): { version: number; layout: LayoutSnapshot };
10212
- /** Restore a `getState()` snapshot; never throws on garbage. */
10213
- setState(snapshot: unknown): number;
10214
- /**
10215
- * Lock or unlock the dashboard at runtime — the "Edit layout" button. A
10216
- * boolean sets all three capabilities; an object sets only the keys it
10217
- * carries. Nothing is destroyed, so every payload survives the toggle.
10218
- *
10219
- * The asymmetry is deliberate: **you can always take a capability away; you
10220
- * can never grant one where the developer said no.** `setInteractive(false)`
10221
- * locks every window, including one whose own spec says `movable: true`;
10222
- * `setInteractive(true)` unlocks only the windows that never opted out.
10223
- *
10224
- * `config.movable: false` and `setInteractive(false)` are deliberately not
10225
- * the same thing: the config states the *default* for windows that declare
10226
- * nothing (and `false` is already that default, so it takes nothing away from
10227
- * a window that opted in), while this is an *active lock*.
10228
- *
10229
- * A key carrying `undefined` is treated as absent, so
10230
- * `setInteractive(getInteractive())` is a no-op in every state.
10231
- *
10232
- * A locked layout is not a read-only dashboard: this module never reads or
10233
- * writes a payload, so a grid inside a window is made read-only with the
10234
- * grid's own settings.
10235
- */
10236
- setInteractive(value: boolean | Partial<LayoutInteractive>): LayoutInteractive;
10237
- /**
10238
- * The layout-level interactivity now in force, as a copy — `undefined` where
10239
- * no layout-level default is set, so the result round-trips through
10240
- * `setInteractive`.
10241
- */
10242
- getInteractive(): LayoutInteractive;
10243
- /** Re-measure every window and emit `window:resized` for those that changed. */
10244
- refresh(): number;
10245
- on(
10246
- name: 'window:moved' | 'window:resized' | 'window:closed' | 'layout:changed'
10247
- | 'beforeWindowMove' | 'beforeWindowResize' | 'beforeWindowClose'
10248
- | 'windowMove:cancelled' | 'windowResize:cancelled' | 'windowClose:cancelled'
10249
- | '*' | string,
10250
- fn: (event: any) => unknown,
10251
- ): () => void;
10252
- off(name: string, fn: (event: any) => unknown): void;
10253
- /** Tear the layout down; whatever the host mounted in a payload is the host's to destroy. */
10254
- destroy(): void;
10255
- }
10256
-
10257
- /** Create a reconfigurable dashboard layout over a host element. */
10258
- export function createLayout(el: HTMLElement, config?: LayoutConfig): Layout;
10259
- export default createLayout;
10260
- }