@gui-chat-plugin/spreadsheet 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +33 -4
  2. package/dist/core/dispatch.d.ts +8 -0
  3. package/dist/core/index.d.ts +4 -2
  4. package/dist/core/paths.d.ts +13 -0
  5. package/dist/core/plugin.d.ts +7 -3
  6. package/dist/core/sheetGuards.d.ts +5 -0
  7. package/dist/core/types.d.ts +25 -3
  8. package/dist/core.cjs +1 -1
  9. package/dist/core.js +2 -2
  10. package/dist/engine/calculator.d.ts +15 -3
  11. package/dist/engine/cellBuilder.d.ts +44 -0
  12. package/dist/engine/cellEmpty.d.ts +13 -0
  13. package/dist/engine/cellFormatting.d.ts +25 -0
  14. package/dist/engine/coerce-boolean.d.ts +7 -0
  15. package/dist/engine/condition.d.ts +49 -0
  16. package/dist/engine/date-locale.d.ts +12 -0
  17. package/dist/engine/date-parser.d.ts +7 -1
  18. package/dist/engine/date-utils.d.ts +7 -4
  19. package/dist/engine/datedif.d.ts +13 -0
  20. package/dist/engine/evaluator.d.ts +44 -1
  21. package/dist/engine/financial-math.d.ts +28 -0
  22. package/dist/engine/formatter.d.ts +9 -0
  23. package/dist/engine/formulaError.d.ts +37 -0
  24. package/dist/engine/formulaRefs.d.ts +37 -0
  25. package/dist/engine/functions/lookup-math.d.ts +19 -0
  26. package/dist/engine/functions/statistical-math.d.ts +41 -0
  27. package/dist/engine/functions/text.d.ts +17 -1
  28. package/dist/engine/guards.d.ts +10 -0
  29. package/dist/engine/index.d.ts +11 -0
  30. package/dist/engine/jsonCellLocator.d.ts +27 -0
  31. package/dist/engine/math-ops.d.ts +41 -0
  32. package/dist/engine/numericCoercion.d.ts +38 -0
  33. package/dist/engine/parser.d.ts +0 -24
  34. package/dist/engine/registry.d.ts +30 -2
  35. package/dist/engine/responseDecoder.d.ts +40 -0
  36. package/dist/engine/spreadsheet-errors.d.ts +42 -0
  37. package/dist/engine/textFormat.d.ts +32 -0
  38. package/dist/engine/translateFormula.d.ts +37 -0
  39. package/dist/engine/types.d.ts +17 -3
  40. package/dist/index.cjs +1 -1
  41. package/dist/index.js +2 -2
  42. package/dist/lang/de.d.ts +3 -0
  43. package/dist/lang/en.d.ts +3 -0
  44. package/dist/lang/es.d.ts +3 -0
  45. package/dist/lang/fr.d.ts +3 -0
  46. package/dist/lang/index.d.ts +1 -0
  47. package/dist/lang/ja.d.ts +3 -0
  48. package/dist/lang/ko.d.ts +3 -0
  49. package/dist/lang/messages.d.ts +20 -0
  50. package/dist/lang/ptBR.d.ts +3 -0
  51. package/dist/lang/zh.d.ts +3 -0
  52. package/dist/samples-BzI0GdUj.cjs +1 -0
  53. package/dist/{samples-sKAnErVb.js → samples-Cylf7cMw.js} +110 -17
  54. package/dist/style.css +1 -1
  55. package/dist/vue/View.vue.d.ts +1 -1
  56. package/dist/vue/cellHighlights.d.ts +42 -0
  57. package/dist/vue/htmlText.d.ts +4 -0
  58. package/dist/vue/index.d.ts +6 -4
  59. package/dist/vue/keyboardNav.d.ts +9 -0
  60. package/dist/vue/sheetSession.d.ts +24 -0
  61. package/dist/vue.cjs +72 -72
  62. package/dist/vue.js +7023 -6575
  63. package/package.json +16 -15
  64. package/dist/samples-ClV76j10.cjs +0 -1
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Pure statistical rules, separated from the range-reading handlers so they can
3
+ * be unit-tested directly.
4
+ */
5
+ import { type SpreadsheetError } from "../spreadsheet-errors";
6
+ /**
7
+ * The arithmetic mean, or `#DIV/0!` when there is nothing to average.
8
+ *
9
+ * Excel divides by the count of NUMBERS, so a range of blanks or text leaves a
10
+ * zero denominator and reports the division rather than a 0 that reads like a
11
+ * genuine average of zeros.
12
+ */
13
+ export declare function computeAverage(values: number[]): number | SpreadsheetError;
14
+ /**
15
+ * The middle value (the mean of the middle two when the count is even), or
16
+ * `#NUM!` when there is no value to sit in the middle.
17
+ *
18
+ * Excel's code here is `#NUM!`, not AVERAGE's `#DIV/0!` — nothing is divided by
19
+ * zero, the median of an empty set simply does not exist. Sorts a copy so the
20
+ * caller's array keeps its order.
21
+ */
22
+ export declare function computeMedian(values: number[]): number | SpreadsheetError;
23
+ /**
24
+ * The most frequently occurring value, or `#N/A` when no value repeats.
25
+ *
26
+ * Excel's MODE is undefined for an all-distinct set, so returning the first
27
+ * element (as a naive "highest frequency wins" loop does when every count is 1)
28
+ * is a silent wrong answer. Ties resolve to the value that appears first, which
29
+ * Map insertion order preserves.
30
+ */
31
+ export declare function computeMode(values: number[]): number | SpreadsheetError;
32
+ /**
33
+ * Sample variance (Excel VAR): the mean squared deviation divided by `n - 1`,
34
+ * not `n`. Dividing by `n` is the POPULATION variance (Excel's VARP); using it
35
+ * for VAR understates the spread. Fewer than two values leave no `n - 1` to
36
+ * divide by, so Excel reports `#DIV/0!` rather than a silent 0.
37
+ */
38
+ export declare function sampleVariance(values: number[]): number | SpreadsheetError;
39
+ /** Sample standard deviation (Excel STDEV): the square root of the sample
40
+ * variance, and `#DIV/0!` on the same fewer-than-two-values boundary. */
41
+ export declare function sampleStdev(values: number[]): number | SpreadsheetError;
@@ -1,4 +1,20 @@
1
1
  /**
2
2
  * Text Functions
3
3
  */
4
- export {};
4
+ import { type SpreadsheetError } from "../spreadsheet-errors";
5
+ export declare const toProperCase: (text: string) => string;
6
+ export declare const takeLeft: (text: string, count: number) => string | SpreadsheetError;
7
+ export declare const takeRight: (text: string, count: number) => string | SpreadsheetError;
8
+ export declare const substituteText: (text: string, oldText: string, newText: string, instance?: number) => string | SpreadsheetError;
9
+ /** Excel MID: 1-based start, count of characters. `substring` SWAPS its bounds
10
+ * when they are reversed, so a negative count read backwards from `start` and
11
+ * returned earlier characters — `MID("Hello",3,-1)` gave "e" instead of an
12
+ * error. Both arguments are validated here instead. */
13
+ export declare const takeMid: (text: string, start: number, count: number) => string | SpreadsheetError;
14
+ export declare const locateSubstring: (find: string, within: string, start: number, options: {
15
+ caseInsensitive: boolean;
16
+ }) => number | SpreadsheetError;
17
+ /** Excel VALUE: the WHOLE string must be a number once its currency symbols and
18
+ * thousands separators are stripped; trailing text is an error, not a prefix to
19
+ * salvage. */
20
+ export declare const parseValueText: (raw: string) => number | SpreadsheetError;
@@ -0,0 +1,10 @@
1
+ /** Narrow `unknown` to a plain object (not null, not array). */
2
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ /** Narrow `unknown` to any object (not null, arrays allowed).
4
+ * Use `isRecord` when you need to access string keys. */
5
+ export declare function isObj(value: unknown): value is object;
6
+ /** The message to show for a thrown value. A non-Error object with a non-empty
7
+ * string `details` (the gRPC convention) or `message` surfaces that field —
8
+ * `details` wins — instead of the `[object Object]` a bare `String(err)`
9
+ * would print. */
10
+ export declare function errorMessage(err: unknown, fallback?: string): string;
@@ -5,9 +5,20 @@
5
5
  */
6
6
  export * from "./types";
7
7
  export * from "./parser";
8
+ export * from "./condition";
9
+ export * from "./date-locale";
10
+ export * from "./cellEmpty";
11
+ export * from "./datedif";
8
12
  export * from "./formatter";
13
+ export * from "./translateFormula";
14
+ export * from "./formulaError";
15
+ export * from "./spreadsheet-errors";
9
16
  export * from "./evaluator";
10
17
  export * from "./calculator";
18
+ export * from "./formulaRefs";
19
+ export * from "./cellBuilder";
20
+ export * from "./responseDecoder";
21
+ export * from "./jsonCellLocator";
11
22
  export * from "./registry";
12
23
  import "./functions";
13
24
  export { SpreadsheetEngine } from "./engine";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Locate the start offset of a specific cell value inside a
3
+ * pretty-printed JSON spreadsheet document. Extracted from
4
+ * `handleTableClick` in `src/plugins/spreadsheet/View.vue` where the
5
+ * inline scanner pushed cognitive complexity to 163.
6
+ *
7
+ * The scanner walks the raw editor text character-by-character,
8
+ * tracking string boundaries, bracket depth, and object depth to
9
+ * find the n-th cell of the m-th row within a named sheet's `data`
10
+ * array. We deliberately do NOT parse the JSON — we need the
11
+ * character offset inside the user's text buffer (which may not be
12
+ * valid JSON mid-edit), so a positional scan is required.
13
+ *
14
+ * Pure — no refs, no DOM. Returns -1 if the cell can't be located.
15
+ * Tested in `test/plugins/spreadsheet/engine/test_jsonCellLocator.ts`.
16
+ */
17
+ /**
18
+ * Given the pretty-printed JSON editor text, a sheet name, and
19
+ * (rowIndex, colIndex), return the character offset of that cell's
20
+ * JSON token. The returned offset can be fed into
21
+ * `textarea.setSelectionRange(offset, offset + cellJsonLength)` to
22
+ * highlight the cell.
23
+ *
24
+ * Returns -1 if the sheet isn't found or the (row, col) is out of
25
+ * range. Never throws.
26
+ */
27
+ export declare function findCellJsonPosition(editorText: string, sheetName: string, rowIndex: number, colIndex: number): number;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Rounding, modulo and significance math for the mathematical functions.
3
+ *
4
+ * Pure number-in / (number | formula-error-value)-out helpers. The rounding
5
+ * direction and the domain rules are exactly where the engine diverged from
6
+ * Excel (rounding toward +∞ instead of away from zero, modulo taking the
7
+ * dividend's sign, negative bases and domains slipping through as NaN/∞), so
8
+ * they are worth testing apart from the argument-reading handlers.
9
+ */
10
+ import { type SpreadsheetError } from "./spreadsheet-errors";
11
+ /** Excel ROUND: half away from zero (JS `Math.round` breaks half toward +∞, so
12
+ * `ROUND(-2.5, 0)` came back -2 instead of -3). */
13
+ export declare function roundTo(value: number, digits: number): number;
14
+ /** Excel ROUNDUP: away from zero. */
15
+ export declare function roundUpTo(value: number, digits: number): number;
16
+ /** Excel ROUNDDOWN: toward zero. */
17
+ export declare function roundDownTo(value: number, digits: number): number;
18
+ /** Excel FLOOR: nearest multiple of `significance` toward zero; opposite signs
19
+ * are #NUM!, and a zero significance is #DIV/0! — the division by the
20
+ * significance is what FLOOR reports, so it wins over the sign check. */
21
+ export declare function floorToSignificance(value: number, significance: number): number | SpreadsheetError;
22
+ /** Excel CEILING: nearest multiple of `significance` away from zero; opposite
23
+ * signs are #NUM!, and a zero significance is 0 — deliberately NOT FLOOR's
24
+ * #DIV/0!, an asymmetry Excel keeps and this engine has to match. */
25
+ export declare function ceilingToSignificance(value: number, significance: number): number | SpreadsheetError;
26
+ /** Excel MOD: result takes the divisor's sign (`MOD(-3, 2) === 1`); dividing by
27
+ * zero is #DIV/0!, not a silent 0. */
28
+ export declare function modulo(value: number, divisor: number): number | SpreadsheetError;
29
+ /** Excel POWER: a negative base with a non-integer exponent has no real root, so
30
+ * it is #NUM! rather than JS's NaN. */
31
+ export declare function power(base: number, exponent: number): number | SpreadsheetError;
32
+ /** Natural log guarded to its domain: `LN(x)` for `x <= 0` is #NUM!, not
33
+ * -∞ / NaN. Reused by LN and LOG. */
34
+ export declare function safeLog(value: number): number | SpreadsheetError;
35
+ /** Excel LOG(value, base): the base must also be positive and not 1, or the
36
+ * result is #NUM! rather than the ∞ / NaN a bare division would give. */
37
+ export declare function logWithBase(value: number, base: number): number | SpreadsheetError;
38
+ /** Base-10 log guarded to its domain (`LOG10(x)` for `x <= 0` is #NUM!). */
39
+ export declare function safeLog10(value: number): number | SpreadsheetError;
40
+ /** Square root guarded to its domain (`SQRT(x)` for `x < 0` is #NUM!, not NaN). */
41
+ export declare function safeSqrt(value: number): number | SpreadsheetError;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Numeric coercion helpers for the spreadsheet engine.
3
+ *
4
+ * Two intentionally different reads share one string parser (`parseNumericString`):
5
+ * - `registry.toNumber` — lenient, for range aggregation (SUM / AVERAGE / …):
6
+ * anything unreadable becomes 0. PINNED (booleans are 0, not Excel's 1/0).
7
+ * - `toScalarNumber` — strict, for single-value math functions (ABS / SIGN):
8
+ * booleans are 1/0 and non-numeric text is `#VALUE!`, matching Excel.
9
+ *
10
+ * `holdsNumber` asks the question neither read can answer once it has coerced:
11
+ * was there a number here at all, or is this 0 the reading of something else.
12
+ */
13
+ import type { CellValue } from "./types";
14
+ import { type SpreadsheetError } from "./spreadsheet-errors";
15
+ /**
16
+ * Parse a spreadsheet string as a number, or `null` when it holds no number.
17
+ *
18
+ * Mirrors the engine's long-standing lenient read: a percentage, a currency
19
+ * amount, or a thousands-separated value, else a bare `parseFloat` (which reads a
20
+ * leading number out of "12abc"). The branch order is load-bearing — each strips
21
+ * only its own characters, so a string mixing "%" and "$" fails in the first.
22
+ */
23
+ export declare function parseNumericString(value: string): number | null;
24
+ /**
25
+ * Whether a value holds a number at all, under the same reading `toNumber` uses.
26
+ *
27
+ * `toNumber` answers 0 for text, for a boolean and for a genuine 0 alike, so a
28
+ * caller that must tell "no number here" from "the number zero" — COUNT — cannot
29
+ * ask it. Booleans are not numbers here, matching `toNumber`'s PINNED behaviour.
30
+ */
31
+ export declare function holdsNumber(value: CellValue): boolean;
32
+ /**
33
+ * Strict scalar coercion for single-value math functions (ABS, SIGN). Follows
34
+ * Excel's scalar rules: a boolean is 1 / 0 and genuinely non-numeric text is
35
+ * `#VALUE!` rather than a silent 0. A partly-numeric string still yields its
36
+ * leading number, matching the engine's other numeric reads.
37
+ */
38
+ export declare function toScalarNumber(value: CellValue): number | SpreadsheetError;
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Handles Excel A1 notation parsing and conversion
5
5
  */
6
- import type { CellRef, RangeRef } from "./types";
7
6
  /**
8
7
  * Convert Excel column letters to 0-based index
9
8
  * A=0, B=1, ..., Z=25, AA=26, etc.
@@ -20,26 +19,3 @@ export declare function columnToIndex(col: string): number;
20
19
  * @returns Column letters (e.g., "A", "Z", "AA")
21
20
  */
22
21
  export declare function indexToColumn(index: number): string;
23
- /**
24
- * Parse a cell reference to its components
25
- * Supports: A1, $A$1, $A1, A$1, Sheet1!A1, 'My Sheet'!A1
26
- *
27
- * @param ref - Cell reference string
28
- * @returns Parsed cell reference object
29
- */
30
- export declare function parseCellRef(ref: string): CellRef;
31
- /**
32
- * Parse a range reference to its components
33
- * Supports: A1:B10, $A$1:$B$10, Sheet1!A1:B10
34
- *
35
- * @param range - Range reference string
36
- * @returns Parsed range reference object
37
- */
38
- export declare function parseRangeRef(range: string): RangeRef;
39
- /**
40
- * Convert a cell reference object back to A1 notation
41
- *
42
- * @param ref - Cell reference object
43
- * @returns A1 notation string
44
- */
45
- export declare function cellRefToA1(ref: CellRef): string;
@@ -10,11 +10,37 @@ export type CellGetter = (ref: string) => CellValue;
10
10
  export type RangeGetter = (range: string) => CellValue[];
11
11
  export type RawRangeGetter = (range: string) => CellValue[];
12
12
  export interface FunctionContext {
13
+ /** The registry name the formula invoked, so a handler can report a failure
14
+ * the way the evaluator names it. */
15
+ functionName: string;
13
16
  getCellValue: CellGetter;
14
17
  getRangeValues: RangeGetter;
15
- getRangeValuesRaw?: RawRangeGetter;
18
+ getRangeValuesRaw?: RawRangeGetter | undefined;
16
19
  evaluateFormula: (formula: string) => CellValue;
17
20
  }
21
+ /** The too-few-arguments error, raised by the evaluator from the registry's
22
+ * `minArgs` before any handler runs — and by `requiredArg` for the argument a
23
+ * handler actually reads, so both report one wording. */
24
+ export declare const tooFewArgumentsError: (funcName: string, minArgs: number) => Error;
25
+ /** The argument at `index`, which the caller has already established is there —
26
+ * the registry's `minArgs` for a mandatory argument, an `args.length` branch for
27
+ * an optional one. An absent one means that guarantee is wrong, so it raises the
28
+ * arity error the guarantee should have raised. Never a default value: a
29
+ * substituted 0 or "" computes a plausible wrong answer, which is worse than
30
+ * the error the formula deserves. */
31
+ export declare const requiredArg: (context: FunctionContext, args: string[], index: number) => string;
32
+ /** The range reader that keeps every cell, for functions whose answer depends on
33
+ * a range's POSITIONS rather than its numbers.
34
+ *
35
+ * `getRangeValues` drops non-numeric cells, which silently renumbers the rows
36
+ * underneath: a criteria/value pair read that way falls out of alignment and
37
+ * aggregates a different row, and an index returned from it points at the wrong
38
+ * one. SUMIF/AVERAGEIF were moved here in #2358; MATCH/XLOOKUP in #2765.
39
+ *
40
+ * Falls back to the numeric reader because `getRangeValuesRaw` is optional on
41
+ * `FunctionContext` — a host that predates it keeps its old behaviour rather
42
+ * than throwing. */
43
+ export declare const rawRangeReader: (context: FunctionContext) => RangeGetter;
18
44
  export type FunctionHandler = (args: string[], context: FunctionContext) => CellValue;
19
45
  export interface FunctionDefinition {
20
46
  name: string;
@@ -35,7 +61,9 @@ declare class FunctionRegistry {
35
61
  }
36
62
  export declare const functionRegistry: FunctionRegistry;
37
63
  /**
38
- * Helper function to convert a value to a number
64
+ * Lenient numeric coercion for range aggregation: anything unreadable is 0.
65
+ * PINNED behaviour (booleans are 0, not Excel's 1/0) — the string parsing lives
66
+ * in numericCoercion.parseNumericString, shared with the strict scalar read.
39
67
  */
40
68
  export declare function toNumber(value: CellValue): number;
41
69
  /**
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Decode the `/api/files/content` response for a spreadsheet file
3
+ * into an "ok with sheets" / "error with message" discriminated
4
+ * union. Extracted from `fetchSheets` in
5
+ * `src/plugins/spreadsheet/View.vue` where the decision tree was
6
+ * inlined as several nested try/catch + if branches.
7
+ *
8
+ * Pure — no fetch, no refs. Takes the parsed JSON body and returns
9
+ * a result the caller can pattern-match on. Tested in
10
+ * `test/plugins/spreadsheet/engine/test_responseDecoder.ts`.
11
+ */
12
+ import type { SheetData } from "./types.js";
13
+ /** Shape of the `/api/files/content` response we care about. The
14
+ * server returns more fields (kind, size, modifiedMs, …) but this
15
+ * decoder only depends on the three that drive branching. */
16
+ export interface FilesContentResponseLike {
17
+ kind?: string;
18
+ content?: string;
19
+ message?: string;
20
+ }
21
+ export type DecodeResult = {
22
+ kind: "ok";
23
+ sheets: SheetData[];
24
+ } | {
25
+ kind: "error";
26
+ message: string;
27
+ };
28
+ /**
29
+ * Turn a parsed `/files/content` body into an OK/error decision:
30
+ *
31
+ * - `kind` present and not "text" → error with the server's message
32
+ * (e.g. "too-large", "binary"). Spreadsheets only live in text
33
+ * JSON files.
34
+ * - Missing or non-string `content` → error.
35
+ * - `content` is not valid JSON → error.
36
+ * - Parsed `content` is not an array → error (server should never
37
+ * return a non-array but the guard protects downstream render).
38
+ * - Otherwise → ok with the sheets array.
39
+ */
40
+ export declare function decodeSpreadsheetResponse(body: FilesContentResponseLike): DecodeResult;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Formula errors as a distinct VALUE type.
3
+ *
4
+ * A `#NUM!` produced by `SQRT(-1)` and the text `"#NUM!"` produced by
5
+ * `CONCAT("#N","UM!")` used to be the same string, so IFERROR could not tell a
6
+ * real error from text that merely spells one (#2451). An error is now its own
7
+ * value carrying the code, which is what the error-aware functions check; the
8
+ * display pass renders it back to `#NUM!` so cells look unchanged.
9
+ */
10
+ export declare const SPREADSHEET_ERRORS: readonly ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A", "#ERROR!"];
11
+ export type SpreadsheetErrorCode = (typeof SPREADSHEET_ERRORS)[number];
12
+ /** A formula error, distinct from any string. `toString` yields the code so the
13
+ * value renders as `#NUM!` wherever the engine coerces a cell value to text. */
14
+ export declare class SpreadsheetError {
15
+ readonly code: SpreadsheetErrorCode;
16
+ constructor(code: SpreadsheetErrorCode);
17
+ toString(): string;
18
+ toJSON(): string;
19
+ }
20
+ export declare const NULL_ERROR: SpreadsheetError;
21
+ export declare const DIV_ZERO_ERROR: SpreadsheetError;
22
+ export declare const VALUE_ERROR: SpreadsheetError;
23
+ export declare const REF_ERROR: SpreadsheetError;
24
+ export declare const NAME_ERROR: SpreadsheetError;
25
+ export declare const NUM_ERROR: SpreadsheetError;
26
+ export declare const NA_ERROR: SpreadsheetError;
27
+ export declare const UNKNOWN_ERROR: SpreadsheetError;
28
+ /** The error value for a code. */
29
+ export declare const spreadsheetError: (code: SpreadsheetErrorCode) => SpreadsheetError;
30
+ /** Whether a value is one of the error CODES as a plain string. Text that spells
31
+ * an error is not an error value — see `isSpreadsheetErrorValue`. */
32
+ export declare function isSpreadsheetError(value: unknown): value is SpreadsheetErrorCode;
33
+ /** Whether a value IS a formula error, as opposed to text that spells one. */
34
+ export declare function isSpreadsheetErrorValue(value: unknown): value is SpreadsheetError;
35
+ /** The code behind a value: an error value's own code, or the code a plain
36
+ * string spells. Used where a literal `#REF!` written into a cell must still
37
+ * poison the arithmetic that reads it, provenance aside. */
38
+ export declare function errorCodeOf(value: unknown): SpreadsheetErrorCode | null;
39
+ /** Whether an evaluated result should be treated as an error: a formula error
40
+ * VALUE, a NaN / infinite number, or a missing value. This is what IFERROR
41
+ * catches — deliberately NOT a look-alike string, which is ordinary text. */
42
+ export declare function isErrorResult(value: unknown): boolean;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Excel number-format patterns for the TEXT function.
3
+ *
4
+ * Deliberately separate from `formatter.formatNumber`, which renders CELL
5
+ * display values: the two disagree on defaults — Excel's `TEXT(0.5,"0%")` is
6
+ * `"50%"`, while a cell carrying the format `0%` has always displayed
7
+ * `"50.00%"` — so sharing the whole path would move every formatted cell in
8
+ * every stored workbook. Only the digit-grouping primitive is shared.
9
+ */
10
+ export interface NumberPattern {
11
+ prefix: string;
12
+ suffix: string;
13
+ useGrouping: boolean;
14
+ integerMinDigits: number;
15
+ minDecimals: number;
16
+ maxDecimals: number;
17
+ isPercent: boolean;
18
+ }
19
+ /**
20
+ * Split a format code into a leading literal, one numeric core and a trailing
21
+ * literal, or `null` when the code uses a feature this module does not render.
22
+ */
23
+ export declare const parseNumberPattern: (pattern: string) => NumberPattern | null;
24
+ /**
25
+ * Render a number the way Excel's TEXT does for the common numeric format
26
+ * codes: `#,##0` grouping, `0.00` fixed decimals, `0.##` optional decimals, a
27
+ * literal prefix/suffix such as `$`, and a `%` that scales by 100.
28
+ *
29
+ * Returns `null` for a format code it does not render, so the caller can keep
30
+ * its own fallback rather than inventing a plausible wrong string.
31
+ */
32
+ export declare const formatWithPattern: (value: number, pattern: string) => string | null;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Excel-formula → JS-expression translation
3
+ *
4
+ * Pure string transforms that turn the Excel-operator form of an
5
+ * already-substituted expression (cell references resolved, functions replaced)
6
+ * into the JavaScript form handed to `new Function`, plus the character
7
+ * allowlists that gate that evaluation. No engine or evaluator state is
8
+ * captured — every function here is input → output only.
9
+ */
10
+ /** Excel `^` exponentiation → JS `**`.
11
+ *
12
+ * Excel's `^` is left-associative (`2^3^2` = `(2^3)^2` = 64); JS `**` is
13
+ * right-associative (`2**3**2` = `2**(3**2)` = 512). This transform does not
14
+ * bridge that difference, so a chained `^` still evaluates the JS way — a known
15
+ * limitation tracked separately (#2359), pinned by the tests. */
16
+ export declare function caretToPow(expr: string): string;
17
+ /** Excel `&` string concatenation → JS `+`, leaving any `&` inside a quoted
18
+ * string literal untouched. A quote toggles the "inside a literal" state only
19
+ * when it is not backslash-escaped. */
20
+ export declare function replaceConcatOperator(expr: string): string;
21
+ /** Excel `=` equality → JS `==`, without disturbing `<=`, `>=` or `!=`. A single
22
+ * `=` is rewritten only when flanked by a non-`<>!` char on the left and a
23
+ * non-`=` on the right.
24
+ *
25
+ * Known limitations, pinned by the tests and tracked separately (#2359): the
26
+ * match consumes both flanking characters so replacements do not overlap
27
+ * (`5=6=7` → `5==6=7`, only the first rewritten); a `=` at either end of the
28
+ * expression has no left/right neighbour and is left alone (`5=` → `5=`); and a
29
+ * hand-typed `==` becomes `===` (its second `=` matches). Excel never emits the
30
+ * last two, so they only bite malformed input. */
31
+ export declare function rewriteComparisonEq(expr: string): string;
32
+ /** Whether an expression contains only the characters an arithmetic evaluation
33
+ * may see: digits, ` + - * / ( ) . ` and spaces. Gates `new Function`. */
34
+ export declare function isSafeArithmetic(expr: string): boolean;
35
+ /** Whether an expression contains only the characters a comparison evaluation
36
+ * may see: the arithmetic set plus ` < > ! = `. Gates `new Function`. */
37
+ export declare function isSafeComparison(expr: string): boolean;
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * Spreadsheet Engine Type Definitions
3
3
  */
4
- export type CellValue = number | string | boolean;
4
+ import type { SpreadsheetError } from "./spreadsheet-errors";
5
+ /** A value as it is STORED in a cell and serialized to the workbook JSON. A
6
+ * formula error is never stored — it only exists as a computed result. */
7
+ export type StoredCellValue = number | string | boolean;
8
+ /** A value as the engine COMPUTES it: a stored value, or a formula error. */
9
+ export type CellValue = StoredCellValue | SpreadsheetError;
5
10
  export interface SpreadsheetCell {
6
- v: CellValue;
11
+ v: StoredCellValue;
7
12
  f?: string;
8
13
  }
9
14
  export interface SheetData {
@@ -46,7 +51,16 @@ export interface CalculationError {
46
51
  error: string;
47
52
  type: "circular" | "invalid_ref" | "div_zero" | "syntax" | "unknown";
48
53
  }
49
- export interface EngineOptions {
54
+ /** Per-calculation settings that change how cell CONTENT is read, as opposed
55
+ * to how the engine runs. Passed down explicitly so the engine stays pure —
56
+ * no ambient locale, no import-time state. */
57
+ export interface CalculateOptions {
58
+ /** Read an ambiguous `A/B/YYYY` date as day-first. Only affects dates whose
59
+ * two leading numbers are both 12 or under; anything else decides itself.
60
+ * See engine/date-locale.ts for how a locale maps onto this. */
61
+ preferDDMMYYYY?: boolean;
62
+ }
63
+ export interface EngineOptions extends CalculateOptions {
50
64
  maxIterations?: number;
51
65
  enableCrossSheetRefs?: boolean;
52
66
  strictMode?: boolean;
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./samples-ClV76j10.cjs");require("./core.cjs"),exports.SYSTEM_PROMPT=e.i,exports.TOOL_DEFINITION=e.a,exports.TOOL_NAME=e.o,exports.executeSpreadsheet=e.n,exports.pluginCore=e.r,exports.samples=e.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./samples-BzI0GdUj.cjs");require("./core.cjs"),exports.SYSTEM_PROMPT=e.d,exports.TOOL_DEFINITION=e.f,exports.TOOL_NAME=e.p,exports.executeSpreadsheet=e.n,exports.executeSpreadsheetDispatch=e.i,exports.isSpreadsheetArtifactPath=e.s,exports.isSpreadsheetDispatchArgs=e.a,exports.pluginCore=e.r,exports.samples=e.t;
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { a as e, i as t, n, o as r, r as i, t as a } from "./samples-sKAnErVb.js";
1
+ import { a as e, d as t, f as n, i as r, n as i, p as a, r as o, s, t as c } from "./samples-Cylf7cMw.js";
2
2
  import "./core.js";
3
- export { t as SYSTEM_PROMPT, e as TOOL_DEFINITION, r as TOOL_NAME, n as executeSpreadsheet, i as pluginCore, a as samples };
3
+ export { t as SYSTEM_PROMPT, n as TOOL_DEFINITION, a as TOOL_NAME, i as executeSpreadsheet, r as executeSpreadsheetDispatch, s as isSpreadsheetArtifactPath, e as isSpreadsheetDispatchArgs, o as pluginCore, c as samples };
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const de: Messages;
3
+ export default de;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const en: Messages;
3
+ export default en;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const es: Messages;
3
+ export default es;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const fr: Messages;
3
+ export default fr;
@@ -0,0 +1 @@
1
+ export declare const useT: () => import("vue").ComputedRef<import("./messages").Messages>;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const ja: Messages;
3
+ export default ja;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const ko: Messages;
3
+ export default ko;
@@ -0,0 +1,20 @@
1
+ export interface Messages {
2
+ untitled: string;
3
+ sheetCount(count: number): string;
4
+ excel: string;
5
+ valuePlaceholder: string;
6
+ valueOrFormulaPlaceholder: string;
7
+ formatPlaceholder: string;
8
+ loading: string;
9
+ noData: string;
10
+ editData: string;
11
+ applyChanges: string;
12
+ dataMustBeArray: string;
13
+ loadFailed(error: string): string;
14
+ saveFailed(error: string): string;
15
+ invalidJsonAlert(error: string): string;
16
+ unknownError: string;
17
+ update: string;
18
+ stringType: string;
19
+ formulaType: string;
20
+ }
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const ptBR: Messages;
3
+ export default ptBR;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const zh: Messages;
3
+ export default zh;
@@ -0,0 +1 @@
1
+ let e=require("gui-chat-protocol");var t=`presentSpreadsheet`,n={type:`function`,name:t,description:`Display an Excel-like spreadsheet with formulas and calculations.`,parameters:{type:`object`,properties:{title:{type:`string`,description:`Title for the spreadsheet`},sheets:{type:`array`,description:`Sheets to render as spreadsheet tabs. Each sheet includes a name and 2D array of cells (rows x columns).`,items:{type:`object`,properties:{name:{type:`string`,description:`Sheet name (e.g., 'Sales Q1', 'Summary')`},data:{type:`array`,description:`Rows of cells. Each cell is an object with 'v' (value) and 'f' (format). Use Excel-style A1 notation in formulas: columns are letters (A, B, C...), rows are 1-based numbers (1, 2, 3...). Values can be text, numbers, dates, or formulas. Examples: [{"v": "Product"}, {"v": 2024, "f": "#,##0"}, {"v": "01/15/2025", "f": "MM/DD/YYYY"}, {"v": "=B2*1.05", "f": "$#,##0.00"}]. Format codes: '$#,##0.00' (currency), '#,##0' (integer), '0.00%' (percent), '0.00' (decimal), 'MM/DD/YYYY' (date), 'DD-MMM-YYYY' (date), 'YYYY-MM-DD' (ISO date).`,items:{type:`array`,description:`Row of cells. Each cell is an object with value and format.`,items:{type:`object`,description:`Cell object with value and optional format. If value is a string starting with '=', it's treated as a formula.`,properties:{v:{oneOf:[{type:`string`},{type:`number`},{type:`boolean`}],description:`Cell value. Can be text, number, date, or formula (string starting with '='). Examples: 'Revenue', 1500000, '01/15/2025', '=SUM(A1:A10)', '=B2-TODAY()'. Date strings like '01/15/2025' are automatically parsed to date serial numbers.`},f:{type:`string`,description:`Optional format code for displaying the value. Common formats: '$#,##0.00' (currency), '#,##0' (integer), '0.00%' (percent), '0.00' (decimal), 'MM/DD/YYYY' (date), 'DD-MMM-YYYY' (date), 'YYYY-MM-DD' (ISO date)`}},required:[`v`]}}}},required:[`name`,`data`]}}},required:[`title`,`sheets`]}},r=`Use ${t} whenever the user needs a spreadsheet-style table, multi-step math, or dynamic what-if analysis—do not summarize in text. Build LIVE sheets where every cell is an object {"v": value, "f": format}. For formulas, set "v" to a string starting with "=" (e.g., {"v": "=B2*1.05", "f": "$#,##0.00"}). For dates, use date strings like "01/15/2025" or date formulas like "=TODAY()" or "=DATE(2025,1,15)". The spreadsheet auto-parses common date formats (MM/DD/YYYY, YYYY-MM-DD, DD-MMM-YYYY) into date serial numbers for calculations. Date arithmetic works: "=B2-TODAY()" calculates days between dates. Never pre-calculate; let the spreadsheet compute using cell refs, functions (SUM, AVERAGE, IF, TODAY, DATE, DATEDIF, etc.), and arithmetic. Standard formats: "$#,##0.00" currency, "#,##0" integer, "0.00%" percent, "0.00" decimal, "MM/DD/YYYY" date, "DD-MMM-YYYY" date, "YYYY-MM-DD" ISO date. Format is optional for plain text/numbers.`;function i(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function a(e){return typeof e==`object`&&!!e}var o=(e,t)=>i(e)&&typeof e[t]==`string`;function s(e,t){return e instanceof Error?e.message:o(e,`details`)&&e.details?e.details:o(e,`message`)&&e.message?e.message:t===void 0?String(e):t}var c=`spreadsheets`,l=`.json`,u=16,d=2,f=e=>`${e.getUTCFullYear()}/${String(e.getUTCMonth()+1).padStart(d,`0`)}`,p=()=>globalThis.crypto.randomUUID().split(`-`).join(``).slice(0,u);function m(t=new Date,n=p()){let r=`${c}/${f(t)}/${n}${l}`;return{relPath:r,filePath:`${e.ARTIFACTS_ROOT}/${r}`}}var h=new Set([``,`.`,`..`]);function g(t){return typeof t!=`string`||!t.startsWith(`${e.ARTIFACTS_ROOT}/${c}/`)||!t.endsWith(l)||t.includes(`\\`)?!1:!t.split(`/`).some(e=>h.has(e))}var _=t=>t.slice(e.ARTIFACTS_ROOT.length+1),v=e=>typeof e==`string`||typeof e==`number`||typeof e==`boolean`,y=e=>e===null||v(e)||i(e)&&(e.v===void 0||e.v===null||v(e.v))&&(e.f===void 0||typeof e.f==`string`),b=e=>Array.isArray(e)&&e.every(y),x=e=>i(e)&&typeof e.name==`string`&&Array.isArray(e.data)&&e.data.every(b);function S(e){return Array.isArray(e)&&e.every(x)?e:null}function C(e){return!i(e)||typeof e.path!=`string`?!1:e.kind===`loadSpreadsheet`||e.kind===`saveSpreadsheet`&&Array.isArray(e.sheets)&&e.sheets.every(x)}var w=e=>i(e)&&typeof e.read==`function`&&typeof e.write==`function`;function T(e){if(!i(e)||!i(e.files))return null;let t=e.files.artifacts;return w(t)?t:null}async function E(e,t){let n=await e.read(_(t)),r=S(JSON.parse(n));if(!r)throw Error(`${t} does not hold an array of sheets`);return{message:`Loaded spreadsheet ${t}`,data:{sheets:r}}}async function D(e,t){if(!g(t.path))throw Error(`path must be a .json file under artifacts/spreadsheets/`);return t.kind===`loadSpreadsheet`?E(e,t.path):(await e.write(_(t.path),JSON.stringify(t.sheets)),{message:`Saved spreadsheet ${t.path}`,data:{sheets:t.path}})}function O(e){if(typeof e!=`string`)return e;try{let t=JSON.parse(e);return console.warn(`Sheets was provided as a string and has been parsed to an array`),t}catch(e){throw Error(`Invalid sheets format: sheets must be an array, not a string. Parse error: ${e instanceof Error?e.message:String(e)}`)}}function k(e){if(!Array.isArray(e)||e.length===0)throw Error(`At least one sheet is required. Sheets must be an array of sheet objects.`);e.forEach(e=>{if(!e.name||!e.data||e.data.length===0)throw Error(`Invalid sheet: ${e.name||`unnamed`}. Each sheet must have a name and data array.`)})}var A=async(e,t)=>{let{title:n}=t,r=O(t.sheets);k(r);let i=`Acknowledge that the spreadsheet has been created and is displayed to the user.`,a=T(e);if(!a)return{message:`Created spreadsheet: ${n}`,title:n,data:{sheets:r},instructions:i};let{relPath:o,filePath:s}=m();return await a.write(o,JSON.stringify(r)),{message:`Created spreadsheet: ${n}. Saved to ${s}; the user can edit cells there, so read that file for the current values.`,title:n,data:{sheets:s},instructions:i}};async function j(e,t){if(!(`kind`in t))return A(e,t);let n=T(e);if(!n)throw Error(`This host does not provide files.artifacts, so spreadsheets cannot be loaded or saved`);if(!C(t))throw Error(`Invalid spreadsheet dispatch`);return D(n,t)}var M={toolDefinition:n,execute:j,generatingMessage:`Creating spreadsheet...`,waitingMessage:`Tell the user that the spreadsheet was created and will be presented shortly.`,isEnabled:()=>!0,systemPrompt:r},N=[{name:`Simple Budget`,args:{title:`Monthly Budget`,sheets:[{name:`Budget`,data:[[{v:`Category`},{v:`Amount`}],[{v:`Income`},{v:5e3,f:`$#,##0.00`}],[{v:`Rent`},{v:-1500,f:`$#,##0.00`}],[{v:`Utilities`},{v:-200,f:`$#,##0.00`}],[{v:`Food`},{v:-600,f:`$#,##0.00`}],[{v:`Transport`},{v:-300,f:`$#,##0.00`}],[{v:`Total`},{v:`=SUM(B2:B6)`,f:`$#,##0.00`}]]}]}},{name:`Sales Report`,args:{title:`Q1 Sales Report`,sheets:[{name:`Sales`,data:[[{v:`Product`},{v:`Jan`},{v:`Feb`},{v:`Mar`},{v:`Total`}],[{v:`Widget A`},{v:1200},{v:1350},{v:1500},{v:`=SUM(B2:D2)`}],[{v:`Widget B`},{v:800},{v:920},{v:1100},{v:`=SUM(B3:D3)`}],[{v:`Widget C`},{v:450},{v:480},{v:520},{v:`=SUM(B4:D4)`}],[{v:`Total`},{v:`=SUM(B2:B4)`},{v:`=SUM(C2:C4)`},{v:`=SUM(D2:D4)`},{v:`=SUM(E2:E4)`}]]}]}},{name:`Loan Calculator`,args:{title:`Loan Calculator`,sheets:[{name:`Calculator`,data:[[{v:`Loan Amount`},{v:25e4,f:`$#,##0`}],[{v:`Interest Rate`},{v:.065,f:`0.00%`}],[{v:`Term (Years)`},{v:30}],[{v:``},{v:``}],[{v:`Monthly Payment`},{v:`=PMT(B2/12, B3*12, -B1)`,f:`$#,##0.00`}],[{v:`Total Interest`},{v:`=B5*B3*12-B1`,f:`$#,##0.00`}]]}]}},{name:`World Population`,args:{title:`世界人口(上位10)`,sheets:[{name:`世界人口(上位10)`,data:[[{v:`順位`},{v:`国・地域`},{v:`人口(概数)`,f:`#,##0`},{v:`上位10合計に占める割合`},{v:`世界人口に占める割合`}],[{v:1,f:`#,##0`},{v:`インド`},{v:1428627e3,f:`#,##0`},{v:`=C2/$C$12`,f:`0.00%`},{v:`=C2/$C$13`,f:`0.00%`}],[{v:2,f:`#,##0`},{v:`中国`},{v:1425671e3,f:`#,##0`},{v:`=C3/$C$12`,f:`0.00%`},{v:`=C3/$C$13`,f:`0.00%`}],[{v:3,f:`#,##0`},{v:`アメリカ合衆国`},{v:334e6,f:`#,##0`},{v:`=C4/$C$12`,f:`0.00%`},{v:`=C4/$C$13`,f:`0.00%`}],[{v:4,f:`#,##0`},{v:`インドネシア`},{v:277e6,f:`#,##0`},{v:`=C5/$C$12`,f:`0.00%`},{v:`=C5/$C$13`,f:`0.00%`}],[{v:5,f:`#,##0`},{v:`パキスタン`},{v:24e7,f:`#,##0`},{v:`=C6/$C$12`,f:`0.00%`},{v:`=C6/$C$13`,f:`0.00%`}],[{v:6,f:`#,##0`},{v:`ナイジェリア`},{v:219e6,f:`#,##0`},{v:`=C7/$C$12`,f:`0.00%`},{v:`=C7/$C$13`,f:`0.00%`}],[{v:7,f:`#,##0`},{v:`ブラジル`},{v:216e6,f:`#,##0`},{v:`=C8/$C$12`,f:`0.00%`},{v:`=C8/$C$13`,f:`0.00%`}],[{v:8,f:`#,##0`},{v:`バングラデシュ`},{v:172e6,f:`#,##0`},{v:`=C9/$C$12`,f:`0.00%`},{v:`=C9/$C$13`,f:`0.00%`}],[{v:9,f:`#,##0`},{v:`ロシア`},{v:146e6,f:`#,##0`},{v:`=C10/$C$12`,f:`0.00%`},{v:`=C10/$C$13`,f:`0.00%`}],[{v:10,f:`#,##0`},{v:`メキシコ`},{v:13e7,f:`#,##0`},{v:`=C11/$C$12`,f:`0.00%`},{v:`=C11/$C$13`,f:`0.00%`}],[{v:``},{v:`合計(上位10)`},{v:`=SUM(C2:C11)`,f:`#,##0`},{v:``},{v:``}],[{v:``},{v:`世界推定人口(例)`},{v:805e7,f:`#,##0`},{v:``},{v:``}]]}]}}];Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"d",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"f",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,"p",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return i}});