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