@toclocoinc/lattice-grid 1.38.0 → 1.40.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 (68) hide show
  1. package/README.md +1 -1
  2. package/docs/API.html +314 -7
  3. package/docs/api-detail.html +29 -1
  4. package/lattice-grid.d.ts +353 -8
  5. package/lattice-grid.esm.min.js +1367 -162
  6. package/lattice-grid.min.cjs +1365 -162
  7. package/lattice-grid.min.js +1365 -162
  8. package/modules/angular.esm.min.js +4 -2
  9. package/modules/angular.min.cjs +4 -2
  10. package/modules/angular.min.js +4 -2
  11. package/modules/chart-alluvial.esm.min.js +1 -1
  12. package/modules/chart-arc.esm.min.js +1 -1
  13. package/modules/chart-bubblemap.esm.min.js +1 -1
  14. package/modules/chart-bump.esm.min.js +1 -1
  15. package/modules/chart-calendar.esm.min.js +1 -1
  16. package/modules/chart-decomposition.esm.min.js +1 -1
  17. package/modules/chart-diverging.esm.min.js +1 -1
  18. package/modules/chart-dumbbell.esm.min.js +1 -1
  19. package/modules/chart-fan.esm.min.js +1 -1
  20. package/modules/chart-hexbin.esm.min.js +1 -1
  21. package/modules/chart-hexmap.esm.min.js +1 -1
  22. package/modules/chart-icicle.esm.min.js +1 -1
  23. package/modules/chart-parallel.esm.min.js +1 -1
  24. package/modules/chart-ridgeline.esm.min.js +1 -1
  25. package/modules/chart-roc.esm.min.js +1 -1
  26. package/modules/chart-slope.esm.min.js +1 -1
  27. package/modules/chart-splom.esm.min.js +1 -1
  28. package/modules/chart-waffle.esm.min.js +1 -1
  29. package/modules/charts.esm.min.js +21 -7
  30. package/modules/charts.min.cjs +21 -7
  31. package/modules/charts.min.js +21 -7
  32. package/modules/data-router.esm.min.js +392 -9
  33. package/modules/data-router.min.cjs +392 -9
  34. package/modules/data-router.min.js +392 -9
  35. package/modules/devtools.esm.min.js +2 -2
  36. package/modules/devtools.min.cjs +2 -2
  37. package/modules/devtools.min.js +2 -2
  38. package/modules/dhtmlx-compat.esm.min.js +4 -4
  39. package/modules/dhtmlx-compat.min.cjs +4 -4
  40. package/modules/dhtmlx-compat.min.js +4 -4
  41. package/modules/gantt.esm.min.js +149 -18
  42. package/modules/gantt.min.cjs +148 -18
  43. package/modules/gantt.min.js +148 -18
  44. package/modules/htmx.esm.min.js +1363 -162
  45. package/modules/htmx.min.cjs +1363 -162
  46. package/modules/htmx.min.js +1363 -162
  47. package/modules/kanban.esm.min.js +350 -7
  48. package/modules/kanban.min.cjs +350 -7
  49. package/modules/kanban.min.js +350 -7
  50. package/modules/kpi.esm.min.js +4 -4
  51. package/modules/kpi.min.cjs +4 -4
  52. package/modules/kpi.min.js +4 -4
  53. package/modules/mock-socket.esm.min.js +2 -2
  54. package/modules/mock-socket.min.cjs +2 -2
  55. package/modules/mock-socket.min.js +2 -2
  56. package/modules/react.esm.min.js +4 -2
  57. package/modules/react.min.cjs +4 -2
  58. package/modules/react.min.js +4 -2
  59. package/modules/svelte.esm.min.js +4 -2
  60. package/modules/svelte.min.cjs +4 -2
  61. package/modules/svelte.min.js +4 -2
  62. package/modules/vue.esm.min.js +4 -2
  63. package/modules/vue.min.cjs +4 -2
  64. package/modules/vue.min.js +4 -2
  65. package/modules/webcomponent.esm.min.js +1365 -162
  66. package/modules/webcomponent.min.cjs +1365 -162
  67. package/modules/webcomponent.min.js +1365 -162
  68. package/package.json +1 -1
@@ -437,7 +437,7 @@
437
437
  <div class="shell">
438
438
  <aside class="rail">
439
439
  <p class="rail__brand">Lattice Grid</p>
440
- <p class="rail__sub">Developer guide · v1.38.0</p>
440
+ <p class="rail__sub">Developer guide · v1.40.0</p>
441
441
  <nav>
442
442
  <div class="rail__group">
443
443
  <span class="rail__label">Start here</span>
@@ -3370,6 +3370,33 @@ columns: [
3370
3370
  }}</code></pre>
3371
3371
  </div>
3372
3372
 
3373
+ <h3 id="declarative-validation">Declarative validation</h3>
3374
+ <p class="lead-in">
3375
+ The <code>edit.validate</code> function above is the imperative form. Where the rules are simple
3376
+ and the same across columns, declare them as data on <code>validation</code> instead
3377
+ (BACKLOG-0000956): <code>required</code>, <code>min</code>/<code>max</code>,
3378
+ <code>minLength</code>/<code>maxLength</code>, <code>pattern</code>, <code>oneOf</code>, and a
3379
+ <code>crossField</code> predicate. Each is checked <strong>before the write</strong>, riding the
3380
+ cancellable <code>beforeEdit</code> event: a failing value cancels the commit, marks the cell
3381
+ with the same accessible invalid state an editor rejection uses, and fires
3382
+ <code>validation:failed</code>. Correcting the value clears the mark and fires
3383
+ <code>validation:cleared</code>. Only a user edit is gated — a host API write is the authority
3384
+ and is never self-vetoed.
3385
+ </p>
3386
+ <div class="example">
3387
+ <p class="example__label">Rules as data</p>
3388
+ <pre><code>columns: [
3389
+ { field: 'name', edit: true, validation: { required: true, minLength: 2 } },
3390
+ { field: 'age', type: 'number', edit: true, validation: { min: 0, max: 120 } },
3391
+ { field: 'code', edit: true, validation: { pattern: '^[A-Z]{3}$', messages: { pattern: 'Three capitals.' } } },
3392
+ ]
3393
+
3394
+ <span class="cmt">// Why a write was refused, and clearing a mark by hand.</span>
3395
+ grid.validation.errorFor('r1', 'age'); <span class="cmt">// { code, message, key, colId } or null</span>
3396
+ grid.on('validation:failed', (e) =&gt; report(e.failures));
3397
+ grid.on('validation:cleared', () =&gt; refreshBanner());</code></pre>
3398
+ </div>
3399
+
3373
3400
  <h3 id="deleting-rows">Deleting rows</h3>
3374
3401
  <p class="lead-in">
3375
3402
  Set <code>rowDelete: true</code> for the built-in delete gesture: Delete or Backspace on the
@@ -6512,6 +6539,7 @@ el.grid.sort.set([{ col: 'charge', dir: 'desc' }]);</code></pre>
6512
6539
  <thead><tr><th>Event</th><th>Fires when</th></tr></thead>
6513
6540
  <tbody>
6514
6541
  <tr><td class="name">column:filter:open</td><td class="desc">Header filter popup opened.</td></tr>
6542
+ <tr><td class="name">column:profile:open</td><td class="desc">The column statistics ("describe") panel was asked to open on a column, from the column menu's "Column statistics" item (<code>{ colId }</code>). A mounted tool panel opens its <code>statistics</code> panel seeded on that column.</td></tr>
6515
6543
  <tr><td class="name">column:grouped</td><td class="desc">The row-group column list changed.</td></tr>
6516
6544
  <tr><td class="name">column:menu:open</td><td class="desc">Header menu opened.</td></tr>
6517
6545
  <tr><td class="name">column:moved</td><td class="desc">Reordered by drag or by API.</td></tr>
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.38.0, type declarations
2
+ * Lattice Grid 1.40.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -522,6 +522,43 @@ export interface ColumnEditSpec {
522
522
  validate?: (p: ValidateParams) => true | string;
523
523
  }
524
524
 
525
+ /**
526
+ * Declarative edit-validation rules for a column (BACKLOG-0000956).
527
+ *
528
+ * Rules are checked in a fixed order — `required` first, then the value-shape
529
+ * rules, then the functions — and the first failure wins. A blank but optional
530
+ * value passes everything after `required`: an empty cell is empty, not "below
531
+ * the minimum". A failure vetoes the commit through `beforeEdit` and marks the
532
+ * cell; the cancellation carries `reason: 'validation:<code>'`.
533
+ */
534
+ export interface ColumnValidation {
535
+ /** The value may not be blank. A string is used as the message. */
536
+ required?: boolean | string;
537
+ /** Minimum, for a number or a date. */
538
+ min?: number;
539
+ /** Maximum, for a number or a date. */
540
+ max?: number;
541
+ /** Minimum text length. */
542
+ minLength?: number;
543
+ /** Maximum text length. */
544
+ maxLength?: number;
545
+ /** A pattern the whole value must match. A string is a RegExp source. */
546
+ pattern?: string | RegExp;
547
+ /** The value must be one of these. */
548
+ oneOf?: unknown[];
549
+ /**
550
+ * A cross-field rule: return `true` to pass, or a message string to fail. The
551
+ * row is passed so a rule can compare against its siblings.
552
+ */
553
+ crossField?: (value: unknown, row: unknown, ctx: { key: string; colId: string; changes: unknown[] }) => true | string | void;
554
+ /** A free-form check, the same contract as `crossField`. */
555
+ validate?: (value: unknown, row: unknown, ctx: { key: string; colId: string; changes: unknown[] }) => true | string | void;
556
+ /** A default message for any rule without its own. */
557
+ message?: string;
558
+ /** Per-rule messages, keyed by rule name (`required`, `min`, `pattern`, …). */
559
+ messages?: Record<string, string>;
560
+ }
561
+
525
562
  export interface ColumnSortSpec {
526
563
  enabled?: boolean;
527
564
  direction?: 'asc' | 'desc' | null;
@@ -616,6 +653,13 @@ export interface Column {
616
653
  cell?: ColumnCellSpec | string;
617
654
  /** Whether and how the cell can be edited. A string names an editor. */
618
655
  edit?: ColumnEditSpec | boolean | string;
656
+ /**
657
+ * Declarative edit-validation rules (BACKLOG-0000956). Each is checked against
658
+ * a value before it is written, through the `beforeEdit` before-event: a
659
+ * failing value cancels the commit and marks the cell. Distinct from and
660
+ * complementary to `edit.validate`, which is an imperative function.
661
+ */
662
+ validation?: ColumnValidation;
619
663
  /** Whether the column sorts, and by what comparison. `false` refuses it. */
620
664
  sort?: ColumnSortSpec | boolean;
621
665
  /** Whether the column filters, and with which filter. A string names one. */
@@ -2299,22 +2343,83 @@ export interface FormattingScale {
2299
2343
  }
2300
2344
 
2301
2345
  /**
2302
- * One rule. Either a condition and the styling it produces, or a colour scale.
2303
- * A rule held as runtime state must be JSON, so `style` may not be a function
2304
- * there: config-time `cell.style` still accepts one.
2346
+ * An in-cell proportional bar (BACKLOG-0000955). Drawn as a CSS gradient on the
2347
+ * cell background — no extra element, and it composes with the cell's text.
2348
+ *
2349
+ * The bar's length is the value's position between `min` and `max`. Give both to
2350
+ * pin the scale (0 to 100 for a percentage); otherwise `from` derives them from
2351
+ * the column — `'minmax'` (the default) spans the data, `'quantile'` the 5th–95th
2352
+ * percentile, `'stddev'` a number of deviations either side of the mean. When the
2353
+ * range straddles zero, bars grow from a shared axis: positive right, negative
2354
+ * left, each in its own colour.
2355
+ */
2356
+ export interface DataBarSpec {
2357
+ min?: number;
2358
+ max?: number;
2359
+ from?: 'minmax' | 'quantile' | 'stddev';
2360
+ low?: number;
2361
+ high?: number;
2362
+ deviations?: number;
2363
+ /** The fill for non-negative values. */
2364
+ colour?: string;
2365
+ /** American spelling of `colour`. */
2366
+ color?: string;
2367
+ /** The fill for negative values. */
2368
+ negativeColour?: string;
2369
+ /** American spelling of `negativeColour`. */
2370
+ negativeColor?: string;
2371
+ /** Which way the bar grows. `'ltr'` (the default) or `'rtl'`. */
2372
+ direction?: 'ltr' | 'rtl';
2373
+ }
2374
+
2375
+ /**
2376
+ * An icon set (BACKLOG-0000955): a glyph placed beside the value by the band it
2377
+ * falls in. Drawn as a `background-image` with padding, so it too needs no extra
2378
+ * element and stays a plain style value.
2379
+ *
2380
+ * `set` names a built-in — `'arrows'`, `'trafficLights'` or `'ratings'` (see
2381
+ * {@link ICON_SETS}) — or supply your own ordered `icons` (SVG documents, data
2382
+ * URIs or `url(...)` values). Bands are split at `thresholds` (ascending, one
2383
+ * fewer than the icons); without them the column's distribution is cut into
2384
+ * equal-count bands. `reverse` flips the order so a high value can read as red.
2385
+ */
2386
+ export interface IconSetSpec {
2387
+ set?: 'arrows' | 'trafficLights' | 'ratings' | string;
2388
+ /** Your own glyphs, low value first: SVG documents, data URIs or `url(...)`. */
2389
+ icons?: string[];
2390
+ /** How many bands, where the set's size is not fixed (e.g. `'ratings'`). */
2391
+ count?: number;
2392
+ /** Band edges, ascending; one fewer than the number of icons. */
2393
+ thresholds?: number[];
2394
+ /** Reverse the glyph order, so the highest band takes the first icon. */
2395
+ reverse?: boolean;
2396
+ /** Glyph height in pixels. Default 16. */
2397
+ size?: number;
2398
+ }
2399
+
2400
+ /**
2401
+ * One rule. A condition and the styling it produces, a colour scale, a data bar
2402
+ * or an icon set. A rule held as runtime state must be JSON, so `style` may not
2403
+ * be a function there (config-time `cell.style` still accepts one) and a data
2404
+ * bar / icon set / scale is the JSON way to say the same visual intent.
2305
2405
  */
2306
2406
  export interface FormattingRule {
2307
2407
  id?: string;
2308
2408
  when?: FormattingCondition;
2309
2409
  style?: CellStyle | ((p: CellParams) => CellStyle | null);
2310
2410
  scale?: FormattingScale;
2411
+ /** An in-cell proportional bar (BACKLOG-0000955). */
2412
+ dataBar?: DataBarSpec;
2413
+ /** A per-band glyph beside the value (BACKLOG-0000955). */
2414
+ iconSet?: IconSetSpec;
2311
2415
  stopIfTrue?: boolean;
2312
2416
  enabled?: boolean;
2313
- icon?: string;
2314
- bar?: boolean;
2315
2417
  label?: string;
2316
2418
  }
2317
2419
 
2420
+ /** The built-in icon set names, id to label, for a panel to offer. */
2421
+ export const ICON_SETS: Readonly<Record<string, string>>;
2422
+
2318
2423
  /** A column id, or `'*'` for every column. */
2319
2424
  export type FormattingScope = string;
2320
2425
 
@@ -2669,6 +2774,22 @@ export interface StatisticsApi {
2669
2774
  * kernels see rows in the order they arrived, which is not the grid's sort.
2670
2775
  */
2671
2776
  series(colId: string, opts: { by: string; periodsPerYear?: number }): SeriesStats | null;
2777
+ /**
2778
+ * Forecast one column forward (BACKLOG-0000963): the stats-surface face of the
2779
+ * {@link forecast} kernel. The column is read over the filtered rows in arrival
2780
+ * order, or ordered by `opts.by` (a date or numeric column, as {@link series}
2781
+ * orders) when the time axis matters, then projected `opts.horizon` steps ahead
2782
+ * by `opts.method` (default `linear`) with a prediction band where one applies.
2783
+ * Every kernel option passes through; returns the same {@link ForecastResult},
2784
+ * or null when the column is unknown or too short.
2785
+ */
2786
+ forecast(colId: string, opts?: {
2787
+ method?: 'movingAverage' | 'ses' | 'holt' | 'holtWinters' | 'linear';
2788
+ horizon?: number; confidence?: number; windowLen?: number;
2789
+ alpha?: number; beta?: number; gamma?: number; period?: number;
2790
+ /** The column to order by before forecasting — a date or numeric axis. */
2791
+ by?: string;
2792
+ }): ForecastResult | null;
2672
2793
  /** A weighted average of one column by another. */
2673
2794
  weightedAverage(colId: string, weightId: string): number | null;
2674
2795
  /** The key a row's data resolves to. */
@@ -2884,6 +3005,80 @@ export function anomalyCondition(
2884
3005
  ): (rows: Iterable<Record<string, unknown>>) =>
2885
3006
  false | { method: string; field: string; flagged: { row: Record<string, unknown>; score: number | null }[] };
2886
3007
 
3008
+ /**
3009
+ * The forecasting methods a caller may ask for (BACKLOG-0000963), named so a
3010
+ * result says which produced it: a trailing moving average, single / double
3011
+ * (Holt) / triple (Holt-Winters) exponential smoothing, and a linear least-squares
3012
+ * fit of the time axis.
3013
+ */
3014
+ export const FORECAST_METHODS: readonly ('movingAverage' | 'ses' | 'holt' | 'holtWinters' | 'linear')[];
3015
+
3016
+ /** One forecast step: the point estimate and, where a band applies, its interval. */
3017
+ export interface ForecastPoint {
3018
+ /** The step ahead, `1 … horizon`. */
3019
+ step: number;
3020
+ /** The time-axis position the step is stamped at, extrapolated at the mean spacing. */
3021
+ at: number;
3022
+ /** The point forecast. */
3023
+ mean: number;
3024
+ /** The prediction-interval lower bound (a future observation), or null when none applies. */
3025
+ lower: number | null;
3026
+ /** The prediction-interval upper bound, or null when none applies. */
3027
+ upper: number | null;
3028
+ /** The mean-response (confidence) lower bound — `linear` only, the band a trendline draws. */
3029
+ lowerMean?: number | null;
3030
+ /** The mean-response (confidence) upper bound — `linear` only. */
3031
+ upperMean?: number | null;
3032
+ /** The prediction standard error the band was built from, or null when none applies. */
3033
+ se: number | null;
3034
+ }
3035
+
3036
+ /** A forecast: the chosen model, its parameters, and the projected points. */
3037
+ export interface ForecastResult {
3038
+ /** Which method produced it. */
3039
+ method: 'movingAverage' | 'ses' | 'holt' | 'holtWinters' | 'linear';
3040
+ /** How many steps ahead were projected. */
3041
+ horizon: number;
3042
+ /** The band level, e.g. 0.95. */
3043
+ confidence: number;
3044
+ /** How many finite readings the fit used. */
3045
+ n: number;
3046
+ /** The residual standard deviation the bands were built from, or null when there was none. */
3047
+ sigma: number | null;
3048
+ /** The fit's coefficient of determination — `linear` only. */
3049
+ r2?: number;
3050
+ /** The model parameters: `slope`/`intercept` (linear), `alpha`/`beta`/`gamma`/`period`, or `windowLen`. */
3051
+ params: {
3052
+ slope?: number; intercept?: number;
3053
+ alpha?: number; beta?: number; gamma?: number; period?: number; windowLen?: number;
3054
+ };
3055
+ /** The forecast, one entry per step. */
3056
+ points: ForecastPoint[];
3057
+ }
3058
+
3059
+ /**
3060
+ * Forecast an ordered series `horizon` steps into the future (BACKLOG-0000963).
3061
+ *
3062
+ * `movingAverage` and `ses` are flat forecasts (the trailing-window mean, the
3063
+ * final smoothed level); `holt` adds a projected trend, `holtWinters` a projected
3064
+ * trend and an additive seasonal of period `opts.period`; `linear` extrapolates
3065
+ * an ordinary least-squares fit of the time axis. A prediction band is carried
3066
+ * where a defensible closed form exists — the exponential-smoothing bands are the
3067
+ * innovations state-space forecast variances at the normal quantile; the linear
3068
+ * and moving-average bands are the exact Student-t intervals, and `linear` also
3069
+ * reports the narrower mean-response (confidence) band. A smoothing factor absent
3070
+ * from `opts` is fit by minimising the in-sample one-step SSE. Returns null when
3071
+ * the series is too short for the chosen method.
3072
+ */
3073
+ export function forecast(
3074
+ seq: ArrayLike<number | null> | { at?: number; value: number | null }[],
3075
+ opts?: {
3076
+ method?: 'movingAverage' | 'ses' | 'holt' | 'holtWinters' | 'linear';
3077
+ horizon?: number; confidence?: number; windowLen?: number;
3078
+ alpha?: number; beta?: number; gamma?: number; period?: number;
3079
+ },
3080
+ ): ForecastResult | null;
3081
+
2887
3082
  export type ShadowKind =
2888
3083
  | 'updates' | 'updatedAt' | 'sinceUpdate' | 'delta' | 'deltaPercent'
2889
3084
  | 'rate' | 'history' | 'firstValue' | 'streak'
@@ -3170,6 +3365,12 @@ export interface ColumnProfile {
3170
3365
  stddev: number | null;
3171
3366
  outliers: number;
3172
3367
  histogram: HistogramBin[];
3368
+ /**
3369
+ * For a categorical (non-numeric) column, the commonest values, largest
3370
+ * first (BACKLOG-0000959). Absent for a numeric column, whose shape the
3371
+ * numeric figures and the histogram already carry.
3372
+ */
3373
+ topValues?: TopValue[];
3173
3374
  }
3174
3375
 
3175
3376
  export interface HistogramBin {
@@ -3178,6 +3379,16 @@ export interface HistogramBin {
3178
3379
  count: number;
3179
3380
  }
3180
3381
 
3382
+ /** One row of a categorical column's top-values table (BACKLOG-0000959). */
3383
+ export interface TopValue {
3384
+ /** The value itself, as it is stored. */
3385
+ value: unknown;
3386
+ /** How many present rows carry it. */
3387
+ count: number;
3388
+ /** Its share of the present values, 0 to 1. */
3389
+ share: number;
3390
+ }
3391
+
3181
3392
  /** How one column differs between the filtered subset and its population. */
3182
3393
  export interface ColumnDifference {
3183
3394
  /** The column id. */
@@ -3429,6 +3640,30 @@ export interface FormattingApi {
3429
3640
  distribution(colId: string): ColumnDistribution | null;
3430
3641
  }
3431
3642
 
3643
+ /** One recorded validation error (BACKLOG-0000956). */
3644
+ export interface ValidationError {
3645
+ key: string;
3646
+ colId: string;
3647
+ code: string;
3648
+ message: string;
3649
+ }
3650
+
3651
+ /** The runtime face of declarative column validation (BACKLOG-0000956). */
3652
+ export interface ValidationApi {
3653
+ /** Run a column's rules against a value, returning the first failure or null. */
3654
+ check(colId: string, value: unknown, row?: unknown): { code: string; message: string } | null;
3655
+ /** The recorded error for one cell, or null when it is valid. */
3656
+ errorFor(key: string, colId: string): ValidationError | null;
3657
+ /** Every cell that currently holds a validation error. */
3658
+ errors(): ValidationError[];
3659
+ /** Clear errors: one cell, a whole row, or all of them. */
3660
+ clear(key?: string, colId?: string): boolean;
3661
+ /** Set or replace a column's rules at runtime; null removes them. */
3662
+ define(colId: string, spec: ColumnValidation | null): void;
3663
+ /** Whether at least one column declares a rule. */
3664
+ readonly active: boolean;
3665
+ }
3666
+
3432
3667
  /** Operators that resolve against the column's own distribution (spec 8.12). */
3433
3668
  export type DistributionOp =
3434
3669
  | 'topPercent' | 'bottomPercent' | 'topN' | 'bottomN'
@@ -3483,7 +3718,8 @@ export type EventName =
3483
3718
  | 'facet:computed' | 'facet:filtered' | 'facet:expanded' | 'facet:failed'
3484
3719
  /* Columns */
3485
3720
  | 'column:moved' | 'column:resized' | 'column:visible' | 'column:pinned'
3486
- | 'column:grouped' | 'column:pivoted' | 'column:filter:open' | 'column:menu:open'
3721
+ | 'column:grouped' | 'column:pivoted' | 'column:filter:open' | 'column:profile:open'
3722
+ | 'column:menu:open'
3487
3723
  | 'pivot:drill'
3488
3724
  | 'columns:changed' | 'columns:tagged' | 'columngroup:changed' | 'header:contextmenu'
3489
3725
  /* Selection and view */
@@ -3496,6 +3732,9 @@ export type EventName =
3496
3732
  | 'state:changed' | 'state:reset' | 'history:changed' | 'history:applied'
3497
3733
  | 'views:changed' | 'view:applied' | 'view:saved' | 'view:removed'
3498
3734
  | 'view:renamed' | 'view:default'
3735
+ /* Validation (BACKLOG-0000956): a declared column rule vetoed an edit, or a
3736
+ * recorded error was cleared. The veto itself rides the cancellable `beforeEdit`. */
3737
+ | 'validation:failed' | 'validation:cleared'
3499
3738
  /* Formatting and presentation */
3500
3739
  | 'formatting:changed' | 'redaction:changed' | 'permissions:changed'
3501
3740
  | 'presentation:changed' | 'presentation:started' | 'presentation:ended'
@@ -4037,12 +4276,37 @@ export interface ImportPreview {
4037
4276
  warnings: string[];
4038
4277
  }
4039
4278
 
4279
+ /** What an `.xlsx` preview carries — an {@link ImportPreview} plus the sheet read (§14). */
4280
+ export interface ImportXlsxPreview {
4281
+ /** The archive path of the worksheet that was read, e.g. `xl/worksheets/sheet1.xml`. */
4282
+ sheet: string | null;
4283
+ /** The source column headings. */
4284
+ header: string[];
4285
+ /** The per-column mapping and inference the user may edit before confirming. */
4286
+ columns: ImportColumn[];
4287
+ /** Every mapped, coerced record the import would add. */
4288
+ records: Record<string, unknown>[];
4289
+ /** The leading records, for a preview table. */
4290
+ sample: Record<string, unknown>[];
4291
+ /** How many data rows the sheet holds. */
4292
+ rowCount: number;
4293
+ /** Anything worth flagging before confirming. */
4294
+ warnings: string[];
4295
+ }
4296
+
4040
4297
  /** Bringing rows in — the mirror of {@link ExportApi} (§14, BACKLOG-0000949). */
4041
4298
  export interface ImportApi {
4042
4299
  /** Parse delimited text into a preview, changing nothing. */
4043
4300
  preview(text: string, opts?: object): ImportPreview;
4044
4301
  /** Parse delimited text into coerced records — the inverse of `export.csv`. */
4045
4302
  csv(text: string, opts?: object): Record<string, unknown>[];
4303
+ /**
4304
+ * Parse an `.xlsx` file's bytes into a preview, changing nothing (§14,
4305
+ * BACKLOG-0000970). Async: the archive is inflated with `DecompressionStream`.
4306
+ */
4307
+ previewXlsx(bytes: Uint8Array | ArrayBuffer, opts?: object): Promise<ImportXlsxPreview>;
4308
+ /** Parse an `.xlsx` file's bytes into coerced records — the inverse of `export.excel`. */
4309
+ xlsx(bytes: Uint8Array | ArrayBuffer, opts?: object): Promise<Record<string, unknown>[]>;
4046
4310
  /** Add or replace the grid's rows from text, a preview or records. */
4047
4311
  apply(
4048
4312
  input: string | ImportPreview | Record<string, unknown>[],
@@ -4899,6 +5163,8 @@ export interface Grid {
4899
5163
  readonly statistics: StatisticsApi;
4900
5164
  /** Formatting a value as the grid would, outside a cell. */
4901
5165
  readonly formatting: FormattingApi;
5166
+ /** Declarative column validation: why a write was refused, and clearing marks. */
5167
+ readonly validation: ValidationApi;
4902
5168
  /** Full-screen control, where it is enabled. */
4903
5169
  readonly maximise?: MaximiseApi;
4904
5170
  /**
@@ -6630,6 +6896,16 @@ declare module 'lattice-grid/modules/gantt' {
6630
6896
  priority?: number;
6631
6897
  /** An explicit row height (px) for the split view; applied to both panels. */
6632
6898
  height?: number;
6899
+ /**
6900
+ * The budgeted cost (BAC) for earned-value analysis (BACKLOG-0000958). When
6901
+ * omitted the task's duration is used as the budget, giving schedule-only EVM.
6902
+ */
6903
+ cost?: number;
6904
+ /**
6905
+ * The actual cost incurred (ACWP) for earned-value analysis
6906
+ * (BACKLOG-0000958). Left out, the task's cost variance/CPI are `null`.
6907
+ */
6908
+ actualCost?: number;
6633
6909
  }
6634
6910
 
6635
6911
  /**
@@ -6771,6 +7047,62 @@ declare module 'lattice-grid/modules/gantt' {
6771
7047
  /** Format an engine day-number as an ISO calendar date (`YYYY-MM-DD`, UTC). */
6772
7048
  export function toISODate(day: number): string | null;
6773
7049
 
7050
+ /** Earned-value metrics for one task or the whole project (BACKLOG-0000958). */
7051
+ interface GanttEarnedValueRow {
7052
+ id: string;
7053
+ name: string;
7054
+ isSummary: boolean;
7055
+ isMilestone: boolean;
7056
+ percentComplete: number | null;
7057
+ /** Whether a baseline (not the fallback scheduled window) drove PV. */
7058
+ hasBaseline: boolean;
7059
+ /** Whether any actual cost fed AC (else AC/CV/CPI are null). */
7060
+ hasActualCost: boolean;
7061
+ /** Budget at completion (the task's cost, or its duration when no cost). */
7062
+ bac: number;
7063
+ /** Planned Value (BCWS): budgeted cost of the work scheduled by the status date. */
7064
+ pv: number;
7065
+ /** Earned Value (BCWP): budgeted cost of the work performed (BAC × %complete). */
7066
+ ev: number;
7067
+ /** Actual Cost (ACWP): what the work performed actually cost, or null. */
7068
+ ac: number | null;
7069
+ /** Schedule Variance (EV − PV); positive is ahead of schedule. */
7070
+ sv: number;
7071
+ /** Cost Variance (EV − AC); positive is under budget; null without AC. */
7072
+ cv: number | null;
7073
+ /** Schedule Performance Index (EV / PV); null when PV is zero. */
7074
+ spi: number | null;
7075
+ /** Cost Performance Index (EV / AC); null without AC or when AC is zero. */
7076
+ cpi: number | null;
7077
+ }
7078
+
7079
+ /** The earned-value result at a status date (BACKLOG-0000958). */
7080
+ interface GanttEarnedValue {
7081
+ ok: boolean;
7082
+ error?: { code: string; message: string };
7083
+ /** The status date the metrics were evaluated at (day-number). */
7084
+ statusDate?: number;
7085
+ /** Every task keyed by id (leaf, summary and derived). */
7086
+ byTask?: Map<string, GanttEarnedValueRow>;
7087
+ /** The same rows in schedule order. */
7088
+ rows?: GanttEarnedValueRow[];
7089
+ /** The project total, rolled up as money sums of the leaves. */
7090
+ project?: GanttEarnedValueRow;
7091
+ }
7092
+
7093
+ /**
7094
+ * Compute earned-value management (EVM) metrics for a scheduled plan at a
7095
+ * status date (BACKLOG-0000958): PV/BCWS from the baseline, EV/BCWP from
7096
+ * %complete, AC/ACWP from the per-task `actualCost`, and the derived SV/CV and
7097
+ * SPI/CPI — per leaf, rolled up to summaries and the project. The math is
7098
+ * implemented locally in the module (no core-compute dependency).
7099
+ */
7100
+ export function computeEarnedValue(
7101
+ tasks: GanttTask[],
7102
+ schedule: GanttSchedule,
7103
+ options?: { statusDate?: number | string | Date; costField?: string; actualCostField?: string },
7104
+ ): GanttEarnedValue;
7105
+
6774
7106
  /** A headless Gantt controller: holds the model, recomputes on edits, emits changes. */
6775
7107
  interface Gantt {
6776
7108
  readonly tasks: GanttTask[];
@@ -6885,7 +7217,13 @@ declare module 'lattice-grid/modules/gantt' {
6885
7217
  showProgress?: boolean;
6886
7218
  showBaseline?: boolean;
6887
7219
  barLabel?: 'name' | 'percent' | 'dates' | 'none' | ((task: GanttScheduledTask) => string);
6888
- columns?: Array<{ key: string; title?: string; width?: number; kind?: 'name' | 'assignee' | 'progress'; render?: (task: GanttScheduledTask, ctx: { rawTask: GanttTask; depth: number }) => unknown }>;
7220
+ /**
7221
+ * Surface earned-value metrics in `kind: 'evm'` columns (BACKLOG-0000958).
7222
+ * `true` computes EVM at the today line (or the project finish); an object
7223
+ * overrides the status date and the cost field names.
7224
+ */
7225
+ evm?: boolean | { statusDate?: number | string | Date; costField?: string; actualCostField?: string };
7226
+ 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 }>;
6889
7227
  }): unknown;
6890
7228
  /**
6891
7229
  * Capture a baseline (planned) snapshot of the current schedule as HOST data
@@ -6893,6 +7231,13 @@ declare module 'lattice-grid/modules/gantt' {
6893
7231
  * `baselineStart`/`baselineEnd` task fields to get variance and ghost bars.
6894
7232
  */
6895
7233
  captureBaseline(): Array<{ id: string; baselineStart: number; baselineEnd: number; baselineDuration: number }>;
7234
+ /**
7235
+ * Compute earned-value (EVM) metrics for the current plan at a status date
7236
+ * (BACKLOG-0000958): PV/EV/AC and the derived SV/CV/SPI/CPI per task, rolled
7237
+ * up to summaries and the project. Budget (BAC) is the task's `cost`, or its
7238
+ * duration when no cost is given; AC comes from `actualCost`.
7239
+ */
7240
+ earnedValue(evmOpts?: { statusDate?: number | string | Date; costField?: string; actualCostField?: string }): GanttEarnedValue;
6896
7241
  /** Detach the mounted view, if any. The host still owns the container. */
6897
7242
  unmount(): void;
6898
7243
  /** The mounted view, or null. */