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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,8 @@
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
5
  * @version 19.1.0-alpha.3
6
- * @date 2025-10-17T11:08:54.279Z
7
- * @hash a11279d
6
+ * @date 2025-10-23T11:12:13.207Z
7
+ * @hash bd756dd
8
8
  */
9
9
 
10
10
  (function (exports) {
@@ -911,8 +911,17 @@
911
911
  /**
912
912
  * Returns a function, that, as long as it continues to be invoked, will not
913
913
  * be triggered. The function will be called after it stops being called for
914
- * N milliseconds. If `immediate` is passed, trigger the function on the
915
- * leading edge, instead of the trailing.
914
+ * N milliseconds. If `immediate` is passed, the function is called is called
915
+ * immediately on the first call and the debouncing is triggered starting the second
916
+ * call in the defined time window.
917
+ *
918
+ * Example:
919
+ * debouncedFunction = debounce(() => console.log('Hello!'), 250);
920
+ * debouncedFunction(); debouncedFunction(); // Will log 'Hello!' after 250ms
921
+ *
922
+ * debouncedFunction = debounce(() => console.log('Hello!'), 250, true);
923
+ * debouncedFunction(); debouncedFunction(); // Will log 'Hello!' and relog it after 250ms
924
+ *
916
925
  *
917
926
  * Also decorate the argument function with two methods: stopDebounce and isDebouncePending.
918
927
  *
@@ -920,21 +929,21 @@
920
929
  */
921
930
  function debounce(func, wait, immediate) {
922
931
  let timeout = undefined;
932
+ let firstCalled = false;
923
933
  const debounced = function () {
924
934
  const context = this;
925
935
  const args = Array.from(arguments);
936
+ if (!firstCalled && immediate) {
937
+ firstCalled = true;
938
+ return func.apply(context, args);
939
+ }
926
940
  function later() {
927
941
  timeout = undefined;
928
- if (!immediate) {
929
- func.apply(context, args);
930
- }
942
+ firstCalled = false;
943
+ func.apply(context, args);
931
944
  }
932
- const callNow = immediate && !timeout;
933
945
  clearTimeout(timeout);
934
946
  timeout = setTimeout(later, wait);
935
- if (callNow) {
936
- func.apply(context, args);
937
- }
938
947
  };
939
948
  debounced.isDebouncePending = () => timeout !== undefined;
940
949
  debounced.stopDebounce = () => {
@@ -5185,6 +5194,29 @@
5185
5194
  removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
5186
5195
  }
5187
5196
  }
5197
+ function profilesContainsZone(profilesStartingPosition, profiles, zone) {
5198
+ const leftValue = zone.left;
5199
+ const rightValue = zone.right;
5200
+ const topValue = zone.top;
5201
+ const bottomValue = zone.bottom + 1;
5202
+ const leftIndex = binaryPredecessorSearch(profilesStartingPosition, leftValue, 0);
5203
+ const rightIndex = binaryPredecessorSearch(profilesStartingPosition, rightValue, leftIndex);
5204
+ if (leftIndex === -1 || rightIndex === -1) {
5205
+ return false;
5206
+ }
5207
+ for (let i = leftIndex; i <= rightIndex; i++) {
5208
+ const profile = profiles.get(profilesStartingPosition[i]);
5209
+ const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, true);
5210
+ const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, true);
5211
+ if (topPredIndex === -1 || topPredIndex % 2 !== 0) {
5212
+ return false;
5213
+ }
5214
+ if (topValue < profile[topPredIndex] || bottomValue > profile[bottomSuccIndex]) {
5215
+ return false;
5216
+ }
5217
+ }
5218
+ return true;
5219
+ }
5188
5220
  function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
5189
5221
  if (value === undefined) {
5190
5222
  // this is only the case when the value correspond to a bottom value that could be undefined
@@ -5269,7 +5301,18 @@
5269
5301
  }
5270
5302
  // add the top and bottom value to the profile and
5271
5303
  // remove all information between the top and bottom index
5272
- profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
5304
+ const toDelete = bottomSuccIndex - topPredIndex - 1;
5305
+ const toInsert = newPoints.length;
5306
+ const start = topPredIndex + 1;
5307
+ // fast path and slow path
5308
+ if (start === profile.length - 1 && toDelete === 1 && toInsert === 1) {
5309
+ // fast path: we just need to replace the last element
5310
+ profile[start] = newPoints[0] ?? newPoints[1];
5311
+ }
5312
+ else {
5313
+ // equivalent but slower and with memory allocation
5314
+ profile.splice(start, toDelete, ...newPoints);
5315
+ }
5273
5316
  }
5274
5317
  function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
5275
5318
  const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
@@ -5308,8 +5351,10 @@
5308
5351
  left,
5309
5352
  bottom,
5310
5353
  right,
5311
- hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
5312
5354
  };
5355
+ if ((bottom === undefined && top !== 0) || (right === undefined && left !== 0)) {
5356
+ profileZone.hasHeader = true;
5357
+ }
5313
5358
  let findCorrespondingZone = false;
5314
5359
  for (let j = pendingZones.length - 1; j >= 0; j--) {
5315
5360
  const pendingZone = pendingZones[j];
@@ -5764,17 +5809,6 @@
5764
5809
  }
5765
5810
  return [leftColumnZone, rightPartZone];
5766
5811
  }
5767
- function aggregatePositionsToZones(positions) {
5768
- const result = {};
5769
- for (const position of positions) {
5770
- result[position.sheetId] ??= [];
5771
- result[position.sheetId].push(positionToZone(position));
5772
- }
5773
- for (const sheetId in result) {
5774
- result[sheetId] = recomputeZones(result[sheetId]);
5775
- }
5776
- return result;
5777
- }
5778
5812
  /**
5779
5813
  * Array of all positions in the zone.
5780
5814
  */
@@ -14523,6 +14557,13 @@
14523
14557
  .add("minute_number", nullHandlerDecorator(minuteNumberAdapter))
14524
14558
  .add("second_number", nullHandlerDecorator(secondNumberAdapter));
14525
14559
 
14560
+ const DEFAULT_PIVOT_STYLE = {
14561
+ displayTotals: true,
14562
+ displayColumnHeaders: true,
14563
+ displayMeasuresRow: true,
14564
+ numberOfRows: Number.MAX_VALUE,
14565
+ numberOfColumns: Number.MAX_VALUE,
14566
+ };
14526
14567
  const AGGREGATOR_NAMES = {
14527
14568
  count: _t("Count"),
14528
14569
  count_distinct: _t("Count Distinct"),
@@ -14818,6 +14859,25 @@
14818
14859
  pivot: { ...definition, collapsedDomains: newDomains },
14819
14860
  });
14820
14861
  }
14862
+ function getPivotStyleFromFnArgs(definition, rowCountArg, includeTotalArg, includeColumnHeadersArg, columnCountArg, includeMeasuresRowArg, locale) {
14863
+ const style = definition.style;
14864
+ const numberOfRows = rowCountArg !== undefined
14865
+ ? toNumber(rowCountArg, locale)
14866
+ : style?.numberOfRows ?? DEFAULT_PIVOT_STYLE.numberOfRows;
14867
+ const numberOfColumns = columnCountArg !== undefined
14868
+ ? toNumber(columnCountArg, locale)
14869
+ : style?.numberOfColumns ?? DEFAULT_PIVOT_STYLE.numberOfColumns;
14870
+ const displayTotals = includeTotalArg !== undefined
14871
+ ? toBoolean(includeTotalArg)
14872
+ : style?.displayTotals ?? DEFAULT_PIVOT_STYLE.displayTotals;
14873
+ const displayColumnHeaders = includeColumnHeadersArg !== undefined
14874
+ ? toBoolean(includeColumnHeadersArg)
14875
+ : style?.displayColumnHeaders ?? DEFAULT_PIVOT_STYLE.displayColumnHeaders;
14876
+ const displayMeasuresRow = includeMeasuresRowArg !== undefined
14877
+ ? toBoolean(includeMeasuresRowArg)
14878
+ : style?.displayMeasuresRow ?? DEFAULT_PIVOT_STYLE.displayMeasuresRow;
14879
+ return { numberOfRows, numberOfColumns, displayTotals, displayColumnHeaders, displayMeasuresRow };
14880
+ }
14821
14881
 
14822
14882
  /**
14823
14883
  * Get the pivot ID from the formula pivot ID.
@@ -15432,24 +15492,18 @@
15432
15492
  arg("column_count (number, optional)", _t("number of columns")),
15433
15493
  arg("include_measure_titles (boolean, default=TRUE)", _t("Whether to include the measure titles row or not.")),
15434
15494
  ],
15435
- compute: function (pivotFormulaId, rowCount = { value: 10000 }, includeTotal = { value: true }, includeColumnHeaders = { value: true }, columnCount = { value: Number.MAX_VALUE }, includeMeasureTitles = { value: true }) {
15495
+ compute: function (pivotFormulaId, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles) {
15436
15496
  const _pivotFormulaId = toString(pivotFormulaId);
15437
- const _rowCount = toNumber(rowCount, this.locale);
15438
- if (_rowCount < 0) {
15497
+ const pivotId = getPivotId(_pivotFormulaId, this.getters);
15498
+ const pivot = this.getters.getPivot(pivotId);
15499
+ const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
15500
+ const pivotStyle = getPivotStyleFromFnArgs(coreDefinition, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles, this.locale);
15501
+ if (pivotStyle.numberOfRows < 0) {
15439
15502
  return new EvaluationError(_t("The number of rows must be positive."));
15440
15503
  }
15441
- const _columnCount = toNumber(columnCount, this.locale);
15442
- if (_columnCount < 0) {
15504
+ if (pivotStyle.numberOfColumns < 0) {
15443
15505
  return new EvaluationError(_t("The number of columns must be positive."));
15444
15506
  }
15445
- const visibilityOptions = {
15446
- displayColumnHeaders: toBoolean(includeColumnHeaders),
15447
- displayTotals: toBoolean(includeTotal),
15448
- displayMeasuresRow: toBoolean(includeMeasureTitles),
15449
- };
15450
- const pivotId = getPivotId(_pivotFormulaId, this.getters);
15451
- const pivot = this.getters.getPivot(pivotId);
15452
- const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
15453
15507
  addPivotDependencies(this, coreDefinition, coreDefinition.measures);
15454
15508
  pivot.init({ reload: pivot.needsReevaluation });
15455
15509
  const error = pivot.assertIsValid({ throwOnError: false });
@@ -15460,20 +15514,20 @@
15460
15514
  if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
15461
15515
  return new EvaluationError(getPivotTooBigErrorMessage(table.numberOfCells, this.locale));
15462
15516
  }
15463
- const cells = table.getPivotCells(visibilityOptions);
15517
+ const cells = table.getPivotCells(pivotStyle);
15464
15518
  let headerRows = 0;
15465
- if (visibilityOptions.displayColumnHeaders) {
15519
+ if (pivotStyle.displayColumnHeaders) {
15466
15520
  headerRows = table.columns.length - 1;
15467
15521
  }
15468
- if (visibilityOptions.displayMeasuresRow) {
15522
+ if (pivotStyle.displayMeasuresRow) {
15469
15523
  headerRows++;
15470
15524
  }
15471
15525
  const pivotTitle = this.getters.getPivotName(pivotId);
15472
- const tableHeight = Math.min(headerRows + _rowCount, cells[0].length);
15526
+ const tableHeight = Math.min(headerRows + pivotStyle.numberOfRows, cells[0].length);
15473
15527
  if (tableHeight === 0) {
15474
15528
  return [[{ value: pivotTitle }]];
15475
15529
  }
15476
- const tableWidth = Math.min(1 + _columnCount, cells.length);
15530
+ const tableWidth = Math.min(1 + pivotStyle.numberOfColumns, cells.length);
15477
15531
  const result = [];
15478
15532
  for (const col of range(0, tableWidth)) {
15479
15533
  result[col] = [];
@@ -15496,7 +15550,7 @@
15496
15550
  }
15497
15551
  }
15498
15552
  }
15499
- if (visibilityOptions.displayColumnHeaders || visibilityOptions.displayMeasuresRow) {
15553
+ if (pivotStyle.displayColumnHeaders || pivotStyle.displayMeasuresRow) {
15500
15554
  result[0][0] = { value: pivotTitle };
15501
15555
  }
15502
15556
  return result;
@@ -17968,6 +18022,10 @@
17968
18022
  }
17969
18023
  return parts;
17970
18024
  }
18025
+ function positionToBoundedRange(position) {
18026
+ const zone = { left: position.col, top: position.row, right: position.col, bottom: position.row };
18027
+ return { sheetId: position.sheetId, zone };
18028
+ }
17971
18029
  /**
17972
18030
  * Check that a zone is valid regarding the order of top-bottom and left-right.
17973
18031
  * Left should be smaller than right, top should be smaller than bottom.
@@ -26656,6 +26714,7 @@
26656
26714
  return data;
26657
26715
  }
26658
26716
  const figureIds = new Set();
26717
+ const chartIds = new Set();
26659
26718
  const uuidGenerator = new UuidGenerator();
26660
26719
  for (const sheet of data.sheets || []) {
26661
26720
  for (const figure of sheet.figures || []) {
@@ -26663,6 +26722,12 @@
26663
26722
  figure.id += uuidGenerator.smallUuid();
26664
26723
  }
26665
26724
  figureIds.add(figure.id);
26725
+ if (figure.tag === "chart") {
26726
+ if (chartIds.has(figure.data?.chartId)) {
26727
+ figure.data.chartId += uuidGenerator.smallUuid();
26728
+ }
26729
+ chartIds.add(figure.data?.chartId);
26730
+ }
26666
26731
  }
26667
26732
  }
26668
26733
  data.uniqueFigureIds = true;
@@ -27030,11 +27095,6 @@
27030
27095
  * @param sheetName couple of old and new sheet names to adapt ranges pointing to that sheet
27031
27096
  */
27032
27097
  adaptRanges(applyChange, sheetId, sheetName) { }
27033
- /**
27034
- * Implement this method to clean unused external resources, such as images
27035
- * stored on a server which have been deleted.
27036
- */
27037
- garbageCollectExternalResources() { }
27038
27098
  }
27039
27099
 
27040
27100
  class BordersPlugin extends CorePlugin {
@@ -30878,17 +30938,6 @@
30878
30938
  break;
30879
30939
  }
30880
30940
  }
30881
- /**
30882
- * Delete unused images from the file store
30883
- */
30884
- garbageCollectExternalResources() {
30885
- const images = new Set(this.getAllImages().map((image) => image.path));
30886
- for (const path of this.syncedImages) {
30887
- if (!images.has(path)) {
30888
- this.fileStore?.delete(path);
30889
- }
30890
- }
30891
- }
30892
30941
  // ---------------------------------------------------------------------------
30893
30942
  // Getters
30894
30943
  // ---------------------------------------------------------------------------
@@ -30950,13 +30999,6 @@
30950
30999
  sheet.images = [...sheet.images, ...images];
30951
31000
  }
30952
31001
  }
30953
- getAllImages() {
30954
- const images = [];
30955
- for (const sheetId in this.images) {
30956
- images.push(...Object.values(this.images[sheetId] || {}).filter(isDefined));
30957
- }
30958
- return images;
30959
- }
30960
31002
  }
30961
31003
 
30962
31004
  class MergePlugin extends CorePlugin {
@@ -31732,26 +31774,22 @@
31732
31774
  getNumberOfDataColumns() {
31733
31775
  return this.columns.at(-1)?.length || 0;
31734
31776
  }
31735
- getSkippedRows(visibilityOptions) {
31777
+ getSkippedRows(pivotStyle) {
31736
31778
  const skippedRows = new Set();
31737
- if (!visibilityOptions.displayColumnHeaders) {
31779
+ if (!pivotStyle.displayColumnHeaders) {
31738
31780
  for (let i = 0; i < this.columns.length - 1; i++) {
31739
31781
  skippedRows.add(i);
31740
31782
  }
31741
31783
  }
31742
- if (!visibilityOptions.displayMeasuresRow) {
31784
+ if (!pivotStyle.displayMeasuresRow) {
31743
31785
  skippedRows.add(this.columns.length - 1);
31744
31786
  }
31745
31787
  return skippedRows;
31746
31788
  }
31747
- getPivotCells(visibilityOptions = {
31748
- displayColumnHeaders: true,
31749
- displayTotals: true,
31750
- displayMeasuresRow: true,
31751
- }) {
31752
- const key = JSON.stringify(visibilityOptions);
31789
+ getPivotCells(pivotStyle = DEFAULT_PIVOT_STYLE) {
31790
+ const key = JSON.stringify(pivotStyle);
31753
31791
  if (!this.pivotCells[key]) {
31754
- const { displayTotals } = visibilityOptions;
31792
+ const { displayTotals } = pivotStyle;
31755
31793
  const numberOfDataRows = this.rows.length;
31756
31794
  const numberOfDataColumns = this.getNumberOfDataColumns();
31757
31795
  let pivotHeight = this.columns.length + numberOfDataRows;
@@ -31763,7 +31801,7 @@
31763
31801
  pivotWidth -= this.measures.length;
31764
31802
  }
31765
31803
  const domainArray = [];
31766
- const skippedRows = this.getSkippedRows(visibilityOptions);
31804
+ const skippedRows = this.getSkippedRows(pivotStyle);
31767
31805
  for (let col = 0; col < pivotWidth; col++) {
31768
31806
  domainArray.push([]);
31769
31807
  for (let row = 0; row < pivotHeight; row++) {
@@ -34882,6 +34920,281 @@
34882
34920
  }
34883
34921
  }
34884
34922
 
34923
+ class ZoneSet {
34924
+ profilesStartingPosition = [0];
34925
+ profiles = new Map([[0, []]]);
34926
+ constructor(zones = []) {
34927
+ for (const zone of zones) {
34928
+ this.add(zone);
34929
+ }
34930
+ }
34931
+ isEmpty() {
34932
+ return this.profiles.size === 1 && this.profiles.get(0)?.length === 0;
34933
+ }
34934
+ add(zone) {
34935
+ modifyProfiles(this.profilesStartingPosition, this.profiles, [zone]);
34936
+ }
34937
+ delete(zone) {
34938
+ modifyProfiles(this.profilesStartingPosition, this.profiles, [zone], true);
34939
+ }
34940
+ has(zone) {
34941
+ return profilesContainsZone(this.profilesStartingPosition, this.profiles, zone);
34942
+ }
34943
+ difference(other) {
34944
+ const result = this.copy();
34945
+ for (const zone of other) {
34946
+ result.delete(zone);
34947
+ }
34948
+ return result;
34949
+ }
34950
+ copy() {
34951
+ const result = new ZoneSet();
34952
+ result.profilesStartingPosition = [...this.profilesStartingPosition];
34953
+ result.profiles = new Map();
34954
+ for (const [key, value] of this.profiles) {
34955
+ result.profiles.set(key, [...value]);
34956
+ }
34957
+ return result;
34958
+ }
34959
+ size() {
34960
+ let size = 0;
34961
+ for (const profile of this.profiles.values()) {
34962
+ size += profile.length;
34963
+ }
34964
+ return size / 2;
34965
+ }
34966
+ /**
34967
+ * iterator of all the zones in the ZoneSet
34968
+ */
34969
+ [Symbol.iterator]() {
34970
+ return constructZonesFromProfiles(this.profilesStartingPosition, this.profiles)[Symbol.iterator]();
34971
+ }
34972
+ }
34973
+
34974
+ class RangeSet {
34975
+ setsBySheetId = {};
34976
+ constructor(ranges = []) {
34977
+ for (const range of ranges) {
34978
+ this.add(range);
34979
+ }
34980
+ }
34981
+ add(range) {
34982
+ if (!this.setsBySheetId[range.sheetId]) {
34983
+ this.setsBySheetId[range.sheetId] = new ZoneSet();
34984
+ }
34985
+ this.setsBySheetId[range.sheetId].add(range.zone);
34986
+ }
34987
+ addMany(ranges) {
34988
+ for (const range of ranges) {
34989
+ this.add(range);
34990
+ }
34991
+ }
34992
+ addPosition(position) {
34993
+ this.add(positionToBoundedRange(position));
34994
+ }
34995
+ addManyPositions(positions) {
34996
+ for (const position of positions) {
34997
+ this.addPosition(position);
34998
+ }
34999
+ }
35000
+ has(range) {
35001
+ if (!this.setsBySheetId[range.sheetId]) {
35002
+ return false;
35003
+ }
35004
+ return this.setsBySheetId[range.sheetId].has(range.zone);
35005
+ }
35006
+ hasPosition(position) {
35007
+ return this.has(positionToBoundedRange(position));
35008
+ }
35009
+ delete(range) {
35010
+ if (!this.setsBySheetId[range.sheetId]) {
35011
+ return;
35012
+ }
35013
+ this.setsBySheetId[range.sheetId].delete(range.zone);
35014
+ }
35015
+ deleteMany(ranges) {
35016
+ for (const range of ranges) {
35017
+ this.delete(range);
35018
+ }
35019
+ }
35020
+ deleteManyPositions(positions) {
35021
+ for (const position of positions) {
35022
+ this.delete(positionToBoundedRange(position));
35023
+ }
35024
+ }
35025
+ difference(other) {
35026
+ const result = new RangeSet();
35027
+ for (const sheetId in this.setsBySheetId) {
35028
+ result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId];
35029
+ }
35030
+ for (const sheetId in other.setsBySheetId) {
35031
+ if (result.setsBySheetId[sheetId]) {
35032
+ result.setsBySheetId[sheetId] = result.setsBySheetId[sheetId].difference(other.setsBySheetId[sheetId]);
35033
+ }
35034
+ }
35035
+ return result;
35036
+ }
35037
+ copy() {
35038
+ const result = new RangeSet();
35039
+ for (const sheetId in this.setsBySheetId) {
35040
+ result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId].copy();
35041
+ }
35042
+ return result;
35043
+ }
35044
+ clear() {
35045
+ this.setsBySheetId = {};
35046
+ }
35047
+ size() {
35048
+ let size = 0;
35049
+ for (const sheetId in this.setsBySheetId) {
35050
+ size += this.setsBySheetId[sheetId].size();
35051
+ }
35052
+ return size;
35053
+ }
35054
+ isEmpty() {
35055
+ for (const sheetId in this.setsBySheetId) {
35056
+ if (!this.setsBySheetId[sheetId].isEmpty()) {
35057
+ return false;
35058
+ }
35059
+ }
35060
+ return true;
35061
+ }
35062
+ /**
35063
+ * iterator of all the ranges in the RangeSet
35064
+ */
35065
+ [Symbol.iterator]() {
35066
+ const result = [];
35067
+ for (const sheetId in this.setsBySheetId) {
35068
+ for (const zone of this.setsBySheetId[sheetId]) {
35069
+ result.push({ sheetId: sheetId, zone });
35070
+ }
35071
+ }
35072
+ return result[Symbol.iterator]();
35073
+ }
35074
+ }
35075
+
35076
+ /**
35077
+ * R-Tree of ranges, mapping zones (r-tree bounding boxes) to ranges (data of the r-tree item).
35078
+ * Ranges associated to the exact same bounding box are grouped together
35079
+ * to reduce the number of nodes in the R-tree.
35080
+ */
35081
+ class DependenciesRTree {
35082
+ rTree;
35083
+ constructor(items = []) {
35084
+ const compactedBoxes = groupSameBoundingBoxes(items);
35085
+ this.rTree = new SpreadsheetRTree(compactedBoxes);
35086
+ }
35087
+ insert(item) {
35088
+ const data = this.rTree.search(item.boundingBox);
35089
+ const itemBoundingBox = item.boundingBox;
35090
+ const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
35091
+ boundingBox.zone.left === itemBoundingBox.zone.left &&
35092
+ boundingBox.zone.top === itemBoundingBox.zone.top &&
35093
+ boundingBox.zone.right === itemBoundingBox.zone.right &&
35094
+ boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
35095
+ if (exactBoundingBox) {
35096
+ exactBoundingBox.data.add(item.data);
35097
+ }
35098
+ else {
35099
+ this.rTree.insert({ ...item, data: new RangeSet([item.data]) });
35100
+ }
35101
+ }
35102
+ search({ zone, sheetId }) {
35103
+ const results = new RangeSet();
35104
+ for (const { data } of this.rTree.search({ zone, sheetId })) {
35105
+ results.addMany(data);
35106
+ }
35107
+ return results;
35108
+ }
35109
+ remove(item) {
35110
+ const data = this.rTree.search(item.boundingBox);
35111
+ const itemBoundingBox = item.boundingBox;
35112
+ const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
35113
+ boundingBox.zone.left === itemBoundingBox.zone.left &&
35114
+ boundingBox.zone.top === itemBoundingBox.zone.top &&
35115
+ boundingBox.zone.right === itemBoundingBox.zone.right &&
35116
+ boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
35117
+ if (exactBoundingBox) {
35118
+ exactBoundingBox.data.delete(item.data);
35119
+ }
35120
+ else {
35121
+ this.rTree.remove({ ...item, data: new RangeSet([item.data]) });
35122
+ }
35123
+ }
35124
+ }
35125
+ /**
35126
+ * Group together all formulas pointing to the exact same dependency (bounding box).
35127
+ * The goal is to optimize the following case:
35128
+ * - if any cell in B1:B1000 changes, C1 must be recomputed
35129
+ * - if any cell in B1:B1000 changes, C2 must be recomputed
35130
+ * - if any cell in B1:B1000 changes, C3 must be recomputed
35131
+ * ...
35132
+ * - if any cell in B1:B1000 changes, C1000 must be recomputed
35133
+ *
35134
+ * Instead of having 1000 entries in the R-tree, we want to have a single entry
35135
+ * with B1:B1000 (bounding box) pointing to C1:C1000 (formulas).
35136
+ */
35137
+ function groupSameBoundingBoxes(items) {
35138
+ // Important: this function must be as fast as possible. It is on the evaluation hot path.
35139
+ let maxCol = 0;
35140
+ let maxRow = 0;
35141
+ for (let i = 0; i < items.length; i++) {
35142
+ const zone = items[i].boundingBox.zone;
35143
+ if (zone.right > maxCol) {
35144
+ maxCol = zone.right;
35145
+ }
35146
+ if (zone.bottom > maxRow) {
35147
+ maxRow = zone.bottom;
35148
+ }
35149
+ }
35150
+ maxCol += 1;
35151
+ maxRow += 1;
35152
+ // in most real-world cases, we can use a fast numeric key
35153
+ // but if the zones are too far right or bottom, we fallback to a slower string key
35154
+ const maxPossibleKey = (((maxRow + 1) * maxCol + 1) * maxRow + 1) * maxCol;
35155
+ const useFastKey = maxPossibleKey <= Number.MAX_SAFE_INTEGER;
35156
+ if (!useFastKey) {
35157
+ console.warn("Max col/row size exceeded, using slow zone key");
35158
+ }
35159
+ const groupedByBBox = {};
35160
+ for (const item of items) {
35161
+ const sheetId = item.boundingBox.sheetId;
35162
+ if (!groupedByBBox[sheetId]) {
35163
+ groupedByBBox[sheetId] = {};
35164
+ }
35165
+ const bBox = item.boundingBox.zone;
35166
+ let bBoxKey = 0;
35167
+ if (useFastKey) {
35168
+ bBoxKey =
35169
+ bBox.left +
35170
+ bBox.top * maxCol +
35171
+ bBox.right * maxCol * maxRow +
35172
+ bBox.bottom * maxCol * maxRow * maxCol;
35173
+ }
35174
+ else {
35175
+ bBoxKey = `${bBox.left},${bBox.top},${bBox.right},${bBox.bottom}`;
35176
+ }
35177
+ if (groupedByBBox[sheetId][bBoxKey]) {
35178
+ const ranges = groupedByBBox[sheetId][bBoxKey].data;
35179
+ ranges.add(item.data);
35180
+ }
35181
+ else {
35182
+ groupedByBBox[sheetId][bBoxKey] = {
35183
+ boundingBox: item.boundingBox,
35184
+ data: new RangeSet([item.data]),
35185
+ };
35186
+ }
35187
+ }
35188
+ const result = [];
35189
+ for (const sheetId in groupedByBBox) {
35190
+ const map = groupedByBBox[sheetId];
35191
+ for (const key in map) {
35192
+ result.push(map[key]);
35193
+ }
35194
+ }
35195
+ return result;
35196
+ }
35197
+
34885
35198
  /**
34886
35199
  * Implementation of a dependency Graph.
34887
35200
  * The graph is used to evaluate the cells in the correct
@@ -34890,12 +35203,10 @@
34890
35203
  * It uses an R-Tree data structure to efficiently find dependent cells.
34891
35204
  */
34892
35205
  class FormulaDependencyGraph {
34893
- createEmptyPositionSet;
34894
35206
  dependencies = new PositionMap();
34895
35207
  rTree;
34896
- constructor(createEmptyPositionSet, data = []) {
34897
- this.createEmptyPositionSet = createEmptyPositionSet;
34898
- this.rTree = new SpreadsheetRTree(data);
35208
+ constructor(data = []) {
35209
+ this.rTree = new DependenciesRTree(data);
34899
35210
  }
34900
35211
  removeAllDependencies(formulaPosition) {
34901
35212
  const ranges = this.dependencies.get(formulaPosition);
@@ -34909,7 +35220,10 @@
34909
35220
  }
34910
35221
  addDependencies(formulaPosition, dependencies) {
34911
35222
  const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
34912
- data: formulaPosition,
35223
+ data: {
35224
+ sheetId: formulaPosition.sheetId,
35225
+ zone: positionToZone(formulaPosition),
35226
+ },
34913
35227
  boundingBox: {
34914
35228
  zone,
34915
35229
  sheetId,
@@ -34927,46 +35241,20 @@
34927
35241
  }
34928
35242
  }
34929
35243
  /**
34930
- * Return all the cells that depend on the provided ranges,
34931
- * in the correct order they should be evaluated.
34932
- * This is called a topological ordering (excluding cycles)
35244
+ * Return all the cells that depend on the provided ranges.
34933
35245
  */
34934
- getCellsDependingOn(ranges, ignore) {
34935
- const visited = this.createEmptyPositionSet();
35246
+ getCellsDependingOn(ranges, visited = new RangeSet()) {
35247
+ visited = visited.copy();
34936
35248
  const queue = Array.from(ranges).reverse();
34937
35249
  while (queue.length > 0) {
34938
35250
  const range = queue.pop();
34939
- const zone = range.zone;
34940
- const sheetId = range.sheetId;
34941
- for (let col = zone.left; col <= zone.right; col++) {
34942
- for (let row = zone.top; row <= zone.bottom; row++) {
34943
- visited.add({ sheetId, col, row });
34944
- }
34945
- }
34946
- const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
34947
- const nextInQueue = {};
34948
- for (const position of impactedPositions) {
34949
- if (!visited.has(position) && !ignore.has(position)) {
34950
- if (!nextInQueue[position.sheetId]) {
34951
- nextInQueue[position.sheetId] = [];
34952
- }
34953
- nextInQueue[position.sheetId].push(positionToZone(position));
34954
- }
34955
- }
34956
- for (const sheetId in nextInQueue) {
34957
- const zones = recomputeZones(nextInQueue[sheetId], []);
34958
- queue.push(...zones.map((zone) => ({ sheetId, zone })));
34959
- }
35251
+ visited.add(range);
35252
+ const impactedRanges = this.rTree.search(range);
35253
+ queue.push(...impactedRanges.difference(visited));
34960
35254
  }
34961
35255
  // remove initial ranges
34962
35256
  for (const range of ranges) {
34963
- const zone = range.zone;
34964
- const sheetId = range.sheetId;
34965
- for (let col = zone.left; col <= zone.right; col++) {
34966
- for (let row = zone.top; row <= zone.bottom; row++) {
34967
- visited.delete({ sheetId, col, row });
34968
- }
34969
- }
35257
+ visited.delete(range);
34970
35258
  }
34971
35259
  return visited;
34972
35260
  }
@@ -35243,7 +35531,7 @@
35243
35531
  getters;
35244
35532
  compilationParams;
35245
35533
  evaluatedCells = new PositionMap();
35246
- formulaDependencies = lazy(new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this)));
35534
+ formulaDependencies = lazy(new FormulaDependencyGraph());
35247
35535
  blockedArrayFormulas = new PositionSet({});
35248
35536
  spreadingRelations = new SpreadingRelation();
35249
35537
  constructor(context, getters) {
@@ -35278,7 +35566,7 @@
35278
35566
  return undefined;
35279
35567
  }
35280
35568
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
35281
- return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
35569
+ return arrayFormulas.find((position) => !this.blockedArrayFormulas.has(position));
35282
35570
  }
35283
35571
  updateDependencies(position) {
35284
35572
  // removing dependencies is slow because it requires
@@ -35322,57 +35610,72 @@
35322
35610
  }
35323
35611
  evaluateCells(positions) {
35324
35612
  const start = performance.now();
35325
- const cellsToCompute = this.createEmptyPositionSet();
35326
- cellsToCompute.addMany(positions);
35613
+ const rangesToCompute = new RangeSet();
35614
+ rangesToCompute.addManyPositions(positions);
35327
35615
  const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
35328
- cellsToCompute.addMany(this.getCellsDependingOn(positions));
35329
- cellsToCompute.addMany(arrayFormulasPositions);
35330
- cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
35331
- this.evaluate(cellsToCompute);
35616
+ rangesToCompute.addMany(this.getCellsDependingOn(rangesToCompute));
35617
+ rangesToCompute.addMany(arrayFormulasPositions);
35618
+ rangesToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
35619
+ this.evaluate(rangesToCompute);
35332
35620
  console.debug("evaluate Cells", performance.now() - start, "ms");
35333
35621
  }
35334
35622
  getArrayFormulasImpactedByChangesOf(positions) {
35335
- const impactedPositions = this.createEmptyPositionSet();
35623
+ const impactedRanges = new RangeSet();
35336
35624
  for (const position of positions) {
35337
35625
  const content = this.getters.getCell(position)?.content;
35338
35626
  const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
35339
35627
  if (arrayFormulaPosition !== undefined) {
35340
35628
  // take into account new collisions.
35341
- impactedPositions.add(arrayFormulaPosition);
35629
+ impactedRanges.addPosition(arrayFormulaPosition);
35342
35630
  }
35343
35631
  if (!content) {
35344
35632
  // The previous content could have blocked some array formulas
35345
- impactedPositions.add(position);
35633
+ impactedRanges.addPosition(position);
35346
35634
  }
35347
35635
  }
35348
- const zonesBySheetIds = aggregatePositionsToZones(impactedPositions);
35349
- for (const sheetId in zonesBySheetIds) {
35350
- for (const zone of zonesBySheetIds[sheetId]) {
35351
- impactedPositions.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
35352
- }
35636
+ for (const range of [...impactedRanges]) {
35637
+ impactedRanges.addMany(this.getArrayFormulasBlockedBy(range.sheetId, range.zone));
35353
35638
  }
35354
- return impactedPositions;
35639
+ return impactedRanges;
35355
35640
  }
35356
35641
  buildDependencyGraph() {
35357
35642
  this.blockedArrayFormulas = this.createEmptyPositionSet();
35358
35643
  this.spreadingRelations = new SpreadingRelation();
35359
35644
  this.formulaDependencies = lazy(() => {
35360
- const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position)
35361
- .filter((range) => !range.invalidSheetName && !range.invalidXc)
35362
- .map((range) => ({
35363
- data: position,
35364
- boundingBox: {
35365
- zone: range.zone,
35366
- sheetId: range.sheetId,
35367
- },
35368
- })));
35369
- return new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this), dependencies);
35645
+ const rTreeItems = [];
35646
+ for (const sheetId of this.getters.getSheetIds()) {
35647
+ const cells = this.getters.getCells(sheetId);
35648
+ for (const cellId in cells) {
35649
+ const cell = cells[cellId];
35650
+ if (cell.isFormula) {
35651
+ const directDependencies = cell.compiledFormula.dependencies;
35652
+ for (const range of directDependencies) {
35653
+ if (range.invalidSheetName || range.invalidXc) {
35654
+ continue;
35655
+ }
35656
+ rTreeItems.push({
35657
+ data: {
35658
+ sheetId,
35659
+ zone: positionToZone(this.getters.getCellPosition(cellId)),
35660
+ },
35661
+ boundingBox: { sheetId: range.sheetId, zone: range.zone },
35662
+ });
35663
+ }
35664
+ }
35665
+ }
35666
+ }
35667
+ return new FormulaDependencyGraph(rTreeItems);
35370
35668
  });
35371
35669
  }
35372
35670
  evaluateAllCells() {
35373
35671
  const start = performance.now();
35374
35672
  this.evaluatedCells = new PositionMap();
35375
- this.evaluate(this.getAllCells());
35673
+ const ranges = [];
35674
+ for (const sheetId of this.getters.getSheetIds()) {
35675
+ const zone = this.getters.getSheetZone(sheetId);
35676
+ ranges.push({ sheetId, zone });
35677
+ }
35678
+ this.evaluate(ranges);
35376
35679
  console.debug("evaluate all cells", performance.now() - start, "ms");
35377
35680
  }
35378
35681
  evaluateFormulaResult(sheetId, formulaString) {
@@ -35396,48 +35699,47 @@
35396
35699
  return handleError(error, "");
35397
35700
  }
35398
35701
  }
35399
- getAllCells() {
35400
- const positions = this.createEmptyPositionSet();
35401
- positions.fillAllPositions();
35402
- return positions;
35403
- }
35404
35702
  /**
35405
35703
  * Return the position of formulas blocked by the given positions
35406
35704
  * as well as all their dependencies.
35407
35705
  */
35408
35706
  getArrayFormulasBlockedBy(sheetId, zone) {
35409
- const arrayFormulaPositions = this.createEmptyPositionSet();
35707
+ const arrayFormulaPositions = new RangeSet();
35410
35708
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(sheetId, zone);
35411
- arrayFormulaPositions.addMany(arrayFormulas);
35709
+ arrayFormulaPositions.addManyPositions(arrayFormulas);
35412
35710
  const spilledPositions = [...arrayFormulas].filter((position) => !this.blockedArrayFormulas.has(position));
35413
35711
  if (spilledPositions.length) {
35414
35712
  // ignore the formula spreading on the position. Keep only the blocked ones
35415
- arrayFormulaPositions.deleteMany(spilledPositions);
35713
+ arrayFormulaPositions.deleteManyPositions(spilledPositions);
35416
35714
  }
35417
35715
  arrayFormulaPositions.addMany(this.getCellsDependingOn(arrayFormulaPositions));
35418
35716
  return arrayFormulaPositions;
35419
35717
  }
35420
- nextPositionsToUpdate = new PositionSet({});
35718
+ nextRangesToUpdate = new RangeSet();
35421
35719
  cellsBeingComputed = new Set();
35422
35720
  symbolsBeingComputed = new Set();
35423
- evaluate(positions) {
35721
+ evaluate(ranges) {
35424
35722
  this.cellsBeingComputed = new Set();
35425
- this.nextPositionsToUpdate = positions;
35723
+ this.nextRangesToUpdate = new RangeSet(ranges);
35426
35724
  let currentIteration = 0;
35427
- while (!this.nextPositionsToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
35725
+ while (!this.nextRangesToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
35428
35726
  this.updateCompilationParameters();
35429
- const positions = this.nextPositionsToUpdate.clear();
35430
- for (let i = 0; i < positions.length; ++i) {
35431
- this.evaluatedCells.delete(positions[i]);
35432
- }
35433
- for (let i = 0; i < positions.length; ++i) {
35434
- const position = positions[i];
35435
- if (this.nextPositionsToUpdate.has(position)) {
35436
- continue;
35437
- }
35438
- const evaluatedCell = this.computeCell(position);
35439
- if (evaluatedCell !== EMPTY_CELL) {
35440
- this.evaluatedCells.set(position, evaluatedCell);
35727
+ const ranges = [...this.nextRangesToUpdate];
35728
+ this.nextRangesToUpdate.clear();
35729
+ this.clearEvaluatedRanges(ranges);
35730
+ for (const range of ranges) {
35731
+ const { left, bottom, right, top } = range.zone;
35732
+ for (let col = left; col <= right; col++) {
35733
+ for (let row = top; row <= bottom; row++) {
35734
+ const position = { sheetId: range.sheetId, col, row };
35735
+ if (this.nextRangesToUpdate.hasPosition(position)) {
35736
+ continue;
35737
+ }
35738
+ const evaluatedCell = this.computeCell(position);
35739
+ if (evaluatedCell !== EMPTY_CELL) {
35740
+ this.evaluatedCells.set(position, evaluatedCell);
35741
+ }
35742
+ }
35441
35743
  }
35442
35744
  }
35443
35745
  onIterationEndEvaluationRegistry.getAll().forEach((callback) => callback(this.getters));
@@ -35446,6 +35748,16 @@
35446
35748
  console.warn("Maximum iteration reached while evaluating cells");
35447
35749
  }
35448
35750
  }
35751
+ clearEvaluatedRanges(ranges) {
35752
+ for (const range of ranges) {
35753
+ const { left, bottom, right, top } = range.zone;
35754
+ for (let col = left; col <= right; col++) {
35755
+ for (let row = top; row <= bottom; row++) {
35756
+ this.evaluatedCells.delete({ sheetId: range.sheetId, col, row });
35757
+ }
35758
+ }
35759
+ }
35760
+ }
35449
35761
  computeCell(position) {
35450
35762
  const evaluation = this.evaluatedCells.get(position);
35451
35763
  if (evaluation) {
@@ -35518,9 +35830,9 @@
35518
35830
  }
35519
35831
  invalidatePositionsDependingOnSpread(sheetId, resultZone) {
35520
35832
  // the result matrix is split in 2 zones to exclude the array formula position
35521
- const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })), this.nextPositionsToUpdate);
35522
- invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
35523
- this.nextPositionsToUpdate.addMany(invalidatedPositions);
35833
+ const invalidatedPositions = this.getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
35834
+ invalidatedPositions.delete({ sheetId, zone: resultZone });
35835
+ this.nextRangesToUpdate.addMany(invalidatedPositions);
35524
35836
  }
35525
35837
  assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
35526
35838
  const numberOfCols = this.getters.getNumberCols(sheetId);
@@ -35595,7 +35907,7 @@
35595
35907
  }
35596
35908
  const sheetId = position.sheetId;
35597
35909
  this.invalidatePositionsDependingOnSpread(sheetId, zone);
35598
- this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
35910
+ this.nextRangesToUpdate.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
35599
35911
  }
35600
35912
  /**
35601
35913
  * Wraps a GetSymbolValue function to add cycle detection
@@ -35630,13 +35942,8 @@
35630
35942
  }
35631
35943
  return cell.compiledFormula.dependencies;
35632
35944
  }
35633
- getCellsDependingOn(positions) {
35634
- const ranges = [];
35635
- const zonesBySheetIds = aggregatePositionsToZones(positions);
35636
- for (const sheetId in zonesBySheetIds) {
35637
- ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
35638
- }
35639
- return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
35945
+ getCellsDependingOn(ranges) {
35946
+ return this.formulaDependencies().getCellsDependingOn(ranges, this.nextRangesToUpdate);
35640
35947
  }
35641
35948
  }
35642
35949
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -40136,18 +40443,8 @@
40136
40443
  return EMPTY_PIVOT_CELL;
40137
40444
  }
40138
40445
  if (functionName === "PIVOT") {
40139
- const includeTotal = toScalar(args[2]);
40140
- const shouldIncludeTotal = includeTotal === undefined ? true : toBoolean(includeTotal);
40141
- const includeColumnHeaders = toScalar(args[3]);
40142
- const includeMeasures = toScalar(args[5]);
40143
- const shouldIncludeMeasures = includeMeasures === undefined ? true : toBoolean(includeMeasures);
40144
- const shouldIncludeColumnHeaders = includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
40145
- const visibilityOptions = {
40146
- displayColumnHeaders: shouldIncludeColumnHeaders,
40147
- displayTotals: shouldIncludeTotal,
40148
- displayMeasuresRow: shouldIncludeMeasures,
40149
- };
40150
- const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(visibilityOptions);
40446
+ 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());
40447
+ const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(pivotStyle);
40151
40448
  const pivotCol = position.col - mainPosition.col;
40152
40449
  const pivotRow = position.row - mainPosition.row;
40153
40450
  return pivotCells[pivotCol][pivotRow];
@@ -49877,7 +50174,6 @@
49877
50174
  const startSnapshot = performance.now();
49878
50175
  console.debug("Snapshot requested");
49879
50176
  this.session.snapshot(this.exportData());
49880
- this.garbageCollectExternalResources();
49881
50177
  console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
49882
50178
  }
49883
50179
  console.debug("Model created in", performance.now() - start, "ms");
@@ -50263,11 +50559,6 @@
50263
50559
  data = deepCopy(data);
50264
50560
  return getXLSX(data);
50265
50561
  }
50266
- garbageCollectExternalResources() {
50267
- for (const plugin of this.corePlugins) {
50268
- plugin.garbageCollectExternalResources();
50269
- }
50270
- }
50271
50562
  }
50272
50563
  function createCommand(type, payload = {}) {
50273
50564
  const command = deepCopy(payload);
@@ -50411,8 +50702,8 @@
50411
50702
 
50412
50703
 
50413
50704
  __info__.version = "19.1.0-alpha.3";
50414
- __info__.date = "2025-10-17T11:08:54.279Z";
50415
- __info__.hash = "a11279d";
50705
+ __info__.date = "2025-10-23T11:12:13.207Z";
50706
+ __info__.hash = "bd756dd";
50416
50707
 
50417
50708
 
50418
50709
  })(this.o_spreadsheet_engine = this.o_spreadsheet_engine || {});