@odoo/o-spreadsheet 18.0.26 → 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.26
6
- * @date 2025-05-02T12:30:31.966Z
7
- * @hash 5bdf504
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);
@@ -6104,6 +6104,32 @@ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6104
6104
  })
6105
6105
  .filter(isDefined);
6106
6106
  }
6107
+ function getNextSheetName(existingNames, baseName = "Sheet") {
6108
+ let i = 1;
6109
+ let name = `${baseName}${i}`;
6110
+ while (existingNames.includes(name)) {
6111
+ name = `${baseName}${i}`;
6112
+ i++;
6113
+ }
6114
+ return name;
6115
+ }
6116
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6117
+ let i = 1;
6118
+ const baseName = _t("Copy of %s", nameToDuplicate);
6119
+ let name = baseName.toString();
6120
+ while (existingNames.includes(name)) {
6121
+ name = `${baseName} (${i})`;
6122
+ i++;
6123
+ }
6124
+ return name;
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
+ }
6107
6133
 
6108
6134
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6109
6135
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
@@ -7792,6 +7818,24 @@ const monthNumberAdapter = {
7792
7818
  return `${normalizedValue}`;
7793
7819
  },
7794
7820
  };
7821
+ /**
7822
+ * normalizes month number + year
7823
+ */
7824
+ const monthAdapter = {
7825
+ normalizeFunctionValue(value) {
7826
+ const date = toNumber(value, DEFAULT_LOCALE);
7827
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
7828
+ },
7829
+ toValueAndFormat(normalizedValue) {
7830
+ return {
7831
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
7832
+ format: "mmmm yyyy",
7833
+ };
7834
+ },
7835
+ toFunctionValue(normalizedValue) {
7836
+ return `"${normalizedValue}"`;
7837
+ },
7838
+ };
7795
7839
  /**
7796
7840
  * normalizes quarter number
7797
7841
  */
@@ -7922,6 +7966,7 @@ pivotTimeAdapterRegistry
7922
7966
  .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
7923
7967
  .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
7924
7968
  .add("month_number", nullHandlerDecorator(monthNumberAdapter))
7969
+ .add("month", nullHandlerDecorator(monthAdapter))
7925
7970
  .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
7926
7971
  .add("day_of_week", nullHandlerDecorator(dayOfWeekAdapter))
7927
7972
  .add("hour_number", nullHandlerDecorator(hourNumberAdapter))
@@ -7938,10 +7983,9 @@ const AGGREGATOR_NAMES = {
7938
7983
  avg: _t("Average"),
7939
7984
  sum: _t("Sum"),
7940
7985
  };
7941
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
7942
7986
  const AGGREGATORS_BY_FIELD_TYPE = {
7943
- integer: NUMBER_CHAR_AGGREGATORS,
7944
- char: NUMBER_CHAR_AGGREGATORS,
7987
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
7988
+ char: ["count_distinct", "count"],
7945
7989
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
7946
7990
  };
7947
7991
  const AGGREGATORS = {};
@@ -8102,10 +8146,7 @@ function toNormalizedPivotValue(dimension, groupValue) {
8102
8146
  return normalizer(groupValueString, dimension.granularity);
8103
8147
  }
8104
8148
  function normalizeDateTime(value, granularity) {
8105
- if (!granularity) {
8106
- throw new Error("Missing granularity");
8107
- }
8108
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8149
+ return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
8109
8150
  }
8110
8151
  function toFunctionPivotValue(value, dimension) {
8111
8152
  if (value === null) {
@@ -8117,10 +8158,7 @@ function toFunctionPivotValue(value, dimension) {
8117
8158
  return pivotToFunctionValueRegistry.get(dimension.type)(value, dimension.granularity);
8118
8159
  }
8119
8160
  function toFunctionValueDateTime(value, granularity) {
8120
- if (!granularity) {
8121
- throw new Error("Missing granularity");
8122
- }
8123
- return pivotTimeAdapter(granularity).toFunctionValue(value);
8161
+ return pivotTimeAdapter(granularity ?? "month").toFunctionValue(value);
8124
8162
  }
8125
8163
  const pivotNormalizationValueRegistry = new Registry();
8126
8164
  pivotNormalizationValueRegistry
@@ -9280,7 +9318,10 @@ function proxifyStoreMutation(store, callback) {
9280
9318
  const functionProxy = new Proxy(value, {
9281
9319
  // trap the function call
9282
9320
  apply(target, thisArg, argArray) {
9283
- Reflect.apply(target, thisStore, argArray);
9321
+ const res = Reflect.apply(target, thisStore, argArray);
9322
+ if (res === "noStateChange") {
9323
+ return;
9324
+ }
9284
9325
  callback();
9285
9326
  },
9286
9327
  });
@@ -9302,7 +9343,7 @@ function getDependencyContainer(env) {
9302
9343
  const ModelStore = createAbstractStore("Model");
9303
9344
 
9304
9345
  class RendererStore {
9305
- mutators = ["register", "unRegister"];
9346
+ mutators = ["register", "unRegister", "drawLayer"];
9306
9347
  renderers = {};
9307
9348
  register(renderer) {
9308
9349
  if (!renderer.renderingLayers.length) {
@@ -9322,14 +9363,14 @@ class RendererStore {
9322
9363
  }
9323
9364
  drawLayer(context, layer) {
9324
9365
  const renderers = this.renderers[layer];
9325
- if (!renderers) {
9326
- return;
9327
- }
9328
- for (const renderer of renderers) {
9329
- context.ctx.save();
9330
- renderer.drawLayer(context, layer);
9331
- 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
+ }
9332
9372
  }
9373
+ return "noStateChange";
9333
9374
  }
9334
9375
  }
9335
9376
 
@@ -9382,16 +9423,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9382
9423
  focusComposer(listener, args) {
9383
9424
  this.activeComposer = listener;
9384
9425
  if (this.getters.isReadonly()) {
9385
- return;
9426
+ return "noStateChange";
9386
9427
  }
9387
9428
  this._focusMode = args.focusMode || "contentFocus";
9388
9429
  if (this._focusMode !== "inactive") {
9389
9430
  this.setComposerContent(args);
9390
9431
  }
9432
+ return;
9391
9433
  }
9392
9434
  focusActiveComposer(args) {
9393
9435
  if (this.getters.isReadonly()) {
9394
- return;
9436
+ return "noStateChange";
9395
9437
  }
9396
9438
  if (!this.activeComposer) {
9397
9439
  throw new Error("No composer is registered");
@@ -9400,6 +9442,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9400
9442
  if (this._focusMode !== "inactive") {
9401
9443
  this.setComposerContent(args);
9402
9444
  }
9445
+ return;
9403
9446
  }
9404
9447
  /**
9405
9448
  * Start the edition or update the content if it's already started.
@@ -11714,7 +11757,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
11714
11757
  ({ xc, sheetName } = splitReference(reference));
11715
11758
  let rangeSheetIndex;
11716
11759
  if (sheetName) {
11717
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
11760
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
11718
11761
  if (index < 0) {
11719
11762
  throw new Error("Unable to find a sheet with the name " + sheetName);
11720
11763
  }
@@ -12055,7 +12098,7 @@ function convertFormula(formula, data) {
12055
12098
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
12056
12099
  externalRefId = Number(externalRefId) - 1;
12057
12100
  cellRef = cellRef.replace(/\$/g, "");
12058
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
12101
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
12059
12102
  if (sheetIndex === -1) {
12060
12103
  return match;
12061
12104
  }
@@ -12718,7 +12761,7 @@ function convertPivotTableConfig(pivotTable) {
12718
12761
  */
12719
12762
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
12720
12763
  for (let tableSheet of convertedSheets) {
12721
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
12764
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
12722
12765
  for (let table of tables) {
12723
12766
  const tabRef = table.name + "[";
12724
12767
  for (let sheet of convertedSheets) {
@@ -15253,6 +15296,7 @@ function repairInitialMessages(data, initialMessages) {
15253
15296
  initialMessages = dropCommands(initialMessages, "SORT_CELLS");
15254
15297
  initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
15255
15298
  initialMessages = fixChartDefinitions(data, initialMessages);
15299
+ initialMessages = fixTranslatedDuplicateSheetName(data, initialMessages);
15256
15300
  return initialMessages;
15257
15301
  }
15258
15302
  /**
@@ -15352,6 +15396,40 @@ function fixChartDefinitions(data, initialMessages) {
15352
15396
  }
15353
15397
  return messages;
15354
15398
  }
15399
+ function fixTranslatedDuplicateSheetName(data, initialMessages) {
15400
+ const sheetNames = {};
15401
+ for (const sheet of data.sheets || []) {
15402
+ sheetNames[sheet.id] = sheet.name;
15403
+ }
15404
+ const messages = [];
15405
+ for (const message of initialMessages) {
15406
+ if (message.type === "REMOTE_REVISION") {
15407
+ const commands = [];
15408
+ for (const cmd of message.commands) {
15409
+ switch (cmd.type) {
15410
+ case "DUPLICATE_SHEET":
15411
+ cmd.sheetNameTo =
15412
+ cmd.sheetNameTo ??
15413
+ getDuplicateSheetName(sheetNames[cmd.sheetId], Object.values(sheetNames));
15414
+ break;
15415
+ case "CREATE_SHEET":
15416
+ case "RENAME_SHEET":
15417
+ sheetNames[cmd.sheetId] = cmd.name || getNextSheetName(Object.values(sheetNames));
15418
+ break;
15419
+ }
15420
+ commands.push(cmd);
15421
+ }
15422
+ messages.push({
15423
+ ...message,
15424
+ commands,
15425
+ });
15426
+ }
15427
+ else {
15428
+ messages.push(message);
15429
+ }
15430
+ }
15431
+ return initialMessages;
15432
+ }
15355
15433
  // -----------------------------------------------------------------------------
15356
15434
  // Helpers
15357
15435
  // -----------------------------------------------------------------------------
@@ -24367,7 +24445,7 @@ const IF = {
24367
24445
  return { value: "" };
24368
24446
  }
24369
24447
  if (result.value === null) {
24370
- result.value = "";
24448
+ return { ...result, value: "" };
24371
24449
  }
24372
24450
  return result;
24373
24451
  },
@@ -24388,7 +24466,7 @@ const IFERROR = {
24388
24466
  return { value: "" };
24389
24467
  }
24390
24468
  if (result.value === null) {
24391
- result.value = "";
24469
+ return { ...result, value: "" };
24392
24470
  }
24393
24471
  return result;
24394
24472
  },
@@ -24409,7 +24487,7 @@ const IFNA = {
24409
24487
  return { value: "" };
24410
24488
  }
24411
24489
  if (result.value === null) {
24412
- result.value = "";
24490
+ return { ...result, value: "" };
24413
24491
  }
24414
24492
  return result;
24415
24493
  },
@@ -24435,7 +24513,7 @@ const IFS = {
24435
24513
  return { value: "" };
24436
24514
  }
24437
24515
  if (result.value === null) {
24438
- result.value = "";
24516
+ return { ...result, value: "" };
24439
24517
  }
24440
24518
  return result;
24441
24519
  }
@@ -24553,6 +24631,11 @@ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
24553
24631
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
24554
24632
  throw new InvalidReferenceError();
24555
24633
  }
24634
+ if (evalContext.__originCellPosition &&
24635
+ range.sheetId === evalContext.__originSheetId &&
24636
+ isZoneInside(positionToZone(evalContext.__originCellPosition), zone)) {
24637
+ throw new CircularDependencyError();
24638
+ }
24556
24639
  dependencies.push(range);
24557
24640
  }
24558
24641
  for (const measure of forMeasures) {
@@ -25001,6 +25084,9 @@ const PIVOT_VALUE = {
25001
25084
  };
25002
25085
  }
25003
25086
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
25087
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
25088
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
25089
+ }
25004
25090
  return pivot.getPivotCellValueAndFormat(_measure, domain);
25005
25091
  },
25006
25092
  };
@@ -25032,6 +25118,9 @@ const PIVOT_HEADER = {
25032
25118
  };
25033
25119
  }
25034
25120
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
25121
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
25122
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
25123
+ }
25035
25124
  const lastNode = domain.at(-1);
25036
25125
  if (lastNode?.field === "measure") {
25037
25126
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -25254,6 +25343,9 @@ function isEmpty(data) {
25254
25343
  return data === undefined || data.value === null;
25255
25344
  }
25256
25345
  const getNeutral = { number: 0, string: "", boolean: false };
25346
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
25347
+ return Math.abs(value1 - value2) < epsilon;
25348
+ }
25257
25349
  const EQ = {
25258
25350
  description: _t("Equal."),
25259
25351
  args: [
@@ -25269,6 +25361,9 @@ const EQ = {
25269
25361
  if (typeof _value2 === "string") {
25270
25362
  _value2 = _value2.toUpperCase();
25271
25363
  }
25364
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
25365
+ return areAlmostEqual(_value1, _value2);
25366
+ }
25272
25367
  return _value1 === _value2;
25273
25368
  },
25274
25369
  };
@@ -25308,6 +25403,9 @@ const GT = {
25308
25403
  ],
25309
25404
  compute: function (value1, value2) {
25310
25405
  return applyRelationalOperator(value1, value2, (v1, v2) => {
25406
+ if (typeof v1 === "number" && typeof v2 === "number") {
25407
+ return !areAlmostEqual(v1, v2) && v1 > v2;
25408
+ }
25311
25409
  return v1 > v2;
25312
25410
  });
25313
25411
  },
@@ -25323,6 +25421,9 @@ const GTE = {
25323
25421
  ],
25324
25422
  compute: function (value1, value2) {
25325
25423
  return applyRelationalOperator(value1, value2, (v1, v2) => {
25424
+ if (typeof v1 === "number" && typeof v2 === "number") {
25425
+ return areAlmostEqual(v1, v2) || v1 > v2;
25426
+ }
25326
25427
  return v1 >= v2;
25327
25428
  });
25328
25429
  },
@@ -26445,10 +26546,18 @@ autoCompleteProviders.add("functions", {
26445
26546
  });
26446
26547
 
26447
26548
  class DOMFocusableElementStore {
26448
- mutators = ["setFocusableElement"];
26549
+ mutators = ["setFocusableElement", "focus"];
26449
26550
  focusableElement = undefined;
26450
26551
  setFocusableElement(element) {
26451
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;
26452
26561
  }
26453
26562
  }
26454
26563
 
@@ -27291,7 +27400,7 @@ class Composer extends owl.Component {
27291
27400
  if (document.activeElement === this.contentHelper.el &&
27292
27401
  this.props.composerStore.editionMode === "inactive" &&
27293
27402
  !this.props.isDefaultFocus) {
27294
- this.DOMFocusableElementStore.focusableElement?.focus();
27403
+ this.DOMFocusableElementStore.focus();
27295
27404
  }
27296
27405
  });
27297
27406
  owl.useEffect(() => {
@@ -27560,6 +27669,13 @@ class Composer extends owl.Component {
27560
27669
  openAssistant() {
27561
27670
  this.assistant.forcedClosed = false;
27562
27671
  }
27672
+ onWheel(event) {
27673
+ // detect if scrollbar is available
27674
+ if (this.composerRef.el &&
27675
+ this.composerRef.el.scrollHeight > this.composerRef.el.clientHeight) {
27676
+ event.stopPropagation();
27677
+ }
27678
+ }
27563
27679
  // ---------------------------------------------------------------------------
27564
27680
  // Private
27565
27681
  // ---------------------------------------------------------------------------
@@ -31756,12 +31872,20 @@ class HoveredCellStore extends SpreadsheetStore {
31756
31872
  }
31757
31873
  }
31758
31874
  hover(position) {
31875
+ if (position.col === this.col && position.row === this.row) {
31876
+ return "noStateChange";
31877
+ }
31759
31878
  this.col = position.col;
31760
31879
  this.row = position.row;
31880
+ return;
31761
31881
  }
31762
31882
  clear() {
31883
+ if (this.col === undefined && this.row === undefined) {
31884
+ return "noStateChange";
31885
+ }
31763
31886
  this.col = undefined;
31764
31887
  this.row = undefined;
31888
+ return;
31765
31889
  }
31766
31890
  }
31767
31891
 
@@ -31783,7 +31907,11 @@ class CellPopoverStore extends SpreadsheetStore {
31783
31907
  this.persistentPopover = { col, row, sheetId, type };
31784
31908
  }
31785
31909
  close() {
31910
+ if (!this.persistentPopover) {
31911
+ return "noStateChange";
31912
+ }
31786
31913
  this.persistentPopover = undefined;
31914
+ return;
31787
31915
  }
31788
31916
  get persistentCellPopover() {
31789
31917
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -32559,10 +32687,13 @@ const duplicateSheet = {
32559
32687
  name: _t("Duplicate"),
32560
32688
  execute: (env) => {
32561
32689
  const sheetIdFrom = env.model.getters.getActiveSheetId();
32690
+ const sheetNameFrom = env.model.getters.getSheetName(sheetIdFrom);
32562
32691
  const sheetIdTo = env.model.uuidGenerator.smallUuid();
32692
+ const sheetNameTo = env.model.getters.getDuplicateSheetName(sheetNameFrom);
32563
32693
  env.model.dispatch("DUPLICATE_SHEET", {
32564
32694
  sheetId: sheetIdFrom,
32565
32695
  sheetIdTo,
32696
+ sheetNameTo,
32566
32697
  });
32567
32698
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
32568
32699
  },
@@ -39133,7 +39264,7 @@ class AbstractComposerStore extends SpreadsheetStore {
39133
39264
  .find((token) => {
39134
39265
  const { xc, sheetName: sheet } = splitReference(token.value);
39135
39266
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
39136
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
39267
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
39137
39268
  return false;
39138
39269
  }
39139
39270
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -43901,8 +44032,8 @@ function compareDimensionValues(dimension, a, b) {
43901
44032
 
43902
44033
  const NULL_SYMBOL = Symbol("NULL");
43903
44034
  function createDate(dimension, value, locale) {
43904
- const granularity = dimension.granularity;
43905
- if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
44035
+ const granularity = dimension.granularity || "month";
44036
+ if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
43906
44037
  throw new Error(`Unknown date granularity: ${granularity}`);
43907
44038
  }
43908
44039
  const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
@@ -43921,6 +44052,9 @@ function createDate(dimension, value, locale) {
43921
44052
  case "month_number":
43922
44053
  number = date.getMonth() + 1;
43923
44054
  break;
44055
+ case "month":
44056
+ number = Math.floor(toNumber(value, locale));
44057
+ break;
43924
44058
  case "iso_week_number":
43925
44059
  number = date.getIsoWeek();
43926
44060
  break;
@@ -44014,6 +44148,10 @@ const MAP_VALUE_DIMENSION_DATE = {
44014
44148
  set: new Set(),
44015
44149
  values: {},
44016
44150
  },
44151
+ month: {
44152
+ set: new Set(),
44153
+ values: {},
44154
+ },
44017
44155
  iso_week_number: {
44018
44156
  set: new Set(),
44019
44157
  values: {},
@@ -44224,7 +44362,7 @@ class SpreadsheetPivot {
44224
44362
  const cells = this.filterDataEntriesFromDomain(this.dataEntries, domain);
44225
44363
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
44226
44364
  if (dimension.type === "datetime") {
44227
- const adapter = pivotTimeAdapter(dimension.granularity);
44365
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
44228
44366
  return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
44229
44367
  }
44230
44368
  if (!finalCell) {
@@ -44342,7 +44480,7 @@ class SpreadsheetPivot {
44342
44480
  if (nonEmptyCells.length === 0) {
44343
44481
  return "integer";
44344
44482
  }
44345
- if (nonEmptyCells.every((cell) => cell.format && isDateTimeFormat(cell.format))) {
44483
+ if (nonEmptyCells.every((cell) => cell.type === CellValueType.number && cell.format && isDateTimeFormat(cell.format))) {
44346
44484
  return "datetime";
44347
44485
  }
44348
44486
  if (nonEmptyCells.every((cell) => cell.type === CellValueType.boolean)) {
@@ -44423,7 +44561,12 @@ class SpreadsheetPivot {
44423
44561
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
44424
44562
  }
44425
44563
  else {
44426
- 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
+ }
44427
44570
  }
44428
44571
  }
44429
44572
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -44437,7 +44580,7 @@ class SpreadsheetPivot {
44437
44580
  for (const entry of dataEntries) {
44438
44581
  for (const dimension of dateDimensions) {
44439
44582
  const value = createDate(dimension, entry[dimension.fieldName]?.value || null, this.getters.getLocale());
44440
- const adapter = pivotTimeAdapter(dimension.granularity);
44583
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
44441
44584
  const { format, value: valueToFormat } = adapter.toValueAndFormat(value, locale);
44442
44585
  entry[dimension.nameWithGranularity] = {
44443
44586
  value,
@@ -44457,6 +44600,7 @@ const dateGranularities = [
44457
44600
  "year",
44458
44601
  "quarter_number",
44459
44602
  "month_number",
44603
+ "month",
44460
44604
  "iso_week_number",
44461
44605
  "day_of_month",
44462
44606
  "day",
@@ -44697,7 +44841,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
44697
44841
  : this.datetimeGranularities);
44698
44842
  }
44699
44843
  for (const field of dateFields) {
44700
- granularitiesPerFields[field.fieldName].delete(field.granularity);
44844
+ granularitiesPerFields[field.fieldName].delete(field.granularity || "month");
44701
44845
  }
44702
44846
  return granularitiesPerFields;
44703
44847
  }
@@ -46855,6 +46999,8 @@ class GridComposer extends owl.Component {
46855
46999
  }
46856
47000
  get composerProps() {
46857
47001
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
47002
+ // Remove the wrapper border width
47003
+ const maxHeight = this.props.gridDims.height - this.rect.y - 2 * COMPOSER_BORDER_WIDTH;
46858
47004
  return {
46859
47005
  rect: { ...this.rect },
46860
47006
  delimitation: {
@@ -46872,6 +47018,7 @@ class GridComposer extends owl.Component {
46872
47018
  }),
46873
47019
  onInputContextMenu: this.props.onInputContextMenu,
46874
47020
  composerStore: this.composerStore,
47021
+ inputStyle: `max-height: ${maxHeight}px;`,
46875
47022
  };
46876
47023
  }
46877
47024
  get containerStyle() {
@@ -49415,10 +49562,6 @@ function useGridDrawing(refName, model, canvasSize) {
49415
49562
  ctx.scale(dpr, dpr);
49416
49563
  for (const layer of OrderedLayers()) {
49417
49564
  model.drawLayer(renderingContext, layer);
49418
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
49419
- // it does not mutate anything. Most importantly it's used
49420
- // during rendering. Invoking a mutator during rendering would
49421
- // trigger another rendering, ultimately resulting in an infinite loop.
49422
49565
  rendererStore.drawLayer(renderingContext, layer);
49423
49566
  }
49424
49567
  }
@@ -50108,7 +50251,7 @@ class Grid extends owl.Component {
50108
50251
  this.cellPopovers = useStore(CellPopoverStore);
50109
50252
  owl.useEffect(() => {
50110
50253
  if (!this.sidePanel.isOpen) {
50111
- this.DOMFocusableElementStore.focusableElement?.focus();
50254
+ this.DOMFocusableElementStore.focus();
50112
50255
  }
50113
50256
  }, () => [this.sidePanel.isOpen]);
50114
50257
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -50316,7 +50459,7 @@ class Grid extends owl.Component {
50316
50459
  focusDefaultElement() {
50317
50460
  if (!this.env.model.getters.getSelectedFigureId() &&
50318
50461
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
50319
- this.DOMFocusableElementStore.focusableElement?.focus();
50462
+ this.DOMFocusableElementStore.focus();
50320
50463
  }
50321
50464
  }
50322
50465
  get gridEl() {
@@ -50686,6 +50829,322 @@ class EditableName extends owl.Component {
50686
50829
  }
50687
50830
  }
50688
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
+
50689
51148
  /**
50690
51149
  * BasePlugin
50691
51150
  *
@@ -52271,9 +52730,7 @@ class ChartPlugin extends CorePlugin {
52271
52730
  : "Success" /* CommandResult.Success */;
52272
52731
  }
52273
52732
  checkChartExists(cmd) {
52274
- return this.getters.getFigureSheetId(cmd.id)
52275
- ? "Success" /* CommandResult.Success */
52276
- : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
52733
+ return this.isChartDefined(cmd.id) ? "Success" /* CommandResult.Success */ : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
52277
52734
  }
52278
52735
  }
52279
52736
 
@@ -54196,7 +54653,7 @@ class RangeAdapter {
54196
54653
  if (range.sheetId === cmd.sheetId) {
54197
54654
  return { changeType: "CHANGE", range };
54198
54655
  }
54199
- if (cmd.name && range.invalidSheetName === cmd.name) {
54656
+ if (isSheetNameEqual(range.invalidSheetName, cmd.name)) {
54200
54657
  const invalidSheetName = undefined;
54201
54658
  const sheetId = cmd.sheetId;
54202
54659
  const newRange = range.clone({ sheetId, invalidSheetName });
@@ -54534,6 +54991,7 @@ class SheetPlugin extends CorePlugin {
54534
54991
  "getCommandZones",
54535
54992
  "getUnboundedZone",
54536
54993
  "checkElementsIncludeAllNonFrozenHeaders",
54994
+ "getDuplicateSheetName",
54537
54995
  ];
54538
54996
  sheetIdsMapName = {};
54539
54997
  orderedSheetIds = [];
@@ -54558,7 +55016,11 @@ class SheetPlugin extends CorePlugin {
54558
55016
  return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
54559
55017
  }
54560
55018
  case "DUPLICATE_SHEET": {
54561
- return this.sheets[cmd.sheetIdTo] ? "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */ : "Success" /* CommandResult.Success */;
55019
+ if (this.sheets[cmd.sheetIdTo])
55020
+ return "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */;
55021
+ if (this.orderedSheetIds.map(this.getSheetName.bind(this)).includes(cmd.sheetNameTo))
55022
+ return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
55023
+ return "Success" /* CommandResult.Success */;
54562
55024
  }
54563
55025
  case "MOVE_SHEET":
54564
55026
  try {
@@ -54635,7 +55097,7 @@ class SheetPlugin extends CorePlugin {
54635
55097
  this.showSheet(cmd.sheetId);
54636
55098
  break;
54637
55099
  case "DUPLICATE_SHEET":
54638
- this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo);
55100
+ this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo, cmd.sheetNameTo);
54639
55101
  break;
54640
55102
  case "DELETE_SHEET":
54641
55103
  this.deleteSheet(this.sheets[cmd.sheetId]);
@@ -54776,7 +55238,7 @@ class SheetPlugin extends CorePlugin {
54776
55238
  if (name) {
54777
55239
  const unquotedName = getUnquotedSheetName(name);
54778
55240
  for (const key in this.sheetIdsMapName) {
54779
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
55241
+ if (isSheetNameEqual(key, unquotedName)) {
54780
55242
  return this.sheetIdsMapName[key];
54781
55243
  }
54782
55244
  }
@@ -54841,14 +55303,8 @@ class SheetPlugin extends CorePlugin {
54841
55303
  return dimension === "COL" ? this.getNumberCols(sheetId) : this.getNumberRows(sheetId);
54842
55304
  }
54843
55305
  getNextSheetName(baseName = "Sheet") {
54844
- let i = 1;
54845
55306
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
54846
- let name = `${baseName}${i}`;
54847
- while (names.includes(name)) {
54848
- name = `${baseName}${i}`;
54849
- i++;
54850
- }
54851
- return name;
55307
+ return getNextSheetName(names, baseName);
54852
55308
  }
54853
55309
  getSheetSize(sheetId) {
54854
55310
  return {
@@ -55030,7 +55486,7 @@ class SheetPlugin extends CorePlugin {
55030
55486
  }
55031
55487
  const { orderedSheetIds, sheets } = this;
55032
55488
  const name = cmd.name && cmd.name.trim().toLowerCase();
55033
- 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)) {
55034
55490
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
55035
55491
  }
55036
55492
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -55094,9 +55550,8 @@ class SheetPlugin extends CorePlugin {
55094
55550
  showSheet(sheetId) {
55095
55551
  this.history.update("sheets", sheetId, "isVisible", true);
55096
55552
  }
55097
- duplicateSheet(fromId, toId) {
55553
+ duplicateSheet(fromId, toId, toName) {
55098
55554
  const sheet = this.getSheet(fromId);
55099
- const toName = this.getDuplicateSheetName(sheet.name);
55100
55555
  const newSheet = deepCopy(sheet);
55101
55556
  newSheet.id = toId;
55102
55557
  newSheet.name = toName;
@@ -55128,15 +55583,8 @@ class SheetPlugin extends CorePlugin {
55128
55583
  this.history.update("sheetIdsMapName", sheetIdsMapName);
55129
55584
  }
55130
55585
  getDuplicateSheetName(sheetName) {
55131
- let i = 1;
55132
55586
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
55133
- const baseName = _t("Copy of %s", sheetName);
55134
- let name = baseName.toString();
55135
- while (names.includes(name)) {
55136
- name = `${baseName} (${i})`;
55137
- i++;
55138
- }
55139
- return name;
55587
+ return getDuplicateSheetName(sheetName, names);
55140
55588
  }
55141
55589
  deleteSheet(sheet) {
55142
55590
  const name = sheet.name;
@@ -57962,8 +58410,8 @@ class SpreadingRelation {
57962
58410
  const EMPTY_ARRAY = [];
57963
58411
 
57964
58412
  const MAX_ITERATION = 30;
57965
- const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
57966
- const EMPTY_CELL = createEvaluatedCell({ value: null });
58413
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
58414
+ const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
57967
58415
  class Evaluator {
57968
58416
  context;
57969
58417
  getters;
@@ -63925,6 +64373,55 @@ class HistoryPlugin extends UIPlugin {
63925
64373
  }
63926
64374
  }
63927
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
+
63928
64425
  class SplitToColumnsPlugin extends UIPlugin {
63929
64426
  static getters = ["getAutomaticSeparator"];
63930
64427
  allowDispatch(cmd) {
@@ -66708,6 +67205,7 @@ const featurePluginRegistry = new Registry()
66708
67205
  .add("automatic_sum", AutomaticSumPlugin)
66709
67206
  .add("format", FormatPlugin)
66710
67207
  .add("insert_pivot", InsertPivotPlugin)
67208
+ .add("pivot_presence", PivotPresencePlugin)
66711
67209
  .add("split_to_columns", SplitToColumnsPlugin)
66712
67210
  .add("collaborative", CollaborativePlugin)
66713
67211
  .add("history", HistoryPlugin)
@@ -67087,11 +67585,11 @@ class BottomBarSheet extends owl.Component {
67087
67585
  if (ev.key === "Enter") {
67088
67586
  ev.preventDefault();
67089
67587
  this.stopEdition();
67090
- this.DOMFocusableElementStore.focusableElement?.focus();
67588
+ this.DOMFocusableElementStore.focus();
67091
67589
  }
67092
67590
  if (ev.key === "Escape") {
67093
67591
  this.cancelEdition();
67094
- this.DOMFocusableElementStore.focusableElement?.focus();
67592
+ this.DOMFocusableElementStore.focus();
67095
67593
  }
67096
67594
  }
67097
67595
  onMouseEventSheetName(ev) {
@@ -73701,6 +74199,7 @@ const components = {
73701
74199
  PivotDimensionOrder,
73702
74200
  PivotDimension,
73703
74201
  PivotLayoutConfigurator,
74202
+ PivotHTMLRenderer,
73704
74203
  EditableName,
73705
74204
  PivotDeferUpdate,
73706
74205
  PivotTitleSection,
@@ -73794,6 +74293,6 @@ exports.tokenColors = tokenColors;
73794
74293
  exports.tokenize = tokenize;
73795
74294
 
73796
74295
 
73797
- __info__.version = "18.0.26";
73798
- __info__.date = "2025-05-02T12:30:31.966Z";
73799
- __info__.hash = "5bdf504";
74296
+ __info__.version = "18.0.28";
74297
+ __info__.date = "2025-05-13T17:53:12.402Z";
74298
+ __info__.hash = "b3088aa";