@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
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -3180,7 +3180,7 @@ function isDateAfter(date, dateAfter) {
3180
3180
  */
3181
3181
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3182
3182
  decimalSeparator = escapeRegExp(decimalSeparator);
3183
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3183
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3184
3184
  });
3185
3185
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3186
3186
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -6102,6 +6102,32 @@ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6102
6102
  })
6103
6103
  .filter(isDefined);
6104
6104
  }
6105
+ function getNextSheetName(existingNames, baseName = "Sheet") {
6106
+ let i = 1;
6107
+ let name = `${baseName}${i}`;
6108
+ while (existingNames.includes(name)) {
6109
+ name = `${baseName}${i}`;
6110
+ i++;
6111
+ }
6112
+ return name;
6113
+ }
6114
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6115
+ let i = 1;
6116
+ const baseName = _t("Copy of %s", nameToDuplicate);
6117
+ let name = baseName.toString();
6118
+ while (existingNames.includes(name)) {
6119
+ name = `${baseName} (${i})`;
6120
+ i++;
6121
+ }
6122
+ return name;
6123
+ }
6124
+ function isSheetNameEqual(name1, name2) {
6125
+ if (name1 === undefined || name2 === undefined) {
6126
+ return false;
6127
+ }
6128
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6129
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6130
+ }
6105
6131
 
6106
6132
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6107
6133
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
@@ -7790,6 +7816,24 @@ const monthNumberAdapter = {
7790
7816
  return `${normalizedValue}`;
7791
7817
  },
7792
7818
  };
7819
+ /**
7820
+ * normalizes month number + year
7821
+ */
7822
+ const monthAdapter = {
7823
+ normalizeFunctionValue(value) {
7824
+ const date = toNumber(value, DEFAULT_LOCALE);
7825
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
7826
+ },
7827
+ toValueAndFormat(normalizedValue) {
7828
+ return {
7829
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
7830
+ format: "mmmm yyyy",
7831
+ };
7832
+ },
7833
+ toFunctionValue(normalizedValue) {
7834
+ return `"${normalizedValue}"`;
7835
+ },
7836
+ };
7793
7837
  /**
7794
7838
  * normalizes quarter number
7795
7839
  */
@@ -7920,6 +7964,7 @@ pivotTimeAdapterRegistry
7920
7964
  .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
7921
7965
  .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
7922
7966
  .add("month_number", nullHandlerDecorator(monthNumberAdapter))
7967
+ .add("month", nullHandlerDecorator(monthAdapter))
7923
7968
  .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
7924
7969
  .add("day_of_week", nullHandlerDecorator(dayOfWeekAdapter))
7925
7970
  .add("hour_number", nullHandlerDecorator(hourNumberAdapter))
@@ -7936,10 +7981,9 @@ const AGGREGATOR_NAMES = {
7936
7981
  avg: _t("Average"),
7937
7982
  sum: _t("Sum"),
7938
7983
  };
7939
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
7940
7984
  const AGGREGATORS_BY_FIELD_TYPE = {
7941
- integer: NUMBER_CHAR_AGGREGATORS,
7942
- char: NUMBER_CHAR_AGGREGATORS,
7985
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
7986
+ char: ["count_distinct", "count"],
7943
7987
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
7944
7988
  };
7945
7989
  const AGGREGATORS = {};
@@ -8100,10 +8144,7 @@ function toNormalizedPivotValue(dimension, groupValue) {
8100
8144
  return normalizer(groupValueString, dimension.granularity);
8101
8145
  }
8102
8146
  function normalizeDateTime(value, granularity) {
8103
- if (!granularity) {
8104
- throw new Error("Missing granularity");
8105
- }
8106
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8147
+ return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
8107
8148
  }
8108
8149
  function toFunctionPivotValue(value, dimension) {
8109
8150
  if (value === null) {
@@ -8115,10 +8156,7 @@ function toFunctionPivotValue(value, dimension) {
8115
8156
  return pivotToFunctionValueRegistry.get(dimension.type)(value, dimension.granularity);
8116
8157
  }
8117
8158
  function toFunctionValueDateTime(value, granularity) {
8118
- if (!granularity) {
8119
- throw new Error("Missing granularity");
8120
- }
8121
- return pivotTimeAdapter(granularity).toFunctionValue(value);
8159
+ return pivotTimeAdapter(granularity ?? "month").toFunctionValue(value);
8122
8160
  }
8123
8161
  const pivotNormalizationValueRegistry = new Registry();
8124
8162
  pivotNormalizationValueRegistry
@@ -9278,7 +9316,10 @@ function proxifyStoreMutation(store, callback) {
9278
9316
  const functionProxy = new Proxy(value, {
9279
9317
  // trap the function call
9280
9318
  apply(target, thisArg, argArray) {
9281
- Reflect.apply(target, thisStore, argArray);
9319
+ const res = Reflect.apply(target, thisStore, argArray);
9320
+ if (res === "noStateChange") {
9321
+ return;
9322
+ }
9282
9323
  callback();
9283
9324
  },
9284
9325
  });
@@ -9300,7 +9341,7 @@ function getDependencyContainer(env) {
9300
9341
  const ModelStore = createAbstractStore("Model");
9301
9342
 
9302
9343
  class RendererStore {
9303
- mutators = ["register", "unRegister"];
9344
+ mutators = ["register", "unRegister", "drawLayer"];
9304
9345
  renderers = {};
9305
9346
  register(renderer) {
9306
9347
  if (!renderer.renderingLayers.length) {
@@ -9320,14 +9361,14 @@ class RendererStore {
9320
9361
  }
9321
9362
  drawLayer(context, layer) {
9322
9363
  const renderers = this.renderers[layer];
9323
- if (!renderers) {
9324
- return;
9325
- }
9326
- for (const renderer of renderers) {
9327
- context.ctx.save();
9328
- renderer.drawLayer(context, layer);
9329
- context.ctx.restore();
9364
+ if (renderers) {
9365
+ for (const renderer of renderers) {
9366
+ context.ctx.save();
9367
+ renderer.drawLayer(context, layer);
9368
+ context.ctx.restore();
9369
+ }
9330
9370
  }
9371
+ return "noStateChange";
9331
9372
  }
9332
9373
  }
9333
9374
 
@@ -9380,16 +9421,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9380
9421
  focusComposer(listener, args) {
9381
9422
  this.activeComposer = listener;
9382
9423
  if (this.getters.isReadonly()) {
9383
- return;
9424
+ return "noStateChange";
9384
9425
  }
9385
9426
  this._focusMode = args.focusMode || "contentFocus";
9386
9427
  if (this._focusMode !== "inactive") {
9387
9428
  this.setComposerContent(args);
9388
9429
  }
9430
+ return;
9389
9431
  }
9390
9432
  focusActiveComposer(args) {
9391
9433
  if (this.getters.isReadonly()) {
9392
- return;
9434
+ return "noStateChange";
9393
9435
  }
9394
9436
  if (!this.activeComposer) {
9395
9437
  throw new Error("No composer is registered");
@@ -9398,6 +9440,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9398
9440
  if (this._focusMode !== "inactive") {
9399
9441
  this.setComposerContent(args);
9400
9442
  }
9443
+ return;
9401
9444
  }
9402
9445
  /**
9403
9446
  * Start the edition or update the content if it's already started.
@@ -11712,7 +11755,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
11712
11755
  ({ xc, sheetName } = splitReference(reference));
11713
11756
  let rangeSheetIndex;
11714
11757
  if (sheetName) {
11715
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
11758
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
11716
11759
  if (index < 0) {
11717
11760
  throw new Error("Unable to find a sheet with the name " + sheetName);
11718
11761
  }
@@ -12053,7 +12096,7 @@ function convertFormula(formula, data) {
12053
12096
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
12054
12097
  externalRefId = Number(externalRefId) - 1;
12055
12098
  cellRef = cellRef.replace(/\$/g, "");
12056
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
12099
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
12057
12100
  if (sheetIndex === -1) {
12058
12101
  return match;
12059
12102
  }
@@ -12716,7 +12759,7 @@ function convertPivotTableConfig(pivotTable) {
12716
12759
  */
12717
12760
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
12718
12761
  for (let tableSheet of convertedSheets) {
12719
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
12762
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
12720
12763
  for (let table of tables) {
12721
12764
  const tabRef = table.name + "[";
12722
12765
  for (let sheet of convertedSheets) {
@@ -15251,6 +15294,7 @@ function repairInitialMessages(data, initialMessages) {
15251
15294
  initialMessages = dropCommands(initialMessages, "SORT_CELLS");
15252
15295
  initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
15253
15296
  initialMessages = fixChartDefinitions(data, initialMessages);
15297
+ initialMessages = fixTranslatedDuplicateSheetName(data, initialMessages);
15254
15298
  return initialMessages;
15255
15299
  }
15256
15300
  /**
@@ -15350,6 +15394,40 @@ function fixChartDefinitions(data, initialMessages) {
15350
15394
  }
15351
15395
  return messages;
15352
15396
  }
15397
+ function fixTranslatedDuplicateSheetName(data, initialMessages) {
15398
+ const sheetNames = {};
15399
+ for (const sheet of data.sheets || []) {
15400
+ sheetNames[sheet.id] = sheet.name;
15401
+ }
15402
+ const messages = [];
15403
+ for (const message of initialMessages) {
15404
+ if (message.type === "REMOTE_REVISION") {
15405
+ const commands = [];
15406
+ for (const cmd of message.commands) {
15407
+ switch (cmd.type) {
15408
+ case "DUPLICATE_SHEET":
15409
+ cmd.sheetNameTo =
15410
+ cmd.sheetNameTo ??
15411
+ getDuplicateSheetName(sheetNames[cmd.sheetId], Object.values(sheetNames));
15412
+ break;
15413
+ case "CREATE_SHEET":
15414
+ case "RENAME_SHEET":
15415
+ sheetNames[cmd.sheetId] = cmd.name || getNextSheetName(Object.values(sheetNames));
15416
+ break;
15417
+ }
15418
+ commands.push(cmd);
15419
+ }
15420
+ messages.push({
15421
+ ...message,
15422
+ commands,
15423
+ });
15424
+ }
15425
+ else {
15426
+ messages.push(message);
15427
+ }
15428
+ }
15429
+ return initialMessages;
15430
+ }
15353
15431
  // -----------------------------------------------------------------------------
15354
15432
  // Helpers
15355
15433
  // -----------------------------------------------------------------------------
@@ -24365,7 +24443,7 @@ const IF = {
24365
24443
  return { value: "" };
24366
24444
  }
24367
24445
  if (result.value === null) {
24368
- result.value = "";
24446
+ return { ...result, value: "" };
24369
24447
  }
24370
24448
  return result;
24371
24449
  },
@@ -24386,7 +24464,7 @@ const IFERROR = {
24386
24464
  return { value: "" };
24387
24465
  }
24388
24466
  if (result.value === null) {
24389
- result.value = "";
24467
+ return { ...result, value: "" };
24390
24468
  }
24391
24469
  return result;
24392
24470
  },
@@ -24407,7 +24485,7 @@ const IFNA = {
24407
24485
  return { value: "" };
24408
24486
  }
24409
24487
  if (result.value === null) {
24410
- result.value = "";
24488
+ return { ...result, value: "" };
24411
24489
  }
24412
24490
  return result;
24413
24491
  },
@@ -24433,7 +24511,7 @@ const IFS = {
24433
24511
  return { value: "" };
24434
24512
  }
24435
24513
  if (result.value === null) {
24436
- result.value = "";
24514
+ return { ...result, value: "" };
24437
24515
  }
24438
24516
  return result;
24439
24517
  }
@@ -24551,6 +24629,11 @@ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
24551
24629
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
24552
24630
  throw new InvalidReferenceError();
24553
24631
  }
24632
+ if (evalContext.__originCellPosition &&
24633
+ range.sheetId === evalContext.__originSheetId &&
24634
+ isZoneInside(positionToZone(evalContext.__originCellPosition), zone)) {
24635
+ throw new CircularDependencyError();
24636
+ }
24554
24637
  dependencies.push(range);
24555
24638
  }
24556
24639
  for (const measure of forMeasures) {
@@ -24999,6 +25082,9 @@ const PIVOT_VALUE = {
24999
25082
  };
25000
25083
  }
25001
25084
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
25085
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
25086
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
25087
+ }
25002
25088
  return pivot.getPivotCellValueAndFormat(_measure, domain);
25003
25089
  },
25004
25090
  };
@@ -25030,6 +25116,9 @@ const PIVOT_HEADER = {
25030
25116
  };
25031
25117
  }
25032
25118
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
25119
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
25120
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
25121
+ }
25033
25122
  const lastNode = domain.at(-1);
25034
25123
  if (lastNode?.field === "measure") {
25035
25124
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -25252,6 +25341,9 @@ function isEmpty(data) {
25252
25341
  return data === undefined || data.value === null;
25253
25342
  }
25254
25343
  const getNeutral = { number: 0, string: "", boolean: false };
25344
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
25345
+ return Math.abs(value1 - value2) < epsilon;
25346
+ }
25255
25347
  const EQ = {
25256
25348
  description: _t("Equal."),
25257
25349
  args: [
@@ -25267,6 +25359,9 @@ const EQ = {
25267
25359
  if (typeof _value2 === "string") {
25268
25360
  _value2 = _value2.toUpperCase();
25269
25361
  }
25362
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
25363
+ return areAlmostEqual(_value1, _value2);
25364
+ }
25270
25365
  return _value1 === _value2;
25271
25366
  },
25272
25367
  };
@@ -25306,6 +25401,9 @@ const GT = {
25306
25401
  ],
25307
25402
  compute: function (value1, value2) {
25308
25403
  return applyRelationalOperator(value1, value2, (v1, v2) => {
25404
+ if (typeof v1 === "number" && typeof v2 === "number") {
25405
+ return !areAlmostEqual(v1, v2) && v1 > v2;
25406
+ }
25309
25407
  return v1 > v2;
25310
25408
  });
25311
25409
  },
@@ -25321,6 +25419,9 @@ const GTE = {
25321
25419
  ],
25322
25420
  compute: function (value1, value2) {
25323
25421
  return applyRelationalOperator(value1, value2, (v1, v2) => {
25422
+ if (typeof v1 === "number" && typeof v2 === "number") {
25423
+ return areAlmostEqual(v1, v2) || v1 > v2;
25424
+ }
25324
25425
  return v1 >= v2;
25325
25426
  });
25326
25427
  },
@@ -26443,10 +26544,18 @@ autoCompleteProviders.add("functions", {
26443
26544
  });
26444
26545
 
26445
26546
  class DOMFocusableElementStore {
26446
- mutators = ["setFocusableElement"];
26547
+ mutators = ["setFocusableElement", "focus"];
26447
26548
  focusableElement = undefined;
26448
26549
  setFocusableElement(element) {
26449
26550
  this.focusableElement = element;
26551
+ return "noStateChange";
26552
+ }
26553
+ focus() {
26554
+ if (this.focusableElement === document.activeElement) {
26555
+ return "noStateChange";
26556
+ }
26557
+ this.focusableElement?.focus();
26558
+ return;
26450
26559
  }
26451
26560
  }
26452
26561
 
@@ -27289,7 +27398,7 @@ class Composer extends Component {
27289
27398
  if (document.activeElement === this.contentHelper.el &&
27290
27399
  this.props.composerStore.editionMode === "inactive" &&
27291
27400
  !this.props.isDefaultFocus) {
27292
- this.DOMFocusableElementStore.focusableElement?.focus();
27401
+ this.DOMFocusableElementStore.focus();
27293
27402
  }
27294
27403
  });
27295
27404
  useEffect(() => {
@@ -27558,6 +27667,13 @@ class Composer extends Component {
27558
27667
  openAssistant() {
27559
27668
  this.assistant.forcedClosed = false;
27560
27669
  }
27670
+ onWheel(event) {
27671
+ // detect if scrollbar is available
27672
+ if (this.composerRef.el &&
27673
+ this.composerRef.el.scrollHeight > this.composerRef.el.clientHeight) {
27674
+ event.stopPropagation();
27675
+ }
27676
+ }
27561
27677
  // ---------------------------------------------------------------------------
27562
27678
  // Private
27563
27679
  // ---------------------------------------------------------------------------
@@ -31754,12 +31870,20 @@ class HoveredCellStore extends SpreadsheetStore {
31754
31870
  }
31755
31871
  }
31756
31872
  hover(position) {
31873
+ if (position.col === this.col && position.row === this.row) {
31874
+ return "noStateChange";
31875
+ }
31757
31876
  this.col = position.col;
31758
31877
  this.row = position.row;
31878
+ return;
31759
31879
  }
31760
31880
  clear() {
31881
+ if (this.col === undefined && this.row === undefined) {
31882
+ return "noStateChange";
31883
+ }
31761
31884
  this.col = undefined;
31762
31885
  this.row = undefined;
31886
+ return;
31763
31887
  }
31764
31888
  }
31765
31889
 
@@ -31781,7 +31905,11 @@ class CellPopoverStore extends SpreadsheetStore {
31781
31905
  this.persistentPopover = { col, row, sheetId, type };
31782
31906
  }
31783
31907
  close() {
31908
+ if (!this.persistentPopover) {
31909
+ return "noStateChange";
31910
+ }
31784
31911
  this.persistentPopover = undefined;
31912
+ return;
31785
31913
  }
31786
31914
  get persistentCellPopover() {
31787
31915
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -32557,10 +32685,13 @@ const duplicateSheet = {
32557
32685
  name: _t("Duplicate"),
32558
32686
  execute: (env) => {
32559
32687
  const sheetIdFrom = env.model.getters.getActiveSheetId();
32688
+ const sheetNameFrom = env.model.getters.getSheetName(sheetIdFrom);
32560
32689
  const sheetIdTo = env.model.uuidGenerator.smallUuid();
32690
+ const sheetNameTo = env.model.getters.getDuplicateSheetName(sheetNameFrom);
32561
32691
  env.model.dispatch("DUPLICATE_SHEET", {
32562
32692
  sheetId: sheetIdFrom,
32563
32693
  sheetIdTo,
32694
+ sheetNameTo,
32564
32695
  });
32565
32696
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
32566
32697
  },
@@ -39131,7 +39262,7 @@ class AbstractComposerStore extends SpreadsheetStore {
39131
39262
  .find((token) => {
39132
39263
  const { xc, sheetName: sheet } = splitReference(token.value);
39133
39264
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
39134
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
39265
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
39135
39266
  return false;
39136
39267
  }
39137
39268
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -43899,8 +44030,8 @@ function compareDimensionValues(dimension, a, b) {
43899
44030
 
43900
44031
  const NULL_SYMBOL = Symbol("NULL");
43901
44032
  function createDate(dimension, value, locale) {
43902
- const granularity = dimension.granularity;
43903
- if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
44033
+ const granularity = dimension.granularity || "month";
44034
+ if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
43904
44035
  throw new Error(`Unknown date granularity: ${granularity}`);
43905
44036
  }
43906
44037
  const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
@@ -43919,6 +44050,9 @@ function createDate(dimension, value, locale) {
43919
44050
  case "month_number":
43920
44051
  number = date.getMonth() + 1;
43921
44052
  break;
44053
+ case "month":
44054
+ number = Math.floor(toNumber(value, locale));
44055
+ break;
43922
44056
  case "iso_week_number":
43923
44057
  number = date.getIsoWeek();
43924
44058
  break;
@@ -44012,6 +44146,10 @@ const MAP_VALUE_DIMENSION_DATE = {
44012
44146
  set: new Set(),
44013
44147
  values: {},
44014
44148
  },
44149
+ month: {
44150
+ set: new Set(),
44151
+ values: {},
44152
+ },
44015
44153
  iso_week_number: {
44016
44154
  set: new Set(),
44017
44155
  values: {},
@@ -44222,7 +44360,7 @@ class SpreadsheetPivot {
44222
44360
  const cells = this.filterDataEntriesFromDomain(this.dataEntries, domain);
44223
44361
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
44224
44362
  if (dimension.type === "datetime") {
44225
- const adapter = pivotTimeAdapter(dimension.granularity);
44363
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
44226
44364
  return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
44227
44365
  }
44228
44366
  if (!finalCell) {
@@ -44340,7 +44478,7 @@ class SpreadsheetPivot {
44340
44478
  if (nonEmptyCells.length === 0) {
44341
44479
  return "integer";
44342
44480
  }
44343
- if (nonEmptyCells.every((cell) => cell.format && isDateTimeFormat(cell.format))) {
44481
+ if (nonEmptyCells.every((cell) => cell.type === CellValueType.number && cell.format && isDateTimeFormat(cell.format))) {
44344
44482
  return "datetime";
44345
44483
  }
44346
44484
  if (nonEmptyCells.every((cell) => cell.type === CellValueType.boolean)) {
@@ -44421,7 +44559,12 @@ class SpreadsheetPivot {
44421
44559
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
44422
44560
  }
44423
44561
  else {
44424
- entry[field.name] = cell;
44562
+ if (field.type === "char") {
44563
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
44564
+ }
44565
+ else {
44566
+ entry[field.name] = cell;
44567
+ }
44425
44568
  }
44426
44569
  }
44427
44570
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -44435,7 +44578,7 @@ class SpreadsheetPivot {
44435
44578
  for (const entry of dataEntries) {
44436
44579
  for (const dimension of dateDimensions) {
44437
44580
  const value = createDate(dimension, entry[dimension.fieldName]?.value || null, this.getters.getLocale());
44438
- const adapter = pivotTimeAdapter(dimension.granularity);
44581
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
44439
44582
  const { format, value: valueToFormat } = adapter.toValueAndFormat(value, locale);
44440
44583
  entry[dimension.nameWithGranularity] = {
44441
44584
  value,
@@ -44455,6 +44598,7 @@ const dateGranularities = [
44455
44598
  "year",
44456
44599
  "quarter_number",
44457
44600
  "month_number",
44601
+ "month",
44458
44602
  "iso_week_number",
44459
44603
  "day_of_month",
44460
44604
  "day",
@@ -44695,7 +44839,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
44695
44839
  : this.datetimeGranularities);
44696
44840
  }
44697
44841
  for (const field of dateFields) {
44698
- granularitiesPerFields[field.fieldName].delete(field.granularity);
44842
+ granularitiesPerFields[field.fieldName].delete(field.granularity || "month");
44699
44843
  }
44700
44844
  return granularitiesPerFields;
44701
44845
  }
@@ -46853,6 +46997,8 @@ class GridComposer extends Component {
46853
46997
  }
46854
46998
  get composerProps() {
46855
46999
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
47000
+ // Remove the wrapper border width
47001
+ const maxHeight = this.props.gridDims.height - this.rect.y - 2 * COMPOSER_BORDER_WIDTH;
46856
47002
  return {
46857
47003
  rect: { ...this.rect },
46858
47004
  delimitation: {
@@ -46870,6 +47016,7 @@ class GridComposer extends Component {
46870
47016
  }),
46871
47017
  onInputContextMenu: this.props.onInputContextMenu,
46872
47018
  composerStore: this.composerStore,
47019
+ inputStyle: `max-height: ${maxHeight}px;`,
46873
47020
  };
46874
47021
  }
46875
47022
  get containerStyle() {
@@ -49413,10 +49560,6 @@ function useGridDrawing(refName, model, canvasSize) {
49413
49560
  ctx.scale(dpr, dpr);
49414
49561
  for (const layer of OrderedLayers()) {
49415
49562
  model.drawLayer(renderingContext, layer);
49416
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
49417
- // it does not mutate anything. Most importantly it's used
49418
- // during rendering. Invoking a mutator during rendering would
49419
- // trigger another rendering, ultimately resulting in an infinite loop.
49420
49563
  rendererStore.drawLayer(renderingContext, layer);
49421
49564
  }
49422
49565
  }
@@ -50106,7 +50249,7 @@ class Grid extends Component {
50106
50249
  this.cellPopovers = useStore(CellPopoverStore);
50107
50250
  useEffect(() => {
50108
50251
  if (!this.sidePanel.isOpen) {
50109
- this.DOMFocusableElementStore.focusableElement?.focus();
50252
+ this.DOMFocusableElementStore.focus();
50110
50253
  }
50111
50254
  }, () => [this.sidePanel.isOpen]);
50112
50255
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -50314,7 +50457,7 @@ class Grid extends Component {
50314
50457
  focusDefaultElement() {
50315
50458
  if (!this.env.model.getters.getSelectedFigureId() &&
50316
50459
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
50317
- this.DOMFocusableElementStore.focusableElement?.focus();
50460
+ this.DOMFocusableElementStore.focus();
50318
50461
  }
50319
50462
  }
50320
50463
  get gridEl() {
@@ -50684,6 +50827,322 @@ class EditableName extends Component {
50684
50827
  }
50685
50828
  }
50686
50829
 
50830
+ css /* scss */ `
50831
+ .o_pivot_html_renderer {
50832
+ width: 100%;
50833
+ border-collapse: collapse;
50834
+
50835
+ &:hover {
50836
+ cursor: pointer;
50837
+ }
50838
+
50839
+ td,
50840
+ th {
50841
+ border: 1px solid #dee2e6;
50842
+ background-color: #fff;
50843
+ padding: 0.3rem;
50844
+ white-space: nowrap;
50845
+
50846
+ &:hover {
50847
+ filter: brightness(0.9);
50848
+ }
50849
+ }
50850
+
50851
+ td {
50852
+ text-align: right;
50853
+ }
50854
+
50855
+ th {
50856
+ background-color: #f5f5f5;
50857
+ font-weight: bold;
50858
+ color: black;
50859
+ }
50860
+
50861
+ .o_missing_value {
50862
+ color: #46646d;
50863
+ background: #e7f2f6;
50864
+ }
50865
+ }
50866
+ `;
50867
+ class PivotHTMLRenderer extends Component {
50868
+ static template = "o_spreadsheet.PivotHTMLRenderer";
50869
+ static components = { Checkbox };
50870
+ static props = {
50871
+ pivotId: String,
50872
+ onCellClicked: Function,
50873
+ };
50874
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
50875
+ data = {
50876
+ columns: [],
50877
+ rows: [],
50878
+ values: [],
50879
+ };
50880
+ state = useState({
50881
+ showMissingValuesOnly: false,
50882
+ });
50883
+ setup() {
50884
+ const table = this.pivot.getTableStructure();
50885
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
50886
+ this.data = {
50887
+ columns: this._buildColHeaders(formulaId, table),
50888
+ rows: this._buildRowHeaders(formulaId, table),
50889
+ values: this._buildValues(formulaId, table),
50890
+ };
50891
+ }
50892
+ get tracker() {
50893
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
50894
+ }
50895
+ // ---------------------------------------------------------------------
50896
+ // Missing values building
50897
+ // ---------------------------------------------------------------------
50898
+ /**
50899
+ * Retrieve the data to display in the Pivot Table
50900
+ * In the case when showMissingValuesOnly is false, the returned value
50901
+ * is the complete data
50902
+ * In the case when showMissingValuesOnly is true, the returned value is
50903
+ * the data which contains only missing values in the rows and cols. In
50904
+ * the rows, we also return the parent rows of rows which contains missing
50905
+ * values, to give context to the user.
50906
+ *
50907
+ */
50908
+ getTableData() {
50909
+ if (!this.state.showMissingValuesOnly) {
50910
+ return this.data;
50911
+ }
50912
+ const colIndexes = this.getColumnsIndexes();
50913
+ const rowIndexes = this.getRowsIndexes();
50914
+ const columns = this.buildColumnsMissing(colIndexes);
50915
+ const rows = this.buildRowsMissing(rowIndexes);
50916
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
50917
+ return { columns, rows, values };
50918
+ }
50919
+ /**
50920
+ * Retrieve the parents of the given row
50921
+ * ex:
50922
+ * Australia
50923
+ * January
50924
+ * February
50925
+ * The parent of "January" is "Australia"
50926
+ */
50927
+ addRecursiveRow(index) {
50928
+ const rows = this.pivot.getTableStructure().rows;
50929
+ const row = [...rows[index].values];
50930
+ if (row.length <= 1) {
50931
+ return [index];
50932
+ }
50933
+ row.pop();
50934
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
50935
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
50936
+ }
50937
+ /**
50938
+ * Create the columns to be used, based on the indexes of the columns in
50939
+ * which a missing value is present
50940
+ *
50941
+ */
50942
+ buildColumnsMissing(indexes) {
50943
+ // columnsMap explode the columns in an array of array of the same
50944
+ // size with the index of each column, repeated 'span' times.
50945
+ // ex:
50946
+ // | A | B |
50947
+ // | 1 | 2 | 3 |
50948
+ // => [
50949
+ // [0, 0, 1]
50950
+ // [0, 1, 2]
50951
+ // ]
50952
+ const columnsMap = [];
50953
+ for (const column of this.data.columns) {
50954
+ const columnMap = [];
50955
+ for (const index in column) {
50956
+ for (let i = 0; i < column[index].span; i++) {
50957
+ columnMap.push(parseInt(index, 10));
50958
+ }
50959
+ }
50960
+ columnsMap.push(columnMap);
50961
+ }
50962
+ // Remove the columns that are not present in indexes
50963
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
50964
+ if (!indexes.includes(i)) {
50965
+ for (const columnMap of columnsMap) {
50966
+ columnMap.splice(i, 1);
50967
+ }
50968
+ }
50969
+ }
50970
+ // Build the columns
50971
+ const columns = [];
50972
+ for (const mapIndex in columnsMap) {
50973
+ const column = [];
50974
+ let index = undefined;
50975
+ let span = 1;
50976
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
50977
+ if (index !== columnsMap[mapIndex][i]) {
50978
+ if (index !== undefined) {
50979
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
50980
+ }
50981
+ index = columnsMap[mapIndex][i];
50982
+ span = 1;
50983
+ }
50984
+ else {
50985
+ span++;
50986
+ }
50987
+ }
50988
+ if (index !== undefined) {
50989
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
50990
+ }
50991
+ columns.push(column);
50992
+ }
50993
+ return columns;
50994
+ }
50995
+ /**
50996
+ * Create the rows to be used, based on the indexes of the rows in
50997
+ * which a missing value is present.
50998
+ */
50999
+ buildRowsMissing(indexes) {
51000
+ return indexes.map((index) => this.data.rows[index]);
51001
+ }
51002
+ /**
51003
+ * Create the value to be used, based on the indexes of the columns and
51004
+ * rows in which a missing value is present.
51005
+ */
51006
+ buildValuesMissing(colIndexes, rowIndexes) {
51007
+ const values = colIndexes.map(() => []);
51008
+ for (const row of rowIndexes) {
51009
+ for (const col in colIndexes) {
51010
+ values[col].push(this.data.values[colIndexes[col]][row]);
51011
+ }
51012
+ }
51013
+ return values;
51014
+ }
51015
+ getColumnsIndexes() {
51016
+ const indexes = new Set();
51017
+ for (let i = 0; i < this.data.columns.length; i++) {
51018
+ const exploded = [];
51019
+ for (let y = 0; y < this.data.columns[i].length; y++) {
51020
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
51021
+ exploded.push(this.data.columns[i][y]);
51022
+ }
51023
+ }
51024
+ for (let y = 0; y < exploded.length; y++) {
51025
+ if (exploded[y].isMissing) {
51026
+ indexes.add(y);
51027
+ }
51028
+ }
51029
+ }
51030
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
51031
+ const values = this.data.values[i];
51032
+ if (values.find((x) => x.isMissing)) {
51033
+ indexes.add(i);
51034
+ }
51035
+ }
51036
+ return Array.from(indexes).sort((a, b) => a - b);
51037
+ }
51038
+ getRowsIndexes() {
51039
+ const rowIndexes = new Set();
51040
+ for (let i = 0; i < this.data.rows.length; i++) {
51041
+ if (this.data.rows[i].isMissing) {
51042
+ rowIndexes.add(i);
51043
+ }
51044
+ for (const col of this.data.values) {
51045
+ if (col[i].isMissing) {
51046
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
51047
+ }
51048
+ }
51049
+ }
51050
+ return Array.from(rowIndexes).sort((a, b) => a - b);
51051
+ }
51052
+ // ---------------------------------------------------------------------
51053
+ // Data table creation
51054
+ // ---------------------------------------------------------------------
51055
+ _buildColHeaders(id, table) {
51056
+ const headers = [];
51057
+ for (const row of table.columns) {
51058
+ const current = [];
51059
+ for (const cell of row) {
51060
+ const args = [];
51061
+ for (let i = 0; i < cell.fields.length; i++) {
51062
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
51063
+ }
51064
+ const domain = this.pivot.parseArgsToPivotDomain(args);
51065
+ const locale = this.env.model.getters.getLocale();
51066
+ if (domain.at(-1)?.field === "measure") {
51067
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
51068
+ current.push({
51069
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
51070
+ value: formatValue(value, { format, locale }),
51071
+ span: cell.width,
51072
+ isMissing: !this.tracker?.isHeaderPresent(domain),
51073
+ });
51074
+ }
51075
+ else {
51076
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
51077
+ current.push({
51078
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
51079
+ value: formatValue(value, { format, locale }),
51080
+ span: cell.width,
51081
+ isMissing: !this.tracker?.isHeaderPresent(domain),
51082
+ });
51083
+ }
51084
+ }
51085
+ headers.push(current);
51086
+ }
51087
+ const last = headers[headers.length - 1];
51088
+ headers[headers.length - 1] = last.map((cell) => {
51089
+ if (!cell.isMissing) {
51090
+ cell.style = "color: #756f6f;";
51091
+ }
51092
+ return cell;
51093
+ });
51094
+ return headers;
51095
+ }
51096
+ _buildRowHeaders(id, table) {
51097
+ const headers = [];
51098
+ for (const row of table.rows) {
51099
+ const args = [];
51100
+ for (let i = 0; i < row.fields.length; i++) {
51101
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
51102
+ }
51103
+ const domain = this.pivot.parseArgsToPivotDomain(args);
51104
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
51105
+ const locale = this.env.model.getters.getLocale();
51106
+ const cell = {
51107
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
51108
+ value: formatValue(value, { format, locale }),
51109
+ isMissing: !this.tracker?.isHeaderPresent(domain),
51110
+ };
51111
+ if (row.indent > 1) {
51112
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
51113
+ }
51114
+ headers.push(cell);
51115
+ }
51116
+ return headers;
51117
+ }
51118
+ _buildValues(id, table) {
51119
+ const values = [];
51120
+ for (const col of table.columns.at(-1) || []) {
51121
+ const current = [];
51122
+ const measure = toString(col.values[col.values.length - 1]);
51123
+ for (const row of table.rows) {
51124
+ const args = [];
51125
+ for (let i = 0; i < row.fields.length; i++) {
51126
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
51127
+ }
51128
+ for (let i = 0; i < col.fields.length - 1; i++) {
51129
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
51130
+ }
51131
+ const domain = this.pivot.parseArgsToPivotDomain(args);
51132
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
51133
+ const locale = this.env.model.getters.getLocale();
51134
+ current.push({
51135
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
51136
+ value: formatValue(value, { format, locale }),
51137
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
51138
+ });
51139
+ }
51140
+ values.push(current);
51141
+ }
51142
+ return values;
51143
+ }
51144
+ }
51145
+
50687
51146
  /**
50688
51147
  * BasePlugin
50689
51148
  *
@@ -52269,9 +52728,7 @@ class ChartPlugin extends CorePlugin {
52269
52728
  : "Success" /* CommandResult.Success */;
52270
52729
  }
52271
52730
  checkChartExists(cmd) {
52272
- return this.getters.getFigureSheetId(cmd.id)
52273
- ? "Success" /* CommandResult.Success */
52274
- : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
52731
+ return this.isChartDefined(cmd.id) ? "Success" /* CommandResult.Success */ : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
52275
52732
  }
52276
52733
  }
52277
52734
 
@@ -54194,7 +54651,7 @@ class RangeAdapter {
54194
54651
  if (range.sheetId === cmd.sheetId) {
54195
54652
  return { changeType: "CHANGE", range };
54196
54653
  }
54197
- if (cmd.name && range.invalidSheetName === cmd.name) {
54654
+ if (isSheetNameEqual(range.invalidSheetName, cmd.name)) {
54198
54655
  const invalidSheetName = undefined;
54199
54656
  const sheetId = cmd.sheetId;
54200
54657
  const newRange = range.clone({ sheetId, invalidSheetName });
@@ -54532,6 +54989,7 @@ class SheetPlugin extends CorePlugin {
54532
54989
  "getCommandZones",
54533
54990
  "getUnboundedZone",
54534
54991
  "checkElementsIncludeAllNonFrozenHeaders",
54992
+ "getDuplicateSheetName",
54535
54993
  ];
54536
54994
  sheetIdsMapName = {};
54537
54995
  orderedSheetIds = [];
@@ -54556,7 +55014,11 @@ class SheetPlugin extends CorePlugin {
54556
55014
  return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
54557
55015
  }
54558
55016
  case "DUPLICATE_SHEET": {
54559
- return this.sheets[cmd.sheetIdTo] ? "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */ : "Success" /* CommandResult.Success */;
55017
+ if (this.sheets[cmd.sheetIdTo])
55018
+ return "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */;
55019
+ if (this.orderedSheetIds.map(this.getSheetName.bind(this)).includes(cmd.sheetNameTo))
55020
+ return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
55021
+ return "Success" /* CommandResult.Success */;
54560
55022
  }
54561
55023
  case "MOVE_SHEET":
54562
55024
  try {
@@ -54633,7 +55095,7 @@ class SheetPlugin extends CorePlugin {
54633
55095
  this.showSheet(cmd.sheetId);
54634
55096
  break;
54635
55097
  case "DUPLICATE_SHEET":
54636
- this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo);
55098
+ this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo, cmd.sheetNameTo);
54637
55099
  break;
54638
55100
  case "DELETE_SHEET":
54639
55101
  this.deleteSheet(this.sheets[cmd.sheetId]);
@@ -54774,7 +55236,7 @@ class SheetPlugin extends CorePlugin {
54774
55236
  if (name) {
54775
55237
  const unquotedName = getUnquotedSheetName(name);
54776
55238
  for (const key in this.sheetIdsMapName) {
54777
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
55239
+ if (isSheetNameEqual(key, unquotedName)) {
54778
55240
  return this.sheetIdsMapName[key];
54779
55241
  }
54780
55242
  }
@@ -54839,14 +55301,8 @@ class SheetPlugin extends CorePlugin {
54839
55301
  return dimension === "COL" ? this.getNumberCols(sheetId) : this.getNumberRows(sheetId);
54840
55302
  }
54841
55303
  getNextSheetName(baseName = "Sheet") {
54842
- let i = 1;
54843
55304
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
54844
- let name = `${baseName}${i}`;
54845
- while (names.includes(name)) {
54846
- name = `${baseName}${i}`;
54847
- i++;
54848
- }
54849
- return name;
55305
+ return getNextSheetName(names, baseName);
54850
55306
  }
54851
55307
  getSheetSize(sheetId) {
54852
55308
  return {
@@ -55028,7 +55484,7 @@ class SheetPlugin extends CorePlugin {
55028
55484
  }
55029
55485
  const { orderedSheetIds, sheets } = this;
55030
55486
  const name = cmd.name && cmd.name.trim().toLowerCase();
55031
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
55487
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
55032
55488
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
55033
55489
  }
55034
55490
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -55092,9 +55548,8 @@ class SheetPlugin extends CorePlugin {
55092
55548
  showSheet(sheetId) {
55093
55549
  this.history.update("sheets", sheetId, "isVisible", true);
55094
55550
  }
55095
- duplicateSheet(fromId, toId) {
55551
+ duplicateSheet(fromId, toId, toName) {
55096
55552
  const sheet = this.getSheet(fromId);
55097
- const toName = this.getDuplicateSheetName(sheet.name);
55098
55553
  const newSheet = deepCopy(sheet);
55099
55554
  newSheet.id = toId;
55100
55555
  newSheet.name = toName;
@@ -55126,15 +55581,8 @@ class SheetPlugin extends CorePlugin {
55126
55581
  this.history.update("sheetIdsMapName", sheetIdsMapName);
55127
55582
  }
55128
55583
  getDuplicateSheetName(sheetName) {
55129
- let i = 1;
55130
55584
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
55131
- const baseName = _t("Copy of %s", sheetName);
55132
- let name = baseName.toString();
55133
- while (names.includes(name)) {
55134
- name = `${baseName} (${i})`;
55135
- i++;
55136
- }
55137
- return name;
55585
+ return getDuplicateSheetName(sheetName, names);
55138
55586
  }
55139
55587
  deleteSheet(sheet) {
55140
55588
  const name = sheet.name;
@@ -57960,8 +58408,8 @@ class SpreadingRelation {
57960
58408
  const EMPTY_ARRAY = [];
57961
58409
 
57962
58410
  const MAX_ITERATION = 30;
57963
- const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
57964
- const EMPTY_CELL = createEvaluatedCell({ value: null });
58411
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
58412
+ const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
57965
58413
  class Evaluator {
57966
58414
  context;
57967
58415
  getters;
@@ -63923,6 +64371,55 @@ class HistoryPlugin extends UIPlugin {
63923
64371
  }
63924
64372
  }
63925
64373
 
64374
+ class PivotPresenceTracker {
64375
+ trackedValues = new Set();
64376
+ domainToArray(domain) {
64377
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
64378
+ }
64379
+ isValuePresent(measure, domain) {
64380
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
64381
+ return this.trackedValues.has(key);
64382
+ }
64383
+ isHeaderPresent(domain) {
64384
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
64385
+ return this.trackedValues.has(key);
64386
+ }
64387
+ trackValue(measure, domain) {
64388
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
64389
+ this.trackedValues.add(key);
64390
+ }
64391
+ trackHeader(domain) {
64392
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
64393
+ this.trackedValues.add(key);
64394
+ }
64395
+ }
64396
+
64397
+ class PivotPresencePlugin extends UIPlugin {
64398
+ static getters = ["getPivotPresenceTracker"];
64399
+ trackPresencePivotId;
64400
+ tracker;
64401
+ handle(cmd) {
64402
+ switch (cmd.type) {
64403
+ case "PIVOT_START_PRESENCE_TRACKING":
64404
+ this.tracker = new PivotPresenceTracker();
64405
+ this.trackPresencePivotId = cmd.pivotId;
64406
+ break;
64407
+ case "PIVOT_STOP_PRESENCE_TRACKING":
64408
+ this.trackPresencePivotId = undefined;
64409
+ break;
64410
+ }
64411
+ }
64412
+ getPivotPresenceTracker(pivotId) {
64413
+ if (this.trackPresencePivotId !== pivotId) {
64414
+ return undefined;
64415
+ }
64416
+ if (!this.tracker) {
64417
+ throw new Error("Tracker not initialized");
64418
+ }
64419
+ return this.tracker;
64420
+ }
64421
+ }
64422
+
63926
64423
  class SplitToColumnsPlugin extends UIPlugin {
63927
64424
  static getters = ["getAutomaticSeparator"];
63928
64425
  allowDispatch(cmd) {
@@ -66706,6 +67203,7 @@ const featurePluginRegistry = new Registry()
66706
67203
  .add("automatic_sum", AutomaticSumPlugin)
66707
67204
  .add("format", FormatPlugin)
66708
67205
  .add("insert_pivot", InsertPivotPlugin)
67206
+ .add("pivot_presence", PivotPresencePlugin)
66709
67207
  .add("split_to_columns", SplitToColumnsPlugin)
66710
67208
  .add("collaborative", CollaborativePlugin)
66711
67209
  .add("history", HistoryPlugin)
@@ -67085,11 +67583,11 @@ class BottomBarSheet extends Component {
67085
67583
  if (ev.key === "Enter") {
67086
67584
  ev.preventDefault();
67087
67585
  this.stopEdition();
67088
- this.DOMFocusableElementStore.focusableElement?.focus();
67586
+ this.DOMFocusableElementStore.focus();
67089
67587
  }
67090
67588
  if (ev.key === "Escape") {
67091
67589
  this.cancelEdition();
67092
- this.DOMFocusableElementStore.focusableElement?.focus();
67590
+ this.DOMFocusableElementStore.focus();
67093
67591
  }
67094
67592
  }
67095
67593
  onMouseEventSheetName(ev) {
@@ -73699,6 +74197,7 @@ const components = {
73699
74197
  PivotDimensionOrder,
73700
74198
  PivotDimension,
73701
74199
  PivotLayoutConfigurator,
74200
+ PivotHTMLRenderer,
73702
74201
  EditableName,
73703
74202
  PivotDeferUpdate,
73704
74203
  PivotTitleSection,
@@ -73749,6 +74248,6 @@ const constants = {
73749
74248
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
73750
74249
 
73751
74250
 
73752
- __info__.version = "18.0.26";
73753
- __info__.date = "2025-05-02T12:30:31.966Z";
73754
- __info__.hash = "5bdf504";
74251
+ __info__.version = "18.0.28";
74252
+ __info__.date = "2025-05-13T17:53:12.402Z";
74253
+ __info__.hash = "b3088aa";