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