@odoo/o-spreadsheet 17.0.4 → 17.0.6
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.cjs.js +970 -220
- package/dist/o-spreadsheet.d.ts +24 -21
- package/dist/o-spreadsheet.esm.js +970 -220
- package/dist/o-spreadsheet.iife.js +970 -220
- package/dist/o-spreadsheet.iife.min.js +274 -276
- package/dist/o_spreadsheet.xml +4 -4
- package/package.json +6 -2
|
@@ -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 17.0.
|
|
6
|
-
* @date
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 17.0.6
|
|
6
|
+
* @date 2024-01-04T12:23:10.357Z
|
|
7
|
+
* @hash 310ca57
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
@@ -331,13 +331,6 @@ const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
|
|
|
331
331
|
//------------------------------------------------------------------------------
|
|
332
332
|
// Miscellaneous
|
|
333
333
|
//------------------------------------------------------------------------------
|
|
334
|
-
/**
|
|
335
|
-
* Stringify an object, like JSON.stringify, except that the first level of keys
|
|
336
|
-
* is ordered.
|
|
337
|
-
*/
|
|
338
|
-
function stringify(obj) {
|
|
339
|
-
return JSON.stringify(obj, Object.keys(obj).sort());
|
|
340
|
-
}
|
|
341
334
|
/**
|
|
342
335
|
* Remove quotes from a quoted string
|
|
343
336
|
* ```js
|
|
@@ -560,7 +553,7 @@ function isObjectEmptyRecursive(argument) {
|
|
|
560
553
|
*/
|
|
561
554
|
function getItemId(item, itemsDic) {
|
|
562
555
|
for (let [key, value] of Object.entries(itemsDic)) {
|
|
563
|
-
if (
|
|
556
|
+
if (deepEquals(value, item)) {
|
|
564
557
|
return parseInt(key, 10);
|
|
565
558
|
}
|
|
566
559
|
}
|
|
@@ -3389,8 +3382,10 @@ function convertInternalFormatToFormat(internalFormat) {
|
|
|
3389
3382
|
const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
|
|
3390
3383
|
// Same as above, but matches the exact string (nothing before or after)
|
|
3391
3384
|
const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
|
|
3392
|
-
/** Reference of a column header (eg. A, AB) */
|
|
3393
|
-
const colHeader = new RegExp(
|
|
3385
|
+
/** Reference of a column header (eg. A, AB, $A) */
|
|
3386
|
+
const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
|
|
3387
|
+
/** Reference of a row header (eg. 1, $1) */
|
|
3388
|
+
const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
|
|
3394
3389
|
/** Reference of a column (eg. A, $CA, Sheet1!B) */
|
|
3395
3390
|
const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
|
|
3396
3391
|
/** Reference of a row (eg. 1, 59, Sheet1!9) */
|
|
@@ -3420,6 +3415,9 @@ function isRowReference(xc) {
|
|
|
3420
3415
|
function isColHeader(str) {
|
|
3421
3416
|
return colHeader.test(str);
|
|
3422
3417
|
}
|
|
3418
|
+
function isRowHeader(str) {
|
|
3419
|
+
return rowHeader.test(str);
|
|
3420
|
+
}
|
|
3423
3421
|
/**
|
|
3424
3422
|
* Return true if the given xc is the reference of a single cell,
|
|
3425
3423
|
* without any specified sheet (e.g. A1)
|
|
@@ -4103,18 +4101,10 @@ class RangeImpl {
|
|
|
4103
4101
|
if (isFullCol) {
|
|
4104
4102
|
parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
4105
4103
|
parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
4106
|
-
if (zone.left === zone.right) {
|
|
4107
|
-
parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
4108
|
-
parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
4109
|
-
}
|
|
4110
4104
|
}
|
|
4111
4105
|
if (isFullRow) {
|
|
4112
4106
|
parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
4113
4107
|
parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
|
|
4114
|
-
if (zone.top === zone.bottom) {
|
|
4115
|
-
parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
4116
|
-
parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
|
|
4117
|
-
}
|
|
4118
4108
|
}
|
|
4119
4109
|
return parts;
|
|
4120
4110
|
}
|
|
@@ -4356,7 +4346,7 @@ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
|
|
|
4356
4346
|
function getDefaultCellHeight(ctx, cell, colSize) {
|
|
4357
4347
|
if (!cell || !cell.content)
|
|
4358
4348
|
return DEFAULT_CELL_HEIGHT;
|
|
4359
|
-
const maxWidth = cell.style?.wrapping ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
|
|
4349
|
+
const maxWidth = cell.style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
|
|
4360
4350
|
const numberOfLines = cell.isFormula
|
|
4361
4351
|
? 1
|
|
4362
4352
|
: splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
|
|
@@ -5291,7 +5281,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5291
5281
|
};
|
|
5292
5282
|
baselineCell = getters.getEvaluatedCell(baselinePosition);
|
|
5293
5283
|
}
|
|
5294
|
-
const background = getters.
|
|
5284
|
+
const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
|
|
5295
5285
|
const locale = getters.getLocale();
|
|
5296
5286
|
return {
|
|
5297
5287
|
title: _t(chart.title),
|
|
@@ -5300,7 +5290,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5300
5290
|
baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
|
|
5301
5291
|
baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
|
|
5302
5292
|
baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
|
|
5303
|
-
fontColor
|
|
5293
|
+
fontColor,
|
|
5304
5294
|
background,
|
|
5305
5295
|
baselineStyle: chart.baselineMode !== "percentage" && baseline
|
|
5306
5296
|
? getters.getCellStyle({
|
|
@@ -6251,10 +6241,6 @@ css /* scss */ `
|
|
|
6251
6241
|
|
|
6252
6242
|
.o-error-tooltip-message {
|
|
6253
6243
|
overflow: hidden;
|
|
6254
|
-
display: -webkit-box; /* Limit to 3 lines */
|
|
6255
|
-
-webkit-line-clamp: 3;
|
|
6256
|
-
line-clamp: 3;
|
|
6257
|
-
-webkit-box-orient: vertical;
|
|
6258
6244
|
}
|
|
6259
6245
|
}
|
|
6260
6246
|
`;
|
|
@@ -6599,6 +6585,7 @@ const FilterMenuPopoverBuilder = {
|
|
|
6599
6585
|
},
|
|
6600
6586
|
};
|
|
6601
6587
|
|
|
6588
|
+
const macRegex = /Mac/i;
|
|
6602
6589
|
/**
|
|
6603
6590
|
* Return true if the event was triggered from
|
|
6604
6591
|
* a child element.
|
|
@@ -6637,6 +6624,17 @@ function* iterateChildren(el) {
|
|
|
6637
6624
|
function getOpenedMenus() {
|
|
6638
6625
|
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
6639
6626
|
}
|
|
6627
|
+
function isMacOS() {
|
|
6628
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
6629
|
+
}
|
|
6630
|
+
/**
|
|
6631
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
6632
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
6633
|
+
* On Mac, this is the "meta" or "command" key.
|
|
6634
|
+
*/
|
|
6635
|
+
function isCtrlKey(ev) {
|
|
6636
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
6637
|
+
}
|
|
6640
6638
|
|
|
6641
6639
|
/**
|
|
6642
6640
|
* Return the o-spreadsheet element position relative
|
|
@@ -8884,7 +8882,7 @@ function createGaugeChartRuntime(chart, getters) {
|
|
|
8884
8882
|
});
|
|
8885
8883
|
return {
|
|
8886
8884
|
chartJsConfig: config,
|
|
8887
|
-
background: getters.
|
|
8885
|
+
background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
|
|
8888
8886
|
};
|
|
8889
8887
|
}
|
|
8890
8888
|
|
|
@@ -8965,7 +8963,7 @@ function getFormatMinDisplayUnit(format) {
|
|
|
8965
8963
|
else if (format.includes("h") || format.includes("H")) {
|
|
8966
8964
|
return "hour";
|
|
8967
8965
|
}
|
|
8968
|
-
else if (format.includes("
|
|
8966
|
+
else if (format.includes("d")) {
|
|
8969
8967
|
return "day";
|
|
8970
8968
|
}
|
|
8971
8969
|
else if (format.includes("M")) {
|
|
@@ -21218,10 +21216,10 @@ const arrowMap = {
|
|
|
21218
21216
|
function updateSelectionWithArrowKeys(ev, selection) {
|
|
21219
21217
|
const direction = arrowMap[ev.key];
|
|
21220
21218
|
if (ev.shiftKey) {
|
|
21221
|
-
selection.resizeAnchorZone(direction, ev
|
|
21219
|
+
selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21222
21220
|
}
|
|
21223
21221
|
else {
|
|
21224
|
-
selection.moveAnchorCell(direction, ev
|
|
21222
|
+
selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21225
21223
|
}
|
|
21226
21224
|
}
|
|
21227
21225
|
|
|
@@ -28206,7 +28204,10 @@ class GridOverlay extends owl.Component {
|
|
|
28206
28204
|
return;
|
|
28207
28205
|
}
|
|
28208
28206
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
28209
|
-
this.props.onCellClicked(col, row, {
|
|
28207
|
+
this.props.onCellClicked(col, row, {
|
|
28208
|
+
expandZone: ev.shiftKey,
|
|
28209
|
+
addZone: isCtrlKey(ev),
|
|
28210
|
+
});
|
|
28210
28211
|
}
|
|
28211
28212
|
onDoubleClick(ev) {
|
|
28212
28213
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
@@ -28281,6 +28282,7 @@ class AbstractResizer extends owl.Component {
|
|
|
28281
28282
|
draggerShadowThickness: 0,
|
|
28282
28283
|
delta: 0,
|
|
28283
28284
|
base: 0,
|
|
28285
|
+
position: "before",
|
|
28284
28286
|
});
|
|
28285
28287
|
_computeHandleDisplay(ev) {
|
|
28286
28288
|
const position = this._getEvOffset(ev);
|
|
@@ -28405,11 +28407,13 @@ class AbstractResizer extends owl.Component {
|
|
|
28405
28407
|
this.state.draggerLinePosition = dimensions.start;
|
|
28406
28408
|
this.state.draggerShadowPosition = dimensions.start;
|
|
28407
28409
|
this.state.base = elementIndex;
|
|
28410
|
+
this.state.position = "before";
|
|
28408
28411
|
}
|
|
28409
28412
|
else if (this._getSelectedZoneEnd() < elementIndex) {
|
|
28410
28413
|
this.state.draggerLinePosition = dimensions.end;
|
|
28411
28414
|
this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
|
|
28412
|
-
this.state.base = elementIndex
|
|
28415
|
+
this.state.base = elementIndex;
|
|
28416
|
+
this.state.position = "after";
|
|
28413
28417
|
}
|
|
28414
28418
|
else {
|
|
28415
28419
|
this.state.draggerLinePosition = startDimensions.start;
|
|
@@ -28433,7 +28437,7 @@ class AbstractResizer extends owl.Component {
|
|
|
28433
28437
|
this._increaseSelection(index);
|
|
28434
28438
|
}
|
|
28435
28439
|
else {
|
|
28436
|
-
this._selectElement(index, ev
|
|
28440
|
+
this._selectElement(index, isCtrlKey(ev));
|
|
28437
28441
|
}
|
|
28438
28442
|
this.lastSelectedElementIndex = index;
|
|
28439
28443
|
const mouseMoveSelect = (col, row) => {
|
|
@@ -28591,13 +28595,14 @@ class ColResizer extends AbstractResizer {
|
|
|
28591
28595
|
dimension: "COL",
|
|
28592
28596
|
base: this.state.base,
|
|
28593
28597
|
elements,
|
|
28598
|
+
position: this.state.position,
|
|
28594
28599
|
});
|
|
28595
28600
|
if (!result.isSuccessful && result.reasons.includes("WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */)) {
|
|
28596
28601
|
this.env.raiseError(MergeErrorMessage);
|
|
28597
28602
|
}
|
|
28598
28603
|
}
|
|
28599
|
-
_selectElement(index,
|
|
28600
|
-
this.env.model.selection.selectColumn(index,
|
|
28604
|
+
_selectElement(index, addDistinctHeader) {
|
|
28605
|
+
this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28601
28606
|
}
|
|
28602
28607
|
_increaseSelection(index) {
|
|
28603
28608
|
this.env.model.selection.selectColumn(index, "updateAnchor");
|
|
@@ -28764,13 +28769,14 @@ class RowResizer extends AbstractResizer {
|
|
|
28764
28769
|
dimension: "ROW",
|
|
28765
28770
|
base: this.state.base,
|
|
28766
28771
|
elements,
|
|
28772
|
+
position: this.state.position,
|
|
28767
28773
|
});
|
|
28768
28774
|
if (!result.isSuccessful && result.reasons.includes("WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */)) {
|
|
28769
28775
|
this.env.raiseError(MergeErrorMessage);
|
|
28770
28776
|
}
|
|
28771
28777
|
}
|
|
28772
|
-
_selectElement(index,
|
|
28773
|
-
this.env.model.selection.selectRow(index,
|
|
28778
|
+
_selectElement(index, addDistinctHeader) {
|
|
28779
|
+
this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28774
28780
|
}
|
|
28775
28781
|
_increaseSelection(index) {
|
|
28776
28782
|
this.env.model.selection.selectRow(index, "updateAnchor");
|
|
@@ -28871,8 +28877,8 @@ function useWheelHandler(handler) {
|
|
|
28871
28877
|
return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
|
|
28872
28878
|
}
|
|
28873
28879
|
const onMouseWheel = (ev) => {
|
|
28874
|
-
const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
|
|
28875
|
-
const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
|
|
28880
|
+
const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
|
|
28881
|
+
const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
|
|
28876
28882
|
handler(deltaX, deltaY);
|
|
28877
28883
|
};
|
|
28878
28884
|
return onMouseWheel;
|
|
@@ -29572,17 +29578,17 @@ class Grid extends owl.Component {
|
|
|
29572
29578
|
// ---------------------------------------------------------------------------
|
|
29573
29579
|
// Zone selection with mouse
|
|
29574
29580
|
// ---------------------------------------------------------------------------
|
|
29575
|
-
onCellClicked(col, row, {
|
|
29581
|
+
onCellClicked(col, row, { addZone, expandZone }) {
|
|
29576
29582
|
if (this.env.model.getters.hasOpenedPopover()) {
|
|
29577
29583
|
this.closeOpenedPopover();
|
|
29578
29584
|
}
|
|
29579
29585
|
if (this.env.model.getters.getEditionMode() === "editing") {
|
|
29580
29586
|
interactiveStopEdition(this.env);
|
|
29581
29587
|
}
|
|
29582
|
-
if (
|
|
29588
|
+
if (expandZone) {
|
|
29583
29589
|
this.env.model.selection.setAnchorCorner(col, row);
|
|
29584
29590
|
}
|
|
29585
|
-
else if (
|
|
29591
|
+
else if (addZone) {
|
|
29586
29592
|
this.env.model.selection.addCellToSelection(col, row);
|
|
29587
29593
|
}
|
|
29588
29594
|
else {
|
|
@@ -29643,7 +29649,7 @@ class Grid extends owl.Component {
|
|
|
29643
29649
|
onKeydown(ev) {
|
|
29644
29650
|
const eventKey = ev.key.toUpperCase();
|
|
29645
29651
|
let keyDownString = "";
|
|
29646
|
-
if (ev
|
|
29652
|
+
if (isCtrlKey(ev) && eventKey !== "CTRL")
|
|
29647
29653
|
keyDownString += "CTRL+";
|
|
29648
29654
|
if (ev.metaKey)
|
|
29649
29655
|
keyDownString += "CTRL+";
|
|
@@ -33326,7 +33332,7 @@ const machine = {
|
|
|
33326
33332
|
SPACE: goTo(State.RightRef),
|
|
33327
33333
|
NUMBER: goTo(State.Found),
|
|
33328
33334
|
REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
|
|
33329
|
-
SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
|
|
33335
|
+
SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
|
|
33330
33336
|
},
|
|
33331
33337
|
[State.RightColumnRef]: {
|
|
33332
33338
|
SPACE: goTo(State.RightColumnRef),
|
|
@@ -33337,6 +33343,7 @@ const machine = {
|
|
|
33337
33343
|
SPACE: goTo(State.RightRowRef),
|
|
33338
33344
|
NUMBER: goTo(State.Found),
|
|
33339
33345
|
REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
|
|
33346
|
+
SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
|
|
33340
33347
|
},
|
|
33341
33348
|
[State.Found]: {},
|
|
33342
33349
|
};
|
|
@@ -34533,7 +34540,7 @@ class BordersPlugin extends CorePlugin {
|
|
|
34533
34540
|
*/
|
|
34534
34541
|
function getBorderId(border) {
|
|
34535
34542
|
for (let [key, value] of Object.entries(borders)) {
|
|
34536
|
-
if (
|
|
34543
|
+
if (deepEquals(value, border)) {
|
|
34537
34544
|
return parseInt(key, 10);
|
|
34538
34545
|
}
|
|
34539
34546
|
}
|
|
@@ -37318,7 +37325,16 @@ class HeaderVisibilityPlugin extends CorePlugin {
|
|
|
37318
37325
|
return consecutiveIndexes;
|
|
37319
37326
|
}
|
|
37320
37327
|
getAllVisibleHeaders(sheetId, dimension) {
|
|
37321
|
-
|
|
37328
|
+
const headers = range(0, this.getters.getNumberHeaders(sheetId, dimension));
|
|
37329
|
+
const foldedHeaders = [];
|
|
37330
|
+
this.getters.getHeaderGroups(sheetId, dimension).forEach((group) => {
|
|
37331
|
+
if (group.isFolded) {
|
|
37332
|
+
foldedHeaders.push(...range(group.start, group.end + 1));
|
|
37333
|
+
}
|
|
37334
|
+
});
|
|
37335
|
+
return headers.filter((i) => {
|
|
37336
|
+
return !this.hiddenHeaders[sheetId][dimension][i] && !foldedHeaders.includes(i);
|
|
37337
|
+
});
|
|
37322
37338
|
}
|
|
37323
37339
|
import(data) {
|
|
37324
37340
|
for (let sheet of data.sheets) {
|
|
@@ -39778,10 +39794,13 @@ class CompilationParametersBuilder {
|
|
|
39778
39794
|
if (evaluatedCell === undefined) {
|
|
39779
39795
|
return { value: null, format: this.getters.getCell(position)?.format };
|
|
39780
39796
|
}
|
|
39797
|
+
if (evaluatedCell.type === CellValueType.error) {
|
|
39798
|
+
throw evaluatedCell.error;
|
|
39799
|
+
}
|
|
39781
39800
|
return evaluatedCell;
|
|
39782
39801
|
}
|
|
39783
39802
|
getEvaluatedCellIfNotEmpty(position) {
|
|
39784
|
-
const evaluatedCell = this.
|
|
39803
|
+
const evaluatedCell = this.computeCell(position);
|
|
39785
39804
|
if (evaluatedCell.type === CellValueType.empty) {
|
|
39786
39805
|
const cell = this.getters.getCell(position);
|
|
39787
39806
|
if (!cell || (!cell.isFormula && cell.content === "")) {
|
|
@@ -39790,13 +39809,6 @@ class CompilationParametersBuilder {
|
|
|
39790
39809
|
}
|
|
39791
39810
|
return evaluatedCell;
|
|
39792
39811
|
}
|
|
39793
|
-
getEvaluatedCell(position) {
|
|
39794
|
-
const evaluatedCell = this.computeCell(position);
|
|
39795
|
-
if (evaluatedCell.type === CellValueType.error) {
|
|
39796
|
-
throw evaluatedCell.error;
|
|
39797
|
-
}
|
|
39798
|
-
return evaluatedCell;
|
|
39799
|
-
}
|
|
39800
39812
|
/**
|
|
39801
39813
|
* Return the values of the cell(s) used in reference, but always in the format of a range even
|
|
39802
39814
|
* if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
|
|
@@ -39819,7 +39831,11 @@ class CompilationParametersBuilder {
|
|
|
39819
39831
|
const { top, left, bottom, right } = zone;
|
|
39820
39832
|
const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
|
|
39821
39833
|
if (cacheKey in this.rangeCache) {
|
|
39822
|
-
|
|
39834
|
+
const result = this.rangeCache[cacheKey];
|
|
39835
|
+
if (result instanceof EvaluationError) {
|
|
39836
|
+
throw result;
|
|
39837
|
+
}
|
|
39838
|
+
return result;
|
|
39823
39839
|
}
|
|
39824
39840
|
const height = _zone.bottom - _zone.top + 1;
|
|
39825
39841
|
const width = _zone.right - _zone.left + 1;
|
|
@@ -39829,6 +39845,11 @@ class CompilationParametersBuilder {
|
|
|
39829
39845
|
const colIndex = col - _zone.left;
|
|
39830
39846
|
matrix[colIndex] = new Array(height);
|
|
39831
39847
|
for (let row = _zone.top; row <= _zone.bottom; row++) {
|
|
39848
|
+
const evaluatedCell = this.getEvaluatedCellIfNotEmpty({ sheetId, col, row });
|
|
39849
|
+
if (evaluatedCell?.type === CellValueType.error) {
|
|
39850
|
+
this.rangeCache[cacheKey] = evaluatedCell.error;
|
|
39851
|
+
throw evaluatedCell.error;
|
|
39852
|
+
}
|
|
39832
39853
|
const rowIndex = row - _zone.top;
|
|
39833
39854
|
matrix[colIndex][rowIndex] = this.readCell({ sheetId, col, row });
|
|
39834
39855
|
}
|
|
@@ -39838,56 +39859,758 @@ class CompilationParametersBuilder {
|
|
|
39838
39859
|
}
|
|
39839
39860
|
}
|
|
39840
39861
|
|
|
39862
|
+
function quickselect(arr, k, left, right, compare) {
|
|
39863
|
+
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
|
|
39864
|
+
}
|
|
39865
|
+
|
|
39866
|
+
function quickselectStep(arr, k, left, right, compare) {
|
|
39867
|
+
|
|
39868
|
+
while (right > left) {
|
|
39869
|
+
if (right - left > 600) {
|
|
39870
|
+
var n = right - left + 1;
|
|
39871
|
+
var m = k - left + 1;
|
|
39872
|
+
var z = Math.log(n);
|
|
39873
|
+
var s = 0.5 * Math.exp(2 * z / 3);
|
|
39874
|
+
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
|
|
39875
|
+
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
|
|
39876
|
+
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
|
|
39877
|
+
quickselectStep(arr, k, newLeft, newRight, compare);
|
|
39878
|
+
}
|
|
39879
|
+
|
|
39880
|
+
var t = arr[k];
|
|
39881
|
+
var i = left;
|
|
39882
|
+
var j = right;
|
|
39883
|
+
|
|
39884
|
+
swap(arr, left, k);
|
|
39885
|
+
if (compare(arr[right], t) > 0) swap(arr, left, right);
|
|
39886
|
+
|
|
39887
|
+
while (i < j) {
|
|
39888
|
+
swap(arr, i, j);
|
|
39889
|
+
i++;
|
|
39890
|
+
j--;
|
|
39891
|
+
while (compare(arr[i], t) < 0) i++;
|
|
39892
|
+
while (compare(arr[j], t) > 0) j--;
|
|
39893
|
+
}
|
|
39894
|
+
|
|
39895
|
+
if (compare(arr[left], t) === 0) swap(arr, left, j);
|
|
39896
|
+
else {
|
|
39897
|
+
j++;
|
|
39898
|
+
swap(arr, j, right);
|
|
39899
|
+
}
|
|
39900
|
+
|
|
39901
|
+
if (j <= k) left = j + 1;
|
|
39902
|
+
if (k <= j) right = j - 1;
|
|
39903
|
+
}
|
|
39904
|
+
}
|
|
39905
|
+
|
|
39906
|
+
function swap(arr, i, j) {
|
|
39907
|
+
var tmp = arr[i];
|
|
39908
|
+
arr[i] = arr[j];
|
|
39909
|
+
arr[j] = tmp;
|
|
39910
|
+
}
|
|
39911
|
+
|
|
39912
|
+
function defaultCompare(a, b) {
|
|
39913
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
39914
|
+
}
|
|
39915
|
+
|
|
39916
|
+
class RBush {
|
|
39917
|
+
constructor(maxEntries = 9) {
|
|
39918
|
+
// max entries in a node is 9 by default; min node fill is 40% for best performance
|
|
39919
|
+
this._maxEntries = Math.max(4, maxEntries);
|
|
39920
|
+
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
|
|
39921
|
+
this.clear();
|
|
39922
|
+
}
|
|
39923
|
+
|
|
39924
|
+
all() {
|
|
39925
|
+
return this._all(this.data, []);
|
|
39926
|
+
}
|
|
39927
|
+
|
|
39928
|
+
search(bbox) {
|
|
39929
|
+
let node = this.data;
|
|
39930
|
+
const result = [];
|
|
39931
|
+
|
|
39932
|
+
if (!intersects(bbox, node)) return result;
|
|
39933
|
+
|
|
39934
|
+
const toBBox = this.toBBox;
|
|
39935
|
+
const nodesToSearch = [];
|
|
39936
|
+
|
|
39937
|
+
while (node) {
|
|
39938
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
39939
|
+
const child = node.children[i];
|
|
39940
|
+
const childBBox = node.leaf ? toBBox(child) : child;
|
|
39941
|
+
|
|
39942
|
+
if (intersects(bbox, childBBox)) {
|
|
39943
|
+
if (node.leaf) result.push(child);
|
|
39944
|
+
else if (contains(bbox, childBBox)) this._all(child, result);
|
|
39945
|
+
else nodesToSearch.push(child);
|
|
39946
|
+
}
|
|
39947
|
+
}
|
|
39948
|
+
node = nodesToSearch.pop();
|
|
39949
|
+
}
|
|
39950
|
+
|
|
39951
|
+
return result;
|
|
39952
|
+
}
|
|
39953
|
+
|
|
39954
|
+
collides(bbox) {
|
|
39955
|
+
let node = this.data;
|
|
39956
|
+
|
|
39957
|
+
if (!intersects(bbox, node)) return false;
|
|
39958
|
+
|
|
39959
|
+
const nodesToSearch = [];
|
|
39960
|
+
while (node) {
|
|
39961
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
39962
|
+
const child = node.children[i];
|
|
39963
|
+
const childBBox = node.leaf ? this.toBBox(child) : child;
|
|
39964
|
+
|
|
39965
|
+
if (intersects(bbox, childBBox)) {
|
|
39966
|
+
if (node.leaf || contains(bbox, childBBox)) return true;
|
|
39967
|
+
nodesToSearch.push(child);
|
|
39968
|
+
}
|
|
39969
|
+
}
|
|
39970
|
+
node = nodesToSearch.pop();
|
|
39971
|
+
}
|
|
39972
|
+
|
|
39973
|
+
return false;
|
|
39974
|
+
}
|
|
39975
|
+
|
|
39976
|
+
load(data) {
|
|
39977
|
+
if (!(data && data.length)) return this;
|
|
39978
|
+
|
|
39979
|
+
if (data.length < this._minEntries) {
|
|
39980
|
+
for (let i = 0; i < data.length; i++) {
|
|
39981
|
+
this.insert(data[i]);
|
|
39982
|
+
}
|
|
39983
|
+
return this;
|
|
39984
|
+
}
|
|
39985
|
+
|
|
39986
|
+
// recursively build the tree with the given data from scratch using OMT algorithm
|
|
39987
|
+
let node = this._build(data.slice(), 0, data.length - 1, 0);
|
|
39988
|
+
|
|
39989
|
+
if (!this.data.children.length) {
|
|
39990
|
+
// save as is if tree is empty
|
|
39991
|
+
this.data = node;
|
|
39992
|
+
|
|
39993
|
+
} else if (this.data.height === node.height) {
|
|
39994
|
+
// split root if trees have the same height
|
|
39995
|
+
this._splitRoot(this.data, node);
|
|
39996
|
+
|
|
39997
|
+
} else {
|
|
39998
|
+
if (this.data.height < node.height) {
|
|
39999
|
+
// swap trees if inserted one is bigger
|
|
40000
|
+
const tmpNode = this.data;
|
|
40001
|
+
this.data = node;
|
|
40002
|
+
node = tmpNode;
|
|
40003
|
+
}
|
|
40004
|
+
|
|
40005
|
+
// insert the small tree into the large tree at appropriate level
|
|
40006
|
+
this._insert(node, this.data.height - node.height - 1, true);
|
|
40007
|
+
}
|
|
40008
|
+
|
|
40009
|
+
return this;
|
|
40010
|
+
}
|
|
40011
|
+
|
|
40012
|
+
insert(item) {
|
|
40013
|
+
if (item) this._insert(item, this.data.height - 1);
|
|
40014
|
+
return this;
|
|
40015
|
+
}
|
|
40016
|
+
|
|
40017
|
+
clear() {
|
|
40018
|
+
this.data = createNode([]);
|
|
40019
|
+
return this;
|
|
40020
|
+
}
|
|
40021
|
+
|
|
40022
|
+
remove(item, equalsFn) {
|
|
40023
|
+
if (!item) return this;
|
|
40024
|
+
|
|
40025
|
+
let node = this.data;
|
|
40026
|
+
const bbox = this.toBBox(item);
|
|
40027
|
+
const path = [];
|
|
40028
|
+
const indexes = [];
|
|
40029
|
+
let i, parent, goingUp;
|
|
40030
|
+
|
|
40031
|
+
// depth-first iterative tree traversal
|
|
40032
|
+
while (node || path.length) {
|
|
40033
|
+
|
|
40034
|
+
if (!node) { // go up
|
|
40035
|
+
node = path.pop();
|
|
40036
|
+
parent = path[path.length - 1];
|
|
40037
|
+
i = indexes.pop();
|
|
40038
|
+
goingUp = true;
|
|
40039
|
+
}
|
|
40040
|
+
|
|
40041
|
+
if (node.leaf) { // check current node
|
|
40042
|
+
const index = findItem(item, node.children, equalsFn);
|
|
40043
|
+
|
|
40044
|
+
if (index !== -1) {
|
|
40045
|
+
// item found, remove the item and condense tree upwards
|
|
40046
|
+
node.children.splice(index, 1);
|
|
40047
|
+
path.push(node);
|
|
40048
|
+
this._condense(path);
|
|
40049
|
+
return this;
|
|
40050
|
+
}
|
|
40051
|
+
}
|
|
40052
|
+
|
|
40053
|
+
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
|
|
40054
|
+
path.push(node);
|
|
40055
|
+
indexes.push(i);
|
|
40056
|
+
i = 0;
|
|
40057
|
+
parent = node;
|
|
40058
|
+
node = node.children[0];
|
|
40059
|
+
|
|
40060
|
+
} else if (parent) { // go right
|
|
40061
|
+
i++;
|
|
40062
|
+
node = parent.children[i];
|
|
40063
|
+
goingUp = false;
|
|
40064
|
+
|
|
40065
|
+
} else node = null; // nothing found
|
|
40066
|
+
}
|
|
40067
|
+
|
|
40068
|
+
return this;
|
|
40069
|
+
}
|
|
40070
|
+
|
|
40071
|
+
toBBox(item) { return item; }
|
|
40072
|
+
|
|
40073
|
+
compareMinX(a, b) { return a.minX - b.minX; }
|
|
40074
|
+
compareMinY(a, b) { return a.minY - b.minY; }
|
|
40075
|
+
|
|
40076
|
+
toJSON() { return this.data; }
|
|
40077
|
+
|
|
40078
|
+
fromJSON(data) {
|
|
40079
|
+
this.data = data;
|
|
40080
|
+
return this;
|
|
40081
|
+
}
|
|
40082
|
+
|
|
40083
|
+
_all(node, result) {
|
|
40084
|
+
const nodesToSearch = [];
|
|
40085
|
+
while (node) {
|
|
40086
|
+
if (node.leaf) result.push(...node.children);
|
|
40087
|
+
else nodesToSearch.push(...node.children);
|
|
40088
|
+
|
|
40089
|
+
node = nodesToSearch.pop();
|
|
40090
|
+
}
|
|
40091
|
+
return result;
|
|
40092
|
+
}
|
|
40093
|
+
|
|
40094
|
+
_build(items, left, right, height) {
|
|
40095
|
+
|
|
40096
|
+
const N = right - left + 1;
|
|
40097
|
+
let M = this._maxEntries;
|
|
40098
|
+
let node;
|
|
40099
|
+
|
|
40100
|
+
if (N <= M) {
|
|
40101
|
+
// reached leaf level; return leaf
|
|
40102
|
+
node = createNode(items.slice(left, right + 1));
|
|
40103
|
+
calcBBox(node, this.toBBox);
|
|
40104
|
+
return node;
|
|
40105
|
+
}
|
|
40106
|
+
|
|
40107
|
+
if (!height) {
|
|
40108
|
+
// target height of the bulk-loaded tree
|
|
40109
|
+
height = Math.ceil(Math.log(N) / Math.log(M));
|
|
40110
|
+
|
|
40111
|
+
// target number of root entries to maximize storage utilization
|
|
40112
|
+
M = Math.ceil(N / Math.pow(M, height - 1));
|
|
40113
|
+
}
|
|
40114
|
+
|
|
40115
|
+
node = createNode([]);
|
|
40116
|
+
node.leaf = false;
|
|
40117
|
+
node.height = height;
|
|
40118
|
+
|
|
40119
|
+
// split the items into M mostly square tiles
|
|
40120
|
+
|
|
40121
|
+
const N2 = Math.ceil(N / M);
|
|
40122
|
+
const N1 = N2 * Math.ceil(Math.sqrt(M));
|
|
40123
|
+
|
|
40124
|
+
multiSelect(items, left, right, N1, this.compareMinX);
|
|
40125
|
+
|
|
40126
|
+
for (let i = left; i <= right; i += N1) {
|
|
40127
|
+
|
|
40128
|
+
const right2 = Math.min(i + N1 - 1, right);
|
|
40129
|
+
|
|
40130
|
+
multiSelect(items, i, right2, N2, this.compareMinY);
|
|
40131
|
+
|
|
40132
|
+
for (let j = i; j <= right2; j += N2) {
|
|
40133
|
+
|
|
40134
|
+
const right3 = Math.min(j + N2 - 1, right2);
|
|
40135
|
+
|
|
40136
|
+
// pack each entry recursively
|
|
40137
|
+
node.children.push(this._build(items, j, right3, height - 1));
|
|
40138
|
+
}
|
|
40139
|
+
}
|
|
40140
|
+
|
|
40141
|
+
calcBBox(node, this.toBBox);
|
|
40142
|
+
|
|
40143
|
+
return node;
|
|
40144
|
+
}
|
|
40145
|
+
|
|
40146
|
+
_chooseSubtree(bbox, node, level, path) {
|
|
40147
|
+
while (true) {
|
|
40148
|
+
path.push(node);
|
|
40149
|
+
|
|
40150
|
+
if (node.leaf || path.length - 1 === level) break;
|
|
40151
|
+
|
|
40152
|
+
let minArea = Infinity;
|
|
40153
|
+
let minEnlargement = Infinity;
|
|
40154
|
+
let targetNode;
|
|
40155
|
+
|
|
40156
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40157
|
+
const child = node.children[i];
|
|
40158
|
+
const area = bboxArea(child);
|
|
40159
|
+
const enlargement = enlargedArea(bbox, child) - area;
|
|
40160
|
+
|
|
40161
|
+
// choose entry with the least area enlargement
|
|
40162
|
+
if (enlargement < minEnlargement) {
|
|
40163
|
+
minEnlargement = enlargement;
|
|
40164
|
+
minArea = area < minArea ? area : minArea;
|
|
40165
|
+
targetNode = child;
|
|
40166
|
+
|
|
40167
|
+
} else if (enlargement === minEnlargement) {
|
|
40168
|
+
// otherwise choose one with the smallest area
|
|
40169
|
+
if (area < minArea) {
|
|
40170
|
+
minArea = area;
|
|
40171
|
+
targetNode = child;
|
|
40172
|
+
}
|
|
40173
|
+
}
|
|
40174
|
+
}
|
|
40175
|
+
|
|
40176
|
+
node = targetNode || node.children[0];
|
|
40177
|
+
}
|
|
40178
|
+
|
|
40179
|
+
return node;
|
|
40180
|
+
}
|
|
40181
|
+
|
|
40182
|
+
_insert(item, level, isNode) {
|
|
40183
|
+
const bbox = isNode ? item : this.toBBox(item);
|
|
40184
|
+
const insertPath = [];
|
|
40185
|
+
|
|
40186
|
+
// find the best node for accommodating the item, saving all nodes along the path too
|
|
40187
|
+
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
|
|
40188
|
+
|
|
40189
|
+
// put the item into the node
|
|
40190
|
+
node.children.push(item);
|
|
40191
|
+
extend(node, bbox);
|
|
40192
|
+
|
|
40193
|
+
// split on node overflow; propagate upwards if necessary
|
|
40194
|
+
while (level >= 0) {
|
|
40195
|
+
if (insertPath[level].children.length > this._maxEntries) {
|
|
40196
|
+
this._split(insertPath, level);
|
|
40197
|
+
level--;
|
|
40198
|
+
} else break;
|
|
40199
|
+
}
|
|
40200
|
+
|
|
40201
|
+
// adjust bboxes along the insertion path
|
|
40202
|
+
this._adjustParentBBoxes(bbox, insertPath, level);
|
|
40203
|
+
}
|
|
40204
|
+
|
|
40205
|
+
// split overflowed node into two
|
|
40206
|
+
_split(insertPath, level) {
|
|
40207
|
+
const node = insertPath[level];
|
|
40208
|
+
const M = node.children.length;
|
|
40209
|
+
const m = this._minEntries;
|
|
40210
|
+
|
|
40211
|
+
this._chooseSplitAxis(node, m, M);
|
|
40212
|
+
|
|
40213
|
+
const splitIndex = this._chooseSplitIndex(node, m, M);
|
|
40214
|
+
|
|
40215
|
+
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
|
|
40216
|
+
newNode.height = node.height;
|
|
40217
|
+
newNode.leaf = node.leaf;
|
|
40218
|
+
|
|
40219
|
+
calcBBox(node, this.toBBox);
|
|
40220
|
+
calcBBox(newNode, this.toBBox);
|
|
40221
|
+
|
|
40222
|
+
if (level) insertPath[level - 1].children.push(newNode);
|
|
40223
|
+
else this._splitRoot(node, newNode);
|
|
40224
|
+
}
|
|
40225
|
+
|
|
40226
|
+
_splitRoot(node, newNode) {
|
|
40227
|
+
// split root node
|
|
40228
|
+
this.data = createNode([node, newNode]);
|
|
40229
|
+
this.data.height = node.height + 1;
|
|
40230
|
+
this.data.leaf = false;
|
|
40231
|
+
calcBBox(this.data, this.toBBox);
|
|
40232
|
+
}
|
|
40233
|
+
|
|
40234
|
+
_chooseSplitIndex(node, m, M) {
|
|
40235
|
+
let index;
|
|
40236
|
+
let minOverlap = Infinity;
|
|
40237
|
+
let minArea = Infinity;
|
|
40238
|
+
|
|
40239
|
+
for (let i = m; i <= M - m; i++) {
|
|
40240
|
+
const bbox1 = distBBox(node, 0, i, this.toBBox);
|
|
40241
|
+
const bbox2 = distBBox(node, i, M, this.toBBox);
|
|
40242
|
+
|
|
40243
|
+
const overlap = intersectionArea(bbox1, bbox2);
|
|
40244
|
+
const area = bboxArea(bbox1) + bboxArea(bbox2);
|
|
40245
|
+
|
|
40246
|
+
// choose distribution with minimum overlap
|
|
40247
|
+
if (overlap < minOverlap) {
|
|
40248
|
+
minOverlap = overlap;
|
|
40249
|
+
index = i;
|
|
40250
|
+
|
|
40251
|
+
minArea = area < minArea ? area : minArea;
|
|
40252
|
+
|
|
40253
|
+
} else if (overlap === minOverlap) {
|
|
40254
|
+
// otherwise choose distribution with minimum area
|
|
40255
|
+
if (area < minArea) {
|
|
40256
|
+
minArea = area;
|
|
40257
|
+
index = i;
|
|
40258
|
+
}
|
|
40259
|
+
}
|
|
40260
|
+
}
|
|
40261
|
+
|
|
40262
|
+
return index || M - m;
|
|
40263
|
+
}
|
|
40264
|
+
|
|
40265
|
+
// sorts node children by the best axis for split
|
|
40266
|
+
_chooseSplitAxis(node, m, M) {
|
|
40267
|
+
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
|
|
40268
|
+
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
|
|
40269
|
+
const xMargin = this._allDistMargin(node, m, M, compareMinX);
|
|
40270
|
+
const yMargin = this._allDistMargin(node, m, M, compareMinY);
|
|
40271
|
+
|
|
40272
|
+
// if total distributions margin value is minimal for x, sort by minX,
|
|
40273
|
+
// otherwise it's already sorted by minY
|
|
40274
|
+
if (xMargin < yMargin) node.children.sort(compareMinX);
|
|
40275
|
+
}
|
|
40276
|
+
|
|
40277
|
+
// total margin of all possible split distributions where each node is at least m full
|
|
40278
|
+
_allDistMargin(node, m, M, compare) {
|
|
40279
|
+
node.children.sort(compare);
|
|
40280
|
+
|
|
40281
|
+
const toBBox = this.toBBox;
|
|
40282
|
+
const leftBBox = distBBox(node, 0, m, toBBox);
|
|
40283
|
+
const rightBBox = distBBox(node, M - m, M, toBBox);
|
|
40284
|
+
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
|
|
40285
|
+
|
|
40286
|
+
for (let i = m; i < M - m; i++) {
|
|
40287
|
+
const child = node.children[i];
|
|
40288
|
+
extend(leftBBox, node.leaf ? toBBox(child) : child);
|
|
40289
|
+
margin += bboxMargin(leftBBox);
|
|
40290
|
+
}
|
|
40291
|
+
|
|
40292
|
+
for (let i = M - m - 1; i >= m; i--) {
|
|
40293
|
+
const child = node.children[i];
|
|
40294
|
+
extend(rightBBox, node.leaf ? toBBox(child) : child);
|
|
40295
|
+
margin += bboxMargin(rightBBox);
|
|
40296
|
+
}
|
|
40297
|
+
|
|
40298
|
+
return margin;
|
|
40299
|
+
}
|
|
40300
|
+
|
|
40301
|
+
_adjustParentBBoxes(bbox, path, level) {
|
|
40302
|
+
// adjust bboxes along the given tree path
|
|
40303
|
+
for (let i = level; i >= 0; i--) {
|
|
40304
|
+
extend(path[i], bbox);
|
|
40305
|
+
}
|
|
40306
|
+
}
|
|
40307
|
+
|
|
40308
|
+
_condense(path) {
|
|
40309
|
+
// go through the path, removing empty nodes and updating bboxes
|
|
40310
|
+
for (let i = path.length - 1, siblings; i >= 0; i--) {
|
|
40311
|
+
if (path[i].children.length === 0) {
|
|
40312
|
+
if (i > 0) {
|
|
40313
|
+
siblings = path[i - 1].children;
|
|
40314
|
+
siblings.splice(siblings.indexOf(path[i]), 1);
|
|
40315
|
+
|
|
40316
|
+
} else this.clear();
|
|
40317
|
+
|
|
40318
|
+
} else calcBBox(path[i], this.toBBox);
|
|
40319
|
+
}
|
|
40320
|
+
}
|
|
40321
|
+
}
|
|
40322
|
+
|
|
40323
|
+
function findItem(item, items, equalsFn) {
|
|
40324
|
+
if (!equalsFn) return items.indexOf(item);
|
|
40325
|
+
|
|
40326
|
+
for (let i = 0; i < items.length; i++) {
|
|
40327
|
+
if (equalsFn(item, items[i])) return i;
|
|
40328
|
+
}
|
|
40329
|
+
return -1;
|
|
40330
|
+
}
|
|
40331
|
+
|
|
40332
|
+
// calculate node's bbox from bboxes of its children
|
|
40333
|
+
function calcBBox(node, toBBox) {
|
|
40334
|
+
distBBox(node, 0, node.children.length, toBBox, node);
|
|
40335
|
+
}
|
|
40336
|
+
|
|
40337
|
+
// min bounding rectangle of node children from k to p-1
|
|
40338
|
+
function distBBox(node, k, p, toBBox, destNode) {
|
|
40339
|
+
if (!destNode) destNode = createNode(null);
|
|
40340
|
+
destNode.minX = Infinity;
|
|
40341
|
+
destNode.minY = Infinity;
|
|
40342
|
+
destNode.maxX = -Infinity;
|
|
40343
|
+
destNode.maxY = -Infinity;
|
|
40344
|
+
|
|
40345
|
+
for (let i = k; i < p; i++) {
|
|
40346
|
+
const child = node.children[i];
|
|
40347
|
+
extend(destNode, node.leaf ? toBBox(child) : child);
|
|
40348
|
+
}
|
|
40349
|
+
|
|
40350
|
+
return destNode;
|
|
40351
|
+
}
|
|
40352
|
+
|
|
40353
|
+
function extend(a, b) {
|
|
40354
|
+
a.minX = Math.min(a.minX, b.minX);
|
|
40355
|
+
a.minY = Math.min(a.minY, b.minY);
|
|
40356
|
+
a.maxX = Math.max(a.maxX, b.maxX);
|
|
40357
|
+
a.maxY = Math.max(a.maxY, b.maxY);
|
|
40358
|
+
return a;
|
|
40359
|
+
}
|
|
40360
|
+
|
|
40361
|
+
function compareNodeMinX(a, b) { return a.minX - b.minX; }
|
|
40362
|
+
function compareNodeMinY(a, b) { return a.minY - b.minY; }
|
|
40363
|
+
|
|
40364
|
+
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
|
|
40365
|
+
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
|
|
40366
|
+
|
|
40367
|
+
function enlargedArea(a, b) {
|
|
40368
|
+
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
|
|
40369
|
+
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
|
|
40370
|
+
}
|
|
40371
|
+
|
|
40372
|
+
function intersectionArea(a, b) {
|
|
40373
|
+
const minX = Math.max(a.minX, b.minX);
|
|
40374
|
+
const minY = Math.max(a.minY, b.minY);
|
|
40375
|
+
const maxX = Math.min(a.maxX, b.maxX);
|
|
40376
|
+
const maxY = Math.min(a.maxY, b.maxY);
|
|
40377
|
+
|
|
40378
|
+
return Math.max(0, maxX - minX) *
|
|
40379
|
+
Math.max(0, maxY - minY);
|
|
40380
|
+
}
|
|
40381
|
+
|
|
40382
|
+
function contains(a, b) {
|
|
40383
|
+
return a.minX <= b.minX &&
|
|
40384
|
+
a.minY <= b.minY &&
|
|
40385
|
+
b.maxX <= a.maxX &&
|
|
40386
|
+
b.maxY <= a.maxY;
|
|
40387
|
+
}
|
|
40388
|
+
|
|
40389
|
+
function intersects(a, b) {
|
|
40390
|
+
return b.minX <= a.maxX &&
|
|
40391
|
+
b.minY <= a.maxY &&
|
|
40392
|
+
b.maxX >= a.minX &&
|
|
40393
|
+
b.maxY >= a.minY;
|
|
40394
|
+
}
|
|
40395
|
+
|
|
40396
|
+
function createNode(children) {
|
|
40397
|
+
return {
|
|
40398
|
+
children,
|
|
40399
|
+
height: 1,
|
|
40400
|
+
leaf: true,
|
|
40401
|
+
minX: Infinity,
|
|
40402
|
+
minY: Infinity,
|
|
40403
|
+
maxX: -Infinity,
|
|
40404
|
+
maxY: -Infinity
|
|
40405
|
+
};
|
|
40406
|
+
}
|
|
40407
|
+
|
|
40408
|
+
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
|
|
40409
|
+
// combines selection algorithm with binary divide & conquer approach
|
|
40410
|
+
|
|
40411
|
+
function multiSelect(arr, left, right, n, compare) {
|
|
40412
|
+
const stack = [left, right];
|
|
40413
|
+
|
|
40414
|
+
while (stack.length) {
|
|
40415
|
+
right = stack.pop();
|
|
40416
|
+
left = stack.pop();
|
|
40417
|
+
|
|
40418
|
+
if (right - left <= n) continue;
|
|
40419
|
+
|
|
40420
|
+
const mid = left + Math.ceil((right - left) / n / 2) * n;
|
|
40421
|
+
quickselect(arr, mid, left, right, compare);
|
|
40422
|
+
|
|
40423
|
+
stack.push(left, mid, mid, right);
|
|
40424
|
+
}
|
|
40425
|
+
}
|
|
40426
|
+
|
|
40427
|
+
/**
|
|
40428
|
+
* R-Tree Data Structure
|
|
40429
|
+
*
|
|
40430
|
+
* R-Tree is a spatial data structure used for efficient indexing and querying
|
|
40431
|
+
* of multi-dimensional objects, particularly in geometric and spatial applications.
|
|
40432
|
+
*
|
|
40433
|
+
* It organizes objects into a tree hierarchy, grouping nearby objects together
|
|
40434
|
+
* in bounding boxes. Each node in the tree represents a bounding box that
|
|
40435
|
+
* contains its child nodes or leaf objects. This hierarchical structure allows
|
|
40436
|
+
* for faster spatial queries.
|
|
40437
|
+
*
|
|
40438
|
+
* @see https://en.wikipedia.org/wiki/R-tree
|
|
40439
|
+
*
|
|
40440
|
+
* Consider a 2D Space with four zones: A, B, C, D
|
|
40441
|
+
* +--------------------------+
|
|
40442
|
+
* | |
|
|
40443
|
+
* | +---+ +-------+ |
|
|
40444
|
+
* | | A | | B | |
|
|
40445
|
+
* | +---+ +-------+ |
|
|
40446
|
+
* | |
|
|
40447
|
+
* | |
|
|
40448
|
+
* | +---+ |
|
|
40449
|
+
* | | C | |
|
|
40450
|
+
* | +---+ |
|
|
40451
|
+
* | +-----------+ |
|
|
40452
|
+
* | | D | |
|
|
40453
|
+
* | +-----------+ |
|
|
40454
|
+
* | |
|
|
40455
|
+
* +--------------------------+
|
|
40456
|
+
*
|
|
40457
|
+
* It groups together zones that are spatially close into a minimum bounding box.
|
|
40458
|
+
* For example, A and B are grouped together in rectangle R1, and C and D are grouped
|
|
40459
|
+
* in R2.
|
|
40460
|
+
*
|
|
40461
|
+
* R0
|
|
40462
|
+
* +--------------------------+
|
|
40463
|
+
* | R1 |
|
|
40464
|
+
* | +-----------------+ |
|
|
40465
|
+
* | | A | | B | |
|
|
40466
|
+
* | +-----------------+ |
|
|
40467
|
+
* | |
|
|
40468
|
+
* | R2 |
|
|
40469
|
+
* | +---+---+---+ |
|
|
40470
|
+
* | | | C | | |
|
|
40471
|
+
* | | +---+ | |
|
|
40472
|
+
* | +-----------+ |
|
|
40473
|
+
* | | D | |
|
|
40474
|
+
* | +-----------+ |
|
|
40475
|
+
* | |
|
|
40476
|
+
* +--------------------------+
|
|
40477
|
+
*
|
|
40478
|
+
* The tree would look like this:
|
|
40479
|
+
* R0
|
|
40480
|
+
* / \
|
|
40481
|
+
* / \
|
|
40482
|
+
* R1 R2
|
|
40483
|
+
* | |
|
|
40484
|
+
* A,B C,D
|
|
40485
|
+
|
|
40486
|
+
* Choosing how to group the zones is crucial for the performance of the tree.
|
|
40487
|
+
* Key considerations include avoiding excessive empty space coverage and minimizing overlap
|
|
40488
|
+
* to reduce the number of subtrees processed during searches.
|
|
40489
|
+
*
|
|
40490
|
+
* Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
|
|
40491
|
+
* which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
|
|
40492
|
+
* the choice cannot be made based on this criterion due to the same enlargement for different groupings,
|
|
40493
|
+
* we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
|
|
40494
|
+
*
|
|
40495
|
+
* This implementation is tailored for spreadsheet use, indexing objects associated
|
|
40496
|
+
* with a zone and a sheet.
|
|
40497
|
+
*
|
|
40498
|
+
* It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
|
|
40499
|
+
* @see https://github.com/mourner/rbush
|
|
40500
|
+
*/
|
|
40501
|
+
class SpreadsheetRTree {
|
|
40502
|
+
/**
|
|
40503
|
+
* One 2D R-tree per sheet
|
|
40504
|
+
*/
|
|
40505
|
+
rTrees = {};
|
|
40506
|
+
/**
|
|
40507
|
+
* Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
|
|
40508
|
+
* faster than inserting items one by one. After bulk loading (bulk insertion into
|
|
40509
|
+
* an empty tree), subsequent query performance is also ~20-30% better.
|
|
40510
|
+
*/
|
|
40511
|
+
constructor(items = []) {
|
|
40512
|
+
const rangesPerSheet = {};
|
|
40513
|
+
for (const item of items) {
|
|
40514
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40515
|
+
if (!rangesPerSheet[sheetId]) {
|
|
40516
|
+
rangesPerSheet[sheetId] = [];
|
|
40517
|
+
}
|
|
40518
|
+
rangesPerSheet[sheetId].push(item);
|
|
40519
|
+
}
|
|
40520
|
+
for (const sheetId in rangesPerSheet) {
|
|
40521
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40522
|
+
this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
|
|
40523
|
+
}
|
|
40524
|
+
}
|
|
40525
|
+
insert(item) {
|
|
40526
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40527
|
+
if (!this.rTrees[sheetId]) {
|
|
40528
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40529
|
+
}
|
|
40530
|
+
this.rTrees[sheetId].insert(item);
|
|
40531
|
+
}
|
|
40532
|
+
search({ zone, sheetId }) {
|
|
40533
|
+
if (!this.rTrees[sheetId]) {
|
|
40534
|
+
return [];
|
|
40535
|
+
}
|
|
40536
|
+
return this.rTrees[sheetId].search({
|
|
40537
|
+
minX: zone.left,
|
|
40538
|
+
minY: zone.top,
|
|
40539
|
+
maxX: zone.right,
|
|
40540
|
+
maxY: zone.bottom,
|
|
40541
|
+
});
|
|
40542
|
+
}
|
|
40543
|
+
remove(item) {
|
|
40544
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40545
|
+
if (!this.rTrees[sheetId]) {
|
|
40546
|
+
return;
|
|
40547
|
+
}
|
|
40548
|
+
this.rTrees[sheetId].remove(item, deepEquals);
|
|
40549
|
+
}
|
|
40550
|
+
}
|
|
40551
|
+
/**
|
|
40552
|
+
* RBush extension to use zones as bounding boxes
|
|
40553
|
+
*/
|
|
40554
|
+
class ZoneRBush extends RBush {
|
|
40555
|
+
toBBox({ boundingBox }) {
|
|
40556
|
+
const zone = boundingBox.zone;
|
|
40557
|
+
return {
|
|
40558
|
+
minX: zone.left,
|
|
40559
|
+
minY: zone.top,
|
|
40560
|
+
maxX: zone.right,
|
|
40561
|
+
maxY: zone.bottom,
|
|
40562
|
+
};
|
|
40563
|
+
}
|
|
40564
|
+
compareMinX(a, b) {
|
|
40565
|
+
return a.boundingBox.zone.left - b.boundingBox.zone.left;
|
|
40566
|
+
}
|
|
40567
|
+
compareMinY(a, b) {
|
|
40568
|
+
return a.boundingBox.zone.top - b.boundingBox.zone.top;
|
|
40569
|
+
}
|
|
40570
|
+
}
|
|
40571
|
+
|
|
39841
40572
|
/**
|
|
39842
|
-
*
|
|
40573
|
+
* Implementation of a dependency Graph.
|
|
39843
40574
|
* The graph is used to evaluate the cells in the correct
|
|
39844
40575
|
* order, and should be updated each time a cell's content is modified
|
|
39845
40576
|
*
|
|
40577
|
+
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
39846
40578
|
*/
|
|
39847
40579
|
class FormulaDependencyGraph {
|
|
39848
|
-
|
|
39849
|
-
* Internal structure:
|
|
39850
|
-
* - key: a cell position (encoded as an integer)
|
|
39851
|
-
* - value: a set of cell positions that depends on the key
|
|
39852
|
-
*
|
|
39853
|
-
* Given
|
|
39854
|
-
* - A1:"= B1 + SQRT(B2)"
|
|
39855
|
-
* - C1:"= B1";
|
|
39856
|
-
* - C2:"= C1"
|
|
39857
|
-
*
|
|
39858
|
-
* we will have something like:
|
|
39859
|
-
* - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
|
|
39860
|
-
* - B2 ---> (A1) meaning A1 depends on B2
|
|
39861
|
-
* - C1 ---> (C2) meaning C2 depends on C1
|
|
39862
|
-
*/
|
|
39863
|
-
inverseDependencies = new Map();
|
|
40580
|
+
encoder;
|
|
39864
40581
|
dependencies = new Map();
|
|
40582
|
+
rTree;
|
|
40583
|
+
constructor(encoder, data = []) {
|
|
40584
|
+
this.encoder = encoder;
|
|
40585
|
+
this.rTree = new SpreadsheetRTree(data);
|
|
40586
|
+
}
|
|
39865
40587
|
removeAllDependencies(formulaPositionId) {
|
|
39866
|
-
const
|
|
39867
|
-
if (!
|
|
40588
|
+
const ranges = this.dependencies.get(formulaPositionId);
|
|
40589
|
+
if (!ranges) {
|
|
39868
40590
|
return;
|
|
39869
40591
|
}
|
|
39870
|
-
for (const
|
|
39871
|
-
this.
|
|
40592
|
+
for (const range of ranges) {
|
|
40593
|
+
this.rTree.remove(range);
|
|
39872
40594
|
}
|
|
39873
40595
|
this.dependencies.delete(formulaPositionId);
|
|
39874
40596
|
}
|
|
39875
40597
|
addDependencies(formulaPositionId, dependencies) {
|
|
39876
|
-
|
|
39877
|
-
|
|
39878
|
-
|
|
39879
|
-
|
|
39880
|
-
|
|
39881
|
-
|
|
39882
|
-
|
|
39883
|
-
|
|
40598
|
+
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
40599
|
+
data: formulaPositionId,
|
|
40600
|
+
boundingBox: {
|
|
40601
|
+
zone,
|
|
40602
|
+
sheetId,
|
|
40603
|
+
},
|
|
40604
|
+
}));
|
|
40605
|
+
for (const item of rTreeItems) {
|
|
40606
|
+
this.rTree.insert(item);
|
|
39884
40607
|
}
|
|
39885
40608
|
const existingDependencies = this.dependencies.get(formulaPositionId);
|
|
39886
40609
|
if (existingDependencies) {
|
|
39887
|
-
existingDependencies.push(...
|
|
40610
|
+
existingDependencies.push(...rTreeItems);
|
|
39888
40611
|
}
|
|
39889
40612
|
else {
|
|
39890
|
-
this.dependencies.set(formulaPositionId,
|
|
40613
|
+
this.dependencies.set(formulaPositionId, rTreeItems);
|
|
39891
40614
|
}
|
|
39892
40615
|
}
|
|
39893
40616
|
/**
|
|
@@ -39895,20 +40618,20 @@ class FormulaDependencyGraph {
|
|
|
39895
40618
|
* in the correct order they should be evaluated.
|
|
39896
40619
|
* This is called a topological ordering (excluding cycles)
|
|
39897
40620
|
*/
|
|
39898
|
-
getCellsDependingOn(
|
|
40621
|
+
getCellsDependingOn(ranges) {
|
|
39899
40622
|
const visited = new JetSet();
|
|
39900
|
-
const queue = Array.from(
|
|
40623
|
+
const queue = Array.from(ranges).reverse();
|
|
39901
40624
|
while (queue.length > 0) {
|
|
39902
|
-
const
|
|
39903
|
-
visited.add(
|
|
39904
|
-
const
|
|
39905
|
-
for (const
|
|
39906
|
-
if (!visited.has(
|
|
39907
|
-
queue.push(
|
|
40625
|
+
const range = queue.pop();
|
|
40626
|
+
visited.add(...this.encoder.encodeBoundingBox(range));
|
|
40627
|
+
const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
|
|
40628
|
+
for (const positionId of impactedPositionIds) {
|
|
40629
|
+
if (!visited.has(positionId)) {
|
|
40630
|
+
queue.push(this.encoder.decodeToBoundingBox(positionId));
|
|
39908
40631
|
}
|
|
39909
40632
|
}
|
|
39910
40633
|
}
|
|
39911
|
-
visited.delete(...
|
|
40634
|
+
visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
|
|
39912
40635
|
return visited;
|
|
39913
40636
|
}
|
|
39914
40637
|
}
|
|
@@ -39996,9 +40719,9 @@ class Evaluator {
|
|
|
39996
40719
|
context;
|
|
39997
40720
|
getters;
|
|
39998
40721
|
compilationParams;
|
|
39999
|
-
|
|
40722
|
+
encoder = new PositionBitsEncoder();
|
|
40000
40723
|
evaluatedCells = new Map();
|
|
40001
|
-
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
40724
|
+
formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
|
|
40002
40725
|
blockedArrayFormulas = new Set();
|
|
40003
40726
|
spreadingRelations = new SpreadingRelation();
|
|
40004
40727
|
constructor(context, getters) {
|
|
@@ -40007,23 +40730,23 @@ class Evaluator {
|
|
|
40007
40730
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40008
40731
|
}
|
|
40009
40732
|
getEvaluatedCell(position) {
|
|
40010
|
-
return (this.evaluatedCells.get(this.
|
|
40733
|
+
return (this.evaluatedCells.get(this.encoder.encode(position)) ||
|
|
40011
40734
|
createEvaluatedCell("", { locale: this.getters.getLocale() }));
|
|
40012
40735
|
}
|
|
40013
40736
|
getSpreadPositionsOf(position) {
|
|
40014
|
-
const positionId = this.
|
|
40737
|
+
const positionId = this.encoder.encode(position);
|
|
40015
40738
|
if (!this.spreadingRelations.isArrayFormula(positionId)) {
|
|
40016
40739
|
return [];
|
|
40017
40740
|
}
|
|
40018
|
-
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.
|
|
40741
|
+
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
|
|
40019
40742
|
}
|
|
40020
40743
|
getArrayFormulaSpreadingOn(position) {
|
|
40021
|
-
const positionId = this.
|
|
40744
|
+
const positionId = this.encoder.encode(position);
|
|
40022
40745
|
const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
|
|
40023
|
-
return formulaPosition !== undefined ? this.
|
|
40746
|
+
return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
|
|
40024
40747
|
}
|
|
40025
40748
|
getEvaluatedPositions() {
|
|
40026
|
-
return [...this.evaluatedCells.keys()].map(this.
|
|
40749
|
+
return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
|
|
40027
40750
|
}
|
|
40028
40751
|
getArrayFormulaSpreadingOnId(positionId) {
|
|
40029
40752
|
if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
|
|
@@ -40033,7 +40756,7 @@ class Evaluator {
|
|
|
40033
40756
|
return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
|
|
40034
40757
|
}
|
|
40035
40758
|
updateDependencies(position) {
|
|
40036
|
-
const positionId = this.
|
|
40759
|
+
const positionId = this.encoder.encode(position);
|
|
40037
40760
|
this.formulaDependencies().removeAllDependencies(positionId);
|
|
40038
40761
|
const dependencies = this.getDirectDependencies(positionId);
|
|
40039
40762
|
this.formulaDependencies().addDependencies(positionId, dependencies);
|
|
@@ -40043,12 +40766,12 @@ class Evaluator {
|
|
|
40043
40766
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40044
40767
|
}
|
|
40045
40768
|
evaluateCells(positions) {
|
|
40046
|
-
const cells = positions.map(this.
|
|
40769
|
+
const cells = positions.map((p) => this.encoder.encode(p));
|
|
40047
40770
|
const cellsToCompute = new JetSet(cells);
|
|
40048
|
-
const
|
|
40771
|
+
const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
|
|
40049
40772
|
cellsToCompute.add(...this.getCellsDependingOn(cells));
|
|
40050
|
-
cellsToCompute.add(...
|
|
40051
|
-
cellsToCompute.add(...this.getCellsDependingOn(
|
|
40773
|
+
cellsToCompute.add(...arrayFormulasPositionIds);
|
|
40774
|
+
cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
|
|
40052
40775
|
this.evaluate(cellsToCompute);
|
|
40053
40776
|
}
|
|
40054
40777
|
getArrayFormulasImpactedByChangesOf(positionIds) {
|
|
@@ -40071,12 +40794,14 @@ class Evaluator {
|
|
|
40071
40794
|
this.blockedArrayFormulas = new Set();
|
|
40072
40795
|
this.spreadingRelations = new SpreadingRelation();
|
|
40073
40796
|
this.formulaDependencies = lazy(() => {
|
|
40074
|
-
const
|
|
40075
|
-
|
|
40076
|
-
|
|
40077
|
-
|
|
40078
|
-
|
|
40079
|
-
|
|
40797
|
+
const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
|
|
40798
|
+
data: positionId,
|
|
40799
|
+
boundingBox: {
|
|
40800
|
+
zone: range.zone,
|
|
40801
|
+
sheetId: range.sheetId,
|
|
40802
|
+
},
|
|
40803
|
+
})));
|
|
40804
|
+
return new FormulaDependencyGraph(this.encoder, dependencies);
|
|
40080
40805
|
});
|
|
40081
40806
|
}
|
|
40082
40807
|
evaluateAllCells() {
|
|
@@ -40098,7 +40823,7 @@ class Evaluator {
|
|
|
40098
40823
|
for (const sheetId of this.getters.getSheetIds()) {
|
|
40099
40824
|
const cellIds = this.getters.getCells(sheetId);
|
|
40100
40825
|
for (const cellId in cellIds) {
|
|
40101
|
-
positionIds.add(this.
|
|
40826
|
+
positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
|
|
40102
40827
|
}
|
|
40103
40828
|
}
|
|
40104
40829
|
return positionIds;
|
|
@@ -40165,7 +40890,7 @@ class Evaluator {
|
|
|
40165
40890
|
}
|
|
40166
40891
|
}
|
|
40167
40892
|
computeAndSave(position) {
|
|
40168
|
-
const positionId = this.
|
|
40893
|
+
const positionId = this.encoder.encode(position);
|
|
40169
40894
|
const evaluatedCell = this.computeCell(positionId);
|
|
40170
40895
|
if (!this.evaluatedCells.has(positionId)) {
|
|
40171
40896
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
@@ -40225,15 +40950,15 @@ class Evaluator {
|
|
|
40225
40950
|
throw new Error(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
|
|
40226
40951
|
}
|
|
40227
40952
|
updateSpreadRelation({ sheetId, col, row, }) {
|
|
40228
|
-
const arrayFormulaPositionId = this.
|
|
40953
|
+
const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40229
40954
|
return (i, j) => {
|
|
40230
40955
|
const position = { sheetId, col: i + col, row: j + row };
|
|
40231
|
-
const resultPositionId = this.
|
|
40956
|
+
const resultPositionId = this.encoder.encode(position);
|
|
40232
40957
|
this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
|
|
40233
40958
|
};
|
|
40234
40959
|
}
|
|
40235
40960
|
checkCollision({ sheetId, col, row }) {
|
|
40236
|
-
const formulaPositionId = this.
|
|
40961
|
+
const formulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40237
40962
|
return (i, j) => {
|
|
40238
40963
|
const position = { sheetId: sheetId, col: i + col, row: j + row };
|
|
40239
40964
|
const rawCell = this.getters.getCell(position);
|
|
@@ -40254,7 +40979,7 @@ class Evaluator {
|
|
|
40254
40979
|
format: format || matrixResult[i][j]?.format,
|
|
40255
40980
|
locale: this.getters.getLocale(),
|
|
40256
40981
|
});
|
|
40257
|
-
const positionId = this.
|
|
40982
|
+
const positionId = this.encoder.encode(position);
|
|
40258
40983
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
40259
40984
|
// check if formula dependencies present in the spread zone
|
|
40260
40985
|
// if so, they need to be recomputed
|
|
@@ -40286,29 +41011,17 @@ class Evaluator {
|
|
|
40286
41011
|
if (!cell?.isFormula) {
|
|
40287
41012
|
return [];
|
|
40288
41013
|
}
|
|
40289
|
-
|
|
40290
|
-
for (const range of cell.compiledFormula.dependencies) {
|
|
40291
|
-
if (range.invalidSheetName || range.invalidXc) {
|
|
40292
|
-
continue;
|
|
40293
|
-
}
|
|
40294
|
-
const sheetId = range.sheetId;
|
|
40295
|
-
forEachPositionsInZone(range.zone, (col, row) => {
|
|
40296
|
-
dependencies.push(this.encodePosition({ sheetId, col, row }));
|
|
40297
|
-
});
|
|
40298
|
-
}
|
|
40299
|
-
return dependencies;
|
|
41014
|
+
return cell.compiledFormula.dependencies;
|
|
40300
41015
|
}
|
|
40301
41016
|
getCellsDependingOn(positionIds) {
|
|
40302
|
-
|
|
41017
|
+
const ranges = [];
|
|
41018
|
+
for (const positionId of positionIds) {
|
|
41019
|
+
ranges.push(this.encoder.decodeToBoundingBox(positionId));
|
|
41020
|
+
}
|
|
41021
|
+
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
40303
41022
|
}
|
|
40304
41023
|
getCell(positionId) {
|
|
40305
|
-
return this.getters.getCell(this.
|
|
40306
|
-
}
|
|
40307
|
-
encodePosition(position) {
|
|
40308
|
-
return this.positionEncoder.encode(position);
|
|
40309
|
-
}
|
|
40310
|
-
decodePosition(positionId) {
|
|
40311
|
-
return this.positionEncoder.decode(positionId);
|
|
41024
|
+
return this.getters.getCell(this.encoder.decode(positionId));
|
|
40312
41025
|
}
|
|
40313
41026
|
}
|
|
40314
41027
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -40364,6 +41077,13 @@ class PositionBitsEncoder {
|
|
|
40364
41077
|
encode({ sheetId, col, row }) {
|
|
40365
41078
|
return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
|
|
40366
41079
|
}
|
|
41080
|
+
encodeBoundingBox({ sheetId, zone }) {
|
|
41081
|
+
const positions = [];
|
|
41082
|
+
forEachPositionsInZone(zone, (col, row) => {
|
|
41083
|
+
positions.push(this.encode({ sheetId, col, row }));
|
|
41084
|
+
});
|
|
41085
|
+
return positions;
|
|
41086
|
+
}
|
|
40367
41087
|
decode(id) {
|
|
40368
41088
|
// keep only the last 21 bits by AND-ing the bit sequence with 21 ones
|
|
40369
41089
|
const row = Number(id & 2097151n);
|
|
@@ -40371,6 +41091,10 @@ class PositionBitsEncoder {
|
|
|
40371
41091
|
const sheetId = this.decodeSheet(id >> 42n);
|
|
40372
41092
|
return { sheetId, col, row };
|
|
40373
41093
|
}
|
|
41094
|
+
decodeToBoundingBox(id) {
|
|
41095
|
+
const { sheetId, col, row } = this.decode(id);
|
|
41096
|
+
return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
|
|
41097
|
+
}
|
|
40374
41098
|
encodeSheet(sheetId) {
|
|
40375
41099
|
const sheetKey = this.sheetMapping[sheetId];
|
|
40376
41100
|
if (sheetKey === undefined) {
|
|
@@ -40541,7 +41265,12 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
40541
41265
|
// Getters
|
|
40542
41266
|
// ---------------------------------------------------------------------------
|
|
40543
41267
|
evaluateFormula(sheetId, formulaString) {
|
|
40544
|
-
|
|
41268
|
+
try {
|
|
41269
|
+
return this.evaluator.evaluateFormula(sheetId, formulaString);
|
|
41270
|
+
}
|
|
41271
|
+
catch (error) {
|
|
41272
|
+
return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
|
|
41273
|
+
}
|
|
40545
41274
|
}
|
|
40546
41275
|
/**
|
|
40547
41276
|
* Return the value of each cell in the range as they are displayed in the grid.
|
|
@@ -40810,7 +41539,7 @@ class CustomColorsPlugin extends UIPlugin {
|
|
|
40810
41539
|
}
|
|
40811
41540
|
|
|
40812
41541
|
class EvaluationChartPlugin extends UIPlugin {
|
|
40813
|
-
static getters = ["getChartRuntime", "
|
|
41542
|
+
static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
|
|
40814
41543
|
charts = {};
|
|
40815
41544
|
createRuntimeChart = chartRuntimeFactory(this.getters);
|
|
40816
41545
|
handle(cmd) {
|
|
@@ -40848,25 +41577,26 @@ class EvaluationChartPlugin extends UIPlugin {
|
|
|
40848
41577
|
return this.charts[figureId];
|
|
40849
41578
|
}
|
|
40850
41579
|
/**
|
|
40851
|
-
* Get the background
|
|
40852
|
-
* of the chart. In order of priority, it will return :
|
|
40853
|
-
*
|
|
40854
|
-
* - the chart background color if one is defined
|
|
40855
|
-
* - the fill color of the cell if one is defined
|
|
40856
|
-
* - the fill color of the cell from conditional formats if one is defined
|
|
40857
|
-
* - the default chart color if no other color is defined
|
|
41580
|
+
* Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
|
|
40858
41581
|
*/
|
|
40859
|
-
|
|
41582
|
+
getStyleOfSingleCellChart(chartBackground, mainRange) {
|
|
40860
41583
|
if (chartBackground)
|
|
40861
|
-
return chartBackground;
|
|
41584
|
+
return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
|
|
40862
41585
|
if (!mainRange) {
|
|
40863
|
-
return
|
|
41586
|
+
return {
|
|
41587
|
+
background: BACKGROUND_CHART_COLOR,
|
|
41588
|
+
fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
|
|
41589
|
+
};
|
|
40864
41590
|
}
|
|
40865
41591
|
const col = mainRange.zone.left;
|
|
40866
41592
|
const row = mainRange.zone.top;
|
|
40867
41593
|
const sheetId = mainRange.sheetId;
|
|
40868
41594
|
const style = this.getters.getCellComputedStyle({ sheetId, col, row });
|
|
40869
|
-
|
|
41595
|
+
const background = style.fillColor || BACKGROUND_CHART_COLOR;
|
|
41596
|
+
return {
|
|
41597
|
+
background,
|
|
41598
|
+
fontColor: style.textColor || chartFontColor(background),
|
|
41599
|
+
};
|
|
40870
41600
|
}
|
|
40871
41601
|
exportForExcel(data) {
|
|
40872
41602
|
for (const sheet of data.sheets) {
|
|
@@ -40977,45 +41707,40 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
|
|
|
40977
41707
|
getComputedStyles(sheetId) {
|
|
40978
41708
|
const computedStyle = {};
|
|
40979
41709
|
for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
|
|
40980
|
-
|
|
40981
|
-
|
|
40982
|
-
|
|
40983
|
-
|
|
40984
|
-
|
|
40985
|
-
|
|
40986
|
-
|
|
40987
|
-
|
|
40988
|
-
|
|
40989
|
-
|
|
40990
|
-
|
|
40991
|
-
for (let
|
|
40992
|
-
|
|
40993
|
-
|
|
40994
|
-
|
|
40995
|
-
const
|
|
40996
|
-
|
|
40997
|
-
|
|
40998
|
-
|
|
40999
|
-
|
|
41000
|
-
|
|
41001
|
-
});
|
|
41002
|
-
}
|
|
41003
|
-
return value;
|
|
41004
|
-
});
|
|
41005
|
-
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41006
|
-
if (!computedStyle[col])
|
|
41007
|
-
computedStyle[col] = [];
|
|
41008
|
-
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41009
|
-
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41710
|
+
switch (cf.rule.type) {
|
|
41711
|
+
case "ColorScaleRule":
|
|
41712
|
+
for (let range of cf.ranges) {
|
|
41713
|
+
this.applyColorScale(sheetId, range, cf.rule, computedStyle);
|
|
41714
|
+
}
|
|
41715
|
+
break;
|
|
41716
|
+
case "CellIsRule":
|
|
41717
|
+
const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
|
|
41718
|
+
for (let ref of cf.ranges) {
|
|
41719
|
+
const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
|
|
41720
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
41721
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
41722
|
+
const predicate = this.rulePredicate[cf.rule.type];
|
|
41723
|
+
const target = { sheetId, col, row };
|
|
41724
|
+
const values = cf.rule.values.map((value, i) => {
|
|
41725
|
+
const compiledFormula = formulas[i];
|
|
41726
|
+
if (compiledFormula) {
|
|
41727
|
+
return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
|
|
41728
|
+
...compiledFormula,
|
|
41729
|
+
dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
|
|
41730
|
+
});
|
|
41010
41731
|
}
|
|
41732
|
+
return value;
|
|
41733
|
+
});
|
|
41734
|
+
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41735
|
+
if (!computedStyle[col])
|
|
41736
|
+
computedStyle[col] = [];
|
|
41737
|
+
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41738
|
+
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41011
41739
|
}
|
|
41012
41740
|
}
|
|
41013
41741
|
}
|
|
41014
|
-
|
|
41015
|
-
|
|
41016
|
-
}
|
|
41017
|
-
catch (_) {
|
|
41018
|
-
// we don't care about the errors within the evaluation of a rule
|
|
41742
|
+
}
|
|
41743
|
+
break;
|
|
41019
41744
|
}
|
|
41020
41745
|
}
|
|
41021
41746
|
return computedStyle;
|
|
@@ -41786,19 +42511,21 @@ class AutofillPlugin extends UIPlugin {
|
|
|
41786
42511
|
let col = zone.left;
|
|
41787
42512
|
let row = zone.bottom;
|
|
41788
42513
|
if (col > 0) {
|
|
41789
|
-
let
|
|
41790
|
-
while (
|
|
42514
|
+
let leftPosition = { sheetId, col: col - 1, row };
|
|
42515
|
+
while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
|
|
42516
|
+
this.getters.getCell(leftPosition)?.content) {
|
|
41791
42517
|
row += 1;
|
|
41792
|
-
|
|
42518
|
+
leftPosition = { sheetId, col: col - 1, row };
|
|
41793
42519
|
}
|
|
41794
42520
|
}
|
|
41795
42521
|
if (row === zone.bottom) {
|
|
41796
42522
|
col = zone.right;
|
|
41797
42523
|
if (col <= this.getters.getNumberCols(sheetId)) {
|
|
41798
|
-
let
|
|
41799
|
-
while (
|
|
42524
|
+
let rightPosition = { sheetId, col: col + 1, row };
|
|
42525
|
+
while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
|
|
42526
|
+
this.getters.getCell(rightPosition)?.content) {
|
|
41800
42527
|
row += 1;
|
|
41801
|
-
|
|
42528
|
+
rightPosition = { sheetId, col: col + 1, row };
|
|
41802
42529
|
}
|
|
41803
42530
|
}
|
|
41804
42531
|
}
|
|
@@ -42790,6 +43517,7 @@ class Session extends EventBus {
|
|
|
42790
43517
|
isReplayingInitialRevisions = false;
|
|
42791
43518
|
processedRevisions = new Set();
|
|
42792
43519
|
uuidGenerator = new UuidGenerator();
|
|
43520
|
+
lastLocalOperation;
|
|
42793
43521
|
/**
|
|
42794
43522
|
* Manages the collaboration between multiple users on the same spreadsheet.
|
|
42795
43523
|
* It can forward local state changes to other users to ensure they all eventually
|
|
@@ -42822,6 +43550,11 @@ class Session extends EventBus {
|
|
|
42822
43550
|
return;
|
|
42823
43551
|
const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes, Date.now());
|
|
42824
43552
|
this.revisions.append(revision.id, revision);
|
|
43553
|
+
// REQUEST_REDO just repeats the last operation, the
|
|
43554
|
+
// last operation is still the same and should not change.
|
|
43555
|
+
if (rootCommand.type !== "REQUEST_REDO") {
|
|
43556
|
+
this.lastLocalOperation = revision;
|
|
43557
|
+
}
|
|
42825
43558
|
this.trigger("new-local-state-update", { id: revision.id });
|
|
42826
43559
|
this.sendUpdateMessage({
|
|
42827
43560
|
type: "REMOTE_REVISION",
|
|
@@ -42918,19 +43651,10 @@ class Session extends EventBus {
|
|
|
42918
43651
|
return this.pendingMessages.length === 0;
|
|
42919
43652
|
}
|
|
42920
43653
|
/**
|
|
42921
|
-
* Get the last local revision
|
|
43654
|
+
* Get the last local revision
|
|
42922
43655
|
* */
|
|
42923
|
-
getLastLocalNonEmptyRevision(
|
|
42924
|
-
|
|
42925
|
-
for (const rev of revisions) {
|
|
42926
|
-
if (rev.rootCommand === "SNAPSHOT")
|
|
42927
|
-
return undefined;
|
|
42928
|
-
if (!rev.rootCommand || rev.rootCommand === "REMOTE")
|
|
42929
|
-
continue;
|
|
42930
|
-
if (!ignoredRootCommands.includes(rev.rootCommand?.type) && rev.commands.length)
|
|
42931
|
-
return rev;
|
|
42932
|
-
}
|
|
42933
|
-
return undefined;
|
|
43656
|
+
getLastLocalNonEmptyRevision() {
|
|
43657
|
+
return this.lastLocalOperation;
|
|
42934
43658
|
}
|
|
42935
43659
|
_move(position) {
|
|
42936
43660
|
// this method is debounced and might be called after the client
|
|
@@ -42990,7 +43714,7 @@ class Session extends EventBus {
|
|
|
42990
43714
|
break;
|
|
42991
43715
|
case "REMOTE_REVISION":
|
|
42992
43716
|
const { clientId, commands, timestamp } = message;
|
|
42993
|
-
const revision = new Revision(message.nextRevisionId, clientId, commands,
|
|
43717
|
+
const revision = new Revision(message.nextRevisionId, clientId, commands, undefined, undefined, timestamp);
|
|
42994
43718
|
if (revision.clientId !== this.clientId) {
|
|
42995
43719
|
this.revisions.insert(revision.id, revision, message.serverRevisionId);
|
|
42996
43720
|
const pendingCommands = this.pendingMessages
|
|
@@ -43003,10 +43727,11 @@ class Session extends EventBus {
|
|
|
43003
43727
|
}
|
|
43004
43728
|
break;
|
|
43005
43729
|
case "SNAPSHOT_CREATED": {
|
|
43006
|
-
const revision = new Revision(message.nextRevisionId, "server", [],
|
|
43730
|
+
const revision = new Revision(message.nextRevisionId, "server", [], undefined, undefined, Date.now());
|
|
43007
43731
|
this.revisions.insert(revision.id, revision, message.serverRevisionId);
|
|
43008
43732
|
this.dropPendingHistoryMessages();
|
|
43009
43733
|
this.trigger("snapshot");
|
|
43734
|
+
this.lastLocalOperation = undefined;
|
|
43010
43735
|
break;
|
|
43011
43736
|
}
|
|
43012
43737
|
}
|
|
@@ -45561,6 +46286,9 @@ class SelectionInputsManagerPlugin extends UIPlugin {
|
|
|
45561
46286
|
// Other
|
|
45562
46287
|
// ---------------------------------------------------------------------------
|
|
45563
46288
|
initInput(id, initialRanges, inputHasSingleRange = false) {
|
|
46289
|
+
if (this.inputs[id]) {
|
|
46290
|
+
this.unfocus();
|
|
46291
|
+
}
|
|
45564
46292
|
this.inputs[id] = new SelectionInputPlugin(this.config, initialRanges, inputHasSingleRange);
|
|
45565
46293
|
if (initialRanges.length === 0) {
|
|
45566
46294
|
const input = this.inputs[id];
|
|
@@ -46359,7 +47087,7 @@ class HistoryPlugin extends UIPlugin {
|
|
|
46359
47087
|
* Ignore standard undo/redo revisions (that are empty)
|
|
46360
47088
|
*/
|
|
46361
47089
|
getPossibleRevisionToRepeat() {
|
|
46362
|
-
return this.session.getLastLocalNonEmptyRevision(
|
|
47090
|
+
return this.session.getLastLocalNonEmptyRevision();
|
|
46363
47091
|
}
|
|
46364
47092
|
}
|
|
46365
47093
|
|
|
@@ -47144,6 +47872,10 @@ function setXcToReferenceType(xc, referenceType) {
|
|
|
47144
47872
|
return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
|
|
47145
47873
|
case "colrow":
|
|
47146
47874
|
indexOfNumber = xc.search(/[0-9]/);
|
|
47875
|
+
if (indexOfNumber === -1 || indexOfNumber === 0) {
|
|
47876
|
+
// no row number (eg. A) or no column (eg. 1)
|
|
47877
|
+
return "$" + xc;
|
|
47878
|
+
}
|
|
47147
47879
|
xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
|
|
47148
47880
|
return "$" + xc;
|
|
47149
47881
|
case "none":
|
|
@@ -47914,6 +48646,12 @@ class FilterEvaluationPlugin extends UIPlugin {
|
|
|
47914
48646
|
break;
|
|
47915
48647
|
case "HIDE_COLUMNS_ROWS":
|
|
47916
48648
|
case "UNHIDE_COLUMNS_ROWS":
|
|
48649
|
+
case "GROUP_HEADERS":
|
|
48650
|
+
case "UNGROUP_HEADERS":
|
|
48651
|
+
case "FOLD_HEADER_GROUP":
|
|
48652
|
+
case "UNFOLD_HEADER_GROUP":
|
|
48653
|
+
case "FOLD_ALL_HEADER_GROUPS":
|
|
48654
|
+
case "UNFOLD_ALL_HEADER_GROUPS":
|
|
47917
48655
|
this.updateHiddenRows();
|
|
47918
48656
|
break;
|
|
47919
48657
|
case "UPDATE_FILTER":
|
|
@@ -48615,7 +49353,7 @@ class GridSelectionPlugin extends UIPlugin {
|
|
|
48615
49353
|
sheetId: cmd.sheetId,
|
|
48616
49354
|
base: cmd.base,
|
|
48617
49355
|
quantity: thickness,
|
|
48618
|
-
position:
|
|
49356
|
+
position: cmd.position,
|
|
48619
49357
|
});
|
|
48620
49358
|
const isCol = cmd.dimension === "COL";
|
|
48621
49359
|
const start = cmd.elements[0];
|
|
@@ -48632,12 +49370,13 @@ class GridSelectionPlugin extends UIPlugin {
|
|
|
48632
49370
|
},
|
|
48633
49371
|
];
|
|
48634
49372
|
const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
|
|
49373
|
+
const base = isBasedBefore ? cmd.base : cmd.base + 1;
|
|
48635
49374
|
const pasteTarget = [
|
|
48636
49375
|
{
|
|
48637
|
-
left: isCol ?
|
|
48638
|
-
right: isCol ?
|
|
48639
|
-
top: !isCol ?
|
|
48640
|
-
bottom: !isCol ?
|
|
49376
|
+
left: isCol ? base : 0,
|
|
49377
|
+
right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
|
|
49378
|
+
top: !isCol ? base : 0,
|
|
49379
|
+
bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
|
|
48641
49380
|
},
|
|
48642
49381
|
];
|
|
48643
49382
|
state.paste(pasteTarget, { selectTarget: true });
|
|
@@ -48672,6 +49411,11 @@ class GridSelectionPlugin extends UIPlugin {
|
|
|
48672
49411
|
doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
|
|
48673
49412
|
return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
|
|
48674
49413
|
}
|
|
49414
|
+
const headers = [cmd.base, ...cmd.elements];
|
|
49415
|
+
const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
|
|
49416
|
+
if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
|
|
49417
|
+
return "InvalidHeaderIndex" /* CommandResult.InvalidHeaderIndex */;
|
|
49418
|
+
}
|
|
48675
49419
|
return "Success" /* CommandResult.Success */;
|
|
48676
49420
|
}
|
|
48677
49421
|
//-------------------------------------------
|
|
@@ -49717,10 +50461,13 @@ class HeaderPositionsUIPlugin extends UIPlugin {
|
|
|
49717
50461
|
}
|
|
49718
50462
|
break;
|
|
49719
50463
|
case "UPDATE_CELL":
|
|
49720
|
-
if ("content" in cmd || "format" in cmd
|
|
50464
|
+
if ("content" in cmd || "format" in cmd) {
|
|
49721
50465
|
this.headerPositions = {};
|
|
49722
50466
|
this.isDirty = true;
|
|
49723
50467
|
}
|
|
50468
|
+
else {
|
|
50469
|
+
this.headerPositions[cmd.sheetId] = this.computeHeaderPositionsOfSheet(cmd.sheetId);
|
|
50470
|
+
}
|
|
49724
50471
|
break;
|
|
49725
50472
|
case "UPDATE_FILTER":
|
|
49726
50473
|
case "REMOVE_FILTER_TABLE":
|
|
@@ -51877,6 +52624,8 @@ css /* scss */ `
|
|
|
51877
52624
|
*:before,
|
|
51878
52625
|
*:after {
|
|
51879
52626
|
box-sizing: content-box;
|
|
52627
|
+
/** rtl not supported ATM */
|
|
52628
|
+
direction: ltr;
|
|
51880
52629
|
}
|
|
51881
52630
|
.o-separator {
|
|
51882
52631
|
border-bottom: ${MENU_SEPARATOR_BORDER_WIDTH}px solid ${SEPARATOR_COLOR};
|
|
@@ -52154,7 +52903,7 @@ class Spreadsheet extends owl.Component {
|
|
|
52154
52903
|
}
|
|
52155
52904
|
onKeydown(ev) {
|
|
52156
52905
|
let keyDownString = "";
|
|
52157
|
-
if (ev
|
|
52906
|
+
if (isCtrlKey(ev)) {
|
|
52158
52907
|
keyDownString += "CTRL+";
|
|
52159
52908
|
}
|
|
52160
52909
|
keyDownString += ev.key.toUpperCase();
|
|
@@ -55866,6 +56615,7 @@ const helpers = {
|
|
|
55866
56615
|
colorToRGBA,
|
|
55867
56616
|
positionToZone,
|
|
55868
56617
|
isDefined: isDefined$1,
|
|
56618
|
+
isMatrix,
|
|
55869
56619
|
lazy,
|
|
55870
56620
|
genericRepeat,
|
|
55871
56621
|
createAction,
|
|
@@ -55949,6 +56699,6 @@ exports.setTranslationMethod = setTranslationMethod;
|
|
|
55949
56699
|
exports.tokenize = tokenize;
|
|
55950
56700
|
|
|
55951
56701
|
|
|
55952
|
-
__info__.version = "17.0.
|
|
55953
|
-
__info__.date = "
|
|
55954
|
-
__info__.hash = "
|
|
56702
|
+
__info__.version = "17.0.6";
|
|
56703
|
+
__info__.date = "2024-01-04T12:23:10.357Z";
|
|
56704
|
+
__info__.hash = "310ca57";
|