@pixerate/editor 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +20 -11
  2. package/dist/agent/index.cjs +8 -8
  3. package/dist/agent/index.d.cts +2 -2
  4. package/dist/agent/index.d.ts +2 -2
  5. package/dist/agent/index.js +2 -2
  6. package/dist/canvas/index.cjs +1269 -13
  7. package/dist/canvas/index.cjs.map +1 -1
  8. package/dist/canvas/index.d.cts +432 -6
  9. package/dist/canvas/index.d.ts +432 -6
  10. package/dist/canvas/index.js +1264 -8
  11. package/dist/canvas/index.js.map +1 -1
  12. package/dist/{chunk-U6NDQFOK.cjs → chunk-6M3BCTBD.cjs} +427 -138
  13. package/dist/chunk-6M3BCTBD.cjs.map +1 -0
  14. package/dist/{chunk-TVRC4KVT.cjs → chunk-AA4ZEVCC.cjs} +845 -90
  15. package/dist/chunk-AA4ZEVCC.cjs.map +1 -0
  16. package/dist/{chunk-LA7PEGU3.cjs → chunk-CL37JGFW.cjs} +4 -4
  17. package/dist/{chunk-LA7PEGU3.cjs.map → chunk-CL37JGFW.cjs.map} +1 -1
  18. package/dist/{chunk-A7DKIA72.js → chunk-DENSCKFC.js} +642 -69
  19. package/dist/chunk-DENSCKFC.js.map +1 -0
  20. package/dist/{chunk-4QPLEUL2.cjs → chunk-GVSMRPHB.cjs} +1046 -54
  21. package/dist/chunk-GVSMRPHB.cjs.map +1 -0
  22. package/dist/{chunk-KYRZDWM4.js → chunk-JJDLPEWL.js} +2 -2
  23. package/dist/{chunk-3TYMNULT.js → chunk-MJZBNWG7.js} +807 -52
  24. package/dist/chunk-MJZBNWG7.js.map +1 -0
  25. package/dist/{chunk-CYFJCKC4.js → chunk-QGHGJ56O.js} +997 -5
  26. package/dist/chunk-QGHGJ56O.js.map +1 -0
  27. package/dist/{chunk-GVW4P7BG.js → chunk-RU52I7QO.js} +305 -16
  28. package/dist/chunk-RU52I7QO.js.map +1 -0
  29. package/dist/{chunk-JYRIOFEN.cjs → chunk-SAZRENEZ.cjs} +647 -74
  30. package/dist/chunk-SAZRENEZ.cjs.map +1 -0
  31. package/dist/{controller-OUdxPY-X.d.ts → controller-B7GIybc4.d.cts} +139 -7
  32. package/dist/{controller-OUdxPY-X.d.cts → controller-B7GIybc4.d.ts} +139 -7
  33. package/dist/extensions/index.cjs +14 -4
  34. package/dist/extensions/index.cjs.map +1 -1
  35. package/dist/extensions/index.d.cts +3 -3
  36. package/dist/extensions/index.d.ts +3 -3
  37. package/dist/extensions/index.js +13 -3
  38. package/dist/{index-k3mFbPx_.d.cts → index-CCr_z9kF.d.cts} +3 -3
  39. package/dist/{index-Ng1pG7JE.d.ts → index-Ck6ajwXh.d.ts} +3 -3
  40. package/dist/index.cjs +250 -16
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +294 -8
  43. package/dist/index.d.ts +294 -8
  44. package/dist/index.js +241 -7
  45. package/dist/index.js.map +1 -1
  46. package/dist/{registry-iAHbqkYt.d.cts → registry-GJI4gToU.d.cts} +15 -4
  47. package/dist/{registry-iAHbqkYt.d.ts → registry-GJI4gToU.d.ts} +15 -4
  48. package/dist/serializers/index.cjs +3 -3
  49. package/dist/serializers/index.d.cts +1 -1
  50. package/dist/serializers/index.d.ts +1 -1
  51. package/dist/serializers/index.js +2 -2
  52. package/dist/spreadsheet/index.cjs +7 -3
  53. package/dist/spreadsheet/index.cjs.map +1 -1
  54. package/dist/spreadsheet/index.d.cts +33 -4
  55. package/dist/spreadsheet/index.d.ts +33 -4
  56. package/dist/spreadsheet/index.js +6 -2
  57. package/dist/{track-changes-G8JcWB8b.d.cts → track-changes-BSF58RK0.d.cts} +92 -9
  58. package/dist/{track-changes-G8JcWB8b.d.ts → track-changes-BSF58RK0.d.ts} +92 -9
  59. package/dist/{types-DMe0vFB1.d.cts → types-CJ4U-HHI.d.cts} +1 -1
  60. package/dist/{types-DMe0vFB1.d.ts → types-CJ4U-HHI.d.ts} +1 -1
  61. package/package.json +1 -1
  62. package/dist/chunk-3TYMNULT.js.map +0 -1
  63. package/dist/chunk-4QPLEUL2.cjs.map +0 -1
  64. package/dist/chunk-A7DKIA72.js.map +0 -1
  65. package/dist/chunk-CYFJCKC4.js.map +0 -1
  66. package/dist/chunk-GVW4P7BG.js.map +0 -1
  67. package/dist/chunk-JYRIOFEN.cjs.map +0 -1
  68. package/dist/chunk-TVRC4KVT.cjs.map +0 -1
  69. package/dist/chunk-U6NDQFOK.cjs.map +0 -1
  70. /package/dist/{chunk-KYRZDWM4.js.map → chunk-JJDLPEWL.js.map} +0 -0
@@ -53,7 +53,7 @@ function tokenizeFormula(formula) {
53
53
  continue;
54
54
  }
55
55
  if (char === "#") {
56
- const match = /^#(?:REF!|DIV\/0!|VALUE!|NAME\?|N\/A|NUM!|CYCLE!|ERROR!)/.exec(input.slice(i));
56
+ const match = /^#(?:REF!|DIV\/0!|VALUE!|NAME\?|N\/A|NUM!|CYCLE!|ERROR!|SPILL!|CALC!)/.exec(input.slice(i));
57
57
  if (!match) {
58
58
  throw new Error(`Unexpected character '#' at position ${i + offset}`);
59
59
  }
@@ -104,7 +104,7 @@ function tokenizeFormula(formula) {
104
104
  i += 2;
105
105
  continue;
106
106
  }
107
- if (["+", "-", "*", "/", "^", "&", "=", "<", ">"].includes(char)) {
107
+ if (["+", "-", "*", "/", "^", "&", "=", "<", ">", "@"].includes(char)) {
108
108
  tokens.push({ type: "OPERATOR", value: char, position: i + offset });
109
109
  i++;
110
110
  continue;
@@ -144,6 +144,11 @@ function tokenizeFormula(formula) {
144
144
  continue;
145
145
  }
146
146
  if (/^\$?[A-Za-z]+\$?[0-9]+$/.test(rawWord)) {
147
+ if (input[i] === "#") {
148
+ i++;
149
+ tokens.push({ type: "SPILL_REF", value: upper.replace(/\$/g, ""), position: start + offset });
150
+ continue;
151
+ }
147
152
  tokens.push({
148
153
  type: "CELL_REF",
149
154
  value: upper.replace(/\$/g, ""),
@@ -265,7 +270,7 @@ var FormulaParser = (_class = class {
265
270
  return expr;
266
271
  }
267
272
  unary() {
268
- if (this.matchOperator("-", "+")) {
273
+ if (this.matchOperator("-", "+", "@")) {
269
274
  const op = this.previous().value;
270
275
  const argument = this.unary();
271
276
  return { type: "UnaryOp", op, argument };
@@ -304,6 +309,11 @@ var FormulaParser = (_class = class {
304
309
  this.advance();
305
310
  return parseRange(token.value);
306
311
  }
312
+ if (token.type === "SPILL_REF") {
313
+ this.advance();
314
+ const parsed = parseCellReference(token.value);
315
+ return { type: "SpillRef", ref: token.value, col: parsed.col, row: parsed.row };
316
+ }
307
317
  if (token.type === "PROPERTY_REF") {
308
318
  this.advance();
309
319
  return {
@@ -329,7 +339,7 @@ var FormulaParser = (_class = class {
329
339
  args
330
340
  };
331
341
  }
332
- return { type: "Literal", value: "#NAME?" };
342
+ return { type: "Name", name };
333
343
  }
334
344
  if (this.match("LPAREN")) {
335
345
  const expr = this.expression();
@@ -391,7 +401,9 @@ var FORMULA_ERRORS = /* @__PURE__ */ new Set([
391
401
  "#N/A",
392
402
  "#NUM!",
393
403
  "#CYCLE!",
394
- "#ERROR!"
404
+ "#ERROR!",
405
+ "#SPILL!",
406
+ "#CALC!"
395
407
  ]);
396
408
  function isFormulaError(value) {
397
409
  return typeof value === "string" && FORMULA_ERRORS.has(value);
@@ -430,6 +442,11 @@ function normalizeValue(value) {
430
442
  return num === null ? value : num;
431
443
  }
432
444
  function compareValues(left, right) {
445
+ if (typeof left === "string" && typeof right === "string") {
446
+ const a = isoDateTime(left);
447
+ const b = a === null ? null : isoDateTime(right);
448
+ if (a !== null && b !== null) return a - b;
449
+ }
433
450
  const rank = (v) => typeof v === "boolean" ? 2 : typeof v === "string" ? 1 : 0;
434
451
  const l = isBlank(left) ? typeof right === "string" ? "" : 0 : left;
435
452
  const r = isBlank(right) ? typeof l === "string" ? "" : 0 : right;
@@ -505,6 +522,20 @@ function toDate(value) {
505
522
  }
506
523
  return null;
507
524
  }
525
+ var ISO_DATE = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/;
526
+ function parseIsoDate(value) {
527
+ if (typeof value !== "string" || value.length < 8 || value.length > 19) return null;
528
+ const iso = ISO_DATE.exec(value.trim());
529
+ if (!iso) return null;
530
+ const [, y, m, d, hh = "0", mm = "0", ss = "0"] = iso;
531
+ const date = new Date(Number(y), Number(m) - 1, Number(d), Number(hh), Number(mm), Number(ss));
532
+ return date.getMonth() === Number(m) - 1 && date.getDate() === Number(d) ? date : null;
533
+ }
534
+ var isoDateHasTime = (value) => /[ T]\d/.test(value.trim());
535
+ function isoDateTime(value) {
536
+ if (value.length < 8 || value.charCodeAt(4) !== 45) return null;
537
+ return _nullishCoalesce(_optionalChain([parseIsoDate, 'call', _2 => _2(value), 'optionalAccess', _3 => _3.getTime, 'call', _4 => _4()]), () => ( null));
538
+ }
508
539
  function dateToSerial(date) {
509
540
  const utc = Date.UTC(
510
541
  date.getFullYear(),
@@ -762,8 +793,8 @@ function formatDate(date, format) {
762
793
  return lower.length >= 2 ? pad2(date.getSeconds()) : String(date.getSeconds());
763
794
  }
764
795
  const position = codes.findIndex((c) => c.i === index);
765
- const prev = _nullishCoalesce(_optionalChain([codes, 'access', _2 => _2[position - 1], 'optionalAccess', _3 => _3.text, 'access', _4 => _4.toLowerCase, 'call', _5 => _5()]), () => ( ""));
766
- const next = _nullishCoalesce(_optionalChain([codes, 'access', _6 => _6[position + 1], 'optionalAccess', _7 => _7.text, 'access', _8 => _8.toLowerCase, 'call', _9 => _9()]), () => ( ""));
796
+ const prev = _nullishCoalesce(_optionalChain([codes, 'access', _5 => _5[position - 1], 'optionalAccess', _6 => _6.text, 'access', _7 => _7.toLowerCase, 'call', _8 => _8()]), () => ( ""));
797
+ const next = _nullishCoalesce(_optionalChain([codes, 'access', _9 => _9[position + 1], 'optionalAccess', _10 => _10.text, 'access', _11 => _11.toLowerCase, 'call', _12 => _12()]), () => ( ""));
767
798
  if (lower.length <= 2 && (prev.startsWith("h") || next.startsWith("s"))) {
768
799
  return lower.length === 2 ? pad2(date.getMinutes()) : String(date.getMinutes());
769
800
  }
@@ -1606,11 +1637,11 @@ var TEXT_FUNCTIONS = [
1606
1637
  {
1607
1638
  name: "TRIM",
1608
1639
  category: "text",
1609
- description: "Removes leading and trailing spaces.",
1640
+ description: "Removes leading and trailing spaces and collapses runs of inner spaces to one, like Excel.",
1610
1641
  args: [TEXT],
1611
1642
  evaluate: (ctx) => {
1612
1643
  const text = ctx.text(0);
1613
- return failed(text) ? text : text.trim();
1644
+ return failed(text) ? text : text.trim().replace(/ {2,}/g, " ");
1614
1645
  }
1615
1646
  },
1616
1647
  {
@@ -2241,6 +2272,335 @@ var INFORMATION_FUNCTIONS = [
2241
2272
  }
2242
2273
  ];
2243
2274
 
2275
+ // src/spreadsheet/formula/functions/array.ts
2276
+ var MAX_ARRAY_CELLS = 1e6;
2277
+ function rangeArg(ctx, index) {
2278
+ const value = ctx.value(index);
2279
+ if (Array.isArray(value)) return toFormulaRange(value);
2280
+ if (isFormulaError(value)) return value;
2281
+ return { rows: 1, cols: 1, values: [value] };
2282
+ }
2283
+ function fromRows(rows, cols) {
2284
+ const out = [];
2285
+ for (let c = 0; c < cols; c++) {
2286
+ for (const row of rows) out.push(row[c]);
2287
+ }
2288
+ return withRangeShape(out, rows.length, cols);
2289
+ }
2290
+ var rowOf = (range, r) => {
2291
+ const row = [];
2292
+ for (let c = 0; c < range.cols; c++) row.push(rangeGet(range, r, c));
2293
+ return row;
2294
+ };
2295
+ var colOf = (range, c) => {
2296
+ const col = [];
2297
+ for (let r = 0; r < range.rows; r++) col.push(rangeGet(range, r, c));
2298
+ return col;
2299
+ };
2300
+ var linesOf = (range, byCol) => byCol ? Array.from({ length: range.cols }, (_, c) => colOf(range, c)) : Array.from({ length: range.rows }, (_, r) => rowOf(range, r));
2301
+ function fromLines(lines, byCol, width) {
2302
+ if (!byCol) return fromRows(lines, width);
2303
+ const out = [];
2304
+ for (const line of lines) for (const v of line) out.push(v);
2305
+ return withRangeShape(out, width, lines.length);
2306
+ }
2307
+ function compareForSort(a, b, order) {
2308
+ const blankA = isBlank(a);
2309
+ const blankB = isBlank(b);
2310
+ if (blankA || blankB) return blankA === blankB ? 0 : blankA ? 1 : -1;
2311
+ return compareValues(normalizeValue(a), normalizeValue(b)) * order;
2312
+ }
2313
+ var sortOrder = (ctx, index) => {
2314
+ const order = ctx.integer(index, 1);
2315
+ if (failed(order)) return order;
2316
+ return order === 1 || order === -1 ? order : ctx.argError(index, "#VALUE!", "must be 1 (ascending) or -1 (descending).");
2317
+ };
2318
+ function includeFlag(value) {
2319
+ if (isFormulaError(value)) return value;
2320
+ const flag = toBoolean(value);
2321
+ return isFormulaError(flag) ? "#VALUE!" : flag;
2322
+ }
2323
+ var ARRAY_FUNCTIONS = [
2324
+ {
2325
+ name: "FILTER",
2326
+ category: "lookup",
2327
+ description: "Returns the rows (or columns) of an array whose include value is TRUE. The result spills.",
2328
+ args: [
2329
+ arg("array", "The range or array to filter.", "range"),
2330
+ arg("include", "TRUE/FALSE for each row (a column as tall as array) or each column (a row as wide as array)."),
2331
+ optional("if_empty", "Returned when nothing matches (default #CALC!).")
2332
+ ],
2333
+ evaluate: (ctx) => {
2334
+ const range = rangeArg(ctx, 0);
2335
+ if (failed(range)) return range;
2336
+ const include = rangeArg(ctx, 1);
2337
+ if (failed(include)) return include;
2338
+ let byCol;
2339
+ if (include.cols === 1 && include.rows === range.rows) byCol = false;
2340
+ else if (include.rows === 1 && include.cols === range.cols) byCol = true;
2341
+ else {
2342
+ return ctx.argError(1, "#VALUE!", "must be a single column as tall as argument 1 (or a single row as wide).");
2343
+ }
2344
+ const lines = linesOf(range, byCol);
2345
+ const kept = [];
2346
+ for (let i = 0; i < lines.length; i++) {
2347
+ const flag = includeFlag(include.values[i]);
2348
+ if (typeof flag === "string") {
2349
+ return flag === "#VALUE!" ? ctx.argError(1, "#VALUE!", `has a value that isn't TRUE or FALSE (${include.values[i]}).`) : flag;
2350
+ }
2351
+ if (flag) kept.push(lines[i]);
2352
+ }
2353
+ if (kept.length === 0) {
2354
+ return ctx.has(2) ? ctx.value(2) : ctx.error("#CALC!", "FILTER found no matching values.");
2355
+ }
2356
+ return fromLines(kept, byCol, byCol ? range.rows : range.cols);
2357
+ }
2358
+ },
2359
+ {
2360
+ name: "SORT",
2361
+ category: "lookup",
2362
+ description: "Sorts the rows (or columns) of an array by one of its columns (or rows). The result spills.",
2363
+ args: [
2364
+ arg("array", "The range or array to sort.", "range"),
2365
+ optional("sort_index", "The column (or row, with by_col) to sort by; 1 = first (default).", "number"),
2366
+ optional("sort_order", "1 = ascending (default), -1 = descending.", "number"),
2367
+ optional("by_col", "TRUE sorts columns by a row; FALSE (default) sorts rows by a column.", "logical")
2368
+ ],
2369
+ evaluate: (ctx) => {
2370
+ const range = rangeArg(ctx, 0);
2371
+ if (failed(range)) return range;
2372
+ const index = ctx.integer(1, 1);
2373
+ if (failed(index)) return index;
2374
+ const order = sortOrder(ctx, 2);
2375
+ if (failed(order)) return order;
2376
+ const byCol = ctx.boolean(3, false);
2377
+ if (failed(byCol)) return byCol;
2378
+ const width = byCol ? range.rows : range.cols;
2379
+ if (index < 1 || index > width) {
2380
+ return ctx.argError(1, "#VALUE!", `must be between 1 and ${width}.`);
2381
+ }
2382
+ const lines = linesOf(range, byCol).map((line, position) => ({ line, position })).sort((a, b) => compareForSort(a.line[index - 1], b.line[index - 1], order) || a.position - b.position).map((entry) => entry.line);
2383
+ return fromLines(lines, byCol, width);
2384
+ }
2385
+ },
2386
+ {
2387
+ name: "SORTBY",
2388
+ category: "lookup",
2389
+ description: "Sorts an array by the values in other ranges (one value per row or column). The result spills.",
2390
+ args: [
2391
+ arg("array", "The range or array to sort.", "range"),
2392
+ arg("by_array", "A column as tall as array (or a row as wide) to sort by.", "range"),
2393
+ optional("sort_order", "1 = ascending (default), -1 = descending.", "number"),
2394
+ arg("by_array", "Another range to sort by, for ties.", "range", { repeating: true, optional: true }),
2395
+ arg("sort_order", "Its order: 1 = ascending (default), -1 = descending.", "number", { repeating: true, optional: true })
2396
+ ],
2397
+ allowTrailingArgument: true,
2398
+ evaluate: (ctx) => {
2399
+ const range = rangeArg(ctx, 0);
2400
+ if (failed(range)) return range;
2401
+ const keys = [];
2402
+ let byCol = null;
2403
+ for (let i = 1; i < ctx.args.length; i += 2) {
2404
+ const by = rangeArg(ctx, i);
2405
+ if (failed(by)) return by;
2406
+ let keyByCol;
2407
+ if (by.cols === 1 && by.rows === range.rows) keyByCol = false;
2408
+ else if (by.rows === 1 && by.cols === range.cols) keyByCol = true;
2409
+ else {
2410
+ return ctx.argError(i, "#VALUE!", "must be a single column as tall as argument 1 (or a single row as wide).");
2411
+ }
2412
+ if (byCol !== null && byCol !== keyByCol) {
2413
+ return ctx.argError(i, "#VALUE!", "must have the same orientation as the other sort ranges.");
2414
+ }
2415
+ byCol = keyByCol;
2416
+ const order = sortOrder(ctx, i + 1);
2417
+ if (failed(order)) return order;
2418
+ keys.push({ values: by.values, order });
2419
+ }
2420
+ const sortByCol = _nullishCoalesce(byCol, () => ( false));
2421
+ const lines = linesOf(range, sortByCol).map((line, position) => ({ line, position })).sort((a, b) => {
2422
+ for (const key of keys) {
2423
+ const result = compareForSort(key.values[a.position], key.values[b.position], key.order);
2424
+ if (result !== 0) return result;
2425
+ }
2426
+ return a.position - b.position;
2427
+ }).map((entry) => entry.line);
2428
+ return fromLines(lines, sortByCol, sortByCol ? range.rows : range.cols);
2429
+ }
2430
+ },
2431
+ {
2432
+ name: "UNIQUE",
2433
+ category: "lookup",
2434
+ description: "Returns the distinct rows (or columns) of an array, in order of first appearance. The result spills.",
2435
+ args: [
2436
+ arg("array", "The range or array.", "range"),
2437
+ optional("by_col", "TRUE compares columns; FALSE (default) compares rows.", "logical"),
2438
+ optional("exactly_once", "TRUE returns only the rows that appear exactly once (default FALSE).", "logical")
2439
+ ],
2440
+ evaluate: (ctx) => {
2441
+ const range = rangeArg(ctx, 0);
2442
+ if (failed(range)) return range;
2443
+ const byCol = ctx.boolean(1, false);
2444
+ if (failed(byCol)) return byCol;
2445
+ const exactlyOnce = ctx.boolean(2, false);
2446
+ if (failed(exactlyOnce)) return exactlyOnce;
2447
+ const keyOf = (line) => JSON.stringify(
2448
+ line.map((v) => {
2449
+ const n = normalizeValue(v);
2450
+ return typeof n === "string" ? `s:${n.toLowerCase()}` : isBlank(n) ? "blank" : `${typeof n}:${String(n)}`;
2451
+ })
2452
+ );
2453
+ const counts = /* @__PURE__ */ new Map();
2454
+ for (const line of linesOf(range, byCol)) {
2455
+ const key = keyOf(line);
2456
+ const entry = counts.get(key);
2457
+ if (entry) entry.count++;
2458
+ else counts.set(key, { line, count: 1 });
2459
+ }
2460
+ const lines = Array.from(counts.values()).filter((entry) => !exactlyOnce || entry.count === 1).map((entry) => entry.line);
2461
+ if (lines.length === 0) return ctx.error("#CALC!", "UNIQUE found no values that appear exactly once.");
2462
+ return fromLines(lines, byCol, byCol ? range.rows : range.cols);
2463
+ }
2464
+ },
2465
+ {
2466
+ name: "SEQUENCE",
2467
+ category: "math",
2468
+ description: "Returns a grid of sequential numbers, filled row by row. The result spills.",
2469
+ args: [
2470
+ arg("rows", "The number of rows.", "number"),
2471
+ optional("columns", "The number of columns (default 1).", "number"),
2472
+ optional("start", "The first number (default 1).", "number"),
2473
+ optional("step", "The increment (default 1).", "number")
2474
+ ],
2475
+ evaluate: (ctx) => {
2476
+ const rows = ctx.integer(0);
2477
+ if (failed(rows)) return rows;
2478
+ const cols = ctx.integer(1, 1);
2479
+ if (failed(cols)) return cols;
2480
+ const start = ctx.number(2, 1);
2481
+ if (failed(start)) return start;
2482
+ const step = ctx.number(3, 1);
2483
+ if (failed(step)) return step;
2484
+ if (rows < 1 || cols < 1) return ctx.error("#CALC!", "SEQUENCE needs at least one row and one column.");
2485
+ if (rows * cols > MAX_ARRAY_CELLS) return ctx.error("#NUM!", `SEQUENCE can return at most ${MAX_ARRAY_CELLS} values.`);
2486
+ const out = [];
2487
+ for (let c = 0; c < cols; c++) {
2488
+ for (let r = 0; r < rows; r++) out.push(start + step * (r * cols + c));
2489
+ }
2490
+ return rows * cols === 1 ? out[0] : withRangeShape(out, rows, cols);
2491
+ }
2492
+ },
2493
+ {
2494
+ name: "TRANSPOSE",
2495
+ category: "lookup",
2496
+ description: "Swaps the rows and columns of an array. The result spills.",
2497
+ args: [arg("array", "The range or array to transpose.", "range")],
2498
+ evaluate: (ctx) => {
2499
+ const range = rangeArg(ctx, 0);
2500
+ if (failed(range)) return range;
2501
+ if (range.rows * range.cols === 1) return range.values[0];
2502
+ const out = [];
2503
+ for (let r = 0; r < range.rows; r++) {
2504
+ for (let c = 0; c < range.cols; c++) out.push(rangeGet(range, r, c));
2505
+ }
2506
+ return withRangeShape(out, range.cols, range.rows);
2507
+ }
2508
+ },
2509
+ {
2510
+ name: "RANDARRAY",
2511
+ category: "math",
2512
+ description: "Returns a grid of random numbers. Changes on every recalculation. The result spills.",
2513
+ args: [
2514
+ optional("rows", "The number of rows (default 1).", "number"),
2515
+ optional("columns", "The number of columns (default 1).", "number"),
2516
+ optional("min", "The smallest number (default 0).", "number"),
2517
+ optional("max", "The largest number (default 1).", "number"),
2518
+ optional("integer", "TRUE returns whole numbers; FALSE (default) decimals.", "logical")
2519
+ ],
2520
+ volatile: true,
2521
+ evaluate: (ctx) => {
2522
+ const rows = ctx.integer(0, 1);
2523
+ if (failed(rows)) return rows;
2524
+ const cols = ctx.integer(1, 1);
2525
+ if (failed(cols)) return cols;
2526
+ const min2 = ctx.number(2, 0);
2527
+ if (failed(min2)) return min2;
2528
+ const max2 = ctx.number(3, 1);
2529
+ if (failed(max2)) return max2;
2530
+ const integer = ctx.boolean(4, false);
2531
+ if (failed(integer)) return integer;
2532
+ if (rows < 1 || cols < 1) return ctx.error("#CALC!", "RANDARRAY needs at least one row and one column.");
2533
+ if (rows * cols > MAX_ARRAY_CELLS) return ctx.error("#NUM!", `RANDARRAY can return at most ${MAX_ARRAY_CELLS} values.`);
2534
+ if (min2 > max2) return ctx.argError(2, "#VALUE!", "can't be larger than max.");
2535
+ if (integer && (!Number.isInteger(min2) || !Number.isInteger(max2))) {
2536
+ return ctx.error("#VALUE!", "RANDARRAY needs whole min and max values when integer is TRUE.");
2537
+ }
2538
+ const next = () => integer ? min2 + Math.floor(Math.random() * (max2 - min2 + 1)) : min2 + Math.random() * (max2 - min2);
2539
+ const out = [];
2540
+ for (let i = 0; i < rows * cols; i++) out.push(next());
2541
+ return rows * cols === 1 ? out[0] : withRangeShape(out, rows, cols);
2542
+ }
2543
+ },
2544
+ {
2545
+ // Evaluated by the evaluator itself (it defines names); listed here for
2546
+ // autocomplete, signatures and argument-count checks.
2547
+ name: "LET",
2548
+ category: "logical",
2549
+ description: "Names the results of calculations so a formula can reuse them: LET(x, A1*2, x+x). The last argument is the calculation that uses the names.",
2550
+ args: [
2551
+ repeating("name", "A name to define (letters, not a cell reference)."),
2552
+ repeating("name_value", "The value of the name.")
2553
+ ],
2554
+ allowTrailingArgument: true,
2555
+ evaluate: (ctx) => ctx.error("#ERROR!", "LET must be evaluated by the formula evaluator.")
2556
+ }
2557
+ ];
2558
+ var ELEMENTWISE_BUILTINS = {
2559
+ ABS: true,
2560
+ ROUND: true,
2561
+ ROUNDUP: true,
2562
+ ROUNDDOWN: true,
2563
+ INT: true,
2564
+ MOD: true,
2565
+ POWER: true,
2566
+ SQRT: true,
2567
+ SIGN: true,
2568
+ CEILING: true,
2569
+ FLOOR: true,
2570
+ LEN: true,
2571
+ UPPER: true,
2572
+ LOWER: true,
2573
+ TRIM: true,
2574
+ PROPER: true,
2575
+ LEFT: true,
2576
+ RIGHT: true,
2577
+ MID: true,
2578
+ FIND: true,
2579
+ SEARCH: true,
2580
+ SUBSTITUTE: true,
2581
+ REPLACE: true,
2582
+ REPT: true,
2583
+ EXACT: true,
2584
+ VALUE: true,
2585
+ TEXT: true,
2586
+ YEAR: true,
2587
+ MONTH: true,
2588
+ DAY: true,
2589
+ WEEKDAY: true,
2590
+ EDATE: true,
2591
+ EOMONTH: true,
2592
+ DATE: true,
2593
+ NOT: true,
2594
+ ISBLANK: true,
2595
+ ISNUMBER: true,
2596
+ ISTEXT: true,
2597
+ ISERROR: true,
2598
+ ISNA: true,
2599
+ IF: [0],
2600
+ IFERROR: [0],
2601
+ IFNA: [0]
2602
+ };
2603
+
2244
2604
  // src/spreadsheet/formula/functions/index.ts
2245
2605
  var BUILTIN_FORMULA_FUNCTIONS = [
2246
2606
  ...MATH_FUNCTIONS,
@@ -2250,8 +2610,12 @@ var BUILTIN_FORMULA_FUNCTIONS = [
2250
2610
  ...TEXT_FUNCTIONS,
2251
2611
  ...LOOKUP_FUNCTIONS,
2252
2612
  ...DATE_FUNCTIONS,
2253
- ...INFORMATION_FUNCTIONS
2254
- ];
2613
+ ...INFORMATION_FUNCTIONS,
2614
+ ...ARRAY_FUNCTIONS
2615
+ ].map((def) => {
2616
+ const elementwise = ELEMENTWISE_BUILTINS[def.name];
2617
+ return elementwise && def.elementwise === void 0 ? { ...def, elementwise } : def;
2618
+ });
2255
2619
 
2256
2620
  // src/spreadsheet/formula/registry.ts
2257
2621
  var registry = /* @__PURE__ */ new Map();
@@ -2373,16 +2737,43 @@ var DEFAULT_MESSAGES = {
2373
2737
  "#N/A": "A value is not available.",
2374
2738
  "#NUM!": "The formula produced an invalid number.",
2375
2739
  "#CYCLE!": "This formula is part of a circular reference.",
2376
- "#ERROR!": "The formula could not be evaluated."
2740
+ "#ERROR!": "The formula could not be evaluated.",
2741
+ "#SPILL!": "The formula's array result can't spill into the cells it needs.",
2742
+ "#CALC!": "The formula produced an empty array."
2377
2743
  };
2378
2744
  function describeFormulaError(code) {
2379
2745
  return _nullishCoalesce(DEFAULT_MESSAGES[code], () => ( "The formula could not be evaluated."));
2380
2746
  }
2381
2747
  var quote = (value) => typeof value === "string" ? `"${value}"` : String(value);
2748
+ var MAX_FORMULA_ARRAY_SIZE = 1e6;
2749
+ function shapeOf(value) {
2750
+ if (!Array.isArray(value)) return { rows: 1, cols: 1 };
2751
+ const range = toFormulaRange(value);
2752
+ return { rows: range.rows, cols: range.cols };
2753
+ }
2754
+ function broadcastGet(value, row, col) {
2755
+ if (!Array.isArray(value)) return value;
2756
+ const range = toFormulaRange(value);
2757
+ const r = range.rows === 1 ? 0 : row;
2758
+ const c = range.cols === 1 ? 0 : col;
2759
+ if (r >= range.rows || c >= range.cols) return "#N/A";
2760
+ return range.values[c * range.rows + r];
2761
+ }
2762
+ function buildArray(rows, cols, fn) {
2763
+ const out = [];
2764
+ for (let c = 0; c < cols; c++) {
2765
+ for (let r = 0; r < rows; r++) out.push(fn(r, c));
2766
+ }
2767
+ return withRangeShape(out, rows, cols);
2768
+ }
2769
+ var isMultiArray = (value) => Array.isArray(value) && value.length > 1;
2770
+ var firstValue = (value) => Array.isArray(value) ? value.length ? value[0] : "" : value;
2382
2771
  var FormulaEvaluator = (_class2 = class {
2383
2772
 
2384
2773
  __init3() {this.diagnostics = []}
2385
- constructor(context = {}) {;_class2.prototype.__init3.call(this);
2774
+ // LET variables, innermost scope last.
2775
+ __init4() {this.scopes = []}
2776
+ constructor(context = {}) {;_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);
2386
2777
  this.context = context;
2387
2778
  }
2388
2779
  /**
@@ -2395,25 +2786,39 @@ var FormulaEvaluator = (_class2 = class {
2395
2786
  }
2396
2787
  /**
2397
2788
  * Evaluates a formula and explains errors. Never throws: malformed formulas
2398
- * return `#ERROR!` with the parser's message. A result that is a whole range
2399
- * (no dynamic-array spilling) is reduced to its first value.
2789
+ * return `#ERROR!` with the parser's message. A result with several values
2790
+ * (a range or a dynamic array) is returned in `array`, with its top-left
2791
+ * value as `value`; a one-value array is just that value.
2400
2792
  */
2401
2793
  evaluateWithDiagnostics(astOrFormula) {
2402
2794
  const saved = this.diagnostics;
2795
+ const savedScopes = this.scopes;
2403
2796
  this.diagnostics = [];
2797
+ this.scopes = [];
2404
2798
  try {
2405
2799
  let value = this.evaluate(astOrFormula);
2406
- if (Array.isArray(value)) value = value.length > 0 ? value[0] : "";
2800
+ let array;
2801
+ if (Array.isArray(value)) {
2802
+ if (value.length > 1) array = toFormulaRange(value);
2803
+ value = firstValue(value);
2804
+ }
2407
2805
  if (isFormulaError(value)) {
2408
2806
  const code = value;
2409
2807
  const diagnostic = this.diagnostics.find((d) => d.code === code);
2410
- return { value, error: code, message: _nullishCoalesce(_optionalChain([diagnostic, 'optionalAccess', _10 => _10.message]), () => ( describeFormulaError(code))) };
2808
+ const result = {
2809
+ value,
2810
+ error: code,
2811
+ message: _nullishCoalesce(_optionalChain([diagnostic, 'optionalAccess', _13 => _13.message]), () => ( describeFormulaError(code)))
2812
+ };
2813
+ if (array) result.array = array;
2814
+ return result;
2411
2815
  }
2412
- return { value };
2816
+ return array ? { value, array } : { value };
2413
2817
  } catch (err) {
2414
- return { value: "#ERROR!", error: "#ERROR!", message: _optionalChain([err, 'optionalAccess', _11 => _11.message]) || "Formula error" };
2818
+ return { value: "#ERROR!", error: "#ERROR!", message: _optionalChain([err, 'optionalAccess', _14 => _14.message]) || "Formula error" };
2415
2819
  } finally {
2416
2820
  this.diagnostics = saved;
2821
+ this.scopes = savedScopes;
2417
2822
  }
2418
2823
  }
2419
2824
  /** Records why an error happened and returns the error code. */
@@ -2442,6 +2847,14 @@ var FormulaEvaluator = (_class2 = class {
2442
2847
  }
2443
2848
  case "Range":
2444
2849
  return this.evaluateRange(node);
2850
+ case "SpillRef":
2851
+ return this.evaluateSpillRef(node);
2852
+ case "Name": {
2853
+ for (let i = this.scopes.length - 1; i >= 0; i--) {
2854
+ if (this.scopes[i].has(node.name)) return this.scopes[i].get(node.name);
2855
+ }
2856
+ return this.fail("#NAME?", "This formula contains an unknown name.");
2857
+ }
2445
2858
  case "PropertyRef": {
2446
2859
  if (!this.context.resolveProperty) {
2447
2860
  throw new Error(`Cannot resolve property [${node.property}]: no property resolver provided`);
@@ -2449,65 +2862,38 @@ var FormulaEvaluator = (_class2 = class {
2449
2862
  return this.context.resolveProperty(node.property);
2450
2863
  }
2451
2864
  case "UnaryOp": {
2865
+ if (node.op === "@") return this.implicitIntersection(node.argument);
2452
2866
  const raw = this.evaluateNode(node.argument);
2453
- const val = toNumber(raw);
2454
- if (isFormulaError(val)) {
2455
- return isFormulaError(raw) ? raw : this.fail("#VALUE!", `The ${node.op} operator can't use text (${quote(raw)}).`);
2867
+ if (isMultiArray(raw)) {
2868
+ const { rows, cols } = shapeOf(raw);
2869
+ return buildArray(rows, cols, (r, c) => this.unary(node.op, broadcastGet(raw, r, c)));
2456
2870
  }
2457
- if (node.op === "-") return -val;
2458
- if (node.op === "+") return val;
2459
- throw new Error(`Unsupported unary operator: ${node.op}`);
2871
+ return this.unary(node.op, firstValue(raw));
2460
2872
  }
2461
2873
  case "BinaryOp": {
2462
2874
  const left = this.evaluateNode(node.left);
2463
2875
  if (isFormulaError(left)) return left;
2464
2876
  const right = this.evaluateNode(node.right);
2465
2877
  if (isFormulaError(right)) return right;
2466
- if (["+", "-", "*", "/", "^"].includes(node.op)) {
2467
- const a = toNumber(left);
2468
- if (isFormulaError(a)) return this.fail("#VALUE!", `The ${node.op} operator can't use text (${quote(left)}).`);
2469
- const b = toNumber(right);
2470
- if (isFormulaError(b)) return this.fail("#VALUE!", `The ${node.op} operator can't use text (${quote(right)}).`);
2471
- const x = a;
2472
- const y = b;
2473
- let result;
2474
- switch (node.op) {
2475
- case "+":
2476
- result = x + y;
2477
- break;
2478
- case "-":
2479
- result = x - y;
2480
- break;
2481
- case "*":
2482
- result = x * y;
2483
- break;
2484
- case "/":
2485
- if (y === 0) return this.fail("#DIV/0!", "The formula divides by zero.");
2486
- result = x / y;
2487
- break;
2488
- default:
2489
- result = Math.pow(x, y);
2490
- }
2491
- return Number.isFinite(result) ? result : this.fail("#NUM!", "The result is too large or not a number.");
2492
- }
2493
- switch (node.op) {
2494
- case "&":
2495
- return toText(left) + toText(right);
2496
- case "=":
2497
- return compareValues(left, right) === 0;
2498
- case "<>":
2499
- return compareValues(left, right) !== 0;
2500
- case "<":
2501
- return compareValues(left, right) < 0;
2502
- case "<=":
2503
- return compareValues(left, right) <= 0;
2504
- case ">":
2505
- return compareValues(left, right) > 0;
2506
- case ">=":
2507
- return compareValues(left, right) >= 0;
2508
- default:
2509
- throw new Error(`Unsupported binary operator: ${node.op}`);
2878
+ if (isMultiArray(left) || isMultiArray(right)) {
2879
+ const a = shapeOf(left);
2880
+ const b = shapeOf(right);
2881
+ const rows = Math.max(a.rows, b.rows);
2882
+ const cols = Math.max(a.cols, b.cols);
2883
+ if (rows * cols > MAX_FORMULA_ARRAY_SIZE) return this.fail("#NUM!", "The array result is too large.");
2884
+ return buildArray(rows, cols, (r2, c) => {
2885
+ const l2 = broadcastGet(left, r2, c);
2886
+ if (isFormulaError(l2)) return l2;
2887
+ const rv = broadcastGet(right, r2, c);
2888
+ if (isFormulaError(rv)) return rv;
2889
+ return this.binary(node.op, l2, rv);
2890
+ });
2510
2891
  }
2892
+ const l = firstValue(left);
2893
+ if (isFormulaError(l)) return l;
2894
+ const r = firstValue(right);
2895
+ if (isFormulaError(r)) return r;
2896
+ return this.binary(node.op, l, r);
2511
2897
  }
2512
2898
  case "FunctionCall":
2513
2899
  return this.callFunction(node);
@@ -2515,6 +2901,134 @@ var FormulaEvaluator = (_class2 = class {
2515
2901
  throw new Error(`Unknown AST node: ${node.type}`);
2516
2902
  }
2517
2903
  }
2904
+ unary(op, raw) {
2905
+ const val = toNumber(raw);
2906
+ if (isFormulaError(val)) {
2907
+ return isFormulaError(raw) ? raw : this.fail("#VALUE!", `The ${op} operator can't use text (${quote(raw)}).`);
2908
+ }
2909
+ if (op === "-") return -val;
2910
+ if (op === "+") return val;
2911
+ throw new Error(`Unsupported unary operator: ${op}`);
2912
+ }
2913
+ /** A binary operator on two single values. */
2914
+ binary(op, left, right) {
2915
+ if (["+", "-", "*", "/", "^"].includes(op)) {
2916
+ if (op === "+" || op === "-") {
2917
+ const leftDate = parseIsoDate(left);
2918
+ const rightDate = parseIsoDate(right);
2919
+ if (leftDate || rightDate) return this.dateArithmetic(op, left, right, leftDate, rightDate);
2920
+ }
2921
+ const a = toNumber(left);
2922
+ if (isFormulaError(a)) return this.fail("#VALUE!", `The ${op} operator can't use text (${quote(left)}).`);
2923
+ const b = toNumber(right);
2924
+ if (isFormulaError(b)) return this.fail("#VALUE!", `The ${op} operator can't use text (${quote(right)}).`);
2925
+ const x = a;
2926
+ const y = b;
2927
+ let result;
2928
+ switch (op) {
2929
+ case "+":
2930
+ result = x + y;
2931
+ break;
2932
+ case "-":
2933
+ result = x - y;
2934
+ break;
2935
+ case "*":
2936
+ result = x * y;
2937
+ break;
2938
+ case "/":
2939
+ if (y === 0) return this.fail("#DIV/0!", "The formula divides by zero.");
2940
+ result = x / y;
2941
+ break;
2942
+ default:
2943
+ result = Math.pow(x, y);
2944
+ }
2945
+ return Number.isFinite(result) ? result : this.fail("#NUM!", "The result is too large or not a number.");
2946
+ }
2947
+ switch (op) {
2948
+ case "&":
2949
+ return toText(left) + toText(right);
2950
+ case "=":
2951
+ return compareValues(left, right) === 0;
2952
+ case "<>":
2953
+ return compareValues(left, right) !== 0;
2954
+ case "<":
2955
+ return compareValues(left, right) < 0;
2956
+ case "<=":
2957
+ return compareValues(left, right) <= 0;
2958
+ case ">":
2959
+ return compareValues(left, right) > 0;
2960
+ case ">=":
2961
+ return compareValues(left, right) >= 0;
2962
+ default:
2963
+ throw new Error(`Unsupported binary operator: ${op}`);
2964
+ }
2965
+ }
2966
+ /**
2967
+ * Dates are ISO text: date +/- days is a date, date - date is a number of
2968
+ * days (fractional when times are involved).
2969
+ */
2970
+ dateArithmetic(op, left, right, leftDate, rightDate) {
2971
+ if (leftDate && rightDate) {
2972
+ if (op === "+") return this.fail("#VALUE!", "Two dates can't be added; subtract them to count the days between.");
2973
+ return cleanFloat(dateToSerial(leftDate) - dateToSerial(rightDate));
2974
+ }
2975
+ if (!leftDate && op === "-") {
2976
+ return this.fail("#VALUE!", `A date (${quote(right)}) can't be subtracted from a number.`);
2977
+ }
2978
+ const date = _nullishCoalesce(leftDate, () => ( rightDate));
2979
+ const dateText = String(leftDate ? left : right);
2980
+ const other = leftDate ? right : left;
2981
+ const days = toNumber(other);
2982
+ if (isFormulaError(days)) return this.fail("#VALUE!", `The ${op} operator can't use text (${quote(other)}).`);
2983
+ const n = days;
2984
+ const result = serialToDate(dateToSerial(date) + (op === "+" ? n : -n));
2985
+ if (!result || result.getFullYear() < 1 || result.getFullYear() > 9999) {
2986
+ return this.fail("#NUM!", "The resulting date is out of range.");
2987
+ }
2988
+ return isoDateHasTime(dateText) || !Number.isInteger(n) ? formatIsoDateTime(result) : formatIsoDate(result);
2989
+ }
2990
+ /**
2991
+ * `@` (implicit intersection): a range reference reads the cell in the
2992
+ * formula's row (one column) or column (one row); arrays give their
2993
+ * top-left value.
2994
+ */
2995
+ implicitIntersection(argument) {
2996
+ const value = this.evaluateNode(argument);
2997
+ if (!Array.isArray(value)) return value;
2998
+ if (value.length <= 1) return firstValue(value);
2999
+ const cell = _optionalChain([this, 'access', _15 => _15.context, 'access', _16 => _16.getFormulaCell, 'optionalCall', _17 => _17()]);
3000
+ if (argument.type === "Range" && cell) {
3001
+ const range = toFormulaRange(value);
3002
+ const top = Math.min(argument.startRow, argument.endRow);
3003
+ const left = Math.min(colNameToIndex(argument.startCol), colNameToIndex(argument.endCol));
3004
+ if (range.cols === 1) {
3005
+ const r = cell.row - top;
3006
+ if (r >= 0 && r < range.rows) return range.values[r];
3007
+ } else if (range.rows === 1) {
3008
+ const c = colNameToIndex(cell.col) - left;
3009
+ if (c >= 0 && c < range.cols) return range.values[c];
3010
+ }
3011
+ return this.fail("#VALUE!", `The @ operator found no cell of ${argument.raw} in the formula's row or column.`);
3012
+ }
3013
+ return value[0];
3014
+ }
3015
+ evaluateSpillRef(node) {
3016
+ if (!this.context.resolveSpill) {
3017
+ throw new Error(`Cannot resolve spill range ${node.ref}#: no spill resolver provided`);
3018
+ }
3019
+ const spill = this.context.resolveSpill(node.col, node.row);
3020
+ if (typeof spill === "string" || !spill) {
3021
+ const code = isFormulaError(spill) ? spill : "#REF!";
3022
+ return this.fail(
3023
+ code,
3024
+ code === "#REF!" ? `${node.ref}# refers to ${node.ref}, which doesn't hold a spilled array.` : `The spill range ${node.ref}# isn't available (${code}).`
3025
+ );
3026
+ }
3027
+ const values = withRangeShape(spill.values.slice(), spill.rows, spill.cols);
3028
+ const error = values.find(isFormulaError);
3029
+ if (error) this.fail(error, `The spill range ${node.ref}# contains an error (${error}).`);
3030
+ return values;
3031
+ }
2518
3032
  evaluateRange(node) {
2519
3033
  if (!this.context.resolveRange) {
2520
3034
  throw new Error(`Cannot resolve range ${node.raw}: no range resolver provided`);
@@ -2532,15 +3046,23 @@ var FormulaEvaluator = (_class2 = class {
2532
3046
  if (!def) return this.fail("#NAME?", `Unknown function "${node.name.toUpperCase()}".`);
2533
3047
  const countError = checkFormulaArgumentCount(def, node.args.length);
2534
3048
  if (countError) return this.fail("#ERROR!", countError);
2535
- const result = def.evaluate(this.createContext(def, node.args));
3049
+ if (def.name === "LET") return this.evaluateLet(node);
3050
+ const args = this.lazyArguments(node.args);
3051
+ if (def.elementwise) {
3052
+ const lifted = this.liftOverArrays(def, args);
3053
+ if (lifted !== void 0) return lifted;
3054
+ }
3055
+ return this.finishCall(def, def.evaluate(this.createContext(def, args)));
3056
+ }
3057
+ finishCall(def, result) {
2536
3058
  if (typeof result === "number" && !Number.isFinite(result)) {
2537
3059
  return this.fail("#NUM!", `${def.name} produced an invalid number.`);
2538
3060
  }
2539
3061
  return result === void 0 || result === null ? "" : result;
2540
3062
  }
2541
- createContext(def, nodes) {
3063
+ lazyArguments(nodes) {
2542
3064
  const cache = /* @__PURE__ */ new Map();
2543
- const args = nodes.map((node, index) => ({
3065
+ return nodes.map((node, index) => ({
2544
3066
  index,
2545
3067
  node,
2546
3068
  value: () => {
@@ -2548,6 +3070,56 @@ var FormulaEvaluator = (_class2 = class {
2548
3070
  return cache.get(index);
2549
3071
  }
2550
3072
  }));
3073
+ }
3074
+ /**
3075
+ * Applies a scalar function element by element when it receives arrays
3076
+ * (`=LEN(A1:A3)`, `=IF(A1:A3>1, "y", "n")`). Arguments typed `range` are
3077
+ * passed whole. Returns undefined when no trigger argument is a
3078
+ * multi-value array.
3079
+ */
3080
+ liftOverArrays(def, args) {
3081
+ const liftable = (index) => _optionalChain([def, 'access', _18 => _18.args, 'access', _19 => _19[getFormulaArgumentSpecIndex(def, index)], 'optionalAccess', _20 => _20.type]) !== "range";
3082
+ const triggers = def.elementwise === true ? args.map((a) => a.index) : def.elementwise;
3083
+ if (!triggers.some((i) => i < args.length && liftable(i) && isMultiArray(args[i].value()))) return void 0;
3084
+ let rows = 1;
3085
+ let cols = 1;
3086
+ for (const a of args) {
3087
+ if (!liftable(a.index)) continue;
3088
+ const shape = shapeOf(a.value());
3089
+ rows = Math.max(rows, shape.rows);
3090
+ cols = Math.max(cols, shape.cols);
3091
+ }
3092
+ if (rows * cols > MAX_FORMULA_ARRAY_SIZE) return this.fail("#NUM!", "The array result is too large.");
3093
+ return buildArray(rows, cols, (r, c) => {
3094
+ const elementArgs = args.map((a) => {
3095
+ if (!liftable(a.index)) return a;
3096
+ const element = broadcastGet(a.value(), r, c);
3097
+ return { index: a.index, node: a.node, value: () => element };
3098
+ });
3099
+ return firstValue(this.finishCall(def, def.evaluate(this.createContext(def, elementArgs))));
3100
+ });
3101
+ }
3102
+ /** LET(name1, value1, [name2, value2, ...], calculation). */
3103
+ evaluateLet(node) {
3104
+ if (node.args.length % 2 === 0) {
3105
+ return this.fail("#ERROR!", "LET needs name/value pairs followed by a calculation.");
3106
+ }
3107
+ const scope = /* @__PURE__ */ new Map();
3108
+ this.scopes.push(scope);
3109
+ try {
3110
+ for (let i = 0; i < node.args.length - 1; i += 2) {
3111
+ const nameNode = node.args[i];
3112
+ if (nameNode.type !== "Name") {
3113
+ return this.fail("#ERROR!", `Argument ${i + 1} of LET must be a name (names can't look like cell references).`);
3114
+ }
3115
+ scope.set(nameNode.name, this.evaluateNode(node.args[i + 1]));
3116
+ }
3117
+ return this.evaluateNode(node.args[node.args.length - 1]);
3118
+ } finally {
3119
+ this.scopes.pop();
3120
+ }
3121
+ }
3122
+ createContext(def, args) {
2551
3123
  const has = (index) => index >= 0 && index < args.length;
2552
3124
  const value = (index) => has(index) ? args[index].value() : "";
2553
3125
  const argError = (index, code, reason) => this.fail(code, `Argument ${index + 1} of ${def.name} ${reason}`);
@@ -2653,5 +3225,6 @@ var FormulaEvaluator = (_class2 = class {
2653
3225
 
2654
3226
 
2655
3227
 
2656
- exports.tokenizeFormula = tokenizeFormula; exports.WHOLE_COLUMN_END_ROW = WHOLE_COLUMN_END_ROW; exports.parseCellReference = parseCellReference; exports.parseRange = parseRange; exports.FormulaParser = FormulaParser; exports.parseFormula = parseFormula; exports.FORMULA_ERRORS = FORMULA_ERRORS; exports.isFormulaError = isFormulaError; exports.colNameToIndex = colNameToIndex; exports.indexToColName = indexToColName; exports.wildcardToRegExp = wildcardToRegExp; exports.parseCriteria = parseCriteria; exports.formatValueWithCode = formatValueWithCode; exports.registerFormulaFunction = registerFormulaFunction; exports.unregisterFormulaFunction = unregisterFormulaFunction; exports.getFormulaFunction = getFormulaFunction; exports.listFormulaFunctions = listFormulaFunctions; exports.searchFormulaFunctions = searchFormulaFunctions; exports.getFormulaArgumentSpecIndex = getFormulaArgumentSpecIndex; exports.checkFormulaArgumentCount = checkFormulaArgumentCount; exports.getFormulaSignatureParts = getFormulaSignatureParts; exports.getFormulaSignature = getFormulaSignature; exports.describeFormulaError = describeFormulaError; exports.FormulaEvaluator = FormulaEvaluator;
2657
- //# sourceMappingURL=chunk-JYRIOFEN.cjs.map
3228
+
3229
+ exports.tokenizeFormula = tokenizeFormula; exports.WHOLE_COLUMN_END_ROW = WHOLE_COLUMN_END_ROW; exports.parseCellReference = parseCellReference; exports.parseRange = parseRange; exports.FormulaParser = FormulaParser; exports.parseFormula = parseFormula; exports.FORMULA_ERRORS = FORMULA_ERRORS; exports.isFormulaError = isFormulaError; exports.colNameToIndex = colNameToIndex; exports.indexToColName = indexToColName; exports.wildcardToRegExp = wildcardToRegExp; exports.parseCriteria = parseCriteria; exports.formatValueWithCode = formatValueWithCode; exports.registerFormulaFunction = registerFormulaFunction; exports.unregisterFormulaFunction = unregisterFormulaFunction; exports.getFormulaFunction = getFormulaFunction; exports.listFormulaFunctions = listFormulaFunctions; exports.searchFormulaFunctions = searchFormulaFunctions; exports.getFormulaArgumentSpecIndex = getFormulaArgumentSpecIndex; exports.checkFormulaArgumentCount = checkFormulaArgumentCount; exports.getFormulaSignatureParts = getFormulaSignatureParts; exports.getFormulaSignature = getFormulaSignature; exports.describeFormulaError = describeFormulaError; exports.MAX_FORMULA_ARRAY_SIZE = MAX_FORMULA_ARRAY_SIZE; exports.FormulaEvaluator = FormulaEvaluator;
3230
+ //# sourceMappingURL=chunk-SAZRENEZ.cjs.map