@odoo/o-spreadsheet 17.3.0-alpha.0 → 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.0
7
- * @date 2024-03-20T13:42:32.042Z
8
- * @hash 073e154
6
+ * @version 17.3.0-alpha.2
7
+ * @date 2024-04-05T14:01:07.060Z
8
+ * @hash 8c5a229
9
9
  */
10
10
 
11
11
  (function (exports, owl) {
@@ -30,7 +30,6 @@
30
30
  const LINK_COLOR = "#017E84";
31
31
  const FILTERS_COLOR = "#188038";
32
32
  const BACKGROUND_HEADER_FILTER_COLOR = "#E6F4EA";
33
- const BACKGROUND_HEADER_SELECTED_FILTER_COLOR = "#CEEAD6";
34
33
  const SEPARATOR_COLOR = "#E0E2E4";
35
34
  const ICONS_COLOR = "#4A4F59";
36
35
  const HEADER_GROUPING_BACKGROUND_COLOR = "#F5F5F5";
@@ -466,7 +465,7 @@
466
465
  }
467
466
  // Generate new Id if the item didn't exist in the dictionary
468
467
  const ids = Object.keys(itemsDic);
469
- const maxId = ids.length === 0 ? 0 : Math.max(...ids.map((id) => parseInt(id, 10)));
468
+ const maxId = ids.length === 0 ? 0 : largeMax(ids.map((id) => parseInt(id, 10)));
470
469
  itemsDic[maxId + 1] = item;
471
470
  return maxId + 1;
472
471
  }
@@ -484,7 +483,7 @@
484
483
  let timeout = undefined;
485
484
  const debounced = function () {
486
485
  const context = this;
487
- const args = arguments;
486
+ const args = Array.from(arguments);
488
487
  function later() {
489
488
  timeout = undefined;
490
489
  if (!immediate) {
@@ -686,6 +685,34 @@
686
685
  }
687
686
  return RegExp(searchValue, flags);
688
687
  }
688
+ /**
689
+ * Alternative to Math.max that works with large arrays.
690
+ * Typically useful for arrays bigger than 100k elements.
691
+ */
692
+ function largeMax(array) {
693
+ let len = array.length;
694
+ if (len < 100_000)
695
+ return Math.max(...array);
696
+ let max = -Infinity;
697
+ while (len--) {
698
+ max = array[len] > max ? array[len] : max;
699
+ }
700
+ return max;
701
+ }
702
+ /**
703
+ * Alternative to Math.min that works with large arrays.
704
+ * Typically useful for arrays bigger than 100k elements.
705
+ */
706
+ function largeMin(array) {
707
+ let len = array.length;
708
+ if (len < 100_000)
709
+ return Math.min(...array);
710
+ let min = +Infinity;
711
+ while (len--) {
712
+ min = array[len] < min ? array[len] : min;
713
+ }
714
+ return min;
715
+ }
689
716
 
690
717
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
691
718
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
@@ -2088,6 +2115,7 @@
2088
2115
  CommandResult["InvalidCopyPasteSelection"] = "InvalidCopyPasteSelection";
2089
2116
  CommandResult["NoChanges"] = "NoChanges";
2090
2117
  CommandResult["InvalidInputId"] = "InvalidInputId";
2118
+ CommandResult["SheetIsHidden"] = "SheetIsHidden";
2091
2119
  })(exports.CommandResult || (exports.CommandResult = {}));
2092
2120
 
2093
2121
  const PLAIN_TEXT_FORMAT = "@"; // see OpenXML spec §18.8.31
@@ -4203,6 +4231,10 @@
4203
4231
  function positionToZone(position) {
4204
4232
  return { left: position.col, right: position.col, top: position.row, bottom: position.row };
4205
4233
  }
4234
+ /** Transform a zone into a zone with only its top-left position */
4235
+ function zoneToTopLeft(zone) {
4236
+ return { ...zone, right: zone.left, bottom: zone.top };
4237
+ }
4206
4238
  function isFullRow(zone) {
4207
4239
  return zone.right === undefined;
4208
4240
  }
@@ -4242,6 +4274,16 @@
4242
4274
  }
4243
4275
  return set;
4244
4276
  }
4277
+ function unionPositionsToZone(positions) {
4278
+ const zone = { top: Infinity, left: Infinity, bottom: -Infinity, right: -Infinity };
4279
+ for (const { col, row } of positions) {
4280
+ zone.top = Math.min(zone.top, row);
4281
+ zone.left = Math.min(zone.left, col);
4282
+ zone.bottom = Math.max(zone.bottom, row);
4283
+ zone.right = Math.max(zone.right, col);
4284
+ }
4285
+ return zone;
4286
+ }
4245
4287
 
4246
4288
  class RangeImpl {
4247
4289
  getSheetSize;
@@ -4545,8 +4587,9 @@
4545
4587
  * Get the default height of the cell given its style.
4546
4588
  */
4547
4589
  function getDefaultCellHeight(ctx, cell, colSize) {
4548
- if (!cell || !cell.content)
4590
+ if (!cell || (!cell.isFormula && !cell.content)) {
4549
4591
  return DEFAULT_CELL_HEIGHT;
4592
+ }
4550
4593
  const maxWidth = cell.style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
4551
4594
  const numberOfLines = cell.isFormula
4552
4595
  ? 1
@@ -5571,7 +5614,7 @@
5571
5614
  const spreader = this.getters.getArrayFormulaSpreadingOn(position);
5572
5615
  let cell = this.getters.getCell(position);
5573
5616
  const evaluatedCell = this.getters.getEvaluatedCell(position);
5574
- if (spreader) {
5617
+ if (spreader && !deepEquals(spreader, position)) {
5575
5618
  const isSpreaderCopied = rowsIndexes.includes(spreader.row) && columnsIndexes.includes(spreader.col);
5576
5619
  const content = isSpreaderCopied
5577
5620
  ? ""
@@ -6218,19 +6261,25 @@
6218
6261
  tableCellsInRow.push({});
6219
6262
  continue;
6220
6263
  }
6264
+ const coreTable = this.getters.getCoreTable(position);
6265
+ const tableZone = coreTable?.range.zone;
6221
6266
  // Copy whole table
6222
- if (zones.some((z) => isZoneInside(table.range.zone, z))) {
6223
- copiedTablesIds.add(table.id);
6267
+ if (coreTable && tableZone && zones.some((z) => isZoneInside(tableZone, z))) {
6268
+ copiedTablesIds.add(coreTable.id);
6224
6269
  const values = [];
6225
- for (const col of range(table.range.zone.left, table.range.zone.right + 1)) {
6226
- values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: table.range.zone.top }));
6270
+ for (const col of range(tableZone.left, tableZone.right + 1)) {
6271
+ values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: tableZone.top }));
6227
6272
  }
6228
6273
  tableCellsInRow.push({
6229
- table: { filtersValues: values, range: table.range, config: table.config },
6274
+ table: {
6275
+ range: coreTable.range,
6276
+ config: coreTable.config,
6277
+ type: coreTable.type,
6278
+ },
6230
6279
  });
6231
6280
  }
6232
6281
  // Copy only style of cell
6233
- else {
6282
+ else if (table) {
6234
6283
  tableCellsInRow.push({ style: this.getTableStyleToCopy(position) });
6235
6284
  }
6236
6285
  }
@@ -6301,7 +6350,7 @@
6301
6350
  }
6302
6351
  pasteTableCell(sheetId, tableCell, position, options) {
6303
6352
  if (tableCell.table && !options?.pasteOption) {
6304
- const { range: tableRange, filtersValues } = tableCell.table;
6353
+ const { range: tableRange } = tableCell.table;
6305
6354
  const zoneDims = zoneToDimension(tableRange.zone);
6306
6355
  const newTableZone = {
6307
6356
  left: position.col,
@@ -6313,18 +6362,13 @@
6313
6362
  sheetId: position.sheetId,
6314
6363
  ranges: [this.getters.getRangeDataFromZone(sheetId, newTableZone)],
6315
6364
  config: tableCell.table.config,
6365
+ tableType: tableCell.table.type,
6316
6366
  });
6317
- for (const i of range(0, filtersValues.length)) {
6318
- this.dispatch("UPDATE_FILTER", {
6319
- sheetId: position.sheetId,
6320
- col: newTableZone.left + i,
6321
- row: newTableZone.top,
6322
- hiddenValues: filtersValues[i],
6323
- });
6324
- }
6325
6367
  }
6326
6368
  // Do not paste table style if we're inside another table
6327
- if (!this.getters.getTable(position)) {
6369
+ // We cannot check for dynamic tables, because at this point the paste can have changed the evaluation, and the
6370
+ // dynamic tables are not yet computed
6371
+ if (!this.getters.getCoreTable(position)) {
6328
6372
  if (tableCell.style?.style && options?.pasteOption !== "asValue") {
6329
6373
  this.dispatch("UPDATE_CELL", { ...position, style: tableCell.style.style });
6330
6374
  }
@@ -6979,10 +7023,17 @@
6979
7023
  },
6980
7024
  open(url, env) {
6981
7025
  const sheetId = parseSheetUrl(url);
6982
- env.model.dispatch("ACTIVATE_SHEET", {
7026
+ const result = env.model.dispatch("ACTIVATE_SHEET", {
6983
7027
  sheetIdFrom: env.model.getters.getActiveSheetId(),
6984
7028
  sheetIdTo: sheetId,
6985
7029
  });
7030
+ if (result.isCancelledBecause("SheetIsHidden" /* CommandResult.SheetIsHidden */)) {
7031
+ env.notifyUser({
7032
+ type: "warning",
7033
+ sticky: false,
7034
+ text: _t("Cannot open the link because the linked sheet is hidden."),
7035
+ });
7036
+ }
6986
7037
  },
6987
7038
  sequence: 0,
6988
7039
  });
@@ -7098,7 +7149,7 @@
7098
7149
  }
7099
7150
  function numberCell(value, format, formattedValue) {
7100
7151
  return {
7101
- value: value || 0,
7152
+ value: value || 0, // necessary to avoid "-0" and NaN values,
7102
7153
  format,
7103
7154
  formattedValue,
7104
7155
  type: CellValueType.number,
@@ -7426,9 +7477,11 @@
7426
7477
  bandedColumns: _t("Banded columns"),
7427
7478
  automaticAutofill: _t("Automatically autofill formulas"),
7428
7479
  totalRow: _t("Total row"),
7480
+ isDynamic: _t("Auto-adjust to formula result"),
7429
7481
  },
7430
7482
  Tooltips: {
7431
7483
  filterWithoutHeader: _t("Cannot have filters without a header row"),
7484
+ isDynamic: _t("For tables based on array formulas only"),
7432
7485
  },
7433
7486
  };
7434
7487
 
@@ -8004,6 +8057,9 @@
8004
8057
  instantiate(Store, ...args) {
8005
8058
  return this.factory.build(Store, ...args);
8006
8059
  }
8060
+ resetStores() {
8061
+ this.dependencies.clear();
8062
+ }
8007
8063
  }
8008
8064
  class StoreFactory {
8009
8065
  get;
@@ -9937,11 +9993,11 @@ stores.inject(MyMetaStore, storeInstance);
9937
9993
  }
9938
9994
  else {
9939
9995
  const range = this.getters.getRangeFromSheetXC(position.sheetId, rule.criterion.values[0]);
9940
- values = this.getters
9996
+ values = Array.from(new Set(this.getters
9941
9997
  .getRangeValues(range)
9942
9998
  .filter(isNotNull)
9943
9999
  .map((value) => value.toString())
9944
- .filter((val) => val !== "");
10000
+ .filter((val) => val !== "")));
9945
10001
  }
9946
10002
  return values.map((value) => ({ text: value }));
9947
10003
  },
@@ -19388,10 +19444,10 @@ stores.inject(MyMetaStore, storeInstance);
19388
19444
  }
19389
19445
  }
19390
19446
  return {
19391
- labels: Object.keys(labelMap),
19447
+ labels: Array.from(labelSet),
19392
19448
  dataSetsValues: datasets.map((dataset, indexOfDataset) => ({
19393
19449
  ...dataset,
19394
- data: Object.values(labelMap).map((dataOfLabel) => dataOfLabel[indexOfDataset]),
19450
+ data: Array.from(labelSet).map((label) => labelMap[label][indexOfDataset]),
19395
19451
  })),
19396
19452
  };
19397
19453
  }
@@ -19410,8 +19466,8 @@ stores.inject(MyMetaStore, storeInstance);
19410
19466
  function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
19411
19467
  const options = {
19412
19468
  // https://www.chartjs.org/docs/latest/general/responsive.html
19413
- responsive: true,
19414
- maintainAspectRatio: false,
19469
+ responsive: true, // will resize when its container is resized
19470
+ maintainAspectRatio: false, // doesn't maintain the aspect ration (width/height =2 by default) so the user has the choice of the exact layout
19415
19471
  layout: {
19416
19472
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
19417
19473
  },
@@ -19457,7 +19513,7 @@ stores.inject(MyMetaStore, storeInstance);
19457
19513
  labels: labels.map(truncateLabel),
19458
19514
  datasets: [],
19459
19515
  },
19460
- platform: undefined,
19516
+ platform: undefined, // This key is optional and will be set by chart.js
19461
19517
  plugins: [],
19462
19518
  };
19463
19519
  }
@@ -19734,7 +19790,7 @@ stores.inject(MyMetaStore, storeInstance);
19734
19790
  },
19735
19791
  y: {
19736
19792
  position: chart.verticalAxisPosition,
19737
- beginAtZero: true,
19793
+ beginAtZero: true, // the origin of the y axis is always zero
19738
19794
  ticks: {
19739
19795
  color: fontColor,
19740
19796
  callback: (value) => {
@@ -19785,6 +19841,204 @@ stores.inject(MyMetaStore, storeInstance);
19785
19841
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
19786
19842
  }
19787
19843
 
19844
+ class ComboChart extends AbstractChart {
19845
+ useBothYAxis;
19846
+ dataSets;
19847
+ labelRange;
19848
+ background;
19849
+ verticalAxisPosition;
19850
+ legendPosition;
19851
+ aggregated;
19852
+ dataSetsHaveTitle;
19853
+ type = "combo";
19854
+ constructor(definition, sheetId, getters) {
19855
+ super(definition, sheetId, getters);
19856
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
19857
+ this.labelRange = createRange(getters, sheetId, definition.labelRange);
19858
+ this.background = definition.background;
19859
+ this.verticalAxisPosition = definition.verticalAxisPosition;
19860
+ this.legendPosition = definition.legendPosition;
19861
+ this.aggregated = definition.aggregated;
19862
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
19863
+ this.useBothYAxis = definition.useBothYAxis;
19864
+ }
19865
+ static transformDefinition(definition, executed) {
19866
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
19867
+ }
19868
+ static validateChartDefinition(validator, definition) {
19869
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
19870
+ }
19871
+ getContextCreation() {
19872
+ return {
19873
+ background: this.background,
19874
+ title: this.title,
19875
+ range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
19876
+ auxiliaryRange: this.labelRange
19877
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
19878
+ : undefined,
19879
+ aggregated: this.aggregated,
19880
+ };
19881
+ }
19882
+ getDefinition() {
19883
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
19884
+ }
19885
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
19886
+ return {
19887
+ type: "combo",
19888
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
19889
+ background: this.background,
19890
+ dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
19891
+ legendPosition: this.legendPosition,
19892
+ verticalAxisPosition: this.verticalAxisPosition,
19893
+ labelRange: labelRange
19894
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
19895
+ : undefined,
19896
+ title: this.title,
19897
+ aggregated: this.aggregated,
19898
+ useBothYAxis: this.useBothYAxis,
19899
+ };
19900
+ }
19901
+ getDefinitionForExcel() {
19902
+ // Excel does not support aggregating labels
19903
+ if (this.aggregated) {
19904
+ return undefined;
19905
+ }
19906
+ const dataSets = this.dataSets
19907
+ .map((ds) => toExcelDataset(this.getters, ds))
19908
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
19909
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
19910
+ return {
19911
+ ...this.getDefinition(),
19912
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
19913
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
19914
+ dataSets,
19915
+ labelRange,
19916
+ };
19917
+ }
19918
+ updateRanges(applyChange) {
19919
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
19920
+ if (!isStale) {
19921
+ return this;
19922
+ }
19923
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
19924
+ return new ComboChart(definition, this.sheetId, this.getters);
19925
+ }
19926
+ static getDefinitionFromContextCreation(context) {
19927
+ return {
19928
+ background: context.background,
19929
+ dataSets: context.range ? context.range : [],
19930
+ dataSetsHaveTitle: false,
19931
+ aggregated: context.aggregated,
19932
+ legendPosition: "top",
19933
+ title: context.title || "",
19934
+ verticalAxisPosition: "left",
19935
+ labelRange: context.auxiliaryRange || undefined,
19936
+ type: "combo",
19937
+ useBothYAxis: false,
19938
+ };
19939
+ }
19940
+ copyForSheetId(sheetId) {
19941
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
19942
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
19943
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
19944
+ return new ComboChart(definition, sheetId, this.getters);
19945
+ }
19946
+ copyInSheetId(sheetId) {
19947
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
19948
+ return new ComboChart(definition, sheetId, this.getters);
19949
+ }
19950
+ }
19951
+ function createComboChartRuntime(chart, getters) {
19952
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
19953
+ const locale = getters.getLocale();
19954
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
19955
+ let labels = labelValues.formattedValues;
19956
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
19957
+ if (chart.dataSetsHaveTitle &&
19958
+ dataSetsValues[0] &&
19959
+ labels.length > dataSetsValues[0].data.length) {
19960
+ labels.shift();
19961
+ }
19962
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
19963
+ if (chart.aggregated) {
19964
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
19965
+ }
19966
+ const localeFormat = { format: dataSetFormat, locale };
19967
+ const fontColor = chartFontColor(chart.background);
19968
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
19969
+ const legend = {
19970
+ labels: { color: fontColor },
19971
+ };
19972
+ if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
19973
+ legend.display = false;
19974
+ }
19975
+ else {
19976
+ legend.position = chart.legendPosition;
19977
+ }
19978
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
19979
+ config.options.layout = {
19980
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
19981
+ };
19982
+ config.options.scales = {
19983
+ x: {
19984
+ ticks: {
19985
+ padding: 5,
19986
+ color: fontColor,
19987
+ },
19988
+ },
19989
+ };
19990
+ const verticalAxis = {
19991
+ beginAtZero: true, // the origin of the y axis is always zero
19992
+ ticks: {
19993
+ color: fontColor,
19994
+ callback: (value) => {
19995
+ value = Number(value);
19996
+ if (isNaN(value))
19997
+ return value;
19998
+ const { locale, format } = localeFormat;
19999
+ return formatValue(value, {
20000
+ locale,
20001
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
20002
+ });
20003
+ },
20004
+ },
20005
+ };
20006
+ if (chart.useBothYAxis) {
20007
+ config.options.scales.y = {
20008
+ ...verticalAxis,
20009
+ position: "left",
20010
+ };
20011
+ config.options.scales.y1 = {
20012
+ ...verticalAxis,
20013
+ position: "right",
20014
+ grid: {
20015
+ display: false,
20016
+ },
20017
+ };
20018
+ }
20019
+ else {
20020
+ config.options.scales.y = {
20021
+ ...verticalAxis,
20022
+ position: chart.verticalAxisPosition,
20023
+ };
20024
+ }
20025
+ const colors = new ChartColors();
20026
+ for (let [index, { label, data }] of dataSetsValues.entries()) {
20027
+ const color = colors.next();
20028
+ const dataset = {
20029
+ label,
20030
+ data,
20031
+ borderColor: color,
20032
+ backgroundColor: color,
20033
+ yAxisID: index > 0 && chart.useBothYAxis ? "y1" : "y",
20034
+ type: index === 0 ? "bar" : "line",
20035
+ order: -index,
20036
+ };
20037
+ config.data.datasets.push(dataset);
20038
+ }
20039
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
20040
+ }
20041
+
19788
20042
  function isDataRangeValid(definition) {
19789
20043
  return definition.dataRange && !rangeReference.test(definition.dataRange)
19790
20044
  ? "InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */
@@ -20123,7 +20377,7 @@ stores.inject(MyMetaStore, storeInstance);
20123
20377
  return undefined;
20124
20378
  }
20125
20379
  const labelsTimestamps = labelDates.map((date) => date.getTime());
20126
- const period = Math.max(...labelsTimestamps) - Math.min(...labelsTimestamps);
20380
+ const period = largeMax(labelsTimestamps) - largeMin(labelsTimestamps);
20127
20381
  const minUnit = getFormatMinDisplayUnit(format);
20128
20382
  if (UNIT_LENGTH.second >= UNIT_LENGTH[minUnit] && Milliseconds.inSeconds(period) < 180) {
20129
20383
  return "second";
@@ -20255,7 +20509,7 @@ stores.inject(MyMetaStore, storeInstance);
20255
20509
  },
20256
20510
  y: {
20257
20511
  position: chart.verticalAxisPosition,
20258
- beginAtZero: true,
20512
+ beginAtZero: true, // the origin of the y axis is always zero
20259
20513
  ticks: {
20260
20514
  color: fontColor,
20261
20515
  callback: (value) => {
@@ -20341,7 +20595,7 @@ stores.inject(MyMetaStore, storeInstance);
20341
20595
  const dataset = {
20342
20596
  label,
20343
20597
  data,
20344
- tension: 0,
20598
+ tension: 0, // 0 -> render straight lines, which is much faster
20345
20599
  borderColor: color,
20346
20600
  backgroundColor,
20347
20601
  pointBackgroundColor: color,
@@ -20563,7 +20817,7 @@ stores.inject(MyMetaStore, storeInstance);
20563
20817
  ...this.getDefinition(),
20564
20818
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
20565
20819
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
20566
- verticalAxisPosition: "left",
20820
+ verticalAxisPosition: "left", //TODO ExcelChartDefinition should be adapted, but can be done later
20567
20821
  dataSets,
20568
20822
  labelRange,
20569
20823
  };
@@ -20611,7 +20865,7 @@ stores.inject(MyMetaStore, storeInstance);
20611
20865
  }
20612
20866
  function getPieColors(colors, dataSetsValues) {
20613
20867
  const pieColors = [];
20614
- const maxLength = Math.max(...dataSetsValues.map((ds) => ds.data.length));
20868
+ const maxLength = largeMax(dataSetsValues.map((ds) => ds.data.length));
20615
20869
  for (let i = 0; i <= maxLength; i++) {
20616
20870
  pieColors.push(colors.next());
20617
20871
  }
@@ -20780,7 +21034,7 @@ stores.inject(MyMetaStore, storeInstance);
20780
21034
  configOptions.elements = {
20781
21035
  point: {
20782
21036
  radius: 3,
20783
- hoverRadius: 3,
21037
+ hoverRadius: 3, // chartJS seems bugged, the point starts with the "hoverRadius" value
20784
21038
  hitRadius: 8,
20785
21039
  },
20786
21040
  };
@@ -20820,6 +21074,16 @@ stores.inject(MyMetaStore, storeInstance);
20820
21074
  name: _t("Bar"),
20821
21075
  sequence: 10,
20822
21076
  });
21077
+ chartRegistry.add("combo", {
21078
+ match: (type) => type === "combo",
21079
+ createChart: (definition, sheetId, getters) => new ComboChart(definition, sheetId, getters),
21080
+ getChartRuntime: createComboChartRuntime,
21081
+ validateChartDefinition: (validator, definition) => ComboChart.validateChartDefinition(validator, definition),
21082
+ transformDefinition: (definition, executed) => ComboChart.transformDefinition(definition, executed),
21083
+ getChartDefinitionFromContextCreation: (context) => ComboChart.getDefinitionFromContextCreation(context),
21084
+ name: _t("Combo"),
21085
+ sequence: 15,
21086
+ });
20823
21087
  chartRegistry.add("line", {
20824
21088
  match: (type) => type === "line",
20825
21089
  createChart: (definition, sheetId, getters) => new LineChart(definition, sheetId, getters),
@@ -20873,6 +21137,7 @@ stores.inject(MyMetaStore, storeInstance);
20873
21137
  const chartComponentRegistry = new Registry();
20874
21138
  chartComponentRegistry.add("line", ChartJsComponent);
20875
21139
  chartComponentRegistry.add("bar", ChartJsComponent);
21140
+ chartComponentRegistry.add("combo", ChartJsComponent);
20876
21141
  chartComponentRegistry.add("pie", ChartJsComponent);
20877
21142
  chartComponentRegistry.add("gauge", GaugeChartComponent);
20878
21143
  chartComponentRegistry.add("scatter", ChartJsComponent);
@@ -22226,6 +22491,7 @@ stores.inject(MyMetaStore, storeInstance);
22226
22491
  }
22227
22492
  edit() {
22228
22493
  const { col, row } = this.props.cellPosition;
22494
+ this.env.model.selection.selectCell(col, row);
22229
22495
  this.cellPopovers.open({ col, row }, "LinkEditor");
22230
22496
  }
22231
22497
  unlink() {
@@ -23130,7 +23396,7 @@ stores.inject(MyMetaStore, storeInstance);
23130
23396
  style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" },
23131
23397
  border: { bottom: { color: colorSet.highlight, style: "thin" } },
23132
23398
  },
23133
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23399
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23134
23400
  firstRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
23135
23401
  secondRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
23136
23402
  });
@@ -23148,7 +23414,7 @@ stores.inject(MyMetaStore, storeInstance);
23148
23414
  },
23149
23415
  },
23150
23416
  headerRow: { border: { bottom: { color: colorSet.highlight, style: "medium" } } },
23151
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23417
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23152
23418
  firstRowStripe: { style: { fillColor: colorSet.light } },
23153
23419
  firstColumnStripe: { style: { fillColor: colorSet.light } },
23154
23420
  });
@@ -23167,7 +23433,7 @@ stores.inject(MyMetaStore, storeInstance);
23167
23433
  headerRow: {
23168
23434
  style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" },
23169
23435
  },
23170
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23436
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23171
23437
  firstRowStripe: { style: { fillColor: colorSet.light } },
23172
23438
  firstColumnStripe: { style: { fillColor: colorSet.light } },
23173
23439
  });
@@ -23203,7 +23469,7 @@ stores.inject(MyMetaStore, storeInstance);
23203
23469
  bottom: { color: "#000000", style: "medium" },
23204
23470
  },
23205
23471
  },
23206
- totalRow: { border: { top: { color: "#000000", style: "medium" } } },
23472
+ totalRow: { border: { top: { color: "#000000", style: "medium" } } }, // @compatibility: should be double line
23207
23473
  headerRow: {
23208
23474
  style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" },
23209
23475
  border: { bottom: { color: "#000000", style: "medium" } },
@@ -23227,7 +23493,7 @@ stores.inject(MyMetaStore, storeInstance);
23227
23493
  },
23228
23494
  style: { fillColor: colorSet.light },
23229
23495
  },
23230
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23496
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23231
23497
  firstRowStripe: { style: { fillColor: colorSet.medium } },
23232
23498
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
23233
23499
  });
@@ -23258,7 +23524,7 @@ stores.inject(MyMetaStore, storeInstance);
23258
23524
  category: "dark",
23259
23525
  colorName: colorSet.name,
23260
23526
  wholeTable: { style: { fillColor: colorSet.light } },
23261
- totalRow: { border: { top: { color: "#000000", style: "medium" } } },
23527
+ totalRow: { border: { top: { color: "#000000", style: "medium" } } }, // @compatibility: should be double line
23262
23528
  headerRow: { style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" } },
23263
23529
  firstRowStripe: { style: { fillColor: colorSet.medium } },
23264
23530
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
@@ -23342,13 +23608,20 @@ stores.inject(MyMetaStore, storeInstance);
23342
23608
  * If a single cell is selected, expand the selection to non-empty adjacent cells to create a table.
23343
23609
  */
23344
23610
  function interactiveCreateTable(env, sheetId, tableConfig) {
23345
- const selection = env.model.getters.getSelectedZones();
23346
- if (selection.length === 1 && getZoneArea(selection[0]) === 1) {
23611
+ let target = env.model.getters.getSelectedZones();
23612
+ let isDynamic = env.model.getters.canCreateDynamicTableOnZones(sheetId, target);
23613
+ if (target.length === 1 && !isDynamic && getZoneArea(target[0]) === 1) {
23347
23614
  env.model.selection.selectTableAroundSelection();
23615
+ target = env.model.getters.getSelectedZones();
23616
+ isDynamic = env.model.getters.canCreateDynamicTableOnZones(sheetId, target);
23348
23617
  }
23349
- const target = env.model.getters.getSelectedZones();
23350
23618
  const ranges = target.map((zone) => env.model.getters.getRangeDataFromZone(sheetId, zone));
23351
- const result = env.model.dispatch("CREATE_TABLE", { ranges, sheetId, config: tableConfig });
23619
+ const result = env.model.dispatch("CREATE_TABLE", {
23620
+ ranges,
23621
+ sheetId,
23622
+ config: tableConfig,
23623
+ tableType: isDynamic ? "dynamic" : "static",
23624
+ });
23352
23625
  if (result.isCancelledBecause("TableOverlap" /* CommandResult.TableOverlap */)) {
23353
23626
  env.raiseError(TableTerms.Errors.TableOverlap);
23354
23627
  }
@@ -23416,8 +23689,8 @@ stores.inject(MyMetaStore, storeInstance);
23416
23689
  let last;
23417
23690
  const activesRows = env.model.getters.getActiveRows();
23418
23691
  if (activesRows.size !== 0) {
23419
- first = Math.min(...activesRows);
23420
- last = Math.max(...activesRows);
23692
+ first = largeMin([...activesRows]);
23693
+ last = largeMax([...activesRows]);
23421
23694
  }
23422
23695
  else {
23423
23696
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23445,8 +23718,8 @@ stores.inject(MyMetaStore, storeInstance);
23445
23718
  let last;
23446
23719
  const activeCols = env.model.getters.getActiveCols();
23447
23720
  if (activeCols.size !== 0) {
23448
- first = Math.min(...activeCols);
23449
- last = Math.max(...activeCols);
23721
+ first = largeMin([...activeCols]);
23722
+ last = largeMax([...activeCols]);
23450
23723
  }
23451
23724
  else {
23452
23725
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23474,8 +23747,8 @@ stores.inject(MyMetaStore, storeInstance);
23474
23747
  let last;
23475
23748
  const activesRows = env.model.getters.getActiveRows();
23476
23749
  if (activesRows.size !== 0) {
23477
- first = Math.min(...activesRows);
23478
- last = Math.max(...activesRows);
23750
+ first = largeMin([...activesRows]);
23751
+ last = largeMax([...activesRows]);
23479
23752
  }
23480
23753
  else {
23481
23754
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23516,8 +23789,8 @@ stores.inject(MyMetaStore, storeInstance);
23516
23789
  let last;
23517
23790
  const activeCols = env.model.getters.getActiveCols();
23518
23791
  if (activeCols.size !== 0) {
23519
- first = Math.min(...activeCols);
23520
- last = Math.max(...activeCols);
23792
+ first = largeMin([...activeCols]);
23793
+ last = largeMax([...activeCols]);
23521
23794
  }
23522
23795
  else {
23523
23796
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23558,7 +23831,7 @@ stores.inject(MyMetaStore, storeInstance);
23558
23831
  let row;
23559
23832
  let quantity;
23560
23833
  if (activeRows.size) {
23561
- row = Math.min(...activeRows);
23834
+ row = largeMin([...activeRows]);
23562
23835
  quantity = activeRows.size;
23563
23836
  }
23564
23837
  else {
@@ -23579,7 +23852,7 @@ stores.inject(MyMetaStore, storeInstance);
23579
23852
  let row;
23580
23853
  let quantity;
23581
23854
  if (activeRows.size) {
23582
- row = Math.max(...activeRows);
23855
+ row = largeMax([...activeRows]);
23583
23856
  quantity = activeRows.size;
23584
23857
  }
23585
23858
  else {
@@ -23600,7 +23873,7 @@ stores.inject(MyMetaStore, storeInstance);
23600
23873
  let column;
23601
23874
  let quantity;
23602
23875
  if (activeCols.size) {
23603
- column = Math.min(...activeCols);
23876
+ column = largeMin([...activeCols]);
23604
23877
  quantity = activeCols.size;
23605
23878
  }
23606
23879
  else {
@@ -23621,7 +23894,7 @@ stores.inject(MyMetaStore, storeInstance);
23621
23894
  let column;
23622
23895
  let quantity;
23623
23896
  if (activeCols.size) {
23624
- column = Math.max(...activeCols);
23897
+ column = largeMax([...activeCols]);
23625
23898
  quantity = activeCols.size;
23626
23899
  }
23627
23900
  else {
@@ -23796,9 +24069,13 @@ stores.inject(MyMetaStore, storeInstance);
23796
24069
  };
23797
24070
  const DELETE_SELECTED_TABLE = (env) => {
23798
24071
  const position = env.model.getters.getActivePosition();
24072
+ const table = env.model.getters.getTable(position);
24073
+ if (!table) {
24074
+ return;
24075
+ }
23799
24076
  env.model.dispatch("REMOVE_TABLE", {
23800
24077
  sheetId: position.sheetId,
23801
- target: [positionToZone(position)],
24078
+ target: [table.range.zone],
23802
24079
  });
23803
24080
  };
23804
24081
  //------------------------------------------------------------------------------
@@ -24214,14 +24491,19 @@ stores.inject(MyMetaStore, storeInstance);
24214
24491
  children: [allFunctionListMenuBuilder],
24215
24492
  };
24216
24493
  function allFunctionListMenuBuilder() {
24217
- const fnNames = functionRegistry.getKeys();
24494
+ const fnNames = functionRegistry.getKeys().filter((key) => !functionRegistry.get(key).hidden);
24218
24495
  return createFormulaFunctions(fnNames);
24219
24496
  }
24220
24497
  const categoriesFunctionListMenuBuilder = () => {
24221
24498
  const functions = functionRegistry.content;
24222
- const categories = [...new Set(functionRegistry.getAll().map((fn) => fn.category))].filter(isDefined$1);
24499
+ const categories = [
24500
+ ...new Set(functionRegistry
24501
+ .getAll()
24502
+ .filter((fn) => !fn.hidden)
24503
+ .map((fn) => fn.category)),
24504
+ ].filter(isDefined$1);
24223
24505
  return categories.sort().map((category, i) => {
24224
- const functionsInCategory = Object.keys(functions).filter((key) => functions[key].category === category);
24506
+ const functionsInCategory = Object.keys(functions).filter((key) => functions[key].category === category && !functions[key].hidden);
24225
24507
  return {
24226
24508
  name: category,
24227
24509
  children: createFormulaFunctions(functionsInCategory),
@@ -27218,6 +27500,22 @@ stores.inject(MyMetaStore, storeInstance);
27218
27500
  static template = "o-spreadsheet-BarChartDesignPanel";
27219
27501
  }
27220
27502
 
27503
+ class ComboChartConfigPanel extends LineBarPieConfigPanel {
27504
+ static template = "o-spreadsheet-ComboChartConfigPanel";
27505
+ get shouldUseRightAxis() {
27506
+ return _t("Use right axis for line series");
27507
+ }
27508
+ onUpdateUseRightAxis(useBothYAxis) {
27509
+ this.props.updateChart(this.props.figureId, {
27510
+ useBothYAxis,
27511
+ });
27512
+ }
27513
+ }
27514
+
27515
+ class ComboChartDesignPanel extends LineBarPieDesignPanel {
27516
+ static template = "o-spreadsheet-ComboChartDesignPanel";
27517
+ }
27518
+
27221
27519
  class GaugeChartConfigPanel extends owl.Component {
27222
27520
  static template = "o-spreadsheet-GaugeChartConfigPanel";
27223
27521
  static components = { ChartErrorSection, ChartDataSeries };
@@ -27575,6 +27873,10 @@ stores.inject(MyMetaStore, storeInstance);
27575
27873
  .add("bar", {
27576
27874
  configuration: BarConfigPanel,
27577
27875
  design: BarChartDesignPanel,
27876
+ })
27877
+ .add("combo", {
27878
+ configuration: ComboChartConfigPanel,
27879
+ design: ComboChartDesignPanel,
27578
27880
  })
27579
27881
  .add("pie", {
27580
27882
  configuration: LineBarPieConfigPanel,
@@ -28379,11 +28681,9 @@ stores.inject(MyMetaStore, storeInstance);
28379
28681
  }
28380
28682
  .o-cell-is-operator {
28381
28683
  margin-bottom: 5px;
28382
- width: 96%;
28383
28684
  }
28384
28685
  .o-cell-is-value {
28385
28686
  margin-bottom: 5px;
28386
- width: 96%;
28387
28687
  }
28388
28688
  .o-color-picker-widget .o-color-picker-button {
28389
28689
  pointer-events: all;
@@ -30217,7 +30517,11 @@ stores.inject(MyMetaStore, storeInstance);
30217
30517
  const composerStore = useStore(ComposerStore);
30218
30518
  // The feature makes no sense if we are editing a cell, because then the selection isn't active
30219
30519
  // Stop the edition when the panel is mounted, and close the panel if the user start editing a cell
30220
- owl.useEffect(this.props.onCloseSidePanel, () => [composerStore.editionMode]);
30520
+ owl.useEffect((editionMode) => {
30521
+ if (editionMode !== "inactive") {
30522
+ this.props.onCloseSidePanel();
30523
+ }
30524
+ }, () => [composerStore.editionMode]);
30221
30525
  owl.onMounted(() => {
30222
30526
  composerStore.stopEdition();
30223
30527
  });
@@ -30303,6 +30607,24 @@ stores.inject(MyMetaStore, storeInstance);
30303
30607
  const contentZone = { ...tableZone, top: tableZone.top + numberOfHeaders };
30304
30608
  return contentZone.top <= contentZone.bottom ? contentZone : undefined;
30305
30609
  }
30610
+ function getTableTopLeft(table) {
30611
+ const range = table.range;
30612
+ return { row: range.zone.top, col: range.zone.left, sheetId: range.sheetId };
30613
+ }
30614
+ function createFilter(id, range, config, createRange) {
30615
+ const zone = range.zone;
30616
+ if (zone.left !== zone.right) {
30617
+ throw new Error("Can only define a filter on a single column");
30618
+ }
30619
+ const filteredZone = { ...zone, top: zone.top + config.numberOfHeaders };
30620
+ const filteredRange = createRange(range.sheetId, filteredZone);
30621
+ return {
30622
+ id,
30623
+ rangeWithHeaders: range,
30624
+ col: zone.left,
30625
+ filteredRange: filteredZone.top > filteredZone.bottom ? undefined : filteredRange,
30626
+ };
30627
+ }
30306
30628
  function getComputedTableStyle(tableConfig, numberOfCols, numberOfRows) {
30307
30629
  return {
30308
30630
  borders: getAllTableBorders(tableConfig, numberOfCols, numberOfRows),
@@ -30740,6 +31062,11 @@ stores.inject(MyMetaStore, storeInstance);
30740
31062
  color: #ffffff;
30741
31063
  background: #d94b4b;
30742
31064
  }
31065
+
31066
+ .o-info-icon {
31067
+ width: 14px;
31068
+ height: 14px;
31069
+ }
30743
31070
  }
30744
31071
  `;
30745
31072
  class TablePanel extends owl.Component {
@@ -30771,6 +31098,29 @@ stores.inject(MyMetaStore, storeInstance);
30771
31098
  const numberOfHeaders = hasHeaders ? 1 : 0;
30772
31099
  this.updateNumberOfHeaders(numberOfHeaders);
30773
31100
  }
31101
+ updateTableIsDynamic(isDynamic) {
31102
+ const newTableType = isDynamic ? "dynamic" : "forceStatic";
31103
+ if (newTableType === this.props.table.type) {
31104
+ return;
31105
+ }
31106
+ const uiTable = this.env.model.getters.getTable(getTableTopLeft(this.props.table));
31107
+ if (!uiTable) {
31108
+ return;
31109
+ }
31110
+ const sheetId = this.env.model.getters.getActiveSheetId();
31111
+ const result = this.env.model.dispatch("UPDATE_TABLE", {
31112
+ sheetId,
31113
+ zone: this.props.table.range.zone,
31114
+ newTableRange: uiTable.range.rangeData,
31115
+ tableType: newTableType,
31116
+ });
31117
+ const updatedTable = this.env.model.getters.getCoreTable(getTableTopLeft(this.props.table));
31118
+ if (result.isSuccessful && updatedTable) {
31119
+ const newTableRange = updatedTable.range;
31120
+ this.state.tableXc = this.env.model.getters.getRangeString(newTableRange, sheetId);
31121
+ this.state.tableZoneErrors = [];
31122
+ }
31123
+ }
30774
31124
  onChangeNumberOfHeaders(ev) {
30775
31125
  const input = ev.target;
30776
31126
  const numberOfHeaders = parseInt(input.value);
@@ -30794,35 +31144,59 @@ stores.inject(MyMetaStore, storeInstance);
30794
31144
  }
30795
31145
  const sheetId = this.env.model.getters.getActiveSheetId();
30796
31146
  this.state.tableXc = ranges[0];
31147
+ const newTableRange = this.env.model.getters.getRangeFromSheetXC(sheetId, this.state.tableXc);
30797
31148
  this.state.tableZoneErrors = this.env.model.canDispatch("UPDATE_TABLE", {
30798
31149
  sheetId,
30799
31150
  zone: this.props.table.range.zone,
30800
31151
  newTableRange: this.env.model.getters.getRangeDataFromXc(sheetId, this.state.tableXc),
31152
+ tableType: this.getNewTableType(newTableRange.zone),
30801
31153
  }).reasons;
30802
31154
  }
30803
31155
  onRangeConfirmed() {
30804
31156
  const sheetId = this.env.model.getters.getActiveSheetId();
30805
- const newRange = this.env.model.getters.getRangeFromSheetXC(sheetId, this.state.tableXc);
31157
+ let newRange = this.env.model.getters.getRangeFromSheetXC(sheetId, this.state.tableXc);
31158
+ if (getZoneArea(newRange.zone) === 1) {
31159
+ const extendedZone = this.env.model.getters.getContiguousZone(sheetId, newRange.zone);
31160
+ newRange = this.env.model.getters.getRangeFromZone(sheetId, extendedZone);
31161
+ }
30806
31162
  const result = this.env.model.dispatch("UPDATE_TABLE", {
30807
31163
  sheetId,
30808
31164
  zone: this.props.table.range.zone,
30809
31165
  newTableRange: newRange.rangeData,
31166
+ tableType: this.getNewTableType(newRange.zone),
30810
31167
  });
30811
- if (result.isSuccessful) {
30812
- const position = { col: newRange.zone.left, row: newRange.zone.top };
31168
+ const position = { sheetId, col: newRange.zone.left, row: newRange.zone.top };
31169
+ const updatedTable = this.env.model.getters.getCoreTable(position);
31170
+ if (result.isSuccessful && updatedTable) {
31171
+ const newTopLeft = getTableTopLeft(updatedTable);
30813
31172
  this.env.model.selection.selectZone({
30814
- zone: positionToZone(position),
30815
- cell: position,
31173
+ zone: positionToZone(newTopLeft),
31174
+ cell: newTopLeft,
30816
31175
  });
31176
+ const newTableRange = updatedTable.range;
31177
+ this.state.tableXc = this.env.model.getters.getRangeString(newTableRange, sheetId);
31178
+ }
31179
+ else {
31180
+ const oldTableRange = this.props.table.range;
31181
+ this.state.tableXc = this.env.model.getters.getRangeString(oldTableRange, sheetId);
30817
31182
  }
30818
31183
  this.state.tableZoneErrors = [];
30819
- this.state.tableXc = result.isSuccessful
30820
- ? this.state.tableXc
30821
- : this.env.model.getters.getRangeString(this.props.table.range, sheetId);
30822
31184
  }
30823
31185
  deleteTable() {
30824
31186
  const sheetId = this.env.model.getters.getActiveSheetId();
30825
- this.env.model.dispatch("REMOVE_TABLE", { sheetId, target: [this.props.table.range.zone] });
31187
+ this.env.model.dispatch("REMOVE_TABLE", {
31188
+ sheetId,
31189
+ target: [this.props.table.range.zone],
31190
+ });
31191
+ }
31192
+ getNewTableType(newTableZone) {
31193
+ if (this.props.table.type === "forceStatic") {
31194
+ return "forceStatic";
31195
+ }
31196
+ const sheetId = this.env.model.getters.getActiveSheetId();
31197
+ return this.env.model.getters.canCreateDynamicTableOnZones(sheetId, [newTableZone])
31198
+ ? "dynamic"
31199
+ : "static";
30826
31200
  }
30827
31201
  get tableConfig() {
30828
31202
  return this.props.table.config;
@@ -30840,6 +31214,14 @@ stores.inject(MyMetaStore, storeInstance);
30840
31214
  get hasFilterCheckboxTooltip() {
30841
31215
  return this.canHaveFilters ? undefined : TableTerms.Tooltips.filterWithoutHeader;
30842
31216
  }
31217
+ get canBeDynamic() {
31218
+ const sheetId = this.env.model.getters.getActiveSheetId();
31219
+ return (this.props.table.type === "dynamic" ||
31220
+ this.env.model.getters.canCreateDynamicTableOnZones(sheetId, [this.props.table.range.zone]));
31221
+ }
31222
+ get dynamicTableTooltip() {
31223
+ return TableTerms.Tooltips.isDynamic;
31224
+ }
30843
31225
  }
30844
31226
 
30845
31227
  const sidePanelRegistry = new Registry();
@@ -30902,11 +31284,8 @@ stores.inject(MyMetaStore, storeInstance);
30902
31284
  if (!table) {
30903
31285
  return { isOpen: false };
30904
31286
  }
30905
- return {
30906
- isOpen: true,
30907
- props: { table },
30908
- key: table.id,
30909
- };
31287
+ const coreTable = getters.getCoreTable(getTableTopLeft(table));
31288
+ return { isOpen: true, props: { table: coreTable }, key: table.id };
30910
31289
  },
30911
31290
  });
30912
31291
 
@@ -31085,6 +31464,9 @@ stores.inject(MyMetaStore, storeInstance);
31085
31464
  el?.focus({ preventScroll: true });
31086
31465
  }
31087
31466
  }, () => [this.env.model.getters.getSelectedFigureId(), this.props.figure.id, this.figureRef.el]);
31467
+ owl.onWillUnmount(() => {
31468
+ this.props.onFigureDeleted();
31469
+ });
31088
31470
  }
31089
31471
  clickAnchor(dirX, dirY, ev) {
31090
31472
  this.props.onClickAnchor(dirX, dirY, ev);
@@ -31103,6 +31485,7 @@ stores.inject(MyMetaStore, storeInstance);
31103
31485
  this.props.onFigureDeleted();
31104
31486
  ev.stopPropagation();
31105
31487
  ev.preventDefault();
31488
+ ev.stopPropagation();
31106
31489
  break;
31107
31490
  case "ArrowDown":
31108
31491
  case "ArrowLeft":
@@ -31123,6 +31506,7 @@ stores.inject(MyMetaStore, storeInstance);
31123
31506
  });
31124
31507
  ev.stopPropagation();
31125
31508
  ev.preventDefault();
31509
+ ev.stopPropagation();
31126
31510
  break;
31127
31511
  }
31128
31512
  }
@@ -31790,6 +32174,7 @@ stores.inject(MyMetaStore, storeInstance);
31790
32174
  // -----------------------------------------------------------------------------
31791
32175
  css /* scss */ `
31792
32176
  .o-formula-assistant {
32177
+ background: #ffffff;
31793
32178
  .o-formula-assistant-head {
31794
32179
  background-color: #f2f2f2;
31795
32180
  padding: 10px;
@@ -31845,6 +32230,9 @@ stores.inject(MyMetaStore, storeInstance);
31845
32230
  this.assistantState.allowCellSelectionBehind = false;
31846
32231
  }, 2000);
31847
32232
  }
32233
+ get formulaArgSeparator() {
32234
+ return this.env.model.getters.getLocale().formulaArgSeparator + " ";
32235
+ }
31848
32236
  }
31849
32237
 
31850
32238
  const functions$2 = functionRegistry.content;
@@ -32004,6 +32392,12 @@ stores.inject(MyMetaStore, storeInstance);
32004
32392
  owl.useEffect(() => {
32005
32393
  this.processContent();
32006
32394
  });
32395
+ owl.onPatched(() => {
32396
+ // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
32397
+ if (this.composerStore.editionMode === "inactive") {
32398
+ this.processTokenAtCursor();
32399
+ }
32400
+ });
32007
32401
  }
32008
32402
  // ---------------------------------------------------------------------------
32009
32403
  // Handlers
@@ -32742,8 +33136,7 @@ stores.inject(MyMetaStore, storeInstance);
32742
33136
  };
32743
33137
  getFilterHeadersPositions() {
32744
33138
  const sheetId = this.env.model.getters.getActiveSheetId();
32745
- const headerPositions = this.env.model.getters.getFilterHeaders(sheetId);
32746
- return headerPositions.map((position) => ({ sheetId, ...position }));
33139
+ return this.env.model.getters.getFilterHeaders(sheetId);
32747
33140
  }
32748
33141
  }
32749
33142
 
@@ -33984,11 +34377,6 @@ stores.inject(MyMetaStore, storeInstance);
33984
34377
  height: 10000px;
33985
34378
  background-color: ${SELECTION_BORDER_COLOR};
33986
34379
  }
33987
- .o-unhide-buttons {
33988
- width: fit-content;
33989
- gap: 5px;
33990
- transform: translate(-50%, 0);
33991
- }
33992
34380
  .o-unhide:hover {
33993
34381
  z-index: ${ComponentsImportance.Grid + 1};
33994
34382
  background-color: lightgrey;
@@ -34150,10 +34538,6 @@ stores.inject(MyMetaStore, storeInstance);
34150
34538
  height: 1px;
34151
34539
  background-color: ${SELECTION_BORDER_COLOR};
34152
34540
  }
34153
- .o-unhide-buttons {
34154
- height: fit-content;
34155
- transform: translate(0, -50%);
34156
- }
34157
34541
  .o-unhide:hover {
34158
34542
  z-index: ${ComponentsImportance.Grid + 1};
34159
34543
  background-color: lightgrey;
@@ -34614,19 +34998,16 @@ stores.inject(MyMetaStore, storeInstance);
34614
34998
  for (let col = left; col <= right; col++) {
34615
34999
  const colZone = { left: col, right: col, top: 0, bottom: numberOfRows - 1 };
34616
35000
  const { x, width } = this.getters.getVisibleRect(colZone);
34617
- const colHasFilter = this.getters.doesZonesContainFilter(sheetId, [colZone]);
34618
35001
  const isColActive = activeCols.has(col);
34619
35002
  const isColSelected = selectedCols.has(col);
34620
35003
  if (isColActive) {
34621
- ctx.fillStyle = colHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;
35004
+ ctx.fillStyle = BACKGROUND_HEADER_ACTIVE_COLOR;
34622
35005
  }
34623
35006
  else if (isColSelected) {
34624
- ctx.fillStyle = colHasFilter
34625
- ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR
34626
- : BACKGROUND_HEADER_SELECTED_COLOR;
35007
+ ctx.fillStyle = BACKGROUND_HEADER_SELECTED_COLOR;
34627
35008
  }
34628
35009
  else {
34629
- ctx.fillStyle = colHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;
35010
+ ctx.fillStyle = BACKGROUND_HEADER_COLOR;
34630
35011
  }
34631
35012
  ctx.fillRect(x, 0, width, HEADER_HEIGHT);
34632
35013
  }
@@ -34634,19 +35015,16 @@ stores.inject(MyMetaStore, storeInstance);
34634
35015
  for (let row = top; row <= bottom; row++) {
34635
35016
  const rowZone = { top: row, bottom: row, left: 0, right: numberOfCols - 1 };
34636
35017
  const { y, height } = this.getters.getVisibleRect(rowZone);
34637
- const rowHasFilter = this.getters.doesZonesContainFilter(sheetId, [rowZone]);
34638
35018
  const isRowActive = activeRows.has(row);
34639
35019
  const isRowSelected = selectedRows.has(row);
34640
35020
  if (isRowActive) {
34641
- ctx.fillStyle = rowHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;
35021
+ ctx.fillStyle = BACKGROUND_HEADER_ACTIVE_COLOR;
34642
35022
  }
34643
35023
  else if (isRowSelected) {
34644
- ctx.fillStyle = rowHasFilter
34645
- ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR
34646
- : BACKGROUND_HEADER_SELECTED_COLOR;
35024
+ ctx.fillStyle = BACKGROUND_HEADER_SELECTED_COLOR;
34647
35025
  }
34648
35026
  else {
34649
- ctx.fillStyle = rowHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;
35027
+ ctx.fillStyle = BACKGROUND_HEADER_COLOR;
34650
35028
  }
34651
35029
  ctx.fillRect(0, y, HEADER_WIDTH, height);
34652
35030
  }
@@ -35367,7 +35745,7 @@ stores.inject(MyMetaStore, storeInstance);
35367
35745
  onScroll(offset) {
35368
35746
  const { scrollX } = this.env.model.getters.getActiveSheetDOMScrollInfo();
35369
35747
  this.env.model.dispatch("SET_VIEWPORT_OFFSET", {
35370
- offsetX: scrollX,
35748
+ offsetX: scrollX, // offsetX is the same
35371
35749
  offsetY: offset,
35372
35750
  });
35373
35751
  }
@@ -35655,8 +36033,8 @@ stores.inject(MyMetaStore, storeInstance);
35655
36033
  "Ctrl+Shift+L": () => this.setHorizontalAlign("left"),
35656
36034
  "Ctrl+Shift+R": () => this.setHorizontalAlign("right"),
35657
36035
  "Ctrl+Shift+V": () => PASTE_AS_VALUE_ACTION(this.env),
35658
- "Ctrl+Shift+<": () => this.clearFormatting(),
35659
- "Ctrl+<": () => this.clearFormatting(),
36036
+ "Ctrl+Shift+<": () => this.clearFormatting(), // for qwerty
36037
+ "Ctrl+<": () => this.clearFormatting(), // for azerty
35660
36038
  "Ctrl+Shift+ ": () => {
35661
36039
  this.env.model.selection.selectAll();
35662
36040
  },
@@ -36092,6 +36470,7 @@ stores.inject(MyMetaStore, storeInstance);
36092
36470
  "surfaceChart",
36093
36471
  "surface3DChart",
36094
36472
  "bubbleChart",
36473
+ "comboChart",
36095
36474
  ];
36096
36475
 
36097
36476
  /** In XLSX color format (no #) */
@@ -36423,10 +36802,10 @@ stores.inject(MyMetaStore, storeInstance);
36423
36802
  const CF_TYPE_CONVERSION_MAP = {
36424
36803
  aboveAverage: undefined,
36425
36804
  expression: undefined,
36426
- cellIs: undefined,
36427
- colorScale: undefined,
36805
+ cellIs: undefined, // exist but isn't an operator in o_spreadsheet
36806
+ colorScale: undefined, // exist but isn't an operator in o_spreadsheet
36428
36807
  dataBar: undefined,
36429
- iconSet: undefined,
36808
+ iconSet: undefined, // exist but isn't an operator in o_spreadsheet
36430
36809
  top10: undefined,
36431
36810
  uniqueValues: undefined,
36432
36811
  duplicateValues: undefined,
@@ -36503,6 +36882,7 @@ stores.inject(MyMetaStore, storeInstance);
36503
36882
  surfaceChart: undefined,
36504
36883
  surface3DChart: undefined,
36505
36884
  bubbleChart: undefined,
36885
+ comboChart: "combo",
36506
36886
  };
36507
36887
  /** Conversion map for the SUBTOTAL(index, formula) function in xlsx, index <=> actual function*/
36508
36888
  const SUBTOTAL_FUNCTION_CONVERSION_MAP = {
@@ -36661,7 +37041,7 @@ stores.inject(MyMetaStore, storeInstance);
36661
37041
  61: "993366",
36662
37042
  62: "333399",
36663
37043
  63: "333333",
36664
- 64: "000000",
37044
+ 64: "000000", // system foreground
36665
37045
  65: "FFFFFF", // system background
36666
37046
  };
36667
37047
  const IMAGE_MIMETYPE_TO_EXTENSION_MAPPING = {
@@ -37853,7 +38233,7 @@ stores.inject(MyMetaStore, storeInstance);
37853
38233
  function getSheetDims(sheet) {
37854
38234
  const dims = [0, 0];
37855
38235
  for (let row of sheet.rows) {
37856
- dims[0] = Math.max(dims[0], ...row.cells.map((cell) => toCartesian(cell.xc).col));
38236
+ dims[0] = Math.max(dims[0], largeMax(row.cells.map((cell) => toCartesian(cell.xc).col)));
37857
38237
  dims[1] = Math.max(dims[1], row.index);
37858
38238
  }
37859
38239
  dims[0] = Math.max(dims[0], EXCEL_IMPORT_DEFAULT_NUMBER_OF_COLS);
@@ -38719,6 +39099,9 @@ stores.inject(MyMetaStore, storeInstance);
38719
39099
  if (!CHART_TYPE_CONVERSION_MAP[chartType]) {
38720
39100
  throw new Error(`Unsupported chart type ${chartType}`);
38721
39101
  }
39102
+ if (CHART_TYPE_CONVERSION_MAP[chartType] === "combo") {
39103
+ return this.extractComboChart(rootChartElement);
39104
+ }
38722
39105
  // Title can be separated into multiple xml elements (for styling and such), we only import the text
38723
39106
  const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:title a:t" }, (textElement) => {
38724
39107
  return textElement.textContent || "";
@@ -38747,6 +39130,37 @@ stores.inject(MyMetaStore, storeInstance);
38747
39130
  };
38748
39131
  })[0];
38749
39132
  }
39133
+ extractComboChart(chartElement) {
39134
+ // Title can be separated into multiple xml elements (for styling and such), we only import the text
39135
+ const chartTitle = this.mapOnElements({ parent: chartElement, query: "c:title a:t" }, (textElement) => {
39136
+ return textElement.textContent || "";
39137
+ }).join("");
39138
+ const barChartGrouping = this.extractChildAttr(chartElement, "c:grouping", "val", {
39139
+ default: "clustered",
39140
+ }).asString();
39141
+ return {
39142
+ title: chartTitle,
39143
+ type: "combo",
39144
+ dataSets: [
39145
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`)),
39146
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`)),
39147
+ ],
39148
+ labelRange: this.extractChildTextContent(chartElement, "c:ser c:cat c:f"),
39149
+ backgroundColor: this.extractChildAttr(chartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
39150
+ default: "ffffff",
39151
+ }).asString(),
39152
+ verticalAxisPosition: this.extractChildAttr(chartElement, "c:valAx > c:axPos", "val", {
39153
+ default: "l",
39154
+ }).asString() === "r"
39155
+ ? "right"
39156
+ : "left",
39157
+ legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(chartElement, "c:legendPos", "val", {
39158
+ default: "b",
39159
+ }).asString()],
39160
+ stacked: barChartGrouping === "stacked",
39161
+ fontColor: "000000",
39162
+ };
39163
+ }
38750
39164
  extractChartDatasets(chartElement) {
38751
39165
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
38752
39166
  return {
@@ -38764,12 +39178,21 @@ stores.inject(MyMetaStore, storeInstance);
38764
39178
  if (!plotAreaElement) {
38765
39179
  throw new Error("Missing plot area in the chart definition.");
38766
39180
  }
39181
+ let globalTag = undefined;
38767
39182
  for (let child of plotAreaElement.children) {
38768
39183
  const tag = removeTagEscapedNamespaces(child.tagName);
38769
39184
  if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
38770
- return tag;
39185
+ if (!globalTag) {
39186
+ globalTag = tag;
39187
+ }
39188
+ else if (globalTag !== tag) {
39189
+ globalTag = "comboChart";
39190
+ }
38771
39191
  }
38772
39192
  }
39193
+ if (globalTag) {
39194
+ return globalTag;
39195
+ }
38773
39196
  throw new Error("Unknown chart type");
38774
39197
  }
38775
39198
  }
@@ -42360,6 +42783,9 @@ stores.inject(MyMetaStore, storeInstance);
42360
42783
  if (newRule.criterion.type === "isBoolean") {
42361
42784
  this.setCenterStyleToBooleanCells(newRule);
42362
42785
  }
42786
+ else if (newRule.criterion.type === "isValueInList") {
42787
+ newRule.criterion.values = Array.from(new Set(newRule.criterion.values));
42788
+ }
42363
42789
  const adaptedRules = this.removeRangesFromRules(sheetId, newRule.ranges, rules);
42364
42790
  const ruleIndex = adaptedRules.findIndex((rule) => rule.id === newRule.id);
42365
42791
  if (ruleIndex !== -1) {
@@ -42756,7 +43182,7 @@ stores.inject(MyMetaStore, storeInstance);
42756
43182
  if (hiddenElements.size >= elements) {
42757
43183
  return "TooManyHiddenElements" /* CommandResult.TooManyHiddenElements */;
42758
43184
  }
42759
- else if (Math.min(...cmd.elements) < 0 || Math.max(...cmd.elements) > elements) {
43185
+ else if (largeMin(cmd.elements) < 0 || largeMax(cmd.elements) > elements) {
42760
43186
  return "InvalidHeaderIndex" /* CommandResult.InvalidHeaderIndex */;
42761
43187
  }
42762
43188
  else {
@@ -43085,7 +43511,6 @@ stores.inject(MyMetaStore, storeInstance);
43085
43511
  "isInSameMerge",
43086
43512
  "isMergeHidden",
43087
43513
  "getMainCellPosition",
43088
- "getBottomLeftCell",
43089
43514
  "expandZone",
43090
43515
  "doesIntersectMerge",
43091
43516
  "doesColumnsHaveCommonMerges",
@@ -43267,13 +43692,6 @@ stores.inject(MyMetaStore, storeInstance);
43267
43692
  const mergeTopLeftPos = this.getMerge(position).topLeft;
43268
43693
  return { sheetId: position.sheetId, col: mergeTopLeftPos.col, row: mergeTopLeftPos.row };
43269
43694
  }
43270
- getBottomLeftCell(position) {
43271
- if (!this.isInMerge(position)) {
43272
- return position;
43273
- }
43274
- const { bottom, left } = this.getMerge(position);
43275
- return { sheetId: position.sheetId, col: left, row: bottom };
43276
- }
43277
43695
  isMergeHidden(sheetId, merge) {
43278
43696
  const hiddenColsGroups = this.getters.getHiddenColsGroups(sheetId);
43279
43697
  const hiddenRowsGroups = this.getters.getHiddenRowsGroups(sheetId);
@@ -43574,8 +43992,8 @@ stores.inject(MyMetaStore, storeInstance);
43574
43992
  let newRange = range;
43575
43993
  let changeType = "NONE";
43576
43994
  for (let group of groups) {
43577
- const min = Math.min(...group);
43578
- const max = Math.max(...group);
43995
+ const min = largeMin(group);
43996
+ const max = largeMax(group);
43579
43997
  if (range.zone[start] <= min && min <= range.zone[end]) {
43580
43998
  const toRemove = Math.min(range.zone[end], max) - min + 1;
43581
43999
  changeType = "RESIZE";
@@ -43957,7 +44375,6 @@ stores.inject(MyMetaStore, storeInstance);
43957
44375
  "getSheetIds",
43958
44376
  "getVisibleSheetIds",
43959
44377
  "isSheetVisible",
43960
- "getEvaluationSheets",
43961
44378
  "doesHeaderExist",
43962
44379
  "doesHeadersExist",
43963
44380
  "getCell",
@@ -44024,8 +44441,8 @@ stores.inject(MyMetaStore, storeInstance);
44024
44441
  }
44025
44442
  return "Success" /* CommandResult.Success */;
44026
44443
  case "REMOVE_COLUMNS_ROWS": {
44027
- const min = Math.min(...cmd.elements);
44028
- const max = Math.max(...cmd.elements);
44444
+ const min = largeMin(cmd.elements);
44445
+ const max = largeMax(cmd.elements);
44029
44446
  if (min < 0 || !this.doesHeaderExist(cmd.sheetId, cmd.dimension, max)) {
44030
44447
  return "InvalidHeaderIndex" /* CommandResult.InvalidHeaderIndex */;
44031
44448
  }
@@ -44216,9 +44633,6 @@ stores.inject(MyMetaStore, storeInstance);
44216
44633
  getVisibleSheetIds() {
44217
44634
  return this.orderedSheetIds.filter(this.isSheetVisible.bind(this));
44218
44635
  }
44219
- getEvaluationSheets() {
44220
- return this.sheets;
44221
- }
44222
44636
  doesHeaderExist(sheetId, dimension, index) {
44223
44637
  return dimension === "COL"
44224
44638
  ? index >= 0 && index < this.getNumberCols(sheetId)
@@ -44227,13 +44641,6 @@ stores.inject(MyMetaStore, storeInstance);
44227
44641
  doesHeadersExist(sheetId, dimension, headerIndexes) {
44228
44642
  return headerIndexes.every((index) => this.doesHeaderExist(sheetId, dimension, index));
44229
44643
  }
44230
- getRow(sheetId, index) {
44231
- const row = this.getSheet(sheetId).rows[index];
44232
- if (!row) {
44233
- throw new Error(`Row ${row} not found.`);
44234
- }
44235
- return row;
44236
- }
44237
44644
  getCell({ sheetId, col, row }) {
44238
44645
  const sheet = this.tryGetSheet(sheetId);
44239
44646
  const cellId = sheet?.rows[row]?.cells[col];
@@ -44818,23 +45225,12 @@ stores.inject(MyMetaStore, storeInstance);
44818
45225
  }
44819
45226
 
44820
45227
  class TablePlugin extends CorePlugin {
44821
- static getters = [
44822
- "doesZonesContainFilter",
44823
- "getFilter",
44824
- "getFilters",
44825
- "getTable",
44826
- "getTables",
44827
- "getTablesInZone",
44828
- "getTablesOverlappingZones",
44829
- "getFilterId",
44830
- "getFilterHeaders",
44831
- "isFilterHeader",
44832
- ];
45228
+ static getters = ["getCoreTable", "getCoreTables"];
44833
45229
  tables = {};
44834
45230
  adaptRanges(applyChange, sheetId) {
44835
45231
  const sheetIds = sheetId ? [sheetId] : this.getters.getSheetIds();
44836
45232
  for (const sheetId of sheetIds) {
44837
- for (const table of this.getTables(sheetId)) {
45233
+ for (const table of this.getCoreTables(sheetId)) {
44838
45234
  this.applyRangeChangeOnTable(sheetId, table, applyChange);
44839
45235
  }
44840
45236
  }
@@ -44850,15 +45246,16 @@ stores.inject(MyMetaStore, storeInstance);
44850
45246
  ? "TableOverlap" /* CommandResult.TableOverlap */
44851
45247
  : "Success" /* CommandResult.Success */, (cmd) => this.checkTableConfigUpdateIsValid(cmd.config));
44852
45248
  case "UPDATE_TABLE":
44853
- const updatedTable = this.getTables(cmd.sheetId).find((table) => deepEquals(table.range.zone, cmd.zone));
45249
+ const updatedTable = this.getTableFromZone(cmd.sheetId, cmd.zone);
44854
45250
  if (!updatedTable) {
44855
45251
  return "TableNotFound" /* CommandResult.TableNotFound */;
44856
45252
  }
44857
45253
  return this.checkValidations(cmd, this.checkUpdatedTableZoneIsValid, (cmd) => this.checkTableConfigUpdateIsValid(cmd.config));
44858
45254
  case "ADD_MERGE":
44859
- for (const merge of cmd.target) {
44860
- for (const table of this.getTables(cmd.sheetId)) {
44861
- if (overlap(table.range.zone, merge)) {
45255
+ for (const table of this.getCoreTables(cmd.sheetId)) {
45256
+ const tableZone = table.range.zone;
45257
+ for (const merge of cmd.target) {
45258
+ if (overlap(tableZone, merge)) {
44862
45259
  return "MergeInTable" /* CommandResult.MergeInTable */;
44863
45260
  }
44864
45261
  }
@@ -44880,8 +45277,11 @@ stores.inject(MyMetaStore, storeInstance);
44880
45277
  }
44881
45278
  case "DUPLICATE_SHEET": {
44882
45279
  const newTables = {};
44883
- for (const table of this.getTables(cmd.sheetId)) {
44884
- newTables[table.id] = this.copyTableForSheet(cmd.sheetIdTo, table);
45280
+ for (const table of this.getCoreTables(cmd.sheetId)) {
45281
+ newTables[table.id] =
45282
+ table.type === "dynamic"
45283
+ ? this.copyDynamicTableForSheet(cmd.sheetIdTo, table)
45284
+ : this.copyStaticTableForSheet(cmd.sheetIdTo, table);
44885
45285
  }
44886
45286
  this.history.update("tables", cmd.sheetIdTo, newTables);
44887
45287
  break;
@@ -44892,14 +45292,17 @@ stores.inject(MyMetaStore, storeInstance);
44892
45292
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
44893
45293
  this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
44894
45294
  const id = this.uuidGenerator.uuidv4();
44895
- const newTable = this.createTable(id, union, cmd.config || DEFAULT_TABLE_CONFIG);
45295
+ const config = cmd.config || DEFAULT_TABLE_CONFIG;
45296
+ const newTable = cmd.tableType === "dynamic"
45297
+ ? this.createDynamicTable(id, union, config)
45298
+ : this.createStaticTable(id, cmd.tableType, union, config);
44896
45299
  this.history.update("tables", cmd.sheetId, newTable.id, newTable);
44897
45300
  break;
44898
45301
  }
44899
45302
  case "REMOVE_TABLE": {
44900
45303
  const tables = {};
44901
- for (const table of this.getTables(cmd.sheetId)) {
44902
- if (cmd.target.every((zone) => !intersection(zone, table.range.zone))) {
45304
+ for (const table of this.getCoreTables(cmd.sheetId)) {
45305
+ if (cmd.target.every((zone) => !intersection(table.range.zone, zone))) {
44903
45306
  tables[table.id] = table;
44904
45307
  }
44905
45308
  }
@@ -44907,23 +45310,15 @@ stores.inject(MyMetaStore, storeInstance);
44907
45310
  break;
44908
45311
  }
44909
45312
  case "UPDATE_TABLE": {
44910
- const table = this.getTables(cmd.sheetId).find((table) => deepEquals(table.range.zone, cmd.zone));
44911
- if (table) {
44912
- const newTableRange = cmd.newTableRange
44913
- ? this.getters.getRangeFromRangeData(cmd.newTableRange)
44914
- : undefined;
44915
- if (newTableRange) {
44916
- const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, newTableRange.zone);
44917
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
44918
- }
44919
- const newTable = this.updateTable(table, newTableRange, cmd.config);
44920
- this.history.update("tables", cmd.sheetId, table.id, newTable);
44921
- }
45313
+ this.updateTable(cmd);
44922
45314
  break;
44923
45315
  }
44924
45316
  case "UPDATE_CELL": {
44925
45317
  const sheetId = cmd.sheetId;
44926
- for (const table of this.getTables(sheetId)) {
45318
+ for (const table of this.getCoreTables(sheetId)) {
45319
+ if (table.type === "dynamic") {
45320
+ continue;
45321
+ }
44927
45322
  const direction = this.canUpdateCellCmdExtendTable(cmd, table);
44928
45323
  if (direction === "down") {
44929
45324
  this.extendTableDown(sheetId, table);
@@ -44947,66 +45342,24 @@ stores.inject(MyMetaStore, storeInstance);
44947
45342
  }
44948
45343
  }
44949
45344
  }
44950
- getFilters(sheetId) {
44951
- return this.getTables(sheetId)
44952
- .filter((table) => table.config.hasFilters)
44953
- .map((table) => table.filters)
44954
- .flat();
44955
- }
44956
- getTables(sheetId) {
45345
+ getCoreTables(sheetId) {
44957
45346
  return this.tables[sheetId] ? Object.values(this.tables[sheetId]).filter(isDefined$1) : [];
44958
45347
  }
44959
- getFilter(position) {
44960
- const table = this.getTable(position);
44961
- if (!table || !table.config.hasFilters) {
44962
- return undefined;
44963
- }
44964
- return table.filters.find((filter) => filter.col === position.col);
44965
- }
44966
- getFilterId(position) {
44967
- return this.getFilter(position)?.id;
44968
- }
44969
- getTable({ sheetId, col, row }) {
44970
- return this.getTables(sheetId).find((table) => isInside(col, row, table.range.zone));
44971
- }
44972
- /** Get the filter tables that are fully inside the given zone */
44973
- getTablesInZone(sheetId, zone) {
44974
- return this.getTables(sheetId).filter((table) => isZoneInside(table.range.zone, zone));
45348
+ getCoreTable({ sheetId, col, row }) {
45349
+ return this.getCoreTables(sheetId).find((table) => isInside(col, row, table.range.zone));
44975
45350
  }
44976
45351
  getTablesOverlappingZones(sheetId, zones) {
44977
- return this.getTables(sheetId).filter((table) => zones.some((zone) => overlap(table.range.zone, zone)));
44978
- }
44979
- doesZonesContainFilter(sheetId, zones) {
44980
- return (this.getTablesOverlappingZones(sheetId, zones).filter((table) => table.config.hasFilters)
44981
- .length > 0);
44982
- }
44983
- getFilterHeaders(sheetId) {
44984
- const headers = [];
44985
- for (const table of this.getTables(sheetId)) {
44986
- if (!table.config.hasFilters) {
44987
- continue;
44988
- }
44989
- const zone = table.range.zone;
44990
- const row = zone.top;
44991
- for (let col = zone.left; col <= zone.right; col++) {
44992
- headers.push({ col, row });
44993
- }
44994
- }
44995
- return headers;
44996
- }
44997
- isFilterHeader({ sheetId, col, row }) {
44998
- const headers = this.getFilterHeaders(sheetId);
44999
- return headers.some((header) => header.col === col && header.row === row);
45352
+ return this.getCoreTables(sheetId).filter((table) => zones.some((zone) => overlap(table.range.zone, zone)));
45000
45353
  }
45001
45354
  /** Extend a table down one row */
45002
45355
  extendTableDown(sheetId, table) {
45003
45356
  const newRange = this.getters.extendRange(table.range, "ROW", 1);
45004
- this.history.update("tables", sheetId, table.id, this.updateTable(table, newRange));
45357
+ this.history.update("tables", sheetId, table.id, this.updateStaticTable(table, newRange));
45005
45358
  }
45006
45359
  /** Extend a table right one col */
45007
45360
  extendTableRight(sheetId, table) {
45008
45361
  const newRange = this.getters.extendRange(table.range, "COL", 1);
45009
- this.history.update("tables", sheetId, table.id, this.updateTable(table, newRange));
45362
+ this.history.update("tables", sheetId, table.id, this.updateStaticTable(table, newRange));
45010
45363
  }
45011
45364
  /**
45012
45365
  * Check if an UpdateCell command should cause the given table to be extended by one row or col.
@@ -45043,12 +45396,22 @@ stores.inject(MyMetaStore, storeInstance);
45043
45396
  const cellContent = this.getters.getCell(cellPosition)?.content;
45044
45397
  if (cellContent ||
45045
45398
  this.getters.isInMerge(cellPosition) ||
45046
- this.getters.getTable(cellPosition)) {
45399
+ this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
45047
45400
  return "none";
45048
45401
  }
45049
45402
  }
45050
45403
  return direction;
45051
45404
  }
45405
+ getTableFromZone(sheetId, zone) {
45406
+ for (const table of this.getCoreTables(sheetId)) {
45407
+ const tableZone = table.range.zone;
45408
+ // Only check top left to match dynamic tables
45409
+ if (tableZone.left === zone.left && tableZone.top === zone.top) {
45410
+ return table;
45411
+ }
45412
+ }
45413
+ return undefined;
45414
+ }
45052
45415
  checkUpdatedTableZoneIsValid(cmd) {
45053
45416
  if (!cmd.newTableRange) {
45054
45417
  return "Success" /* CommandResult.Success */;
@@ -45058,7 +45421,11 @@ stores.inject(MyMetaStore, storeInstance);
45058
45421
  if (zoneIsInSheet !== "Success" /* CommandResult.Success */) {
45059
45422
  return zoneIsInSheet;
45060
45423
  }
45061
- const overlappingTables = this.getTablesOverlappingZones(cmd.sheetId, [newTableZone]).filter((table) => !deepEquals(table.range.zone, cmd.zone));
45424
+ const updatedTable = this.getTableFromZone(cmd.sheetId, cmd.zone);
45425
+ if (!updatedTable) {
45426
+ return "TableNotFound" /* CommandResult.TableNotFound */;
45427
+ }
45428
+ const overlappingTables = this.getTablesOverlappingZones(cmd.sheetId, [newTableZone]).filter((table) => table.id !== updatedTable.id);
45062
45429
  return overlappingTables.length ? "TableOverlap" /* CommandResult.TableOverlap */ : "Success" /* CommandResult.Success */;
45063
45430
  }
45064
45431
  checkTableConfigUpdateIsValid(config) {
@@ -45076,7 +45443,7 @@ stores.inject(MyMetaStore, storeInstance);
45076
45443
  }
45077
45444
  return "Success" /* CommandResult.Success */;
45078
45445
  }
45079
- createTable(id, tableRange, config, filters) {
45446
+ createStaticTable(id, type, tableRange, config, filters) {
45080
45447
  const zone = tableRange.zone;
45081
45448
  if (!filters) {
45082
45449
  filters = [];
@@ -45091,9 +45458,51 @@ stores.inject(MyMetaStore, storeInstance);
45091
45458
  range: tableRange,
45092
45459
  filters,
45093
45460
  config,
45461
+ type,
45094
45462
  };
45095
45463
  }
45096
- updateTable(table, newRange, configUpdate) {
45464
+ createDynamicTable(id, tableRange, config) {
45465
+ const zone = zoneToTopLeft(tableRange.zone);
45466
+ return {
45467
+ id,
45468
+ range: this.getters.getRangeFromZone(tableRange.sheetId, zone),
45469
+ config,
45470
+ type: "dynamic",
45471
+ };
45472
+ }
45473
+ updateTable(cmd) {
45474
+ const table = this.getTableFromZone(cmd.sheetId, cmd.zone);
45475
+ if (!table) {
45476
+ return;
45477
+ }
45478
+ const newTableRange = cmd.newTableRange
45479
+ ? this.getters.getRangeFromRangeData(cmd.newTableRange)
45480
+ : undefined;
45481
+ if (newTableRange) {
45482
+ const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, newTableRange.zone);
45483
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
45484
+ }
45485
+ const range = newTableRange || table.range;
45486
+ const newConfig = this.updateTableConfig(cmd.config, table.config);
45487
+ const newTableType = cmd.tableType ?? table.type;
45488
+ if ((newTableType === "dynamic" && table.type !== "dynamic") ||
45489
+ (newTableType !== "dynamic" && table.type === "dynamic")) {
45490
+ const newTable = newTableType === "dynamic"
45491
+ ? this.createDynamicTable(table.id, range, newConfig)
45492
+ : this.createStaticTable(table.id, newTableType, range, newConfig);
45493
+ this.history.update("tables", cmd.sheetId, table.id, newTable);
45494
+ }
45495
+ else {
45496
+ const updatedTable = table.type === "dynamic"
45497
+ ? this.updateDynamicTable(table, range, newConfig)
45498
+ : this.updateStaticTable(table, range, newConfig, newTableType);
45499
+ this.history.update("tables", cmd.sheetId, table.id, updatedTable);
45500
+ }
45501
+ }
45502
+ updateStaticTable(table, newRange, configUpdate, newTableType = table.type) {
45503
+ if (newTableType === "dynamic") {
45504
+ throw new Error("Cannot use updateStaticTable to update a dynamic table");
45505
+ }
45097
45506
  const tableRange = newRange ? newRange : table.range;
45098
45507
  const tableZone = tableRange.zone;
45099
45508
  const newConfig = this.updateTableConfig(configUpdate, table.config);
@@ -45114,8 +45523,16 @@ stores.inject(MyMetaStore, storeInstance);
45114
45523
  range: tableRange,
45115
45524
  config,
45116
45525
  filters: filters.length ? filters : table.filters,
45526
+ type: newTableType,
45117
45527
  };
45118
45528
  }
45529
+ updateDynamicTable(table, newRange, newConfig) {
45530
+ const range = newRange
45531
+ ? this.getters.getRangeFromZone(newRange.sheetId, zoneToTopLeft(newRange.zone))
45532
+ : table.range;
45533
+ const config = newConfig ? newConfig : table.config;
45534
+ return { ...table, range, config };
45535
+ }
45119
45536
  /**
45120
45537
  * Update the old config of a table with the new partial config from an UpdateTable command.
45121
45538
  *
@@ -45137,35 +45554,29 @@ stores.inject(MyMetaStore, storeInstance);
45137
45554
  }
45138
45555
  createFilterFromZone(id, sheetId, zone, config) {
45139
45556
  const range = this.getters.getRangeFromZone(sheetId, zone);
45140
- return this.createFilter(id, range, config);
45141
- }
45142
- createFilter(id, range, config) {
45143
- const zone = range.zone;
45144
- if (zone.left !== zone.right) {
45145
- throw new Error("Can only define a filter on a single column");
45146
- }
45147
- const contentZone = getTableContentZone(zone, config);
45148
- const filteredRange = contentZone
45149
- ? this.getters.getRangeFromZone(range.sheetId, contentZone)
45150
- : undefined;
45151
- return {
45152
- id,
45153
- rangeWithHeaders: range,
45154
- col: zone.left,
45155
- filteredRange,
45156
- };
45557
+ return createFilter(id, range, config, this.getters.getRangeFromZone);
45157
45558
  }
45158
- copyTableForSheet(sheetId, table) {
45559
+ copyStaticTableForSheet(sheetId, table) {
45159
45560
  const newRange = this.getters.getRangeFromZone(sheetId, table.range.zone);
45160
45561
  const newFilters = table.filters.map((filter) => {
45161
45562
  const newFilterRange = this.getters.getRangeFromZone(sheetId, filter.rangeWithHeaders.zone);
45162
- return this.createFilter(filter.id, newFilterRange, table.config);
45563
+ return createFilter(filter.id, newFilterRange, table.config, this.getters.getRangeFromZone);
45163
45564
  });
45164
45565
  return {
45165
45566
  id: table.id,
45166
45567
  range: newRange,
45167
45568
  filters: newFilters,
45168
45569
  config: deepCopy(table.config),
45570
+ type: table.type,
45571
+ };
45572
+ }
45573
+ copyDynamicTableForSheet(sheetId, table) {
45574
+ const newRange = this.getters.getRangeFromZone(sheetId, table.range.zone);
45575
+ return {
45576
+ id: table.id,
45577
+ range: newRange,
45578
+ config: deepCopy(table.config),
45579
+ type: "dynamic",
45169
45580
  };
45170
45581
  }
45171
45582
  applyRangeChangeOnTable(sheetId, table, applyChange) {
@@ -45180,6 +45591,11 @@ stores.inject(MyMetaStore, storeInstance);
45180
45591
  default:
45181
45592
  newTableRange = tableRangeChange.range;
45182
45593
  }
45594
+ if (table.type === "dynamic") {
45595
+ const newTable = this.updateDynamicTable(table, newTableRange);
45596
+ this.history.update("tables", sheetId, table.id, newTable);
45597
+ return;
45598
+ }
45183
45599
  const filters = [];
45184
45600
  for (const filter of table.filters) {
45185
45601
  const filterRangeChange = applyChange(filter.rangeWithHeaders);
@@ -45191,7 +45607,7 @@ stores.inject(MyMetaStore, storeInstance);
45191
45607
  break;
45192
45608
  default:
45193
45609
  const newFilterRange = filterRangeChange.range;
45194
- const newFilter = this.createFilter(filter.id, newFilterRange, table.config);
45610
+ const newFilter = createFilter(filter.id, newFilterRange, table.config, this.getters.getRangeFromZone);
45195
45611
  filters.push(newFilter);
45196
45612
  }
45197
45613
  }
@@ -45206,7 +45622,7 @@ stores.inject(MyMetaStore, storeInstance);
45206
45622
  }
45207
45623
  filters.sort((f1, f2) => f1.col - f2.col);
45208
45624
  }
45209
- const newTable = this.createTable(table.id, newTableRange, table.config, filters);
45625
+ const newTable = this.createStaticTable(table.id, table.type, newTableRange, table.config, filters);
45210
45626
  this.history.update("tables", sheetId, table.id, newTable);
45211
45627
  }
45212
45628
  // ---------------------------------------------------------------------------
@@ -45217,16 +45633,20 @@ stores.inject(MyMetaStore, storeInstance);
45217
45633
  for (const tableData of sheet.tables || []) {
45218
45634
  const uuid = this.uuidGenerator.uuidv4();
45219
45635
  const tableConfig = tableData.config || DEFAULT_TABLE_CONFIG;
45220
- const tableRange = this.getters.getRangeFromSheetXC(sheet.id, tableData.range);
45221
- const table = this.createTable(uuid, tableRange, tableConfig);
45636
+ const range = this.getters.getRangeFromSheetXC(sheet.id, tableData.range);
45637
+ const tableType = tableData.type || "static";
45638
+ const table = tableType === "dynamic"
45639
+ ? this.createDynamicTable(uuid, range, tableConfig)
45640
+ : this.createStaticTable(uuid, tableType, range, tableConfig);
45222
45641
  this.history.update("tables", sheet.id, table.id, table);
45223
45642
  }
45224
45643
  }
45225
45644
  }
45226
45645
  export(data) {
45227
45646
  for (const sheet of data.sheets) {
45228
- for (const table of this.getTables(sheet.id)) {
45229
- const tableData = { range: zoneToXc(table.range.zone) };
45647
+ for (const table of this.getCoreTables(sheet.id)) {
45648
+ const range = zoneToXc(table.range.zone);
45649
+ const tableData = { range, type: table.type };
45230
45650
  if (!deepEquals(table.config, DEFAULT_TABLE_CONFIG)) {
45231
45651
  tableData.config = table.config;
45232
45652
  }
@@ -45789,12 +46209,6 @@ stores.inject(MyMetaStore, storeInstance);
45789
46209
  : _t("Function %s expects its parameters to be single values or single cell references, not ranges.", functionName.toString()));
45790
46210
  }
45791
46211
  const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
45792
- return this.readCell(position);
45793
- }
45794
- readCell(position) {
45795
- if (!this.getters.tryGetSheet(position.sheetId)) {
45796
- throw new EvaluationError(_t("Invalid sheet name"));
45797
- }
45798
46212
  return this.computeCell(position);
45799
46213
  }
45800
46214
  /**
@@ -45830,7 +46244,7 @@ stores.inject(MyMetaStore, storeInstance);
45830
46244
  matrix[colIndex] = new Array(height);
45831
46245
  for (let row = _zone.top; row <= _zone.bottom; row++) {
45832
46246
  const rowIndex = row - _zone.top;
45833
- matrix[colIndex][rowIndex] = this.readCell({ sheetId, col, row });
46247
+ matrix[colIndex][rowIndex] = this.computeCell({ sheetId, col, row });
45834
46248
  }
45835
46249
  }
45836
46250
  this.rangeCache[cacheKey] = matrix;
@@ -46938,18 +47352,22 @@ stores.inject(MyMetaStore, storeInstance);
46938
47352
  getEvaluatedCell(position) {
46939
47353
  return this.evaluatedCells.get(position) || EMPTY_CELL;
46940
47354
  }
46941
- getSpreadPositionsOf(position) {
47355
+ getSpreadZone(position) {
46942
47356
  if (!this.spreadingRelations.isArrayFormula(position)) {
46943
- return [];
47357
+ return undefined;
46944
47358
  }
46945
- return Array.from(this.spreadingRelations.getArrayResultPositions(position));
47359
+ if (this.evaluatedCells.get(position)?.type === CellValueType.error) {
47360
+ return positionToZone(position);
47361
+ }
47362
+ const spreadPositions = Array.from(this.spreadingRelations.getArrayResultPositions(position));
47363
+ return union(positionToZone(position), unionPositionsToZone(spreadPositions));
46946
47364
  }
46947
47365
  getEvaluatedPositions() {
46948
47366
  return this.evaluatedCells.keys();
46949
47367
  }
46950
47368
  getArrayFormulaSpreadingOn(position) {
46951
47369
  if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
46952
- return undefined;
47370
+ return this.spreadingRelations.isArrayFormula(position) ? position : undefined;
46953
47371
  }
46954
47372
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
46955
47373
  return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
@@ -47078,6 +47496,7 @@ stores.inject(MyMetaStore, storeInstance);
47078
47496
  if (!this.blockedArrayFormulas.has(position)) {
47079
47497
  this.invalidateSpreading(position);
47080
47498
  }
47499
+ this.spreadingRelations.removeNode(position);
47081
47500
  const cell = this.getters.getCell(position);
47082
47501
  if (cell === undefined) {
47083
47502
  return EMPTY_CELL;
@@ -47188,7 +47607,6 @@ stores.inject(MyMetaStore, storeInstance);
47188
47607
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
47189
47608
  this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
47190
47609
  }
47191
- this.spreadingRelations.removeNode(position);
47192
47610
  }
47193
47611
  // ----------------------------------------------------------
47194
47612
  // COMMON FUNCTIONALITY
@@ -47347,7 +47765,7 @@ stores.inject(MyMetaStore, storeInstance);
47347
47765
  "getEvaluatedCell",
47348
47766
  "getEvaluatedCells",
47349
47767
  "getEvaluatedCellsInZone",
47350
- "getSpreadPositionsOf",
47768
+ "getSpreadZone",
47351
47769
  "getArrayFormulaSpreadingOn",
47352
47770
  "isEmpty",
47353
47771
  ];
@@ -47453,8 +47871,11 @@ stores.inject(MyMetaStore, storeInstance);
47453
47871
  getEvaluatedCellsInZone(sheetId, zone) {
47454
47872
  return positions(zone).map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId, col, row }));
47455
47873
  }
47456
- getSpreadPositionsOf(position) {
47457
- return this.evaluator.getSpreadPositionsOf(position);
47874
+ /**
47875
+ * Return the spread zone the position is part of, if any
47876
+ */
47877
+ getSpreadZone(position) {
47878
+ return this.evaluator.getSpreadZone(position);
47458
47879
  }
47459
47880
  getArrayFormulaSpreadingOn(position) {
47460
47881
  return this.evaluator.getArrayFormulaSpreadingOn(position);
@@ -47494,7 +47915,7 @@ stores.inject(MyMetaStore, storeInstance);
47494
47915
  ? getItemId(newFormat, data.formats)
47495
47916
  : exportedCellData.format;
47496
47917
  let content;
47497
- if (formulaCell instanceof FormulaCellWithDependencies) {
47918
+ if (isFormula && formulaCell instanceof FormulaCellWithDependencies) {
47498
47919
  content = formulaCell.contentWithFixedReferences;
47499
47920
  }
47500
47921
  else {
@@ -47542,17 +47963,17 @@ stores.inject(MyMetaStore, storeInstance);
47542
47963
  */
47543
47964
  function sortWithClusters(colorsToSort) {
47544
47965
  const clusters = [
47545
- { leadColor: rgba(255, 0, 0), colors: [] },
47546
- { leadColor: rgba(255, 128, 0), colors: [] },
47547
- { leadColor: rgba(128, 128, 0), colors: [] },
47548
- { leadColor: rgba(128, 255, 0), colors: [] },
47549
- { leadColor: rgba(0, 255, 0), colors: [] },
47550
- { leadColor: rgba(0, 255, 128), colors: [] },
47551
- { leadColor: rgba(0, 255, 255), colors: [] },
47552
- { leadColor: rgba(0, 127, 255), colors: [] },
47553
- { leadColor: rgba(0, 0, 255), colors: [] },
47554
- { leadColor: rgba(127, 0, 255), colors: [] },
47555
- { leadColor: rgba(128, 0, 128), colors: [] },
47966
+ { leadColor: rgba(255, 0, 0), colors: [] }, // red
47967
+ { leadColor: rgba(255, 128, 0), colors: [] }, // orange
47968
+ { leadColor: rgba(128, 128, 0), colors: [] }, // yellow
47969
+ { leadColor: rgba(128, 255, 0), colors: [] }, // chartreuse
47970
+ { leadColor: rgba(0, 255, 0), colors: [] }, // green
47971
+ { leadColor: rgba(0, 255, 128), colors: [] }, // spring green
47972
+ { leadColor: rgba(0, 255, 255), colors: [] }, // cyan
47973
+ { leadColor: rgba(0, 127, 255), colors: [] }, // azure
47974
+ { leadColor: rgba(0, 0, 255), colors: [] }, // blue
47975
+ { leadColor: rgba(127, 0, 255), colors: [] }, // violet
47976
+ { leadColor: rgba(128, 0, 128), colors: [] }, // magenta
47556
47977
  { leadColor: rgba(255, 0, 128), colors: [] }, // rose
47557
47978
  ];
47558
47979
  for (const color of colorsToSort.map(colorToRGBA)) {
@@ -47943,13 +48364,13 @@ stores.inject(MyMetaStore, storeInstance);
47943
48364
  .map((cell) => cell.value);
47944
48365
  switch (threshold.type) {
47945
48366
  case "value":
47946
- const result = functionName === "max" ? Math.max(...rangeValues) : Math.min(...rangeValues);
48367
+ const result = functionName === "max" ? largeMax(rangeValues) : largeMin(rangeValues);
47947
48368
  return result;
47948
48369
  case "number":
47949
48370
  return Number(threshold.value);
47950
48371
  case "percentage":
47951
- const min = Math.min(...rangeValues);
47952
- const max = Math.max(...rangeValues);
48372
+ const min = largeMin(rangeValues);
48373
+ const max = largeMax(rangeValues);
47953
48374
  const delta = max - min;
47954
48375
  return min + (delta * Number(threshold.value)) / 100;
47955
48376
  case "percentile":
@@ -48286,6 +48707,180 @@ stores.inject(MyMetaStore, storeInstance);
48286
48707
  }
48287
48708
  }
48288
48709
 
48710
+ class DynamicTablesPlugin extends UIPlugin {
48711
+ static getters = [
48712
+ "canCreateDynamicTableOnZones",
48713
+ "doesZonesContainFilter",
48714
+ "getFilter",
48715
+ "getFilters",
48716
+ "getTable",
48717
+ "getTables",
48718
+ "getTablesOverlappingZones",
48719
+ "getFilterId",
48720
+ "getFilterHeaders",
48721
+ "isFilterHeader",
48722
+ ];
48723
+ tables = {};
48724
+ handle(cmd) {
48725
+ if (invalidateEvaluationCommands.has(cmd.type) ||
48726
+ (cmd.type === "UPDATE_CELL" && "content" in cmd) ||
48727
+ cmd.type === "EVALUATE_CELLS") {
48728
+ this.tables = {};
48729
+ return;
48730
+ }
48731
+ switch (cmd.type) {
48732
+ case "CREATE_TABLE":
48733
+ case "REMOVE_TABLE":
48734
+ case "UPDATE_TABLE":
48735
+ case "DELETE_CONTENT":
48736
+ this.tables = {};
48737
+ break;
48738
+ }
48739
+ }
48740
+ finalize() {
48741
+ for (const sheetId of this.getters.getSheetIds()) {
48742
+ if (!this.tables[sheetId]) {
48743
+ this.tables[sheetId] = this.computeTables(sheetId);
48744
+ }
48745
+ }
48746
+ }
48747
+ computeTables(sheetId) {
48748
+ const tables = [];
48749
+ const coreTables = this.getters.getCoreTables(sheetId);
48750
+ // First we create the static tables, so we can use them to compute collision with dynamic tables
48751
+ for (const table of coreTables) {
48752
+ if (table.type === "dynamic")
48753
+ continue;
48754
+ tables.push(table);
48755
+ }
48756
+ const staticTables = [...tables];
48757
+ // Then we create the dynamic tables
48758
+ for (const coreTable of coreTables) {
48759
+ if (coreTable.type !== "dynamic")
48760
+ continue;
48761
+ const table = this.coreTableToTable(sheetId, coreTable);
48762
+ let tableZone = table.range.zone;
48763
+ // Reduce the zone to avoid collision with static tables. Per design, dynamic tables can't overlap with other
48764
+ // dynamic tables, because formulas cannot spread on the same area, so we don't need to check for that.
48765
+ for (const staticTable of staticTables) {
48766
+ if (overlap(tableZone, staticTable.range.zone)) {
48767
+ tableZone = { ...tableZone, right: staticTable.range.zone.left - 1 };
48768
+ }
48769
+ }
48770
+ tables.push({ ...table, range: this.getters.getRangeFromZone(sheetId, tableZone) });
48771
+ }
48772
+ return tables;
48773
+ }
48774
+ getFilters(sheetId) {
48775
+ return this.getTables(sheetId)
48776
+ .filter((table) => table.config.hasFilters)
48777
+ .map((table) => table.filters)
48778
+ .flat();
48779
+ }
48780
+ getTables(sheetId) {
48781
+ return this.tables[sheetId] || [];
48782
+ }
48783
+ getFilter(position) {
48784
+ const table = this.getTable(position);
48785
+ if (!table || !table.config.hasFilters) {
48786
+ return undefined;
48787
+ }
48788
+ return table.filters.find((filter) => filter.col === position.col);
48789
+ }
48790
+ getFilterId(position) {
48791
+ return this.getFilter(position)?.id;
48792
+ }
48793
+ getTable({ sheetId, col, row }) {
48794
+ return this.getTables(sheetId).find((table) => isInside(col, row, table.range.zone));
48795
+ }
48796
+ getTablesOverlappingZones(sheetId, zones) {
48797
+ return this.getTables(sheetId).filter((table) => zones.some((zone) => overlap(table.range.zone, zone)));
48798
+ }
48799
+ doesZonesContainFilter(sheetId, zones) {
48800
+ return this.getTablesOverlappingZones(sheetId, zones).some((table) => table.config.hasFilters);
48801
+ }
48802
+ getFilterHeaders(sheetId) {
48803
+ const headers = [];
48804
+ for (const table of this.getTables(sheetId)) {
48805
+ if (!table.config.hasFilters) {
48806
+ continue;
48807
+ }
48808
+ const zone = table.range.zone;
48809
+ const row = zone.top;
48810
+ for (let col = zone.left; col <= zone.right; col++) {
48811
+ headers.push({ sheetId, col, row });
48812
+ }
48813
+ }
48814
+ return headers;
48815
+ }
48816
+ isFilterHeader({ sheetId, col, row }) {
48817
+ const headers = this.getFilterHeaders(sheetId);
48818
+ return headers.some((header) => header.col === col && header.row === row);
48819
+ }
48820
+ /**
48821
+ * Check if we can create a dynamic table on the given zones.
48822
+ * - The zones must be continuous
48823
+ * - The union of the zones must be either:
48824
+ * - A single cell that contains an array formula
48825
+ * - All the spread cells of a single array formula
48826
+ */
48827
+ canCreateDynamicTableOnZones(sheetId, zones) {
48828
+ if (!areZonesContinuous(zones)) {
48829
+ return false;
48830
+ }
48831
+ const unionZone = union(...zones);
48832
+ const topLeft = { col: unionZone.left, row: unionZone.top, sheetId };
48833
+ const parentSpreadingCell = this.getters.getArrayFormulaSpreadingOn(topLeft);
48834
+ if (!parentSpreadingCell) {
48835
+ return false;
48836
+ }
48837
+ else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
48838
+ return true;
48839
+ }
48840
+ const zone = this.getters.getSpreadZone(parentSpreadingCell);
48841
+ return deepEquals(unionZone, zone);
48842
+ }
48843
+ coreTableToTable(sheetId, table) {
48844
+ if (table.type !== "dynamic") {
48845
+ return table;
48846
+ }
48847
+ const tableZone = table.range.zone;
48848
+ const tablePosition = { sheetId, col: tableZone.left, row: tableZone.top };
48849
+ const zone = this.getters.getSpreadZone(tablePosition) ?? table.range.zone;
48850
+ const range = this.getters.getRangeFromZone(sheetId, zone);
48851
+ const filters = this.getDynamicTableFilters(sheetId, table, zone);
48852
+ return { id: table.id, range, filters, config: table.config };
48853
+ }
48854
+ getDynamicTableFilters(sheetId, table, tableZone) {
48855
+ const filters = [];
48856
+ const { top, bottom, left, right } = tableZone;
48857
+ for (let col = left; col <= right; col++) {
48858
+ const tableColIndex = col - left;
48859
+ const zone = { left: col, right: col, top, bottom };
48860
+ const filter = createFilter(this.getDynamicTableFilterId(table.id, tableColIndex), this.getters.getRangeFromZone(sheetId, zone), table.config, this.getters.getRangeFromZone);
48861
+ filters.push(filter);
48862
+ }
48863
+ return filters;
48864
+ }
48865
+ getDynamicTableFilterId(tableId, tableCol) {
48866
+ return tableId + "_" + tableCol;
48867
+ }
48868
+ exportForExcel(data) {
48869
+ for (const sheet of data.sheets) {
48870
+ for (const tableData of sheet.tables) {
48871
+ const zone = toZone(tableData.range);
48872
+ const topLeft = { sheetId: sheet.id, col: zone.left, row: zone.top };
48873
+ const coreTable = this.getters.getCoreTable(topLeft);
48874
+ const table = this.getTable(topLeft);
48875
+ if (coreTable?.type !== "dynamic" || !table) {
48876
+ continue;
48877
+ }
48878
+ tableData.range = zoneToXc(table.range.zone);
48879
+ }
48880
+ }
48881
+ }
48882
+ }
48883
+
48289
48884
  class HeaderSizeUIPlugin extends UIPlugin {
48290
48885
  static getters = ["getRowSize", "getHeaderSize"];
48291
48886
  tallestCellInRow = {};
@@ -48701,8 +49296,7 @@ stores.inject(MyMetaStore, storeInstance);
48701
49296
  let row = zone.bottom;
48702
49297
  if (col > 0) {
48703
49298
  let leftPosition = { sheetId, col: col - 1, row };
48704
- while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
48705
- this.getters.getCell(leftPosition)?.content) {
49299
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty) {
48706
49300
  row += 1;
48707
49301
  leftPosition = { sheetId, col: col - 1, row };
48708
49302
  }
@@ -48711,8 +49305,7 @@ stores.inject(MyMetaStore, storeInstance);
48711
49305
  col = zone.right;
48712
49306
  if (col <= this.getters.getNumberCols(sheetId)) {
48713
49307
  let rightPosition = { sheetId, col: col + 1, row };
48714
- while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
48715
- this.getters.getCell(rightPosition)?.content) {
49308
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty) {
48716
49309
  row += 1;
48717
49310
  rightPosition = { sheetId, col: col + 1, row };
48718
49311
  }
@@ -49022,13 +49615,13 @@ stores.inject(MyMetaStore, storeInstance);
49022
49615
  const cells = this.getters.getEvaluatedCellsInZone(sheet.id, zone);
49023
49616
  const cellPositions = range(end, -1, -1);
49024
49617
  const invalidCells = cellPositions.filter((position) => cells[position] && !cells[position].isAutoSummable);
49025
- const maxValidPosition = Math.max(...invalidCells);
49618
+ const maxValidPosition = largeMax(invalidCells);
49026
49619
  const numberSequences = groupConsecutive(cellPositions.filter((position) => this.isNumber(cells[position])));
49027
49620
  const firstSequence = numberSequences[0] || [];
49028
- if (Math.max(...firstSequence) < maxValidPosition) {
49621
+ if (largeMax(firstSequence) < maxValidPosition) {
49029
49622
  return Infinity;
49030
49623
  }
49031
- return Math.min(...firstSequence);
49624
+ return largeMin(firstSequence);
49032
49625
  }
49033
49626
  shouldFindData(sheetId, zone) {
49034
49627
  return this.getters.isEmpty(sheetId, zone) || this.getters.isSingleCellOrMerge(sheetId, zone);
@@ -50603,8 +51196,6 @@ stores.inject(MyMetaStore, storeInstance);
50603
51196
  static getters = [
50604
51197
  "doesCellHaveGridIcon",
50605
51198
  "getCellWidth",
50606
- "getCellComputedBorder",
50607
- "getCellComputedStyle",
50608
51199
  "getTextWidth",
50609
51200
  "getCellText",
50610
51201
  "getCellMultiLineText",
@@ -50648,7 +51239,7 @@ stores.inject(MyMetaStore, storeInstance);
50648
51239
  // Getters
50649
51240
  // ---------------------------------------------------------------------------
50650
51241
  getCellWidth(position) {
50651
- const style = this.getCellComputedStyle(position);
51242
+ const style = this.getters.getCellComputedStyle(position);
50652
51243
  let contentWidth = 0;
50653
51244
  const content = this.getters.getEvaluatedCell(position).formattedValue;
50654
51245
  if (content) {
@@ -50741,35 +51332,12 @@ stores.inject(MyMetaStore, storeInstance);
50741
51332
  */
50742
51333
  isCellEmpty(position) {
50743
51334
  const mainPosition = this.getters.getMainCellPosition(position);
50744
- return !(this.getters.getCorrespondingFormulaCell(mainPosition) ||
50745
- this.getters.getCell(mainPosition)?.content);
50746
- }
50747
- getCellComputedBorder(position) {
50748
- const cellBorder = this.getters.getCellBorder(position) || {};
50749
- const cellTableBorder = this.getters.getCellTableBorder(position) || {};
50750
- // Use removeFalsyAttributes to avoid overwriting borders with undefined values
50751
- const border = { ...cellTableBorder, ...removeFalsyAttributes(cellBorder) };
50752
- return isObjectEmptyRecursive(border) ? null : border;
50753
- }
50754
- getCellComputedStyle(position) {
50755
- const cell = this.getters.getCell(position);
50756
- const cfStyle = this.getters.getCellConditionalFormatStyle(position);
50757
- const tableStyle = this.getters.getCellTableStyle(position);
50758
- const computedStyle = {
50759
- ...removeFalsyAttributes(tableStyle),
50760
- ...removeFalsyAttributes(cell?.style),
50761
- ...removeFalsyAttributes(cfStyle),
50762
- };
50763
- const evaluatedCell = this.getters.getEvaluatedCell(position);
50764
- if (evaluatedCell.link && !computedStyle.textColor) {
50765
- computedStyle.textColor = LINK_COLOR;
50766
- }
50767
- return computedStyle;
51335
+ return this.getters.getEvaluatedCell(mainPosition).type === CellValueType.empty;
50768
51336
  }
50769
51337
  getColMaxWidth(sheetId, index) {
50770
51338
  const cellsPositions = positions(this.getters.getColsZone(sheetId, index, index));
50771
51339
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
50772
- return Math.max(0, ...sizes);
51340
+ return Math.max(0, largeMax(sizes));
50773
51341
  }
50774
51342
  /**
50775
51343
  * Check that any "sheetId" in the command matches an existing
@@ -50798,6 +51366,236 @@ stores.inject(MyMetaStore, storeInstance);
50798
51366
  }
50799
51367
  }
50800
51368
 
51369
+ class TableStylePlugin extends UIPlugin {
51370
+ static getters = ["getCellTableStyle", "getCellTableBorder"];
51371
+ tableStyles = {};
51372
+ handle(cmd) {
51373
+ if (invalidateEvaluationCommands.has(cmd.type) ||
51374
+ (cmd.type === "UPDATE_CELL" && "content" in cmd) ||
51375
+ cmd.type === "EVALUATE_CELLS") {
51376
+ this.tableStyles = {};
51377
+ return;
51378
+ }
51379
+ if (doesCommandInvalidatesTableStyle(cmd)) {
51380
+ delete this.tableStyles[cmd.sheetId];
51381
+ return;
51382
+ }
51383
+ }
51384
+ finalize() {
51385
+ for (const sheetId of this.getters.getSheetIds()) {
51386
+ if (!this.tableStyles[sheetId]) {
51387
+ this.tableStyles[sheetId] = {};
51388
+ }
51389
+ for (const table of this.getters.getTables(sheetId)) {
51390
+ if (!this.tableStyles[sheetId][table.id]) {
51391
+ this.tableStyles[sheetId][table.id] = this.computeTableStyle(sheetId, table);
51392
+ }
51393
+ }
51394
+ }
51395
+ }
51396
+ getCellTableStyle(position) {
51397
+ const table = this.getters.getTable(position);
51398
+ if (!table) {
51399
+ return undefined;
51400
+ }
51401
+ return this.tableStyles[position.sheetId][table.id]().styles[position.col]?.[position.row];
51402
+ }
51403
+ getCellTableBorder(position) {
51404
+ const table = this.getters.getTable(position);
51405
+ if (!table) {
51406
+ return undefined;
51407
+ }
51408
+ return this.tableStyles[position.sheetId][table.id]().borders[position.col]?.[position.row];
51409
+ }
51410
+ computeTableStyle(sheetId, table) {
51411
+ return lazy(() => {
51412
+ const { config, numberOfCols, numberOfRows } = this.getTableRuntimeConfig(sheetId, table);
51413
+ const relativeTableStyle = getComputedTableStyle(config, numberOfCols, numberOfRows);
51414
+ // Return the style with sheet coordinates instead of tables coordinates
51415
+ const mapping = this.getTableMapping(sheetId, table);
51416
+ const absoluteTableStyle = { borders: {}, styles: {} };
51417
+ for (let col = 0; col < numberOfCols; col++) {
51418
+ const colInSheet = mapping.colMapping[col];
51419
+ absoluteTableStyle.borders[colInSheet] = {};
51420
+ absoluteTableStyle.styles[colInSheet] = {};
51421
+ for (let row = 0; row < numberOfRows; row++) {
51422
+ const rowInSheet = mapping.rowMapping[row];
51423
+ absoluteTableStyle.borders[colInSheet][rowInSheet] = relativeTableStyle.borders[col][row];
51424
+ absoluteTableStyle.styles[colInSheet][rowInSheet] = relativeTableStyle.styles[col][row];
51425
+ }
51426
+ }
51427
+ return absoluteTableStyle;
51428
+ });
51429
+ }
51430
+ /**
51431
+ * Get the actual table config that will be used to compute the table style. It is different from
51432
+ * the config of the table because of hidden rows and columns in the sheet. For example remove the
51433
+ * hidden rows from config.numberOfHeaders.
51434
+ */
51435
+ getTableRuntimeConfig(sheetId, table) {
51436
+ const tableZone = table.range.zone;
51437
+ const config = { ...table.config };
51438
+ let numberOfCols = tableZone.right - tableZone.left + 1;
51439
+ let numberOfRows = tableZone.bottom - tableZone.top + 1;
51440
+ for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51441
+ if (!this.getters.isRowHidden(sheetId, row)) {
51442
+ continue;
51443
+ }
51444
+ numberOfRows--;
51445
+ if (row - tableZone.top < table.config.numberOfHeaders) {
51446
+ config.numberOfHeaders--;
51447
+ if (config.numberOfHeaders < 0) {
51448
+ config.numberOfHeaders = 0;
51449
+ }
51450
+ }
51451
+ if (row === tableZone.bottom) {
51452
+ config.totalRow = false;
51453
+ }
51454
+ }
51455
+ for (let col = tableZone.left; col <= tableZone.right; col++) {
51456
+ if (!this.getters.isColHidden(sheetId, col)) {
51457
+ continue;
51458
+ }
51459
+ numberOfCols--;
51460
+ if (col === tableZone.left) {
51461
+ config.firstColumn = false;
51462
+ }
51463
+ if (col === tableZone.right) {
51464
+ config.lastColumn = false;
51465
+ }
51466
+ }
51467
+ return {
51468
+ config,
51469
+ numberOfCols,
51470
+ numberOfRows,
51471
+ };
51472
+ }
51473
+ /**
51474
+ * Get a mapping: relative col/row position in the table <=> col/row in the sheet
51475
+ */
51476
+ getTableMapping(sheetId, table) {
51477
+ const colMapping = {};
51478
+ const rowMapping = {};
51479
+ let colOffset = 0;
51480
+ let rowOffset = 0;
51481
+ const tableZone = table.range.zone;
51482
+ for (let col = tableZone.left; col <= tableZone.right; col++) {
51483
+ if (this.getters.isColHidden(sheetId, col)) {
51484
+ continue;
51485
+ }
51486
+ colMapping[colOffset] = col;
51487
+ colOffset++;
51488
+ for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51489
+ if (this.getters.isRowHidden(sheetId, row)) {
51490
+ continue;
51491
+ }
51492
+ rowMapping[rowOffset] = row;
51493
+ rowOffset++;
51494
+ }
51495
+ }
51496
+ return {
51497
+ colMapping,
51498
+ rowMapping,
51499
+ };
51500
+ }
51501
+ }
51502
+ const invalidateTableStyleCommands = [
51503
+ "HIDE_COLUMNS_ROWS",
51504
+ "UNHIDE_COLUMNS_ROWS",
51505
+ "UNFOLD_HEADER_GROUP",
51506
+ "FOLD_HEADER_GROUP",
51507
+ "FOLD_ALL_HEADER_GROUPS",
51508
+ "UNFOLD_ALL_HEADER_GROUPS",
51509
+ "CREATE_TABLE",
51510
+ "UPDATE_TABLE",
51511
+ "UPDATE_FILTER",
51512
+ ];
51513
+ const invalidateTableStyleCommandsSet = new Set(invalidateTableStyleCommands);
51514
+ function doesCommandInvalidatesTableStyle(cmd) {
51515
+ return invalidateTableStyleCommandsSet.has(cmd.type);
51516
+ }
51517
+
51518
+ class CellComputedStylePlugin extends UIPlugin {
51519
+ static getters = ["getCellComputedBorder", "getCellComputedStyle"];
51520
+ styles = {};
51521
+ borders = {};
51522
+ handle(cmd) {
51523
+ if (invalidateEvaluationCommands.has(cmd.type) ||
51524
+ cmd.type === "UPDATE_CELL" ||
51525
+ cmd.type === "EVALUATE_CELLS") {
51526
+ this.styles = {};
51527
+ this.borders = {};
51528
+ return;
51529
+ }
51530
+ if (doesCommandInvalidatesTableStyle(cmd)) {
51531
+ delete this.styles[cmd.sheetId];
51532
+ delete this.borders[cmd.sheetId];
51533
+ return;
51534
+ }
51535
+ if (invalidateCFEvaluationCommands.has(cmd.type)) {
51536
+ this.styles = {};
51537
+ return;
51538
+ }
51539
+ }
51540
+ getCellComputedBorder(position) {
51541
+ const { sheetId, row, col } = position;
51542
+ if (this.borders[sheetId]?.[row]?.[col] !== undefined) {
51543
+ return this.borders[sheetId][row][col];
51544
+ }
51545
+ if (!this.borders[sheetId]) {
51546
+ this.borders[sheetId] = {};
51547
+ }
51548
+ if (!this.borders[sheetId][row]) {
51549
+ this.borders[sheetId][row] = {};
51550
+ }
51551
+ if (!this.borders[sheetId][row][col]) {
51552
+ this.borders[sheetId][row][col] = this.computeCellBorder(position);
51553
+ }
51554
+ return this.borders[sheetId][row][col];
51555
+ }
51556
+ getCellComputedStyle(position) {
51557
+ const { sheetId, row, col } = position;
51558
+ if (this.styles[sheetId]?.[row]?.[col] !== undefined) {
51559
+ return this.styles[sheetId][row][col];
51560
+ }
51561
+ if (!this.styles[sheetId]) {
51562
+ this.styles[sheetId] = {};
51563
+ }
51564
+ if (!this.styles[sheetId][row]) {
51565
+ this.styles[sheetId][row] = {};
51566
+ }
51567
+ if (!this.styles[sheetId][row][col]) {
51568
+ this.styles[sheetId][row][col] = this.computeCellStyle(position);
51569
+ }
51570
+ return this.styles[sheetId][row][col];
51571
+ }
51572
+ computeCellBorder(position) {
51573
+ const cellBorder = this.getters.getCellBorder(position) || {};
51574
+ const cellTableBorder = this.getters.getCellTableBorder(position) || {};
51575
+ // Use removeFalsyAttributes to avoid overwriting borders with undefined values
51576
+ const border = {
51577
+ ...removeFalsyAttributes(cellTableBorder),
51578
+ ...removeFalsyAttributes(cellBorder),
51579
+ };
51580
+ return isObjectEmptyRecursive(border) ? null : border;
51581
+ }
51582
+ computeCellStyle(position) {
51583
+ const cell = this.getters.getCell(position);
51584
+ const cfStyle = this.getters.getCellConditionalFormatStyle(position);
51585
+ const tableStyle = this.getters.getCellTableStyle(position);
51586
+ const computedStyle = {
51587
+ ...removeFalsyAttributes(tableStyle),
51588
+ ...removeFalsyAttributes(cell?.style),
51589
+ ...removeFalsyAttributes(cfStyle),
51590
+ };
51591
+ const evaluatedCell = this.getters.getEvaluatedCell(position);
51592
+ if (evaluatedCell.link && !computedStyle.textColor) {
51593
+ computedStyle.textColor = LINK_COLOR;
51594
+ }
51595
+ return computedStyle;
51596
+ }
51597
+ }
51598
+
50801
51599
  const genericRepeatsTransforms = [
50802
51600
  repeatSheetDependantCommand,
50803
51601
  repeatTargetDependantCommand,
@@ -51333,10 +52131,11 @@ stores.inject(MyMetaStore, storeInstance);
51333
52131
  handle(cmd) {
51334
52132
  switch (cmd.type) {
51335
52133
  case "AUTOFILL_TABLE_COLUMN":
51336
- const table = this.getters.getTable(cmd);
52134
+ const table = this.getters.getCoreTable(cmd);
51337
52135
  const cell = this.getters.getCell(cmd);
51338
- if (!table || !table.config.automaticAutofill || !cell?.isFormula)
52136
+ if (!table?.config.automaticAutofill || table.type === "dynamic" || !cell?.isFormula) {
51339
52137
  return;
52138
+ }
51340
52139
  const { col, row } = cmd;
51341
52140
  const tableContentZone = getTableContentZone(table.range.zone, table.config);
51342
52141
  if (tableContentZone && isInside(col, row, tableContentZone)) {
@@ -51374,148 +52173,6 @@ stores.inject(MyMetaStore, storeInstance);
51374
52173
  }
51375
52174
  }
51376
52175
 
51377
- class TableStylePlugin extends UIPlugin {
51378
- static getters = ["getCellTableStyle", "getCellTableBorder"];
51379
- tableStyles = {};
51380
- handle(cmd) {
51381
- if (invalidateEvaluationCommands.has(cmd.type) ||
51382
- (cmd.type === "UPDATE_CELL" && "content" in cmd) ||
51383
- cmd.type === "EVALUATE_CELLS") {
51384
- this.tableStyles = {};
51385
- return;
51386
- }
51387
- switch (cmd.type) {
51388
- case "HIDE_COLUMNS_ROWS":
51389
- case "UNHIDE_COLUMNS_ROWS":
51390
- case "UNFOLD_HEADER_GROUP":
51391
- case "FOLD_HEADER_GROUP":
51392
- case "FOLD_ALL_HEADER_GROUPS":
51393
- case "UNFOLD_ALL_HEADER_GROUPS":
51394
- case "UPDATE_TABLE":
51395
- case "UPDATE_FILTER":
51396
- delete this.tableStyles[cmd.sheetId];
51397
- break;
51398
- }
51399
- }
51400
- finalize() {
51401
- for (const sheetId of this.getters.getSheetIds()) {
51402
- if (!this.tableStyles[sheetId]) {
51403
- this.tableStyles[sheetId] = {};
51404
- }
51405
- for (const table of this.getters.getTables(sheetId)) {
51406
- if (!this.tableStyles[sheetId][table.id]) {
51407
- this.tableStyles[sheetId][table.id] = this.computeTableStyle(sheetId, table);
51408
- }
51409
- }
51410
- }
51411
- }
51412
- getCellTableStyle(position) {
51413
- const table = this.getters.getTable(position);
51414
- if (!table) {
51415
- return undefined;
51416
- }
51417
- return this.tableStyles[position.sheetId][table.id]().styles[position.col]?.[position.row];
51418
- }
51419
- getCellTableBorder(position) {
51420
- const table = this.getters.getTable(position);
51421
- if (!table) {
51422
- return undefined;
51423
- }
51424
- return this.tableStyles[position.sheetId][table.id]().borders[position.col]?.[position.row];
51425
- }
51426
- computeTableStyle(sheetId, table) {
51427
- return lazy(() => {
51428
- const { config, numberOfCols, numberOfRows } = this.getTableRuntimeConfig(sheetId, table);
51429
- const relativeTableStyle = getComputedTableStyle(config, numberOfCols, numberOfRows);
51430
- // Return the style with sheet coordinates instead of tables coordinates
51431
- const mapping = this.getTableMapping(sheetId, table);
51432
- const absoluteTableStyle = { borders: {}, styles: {} };
51433
- for (let col = 0; col < numberOfCols; col++) {
51434
- const colInSheet = mapping.colMapping[col];
51435
- absoluteTableStyle.borders[colInSheet] = {};
51436
- absoluteTableStyle.styles[colInSheet] = {};
51437
- for (let row = 0; row < numberOfRows; row++) {
51438
- const rowInSheet = mapping.rowMapping[row];
51439
- absoluteTableStyle.borders[colInSheet][rowInSheet] = relativeTableStyle.borders[col][row];
51440
- absoluteTableStyle.styles[colInSheet][rowInSheet] = relativeTableStyle.styles[col][row];
51441
- }
51442
- }
51443
- return absoluteTableStyle;
51444
- });
51445
- }
51446
- /**
51447
- * Get the actual table config that will be used to compute the table style. It is different from
51448
- * the config of the table because of hidden rows and columns in the sheet. For example remove the
51449
- * hidden rows from config.numberOfHeaders.
51450
- */
51451
- getTableRuntimeConfig(sheetId, table) {
51452
- const tableZone = table.range.zone;
51453
- const config = { ...table.config };
51454
- let numberOfCols = tableZone.right - tableZone.left + 1;
51455
- let numberOfRows = tableZone.bottom - tableZone.top + 1;
51456
- for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51457
- if (!this.getters.isRowHidden(sheetId, row)) {
51458
- continue;
51459
- }
51460
- numberOfRows--;
51461
- if (row - tableZone.top < table.config.numberOfHeaders) {
51462
- config.numberOfHeaders--;
51463
- if (config.numberOfHeaders < 0) {
51464
- config.numberOfHeaders = 0;
51465
- }
51466
- }
51467
- if (row === tableZone.bottom) {
51468
- config.totalRow = false;
51469
- }
51470
- }
51471
- for (let col = tableZone.left; col <= tableZone.right; col++) {
51472
- if (!this.getters.isColHidden(sheetId, col)) {
51473
- continue;
51474
- }
51475
- numberOfCols--;
51476
- if (col === tableZone.left) {
51477
- config.firstColumn = false;
51478
- }
51479
- if (col === tableZone.right) {
51480
- config.lastColumn = false;
51481
- }
51482
- }
51483
- return {
51484
- config,
51485
- numberOfCols,
51486
- numberOfRows,
51487
- };
51488
- }
51489
- /**
51490
- * Get a mapping: relative col/row position in the table <=> col/row in the sheet
51491
- */
51492
- getTableMapping(sheetId, table) {
51493
- const colMapping = {};
51494
- const rowMapping = {};
51495
- let colOffset = 0;
51496
- let rowOffset = 0;
51497
- const tableZone = table.range.zone;
51498
- for (let col = tableZone.left; col <= tableZone.right; col++) {
51499
- if (this.getters.isColHidden(sheetId, col)) {
51500
- continue;
51501
- }
51502
- colMapping[colOffset] = col;
51503
- colOffset++;
51504
- for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51505
- if (this.getters.isRowHidden(sheetId, row)) {
51506
- continue;
51507
- }
51508
- rowMapping[rowOffset] = row;
51509
- rowOffset++;
51510
- }
51511
- }
51512
- return {
51513
- colMapping,
51514
- rowMapping,
51515
- };
51516
- }
51517
- }
51518
-
51519
52176
  /**
51520
52177
  * Clipboard Plugin
51521
52178
  *
@@ -52065,9 +52722,6 @@ stores.inject(MyMetaStore, storeInstance);
52065
52722
  case "START":
52066
52723
  for (const sheetId of this.getters.getSheetIds()) {
52067
52724
  this.filterValues[sheetId] = {};
52068
- for (const filter of this.getters.getFilters(sheetId)) {
52069
- this.filterValues[sheetId][filter.id] = [];
52070
- }
52071
52725
  }
52072
52726
  break;
52073
52727
  case "CREATE_SHEET":
@@ -52088,16 +52742,7 @@ stores.inject(MyMetaStore, storeInstance);
52088
52742
  this.updateHiddenRows();
52089
52743
  break;
52090
52744
  case "DUPLICATE_SHEET":
52091
- const filterValues = {};
52092
- for (const newFilter of this.getters.getFilters(cmd.sheetIdTo)) {
52093
- const zone = newFilter.rangeWithHeaders.zone;
52094
- filterValues[newFilter.id] = this.getFilterHiddenValues({
52095
- sheetId: cmd.sheetId,
52096
- col: zone.left,
52097
- row: zone.top,
52098
- });
52099
- }
52100
- this.filterValues[cmd.sheetIdTo] = filterValues;
52745
+ this.filterValues[cmd.sheetIdTo] = deepCopy(this.filterValues[cmd.sheetId]);
52101
52746
  break;
52102
52747
  // If we don't handle DELETE_SHEET, on one hand we will have some residual data, on the other hand we keep the data
52103
52748
  // on DELETE_SHEET followed by undo
@@ -52237,38 +52882,6 @@ stores.inject(MyMetaStore, storeInstance);
52237
52882
  }
52238
52883
  }
52239
52884
 
52240
- const selectionStatisticFunctions = [
52241
- {
52242
- name: _t("Sum"),
52243
- types: [CellValueType.number],
52244
- compute: (values, locale) => sum([[values]], locale),
52245
- },
52246
- {
52247
- name: _t("Avg"),
52248
- types: [CellValueType.number],
52249
- compute: (values, locale) => average([[values]], locale),
52250
- },
52251
- {
52252
- name: _t("Min"),
52253
- types: [CellValueType.number],
52254
- compute: (values, locale) => min([[values]], locale),
52255
- },
52256
- {
52257
- name: _t("Max"),
52258
- types: [CellValueType.number],
52259
- compute: (values, locale) => max([[values]], locale),
52260
- },
52261
- {
52262
- name: _t("Count"),
52263
- types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
52264
- compute: (values) => countAny([[values]]),
52265
- },
52266
- {
52267
- name: _t("Count Numbers"),
52268
- types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
52269
- compute: (values, locale) => countNumbers([[values]], locale),
52270
- },
52271
- ];
52272
52885
  /**
52273
52886
  * SelectionPlugin
52274
52887
  */
@@ -52284,8 +52897,6 @@ stores.inject(MyMetaStore, storeInstance);
52284
52897
  "getSelectedZones",
52285
52898
  "getSelectedZone",
52286
52899
  "getSelectedCells",
52287
- "getStatisticFnResults",
52288
- "getAggregate",
52289
52900
  "getSelectedFigureId",
52290
52901
  "getSelection",
52291
52902
  "getActivePosition",
@@ -52320,7 +52931,10 @@ stores.inject(MyMetaStore, storeInstance);
52320
52931
  switch (cmd.type) {
52321
52932
  case "ACTIVATE_SHEET":
52322
52933
  try {
52323
- this.getters.getSheet(cmd.sheetIdTo);
52934
+ const sheet = this.getters.getSheet(cmd.sheetIdTo);
52935
+ if (!sheet.isVisible) {
52936
+ return "SheetIsHidden" /* CommandResult.SheetIsHidden */;
52937
+ }
52324
52938
  break;
52325
52939
  }
52326
52940
  catch (error) {
@@ -52474,6 +53088,7 @@ stores.inject(MyMetaStore, storeInstance);
52474
53088
  this.gridSelection.zones = this.gridSelection.zones.map((z) => this.getters.expandZone(sheetId, z));
52475
53089
  this.gridSelection.anchor.zone = this.getters.expandZone(sheetId, this.gridSelection.anchor.zone);
52476
53090
  this.setSelectionMixin(this.gridSelection.anchor, this.gridSelection.zones);
53091
+ this.selectedFigureId = null;
52477
53092
  break;
52478
53093
  }
52479
53094
  /** Any change to the selection has to be reflected in the selection processor. */
@@ -52568,52 +53183,6 @@ stores.inject(MyMetaStore, storeInstance);
52568
53183
  : this.getters.getNextVisibleCellPosition({ sheetId, col: 0, row: 0 });
52569
53184
  }
52570
53185
  }
52571
- getStatisticFnResults() {
52572
- const sheetId = this.getters.getActiveSheetId();
52573
- const cells = new Set();
52574
- for (const zone of this.gridSelection.zones) {
52575
- for (const { col, row } of positions(zone)) {
52576
- if (this.getters.isRowHidden(sheetId, row) || this.getters.isColHidden(sheetId, col)) {
52577
- continue; // Skip hidden cells
52578
- }
52579
- const evaluatedCell = this.getters.getEvaluatedCell({ sheetId, col, row });
52580
- if (evaluatedCell.type !== CellValueType.empty) {
52581
- cells.add(evaluatedCell);
52582
- }
52583
- }
52584
- }
52585
- const locale = this.getters.getLocale();
52586
- let statisticFnResults = {};
52587
- for (let fn of selectionStatisticFunctions) {
52588
- // We don't want to display statistical information when there is no interest:
52589
- // We set the statistical result to undefined if the data handled by the selection
52590
- // does not match the data handled by the function.
52591
- // Ex: if there are only texts in the selection, we prefer that the SUM result
52592
- // be displayed as undefined rather than 0.
52593
- let fnResult = undefined;
52594
- const evaluatedCells = [...cells].filter((c) => fn.types.includes(c.type));
52595
- if (evaluatedCells.length) {
52596
- fnResult = fn.compute(evaluatedCells, locale);
52597
- }
52598
- statisticFnResults[fn.name] = fnResult;
52599
- }
52600
- return statisticFnResults;
52601
- }
52602
- getAggregate() {
52603
- let aggregate = 0;
52604
- let n = 0;
52605
- const sheetId = this.getters.getActiveSheetId();
52606
- const cellPositions = this.gridSelection.zones.map(positions).flat();
52607
- for (const { col, row } of cellPositions) {
52608
- const cell = this.getters.getEvaluatedCell({ sheetId, col, row });
52609
- if (cell.type === CellValueType.number) {
52610
- n++;
52611
- aggregate += cell.value;
52612
- }
52613
- }
52614
- const locale = this.getters.getLocale();
52615
- return n < 2 ? null : formatValue(aggregate, { locale });
52616
- }
52617
53186
  isSelected(zone) {
52618
53187
  return !!this.getters.getSelectedZones().find((z) => isEqual(z, zone));
52619
53188
  }
@@ -52655,9 +53224,6 @@ stores.inject(MyMetaStore, storeInstance);
52655
53224
  // Other
52656
53225
  // ---------------------------------------------------------------------------
52657
53226
  activateSheet(sheetIdFrom, sheetIdTo) {
52658
- if (!this.getters.isSheetVisible(sheetIdTo)) {
52659
- this.dispatch("SHOW_SHEET", { sheetId: sheetIdTo });
52660
- }
52661
53227
  this.setActiveSheet(sheetIdTo);
52662
53228
  this.sheetsData[sheetIdFrom] = {
52663
53229
  gridSelection: deepCopy(this.gridSelection),
@@ -53664,7 +54230,7 @@ stores.inject(MyMetaStore, storeInstance);
53664
54230
  * column of the current viewport
53665
54231
  */
53666
54232
  getColDimensionsInViewport(sheetId, col) {
53667
- const left = Math.min(...this.getters.getSheetViewVisibleCols());
54233
+ const left = largeMin(this.getters.getSheetViewVisibleCols());
53668
54234
  const start = this.getters.getColRowOffsetInViewport("COL", left, col);
53669
54235
  const size = this.getters.getColSize(sheetId, col);
53670
54236
  const isColHidden = this.getters.isColHidden(sheetId, col);
@@ -53679,7 +54245,7 @@ stores.inject(MyMetaStore, storeInstance);
53679
54245
  * of the current viewport
53680
54246
  */
53681
54247
  getRowDimensionsInViewport(sheetId, row) {
53682
- const top = Math.min(...this.getters.getSheetViewVisibleRows());
54248
+ const top = largeMin(this.getters.getSheetViewVisibleRows());
53683
54249
  const start = this.getters.getColRowOffsetInViewport("ROW", top, row);
53684
54250
  const size = this.getters.getRowSize(sheetId, row);
53685
54251
  const isRowHidden = this.getters.isRowHidden(sheetId, row);
@@ -54031,6 +54597,7 @@ stores.inject(MyMetaStore, storeInstance);
54031
54597
  .add("evaluation_filter", FilterEvaluationPlugin)
54032
54598
  .add("header_visibility_ui", HeaderVisibilityUIPlugin)
54033
54599
  .add("table_style", TableStylePlugin)
54600
+ .add("cell_computed_style", CellComputedStylePlugin)
54034
54601
  .add("header_positions", HeaderPositionsUIPlugin)
54035
54602
  .add("viewport", SheetViewPlugin)
54036
54603
  .add("clipboard", ClipboardPlugin);
@@ -54040,8 +54607,9 @@ stores.inject(MyMetaStore, storeInstance);
54040
54607
  .add("evaluation_chart", EvaluationChartPlugin)
54041
54608
  .add("evaluation_cf", EvaluationConditionalFormatPlugin)
54042
54609
  .add("row_size", HeaderSizeUIPlugin)
54043
- .add("custom_colors", CustomColorsPlugin)
54044
- .add("data_validation_ui", EvaluationDataValidationPlugin);
54610
+ .add("data_validation_ui", EvaluationDataValidationPlugin)
54611
+ .add("dynamic_tables", DynamicTablesPlugin)
54612
+ .add("custom_colors", CustomColorsPlugin);
54045
54613
 
54046
54614
  const clickableCellRegistry = new Registry();
54047
54615
  clickableCellRegistry.add("link", {
@@ -54090,6 +54658,38 @@ stores.inject(MyMetaStore, storeInstance);
54090
54658
  }
54091
54659
  }
54092
54660
 
54661
+ class ArrayFormulaHighlight extends SpreadsheetStore {
54662
+ highlightStore = this.get(HighlightStore);
54663
+ constructor(get) {
54664
+ super(get);
54665
+ this.highlightStore.register(this);
54666
+ }
54667
+ get highlights() {
54668
+ const zone = this.getHighlightZone();
54669
+ if (!zone) {
54670
+ return [];
54671
+ }
54672
+ const sheetId = this.model.getters.getActiveSheetId();
54673
+ return [
54674
+ {
54675
+ sheetId,
54676
+ zone,
54677
+ color: "#17A2B8",
54678
+ noFill: true,
54679
+ thinLine: true,
54680
+ },
54681
+ ];
54682
+ }
54683
+ getHighlightZone() {
54684
+ const position = this.model.getters.getActivePosition();
54685
+ const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
54686
+ const spreadZone = spreader
54687
+ ? this.model.getters.getSpreadZone(spreader)
54688
+ : this.model.getters.getSpreadZone(position);
54689
+ return spreadZone;
54690
+ }
54691
+ }
54692
+
54093
54693
  const RIPPLE_KEY_FRAMES = [
54094
54694
  { transform: "scale(0)" },
54095
54695
  { transform: "scale(0.8)", offset: 0.33 },
@@ -54392,12 +54992,14 @@ stores.inject(MyMetaStore, storeInstance);
54392
54992
  this.editionState = "initializing";
54393
54993
  }
54394
54994
  stopEdition() {
54395
- if (!this.state.isEditing)
54995
+ const input = this.sheetNameRef.el;
54996
+ if (!this.state.isEditing || !input)
54396
54997
  return;
54397
54998
  this.state.isEditing = false;
54398
54999
  this.editionState = "initializing";
54399
- this.sheetNameRef.el?.blur();
55000
+ input.blur();
54400
55001
  const inputValue = this.getInputContent() || "";
55002
+ input.innerText = inputValue;
54401
55003
  interactiveRenameSheet(this.env, this.props.sheetId, inputValue, () => this.startEdition());
54402
55004
  }
54403
55005
  cancelEdition() {
@@ -54441,6 +55043,115 @@ stores.inject(MyMetaStore, storeInstance);
54441
55043
  }
54442
55044
  }
54443
55045
 
55046
+ const selectionStatisticFunctions = [
55047
+ {
55048
+ name: _t("Sum"),
55049
+ types: [CellValueType.number],
55050
+ compute: (values, locale) => sum([[values]], locale),
55051
+ },
55052
+ {
55053
+ name: _t("Avg"),
55054
+ types: [CellValueType.number],
55055
+ compute: (values, locale) => average([[values]], locale),
55056
+ },
55057
+ {
55058
+ name: _t("Min"),
55059
+ types: [CellValueType.number],
55060
+ compute: (values, locale) => min([[values]], locale),
55061
+ },
55062
+ {
55063
+ name: _t("Max"),
55064
+ types: [CellValueType.number],
55065
+ compute: (values, locale) => max([[values]], locale),
55066
+ },
55067
+ {
55068
+ name: _t("Count"),
55069
+ types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
55070
+ compute: (values) => countAny([[values]]),
55071
+ },
55072
+ {
55073
+ name: _t("Count Numbers"),
55074
+ types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
55075
+ compute: (values, locale) => countNumbers([[values]], locale),
55076
+ },
55077
+ ];
55078
+ class AggregateStatisticsStore extends SpreadsheetStore {
55079
+ statisticFnResults = this._computeStatisticFnResults();
55080
+ isDirty = false;
55081
+ constructor(get) {
55082
+ super(get);
55083
+ this.model.selection.observe(this, {
55084
+ handleEvent: this.handleEvent.bind(this),
55085
+ });
55086
+ }
55087
+ handle(cmd) {
55088
+ if (invalidateEvaluationCommands.has(cmd.type) ||
55089
+ (cmd.type === "UPDATE_CELL" && "content" in cmd)) {
55090
+ this.isDirty = true;
55091
+ }
55092
+ switch (cmd.type) {
55093
+ case "HIDE_COLUMNS_ROWS":
55094
+ case "UNHIDE_COLUMNS_ROWS":
55095
+ case "GROUP_HEADERS":
55096
+ case "UNGROUP_HEADERS":
55097
+ case "ACTIVATE_SHEET":
55098
+ case "ACTIVATE_NEXT_SHEET":
55099
+ case "ACTIVATE_PREVIOUS_SHEET":
55100
+ case "EVALUATE_CELLS":
55101
+ case "UNDO":
55102
+ case "REDO":
55103
+ this.isDirty = true;
55104
+ }
55105
+ }
55106
+ finalize() {
55107
+ if (this.isDirty) {
55108
+ this.isDirty = false;
55109
+ this.statisticFnResults = this._computeStatisticFnResults();
55110
+ }
55111
+ }
55112
+ handleEvent() {
55113
+ if (this.getters.isGridSelectionActive()) {
55114
+ this.statisticFnResults = this._computeStatisticFnResults();
55115
+ }
55116
+ }
55117
+ _computeStatisticFnResults() {
55118
+ const getters = this.getters;
55119
+ const sheetId = getters.getActiveSheetId();
55120
+ const cells = new Set();
55121
+ const zones = getters.getSelectedZones();
55122
+ for (const zone of zones) {
55123
+ for (let col = zone.left; col <= zone.right; col++) {
55124
+ for (let row = zone.top; row <= zone.bottom; row++) {
55125
+ if (getters.isRowHidden(sheetId, row) || getters.isColHidden(sheetId, col)) {
55126
+ continue; // Skip hidden cells
55127
+ }
55128
+ const evaluatedCell = getters.getEvaluatedCell({ sheetId, col, row });
55129
+ if (evaluatedCell.type !== CellValueType.empty) {
55130
+ cells.add(evaluatedCell);
55131
+ }
55132
+ }
55133
+ }
55134
+ }
55135
+ const locale = getters.getLocale();
55136
+ let statisticFnResults = {};
55137
+ const cellsArray = [...cells];
55138
+ for (let fn of selectionStatisticFunctions) {
55139
+ // We don't want to display statistical information when there is no interest:
55140
+ // We set the statistical result to undefined if the data handled by the selection
55141
+ // does not match the data handled by the function.
55142
+ // Ex: if there are only texts in the selection, we prefer that the SUM result
55143
+ // be displayed as undefined rather than 0.
55144
+ let fnResult = undefined;
55145
+ const evaluatedCells = cellsArray.filter((c) => fn.types.includes(c.type));
55146
+ if (evaluatedCells.length) {
55147
+ fnResult = fn.compute(evaluatedCells, locale);
55148
+ }
55149
+ statisticFnResults[fn.name] = fnResult;
55150
+ }
55151
+ return statisticFnResults;
55152
+ }
55153
+ }
55154
+
54444
55155
  // -----------------------------------------------------------------------------
54445
55156
  // SpreadSheet
54446
55157
  // -----------------------------------------------------------------------------
@@ -54456,40 +55167,38 @@ stores.inject(MyMetaStore, storeInstance);
54456
55167
  }
54457
55168
  `;
54458
55169
  class BottomBarStatistic extends owl.Component {
54459
- static template = "o-spreadsheet-BottomBarStatisic";
55170
+ static template = "o-spreadsheet-BottomBarStatistic";
54460
55171
  static props = {
54461
55172
  openContextMenu: Function,
54462
55173
  closeContextMenu: Function,
54463
55174
  };
54464
55175
  static components = { Ripple };
54465
55176
  selectedStatisticFn = "";
54466
- statisticFnResults = {};
55177
+ store;
54467
55178
  setup() {
54468
- this.statisticFnResults = this.env.model.getters.getStatisticFnResults();
55179
+ this.store = useStore(AggregateStatisticsStore);
54469
55180
  owl.onWillUpdateProps(() => {
54470
- const newStatisticFnResults = this.env.model.getters.getStatisticFnResults();
54471
- if (!deepEquals(newStatisticFnResults, this.statisticFnResults)) {
55181
+ if (Object.values(this.store.statisticFnResults).every((result) => result === undefined)) {
54472
55182
  this.props.closeContextMenu();
54473
55183
  }
54474
- this.statisticFnResults = newStatisticFnResults;
54475
55184
  });
54476
55185
  }
54477
55186
  getSelectedStatistic() {
54478
55187
  // don't display button if no function has a result
54479
- if (Object.values(this.statisticFnResults).every((result) => result === undefined)) {
55188
+ if (Object.values(this.store.statisticFnResults).every((result) => result === undefined)) {
54480
55189
  return undefined;
54481
55190
  }
54482
55191
  if (this.selectedStatisticFn === "") {
54483
- this.selectedStatisticFn = Object.keys(this.statisticFnResults)[0];
55192
+ this.selectedStatisticFn = Object.keys(this.store.statisticFnResults)[0];
54484
55193
  }
54485
- return this.getComposedFnName(this.selectedStatisticFn, this.statisticFnResults[this.selectedStatisticFn]);
55194
+ return this.getComposedFnName(this.selectedStatisticFn);
54486
55195
  }
54487
55196
  listSelectionStatistics(ev) {
54488
55197
  const registry = new MenuItemRegistry();
54489
55198
  let i = 0;
54490
- for (let [fnName, fnValue] of Object.entries(this.statisticFnResults)) {
55199
+ for (let [fnName] of Object.entries(this.store.statisticFnResults)) {
54491
55200
  registry.add(fnName, {
54492
- name: this.getComposedFnName(fnName, fnValue),
55201
+ name: () => this.getComposedFnName(fnName),
54493
55202
  sequence: i,
54494
55203
  isReadonlyAllowed: true,
54495
55204
  execute: () => {
@@ -54502,8 +55211,9 @@ stores.inject(MyMetaStore, storeInstance);
54502
55211
  const { top, left, width } = target.getBoundingClientRect();
54503
55212
  this.props.openContextMenu(left + width, top, registry);
54504
55213
  }
54505
- getComposedFnName(fnName, fnValue) {
55214
+ getComposedFnName(fnName) {
54506
55215
  const locale = this.env.model.getters.getLocale();
55216
+ const fnValue = this.store.statisticFnResults[fnName];
54507
55217
  return fnName + ": " + (fnValue !== undefined ? formatValue(fnValue, { locale }) : "__");
54508
55218
  }
54509
55219
  }
@@ -54612,10 +55322,14 @@ stores.inject(MyMetaStore, storeInstance);
54612
55322
  name: sheet.name,
54613
55323
  sequence: i,
54614
55324
  isReadonlyAllowed: true,
54615
- textColor: sheet.isVisible ? undefined : "grey",
55325
+ textColor: sheet.isVisible ? undefined : "#808080",
54616
55326
  execute: (env) => {
55327
+ if (!this.env.model.getters.isSheetVisible(sheetId)) {
55328
+ this.env.model.dispatch("SHOW_SHEET", { sheetId });
55329
+ }
54617
55330
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: from, sheetIdTo: sheetId });
54618
55331
  },
55332
+ isEnabled: (env) => (env.model.getters.isReadonly() ? sheet.isVisible : true),
54619
55333
  });
54620
55334
  i++;
54621
55335
  }
@@ -54688,7 +55402,7 @@ stores.inject(MyMetaStore, storeInstance);
54688
55402
  this.sheetListRef.el.scrollTo({ top: 0, left: scroll, behavior: "smooth" });
54689
55403
  }
54690
55404
  onSheetMouseDown(sheetId, event) {
54691
- if (event.button !== 0)
55405
+ if (event.button !== 0 || this.env.model.getters.isReadonly())
54692
55406
  return;
54693
55407
  this.closeMenu();
54694
55408
  const visibleSheets = this.getVisibleSheets();
@@ -54724,7 +55438,7 @@ stores.inject(MyMetaStore, storeInstance);
54724
55438
  .map((sheetEl) => sheetEl.getBoundingClientRect())
54725
55439
  .map((rect) => ({
54726
55440
  x: rect.x,
54727
- width: rect.width - 1,
55441
+ width: rect.width - 1, // -1 to compensate negative margin
54728
55442
  y: rect.y,
54729
55443
  height: rect.height,
54730
55444
  }));
@@ -54951,7 +55665,7 @@ stores.inject(MyMetaStore, storeInstance);
54951
55665
  }
54952
55666
  return cssPropertiesToCss({
54953
55667
  top: `${groupBox.headerRect.height / 2}px`,
54954
- left: `calc(50% - 1px)`,
55668
+ left: `calc(50% - 1px)`, // -1px: we want the border to be on the center
54955
55669
  width: `30%`,
54956
55670
  height: `calc(100% - ${groupBox.headerRect.height / 2}px)`,
54957
55671
  "border-left": `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
@@ -55003,7 +55717,7 @@ stores.inject(MyMetaStore, storeInstance);
55003
55717
  return "";
55004
55718
  }
55005
55719
  return cssPropertiesToCss({
55006
- top: `calc(50% - 1px)`,
55720
+ top: `calc(50% - 1px)`, // -1px: we want the border to be on the center
55007
55721
  left: `${groupBox.headerRect.width / 2}px`,
55008
55722
  width: `calc(100% - ${groupBox.headerRect.width / 2}px)`,
55009
55723
  height: `30%`,
@@ -56092,12 +56806,9 @@ stores.inject(MyMetaStore, storeInstance);
56092
56806
  .text-muted {
56093
56807
  color: grey !important;
56094
56808
  }
56095
- button {
56096
- color: #333;
56097
- }
56098
56809
  .o-disabled {
56099
56810
  opacity: 0.4;
56100
- pointer: default;
56811
+ cursor: default;
56101
56812
  pointer-events: none;
56102
56813
  }
56103
56814
 
@@ -56215,17 +56926,17 @@ stores.inject(MyMetaStore, storeInstance);
56215
56926
  }
56216
56927
 
56217
56928
  .o-button {
56218
- border: 1px solid lightgrey;
56929
+ border: 1px solid;
56219
56930
  padding: 0px 20px 0px 20px;
56220
56931
  border-radius: 4px;
56221
56932
  font-weight: 500;
56222
56933
  font-size: 14px;
56223
56934
  height: 30px;
56224
56935
  line-height: 16px;
56225
- background: white;
56226
56936
  margin-right: 8px;
56227
- &:hover:enabled {
56228
- background-color: rgba(0, 0, 0, 0.08);
56937
+
56938
+ &:not(:hover) {
56939
+ background-color: transparent;
56229
56940
  }
56230
56941
 
56231
56942
  &:enabled {
@@ -56239,6 +56950,15 @@ stores.inject(MyMetaStore, storeInstance);
56239
56950
  &:last-child {
56240
56951
  margin-right: 0px;
56241
56952
  }
56953
+
56954
+ &.o-button-grey {
56955
+ border-color: lightgrey;
56956
+ background: #ffffff;
56957
+ color: #333;
56958
+ &:hover:enabled {
56959
+ background-color: rgba(0, 0, 0, 0.08);
56960
+ }
56961
+ }
56242
56962
  }
56243
56963
 
56244
56964
  .o-input {
@@ -56254,7 +56974,7 @@ stores.inject(MyMetaStore, storeInstance);
56254
56974
 
56255
56975
  .o-number-input {
56256
56976
  /* Remove number input arrows */
56257
- -moz-appearance: textfield;
56977
+ appearance: textfield;
56258
56978
  &::-webkit-outer-spin-button,
56259
56979
  &::-webkit-inner-spin-button {
56260
56980
  -webkit-appearance: none;
@@ -56297,6 +57017,7 @@ stores.inject(MyMetaStore, storeInstance);
56297
57017
  this.notificationStore = useStore(NotificationStore);
56298
57018
  this.composerFocusStore = useStore(ComposerFocusStore);
56299
57019
  this.sidePanel = useStore(SidePanelStore);
57020
+ useStore(ArrayFormulaHighlight);
56300
57021
  this.keyDownMapping = {
56301
57022
  "CTRL+H": () => this.sidePanel.toggle("FindAndReplace", {}),
56302
57023
  "CTRL+F": () => this.sidePanel.toggle("FindAndReplace", {}),
@@ -56399,7 +57120,7 @@ stores.inject(MyMetaStore, storeInstance);
56399
57120
  const gridColSize = GROUP_LAYER_WIDTH * this.rowLayers.length;
56400
57121
  const gridRowSize = GROUP_LAYER_WIDTH * this.colLayers.length;
56401
57122
  return cssPropertiesToCss({
56402
- "grid-template-columns": `${gridColSize ? gridColSize + 2 : 0}px auto`,
57123
+ "grid-template-columns": `${gridColSize ? gridColSize + 2 : 0}px auto`, // +2: margins
56403
57124
  "grid-template-rows": `${gridRowSize ? gridRowSize + 2 : 0}px auto`,
56404
57125
  });
56405
57126
  }
@@ -57131,14 +57852,6 @@ stores.inject(MyMetaStore, storeInstance);
57131
57852
  this.revertBefore(operationId);
57132
57853
  this.tree.drop(operationId);
57133
57854
  }
57134
- getRevertedExecution() {
57135
- const data = [];
57136
- const operations = this.tree.revertedExecution(this.HEAD_BRANCH);
57137
- for (const { operation } of operations) {
57138
- data.push(operation.data);
57139
- }
57140
- return data;
57141
- }
57142
57855
  /**
57143
57856
  * Revert the state as it was *before* the given operation was executed.
57144
57857
  */
@@ -57927,6 +58640,9 @@ stores.inject(MyMetaStore, storeInstance);
57927
58640
  case "bar":
57928
58641
  plot = addBarChart(chart.data);
57929
58642
  break;
58643
+ case "combo":
58644
+ plot = addComboChart(chart.data);
58645
+ break;
57930
58646
  case "line":
57931
58647
  plot = addLineChart(chart.data);
57932
58648
  break;
@@ -58089,6 +58805,79 @@ stores.inject(MyMetaStore, storeInstance);
58089
58805
  ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
58090
58806
  `;
58091
58807
  }
58808
+ function addComboChart(chart) {
58809
+ // gapWitdh and overlap that define the space between clusters (in %) and the overlap between datasets (from -100: completely scattered to 100, completely overlapped)
58810
+ // see gapWidth : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_gapWidth_topic_ID0EFVEQB.html#topic_ID0EFVEQB
58811
+ // see overlap : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_overlap_topic_ID0ELYQQB.html#topic_ID0ELYQQB
58812
+ //
58813
+ // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
58814
+ // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
58815
+ const colors = new ChartColors();
58816
+ const dataSetsNodes = [];
58817
+ for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
58818
+ const color = toXlsxHexColor(colors.next());
58819
+ const dataShapeProperty = shapeProperty({
58820
+ backgroundColor: color,
58821
+ line: { color },
58822
+ });
58823
+ dataSetsNodes.push(dsIndex === "0"
58824
+ ? escapeXml /*xml*/ `
58825
+ <c:ser>
58826
+ <c:idx val="${dsIndex}"/>
58827
+ <c:order val="${dsIndex}"/>
58828
+ ${dataset.label ? escapeXml /*xml*/ `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
58829
+ ${dataShapeProperty}
58830
+ ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
58831
+ <c:val> <!-- x-coordinate values -->
58832
+ ${numberRef(dataset.range)}
58833
+ </c:val>
58834
+ </c:ser>
58835
+ `
58836
+ : escapeXml /*xml*/ `
58837
+ <c:ser>
58838
+ <c:idx val="${dsIndex}"/>
58839
+ <c:order val="${dsIndex}"/>
58840
+ <c:smooth val="0"/>
58841
+ <c:marker>
58842
+ <c:symbol val="circle" />
58843
+ <c:size val="5"/>
58844
+ </c:marker>
58845
+ ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
58846
+ ${dataShapeProperty}
58847
+ ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
58848
+ <c:val> <!-- x-coordinate values -->
58849
+ ${numberRef(dataset.range)}
58850
+ </c:val>
58851
+ </c:ser>
58852
+ `);
58853
+ }
58854
+ // Excel does not support this feature
58855
+ const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
58856
+ const overlap = chart.stacked ? 100 : -20;
58857
+ return escapeXml /*xml*/ `
58858
+ <c:barChart>
58859
+ <c:barDir val="col"/>
58860
+ <c:grouping val="clustered"/>
58861
+ <c:overlap val="${overlap}"/>
58862
+ <c:gapWidth val="70"/>
58863
+ <!-- each data marker in the series does not have a different color -->
58864
+ <c:varyColors val="0"/>
58865
+ ${dataSetsNodes[0]}
58866
+ <c:axId val="${catAxId}" />
58867
+ <c:axId val="${valAxId}" />
58868
+ </c:barChart>
58869
+ <c:lineChart>
58870
+ <c:grouping val="standard"/>
58871
+ <!-- each data marker in the series does not have a different color -->
58872
+ <c:varyColors val="0"/>
58873
+ ${joinXmlNodes(dataSetsNodes.slice(1))}
58874
+ <c:axId val="${catAxId}" />
58875
+ <c:axId val="${valAxId}" />
58876
+ </c:lineChart>
58877
+ ${addAx("b", "c:catAx", catAxId, valAxId, { fontColor: chart.fontColor })}
58878
+ ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
58879
+ `;
58880
+ }
58092
58881
  function addLineChart(chart) {
58093
58882
  const colors = new ChartColors();
58094
58883
  const dataSetsNodes = [];
@@ -58136,7 +58925,7 @@ stores.inject(MyMetaStore, storeInstance);
58136
58925
  }
58137
58926
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
58138
58927
  const colors = new ChartColors();
58139
- const maxLength = Math.max(...chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
58928
+ const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
58140
58929
  const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));
58141
58930
  const dataSetsNodes = [];
58142
58931
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets).reverse()) {
@@ -59014,7 +59803,7 @@ stores.inject(MyMetaStore, storeInstance);
59014
59803
  const colHeaderXc = toXC(tableZone.left + i, tableZone.top);
59015
59804
  const colName = sheetData.cells[colHeaderXc]?.content || `col${i}`;
59016
59805
  const colAttributes = [
59017
- ["id", i + 1],
59806
+ ["id", i + 1], // id cannot be 0
59018
59807
  ["name", colName],
59019
59808
  ];
59020
59809
  columns.push(escapeXml /*xml*/ `<tableColumn ${formatAttributes(colAttributes)}/>`);
@@ -59230,6 +60019,7 @@ stores.inject(MyMetaStore, storeInstance);
59230
60019
  * https://www.ecma-international.org/publications-and-standards/standards/ecma-376/
59231
60020
  */
59232
60021
  function getXLSX(data) {
60022
+ data = fixLengthySheetNames(data);
59233
60023
  const files = [];
59234
60024
  const construct = getDefaultXLSXStructure();
59235
60025
  files.push(createWorkbook(data, construct));
@@ -59477,6 +60267,40 @@ stores.inject(MyMetaStore, storeInstance);
59477
60267
  `;
59478
60268
  return createXMLFile(parseXML(xml), "_rels/.rels");
59479
60269
  }
60270
+ /**
60271
+ * Excel sheet names are maximum 31 characters while o-spreadsheet do not have this limit.
60272
+ * This method converts the sheet names to be within the 31 characters limit.
60273
+ * The cells/charts referencing this sheet will be updated accordingly.
60274
+ */
60275
+ function fixLengthySheetNames(data) {
60276
+ const nameMapping = {};
60277
+ const newNames = new Set();
60278
+ for (const sheet of data.sheets) {
60279
+ let newName = sheet.name.slice(0, 31);
60280
+ let i = 1;
60281
+ while (newNames.has(newName)) {
60282
+ newName = newName.slice(0, 31 - String(i).length) + i++;
60283
+ }
60284
+ newNames.add(newName);
60285
+ if (newName !== sheet.name) {
60286
+ nameMapping[sheet.name] = newName;
60287
+ sheet.name = newName;
60288
+ }
60289
+ }
60290
+ if (!Object.keys(nameMapping).length) {
60291
+ return data;
60292
+ }
60293
+ const sheetWithNewNames = Object.keys(nameMapping).sort((a, b) => b.length - a.length);
60294
+ let stringifiedData = JSON.stringify(data);
60295
+ for (const sheetName of sheetWithNewNames) {
60296
+ const regex = new RegExp(`'?${escapeRegExp(sheetName)}'?!`, "g");
60297
+ stringifiedData = stringifiedData.replaceAll(regex, (match) => {
60298
+ const newName = nameMapping[sheetName];
60299
+ return match.replace(sheetName, newName);
60300
+ });
60301
+ }
60302
+ return JSON.parse(stringifiedData);
60303
+ }
59480
60304
 
59481
60305
  var Status;
59482
60306
  (function (Status) {
@@ -60166,9 +60990,9 @@ stores.inject(MyMetaStore, storeInstance);
60166
60990
  exports.tokenize = tokenize;
60167
60991
 
60168
60992
 
60169
- __info__.version = "17.3.0-alpha.0";
60170
- __info__.date = "2024-03-20T13:42:32.042Z";
60171
- __info__.hash = "073e154";
60993
+ __info__.version = "17.3.0-alpha.2";
60994
+ __info__.date = "2024-04-05T14:01:07.060Z";
60995
+ __info__.hash = "8c5a229";
60172
60996
 
60173
60997
 
60174
60998
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);