@ai-matrx/design-system 0.21.13 → 0.22.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 (61) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/content-transfer.cjs +1 -0
  3. package/dist/content-transfer.cjs.map +1 -1
  4. package/dist/content-transfer.js +1 -0
  5. package/dist/content-transfer.js.map +1 -1
  6. package/dist/data-table/copy-helpers.cjs.map +1 -1
  7. package/dist/data-table/copy-helpers.d.cts +2 -1
  8. package/dist/data-table/copy-helpers.d.ts +2 -1
  9. package/dist/data-table/copy-helpers.js.map +1 -1
  10. package/dist/data-table/data-row-inspector.cjs +1 -0
  11. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  12. package/dist/data-table/data-row-inspector.js +1 -0
  13. package/dist/data-table/data-row-inspector.js.map +1 -1
  14. package/dist/data-table/data-row-window.cjs +1 -0
  15. package/dist/data-table/data-row-window.cjs.map +1 -1
  16. package/dist/data-table/data-row-window.js +1 -0
  17. package/dist/data-table/data-row-window.js.map +1 -1
  18. package/dist/data-table/filter-engine.cjs +6 -2
  19. package/dist/data-table/filter-engine.cjs.map +1 -1
  20. package/dist/data-table/filter-engine.d.cts +2 -1
  21. package/dist/data-table/filter-engine.d.ts +2 -1
  22. package/dist/data-table/filter-engine.js +6 -2
  23. package/dist/data-table/filter-engine.js.map +1 -1
  24. package/dist/data-table/host.d.cts +2 -1
  25. package/dist/data-table/host.d.ts +2 -1
  26. package/dist/data-table/index.cjs +1547 -425
  27. package/dist/data-table/index.cjs.map +1 -1
  28. package/dist/data-table/index.d.cts +91 -3
  29. package/dist/data-table/index.d.ts +91 -3
  30. package/dist/data-table/index.js +1549 -423
  31. package/dist/data-table/index.js.map +1 -1
  32. package/dist/data-table/infer-filter.cjs.map +1 -1
  33. package/dist/data-table/infer-filter.d.cts +2 -1
  34. package/dist/data-table/infer-filter.d.ts +2 -1
  35. package/dist/data-table/infer-filter.js.map +1 -1
  36. package/dist/data-table/layered-filters.d.cts +2 -1
  37. package/dist/data-table/layered-filters.d.ts +2 -1
  38. package/dist/data-table/query-control.cjs.map +1 -1
  39. package/dist/data-table/query-control.d.cts +2 -1
  40. package/dist/data-table/query-control.d.ts +2 -1
  41. package/dist/data-table/query-control.js.map +1 -1
  42. package/dist/data-table/types.cjs.map +1 -1
  43. package/dist/data-table/types.d.cts +2 -1
  44. package/dist/data-table/types.d.ts +2 -1
  45. package/dist/data-table/url-state.d.cts +2 -1
  46. package/dist/data-table/url-state.d.ts +2 -1
  47. package/dist/data-table/xlsx.cjs.map +1 -1
  48. package/dist/data-table/xlsx.d.cts +2 -1
  49. package/dist/data-table/xlsx.d.ts +2 -1
  50. package/dist/data-table/xlsx.js.map +1 -1
  51. package/dist/field-formats/index.cjs +741 -0
  52. package/dist/field-formats/index.cjs.map +1 -0
  53. package/dist/field-formats/index.d.cts +45 -0
  54. package/dist/field-formats/index.d.ts +45 -0
  55. package/dist/field-formats/index.js +723 -0
  56. package/dist/field-formats/index.js.map +1 -0
  57. package/dist/types-BdbvlF9-.d.cts +204 -0
  58. package/dist/types-BdbvlF9-.d.ts +204 -0
  59. package/dist/{types-D9b_LfTB.d.ts → types-D-flTpK0.d.ts} +241 -1
  60. package/dist/{types-DaKPLM6T.d.cts → types-Sn249eoy.d.cts} +241 -1
  61. package/package.json +11 -1
@@ -1,6 +1,7 @@
1
1
  import { ContentTransferReference } from './content-transfer.js';
2
2
  import { ReactNode } from 'react';
3
3
  import { AiVariant, AiCustomSource, CopyExportConfig } from './data-table/copy-types.js';
4
+ import { b as FieldFormatConfig } from './types-BdbvlF9-.js';
4
5
 
5
6
  type TableSearchRole = "id" | "name" | "description" | "other";
6
7
  interface TableSearchScope {
@@ -14,6 +15,9 @@ interface TableRankedSearchOptions {
14
15
  scopes?: readonly TableSearchScope[];
15
16
  matchMode?: TableSearchMatchMode;
16
17
  }
18
+ /** Column metadata overrides inference; identity substrings always outrank prose. */
19
+ declare function inferTableSearchRole(field: string): TableSearchRole;
20
+ declare function resolveTableSearchScopes<T>(rows: readonly T[], columns: readonly MatrxColumnDef<T>[], options?: TableRankedSearchOptions): TableSearchScope[];
17
21
  /** Apply to the complete authorized result BEFORE slicing pages. Remote owners
18
22
  * must implement the same contract at their source, never on a partial page. */
19
23
  declare function rankTableSearchRows<T>(rows: readonly T[], columns: readonly MatrxColumnDef<T>[], query: string, options?: TableRankedSearchOptions, extraText?: (row: T) => string): T[];
@@ -77,6 +81,151 @@ declare function columnFiltersToLayeredRules(filters: ColumnFiltersState, fieldI
77
81
  declare function layeredFilterMatchesValue(value: unknown, rule: LayeredFilterRule): boolean;
78
82
  declare function layeredFilterRuleSummary(rule: LayeredFilterRule, fields: readonly LayeredFilterField[]): string;
79
83
 
84
+ /**
85
+ * Table style — colors on a user data table, the Airtable way.
86
+ *
87
+ * WHAT IS ALLOWED, AND WHY ONLY THIS. A typed dataset is not a canvas; the
88
+ * platform already has Workbooks (Univer) for arbitrary per-cell formatting.
89
+ * Here, color carries MEANING and stays out of the data:
90
+ *
91
+ * 1. COLOR BY A COLUMN — a choice column's option colors (or a boolean's
92
+ * true/false) tint the row or that column's cells. Zero configuration:
93
+ * the Status column already knows "Blocked" is red.
94
+ * 2. RULES — "when Amount > 1000, tint the row amber". Evaluated live, so a
95
+ * tint is never stale.
96
+ * 3. MANUAL HIGHLIGHTS — a small palette on a cell, a row, or a column, for
97
+ * the "look at this one" gesture. The same palette the choice chips use,
98
+ * so the table never has two vocabularies of color.
99
+ *
100
+ * WHERE IT LIVES. This module is the MODEL only — pure, no React, no DOM, no
101
+ * network. Each consumer names its own home for the same document and writes it
102
+ * surgically BY PATH (`stylePath.*`), so two editors highlighting different
103
+ * cells never overwrite each other:
104
+ *
105
+ * - user data tables (`/data`): `udt_datasets.metadata.style`, written by
106
+ * `public.udt_set_table_style(p_table_id, p_path, p_value)`.
107
+ * - the record store: the table's saved view / Table settings document,
108
+ * written by `custom.table_style_set(p_table, p_path, p_value)`.
109
+ *
110
+ * Copy, export, agents and the data itself never see the style.
111
+ *
112
+ * PRECEDENCE (most deliberate wins): a manual cell highlight > a cell rule >
113
+ * color-by targeting cells > a manual column highlight, for the CELL; a manual
114
+ * row highlight > a row rule > color-by targeting rows, for the ROW. A cell
115
+ * tint always paints over the row tint underneath it.
116
+ *
117
+ * Pure module: no React, no DOM, no Supabase.
118
+ */
119
+ declare const TABLE_STYLE_VERSION: 1;
120
+ /**
121
+ * The highlight palette.
122
+ *
123
+ * These are the SAME seven names the choice-chip palette uses minus "neutral"
124
+ * (which means "no color"), spelled out here so the model carries no import at
125
+ * all. Names, never raw hex: a name is legible on both grounds because each one
126
+ * ships its own `dark:` class below, and a retired name degrades to "no color"
127
+ * instead of to an unreadable swatch. A consumer that also draws choice chips
128
+ * must keep its chip palette on these names, or the table grows two vocabularies
129
+ * of color.
130
+ */
131
+ declare const STYLE_COLORS: readonly ["slate", "green", "amber", "red", "blue", "violet", "teal"];
132
+ type StyleColor = (typeof STYLE_COLORS)[number];
133
+ declare function isStyleColor(v: unknown): v is StyleColor;
134
+ declare const COLOR_RULE_OPS: readonly ["is", "is_not", "contains", "is_empty", "not_empty", "is_true", "is_false", "gt", "gte", "lt", "lte"];
135
+ type ColorRuleOp = (typeof COLOR_RULE_OPS)[number];
136
+ /** Which ops take a value to compare against. */
137
+ declare const OPS_WITH_VALUE: readonly ColorRuleOp[];
138
+ declare const COLOR_RULE_OP_LABELS: Record<ColorRuleOp, string>;
139
+ type StyleTarget = "row" | "cell";
140
+ type ColorRule = {
141
+ id: string;
142
+ /** Machine field name the condition reads. */
143
+ field: string;
144
+ op: ColorRuleOp;
145
+ /** Compared as text for `is` / `is_not` / `contains`, as a number for gt/lt. */
146
+ value?: string;
147
+ color: StyleColor;
148
+ /** Tint the whole row, or only this column's cell. */
149
+ target: StyleTarget;
150
+ };
151
+ type ColorBy = {
152
+ field: string;
153
+ target: StyleTarget;
154
+ };
155
+ type TableStyle = {
156
+ version: typeof TABLE_STYLE_VERSION;
157
+ colorBy?: ColorBy | null;
158
+ rules?: ColorRule[];
159
+ /** Manual row highlights, by row id. */
160
+ rows?: Record<string, StyleColor>;
161
+ /** Manual cell highlights, by row id then field name. */
162
+ cells?: Record<string, Record<string, StyleColor>>;
163
+ /** Manual column highlights, by field name. */
164
+ columns?: Record<string, StyleColor>;
165
+ };
166
+ declare const EMPTY_TABLE_STYLE: TableStyle;
167
+ /** The conventional key the style document lives under on a table's own metadata. */
168
+ declare const TABLE_STYLE_METADATA_KEY = "style";
169
+ /** True when nothing in the style would paint anything. */
170
+ declare function tableStyleIsEmpty(style: TableStyle): boolean;
171
+ /**
172
+ * Read a style off table metadata. Tolerant by design: an unknown key, a
173
+ * retired color name or a malformed rule is dropped, never thrown, because a
174
+ * table must still render when its style is stale.
175
+ */
176
+ declare function parseTableStyle(raw: unknown): TableStyle;
177
+ /** Style out of a table-metadata blob (or null / undefined). */
178
+ declare function tableStyleFromMetadata(metadata: unknown): TableStyle;
179
+ /** Does `value` satisfy `rule`? Text ops compare case-insensitively; number ops parse both sides. */
180
+ declare function evaluateRule(rule: ColorRule, value: unknown): boolean;
181
+ /**
182
+ * The color a choice / boolean column gives a value, for color-by. The caller
183
+ * supplies the lookup because option colors live on the resolved choice list
184
+ * (which may come from a structured list), not on the style.
185
+ */
186
+ type ChoiceColorLookup = (fieldName: string, value: unknown) => StyleColor | undefined;
187
+ /**
188
+ * The color an option paints with. An option that DECLARED a color uses it; an
189
+ * option without one gets a stable color from its position in the list, so
190
+ * "color rows by Status" always paints something — Airtable assigns every
191
+ * option a color for the same reason. A column whose options were never
192
+ * colored would otherwise look like color-by did nothing at all.
193
+ */
194
+ declare function colorForChoice(choices: readonly {
195
+ value: string;
196
+ color?: string;
197
+ }[] | undefined, value: unknown): StyleColor | undefined;
198
+ /**
199
+ * Boolean columns have no option list; color-by paints true green and leaves
200
+ * false untinted, which is the "done / not done" reading every checklist has.
201
+ */
202
+ declare const BOOLEAN_TRUE_COLOR: StyleColor;
203
+ /** The tint for a whole row, or null for none. */
204
+ declare function resolveRowColor(style: TableStyle, row: {
205
+ id: string;
206
+ data: Record<string, unknown>;
207
+ }, choiceColorFor: ChoiceColorLookup): StyleColor | null;
208
+ /** The tint for one cell (painted over the row tint), or null for none. */
209
+ declare function resolveCellColor(style: TableStyle, row: {
210
+ id: string;
211
+ data: Record<string, unknown>;
212
+ }, fieldName: string, choiceColorFor: ChoiceColorLookup): StyleColor | null;
213
+ declare const ROW_TINT_CLASS: Record<StyleColor, string>;
214
+ declare const CELL_TINT_CLASS: Record<StyleColor, string>;
215
+ /** A solid swatch for palette menus. */
216
+ declare const SWATCH_CLASS: Record<StyleColor, string>;
217
+ declare const STYLE_COLOR_LABELS: Record<StyleColor, string>;
218
+ type StylePath = readonly string[];
219
+ declare const stylePath: {
220
+ colorBy: () => StylePath;
221
+ rules: () => StylePath;
222
+ row: (rowId: string) => StylePath;
223
+ cell: (rowId: string, fieldName: string) => StylePath;
224
+ column: (fieldName: string) => StylePath;
225
+ };
226
+ /** Apply a path write locally, so the grid repaints before the round trip returns. */
227
+ declare function applyStylePath(style: TableStyle, path: StylePath, value: unknown): TableStyle;
228
+
80
229
  /** How a column's filter UI behaves. `auto` infers from sample values. */
81
230
  type ColumnFilterKind = "auto" | "text" | "select" | "boolean" | "number" | false;
82
231
  type SortDirection = "asc" | "desc";
@@ -256,6 +405,36 @@ interface MatrxColumnDef<T> {
256
405
  /** Force non-navigable for this column. */
257
406
  forbidden?: boolean | ((id: string, row: T) => boolean);
258
407
  };
408
+ /**
409
+ * How this column's value READS — money, percent, a date style, decimal
410
+ * places. The vocabulary is the platform's one field-format registry
411
+ * (`@ai-matrx/design-system/field-formats`), the same `{id, options}` a user
412
+ * data table stores on its column and the record store stores on its Field,
413
+ * so `$1,234.56` is the same `$1,234.56` on every table in the product.
414
+ *
415
+ * A declarative format, not another render function, because a `cell`
416
+ * callback is invisible to everything else: with `format` the column also
417
+ * right-aligns itself, and a consumer's own `cell` asks the primitive for the
418
+ * same text through `formatColumnValue(col, value)` rather than growing a
419
+ * second money formatter beside this one.
420
+ *
421
+ * THE FALLBACK LAW travels with it: a value that does not fit the declared
422
+ * format is never blanked and never throws — it renders its base-type text in
423
+ * amber with the reason on hover, so the person SEES the mismatch.
424
+ *
425
+ * `cell` still wins when both are set.
426
+ */
427
+ format?: FieldFormatConfig;
428
+ /**
429
+ * Keep this column in place while the rest of the table scrolls sideways.
430
+ *
431
+ * Only the LEADING RUN freezes, only from `sm:` up, and only when the widths
432
+ * the offsets need are known — `./frozen-columns.ts` says why each of those
433
+ * three is a rule and not a default. Give a frozen column an explicit
434
+ * `width`; without one the table declines to freeze anything rather than pin
435
+ * a column at a guessed offset.
436
+ */
437
+ frozen?: boolean;
259
438
  className?: string;
260
439
  headerClassName?: string;
261
440
  width?: string | number;
@@ -397,6 +576,14 @@ interface MatrxTablePagination<T> {
397
576
  };
398
577
  retrySource?: () => void;
399
578
  sourceUntil?: string;
579
+ /** Source records differ from rendered rows when the host groups occurrences. */
580
+ sourceRecords?: {
581
+ loaded: number;
582
+ total?: number | undefined;
583
+ label: string;
584
+ rowLabel: string;
585
+ matchedRecords?: number;
586
+ };
400
587
  sourcePageSize?: {
401
588
  value: number;
402
589
  options: readonly number[];
@@ -568,6 +755,17 @@ interface TableViewSnapshot {
568
755
  hidden: string[];
569
756
  widths?: Record<string, number>;
570
757
  };
758
+ /**
759
+ * The view's COLORS — color-by-a-column, rules, and manual highlights
760
+ * (`./table-style.ts`). Optional so every view saved before colors existed
761
+ * still parses; absent means "this view paints nothing".
762
+ *
763
+ * Colors belong to the VIEW and not to the data, for the same reason the
764
+ * filters do: two people can read one table two ways, and neither reading is
765
+ * a fact about the records. That is also why this snapshot is the only place
766
+ * a style is carried — copy, export and agents read the rows, never this.
767
+ */
768
+ style?: TableStyle;
571
769
  }
572
770
  /** Host port for durable named views. The host owns actor, tenancy and CRUD. */
573
771
  interface TableSavedViewsProps {
@@ -844,6 +1042,28 @@ interface MatrxDataTableMobileCardControls {
844
1042
  expandedDetail: ReactNode;
845
1043
  }
846
1044
  type MatrxDataTableDensity = "condensed" | "normal" | "spacious";
1045
+ /**
1046
+ * What the table needs in order to paint a `TableStyle` itself.
1047
+ *
1048
+ * `fieldOf` exists because a style names DATA fields ("tint the row when
1049
+ * `status` is Blocked") while the table addresses COLUMNS. In the common case
1050
+ * the column id IS the field name and the default is right; a table whose
1051
+ * column ids are decorated (`records-grid-notes`) maps them here.
1052
+ */
1053
+ interface MatrxDataTableStyleConfig<T> {
1054
+ /** The style document. */
1055
+ style: TableStyle;
1056
+ /** The row's data as the rules read it. Defaults to the row itself. */
1057
+ documentOf?: (row: T) => Record<string, unknown>;
1058
+ /** Column id to the field name a rule would name. Defaults to identity. */
1059
+ fieldOf?: (columnId: string) => string;
1060
+ /**
1061
+ * The color a choice column's option paints with, for color-by. The consumer
1062
+ * supplies it because option colors live on the resolved option list, which
1063
+ * may come from a shared pick list this package cannot reach.
1064
+ */
1065
+ choiceColorFor?: ChoiceColorLookup;
1066
+ }
847
1067
  interface MatrxDataTableProps<T> {
848
1068
  data: T[];
849
1069
  columns: MatrxColumnDef<T>[];
@@ -953,6 +1173,26 @@ interface MatrxDataTableProps<T> {
953
1173
  footerRows?: T[];
954
1174
  /** Per-row presentation class, after canonical selection/highlight state. */
955
1175
  rowClassName?: (row: T, index: number) => string | undefined;
1176
+ /**
1177
+ * Per-CELL presentation class, after the column's own static `className`.
1178
+ *
1179
+ * The primitive had `rowClassName` and a static per-column `className`, and
1180
+ * nothing in between — so every table that needed to say something about ONE
1181
+ * cell had to grow a `cell` render function and paint inside it, where the
1182
+ * table can no longer see it. This is the missing rung, and `tableStyle`
1183
+ * below is the declarative thing built on top of it.
1184
+ */
1185
+ cellClassName?: (row: T, columnId: string, index: number) => string | undefined;
1186
+ /**
1187
+ * COLORS, declaratively — color by a column, rules, manual highlights.
1188
+ *
1189
+ * The model is `./table-style.ts`: one document, evaluated live on every
1190
+ * render so a tint is never stale, with a documented precedence between the
1191
+ * three ways a cell can earn a color. Pass the document and the table paints
1192
+ * itself; the consumer owns only where that document is STORED and how a
1193
+ * person edits it.
1194
+ */
1195
+ tableStyle?: MatrxDataTableStyleConfig<T>;
956
1196
  /** Highlight an index in the currently rendered page without selecting it. */
957
1197
  highlightedIndex?: number | undefined;
958
1198
  /** Receives the complete visible local view after local processing, before paging. Attaching after absence receives one snapshot; callback identity changes alone do not notify. */
@@ -1018,4 +1258,4 @@ interface MatrxDataTableProps<T> {
1018
1258
  onRowOpen?: (row: T) => void;
1019
1259
  }
1020
1260
 
1021
- export { type AnyOfColumnSearch as A, type MatrxDataTableWindowConfig as B, type ColumnFiltersState as C, type MatrxTablePagination as D, type SortDirection as E, type TableViewQuery as F, type TextFilterMode as G, type ToolbarFacet as H, rankTableSearchRows as I, LAYERED_FILTER_OPERATORS as J, LAYERED_FILTER_OPERATOR_LABELS as K, type LayeredFilterRule as L, type MatrxColumnDef as M, type LayeredFilterField as N, type LayeredFilterOperator as O, columnFiltersToLayeredRules as P, completeLayeredFilterRules as Q, decodeLayeredFilterRules as R, type SortState as S, type TableSearchMatchMode as T, encodeLayeredFilterRules as U, isCompleteLayeredFilterRule as V, isLayeredFilterOperator as W, layeredFilterMatchesValue as X, layeredFilterNeedsValue as Y, layeredFilterRuleSummary as Z, operatorsForLayeredField as _, type ColumnFilterValue as a, type TableSavedViewsProps as b, type MatrxDataTableRecordControls as c, type MatrxDataTableDensity as d, type MatrxDataTableQueryState as e, type ColumnFilterKind as f, type MatrxDataTableCopyConfig as g, type MatrxDataTableProps as h, type TableViewSnapshot as i, type ColumnMarker as j, type CellEditType as k, type CellEditsMap as l, type MatrxDataTableContextMenuConfig as m, type MatrxDataTableDetailConfig as n, type MatrxDataTableEditConfig as o, type MatrxDataTableEmptyState as p, type MatrxDataTableExpandedDetailConfig as q, type MatrxDataTableFilterUi as r, type MatrxDataTableHierarchyConfig as s, type MatrxDataTableHierarchyMove as t, type MatrxDataTableMobileCardControls as u, type MatrxDataTableQueryControl as v, type MatrxDataTableSelectionConfig as w, type MatrxDataTableSourceProcessing as x, type MatrxDataTableToolbar as y, type MatrxDataTableUrlStateConfig as z };
1261
+ export { TABLE_STYLE_METADATA_KEY as $, type AnyOfColumnSearch as A, BOOLEAN_TRUE_COLOR as B, type ColumnFiltersState as C, type MatrxDataTableHierarchyMove as D, EMPTY_TABLE_STYLE as E, type MatrxDataTableMobileCardControls as F, type MatrxDataTableQueryControl as G, type MatrxDataTableSelectionConfig as H, type MatrxDataTableSourceProcessing as I, type MatrxDataTableStyleConfig as J, type MatrxDataTableToolbar as K, type LayeredFilterRule as L, type MatrxColumnDef as M, type MatrxDataTableUrlStateConfig as N, type MatrxDataTableWindowConfig as O, type MatrxTablePagination as P, OPS_WITH_VALUE as Q, ROW_TINT_CLASS as R, type SortState as S, type TableSearchMatchMode as T, STYLE_COLORS as U, STYLE_COLOR_LABELS as V, SWATCH_CLASS as W, type SortDirection as X, type StyleColor as Y, type StylePath as Z, type StyleTarget as _, type ColumnFilterValue as a, TABLE_STYLE_VERSION as a0, type TableRankedSearchOptions as a1, type TableSearchRole as a2, type TableSearchScope as a3, type TableStyle as a4, type TableViewQuery as a5, type TextFilterMode as a6, type ToolbarFacet as a7, applyStylePath as a8, colorForChoice as a9, evaluateRule as aa, inferTableSearchRole as ab, isStyleColor as ac, parseTableStyle as ad, rankTableSearchRows as ae, resolveCellColor as af, resolveRowColor as ag, resolveTableSearchScopes as ah, stylePath as ai, tableStyleFromMetadata as aj, tableStyleIsEmpty as ak, LAYERED_FILTER_OPERATORS as al, LAYERED_FILTER_OPERATOR_LABELS as am, type LayeredFilterField as an, type LayeredFilterOperator as ao, columnFiltersToLayeredRules as ap, completeLayeredFilterRules as aq, decodeLayeredFilterRules as ar, encodeLayeredFilterRules as as, isCompleteLayeredFilterRule as at, isLayeredFilterOperator as au, layeredFilterMatchesValue as av, layeredFilterNeedsValue as aw, layeredFilterRuleSummary as ax, operatorsForLayeredField as ay, type TableSavedViewsProps as b, type MatrxDataTableRecordControls as c, type MatrxDataTableDensity as d, type MatrxDataTableQueryState as e, type ColumnFilterKind as f, type MatrxDataTableCopyConfig as g, type MatrxDataTableProps as h, type TableViewSnapshot as i, type ColumnMarker as j, CELL_TINT_CLASS as k, COLOR_RULE_OPS as l, COLOR_RULE_OP_LABELS as m, type CellEditType as n, type CellEditsMap as o, type ChoiceColorLookup as p, type ColorBy as q, type ColorRule as r, type ColorRuleOp as s, type MatrxDataTableContextMenuConfig as t, type MatrxDataTableDetailConfig as u, type MatrxDataTableEditConfig as v, type MatrxDataTableEmptyState as w, type MatrxDataTableExpandedDetailConfig as x, type MatrxDataTableFilterUi as y, type MatrxDataTableHierarchyConfig as z };
@@ -1,6 +1,7 @@
1
1
  import { ContentTransferReference } from './content-transfer.cjs';
2
2
  import { ReactNode } from 'react';
3
3
  import { AiVariant, AiCustomSource, CopyExportConfig } from './data-table/copy-types.cjs';
4
+ import { b as FieldFormatConfig } from './types-BdbvlF9-.cjs';
4
5
 
5
6
  type TableSearchRole = "id" | "name" | "description" | "other";
6
7
  interface TableSearchScope {
@@ -14,6 +15,9 @@ interface TableRankedSearchOptions {
14
15
  scopes?: readonly TableSearchScope[];
15
16
  matchMode?: TableSearchMatchMode;
16
17
  }
18
+ /** Column metadata overrides inference; identity substrings always outrank prose. */
19
+ declare function inferTableSearchRole(field: string): TableSearchRole;
20
+ declare function resolveTableSearchScopes<T>(rows: readonly T[], columns: readonly MatrxColumnDef<T>[], options?: TableRankedSearchOptions): TableSearchScope[];
17
21
  /** Apply to the complete authorized result BEFORE slicing pages. Remote owners
18
22
  * must implement the same contract at their source, never on a partial page. */
19
23
  declare function rankTableSearchRows<T>(rows: readonly T[], columns: readonly MatrxColumnDef<T>[], query: string, options?: TableRankedSearchOptions, extraText?: (row: T) => string): T[];
@@ -77,6 +81,151 @@ declare function columnFiltersToLayeredRules(filters: ColumnFiltersState, fieldI
77
81
  declare function layeredFilterMatchesValue(value: unknown, rule: LayeredFilterRule): boolean;
78
82
  declare function layeredFilterRuleSummary(rule: LayeredFilterRule, fields: readonly LayeredFilterField[]): string;
79
83
 
84
+ /**
85
+ * Table style — colors on a user data table, the Airtable way.
86
+ *
87
+ * WHAT IS ALLOWED, AND WHY ONLY THIS. A typed dataset is not a canvas; the
88
+ * platform already has Workbooks (Univer) for arbitrary per-cell formatting.
89
+ * Here, color carries MEANING and stays out of the data:
90
+ *
91
+ * 1. COLOR BY A COLUMN — a choice column's option colors (or a boolean's
92
+ * true/false) tint the row or that column's cells. Zero configuration:
93
+ * the Status column already knows "Blocked" is red.
94
+ * 2. RULES — "when Amount > 1000, tint the row amber". Evaluated live, so a
95
+ * tint is never stale.
96
+ * 3. MANUAL HIGHLIGHTS — a small palette on a cell, a row, or a column, for
97
+ * the "look at this one" gesture. The same palette the choice chips use,
98
+ * so the table never has two vocabularies of color.
99
+ *
100
+ * WHERE IT LIVES. This module is the MODEL only — pure, no React, no DOM, no
101
+ * network. Each consumer names its own home for the same document and writes it
102
+ * surgically BY PATH (`stylePath.*`), so two editors highlighting different
103
+ * cells never overwrite each other:
104
+ *
105
+ * - user data tables (`/data`): `udt_datasets.metadata.style`, written by
106
+ * `public.udt_set_table_style(p_table_id, p_path, p_value)`.
107
+ * - the record store: the table's saved view / Table settings document,
108
+ * written by `custom.table_style_set(p_table, p_path, p_value)`.
109
+ *
110
+ * Copy, export, agents and the data itself never see the style.
111
+ *
112
+ * PRECEDENCE (most deliberate wins): a manual cell highlight > a cell rule >
113
+ * color-by targeting cells > a manual column highlight, for the CELL; a manual
114
+ * row highlight > a row rule > color-by targeting rows, for the ROW. A cell
115
+ * tint always paints over the row tint underneath it.
116
+ *
117
+ * Pure module: no React, no DOM, no Supabase.
118
+ */
119
+ declare const TABLE_STYLE_VERSION: 1;
120
+ /**
121
+ * The highlight palette.
122
+ *
123
+ * These are the SAME seven names the choice-chip palette uses minus "neutral"
124
+ * (which means "no color"), spelled out here so the model carries no import at
125
+ * all. Names, never raw hex: a name is legible on both grounds because each one
126
+ * ships its own `dark:` class below, and a retired name degrades to "no color"
127
+ * instead of to an unreadable swatch. A consumer that also draws choice chips
128
+ * must keep its chip palette on these names, or the table grows two vocabularies
129
+ * of color.
130
+ */
131
+ declare const STYLE_COLORS: readonly ["slate", "green", "amber", "red", "blue", "violet", "teal"];
132
+ type StyleColor = (typeof STYLE_COLORS)[number];
133
+ declare function isStyleColor(v: unknown): v is StyleColor;
134
+ declare const COLOR_RULE_OPS: readonly ["is", "is_not", "contains", "is_empty", "not_empty", "is_true", "is_false", "gt", "gte", "lt", "lte"];
135
+ type ColorRuleOp = (typeof COLOR_RULE_OPS)[number];
136
+ /** Which ops take a value to compare against. */
137
+ declare const OPS_WITH_VALUE: readonly ColorRuleOp[];
138
+ declare const COLOR_RULE_OP_LABELS: Record<ColorRuleOp, string>;
139
+ type StyleTarget = "row" | "cell";
140
+ type ColorRule = {
141
+ id: string;
142
+ /** Machine field name the condition reads. */
143
+ field: string;
144
+ op: ColorRuleOp;
145
+ /** Compared as text for `is` / `is_not` / `contains`, as a number for gt/lt. */
146
+ value?: string;
147
+ color: StyleColor;
148
+ /** Tint the whole row, or only this column's cell. */
149
+ target: StyleTarget;
150
+ };
151
+ type ColorBy = {
152
+ field: string;
153
+ target: StyleTarget;
154
+ };
155
+ type TableStyle = {
156
+ version: typeof TABLE_STYLE_VERSION;
157
+ colorBy?: ColorBy | null;
158
+ rules?: ColorRule[];
159
+ /** Manual row highlights, by row id. */
160
+ rows?: Record<string, StyleColor>;
161
+ /** Manual cell highlights, by row id then field name. */
162
+ cells?: Record<string, Record<string, StyleColor>>;
163
+ /** Manual column highlights, by field name. */
164
+ columns?: Record<string, StyleColor>;
165
+ };
166
+ declare const EMPTY_TABLE_STYLE: TableStyle;
167
+ /** The conventional key the style document lives under on a table's own metadata. */
168
+ declare const TABLE_STYLE_METADATA_KEY = "style";
169
+ /** True when nothing in the style would paint anything. */
170
+ declare function tableStyleIsEmpty(style: TableStyle): boolean;
171
+ /**
172
+ * Read a style off table metadata. Tolerant by design: an unknown key, a
173
+ * retired color name or a malformed rule is dropped, never thrown, because a
174
+ * table must still render when its style is stale.
175
+ */
176
+ declare function parseTableStyle(raw: unknown): TableStyle;
177
+ /** Style out of a table-metadata blob (or null / undefined). */
178
+ declare function tableStyleFromMetadata(metadata: unknown): TableStyle;
179
+ /** Does `value` satisfy `rule`? Text ops compare case-insensitively; number ops parse both sides. */
180
+ declare function evaluateRule(rule: ColorRule, value: unknown): boolean;
181
+ /**
182
+ * The color a choice / boolean column gives a value, for color-by. The caller
183
+ * supplies the lookup because option colors live on the resolved choice list
184
+ * (which may come from a structured list), not on the style.
185
+ */
186
+ type ChoiceColorLookup = (fieldName: string, value: unknown) => StyleColor | undefined;
187
+ /**
188
+ * The color an option paints with. An option that DECLARED a color uses it; an
189
+ * option without one gets a stable color from its position in the list, so
190
+ * "color rows by Status" always paints something — Airtable assigns every
191
+ * option a color for the same reason. A column whose options were never
192
+ * colored would otherwise look like color-by did nothing at all.
193
+ */
194
+ declare function colorForChoice(choices: readonly {
195
+ value: string;
196
+ color?: string;
197
+ }[] | undefined, value: unknown): StyleColor | undefined;
198
+ /**
199
+ * Boolean columns have no option list; color-by paints true green and leaves
200
+ * false untinted, which is the "done / not done" reading every checklist has.
201
+ */
202
+ declare const BOOLEAN_TRUE_COLOR: StyleColor;
203
+ /** The tint for a whole row, or null for none. */
204
+ declare function resolveRowColor(style: TableStyle, row: {
205
+ id: string;
206
+ data: Record<string, unknown>;
207
+ }, choiceColorFor: ChoiceColorLookup): StyleColor | null;
208
+ /** The tint for one cell (painted over the row tint), or null for none. */
209
+ declare function resolveCellColor(style: TableStyle, row: {
210
+ id: string;
211
+ data: Record<string, unknown>;
212
+ }, fieldName: string, choiceColorFor: ChoiceColorLookup): StyleColor | null;
213
+ declare const ROW_TINT_CLASS: Record<StyleColor, string>;
214
+ declare const CELL_TINT_CLASS: Record<StyleColor, string>;
215
+ /** A solid swatch for palette menus. */
216
+ declare const SWATCH_CLASS: Record<StyleColor, string>;
217
+ declare const STYLE_COLOR_LABELS: Record<StyleColor, string>;
218
+ type StylePath = readonly string[];
219
+ declare const stylePath: {
220
+ colorBy: () => StylePath;
221
+ rules: () => StylePath;
222
+ row: (rowId: string) => StylePath;
223
+ cell: (rowId: string, fieldName: string) => StylePath;
224
+ column: (fieldName: string) => StylePath;
225
+ };
226
+ /** Apply a path write locally, so the grid repaints before the round trip returns. */
227
+ declare function applyStylePath(style: TableStyle, path: StylePath, value: unknown): TableStyle;
228
+
80
229
  /** How a column's filter UI behaves. `auto` infers from sample values. */
81
230
  type ColumnFilterKind = "auto" | "text" | "select" | "boolean" | "number" | false;
82
231
  type SortDirection = "asc" | "desc";
@@ -256,6 +405,36 @@ interface MatrxColumnDef<T> {
256
405
  /** Force non-navigable for this column. */
257
406
  forbidden?: boolean | ((id: string, row: T) => boolean);
258
407
  };
408
+ /**
409
+ * How this column's value READS — money, percent, a date style, decimal
410
+ * places. The vocabulary is the platform's one field-format registry
411
+ * (`@ai-matrx/design-system/field-formats`), the same `{id, options}` a user
412
+ * data table stores on its column and the record store stores on its Field,
413
+ * so `$1,234.56` is the same `$1,234.56` on every table in the product.
414
+ *
415
+ * A declarative format, not another render function, because a `cell`
416
+ * callback is invisible to everything else: with `format` the column also
417
+ * right-aligns itself, and a consumer's own `cell` asks the primitive for the
418
+ * same text through `formatColumnValue(col, value)` rather than growing a
419
+ * second money formatter beside this one.
420
+ *
421
+ * THE FALLBACK LAW travels with it: a value that does not fit the declared
422
+ * format is never blanked and never throws — it renders its base-type text in
423
+ * amber with the reason on hover, so the person SEES the mismatch.
424
+ *
425
+ * `cell` still wins when both are set.
426
+ */
427
+ format?: FieldFormatConfig;
428
+ /**
429
+ * Keep this column in place while the rest of the table scrolls sideways.
430
+ *
431
+ * Only the LEADING RUN freezes, only from `sm:` up, and only when the widths
432
+ * the offsets need are known — `./frozen-columns.ts` says why each of those
433
+ * three is a rule and not a default. Give a frozen column an explicit
434
+ * `width`; without one the table declines to freeze anything rather than pin
435
+ * a column at a guessed offset.
436
+ */
437
+ frozen?: boolean;
259
438
  className?: string;
260
439
  headerClassName?: string;
261
440
  width?: string | number;
@@ -397,6 +576,14 @@ interface MatrxTablePagination<T> {
397
576
  };
398
577
  retrySource?: () => void;
399
578
  sourceUntil?: string;
579
+ /** Source records differ from rendered rows when the host groups occurrences. */
580
+ sourceRecords?: {
581
+ loaded: number;
582
+ total?: number | undefined;
583
+ label: string;
584
+ rowLabel: string;
585
+ matchedRecords?: number;
586
+ };
400
587
  sourcePageSize?: {
401
588
  value: number;
402
589
  options: readonly number[];
@@ -568,6 +755,17 @@ interface TableViewSnapshot {
568
755
  hidden: string[];
569
756
  widths?: Record<string, number>;
570
757
  };
758
+ /**
759
+ * The view's COLORS — color-by-a-column, rules, and manual highlights
760
+ * (`./table-style.ts`). Optional so every view saved before colors existed
761
+ * still parses; absent means "this view paints nothing".
762
+ *
763
+ * Colors belong to the VIEW and not to the data, for the same reason the
764
+ * filters do: two people can read one table two ways, and neither reading is
765
+ * a fact about the records. That is also why this snapshot is the only place
766
+ * a style is carried — copy, export and agents read the rows, never this.
767
+ */
768
+ style?: TableStyle;
571
769
  }
572
770
  /** Host port for durable named views. The host owns actor, tenancy and CRUD. */
573
771
  interface TableSavedViewsProps {
@@ -844,6 +1042,28 @@ interface MatrxDataTableMobileCardControls {
844
1042
  expandedDetail: ReactNode;
845
1043
  }
846
1044
  type MatrxDataTableDensity = "condensed" | "normal" | "spacious";
1045
+ /**
1046
+ * What the table needs in order to paint a `TableStyle` itself.
1047
+ *
1048
+ * `fieldOf` exists because a style names DATA fields ("tint the row when
1049
+ * `status` is Blocked") while the table addresses COLUMNS. In the common case
1050
+ * the column id IS the field name and the default is right; a table whose
1051
+ * column ids are decorated (`records-grid-notes`) maps them here.
1052
+ */
1053
+ interface MatrxDataTableStyleConfig<T> {
1054
+ /** The style document. */
1055
+ style: TableStyle;
1056
+ /** The row's data as the rules read it. Defaults to the row itself. */
1057
+ documentOf?: (row: T) => Record<string, unknown>;
1058
+ /** Column id to the field name a rule would name. Defaults to identity. */
1059
+ fieldOf?: (columnId: string) => string;
1060
+ /**
1061
+ * The color a choice column's option paints with, for color-by. The consumer
1062
+ * supplies it because option colors live on the resolved option list, which
1063
+ * may come from a shared pick list this package cannot reach.
1064
+ */
1065
+ choiceColorFor?: ChoiceColorLookup;
1066
+ }
847
1067
  interface MatrxDataTableProps<T> {
848
1068
  data: T[];
849
1069
  columns: MatrxColumnDef<T>[];
@@ -953,6 +1173,26 @@ interface MatrxDataTableProps<T> {
953
1173
  footerRows?: T[];
954
1174
  /** Per-row presentation class, after canonical selection/highlight state. */
955
1175
  rowClassName?: (row: T, index: number) => string | undefined;
1176
+ /**
1177
+ * Per-CELL presentation class, after the column's own static `className`.
1178
+ *
1179
+ * The primitive had `rowClassName` and a static per-column `className`, and
1180
+ * nothing in between — so every table that needed to say something about ONE
1181
+ * cell had to grow a `cell` render function and paint inside it, where the
1182
+ * table can no longer see it. This is the missing rung, and `tableStyle`
1183
+ * below is the declarative thing built on top of it.
1184
+ */
1185
+ cellClassName?: (row: T, columnId: string, index: number) => string | undefined;
1186
+ /**
1187
+ * COLORS, declaratively — color by a column, rules, manual highlights.
1188
+ *
1189
+ * The model is `./table-style.ts`: one document, evaluated live on every
1190
+ * render so a tint is never stale, with a documented precedence between the
1191
+ * three ways a cell can earn a color. Pass the document and the table paints
1192
+ * itself; the consumer owns only where that document is STORED and how a
1193
+ * person edits it.
1194
+ */
1195
+ tableStyle?: MatrxDataTableStyleConfig<T>;
956
1196
  /** Highlight an index in the currently rendered page without selecting it. */
957
1197
  highlightedIndex?: number | undefined;
958
1198
  /** Receives the complete visible local view after local processing, before paging. Attaching after absence receives one snapshot; callback identity changes alone do not notify. */
@@ -1018,4 +1258,4 @@ interface MatrxDataTableProps<T> {
1018
1258
  onRowOpen?: (row: T) => void;
1019
1259
  }
1020
1260
 
1021
- export { type AnyOfColumnSearch as A, type MatrxDataTableWindowConfig as B, type ColumnFiltersState as C, type MatrxTablePagination as D, type SortDirection as E, type TableViewQuery as F, type TextFilterMode as G, type ToolbarFacet as H, rankTableSearchRows as I, LAYERED_FILTER_OPERATORS as J, LAYERED_FILTER_OPERATOR_LABELS as K, type LayeredFilterRule as L, type MatrxColumnDef as M, type LayeredFilterField as N, type LayeredFilterOperator as O, columnFiltersToLayeredRules as P, completeLayeredFilterRules as Q, decodeLayeredFilterRules as R, type SortState as S, type TableSearchMatchMode as T, encodeLayeredFilterRules as U, isCompleteLayeredFilterRule as V, isLayeredFilterOperator as W, layeredFilterMatchesValue as X, layeredFilterNeedsValue as Y, layeredFilterRuleSummary as Z, operatorsForLayeredField as _, type ColumnFilterValue as a, type TableSavedViewsProps as b, type MatrxDataTableRecordControls as c, type MatrxDataTableDensity as d, type MatrxDataTableQueryState as e, type ColumnFilterKind as f, type MatrxDataTableCopyConfig as g, type MatrxDataTableProps as h, type TableViewSnapshot as i, type ColumnMarker as j, type CellEditType as k, type CellEditsMap as l, type MatrxDataTableContextMenuConfig as m, type MatrxDataTableDetailConfig as n, type MatrxDataTableEditConfig as o, type MatrxDataTableEmptyState as p, type MatrxDataTableExpandedDetailConfig as q, type MatrxDataTableFilterUi as r, type MatrxDataTableHierarchyConfig as s, type MatrxDataTableHierarchyMove as t, type MatrxDataTableMobileCardControls as u, type MatrxDataTableQueryControl as v, type MatrxDataTableSelectionConfig as w, type MatrxDataTableSourceProcessing as x, type MatrxDataTableToolbar as y, type MatrxDataTableUrlStateConfig as z };
1261
+ export { TABLE_STYLE_METADATA_KEY as $, type AnyOfColumnSearch as A, BOOLEAN_TRUE_COLOR as B, type ColumnFiltersState as C, type MatrxDataTableHierarchyMove as D, EMPTY_TABLE_STYLE as E, type MatrxDataTableMobileCardControls as F, type MatrxDataTableQueryControl as G, type MatrxDataTableSelectionConfig as H, type MatrxDataTableSourceProcessing as I, type MatrxDataTableStyleConfig as J, type MatrxDataTableToolbar as K, type LayeredFilterRule as L, type MatrxColumnDef as M, type MatrxDataTableUrlStateConfig as N, type MatrxDataTableWindowConfig as O, type MatrxTablePagination as P, OPS_WITH_VALUE as Q, ROW_TINT_CLASS as R, type SortState as S, type TableSearchMatchMode as T, STYLE_COLORS as U, STYLE_COLOR_LABELS as V, SWATCH_CLASS as W, type SortDirection as X, type StyleColor as Y, type StylePath as Z, type StyleTarget as _, type ColumnFilterValue as a, TABLE_STYLE_VERSION as a0, type TableRankedSearchOptions as a1, type TableSearchRole as a2, type TableSearchScope as a3, type TableStyle as a4, type TableViewQuery as a5, type TextFilterMode as a6, type ToolbarFacet as a7, applyStylePath as a8, colorForChoice as a9, evaluateRule as aa, inferTableSearchRole as ab, isStyleColor as ac, parseTableStyle as ad, rankTableSearchRows as ae, resolveCellColor as af, resolveRowColor as ag, resolveTableSearchScopes as ah, stylePath as ai, tableStyleFromMetadata as aj, tableStyleIsEmpty as ak, LAYERED_FILTER_OPERATORS as al, LAYERED_FILTER_OPERATOR_LABELS as am, type LayeredFilterField as an, type LayeredFilterOperator as ao, columnFiltersToLayeredRules as ap, completeLayeredFilterRules as aq, decodeLayeredFilterRules as ar, encodeLayeredFilterRules as as, isCompleteLayeredFilterRule as at, isLayeredFilterOperator as au, layeredFilterMatchesValue as av, layeredFilterNeedsValue as aw, layeredFilterRuleSummary as ax, operatorsForLayeredField as ay, type TableSavedViewsProps as b, type MatrxDataTableRecordControls as c, type MatrxDataTableDensity as d, type MatrxDataTableQueryState as e, type ColumnFilterKind as f, type MatrxDataTableCopyConfig as g, type MatrxDataTableProps as h, type TableViewSnapshot as i, type ColumnMarker as j, CELL_TINT_CLASS as k, COLOR_RULE_OPS as l, COLOR_RULE_OP_LABELS as m, type CellEditType as n, type CellEditsMap as o, type ChoiceColorLookup as p, type ColorBy as q, type ColorRule as r, type ColorRuleOp as s, type MatrxDataTableContextMenuConfig as t, type MatrxDataTableDetailConfig as u, type MatrxDataTableEditConfig as v, type MatrxDataTableEmptyState as w, type MatrxDataTableExpandedDetailConfig as x, type MatrxDataTableFilterUi as y, type MatrxDataTableHierarchyConfig as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/design-system",
3
- "version": "0.21.13",
3
+ "version": "0.22.0",
4
4
  "description": "AI Matrx semantic React primitives shared across Vite, Next.js, desktop, and customer-built applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -78,6 +78,16 @@
78
78
  "default": "./dist/data-table/index.cjs"
79
79
  }
80
80
  },
81
+ "./field-formats": {
82
+ "import": {
83
+ "types": "./dist/field-formats/index.d.ts",
84
+ "default": "./dist/field-formats/index.js"
85
+ },
86
+ "require": {
87
+ "types": "./dist/field-formats/index.d.cts",
88
+ "default": "./dist/field-formats/index.cjs"
89
+ }
90
+ },
81
91
  "./data-table/host": {
82
92
  "import": {
83
93
  "types": "./dist/data-table/host.d.ts",