@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 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/formulas/formulas.ts"],"sourcesContent":["/**\n * @ai-matrx/design-system/formulas — THE formula language for computed columns.\n *\n * This module moved here from the app so every table in the product shares\n * one formula language. It stays pure: no React, no DOM, no network.\n *\n * A **formula column** holds nothing. Its cell is always `null` in the\n * database; the value the user sees is computed, on read, from the OTHER\n * columns of the SAME row. Champion: Airtable's formula field.\n *\n * THE THREE RULES THIS FILE OBEYS\n *\n * 1. **No `eval`, no `new Function`, no dynamic code.** A formula is user\n * content; it is tokenized, parsed into an AST, and interpreted. Nothing a\n * user types ever becomes JavaScript.\n * 2. **Nothing throws.** `parseFormula` and `evaluateFormula` both return a\n * discriminated result. A syntax error, a missing column, a division by\n * zero and a type mismatch are all `ok: false` with a plain-English\n * sentence a non-technical person can act on — never an exception, never a\n * silent blank cell (THE FALLBACK LAW, `lib/field-formats/FEATURE.md`).\n * 3. **The engine is pure.** It reads cells through a caller-supplied\n * `resolve(name)`. It never touches Redux, Supabase, or the DOM, which is\n * what makes every rule below testable.\n *\n * THE COERCION RULES (the whole contract, in one place)\n *\n * - **BLANK** is `null`, `undefined`, or `\"\"`. All three are the same value to\n * a formula, because a text cell that was cleared holds `\"\"` and a number\n * cell that was cleared holds `null`, and a user does not distinguish them.\n * - **A missing column is NOT blank.** `resolve()` returns `undefined` for a\n * column that does not exist (as opposed to `null` for one that is empty),\n * and that is an error: `{a} + {Typo}` must say the column is unknown rather\n * than quietly computing with zero.\n * - **Arithmetic treats BLANK as 0.** `{a} + {b}` with `b` empty is `{a}`.\n * That is the tolerant reading every spreadsheet uses, and the alternative\n * (the whole cell erroring because one input is empty) makes a formula\n * column useless on real, partly-filled data.\n * - **Numeric strings coerce.** `\"1,234\"`, `\" 42 \"`, `\"$5\"` and `\"3.5\"` are\n * numbers in arithmetic; `\"n/a\"` is a type error naming the value.\n * - **Booleans are 1 and 0 in arithmetic**, so SUM over a checkbox column\n * counts the ticks.\n * - **Aggregates SKIP blanks.** `SUM`/`MIN`/`MAX`/`AVERAGE` ignore empty\n * inputs entirely — `AVERAGE` divides by the count of the values that were\n * actually there, and returns BLANK when there were none.\n * - **Text conversion**: BLANK becomes `\"\"`, a number becomes its shortest\n * exact decimal, a boolean becomes `\"true\"` / `\"false\"`.\n * - **Comparison** is numeric when both sides read as numbers, otherwise a\n * plain string comparison. BLANK takes the shape of what it is compared\n * against — `0` beside a number, `\"\"` beside text — so `{a} = BLANK()`,\n * `{a} = 0` and `{a} = ''` all hold for an empty cell.\n * - **Truthiness** (for `IF` / `AND` / `OR` / `NOT`): BLANK is false, a\n * boolean is itself, a number is true unless `0`, a string is true unless\n * empty.\n * - **Dates are ISO strings, read and written in UTC.** A date function\n * accepts an ISO string (or a `Date`) and returns an ISO string — date-only\n * (`2026-09-14`) when the input was date-only, full (`2026-09-14T…Z`)\n * otherwise. UTC throughout on purpose: reading `2026-09-14` with local\n * getters turns it into the 13th for half the planet.\n * - **Division (and `%`) by zero is an error**, never `Infinity` and never\n * `NaN`. A cell may not lie about arithmetic that did not happen.\n */\n\n// ─── AST ─────────────────────────────────────────────────────────────────────\n\nexport type FormulaValue = string | number | boolean | null;\n\nexport type BinaryOperator =\n | \"+\"\n | \"-\"\n | \"*\"\n | \"/\"\n | \"%\"\n | \"&\"\n | \"=\"\n | \"!=\"\n | \"<>\"\n | \"<\"\n | \"<=\"\n | \">\"\n | \">=\";\n\nexport type FormulaAst =\n | { kind: \"number\"; value: number; position: number }\n | { kind: \"string\"; value: string; position: number }\n | { kind: \"boolean\"; value: boolean; position: number }\n | { kind: \"reference\"; name: string; position: number }\n | { kind: \"unary\"; operator: \"-\"; operand: FormulaAst; position: number }\n | {\n kind: \"binary\";\n operator: BinaryOperator;\n left: FormulaAst;\n right: FormulaAst;\n position: number;\n }\n | {\n kind: \"call\";\n name: FormulaFunctionName;\n args: FormulaAst[];\n position: number;\n };\n\nexport type ParseResult =\n | { ok: true; ast: FormulaAst; references: string[] }\n | { ok: false; error: string; position: number };\n\nexport type FormulaResult =\n | { ok: true; value: FormulaValue }\n | { ok: false; error: string };\n\nexport type FormulaResultType = \"number\" | \"text\" | \"boolean\" | \"date\" | \"unknown\";\n\n/** What a cell lookup may answer: a value, BLANK, or `undefined` = no such column. */\nexport type ResolveCell = (name: string) => unknown;\n\n/** Best-effort declared type of a column, for static result typing. */\nexport type ResolveColumnType = (\n name: string,\n) => \"number\" | \"text\" | \"boolean\" | \"date\" | undefined;\n\n// ─── the function table ──────────────────────────────────────────────────────\n\nconst FUNCTION_SPECS = {\n SUM: {\n signature: \"SUM(number, …)\",\n description: \"Adds every value, ignoring empty ones.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n MIN: {\n signature: \"MIN(number, …)\",\n description: \"The smallest value, ignoring empty ones.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n MAX: {\n signature: \"MAX(number, …)\",\n description: \"The largest value, ignoring empty ones.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n AVERAGE: {\n signature: \"AVERAGE(number, …)\",\n description:\n \"The average of the values that are filled in. Empty values are not counted.\",\n min: 1,\n max: Infinity,\n result: \"number\",\n },\n ROUND: {\n signature: \"ROUND(number, places?)\",\n description:\n \"Rounds to the given number of decimal places (0 when omitted). Halves round away from zero.\",\n min: 1,\n max: 2,\n result: \"number\",\n },\n ABS: {\n signature: \"ABS(number)\",\n description: \"The value without its minus sign.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n LEN: {\n signature: \"LEN(text)\",\n description: \"How many characters the text has.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n UPPER: {\n signature: \"UPPER(text)\",\n description: \"The text in capitals.\",\n min: 1,\n max: 1,\n result: \"text\",\n },\n LOWER: {\n signature: \"LOWER(text)\",\n description: \"The text in lower case.\",\n min: 1,\n max: 1,\n result: \"text\",\n },\n TRIM: {\n signature: \"TRIM(text)\",\n description: \"The text without leading or trailing spaces.\",\n min: 1,\n max: 1,\n result: \"text\",\n },\n CONCATENATE: {\n signature: \"CONCATENATE(text, …)\",\n description: \"Joins every value into one piece of text.\",\n min: 1,\n max: Infinity,\n result: \"text\",\n },\n LEFT: {\n signature: \"LEFT(text, count)\",\n description: \"The first few characters of the text.\",\n min: 2,\n max: 2,\n result: \"text\",\n },\n RIGHT: {\n signature: \"RIGHT(text, count)\",\n description: \"The last few characters of the text.\",\n min: 2,\n max: 2,\n result: \"text\",\n },\n CONTAINS: {\n signature: \"CONTAINS(text, part)\",\n description:\n \"Yes when the text contains that part. Upper and lower case are treated the same.\",\n min: 2,\n max: 2,\n result: \"boolean\",\n },\n IF: {\n signature: \"IF(condition, then, otherwise?)\",\n description:\n \"The second value when the condition holds, otherwise the third (empty when omitted).\",\n min: 2,\n max: 3,\n result: \"unknown\",\n },\n AND: {\n signature: \"AND(condition, …)\",\n description: \"Yes when every condition holds.\",\n min: 1,\n max: Infinity,\n result: \"boolean\",\n },\n OR: {\n signature: \"OR(condition, …)\",\n description: \"Yes when at least one condition holds.\",\n min: 1,\n max: Infinity,\n result: \"boolean\",\n },\n NOT: {\n signature: \"NOT(condition)\",\n description: \"Turns yes into no and no into yes.\",\n min: 1,\n max: 1,\n result: \"boolean\",\n },\n BLANK: {\n signature: \"BLANK()\",\n description: \"An empty value, for comparing against or returning.\",\n min: 0,\n max: 0,\n result: \"unknown\",\n },\n ISBLANK: {\n signature: \"ISBLANK(value)\",\n description: \"Yes when the value is empty.\",\n min: 1,\n max: 1,\n result: \"boolean\",\n },\n TODAY: {\n signature: \"TODAY()\",\n description: \"Today's date.\",\n min: 0,\n max: 0,\n result: \"date\",\n },\n NOW: {\n signature: \"NOW()\",\n description: \"The current date and time.\",\n min: 0,\n max: 0,\n result: \"date\",\n },\n DATEDIFF: {\n signature: \"DATEDIFF(from, to, 'days' | 'hours' | 'minutes')\",\n description:\n \"How far the second date is after the first. Negative when it is earlier.\",\n min: 3,\n max: 3,\n result: \"number\",\n },\n YEAR: {\n signature: \"YEAR(date)\",\n description: \"The four-digit year of the date.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n MONTH: {\n signature: \"MONTH(date)\",\n description: \"The month of the date, 1 to 12.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n DAY: {\n signature: \"DAY(date)\",\n description: \"The day of the month, 1 to 31.\",\n min: 1,\n max: 1,\n result: \"number\",\n },\n DATEADD: {\n signature: \"DATEADD(date, count, 'days' | 'months' | 'years')\",\n description:\n \"The date moved forward by that many units. Use a negative count to go back.\",\n min: 3,\n max: 3,\n result: \"date\",\n },\n} as const satisfies Record<\n string,\n {\n signature: string;\n description: string;\n min: number;\n max: number;\n result: FormulaResultType;\n }\n>;\n\nexport type FormulaFunctionName = keyof typeof FUNCTION_SPECS;\n\nconst FUNCTION_NAMES = Object.keys(FUNCTION_SPECS) as FormulaFunctionName[];\n\n/** The help list the formula editor shows. Order is the order it renders in. */\nexport const FORMULA_FUNCTIONS: readonly {\n name: FormulaFunctionName;\n signature: string;\n description: string;\n}[] = Object.freeze(\n FUNCTION_NAMES.map((name) => ({\n name,\n signature: FUNCTION_SPECS[name].signature,\n description: FUNCTION_SPECS[name].description,\n })),\n);\n\n// ─── tokenizer ───────────────────────────────────────────────────────────────\n\ntype TokenType =\n | \"number\"\n | \"string\"\n | \"identifier\"\n | \"reference\"\n | \"operator\"\n | \"paren-open\"\n | \"paren-close\"\n | \"comma\"\n | \"end\";\n\ntype Token = { type: TokenType; text: string; position: number };\n\n/** Thrown only INSIDE this module; every public entry point catches it. */\nclass FormulaError extends Error {\n readonly position: number;\n constructor(message: string, position = 0) {\n super(message);\n this.name = \"FormulaError\";\n this.position = position;\n }\n}\n\nconst TWO_CHAR_OPERATORS = [\"!=\", \"<>\", \"<=\", \">=\"];\nconst ONE_CHAR_OPERATORS = [\"+\", \"-\", \"*\", \"/\", \"%\", \"&\", \"=\", \"<\", \">\"];\n\nfunction isDigit(ch: string): boolean {\n return ch >= \"0\" && ch <= \"9\";\n}\n\nfunction isIdentifierStart(ch: string): boolean {\n return /[A-Za-z_]/.test(ch);\n}\n\nfunction isIdentifierPart(ch: string): boolean {\n return /[A-Za-z0-9_]/.test(ch);\n}\n\nfunction tokenize(source: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n while (i < source.length) {\n const ch = source[i];\n // `i < source.length` already guarantees a character here; the guard only\n // makes that knowledge explicit for the compiler.\n if (ch === undefined) break;\n\n if (/\\s/.test(ch)) {\n i += 1;\n continue;\n }\n\n if (ch === \"{\") {\n const close = source.indexOf(\"}\", i + 1);\n if (close === -1) {\n throw new FormulaError(\n \"This column reference is missing its closing brace `}`.\",\n i,\n );\n }\n const name = source.slice(i + 1, close).trim();\n if (name === \"\") {\n throw new FormulaError(\"This column reference has no name inside `{}`.\", i);\n }\n tokens.push({ type: \"reference\", text: name, position: i });\n i = close + 1;\n continue;\n }\n\n if (ch === '\"' || ch === \"'\") {\n const quote = ch;\n let j = i + 1;\n let text = \"\";\n let closed = false;\n while (j < source.length) {\n if (source[j] === \"\\\\\" && j + 1 < source.length) {\n text += source[j + 1];\n j += 2;\n continue;\n }\n if (source[j] === quote) {\n closed = true;\n j += 1;\n break;\n }\n text += source[j];\n j += 1;\n }\n if (!closed) {\n throw new FormulaError(\n `This text is missing its closing ${quote === '\"' ? \"double\" : \"single\"} quote.`,\n i,\n );\n }\n tokens.push({ type: \"string\", text, position: i });\n i = j;\n continue;\n }\n\n if (isDigit(ch) || (ch === \".\" && isDigit(source[i + 1] ?? \"\"))) {\n let j = i;\n let seenDot = false;\n while (j < source.length) {\n // The `j < source.length` above guarantees a character; `?? \"\"` is\n // never taken and reads as \"not a digit\", which ends the number.\n if (isDigit(source[j] ?? \"\")) {\n j += 1;\n continue;\n }\n if (source[j] === \".\" && !seenDot) {\n seenDot = true;\n j += 1;\n continue;\n }\n break;\n }\n tokens.push({ type: \"number\", text: source.slice(i, j), position: i });\n i = j;\n continue;\n }\n\n if (isIdentifierStart(ch)) {\n let j = i;\n // Guarded by `j < source.length`; `?? \"\"` is never taken and reads as\n // \"not an identifier character\", which ends the identifier.\n while (j < source.length && isIdentifierPart(source[j] ?? \"\")) j += 1;\n tokens.push({ type: \"identifier\", text: source.slice(i, j), position: i });\n i = j;\n continue;\n }\n\n if (ch === \"(\") {\n tokens.push({ type: \"paren-open\", text: ch, position: i });\n i += 1;\n continue;\n }\n if (ch === \")\") {\n tokens.push({ type: \"paren-close\", text: ch, position: i });\n i += 1;\n continue;\n }\n if (ch === \",\") {\n tokens.push({ type: \"comma\", text: ch, position: i });\n i += 1;\n continue;\n }\n\n const two = source.slice(i, i + 2);\n if (TWO_CHAR_OPERATORS.includes(two)) {\n tokens.push({ type: \"operator\", text: two, position: i });\n i += 2;\n continue;\n }\n if (ONE_CHAR_OPERATORS.includes(ch)) {\n tokens.push({ type: \"operator\", text: ch, position: i });\n i += 1;\n continue;\n }\n\n throw new FormulaError(`\\`${ch}\\` does not mean anything in a formula.`, i);\n }\n\n tokens.push({ type: \"end\", text: \"\", position: source.length });\n return tokens;\n}\n\n// ─── parser ──────────────────────────────────────────────────────────────────\n//\n// Precedence, loosest first:\n// comparison = != <> < <= > >=\n// concat &\n// additive + -\n// multiplicative * / %\n// unary -\n// primary literal · {reference} · FUNC(…) · ( … )\n\nconst COMPARISON_OPERATORS = [\"=\", \"!=\", \"<>\", \"<\", \"<=\", \">\", \">=\"];\n\nclass Parser {\n private readonly tokens: Token[];\n private index = 0;\n readonly references: string[] = [];\n\n /**\n * `tokenize` always terminates the list with an `end` token and the grammar\n * never advances past it, so neither accessor below can actually run off the\n * end. This sentinel is what they would return if one ever did, which keeps\n * both accessors total without an assertion.\n */\n private readonly endToken: Token;\n\n constructor(tokens: Token[]) {\n this.tokens = tokens;\n this.endToken = tokens[tokens.length - 1] ?? {\n type: \"end\",\n text: \"\",\n position: 0,\n };\n }\n\n private peek(): Token {\n return this.tokens[this.index] ?? this.endToken;\n }\n\n private next(): Token {\n return this.tokens[this.index++] ?? this.endToken;\n }\n\n private matchOperator(candidates: readonly string[]): Token | null {\n const token = this.peek();\n if (token.type === \"operator\" && candidates.includes(token.text)) {\n this.index += 1;\n return token;\n }\n return null;\n }\n\n parseProgram(): FormulaAst {\n const expression = this.parseComparison();\n const trailing = this.peek();\n if (trailing.type !== \"end\") {\n throw new FormulaError(\n `Nothing should follow the formula here — remove \\`${trailing.text}\\`.`,\n trailing.position,\n );\n }\n return expression;\n }\n\n private parseComparison(): FormulaAst {\n let left = this.parseConcat();\n let operator = this.matchOperator(COMPARISON_OPERATORS);\n while (operator) {\n const right = this.parseConcat();\n left = {\n kind: \"binary\",\n operator: operator.text as BinaryOperator,\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator(COMPARISON_OPERATORS);\n }\n return left;\n }\n\n private parseConcat(): FormulaAst {\n let left = this.parseAdditive();\n let operator = this.matchOperator([\"&\"]);\n while (operator) {\n const right = this.parseAdditive();\n left = {\n kind: \"binary\",\n operator: \"&\",\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator([\"&\"]);\n }\n return left;\n }\n\n private parseAdditive(): FormulaAst {\n let left = this.parseMultiplicative();\n let operator = this.matchOperator([\"+\", \"-\"]);\n while (operator) {\n const right = this.parseMultiplicative();\n left = {\n kind: \"binary\",\n operator: operator.text as BinaryOperator,\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator([\"+\", \"-\"]);\n }\n return left;\n }\n\n private parseMultiplicative(): FormulaAst {\n let left = this.parseUnary();\n let operator = this.matchOperator([\"*\", \"/\", \"%\"]);\n while (operator) {\n const right = this.parseUnary();\n left = {\n kind: \"binary\",\n operator: operator.text as BinaryOperator,\n left,\n right,\n position: operator.position,\n };\n operator = this.matchOperator([\"*\", \"/\", \"%\"]);\n }\n return left;\n }\n\n private parseUnary(): FormulaAst {\n const minus = this.matchOperator([\"-\"]);\n if (minus) {\n return {\n kind: \"unary\",\n operator: \"-\",\n operand: this.parseUnary(),\n position: minus.position,\n };\n }\n // A leading `+` is harmless and people type it; accept and ignore it.\n const plus = this.matchOperator([\"+\"]);\n if (plus) return this.parseUnary();\n return this.parsePrimary();\n }\n\n private parsePrimary(): FormulaAst {\n const token = this.next();\n\n if (token.type === \"number\") {\n return { kind: \"number\", value: Number(token.text), position: token.position };\n }\n\n if (token.type === \"string\") {\n return { kind: \"string\", value: token.text, position: token.position };\n }\n\n if (token.type === \"reference\") {\n if (!this.references.includes(token.text)) this.references.push(token.text);\n return { kind: \"reference\", name: token.text, position: token.position };\n }\n\n if (token.type === \"paren-open\") {\n const inner = this.parseComparison();\n const close = this.next();\n if (close.type !== \"paren-close\") {\n throw new FormulaError(\n \"This opening bracket `(` never closes.\",\n token.position,\n );\n }\n return inner;\n }\n\n if (token.type === \"identifier\") {\n const upper = token.text.toUpperCase();\n if (upper === \"TRUE\" || upper === \"FALSE\") {\n return {\n kind: \"boolean\",\n value: upper === \"TRUE\",\n position: token.position,\n };\n }\n if (!(FUNCTION_NAMES as string[]).includes(upper)) {\n throw new FormulaError(\n `There is no function called \\`${token.text}\\`. A column goes in braces, like {${token.text}}.`,\n token.position,\n );\n }\n const name = upper as FormulaFunctionName;\n const open = this.next();\n if (open.type !== \"paren-open\") {\n throw new FormulaError(\n `\\`${name}\\` needs brackets after it, like ${FUNCTION_SPECS[name].signature}.`,\n token.position,\n );\n }\n const args: FormulaAst[] = [];\n if (this.peek().type === \"paren-close\") {\n this.index += 1;\n } else {\n for (;;) {\n args.push(this.parseComparison());\n const separator = this.next();\n if (separator.type === \"comma\") continue;\n if (separator.type === \"paren-close\") break;\n throw new FormulaError(\n `\\`${name}\\` is missing a comma or its closing bracket.`,\n separator.position,\n );\n }\n }\n const spec = FUNCTION_SPECS[name];\n if (args.length < spec.min || args.length > spec.max) {\n throw new FormulaError(\n `\\`${name}\\` was given ${args.length} value${args.length === 1 ? \"\" : \"s\"}. Use ${spec.signature}.`,\n token.position,\n );\n }\n return { kind: \"call\", name, args, position: token.position };\n }\n\n if (token.type === \"end\") {\n throw new FormulaError(\"The formula stops before it is finished.\", token.position);\n }\n\n throw new FormulaError(\n `\\`${token.text}\\` cannot start a value here.`,\n token.position,\n );\n }\n}\n\n/**\n * Turn formula text into an AST plus the list of columns it reads.\n *\n * `references` holds every `{…}` name exactly as typed, in first-use order —\n * the caller resolves them (display name OR machine field name) through its own\n * lookup, so this module never needs to know the table.\n */\nexport function parseFormula(source: string): ParseResult {\n try {\n const text = source ?? \"\";\n if (text.trim() === \"\") {\n return { ok: false, error: \"This formula is empty.\", position: 0 };\n }\n const parser = new Parser(tokenize(text));\n const ast = parser.parseProgram();\n return { ok: true, ast, references: parser.references };\n } catch (error) {\n if (error instanceof FormulaError) {\n return { ok: false, error: error.message, position: error.position };\n }\n return {\n ok: false,\n error: \"This formula could not be read.\",\n position: 0,\n };\n }\n}\n\n// ─── value coercion ──────────────────────────────────────────────────────────\n\nconst MISSING = Symbol(\"missing-column\");\n\nfunction isBlank(value: FormulaValue): boolean {\n return value === null || value === \"\";\n}\n\n/** Shortest exact decimal for a number — keeps `0.1 + 0.2` out of the cell. */\nfunction numberToText(n: number): string {\n if (Number.isInteger(n)) return String(n);\n const trimmed = Number(n.toPrecision(15));\n return String(Number.isFinite(trimmed) ? trimmed : n);\n}\n\nfunction toText(value: FormulaValue): string {\n if (value === null) return \"\";\n if (typeof value === \"number\") return numberToText(value);\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n return value;\n}\n\n/** A number, or `null` when the value simply is not one. BLANK reads as 0. */\nfunction looseNumber(value: FormulaValue): number | null {\n if (value === null || value === \"\") return 0;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"boolean\") return value ? 1 : 0;\n const cleaned = value.replace(/[,\\s$€£¥%]/g, \"\");\n if (cleaned === \"\") return 0;\n const n = Number(cleaned);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction requireNumber(value: FormulaValue, what: string): number {\n const n = looseNumber(value);\n if (n === null) {\n throw new FormulaError(`${what} needs a number, but got \"${toText(value)}\".`);\n }\n return n;\n}\n\nfunction truthy(value: FormulaValue): boolean {\n if (value === null || value === \"\") return false;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return value !== 0;\n return true;\n}\n\n/** Both sides numeric? compare as numbers. Otherwise compare as text. */\nfunction compareValues(left: FormulaValue, right: FormulaValue): number {\n const looksNumeric = (v: FormulaValue) =>\n typeof v === \"number\" ||\n (typeof v === \"string\" && v.trim() !== \"\" && looseNumber(v) !== null);\n // BLANK takes the shape of whatever it is compared against: 0 beside a\n // number, \"\" beside text. `{Empty} = 0` and `{Empty} = ''` are both true.\n if (isBlank(left) && isBlank(right)) return 0;\n const blankBesideNumber =\n (isBlank(left) && looksNumeric(right)) ||\n (isBlank(right) && looksNumeric(left));\n if (blankBesideNumber || (looksNumeric(left) && looksNumeric(right))) {\n const a = looseNumber(left) ?? 0;\n const b = looseNumber(right) ?? 0;\n return a < b ? -1 : a > b ? 1 : 0;\n }\n if (typeof left === \"boolean\" || typeof right === \"boolean\") {\n const a = truthy(left);\n const b = truthy(right);\n return a === b ? 0 : a ? 1 : -1;\n }\n const a = toText(left);\n const b = toText(right);\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n// ─── dates ───────────────────────────────────────────────────────────────────\n\nconst DATE_ONLY_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\ntype ParsedDate = { ms: number; dateOnly: boolean };\n\nfunction requireDate(value: FormulaValue, what: string): ParsedDate {\n if (value === null || value === \"\") {\n throw new FormulaError(`${what} needs a date, but that value is empty.`);\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n const ms = Date.parse(trimmed);\n if (!Number.isNaN(ms)) return { ms, dateOnly: DATE_ONLY_RE.test(trimmed) };\n }\n throw new FormulaError(`${what} needs a date, but got \"${toText(value)}\".`);\n}\n\nfunction isoFrom(ms: number, dateOnly: boolean): string {\n const iso = new Date(ms).toISOString();\n return dateOnly ? iso.slice(0, 10) : iso;\n}\n\nconst DATEDIFF_UNITS: Record<string, number> = {\n days: 86_400_000,\n hours: 3_600_000,\n minutes: 60_000,\n};\n\nfunction requireUnit(\n value: FormulaValue,\n allowed: string[],\n fn: string,\n): string {\n const unit = toText(value).trim().toLowerCase();\n if (!allowed.includes(unit)) {\n throw new FormulaError(\n `\\`${fn}\\` needs one of ${allowed.map((u) => `'${u}'`).join(\", \")} — got \"${toText(value)}\".`,\n );\n }\n return unit;\n}\n\n/** Add months/years in UTC, clamping to the end of a shorter month. */\nfunction addMonths(ms: number, count: number): number {\n const d = new Date(ms);\n const day = d.getUTCDate();\n const target = new Date(ms);\n target.setUTCDate(1);\n target.setUTCMonth(target.getUTCMonth() + count);\n const lastDay = new Date(\n Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0),\n ).getUTCDate();\n target.setUTCDate(Math.min(day, lastDay));\n return target.getTime();\n}\n\n// ─── evaluator ───────────────────────────────────────────────────────────────\n\n/** SUM-style: flatten args, drop blanks, demand numbers of the rest. */\nfunction numericArgs(values: FormulaValue[], fn: string): number[] {\n const out: number[] = [];\n for (const value of values) {\n if (isBlank(value)) continue;\n out.push(requireNumber(value, `\\`${fn}\\``));\n }\n return out;\n}\n\nfunction roundHalfAway(n: number, places: number): number {\n const factor = 10 ** places;\n const scaled = n * factor;\n // Nudge past the float representation of an exact half (1.005 * 100 = 100.49999…).\n const corrected = Number(scaled.toPrecision(15));\n const rounded =\n corrected < 0 ? -Math.round(-corrected) : Math.round(corrected);\n return rounded / factor;\n}\n\n/**\n * Positional argument of a call. The parser has already checked every call\n * against its `FUNCTION_SPECS` min/max, so each index read below is present;\n * this accessor only makes that knowledge explicit. An index that somehow was\n * not supplied reads as BLANK — the value this language already uses for \"not\n * given\" — so nothing here can throw on a missing argument.\n */\nfunction argAt(args: readonly FormulaValue[], index: number): FormulaValue {\n return args[index] ?? null;\n}\n\nfunction callFunction(\n name: FormulaFunctionName,\n args: FormulaValue[],\n): FormulaValue {\n switch (name) {\n case \"SUM\":\n return numericArgs(args, \"SUM\").reduce((a, b) => a + b, 0);\n case \"MIN\": {\n const nums = numericArgs(args, \"MIN\");\n return nums.length === 0 ? null : Math.min(...nums);\n }\n case \"MAX\": {\n const nums = numericArgs(args, \"MAX\");\n return nums.length === 0 ? null : Math.max(...nums);\n }\n case \"AVERAGE\": {\n const nums = numericArgs(args, \"AVERAGE\");\n if (nums.length === 0) return null;\n return nums.reduce((a, b) => a + b, 0) / nums.length;\n }\n case \"ROUND\": {\n const n = requireNumber(argAt(args, 0), \"`ROUND`\");\n const places =\n args.length > 1 ? Math.trunc(requireNumber(argAt(args, 1), \"`ROUND`\")) : 0;\n return roundHalfAway(n, places);\n }\n case \"ABS\":\n return Math.abs(requireNumber(argAt(args, 0), \"`ABS`\"));\n case \"LEN\":\n return toText(argAt(args, 0)).length;\n case \"UPPER\":\n return toText(argAt(args, 0)).toUpperCase();\n case \"LOWER\":\n return toText(argAt(args, 0)).toLowerCase();\n case \"TRIM\":\n return toText(argAt(args, 0)).trim();\n case \"CONCATENATE\":\n return args.map(toText).join(\"\");\n case \"LEFT\": {\n const count = Math.trunc(requireNumber(argAt(args, 1), \"`LEFT`\"));\n return toText(argAt(args, 0)).slice(0, Math.max(0, count));\n }\n case \"RIGHT\": {\n const count = Math.trunc(requireNumber(argAt(args, 1), \"`RIGHT`\"));\n if (count <= 0) return \"\";\n return toText(argAt(args, 0)).slice(-count);\n }\n case \"CONTAINS\":\n return toText(argAt(args, 0))\n .toLowerCase()\n .includes(toText(argAt(args, 1)).toLowerCase());\n case \"NOT\":\n return !truthy(argAt(args, 0));\n case \"BLANK\":\n return null;\n case \"ISBLANK\":\n return isBlank(argAt(args, 0));\n case \"TODAY\":\n return new Date().toISOString().slice(0, 10);\n case \"NOW\":\n return new Date().toISOString();\n case \"DATEDIFF\": {\n const from = requireDate(argAt(args, 0), \"`DATEDIFF`\");\n const to = requireDate(argAt(args, 1), \"`DATEDIFF`\");\n const unit = requireUnit(argAt(args, 2), Object.keys(DATEDIFF_UNITS), \"DATEDIFF\");\n // `requireUnit` was handed this map's own key list, so it can only return\n // a key that is present; the guard makes that explicit.\n const perUnit = DATEDIFF_UNITS[unit];\n if (perUnit === undefined) return null;\n return Math.trunc((to.ms - from.ms) / perUnit);\n }\n case \"YEAR\":\n return new Date(requireDate(argAt(args, 0), \"`YEAR`\").ms).getUTCFullYear();\n case \"MONTH\":\n return new Date(requireDate(argAt(args, 0), \"`MONTH`\").ms).getUTCMonth() + 1;\n case \"DAY\":\n return new Date(requireDate(argAt(args, 0), \"`DAY`\").ms).getUTCDate();\n case \"DATEADD\": {\n const base = requireDate(argAt(args, 0), \"`DATEADD`\");\n const count = Math.trunc(requireNumber(argAt(args, 1), \"`DATEADD`\"));\n const unit = requireUnit(argAt(args, 2), [\"days\", \"months\", \"years\"], \"DATEADD\");\n const ms =\n unit === \"days\"\n ? base.ms + count * 86_400_000\n : addMonths(base.ms, unit === \"months\" ? count : count * 12);\n return isoFrom(ms, base.dateOnly);\n }\n // IF / AND / OR short-circuit in `evaluateNode` and never reach here.\n case \"IF\":\n case \"AND\":\n case \"OR\":\n throw new FormulaError(`\\`${name}\\` could not be evaluated.`);\n }\n}\n\nfunction applyBinary(\n operator: BinaryOperator,\n left: FormulaValue,\n right: FormulaValue,\n): FormulaValue {\n switch (operator) {\n case \"&\":\n return toText(left) + toText(right);\n case \"+\":\n return (\n requireNumber(left, \"`+`\") + requireNumber(right, \"`+`\")\n );\n case \"-\":\n return requireNumber(left, \"`-`\") - requireNumber(right, \"`-`\");\n case \"*\":\n return requireNumber(left, \"`*`\") * requireNumber(right, \"`*`\");\n case \"/\": {\n const divisor = requireNumber(right, \"`/`\");\n if (divisor === 0) {\n throw new FormulaError(\"This formula divides by zero.\");\n }\n return requireNumber(left, \"`/`\") / divisor;\n }\n case \"%\": {\n const divisor = requireNumber(right, \"`%`\");\n if (divisor === 0) {\n throw new FormulaError(\"This formula divides by zero.\");\n }\n return requireNumber(left, \"`%`\") % divisor;\n }\n case \"=\":\n return compareValues(left, right) === 0;\n case \"!=\":\n case \"<>\":\n return compareValues(left, right) !== 0;\n case \"<\":\n return compareValues(left, right) < 0;\n case \"<=\":\n return compareValues(left, right) <= 0;\n case \">\":\n return compareValues(left, right) > 0;\n case \">=\":\n return compareValues(left, right) >= 0;\n }\n}\n\nfunction normalizeCell(raw: unknown, name: string): FormulaValue {\n if (raw === MISSING) {\n throw new FormulaError(`There is no column called {${name}}.`);\n }\n if (raw === null || raw === undefined) return null;\n if (typeof raw === \"string\" || typeof raw === \"boolean\") return raw;\n if (typeof raw === \"number\") {\n if (!Number.isFinite(raw)) {\n throw new FormulaError(`The value in {${name}} is not a usable number.`);\n }\n return raw;\n }\n if (raw instanceof Date) {\n return Number.isNaN(raw.getTime()) ? null : raw.toISOString();\n }\n // Arrays and objects have no scalar reading; JSON keeps them legible.\n try {\n return JSON.stringify(raw);\n } catch {\n return String(raw);\n }\n}\n\nfunction evaluateNode(node: FormulaAst, resolve: ResolveCell): FormulaValue {\n switch (node.kind) {\n case \"number\":\n case \"string\":\n case \"boolean\":\n return node.value;\n case \"reference\": {\n const raw = resolve(node.name);\n // `undefined` means the column does not exist; a blank cell is null/\"\".\n return normalizeCell(raw === undefined ? MISSING : raw, node.name);\n }\n case \"unary\":\n return -requireNumber(evaluateNode(node.operand, resolve), \"`-`\");\n case \"binary\":\n return applyBinary(\n node.operator,\n evaluateNode(node.left, resolve),\n evaluateNode(node.right, resolve),\n );\n case \"call\": {\n if (node.name === \"IF\") {\n // The parser enforces IF's 2–3 arity, so both of these are present; an\n // absent one would read as BLANK rather than throw.\n const test = node.args[0];\n const whenTrue = node.args[1];\n const condition = truthy(test ? evaluateNode(test, resolve) : null);\n if (condition) return whenTrue ? evaluateNode(whenTrue, resolve) : null;\n const whenFalse = node.args[2];\n return node.args.length > 2 && whenFalse\n ? evaluateNode(whenFalse, resolve)\n : null;\n }\n if (node.name === \"AND\") {\n for (const arg of node.args) {\n if (!truthy(evaluateNode(arg, resolve))) return false;\n }\n return true;\n }\n if (node.name === \"OR\") {\n for (const arg of node.args) {\n if (truthy(evaluateNode(arg, resolve))) return true;\n }\n return false;\n }\n return callFunction(\n node.name,\n node.args.map((arg) => evaluateNode(arg, resolve)),\n );\n }\n }\n}\n\n/**\n * Compute one cell. NEVER throws.\n *\n * `resolve(name)` is given the name exactly as it was typed between the braces\n * and must return the cell's value, or `undefined` when no such column exists —\n * the difference between \"empty\" and \"misspelt\", which is the difference\n * between a working formula and a wrong answer.\n */\nexport function evaluateFormula(\n ast: FormulaAst,\n resolve: ResolveCell,\n): FormulaResult {\n try {\n const value = evaluateNode(ast, resolve);\n if (typeof value === \"number\" && !Number.isFinite(value)) {\n return { ok: false, error: \"This formula produced a number too large to show.\" };\n }\n return { ok: true, value };\n } catch (error) {\n if (error instanceof FormulaError) return { ok: false, error: error.message };\n return { ok: false, error: \"This formula could not be calculated.\" };\n }\n}\n\n// ─── static result type ──────────────────────────────────────────────────────\n\n/**\n * Best-effort: what kind of value will this formula produce? The UI uses it to\n * pick a sensible display format for the computed column. `unknown` is always a\n * legal answer — never guess a format on a shaky reading.\n */\nexport function formulaResultType(\n ast: FormulaAst,\n columnType: ResolveColumnType,\n): FormulaResultType {\n switch (ast.kind) {\n case \"number\":\n return \"number\";\n case \"string\":\n return \"text\";\n case \"boolean\":\n return \"boolean\";\n case \"unary\":\n return \"number\";\n case \"reference\":\n return columnType(ast.name) ?? \"unknown\";\n case \"binary\": {\n if (ast.operator === \"&\") return \"text\";\n if (COMPARISON_OPERATORS.includes(ast.operator)) return \"boolean\";\n return \"number\";\n }\n case \"call\": {\n if (ast.name === \"IF\") {\n // Arity is parser-enforced: IF always carries its \"then\" branch. An\n // absent branch types as \"unknown\", the same neutral this function\n // already returns for anything it cannot pin down.\n const thenArg = ast.args[1];\n const then = thenArg ? formulaResultType(thenArg, columnType) : \"unknown\";\n const otherwiseArg = ast.args[2];\n if (ast.args.length < 3 || !otherwiseArg) return then;\n const otherwise = formulaResultType(otherwiseArg, columnType);\n if (then === otherwise) return then;\n if (then === \"unknown\") return otherwise;\n if (otherwise === \"unknown\") return then;\n return \"unknown\";\n }\n if (ast.name === \"MIN\" || ast.name === \"MAX\") {\n // MIN/MAX over dates is still dates; over anything else, a number.\n const types = ast.args.map((a) => formulaResultType(a, columnType));\n if (types.length > 0 && types.every((t) => t === \"date\")) return \"date\";\n return \"number\";\n }\n return FUNCTION_SPECS[ast.name].result;\n }\n }\n}\n\n// ─── computed columns over rows ─────────────────────────────────────────────\n//\n// THE ONE PLACE a formula column's value is injected into a row. The grid,\n// every copy/export reader (`getCompleteTable` consumers), the client-side\n// sort and the agent scope all call this, so no reader can ever hand out a\n// formula column as the empty cell the database actually stores.\n\n/** The slice of a field row this helper needs to find and evaluate formulas. */\nexport interface ComputedColumnField {\n field_name: string;\n display_name: string;\n data_type: string;\n metadata?: unknown;\n}\n\nexport interface ComputedColumn<F extends ComputedColumnField = ComputedColumnField> {\n field: F;\n parsed: ParseResult;\n}\n\n/**\n * Read the field's declared format WITHOUT importing `lib/field-formats`\n * (which imports nothing from `features/`, and the reverse direction is the\n * one this module keeps clean too). The shape is `metadata.format = { id,\n * options }`, exactly what `readFieldFormatConfig` reads.\n */\nfunction formulaExpressionOf(field: ComputedColumnField): string | null {\n const metadata = field.metadata;\n if (!metadata || typeof metadata !== \"object\") return null;\n const format = (metadata as { format?: unknown }).format;\n if (!format || typeof format !== \"object\") return null;\n if ((format as { id?: unknown }).id !== \"formula\") return null;\n const options = (format as { options?: unknown }).options;\n const formula =\n options && typeof options === \"object\"\n ? (options as { formula?: unknown }).formula\n : undefined;\n const expression =\n formula && typeof formula === \"object\"\n ? (formula as { expression?: unknown }).expression\n : undefined;\n return typeof expression === \"string\" ? expression : \"\";\n}\n\n/** True when the column's declared format is `formula`. */\nexport function isFormulaColumn(field: ComputedColumnField): boolean {\n return formulaExpressionOf(field) !== null;\n}\n\n/** Every formula column among `fields`, each with its expression parsed once. */\nexport function formulaColumnsOf<F extends ComputedColumnField>(\n fields: readonly F[],\n): ComputedColumn<F>[] {\n return fields.flatMap((field) => {\n const expression = formulaExpressionOf(field);\n if (expression === null) return [];\n return [{ field, parsed: parseFormula(expression) }];\n });\n}\n\nexport interface ComputedRowsResult<R> {\n rows: R[];\n /** `${rowId}::${fieldName}` → the plain-English reason that cell shows #ERROR. */\n errors: Map<string, string>;\n /** Machine names of the formula columns, for callers that must skip them. */\n formulaFieldNames: Set<string>;\n}\n\n/**\n * Return `rows` with every formula column's value computed from the row's\n * other cells. Pure; the input rows are never mutated. When `fields` holds no\n * formula column the SAME array is returned, so callers pay nothing.\n *\n * Formula columns are evaluated in `fields` order, and each one is written into\n * the working copy of the row before the next runs — so a later formula may\n * reference an earlier one. References resolve by machine name first, then by\n * display name (case-insensitive), matching the grid.\n */\nexport function withComputedColumns<\n R extends { id: string; data: Record<string, unknown> },\n F extends ComputedColumnField,\n>(rows: readonly R[], fields: readonly F[]): ComputedRowsResult<R> {\n const columns = formulaColumnsOf(fields);\n const formulaFieldNames = new Set(columns.map((c) => c.field.field_name));\n const errors = new Map<string, string>();\n if (columns.length === 0) {\n return { rows: rows as R[], errors, formulaFieldNames };\n }\n // A reference resolves against the table's COLUMNS, never against the keys a\n // particular row happens to carry: a row saved with only some of its cells\n // (every new row, every sparse import) has no key for the rest, and that is\n // BLANK — not \"no such column\", which is the #ERROR the engine reserves for\n // a misspelt reference.\n const fieldNameByReference = new Map<string, string>();\n for (const f of fields) {\n fieldNameByReference.set(f.display_name.toLowerCase(), f.field_name);\n }\n for (const f of fields) {\n // Machine names win over a display name that happens to collide.\n fieldNameByReference.set(f.field_name.toLowerCase(), f.field_name);\n }\n const computed = rows.map((row) => {\n const data: Record<string, unknown> = { ...(row.data ?? {}) };\n const resolve: ResolveCell = (name) => {\n if (name in data) return data[name];\n const fieldName = fieldNameByReference.get(name.toLowerCase());\n if (fieldName === undefined) return undefined; // truly no such column\n return data[fieldName] ?? null;\n };\n for (const { field, parsed } of columns) {\n if (!parsed.ok) {\n data[field.field_name] = null;\n errors.set(`${row.id}::${field.field_name}`, parsed.error);\n continue;\n }\n const result = evaluateFormula(parsed.ast, resolve);\n if (result.ok) data[field.field_name] = result.value;\n else {\n data[field.field_name] = null;\n errors.set(`${row.id}::${field.field_name}`, result.error);\n }\n }\n return { ...row, data };\n });\n return { rows: computed, errors, formulaFieldNames };\n}\n\n// ─── renaming a column a formula refers to ──────────────────────────────────\n\n/**\n * Rewrite every `{from}` reference in `expression` to `{to}` — what keeps a\n * formula working when the column it names is renamed (Airtable and Sheets do\n * this silently; a rename that turned a working column into #ERROR would be\n * the platform punishing the user for tidying up).\n *\n * Walks the source with the tokenizer's own rules for the two things that\n * matter: a `{…}` inside a quoted string is TEXT and is left alone, and `\\`\n * escapes the next character inside a string. The match is on the trimmed\n * name, case-insensitive — the same comparison `withComputedColumns` makes.\n * Returns the SAME string when nothing referenced `from`.\n */\nexport function rewriteFormulaReferences(\n expression: string,\n from: string,\n to: string,\n): string {\n const target = from.trim().toLowerCase();\n if (!target) return expression;\n let out = \"\";\n let i = 0;\n while (i < expression.length) {\n const ch = expression[i];\n if (ch === '\"' || ch === \"'\") {\n let j = i + 1;\n while (j < expression.length) {\n if (expression[j] === \"\\\\\" && j + 1 < expression.length) {\n j += 2;\n continue;\n }\n if (expression[j] === ch) {\n j += 1;\n break;\n }\n j += 1;\n }\n out += expression.slice(i, j);\n i = j;\n continue;\n }\n if (ch === \"{\") {\n const close = expression.indexOf(\"}\", i + 1);\n if (close === -1) {\n out += expression.slice(i);\n break;\n }\n const name = expression.slice(i + 1, close);\n out += name.trim().toLowerCase() === target ? `{${to}}` : expression.slice(i, close + 1);\n i = close + 1;\n continue;\n }\n out += ch;\n i += 1;\n }\n return out;\n}\n"],"mappings":"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","names":["a","b"]}
|
|
@@ -201,4 +201,4 @@ type FieldFormatDef = {
|
|
|
201
201
|
parse: (raw: unknown, options: FieldFormatOptions) => unknown;
|
|
202
202
|
};
|
|
203
203
|
|
|
204
|
-
export type {
|
|
204
|
+
export type { FieldFormatConfig as F, StructuredListBinding as S, FieldFormatId as a, FieldFormatDef as b, FormatResult as c, FieldBaseType as d, FieldChoice as e, FieldEditorKind as f, FieldFormatOptions as g };
|
|
@@ -201,4 +201,4 @@ type FieldFormatDef = {
|
|
|
201
201
|
parse: (raw: unknown, options: FieldFormatOptions) => unknown;
|
|
202
202
|
};
|
|
203
203
|
|
|
204
|
-
export type {
|
|
204
|
+
export type { FieldFormatConfig as F, StructuredListBinding as S, FieldFormatId as a, FieldFormatDef as b, FormatResult as c, FieldBaseType as d, FieldChoice as e, FieldEditorKind as f, FieldFormatOptions as g };
|
|
@@ -1,7 +1,10 @@
|
|
|
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 {
|
|
4
|
+
import { F as FieldFormatConfig } from './types-CJWU1Gae.cjs';
|
|
5
|
+
import { GroupAggregateSpec, GroupOrder } from './data-table/grouping.cjs';
|
|
6
|
+
import { CellPatch } from './data-table/fill.cjs';
|
|
7
|
+
import { SpreadsheetIntent } from './data-table/spreadsheet.cjs';
|
|
5
8
|
|
|
6
9
|
type TableSearchRole = "id" | "name" | "description" | "other";
|
|
7
10
|
interface TableSearchScope {
|
|
@@ -1064,6 +1067,135 @@ interface MatrxDataTableStyleConfig<T> {
|
|
|
1064
1067
|
*/
|
|
1065
1068
|
choiceColorFor?: ChoiceColorLookup;
|
|
1066
1069
|
}
|
|
1070
|
+
/**
|
|
1071
|
+
* GROUPING — "group these rows by Status" with collapsible sections, a count
|
|
1072
|
+
* and a subtotal on each.
|
|
1073
|
+
*
|
|
1074
|
+
* This is the primitive's answer to the biggest gap against Airtable, Google
|
|
1075
|
+
* Sheets and Notion. It is opt-in, and it is CONTROLLED for the two pieces of
|
|
1076
|
+
* state a person expects to survive: which column groups, and which sections
|
|
1077
|
+
* are shut. Both are ordinary values, so a saved view carries them like any
|
|
1078
|
+
* other reading of the table.
|
|
1079
|
+
*
|
|
1080
|
+
* Grouping runs AFTER local processing and over the rows the table is about to
|
|
1081
|
+
* render. It never re-sorts inside a group: the sort the person chose is the
|
|
1082
|
+
* order they see.
|
|
1083
|
+
*/
|
|
1084
|
+
interface MatrxDataTableGroupingConfig<T> {
|
|
1085
|
+
/** Column id to group by. `null` means grouping is available but off. */
|
|
1086
|
+
columnId: string | null;
|
|
1087
|
+
/** Emitted when the person picks a different column (or clears it). */
|
|
1088
|
+
onColumnIdChange?: ((columnId: string | null) => void) | undefined;
|
|
1089
|
+
/**
|
|
1090
|
+
* Columns offered in the group-by control. Defaults to every visible column
|
|
1091
|
+
* that is not an actions column. A column with thousands of distinct values
|
|
1092
|
+
* is a bad grouping and the consumer is the only one who knows that.
|
|
1093
|
+
*/
|
|
1094
|
+
groupableColumnIds?: string[] | undefined;
|
|
1095
|
+
/** Per-column subtotal shown on every group header, keyed by column id. */
|
|
1096
|
+
aggregates?: Record<string, GroupAggregateSpec> | undefined;
|
|
1097
|
+
/** Controlled collapsed group keys (`TableGroup.key`). */
|
|
1098
|
+
collapsed?: string[] | undefined;
|
|
1099
|
+
onCollapsedChange?: ((collapsed: string[]) => void) | undefined;
|
|
1100
|
+
/** Section order. Default `"value-asc"`; blanks always sort last. */
|
|
1101
|
+
order?: GroupOrder | undefined;
|
|
1102
|
+
/** Label for the section of rows whose grouping value is blank. */
|
|
1103
|
+
emptyLabel?: string | undefined;
|
|
1104
|
+
/** Singular noun for a group's summary ("job" → "12 jobs"). */
|
|
1105
|
+
rowNoun?: string | undefined;
|
|
1106
|
+
/**
|
|
1107
|
+
* Reads the grouping value for a column. Defaults to the column's own
|
|
1108
|
+
* accessor.
|
|
1109
|
+
*
|
|
1110
|
+
* NOT named `valueOf`: every object in JavaScript already has one, so an
|
|
1111
|
+
* optional config field by that name is always "present" and a `config.valueOf
|
|
1112
|
+
* ? … : …` check silently calls `Object.prototype.valueOf` and groups every
|
|
1113
|
+
* row under the config object itself. It did exactly that here once.
|
|
1114
|
+
*/
|
|
1115
|
+
readCell?: ((row: T, columnId: string) => unknown) | undefined;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* VIRTUALIZATION — render the rows a person can actually see.
|
|
1119
|
+
*
|
|
1120
|
+
* Off by default, because most tables in the product are short and a windowed
|
|
1121
|
+
* body costs a scroll container contract that a short table does not need.
|
|
1122
|
+
* On, it engages only past `threshold` rows, so a 40-row table behaves exactly
|
|
1123
|
+
* as it always did and a 100,000-row table scrolls.
|
|
1124
|
+
*/
|
|
1125
|
+
interface MatrxDataTableVirtualizeConfig {
|
|
1126
|
+
enabled?: boolean | undefined;
|
|
1127
|
+
/** Row height in px used for the window math. Default follows density. */
|
|
1128
|
+
rowHeight?: number | undefined;
|
|
1129
|
+
/** Rows rendered beyond each edge of the viewport. Default 8. */
|
|
1130
|
+
overscan?: number | undefined;
|
|
1131
|
+
/** Row count below which the body renders whole. Default 150. */
|
|
1132
|
+
threshold?: number | undefined;
|
|
1133
|
+
/**
|
|
1134
|
+
* Window the COLUMNS too, for tables hundreds of columns wide. Frozen
|
|
1135
|
+
* columns are always rendered, never windowed out.
|
|
1136
|
+
*/
|
|
1137
|
+
columns?: boolean | undefined;
|
|
1138
|
+
/** Column width in px used for the horizontal window. Default 160. */
|
|
1139
|
+
columnWidth?: number | undefined;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* THE HONEST COUNT.
|
|
1143
|
+
*
|
|
1144
|
+
* A table that filters over part of its rows and says nothing is a screen that
|
|
1145
|
+
* lies. Declare what the answer actually covers and the table states it, in
|
|
1146
|
+
* plain English, above the rows — and never presents a partial count as exact.
|
|
1147
|
+
*
|
|
1148
|
+
* Omit this entirely and the table says nothing, which is correct for a table
|
|
1149
|
+
* whose rows are all in hand.
|
|
1150
|
+
*/
|
|
1151
|
+
interface MatrxDataTableCoverageConfig {
|
|
1152
|
+
/** Rows the source says match the current query, when it can say. */
|
|
1153
|
+
matched?: number | undefined;
|
|
1154
|
+
/** Rows in the table irrespective of the query, when known. */
|
|
1155
|
+
total?: number | undefined;
|
|
1156
|
+
/** A ceiling the source imposed on this answer (a fetch cap). */
|
|
1157
|
+
cap?: number | undefined;
|
|
1158
|
+
/** Who answered the filters and sort. Default `"client"`. */
|
|
1159
|
+
answeredBy?: "source" | "client" | undefined;
|
|
1160
|
+
/** Singular noun for the sentence. Default "row". */
|
|
1161
|
+
noun?: string | undefined;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* SPREADSHEET GESTURES — range selection, Excel paste, fill, undo.
|
|
1165
|
+
*
|
|
1166
|
+
* The `/data` grid had all of this and nothing else in the product could reach
|
|
1167
|
+
* it. Opt-in, because a reference table with one editable column does not want
|
|
1168
|
+
* a cell cursor; a table a person works IN does.
|
|
1169
|
+
*
|
|
1170
|
+
* The table owns the gestures and the selection rectangle; the CONSUMER owns
|
|
1171
|
+
* the writes. Every mutation arrives as a list of `CellPatch`, so the caller
|
|
1172
|
+
* applies it through the door it already has and the table never invents a
|
|
1173
|
+
* persistence path of its own. That is also what makes undo honest: the stack
|
|
1174
|
+
* holds before-values, and an undo is just another patch list.
|
|
1175
|
+
*/
|
|
1176
|
+
interface MatrxDataTableSpreadsheetConfig<T> {
|
|
1177
|
+
enabled?: boolean | undefined;
|
|
1178
|
+
/**
|
|
1179
|
+
* Apply cell writes. Called for a paste, a fill-down, a fill-handle drag, a
|
|
1180
|
+
* clear, an undo and a redo. The table re-reads from `data`, so a write the
|
|
1181
|
+
* caller refuses simply never appears.
|
|
1182
|
+
*/
|
|
1183
|
+
onPatch: (patches: CellPatch[], intent: SpreadsheetIntent) => void | Promise<void>;
|
|
1184
|
+
/** Column ids a patch may target. Defaults to every column marked `editable`. */
|
|
1185
|
+
writableColumnIds?: string[] | undefined;
|
|
1186
|
+
/** Why a column refuses a write, as a sentence a person can act on. */
|
|
1187
|
+
columnRefusal?: ((columnId: string) => string | undefined) | undefined;
|
|
1188
|
+
/** Reads a cell for copy and fill. Defaults to the column's own accessor. */
|
|
1189
|
+
readCell?: ((row: T, columnId: string) => unknown) | undefined;
|
|
1190
|
+
/** The little square at the corner of a selection. Default true. */
|
|
1191
|
+
fillHandle?: boolean | undefined;
|
|
1192
|
+
/** Paste real Excel/Sheets TSV into the selection. Default true. */
|
|
1193
|
+
paste?: boolean | undefined;
|
|
1194
|
+
/** Undo depth. 0 turns undo off. Default 100. */
|
|
1195
|
+
undoDepth?: number | undefined;
|
|
1196
|
+
/** Says what just happened, and why something did not. */
|
|
1197
|
+
notify?: ((message: string) => void) | undefined;
|
|
1198
|
+
}
|
|
1067
1199
|
interface MatrxDataTableProps<T> {
|
|
1068
1200
|
data: T[];
|
|
1069
1201
|
columns: MatrxColumnDef<T>[];
|
|
@@ -1193,6 +1325,26 @@ interface MatrxDataTableProps<T> {
|
|
|
1193
1325
|
* person edits it.
|
|
1194
1326
|
*/
|
|
1195
1327
|
tableStyle?: MatrxDataTableStyleConfig<T>;
|
|
1328
|
+
/**
|
|
1329
|
+
* GROUPS — collapsible sections by a column, with a count and per-column
|
|
1330
|
+
* subtotals on each header. Opt-in and controlled; see the config's own doc.
|
|
1331
|
+
*/
|
|
1332
|
+
grouping?: MatrxDataTableGroupingConfig<T>;
|
|
1333
|
+
/** Render only the rows in view. Opt-in; engages past its own threshold. */
|
|
1334
|
+
virtualize?: boolean | MatrxDataTableVirtualizeConfig;
|
|
1335
|
+
/**
|
|
1336
|
+
* What the current answer actually covers. Declare it and the table says so
|
|
1337
|
+
* on screen when a filter is answering over part of the table, instead of
|
|
1338
|
+
* presenting a partial count as the whole truth.
|
|
1339
|
+
*/
|
|
1340
|
+
coverage?: MatrxDataTableCoverageConfig;
|
|
1341
|
+
/** Range selection, Excel paste, fill handle, fill-down, undo. Opt-in. */
|
|
1342
|
+
spreadsheet?: MatrxDataTableSpreadsheetConfig<T>;
|
|
1343
|
+
/**
|
|
1344
|
+
* Explicit body row height in px. Overrides the density default. Use it when
|
|
1345
|
+
* a saved view remembers a row height the person chose.
|
|
1346
|
+
*/
|
|
1347
|
+
rowHeight?: number;
|
|
1196
1348
|
/** Highlight an index in the currently rendered page without selecting it. */
|
|
1197
1349
|
highlightedIndex?: number | undefined;
|
|
1198
1350
|
/** Receives the complete visible local view after local processing, before paging. Attaching after absence receives one snapshot; callback identity changes alone do not notify. */
|
|
@@ -1258,4 +1410,4 @@ interface MatrxDataTableProps<T> {
|
|
|
1258
1410
|
onRowOpen?: (row: T) => void;
|
|
1259
1411
|
}
|
|
1260
1412
|
|
|
1261
|
-
export {
|
|
1413
|
+
export { type SortDirection as $, type AnyOfColumnSearch as A, BOOLEAN_TRUE_COLOR as B, type ColumnFiltersState as C, type MatrxDataTableGroupingConfig as D, EMPTY_TABLE_STYLE as E, type MatrxDataTableHierarchyConfig as F, type MatrxDataTableHierarchyMove as G, type MatrxDataTableMobileCardControls as H, type MatrxDataTableQueryControl as I, type MatrxDataTableSelectionConfig as J, type MatrxDataTableSourceProcessing as K, type LayeredFilterRule as L, type MatrxColumnDef as M, type MatrxDataTableSpreadsheetConfig as N, type MatrxDataTableStyleConfig as O, type MatrxDataTableToolbar as P, type MatrxDataTableUrlStateConfig as Q, type MatrxDataTableVirtualizeConfig as R, type SortState as S, type TableSearchMatchMode as T, type MatrxDataTableWindowConfig as U, type MatrxTablePagination as V, OPS_WITH_VALUE as W, ROW_TINT_CLASS as X, STYLE_COLORS as Y, STYLE_COLOR_LABELS as Z, SWATCH_CLASS as _, type ColumnFilterValue as a, type StyleColor as a0, type StylePath as a1, type StyleTarget as a2, TABLE_STYLE_METADATA_KEY as a3, TABLE_STYLE_VERSION as a4, type TableRankedSearchOptions as a5, type TableSearchRole as a6, type TableSearchScope as a7, type TableStyle as a8, type TableViewQuery as a9, layeredFilterNeedsValue as aA, layeredFilterRuleSummary as aB, operatorsForLayeredField as aC, type TextFilterMode as aa, type ToolbarFacet as ab, applyStylePath as ac, colorForChoice as ad, evaluateRule as ae, inferTableSearchRole as af, isStyleColor as ag, parseTableStyle as ah, rankTableSearchRows as ai, resolveCellColor as aj, resolveRowColor as ak, resolveTableSearchScopes as al, stylePath as am, tableStyleFromMetadata as an, tableStyleIsEmpty as ao, LAYERED_FILTER_OPERATORS as ap, LAYERED_FILTER_OPERATOR_LABELS as aq, type LayeredFilterField as ar, type LayeredFilterOperator as as, columnFiltersToLayeredRules as at, completeLayeredFilterRules as au, decodeLayeredFilterRules as av, encodeLayeredFilterRules as aw, isCompleteLayeredFilterRule as ax, isLayeredFilterOperator as ay, layeredFilterMatchesValue as az, 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 MatrxDataTableCoverageConfig as u, type MatrxDataTableDetailConfig as v, type MatrxDataTableEditConfig as w, type MatrxDataTableEmptyState as x, type MatrxDataTableExpandedDetailConfig as y, type MatrxDataTableFilterUi as z };
|
|
@@ -1,7 +1,10 @@
|
|
|
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 {
|
|
4
|
+
import { F as FieldFormatConfig } from './types-CJWU1Gae.js';
|
|
5
|
+
import { GroupAggregateSpec, GroupOrder } from './data-table/grouping.js';
|
|
6
|
+
import { CellPatch } from './data-table/fill.js';
|
|
7
|
+
import { SpreadsheetIntent } from './data-table/spreadsheet.js';
|
|
5
8
|
|
|
6
9
|
type TableSearchRole = "id" | "name" | "description" | "other";
|
|
7
10
|
interface TableSearchScope {
|
|
@@ -1064,6 +1067,135 @@ interface MatrxDataTableStyleConfig<T> {
|
|
|
1064
1067
|
*/
|
|
1065
1068
|
choiceColorFor?: ChoiceColorLookup;
|
|
1066
1069
|
}
|
|
1070
|
+
/**
|
|
1071
|
+
* GROUPING — "group these rows by Status" with collapsible sections, a count
|
|
1072
|
+
* and a subtotal on each.
|
|
1073
|
+
*
|
|
1074
|
+
* This is the primitive's answer to the biggest gap against Airtable, Google
|
|
1075
|
+
* Sheets and Notion. It is opt-in, and it is CONTROLLED for the two pieces of
|
|
1076
|
+
* state a person expects to survive: which column groups, and which sections
|
|
1077
|
+
* are shut. Both are ordinary values, so a saved view carries them like any
|
|
1078
|
+
* other reading of the table.
|
|
1079
|
+
*
|
|
1080
|
+
* Grouping runs AFTER local processing and over the rows the table is about to
|
|
1081
|
+
* render. It never re-sorts inside a group: the sort the person chose is the
|
|
1082
|
+
* order they see.
|
|
1083
|
+
*/
|
|
1084
|
+
interface MatrxDataTableGroupingConfig<T> {
|
|
1085
|
+
/** Column id to group by. `null` means grouping is available but off. */
|
|
1086
|
+
columnId: string | null;
|
|
1087
|
+
/** Emitted when the person picks a different column (or clears it). */
|
|
1088
|
+
onColumnIdChange?: ((columnId: string | null) => void) | undefined;
|
|
1089
|
+
/**
|
|
1090
|
+
* Columns offered in the group-by control. Defaults to every visible column
|
|
1091
|
+
* that is not an actions column. A column with thousands of distinct values
|
|
1092
|
+
* is a bad grouping and the consumer is the only one who knows that.
|
|
1093
|
+
*/
|
|
1094
|
+
groupableColumnIds?: string[] | undefined;
|
|
1095
|
+
/** Per-column subtotal shown on every group header, keyed by column id. */
|
|
1096
|
+
aggregates?: Record<string, GroupAggregateSpec> | undefined;
|
|
1097
|
+
/** Controlled collapsed group keys (`TableGroup.key`). */
|
|
1098
|
+
collapsed?: string[] | undefined;
|
|
1099
|
+
onCollapsedChange?: ((collapsed: string[]) => void) | undefined;
|
|
1100
|
+
/** Section order. Default `"value-asc"`; blanks always sort last. */
|
|
1101
|
+
order?: GroupOrder | undefined;
|
|
1102
|
+
/** Label for the section of rows whose grouping value is blank. */
|
|
1103
|
+
emptyLabel?: string | undefined;
|
|
1104
|
+
/** Singular noun for a group's summary ("job" → "12 jobs"). */
|
|
1105
|
+
rowNoun?: string | undefined;
|
|
1106
|
+
/**
|
|
1107
|
+
* Reads the grouping value for a column. Defaults to the column's own
|
|
1108
|
+
* accessor.
|
|
1109
|
+
*
|
|
1110
|
+
* NOT named `valueOf`: every object in JavaScript already has one, so an
|
|
1111
|
+
* optional config field by that name is always "present" and a `config.valueOf
|
|
1112
|
+
* ? … : …` check silently calls `Object.prototype.valueOf` and groups every
|
|
1113
|
+
* row under the config object itself. It did exactly that here once.
|
|
1114
|
+
*/
|
|
1115
|
+
readCell?: ((row: T, columnId: string) => unknown) | undefined;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* VIRTUALIZATION — render the rows a person can actually see.
|
|
1119
|
+
*
|
|
1120
|
+
* Off by default, because most tables in the product are short and a windowed
|
|
1121
|
+
* body costs a scroll container contract that a short table does not need.
|
|
1122
|
+
* On, it engages only past `threshold` rows, so a 40-row table behaves exactly
|
|
1123
|
+
* as it always did and a 100,000-row table scrolls.
|
|
1124
|
+
*/
|
|
1125
|
+
interface MatrxDataTableVirtualizeConfig {
|
|
1126
|
+
enabled?: boolean | undefined;
|
|
1127
|
+
/** Row height in px used for the window math. Default follows density. */
|
|
1128
|
+
rowHeight?: number | undefined;
|
|
1129
|
+
/** Rows rendered beyond each edge of the viewport. Default 8. */
|
|
1130
|
+
overscan?: number | undefined;
|
|
1131
|
+
/** Row count below which the body renders whole. Default 150. */
|
|
1132
|
+
threshold?: number | undefined;
|
|
1133
|
+
/**
|
|
1134
|
+
* Window the COLUMNS too, for tables hundreds of columns wide. Frozen
|
|
1135
|
+
* columns are always rendered, never windowed out.
|
|
1136
|
+
*/
|
|
1137
|
+
columns?: boolean | undefined;
|
|
1138
|
+
/** Column width in px used for the horizontal window. Default 160. */
|
|
1139
|
+
columnWidth?: number | undefined;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* THE HONEST COUNT.
|
|
1143
|
+
*
|
|
1144
|
+
* A table that filters over part of its rows and says nothing is a screen that
|
|
1145
|
+
* lies. Declare what the answer actually covers and the table states it, in
|
|
1146
|
+
* plain English, above the rows — and never presents a partial count as exact.
|
|
1147
|
+
*
|
|
1148
|
+
* Omit this entirely and the table says nothing, which is correct for a table
|
|
1149
|
+
* whose rows are all in hand.
|
|
1150
|
+
*/
|
|
1151
|
+
interface MatrxDataTableCoverageConfig {
|
|
1152
|
+
/** Rows the source says match the current query, when it can say. */
|
|
1153
|
+
matched?: number | undefined;
|
|
1154
|
+
/** Rows in the table irrespective of the query, when known. */
|
|
1155
|
+
total?: number | undefined;
|
|
1156
|
+
/** A ceiling the source imposed on this answer (a fetch cap). */
|
|
1157
|
+
cap?: number | undefined;
|
|
1158
|
+
/** Who answered the filters and sort. Default `"client"`. */
|
|
1159
|
+
answeredBy?: "source" | "client" | undefined;
|
|
1160
|
+
/** Singular noun for the sentence. Default "row". */
|
|
1161
|
+
noun?: string | undefined;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* SPREADSHEET GESTURES — range selection, Excel paste, fill, undo.
|
|
1165
|
+
*
|
|
1166
|
+
* The `/data` grid had all of this and nothing else in the product could reach
|
|
1167
|
+
* it. Opt-in, because a reference table with one editable column does not want
|
|
1168
|
+
* a cell cursor; a table a person works IN does.
|
|
1169
|
+
*
|
|
1170
|
+
* The table owns the gestures and the selection rectangle; the CONSUMER owns
|
|
1171
|
+
* the writes. Every mutation arrives as a list of `CellPatch`, so the caller
|
|
1172
|
+
* applies it through the door it already has and the table never invents a
|
|
1173
|
+
* persistence path of its own. That is also what makes undo honest: the stack
|
|
1174
|
+
* holds before-values, and an undo is just another patch list.
|
|
1175
|
+
*/
|
|
1176
|
+
interface MatrxDataTableSpreadsheetConfig<T> {
|
|
1177
|
+
enabled?: boolean | undefined;
|
|
1178
|
+
/**
|
|
1179
|
+
* Apply cell writes. Called for a paste, a fill-down, a fill-handle drag, a
|
|
1180
|
+
* clear, an undo and a redo. The table re-reads from `data`, so a write the
|
|
1181
|
+
* caller refuses simply never appears.
|
|
1182
|
+
*/
|
|
1183
|
+
onPatch: (patches: CellPatch[], intent: SpreadsheetIntent) => void | Promise<void>;
|
|
1184
|
+
/** Column ids a patch may target. Defaults to every column marked `editable`. */
|
|
1185
|
+
writableColumnIds?: string[] | undefined;
|
|
1186
|
+
/** Why a column refuses a write, as a sentence a person can act on. */
|
|
1187
|
+
columnRefusal?: ((columnId: string) => string | undefined) | undefined;
|
|
1188
|
+
/** Reads a cell for copy and fill. Defaults to the column's own accessor. */
|
|
1189
|
+
readCell?: ((row: T, columnId: string) => unknown) | undefined;
|
|
1190
|
+
/** The little square at the corner of a selection. Default true. */
|
|
1191
|
+
fillHandle?: boolean | undefined;
|
|
1192
|
+
/** Paste real Excel/Sheets TSV into the selection. Default true. */
|
|
1193
|
+
paste?: boolean | undefined;
|
|
1194
|
+
/** Undo depth. 0 turns undo off. Default 100. */
|
|
1195
|
+
undoDepth?: number | undefined;
|
|
1196
|
+
/** Says what just happened, and why something did not. */
|
|
1197
|
+
notify?: ((message: string) => void) | undefined;
|
|
1198
|
+
}
|
|
1067
1199
|
interface MatrxDataTableProps<T> {
|
|
1068
1200
|
data: T[];
|
|
1069
1201
|
columns: MatrxColumnDef<T>[];
|
|
@@ -1193,6 +1325,26 @@ interface MatrxDataTableProps<T> {
|
|
|
1193
1325
|
* person edits it.
|
|
1194
1326
|
*/
|
|
1195
1327
|
tableStyle?: MatrxDataTableStyleConfig<T>;
|
|
1328
|
+
/**
|
|
1329
|
+
* GROUPS — collapsible sections by a column, with a count and per-column
|
|
1330
|
+
* subtotals on each header. Opt-in and controlled; see the config's own doc.
|
|
1331
|
+
*/
|
|
1332
|
+
grouping?: MatrxDataTableGroupingConfig<T>;
|
|
1333
|
+
/** Render only the rows in view. Opt-in; engages past its own threshold. */
|
|
1334
|
+
virtualize?: boolean | MatrxDataTableVirtualizeConfig;
|
|
1335
|
+
/**
|
|
1336
|
+
* What the current answer actually covers. Declare it and the table says so
|
|
1337
|
+
* on screen when a filter is answering over part of the table, instead of
|
|
1338
|
+
* presenting a partial count as the whole truth.
|
|
1339
|
+
*/
|
|
1340
|
+
coverage?: MatrxDataTableCoverageConfig;
|
|
1341
|
+
/** Range selection, Excel paste, fill handle, fill-down, undo. Opt-in. */
|
|
1342
|
+
spreadsheet?: MatrxDataTableSpreadsheetConfig<T>;
|
|
1343
|
+
/**
|
|
1344
|
+
* Explicit body row height in px. Overrides the density default. Use it when
|
|
1345
|
+
* a saved view remembers a row height the person chose.
|
|
1346
|
+
*/
|
|
1347
|
+
rowHeight?: number;
|
|
1196
1348
|
/** Highlight an index in the currently rendered page without selecting it. */
|
|
1197
1349
|
highlightedIndex?: number | undefined;
|
|
1198
1350
|
/** Receives the complete visible local view after local processing, before paging. Attaching after absence receives one snapshot; callback identity changes alone do not notify. */
|
|
@@ -1258,4 +1410,4 @@ interface MatrxDataTableProps<T> {
|
|
|
1258
1410
|
onRowOpen?: (row: T) => void;
|
|
1259
1411
|
}
|
|
1260
1412
|
|
|
1261
|
-
export {
|
|
1413
|
+
export { type SortDirection as $, type AnyOfColumnSearch as A, BOOLEAN_TRUE_COLOR as B, type ColumnFiltersState as C, type MatrxDataTableGroupingConfig as D, EMPTY_TABLE_STYLE as E, type MatrxDataTableHierarchyConfig as F, type MatrxDataTableHierarchyMove as G, type MatrxDataTableMobileCardControls as H, type MatrxDataTableQueryControl as I, type MatrxDataTableSelectionConfig as J, type MatrxDataTableSourceProcessing as K, type LayeredFilterRule as L, type MatrxColumnDef as M, type MatrxDataTableSpreadsheetConfig as N, type MatrxDataTableStyleConfig as O, type MatrxDataTableToolbar as P, type MatrxDataTableUrlStateConfig as Q, type MatrxDataTableVirtualizeConfig as R, type SortState as S, type TableSearchMatchMode as T, type MatrxDataTableWindowConfig as U, type MatrxTablePagination as V, OPS_WITH_VALUE as W, ROW_TINT_CLASS as X, STYLE_COLORS as Y, STYLE_COLOR_LABELS as Z, SWATCH_CLASS as _, type ColumnFilterValue as a, type StyleColor as a0, type StylePath as a1, type StyleTarget as a2, TABLE_STYLE_METADATA_KEY as a3, TABLE_STYLE_VERSION as a4, type TableRankedSearchOptions as a5, type TableSearchRole as a6, type TableSearchScope as a7, type TableStyle as a8, type TableViewQuery as a9, layeredFilterNeedsValue as aA, layeredFilterRuleSummary as aB, operatorsForLayeredField as aC, type TextFilterMode as aa, type ToolbarFacet as ab, applyStylePath as ac, colorForChoice as ad, evaluateRule as ae, inferTableSearchRole as af, isStyleColor as ag, parseTableStyle as ah, rankTableSearchRows as ai, resolveCellColor as aj, resolveRowColor as ak, resolveTableSearchScopes as al, stylePath as am, tableStyleFromMetadata as an, tableStyleIsEmpty as ao, LAYERED_FILTER_OPERATORS as ap, LAYERED_FILTER_OPERATOR_LABELS as aq, type LayeredFilterField as ar, type LayeredFilterOperator as as, columnFiltersToLayeredRules as at, completeLayeredFilterRules as au, decodeLayeredFilterRules as av, encodeLayeredFilterRules as aw, isCompleteLayeredFilterRule as ax, isLayeredFilterOperator as ay, layeredFilterMatchesValue as az, 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 MatrxDataTableCoverageConfig as u, type MatrxDataTableDetailConfig as v, type MatrxDataTableEditConfig as w, type MatrxDataTableEmptyState as x, type MatrxDataTableExpandedDetailConfig as y, type MatrxDataTableFilterUi as z };
|