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