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