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