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