@odoo/o-spreadsheet 19.1.0-alpha.7 → 19.1.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 19.1.0-alpha.7
6
- * @date 2025-10-17T11:09:35.690Z
7
- * @hash a11279d
5
+ * @version 19.1.0-alpha.8
6
+ * @date 2025-10-23T08:20:05.310Z
7
+ * @hash 78717d4
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -1154,6 +1154,29 @@
1154
1154
  removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
1155
1155
  }
1156
1156
  }
1157
+ function profilesContainsZone(profilesStartingPosition, profiles, zone) {
1158
+ const leftValue = zone.left;
1159
+ const rightValue = zone.right;
1160
+ const topValue = zone.top;
1161
+ const bottomValue = zone.bottom + 1;
1162
+ const leftIndex = binaryPredecessorSearch(profilesStartingPosition, leftValue, 0);
1163
+ const rightIndex = binaryPredecessorSearch(profilesStartingPosition, rightValue, leftIndex);
1164
+ if (leftIndex === -1 || rightIndex === -1) {
1165
+ return false;
1166
+ }
1167
+ for (let i = leftIndex; i <= rightIndex; i++) {
1168
+ const profile = profiles.get(profilesStartingPosition[i]);
1169
+ const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, true);
1170
+ const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, true);
1171
+ if (topPredIndex === -1 || topPredIndex % 2 !== 0) {
1172
+ return false;
1173
+ }
1174
+ if (topValue < profile[topPredIndex] || bottomValue > profile[bottomSuccIndex]) {
1175
+ return false;
1176
+ }
1177
+ }
1178
+ return true;
1179
+ }
1157
1180
  function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
1158
1181
  if (value === undefined) {
1159
1182
  // this is only the case when the value correspond to a bottom value that could be undefined
@@ -1238,7 +1261,18 @@
1238
1261
  }
1239
1262
  // add the top and bottom value to the profile and
1240
1263
  // remove all information between the top and bottom index
1241
- profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
1264
+ const toDelete = bottomSuccIndex - topPredIndex - 1;
1265
+ const toInsert = newPoints.length;
1266
+ const start = topPredIndex + 1;
1267
+ // fast path and slow path
1268
+ if (start === profile.length - 1 && toDelete === 1 && toInsert === 1) {
1269
+ // fast path: we just need to replace the last element
1270
+ profile[start] = newPoints[0] ?? newPoints[1];
1271
+ }
1272
+ else {
1273
+ // equivalent but slower and with memory allocation
1274
+ profile.splice(start, toDelete, ...newPoints);
1275
+ }
1242
1276
  }
1243
1277
  function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
1244
1278
  const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
@@ -1277,8 +1311,10 @@
1277
1311
  left,
1278
1312
  bottom,
1279
1313
  right,
1280
- hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
1281
1314
  };
1315
+ if ((bottom === undefined && top !== 0) || (right === undefined && left !== 0)) {
1316
+ profileZone.hasHeader = true;
1317
+ }
1282
1318
  let findCorrespondingZone = false;
1283
1319
  for (let j = pendingZones.length - 1; j >= 0; j--) {
1284
1320
  const pendingZone = pendingZones[j];
@@ -1763,17 +1799,6 @@
1763
1799
  }
1764
1800
  return [leftColumnZone, rightPartZone];
1765
1801
  }
1766
- function aggregatePositionsToZones(positions) {
1767
- const result = {};
1768
- for (const position of positions) {
1769
- result[position.sheetId] ??= [];
1770
- result[position.sheetId].push(positionToZone(position));
1771
- }
1772
- for (const sheetId in result) {
1773
- result[sheetId] = recomputeZones(result[sheetId]);
1774
- }
1775
- return result;
1776
- }
1777
1802
  /**
1778
1803
  * Array of all positions in the zone.
1779
1804
  */
@@ -14882,6 +14907,13 @@
14882
14907
  .add("minute_number", nullHandlerDecorator(minuteNumberAdapter))
14883
14908
  .add("second_number", nullHandlerDecorator(secondNumberAdapter));
14884
14909
 
14910
+ const DEFAULT_PIVOT_STYLE = {
14911
+ displayTotals: true,
14912
+ displayColumnHeaders: true,
14913
+ displayMeasuresRow: true,
14914
+ numberOfRows: Number.MAX_VALUE,
14915
+ numberOfColumns: Number.MAX_VALUE,
14916
+ };
14885
14917
  const AGGREGATOR_NAMES = {
14886
14918
  count: _t$1("Count"),
14887
14919
  count_distinct: _t$1("Count Distinct"),
@@ -15215,6 +15247,25 @@
15215
15247
  pivot: { ...definition, collapsedDomains: newDomains },
15216
15248
  });
15217
15249
  }
15250
+ function getPivotStyleFromFnArgs(definition, rowCountArg, includeTotalArg, includeColumnHeadersArg, columnCountArg, includeMeasuresRowArg, locale) {
15251
+ const style = definition.style;
15252
+ const numberOfRows = rowCountArg !== undefined
15253
+ ? toNumber(rowCountArg, locale)
15254
+ : style?.numberOfRows ?? DEFAULT_PIVOT_STYLE.numberOfRows;
15255
+ const numberOfColumns = columnCountArg !== undefined
15256
+ ? toNumber(columnCountArg, locale)
15257
+ : style?.numberOfColumns ?? DEFAULT_PIVOT_STYLE.numberOfColumns;
15258
+ const displayTotals = includeTotalArg !== undefined
15259
+ ? toBoolean(includeTotalArg)
15260
+ : style?.displayTotals ?? DEFAULT_PIVOT_STYLE.displayTotals;
15261
+ const displayColumnHeaders = includeColumnHeadersArg !== undefined
15262
+ ? toBoolean(includeColumnHeadersArg)
15263
+ : style?.displayColumnHeaders ?? DEFAULT_PIVOT_STYLE.displayColumnHeaders;
15264
+ const displayMeasuresRow = includeMeasuresRowArg !== undefined
15265
+ ? toBoolean(includeMeasuresRowArg)
15266
+ : style?.displayMeasuresRow ?? DEFAULT_PIVOT_STYLE.displayMeasuresRow;
15267
+ return { numberOfRows, numberOfColumns, displayTotals, displayColumnHeaders, displayMeasuresRow };
15268
+ }
15218
15269
 
15219
15270
  /**
15220
15271
  * Get the pivot ID from the formula pivot ID.
@@ -15829,24 +15880,18 @@
15829
15880
  arg("column_count (number, optional)", _t$1("number of columns")),
15830
15881
  arg("include_measure_titles (boolean, default=TRUE)", _t$1("Whether to include the measure titles row or not.")),
15831
15882
  ],
15832
- compute: function (pivotFormulaId, rowCount = { value: 10000 }, includeTotal = { value: true }, includeColumnHeaders = { value: true }, columnCount = { value: Number.MAX_VALUE }, includeMeasureTitles = { value: true }) {
15883
+ compute: function (pivotFormulaId, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles) {
15833
15884
  const _pivotFormulaId = toString(pivotFormulaId);
15834
- const _rowCount = toNumber(rowCount, this.locale);
15835
- if (_rowCount < 0) {
15885
+ const pivotId = getPivotId(_pivotFormulaId, this.getters);
15886
+ const pivot = this.getters.getPivot(pivotId);
15887
+ const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
15888
+ const pivotStyle = getPivotStyleFromFnArgs(coreDefinition, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles, this.locale);
15889
+ if (pivotStyle.numberOfRows < 0) {
15836
15890
  return new EvaluationError(_t$1("The number of rows must be positive."));
15837
15891
  }
15838
- const _columnCount = toNumber(columnCount, this.locale);
15839
- if (_columnCount < 0) {
15892
+ if (pivotStyle.numberOfColumns < 0) {
15840
15893
  return new EvaluationError(_t$1("The number of columns must be positive."));
15841
15894
  }
15842
- const visibilityOptions = {
15843
- displayColumnHeaders: toBoolean(includeColumnHeaders),
15844
- displayTotals: toBoolean(includeTotal),
15845
- displayMeasuresRow: toBoolean(includeMeasureTitles),
15846
- };
15847
- const pivotId = getPivotId(_pivotFormulaId, this.getters);
15848
- const pivot = this.getters.getPivot(pivotId);
15849
- const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
15850
15895
  addPivotDependencies(this, coreDefinition, coreDefinition.measures);
15851
15896
  pivot.init({ reload: pivot.needsReevaluation });
15852
15897
  const error = pivot.assertIsValid({ throwOnError: false });
@@ -15857,20 +15902,20 @@
15857
15902
  if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
15858
15903
  return new EvaluationError(getPivotTooBigErrorMessage$1(table.numberOfCells, this.locale));
15859
15904
  }
15860
- const cells = table.getPivotCells(visibilityOptions);
15905
+ const cells = table.getPivotCells(pivotStyle);
15861
15906
  let headerRows = 0;
15862
- if (visibilityOptions.displayColumnHeaders) {
15907
+ if (pivotStyle.displayColumnHeaders) {
15863
15908
  headerRows = table.columns.length - 1;
15864
15909
  }
15865
- if (visibilityOptions.displayMeasuresRow) {
15910
+ if (pivotStyle.displayMeasuresRow) {
15866
15911
  headerRows++;
15867
15912
  }
15868
15913
  const pivotTitle = this.getters.getPivotName(pivotId);
15869
- const tableHeight = Math.min(headerRows + _rowCount, cells[0].length);
15914
+ const tableHeight = Math.min(headerRows + pivotStyle.numberOfRows, cells[0].length);
15870
15915
  if (tableHeight === 0) {
15871
15916
  return [[{ value: pivotTitle }]];
15872
15917
  }
15873
- const tableWidth = Math.min(1 + _columnCount, cells.length);
15918
+ const tableWidth = Math.min(1 + pivotStyle.numberOfColumns, cells.length);
15874
15919
  const result = [];
15875
15920
  for (const col of range$1(0, tableWidth)) {
15876
15921
  result[col] = [];
@@ -15893,7 +15938,7 @@
15893
15938
  }
15894
15939
  }
15895
15940
  }
15896
- if (visibilityOptions.displayColumnHeaders || visibilityOptions.displayMeasuresRow) {
15941
+ if (pivotStyle.displayColumnHeaders || pivotStyle.displayMeasuresRow) {
15897
15942
  result[0][0] = { value: pivotTitle };
15898
15943
  }
15899
15944
  return result;
@@ -19495,6 +19540,10 @@ stores.inject(MyMetaStore, storeInstance);
19495
19540
  }
19496
19541
  return parts;
19497
19542
  }
19543
+ function positionToBoundedRange(position) {
19544
+ const zone = { left: position.col, top: position.row, right: position.col, bottom: position.row };
19545
+ return { sheetId: position.sheetId, zone };
19546
+ }
19498
19547
  /**
19499
19548
  * Check that a zone is valid regarding the order of top-bottom and left-right.
19500
19549
  * Left should be smaller than right, top should be smaller than bottom.
@@ -21361,6 +21410,9 @@ stores.inject(MyMetaStore, storeInstance);
21361
21410
  backgroundColor,
21362
21411
  yAxisID: definition.horizontal ? "y" : definition.dataSets?.[index].yAxisId || "y",
21363
21412
  xAxisID: "x",
21413
+ barPercentage: 0.9,
21414
+ categoryPercentage: dataSetsValues.length > 1 ? 0.8 : 1,
21415
+ borderRadius: 2,
21364
21416
  };
21365
21417
  dataSets.push(dataset);
21366
21418
  const trendConfig = definition.dataSets?.[index].trend;
@@ -21489,6 +21541,7 @@ stores.inject(MyMetaStore, storeInstance);
21489
21541
  const dataSets = [];
21490
21542
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
21491
21543
  const trendDatasets = [];
21544
+ const barDatasets = dataSetsValues.filter((_, i) => (definition.dataSets?.[i].type ?? "line") === "bar");
21492
21545
  for (let index = 0; index < dataSetsValues.length; index++) {
21493
21546
  let { label, data, hidden } = dataSetsValues[index];
21494
21547
  label = definition.dataSets?.[index].label || label;
@@ -21507,6 +21560,11 @@ stores.inject(MyMetaStore, storeInstance);
21507
21560
  order: type === "bar" ? dataSetsValues.length + index : index,
21508
21561
  pointRadius: definition.hideDataMarkers ? 0 : LINE_DATA_POINT_RADIUS,
21509
21562
  };
21563
+ if (dataset.type === "bar") {
21564
+ dataset.barPercentage = 0.9;
21565
+ dataset.categoryPercentage = barDatasets.length > 1 ? 0.8 : 1;
21566
+ dataset.borderRadius = 2;
21567
+ }
21510
21568
  dataSets.push(dataset);
21511
21569
  const trendConfig = definition.dataSets?.[index].trend;
21512
21570
  const trendData = args.trendDataSetsValues?.[index];
@@ -33489,6 +33547,7 @@ stores.inject(MyMetaStore, storeInstance);
33489
33547
  return data;
33490
33548
  }
33491
33549
  const figureIds = new Set();
33550
+ const chartIds = new Set();
33492
33551
  const uuidGenerator = new UuidGenerator();
33493
33552
  for (const sheet of data.sheets || []) {
33494
33553
  for (const figure of sheet.figures || []) {
@@ -33496,6 +33555,12 @@ stores.inject(MyMetaStore, storeInstance);
33496
33555
  figure.id += uuidGenerator.smallUuid();
33497
33556
  }
33498
33557
  figureIds.add(figure.id);
33558
+ if (figure.tag === "chart") {
33559
+ if (chartIds.has(figure.data?.chartId)) {
33560
+ figure.data.chartId += uuidGenerator.smallUuid();
33561
+ }
33562
+ chartIds.add(figure.data?.chartId);
33563
+ }
33499
33564
  }
33500
33565
  }
33501
33566
  data.uniqueFigureIds = true;
@@ -33863,11 +33928,6 @@ stores.inject(MyMetaStore, storeInstance);
33863
33928
  * @param sheetName couple of old and new sheet names to adapt ranges pointing to that sheet
33864
33929
  */
33865
33930
  adaptRanges(applyChange, sheetId, sheetName) { }
33866
- /**
33867
- * Implement this method to clean unused external resources, such as images
33868
- * stored on a server which have been deleted.
33869
- */
33870
- garbageCollectExternalResources() { }
33871
33931
  }
33872
33932
 
33873
33933
  class BordersPlugin extends CorePlugin {
@@ -37711,17 +37771,6 @@ stores.inject(MyMetaStore, storeInstance);
37711
37771
  break;
37712
37772
  }
37713
37773
  }
37714
- /**
37715
- * Delete unused images from the file store
37716
- */
37717
- garbageCollectExternalResources() {
37718
- const images = new Set(this.getAllImages().map((image) => image.path));
37719
- for (const path of this.syncedImages) {
37720
- if (!images.has(path)) {
37721
- this.fileStore?.delete(path);
37722
- }
37723
- }
37724
- }
37725
37774
  // ---------------------------------------------------------------------------
37726
37775
  // Getters
37727
37776
  // ---------------------------------------------------------------------------
@@ -37783,13 +37832,6 @@ stores.inject(MyMetaStore, storeInstance);
37783
37832
  sheet.images = [...sheet.images, ...images];
37784
37833
  }
37785
37834
  }
37786
- getAllImages() {
37787
- const images = [];
37788
- for (const sheetId in this.images) {
37789
- images.push(...Object.values(this.images[sheetId] || {}).filter(isDefined$1));
37790
- }
37791
- return images;
37792
- }
37793
37835
  }
37794
37836
 
37795
37837
  class MergePlugin extends CorePlugin {
@@ -38562,26 +38604,22 @@ stores.inject(MyMetaStore, storeInstance);
38562
38604
  getNumberOfDataColumns() {
38563
38605
  return this.columns.at(-1)?.length || 0;
38564
38606
  }
38565
- getSkippedRows(visibilityOptions) {
38607
+ getSkippedRows(pivotStyle) {
38566
38608
  const skippedRows = new Set();
38567
- if (!visibilityOptions.displayColumnHeaders) {
38609
+ if (!pivotStyle.displayColumnHeaders) {
38568
38610
  for (let i = 0; i < this.columns.length - 1; i++) {
38569
38611
  skippedRows.add(i);
38570
38612
  }
38571
38613
  }
38572
- if (!visibilityOptions.displayMeasuresRow) {
38614
+ if (!pivotStyle.displayMeasuresRow) {
38573
38615
  skippedRows.add(this.columns.length - 1);
38574
38616
  }
38575
38617
  return skippedRows;
38576
38618
  }
38577
- getPivotCells(visibilityOptions = {
38578
- displayColumnHeaders: true,
38579
- displayTotals: true,
38580
- displayMeasuresRow: true,
38581
- }) {
38582
- const key = JSON.stringify(visibilityOptions);
38619
+ getPivotCells(pivotStyle = DEFAULT_PIVOT_STYLE) {
38620
+ const key = JSON.stringify(pivotStyle);
38583
38621
  if (!this.pivotCells[key]) {
38584
- const { displayTotals } = visibilityOptions;
38622
+ const { displayTotals } = pivotStyle;
38585
38623
  const numberOfDataRows = this.rows.length;
38586
38624
  const numberOfDataColumns = this.getNumberOfDataColumns();
38587
38625
  let pivotHeight = this.columns.length + numberOfDataRows;
@@ -38593,7 +38631,7 @@ stores.inject(MyMetaStore, storeInstance);
38593
38631
  pivotWidth -= this.measures.length;
38594
38632
  }
38595
38633
  const domainArray = [];
38596
- const skippedRows = this.getSkippedRows(visibilityOptions);
38634
+ const skippedRows = this.getSkippedRows(pivotStyle);
38597
38635
  for (let col = 0; col < pivotWidth; col++) {
38598
38636
  domainArray.push([]);
38599
38637
  for (let row = 0; row < pivotHeight; row++) {
@@ -41685,6 +41723,281 @@ stores.inject(MyMetaStore, storeInstance);
41685
41723
  }
41686
41724
  }
41687
41725
 
41726
+ class ZoneSet {
41727
+ profilesStartingPosition = [0];
41728
+ profiles = new Map([[0, []]]);
41729
+ constructor(zones = []) {
41730
+ for (const zone of zones) {
41731
+ this.add(zone);
41732
+ }
41733
+ }
41734
+ isEmpty() {
41735
+ return this.profiles.size === 1 && this.profiles.get(0)?.length === 0;
41736
+ }
41737
+ add(zone) {
41738
+ modifyProfiles(this.profilesStartingPosition, this.profiles, [zone]);
41739
+ }
41740
+ delete(zone) {
41741
+ modifyProfiles(this.profilesStartingPosition, this.profiles, [zone], true);
41742
+ }
41743
+ has(zone) {
41744
+ return profilesContainsZone(this.profilesStartingPosition, this.profiles, zone);
41745
+ }
41746
+ difference(other) {
41747
+ const result = this.copy();
41748
+ for (const zone of other) {
41749
+ result.delete(zone);
41750
+ }
41751
+ return result;
41752
+ }
41753
+ copy() {
41754
+ const result = new ZoneSet();
41755
+ result.profilesStartingPosition = [...this.profilesStartingPosition];
41756
+ result.profiles = new Map();
41757
+ for (const [key, value] of this.profiles) {
41758
+ result.profiles.set(key, [...value]);
41759
+ }
41760
+ return result;
41761
+ }
41762
+ size() {
41763
+ let size = 0;
41764
+ for (const profile of this.profiles.values()) {
41765
+ size += profile.length;
41766
+ }
41767
+ return size / 2;
41768
+ }
41769
+ /**
41770
+ * iterator of all the zones in the ZoneSet
41771
+ */
41772
+ [Symbol.iterator]() {
41773
+ return constructZonesFromProfiles(this.profilesStartingPosition, this.profiles)[Symbol.iterator]();
41774
+ }
41775
+ }
41776
+
41777
+ class RangeSet {
41778
+ setsBySheetId = {};
41779
+ constructor(ranges = []) {
41780
+ for (const range of ranges) {
41781
+ this.add(range);
41782
+ }
41783
+ }
41784
+ add(range) {
41785
+ if (!this.setsBySheetId[range.sheetId]) {
41786
+ this.setsBySheetId[range.sheetId] = new ZoneSet();
41787
+ }
41788
+ this.setsBySheetId[range.sheetId].add(range.zone);
41789
+ }
41790
+ addMany(ranges) {
41791
+ for (const range of ranges) {
41792
+ this.add(range);
41793
+ }
41794
+ }
41795
+ addPosition(position) {
41796
+ this.add(positionToBoundedRange(position));
41797
+ }
41798
+ addManyPositions(positions) {
41799
+ for (const position of positions) {
41800
+ this.addPosition(position);
41801
+ }
41802
+ }
41803
+ has(range) {
41804
+ if (!this.setsBySheetId[range.sheetId]) {
41805
+ return false;
41806
+ }
41807
+ return this.setsBySheetId[range.sheetId].has(range.zone);
41808
+ }
41809
+ hasPosition(position) {
41810
+ return this.has(positionToBoundedRange(position));
41811
+ }
41812
+ delete(range) {
41813
+ if (!this.setsBySheetId[range.sheetId]) {
41814
+ return;
41815
+ }
41816
+ this.setsBySheetId[range.sheetId].delete(range.zone);
41817
+ }
41818
+ deleteMany(ranges) {
41819
+ for (const range of ranges) {
41820
+ this.delete(range);
41821
+ }
41822
+ }
41823
+ deleteManyPositions(positions) {
41824
+ for (const position of positions) {
41825
+ this.delete(positionToBoundedRange(position));
41826
+ }
41827
+ }
41828
+ difference(other) {
41829
+ const result = new RangeSet();
41830
+ for (const sheetId in this.setsBySheetId) {
41831
+ result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId];
41832
+ }
41833
+ for (const sheetId in other.setsBySheetId) {
41834
+ if (result.setsBySheetId[sheetId]) {
41835
+ result.setsBySheetId[sheetId] = result.setsBySheetId[sheetId].difference(other.setsBySheetId[sheetId]);
41836
+ }
41837
+ }
41838
+ return result;
41839
+ }
41840
+ copy() {
41841
+ const result = new RangeSet();
41842
+ for (const sheetId in this.setsBySheetId) {
41843
+ result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId].copy();
41844
+ }
41845
+ return result;
41846
+ }
41847
+ clear() {
41848
+ this.setsBySheetId = {};
41849
+ }
41850
+ size() {
41851
+ let size = 0;
41852
+ for (const sheetId in this.setsBySheetId) {
41853
+ size += this.setsBySheetId[sheetId].size();
41854
+ }
41855
+ return size;
41856
+ }
41857
+ isEmpty() {
41858
+ for (const sheetId in this.setsBySheetId) {
41859
+ if (!this.setsBySheetId[sheetId].isEmpty()) {
41860
+ return false;
41861
+ }
41862
+ }
41863
+ return true;
41864
+ }
41865
+ /**
41866
+ * iterator of all the ranges in the RangeSet
41867
+ */
41868
+ [Symbol.iterator]() {
41869
+ const result = [];
41870
+ for (const sheetId in this.setsBySheetId) {
41871
+ for (const zone of this.setsBySheetId[sheetId]) {
41872
+ result.push({ sheetId: sheetId, zone });
41873
+ }
41874
+ }
41875
+ return result[Symbol.iterator]();
41876
+ }
41877
+ }
41878
+
41879
+ /**
41880
+ * R-Tree of ranges, mapping zones (r-tree bounding boxes) to ranges (data of the r-tree item).
41881
+ * Ranges associated to the exact same bounding box are grouped together
41882
+ * to reduce the number of nodes in the R-tree.
41883
+ */
41884
+ class DependenciesRTree {
41885
+ rTree;
41886
+ constructor(items = []) {
41887
+ const compactedBoxes = groupSameBoundingBoxes(items);
41888
+ this.rTree = new SpreadsheetRTree(compactedBoxes);
41889
+ }
41890
+ insert(item) {
41891
+ const data = this.rTree.search(item.boundingBox);
41892
+ const itemBoundingBox = item.boundingBox;
41893
+ const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
41894
+ boundingBox.zone.left === itemBoundingBox.zone.left &&
41895
+ boundingBox.zone.top === itemBoundingBox.zone.top &&
41896
+ boundingBox.zone.right === itemBoundingBox.zone.right &&
41897
+ boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
41898
+ if (exactBoundingBox) {
41899
+ exactBoundingBox.data.add(item.data);
41900
+ }
41901
+ else {
41902
+ this.rTree.insert({ ...item, data: new RangeSet([item.data]) });
41903
+ }
41904
+ }
41905
+ search({ zone, sheetId }) {
41906
+ const results = new RangeSet();
41907
+ for (const { data } of this.rTree.search({ zone, sheetId })) {
41908
+ results.addMany(data);
41909
+ }
41910
+ return results;
41911
+ }
41912
+ remove(item) {
41913
+ const data = this.rTree.search(item.boundingBox);
41914
+ const itemBoundingBox = item.boundingBox;
41915
+ const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
41916
+ boundingBox.zone.left === itemBoundingBox.zone.left &&
41917
+ boundingBox.zone.top === itemBoundingBox.zone.top &&
41918
+ boundingBox.zone.right === itemBoundingBox.zone.right &&
41919
+ boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
41920
+ if (exactBoundingBox) {
41921
+ exactBoundingBox.data.delete(item.data);
41922
+ }
41923
+ else {
41924
+ this.rTree.remove({ ...item, data: new RangeSet([item.data]) });
41925
+ }
41926
+ }
41927
+ }
41928
+ /**
41929
+ * Group together all formulas pointing to the exact same dependency (bounding box).
41930
+ * The goal is to optimize the following case:
41931
+ * - if any cell in B1:B1000 changes, C1 must be recomputed
41932
+ * - if any cell in B1:B1000 changes, C2 must be recomputed
41933
+ * - if any cell in B1:B1000 changes, C3 must be recomputed
41934
+ * ...
41935
+ * - if any cell in B1:B1000 changes, C1000 must be recomputed
41936
+ *
41937
+ * Instead of having 1000 entries in the R-tree, we want to have a single entry
41938
+ * with B1:B1000 (bounding box) pointing to C1:C1000 (formulas).
41939
+ */
41940
+ function groupSameBoundingBoxes(items) {
41941
+ // Important: this function must be as fast as possible. It is on the evaluation hot path.
41942
+ let maxCol = 0;
41943
+ let maxRow = 0;
41944
+ for (let i = 0; i < items.length; i++) {
41945
+ const zone = items[i].boundingBox.zone;
41946
+ if (zone.right > maxCol) {
41947
+ maxCol = zone.right;
41948
+ }
41949
+ if (zone.bottom > maxRow) {
41950
+ maxRow = zone.bottom;
41951
+ }
41952
+ }
41953
+ maxCol += 1;
41954
+ maxRow += 1;
41955
+ // in most real-world cases, we can use a fast numeric key
41956
+ // but if the zones are too far right or bottom, we fallback to a slower string key
41957
+ const maxPossibleKey = (((maxRow + 1) * maxCol + 1) * maxRow + 1) * maxCol;
41958
+ const useFastKey = maxPossibleKey <= Number.MAX_SAFE_INTEGER;
41959
+ if (!useFastKey) {
41960
+ console.warn("Max col/row size exceeded, using slow zone key");
41961
+ }
41962
+ const groupedByBBox = {};
41963
+ for (const item of items) {
41964
+ const sheetId = item.boundingBox.sheetId;
41965
+ if (!groupedByBBox[sheetId]) {
41966
+ groupedByBBox[sheetId] = {};
41967
+ }
41968
+ const bBox = item.boundingBox.zone;
41969
+ let bBoxKey = 0;
41970
+ if (useFastKey) {
41971
+ bBoxKey =
41972
+ bBox.left +
41973
+ bBox.top * maxCol +
41974
+ bBox.right * maxCol * maxRow +
41975
+ bBox.bottom * maxCol * maxRow * maxCol;
41976
+ }
41977
+ else {
41978
+ bBoxKey = `${bBox.left},${bBox.top},${bBox.right},${bBox.bottom}`;
41979
+ }
41980
+ if (groupedByBBox[sheetId][bBoxKey]) {
41981
+ const ranges = groupedByBBox[sheetId][bBoxKey].data;
41982
+ ranges.add(item.data);
41983
+ }
41984
+ else {
41985
+ groupedByBBox[sheetId][bBoxKey] = {
41986
+ boundingBox: item.boundingBox,
41987
+ data: new RangeSet([item.data]),
41988
+ };
41989
+ }
41990
+ }
41991
+ const result = [];
41992
+ for (const sheetId in groupedByBBox) {
41993
+ const map = groupedByBBox[sheetId];
41994
+ for (const key in map) {
41995
+ result.push(map[key]);
41996
+ }
41997
+ }
41998
+ return result;
41999
+ }
42000
+
41688
42001
  /**
41689
42002
  * Implementation of a dependency Graph.
41690
42003
  * The graph is used to evaluate the cells in the correct
@@ -41693,12 +42006,10 @@ stores.inject(MyMetaStore, storeInstance);
41693
42006
  * It uses an R-Tree data structure to efficiently find dependent cells.
41694
42007
  */
41695
42008
  class FormulaDependencyGraph {
41696
- createEmptyPositionSet;
41697
42009
  dependencies = new PositionMap();
41698
42010
  rTree;
41699
- constructor(createEmptyPositionSet, data = []) {
41700
- this.createEmptyPositionSet = createEmptyPositionSet;
41701
- this.rTree = new SpreadsheetRTree(data);
42011
+ constructor(data = []) {
42012
+ this.rTree = new DependenciesRTree(data);
41702
42013
  }
41703
42014
  removeAllDependencies(formulaPosition) {
41704
42015
  const ranges = this.dependencies.get(formulaPosition);
@@ -41712,7 +42023,10 @@ stores.inject(MyMetaStore, storeInstance);
41712
42023
  }
41713
42024
  addDependencies(formulaPosition, dependencies) {
41714
42025
  const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
41715
- data: formulaPosition,
42026
+ data: {
42027
+ sheetId: formulaPosition.sheetId,
42028
+ zone: positionToZone(formulaPosition),
42029
+ },
41716
42030
  boundingBox: {
41717
42031
  zone,
41718
42032
  sheetId,
@@ -41730,46 +42044,20 @@ stores.inject(MyMetaStore, storeInstance);
41730
42044
  }
41731
42045
  }
41732
42046
  /**
41733
- * Return all the cells that depend on the provided ranges,
41734
- * in the correct order they should be evaluated.
41735
- * This is called a topological ordering (excluding cycles)
42047
+ * Return all the cells that depend on the provided ranges.
41736
42048
  */
41737
- getCellsDependingOn(ranges, ignore) {
41738
- const visited = this.createEmptyPositionSet();
42049
+ getCellsDependingOn(ranges, visited = new RangeSet()) {
42050
+ visited = visited.copy();
41739
42051
  const queue = Array.from(ranges).reverse();
41740
42052
  while (queue.length > 0) {
41741
42053
  const range = queue.pop();
41742
- const zone = range.zone;
41743
- const sheetId = range.sheetId;
41744
- for (let col = zone.left; col <= zone.right; col++) {
41745
- for (let row = zone.top; row <= zone.bottom; row++) {
41746
- visited.add({ sheetId, col, row });
41747
- }
41748
- }
41749
- const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
41750
- const nextInQueue = {};
41751
- for (const position of impactedPositions) {
41752
- if (!visited.has(position) && !ignore.has(position)) {
41753
- if (!nextInQueue[position.sheetId]) {
41754
- nextInQueue[position.sheetId] = [];
41755
- }
41756
- nextInQueue[position.sheetId].push(positionToZone(position));
41757
- }
41758
- }
41759
- for (const sheetId in nextInQueue) {
41760
- const zones = recomputeZones(nextInQueue[sheetId], []);
41761
- queue.push(...zones.map((zone) => ({ sheetId, zone })));
41762
- }
42054
+ visited.add(range);
42055
+ const impactedRanges = this.rTree.search(range);
42056
+ queue.push(...impactedRanges.difference(visited));
41763
42057
  }
41764
42058
  // remove initial ranges
41765
42059
  for (const range of ranges) {
41766
- const zone = range.zone;
41767
- const sheetId = range.sheetId;
41768
- for (let col = zone.left; col <= zone.right; col++) {
41769
- for (let row = zone.top; row <= zone.bottom; row++) {
41770
- visited.delete({ sheetId, col, row });
41771
- }
41772
- }
42060
+ visited.delete(range);
41773
42061
  }
41774
42062
  return visited;
41775
42063
  }
@@ -42046,7 +42334,7 @@ stores.inject(MyMetaStore, storeInstance);
42046
42334
  getters;
42047
42335
  compilationParams;
42048
42336
  evaluatedCells = new PositionMap();
42049
- formulaDependencies = lazy(new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this)));
42337
+ formulaDependencies = lazy(new FormulaDependencyGraph());
42050
42338
  blockedArrayFormulas = new PositionSet({});
42051
42339
  spreadingRelations = new SpreadingRelation();
42052
42340
  constructor(context, getters) {
@@ -42081,7 +42369,7 @@ stores.inject(MyMetaStore, storeInstance);
42081
42369
  return undefined;
42082
42370
  }
42083
42371
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
42084
- return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
42372
+ return arrayFormulas.find((position) => !this.blockedArrayFormulas.has(position));
42085
42373
  }
42086
42374
  updateDependencies(position) {
42087
42375
  // removing dependencies is slow because it requires
@@ -42125,57 +42413,72 @@ stores.inject(MyMetaStore, storeInstance);
42125
42413
  }
42126
42414
  evaluateCells(positions) {
42127
42415
  const start = performance.now();
42128
- const cellsToCompute = this.createEmptyPositionSet();
42129
- cellsToCompute.addMany(positions);
42416
+ const rangesToCompute = new RangeSet();
42417
+ rangesToCompute.addManyPositions(positions);
42130
42418
  const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
42131
- cellsToCompute.addMany(this.getCellsDependingOn(positions));
42132
- cellsToCompute.addMany(arrayFormulasPositions);
42133
- cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
42134
- this.evaluate(cellsToCompute);
42419
+ rangesToCompute.addMany(this.getCellsDependingOn(rangesToCompute));
42420
+ rangesToCompute.addMany(arrayFormulasPositions);
42421
+ rangesToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
42422
+ this.evaluate(rangesToCompute);
42135
42423
  console.debug("evaluate Cells", performance.now() - start, "ms");
42136
42424
  }
42137
42425
  getArrayFormulasImpactedByChangesOf(positions) {
42138
- const impactedPositions = this.createEmptyPositionSet();
42426
+ const impactedRanges = new RangeSet();
42139
42427
  for (const position of positions) {
42140
42428
  const content = this.getters.getCell(position)?.content;
42141
42429
  const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
42142
42430
  if (arrayFormulaPosition !== undefined) {
42143
42431
  // take into account new collisions.
42144
- impactedPositions.add(arrayFormulaPosition);
42432
+ impactedRanges.addPosition(arrayFormulaPosition);
42145
42433
  }
42146
42434
  if (!content) {
42147
42435
  // The previous content could have blocked some array formulas
42148
- impactedPositions.add(position);
42436
+ impactedRanges.addPosition(position);
42149
42437
  }
42150
42438
  }
42151
- const zonesBySheetIds = aggregatePositionsToZones(impactedPositions);
42152
- for (const sheetId in zonesBySheetIds) {
42153
- for (const zone of zonesBySheetIds[sheetId]) {
42154
- impactedPositions.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
42155
- }
42439
+ for (const range of [...impactedRanges]) {
42440
+ impactedRanges.addMany(this.getArrayFormulasBlockedBy(range.sheetId, range.zone));
42156
42441
  }
42157
- return impactedPositions;
42442
+ return impactedRanges;
42158
42443
  }
42159
42444
  buildDependencyGraph() {
42160
42445
  this.blockedArrayFormulas = this.createEmptyPositionSet();
42161
42446
  this.spreadingRelations = new SpreadingRelation();
42162
42447
  this.formulaDependencies = lazy(() => {
42163
- const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position)
42164
- .filter((range) => !range.invalidSheetName && !range.invalidXc)
42165
- .map((range) => ({
42166
- data: position,
42167
- boundingBox: {
42168
- zone: range.zone,
42169
- sheetId: range.sheetId,
42170
- },
42171
- })));
42172
- return new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this), dependencies);
42448
+ const rTreeItems = [];
42449
+ for (const sheetId of this.getters.getSheetIds()) {
42450
+ const cells = this.getters.getCells(sheetId);
42451
+ for (const cellId in cells) {
42452
+ const cell = cells[cellId];
42453
+ if (cell.isFormula) {
42454
+ const directDependencies = cell.compiledFormula.dependencies;
42455
+ for (const range of directDependencies) {
42456
+ if (range.invalidSheetName || range.invalidXc) {
42457
+ continue;
42458
+ }
42459
+ rTreeItems.push({
42460
+ data: {
42461
+ sheetId,
42462
+ zone: positionToZone(this.getters.getCellPosition(cellId)),
42463
+ },
42464
+ boundingBox: { sheetId: range.sheetId, zone: range.zone },
42465
+ });
42466
+ }
42467
+ }
42468
+ }
42469
+ }
42470
+ return new FormulaDependencyGraph(rTreeItems);
42173
42471
  });
42174
42472
  }
42175
42473
  evaluateAllCells() {
42176
42474
  const start = performance.now();
42177
42475
  this.evaluatedCells = new PositionMap();
42178
- this.evaluate(this.getAllCells());
42476
+ const ranges = [];
42477
+ for (const sheetId of this.getters.getSheetIds()) {
42478
+ const zone = this.getters.getSheetZone(sheetId);
42479
+ ranges.push({ sheetId, zone });
42480
+ }
42481
+ this.evaluate(ranges);
42179
42482
  console.debug("evaluate all cells", performance.now() - start, "ms");
42180
42483
  }
42181
42484
  evaluateFormulaResult(sheetId, formulaString) {
@@ -42199,48 +42502,47 @@ stores.inject(MyMetaStore, storeInstance);
42199
42502
  return handleError(error, "");
42200
42503
  }
42201
42504
  }
42202
- getAllCells() {
42203
- const positions = this.createEmptyPositionSet();
42204
- positions.fillAllPositions();
42205
- return positions;
42206
- }
42207
42505
  /**
42208
42506
  * Return the position of formulas blocked by the given positions
42209
42507
  * as well as all their dependencies.
42210
42508
  */
42211
42509
  getArrayFormulasBlockedBy(sheetId, zone) {
42212
- const arrayFormulaPositions = this.createEmptyPositionSet();
42510
+ const arrayFormulaPositions = new RangeSet();
42213
42511
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(sheetId, zone);
42214
- arrayFormulaPositions.addMany(arrayFormulas);
42512
+ arrayFormulaPositions.addManyPositions(arrayFormulas);
42215
42513
  const spilledPositions = [...arrayFormulas].filter((position) => !this.blockedArrayFormulas.has(position));
42216
42514
  if (spilledPositions.length) {
42217
42515
  // ignore the formula spreading on the position. Keep only the blocked ones
42218
- arrayFormulaPositions.deleteMany(spilledPositions);
42516
+ arrayFormulaPositions.deleteManyPositions(spilledPositions);
42219
42517
  }
42220
42518
  arrayFormulaPositions.addMany(this.getCellsDependingOn(arrayFormulaPositions));
42221
42519
  return arrayFormulaPositions;
42222
42520
  }
42223
- nextPositionsToUpdate = new PositionSet({});
42521
+ nextRangesToUpdate = new RangeSet();
42224
42522
  cellsBeingComputed = new Set();
42225
42523
  symbolsBeingComputed = new Set();
42226
- evaluate(positions) {
42524
+ evaluate(ranges) {
42227
42525
  this.cellsBeingComputed = new Set();
42228
- this.nextPositionsToUpdate = positions;
42526
+ this.nextRangesToUpdate = new RangeSet(ranges);
42229
42527
  let currentIteration = 0;
42230
- while (!this.nextPositionsToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
42528
+ while (!this.nextRangesToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
42231
42529
  this.updateCompilationParameters();
42232
- const positions = this.nextPositionsToUpdate.clear();
42233
- for (let i = 0; i < positions.length; ++i) {
42234
- this.evaluatedCells.delete(positions[i]);
42235
- }
42236
- for (let i = 0; i < positions.length; ++i) {
42237
- const position = positions[i];
42238
- if (this.nextPositionsToUpdate.has(position)) {
42239
- continue;
42240
- }
42241
- const evaluatedCell = this.computeCell(position);
42242
- if (evaluatedCell !== EMPTY_CELL) {
42243
- this.evaluatedCells.set(position, evaluatedCell);
42530
+ const ranges = [...this.nextRangesToUpdate];
42531
+ this.nextRangesToUpdate.clear();
42532
+ this.clearEvaluatedRanges(ranges);
42533
+ for (const range of ranges) {
42534
+ const { left, bottom, right, top } = range.zone;
42535
+ for (let col = left; col <= right; col++) {
42536
+ for (let row = top; row <= bottom; row++) {
42537
+ const position = { sheetId: range.sheetId, col, row };
42538
+ if (this.nextRangesToUpdate.hasPosition(position)) {
42539
+ continue;
42540
+ }
42541
+ const evaluatedCell = this.computeCell(position);
42542
+ if (evaluatedCell !== EMPTY_CELL) {
42543
+ this.evaluatedCells.set(position, evaluatedCell);
42544
+ }
42545
+ }
42244
42546
  }
42245
42547
  }
42246
42548
  onIterationEndEvaluationRegistry.getAll().forEach((callback) => callback(this.getters));
@@ -42249,6 +42551,16 @@ stores.inject(MyMetaStore, storeInstance);
42249
42551
  console.warn("Maximum iteration reached while evaluating cells");
42250
42552
  }
42251
42553
  }
42554
+ clearEvaluatedRanges(ranges) {
42555
+ for (const range of ranges) {
42556
+ const { left, bottom, right, top } = range.zone;
42557
+ for (let col = left; col <= right; col++) {
42558
+ for (let row = top; row <= bottom; row++) {
42559
+ this.evaluatedCells.delete({ sheetId: range.sheetId, col, row });
42560
+ }
42561
+ }
42562
+ }
42563
+ }
42252
42564
  computeCell(position) {
42253
42565
  const evaluation = this.evaluatedCells.get(position);
42254
42566
  if (evaluation) {
@@ -42321,9 +42633,9 @@ stores.inject(MyMetaStore, storeInstance);
42321
42633
  }
42322
42634
  invalidatePositionsDependingOnSpread(sheetId, resultZone) {
42323
42635
  // the result matrix is split in 2 zones to exclude the array formula position
42324
- const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })), this.nextPositionsToUpdate);
42325
- invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
42326
- this.nextPositionsToUpdate.addMany(invalidatedPositions);
42636
+ const invalidatedPositions = this.getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
42637
+ invalidatedPositions.delete({ sheetId, zone: resultZone });
42638
+ this.nextRangesToUpdate.addMany(invalidatedPositions);
42327
42639
  }
42328
42640
  assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
42329
42641
  const numberOfCols = this.getters.getNumberCols(sheetId);
@@ -42398,7 +42710,7 @@ stores.inject(MyMetaStore, storeInstance);
42398
42710
  }
42399
42711
  const sheetId = position.sheetId;
42400
42712
  this.invalidatePositionsDependingOnSpread(sheetId, zone);
42401
- this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
42713
+ this.nextRangesToUpdate.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
42402
42714
  }
42403
42715
  /**
42404
42716
  * Wraps a GetSymbolValue function to add cycle detection
@@ -42433,13 +42745,8 @@ stores.inject(MyMetaStore, storeInstance);
42433
42745
  }
42434
42746
  return cell.compiledFormula.dependencies;
42435
42747
  }
42436
- getCellsDependingOn(positions) {
42437
- const ranges = [];
42438
- const zonesBySheetIds = aggregatePositionsToZones(positions);
42439
- for (const sheetId in zonesBySheetIds) {
42440
- ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
42441
- }
42442
- return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
42748
+ getCellsDependingOn(ranges) {
42749
+ return this.formulaDependencies().getCellsDependingOn(ranges, this.nextRangesToUpdate);
42443
42750
  }
42444
42751
  }
42445
42752
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -46106,18 +46413,8 @@ stores.inject(MyMetaStore, storeInstance);
46106
46413
  return EMPTY_PIVOT_CELL;
46107
46414
  }
46108
46415
  if (functionName === "PIVOT") {
46109
- const includeTotal = toScalar(args[2]);
46110
- const shouldIncludeTotal = includeTotal === undefined ? true : toBoolean(includeTotal);
46111
- const includeColumnHeaders = toScalar(args[3]);
46112
- const includeMeasures = toScalar(args[5]);
46113
- const shouldIncludeMeasures = includeMeasures === undefined ? true : toBoolean(includeMeasures);
46114
- const shouldIncludeColumnHeaders = includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
46115
- const visibilityOptions = {
46116
- displayColumnHeaders: shouldIncludeColumnHeaders,
46117
- displayTotals: shouldIncludeTotal,
46118
- displayMeasuresRow: shouldIncludeMeasures,
46119
- };
46120
- const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(visibilityOptions);
46416
+ const pivotStyle = getPivotStyleFromFnArgs(this.getters.getPivotCoreDefinition(pivotId), toScalar(args[1]), toScalar(args[2]), toScalar(args[3]), toScalar(args[4]), toScalar(args[5]), this.getters.getLocale());
46417
+ const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(pivotStyle);
46121
46418
  const pivotCol = position.col - mainPosition.col;
46122
46419
  const pivotRow = position.row - mainPosition.row;
46123
46420
  return pivotCells[pivotCol][pivotRow];
@@ -55832,7 +56129,6 @@ stores.inject(MyMetaStore, storeInstance);
55832
56129
  const startSnapshot = performance.now();
55833
56130
  console.debug("Snapshot requested");
55834
56131
  this.session.snapshot(this.exportData());
55835
- this.garbageCollectExternalResources();
55836
56132
  console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
55837
56133
  }
55838
56134
  console.debug("Model created in", performance.now() - start, "ms");
@@ -56218,11 +56514,6 @@ stores.inject(MyMetaStore, storeInstance);
56218
56514
  data = deepCopy$1(data);
56219
56515
  return getXLSX(data);
56220
56516
  }
56221
- garbageCollectExternalResources() {
56222
- for (const plugin of this.corePlugins) {
56223
- plugin.garbageCollectExternalResources();
56224
- }
56225
- }
56226
56517
  }
56227
56518
  function createCommand(type, payload = {}) {
56228
56519
  const command = deepCopy$1(payload);
@@ -65613,12 +65904,23 @@ stores.inject(MyMetaStore, storeInstance);
65613
65904
  .add("LinkEditor", LinkEditorPopoverBuilder)
65614
65905
  .add("FilterMenu", FilterMenuPopoverBuilder);
65615
65906
 
65616
- const CHART_LIMITS = {
65617
- MAX_PIE_CATEGORIES: 7,
65618
- MAX_PIE_CATEGORIES_NO_TITLE: 6,
65619
- MIN_RADAR_CATEGORIES: 3,
65620
- MAX_RADAR_CATEGORIES: 12,
65621
- PERCENTAGE_THRESHOLD: 100,
65907
+ const DEFAULT_BAR_CHART_CONFIG = {
65908
+ type: "bar",
65909
+ title: {},
65910
+ dataSets: [],
65911
+ legendPosition: "none",
65912
+ dataSetsHaveTitle: false,
65913
+ stacked: false,
65914
+ };
65915
+ const DEFAULT_LINE_CHART_CONFIG = {
65916
+ type: "line",
65917
+ title: {},
65918
+ dataSets: [],
65919
+ legendPosition: "none",
65920
+ dataSetsHaveTitle: false,
65921
+ stacked: false,
65922
+ cumulative: false,
65923
+ labelsAsText: false,
65622
65924
  };
65623
65925
  function getUnboundRange(getters, zone) {
65624
65926
  return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
@@ -65657,43 +65959,19 @@ stores.inject(MyMetaStore, storeInstance);
65657
65959
  return detectedType;
65658
65960
  }
65659
65961
  function categorizeColumns(zones, getters) {
65660
- const columns = {
65661
- number: [],
65662
- text: [],
65663
- date: [],
65664
- };
65962
+ const columns = [];
65665
65963
  for (const zone of getZonesByColumns(zones)) {
65666
65964
  const cells = getters.getEvaluatedCellsInZone(getters.getActiveSheetId(), zone);
65667
- const type = detectColumnType(cells);
65668
- if (type !== "empty") {
65669
- const targetType = type === "percentage" ? "number" : type;
65670
- columns[targetType].push({ zone, type });
65671
- }
65965
+ columns.push({ zone, type: detectColumnType(cells) });
65672
65966
  }
65673
65967
  return columns;
65674
65968
  }
65675
65969
  function getCellStats(getters, zone) {
65676
65970
  const cells = getters.getEvaluatedCellsInZone(getters.getActiveSheetId(), zone);
65677
- const uniqueValues = new Set();
65678
- let totalCount = 0;
65679
- let percentageSum = 0;
65680
- for (let i = 0; i < cells.length; i++) {
65681
- const { value } = cells[i];
65682
- const str = value?.toString().trim();
65683
- if (!str) {
65684
- continue;
65685
- }
65686
- uniqueValues.add(str);
65687
- totalCount++;
65688
- const num = Number(value);
65689
- if (!isNaN(num)) {
65690
- percentageSum += Math.abs(num) * 100;
65691
- }
65692
- }
65971
+ const values = cells.map((c) => c.value?.toString().trim() || "").filter((s) => s);
65693
65972
  return {
65694
- uniqueCount: uniqueValues.size,
65695
- totalCount,
65696
- percentageSum,
65973
+ uniqueCount: new Set(values).size,
65974
+ totalCount: values.length,
65697
65975
  };
65698
65976
  }
65699
65977
  function isDatasetTitled(getters, column) {
@@ -65704,167 +65982,191 @@ stores.inject(MyMetaStore, storeInstance);
65704
65982
  });
65705
65983
  return ![CellValueType.number, CellValueType.empty].includes(titleCell.type);
65706
65984
  }
65707
- function createBaseChart(type, dataSets, options = {}) {
65708
- return {
65709
- type,
65710
- title: {},
65711
- dataSets,
65712
- legendPosition: "none",
65713
- ...options,
65714
- };
65715
- }
65985
+ /**
65986
+ * Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
65987
+ * - If the column contains a single cell, create a scorecard.
65988
+ * - If the column type is "percentage", create a pie chart.
65989
+ * - If the column type is "text", create a pie chart
65990
+ * - If the column type is "date", create a line chart.
65991
+ * - Otherwise, create a bar chart.
65992
+ */
65716
65993
  function buildSingleColumnChart(column, getters) {
65717
65994
  const { type, zone } = column;
65718
65995
  const sheetId = getters.getActiveSheetId();
65719
65996
  const dataSetsHaveTitle = isDatasetTitled(getters, column);
65720
65997
  const dataRange = getUnboundRange(getters, zone);
65721
65998
  const titleCell = getters.getEvaluatedCell({ sheetId, col: zone.left, row: zone.top });
65999
+ if (getZoneArea(zone) === 1) {
66000
+ return buildScorecard(zone, getters);
66001
+ }
65722
66002
  switch (type) {
65723
66003
  case "percentage":
65724
- const { percentageSum } = getCellStats(getters, zone);
65725
- return createBaseChart("pie", [{ dataRange }], {
66004
+ return {
66005
+ type: "pie",
65726
66006
  title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
66007
+ dataSets: [{ dataRange }],
66008
+ legendPosition: "none",
65727
66009
  dataSetsHaveTitle,
65728
- isDoughnut: percentageSum < CHART_LIMITS.PERCENTAGE_THRESHOLD,
65729
- });
66010
+ };
65730
66011
  case "text":
65731
66012
  const cells = getters.getEvaluatedCellsInZone(sheetId, zone);
65732
66013
  const titleCount = cells.reduce((count, cell) => (cell.value === titleCell.value ? count + 1 : count), 0);
65733
66014
  const hasUniqueTitle = titleCell.value !== null && titleCount === 1;
65734
- return createBaseChart("pie", [{ dataRange }], {
66015
+ return {
66016
+ type: "pie",
65735
66017
  title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
66018
+ dataSets: [{ dataRange }],
65736
66019
  labelRange: dataRange,
65737
66020
  dataSetsHaveTitle: hasUniqueTitle,
65738
- isDoughnut: false,
65739
66021
  aggregated: true,
65740
66022
  legendPosition: "top",
65741
- });
65742
- // TODO: Handle date column with matrix chart when matrix chart is supported
66023
+ };
65743
66024
  case "date":
65744
- return createBaseChart("line", [{ dataRange }], {
65745
- labelRange: dataRange,
66025
+ return {
66026
+ ...DEFAULT_LINE_CHART_CONFIG,
66027
+ type: "line",
66028
+ title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
66029
+ dataSets: [{ dataRange }],
65746
66030
  dataSetsHaveTitle,
65747
- cumulative: false,
65748
- labelsAsText: false,
65749
- });
66031
+ };
65750
66032
  }
65751
- return createBaseChart("bar", [{ dataRange }], { dataSetsHaveTitle });
66033
+ return {
66034
+ ...DEFAULT_BAR_CHART_CONFIG,
66035
+ title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
66036
+ dataSets: [{ dataRange }],
66037
+ dataSetsHaveTitle,
66038
+ };
65752
66039
  }
66040
+ /**
66041
+ * Builds a chart definition for a selection of two columns. The logic to detect the chart type always consider the
66042
+ * columns left to right, and is as follows:
66043
+ * - any type + percentage columns: pie chart
66044
+ * - number + number columns: scatter chart
66045
+ * - date + number columns: line chart
66046
+ * - text + number columns: treemap if repetition in labels
66047
+ * - any other combination: bar chart
66048
+ */
65753
66049
  function buildTwoColumnChart(columns, getters) {
65754
- const { number: numberColumns, text: textColumns, date: dateColumns } = columns;
65755
- if (numberColumns.length === 2) {
65756
- return createBaseChart("scatter", [{ dataRange: getUnboundRange(getters, numberColumns[1].zone) }], {
65757
- labelRange: getUnboundRange(getters, numberColumns[0].zone),
65758
- dataSetsHaveTitle: isDatasetTitled(getters, numberColumns[1]),
65759
- labelsAsText: false,
65760
- });
66050
+ if (columns.length !== 2) {
66051
+ throw new Error("buildTwoColumnChart expects exactly two columns");
65761
66052
  }
65762
- // TODO: Handle date + number with matrix chart when matrix chart is supported
65763
- if (dateColumns.length === 1 && numberColumns.length === 1) {
65764
- return createBaseChart("line", [{ dataRange: getUnboundRange(getters, numberColumns[0].zone) }], {
65765
- labelRange: getUnboundRange(getters, dateColumns[0].zone),
65766
- dataSetsHaveTitle: isDatasetTitled(getters, numberColumns[0]),
65767
- aggregated: false,
65768
- cumulative: false,
66053
+ if (columns[1].type === "percentage") {
66054
+ return {
66055
+ type: "pie",
66056
+ title: {},
66057
+ dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
66058
+ labelRange: getUnboundRange(getters, columns[0].zone),
66059
+ dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
66060
+ aggregated: true,
66061
+ legendPosition: "none",
66062
+ };
66063
+ }
66064
+ if (columns[0].type === "number" && columns[1].type === "number") {
66065
+ return {
66066
+ type: "scatter",
66067
+ title: {},
66068
+ dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
66069
+ labelRange: getUnboundRange(getters, columns[0].zone),
66070
+ dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
65769
66071
  labelsAsText: false,
65770
- });
66072
+ legendPosition: "none",
66073
+ };
66074
+ }
66075
+ // TODO: Handle date + number with calendar chart when implemented (and change the docstring)
66076
+ if (columns[0].type === "date" && columns[1].type === "number") {
66077
+ return {
66078
+ ...DEFAULT_LINE_CHART_CONFIG,
66079
+ type: "line",
66080
+ dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
66081
+ labelRange: getUnboundRange(getters, columns[0].zone),
66082
+ dataSetsHaveTitle: isDatasetTitled(getters, columns[0]),
66083
+ };
65771
66084
  }
65772
- if (textColumns.length === 1 && numberColumns.length === 1) {
65773
- const [textColumn] = textColumns;
65774
- const [numberColumn] = numberColumns;
66085
+ if (columns[0].type === "text" && columns[1].type === "number") {
66086
+ const textColumn = columns[0];
66087
+ const numberColumn = columns[1];
65775
66088
  const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
65776
66089
  const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
65777
- const maxCategories = dataSetsHaveTitle
65778
- ? CHART_LIMITS.MAX_PIE_CATEGORIES
65779
- : CHART_LIMITS.MAX_PIE_CATEGORIES_NO_TITLE;
65780
- const labelRange = getUnboundRange(getters, textColumn.zone);
65781
- const dataRange = getUnboundRange(getters, numberColumn.zone);
65782
- if (uniqueCount <= maxCategories) {
65783
- const { percentageSum } = getCellStats(getters, numberColumn.zone);
65784
- return createBaseChart("pie", [{ dataRange }], {
65785
- labelRange,
65786
- dataSetsHaveTitle,
65787
- isDoughnut: numberColumn.type === "percentage" && percentageSum < CHART_LIMITS.PERCENTAGE_THRESHOLD,
65788
- aggregated: true,
65789
- legendPosition: "top",
65790
- });
65791
- }
65792
- // Use treemap when categories repeat, as pie chart would be cluttered
65793
66090
  if (uniqueCount !== totalCount) {
65794
- return createBaseChart("treemap", [{ dataRange: labelRange }], {
65795
- labelRange: dataRange,
66091
+ return {
66092
+ type: "treemap",
66093
+ title: {},
66094
+ dataSets: [{ dataRange: getUnboundRange(getters, textColumn.zone) }],
66095
+ labelRange: getUnboundRange(getters, numberColumn.zone),
65796
66096
  dataSetsHaveTitle,
65797
- });
66097
+ legendPosition: "none",
66098
+ };
65798
66099
  }
65799
- return createBaseChart("bar", [{ dataRange }], {
65800
- labelRange,
65801
- dataSetsHaveTitle,
65802
- });
65803
66100
  }
65804
- const labelColumn = textColumns[0] || dateColumns[0] || numberColumns[0];
65805
- const dataColumn = numberColumns[0] || textColumns[0] || dateColumns[0];
65806
- return createBaseChart("line", [{ dataRange: getUnboundRange(getters, dataColumn.zone) }], {
65807
- labelRange: getUnboundRange(getters, labelColumn.zone),
65808
- dataSetsHaveTitle: isDatasetTitled(getters, dataColumn),
65809
- cumulative: false,
65810
- labelsAsText: true,
65811
- });
66101
+ return {
66102
+ ...DEFAULT_BAR_CHART_CONFIG,
66103
+ dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
66104
+ labelRange: getUnboundRange(getters, columns[0].zone),
66105
+ dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
66106
+ };
65812
66107
  }
66108
+ /**
66109
+ * Builds a chart definition for a selection more than two columns. The logic to detect the chart type always consider
66110
+ * the columns left to right, and is as follows:
66111
+ * - multiple text + single number/percentage columns: sunburst if 3+ text columns, treemap otherwise
66112
+ * - any type + multiple percentage columns: pie chart
66113
+ * - date + multiple number columns: line chart
66114
+ * - any other combination: bar chart
66115
+ */
65813
66116
  function buildMultiColumnChart(columns, getters) {
65814
- const { number: numberColumns, text: textColumns, date: dateColumns } = columns;
65815
- const dataSetsHaveTitle = numberColumns.some((col) => isDatasetTitled(getters, col));
65816
- if (textColumns.length >= 2 && numberColumns.length === 1) {
65817
- const sortedTextColumns = textColumns.sort((colA, colB) => getCellStats(getters, colA.zone).uniqueCount - getCellStats(getters, colB.zone).uniqueCount);
65818
- const dataSets = sortedTextColumns.map(({ zone }) => ({
66117
+ if (columns.length < 3) {
66118
+ throw new Error("buildMultiColumnChart expects at least three columns");
66119
+ }
66120
+ const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
66121
+ const lastColumn = columns[columns.length - 1];
66122
+ const columnsExceptLast = columns.slice(0, columns.length - 1);
66123
+ if ((lastColumn.type === "percentage" || lastColumn.type === "number") &&
66124
+ columnsExceptLast.every((col) => col.type === "text")) {
66125
+ const dataSets = columnsExceptLast.map(({ zone }) => ({
65819
66126
  dataRange: getUnboundRange(getters, zone),
65820
66127
  }));
65821
- return createBaseChart(textColumns.length >= 3 ? "sunburst" : "treemap", dataSets, {
65822
- labelRange: getUnboundRange(getters, numberColumns[0].zone),
66128
+ return {
66129
+ type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
66130
+ title: {},
66131
+ dataSets,
66132
+ labelRange: getUnboundRange(getters, lastColumn.zone),
65823
66133
  dataSetsHaveTitle,
65824
- });
66134
+ legendPosition: "none",
66135
+ };
65825
66136
  }
65826
- const dataSets = recomputeZones(numberColumns.map((col) => col.zone)).map((zone) => ({
66137
+ const firstColumn = columns[0];
66138
+ const columnsExceptFirst = columns.slice(1);
66139
+ const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }) => ({
65827
66140
  dataRange: getUnboundRange(getters, zone),
65828
66141
  }));
65829
- if (dateColumns.length === 1 && numberColumns.length > 1) {
65830
- return createBaseChart("line", dataSets, {
65831
- labelRange: getUnboundRange(getters, dateColumns[0].zone),
66142
+ if (columnsExceptFirst.every((col) => col.type === "percentage")) {
66143
+ return {
66144
+ type: "pie",
66145
+ title: {},
66146
+ dataSets: rangesOfColumnsExceptFirst,
66147
+ labelRange: getUnboundRange(getters, firstColumn.zone),
65832
66148
  dataSetsHaveTitle,
65833
- cumulative: false,
65834
- labelsAsText: false,
66149
+ aggregated: false,
65835
66150
  legendPosition: "top",
65836
- });
66151
+ };
65837
66152
  }
65838
- if (textColumns.length === 1 && numberColumns.length >= 2) {
65839
- const [textColumn] = textColumns;
65840
- const firstCell = getters.getEvaluatedCell({
65841
- sheetId: getters.getActiveSheetId(),
65842
- row: textColumn.zone.top,
65843
- col: textColumn.zone.left,
65844
- });
65845
- const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
65846
- const categoryCount = dataSetsHaveTitle && firstCell.value ? uniqueCount - 1 : uniqueCount;
65847
- const expectedDataCount = categoryCount * numberColumns.length + (dataSetsHaveTitle ? numberColumns.length : 0);
65848
- const actualDataCount = numberColumns.reduce((sum, dataCol) => sum + getCellStats(getters, dataCol.zone).totalCount, 0);
65849
- if (uniqueCount === totalCount &&
65850
- uniqueCount >= CHART_LIMITS.MIN_RADAR_CATEGORIES &&
65851
- uniqueCount <= CHART_LIMITS.MAX_RADAR_CATEGORIES &&
65852
- expectedDataCount === actualDataCount) {
65853
- return createBaseChart("radar", dataSets, {
65854
- title: dataSetsHaveTitle && firstCell.value ? { text: String(firstCell.value) } : {},
65855
- labelRange: getUnboundRange(getters, textColumn.zone),
65856
- dataSetsHaveTitle,
65857
- legendPosition: "top",
65858
- });
65859
- }
66153
+ if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) {
66154
+ return {
66155
+ ...DEFAULT_LINE_CHART_CONFIG,
66156
+ type: "line",
66157
+ dataSets: rangesOfColumnsExceptFirst,
66158
+ labelRange: getUnboundRange(getters, firstColumn.zone),
66159
+ dataSetsHaveTitle,
66160
+ legendPosition: "top",
66161
+ };
65860
66162
  }
65861
- const labelColumn = textColumns[0] || dateColumns[0] || numberColumns[0];
65862
- return createBaseChart("bar", dataSets, {
65863
- labelRange: dataSets.length ? getUnboundRange(getters, labelColumn.zone) : "",
66163
+ return {
66164
+ ...DEFAULT_BAR_CHART_CONFIG,
66165
+ dataSets: rangesOfColumnsExceptFirst,
66166
+ labelRange: getUnboundRange(getters, firstColumn.zone),
65864
66167
  dataSetsHaveTitle,
65865
- aggregated: true,
65866
66168
  legendPosition: "top",
65867
- });
66169
+ };
65868
66170
  }
65869
66171
  function buildScorecard(zone, getters) {
65870
66172
  const cell = getters.getCell({
@@ -65887,22 +66189,18 @@ stores.inject(MyMetaStore, storeInstance);
65887
66189
  */
65888
66190
  function getSmartChartDefinition(zones, getters) {
65889
66191
  const columns = categorizeColumns(zones, getters);
65890
- const { number: numberColumns, text: textColumns, date: dateColumns } = columns;
65891
- const columnCount = numberColumns.length + textColumns.length + dateColumns.length;
65892
- switch (columnCount) {
65893
- case 0:
65894
- return createBaseChart("bar", [{ dataRange: getUnboundRange(getters, zones[0]) }], {
65895
- dataSetsHaveTitle: false,
65896
- });
66192
+ if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
66193
+ const dataSets = columns.map(({ zone }) => ({ dataRange: getUnboundRange(getters, zone) }));
66194
+ return { ...DEFAULT_BAR_CHART_CONFIG, dataSets };
66195
+ }
66196
+ const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
66197
+ switch (nonEmptyColumns.length) {
65897
66198
  case 1:
65898
- const singleColumn = numberColumns[0] || textColumns[0] || dateColumns[0];
65899
- return getZoneArea(singleColumn.zone) === 1
65900
- ? buildScorecard(singleColumn.zone, getters)
65901
- : buildSingleColumnChart(singleColumn, getters);
66199
+ return buildSingleColumnChart(nonEmptyColumns[0], getters);
65902
66200
  case 2:
65903
- return buildTwoColumnChart(columns, getters);
66201
+ return buildTwoColumnChart(nonEmptyColumns, getters);
65904
66202
  default:
65905
- return buildMultiColumnChart(columns, getters);
66203
+ return buildMultiColumnChart(nonEmptyColumns, getters);
65906
66204
  }
65907
66205
  }
65908
66206
 
@@ -66435,23 +66733,11 @@ stores.inject(MyMetaStore, storeInstance);
66435
66733
  //------------------------------------------------------------------------------
66436
66734
  // Image
66437
66735
  //------------------------------------------------------------------------------
66438
- async function requestImage(env) {
66439
- try {
66440
- return await env.imageProvider.requestImage();
66441
- }
66442
- catch {
66443
- env.raiseError(_t$1("An unexpected error occurred during the image transfer"));
66444
- return;
66445
- }
66446
- }
66447
66736
  const CREATE_IMAGE = async (env) => {
66448
66737
  if (env.imageProvider) {
66449
66738
  const sheetId = env.model.getters.getActiveSheetId();
66450
66739
  const figureId = env.model.uuidGenerator.smallUuid();
66451
- const image = await requestImage(env);
66452
- if (!image) {
66453
- return;
66454
- }
66740
+ const image = await env.imageProvider.requestImage();
66455
66741
  const size = getMaxFigureSize(env.model.getters, image.size);
66456
66742
  const { col, row, offset } = centerFigurePosition(env.model.getters, size);
66457
66743
  env.model.dispatch("CREATE_IMAGE", {
@@ -78050,16 +78336,18 @@ stores.inject(MyMetaStore, storeInstance);
78050
78336
 
78051
78337
  class PivotSidePanelStore extends SpreadsheetStore {
78052
78338
  pivotId;
78339
+ updateMode;
78053
78340
  mutators = ["reset", "deferUpdates", "applyUpdate", "discardPendingUpdate", "update"];
78054
- updatesAreDeferred;
78341
+ _updatesAreDeferred;
78055
78342
  draft = null;
78056
78343
  notification = this.get(NotificationStore);
78057
78344
  alreadyNotified = false;
78058
78345
  alreadyNotifiedForPivotSize = false;
78059
- constructor(get, pivotId) {
78346
+ constructor(get, pivotId, updateMode = "canDefer") {
78060
78347
  super(get);
78061
78348
  this.pivotId = pivotId;
78062
- this.updatesAreDeferred =
78349
+ this.updateMode = updateMode;
78350
+ this._updatesAreDeferred =
78063
78351
  this.getters.getPivotCoreDefinition(this.pivotId).deferUpdates ?? false;
78064
78352
  }
78065
78353
  handle(cmd) {
@@ -78070,6 +78358,9 @@ stores.inject(MyMetaStore, storeInstance);
78070
78358
  }
78071
78359
  }
78072
78360
  }
78361
+ get updatesAreDeferred() {
78362
+ return this.updateMode === "neverDefer" ? false : this._updatesAreDeferred;
78363
+ }
78073
78364
  get fields() {
78074
78365
  return this.pivot.getFields();
78075
78366
  }
@@ -78144,7 +78435,7 @@ stores.inject(MyMetaStore, storeInstance);
78144
78435
  }
78145
78436
  reset(pivotId) {
78146
78437
  this.pivotId = pivotId;
78147
- this.updatesAreDeferred = true;
78438
+ this._updatesAreDeferred = true;
78148
78439
  this.draft = null;
78149
78440
  }
78150
78441
  deferUpdates(shouldDefer) {
@@ -78155,7 +78446,7 @@ stores.inject(MyMetaStore, storeInstance);
78155
78446
  else {
78156
78447
  this.update({ deferUpdates: shouldDefer });
78157
78448
  }
78158
- this.updatesAreDeferred = shouldDefer;
78449
+ this._updatesAreDeferred = shouldDefer;
78159
78450
  }
78160
78451
  applyUpdate() {
78161
78452
  if (this.draft) {
@@ -78409,6 +78700,26 @@ stores.inject(MyMetaStore, storeInstance);
78409
78700
  editor: PivotSpreadsheetSidePanel,
78410
78701
  });
78411
78702
 
78703
+ class PivotDesignPanel extends owl.Component {
78704
+ static template = "o-spreadsheet-PivotDesignPanel";
78705
+ static props = { pivotId: String };
78706
+ static components = { Section, Checkbox };
78707
+ store;
78708
+ setup() {
78709
+ this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId, "neverDefer");
78710
+ }
78711
+ updatePivotStyleProperty(key, value) {
78712
+ this.store.update({ style: { ...this.pivotStyle, [key]: value } });
78713
+ }
78714
+ get pivotStyle() {
78715
+ const pivot = this.env.model.getters.getPivotCoreDefinition(this.props.pivotId);
78716
+ return pivot.style || {};
78717
+ }
78718
+ get defaultStyle() {
78719
+ return DEFAULT_PIVOT_STYLE;
78720
+ }
78721
+ }
78722
+
78412
78723
  class PivotSidePanel extends owl.Component {
78413
78724
  static template = "o-spreadsheet-PivotSidePanel";
78414
78725
  static props = {
@@ -78418,6 +78729,13 @@ stores.inject(MyMetaStore, storeInstance);
78418
78729
  static components = {
78419
78730
  PivotLayoutConfigurator,
78420
78731
  Section,
78732
+ PivotDesignPanel,
78733
+ };
78734
+ state = owl.useState({ panel: "configuration" });
78735
+ panelContentRef = owl.useRef("panelContent");
78736
+ scrollPositions = {
78737
+ configuration: 0,
78738
+ design: 0,
78421
78739
  };
78422
78740
  setup() {
78423
78741
  useHighlights(this);
@@ -78432,6 +78750,13 @@ stores.inject(MyMetaStore, storeInstance);
78432
78750
  get highlights() {
78433
78751
  return getPivotHighlights(this.env.model.getters, this.props.pivotId);
78434
78752
  }
78753
+ switchPanel(panel) {
78754
+ const el = this.panelContentRef.el;
78755
+ if (el) {
78756
+ this.scrollPositions[this.state.panel] = el.scrollTop;
78757
+ }
78758
+ this.state.panel = panel;
78759
+ }
78435
78760
  }
78436
78761
 
78437
78762
  class RemoveDuplicatesPanel extends owl.Component {
@@ -87581,9 +87906,9 @@ stores.inject(MyMetaStore, storeInstance);
87581
87906
  exports.tokenize = tokenize;
87582
87907
 
87583
87908
 
87584
- __info__.version = "19.1.0-alpha.7";
87585
- __info__.date = "2025-10-17T11:09:35.690Z";
87586
- __info__.hash = "a11279d";
87909
+ __info__.version = "19.1.0-alpha.8";
87910
+ __info__.date = "2025-10-23T08:20:05.310Z";
87911
+ __info__.hash = "78717d4";
87587
87912
 
87588
87913
 
87589
87914
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);