@odoo/o-spreadsheet 18.3.2 → 18.3.4

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.3.2
6
- * @date 2025-05-12T05:25:42.099Z
7
- * @hash 57d5a67
5
+ * @version 18.3.4
6
+ * @date 2025-05-20T05:57:03.751Z
7
+ * @hash 28f4521
8
8
  */
9
9
 
10
10
  'use strict';
@@ -1625,18 +1625,53 @@ function lettersToNumber(letters) {
1625
1625
  let result = 0;
1626
1626
  const l = letters.length;
1627
1627
  for (let i = 0; i < l; i++) {
1628
- const charCode = letters.charCodeAt(i);
1629
- const colIndex = charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1628
+ const colIndex = charToNumber(letters[i]);
1630
1629
  result = result * 26 + colIndex;
1631
1630
  }
1632
1631
  return result - 1;
1633
1632
  }
1633
+ function charToNumber(char) {
1634
+ const charCode = char.charCodeAt(0);
1635
+ return charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1636
+ }
1634
1637
  function isCharALetter(char) {
1635
1638
  return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
1636
1639
  }
1637
1640
  function isCharADigit(char) {
1638
1641
  return char >= "0" && char <= "9";
1639
1642
  }
1643
+ // we limit the max column to 3 letters and max row to 7 digits for performance reasons
1644
+ const MAX_COL = lettersToNumber("ZZZ");
1645
+ const MAX_ROW = 9999998;
1646
+ function consumeSpaces(chars) {
1647
+ while (chars.current === " ") {
1648
+ chars.advanceBy(1);
1649
+ }
1650
+ }
1651
+ function consumeLetters(chars) {
1652
+ if (chars.current === "$")
1653
+ chars.advanceBy(1);
1654
+ if (!chars.current || !isCharALetter(chars.current)) {
1655
+ return -1;
1656
+ }
1657
+ let colCoordinate = 0;
1658
+ while (chars.current && isCharALetter(chars.current)) {
1659
+ colCoordinate = colCoordinate * 26 + charToNumber(chars.shift());
1660
+ }
1661
+ return colCoordinate;
1662
+ }
1663
+ function consumeDigits(chars) {
1664
+ if (chars.current === "$")
1665
+ chars.advanceBy(1);
1666
+ if (!chars.current || !isCharADigit(chars.current)) {
1667
+ return -1;
1668
+ }
1669
+ let num = 0;
1670
+ while (chars.current && isCharADigit(chars.current)) {
1671
+ num = num * 10 + Number(chars.shift());
1672
+ }
1673
+ return num;
1674
+ }
1640
1675
  /**
1641
1676
  * Convert a "XC" coordinate to cartesian coordinates.
1642
1677
  *
@@ -1647,33 +1682,17 @@ function isCharADigit(char) {
1647
1682
  * Note: it also accepts lowercase coordinates, but not fixed references
1648
1683
  */
1649
1684
  function toCartesian(xc) {
1650
- xc = xc.trim();
1651
- let letterPart = "";
1652
- let numberPart = "";
1653
- let i = 0;
1654
- // Process letter part
1655
- if (xc[i] === "$")
1656
- i++;
1657
- while (i < xc.length && isCharALetter(xc[i])) {
1658
- letterPart += xc[i++];
1659
- }
1660
- if (letterPart.length === 0 || letterPart.length > 3) {
1661
- // limit to max 3 letters for performance reasons
1685
+ const chars = new TokenizingChars(xc);
1686
+ consumeSpaces(chars);
1687
+ const letterPart = consumeLetters(chars);
1688
+ if (letterPart === -1 || !chars.current) {
1662
1689
  throw new Error(`Invalid cell description: ${xc}`);
1663
1690
  }
1664
- // Process number part
1665
- if (xc[i] === "$")
1666
- i++;
1667
- while (i < xc.length && isCharADigit(xc[i])) {
1668
- numberPart += xc[i++];
1669
- }
1670
- if (i !== xc.length || numberPart.length === 0 || numberPart.length > 7) {
1671
- // limit to max 7 numbers for performance reasons
1672
- throw new Error(`Invalid cell description: ${xc}`);
1673
- }
1674
- const col = lettersToNumber(letterPart);
1675
- const row = Number(numberPart) - 1;
1676
- if (isNaN(row)) {
1691
+ const num = consumeDigits(chars);
1692
+ consumeSpaces(chars);
1693
+ const col = letterPart - 1;
1694
+ const row = num - 1;
1695
+ if (!chars.isOver() || col > MAX_COL || row > MAX_ROW) {
1677
1696
  throw new Error(`Invalid cell description: ${xc}`);
1678
1697
  }
1679
1698
  return { col, row };
@@ -2028,67 +2047,6 @@ function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
2028
2047
  return result;
2029
2048
  }
2030
2049
 
2031
- /** Reference of a cell (eg. A1, $B$5) */
2032
- const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2033
- // Same as above, but matches the exact string (nothing before or after)
2034
- const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2035
- /** Reference of a column header (eg. A, AB, $A) */
2036
- const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
2037
- /** Reference of a row header (eg. 1, $1) */
2038
- const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
2039
- /** Reference of a column (eg. A, $CA, Sheet1!B) */
2040
- const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2041
- /** Reference of a row (eg. 1, 59, Sheet1!9) */
2042
- const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
2043
- /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
2044
- const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
2045
- /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
2046
- const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
2047
- /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
2048
- const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
2049
- "(" +
2050
- [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
2051
- ")" +
2052
- /$/.source, "i");
2053
- /**
2054
- * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
2055
- */
2056
- function isColReference(xc) {
2057
- return colReference.test(xc);
2058
- }
2059
- /**
2060
- * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
2061
- */
2062
- function isRowReference(xc) {
2063
- return rowReference.test(xc);
2064
- }
2065
- function isColHeader(str) {
2066
- return colHeader.test(str);
2067
- }
2068
- function isRowHeader(str) {
2069
- return rowHeader.test(str);
2070
- }
2071
- /**
2072
- * Return true if the given xc is the reference of a single cell,
2073
- * without any specified sheet (e.g. A1)
2074
- */
2075
- function isSingleCellReference(xc) {
2076
- return singleCellReference.test(xc);
2077
- }
2078
- function splitReference(ref) {
2079
- if (!ref.includes("!")) {
2080
- return { xc: ref };
2081
- }
2082
- const parts = ref.split("!");
2083
- const xc = parts.pop();
2084
- const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
2085
- return { sheetName, xc };
2086
- }
2087
- /** Return a reference SheetName!xc from the given arguments */
2088
- function getFullReference(sheetName, xc) {
2089
- return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
2090
- }
2091
-
2092
2050
  /**
2093
2051
  * Convert from a cartesian reference to a Zone
2094
2052
  * The range boundaries will be kept in the same order as the
@@ -2106,63 +2064,55 @@ function getFullReference(sheetName, xc) {
2106
2064
  *
2107
2065
  */
2108
2066
  function toZoneWithoutBoundaryChanges(xc) {
2109
- if (xc.includes("!")) {
2110
- xc = xc.split("!").at(-1);
2111
- }
2112
- if (xc.includes("$")) {
2113
- xc = xc.replaceAll("$", "");
2114
- }
2115
- let firstRangePart = "";
2116
- let secondRangePart;
2117
- if (xc.includes(":")) {
2118
- [firstRangePart, secondRangePart] = xc.split(":");
2119
- firstRangePart = firstRangePart.trim();
2120
- secondRangePart = secondRangePart.trim();
2121
- }
2122
- else {
2123
- firstRangePart = xc.trim();
2124
- }
2067
+ const chars = new TokenizingChars(xc);
2068
+ consumeSpaces(chars);
2069
+ const sheetSeparatorIndex = xc.indexOf("!");
2070
+ if (sheetSeparatorIndex !== -1) {
2071
+ chars.advanceBy(sheetSeparatorIndex + 1);
2072
+ }
2073
+ const leftLetters = consumeLetters(chars);
2074
+ const leftNumbers = consumeDigits(chars);
2125
2075
  let top, bottom, left, right;
2126
2076
  let fullCol = false;
2127
2077
  let fullRow = false;
2128
2078
  let hasHeader = false;
2129
- if (isColReference(firstRangePart)) {
2130
- left = right = lettersToNumber(firstRangePart);
2079
+ if (leftNumbers === -1) {
2080
+ left = right = leftLetters - 1;
2131
2081
  top = bottom = 0;
2132
2082
  fullCol = true;
2133
2083
  }
2134
- else if (isRowReference(firstRangePart)) {
2135
- top = bottom = parseInt(firstRangePart, 10) - 1;
2084
+ else if (leftLetters === -1) {
2085
+ top = bottom = leftNumbers - 1;
2136
2086
  left = right = 0;
2137
2087
  fullRow = true;
2138
2088
  }
2139
2089
  else {
2140
- const c = toCartesian(firstRangePart);
2141
- left = right = c.col;
2142
- top = bottom = c.row;
2090
+ left = right = leftLetters - 1;
2091
+ top = bottom = leftNumbers - 1;
2143
2092
  hasHeader = true;
2144
2093
  }
2145
- if (secondRangePart) {
2146
- if (isColReference(secondRangePart)) {
2147
- right = lettersToNumber(secondRangePart);
2094
+ consumeSpaces(chars);
2095
+ if (chars.current === ":") {
2096
+ chars.advanceBy(1);
2097
+ consumeSpaces(chars);
2098
+ const rightLetters = consumeLetters(chars);
2099
+ const rightNumbers = consumeDigits(chars);
2100
+ if (rightNumbers === -1) {
2101
+ right = rightLetters - 1;
2148
2102
  fullCol = true;
2149
2103
  }
2150
- else if (isRowReference(secondRangePart)) {
2151
- bottom = parseInt(secondRangePart, 10) - 1;
2104
+ else if (rightLetters === -1) {
2105
+ bottom = rightNumbers - 1;
2152
2106
  fullRow = true;
2153
2107
  }
2154
2108
  else {
2155
- const c = toCartesian(secondRangePart);
2156
- right = c.col;
2157
- bottom = c.row;
2109
+ right = rightLetters - 1;
2110
+ bottom = rightNumbers - 1;
2158
2111
  top = fullCol ? bottom : top;
2159
2112
  left = fullRow ? right : left;
2160
2113
  hasHeader = true;
2161
2114
  }
2162
2115
  }
2163
- if (fullCol && fullRow) {
2164
- throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2165
- }
2166
2116
  const zone = {
2167
2117
  top,
2168
2118
  left,
@@ -2191,7 +2141,16 @@ function toZoneWithoutBoundaryChanges(xc) {
2191
2141
  */
2192
2142
  function toUnboundedZone(xc) {
2193
2143
  const zone = toZoneWithoutBoundaryChanges(xc);
2194
- return reorderZone(zone);
2144
+ const orderedZone = reorderZone(zone);
2145
+ const bottom = orderedZone.bottom;
2146
+ const right = orderedZone.right;
2147
+ if ((bottom !== undefined && bottom > MAX_ROW) || (right !== undefined && right > MAX_COL)) {
2148
+ throw new Error(`Range string out of bounds: ${xc}`); // limit the size of the zone for performance
2149
+ }
2150
+ if (bottom === undefined && right === undefined) {
2151
+ throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2152
+ }
2153
+ return orderedZone;
2195
2154
  }
2196
2155
  /**
2197
2156
  * Convert from a cartesian reference to a Zone.
@@ -3321,7 +3280,7 @@ function isDateAfter(date, dateAfter) {
3321
3280
  */
3322
3281
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3323
3282
  decimalSeparator = escapeRegExp(decimalSeparator);
3324
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3283
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3325
3284
  });
3326
3285
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3327
3286
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -6002,6 +5961,128 @@ function scrollDelay(value) {
6002
5961
  return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
6003
5962
  }
6004
5963
 
5964
+ /** Reference of a cell (eg. A1, $B$5) */
5965
+ const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
5966
+ // Same as above, but matches the exact string (nothing before or after)
5967
+ const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
5968
+ /** Reference of a column header (eg. A, AB, $A) */
5969
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
5970
+ /** Reference of a row header (eg. 1, $1) */
5971
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
5972
+ /** Reference of a column (eg. A, $CA, Sheet1!B) */
5973
+ const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
5974
+ /** Reference of a row (eg. 1, 59, Sheet1!9) */
5975
+ const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
5976
+ /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
5977
+ const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
5978
+ /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
5979
+ const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
5980
+ /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
5981
+ const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
5982
+ "(" +
5983
+ [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
5984
+ ")" +
5985
+ /$/.source, "i");
5986
+ /**
5987
+ * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
5988
+ */
5989
+ function isColReference(xc) {
5990
+ return colReference.test(xc);
5991
+ }
5992
+ /**
5993
+ * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
5994
+ */
5995
+ function isRowReference(xc) {
5996
+ return rowReference.test(xc);
5997
+ }
5998
+ function isColHeader(str) {
5999
+ return colHeader.test(str);
6000
+ }
6001
+ function isRowHeader(str) {
6002
+ return rowHeader.test(str);
6003
+ }
6004
+ /**
6005
+ * Return true if the given xc is the reference of a single cell,
6006
+ * without any specified sheet (e.g. A1)
6007
+ */
6008
+ function isSingleCellReference(xc) {
6009
+ return singleCellReference.test(xc);
6010
+ }
6011
+ function splitReference(ref) {
6012
+ if (!ref.includes("!")) {
6013
+ return { xc: ref };
6014
+ }
6015
+ const parts = ref.split("!");
6016
+ const xc = parts.pop();
6017
+ const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
6018
+ return { sheetName, xc };
6019
+ }
6020
+ /** Return a reference SheetName!xc from the given arguments */
6021
+ function getFullReference(sheetName, xc) {
6022
+ return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
6023
+ }
6024
+
6025
+ function createDefaultRows(rowNumber) {
6026
+ const rows = [];
6027
+ for (let i = 0; i < rowNumber; i++) {
6028
+ const row = {
6029
+ cells: {},
6030
+ };
6031
+ rows.push(row);
6032
+ }
6033
+ return rows;
6034
+ }
6035
+ function moveHeaderIndexesOnHeaderAddition(indexHeaderAdded, numberAdded, headers) {
6036
+ return headers.map((header) => {
6037
+ if (header >= indexHeaderAdded) {
6038
+ return header + numberAdded;
6039
+ }
6040
+ return header;
6041
+ });
6042
+ }
6043
+ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6044
+ deletedHeaders = [...deletedHeaders].sort((a, b) => b - a);
6045
+ return headers
6046
+ .map((header) => {
6047
+ for (const deletedHeader of deletedHeaders) {
6048
+ if (header > deletedHeader) {
6049
+ header--;
6050
+ }
6051
+ else if (header === deletedHeader) {
6052
+ return undefined;
6053
+ }
6054
+ }
6055
+ return header;
6056
+ })
6057
+ .filter(isDefined);
6058
+ }
6059
+ function getNextSheetName(existingNames, baseName = "Sheet") {
6060
+ let i = 1;
6061
+ let name = `${baseName}${i}`;
6062
+ while (existingNames.includes(name)) {
6063
+ name = `${baseName}${i}`;
6064
+ i++;
6065
+ }
6066
+ return name;
6067
+ }
6068
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6069
+ let i = 1;
6070
+ const baseName = _t("Copy of %s", nameToDuplicate);
6071
+ let name = baseName.toString();
6072
+ while (existingNames.includes(name)) {
6073
+ name = `${baseName} (${i})`;
6074
+ i++;
6075
+ }
6076
+ return name;
6077
+ }
6078
+ function isSheetNameEqual(name1, name2) {
6079
+ if (name1 === undefined || name2 === undefined) {
6080
+ return false;
6081
+ }
6082
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6083
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6084
+ }
6085
+
6005
6086
  function createRange(args, getSheetSize) {
6006
6087
  const unboundedZone = args.zone;
6007
6088
  const zone = boundUnboundedZone(unboundedZone, getSheetSize(args.sheetId));
@@ -6292,7 +6373,7 @@ function getApplyRangeChangeRemoveColRow(cmd) {
6292
6373
  elements.sort((a, b) => b - a);
6293
6374
  const groups = groupConsecutive(elements);
6294
6375
  return (range) => {
6295
- if (range.sheetId !== cmd.sheetId) {
6376
+ if (!isSheetNameEqual(range.sheetId, cmd.sheetId)) {
6296
6377
  return { changeType: "NONE" };
6297
6378
  }
6298
6379
  let newRange = range;
@@ -6499,60 +6580,6 @@ function fuzzyLookup(pattern, list, fn) {
6499
6580
  return results.map((r) => r.elem);
6500
6581
  }
6501
6582
 
6502
- function createDefaultRows(rowNumber) {
6503
- const rows = [];
6504
- for (let i = 0; i < rowNumber; i++) {
6505
- const row = {
6506
- cells: {},
6507
- };
6508
- rows.push(row);
6509
- }
6510
- return rows;
6511
- }
6512
- function moveHeaderIndexesOnHeaderAddition(indexHeaderAdded, numberAdded, headers) {
6513
- return headers.map((header) => {
6514
- if (header >= indexHeaderAdded) {
6515
- return header + numberAdded;
6516
- }
6517
- return header;
6518
- });
6519
- }
6520
- function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6521
- deletedHeaders = [...deletedHeaders].sort((a, b) => b - a);
6522
- return headers
6523
- .map((header) => {
6524
- for (const deletedHeader of deletedHeaders) {
6525
- if (header > deletedHeader) {
6526
- header--;
6527
- }
6528
- else if (header === deletedHeader) {
6529
- return undefined;
6530
- }
6531
- }
6532
- return header;
6533
- })
6534
- .filter(isDefined);
6535
- }
6536
- function getNextSheetName(existingNames, baseName = "Sheet") {
6537
- let i = 1;
6538
- let name = `${baseName}${i}`;
6539
- while (existingNames.includes(name)) {
6540
- name = `${baseName}${i}`;
6541
- i++;
6542
- }
6543
- return name;
6544
- }
6545
- function getDuplicateSheetName(nameToDuplicate, existingNames) {
6546
- let i = 1;
6547
- const baseName = _t("Copy of %s", nameToDuplicate);
6548
- let name = baseName.toString();
6549
- while (existingNames.includes(name)) {
6550
- name = `${baseName} (${i})`;
6551
- i++;
6552
- }
6553
- return name;
6554
- }
6555
-
6556
6583
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6557
6584
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
6558
6585
  }
@@ -8309,7 +8336,8 @@ const monthAdapter = {
8309
8336
  };
8310
8337
  },
8311
8338
  toFunctionValue(normalizedValue) {
8312
- return `"${normalizedValue}"`;
8339
+ const jsDate = toJsDate(normalizedValue, DEFAULT_LOCALE);
8340
+ return `DATE(${jsDate.getFullYear()},${jsDate.getMonth() + 1},1)`;
8313
8341
  },
8314
8342
  };
8315
8343
  /**
@@ -8464,10 +8492,9 @@ const AGGREGATOR_NAMES = {
8464
8492
  avg: _t("Average"),
8465
8493
  sum: _t("Sum"),
8466
8494
  };
8467
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8468
8495
  const AGGREGATORS_BY_FIELD_TYPE = {
8469
- integer: NUMBER_CHAR_AGGREGATORS,
8470
- char: NUMBER_CHAR_AGGREGATORS,
8496
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8497
+ char: ["count_distinct", "count"],
8471
8498
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8472
8499
  datetime: ["max", "min", "count_distinct", "count"],
8473
8500
  };
@@ -9835,7 +9862,10 @@ function proxifyStoreMutation(store, callback) {
9835
9862
  const functionProxy = new Proxy(value, {
9836
9863
  // trap the function call
9837
9864
  apply(target, thisArg, argArray) {
9838
- Reflect.apply(target, thisStore, argArray);
9865
+ const res = Reflect.apply(target, thisStore, argArray);
9866
+ if (res === "noStateChange") {
9867
+ return;
9868
+ }
9839
9869
  callback();
9840
9870
  },
9841
9871
  });
@@ -9857,7 +9887,7 @@ function getDependencyContainer(env) {
9857
9887
  const ModelStore = createAbstractStore("Model");
9858
9888
 
9859
9889
  class RendererStore {
9860
- mutators = ["register", "unRegister"];
9890
+ mutators = ["register", "unRegister", "drawLayer"];
9861
9891
  renderers = {};
9862
9892
  register(renderer) {
9863
9893
  if (!renderer.renderingLayers.length) {
@@ -9877,14 +9907,14 @@ class RendererStore {
9877
9907
  }
9878
9908
  drawLayer(context, layer) {
9879
9909
  const renderers = this.renderers[layer];
9880
- if (!renderers) {
9881
- return;
9882
- }
9883
- for (const renderer of renderers) {
9884
- context.ctx.save();
9885
- renderer.drawLayer(context, layer);
9886
- context.ctx.restore();
9910
+ if (renderers) {
9911
+ for (const renderer of renderers) {
9912
+ context.ctx.save();
9913
+ renderer.drawLayer(context, layer);
9914
+ context.ctx.restore();
9915
+ }
9887
9916
  }
9917
+ return "noStateChange";
9888
9918
  }
9889
9919
  }
9890
9920
 
@@ -9937,16 +9967,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9937
9967
  focusComposer(listener, args) {
9938
9968
  this.activeComposer = listener;
9939
9969
  if (this.getters.isReadonly()) {
9940
- return;
9970
+ return "noStateChange";
9941
9971
  }
9942
9972
  this._focusMode = args.focusMode || "contentFocus";
9943
9973
  if (this._focusMode !== "inactive") {
9944
9974
  this.setComposerContent(args);
9945
9975
  }
9976
+ return;
9946
9977
  }
9947
9978
  focusActiveComposer(args) {
9948
9979
  if (this.getters.isReadonly()) {
9949
- return;
9980
+ return "noStateChange";
9950
9981
  }
9951
9982
  if (!this.activeComposer) {
9952
9983
  throw new Error("No composer is registered");
@@ -9955,6 +9986,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9955
9986
  if (this._focusMode !== "inactive") {
9956
9987
  this.setComposerContent(args);
9957
9988
  }
9989
+ return;
9958
9990
  }
9959
9991
  /**
9960
9992
  * Start the edition or update the content if it's already started.
@@ -10558,7 +10590,7 @@ function getDefinedAxis(definition) {
10558
10590
  }
10559
10591
  function formatChartDatasetValue(axisFormats, locale) {
10560
10592
  return (value, axisId) => {
10561
- const format = axisId ? axisFormats?.[axisId] : undefined;
10593
+ const format = axisFormats?.[axisId];
10562
10594
  return formatTickValue({ format, locale })(value);
10563
10595
  };
10564
10596
  }
@@ -10735,7 +10767,7 @@ function drawPieChartValues(chart, options, ctx) {
10735
10767
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10736
10768
  ctx.fillStyle = chartFontColor(options.background);
10737
10769
  ctx.strokeStyle = options.background || "#ffffff";
10738
- const displayValue = options.callback(value);
10770
+ const displayValue = options.callback(value, "y");
10739
10771
  drawTextWithBackground(displayValue, x, y, ctx);
10740
10772
  }
10741
10773
  }
@@ -20544,6 +20576,9 @@ const PIVOT_VALUE = {
20544
20576
  };
20545
20577
  }
20546
20578
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
20579
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
20580
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
20581
+ }
20547
20582
  return pivot.getPivotCellValueAndFormat(_measure, domain);
20548
20583
  },
20549
20584
  };
@@ -20575,6 +20610,9 @@ const PIVOT_HEADER = {
20575
20610
  };
20576
20611
  }
20577
20612
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
20613
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
20614
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
20615
+ }
20578
20616
  const lastNode = domain.at(-1);
20579
20617
  if (lastNode?.field === "measure") {
20580
20618
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -20797,6 +20835,9 @@ function isEmpty(data) {
20797
20835
  return data === undefined || data.value === null;
20798
20836
  }
20799
20837
  const getNeutral = { number: 0, string: "", boolean: false };
20838
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
20839
+ return Math.abs(value1 - value2) < epsilon;
20840
+ }
20800
20841
  const EQ = {
20801
20842
  description: _t("Equal."),
20802
20843
  args: [
@@ -20818,6 +20859,9 @@ const EQ = {
20818
20859
  if (typeof _value2 === "string") {
20819
20860
  _value2 = _value2.toUpperCase();
20820
20861
  }
20862
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
20863
+ return { value: areAlmostEqual(_value1, _value2) };
20864
+ }
20821
20865
  return { value: _value1 === _value2 };
20822
20866
  },
20823
20867
  };
@@ -20857,6 +20901,9 @@ const GT = {
20857
20901
  ],
20858
20902
  compute: function (value1, value2) {
20859
20903
  return applyRelationalOperator(value1, value2, (v1, v2) => {
20904
+ if (typeof v1 === "number" && typeof v2 === "number") {
20905
+ return !areAlmostEqual(v1, v2) && v1 > v2;
20906
+ }
20860
20907
  return v1 > v2;
20861
20908
  });
20862
20909
  },
@@ -20872,6 +20919,9 @@ const GTE = {
20872
20919
  ],
20873
20920
  compute: function (value1, value2) {
20874
20921
  return applyRelationalOperator(value1, value2, (v1, v2) => {
20922
+ if (typeof v1 === "number" && typeof v2 === "number") {
20923
+ return areAlmostEqual(v1, v2) || v1 > v2;
20924
+ }
20875
20925
  return v1 >= v2;
20876
20926
  });
20877
20927
  },
@@ -22564,7 +22614,7 @@ class AbstractComposerStore extends SpreadsheetStore {
22564
22614
  .find((token) => {
22565
22615
  const { xc, sheetName: sheet } = splitReference(token.value);
22566
22616
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
22567
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
22617
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
22568
22618
  return false;
22569
22619
  }
22570
22620
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -24501,6 +24551,8 @@ async function chartToImageFile(runtime, figure, type) {
24501
24551
  const div = document.createElement("div");
24502
24552
  div.style.width = `${figure.width}px`;
24503
24553
  div.style.height = `${figure.height}px`;
24554
+ div.style.position = "fixed";
24555
+ div.style.opacity = "0";
24504
24556
  const canvas = document.createElement("canvas");
24505
24557
  div.append(canvas);
24506
24558
  canvas.setAttribute("width", figure.width.toString());
@@ -29703,10 +29755,15 @@ function getChartMenu(figureId, onFigureDeleted, env) {
29703
29755
  const imageUrl = chartToImageUrl(runtime, figure, chartType);
29704
29756
  const innerHTML = `<img src="${xmlEscape(imageUrl)}" />`;
29705
29757
  const blob = await chartToImageFile(runtime, figure, chartType);
29706
- env.clipboard.write({
29758
+ await env.clipboard.write({
29707
29759
  "text/html": innerHTML,
29708
29760
  "image/png": blob,
29709
29761
  });
29762
+ env.notifyUser({
29763
+ text: _t("The chart was copied to your clipboard"),
29764
+ sticky: false,
29765
+ type: "info",
29766
+ });
29710
29767
  },
29711
29768
  },
29712
29769
  {
@@ -31556,7 +31613,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
31556
31613
  ({ xc, sheetName } = splitReference(reference));
31557
31614
  let rangeSheetIndex;
31558
31615
  if (sheetName) {
31559
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
31616
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
31560
31617
  if (index < 0) {
31561
31618
  throw new Error("Unable to find a sheet with the name " + sheetName);
31562
31619
  }
@@ -31913,7 +31970,7 @@ function convertFormula(formula, data) {
31913
31970
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
31914
31971
  externalRefId = Number(externalRefId) - 1;
31915
31972
  cellRef = cellRef.replace(/\$/g, "");
31916
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
31973
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
31917
31974
  if (sheetIndex === -1) {
31918
31975
  return match;
31919
31976
  }
@@ -32564,7 +32621,7 @@ function convertPivotTableConfig(pivotTable) {
32564
32621
  */
32565
32622
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
32566
32623
  for (let tableSheet of convertedSheets) {
32567
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
32624
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
32568
32625
  for (let table of tables) {
32569
32626
  const tabRef = table.name + "[";
32570
32627
  for (let sheet of convertedSheets) {
@@ -35219,12 +35276,20 @@ class DelayedHoveredCellStore extends SpreadsheetStore {
35219
35276
  }
35220
35277
  }
35221
35278
  hover(position) {
35279
+ if (position.col === this.col && position.row === this.row) {
35280
+ return "noStateChange";
35281
+ }
35222
35282
  this.col = position.col;
35223
35283
  this.row = position.row;
35284
+ return;
35224
35285
  }
35225
35286
  clear() {
35287
+ if (this.col === undefined && this.row === undefined) {
35288
+ return "noStateChange";
35289
+ }
35226
35290
  this.col = undefined;
35227
35291
  this.row = undefined;
35292
+ return;
35228
35293
  }
35229
35294
  }
35230
35295
 
@@ -35246,7 +35311,11 @@ class CellPopoverStore extends SpreadsheetStore {
35246
35311
  this.persistentPopover = { col, row, sheetId, type };
35247
35312
  }
35248
35313
  close() {
35314
+ if (!this.persistentPopover) {
35315
+ return "noStateChange";
35316
+ }
35249
35317
  this.persistentPopover = undefined;
35318
+ return;
35250
35319
  }
35251
35320
  get persistentCellPopover() {
35252
35321
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -42364,10 +42433,18 @@ class GaugeChartConfigPanel extends owl.Component {
42364
42433
  }
42365
42434
 
42366
42435
  class DOMFocusableElementStore {
42367
- mutators = ["setFocusableElement"];
42436
+ mutators = ["setFocusableElement", "focus"];
42368
42437
  focusableElement = undefined;
42369
42438
  setFocusableElement(element) {
42370
42439
  this.focusableElement = element;
42440
+ return "noStateChange";
42441
+ }
42442
+ focus() {
42443
+ if (this.focusableElement === document.activeElement) {
42444
+ return "noStateChange";
42445
+ }
42446
+ this.focusableElement?.focus();
42447
+ return;
42371
42448
  }
42372
42449
  }
42373
42450
 
@@ -43039,7 +43116,7 @@ class Composer extends owl.Component {
43039
43116
  if (document.activeElement === this.contentHelper.el &&
43040
43117
  this.props.composerStore.editionMode === "inactive" &&
43041
43118
  !this.props.isDefaultFocus) {
43042
- this.DOMFocusableElementStore.focusableElement?.focus();
43119
+ this.DOMFocusableElementStore.focus();
43043
43120
  }
43044
43121
  });
43045
43122
  owl.useEffect(() => {
@@ -43553,12 +43630,13 @@ class StandaloneComposerStore extends AbstractComposerStore {
43553
43630
  return res;
43554
43631
  }
43555
43632
  getComposerContent() {
43633
+ let content = this._currentContent;
43556
43634
  if (this.editionMode === "inactive") {
43557
43635
  // References in the content might not be linked to the current active sheet
43558
43636
  // We here force the sheet name prefix for all references that are not in
43559
43637
  // the current active sheet
43560
43638
  const defaultRangeSheetId = this.args().defaultRangeSheetId;
43561
- return rangeTokenize(this.args().content)
43639
+ content = rangeTokenize(this.args().content)
43562
43640
  .map((token) => {
43563
43641
  if (token.type === "REFERENCE") {
43564
43642
  const range = this.getters.getRangeFromSheetXC(defaultRangeSheetId, token.value);
@@ -43568,7 +43646,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
43568
43646
  })
43569
43647
  .join("");
43570
43648
  }
43571
- return this._currentContent;
43649
+ return localizeContent(content, this.getters.getLocale());
43572
43650
  }
43573
43651
  stopEdition() {
43574
43652
  this._stopEdition();
@@ -48033,6 +48111,9 @@ class PivotMeasureEditor extends owl.Component {
48033
48111
  }
48034
48112
  return undefined;
48035
48113
  }
48114
+ get isCalculatedMeasureInvalid() {
48115
+ return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
48116
+ }
48036
48117
  }
48037
48118
 
48038
48119
  css /* scss */ `
@@ -49577,7 +49658,12 @@ class SpreadsheetPivot {
49577
49658
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
49578
49659
  }
49579
49660
  else {
49580
- entry[field.name] = cell;
49661
+ if (field.type === "char") {
49662
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
49663
+ }
49664
+ else {
49665
+ entry[field.name] = cell;
49666
+ }
49581
49667
  }
49582
49668
  }
49583
49669
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -52465,14 +52551,14 @@ function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }
52465
52551
  const deltaX = Math.min(dirX * (mouseInitialX - mouseX + scrollX - initialScrollX), width - minFigSize);
52466
52552
  const deltaY = Math.min(dirY * (mouseInitialY - mouseY + scrollY - initialScrollY), height - minFigSize);
52467
52553
  const fraction = Math.min(deltaX / width, deltaY / height);
52468
- width = width * (1 - fraction);
52469
- height = height * (1 - fraction);
52470
52554
  if (dirX < 0) {
52471
52555
  x = x + width * fraction;
52472
52556
  }
52473
52557
  if (dirY < 0) {
52474
52558
  y = y + height * fraction;
52475
52559
  }
52560
+ width = width * (1 - fraction);
52561
+ height = height * (1 - fraction);
52476
52562
  }
52477
52563
  else {
52478
52564
  const deltaX = Math.max(dirX * (mouseX - mouseInitialX + scrollX - initialScrollX), minFigSize - width);
@@ -53302,9 +53388,13 @@ class HoveredTableStore extends SpreadsheetStore {
53302
53388
  }
53303
53389
  }
53304
53390
  hover(position) {
53391
+ if (position.col === this.col && position.row === this.row) {
53392
+ return "noStateChange";
53393
+ }
53305
53394
  this.col = position.col;
53306
53395
  this.row = position.row;
53307
53396
  this.computeOverlay();
53397
+ return;
53308
53398
  }
53309
53399
  clear() {
53310
53400
  this.col = undefined;
@@ -54936,10 +55026,6 @@ function useGridDrawing(refName, model, canvasSize) {
54936
55026
  ctx.scale(dpr, dpr);
54937
55027
  for (const layer of OrderedLayers()) {
54938
55028
  model.drawLayer(renderingContext, layer);
54939
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
54940
- // it does not mutate anything. Most importantly it's used
54941
- // during rendering. Invoking a mutator during rendering would
54942
- // trigger another rendering, ultimately resulting in an infinite loop.
54943
55029
  rendererStore.drawLayer(renderingContext, layer);
54944
55030
  }
54945
55031
  }
@@ -55633,7 +55719,7 @@ class Grid extends owl.Component {
55633
55719
  this.cellPopovers = useStore(CellPopoverStore);
55634
55720
  owl.useEffect(() => {
55635
55721
  if (!this.sidePanel.isOpen) {
55636
- this.DOMFocusableElementStore.focusableElement?.focus();
55722
+ this.DOMFocusableElementStore.focus();
55637
55723
  }
55638
55724
  }, () => [this.sidePanel.isOpen]);
55639
55725
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -55842,7 +55928,7 @@ class Grid extends owl.Component {
55842
55928
  focusDefaultElement() {
55843
55929
  if (!this.env.model.getters.getSelectedFigureId() &&
55844
55930
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
55845
- this.DOMFocusableElementStore.focusableElement?.focus();
55931
+ this.DOMFocusableElementStore.focus();
55846
55932
  }
55847
55933
  }
55848
55934
  get gridEl() {
@@ -56191,6 +56277,322 @@ class Grid extends owl.Component {
56191
56277
  }
56192
56278
  }
56193
56279
 
56280
+ css /* scss */ `
56281
+ .o_pivot_html_renderer {
56282
+ width: 100%;
56283
+ border-collapse: collapse;
56284
+
56285
+ &:hover {
56286
+ cursor: pointer;
56287
+ }
56288
+
56289
+ td,
56290
+ th {
56291
+ border: 1px solid #dee2e6;
56292
+ background-color: #fff;
56293
+ padding: 0.3rem;
56294
+ white-space: nowrap;
56295
+
56296
+ &:hover {
56297
+ filter: brightness(0.9);
56298
+ }
56299
+ }
56300
+
56301
+ td {
56302
+ text-align: right;
56303
+ }
56304
+
56305
+ th {
56306
+ background-color: #f5f5f5;
56307
+ font-weight: bold;
56308
+ color: black;
56309
+ }
56310
+
56311
+ .o_missing_value {
56312
+ color: #46646d;
56313
+ background: #e7f2f6;
56314
+ }
56315
+ }
56316
+ `;
56317
+ class PivotHTMLRenderer extends owl.Component {
56318
+ static template = "o_spreadsheet.PivotHTMLRenderer";
56319
+ static components = { Checkbox };
56320
+ static props = {
56321
+ pivotId: String,
56322
+ onCellClicked: Function,
56323
+ };
56324
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
56325
+ data = {
56326
+ columns: [],
56327
+ rows: [],
56328
+ values: [],
56329
+ };
56330
+ state = owl.useState({
56331
+ showMissingValuesOnly: false,
56332
+ });
56333
+ setup() {
56334
+ const table = this.pivot.getTableStructure();
56335
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
56336
+ this.data = {
56337
+ columns: this._buildColHeaders(formulaId, table),
56338
+ rows: this._buildRowHeaders(formulaId, table),
56339
+ values: this._buildValues(formulaId, table),
56340
+ };
56341
+ }
56342
+ get tracker() {
56343
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
56344
+ }
56345
+ // ---------------------------------------------------------------------
56346
+ // Missing values building
56347
+ // ---------------------------------------------------------------------
56348
+ /**
56349
+ * Retrieve the data to display in the Pivot Table
56350
+ * In the case when showMissingValuesOnly is false, the returned value
56351
+ * is the complete data
56352
+ * In the case when showMissingValuesOnly is true, the returned value is
56353
+ * the data which contains only missing values in the rows and cols. In
56354
+ * the rows, we also return the parent rows of rows which contains missing
56355
+ * values, to give context to the user.
56356
+ *
56357
+ */
56358
+ getTableData() {
56359
+ if (!this.state.showMissingValuesOnly) {
56360
+ return this.data;
56361
+ }
56362
+ const colIndexes = this.getColumnsIndexes();
56363
+ const rowIndexes = this.getRowsIndexes();
56364
+ const columns = this.buildColumnsMissing(colIndexes);
56365
+ const rows = this.buildRowsMissing(rowIndexes);
56366
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
56367
+ return { columns, rows, values };
56368
+ }
56369
+ /**
56370
+ * Retrieve the parents of the given row
56371
+ * ex:
56372
+ * Australia
56373
+ * January
56374
+ * February
56375
+ * The parent of "January" is "Australia"
56376
+ */
56377
+ addRecursiveRow(index) {
56378
+ const rows = this.pivot.getTableStructure().rows;
56379
+ const row = [...rows[index].values];
56380
+ if (row.length <= 1) {
56381
+ return [index];
56382
+ }
56383
+ row.pop();
56384
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
56385
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
56386
+ }
56387
+ /**
56388
+ * Create the columns to be used, based on the indexes of the columns in
56389
+ * which a missing value is present
56390
+ *
56391
+ */
56392
+ buildColumnsMissing(indexes) {
56393
+ // columnsMap explode the columns in an array of array of the same
56394
+ // size with the index of each column, repeated 'span' times.
56395
+ // ex:
56396
+ // | A | B |
56397
+ // | 1 | 2 | 3 |
56398
+ // => [
56399
+ // [0, 0, 1]
56400
+ // [0, 1, 2]
56401
+ // ]
56402
+ const columnsMap = [];
56403
+ for (const column of this.data.columns) {
56404
+ const columnMap = [];
56405
+ for (const index in column) {
56406
+ for (let i = 0; i < column[index].span; i++) {
56407
+ columnMap.push(parseInt(index, 10));
56408
+ }
56409
+ }
56410
+ columnsMap.push(columnMap);
56411
+ }
56412
+ // Remove the columns that are not present in indexes
56413
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
56414
+ if (!indexes.includes(i)) {
56415
+ for (const columnMap of columnsMap) {
56416
+ columnMap.splice(i, 1);
56417
+ }
56418
+ }
56419
+ }
56420
+ // Build the columns
56421
+ const columns = [];
56422
+ for (const mapIndex in columnsMap) {
56423
+ const column = [];
56424
+ let index = undefined;
56425
+ let span = 1;
56426
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
56427
+ if (index !== columnsMap[mapIndex][i]) {
56428
+ if (index !== undefined) {
56429
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
56430
+ }
56431
+ index = columnsMap[mapIndex][i];
56432
+ span = 1;
56433
+ }
56434
+ else {
56435
+ span++;
56436
+ }
56437
+ }
56438
+ if (index !== undefined) {
56439
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
56440
+ }
56441
+ columns.push(column);
56442
+ }
56443
+ return columns;
56444
+ }
56445
+ /**
56446
+ * Create the rows to be used, based on the indexes of the rows in
56447
+ * which a missing value is present.
56448
+ */
56449
+ buildRowsMissing(indexes) {
56450
+ return indexes.map((index) => this.data.rows[index]);
56451
+ }
56452
+ /**
56453
+ * Create the value to be used, based on the indexes of the columns and
56454
+ * rows in which a missing value is present.
56455
+ */
56456
+ buildValuesMissing(colIndexes, rowIndexes) {
56457
+ const values = colIndexes.map(() => []);
56458
+ for (const row of rowIndexes) {
56459
+ for (const col in colIndexes) {
56460
+ values[col].push(this.data.values[colIndexes[col]][row]);
56461
+ }
56462
+ }
56463
+ return values;
56464
+ }
56465
+ getColumnsIndexes() {
56466
+ const indexes = new Set();
56467
+ for (let i = 0; i < this.data.columns.length; i++) {
56468
+ const exploded = [];
56469
+ for (let y = 0; y < this.data.columns[i].length; y++) {
56470
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
56471
+ exploded.push(this.data.columns[i][y]);
56472
+ }
56473
+ }
56474
+ for (let y = 0; y < exploded.length; y++) {
56475
+ if (exploded[y].isMissing) {
56476
+ indexes.add(y);
56477
+ }
56478
+ }
56479
+ }
56480
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
56481
+ const values = this.data.values[i];
56482
+ if (values.find((x) => x.isMissing)) {
56483
+ indexes.add(i);
56484
+ }
56485
+ }
56486
+ return Array.from(indexes).sort((a, b) => a - b);
56487
+ }
56488
+ getRowsIndexes() {
56489
+ const rowIndexes = new Set();
56490
+ for (let i = 0; i < this.data.rows.length; i++) {
56491
+ if (this.data.rows[i].isMissing) {
56492
+ rowIndexes.add(i);
56493
+ }
56494
+ for (const col of this.data.values) {
56495
+ if (col[i].isMissing) {
56496
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
56497
+ }
56498
+ }
56499
+ }
56500
+ return Array.from(rowIndexes).sort((a, b) => a - b);
56501
+ }
56502
+ // ---------------------------------------------------------------------
56503
+ // Data table creation
56504
+ // ---------------------------------------------------------------------
56505
+ _buildColHeaders(id, table) {
56506
+ const headers = [];
56507
+ for (const row of table.columns) {
56508
+ const current = [];
56509
+ for (const cell of row) {
56510
+ const args = [];
56511
+ for (let i = 0; i < cell.fields.length; i++) {
56512
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
56513
+ }
56514
+ const domain = this.pivot.parseArgsToPivotDomain(args);
56515
+ const locale = this.env.model.getters.getLocale();
56516
+ if (domain.at(-1)?.field === "measure") {
56517
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
56518
+ current.push({
56519
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
56520
+ value: formatValue(value, { format, locale }),
56521
+ span: cell.width,
56522
+ isMissing: !this.tracker?.isHeaderPresent(domain),
56523
+ });
56524
+ }
56525
+ else {
56526
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
56527
+ current.push({
56528
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
56529
+ value: formatValue(value, { format, locale }),
56530
+ span: cell.width,
56531
+ isMissing: !this.tracker?.isHeaderPresent(domain),
56532
+ });
56533
+ }
56534
+ }
56535
+ headers.push(current);
56536
+ }
56537
+ const last = headers[headers.length - 1];
56538
+ headers[headers.length - 1] = last.map((cell) => {
56539
+ if (!cell.isMissing) {
56540
+ cell.style = "color: #756f6f;";
56541
+ }
56542
+ return cell;
56543
+ });
56544
+ return headers;
56545
+ }
56546
+ _buildRowHeaders(id, table) {
56547
+ const headers = [];
56548
+ for (const row of table.rows) {
56549
+ const args = [];
56550
+ for (let i = 0; i < row.fields.length; i++) {
56551
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
56552
+ }
56553
+ const domain = this.pivot.parseArgsToPivotDomain(args);
56554
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
56555
+ const locale = this.env.model.getters.getLocale();
56556
+ const cell = {
56557
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
56558
+ value: formatValue(value, { format, locale }),
56559
+ isMissing: !this.tracker?.isHeaderPresent(domain),
56560
+ };
56561
+ if (row.indent > 1) {
56562
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
56563
+ }
56564
+ headers.push(cell);
56565
+ }
56566
+ return headers;
56567
+ }
56568
+ _buildValues(id, table) {
56569
+ const values = [];
56570
+ for (const col of table.columns.at(-1) || []) {
56571
+ const current = [];
56572
+ const measure = toString(col.values[col.values.length - 1]);
56573
+ for (const row of table.rows) {
56574
+ const args = [];
56575
+ for (let i = 0; i < row.fields.length; i++) {
56576
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
56577
+ }
56578
+ for (let i = 0; i < col.fields.length - 1; i++) {
56579
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
56580
+ }
56581
+ const domain = this.pivot.parseArgsToPivotDomain(args);
56582
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
56583
+ const locale = this.env.model.getters.getLocale();
56584
+ current.push({
56585
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
56586
+ value: formatValue(value, { format, locale }),
56587
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
56588
+ });
56589
+ }
56590
+ values.push(current);
56591
+ }
56592
+ return values;
56593
+ }
56594
+ }
56595
+
56194
56596
  /**
56195
56597
  * BasePlugin
56196
56598
  *
@@ -60210,7 +60612,7 @@ class SheetPlugin extends CorePlugin {
60210
60612
  if (name) {
60211
60613
  const unquotedName = getUnquotedSheetName(name);
60212
60614
  for (const key in this.sheetIdsMapName) {
60213
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
60615
+ if (isSheetNameEqual(key, unquotedName)) {
60214
60616
  return this.sheetIdsMapName[key];
60215
60617
  }
60216
60618
  }
@@ -60459,7 +60861,7 @@ class SheetPlugin extends CorePlugin {
60459
60861
  }
60460
60862
  const { orderedSheetIds, sheets } = this;
60461
60863
  const name = sheetName && sheetName.trim().toLowerCase();
60462
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
60864
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
60463
60865
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
60464
60866
  }
60465
60867
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -63398,10 +63800,9 @@ class Evaluator {
63398
63800
  return this.evaluatedCells.keysForSheet(sheetId);
63399
63801
  }
63400
63802
  getArrayFormulaSpreadingOn(position) {
63401
- const hasArrayFormulaResult = this.getEvaluatedCell(position).type !== CellValueType.empty &&
63402
- !this.getters.getCell(position)?.isFormula;
63403
- if (!hasArrayFormulaResult) {
63404
- return this.spreadingRelations.isArrayFormula(position) ? position : undefined;
63803
+ const isEmpty = this.getEvaluatedCell(position).type === CellValueType.empty;
63804
+ if (isEmpty) {
63805
+ return undefined;
63405
63806
  }
63406
63807
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
63407
63808
  return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
@@ -69543,6 +69944,55 @@ class HistoryPlugin extends UIPlugin {
69543
69944
  }
69544
69945
  }
69545
69946
 
69947
+ class PivotPresenceTracker {
69948
+ trackedValues = new Set();
69949
+ domainToArray(domain) {
69950
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
69951
+ }
69952
+ isValuePresent(measure, domain) {
69953
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
69954
+ return this.trackedValues.has(key);
69955
+ }
69956
+ isHeaderPresent(domain) {
69957
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
69958
+ return this.trackedValues.has(key);
69959
+ }
69960
+ trackValue(measure, domain) {
69961
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
69962
+ this.trackedValues.add(key);
69963
+ }
69964
+ trackHeader(domain) {
69965
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
69966
+ this.trackedValues.add(key);
69967
+ }
69968
+ }
69969
+
69970
+ class PivotPresencePlugin extends UIPlugin {
69971
+ static getters = ["getPivotPresenceTracker"];
69972
+ trackPresencePivotId;
69973
+ tracker;
69974
+ handle(cmd) {
69975
+ switch (cmd.type) {
69976
+ case "PIVOT_START_PRESENCE_TRACKING":
69977
+ this.tracker = new PivotPresenceTracker();
69978
+ this.trackPresencePivotId = cmd.pivotId;
69979
+ break;
69980
+ case "PIVOT_STOP_PRESENCE_TRACKING":
69981
+ this.trackPresencePivotId = undefined;
69982
+ break;
69983
+ }
69984
+ }
69985
+ getPivotPresenceTracker(pivotId) {
69986
+ if (this.trackPresencePivotId !== pivotId) {
69987
+ return undefined;
69988
+ }
69989
+ if (!this.tracker) {
69990
+ throw new Error("Tracker not initialized");
69991
+ }
69992
+ return this.tracker;
69993
+ }
69994
+ }
69995
+
69546
69996
  class SplitToColumnsPlugin extends UIPlugin {
69547
69997
  static getters = ["getAutomaticSeparator"];
69548
69998
  allowDispatch(cmd) {
@@ -72511,6 +72961,7 @@ const featurePluginRegistry = new Registry()
72511
72961
  .add("automatic_sum", AutomaticSumPlugin)
72512
72962
  .add("format", FormatPlugin)
72513
72963
  .add("insert_pivot", InsertPivotPlugin)
72964
+ .add("pivot_presence", PivotPresencePlugin)
72514
72965
  .add("split_to_columns", SplitToColumnsPlugin)
72515
72966
  .add("collaborative", CollaborativePlugin)
72516
72967
  .add("history", HistoryPlugin)
@@ -72900,11 +73351,11 @@ class BottomBarSheet extends owl.Component {
72900
73351
  if (ev.key === "Enter") {
72901
73352
  ev.preventDefault();
72902
73353
  this.stopEdition();
72903
- this.DOMFocusableElementStore.focusableElement?.focus();
73354
+ this.DOMFocusableElementStore.focus();
72904
73355
  }
72905
73356
  if (ev.key === "Escape") {
72906
73357
  this.cancelEdition();
72907
- this.DOMFocusableElementStore.focusableElement?.focus();
73358
+ this.DOMFocusableElementStore.focus();
72908
73359
  }
72909
73360
  }
72910
73361
  onMouseEventSheetName(ev) {
@@ -79913,6 +80364,7 @@ const components = {
79913
80364
  PivotDimensionOrder,
79914
80365
  PivotDimension,
79915
80366
  PivotLayoutConfigurator,
80367
+ PivotHTMLRenderer,
79916
80368
  PivotDeferUpdate,
79917
80369
  PivotTitleSection,
79918
80370
  CogWheelMenu,
@@ -80010,6 +80462,6 @@ exports.tokenColors = tokenColors;
80010
80462
  exports.tokenize = tokenize;
80011
80463
 
80012
80464
 
80013
- __info__.version = "18.3.2";
80014
- __info__.date = "2025-05-12T05:25:42.099Z";
80015
- __info__.hash = "57d5a67";
80465
+ __info__.version = "18.3.4";
80466
+ __info__.date = "2025-05-20T05:57:03.751Z";
80467
+ __info__.hash = "28f4521";