@odoo/o-spreadsheet 17.0.5 → 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 +877 -167
- package/dist/o-spreadsheet.d.ts +17 -17
- package/dist/o-spreadsheet.esm.js +877 -167
- package/dist/o-spreadsheet.iife.js +877 -167
- package/dist/o-spreadsheet.iife.min.js +206 -206
- package/dist/o_spreadsheet.xml +3 -3
- 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
|
}
|
|
@@ -5288,7 +5281,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5288
5281
|
};
|
|
5289
5282
|
baselineCell = getters.getEvaluatedCell(baselinePosition);
|
|
5290
5283
|
}
|
|
5291
|
-
const background = getters.
|
|
5284
|
+
const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
|
|
5292
5285
|
const locale = getters.getLocale();
|
|
5293
5286
|
return {
|
|
5294
5287
|
title: _t(chart.title),
|
|
@@ -5297,7 +5290,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5297
5290
|
baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
|
|
5298
5291
|
baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
|
|
5299
5292
|
baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
|
|
5300
|
-
fontColor
|
|
5293
|
+
fontColor,
|
|
5301
5294
|
background,
|
|
5302
5295
|
baselineStyle: chart.baselineMode !== "percentage" && baseline
|
|
5303
5296
|
? getters.getCellStyle({
|
|
@@ -6592,6 +6585,7 @@ const FilterMenuPopoverBuilder = {
|
|
|
6592
6585
|
},
|
|
6593
6586
|
};
|
|
6594
6587
|
|
|
6588
|
+
const macRegex = /Mac/i;
|
|
6595
6589
|
/**
|
|
6596
6590
|
* Return true if the event was triggered from
|
|
6597
6591
|
* a child element.
|
|
@@ -6631,7 +6625,15 @@ function getOpenedMenus() {
|
|
|
6631
6625
|
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
6632
6626
|
}
|
|
6633
6627
|
function isMacOS() {
|
|
6634
|
-
return navigator.userAgent
|
|
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;
|
|
6635
6637
|
}
|
|
6636
6638
|
|
|
6637
6639
|
/**
|
|
@@ -8880,7 +8882,7 @@ function createGaugeChartRuntime(chart, getters) {
|
|
|
8880
8882
|
});
|
|
8881
8883
|
return {
|
|
8882
8884
|
chartJsConfig: config,
|
|
8883
|
-
background: getters.
|
|
8885
|
+
background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
|
|
8884
8886
|
};
|
|
8885
8887
|
}
|
|
8886
8888
|
|
|
@@ -8961,7 +8963,7 @@ function getFormatMinDisplayUnit(format) {
|
|
|
8961
8963
|
else if (format.includes("h") || format.includes("H")) {
|
|
8962
8964
|
return "hour";
|
|
8963
8965
|
}
|
|
8964
|
-
else if (format.includes("
|
|
8966
|
+
else if (format.includes("d")) {
|
|
8965
8967
|
return "day";
|
|
8966
8968
|
}
|
|
8967
8969
|
else if (format.includes("M")) {
|
|
@@ -21214,10 +21216,10 @@ const arrowMap = {
|
|
|
21214
21216
|
function updateSelectionWithArrowKeys(ev, selection) {
|
|
21215
21217
|
const direction = arrowMap[ev.key];
|
|
21216
21218
|
if (ev.shiftKey) {
|
|
21217
|
-
selection.resizeAnchorZone(direction, ev
|
|
21219
|
+
selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21218
21220
|
}
|
|
21219
21221
|
else {
|
|
21220
|
-
selection.moveAnchorCell(direction, ev
|
|
21222
|
+
selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21221
21223
|
}
|
|
21222
21224
|
}
|
|
21223
21225
|
|
|
@@ -28202,7 +28204,10 @@ class GridOverlay extends owl.Component {
|
|
|
28202
28204
|
return;
|
|
28203
28205
|
}
|
|
28204
28206
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
28205
|
-
this.props.onCellClicked(col, row, {
|
|
28207
|
+
this.props.onCellClicked(col, row, {
|
|
28208
|
+
expandZone: ev.shiftKey,
|
|
28209
|
+
addZone: isCtrlKey(ev),
|
|
28210
|
+
});
|
|
28206
28211
|
}
|
|
28207
28212
|
onDoubleClick(ev) {
|
|
28208
28213
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
@@ -28432,7 +28437,7 @@ class AbstractResizer extends owl.Component {
|
|
|
28432
28437
|
this._increaseSelection(index);
|
|
28433
28438
|
}
|
|
28434
28439
|
else {
|
|
28435
|
-
this._selectElement(index, ev
|
|
28440
|
+
this._selectElement(index, isCtrlKey(ev));
|
|
28436
28441
|
}
|
|
28437
28442
|
this.lastSelectedElementIndex = index;
|
|
28438
28443
|
const mouseMoveSelect = (col, row) => {
|
|
@@ -28596,8 +28601,8 @@ class ColResizer extends AbstractResizer {
|
|
|
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");
|
|
@@ -28770,8 +28775,8 @@ class RowResizer extends AbstractResizer {
|
|
|
28770
28775
|
this.env.raiseError(MergeErrorMessage);
|
|
28771
28776
|
}
|
|
28772
28777
|
}
|
|
28773
|
-
_selectElement(index,
|
|
28774
|
-
this.env.model.selection.selectRow(index,
|
|
28778
|
+
_selectElement(index, addDistinctHeader) {
|
|
28779
|
+
this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28775
28780
|
}
|
|
28776
28781
|
_increaseSelection(index) {
|
|
28777
28782
|
this.env.model.selection.selectRow(index, "updateAnchor");
|
|
@@ -29573,17 +29578,17 @@ class Grid extends owl.Component {
|
|
|
29573
29578
|
// ---------------------------------------------------------------------------
|
|
29574
29579
|
// Zone selection with mouse
|
|
29575
29580
|
// ---------------------------------------------------------------------------
|
|
29576
|
-
onCellClicked(col, row, {
|
|
29581
|
+
onCellClicked(col, row, { addZone, expandZone }) {
|
|
29577
29582
|
if (this.env.model.getters.hasOpenedPopover()) {
|
|
29578
29583
|
this.closeOpenedPopover();
|
|
29579
29584
|
}
|
|
29580
29585
|
if (this.env.model.getters.getEditionMode() === "editing") {
|
|
29581
29586
|
interactiveStopEdition(this.env);
|
|
29582
29587
|
}
|
|
29583
|
-
if (
|
|
29588
|
+
if (expandZone) {
|
|
29584
29589
|
this.env.model.selection.setAnchorCorner(col, row);
|
|
29585
29590
|
}
|
|
29586
|
-
else if (
|
|
29591
|
+
else if (addZone) {
|
|
29587
29592
|
this.env.model.selection.addCellToSelection(col, row);
|
|
29588
29593
|
}
|
|
29589
29594
|
else {
|
|
@@ -29644,7 +29649,7 @@ class Grid extends owl.Component {
|
|
|
29644
29649
|
onKeydown(ev) {
|
|
29645
29650
|
const eventKey = ev.key.toUpperCase();
|
|
29646
29651
|
let keyDownString = "";
|
|
29647
|
-
if (ev
|
|
29652
|
+
if (isCtrlKey(ev) && eventKey !== "CTRL")
|
|
29648
29653
|
keyDownString += "CTRL+";
|
|
29649
29654
|
if (ev.metaKey)
|
|
29650
29655
|
keyDownString += "CTRL+";
|
|
@@ -34535,7 +34540,7 @@ class BordersPlugin extends CorePlugin {
|
|
|
34535
34540
|
*/
|
|
34536
34541
|
function getBorderId(border) {
|
|
34537
34542
|
for (let [key, value] of Object.entries(borders)) {
|
|
34538
|
-
if (
|
|
34543
|
+
if (deepEquals(value, border)) {
|
|
34539
34544
|
return parseInt(key, 10);
|
|
34540
34545
|
}
|
|
34541
34546
|
}
|
|
@@ -39854,56 +39859,758 @@ class CompilationParametersBuilder {
|
|
|
39854
39859
|
}
|
|
39855
39860
|
}
|
|
39856
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
|
+
|
|
39857
40427
|
/**
|
|
39858
|
-
*
|
|
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
|
+
|
|
40572
|
+
/**
|
|
40573
|
+
* Implementation of a dependency Graph.
|
|
39859
40574
|
* The graph is used to evaluate the cells in the correct
|
|
39860
40575
|
* order, and should be updated each time a cell's content is modified
|
|
39861
40576
|
*
|
|
40577
|
+
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
39862
40578
|
*/
|
|
39863
40579
|
class FormulaDependencyGraph {
|
|
39864
|
-
|
|
39865
|
-
* Internal structure:
|
|
39866
|
-
* - key: a cell position (encoded as an integer)
|
|
39867
|
-
* - value: a set of cell positions that depends on the key
|
|
39868
|
-
*
|
|
39869
|
-
* Given
|
|
39870
|
-
* - A1:"= B1 + SQRT(B2)"
|
|
39871
|
-
* - C1:"= B1";
|
|
39872
|
-
* - C2:"= C1"
|
|
39873
|
-
*
|
|
39874
|
-
* we will have something like:
|
|
39875
|
-
* - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
|
|
39876
|
-
* - B2 ---> (A1) meaning A1 depends on B2
|
|
39877
|
-
* - C1 ---> (C2) meaning C2 depends on C1
|
|
39878
|
-
*/
|
|
39879
|
-
inverseDependencies = new Map();
|
|
40580
|
+
encoder;
|
|
39880
40581
|
dependencies = new Map();
|
|
40582
|
+
rTree;
|
|
40583
|
+
constructor(encoder, data = []) {
|
|
40584
|
+
this.encoder = encoder;
|
|
40585
|
+
this.rTree = new SpreadsheetRTree(data);
|
|
40586
|
+
}
|
|
39881
40587
|
removeAllDependencies(formulaPositionId) {
|
|
39882
|
-
const
|
|
39883
|
-
if (!
|
|
40588
|
+
const ranges = this.dependencies.get(formulaPositionId);
|
|
40589
|
+
if (!ranges) {
|
|
39884
40590
|
return;
|
|
39885
40591
|
}
|
|
39886
|
-
for (const
|
|
39887
|
-
this.
|
|
40592
|
+
for (const range of ranges) {
|
|
40593
|
+
this.rTree.remove(range);
|
|
39888
40594
|
}
|
|
39889
40595
|
this.dependencies.delete(formulaPositionId);
|
|
39890
40596
|
}
|
|
39891
40597
|
addDependencies(formulaPositionId, dependencies) {
|
|
39892
|
-
|
|
39893
|
-
|
|
39894
|
-
|
|
39895
|
-
|
|
39896
|
-
|
|
39897
|
-
|
|
39898
|
-
|
|
39899
|
-
|
|
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);
|
|
39900
40607
|
}
|
|
39901
40608
|
const existingDependencies = this.dependencies.get(formulaPositionId);
|
|
39902
40609
|
if (existingDependencies) {
|
|
39903
|
-
existingDependencies.push(...
|
|
40610
|
+
existingDependencies.push(...rTreeItems);
|
|
39904
40611
|
}
|
|
39905
40612
|
else {
|
|
39906
|
-
this.dependencies.set(formulaPositionId,
|
|
40613
|
+
this.dependencies.set(formulaPositionId, rTreeItems);
|
|
39907
40614
|
}
|
|
39908
40615
|
}
|
|
39909
40616
|
/**
|
|
@@ -39911,20 +40618,20 @@ class FormulaDependencyGraph {
|
|
|
39911
40618
|
* in the correct order they should be evaluated.
|
|
39912
40619
|
* This is called a topological ordering (excluding cycles)
|
|
39913
40620
|
*/
|
|
39914
|
-
getCellsDependingOn(
|
|
40621
|
+
getCellsDependingOn(ranges) {
|
|
39915
40622
|
const visited = new JetSet();
|
|
39916
|
-
const queue = Array.from(
|
|
40623
|
+
const queue = Array.from(ranges).reverse();
|
|
39917
40624
|
while (queue.length > 0) {
|
|
39918
|
-
const
|
|
39919
|
-
visited.add(
|
|
39920
|
-
const
|
|
39921
|
-
for (const
|
|
39922
|
-
if (!visited.has(
|
|
39923
|
-
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));
|
|
39924
40631
|
}
|
|
39925
40632
|
}
|
|
39926
40633
|
}
|
|
39927
|
-
visited.delete(...
|
|
40634
|
+
visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
|
|
39928
40635
|
return visited;
|
|
39929
40636
|
}
|
|
39930
40637
|
}
|
|
@@ -40012,9 +40719,9 @@ class Evaluator {
|
|
|
40012
40719
|
context;
|
|
40013
40720
|
getters;
|
|
40014
40721
|
compilationParams;
|
|
40015
|
-
|
|
40722
|
+
encoder = new PositionBitsEncoder();
|
|
40016
40723
|
evaluatedCells = new Map();
|
|
40017
|
-
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
40724
|
+
formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
|
|
40018
40725
|
blockedArrayFormulas = new Set();
|
|
40019
40726
|
spreadingRelations = new SpreadingRelation();
|
|
40020
40727
|
constructor(context, getters) {
|
|
@@ -40023,23 +40730,23 @@ class Evaluator {
|
|
|
40023
40730
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40024
40731
|
}
|
|
40025
40732
|
getEvaluatedCell(position) {
|
|
40026
|
-
return (this.evaluatedCells.get(this.
|
|
40733
|
+
return (this.evaluatedCells.get(this.encoder.encode(position)) ||
|
|
40027
40734
|
createEvaluatedCell("", { locale: this.getters.getLocale() }));
|
|
40028
40735
|
}
|
|
40029
40736
|
getSpreadPositionsOf(position) {
|
|
40030
|
-
const positionId = this.
|
|
40737
|
+
const positionId = this.encoder.encode(position);
|
|
40031
40738
|
if (!this.spreadingRelations.isArrayFormula(positionId)) {
|
|
40032
40739
|
return [];
|
|
40033
40740
|
}
|
|
40034
|
-
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.
|
|
40741
|
+
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
|
|
40035
40742
|
}
|
|
40036
40743
|
getArrayFormulaSpreadingOn(position) {
|
|
40037
|
-
const positionId = this.
|
|
40744
|
+
const positionId = this.encoder.encode(position);
|
|
40038
40745
|
const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
|
|
40039
|
-
return formulaPosition !== undefined ? this.
|
|
40746
|
+
return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
|
|
40040
40747
|
}
|
|
40041
40748
|
getEvaluatedPositions() {
|
|
40042
|
-
return [...this.evaluatedCells.keys()].map(this.
|
|
40749
|
+
return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
|
|
40043
40750
|
}
|
|
40044
40751
|
getArrayFormulaSpreadingOnId(positionId) {
|
|
40045
40752
|
if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
|
|
@@ -40049,7 +40756,7 @@ class Evaluator {
|
|
|
40049
40756
|
return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
|
|
40050
40757
|
}
|
|
40051
40758
|
updateDependencies(position) {
|
|
40052
|
-
const positionId = this.
|
|
40759
|
+
const positionId = this.encoder.encode(position);
|
|
40053
40760
|
this.formulaDependencies().removeAllDependencies(positionId);
|
|
40054
40761
|
const dependencies = this.getDirectDependencies(positionId);
|
|
40055
40762
|
this.formulaDependencies().addDependencies(positionId, dependencies);
|
|
@@ -40059,12 +40766,12 @@ class Evaluator {
|
|
|
40059
40766
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40060
40767
|
}
|
|
40061
40768
|
evaluateCells(positions) {
|
|
40062
|
-
const cells = positions.map(this.
|
|
40769
|
+
const cells = positions.map((p) => this.encoder.encode(p));
|
|
40063
40770
|
const cellsToCompute = new JetSet(cells);
|
|
40064
|
-
const
|
|
40771
|
+
const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
|
|
40065
40772
|
cellsToCompute.add(...this.getCellsDependingOn(cells));
|
|
40066
|
-
cellsToCompute.add(...
|
|
40067
|
-
cellsToCompute.add(...this.getCellsDependingOn(
|
|
40773
|
+
cellsToCompute.add(...arrayFormulasPositionIds);
|
|
40774
|
+
cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
|
|
40068
40775
|
this.evaluate(cellsToCompute);
|
|
40069
40776
|
}
|
|
40070
40777
|
getArrayFormulasImpactedByChangesOf(positionIds) {
|
|
@@ -40087,12 +40794,14 @@ class Evaluator {
|
|
|
40087
40794
|
this.blockedArrayFormulas = new Set();
|
|
40088
40795
|
this.spreadingRelations = new SpreadingRelation();
|
|
40089
40796
|
this.formulaDependencies = lazy(() => {
|
|
40090
|
-
const
|
|
40091
|
-
|
|
40092
|
-
|
|
40093
|
-
|
|
40094
|
-
|
|
40095
|
-
|
|
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);
|
|
40096
40805
|
});
|
|
40097
40806
|
}
|
|
40098
40807
|
evaluateAllCells() {
|
|
@@ -40114,7 +40823,7 @@ class Evaluator {
|
|
|
40114
40823
|
for (const sheetId of this.getters.getSheetIds()) {
|
|
40115
40824
|
const cellIds = this.getters.getCells(sheetId);
|
|
40116
40825
|
for (const cellId in cellIds) {
|
|
40117
|
-
positionIds.add(this.
|
|
40826
|
+
positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
|
|
40118
40827
|
}
|
|
40119
40828
|
}
|
|
40120
40829
|
return positionIds;
|
|
@@ -40181,7 +40890,7 @@ class Evaluator {
|
|
|
40181
40890
|
}
|
|
40182
40891
|
}
|
|
40183
40892
|
computeAndSave(position) {
|
|
40184
|
-
const positionId = this.
|
|
40893
|
+
const positionId = this.encoder.encode(position);
|
|
40185
40894
|
const evaluatedCell = this.computeCell(positionId);
|
|
40186
40895
|
if (!this.evaluatedCells.has(positionId)) {
|
|
40187
40896
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
@@ -40241,15 +40950,15 @@ class Evaluator {
|
|
|
40241
40950
|
throw new Error(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
|
|
40242
40951
|
}
|
|
40243
40952
|
updateSpreadRelation({ sheetId, col, row, }) {
|
|
40244
|
-
const arrayFormulaPositionId = this.
|
|
40953
|
+
const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40245
40954
|
return (i, j) => {
|
|
40246
40955
|
const position = { sheetId, col: i + col, row: j + row };
|
|
40247
|
-
const resultPositionId = this.
|
|
40956
|
+
const resultPositionId = this.encoder.encode(position);
|
|
40248
40957
|
this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
|
|
40249
40958
|
};
|
|
40250
40959
|
}
|
|
40251
40960
|
checkCollision({ sheetId, col, row }) {
|
|
40252
|
-
const formulaPositionId = this.
|
|
40961
|
+
const formulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40253
40962
|
return (i, j) => {
|
|
40254
40963
|
const position = { sheetId: sheetId, col: i + col, row: j + row };
|
|
40255
40964
|
const rawCell = this.getters.getCell(position);
|
|
@@ -40270,7 +40979,7 @@ class Evaluator {
|
|
|
40270
40979
|
format: format || matrixResult[i][j]?.format,
|
|
40271
40980
|
locale: this.getters.getLocale(),
|
|
40272
40981
|
});
|
|
40273
|
-
const positionId = this.
|
|
40982
|
+
const positionId = this.encoder.encode(position);
|
|
40274
40983
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
40275
40984
|
// check if formula dependencies present in the spread zone
|
|
40276
40985
|
// if so, they need to be recomputed
|
|
@@ -40302,29 +41011,17 @@ class Evaluator {
|
|
|
40302
41011
|
if (!cell?.isFormula) {
|
|
40303
41012
|
return [];
|
|
40304
41013
|
}
|
|
40305
|
-
|
|
40306
|
-
for (const range of cell.compiledFormula.dependencies) {
|
|
40307
|
-
if (range.invalidSheetName || range.invalidXc) {
|
|
40308
|
-
continue;
|
|
40309
|
-
}
|
|
40310
|
-
const sheetId = range.sheetId;
|
|
40311
|
-
forEachPositionsInZone(range.zone, (col, row) => {
|
|
40312
|
-
dependencies.push(this.encodePosition({ sheetId, col, row }));
|
|
40313
|
-
});
|
|
40314
|
-
}
|
|
40315
|
-
return dependencies;
|
|
41014
|
+
return cell.compiledFormula.dependencies;
|
|
40316
41015
|
}
|
|
40317
41016
|
getCellsDependingOn(positionIds) {
|
|
40318
|
-
|
|
41017
|
+
const ranges = [];
|
|
41018
|
+
for (const positionId of positionIds) {
|
|
41019
|
+
ranges.push(this.encoder.decodeToBoundingBox(positionId));
|
|
41020
|
+
}
|
|
41021
|
+
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
40319
41022
|
}
|
|
40320
41023
|
getCell(positionId) {
|
|
40321
|
-
return this.getters.getCell(this.
|
|
40322
|
-
}
|
|
40323
|
-
encodePosition(position) {
|
|
40324
|
-
return this.positionEncoder.encode(position);
|
|
40325
|
-
}
|
|
40326
|
-
decodePosition(positionId) {
|
|
40327
|
-
return this.positionEncoder.decode(positionId);
|
|
41024
|
+
return this.getters.getCell(this.encoder.decode(positionId));
|
|
40328
41025
|
}
|
|
40329
41026
|
}
|
|
40330
41027
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -40380,6 +41077,13 @@ class PositionBitsEncoder {
|
|
|
40380
41077
|
encode({ sheetId, col, row }) {
|
|
40381
41078
|
return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
|
|
40382
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
|
+
}
|
|
40383
41087
|
decode(id) {
|
|
40384
41088
|
// keep only the last 21 bits by AND-ing the bit sequence with 21 ones
|
|
40385
41089
|
const row = Number(id & 2097151n);
|
|
@@ -40387,6 +41091,10 @@ class PositionBitsEncoder {
|
|
|
40387
41091
|
const sheetId = this.decodeSheet(id >> 42n);
|
|
40388
41092
|
return { sheetId, col, row };
|
|
40389
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
|
+
}
|
|
40390
41098
|
encodeSheet(sheetId) {
|
|
40391
41099
|
const sheetKey = this.sheetMapping[sheetId];
|
|
40392
41100
|
if (sheetKey === undefined) {
|
|
@@ -40557,7 +41265,12 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
40557
41265
|
// Getters
|
|
40558
41266
|
// ---------------------------------------------------------------------------
|
|
40559
41267
|
evaluateFormula(sheetId, formulaString) {
|
|
40560
|
-
|
|
41268
|
+
try {
|
|
41269
|
+
return this.evaluator.evaluateFormula(sheetId, formulaString);
|
|
41270
|
+
}
|
|
41271
|
+
catch (error) {
|
|
41272
|
+
return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
|
|
41273
|
+
}
|
|
40561
41274
|
}
|
|
40562
41275
|
/**
|
|
40563
41276
|
* Return the value of each cell in the range as they are displayed in the grid.
|
|
@@ -40826,7 +41539,7 @@ class CustomColorsPlugin extends UIPlugin {
|
|
|
40826
41539
|
}
|
|
40827
41540
|
|
|
40828
41541
|
class EvaluationChartPlugin extends UIPlugin {
|
|
40829
|
-
static getters = ["getChartRuntime", "
|
|
41542
|
+
static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
|
|
40830
41543
|
charts = {};
|
|
40831
41544
|
createRuntimeChart = chartRuntimeFactory(this.getters);
|
|
40832
41545
|
handle(cmd) {
|
|
@@ -40864,25 +41577,26 @@ class EvaluationChartPlugin extends UIPlugin {
|
|
|
40864
41577
|
return this.charts[figureId];
|
|
40865
41578
|
}
|
|
40866
41579
|
/**
|
|
40867
|
-
* Get the background
|
|
40868
|
-
* of the chart. In order of priority, it will return :
|
|
40869
|
-
*
|
|
40870
|
-
* - the chart background color if one is defined
|
|
40871
|
-
* - the fill color of the cell if one is defined
|
|
40872
|
-
* - the fill color of the cell from conditional formats if one is defined
|
|
40873
|
-
* - 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.
|
|
40874
41581
|
*/
|
|
40875
|
-
|
|
41582
|
+
getStyleOfSingleCellChart(chartBackground, mainRange) {
|
|
40876
41583
|
if (chartBackground)
|
|
40877
|
-
return chartBackground;
|
|
41584
|
+
return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
|
|
40878
41585
|
if (!mainRange) {
|
|
40879
|
-
return
|
|
41586
|
+
return {
|
|
41587
|
+
background: BACKGROUND_CHART_COLOR,
|
|
41588
|
+
fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
|
|
41589
|
+
};
|
|
40880
41590
|
}
|
|
40881
41591
|
const col = mainRange.zone.left;
|
|
40882
41592
|
const row = mainRange.zone.top;
|
|
40883
41593
|
const sheetId = mainRange.sheetId;
|
|
40884
41594
|
const style = this.getters.getCellComputedStyle({ sheetId, col, row });
|
|
40885
|
-
|
|
41595
|
+
const background = style.fillColor || BACKGROUND_CHART_COLOR;
|
|
41596
|
+
return {
|
|
41597
|
+
background,
|
|
41598
|
+
fontColor: style.textColor || chartFontColor(background),
|
|
41599
|
+
};
|
|
40886
41600
|
}
|
|
40887
41601
|
exportForExcel(data) {
|
|
40888
41602
|
for (const sheet of data.sheets) {
|
|
@@ -40993,45 +41707,40 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
|
|
|
40993
41707
|
getComputedStyles(sheetId) {
|
|
40994
41708
|
const computedStyle = {};
|
|
40995
41709
|
for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
|
|
40996
|
-
|
|
40997
|
-
|
|
40998
|
-
|
|
40999
|
-
|
|
41000
|
-
|
|
41001
|
-
|
|
41002
|
-
|
|
41003
|
-
|
|
41004
|
-
|
|
41005
|
-
|
|
41006
|
-
|
|
41007
|
-
for (let
|
|
41008
|
-
|
|
41009
|
-
|
|
41010
|
-
|
|
41011
|
-
const
|
|
41012
|
-
|
|
41013
|
-
|
|
41014
|
-
|
|
41015
|
-
|
|
41016
|
-
|
|
41017
|
-
});
|
|
41018
|
-
}
|
|
41019
|
-
return value;
|
|
41020
|
-
});
|
|
41021
|
-
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41022
|
-
if (!computedStyle[col])
|
|
41023
|
-
computedStyle[col] = [];
|
|
41024
|
-
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41025
|
-
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
|
+
});
|
|
41026
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);
|
|
41027
41739
|
}
|
|
41028
41740
|
}
|
|
41029
41741
|
}
|
|
41030
|
-
|
|
41031
|
-
|
|
41032
|
-
}
|
|
41033
|
-
catch (_) {
|
|
41034
|
-
// we don't care about the errors within the evaluation of a rule
|
|
41742
|
+
}
|
|
41743
|
+
break;
|
|
41035
41744
|
}
|
|
41036
41745
|
}
|
|
41037
41746
|
return computedStyle;
|
|
@@ -41803,7 +42512,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
41803
42512
|
let row = zone.bottom;
|
|
41804
42513
|
if (col > 0) {
|
|
41805
42514
|
let leftPosition = { sheetId, col: col - 1, row };
|
|
41806
|
-
while (this.getters.
|
|
42515
|
+
while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
|
|
41807
42516
|
this.getters.getCell(leftPosition)?.content) {
|
|
41808
42517
|
row += 1;
|
|
41809
42518
|
leftPosition = { sheetId, col: col - 1, row };
|
|
@@ -41813,7 +42522,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
41813
42522
|
col = zone.right;
|
|
41814
42523
|
if (col <= this.getters.getNumberCols(sheetId)) {
|
|
41815
42524
|
let rightPosition = { sheetId, col: col + 1, row };
|
|
41816
|
-
while (this.getters.
|
|
42525
|
+
while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
|
|
41817
42526
|
this.getters.getCell(rightPosition)?.content) {
|
|
41818
42527
|
row += 1;
|
|
41819
42528
|
rightPosition = { sheetId, col: col + 1, row };
|
|
@@ -52194,7 +52903,7 @@ class Spreadsheet extends owl.Component {
|
|
|
52194
52903
|
}
|
|
52195
52904
|
onKeydown(ev) {
|
|
52196
52905
|
let keyDownString = "";
|
|
52197
|
-
if (ev
|
|
52906
|
+
if (isCtrlKey(ev)) {
|
|
52198
52907
|
keyDownString += "CTRL+";
|
|
52199
52908
|
}
|
|
52200
52909
|
keyDownString += ev.key.toUpperCase();
|
|
@@ -55906,6 +56615,7 @@ const helpers = {
|
|
|
55906
56615
|
colorToRGBA,
|
|
55907
56616
|
positionToZone,
|
|
55908
56617
|
isDefined: isDefined$1,
|
|
56618
|
+
isMatrix,
|
|
55909
56619
|
lazy,
|
|
55910
56620
|
genericRepeat,
|
|
55911
56621
|
createAction,
|
|
@@ -55989,6 +56699,6 @@ exports.setTranslationMethod = setTranslationMethod;
|
|
|
55989
56699
|
exports.tokenize = tokenize;
|
|
55990
56700
|
|
|
55991
56701
|
|
|
55992
|
-
__info__.version = "17.0.
|
|
55993
|
-
__info__.date = "
|
|
55994
|
-
__info__.hash = "
|
|
56702
|
+
__info__.version = "17.0.6";
|
|
56703
|
+
__info__.date = "2024-01-04T12:23:10.357Z";
|
|
56704
|
+
__info__.hash = "310ca57";
|