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