@odoo/o-spreadsheet 18.0.27 → 18.0.28

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.28
6
+ * @date 2025-05-13T17:53:12.402Z
7
+ * @hash b3088aa
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';
@@ -3180,7 +3180,7 @@ function isDateAfter(date, dateAfter) {
3180
3180
  */
3181
3181
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3182
3182
  decimalSeparator = escapeRegExp(decimalSeparator);
3183
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3183
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3184
3184
  });
3185
3185
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3186
3186
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -6121,6 +6121,13 @@ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6121
6121
  }
6122
6122
  return name;
6123
6123
  }
6124
+ function isSheetNameEqual(name1, name2) {
6125
+ if (name1 === undefined || name2 === undefined) {
6126
+ return false;
6127
+ }
6128
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6129
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6130
+ }
6124
6131
 
6125
6132
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6126
6133
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
@@ -7974,10 +7981,9 @@ const AGGREGATOR_NAMES = {
7974
7981
  avg: _t("Average"),
7975
7982
  sum: _t("Sum"),
7976
7983
  };
7977
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
7978
7984
  const AGGREGATORS_BY_FIELD_TYPE = {
7979
- integer: NUMBER_CHAR_AGGREGATORS,
7980
- char: NUMBER_CHAR_AGGREGATORS,
7985
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
7986
+ char: ["count_distinct", "count"],
7981
7987
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
7982
7988
  };
7983
7989
  const AGGREGATORS = {};
@@ -9310,7 +9316,10 @@ function proxifyStoreMutation(store, callback) {
9310
9316
  const functionProxy = new Proxy(value, {
9311
9317
  // trap the function call
9312
9318
  apply(target, thisArg, argArray) {
9313
- Reflect.apply(target, thisStore, argArray);
9319
+ const res = Reflect.apply(target, thisStore, argArray);
9320
+ if (res === "noStateChange") {
9321
+ return;
9322
+ }
9314
9323
  callback();
9315
9324
  },
9316
9325
  });
@@ -9332,7 +9341,7 @@ function getDependencyContainer(env) {
9332
9341
  const ModelStore = createAbstractStore("Model");
9333
9342
 
9334
9343
  class RendererStore {
9335
- mutators = ["register", "unRegister"];
9344
+ mutators = ["register", "unRegister", "drawLayer"];
9336
9345
  renderers = {};
9337
9346
  register(renderer) {
9338
9347
  if (!renderer.renderingLayers.length) {
@@ -9352,14 +9361,14 @@ class RendererStore {
9352
9361
  }
9353
9362
  drawLayer(context, layer) {
9354
9363
  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();
9364
+ if (renderers) {
9365
+ for (const renderer of renderers) {
9366
+ context.ctx.save();
9367
+ renderer.drawLayer(context, layer);
9368
+ context.ctx.restore();
9369
+ }
9362
9370
  }
9371
+ return "noStateChange";
9363
9372
  }
9364
9373
  }
9365
9374
 
@@ -9412,16 +9421,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9412
9421
  focusComposer(listener, args) {
9413
9422
  this.activeComposer = listener;
9414
9423
  if (this.getters.isReadonly()) {
9415
- return;
9424
+ return "noStateChange";
9416
9425
  }
9417
9426
  this._focusMode = args.focusMode || "contentFocus";
9418
9427
  if (this._focusMode !== "inactive") {
9419
9428
  this.setComposerContent(args);
9420
9429
  }
9430
+ return;
9421
9431
  }
9422
9432
  focusActiveComposer(args) {
9423
9433
  if (this.getters.isReadonly()) {
9424
- return;
9434
+ return "noStateChange";
9425
9435
  }
9426
9436
  if (!this.activeComposer) {
9427
9437
  throw new Error("No composer is registered");
@@ -9430,6 +9440,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9430
9440
  if (this._focusMode !== "inactive") {
9431
9441
  this.setComposerContent(args);
9432
9442
  }
9443
+ return;
9433
9444
  }
9434
9445
  /**
9435
9446
  * Start the edition or update the content if it's already started.
@@ -11744,7 +11755,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
11744
11755
  ({ xc, sheetName } = splitReference(reference));
11745
11756
  let rangeSheetIndex;
11746
11757
  if (sheetName) {
11747
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
11758
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
11748
11759
  if (index < 0) {
11749
11760
  throw new Error("Unable to find a sheet with the name " + sheetName);
11750
11761
  }
@@ -12085,7 +12096,7 @@ function convertFormula(formula, data) {
12085
12096
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
12086
12097
  externalRefId = Number(externalRefId) - 1;
12087
12098
  cellRef = cellRef.replace(/\$/g, "");
12088
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
12099
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
12089
12100
  if (sheetIndex === -1) {
12090
12101
  return match;
12091
12102
  }
@@ -12748,7 +12759,7 @@ function convertPivotTableConfig(pivotTable) {
12748
12759
  */
12749
12760
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
12750
12761
  for (let tableSheet of convertedSheets) {
12751
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
12762
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
12752
12763
  for (let table of tables) {
12753
12764
  const tabRef = table.name + "[";
12754
12765
  for (let sheet of convertedSheets) {
@@ -25071,6 +25082,9 @@ const PIVOT_VALUE = {
25071
25082
  };
25072
25083
  }
25073
25084
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
25085
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
25086
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
25087
+ }
25074
25088
  return pivot.getPivotCellValueAndFormat(_measure, domain);
25075
25089
  },
25076
25090
  };
@@ -25102,6 +25116,9 @@ const PIVOT_HEADER = {
25102
25116
  };
25103
25117
  }
25104
25118
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
25119
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
25120
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
25121
+ }
25105
25122
  const lastNode = domain.at(-1);
25106
25123
  if (lastNode?.field === "measure") {
25107
25124
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -25324,6 +25341,9 @@ function isEmpty(data) {
25324
25341
  return data === undefined || data.value === null;
25325
25342
  }
25326
25343
  const getNeutral = { number: 0, string: "", boolean: false };
25344
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
25345
+ return Math.abs(value1 - value2) < epsilon;
25346
+ }
25327
25347
  const EQ = {
25328
25348
  description: _t("Equal."),
25329
25349
  args: [
@@ -25339,6 +25359,9 @@ const EQ = {
25339
25359
  if (typeof _value2 === "string") {
25340
25360
  _value2 = _value2.toUpperCase();
25341
25361
  }
25362
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
25363
+ return areAlmostEqual(_value1, _value2);
25364
+ }
25342
25365
  return _value1 === _value2;
25343
25366
  },
25344
25367
  };
@@ -25378,6 +25401,9 @@ const GT = {
25378
25401
  ],
25379
25402
  compute: function (value1, value2) {
25380
25403
  return applyRelationalOperator(value1, value2, (v1, v2) => {
25404
+ if (typeof v1 === "number" && typeof v2 === "number") {
25405
+ return !areAlmostEqual(v1, v2) && v1 > v2;
25406
+ }
25381
25407
  return v1 > v2;
25382
25408
  });
25383
25409
  },
@@ -25393,6 +25419,9 @@ const GTE = {
25393
25419
  ],
25394
25420
  compute: function (value1, value2) {
25395
25421
  return applyRelationalOperator(value1, value2, (v1, v2) => {
25422
+ if (typeof v1 === "number" && typeof v2 === "number") {
25423
+ return areAlmostEqual(v1, v2) || v1 > v2;
25424
+ }
25396
25425
  return v1 >= v2;
25397
25426
  });
25398
25427
  },
@@ -26515,10 +26544,18 @@ autoCompleteProviders.add("functions", {
26515
26544
  });
26516
26545
 
26517
26546
  class DOMFocusableElementStore {
26518
- mutators = ["setFocusableElement"];
26547
+ mutators = ["setFocusableElement", "focus"];
26519
26548
  focusableElement = undefined;
26520
26549
  setFocusableElement(element) {
26521
26550
  this.focusableElement = element;
26551
+ return "noStateChange";
26552
+ }
26553
+ focus() {
26554
+ if (this.focusableElement === document.activeElement) {
26555
+ return "noStateChange";
26556
+ }
26557
+ this.focusableElement?.focus();
26558
+ return;
26522
26559
  }
26523
26560
  }
26524
26561
 
@@ -27361,7 +27398,7 @@ class Composer extends Component {
27361
27398
  if (document.activeElement === this.contentHelper.el &&
27362
27399
  this.props.composerStore.editionMode === "inactive" &&
27363
27400
  !this.props.isDefaultFocus) {
27364
- this.DOMFocusableElementStore.focusableElement?.focus();
27401
+ this.DOMFocusableElementStore.focus();
27365
27402
  }
27366
27403
  });
27367
27404
  useEffect(() => {
@@ -31833,12 +31870,20 @@ class HoveredCellStore extends SpreadsheetStore {
31833
31870
  }
31834
31871
  }
31835
31872
  hover(position) {
31873
+ if (position.col === this.col && position.row === this.row) {
31874
+ return "noStateChange";
31875
+ }
31836
31876
  this.col = position.col;
31837
31877
  this.row = position.row;
31878
+ return;
31838
31879
  }
31839
31880
  clear() {
31881
+ if (this.col === undefined && this.row === undefined) {
31882
+ return "noStateChange";
31883
+ }
31840
31884
  this.col = undefined;
31841
31885
  this.row = undefined;
31886
+ return;
31842
31887
  }
31843
31888
  }
31844
31889
 
@@ -31860,7 +31905,11 @@ class CellPopoverStore extends SpreadsheetStore {
31860
31905
  this.persistentPopover = { col, row, sheetId, type };
31861
31906
  }
31862
31907
  close() {
31908
+ if (!this.persistentPopover) {
31909
+ return "noStateChange";
31910
+ }
31863
31911
  this.persistentPopover = undefined;
31912
+ return;
31864
31913
  }
31865
31914
  get persistentCellPopover() {
31866
31915
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -39213,7 +39262,7 @@ class AbstractComposerStore extends SpreadsheetStore {
39213
39262
  .find((token) => {
39214
39263
  const { xc, sheetName: sheet } = splitReference(token.value);
39215
39264
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
39216
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
39265
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
39217
39266
  return false;
39218
39267
  }
39219
39268
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -44510,7 +44559,12 @@ class SpreadsheetPivot {
44510
44559
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
44511
44560
  }
44512
44561
  else {
44513
- entry[field.name] = cell;
44562
+ if (field.type === "char") {
44563
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
44564
+ }
44565
+ else {
44566
+ entry[field.name] = cell;
44567
+ }
44514
44568
  }
44515
44569
  }
44516
44570
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -49506,10 +49560,6 @@ function useGridDrawing(refName, model, canvasSize) {
49506
49560
  ctx.scale(dpr, dpr);
49507
49561
  for (const layer of OrderedLayers()) {
49508
49562
  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
49563
  rendererStore.drawLayer(renderingContext, layer);
49514
49564
  }
49515
49565
  }
@@ -50199,7 +50249,7 @@ class Grid extends Component {
50199
50249
  this.cellPopovers = useStore(CellPopoverStore);
50200
50250
  useEffect(() => {
50201
50251
  if (!this.sidePanel.isOpen) {
50202
- this.DOMFocusableElementStore.focusableElement?.focus();
50252
+ this.DOMFocusableElementStore.focus();
50203
50253
  }
50204
50254
  }, () => [this.sidePanel.isOpen]);
50205
50255
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -50407,7 +50457,7 @@ class Grid extends Component {
50407
50457
  focusDefaultElement() {
50408
50458
  if (!this.env.model.getters.getSelectedFigureId() &&
50409
50459
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
50410
- this.DOMFocusableElementStore.focusableElement?.focus();
50460
+ this.DOMFocusableElementStore.focus();
50411
50461
  }
50412
50462
  }
50413
50463
  get gridEl() {
@@ -50777,6 +50827,322 @@ class EditableName extends Component {
50777
50827
  }
50778
50828
  }
50779
50829
 
50830
+ css /* scss */ `
50831
+ .o_pivot_html_renderer {
50832
+ width: 100%;
50833
+ border-collapse: collapse;
50834
+
50835
+ &:hover {
50836
+ cursor: pointer;
50837
+ }
50838
+
50839
+ td,
50840
+ th {
50841
+ border: 1px solid #dee2e6;
50842
+ background-color: #fff;
50843
+ padding: 0.3rem;
50844
+ white-space: nowrap;
50845
+
50846
+ &:hover {
50847
+ filter: brightness(0.9);
50848
+ }
50849
+ }
50850
+
50851
+ td {
50852
+ text-align: right;
50853
+ }
50854
+
50855
+ th {
50856
+ background-color: #f5f5f5;
50857
+ font-weight: bold;
50858
+ color: black;
50859
+ }
50860
+
50861
+ .o_missing_value {
50862
+ color: #46646d;
50863
+ background: #e7f2f6;
50864
+ }
50865
+ }
50866
+ `;
50867
+ class PivotHTMLRenderer extends Component {
50868
+ static template = "o_spreadsheet.PivotHTMLRenderer";
50869
+ static components = { Checkbox };
50870
+ static props = {
50871
+ pivotId: String,
50872
+ onCellClicked: Function,
50873
+ };
50874
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
50875
+ data = {
50876
+ columns: [],
50877
+ rows: [],
50878
+ values: [],
50879
+ };
50880
+ state = useState({
50881
+ showMissingValuesOnly: false,
50882
+ });
50883
+ setup() {
50884
+ const table = this.pivot.getTableStructure();
50885
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
50886
+ this.data = {
50887
+ columns: this._buildColHeaders(formulaId, table),
50888
+ rows: this._buildRowHeaders(formulaId, table),
50889
+ values: this._buildValues(formulaId, table),
50890
+ };
50891
+ }
50892
+ get tracker() {
50893
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
50894
+ }
50895
+ // ---------------------------------------------------------------------
50896
+ // Missing values building
50897
+ // ---------------------------------------------------------------------
50898
+ /**
50899
+ * Retrieve the data to display in the Pivot Table
50900
+ * In the case when showMissingValuesOnly is false, the returned value
50901
+ * is the complete data
50902
+ * In the case when showMissingValuesOnly is true, the returned value is
50903
+ * the data which contains only missing values in the rows and cols. In
50904
+ * the rows, we also return the parent rows of rows which contains missing
50905
+ * values, to give context to the user.
50906
+ *
50907
+ */
50908
+ getTableData() {
50909
+ if (!this.state.showMissingValuesOnly) {
50910
+ return this.data;
50911
+ }
50912
+ const colIndexes = this.getColumnsIndexes();
50913
+ const rowIndexes = this.getRowsIndexes();
50914
+ const columns = this.buildColumnsMissing(colIndexes);
50915
+ const rows = this.buildRowsMissing(rowIndexes);
50916
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
50917
+ return { columns, rows, values };
50918
+ }
50919
+ /**
50920
+ * Retrieve the parents of the given row
50921
+ * ex:
50922
+ * Australia
50923
+ * January
50924
+ * February
50925
+ * The parent of "January" is "Australia"
50926
+ */
50927
+ addRecursiveRow(index) {
50928
+ const rows = this.pivot.getTableStructure().rows;
50929
+ const row = [...rows[index].values];
50930
+ if (row.length <= 1) {
50931
+ return [index];
50932
+ }
50933
+ row.pop();
50934
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
50935
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
50936
+ }
50937
+ /**
50938
+ * Create the columns to be used, based on the indexes of the columns in
50939
+ * which a missing value is present
50940
+ *
50941
+ */
50942
+ buildColumnsMissing(indexes) {
50943
+ // columnsMap explode the columns in an array of array of the same
50944
+ // size with the index of each column, repeated 'span' times.
50945
+ // ex:
50946
+ // | A | B |
50947
+ // | 1 | 2 | 3 |
50948
+ // => [
50949
+ // [0, 0, 1]
50950
+ // [0, 1, 2]
50951
+ // ]
50952
+ const columnsMap = [];
50953
+ for (const column of this.data.columns) {
50954
+ const columnMap = [];
50955
+ for (const index in column) {
50956
+ for (let i = 0; i < column[index].span; i++) {
50957
+ columnMap.push(parseInt(index, 10));
50958
+ }
50959
+ }
50960
+ columnsMap.push(columnMap);
50961
+ }
50962
+ // Remove the columns that are not present in indexes
50963
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
50964
+ if (!indexes.includes(i)) {
50965
+ for (const columnMap of columnsMap) {
50966
+ columnMap.splice(i, 1);
50967
+ }
50968
+ }
50969
+ }
50970
+ // Build the columns
50971
+ const columns = [];
50972
+ for (const mapIndex in columnsMap) {
50973
+ const column = [];
50974
+ let index = undefined;
50975
+ let span = 1;
50976
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
50977
+ if (index !== columnsMap[mapIndex][i]) {
50978
+ if (index !== undefined) {
50979
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
50980
+ }
50981
+ index = columnsMap[mapIndex][i];
50982
+ span = 1;
50983
+ }
50984
+ else {
50985
+ span++;
50986
+ }
50987
+ }
50988
+ if (index !== undefined) {
50989
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
50990
+ }
50991
+ columns.push(column);
50992
+ }
50993
+ return columns;
50994
+ }
50995
+ /**
50996
+ * Create the rows to be used, based on the indexes of the rows in
50997
+ * which a missing value is present.
50998
+ */
50999
+ buildRowsMissing(indexes) {
51000
+ return indexes.map((index) => this.data.rows[index]);
51001
+ }
51002
+ /**
51003
+ * Create the value to be used, based on the indexes of the columns and
51004
+ * rows in which a missing value is present.
51005
+ */
51006
+ buildValuesMissing(colIndexes, rowIndexes) {
51007
+ const values = colIndexes.map(() => []);
51008
+ for (const row of rowIndexes) {
51009
+ for (const col in colIndexes) {
51010
+ values[col].push(this.data.values[colIndexes[col]][row]);
51011
+ }
51012
+ }
51013
+ return values;
51014
+ }
51015
+ getColumnsIndexes() {
51016
+ const indexes = new Set();
51017
+ for (let i = 0; i < this.data.columns.length; i++) {
51018
+ const exploded = [];
51019
+ for (let y = 0; y < this.data.columns[i].length; y++) {
51020
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
51021
+ exploded.push(this.data.columns[i][y]);
51022
+ }
51023
+ }
51024
+ for (let y = 0; y < exploded.length; y++) {
51025
+ if (exploded[y].isMissing) {
51026
+ indexes.add(y);
51027
+ }
51028
+ }
51029
+ }
51030
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
51031
+ const values = this.data.values[i];
51032
+ if (values.find((x) => x.isMissing)) {
51033
+ indexes.add(i);
51034
+ }
51035
+ }
51036
+ return Array.from(indexes).sort((a, b) => a - b);
51037
+ }
51038
+ getRowsIndexes() {
51039
+ const rowIndexes = new Set();
51040
+ for (let i = 0; i < this.data.rows.length; i++) {
51041
+ if (this.data.rows[i].isMissing) {
51042
+ rowIndexes.add(i);
51043
+ }
51044
+ for (const col of this.data.values) {
51045
+ if (col[i].isMissing) {
51046
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
51047
+ }
51048
+ }
51049
+ }
51050
+ return Array.from(rowIndexes).sort((a, b) => a - b);
51051
+ }
51052
+ // ---------------------------------------------------------------------
51053
+ // Data table creation
51054
+ // ---------------------------------------------------------------------
51055
+ _buildColHeaders(id, table) {
51056
+ const headers = [];
51057
+ for (const row of table.columns) {
51058
+ const current = [];
51059
+ for (const cell of row) {
51060
+ const args = [];
51061
+ for (let i = 0; i < cell.fields.length; i++) {
51062
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
51063
+ }
51064
+ const domain = this.pivot.parseArgsToPivotDomain(args);
51065
+ const locale = this.env.model.getters.getLocale();
51066
+ if (domain.at(-1)?.field === "measure") {
51067
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
51068
+ current.push({
51069
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
51070
+ value: formatValue(value, { format, locale }),
51071
+ span: cell.width,
51072
+ isMissing: !this.tracker?.isHeaderPresent(domain),
51073
+ });
51074
+ }
51075
+ else {
51076
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
51077
+ current.push({
51078
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
51079
+ value: formatValue(value, { format, locale }),
51080
+ span: cell.width,
51081
+ isMissing: !this.tracker?.isHeaderPresent(domain),
51082
+ });
51083
+ }
51084
+ }
51085
+ headers.push(current);
51086
+ }
51087
+ const last = headers[headers.length - 1];
51088
+ headers[headers.length - 1] = last.map((cell) => {
51089
+ if (!cell.isMissing) {
51090
+ cell.style = "color: #756f6f;";
51091
+ }
51092
+ return cell;
51093
+ });
51094
+ return headers;
51095
+ }
51096
+ _buildRowHeaders(id, table) {
51097
+ const headers = [];
51098
+ for (const row of table.rows) {
51099
+ const args = [];
51100
+ for (let i = 0; i < row.fields.length; i++) {
51101
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
51102
+ }
51103
+ const domain = this.pivot.parseArgsToPivotDomain(args);
51104
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
51105
+ const locale = this.env.model.getters.getLocale();
51106
+ const cell = {
51107
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
51108
+ value: formatValue(value, { format, locale }),
51109
+ isMissing: !this.tracker?.isHeaderPresent(domain),
51110
+ };
51111
+ if (row.indent > 1) {
51112
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
51113
+ }
51114
+ headers.push(cell);
51115
+ }
51116
+ return headers;
51117
+ }
51118
+ _buildValues(id, table) {
51119
+ const values = [];
51120
+ for (const col of table.columns.at(-1) || []) {
51121
+ const current = [];
51122
+ const measure = toString(col.values[col.values.length - 1]);
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
+ for (let i = 0; i < col.fields.length - 1; i++) {
51129
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
51130
+ }
51131
+ const domain = this.pivot.parseArgsToPivotDomain(args);
51132
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
51133
+ const locale = this.env.model.getters.getLocale();
51134
+ current.push({
51135
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
51136
+ value: formatValue(value, { format, locale }),
51137
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
51138
+ });
51139
+ }
51140
+ values.push(current);
51141
+ }
51142
+ return values;
51143
+ }
51144
+ }
51145
+
50780
51146
  /**
50781
51147
  * BasePlugin
50782
51148
  *
@@ -54285,7 +54651,7 @@ class RangeAdapter {
54285
54651
  if (range.sheetId === cmd.sheetId) {
54286
54652
  return { changeType: "CHANGE", range };
54287
54653
  }
54288
- if (cmd.name && range.invalidSheetName === cmd.name) {
54654
+ if (isSheetNameEqual(range.invalidSheetName, cmd.name)) {
54289
54655
  const invalidSheetName = undefined;
54290
54656
  const sheetId = cmd.sheetId;
54291
54657
  const newRange = range.clone({ sheetId, invalidSheetName });
@@ -54870,7 +55236,7 @@ class SheetPlugin extends CorePlugin {
54870
55236
  if (name) {
54871
55237
  const unquotedName = getUnquotedSheetName(name);
54872
55238
  for (const key in this.sheetIdsMapName) {
54873
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
55239
+ if (isSheetNameEqual(key, unquotedName)) {
54874
55240
  return this.sheetIdsMapName[key];
54875
55241
  }
54876
55242
  }
@@ -55118,7 +55484,7 @@ class SheetPlugin extends CorePlugin {
55118
55484
  }
55119
55485
  const { orderedSheetIds, sheets } = this;
55120
55486
  const name = cmd.name && cmd.name.trim().toLowerCase();
55121
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
55487
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
55122
55488
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
55123
55489
  }
55124
55490
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -64005,6 +64371,55 @@ class HistoryPlugin extends UIPlugin {
64005
64371
  }
64006
64372
  }
64007
64373
 
64374
+ class PivotPresenceTracker {
64375
+ trackedValues = new Set();
64376
+ domainToArray(domain) {
64377
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
64378
+ }
64379
+ isValuePresent(measure, domain) {
64380
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
64381
+ return this.trackedValues.has(key);
64382
+ }
64383
+ isHeaderPresent(domain) {
64384
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
64385
+ return this.trackedValues.has(key);
64386
+ }
64387
+ trackValue(measure, domain) {
64388
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
64389
+ this.trackedValues.add(key);
64390
+ }
64391
+ trackHeader(domain) {
64392
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
64393
+ this.trackedValues.add(key);
64394
+ }
64395
+ }
64396
+
64397
+ class PivotPresencePlugin extends UIPlugin {
64398
+ static getters = ["getPivotPresenceTracker"];
64399
+ trackPresencePivotId;
64400
+ tracker;
64401
+ handle(cmd) {
64402
+ switch (cmd.type) {
64403
+ case "PIVOT_START_PRESENCE_TRACKING":
64404
+ this.tracker = new PivotPresenceTracker();
64405
+ this.trackPresencePivotId = cmd.pivotId;
64406
+ break;
64407
+ case "PIVOT_STOP_PRESENCE_TRACKING":
64408
+ this.trackPresencePivotId = undefined;
64409
+ break;
64410
+ }
64411
+ }
64412
+ getPivotPresenceTracker(pivotId) {
64413
+ if (this.trackPresencePivotId !== pivotId) {
64414
+ return undefined;
64415
+ }
64416
+ if (!this.tracker) {
64417
+ throw new Error("Tracker not initialized");
64418
+ }
64419
+ return this.tracker;
64420
+ }
64421
+ }
64422
+
64008
64423
  class SplitToColumnsPlugin extends UIPlugin {
64009
64424
  static getters = ["getAutomaticSeparator"];
64010
64425
  allowDispatch(cmd) {
@@ -66788,6 +67203,7 @@ const featurePluginRegistry = new Registry()
66788
67203
  .add("automatic_sum", AutomaticSumPlugin)
66789
67204
  .add("format", FormatPlugin)
66790
67205
  .add("insert_pivot", InsertPivotPlugin)
67206
+ .add("pivot_presence", PivotPresencePlugin)
66791
67207
  .add("split_to_columns", SplitToColumnsPlugin)
66792
67208
  .add("collaborative", CollaborativePlugin)
66793
67209
  .add("history", HistoryPlugin)
@@ -67167,11 +67583,11 @@ class BottomBarSheet extends Component {
67167
67583
  if (ev.key === "Enter") {
67168
67584
  ev.preventDefault();
67169
67585
  this.stopEdition();
67170
- this.DOMFocusableElementStore.focusableElement?.focus();
67586
+ this.DOMFocusableElementStore.focus();
67171
67587
  }
67172
67588
  if (ev.key === "Escape") {
67173
67589
  this.cancelEdition();
67174
- this.DOMFocusableElementStore.focusableElement?.focus();
67590
+ this.DOMFocusableElementStore.focus();
67175
67591
  }
67176
67592
  }
67177
67593
  onMouseEventSheetName(ev) {
@@ -73781,6 +74197,7 @@ const components = {
73781
74197
  PivotDimensionOrder,
73782
74198
  PivotDimension,
73783
74199
  PivotLayoutConfigurator,
74200
+ PivotHTMLRenderer,
73784
74201
  EditableName,
73785
74202
  PivotDeferUpdate,
73786
74203
  PivotTitleSection,
@@ -73831,6 +74248,6 @@ const constants = {
73831
74248
  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
74249
 
73833
74250
 
73834
- __info__.version = "18.0.27";
73835
- __info__.date = "2025-05-12T05:25:47.149Z";
73836
- __info__.hash = "9b36340";
74251
+ __info__.version = "18.0.28";
74252
+ __info__.date = "2025-05-13T17:53:12.402Z";
74253
+ __info__.hash = "b3088aa";