@odoo/o-spreadsheet 18.0.27 → 18.0.29

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.0.27
6
- * @date 2025-05-12T05:25:47.149Z
7
- * @hash 9b36340
5
+ * @version 18.0.29
6
+ * @date 2025-05-20T05:54:57.329Z
7
+ * @hash 8213c0e
8
8
  */
9
9
 
10
10
  'use strict';
@@ -1454,18 +1454,53 @@ function lettersToNumber(letters) {
1454
1454
  let result = 0;
1455
1455
  const l = letters.length;
1456
1456
  for (let i = 0; i < l; i++) {
1457
- const charCode = letters.charCodeAt(i);
1458
- const colIndex = charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1457
+ const colIndex = charToNumber(letters[i]);
1459
1458
  result = result * 26 + colIndex;
1460
1459
  }
1461
1460
  return result - 1;
1462
1461
  }
1462
+ function charToNumber(char) {
1463
+ const charCode = char.charCodeAt(0);
1464
+ return charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1465
+ }
1463
1466
  function isCharALetter(char) {
1464
1467
  return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
1465
1468
  }
1466
1469
  function isCharADigit(char) {
1467
1470
  return char >= "0" && char <= "9";
1468
1471
  }
1472
+ // we limit the max column to 3 letters and max row to 7 digits for performance reasons
1473
+ const MAX_COL = lettersToNumber("ZZZ");
1474
+ const MAX_ROW = 9999998;
1475
+ function consumeSpaces(chars) {
1476
+ while (chars.current === " ") {
1477
+ chars.advanceBy(1);
1478
+ }
1479
+ }
1480
+ function consumeLetters(chars) {
1481
+ if (chars.current === "$")
1482
+ chars.advanceBy(1);
1483
+ if (!chars.current || !isCharALetter(chars.current)) {
1484
+ return -1;
1485
+ }
1486
+ let colCoordinate = 0;
1487
+ while (chars.current && isCharALetter(chars.current)) {
1488
+ colCoordinate = colCoordinate * 26 + charToNumber(chars.shift());
1489
+ }
1490
+ return colCoordinate;
1491
+ }
1492
+ function consumeDigits(chars) {
1493
+ if (chars.current === "$")
1494
+ chars.advanceBy(1);
1495
+ if (!chars.current || !isCharADigit(chars.current)) {
1496
+ return -1;
1497
+ }
1498
+ let num = 0;
1499
+ while (chars.current && isCharADigit(chars.current)) {
1500
+ num = num * 10 + Number(chars.shift());
1501
+ }
1502
+ return num;
1503
+ }
1469
1504
  /**
1470
1505
  * Convert a "XC" coordinate to cartesian coordinates.
1471
1506
  *
@@ -1476,33 +1511,17 @@ function isCharADigit(char) {
1476
1511
  * Note: it also accepts lowercase coordinates, but not fixed references
1477
1512
  */
1478
1513
  function toCartesian(xc) {
1479
- xc = xc.trim();
1480
- let letterPart = "";
1481
- let numberPart = "";
1482
- let i = 0;
1483
- // Process letter part
1484
- if (xc[i] === "$")
1485
- i++;
1486
- while (i < xc.length && isCharALetter(xc[i])) {
1487
- letterPart += xc[i++];
1488
- }
1489
- if (letterPart.length === 0 || letterPart.length > 3) {
1490
- // limit to max 3 letters for performance reasons
1514
+ const chars = new TokenizingChars(xc);
1515
+ consumeSpaces(chars);
1516
+ const letterPart = consumeLetters(chars);
1517
+ if (letterPart === -1 || !chars.current) {
1491
1518
  throw new Error(`Invalid cell description: ${xc}`);
1492
1519
  }
1493
- // Process number part
1494
- if (xc[i] === "$")
1495
- i++;
1496
- while (i < xc.length && isCharADigit(xc[i])) {
1497
- numberPart += xc[i++];
1498
- }
1499
- if (i !== xc.length || numberPart.length === 0 || numberPart.length > 7) {
1500
- // limit to max 7 numbers for performance reasons
1501
- throw new Error(`Invalid cell description: ${xc}`);
1502
- }
1503
- const col = lettersToNumber(letterPart);
1504
- const row = Number(numberPart) - 1;
1505
- if (isNaN(row)) {
1520
+ const num = consumeDigits(chars);
1521
+ consumeSpaces(chars);
1522
+ const col = letterPart - 1;
1523
+ const row = num - 1;
1524
+ if (!chars.isOver() || col > MAX_COL || row > MAX_ROW) {
1506
1525
  throw new Error(`Invalid cell description: ${xc}`);
1507
1526
  }
1508
1527
  return { col, row };
@@ -1914,67 +1933,6 @@ class LazyTranslatedString extends String {
1914
1933
  }
1915
1934
  }
1916
1935
 
1917
- /** Reference of a cell (eg. A1, $B$5) */
1918
- const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
1919
- // Same as above, but matches the exact string (nothing before or after)
1920
- const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
1921
- /** Reference of a column header (eg. A, AB, $A) */
1922
- const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
1923
- /** Reference of a row header (eg. 1, $1) */
1924
- const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
1925
- /** Reference of a column (eg. A, $CA, Sheet1!B) */
1926
- const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
1927
- /** Reference of a row (eg. 1, 59, Sheet1!9) */
1928
- const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
1929
- /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
1930
- const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
1931
- /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
1932
- const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
1933
- /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
1934
- const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
1935
- "(" +
1936
- [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
1937
- ")" +
1938
- /$/.source, "i");
1939
- /**
1940
- * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
1941
- */
1942
- function isColReference(xc) {
1943
- return colReference.test(xc);
1944
- }
1945
- /**
1946
- * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
1947
- */
1948
- function isRowReference(xc) {
1949
- return rowReference.test(xc);
1950
- }
1951
- function isColHeader(str) {
1952
- return colHeader.test(str);
1953
- }
1954
- function isRowHeader(str) {
1955
- return rowHeader.test(str);
1956
- }
1957
- /**
1958
- * Return true if the given xc is the reference of a single cell,
1959
- * without any specified sheet (e.g. A1)
1960
- */
1961
- function isSingleCellReference(xc) {
1962
- return singleCellReference.test(xc);
1963
- }
1964
- function splitReference(ref) {
1965
- if (!ref.includes("!")) {
1966
- return { xc: ref };
1967
- }
1968
- const parts = ref.split("!");
1969
- const xc = parts.pop();
1970
- const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
1971
- return { sheetName, xc };
1972
- }
1973
- /** Return a reference SheetName!xc from the given arguments */
1974
- function getFullReference(sheetName, xc) {
1975
- return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
1976
- }
1977
-
1978
1936
  /**
1979
1937
  * Convert from a cartesian reference to a Zone
1980
1938
  * The range boundaries will be kept in the same order as the
@@ -1992,63 +1950,55 @@ function getFullReference(sheetName, xc) {
1992
1950
  *
1993
1951
  */
1994
1952
  function toZoneWithoutBoundaryChanges(xc) {
1995
- if (xc.includes("!")) {
1996
- xc = xc.split("!").at(-1);
1997
- }
1998
- if (xc.includes("$")) {
1999
- xc = xc.replaceAll("$", "");
2000
- }
2001
- let firstRangePart = "";
2002
- let secondRangePart;
2003
- if (xc.includes(":")) {
2004
- [firstRangePart, secondRangePart] = xc.split(":");
2005
- firstRangePart = firstRangePart.trim();
2006
- secondRangePart = secondRangePart.trim();
2007
- }
2008
- else {
2009
- firstRangePart = xc.trim();
2010
- }
1953
+ const chars = new TokenizingChars(xc);
1954
+ consumeSpaces(chars);
1955
+ const sheetSeparatorIndex = xc.indexOf("!");
1956
+ if (sheetSeparatorIndex !== -1) {
1957
+ chars.advanceBy(sheetSeparatorIndex + 1);
1958
+ }
1959
+ const leftLetters = consumeLetters(chars);
1960
+ const leftNumbers = consumeDigits(chars);
2011
1961
  let top, bottom, left, right;
2012
1962
  let fullCol = false;
2013
1963
  let fullRow = false;
2014
1964
  let hasHeader = false;
2015
- if (isColReference(firstRangePart)) {
2016
- left = right = lettersToNumber(firstRangePart);
1965
+ if (leftNumbers === -1) {
1966
+ left = right = leftLetters - 1;
2017
1967
  top = bottom = 0;
2018
1968
  fullCol = true;
2019
1969
  }
2020
- else if (isRowReference(firstRangePart)) {
2021
- top = bottom = parseInt(firstRangePart, 10) - 1;
1970
+ else if (leftLetters === -1) {
1971
+ top = bottom = leftNumbers - 1;
2022
1972
  left = right = 0;
2023
1973
  fullRow = true;
2024
1974
  }
2025
1975
  else {
2026
- const c = toCartesian(firstRangePart);
2027
- left = right = c.col;
2028
- top = bottom = c.row;
1976
+ left = right = leftLetters - 1;
1977
+ top = bottom = leftNumbers - 1;
2029
1978
  hasHeader = true;
2030
1979
  }
2031
- if (secondRangePart) {
2032
- if (isColReference(secondRangePart)) {
2033
- right = lettersToNumber(secondRangePart);
1980
+ consumeSpaces(chars);
1981
+ if (chars.current === ":") {
1982
+ chars.advanceBy(1);
1983
+ consumeSpaces(chars);
1984
+ const rightLetters = consumeLetters(chars);
1985
+ const rightNumbers = consumeDigits(chars);
1986
+ if (rightNumbers === -1) {
1987
+ right = rightLetters - 1;
2034
1988
  fullCol = true;
2035
1989
  }
2036
- else if (isRowReference(secondRangePart)) {
2037
- bottom = parseInt(secondRangePart, 10) - 1;
1990
+ else if (rightLetters === -1) {
1991
+ bottom = rightNumbers - 1;
2038
1992
  fullRow = true;
2039
1993
  }
2040
1994
  else {
2041
- const c = toCartesian(secondRangePart);
2042
- right = c.col;
2043
- bottom = c.row;
1995
+ right = rightLetters - 1;
1996
+ bottom = rightNumbers - 1;
2044
1997
  top = fullCol ? bottom : top;
2045
1998
  left = fullRow ? right : left;
2046
1999
  hasHeader = true;
2047
2000
  }
2048
2001
  }
2049
- if (fullCol && fullRow) {
2050
- throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2051
- }
2052
2002
  const zone = {
2053
2003
  top,
2054
2004
  left,
@@ -2077,7 +2027,16 @@ function toZoneWithoutBoundaryChanges(xc) {
2077
2027
  */
2078
2028
  function toUnboundedZone(xc) {
2079
2029
  const zone = toZoneWithoutBoundaryChanges(xc);
2080
- return reorderZone(zone);
2030
+ const orderedZone = reorderZone(zone);
2031
+ const bottom = orderedZone.bottom;
2032
+ const right = orderedZone.right;
2033
+ if ((bottom !== undefined && bottom > MAX_ROW) || (right !== undefined && right > MAX_COL)) {
2034
+ throw new Error(`Range string out of bounds: ${xc}`); // limit the size of the zone for performance
2035
+ }
2036
+ if (bottom === undefined && right === undefined) {
2037
+ throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
2038
+ }
2039
+ return orderedZone;
2081
2040
  }
2082
2041
  /**
2083
2042
  * Convert from a cartesian reference to a Zone.
@@ -3182,7 +3141,7 @@ function isDateAfter(date, dateAfter) {
3182
3141
  */
3183
3142
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3184
3143
  decimalSeparator = escapeRegExp(decimalSeparator);
3185
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3144
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3186
3145
  });
3187
3146
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3188
3147
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -5798,6 +5757,67 @@ function scrollDelay(value) {
5798
5757
  return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
5799
5758
  }
5800
5759
 
5760
+ /** Reference of a cell (eg. A1, $B$5) */
5761
+ const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
5762
+ // Same as above, but matches the exact string (nothing before or after)
5763
+ const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
5764
+ /** Reference of a column header (eg. A, AB, $A) */
5765
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
5766
+ /** Reference of a row header (eg. 1, $1) */
5767
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
5768
+ /** Reference of a column (eg. A, $CA, Sheet1!B) */
5769
+ const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
5770
+ /** Reference of a row (eg. 1, 59, Sheet1!9) */
5771
+ const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
5772
+ /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
5773
+ const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
5774
+ /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
5775
+ const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
5776
+ /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
5777
+ const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
5778
+ "(" +
5779
+ [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
5780
+ ")" +
5781
+ /$/.source, "i");
5782
+ /**
5783
+ * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
5784
+ */
5785
+ function isColReference(xc) {
5786
+ return colReference.test(xc);
5787
+ }
5788
+ /**
5789
+ * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
5790
+ */
5791
+ function isRowReference(xc) {
5792
+ return rowReference.test(xc);
5793
+ }
5794
+ function isColHeader(str) {
5795
+ return colHeader.test(str);
5796
+ }
5797
+ function isRowHeader(str) {
5798
+ return rowHeader.test(str);
5799
+ }
5800
+ /**
5801
+ * Return true if the given xc is the reference of a single cell,
5802
+ * without any specified sheet (e.g. A1)
5803
+ */
5804
+ function isSingleCellReference(xc) {
5805
+ return singleCellReference.test(xc);
5806
+ }
5807
+ function splitReference(ref) {
5808
+ if (!ref.includes("!")) {
5809
+ return { xc: ref };
5810
+ }
5811
+ const parts = ref.split("!");
5812
+ const xc = parts.pop();
5813
+ const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
5814
+ return { sheetName, xc };
5815
+ }
5816
+ /** Return a reference SheetName!xc from the given arguments */
5817
+ function getFullReference(sheetName, xc) {
5818
+ return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
5819
+ }
5820
+
5801
5821
  class RangeImpl {
5802
5822
  getSheetSize;
5803
5823
  _zone;
@@ -6123,6 +6143,13 @@ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6123
6143
  }
6124
6144
  return name;
6125
6145
  }
6146
+ function isSheetNameEqual(name1, name2) {
6147
+ if (name1 === undefined || name2 === undefined) {
6148
+ return false;
6149
+ }
6150
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6151
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6152
+ }
6126
6153
 
6127
6154
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6128
6155
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
@@ -7976,10 +8003,9 @@ const AGGREGATOR_NAMES = {
7976
8003
  avg: _t("Average"),
7977
8004
  sum: _t("Sum"),
7978
8005
  };
7979
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
7980
8006
  const AGGREGATORS_BY_FIELD_TYPE = {
7981
- integer: NUMBER_CHAR_AGGREGATORS,
7982
- char: NUMBER_CHAR_AGGREGATORS,
8007
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8008
+ char: ["count_distinct", "count"],
7983
8009
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
7984
8010
  };
7985
8011
  const AGGREGATORS = {};
@@ -9312,7 +9338,10 @@ function proxifyStoreMutation(store, callback) {
9312
9338
  const functionProxy = new Proxy(value, {
9313
9339
  // trap the function call
9314
9340
  apply(target, thisArg, argArray) {
9315
- Reflect.apply(target, thisStore, argArray);
9341
+ const res = Reflect.apply(target, thisStore, argArray);
9342
+ if (res === "noStateChange") {
9343
+ return;
9344
+ }
9316
9345
  callback();
9317
9346
  },
9318
9347
  });
@@ -9334,7 +9363,7 @@ function getDependencyContainer(env) {
9334
9363
  const ModelStore = createAbstractStore("Model");
9335
9364
 
9336
9365
  class RendererStore {
9337
- mutators = ["register", "unRegister"];
9366
+ mutators = ["register", "unRegister", "drawLayer"];
9338
9367
  renderers = {};
9339
9368
  register(renderer) {
9340
9369
  if (!renderer.renderingLayers.length) {
@@ -9354,14 +9383,14 @@ class RendererStore {
9354
9383
  }
9355
9384
  drawLayer(context, layer) {
9356
9385
  const renderers = this.renderers[layer];
9357
- if (!renderers) {
9358
- return;
9359
- }
9360
- for (const renderer of renderers) {
9361
- context.ctx.save();
9362
- renderer.drawLayer(context, layer);
9363
- context.ctx.restore();
9386
+ if (renderers) {
9387
+ for (const renderer of renderers) {
9388
+ context.ctx.save();
9389
+ renderer.drawLayer(context, layer);
9390
+ context.ctx.restore();
9391
+ }
9364
9392
  }
9393
+ return "noStateChange";
9365
9394
  }
9366
9395
  }
9367
9396
 
@@ -9414,16 +9443,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9414
9443
  focusComposer(listener, args) {
9415
9444
  this.activeComposer = listener;
9416
9445
  if (this.getters.isReadonly()) {
9417
- return;
9446
+ return "noStateChange";
9418
9447
  }
9419
9448
  this._focusMode = args.focusMode || "contentFocus";
9420
9449
  if (this._focusMode !== "inactive") {
9421
9450
  this.setComposerContent(args);
9422
9451
  }
9452
+ return;
9423
9453
  }
9424
9454
  focusActiveComposer(args) {
9425
9455
  if (this.getters.isReadonly()) {
9426
- return;
9456
+ return "noStateChange";
9427
9457
  }
9428
9458
  if (!this.activeComposer) {
9429
9459
  throw new Error("No composer is registered");
@@ -9432,6 +9462,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9432
9462
  if (this._focusMode !== "inactive") {
9433
9463
  this.setComposerContent(args);
9434
9464
  }
9465
+ return;
9435
9466
  }
9436
9467
  /**
9437
9468
  * Start the edition or update the content if it's already started.
@@ -11746,7 +11777,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
11746
11777
  ({ xc, sheetName } = splitReference(reference));
11747
11778
  let rangeSheetIndex;
11748
11779
  if (sheetName) {
11749
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
11780
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
11750
11781
  if (index < 0) {
11751
11782
  throw new Error("Unable to find a sheet with the name " + sheetName);
11752
11783
  }
@@ -12087,7 +12118,7 @@ function convertFormula(formula, data) {
12087
12118
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
12088
12119
  externalRefId = Number(externalRefId) - 1;
12089
12120
  cellRef = cellRef.replace(/\$/g, "");
12090
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
12121
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
12091
12122
  if (sheetIndex === -1) {
12092
12123
  return match;
12093
12124
  }
@@ -12750,7 +12781,7 @@ function convertPivotTableConfig(pivotTable) {
12750
12781
  */
12751
12782
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
12752
12783
  for (let tableSheet of convertedSheets) {
12753
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
12784
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
12754
12785
  for (let table of tables) {
12755
12786
  const tabRef = table.name + "[";
12756
12787
  for (let sheet of convertedSheets) {
@@ -25073,6 +25104,9 @@ const PIVOT_VALUE = {
25073
25104
  };
25074
25105
  }
25075
25106
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
25107
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
25108
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
25109
+ }
25076
25110
  return pivot.getPivotCellValueAndFormat(_measure, domain);
25077
25111
  },
25078
25112
  };
@@ -25104,6 +25138,9 @@ const PIVOT_HEADER = {
25104
25138
  };
25105
25139
  }
25106
25140
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
25141
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
25142
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
25143
+ }
25107
25144
  const lastNode = domain.at(-1);
25108
25145
  if (lastNode?.field === "measure") {
25109
25146
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -25326,6 +25363,9 @@ function isEmpty(data) {
25326
25363
  return data === undefined || data.value === null;
25327
25364
  }
25328
25365
  const getNeutral = { number: 0, string: "", boolean: false };
25366
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
25367
+ return Math.abs(value1 - value2) < epsilon;
25368
+ }
25329
25369
  const EQ = {
25330
25370
  description: _t("Equal."),
25331
25371
  args: [
@@ -25341,6 +25381,9 @@ const EQ = {
25341
25381
  if (typeof _value2 === "string") {
25342
25382
  _value2 = _value2.toUpperCase();
25343
25383
  }
25384
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
25385
+ return areAlmostEqual(_value1, _value2);
25386
+ }
25344
25387
  return _value1 === _value2;
25345
25388
  },
25346
25389
  };
@@ -25380,6 +25423,9 @@ const GT = {
25380
25423
  ],
25381
25424
  compute: function (value1, value2) {
25382
25425
  return applyRelationalOperator(value1, value2, (v1, v2) => {
25426
+ if (typeof v1 === "number" && typeof v2 === "number") {
25427
+ return !areAlmostEqual(v1, v2) && v1 > v2;
25428
+ }
25383
25429
  return v1 > v2;
25384
25430
  });
25385
25431
  },
@@ -25395,6 +25441,9 @@ const GTE = {
25395
25441
  ],
25396
25442
  compute: function (value1, value2) {
25397
25443
  return applyRelationalOperator(value1, value2, (v1, v2) => {
25444
+ if (typeof v1 === "number" && typeof v2 === "number") {
25445
+ return areAlmostEqual(v1, v2) || v1 > v2;
25446
+ }
25398
25447
  return v1 >= v2;
25399
25448
  });
25400
25449
  },
@@ -26517,10 +26566,18 @@ autoCompleteProviders.add("functions", {
26517
26566
  });
26518
26567
 
26519
26568
  class DOMFocusableElementStore {
26520
- mutators = ["setFocusableElement"];
26569
+ mutators = ["setFocusableElement", "focus"];
26521
26570
  focusableElement = undefined;
26522
26571
  setFocusableElement(element) {
26523
26572
  this.focusableElement = element;
26573
+ return "noStateChange";
26574
+ }
26575
+ focus() {
26576
+ if (this.focusableElement === document.activeElement) {
26577
+ return "noStateChange";
26578
+ }
26579
+ this.focusableElement?.focus();
26580
+ return;
26524
26581
  }
26525
26582
  }
26526
26583
 
@@ -27363,7 +27420,7 @@ class Composer extends owl.Component {
27363
27420
  if (document.activeElement === this.contentHelper.el &&
27364
27421
  this.props.composerStore.editionMode === "inactive" &&
27365
27422
  !this.props.isDefaultFocus) {
27366
- this.DOMFocusableElementStore.focusableElement?.focus();
27423
+ this.DOMFocusableElementStore.focus();
27367
27424
  }
27368
27425
  });
27369
27426
  owl.useEffect(() => {
@@ -31835,12 +31892,20 @@ class HoveredCellStore extends SpreadsheetStore {
31835
31892
  }
31836
31893
  }
31837
31894
  hover(position) {
31895
+ if (position.col === this.col && position.row === this.row) {
31896
+ return "noStateChange";
31897
+ }
31838
31898
  this.col = position.col;
31839
31899
  this.row = position.row;
31900
+ return;
31840
31901
  }
31841
31902
  clear() {
31903
+ if (this.col === undefined && this.row === undefined) {
31904
+ return "noStateChange";
31905
+ }
31842
31906
  this.col = undefined;
31843
31907
  this.row = undefined;
31908
+ return;
31844
31909
  }
31845
31910
  }
31846
31911
 
@@ -31862,7 +31927,11 @@ class CellPopoverStore extends SpreadsheetStore {
31862
31927
  this.persistentPopover = { col, row, sheetId, type };
31863
31928
  }
31864
31929
  close() {
31930
+ if (!this.persistentPopover) {
31931
+ return "noStateChange";
31932
+ }
31865
31933
  this.persistentPopover = undefined;
31934
+ return;
31866
31935
  }
31867
31936
  get persistentCellPopover() {
31868
31937
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -39215,7 +39284,7 @@ class AbstractComposerStore extends SpreadsheetStore {
39215
39284
  .find((token) => {
39216
39285
  const { xc, sheetName: sheet } = splitReference(token.value);
39217
39286
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
39218
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
39287
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
39219
39288
  return false;
39220
39289
  }
39221
39290
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -39446,12 +39515,13 @@ class StandaloneComposerStore extends AbstractComposerStore {
39446
39515
  return providersDefinitions;
39447
39516
  }
39448
39517
  getComposerContent() {
39518
+ let content = this._currentContent;
39449
39519
  if (this.editionMode === "inactive") {
39450
39520
  // References in the content might not be linked to the current active sheet
39451
39521
  // We here force the sheet name prefix for all references that are not in
39452
39522
  // the current active sheet
39453
39523
  const defaultRangeSheetId = this.args().defaultRangeSheetId;
39454
- return rangeTokenize(this.args().content)
39524
+ content = rangeTokenize(this.args().content)
39455
39525
  .map((token) => {
39456
39526
  if (token.type === "REFERENCE") {
39457
39527
  const range = this.getters.getRangeFromSheetXC(defaultRangeSheetId, token.value);
@@ -39461,7 +39531,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
39461
39531
  })
39462
39532
  .join("");
39463
39533
  }
39464
- return this._currentContent;
39534
+ return localizeContent(content, this.getters.getLocale());
39465
39535
  }
39466
39536
  stopEdition() {
39467
39537
  this._stopEdition();
@@ -43064,6 +43134,9 @@ class PivotMeasureEditor extends owl.Component {
43064
43134
  measure: this.props.measure,
43065
43135
  });
43066
43136
  }
43137
+ get isCalculatedMeasureInvalid() {
43138
+ return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
43139
+ }
43067
43140
  }
43068
43141
 
43069
43142
  css /* scss */ `
@@ -44512,7 +44585,12 @@ class SpreadsheetPivot {
44512
44585
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
44513
44586
  }
44514
44587
  else {
44515
- entry[field.name] = cell;
44588
+ if (field.type === "char") {
44589
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
44590
+ }
44591
+ else {
44592
+ entry[field.name] = cell;
44593
+ }
44516
44594
  }
44517
44595
  }
44518
44596
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -49508,10 +49586,6 @@ function useGridDrawing(refName, model, canvasSize) {
49508
49586
  ctx.scale(dpr, dpr);
49509
49587
  for (const layer of OrderedLayers()) {
49510
49588
  model.drawLayer(renderingContext, layer);
49511
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
49512
- // it does not mutate anything. Most importantly it's used
49513
- // during rendering. Invoking a mutator during rendering would
49514
- // trigger another rendering, ultimately resulting in an infinite loop.
49515
49589
  rendererStore.drawLayer(renderingContext, layer);
49516
49590
  }
49517
49591
  }
@@ -50201,7 +50275,7 @@ class Grid extends owl.Component {
50201
50275
  this.cellPopovers = useStore(CellPopoverStore);
50202
50276
  owl.useEffect(() => {
50203
50277
  if (!this.sidePanel.isOpen) {
50204
- this.DOMFocusableElementStore.focusableElement?.focus();
50278
+ this.DOMFocusableElementStore.focus();
50205
50279
  }
50206
50280
  }, () => [this.sidePanel.isOpen]);
50207
50281
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -50409,7 +50483,7 @@ class Grid extends owl.Component {
50409
50483
  focusDefaultElement() {
50410
50484
  if (!this.env.model.getters.getSelectedFigureId() &&
50411
50485
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
50412
- this.DOMFocusableElementStore.focusableElement?.focus();
50486
+ this.DOMFocusableElementStore.focus();
50413
50487
  }
50414
50488
  }
50415
50489
  get gridEl() {
@@ -50779,6 +50853,322 @@ class EditableName extends owl.Component {
50779
50853
  }
50780
50854
  }
50781
50855
 
50856
+ css /* scss */ `
50857
+ .o_pivot_html_renderer {
50858
+ width: 100%;
50859
+ border-collapse: collapse;
50860
+
50861
+ &:hover {
50862
+ cursor: pointer;
50863
+ }
50864
+
50865
+ td,
50866
+ th {
50867
+ border: 1px solid #dee2e6;
50868
+ background-color: #fff;
50869
+ padding: 0.3rem;
50870
+ white-space: nowrap;
50871
+
50872
+ &:hover {
50873
+ filter: brightness(0.9);
50874
+ }
50875
+ }
50876
+
50877
+ td {
50878
+ text-align: right;
50879
+ }
50880
+
50881
+ th {
50882
+ background-color: #f5f5f5;
50883
+ font-weight: bold;
50884
+ color: black;
50885
+ }
50886
+
50887
+ .o_missing_value {
50888
+ color: #46646d;
50889
+ background: #e7f2f6;
50890
+ }
50891
+ }
50892
+ `;
50893
+ class PivotHTMLRenderer extends owl.Component {
50894
+ static template = "o_spreadsheet.PivotHTMLRenderer";
50895
+ static components = { Checkbox };
50896
+ static props = {
50897
+ pivotId: String,
50898
+ onCellClicked: Function,
50899
+ };
50900
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
50901
+ data = {
50902
+ columns: [],
50903
+ rows: [],
50904
+ values: [],
50905
+ };
50906
+ state = owl.useState({
50907
+ showMissingValuesOnly: false,
50908
+ });
50909
+ setup() {
50910
+ const table = this.pivot.getTableStructure();
50911
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
50912
+ this.data = {
50913
+ columns: this._buildColHeaders(formulaId, table),
50914
+ rows: this._buildRowHeaders(formulaId, table),
50915
+ values: this._buildValues(formulaId, table),
50916
+ };
50917
+ }
50918
+ get tracker() {
50919
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
50920
+ }
50921
+ // ---------------------------------------------------------------------
50922
+ // Missing values building
50923
+ // ---------------------------------------------------------------------
50924
+ /**
50925
+ * Retrieve the data to display in the Pivot Table
50926
+ * In the case when showMissingValuesOnly is false, the returned value
50927
+ * is the complete data
50928
+ * In the case when showMissingValuesOnly is true, the returned value is
50929
+ * the data which contains only missing values in the rows and cols. In
50930
+ * the rows, we also return the parent rows of rows which contains missing
50931
+ * values, to give context to the user.
50932
+ *
50933
+ */
50934
+ getTableData() {
50935
+ if (!this.state.showMissingValuesOnly) {
50936
+ return this.data;
50937
+ }
50938
+ const colIndexes = this.getColumnsIndexes();
50939
+ const rowIndexes = this.getRowsIndexes();
50940
+ const columns = this.buildColumnsMissing(colIndexes);
50941
+ const rows = this.buildRowsMissing(rowIndexes);
50942
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
50943
+ return { columns, rows, values };
50944
+ }
50945
+ /**
50946
+ * Retrieve the parents of the given row
50947
+ * ex:
50948
+ * Australia
50949
+ * January
50950
+ * February
50951
+ * The parent of "January" is "Australia"
50952
+ */
50953
+ addRecursiveRow(index) {
50954
+ const rows = this.pivot.getTableStructure().rows;
50955
+ const row = [...rows[index].values];
50956
+ if (row.length <= 1) {
50957
+ return [index];
50958
+ }
50959
+ row.pop();
50960
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
50961
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
50962
+ }
50963
+ /**
50964
+ * Create the columns to be used, based on the indexes of the columns in
50965
+ * which a missing value is present
50966
+ *
50967
+ */
50968
+ buildColumnsMissing(indexes) {
50969
+ // columnsMap explode the columns in an array of array of the same
50970
+ // size with the index of each column, repeated 'span' times.
50971
+ // ex:
50972
+ // | A | B |
50973
+ // | 1 | 2 | 3 |
50974
+ // => [
50975
+ // [0, 0, 1]
50976
+ // [0, 1, 2]
50977
+ // ]
50978
+ const columnsMap = [];
50979
+ for (const column of this.data.columns) {
50980
+ const columnMap = [];
50981
+ for (const index in column) {
50982
+ for (let i = 0; i < column[index].span; i++) {
50983
+ columnMap.push(parseInt(index, 10));
50984
+ }
50985
+ }
50986
+ columnsMap.push(columnMap);
50987
+ }
50988
+ // Remove the columns that are not present in indexes
50989
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
50990
+ if (!indexes.includes(i)) {
50991
+ for (const columnMap of columnsMap) {
50992
+ columnMap.splice(i, 1);
50993
+ }
50994
+ }
50995
+ }
50996
+ // Build the columns
50997
+ const columns = [];
50998
+ for (const mapIndex in columnsMap) {
50999
+ const column = [];
51000
+ let index = undefined;
51001
+ let span = 1;
51002
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
51003
+ if (index !== columnsMap[mapIndex][i]) {
51004
+ if (index !== undefined) {
51005
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
51006
+ }
51007
+ index = columnsMap[mapIndex][i];
51008
+ span = 1;
51009
+ }
51010
+ else {
51011
+ span++;
51012
+ }
51013
+ }
51014
+ if (index !== undefined) {
51015
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
51016
+ }
51017
+ columns.push(column);
51018
+ }
51019
+ return columns;
51020
+ }
51021
+ /**
51022
+ * Create the rows to be used, based on the indexes of the rows in
51023
+ * which a missing value is present.
51024
+ */
51025
+ buildRowsMissing(indexes) {
51026
+ return indexes.map((index) => this.data.rows[index]);
51027
+ }
51028
+ /**
51029
+ * Create the value to be used, based on the indexes of the columns and
51030
+ * rows in which a missing value is present.
51031
+ */
51032
+ buildValuesMissing(colIndexes, rowIndexes) {
51033
+ const values = colIndexes.map(() => []);
51034
+ for (const row of rowIndexes) {
51035
+ for (const col in colIndexes) {
51036
+ values[col].push(this.data.values[colIndexes[col]][row]);
51037
+ }
51038
+ }
51039
+ return values;
51040
+ }
51041
+ getColumnsIndexes() {
51042
+ const indexes = new Set();
51043
+ for (let i = 0; i < this.data.columns.length; i++) {
51044
+ const exploded = [];
51045
+ for (let y = 0; y < this.data.columns[i].length; y++) {
51046
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
51047
+ exploded.push(this.data.columns[i][y]);
51048
+ }
51049
+ }
51050
+ for (let y = 0; y < exploded.length; y++) {
51051
+ if (exploded[y].isMissing) {
51052
+ indexes.add(y);
51053
+ }
51054
+ }
51055
+ }
51056
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
51057
+ const values = this.data.values[i];
51058
+ if (values.find((x) => x.isMissing)) {
51059
+ indexes.add(i);
51060
+ }
51061
+ }
51062
+ return Array.from(indexes).sort((a, b) => a - b);
51063
+ }
51064
+ getRowsIndexes() {
51065
+ const rowIndexes = new Set();
51066
+ for (let i = 0; i < this.data.rows.length; i++) {
51067
+ if (this.data.rows[i].isMissing) {
51068
+ rowIndexes.add(i);
51069
+ }
51070
+ for (const col of this.data.values) {
51071
+ if (col[i].isMissing) {
51072
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
51073
+ }
51074
+ }
51075
+ }
51076
+ return Array.from(rowIndexes).sort((a, b) => a - b);
51077
+ }
51078
+ // ---------------------------------------------------------------------
51079
+ // Data table creation
51080
+ // ---------------------------------------------------------------------
51081
+ _buildColHeaders(id, table) {
51082
+ const headers = [];
51083
+ for (const row of table.columns) {
51084
+ const current = [];
51085
+ for (const cell of row) {
51086
+ const args = [];
51087
+ for (let i = 0; i < cell.fields.length; i++) {
51088
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
51089
+ }
51090
+ const domain = this.pivot.parseArgsToPivotDomain(args);
51091
+ const locale = this.env.model.getters.getLocale();
51092
+ if (domain.at(-1)?.field === "measure") {
51093
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
51094
+ current.push({
51095
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
51096
+ value: formatValue(value, { format, locale }),
51097
+ span: cell.width,
51098
+ isMissing: !this.tracker?.isHeaderPresent(domain),
51099
+ });
51100
+ }
51101
+ else {
51102
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
51103
+ current.push({
51104
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
51105
+ value: formatValue(value, { format, locale }),
51106
+ span: cell.width,
51107
+ isMissing: !this.tracker?.isHeaderPresent(domain),
51108
+ });
51109
+ }
51110
+ }
51111
+ headers.push(current);
51112
+ }
51113
+ const last = headers[headers.length - 1];
51114
+ headers[headers.length - 1] = last.map((cell) => {
51115
+ if (!cell.isMissing) {
51116
+ cell.style = "color: #756f6f;";
51117
+ }
51118
+ return cell;
51119
+ });
51120
+ return headers;
51121
+ }
51122
+ _buildRowHeaders(id, table) {
51123
+ const headers = [];
51124
+ for (const row of table.rows) {
51125
+ const args = [];
51126
+ for (let i = 0; i < row.fields.length; i++) {
51127
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
51128
+ }
51129
+ const domain = this.pivot.parseArgsToPivotDomain(args);
51130
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
51131
+ const locale = this.env.model.getters.getLocale();
51132
+ const cell = {
51133
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
51134
+ value: formatValue(value, { format, locale }),
51135
+ isMissing: !this.tracker?.isHeaderPresent(domain),
51136
+ };
51137
+ if (row.indent > 1) {
51138
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
51139
+ }
51140
+ headers.push(cell);
51141
+ }
51142
+ return headers;
51143
+ }
51144
+ _buildValues(id, table) {
51145
+ const values = [];
51146
+ for (const col of table.columns.at(-1) || []) {
51147
+ const current = [];
51148
+ const measure = toString(col.values[col.values.length - 1]);
51149
+ for (const row of table.rows) {
51150
+ const args = [];
51151
+ for (let i = 0; i < row.fields.length; i++) {
51152
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
51153
+ }
51154
+ for (let i = 0; i < col.fields.length - 1; i++) {
51155
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
51156
+ }
51157
+ const domain = this.pivot.parseArgsToPivotDomain(args);
51158
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
51159
+ const locale = this.env.model.getters.getLocale();
51160
+ current.push({
51161
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
51162
+ value: formatValue(value, { format, locale }),
51163
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
51164
+ });
51165
+ }
51166
+ values.push(current);
51167
+ }
51168
+ return values;
51169
+ }
51170
+ }
51171
+
50782
51172
  /**
50783
51173
  * BasePlugin
50784
51174
  *
@@ -54287,7 +54677,7 @@ class RangeAdapter {
54287
54677
  if (range.sheetId === cmd.sheetId) {
54288
54678
  return { changeType: "CHANGE", range };
54289
54679
  }
54290
- if (cmd.name && range.invalidSheetName === cmd.name) {
54680
+ if (isSheetNameEqual(range.invalidSheetName, cmd.name)) {
54291
54681
  const invalidSheetName = undefined;
54292
54682
  const sheetId = cmd.sheetId;
54293
54683
  const newRange = range.clone({ sheetId, invalidSheetName });
@@ -54872,7 +55262,7 @@ class SheetPlugin extends CorePlugin {
54872
55262
  if (name) {
54873
55263
  const unquotedName = getUnquotedSheetName(name);
54874
55264
  for (const key in this.sheetIdsMapName) {
54875
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
55265
+ if (isSheetNameEqual(key, unquotedName)) {
54876
55266
  return this.sheetIdsMapName[key];
54877
55267
  }
54878
55268
  }
@@ -55120,7 +55510,7 @@ class SheetPlugin extends CorePlugin {
55120
55510
  }
55121
55511
  const { orderedSheetIds, sheets } = this;
55122
55512
  const name = cmd.name && cmd.name.trim().toLowerCase();
55123
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
55513
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
55124
55514
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
55125
55515
  }
55126
55516
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -58081,10 +58471,9 @@ class Evaluator {
58081
58471
  return this.evaluatedCells.keysForSheet(sheetId);
58082
58472
  }
58083
58473
  getArrayFormulaSpreadingOn(position) {
58084
- const hasArrayFormulaResult = this.getEvaluatedCell(position).type !== CellValueType.empty &&
58085
- !this.getters.getCell(position)?.isFormula;
58086
- if (!hasArrayFormulaResult) {
58087
- return this.spreadingRelations.isArrayFormula(position) ? position : undefined;
58474
+ const isEmpty = this.getEvaluatedCell(position).type === CellValueType.empty;
58475
+ if (isEmpty) {
58476
+ return undefined;
58088
58477
  }
58089
58478
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
58090
58479
  return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
@@ -64007,6 +64396,55 @@ class HistoryPlugin extends UIPlugin {
64007
64396
  }
64008
64397
  }
64009
64398
 
64399
+ class PivotPresenceTracker {
64400
+ trackedValues = new Set();
64401
+ domainToArray(domain) {
64402
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
64403
+ }
64404
+ isValuePresent(measure, domain) {
64405
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
64406
+ return this.trackedValues.has(key);
64407
+ }
64408
+ isHeaderPresent(domain) {
64409
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
64410
+ return this.trackedValues.has(key);
64411
+ }
64412
+ trackValue(measure, domain) {
64413
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
64414
+ this.trackedValues.add(key);
64415
+ }
64416
+ trackHeader(domain) {
64417
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
64418
+ this.trackedValues.add(key);
64419
+ }
64420
+ }
64421
+
64422
+ class PivotPresencePlugin extends UIPlugin {
64423
+ static getters = ["getPivotPresenceTracker"];
64424
+ trackPresencePivotId;
64425
+ tracker;
64426
+ handle(cmd) {
64427
+ switch (cmd.type) {
64428
+ case "PIVOT_START_PRESENCE_TRACKING":
64429
+ this.tracker = new PivotPresenceTracker();
64430
+ this.trackPresencePivotId = cmd.pivotId;
64431
+ break;
64432
+ case "PIVOT_STOP_PRESENCE_TRACKING":
64433
+ this.trackPresencePivotId = undefined;
64434
+ break;
64435
+ }
64436
+ }
64437
+ getPivotPresenceTracker(pivotId) {
64438
+ if (this.trackPresencePivotId !== pivotId) {
64439
+ return undefined;
64440
+ }
64441
+ if (!this.tracker) {
64442
+ throw new Error("Tracker not initialized");
64443
+ }
64444
+ return this.tracker;
64445
+ }
64446
+ }
64447
+
64010
64448
  class SplitToColumnsPlugin extends UIPlugin {
64011
64449
  static getters = ["getAutomaticSeparator"];
64012
64450
  allowDispatch(cmd) {
@@ -66790,6 +67228,7 @@ const featurePluginRegistry = new Registry()
66790
67228
  .add("automatic_sum", AutomaticSumPlugin)
66791
67229
  .add("format", FormatPlugin)
66792
67230
  .add("insert_pivot", InsertPivotPlugin)
67231
+ .add("pivot_presence", PivotPresencePlugin)
66793
67232
  .add("split_to_columns", SplitToColumnsPlugin)
66794
67233
  .add("collaborative", CollaborativePlugin)
66795
67234
  .add("history", HistoryPlugin)
@@ -67169,11 +67608,11 @@ class BottomBarSheet extends owl.Component {
67169
67608
  if (ev.key === "Enter") {
67170
67609
  ev.preventDefault();
67171
67610
  this.stopEdition();
67172
- this.DOMFocusableElementStore.focusableElement?.focus();
67611
+ this.DOMFocusableElementStore.focus();
67173
67612
  }
67174
67613
  if (ev.key === "Escape") {
67175
67614
  this.cancelEdition();
67176
- this.DOMFocusableElementStore.focusableElement?.focus();
67615
+ this.DOMFocusableElementStore.focus();
67177
67616
  }
67178
67617
  }
67179
67618
  onMouseEventSheetName(ev) {
@@ -73783,6 +74222,7 @@ const components = {
73783
74222
  PivotDimensionOrder,
73784
74223
  PivotDimension,
73785
74224
  PivotLayoutConfigurator,
74225
+ PivotHTMLRenderer,
73786
74226
  EditableName,
73787
74227
  PivotDeferUpdate,
73788
74228
  PivotTitleSection,
@@ -73876,6 +74316,6 @@ exports.tokenColors = tokenColors;
73876
74316
  exports.tokenize = tokenize;
73877
74317
 
73878
74318
 
73879
- __info__.version = "18.0.27";
73880
- __info__.date = "2025-05-12T05:25:47.149Z";
73881
- __info__.hash = "9b36340";
74319
+ __info__.version = "18.0.29";
74320
+ __info__.date = "2025-05-20T05:54:57.329Z";
74321
+ __info__.hash = "8213c0e";