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