@ai-matrx/design-system 0.21.14 → 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 (37) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/data-table/copy-helpers.d.cts +2 -1
  3. package/dist/data-table/copy-helpers.d.ts +2 -1
  4. package/dist/data-table/filter-engine.d.cts +2 -1
  5. package/dist/data-table/filter-engine.d.ts +2 -1
  6. package/dist/data-table/host.d.cts +2 -1
  7. package/dist/data-table/host.d.ts +2 -1
  8. package/dist/data-table/index.cjs +1525 -398
  9. package/dist/data-table/index.cjs.map +1 -1
  10. package/dist/data-table/index.d.cts +91 -3
  11. package/dist/data-table/index.d.ts +91 -3
  12. package/dist/data-table/index.js +1526 -395
  13. package/dist/data-table/index.js.map +1 -1
  14. package/dist/data-table/infer-filter.d.cts +2 -1
  15. package/dist/data-table/infer-filter.d.ts +2 -1
  16. package/dist/data-table/layered-filters.d.cts +2 -1
  17. package/dist/data-table/layered-filters.d.ts +2 -1
  18. package/dist/data-table/query-control.d.cts +2 -1
  19. package/dist/data-table/query-control.d.ts +2 -1
  20. package/dist/data-table/types.cjs.map +1 -1
  21. package/dist/data-table/types.d.cts +2 -1
  22. package/dist/data-table/types.d.ts +2 -1
  23. package/dist/data-table/url-state.d.cts +2 -1
  24. package/dist/data-table/url-state.d.ts +2 -1
  25. package/dist/data-table/xlsx.d.cts +2 -1
  26. package/dist/data-table/xlsx.d.ts +2 -1
  27. package/dist/field-formats/index.cjs +741 -0
  28. package/dist/field-formats/index.cjs.map +1 -0
  29. package/dist/field-formats/index.d.cts +45 -0
  30. package/dist/field-formats/index.d.ts +45 -0
  31. package/dist/field-formats/index.js +723 -0
  32. package/dist/field-formats/index.js.map +1 -0
  33. package/dist/types-BdbvlF9-.d.cts +204 -0
  34. package/dist/types-BdbvlF9-.d.ts +204 -0
  35. package/dist/{types-emLxze_m.d.ts → types-D-flTpK0.d.ts} +230 -1
  36. package/dist/{types-BgqIKbhq.d.cts → types-Sn249eoy.d.cts} +230 -1
  37. 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 {
@@ -80,6 +81,151 @@ declare function columnFiltersToLayeredRules(filters: ColumnFiltersState, fieldI
80
81
  declare function layeredFilterMatchesValue(value: unknown, rule: LayeredFilterRule): boolean;
81
82
  declare function layeredFilterRuleSummary(rule: LayeredFilterRule, fields: readonly LayeredFilterField[]): string;
82
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
+
83
229
  /** How a column's filter UI behaves. `auto` infers from sample values. */
84
230
  type ColumnFilterKind = "auto" | "text" | "select" | "boolean" | "number" | false;
85
231
  type SortDirection = "asc" | "desc";
@@ -259,6 +405,36 @@ interface MatrxColumnDef<T> {
259
405
  /** Force non-navigable for this column. */
260
406
  forbidden?: boolean | ((id: string, row: T) => boolean);
261
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;
262
438
  className?: string;
263
439
  headerClassName?: string;
264
440
  width?: string | number;
@@ -579,6 +755,17 @@ interface TableViewSnapshot {
579
755
  hidden: string[];
580
756
  widths?: Record<string, number>;
581
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;
582
769
  }
583
770
  /** Host port for durable named views. The host owns actor, tenancy and CRUD. */
584
771
  interface TableSavedViewsProps {
@@ -855,6 +1042,28 @@ interface MatrxDataTableMobileCardControls {
855
1042
  expandedDetail: ReactNode;
856
1043
  }
857
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
+ }
858
1067
  interface MatrxDataTableProps<T> {
859
1068
  data: T[];
860
1069
  columns: MatrxColumnDef<T>[];
@@ -964,6 +1173,26 @@ interface MatrxDataTableProps<T> {
964
1173
  footerRows?: T[];
965
1174
  /** Per-row presentation class, after canonical selection/highlight state. */
966
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>;
967
1196
  /** Highlight an index in the currently rendered page without selecting it. */
968
1197
  highlightedIndex?: number | undefined;
969
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. */
@@ -1029,4 +1258,4 @@ interface MatrxDataTableProps<T> {
1029
1258
  onRowOpen?: (row: T) => void;
1030
1259
  }
1031
1260
 
1032
- export { isLayeredFilterOperator as $, type AnyOfColumnSearch as A, type MatrxDataTableWindowConfig as B, type ColumnFiltersState as C, type MatrxTablePagination as D, type SortDirection as E, type TableRankedSearchOptions as F, type TableSearchRole as G, type TableSearchScope as H, type TableViewQuery as I, type TextFilterMode as J, type ToolbarFacet as K, type LayeredFilterRule as L, type MatrxColumnDef as M, inferTableSearchRole as N, rankTableSearchRows as O, resolveTableSearchScopes as P, LAYERED_FILTER_OPERATORS as Q, LAYERED_FILTER_OPERATOR_LABELS as R, type SortState as S, type TableSearchMatchMode as T, type LayeredFilterField as U, type LayeredFilterOperator as V, columnFiltersToLayeredRules as W, completeLayeredFilterRules as X, decodeLayeredFilterRules as Y, encodeLayeredFilterRules as Z, isCompleteLayeredFilterRule as _, type ColumnFilterValue as a, layeredFilterMatchesValue as a0, layeredFilterNeedsValue as a1, layeredFilterRuleSummary as a2, operatorsForLayeredField as a3, 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 {
@@ -80,6 +81,151 @@ declare function columnFiltersToLayeredRules(filters: ColumnFiltersState, fieldI
80
81
  declare function layeredFilterMatchesValue(value: unknown, rule: LayeredFilterRule): boolean;
81
82
  declare function layeredFilterRuleSummary(rule: LayeredFilterRule, fields: readonly LayeredFilterField[]): string;
82
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
+
83
229
  /** How a column's filter UI behaves. `auto` infers from sample values. */
84
230
  type ColumnFilterKind = "auto" | "text" | "select" | "boolean" | "number" | false;
85
231
  type SortDirection = "asc" | "desc";
@@ -259,6 +405,36 @@ interface MatrxColumnDef<T> {
259
405
  /** Force non-navigable for this column. */
260
406
  forbidden?: boolean | ((id: string, row: T) => boolean);
261
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;
262
438
  className?: string;
263
439
  headerClassName?: string;
264
440
  width?: string | number;
@@ -579,6 +755,17 @@ interface TableViewSnapshot {
579
755
  hidden: string[];
580
756
  widths?: Record<string, number>;
581
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;
582
769
  }
583
770
  /** Host port for durable named views. The host owns actor, tenancy and CRUD. */
584
771
  interface TableSavedViewsProps {
@@ -855,6 +1042,28 @@ interface MatrxDataTableMobileCardControls {
855
1042
  expandedDetail: ReactNode;
856
1043
  }
857
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
+ }
858
1067
  interface MatrxDataTableProps<T> {
859
1068
  data: T[];
860
1069
  columns: MatrxColumnDef<T>[];
@@ -964,6 +1173,26 @@ interface MatrxDataTableProps<T> {
964
1173
  footerRows?: T[];
965
1174
  /** Per-row presentation class, after canonical selection/highlight state. */
966
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>;
967
1196
  /** Highlight an index in the currently rendered page without selecting it. */
968
1197
  highlightedIndex?: number | undefined;
969
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. */
@@ -1029,4 +1258,4 @@ interface MatrxDataTableProps<T> {
1029
1258
  onRowOpen?: (row: T) => void;
1030
1259
  }
1031
1260
 
1032
- export { isLayeredFilterOperator as $, type AnyOfColumnSearch as A, type MatrxDataTableWindowConfig as B, type ColumnFiltersState as C, type MatrxTablePagination as D, type SortDirection as E, type TableRankedSearchOptions as F, type TableSearchRole as G, type TableSearchScope as H, type TableViewQuery as I, type TextFilterMode as J, type ToolbarFacet as K, type LayeredFilterRule as L, type MatrxColumnDef as M, inferTableSearchRole as N, rankTableSearchRows as O, resolveTableSearchScopes as P, LAYERED_FILTER_OPERATORS as Q, LAYERED_FILTER_OPERATOR_LABELS as R, type SortState as S, type TableSearchMatchMode as T, type LayeredFilterField as U, type LayeredFilterOperator as V, columnFiltersToLayeredRules as W, completeLayeredFilterRules as X, decodeLayeredFilterRules as Y, encodeLayeredFilterRules as Z, isCompleteLayeredFilterRule as _, type ColumnFilterValue as a, layeredFilterMatchesValue as a0, layeredFilterNeedsValue as a1, layeredFilterRuleSummary as a2, operatorsForLayeredField as a3, 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.14",
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",