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