@odoo/o-spreadsheet 18.1.20 → 18.1.22

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.1.20
6
- * @date 2025-05-13T17:52:28.174Z
7
- * @hash 3e43a46
5
+ * @version 18.1.22
6
+ * @date 2025-05-26T12:35:56.145Z
7
+ * @hash ff4b0ba
8
8
  */
9
9
 
10
10
  'use strict';
@@ -1608,18 +1608,53 @@ function lettersToNumber(letters) {
1608
1608
  let result = 0;
1609
1609
  const l = letters.length;
1610
1610
  for (let i = 0; i < l; i++) {
1611
- const charCode = letters.charCodeAt(i);
1612
- const colIndex = charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1611
+ const colIndex = charToNumber(letters[i]);
1613
1612
  result = result * 26 + colIndex;
1614
1613
  }
1615
1614
  return result - 1;
1616
1615
  }
1616
+ function charToNumber(char) {
1617
+ const charCode = char.charCodeAt(0);
1618
+ return charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1619
+ }
1617
1620
  function isCharALetter(char) {
1618
1621
  return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
1619
1622
  }
1620
1623
  function isCharADigit(char) {
1621
1624
  return char >= "0" && char <= "9";
1622
1625
  }
1626
+ // we limit the max column to 3 letters and max row to 7 digits for performance reasons
1627
+ const MAX_COL = lettersToNumber("ZZZ");
1628
+ const MAX_ROW = 9999998;
1629
+ function consumeSpaces(chars) {
1630
+ while (chars.current === " ") {
1631
+ chars.advanceBy(1);
1632
+ }
1633
+ }
1634
+ function consumeLetters(chars) {
1635
+ if (chars.current === "$")
1636
+ chars.advanceBy(1);
1637
+ if (!chars.current || !isCharALetter(chars.current)) {
1638
+ return -1;
1639
+ }
1640
+ let colCoordinate = 0;
1641
+ while (chars.current && isCharALetter(chars.current)) {
1642
+ colCoordinate = colCoordinate * 26 + charToNumber(chars.shift());
1643
+ }
1644
+ return colCoordinate;
1645
+ }
1646
+ function consumeDigits(chars) {
1647
+ if (chars.current === "$")
1648
+ chars.advanceBy(1);
1649
+ if (!chars.current || !isCharADigit(chars.current)) {
1650
+ return -1;
1651
+ }
1652
+ let num = 0;
1653
+ while (chars.current && isCharADigit(chars.current)) {
1654
+ num = num * 10 + Number(chars.shift());
1655
+ }
1656
+ return num;
1657
+ }
1623
1658
  /**
1624
1659
  * Convert a "XC" coordinate to cartesian coordinates.
1625
1660
  *
@@ -1630,33 +1665,17 @@ function isCharADigit(char) {
1630
1665
  * Note: it also accepts lowercase coordinates, but not fixed references
1631
1666
  */
1632
1667
  function toCartesian(xc) {
1633
- xc = xc.trim();
1634
- let letterPart = "";
1635
- let numberPart = "";
1636
- let i = 0;
1637
- // Process letter part
1638
- if (xc[i] === "$")
1639
- i++;
1640
- while (i < xc.length && isCharALetter(xc[i])) {
1641
- letterPart += xc[i++];
1642
- }
1643
- if (letterPart.length === 0 || letterPart.length > 3) {
1644
- // limit to max 3 letters for performance reasons
1668
+ const chars = new TokenizingChars(xc);
1669
+ consumeSpaces(chars);
1670
+ const letterPart = consumeLetters(chars);
1671
+ if (letterPart === -1 || !chars.current) {
1645
1672
  throw new Error(`Invalid cell description: ${xc}`);
1646
1673
  }
1647
- // Process number part
1648
- if (xc[i] === "$")
1649
- i++;
1650
- while (i < xc.length && isCharADigit(xc[i])) {
1651
- numberPart += xc[i++];
1652
- }
1653
- if (i !== xc.length || numberPart.length === 0 || numberPart.length > 7) {
1654
- // limit to max 7 numbers for performance reasons
1655
- throw new Error(`Invalid cell description: ${xc}`);
1656
- }
1657
- const col = lettersToNumber(letterPart);
1658
- const row = Number(numberPart) - 1;
1659
- if (isNaN(row)) {
1674
+ const num = consumeDigits(chars);
1675
+ consumeSpaces(chars);
1676
+ const col = letterPart - 1;
1677
+ const row = num - 1;
1678
+ if (!chars.isOver() || col > MAX_COL || row > MAX_ROW) {
1660
1679
  throw new Error(`Invalid cell description: ${xc}`);
1661
1680
  }
1662
1681
  return { col, row };
@@ -2068,67 +2087,6 @@ class LazyTranslatedString extends String {
2068
2087
  }
2069
2088
  }
2070
2089
 
2071
- /** Reference of a cell (eg. A1, $B$5) */
2072
- const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2073
- // Same as above, but matches the exact string (nothing before or after)
2074
- const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2075
- /** Reference of a column header (eg. A, AB, $A) */
2076
- const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
2077
- /** Reference of a row header (eg. 1, $1) */
2078
- const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
2079
- /** Reference of a column (eg. A, $CA, Sheet1!B) */
2080
- const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2081
- /** Reference of a row (eg. 1, 59, Sheet1!9) */
2082
- const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
2083
- /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
2084
- const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
2085
- /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
2086
- const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
2087
- /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
2088
- const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
2089
- "(" +
2090
- [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
2091
- ")" +
2092
- /$/.source, "i");
2093
- /**
2094
- * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
2095
- */
2096
- function isColReference(xc) {
2097
- return colReference.test(xc);
2098
- }
2099
- /**
2100
- * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
2101
- */
2102
- function isRowReference(xc) {
2103
- return rowReference.test(xc);
2104
- }
2105
- function isColHeader(str) {
2106
- return colHeader.test(str);
2107
- }
2108
- function isRowHeader(str) {
2109
- return rowHeader.test(str);
2110
- }
2111
- /**
2112
- * Return true if the given xc is the reference of a single cell,
2113
- * without any specified sheet (e.g. A1)
2114
- */
2115
- function isSingleCellReference(xc) {
2116
- return singleCellReference.test(xc);
2117
- }
2118
- function splitReference(ref) {
2119
- if (!ref.includes("!")) {
2120
- return { xc: ref };
2121
- }
2122
- const parts = ref.split("!");
2123
- const xc = parts.pop();
2124
- const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
2125
- return { sheetName, xc };
2126
- }
2127
- /** Return a reference SheetName!xc from the given arguments */
2128
- function getFullReference(sheetName, xc) {
2129
- return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
2130
- }
2131
-
2132
2090
  /**
2133
2091
  * Convert from a cartesian reference to a Zone
2134
2092
  * The range boundaries will be kept in the same order as the
@@ -2146,63 +2104,55 @@ function getFullReference(sheetName, xc) {
2146
2104
  *
2147
2105
  */
2148
2106
  function toZoneWithoutBoundaryChanges(xc) {
2149
- if (xc.includes("!")) {
2150
- xc = xc.split("!").at(-1);
2151
- }
2152
- if (xc.includes("$")) {
2153
- xc = xc.replaceAll("$", "");
2154
- }
2155
- let firstRangePart = "";
2156
- let secondRangePart;
2157
- if (xc.includes(":")) {
2158
- [firstRangePart, secondRangePart] = xc.split(":");
2159
- firstRangePart = firstRangePart.trim();
2160
- secondRangePart = secondRangePart.trim();
2161
- }
2162
- else {
2163
- firstRangePart = xc.trim();
2164
- }
2107
+ const chars = new TokenizingChars(xc);
2108
+ consumeSpaces(chars);
2109
+ const sheetSeparatorIndex = xc.indexOf("!");
2110
+ if (sheetSeparatorIndex !== -1) {
2111
+ chars.advanceBy(sheetSeparatorIndex + 1);
2112
+ }
2113
+ const leftLetters = consumeLetters(chars);
2114
+ const leftNumbers = consumeDigits(chars);
2165
2115
  let top, bottom, left, right;
2166
2116
  let fullCol = false;
2167
2117
  let fullRow = false;
2168
2118
  let hasHeader = false;
2169
- if (isColReference(firstRangePart)) {
2170
- left = right = lettersToNumber(firstRangePart);
2119
+ if (leftNumbers === -1) {
2120
+ left = right = leftLetters - 1;
2171
2121
  top = bottom = 0;
2172
2122
  fullCol = true;
2173
2123
  }
2174
- else if (isRowReference(firstRangePart)) {
2175
- top = bottom = parseInt(firstRangePart, 10) - 1;
2124
+ else if (leftLetters === -1) {
2125
+ top = bottom = leftNumbers - 1;
2176
2126
  left = right = 0;
2177
2127
  fullRow = true;
2178
2128
  }
2179
2129
  else {
2180
- const c = toCartesian(firstRangePart);
2181
- left = right = c.col;
2182
- top = bottom = c.row;
2130
+ left = right = leftLetters - 1;
2131
+ top = bottom = leftNumbers - 1;
2183
2132
  hasHeader = true;
2184
2133
  }
2185
- if (secondRangePart) {
2186
- if (isColReference(secondRangePart)) {
2187
- right = lettersToNumber(secondRangePart);
2134
+ consumeSpaces(chars);
2135
+ if (chars.current === ":") {
2136
+ chars.advanceBy(1);
2137
+ consumeSpaces(chars);
2138
+ const rightLetters = consumeLetters(chars);
2139
+ const rightNumbers = consumeDigits(chars);
2140
+ if (rightNumbers === -1) {
2141
+ right = rightLetters - 1;
2188
2142
  fullCol = true;
2189
2143
  }
2190
- else if (isRowReference(secondRangePart)) {
2191
- bottom = parseInt(secondRangePart, 10) - 1;
2144
+ else if (rightLetters === -1) {
2145
+ bottom = rightNumbers - 1;
2192
2146
  fullRow = true;
2193
2147
  }
2194
2148
  else {
2195
- const c = toCartesian(secondRangePart);
2196
- right = c.col;
2197
- bottom = c.row;
2149
+ right = rightLetters - 1;
2150
+ bottom = rightNumbers - 1;
2198
2151
  top = fullCol ? bottom : top;
2199
2152
  left = fullRow ? right : left;
2200
2153
  hasHeader = true;
2201
2154
  }
2202
2155
  }
2203
- if (fullCol && fullRow) {
2204
- throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2205
- }
2206
2156
  const zone = {
2207
2157
  top,
2208
2158
  left,
@@ -2231,7 +2181,16 @@ function toZoneWithoutBoundaryChanges(xc) {
2231
2181
  */
2232
2182
  function toUnboundedZone(xc) {
2233
2183
  const zone = toZoneWithoutBoundaryChanges(xc);
2234
- return reorderZone(zone);
2184
+ const orderedZone = reorderZone(zone);
2185
+ const bottom = orderedZone.bottom;
2186
+ const right = orderedZone.right;
2187
+ if ((bottom !== undefined && bottom > MAX_ROW) || (right !== undefined && right > MAX_COL)) {
2188
+ throw new Error(`Range string out of bounds: ${xc}`); // limit the size of the zone for performance
2189
+ }
2190
+ if (bottom === undefined && right === undefined) {
2191
+ throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2192
+ }
2193
+ return orderedZone;
2235
2194
  }
2236
2195
  /**
2237
2196
  * Convert from a cartesian reference to a Zone.
@@ -4183,6 +4142,113 @@ function transposeMatrix(matrix) {
4183
4142
  }
4184
4143
  return generateMatrix(matrix[0].length, matrix.length, (i, j) => matrix[j][i]);
4185
4144
  }
4145
+ /**
4146
+ * Enables a formula function to accept matrix or vector inputs instead of simple value, computing results across multiple dimensions.
4147
+ *
4148
+ * ```
4149
+ * / |‾ ‾| \ |‾ ‾|
4150
+ * | | [A] | | | compute(A, D, E), compute(A, D, F), compute(A, D, G) |
4151
+ * applyVectorization| compute, | [B], D, [E, F, G] | | <=> | compute(B, D, E), compute(B, D, F), compute(B, D, G) |
4152
+ * | | [C] | | | compute(C, D, E), compute(C, D, F), compute(C, D, G) |
4153
+ * \ |_ _| / |_ _|
4154
+ * ```
4155
+ *
4156
+ * By default, all arguments are vectorized. To control which arguments are vectorized,
4157
+ * pass an `acceptToVectorize` boolean array of the same length as `args`:
4158
+ * - `true` enables vectorization for that argument
4159
+ * - `false` disables vectorization for that argument
4160
+ *
4161
+ * For example, with `[true, true, false]` on previous example you get:
4162
+ *
4163
+ * ```
4164
+ * |‾ ‾|
4165
+ * | compute(A, D, [E, F, G]) |
4166
+ * | compute(B, D, [E, F, G]) |
4167
+ * | compute(C, D, [E, F, G]) |
4168
+ * |_ _|
4169
+ * ```
4170
+ *
4171
+ * @remarks
4172
+ * This helper is automatically applied (by default) to **all** `compute` functions
4173
+ * across the various spreadsheet formula modules:
4174
+ * - If an argument is declared **scalar** (not `"range"`), it is vectorized.
4175
+ * - If **all** arguments are declared **ranges**, no vectorization occurs.
4176
+ * - e.g. `SUM(A1:B2)` returns a 1×1 sum over the range.
4177
+ * - e.g. `COS(A1:B2)` over `A1:B2` returns a 2×2 element-wise result.
4178
+ * - For special behaviors (e.g. the `IF` function), you may declare all arguments
4179
+ * as ranges and invoke this helper directly within your `compute` implementation.
4180
+ */
4181
+ function applyVectorization(formula, args, acceptToVectorize = undefined) {
4182
+ let countVectorizedCol = 1;
4183
+ let countVectorizedRow = 1;
4184
+ let vectorizedColLimit = Infinity;
4185
+ let vectorizedRowLimit = Infinity;
4186
+ let vectorArgsType = undefined;
4187
+ for (let i = 0; i < args.length; i++) {
4188
+ const arg = args[i];
4189
+ if (isMatrix(arg) && (acceptToVectorize === undefined || acceptToVectorize[i])) {
4190
+ const nColumns = arg.length;
4191
+ const nRows = arg[0].length;
4192
+ if (nColumns !== 1 || nRows !== 1) {
4193
+ vectorArgsType ??= new Array(args.length);
4194
+ if (nColumns !== 1 && nRows !== 1) {
4195
+ vectorArgsType[i] = "matrix";
4196
+ countVectorizedCol = Math.max(countVectorizedCol, nColumns);
4197
+ countVectorizedRow = Math.max(countVectorizedRow, nRows);
4198
+ vectorizedColLimit = Math.min(vectorizedColLimit, nColumns);
4199
+ vectorizedRowLimit = Math.min(vectorizedRowLimit, nRows);
4200
+ }
4201
+ else if (nColumns !== 1) {
4202
+ vectorArgsType[i] = "horizontal";
4203
+ countVectorizedCol = Math.max(countVectorizedCol, nColumns);
4204
+ vectorizedColLimit = Math.min(vectorizedColLimit, nColumns);
4205
+ }
4206
+ else if (nRows !== 1) {
4207
+ vectorArgsType[i] = "vertical";
4208
+ countVectorizedRow = Math.max(countVectorizedRow, nRows);
4209
+ vectorizedRowLimit = Math.min(vectorizedRowLimit, nRows);
4210
+ }
4211
+ }
4212
+ else {
4213
+ args[i] = arg[0][0];
4214
+ }
4215
+ }
4216
+ }
4217
+ if (countVectorizedCol === 1 && countVectorizedRow === 1) {
4218
+ // either this function is not vectorized or it ends up with a 1x1 dimension
4219
+ return formula(...args);
4220
+ }
4221
+ const getArgOffset = (i, j) => args.map((arg, index) => {
4222
+ switch (vectorArgsType?.[index]) {
4223
+ case "matrix":
4224
+ return arg[i][j];
4225
+ case "horizontal":
4226
+ return arg[i][0];
4227
+ case "vertical":
4228
+ return arg[0][j];
4229
+ case undefined:
4230
+ return arg;
4231
+ }
4232
+ });
4233
+ return generateMatrix(countVectorizedCol, countVectorizedRow, (col, row) => {
4234
+ if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) {
4235
+ return new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
4236
+ }
4237
+ const singleCellComputeResult = formula(...getArgOffset(col, row));
4238
+ // In the case where the user tries to vectorize arguments of an array formula, we will get an
4239
+ // array for every combination of the vectorized arguments, which will lead to a 3D matrix and
4240
+ // we won't be able to return the values.
4241
+ // In this case, we keep the first element of each spreading part, just as Excel does, and
4242
+ // create an array with these parts.
4243
+ // For exemple, we have MUNIT(x) that return an unitary matrix of x*x. If we use it with a
4244
+ // range, like MUNIT(A1:A2), we will get two unitary matrices (one for the value in A1 and one
4245
+ // for the value in A2). In this case, we will simply take the first value of each matrix and
4246
+ // return the array [First value of MUNIT(A1), First value of MUNIT(A2)].
4247
+ return isMatrix(singleCellComputeResult)
4248
+ ? singleCellComputeResult[0][0]
4249
+ : singleCellComputeResult;
4250
+ });
4251
+ }
4186
4252
  // -----------------------------------------------------------------------------
4187
4253
  // CONDITIONAL EXPLORE FUNCTIONS
4188
4254
  // -----------------------------------------------------------------------------
@@ -5967,6 +6033,67 @@ function scrollDelay(value) {
5967
6033
  return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
5968
6034
  }
5969
6035
 
6036
+ /** Reference of a cell (eg. A1, $B$5) */
6037
+ const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
6038
+ // Same as above, but matches the exact string (nothing before or after)
6039
+ const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
6040
+ /** Reference of a column header (eg. A, AB, $A) */
6041
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
6042
+ /** Reference of a row header (eg. 1, $1) */
6043
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
6044
+ /** Reference of a column (eg. A, $CA, Sheet1!B) */
6045
+ const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
6046
+ /** Reference of a row (eg. 1, 59, Sheet1!9) */
6047
+ const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
6048
+ /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
6049
+ const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
6050
+ /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
6051
+ const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
6052
+ /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
6053
+ const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
6054
+ "(" +
6055
+ [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
6056
+ ")" +
6057
+ /$/.source, "i");
6058
+ /**
6059
+ * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
6060
+ */
6061
+ function isColReference(xc) {
6062
+ return colReference.test(xc);
6063
+ }
6064
+ /**
6065
+ * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
6066
+ */
6067
+ function isRowReference(xc) {
6068
+ return rowReference.test(xc);
6069
+ }
6070
+ function isColHeader(str) {
6071
+ return colHeader.test(str);
6072
+ }
6073
+ function isRowHeader(str) {
6074
+ return rowHeader.test(str);
6075
+ }
6076
+ /**
6077
+ * Return true if the given xc is the reference of a single cell,
6078
+ * without any specified sheet (e.g. A1)
6079
+ */
6080
+ function isSingleCellReference(xc) {
6081
+ return singleCellReference.test(xc);
6082
+ }
6083
+ function splitReference(ref) {
6084
+ if (!ref.includes("!")) {
6085
+ return { xc: ref };
6086
+ }
6087
+ const parts = ref.split("!");
6088
+ const xc = parts.pop();
6089
+ const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
6090
+ return { sheetName, xc };
6091
+ }
6092
+ /** Return a reference SheetName!xc from the given arguments */
6093
+ function getFullReference(sheetName, xc) {
6094
+ return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
6095
+ }
6096
+
5970
6097
  class RangeImpl {
5971
6098
  getSheetSize;
5972
6099
  _zone;
@@ -7413,14 +7540,20 @@ function multiplyMatrices(matrix1, matrix2) {
7413
7540
  /**
7414
7541
  * Return the input if it's a scalar or the first element of the input if it's a matrix.
7415
7542
  */
7416
- function toScalar(matrix) {
7417
- if (!isMatrix(matrix)) {
7418
- return matrix;
7543
+ function toScalar(arg) {
7544
+ if (!isMatrix(arg)) {
7545
+ return arg;
7419
7546
  }
7420
- if (matrix.length !== 1 || matrix[0].length !== 1) {
7547
+ if (!isSingleElementMatrix(arg)) {
7421
7548
  throw new EvaluationError(_t("The value should be a scalar or a 1x1 matrix"));
7422
7549
  }
7423
- return matrix[0][0];
7550
+ return arg[0][0];
7551
+ }
7552
+ function isSingleElementMatrix(matrix) {
7553
+ return matrix.length === 1 && matrix[0].length === 1;
7554
+ }
7555
+ function isMultipleElementMatrix(arg) {
7556
+ return isMatrix(arg) && !isSingleElementMatrix(arg);
7424
7557
  }
7425
7558
 
7426
7559
  function assertSameNumberOfElements(...args) {
@@ -15264,7 +15397,7 @@ const FILTER = {
15264
15397
  }
15265
15398
  return mode === "row" ? transposeMatrix(result) : result;
15266
15399
  },
15267
- isExported: true,
15400
+ isExported: false,
15268
15401
  };
15269
15402
  // -----------------------------------------------------------------------------
15270
15403
  // SORT
@@ -18395,16 +18528,23 @@ const FALSE = {
18395
18528
  const IF = {
18396
18529
  description: _t("Returns value depending on logical expression."),
18397
18530
  args: [
18398
- 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.")),
18399
- arg("value_if_true (any)", _t("The value the function returns if logical_expression is TRUE.")),
18400
- arg("value_if_false (any, default=FALSE)", _t("The value the function returns if logical_expression is FALSE.")),
18531
+ 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.")),
18532
+ arg("value_if_true (any, range)", _t("The value the function returns if logical_expression is TRUE.")),
18533
+ arg("value_if_false (any, range, default=FALSE)", _t("The value the function returns if logical_expression is FALSE.")),
18401
18534
  ],
18402
18535
  compute: function (logicalExpression, valueIfTrue, valueIfFalse) {
18403
- const result = toBoolean(logicalExpression?.value) ? valueIfTrue : valueIfFalse;
18536
+ if (isMultipleElementMatrix(logicalExpression)) {
18537
+ return applyVectorization(IF.compute, [logicalExpression, valueIfTrue, valueIfFalse]);
18538
+ }
18539
+ let result = toBoolean(toScalar(logicalExpression)) ? valueIfTrue : valueIfFalse;
18540
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
18541
+ if (!isMultipleElementMatrix(result)) {
18542
+ result = toScalar(result);
18543
+ }
18404
18544
  if (result === undefined) {
18405
18545
  return { value: "" };
18406
18546
  }
18407
- if (result.value === null) {
18547
+ if (!isMatrix(result) && result.value === null) {
18408
18548
  return { ...result, value: "" };
18409
18549
  }
18410
18550
  return result;
@@ -18417,15 +18557,22 @@ const IF = {
18417
18557
  const IFERROR = {
18418
18558
  description: _t("Value if it is not an error, otherwise 2nd argument."),
18419
18559
  args: [
18420
- arg("value (any)", _t("The value to return if value itself is not an error.")),
18421
- arg(`value_if_error (any, default="empty")`, _t("The value the function returns if value is an error.")),
18560
+ arg("value (any, range)", _t("The value to return if value itself is not an error.")),
18561
+ arg(`value_if_error (any, range, default="empty")`, _t("The value the function returns if value is an error.")),
18422
18562
  ],
18423
- compute: function (value, valueIfError = { value: "" }) {
18424
- const result = isEvaluationError(value?.value) ? valueIfError : value;
18563
+ compute: function (value, valueIfError) {
18564
+ if (isMultipleElementMatrix(value)) {
18565
+ return applyVectorization(IFERROR.compute, [value, valueIfError]);
18566
+ }
18567
+ let result = isEvaluationError(toScalar(value)?.value) ? valueIfError : value;
18568
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
18569
+ if (!isMultipleElementMatrix(result)) {
18570
+ result = toScalar(result);
18571
+ }
18425
18572
  if (result === undefined) {
18426
18573
  return { value: "" };
18427
18574
  }
18428
- if (result.value === null) {
18575
+ if (!isMatrix(result) && result.value === null) {
18429
18576
  return { ...result, value: "" };
18430
18577
  }
18431
18578
  return result;
@@ -18438,15 +18585,22 @@ const IFERROR = {
18438
18585
  const IFNA = {
18439
18586
  description: _t("Value if it is not an #N/A error, otherwise 2nd argument."),
18440
18587
  args: [
18441
- arg("value (any)", _t("The value to return if value itself is not #N/A an error.")),
18442
- arg(`value_if_error (any, default="empty")`, _t("The value the function returns if value is an #N/A error.")),
18588
+ arg("value (any, range)", _t("The value to return if value itself is not #N/A an error.")),
18589
+ arg(`value_if_error (any, range, default="empty")`, _t("The value the function returns if value is an #N/A error.")),
18443
18590
  ],
18444
- compute: function (value, valueIfError = { value: "" }) {
18445
- const result = value?.value === CellErrorType.NotAvailable ? valueIfError : value;
18591
+ compute: function (value, valueIfError) {
18592
+ if (isMultipleElementMatrix(value)) {
18593
+ return applyVectorization(IFNA.compute, [value, valueIfError]);
18594
+ }
18595
+ let result = toScalar(value)?.value === CellErrorType.NotAvailable ? valueIfError : value;
18596
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
18597
+ if (!isMultipleElementMatrix(result)) {
18598
+ result = toScalar(result);
18599
+ }
18446
18600
  if (result === undefined) {
18447
18601
  return { value: "" };
18448
18602
  }
18449
- if (result.value === null) {
18603
+ if (!isMatrix(result) && result.value === null) {
18450
18604
  return { ...result, value: "" };
18451
18605
  }
18452
18606
  return result;
@@ -18459,23 +18613,31 @@ const IFNA = {
18459
18613
  const IFS = {
18460
18614
  description: _t("Returns a value depending on multiple logical expressions."),
18461
18615
  args: [
18462
- 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.")),
18463
- arg("value1 (any)", _t("The returned value if condition1 is TRUE.")),
18464
- arg("condition2 (boolean, any, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
18465
- arg("value2 (any, repeating)", _t("Additional values to be returned if their corresponding conditions are TRUE.")),
18616
+ 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.")),
18617
+ arg("value1 (any, range)", _t("The returned value if condition1 is TRUE.")),
18618
+ arg("condition2 (boolean, any, range, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
18619
+ arg("value2 (any, range, repeating)", _t("Additional values to be returned if their corresponding conditions are TRUE.")),
18466
18620
  ],
18467
18621
  compute: function (...values) {
18468
18622
  assert(() => values.length % 2 === 0, _t("Wrong number of arguments. Expected an even number of arguments."));
18469
- for (let n = 0; n < values.length - 1; n += 2) {
18470
- if (toBoolean(values[n]?.value)) {
18471
- const result = values[n + 1];
18472
- if (result === undefined) {
18623
+ while (values.length > 0) {
18624
+ if (isMultipleElementMatrix(values[0])) {
18625
+ return applyVectorization(IFS.compute, values);
18626
+ }
18627
+ const condition = toBoolean(toScalar(values.shift()));
18628
+ let valueIfTrue = values.shift();
18629
+ if (condition) {
18630
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
18631
+ if (!isMultipleElementMatrix(valueIfTrue)) {
18632
+ valueIfTrue = toScalar(valueIfTrue);
18633
+ }
18634
+ if (valueIfTrue === undefined) {
18473
18635
  return { value: "" };
18474
18636
  }
18475
- if (result.value === null) {
18476
- return { ...result, value: "" };
18637
+ if (!isMatrix(valueIfTrue) && valueIfTrue.value === null) {
18638
+ return { ...valueIfTrue, value: "" };
18477
18639
  }
18478
- return result;
18640
+ return valueIfTrue;
18479
18641
  }
18480
18642
  }
18481
18643
  return new EvaluationError(_t("No match."));
@@ -20137,7 +20299,7 @@ const SPLIT = {
20137
20299
  }
20138
20300
  return transposeMatrix([result]);
20139
20301
  },
20140
- isExported: true,
20302
+ isExported: false,
20141
20303
  };
20142
20304
  // -----------------------------------------------------------------------------
20143
20305
  // SUBSTITUTE
@@ -20330,86 +20492,21 @@ for (let category of categories) {
20330
20492
  functionRegistry.add(name, { isExported: false, ...addDescr });
20331
20493
  }
20332
20494
  }
20333
- const notAvailableError = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
20495
+ //------------------------------------------------------------------------------
20496
+ // CREATE COMPUTE FUNCTION
20497
+ //------------------------------------------------------------------------------
20334
20498
  function createComputeFunction(descr, functionName) {
20335
20499
  function vectorizedCompute(...args) {
20336
- let countVectorizableCol = 1;
20337
- let countVectorizableRow = 1;
20338
- let vectorizableColLimit = Infinity;
20339
- let vectorizableRowLimit = Infinity;
20340
- let vectorArgsType = undefined;
20341
- //#region Compute vectorisation limits
20500
+ const acceptToVectorize = [];
20342
20501
  for (let i = 0; i < args.length; i++) {
20343
20502
  const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
20344
20503
  const arg = args[i];
20345
- if (isMatrix(arg) && !argDefinition.acceptMatrix) {
20346
- // if argDefinition does not accept a matrix but arg is still a matrix
20347
- // --> triggers the arguments vectorization
20348
- const nColumns = arg.length;
20349
- const nRows = arg[0].length;
20350
- if (nColumns !== 1 || nRows !== 1) {
20351
- vectorArgsType ??= new Array(args.length);
20352
- if (nColumns !== 1 && nRows !== 1) {
20353
- vectorArgsType[i] = "matrix";
20354
- countVectorizableCol = Math.max(countVectorizableCol, nColumns);
20355
- countVectorizableRow = Math.max(countVectorizableRow, nRows);
20356
- vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
20357
- vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
20358
- }
20359
- else if (nColumns !== 1) {
20360
- vectorArgsType[i] = "horizontal";
20361
- countVectorizableCol = Math.max(countVectorizableCol, nColumns);
20362
- vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
20363
- }
20364
- else if (nRows !== 1) {
20365
- vectorArgsType[i] = "vertical";
20366
- countVectorizableRow = Math.max(countVectorizableRow, nRows);
20367
- vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
20368
- }
20369
- }
20370
- else {
20371
- args[i] = arg[0][0];
20372
- }
20373
- }
20374
20504
  if (!isMatrix(arg) && argDefinition.acceptMatrixOnly) {
20375
20505
  throw new BadExpressionError(_t("Function %s expects the parameter '%s' to be reference to a cell or range.", functionName, (i + 1).toString()));
20376
20506
  }
20507
+ acceptToVectorize.push(!argDefinition.acceptMatrix);
20377
20508
  }
20378
- //#endregion
20379
- if (countVectorizableCol === 1 && countVectorizableRow === 1) {
20380
- // either this function is not vectorized or it ends up with a 1x1 dimension
20381
- return errorHandlingCompute.apply(this, args);
20382
- }
20383
- const getArgOffset = (i, j) => args.map((arg, index) => {
20384
- switch (vectorArgsType?.[index]) {
20385
- case "matrix":
20386
- return arg[i][j];
20387
- case "horizontal":
20388
- return arg[i][0];
20389
- case "vertical":
20390
- return arg[0][j];
20391
- case undefined:
20392
- return arg;
20393
- }
20394
- });
20395
- return generateMatrix(countVectorizableCol, countVectorizableRow, (col, row) => {
20396
- if (col > vectorizableColLimit - 1 || row > vectorizableRowLimit - 1) {
20397
- return notAvailableError;
20398
- }
20399
- const singleCellComputeResult = errorHandlingCompute.apply(this, getArgOffset(col, row));
20400
- // In the case where the user tries to vectorize arguments of an array formula, we will get an
20401
- // array for every combination of the vectorized arguments, which will lead to a 3D matrix and
20402
- // we won't be able to return the values.
20403
- // In this case, we keep the first element of each spreading part, just as Excel does, and
20404
- // create an array with these parts.
20405
- // For exemple, we have MUNIT(x) that return an unitary matrix of x*x. If we use it with a
20406
- // range, like MUNIT(A1:A2), we will get two unitary matrices (one for the value in A1 and one
20407
- // for the value in A2). In this case, we will simply take the first value of each matrix and
20408
- // return the array [First value of MUNIT(A1), First value of MUNIT(A2)].
20409
- return isMatrix(singleCellComputeResult)
20410
- ? singleCellComputeResult[0][0]
20411
- : singleCellComputeResult;
20412
- });
20509
+ return applyVectorization(errorHandlingCompute.bind(this), args, acceptToVectorize);
20413
20510
  }
20414
20511
  function errorHandlingCompute(...args) {
20415
20512
  for (let i = 0; i < args.length; i++) {
@@ -21254,7 +21351,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21254
21351
  proposals &&
21255
21352
  !["ARG_SEPARATOR", "LEFT_PAREN", "OPERATOR"].includes(tokenAtCursor.type)) {
21256
21353
  const filteredProposals = fuzzyLookup(searchTerm, proposals, (p) => p.fuzzySearchKey || p.text);
21257
- if (!exactMatch || filteredProposals.length > 1) {
21354
+ if (!exactMatch || filteredProposals.length) {
21258
21355
  proposals = filteredProposals;
21259
21356
  }
21260
21357
  }
@@ -41346,12 +41443,13 @@ class StandaloneComposerStore extends AbstractComposerStore {
41346
41443
  return providersDefinitions;
41347
41444
  }
41348
41445
  getComposerContent() {
41446
+ let content = this._currentContent;
41349
41447
  if (this.editionMode === "inactive") {
41350
41448
  // References in the content might not be linked to the current active sheet
41351
41449
  // We here force the sheet name prefix for all references that are not in
41352
41450
  // the current active sheet
41353
41451
  const defaultRangeSheetId = this.args().defaultRangeSheetId;
41354
- return rangeTokenize(this.args().content)
41452
+ content = rangeTokenize(this.args().content)
41355
41453
  .map((token) => {
41356
41454
  if (token.type === "REFERENCE") {
41357
41455
  const range = this.getters.getRangeFromSheetXC(defaultRangeSheetId, token.value);
@@ -41361,7 +41459,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
41361
41459
  })
41362
41460
  .join("");
41363
41461
  }
41364
- return this._currentContent;
41462
+ return localizeContent(content, this.getters.getLocale());
41365
41463
  }
41366
41464
  stopEdition() {
41367
41465
  this._stopEdition();
@@ -45107,6 +45205,9 @@ class PivotMeasureEditor extends owl.Component {
45107
45205
  }
45108
45206
  return undefined;
45109
45207
  }
45208
+ get isCalculatedMeasureInvalid() {
45209
+ return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
45210
+ }
45110
45211
  }
45111
45212
 
45112
45213
  css /* scss */ `
@@ -46117,7 +46218,9 @@ function compareDimensionValues(dimension, a, b) {
46117
46218
  return dimension.order === "asc" ? -1 : 1;
46118
46219
  }
46119
46220
  if (dimension.type === "integer" || dimension.type === "datetime") {
46120
- return dimension.order === "asc" ? Number(a) - Number(b) : Number(b) - Number(a);
46221
+ return dimension.order === "asc"
46222
+ ? toNumber(a, DEFAULT_LOCALE) - toNumber(b, DEFAULT_LOCALE)
46223
+ : toNumber(b, DEFAULT_LOCALE) - toNumber(a, DEFAULT_LOCALE);
46121
46224
  }
46122
46225
  return dimension.order === "asc" ? a.localeCompare(b) : b.localeCompare(a);
46123
46226
  }
@@ -48883,8 +48986,7 @@ class CellComposerStore extends AbstractComposerStore {
48883
48986
  if (!spreader) {
48884
48987
  return undefined;
48885
48988
  }
48886
- const cell = this.getters.getCell(spreader);
48887
- return cell?.content;
48989
+ return this.getters.getCellText(spreader, { showFormula: true });
48888
48990
  }
48889
48991
  get currentEditedCell() {
48890
48992
  return {
@@ -51695,6 +51797,7 @@ function useGridDrawing(refName, model, canvasSize) {
51695
51797
  const friction = 0.95;
51696
51798
  const verticalScrollFactor = 1;
51697
51799
  const horizontalScrollFactor = 1;
51800
+ const resetTimeoutDuration = 100;
51698
51801
  function useTouchScroll(ref, updateScroll, canMoveUp) {
51699
51802
  let lastX = 0;
51700
51803
  let lastY = 0;
@@ -51702,6 +51805,7 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
51702
51805
  let velocityY = 0;
51703
51806
  let isMouseDown = false;
51704
51807
  let lastTime = 0;
51808
+ let resetTimeout = null;
51705
51809
  useRefListener(ref, "touchstart", onTouchStart, { capture: false });
51706
51810
  useRefListener(ref, "touchmove", onTouchMove, { capture: false });
51707
51811
  useRefListener(ref, "touchend", onTouchEnd, { capture: false });
@@ -51714,6 +51818,10 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
51714
51818
  function onTouchMove(event) {
51715
51819
  if (!isMouseDown)
51716
51820
  return;
51821
+ if (resetTimeout) {
51822
+ clearTimeout(resetTimeout);
51823
+ resetTimeout = null;
51824
+ }
51717
51825
  const currentTime = Date.now();
51718
51826
  const { clientX, clientY } = event.touches[0];
51719
51827
  let deltaX = lastX - clientX;
@@ -51730,6 +51838,10 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
51730
51838
  }
51731
51839
  event.stopPropagation();
51732
51840
  }
51841
+ resetTimeout = setTimeout(() => {
51842
+ velocityX = 0;
51843
+ velocityY = 0;
51844
+ }, resetTimeoutDuration);
51733
51845
  updateScroll(deltaX * horizontalScrollFactor, deltaY * verticalScrollFactor);
51734
51846
  }
51735
51847
  function onTouchEnd(ev) {
@@ -60479,10 +60591,9 @@ class Evaluator {
60479
60591
  return this.evaluatedCells.keysForSheet(sheetId);
60480
60592
  }
60481
60593
  getArrayFormulaSpreadingOn(position) {
60482
- const hasArrayFormulaResult = this.getEvaluatedCell(position).type !== CellValueType.empty &&
60483
- !this.getters.getCell(position)?.isFormula;
60484
- if (!hasArrayFormulaResult) {
60485
- return this.spreadingRelations.isArrayFormula(position) ? position : undefined;
60594
+ const isEmpty = this.getEvaluatedCell(position).type === CellValueType.empty;
60595
+ if (isEmpty) {
60596
+ return undefined;
60486
60597
  }
60487
60598
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
60488
60599
  return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
@@ -76326,6 +76437,6 @@ exports.tokenColors = tokenColors;
76326
76437
  exports.tokenize = tokenize;
76327
76438
 
76328
76439
 
76329
- __info__.version = "18.1.20";
76330
- __info__.date = "2025-05-13T17:52:28.174Z";
76331
- __info__.hash = "3e43a46";
76440
+ __info__.version = "18.1.22";
76441
+ __info__.date = "2025-05-26T12:35:56.145Z";
76442
+ __info__.hash = "ff4b0ba";