@odoo/o-spreadsheet 18.1.19 → 18.1.21

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.1.19
6
- * @date 2025-05-12T05:26:05.861Z
7
- * @hash 44cc170
5
+ * @version 18.1.21
6
+ * @date 2025-05-20T05:54:45.398Z
7
+ * @hash 89ed6a9
8
8
  */
9
9
 
10
10
  'use strict';
@@ -1608,18 +1608,53 @@ function lettersToNumber(letters) {
1608
1608
  let result = 0;
1609
1609
  const l = letters.length;
1610
1610
  for (let i = 0; i < l; i++) {
1611
- const charCode = letters.charCodeAt(i);
1612
- const colIndex = charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1611
+ const colIndex = charToNumber(letters[i]);
1613
1612
  result = result * 26 + colIndex;
1614
1613
  }
1615
1614
  return result - 1;
1616
1615
  }
1616
+ function charToNumber(char) {
1617
+ const charCode = char.charCodeAt(0);
1618
+ return charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1619
+ }
1617
1620
  function isCharALetter(char) {
1618
1621
  return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
1619
1622
  }
1620
1623
  function isCharADigit(char) {
1621
1624
  return char >= "0" && char <= "9";
1622
1625
  }
1626
+ // we limit the max column to 3 letters and max row to 7 digits for performance reasons
1627
+ const MAX_COL = lettersToNumber("ZZZ");
1628
+ const MAX_ROW = 9999998;
1629
+ function consumeSpaces(chars) {
1630
+ while (chars.current === " ") {
1631
+ chars.advanceBy(1);
1632
+ }
1633
+ }
1634
+ function consumeLetters(chars) {
1635
+ if (chars.current === "$")
1636
+ chars.advanceBy(1);
1637
+ if (!chars.current || !isCharALetter(chars.current)) {
1638
+ return -1;
1639
+ }
1640
+ let colCoordinate = 0;
1641
+ while (chars.current && isCharALetter(chars.current)) {
1642
+ colCoordinate = colCoordinate * 26 + charToNumber(chars.shift());
1643
+ }
1644
+ return colCoordinate;
1645
+ }
1646
+ function consumeDigits(chars) {
1647
+ if (chars.current === "$")
1648
+ chars.advanceBy(1);
1649
+ if (!chars.current || !isCharADigit(chars.current)) {
1650
+ return -1;
1651
+ }
1652
+ let num = 0;
1653
+ while (chars.current && isCharADigit(chars.current)) {
1654
+ num = num * 10 + Number(chars.shift());
1655
+ }
1656
+ return num;
1657
+ }
1623
1658
  /**
1624
1659
  * Convert a "XC" coordinate to cartesian coordinates.
1625
1660
  *
@@ -1630,33 +1665,17 @@ function isCharADigit(char) {
1630
1665
  * Note: it also accepts lowercase coordinates, but not fixed references
1631
1666
  */
1632
1667
  function toCartesian(xc) {
1633
- xc = xc.trim();
1634
- let letterPart = "";
1635
- let numberPart = "";
1636
- let i = 0;
1637
- // Process letter part
1638
- if (xc[i] === "$")
1639
- i++;
1640
- while (i < xc.length && isCharALetter(xc[i])) {
1641
- letterPart += xc[i++];
1642
- }
1643
- if (letterPart.length === 0 || letterPart.length > 3) {
1644
- // limit to max 3 letters for performance reasons
1668
+ const chars = new TokenizingChars(xc);
1669
+ consumeSpaces(chars);
1670
+ const letterPart = consumeLetters(chars);
1671
+ if (letterPart === -1 || !chars.current) {
1645
1672
  throw new Error(`Invalid cell description: ${xc}`);
1646
1673
  }
1647
- // Process number part
1648
- if (xc[i] === "$")
1649
- i++;
1650
- while (i < xc.length && isCharADigit(xc[i])) {
1651
- numberPart += xc[i++];
1652
- }
1653
- if (i !== xc.length || numberPart.length === 0 || numberPart.length > 7) {
1654
- // limit to max 7 numbers for performance reasons
1655
- throw new Error(`Invalid cell description: ${xc}`);
1656
- }
1657
- const col = lettersToNumber(letterPart);
1658
- const row = Number(numberPart) - 1;
1659
- if (isNaN(row)) {
1674
+ const num = consumeDigits(chars);
1675
+ consumeSpaces(chars);
1676
+ const col = letterPart - 1;
1677
+ const row = num - 1;
1678
+ if (!chars.isOver() || col > MAX_COL || row > MAX_ROW) {
1660
1679
  throw new Error(`Invalid cell description: ${xc}`);
1661
1680
  }
1662
1681
  return { col, row };
@@ -2068,67 +2087,6 @@ class LazyTranslatedString extends String {
2068
2087
  }
2069
2088
  }
2070
2089
 
2071
- /** Reference of a cell (eg. A1, $B$5) */
2072
- const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2073
- // Same as above, but matches the exact string (nothing before or after)
2074
- const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2075
- /** Reference of a column header (eg. A, AB, $A) */
2076
- const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
2077
- /** Reference of a row header (eg. 1, $1) */
2078
- const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
2079
- /** Reference of a column (eg. A, $CA, Sheet1!B) */
2080
- const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2081
- /** Reference of a row (eg. 1, 59, Sheet1!9) */
2082
- const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
2083
- /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
2084
- const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
2085
- /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
2086
- const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
2087
- /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
2088
- const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
2089
- "(" +
2090
- [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
2091
- ")" +
2092
- /$/.source, "i");
2093
- /**
2094
- * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
2095
- */
2096
- function isColReference(xc) {
2097
- return colReference.test(xc);
2098
- }
2099
- /**
2100
- * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
2101
- */
2102
- function isRowReference(xc) {
2103
- return rowReference.test(xc);
2104
- }
2105
- function isColHeader(str) {
2106
- return colHeader.test(str);
2107
- }
2108
- function isRowHeader(str) {
2109
- return rowHeader.test(str);
2110
- }
2111
- /**
2112
- * Return true if the given xc is the reference of a single cell,
2113
- * without any specified sheet (e.g. A1)
2114
- */
2115
- function isSingleCellReference(xc) {
2116
- return singleCellReference.test(xc);
2117
- }
2118
- function splitReference(ref) {
2119
- if (!ref.includes("!")) {
2120
- return { xc: ref };
2121
- }
2122
- const parts = ref.split("!");
2123
- const xc = parts.pop();
2124
- const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
2125
- return { sheetName, xc };
2126
- }
2127
- /** Return a reference SheetName!xc from the given arguments */
2128
- function getFullReference(sheetName, xc) {
2129
- return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
2130
- }
2131
-
2132
2090
  /**
2133
2091
  * Convert from a cartesian reference to a Zone
2134
2092
  * The range boundaries will be kept in the same order as the
@@ -2146,63 +2104,55 @@ function getFullReference(sheetName, xc) {
2146
2104
  *
2147
2105
  */
2148
2106
  function toZoneWithoutBoundaryChanges(xc) {
2149
- if (xc.includes("!")) {
2150
- xc = xc.split("!").at(-1);
2151
- }
2152
- if (xc.includes("$")) {
2153
- xc = xc.replaceAll("$", "");
2154
- }
2155
- let firstRangePart = "";
2156
- let secondRangePart;
2157
- if (xc.includes(":")) {
2158
- [firstRangePart, secondRangePart] = xc.split(":");
2159
- firstRangePart = firstRangePart.trim();
2160
- secondRangePart = secondRangePart.trim();
2161
- }
2162
- else {
2163
- firstRangePart = xc.trim();
2164
- }
2107
+ const chars = new TokenizingChars(xc);
2108
+ consumeSpaces(chars);
2109
+ const sheetSeparatorIndex = xc.indexOf("!");
2110
+ if (sheetSeparatorIndex !== -1) {
2111
+ chars.advanceBy(sheetSeparatorIndex + 1);
2112
+ }
2113
+ const leftLetters = consumeLetters(chars);
2114
+ const leftNumbers = consumeDigits(chars);
2165
2115
  let top, bottom, left, right;
2166
2116
  let fullCol = false;
2167
2117
  let fullRow = false;
2168
2118
  let hasHeader = false;
2169
- if (isColReference(firstRangePart)) {
2170
- left = right = lettersToNumber(firstRangePart);
2119
+ if (leftNumbers === -1) {
2120
+ left = right = leftLetters - 1;
2171
2121
  top = bottom = 0;
2172
2122
  fullCol = true;
2173
2123
  }
2174
- else if (isRowReference(firstRangePart)) {
2175
- top = bottom = parseInt(firstRangePart, 10) - 1;
2124
+ else if (leftLetters === -1) {
2125
+ top = bottom = leftNumbers - 1;
2176
2126
  left = right = 0;
2177
2127
  fullRow = true;
2178
2128
  }
2179
2129
  else {
2180
- const c = toCartesian(firstRangePart);
2181
- left = right = c.col;
2182
- top = bottom = c.row;
2130
+ left = right = leftLetters - 1;
2131
+ top = bottom = leftNumbers - 1;
2183
2132
  hasHeader = true;
2184
2133
  }
2185
- if (secondRangePart) {
2186
- if (isColReference(secondRangePart)) {
2187
- right = lettersToNumber(secondRangePart);
2134
+ consumeSpaces(chars);
2135
+ if (chars.current === ":") {
2136
+ chars.advanceBy(1);
2137
+ consumeSpaces(chars);
2138
+ const rightLetters = consumeLetters(chars);
2139
+ const rightNumbers = consumeDigits(chars);
2140
+ if (rightNumbers === -1) {
2141
+ right = rightLetters - 1;
2188
2142
  fullCol = true;
2189
2143
  }
2190
- else if (isRowReference(secondRangePart)) {
2191
- bottom = parseInt(secondRangePart, 10) - 1;
2144
+ else if (rightLetters === -1) {
2145
+ bottom = rightNumbers - 1;
2192
2146
  fullRow = true;
2193
2147
  }
2194
2148
  else {
2195
- const c = toCartesian(secondRangePart);
2196
- right = c.col;
2197
- bottom = c.row;
2149
+ right = rightLetters - 1;
2150
+ bottom = rightNumbers - 1;
2198
2151
  top = fullCol ? bottom : top;
2199
2152
  left = fullRow ? right : left;
2200
2153
  hasHeader = true;
2201
2154
  }
2202
2155
  }
2203
- if (fullCol && fullRow) {
2204
- throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2205
- }
2206
2156
  const zone = {
2207
2157
  top,
2208
2158
  left,
@@ -2231,7 +2181,16 @@ function toZoneWithoutBoundaryChanges(xc) {
2231
2181
  */
2232
2182
  function toUnboundedZone(xc) {
2233
2183
  const zone = toZoneWithoutBoundaryChanges(xc);
2234
- return reorderZone(zone);
2184
+ const orderedZone = reorderZone(zone);
2185
+ const bottom = orderedZone.bottom;
2186
+ const right = orderedZone.right;
2187
+ if ((bottom !== undefined && bottom > MAX_ROW) || (right !== undefined && right > MAX_COL)) {
2188
+ throw new Error(`Range string out of bounds: ${xc}`); // limit the size of the zone for performance
2189
+ }
2190
+ if (bottom === undefined && right === undefined) {
2191
+ throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2192
+ }
2193
+ return orderedZone;
2235
2194
  }
2236
2195
  /**
2237
2196
  * Convert from a cartesian reference to a Zone.
@@ -3348,7 +3307,7 @@ function isDateAfter(date, dateAfter) {
3348
3307
  */
3349
3308
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3350
3309
  decimalSeparator = escapeRegExp(decimalSeparator);
3351
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3310
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3352
3311
  });
3353
3312
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3354
3313
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -5967,6 +5926,67 @@ function scrollDelay(value) {
5967
5926
  return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
5968
5927
  }
5969
5928
 
5929
+ /** Reference of a cell (eg. A1, $B$5) */
5930
+ const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
5931
+ // Same as above, but matches the exact string (nothing before or after)
5932
+ const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
5933
+ /** Reference of a column header (eg. A, AB, $A) */
5934
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
5935
+ /** Reference of a row header (eg. 1, $1) */
5936
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
5937
+ /** Reference of a column (eg. A, $CA, Sheet1!B) */
5938
+ const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
5939
+ /** Reference of a row (eg. 1, 59, Sheet1!9) */
5940
+ const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
5941
+ /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
5942
+ const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
5943
+ /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
5944
+ const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
5945
+ /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
5946
+ const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
5947
+ "(" +
5948
+ [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
5949
+ ")" +
5950
+ /$/.source, "i");
5951
+ /**
5952
+ * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
5953
+ */
5954
+ function isColReference(xc) {
5955
+ return colReference.test(xc);
5956
+ }
5957
+ /**
5958
+ * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
5959
+ */
5960
+ function isRowReference(xc) {
5961
+ return rowReference.test(xc);
5962
+ }
5963
+ function isColHeader(str) {
5964
+ return colHeader.test(str);
5965
+ }
5966
+ function isRowHeader(str) {
5967
+ return rowHeader.test(str);
5968
+ }
5969
+ /**
5970
+ * Return true if the given xc is the reference of a single cell,
5971
+ * without any specified sheet (e.g. A1)
5972
+ */
5973
+ function isSingleCellReference(xc) {
5974
+ return singleCellReference.test(xc);
5975
+ }
5976
+ function splitReference(ref) {
5977
+ if (!ref.includes("!")) {
5978
+ return { xc: ref };
5979
+ }
5980
+ const parts = ref.split("!");
5981
+ const xc = parts.pop();
5982
+ const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
5983
+ return { sheetName, xc };
5984
+ }
5985
+ /** Return a reference SheetName!xc from the given arguments */
5986
+ function getFullReference(sheetName, xc) {
5987
+ return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
5988
+ }
5989
+
5970
5990
  class RangeImpl {
5971
5991
  getSheetSize;
5972
5992
  _zone;
@@ -6292,6 +6312,13 @@ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6292
6312
  }
6293
6313
  return name;
6294
6314
  }
6315
+ function isSheetNameEqual(name1, name2) {
6316
+ if (name1 === undefined || name2 === undefined) {
6317
+ return false;
6318
+ }
6319
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6320
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6321
+ }
6295
6322
 
6296
6323
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6297
6324
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
@@ -8155,10 +8182,9 @@ const AGGREGATOR_NAMES = {
8155
8182
  avg: _t("Average"),
8156
8183
  sum: _t("Sum"),
8157
8184
  };
8158
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8159
8185
  const AGGREGATORS_BY_FIELD_TYPE = {
8160
- integer: NUMBER_CHAR_AGGREGATORS,
8161
- char: NUMBER_CHAR_AGGREGATORS,
8186
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8187
+ char: ["count_distinct", "count"],
8162
8188
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8163
8189
  };
8164
8190
  const AGGREGATORS = {};
@@ -9510,7 +9536,10 @@ function proxifyStoreMutation(store, callback) {
9510
9536
  const functionProxy = new Proxy(value, {
9511
9537
  // trap the function call
9512
9538
  apply(target, thisArg, argArray) {
9513
- Reflect.apply(target, thisStore, argArray);
9539
+ const res = Reflect.apply(target, thisStore, argArray);
9540
+ if (res === "noStateChange") {
9541
+ return;
9542
+ }
9514
9543
  callback();
9515
9544
  },
9516
9545
  });
@@ -9532,7 +9561,7 @@ function getDependencyContainer(env) {
9532
9561
  const ModelStore = createAbstractStore("Model");
9533
9562
 
9534
9563
  class RendererStore {
9535
- mutators = ["register", "unRegister"];
9564
+ mutators = ["register", "unRegister", "drawLayer"];
9536
9565
  renderers = {};
9537
9566
  register(renderer) {
9538
9567
  if (!renderer.renderingLayers.length) {
@@ -9552,14 +9581,14 @@ class RendererStore {
9552
9581
  }
9553
9582
  drawLayer(context, layer) {
9554
9583
  const renderers = this.renderers[layer];
9555
- if (!renderers) {
9556
- return;
9557
- }
9558
- for (const renderer of renderers) {
9559
- context.ctx.save();
9560
- renderer.drawLayer(context, layer);
9561
- context.ctx.restore();
9584
+ if (renderers) {
9585
+ for (const renderer of renderers) {
9586
+ context.ctx.save();
9587
+ renderer.drawLayer(context, layer);
9588
+ context.ctx.restore();
9589
+ }
9562
9590
  }
9591
+ return "noStateChange";
9563
9592
  }
9564
9593
  }
9565
9594
 
@@ -9612,16 +9641,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9612
9641
  focusComposer(listener, args) {
9613
9642
  this.activeComposer = listener;
9614
9643
  if (this.getters.isReadonly()) {
9615
- return;
9644
+ return "noStateChange";
9616
9645
  }
9617
9646
  this._focusMode = args.focusMode || "contentFocus";
9618
9647
  if (this._focusMode !== "inactive") {
9619
9648
  this.setComposerContent(args);
9620
9649
  }
9650
+ return;
9621
9651
  }
9622
9652
  focusActiveComposer(args) {
9623
9653
  if (this.getters.isReadonly()) {
9624
- return;
9654
+ return "noStateChange";
9625
9655
  }
9626
9656
  if (!this.activeComposer) {
9627
9657
  throw new Error("No composer is registered");
@@ -9630,6 +9660,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9630
9660
  if (this._focusMode !== "inactive") {
9631
9661
  this.setComposerContent(args);
9632
9662
  }
9663
+ return;
9633
9664
  }
9634
9665
  /**
9635
9666
  * Start the edition or update the content if it's already started.
@@ -9979,7 +10010,7 @@ function getDefinedAxis(definition) {
9979
10010
  }
9980
10011
  function formatChartDatasetValue(axisFormats, locale) {
9981
10012
  return (value, axisId) => {
9982
- const format = axisId ? axisFormats?.[axisId] : undefined;
10013
+ const format = axisFormats?.[axisId];
9983
10014
  return formatTickValue({ format, locale })(value);
9984
10015
  };
9985
10016
  }
@@ -10143,7 +10174,7 @@ function drawPieChartValues(chart, options, ctx) {
10143
10174
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10144
10175
  ctx.fillStyle = chartFontColor(options.background);
10145
10176
  ctx.strokeStyle = options.background || "#ffffff";
10146
- const displayValue = options.callback(value);
10177
+ const displayValue = options.callback(value, "y");
10147
10178
  drawTextWithBackground(displayValue, x, y, ctx);
10148
10179
  }
10149
10180
  }
@@ -19033,6 +19064,9 @@ const PIVOT_VALUE = {
19033
19064
  };
19034
19065
  }
19035
19066
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19067
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19068
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
19069
+ }
19036
19070
  return pivot.getPivotCellValueAndFormat(_measure, domain);
19037
19071
  },
19038
19072
  };
@@ -19064,6 +19098,9 @@ const PIVOT_HEADER = {
19064
19098
  };
19065
19099
  }
19066
19100
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19101
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19102
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
19103
+ }
19067
19104
  const lastNode = domain.at(-1);
19068
19105
  if (lastNode?.field === "measure") {
19069
19106
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -19286,6 +19323,9 @@ function isEmpty(data) {
19286
19323
  return data === undefined || data.value === null;
19287
19324
  }
19288
19325
  const getNeutral = { number: 0, string: "", boolean: false };
19326
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
19327
+ return Math.abs(value1 - value2) < epsilon;
19328
+ }
19289
19329
  const EQ = {
19290
19330
  description: _t("Equal."),
19291
19331
  args: [
@@ -19307,6 +19347,9 @@ const EQ = {
19307
19347
  if (typeof _value2 === "string") {
19308
19348
  _value2 = _value2.toUpperCase();
19309
19349
  }
19350
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
19351
+ return { value: areAlmostEqual(_value1, _value2) };
19352
+ }
19310
19353
  return { value: _value1 === _value2 };
19311
19354
  },
19312
19355
  };
@@ -19346,6 +19389,9 @@ const GT = {
19346
19389
  ],
19347
19390
  compute: function (value1, value2) {
19348
19391
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19392
+ if (typeof v1 === "number" && typeof v2 === "number") {
19393
+ return !areAlmostEqual(v1, v2) && v1 > v2;
19394
+ }
19349
19395
  return v1 > v2;
19350
19396
  });
19351
19397
  },
@@ -19361,6 +19407,9 @@ const GTE = {
19361
19407
  ],
19362
19408
  compute: function (value1, value2) {
19363
19409
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19410
+ if (typeof v1 === "number" && typeof v2 === "number") {
19411
+ return areAlmostEqual(v1, v2) || v1 > v2;
19412
+ }
19364
19413
  return v1 >= v2;
19365
19414
  });
19366
19415
  },
@@ -20967,7 +21016,7 @@ class AbstractComposerStore extends SpreadsheetStore {
20967
21016
  .find((token) => {
20968
21017
  const { xc, sheetName: sheet } = splitReference(token.value);
20969
21018
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
20970
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
21019
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
20971
21020
  return false;
20972
21021
  }
20973
21022
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -24578,7 +24627,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
24578
24627
  ({ xc, sheetName } = splitReference(reference));
24579
24628
  let rangeSheetIndex;
24580
24629
  if (sheetName) {
24581
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
24630
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
24582
24631
  if (index < 0) {
24583
24632
  throw new Error("Unable to find a sheet with the name " + sheetName);
24584
24633
  }
@@ -24919,7 +24968,7 @@ function convertFormula(formula, data) {
24919
24968
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
24920
24969
  externalRefId = Number(externalRefId) - 1;
24921
24970
  cellRef = cellRef.replace(/\$/g, "");
24922
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
24971
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
24923
24972
  if (sheetIndex === -1) {
24924
24973
  return match;
24925
24974
  }
@@ -25570,7 +25619,7 @@ function convertPivotTableConfig(pivotTable) {
25570
25619
  */
25571
25620
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
25572
25621
  for (let tableSheet of convertedSheets) {
25573
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
25622
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
25574
25623
  for (let table of tables) {
25575
25624
  const tabRef = table.name + "[";
25576
25625
  for (let sheet of convertedSheets) {
@@ -32405,12 +32454,20 @@ class HoveredCellStore extends SpreadsheetStore {
32405
32454
  }
32406
32455
  }
32407
32456
  hover(position) {
32457
+ if (position.col === this.col && position.row === this.row) {
32458
+ return "noStateChange";
32459
+ }
32408
32460
  this.col = position.col;
32409
32461
  this.row = position.row;
32462
+ return;
32410
32463
  }
32411
32464
  clear() {
32465
+ if (this.col === undefined && this.row === undefined) {
32466
+ return "noStateChange";
32467
+ }
32412
32468
  this.col = undefined;
32413
32469
  this.row = undefined;
32470
+ return;
32414
32471
  }
32415
32472
  }
32416
32473
 
@@ -32432,7 +32489,11 @@ class CellPopoverStore extends SpreadsheetStore {
32432
32489
  this.persistentPopover = { col, row, sheetId, type };
32433
32490
  }
32434
32491
  close() {
32492
+ if (!this.persistentPopover) {
32493
+ return "noStateChange";
32494
+ }
32435
32495
  this.persistentPopover = undefined;
32496
+ return;
32436
32497
  }
32437
32498
  get persistentCellPopover() {
32438
32499
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -40264,10 +40325,18 @@ class ChartPanel extends owl.Component {
40264
40325
  }
40265
40326
 
40266
40327
  class DOMFocusableElementStore {
40267
- mutators = ["setFocusableElement"];
40328
+ mutators = ["setFocusableElement", "focus"];
40268
40329
  focusableElement = undefined;
40269
40330
  setFocusableElement(element) {
40270
40331
  this.focusableElement = element;
40332
+ return "noStateChange";
40333
+ }
40334
+ focus() {
40335
+ if (this.focusableElement === document.activeElement) {
40336
+ return "noStateChange";
40337
+ }
40338
+ this.focusableElement?.focus();
40339
+ return;
40271
40340
  }
40272
40341
  }
40273
40342
 
@@ -40857,7 +40926,7 @@ class Composer extends owl.Component {
40857
40926
  if (document.activeElement === this.contentHelper.el &&
40858
40927
  this.props.composerStore.editionMode === "inactive" &&
40859
40928
  !this.props.isDefaultFocus) {
40860
- this.DOMFocusableElementStore.focusableElement?.focus();
40929
+ this.DOMFocusableElementStore.focus();
40861
40930
  }
40862
40931
  });
40863
40932
  owl.useEffect(() => {
@@ -41297,12 +41366,13 @@ class StandaloneComposerStore extends AbstractComposerStore {
41297
41366
  return providersDefinitions;
41298
41367
  }
41299
41368
  getComposerContent() {
41369
+ let content = this._currentContent;
41300
41370
  if (this.editionMode === "inactive") {
41301
41371
  // References in the content might not be linked to the current active sheet
41302
41372
  // We here force the sheet name prefix for all references that are not in
41303
41373
  // the current active sheet
41304
41374
  const defaultRangeSheetId = this.args().defaultRangeSheetId;
41305
- return rangeTokenize(this.args().content)
41375
+ content = rangeTokenize(this.args().content)
41306
41376
  .map((token) => {
41307
41377
  if (token.type === "REFERENCE") {
41308
41378
  const range = this.getters.getRangeFromSheetXC(defaultRangeSheetId, token.value);
@@ -41312,7 +41382,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
41312
41382
  })
41313
41383
  .join("");
41314
41384
  }
41315
- return this._currentContent;
41385
+ return localizeContent(content, this.getters.getLocale());
41316
41386
  }
41317
41387
  stopEdition() {
41318
41388
  this._stopEdition();
@@ -45058,6 +45128,9 @@ class PivotMeasureEditor extends owl.Component {
45058
45128
  }
45059
45129
  return undefined;
45060
45130
  }
45131
+ get isCalculatedMeasureInvalid() {
45132
+ return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
45133
+ }
45061
45134
  }
45062
45135
 
45063
45136
  css /* scss */ `
@@ -46604,7 +46677,12 @@ class SpreadsheetPivot {
46604
46677
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
46605
46678
  }
46606
46679
  else {
46607
- entry[field.name] = cell;
46680
+ if (field.type === "char") {
46681
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
46682
+ }
46683
+ else {
46684
+ entry[field.name] = cell;
46685
+ }
46608
46686
  }
46609
46687
  }
46610
46688
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -51633,10 +51711,6 @@ function useGridDrawing(refName, model, canvasSize) {
51633
51711
  ctx.scale(dpr, dpr);
51634
51712
  for (const layer of OrderedLayers()) {
51635
51713
  model.drawLayer(renderingContext, layer);
51636
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
51637
- // it does not mutate anything. Most importantly it's used
51638
- // during rendering. Invoking a mutator during rendering would
51639
- // trigger another rendering, ultimately resulting in an infinite loop.
51640
51714
  rendererStore.drawLayer(renderingContext, layer);
51641
51715
  }
51642
51716
  }
@@ -52326,7 +52400,7 @@ class Grid extends owl.Component {
52326
52400
  this.cellPopovers = useStore(CellPopoverStore);
52327
52401
  owl.useEffect(() => {
52328
52402
  if (!this.sidePanel.isOpen) {
52329
- this.DOMFocusableElementStore.focusableElement?.focus();
52403
+ this.DOMFocusableElementStore.focus();
52330
52404
  }
52331
52405
  }, () => [this.sidePanel.isOpen]);
52332
52406
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -52536,7 +52610,7 @@ class Grid extends owl.Component {
52536
52610
  focusDefaultElement() {
52537
52611
  if (!this.env.model.getters.getSelectedFigureId() &&
52538
52612
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
52539
- this.DOMFocusableElementStore.focusableElement?.focus();
52613
+ this.DOMFocusableElementStore.focus();
52540
52614
  }
52541
52615
  }
52542
52616
  get gridEl() {
@@ -52881,6 +52955,322 @@ class Grid extends owl.Component {
52881
52955
  }
52882
52956
  }
52883
52957
 
52958
+ css /* scss */ `
52959
+ .o_pivot_html_renderer {
52960
+ width: 100%;
52961
+ border-collapse: collapse;
52962
+
52963
+ &:hover {
52964
+ cursor: pointer;
52965
+ }
52966
+
52967
+ td,
52968
+ th {
52969
+ border: 1px solid #dee2e6;
52970
+ background-color: #fff;
52971
+ padding: 0.3rem;
52972
+ white-space: nowrap;
52973
+
52974
+ &:hover {
52975
+ filter: brightness(0.9);
52976
+ }
52977
+ }
52978
+
52979
+ td {
52980
+ text-align: right;
52981
+ }
52982
+
52983
+ th {
52984
+ background-color: #f5f5f5;
52985
+ font-weight: bold;
52986
+ color: black;
52987
+ }
52988
+
52989
+ .o_missing_value {
52990
+ color: #46646d;
52991
+ background: #e7f2f6;
52992
+ }
52993
+ }
52994
+ `;
52995
+ class PivotHTMLRenderer extends owl.Component {
52996
+ static template = "o_spreadsheet.PivotHTMLRenderer";
52997
+ static components = { Checkbox };
52998
+ static props = {
52999
+ pivotId: String,
53000
+ onCellClicked: Function,
53001
+ };
53002
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
53003
+ data = {
53004
+ columns: [],
53005
+ rows: [],
53006
+ values: [],
53007
+ };
53008
+ state = owl.useState({
53009
+ showMissingValuesOnly: false,
53010
+ });
53011
+ setup() {
53012
+ const table = this.pivot.getTableStructure();
53013
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
53014
+ this.data = {
53015
+ columns: this._buildColHeaders(formulaId, table),
53016
+ rows: this._buildRowHeaders(formulaId, table),
53017
+ values: this._buildValues(formulaId, table),
53018
+ };
53019
+ }
53020
+ get tracker() {
53021
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
53022
+ }
53023
+ // ---------------------------------------------------------------------
53024
+ // Missing values building
53025
+ // ---------------------------------------------------------------------
53026
+ /**
53027
+ * Retrieve the data to display in the Pivot Table
53028
+ * In the case when showMissingValuesOnly is false, the returned value
53029
+ * is the complete data
53030
+ * In the case when showMissingValuesOnly is true, the returned value is
53031
+ * the data which contains only missing values in the rows and cols. In
53032
+ * the rows, we also return the parent rows of rows which contains missing
53033
+ * values, to give context to the user.
53034
+ *
53035
+ */
53036
+ getTableData() {
53037
+ if (!this.state.showMissingValuesOnly) {
53038
+ return this.data;
53039
+ }
53040
+ const colIndexes = this.getColumnsIndexes();
53041
+ const rowIndexes = this.getRowsIndexes();
53042
+ const columns = this.buildColumnsMissing(colIndexes);
53043
+ const rows = this.buildRowsMissing(rowIndexes);
53044
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
53045
+ return { columns, rows, values };
53046
+ }
53047
+ /**
53048
+ * Retrieve the parents of the given row
53049
+ * ex:
53050
+ * Australia
53051
+ * January
53052
+ * February
53053
+ * The parent of "January" is "Australia"
53054
+ */
53055
+ addRecursiveRow(index) {
53056
+ const rows = this.pivot.getTableStructure().rows;
53057
+ const row = [...rows[index].values];
53058
+ if (row.length <= 1) {
53059
+ return [index];
53060
+ }
53061
+ row.pop();
53062
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
53063
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
53064
+ }
53065
+ /**
53066
+ * Create the columns to be used, based on the indexes of the columns in
53067
+ * which a missing value is present
53068
+ *
53069
+ */
53070
+ buildColumnsMissing(indexes) {
53071
+ // columnsMap explode the columns in an array of array of the same
53072
+ // size with the index of each column, repeated 'span' times.
53073
+ // ex:
53074
+ // | A | B |
53075
+ // | 1 | 2 | 3 |
53076
+ // => [
53077
+ // [0, 0, 1]
53078
+ // [0, 1, 2]
53079
+ // ]
53080
+ const columnsMap = [];
53081
+ for (const column of this.data.columns) {
53082
+ const columnMap = [];
53083
+ for (const index in column) {
53084
+ for (let i = 0; i < column[index].span; i++) {
53085
+ columnMap.push(parseInt(index, 10));
53086
+ }
53087
+ }
53088
+ columnsMap.push(columnMap);
53089
+ }
53090
+ // Remove the columns that are not present in indexes
53091
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
53092
+ if (!indexes.includes(i)) {
53093
+ for (const columnMap of columnsMap) {
53094
+ columnMap.splice(i, 1);
53095
+ }
53096
+ }
53097
+ }
53098
+ // Build the columns
53099
+ const columns = [];
53100
+ for (const mapIndex in columnsMap) {
53101
+ const column = [];
53102
+ let index = undefined;
53103
+ let span = 1;
53104
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
53105
+ if (index !== columnsMap[mapIndex][i]) {
53106
+ if (index !== undefined) {
53107
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53108
+ }
53109
+ index = columnsMap[mapIndex][i];
53110
+ span = 1;
53111
+ }
53112
+ else {
53113
+ span++;
53114
+ }
53115
+ }
53116
+ if (index !== undefined) {
53117
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53118
+ }
53119
+ columns.push(column);
53120
+ }
53121
+ return columns;
53122
+ }
53123
+ /**
53124
+ * Create the rows to be used, based on the indexes of the rows in
53125
+ * which a missing value is present.
53126
+ */
53127
+ buildRowsMissing(indexes) {
53128
+ return indexes.map((index) => this.data.rows[index]);
53129
+ }
53130
+ /**
53131
+ * Create the value to be used, based on the indexes of the columns and
53132
+ * rows in which a missing value is present.
53133
+ */
53134
+ buildValuesMissing(colIndexes, rowIndexes) {
53135
+ const values = colIndexes.map(() => []);
53136
+ for (const row of rowIndexes) {
53137
+ for (const col in colIndexes) {
53138
+ values[col].push(this.data.values[colIndexes[col]][row]);
53139
+ }
53140
+ }
53141
+ return values;
53142
+ }
53143
+ getColumnsIndexes() {
53144
+ const indexes = new Set();
53145
+ for (let i = 0; i < this.data.columns.length; i++) {
53146
+ const exploded = [];
53147
+ for (let y = 0; y < this.data.columns[i].length; y++) {
53148
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
53149
+ exploded.push(this.data.columns[i][y]);
53150
+ }
53151
+ }
53152
+ for (let y = 0; y < exploded.length; y++) {
53153
+ if (exploded[y].isMissing) {
53154
+ indexes.add(y);
53155
+ }
53156
+ }
53157
+ }
53158
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
53159
+ const values = this.data.values[i];
53160
+ if (values.find((x) => x.isMissing)) {
53161
+ indexes.add(i);
53162
+ }
53163
+ }
53164
+ return Array.from(indexes).sort((a, b) => a - b);
53165
+ }
53166
+ getRowsIndexes() {
53167
+ const rowIndexes = new Set();
53168
+ for (let i = 0; i < this.data.rows.length; i++) {
53169
+ if (this.data.rows[i].isMissing) {
53170
+ rowIndexes.add(i);
53171
+ }
53172
+ for (const col of this.data.values) {
53173
+ if (col[i].isMissing) {
53174
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
53175
+ }
53176
+ }
53177
+ }
53178
+ return Array.from(rowIndexes).sort((a, b) => a - b);
53179
+ }
53180
+ // ---------------------------------------------------------------------
53181
+ // Data table creation
53182
+ // ---------------------------------------------------------------------
53183
+ _buildColHeaders(id, table) {
53184
+ const headers = [];
53185
+ for (const row of table.columns) {
53186
+ const current = [];
53187
+ for (const cell of row) {
53188
+ const args = [];
53189
+ for (let i = 0; i < cell.fields.length; i++) {
53190
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
53191
+ }
53192
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53193
+ const locale = this.env.model.getters.getLocale();
53194
+ if (domain.at(-1)?.field === "measure") {
53195
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
53196
+ current.push({
53197
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53198
+ value: formatValue(value, { format, locale }),
53199
+ span: cell.width,
53200
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53201
+ });
53202
+ }
53203
+ else {
53204
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53205
+ current.push({
53206
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53207
+ value: formatValue(value, { format, locale }),
53208
+ span: cell.width,
53209
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53210
+ });
53211
+ }
53212
+ }
53213
+ headers.push(current);
53214
+ }
53215
+ const last = headers[headers.length - 1];
53216
+ headers[headers.length - 1] = last.map((cell) => {
53217
+ if (!cell.isMissing) {
53218
+ cell.style = "color: #756f6f;";
53219
+ }
53220
+ return cell;
53221
+ });
53222
+ return headers;
53223
+ }
53224
+ _buildRowHeaders(id, table) {
53225
+ const headers = [];
53226
+ for (const row of table.rows) {
53227
+ const args = [];
53228
+ for (let i = 0; i < row.fields.length; i++) {
53229
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53230
+ }
53231
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53232
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53233
+ const locale = this.env.model.getters.getLocale();
53234
+ const cell = {
53235
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53236
+ value: formatValue(value, { format, locale }),
53237
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53238
+ };
53239
+ if (row.indent > 1) {
53240
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
53241
+ }
53242
+ headers.push(cell);
53243
+ }
53244
+ return headers;
53245
+ }
53246
+ _buildValues(id, table) {
53247
+ const values = [];
53248
+ for (const col of table.columns.at(-1) || []) {
53249
+ const current = [];
53250
+ const measure = toString(col.values[col.values.length - 1]);
53251
+ for (const row of table.rows) {
53252
+ const args = [];
53253
+ for (let i = 0; i < row.fields.length; i++) {
53254
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53255
+ }
53256
+ for (let i = 0; i < col.fields.length - 1; i++) {
53257
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
53258
+ }
53259
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53260
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
53261
+ const locale = this.env.model.getters.getLocale();
53262
+ current.push({
53263
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
53264
+ value: formatValue(value, { format, locale }),
53265
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
53266
+ });
53267
+ }
53268
+ values.push(current);
53269
+ }
53270
+ return values;
53271
+ }
53272
+ }
53273
+
52884
53274
  /**
52885
53275
  * BasePlugin
52886
53276
  *
@@ -56318,7 +56708,7 @@ class RangeAdapter {
56318
56708
  if (range.sheetId === cmd.sheetId) {
56319
56709
  return { changeType: "CHANGE", range };
56320
56710
  }
56321
- if (cmd.name && range.invalidSheetName === cmd.name) {
56711
+ if (isSheetNameEqual(range.invalidSheetName, cmd.name)) {
56322
56712
  const invalidSheetName = undefined;
56323
56713
  const sheetId = cmd.sheetId;
56324
56714
  const newRange = range.clone({ sheetId, invalidSheetName });
@@ -56903,7 +57293,7 @@ class SheetPlugin extends CorePlugin {
56903
57293
  if (name) {
56904
57294
  const unquotedName = getUnquotedSheetName(name);
56905
57295
  for (const key in this.sheetIdsMapName) {
56906
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
57296
+ if (isSheetNameEqual(key, unquotedName)) {
56907
57297
  return this.sheetIdsMapName[key];
56908
57298
  }
56909
57299
  }
@@ -57151,7 +57541,7 @@ class SheetPlugin extends CorePlugin {
57151
57541
  }
57152
57542
  const { orderedSheetIds, sheets } = this;
57153
57543
  const name = cmd.name && cmd.name.trim().toLowerCase();
57154
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
57544
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
57155
57545
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
57156
57546
  }
57157
57547
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -60113,10 +60503,9 @@ class Evaluator {
60113
60503
  return this.evaluatedCells.keysForSheet(sheetId);
60114
60504
  }
60115
60505
  getArrayFormulaSpreadingOn(position) {
60116
- const hasArrayFormulaResult = this.getEvaluatedCell(position).type !== CellValueType.empty &&
60117
- !this.getters.getCell(position)?.isFormula;
60118
- if (!hasArrayFormulaResult) {
60119
- return this.spreadingRelations.isArrayFormula(position) ? position : undefined;
60506
+ const isEmpty = this.getEvaluatedCell(position).type === CellValueType.empty;
60507
+ if (isEmpty) {
60508
+ return undefined;
60120
60509
  }
60121
60510
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
60122
60511
  return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
@@ -66054,6 +66443,55 @@ class HistoryPlugin extends UIPlugin {
66054
66443
  }
66055
66444
  }
66056
66445
 
66446
+ class PivotPresenceTracker {
66447
+ trackedValues = new Set();
66448
+ domainToArray(domain) {
66449
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
66450
+ }
66451
+ isValuePresent(measure, domain) {
66452
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66453
+ return this.trackedValues.has(key);
66454
+ }
66455
+ isHeaderPresent(domain) {
66456
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66457
+ return this.trackedValues.has(key);
66458
+ }
66459
+ trackValue(measure, domain) {
66460
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66461
+ this.trackedValues.add(key);
66462
+ }
66463
+ trackHeader(domain) {
66464
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66465
+ this.trackedValues.add(key);
66466
+ }
66467
+ }
66468
+
66469
+ class PivotPresencePlugin extends UIPlugin {
66470
+ static getters = ["getPivotPresenceTracker"];
66471
+ trackPresencePivotId;
66472
+ tracker;
66473
+ handle(cmd) {
66474
+ switch (cmd.type) {
66475
+ case "PIVOT_START_PRESENCE_TRACKING":
66476
+ this.tracker = new PivotPresenceTracker();
66477
+ this.trackPresencePivotId = cmd.pivotId;
66478
+ break;
66479
+ case "PIVOT_STOP_PRESENCE_TRACKING":
66480
+ this.trackPresencePivotId = undefined;
66481
+ break;
66482
+ }
66483
+ }
66484
+ getPivotPresenceTracker(pivotId) {
66485
+ if (this.trackPresencePivotId !== pivotId) {
66486
+ return undefined;
66487
+ }
66488
+ if (!this.tracker) {
66489
+ throw new Error("Tracker not initialized");
66490
+ }
66491
+ return this.tracker;
66492
+ }
66493
+ }
66494
+
66057
66495
  class SplitToColumnsPlugin extends UIPlugin {
66058
66496
  static getters = ["getAutomaticSeparator"];
66059
66497
  allowDispatch(cmd) {
@@ -68841,6 +69279,7 @@ const featurePluginRegistry = new Registry()
68841
69279
  .add("automatic_sum", AutomaticSumPlugin)
68842
69280
  .add("format", FormatPlugin)
68843
69281
  .add("insert_pivot", InsertPivotPlugin)
69282
+ .add("pivot_presence", PivotPresencePlugin)
68844
69283
  .add("split_to_columns", SplitToColumnsPlugin)
68845
69284
  .add("collaborative", CollaborativePlugin)
68846
69285
  .add("history", HistoryPlugin)
@@ -69221,11 +69660,11 @@ class BottomBarSheet extends owl.Component {
69221
69660
  if (ev.key === "Enter") {
69222
69661
  ev.preventDefault();
69223
69662
  this.stopEdition();
69224
- this.DOMFocusableElementStore.focusableElement?.focus();
69663
+ this.DOMFocusableElementStore.focus();
69225
69664
  }
69226
69665
  if (ev.key === "Escape") {
69227
69666
  this.cancelEdition();
69228
- this.DOMFocusableElementStore.focusableElement?.focus();
69667
+ this.DOMFocusableElementStore.focus();
69229
69668
  }
69230
69669
  }
69231
69670
  onMouseEventSheetName(ev) {
@@ -75816,6 +76255,7 @@ const components = {
75816
76255
  PivotDimensionOrder,
75817
76256
  PivotDimension,
75818
76257
  PivotLayoutConfigurator,
76258
+ PivotHTMLRenderer,
75819
76259
  PivotDeferUpdate,
75820
76260
  PivotTitleSection,
75821
76261
  CogWheelMenu,
@@ -75909,6 +76349,6 @@ exports.tokenColors = tokenColors;
75909
76349
  exports.tokenize = tokenize;
75910
76350
 
75911
76351
 
75912
- __info__.version = "18.1.19";
75913
- __info__.date = "2025-05-12T05:26:05.861Z";
75914
- __info__.hash = "44cc170";
76352
+ __info__.version = "18.1.21";
76353
+ __info__.date = "2025-05-20T05:54:45.398Z";
76354
+ __info__.hash = "89ed6a9";