@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
  'use strict';
@@ -3348,7 +3348,7 @@ function isDateAfter(date, dateAfter) {
3348
3348
  */
3349
3349
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3350
3350
  decimalSeparator = escapeRegExp(decimalSeparator);
3351
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3351
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3352
3352
  });
3353
3353
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3354
3354
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -6273,6 +6273,32 @@ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6273
6273
  })
6274
6274
  .filter(isDefined);
6275
6275
  }
6276
+ function getNextSheetName(existingNames, baseName = "Sheet") {
6277
+ let i = 1;
6278
+ let name = `${baseName}${i}`;
6279
+ while (existingNames.includes(name)) {
6280
+ name = `${baseName}${i}`;
6281
+ i++;
6282
+ }
6283
+ return name;
6284
+ }
6285
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6286
+ let i = 1;
6287
+ const baseName = _t("Copy of %s", nameToDuplicate);
6288
+ let name = baseName.toString();
6289
+ while (existingNames.includes(name)) {
6290
+ name = `${baseName} (${i})`;
6291
+ i++;
6292
+ }
6293
+ return name;
6294
+ }
6295
+ function isSheetNameEqual(name1, name2) {
6296
+ if (name1 === undefined || name2 === undefined) {
6297
+ return false;
6298
+ }
6299
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6300
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6301
+ }
6276
6302
 
6277
6303
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6278
6304
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
@@ -7971,6 +7997,24 @@ const monthNumberAdapter = {
7971
7997
  return `${normalizedValue}`;
7972
7998
  },
7973
7999
  };
8000
+ /**
8001
+ * normalizes month number + year
8002
+ */
8003
+ const monthAdapter = {
8004
+ normalizeFunctionValue(value) {
8005
+ const date = toNumber(value, DEFAULT_LOCALE);
8006
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
8007
+ },
8008
+ toValueAndFormat(normalizedValue) {
8009
+ return {
8010
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
8011
+ format: "mmmm yyyy",
8012
+ };
8013
+ },
8014
+ toFunctionValue(normalizedValue) {
8015
+ return `"${normalizedValue}"`;
8016
+ },
8017
+ };
7974
8018
  /**
7975
8019
  * normalizes quarter number
7976
8020
  */
@@ -8101,6 +8145,7 @@ pivotTimeAdapterRegistry
8101
8145
  .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
8102
8146
  .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
8103
8147
  .add("month_number", nullHandlerDecorator(monthNumberAdapter))
8148
+ .add("month", nullHandlerDecorator(monthAdapter))
8104
8149
  .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
8105
8150
  .add("day_of_week", nullHandlerDecorator(dayOfWeekAdapter))
8106
8151
  .add("hour_number", nullHandlerDecorator(hourNumberAdapter))
@@ -8117,10 +8162,9 @@ const AGGREGATOR_NAMES = {
8117
8162
  avg: _t("Average"),
8118
8163
  sum: _t("Sum"),
8119
8164
  };
8120
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8121
8165
  const AGGREGATORS_BY_FIELD_TYPE = {
8122
- integer: NUMBER_CHAR_AGGREGATORS,
8123
- char: NUMBER_CHAR_AGGREGATORS,
8166
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8167
+ char: ["count_distinct", "count"],
8124
8168
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8125
8169
  };
8126
8170
  const AGGREGATORS = {};
@@ -8280,10 +8324,7 @@ function toNormalizedPivotValue(dimension, groupValue) {
8280
8324
  return normalizer(groupValueString, dimension.granularity);
8281
8325
  }
8282
8326
  function normalizeDateTime(value, granularity) {
8283
- if (!granularity) {
8284
- throw new Error("Missing granularity");
8285
- }
8286
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8327
+ return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
8287
8328
  }
8288
8329
  function toFunctionPivotValue(value, dimension) {
8289
8330
  if (value === null) {
@@ -8295,10 +8336,7 @@ function toFunctionPivotValue(value, dimension) {
8295
8336
  return pivotToFunctionValueRegistry.get(dimension.type)(value, dimension.granularity);
8296
8337
  }
8297
8338
  function toFunctionValueDateTime(value, granularity) {
8298
- if (!granularity) {
8299
- throw new Error("Missing granularity");
8300
- }
8301
- return pivotTimeAdapter(granularity).toFunctionValue(value);
8339
+ return pivotTimeAdapter(granularity ?? "month").toFunctionValue(value);
8302
8340
  }
8303
8341
  const pivotNormalizationValueRegistry = new Registry();
8304
8342
  pivotNormalizationValueRegistry
@@ -9478,7 +9516,10 @@ function proxifyStoreMutation(store, callback) {
9478
9516
  const functionProxy = new Proxy(value, {
9479
9517
  // trap the function call
9480
9518
  apply(target, thisArg, argArray) {
9481
- Reflect.apply(target, thisStore, argArray);
9519
+ const res = Reflect.apply(target, thisStore, argArray);
9520
+ if (res === "noStateChange") {
9521
+ return;
9522
+ }
9482
9523
  callback();
9483
9524
  },
9484
9525
  });
@@ -9500,7 +9541,7 @@ function getDependencyContainer(env) {
9500
9541
  const ModelStore = createAbstractStore("Model");
9501
9542
 
9502
9543
  class RendererStore {
9503
- mutators = ["register", "unRegister"];
9544
+ mutators = ["register", "unRegister", "drawLayer"];
9504
9545
  renderers = {};
9505
9546
  register(renderer) {
9506
9547
  if (!renderer.renderingLayers.length) {
@@ -9520,14 +9561,14 @@ class RendererStore {
9520
9561
  }
9521
9562
  drawLayer(context, layer) {
9522
9563
  const renderers = this.renderers[layer];
9523
- if (!renderers) {
9524
- return;
9525
- }
9526
- for (const renderer of renderers) {
9527
- context.ctx.save();
9528
- renderer.drawLayer(context, layer);
9529
- context.ctx.restore();
9564
+ if (renderers) {
9565
+ for (const renderer of renderers) {
9566
+ context.ctx.save();
9567
+ renderer.drawLayer(context, layer);
9568
+ context.ctx.restore();
9569
+ }
9530
9570
  }
9571
+ return "noStateChange";
9531
9572
  }
9532
9573
  }
9533
9574
 
@@ -9580,16 +9621,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9580
9621
  focusComposer(listener, args) {
9581
9622
  this.activeComposer = listener;
9582
9623
  if (this.getters.isReadonly()) {
9583
- return;
9624
+ return "noStateChange";
9584
9625
  }
9585
9626
  this._focusMode = args.focusMode || "contentFocus";
9586
9627
  if (this._focusMode !== "inactive") {
9587
9628
  this.setComposerContent(args);
9588
9629
  }
9630
+ return;
9589
9631
  }
9590
9632
  focusActiveComposer(args) {
9591
9633
  if (this.getters.isReadonly()) {
9592
- return;
9634
+ return "noStateChange";
9593
9635
  }
9594
9636
  if (!this.activeComposer) {
9595
9637
  throw new Error("No composer is registered");
@@ -9598,6 +9640,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9598
9640
  if (this._focusMode !== "inactive") {
9599
9641
  this.setComposerContent(args);
9600
9642
  }
9643
+ return;
9601
9644
  }
9602
9645
  /**
9603
9646
  * Start the edition or update the content if it's already started.
@@ -9613,12 +9656,24 @@ class ComposerFocusStore extends SpreadsheetStore {
9613
9656
  }
9614
9657
 
9615
9658
  const chartJsExtensionRegistry = new Registry();
9616
- /** Return window.Chart, making sure all our extensions are loaded in ChartJS */
9617
- function getChartJSConstructor() {
9618
- if (window.Chart && !window.Chart?.registry.plugins.get("chartShowValuesPlugin")) {
9619
- window.Chart.register(...chartJsExtensionRegistry.getAll());
9659
+ function areChartJSExtensionsLoaded() {
9660
+ return !!window.Chart.registry.plugins.get("chartShowValuesPlugin");
9661
+ }
9662
+ function registerChartJSExtensions() {
9663
+ if (!window.Chart || areChartJSExtensionsLoaded()) {
9664
+ return;
9665
+ }
9666
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
9667
+ registryItem.register(window.Chart);
9668
+ }
9669
+ }
9670
+ function unregisterChartJsExtensions() {
9671
+ if (!window.Chart) {
9672
+ return;
9673
+ }
9674
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
9675
+ registryItem.unregister(window.Chart);
9620
9676
  }
9621
- return window.Chart;
9622
9677
  }
9623
9678
 
9624
9679
  const TREND_LINE_XAXIS_ID = "x1";
@@ -9935,7 +9990,7 @@ function getDefinedAxis(definition) {
9935
9990
  }
9936
9991
  function formatChartDatasetValue(axisFormats, locale) {
9937
9992
  return (value, axisId) => {
9938
- const format = axisId ? axisFormats?.[axisId] : undefined;
9993
+ const format = axisFormats?.[axisId];
9939
9994
  return formatTickValue({ format, locale })(value);
9940
9995
  };
9941
9996
  }
@@ -10099,7 +10154,7 @@ function drawPieChartValues(chart, options, ctx) {
10099
10154
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10100
10155
  ctx.fillStyle = chartFontColor(options.background);
10101
10156
  ctx.strokeStyle = options.background || "#ffffff";
10102
- const displayValue = options.callback(value);
10157
+ const displayValue = options.callback(value, "y");
10103
10158
  drawTextWithBackground(displayValue, x, y, ctx);
10104
10159
  }
10105
10160
  }
@@ -10159,8 +10214,14 @@ function getNextNonEmptyBar(bars, startIndex) {
10159
10214
  return bars.find((bar, i) => i > startIndex && bar.height !== 0);
10160
10215
  }
10161
10216
 
10162
- chartJsExtensionRegistry.add("chartShowValuesPlugin", chartShowValuesPlugin);
10163
- chartJsExtensionRegistry.add("waterfallLinesPlugin", waterfallLinesPlugin);
10217
+ chartJsExtensionRegistry.add("chartShowValuesPlugin", {
10218
+ register: (Chart) => Chart.register(chartShowValuesPlugin),
10219
+ unregister: (Chart) => Chart.unregister(chartShowValuesPlugin),
10220
+ });
10221
+ chartJsExtensionRegistry.add("waterfallLinesPlugin", {
10222
+ register: (Chart) => Chart.register(waterfallLinesPlugin),
10223
+ unregister: (Chart) => Chart.unregister(waterfallLinesPlugin),
10224
+ });
10164
10225
  class ChartJsComponent extends owl.Component {
10165
10226
  static template = "o-spreadsheet-ChartJsComponent";
10166
10227
  static props = {
@@ -10212,8 +10273,7 @@ class ChartJsComponent extends owl.Component {
10212
10273
  createChart(chartData) {
10213
10274
  const canvas = this.canvas.el;
10214
10275
  const ctx = canvas.getContext("2d");
10215
- const Chart = getChartJSConstructor();
10216
- this.chart = new Chart(ctx, chartData);
10276
+ this.chart = new window.Chart(ctx, chartData);
10217
10277
  }
10218
10278
  updateChartJs(chartData) {
10219
10279
  if (chartData.data && chartData.data.datasets) {
@@ -18345,7 +18405,7 @@ const IF = {
18345
18405
  return { value: "" };
18346
18406
  }
18347
18407
  if (result.value === null) {
18348
- result.value = "";
18408
+ return { ...result, value: "" };
18349
18409
  }
18350
18410
  return result;
18351
18411
  },
@@ -18366,7 +18426,7 @@ const IFERROR = {
18366
18426
  return { value: "" };
18367
18427
  }
18368
18428
  if (result.value === null) {
18369
- result.value = "";
18429
+ return { ...result, value: "" };
18370
18430
  }
18371
18431
  return result;
18372
18432
  },
@@ -18387,7 +18447,7 @@ const IFNA = {
18387
18447
  return { value: "" };
18388
18448
  }
18389
18449
  if (result.value === null) {
18390
- result.value = "";
18450
+ return { ...result, value: "" };
18391
18451
  }
18392
18452
  return result;
18393
18453
  },
@@ -18413,7 +18473,7 @@ const IFS = {
18413
18473
  return { value: "" };
18414
18474
  }
18415
18475
  if (result.value === null) {
18416
- result.value = "";
18476
+ return { ...result, value: "" };
18417
18477
  }
18418
18478
  return result;
18419
18479
  }
@@ -18531,6 +18591,11 @@ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
18531
18591
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
18532
18592
  throw new InvalidReferenceError();
18533
18593
  }
18594
+ if (evalContext.__originCellPosition &&
18595
+ range.sheetId === evalContext.__originSheetId &&
18596
+ isZoneInside(positionToZone(evalContext.__originCellPosition), zone)) {
18597
+ throw new CircularDependencyError();
18598
+ }
18534
18599
  dependencies.push(range);
18535
18600
  }
18536
18601
  for (const measure of forMeasures) {
@@ -18979,6 +19044,9 @@ const PIVOT_VALUE = {
18979
19044
  };
18980
19045
  }
18981
19046
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19047
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19048
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
19049
+ }
18982
19050
  return pivot.getPivotCellValueAndFormat(_measure, domain);
18983
19051
  },
18984
19052
  };
@@ -19010,6 +19078,9 @@ const PIVOT_HEADER = {
19010
19078
  };
19011
19079
  }
19012
19080
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19081
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19082
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
19083
+ }
19013
19084
  const lastNode = domain.at(-1);
19014
19085
  if (lastNode?.field === "measure") {
19015
19086
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -19232,6 +19303,9 @@ function isEmpty(data) {
19232
19303
  return data === undefined || data.value === null;
19233
19304
  }
19234
19305
  const getNeutral = { number: 0, string: "", boolean: false };
19306
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
19307
+ return Math.abs(value1 - value2) < epsilon;
19308
+ }
19235
19309
  const EQ = {
19236
19310
  description: _t("Equal."),
19237
19311
  args: [
@@ -19253,6 +19327,9 @@ const EQ = {
19253
19327
  if (typeof _value2 === "string") {
19254
19328
  _value2 = _value2.toUpperCase();
19255
19329
  }
19330
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
19331
+ return { value: areAlmostEqual(_value1, _value2) };
19332
+ }
19256
19333
  return { value: _value1 === _value2 };
19257
19334
  },
19258
19335
  };
@@ -19292,6 +19369,9 @@ const GT = {
19292
19369
  ],
19293
19370
  compute: function (value1, value2) {
19294
19371
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19372
+ if (typeof v1 === "number" && typeof v2 === "number") {
19373
+ return !areAlmostEqual(v1, v2) && v1 > v2;
19374
+ }
19295
19375
  return v1 > v2;
19296
19376
  });
19297
19377
  },
@@ -19307,6 +19387,9 @@ const GTE = {
19307
19387
  ],
19308
19388
  compute: function (value1, value2) {
19309
19389
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19390
+ if (typeof v1 === "number" && typeof v2 === "number") {
19391
+ return areAlmostEqual(v1, v2) || v1 > v2;
19392
+ }
19310
19393
  return v1 >= v2;
19311
19394
  });
19312
19395
  },
@@ -20913,7 +20996,7 @@ class AbstractComposerStore extends SpreadsheetStore {
20913
20996
  .find((token) => {
20914
20997
  const { xc, sheetName: sheet } = splitReference(token.value);
20915
20998
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
20916
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
20999
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
20917
21000
  return false;
20918
21001
  }
20919
21002
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -22792,6 +22875,7 @@ const CHART_COMMON_OPTIONS = {
22792
22875
  },
22793
22876
  },
22794
22877
  animation: false,
22878
+ events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "mouseup"],
22795
22879
  };
22796
22880
  function truncateLabel(label) {
22797
22881
  if (!label) {
@@ -22817,8 +22901,7 @@ function chartToImage(runtime, figure, type) {
22817
22901
  if ("chartJsConfig" in runtime) {
22818
22902
  const config = deepCopy(runtime.chartJsConfig);
22819
22903
  config.plugins = [backgroundColorChartJSPlugin];
22820
- const Chart = getChartJSConstructor();
22821
- const chart = new Chart(canvas, config);
22904
+ const chart = new window.Chart(canvas, config);
22822
22905
  const imgContent = chart.toBase64Image();
22823
22906
  chart.destroy();
22824
22907
  div.remove();
@@ -24524,7 +24607,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
24524
24607
  ({ xc, sheetName } = splitReference(reference));
24525
24608
  let rangeSheetIndex;
24526
24609
  if (sheetName) {
24527
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
24610
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
24528
24611
  if (index < 0) {
24529
24612
  throw new Error("Unable to find a sheet with the name " + sheetName);
24530
24613
  }
@@ -24865,7 +24948,7 @@ function convertFormula(formula, data) {
24865
24948
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
24866
24949
  externalRefId = Number(externalRefId) - 1;
24867
24950
  cellRef = cellRef.replace(/\$/g, "");
24868
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
24951
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
24869
24952
  if (sheetIndex === -1) {
24870
24953
  return match;
24871
24954
  }
@@ -25516,7 +25599,7 @@ function convertPivotTableConfig(pivotTable) {
25516
25599
  */
25517
25600
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
25518
25601
  for (let tableSheet of convertedSheets) {
25519
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
25602
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
25520
25603
  for (let table of tables) {
25521
25604
  const tabRef = table.name + "[";
25522
25605
  for (let sheet of convertedSheets) {
@@ -27950,6 +28033,7 @@ function repairInitialMessages(data, initialMessages) {
27950
28033
  initialMessages = dropCommands(initialMessages, "SORT_CELLS");
27951
28034
  initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
27952
28035
  initialMessages = fixChartDefinitions(data, initialMessages);
28036
+ initialMessages = fixTranslatedDuplicateSheetName(data, initialMessages);
27953
28037
  return initialMessages;
27954
28038
  }
27955
28039
  /**
@@ -28049,6 +28133,40 @@ function fixChartDefinitions(data, initialMessages) {
28049
28133
  }
28050
28134
  return messages;
28051
28135
  }
28136
+ function fixTranslatedDuplicateSheetName(data, initialMessages) {
28137
+ const sheetNames = {};
28138
+ for (const sheet of data.sheets || []) {
28139
+ sheetNames[sheet.id] = sheet.name;
28140
+ }
28141
+ const messages = [];
28142
+ for (const message of initialMessages) {
28143
+ if (message.type === "REMOTE_REVISION") {
28144
+ const commands = [];
28145
+ for (const cmd of message.commands) {
28146
+ switch (cmd.type) {
28147
+ case "DUPLICATE_SHEET":
28148
+ cmd.sheetNameTo =
28149
+ cmd.sheetNameTo ??
28150
+ getDuplicateSheetName(sheetNames[cmd.sheetId], Object.values(sheetNames));
28151
+ break;
28152
+ case "CREATE_SHEET":
28153
+ case "RENAME_SHEET":
28154
+ sheetNames[cmd.sheetId] = cmd.name || getNextSheetName(Object.values(sheetNames));
28155
+ break;
28156
+ }
28157
+ commands.push(cmd);
28158
+ }
28159
+ messages.push({
28160
+ ...message,
28161
+ commands,
28162
+ });
28163
+ }
28164
+ else {
28165
+ messages.push(message);
28166
+ }
28167
+ }
28168
+ return initialMessages;
28169
+ }
28052
28170
  // -----------------------------------------------------------------------------
28053
28171
  // Helpers
28054
28172
  // -----------------------------------------------------------------------------
@@ -28846,12 +28964,11 @@ function canBeLinearChart(definition, dataSets, labelRange, getters) {
28846
28964
  }
28847
28965
  let missingTimeAdapterAlreadyWarned = false;
28848
28966
  function isLuxonTimeAdapterInstalled() {
28849
- const Chart = getChartJSConstructor();
28850
- if (!Chart) {
28967
+ if (!window.Chart) {
28851
28968
  return false;
28852
28969
  }
28853
28970
  // @ts-ignore
28854
- const adapter = new Chart._adapters._date({});
28971
+ const adapter = new window.Chart._adapters._date({});
28855
28972
  const isInstalled = adapter._id === "luxon";
28856
28973
  if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
28857
28974
  missingTimeAdapterAlreadyWarned = true;
@@ -29489,6 +29606,9 @@ const INTERACTIVE_LEGEND_CONFIG = {
29489
29606
  target.style.cursor = "default";
29490
29607
  },
29491
29608
  onClick: (event, legendItem, legend) => {
29609
+ if (event.type !== "click") {
29610
+ return;
29611
+ }
29492
29612
  const index = legendItem.datasetIndex;
29493
29613
  if (!legend.legendItems || index === undefined) {
29494
29614
  return;
@@ -32314,12 +32434,20 @@ class HoveredCellStore extends SpreadsheetStore {
32314
32434
  }
32315
32435
  }
32316
32436
  hover(position) {
32437
+ if (position.col === this.col && position.row === this.row) {
32438
+ return "noStateChange";
32439
+ }
32317
32440
  this.col = position.col;
32318
32441
  this.row = position.row;
32442
+ return;
32319
32443
  }
32320
32444
  clear() {
32445
+ if (this.col === undefined && this.row === undefined) {
32446
+ return "noStateChange";
32447
+ }
32321
32448
  this.col = undefined;
32322
32449
  this.row = undefined;
32450
+ return;
32323
32451
  }
32324
32452
  }
32325
32453
 
@@ -32341,7 +32469,11 @@ class CellPopoverStore extends SpreadsheetStore {
32341
32469
  this.persistentPopover = { col, row, sheetId, type };
32342
32470
  }
32343
32471
  close() {
32472
+ if (!this.persistentPopover) {
32473
+ return "noStateChange";
32474
+ }
32344
32475
  this.persistentPopover = undefined;
32476
+ return;
32345
32477
  }
32346
32478
  get persistentCellPopover() {
32347
32479
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -33214,10 +33346,13 @@ const duplicateSheet = {
33214
33346
  name: _t("Duplicate"),
33215
33347
  execute: (env) => {
33216
33348
  const sheetIdFrom = env.model.getters.getActiveSheetId();
33349
+ const sheetNameFrom = env.model.getters.getSheetName(sheetIdFrom);
33217
33350
  const sheetIdTo = env.model.uuidGenerator.smallUuid();
33351
+ const sheetNameTo = env.model.getters.getDuplicateSheetName(sheetNameFrom);
33218
33352
  env.model.dispatch("DUPLICATE_SHEET", {
33219
33353
  sheetId: sheetIdFrom,
33220
33354
  sheetIdTo,
33355
+ sheetNameTo,
33221
33356
  });
33222
33357
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
33223
33358
  },
@@ -40170,10 +40305,18 @@ class ChartPanel extends owl.Component {
40170
40305
  }
40171
40306
 
40172
40307
  class DOMFocusableElementStore {
40173
- mutators = ["setFocusableElement"];
40308
+ mutators = ["setFocusableElement", "focus"];
40174
40309
  focusableElement = undefined;
40175
40310
  setFocusableElement(element) {
40176
40311
  this.focusableElement = element;
40312
+ return "noStateChange";
40313
+ }
40314
+ focus() {
40315
+ if (this.focusableElement === document.activeElement) {
40316
+ return "noStateChange";
40317
+ }
40318
+ this.focusableElement?.focus();
40319
+ return;
40177
40320
  }
40178
40321
  }
40179
40322
 
@@ -40763,7 +40906,7 @@ class Composer extends owl.Component {
40763
40906
  if (document.activeElement === this.contentHelper.el &&
40764
40907
  this.props.composerStore.editionMode === "inactive" &&
40765
40908
  !this.props.isDefaultFocus) {
40766
- this.DOMFocusableElementStore.focusableElement?.focus();
40909
+ this.DOMFocusableElementStore.focus();
40767
40910
  }
40768
40911
  });
40769
40912
  owl.useEffect(() => {
@@ -41037,6 +41180,13 @@ class Composer extends owl.Component {
41037
41180
  openAssistant() {
41038
41181
  this.assistant.forcedClosed = false;
41039
41182
  }
41183
+ onWheel(event) {
41184
+ // detect if scrollbar is available
41185
+ if (this.composerRef.el &&
41186
+ this.composerRef.el.scrollHeight > this.composerRef.el.clientHeight) {
41187
+ event.stopPropagation();
41188
+ }
41189
+ }
41040
41190
  // ---------------------------------------------------------------------------
41041
41191
  // Private
41042
41192
  // ---------------------------------------------------------------------------
@@ -45974,8 +46124,8 @@ function compareDimensionValues(dimension, a, b) {
45974
46124
 
45975
46125
  const NULL_SYMBOL = Symbol("NULL");
45976
46126
  function createDate(dimension, value, locale) {
45977
- const granularity = dimension.granularity;
45978
- if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
46127
+ const granularity = dimension.granularity || "month";
46128
+ if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
45979
46129
  throw new Error(`Unknown date granularity: ${granularity}`);
45980
46130
  }
45981
46131
  const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
@@ -45994,6 +46144,9 @@ function createDate(dimension, value, locale) {
45994
46144
  case "month_number":
45995
46145
  number = date.getMonth() + 1;
45996
46146
  break;
46147
+ case "month":
46148
+ number = Math.floor(toNumber(value, locale));
46149
+ break;
45997
46150
  case "iso_week_number":
45998
46151
  number = date.getIsoWeek();
45999
46152
  break;
@@ -46087,6 +46240,10 @@ const MAP_VALUE_DIMENSION_DATE = {
46087
46240
  set: new Set(),
46088
46241
  values: {},
46089
46242
  },
46243
+ month: {
46244
+ set: new Set(),
46245
+ values: {},
46246
+ },
46090
46247
  iso_week_number: {
46091
46248
  set: new Set(),
46092
46249
  values: {},
@@ -46297,7 +46454,7 @@ class SpreadsheetPivot {
46297
46454
  const cells = this.filterDataEntriesFromDomain(this.dataEntries, domain);
46298
46455
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
46299
46456
  if (dimension.type === "datetime") {
46300
- const adapter = pivotTimeAdapter(dimension.granularity);
46457
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
46301
46458
  return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
46302
46459
  }
46303
46460
  if (!finalCell) {
@@ -46415,7 +46572,7 @@ class SpreadsheetPivot {
46415
46572
  if (nonEmptyCells.length === 0) {
46416
46573
  return "integer";
46417
46574
  }
46418
- if (nonEmptyCells.every((cell) => cell.format && isDateTimeFormat(cell.format))) {
46575
+ if (nonEmptyCells.every((cell) => cell.type === CellValueType.number && cell.format && isDateTimeFormat(cell.format))) {
46419
46576
  return "datetime";
46420
46577
  }
46421
46578
  if (nonEmptyCells.every((cell) => cell.type === CellValueType.boolean)) {
@@ -46496,7 +46653,12 @@ class SpreadsheetPivot {
46496
46653
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
46497
46654
  }
46498
46655
  else {
46499
- entry[field.name] = cell;
46656
+ if (field.type === "char") {
46657
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
46658
+ }
46659
+ else {
46660
+ entry[field.name] = cell;
46661
+ }
46500
46662
  }
46501
46663
  }
46502
46664
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -46510,7 +46672,7 @@ class SpreadsheetPivot {
46510
46672
  for (const entry of dataEntries) {
46511
46673
  for (const dimension of dateDimensions) {
46512
46674
  const value = createDate(dimension, entry[dimension.fieldName]?.value || null, this.getters.getLocale());
46513
- const adapter = pivotTimeAdapter(dimension.granularity);
46675
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
46514
46676
  const { format, value: valueToFormat } = adapter.toValueAndFormat(value, locale);
46515
46677
  entry[dimension.nameWithGranularity] = {
46516
46678
  value,
@@ -46530,6 +46692,7 @@ const dateGranularities = [
46530
46692
  "year",
46531
46693
  "quarter_number",
46532
46694
  "month_number",
46695
+ "month",
46533
46696
  "iso_week_number",
46534
46697
  "day_of_month",
46535
46698
  "day",
@@ -46777,7 +46940,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
46777
46940
  : this.datetimeGranularities);
46778
46941
  }
46779
46942
  for (const field of dateFields) {
46780
- granularitiesPerFields[field.fieldName].delete(field.granularity);
46943
+ granularitiesPerFields[field.fieldName].delete(field.granularity || "month");
46781
46944
  }
46782
46945
  return granularitiesPerFields;
46783
46946
  }
@@ -48952,6 +49115,8 @@ class GridComposer extends owl.Component {
48952
49115
  }
48953
49116
  get composerProps() {
48954
49117
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
49118
+ // Remove the wrapper border width
49119
+ const maxHeight = this.props.gridDims.height - this.rect.y - 2 * COMPOSER_BORDER_WIDTH;
48955
49120
  return {
48956
49121
  rect: { ...this.rect },
48957
49122
  delimitation: {
@@ -48969,6 +49134,7 @@ class GridComposer extends owl.Component {
48969
49134
  }),
48970
49135
  onInputContextMenu: this.props.onInputContextMenu,
48971
49136
  composerStore: this.composerStore,
49137
+ inputStyle: `max-height: ${maxHeight}px;`,
48972
49138
  };
48973
49139
  }
48974
49140
  get containerStyle() {
@@ -51521,10 +51687,6 @@ function useGridDrawing(refName, model, canvasSize) {
51521
51687
  ctx.scale(dpr, dpr);
51522
51688
  for (const layer of OrderedLayers()) {
51523
51689
  model.drawLayer(renderingContext, layer);
51524
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
51525
- // it does not mutate anything. Most importantly it's used
51526
- // during rendering. Invoking a mutator during rendering would
51527
- // trigger another rendering, ultimately resulting in an infinite loop.
51528
51690
  rendererStore.drawLayer(renderingContext, layer);
51529
51691
  }
51530
51692
  }
@@ -52214,7 +52376,7 @@ class Grid extends owl.Component {
52214
52376
  this.cellPopovers = useStore(CellPopoverStore);
52215
52377
  owl.useEffect(() => {
52216
52378
  if (!this.sidePanel.isOpen) {
52217
- this.DOMFocusableElementStore.focusableElement?.focus();
52379
+ this.DOMFocusableElementStore.focus();
52218
52380
  }
52219
52381
  }, () => [this.sidePanel.isOpen]);
52220
52382
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -52424,7 +52586,7 @@ class Grid extends owl.Component {
52424
52586
  focusDefaultElement() {
52425
52587
  if (!this.env.model.getters.getSelectedFigureId() &&
52426
52588
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
52427
- this.DOMFocusableElementStore.focusableElement?.focus();
52589
+ this.DOMFocusableElementStore.focus();
52428
52590
  }
52429
52591
  }
52430
52592
  get gridEl() {
@@ -52769,6 +52931,322 @@ class Grid extends owl.Component {
52769
52931
  }
52770
52932
  }
52771
52933
 
52934
+ css /* scss */ `
52935
+ .o_pivot_html_renderer {
52936
+ width: 100%;
52937
+ border-collapse: collapse;
52938
+
52939
+ &:hover {
52940
+ cursor: pointer;
52941
+ }
52942
+
52943
+ td,
52944
+ th {
52945
+ border: 1px solid #dee2e6;
52946
+ background-color: #fff;
52947
+ padding: 0.3rem;
52948
+ white-space: nowrap;
52949
+
52950
+ &:hover {
52951
+ filter: brightness(0.9);
52952
+ }
52953
+ }
52954
+
52955
+ td {
52956
+ text-align: right;
52957
+ }
52958
+
52959
+ th {
52960
+ background-color: #f5f5f5;
52961
+ font-weight: bold;
52962
+ color: black;
52963
+ }
52964
+
52965
+ .o_missing_value {
52966
+ color: #46646d;
52967
+ background: #e7f2f6;
52968
+ }
52969
+ }
52970
+ `;
52971
+ class PivotHTMLRenderer extends owl.Component {
52972
+ static template = "o_spreadsheet.PivotHTMLRenderer";
52973
+ static components = { Checkbox };
52974
+ static props = {
52975
+ pivotId: String,
52976
+ onCellClicked: Function,
52977
+ };
52978
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
52979
+ data = {
52980
+ columns: [],
52981
+ rows: [],
52982
+ values: [],
52983
+ };
52984
+ state = owl.useState({
52985
+ showMissingValuesOnly: false,
52986
+ });
52987
+ setup() {
52988
+ const table = this.pivot.getTableStructure();
52989
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
52990
+ this.data = {
52991
+ columns: this._buildColHeaders(formulaId, table),
52992
+ rows: this._buildRowHeaders(formulaId, table),
52993
+ values: this._buildValues(formulaId, table),
52994
+ };
52995
+ }
52996
+ get tracker() {
52997
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
52998
+ }
52999
+ // ---------------------------------------------------------------------
53000
+ // Missing values building
53001
+ // ---------------------------------------------------------------------
53002
+ /**
53003
+ * Retrieve the data to display in the Pivot Table
53004
+ * In the case when showMissingValuesOnly is false, the returned value
53005
+ * is the complete data
53006
+ * In the case when showMissingValuesOnly is true, the returned value is
53007
+ * the data which contains only missing values in the rows and cols. In
53008
+ * the rows, we also return the parent rows of rows which contains missing
53009
+ * values, to give context to the user.
53010
+ *
53011
+ */
53012
+ getTableData() {
53013
+ if (!this.state.showMissingValuesOnly) {
53014
+ return this.data;
53015
+ }
53016
+ const colIndexes = this.getColumnsIndexes();
53017
+ const rowIndexes = this.getRowsIndexes();
53018
+ const columns = this.buildColumnsMissing(colIndexes);
53019
+ const rows = this.buildRowsMissing(rowIndexes);
53020
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
53021
+ return { columns, rows, values };
53022
+ }
53023
+ /**
53024
+ * Retrieve the parents of the given row
53025
+ * ex:
53026
+ * Australia
53027
+ * January
53028
+ * February
53029
+ * The parent of "January" is "Australia"
53030
+ */
53031
+ addRecursiveRow(index) {
53032
+ const rows = this.pivot.getTableStructure().rows;
53033
+ const row = [...rows[index].values];
53034
+ if (row.length <= 1) {
53035
+ return [index];
53036
+ }
53037
+ row.pop();
53038
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
53039
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
53040
+ }
53041
+ /**
53042
+ * Create the columns to be used, based on the indexes of the columns in
53043
+ * which a missing value is present
53044
+ *
53045
+ */
53046
+ buildColumnsMissing(indexes) {
53047
+ // columnsMap explode the columns in an array of array of the same
53048
+ // size with the index of each column, repeated 'span' times.
53049
+ // ex:
53050
+ // | A | B |
53051
+ // | 1 | 2 | 3 |
53052
+ // => [
53053
+ // [0, 0, 1]
53054
+ // [0, 1, 2]
53055
+ // ]
53056
+ const columnsMap = [];
53057
+ for (const column of this.data.columns) {
53058
+ const columnMap = [];
53059
+ for (const index in column) {
53060
+ for (let i = 0; i < column[index].span; i++) {
53061
+ columnMap.push(parseInt(index, 10));
53062
+ }
53063
+ }
53064
+ columnsMap.push(columnMap);
53065
+ }
53066
+ // Remove the columns that are not present in indexes
53067
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
53068
+ if (!indexes.includes(i)) {
53069
+ for (const columnMap of columnsMap) {
53070
+ columnMap.splice(i, 1);
53071
+ }
53072
+ }
53073
+ }
53074
+ // Build the columns
53075
+ const columns = [];
53076
+ for (const mapIndex in columnsMap) {
53077
+ const column = [];
53078
+ let index = undefined;
53079
+ let span = 1;
53080
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
53081
+ if (index !== columnsMap[mapIndex][i]) {
53082
+ if (index !== undefined) {
53083
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53084
+ }
53085
+ index = columnsMap[mapIndex][i];
53086
+ span = 1;
53087
+ }
53088
+ else {
53089
+ span++;
53090
+ }
53091
+ }
53092
+ if (index !== undefined) {
53093
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53094
+ }
53095
+ columns.push(column);
53096
+ }
53097
+ return columns;
53098
+ }
53099
+ /**
53100
+ * Create the rows to be used, based on the indexes of the rows in
53101
+ * which a missing value is present.
53102
+ */
53103
+ buildRowsMissing(indexes) {
53104
+ return indexes.map((index) => this.data.rows[index]);
53105
+ }
53106
+ /**
53107
+ * Create the value to be used, based on the indexes of the columns and
53108
+ * rows in which a missing value is present.
53109
+ */
53110
+ buildValuesMissing(colIndexes, rowIndexes) {
53111
+ const values = colIndexes.map(() => []);
53112
+ for (const row of rowIndexes) {
53113
+ for (const col in colIndexes) {
53114
+ values[col].push(this.data.values[colIndexes[col]][row]);
53115
+ }
53116
+ }
53117
+ return values;
53118
+ }
53119
+ getColumnsIndexes() {
53120
+ const indexes = new Set();
53121
+ for (let i = 0; i < this.data.columns.length; i++) {
53122
+ const exploded = [];
53123
+ for (let y = 0; y < this.data.columns[i].length; y++) {
53124
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
53125
+ exploded.push(this.data.columns[i][y]);
53126
+ }
53127
+ }
53128
+ for (let y = 0; y < exploded.length; y++) {
53129
+ if (exploded[y].isMissing) {
53130
+ indexes.add(y);
53131
+ }
53132
+ }
53133
+ }
53134
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
53135
+ const values = this.data.values[i];
53136
+ if (values.find((x) => x.isMissing)) {
53137
+ indexes.add(i);
53138
+ }
53139
+ }
53140
+ return Array.from(indexes).sort((a, b) => a - b);
53141
+ }
53142
+ getRowsIndexes() {
53143
+ const rowIndexes = new Set();
53144
+ for (let i = 0; i < this.data.rows.length; i++) {
53145
+ if (this.data.rows[i].isMissing) {
53146
+ rowIndexes.add(i);
53147
+ }
53148
+ for (const col of this.data.values) {
53149
+ if (col[i].isMissing) {
53150
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
53151
+ }
53152
+ }
53153
+ }
53154
+ return Array.from(rowIndexes).sort((a, b) => a - b);
53155
+ }
53156
+ // ---------------------------------------------------------------------
53157
+ // Data table creation
53158
+ // ---------------------------------------------------------------------
53159
+ _buildColHeaders(id, table) {
53160
+ const headers = [];
53161
+ for (const row of table.columns) {
53162
+ const current = [];
53163
+ for (const cell of row) {
53164
+ const args = [];
53165
+ for (let i = 0; i < cell.fields.length; i++) {
53166
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
53167
+ }
53168
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53169
+ const locale = this.env.model.getters.getLocale();
53170
+ if (domain.at(-1)?.field === "measure") {
53171
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
53172
+ current.push({
53173
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53174
+ value: formatValue(value, { format, locale }),
53175
+ span: cell.width,
53176
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53177
+ });
53178
+ }
53179
+ else {
53180
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53181
+ current.push({
53182
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53183
+ value: formatValue(value, { format, locale }),
53184
+ span: cell.width,
53185
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53186
+ });
53187
+ }
53188
+ }
53189
+ headers.push(current);
53190
+ }
53191
+ const last = headers[headers.length - 1];
53192
+ headers[headers.length - 1] = last.map((cell) => {
53193
+ if (!cell.isMissing) {
53194
+ cell.style = "color: #756f6f;";
53195
+ }
53196
+ return cell;
53197
+ });
53198
+ return headers;
53199
+ }
53200
+ _buildRowHeaders(id, table) {
53201
+ const headers = [];
53202
+ for (const row of table.rows) {
53203
+ const args = [];
53204
+ for (let i = 0; i < row.fields.length; i++) {
53205
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53206
+ }
53207
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53208
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53209
+ const locale = this.env.model.getters.getLocale();
53210
+ const cell = {
53211
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53212
+ value: formatValue(value, { format, locale }),
53213
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53214
+ };
53215
+ if (row.indent > 1) {
53216
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
53217
+ }
53218
+ headers.push(cell);
53219
+ }
53220
+ return headers;
53221
+ }
53222
+ _buildValues(id, table) {
53223
+ const values = [];
53224
+ for (const col of table.columns.at(-1) || []) {
53225
+ const current = [];
53226
+ const measure = toString(col.values[col.values.length - 1]);
53227
+ for (const row of table.rows) {
53228
+ const args = [];
53229
+ for (let i = 0; i < row.fields.length; i++) {
53230
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53231
+ }
53232
+ for (let i = 0; i < col.fields.length - 1; i++) {
53233
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
53234
+ }
53235
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53236
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
53237
+ const locale = this.env.model.getters.getLocale();
53238
+ current.push({
53239
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
53240
+ value: formatValue(value, { format, locale }),
53241
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
53242
+ });
53243
+ }
53244
+ values.push(current);
53245
+ }
53246
+ return values;
53247
+ }
53248
+ }
53249
+
52772
53250
  /**
52773
53251
  * BasePlugin
52774
53252
  *
@@ -54259,9 +54737,7 @@ class ChartPlugin extends CorePlugin {
54259
54737
  : "Success" /* CommandResult.Success */;
54260
54738
  }
54261
54739
  checkChartExists(cmd) {
54262
- return this.getters.getFigureSheetId(cmd.id)
54263
- ? "Success" /* CommandResult.Success */
54264
- : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
54740
+ return this.isChartDefined(cmd.id) ? "Success" /* CommandResult.Success */ : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
54265
54741
  }
54266
54742
  }
54267
54743
 
@@ -56208,7 +56684,7 @@ class RangeAdapter {
56208
56684
  if (range.sheetId === cmd.sheetId) {
56209
56685
  return { changeType: "CHANGE", range };
56210
56686
  }
56211
- if (cmd.name && range.invalidSheetName === cmd.name) {
56687
+ if (isSheetNameEqual(range.invalidSheetName, cmd.name)) {
56212
56688
  const invalidSheetName = undefined;
56213
56689
  const sheetId = cmd.sheetId;
56214
56690
  const newRange = range.clone({ sheetId, invalidSheetName });
@@ -56546,6 +57022,7 @@ class SheetPlugin extends CorePlugin {
56546
57022
  "getCommandZones",
56547
57023
  "getUnboundedZone",
56548
57024
  "checkElementsIncludeAllNonFrozenHeaders",
57025
+ "getDuplicateSheetName",
56549
57026
  ];
56550
57027
  sheetIdsMapName = {};
56551
57028
  orderedSheetIds = [];
@@ -56570,7 +57047,11 @@ class SheetPlugin extends CorePlugin {
56570
57047
  return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
56571
57048
  }
56572
57049
  case "DUPLICATE_SHEET": {
56573
- return this.sheets[cmd.sheetIdTo] ? "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */ : "Success" /* CommandResult.Success */;
57050
+ if (this.sheets[cmd.sheetIdTo])
57051
+ return "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */;
57052
+ if (this.orderedSheetIds.map(this.getSheetName.bind(this)).includes(cmd.sheetNameTo))
57053
+ return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
57054
+ return "Success" /* CommandResult.Success */;
56574
57055
  }
56575
57056
  case "MOVE_SHEET":
56576
57057
  try {
@@ -56647,7 +57128,7 @@ class SheetPlugin extends CorePlugin {
56647
57128
  this.showSheet(cmd.sheetId);
56648
57129
  break;
56649
57130
  case "DUPLICATE_SHEET":
56650
- this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo);
57131
+ this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo, cmd.sheetNameTo);
56651
57132
  break;
56652
57133
  case "DELETE_SHEET":
56653
57134
  this.deleteSheet(this.sheets[cmd.sheetId]);
@@ -56788,7 +57269,7 @@ class SheetPlugin extends CorePlugin {
56788
57269
  if (name) {
56789
57270
  const unquotedName = getUnquotedSheetName(name);
56790
57271
  for (const key in this.sheetIdsMapName) {
56791
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
57272
+ if (isSheetNameEqual(key, unquotedName)) {
56792
57273
  return this.sheetIdsMapName[key];
56793
57274
  }
56794
57275
  }
@@ -56853,14 +57334,8 @@ class SheetPlugin extends CorePlugin {
56853
57334
  return dimension === "COL" ? this.getNumberCols(sheetId) : this.getNumberRows(sheetId);
56854
57335
  }
56855
57336
  getNextSheetName(baseName = "Sheet") {
56856
- let i = 1;
56857
57337
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
56858
- let name = `${baseName}${i}`;
56859
- while (names.includes(name)) {
56860
- name = `${baseName}${i}`;
56861
- i++;
56862
- }
56863
- return name;
57338
+ return getNextSheetName(names, baseName);
56864
57339
  }
56865
57340
  getSheetSize(sheetId) {
56866
57341
  return {
@@ -57042,7 +57517,7 @@ class SheetPlugin extends CorePlugin {
57042
57517
  }
57043
57518
  const { orderedSheetIds, sheets } = this;
57044
57519
  const name = cmd.name && cmd.name.trim().toLowerCase();
57045
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
57520
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
57046
57521
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
57047
57522
  }
57048
57523
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -57106,9 +57581,8 @@ class SheetPlugin extends CorePlugin {
57106
57581
  showSheet(sheetId) {
57107
57582
  this.history.update("sheets", sheetId, "isVisible", true);
57108
57583
  }
57109
- duplicateSheet(fromId, toId) {
57584
+ duplicateSheet(fromId, toId, toName) {
57110
57585
  const sheet = this.getSheet(fromId);
57111
- const toName = this.getDuplicateSheetName(sheet.name);
57112
57586
  const newSheet = deepCopy(sheet);
57113
57587
  newSheet.id = toId;
57114
57588
  newSheet.name = toName;
@@ -57140,15 +57614,8 @@ class SheetPlugin extends CorePlugin {
57140
57614
  this.history.update("sheetIdsMapName", sheetIdsMapName);
57141
57615
  }
57142
57616
  getDuplicateSheetName(sheetName) {
57143
- let i = 1;
57144
57617
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
57145
- const baseName = _t("Copy of %s", sheetName);
57146
- let name = baseName.toString();
57147
- while (names.includes(name)) {
57148
- name = `${baseName} (${i})`;
57149
- i++;
57150
- }
57151
- return name;
57618
+ return getDuplicateSheetName(sheetName, names);
57152
57619
  }
57153
57620
  deleteSheet(sheet) {
57154
57621
  const name = sheet.name;
@@ -59975,8 +60442,8 @@ class SpreadingRelation {
59975
60442
  const EMPTY_ARRAY = [];
59976
60443
 
59977
60444
  const MAX_ITERATION = 30;
59978
- const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
59979
- const EMPTY_CELL = createEvaluatedCell({ value: null });
60445
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
60446
+ const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
59980
60447
  class Evaluator {
59981
60448
  context;
59982
60449
  getters;
@@ -65953,6 +66420,55 @@ class HistoryPlugin extends UIPlugin {
65953
66420
  }
65954
66421
  }
65955
66422
 
66423
+ class PivotPresenceTracker {
66424
+ trackedValues = new Set();
66425
+ domainToArray(domain) {
66426
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
66427
+ }
66428
+ isValuePresent(measure, domain) {
66429
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66430
+ return this.trackedValues.has(key);
66431
+ }
66432
+ isHeaderPresent(domain) {
66433
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66434
+ return this.trackedValues.has(key);
66435
+ }
66436
+ trackValue(measure, domain) {
66437
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66438
+ this.trackedValues.add(key);
66439
+ }
66440
+ trackHeader(domain) {
66441
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66442
+ this.trackedValues.add(key);
66443
+ }
66444
+ }
66445
+
66446
+ class PivotPresencePlugin extends UIPlugin {
66447
+ static getters = ["getPivotPresenceTracker"];
66448
+ trackPresencePivotId;
66449
+ tracker;
66450
+ handle(cmd) {
66451
+ switch (cmd.type) {
66452
+ case "PIVOT_START_PRESENCE_TRACKING":
66453
+ this.tracker = new PivotPresenceTracker();
66454
+ this.trackPresencePivotId = cmd.pivotId;
66455
+ break;
66456
+ case "PIVOT_STOP_PRESENCE_TRACKING":
66457
+ this.trackPresencePivotId = undefined;
66458
+ break;
66459
+ }
66460
+ }
66461
+ getPivotPresenceTracker(pivotId) {
66462
+ if (this.trackPresencePivotId !== pivotId) {
66463
+ return undefined;
66464
+ }
66465
+ if (!this.tracker) {
66466
+ throw new Error("Tracker not initialized");
66467
+ }
66468
+ return this.tracker;
66469
+ }
66470
+ }
66471
+
65956
66472
  class SplitToColumnsPlugin extends UIPlugin {
65957
66473
  static getters = ["getAutomaticSeparator"];
65958
66474
  allowDispatch(cmd) {
@@ -68740,6 +69256,7 @@ const featurePluginRegistry = new Registry()
68740
69256
  .add("automatic_sum", AutomaticSumPlugin)
68741
69257
  .add("format", FormatPlugin)
68742
69258
  .add("insert_pivot", InsertPivotPlugin)
69259
+ .add("pivot_presence", PivotPresencePlugin)
68743
69260
  .add("split_to_columns", SplitToColumnsPlugin)
68744
69261
  .add("collaborative", CollaborativePlugin)
68745
69262
  .add("history", HistoryPlugin)
@@ -69120,11 +69637,11 @@ class BottomBarSheet extends owl.Component {
69120
69637
  if (ev.key === "Enter") {
69121
69638
  ev.preventDefault();
69122
69639
  this.stopEdition();
69123
- this.DOMFocusableElementStore.focusableElement?.focus();
69640
+ this.DOMFocusableElementStore.focus();
69124
69641
  }
69125
69642
  if (ev.key === "Escape") {
69126
69643
  this.cancelEdition();
69127
- this.DOMFocusableElementStore.focusableElement?.focus();
69644
+ this.DOMFocusableElementStore.focus();
69128
69645
  }
69129
69646
  }
69130
69647
  onMouseEventSheetName(ev) {
@@ -71308,11 +71825,13 @@ class Spreadsheet extends owl.Component {
71308
71825
  this.checkViewportSize();
71309
71826
  stores.on("store-updated", this, render);
71310
71827
  resizeObserver.observe(this.spreadsheetRef.el);
71828
+ registerChartJSExtensions();
71311
71829
  });
71312
71830
  owl.onWillUnmount(() => {
71313
71831
  this.unbindModelEvents();
71314
71832
  stores.off("store-updated", this);
71315
71833
  resizeObserver.disconnect();
71834
+ unregisterChartJsExtensions();
71316
71835
  });
71317
71836
  owl.onPatched(() => {
71318
71837
  this.checkViewportSize();
@@ -75713,6 +76232,7 @@ const components = {
75713
76232
  PivotDimensionOrder,
75714
76233
  PivotDimension,
75715
76234
  PivotLayoutConfigurator,
76235
+ PivotHTMLRenderer,
75716
76236
  PivotDeferUpdate,
75717
76237
  PivotTitleSection,
75718
76238
  CogWheelMenu,
@@ -75806,6 +76326,6 @@ exports.tokenColors = tokenColors;
75806
76326
  exports.tokenize = tokenize;
75807
76327
 
75808
76328
 
75809
- __info__.version = "18.1.18";
75810
- __info__.date = "2025-05-02T12:30:47.822Z";
75811
- __info__.hash = "29c21c6";
76329
+ __info__.version = "18.1.20";
76330
+ __info__.date = "2025-05-13T17:52:28.174Z";
76331
+ __info__.hash = "3e43a46";