@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
|
import { Component, useRef, onMounted, useEffect, onWillPatch, useState, onWillUpdateProps, onPatched, useComponent, useExternalListener, onWillUnmount, onWillStart, xml, useChildSubEnv, useSubEnv, markRaw } from '@odoo/owl';
|
|
@@ -329,13 +329,6 @@ const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
|
|
|
329
329
|
//------------------------------------------------------------------------------
|
|
330
330
|
// Miscellaneous
|
|
331
331
|
//------------------------------------------------------------------------------
|
|
332
|
-
/**
|
|
333
|
-
* Stringify an object, like JSON.stringify, except that the first level of keys
|
|
334
|
-
* is ordered.
|
|
335
|
-
*/
|
|
336
|
-
function stringify(obj) {
|
|
337
|
-
return JSON.stringify(obj, Object.keys(obj).sort());
|
|
338
|
-
}
|
|
339
332
|
/**
|
|
340
333
|
* Remove quotes from a quoted string
|
|
341
334
|
* ```js
|
|
@@ -558,7 +551,7 @@ function isObjectEmptyRecursive(argument) {
|
|
|
558
551
|
*/
|
|
559
552
|
function getItemId(item, itemsDic) {
|
|
560
553
|
for (let [key, value] of Object.entries(itemsDic)) {
|
|
561
|
-
if (
|
|
554
|
+
if (deepEquals(value, item)) {
|
|
562
555
|
return parseInt(key, 10);
|
|
563
556
|
}
|
|
564
557
|
}
|
|
@@ -5286,7 +5279,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5286
5279
|
};
|
|
5287
5280
|
baselineCell = getters.getEvaluatedCell(baselinePosition);
|
|
5288
5281
|
}
|
|
5289
|
-
const background = getters.
|
|
5282
|
+
const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
|
|
5290
5283
|
const locale = getters.getLocale();
|
|
5291
5284
|
return {
|
|
5292
5285
|
title: _t(chart.title),
|
|
@@ -5295,7 +5288,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
5295
5288
|
baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
|
|
5296
5289
|
baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
|
|
5297
5290
|
baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
|
|
5298
|
-
fontColor
|
|
5291
|
+
fontColor,
|
|
5299
5292
|
background,
|
|
5300
5293
|
baselineStyle: chart.baselineMode !== "percentage" && baseline
|
|
5301
5294
|
? getters.getCellStyle({
|
|
@@ -6590,6 +6583,7 @@ const FilterMenuPopoverBuilder = {
|
|
|
6590
6583
|
},
|
|
6591
6584
|
};
|
|
6592
6585
|
|
|
6586
|
+
const macRegex = /Mac/i;
|
|
6593
6587
|
/**
|
|
6594
6588
|
* Return true if the event was triggered from
|
|
6595
6589
|
* a child element.
|
|
@@ -6629,7 +6623,15 @@ function getOpenedMenus() {
|
|
|
6629
6623
|
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
6630
6624
|
}
|
|
6631
6625
|
function isMacOS() {
|
|
6632
|
-
return navigator.userAgent
|
|
6626
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
6627
|
+
}
|
|
6628
|
+
/**
|
|
6629
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
6630
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
6631
|
+
* On Mac, this is the "meta" or "command" key.
|
|
6632
|
+
*/
|
|
6633
|
+
function isCtrlKey(ev) {
|
|
6634
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
6633
6635
|
}
|
|
6634
6636
|
|
|
6635
6637
|
/**
|
|
@@ -8878,7 +8880,7 @@ function createGaugeChartRuntime(chart, getters) {
|
|
|
8878
8880
|
});
|
|
8879
8881
|
return {
|
|
8880
8882
|
chartJsConfig: config,
|
|
8881
|
-
background: getters.
|
|
8883
|
+
background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
|
|
8882
8884
|
};
|
|
8883
8885
|
}
|
|
8884
8886
|
|
|
@@ -8959,7 +8961,7 @@ function getFormatMinDisplayUnit(format) {
|
|
|
8959
8961
|
else if (format.includes("h") || format.includes("H")) {
|
|
8960
8962
|
return "hour";
|
|
8961
8963
|
}
|
|
8962
|
-
else if (format.includes("
|
|
8964
|
+
else if (format.includes("d")) {
|
|
8963
8965
|
return "day";
|
|
8964
8966
|
}
|
|
8965
8967
|
else if (format.includes("M")) {
|
|
@@ -21212,10 +21214,10 @@ const arrowMap = {
|
|
|
21212
21214
|
function updateSelectionWithArrowKeys(ev, selection) {
|
|
21213
21215
|
const direction = arrowMap[ev.key];
|
|
21214
21216
|
if (ev.shiftKey) {
|
|
21215
|
-
selection.resizeAnchorZone(direction, ev
|
|
21217
|
+
selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21216
21218
|
}
|
|
21217
21219
|
else {
|
|
21218
|
-
selection.moveAnchorCell(direction, ev
|
|
21220
|
+
selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21219
21221
|
}
|
|
21220
21222
|
}
|
|
21221
21223
|
|
|
@@ -28200,7 +28202,10 @@ class GridOverlay extends Component {
|
|
|
28200
28202
|
return;
|
|
28201
28203
|
}
|
|
28202
28204
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
28203
|
-
this.props.onCellClicked(col, row, {
|
|
28205
|
+
this.props.onCellClicked(col, row, {
|
|
28206
|
+
expandZone: ev.shiftKey,
|
|
28207
|
+
addZone: isCtrlKey(ev),
|
|
28208
|
+
});
|
|
28204
28209
|
}
|
|
28205
28210
|
onDoubleClick(ev) {
|
|
28206
28211
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
@@ -28430,7 +28435,7 @@ class AbstractResizer extends Component {
|
|
|
28430
28435
|
this._increaseSelection(index);
|
|
28431
28436
|
}
|
|
28432
28437
|
else {
|
|
28433
|
-
this._selectElement(index, ev
|
|
28438
|
+
this._selectElement(index, isCtrlKey(ev));
|
|
28434
28439
|
}
|
|
28435
28440
|
this.lastSelectedElementIndex = index;
|
|
28436
28441
|
const mouseMoveSelect = (col, row) => {
|
|
@@ -28594,8 +28599,8 @@ class ColResizer extends AbstractResizer {
|
|
|
28594
28599
|
this.env.raiseError(MergeErrorMessage);
|
|
28595
28600
|
}
|
|
28596
28601
|
}
|
|
28597
|
-
_selectElement(index,
|
|
28598
|
-
this.env.model.selection.selectColumn(index,
|
|
28602
|
+
_selectElement(index, addDistinctHeader) {
|
|
28603
|
+
this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28599
28604
|
}
|
|
28600
28605
|
_increaseSelection(index) {
|
|
28601
28606
|
this.env.model.selection.selectColumn(index, "updateAnchor");
|
|
@@ -28768,8 +28773,8 @@ class RowResizer extends AbstractResizer {
|
|
|
28768
28773
|
this.env.raiseError(MergeErrorMessage);
|
|
28769
28774
|
}
|
|
28770
28775
|
}
|
|
28771
|
-
_selectElement(index,
|
|
28772
|
-
this.env.model.selection.selectRow(index,
|
|
28776
|
+
_selectElement(index, addDistinctHeader) {
|
|
28777
|
+
this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28773
28778
|
}
|
|
28774
28779
|
_increaseSelection(index) {
|
|
28775
28780
|
this.env.model.selection.selectRow(index, "updateAnchor");
|
|
@@ -29571,17 +29576,17 @@ class Grid extends Component {
|
|
|
29571
29576
|
// ---------------------------------------------------------------------------
|
|
29572
29577
|
// Zone selection with mouse
|
|
29573
29578
|
// ---------------------------------------------------------------------------
|
|
29574
|
-
onCellClicked(col, row, {
|
|
29579
|
+
onCellClicked(col, row, { addZone, expandZone }) {
|
|
29575
29580
|
if (this.env.model.getters.hasOpenedPopover()) {
|
|
29576
29581
|
this.closeOpenedPopover();
|
|
29577
29582
|
}
|
|
29578
29583
|
if (this.env.model.getters.getEditionMode() === "editing") {
|
|
29579
29584
|
interactiveStopEdition(this.env);
|
|
29580
29585
|
}
|
|
29581
|
-
if (
|
|
29586
|
+
if (expandZone) {
|
|
29582
29587
|
this.env.model.selection.setAnchorCorner(col, row);
|
|
29583
29588
|
}
|
|
29584
|
-
else if (
|
|
29589
|
+
else if (addZone) {
|
|
29585
29590
|
this.env.model.selection.addCellToSelection(col, row);
|
|
29586
29591
|
}
|
|
29587
29592
|
else {
|
|
@@ -29642,7 +29647,7 @@ class Grid extends Component {
|
|
|
29642
29647
|
onKeydown(ev) {
|
|
29643
29648
|
const eventKey = ev.key.toUpperCase();
|
|
29644
29649
|
let keyDownString = "";
|
|
29645
|
-
if (ev
|
|
29650
|
+
if (isCtrlKey(ev) && eventKey !== "CTRL")
|
|
29646
29651
|
keyDownString += "CTRL+";
|
|
29647
29652
|
if (ev.metaKey)
|
|
29648
29653
|
keyDownString += "CTRL+";
|
|
@@ -34533,7 +34538,7 @@ class BordersPlugin extends CorePlugin {
|
|
|
34533
34538
|
*/
|
|
34534
34539
|
function getBorderId(border) {
|
|
34535
34540
|
for (let [key, value] of Object.entries(borders)) {
|
|
34536
|
-
if (
|
|
34541
|
+
if (deepEquals(value, border)) {
|
|
34537
34542
|
return parseInt(key, 10);
|
|
34538
34543
|
}
|
|
34539
34544
|
}
|
|
@@ -39852,56 +39857,758 @@ class CompilationParametersBuilder {
|
|
|
39852
39857
|
}
|
|
39853
39858
|
}
|
|
39854
39859
|
|
|
39860
|
+
function quickselect(arr, k, left, right, compare) {
|
|
39861
|
+
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
|
|
39862
|
+
}
|
|
39863
|
+
|
|
39864
|
+
function quickselectStep(arr, k, left, right, compare) {
|
|
39865
|
+
|
|
39866
|
+
while (right > left) {
|
|
39867
|
+
if (right - left > 600) {
|
|
39868
|
+
var n = right - left + 1;
|
|
39869
|
+
var m = k - left + 1;
|
|
39870
|
+
var z = Math.log(n);
|
|
39871
|
+
var s = 0.5 * Math.exp(2 * z / 3);
|
|
39872
|
+
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
|
|
39873
|
+
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
|
|
39874
|
+
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
|
|
39875
|
+
quickselectStep(arr, k, newLeft, newRight, compare);
|
|
39876
|
+
}
|
|
39877
|
+
|
|
39878
|
+
var t = arr[k];
|
|
39879
|
+
var i = left;
|
|
39880
|
+
var j = right;
|
|
39881
|
+
|
|
39882
|
+
swap(arr, left, k);
|
|
39883
|
+
if (compare(arr[right], t) > 0) swap(arr, left, right);
|
|
39884
|
+
|
|
39885
|
+
while (i < j) {
|
|
39886
|
+
swap(arr, i, j);
|
|
39887
|
+
i++;
|
|
39888
|
+
j--;
|
|
39889
|
+
while (compare(arr[i], t) < 0) i++;
|
|
39890
|
+
while (compare(arr[j], t) > 0) j--;
|
|
39891
|
+
}
|
|
39892
|
+
|
|
39893
|
+
if (compare(arr[left], t) === 0) swap(arr, left, j);
|
|
39894
|
+
else {
|
|
39895
|
+
j++;
|
|
39896
|
+
swap(arr, j, right);
|
|
39897
|
+
}
|
|
39898
|
+
|
|
39899
|
+
if (j <= k) left = j + 1;
|
|
39900
|
+
if (k <= j) right = j - 1;
|
|
39901
|
+
}
|
|
39902
|
+
}
|
|
39903
|
+
|
|
39904
|
+
function swap(arr, i, j) {
|
|
39905
|
+
var tmp = arr[i];
|
|
39906
|
+
arr[i] = arr[j];
|
|
39907
|
+
arr[j] = tmp;
|
|
39908
|
+
}
|
|
39909
|
+
|
|
39910
|
+
function defaultCompare(a, b) {
|
|
39911
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
39912
|
+
}
|
|
39913
|
+
|
|
39914
|
+
class RBush {
|
|
39915
|
+
constructor(maxEntries = 9) {
|
|
39916
|
+
// max entries in a node is 9 by default; min node fill is 40% for best performance
|
|
39917
|
+
this._maxEntries = Math.max(4, maxEntries);
|
|
39918
|
+
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
|
|
39919
|
+
this.clear();
|
|
39920
|
+
}
|
|
39921
|
+
|
|
39922
|
+
all() {
|
|
39923
|
+
return this._all(this.data, []);
|
|
39924
|
+
}
|
|
39925
|
+
|
|
39926
|
+
search(bbox) {
|
|
39927
|
+
let node = this.data;
|
|
39928
|
+
const result = [];
|
|
39929
|
+
|
|
39930
|
+
if (!intersects(bbox, node)) return result;
|
|
39931
|
+
|
|
39932
|
+
const toBBox = this.toBBox;
|
|
39933
|
+
const nodesToSearch = [];
|
|
39934
|
+
|
|
39935
|
+
while (node) {
|
|
39936
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
39937
|
+
const child = node.children[i];
|
|
39938
|
+
const childBBox = node.leaf ? toBBox(child) : child;
|
|
39939
|
+
|
|
39940
|
+
if (intersects(bbox, childBBox)) {
|
|
39941
|
+
if (node.leaf) result.push(child);
|
|
39942
|
+
else if (contains(bbox, childBBox)) this._all(child, result);
|
|
39943
|
+
else nodesToSearch.push(child);
|
|
39944
|
+
}
|
|
39945
|
+
}
|
|
39946
|
+
node = nodesToSearch.pop();
|
|
39947
|
+
}
|
|
39948
|
+
|
|
39949
|
+
return result;
|
|
39950
|
+
}
|
|
39951
|
+
|
|
39952
|
+
collides(bbox) {
|
|
39953
|
+
let node = this.data;
|
|
39954
|
+
|
|
39955
|
+
if (!intersects(bbox, node)) return false;
|
|
39956
|
+
|
|
39957
|
+
const nodesToSearch = [];
|
|
39958
|
+
while (node) {
|
|
39959
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
39960
|
+
const child = node.children[i];
|
|
39961
|
+
const childBBox = node.leaf ? this.toBBox(child) : child;
|
|
39962
|
+
|
|
39963
|
+
if (intersects(bbox, childBBox)) {
|
|
39964
|
+
if (node.leaf || contains(bbox, childBBox)) return true;
|
|
39965
|
+
nodesToSearch.push(child);
|
|
39966
|
+
}
|
|
39967
|
+
}
|
|
39968
|
+
node = nodesToSearch.pop();
|
|
39969
|
+
}
|
|
39970
|
+
|
|
39971
|
+
return false;
|
|
39972
|
+
}
|
|
39973
|
+
|
|
39974
|
+
load(data) {
|
|
39975
|
+
if (!(data && data.length)) return this;
|
|
39976
|
+
|
|
39977
|
+
if (data.length < this._minEntries) {
|
|
39978
|
+
for (let i = 0; i < data.length; i++) {
|
|
39979
|
+
this.insert(data[i]);
|
|
39980
|
+
}
|
|
39981
|
+
return this;
|
|
39982
|
+
}
|
|
39983
|
+
|
|
39984
|
+
// recursively build the tree with the given data from scratch using OMT algorithm
|
|
39985
|
+
let node = this._build(data.slice(), 0, data.length - 1, 0);
|
|
39986
|
+
|
|
39987
|
+
if (!this.data.children.length) {
|
|
39988
|
+
// save as is if tree is empty
|
|
39989
|
+
this.data = node;
|
|
39990
|
+
|
|
39991
|
+
} else if (this.data.height === node.height) {
|
|
39992
|
+
// split root if trees have the same height
|
|
39993
|
+
this._splitRoot(this.data, node);
|
|
39994
|
+
|
|
39995
|
+
} else {
|
|
39996
|
+
if (this.data.height < node.height) {
|
|
39997
|
+
// swap trees if inserted one is bigger
|
|
39998
|
+
const tmpNode = this.data;
|
|
39999
|
+
this.data = node;
|
|
40000
|
+
node = tmpNode;
|
|
40001
|
+
}
|
|
40002
|
+
|
|
40003
|
+
// insert the small tree into the large tree at appropriate level
|
|
40004
|
+
this._insert(node, this.data.height - node.height - 1, true);
|
|
40005
|
+
}
|
|
40006
|
+
|
|
40007
|
+
return this;
|
|
40008
|
+
}
|
|
40009
|
+
|
|
40010
|
+
insert(item) {
|
|
40011
|
+
if (item) this._insert(item, this.data.height - 1);
|
|
40012
|
+
return this;
|
|
40013
|
+
}
|
|
40014
|
+
|
|
40015
|
+
clear() {
|
|
40016
|
+
this.data = createNode([]);
|
|
40017
|
+
return this;
|
|
40018
|
+
}
|
|
40019
|
+
|
|
40020
|
+
remove(item, equalsFn) {
|
|
40021
|
+
if (!item) return this;
|
|
40022
|
+
|
|
40023
|
+
let node = this.data;
|
|
40024
|
+
const bbox = this.toBBox(item);
|
|
40025
|
+
const path = [];
|
|
40026
|
+
const indexes = [];
|
|
40027
|
+
let i, parent, goingUp;
|
|
40028
|
+
|
|
40029
|
+
// depth-first iterative tree traversal
|
|
40030
|
+
while (node || path.length) {
|
|
40031
|
+
|
|
40032
|
+
if (!node) { // go up
|
|
40033
|
+
node = path.pop();
|
|
40034
|
+
parent = path[path.length - 1];
|
|
40035
|
+
i = indexes.pop();
|
|
40036
|
+
goingUp = true;
|
|
40037
|
+
}
|
|
40038
|
+
|
|
40039
|
+
if (node.leaf) { // check current node
|
|
40040
|
+
const index = findItem(item, node.children, equalsFn);
|
|
40041
|
+
|
|
40042
|
+
if (index !== -1) {
|
|
40043
|
+
// item found, remove the item and condense tree upwards
|
|
40044
|
+
node.children.splice(index, 1);
|
|
40045
|
+
path.push(node);
|
|
40046
|
+
this._condense(path);
|
|
40047
|
+
return this;
|
|
40048
|
+
}
|
|
40049
|
+
}
|
|
40050
|
+
|
|
40051
|
+
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
|
|
40052
|
+
path.push(node);
|
|
40053
|
+
indexes.push(i);
|
|
40054
|
+
i = 0;
|
|
40055
|
+
parent = node;
|
|
40056
|
+
node = node.children[0];
|
|
40057
|
+
|
|
40058
|
+
} else if (parent) { // go right
|
|
40059
|
+
i++;
|
|
40060
|
+
node = parent.children[i];
|
|
40061
|
+
goingUp = false;
|
|
40062
|
+
|
|
40063
|
+
} else node = null; // nothing found
|
|
40064
|
+
}
|
|
40065
|
+
|
|
40066
|
+
return this;
|
|
40067
|
+
}
|
|
40068
|
+
|
|
40069
|
+
toBBox(item) { return item; }
|
|
40070
|
+
|
|
40071
|
+
compareMinX(a, b) { return a.minX - b.minX; }
|
|
40072
|
+
compareMinY(a, b) { return a.minY - b.minY; }
|
|
40073
|
+
|
|
40074
|
+
toJSON() { return this.data; }
|
|
40075
|
+
|
|
40076
|
+
fromJSON(data) {
|
|
40077
|
+
this.data = data;
|
|
40078
|
+
return this;
|
|
40079
|
+
}
|
|
40080
|
+
|
|
40081
|
+
_all(node, result) {
|
|
40082
|
+
const nodesToSearch = [];
|
|
40083
|
+
while (node) {
|
|
40084
|
+
if (node.leaf) result.push(...node.children);
|
|
40085
|
+
else nodesToSearch.push(...node.children);
|
|
40086
|
+
|
|
40087
|
+
node = nodesToSearch.pop();
|
|
40088
|
+
}
|
|
40089
|
+
return result;
|
|
40090
|
+
}
|
|
40091
|
+
|
|
40092
|
+
_build(items, left, right, height) {
|
|
40093
|
+
|
|
40094
|
+
const N = right - left + 1;
|
|
40095
|
+
let M = this._maxEntries;
|
|
40096
|
+
let node;
|
|
40097
|
+
|
|
40098
|
+
if (N <= M) {
|
|
40099
|
+
// reached leaf level; return leaf
|
|
40100
|
+
node = createNode(items.slice(left, right + 1));
|
|
40101
|
+
calcBBox(node, this.toBBox);
|
|
40102
|
+
return node;
|
|
40103
|
+
}
|
|
40104
|
+
|
|
40105
|
+
if (!height) {
|
|
40106
|
+
// target height of the bulk-loaded tree
|
|
40107
|
+
height = Math.ceil(Math.log(N) / Math.log(M));
|
|
40108
|
+
|
|
40109
|
+
// target number of root entries to maximize storage utilization
|
|
40110
|
+
M = Math.ceil(N / Math.pow(M, height - 1));
|
|
40111
|
+
}
|
|
40112
|
+
|
|
40113
|
+
node = createNode([]);
|
|
40114
|
+
node.leaf = false;
|
|
40115
|
+
node.height = height;
|
|
40116
|
+
|
|
40117
|
+
// split the items into M mostly square tiles
|
|
40118
|
+
|
|
40119
|
+
const N2 = Math.ceil(N / M);
|
|
40120
|
+
const N1 = N2 * Math.ceil(Math.sqrt(M));
|
|
40121
|
+
|
|
40122
|
+
multiSelect(items, left, right, N1, this.compareMinX);
|
|
40123
|
+
|
|
40124
|
+
for (let i = left; i <= right; i += N1) {
|
|
40125
|
+
|
|
40126
|
+
const right2 = Math.min(i + N1 - 1, right);
|
|
40127
|
+
|
|
40128
|
+
multiSelect(items, i, right2, N2, this.compareMinY);
|
|
40129
|
+
|
|
40130
|
+
for (let j = i; j <= right2; j += N2) {
|
|
40131
|
+
|
|
40132
|
+
const right3 = Math.min(j + N2 - 1, right2);
|
|
40133
|
+
|
|
40134
|
+
// pack each entry recursively
|
|
40135
|
+
node.children.push(this._build(items, j, right3, height - 1));
|
|
40136
|
+
}
|
|
40137
|
+
}
|
|
40138
|
+
|
|
40139
|
+
calcBBox(node, this.toBBox);
|
|
40140
|
+
|
|
40141
|
+
return node;
|
|
40142
|
+
}
|
|
40143
|
+
|
|
40144
|
+
_chooseSubtree(bbox, node, level, path) {
|
|
40145
|
+
while (true) {
|
|
40146
|
+
path.push(node);
|
|
40147
|
+
|
|
40148
|
+
if (node.leaf || path.length - 1 === level) break;
|
|
40149
|
+
|
|
40150
|
+
let minArea = Infinity;
|
|
40151
|
+
let minEnlargement = Infinity;
|
|
40152
|
+
let targetNode;
|
|
40153
|
+
|
|
40154
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40155
|
+
const child = node.children[i];
|
|
40156
|
+
const area = bboxArea(child);
|
|
40157
|
+
const enlargement = enlargedArea(bbox, child) - area;
|
|
40158
|
+
|
|
40159
|
+
// choose entry with the least area enlargement
|
|
40160
|
+
if (enlargement < minEnlargement) {
|
|
40161
|
+
minEnlargement = enlargement;
|
|
40162
|
+
minArea = area < minArea ? area : minArea;
|
|
40163
|
+
targetNode = child;
|
|
40164
|
+
|
|
40165
|
+
} else if (enlargement === minEnlargement) {
|
|
40166
|
+
// otherwise choose one with the smallest area
|
|
40167
|
+
if (area < minArea) {
|
|
40168
|
+
minArea = area;
|
|
40169
|
+
targetNode = child;
|
|
40170
|
+
}
|
|
40171
|
+
}
|
|
40172
|
+
}
|
|
40173
|
+
|
|
40174
|
+
node = targetNode || node.children[0];
|
|
40175
|
+
}
|
|
40176
|
+
|
|
40177
|
+
return node;
|
|
40178
|
+
}
|
|
40179
|
+
|
|
40180
|
+
_insert(item, level, isNode) {
|
|
40181
|
+
const bbox = isNode ? item : this.toBBox(item);
|
|
40182
|
+
const insertPath = [];
|
|
40183
|
+
|
|
40184
|
+
// find the best node for accommodating the item, saving all nodes along the path too
|
|
40185
|
+
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
|
|
40186
|
+
|
|
40187
|
+
// put the item into the node
|
|
40188
|
+
node.children.push(item);
|
|
40189
|
+
extend(node, bbox);
|
|
40190
|
+
|
|
40191
|
+
// split on node overflow; propagate upwards if necessary
|
|
40192
|
+
while (level >= 0) {
|
|
40193
|
+
if (insertPath[level].children.length > this._maxEntries) {
|
|
40194
|
+
this._split(insertPath, level);
|
|
40195
|
+
level--;
|
|
40196
|
+
} else break;
|
|
40197
|
+
}
|
|
40198
|
+
|
|
40199
|
+
// adjust bboxes along the insertion path
|
|
40200
|
+
this._adjustParentBBoxes(bbox, insertPath, level);
|
|
40201
|
+
}
|
|
40202
|
+
|
|
40203
|
+
// split overflowed node into two
|
|
40204
|
+
_split(insertPath, level) {
|
|
40205
|
+
const node = insertPath[level];
|
|
40206
|
+
const M = node.children.length;
|
|
40207
|
+
const m = this._minEntries;
|
|
40208
|
+
|
|
40209
|
+
this._chooseSplitAxis(node, m, M);
|
|
40210
|
+
|
|
40211
|
+
const splitIndex = this._chooseSplitIndex(node, m, M);
|
|
40212
|
+
|
|
40213
|
+
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
|
|
40214
|
+
newNode.height = node.height;
|
|
40215
|
+
newNode.leaf = node.leaf;
|
|
40216
|
+
|
|
40217
|
+
calcBBox(node, this.toBBox);
|
|
40218
|
+
calcBBox(newNode, this.toBBox);
|
|
40219
|
+
|
|
40220
|
+
if (level) insertPath[level - 1].children.push(newNode);
|
|
40221
|
+
else this._splitRoot(node, newNode);
|
|
40222
|
+
}
|
|
40223
|
+
|
|
40224
|
+
_splitRoot(node, newNode) {
|
|
40225
|
+
// split root node
|
|
40226
|
+
this.data = createNode([node, newNode]);
|
|
40227
|
+
this.data.height = node.height + 1;
|
|
40228
|
+
this.data.leaf = false;
|
|
40229
|
+
calcBBox(this.data, this.toBBox);
|
|
40230
|
+
}
|
|
40231
|
+
|
|
40232
|
+
_chooseSplitIndex(node, m, M) {
|
|
40233
|
+
let index;
|
|
40234
|
+
let minOverlap = Infinity;
|
|
40235
|
+
let minArea = Infinity;
|
|
40236
|
+
|
|
40237
|
+
for (let i = m; i <= M - m; i++) {
|
|
40238
|
+
const bbox1 = distBBox(node, 0, i, this.toBBox);
|
|
40239
|
+
const bbox2 = distBBox(node, i, M, this.toBBox);
|
|
40240
|
+
|
|
40241
|
+
const overlap = intersectionArea(bbox1, bbox2);
|
|
40242
|
+
const area = bboxArea(bbox1) + bboxArea(bbox2);
|
|
40243
|
+
|
|
40244
|
+
// choose distribution with minimum overlap
|
|
40245
|
+
if (overlap < minOverlap) {
|
|
40246
|
+
minOverlap = overlap;
|
|
40247
|
+
index = i;
|
|
40248
|
+
|
|
40249
|
+
minArea = area < minArea ? area : minArea;
|
|
40250
|
+
|
|
40251
|
+
} else if (overlap === minOverlap) {
|
|
40252
|
+
// otherwise choose distribution with minimum area
|
|
40253
|
+
if (area < minArea) {
|
|
40254
|
+
minArea = area;
|
|
40255
|
+
index = i;
|
|
40256
|
+
}
|
|
40257
|
+
}
|
|
40258
|
+
}
|
|
40259
|
+
|
|
40260
|
+
return index || M - m;
|
|
40261
|
+
}
|
|
40262
|
+
|
|
40263
|
+
// sorts node children by the best axis for split
|
|
40264
|
+
_chooseSplitAxis(node, m, M) {
|
|
40265
|
+
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
|
|
40266
|
+
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
|
|
40267
|
+
const xMargin = this._allDistMargin(node, m, M, compareMinX);
|
|
40268
|
+
const yMargin = this._allDistMargin(node, m, M, compareMinY);
|
|
40269
|
+
|
|
40270
|
+
// if total distributions margin value is minimal for x, sort by minX,
|
|
40271
|
+
// otherwise it's already sorted by minY
|
|
40272
|
+
if (xMargin < yMargin) node.children.sort(compareMinX);
|
|
40273
|
+
}
|
|
40274
|
+
|
|
40275
|
+
// total margin of all possible split distributions where each node is at least m full
|
|
40276
|
+
_allDistMargin(node, m, M, compare) {
|
|
40277
|
+
node.children.sort(compare);
|
|
40278
|
+
|
|
40279
|
+
const toBBox = this.toBBox;
|
|
40280
|
+
const leftBBox = distBBox(node, 0, m, toBBox);
|
|
40281
|
+
const rightBBox = distBBox(node, M - m, M, toBBox);
|
|
40282
|
+
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
|
|
40283
|
+
|
|
40284
|
+
for (let i = m; i < M - m; i++) {
|
|
40285
|
+
const child = node.children[i];
|
|
40286
|
+
extend(leftBBox, node.leaf ? toBBox(child) : child);
|
|
40287
|
+
margin += bboxMargin(leftBBox);
|
|
40288
|
+
}
|
|
40289
|
+
|
|
40290
|
+
for (let i = M - m - 1; i >= m; i--) {
|
|
40291
|
+
const child = node.children[i];
|
|
40292
|
+
extend(rightBBox, node.leaf ? toBBox(child) : child);
|
|
40293
|
+
margin += bboxMargin(rightBBox);
|
|
40294
|
+
}
|
|
40295
|
+
|
|
40296
|
+
return margin;
|
|
40297
|
+
}
|
|
40298
|
+
|
|
40299
|
+
_adjustParentBBoxes(bbox, path, level) {
|
|
40300
|
+
// adjust bboxes along the given tree path
|
|
40301
|
+
for (let i = level; i >= 0; i--) {
|
|
40302
|
+
extend(path[i], bbox);
|
|
40303
|
+
}
|
|
40304
|
+
}
|
|
40305
|
+
|
|
40306
|
+
_condense(path) {
|
|
40307
|
+
// go through the path, removing empty nodes and updating bboxes
|
|
40308
|
+
for (let i = path.length - 1, siblings; i >= 0; i--) {
|
|
40309
|
+
if (path[i].children.length === 0) {
|
|
40310
|
+
if (i > 0) {
|
|
40311
|
+
siblings = path[i - 1].children;
|
|
40312
|
+
siblings.splice(siblings.indexOf(path[i]), 1);
|
|
40313
|
+
|
|
40314
|
+
} else this.clear();
|
|
40315
|
+
|
|
40316
|
+
} else calcBBox(path[i], this.toBBox);
|
|
40317
|
+
}
|
|
40318
|
+
}
|
|
40319
|
+
}
|
|
40320
|
+
|
|
40321
|
+
function findItem(item, items, equalsFn) {
|
|
40322
|
+
if (!equalsFn) return items.indexOf(item);
|
|
40323
|
+
|
|
40324
|
+
for (let i = 0; i < items.length; i++) {
|
|
40325
|
+
if (equalsFn(item, items[i])) return i;
|
|
40326
|
+
}
|
|
40327
|
+
return -1;
|
|
40328
|
+
}
|
|
40329
|
+
|
|
40330
|
+
// calculate node's bbox from bboxes of its children
|
|
40331
|
+
function calcBBox(node, toBBox) {
|
|
40332
|
+
distBBox(node, 0, node.children.length, toBBox, node);
|
|
40333
|
+
}
|
|
40334
|
+
|
|
40335
|
+
// min bounding rectangle of node children from k to p-1
|
|
40336
|
+
function distBBox(node, k, p, toBBox, destNode) {
|
|
40337
|
+
if (!destNode) destNode = createNode(null);
|
|
40338
|
+
destNode.minX = Infinity;
|
|
40339
|
+
destNode.minY = Infinity;
|
|
40340
|
+
destNode.maxX = -Infinity;
|
|
40341
|
+
destNode.maxY = -Infinity;
|
|
40342
|
+
|
|
40343
|
+
for (let i = k; i < p; i++) {
|
|
40344
|
+
const child = node.children[i];
|
|
40345
|
+
extend(destNode, node.leaf ? toBBox(child) : child);
|
|
40346
|
+
}
|
|
40347
|
+
|
|
40348
|
+
return destNode;
|
|
40349
|
+
}
|
|
40350
|
+
|
|
40351
|
+
function extend(a, b) {
|
|
40352
|
+
a.minX = Math.min(a.minX, b.minX);
|
|
40353
|
+
a.minY = Math.min(a.minY, b.minY);
|
|
40354
|
+
a.maxX = Math.max(a.maxX, b.maxX);
|
|
40355
|
+
a.maxY = Math.max(a.maxY, b.maxY);
|
|
40356
|
+
return a;
|
|
40357
|
+
}
|
|
40358
|
+
|
|
40359
|
+
function compareNodeMinX(a, b) { return a.minX - b.minX; }
|
|
40360
|
+
function compareNodeMinY(a, b) { return a.minY - b.minY; }
|
|
40361
|
+
|
|
40362
|
+
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
|
|
40363
|
+
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
|
|
40364
|
+
|
|
40365
|
+
function enlargedArea(a, b) {
|
|
40366
|
+
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
|
|
40367
|
+
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
|
|
40368
|
+
}
|
|
40369
|
+
|
|
40370
|
+
function intersectionArea(a, b) {
|
|
40371
|
+
const minX = Math.max(a.minX, b.minX);
|
|
40372
|
+
const minY = Math.max(a.minY, b.minY);
|
|
40373
|
+
const maxX = Math.min(a.maxX, b.maxX);
|
|
40374
|
+
const maxY = Math.min(a.maxY, b.maxY);
|
|
40375
|
+
|
|
40376
|
+
return Math.max(0, maxX - minX) *
|
|
40377
|
+
Math.max(0, maxY - minY);
|
|
40378
|
+
}
|
|
40379
|
+
|
|
40380
|
+
function contains(a, b) {
|
|
40381
|
+
return a.minX <= b.minX &&
|
|
40382
|
+
a.minY <= b.minY &&
|
|
40383
|
+
b.maxX <= a.maxX &&
|
|
40384
|
+
b.maxY <= a.maxY;
|
|
40385
|
+
}
|
|
40386
|
+
|
|
40387
|
+
function intersects(a, b) {
|
|
40388
|
+
return b.minX <= a.maxX &&
|
|
40389
|
+
b.minY <= a.maxY &&
|
|
40390
|
+
b.maxX >= a.minX &&
|
|
40391
|
+
b.maxY >= a.minY;
|
|
40392
|
+
}
|
|
40393
|
+
|
|
40394
|
+
function createNode(children) {
|
|
40395
|
+
return {
|
|
40396
|
+
children,
|
|
40397
|
+
height: 1,
|
|
40398
|
+
leaf: true,
|
|
40399
|
+
minX: Infinity,
|
|
40400
|
+
minY: Infinity,
|
|
40401
|
+
maxX: -Infinity,
|
|
40402
|
+
maxY: -Infinity
|
|
40403
|
+
};
|
|
40404
|
+
}
|
|
40405
|
+
|
|
40406
|
+
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
|
|
40407
|
+
// combines selection algorithm with binary divide & conquer approach
|
|
40408
|
+
|
|
40409
|
+
function multiSelect(arr, left, right, n, compare) {
|
|
40410
|
+
const stack = [left, right];
|
|
40411
|
+
|
|
40412
|
+
while (stack.length) {
|
|
40413
|
+
right = stack.pop();
|
|
40414
|
+
left = stack.pop();
|
|
40415
|
+
|
|
40416
|
+
if (right - left <= n) continue;
|
|
40417
|
+
|
|
40418
|
+
const mid = left + Math.ceil((right - left) / n / 2) * n;
|
|
40419
|
+
quickselect(arr, mid, left, right, compare);
|
|
40420
|
+
|
|
40421
|
+
stack.push(left, mid, mid, right);
|
|
40422
|
+
}
|
|
40423
|
+
}
|
|
40424
|
+
|
|
39855
40425
|
/**
|
|
39856
|
-
*
|
|
40426
|
+
* R-Tree Data Structure
|
|
40427
|
+
*
|
|
40428
|
+
* R-Tree is a spatial data structure used for efficient indexing and querying
|
|
40429
|
+
* of multi-dimensional objects, particularly in geometric and spatial applications.
|
|
40430
|
+
*
|
|
40431
|
+
* It organizes objects into a tree hierarchy, grouping nearby objects together
|
|
40432
|
+
* in bounding boxes. Each node in the tree represents a bounding box that
|
|
40433
|
+
* contains its child nodes or leaf objects. This hierarchical structure allows
|
|
40434
|
+
* for faster spatial queries.
|
|
40435
|
+
*
|
|
40436
|
+
* @see https://en.wikipedia.org/wiki/R-tree
|
|
40437
|
+
*
|
|
40438
|
+
* Consider a 2D Space with four zones: A, B, C, D
|
|
40439
|
+
* +--------------------------+
|
|
40440
|
+
* | |
|
|
40441
|
+
* | +---+ +-------+ |
|
|
40442
|
+
* | | A | | B | |
|
|
40443
|
+
* | +---+ +-------+ |
|
|
40444
|
+
* | |
|
|
40445
|
+
* | |
|
|
40446
|
+
* | +---+ |
|
|
40447
|
+
* | | C | |
|
|
40448
|
+
* | +---+ |
|
|
40449
|
+
* | +-----------+ |
|
|
40450
|
+
* | | D | |
|
|
40451
|
+
* | +-----------+ |
|
|
40452
|
+
* | |
|
|
40453
|
+
* +--------------------------+
|
|
40454
|
+
*
|
|
40455
|
+
* It groups together zones that are spatially close into a minimum bounding box.
|
|
40456
|
+
* For example, A and B are grouped together in rectangle R1, and C and D are grouped
|
|
40457
|
+
* in R2.
|
|
40458
|
+
*
|
|
40459
|
+
* R0
|
|
40460
|
+
* +--------------------------+
|
|
40461
|
+
* | R1 |
|
|
40462
|
+
* | +-----------------+ |
|
|
40463
|
+
* | | A | | B | |
|
|
40464
|
+
* | +-----------------+ |
|
|
40465
|
+
* | |
|
|
40466
|
+
* | R2 |
|
|
40467
|
+
* | +---+---+---+ |
|
|
40468
|
+
* | | | C | | |
|
|
40469
|
+
* | | +---+ | |
|
|
40470
|
+
* | +-----------+ |
|
|
40471
|
+
* | | D | |
|
|
40472
|
+
* | +-----------+ |
|
|
40473
|
+
* | |
|
|
40474
|
+
* +--------------------------+
|
|
40475
|
+
*
|
|
40476
|
+
* The tree would look like this:
|
|
40477
|
+
* R0
|
|
40478
|
+
* / \
|
|
40479
|
+
* / \
|
|
40480
|
+
* R1 R2
|
|
40481
|
+
* | |
|
|
40482
|
+
* A,B C,D
|
|
40483
|
+
|
|
40484
|
+
* Choosing how to group the zones is crucial for the performance of the tree.
|
|
40485
|
+
* Key considerations include avoiding excessive empty space coverage and minimizing overlap
|
|
40486
|
+
* to reduce the number of subtrees processed during searches.
|
|
40487
|
+
*
|
|
40488
|
+
* Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
|
|
40489
|
+
* which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
|
|
40490
|
+
* the choice cannot be made based on this criterion due to the same enlargement for different groupings,
|
|
40491
|
+
* we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
|
|
40492
|
+
*
|
|
40493
|
+
* This implementation is tailored for spreadsheet use, indexing objects associated
|
|
40494
|
+
* with a zone and a sheet.
|
|
40495
|
+
*
|
|
40496
|
+
* It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
|
|
40497
|
+
* @see https://github.com/mourner/rbush
|
|
40498
|
+
*/
|
|
40499
|
+
class SpreadsheetRTree {
|
|
40500
|
+
/**
|
|
40501
|
+
* One 2D R-tree per sheet
|
|
40502
|
+
*/
|
|
40503
|
+
rTrees = {};
|
|
40504
|
+
/**
|
|
40505
|
+
* Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
|
|
40506
|
+
* faster than inserting items one by one. After bulk loading (bulk insertion into
|
|
40507
|
+
* an empty tree), subsequent query performance is also ~20-30% better.
|
|
40508
|
+
*/
|
|
40509
|
+
constructor(items = []) {
|
|
40510
|
+
const rangesPerSheet = {};
|
|
40511
|
+
for (const item of items) {
|
|
40512
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40513
|
+
if (!rangesPerSheet[sheetId]) {
|
|
40514
|
+
rangesPerSheet[sheetId] = [];
|
|
40515
|
+
}
|
|
40516
|
+
rangesPerSheet[sheetId].push(item);
|
|
40517
|
+
}
|
|
40518
|
+
for (const sheetId in rangesPerSheet) {
|
|
40519
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40520
|
+
this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
|
|
40521
|
+
}
|
|
40522
|
+
}
|
|
40523
|
+
insert(item) {
|
|
40524
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40525
|
+
if (!this.rTrees[sheetId]) {
|
|
40526
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40527
|
+
}
|
|
40528
|
+
this.rTrees[sheetId].insert(item);
|
|
40529
|
+
}
|
|
40530
|
+
search({ zone, sheetId }) {
|
|
40531
|
+
if (!this.rTrees[sheetId]) {
|
|
40532
|
+
return [];
|
|
40533
|
+
}
|
|
40534
|
+
return this.rTrees[sheetId].search({
|
|
40535
|
+
minX: zone.left,
|
|
40536
|
+
minY: zone.top,
|
|
40537
|
+
maxX: zone.right,
|
|
40538
|
+
maxY: zone.bottom,
|
|
40539
|
+
});
|
|
40540
|
+
}
|
|
40541
|
+
remove(item) {
|
|
40542
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40543
|
+
if (!this.rTrees[sheetId]) {
|
|
40544
|
+
return;
|
|
40545
|
+
}
|
|
40546
|
+
this.rTrees[sheetId].remove(item, deepEquals);
|
|
40547
|
+
}
|
|
40548
|
+
}
|
|
40549
|
+
/**
|
|
40550
|
+
* RBush extension to use zones as bounding boxes
|
|
40551
|
+
*/
|
|
40552
|
+
class ZoneRBush extends RBush {
|
|
40553
|
+
toBBox({ boundingBox }) {
|
|
40554
|
+
const zone = boundingBox.zone;
|
|
40555
|
+
return {
|
|
40556
|
+
minX: zone.left,
|
|
40557
|
+
minY: zone.top,
|
|
40558
|
+
maxX: zone.right,
|
|
40559
|
+
maxY: zone.bottom,
|
|
40560
|
+
};
|
|
40561
|
+
}
|
|
40562
|
+
compareMinX(a, b) {
|
|
40563
|
+
return a.boundingBox.zone.left - b.boundingBox.zone.left;
|
|
40564
|
+
}
|
|
40565
|
+
compareMinY(a, b) {
|
|
40566
|
+
return a.boundingBox.zone.top - b.boundingBox.zone.top;
|
|
40567
|
+
}
|
|
40568
|
+
}
|
|
40569
|
+
|
|
40570
|
+
/**
|
|
40571
|
+
* Implementation of a dependency Graph.
|
|
39857
40572
|
* The graph is used to evaluate the cells in the correct
|
|
39858
40573
|
* order, and should be updated each time a cell's content is modified
|
|
39859
40574
|
*
|
|
40575
|
+
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
39860
40576
|
*/
|
|
39861
40577
|
class FormulaDependencyGraph {
|
|
39862
|
-
|
|
39863
|
-
* Internal structure:
|
|
39864
|
-
* - key: a cell position (encoded as an integer)
|
|
39865
|
-
* - value: a set of cell positions that depends on the key
|
|
39866
|
-
*
|
|
39867
|
-
* Given
|
|
39868
|
-
* - A1:"= B1 + SQRT(B2)"
|
|
39869
|
-
* - C1:"= B1";
|
|
39870
|
-
* - C2:"= C1"
|
|
39871
|
-
*
|
|
39872
|
-
* we will have something like:
|
|
39873
|
-
* - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
|
|
39874
|
-
* - B2 ---> (A1) meaning A1 depends on B2
|
|
39875
|
-
* - C1 ---> (C2) meaning C2 depends on C1
|
|
39876
|
-
*/
|
|
39877
|
-
inverseDependencies = new Map();
|
|
40578
|
+
encoder;
|
|
39878
40579
|
dependencies = new Map();
|
|
40580
|
+
rTree;
|
|
40581
|
+
constructor(encoder, data = []) {
|
|
40582
|
+
this.encoder = encoder;
|
|
40583
|
+
this.rTree = new SpreadsheetRTree(data);
|
|
40584
|
+
}
|
|
39879
40585
|
removeAllDependencies(formulaPositionId) {
|
|
39880
|
-
const
|
|
39881
|
-
if (!
|
|
40586
|
+
const ranges = this.dependencies.get(formulaPositionId);
|
|
40587
|
+
if (!ranges) {
|
|
39882
40588
|
return;
|
|
39883
40589
|
}
|
|
39884
|
-
for (const
|
|
39885
|
-
this.
|
|
40590
|
+
for (const range of ranges) {
|
|
40591
|
+
this.rTree.remove(range);
|
|
39886
40592
|
}
|
|
39887
40593
|
this.dependencies.delete(formulaPositionId);
|
|
39888
40594
|
}
|
|
39889
40595
|
addDependencies(formulaPositionId, dependencies) {
|
|
39890
|
-
|
|
39891
|
-
|
|
39892
|
-
|
|
39893
|
-
|
|
39894
|
-
|
|
39895
|
-
|
|
39896
|
-
|
|
39897
|
-
|
|
40596
|
+
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
40597
|
+
data: formulaPositionId,
|
|
40598
|
+
boundingBox: {
|
|
40599
|
+
zone,
|
|
40600
|
+
sheetId,
|
|
40601
|
+
},
|
|
40602
|
+
}));
|
|
40603
|
+
for (const item of rTreeItems) {
|
|
40604
|
+
this.rTree.insert(item);
|
|
39898
40605
|
}
|
|
39899
40606
|
const existingDependencies = this.dependencies.get(formulaPositionId);
|
|
39900
40607
|
if (existingDependencies) {
|
|
39901
|
-
existingDependencies.push(...
|
|
40608
|
+
existingDependencies.push(...rTreeItems);
|
|
39902
40609
|
}
|
|
39903
40610
|
else {
|
|
39904
|
-
this.dependencies.set(formulaPositionId,
|
|
40611
|
+
this.dependencies.set(formulaPositionId, rTreeItems);
|
|
39905
40612
|
}
|
|
39906
40613
|
}
|
|
39907
40614
|
/**
|
|
@@ -39909,20 +40616,20 @@ class FormulaDependencyGraph {
|
|
|
39909
40616
|
* in the correct order they should be evaluated.
|
|
39910
40617
|
* This is called a topological ordering (excluding cycles)
|
|
39911
40618
|
*/
|
|
39912
|
-
getCellsDependingOn(
|
|
40619
|
+
getCellsDependingOn(ranges) {
|
|
39913
40620
|
const visited = new JetSet();
|
|
39914
|
-
const queue = Array.from(
|
|
40621
|
+
const queue = Array.from(ranges).reverse();
|
|
39915
40622
|
while (queue.length > 0) {
|
|
39916
|
-
const
|
|
39917
|
-
visited.add(
|
|
39918
|
-
const
|
|
39919
|
-
for (const
|
|
39920
|
-
if (!visited.has(
|
|
39921
|
-
queue.push(
|
|
40623
|
+
const range = queue.pop();
|
|
40624
|
+
visited.add(...this.encoder.encodeBoundingBox(range));
|
|
40625
|
+
const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
|
|
40626
|
+
for (const positionId of impactedPositionIds) {
|
|
40627
|
+
if (!visited.has(positionId)) {
|
|
40628
|
+
queue.push(this.encoder.decodeToBoundingBox(positionId));
|
|
39922
40629
|
}
|
|
39923
40630
|
}
|
|
39924
40631
|
}
|
|
39925
|
-
visited.delete(...
|
|
40632
|
+
visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
|
|
39926
40633
|
return visited;
|
|
39927
40634
|
}
|
|
39928
40635
|
}
|
|
@@ -40010,9 +40717,9 @@ class Evaluator {
|
|
|
40010
40717
|
context;
|
|
40011
40718
|
getters;
|
|
40012
40719
|
compilationParams;
|
|
40013
|
-
|
|
40720
|
+
encoder = new PositionBitsEncoder();
|
|
40014
40721
|
evaluatedCells = new Map();
|
|
40015
|
-
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
40722
|
+
formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
|
|
40016
40723
|
blockedArrayFormulas = new Set();
|
|
40017
40724
|
spreadingRelations = new SpreadingRelation();
|
|
40018
40725
|
constructor(context, getters) {
|
|
@@ -40021,23 +40728,23 @@ class Evaluator {
|
|
|
40021
40728
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40022
40729
|
}
|
|
40023
40730
|
getEvaluatedCell(position) {
|
|
40024
|
-
return (this.evaluatedCells.get(this.
|
|
40731
|
+
return (this.evaluatedCells.get(this.encoder.encode(position)) ||
|
|
40025
40732
|
createEvaluatedCell("", { locale: this.getters.getLocale() }));
|
|
40026
40733
|
}
|
|
40027
40734
|
getSpreadPositionsOf(position) {
|
|
40028
|
-
const positionId = this.
|
|
40735
|
+
const positionId = this.encoder.encode(position);
|
|
40029
40736
|
if (!this.spreadingRelations.isArrayFormula(positionId)) {
|
|
40030
40737
|
return [];
|
|
40031
40738
|
}
|
|
40032
|
-
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.
|
|
40739
|
+
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
|
|
40033
40740
|
}
|
|
40034
40741
|
getArrayFormulaSpreadingOn(position) {
|
|
40035
|
-
const positionId = this.
|
|
40742
|
+
const positionId = this.encoder.encode(position);
|
|
40036
40743
|
const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
|
|
40037
|
-
return formulaPosition !== undefined ? this.
|
|
40744
|
+
return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
|
|
40038
40745
|
}
|
|
40039
40746
|
getEvaluatedPositions() {
|
|
40040
|
-
return [...this.evaluatedCells.keys()].map(this.
|
|
40747
|
+
return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
|
|
40041
40748
|
}
|
|
40042
40749
|
getArrayFormulaSpreadingOnId(positionId) {
|
|
40043
40750
|
if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
|
|
@@ -40047,7 +40754,7 @@ class Evaluator {
|
|
|
40047
40754
|
return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
|
|
40048
40755
|
}
|
|
40049
40756
|
updateDependencies(position) {
|
|
40050
|
-
const positionId = this.
|
|
40757
|
+
const positionId = this.encoder.encode(position);
|
|
40051
40758
|
this.formulaDependencies().removeAllDependencies(positionId);
|
|
40052
40759
|
const dependencies = this.getDirectDependencies(positionId);
|
|
40053
40760
|
this.formulaDependencies().addDependencies(positionId, dependencies);
|
|
@@ -40057,12 +40764,12 @@ class Evaluator {
|
|
|
40057
40764
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40058
40765
|
}
|
|
40059
40766
|
evaluateCells(positions) {
|
|
40060
|
-
const cells = positions.map(this.
|
|
40767
|
+
const cells = positions.map((p) => this.encoder.encode(p));
|
|
40061
40768
|
const cellsToCompute = new JetSet(cells);
|
|
40062
|
-
const
|
|
40769
|
+
const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
|
|
40063
40770
|
cellsToCompute.add(...this.getCellsDependingOn(cells));
|
|
40064
|
-
cellsToCompute.add(...
|
|
40065
|
-
cellsToCompute.add(...this.getCellsDependingOn(
|
|
40771
|
+
cellsToCompute.add(...arrayFormulasPositionIds);
|
|
40772
|
+
cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
|
|
40066
40773
|
this.evaluate(cellsToCompute);
|
|
40067
40774
|
}
|
|
40068
40775
|
getArrayFormulasImpactedByChangesOf(positionIds) {
|
|
@@ -40085,12 +40792,14 @@ class Evaluator {
|
|
|
40085
40792
|
this.blockedArrayFormulas = new Set();
|
|
40086
40793
|
this.spreadingRelations = new SpreadingRelation();
|
|
40087
40794
|
this.formulaDependencies = lazy(() => {
|
|
40088
|
-
const
|
|
40089
|
-
|
|
40090
|
-
|
|
40091
|
-
|
|
40092
|
-
|
|
40093
|
-
|
|
40795
|
+
const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
|
|
40796
|
+
data: positionId,
|
|
40797
|
+
boundingBox: {
|
|
40798
|
+
zone: range.zone,
|
|
40799
|
+
sheetId: range.sheetId,
|
|
40800
|
+
},
|
|
40801
|
+
})));
|
|
40802
|
+
return new FormulaDependencyGraph(this.encoder, dependencies);
|
|
40094
40803
|
});
|
|
40095
40804
|
}
|
|
40096
40805
|
evaluateAllCells() {
|
|
@@ -40112,7 +40821,7 @@ class Evaluator {
|
|
|
40112
40821
|
for (const sheetId of this.getters.getSheetIds()) {
|
|
40113
40822
|
const cellIds = this.getters.getCells(sheetId);
|
|
40114
40823
|
for (const cellId in cellIds) {
|
|
40115
|
-
positionIds.add(this.
|
|
40824
|
+
positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
|
|
40116
40825
|
}
|
|
40117
40826
|
}
|
|
40118
40827
|
return positionIds;
|
|
@@ -40179,7 +40888,7 @@ class Evaluator {
|
|
|
40179
40888
|
}
|
|
40180
40889
|
}
|
|
40181
40890
|
computeAndSave(position) {
|
|
40182
|
-
const positionId = this.
|
|
40891
|
+
const positionId = this.encoder.encode(position);
|
|
40183
40892
|
const evaluatedCell = this.computeCell(positionId);
|
|
40184
40893
|
if (!this.evaluatedCells.has(positionId)) {
|
|
40185
40894
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
@@ -40239,15 +40948,15 @@ class Evaluator {
|
|
|
40239
40948
|
throw new Error(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
|
|
40240
40949
|
}
|
|
40241
40950
|
updateSpreadRelation({ sheetId, col, row, }) {
|
|
40242
|
-
const arrayFormulaPositionId = this.
|
|
40951
|
+
const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40243
40952
|
return (i, j) => {
|
|
40244
40953
|
const position = { sheetId, col: i + col, row: j + row };
|
|
40245
|
-
const resultPositionId = this.
|
|
40954
|
+
const resultPositionId = this.encoder.encode(position);
|
|
40246
40955
|
this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
|
|
40247
40956
|
};
|
|
40248
40957
|
}
|
|
40249
40958
|
checkCollision({ sheetId, col, row }) {
|
|
40250
|
-
const formulaPositionId = this.
|
|
40959
|
+
const formulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40251
40960
|
return (i, j) => {
|
|
40252
40961
|
const position = { sheetId: sheetId, col: i + col, row: j + row };
|
|
40253
40962
|
const rawCell = this.getters.getCell(position);
|
|
@@ -40268,7 +40977,7 @@ class Evaluator {
|
|
|
40268
40977
|
format: format || matrixResult[i][j]?.format,
|
|
40269
40978
|
locale: this.getters.getLocale(),
|
|
40270
40979
|
});
|
|
40271
|
-
const positionId = this.
|
|
40980
|
+
const positionId = this.encoder.encode(position);
|
|
40272
40981
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
40273
40982
|
// check if formula dependencies present in the spread zone
|
|
40274
40983
|
// if so, they need to be recomputed
|
|
@@ -40300,29 +41009,17 @@ class Evaluator {
|
|
|
40300
41009
|
if (!cell?.isFormula) {
|
|
40301
41010
|
return [];
|
|
40302
41011
|
}
|
|
40303
|
-
|
|
40304
|
-
for (const range of cell.compiledFormula.dependencies) {
|
|
40305
|
-
if (range.invalidSheetName || range.invalidXc) {
|
|
40306
|
-
continue;
|
|
40307
|
-
}
|
|
40308
|
-
const sheetId = range.sheetId;
|
|
40309
|
-
forEachPositionsInZone(range.zone, (col, row) => {
|
|
40310
|
-
dependencies.push(this.encodePosition({ sheetId, col, row }));
|
|
40311
|
-
});
|
|
40312
|
-
}
|
|
40313
|
-
return dependencies;
|
|
41012
|
+
return cell.compiledFormula.dependencies;
|
|
40314
41013
|
}
|
|
40315
41014
|
getCellsDependingOn(positionIds) {
|
|
40316
|
-
|
|
41015
|
+
const ranges = [];
|
|
41016
|
+
for (const positionId of positionIds) {
|
|
41017
|
+
ranges.push(this.encoder.decodeToBoundingBox(positionId));
|
|
41018
|
+
}
|
|
41019
|
+
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
40317
41020
|
}
|
|
40318
41021
|
getCell(positionId) {
|
|
40319
|
-
return this.getters.getCell(this.
|
|
40320
|
-
}
|
|
40321
|
-
encodePosition(position) {
|
|
40322
|
-
return this.positionEncoder.encode(position);
|
|
40323
|
-
}
|
|
40324
|
-
decodePosition(positionId) {
|
|
40325
|
-
return this.positionEncoder.decode(positionId);
|
|
41022
|
+
return this.getters.getCell(this.encoder.decode(positionId));
|
|
40326
41023
|
}
|
|
40327
41024
|
}
|
|
40328
41025
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -40378,6 +41075,13 @@ class PositionBitsEncoder {
|
|
|
40378
41075
|
encode({ sheetId, col, row }) {
|
|
40379
41076
|
return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
|
|
40380
41077
|
}
|
|
41078
|
+
encodeBoundingBox({ sheetId, zone }) {
|
|
41079
|
+
const positions = [];
|
|
41080
|
+
forEachPositionsInZone(zone, (col, row) => {
|
|
41081
|
+
positions.push(this.encode({ sheetId, col, row }));
|
|
41082
|
+
});
|
|
41083
|
+
return positions;
|
|
41084
|
+
}
|
|
40381
41085
|
decode(id) {
|
|
40382
41086
|
// keep only the last 21 bits by AND-ing the bit sequence with 21 ones
|
|
40383
41087
|
const row = Number(id & 2097151n);
|
|
@@ -40385,6 +41089,10 @@ class PositionBitsEncoder {
|
|
|
40385
41089
|
const sheetId = this.decodeSheet(id >> 42n);
|
|
40386
41090
|
return { sheetId, col, row };
|
|
40387
41091
|
}
|
|
41092
|
+
decodeToBoundingBox(id) {
|
|
41093
|
+
const { sheetId, col, row } = this.decode(id);
|
|
41094
|
+
return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
|
|
41095
|
+
}
|
|
40388
41096
|
encodeSheet(sheetId) {
|
|
40389
41097
|
const sheetKey = this.sheetMapping[sheetId];
|
|
40390
41098
|
if (sheetKey === undefined) {
|
|
@@ -40555,7 +41263,12 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
40555
41263
|
// Getters
|
|
40556
41264
|
// ---------------------------------------------------------------------------
|
|
40557
41265
|
evaluateFormula(sheetId, formulaString) {
|
|
40558
|
-
|
|
41266
|
+
try {
|
|
41267
|
+
return this.evaluator.evaluateFormula(sheetId, formulaString);
|
|
41268
|
+
}
|
|
41269
|
+
catch (error) {
|
|
41270
|
+
return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
|
|
41271
|
+
}
|
|
40559
41272
|
}
|
|
40560
41273
|
/**
|
|
40561
41274
|
* Return the value of each cell in the range as they are displayed in the grid.
|
|
@@ -40824,7 +41537,7 @@ class CustomColorsPlugin extends UIPlugin {
|
|
|
40824
41537
|
}
|
|
40825
41538
|
|
|
40826
41539
|
class EvaluationChartPlugin extends UIPlugin {
|
|
40827
|
-
static getters = ["getChartRuntime", "
|
|
41540
|
+
static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
|
|
40828
41541
|
charts = {};
|
|
40829
41542
|
createRuntimeChart = chartRuntimeFactory(this.getters);
|
|
40830
41543
|
handle(cmd) {
|
|
@@ -40862,25 +41575,26 @@ class EvaluationChartPlugin extends UIPlugin {
|
|
|
40862
41575
|
return this.charts[figureId];
|
|
40863
41576
|
}
|
|
40864
41577
|
/**
|
|
40865
|
-
* Get the background
|
|
40866
|
-
* of the chart. In order of priority, it will return :
|
|
40867
|
-
*
|
|
40868
|
-
* - the chart background color if one is defined
|
|
40869
|
-
* - the fill color of the cell if one is defined
|
|
40870
|
-
* - the fill color of the cell from conditional formats if one is defined
|
|
40871
|
-
* - the default chart color if no other color is defined
|
|
41578
|
+
* Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
|
|
40872
41579
|
*/
|
|
40873
|
-
|
|
41580
|
+
getStyleOfSingleCellChart(chartBackground, mainRange) {
|
|
40874
41581
|
if (chartBackground)
|
|
40875
|
-
return chartBackground;
|
|
41582
|
+
return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
|
|
40876
41583
|
if (!mainRange) {
|
|
40877
|
-
return
|
|
41584
|
+
return {
|
|
41585
|
+
background: BACKGROUND_CHART_COLOR,
|
|
41586
|
+
fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
|
|
41587
|
+
};
|
|
40878
41588
|
}
|
|
40879
41589
|
const col = mainRange.zone.left;
|
|
40880
41590
|
const row = mainRange.zone.top;
|
|
40881
41591
|
const sheetId = mainRange.sheetId;
|
|
40882
41592
|
const style = this.getters.getCellComputedStyle({ sheetId, col, row });
|
|
40883
|
-
|
|
41593
|
+
const background = style.fillColor || BACKGROUND_CHART_COLOR;
|
|
41594
|
+
return {
|
|
41595
|
+
background,
|
|
41596
|
+
fontColor: style.textColor || chartFontColor(background),
|
|
41597
|
+
};
|
|
40884
41598
|
}
|
|
40885
41599
|
exportForExcel(data) {
|
|
40886
41600
|
for (const sheet of data.sheets) {
|
|
@@ -40991,45 +41705,40 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
|
|
|
40991
41705
|
getComputedStyles(sheetId) {
|
|
40992
41706
|
const computedStyle = {};
|
|
40993
41707
|
for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
|
|
40994
|
-
|
|
40995
|
-
|
|
40996
|
-
|
|
40997
|
-
|
|
40998
|
-
|
|
40999
|
-
|
|
41000
|
-
|
|
41001
|
-
|
|
41002
|
-
|
|
41003
|
-
|
|
41004
|
-
|
|
41005
|
-
for (let
|
|
41006
|
-
|
|
41007
|
-
|
|
41008
|
-
|
|
41009
|
-
const
|
|
41010
|
-
|
|
41011
|
-
|
|
41012
|
-
|
|
41013
|
-
|
|
41014
|
-
|
|
41015
|
-
});
|
|
41016
|
-
}
|
|
41017
|
-
return value;
|
|
41018
|
-
});
|
|
41019
|
-
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41020
|
-
if (!computedStyle[col])
|
|
41021
|
-
computedStyle[col] = [];
|
|
41022
|
-
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41023
|
-
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41708
|
+
switch (cf.rule.type) {
|
|
41709
|
+
case "ColorScaleRule":
|
|
41710
|
+
for (let range of cf.ranges) {
|
|
41711
|
+
this.applyColorScale(sheetId, range, cf.rule, computedStyle);
|
|
41712
|
+
}
|
|
41713
|
+
break;
|
|
41714
|
+
case "CellIsRule":
|
|
41715
|
+
const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
|
|
41716
|
+
for (let ref of cf.ranges) {
|
|
41717
|
+
const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
|
|
41718
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
41719
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
41720
|
+
const predicate = this.rulePredicate[cf.rule.type];
|
|
41721
|
+
const target = { sheetId, col, row };
|
|
41722
|
+
const values = cf.rule.values.map((value, i) => {
|
|
41723
|
+
const compiledFormula = formulas[i];
|
|
41724
|
+
if (compiledFormula) {
|
|
41725
|
+
return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
|
|
41726
|
+
...compiledFormula,
|
|
41727
|
+
dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
|
|
41728
|
+
});
|
|
41024
41729
|
}
|
|
41730
|
+
return value;
|
|
41731
|
+
});
|
|
41732
|
+
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41733
|
+
if (!computedStyle[col])
|
|
41734
|
+
computedStyle[col] = [];
|
|
41735
|
+
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41736
|
+
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41025
41737
|
}
|
|
41026
41738
|
}
|
|
41027
41739
|
}
|
|
41028
|
-
|
|
41029
|
-
|
|
41030
|
-
}
|
|
41031
|
-
catch (_) {
|
|
41032
|
-
// we don't care about the errors within the evaluation of a rule
|
|
41740
|
+
}
|
|
41741
|
+
break;
|
|
41033
41742
|
}
|
|
41034
41743
|
}
|
|
41035
41744
|
return computedStyle;
|
|
@@ -41801,7 +42510,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
41801
42510
|
let row = zone.bottom;
|
|
41802
42511
|
if (col > 0) {
|
|
41803
42512
|
let leftPosition = { sheetId, col: col - 1, row };
|
|
41804
|
-
while (this.getters.
|
|
42513
|
+
while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
|
|
41805
42514
|
this.getters.getCell(leftPosition)?.content) {
|
|
41806
42515
|
row += 1;
|
|
41807
42516
|
leftPosition = { sheetId, col: col - 1, row };
|
|
@@ -41811,7 +42520,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
41811
42520
|
col = zone.right;
|
|
41812
42521
|
if (col <= this.getters.getNumberCols(sheetId)) {
|
|
41813
42522
|
let rightPosition = { sheetId, col: col + 1, row };
|
|
41814
|
-
while (this.getters.
|
|
42523
|
+
while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
|
|
41815
42524
|
this.getters.getCell(rightPosition)?.content) {
|
|
41816
42525
|
row += 1;
|
|
41817
42526
|
rightPosition = { sheetId, col: col + 1, row };
|
|
@@ -52192,7 +52901,7 @@ class Spreadsheet extends Component {
|
|
|
52192
52901
|
}
|
|
52193
52902
|
onKeydown(ev) {
|
|
52194
52903
|
let keyDownString = "";
|
|
52195
|
-
if (ev
|
|
52904
|
+
if (isCtrlKey(ev)) {
|
|
52196
52905
|
keyDownString += "CTRL+";
|
|
52197
52906
|
}
|
|
52198
52907
|
keyDownString += ev.key.toUpperCase();
|
|
@@ -55904,6 +56613,7 @@ const helpers = {
|
|
|
55904
56613
|
colorToRGBA,
|
|
55905
56614
|
positionToZone,
|
|
55906
56615
|
isDefined: isDefined$1,
|
|
56616
|
+
isMatrix,
|
|
55907
56617
|
lazy,
|
|
55908
56618
|
genericRepeat,
|
|
55909
56619
|
createAction,
|
|
@@ -55952,6 +56662,6 @@ const constants = {
|
|
|
55952
56662
|
export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
|
|
55953
56663
|
|
|
55954
56664
|
|
|
55955
|
-
__info__.version = "17.0.
|
|
55956
|
-
__info__.date = "
|
|
55957
|
-
__info__.hash = "
|
|
56665
|
+
__info__.version = "17.0.6";
|
|
56666
|
+
__info__.date = "2024-01-04T12:23:10.357Z";
|
|
56667
|
+
__info__.hash = "310ca57";
|