@odoo/o-spreadsheet 17.3.0-alpha.1 → 17.3.0-alpha.2

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.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.3.0-alpha.1
7
- * @date 2024-03-25T09:43:36.072Z
8
- * @hash 4095c41
6
+ * @version 17.3.0-alpha.2
7
+ * @date 2024-04-05T14:01:07.060Z
8
+ * @hash 8c5a229
9
9
  */
10
10
 
11
11
  (function (exports, owl) {
@@ -465,7 +465,7 @@
465
465
  }
466
466
  // Generate new Id if the item didn't exist in the dictionary
467
467
  const ids = Object.keys(itemsDic);
468
- const maxId = ids.length === 0 ? 0 : Math.max(...ids.map((id) => parseInt(id, 10)));
468
+ const maxId = ids.length === 0 ? 0 : largeMax(ids.map((id) => parseInt(id, 10)));
469
469
  itemsDic[maxId + 1] = item;
470
470
  return maxId + 1;
471
471
  }
@@ -483,7 +483,7 @@
483
483
  let timeout = undefined;
484
484
  const debounced = function () {
485
485
  const context = this;
486
- const args = arguments;
486
+ const args = Array.from(arguments);
487
487
  function later() {
488
488
  timeout = undefined;
489
489
  if (!immediate) {
@@ -685,6 +685,34 @@
685
685
  }
686
686
  return RegExp(searchValue, flags);
687
687
  }
688
+ /**
689
+ * Alternative to Math.max that works with large arrays.
690
+ * Typically useful for arrays bigger than 100k elements.
691
+ */
692
+ function largeMax(array) {
693
+ let len = array.length;
694
+ if (len < 100_000)
695
+ return Math.max(...array);
696
+ let max = -Infinity;
697
+ while (len--) {
698
+ max = array[len] > max ? array[len] : max;
699
+ }
700
+ return max;
701
+ }
702
+ /**
703
+ * Alternative to Math.min that works with large arrays.
704
+ * Typically useful for arrays bigger than 100k elements.
705
+ */
706
+ function largeMin(array) {
707
+ let len = array.length;
708
+ if (len < 100_000)
709
+ return Math.min(...array);
710
+ let min = +Infinity;
711
+ while (len--) {
712
+ min = array[len] < min ? array[len] : min;
713
+ }
714
+ return min;
715
+ }
688
716
 
689
717
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
690
718
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
@@ -2087,6 +2115,7 @@
2087
2115
  CommandResult["InvalidCopyPasteSelection"] = "InvalidCopyPasteSelection";
2088
2116
  CommandResult["NoChanges"] = "NoChanges";
2089
2117
  CommandResult["InvalidInputId"] = "InvalidInputId";
2118
+ CommandResult["SheetIsHidden"] = "SheetIsHidden";
2090
2119
  })(exports.CommandResult || (exports.CommandResult = {}));
2091
2120
 
2092
2121
  const PLAIN_TEXT_FORMAT = "@"; // see OpenXML spec §18.8.31
@@ -4558,8 +4587,9 @@
4558
4587
  * Get the default height of the cell given its style.
4559
4588
  */
4560
4589
  function getDefaultCellHeight(ctx, cell, colSize) {
4561
- if (!cell || !cell.content)
4590
+ if (!cell || (!cell.isFormula && !cell.content)) {
4562
4591
  return DEFAULT_CELL_HEIGHT;
4592
+ }
4563
4593
  const maxWidth = cell.style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
4564
4594
  const numberOfLines = cell.isFormula
4565
4595
  ? 1
@@ -6993,10 +7023,17 @@
6993
7023
  },
6994
7024
  open(url, env) {
6995
7025
  const sheetId = parseSheetUrl(url);
6996
- env.model.dispatch("ACTIVATE_SHEET", {
7026
+ const result = env.model.dispatch("ACTIVATE_SHEET", {
6997
7027
  sheetIdFrom: env.model.getters.getActiveSheetId(),
6998
7028
  sheetIdTo: sheetId,
6999
7029
  });
7030
+ if (result.isCancelledBecause("SheetIsHidden" /* CommandResult.SheetIsHidden */)) {
7031
+ env.notifyUser({
7032
+ type: "warning",
7033
+ sticky: false,
7034
+ text: _t("Cannot open the link because the linked sheet is hidden."),
7035
+ });
7036
+ }
7000
7037
  },
7001
7038
  sequence: 0,
7002
7039
  });
@@ -7112,7 +7149,7 @@
7112
7149
  }
7113
7150
  function numberCell(value, format, formattedValue) {
7114
7151
  return {
7115
- value: value || 0,
7152
+ value: value || 0, // necessary to avoid "-0" and NaN values,
7116
7153
  format,
7117
7154
  formattedValue,
7118
7155
  type: CellValueType.number,
@@ -9956,11 +9993,11 @@ stores.inject(MyMetaStore, storeInstance);
9956
9993
  }
9957
9994
  else {
9958
9995
  const range = this.getters.getRangeFromSheetXC(position.sheetId, rule.criterion.values[0]);
9959
- values = this.getters
9996
+ values = Array.from(new Set(this.getters
9960
9997
  .getRangeValues(range)
9961
9998
  .filter(isNotNull)
9962
9999
  .map((value) => value.toString())
9963
- .filter((val) => val !== "");
10000
+ .filter((val) => val !== "")));
9964
10001
  }
9965
10002
  return values.map((value) => ({ text: value }));
9966
10003
  },
@@ -19407,10 +19444,10 @@ stores.inject(MyMetaStore, storeInstance);
19407
19444
  }
19408
19445
  }
19409
19446
  return {
19410
- labels: Object.keys(labelMap),
19447
+ labels: Array.from(labelSet),
19411
19448
  dataSetsValues: datasets.map((dataset, indexOfDataset) => ({
19412
19449
  ...dataset,
19413
- data: Object.values(labelMap).map((dataOfLabel) => dataOfLabel[indexOfDataset]),
19450
+ data: Array.from(labelSet).map((label) => labelMap[label][indexOfDataset]),
19414
19451
  })),
19415
19452
  };
19416
19453
  }
@@ -19429,8 +19466,8 @@ stores.inject(MyMetaStore, storeInstance);
19429
19466
  function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
19430
19467
  const options = {
19431
19468
  // https://www.chartjs.org/docs/latest/general/responsive.html
19432
- responsive: true,
19433
- maintainAspectRatio: false,
19469
+ responsive: true, // will resize when its container is resized
19470
+ maintainAspectRatio: false, // doesn't maintain the aspect ration (width/height =2 by default) so the user has the choice of the exact layout
19434
19471
  layout: {
19435
19472
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
19436
19473
  },
@@ -19476,7 +19513,7 @@ stores.inject(MyMetaStore, storeInstance);
19476
19513
  labels: labels.map(truncateLabel),
19477
19514
  datasets: [],
19478
19515
  },
19479
- platform: undefined,
19516
+ platform: undefined, // This key is optional and will be set by chart.js
19480
19517
  plugins: [],
19481
19518
  };
19482
19519
  }
@@ -19753,7 +19790,7 @@ stores.inject(MyMetaStore, storeInstance);
19753
19790
  },
19754
19791
  y: {
19755
19792
  position: chart.verticalAxisPosition,
19756
- beginAtZero: true,
19793
+ beginAtZero: true, // the origin of the y axis is always zero
19757
19794
  ticks: {
19758
19795
  color: fontColor,
19759
19796
  callback: (value) => {
@@ -19804,6 +19841,204 @@ stores.inject(MyMetaStore, storeInstance);
19804
19841
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
19805
19842
  }
19806
19843
 
19844
+ class ComboChart extends AbstractChart {
19845
+ useBothYAxis;
19846
+ dataSets;
19847
+ labelRange;
19848
+ background;
19849
+ verticalAxisPosition;
19850
+ legendPosition;
19851
+ aggregated;
19852
+ dataSetsHaveTitle;
19853
+ type = "combo";
19854
+ constructor(definition, sheetId, getters) {
19855
+ super(definition, sheetId, getters);
19856
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
19857
+ this.labelRange = createRange(getters, sheetId, definition.labelRange);
19858
+ this.background = definition.background;
19859
+ this.verticalAxisPosition = definition.verticalAxisPosition;
19860
+ this.legendPosition = definition.legendPosition;
19861
+ this.aggregated = definition.aggregated;
19862
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
19863
+ this.useBothYAxis = definition.useBothYAxis;
19864
+ }
19865
+ static transformDefinition(definition, executed) {
19866
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
19867
+ }
19868
+ static validateChartDefinition(validator, definition) {
19869
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
19870
+ }
19871
+ getContextCreation() {
19872
+ return {
19873
+ background: this.background,
19874
+ title: this.title,
19875
+ range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
19876
+ auxiliaryRange: this.labelRange
19877
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
19878
+ : undefined,
19879
+ aggregated: this.aggregated,
19880
+ };
19881
+ }
19882
+ getDefinition() {
19883
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
19884
+ }
19885
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
19886
+ return {
19887
+ type: "combo",
19888
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
19889
+ background: this.background,
19890
+ dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
19891
+ legendPosition: this.legendPosition,
19892
+ verticalAxisPosition: this.verticalAxisPosition,
19893
+ labelRange: labelRange
19894
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
19895
+ : undefined,
19896
+ title: this.title,
19897
+ aggregated: this.aggregated,
19898
+ useBothYAxis: this.useBothYAxis,
19899
+ };
19900
+ }
19901
+ getDefinitionForExcel() {
19902
+ // Excel does not support aggregating labels
19903
+ if (this.aggregated) {
19904
+ return undefined;
19905
+ }
19906
+ const dataSets = this.dataSets
19907
+ .map((ds) => toExcelDataset(this.getters, ds))
19908
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
19909
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
19910
+ return {
19911
+ ...this.getDefinition(),
19912
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
19913
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
19914
+ dataSets,
19915
+ labelRange,
19916
+ };
19917
+ }
19918
+ updateRanges(applyChange) {
19919
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
19920
+ if (!isStale) {
19921
+ return this;
19922
+ }
19923
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
19924
+ return new ComboChart(definition, this.sheetId, this.getters);
19925
+ }
19926
+ static getDefinitionFromContextCreation(context) {
19927
+ return {
19928
+ background: context.background,
19929
+ dataSets: context.range ? context.range : [],
19930
+ dataSetsHaveTitle: false,
19931
+ aggregated: context.aggregated,
19932
+ legendPosition: "top",
19933
+ title: context.title || "",
19934
+ verticalAxisPosition: "left",
19935
+ labelRange: context.auxiliaryRange || undefined,
19936
+ type: "combo",
19937
+ useBothYAxis: false,
19938
+ };
19939
+ }
19940
+ copyForSheetId(sheetId) {
19941
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
19942
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
19943
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
19944
+ return new ComboChart(definition, sheetId, this.getters);
19945
+ }
19946
+ copyInSheetId(sheetId) {
19947
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
19948
+ return new ComboChart(definition, sheetId, this.getters);
19949
+ }
19950
+ }
19951
+ function createComboChartRuntime(chart, getters) {
19952
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
19953
+ const locale = getters.getLocale();
19954
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
19955
+ let labels = labelValues.formattedValues;
19956
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
19957
+ if (chart.dataSetsHaveTitle &&
19958
+ dataSetsValues[0] &&
19959
+ labels.length > dataSetsValues[0].data.length) {
19960
+ labels.shift();
19961
+ }
19962
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
19963
+ if (chart.aggregated) {
19964
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
19965
+ }
19966
+ const localeFormat = { format: dataSetFormat, locale };
19967
+ const fontColor = chartFontColor(chart.background);
19968
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
19969
+ const legend = {
19970
+ labels: { color: fontColor },
19971
+ };
19972
+ if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
19973
+ legend.display = false;
19974
+ }
19975
+ else {
19976
+ legend.position = chart.legendPosition;
19977
+ }
19978
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
19979
+ config.options.layout = {
19980
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
19981
+ };
19982
+ config.options.scales = {
19983
+ x: {
19984
+ ticks: {
19985
+ padding: 5,
19986
+ color: fontColor,
19987
+ },
19988
+ },
19989
+ };
19990
+ const verticalAxis = {
19991
+ beginAtZero: true, // the origin of the y axis is always zero
19992
+ ticks: {
19993
+ color: fontColor,
19994
+ callback: (value) => {
19995
+ value = Number(value);
19996
+ if (isNaN(value))
19997
+ return value;
19998
+ const { locale, format } = localeFormat;
19999
+ return formatValue(value, {
20000
+ locale,
20001
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
20002
+ });
20003
+ },
20004
+ },
20005
+ };
20006
+ if (chart.useBothYAxis) {
20007
+ config.options.scales.y = {
20008
+ ...verticalAxis,
20009
+ position: "left",
20010
+ };
20011
+ config.options.scales.y1 = {
20012
+ ...verticalAxis,
20013
+ position: "right",
20014
+ grid: {
20015
+ display: false,
20016
+ },
20017
+ };
20018
+ }
20019
+ else {
20020
+ config.options.scales.y = {
20021
+ ...verticalAxis,
20022
+ position: chart.verticalAxisPosition,
20023
+ };
20024
+ }
20025
+ const colors = new ChartColors();
20026
+ for (let [index, { label, data }] of dataSetsValues.entries()) {
20027
+ const color = colors.next();
20028
+ const dataset = {
20029
+ label,
20030
+ data,
20031
+ borderColor: color,
20032
+ backgroundColor: color,
20033
+ yAxisID: index > 0 && chart.useBothYAxis ? "y1" : "y",
20034
+ type: index === 0 ? "bar" : "line",
20035
+ order: -index,
20036
+ };
20037
+ config.data.datasets.push(dataset);
20038
+ }
20039
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
20040
+ }
20041
+
19807
20042
  function isDataRangeValid(definition) {
19808
20043
  return definition.dataRange && !rangeReference.test(definition.dataRange)
19809
20044
  ? "InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */
@@ -20142,7 +20377,7 @@ stores.inject(MyMetaStore, storeInstance);
20142
20377
  return undefined;
20143
20378
  }
20144
20379
  const labelsTimestamps = labelDates.map((date) => date.getTime());
20145
- const period = Math.max(...labelsTimestamps) - Math.min(...labelsTimestamps);
20380
+ const period = largeMax(labelsTimestamps) - largeMin(labelsTimestamps);
20146
20381
  const minUnit = getFormatMinDisplayUnit(format);
20147
20382
  if (UNIT_LENGTH.second >= UNIT_LENGTH[minUnit] && Milliseconds.inSeconds(period) < 180) {
20148
20383
  return "second";
@@ -20274,7 +20509,7 @@ stores.inject(MyMetaStore, storeInstance);
20274
20509
  },
20275
20510
  y: {
20276
20511
  position: chart.verticalAxisPosition,
20277
- beginAtZero: true,
20512
+ beginAtZero: true, // the origin of the y axis is always zero
20278
20513
  ticks: {
20279
20514
  color: fontColor,
20280
20515
  callback: (value) => {
@@ -20360,7 +20595,7 @@ stores.inject(MyMetaStore, storeInstance);
20360
20595
  const dataset = {
20361
20596
  label,
20362
20597
  data,
20363
- tension: 0,
20598
+ tension: 0, // 0 -> render straight lines, which is much faster
20364
20599
  borderColor: color,
20365
20600
  backgroundColor,
20366
20601
  pointBackgroundColor: color,
@@ -20582,7 +20817,7 @@ stores.inject(MyMetaStore, storeInstance);
20582
20817
  ...this.getDefinition(),
20583
20818
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
20584
20819
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
20585
- verticalAxisPosition: "left",
20820
+ verticalAxisPosition: "left", //TODO ExcelChartDefinition should be adapted, but can be done later
20586
20821
  dataSets,
20587
20822
  labelRange,
20588
20823
  };
@@ -20630,7 +20865,7 @@ stores.inject(MyMetaStore, storeInstance);
20630
20865
  }
20631
20866
  function getPieColors(colors, dataSetsValues) {
20632
20867
  const pieColors = [];
20633
- const maxLength = Math.max(...dataSetsValues.map((ds) => ds.data.length));
20868
+ const maxLength = largeMax(dataSetsValues.map((ds) => ds.data.length));
20634
20869
  for (let i = 0; i <= maxLength; i++) {
20635
20870
  pieColors.push(colors.next());
20636
20871
  }
@@ -20799,7 +21034,7 @@ stores.inject(MyMetaStore, storeInstance);
20799
21034
  configOptions.elements = {
20800
21035
  point: {
20801
21036
  radius: 3,
20802
- hoverRadius: 3,
21037
+ hoverRadius: 3, // chartJS seems bugged, the point starts with the "hoverRadius" value
20803
21038
  hitRadius: 8,
20804
21039
  },
20805
21040
  };
@@ -20839,6 +21074,16 @@ stores.inject(MyMetaStore, storeInstance);
20839
21074
  name: _t("Bar"),
20840
21075
  sequence: 10,
20841
21076
  });
21077
+ chartRegistry.add("combo", {
21078
+ match: (type) => type === "combo",
21079
+ createChart: (definition, sheetId, getters) => new ComboChart(definition, sheetId, getters),
21080
+ getChartRuntime: createComboChartRuntime,
21081
+ validateChartDefinition: (validator, definition) => ComboChart.validateChartDefinition(validator, definition),
21082
+ transformDefinition: (definition, executed) => ComboChart.transformDefinition(definition, executed),
21083
+ getChartDefinitionFromContextCreation: (context) => ComboChart.getDefinitionFromContextCreation(context),
21084
+ name: _t("Combo"),
21085
+ sequence: 15,
21086
+ });
20842
21087
  chartRegistry.add("line", {
20843
21088
  match: (type) => type === "line",
20844
21089
  createChart: (definition, sheetId, getters) => new LineChart(definition, sheetId, getters),
@@ -20892,6 +21137,7 @@ stores.inject(MyMetaStore, storeInstance);
20892
21137
  const chartComponentRegistry = new Registry();
20893
21138
  chartComponentRegistry.add("line", ChartJsComponent);
20894
21139
  chartComponentRegistry.add("bar", ChartJsComponent);
21140
+ chartComponentRegistry.add("combo", ChartJsComponent);
20895
21141
  chartComponentRegistry.add("pie", ChartJsComponent);
20896
21142
  chartComponentRegistry.add("gauge", GaugeChartComponent);
20897
21143
  chartComponentRegistry.add("scatter", ChartJsComponent);
@@ -23150,7 +23396,7 @@ stores.inject(MyMetaStore, storeInstance);
23150
23396
  style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" },
23151
23397
  border: { bottom: { color: colorSet.highlight, style: "thin" } },
23152
23398
  },
23153
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23399
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23154
23400
  firstRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
23155
23401
  secondRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
23156
23402
  });
@@ -23168,7 +23414,7 @@ stores.inject(MyMetaStore, storeInstance);
23168
23414
  },
23169
23415
  },
23170
23416
  headerRow: { border: { bottom: { color: colorSet.highlight, style: "medium" } } },
23171
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23417
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23172
23418
  firstRowStripe: { style: { fillColor: colorSet.light } },
23173
23419
  firstColumnStripe: { style: { fillColor: colorSet.light } },
23174
23420
  });
@@ -23187,7 +23433,7 @@ stores.inject(MyMetaStore, storeInstance);
23187
23433
  headerRow: {
23188
23434
  style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" },
23189
23435
  },
23190
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23436
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23191
23437
  firstRowStripe: { style: { fillColor: colorSet.light } },
23192
23438
  firstColumnStripe: { style: { fillColor: colorSet.light } },
23193
23439
  });
@@ -23223,7 +23469,7 @@ stores.inject(MyMetaStore, storeInstance);
23223
23469
  bottom: { color: "#000000", style: "medium" },
23224
23470
  },
23225
23471
  },
23226
- totalRow: { border: { top: { color: "#000000", style: "medium" } } },
23472
+ totalRow: { border: { top: { color: "#000000", style: "medium" } } }, // @compatibility: should be double line
23227
23473
  headerRow: {
23228
23474
  style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" },
23229
23475
  border: { bottom: { color: "#000000", style: "medium" } },
@@ -23247,7 +23493,7 @@ stores.inject(MyMetaStore, storeInstance);
23247
23493
  },
23248
23494
  style: { fillColor: colorSet.light },
23249
23495
  },
23250
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23496
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23251
23497
  firstRowStripe: { style: { fillColor: colorSet.medium } },
23252
23498
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
23253
23499
  });
@@ -23278,7 +23524,7 @@ stores.inject(MyMetaStore, storeInstance);
23278
23524
  category: "dark",
23279
23525
  colorName: colorSet.name,
23280
23526
  wholeTable: { style: { fillColor: colorSet.light } },
23281
- totalRow: { border: { top: { color: "#000000", style: "medium" } } },
23527
+ totalRow: { border: { top: { color: "#000000", style: "medium" } } }, // @compatibility: should be double line
23282
23528
  headerRow: { style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" } },
23283
23529
  firstRowStripe: { style: { fillColor: colorSet.medium } },
23284
23530
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
@@ -23443,8 +23689,8 @@ stores.inject(MyMetaStore, storeInstance);
23443
23689
  let last;
23444
23690
  const activesRows = env.model.getters.getActiveRows();
23445
23691
  if (activesRows.size !== 0) {
23446
- first = Math.min(...activesRows);
23447
- last = Math.max(...activesRows);
23692
+ first = largeMin([...activesRows]);
23693
+ last = largeMax([...activesRows]);
23448
23694
  }
23449
23695
  else {
23450
23696
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23472,8 +23718,8 @@ stores.inject(MyMetaStore, storeInstance);
23472
23718
  let last;
23473
23719
  const activeCols = env.model.getters.getActiveCols();
23474
23720
  if (activeCols.size !== 0) {
23475
- first = Math.min(...activeCols);
23476
- last = Math.max(...activeCols);
23721
+ first = largeMin([...activeCols]);
23722
+ last = largeMax([...activeCols]);
23477
23723
  }
23478
23724
  else {
23479
23725
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23501,8 +23747,8 @@ stores.inject(MyMetaStore, storeInstance);
23501
23747
  let last;
23502
23748
  const activesRows = env.model.getters.getActiveRows();
23503
23749
  if (activesRows.size !== 0) {
23504
- first = Math.min(...activesRows);
23505
- last = Math.max(...activesRows);
23750
+ first = largeMin([...activesRows]);
23751
+ last = largeMax([...activesRows]);
23506
23752
  }
23507
23753
  else {
23508
23754
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23543,8 +23789,8 @@ stores.inject(MyMetaStore, storeInstance);
23543
23789
  let last;
23544
23790
  const activeCols = env.model.getters.getActiveCols();
23545
23791
  if (activeCols.size !== 0) {
23546
- first = Math.min(...activeCols);
23547
- last = Math.max(...activeCols);
23792
+ first = largeMin([...activeCols]);
23793
+ last = largeMax([...activeCols]);
23548
23794
  }
23549
23795
  else {
23550
23796
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23585,7 +23831,7 @@ stores.inject(MyMetaStore, storeInstance);
23585
23831
  let row;
23586
23832
  let quantity;
23587
23833
  if (activeRows.size) {
23588
- row = Math.min(...activeRows);
23834
+ row = largeMin([...activeRows]);
23589
23835
  quantity = activeRows.size;
23590
23836
  }
23591
23837
  else {
@@ -23606,7 +23852,7 @@ stores.inject(MyMetaStore, storeInstance);
23606
23852
  let row;
23607
23853
  let quantity;
23608
23854
  if (activeRows.size) {
23609
- row = Math.max(...activeRows);
23855
+ row = largeMax([...activeRows]);
23610
23856
  quantity = activeRows.size;
23611
23857
  }
23612
23858
  else {
@@ -23627,7 +23873,7 @@ stores.inject(MyMetaStore, storeInstance);
23627
23873
  let column;
23628
23874
  let quantity;
23629
23875
  if (activeCols.size) {
23630
- column = Math.min(...activeCols);
23876
+ column = largeMin([...activeCols]);
23631
23877
  quantity = activeCols.size;
23632
23878
  }
23633
23879
  else {
@@ -23648,7 +23894,7 @@ stores.inject(MyMetaStore, storeInstance);
23648
23894
  let column;
23649
23895
  let quantity;
23650
23896
  if (activeCols.size) {
23651
- column = Math.max(...activeCols);
23897
+ column = largeMax([...activeCols]);
23652
23898
  quantity = activeCols.size;
23653
23899
  }
23654
23900
  else {
@@ -27254,6 +27500,22 @@ stores.inject(MyMetaStore, storeInstance);
27254
27500
  static template = "o-spreadsheet-BarChartDesignPanel";
27255
27501
  }
27256
27502
 
27503
+ class ComboChartConfigPanel extends LineBarPieConfigPanel {
27504
+ static template = "o-spreadsheet-ComboChartConfigPanel";
27505
+ get shouldUseRightAxis() {
27506
+ return _t("Use right axis for line series");
27507
+ }
27508
+ onUpdateUseRightAxis(useBothYAxis) {
27509
+ this.props.updateChart(this.props.figureId, {
27510
+ useBothYAxis,
27511
+ });
27512
+ }
27513
+ }
27514
+
27515
+ class ComboChartDesignPanel extends LineBarPieDesignPanel {
27516
+ static template = "o-spreadsheet-ComboChartDesignPanel";
27517
+ }
27518
+
27257
27519
  class GaugeChartConfigPanel extends owl.Component {
27258
27520
  static template = "o-spreadsheet-GaugeChartConfigPanel";
27259
27521
  static components = { ChartErrorSection, ChartDataSeries };
@@ -27611,6 +27873,10 @@ stores.inject(MyMetaStore, storeInstance);
27611
27873
  .add("bar", {
27612
27874
  configuration: BarConfigPanel,
27613
27875
  design: BarChartDesignPanel,
27876
+ })
27877
+ .add("combo", {
27878
+ configuration: ComboChartConfigPanel,
27879
+ design: ComboChartDesignPanel,
27614
27880
  })
27615
27881
  .add("pie", {
27616
27882
  configuration: LineBarPieConfigPanel,
@@ -30251,7 +30517,11 @@ stores.inject(MyMetaStore, storeInstance);
30251
30517
  const composerStore = useStore(ComposerStore);
30252
30518
  // The feature makes no sense if we are editing a cell, because then the selection isn't active
30253
30519
  // Stop the edition when the panel is mounted, and close the panel if the user start editing a cell
30254
- owl.useEffect(this.props.onCloseSidePanel, () => [composerStore.editionMode]);
30520
+ owl.useEffect((editionMode) => {
30521
+ if (editionMode !== "inactive") {
30522
+ this.props.onCloseSidePanel();
30523
+ }
30524
+ }, () => [composerStore.editionMode]);
30255
30525
  owl.onMounted(() => {
30256
30526
  composerStore.stopEdition();
30257
30527
  });
@@ -31960,6 +32230,9 @@ stores.inject(MyMetaStore, storeInstance);
31960
32230
  this.assistantState.allowCellSelectionBehind = false;
31961
32231
  }, 2000);
31962
32232
  }
32233
+ get formulaArgSeparator() {
32234
+ return this.env.model.getters.getLocale().formulaArgSeparator + " ";
32235
+ }
31963
32236
  }
31964
32237
 
31965
32238
  const functions$2 = functionRegistry.content;
@@ -32119,6 +32392,12 @@ stores.inject(MyMetaStore, storeInstance);
32119
32392
  owl.useEffect(() => {
32120
32393
  this.processContent();
32121
32394
  });
32395
+ owl.onPatched(() => {
32396
+ // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
32397
+ if (this.composerStore.editionMode === "inactive") {
32398
+ this.processTokenAtCursor();
32399
+ }
32400
+ });
32122
32401
  }
32123
32402
  // ---------------------------------------------------------------------------
32124
32403
  // Handlers
@@ -34098,11 +34377,6 @@ stores.inject(MyMetaStore, storeInstance);
34098
34377
  height: 10000px;
34099
34378
  background-color: ${SELECTION_BORDER_COLOR};
34100
34379
  }
34101
- .o-unhide-buttons {
34102
- width: fit-content;
34103
- gap: 5px;
34104
- transform: translate(-50%, 0);
34105
- }
34106
34380
  .o-unhide:hover {
34107
34381
  z-index: ${ComponentsImportance.Grid + 1};
34108
34382
  background-color: lightgrey;
@@ -34264,10 +34538,6 @@ stores.inject(MyMetaStore, storeInstance);
34264
34538
  height: 1px;
34265
34539
  background-color: ${SELECTION_BORDER_COLOR};
34266
34540
  }
34267
- .o-unhide-buttons {
34268
- height: fit-content;
34269
- transform: translate(0, -50%);
34270
- }
34271
34541
  .o-unhide:hover {
34272
34542
  z-index: ${ComponentsImportance.Grid + 1};
34273
34543
  background-color: lightgrey;
@@ -35475,7 +35745,7 @@ stores.inject(MyMetaStore, storeInstance);
35475
35745
  onScroll(offset) {
35476
35746
  const { scrollX } = this.env.model.getters.getActiveSheetDOMScrollInfo();
35477
35747
  this.env.model.dispatch("SET_VIEWPORT_OFFSET", {
35478
- offsetX: scrollX,
35748
+ offsetX: scrollX, // offsetX is the same
35479
35749
  offsetY: offset,
35480
35750
  });
35481
35751
  }
@@ -35763,8 +36033,8 @@ stores.inject(MyMetaStore, storeInstance);
35763
36033
  "Ctrl+Shift+L": () => this.setHorizontalAlign("left"),
35764
36034
  "Ctrl+Shift+R": () => this.setHorizontalAlign("right"),
35765
36035
  "Ctrl+Shift+V": () => PASTE_AS_VALUE_ACTION(this.env),
35766
- "Ctrl+Shift+<": () => this.clearFormatting(),
35767
- "Ctrl+<": () => this.clearFormatting(),
36036
+ "Ctrl+Shift+<": () => this.clearFormatting(), // for qwerty
36037
+ "Ctrl+<": () => this.clearFormatting(), // for azerty
35768
36038
  "Ctrl+Shift+ ": () => {
35769
36039
  this.env.model.selection.selectAll();
35770
36040
  },
@@ -36200,6 +36470,7 @@ stores.inject(MyMetaStore, storeInstance);
36200
36470
  "surfaceChart",
36201
36471
  "surface3DChart",
36202
36472
  "bubbleChart",
36473
+ "comboChart",
36203
36474
  ];
36204
36475
 
36205
36476
  /** In XLSX color format (no #) */
@@ -36531,10 +36802,10 @@ stores.inject(MyMetaStore, storeInstance);
36531
36802
  const CF_TYPE_CONVERSION_MAP = {
36532
36803
  aboveAverage: undefined,
36533
36804
  expression: undefined,
36534
- cellIs: undefined,
36535
- colorScale: undefined,
36805
+ cellIs: undefined, // exist but isn't an operator in o_spreadsheet
36806
+ colorScale: undefined, // exist but isn't an operator in o_spreadsheet
36536
36807
  dataBar: undefined,
36537
- iconSet: undefined,
36808
+ iconSet: undefined, // exist but isn't an operator in o_spreadsheet
36538
36809
  top10: undefined,
36539
36810
  uniqueValues: undefined,
36540
36811
  duplicateValues: undefined,
@@ -36611,6 +36882,7 @@ stores.inject(MyMetaStore, storeInstance);
36611
36882
  surfaceChart: undefined,
36612
36883
  surface3DChart: undefined,
36613
36884
  bubbleChart: undefined,
36885
+ comboChart: "combo",
36614
36886
  };
36615
36887
  /** Conversion map for the SUBTOTAL(index, formula) function in xlsx, index <=> actual function*/
36616
36888
  const SUBTOTAL_FUNCTION_CONVERSION_MAP = {
@@ -36769,7 +37041,7 @@ stores.inject(MyMetaStore, storeInstance);
36769
37041
  61: "993366",
36770
37042
  62: "333399",
36771
37043
  63: "333333",
36772
- 64: "000000",
37044
+ 64: "000000", // system foreground
36773
37045
  65: "FFFFFF", // system background
36774
37046
  };
36775
37047
  const IMAGE_MIMETYPE_TO_EXTENSION_MAPPING = {
@@ -37961,7 +38233,7 @@ stores.inject(MyMetaStore, storeInstance);
37961
38233
  function getSheetDims(sheet) {
37962
38234
  const dims = [0, 0];
37963
38235
  for (let row of sheet.rows) {
37964
- dims[0] = Math.max(dims[0], ...row.cells.map((cell) => toCartesian(cell.xc).col));
38236
+ dims[0] = Math.max(dims[0], largeMax(row.cells.map((cell) => toCartesian(cell.xc).col)));
37965
38237
  dims[1] = Math.max(dims[1], row.index);
37966
38238
  }
37967
38239
  dims[0] = Math.max(dims[0], EXCEL_IMPORT_DEFAULT_NUMBER_OF_COLS);
@@ -38827,6 +39099,9 @@ stores.inject(MyMetaStore, storeInstance);
38827
39099
  if (!CHART_TYPE_CONVERSION_MAP[chartType]) {
38828
39100
  throw new Error(`Unsupported chart type ${chartType}`);
38829
39101
  }
39102
+ if (CHART_TYPE_CONVERSION_MAP[chartType] === "combo") {
39103
+ return this.extractComboChart(rootChartElement);
39104
+ }
38830
39105
  // Title can be separated into multiple xml elements (for styling and such), we only import the text
38831
39106
  const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:title a:t" }, (textElement) => {
38832
39107
  return textElement.textContent || "";
@@ -38855,6 +39130,37 @@ stores.inject(MyMetaStore, storeInstance);
38855
39130
  };
38856
39131
  })[0];
38857
39132
  }
39133
+ extractComboChart(chartElement) {
39134
+ // Title can be separated into multiple xml elements (for styling and such), we only import the text
39135
+ const chartTitle = this.mapOnElements({ parent: chartElement, query: "c:title a:t" }, (textElement) => {
39136
+ return textElement.textContent || "";
39137
+ }).join("");
39138
+ const barChartGrouping = this.extractChildAttr(chartElement, "c:grouping", "val", {
39139
+ default: "clustered",
39140
+ }).asString();
39141
+ return {
39142
+ title: chartTitle,
39143
+ type: "combo",
39144
+ dataSets: [
39145
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`)),
39146
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`)),
39147
+ ],
39148
+ labelRange: this.extractChildTextContent(chartElement, "c:ser c:cat c:f"),
39149
+ backgroundColor: this.extractChildAttr(chartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
39150
+ default: "ffffff",
39151
+ }).asString(),
39152
+ verticalAxisPosition: this.extractChildAttr(chartElement, "c:valAx > c:axPos", "val", {
39153
+ default: "l",
39154
+ }).asString() === "r"
39155
+ ? "right"
39156
+ : "left",
39157
+ legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(chartElement, "c:legendPos", "val", {
39158
+ default: "b",
39159
+ }).asString()],
39160
+ stacked: barChartGrouping === "stacked",
39161
+ fontColor: "000000",
39162
+ };
39163
+ }
38858
39164
  extractChartDatasets(chartElement) {
38859
39165
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
38860
39166
  return {
@@ -38872,12 +39178,21 @@ stores.inject(MyMetaStore, storeInstance);
38872
39178
  if (!plotAreaElement) {
38873
39179
  throw new Error("Missing plot area in the chart definition.");
38874
39180
  }
39181
+ let globalTag = undefined;
38875
39182
  for (let child of plotAreaElement.children) {
38876
39183
  const tag = removeTagEscapedNamespaces(child.tagName);
38877
39184
  if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
38878
- return tag;
39185
+ if (!globalTag) {
39186
+ globalTag = tag;
39187
+ }
39188
+ else if (globalTag !== tag) {
39189
+ globalTag = "comboChart";
39190
+ }
38879
39191
  }
38880
39192
  }
39193
+ if (globalTag) {
39194
+ return globalTag;
39195
+ }
38881
39196
  throw new Error("Unknown chart type");
38882
39197
  }
38883
39198
  }
@@ -42468,6 +42783,9 @@ stores.inject(MyMetaStore, storeInstance);
42468
42783
  if (newRule.criterion.type === "isBoolean") {
42469
42784
  this.setCenterStyleToBooleanCells(newRule);
42470
42785
  }
42786
+ else if (newRule.criterion.type === "isValueInList") {
42787
+ newRule.criterion.values = Array.from(new Set(newRule.criterion.values));
42788
+ }
42471
42789
  const adaptedRules = this.removeRangesFromRules(sheetId, newRule.ranges, rules);
42472
42790
  const ruleIndex = adaptedRules.findIndex((rule) => rule.id === newRule.id);
42473
42791
  if (ruleIndex !== -1) {
@@ -42864,7 +43182,7 @@ stores.inject(MyMetaStore, storeInstance);
42864
43182
  if (hiddenElements.size >= elements) {
42865
43183
  return "TooManyHiddenElements" /* CommandResult.TooManyHiddenElements */;
42866
43184
  }
42867
- else if (Math.min(...cmd.elements) < 0 || Math.max(...cmd.elements) > elements) {
43185
+ else if (largeMin(cmd.elements) < 0 || largeMax(cmd.elements) > elements) {
42868
43186
  return "InvalidHeaderIndex" /* CommandResult.InvalidHeaderIndex */;
42869
43187
  }
42870
43188
  else {
@@ -43193,7 +43511,6 @@ stores.inject(MyMetaStore, storeInstance);
43193
43511
  "isInSameMerge",
43194
43512
  "isMergeHidden",
43195
43513
  "getMainCellPosition",
43196
- "getBottomLeftCell",
43197
43514
  "expandZone",
43198
43515
  "doesIntersectMerge",
43199
43516
  "doesColumnsHaveCommonMerges",
@@ -43375,13 +43692,6 @@ stores.inject(MyMetaStore, storeInstance);
43375
43692
  const mergeTopLeftPos = this.getMerge(position).topLeft;
43376
43693
  return { sheetId: position.sheetId, col: mergeTopLeftPos.col, row: mergeTopLeftPos.row };
43377
43694
  }
43378
- getBottomLeftCell(position) {
43379
- if (!this.isInMerge(position)) {
43380
- return position;
43381
- }
43382
- const { bottom, left } = this.getMerge(position);
43383
- return { sheetId: position.sheetId, col: left, row: bottom };
43384
- }
43385
43695
  isMergeHidden(sheetId, merge) {
43386
43696
  const hiddenColsGroups = this.getters.getHiddenColsGroups(sheetId);
43387
43697
  const hiddenRowsGroups = this.getters.getHiddenRowsGroups(sheetId);
@@ -43682,8 +43992,8 @@ stores.inject(MyMetaStore, storeInstance);
43682
43992
  let newRange = range;
43683
43993
  let changeType = "NONE";
43684
43994
  for (let group of groups) {
43685
- const min = Math.min(...group);
43686
- const max = Math.max(...group);
43995
+ const min = largeMin(group);
43996
+ const max = largeMax(group);
43687
43997
  if (range.zone[start] <= min && min <= range.zone[end]) {
43688
43998
  const toRemove = Math.min(range.zone[end], max) - min + 1;
43689
43999
  changeType = "RESIZE";
@@ -44065,7 +44375,6 @@ stores.inject(MyMetaStore, storeInstance);
44065
44375
  "getSheetIds",
44066
44376
  "getVisibleSheetIds",
44067
44377
  "isSheetVisible",
44068
- "getEvaluationSheets",
44069
44378
  "doesHeaderExist",
44070
44379
  "doesHeadersExist",
44071
44380
  "getCell",
@@ -44132,8 +44441,8 @@ stores.inject(MyMetaStore, storeInstance);
44132
44441
  }
44133
44442
  return "Success" /* CommandResult.Success */;
44134
44443
  case "REMOVE_COLUMNS_ROWS": {
44135
- const min = Math.min(...cmd.elements);
44136
- const max = Math.max(...cmd.elements);
44444
+ const min = largeMin(cmd.elements);
44445
+ const max = largeMax(cmd.elements);
44137
44446
  if (min < 0 || !this.doesHeaderExist(cmd.sheetId, cmd.dimension, max)) {
44138
44447
  return "InvalidHeaderIndex" /* CommandResult.InvalidHeaderIndex */;
44139
44448
  }
@@ -44324,9 +44633,6 @@ stores.inject(MyMetaStore, storeInstance);
44324
44633
  getVisibleSheetIds() {
44325
44634
  return this.orderedSheetIds.filter(this.isSheetVisible.bind(this));
44326
44635
  }
44327
- getEvaluationSheets() {
44328
- return this.sheets;
44329
- }
44330
44636
  doesHeaderExist(sheetId, dimension, index) {
44331
44637
  return dimension === "COL"
44332
44638
  ? index >= 0 && index < this.getNumberCols(sheetId)
@@ -44335,13 +44641,6 @@ stores.inject(MyMetaStore, storeInstance);
44335
44641
  doesHeadersExist(sheetId, dimension, headerIndexes) {
44336
44642
  return headerIndexes.every((index) => this.doesHeaderExist(sheetId, dimension, index));
44337
44643
  }
44338
- getRow(sheetId, index) {
44339
- const row = this.getSheet(sheetId).rows[index];
44340
- if (!row) {
44341
- throw new Error(`Row ${row} not found.`);
44342
- }
44343
- return row;
44344
- }
44345
44644
  getCell({ sheetId, col, row }) {
44346
44645
  const sheet = this.tryGetSheet(sheetId);
44347
44646
  const cellId = sheet?.rows[row]?.cells[col];
@@ -45910,12 +46209,6 @@ stores.inject(MyMetaStore, storeInstance);
45910
46209
  : _t("Function %s expects its parameters to be single values or single cell references, not ranges.", functionName.toString()));
45911
46210
  }
45912
46211
  const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
45913
- return this.readCell(position);
45914
- }
45915
- readCell(position) {
45916
- if (!this.getters.tryGetSheet(position.sheetId)) {
45917
- throw new EvaluationError(_t("Invalid sheet name"));
45918
- }
45919
46212
  return this.computeCell(position);
45920
46213
  }
45921
46214
  /**
@@ -45951,7 +46244,7 @@ stores.inject(MyMetaStore, storeInstance);
45951
46244
  matrix[colIndex] = new Array(height);
45952
46245
  for (let row = _zone.top; row <= _zone.bottom; row++) {
45953
46246
  const rowIndex = row - _zone.top;
45954
- matrix[colIndex][rowIndex] = this.readCell({ sheetId, col, row });
46247
+ matrix[colIndex][rowIndex] = this.computeCell({ sheetId, col, row });
45955
46248
  }
45956
46249
  }
45957
46250
  this.rangeCache[cacheKey] = matrix;
@@ -47059,15 +47352,15 @@ stores.inject(MyMetaStore, storeInstance);
47059
47352
  getEvaluatedCell(position) {
47060
47353
  return this.evaluatedCells.get(position) || EMPTY_CELL;
47061
47354
  }
47062
- getSpreadPositionsOf(position) {
47355
+ getSpreadZone(position) {
47063
47356
  if (!this.spreadingRelations.isArrayFormula(position)) {
47064
- return [];
47357
+ return undefined;
47065
47358
  }
47066
47359
  if (this.evaluatedCells.get(position)?.type === CellValueType.error) {
47067
- return [position];
47360
+ return positionToZone(position);
47068
47361
  }
47069
47362
  const spreadPositions = Array.from(this.spreadingRelations.getArrayResultPositions(position));
47070
- return [position, ...spreadPositions];
47363
+ return union(positionToZone(position), unionPositionsToZone(spreadPositions));
47071
47364
  }
47072
47365
  getEvaluatedPositions() {
47073
47366
  return this.evaluatedCells.keys();
@@ -47472,7 +47765,7 @@ stores.inject(MyMetaStore, storeInstance);
47472
47765
  "getEvaluatedCell",
47473
47766
  "getEvaluatedCells",
47474
47767
  "getEvaluatedCellsInZone",
47475
- "getSpreadPositionsOf",
47768
+ "getSpreadZone",
47476
47769
  "getArrayFormulaSpreadingOn",
47477
47770
  "isEmpty",
47478
47771
  ];
@@ -47578,8 +47871,11 @@ stores.inject(MyMetaStore, storeInstance);
47578
47871
  getEvaluatedCellsInZone(sheetId, zone) {
47579
47872
  return positions(zone).map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId, col, row }));
47580
47873
  }
47581
- getSpreadPositionsOf(position) {
47582
- return this.evaluator.getSpreadPositionsOf(position);
47874
+ /**
47875
+ * Return the spread zone the position is part of, if any
47876
+ */
47877
+ getSpreadZone(position) {
47878
+ return this.evaluator.getSpreadZone(position);
47583
47879
  }
47584
47880
  getArrayFormulaSpreadingOn(position) {
47585
47881
  return this.evaluator.getArrayFormulaSpreadingOn(position);
@@ -47619,7 +47915,7 @@ stores.inject(MyMetaStore, storeInstance);
47619
47915
  ? getItemId(newFormat, data.formats)
47620
47916
  : exportedCellData.format;
47621
47917
  let content;
47622
- if (formulaCell instanceof FormulaCellWithDependencies) {
47918
+ if (isFormula && formulaCell instanceof FormulaCellWithDependencies) {
47623
47919
  content = formulaCell.contentWithFixedReferences;
47624
47920
  }
47625
47921
  else {
@@ -47667,17 +47963,17 @@ stores.inject(MyMetaStore, storeInstance);
47667
47963
  */
47668
47964
  function sortWithClusters(colorsToSort) {
47669
47965
  const clusters = [
47670
- { leadColor: rgba(255, 0, 0), colors: [] },
47671
- { leadColor: rgba(255, 128, 0), colors: [] },
47672
- { leadColor: rgba(128, 128, 0), colors: [] },
47673
- { leadColor: rgba(128, 255, 0), colors: [] },
47674
- { leadColor: rgba(0, 255, 0), colors: [] },
47675
- { leadColor: rgba(0, 255, 128), colors: [] },
47676
- { leadColor: rgba(0, 255, 255), colors: [] },
47677
- { leadColor: rgba(0, 127, 255), colors: [] },
47678
- { leadColor: rgba(0, 0, 255), colors: [] },
47679
- { leadColor: rgba(127, 0, 255), colors: [] },
47680
- { leadColor: rgba(128, 0, 128), colors: [] },
47966
+ { leadColor: rgba(255, 0, 0), colors: [] }, // red
47967
+ { leadColor: rgba(255, 128, 0), colors: [] }, // orange
47968
+ { leadColor: rgba(128, 128, 0), colors: [] }, // yellow
47969
+ { leadColor: rgba(128, 255, 0), colors: [] }, // chartreuse
47970
+ { leadColor: rgba(0, 255, 0), colors: [] }, // green
47971
+ { leadColor: rgba(0, 255, 128), colors: [] }, // spring green
47972
+ { leadColor: rgba(0, 255, 255), colors: [] }, // cyan
47973
+ { leadColor: rgba(0, 127, 255), colors: [] }, // azure
47974
+ { leadColor: rgba(0, 0, 255), colors: [] }, // blue
47975
+ { leadColor: rgba(127, 0, 255), colors: [] }, // violet
47976
+ { leadColor: rgba(128, 0, 128), colors: [] }, // magenta
47681
47977
  { leadColor: rgba(255, 0, 128), colors: [] }, // rose
47682
47978
  ];
47683
47979
  for (const color of colorsToSort.map(colorToRGBA)) {
@@ -48068,13 +48364,13 @@ stores.inject(MyMetaStore, storeInstance);
48068
48364
  .map((cell) => cell.value);
48069
48365
  switch (threshold.type) {
48070
48366
  case "value":
48071
- const result = functionName === "max" ? Math.max(...rangeValues) : Math.min(...rangeValues);
48367
+ const result = functionName === "max" ? largeMax(rangeValues) : largeMin(rangeValues);
48072
48368
  return result;
48073
48369
  case "number":
48074
48370
  return Number(threshold.value);
48075
48371
  case "percentage":
48076
- const min = Math.min(...rangeValues);
48077
- const max = Math.max(...rangeValues);
48372
+ const min = largeMin(rangeValues);
48373
+ const max = largeMax(rangeValues);
48078
48374
  const delta = max - min;
48079
48375
  return min + (delta * Number(threshold.value)) / 100;
48080
48376
  case "percentile":
@@ -48541,8 +48837,8 @@ stores.inject(MyMetaStore, storeInstance);
48541
48837
  else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
48542
48838
  return true;
48543
48839
  }
48544
- const spreadPositions = this.getters.getSpreadPositionsOf(parentSpreadingCell);
48545
- return deepEquals(unionZone, unionPositionsToZone(spreadPositions));
48840
+ const zone = this.getters.getSpreadZone(parentSpreadingCell);
48841
+ return deepEquals(unionZone, zone);
48546
48842
  }
48547
48843
  coreTableToTable(sheetId, table) {
48548
48844
  if (table.type !== "dynamic") {
@@ -48550,8 +48846,7 @@ stores.inject(MyMetaStore, storeInstance);
48550
48846
  }
48551
48847
  const tableZone = table.range.zone;
48552
48848
  const tablePosition = { sheetId, col: tableZone.left, row: tableZone.top };
48553
- const spreadPositions = this.getters.getSpreadPositionsOf(tablePosition);
48554
- const zone = spreadPositions.length ? unionPositionsToZone(spreadPositions) : table.range.zone;
48849
+ const zone = this.getters.getSpreadZone(tablePosition) ?? table.range.zone;
48555
48850
  const range = this.getters.getRangeFromZone(sheetId, zone);
48556
48851
  const filters = this.getDynamicTableFilters(sheetId, table, zone);
48557
48852
  return { id: table.id, range, filters, config: table.config };
@@ -49001,8 +49296,7 @@ stores.inject(MyMetaStore, storeInstance);
49001
49296
  let row = zone.bottom;
49002
49297
  if (col > 0) {
49003
49298
  let leftPosition = { sheetId, col: col - 1, row };
49004
- while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
49005
- this.getters.getCell(leftPosition)?.content) {
49299
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty) {
49006
49300
  row += 1;
49007
49301
  leftPosition = { sheetId, col: col - 1, row };
49008
49302
  }
@@ -49011,8 +49305,7 @@ stores.inject(MyMetaStore, storeInstance);
49011
49305
  col = zone.right;
49012
49306
  if (col <= this.getters.getNumberCols(sheetId)) {
49013
49307
  let rightPosition = { sheetId, col: col + 1, row };
49014
- while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
49015
- this.getters.getCell(rightPosition)?.content) {
49308
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty) {
49016
49309
  row += 1;
49017
49310
  rightPosition = { sheetId, col: col + 1, row };
49018
49311
  }
@@ -49322,13 +49615,13 @@ stores.inject(MyMetaStore, storeInstance);
49322
49615
  const cells = this.getters.getEvaluatedCellsInZone(sheet.id, zone);
49323
49616
  const cellPositions = range(end, -1, -1);
49324
49617
  const invalidCells = cellPositions.filter((position) => cells[position] && !cells[position].isAutoSummable);
49325
- const maxValidPosition = Math.max(...invalidCells);
49618
+ const maxValidPosition = largeMax(invalidCells);
49326
49619
  const numberSequences = groupConsecutive(cellPositions.filter((position) => this.isNumber(cells[position])));
49327
49620
  const firstSequence = numberSequences[0] || [];
49328
- if (Math.max(...firstSequence) < maxValidPosition) {
49621
+ if (largeMax(firstSequence) < maxValidPosition) {
49329
49622
  return Infinity;
49330
49623
  }
49331
- return Math.min(...firstSequence);
49624
+ return largeMin(firstSequence);
49332
49625
  }
49333
49626
  shouldFindData(sheetId, zone) {
49334
49627
  return this.getters.isEmpty(sheetId, zone) || this.getters.isSingleCellOrMerge(sheetId, zone);
@@ -50903,8 +51196,6 @@ stores.inject(MyMetaStore, storeInstance);
50903
51196
  static getters = [
50904
51197
  "doesCellHaveGridIcon",
50905
51198
  "getCellWidth",
50906
- "getCellComputedBorder",
50907
- "getCellComputedStyle",
50908
51199
  "getTextWidth",
50909
51200
  "getCellText",
50910
51201
  "getCellMultiLineText",
@@ -50948,7 +51239,7 @@ stores.inject(MyMetaStore, storeInstance);
50948
51239
  // Getters
50949
51240
  // ---------------------------------------------------------------------------
50950
51241
  getCellWidth(position) {
50951
- const style = this.getCellComputedStyle(position);
51242
+ const style = this.getters.getCellComputedStyle(position);
50952
51243
  let contentWidth = 0;
50953
51244
  const content = this.getters.getEvaluatedCell(position).formattedValue;
50954
51245
  if (content) {
@@ -51041,35 +51332,12 @@ stores.inject(MyMetaStore, storeInstance);
51041
51332
  */
51042
51333
  isCellEmpty(position) {
51043
51334
  const mainPosition = this.getters.getMainCellPosition(position);
51044
- return !(this.getters.getCorrespondingFormulaCell(mainPosition) ||
51045
- this.getters.getCell(mainPosition)?.content);
51046
- }
51047
- getCellComputedBorder(position) {
51048
- const cellBorder = this.getters.getCellBorder(position) || {};
51049
- const cellTableBorder = this.getters.getCellTableBorder(position) || {};
51050
- // Use removeFalsyAttributes to avoid overwriting borders with undefined values
51051
- const border = { ...cellTableBorder, ...removeFalsyAttributes(cellBorder) };
51052
- return isObjectEmptyRecursive(border) ? null : border;
51053
- }
51054
- getCellComputedStyle(position) {
51055
- const cell = this.getters.getCell(position);
51056
- const cfStyle = this.getters.getCellConditionalFormatStyle(position);
51057
- const tableStyle = this.getters.getCellTableStyle(position);
51058
- const computedStyle = {
51059
- ...removeFalsyAttributes(tableStyle),
51060
- ...removeFalsyAttributes(cell?.style),
51061
- ...removeFalsyAttributes(cfStyle),
51062
- };
51063
- const evaluatedCell = this.getters.getEvaluatedCell(position);
51064
- if (evaluatedCell.link && !computedStyle.textColor) {
51065
- computedStyle.textColor = LINK_COLOR;
51066
- }
51067
- return computedStyle;
51335
+ return this.getters.getEvaluatedCell(mainPosition).type === CellValueType.empty;
51068
51336
  }
51069
51337
  getColMaxWidth(sheetId, index) {
51070
51338
  const cellsPositions = positions(this.getters.getColsZone(sheetId, index, index));
51071
51339
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
51072
- return Math.max(0, ...sizes);
51340
+ return Math.max(0, largeMax(sizes));
51073
51341
  }
51074
51342
  /**
51075
51343
  * Check that any "sheetId" in the command matches an existing
@@ -51098,6 +51366,236 @@ stores.inject(MyMetaStore, storeInstance);
51098
51366
  }
51099
51367
  }
51100
51368
 
51369
+ class TableStylePlugin extends UIPlugin {
51370
+ static getters = ["getCellTableStyle", "getCellTableBorder"];
51371
+ tableStyles = {};
51372
+ handle(cmd) {
51373
+ if (invalidateEvaluationCommands.has(cmd.type) ||
51374
+ (cmd.type === "UPDATE_CELL" && "content" in cmd) ||
51375
+ cmd.type === "EVALUATE_CELLS") {
51376
+ this.tableStyles = {};
51377
+ return;
51378
+ }
51379
+ if (doesCommandInvalidatesTableStyle(cmd)) {
51380
+ delete this.tableStyles[cmd.sheetId];
51381
+ return;
51382
+ }
51383
+ }
51384
+ finalize() {
51385
+ for (const sheetId of this.getters.getSheetIds()) {
51386
+ if (!this.tableStyles[sheetId]) {
51387
+ this.tableStyles[sheetId] = {};
51388
+ }
51389
+ for (const table of this.getters.getTables(sheetId)) {
51390
+ if (!this.tableStyles[sheetId][table.id]) {
51391
+ this.tableStyles[sheetId][table.id] = this.computeTableStyle(sheetId, table);
51392
+ }
51393
+ }
51394
+ }
51395
+ }
51396
+ getCellTableStyle(position) {
51397
+ const table = this.getters.getTable(position);
51398
+ if (!table) {
51399
+ return undefined;
51400
+ }
51401
+ return this.tableStyles[position.sheetId][table.id]().styles[position.col]?.[position.row];
51402
+ }
51403
+ getCellTableBorder(position) {
51404
+ const table = this.getters.getTable(position);
51405
+ if (!table) {
51406
+ return undefined;
51407
+ }
51408
+ return this.tableStyles[position.sheetId][table.id]().borders[position.col]?.[position.row];
51409
+ }
51410
+ computeTableStyle(sheetId, table) {
51411
+ return lazy(() => {
51412
+ const { config, numberOfCols, numberOfRows } = this.getTableRuntimeConfig(sheetId, table);
51413
+ const relativeTableStyle = getComputedTableStyle(config, numberOfCols, numberOfRows);
51414
+ // Return the style with sheet coordinates instead of tables coordinates
51415
+ const mapping = this.getTableMapping(sheetId, table);
51416
+ const absoluteTableStyle = { borders: {}, styles: {} };
51417
+ for (let col = 0; col < numberOfCols; col++) {
51418
+ const colInSheet = mapping.colMapping[col];
51419
+ absoluteTableStyle.borders[colInSheet] = {};
51420
+ absoluteTableStyle.styles[colInSheet] = {};
51421
+ for (let row = 0; row < numberOfRows; row++) {
51422
+ const rowInSheet = mapping.rowMapping[row];
51423
+ absoluteTableStyle.borders[colInSheet][rowInSheet] = relativeTableStyle.borders[col][row];
51424
+ absoluteTableStyle.styles[colInSheet][rowInSheet] = relativeTableStyle.styles[col][row];
51425
+ }
51426
+ }
51427
+ return absoluteTableStyle;
51428
+ });
51429
+ }
51430
+ /**
51431
+ * Get the actual table config that will be used to compute the table style. It is different from
51432
+ * the config of the table because of hidden rows and columns in the sheet. For example remove the
51433
+ * hidden rows from config.numberOfHeaders.
51434
+ */
51435
+ getTableRuntimeConfig(sheetId, table) {
51436
+ const tableZone = table.range.zone;
51437
+ const config = { ...table.config };
51438
+ let numberOfCols = tableZone.right - tableZone.left + 1;
51439
+ let numberOfRows = tableZone.bottom - tableZone.top + 1;
51440
+ for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51441
+ if (!this.getters.isRowHidden(sheetId, row)) {
51442
+ continue;
51443
+ }
51444
+ numberOfRows--;
51445
+ if (row - tableZone.top < table.config.numberOfHeaders) {
51446
+ config.numberOfHeaders--;
51447
+ if (config.numberOfHeaders < 0) {
51448
+ config.numberOfHeaders = 0;
51449
+ }
51450
+ }
51451
+ if (row === tableZone.bottom) {
51452
+ config.totalRow = false;
51453
+ }
51454
+ }
51455
+ for (let col = tableZone.left; col <= tableZone.right; col++) {
51456
+ if (!this.getters.isColHidden(sheetId, col)) {
51457
+ continue;
51458
+ }
51459
+ numberOfCols--;
51460
+ if (col === tableZone.left) {
51461
+ config.firstColumn = false;
51462
+ }
51463
+ if (col === tableZone.right) {
51464
+ config.lastColumn = false;
51465
+ }
51466
+ }
51467
+ return {
51468
+ config,
51469
+ numberOfCols,
51470
+ numberOfRows,
51471
+ };
51472
+ }
51473
+ /**
51474
+ * Get a mapping: relative col/row position in the table <=> col/row in the sheet
51475
+ */
51476
+ getTableMapping(sheetId, table) {
51477
+ const colMapping = {};
51478
+ const rowMapping = {};
51479
+ let colOffset = 0;
51480
+ let rowOffset = 0;
51481
+ const tableZone = table.range.zone;
51482
+ for (let col = tableZone.left; col <= tableZone.right; col++) {
51483
+ if (this.getters.isColHidden(sheetId, col)) {
51484
+ continue;
51485
+ }
51486
+ colMapping[colOffset] = col;
51487
+ colOffset++;
51488
+ for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51489
+ if (this.getters.isRowHidden(sheetId, row)) {
51490
+ continue;
51491
+ }
51492
+ rowMapping[rowOffset] = row;
51493
+ rowOffset++;
51494
+ }
51495
+ }
51496
+ return {
51497
+ colMapping,
51498
+ rowMapping,
51499
+ };
51500
+ }
51501
+ }
51502
+ const invalidateTableStyleCommands = [
51503
+ "HIDE_COLUMNS_ROWS",
51504
+ "UNHIDE_COLUMNS_ROWS",
51505
+ "UNFOLD_HEADER_GROUP",
51506
+ "FOLD_HEADER_GROUP",
51507
+ "FOLD_ALL_HEADER_GROUPS",
51508
+ "UNFOLD_ALL_HEADER_GROUPS",
51509
+ "CREATE_TABLE",
51510
+ "UPDATE_TABLE",
51511
+ "UPDATE_FILTER",
51512
+ ];
51513
+ const invalidateTableStyleCommandsSet = new Set(invalidateTableStyleCommands);
51514
+ function doesCommandInvalidatesTableStyle(cmd) {
51515
+ return invalidateTableStyleCommandsSet.has(cmd.type);
51516
+ }
51517
+
51518
+ class CellComputedStylePlugin extends UIPlugin {
51519
+ static getters = ["getCellComputedBorder", "getCellComputedStyle"];
51520
+ styles = {};
51521
+ borders = {};
51522
+ handle(cmd) {
51523
+ if (invalidateEvaluationCommands.has(cmd.type) ||
51524
+ cmd.type === "UPDATE_CELL" ||
51525
+ cmd.type === "EVALUATE_CELLS") {
51526
+ this.styles = {};
51527
+ this.borders = {};
51528
+ return;
51529
+ }
51530
+ if (doesCommandInvalidatesTableStyle(cmd)) {
51531
+ delete this.styles[cmd.sheetId];
51532
+ delete this.borders[cmd.sheetId];
51533
+ return;
51534
+ }
51535
+ if (invalidateCFEvaluationCommands.has(cmd.type)) {
51536
+ this.styles = {};
51537
+ return;
51538
+ }
51539
+ }
51540
+ getCellComputedBorder(position) {
51541
+ const { sheetId, row, col } = position;
51542
+ if (this.borders[sheetId]?.[row]?.[col] !== undefined) {
51543
+ return this.borders[sheetId][row][col];
51544
+ }
51545
+ if (!this.borders[sheetId]) {
51546
+ this.borders[sheetId] = {};
51547
+ }
51548
+ if (!this.borders[sheetId][row]) {
51549
+ this.borders[sheetId][row] = {};
51550
+ }
51551
+ if (!this.borders[sheetId][row][col]) {
51552
+ this.borders[sheetId][row][col] = this.computeCellBorder(position);
51553
+ }
51554
+ return this.borders[sheetId][row][col];
51555
+ }
51556
+ getCellComputedStyle(position) {
51557
+ const { sheetId, row, col } = position;
51558
+ if (this.styles[sheetId]?.[row]?.[col] !== undefined) {
51559
+ return this.styles[sheetId][row][col];
51560
+ }
51561
+ if (!this.styles[sheetId]) {
51562
+ this.styles[sheetId] = {};
51563
+ }
51564
+ if (!this.styles[sheetId][row]) {
51565
+ this.styles[sheetId][row] = {};
51566
+ }
51567
+ if (!this.styles[sheetId][row][col]) {
51568
+ this.styles[sheetId][row][col] = this.computeCellStyle(position);
51569
+ }
51570
+ return this.styles[sheetId][row][col];
51571
+ }
51572
+ computeCellBorder(position) {
51573
+ const cellBorder = this.getters.getCellBorder(position) || {};
51574
+ const cellTableBorder = this.getters.getCellTableBorder(position) || {};
51575
+ // Use removeFalsyAttributes to avoid overwriting borders with undefined values
51576
+ const border = {
51577
+ ...removeFalsyAttributes(cellTableBorder),
51578
+ ...removeFalsyAttributes(cellBorder),
51579
+ };
51580
+ return isObjectEmptyRecursive(border) ? null : border;
51581
+ }
51582
+ computeCellStyle(position) {
51583
+ const cell = this.getters.getCell(position);
51584
+ const cfStyle = this.getters.getCellConditionalFormatStyle(position);
51585
+ const tableStyle = this.getters.getCellTableStyle(position);
51586
+ const computedStyle = {
51587
+ ...removeFalsyAttributes(tableStyle),
51588
+ ...removeFalsyAttributes(cell?.style),
51589
+ ...removeFalsyAttributes(cfStyle),
51590
+ };
51591
+ const evaluatedCell = this.getters.getEvaluatedCell(position);
51592
+ if (evaluatedCell.link && !computedStyle.textColor) {
51593
+ computedStyle.textColor = LINK_COLOR;
51594
+ }
51595
+ return computedStyle;
51596
+ }
51597
+ }
51598
+
51101
51599
  const genericRepeatsTransforms = [
51102
51600
  repeatSheetDependantCommand,
51103
51601
  repeatTargetDependantCommand,
@@ -51675,148 +52173,6 @@ stores.inject(MyMetaStore, storeInstance);
51675
52173
  }
51676
52174
  }
51677
52175
 
51678
- class TableStylePlugin extends UIPlugin {
51679
- static getters = ["getCellTableStyle", "getCellTableBorder"];
51680
- tableStyles = {};
51681
- handle(cmd) {
51682
- if (invalidateEvaluationCommands.has(cmd.type) ||
51683
- (cmd.type === "UPDATE_CELL" && "content" in cmd) ||
51684
- cmd.type === "EVALUATE_CELLS") {
51685
- this.tableStyles = {};
51686
- return;
51687
- }
51688
- switch (cmd.type) {
51689
- case "HIDE_COLUMNS_ROWS":
51690
- case "UNHIDE_COLUMNS_ROWS":
51691
- case "UNFOLD_HEADER_GROUP":
51692
- case "FOLD_HEADER_GROUP":
51693
- case "FOLD_ALL_HEADER_GROUPS":
51694
- case "UNFOLD_ALL_HEADER_GROUPS":
51695
- case "UPDATE_TABLE":
51696
- case "UPDATE_FILTER":
51697
- delete this.tableStyles[cmd.sheetId];
51698
- break;
51699
- }
51700
- }
51701
- finalize() {
51702
- for (const sheetId of this.getters.getSheetIds()) {
51703
- if (!this.tableStyles[sheetId]) {
51704
- this.tableStyles[sheetId] = {};
51705
- }
51706
- for (const table of this.getters.getTables(sheetId)) {
51707
- if (!this.tableStyles[sheetId][table.id]) {
51708
- this.tableStyles[sheetId][table.id] = this.computeTableStyle(sheetId, table);
51709
- }
51710
- }
51711
- }
51712
- }
51713
- getCellTableStyle(position) {
51714
- const table = this.getters.getTable(position);
51715
- if (!table) {
51716
- return undefined;
51717
- }
51718
- return this.tableStyles[position.sheetId][table.id]().styles[position.col]?.[position.row];
51719
- }
51720
- getCellTableBorder(position) {
51721
- const table = this.getters.getTable(position);
51722
- if (!table) {
51723
- return undefined;
51724
- }
51725
- return this.tableStyles[position.sheetId][table.id]().borders[position.col]?.[position.row];
51726
- }
51727
- computeTableStyle(sheetId, table) {
51728
- return lazy(() => {
51729
- const { config, numberOfCols, numberOfRows } = this.getTableRuntimeConfig(sheetId, table);
51730
- const relativeTableStyle = getComputedTableStyle(config, numberOfCols, numberOfRows);
51731
- // Return the style with sheet coordinates instead of tables coordinates
51732
- const mapping = this.getTableMapping(sheetId, table);
51733
- const absoluteTableStyle = { borders: {}, styles: {} };
51734
- for (let col = 0; col < numberOfCols; col++) {
51735
- const colInSheet = mapping.colMapping[col];
51736
- absoluteTableStyle.borders[colInSheet] = {};
51737
- absoluteTableStyle.styles[colInSheet] = {};
51738
- for (let row = 0; row < numberOfRows; row++) {
51739
- const rowInSheet = mapping.rowMapping[row];
51740
- absoluteTableStyle.borders[colInSheet][rowInSheet] = relativeTableStyle.borders[col][row];
51741
- absoluteTableStyle.styles[colInSheet][rowInSheet] = relativeTableStyle.styles[col][row];
51742
- }
51743
- }
51744
- return absoluteTableStyle;
51745
- });
51746
- }
51747
- /**
51748
- * Get the actual table config that will be used to compute the table style. It is different from
51749
- * the config of the table because of hidden rows and columns in the sheet. For example remove the
51750
- * hidden rows from config.numberOfHeaders.
51751
- */
51752
- getTableRuntimeConfig(sheetId, table) {
51753
- const tableZone = table.range.zone;
51754
- const config = { ...table.config };
51755
- let numberOfCols = tableZone.right - tableZone.left + 1;
51756
- let numberOfRows = tableZone.bottom - tableZone.top + 1;
51757
- for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51758
- if (!this.getters.isRowHidden(sheetId, row)) {
51759
- continue;
51760
- }
51761
- numberOfRows--;
51762
- if (row - tableZone.top < table.config.numberOfHeaders) {
51763
- config.numberOfHeaders--;
51764
- if (config.numberOfHeaders < 0) {
51765
- config.numberOfHeaders = 0;
51766
- }
51767
- }
51768
- if (row === tableZone.bottom) {
51769
- config.totalRow = false;
51770
- }
51771
- }
51772
- for (let col = tableZone.left; col <= tableZone.right; col++) {
51773
- if (!this.getters.isColHidden(sheetId, col)) {
51774
- continue;
51775
- }
51776
- numberOfCols--;
51777
- if (col === tableZone.left) {
51778
- config.firstColumn = false;
51779
- }
51780
- if (col === tableZone.right) {
51781
- config.lastColumn = false;
51782
- }
51783
- }
51784
- return {
51785
- config,
51786
- numberOfCols,
51787
- numberOfRows,
51788
- };
51789
- }
51790
- /**
51791
- * Get a mapping: relative col/row position in the table <=> col/row in the sheet
51792
- */
51793
- getTableMapping(sheetId, table) {
51794
- const colMapping = {};
51795
- const rowMapping = {};
51796
- let colOffset = 0;
51797
- let rowOffset = 0;
51798
- const tableZone = table.range.zone;
51799
- for (let col = tableZone.left; col <= tableZone.right; col++) {
51800
- if (this.getters.isColHidden(sheetId, col)) {
51801
- continue;
51802
- }
51803
- colMapping[colOffset] = col;
51804
- colOffset++;
51805
- for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51806
- if (this.getters.isRowHidden(sheetId, row)) {
51807
- continue;
51808
- }
51809
- rowMapping[rowOffset] = row;
51810
- rowOffset++;
51811
- }
51812
- }
51813
- return {
51814
- colMapping,
51815
- rowMapping,
51816
- };
51817
- }
51818
- }
51819
-
51820
52176
  /**
51821
52177
  * Clipboard Plugin
51822
52178
  *
@@ -52526,38 +52882,6 @@ stores.inject(MyMetaStore, storeInstance);
52526
52882
  }
52527
52883
  }
52528
52884
 
52529
- const selectionStatisticFunctions = [
52530
- {
52531
- name: _t("Sum"),
52532
- types: [CellValueType.number],
52533
- compute: (values, locale) => sum([[values]], locale),
52534
- },
52535
- {
52536
- name: _t("Avg"),
52537
- types: [CellValueType.number],
52538
- compute: (values, locale) => average([[values]], locale),
52539
- },
52540
- {
52541
- name: _t("Min"),
52542
- types: [CellValueType.number],
52543
- compute: (values, locale) => min([[values]], locale),
52544
- },
52545
- {
52546
- name: _t("Max"),
52547
- types: [CellValueType.number],
52548
- compute: (values, locale) => max([[values]], locale),
52549
- },
52550
- {
52551
- name: _t("Count"),
52552
- types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
52553
- compute: (values) => countAny([[values]]),
52554
- },
52555
- {
52556
- name: _t("Count Numbers"),
52557
- types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
52558
- compute: (values, locale) => countNumbers([[values]], locale),
52559
- },
52560
- ];
52561
52885
  /**
52562
52886
  * SelectionPlugin
52563
52887
  */
@@ -52573,8 +52897,6 @@ stores.inject(MyMetaStore, storeInstance);
52573
52897
  "getSelectedZones",
52574
52898
  "getSelectedZone",
52575
52899
  "getSelectedCells",
52576
- "getStatisticFnResults",
52577
- "getAggregate",
52578
52900
  "getSelectedFigureId",
52579
52901
  "getSelection",
52580
52902
  "getActivePosition",
@@ -52609,7 +52931,10 @@ stores.inject(MyMetaStore, storeInstance);
52609
52931
  switch (cmd.type) {
52610
52932
  case "ACTIVATE_SHEET":
52611
52933
  try {
52612
- this.getters.getSheet(cmd.sheetIdTo);
52934
+ const sheet = this.getters.getSheet(cmd.sheetIdTo);
52935
+ if (!sheet.isVisible) {
52936
+ return "SheetIsHidden" /* CommandResult.SheetIsHidden */;
52937
+ }
52613
52938
  break;
52614
52939
  }
52615
52940
  catch (error) {
@@ -52858,52 +53183,6 @@ stores.inject(MyMetaStore, storeInstance);
52858
53183
  : this.getters.getNextVisibleCellPosition({ sheetId, col: 0, row: 0 });
52859
53184
  }
52860
53185
  }
52861
- getStatisticFnResults() {
52862
- const sheetId = this.getters.getActiveSheetId();
52863
- const cells = new Set();
52864
- for (const zone of this.gridSelection.zones) {
52865
- for (const { col, row } of positions(zone)) {
52866
- if (this.getters.isRowHidden(sheetId, row) || this.getters.isColHidden(sheetId, col)) {
52867
- continue; // Skip hidden cells
52868
- }
52869
- const evaluatedCell = this.getters.getEvaluatedCell({ sheetId, col, row });
52870
- if (evaluatedCell.type !== CellValueType.empty) {
52871
- cells.add(evaluatedCell);
52872
- }
52873
- }
52874
- }
52875
- const locale = this.getters.getLocale();
52876
- let statisticFnResults = {};
52877
- for (let fn of selectionStatisticFunctions) {
52878
- // We don't want to display statistical information when there is no interest:
52879
- // We set the statistical result to undefined if the data handled by the selection
52880
- // does not match the data handled by the function.
52881
- // Ex: if there are only texts in the selection, we prefer that the SUM result
52882
- // be displayed as undefined rather than 0.
52883
- let fnResult = undefined;
52884
- const evaluatedCells = [...cells].filter((c) => fn.types.includes(c.type));
52885
- if (evaluatedCells.length) {
52886
- fnResult = fn.compute(evaluatedCells, locale);
52887
- }
52888
- statisticFnResults[fn.name] = fnResult;
52889
- }
52890
- return statisticFnResults;
52891
- }
52892
- getAggregate() {
52893
- let aggregate = 0;
52894
- let n = 0;
52895
- const sheetId = this.getters.getActiveSheetId();
52896
- const cellPositions = this.gridSelection.zones.map(positions).flat();
52897
- for (const { col, row } of cellPositions) {
52898
- const cell = this.getters.getEvaluatedCell({ sheetId, col, row });
52899
- if (cell.type === CellValueType.number) {
52900
- n++;
52901
- aggregate += cell.value;
52902
- }
52903
- }
52904
- const locale = this.getters.getLocale();
52905
- return n < 2 ? null : formatValue(aggregate, { locale });
52906
- }
52907
53186
  isSelected(zone) {
52908
53187
  return !!this.getters.getSelectedZones().find((z) => isEqual(z, zone));
52909
53188
  }
@@ -52945,9 +53224,6 @@ stores.inject(MyMetaStore, storeInstance);
52945
53224
  // Other
52946
53225
  // ---------------------------------------------------------------------------
52947
53226
  activateSheet(sheetIdFrom, sheetIdTo) {
52948
- if (!this.getters.isSheetVisible(sheetIdTo)) {
52949
- this.dispatch("SHOW_SHEET", { sheetId: sheetIdTo });
52950
- }
52951
53227
  this.setActiveSheet(sheetIdTo);
52952
53228
  this.sheetsData[sheetIdFrom] = {
52953
53229
  gridSelection: deepCopy(this.gridSelection),
@@ -53954,7 +54230,7 @@ stores.inject(MyMetaStore, storeInstance);
53954
54230
  * column of the current viewport
53955
54231
  */
53956
54232
  getColDimensionsInViewport(sheetId, col) {
53957
- const left = Math.min(...this.getters.getSheetViewVisibleCols());
54233
+ const left = largeMin(this.getters.getSheetViewVisibleCols());
53958
54234
  const start = this.getters.getColRowOffsetInViewport("COL", left, col);
53959
54235
  const size = this.getters.getColSize(sheetId, col);
53960
54236
  const isColHidden = this.getters.isColHidden(sheetId, col);
@@ -53969,7 +54245,7 @@ stores.inject(MyMetaStore, storeInstance);
53969
54245
  * of the current viewport
53970
54246
  */
53971
54247
  getRowDimensionsInViewport(sheetId, row) {
53972
- const top = Math.min(...this.getters.getSheetViewVisibleRows());
54248
+ const top = largeMin(this.getters.getSheetViewVisibleRows());
53973
54249
  const start = this.getters.getColRowOffsetInViewport("ROW", top, row);
53974
54250
  const size = this.getters.getRowSize(sheetId, row);
53975
54251
  const isRowHidden = this.getters.isRowHidden(sheetId, row);
@@ -54321,6 +54597,7 @@ stores.inject(MyMetaStore, storeInstance);
54321
54597
  .add("evaluation_filter", FilterEvaluationPlugin)
54322
54598
  .add("header_visibility_ui", HeaderVisibilityUIPlugin)
54323
54599
  .add("table_style", TableStylePlugin)
54600
+ .add("cell_computed_style", CellComputedStylePlugin)
54324
54601
  .add("header_positions", HeaderPositionsUIPlugin)
54325
54602
  .add("viewport", SheetViewPlugin)
54326
54603
  .add("clipboard", ClipboardPlugin);
@@ -54381,6 +54658,38 @@ stores.inject(MyMetaStore, storeInstance);
54381
54658
  }
54382
54659
  }
54383
54660
 
54661
+ class ArrayFormulaHighlight extends SpreadsheetStore {
54662
+ highlightStore = this.get(HighlightStore);
54663
+ constructor(get) {
54664
+ super(get);
54665
+ this.highlightStore.register(this);
54666
+ }
54667
+ get highlights() {
54668
+ const zone = this.getHighlightZone();
54669
+ if (!zone) {
54670
+ return [];
54671
+ }
54672
+ const sheetId = this.model.getters.getActiveSheetId();
54673
+ return [
54674
+ {
54675
+ sheetId,
54676
+ zone,
54677
+ color: "#17A2B8",
54678
+ noFill: true,
54679
+ thinLine: true,
54680
+ },
54681
+ ];
54682
+ }
54683
+ getHighlightZone() {
54684
+ const position = this.model.getters.getActivePosition();
54685
+ const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
54686
+ const spreadZone = spreader
54687
+ ? this.model.getters.getSpreadZone(spreader)
54688
+ : this.model.getters.getSpreadZone(position);
54689
+ return spreadZone;
54690
+ }
54691
+ }
54692
+
54384
54693
  const RIPPLE_KEY_FRAMES = [
54385
54694
  { transform: "scale(0)" },
54386
54695
  { transform: "scale(0.8)", offset: 0.33 },
@@ -54683,12 +54992,14 @@ stores.inject(MyMetaStore, storeInstance);
54683
54992
  this.editionState = "initializing";
54684
54993
  }
54685
54994
  stopEdition() {
54686
- if (!this.state.isEditing)
54995
+ const input = this.sheetNameRef.el;
54996
+ if (!this.state.isEditing || !input)
54687
54997
  return;
54688
54998
  this.state.isEditing = false;
54689
54999
  this.editionState = "initializing";
54690
- this.sheetNameRef.el?.blur();
55000
+ input.blur();
54691
55001
  const inputValue = this.getInputContent() || "";
55002
+ input.innerText = inputValue;
54692
55003
  interactiveRenameSheet(this.env, this.props.sheetId, inputValue, () => this.startEdition());
54693
55004
  }
54694
55005
  cancelEdition() {
@@ -54732,6 +55043,115 @@ stores.inject(MyMetaStore, storeInstance);
54732
55043
  }
54733
55044
  }
54734
55045
 
55046
+ const selectionStatisticFunctions = [
55047
+ {
55048
+ name: _t("Sum"),
55049
+ types: [CellValueType.number],
55050
+ compute: (values, locale) => sum([[values]], locale),
55051
+ },
55052
+ {
55053
+ name: _t("Avg"),
55054
+ types: [CellValueType.number],
55055
+ compute: (values, locale) => average([[values]], locale),
55056
+ },
55057
+ {
55058
+ name: _t("Min"),
55059
+ types: [CellValueType.number],
55060
+ compute: (values, locale) => min([[values]], locale),
55061
+ },
55062
+ {
55063
+ name: _t("Max"),
55064
+ types: [CellValueType.number],
55065
+ compute: (values, locale) => max([[values]], locale),
55066
+ },
55067
+ {
55068
+ name: _t("Count"),
55069
+ types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
55070
+ compute: (values) => countAny([[values]]),
55071
+ },
55072
+ {
55073
+ name: _t("Count Numbers"),
55074
+ types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
55075
+ compute: (values, locale) => countNumbers([[values]], locale),
55076
+ },
55077
+ ];
55078
+ class AggregateStatisticsStore extends SpreadsheetStore {
55079
+ statisticFnResults = this._computeStatisticFnResults();
55080
+ isDirty = false;
55081
+ constructor(get) {
55082
+ super(get);
55083
+ this.model.selection.observe(this, {
55084
+ handleEvent: this.handleEvent.bind(this),
55085
+ });
55086
+ }
55087
+ handle(cmd) {
55088
+ if (invalidateEvaluationCommands.has(cmd.type) ||
55089
+ (cmd.type === "UPDATE_CELL" && "content" in cmd)) {
55090
+ this.isDirty = true;
55091
+ }
55092
+ switch (cmd.type) {
55093
+ case "HIDE_COLUMNS_ROWS":
55094
+ case "UNHIDE_COLUMNS_ROWS":
55095
+ case "GROUP_HEADERS":
55096
+ case "UNGROUP_HEADERS":
55097
+ case "ACTIVATE_SHEET":
55098
+ case "ACTIVATE_NEXT_SHEET":
55099
+ case "ACTIVATE_PREVIOUS_SHEET":
55100
+ case "EVALUATE_CELLS":
55101
+ case "UNDO":
55102
+ case "REDO":
55103
+ this.isDirty = true;
55104
+ }
55105
+ }
55106
+ finalize() {
55107
+ if (this.isDirty) {
55108
+ this.isDirty = false;
55109
+ this.statisticFnResults = this._computeStatisticFnResults();
55110
+ }
55111
+ }
55112
+ handleEvent() {
55113
+ if (this.getters.isGridSelectionActive()) {
55114
+ this.statisticFnResults = this._computeStatisticFnResults();
55115
+ }
55116
+ }
55117
+ _computeStatisticFnResults() {
55118
+ const getters = this.getters;
55119
+ const sheetId = getters.getActiveSheetId();
55120
+ const cells = new Set();
55121
+ const zones = getters.getSelectedZones();
55122
+ for (const zone of zones) {
55123
+ for (let col = zone.left; col <= zone.right; col++) {
55124
+ for (let row = zone.top; row <= zone.bottom; row++) {
55125
+ if (getters.isRowHidden(sheetId, row) || getters.isColHidden(sheetId, col)) {
55126
+ continue; // Skip hidden cells
55127
+ }
55128
+ const evaluatedCell = getters.getEvaluatedCell({ sheetId, col, row });
55129
+ if (evaluatedCell.type !== CellValueType.empty) {
55130
+ cells.add(evaluatedCell);
55131
+ }
55132
+ }
55133
+ }
55134
+ }
55135
+ const locale = getters.getLocale();
55136
+ let statisticFnResults = {};
55137
+ const cellsArray = [...cells];
55138
+ for (let fn of selectionStatisticFunctions) {
55139
+ // We don't want to display statistical information when there is no interest:
55140
+ // We set the statistical result to undefined if the data handled by the selection
55141
+ // does not match the data handled by the function.
55142
+ // Ex: if there are only texts in the selection, we prefer that the SUM result
55143
+ // be displayed as undefined rather than 0.
55144
+ let fnResult = undefined;
55145
+ const evaluatedCells = cellsArray.filter((c) => fn.types.includes(c.type));
55146
+ if (evaluatedCells.length) {
55147
+ fnResult = fn.compute(evaluatedCells, locale);
55148
+ }
55149
+ statisticFnResults[fn.name] = fnResult;
55150
+ }
55151
+ return statisticFnResults;
55152
+ }
55153
+ }
55154
+
54735
55155
  // -----------------------------------------------------------------------------
54736
55156
  // SpreadSheet
54737
55157
  // -----------------------------------------------------------------------------
@@ -54747,40 +55167,38 @@ stores.inject(MyMetaStore, storeInstance);
54747
55167
  }
54748
55168
  `;
54749
55169
  class BottomBarStatistic extends owl.Component {
54750
- static template = "o-spreadsheet-BottomBarStatisic";
55170
+ static template = "o-spreadsheet-BottomBarStatistic";
54751
55171
  static props = {
54752
55172
  openContextMenu: Function,
54753
55173
  closeContextMenu: Function,
54754
55174
  };
54755
55175
  static components = { Ripple };
54756
55176
  selectedStatisticFn = "";
54757
- statisticFnResults = {};
55177
+ store;
54758
55178
  setup() {
54759
- this.statisticFnResults = this.env.model.getters.getStatisticFnResults();
55179
+ this.store = useStore(AggregateStatisticsStore);
54760
55180
  owl.onWillUpdateProps(() => {
54761
- const newStatisticFnResults = this.env.model.getters.getStatisticFnResults();
54762
- if (!deepEquals(newStatisticFnResults, this.statisticFnResults)) {
55181
+ if (Object.values(this.store.statisticFnResults).every((result) => result === undefined)) {
54763
55182
  this.props.closeContextMenu();
54764
55183
  }
54765
- this.statisticFnResults = newStatisticFnResults;
54766
55184
  });
54767
55185
  }
54768
55186
  getSelectedStatistic() {
54769
55187
  // don't display button if no function has a result
54770
- if (Object.values(this.statisticFnResults).every((result) => result === undefined)) {
55188
+ if (Object.values(this.store.statisticFnResults).every((result) => result === undefined)) {
54771
55189
  return undefined;
54772
55190
  }
54773
55191
  if (this.selectedStatisticFn === "") {
54774
- this.selectedStatisticFn = Object.keys(this.statisticFnResults)[0];
55192
+ this.selectedStatisticFn = Object.keys(this.store.statisticFnResults)[0];
54775
55193
  }
54776
- return this.getComposedFnName(this.selectedStatisticFn, this.statisticFnResults[this.selectedStatisticFn]);
55194
+ return this.getComposedFnName(this.selectedStatisticFn);
54777
55195
  }
54778
55196
  listSelectionStatistics(ev) {
54779
55197
  const registry = new MenuItemRegistry();
54780
55198
  let i = 0;
54781
- for (let [fnName, fnValue] of Object.entries(this.statisticFnResults)) {
55199
+ for (let [fnName] of Object.entries(this.store.statisticFnResults)) {
54782
55200
  registry.add(fnName, {
54783
- name: this.getComposedFnName(fnName, fnValue),
55201
+ name: () => this.getComposedFnName(fnName),
54784
55202
  sequence: i,
54785
55203
  isReadonlyAllowed: true,
54786
55204
  execute: () => {
@@ -54793,8 +55211,9 @@ stores.inject(MyMetaStore, storeInstance);
54793
55211
  const { top, left, width } = target.getBoundingClientRect();
54794
55212
  this.props.openContextMenu(left + width, top, registry);
54795
55213
  }
54796
- getComposedFnName(fnName, fnValue) {
55214
+ getComposedFnName(fnName) {
54797
55215
  const locale = this.env.model.getters.getLocale();
55216
+ const fnValue = this.store.statisticFnResults[fnName];
54798
55217
  return fnName + ": " + (fnValue !== undefined ? formatValue(fnValue, { locale }) : "__");
54799
55218
  }
54800
55219
  }
@@ -54903,10 +55322,14 @@ stores.inject(MyMetaStore, storeInstance);
54903
55322
  name: sheet.name,
54904
55323
  sequence: i,
54905
55324
  isReadonlyAllowed: true,
54906
- textColor: sheet.isVisible ? undefined : "grey",
55325
+ textColor: sheet.isVisible ? undefined : "#808080",
54907
55326
  execute: (env) => {
55327
+ if (!this.env.model.getters.isSheetVisible(sheetId)) {
55328
+ this.env.model.dispatch("SHOW_SHEET", { sheetId });
55329
+ }
54908
55330
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: from, sheetIdTo: sheetId });
54909
55331
  },
55332
+ isEnabled: (env) => (env.model.getters.isReadonly() ? sheet.isVisible : true),
54910
55333
  });
54911
55334
  i++;
54912
55335
  }
@@ -54979,7 +55402,7 @@ stores.inject(MyMetaStore, storeInstance);
54979
55402
  this.sheetListRef.el.scrollTo({ top: 0, left: scroll, behavior: "smooth" });
54980
55403
  }
54981
55404
  onSheetMouseDown(sheetId, event) {
54982
- if (event.button !== 0)
55405
+ if (event.button !== 0 || this.env.model.getters.isReadonly())
54983
55406
  return;
54984
55407
  this.closeMenu();
54985
55408
  const visibleSheets = this.getVisibleSheets();
@@ -55015,7 +55438,7 @@ stores.inject(MyMetaStore, storeInstance);
55015
55438
  .map((sheetEl) => sheetEl.getBoundingClientRect())
55016
55439
  .map((rect) => ({
55017
55440
  x: rect.x,
55018
- width: rect.width - 1,
55441
+ width: rect.width - 1, // -1 to compensate negative margin
55019
55442
  y: rect.y,
55020
55443
  height: rect.height,
55021
55444
  }));
@@ -55242,7 +55665,7 @@ stores.inject(MyMetaStore, storeInstance);
55242
55665
  }
55243
55666
  return cssPropertiesToCss({
55244
55667
  top: `${groupBox.headerRect.height / 2}px`,
55245
- left: `calc(50% - 1px)`,
55668
+ left: `calc(50% - 1px)`, // -1px: we want the border to be on the center
55246
55669
  width: `30%`,
55247
55670
  height: `calc(100% - ${groupBox.headerRect.height / 2}px)`,
55248
55671
  "border-left": `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
@@ -55294,7 +55717,7 @@ stores.inject(MyMetaStore, storeInstance);
55294
55717
  return "";
55295
55718
  }
55296
55719
  return cssPropertiesToCss({
55297
- top: `calc(50% - 1px)`,
55720
+ top: `calc(50% - 1px)`, // -1px: we want the border to be on the center
55298
55721
  left: `${groupBox.headerRect.width / 2}px`,
55299
55722
  width: `calc(100% - ${groupBox.headerRect.width / 2}px)`,
55300
55723
  height: `30%`,
@@ -56385,7 +56808,7 @@ stores.inject(MyMetaStore, storeInstance);
56385
56808
  }
56386
56809
  .o-disabled {
56387
56810
  opacity: 0.4;
56388
- pointer: default;
56811
+ cursor: default;
56389
56812
  pointer-events: none;
56390
56813
  }
56391
56814
 
@@ -56551,7 +56974,7 @@ stores.inject(MyMetaStore, storeInstance);
56551
56974
 
56552
56975
  .o-number-input {
56553
56976
  /* Remove number input arrows */
56554
- -moz-appearance: textfield;
56977
+ appearance: textfield;
56555
56978
  &::-webkit-outer-spin-button,
56556
56979
  &::-webkit-inner-spin-button {
56557
56980
  -webkit-appearance: none;
@@ -56594,6 +57017,7 @@ stores.inject(MyMetaStore, storeInstance);
56594
57017
  this.notificationStore = useStore(NotificationStore);
56595
57018
  this.composerFocusStore = useStore(ComposerFocusStore);
56596
57019
  this.sidePanel = useStore(SidePanelStore);
57020
+ useStore(ArrayFormulaHighlight);
56597
57021
  this.keyDownMapping = {
56598
57022
  "CTRL+H": () => this.sidePanel.toggle("FindAndReplace", {}),
56599
57023
  "CTRL+F": () => this.sidePanel.toggle("FindAndReplace", {}),
@@ -56696,7 +57120,7 @@ stores.inject(MyMetaStore, storeInstance);
56696
57120
  const gridColSize = GROUP_LAYER_WIDTH * this.rowLayers.length;
56697
57121
  const gridRowSize = GROUP_LAYER_WIDTH * this.colLayers.length;
56698
57122
  return cssPropertiesToCss({
56699
- "grid-template-columns": `${gridColSize ? gridColSize + 2 : 0}px auto`,
57123
+ "grid-template-columns": `${gridColSize ? gridColSize + 2 : 0}px auto`, // +2: margins
56700
57124
  "grid-template-rows": `${gridRowSize ? gridRowSize + 2 : 0}px auto`,
56701
57125
  });
56702
57126
  }
@@ -57428,14 +57852,6 @@ stores.inject(MyMetaStore, storeInstance);
57428
57852
  this.revertBefore(operationId);
57429
57853
  this.tree.drop(operationId);
57430
57854
  }
57431
- getRevertedExecution() {
57432
- const data = [];
57433
- const operations = this.tree.revertedExecution(this.HEAD_BRANCH);
57434
- for (const { operation } of operations) {
57435
- data.push(operation.data);
57436
- }
57437
- return data;
57438
- }
57439
57855
  /**
57440
57856
  * Revert the state as it was *before* the given operation was executed.
57441
57857
  */
@@ -58224,6 +58640,9 @@ stores.inject(MyMetaStore, storeInstance);
58224
58640
  case "bar":
58225
58641
  plot = addBarChart(chart.data);
58226
58642
  break;
58643
+ case "combo":
58644
+ plot = addComboChart(chart.data);
58645
+ break;
58227
58646
  case "line":
58228
58647
  plot = addLineChart(chart.data);
58229
58648
  break;
@@ -58386,6 +58805,79 @@ stores.inject(MyMetaStore, storeInstance);
58386
58805
  ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
58387
58806
  `;
58388
58807
  }
58808
+ function addComboChart(chart) {
58809
+ // gapWitdh and overlap that define the space between clusters (in %) and the overlap between datasets (from -100: completely scattered to 100, completely overlapped)
58810
+ // see gapWidth : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_gapWidth_topic_ID0EFVEQB.html#topic_ID0EFVEQB
58811
+ // see overlap : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_overlap_topic_ID0ELYQQB.html#topic_ID0ELYQQB
58812
+ //
58813
+ // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
58814
+ // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
58815
+ const colors = new ChartColors();
58816
+ const dataSetsNodes = [];
58817
+ for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
58818
+ const color = toXlsxHexColor(colors.next());
58819
+ const dataShapeProperty = shapeProperty({
58820
+ backgroundColor: color,
58821
+ line: { color },
58822
+ });
58823
+ dataSetsNodes.push(dsIndex === "0"
58824
+ ? escapeXml /*xml*/ `
58825
+ <c:ser>
58826
+ <c:idx val="${dsIndex}"/>
58827
+ <c:order val="${dsIndex}"/>
58828
+ ${dataset.label ? escapeXml /*xml*/ `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
58829
+ ${dataShapeProperty}
58830
+ ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
58831
+ <c:val> <!-- x-coordinate values -->
58832
+ ${numberRef(dataset.range)}
58833
+ </c:val>
58834
+ </c:ser>
58835
+ `
58836
+ : escapeXml /*xml*/ `
58837
+ <c:ser>
58838
+ <c:idx val="${dsIndex}"/>
58839
+ <c:order val="${dsIndex}"/>
58840
+ <c:smooth val="0"/>
58841
+ <c:marker>
58842
+ <c:symbol val="circle" />
58843
+ <c:size val="5"/>
58844
+ </c:marker>
58845
+ ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
58846
+ ${dataShapeProperty}
58847
+ ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
58848
+ <c:val> <!-- x-coordinate values -->
58849
+ ${numberRef(dataset.range)}
58850
+ </c:val>
58851
+ </c:ser>
58852
+ `);
58853
+ }
58854
+ // Excel does not support this feature
58855
+ const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
58856
+ const overlap = chart.stacked ? 100 : -20;
58857
+ return escapeXml /*xml*/ `
58858
+ <c:barChart>
58859
+ <c:barDir val="col"/>
58860
+ <c:grouping val="clustered"/>
58861
+ <c:overlap val="${overlap}"/>
58862
+ <c:gapWidth val="70"/>
58863
+ <!-- each data marker in the series does not have a different color -->
58864
+ <c:varyColors val="0"/>
58865
+ ${dataSetsNodes[0]}
58866
+ <c:axId val="${catAxId}" />
58867
+ <c:axId val="${valAxId}" />
58868
+ </c:barChart>
58869
+ <c:lineChart>
58870
+ <c:grouping val="standard"/>
58871
+ <!-- each data marker in the series does not have a different color -->
58872
+ <c:varyColors val="0"/>
58873
+ ${joinXmlNodes(dataSetsNodes.slice(1))}
58874
+ <c:axId val="${catAxId}" />
58875
+ <c:axId val="${valAxId}" />
58876
+ </c:lineChart>
58877
+ ${addAx("b", "c:catAx", catAxId, valAxId, { fontColor: chart.fontColor })}
58878
+ ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
58879
+ `;
58880
+ }
58389
58881
  function addLineChart(chart) {
58390
58882
  const colors = new ChartColors();
58391
58883
  const dataSetsNodes = [];
@@ -58433,7 +58925,7 @@ stores.inject(MyMetaStore, storeInstance);
58433
58925
  }
58434
58926
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
58435
58927
  const colors = new ChartColors();
58436
- const maxLength = Math.max(...chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
58928
+ const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
58437
58929
  const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));
58438
58930
  const dataSetsNodes = [];
58439
58931
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets).reverse()) {
@@ -59311,7 +59803,7 @@ stores.inject(MyMetaStore, storeInstance);
59311
59803
  const colHeaderXc = toXC(tableZone.left + i, tableZone.top);
59312
59804
  const colName = sheetData.cells[colHeaderXc]?.content || `col${i}`;
59313
59805
  const colAttributes = [
59314
- ["id", i + 1],
59806
+ ["id", i + 1], // id cannot be 0
59315
59807
  ["name", colName],
59316
59808
  ];
59317
59809
  columns.push(escapeXml /*xml*/ `<tableColumn ${formatAttributes(colAttributes)}/>`);
@@ -59527,6 +60019,7 @@ stores.inject(MyMetaStore, storeInstance);
59527
60019
  * https://www.ecma-international.org/publications-and-standards/standards/ecma-376/
59528
60020
  */
59529
60021
  function getXLSX(data) {
60022
+ data = fixLengthySheetNames(data);
59530
60023
  const files = [];
59531
60024
  const construct = getDefaultXLSXStructure();
59532
60025
  files.push(createWorkbook(data, construct));
@@ -59774,6 +60267,40 @@ stores.inject(MyMetaStore, storeInstance);
59774
60267
  `;
59775
60268
  return createXMLFile(parseXML(xml), "_rels/.rels");
59776
60269
  }
60270
+ /**
60271
+ * Excel sheet names are maximum 31 characters while o-spreadsheet do not have this limit.
60272
+ * This method converts the sheet names to be within the 31 characters limit.
60273
+ * The cells/charts referencing this sheet will be updated accordingly.
60274
+ */
60275
+ function fixLengthySheetNames(data) {
60276
+ const nameMapping = {};
60277
+ const newNames = new Set();
60278
+ for (const sheet of data.sheets) {
60279
+ let newName = sheet.name.slice(0, 31);
60280
+ let i = 1;
60281
+ while (newNames.has(newName)) {
60282
+ newName = newName.slice(0, 31 - String(i).length) + i++;
60283
+ }
60284
+ newNames.add(newName);
60285
+ if (newName !== sheet.name) {
60286
+ nameMapping[sheet.name] = newName;
60287
+ sheet.name = newName;
60288
+ }
60289
+ }
60290
+ if (!Object.keys(nameMapping).length) {
60291
+ return data;
60292
+ }
60293
+ const sheetWithNewNames = Object.keys(nameMapping).sort((a, b) => b.length - a.length);
60294
+ let stringifiedData = JSON.stringify(data);
60295
+ for (const sheetName of sheetWithNewNames) {
60296
+ const regex = new RegExp(`'?${escapeRegExp(sheetName)}'?!`, "g");
60297
+ stringifiedData = stringifiedData.replaceAll(regex, (match) => {
60298
+ const newName = nameMapping[sheetName];
60299
+ return match.replace(sheetName, newName);
60300
+ });
60301
+ }
60302
+ return JSON.parse(stringifiedData);
60303
+ }
59777
60304
 
59778
60305
  var Status;
59779
60306
  (function (Status) {
@@ -60463,9 +60990,9 @@ stores.inject(MyMetaStore, storeInstance);
60463
60990
  exports.tokenize = tokenize;
60464
60991
 
60465
60992
 
60466
- __info__.version = "17.3.0-alpha.1";
60467
- __info__.date = "2024-03-25T09:43:36.072Z";
60468
- __info__.hash = "4095c41";
60993
+ __info__.version = "17.3.0-alpha.2";
60994
+ __info__.date = "2024-04-05T14:01:07.060Z";
60995
+ __info__.hash = "8c5a229";
60469
60996
 
60470
60997
 
60471
60998
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);