@odoo/o-spreadsheet 18.0.28 → 18.0.30

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.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.0.28
6
- * @date 2025-05-13T17:53:12.402Z
7
- * @hash b3088aa
5
+ * @version 18.0.30
6
+ * @date 2025-05-26T12:35:05.184Z
7
+ * @hash 838c4f7
8
8
  */
9
9
 
10
10
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -1452,18 +1452,53 @@ function lettersToNumber(letters) {
1452
1452
  let result = 0;
1453
1453
  const l = letters.length;
1454
1454
  for (let i = 0; i < l; i++) {
1455
- const charCode = letters.charCodeAt(i);
1456
- const colIndex = charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1455
+ const colIndex = charToNumber(letters[i]);
1457
1456
  result = result * 26 + colIndex;
1458
1457
  }
1459
1458
  return result - 1;
1460
1459
  }
1460
+ function charToNumber(char) {
1461
+ const charCode = char.charCodeAt(0);
1462
+ return charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1463
+ }
1461
1464
  function isCharALetter(char) {
1462
1465
  return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
1463
1466
  }
1464
1467
  function isCharADigit(char) {
1465
1468
  return char >= "0" && char <= "9";
1466
1469
  }
1470
+ // we limit the max column to 3 letters and max row to 7 digits for performance reasons
1471
+ const MAX_COL = lettersToNumber("ZZZ");
1472
+ const MAX_ROW = 9999998;
1473
+ function consumeSpaces(chars) {
1474
+ while (chars.current === " ") {
1475
+ chars.advanceBy(1);
1476
+ }
1477
+ }
1478
+ function consumeLetters(chars) {
1479
+ if (chars.current === "$")
1480
+ chars.advanceBy(1);
1481
+ if (!chars.current || !isCharALetter(chars.current)) {
1482
+ return -1;
1483
+ }
1484
+ let colCoordinate = 0;
1485
+ while (chars.current && isCharALetter(chars.current)) {
1486
+ colCoordinate = colCoordinate * 26 + charToNumber(chars.shift());
1487
+ }
1488
+ return colCoordinate;
1489
+ }
1490
+ function consumeDigits(chars) {
1491
+ if (chars.current === "$")
1492
+ chars.advanceBy(1);
1493
+ if (!chars.current || !isCharADigit(chars.current)) {
1494
+ return -1;
1495
+ }
1496
+ let num = 0;
1497
+ while (chars.current && isCharADigit(chars.current)) {
1498
+ num = num * 10 + Number(chars.shift());
1499
+ }
1500
+ return num;
1501
+ }
1467
1502
  /**
1468
1503
  * Convert a "XC" coordinate to cartesian coordinates.
1469
1504
  *
@@ -1474,33 +1509,17 @@ function isCharADigit(char) {
1474
1509
  * Note: it also accepts lowercase coordinates, but not fixed references
1475
1510
  */
1476
1511
  function toCartesian(xc) {
1477
- xc = xc.trim();
1478
- let letterPart = "";
1479
- let numberPart = "";
1480
- let i = 0;
1481
- // Process letter part
1482
- if (xc[i] === "$")
1483
- i++;
1484
- while (i < xc.length && isCharALetter(xc[i])) {
1485
- letterPart += xc[i++];
1486
- }
1487
- if (letterPart.length === 0 || letterPart.length > 3) {
1488
- // limit to max 3 letters for performance reasons
1512
+ const chars = new TokenizingChars(xc);
1513
+ consumeSpaces(chars);
1514
+ const letterPart = consumeLetters(chars);
1515
+ if (letterPart === -1 || !chars.current) {
1489
1516
  throw new Error(`Invalid cell description: ${xc}`);
1490
1517
  }
1491
- // Process number part
1492
- if (xc[i] === "$")
1493
- i++;
1494
- while (i < xc.length && isCharADigit(xc[i])) {
1495
- numberPart += xc[i++];
1496
- }
1497
- if (i !== xc.length || numberPart.length === 0 || numberPart.length > 7) {
1498
- // limit to max 7 numbers for performance reasons
1499
- throw new Error(`Invalid cell description: ${xc}`);
1500
- }
1501
- const col = lettersToNumber(letterPart);
1502
- const row = Number(numberPart) - 1;
1503
- if (isNaN(row)) {
1518
+ const num = consumeDigits(chars);
1519
+ consumeSpaces(chars);
1520
+ const col = letterPart - 1;
1521
+ const row = num - 1;
1522
+ if (!chars.isOver() || col > MAX_COL || row > MAX_ROW) {
1504
1523
  throw new Error(`Invalid cell description: ${xc}`);
1505
1524
  }
1506
1525
  return { col, row };
@@ -1912,67 +1931,6 @@ class LazyTranslatedString extends String {
1912
1931
  }
1913
1932
  }
1914
1933
 
1915
- /** Reference of a cell (eg. A1, $B$5) */
1916
- const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
1917
- // Same as above, but matches the exact string (nothing before or after)
1918
- const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
1919
- /** Reference of a column header (eg. A, AB, $A) */
1920
- const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
1921
- /** Reference of a row header (eg. 1, $1) */
1922
- const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
1923
- /** Reference of a column (eg. A, $CA, Sheet1!B) */
1924
- const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
1925
- /** Reference of a row (eg. 1, 59, Sheet1!9) */
1926
- const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
1927
- /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
1928
- const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
1929
- /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
1930
- const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
1931
- /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
1932
- const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
1933
- "(" +
1934
- [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
1935
- ")" +
1936
- /$/.source, "i");
1937
- /**
1938
- * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
1939
- */
1940
- function isColReference(xc) {
1941
- return colReference.test(xc);
1942
- }
1943
- /**
1944
- * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
1945
- */
1946
- function isRowReference(xc) {
1947
- return rowReference.test(xc);
1948
- }
1949
- function isColHeader(str) {
1950
- return colHeader.test(str);
1951
- }
1952
- function isRowHeader(str) {
1953
- return rowHeader.test(str);
1954
- }
1955
- /**
1956
- * Return true if the given xc is the reference of a single cell,
1957
- * without any specified sheet (e.g. A1)
1958
- */
1959
- function isSingleCellReference(xc) {
1960
- return singleCellReference.test(xc);
1961
- }
1962
- function splitReference(ref) {
1963
- if (!ref.includes("!")) {
1964
- return { xc: ref };
1965
- }
1966
- const parts = ref.split("!");
1967
- const xc = parts.pop();
1968
- const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
1969
- return { sheetName, xc };
1970
- }
1971
- /** Return a reference SheetName!xc from the given arguments */
1972
- function getFullReference(sheetName, xc) {
1973
- return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
1974
- }
1975
-
1976
1934
  /**
1977
1935
  * Convert from a cartesian reference to a Zone
1978
1936
  * The range boundaries will be kept in the same order as the
@@ -1990,63 +1948,55 @@ function getFullReference(sheetName, xc) {
1990
1948
  *
1991
1949
  */
1992
1950
  function toZoneWithoutBoundaryChanges(xc) {
1993
- if (xc.includes("!")) {
1994
- xc = xc.split("!").at(-1);
1995
- }
1996
- if (xc.includes("$")) {
1997
- xc = xc.replaceAll("$", "");
1998
- }
1999
- let firstRangePart = "";
2000
- let secondRangePart;
2001
- if (xc.includes(":")) {
2002
- [firstRangePart, secondRangePart] = xc.split(":");
2003
- firstRangePart = firstRangePart.trim();
2004
- secondRangePart = secondRangePart.trim();
2005
- }
2006
- else {
2007
- firstRangePart = xc.trim();
2008
- }
1951
+ const chars = new TokenizingChars(xc);
1952
+ consumeSpaces(chars);
1953
+ const sheetSeparatorIndex = xc.indexOf("!");
1954
+ if (sheetSeparatorIndex !== -1) {
1955
+ chars.advanceBy(sheetSeparatorIndex + 1);
1956
+ }
1957
+ const leftLetters = consumeLetters(chars);
1958
+ const leftNumbers = consumeDigits(chars);
2009
1959
  let top, bottom, left, right;
2010
1960
  let fullCol = false;
2011
1961
  let fullRow = false;
2012
1962
  let hasHeader = false;
2013
- if (isColReference(firstRangePart)) {
2014
- left = right = lettersToNumber(firstRangePart);
1963
+ if (leftNumbers === -1) {
1964
+ left = right = leftLetters - 1;
2015
1965
  top = bottom = 0;
2016
1966
  fullCol = true;
2017
1967
  }
2018
- else if (isRowReference(firstRangePart)) {
2019
- top = bottom = parseInt(firstRangePart, 10) - 1;
1968
+ else if (leftLetters === -1) {
1969
+ top = bottom = leftNumbers - 1;
2020
1970
  left = right = 0;
2021
1971
  fullRow = true;
2022
1972
  }
2023
1973
  else {
2024
- const c = toCartesian(firstRangePart);
2025
- left = right = c.col;
2026
- top = bottom = c.row;
1974
+ left = right = leftLetters - 1;
1975
+ top = bottom = leftNumbers - 1;
2027
1976
  hasHeader = true;
2028
1977
  }
2029
- if (secondRangePart) {
2030
- if (isColReference(secondRangePart)) {
2031
- right = lettersToNumber(secondRangePart);
1978
+ consumeSpaces(chars);
1979
+ if (chars.current === ":") {
1980
+ chars.advanceBy(1);
1981
+ consumeSpaces(chars);
1982
+ const rightLetters = consumeLetters(chars);
1983
+ const rightNumbers = consumeDigits(chars);
1984
+ if (rightNumbers === -1) {
1985
+ right = rightLetters - 1;
2032
1986
  fullCol = true;
2033
1987
  }
2034
- else if (isRowReference(secondRangePart)) {
2035
- bottom = parseInt(secondRangePart, 10) - 1;
1988
+ else if (rightLetters === -1) {
1989
+ bottom = rightNumbers - 1;
2036
1990
  fullRow = true;
2037
1991
  }
2038
1992
  else {
2039
- const c = toCartesian(secondRangePart);
2040
- right = c.col;
2041
- bottom = c.row;
1993
+ right = rightLetters - 1;
1994
+ bottom = rightNumbers - 1;
2042
1995
  top = fullCol ? bottom : top;
2043
1996
  left = fullRow ? right : left;
2044
1997
  hasHeader = true;
2045
1998
  }
2046
1999
  }
2047
- if (fullCol && fullRow) {
2048
- throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2049
- }
2050
2000
  const zone = {
2051
2001
  top,
2052
2002
  left,
@@ -2075,7 +2025,16 @@ function toZoneWithoutBoundaryChanges(xc) {
2075
2025
  */
2076
2026
  function toUnboundedZone(xc) {
2077
2027
  const zone = toZoneWithoutBoundaryChanges(xc);
2078
- return reorderZone(zone);
2028
+ const orderedZone = reorderZone(zone);
2029
+ const bottom = orderedZone.bottom;
2030
+ const right = orderedZone.right;
2031
+ if ((bottom !== undefined && bottom > MAX_ROW) || (right !== undefined && right > MAX_COL)) {
2032
+ throw new Error(`Range string out of bounds: ${xc}`); // limit the size of the zone for performance
2033
+ }
2034
+ if (bottom === undefined && right === undefined) {
2035
+ throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2036
+ }
2037
+ return orderedZone;
2079
2038
  }
2080
2039
  /**
2081
2040
  * Convert from a cartesian reference to a Zone.
@@ -4012,6 +3971,113 @@ function transposeMatrix(matrix) {
4012
3971
  }
4013
3972
  return generateMatrix(matrix[0].length, matrix.length, (i, j) => matrix[j][i]);
4014
3973
  }
3974
+ /**
3975
+ * Enables a formula function to accept matrix or vector inputs instead of simple value, computing results across multiple dimensions.
3976
+ *
3977
+ * ```
3978
+ * / |‾ ‾| \ |‾ ‾|
3979
+ * | | [A] | | | compute(A, D, E), compute(A, D, F), compute(A, D, G) |
3980
+ * applyVectorization| compute, | [B], D, [E, F, G] | | <=> | compute(B, D, E), compute(B, D, F), compute(B, D, G) |
3981
+ * | | [C] | | | compute(C, D, E), compute(C, D, F), compute(C, D, G) |
3982
+ * \ |_ _| / |_ _|
3983
+ * ```
3984
+ *
3985
+ * By default, all arguments are vectorized. To control which arguments are vectorized,
3986
+ * pass an `acceptToVectorize` boolean array of the same length as `args`:
3987
+ * - `true` enables vectorization for that argument
3988
+ * - `false` disables vectorization for that argument
3989
+ *
3990
+ * For example, with `[true, true, false]` on previous example you get:
3991
+ *
3992
+ * ```
3993
+ * |‾ ‾|
3994
+ * | compute(A, D, [E, F, G]) |
3995
+ * | compute(B, D, [E, F, G]) |
3996
+ * | compute(C, D, [E, F, G]) |
3997
+ * |_ _|
3998
+ * ```
3999
+ *
4000
+ * @remarks
4001
+ * This helper is automatically applied (by default) to **all** `compute` functions
4002
+ * across the various spreadsheet formula modules:
4003
+ * - If an argument is declared **scalar** (not `"range"`), it is vectorized.
4004
+ * - If **all** arguments are declared **ranges**, no vectorization occurs.
4005
+ * - e.g. `SUM(A1:B2)` returns a 1×1 sum over the range.
4006
+ * - e.g. `COS(A1:B2)` over `A1:B2` returns a 2×2 element-wise result.
4007
+ * - For special behaviors (e.g. the `IF` function), you may declare all arguments
4008
+ * as ranges and invoke this helper directly within your `compute` implementation.
4009
+ */
4010
+ function applyVectorization(formula, args, acceptToVectorize = undefined) {
4011
+ let countVectorizedCol = 1;
4012
+ let countVectorizedRow = 1;
4013
+ let vectorizedColLimit = Infinity;
4014
+ let vectorizedRowLimit = Infinity;
4015
+ let vectorArgsType = undefined;
4016
+ for (let i = 0; i < args.length; i++) {
4017
+ const arg = args[i];
4018
+ if (isMatrix(arg) && (acceptToVectorize === undefined || acceptToVectorize[i])) {
4019
+ const nColumns = arg.length;
4020
+ const nRows = arg[0].length;
4021
+ if (nColumns !== 1 || nRows !== 1) {
4022
+ vectorArgsType ??= new Array(args.length);
4023
+ if (nColumns !== 1 && nRows !== 1) {
4024
+ vectorArgsType[i] = "matrix";
4025
+ countVectorizedCol = Math.max(countVectorizedCol, nColumns);
4026
+ countVectorizedRow = Math.max(countVectorizedRow, nRows);
4027
+ vectorizedColLimit = Math.min(vectorizedColLimit, nColumns);
4028
+ vectorizedRowLimit = Math.min(vectorizedRowLimit, nRows);
4029
+ }
4030
+ else if (nColumns !== 1) {
4031
+ vectorArgsType[i] = "horizontal";
4032
+ countVectorizedCol = Math.max(countVectorizedCol, nColumns);
4033
+ vectorizedColLimit = Math.min(vectorizedColLimit, nColumns);
4034
+ }
4035
+ else if (nRows !== 1) {
4036
+ vectorArgsType[i] = "vertical";
4037
+ countVectorizedRow = Math.max(countVectorizedRow, nRows);
4038
+ vectorizedRowLimit = Math.min(vectorizedRowLimit, nRows);
4039
+ }
4040
+ }
4041
+ else {
4042
+ args[i] = arg[0][0];
4043
+ }
4044
+ }
4045
+ }
4046
+ if (countVectorizedCol === 1 && countVectorizedRow === 1) {
4047
+ // either this function is not vectorized or it ends up with a 1x1 dimension
4048
+ return formula(...args);
4049
+ }
4050
+ const getArgOffset = (i, j) => args.map((arg, index) => {
4051
+ switch (vectorArgsType?.[index]) {
4052
+ case "matrix":
4053
+ return arg[i][j];
4054
+ case "horizontal":
4055
+ return arg[i][0];
4056
+ case "vertical":
4057
+ return arg[0][j];
4058
+ case undefined:
4059
+ return arg;
4060
+ }
4061
+ });
4062
+ return generateMatrix(countVectorizedCol, countVectorizedRow, (col, row) => {
4063
+ if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) {
4064
+ return new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
4065
+ }
4066
+ const singleCellComputeResult = formula(...getArgOffset(col, row));
4067
+ // In the case where the user tries to vectorize arguments of an array formula, we will get an
4068
+ // array for every combination of the vectorized arguments, which will lead to a 3D matrix and
4069
+ // we won't be able to return the values.
4070
+ // In this case, we keep the first element of each spreading part, just as Excel does, and
4071
+ // create an array with these parts.
4072
+ // For exemple, we have MUNIT(x) that return an unitary matrix of x*x. If we use it with a
4073
+ // range, like MUNIT(A1:A2), we will get two unitary matrices (one for the value in A1 and one
4074
+ // for the value in A2). In this case, we will simply take the first value of each matrix and
4075
+ // return the array [First value of MUNIT(A1), First value of MUNIT(A2)].
4076
+ return isMatrix(singleCellComputeResult)
4077
+ ? singleCellComputeResult[0][0]
4078
+ : singleCellComputeResult;
4079
+ });
4080
+ }
4015
4081
  // -----------------------------------------------------------------------------
4016
4082
  // CONDITIONAL EXPLORE FUNCTIONS
4017
4083
  // -----------------------------------------------------------------------------
@@ -5796,6 +5862,67 @@ function scrollDelay(value) {
5796
5862
  return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
5797
5863
  }
5798
5864
 
5865
+ /** Reference of a cell (eg. A1, $B$5) */
5866
+ const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
5867
+ // Same as above, but matches the exact string (nothing before or after)
5868
+ const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
5869
+ /** Reference of a column header (eg. A, AB, $A) */
5870
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
5871
+ /** Reference of a row header (eg. 1, $1) */
5872
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
5873
+ /** Reference of a column (eg. A, $CA, Sheet1!B) */
5874
+ const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
5875
+ /** Reference of a row (eg. 1, 59, Sheet1!9) */
5876
+ const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
5877
+ /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
5878
+ const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
5879
+ /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
5880
+ const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
5881
+ /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
5882
+ const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
5883
+ "(" +
5884
+ [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
5885
+ ")" +
5886
+ /$/.source, "i");
5887
+ /**
5888
+ * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
5889
+ */
5890
+ function isColReference(xc) {
5891
+ return colReference.test(xc);
5892
+ }
5893
+ /**
5894
+ * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
5895
+ */
5896
+ function isRowReference(xc) {
5897
+ return rowReference.test(xc);
5898
+ }
5899
+ function isColHeader(str) {
5900
+ return colHeader.test(str);
5901
+ }
5902
+ function isRowHeader(str) {
5903
+ return rowHeader.test(str);
5904
+ }
5905
+ /**
5906
+ * Return true if the given xc is the reference of a single cell,
5907
+ * without any specified sheet (e.g. A1)
5908
+ */
5909
+ function isSingleCellReference(xc) {
5910
+ return singleCellReference.test(xc);
5911
+ }
5912
+ function splitReference(ref) {
5913
+ if (!ref.includes("!")) {
5914
+ return { xc: ref };
5915
+ }
5916
+ const parts = ref.split("!");
5917
+ const xc = parts.pop();
5918
+ const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
5919
+ return { sheetName, xc };
5920
+ }
5921
+ /** Return a reference SheetName!xc from the given arguments */
5922
+ function getFullReference(sheetName, xc) {
5923
+ return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
5924
+ }
5925
+
5799
5926
  class RangeImpl {
5800
5927
  getSheetSize;
5801
5928
  _zone;
@@ -7257,14 +7384,20 @@ function multiplyMatrices(matrix1, matrix2) {
7257
7384
  /**
7258
7385
  * Return the input if it's a scalar or the first element of the input if it's a matrix.
7259
7386
  */
7260
- function toScalar(matrix) {
7261
- if (!isMatrix(matrix)) {
7262
- return matrix;
7387
+ function toScalar(arg) {
7388
+ if (!isMatrix(arg)) {
7389
+ return arg;
7263
7390
  }
7264
- if (matrix.length !== 1 || matrix[0].length !== 1) {
7391
+ if (!isSingleElementMatrix(arg)) {
7265
7392
  throw new EvaluationError(_t("The value should be a scalar or a 1x1 matrix"));
7266
7393
  }
7267
- return matrix[0][0];
7394
+ return arg[0][0];
7395
+ }
7396
+ function isSingleElementMatrix(matrix) {
7397
+ return matrix.length === 1 && matrix[0].length === 1;
7398
+ }
7399
+ function isMultipleElementMatrix(arg) {
7400
+ return isMatrix(arg) && !isSingleElementMatrix(arg);
7268
7401
  }
7269
7402
 
7270
7403
  function assertSameNumberOfElements(...args) {
@@ -21465,7 +21598,7 @@ const FILTER = {
21465
21598
  }
21466
21599
  return mode === "row" ? transposeMatrix(result) : result;
21467
21600
  },
21468
- isExported: true,
21601
+ isExported: false,
21469
21602
  };
21470
21603
  // -----------------------------------------------------------------------------
21471
21604
  // SORT
@@ -24433,16 +24566,23 @@ const FALSE = {
24433
24566
  const IF = {
24434
24567
  description: _t("Returns value depending on logical expression."),
24435
24568
  args: [
24436
- arg("logical_expression (boolean)", _t("An expression or reference to a cell containing an expression that represents some logical value, i.e. TRUE or FALSE.")),
24437
- arg("value_if_true (any)", _t("The value the function returns if logical_expression is TRUE.")),
24438
- arg("value_if_false (any, default=FALSE)", _t("The value the function returns if logical_expression is FALSE.")),
24569
+ arg("logical_expression (boolean, range<boolean>)", _t("An expression or reference to a cell containing an expression that represents some logical value, i.e. TRUE or FALSE.")),
24570
+ arg("value_if_true (any, range)", _t("The value the function returns if logical_expression is TRUE.")),
24571
+ arg("value_if_false (any, range, default=FALSE)", _t("The value the function returns if logical_expression is FALSE.")),
24439
24572
  ],
24440
24573
  compute: function (logicalExpression, valueIfTrue, valueIfFalse) {
24441
- const result = toBoolean(logicalExpression?.value) ? valueIfTrue : valueIfFalse;
24574
+ if (isMultipleElementMatrix(logicalExpression)) {
24575
+ return applyVectorization(IF.compute, [logicalExpression, valueIfTrue, valueIfFalse]);
24576
+ }
24577
+ let result = toBoolean(toScalar(logicalExpression)) ? valueIfTrue : valueIfFalse;
24578
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
24579
+ if (!isMultipleElementMatrix(result)) {
24580
+ result = toScalar(result);
24581
+ }
24442
24582
  if (result === undefined) {
24443
24583
  return { value: "" };
24444
24584
  }
24445
- if (result.value === null) {
24585
+ if (!isMatrix(result) && result.value === null) {
24446
24586
  return { ...result, value: "" };
24447
24587
  }
24448
24588
  return result;
@@ -24455,15 +24595,22 @@ const IF = {
24455
24595
  const IFERROR = {
24456
24596
  description: _t("Value if it is not an error, otherwise 2nd argument."),
24457
24597
  args: [
24458
- arg("value (any)", _t("The value to return if value itself is not an error.")),
24459
- arg(`value_if_error (any, default="empty")`, _t("The value the function returns if value is an error.")),
24598
+ arg("value (any, range)", _t("The value to return if value itself is not an error.")),
24599
+ arg(`value_if_error (any, range, default="empty")`, _t("The value the function returns if value is an error.")),
24460
24600
  ],
24461
- compute: function (value, valueIfError = { value: "" }) {
24462
- const result = isEvaluationError(value?.value) ? valueIfError : value;
24601
+ compute: function (value, valueIfError) {
24602
+ if (isMultipleElementMatrix(value)) {
24603
+ return applyVectorization(IFERROR.compute, [value, valueIfError]);
24604
+ }
24605
+ let result = isEvaluationError(toScalar(value)?.value) ? valueIfError : value;
24606
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
24607
+ if (!isMultipleElementMatrix(result)) {
24608
+ result = toScalar(result);
24609
+ }
24463
24610
  if (result === undefined) {
24464
24611
  return { value: "" };
24465
24612
  }
24466
- if (result.value === null) {
24613
+ if (!isMatrix(result) && result.value === null) {
24467
24614
  return { ...result, value: "" };
24468
24615
  }
24469
24616
  return result;
@@ -24476,15 +24623,22 @@ const IFERROR = {
24476
24623
  const IFNA = {
24477
24624
  description: _t("Value if it is not an #N/A error, otherwise 2nd argument."),
24478
24625
  args: [
24479
- arg("value (any)", _t("The value to return if value itself is not #N/A an error.")),
24480
- arg(`value_if_error (any, default="empty")`, _t("The value the function returns if value is an #N/A error.")),
24626
+ arg("value (any, range)", _t("The value to return if value itself is not #N/A an error.")),
24627
+ arg(`value_if_error (any, range, default="empty")`, _t("The value the function returns if value is an #N/A error.")),
24481
24628
  ],
24482
- compute: function (value, valueIfError = { value: "" }) {
24483
- const result = value?.value === CellErrorType.NotAvailable ? valueIfError : value;
24629
+ compute: function (value, valueIfError) {
24630
+ if (isMultipleElementMatrix(value)) {
24631
+ return applyVectorization(IFNA.compute, [value, valueIfError]);
24632
+ }
24633
+ let result = toScalar(value)?.value === CellErrorType.NotAvailable ? valueIfError : value;
24634
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
24635
+ if (!isMultipleElementMatrix(result)) {
24636
+ result = toScalar(result);
24637
+ }
24484
24638
  if (result === undefined) {
24485
24639
  return { value: "" };
24486
24640
  }
24487
- if (result.value === null) {
24641
+ if (!isMatrix(result) && result.value === null) {
24488
24642
  return { ...result, value: "" };
24489
24643
  }
24490
24644
  return result;
@@ -24497,23 +24651,31 @@ const IFNA = {
24497
24651
  const IFS = {
24498
24652
  description: _t("Returns a value depending on multiple logical expressions."),
24499
24653
  args: [
24500
- arg("condition1 (boolean)", _t("The first condition to be evaluated. This can be a boolean, a number, an array, or a reference to any of those.")),
24501
- arg("value1 (any)", _t("The returned value if condition1 is TRUE.")),
24502
- arg("condition2 (boolean, any, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
24503
- arg("value2 (any, repeating)", _t("Additional values to be returned if their corresponding conditions are TRUE.")),
24654
+ arg("condition1 (boolean, range<boolean>)", _t("The first condition to be evaluated. This can be a boolean, a number, an array, or a reference to any of those.")),
24655
+ arg("value1 (any, range)", _t("The returned value if condition1 is TRUE.")),
24656
+ arg("condition2 (boolean, any, range, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
24657
+ arg("value2 (any, range, repeating)", _t("Additional values to be returned if their corresponding conditions are TRUE.")),
24504
24658
  ],
24505
24659
  compute: function (...values) {
24506
24660
  assert(() => values.length % 2 === 0, _t("Wrong number of arguments. Expected an even number of arguments."));
24507
- for (let n = 0; n < values.length - 1; n += 2) {
24508
- if (toBoolean(values[n]?.value)) {
24509
- const result = values[n + 1];
24510
- if (result === undefined) {
24661
+ while (values.length > 0) {
24662
+ if (isMultipleElementMatrix(values[0])) {
24663
+ return applyVectorization(IFS.compute, values);
24664
+ }
24665
+ const condition = toBoolean(toScalar(values.shift()));
24666
+ let valueIfTrue = values.shift();
24667
+ if (condition) {
24668
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
24669
+ if (!isMultipleElementMatrix(valueIfTrue)) {
24670
+ valueIfTrue = toScalar(valueIfTrue);
24671
+ }
24672
+ if (valueIfTrue === undefined) {
24511
24673
  return { value: "" };
24512
24674
  }
24513
- if (result.value === null) {
24514
- return { ...result, value: "" };
24675
+ if (!isMatrix(valueIfTrue) && valueIfTrue.value === null) {
24676
+ return { ...valueIfTrue, value: "" };
24515
24677
  }
24516
- return result;
24678
+ return valueIfTrue;
24517
24679
  }
24518
24680
  }
24519
24681
  return new EvaluationError(_t("No match."));
@@ -26157,7 +26319,7 @@ const SPLIT = {
26157
26319
  }
26158
26320
  return transposeMatrix([result]);
26159
26321
  },
26160
- isExported: true,
26322
+ isExported: false,
26161
26323
  };
26162
26324
  // -----------------------------------------------------------------------------
26163
26325
  // SUBSTITUTE
@@ -26350,86 +26512,21 @@ for (let category of categories) {
26350
26512
  functionRegistry.add(name, { isExported: false, ...addDescr });
26351
26513
  }
26352
26514
  }
26353
- const notAvailableError = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
26515
+ //------------------------------------------------------------------------------
26516
+ // CREATE COMPUTE FUNCTION
26517
+ //------------------------------------------------------------------------------
26354
26518
  function createComputeFunction(descr, functionName) {
26355
26519
  function vectorizedCompute(...args) {
26356
- let countVectorizableCol = 1;
26357
- let countVectorizableRow = 1;
26358
- let vectorizableColLimit = Infinity;
26359
- let vectorizableRowLimit = Infinity;
26360
- let vectorArgsType = undefined;
26361
- //#region Compute vectorisation limits
26520
+ const acceptToVectorize = [];
26362
26521
  for (let i = 0; i < args.length; i++) {
26363
26522
  const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
26364
26523
  const arg = args[i];
26365
- if (isMatrix(arg) && !argDefinition.acceptMatrix) {
26366
- // if argDefinition does not accept a matrix but arg is still a matrix
26367
- // --> triggers the arguments vectorization
26368
- const nColumns = arg.length;
26369
- const nRows = arg[0].length;
26370
- if (nColumns !== 1 || nRows !== 1) {
26371
- vectorArgsType ??= new Array(args.length);
26372
- if (nColumns !== 1 && nRows !== 1) {
26373
- vectorArgsType[i] = "matrix";
26374
- countVectorizableCol = Math.max(countVectorizableCol, nColumns);
26375
- countVectorizableRow = Math.max(countVectorizableRow, nRows);
26376
- vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
26377
- vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
26378
- }
26379
- else if (nColumns !== 1) {
26380
- vectorArgsType[i] = "horizontal";
26381
- countVectorizableCol = Math.max(countVectorizableCol, nColumns);
26382
- vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
26383
- }
26384
- else if (nRows !== 1) {
26385
- vectorArgsType[i] = "vertical";
26386
- countVectorizableRow = Math.max(countVectorizableRow, nRows);
26387
- vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
26388
- }
26389
- }
26390
- else {
26391
- args[i] = arg[0][0];
26392
- }
26393
- }
26394
26524
  if (!isMatrix(arg) && argDefinition.acceptMatrixOnly) {
26395
26525
  throw new BadExpressionError(_t("Function %s expects the parameter '%s' to be reference to a cell or range.", functionName, (i + 1).toString()));
26396
26526
  }
26527
+ acceptToVectorize.push(!argDefinition.acceptMatrix);
26397
26528
  }
26398
- //#endregion
26399
- if (countVectorizableCol === 1 && countVectorizableRow === 1) {
26400
- // either this function is not vectorized or it ends up with a 1x1 dimension
26401
- return errorHandlingCompute.apply(this, args);
26402
- }
26403
- const getArgOffset = (i, j) => args.map((arg, index) => {
26404
- switch (vectorArgsType?.[index]) {
26405
- case "matrix":
26406
- return arg[i][j];
26407
- case "horizontal":
26408
- return arg[i][0];
26409
- case "vertical":
26410
- return arg[0][j];
26411
- case undefined:
26412
- return arg;
26413
- }
26414
- });
26415
- return generateMatrix(countVectorizableCol, countVectorizableRow, (col, row) => {
26416
- if (col > vectorizableColLimit - 1 || row > vectorizableRowLimit - 1) {
26417
- return notAvailableError;
26418
- }
26419
- const singleCellComputeResult = errorHandlingCompute.apply(this, getArgOffset(col, row));
26420
- // In the case where the user tries to vectorize arguments of an array formula, we will get an
26421
- // array for every combination of the vectorized arguments, which will lead to a 3D matrix and
26422
- // we won't be able to return the values.
26423
- // In this case, we keep the first element of each spreading part, just as Excel does, and
26424
- // create an array with these parts.
26425
- // For exemple, we have MUNIT(x) that return an unitary matrix of x*x. If we use it with a
26426
- // range, like MUNIT(A1:A2), we will get two unitary matrices (one for the value in A1 and one
26427
- // for the value in A2). In this case, we will simply take the first value of each matrix and
26428
- // return the array [First value of MUNIT(A1), First value of MUNIT(A2)].
26429
- return isMatrix(singleCellComputeResult)
26430
- ? singleCellComputeResult[0][0]
26431
- : singleCellComputeResult;
26432
- });
26529
+ return applyVectorization(errorHandlingCompute.bind(this), args, acceptToVectorize);
26433
26530
  }
26434
26531
  function errorHandlingCompute(...args) {
26435
26532
  for (let i = 0; i < args.length; i++) {
@@ -39418,7 +39515,7 @@ class AbstractComposerStore extends SpreadsheetStore {
39418
39515
  proposals &&
39419
39516
  !["ARG_SEPARATOR", "LEFT_PAREN", "OPERATOR"].includes(tokenAtCursor.type)) {
39420
39517
  const filteredProposals = fuzzyLookup(searchTerm, proposals, (p) => p.fuzzySearchKey || p.text);
39421
- if (!exactMatch || filteredProposals.length > 1) {
39518
+ if (!exactMatch || filteredProposals.length) {
39422
39519
  proposals = filteredProposals;
39423
39520
  }
39424
39521
  }
@@ -39493,12 +39590,13 @@ class StandaloneComposerStore extends AbstractComposerStore {
39493
39590
  return providersDefinitions;
39494
39591
  }
39495
39592
  getComposerContent() {
39593
+ let content = this._currentContent;
39496
39594
  if (this.editionMode === "inactive") {
39497
39595
  // References in the content might not be linked to the current active sheet
39498
39596
  // We here force the sheet name prefix for all references that are not in
39499
39597
  // the current active sheet
39500
39598
  const defaultRangeSheetId = this.args().defaultRangeSheetId;
39501
- return rangeTokenize(this.args().content)
39599
+ content = rangeTokenize(this.args().content)
39502
39600
  .map((token) => {
39503
39601
  if (token.type === "REFERENCE") {
39504
39602
  const range = this.getters.getRangeFromSheetXC(defaultRangeSheetId, token.value);
@@ -39508,7 +39606,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
39508
39606
  })
39509
39607
  .join("");
39510
39608
  }
39511
- return this._currentContent;
39609
+ return localizeContent(content, this.getters.getLocale());
39512
39610
  }
39513
39611
  stopEdition() {
39514
39612
  this._stopEdition();
@@ -43111,6 +43209,9 @@ class PivotMeasureEditor extends Component {
43111
43209
  measure: this.props.measure,
43112
43210
  });
43113
43211
  }
43212
+ get isCalculatedMeasureInvalid() {
43213
+ return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
43214
+ }
43114
43215
  }
43115
43216
 
43116
43217
  css /* scss */ `
@@ -44023,7 +44124,9 @@ function compareDimensionValues(dimension, a, b) {
44023
44124
  return dimension.order === "asc" ? -1 : 1;
44024
44125
  }
44025
44126
  if (dimension.type === "integer" || dimension.type === "datetime") {
44026
- return dimension.order === "asc" ? Number(a) - Number(b) : Number(b) - Number(a);
44127
+ return dimension.order === "asc"
44128
+ ? toNumber(a, DEFAULT_LOCALE) - toNumber(b, DEFAULT_LOCALE)
44129
+ : toNumber(b, DEFAULT_LOCALE) - toNumber(a, DEFAULT_LOCALE);
44027
44130
  }
44028
44131
  return dimension.order === "asc" ? a.localeCompare(b) : b.localeCompare(a);
44029
44132
  }
@@ -46764,8 +46867,7 @@ class CellComposerStore extends AbstractComposerStore {
46764
46867
  if (!spreader) {
46765
46868
  return undefined;
46766
46869
  }
46767
- const cell = this.getters.getCell(spreader);
46768
- return cell?.content;
46870
+ return this.getters.getCellText(spreader, { showFormula: true });
46769
46871
  }
46770
46872
  get currentEditedCell() {
46771
46873
  return {
@@ -49568,6 +49670,7 @@ function useGridDrawing(refName, model, canvasSize) {
49568
49670
  const friction = 0.95;
49569
49671
  const verticalScrollFactor = 1;
49570
49672
  const horizontalScrollFactor = 1;
49673
+ const resetTimeoutDuration = 100;
49571
49674
  function useTouchScroll(ref, updateScroll, canMoveUp) {
49572
49675
  let lastX = 0;
49573
49676
  let lastY = 0;
@@ -49575,6 +49678,7 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
49575
49678
  let velocityY = 0;
49576
49679
  let isMouseDown = false;
49577
49680
  let lastTime = 0;
49681
+ let resetTimeout = null;
49578
49682
  useRefListener(ref, "touchstart", onTouchStart, { capture: false });
49579
49683
  useRefListener(ref, "touchmove", onTouchMove, { capture: false });
49580
49684
  useRefListener(ref, "touchend", onTouchEnd, { capture: false });
@@ -49587,6 +49691,10 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
49587
49691
  function onTouchMove(event) {
49588
49692
  if (!isMouseDown)
49589
49693
  return;
49694
+ if (resetTimeout) {
49695
+ clearTimeout(resetTimeout);
49696
+ resetTimeout = null;
49697
+ }
49590
49698
  const currentTime = Date.now();
49591
49699
  const { clientX, clientY } = event.touches[0];
49592
49700
  let deltaX = lastX - clientX;
@@ -49603,6 +49711,10 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
49603
49711
  }
49604
49712
  event.stopPropagation();
49605
49713
  }
49714
+ resetTimeout = setTimeout(() => {
49715
+ velocityX = 0;
49716
+ velocityY = 0;
49717
+ }, resetTimeoutDuration);
49606
49718
  updateScroll(deltaX * horizontalScrollFactor, deltaY * verticalScrollFactor);
49607
49719
  }
49608
49720
  function onTouchEnd(ev) {
@@ -58445,10 +58557,9 @@ class Evaluator {
58445
58557
  return this.evaluatedCells.keysForSheet(sheetId);
58446
58558
  }
58447
58559
  getArrayFormulaSpreadingOn(position) {
58448
- const hasArrayFormulaResult = this.getEvaluatedCell(position).type !== CellValueType.empty &&
58449
- !this.getters.getCell(position)?.isFormula;
58450
- if (!hasArrayFormulaResult) {
58451
- return this.spreadingRelations.isArrayFormula(position) ? position : undefined;
58560
+ const isEmpty = this.getEvaluatedCell(position).type === CellValueType.empty;
58561
+ if (isEmpty) {
58562
+ return undefined;
58452
58563
  }
58453
58564
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
58454
58565
  return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
@@ -74248,6 +74359,6 @@ const constants = {
74248
74359
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
74249
74360
 
74250
74361
 
74251
- __info__.version = "18.0.28";
74252
- __info__.date = "2025-05-13T17:53:12.402Z";
74253
- __info__.hash = "b3088aa";
74362
+ __info__.version = "18.0.30";
74363
+ __info__.date = "2025-05-26T12:35:05.184Z";
74364
+ __info__.hash = "838c4f7";