@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
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, App, blockDom, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -3357,7 +3357,7 @@ function isDateAfter(date, dateAfter) {
3357
3357
  */
3358
3358
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3359
3359
  decimalSeparator = escapeRegExp(decimalSeparator);
3360
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3360
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3361
3361
  });
3362
3362
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3363
3363
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -6280,6 +6280,32 @@ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6280
6280
  })
6281
6281
  .filter(isDefined);
6282
6282
  }
6283
+ function getNextSheetName(existingNames, baseName = "Sheet") {
6284
+ let i = 1;
6285
+ let name = `${baseName}${i}`;
6286
+ while (existingNames.includes(name)) {
6287
+ name = `${baseName}${i}`;
6288
+ i++;
6289
+ }
6290
+ return name;
6291
+ }
6292
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6293
+ let i = 1;
6294
+ const baseName = _t("Copy of %s", nameToDuplicate);
6295
+ let name = baseName.toString();
6296
+ while (existingNames.includes(name)) {
6297
+ name = `${baseName} (${i})`;
6298
+ i++;
6299
+ }
6300
+ return name;
6301
+ }
6302
+ function isSheetNameEqual(name1, name2) {
6303
+ if (name1 === undefined || name2 === undefined) {
6304
+ return false;
6305
+ }
6306
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6307
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6308
+ }
6283
6309
 
6284
6310
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6285
6311
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
@@ -7978,6 +8004,24 @@ const monthNumberAdapter = {
7978
8004
  return `${normalizedValue}`;
7979
8005
  },
7980
8006
  };
8007
+ /**
8008
+ * normalizes month number + year
8009
+ */
8010
+ const monthAdapter = {
8011
+ normalizeFunctionValue(value) {
8012
+ const date = toNumber(value, DEFAULT_LOCALE);
8013
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
8014
+ },
8015
+ toValueAndFormat(normalizedValue) {
8016
+ return {
8017
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
8018
+ format: "mmmm yyyy",
8019
+ };
8020
+ },
8021
+ toFunctionValue(normalizedValue) {
8022
+ return `"${normalizedValue}"`;
8023
+ },
8024
+ };
7981
8025
  /**
7982
8026
  * normalizes quarter number
7983
8027
  */
@@ -8108,6 +8152,7 @@ pivotTimeAdapterRegistry
8108
8152
  .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
8109
8153
  .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
8110
8154
  .add("month_number", nullHandlerDecorator(monthNumberAdapter))
8155
+ .add("month", nullHandlerDecorator(monthAdapter))
8111
8156
  .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
8112
8157
  .add("day_of_week", nullHandlerDecorator(dayOfWeekAdapter))
8113
8158
  .add("hour_number", nullHandlerDecorator(hourNumberAdapter))
@@ -8124,10 +8169,9 @@ const AGGREGATOR_NAMES = {
8124
8169
  avg: _t("Average"),
8125
8170
  sum: _t("Sum"),
8126
8171
  };
8127
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8128
8172
  const AGGREGATORS_BY_FIELD_TYPE = {
8129
- integer: NUMBER_CHAR_AGGREGATORS,
8130
- char: NUMBER_CHAR_AGGREGATORS,
8173
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8174
+ char: ["count_distinct", "count"],
8131
8175
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8132
8176
  datetime: ["max", "min", "count_distinct", "count"],
8133
8177
  };
@@ -8288,10 +8332,7 @@ function toNormalizedPivotValue(dimension, groupValue) {
8288
8332
  return normalizer(groupValueString, dimension.granularity);
8289
8333
  }
8290
8334
  function normalizeDateTime(value, granularity) {
8291
- if (!granularity) {
8292
- throw new Error("Missing granularity");
8293
- }
8294
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8335
+ return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
8295
8336
  }
8296
8337
  function toFunctionPivotValue(value, dimension) {
8297
8338
  if (value === null) {
@@ -8303,10 +8344,7 @@ function toFunctionPivotValue(value, dimension) {
8303
8344
  return pivotToFunctionValueRegistry.get(dimension.type)(value, dimension.granularity);
8304
8345
  }
8305
8346
  function toFunctionValueDateTime(value, granularity) {
8306
- if (!granularity) {
8307
- throw new Error("Missing granularity");
8308
- }
8309
- return pivotTimeAdapter(granularity).toFunctionValue(value);
8347
+ return pivotTimeAdapter(granularity ?? "month").toFunctionValue(value);
8310
8348
  }
8311
8349
  const pivotNormalizationValueRegistry = new Registry();
8312
8350
  pivotNormalizationValueRegistry
@@ -9486,7 +9524,10 @@ function proxifyStoreMutation(store, callback) {
9486
9524
  const functionProxy = new Proxy(value, {
9487
9525
  // trap the function call
9488
9526
  apply(target, thisArg, argArray) {
9489
- Reflect.apply(target, thisStore, argArray);
9527
+ const res = Reflect.apply(target, thisStore, argArray);
9528
+ if (res === "noStateChange") {
9529
+ return;
9530
+ }
9490
9531
  callback();
9491
9532
  },
9492
9533
  });
@@ -9508,7 +9549,7 @@ function getDependencyContainer(env) {
9508
9549
  const ModelStore = createAbstractStore("Model");
9509
9550
 
9510
9551
  class RendererStore {
9511
- mutators = ["register", "unRegister"];
9552
+ mutators = ["register", "unRegister", "drawLayer"];
9512
9553
  renderers = {};
9513
9554
  register(renderer) {
9514
9555
  if (!renderer.renderingLayers.length) {
@@ -9528,14 +9569,14 @@ class RendererStore {
9528
9569
  }
9529
9570
  drawLayer(context, layer) {
9530
9571
  const renderers = this.renderers[layer];
9531
- if (!renderers) {
9532
- return;
9533
- }
9534
- for (const renderer of renderers) {
9535
- context.ctx.save();
9536
- renderer.drawLayer(context, layer);
9537
- context.ctx.restore();
9572
+ if (renderers) {
9573
+ for (const renderer of renderers) {
9574
+ context.ctx.save();
9575
+ renderer.drawLayer(context, layer);
9576
+ context.ctx.restore();
9577
+ }
9538
9578
  }
9579
+ return "noStateChange";
9539
9580
  }
9540
9581
  }
9541
9582
 
@@ -9588,16 +9629,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9588
9629
  focusComposer(listener, args) {
9589
9630
  this.activeComposer = listener;
9590
9631
  if (this.getters.isReadonly()) {
9591
- return;
9632
+ return "noStateChange";
9592
9633
  }
9593
9634
  this._focusMode = args.focusMode || "contentFocus";
9594
9635
  if (this._focusMode !== "inactive") {
9595
9636
  this.setComposerContent(args);
9596
9637
  }
9638
+ return;
9597
9639
  }
9598
9640
  focusActiveComposer(args) {
9599
9641
  if (this.getters.isReadonly()) {
9600
- return;
9642
+ return "noStateChange";
9601
9643
  }
9602
9644
  if (!this.activeComposer) {
9603
9645
  throw new Error("No composer is registered");
@@ -9606,6 +9648,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9606
9648
  if (this._focusMode !== "inactive") {
9607
9649
  this.setComposerContent(args);
9608
9650
  }
9651
+ return;
9609
9652
  }
9610
9653
  /**
9611
9654
  * Start the edition or update the content if it's already started.
@@ -9765,12 +9808,24 @@ function getElementMargins(el) {
9765
9808
  }
9766
9809
 
9767
9810
  const chartJsExtensionRegistry = new Registry();
9768
- /** Return window.Chart, making sure all our extensions are loaded in ChartJS */
9769
- function getChartJSConstructor() {
9770
- if (window.Chart && !window.Chart?.registry.plugins.get("chartShowValuesPlugin")) {
9771
- window.Chart.register(...chartJsExtensionRegistry.getAll());
9811
+ function areChartJSExtensionsLoaded() {
9812
+ return !!window.Chart.registry.plugins.get("chartShowValuesPlugin");
9813
+ }
9814
+ function registerChartJSExtensions() {
9815
+ if (!window.Chart || areChartJSExtensionsLoaded()) {
9816
+ return;
9817
+ }
9818
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
9819
+ registryItem.register(window.Chart);
9820
+ }
9821
+ }
9822
+ function unregisterChartJsExtensions() {
9823
+ if (!window.Chart) {
9824
+ return;
9825
+ }
9826
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
9827
+ registryItem.unregister(window.Chart);
9772
9828
  }
9773
- return window.Chart;
9774
9829
  }
9775
9830
 
9776
9831
  const TREND_LINE_XAXIS_ID = "x1";
@@ -10087,7 +10142,7 @@ function getDefinedAxis(definition) {
10087
10142
  }
10088
10143
  function formatChartDatasetValue(axisFormats, locale) {
10089
10144
  return (value, axisId) => {
10090
- const format = axisId ? axisFormats?.[axisId] : undefined;
10145
+ const format = axisFormats?.[axisId];
10091
10146
  return formatTickValue({ format, locale })(value);
10092
10147
  };
10093
10148
  }
@@ -10261,7 +10316,7 @@ function drawPieChartValues(chart, options, ctx) {
10261
10316
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10262
10317
  ctx.fillStyle = chartFontColor(options.background);
10263
10318
  ctx.strokeStyle = options.background || "#ffffff";
10264
- const displayValue = options.callback(value);
10319
+ const displayValue = options.callback(value, "y");
10265
10320
  drawTextWithBackground(displayValue, x, y, ctx);
10266
10321
  }
10267
10322
  }
@@ -10330,8 +10385,14 @@ css /* scss */ `
10330
10385
  }
10331
10386
  }
10332
10387
  `;
10333
- chartJsExtensionRegistry.add("chartShowValuesPlugin", chartShowValuesPlugin);
10334
- chartJsExtensionRegistry.add("waterfallLinesPlugin", waterfallLinesPlugin);
10388
+ chartJsExtensionRegistry.add("chartShowValuesPlugin", {
10389
+ register: (Chart) => Chart.register(chartShowValuesPlugin),
10390
+ unregister: (Chart) => Chart.unregister(chartShowValuesPlugin),
10391
+ });
10392
+ chartJsExtensionRegistry.add("waterfallLinesPlugin", {
10393
+ register: (Chart) => Chart.register(waterfallLinesPlugin),
10394
+ unregister: (Chart) => Chart.unregister(waterfallLinesPlugin),
10395
+ });
10335
10396
  class ChartJsComponent extends Component {
10336
10397
  static template = "o-spreadsheet-ChartJsComponent";
10337
10398
  static props = {
@@ -10383,8 +10444,7 @@ class ChartJsComponent extends Component {
10383
10444
  createChart(chartData) {
10384
10445
  const canvas = this.canvas.el;
10385
10446
  const ctx = canvas.getContext("2d");
10386
- const Chart = getChartJSConstructor();
10387
- this.chart = new Chart(ctx, chartData);
10447
+ this.chart = new window.Chart(ctx, chartData);
10388
10448
  }
10389
10449
  updateChartJs(chartData) {
10390
10450
  if (chartData.data && chartData.data.datasets) {
@@ -18516,7 +18576,7 @@ const IF = {
18516
18576
  return { value: "" };
18517
18577
  }
18518
18578
  if (result.value === null) {
18519
- result.value = "";
18579
+ return { ...result, value: "" };
18520
18580
  }
18521
18581
  return result;
18522
18582
  },
@@ -18537,7 +18597,7 @@ const IFERROR = {
18537
18597
  return { value: "" };
18538
18598
  }
18539
18599
  if (result.value === null) {
18540
- result.value = "";
18600
+ return { ...result, value: "" };
18541
18601
  }
18542
18602
  return result;
18543
18603
  },
@@ -18558,7 +18618,7 @@ const IFNA = {
18558
18618
  return { value: "" };
18559
18619
  }
18560
18620
  if (result.value === null) {
18561
- result.value = "";
18621
+ return { ...result, value: "" };
18562
18622
  }
18563
18623
  return result;
18564
18624
  },
@@ -18584,7 +18644,7 @@ const IFS = {
18584
18644
  return { value: "" };
18585
18645
  }
18586
18646
  if (result.value === null) {
18587
- result.value = "";
18647
+ return { ...result, value: "" };
18588
18648
  }
18589
18649
  return result;
18590
18650
  }
@@ -18702,6 +18762,11 @@ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
18702
18762
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
18703
18763
  throw new InvalidReferenceError();
18704
18764
  }
18765
+ if (evalContext.__originCellPosition &&
18766
+ range.sheetId === evalContext.__originSheetId &&
18767
+ isZoneInside(positionToZone(evalContext.__originCellPosition), zone)) {
18768
+ throw new CircularDependencyError();
18769
+ }
18705
18770
  dependencies.push(range);
18706
18771
  }
18707
18772
  for (const measure of forMeasures) {
@@ -19150,6 +19215,9 @@ const PIVOT_VALUE = {
19150
19215
  };
19151
19216
  }
19152
19217
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19218
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19219
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
19220
+ }
19153
19221
  return pivot.getPivotCellValueAndFormat(_measure, domain);
19154
19222
  },
19155
19223
  };
@@ -19181,6 +19249,9 @@ const PIVOT_HEADER = {
19181
19249
  };
19182
19250
  }
19183
19251
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
19252
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
19253
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
19254
+ }
19184
19255
  const lastNode = domain.at(-1);
19185
19256
  if (lastNode?.field === "measure") {
19186
19257
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -19403,6 +19474,9 @@ function isEmpty(data) {
19403
19474
  return data === undefined || data.value === null;
19404
19475
  }
19405
19476
  const getNeutral = { number: 0, string: "", boolean: false };
19477
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
19478
+ return Math.abs(value1 - value2) < epsilon;
19479
+ }
19406
19480
  const EQ = {
19407
19481
  description: _t("Equal."),
19408
19482
  args: [
@@ -19424,6 +19498,9 @@ const EQ = {
19424
19498
  if (typeof _value2 === "string") {
19425
19499
  _value2 = _value2.toUpperCase();
19426
19500
  }
19501
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
19502
+ return { value: areAlmostEqual(_value1, _value2) };
19503
+ }
19427
19504
  return { value: _value1 === _value2 };
19428
19505
  },
19429
19506
  };
@@ -19463,6 +19540,9 @@ const GT = {
19463
19540
  ],
19464
19541
  compute: function (value1, value2) {
19465
19542
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19543
+ if (typeof v1 === "number" && typeof v2 === "number") {
19544
+ return !areAlmostEqual(v1, v2) && v1 > v2;
19545
+ }
19466
19546
  return v1 > v2;
19467
19547
  });
19468
19548
  },
@@ -19478,6 +19558,9 @@ const GTE = {
19478
19558
  ],
19479
19559
  compute: function (value1, value2) {
19480
19560
  return applyRelationalOperator(value1, value2, (v1, v2) => {
19561
+ if (typeof v1 === "number" && typeof v2 === "number") {
19562
+ return areAlmostEqual(v1, v2) || v1 > v2;
19563
+ }
19481
19564
  return v1 >= v2;
19482
19565
  });
19483
19566
  },
@@ -21084,7 +21167,7 @@ class AbstractComposerStore extends SpreadsheetStore {
21084
21167
  .find((token) => {
21085
21168
  const { xc, sheetName: sheet } = splitReference(token.value);
21086
21169
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
21087
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
21170
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
21088
21171
  return false;
21089
21172
  }
21090
21173
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -22963,6 +23046,7 @@ const CHART_COMMON_OPTIONS = {
22963
23046
  },
22964
23047
  },
22965
23048
  animation: false,
23049
+ events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "mouseup"],
22966
23050
  };
22967
23051
  function chartToImage(runtime, figure, type) {
22968
23052
  // wrap the canvas in a div with a fixed size because chart.js would
@@ -22979,8 +23063,7 @@ function chartToImage(runtime, figure, type) {
22979
23063
  if ("chartJsConfig" in runtime) {
22980
23064
  const config = deepCopy(runtime.chartJsConfig);
22981
23065
  config.plugins = [backgroundColorChartJSPlugin];
22982
- const Chart = getChartJSConstructor();
22983
- const chart = new Chart(canvas, config);
23066
+ const chart = new window.Chart(canvas, config);
22984
23067
  const imgContent = chart.toBase64Image();
22985
23068
  chart.destroy();
22986
23069
  div.remove();
@@ -24546,7 +24629,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
24546
24629
  ({ xc, sheetName } = splitReference(reference));
24547
24630
  let rangeSheetIndex;
24548
24631
  if (sheetName) {
24549
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
24632
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
24550
24633
  if (index < 0) {
24551
24634
  throw new Error("Unable to find a sheet with the name " + sheetName);
24552
24635
  }
@@ -24887,7 +24970,7 @@ function convertFormula(formula, data) {
24887
24970
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
24888
24971
  externalRefId = Number(externalRefId) - 1;
24889
24972
  cellRef = cellRef.replace(/\$/g, "");
24890
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
24973
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
24891
24974
  if (sheetIndex === -1) {
24892
24975
  return match;
24893
24976
  }
@@ -25538,7 +25621,7 @@ function convertPivotTableConfig(pivotTable) {
25538
25621
  */
25539
25622
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
25540
25623
  for (let tableSheet of convertedSheets) {
25541
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
25624
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
25542
25625
  for (let table of tables) {
25543
25626
  const tabRef = table.name + "[";
25544
25627
  for (let sheet of convertedSheets) {
@@ -27973,6 +28056,7 @@ function repairInitialMessages(data, initialMessages) {
27973
28056
  initialMessages = dropCommands(initialMessages, "SORT_CELLS");
27974
28057
  initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
27975
28058
  initialMessages = fixChartDefinitions(data, initialMessages);
28059
+ initialMessages = fixTranslatedDuplicateSheetName(data, initialMessages);
27976
28060
  return initialMessages;
27977
28061
  }
27978
28062
  /**
@@ -28072,6 +28156,40 @@ function fixChartDefinitions(data, initialMessages) {
28072
28156
  }
28073
28157
  return messages;
28074
28158
  }
28159
+ function fixTranslatedDuplicateSheetName(data, initialMessages) {
28160
+ const sheetNames = {};
28161
+ for (const sheet of data.sheets || []) {
28162
+ sheetNames[sheet.id] = sheet.name;
28163
+ }
28164
+ const messages = [];
28165
+ for (const message of initialMessages) {
28166
+ if (message.type === "REMOTE_REVISION") {
28167
+ const commands = [];
28168
+ for (const cmd of message.commands) {
28169
+ switch (cmd.type) {
28170
+ case "DUPLICATE_SHEET":
28171
+ cmd.sheetNameTo =
28172
+ cmd.sheetNameTo ??
28173
+ getDuplicateSheetName(sheetNames[cmd.sheetId], Object.values(sheetNames));
28174
+ break;
28175
+ case "CREATE_SHEET":
28176
+ case "RENAME_SHEET":
28177
+ sheetNames[cmd.sheetId] = cmd.name || getNextSheetName(Object.values(sheetNames));
28178
+ break;
28179
+ }
28180
+ commands.push(cmd);
28181
+ }
28182
+ messages.push({
28183
+ ...message,
28184
+ commands,
28185
+ });
28186
+ }
28187
+ else {
28188
+ messages.push(message);
28189
+ }
28190
+ }
28191
+ return initialMessages;
28192
+ }
28075
28193
  // -----------------------------------------------------------------------------
28076
28194
  // Helpers
28077
28195
  // -----------------------------------------------------------------------------
@@ -28868,12 +28986,11 @@ function canBeLinearChart(definition, dataSets, labelRange, getters) {
28868
28986
  }
28869
28987
  let missingTimeAdapterAlreadyWarned = false;
28870
28988
  function isLuxonTimeAdapterInstalled() {
28871
- const Chart = getChartJSConstructor();
28872
- if (!Chart) {
28989
+ if (!window.Chart) {
28873
28990
  return false;
28874
28991
  }
28875
28992
  // @ts-ignore
28876
- const adapter = new Chart._adapters._date({});
28993
+ const adapter = new window.Chart._adapters._date({});
28877
28994
  const isInstalled = adapter._id === "luxon";
28878
28995
  if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
28879
28996
  missingTimeAdapterAlreadyWarned = true;
@@ -29511,6 +29628,9 @@ const INTERACTIVE_LEGEND_CONFIG = {
29511
29628
  target.style.cursor = "default";
29512
29629
  },
29513
29630
  onClick: (event, legendItem, legend) => {
29631
+ if (event.type !== "click") {
29632
+ return;
29633
+ }
29514
29634
  const index = legendItem.datasetIndex;
29515
29635
  if (!legend.legendItems || index === undefined) {
29516
29636
  return;
@@ -32493,12 +32613,20 @@ class HoveredCellStore extends SpreadsheetStore {
32493
32613
  }
32494
32614
  }
32495
32615
  hover(position) {
32616
+ if (position.col === this.col && position.row === this.row) {
32617
+ return "noStateChange";
32618
+ }
32496
32619
  this.col = position.col;
32497
32620
  this.row = position.row;
32621
+ return;
32498
32622
  }
32499
32623
  clear() {
32624
+ if (this.col === undefined && this.row === undefined) {
32625
+ return "noStateChange";
32626
+ }
32500
32627
  this.col = undefined;
32501
32628
  this.row = undefined;
32629
+ return;
32502
32630
  }
32503
32631
  }
32504
32632
 
@@ -32520,7 +32648,11 @@ class CellPopoverStore extends SpreadsheetStore {
32520
32648
  this.persistentPopover = { col, row, sheetId, type };
32521
32649
  }
32522
32650
  close() {
32651
+ if (!this.persistentPopover) {
32652
+ return "noStateChange";
32653
+ }
32523
32654
  this.persistentPopover = undefined;
32655
+ return;
32524
32656
  }
32525
32657
  get persistentCellPopover() {
32526
32658
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -33410,10 +33542,13 @@ const duplicateSheet = {
33410
33542
  name: _t("Duplicate"),
33411
33543
  execute: (env) => {
33412
33544
  const sheetIdFrom = env.model.getters.getActiveSheetId();
33545
+ const sheetNameFrom = env.model.getters.getSheetName(sheetIdFrom);
33413
33546
  const sheetIdTo = env.model.uuidGenerator.smallUuid();
33547
+ const sheetNameTo = env.model.getters.getDuplicateSheetName(sheetNameFrom);
33414
33548
  env.model.dispatch("DUPLICATE_SHEET", {
33415
33549
  sheetId: sheetIdFrom,
33416
33550
  sheetIdTo,
33551
+ sheetNameTo,
33417
33552
  });
33418
33553
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
33419
33554
  },
@@ -38616,7 +38751,7 @@ class GenericChartConfigPanel extends Component {
38616
38751
  const cancelledReasons = [
38617
38752
  ...(this.state.datasetDispatchResult?.reasons || []),
38618
38753
  ...(this.state.labelsDispatchResult?.reasons || []),
38619
- ];
38754
+ ].filter((reason) => reason !== "NoChanges" /* CommandResult.NoChanges */);
38620
38755
  return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
38621
38756
  }
38622
38757
  get isDatasetInvalid() {
@@ -39973,10 +40108,18 @@ class GaugeChartConfigPanel extends Component {
39973
40108
  }
39974
40109
 
39975
40110
  class DOMFocusableElementStore {
39976
- mutators = ["setFocusableElement"];
40111
+ mutators = ["setFocusableElement", "focus"];
39977
40112
  focusableElement = undefined;
39978
40113
  setFocusableElement(element) {
39979
40114
  this.focusableElement = element;
40115
+ return "noStateChange";
40116
+ }
40117
+ focus() {
40118
+ if (this.focusableElement === document.activeElement) {
40119
+ return "noStateChange";
40120
+ }
40121
+ this.focusableElement?.focus();
40122
+ return;
39980
40123
  }
39981
40124
  }
39982
40125
 
@@ -40564,7 +40707,7 @@ class Composer extends Component {
40564
40707
  if (document.activeElement === this.contentHelper.el &&
40565
40708
  this.props.composerStore.editionMode === "inactive" &&
40566
40709
  !this.props.isDefaultFocus) {
40567
- this.DOMFocusableElementStore.focusableElement?.focus();
40710
+ this.DOMFocusableElementStore.focus();
40568
40711
  }
40569
40712
  });
40570
40713
  useEffect(() => {
@@ -40838,6 +40981,13 @@ class Composer extends Component {
40838
40981
  openAssistant() {
40839
40982
  this.assistant.forcedClosed = false;
40840
40983
  }
40984
+ onWheel(event) {
40985
+ // detect if scrollbar is available
40986
+ if (this.composerRef.el &&
40987
+ this.composerRef.el.scrollHeight > this.composerRef.el.clientHeight) {
40988
+ event.stopPropagation();
40989
+ }
40990
+ }
40841
40991
  // ---------------------------------------------------------------------------
40842
40992
  // Private
40843
40993
  // ---------------------------------------------------------------------------
@@ -46314,8 +46464,8 @@ function compareDimensionValues(dimension, a, b) {
46314
46464
 
46315
46465
  const NULL_SYMBOL = Symbol("NULL");
46316
46466
  function createDate(dimension, value, locale) {
46317
- const granularity = dimension.granularity;
46318
- if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
46467
+ const granularity = dimension.granularity || "month";
46468
+ if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
46319
46469
  throw new Error(`Unknown date granularity: ${granularity}`);
46320
46470
  }
46321
46471
  const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
@@ -46334,6 +46484,9 @@ function createDate(dimension, value, locale) {
46334
46484
  case "month_number":
46335
46485
  number = date.getMonth() + 1;
46336
46486
  break;
46487
+ case "month":
46488
+ number = Math.floor(toNumber(value, locale));
46489
+ break;
46337
46490
  case "iso_week_number":
46338
46491
  number = date.getIsoWeek();
46339
46492
  break;
@@ -46427,6 +46580,10 @@ const MAP_VALUE_DIMENSION_DATE = {
46427
46580
  set: new Set(),
46428
46581
  values: {},
46429
46582
  },
46583
+ month: {
46584
+ set: new Set(),
46585
+ values: {},
46586
+ },
46430
46587
  iso_week_number: {
46431
46588
  set: new Set(),
46432
46589
  values: {},
@@ -46637,7 +46794,7 @@ class SpreadsheetPivot {
46637
46794
  const cells = this.filterDataEntriesFromDomain(this.dataEntries, domain);
46638
46795
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
46639
46796
  if (dimension.type === "datetime") {
46640
- const adapter = pivotTimeAdapter(dimension.granularity);
46797
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
46641
46798
  return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
46642
46799
  }
46643
46800
  if (!finalCell) {
@@ -46755,7 +46912,7 @@ class SpreadsheetPivot {
46755
46912
  if (nonEmptyCells.length === 0) {
46756
46913
  return "integer";
46757
46914
  }
46758
- if (nonEmptyCells.every((cell) => cell.format && isDateTimeFormat(cell.format))) {
46915
+ if (nonEmptyCells.every((cell) => cell.type === CellValueType.number && cell.format && isDateTimeFormat(cell.format))) {
46759
46916
  return "datetime";
46760
46917
  }
46761
46918
  if (nonEmptyCells.every((cell) => cell.type === CellValueType.boolean)) {
@@ -46834,7 +46991,12 @@ class SpreadsheetPivot {
46834
46991
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
46835
46992
  }
46836
46993
  else {
46837
- entry[field.name] = cell;
46994
+ if (field.type === "char") {
46995
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
46996
+ }
46997
+ else {
46998
+ entry[field.name] = cell;
46999
+ }
46838
47000
  }
46839
47001
  }
46840
47002
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -46848,7 +47010,7 @@ class SpreadsheetPivot {
46848
47010
  for (const entry of dataEntries) {
46849
47011
  for (const dimension of dateDimensions) {
46850
47012
  const value = createDate(dimension, entry[dimension.fieldName]?.value || null, this.getters.getLocale());
46851
- const adapter = pivotTimeAdapter(dimension.granularity);
47013
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
46852
47014
  const { format, value: valueToFormat } = adapter.toValueAndFormat(value, locale);
46853
47015
  entry[dimension.nameWithGranularity] = {
46854
47016
  value,
@@ -46868,6 +47030,7 @@ const dateGranularities = [
46868
47030
  "year",
46869
47031
  "quarter_number",
46870
47032
  "month_number",
47033
+ "month",
46871
47034
  "iso_week_number",
46872
47035
  "day_of_month",
46873
47036
  "day",
@@ -47118,7 +47281,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
47118
47281
  : this.datetimeGranularities);
47119
47282
  }
47120
47283
  for (const field of dateFields) {
47121
- granularitiesPerFields[field.fieldName].delete(field.granularity);
47284
+ granularitiesPerFields[field.fieldName].delete(field.granularity || "month");
47122
47285
  }
47123
47286
  return granularitiesPerFields;
47124
47287
  }
@@ -49288,6 +49451,8 @@ class GridComposer extends Component {
49288
49451
  }
49289
49452
  get composerProps() {
49290
49453
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
49454
+ // Remove the wrapper border width
49455
+ const maxHeight = this.props.gridDims.height - this.rect.y - 2 * COMPOSER_BORDER_WIDTH;
49291
49456
  return {
49292
49457
  rect: { ...this.rect },
49293
49458
  delimitation: {
@@ -49305,6 +49470,7 @@ class GridComposer extends Component {
49305
49470
  }),
49306
49471
  onInputContextMenu: this.props.onInputContextMenu,
49307
49472
  composerStore: this.composerStore,
49473
+ inputStyle: `max-height: ${maxHeight}px;`,
49308
49474
  };
49309
49475
  }
49310
49476
  get containerStyle() {
@@ -51968,10 +52134,6 @@ function useGridDrawing(refName, model, canvasSize) {
51968
52134
  ctx.scale(dpr, dpr);
51969
52135
  for (const layer of OrderedLayers()) {
51970
52136
  model.drawLayer(renderingContext, layer);
51971
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
51972
- // it does not mutate anything. Most importantly it's used
51973
- // during rendering. Invoking a mutator during rendering would
51974
- // trigger another rendering, ultimately resulting in an infinite loop.
51975
52137
  rendererStore.drawLayer(renderingContext, layer);
51976
52138
  }
51977
52139
  }
@@ -52662,7 +52824,7 @@ class Grid extends Component {
52662
52824
  this.cellPopovers = useStore(CellPopoverStore);
52663
52825
  useEffect(() => {
52664
52826
  if (!this.sidePanel.isOpen) {
52665
- this.DOMFocusableElementStore.focusableElement?.focus();
52827
+ this.DOMFocusableElementStore.focus();
52666
52828
  }
52667
52829
  }, () => [this.sidePanel.isOpen]);
52668
52830
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -52872,7 +53034,7 @@ class Grid extends Component {
52872
53034
  focusDefaultElement() {
52873
53035
  if (!this.env.model.getters.getSelectedFigureId() &&
52874
53036
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
52875
- this.DOMFocusableElementStore.focusableElement?.focus();
53037
+ this.DOMFocusableElementStore.focus();
52876
53038
  }
52877
53039
  }
52878
53040
  get gridEl() {
@@ -53217,6 +53379,322 @@ class Grid extends Component {
53217
53379
  }
53218
53380
  }
53219
53381
 
53382
+ css /* scss */ `
53383
+ .o_pivot_html_renderer {
53384
+ width: 100%;
53385
+ border-collapse: collapse;
53386
+
53387
+ &:hover {
53388
+ cursor: pointer;
53389
+ }
53390
+
53391
+ td,
53392
+ th {
53393
+ border: 1px solid #dee2e6;
53394
+ background-color: #fff;
53395
+ padding: 0.3rem;
53396
+ white-space: nowrap;
53397
+
53398
+ &:hover {
53399
+ filter: brightness(0.9);
53400
+ }
53401
+ }
53402
+
53403
+ td {
53404
+ text-align: right;
53405
+ }
53406
+
53407
+ th {
53408
+ background-color: #f5f5f5;
53409
+ font-weight: bold;
53410
+ color: black;
53411
+ }
53412
+
53413
+ .o_missing_value {
53414
+ color: #46646d;
53415
+ background: #e7f2f6;
53416
+ }
53417
+ }
53418
+ `;
53419
+ class PivotHTMLRenderer extends Component {
53420
+ static template = "o_spreadsheet.PivotHTMLRenderer";
53421
+ static components = { Checkbox };
53422
+ static props = {
53423
+ pivotId: String,
53424
+ onCellClicked: Function,
53425
+ };
53426
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
53427
+ data = {
53428
+ columns: [],
53429
+ rows: [],
53430
+ values: [],
53431
+ };
53432
+ state = useState({
53433
+ showMissingValuesOnly: false,
53434
+ });
53435
+ setup() {
53436
+ const table = this.pivot.getTableStructure();
53437
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
53438
+ this.data = {
53439
+ columns: this._buildColHeaders(formulaId, table),
53440
+ rows: this._buildRowHeaders(formulaId, table),
53441
+ values: this._buildValues(formulaId, table),
53442
+ };
53443
+ }
53444
+ get tracker() {
53445
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
53446
+ }
53447
+ // ---------------------------------------------------------------------
53448
+ // Missing values building
53449
+ // ---------------------------------------------------------------------
53450
+ /**
53451
+ * Retrieve the data to display in the Pivot Table
53452
+ * In the case when showMissingValuesOnly is false, the returned value
53453
+ * is the complete data
53454
+ * In the case when showMissingValuesOnly is true, the returned value is
53455
+ * the data which contains only missing values in the rows and cols. In
53456
+ * the rows, we also return the parent rows of rows which contains missing
53457
+ * values, to give context to the user.
53458
+ *
53459
+ */
53460
+ getTableData() {
53461
+ if (!this.state.showMissingValuesOnly) {
53462
+ return this.data;
53463
+ }
53464
+ const colIndexes = this.getColumnsIndexes();
53465
+ const rowIndexes = this.getRowsIndexes();
53466
+ const columns = this.buildColumnsMissing(colIndexes);
53467
+ const rows = this.buildRowsMissing(rowIndexes);
53468
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
53469
+ return { columns, rows, values };
53470
+ }
53471
+ /**
53472
+ * Retrieve the parents of the given row
53473
+ * ex:
53474
+ * Australia
53475
+ * January
53476
+ * February
53477
+ * The parent of "January" is "Australia"
53478
+ */
53479
+ addRecursiveRow(index) {
53480
+ const rows = this.pivot.getTableStructure().rows;
53481
+ const row = [...rows[index].values];
53482
+ if (row.length <= 1) {
53483
+ return [index];
53484
+ }
53485
+ row.pop();
53486
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
53487
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
53488
+ }
53489
+ /**
53490
+ * Create the columns to be used, based on the indexes of the columns in
53491
+ * which a missing value is present
53492
+ *
53493
+ */
53494
+ buildColumnsMissing(indexes) {
53495
+ // columnsMap explode the columns in an array of array of the same
53496
+ // size with the index of each column, repeated 'span' times.
53497
+ // ex:
53498
+ // | A | B |
53499
+ // | 1 | 2 | 3 |
53500
+ // => [
53501
+ // [0, 0, 1]
53502
+ // [0, 1, 2]
53503
+ // ]
53504
+ const columnsMap = [];
53505
+ for (const column of this.data.columns) {
53506
+ const columnMap = [];
53507
+ for (const index in column) {
53508
+ for (let i = 0; i < column[index].span; i++) {
53509
+ columnMap.push(parseInt(index, 10));
53510
+ }
53511
+ }
53512
+ columnsMap.push(columnMap);
53513
+ }
53514
+ // Remove the columns that are not present in indexes
53515
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
53516
+ if (!indexes.includes(i)) {
53517
+ for (const columnMap of columnsMap) {
53518
+ columnMap.splice(i, 1);
53519
+ }
53520
+ }
53521
+ }
53522
+ // Build the columns
53523
+ const columns = [];
53524
+ for (const mapIndex in columnsMap) {
53525
+ const column = [];
53526
+ let index = undefined;
53527
+ let span = 1;
53528
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
53529
+ if (index !== columnsMap[mapIndex][i]) {
53530
+ if (index !== undefined) {
53531
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53532
+ }
53533
+ index = columnsMap[mapIndex][i];
53534
+ span = 1;
53535
+ }
53536
+ else {
53537
+ span++;
53538
+ }
53539
+ }
53540
+ if (index !== undefined) {
53541
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
53542
+ }
53543
+ columns.push(column);
53544
+ }
53545
+ return columns;
53546
+ }
53547
+ /**
53548
+ * Create the rows to be used, based on the indexes of the rows in
53549
+ * which a missing value is present.
53550
+ */
53551
+ buildRowsMissing(indexes) {
53552
+ return indexes.map((index) => this.data.rows[index]);
53553
+ }
53554
+ /**
53555
+ * Create the value to be used, based on the indexes of the columns and
53556
+ * rows in which a missing value is present.
53557
+ */
53558
+ buildValuesMissing(colIndexes, rowIndexes) {
53559
+ const values = colIndexes.map(() => []);
53560
+ for (const row of rowIndexes) {
53561
+ for (const col in colIndexes) {
53562
+ values[col].push(this.data.values[colIndexes[col]][row]);
53563
+ }
53564
+ }
53565
+ return values;
53566
+ }
53567
+ getColumnsIndexes() {
53568
+ const indexes = new Set();
53569
+ for (let i = 0; i < this.data.columns.length; i++) {
53570
+ const exploded = [];
53571
+ for (let y = 0; y < this.data.columns[i].length; y++) {
53572
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
53573
+ exploded.push(this.data.columns[i][y]);
53574
+ }
53575
+ }
53576
+ for (let y = 0; y < exploded.length; y++) {
53577
+ if (exploded[y].isMissing) {
53578
+ indexes.add(y);
53579
+ }
53580
+ }
53581
+ }
53582
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
53583
+ const values = this.data.values[i];
53584
+ if (values.find((x) => x.isMissing)) {
53585
+ indexes.add(i);
53586
+ }
53587
+ }
53588
+ return Array.from(indexes).sort((a, b) => a - b);
53589
+ }
53590
+ getRowsIndexes() {
53591
+ const rowIndexes = new Set();
53592
+ for (let i = 0; i < this.data.rows.length; i++) {
53593
+ if (this.data.rows[i].isMissing) {
53594
+ rowIndexes.add(i);
53595
+ }
53596
+ for (const col of this.data.values) {
53597
+ if (col[i].isMissing) {
53598
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
53599
+ }
53600
+ }
53601
+ }
53602
+ return Array.from(rowIndexes).sort((a, b) => a - b);
53603
+ }
53604
+ // ---------------------------------------------------------------------
53605
+ // Data table creation
53606
+ // ---------------------------------------------------------------------
53607
+ _buildColHeaders(id, table) {
53608
+ const headers = [];
53609
+ for (const row of table.columns) {
53610
+ const current = [];
53611
+ for (const cell of row) {
53612
+ const args = [];
53613
+ for (let i = 0; i < cell.fields.length; i++) {
53614
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
53615
+ }
53616
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53617
+ const locale = this.env.model.getters.getLocale();
53618
+ if (domain.at(-1)?.field === "measure") {
53619
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
53620
+ current.push({
53621
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53622
+ value: formatValue(value, { format, locale }),
53623
+ span: cell.width,
53624
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53625
+ });
53626
+ }
53627
+ else {
53628
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53629
+ current.push({
53630
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53631
+ value: formatValue(value, { format, locale }),
53632
+ span: cell.width,
53633
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53634
+ });
53635
+ }
53636
+ }
53637
+ headers.push(current);
53638
+ }
53639
+ const last = headers[headers.length - 1];
53640
+ headers[headers.length - 1] = last.map((cell) => {
53641
+ if (!cell.isMissing) {
53642
+ cell.style = "color: #756f6f;";
53643
+ }
53644
+ return cell;
53645
+ });
53646
+ return headers;
53647
+ }
53648
+ _buildRowHeaders(id, table) {
53649
+ const headers = [];
53650
+ for (const row of table.rows) {
53651
+ const args = [];
53652
+ for (let i = 0; i < row.fields.length; i++) {
53653
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53654
+ }
53655
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53656
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
53657
+ const locale = this.env.model.getters.getLocale();
53658
+ const cell = {
53659
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
53660
+ value: formatValue(value, { format, locale }),
53661
+ isMissing: !this.tracker?.isHeaderPresent(domain),
53662
+ };
53663
+ if (row.indent > 1) {
53664
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
53665
+ }
53666
+ headers.push(cell);
53667
+ }
53668
+ return headers;
53669
+ }
53670
+ _buildValues(id, table) {
53671
+ const values = [];
53672
+ for (const col of table.columns.at(-1) || []) {
53673
+ const current = [];
53674
+ const measure = toString(col.values[col.values.length - 1]);
53675
+ for (const row of table.rows) {
53676
+ const args = [];
53677
+ for (let i = 0; i < row.fields.length; i++) {
53678
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
53679
+ }
53680
+ for (let i = 0; i < col.fields.length - 1; i++) {
53681
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
53682
+ }
53683
+ const domain = this.pivot.parseArgsToPivotDomain(args);
53684
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
53685
+ const locale = this.env.model.getters.getLocale();
53686
+ current.push({
53687
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
53688
+ value: formatValue(value, { format, locale }),
53689
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
53690
+ });
53691
+ }
53692
+ values.push(current);
53693
+ }
53694
+ return values;
53695
+ }
53696
+ }
53697
+
53220
53698
  /**
53221
53699
  * BasePlugin
53222
53700
  *
@@ -54567,7 +55045,7 @@ class ChartPlugin extends CorePlugin {
54567
55045
  case "CREATE_CHART":
54568
55046
  return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartDuplicate));
54569
55047
  case "UPDATE_CHART":
54570
- return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists));
55048
+ return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists, this.checkChartChanged));
54571
55049
  default:
54572
55050
  return "Success" /* CommandResult.Success */;
54573
55051
  }
@@ -54722,9 +55200,12 @@ class ChartPlugin extends CorePlugin {
54722
55200
  : "Success" /* CommandResult.Success */;
54723
55201
  }
54724
55202
  checkChartExists(cmd) {
54725
- return this.getters.getFigureSheetId(cmd.id)
54726
- ? "Success" /* CommandResult.Success */
54727
- : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
55203
+ return this.isChartDefined(cmd.id) ? "Success" /* CommandResult.Success */ : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
55204
+ }
55205
+ checkChartChanged(cmd) {
55206
+ return deepEquals(this.getChartDefinition(cmd.id), cmd.definition)
55207
+ ? "NoChanges" /* CommandResult.NoChanges */
55208
+ : "Success" /* CommandResult.Success */;
54728
55209
  }
54729
55210
  }
54730
55211
 
@@ -56673,7 +57154,7 @@ class RangeAdapter {
56673
57154
  if (range.sheetId === cmd.sheetId) {
56674
57155
  return { changeType: "CHANGE", range };
56675
57156
  }
56676
- if (cmd.name && range.invalidSheetName === cmd.name) {
57157
+ if (isSheetNameEqual(range.invalidSheetName, cmd.name)) {
56677
57158
  const invalidSheetName = undefined;
56678
57159
  const sheetId = cmd.sheetId;
56679
57160
  const newRange = range.clone({ sheetId, invalidSheetName });
@@ -57043,6 +57524,7 @@ class SheetPlugin extends CorePlugin {
57043
57524
  "getCommandZones",
57044
57525
  "getUnboundedZone",
57045
57526
  "checkElementsIncludeAllNonFrozenHeaders",
57527
+ "getDuplicateSheetName",
57046
57528
  ];
57047
57529
  sheetIdsMapName = {};
57048
57530
  orderedSheetIds = [];
@@ -57067,7 +57549,11 @@ class SheetPlugin extends CorePlugin {
57067
57549
  return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
57068
57550
  }
57069
57551
  case "DUPLICATE_SHEET": {
57070
- return this.sheets[cmd.sheetIdTo] ? "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */ : "Success" /* CommandResult.Success */;
57552
+ if (this.sheets[cmd.sheetIdTo])
57553
+ return "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */;
57554
+ if (this.orderedSheetIds.map(this.getSheetName.bind(this)).includes(cmd.sheetNameTo))
57555
+ return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
57556
+ return "Success" /* CommandResult.Success */;
57071
57557
  }
57072
57558
  case "MOVE_SHEET":
57073
57559
  try {
@@ -57144,7 +57630,7 @@ class SheetPlugin extends CorePlugin {
57144
57630
  this.showSheet(cmd.sheetId);
57145
57631
  break;
57146
57632
  case "DUPLICATE_SHEET":
57147
- this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo);
57633
+ this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo, cmd.sheetNameTo);
57148
57634
  break;
57149
57635
  case "DELETE_SHEET":
57150
57636
  this.deleteSheet(this.sheets[cmd.sheetId]);
@@ -57285,7 +57771,7 @@ class SheetPlugin extends CorePlugin {
57285
57771
  if (name) {
57286
57772
  const unquotedName = getUnquotedSheetName(name);
57287
57773
  for (const key in this.sheetIdsMapName) {
57288
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
57774
+ if (isSheetNameEqual(key, unquotedName)) {
57289
57775
  return this.sheetIdsMapName[key];
57290
57776
  }
57291
57777
  }
@@ -57351,10 +57837,7 @@ class SheetPlugin extends CorePlugin {
57351
57837
  }
57352
57838
  getNextSheetName(baseName = "Sheet") {
57353
57839
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
57354
- return getUniqueText(baseName, names, {
57355
- compute: (name, i) => `${name}${i}`,
57356
- computeFirstOne: true,
57357
- });
57840
+ return getNextSheetName(names, baseName);
57358
57841
  }
57359
57842
  getSheetSize(sheetId) {
57360
57843
  return {
@@ -57536,7 +58019,7 @@ class SheetPlugin extends CorePlugin {
57536
58019
  }
57537
58020
  const { orderedSheetIds, sheets } = this;
57538
58021
  const name = cmd.name && cmd.name.trim().toLowerCase();
57539
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
58022
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
57540
58023
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
57541
58024
  }
57542
58025
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -57600,9 +58083,8 @@ class SheetPlugin extends CorePlugin {
57600
58083
  showSheet(sheetId) {
57601
58084
  this.history.update("sheets", sheetId, "isVisible", true);
57602
58085
  }
57603
- duplicateSheet(fromId, toId) {
58086
+ duplicateSheet(fromId, toId, toName) {
57604
58087
  const sheet = this.getSheet(fromId);
57605
- const toName = this.getDuplicateSheetName(sheet.name);
57606
58088
  const newSheet = deepCopy(sheet);
57607
58089
  newSheet.id = toId;
57608
58090
  newSheet.name = toName;
@@ -57635,8 +58117,7 @@ class SheetPlugin extends CorePlugin {
57635
58117
  }
57636
58118
  getDuplicateSheetName(sheetName) {
57637
58119
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
57638
- const baseName = _t("Copy of %s", sheetName);
57639
- return getUniqueText(baseName.toString(), names);
58120
+ return getDuplicateSheetName(sheetName, names);
57640
58121
  }
57641
58122
  deleteSheet(sheet) {
57642
58123
  const name = sheet.name;
@@ -60431,8 +60912,8 @@ class SpreadingRelation {
60431
60912
  const EMPTY_ARRAY = [];
60432
60913
 
60433
60914
  const MAX_ITERATION = 30;
60434
- const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
60435
- const EMPTY_CELL = createEvaluatedCell({ value: null });
60915
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
60916
+ const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
60436
60917
  class Evaluator {
60437
60918
  context;
60438
60919
  getters;
@@ -66431,6 +66912,55 @@ class HistoryPlugin extends UIPlugin {
66431
66912
  }
66432
66913
  }
66433
66914
 
66915
+ class PivotPresenceTracker {
66916
+ trackedValues = new Set();
66917
+ domainToArray(domain) {
66918
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
66919
+ }
66920
+ isValuePresent(measure, domain) {
66921
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66922
+ return this.trackedValues.has(key);
66923
+ }
66924
+ isHeaderPresent(domain) {
66925
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66926
+ return this.trackedValues.has(key);
66927
+ }
66928
+ trackValue(measure, domain) {
66929
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
66930
+ this.trackedValues.add(key);
66931
+ }
66932
+ trackHeader(domain) {
66933
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
66934
+ this.trackedValues.add(key);
66935
+ }
66936
+ }
66937
+
66938
+ class PivotPresencePlugin extends UIPlugin {
66939
+ static getters = ["getPivotPresenceTracker"];
66940
+ trackPresencePivotId;
66941
+ tracker;
66942
+ handle(cmd) {
66943
+ switch (cmd.type) {
66944
+ case "PIVOT_START_PRESENCE_TRACKING":
66945
+ this.tracker = new PivotPresenceTracker();
66946
+ this.trackPresencePivotId = cmd.pivotId;
66947
+ break;
66948
+ case "PIVOT_STOP_PRESENCE_TRACKING":
66949
+ this.trackPresencePivotId = undefined;
66950
+ break;
66951
+ }
66952
+ }
66953
+ getPivotPresenceTracker(pivotId) {
66954
+ if (this.trackPresencePivotId !== pivotId) {
66955
+ return undefined;
66956
+ }
66957
+ if (!this.tracker) {
66958
+ throw new Error("Tracker not initialized");
66959
+ }
66960
+ return this.tracker;
66961
+ }
66962
+ }
66963
+
66434
66964
  class SplitToColumnsPlugin extends UIPlugin {
66435
66965
  static getters = ["getAutomaticSeparator"];
66436
66966
  allowDispatch(cmd) {
@@ -69181,6 +69711,7 @@ const featurePluginRegistry = new Registry()
69181
69711
  .add("automatic_sum", AutomaticSumPlugin)
69182
69712
  .add("format", FormatPlugin)
69183
69713
  .add("insert_pivot", InsertPivotPlugin)
69714
+ .add("pivot_presence", PivotPresencePlugin)
69184
69715
  .add("split_to_columns", SplitToColumnsPlugin)
69185
69716
  .add("collaborative", CollaborativePlugin)
69186
69717
  .add("history", HistoryPlugin)
@@ -69561,11 +70092,11 @@ class BottomBarSheet extends Component {
69561
70092
  if (ev.key === "Enter") {
69562
70093
  ev.preventDefault();
69563
70094
  this.stopEdition();
69564
- this.DOMFocusableElementStore.focusableElement?.focus();
70095
+ this.DOMFocusableElementStore.focus();
69565
70096
  }
69566
70097
  if (ev.key === "Escape") {
69567
70098
  this.cancelEdition();
69568
- this.DOMFocusableElementStore.focusableElement?.focus();
70099
+ this.DOMFocusableElementStore.focus();
69569
70100
  }
69570
70101
  }
69571
70102
  onMouseEventSheetName(ev) {
@@ -71737,11 +72268,13 @@ class Spreadsheet extends Component {
71737
72268
  this.checkViewportSize();
71738
72269
  stores.on("store-updated", this, render);
71739
72270
  resizeObserver.observe(this.spreadsheetRef.el);
72271
+ registerChartJSExtensions();
71740
72272
  });
71741
72273
  onWillUnmount(() => {
71742
72274
  this.unbindModelEvents();
71743
72275
  stores.off("store-updated", this);
71744
72276
  resizeObserver.disconnect();
72277
+ unregisterChartJsExtensions();
71745
72278
  });
71746
72279
  onPatched(() => {
71747
72280
  this.checkViewportSize();
@@ -76174,6 +76707,7 @@ const components = {
76174
76707
  PivotDimensionOrder,
76175
76708
  PivotDimension,
76176
76709
  PivotLayoutConfigurator,
76710
+ PivotHTMLRenderer,
76177
76711
  PivotDeferUpdate,
76178
76712
  PivotTitleSection,
76179
76713
  CogWheelMenu,
@@ -76223,6 +76757,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
76223
76757
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
76224
76758
 
76225
76759
 
76226
- __info__.version = "18.2.10";
76227
- __info__.date = "2025-05-02T12:34:39.632Z";
76228
- __info__.hash = "e8ff3fc";
76760
+ __info__.version = "18.2.12";
76761
+ __info__.date = "2025-05-13T17:52:23.989Z";
76762
+ __info__.hash = "ba2ba9b";