@odoo/o-spreadsheet 18.2.12 → 18.2.14

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.2.12
6
- * @date 2025-05-13T17:52:23.989Z
7
- * @hash ba2ba9b
5
+ * @version 18.2.14
6
+ * @date 2025-05-26T12:35:51.528Z
7
+ * @hash db90fca
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -1618,18 +1618,53 @@
1618
1618
  let result = 0;
1619
1619
  const l = letters.length;
1620
1620
  for (let i = 0; i < l; i++) {
1621
- const charCode = letters.charCodeAt(i);
1622
- const colIndex = charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1621
+ const colIndex = charToNumber(letters[i]);
1623
1622
  result = result * 26 + colIndex;
1624
1623
  }
1625
1624
  return result - 1;
1626
1625
  }
1626
+ function charToNumber(char) {
1627
+ const charCode = char.charCodeAt(0);
1628
+ return charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1629
+ }
1627
1630
  function isCharALetter(char) {
1628
1631
  return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
1629
1632
  }
1630
1633
  function isCharADigit(char) {
1631
1634
  return char >= "0" && char <= "9";
1632
1635
  }
1636
+ // we limit the max column to 3 letters and max row to 7 digits for performance reasons
1637
+ const MAX_COL = lettersToNumber("ZZZ");
1638
+ const MAX_ROW = 9999998;
1639
+ function consumeSpaces(chars) {
1640
+ while (chars.current === " ") {
1641
+ chars.advanceBy(1);
1642
+ }
1643
+ }
1644
+ function consumeLetters(chars) {
1645
+ if (chars.current === "$")
1646
+ chars.advanceBy(1);
1647
+ if (!chars.current || !isCharALetter(chars.current)) {
1648
+ return -1;
1649
+ }
1650
+ let colCoordinate = 0;
1651
+ while (chars.current && isCharALetter(chars.current)) {
1652
+ colCoordinate = colCoordinate * 26 + charToNumber(chars.shift());
1653
+ }
1654
+ return colCoordinate;
1655
+ }
1656
+ function consumeDigits(chars) {
1657
+ if (chars.current === "$")
1658
+ chars.advanceBy(1);
1659
+ if (!chars.current || !isCharADigit(chars.current)) {
1660
+ return -1;
1661
+ }
1662
+ let num = 0;
1663
+ while (chars.current && isCharADigit(chars.current)) {
1664
+ num = num * 10 + Number(chars.shift());
1665
+ }
1666
+ return num;
1667
+ }
1633
1668
  /**
1634
1669
  * Convert a "XC" coordinate to cartesian coordinates.
1635
1670
  *
@@ -1640,33 +1675,17 @@
1640
1675
  * Note: it also accepts lowercase coordinates, but not fixed references
1641
1676
  */
1642
1677
  function toCartesian(xc) {
1643
- xc = xc.trim();
1644
- let letterPart = "";
1645
- let numberPart = "";
1646
- let i = 0;
1647
- // Process letter part
1648
- if (xc[i] === "$")
1649
- i++;
1650
- while (i < xc.length && isCharALetter(xc[i])) {
1651
- letterPart += xc[i++];
1652
- }
1653
- if (letterPart.length === 0 || letterPart.length > 3) {
1654
- // limit to max 3 letters for performance reasons
1678
+ const chars = new TokenizingChars(xc);
1679
+ consumeSpaces(chars);
1680
+ const letterPart = consumeLetters(chars);
1681
+ if (letterPart === -1 || !chars.current) {
1655
1682
  throw new Error(`Invalid cell description: ${xc}`);
1656
1683
  }
1657
- // Process number part
1658
- if (xc[i] === "$")
1659
- i++;
1660
- while (i < xc.length && isCharADigit(xc[i])) {
1661
- numberPart += xc[i++];
1662
- }
1663
- if (i !== xc.length || numberPart.length === 0 || numberPart.length > 7) {
1664
- // limit to max 7 numbers for performance reasons
1665
- throw new Error(`Invalid cell description: ${xc}`);
1666
- }
1667
- const col = lettersToNumber(letterPart);
1668
- const row = Number(numberPart) - 1;
1669
- if (isNaN(row)) {
1684
+ const num = consumeDigits(chars);
1685
+ consumeSpaces(chars);
1686
+ const col = letterPart - 1;
1687
+ const row = num - 1;
1688
+ if (!chars.isOver() || col > MAX_COL || row > MAX_ROW) {
1670
1689
  throw new Error(`Invalid cell description: ${xc}`);
1671
1690
  }
1672
1691
  return { col, row };
@@ -2078,67 +2097,6 @@
2078
2097
  }
2079
2098
  }
2080
2099
 
2081
- /** Reference of a cell (eg. A1, $B$5) */
2082
- const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2083
- // Same as above, but matches the exact string (nothing before or after)
2084
- const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2085
- /** Reference of a column header (eg. A, AB, $A) */
2086
- const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
2087
- /** Reference of a row header (eg. 1, $1) */
2088
- const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
2089
- /** Reference of a column (eg. A, $CA, Sheet1!B) */
2090
- const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2091
- /** Reference of a row (eg. 1, 59, Sheet1!9) */
2092
- const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
2093
- /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
2094
- const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
2095
- /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
2096
- const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
2097
- /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
2098
- const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
2099
- "(" +
2100
- [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
2101
- ")" +
2102
- /$/.source, "i");
2103
- /**
2104
- * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
2105
- */
2106
- function isColReference(xc) {
2107
- return colReference.test(xc);
2108
- }
2109
- /**
2110
- * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
2111
- */
2112
- function isRowReference(xc) {
2113
- return rowReference.test(xc);
2114
- }
2115
- function isColHeader(str) {
2116
- return colHeader.test(str);
2117
- }
2118
- function isRowHeader(str) {
2119
- return rowHeader.test(str);
2120
- }
2121
- /**
2122
- * Return true if the given xc is the reference of a single cell,
2123
- * without any specified sheet (e.g. A1)
2124
- */
2125
- function isSingleCellReference(xc) {
2126
- return singleCellReference.test(xc);
2127
- }
2128
- function splitReference(ref) {
2129
- if (!ref.includes("!")) {
2130
- return { xc: ref };
2131
- }
2132
- const parts = ref.split("!");
2133
- const xc = parts.pop();
2134
- const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
2135
- return { sheetName, xc };
2136
- }
2137
- /** Return a reference SheetName!xc from the given arguments */
2138
- function getFullReference(sheetName, xc) {
2139
- return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
2140
- }
2141
-
2142
2100
  /**
2143
2101
  * Convert from a cartesian reference to a Zone
2144
2102
  * The range boundaries will be kept in the same order as the
@@ -2156,63 +2114,55 @@
2156
2114
  *
2157
2115
  */
2158
2116
  function toZoneWithoutBoundaryChanges(xc) {
2159
- if (xc.includes("!")) {
2160
- xc = xc.split("!").at(-1);
2161
- }
2162
- if (xc.includes("$")) {
2163
- xc = xc.replaceAll("$", "");
2164
- }
2165
- let firstRangePart = "";
2166
- let secondRangePart;
2167
- if (xc.includes(":")) {
2168
- [firstRangePart, secondRangePart] = xc.split(":");
2169
- firstRangePart = firstRangePart.trim();
2170
- secondRangePart = secondRangePart.trim();
2171
- }
2172
- else {
2173
- firstRangePart = xc.trim();
2174
- }
2117
+ const chars = new TokenizingChars(xc);
2118
+ consumeSpaces(chars);
2119
+ const sheetSeparatorIndex = xc.indexOf("!");
2120
+ if (sheetSeparatorIndex !== -1) {
2121
+ chars.advanceBy(sheetSeparatorIndex + 1);
2122
+ }
2123
+ const leftLetters = consumeLetters(chars);
2124
+ const leftNumbers = consumeDigits(chars);
2175
2125
  let top, bottom, left, right;
2176
2126
  let fullCol = false;
2177
2127
  let fullRow = false;
2178
2128
  let hasHeader = false;
2179
- if (isColReference(firstRangePart)) {
2180
- left = right = lettersToNumber(firstRangePart);
2129
+ if (leftNumbers === -1) {
2130
+ left = right = leftLetters - 1;
2181
2131
  top = bottom = 0;
2182
2132
  fullCol = true;
2183
2133
  }
2184
- else if (isRowReference(firstRangePart)) {
2185
- top = bottom = parseInt(firstRangePart, 10) - 1;
2134
+ else if (leftLetters === -1) {
2135
+ top = bottom = leftNumbers - 1;
2186
2136
  left = right = 0;
2187
2137
  fullRow = true;
2188
2138
  }
2189
2139
  else {
2190
- const c = toCartesian(firstRangePart);
2191
- left = right = c.col;
2192
- top = bottom = c.row;
2140
+ left = right = leftLetters - 1;
2141
+ top = bottom = leftNumbers - 1;
2193
2142
  hasHeader = true;
2194
2143
  }
2195
- if (secondRangePart) {
2196
- if (isColReference(secondRangePart)) {
2197
- right = lettersToNumber(secondRangePart);
2144
+ consumeSpaces(chars);
2145
+ if (chars.current === ":") {
2146
+ chars.advanceBy(1);
2147
+ consumeSpaces(chars);
2148
+ const rightLetters = consumeLetters(chars);
2149
+ const rightNumbers = consumeDigits(chars);
2150
+ if (rightNumbers === -1) {
2151
+ right = rightLetters - 1;
2198
2152
  fullCol = true;
2199
2153
  }
2200
- else if (isRowReference(secondRangePart)) {
2201
- bottom = parseInt(secondRangePart, 10) - 1;
2154
+ else if (rightLetters === -1) {
2155
+ bottom = rightNumbers - 1;
2202
2156
  fullRow = true;
2203
2157
  }
2204
2158
  else {
2205
- const c = toCartesian(secondRangePart);
2206
- right = c.col;
2207
- bottom = c.row;
2159
+ right = rightLetters - 1;
2160
+ bottom = rightNumbers - 1;
2208
2161
  top = fullCol ? bottom : top;
2209
2162
  left = fullRow ? right : left;
2210
2163
  hasHeader = true;
2211
2164
  }
2212
2165
  }
2213
- if (fullCol && fullRow) {
2214
- throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2215
- }
2216
2166
  const zone = {
2217
2167
  top,
2218
2168
  left,
@@ -2241,7 +2191,16 @@
2241
2191
  */
2242
2192
  function toUnboundedZone(xc) {
2243
2193
  const zone = toZoneWithoutBoundaryChanges(xc);
2244
- return reorderZone(zone);
2194
+ const orderedZone = reorderZone(zone);
2195
+ const bottom = orderedZone.bottom;
2196
+ const right = orderedZone.right;
2197
+ if ((bottom !== undefined && bottom > MAX_ROW) || (right !== undefined && right > MAX_COL)) {
2198
+ throw new Error(`Range string out of bounds: ${xc}`); // limit the size of the zone for performance
2199
+ }
2200
+ if (bottom === undefined && right === undefined) {
2201
+ throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2202
+ }
2203
+ return orderedZone;
2245
2204
  }
2246
2205
  /**
2247
2206
  * Convert from a cartesian reference to a Zone.
@@ -4191,6 +4150,113 @@
4191
4150
  }
4192
4151
  return generateMatrix(matrix[0].length, matrix.length, (i, j) => matrix[j][i]);
4193
4152
  }
4153
+ /**
4154
+ * Enables a formula function to accept matrix or vector inputs instead of simple value, computing results across multiple dimensions.
4155
+ *
4156
+ * ```
4157
+ * / |‾ ‾| \ |‾ ‾|
4158
+ * | | [A] | | | compute(A, D, E), compute(A, D, F), compute(A, D, G) |
4159
+ * applyVectorization| compute, | [B], D, [E, F, G] | | <=> | compute(B, D, E), compute(B, D, F), compute(B, D, G) |
4160
+ * | | [C] | | | compute(C, D, E), compute(C, D, F), compute(C, D, G) |
4161
+ * \ |_ _| / |_ _|
4162
+ * ```
4163
+ *
4164
+ * By default, all arguments are vectorized. To control which arguments are vectorized,
4165
+ * pass an `acceptToVectorize` boolean array of the same length as `args`:
4166
+ * - `true` enables vectorization for that argument
4167
+ * - `false` disables vectorization for that argument
4168
+ *
4169
+ * For example, with `[true, true, false]` on previous example you get:
4170
+ *
4171
+ * ```
4172
+ * |‾ ‾|
4173
+ * | compute(A, D, [E, F, G]) |
4174
+ * | compute(B, D, [E, F, G]) |
4175
+ * | compute(C, D, [E, F, G]) |
4176
+ * |_ _|
4177
+ * ```
4178
+ *
4179
+ * @remarks
4180
+ * This helper is automatically applied (by default) to **all** `compute` functions
4181
+ * across the various spreadsheet formula modules:
4182
+ * - If an argument is declared **scalar** (not `"range"`), it is vectorized.
4183
+ * - If **all** arguments are declared **ranges**, no vectorization occurs.
4184
+ * - e.g. `SUM(A1:B2)` returns a 1×1 sum over the range.
4185
+ * - e.g. `COS(A1:B2)` over `A1:B2` returns a 2×2 element-wise result.
4186
+ * - For special behaviors (e.g. the `IF` function), you may declare all arguments
4187
+ * as ranges and invoke this helper directly within your `compute` implementation.
4188
+ */
4189
+ function applyVectorization(formula, args, acceptToVectorize = undefined) {
4190
+ let countVectorizedCol = 1;
4191
+ let countVectorizedRow = 1;
4192
+ let vectorizedColLimit = Infinity;
4193
+ let vectorizedRowLimit = Infinity;
4194
+ let vectorArgsType = undefined;
4195
+ for (let i = 0; i < args.length; i++) {
4196
+ const arg = args[i];
4197
+ if (isMatrix(arg) && (acceptToVectorize === undefined || acceptToVectorize[i])) {
4198
+ const nColumns = arg.length;
4199
+ const nRows = arg[0].length;
4200
+ if (nColumns !== 1 || nRows !== 1) {
4201
+ vectorArgsType ??= new Array(args.length);
4202
+ if (nColumns !== 1 && nRows !== 1) {
4203
+ vectorArgsType[i] = "matrix";
4204
+ countVectorizedCol = Math.max(countVectorizedCol, nColumns);
4205
+ countVectorizedRow = Math.max(countVectorizedRow, nRows);
4206
+ vectorizedColLimit = Math.min(vectorizedColLimit, nColumns);
4207
+ vectorizedRowLimit = Math.min(vectorizedRowLimit, nRows);
4208
+ }
4209
+ else if (nColumns !== 1) {
4210
+ vectorArgsType[i] = "horizontal";
4211
+ countVectorizedCol = Math.max(countVectorizedCol, nColumns);
4212
+ vectorizedColLimit = Math.min(vectorizedColLimit, nColumns);
4213
+ }
4214
+ else if (nRows !== 1) {
4215
+ vectorArgsType[i] = "vertical";
4216
+ countVectorizedRow = Math.max(countVectorizedRow, nRows);
4217
+ vectorizedRowLimit = Math.min(vectorizedRowLimit, nRows);
4218
+ }
4219
+ }
4220
+ else {
4221
+ args[i] = arg[0][0];
4222
+ }
4223
+ }
4224
+ }
4225
+ if (countVectorizedCol === 1 && countVectorizedRow === 1) {
4226
+ // either this function is not vectorized or it ends up with a 1x1 dimension
4227
+ return formula(...args);
4228
+ }
4229
+ const getArgOffset = (i, j) => args.map((arg, index) => {
4230
+ switch (vectorArgsType?.[index]) {
4231
+ case "matrix":
4232
+ return arg[i][j];
4233
+ case "horizontal":
4234
+ return arg[i][0];
4235
+ case "vertical":
4236
+ return arg[0][j];
4237
+ case undefined:
4238
+ return arg;
4239
+ }
4240
+ });
4241
+ return generateMatrix(countVectorizedCol, countVectorizedRow, (col, row) => {
4242
+ if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) {
4243
+ return new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
4244
+ }
4245
+ const singleCellComputeResult = formula(...getArgOffset(col, row));
4246
+ // In the case where the user tries to vectorize arguments of an array formula, we will get an
4247
+ // array for every combination of the vectorized arguments, which will lead to a 3D matrix and
4248
+ // we won't be able to return the values.
4249
+ // In this case, we keep the first element of each spreading part, just as Excel does, and
4250
+ // create an array with these parts.
4251
+ // For exemple, we have MUNIT(x) that return an unitary matrix of x*x. If we use it with a
4252
+ // range, like MUNIT(A1:A2), we will get two unitary matrices (one for the value in A1 and one
4253
+ // for the value in A2). In this case, we will simply take the first value of each matrix and
4254
+ // return the array [First value of MUNIT(A1), First value of MUNIT(A2)].
4255
+ return isMatrix(singleCellComputeResult)
4256
+ ? singleCellComputeResult[0][0]
4257
+ : singleCellComputeResult;
4258
+ });
4259
+ }
4194
4260
  // -----------------------------------------------------------------------------
4195
4261
  // CONDITIONAL EXPLORE FUNCTIONS
4196
4262
  // -----------------------------------------------------------------------------
@@ -5975,6 +6041,67 @@
5975
6041
  return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
5976
6042
  }
5977
6043
 
6044
+ /** Reference of a cell (eg. A1, $B$5) */
6045
+ const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
6046
+ // Same as above, but matches the exact string (nothing before or after)
6047
+ const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
6048
+ /** Reference of a column header (eg. A, AB, $A) */
6049
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
6050
+ /** Reference of a row header (eg. 1, $1) */
6051
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
6052
+ /** Reference of a column (eg. A, $CA, Sheet1!B) */
6053
+ const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
6054
+ /** Reference of a row (eg. 1, 59, Sheet1!9) */
6055
+ const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
6056
+ /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
6057
+ const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
6058
+ /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
6059
+ const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
6060
+ /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
6061
+ const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
6062
+ "(" +
6063
+ [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
6064
+ ")" +
6065
+ /$/.source, "i");
6066
+ /**
6067
+ * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
6068
+ */
6069
+ function isColReference(xc) {
6070
+ return colReference.test(xc);
6071
+ }
6072
+ /**
6073
+ * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
6074
+ */
6075
+ function isRowReference(xc) {
6076
+ return rowReference.test(xc);
6077
+ }
6078
+ function isColHeader(str) {
6079
+ return colHeader.test(str);
6080
+ }
6081
+ function isRowHeader(str) {
6082
+ return rowHeader.test(str);
6083
+ }
6084
+ /**
6085
+ * Return true if the given xc is the reference of a single cell,
6086
+ * without any specified sheet (e.g. A1)
6087
+ */
6088
+ function isSingleCellReference(xc) {
6089
+ return singleCellReference.test(xc);
6090
+ }
6091
+ function splitReference(ref) {
6092
+ if (!ref.includes("!")) {
6093
+ return { xc: ref };
6094
+ }
6095
+ const parts = ref.split("!");
6096
+ const xc = parts.pop();
6097
+ const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
6098
+ return { sheetName, xc };
6099
+ }
6100
+ /** Return a reference SheetName!xc from the given arguments */
6101
+ function getFullReference(sheetName, xc) {
6102
+ return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
6103
+ }
6104
+
5978
6105
  class RangeImpl {
5979
6106
  getSheetSize;
5980
6107
  _zone;
@@ -7421,14 +7548,20 @@
7421
7548
  /**
7422
7549
  * Return the input if it's a scalar or the first element of the input if it's a matrix.
7423
7550
  */
7424
- function toScalar(matrix) {
7425
- if (!isMatrix(matrix)) {
7426
- return matrix;
7551
+ function toScalar(arg) {
7552
+ if (!isMatrix(arg)) {
7553
+ return arg;
7427
7554
  }
7428
- if (matrix.length !== 1 || matrix[0].length !== 1) {
7555
+ if (!isSingleElementMatrix(arg)) {
7429
7556
  throw new EvaluationError(_t("The value should be a scalar or a 1x1 matrix"));
7430
7557
  }
7431
- return matrix[0][0];
7558
+ return arg[0][0];
7559
+ }
7560
+ function isSingleElementMatrix(matrix) {
7561
+ return matrix.length === 1 && matrix[0].length === 1;
7562
+ }
7563
+ function isMultipleElementMatrix(arg) {
7564
+ return isMatrix(arg) && !isSingleElementMatrix(arg);
7432
7565
  }
7433
7566
 
7434
7567
  function assertSameNumberOfElements(...args) {
@@ -15436,7 +15569,7 @@ stores.inject(MyMetaStore, storeInstance);
15436
15569
  }
15437
15570
  return mode === "row" ? transposeMatrix(result) : result;
15438
15571
  },
15439
- isExported: true,
15572
+ isExported: false,
15440
15573
  };
15441
15574
  // -----------------------------------------------------------------------------
15442
15575
  // SORT
@@ -18567,16 +18700,23 @@ stores.inject(MyMetaStore, storeInstance);
18567
18700
  const IF = {
18568
18701
  description: _t("Returns value depending on logical expression."),
18569
18702
  args: [
18570
- 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.")),
18571
- arg("value_if_true (any)", _t("The value the function returns if logical_expression is TRUE.")),
18572
- arg("value_if_false (any, default=FALSE)", _t("The value the function returns if logical_expression is FALSE.")),
18703
+ 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.")),
18704
+ arg("value_if_true (any, range)", _t("The value the function returns if logical_expression is TRUE.")),
18705
+ arg("value_if_false (any, range, default=FALSE)", _t("The value the function returns if logical_expression is FALSE.")),
18573
18706
  ],
18574
18707
  compute: function (logicalExpression, valueIfTrue, valueIfFalse) {
18575
- const result = toBoolean(logicalExpression?.value) ? valueIfTrue : valueIfFalse;
18708
+ if (isMultipleElementMatrix(logicalExpression)) {
18709
+ return applyVectorization(IF.compute, [logicalExpression, valueIfTrue, valueIfFalse]);
18710
+ }
18711
+ let result = toBoolean(toScalar(logicalExpression)) ? valueIfTrue : valueIfFalse;
18712
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
18713
+ if (!isMultipleElementMatrix(result)) {
18714
+ result = toScalar(result);
18715
+ }
18576
18716
  if (result === undefined) {
18577
18717
  return { value: "" };
18578
18718
  }
18579
- if (result.value === null) {
18719
+ if (!isMatrix(result) && result.value === null) {
18580
18720
  return { ...result, value: "" };
18581
18721
  }
18582
18722
  return result;
@@ -18589,15 +18729,22 @@ stores.inject(MyMetaStore, storeInstance);
18589
18729
  const IFERROR = {
18590
18730
  description: _t("Value if it is not an error, otherwise 2nd argument."),
18591
18731
  args: [
18592
- arg("value (any)", _t("The value to return if value itself is not an error.")),
18593
- arg(`value_if_error (any, default="empty")`, _t("The value the function returns if value is an error.")),
18732
+ arg("value (any, range)", _t("The value to return if value itself is not an error.")),
18733
+ arg(`value_if_error (any, range, default="empty")`, _t("The value the function returns if value is an error.")),
18594
18734
  ],
18595
- compute: function (value, valueIfError = { value: "" }) {
18596
- const result = isEvaluationError(value?.value) ? valueIfError : value;
18735
+ compute: function (value, valueIfError) {
18736
+ if (isMultipleElementMatrix(value)) {
18737
+ return applyVectorization(IFERROR.compute, [value, valueIfError]);
18738
+ }
18739
+ let result = isEvaluationError(toScalar(value)?.value) ? valueIfError : value;
18740
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
18741
+ if (!isMultipleElementMatrix(result)) {
18742
+ result = toScalar(result);
18743
+ }
18597
18744
  if (result === undefined) {
18598
18745
  return { value: "" };
18599
18746
  }
18600
- if (result.value === null) {
18747
+ if (!isMatrix(result) && result.value === null) {
18601
18748
  return { ...result, value: "" };
18602
18749
  }
18603
18750
  return result;
@@ -18610,15 +18757,22 @@ stores.inject(MyMetaStore, storeInstance);
18610
18757
  const IFNA = {
18611
18758
  description: _t("Value if it is not an #N/A error, otherwise 2nd argument."),
18612
18759
  args: [
18613
- arg("value (any)", _t("The value to return if value itself is not #N/A an error.")),
18614
- arg(`value_if_error (any, default="empty")`, _t("The value the function returns if value is an #N/A error.")),
18760
+ arg("value (any, range)", _t("The value to return if value itself is not #N/A an error.")),
18761
+ arg(`value_if_error (any, range, default="empty")`, _t("The value the function returns if value is an #N/A error.")),
18615
18762
  ],
18616
- compute: function (value, valueIfError = { value: "" }) {
18617
- const result = value?.value === CellErrorType.NotAvailable ? valueIfError : value;
18763
+ compute: function (value, valueIfError) {
18764
+ if (isMultipleElementMatrix(value)) {
18765
+ return applyVectorization(IFNA.compute, [value, valueIfError]);
18766
+ }
18767
+ let result = toScalar(value)?.value === CellErrorType.NotAvailable ? valueIfError : value;
18768
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
18769
+ if (!isMultipleElementMatrix(result)) {
18770
+ result = toScalar(result);
18771
+ }
18618
18772
  if (result === undefined) {
18619
18773
  return { value: "" };
18620
18774
  }
18621
- if (result.value === null) {
18775
+ if (!isMatrix(result) && result.value === null) {
18622
18776
  return { ...result, value: "" };
18623
18777
  }
18624
18778
  return result;
@@ -18631,23 +18785,31 @@ stores.inject(MyMetaStore, storeInstance);
18631
18785
  const IFS = {
18632
18786
  description: _t("Returns a value depending on multiple logical expressions."),
18633
18787
  args: [
18634
- 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.")),
18635
- arg("value1 (any)", _t("The returned value if condition1 is TRUE.")),
18636
- arg("condition2 (boolean, any, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
18637
- arg("value2 (any, repeating)", _t("Additional values to be returned if their corresponding conditions are TRUE.")),
18788
+ 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.")),
18789
+ arg("value1 (any, range)", _t("The returned value if condition1 is TRUE.")),
18790
+ arg("condition2 (boolean, any, range, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
18791
+ arg("value2 (any, range, repeating)", _t("Additional values to be returned if their corresponding conditions are TRUE.")),
18638
18792
  ],
18639
18793
  compute: function (...values) {
18640
18794
  assert(() => values.length % 2 === 0, _t("Wrong number of arguments. Expected an even number of arguments."));
18641
- for (let n = 0; n < values.length - 1; n += 2) {
18642
- if (toBoolean(values[n]?.value)) {
18643
- const result = values[n + 1];
18644
- if (result === undefined) {
18795
+ while (values.length > 0) {
18796
+ if (isMultipleElementMatrix(values[0])) {
18797
+ return applyVectorization(IFS.compute, values);
18798
+ }
18799
+ const condition = toBoolean(toScalar(values.shift()));
18800
+ let valueIfTrue = values.shift();
18801
+ if (condition) {
18802
+ // useful for interpreting empty cell references as empty strings. But must be removed to make empty cell references equal to zero
18803
+ if (!isMultipleElementMatrix(valueIfTrue)) {
18804
+ valueIfTrue = toScalar(valueIfTrue);
18805
+ }
18806
+ if (valueIfTrue === undefined) {
18645
18807
  return { value: "" };
18646
18808
  }
18647
- if (result.value === null) {
18648
- return { ...result, value: "" };
18809
+ if (!isMatrix(valueIfTrue) && valueIfTrue.value === null) {
18810
+ return { ...valueIfTrue, value: "" };
18649
18811
  }
18650
- return result;
18812
+ return valueIfTrue;
18651
18813
  }
18652
18814
  }
18653
18815
  return new EvaluationError(_t("No match."));
@@ -20309,7 +20471,7 @@ stores.inject(MyMetaStore, storeInstance);
20309
20471
  }
20310
20472
  return transposeMatrix([result]);
20311
20473
  },
20312
- isExported: true,
20474
+ isExported: false,
20313
20475
  };
20314
20476
  // -----------------------------------------------------------------------------
20315
20477
  // SUBSTITUTE
@@ -20502,86 +20664,21 @@ stores.inject(MyMetaStore, storeInstance);
20502
20664
  functionRegistry.add(name, { isExported: false, ...addDescr });
20503
20665
  }
20504
20666
  }
20505
- const notAvailableError = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
20667
+ //------------------------------------------------------------------------------
20668
+ // CREATE COMPUTE FUNCTION
20669
+ //------------------------------------------------------------------------------
20506
20670
  function createComputeFunction(descr, functionName) {
20507
20671
  function vectorizedCompute(...args) {
20508
- let countVectorizableCol = 1;
20509
- let countVectorizableRow = 1;
20510
- let vectorizableColLimit = Infinity;
20511
- let vectorizableRowLimit = Infinity;
20512
- let vectorArgsType = undefined;
20513
- //#region Compute vectorisation limits
20672
+ const acceptToVectorize = [];
20514
20673
  for (let i = 0; i < args.length; i++) {
20515
20674
  const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
20516
20675
  const arg = args[i];
20517
- if (isMatrix(arg) && !argDefinition.acceptMatrix) {
20518
- // if argDefinition does not accept a matrix but arg is still a matrix
20519
- // --> triggers the arguments vectorization
20520
- const nColumns = arg.length;
20521
- const nRows = arg[0].length;
20522
- if (nColumns !== 1 || nRows !== 1) {
20523
- vectorArgsType ??= new Array(args.length);
20524
- if (nColumns !== 1 && nRows !== 1) {
20525
- vectorArgsType[i] = "matrix";
20526
- countVectorizableCol = Math.max(countVectorizableCol, nColumns);
20527
- countVectorizableRow = Math.max(countVectorizableRow, nRows);
20528
- vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
20529
- vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
20530
- }
20531
- else if (nColumns !== 1) {
20532
- vectorArgsType[i] = "horizontal";
20533
- countVectorizableCol = Math.max(countVectorizableCol, nColumns);
20534
- vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
20535
- }
20536
- else if (nRows !== 1) {
20537
- vectorArgsType[i] = "vertical";
20538
- countVectorizableRow = Math.max(countVectorizableRow, nRows);
20539
- vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
20540
- }
20541
- }
20542
- else {
20543
- args[i] = arg[0][0];
20544
- }
20545
- }
20546
20676
  if (!isMatrix(arg) && argDefinition.acceptMatrixOnly) {
20547
20677
  throw new BadExpressionError(_t("Function %s expects the parameter '%s' to be reference to a cell or range.", functionName, (i + 1).toString()));
20548
20678
  }
20679
+ acceptToVectorize.push(!argDefinition.acceptMatrix);
20549
20680
  }
20550
- //#endregion
20551
- if (countVectorizableCol === 1 && countVectorizableRow === 1) {
20552
- // either this function is not vectorized or it ends up with a 1x1 dimension
20553
- return errorHandlingCompute.apply(this, args);
20554
- }
20555
- const getArgOffset = (i, j) => args.map((arg, index) => {
20556
- switch (vectorArgsType?.[index]) {
20557
- case "matrix":
20558
- return arg[i][j];
20559
- case "horizontal":
20560
- return arg[i][0];
20561
- case "vertical":
20562
- return arg[0][j];
20563
- case undefined:
20564
- return arg;
20565
- }
20566
- });
20567
- return generateMatrix(countVectorizableCol, countVectorizableRow, (col, row) => {
20568
- if (col > vectorizableColLimit - 1 || row > vectorizableRowLimit - 1) {
20569
- return notAvailableError;
20570
- }
20571
- const singleCellComputeResult = errorHandlingCompute.apply(this, getArgOffset(col, row));
20572
- // In the case where the user tries to vectorize arguments of an array formula, we will get an
20573
- // array for every combination of the vectorized arguments, which will lead to a 3D matrix and
20574
- // we won't be able to return the values.
20575
- // In this case, we keep the first element of each spreading part, just as Excel does, and
20576
- // create an array with these parts.
20577
- // For exemple, we have MUNIT(x) that return an unitary matrix of x*x. If we use it with a
20578
- // range, like MUNIT(A1:A2), we will get two unitary matrices (one for the value in A1 and one
20579
- // for the value in A2). In this case, we will simply take the first value of each matrix and
20580
- // return the array [First value of MUNIT(A1), First value of MUNIT(A2)].
20581
- return isMatrix(singleCellComputeResult)
20582
- ? singleCellComputeResult[0][0]
20583
- : singleCellComputeResult;
20584
- });
20681
+ return applyVectorization(errorHandlingCompute.bind(this), args, acceptToVectorize);
20585
20682
  }
20586
20683
  function errorHandlingCompute(...args) {
20587
20684
  for (let i = 0; i < args.length; i++) {
@@ -21426,7 +21523,7 @@ stores.inject(MyMetaStore, storeInstance);
21426
21523
  proposals &&
21427
21524
  !["ARG_SEPARATOR", "LEFT_PAREN", "OPERATOR"].includes(tokenAtCursor.type)) {
21428
21525
  const filteredProposals = fuzzyLookup(searchTerm, proposals, (p) => p.fuzzySearchKey || p.text);
21429
- if (!exactMatch || filteredProposals.length > 1) {
21526
+ if (!exactMatch || filteredProposals.length) {
21430
21527
  proposals = filteredProposals;
21431
21528
  }
21432
21529
  }
@@ -41158,12 +41255,13 @@ stores.inject(MyMetaStore, storeInstance);
41158
41255
  return res;
41159
41256
  }
41160
41257
  getComposerContent() {
41258
+ let content = this._currentContent;
41161
41259
  if (this.editionMode === "inactive") {
41162
41260
  // References in the content might not be linked to the current active sheet
41163
41261
  // We here force the sheet name prefix for all references that are not in
41164
41262
  // the current active sheet
41165
41263
  const defaultRangeSheetId = this.args().defaultRangeSheetId;
41166
- return rangeTokenize(this.args().content)
41264
+ content = rangeTokenize(this.args().content)
41167
41265
  .map((token) => {
41168
41266
  if (token.type === "REFERENCE") {
41169
41267
  const range = this.getters.getRangeFromSheetXC(defaultRangeSheetId, token.value);
@@ -41173,7 +41271,7 @@ stores.inject(MyMetaStore, storeInstance);
41173
41271
  })
41174
41272
  .join("");
41175
41273
  }
41176
- return this._currentContent;
41274
+ return localizeContent(content, this.getters.getLocale());
41177
41275
  }
41178
41276
  stopEdition() {
41179
41277
  this._stopEdition();
@@ -45448,6 +45546,9 @@ stores.inject(MyMetaStore, storeInstance);
45448
45546
  }
45449
45547
  return undefined;
45450
45548
  }
45549
+ get isCalculatedMeasureInvalid() {
45550
+ return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
45551
+ }
45451
45552
  }
45452
45553
 
45453
45554
  css /* scss */ `
@@ -46458,7 +46559,9 @@ stores.inject(MyMetaStore, storeInstance);
46458
46559
  return dimension.order === "asc" ? -1 : 1;
46459
46560
  }
46460
46561
  if (dimension.type === "integer" || dimension.type === "datetime") {
46461
- return dimension.order === "asc" ? Number(a) - Number(b) : Number(b) - Number(a);
46562
+ return dimension.order === "asc"
46563
+ ? toNumber(a, DEFAULT_LOCALE) - toNumber(b, DEFAULT_LOCALE)
46564
+ : toNumber(b, DEFAULT_LOCALE) - toNumber(a, DEFAULT_LOCALE);
46462
46565
  }
46463
46566
  return dimension.order === "asc" ? a.localeCompare(b) : b.localeCompare(a);
46464
46567
  }
@@ -49221,8 +49324,7 @@ stores.inject(MyMetaStore, storeInstance);
49221
49324
  if (!spreader) {
49222
49325
  return undefined;
49223
49326
  }
49224
- const cell = this.getters.getCell(spreader);
49225
- return cell?.content;
49327
+ return this.getters.getCellText(spreader, { showFormula: true });
49226
49328
  }
49227
49329
  get currentEditedCell() {
49228
49330
  return {
@@ -52143,6 +52245,7 @@ stores.inject(MyMetaStore, storeInstance);
52143
52245
  const friction = 0.95;
52144
52246
  const verticalScrollFactor = 1;
52145
52247
  const horizontalScrollFactor = 1;
52248
+ const resetTimeoutDuration = 100;
52146
52249
  function useTouchScroll(ref, updateScroll, canMoveUp) {
52147
52250
  let lastX = 0;
52148
52251
  let lastY = 0;
@@ -52150,6 +52253,7 @@ stores.inject(MyMetaStore, storeInstance);
52150
52253
  let velocityY = 0;
52151
52254
  let isMouseDown = false;
52152
52255
  let lastTime = 0;
52256
+ let resetTimeout = null;
52153
52257
  useRefListener(ref, "touchstart", onTouchStart, { capture: false });
52154
52258
  useRefListener(ref, "touchmove", onTouchMove, { capture: false });
52155
52259
  useRefListener(ref, "touchend", onTouchEnd, { capture: false });
@@ -52162,6 +52266,10 @@ stores.inject(MyMetaStore, storeInstance);
52162
52266
  function onTouchMove(event) {
52163
52267
  if (!isMouseDown)
52164
52268
  return;
52269
+ if (resetTimeout) {
52270
+ clearTimeout(resetTimeout);
52271
+ resetTimeout = null;
52272
+ }
52165
52273
  const currentTime = Date.now();
52166
52274
  const { clientX, clientY } = event.touches[0];
52167
52275
  let deltaX = lastX - clientX;
@@ -52178,6 +52286,10 @@ stores.inject(MyMetaStore, storeInstance);
52178
52286
  }
52179
52287
  event.stopPropagation();
52180
52288
  }
52289
+ resetTimeout = setTimeout(() => {
52290
+ velocityX = 0;
52291
+ velocityY = 0;
52292
+ }, resetTimeoutDuration);
52181
52293
  updateScroll(deltaX * horizontalScrollFactor, deltaY * verticalScrollFactor);
52182
52294
  }
52183
52295
  function onTouchEnd(ev) {
@@ -60950,10 +61062,9 @@ stores.inject(MyMetaStore, storeInstance);
60950
61062
  return this.evaluatedCells.keysForSheet(sheetId);
60951
61063
  }
60952
61064
  getArrayFormulaSpreadingOn(position) {
60953
- const hasArrayFormulaResult = this.getEvaluatedCell(position).type !== CellValueType.empty &&
60954
- !this.getters.getCell(position)?.isFormula;
60955
- if (!hasArrayFormulaResult) {
60956
- return this.spreadingRelations.isArrayFormula(position) ? position : undefined;
61065
+ const isEmpty = this.getEvaluatedCell(position).type === CellValueType.empty;
61066
+ if (isEmpty) {
61067
+ return undefined;
60957
61068
  }
60958
61069
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
60959
61070
  return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
@@ -76803,9 +76914,9 @@ stores.inject(MyMetaStore, storeInstance);
76803
76914
  exports.tokenize = tokenize;
76804
76915
 
76805
76916
 
76806
- __info__.version = "18.2.12";
76807
- __info__.date = "2025-05-13T17:52:23.989Z";
76808
- __info__.hash = "ba2ba9b";
76917
+ __info__.version = "18.2.14";
76918
+ __info__.date = "2025-05-26T12:35:51.528Z";
76919
+ __info__.hash = "db90fca";
76809
76920
 
76810
76921
 
76811
76922
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);