@odoo/o-spreadsheet 18.3.1 → 18.3.3

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.3.1
6
- * @date 2025-05-02T12:33:27.384Z
7
- * @hash 7b9574b
5
+ * @version 18.3.3
6
+ * @date 2025-05-13T17:54:43.312Z
7
+ * @hash b79924a
8
8
  */
9
9
 
10
10
  'use strict';
@@ -3321,7 +3321,7 @@ function isDateAfter(date, dateAfter) {
3321
3321
  */
3322
3322
  const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSeparator) {
3323
3323
  decimalSeparator = escapeRegExp(decimalSeparator);
3324
- return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3324
+ return new RegExp(`(?:^-?\\d+(?:${decimalSeparator}?\\d*(?:e(\\+|-)?\\d+)?)?|^-?${decimalSeparator}\\d+)(?!\\w|!)`);
3325
3325
  });
3326
3326
  const getNumberRegex = memoize(function getNumberRegex(locale) {
3327
3327
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
@@ -6002,6 +6002,67 @@ function scrollDelay(value) {
6002
6002
  return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
6003
6003
  }
6004
6004
 
6005
+ function createDefaultRows(rowNumber) {
6006
+ const rows = [];
6007
+ for (let i = 0; i < rowNumber; i++) {
6008
+ const row = {
6009
+ cells: {},
6010
+ };
6011
+ rows.push(row);
6012
+ }
6013
+ return rows;
6014
+ }
6015
+ function moveHeaderIndexesOnHeaderAddition(indexHeaderAdded, numberAdded, headers) {
6016
+ return headers.map((header) => {
6017
+ if (header >= indexHeaderAdded) {
6018
+ return header + numberAdded;
6019
+ }
6020
+ return header;
6021
+ });
6022
+ }
6023
+ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6024
+ deletedHeaders = [...deletedHeaders].sort((a, b) => b - a);
6025
+ return headers
6026
+ .map((header) => {
6027
+ for (const deletedHeader of deletedHeaders) {
6028
+ if (header > deletedHeader) {
6029
+ header--;
6030
+ }
6031
+ else if (header === deletedHeader) {
6032
+ return undefined;
6033
+ }
6034
+ }
6035
+ return header;
6036
+ })
6037
+ .filter(isDefined);
6038
+ }
6039
+ function getNextSheetName(existingNames, baseName = "Sheet") {
6040
+ let i = 1;
6041
+ let name = `${baseName}${i}`;
6042
+ while (existingNames.includes(name)) {
6043
+ name = `${baseName}${i}`;
6044
+ i++;
6045
+ }
6046
+ return name;
6047
+ }
6048
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
6049
+ let i = 1;
6050
+ const baseName = _t("Copy of %s", nameToDuplicate);
6051
+ let name = baseName.toString();
6052
+ while (existingNames.includes(name)) {
6053
+ name = `${baseName} (${i})`;
6054
+ i++;
6055
+ }
6056
+ return name;
6057
+ }
6058
+ function isSheetNameEqual(name1, name2) {
6059
+ if (name1 === undefined || name2 === undefined) {
6060
+ return false;
6061
+ }
6062
+ return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6063
+ getUnquotedSheetName(name2.trim().toUpperCase()));
6064
+ }
6065
+
6005
6066
  function createRange(args, getSheetSize) {
6006
6067
  const unboundedZone = args.zone;
6007
6068
  const zone = boundUnboundedZone(unboundedZone, getSheetSize(args.sheetId));
@@ -6292,7 +6353,7 @@ function getApplyRangeChangeRemoveColRow(cmd) {
6292
6353
  elements.sort((a, b) => b - a);
6293
6354
  const groups = groupConsecutive(elements);
6294
6355
  return (range) => {
6295
- if (range.sheetId !== cmd.sheetId) {
6356
+ if (!isSheetNameEqual(range.sheetId, cmd.sheetId)) {
6296
6357
  return { changeType: "NONE" };
6297
6358
  }
6298
6359
  let newRange = range;
@@ -6499,41 +6560,6 @@ function fuzzyLookup(pattern, list, fn) {
6499
6560
  return results.map((r) => r.elem);
6500
6561
  }
6501
6562
 
6502
- function createDefaultRows(rowNumber) {
6503
- const rows = [];
6504
- for (let i = 0; i < rowNumber; i++) {
6505
- const row = {
6506
- cells: {},
6507
- };
6508
- rows.push(row);
6509
- }
6510
- return rows;
6511
- }
6512
- function moveHeaderIndexesOnHeaderAddition(indexHeaderAdded, numberAdded, headers) {
6513
- return headers.map((header) => {
6514
- if (header >= indexHeaderAdded) {
6515
- return header + numberAdded;
6516
- }
6517
- return header;
6518
- });
6519
- }
6520
- function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6521
- deletedHeaders = [...deletedHeaders].sort((a, b) => b - a);
6522
- return headers
6523
- .map((header) => {
6524
- for (const deletedHeader of deletedHeaders) {
6525
- if (header > deletedHeader) {
6526
- header--;
6527
- }
6528
- else if (header === deletedHeader) {
6529
- return undefined;
6530
- }
6531
- }
6532
- return header;
6533
- })
6534
- .filter(isDefined);
6535
- }
6536
-
6537
6563
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
6538
6564
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
6539
6565
  }
@@ -8275,6 +8301,25 @@ const monthNumberAdapter = {
8275
8301
  return `${normalizedValue}`;
8276
8302
  },
8277
8303
  };
8304
+ /**
8305
+ * normalizes month number + year
8306
+ */
8307
+ const monthAdapter = {
8308
+ normalizeFunctionValue(value) {
8309
+ const date = toNumber(value, DEFAULT_LOCALE);
8310
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
8311
+ },
8312
+ toValueAndFormat(normalizedValue) {
8313
+ return {
8314
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
8315
+ format: "mmmm yyyy",
8316
+ };
8317
+ },
8318
+ toFunctionValue(normalizedValue) {
8319
+ const jsDate = toJsDate(normalizedValue, DEFAULT_LOCALE);
8320
+ return `DATE(${jsDate.getFullYear()},${jsDate.getMonth() + 1},1)`;
8321
+ },
8322
+ };
8278
8323
  /**
8279
8324
  * normalizes quarter number
8280
8325
  */
@@ -8410,6 +8455,7 @@ pivotTimeAdapterRegistry
8410
8455
  .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
8411
8456
  .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
8412
8457
  .add("month_number", nullHandlerDecorator(monthNumberAdapter))
8458
+ .add("month", nullHandlerDecorator(monthAdapter))
8413
8459
  .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
8414
8460
  .add("day_of_week", nullHandlerDecorator(dayOfWeekAdapter))
8415
8461
  .add("hour_number", nullHandlerDecorator(hourNumberAdapter))
@@ -8426,10 +8472,9 @@ const AGGREGATOR_NAMES = {
8426
8472
  avg: _t("Average"),
8427
8473
  sum: _t("Sum"),
8428
8474
  };
8429
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8430
8475
  const AGGREGATORS_BY_FIELD_TYPE = {
8431
- integer: NUMBER_CHAR_AGGREGATORS,
8432
- char: NUMBER_CHAR_AGGREGATORS,
8476
+ integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8477
+ char: ["count_distinct", "count"],
8433
8478
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8434
8479
  datetime: ["max", "min", "count_distinct", "count"],
8435
8480
  };
@@ -8590,10 +8635,7 @@ function toNormalizedPivotValue(dimension, groupValue) {
8590
8635
  return normalizer(groupValueString, dimension.granularity);
8591
8636
  }
8592
8637
  function normalizeDateTime(value, granularity) {
8593
- if (!granularity) {
8594
- throw new Error("Missing granularity");
8595
- }
8596
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8638
+ return pivotTimeAdapter(granularity ?? "month").normalizeFunctionValue(value);
8597
8639
  }
8598
8640
  function toFunctionPivotValue(value, dimension) {
8599
8641
  if (value === null) {
@@ -8605,10 +8647,7 @@ function toFunctionPivotValue(value, dimension) {
8605
8647
  return pivotToFunctionValueRegistry.get(dimension.type)(value, dimension.granularity);
8606
8648
  }
8607
8649
  function toFunctionValueDateTime(value, granularity) {
8608
- if (!granularity) {
8609
- throw new Error("Missing granularity");
8610
- }
8611
- return pivotTimeAdapter(granularity).toFunctionValue(value);
8650
+ return pivotTimeAdapter(granularity ?? "month").toFunctionValue(value);
8612
8651
  }
8613
8652
  const pivotNormalizationValueRegistry = new Registry();
8614
8653
  pivotNormalizationValueRegistry
@@ -9803,7 +9842,10 @@ function proxifyStoreMutation(store, callback) {
9803
9842
  const functionProxy = new Proxy(value, {
9804
9843
  // trap the function call
9805
9844
  apply(target, thisArg, argArray) {
9806
- Reflect.apply(target, thisStore, argArray);
9845
+ const res = Reflect.apply(target, thisStore, argArray);
9846
+ if (res === "noStateChange") {
9847
+ return;
9848
+ }
9807
9849
  callback();
9808
9850
  },
9809
9851
  });
@@ -9825,7 +9867,7 @@ function getDependencyContainer(env) {
9825
9867
  const ModelStore = createAbstractStore("Model");
9826
9868
 
9827
9869
  class RendererStore {
9828
- mutators = ["register", "unRegister"];
9870
+ mutators = ["register", "unRegister", "drawLayer"];
9829
9871
  renderers = {};
9830
9872
  register(renderer) {
9831
9873
  if (!renderer.renderingLayers.length) {
@@ -9845,14 +9887,14 @@ class RendererStore {
9845
9887
  }
9846
9888
  drawLayer(context, layer) {
9847
9889
  const renderers = this.renderers[layer];
9848
- if (!renderers) {
9849
- return;
9850
- }
9851
- for (const renderer of renderers) {
9852
- context.ctx.save();
9853
- renderer.drawLayer(context, layer);
9854
- context.ctx.restore();
9890
+ if (renderers) {
9891
+ for (const renderer of renderers) {
9892
+ context.ctx.save();
9893
+ renderer.drawLayer(context, layer);
9894
+ context.ctx.restore();
9895
+ }
9855
9896
  }
9897
+ return "noStateChange";
9856
9898
  }
9857
9899
  }
9858
9900
 
@@ -9905,16 +9947,17 @@ class ComposerFocusStore extends SpreadsheetStore {
9905
9947
  focusComposer(listener, args) {
9906
9948
  this.activeComposer = listener;
9907
9949
  if (this.getters.isReadonly()) {
9908
- return;
9950
+ return "noStateChange";
9909
9951
  }
9910
9952
  this._focusMode = args.focusMode || "contentFocus";
9911
9953
  if (this._focusMode !== "inactive") {
9912
9954
  this.setComposerContent(args);
9913
9955
  }
9956
+ return;
9914
9957
  }
9915
9958
  focusActiveComposer(args) {
9916
9959
  if (this.getters.isReadonly()) {
9917
- return;
9960
+ return "noStateChange";
9918
9961
  }
9919
9962
  if (!this.activeComposer) {
9920
9963
  throw new Error("No composer is registered");
@@ -9923,6 +9966,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9923
9966
  if (this._focusMode !== "inactive") {
9924
9967
  this.setComposerContent(args);
9925
9968
  }
9969
+ return;
9926
9970
  }
9927
9971
  /**
9928
9972
  * Start the edition or update the content if it's already started.
@@ -10082,20 +10126,24 @@ function getElementMargins(el) {
10082
10126
  }
10083
10127
 
10084
10128
  const chartJsExtensionRegistry = new Registry();
10085
- /** Return window.Chart, making sure all our extensions are loaded in ChartJS */
10086
- function getChartJSConstructor() {
10087
- if (window.Chart && !window.Chart?.registry.plugins.get("chartShowValuesPlugin")) {
10088
- const extensions = chartJsExtensionRegistry.getAll();
10089
- for (const extension of extensions) {
10090
- if (typeof extension === "function") {
10091
- extension(window.Chart);
10092
- }
10093
- else {
10094
- window.Chart.register(extension);
10095
- }
10096
- }
10129
+ function areChartJSExtensionsLoaded() {
10130
+ return !!window.Chart.registry.plugins.get("chartShowValuesPlugin");
10131
+ }
10132
+ function registerChartJSExtensions() {
10133
+ if (!window.Chart || areChartJSExtensionsLoaded()) {
10134
+ return;
10135
+ }
10136
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
10137
+ registryItem.register(window.Chart);
10138
+ }
10139
+ }
10140
+ function unregisterChartJsExtensions() {
10141
+ if (!window.Chart) {
10142
+ return;
10143
+ }
10144
+ for (const registryItem of chartJsExtensionRegistry.getAll()) {
10145
+ registryItem.unregister(window.Chart);
10097
10146
  }
10098
- return window.Chart;
10099
10147
  }
10100
10148
 
10101
10149
  function getFunnelChartController() {
@@ -10522,7 +10570,7 @@ function getDefinedAxis(definition) {
10522
10570
  }
10523
10571
  function formatChartDatasetValue(axisFormats, locale) {
10524
10572
  return (value, axisId) => {
10525
- const format = axisId ? axisFormats?.[axisId] : undefined;
10573
+ const format = axisFormats?.[axisId];
10526
10574
  return formatTickValue({ format, locale })(value);
10527
10575
  };
10528
10576
  }
@@ -10699,7 +10747,7 @@ function drawPieChartValues(chart, options, ctx) {
10699
10747
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10700
10748
  ctx.fillStyle = chartFontColor(options.background);
10701
10749
  ctx.strokeStyle = options.background || "#ffffff";
10702
- const displayValue = options.callback(value);
10750
+ const displayValue = options.callback(value, "y");
10703
10751
  drawTextWithBackground(displayValue, x, y, ctx);
10704
10752
  }
10705
10753
  }
@@ -11461,13 +11509,35 @@ css /* scss */ `
11461
11509
  }
11462
11510
  }
11463
11511
  `;
11464
- chartJsExtensionRegistry.add("chartShowValuesPlugin", chartShowValuesPlugin);
11465
- chartJsExtensionRegistry.add("waterfallLinesPlugin", waterfallLinesPlugin);
11466
- chartJsExtensionRegistry.add("funnelController", (Chart) => Chart.register(getFunnelChartController()));
11467
- chartJsExtensionRegistry.add("funnelElement", (Chart) => Chart.register(getFunnelChartElement()));
11468
- chartJsExtensionRegistry.add("funnelTooltipPositioner", (Chart) => (Chart.Tooltip.positioners.funnelTooltipPositioner = funnelTooltipPositioner));
11469
- chartJsExtensionRegistry.add("sunburstLabelsPlugin", sunburstLabelsPlugin);
11470
- chartJsExtensionRegistry.add("sunburstHoverPlugin", sunburstHoverPlugin);
11512
+ chartJsExtensionRegistry.add("chartShowValuesPlugin", {
11513
+ register: (Chart) => Chart.register(chartShowValuesPlugin),
11514
+ unregister: (Chart) => Chart.unregister(chartShowValuesPlugin),
11515
+ });
11516
+ chartJsExtensionRegistry.add("waterfallLinesPlugin", {
11517
+ register: (Chart) => Chart.register(waterfallLinesPlugin),
11518
+ unregister: (Chart) => Chart.unregister(waterfallLinesPlugin),
11519
+ });
11520
+ chartJsExtensionRegistry.add("funnelController", {
11521
+ register: (Chart) => Chart.register(getFunnelChartController()),
11522
+ unregister: (Chart) => Chart.unregister(getFunnelChartController()),
11523
+ });
11524
+ chartJsExtensionRegistry.add("funnelElement", {
11525
+ register: (Chart) => Chart.register(getFunnelChartElement()),
11526
+ unregister: (Chart) => Chart.unregister(getFunnelChartElement()),
11527
+ });
11528
+ chartJsExtensionRegistry.add("funnelTooltipPositioner", {
11529
+ register: (Chart) => (Chart.Tooltip.positioners.funnelTooltipPositioner = funnelTooltipPositioner),
11530
+ // @ts-expect-error
11531
+ unregister: (Chart) => (Chart.Tooltip.positioners.funnelTooltipPositioner = undefined),
11532
+ });
11533
+ chartJsExtensionRegistry.add("sunburstLabelsPlugin", {
11534
+ register: (Chart) => Chart.register(sunburstLabelsPlugin),
11535
+ unregister: (Chart) => Chart.unregister(sunburstLabelsPlugin),
11536
+ });
11537
+ chartJsExtensionRegistry.add("sunburstHoverPlugin", {
11538
+ register: (Chart) => Chart.register(sunburstHoverPlugin),
11539
+ unregister: (Chart) => Chart.unregister(sunburstHoverPlugin),
11540
+ });
11471
11541
  class ChartJsComponent extends owl.Component {
11472
11542
  static template = "o-spreadsheet-ChartJsComponent";
11473
11543
  static props = {
@@ -11519,8 +11589,7 @@ class ChartJsComponent extends owl.Component {
11519
11589
  createChart(chartData) {
11520
11590
  const canvas = this.canvas.el;
11521
11591
  const ctx = canvas.getContext("2d");
11522
- const Chart = getChartJSConstructor();
11523
- this.chart = new Chart(ctx, chartData);
11592
+ this.chart = new window.Chart(ctx, chartData);
11524
11593
  }
11525
11594
  updateChartJs(chartData) {
11526
11595
  if (chartData.data && chartData.data.datasets) {
@@ -19825,7 +19894,7 @@ const IF = {
19825
19894
  return { value: "" };
19826
19895
  }
19827
19896
  if (result.value === null) {
19828
- result.value = "";
19897
+ return { ...result, value: "" };
19829
19898
  }
19830
19899
  return result;
19831
19900
  },
@@ -19846,7 +19915,7 @@ const IFERROR = {
19846
19915
  return { value: "" };
19847
19916
  }
19848
19917
  if (result.value === null) {
19849
- result.value = "";
19918
+ return { ...result, value: "" };
19850
19919
  }
19851
19920
  return result;
19852
19921
  },
@@ -19867,7 +19936,7 @@ const IFNA = {
19867
19936
  return { value: "" };
19868
19937
  }
19869
19938
  if (result.value === null) {
19870
- result.value = "";
19939
+ return { ...result, value: "" };
19871
19940
  }
19872
19941
  return result;
19873
19942
  },
@@ -19893,7 +19962,7 @@ const IFS = {
19893
19962
  return { value: "" };
19894
19963
  }
19895
19964
  if (result.value === null) {
19896
- result.value = "";
19965
+ return { ...result, value: "" };
19897
19966
  }
19898
19967
  return result;
19899
19968
  }
@@ -20040,6 +20109,11 @@ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
20040
20109
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
20041
20110
  throw new InvalidReferenceError();
20042
20111
  }
20112
+ if (evalContext.__originCellPosition &&
20113
+ range.sheetId === evalContext.__originSheetId &&
20114
+ isZoneInside(positionToZone(evalContext.__originCellPosition), zone)) {
20115
+ throw new CircularDependencyError();
20116
+ }
20043
20117
  dependencies.push(range);
20044
20118
  }
20045
20119
  for (const measure of forMeasures) {
@@ -20482,6 +20556,9 @@ const PIVOT_VALUE = {
20482
20556
  };
20483
20557
  }
20484
20558
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
20559
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
20560
+ this.getters.getPivotPresenceTracker(pivotId)?.trackValue(_measure, domain);
20561
+ }
20485
20562
  return pivot.getPivotCellValueAndFormat(_measure, domain);
20486
20563
  },
20487
20564
  };
@@ -20513,6 +20590,9 @@ const PIVOT_HEADER = {
20513
20590
  };
20514
20591
  }
20515
20592
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
20593
+ if (this.getters.getActiveSheetId() === this.__originSheetId) {
20594
+ this.getters.getPivotPresenceTracker(_pivotId)?.trackHeader(domain);
20595
+ }
20516
20596
  const lastNode = domain.at(-1);
20517
20597
  if (lastNode?.field === "measure") {
20518
20598
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
@@ -20735,6 +20815,9 @@ function isEmpty(data) {
20735
20815
  return data === undefined || data.value === null;
20736
20816
  }
20737
20817
  const getNeutral = { number: 0, string: "", boolean: false };
20818
+ function areAlmostEqual(value1, value2, epsilon = 2e-16) {
20819
+ return Math.abs(value1 - value2) < epsilon;
20820
+ }
20738
20821
  const EQ = {
20739
20822
  description: _t("Equal."),
20740
20823
  args: [
@@ -20756,6 +20839,9 @@ const EQ = {
20756
20839
  if (typeof _value2 === "string") {
20757
20840
  _value2 = _value2.toUpperCase();
20758
20841
  }
20842
+ if (typeof _value1 === "number" && typeof _value2 === "number") {
20843
+ return { value: areAlmostEqual(_value1, _value2) };
20844
+ }
20759
20845
  return { value: _value1 === _value2 };
20760
20846
  },
20761
20847
  };
@@ -20795,6 +20881,9 @@ const GT = {
20795
20881
  ],
20796
20882
  compute: function (value1, value2) {
20797
20883
  return applyRelationalOperator(value1, value2, (v1, v2) => {
20884
+ if (typeof v1 === "number" && typeof v2 === "number") {
20885
+ return !areAlmostEqual(v1, v2) && v1 > v2;
20886
+ }
20798
20887
  return v1 > v2;
20799
20888
  });
20800
20889
  },
@@ -20810,6 +20899,9 @@ const GTE = {
20810
20899
  ],
20811
20900
  compute: function (value1, value2) {
20812
20901
  return applyRelationalOperator(value1, value2, (v1, v2) => {
20902
+ if (typeof v1 === "number" && typeof v2 === "number") {
20903
+ return areAlmostEqual(v1, v2) || v1 > v2;
20904
+ }
20813
20905
  return v1 >= v2;
20814
20906
  });
20815
20907
  },
@@ -22502,7 +22594,7 @@ class AbstractComposerStore extends SpreadsheetStore {
22502
22594
  .find((token) => {
22503
22595
  const { xc, sheetName: sheet } = splitReference(token.value);
22504
22596
  const sheetName = sheet || this.getters.getSheetName(this.sheetId);
22505
- if (this.getters.getSheetName(activeSheetId) !== sheetName) {
22597
+ if (!isSheetNameEqual(this.getters.getSheetName(activeSheetId), sheetName)) {
22506
22598
  return false;
22507
22599
  }
22508
22600
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
@@ -24399,6 +24491,7 @@ const CHART_COMMON_OPTIONS = {
24399
24491
  },
24400
24492
  },
24401
24493
  animation: false,
24494
+ events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "mouseup"],
24402
24495
  };
24403
24496
  function chartToImageUrl(runtime, figure, type) {
24404
24497
  // wrap the canvas in a div with a fixed size because chart.js would
@@ -24438,6 +24531,8 @@ async function chartToImageFile(runtime, figure, type) {
24438
24531
  const div = document.createElement("div");
24439
24532
  div.style.width = `${figure.width}px`;
24440
24533
  div.style.height = `${figure.height}px`;
24534
+ div.style.position = "fixed";
24535
+ div.style.opacity = "0";
24441
24536
  const canvas = document.createElement("canvas");
24442
24537
  div.append(canvas);
24443
24538
  canvas.setAttribute("width", figure.width.toString());
@@ -24448,8 +24543,7 @@ async function chartToImageFile(runtime, figure, type) {
24448
24543
  if ("chartJsConfig" in runtime) {
24449
24544
  const config = deepCopy(runtime.chartJsConfig);
24450
24545
  config.plugins = [backgroundColorChartJSPlugin];
24451
- const Chart = getChartJSConstructor();
24452
- const chart = new Chart(canvas, config);
24546
+ const chart = new window.Chart(canvas, config);
24453
24547
  chartBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
24454
24548
  chart.destroy();
24455
24549
  }
@@ -25201,11 +25295,10 @@ function canBeLinearChart(definition, dataSets, labelRange, getters) {
25201
25295
  }
25202
25296
  let missingTimeAdapterAlreadyWarned = false;
25203
25297
  function isLuxonTimeAdapterInstalled() {
25204
- const Chart = getChartJSConstructor();
25205
- if (!Chart) {
25298
+ if (!window.Chart) {
25206
25299
  return false;
25207
25300
  }
25208
- const adapter = new Chart._adapters._date({});
25301
+ const adapter = new window.Chart._adapters._date({});
25209
25302
  // @ts-ignore
25210
25303
  const isInstalled = adapter._id === "luxon";
25211
25304
  if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
@@ -25703,6 +25796,9 @@ const INTERACTIVE_LEGEND_CONFIG = {
25703
25796
  target.style.cursor = "default";
25704
25797
  },
25705
25798
  onClick: (event, legendItem, legend) => {
25799
+ if (event.type !== "click") {
25800
+ return;
25801
+ }
25706
25802
  const index = legendItem.datasetIndex;
25707
25803
  if (!legend.legendItems || index === undefined) {
25708
25804
  return;
@@ -29639,10 +29735,15 @@ function getChartMenu(figureId, onFigureDeleted, env) {
29639
29735
  const imageUrl = chartToImageUrl(runtime, figure, chartType);
29640
29736
  const innerHTML = `<img src="${xmlEscape(imageUrl)}" />`;
29641
29737
  const blob = await chartToImageFile(runtime, figure, chartType);
29642
- env.clipboard.write({
29738
+ await env.clipboard.write({
29643
29739
  "text/html": innerHTML,
29644
29740
  "image/png": blob,
29645
29741
  });
29742
+ env.notifyUser({
29743
+ text: _t("The chart was copied to your clipboard"),
29744
+ sticky: false,
29745
+ type: "info",
29746
+ });
29646
29747
  },
29647
29748
  },
29648
29749
  {
@@ -31492,7 +31593,7 @@ function getRangeSize(reference, defaultSheetIndex, data) {
31492
31593
  ({ xc, sheetName } = splitReference(reference));
31493
31594
  let rangeSheetIndex;
31494
31595
  if (sheetName) {
31495
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
31596
+ const index = data.sheets.findIndex((sheet) => isSheetNameEqual(sheet.name, sheetName));
31496
31597
  if (index < 0) {
31497
31598
  throw new Error("Unable to find a sheet with the name " + sheetName);
31498
31599
  }
@@ -31849,7 +31950,7 @@ function convertFormula(formula, data) {
31849
31950
  formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
31850
31951
  externalRefId = Number(externalRefId) - 1;
31851
31952
  cellRef = cellRef.replace(/\$/g, "");
31852
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
31953
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => isSheetNameEqual(name, sheetName));
31853
31954
  if (sheetIndex === -1) {
31854
31955
  return match;
31855
31956
  }
@@ -32500,7 +32601,7 @@ function convertPivotTableConfig(pivotTable) {
32500
32601
  */
32501
32602
  function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
32502
32603
  for (let tableSheet of convertedSheets) {
32503
- const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
32604
+ const tables = xlsxSheets.find((s) => isSheetNameEqual(s.sheetName, tableSheet.name)).tables;
32504
32605
  for (let table of tables) {
32505
32606
  const tabRef = table.name + "[";
32506
32607
  for (let sheet of convertedSheets) {
@@ -34849,6 +34950,7 @@ function repairInitialMessages(data, initialMessages) {
34849
34950
  initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
34850
34951
  initialMessages = fixChartDefinitions(data, initialMessages);
34851
34952
  initialMessages = fixFigureOffset(data, initialMessages);
34953
+ initialMessages = fixTranslatedDuplicateSheetName(data, initialMessages);
34852
34954
  return initialMessages;
34853
34955
  }
34854
34956
  /**
@@ -34981,6 +35083,42 @@ function fixFigureOffset(data, messages) {
34981
35083
  }
34982
35084
  return messages;
34983
35085
  }
35086
+ function fixTranslatedDuplicateSheetName(data, initialMessages) {
35087
+ const sheetNames = {};
35088
+ for (const sheet of data.sheets || []) {
35089
+ sheetNames[sheet.id] = sheet.name;
35090
+ }
35091
+ const messages = [];
35092
+ for (const message of initialMessages) {
35093
+ if (message.type === "REMOTE_REVISION") {
35094
+ const commands = [];
35095
+ for (const cmd of message.commands) {
35096
+ switch (cmd.type) {
35097
+ case "DUPLICATE_SHEET":
35098
+ cmd.sheetNameTo =
35099
+ cmd.sheetNameTo ??
35100
+ getDuplicateSheetName(sheetNames[cmd.sheetId], Object.values(sheetNames));
35101
+ break;
35102
+ case "CREATE_SHEET":
35103
+ sheetNames[cmd.sheetId] = cmd.name || getNextSheetName(Object.values(sheetNames));
35104
+ break;
35105
+ case "RENAME_SHEET":
35106
+ sheetNames[cmd.sheetId] = cmd.newName || getNextSheetName(Object.values(sheetNames));
35107
+ break;
35108
+ }
35109
+ commands.push(cmd);
35110
+ }
35111
+ messages.push({
35112
+ ...message,
35113
+ commands,
35114
+ });
35115
+ }
35116
+ else {
35117
+ messages.push(message);
35118
+ }
35119
+ }
35120
+ return initialMessages;
35121
+ }
34984
35122
  // -----------------------------------------------------------------------------
34985
35123
  // Helpers
34986
35124
  // -----------------------------------------------------------------------------
@@ -35118,12 +35256,20 @@ class DelayedHoveredCellStore extends SpreadsheetStore {
35118
35256
  }
35119
35257
  }
35120
35258
  hover(position) {
35259
+ if (position.col === this.col && position.row === this.row) {
35260
+ return "noStateChange";
35261
+ }
35121
35262
  this.col = position.col;
35122
35263
  this.row = position.row;
35264
+ return;
35123
35265
  }
35124
35266
  clear() {
35267
+ if (this.col === undefined && this.row === undefined) {
35268
+ return "noStateChange";
35269
+ }
35125
35270
  this.col = undefined;
35126
35271
  this.row = undefined;
35272
+ return;
35127
35273
  }
35128
35274
  }
35129
35275
 
@@ -35145,7 +35291,11 @@ class CellPopoverStore extends SpreadsheetStore {
35145
35291
  this.persistentPopover = { col, row, sheetId, type };
35146
35292
  }
35147
35293
  close() {
35294
+ if (!this.persistentPopover) {
35295
+ return "noStateChange";
35296
+ }
35148
35297
  this.persistentPopover = undefined;
35298
+ return;
35149
35299
  }
35150
35300
  get persistentCellPopover() {
35151
35301
  return ((this.persistentPopover && { isOpen: true, ...this.persistentPopover }) || { isOpen: false });
@@ -36039,10 +36189,13 @@ const duplicateSheet = {
36039
36189
  name: _t("Duplicate"),
36040
36190
  execute: (env) => {
36041
36191
  const sheetIdFrom = env.model.getters.getActiveSheetId();
36192
+ const sheetNameFrom = env.model.getters.getSheetName(sheetIdFrom);
36042
36193
  const sheetIdTo = env.model.uuidGenerator.smallUuid();
36194
+ const sheetNameTo = env.model.getters.getDuplicateSheetName(sheetNameFrom);
36043
36195
  env.model.dispatch("DUPLICATE_SHEET", {
36044
36196
  sheetId: sheetIdFrom,
36045
36197
  sheetIdTo,
36198
+ sheetNameTo,
36046
36199
  });
36047
36200
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
36048
36201
  },
@@ -40683,7 +40836,7 @@ class GenericChartConfigPanel extends owl.Component {
40683
40836
  const cancelledReasons = [
40684
40837
  ...(this.state.datasetDispatchResult?.reasons || []),
40685
40838
  ...(this.state.labelsDispatchResult?.reasons || []),
40686
- ];
40839
+ ].filter((reason) => reason !== "NoChanges" /* CommandResult.NoChanges */);
40687
40840
  return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
40688
40841
  }
40689
40842
  get isDatasetInvalid() {
@@ -42260,10 +42413,18 @@ class GaugeChartConfigPanel extends owl.Component {
42260
42413
  }
42261
42414
 
42262
42415
  class DOMFocusableElementStore {
42263
- mutators = ["setFocusableElement"];
42416
+ mutators = ["setFocusableElement", "focus"];
42264
42417
  focusableElement = undefined;
42265
42418
  setFocusableElement(element) {
42266
42419
  this.focusableElement = element;
42420
+ return "noStateChange";
42421
+ }
42422
+ focus() {
42423
+ if (this.focusableElement === document.activeElement) {
42424
+ return "noStateChange";
42425
+ }
42426
+ this.focusableElement?.focus();
42427
+ return;
42267
42428
  }
42268
42429
  }
42269
42430
 
@@ -42935,7 +43096,7 @@ class Composer extends owl.Component {
42935
43096
  if (document.activeElement === this.contentHelper.el &&
42936
43097
  this.props.composerStore.editionMode === "inactive" &&
42937
43098
  !this.props.isDefaultFocus) {
42938
- this.DOMFocusableElementStore.focusableElement?.focus();
43099
+ this.DOMFocusableElementStore.focus();
42939
43100
  }
42940
43101
  });
42941
43102
  owl.useEffect(() => {
@@ -43215,6 +43376,13 @@ class Composer extends owl.Component {
43215
43376
  openAssistant() {
43216
43377
  this.assistant.forcedClosed = false;
43217
43378
  }
43379
+ onWheel(event) {
43380
+ // detect if scrollbar is available
43381
+ if (this.composerRef.el &&
43382
+ this.composerRef.el.scrollHeight > this.composerRef.el.clientHeight) {
43383
+ event.stopPropagation();
43384
+ }
43385
+ }
43218
43386
  // ---------------------------------------------------------------------------
43219
43387
  // Private
43220
43388
  // ---------------------------------------------------------------------------
@@ -48939,8 +49107,8 @@ function compareDimensionValues(dimension, a, b) {
48939
49107
 
48940
49108
  const NULL_SYMBOL = Symbol("NULL");
48941
49109
  function createDate(dimension, value, locale) {
48942
- const granularity = dimension.granularity;
48943
- if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
49110
+ const granularity = dimension.granularity || "month";
49111
+ if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
48944
49112
  throw new Error(`Unknown date granularity: ${granularity}`);
48945
49113
  }
48946
49114
  const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
@@ -48959,6 +49127,9 @@ function createDate(dimension, value, locale) {
48959
49127
  case "month_number":
48960
49128
  number = date.getMonth() + 1;
48961
49129
  break;
49130
+ case "month":
49131
+ number = Math.floor(toNumber(value, locale));
49132
+ break;
48962
49133
  case "iso_week_number":
48963
49134
  number = date.getIsoWeek();
48964
49135
  break;
@@ -49052,6 +49223,10 @@ const MAP_VALUE_DIMENSION_DATE = {
49052
49223
  set: new Set(),
49053
49224
  values: {},
49054
49225
  },
49226
+ month: {
49227
+ set: new Set(),
49228
+ values: {},
49229
+ },
49055
49230
  iso_week_number: {
49056
49231
  set: new Set(),
49057
49232
  values: {},
@@ -49262,7 +49437,7 @@ class SpreadsheetPivot {
49262
49437
  const cells = this.filterDataEntriesFromDomain(this.dataEntries, domain);
49263
49438
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
49264
49439
  if (dimension.type === "datetime") {
49265
- const adapter = pivotTimeAdapter(dimension.granularity);
49440
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
49266
49441
  return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
49267
49442
  }
49268
49443
  if (!finalCell) {
@@ -49380,7 +49555,7 @@ class SpreadsheetPivot {
49380
49555
  if (nonEmptyCells.length === 0) {
49381
49556
  return "integer";
49382
49557
  }
49383
- if (nonEmptyCells.every((cell) => cell.format && isDateTimeFormat(cell.format))) {
49558
+ if (nonEmptyCells.every((cell) => cell.type === CellValueType.number && cell.format && isDateTimeFormat(cell.format))) {
49384
49559
  return "datetime";
49385
49560
  }
49386
49561
  if (nonEmptyCells.every((cell) => cell.type === CellValueType.boolean)) {
@@ -49459,7 +49634,12 @@ class SpreadsheetPivot {
49459
49634
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
49460
49635
  }
49461
49636
  else {
49462
- entry[field.name] = cell;
49637
+ if (field.type === "char") {
49638
+ entry[field.name] = { ...cell, value: cell.formattedValue || null };
49639
+ }
49640
+ else {
49641
+ entry[field.name] = cell;
49642
+ }
49463
49643
  }
49464
49644
  }
49465
49645
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -49473,7 +49653,7 @@ class SpreadsheetPivot {
49473
49653
  for (const entry of dataEntries) {
49474
49654
  for (const dimension of dateDimensions) {
49475
49655
  const value = createDate(dimension, entry[dimension.fieldName]?.value || null, this.getters.getLocale());
49476
- const adapter = pivotTimeAdapter(dimension.granularity);
49656
+ const adapter = pivotTimeAdapter((dimension.granularity || "month"));
49477
49657
  const { format, value: valueToFormat } = adapter.toValueAndFormat(value, locale);
49478
49658
  entry[dimension.nameWithGranularity] = {
49479
49659
  value,
@@ -49493,6 +49673,7 @@ const dateGranularities = [
49493
49673
  "year",
49494
49674
  "quarter_number",
49495
49675
  "month_number",
49676
+ "month",
49496
49677
  "iso_week_number",
49497
49678
  "day_of_month",
49498
49679
  "day",
@@ -49743,7 +49924,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
49743
49924
  : this.datetimeGranularities);
49744
49925
  }
49745
49926
  for (const field of dateFields) {
49746
- granularitiesPerFields[field.fieldName].delete(field.granularity);
49927
+ granularitiesPerFields[field.fieldName].delete(field.granularity || "month");
49747
49928
  }
49748
49929
  return granularitiesPerFields;
49749
49930
  }
@@ -52083,6 +52264,8 @@ class GridComposer extends owl.Component {
52083
52264
  }
52084
52265
  get composerProps() {
52085
52266
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
52267
+ // Remove the wrapper border width
52268
+ const maxHeight = this.props.gridDims.height - this.rect.y - 2 * COMPOSER_BORDER_WIDTH;
52086
52269
  return {
52087
52270
  rect: { ...this.rect },
52088
52271
  delimitation: {
@@ -52100,6 +52283,7 @@ class GridComposer extends owl.Component {
52100
52283
  }),
52101
52284
  onInputContextMenu: this.props.onInputContextMenu,
52102
52285
  composerStore: this.composerStore,
52286
+ inputStyle: `max-height: ${maxHeight}px;`,
52103
52287
  };
52104
52288
  }
52105
52289
  get containerStyle() {
@@ -53180,9 +53364,13 @@ class HoveredTableStore extends SpreadsheetStore {
53180
53364
  }
53181
53365
  }
53182
53366
  hover(position) {
53367
+ if (position.col === this.col && position.row === this.row) {
53368
+ return "noStateChange";
53369
+ }
53183
53370
  this.col = position.col;
53184
53371
  this.row = position.row;
53185
53372
  this.computeOverlay();
53373
+ return;
53186
53374
  }
53187
53375
  clear() {
53188
53376
  this.col = undefined;
@@ -54814,10 +55002,6 @@ function useGridDrawing(refName, model, canvasSize) {
54814
55002
  ctx.scale(dpr, dpr);
54815
55003
  for (const layer of OrderedLayers()) {
54816
55004
  model.drawLayer(renderingContext, layer);
54817
- // @ts-ignore 'drawLayer' is not declated as a mutator because:
54818
- // it does not mutate anything. Most importantly it's used
54819
- // during rendering. Invoking a mutator during rendering would
54820
- // trigger another rendering, ultimately resulting in an infinite loop.
54821
55005
  rendererStore.drawLayer(renderingContext, layer);
54822
55006
  }
54823
55007
  }
@@ -55511,7 +55695,7 @@ class Grid extends owl.Component {
55511
55695
  this.cellPopovers = useStore(CellPopoverStore);
55512
55696
  owl.useEffect(() => {
55513
55697
  if (!this.sidePanel.isOpen) {
55514
- this.DOMFocusableElementStore.focusableElement?.focus();
55698
+ this.DOMFocusableElementStore.focus();
55515
55699
  }
55516
55700
  }, () => [this.sidePanel.isOpen]);
55517
55701
  useTouchScroll(this.gridRef, this.moveCanvas.bind(this), () => {
@@ -55720,7 +55904,7 @@ class Grid extends owl.Component {
55720
55904
  focusDefaultElement() {
55721
55905
  if (!this.env.model.getters.getSelectedFigureId() &&
55722
55906
  this.composerFocusStore.activeComposer.editionMode === "inactive") {
55723
- this.DOMFocusableElementStore.focusableElement?.focus();
55907
+ this.DOMFocusableElementStore.focus();
55724
55908
  }
55725
55909
  }
55726
55910
  get gridEl() {
@@ -56069,6 +56253,322 @@ class Grid extends owl.Component {
56069
56253
  }
56070
56254
  }
56071
56255
 
56256
+ css /* scss */ `
56257
+ .o_pivot_html_renderer {
56258
+ width: 100%;
56259
+ border-collapse: collapse;
56260
+
56261
+ &:hover {
56262
+ cursor: pointer;
56263
+ }
56264
+
56265
+ td,
56266
+ th {
56267
+ border: 1px solid #dee2e6;
56268
+ background-color: #fff;
56269
+ padding: 0.3rem;
56270
+ white-space: nowrap;
56271
+
56272
+ &:hover {
56273
+ filter: brightness(0.9);
56274
+ }
56275
+ }
56276
+
56277
+ td {
56278
+ text-align: right;
56279
+ }
56280
+
56281
+ th {
56282
+ background-color: #f5f5f5;
56283
+ font-weight: bold;
56284
+ color: black;
56285
+ }
56286
+
56287
+ .o_missing_value {
56288
+ color: #46646d;
56289
+ background: #e7f2f6;
56290
+ }
56291
+ }
56292
+ `;
56293
+ class PivotHTMLRenderer extends owl.Component {
56294
+ static template = "o_spreadsheet.PivotHTMLRenderer";
56295
+ static components = { Checkbox };
56296
+ static props = {
56297
+ pivotId: String,
56298
+ onCellClicked: Function,
56299
+ };
56300
+ pivot = this.env.model.getters.getPivot(this.props.pivotId);
56301
+ data = {
56302
+ columns: [],
56303
+ rows: [],
56304
+ values: [],
56305
+ };
56306
+ state = owl.useState({
56307
+ showMissingValuesOnly: false,
56308
+ });
56309
+ setup() {
56310
+ const table = this.pivot.getTableStructure();
56311
+ const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
56312
+ this.data = {
56313
+ columns: this._buildColHeaders(formulaId, table),
56314
+ rows: this._buildRowHeaders(formulaId, table),
56315
+ values: this._buildValues(formulaId, table),
56316
+ };
56317
+ }
56318
+ get tracker() {
56319
+ return this.env.model.getters.getPivotPresenceTracker(this.props.pivotId);
56320
+ }
56321
+ // ---------------------------------------------------------------------
56322
+ // Missing values building
56323
+ // ---------------------------------------------------------------------
56324
+ /**
56325
+ * Retrieve the data to display in the Pivot Table
56326
+ * In the case when showMissingValuesOnly is false, the returned value
56327
+ * is the complete data
56328
+ * In the case when showMissingValuesOnly is true, the returned value is
56329
+ * the data which contains only missing values in the rows and cols. In
56330
+ * the rows, we also return the parent rows of rows which contains missing
56331
+ * values, to give context to the user.
56332
+ *
56333
+ */
56334
+ getTableData() {
56335
+ if (!this.state.showMissingValuesOnly) {
56336
+ return this.data;
56337
+ }
56338
+ const colIndexes = this.getColumnsIndexes();
56339
+ const rowIndexes = this.getRowsIndexes();
56340
+ const columns = this.buildColumnsMissing(colIndexes);
56341
+ const rows = this.buildRowsMissing(rowIndexes);
56342
+ const values = this.buildValuesMissing(colIndexes, rowIndexes);
56343
+ return { columns, rows, values };
56344
+ }
56345
+ /**
56346
+ * Retrieve the parents of the given row
56347
+ * ex:
56348
+ * Australia
56349
+ * January
56350
+ * February
56351
+ * The parent of "January" is "Australia"
56352
+ */
56353
+ addRecursiveRow(index) {
56354
+ const rows = this.pivot.getTableStructure().rows;
56355
+ const row = [...rows[index].values];
56356
+ if (row.length <= 1) {
56357
+ return [index];
56358
+ }
56359
+ row.pop();
56360
+ const parentRowIndex = rows.findIndex((r) => JSON.stringify(r.values) === JSON.stringify(row));
56361
+ return [index].concat(this.addRecursiveRow(parentRowIndex));
56362
+ }
56363
+ /**
56364
+ * Create the columns to be used, based on the indexes of the columns in
56365
+ * which a missing value is present
56366
+ *
56367
+ */
56368
+ buildColumnsMissing(indexes) {
56369
+ // columnsMap explode the columns in an array of array of the same
56370
+ // size with the index of each column, repeated 'span' times.
56371
+ // ex:
56372
+ // | A | B |
56373
+ // | 1 | 2 | 3 |
56374
+ // => [
56375
+ // [0, 0, 1]
56376
+ // [0, 1, 2]
56377
+ // ]
56378
+ const columnsMap = [];
56379
+ for (const column of this.data.columns) {
56380
+ const columnMap = [];
56381
+ for (const index in column) {
56382
+ for (let i = 0; i < column[index].span; i++) {
56383
+ columnMap.push(parseInt(index, 10));
56384
+ }
56385
+ }
56386
+ columnsMap.push(columnMap);
56387
+ }
56388
+ // Remove the columns that are not present in indexes
56389
+ for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {
56390
+ if (!indexes.includes(i)) {
56391
+ for (const columnMap of columnsMap) {
56392
+ columnMap.splice(i, 1);
56393
+ }
56394
+ }
56395
+ }
56396
+ // Build the columns
56397
+ const columns = [];
56398
+ for (const mapIndex in columnsMap) {
56399
+ const column = [];
56400
+ let index = undefined;
56401
+ let span = 1;
56402
+ for (let i = 0; i < columnsMap[mapIndex].length; i++) {
56403
+ if (index !== columnsMap[mapIndex][i]) {
56404
+ if (index !== undefined) {
56405
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
56406
+ }
56407
+ index = columnsMap[mapIndex][i];
56408
+ span = 1;
56409
+ }
56410
+ else {
56411
+ span++;
56412
+ }
56413
+ }
56414
+ if (index !== undefined) {
56415
+ column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));
56416
+ }
56417
+ columns.push(column);
56418
+ }
56419
+ return columns;
56420
+ }
56421
+ /**
56422
+ * Create the rows to be used, based on the indexes of the rows in
56423
+ * which a missing value is present.
56424
+ */
56425
+ buildRowsMissing(indexes) {
56426
+ return indexes.map((index) => this.data.rows[index]);
56427
+ }
56428
+ /**
56429
+ * Create the value to be used, based on the indexes of the columns and
56430
+ * rows in which a missing value is present.
56431
+ */
56432
+ buildValuesMissing(colIndexes, rowIndexes) {
56433
+ const values = colIndexes.map(() => []);
56434
+ for (const row of rowIndexes) {
56435
+ for (const col in colIndexes) {
56436
+ values[col].push(this.data.values[colIndexes[col]][row]);
56437
+ }
56438
+ }
56439
+ return values;
56440
+ }
56441
+ getColumnsIndexes() {
56442
+ const indexes = new Set();
56443
+ for (let i = 0; i < this.data.columns.length; i++) {
56444
+ const exploded = [];
56445
+ for (let y = 0; y < this.data.columns[i].length; y++) {
56446
+ for (let x = 0; x < this.data.columns[i][y].span; x++) {
56447
+ exploded.push(this.data.columns[i][y]);
56448
+ }
56449
+ }
56450
+ for (let y = 0; y < exploded.length; y++) {
56451
+ if (exploded[y].isMissing) {
56452
+ indexes.add(y);
56453
+ }
56454
+ }
56455
+ }
56456
+ for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {
56457
+ const values = this.data.values[i];
56458
+ if (values.find((x) => x.isMissing)) {
56459
+ indexes.add(i);
56460
+ }
56461
+ }
56462
+ return Array.from(indexes).sort((a, b) => a - b);
56463
+ }
56464
+ getRowsIndexes() {
56465
+ const rowIndexes = new Set();
56466
+ for (let i = 0; i < this.data.rows.length; i++) {
56467
+ if (this.data.rows[i].isMissing) {
56468
+ rowIndexes.add(i);
56469
+ }
56470
+ for (const col of this.data.values) {
56471
+ if (col[i].isMissing) {
56472
+ this.addRecursiveRow(i).forEach((x) => rowIndexes.add(x));
56473
+ }
56474
+ }
56475
+ }
56476
+ return Array.from(rowIndexes).sort((a, b) => a - b);
56477
+ }
56478
+ // ---------------------------------------------------------------------
56479
+ // Data table creation
56480
+ // ---------------------------------------------------------------------
56481
+ _buildColHeaders(id, table) {
56482
+ const headers = [];
56483
+ for (const row of table.columns) {
56484
+ const current = [];
56485
+ for (const cell of row) {
56486
+ const args = [];
56487
+ for (let i = 0; i < cell.fields.length; i++) {
56488
+ args.push({ value: cell.fields[i] }, { value: cell.values[i] });
56489
+ }
56490
+ const domain = this.pivot.parseArgsToPivotDomain(args);
56491
+ const locale = this.env.model.getters.getLocale();
56492
+ if (domain.at(-1)?.field === "measure") {
56493
+ const { value, format } = this.pivot.getPivotMeasureValue(toString(domain.at(-1).value), domain);
56494
+ current.push({
56495
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
56496
+ value: formatValue(value, { format, locale }),
56497
+ span: cell.width,
56498
+ isMissing: !this.tracker?.isHeaderPresent(domain),
56499
+ });
56500
+ }
56501
+ else {
56502
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
56503
+ current.push({
56504
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
56505
+ value: formatValue(value, { format, locale }),
56506
+ span: cell.width,
56507
+ isMissing: !this.tracker?.isHeaderPresent(domain),
56508
+ });
56509
+ }
56510
+ }
56511
+ headers.push(current);
56512
+ }
56513
+ const last = headers[headers.length - 1];
56514
+ headers[headers.length - 1] = last.map((cell) => {
56515
+ if (!cell.isMissing) {
56516
+ cell.style = "color: #756f6f;";
56517
+ }
56518
+ return cell;
56519
+ });
56520
+ return headers;
56521
+ }
56522
+ _buildRowHeaders(id, table) {
56523
+ const headers = [];
56524
+ for (const row of table.rows) {
56525
+ const args = [];
56526
+ for (let i = 0; i < row.fields.length; i++) {
56527
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
56528
+ }
56529
+ const domain = this.pivot.parseArgsToPivotDomain(args);
56530
+ const { value, format } = this.pivot.getPivotHeaderValueAndFormat(domain);
56531
+ const locale = this.env.model.getters.getLocale();
56532
+ const cell = {
56533
+ formula: `=PIVOT.HEADER(${generatePivotArgs(id, domain).join(",")})`,
56534
+ value: formatValue(value, { format, locale }),
56535
+ isMissing: !this.tracker?.isHeaderPresent(domain),
56536
+ };
56537
+ if (row.indent > 1) {
56538
+ cell.style = `padding-left: ${row.indent - 1 * 10}px`;
56539
+ }
56540
+ headers.push(cell);
56541
+ }
56542
+ return headers;
56543
+ }
56544
+ _buildValues(id, table) {
56545
+ const values = [];
56546
+ for (const col of table.columns.at(-1) || []) {
56547
+ const current = [];
56548
+ const measure = toString(col.values[col.values.length - 1]);
56549
+ for (const row of table.rows) {
56550
+ const args = [];
56551
+ for (let i = 0; i < row.fields.length; i++) {
56552
+ args.push({ value: row.fields[i] }, { value: row.values[i] });
56553
+ }
56554
+ for (let i = 0; i < col.fields.length - 1; i++) {
56555
+ args.push({ value: col.fields[i] }, { value: col.values[i] });
56556
+ }
56557
+ const domain = this.pivot.parseArgsToPivotDomain(args);
56558
+ const { value, format } = this.pivot.getPivotCellValueAndFormat(measure, domain);
56559
+ const locale = this.env.model.getters.getLocale();
56560
+ current.push({
56561
+ formula: `=PIVOT.VALUE(${generatePivotArgs(id, domain, measure).join(",")})`,
56562
+ value: formatValue(value, { format, locale }),
56563
+ isMissing: !this.tracker?.isValuePresent(measure, domain),
56564
+ });
56565
+ }
56566
+ values.push(current);
56567
+ }
56568
+ return values;
56569
+ }
56570
+ }
56571
+
56072
56572
  /**
56073
56573
  * BasePlugin
56074
56574
  *
@@ -57421,7 +57921,7 @@ class ChartPlugin extends CorePlugin {
57421
57921
  case "CREATE_CHART":
57422
57922
  return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartDuplicate));
57423
57923
  case "UPDATE_CHART":
57424
- return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists));
57924
+ return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists, this.checkChartChanged));
57425
57925
  default:
57426
57926
  return "Success" /* CommandResult.Success */;
57427
57927
  }
@@ -57575,10 +58075,15 @@ class ChartPlugin extends CorePlugin {
57575
58075
  : "Success" /* CommandResult.Success */;
57576
58076
  }
57577
58077
  checkChartExists(cmd) {
57578
- return this.getters.getFigureSheetId(cmd.figureId)
58078
+ return this.isChartDefined(cmd.figureId)
57579
58079
  ? "Success" /* CommandResult.Success */
57580
58080
  : "ChartDoesNotExist" /* CommandResult.ChartDoesNotExist */;
57581
58081
  }
58082
+ checkChartChanged(cmd) {
58083
+ return deepEquals(this.getChartDefinition(cmd.figureId), cmd.definition)
58084
+ ? "NoChanges" /* CommandResult.NoChanges */
58085
+ : "Success" /* CommandResult.Success */;
58086
+ }
57582
58087
  }
57583
58088
 
57584
58089
  // -----------------------------------------------------------------------------
@@ -59836,6 +60341,7 @@ class SheetPlugin extends CorePlugin {
59836
60341
  "getCommandZones",
59837
60342
  "getUnboundedZone",
59838
60343
  "checkElementsIncludeAllNonFrozenHeaders",
60344
+ "getDuplicateSheetName",
59839
60345
  ];
59840
60346
  sheetIdsMapName = {};
59841
60347
  orderedSheetIds = [];
@@ -59860,7 +60366,11 @@ class SheetPlugin extends CorePlugin {
59860
60366
  return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
59861
60367
  }
59862
60368
  case "DUPLICATE_SHEET": {
59863
- return this.sheets[cmd.sheetIdTo] ? "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */ : "Success" /* CommandResult.Success */;
60369
+ if (this.sheets[cmd.sheetIdTo])
60370
+ return "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */;
60371
+ if (this.orderedSheetIds.map(this.getSheetName.bind(this)).includes(cmd.sheetNameTo))
60372
+ return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
60373
+ return "Success" /* CommandResult.Success */;
59864
60374
  }
59865
60375
  case "MOVE_SHEET":
59866
60376
  try {
@@ -59937,7 +60447,7 @@ class SheetPlugin extends CorePlugin {
59937
60447
  this.showSheet(cmd.sheetId);
59938
60448
  break;
59939
60449
  case "DUPLICATE_SHEET":
59940
- this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo);
60450
+ this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo, cmd.sheetNameTo);
59941
60451
  break;
59942
60452
  case "DELETE_SHEET":
59943
60453
  this.deleteSheet(this.sheets[cmd.sheetId]);
@@ -60078,7 +60588,7 @@ class SheetPlugin extends CorePlugin {
60078
60588
  if (name) {
60079
60589
  const unquotedName = getUnquotedSheetName(name);
60080
60590
  for (const key in this.sheetIdsMapName) {
60081
- if (key.toUpperCase() === unquotedName.toUpperCase()) {
60591
+ if (isSheetNameEqual(key, unquotedName)) {
60082
60592
  return this.sheetIdsMapName[key];
60083
60593
  }
60084
60594
  }
@@ -60144,10 +60654,7 @@ class SheetPlugin extends CorePlugin {
60144
60654
  }
60145
60655
  getNextSheetName(baseName = "Sheet") {
60146
60656
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
60147
- return getUniqueText(baseName, names, {
60148
- compute: (name, i) => `${name}${i}`,
60149
- computeFirstOne: true,
60150
- });
60657
+ return getNextSheetName(names, baseName);
60151
60658
  }
60152
60659
  getSheetSize(sheetId) {
60153
60660
  return {
@@ -60330,7 +60837,7 @@ class SheetPlugin extends CorePlugin {
60330
60837
  }
60331
60838
  const { orderedSheetIds, sheets } = this;
60332
60839
  const name = sheetName && sheetName.trim().toLowerCase();
60333
- if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
60840
+ if (orderedSheetIds.find((id) => isSheetNameEqual(sheets[id]?.name, name) && id !== cmd.sheetId)) {
60334
60841
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
60335
60842
  }
60336
60843
  if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
@@ -60394,9 +60901,8 @@ class SheetPlugin extends CorePlugin {
60394
60901
  showSheet(sheetId) {
60395
60902
  this.history.update("sheets", sheetId, "isVisible", true);
60396
60903
  }
60397
- duplicateSheet(fromId, toId) {
60904
+ duplicateSheet(fromId, toId, toName) {
60398
60905
  const sheet = this.getSheet(fromId);
60399
- const toName = this.getDuplicateSheetName(sheet.name);
60400
60906
  const newSheet = deepCopy(sheet);
60401
60907
  newSheet.id = toId;
60402
60908
  newSheet.name = toName;
@@ -60429,8 +60935,7 @@ class SheetPlugin extends CorePlugin {
60429
60935
  }
60430
60936
  getDuplicateSheetName(sheetName) {
60431
60937
  const names = this.orderedSheetIds.map(this.getSheetName.bind(this));
60432
- const baseName = _t("Copy of %s", sheetName);
60433
- return getUniqueText(baseName.toString(), names);
60938
+ return getDuplicateSheetName(sheetName, names);
60434
60939
  }
60435
60940
  deleteSheet(sheet) {
60436
60941
  const name = sheet.name;
@@ -63234,8 +63739,8 @@ class SpreadingRelation {
63234
63739
  const EMPTY_ARRAY = [];
63235
63740
 
63236
63741
  const MAX_ITERATION = 30;
63237
- const ERROR_CYCLE_CELL = createEvaluatedCell(new CircularDependencyError());
63238
- const EMPTY_CELL = createEvaluatedCell({ value: null });
63742
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
63743
+ const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
63239
63744
  class Evaluator {
63240
63745
  context;
63241
63746
  getters;
@@ -69416,6 +69921,55 @@ class HistoryPlugin extends UIPlugin {
69416
69921
  }
69417
69922
  }
69418
69923
 
69924
+ class PivotPresenceTracker {
69925
+ trackedValues = new Set();
69926
+ domainToArray(domain) {
69927
+ return domain.flatMap((node) => [node.field, toString(node.value)]);
69928
+ }
69929
+ isValuePresent(measure, domain) {
69930
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
69931
+ return this.trackedValues.has(key);
69932
+ }
69933
+ isHeaderPresent(domain) {
69934
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
69935
+ return this.trackedValues.has(key);
69936
+ }
69937
+ trackValue(measure, domain) {
69938
+ const key = JSON.stringify({ measure, domain: this.domainToArray(domain) });
69939
+ this.trackedValues.add(key);
69940
+ }
69941
+ trackHeader(domain) {
69942
+ const key = JSON.stringify({ domain: this.domainToArray(domain) });
69943
+ this.trackedValues.add(key);
69944
+ }
69945
+ }
69946
+
69947
+ class PivotPresencePlugin extends UIPlugin {
69948
+ static getters = ["getPivotPresenceTracker"];
69949
+ trackPresencePivotId;
69950
+ tracker;
69951
+ handle(cmd) {
69952
+ switch (cmd.type) {
69953
+ case "PIVOT_START_PRESENCE_TRACKING":
69954
+ this.tracker = new PivotPresenceTracker();
69955
+ this.trackPresencePivotId = cmd.pivotId;
69956
+ break;
69957
+ case "PIVOT_STOP_PRESENCE_TRACKING":
69958
+ this.trackPresencePivotId = undefined;
69959
+ break;
69960
+ }
69961
+ }
69962
+ getPivotPresenceTracker(pivotId) {
69963
+ if (this.trackPresencePivotId !== pivotId) {
69964
+ return undefined;
69965
+ }
69966
+ if (!this.tracker) {
69967
+ throw new Error("Tracker not initialized");
69968
+ }
69969
+ return this.tracker;
69970
+ }
69971
+ }
69972
+
69419
69973
  class SplitToColumnsPlugin extends UIPlugin {
69420
69974
  static getters = ["getAutomaticSeparator"];
69421
69975
  allowDispatch(cmd) {
@@ -72384,6 +72938,7 @@ const featurePluginRegistry = new Registry()
72384
72938
  .add("automatic_sum", AutomaticSumPlugin)
72385
72939
  .add("format", FormatPlugin)
72386
72940
  .add("insert_pivot", InsertPivotPlugin)
72941
+ .add("pivot_presence", PivotPresencePlugin)
72387
72942
  .add("split_to_columns", SplitToColumnsPlugin)
72388
72943
  .add("collaborative", CollaborativePlugin)
72389
72944
  .add("history", HistoryPlugin)
@@ -72773,11 +73328,11 @@ class BottomBarSheet extends owl.Component {
72773
73328
  if (ev.key === "Enter") {
72774
73329
  ev.preventDefault();
72775
73330
  this.stopEdition();
72776
- this.DOMFocusableElementStore.focusableElement?.focus();
73331
+ this.DOMFocusableElementStore.focus();
72777
73332
  }
72778
73333
  if (ev.key === "Escape") {
72779
73334
  this.cancelEdition();
72780
- this.DOMFocusableElementStore.focusableElement?.focus();
73335
+ this.DOMFocusableElementStore.focus();
72781
73336
  }
72782
73337
  }
72783
73338
  onMouseEventSheetName(ev) {
@@ -75338,11 +75893,13 @@ class Spreadsheet extends owl.Component {
75338
75893
  this.checkViewportSize();
75339
75894
  stores.on("store-updated", this, render);
75340
75895
  resizeObserver.observe(this.spreadsheetRef.el);
75896
+ registerChartJSExtensions();
75341
75897
  });
75342
75898
  owl.onWillUnmount(() => {
75343
75899
  this.unbindModelEvents();
75344
75900
  stores.off("store-updated", this);
75345
75901
  resizeObserver.disconnect();
75902
+ unregisterChartJsExtensions();
75346
75903
  });
75347
75904
  owl.onPatched(() => {
75348
75905
  this.checkViewportSize();
@@ -79784,6 +80341,7 @@ const components = {
79784
80341
  PivotDimensionOrder,
79785
80342
  PivotDimension,
79786
80343
  PivotLayoutConfigurator,
80344
+ PivotHTMLRenderer,
79787
80345
  PivotDeferUpdate,
79788
80346
  PivotTitleSection,
79789
80347
  CogWheelMenu,
@@ -79881,6 +80439,6 @@ exports.tokenColors = tokenColors;
79881
80439
  exports.tokenize = tokenize;
79882
80440
 
79883
80441
 
79884
- __info__.version = "18.3.1";
79885
- __info__.date = "2025-05-02T12:33:27.384Z";
79886
- __info__.hash = "7b9574b";
80442
+ __info__.version = "18.3.3";
80443
+ __info__.date = "2025-05-13T17:54:43.312Z";
80444
+ __info__.hash = "b79924a";