@odoo/o-spreadsheet 18.1.18 → 18.1.20

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.1.18
6
- * @date 2025-05-02T12:30:47.822Z
7
- * @hash 29c21c6
5
+ * @version 18.1.20
6
+ * @date 2025-05-13T17:52:28.174Z
7
+ * @hash 3e43a46
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';
@@ -3346,7 +3346,7 @@ function isDateAfter(date, dateAfter) {
3346
3346
  */
3347
3347
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3348
3348
  decimalSeparator = escapeRegExp(decimalSeparator);
3349
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3349
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3350
3350
  });
3351
3351
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3352
3352
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -6271,6 +6271,32 @@ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6271
6271
  })
6272
6272
  .filter(isDefined);
6273
6273
  }
6274
+ function getNextSheetName(existingNames, baseName = "Sheet") {
6275
+ let i = 1;
6276
+ let name = `${baseName}${i}`;
6277
+ while (existingNames.includes(name)) {
6278
+ name = `${baseName}${i}`;
6279
+ i++;
6280
+ }
6281
+ return name;
6282
+ }
6283
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6284
+ let i = 1;
6285
+ const baseName = _t("Copy of %s", nameToDuplicate);
6286
+ let name = baseName.toString();
6287
+ while (existingNames.includes(name)) {
6288
+ name = `${baseName} (${i})`;
6289
+ i++;
6290
+ }
6291
+ return name;
6292
+ }
6293
+ function isSheetNameEqual(name1, name2) {
6294
+ if (name1 === undefined || name2 === undefined) {
6295
+ return false;
6296
+ }
6297
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6298
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6299
+ }
6274
6300
 
6275
6301
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6276
6302
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
@@ -7969,6 +7995,24 @@ const monthNumberAdapter = {
7969
7995
  return `${normalizedValue}`;
7970
7996
  },
7971
7997
  };
7998
+ /**
7999
+ * normalizes month number + year
8000
+ */
8001
+ const monthAdapter = {
8002
+ normalizeFunctionValue(value) {
8003
+ const date = toNumber(value, DEFAULT_LOCALE);
8004
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
8005
+ },
8006
+ toValueAndFormat(normalizedValue) {
8007
+ return {
8008
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
8009
+ format: "mmmm yyyy",
8010
+ };
8011
+ },
8012
+ toFunctionValue(normalizedValue) {
8013
+ return `"${normalizedValue}"`;
8014
+ },
8015
+ };
7972
8016
  /**
7973
8017
  * normalizes quarter number
7974
8018
  */
@@ -8099,6 +8143,7 @@ pivotTimeAdapterRegistry
8099
8143
  .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
8100
8144
  .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
8101
8145
  .add("month_number", nullHandlerDecorator(monthNumberAdapter))
8146
+ .add("month", nullHandlerDecorator(monthAdapter))
8102
8147
  .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
8103
8148
  .add("day_of_week", nullHandlerDecorator(dayOfWeekAdapter))
8104
8149
  .add("hour_number", nullHandlerDecorator(hourNumberAdapter))
@@ -8115,10 +8160,9 @@ const AGGREGATOR_NAMES = {
8115
8160
  avg: _t("Average"),
8116
8161
  sum: _t("Sum"),
8117
8162
  };
8118
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8119
8163
  const AGGREGATORS_BY_FIELD_TYPE = {
8120
- integer: NUMBER_CHAR_AGGREGATORS,
8121
- char: NUMBER_CHAR_AGGREGATORS,
8164
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8165
+ char: ["count_distinct", "count"],
8122
8166
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8123
8167
  };
8124
8168
  const AGGREGATORS = {};
@@ -8278,10 +8322,7 @@ function toNormalizedPivotValue(dimension, groupValue) {
8278
8322
  return normalizer(groupValueString, dimension.granularity);
8279
8323
  }
8280
8324
  function normalizeDateTime(value, granularity) {
8281
- if (!granularity) {
8282
- throw new Error("Missing granularity");
8283
- }
8284
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8325
+ return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
8285
8326
  }
8286
8327
  function toFunctionPivotValue(value, dimension) {
8287
8328
  if (value === null) {
@@ -8293,10 +8334,7 @@ function toFunctionPivotValue(value, dimension) {
8293
8334
  return pivotToFunctionValueRegistry.get(dimension.type)(value, dimension.granularity);
8294
8335
  }
8295
8336
  function toFunctionValueDateTime(value, granularity) {
8296
- if (!granularity) {
8297
- throw new Error("Missing granularity");
8298
- }
8299
- return pivotTimeAdapter(granularity).toFunctionValue(value);
8337
+ return pivotTimeAdapter(granularity ?? "month").toFunctionValue(value);
8300
8338
  }
8301
8339
  const pivotNormalizationValueRegistry = new Registry();
8302
8340
  pivotNormalizationValueRegistry
@@ -9476,7 +9514,10 @@ function proxifyStoreMutation(store, callback) {
9476
9514
  const functionProxy = new Proxy(value, {
9477
9515
  // trap the function call
9478
9516
  apply(target, thisArg, argArray) {
9479
- Reflect.apply(target, thisStore, argArray);
9517
+ const res = Reflect.apply(target, thisStore, argArray);
9518
+ if (res === "noStateChange") {
9519
+ return;
9520
+ }
9480
9521
  callback();
9481
9522
  },
9482
9523
  });
@@ -9498,7 +9539,7 @@ function getDependencyContainer(env) {
9498
9539
  const ModelStore = createAbstractStore("Model");
9499
9540
 
9500
9541
  class RendererStore {
9501
- mutators = ["register", "unRegister"];
9542
+ mutators = ["register", "unRegister", "drawLayer"];
9502
9543
  renderers = {};
9503
9544
  register(renderer) {
9504
9545
  if (!renderer.renderingLayers.length) {
@@ -9518,14 +9559,14 @@ class RendererStore {
9518
9559
  }
9519
9560
  drawLayer(context, layer) {
9520
9561
  const renderers = this.renderers[layer];
9521
- if (!renderers) {
9522
- return;
9523
- }
9524
- for (const renderer of renderers) {
9525
- context.ctx.save();
9526
- renderer.drawLayer(context, layer);
9527
- context.ctx.restore();
9562
+ if (renderers) {
9563
+ for (const renderer of renderers) {
9564
+ context.ctx.save();
9565
+ renderer.drawLayer(context, layer);
9566
+ context.ctx.restore();
9567
+ }
9528
9568
  }
9569
+ return "noStateChange";
9529
9570
  }
9530
9571
  }
9531
9572
 
@@ -9578,16 +9619,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9578
9619
  focusComposer(listener, args) {
9579
9620
  this.activeComposer = listener;
9580
9621
  if (this.getters.isReadonly()) {
9581
- return;
9622
+ return "noStateChange";
9582
9623
  }
9583
9624
  this._focusMode = args.focusMode || "contentFocus";
9584
9625
  if (this._focusMode !== "inactive") {
9585
9626
  this.setComposerContent(args);
9586
9627
  }
9628
+ return;
9587
9629
  }
9588
9630
  focusActiveComposer(args) {
9589
9631
  if (this.getters.isReadonly()) {
9590
- return;
9632
+ return "noStateChange";
9591
9633
  }
9592
9634
  if (!this.activeComposer) {
9593
9635
  throw new Error("No composer is registered");
@@ -9596,6 +9638,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9596
9638
  if (this._focusMode !== "inactive") {
9597
9639
  this.setComposerContent(args);
9598
9640
  }
9641
+ return;
9599
9642
  }
9600
9643
  /**
9601
9644
  * Start the edition or update the content if it's already started.
@@ -9611,12 +9654,24 @@ class ComposerFocusStore extends SpreadsheetStore {
9611
9654
  }
9612
9655
 
9613
9656
  const chartJsExtensionRegistry = new Registry();
9614
- /** Return window.Chart, making sure all our extensions are loaded in ChartJS */
9615
- function getChartJSConstructor() {
9616
- if (window.Chart && !window.Chart?.registry.plugins.get("chartShowValuesPlugin")) {
9617
- window.Chart.register(...chartJsExtensionRegistry.getAll());
9657
+ function areChartJSExtensionsLoaded() {
9658
+ return !!window.Chart.registry.plugins.get("chartShowValuesPlugin");
9659
+ }
9660
+ function registerChartJSExtensions() {
9661
+ if (!window.Chart || areChartJSExtensionsLoaded()) {
9662
+ return;
9663
+ }
9664
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
9665
+ registryItem.register(window.Chart);
9666
+ }
9667
+ }
9668
+ function unregisterChartJsExtensions() {
9669
+ if (!window.Chart) {
9670
+ return;
9671
+ }
9672
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
9673
+ registryItem.unregister(window.Chart);
9618
9674
  }
9619
- return window.Chart;
9620
9675
  }
9621
9676
 
9622
9677
  const TREND_LINE_XAXIS_ID = "x1";
@@ -9933,7 +9988,7 @@ function getDefinedAxis(definition) {
9933
9988
  }
9934
9989
  function formatChartDatasetValue(axisFormats, locale) {
9935
9990
  return (value, axisId) => {
9936
- const format = axisId ? axisFormats?.[axisId] : undefined;
9991
+ const format = axisFormats?.[axisId];
9937
9992
  return formatTickValue({ format, locale })(value);
9938
9993
  };
9939
9994
  }
@@ -10097,7 +10152,7 @@ function drawPieChartValues(chart, options, ctx) {
10097
10152
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10098
10153
  ctx.fillStyle = chartFontColor(options.background);
10099
10154
  ctx.strokeStyle = options.background || "#ffffff";
10100
- const displayValue = options.callback(value);
10155
+ const displayValue = options.callback(value, "y");
10101
10156
  drawTextWithBackground(displayValue, x, y, ctx);
10102
10157
  }
10103
10158
  }
@@ -10157,8 +10212,14 @@ function getNextNonEmptyBar(bars, startIndex) {
10157
10212
  return bars.find((bar, i) => i > startIndex && bar.height !== 0);
10158
10213
  }
10159
10214
 
10160
- chartJsExtensionRegistry.add("chartShowValuesPlugin", chartShowValuesPlugin);
10161
- chartJsExtensionRegistry.add("waterfallLinesPlugin", waterfallLinesPlugin);
10215
+ chartJsExtensionRegistry.add("chartShowValuesPlugin", {
10216
+ register: (Chart) => Chart.register(chartShowValuesPlugin),
10217
+ unregister: (Chart) => Chart.unregister(chartShowValuesPlugin),
10218
+ });
10219
+ chartJsExtensionRegistry.add("waterfallLinesPlugin", {
10220
+ register: (Chart) => Chart.register(waterfallLinesPlugin),
10221
+ unregister: (Chart) => Chart.unregister(waterfallLinesPlugin),
10222
+ });
10162
10223
  class ChartJsComponent extends Component {
10163
10224
  static template = "o-spreadsheet-ChartJsComponent";
10164
10225
  static props = {
@@ -10210,8 +10271,7 @@ class ChartJsComponent extends Component {
10210
10271
  createChart(chartData) {
10211
10272
  const canvas = this.canvas.el;
10212
10273
  const ctx = canvas.getContext("2d");
10213
- const Chart = getChartJSConstructor();
10214
- this.chart = new Chart(ctx, chartData);
10274
+ this.chart = new window.Chart(ctx, chartData);
10215
10275
  }
10216
10276
  updateChartJs(chartData) {
10217
10277
  if (chartData.data && chartData.data.datasets) {
@@ -18343,7 +18403,7 @@ const IF = {
18343
18403
  return { value: "" };
18344
18404
  }
18345
18405
  if (result.value === null) {
18346
- result.value = "";
18406
+ return { ...result, value: "" };
18347
18407
  }
18348
18408
  return result;
18349
18409
  },
@@ -18364,7 +18424,7 @@ const IFERROR = {
18364
18424
  return { value: "" };
18365
18425
  }
18366
18426
  if (result.value === null) {
18367
- result.value = "";
18427
+ return { ...result, value: "" };
18368
18428
  }
18369
18429
  return result;
18370
18430
  },
@@ -18385,7 +18445,7 @@ const IFNA = {
18385
18445
  return { value: "" };
18386
18446
  }
18387
18447
  if (result.value === null) {
18388
- result.value = "";
18448
+ return { ...result, value: "" };
18389
18449
  }
18390
18450
  return result;
18391
18451
  },
@@ -18411,7 +18471,7 @@ const IFS = {
18411
18471
  return { value: "" };
18412
18472
  }
18413
18473
  if (result.value === null) {
18414
- result.value = "";
18474
+ return { ...result, value: "" };
18415
18475
  }
18416
18476
  return result;
18417
18477
  }
@@ -18529,6 +18589,11 @@ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
18529
18589
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
18530
18590
  throw new InvalidReferenceError();
18531
18591
  }
18592
+ if (evalContext.__originCellPosition &&
18593
+ range.sheetId === evalContext.__originSheetId &&
18594
+ isZoneInside(positionToZone(evalContext.__originCellPosition), zone)) {
18595
+ throw new CircularDependencyError();
18596
+ }
18532
18597
  dependencies.push(range);
18533
18598
  }
18534
18599
  for (const measure of forMeasures) {
@@ -18977,6 +19042,9 @@ const PIVOT_VALUE = {
18977
19042
  };
18978
19043
  }
18979
19044
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19045
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19046
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
19047
+ }
18980
19048
  return pivot.getPivotCellValueAndFormat(_measure, domain);
18981
19049
  },
18982
19050
  };
@@ -19008,6 +19076,9 @@ const PIVOT_HEADER = {
19008
19076
  };
19009
19077
  }
19010
19078
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19079
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19080
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
19081
+ }
19011
19082
  const lastNode = domain.at(-1);
19012
19083
  if (lastNode?.field === "measure") {
19013
19084
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -19230,6 +19301,9 @@ function isEmpty(data) {
19230
19301
  return data === undefined || data.value === null;
19231
19302
  }
19232
19303
  const getNeutral = { number: 0, string: "", boolean: false };
19304
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
19305
+ return Math.abs(value1 - value2) < epsilon;
19306
+ }
19233
19307
  const EQ = {
19234
19308
  description: _t("Equal."),
19235
19309
  args: [
@@ -19251,6 +19325,9 @@ const EQ = {
19251
19325
  if (typeof _value2 === "string") {
19252
19326
  _value2 = _value2.toUpperCase();
19253
19327
  }
19328
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
19329
+ return { value: areAlmostEqual(_value1, _value2) };
19330
+ }
19254
19331
  return { value: _value1 === _value2 };
19255
19332
  },
19256
19333
  };
@@ -19290,6 +19367,9 @@ const GT = {
19290
19367
  ],
19291
19368
  compute: function (value1, value2) {
19292
19369
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19370
+ if (typeof v1 === "number" && typeof v2 === "number") {
19371
+ return !areAlmostEqual(v1, v2) && v1 > v2;
19372
+ }
19293
19373
  return v1 > v2;
19294
19374
  });
19295
19375
  },
@@ -19305,6 +19385,9 @@ const GTE = {
19305
19385
  ],
19306
19386
  compute: function (value1, value2) {
19307
19387
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19388
+ if (typeof v1 === "number" && typeof v2 === "number") {
19389
+ return areAlmostEqual(v1, v2) || v1 > v2;
19390
+ }
19308
19391
  return v1 >= v2;
19309
19392
  });
19310
19393
  },
@@ -20911,7 +20994,7 @@ class AbstractComposerStore extends SpreadsheetStore {
20911
20994
  .find((token) => {
20912
20995
  const { xc, sheetName: sheet } = splitReference(token.value);
20913
20996
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
20914
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
20997
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
20915
20998
  return false;
20916
20999
  }
20917
21000
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -22790,6 +22873,7 @@ const CHART_COMMON_OPTIONS = {
22790
22873
  },
22791
22874
  },
22792
22875
  animation: false,
22876
+ events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "mouseup"],
22793
22877
  };
22794
22878
  function truncateLabel(label) {
22795
22879
  if (!label) {
@@ -22815,8 +22899,7 @@ function chartToImage(runtime, figure, type) {
22815
22899
  if ("chartJsConfig" in runtime) {
22816
22900
  const config = deepCopy(runtime.chartJsConfig);
22817
22901
  config.plugins = [backgroundColorChartJSPlugin];
22818
- const Chart = getChartJSConstructor();
22819
- const chart = new Chart(canvas, config);
22902
+ const chart = new window.Chart(canvas, config);
22820
22903
  const imgContent = chart.toBase64Image();
22821
22904
  chart.destroy();
22822
22905
  div.remove();
@@ -24522,7 +24605,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
24522
24605
  ({ xc, sheetName } = splitReference(reference));
24523
24606
  let rangeSheetIndex;
24524
24607
  if (sheetName) {
24525
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
24608
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
24526
24609
  if (index < 0) {
24527
24610
  throw new Error("Unable to find a sheet with the name " + sheetName);
24528
24611
  }
@@ -24863,7 +24946,7 @@ function convertFormula(formula, data) {
24863
24946
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
24864
24947
  externalRefId = Number(externalRefId) - 1;
24865
24948
  cellRef = cellRef.replace(/\$/g, "");
24866
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
24949
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
24867
24950
  if (sheetIndex === -1) {
24868
24951
  return match;
24869
24952
  }
@@ -25514,7 +25597,7 @@ function convertPivotTableConfig(pivotTable) {
25514
25597
  */
25515
25598
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
25516
25599
  for (let tableSheet of convertedSheets) {
25517
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
25600
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
25518
25601
  for (let table of tables) {
25519
25602
  const tabRef = table.name + "[";
25520
25603
  for (let sheet of convertedSheets) {
@@ -27948,6 +28031,7 @@ function repairInitialMessages(data, initialMessages) {
27948
28031
  initialMessages = dropCommands(initialMessages, "SORT_CELLS");
27949
28032
  initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
27950
28033
  initialMessages = fixChartDefinitions(data, initialMessages);
28034
+ initialMessages = fixTranslatedDuplicateSheetName(data, initialMessages);
27951
28035
  return initialMessages;
27952
28036
  }
27953
28037
  /**
@@ -28047,6 +28131,40 @@ function fixChartDefinitions(data, initialMessages) {
28047
28131
  }
28048
28132
  return messages;
28049
28133
  }
28134
+ function fixTranslatedDuplicateSheetName(data, initialMessages) {
28135
+ const sheetNames = {};
28136
+ for (const sheet of data.sheets || []) {
28137
+ sheetNames[sheet.id] = sheet.name;
28138
+ }
28139
+ const messages = [];
28140
+ for (const message of initialMessages) {
28141
+ if (message.type === "REMOTE_REVISION") {
28142
+ const commands = [];
28143
+ for (const cmd of message.commands) {
28144
+ switch (cmd.type) {
28145
+ case "DUPLICATE_SHEET":
28146
+ cmd.sheetNameTo =
28147
+ cmd.sheetNameTo ??
28148
+ getDuplicateSheetName(sheetNames[cmd.sheetId], Object.values(sheetNames));
28149
+ break;
28150
+ case "CREATE_SHEET":
28151
+ case "RENAME_SHEET":
28152
+ sheetNames[cmd.sheetId] = cmd.name || getNextSheetName(Object.values(sheetNames));
28153
+ break;
28154
+ }
28155
+ commands.push(cmd);
28156
+ }
28157
+ messages.push({
28158
+ ...message,
28159
+ commands,
28160
+ });
28161
+ }
28162
+ else {
28163
+ messages.push(message);
28164
+ }
28165
+ }
28166
+ return initialMessages;
28167
+ }
28050
28168
  // -----------------------------------------------------------------------------
28051
28169
  // Helpers
28052
28170
  // -----------------------------------------------------------------------------
@@ -28844,12 +28962,11 @@ function canBeLinearChart(definition, dataSets, labelRange, getters) {
28844
28962
  }
28845
28963
  let missingTimeAdapterAlreadyWarned = false;
28846
28964
  function isLuxonTimeAdapterInstalled() {
28847
- const Chart = getChartJSConstructor();
28848
- if (!Chart) {
28965
+ if (!window.Chart) {
28849
28966
  return false;
28850
28967
  }
28851
28968
  // @ts-ignore
28852
- const adapter = new Chart._adapters._date({});
28969
+ const adapter = new window.Chart._adapters._date({});
28853
28970
  const isInstalled = adapter._id === "luxon";
28854
28971
  if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
28855
28972
  missingTimeAdapterAlreadyWarned = true;
@@ -29487,6 +29604,9 @@ const INTERACTIVE_LEGEND_CONFIG = {
29487
29604
  target.style.cursor = "default";
29488
29605
  },
29489
29606
  onClick: (event, legendItem, legend) => {
29607
+ if (event.type !== "click") {
29608
+ return;
29609
+ }
29490
29610
  const index = legendItem.datasetIndex;
29491
29611
  if (!legend.legendItems || index === undefined) {
29492
29612
  return;
@@ -32312,12 +32432,20 @@ class HoveredCellStore extends SpreadsheetStore {
32312
32432
  }
32313
32433
  }
32314
32434
  hover(position) {
32435
+ if (position.col === this.col && position.row === this.row) {
32436
+ return "noStateChange";
32437
+ }
32315
32438
  this.col = position.col;
32316
32439
  this.row = position.row;
32440
+ return;
32317
32441
  }
32318
32442
  clear() {
32443
+ if (this.col === undefined && this.row === undefined) {
32444
+ return "noStateChange";
32445
+ }
32319
32446
  this.col = undefined;
32320
32447
  this.row = undefined;
32448
+ return;
32321
32449
  }
32322
32450
  }
32323
32451
 
@@ -32339,7 +32467,11 @@ class CellPopoverStore extends SpreadsheetStore {
32339
32467
  this.persistentPopover = { col, row, sheetId, type };
32340
32468
  }
32341
32469
  close() {
32470
+ if (!this.persistentPopover) {
32471
+ return "noStateChange";
32472
+ }
32342
32473
  this.persistentPopover = undefined;
32474
+ return;
32343
32475
  }
32344
32476
  get persistentCellPopover() {
32345
32477
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -33212,10 +33344,13 @@ const duplicateSheet = {
33212
33344
  name: _t("Duplicate"),
33213
33345
  execute: (env) => {
33214
33346
  const sheetIdFrom = env.model.getters.getActiveSheetId();
33347
+ const sheetNameFrom = env.model.getters.getSheetName(sheetIdFrom);
33215
33348
  const sheetIdTo = env.model.uuidGenerator.smallUuid();
33349
+ const sheetNameTo = env.model.getters.getDuplicateSheetName(sheetNameFrom);
33216
33350
  env.model.dispatch("DUPLICATE_SHEET", {
33217
33351
  sheetId: sheetIdFrom,
33218
33352
  sheetIdTo,
33353
+ sheetNameTo,
33219
33354
  });
33220
33355
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
33221
33356
  },
@@ -40168,10 +40303,18 @@ class ChartPanel extends Component {
40168
40303
  }
40169
40304
 
40170
40305
  class DOMFocusableElementStore {
40171
- mutators = ["setFocusableElement"];
40306
+ mutators = ["setFocusableElement", "focus"];
40172
40307
  focusableElement = undefined;
40173
40308
  setFocusableElement(element) {
40174
40309
  this.focusableElement = element;
40310
+ return "noStateChange";
40311
+ }
40312
+ focus() {
40313
+ if (this.focusableElement === document.activeElement) {
40314
+ return "noStateChange";
40315
+ }
40316
+ this.focusableElement?.focus();
40317
+ return;
40175
40318
  }
40176
40319
  }
40177
40320
 
@@ -40761,7 +40904,7 @@ class Composer extends Component {
40761
40904
  if (document.activeElement === this.contentHelper.el &&
40762
40905
  this.props.composerStore.editionMode === "inactive" &&
40763
40906
  !this.props.isDefaultFocus) {
40764
- this.DOMFocusableElementStore.focusableElement?.focus();
40907
+ this.DOMFocusableElementStore.focus();
40765
40908
  }
40766
40909
  });
40767
40910
  useEffect(() => {
@@ -41035,6 +41178,13 @@ class Composer extends Component {
41035
41178
  openAssistant() {
41036
41179
  this.assistant.forcedClosed = false;
41037
41180
  }
41181
+ onWheel(event) {
41182
+ // detect if scrollbar is available
41183
+ if (this.composerRef.el &&
41184
+ this.composerRef.el.scrollHeight > this.composerRef.el.clientHeight) {
41185
+ event.stopPropagation();
41186
+ }
41187
+ }
41038
41188
  // ---------------------------------------------------------------------------
41039
41189
  // Private
41040
41190
  // ---------------------------------------------------------------------------
@@ -45972,8 +46122,8 @@ function compareDimensionValues(dimension, a, b) {
45972
46122
 
45973
46123
  const NULL_SYMBOL = Symbol("NULL");
45974
46124
  function createDate(dimension, value, locale) {
45975
- const granularity = dimension.granularity;
45976
- if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
46125
+ const granularity = dimension.granularity || "month";
46126
+ if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
45977
46127
  throw new Error(`Unknown date granularity: ${granularity}`);
45978
46128
  }
45979
46129
  const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
@@ -45992,6 +46142,9 @@ function createDate(dimension, value, locale) {
45992
46142
  case "month_number":
45993
46143
  number = date.getMonth() + 1;
45994
46144
  break;
46145
+ case "month":
46146
+ number = Math.floor(toNumber(value, locale));
46147
+ break;
45995
46148
  case "iso_week_number":
45996
46149
  number = date.getIsoWeek();
45997
46150
  break;
@@ -46085,6 +46238,10 @@ const MAP_VALUE_DIMENSION_DATE = {
46085
46238
  set: new Set(),
46086
46239
  values: {},
46087
46240
  },
46241
+ month: {
46242
+ set: new Set(),
46243
+ values: {},
46244
+ },
46088
46245
  iso_week_number: {
46089
46246
  set: new Set(),
46090
46247
  values: {},
@@ -46295,7 +46452,7 @@ class SpreadsheetPivot {
46295
46452
  const cells = this.filterDataEntriesFromDomain(this.dataEntries, domain);
46296
46453
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
46297
46454
  if (dimension.type === "datetime") {
46298
- const adapter = pivotTimeAdapter(dimension.granularity);
46455
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
46299
46456
  return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
46300
46457
  }
46301
46458
  if (!finalCell) {
@@ -46413,7 +46570,7 @@ class SpreadsheetPivot {
46413
46570
  if (nonEmptyCells.length === 0) {
46414
46571
  return "integer";
46415
46572
  }
46416
- if (nonEmptyCells.every((cell) => cell.format && isDateTimeFormat(cell.format))) {
46573
+ if (nonEmptyCells.every((cell) => cell.type === CellValueType.number && cell.format && isDateTimeFormat(cell.format))) {
46417
46574
  return "datetime";
46418
46575
  }
46419
46576
  if (nonEmptyCells.every((cell) => cell.type === CellValueType.boolean)) {
@@ -46494,7 +46651,12 @@ class SpreadsheetPivot {
46494
46651
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
46495
46652
  }
46496
46653
  else {
46497
- entry[field.name] = cell;
46654
+ if (field.type === "char") {
46655
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
46656
+ }
46657
+ else {
46658
+ entry[field.name] = cell;
46659
+ }
46498
46660
  }
46499
46661
  }
46500
46662
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -46508,7 +46670,7 @@ class SpreadsheetPivot {
46508
46670
  for (const entry of dataEntries) {
46509
46671
  for (const dimension of dateDimensions) {
46510
46672
  const value = createDate(dimension, entry[dimension.fieldName]?.value || null, this.getters.getLocale());
46511
- const adapter = pivotTimeAdapter(dimension.granularity);
46673
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
46512
46674
  const { format, value: valueToFormat } = adapter.toValueAndFormat(value, locale);
46513
46675
  entry[dimension.nameWithGranularity] = {
46514
46676
  value,
@@ -46528,6 +46690,7 @@ const dateGranularities = [
46528
46690
  "year",
46529
46691
  "quarter_number",
46530
46692
  "month_number",
46693
+ "month",
46531
46694
  "iso_week_number",
46532
46695
  "day_of_month",
46533
46696
  "day",
@@ -46775,7 +46938,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
46775
46938
  : this.datetimeGranularities);
46776
46939
  }
46777
46940
  for (const field of dateFields) {
46778
- granularitiesPerFields[field.fieldName].delete(field.granularity);
46941
+ granularitiesPerFields[field.fieldName].delete(field.granularity || "month");
46779
46942
  }
46780
46943
  return granularitiesPerFields;
46781
46944
  }
@@ -48950,6 +49113,8 @@ class GridComposer extends Component {
48950
49113
  }
48951
49114
  get composerProps() {
48952
49115
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
49116
+ // Remove the wrapper border width
49117
+ const maxHeight = this.props.gridDims.height - this.rect.y - 2 * COMPOSER_BORDER_WIDTH;
48953
49118
  return {
48954
49119
  rect: { ...this.rect },
48955
49120
  delimitation: {
@@ -48967,6 +49132,7 @@ class GridComposer extends Component {
48967
49132
  }),
48968
49133
  onInputContextMenu: this.props.onInputContextMenu,
48969
49134
  composerStore: this.composerStore,
49135
+ inputStyle: `max-height: ${maxHeight}px;`,
48970
49136
  };
48971
49137
  }
48972
49138
  get containerStyle() {
@@ -51519,10 +51685,6 @@ function useGridDrawing(refName, model, canvasSize) {
51519
51685
  ctx.scale(dpr, dpr);
51520
51686
  for (const layer of OrderedLayers()) {
51521
51687
  model.drawLayer(renderingContext, layer);
51522
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
51523
- // it does not mutate anything. Most importantly it's used
51524
- // during rendering. Invoking a mutator during rendering would
51525
- // trigger another rendering, ultimately resulting in an infinite loop.
51526
51688
  rendererStore.drawLayer(renderingContext, layer);
51527
51689
  }
51528
51690
  }
@@ -52212,7 +52374,7 @@ class Grid extends Component {
52212
52374
  this.cellPopovers = useStore(CellPopoverStore);
52213
52375
  useEffect(() => {
52214
52376
  if (!this.sidePanel.isOpen) {
52215
- this.DOMFocusableElementStore.focusableElement?.focus();
52377
+ this.DOMFocusableElementStore.focus();
52216
52378
  }
52217
52379
  }, () => [this.sidePanel.isOpen]);
52218
52380
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -52422,7 +52584,7 @@ class Grid extends Component {
52422
52584
  focusDefaultElement() {
52423
52585
  if (!this.env.model.getters.getSelectedFigureId() &&
52424
52586
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
52425
- this.DOMFocusableElementStore.focusableElement?.focus();
52587
+ this.DOMFocusableElementStore.focus();
52426
52588
  }
52427
52589
  }
52428
52590
  get gridEl() {
@@ -52767,6 +52929,322 @@ class Grid extends Component {
52767
52929
  }
52768
52930
  }
52769
52931
 
52932
+ css /* scss */ `
52933
+ .o_pivot_html_renderer {
52934
+ width: 100%;
52935
+ border-collapse: collapse;
52936
+
52937
+ &:hover {
52938
+ cursor: pointer;
52939
+ }
52940
+
52941
+ td,
52942
+ th {
52943
+ border: 1px solid #dee2e6;
52944
+ background-color: #fff;
52945
+ padding: 0.3rem;
52946
+ white-space: nowrap;
52947
+
52948
+ &:hover {
52949
+ filter: brightness(0.9);
52950
+ }
52951
+ }
52952
+
52953
+ td {
52954
+ text-align: right;
52955
+ }
52956
+
52957
+ th {
52958
+ background-color: #f5f5f5;
52959
+ font-weight: bold;
52960
+ color: black;
52961
+ }
52962
+
52963
+ .o_missing_value {
52964
+ color: #46646d;
52965
+ background: #e7f2f6;
52966
+ }
52967
+ }
52968
+ `;
52969
+ class PivotHTMLRenderer extends Component {
52970
+ static template = "o_spreadsheet.PivotHTMLRenderer";
52971
+ static components = { Checkbox };
52972
+ static props = {
52973
+ pivotId: String,
52974
+ onCellClicked: Function,
52975
+ };
52976
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
52977
+ data = {
52978
+ columns: [],
52979
+ rows: [],
52980
+ values: [],
52981
+ };
52982
+ state = useState({
52983
+ showMissingValuesOnly: false,
52984
+ });
52985
+ setup() {
52986
+ const table = this.pivot.getTableStructure();
52987
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
52988
+ this.data = {
52989
+ columns: this._buildColHeaders(formulaId, table),
52990
+ rows: this._buildRowHeaders(formulaId, table),
52991
+ values: this._buildValues(formulaId, table),
52992
+ };
52993
+ }
52994
+ get tracker() {
52995
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
52996
+ }
52997
+ // ---------------------------------------------------------------------
52998
+ // Missing values building
52999
+ // ---------------------------------------------------------------------
53000
+ /**
53001
+ * Retrieve the data to display in the Pivot Table
53002
+ * In the case when showMissingValuesOnly is false, the returned value
53003
+ * is the complete data
53004
+ * In the case when showMissingValuesOnly is true, the returned value is
53005
+ * the data which contains only missing values in the rows and cols. In
53006
+ * the rows, we also return the parent rows of rows which contains missing
53007
+ * values, to give context to the user.
53008
+ *
53009
+ */
53010
+ getTableData() {
53011
+ if (!this.state.showMissingValuesOnly) {
53012
+ return this.data;
53013
+ }
53014
+ const colIndexes = this.getColumnsIndexes();
53015
+ const rowIndexes = this.getRowsIndexes();
53016
+ const columns = this.buildColumnsMissing(colIndexes);
53017
+ const rows = this.buildRowsMissing(rowIndexes);
53018
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
53019
+ return { columns, rows, values };
53020
+ }
53021
+ /**
53022
+ * Retrieve the parents of the given row
53023
+ * ex:
53024
+ * Australia
53025
+ * January
53026
+ * February
53027
+ * The parent of "January" is "Australia"
53028
+ */
53029
+ addRecursiveRow(index) {
53030
+ const rows = this.pivot.getTableStructure().rows;
53031
+ const row = [...rows[index].values];
53032
+ if (row.length <= 1) {
53033
+ return [index];
53034
+ }
53035
+ row.pop();
53036
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
53037
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
53038
+ }
53039
+ /**
53040
+ * Create the columns to be used, based on the indexes of the columns in
53041
+ * which a missing value is present
53042
+ *
53043
+ */
53044
+ buildColumnsMissing(indexes) {
53045
+ // columnsMap explode the columns in an array of array of the same
53046
+ // size with the index of each column, repeated 'span' times.
53047
+ // ex:
53048
+ // | A | B |
53049
+ // | 1 | 2 | 3 |
53050
+ // => [
53051
+ // [0, 0, 1]
53052
+ // [0, 1, 2]
53053
+ // ]
53054
+ const columnsMap = [];
53055
+ for (const column of this.data.columns) {
53056
+ const columnMap = [];
53057
+ for (const index in column) {
53058
+ for (let i = 0; i < column[index].span; i++) {
53059
+ columnMap.push(parseInt(index, 10));
53060
+ }
53061
+ }
53062
+ columnsMap.push(columnMap);
53063
+ }
53064
+ // Remove the columns that are not present in indexes
53065
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
53066
+ if (!indexes.includes(i)) {
53067
+ for (const columnMap of columnsMap) {
53068
+ columnMap.splice(i, 1);
53069
+ }
53070
+ }
53071
+ }
53072
+ // Build the columns
53073
+ const columns = [];
53074
+ for (const mapIndex in columnsMap) {
53075
+ const column = [];
53076
+ let index = undefined;
53077
+ let span = 1;
53078
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
53079
+ if (index !== columnsMap[mapIndex][i]) {
53080
+ if (index !== undefined) {
53081
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53082
+ }
53083
+ index = columnsMap[mapIndex][i];
53084
+ span = 1;
53085
+ }
53086
+ else {
53087
+ span++;
53088
+ }
53089
+ }
53090
+ if (index !== undefined) {
53091
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53092
+ }
53093
+ columns.push(column);
53094
+ }
53095
+ return columns;
53096
+ }
53097
+ /**
53098
+ * Create the rows to be used, based on the indexes of the rows in
53099
+ * which a missing value is present.
53100
+ */
53101
+ buildRowsMissing(indexes) {
53102
+ return indexes.map((index) => this.data.rows[index]);
53103
+ }
53104
+ /**
53105
+ * Create the value to be used, based on the indexes of the columns and
53106
+ * rows in which a missing value is present.
53107
+ */
53108
+ buildValuesMissing(colIndexes, rowIndexes) {
53109
+ const values = colIndexes.map(() => []);
53110
+ for (const row of rowIndexes) {
53111
+ for (const col in colIndexes) {
53112
+ values[col].push(this.data.values[colIndexes[col]][row]);
53113
+ }
53114
+ }
53115
+ return values;
53116
+ }
53117
+ getColumnsIndexes() {
53118
+ const indexes = new Set();
53119
+ for (let i = 0; i < this.data.columns.length; i++) {
53120
+ const exploded = [];
53121
+ for (let y = 0; y < this.data.columns[i].length; y++) {
53122
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
53123
+ exploded.push(this.data.columns[i][y]);
53124
+ }
53125
+ }
53126
+ for (let y = 0; y < exploded.length; y++) {
53127
+ if (exploded[y].isMissing) {
53128
+ indexes.add(y);
53129
+ }
53130
+ }
53131
+ }
53132
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
53133
+ const values = this.data.values[i];
53134
+ if (values.find((x) => x.isMissing)) {
53135
+ indexes.add(i);
53136
+ }
53137
+ }
53138
+ return Array.from(indexes).sort((a, b) => a - b);
53139
+ }
53140
+ getRowsIndexes() {
53141
+ const rowIndexes = new Set();
53142
+ for (let i = 0; i < this.data.rows.length; i++) {
53143
+ if (this.data.rows[i].isMissing) {
53144
+ rowIndexes.add(i);
53145
+ }
53146
+ for (const col of this.data.values) {
53147
+ if (col[i].isMissing) {
53148
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
53149
+ }
53150
+ }
53151
+ }
53152
+ return Array.from(rowIndexes).sort((a, b) => a - b);
53153
+ }
53154
+ // ---------------------------------------------------------------------
53155
+ // Data table creation
53156
+ // ---------------------------------------------------------------------
53157
+ _buildColHeaders(id, table) {
53158
+ const headers = [];
53159
+ for (const row of table.columns) {
53160
+ const current = [];
53161
+ for (const cell of row) {
53162
+ const args = [];
53163
+ for (let i = 0; i < cell.fields.length; i++) {
53164
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
53165
+ }
53166
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53167
+ const locale = this.env.model.getters.getLocale();
53168
+ if (domain.at(-1)?.field === "measure") {
53169
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
53170
+ current.push({
53171
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53172
+ value: formatValue(value, { format, locale }),
53173
+ span: cell.width,
53174
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53175
+ });
53176
+ }
53177
+ else {
53178
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53179
+ current.push({
53180
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53181
+ value: formatValue(value, { format, locale }),
53182
+ span: cell.width,
53183
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53184
+ });
53185
+ }
53186
+ }
53187
+ headers.push(current);
53188
+ }
53189
+ const last = headers[headers.length - 1];
53190
+ headers[headers.length - 1] = last.map((cell) => {
53191
+ if (!cell.isMissing) {
53192
+ cell.style = "color: #756f6f;";
53193
+ }
53194
+ return cell;
53195
+ });
53196
+ return headers;
53197
+ }
53198
+ _buildRowHeaders(id, table) {
53199
+ const headers = [];
53200
+ for (const row of table.rows) {
53201
+ const args = [];
53202
+ for (let i = 0; i < row.fields.length; i++) {
53203
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53204
+ }
53205
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53206
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53207
+ const locale = this.env.model.getters.getLocale();
53208
+ const cell = {
53209
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53210
+ value: formatValue(value, { format, locale }),
53211
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53212
+ };
53213
+ if (row.indent > 1) {
53214
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
53215
+ }
53216
+ headers.push(cell);
53217
+ }
53218
+ return headers;
53219
+ }
53220
+ _buildValues(id, table) {
53221
+ const values = [];
53222
+ for (const col of table.columns.at(-1) || []) {
53223
+ const current = [];
53224
+ const measure = toString(col.values[col.values.length - 1]);
53225
+ for (const row of table.rows) {
53226
+ const args = [];
53227
+ for (let i = 0; i < row.fields.length; i++) {
53228
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53229
+ }
53230
+ for (let i = 0; i < col.fields.length - 1; i++) {
53231
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
53232
+ }
53233
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53234
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
53235
+ const locale = this.env.model.getters.getLocale();
53236
+ current.push({
53237
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
53238
+ value: formatValue(value, { format, locale }),
53239
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
53240
+ });
53241
+ }
53242
+ values.push(current);
53243
+ }
53244
+ return values;
53245
+ }
53246
+ }
53247
+
52770
53248
  /**
52771
53249
  * BasePlugin
52772
53250
  *
@@ -54257,9 +54735,7 @@ class ChartPlugin extends CorePlugin {
54257
54735
  : "Success" /* CommandResult.Success */;
54258
54736
  }
54259
54737
  checkChartExists(cmd) {
54260
- return this.getters.getFigureSheetId(cmd.id)
54261
- ? "Success" /* CommandResult.Success */
54262
- : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
54738
+ return this.isChartDefined(cmd.id) ? "Success" /* CommandResult.Success */ : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
54263
54739
  }
54264
54740
  }
54265
54741
 
@@ -56206,7 +56682,7 @@ class RangeAdapter {
56206
56682
  if (range.sheetId === cmd.sheetId) {
56207
56683
  return { changeType: "CHANGE", range };
56208
56684
  }
56209
- if (cmd.name && range.invalidSheetName === cmd.name) {
56685
+ if (isSheetNameEqual(range.invalidSheetName, cmd.name)) {
56210
56686
  const invalidSheetName = undefined;
56211
56687
  const sheetId = cmd.sheetId;
56212
56688
  const newRange = range.clone({ sheetId, invalidSheetName });
@@ -56544,6 +57020,7 @@ class SheetPlugin extends CorePlugin {
56544
57020
  "getCommandZones",
56545
57021
  "getUnboundedZone",
56546
57022
  "checkElementsIncludeAllNonFrozenHeaders",
57023
+ "getDuplicateSheetName",
56547
57024
  ];
56548
57025
  sheetIdsMapName = {};
56549
57026
  orderedSheetIds = [];
@@ -56568,7 +57045,11 @@ class SheetPlugin extends CorePlugin {
56568
57045
  return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
56569
57046
  }
56570
57047
  case "DUPLICATE_SHEET": {
56571
- return this.sheets[cmd.sheetIdTo] ? "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */ : "Success" /* CommandResult.Success */;
57048
+ if (this.sheets[cmd.sheetIdTo])
57049
+ return "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */;
57050
+ if (this.orderedSheetIds.map(this.getSheetName.bind(this)).includes(cmd.sheetNameTo))
57051
+ return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
57052
+ return "Success" /* CommandResult.Success */;
56572
57053
  }
56573
57054
  case "MOVE_SHEET":
56574
57055
  try {
@@ -56645,7 +57126,7 @@ class SheetPlugin extends CorePlugin {
56645
57126
  this.showSheet(cmd.sheetId);
56646
57127
  break;
56647
57128
  case "DUPLICATE_SHEET":
56648
- this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo);
57129
+ this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo, cmd.sheetNameTo);
56649
57130
  break;
56650
57131
  case "DELETE_SHEET":
56651
57132
  this.deleteSheet(this.sheets[cmd.sheetId]);
@@ -56786,7 +57267,7 @@ class SheetPlugin extends CorePlugin {
56786
57267
  if (name) {
56787
57268
  const unquotedName = getUnquotedSheetName(name);
56788
57269
  for (const key in this.sheetIdsMapName) {
56789
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
57270
+ if (isSheetNameEqual(key, unquotedName)) {
56790
57271
  return this.sheetIdsMapName[key];
56791
57272
  }
56792
57273
  }
@@ -56851,14 +57332,8 @@ class SheetPlugin extends CorePlugin {
56851
57332
  return dimension === "COL" ? this.getNumberCols(sheetId) : this.getNumberRows(sheetId);
56852
57333
  }
56853
57334
  getNextSheetName(baseName = "Sheet") {
56854
- let i = 1;
56855
57335
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
56856
- let name = `${baseName}${i}`;
56857
- while (names.includes(name)) {
56858
- name = `${baseName}${i}`;
56859
- i++;
56860
- }
56861
- return name;
57336
+ return getNextSheetName(names, baseName);
56862
57337
  }
56863
57338
  getSheetSize(sheetId) {
56864
57339
  return {
@@ -57040,7 +57515,7 @@ class SheetPlugin extends CorePlugin {
57040
57515
  }
57041
57516
  const { orderedSheetIds, sheets } = this;
57042
57517
  const name = cmd.name && cmd.name.trim().toLowerCase();
57043
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
57518
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
57044
57519
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
57045
57520
  }
57046
57521
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -57104,9 +57579,8 @@ class SheetPlugin extends CorePlugin {
57104
57579
  showSheet(sheetId) {
57105
57580
  this.history.update("sheets", sheetId, "isVisible", true);
57106
57581
  }
57107
- duplicateSheet(fromId, toId) {
57582
+ duplicateSheet(fromId, toId, toName) {
57108
57583
  const sheet = this.getSheet(fromId);
57109
- const toName = this.getDuplicateSheetName(sheet.name);
57110
57584
  const newSheet = deepCopy(sheet);
57111
57585
  newSheet.id = toId;
57112
57586
  newSheet.name = toName;
@@ -57138,15 +57612,8 @@ class SheetPlugin extends CorePlugin {
57138
57612
  this.history.update("sheetIdsMapName", sheetIdsMapName);
57139
57613
  }
57140
57614
  getDuplicateSheetName(sheetName) {
57141
- let i = 1;
57142
57615
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
57143
- const baseName = _t("Copy of %s", sheetName);
57144
- let name = baseName.toString();
57145
- while (names.includes(name)) {
57146
- name = `${baseName} (${i})`;
57147
- i++;
57148
- }
57149
- return name;
57616
+ return getDuplicateSheetName(sheetName, names);
57150
57617
  }
57151
57618
  deleteSheet(sheet) {
57152
57619
  const name = sheet.name;
@@ -59973,8 +60440,8 @@ class SpreadingRelation {
59973
60440
  const EMPTY_ARRAY = [];
59974
60441
 
59975
60442
  const MAX_ITERATION = 30;
59976
- const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
59977
- const EMPTY_CELL = createEvaluatedCell({ value: null });
60443
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
60444
+ const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
59978
60445
  class Evaluator {
59979
60446
  context;
59980
60447
  getters;
@@ -65951,6 +66418,55 @@ class HistoryPlugin extends UIPlugin {
65951
66418
  }
65952
66419
  }
65953
66420
 
66421
+ class PivotPresenceTracker {
66422
+ trackedValues = new Set();
66423
+ domainToArray(domain) {
66424
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
66425
+ }
66426
+ isValuePresent(measure, domain) {
66427
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66428
+ return this.trackedValues.has(key);
66429
+ }
66430
+ isHeaderPresent(domain) {
66431
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66432
+ return this.trackedValues.has(key);
66433
+ }
66434
+ trackValue(measure, domain) {
66435
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66436
+ this.trackedValues.add(key);
66437
+ }
66438
+ trackHeader(domain) {
66439
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66440
+ this.trackedValues.add(key);
66441
+ }
66442
+ }
66443
+
66444
+ class PivotPresencePlugin extends UIPlugin {
66445
+ static getters = ["getPivotPresenceTracker"];
66446
+ trackPresencePivotId;
66447
+ tracker;
66448
+ handle(cmd) {
66449
+ switch (cmd.type) {
66450
+ case "PIVOT_START_PRESENCE_TRACKING":
66451
+ this.tracker = new PivotPresenceTracker();
66452
+ this.trackPresencePivotId = cmd.pivotId;
66453
+ break;
66454
+ case "PIVOT_STOP_PRESENCE_TRACKING":
66455
+ this.trackPresencePivotId = undefined;
66456
+ break;
66457
+ }
66458
+ }
66459
+ getPivotPresenceTracker(pivotId) {
66460
+ if (this.trackPresencePivotId !== pivotId) {
66461
+ return undefined;
66462
+ }
66463
+ if (!this.tracker) {
66464
+ throw new Error("Tracker not initialized");
66465
+ }
66466
+ return this.tracker;
66467
+ }
66468
+ }
66469
+
65954
66470
  class SplitToColumnsPlugin extends UIPlugin {
65955
66471
  static getters = ["getAutomaticSeparator"];
65956
66472
  allowDispatch(cmd) {
@@ -68738,6 +69254,7 @@ const featurePluginRegistry = new Registry()
68738
69254
  .add("automatic_sum", AutomaticSumPlugin)
68739
69255
  .add("format", FormatPlugin)
68740
69256
  .add("insert_pivot", InsertPivotPlugin)
69257
+ .add("pivot_presence", PivotPresencePlugin)
68741
69258
  .add("split_to_columns", SplitToColumnsPlugin)
68742
69259
  .add("collaborative", CollaborativePlugin)
68743
69260
  .add("history", HistoryPlugin)
@@ -69118,11 +69635,11 @@ class BottomBarSheet extends Component {
69118
69635
  if (ev.key === "Enter") {
69119
69636
  ev.preventDefault();
69120
69637
  this.stopEdition();
69121
- this.DOMFocusableElementStore.focusableElement?.focus();
69638
+ this.DOMFocusableElementStore.focus();
69122
69639
  }
69123
69640
  if (ev.key === "Escape") {
69124
69641
  this.cancelEdition();
69125
- this.DOMFocusableElementStore.focusableElement?.focus();
69642
+ this.DOMFocusableElementStore.focus();
69126
69643
  }
69127
69644
  }
69128
69645
  onMouseEventSheetName(ev) {
@@ -71306,11 +71823,13 @@ class Spreadsheet extends Component {
71306
71823
  this.checkViewportSize();
71307
71824
  stores.on("store-updated", this, render);
71308
71825
  resizeObserver.observe(this.spreadsheetRef.el);
71826
+ registerChartJSExtensions();
71309
71827
  });
71310
71828
  onWillUnmount(() => {
71311
71829
  this.unbindModelEvents();
71312
71830
  stores.off("store-updated", this);
71313
71831
  resizeObserver.disconnect();
71832
+ unregisterChartJsExtensions();
71314
71833
  });
71315
71834
  onPatched(() => {
71316
71835
  this.checkViewportSize();
@@ -75711,6 +76230,7 @@ const components = {
75711
76230
  PivotDimensionOrder,
75712
76231
  PivotDimension,
75713
76232
  PivotLayoutConfigurator,
76233
+ PivotHTMLRenderer,
75714
76234
  PivotDeferUpdate,
75715
76235
  PivotTitleSection,
75716
76236
  CogWheelMenu,
@@ -75760,6 +76280,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
75760
76280
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, 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 };
75761
76281
 
75762
76282
 
75763
- __info__.version = "18.1.18";
75764
- __info__.date = "2025-05-02T12:30:47.822Z";
75765
- __info__.hash = "29c21c6";
76283
+ __info__.version = "18.1.20";
76284
+ __info__.date = "2025-05-13T17:52:28.174Z";
76285
+ __info__.hash = "3e43a46";