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