@ai-matrx/design-system 0.22.0 → 0.25.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.
- package/CHANGELOG.md +151 -0
- package/dist/data-table/cell-undo.cjs +98 -0
- package/dist/data-table/cell-undo.cjs.map +1 -0
- package/dist/data-table/cell-undo.d.cts +85 -0
- package/dist/data-table/cell-undo.d.ts +85 -0
- package/dist/data-table/cell-undo.js +78 -0
- package/dist/data-table/cell-undo.js.map +1 -0
- package/dist/data-table/copy-helpers.d.cts +6 -2
- package/dist/data-table/copy-helpers.d.ts +6 -2
- package/dist/data-table/coverage.cjs +116 -0
- package/dist/data-table/coverage.cjs.map +1 -0
- package/dist/data-table/coverage.d.cts +65 -0
- package/dist/data-table/coverage.d.ts +65 -0
- package/dist/data-table/coverage.js +96 -0
- package/dist/data-table/coverage.js.map +1 -0
- package/dist/data-table/fill.cjs +299 -0
- package/dist/data-table/fill.cjs.map +1 -0
- package/dist/data-table/fill.d.cts +129 -0
- package/dist/data-table/fill.d.ts +129 -0
- package/dist/data-table/fill.js +277 -0
- package/dist/data-table/fill.js.map +1 -0
- package/dist/data-table/filter-engine.d.cts +6 -2
- package/dist/data-table/filter-engine.d.ts +6 -2
- package/dist/data-table/grid-clipboard.cjs +165 -0
- package/dist/data-table/grid-clipboard.cjs.map +1 -0
- package/dist/data-table/grid-clipboard.d.cts +85 -0
- package/dist/data-table/grid-clipboard.d.ts +85 -0
- package/dist/data-table/grid-clipboard.js +145 -0
- package/dist/data-table/grid-clipboard.js.map +1 -0
- package/dist/data-table/grid-selection.cjs +258 -0
- package/dist/data-table/grid-selection.cjs.map +1 -0
- package/dist/data-table/grid-selection.d.cts +166 -0
- package/dist/data-table/grid-selection.d.ts +166 -0
- package/dist/data-table/grid-selection.js +238 -0
- package/dist/data-table/grid-selection.js.map +1 -0
- package/dist/data-table/grouping.cjs +302 -0
- package/dist/data-table/grouping.cjs.map +1 -0
- package/dist/data-table/grouping.d.cts +119 -0
- package/dist/data-table/grouping.d.ts +119 -0
- package/dist/data-table/grouping.js +282 -0
- package/dist/data-table/grouping.js.map +1 -0
- package/dist/data-table/host.d.cts +6 -2
- package/dist/data-table/host.d.ts +6 -2
- package/dist/data-table/index.cjs +2433 -246
- package/dist/data-table/index.cjs.map +1 -1
- package/dist/data-table/index.d.cts +11 -3
- package/dist/data-table/index.d.ts +11 -3
- package/dist/data-table/index.js +2425 -238
- package/dist/data-table/index.js.map +1 -1
- package/dist/data-table/infer-filter.d.cts +6 -2
- package/dist/data-table/infer-filter.d.ts +6 -2
- package/dist/data-table/layered-filters.d.cts +6 -2
- package/dist/data-table/layered-filters.d.ts +6 -2
- package/dist/data-table/query-control.d.cts +6 -2
- package/dist/data-table/query-control.d.ts +6 -2
- package/dist/data-table/spreadsheet.cjs +1014 -0
- package/dist/data-table/spreadsheet.cjs.map +1 -0
- package/dist/data-table/spreadsheet.d.cts +118 -0
- package/dist/data-table/spreadsheet.d.ts +118 -0
- package/dist/data-table/spreadsheet.js +994 -0
- package/dist/data-table/spreadsheet.js.map +1 -0
- package/dist/data-table/types.cjs.map +1 -1
- package/dist/data-table/types.d.cts +6 -2
- package/dist/data-table/types.d.ts +6 -2
- package/dist/data-table/url-state.d.cts +6 -2
- package/dist/data-table/url-state.d.ts +6 -2
- package/dist/data-table/validation.cjs +229 -0
- package/dist/data-table/validation.cjs.map +1 -0
- package/dist/data-table/validation.d.cts +125 -0
- package/dist/data-table/validation.d.ts +125 -0
- package/dist/data-table/validation.js +209 -0
- package/dist/data-table/validation.js.map +1 -0
- package/dist/data-table/virtual-window.cjs +203 -0
- package/dist/data-table/virtual-window.cjs.map +1 -0
- package/dist/data-table/virtual-window.d.cts +84 -0
- package/dist/data-table/virtual-window.d.ts +84 -0
- package/dist/data-table/virtual-window.js +183 -0
- package/dist/data-table/virtual-window.js.map +1 -0
- package/dist/data-table/xlsx.d.cts +6 -2
- package/dist/data-table/xlsx.d.ts +6 -2
- package/dist/field-formats/index.d.cts +2 -2
- package/dist/field-formats/index.d.ts +2 -2
- package/dist/formulas/index.cjs +1041 -0
- package/dist/formulas/index.cjs.map +1 -0
- package/dist/formulas/index.d.cts +389 -0
- package/dist/formulas/index.d.ts +389 -0
- package/dist/formulas/index.js +1019 -0
- package/dist/formulas/index.js.map +1 -0
- package/dist/{types-BdbvlF9-.d.cts → types-CJWU1Gae.d.cts} +1 -1
- package/dist/{types-BdbvlF9-.d.ts → types-CJWU1Gae.d.ts} +1 -1
- package/dist/{types-Sn249eoy.d.cts → types-CmQrOz-T.d.cts} +154 -2
- package/dist/{types-D-flTpK0.d.ts → types-nlLCMiqd.d.ts} +154 -2
- package/package.json +109 -2
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ai-matrx/design-system/formulas — THE formula language for computed columns.
|
|
3
|
+
*
|
|
4
|
+
* This module moved here from the app so every table in the product shares
|
|
5
|
+
* one formula language. It stays pure: no React, no DOM, no network.
|
|
6
|
+
*
|
|
7
|
+
* A **formula column** holds nothing. Its cell is always `null` in the
|
|
8
|
+
* database; the value the user sees is computed, on read, from the OTHER
|
|
9
|
+
* columns of the SAME row. Champion: Airtable's formula field.
|
|
10
|
+
*
|
|
11
|
+
* THE THREE RULES THIS FILE OBEYS
|
|
12
|
+
*
|
|
13
|
+
* 1. **No `eval`, no `new Function`, no dynamic code.** A formula is user
|
|
14
|
+
* content; it is tokenized, parsed into an AST, and interpreted. Nothing a
|
|
15
|
+
* user types ever becomes JavaScript.
|
|
16
|
+
* 2. **Nothing throws.** `parseFormula` and `evaluateFormula` both return a
|
|
17
|
+
* discriminated result. A syntax error, a missing column, a division by
|
|
18
|
+
* zero and a type mismatch are all `ok: false` with a plain-English
|
|
19
|
+
* sentence a non-technical person can act on — never an exception, never a
|
|
20
|
+
* silent blank cell (THE FALLBACK LAW, `lib/field-formats/FEATURE.md`).
|
|
21
|
+
* 3. **The engine is pure.** It reads cells through a caller-supplied
|
|
22
|
+
* `resolve(name)`. It never touches Redux, Supabase, or the DOM, which is
|
|
23
|
+
* what makes every rule below testable.
|
|
24
|
+
*
|
|
25
|
+
* THE COERCION RULES (the whole contract, in one place)
|
|
26
|
+
*
|
|
27
|
+
* - **BLANK** is `null`, `undefined`, or `""`. All three are the same value to
|
|
28
|
+
* a formula, because a text cell that was cleared holds `""` and a number
|
|
29
|
+
* cell that was cleared holds `null`, and a user does not distinguish them.
|
|
30
|
+
* - **A missing column is NOT blank.** `resolve()` returns `undefined` for a
|
|
31
|
+
* column that does not exist (as opposed to `null` for one that is empty),
|
|
32
|
+
* and that is an error: `{a} + {Typo}` must say the column is unknown rather
|
|
33
|
+
* than quietly computing with zero.
|
|
34
|
+
* - **Arithmetic treats BLANK as 0.** `{a} + {b}` with `b` empty is `{a}`.
|
|
35
|
+
* That is the tolerant reading every spreadsheet uses, and the alternative
|
|
36
|
+
* (the whole cell erroring because one input is empty) makes a formula
|
|
37
|
+
* column useless on real, partly-filled data.
|
|
38
|
+
* - **Numeric strings coerce.** `"1,234"`, `" 42 "`, `"$5"` and `"3.5"` are
|
|
39
|
+
* numbers in arithmetic; `"n/a"` is a type error naming the value.
|
|
40
|
+
* - **Booleans are 1 and 0 in arithmetic**, so SUM over a checkbox column
|
|
41
|
+
* counts the ticks.
|
|
42
|
+
* - **Aggregates SKIP blanks.** `SUM`/`MIN`/`MAX`/`AVERAGE` ignore empty
|
|
43
|
+
* inputs entirely — `AVERAGE` divides by the count of the values that were
|
|
44
|
+
* actually there, and returns BLANK when there were none.
|
|
45
|
+
* - **Text conversion**: BLANK becomes `""`, a number becomes its shortest
|
|
46
|
+
* exact decimal, a boolean becomes `"true"` / `"false"`.
|
|
47
|
+
* - **Comparison** is numeric when both sides read as numbers, otherwise a
|
|
48
|
+
* plain string comparison. BLANK takes the shape of what it is compared
|
|
49
|
+
* against — `0` beside a number, `""` beside text — so `{a} = BLANK()`,
|
|
50
|
+
* `{a} = 0` and `{a} = ''` all hold for an empty cell.
|
|
51
|
+
* - **Truthiness** (for `IF` / `AND` / `OR` / `NOT`): BLANK is false, a
|
|
52
|
+
* boolean is itself, a number is true unless `0`, a string is true unless
|
|
53
|
+
* empty.
|
|
54
|
+
* - **Dates are ISO strings, read and written in UTC.** A date function
|
|
55
|
+
* accepts an ISO string (or a `Date`) and returns an ISO string — date-only
|
|
56
|
+
* (`2026-09-14`) when the input was date-only, full (`2026-09-14T…Z`)
|
|
57
|
+
* otherwise. UTC throughout on purpose: reading `2026-09-14` with local
|
|
58
|
+
* getters turns it into the 13th for half the planet.
|
|
59
|
+
* - **Division (and `%`) by zero is an error**, never `Infinity` and never
|
|
60
|
+
* `NaN`. A cell may not lie about arithmetic that did not happen.
|
|
61
|
+
*/
|
|
62
|
+
type FormulaValue = string | number | boolean | null;
|
|
63
|
+
type BinaryOperator = "+" | "-" | "*" | "/" | "%" | "&" | "=" | "!=" | "<>" | "<" | "<=" | ">" | ">=";
|
|
64
|
+
type FormulaAst = {
|
|
65
|
+
kind: "number";
|
|
66
|
+
value: number;
|
|
67
|
+
position: number;
|
|
68
|
+
} | {
|
|
69
|
+
kind: "string";
|
|
70
|
+
value: string;
|
|
71
|
+
position: number;
|
|
72
|
+
} | {
|
|
73
|
+
kind: "boolean";
|
|
74
|
+
value: boolean;
|
|
75
|
+
position: number;
|
|
76
|
+
} | {
|
|
77
|
+
kind: "reference";
|
|
78
|
+
name: string;
|
|
79
|
+
position: number;
|
|
80
|
+
} | {
|
|
81
|
+
kind: "unary";
|
|
82
|
+
operator: "-";
|
|
83
|
+
operand: FormulaAst;
|
|
84
|
+
position: number;
|
|
85
|
+
} | {
|
|
86
|
+
kind: "binary";
|
|
87
|
+
operator: BinaryOperator;
|
|
88
|
+
left: FormulaAst;
|
|
89
|
+
right: FormulaAst;
|
|
90
|
+
position: number;
|
|
91
|
+
} | {
|
|
92
|
+
kind: "call";
|
|
93
|
+
name: FormulaFunctionName;
|
|
94
|
+
args: FormulaAst[];
|
|
95
|
+
position: number;
|
|
96
|
+
};
|
|
97
|
+
type ParseResult = {
|
|
98
|
+
ok: true;
|
|
99
|
+
ast: FormulaAst;
|
|
100
|
+
references: string[];
|
|
101
|
+
} | {
|
|
102
|
+
ok: false;
|
|
103
|
+
error: string;
|
|
104
|
+
position: number;
|
|
105
|
+
};
|
|
106
|
+
type FormulaResult = {
|
|
107
|
+
ok: true;
|
|
108
|
+
value: FormulaValue;
|
|
109
|
+
} | {
|
|
110
|
+
ok: false;
|
|
111
|
+
error: string;
|
|
112
|
+
};
|
|
113
|
+
type FormulaResultType = "number" | "text" | "boolean" | "date" | "unknown";
|
|
114
|
+
/** What a cell lookup may answer: a value, BLANK, or `undefined` = no such column. */
|
|
115
|
+
type ResolveCell = (name: string) => unknown;
|
|
116
|
+
/** Best-effort declared type of a column, for static result typing. */
|
|
117
|
+
type ResolveColumnType = (name: string) => "number" | "text" | "boolean" | "date" | undefined;
|
|
118
|
+
declare const FUNCTION_SPECS: {
|
|
119
|
+
readonly SUM: {
|
|
120
|
+
readonly signature: "SUM(number, …)";
|
|
121
|
+
readonly description: "Adds every value, ignoring empty ones.";
|
|
122
|
+
readonly min: 1;
|
|
123
|
+
readonly max: number;
|
|
124
|
+
readonly result: "number";
|
|
125
|
+
};
|
|
126
|
+
readonly MIN: {
|
|
127
|
+
readonly signature: "MIN(number, …)";
|
|
128
|
+
readonly description: "The smallest value, ignoring empty ones.";
|
|
129
|
+
readonly min: 1;
|
|
130
|
+
readonly max: number;
|
|
131
|
+
readonly result: "number";
|
|
132
|
+
};
|
|
133
|
+
readonly MAX: {
|
|
134
|
+
readonly signature: "MAX(number, …)";
|
|
135
|
+
readonly description: "The largest value, ignoring empty ones.";
|
|
136
|
+
readonly min: 1;
|
|
137
|
+
readonly max: number;
|
|
138
|
+
readonly result: "number";
|
|
139
|
+
};
|
|
140
|
+
readonly AVERAGE: {
|
|
141
|
+
readonly signature: "AVERAGE(number, …)";
|
|
142
|
+
readonly description: "The average of the values that are filled in. Empty values are not counted.";
|
|
143
|
+
readonly min: 1;
|
|
144
|
+
readonly max: number;
|
|
145
|
+
readonly result: "number";
|
|
146
|
+
};
|
|
147
|
+
readonly ROUND: {
|
|
148
|
+
readonly signature: "ROUND(number, places?)";
|
|
149
|
+
readonly description: "Rounds to the given number of decimal places (0 when omitted). Halves round away from zero.";
|
|
150
|
+
readonly min: 1;
|
|
151
|
+
readonly max: 2;
|
|
152
|
+
readonly result: "number";
|
|
153
|
+
};
|
|
154
|
+
readonly ABS: {
|
|
155
|
+
readonly signature: "ABS(number)";
|
|
156
|
+
readonly description: "The value without its minus sign.";
|
|
157
|
+
readonly min: 1;
|
|
158
|
+
readonly max: 1;
|
|
159
|
+
readonly result: "number";
|
|
160
|
+
};
|
|
161
|
+
readonly LEN: {
|
|
162
|
+
readonly signature: "LEN(text)";
|
|
163
|
+
readonly description: "How many characters the text has.";
|
|
164
|
+
readonly min: 1;
|
|
165
|
+
readonly max: 1;
|
|
166
|
+
readonly result: "number";
|
|
167
|
+
};
|
|
168
|
+
readonly UPPER: {
|
|
169
|
+
readonly signature: "UPPER(text)";
|
|
170
|
+
readonly description: "The text in capitals.";
|
|
171
|
+
readonly min: 1;
|
|
172
|
+
readonly max: 1;
|
|
173
|
+
readonly result: "text";
|
|
174
|
+
};
|
|
175
|
+
readonly LOWER: {
|
|
176
|
+
readonly signature: "LOWER(text)";
|
|
177
|
+
readonly description: "The text in lower case.";
|
|
178
|
+
readonly min: 1;
|
|
179
|
+
readonly max: 1;
|
|
180
|
+
readonly result: "text";
|
|
181
|
+
};
|
|
182
|
+
readonly TRIM: {
|
|
183
|
+
readonly signature: "TRIM(text)";
|
|
184
|
+
readonly description: "The text without leading or trailing spaces.";
|
|
185
|
+
readonly min: 1;
|
|
186
|
+
readonly max: 1;
|
|
187
|
+
readonly result: "text";
|
|
188
|
+
};
|
|
189
|
+
readonly CONCATENATE: {
|
|
190
|
+
readonly signature: "CONCATENATE(text, …)";
|
|
191
|
+
readonly description: "Joins every value into one piece of text.";
|
|
192
|
+
readonly min: 1;
|
|
193
|
+
readonly max: number;
|
|
194
|
+
readonly result: "text";
|
|
195
|
+
};
|
|
196
|
+
readonly LEFT: {
|
|
197
|
+
readonly signature: "LEFT(text, count)";
|
|
198
|
+
readonly description: "The first few characters of the text.";
|
|
199
|
+
readonly min: 2;
|
|
200
|
+
readonly max: 2;
|
|
201
|
+
readonly result: "text";
|
|
202
|
+
};
|
|
203
|
+
readonly RIGHT: {
|
|
204
|
+
readonly signature: "RIGHT(text, count)";
|
|
205
|
+
readonly description: "The last few characters of the text.";
|
|
206
|
+
readonly min: 2;
|
|
207
|
+
readonly max: 2;
|
|
208
|
+
readonly result: "text";
|
|
209
|
+
};
|
|
210
|
+
readonly CONTAINS: {
|
|
211
|
+
readonly signature: "CONTAINS(text, part)";
|
|
212
|
+
readonly description: "Yes when the text contains that part. Upper and lower case are treated the same.";
|
|
213
|
+
readonly min: 2;
|
|
214
|
+
readonly max: 2;
|
|
215
|
+
readonly result: "boolean";
|
|
216
|
+
};
|
|
217
|
+
readonly IF: {
|
|
218
|
+
readonly signature: "IF(condition, then, otherwise?)";
|
|
219
|
+
readonly description: "The second value when the condition holds, otherwise the third (empty when omitted).";
|
|
220
|
+
readonly min: 2;
|
|
221
|
+
readonly max: 3;
|
|
222
|
+
readonly result: "unknown";
|
|
223
|
+
};
|
|
224
|
+
readonly AND: {
|
|
225
|
+
readonly signature: "AND(condition, …)";
|
|
226
|
+
readonly description: "Yes when every condition holds.";
|
|
227
|
+
readonly min: 1;
|
|
228
|
+
readonly max: number;
|
|
229
|
+
readonly result: "boolean";
|
|
230
|
+
};
|
|
231
|
+
readonly OR: {
|
|
232
|
+
readonly signature: "OR(condition, …)";
|
|
233
|
+
readonly description: "Yes when at least one condition holds.";
|
|
234
|
+
readonly min: 1;
|
|
235
|
+
readonly max: number;
|
|
236
|
+
readonly result: "boolean";
|
|
237
|
+
};
|
|
238
|
+
readonly NOT: {
|
|
239
|
+
readonly signature: "NOT(condition)";
|
|
240
|
+
readonly description: "Turns yes into no and no into yes.";
|
|
241
|
+
readonly min: 1;
|
|
242
|
+
readonly max: 1;
|
|
243
|
+
readonly result: "boolean";
|
|
244
|
+
};
|
|
245
|
+
readonly BLANK: {
|
|
246
|
+
readonly signature: "BLANK()";
|
|
247
|
+
readonly description: "An empty value, for comparing against or returning.";
|
|
248
|
+
readonly min: 0;
|
|
249
|
+
readonly max: 0;
|
|
250
|
+
readonly result: "unknown";
|
|
251
|
+
};
|
|
252
|
+
readonly ISBLANK: {
|
|
253
|
+
readonly signature: "ISBLANK(value)";
|
|
254
|
+
readonly description: "Yes when the value is empty.";
|
|
255
|
+
readonly min: 1;
|
|
256
|
+
readonly max: 1;
|
|
257
|
+
readonly result: "boolean";
|
|
258
|
+
};
|
|
259
|
+
readonly TODAY: {
|
|
260
|
+
readonly signature: "TODAY()";
|
|
261
|
+
readonly description: "Today's date.";
|
|
262
|
+
readonly min: 0;
|
|
263
|
+
readonly max: 0;
|
|
264
|
+
readonly result: "date";
|
|
265
|
+
};
|
|
266
|
+
readonly NOW: {
|
|
267
|
+
readonly signature: "NOW()";
|
|
268
|
+
readonly description: "The current date and time.";
|
|
269
|
+
readonly min: 0;
|
|
270
|
+
readonly max: 0;
|
|
271
|
+
readonly result: "date";
|
|
272
|
+
};
|
|
273
|
+
readonly DATEDIFF: {
|
|
274
|
+
readonly signature: "DATEDIFF(from, to, 'days' | 'hours' | 'minutes')";
|
|
275
|
+
readonly description: "How far the second date is after the first. Negative when it is earlier.";
|
|
276
|
+
readonly min: 3;
|
|
277
|
+
readonly max: 3;
|
|
278
|
+
readonly result: "number";
|
|
279
|
+
};
|
|
280
|
+
readonly YEAR: {
|
|
281
|
+
readonly signature: "YEAR(date)";
|
|
282
|
+
readonly description: "The four-digit year of the date.";
|
|
283
|
+
readonly min: 1;
|
|
284
|
+
readonly max: 1;
|
|
285
|
+
readonly result: "number";
|
|
286
|
+
};
|
|
287
|
+
readonly MONTH: {
|
|
288
|
+
readonly signature: "MONTH(date)";
|
|
289
|
+
readonly description: "The month of the date, 1 to 12.";
|
|
290
|
+
readonly min: 1;
|
|
291
|
+
readonly max: 1;
|
|
292
|
+
readonly result: "number";
|
|
293
|
+
};
|
|
294
|
+
readonly DAY: {
|
|
295
|
+
readonly signature: "DAY(date)";
|
|
296
|
+
readonly description: "The day of the month, 1 to 31.";
|
|
297
|
+
readonly min: 1;
|
|
298
|
+
readonly max: 1;
|
|
299
|
+
readonly result: "number";
|
|
300
|
+
};
|
|
301
|
+
readonly DATEADD: {
|
|
302
|
+
readonly signature: "DATEADD(date, count, 'days' | 'months' | 'years')";
|
|
303
|
+
readonly description: "The date moved forward by that many units. Use a negative count to go back.";
|
|
304
|
+
readonly min: 3;
|
|
305
|
+
readonly max: 3;
|
|
306
|
+
readonly result: "date";
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
type FormulaFunctionName = keyof typeof FUNCTION_SPECS;
|
|
310
|
+
/** The help list the formula editor shows. Order is the order it renders in. */
|
|
311
|
+
declare const FORMULA_FUNCTIONS: readonly {
|
|
312
|
+
name: FormulaFunctionName;
|
|
313
|
+
signature: string;
|
|
314
|
+
description: string;
|
|
315
|
+
}[];
|
|
316
|
+
/**
|
|
317
|
+
* Turn formula text into an AST plus the list of columns it reads.
|
|
318
|
+
*
|
|
319
|
+
* `references` holds every `{…}` name exactly as typed, in first-use order —
|
|
320
|
+
* the caller resolves them (display name OR machine field name) through its own
|
|
321
|
+
* lookup, so this module never needs to know the table.
|
|
322
|
+
*/
|
|
323
|
+
declare function parseFormula(source: string): ParseResult;
|
|
324
|
+
/**
|
|
325
|
+
* Compute one cell. NEVER throws.
|
|
326
|
+
*
|
|
327
|
+
* `resolve(name)` is given the name exactly as it was typed between the braces
|
|
328
|
+
* and must return the cell's value, or `undefined` when no such column exists —
|
|
329
|
+
* the difference between "empty" and "misspelt", which is the difference
|
|
330
|
+
* between a working formula and a wrong answer.
|
|
331
|
+
*/
|
|
332
|
+
declare function evaluateFormula(ast: FormulaAst, resolve: ResolveCell): FormulaResult;
|
|
333
|
+
/**
|
|
334
|
+
* Best-effort: what kind of value will this formula produce? The UI uses it to
|
|
335
|
+
* pick a sensible display format for the computed column. `unknown` is always a
|
|
336
|
+
* legal answer — never guess a format on a shaky reading.
|
|
337
|
+
*/
|
|
338
|
+
declare function formulaResultType(ast: FormulaAst, columnType: ResolveColumnType): FormulaResultType;
|
|
339
|
+
/** The slice of a field row this helper needs to find and evaluate formulas. */
|
|
340
|
+
interface ComputedColumnField {
|
|
341
|
+
field_name: string;
|
|
342
|
+
display_name: string;
|
|
343
|
+
data_type: string;
|
|
344
|
+
metadata?: unknown;
|
|
345
|
+
}
|
|
346
|
+
interface ComputedColumn<F extends ComputedColumnField = ComputedColumnField> {
|
|
347
|
+
field: F;
|
|
348
|
+
parsed: ParseResult;
|
|
349
|
+
}
|
|
350
|
+
/** True when the column's declared format is `formula`. */
|
|
351
|
+
declare function isFormulaColumn(field: ComputedColumnField): boolean;
|
|
352
|
+
/** Every formula column among `fields`, each with its expression parsed once. */
|
|
353
|
+
declare function formulaColumnsOf<F extends ComputedColumnField>(fields: readonly F[]): ComputedColumn<F>[];
|
|
354
|
+
interface ComputedRowsResult<R> {
|
|
355
|
+
rows: R[];
|
|
356
|
+
/** `${rowId}::${fieldName}` → the plain-English reason that cell shows #ERROR. */
|
|
357
|
+
errors: Map<string, string>;
|
|
358
|
+
/** Machine names of the formula columns, for callers that must skip them. */
|
|
359
|
+
formulaFieldNames: Set<string>;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Return `rows` with every formula column's value computed from the row's
|
|
363
|
+
* other cells. Pure; the input rows are never mutated. When `fields` holds no
|
|
364
|
+
* formula column the SAME array is returned, so callers pay nothing.
|
|
365
|
+
*
|
|
366
|
+
* Formula columns are evaluated in `fields` order, and each one is written into
|
|
367
|
+
* the working copy of the row before the next runs — so a later formula may
|
|
368
|
+
* reference an earlier one. References resolve by machine name first, then by
|
|
369
|
+
* display name (case-insensitive), matching the grid.
|
|
370
|
+
*/
|
|
371
|
+
declare function withComputedColumns<R extends {
|
|
372
|
+
id: string;
|
|
373
|
+
data: Record<string, unknown>;
|
|
374
|
+
}, F extends ComputedColumnField>(rows: readonly R[], fields: readonly F[]): ComputedRowsResult<R>;
|
|
375
|
+
/**
|
|
376
|
+
* Rewrite every `{from}` reference in `expression` to `{to}` — what keeps a
|
|
377
|
+
* formula working when the column it names is renamed (Airtable and Sheets do
|
|
378
|
+
* this silently; a rename that turned a working column into #ERROR would be
|
|
379
|
+
* the platform punishing the user for tidying up).
|
|
380
|
+
*
|
|
381
|
+
* Walks the source with the tokenizer's own rules for the two things that
|
|
382
|
+
* matter: a `{…}` inside a quoted string is TEXT and is left alone, and `\`
|
|
383
|
+
* escapes the next character inside a string. The match is on the trimmed
|
|
384
|
+
* name, case-insensitive — the same comparison `withComputedColumns` makes.
|
|
385
|
+
* Returns the SAME string when nothing referenced `from`.
|
|
386
|
+
*/
|
|
387
|
+
declare function rewriteFormulaReferences(expression: string, from: string, to: string): string;
|
|
388
|
+
|
|
389
|
+
export { type BinaryOperator, type ComputedColumn, type ComputedColumnField, type ComputedRowsResult, FORMULA_FUNCTIONS, type FormulaAst, type FormulaFunctionName, type FormulaResult, type FormulaResultType, type FormulaValue, type ParseResult, type ResolveCell, type ResolveColumnType, evaluateFormula, formulaColumnsOf, formulaResultType, isFormulaColumn, parseFormula, rewriteFormulaReferences, withComputedColumns };
|