@odoo/o-spreadsheet 18.2.10 → 18.2.12

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.2.10
6
- * @date 2025-05-02T12:34:39.632Z
7
- * @hash e8ff3fc
5
+ * @version 18.2.12
6
+ * @date 2025-05-13T17:52:23.989Z
7
+ * @hash ba2ba9b
8
8
  */
9
9
 
10
10
  'use strict';
@@ -3359,7 +3359,7 @@ function isDateAfter(date, dateAfter) {
3359
3359
  */
3360
3360
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3361
3361
  decimalSeparator = escapeRegExp(decimalSeparator);
3362
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3362
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3363
3363
  });
3364
3364
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3365
3365
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -6282,6 +6282,32 @@ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6282
6282
  })
6283
6283
  .filter(isDefined);
6284
6284
  }
6285
+ function getNextSheetName(existingNames, baseName = "Sheet") {
6286
+ let i = 1;
6287
+ let name = `${baseName}${i}`;
6288
+ while (existingNames.includes(name)) {
6289
+ name = `${baseName}${i}`;
6290
+ i++;
6291
+ }
6292
+ return name;
6293
+ }
6294
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6295
+ let i = 1;
6296
+ const baseName = _t("Copy of %s", nameToDuplicate);
6297
+ let name = baseName.toString();
6298
+ while (existingNames.includes(name)) {
6299
+ name = `${baseName} (${i})`;
6300
+ i++;
6301
+ }
6302
+ return name;
6303
+ }
6304
+ function isSheetNameEqual(name1, name2) {
6305
+ if (name1 === undefined || name2 === undefined) {
6306
+ return false;
6307
+ }
6308
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6309
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6310
+ }
6285
6311
 
6286
6312
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6287
6313
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
@@ -7980,6 +8006,24 @@ const monthNumberAdapter = {
7980
8006
  return `${normalizedValue}`;
7981
8007
  },
7982
8008
  };
8009
+ /**
8010
+ * normalizes month number + year
8011
+ */
8012
+ const monthAdapter = {
8013
+ normalizeFunctionValue(value) {
8014
+ const date = toNumber(value, DEFAULT_LOCALE);
8015
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
8016
+ },
8017
+ toValueAndFormat(normalizedValue) {
8018
+ return {
8019
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
8020
+ format: "mmmm yyyy",
8021
+ };
8022
+ },
8023
+ toFunctionValue(normalizedValue) {
8024
+ return `"${normalizedValue}"`;
8025
+ },
8026
+ };
7983
8027
  /**
7984
8028
  * normalizes quarter number
7985
8029
  */
@@ -8110,6 +8154,7 @@ pivotTimeAdapterRegistry
8110
8154
  .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
8111
8155
  .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
8112
8156
  .add("month_number", nullHandlerDecorator(monthNumberAdapter))
8157
+ .add("month", nullHandlerDecorator(monthAdapter))
8113
8158
  .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
8114
8159
  .add("day_of_week", nullHandlerDecorator(dayOfWeekAdapter))
8115
8160
  .add("hour_number", nullHandlerDecorator(hourNumberAdapter))
@@ -8126,10 +8171,9 @@ const AGGREGATOR_NAMES = {
8126
8171
  avg: _t("Average"),
8127
8172
  sum: _t("Sum"),
8128
8173
  };
8129
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8130
8174
  const AGGREGATORS_BY_FIELD_TYPE = {
8131
- integer: NUMBER_CHAR_AGGREGATORS,
8132
- char: NUMBER_CHAR_AGGREGATORS,
8175
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8176
+ char: ["count_distinct", "count"],
8133
8177
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8134
8178
  datetime: ["max", "min", "count_distinct", "count"],
8135
8179
  };
@@ -8290,10 +8334,7 @@ function toNormalizedPivotValue(dimension, groupValue) {
8290
8334
  return normalizer(groupValueString, dimension.granularity);
8291
8335
  }
8292
8336
  function normalizeDateTime(value, granularity) {
8293
- if (!granularity) {
8294
- throw new Error("Missing granularity");
8295
- }
8296
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8337
+ return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
8297
8338
  }
8298
8339
  function toFunctionPivotValue(value, dimension) {
8299
8340
  if (value === null) {
@@ -8305,10 +8346,7 @@ function toFunctionPivotValue(value, dimension) {
8305
8346
  return pivotToFunctionValueRegistry.get(dimension.type)(value, dimension.granularity);
8306
8347
  }
8307
8348
  function toFunctionValueDateTime(value, granularity) {
8308
- if (!granularity) {
8309
- throw new Error("Missing granularity");
8310
- }
8311
- return pivotTimeAdapter(granularity).toFunctionValue(value);
8349
+ return pivotTimeAdapter(granularity ?? "month").toFunctionValue(value);
8312
8350
  }
8313
8351
  const pivotNormalizationValueRegistry = new Registry();
8314
8352
  pivotNormalizationValueRegistry
@@ -9488,7 +9526,10 @@ function proxifyStoreMutation(store, callback) {
9488
9526
  const functionProxy = new Proxy(value, {
9489
9527
  // trap the function call
9490
9528
  apply(target, thisArg, argArray) {
9491
- Reflect.apply(target, thisStore, argArray);
9529
+ const res = Reflect.apply(target, thisStore, argArray);
9530
+ if (res === "noStateChange") {
9531
+ return;
9532
+ }
9492
9533
  callback();
9493
9534
  },
9494
9535
  });
@@ -9510,7 +9551,7 @@ function getDependencyContainer(env) {
9510
9551
  const ModelStore = createAbstractStore("Model");
9511
9552
 
9512
9553
  class RendererStore {
9513
- mutators = ["register", "unRegister"];
9554
+ mutators = ["register", "unRegister", "drawLayer"];
9514
9555
  renderers = {};
9515
9556
  register(renderer) {
9516
9557
  if (!renderer.renderingLayers.length) {
@@ -9530,14 +9571,14 @@ class RendererStore {
9530
9571
  }
9531
9572
  drawLayer(context, layer) {
9532
9573
  const renderers = this.renderers[layer];
9533
- if (!renderers) {
9534
- return;
9535
- }
9536
- for (const renderer of renderers) {
9537
- context.ctx.save();
9538
- renderer.drawLayer(context, layer);
9539
- context.ctx.restore();
9574
+ if (renderers) {
9575
+ for (const renderer of renderers) {
9576
+ context.ctx.save();
9577
+ renderer.drawLayer(context, layer);
9578
+ context.ctx.restore();
9579
+ }
9540
9580
  }
9581
+ return "noStateChange";
9541
9582
  }
9542
9583
  }
9543
9584
 
@@ -9590,16 +9631,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9590
9631
  focusComposer(listener, args) {
9591
9632
  this.activeComposer = listener;
9592
9633
  if (this.getters.isReadonly()) {
9593
- return;
9634
+ return "noStateChange";
9594
9635
  }
9595
9636
  this._focusMode = args.focusMode || "contentFocus";
9596
9637
  if (this._focusMode !== "inactive") {
9597
9638
  this.setComposerContent(args);
9598
9639
  }
9640
+ return;
9599
9641
  }
9600
9642
  focusActiveComposer(args) {
9601
9643
  if (this.getters.isReadonly()) {
9602
- return;
9644
+ return "noStateChange";
9603
9645
  }
9604
9646
  if (!this.activeComposer) {
9605
9647
  throw new Error("No composer is registered");
@@ -9608,6 +9650,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9608
9650
  if (this._focusMode !== "inactive") {
9609
9651
  this.setComposerContent(args);
9610
9652
  }
9653
+ return;
9611
9654
  }
9612
9655
  /**
9613
9656
  * Start the edition or update the content if it's already started.
@@ -9767,12 +9810,24 @@ function getElementMargins(el) {
9767
9810
  }
9768
9811
 
9769
9812
  const chartJsExtensionRegistry = new Registry();
9770
- /** Return window.Chart, making sure all our extensions are loaded in ChartJS */
9771
- function getChartJSConstructor() {
9772
- if (window.Chart && !window.Chart?.registry.plugins.get("chartShowValuesPlugin")) {
9773
- window.Chart.register(...chartJsExtensionRegistry.getAll());
9813
+ function areChartJSExtensionsLoaded() {
9814
+ return !!window.Chart.registry.plugins.get("chartShowValuesPlugin");
9815
+ }
9816
+ function registerChartJSExtensions() {
9817
+ if (!window.Chart || areChartJSExtensionsLoaded()) {
9818
+ return;
9819
+ }
9820
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
9821
+ registryItem.register(window.Chart);
9822
+ }
9823
+ }
9824
+ function unregisterChartJsExtensions() {
9825
+ if (!window.Chart) {
9826
+ return;
9827
+ }
9828
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
9829
+ registryItem.unregister(window.Chart);
9774
9830
  }
9775
- return window.Chart;
9776
9831
  }
9777
9832
 
9778
9833
  const TREND_LINE_XAXIS_ID = "x1";
@@ -10089,7 +10144,7 @@ function getDefinedAxis(definition) {
10089
10144
  }
10090
10145
  function formatChartDatasetValue(axisFormats, locale) {
10091
10146
  return (value, axisId) => {
10092
- const format = axisId ? axisFormats?.[axisId] : undefined;
10147
+ const format = axisFormats?.[axisId];
10093
10148
  return formatTickValue({ format, locale })(value);
10094
10149
  };
10095
10150
  }
@@ -10263,7 +10318,7 @@ function drawPieChartValues(chart, options, ctx) {
10263
10318
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10264
10319
  ctx.fillStyle = chartFontColor(options.background);
10265
10320
  ctx.strokeStyle = options.background || "#ffffff";
10266
- const displayValue = options.callback(value);
10321
+ const displayValue = options.callback(value, "y");
10267
10322
  drawTextWithBackground(displayValue, x, y, ctx);
10268
10323
  }
10269
10324
  }
@@ -10332,8 +10387,14 @@ css /* scss */ `
10332
10387
  }
10333
10388
  }
10334
10389
  `;
10335
- chartJsExtensionRegistry.add("chartShowValuesPlugin", chartShowValuesPlugin);
10336
- chartJsExtensionRegistry.add("waterfallLinesPlugin", waterfallLinesPlugin);
10390
+ chartJsExtensionRegistry.add("chartShowValuesPlugin", {
10391
+ register: (Chart) => Chart.register(chartShowValuesPlugin),
10392
+ unregister: (Chart) => Chart.unregister(chartShowValuesPlugin),
10393
+ });
10394
+ chartJsExtensionRegistry.add("waterfallLinesPlugin", {
10395
+ register: (Chart) => Chart.register(waterfallLinesPlugin),
10396
+ unregister: (Chart) => Chart.unregister(waterfallLinesPlugin),
10397
+ });
10337
10398
  class ChartJsComponent extends owl.Component {
10338
10399
  static template = "o-spreadsheet-ChartJsComponent";
10339
10400
  static props = {
@@ -10385,8 +10446,7 @@ class ChartJsComponent extends owl.Component {
10385
10446
  createChart(chartData) {
10386
10447
  const canvas = this.canvas.el;
10387
10448
  const ctx = canvas.getContext("2d");
10388
- const Chart = getChartJSConstructor();
10389
- this.chart = new Chart(ctx, chartData);
10449
+ this.chart = new window.Chart(ctx, chartData);
10390
10450
  }
10391
10451
  updateChartJs(chartData) {
10392
10452
  if (chartData.data && chartData.data.datasets) {
@@ -18518,7 +18578,7 @@ const IF = {
18518
18578
  return { value: "" };
18519
18579
  }
18520
18580
  if (result.value === null) {
18521
- result.value = "";
18581
+ return { ...result, value: "" };
18522
18582
  }
18523
18583
  return result;
18524
18584
  },
@@ -18539,7 +18599,7 @@ const IFERROR = {
18539
18599
  return { value: "" };
18540
18600
  }
18541
18601
  if (result.value === null) {
18542
- result.value = "";
18602
+ return { ...result, value: "" };
18543
18603
  }
18544
18604
  return result;
18545
18605
  },
@@ -18560,7 +18620,7 @@ const IFNA = {
18560
18620
  return { value: "" };
18561
18621
  }
18562
18622
  if (result.value === null) {
18563
- result.value = "";
18623
+ return { ...result, value: "" };
18564
18624
  }
18565
18625
  return result;
18566
18626
  },
@@ -18586,7 +18646,7 @@ const IFS = {
18586
18646
  return { value: "" };
18587
18647
  }
18588
18648
  if (result.value === null) {
18589
- result.value = "";
18649
+ return { ...result, value: "" };
18590
18650
  }
18591
18651
  return result;
18592
18652
  }
@@ -18704,6 +18764,11 @@ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
18704
18764
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
18705
18765
  throw new InvalidReferenceError();
18706
18766
  }
18767
+ if (evalContext.__originCellPosition &&
18768
+ range.sheetId === evalContext.__originSheetId &&
18769
+ isZoneInside(positionToZone(evalContext.__originCellPosition), zone)) {
18770
+ throw new CircularDependencyError();
18771
+ }
18707
18772
  dependencies.push(range);
18708
18773
  }
18709
18774
  for (const measure of forMeasures) {
@@ -19152,6 +19217,9 @@ const PIVOT_VALUE = {
19152
19217
  };
19153
19218
  }
19154
19219
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19220
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19221
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
19222
+ }
19155
19223
  return pivot.getPivotCellValueAndFormat(_measure, domain);
19156
19224
  },
19157
19225
  };
@@ -19183,6 +19251,9 @@ const PIVOT_HEADER = {
19183
19251
  };
19184
19252
  }
19185
19253
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19254
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19255
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
19256
+ }
19186
19257
  const lastNode = domain.at(-1);
19187
19258
  if (lastNode?.field === "measure") {
19188
19259
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -19405,6 +19476,9 @@ function isEmpty(data) {
19405
19476
  return data === undefined || data.value === null;
19406
19477
  }
19407
19478
  const getNeutral = { number: 0, string: "", boolean: false };
19479
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
19480
+ return Math.abs(value1 - value2) < epsilon;
19481
+ }
19408
19482
  const EQ = {
19409
19483
  description: _t("Equal."),
19410
19484
  args: [
@@ -19426,6 +19500,9 @@ const EQ = {
19426
19500
  if (typeof _value2 === "string") {
19427
19501
  _value2 = _value2.toUpperCase();
19428
19502
  }
19503
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
19504
+ return { value: areAlmostEqual(_value1, _value2) };
19505
+ }
19429
19506
  return { value: _value1 === _value2 };
19430
19507
  },
19431
19508
  };
@@ -19465,6 +19542,9 @@ const GT = {
19465
19542
  ],
19466
19543
  compute: function (value1, value2) {
19467
19544
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19545
+ if (typeof v1 === "number" && typeof v2 === "number") {
19546
+ return !areAlmostEqual(v1, v2) && v1 > v2;
19547
+ }
19468
19548
  return v1 > v2;
19469
19549
  });
19470
19550
  },
@@ -19480,6 +19560,9 @@ const GTE = {
19480
19560
  ],
19481
19561
  compute: function (value1, value2) {
19482
19562
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19563
+ if (typeof v1 === "number" && typeof v2 === "number") {
19564
+ return areAlmostEqual(v1, v2) || v1 > v2;
19565
+ }
19483
19566
  return v1 >= v2;
19484
19567
  });
19485
19568
  },
@@ -21086,7 +21169,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21086
21169
  .find((token) => {
21087
21170
  const { xc, sheetName: sheet } = splitReference(token.value);
21088
21171
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
21089
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
21172
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
21090
21173
  return false;
21091
21174
  }
21092
21175
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -22965,6 +23048,7 @@ const CHART_COMMON_OPTIONS = {
22965
23048
  },
22966
23049
  },
22967
23050
  animation: false,
23051
+ events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "mouseup"],
22968
23052
  };
22969
23053
  function chartToImage(runtime, figure, type) {
22970
23054
  // wrap the canvas in a div with a fixed size because chart.js would
@@ -22981,8 +23065,7 @@ function chartToImage(runtime, figure, type) {
22981
23065
  if ("chartJsConfig" in runtime) {
22982
23066
  const config = deepCopy(runtime.chartJsConfig);
22983
23067
  config.plugins = [backgroundColorChartJSPlugin];
22984
- const Chart = getChartJSConstructor();
22985
- const chart = new Chart(canvas, config);
23068
+ const chart = new window.Chart(canvas, config);
22986
23069
  const imgContent = chart.toBase64Image();
22987
23070
  chart.destroy();
22988
23071
  div.remove();
@@ -24548,7 +24631,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
24548
24631
  ({ xc, sheetName } = splitReference(reference));
24549
24632
  let rangeSheetIndex;
24550
24633
  if (sheetName) {
24551
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
24634
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
24552
24635
  if (index < 0) {
24553
24636
  throw new Error("Unable to find a sheet with the name " + sheetName);
24554
24637
  }
@@ -24889,7 +24972,7 @@ function convertFormula(formula, data) {
24889
24972
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
24890
24973
  externalRefId = Number(externalRefId) - 1;
24891
24974
  cellRef = cellRef.replace(/\$/g, "");
24892
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
24975
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
24893
24976
  if (sheetIndex === -1) {
24894
24977
  return match;
24895
24978
  }
@@ -25540,7 +25623,7 @@ function convertPivotTableConfig(pivotTable) {
25540
25623
  */
25541
25624
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
25542
25625
  for (let tableSheet of convertedSheets) {
25543
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
25626
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
25544
25627
  for (let table of tables) {
25545
25628
  const tabRef = table.name + "[";
25546
25629
  for (let sheet of convertedSheets) {
@@ -27975,6 +28058,7 @@ function repairInitialMessages(data, initialMessages) {
27975
28058
  initialMessages = dropCommands(initialMessages, "SORT_CELLS");
27976
28059
  initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
27977
28060
  initialMessages = fixChartDefinitions(data, initialMessages);
28061
+ initialMessages = fixTranslatedDuplicateSheetName(data, initialMessages);
27978
28062
  return initialMessages;
27979
28063
  }
27980
28064
  /**
@@ -28074,6 +28158,40 @@ function fixChartDefinitions(data, initialMessages) {
28074
28158
  }
28075
28159
  return messages;
28076
28160
  }
28161
+ function fixTranslatedDuplicateSheetName(data, initialMessages) {
28162
+ const sheetNames = {};
28163
+ for (const sheet of data.sheets || []) {
28164
+ sheetNames[sheet.id] = sheet.name;
28165
+ }
28166
+ const messages = [];
28167
+ for (const message of initialMessages) {
28168
+ if (message.type === "REMOTE_REVISION") {
28169
+ const commands = [];
28170
+ for (const cmd of message.commands) {
28171
+ switch (cmd.type) {
28172
+ case "DUPLICATE_SHEET":
28173
+ cmd.sheetNameTo =
28174
+ cmd.sheetNameTo ??
28175
+ getDuplicateSheetName(sheetNames[cmd.sheetId], Object.values(sheetNames));
28176
+ break;
28177
+ case "CREATE_SHEET":
28178
+ case "RENAME_SHEET":
28179
+ sheetNames[cmd.sheetId] = cmd.name || getNextSheetName(Object.values(sheetNames));
28180
+ break;
28181
+ }
28182
+ commands.push(cmd);
28183
+ }
28184
+ messages.push({
28185
+ ...message,
28186
+ commands,
28187
+ });
28188
+ }
28189
+ else {
28190
+ messages.push(message);
28191
+ }
28192
+ }
28193
+ return initialMessages;
28194
+ }
28077
28195
  // -----------------------------------------------------------------------------
28078
28196
  // Helpers
28079
28197
  // -----------------------------------------------------------------------------
@@ -28870,12 +28988,11 @@ function canBeLinearChart(definition, dataSets, labelRange, getters) {
28870
28988
  }
28871
28989
  let missingTimeAdapterAlreadyWarned = false;
28872
28990
  function isLuxonTimeAdapterInstalled() {
28873
- const Chart = getChartJSConstructor();
28874
- if (!Chart) {
28991
+ if (!window.Chart) {
28875
28992
  return false;
28876
28993
  }
28877
28994
  // @ts-ignore
28878
- const adapter = new Chart._adapters._date({});
28995
+ const adapter = new window.Chart._adapters._date({});
28879
28996
  const isInstalled = adapter._id === "luxon";
28880
28997
  if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
28881
28998
  missingTimeAdapterAlreadyWarned = true;
@@ -29513,6 +29630,9 @@ const INTERACTIVE_LEGEND_CONFIG = {
29513
29630
  target.style.cursor = "default";
29514
29631
  },
29515
29632
  onClick: (event, legendItem, legend) => {
29633
+ if (event.type !== "click") {
29634
+ return;
29635
+ }
29516
29636
  const index = legendItem.datasetIndex;
29517
29637
  if (!legend.legendItems || index === undefined) {
29518
29638
  return;
@@ -32495,12 +32615,20 @@ class HoveredCellStore extends SpreadsheetStore {
32495
32615
  }
32496
32616
  }
32497
32617
  hover(position) {
32618
+ if (position.col === this.col && position.row === this.row) {
32619
+ return "noStateChange";
32620
+ }
32498
32621
  this.col = position.col;
32499
32622
  this.row = position.row;
32623
+ return;
32500
32624
  }
32501
32625
  clear() {
32626
+ if (this.col === undefined && this.row === undefined) {
32627
+ return "noStateChange";
32628
+ }
32502
32629
  this.col = undefined;
32503
32630
  this.row = undefined;
32631
+ return;
32504
32632
  }
32505
32633
  }
32506
32634
 
@@ -32522,7 +32650,11 @@ class CellPopoverStore extends SpreadsheetStore {
32522
32650
  this.persistentPopover = { col, row, sheetId, type };
32523
32651
  }
32524
32652
  close() {
32653
+ if (!this.persistentPopover) {
32654
+ return "noStateChange";
32655
+ }
32525
32656
  this.persistentPopover = undefined;
32657
+ return;
32526
32658
  }
32527
32659
  get persistentCellPopover() {
32528
32660
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -33412,10 +33544,13 @@ const duplicateSheet = {
33412
33544
  name: _t("Duplicate"),
33413
33545
  execute: (env) => {
33414
33546
  const sheetIdFrom = env.model.getters.getActiveSheetId();
33547
+ const sheetNameFrom = env.model.getters.getSheetName(sheetIdFrom);
33415
33548
  const sheetIdTo = env.model.uuidGenerator.smallUuid();
33549
+ const sheetNameTo = env.model.getters.getDuplicateSheetName(sheetNameFrom);
33416
33550
  env.model.dispatch("DUPLICATE_SHEET", {
33417
33551
  sheetId: sheetIdFrom,
33418
33552
  sheetIdTo,
33553
+ sheetNameTo,
33419
33554
  });
33420
33555
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
33421
33556
  },
@@ -38618,7 +38753,7 @@ class GenericChartConfigPanel extends owl.Component {
38618
38753
  const cancelledReasons = [
38619
38754
  ...(this.state.datasetDispatchResult?.reasons || []),
38620
38755
  ...(this.state.labelsDispatchResult?.reasons || []),
38621
- ];
38756
+ ].filter((reason) => reason !== "NoChanges" /* CommandResult.NoChanges */);
38622
38757
  return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
38623
38758
  }
38624
38759
  get isDatasetInvalid() {
@@ -39975,10 +40110,18 @@ class GaugeChartConfigPanel extends owl.Component {
39975
40110
  }
39976
40111
 
39977
40112
  class DOMFocusableElementStore {
39978
- mutators = ["setFocusableElement"];
40113
+ mutators = ["setFocusableElement", "focus"];
39979
40114
  focusableElement = undefined;
39980
40115
  setFocusableElement(element) {
39981
40116
  this.focusableElement = element;
40117
+ return "noStateChange";
40118
+ }
40119
+ focus() {
40120
+ if (this.focusableElement === document.activeElement) {
40121
+ return "noStateChange";
40122
+ }
40123
+ this.focusableElement?.focus();
40124
+ return;
39982
40125
  }
39983
40126
  }
39984
40127
 
@@ -40566,7 +40709,7 @@ class Composer extends owl.Component {
40566
40709
  if (document.activeElement === this.contentHelper.el &&
40567
40710
  this.props.composerStore.editionMode === "inactive" &&
40568
40711
  !this.props.isDefaultFocus) {
40569
- this.DOMFocusableElementStore.focusableElement?.focus();
40712
+ this.DOMFocusableElementStore.focus();
40570
40713
  }
40571
40714
  });
40572
40715
  owl.useEffect(() => {
@@ -40840,6 +40983,13 @@ class Composer extends owl.Component {
40840
40983
  openAssistant() {
40841
40984
  this.assistant.forcedClosed = false;
40842
40985
  }
40986
+ onWheel(event) {
40987
+ // detect if scrollbar is available
40988
+ if (this.composerRef.el &&
40989
+ this.composerRef.el.scrollHeight > this.composerRef.el.clientHeight) {
40990
+ event.stopPropagation();
40991
+ }
40992
+ }
40843
40993
  // ---------------------------------------------------------------------------
40844
40994
  // Private
40845
40995
  // ---------------------------------------------------------------------------
@@ -46316,8 +46466,8 @@ function compareDimensionValues(dimension, a, b) {
46316
46466
 
46317
46467
  const NULL_SYMBOL = Symbol("NULL");
46318
46468
  function createDate(dimension, value, locale) {
46319
- const granularity = dimension.granularity;
46320
- if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
46469
+ const granularity = dimension.granularity || "month";
46470
+ if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
46321
46471
  throw new Error(`Unknown date granularity: ${granularity}`);
46322
46472
  }
46323
46473
  const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
@@ -46336,6 +46486,9 @@ function createDate(dimension, value, locale) {
46336
46486
  case "month_number":
46337
46487
  number = date.getMonth() + 1;
46338
46488
  break;
46489
+ case "month":
46490
+ number = Math.floor(toNumber(value, locale));
46491
+ break;
46339
46492
  case "iso_week_number":
46340
46493
  number = date.getIsoWeek();
46341
46494
  break;
@@ -46429,6 +46582,10 @@ const MAP_VALUE_DIMENSION_DATE = {
46429
46582
  set: new Set(),
46430
46583
  values: {},
46431
46584
  },
46585
+ month: {
46586
+ set: new Set(),
46587
+ values: {},
46588
+ },
46432
46589
  iso_week_number: {
46433
46590
  set: new Set(),
46434
46591
  values: {},
@@ -46639,7 +46796,7 @@ class SpreadsheetPivot {
46639
46796
  const cells = this.filterDataEntriesFromDomain(this.dataEntries, domain);
46640
46797
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
46641
46798
  if (dimension.type === "datetime") {
46642
- const adapter = pivotTimeAdapter(dimension.granularity);
46799
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
46643
46800
  return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
46644
46801
  }
46645
46802
  if (!finalCell) {
@@ -46757,7 +46914,7 @@ class SpreadsheetPivot {
46757
46914
  if (nonEmptyCells.length === 0) {
46758
46915
  return "integer";
46759
46916
  }
46760
- if (nonEmptyCells.every((cell) => cell.format && isDateTimeFormat(cell.format))) {
46917
+ if (nonEmptyCells.every((cell) => cell.type === CellValueType.number && cell.format && isDateTimeFormat(cell.format))) {
46761
46918
  return "datetime";
46762
46919
  }
46763
46920
  if (nonEmptyCells.every((cell) => cell.type === CellValueType.boolean)) {
@@ -46836,7 +46993,12 @@ class SpreadsheetPivot {
46836
46993
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
46837
46994
  }
46838
46995
  else {
46839
- entry[field.name] = cell;
46996
+ if (field.type === "char") {
46997
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
46998
+ }
46999
+ else {
47000
+ entry[field.name] = cell;
47001
+ }
46840
47002
  }
46841
47003
  }
46842
47004
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -46850,7 +47012,7 @@ class SpreadsheetPivot {
46850
47012
  for (const entry of dataEntries) {
46851
47013
  for (const dimension of dateDimensions) {
46852
47014
  const value = createDate(dimension, entry[dimension.fieldName]?.value || null, this.getters.getLocale());
46853
- const adapter = pivotTimeAdapter(dimension.granularity);
47015
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
46854
47016
  const { format, value: valueToFormat } = adapter.toValueAndFormat(value, locale);
46855
47017
  entry[dimension.nameWithGranularity] = {
46856
47018
  value,
@@ -46870,6 +47032,7 @@ const dateGranularities = [
46870
47032
  "year",
46871
47033
  "quarter_number",
46872
47034
  "month_number",
47035
+ "month",
46873
47036
  "iso_week_number",
46874
47037
  "day_of_month",
46875
47038
  "day",
@@ -47120,7 +47283,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
47120
47283
  : this.datetimeGranularities);
47121
47284
  }
47122
47285
  for (const field of dateFields) {
47123
- granularitiesPerFields[field.fieldName].delete(field.granularity);
47286
+ granularitiesPerFields[field.fieldName].delete(field.granularity || "month");
47124
47287
  }
47125
47288
  return granularitiesPerFields;
47126
47289
  }
@@ -49290,6 +49453,8 @@ class GridComposer extends owl.Component {
49290
49453
  }
49291
49454
  get composerProps() {
49292
49455
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
49456
+ // Remove the wrapper border width
49457
+ const maxHeight = this.props.gridDims.height - this.rect.y - 2 * COMPOSER_BORDER_WIDTH;
49293
49458
  return {
49294
49459
  rect: { ...this.rect },
49295
49460
  delimitation: {
@@ -49307,6 +49472,7 @@ class GridComposer extends owl.Component {
49307
49472
  }),
49308
49473
  onInputContextMenu: this.props.onInputContextMenu,
49309
49474
  composerStore: this.composerStore,
49475
+ inputStyle: `max-height: ${maxHeight}px;`,
49310
49476
  };
49311
49477
  }
49312
49478
  get containerStyle() {
@@ -51970,10 +52136,6 @@ function useGridDrawing(refName, model, canvasSize) {
51970
52136
  ctx.scale(dpr, dpr);
51971
52137
  for (const layer of OrderedLayers()) {
51972
52138
  model.drawLayer(renderingContext, layer);
51973
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
51974
- // it does not mutate anything. Most importantly it's used
51975
- // during rendering. Invoking a mutator during rendering would
51976
- // trigger another rendering, ultimately resulting in an infinite loop.
51977
52139
  rendererStore.drawLayer(renderingContext, layer);
51978
52140
  }
51979
52141
  }
@@ -52664,7 +52826,7 @@ class Grid extends owl.Component {
52664
52826
  this.cellPopovers = useStore(CellPopoverStore);
52665
52827
  owl.useEffect(() => {
52666
52828
  if (!this.sidePanel.isOpen) {
52667
- this.DOMFocusableElementStore.focusableElement?.focus();
52829
+ this.DOMFocusableElementStore.focus();
52668
52830
  }
52669
52831
  }, () => [this.sidePanel.isOpen]);
52670
52832
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -52874,7 +53036,7 @@ class Grid extends owl.Component {
52874
53036
  focusDefaultElement() {
52875
53037
  if (!this.env.model.getters.getSelectedFigureId() &&
52876
53038
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
52877
- this.DOMFocusableElementStore.focusableElement?.focus();
53039
+ this.DOMFocusableElementStore.focus();
52878
53040
  }
52879
53041
  }
52880
53042
  get gridEl() {
@@ -53219,6 +53381,322 @@ class Grid extends owl.Component {
53219
53381
  }
53220
53382
  }
53221
53383
 
53384
+ css /* scss */ `
53385
+ .o_pivot_html_renderer {
53386
+ width: 100%;
53387
+ border-collapse: collapse;
53388
+
53389
+ &:hover {
53390
+ cursor: pointer;
53391
+ }
53392
+
53393
+ td,
53394
+ th {
53395
+ border: 1px solid #dee2e6;
53396
+ background-color: #fff;
53397
+ padding: 0.3rem;
53398
+ white-space: nowrap;
53399
+
53400
+ &:hover {
53401
+ filter: brightness(0.9);
53402
+ }
53403
+ }
53404
+
53405
+ td {
53406
+ text-align: right;
53407
+ }
53408
+
53409
+ th {
53410
+ background-color: #f5f5f5;
53411
+ font-weight: bold;
53412
+ color: black;
53413
+ }
53414
+
53415
+ .o_missing_value {
53416
+ color: #46646d;
53417
+ background: #e7f2f6;
53418
+ }
53419
+ }
53420
+ `;
53421
+ class PivotHTMLRenderer extends owl.Component {
53422
+ static template = "o_spreadsheet.PivotHTMLRenderer";
53423
+ static components = { Checkbox };
53424
+ static props = {
53425
+ pivotId: String,
53426
+ onCellClicked: Function,
53427
+ };
53428
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
53429
+ data = {
53430
+ columns: [],
53431
+ rows: [],
53432
+ values: [],
53433
+ };
53434
+ state = owl.useState({
53435
+ showMissingValuesOnly: false,
53436
+ });
53437
+ setup() {
53438
+ const table = this.pivot.getTableStructure();
53439
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
53440
+ this.data = {
53441
+ columns: this._buildColHeaders(formulaId, table),
53442
+ rows: this._buildRowHeaders(formulaId, table),
53443
+ values: this._buildValues(formulaId, table),
53444
+ };
53445
+ }
53446
+ get tracker() {
53447
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
53448
+ }
53449
+ // ---------------------------------------------------------------------
53450
+ // Missing values building
53451
+ // ---------------------------------------------------------------------
53452
+ /**
53453
+ * Retrieve the data to display in the Pivot Table
53454
+ * In the case when showMissingValuesOnly is false, the returned value
53455
+ * is the complete data
53456
+ * In the case when showMissingValuesOnly is true, the returned value is
53457
+ * the data which contains only missing values in the rows and cols. In
53458
+ * the rows, we also return the parent rows of rows which contains missing
53459
+ * values, to give context to the user.
53460
+ *
53461
+ */
53462
+ getTableData() {
53463
+ if (!this.state.showMissingValuesOnly) {
53464
+ return this.data;
53465
+ }
53466
+ const colIndexes = this.getColumnsIndexes();
53467
+ const rowIndexes = this.getRowsIndexes();
53468
+ const columns = this.buildColumnsMissing(colIndexes);
53469
+ const rows = this.buildRowsMissing(rowIndexes);
53470
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
53471
+ return { columns, rows, values };
53472
+ }
53473
+ /**
53474
+ * Retrieve the parents of the given row
53475
+ * ex:
53476
+ * Australia
53477
+ * January
53478
+ * February
53479
+ * The parent of "January" is "Australia"
53480
+ */
53481
+ addRecursiveRow(index) {
53482
+ const rows = this.pivot.getTableStructure().rows;
53483
+ const row = [...rows[index].values];
53484
+ if (row.length <= 1) {
53485
+ return [index];
53486
+ }
53487
+ row.pop();
53488
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
53489
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
53490
+ }
53491
+ /**
53492
+ * Create the columns to be used, based on the indexes of the columns in
53493
+ * which a missing value is present
53494
+ *
53495
+ */
53496
+ buildColumnsMissing(indexes) {
53497
+ // columnsMap explode the columns in an array of array of the same
53498
+ // size with the index of each column, repeated 'span' times.
53499
+ // ex:
53500
+ // | A | B |
53501
+ // | 1 | 2 | 3 |
53502
+ // => [
53503
+ // [0, 0, 1]
53504
+ // [0, 1, 2]
53505
+ // ]
53506
+ const columnsMap = [];
53507
+ for (const column of this.data.columns) {
53508
+ const columnMap = [];
53509
+ for (const index in column) {
53510
+ for (let i = 0; i < column[index].span; i++) {
53511
+ columnMap.push(parseInt(index, 10));
53512
+ }
53513
+ }
53514
+ columnsMap.push(columnMap);
53515
+ }
53516
+ // Remove the columns that are not present in indexes
53517
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
53518
+ if (!indexes.includes(i)) {
53519
+ for (const columnMap of columnsMap) {
53520
+ columnMap.splice(i, 1);
53521
+ }
53522
+ }
53523
+ }
53524
+ // Build the columns
53525
+ const columns = [];
53526
+ for (const mapIndex in columnsMap) {
53527
+ const column = [];
53528
+ let index = undefined;
53529
+ let span = 1;
53530
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
53531
+ if (index !== columnsMap[mapIndex][i]) {
53532
+ if (index !== undefined) {
53533
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53534
+ }
53535
+ index = columnsMap[mapIndex][i];
53536
+ span = 1;
53537
+ }
53538
+ else {
53539
+ span++;
53540
+ }
53541
+ }
53542
+ if (index !== undefined) {
53543
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53544
+ }
53545
+ columns.push(column);
53546
+ }
53547
+ return columns;
53548
+ }
53549
+ /**
53550
+ * Create the rows to be used, based on the indexes of the rows in
53551
+ * which a missing value is present.
53552
+ */
53553
+ buildRowsMissing(indexes) {
53554
+ return indexes.map((index) => this.data.rows[index]);
53555
+ }
53556
+ /**
53557
+ * Create the value to be used, based on the indexes of the columns and
53558
+ * rows in which a missing value is present.
53559
+ */
53560
+ buildValuesMissing(colIndexes, rowIndexes) {
53561
+ const values = colIndexes.map(() => []);
53562
+ for (const row of rowIndexes) {
53563
+ for (const col in colIndexes) {
53564
+ values[col].push(this.data.values[colIndexes[col]][row]);
53565
+ }
53566
+ }
53567
+ return values;
53568
+ }
53569
+ getColumnsIndexes() {
53570
+ const indexes = new Set();
53571
+ for (let i = 0; i < this.data.columns.length; i++) {
53572
+ const exploded = [];
53573
+ for (let y = 0; y < this.data.columns[i].length; y++) {
53574
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
53575
+ exploded.push(this.data.columns[i][y]);
53576
+ }
53577
+ }
53578
+ for (let y = 0; y < exploded.length; y++) {
53579
+ if (exploded[y].isMissing) {
53580
+ indexes.add(y);
53581
+ }
53582
+ }
53583
+ }
53584
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
53585
+ const values = this.data.values[i];
53586
+ if (values.find((x) => x.isMissing)) {
53587
+ indexes.add(i);
53588
+ }
53589
+ }
53590
+ return Array.from(indexes).sort((a, b) => a - b);
53591
+ }
53592
+ getRowsIndexes() {
53593
+ const rowIndexes = new Set();
53594
+ for (let i = 0; i < this.data.rows.length; i++) {
53595
+ if (this.data.rows[i].isMissing) {
53596
+ rowIndexes.add(i);
53597
+ }
53598
+ for (const col of this.data.values) {
53599
+ if (col[i].isMissing) {
53600
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
53601
+ }
53602
+ }
53603
+ }
53604
+ return Array.from(rowIndexes).sort((a, b) => a - b);
53605
+ }
53606
+ // ---------------------------------------------------------------------
53607
+ // Data table creation
53608
+ // ---------------------------------------------------------------------
53609
+ _buildColHeaders(id, table) {
53610
+ const headers = [];
53611
+ for (const row of table.columns) {
53612
+ const current = [];
53613
+ for (const cell of row) {
53614
+ const args = [];
53615
+ for (let i = 0; i < cell.fields.length; i++) {
53616
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
53617
+ }
53618
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53619
+ const locale = this.env.model.getters.getLocale();
53620
+ if (domain.at(-1)?.field === "measure") {
53621
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
53622
+ current.push({
53623
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53624
+ value: formatValue(value, { format, locale }),
53625
+ span: cell.width,
53626
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53627
+ });
53628
+ }
53629
+ else {
53630
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53631
+ current.push({
53632
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53633
+ value: formatValue(value, { format, locale }),
53634
+ span: cell.width,
53635
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53636
+ });
53637
+ }
53638
+ }
53639
+ headers.push(current);
53640
+ }
53641
+ const last = headers[headers.length - 1];
53642
+ headers[headers.length - 1] = last.map((cell) => {
53643
+ if (!cell.isMissing) {
53644
+ cell.style = "color: #756f6f;";
53645
+ }
53646
+ return cell;
53647
+ });
53648
+ return headers;
53649
+ }
53650
+ _buildRowHeaders(id, table) {
53651
+ const headers = [];
53652
+ for (const row of table.rows) {
53653
+ const args = [];
53654
+ for (let i = 0; i < row.fields.length; i++) {
53655
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53656
+ }
53657
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53658
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53659
+ const locale = this.env.model.getters.getLocale();
53660
+ const cell = {
53661
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53662
+ value: formatValue(value, { format, locale }),
53663
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53664
+ };
53665
+ if (row.indent > 1) {
53666
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
53667
+ }
53668
+ headers.push(cell);
53669
+ }
53670
+ return headers;
53671
+ }
53672
+ _buildValues(id, table) {
53673
+ const values = [];
53674
+ for (const col of table.columns.at(-1) || []) {
53675
+ const current = [];
53676
+ const measure = toString(col.values[col.values.length - 1]);
53677
+ for (const row of table.rows) {
53678
+ const args = [];
53679
+ for (let i = 0; i < row.fields.length; i++) {
53680
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53681
+ }
53682
+ for (let i = 0; i < col.fields.length - 1; i++) {
53683
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
53684
+ }
53685
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53686
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
53687
+ const locale = this.env.model.getters.getLocale();
53688
+ current.push({
53689
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
53690
+ value: formatValue(value, { format, locale }),
53691
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
53692
+ });
53693
+ }
53694
+ values.push(current);
53695
+ }
53696
+ return values;
53697
+ }
53698
+ }
53699
+
53222
53700
  /**
53223
53701
  * BasePlugin
53224
53702
  *
@@ -54569,7 +55047,7 @@ class ChartPlugin extends CorePlugin {
54569
55047
  case "CREATE_CHART":
54570
55048
  return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartDuplicate));
54571
55049
  case "UPDATE_CHART":
54572
- return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists));
55050
+ return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists, this.checkChartChanged));
54573
55051
  default:
54574
55052
  return "Success" /* CommandResult.Success */;
54575
55053
  }
@@ -54724,9 +55202,12 @@ class ChartPlugin extends CorePlugin {
54724
55202
  : "Success" /* CommandResult.Success */;
54725
55203
  }
54726
55204
  checkChartExists(cmd) {
54727
- return this.getters.getFigureSheetId(cmd.id)
54728
- ? "Success" /* CommandResult.Success */
54729
- : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
55205
+ return this.isChartDefined(cmd.id) ? "Success" /* CommandResult.Success */ : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
55206
+ }
55207
+ checkChartChanged(cmd) {
55208
+ return deepEquals(this.getChartDefinition(cmd.id), cmd.definition)
55209
+ ? "NoChanges" /* CommandResult.NoChanges */
55210
+ : "Success" /* CommandResult.Success */;
54730
55211
  }
54731
55212
  }
54732
55213
 
@@ -56675,7 +57156,7 @@ class RangeAdapter {
56675
57156
  if (range.sheetId === cmd.sheetId) {
56676
57157
  return { changeType: "CHANGE", range };
56677
57158
  }
56678
- if (cmd.name && range.invalidSheetName === cmd.name) {
57159
+ if (isSheetNameEqual(range.invalidSheetName, cmd.name)) {
56679
57160
  const invalidSheetName = undefined;
56680
57161
  const sheetId = cmd.sheetId;
56681
57162
  const newRange = range.clone({ sheetId, invalidSheetName });
@@ -57045,6 +57526,7 @@ class SheetPlugin extends CorePlugin {
57045
57526
  "getCommandZones",
57046
57527
  "getUnboundedZone",
57047
57528
  "checkElementsIncludeAllNonFrozenHeaders",
57529
+ "getDuplicateSheetName",
57048
57530
  ];
57049
57531
  sheetIdsMapName = {};
57050
57532
  orderedSheetIds = [];
@@ -57069,7 +57551,11 @@ class SheetPlugin extends CorePlugin {
57069
57551
  return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
57070
57552
  }
57071
57553
  case "DUPLICATE_SHEET": {
57072
- return this.sheets[cmd.sheetIdTo] ? "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */ : "Success" /* CommandResult.Success */;
57554
+ if (this.sheets[cmd.sheetIdTo])
57555
+ return "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */;
57556
+ if (this.orderedSheetIds.map(this.getSheetName.bind(this)).includes(cmd.sheetNameTo))
57557
+ return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
57558
+ return "Success" /* CommandResult.Success */;
57073
57559
  }
57074
57560
  case "MOVE_SHEET":
57075
57561
  try {
@@ -57146,7 +57632,7 @@ class SheetPlugin extends CorePlugin {
57146
57632
  this.showSheet(cmd.sheetId);
57147
57633
  break;
57148
57634
  case "DUPLICATE_SHEET":
57149
- this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo);
57635
+ this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo, cmd.sheetNameTo);
57150
57636
  break;
57151
57637
  case "DELETE_SHEET":
57152
57638
  this.deleteSheet(this.sheets[cmd.sheetId]);
@@ -57287,7 +57773,7 @@ class SheetPlugin extends CorePlugin {
57287
57773
  if (name) {
57288
57774
  const unquotedName = getUnquotedSheetName(name);
57289
57775
  for (const key in this.sheetIdsMapName) {
57290
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
57776
+ if (isSheetNameEqual(key, unquotedName)) {
57291
57777
  return this.sheetIdsMapName[key];
57292
57778
  }
57293
57779
  }
@@ -57353,10 +57839,7 @@ class SheetPlugin extends CorePlugin {
57353
57839
  }
57354
57840
  getNextSheetName(baseName = "Sheet") {
57355
57841
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
57356
- return getUniqueText(baseName, names, {
57357
- compute: (name, i) => `${name}${i}`,
57358
- computeFirstOne: true,
57359
- });
57842
+ return getNextSheetName(names, baseName);
57360
57843
  }
57361
57844
  getSheetSize(sheetId) {
57362
57845
  return {
@@ -57538,7 +58021,7 @@ class SheetPlugin extends CorePlugin {
57538
58021
  }
57539
58022
  const { orderedSheetIds, sheets } = this;
57540
58023
  const name = cmd.name && cmd.name.trim().toLowerCase();
57541
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
58024
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
57542
58025
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
57543
58026
  }
57544
58027
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -57602,9 +58085,8 @@ class SheetPlugin extends CorePlugin {
57602
58085
  showSheet(sheetId) {
57603
58086
  this.history.update("sheets", sheetId, "isVisible", true);
57604
58087
  }
57605
- duplicateSheet(fromId, toId) {
58088
+ duplicateSheet(fromId, toId, toName) {
57606
58089
  const sheet = this.getSheet(fromId);
57607
- const toName = this.getDuplicateSheetName(sheet.name);
57608
58090
  const newSheet = deepCopy(sheet);
57609
58091
  newSheet.id = toId;
57610
58092
  newSheet.name = toName;
@@ -57637,8 +58119,7 @@ class SheetPlugin extends CorePlugin {
57637
58119
  }
57638
58120
  getDuplicateSheetName(sheetName) {
57639
58121
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
57640
- const baseName = _t("Copy of %s", sheetName);
57641
- return getUniqueText(baseName.toString(), names);
58122
+ return getDuplicateSheetName(sheetName, names);
57642
58123
  }
57643
58124
  deleteSheet(sheet) {
57644
58125
  const name = sheet.name;
@@ -60433,8 +60914,8 @@ class SpreadingRelation {
60433
60914
  const EMPTY_ARRAY = [];
60434
60915
 
60435
60916
  const MAX_ITERATION = 30;
60436
- const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
60437
- const EMPTY_CELL = createEvaluatedCell({ value: null });
60917
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
60918
+ const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
60438
60919
  class Evaluator {
60439
60920
  context;
60440
60921
  getters;
@@ -66433,6 +66914,55 @@ class HistoryPlugin extends UIPlugin {
66433
66914
  }
66434
66915
  }
66435
66916
 
66917
+ class PivotPresenceTracker {
66918
+ trackedValues = new Set();
66919
+ domainToArray(domain) {
66920
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
66921
+ }
66922
+ isValuePresent(measure, domain) {
66923
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66924
+ return this.trackedValues.has(key);
66925
+ }
66926
+ isHeaderPresent(domain) {
66927
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66928
+ return this.trackedValues.has(key);
66929
+ }
66930
+ trackValue(measure, domain) {
66931
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66932
+ this.trackedValues.add(key);
66933
+ }
66934
+ trackHeader(domain) {
66935
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66936
+ this.trackedValues.add(key);
66937
+ }
66938
+ }
66939
+
66940
+ class PivotPresencePlugin extends UIPlugin {
66941
+ static getters = ["getPivotPresenceTracker"];
66942
+ trackPresencePivotId;
66943
+ tracker;
66944
+ handle(cmd) {
66945
+ switch (cmd.type) {
66946
+ case "PIVOT_START_PRESENCE_TRACKING":
66947
+ this.tracker = new PivotPresenceTracker();
66948
+ this.trackPresencePivotId = cmd.pivotId;
66949
+ break;
66950
+ case "PIVOT_STOP_PRESENCE_TRACKING":
66951
+ this.trackPresencePivotId = undefined;
66952
+ break;
66953
+ }
66954
+ }
66955
+ getPivotPresenceTracker(pivotId) {
66956
+ if (this.trackPresencePivotId !== pivotId) {
66957
+ return undefined;
66958
+ }
66959
+ if (!this.tracker) {
66960
+ throw new Error("Tracker not initialized");
66961
+ }
66962
+ return this.tracker;
66963
+ }
66964
+ }
66965
+
66436
66966
  class SplitToColumnsPlugin extends UIPlugin {
66437
66967
  static getters = ["getAutomaticSeparator"];
66438
66968
  allowDispatch(cmd) {
@@ -69183,6 +69713,7 @@ const featurePluginRegistry = new Registry()
69183
69713
  .add("automatic_sum", AutomaticSumPlugin)
69184
69714
  .add("format", FormatPlugin)
69185
69715
  .add("insert_pivot", InsertPivotPlugin)
69716
+ .add("pivot_presence", PivotPresencePlugin)
69186
69717
  .add("split_to_columns", SplitToColumnsPlugin)
69187
69718
  .add("collaborative", CollaborativePlugin)
69188
69719
  .add("history", HistoryPlugin)
@@ -69563,11 +70094,11 @@ class BottomBarSheet extends owl.Component {
69563
70094
  if (ev.key === "Enter") {
69564
70095
  ev.preventDefault();
69565
70096
  this.stopEdition();
69566
- this.DOMFocusableElementStore.focusableElement?.focus();
70097
+ this.DOMFocusableElementStore.focus();
69567
70098
  }
69568
70099
  if (ev.key === "Escape") {
69569
70100
  this.cancelEdition();
69570
- this.DOMFocusableElementStore.focusableElement?.focus();
70101
+ this.DOMFocusableElementStore.focus();
69571
70102
  }
69572
70103
  }
69573
70104
  onMouseEventSheetName(ev) {
@@ -71739,11 +72270,13 @@ class Spreadsheet extends owl.Component {
71739
72270
  this.checkViewportSize();
71740
72271
  stores.on("store-updated", this, render);
71741
72272
  resizeObserver.observe(this.spreadsheetRef.el);
72273
+ registerChartJSExtensions();
71742
72274
  });
71743
72275
  owl.onWillUnmount(() => {
71744
72276
  this.unbindModelEvents();
71745
72277
  stores.off("store-updated", this);
71746
72278
  resizeObserver.disconnect();
72279
+ unregisterChartJsExtensions();
71747
72280
  });
71748
72281
  owl.onPatched(() => {
71749
72282
  this.checkViewportSize();
@@ -76176,6 +76709,7 @@ const components = {
76176
76709
  PivotDimensionOrder,
76177
76710
  PivotDimension,
76178
76711
  PivotLayoutConfigurator,
76712
+ PivotHTMLRenderer,
76179
76713
  PivotDeferUpdate,
76180
76714
  PivotTitleSection,
76181
76715
  CogWheelMenu,
@@ -76270,6 +76804,6 @@ exports.tokenColors = tokenColors;
76270
76804
  exports.tokenize = tokenize;
76271
76805
 
76272
76806
 
76273
- __info__.version = "18.2.10";
76274
- __info__.date = "2025-05-02T12:34:39.632Z";
76275
- __info__.hash = "e8ff3fc";
76807
+ __info__.version = "18.2.12";
76808
+ __info__.date = "2025-05-13T17:52:23.989Z";
76809
+ __info__.hash = "ba2ba9b";