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