@devmm/puredocs-excel-formula 1.0.8 → 1.1.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/dist/index.cjs CHANGED
@@ -31,6 +31,13 @@ var FormulaError = /* @__PURE__ */ ((FormulaError2) => {
31
31
  FormulaError2[FormulaError2["Spill"] = 9] = "Spill";
32
32
  return FormulaError2;
33
33
  })(FormulaError || {});
34
+ var DECIMAL = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
35
+ function numericText(text) {
36
+ const trimmed = text.trim();
37
+ if (!DECIMAL.test(trimmed)) return null;
38
+ const n = Number(trimmed);
39
+ return Number.isFinite(n) ? n : null;
40
+ }
34
41
  var TailCall = class {
35
42
  constructor(lambda, args) {
36
43
  this.lambda = lambda;
@@ -807,6 +814,16 @@ var ArrayLiteralNode = class extends FormulaNode {
807
814
  }
808
815
  };
809
816
  _value = new WeakMap();
817
+ var ValueNode = class extends FormulaNode {
818
+ /** Mutable so one wrapper can be re-pointed across a lifted call's cells. */
819
+ constructor(value2) {
820
+ super();
821
+ this.value = value2;
822
+ }
823
+ evaluate() {
824
+ return this.value;
825
+ }
826
+ };
810
827
  var BlankNode = class extends FormulaNode {
811
828
  evaluate() {
812
829
  return FormulaValue.blank;
@@ -1433,6 +1450,7 @@ var indexes = /* @__PURE__ */ new WeakMap();
1433
1450
  function build(arr) {
1434
1451
  const byText = /* @__PURE__ */ new Map();
1435
1452
  const byNumber = /* @__PURE__ */ new Map();
1453
+ const byTextNumber = /* @__PURE__ */ new Map();
1436
1454
  const classes = /* @__PURE__ */ new Map();
1437
1455
  const addToClass = (kind, v, i2) => {
1438
1456
  const group = classes.get(kind);
@@ -1452,6 +1470,14 @@ function build(arr) {
1452
1470
  else byNumber.set(n, [i]);
1453
1471
  } else {
1454
1472
  addToClass(v.kind, v, i);
1473
+ if (v.isText) {
1474
+ const n = numericText(v.textValue);
1475
+ if (n !== null) {
1476
+ const atText2 = byTextNumber.get(n);
1477
+ if (atText2) atText2.push(i);
1478
+ else byTextNumber.set(n, [i]);
1479
+ }
1480
+ }
1455
1481
  }
1456
1482
  i++;
1457
1483
  }
@@ -1460,7 +1486,15 @@ function build(arr) {
1460
1486
  for (let k = 0; k < sortedNumbers.length; k++) {
1461
1487
  cumulative[k + 1] = cumulative[k] + byNumber.get(sortedNumbers[k]).length;
1462
1488
  }
1463
- return { byText, byNumber, sortedNumbers, cumulative, classes: [...classes.values()] };
1489
+ return {
1490
+ byText,
1491
+ byNumber,
1492
+ sortedNumbers,
1493
+ cumulative,
1494
+ byTextNumber,
1495
+ sortedTextNumbers: Float64Array.from(byTextNumber.keys()).sort(),
1496
+ classes: [...classes.values()]
1497
+ };
1464
1498
  }
1465
1499
  function indexOf(arr) {
1466
1500
  let idx = indexes.get(arr);
@@ -1498,19 +1532,25 @@ function matchingPositions(criteriaRange, criteria, minSize) {
1498
1532
  if (criteria.kind === "equalsNumber") {
1499
1533
  const target = criteria.number;
1500
1534
  const idx = indexOf(arr);
1501
- const exact2 = idx.byNumber.get(target);
1502
- let near;
1503
- const { sortedNumbers } = idx;
1504
- for (let k = lowerBound(sortedNumbers, target - EPSILON); k < sortedNumbers.length; k++) {
1505
- const value2 = sortedNumbers[k];
1506
- if (value2 > target + EPSILON) break;
1507
- if (value2 === target) continue;
1508
- if (!criteria.test(FormulaValue.number(value2))) continue;
1509
- (near ?? (near = [])).push(...idx.byNumber.get(value2));
1510
- }
1535
+ const groups = [];
1536
+ const collect = (byValue, sorted) => {
1537
+ const exact2 = byValue.get(target);
1538
+ if (exact2) groups.push(exact2);
1539
+ for (let k = lowerBound(sorted, target - EPSILON); k < sorted.length; k++) {
1540
+ const value2 = sorted[k];
1541
+ if (value2 > target + EPSILON) break;
1542
+ if (value2 === target) continue;
1543
+ if (!criteria.test(FormulaValue.number(value2))) continue;
1544
+ groups.push(byValue.get(value2));
1545
+ }
1546
+ };
1547
+ collect(idx.byNumber, idx.sortedNumbers);
1548
+ collect(idx.byTextNumber, idx.sortedTextNumbers);
1511
1549
  const blanks = criteria.test(FormulaValue.blank) ? blankPositions(idx) : void 0;
1512
- if (!near && !blanks?.length) return exact2 ?? EMPTY;
1513
- const merged = [...exact2 ?? EMPTY, ...near ?? EMPTY, ...blanks ?? EMPTY];
1550
+ if (blanks?.length) groups.push(blanks);
1551
+ if (groups.length === 0) return EMPTY;
1552
+ if (groups.length === 1) return groups[0];
1553
+ const merged = groups.flat();
1514
1554
  merged.sort((a, b) => a - b);
1515
1555
  return merged;
1516
1556
  }
@@ -1576,42 +1616,42 @@ function registerMath(r) {
1576
1616
  r.register("PRODUCT", product, 1);
1577
1617
  r.register("SUMPRODUCT", sumProduct, 1);
1578
1618
  r.register("SUMSQ", sumSq, 1);
1579
- r.register("ABS", abs, 1, 1);
1580
- r.register("ROUND", round, 2, 2);
1581
- r.register("ROUNDUP", roundUp, 2, 2);
1582
- r.register("ROUNDDOWN", roundDown, 2, 2);
1583
- r.register("CEILING", ceiling, 2, 2);
1584
- r.register("CEILING.MATH", ceiling, 2, 2);
1585
- r.register("FLOOR", floor, 2, 2);
1586
- r.register("FLOOR.MATH", floor, 2, 2);
1587
- r.register("INT", int_, 1, 1);
1588
- r.register("TRUNC", trunc, 1, 2);
1589
- r.register("MOD", mod, 2, 2);
1590
- r.register("QUOTIENT", quotient, 2, 2);
1591
- r.register("POWER", power, 2, 2);
1592
- r.register("SQRT", sqrt, 1, 1);
1593
- r.register("SIGN", sign, 1, 1);
1619
+ r.registerElementwise("ABS", abs, 1, 1);
1620
+ r.registerElementwise("ROUND", round, 2, 2);
1621
+ r.registerElementwise("ROUNDUP", roundUp, 2, 2);
1622
+ r.registerElementwise("ROUNDDOWN", roundDown, 2, 2);
1623
+ r.registerElementwise("CEILING", ceiling, 2, 2);
1624
+ r.registerElementwise("CEILING.MATH", ceiling, 2, 2);
1625
+ r.registerElementwise("FLOOR", floor, 2, 2);
1626
+ r.registerElementwise("FLOOR.MATH", floor, 2, 2);
1627
+ r.registerElementwise("INT", int_, 1, 1);
1628
+ r.registerElementwise("TRUNC", trunc, 1, 2);
1629
+ r.registerElementwise("MOD", mod, 2, 2);
1630
+ r.registerElementwise("QUOTIENT", quotient, 2, 2);
1631
+ r.registerElementwise("POWER", power, 2, 2);
1632
+ r.registerElementwise("SQRT", sqrt, 1, 1);
1633
+ r.registerElementwise("SIGN", sign, 1, 1);
1594
1634
  r.register("PI", pi, 0, 0);
1595
1635
  r.register("RAND", rand, 0, 0, true);
1596
1636
  r.register("RANDBETWEEN", randBetween, 2, 2, true);
1597
- r.register("LOG", log, 1, 2);
1598
- r.register("LOG10", log10, 1, 1);
1599
- r.register("LN", ln, 1, 1);
1600
- r.register("EXP", exp, 1, 1);
1601
- r.register("FACT", fact, 1, 1);
1602
- r.register("COMBIN", combin, 2, 2);
1603
- r.register("SIN", unary(Math.sin), 1, 1);
1604
- r.register("COS", unary(Math.cos), 1, 1);
1605
- r.register("TAN", unary(Math.tan), 1, 1);
1606
- r.register("ASIN", asin, 1, 1);
1607
- r.register("ACOS", acos, 1, 1);
1608
- r.register("ATAN", unary(Math.atan), 1, 1);
1609
- r.register("ATAN2", atan2, 2, 2);
1610
- r.register("SINH", unary(Math.sinh), 1, 1);
1611
- r.register("COSH", unary(Math.cosh), 1, 1);
1612
- r.register("TANH", unary(Math.tanh), 1, 1);
1613
- r.register("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
1614
- r.register("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
1637
+ r.registerElementwise("LOG", log, 1, 2);
1638
+ r.registerElementwise("LOG10", log10, 1, 1);
1639
+ r.registerElementwise("LN", ln, 1, 1);
1640
+ r.registerElementwise("EXP", exp, 1, 1);
1641
+ r.registerElementwise("FACT", fact, 1, 1);
1642
+ r.registerElementwise("COMBIN", combin, 2, 2);
1643
+ r.registerElementwise("SIN", unary(Math.sin), 1, 1);
1644
+ r.registerElementwise("COS", unary(Math.cos), 1, 1);
1645
+ r.registerElementwise("TAN", unary(Math.tan), 1, 1);
1646
+ r.registerElementwise("ASIN", asin, 1, 1);
1647
+ r.registerElementwise("ACOS", acos, 1, 1);
1648
+ r.registerElementwise("ATAN", unary(Math.atan), 1, 1);
1649
+ r.registerElementwise("ATAN2", atan2, 2, 2);
1650
+ r.registerElementwise("SINH", unary(Math.sinh), 1, 1);
1651
+ r.registerElementwise("COSH", unary(Math.cosh), 1, 1);
1652
+ r.registerElementwise("TANH", unary(Math.tanh), 1, 1);
1653
+ r.registerElementwise("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
1654
+ r.registerElementwise("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
1615
1655
  }
1616
1656
  function valueAt(v, i) {
1617
1657
  return v.isArray ? v.arrayVal.getFlat(i) : v;
@@ -2043,31 +2083,31 @@ function combin(a, c) {
2043
2083
  return FormulaValue.number(Math.round(result));
2044
2084
  }
2045
2085
  function registerText(r) {
2046
- r.register("LEFT", left, 1, 2);
2047
- r.register("RIGHT", right, 1, 2);
2048
- r.register("MID", mid, 3, 3);
2049
- r.register("LEN", len, 1, 1);
2050
- r.register("UPPER", upper, 1, 1);
2051
- r.register("LOWER", lower, 1, 1);
2052
- r.register("PROPER", proper, 1, 1);
2053
- r.register("TRIM", trim, 1, 1);
2054
- r.register("CLEAN", clean, 1, 1);
2055
- r.register("SUBSTITUTE", substitute, 3, 4);
2056
- r.register("REPLACE", replace, 4, 4);
2086
+ r.registerElementwise("LEFT", left, 1, 2);
2087
+ r.registerElementwise("RIGHT", right, 1, 2);
2088
+ r.registerElementwise("MID", mid, 3, 3);
2089
+ r.registerElementwise("LEN", len, 1, 1);
2090
+ r.registerElementwise("UPPER", upper, 1, 1);
2091
+ r.registerElementwise("LOWER", lower, 1, 1);
2092
+ r.registerElementwise("PROPER", proper, 1, 1);
2093
+ r.registerElementwise("TRIM", trim, 1, 1);
2094
+ r.registerElementwise("CLEAN", clean, 1, 1);
2095
+ r.registerElementwise("SUBSTITUTE", substitute, 3, 4);
2096
+ r.registerElementwise("REPLACE", replace, 4, 4);
2057
2097
  r.register("CONCATENATE", concatenate, 1);
2058
2098
  r.register("CONCAT", concatenate, 1);
2059
2099
  r.register("TEXTJOIN", textJoin, 3);
2060
- r.register("TEXT", text_, 2, 2);
2061
- r.register("VALUE", value, 1, 1);
2062
- r.register("FIND", find, 2, 3);
2063
- r.register("SEARCH", search, 2, 3);
2064
- r.register("REPT", rept, 2, 2);
2065
- r.register("EXACT", exact, 2, 2);
2066
- r.register("CHAR", char_, 1, 1);
2067
- r.register("CODE", code, 1, 1);
2068
- r.register("T", t_, 1, 1);
2069
- r.register("TEXTBEFORE", textBefore, 2, 3);
2070
- r.register("TEXTAFTER", textAfter, 2, 3);
2100
+ r.registerElementwise("TEXT", text_, 2, 2);
2101
+ r.registerElementwise("VALUE", value, 1, 1);
2102
+ r.registerElementwise("FIND", find, 2, 3);
2103
+ r.registerElementwise("SEARCH", search, 2, 3);
2104
+ r.registerElementwise("REPT", rept, 2, 2);
2105
+ r.registerElementwise("EXACT", exact, 2, 2);
2106
+ r.registerElementwise("CHAR", char_, 1, 1);
2107
+ r.registerElementwise("CODE", code, 1, 1);
2108
+ r.registerElementwise("T", t_, 1, 1);
2109
+ r.registerElementwise("TEXTBEFORE", textBefore, 2, 3);
2110
+ r.registerElementwise("TEXTAFTER", textAfter, 2, 3);
2071
2111
  }
2072
2112
  function str(a, c, idx) {
2073
2113
  const r = exports.FormulaHelper.evalString(a[idx], c);
@@ -2381,6 +2421,7 @@ function registerLogical(r) {
2381
2421
  function if_(a, c) {
2382
2422
  const cond = a[0].evaluate(c);
2383
2423
  if (cond.isError) return cond;
2424
+ if (cond.isArray) return ifArray(cond.arrayVal, a, c);
2384
2425
  const b = cond.coerceToBool();
2385
2426
  if (b.isError) return b;
2386
2427
  return b.booleanValue ? a[1].evaluate(c) : a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_;
@@ -2457,13 +2498,63 @@ function xor(a, c) {
2457
2498
  }
2458
2499
  return FormulaValue.boolean(trueCount % 2 === 1);
2459
2500
  }
2460
- function ifError(a, c) {
2501
+ function ifArray(mask, a, c) {
2502
+ let wantTrue = false, wantFalse = false;
2503
+ for (const item of mask.values()) {
2504
+ const b = item.coerceToBool();
2505
+ if (b.isError) return b;
2506
+ if (b.booleanValue) wantTrue = true;
2507
+ else wantFalse = true;
2508
+ if (wantTrue && wantFalse) break;
2509
+ }
2510
+ const yes = wantTrue ? a[1].evaluate(c) : FormulaValue.blank;
2511
+ const no = wantFalse ? a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_ : FormulaValue.blank;
2512
+ const pick = (v, r, col) => {
2513
+ if (!v.isArray) return v;
2514
+ const arr = v.arrayVal;
2515
+ return r < arr.rows && col < arr.columns ? arr.get(r, col) : FormulaValue.errorNA;
2516
+ };
2517
+ const out = new ArrayValue(mask.rows, mask.columns);
2518
+ for (let r = 0; r < mask.rows; r++) {
2519
+ for (let col = 0; col < mask.columns; col++) {
2520
+ const b = mask.get(r, col).coerceToBool();
2521
+ out.set(r, col, b.booleanValue ? pick(yes, r, col) : pick(no, r, col));
2522
+ }
2523
+ }
2524
+ return FormulaValue.array(out);
2525
+ }
2526
+ function ifCaught(a, c, caught) {
2461
2527
  const v = a[0].evaluate(c);
2462
- return v.isError ? a[1].evaluate(c) : v;
2528
+ if (!v.isArray) return caught(v) ? a[1].evaluate(c) : v;
2529
+ const arr = v.arrayVal;
2530
+ let any = false;
2531
+ for (const item of arr.values()) {
2532
+ if (caught(item)) {
2533
+ any = true;
2534
+ break;
2535
+ }
2536
+ }
2537
+ if (!any) return v;
2538
+ const fb = a[1].evaluate(c);
2539
+ const fbArr = fb.isArray ? fb.arrayVal : null;
2540
+ const out = new ArrayValue(arr.rows, arr.columns);
2541
+ for (let r = 0; r < arr.rows; r++) {
2542
+ for (let col = 0; col < arr.columns; col++) {
2543
+ const item = arr.get(r, col);
2544
+ if (!caught(item)) {
2545
+ out.set(r, col, item);
2546
+ continue;
2547
+ }
2548
+ out.set(r, col, fbArr ? r < fbArr.rows && col < fbArr.columns ? fbArr.get(r, col) : FormulaValue.errorNA : fb);
2549
+ }
2550
+ }
2551
+ return FormulaValue.array(out);
2552
+ }
2553
+ function ifError(a, c) {
2554
+ return ifCaught(a, c, (v) => v.isError);
2463
2555
  }
2464
2556
  function ifNa(a, c) {
2465
- const v = a[0].evaluate(c);
2466
- return v.isError && v.errorCode === 7 /* NA */ ? a[1].evaluate(c) : v;
2557
+ return ifCaught(a, c, (v) => v.isError && v.errorCode === 7 /* NA */);
2467
2558
  }
2468
2559
  function ifs(a, c) {
2469
2560
  for (let i = 0; i + 1 < a.length; i += 2) {
@@ -2520,21 +2611,21 @@ function fromOA(oa) {
2520
2611
  function registerDate(r) {
2521
2612
  r.register("NOW", now, 0, 0, true);
2522
2613
  r.register("TODAY", today, 0, 0, true);
2523
- r.register("DATE", date_, 3, 3);
2524
- r.register("YEAR", year, 1, 1);
2525
- r.register("MONTH", month, 1, 1);
2526
- r.register("DAY", day, 1, 1);
2527
- r.register("HOUR", hour, 1, 1);
2528
- r.register("MINUTE", minute, 1, 1);
2529
- r.register("SECOND", second, 1, 1);
2530
- r.register("DATEVALUE", dateValue, 1, 1);
2531
- r.register("DAYS", days, 2, 2);
2532
- r.register("EDATE", edate, 2, 2);
2533
- r.register("EOMONTH", eomonth, 2, 2);
2534
- r.register("WEEKDAY", weekday, 1, 2);
2535
- r.register("WEEKNUM", weeknum, 1, 2);
2614
+ r.registerElementwise("DATE", date_, 3, 3);
2615
+ r.registerElementwise("YEAR", year, 1, 1);
2616
+ r.registerElementwise("MONTH", month, 1, 1);
2617
+ r.registerElementwise("DAY", day, 1, 1);
2618
+ r.registerElementwise("HOUR", hour, 1, 1);
2619
+ r.registerElementwise("MINUTE", minute, 1, 1);
2620
+ r.registerElementwise("SECOND", second, 1, 1);
2621
+ r.registerElementwise("DATEVALUE", dateValue, 1, 1);
2622
+ r.registerElementwise("DAYS", days, 2, 2);
2623
+ r.registerElementwise("EDATE", edate, 2, 2);
2624
+ r.registerElementwise("EOMONTH", eomonth, 2, 2);
2625
+ r.registerElementwise("WEEKDAY", weekday, 1, 2);
2626
+ r.registerElementwise("WEEKNUM", weeknum, 1, 2);
2536
2627
  r.register("NETWORKDAYS", networkdays, 2, 3);
2537
- r.register("DATEDIF", datedif, 3, 3);
2628
+ r.registerElementwise("DATEDIF", datedif, 3, 3);
2538
2629
  }
2539
2630
  function evalDate(a, c, idx) {
2540
2631
  const r = exports.FormulaHelper.evalDouble(a[idx], c);
@@ -3347,20 +3438,20 @@ function ifsAggregate(a, c, mode2) {
3347
3438
 
3348
3439
  // src/functions/info-functions.ts
3349
3440
  function registerInfo(r) {
3350
- r.register("ISBLANK", isBlank, 1, 1);
3351
- r.register("ISNUMBER", isNumber, 1, 1);
3352
- r.register("ISTEXT", isText, 1, 1);
3353
- r.register("ISERROR", isError, 1, 1);
3354
- r.register("ISERR", isErr, 1, 1);
3355
- r.register("ISLOGICAL", isLogical, 1, 1);
3356
- r.register("ISNA", isNa, 1, 1);
3357
- r.register("ISNONTEXT", isNonText, 1, 1);
3441
+ r.registerElementwise("ISBLANK", isBlank, 1, 1);
3442
+ r.registerElementwise("ISNUMBER", isNumber, 1, 1);
3443
+ r.registerElementwise("ISTEXT", isText, 1, 1);
3444
+ r.registerElementwise("ISERROR", isError, 1, 1);
3445
+ r.registerElementwise("ISERR", isErr, 1, 1);
3446
+ r.registerElementwise("ISLOGICAL", isLogical, 1, 1);
3447
+ r.registerElementwise("ISNA", isNa, 1, 1);
3448
+ r.registerElementwise("ISNONTEXT", isNonText, 1, 1);
3358
3449
  r.register("NA", na, 0, 0);
3359
- r.register("TYPE", type_, 1, 1);
3360
- r.register("N", n_, 1, 1);
3361
- r.register("ISODD", isOdd, 1, 1);
3362
- r.register("ISEVEN", isEven, 1, 1);
3363
- r.register("ERROR.TYPE", errorType, 1, 1);
3450
+ r.registerElementwise("TYPE", type_, 1, 1);
3451
+ r.registerElementwise("N", n_, 1, 1);
3452
+ r.registerElementwise("ISODD", isOdd, 1, 1);
3453
+ r.registerElementwise("ISEVEN", isEven, 1, 1);
3454
+ r.registerElementwise("ERROR.TYPE", errorType, 1, 1);
3364
3455
  }
3365
3456
  function isBlank(a, c) {
3366
3457
  return FormulaValue.boolean(a[0].evaluate(c).isBlank);
@@ -4448,7 +4539,8 @@ function toRow(a, c) {
4448
4539
  }
4449
4540
 
4450
4541
  // src/function-registry.ts
4451
- var _default, _functions, _FunctionRegistry_instances, registerBuiltIns_fn;
4542
+ var MAX_LIFTED_CELLS = 1048576;
4543
+ var _default, _functions, _FunctionRegistry_static, executeElementwise_fn, _FunctionRegistry_instances, registerBuiltIns_fn;
4452
4544
  var _FunctionRegistry = class _FunctionRegistry {
4453
4545
  constructor() {
4454
4546
  __privateAdd(this, _FunctionRegistry_instances);
@@ -4470,17 +4562,45 @@ var _FunctionRegistry = class _FunctionRegistry {
4470
4562
  minArgs,
4471
4563
  maxArgs,
4472
4564
  isVolatile,
4473
- handler
4565
+ handler,
4566
+ elementwise: false
4567
+ });
4568
+ }
4569
+ /**
4570
+ * Registers a **scalar** function — one whose arguments are single values —
4571
+ * and lifts it over arrays automatically.
4572
+ *
4573
+ * `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
4574
+ * the grid maps it over the range, giving a 99-row array. Without that lifting
4575
+ * the range collapses to one value, so the dynamic array built on top of it —
4576
+ * `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
4577
+ * `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
4578
+ * text/math/date functions then get it for free and cannot each get it subtly
4579
+ * wrong.
4580
+ *
4581
+ * Only mark functions that are genuinely per-cell. Anything that *consumes* an
4582
+ * array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
4583
+ * `register`, or lifting would map it over the array it is meant to reduce.
4584
+ */
4585
+ registerElementwise(name, handler, minArgs = 0, maxArgs = -1) {
4586
+ __privateGet(this, _functions).set(name.toUpperCase(), {
4587
+ name: name.toUpperCase(),
4588
+ minArgs,
4589
+ maxArgs,
4590
+ isVolatile: false,
4591
+ handler,
4592
+ elementwise: true
4474
4593
  });
4475
4594
  }
4476
4595
  /** Executes a function by name with argument validation. */
4477
4596
  execute(name, args, ctx) {
4597
+ var _a;
4478
4598
  const def = __privateGet(this, _functions).get(name.toUpperCase());
4479
4599
  if (!def) return FormulaValue.errorName;
4480
4600
  if (args.length < def.minArgs) return FormulaValue.errorValue;
4481
4601
  if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
4482
4602
  try {
4483
- return def.handler(args, ctx);
4603
+ return def.elementwise ? __privateMethod(_a = _FunctionRegistry, _FunctionRegistry_static, executeElementwise_fn).call(_a, def, args, ctx) : def.handler(args, ctx);
4484
4604
  } catch {
4485
4605
  return FormulaValue.errorValue;
4486
4606
  }
@@ -4496,6 +4616,42 @@ var _FunctionRegistry = class _FunctionRegistry {
4496
4616
  };
4497
4617
  _default = new WeakMap();
4498
4618
  _functions = new WeakMap();
4619
+ _FunctionRegistry_static = new WeakSet();
4620
+ executeElementwise_fn = function(def, args, ctx) {
4621
+ const values = args.map((a) => a.evaluate(ctx));
4622
+ let rows2 = 1, cols = 1;
4623
+ for (const v of values) {
4624
+ if (!v.isArray) continue;
4625
+ const a = v.arrayVal;
4626
+ if (a.rows > rows2) rows2 = a.rows;
4627
+ if (a.columns > cols) cols = a.columns;
4628
+ }
4629
+ if (rows2 === 1 && cols === 1) {
4630
+ return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
4631
+ }
4632
+ if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
4633
+ for (const v of values) {
4634
+ if (!v.isArray) continue;
4635
+ const a = v.arrayVal;
4636
+ if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
4637
+ return FormulaValue.errorValue;
4638
+ }
4639
+ }
4640
+ const nodes = values.map((v) => new ValueNode(v));
4641
+ const out = new ArrayValue(rows2, cols);
4642
+ for (let r = 0; r < rows2; r++) {
4643
+ for (let c = 0; c < cols; c++) {
4644
+ for (let i = 0; i < values.length; i++) {
4645
+ const v = values[i];
4646
+ if (!v.isArray) continue;
4647
+ const a = v.arrayVal;
4648
+ nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
4649
+ }
4650
+ out.set(r, c, def.handler(nodes, ctx));
4651
+ }
4652
+ }
4653
+ return FormulaValue.array(out);
4654
+ };
4499
4655
  _FunctionRegistry_instances = new WeakSet();
4500
4656
  registerBuiltIns_fn = function() {
4501
4657
  registerMath(this);
@@ -4510,6 +4666,7 @@ registerBuiltIns_fn = function() {
4510
4666
  registerAggregate(this);
4511
4667
  registerArray(this);
4512
4668
  };
4669
+ __privateAdd(_FunctionRegistry, _FunctionRegistry_static);
4513
4670
  __privateAdd(_FunctionRegistry, _default);
4514
4671
  var FunctionRegistry = _FunctionRegistry;
4515
4672
  exports.FormulaHelper = void 0;
@@ -4575,7 +4732,8 @@ exports.FormulaHelper = void 0;
4575
4732
  return rhsNum !== null ? numberEquality(rhsNum) : textEquality(rhs);
4576
4733
  }
4577
4734
  if (op === "<>") {
4578
- return { kind: "other", test: (v) => !FormulaValue.areEqual(v, rhsValue) };
4735
+ const equals = rhsNum !== null ? numberEquality(rhsNum) : textEquality(rhs);
4736
+ return { kind: "other", test: (v) => !equals.test(v) };
4579
4737
  }
4580
4738
  const wanted = op === ">" ? (c) => c > 0 : op === ">=" ? (c) => c >= 0 : op === "<" ? (c) => c < 0 : (c) => c <= 0;
4581
4739
  const test = (v) => wanted(FormulaValue.compare(v, rhsValue));
@@ -4591,7 +4749,16 @@ exports.FormulaHelper = void 0;
4591
4749
  FormulaHelper2.compileCriteria = compileCriteria;
4592
4750
  function numberEquality(n) {
4593
4751
  const target = FormulaValue.number(n);
4594
- return { kind: "equalsNumber", number: n, test: (v) => FormulaValue.areEqual(v, target) };
4752
+ return {
4753
+ kind: "equalsNumber",
4754
+ number: n,
4755
+ test: (v) => {
4756
+ if (FormulaValue.areEqual(v, target)) return true;
4757
+ if (!v.isText) return false;
4758
+ const asNumber = numericText(v.textValue);
4759
+ return asNumber !== null && Math.abs(asNumber - n) < 1e-10;
4760
+ }
4761
+ };
4595
4762
  }
4596
4763
  function textEquality(s) {
4597
4764
  const upper2 = s.toUpperCase();
@@ -5371,6 +5538,7 @@ function createWorkbookRecalcModel(workbook) {
5371
5538
  getDefinedName: (name, sheet) => workbook.getDefinedName(name, sheet)
5372
5539
  };
5373
5540
  }
5541
+ var MAX_ADOPTED_SPILL_CELLS = 262144;
5374
5542
  var normSheet = (s) => s.toUpperCase();
5375
5543
  var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
5376
5544
  var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
@@ -5381,7 +5549,7 @@ function parseKey(key) {
5381
5549
  const { row: row2, column: column2 } = puredocsExcel.parseCellRef(ref);
5382
5550
  return { sheet, ref, row: row2, col: column2 };
5383
5551
  }
5384
- var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _dependents, _volatiles, _sheetLikes, _workbookLike, _rangeCache, _RecalcEngine_instances, writeCell_fn, register_fn, unregister_fn, rememberSheet_fn, recalcInOrder_fn, applyShift_fn, shiftPrecedents_fn, shiftSpillBookkeeping_fn, recalcFrom_fn, runFixpoint_fn, addVolatiles_fn, dependentClosure_fn, forEachDirectDependent_fn, evaluatePass_fn, writeResult_fn, isOccupied_fn, clearSpill_fn, evaluateFormulaValue_fn, sheetLike_fn, buildSheetLike_fn, buildWorkbookLike_fn;
5552
+ var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _dependents, _volatiles, _sheetLikes, _workbookLike, _rangeCache, _RecalcEngine_instances, writeCell_fn, register_fn, unregister_fn, rememberSheet_fn, adoptDeclaredSpill_fn, recalcInOrder_fn, applyShift_fn, shiftPrecedents_fn, shiftSpillBookkeeping_fn, recalcFrom_fn, runFixpoint_fn, addVolatiles_fn, dependentClosure_fn, forEachDirectDependent_fn, evaluatePass_fn, writeResult_fn, isOccupied_fn, clearSpill_fn, evaluateFormulaValue_fn, sheetLike_fn, buildSheetLike_fn, buildWorkbookLike_fn;
5385
5553
  var RecalcEngine = class {
5386
5554
  constructor(model, registry = FunctionRegistry.default) {
5387
5555
  __privateAdd(this, _RecalcEngine_instances);
@@ -5489,11 +5657,12 @@ var RecalcEngine = class {
5489
5657
  const ast = parseFormulaUncached(text);
5490
5658
  const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
5491
5659
  __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5660
+ const key = keyOf(s, r);
5492
5661
  __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
5493
5662
  sheet: s,
5494
5663
  sheetName: sheet,
5495
5664
  ref: r,
5496
- key: keyOf(s, r),
5665
+ key,
5497
5666
  row: row2,
5498
5667
  col: column2,
5499
5668
  formula: text,
@@ -5501,6 +5670,7 @@ var RecalcEngine = class {
5501
5670
  refs,
5502
5671
  volatile
5503
5672
  });
5673
+ __privateMethod(this, _RecalcEngine_instances, adoptDeclaredSpill_fn).call(this, s, sheet, r, key, row2, column2);
5504
5674
  registered++;
5505
5675
  } catch (err) {
5506
5676
  failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
@@ -5668,6 +5838,51 @@ unregister_fn = function(key) {
5668
5838
  rememberSheet_fn = function(name) {
5669
5839
  __privateGet(this, _sheetNames).set(normSheet(name), name);
5670
5840
  };
5841
+ /**
5842
+ * Takes ownership of the spill block a *file* already declared for an anchor.
5843
+ *
5844
+ * Excel stores a dynamic array as one formula on the anchor plus the spilled
5845
+ * results written as plain cached values in the cells below/right, and records
5846
+ * the extent in the anchor's `<f t="array" ref="…">`. Without this step the
5847
+ * engine meets those cached values on the first recalculation, sees literal
5848
+ * content inside the block it wants to spill into, and reports `#SPILL!` for
5849
+ * every dynamic array in every real file. Registering the declared block as
5850
+ * this anchor's existing spill makes them what they actually are — the anchor's
5851
+ * own territory, free to overwrite — and reuses the ordinary shrink path, so a
5852
+ * result smaller than the declared block clears the leftovers.
5853
+ *
5854
+ * Cost is one Set entry per already-spilled cell, paid once at load. A declared
5855
+ * block far larger than any real spill (a corrupt or hand-written `ref`) is
5856
+ * skipped rather than materialised.
5857
+ */
5858
+ adoptDeclaredSpill_fn = function(s, sheetName, ref, anchorKey, row2, col) {
5859
+ const declared = __privateGet(this, _model).getSpillRange?.(sheetName, ref);
5860
+ if (!declared || !declared.includes(":")) return;
5861
+ let rect;
5862
+ try {
5863
+ rect = puredocsExcel.parseRangeRef(declared);
5864
+ } catch {
5865
+ return;
5866
+ }
5867
+ if (rect.startRow !== row2 || rect.startColumn !== col) return;
5868
+ const height = rect.endRow - rect.startRow + 1;
5869
+ const width = rect.endColumn - rect.startColumn + 1;
5870
+ if (height < 1 || width < 1) return;
5871
+ if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
5872
+ if (height === 1 && width === 1) return;
5873
+ const members = /* @__PURE__ */ new Set();
5874
+ for (let r = rect.startRow; r <= rect.endRow; r++) {
5875
+ for (let c = rect.startColumn; c <= rect.endColumn; c++) {
5876
+ if (r === row2 && c === col) continue;
5877
+ const key = keyOf(s, puredocsExcel.cellRefFromRowCol(r, c));
5878
+ if (__privateGet(this, _formulas).has(key)) continue;
5879
+ if (__privateGet(this, _spillOwner).has(key)) continue;
5880
+ members.add(key);
5881
+ __privateGet(this, _spillOwner).set(key, anchorKey);
5882
+ }
5883
+ }
5884
+ if (members.size > 0) __privateGet(this, _spills).set(anchorKey, members);
5885
+ };
5671
5886
  /**
5672
5887
  * One pass in a caller-supplied order, watching for inversions.
5673
5888
  *