@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.
- package/dist/o-spreadsheet-engine.d.ts +19 -22
- package/dist/o-spreadsheet-engine.esm.js +489 -198
- package/dist/o-spreadsheet-engine.iife.js +489 -198
- package/dist/o-spreadsheet-engine.min.iife.js +313 -313
- package/dist/o-spreadsheet.d.ts +259 -171
- package/dist/o_spreadsheet.esm.js +804 -424
- package/dist/o_spreadsheet.iife.js +804 -424
- package/dist/o_spreadsheet.min.iife.js +317 -317
- package/dist/o_spreadsheet.xml +147 -37
- package/package.json +1 -1
|
@@ -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-
|
|
7
|
-
* @hash
|
|
6
|
+
* @date 2025-10-23T11:12:13.207Z
|
|
7
|
+
* @hash bd756dd
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
class FunctionCodeBuilder {
|
|
@@ -908,8 +908,17 @@ function isObjectEmptyRecursive(argument) {
|
|
|
908
908
|
/**
|
|
909
909
|
* Returns a function, that, as long as it continues to be invoked, will not
|
|
910
910
|
* be triggered. The function will be called after it stops being called for
|
|
911
|
-
* N milliseconds. If `immediate` is passed,
|
|
912
|
-
*
|
|
911
|
+
* N milliseconds. If `immediate` is passed, the function is called is called
|
|
912
|
+
* immediately on the first call and the debouncing is triggered starting the second
|
|
913
|
+
* call in the defined time window.
|
|
914
|
+
*
|
|
915
|
+
* Example:
|
|
916
|
+
* debouncedFunction = debounce(() => console.log('Hello!'), 250);
|
|
917
|
+
* debouncedFunction(); debouncedFunction(); // Will log 'Hello!' after 250ms
|
|
918
|
+
*
|
|
919
|
+
* debouncedFunction = debounce(() => console.log('Hello!'), 250, true);
|
|
920
|
+
* debouncedFunction(); debouncedFunction(); // Will log 'Hello!' and relog it after 250ms
|
|
921
|
+
*
|
|
913
922
|
*
|
|
914
923
|
* Also decorate the argument function with two methods: stopDebounce and isDebouncePending.
|
|
915
924
|
*
|
|
@@ -917,21 +926,21 @@ function isObjectEmptyRecursive(argument) {
|
|
|
917
926
|
*/
|
|
918
927
|
function debounce(func, wait, immediate) {
|
|
919
928
|
let timeout = undefined;
|
|
929
|
+
let firstCalled = false;
|
|
920
930
|
const debounced = function () {
|
|
921
931
|
const context = this;
|
|
922
932
|
const args = Array.from(arguments);
|
|
933
|
+
if (!firstCalled && immediate) {
|
|
934
|
+
firstCalled = true;
|
|
935
|
+
return func.apply(context, args);
|
|
936
|
+
}
|
|
923
937
|
function later() {
|
|
924
938
|
timeout = undefined;
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
}
|
|
939
|
+
firstCalled = false;
|
|
940
|
+
func.apply(context, args);
|
|
928
941
|
}
|
|
929
|
-
const callNow = immediate && !timeout;
|
|
930
942
|
clearTimeout(timeout);
|
|
931
943
|
timeout = setTimeout(later, wait);
|
|
932
|
-
if (callNow) {
|
|
933
|
-
func.apply(context, args);
|
|
934
|
-
}
|
|
935
944
|
};
|
|
936
945
|
debounced.isDebouncePending = () => timeout !== undefined;
|
|
937
946
|
debounced.stopDebounce = () => {
|
|
@@ -5182,6 +5191,29 @@ profilesStartingPosition, profiles, zones, toRemove = false) {
|
|
|
5182
5191
|
removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
|
|
5183
5192
|
}
|
|
5184
5193
|
}
|
|
5194
|
+
function profilesContainsZone(profilesStartingPosition, profiles, zone) {
|
|
5195
|
+
const leftValue = zone.left;
|
|
5196
|
+
const rightValue = zone.right;
|
|
5197
|
+
const topValue = zone.top;
|
|
5198
|
+
const bottomValue = zone.bottom + 1;
|
|
5199
|
+
const leftIndex = binaryPredecessorSearch(profilesStartingPosition, leftValue, 0);
|
|
5200
|
+
const rightIndex = binaryPredecessorSearch(profilesStartingPosition, rightValue, leftIndex);
|
|
5201
|
+
if (leftIndex === -1 || rightIndex === -1) {
|
|
5202
|
+
return false;
|
|
5203
|
+
}
|
|
5204
|
+
for (let i = leftIndex; i <= rightIndex; i++) {
|
|
5205
|
+
const profile = profiles.get(profilesStartingPosition[i]);
|
|
5206
|
+
const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, true);
|
|
5207
|
+
const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, true);
|
|
5208
|
+
if (topPredIndex === -1 || topPredIndex % 2 !== 0) {
|
|
5209
|
+
return false;
|
|
5210
|
+
}
|
|
5211
|
+
if (topValue < profile[topPredIndex] || bottomValue > profile[bottomSuccIndex]) {
|
|
5212
|
+
return false;
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
return true;
|
|
5216
|
+
}
|
|
5185
5217
|
function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
|
|
5186
5218
|
if (value === undefined) {
|
|
5187
5219
|
// this is only the case when the value correspond to a bottom value that could be undefined
|
|
@@ -5266,7 +5298,18 @@ function modifyProfile(profile, zone, toRemove = false) {
|
|
|
5266
5298
|
}
|
|
5267
5299
|
// add the top and bottom value to the profile and
|
|
5268
5300
|
// remove all information between the top and bottom index
|
|
5269
|
-
|
|
5301
|
+
const toDelete = bottomSuccIndex - topPredIndex - 1;
|
|
5302
|
+
const toInsert = newPoints.length;
|
|
5303
|
+
const start = topPredIndex + 1;
|
|
5304
|
+
// fast path and slow path
|
|
5305
|
+
if (start === profile.length - 1 && toDelete === 1 && toInsert === 1) {
|
|
5306
|
+
// fast path: we just need to replace the last element
|
|
5307
|
+
profile[start] = newPoints[0] ?? newPoints[1];
|
|
5308
|
+
}
|
|
5309
|
+
else {
|
|
5310
|
+
// equivalent but slower and with memory allocation
|
|
5311
|
+
profile.splice(start, toDelete, ...newPoints);
|
|
5312
|
+
}
|
|
5270
5313
|
}
|
|
5271
5314
|
function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
|
|
5272
5315
|
const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
|
|
@@ -5305,8 +5348,10 @@ function constructZonesFromProfiles(profilesStartingPosition, profiles) {
|
|
|
5305
5348
|
left,
|
|
5306
5349
|
bottom,
|
|
5307
5350
|
right,
|
|
5308
|
-
hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
|
|
5309
5351
|
};
|
|
5352
|
+
if ((bottom === undefined && top !== 0) || (right === undefined && left !== 0)) {
|
|
5353
|
+
profileZone.hasHeader = true;
|
|
5354
|
+
}
|
|
5310
5355
|
let findCorrespondingZone = false;
|
|
5311
5356
|
for (let j = pendingZones.length - 1; j >= 0; j--) {
|
|
5312
5357
|
const pendingZone = pendingZones[j];
|
|
@@ -5761,17 +5806,6 @@ function excludeTopLeft(zone) {
|
|
|
5761
5806
|
}
|
|
5762
5807
|
return [leftColumnZone, rightPartZone];
|
|
5763
5808
|
}
|
|
5764
|
-
function aggregatePositionsToZones(positions) {
|
|
5765
|
-
const result = {};
|
|
5766
|
-
for (const position of positions) {
|
|
5767
|
-
result[position.sheetId] ??= [];
|
|
5768
|
-
result[position.sheetId].push(positionToZone(position));
|
|
5769
|
-
}
|
|
5770
|
-
for (const sheetId in result) {
|
|
5771
|
-
result[sheetId] = recomputeZones(result[sheetId]);
|
|
5772
|
-
}
|
|
5773
|
-
return result;
|
|
5774
|
-
}
|
|
5775
5809
|
/**
|
|
5776
5810
|
* Array of all positions in the zone.
|
|
5777
5811
|
*/
|
|
@@ -14520,6 +14554,13 @@ pivotTimeAdapterRegistry
|
|
|
14520
14554
|
.add("minute_number", nullHandlerDecorator(minuteNumberAdapter))
|
|
14521
14555
|
.add("second_number", nullHandlerDecorator(secondNumberAdapter));
|
|
14522
14556
|
|
|
14557
|
+
const DEFAULT_PIVOT_STYLE = {
|
|
14558
|
+
displayTotals: true,
|
|
14559
|
+
displayColumnHeaders: true,
|
|
14560
|
+
displayMeasuresRow: true,
|
|
14561
|
+
numberOfRows: Number.MAX_VALUE,
|
|
14562
|
+
numberOfColumns: Number.MAX_VALUE,
|
|
14563
|
+
};
|
|
14523
14564
|
const AGGREGATOR_NAMES = {
|
|
14524
14565
|
count: _t("Count"),
|
|
14525
14566
|
count_distinct: _t("Count Distinct"),
|
|
@@ -14815,6 +14856,25 @@ function togglePivotCollapse(position, env) {
|
|
|
14815
14856
|
pivot: { ...definition, collapsedDomains: newDomains },
|
|
14816
14857
|
});
|
|
14817
14858
|
}
|
|
14859
|
+
function getPivotStyleFromFnArgs(definition, rowCountArg, includeTotalArg, includeColumnHeadersArg, columnCountArg, includeMeasuresRowArg, locale) {
|
|
14860
|
+
const style = definition.style;
|
|
14861
|
+
const numberOfRows = rowCountArg !== undefined
|
|
14862
|
+
? toNumber(rowCountArg, locale)
|
|
14863
|
+
: style?.numberOfRows ?? DEFAULT_PIVOT_STYLE.numberOfRows;
|
|
14864
|
+
const numberOfColumns = columnCountArg !== undefined
|
|
14865
|
+
? toNumber(columnCountArg, locale)
|
|
14866
|
+
: style?.numberOfColumns ?? DEFAULT_PIVOT_STYLE.numberOfColumns;
|
|
14867
|
+
const displayTotals = includeTotalArg !== undefined
|
|
14868
|
+
? toBoolean(includeTotalArg)
|
|
14869
|
+
: style?.displayTotals ?? DEFAULT_PIVOT_STYLE.displayTotals;
|
|
14870
|
+
const displayColumnHeaders = includeColumnHeadersArg !== undefined
|
|
14871
|
+
? toBoolean(includeColumnHeadersArg)
|
|
14872
|
+
: style?.displayColumnHeaders ?? DEFAULT_PIVOT_STYLE.displayColumnHeaders;
|
|
14873
|
+
const displayMeasuresRow = includeMeasuresRowArg !== undefined
|
|
14874
|
+
? toBoolean(includeMeasuresRowArg)
|
|
14875
|
+
: style?.displayMeasuresRow ?? DEFAULT_PIVOT_STYLE.displayMeasuresRow;
|
|
14876
|
+
return { numberOfRows, numberOfColumns, displayTotals, displayColumnHeaders, displayMeasuresRow };
|
|
14877
|
+
}
|
|
14818
14878
|
|
|
14819
14879
|
/**
|
|
14820
14880
|
* Get the pivot ID from the formula pivot ID.
|
|
@@ -15429,24 +15489,18 @@ const PIVOT = {
|
|
|
15429
15489
|
arg("column_count (number, optional)", _t("number of columns")),
|
|
15430
15490
|
arg("include_measure_titles (boolean, default=TRUE)", _t("Whether to include the measure titles row or not.")),
|
|
15431
15491
|
],
|
|
15432
|
-
compute: function (pivotFormulaId, rowCount
|
|
15492
|
+
compute: function (pivotFormulaId, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles) {
|
|
15433
15493
|
const _pivotFormulaId = toString(pivotFormulaId);
|
|
15434
|
-
const
|
|
15435
|
-
|
|
15494
|
+
const pivotId = getPivotId(_pivotFormulaId, this.getters);
|
|
15495
|
+
const pivot = this.getters.getPivot(pivotId);
|
|
15496
|
+
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
15497
|
+
const pivotStyle = getPivotStyleFromFnArgs(coreDefinition, rowCount, includeTotal, includeColumnHeaders, columnCount, includeMeasureTitles, this.locale);
|
|
15498
|
+
if (pivotStyle.numberOfRows < 0) {
|
|
15436
15499
|
return new EvaluationError(_t("The number of rows must be positive."));
|
|
15437
15500
|
}
|
|
15438
|
-
|
|
15439
|
-
if (_columnCount < 0) {
|
|
15501
|
+
if (pivotStyle.numberOfColumns < 0) {
|
|
15440
15502
|
return new EvaluationError(_t("The number of columns must be positive."));
|
|
15441
15503
|
}
|
|
15442
|
-
const visibilityOptions = {
|
|
15443
|
-
displayColumnHeaders: toBoolean(includeColumnHeaders),
|
|
15444
|
-
displayTotals: toBoolean(includeTotal),
|
|
15445
|
-
displayMeasuresRow: toBoolean(includeMeasureTitles),
|
|
15446
|
-
};
|
|
15447
|
-
const pivotId = getPivotId(_pivotFormulaId, this.getters);
|
|
15448
|
-
const pivot = this.getters.getPivot(pivotId);
|
|
15449
|
-
const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
|
|
15450
15504
|
addPivotDependencies(this, coreDefinition, coreDefinition.measures);
|
|
15451
15505
|
pivot.init({ reload: pivot.needsReevaluation });
|
|
15452
15506
|
const error = pivot.assertIsValid({ throwOnError: false });
|
|
@@ -15457,20 +15511,20 @@ const PIVOT = {
|
|
|
15457
15511
|
if (table.numberOfCells > PIVOT_MAX_NUMBER_OF_CELLS) {
|
|
15458
15512
|
return new EvaluationError(getPivotTooBigErrorMessage(table.numberOfCells, this.locale));
|
|
15459
15513
|
}
|
|
15460
|
-
const cells = table.getPivotCells(
|
|
15514
|
+
const cells = table.getPivotCells(pivotStyle);
|
|
15461
15515
|
let headerRows = 0;
|
|
15462
|
-
if (
|
|
15516
|
+
if (pivotStyle.displayColumnHeaders) {
|
|
15463
15517
|
headerRows = table.columns.length - 1;
|
|
15464
15518
|
}
|
|
15465
|
-
if (
|
|
15519
|
+
if (pivotStyle.displayMeasuresRow) {
|
|
15466
15520
|
headerRows++;
|
|
15467
15521
|
}
|
|
15468
15522
|
const pivotTitle = this.getters.getPivotName(pivotId);
|
|
15469
|
-
const tableHeight = Math.min(headerRows +
|
|
15523
|
+
const tableHeight = Math.min(headerRows + pivotStyle.numberOfRows, cells[0].length);
|
|
15470
15524
|
if (tableHeight === 0) {
|
|
15471
15525
|
return [[{ value: pivotTitle }]];
|
|
15472
15526
|
}
|
|
15473
|
-
const tableWidth = Math.min(1 +
|
|
15527
|
+
const tableWidth = Math.min(1 + pivotStyle.numberOfColumns, cells.length);
|
|
15474
15528
|
const result = [];
|
|
15475
15529
|
for (const col of range(0, tableWidth)) {
|
|
15476
15530
|
result[col] = [];
|
|
@@ -15493,7 +15547,7 @@ const PIVOT = {
|
|
|
15493
15547
|
}
|
|
15494
15548
|
}
|
|
15495
15549
|
}
|
|
15496
|
-
if (
|
|
15550
|
+
if (pivotStyle.displayColumnHeaders || pivotStyle.displayMeasuresRow) {
|
|
15497
15551
|
result[0][0] = { value: pivotTitle };
|
|
15498
15552
|
}
|
|
15499
15553
|
return result;
|
|
@@ -17965,6 +18019,10 @@ function getRangeParts(xc, zone) {
|
|
|
17965
18019
|
}
|
|
17966
18020
|
return parts;
|
|
17967
18021
|
}
|
|
18022
|
+
function positionToBoundedRange(position) {
|
|
18023
|
+
const zone = { left: position.col, top: position.row, right: position.col, bottom: position.row };
|
|
18024
|
+
return { sheetId: position.sheetId, zone };
|
|
18025
|
+
}
|
|
17968
18026
|
/**
|
|
17969
18027
|
* Check that a zone is valid regarding the order of top-bottom and left-right.
|
|
17970
18028
|
* Left should be smaller than right, top should be smaller than bottom.
|
|
@@ -26653,6 +26711,7 @@ function forceUnicityOfFigure(data) {
|
|
|
26653
26711
|
return data;
|
|
26654
26712
|
}
|
|
26655
26713
|
const figureIds = new Set();
|
|
26714
|
+
const chartIds = new Set();
|
|
26656
26715
|
const uuidGenerator = new UuidGenerator();
|
|
26657
26716
|
for (const sheet of data.sheets || []) {
|
|
26658
26717
|
for (const figure of sheet.figures || []) {
|
|
@@ -26660,6 +26719,12 @@ function forceUnicityOfFigure(data) {
|
|
|
26660
26719
|
figure.id += uuidGenerator.smallUuid();
|
|
26661
26720
|
}
|
|
26662
26721
|
figureIds.add(figure.id);
|
|
26722
|
+
if (figure.tag === "chart") {
|
|
26723
|
+
if (chartIds.has(figure.data?.chartId)) {
|
|
26724
|
+
figure.data.chartId += uuidGenerator.smallUuid();
|
|
26725
|
+
}
|
|
26726
|
+
chartIds.add(figure.data?.chartId);
|
|
26727
|
+
}
|
|
26663
26728
|
}
|
|
26664
26729
|
}
|
|
26665
26730
|
data.uniqueFigureIds = true;
|
|
@@ -27027,11 +27092,6 @@ class CorePlugin extends BasePlugin {
|
|
|
27027
27092
|
* @param sheetName couple of old and new sheet names to adapt ranges pointing to that sheet
|
|
27028
27093
|
*/
|
|
27029
27094
|
adaptRanges(applyChange, sheetId, sheetName) { }
|
|
27030
|
-
/**
|
|
27031
|
-
* Implement this method to clean unused external resources, such as images
|
|
27032
|
-
* stored on a server which have been deleted.
|
|
27033
|
-
*/
|
|
27034
|
-
garbageCollectExternalResources() { }
|
|
27035
27095
|
}
|
|
27036
27096
|
|
|
27037
27097
|
class BordersPlugin extends CorePlugin {
|
|
@@ -30875,17 +30935,6 @@ class ImagePlugin extends CorePlugin {
|
|
|
30875
30935
|
break;
|
|
30876
30936
|
}
|
|
30877
30937
|
}
|
|
30878
|
-
/**
|
|
30879
|
-
* Delete unused images from the file store
|
|
30880
|
-
*/
|
|
30881
|
-
garbageCollectExternalResources() {
|
|
30882
|
-
const images = new Set(this.getAllImages().map((image) => image.path));
|
|
30883
|
-
for (const path of this.syncedImages) {
|
|
30884
|
-
if (!images.has(path)) {
|
|
30885
|
-
this.fileStore?.delete(path);
|
|
30886
|
-
}
|
|
30887
|
-
}
|
|
30888
|
-
}
|
|
30889
30938
|
// ---------------------------------------------------------------------------
|
|
30890
30939
|
// Getters
|
|
30891
30940
|
// ---------------------------------------------------------------------------
|
|
@@ -30947,13 +30996,6 @@ class ImagePlugin extends CorePlugin {
|
|
|
30947
30996
|
sheet.images = [...sheet.images, ...images];
|
|
30948
30997
|
}
|
|
30949
30998
|
}
|
|
30950
|
-
getAllImages() {
|
|
30951
|
-
const images = [];
|
|
30952
|
-
for (const sheetId in this.images) {
|
|
30953
|
-
images.push(...Object.values(this.images[sheetId] || {}).filter(isDefined));
|
|
30954
|
-
}
|
|
30955
|
-
return images;
|
|
30956
|
-
}
|
|
30957
30999
|
}
|
|
30958
31000
|
|
|
30959
31001
|
class MergePlugin extends CorePlugin {
|
|
@@ -31729,26 +31771,22 @@ class SpreadsheetPivotTable {
|
|
|
31729
31771
|
getNumberOfDataColumns() {
|
|
31730
31772
|
return this.columns.at(-1)?.length || 0;
|
|
31731
31773
|
}
|
|
31732
|
-
getSkippedRows(
|
|
31774
|
+
getSkippedRows(pivotStyle) {
|
|
31733
31775
|
const skippedRows = new Set();
|
|
31734
|
-
if (!
|
|
31776
|
+
if (!pivotStyle.displayColumnHeaders) {
|
|
31735
31777
|
for (let i = 0; i < this.columns.length - 1; i++) {
|
|
31736
31778
|
skippedRows.add(i);
|
|
31737
31779
|
}
|
|
31738
31780
|
}
|
|
31739
|
-
if (!
|
|
31781
|
+
if (!pivotStyle.displayMeasuresRow) {
|
|
31740
31782
|
skippedRows.add(this.columns.length - 1);
|
|
31741
31783
|
}
|
|
31742
31784
|
return skippedRows;
|
|
31743
31785
|
}
|
|
31744
|
-
getPivotCells(
|
|
31745
|
-
|
|
31746
|
-
displayTotals: true,
|
|
31747
|
-
displayMeasuresRow: true,
|
|
31748
|
-
}) {
|
|
31749
|
-
const key = JSON.stringify(visibilityOptions);
|
|
31786
|
+
getPivotCells(pivotStyle = DEFAULT_PIVOT_STYLE) {
|
|
31787
|
+
const key = JSON.stringify(pivotStyle);
|
|
31750
31788
|
if (!this.pivotCells[key]) {
|
|
31751
|
-
const { displayTotals } =
|
|
31789
|
+
const { displayTotals } = pivotStyle;
|
|
31752
31790
|
const numberOfDataRows = this.rows.length;
|
|
31753
31791
|
const numberOfDataColumns = this.getNumberOfDataColumns();
|
|
31754
31792
|
let pivotHeight = this.columns.length + numberOfDataRows;
|
|
@@ -31760,7 +31798,7 @@ class SpreadsheetPivotTable {
|
|
|
31760
31798
|
pivotWidth -= this.measures.length;
|
|
31761
31799
|
}
|
|
31762
31800
|
const domainArray = [];
|
|
31763
|
-
const skippedRows = this.getSkippedRows(
|
|
31801
|
+
const skippedRows = this.getSkippedRows(pivotStyle);
|
|
31764
31802
|
for (let col = 0; col < pivotWidth; col++) {
|
|
31765
31803
|
domainArray.push([]);
|
|
31766
31804
|
for (let row = 0; row < pivotHeight; row++) {
|
|
@@ -34879,6 +34917,281 @@ class ZoneRBush extends RBush {
|
|
|
34879
34917
|
}
|
|
34880
34918
|
}
|
|
34881
34919
|
|
|
34920
|
+
class ZoneSet {
|
|
34921
|
+
profilesStartingPosition = [0];
|
|
34922
|
+
profiles = new Map([[0, []]]);
|
|
34923
|
+
constructor(zones = []) {
|
|
34924
|
+
for (const zone of zones) {
|
|
34925
|
+
this.add(zone);
|
|
34926
|
+
}
|
|
34927
|
+
}
|
|
34928
|
+
isEmpty() {
|
|
34929
|
+
return this.profiles.size === 1 && this.profiles.get(0)?.length === 0;
|
|
34930
|
+
}
|
|
34931
|
+
add(zone) {
|
|
34932
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone]);
|
|
34933
|
+
}
|
|
34934
|
+
delete(zone) {
|
|
34935
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone], true);
|
|
34936
|
+
}
|
|
34937
|
+
has(zone) {
|
|
34938
|
+
return profilesContainsZone(this.profilesStartingPosition, this.profiles, zone);
|
|
34939
|
+
}
|
|
34940
|
+
difference(other) {
|
|
34941
|
+
const result = this.copy();
|
|
34942
|
+
for (const zone of other) {
|
|
34943
|
+
result.delete(zone);
|
|
34944
|
+
}
|
|
34945
|
+
return result;
|
|
34946
|
+
}
|
|
34947
|
+
copy() {
|
|
34948
|
+
const result = new ZoneSet();
|
|
34949
|
+
result.profilesStartingPosition = [...this.profilesStartingPosition];
|
|
34950
|
+
result.profiles = new Map();
|
|
34951
|
+
for (const [key, value] of this.profiles) {
|
|
34952
|
+
result.profiles.set(key, [...value]);
|
|
34953
|
+
}
|
|
34954
|
+
return result;
|
|
34955
|
+
}
|
|
34956
|
+
size() {
|
|
34957
|
+
let size = 0;
|
|
34958
|
+
for (const profile of this.profiles.values()) {
|
|
34959
|
+
size += profile.length;
|
|
34960
|
+
}
|
|
34961
|
+
return size / 2;
|
|
34962
|
+
}
|
|
34963
|
+
/**
|
|
34964
|
+
* iterator of all the zones in the ZoneSet
|
|
34965
|
+
*/
|
|
34966
|
+
[Symbol.iterator]() {
|
|
34967
|
+
return constructZonesFromProfiles(this.profilesStartingPosition, this.profiles)[Symbol.iterator]();
|
|
34968
|
+
}
|
|
34969
|
+
}
|
|
34970
|
+
|
|
34971
|
+
class RangeSet {
|
|
34972
|
+
setsBySheetId = {};
|
|
34973
|
+
constructor(ranges = []) {
|
|
34974
|
+
for (const range of ranges) {
|
|
34975
|
+
this.add(range);
|
|
34976
|
+
}
|
|
34977
|
+
}
|
|
34978
|
+
add(range) {
|
|
34979
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
34980
|
+
this.setsBySheetId[range.sheetId] = new ZoneSet();
|
|
34981
|
+
}
|
|
34982
|
+
this.setsBySheetId[range.sheetId].add(range.zone);
|
|
34983
|
+
}
|
|
34984
|
+
addMany(ranges) {
|
|
34985
|
+
for (const range of ranges) {
|
|
34986
|
+
this.add(range);
|
|
34987
|
+
}
|
|
34988
|
+
}
|
|
34989
|
+
addPosition(position) {
|
|
34990
|
+
this.add(positionToBoundedRange(position));
|
|
34991
|
+
}
|
|
34992
|
+
addManyPositions(positions) {
|
|
34993
|
+
for (const position of positions) {
|
|
34994
|
+
this.addPosition(position);
|
|
34995
|
+
}
|
|
34996
|
+
}
|
|
34997
|
+
has(range) {
|
|
34998
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
34999
|
+
return false;
|
|
35000
|
+
}
|
|
35001
|
+
return this.setsBySheetId[range.sheetId].has(range.zone);
|
|
35002
|
+
}
|
|
35003
|
+
hasPosition(position) {
|
|
35004
|
+
return this.has(positionToBoundedRange(position));
|
|
35005
|
+
}
|
|
35006
|
+
delete(range) {
|
|
35007
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
35008
|
+
return;
|
|
35009
|
+
}
|
|
35010
|
+
this.setsBySheetId[range.sheetId].delete(range.zone);
|
|
35011
|
+
}
|
|
35012
|
+
deleteMany(ranges) {
|
|
35013
|
+
for (const range of ranges) {
|
|
35014
|
+
this.delete(range);
|
|
35015
|
+
}
|
|
35016
|
+
}
|
|
35017
|
+
deleteManyPositions(positions) {
|
|
35018
|
+
for (const position of positions) {
|
|
35019
|
+
this.delete(positionToBoundedRange(position));
|
|
35020
|
+
}
|
|
35021
|
+
}
|
|
35022
|
+
difference(other) {
|
|
35023
|
+
const result = new RangeSet();
|
|
35024
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35025
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId];
|
|
35026
|
+
}
|
|
35027
|
+
for (const sheetId in other.setsBySheetId) {
|
|
35028
|
+
if (result.setsBySheetId[sheetId]) {
|
|
35029
|
+
result.setsBySheetId[sheetId] = result.setsBySheetId[sheetId].difference(other.setsBySheetId[sheetId]);
|
|
35030
|
+
}
|
|
35031
|
+
}
|
|
35032
|
+
return result;
|
|
35033
|
+
}
|
|
35034
|
+
copy() {
|
|
35035
|
+
const result = new RangeSet();
|
|
35036
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35037
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId].copy();
|
|
35038
|
+
}
|
|
35039
|
+
return result;
|
|
35040
|
+
}
|
|
35041
|
+
clear() {
|
|
35042
|
+
this.setsBySheetId = {};
|
|
35043
|
+
}
|
|
35044
|
+
size() {
|
|
35045
|
+
let size = 0;
|
|
35046
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35047
|
+
size += this.setsBySheetId[sheetId].size();
|
|
35048
|
+
}
|
|
35049
|
+
return size;
|
|
35050
|
+
}
|
|
35051
|
+
isEmpty() {
|
|
35052
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35053
|
+
if (!this.setsBySheetId[sheetId].isEmpty()) {
|
|
35054
|
+
return false;
|
|
35055
|
+
}
|
|
35056
|
+
}
|
|
35057
|
+
return true;
|
|
35058
|
+
}
|
|
35059
|
+
/**
|
|
35060
|
+
* iterator of all the ranges in the RangeSet
|
|
35061
|
+
*/
|
|
35062
|
+
[Symbol.iterator]() {
|
|
35063
|
+
const result = [];
|
|
35064
|
+
for (const sheetId in this.setsBySheetId) {
|
|
35065
|
+
for (const zone of this.setsBySheetId[sheetId]) {
|
|
35066
|
+
result.push({ sheetId: sheetId, zone });
|
|
35067
|
+
}
|
|
35068
|
+
}
|
|
35069
|
+
return result[Symbol.iterator]();
|
|
35070
|
+
}
|
|
35071
|
+
}
|
|
35072
|
+
|
|
35073
|
+
/**
|
|
35074
|
+
* R-Tree of ranges, mapping zones (r-tree bounding boxes) to ranges (data of the r-tree item).
|
|
35075
|
+
* Ranges associated to the exact same bounding box are grouped together
|
|
35076
|
+
* to reduce the number of nodes in the R-tree.
|
|
35077
|
+
*/
|
|
35078
|
+
class DependenciesRTree {
|
|
35079
|
+
rTree;
|
|
35080
|
+
constructor(items = []) {
|
|
35081
|
+
const compactedBoxes = groupSameBoundingBoxes(items);
|
|
35082
|
+
this.rTree = new SpreadsheetRTree(compactedBoxes);
|
|
35083
|
+
}
|
|
35084
|
+
insert(item) {
|
|
35085
|
+
const data = this.rTree.search(item.boundingBox);
|
|
35086
|
+
const itemBoundingBox = item.boundingBox;
|
|
35087
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
35088
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
35089
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
35090
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
35091
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
35092
|
+
if (exactBoundingBox) {
|
|
35093
|
+
exactBoundingBox.data.add(item.data);
|
|
35094
|
+
}
|
|
35095
|
+
else {
|
|
35096
|
+
this.rTree.insert({ ...item, data: new RangeSet([item.data]) });
|
|
35097
|
+
}
|
|
35098
|
+
}
|
|
35099
|
+
search({ zone, sheetId }) {
|
|
35100
|
+
const results = new RangeSet();
|
|
35101
|
+
for (const { data } of this.rTree.search({ zone, sheetId })) {
|
|
35102
|
+
results.addMany(data);
|
|
35103
|
+
}
|
|
35104
|
+
return results;
|
|
35105
|
+
}
|
|
35106
|
+
remove(item) {
|
|
35107
|
+
const data = this.rTree.search(item.boundingBox);
|
|
35108
|
+
const itemBoundingBox = item.boundingBox;
|
|
35109
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
35110
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
35111
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
35112
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
35113
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
35114
|
+
if (exactBoundingBox) {
|
|
35115
|
+
exactBoundingBox.data.delete(item.data);
|
|
35116
|
+
}
|
|
35117
|
+
else {
|
|
35118
|
+
this.rTree.remove({ ...item, data: new RangeSet([item.data]) });
|
|
35119
|
+
}
|
|
35120
|
+
}
|
|
35121
|
+
}
|
|
35122
|
+
/**
|
|
35123
|
+
* Group together all formulas pointing to the exact same dependency (bounding box).
|
|
35124
|
+
* The goal is to optimize the following case:
|
|
35125
|
+
* - if any cell in B1:B1000 changes, C1 must be recomputed
|
|
35126
|
+
* - if any cell in B1:B1000 changes, C2 must be recomputed
|
|
35127
|
+
* - if any cell in B1:B1000 changes, C3 must be recomputed
|
|
35128
|
+
* ...
|
|
35129
|
+
* - if any cell in B1:B1000 changes, C1000 must be recomputed
|
|
35130
|
+
*
|
|
35131
|
+
* Instead of having 1000 entries in the R-tree, we want to have a single entry
|
|
35132
|
+
* with B1:B1000 (bounding box) pointing to C1:C1000 (formulas).
|
|
35133
|
+
*/
|
|
35134
|
+
function groupSameBoundingBoxes(items) {
|
|
35135
|
+
// Important: this function must be as fast as possible. It is on the evaluation hot path.
|
|
35136
|
+
let maxCol = 0;
|
|
35137
|
+
let maxRow = 0;
|
|
35138
|
+
for (let i = 0; i < items.length; i++) {
|
|
35139
|
+
const zone = items[i].boundingBox.zone;
|
|
35140
|
+
if (zone.right > maxCol) {
|
|
35141
|
+
maxCol = zone.right;
|
|
35142
|
+
}
|
|
35143
|
+
if (zone.bottom > maxRow) {
|
|
35144
|
+
maxRow = zone.bottom;
|
|
35145
|
+
}
|
|
35146
|
+
}
|
|
35147
|
+
maxCol += 1;
|
|
35148
|
+
maxRow += 1;
|
|
35149
|
+
// in most real-world cases, we can use a fast numeric key
|
|
35150
|
+
// but if the zones are too far right or bottom, we fallback to a slower string key
|
|
35151
|
+
const maxPossibleKey = (((maxRow + 1) * maxCol + 1) * maxRow + 1) * maxCol;
|
|
35152
|
+
const useFastKey = maxPossibleKey <= Number.MAX_SAFE_INTEGER;
|
|
35153
|
+
if (!useFastKey) {
|
|
35154
|
+
console.warn("Max col/row size exceeded, using slow zone key");
|
|
35155
|
+
}
|
|
35156
|
+
const groupedByBBox = {};
|
|
35157
|
+
for (const item of items) {
|
|
35158
|
+
const sheetId = item.boundingBox.sheetId;
|
|
35159
|
+
if (!groupedByBBox[sheetId]) {
|
|
35160
|
+
groupedByBBox[sheetId] = {};
|
|
35161
|
+
}
|
|
35162
|
+
const bBox = item.boundingBox.zone;
|
|
35163
|
+
let bBoxKey = 0;
|
|
35164
|
+
if (useFastKey) {
|
|
35165
|
+
bBoxKey =
|
|
35166
|
+
bBox.left +
|
|
35167
|
+
bBox.top * maxCol +
|
|
35168
|
+
bBox.right * maxCol * maxRow +
|
|
35169
|
+
bBox.bottom * maxCol * maxRow * maxCol;
|
|
35170
|
+
}
|
|
35171
|
+
else {
|
|
35172
|
+
bBoxKey = `${bBox.left},${bBox.top},${bBox.right},${bBox.bottom}`;
|
|
35173
|
+
}
|
|
35174
|
+
if (groupedByBBox[sheetId][bBoxKey]) {
|
|
35175
|
+
const ranges = groupedByBBox[sheetId][bBoxKey].data;
|
|
35176
|
+
ranges.add(item.data);
|
|
35177
|
+
}
|
|
35178
|
+
else {
|
|
35179
|
+
groupedByBBox[sheetId][bBoxKey] = {
|
|
35180
|
+
boundingBox: item.boundingBox,
|
|
35181
|
+
data: new RangeSet([item.data]),
|
|
35182
|
+
};
|
|
35183
|
+
}
|
|
35184
|
+
}
|
|
35185
|
+
const result = [];
|
|
35186
|
+
for (const sheetId in groupedByBBox) {
|
|
35187
|
+
const map = groupedByBBox[sheetId];
|
|
35188
|
+
for (const key in map) {
|
|
35189
|
+
result.push(map[key]);
|
|
35190
|
+
}
|
|
35191
|
+
}
|
|
35192
|
+
return result;
|
|
35193
|
+
}
|
|
35194
|
+
|
|
34882
35195
|
/**
|
|
34883
35196
|
* Implementation of a dependency Graph.
|
|
34884
35197
|
* The graph is used to evaluate the cells in the correct
|
|
@@ -34887,12 +35200,10 @@ class ZoneRBush extends RBush {
|
|
|
34887
35200
|
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
34888
35201
|
*/
|
|
34889
35202
|
class FormulaDependencyGraph {
|
|
34890
|
-
createEmptyPositionSet;
|
|
34891
35203
|
dependencies = new PositionMap();
|
|
34892
35204
|
rTree;
|
|
34893
|
-
constructor(
|
|
34894
|
-
this.
|
|
34895
|
-
this.rTree = new SpreadsheetRTree(data);
|
|
35205
|
+
constructor(data = []) {
|
|
35206
|
+
this.rTree = new DependenciesRTree(data);
|
|
34896
35207
|
}
|
|
34897
35208
|
removeAllDependencies(formulaPosition) {
|
|
34898
35209
|
const ranges = this.dependencies.get(formulaPosition);
|
|
@@ -34906,7 +35217,10 @@ class FormulaDependencyGraph {
|
|
|
34906
35217
|
}
|
|
34907
35218
|
addDependencies(formulaPosition, dependencies) {
|
|
34908
35219
|
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
34909
|
-
data:
|
|
35220
|
+
data: {
|
|
35221
|
+
sheetId: formulaPosition.sheetId,
|
|
35222
|
+
zone: positionToZone(formulaPosition),
|
|
35223
|
+
},
|
|
34910
35224
|
boundingBox: {
|
|
34911
35225
|
zone,
|
|
34912
35226
|
sheetId,
|
|
@@ -34924,46 +35238,20 @@ class FormulaDependencyGraph {
|
|
|
34924
35238
|
}
|
|
34925
35239
|
}
|
|
34926
35240
|
/**
|
|
34927
|
-
* Return all the cells that depend on the provided ranges
|
|
34928
|
-
* in the correct order they should be evaluated.
|
|
34929
|
-
* This is called a topological ordering (excluding cycles)
|
|
35241
|
+
* Return all the cells that depend on the provided ranges.
|
|
34930
35242
|
*/
|
|
34931
|
-
getCellsDependingOn(ranges,
|
|
34932
|
-
|
|
35243
|
+
getCellsDependingOn(ranges, visited = new RangeSet()) {
|
|
35244
|
+
visited = visited.copy();
|
|
34933
35245
|
const queue = Array.from(ranges).reverse();
|
|
34934
35246
|
while (queue.length > 0) {
|
|
34935
35247
|
const range = queue.pop();
|
|
34936
|
-
|
|
34937
|
-
const
|
|
34938
|
-
|
|
34939
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
34940
|
-
visited.add({ sheetId, col, row });
|
|
34941
|
-
}
|
|
34942
|
-
}
|
|
34943
|
-
const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
|
|
34944
|
-
const nextInQueue = {};
|
|
34945
|
-
for (const position of impactedPositions) {
|
|
34946
|
-
if (!visited.has(position) && !ignore.has(position)) {
|
|
34947
|
-
if (!nextInQueue[position.sheetId]) {
|
|
34948
|
-
nextInQueue[position.sheetId] = [];
|
|
34949
|
-
}
|
|
34950
|
-
nextInQueue[position.sheetId].push(positionToZone(position));
|
|
34951
|
-
}
|
|
34952
|
-
}
|
|
34953
|
-
for (const sheetId in nextInQueue) {
|
|
34954
|
-
const zones = recomputeZones(nextInQueue[sheetId], []);
|
|
34955
|
-
queue.push(...zones.map((zone) => ({ sheetId, zone })));
|
|
34956
|
-
}
|
|
35248
|
+
visited.add(range);
|
|
35249
|
+
const impactedRanges = this.rTree.search(range);
|
|
35250
|
+
queue.push(...impactedRanges.difference(visited));
|
|
34957
35251
|
}
|
|
34958
35252
|
// remove initial ranges
|
|
34959
35253
|
for (const range of ranges) {
|
|
34960
|
-
|
|
34961
|
-
const sheetId = range.sheetId;
|
|
34962
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
34963
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
34964
|
-
visited.delete({ sheetId, col, row });
|
|
34965
|
-
}
|
|
34966
|
-
}
|
|
35254
|
+
visited.delete(range);
|
|
34967
35255
|
}
|
|
34968
35256
|
return visited;
|
|
34969
35257
|
}
|
|
@@ -35240,7 +35528,7 @@ class Evaluator {
|
|
|
35240
35528
|
getters;
|
|
35241
35529
|
compilationParams;
|
|
35242
35530
|
evaluatedCells = new PositionMap();
|
|
35243
|
-
formulaDependencies = lazy(new FormulaDependencyGraph(
|
|
35531
|
+
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
35244
35532
|
blockedArrayFormulas = new PositionSet({});
|
|
35245
35533
|
spreadingRelations = new SpreadingRelation();
|
|
35246
35534
|
constructor(context, getters) {
|
|
@@ -35275,7 +35563,7 @@ class Evaluator {
|
|
|
35275
35563
|
return undefined;
|
|
35276
35564
|
}
|
|
35277
35565
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
|
|
35278
|
-
return
|
|
35566
|
+
return arrayFormulas.find((position) => !this.blockedArrayFormulas.has(position));
|
|
35279
35567
|
}
|
|
35280
35568
|
updateDependencies(position) {
|
|
35281
35569
|
// removing dependencies is slow because it requires
|
|
@@ -35319,57 +35607,72 @@ class Evaluator {
|
|
|
35319
35607
|
}
|
|
35320
35608
|
evaluateCells(positions) {
|
|
35321
35609
|
const start = performance.now();
|
|
35322
|
-
const
|
|
35323
|
-
|
|
35610
|
+
const rangesToCompute = new RangeSet();
|
|
35611
|
+
rangesToCompute.addManyPositions(positions);
|
|
35324
35612
|
const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
|
|
35325
|
-
|
|
35326
|
-
|
|
35327
|
-
|
|
35328
|
-
this.evaluate(
|
|
35613
|
+
rangesToCompute.addMany(this.getCellsDependingOn(rangesToCompute));
|
|
35614
|
+
rangesToCompute.addMany(arrayFormulasPositions);
|
|
35615
|
+
rangesToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
|
|
35616
|
+
this.evaluate(rangesToCompute);
|
|
35329
35617
|
console.debug("evaluate Cells", performance.now() - start, "ms");
|
|
35330
35618
|
}
|
|
35331
35619
|
getArrayFormulasImpactedByChangesOf(positions) {
|
|
35332
|
-
const
|
|
35620
|
+
const impactedRanges = new RangeSet();
|
|
35333
35621
|
for (const position of positions) {
|
|
35334
35622
|
const content = this.getters.getCell(position)?.content;
|
|
35335
35623
|
const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
|
|
35336
35624
|
if (arrayFormulaPosition !== undefined) {
|
|
35337
35625
|
// take into account new collisions.
|
|
35338
|
-
|
|
35626
|
+
impactedRanges.addPosition(arrayFormulaPosition);
|
|
35339
35627
|
}
|
|
35340
35628
|
if (!content) {
|
|
35341
35629
|
// The previous content could have blocked some array formulas
|
|
35342
|
-
|
|
35630
|
+
impactedRanges.addPosition(position);
|
|
35343
35631
|
}
|
|
35344
35632
|
}
|
|
35345
|
-
const
|
|
35346
|
-
|
|
35347
|
-
for (const zone of zonesBySheetIds[sheetId]) {
|
|
35348
|
-
impactedPositions.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
35349
|
-
}
|
|
35633
|
+
for (const range of [...impactedRanges]) {
|
|
35634
|
+
impactedRanges.addMany(this.getArrayFormulasBlockedBy(range.sheetId, range.zone));
|
|
35350
35635
|
}
|
|
35351
|
-
return
|
|
35636
|
+
return impactedRanges;
|
|
35352
35637
|
}
|
|
35353
35638
|
buildDependencyGraph() {
|
|
35354
35639
|
this.blockedArrayFormulas = this.createEmptyPositionSet();
|
|
35355
35640
|
this.spreadingRelations = new SpreadingRelation();
|
|
35356
35641
|
this.formulaDependencies = lazy(() => {
|
|
35357
|
-
const
|
|
35358
|
-
|
|
35359
|
-
.
|
|
35360
|
-
|
|
35361
|
-
|
|
35362
|
-
|
|
35363
|
-
|
|
35364
|
-
|
|
35365
|
-
|
|
35366
|
-
|
|
35642
|
+
const rTreeItems = [];
|
|
35643
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
35644
|
+
const cells = this.getters.getCells(sheetId);
|
|
35645
|
+
for (const cellId in cells) {
|
|
35646
|
+
const cell = cells[cellId];
|
|
35647
|
+
if (cell.isFormula) {
|
|
35648
|
+
const directDependencies = cell.compiledFormula.dependencies;
|
|
35649
|
+
for (const range of directDependencies) {
|
|
35650
|
+
if (range.invalidSheetName || range.invalidXc) {
|
|
35651
|
+
continue;
|
|
35652
|
+
}
|
|
35653
|
+
rTreeItems.push({
|
|
35654
|
+
data: {
|
|
35655
|
+
sheetId,
|
|
35656
|
+
zone: positionToZone(this.getters.getCellPosition(cellId)),
|
|
35657
|
+
},
|
|
35658
|
+
boundingBox: { sheetId: range.sheetId, zone: range.zone },
|
|
35659
|
+
});
|
|
35660
|
+
}
|
|
35661
|
+
}
|
|
35662
|
+
}
|
|
35663
|
+
}
|
|
35664
|
+
return new FormulaDependencyGraph(rTreeItems);
|
|
35367
35665
|
});
|
|
35368
35666
|
}
|
|
35369
35667
|
evaluateAllCells() {
|
|
35370
35668
|
const start = performance.now();
|
|
35371
35669
|
this.evaluatedCells = new PositionMap();
|
|
35372
|
-
|
|
35670
|
+
const ranges = [];
|
|
35671
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
35672
|
+
const zone = this.getters.getSheetZone(sheetId);
|
|
35673
|
+
ranges.push({ sheetId, zone });
|
|
35674
|
+
}
|
|
35675
|
+
this.evaluate(ranges);
|
|
35373
35676
|
console.debug("evaluate all cells", performance.now() - start, "ms");
|
|
35374
35677
|
}
|
|
35375
35678
|
evaluateFormulaResult(sheetId, formulaString) {
|
|
@@ -35393,48 +35696,47 @@ class Evaluator {
|
|
|
35393
35696
|
return handleError(error, "");
|
|
35394
35697
|
}
|
|
35395
35698
|
}
|
|
35396
|
-
getAllCells() {
|
|
35397
|
-
const positions = this.createEmptyPositionSet();
|
|
35398
|
-
positions.fillAllPositions();
|
|
35399
|
-
return positions;
|
|
35400
|
-
}
|
|
35401
35699
|
/**
|
|
35402
35700
|
* Return the position of formulas blocked by the given positions
|
|
35403
35701
|
* as well as all their dependencies.
|
|
35404
35702
|
*/
|
|
35405
35703
|
getArrayFormulasBlockedBy(sheetId, zone) {
|
|
35406
|
-
const arrayFormulaPositions =
|
|
35704
|
+
const arrayFormulaPositions = new RangeSet();
|
|
35407
35705
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(sheetId, zone);
|
|
35408
|
-
arrayFormulaPositions.
|
|
35706
|
+
arrayFormulaPositions.addManyPositions(arrayFormulas);
|
|
35409
35707
|
const spilledPositions = [...arrayFormulas].filter((position) => !this.blockedArrayFormulas.has(position));
|
|
35410
35708
|
if (spilledPositions.length) {
|
|
35411
35709
|
// ignore the formula spreading on the position. Keep only the blocked ones
|
|
35412
|
-
arrayFormulaPositions.
|
|
35710
|
+
arrayFormulaPositions.deleteManyPositions(spilledPositions);
|
|
35413
35711
|
}
|
|
35414
35712
|
arrayFormulaPositions.addMany(this.getCellsDependingOn(arrayFormulaPositions));
|
|
35415
35713
|
return arrayFormulaPositions;
|
|
35416
35714
|
}
|
|
35417
|
-
|
|
35715
|
+
nextRangesToUpdate = new RangeSet();
|
|
35418
35716
|
cellsBeingComputed = new Set();
|
|
35419
35717
|
symbolsBeingComputed = new Set();
|
|
35420
|
-
evaluate(
|
|
35718
|
+
evaluate(ranges) {
|
|
35421
35719
|
this.cellsBeingComputed = new Set();
|
|
35422
|
-
this.
|
|
35720
|
+
this.nextRangesToUpdate = new RangeSet(ranges);
|
|
35423
35721
|
let currentIteration = 0;
|
|
35424
|
-
while (!this.
|
|
35722
|
+
while (!this.nextRangesToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
|
|
35425
35723
|
this.updateCompilationParameters();
|
|
35426
|
-
const
|
|
35427
|
-
|
|
35428
|
-
|
|
35429
|
-
|
|
35430
|
-
|
|
35431
|
-
|
|
35432
|
-
|
|
35433
|
-
|
|
35434
|
-
|
|
35435
|
-
|
|
35436
|
-
|
|
35437
|
-
|
|
35724
|
+
const ranges = [...this.nextRangesToUpdate];
|
|
35725
|
+
this.nextRangesToUpdate.clear();
|
|
35726
|
+
this.clearEvaluatedRanges(ranges);
|
|
35727
|
+
for (const range of ranges) {
|
|
35728
|
+
const { left, bottom, right, top } = range.zone;
|
|
35729
|
+
for (let col = left; col <= right; col++) {
|
|
35730
|
+
for (let row = top; row <= bottom; row++) {
|
|
35731
|
+
const position = { sheetId: range.sheetId, col, row };
|
|
35732
|
+
if (this.nextRangesToUpdate.hasPosition(position)) {
|
|
35733
|
+
continue;
|
|
35734
|
+
}
|
|
35735
|
+
const evaluatedCell = this.computeCell(position);
|
|
35736
|
+
if (evaluatedCell !== EMPTY_CELL) {
|
|
35737
|
+
this.evaluatedCells.set(position, evaluatedCell);
|
|
35738
|
+
}
|
|
35739
|
+
}
|
|
35438
35740
|
}
|
|
35439
35741
|
}
|
|
35440
35742
|
onIterationEndEvaluationRegistry.getAll().forEach((callback) => callback(this.getters));
|
|
@@ -35443,6 +35745,16 @@ class Evaluator {
|
|
|
35443
35745
|
console.warn("Maximum iteration reached while evaluating cells");
|
|
35444
35746
|
}
|
|
35445
35747
|
}
|
|
35748
|
+
clearEvaluatedRanges(ranges) {
|
|
35749
|
+
for (const range of ranges) {
|
|
35750
|
+
const { left, bottom, right, top } = range.zone;
|
|
35751
|
+
for (let col = left; col <= right; col++) {
|
|
35752
|
+
for (let row = top; row <= bottom; row++) {
|
|
35753
|
+
this.evaluatedCells.delete({ sheetId: range.sheetId, col, row });
|
|
35754
|
+
}
|
|
35755
|
+
}
|
|
35756
|
+
}
|
|
35757
|
+
}
|
|
35446
35758
|
computeCell(position) {
|
|
35447
35759
|
const evaluation = this.evaluatedCells.get(position);
|
|
35448
35760
|
if (evaluation) {
|
|
@@ -35515,9 +35827,9 @@ class Evaluator {
|
|
|
35515
35827
|
}
|
|
35516
35828
|
invalidatePositionsDependingOnSpread(sheetId, resultZone) {
|
|
35517
35829
|
// the result matrix is split in 2 zones to exclude the array formula position
|
|
35518
|
-
const invalidatedPositions = this.
|
|
35519
|
-
invalidatedPositions.delete({ sheetId,
|
|
35520
|
-
this.
|
|
35830
|
+
const invalidatedPositions = this.getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
|
|
35831
|
+
invalidatedPositions.delete({ sheetId, zone: resultZone });
|
|
35832
|
+
this.nextRangesToUpdate.addMany(invalidatedPositions);
|
|
35521
35833
|
}
|
|
35522
35834
|
assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
|
|
35523
35835
|
const numberOfCols = this.getters.getNumberCols(sheetId);
|
|
@@ -35592,7 +35904,7 @@ class Evaluator {
|
|
|
35592
35904
|
}
|
|
35593
35905
|
const sheetId = position.sheetId;
|
|
35594
35906
|
this.invalidatePositionsDependingOnSpread(sheetId, zone);
|
|
35595
|
-
this.
|
|
35907
|
+
this.nextRangesToUpdate.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
35596
35908
|
}
|
|
35597
35909
|
/**
|
|
35598
35910
|
* Wraps a GetSymbolValue function to add cycle detection
|
|
@@ -35627,13 +35939,8 @@ class Evaluator {
|
|
|
35627
35939
|
}
|
|
35628
35940
|
return cell.compiledFormula.dependencies;
|
|
35629
35941
|
}
|
|
35630
|
-
getCellsDependingOn(
|
|
35631
|
-
|
|
35632
|
-
const zonesBySheetIds = aggregatePositionsToZones(positions);
|
|
35633
|
-
for (const sheetId in zonesBySheetIds) {
|
|
35634
|
-
ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
|
|
35635
|
-
}
|
|
35636
|
-
return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
|
|
35942
|
+
getCellsDependingOn(ranges) {
|
|
35943
|
+
return this.formulaDependencies().getCellsDependingOn(ranges, this.nextRangesToUpdate);
|
|
35637
35944
|
}
|
|
35638
35945
|
}
|
|
35639
35946
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -40133,18 +40440,8 @@ class PivotUIPlugin extends CoreViewPlugin {
|
|
|
40133
40440
|
return EMPTY_PIVOT_CELL;
|
|
40134
40441
|
}
|
|
40135
40442
|
if (functionName === "PIVOT") {
|
|
40136
|
-
const
|
|
40137
|
-
const
|
|
40138
|
-
const includeColumnHeaders = toScalar(args[3]);
|
|
40139
|
-
const includeMeasures = toScalar(args[5]);
|
|
40140
|
-
const shouldIncludeMeasures = includeMeasures === undefined ? true : toBoolean(includeMeasures);
|
|
40141
|
-
const shouldIncludeColumnHeaders = includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
|
|
40142
|
-
const visibilityOptions = {
|
|
40143
|
-
displayColumnHeaders: shouldIncludeColumnHeaders,
|
|
40144
|
-
displayTotals: shouldIncludeTotal,
|
|
40145
|
-
displayMeasuresRow: shouldIncludeMeasures,
|
|
40146
|
-
};
|
|
40147
|
-
const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(visibilityOptions);
|
|
40443
|
+
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());
|
|
40444
|
+
const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(pivotStyle);
|
|
40148
40445
|
const pivotCol = position.col - mainPosition.col;
|
|
40149
40446
|
const pivotRow = position.row - mainPosition.row;
|
|
40150
40447
|
return pivotCells[pivotCol][pivotRow];
|
|
@@ -49874,7 +50171,6 @@ class Model extends EventBus {
|
|
|
49874
50171
|
const startSnapshot = performance.now();
|
|
49875
50172
|
console.debug("Snapshot requested");
|
|
49876
50173
|
this.session.snapshot(this.exportData());
|
|
49877
|
-
this.garbageCollectExternalResources();
|
|
49878
50174
|
console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
|
|
49879
50175
|
}
|
|
49880
50176
|
console.debug("Model created in", performance.now() - start, "ms");
|
|
@@ -50260,11 +50556,6 @@ class Model extends EventBus {
|
|
|
50260
50556
|
data = deepCopy(data);
|
|
50261
50557
|
return getXLSX(data);
|
|
50262
50558
|
}
|
|
50263
|
-
garbageCollectExternalResources() {
|
|
50264
|
-
for (const plugin of this.corePlugins) {
|
|
50265
|
-
plugin.garbageCollectExternalResources();
|
|
50266
|
-
}
|
|
50267
|
-
}
|
|
50268
50559
|
}
|
|
50269
50560
|
function createCommand(type, payload = {}) {
|
|
50270
50561
|
const command = deepCopy(payload);
|
|
@@ -50300,5 +50591,5 @@ export { BadExpressionError, BasePlugin, CellErrorType, CircularDependencyError,
|
|
|
50300
50591
|
|
|
50301
50592
|
|
|
50302
50593
|
__info__.version = "19.1.0-alpha.3";
|
|
50303
|
-
__info__.date = "2025-10-
|
|
50304
|
-
__info__.hash = "
|
|
50594
|
+
__info__.date = "2025-10-23T11:12:13.207Z";
|
|
50595
|
+
__info__.hash = "bd756dd";
|