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