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