@odoo/o-spreadsheet 16.4.16 → 16.4.17
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 +846 -132
- package/dist/o-spreadsheet.d.ts +10 -10
- package/dist/o-spreadsheet.esm.js +846 -132
- package/dist/o-spreadsheet.iife.js +846 -132
- package/dist/o-spreadsheet.iife.min.js +270 -270
- package/dist/o_spreadsheet.xml +3 -3
- package/package.json +4 -2
|
@@ -5601,6 +5601,7 @@ const FilterMenuPopoverBuilder = {
|
|
|
5601
5601
|
},
|
|
5602
5602
|
};
|
|
5603
5603
|
|
|
5604
|
+
const macRegex = /Mac/i;
|
|
5604
5605
|
/**
|
|
5605
5606
|
* Return true if the event was triggered from
|
|
5606
5607
|
* a child element.
|
|
@@ -5640,7 +5641,15 @@ function getOpenedMenus() {
|
|
|
5640
5641
|
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
5641
5642
|
}
|
|
5642
5643
|
function isMacOS() {
|
|
5643
|
-
return navigator.userAgent
|
|
5644
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
5645
|
+
}
|
|
5646
|
+
/**
|
|
5647
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
5648
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
5649
|
+
* On Mac, this is the "meta" or "command" key.
|
|
5650
|
+
*/
|
|
5651
|
+
function isCtrlKey(ev) {
|
|
5652
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
5644
5653
|
}
|
|
5645
5654
|
|
|
5646
5655
|
/**
|
|
@@ -20494,10 +20503,10 @@ const arrowMap = {
|
|
|
20494
20503
|
function updateSelectionWithArrowKeys(ev, selection) {
|
|
20495
20504
|
const direction = arrowMap[ev.key];
|
|
20496
20505
|
if (ev.shiftKey) {
|
|
20497
|
-
selection.resizeAnchorZone(direction, ev
|
|
20506
|
+
selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
|
|
20498
20507
|
}
|
|
20499
20508
|
else {
|
|
20500
|
-
selection.moveAnchorCell(direction, ev
|
|
20509
|
+
selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
|
|
20501
20510
|
}
|
|
20502
20511
|
}
|
|
20503
20512
|
|
|
@@ -25542,7 +25551,10 @@ class GridOverlay extends owl.Component {
|
|
|
25542
25551
|
return;
|
|
25543
25552
|
}
|
|
25544
25553
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
25545
|
-
this.props.onCellClicked(col, row, {
|
|
25554
|
+
this.props.onCellClicked(col, row, {
|
|
25555
|
+
expandZone: ev.shiftKey,
|
|
25556
|
+
addZone: isCtrlKey(ev),
|
|
25557
|
+
});
|
|
25546
25558
|
}
|
|
25547
25559
|
onDoubleClick(ev) {
|
|
25548
25560
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
@@ -25770,7 +25782,7 @@ class AbstractResizer extends owl.Component {
|
|
|
25770
25782
|
this._increaseSelection(index);
|
|
25771
25783
|
}
|
|
25772
25784
|
else {
|
|
25773
|
-
this._selectElement(index, ev
|
|
25785
|
+
this._selectElement(index, isCtrlKey(ev));
|
|
25774
25786
|
}
|
|
25775
25787
|
this.lastSelectedElementIndex = index;
|
|
25776
25788
|
const mouseMoveSelect = (col, row) => {
|
|
@@ -25783,7 +25795,7 @@ class AbstractResizer extends owl.Component {
|
|
|
25783
25795
|
const mouseUpSelect = () => {
|
|
25784
25796
|
this.state.isSelecting = false;
|
|
25785
25797
|
this.lastSelectedElementIndex = null;
|
|
25786
|
-
this.env.model.dispatch(ev
|
|
25798
|
+
this.env.model.dispatch(isCtrlKey(ev) ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
|
|
25787
25799
|
this._computeGrabDisplay(ev);
|
|
25788
25800
|
};
|
|
25789
25801
|
dragAndDropBeyondTheViewport(this.env, mouseMoveSelect, mouseUpSelect);
|
|
@@ -25935,8 +25947,8 @@ class ColResizer extends AbstractResizer {
|
|
|
25935
25947
|
this.env.raiseError(MergeErrorMessage);
|
|
25936
25948
|
}
|
|
25937
25949
|
}
|
|
25938
|
-
_selectElement(index,
|
|
25939
|
-
this.env.model.selection.selectColumn(index,
|
|
25950
|
+
_selectElement(index, addDistinctHeader) {
|
|
25951
|
+
this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
25940
25952
|
}
|
|
25941
25953
|
_increaseSelection(index) {
|
|
25942
25954
|
this.env.model.selection.selectColumn(index, "updateAnchor");
|
|
@@ -26109,8 +26121,8 @@ class RowResizer extends AbstractResizer {
|
|
|
26109
26121
|
this.env.raiseError(MergeErrorMessage);
|
|
26110
26122
|
}
|
|
26111
26123
|
}
|
|
26112
|
-
_selectElement(index,
|
|
26113
|
-
this.env.model.selection.selectRow(index,
|
|
26124
|
+
_selectElement(index, addDistinctHeader) {
|
|
26125
|
+
this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
26114
26126
|
}
|
|
26115
26127
|
_increaseSelection(index) {
|
|
26116
26128
|
this.env.model.selection.selectRow(index, "updateAnchor");
|
|
@@ -26916,8 +26928,8 @@ class Grid extends owl.Component {
|
|
|
26916
26928
|
// ---------------------------------------------------------------------------
|
|
26917
26929
|
// Zone selection with mouse
|
|
26918
26930
|
// ---------------------------------------------------------------------------
|
|
26919
|
-
onCellClicked(col, row, {
|
|
26920
|
-
if (
|
|
26931
|
+
onCellClicked(col, row, { addZone, expandZone }) {
|
|
26932
|
+
if (addZone) {
|
|
26921
26933
|
this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
|
|
26922
26934
|
}
|
|
26923
26935
|
if (this.env.model.getters.hasOpenedPopover()) {
|
|
@@ -26926,10 +26938,10 @@ class Grid extends owl.Component {
|
|
|
26926
26938
|
if (this.env.model.getters.getEditionMode() === "editing") {
|
|
26927
26939
|
this.env.model.dispatch("STOP_EDITION");
|
|
26928
26940
|
}
|
|
26929
|
-
if (
|
|
26941
|
+
if (expandZone) {
|
|
26930
26942
|
this.env.model.selection.setAnchorCorner(col, row);
|
|
26931
26943
|
}
|
|
26932
|
-
else if (
|
|
26944
|
+
else if (addZone) {
|
|
26933
26945
|
this.env.model.selection.addCellToSelection(col, row);
|
|
26934
26946
|
}
|
|
26935
26947
|
else {
|
|
@@ -26991,9 +27003,7 @@ class Grid extends owl.Component {
|
|
|
26991
27003
|
return;
|
|
26992
27004
|
}
|
|
26993
27005
|
let keyDownString = "";
|
|
26994
|
-
if (ev
|
|
26995
|
-
keyDownString += "CTRL+";
|
|
26996
|
-
if (ev.metaKey)
|
|
27006
|
+
if (isCtrlKey(ev))
|
|
26997
27007
|
keyDownString += "CTRL+";
|
|
26998
27008
|
if (ev.altKey)
|
|
26999
27009
|
keyDownString += "ALT+";
|
|
@@ -35562,56 +35572,758 @@ class CompilationParametersBuilder {
|
|
|
35562
35572
|
}
|
|
35563
35573
|
}
|
|
35564
35574
|
|
|
35575
|
+
function quickselect(arr, k, left, right, compare) {
|
|
35576
|
+
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
|
|
35577
|
+
}
|
|
35578
|
+
|
|
35579
|
+
function quickselectStep(arr, k, left, right, compare) {
|
|
35580
|
+
|
|
35581
|
+
while (right > left) {
|
|
35582
|
+
if (right - left > 600) {
|
|
35583
|
+
var n = right - left + 1;
|
|
35584
|
+
var m = k - left + 1;
|
|
35585
|
+
var z = Math.log(n);
|
|
35586
|
+
var s = 0.5 * Math.exp(2 * z / 3);
|
|
35587
|
+
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
|
|
35588
|
+
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
|
|
35589
|
+
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
|
|
35590
|
+
quickselectStep(arr, k, newLeft, newRight, compare);
|
|
35591
|
+
}
|
|
35592
|
+
|
|
35593
|
+
var t = arr[k];
|
|
35594
|
+
var i = left;
|
|
35595
|
+
var j = right;
|
|
35596
|
+
|
|
35597
|
+
swap(arr, left, k);
|
|
35598
|
+
if (compare(arr[right], t) > 0) swap(arr, left, right);
|
|
35599
|
+
|
|
35600
|
+
while (i < j) {
|
|
35601
|
+
swap(arr, i, j);
|
|
35602
|
+
i++;
|
|
35603
|
+
j--;
|
|
35604
|
+
while (compare(arr[i], t) < 0) i++;
|
|
35605
|
+
while (compare(arr[j], t) > 0) j--;
|
|
35606
|
+
}
|
|
35607
|
+
|
|
35608
|
+
if (compare(arr[left], t) === 0) swap(arr, left, j);
|
|
35609
|
+
else {
|
|
35610
|
+
j++;
|
|
35611
|
+
swap(arr, j, right);
|
|
35612
|
+
}
|
|
35613
|
+
|
|
35614
|
+
if (j <= k) left = j + 1;
|
|
35615
|
+
if (k <= j) right = j - 1;
|
|
35616
|
+
}
|
|
35617
|
+
}
|
|
35618
|
+
|
|
35619
|
+
function swap(arr, i, j) {
|
|
35620
|
+
var tmp = arr[i];
|
|
35621
|
+
arr[i] = arr[j];
|
|
35622
|
+
arr[j] = tmp;
|
|
35623
|
+
}
|
|
35624
|
+
|
|
35625
|
+
function defaultCompare(a, b) {
|
|
35626
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
35627
|
+
}
|
|
35628
|
+
|
|
35629
|
+
class RBush {
|
|
35630
|
+
constructor(maxEntries = 9) {
|
|
35631
|
+
// max entries in a node is 9 by default; min node fill is 40% for best performance
|
|
35632
|
+
this._maxEntries = Math.max(4, maxEntries);
|
|
35633
|
+
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
|
|
35634
|
+
this.clear();
|
|
35635
|
+
}
|
|
35636
|
+
|
|
35637
|
+
all() {
|
|
35638
|
+
return this._all(this.data, []);
|
|
35639
|
+
}
|
|
35640
|
+
|
|
35641
|
+
search(bbox) {
|
|
35642
|
+
let node = this.data;
|
|
35643
|
+
const result = [];
|
|
35644
|
+
|
|
35645
|
+
if (!intersects(bbox, node)) return result;
|
|
35646
|
+
|
|
35647
|
+
const toBBox = this.toBBox;
|
|
35648
|
+
const nodesToSearch = [];
|
|
35649
|
+
|
|
35650
|
+
while (node) {
|
|
35651
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
35652
|
+
const child = node.children[i];
|
|
35653
|
+
const childBBox = node.leaf ? toBBox(child) : child;
|
|
35654
|
+
|
|
35655
|
+
if (intersects(bbox, childBBox)) {
|
|
35656
|
+
if (node.leaf) result.push(child);
|
|
35657
|
+
else if (contains(bbox, childBBox)) this._all(child, result);
|
|
35658
|
+
else nodesToSearch.push(child);
|
|
35659
|
+
}
|
|
35660
|
+
}
|
|
35661
|
+
node = nodesToSearch.pop();
|
|
35662
|
+
}
|
|
35663
|
+
|
|
35664
|
+
return result;
|
|
35665
|
+
}
|
|
35666
|
+
|
|
35667
|
+
collides(bbox) {
|
|
35668
|
+
let node = this.data;
|
|
35669
|
+
|
|
35670
|
+
if (!intersects(bbox, node)) return false;
|
|
35671
|
+
|
|
35672
|
+
const nodesToSearch = [];
|
|
35673
|
+
while (node) {
|
|
35674
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
35675
|
+
const child = node.children[i];
|
|
35676
|
+
const childBBox = node.leaf ? this.toBBox(child) : child;
|
|
35677
|
+
|
|
35678
|
+
if (intersects(bbox, childBBox)) {
|
|
35679
|
+
if (node.leaf || contains(bbox, childBBox)) return true;
|
|
35680
|
+
nodesToSearch.push(child);
|
|
35681
|
+
}
|
|
35682
|
+
}
|
|
35683
|
+
node = nodesToSearch.pop();
|
|
35684
|
+
}
|
|
35685
|
+
|
|
35686
|
+
return false;
|
|
35687
|
+
}
|
|
35688
|
+
|
|
35689
|
+
load(data) {
|
|
35690
|
+
if (!(data && data.length)) return this;
|
|
35691
|
+
|
|
35692
|
+
if (data.length < this._minEntries) {
|
|
35693
|
+
for (let i = 0; i < data.length; i++) {
|
|
35694
|
+
this.insert(data[i]);
|
|
35695
|
+
}
|
|
35696
|
+
return this;
|
|
35697
|
+
}
|
|
35698
|
+
|
|
35699
|
+
// recursively build the tree with the given data from scratch using OMT algorithm
|
|
35700
|
+
let node = this._build(data.slice(), 0, data.length - 1, 0);
|
|
35701
|
+
|
|
35702
|
+
if (!this.data.children.length) {
|
|
35703
|
+
// save as is if tree is empty
|
|
35704
|
+
this.data = node;
|
|
35705
|
+
|
|
35706
|
+
} else if (this.data.height === node.height) {
|
|
35707
|
+
// split root if trees have the same height
|
|
35708
|
+
this._splitRoot(this.data, node);
|
|
35709
|
+
|
|
35710
|
+
} else {
|
|
35711
|
+
if (this.data.height < node.height) {
|
|
35712
|
+
// swap trees if inserted one is bigger
|
|
35713
|
+
const tmpNode = this.data;
|
|
35714
|
+
this.data = node;
|
|
35715
|
+
node = tmpNode;
|
|
35716
|
+
}
|
|
35717
|
+
|
|
35718
|
+
// insert the small tree into the large tree at appropriate level
|
|
35719
|
+
this._insert(node, this.data.height - node.height - 1, true);
|
|
35720
|
+
}
|
|
35721
|
+
|
|
35722
|
+
return this;
|
|
35723
|
+
}
|
|
35724
|
+
|
|
35725
|
+
insert(item) {
|
|
35726
|
+
if (item) this._insert(item, this.data.height - 1);
|
|
35727
|
+
return this;
|
|
35728
|
+
}
|
|
35729
|
+
|
|
35730
|
+
clear() {
|
|
35731
|
+
this.data = createNode([]);
|
|
35732
|
+
return this;
|
|
35733
|
+
}
|
|
35734
|
+
|
|
35735
|
+
remove(item, equalsFn) {
|
|
35736
|
+
if (!item) return this;
|
|
35737
|
+
|
|
35738
|
+
let node = this.data;
|
|
35739
|
+
const bbox = this.toBBox(item);
|
|
35740
|
+
const path = [];
|
|
35741
|
+
const indexes = [];
|
|
35742
|
+
let i, parent, goingUp;
|
|
35743
|
+
|
|
35744
|
+
// depth-first iterative tree traversal
|
|
35745
|
+
while (node || path.length) {
|
|
35746
|
+
|
|
35747
|
+
if (!node) { // go up
|
|
35748
|
+
node = path.pop();
|
|
35749
|
+
parent = path[path.length - 1];
|
|
35750
|
+
i = indexes.pop();
|
|
35751
|
+
goingUp = true;
|
|
35752
|
+
}
|
|
35753
|
+
|
|
35754
|
+
if (node.leaf) { // check current node
|
|
35755
|
+
const index = findItem(item, node.children, equalsFn);
|
|
35756
|
+
|
|
35757
|
+
if (index !== -1) {
|
|
35758
|
+
// item found, remove the item and condense tree upwards
|
|
35759
|
+
node.children.splice(index, 1);
|
|
35760
|
+
path.push(node);
|
|
35761
|
+
this._condense(path);
|
|
35762
|
+
return this;
|
|
35763
|
+
}
|
|
35764
|
+
}
|
|
35765
|
+
|
|
35766
|
+
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
|
|
35767
|
+
path.push(node);
|
|
35768
|
+
indexes.push(i);
|
|
35769
|
+
i = 0;
|
|
35770
|
+
parent = node;
|
|
35771
|
+
node = node.children[0];
|
|
35772
|
+
|
|
35773
|
+
} else if (parent) { // go right
|
|
35774
|
+
i++;
|
|
35775
|
+
node = parent.children[i];
|
|
35776
|
+
goingUp = false;
|
|
35777
|
+
|
|
35778
|
+
} else node = null; // nothing found
|
|
35779
|
+
}
|
|
35780
|
+
|
|
35781
|
+
return this;
|
|
35782
|
+
}
|
|
35783
|
+
|
|
35784
|
+
toBBox(item) { return item; }
|
|
35785
|
+
|
|
35786
|
+
compareMinX(a, b) { return a.minX - b.minX; }
|
|
35787
|
+
compareMinY(a, b) { return a.minY - b.minY; }
|
|
35788
|
+
|
|
35789
|
+
toJSON() { return this.data; }
|
|
35790
|
+
|
|
35791
|
+
fromJSON(data) {
|
|
35792
|
+
this.data = data;
|
|
35793
|
+
return this;
|
|
35794
|
+
}
|
|
35795
|
+
|
|
35796
|
+
_all(node, result) {
|
|
35797
|
+
const nodesToSearch = [];
|
|
35798
|
+
while (node) {
|
|
35799
|
+
if (node.leaf) result.push(...node.children);
|
|
35800
|
+
else nodesToSearch.push(...node.children);
|
|
35801
|
+
|
|
35802
|
+
node = nodesToSearch.pop();
|
|
35803
|
+
}
|
|
35804
|
+
return result;
|
|
35805
|
+
}
|
|
35806
|
+
|
|
35807
|
+
_build(items, left, right, height) {
|
|
35808
|
+
|
|
35809
|
+
const N = right - left + 1;
|
|
35810
|
+
let M = this._maxEntries;
|
|
35811
|
+
let node;
|
|
35812
|
+
|
|
35813
|
+
if (N <= M) {
|
|
35814
|
+
// reached leaf level; return leaf
|
|
35815
|
+
node = createNode(items.slice(left, right + 1));
|
|
35816
|
+
calcBBox(node, this.toBBox);
|
|
35817
|
+
return node;
|
|
35818
|
+
}
|
|
35819
|
+
|
|
35820
|
+
if (!height) {
|
|
35821
|
+
// target height of the bulk-loaded tree
|
|
35822
|
+
height = Math.ceil(Math.log(N) / Math.log(M));
|
|
35823
|
+
|
|
35824
|
+
// target number of root entries to maximize storage utilization
|
|
35825
|
+
M = Math.ceil(N / Math.pow(M, height - 1));
|
|
35826
|
+
}
|
|
35827
|
+
|
|
35828
|
+
node = createNode([]);
|
|
35829
|
+
node.leaf = false;
|
|
35830
|
+
node.height = height;
|
|
35831
|
+
|
|
35832
|
+
// split the items into M mostly square tiles
|
|
35833
|
+
|
|
35834
|
+
const N2 = Math.ceil(N / M);
|
|
35835
|
+
const N1 = N2 * Math.ceil(Math.sqrt(M));
|
|
35836
|
+
|
|
35837
|
+
multiSelect(items, left, right, N1, this.compareMinX);
|
|
35838
|
+
|
|
35839
|
+
for (let i = left; i <= right; i += N1) {
|
|
35840
|
+
|
|
35841
|
+
const right2 = Math.min(i + N1 - 1, right);
|
|
35842
|
+
|
|
35843
|
+
multiSelect(items, i, right2, N2, this.compareMinY);
|
|
35844
|
+
|
|
35845
|
+
for (let j = i; j <= right2; j += N2) {
|
|
35846
|
+
|
|
35847
|
+
const right3 = Math.min(j + N2 - 1, right2);
|
|
35848
|
+
|
|
35849
|
+
// pack each entry recursively
|
|
35850
|
+
node.children.push(this._build(items, j, right3, height - 1));
|
|
35851
|
+
}
|
|
35852
|
+
}
|
|
35853
|
+
|
|
35854
|
+
calcBBox(node, this.toBBox);
|
|
35855
|
+
|
|
35856
|
+
return node;
|
|
35857
|
+
}
|
|
35858
|
+
|
|
35859
|
+
_chooseSubtree(bbox, node, level, path) {
|
|
35860
|
+
while (true) {
|
|
35861
|
+
path.push(node);
|
|
35862
|
+
|
|
35863
|
+
if (node.leaf || path.length - 1 === level) break;
|
|
35864
|
+
|
|
35865
|
+
let minArea = Infinity;
|
|
35866
|
+
let minEnlargement = Infinity;
|
|
35867
|
+
let targetNode;
|
|
35868
|
+
|
|
35869
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
35870
|
+
const child = node.children[i];
|
|
35871
|
+
const area = bboxArea(child);
|
|
35872
|
+
const enlargement = enlargedArea(bbox, child) - area;
|
|
35873
|
+
|
|
35874
|
+
// choose entry with the least area enlargement
|
|
35875
|
+
if (enlargement < minEnlargement) {
|
|
35876
|
+
minEnlargement = enlargement;
|
|
35877
|
+
minArea = area < minArea ? area : minArea;
|
|
35878
|
+
targetNode = child;
|
|
35879
|
+
|
|
35880
|
+
} else if (enlargement === minEnlargement) {
|
|
35881
|
+
// otherwise choose one with the smallest area
|
|
35882
|
+
if (area < minArea) {
|
|
35883
|
+
minArea = area;
|
|
35884
|
+
targetNode = child;
|
|
35885
|
+
}
|
|
35886
|
+
}
|
|
35887
|
+
}
|
|
35888
|
+
|
|
35889
|
+
node = targetNode || node.children[0];
|
|
35890
|
+
}
|
|
35891
|
+
|
|
35892
|
+
return node;
|
|
35893
|
+
}
|
|
35894
|
+
|
|
35895
|
+
_insert(item, level, isNode) {
|
|
35896
|
+
const bbox = isNode ? item : this.toBBox(item);
|
|
35897
|
+
const insertPath = [];
|
|
35898
|
+
|
|
35899
|
+
// find the best node for accommodating the item, saving all nodes along the path too
|
|
35900
|
+
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
|
|
35901
|
+
|
|
35902
|
+
// put the item into the node
|
|
35903
|
+
node.children.push(item);
|
|
35904
|
+
extend(node, bbox);
|
|
35905
|
+
|
|
35906
|
+
// split on node overflow; propagate upwards if necessary
|
|
35907
|
+
while (level >= 0) {
|
|
35908
|
+
if (insertPath[level].children.length > this._maxEntries) {
|
|
35909
|
+
this._split(insertPath, level);
|
|
35910
|
+
level--;
|
|
35911
|
+
} else break;
|
|
35912
|
+
}
|
|
35913
|
+
|
|
35914
|
+
// adjust bboxes along the insertion path
|
|
35915
|
+
this._adjustParentBBoxes(bbox, insertPath, level);
|
|
35916
|
+
}
|
|
35917
|
+
|
|
35918
|
+
// split overflowed node into two
|
|
35919
|
+
_split(insertPath, level) {
|
|
35920
|
+
const node = insertPath[level];
|
|
35921
|
+
const M = node.children.length;
|
|
35922
|
+
const m = this._minEntries;
|
|
35923
|
+
|
|
35924
|
+
this._chooseSplitAxis(node, m, M);
|
|
35925
|
+
|
|
35926
|
+
const splitIndex = this._chooseSplitIndex(node, m, M);
|
|
35927
|
+
|
|
35928
|
+
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
|
|
35929
|
+
newNode.height = node.height;
|
|
35930
|
+
newNode.leaf = node.leaf;
|
|
35931
|
+
|
|
35932
|
+
calcBBox(node, this.toBBox);
|
|
35933
|
+
calcBBox(newNode, this.toBBox);
|
|
35934
|
+
|
|
35935
|
+
if (level) insertPath[level - 1].children.push(newNode);
|
|
35936
|
+
else this._splitRoot(node, newNode);
|
|
35937
|
+
}
|
|
35938
|
+
|
|
35939
|
+
_splitRoot(node, newNode) {
|
|
35940
|
+
// split root node
|
|
35941
|
+
this.data = createNode([node, newNode]);
|
|
35942
|
+
this.data.height = node.height + 1;
|
|
35943
|
+
this.data.leaf = false;
|
|
35944
|
+
calcBBox(this.data, this.toBBox);
|
|
35945
|
+
}
|
|
35946
|
+
|
|
35947
|
+
_chooseSplitIndex(node, m, M) {
|
|
35948
|
+
let index;
|
|
35949
|
+
let minOverlap = Infinity;
|
|
35950
|
+
let minArea = Infinity;
|
|
35951
|
+
|
|
35952
|
+
for (let i = m; i <= M - m; i++) {
|
|
35953
|
+
const bbox1 = distBBox(node, 0, i, this.toBBox);
|
|
35954
|
+
const bbox2 = distBBox(node, i, M, this.toBBox);
|
|
35955
|
+
|
|
35956
|
+
const overlap = intersectionArea(bbox1, bbox2);
|
|
35957
|
+
const area = bboxArea(bbox1) + bboxArea(bbox2);
|
|
35958
|
+
|
|
35959
|
+
// choose distribution with minimum overlap
|
|
35960
|
+
if (overlap < minOverlap) {
|
|
35961
|
+
minOverlap = overlap;
|
|
35962
|
+
index = i;
|
|
35963
|
+
|
|
35964
|
+
minArea = area < minArea ? area : minArea;
|
|
35965
|
+
|
|
35966
|
+
} else if (overlap === minOverlap) {
|
|
35967
|
+
// otherwise choose distribution with minimum area
|
|
35968
|
+
if (area < minArea) {
|
|
35969
|
+
minArea = area;
|
|
35970
|
+
index = i;
|
|
35971
|
+
}
|
|
35972
|
+
}
|
|
35973
|
+
}
|
|
35974
|
+
|
|
35975
|
+
return index || M - m;
|
|
35976
|
+
}
|
|
35977
|
+
|
|
35978
|
+
// sorts node children by the best axis for split
|
|
35979
|
+
_chooseSplitAxis(node, m, M) {
|
|
35980
|
+
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
|
|
35981
|
+
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
|
|
35982
|
+
const xMargin = this._allDistMargin(node, m, M, compareMinX);
|
|
35983
|
+
const yMargin = this._allDistMargin(node, m, M, compareMinY);
|
|
35984
|
+
|
|
35985
|
+
// if total distributions margin value is minimal for x, sort by minX,
|
|
35986
|
+
// otherwise it's already sorted by minY
|
|
35987
|
+
if (xMargin < yMargin) node.children.sort(compareMinX);
|
|
35988
|
+
}
|
|
35989
|
+
|
|
35990
|
+
// total margin of all possible split distributions where each node is at least m full
|
|
35991
|
+
_allDistMargin(node, m, M, compare) {
|
|
35992
|
+
node.children.sort(compare);
|
|
35993
|
+
|
|
35994
|
+
const toBBox = this.toBBox;
|
|
35995
|
+
const leftBBox = distBBox(node, 0, m, toBBox);
|
|
35996
|
+
const rightBBox = distBBox(node, M - m, M, toBBox);
|
|
35997
|
+
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
|
|
35998
|
+
|
|
35999
|
+
for (let i = m; i < M - m; i++) {
|
|
36000
|
+
const child = node.children[i];
|
|
36001
|
+
extend(leftBBox, node.leaf ? toBBox(child) : child);
|
|
36002
|
+
margin += bboxMargin(leftBBox);
|
|
36003
|
+
}
|
|
36004
|
+
|
|
36005
|
+
for (let i = M - m - 1; i >= m; i--) {
|
|
36006
|
+
const child = node.children[i];
|
|
36007
|
+
extend(rightBBox, node.leaf ? toBBox(child) : child);
|
|
36008
|
+
margin += bboxMargin(rightBBox);
|
|
36009
|
+
}
|
|
36010
|
+
|
|
36011
|
+
return margin;
|
|
36012
|
+
}
|
|
36013
|
+
|
|
36014
|
+
_adjustParentBBoxes(bbox, path, level) {
|
|
36015
|
+
// adjust bboxes along the given tree path
|
|
36016
|
+
for (let i = level; i >= 0; i--) {
|
|
36017
|
+
extend(path[i], bbox);
|
|
36018
|
+
}
|
|
36019
|
+
}
|
|
36020
|
+
|
|
36021
|
+
_condense(path) {
|
|
36022
|
+
// go through the path, removing empty nodes and updating bboxes
|
|
36023
|
+
for (let i = path.length - 1, siblings; i >= 0; i--) {
|
|
36024
|
+
if (path[i].children.length === 0) {
|
|
36025
|
+
if (i > 0) {
|
|
36026
|
+
siblings = path[i - 1].children;
|
|
36027
|
+
siblings.splice(siblings.indexOf(path[i]), 1);
|
|
36028
|
+
|
|
36029
|
+
} else this.clear();
|
|
36030
|
+
|
|
36031
|
+
} else calcBBox(path[i], this.toBBox);
|
|
36032
|
+
}
|
|
36033
|
+
}
|
|
36034
|
+
}
|
|
36035
|
+
|
|
36036
|
+
function findItem(item, items, equalsFn) {
|
|
36037
|
+
if (!equalsFn) return items.indexOf(item);
|
|
36038
|
+
|
|
36039
|
+
for (let i = 0; i < items.length; i++) {
|
|
36040
|
+
if (equalsFn(item, items[i])) return i;
|
|
36041
|
+
}
|
|
36042
|
+
return -1;
|
|
36043
|
+
}
|
|
36044
|
+
|
|
36045
|
+
// calculate node's bbox from bboxes of its children
|
|
36046
|
+
function calcBBox(node, toBBox) {
|
|
36047
|
+
distBBox(node, 0, node.children.length, toBBox, node);
|
|
36048
|
+
}
|
|
36049
|
+
|
|
36050
|
+
// min bounding rectangle of node children from k to p-1
|
|
36051
|
+
function distBBox(node, k, p, toBBox, destNode) {
|
|
36052
|
+
if (!destNode) destNode = createNode(null);
|
|
36053
|
+
destNode.minX = Infinity;
|
|
36054
|
+
destNode.minY = Infinity;
|
|
36055
|
+
destNode.maxX = -Infinity;
|
|
36056
|
+
destNode.maxY = -Infinity;
|
|
36057
|
+
|
|
36058
|
+
for (let i = k; i < p; i++) {
|
|
36059
|
+
const child = node.children[i];
|
|
36060
|
+
extend(destNode, node.leaf ? toBBox(child) : child);
|
|
36061
|
+
}
|
|
36062
|
+
|
|
36063
|
+
return destNode;
|
|
36064
|
+
}
|
|
36065
|
+
|
|
36066
|
+
function extend(a, b) {
|
|
36067
|
+
a.minX = Math.min(a.minX, b.minX);
|
|
36068
|
+
a.minY = Math.min(a.minY, b.minY);
|
|
36069
|
+
a.maxX = Math.max(a.maxX, b.maxX);
|
|
36070
|
+
a.maxY = Math.max(a.maxY, b.maxY);
|
|
36071
|
+
return a;
|
|
36072
|
+
}
|
|
36073
|
+
|
|
36074
|
+
function compareNodeMinX(a, b) { return a.minX - b.minX; }
|
|
36075
|
+
function compareNodeMinY(a, b) { return a.minY - b.minY; }
|
|
36076
|
+
|
|
36077
|
+
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
|
|
36078
|
+
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
|
|
36079
|
+
|
|
36080
|
+
function enlargedArea(a, b) {
|
|
36081
|
+
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
|
|
36082
|
+
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
|
|
36083
|
+
}
|
|
36084
|
+
|
|
36085
|
+
function intersectionArea(a, b) {
|
|
36086
|
+
const minX = Math.max(a.minX, b.minX);
|
|
36087
|
+
const minY = Math.max(a.minY, b.minY);
|
|
36088
|
+
const maxX = Math.min(a.maxX, b.maxX);
|
|
36089
|
+
const maxY = Math.min(a.maxY, b.maxY);
|
|
36090
|
+
|
|
36091
|
+
return Math.max(0, maxX - minX) *
|
|
36092
|
+
Math.max(0, maxY - minY);
|
|
36093
|
+
}
|
|
36094
|
+
|
|
36095
|
+
function contains(a, b) {
|
|
36096
|
+
return a.minX <= b.minX &&
|
|
36097
|
+
a.minY <= b.minY &&
|
|
36098
|
+
b.maxX <= a.maxX &&
|
|
36099
|
+
b.maxY <= a.maxY;
|
|
36100
|
+
}
|
|
36101
|
+
|
|
36102
|
+
function intersects(a, b) {
|
|
36103
|
+
return b.minX <= a.maxX &&
|
|
36104
|
+
b.minY <= a.maxY &&
|
|
36105
|
+
b.maxX >= a.minX &&
|
|
36106
|
+
b.maxY >= a.minY;
|
|
36107
|
+
}
|
|
36108
|
+
|
|
36109
|
+
function createNode(children) {
|
|
36110
|
+
return {
|
|
36111
|
+
children,
|
|
36112
|
+
height: 1,
|
|
36113
|
+
leaf: true,
|
|
36114
|
+
minX: Infinity,
|
|
36115
|
+
minY: Infinity,
|
|
36116
|
+
maxX: -Infinity,
|
|
36117
|
+
maxY: -Infinity
|
|
36118
|
+
};
|
|
36119
|
+
}
|
|
36120
|
+
|
|
36121
|
+
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
|
|
36122
|
+
// combines selection algorithm with binary divide & conquer approach
|
|
36123
|
+
|
|
36124
|
+
function multiSelect(arr, left, right, n, compare) {
|
|
36125
|
+
const stack = [left, right];
|
|
36126
|
+
|
|
36127
|
+
while (stack.length) {
|
|
36128
|
+
right = stack.pop();
|
|
36129
|
+
left = stack.pop();
|
|
36130
|
+
|
|
36131
|
+
if (right - left <= n) continue;
|
|
36132
|
+
|
|
36133
|
+
const mid = left + Math.ceil((right - left) / n / 2) * n;
|
|
36134
|
+
quickselect(arr, mid, left, right, compare);
|
|
36135
|
+
|
|
36136
|
+
stack.push(left, mid, mid, right);
|
|
36137
|
+
}
|
|
36138
|
+
}
|
|
36139
|
+
|
|
36140
|
+
/**
|
|
36141
|
+
* R-Tree Data Structure
|
|
36142
|
+
*
|
|
36143
|
+
* R-Tree is a spatial data structure used for efficient indexing and querying
|
|
36144
|
+
* of multi-dimensional objects, particularly in geometric and spatial applications.
|
|
36145
|
+
*
|
|
36146
|
+
* It organizes objects into a tree hierarchy, grouping nearby objects together
|
|
36147
|
+
* in bounding boxes. Each node in the tree represents a bounding box that
|
|
36148
|
+
* contains its child nodes or leaf objects. This hierarchical structure allows
|
|
36149
|
+
* for faster spatial queries.
|
|
36150
|
+
*
|
|
36151
|
+
* @see https://en.wikipedia.org/wiki/R-tree
|
|
36152
|
+
*
|
|
36153
|
+
* Consider a 2D Space with four zones: A, B, C, D
|
|
36154
|
+
* +--------------------------+
|
|
36155
|
+
* | |
|
|
36156
|
+
* | +---+ +-------+ |
|
|
36157
|
+
* | | A | | B | |
|
|
36158
|
+
* | +---+ +-------+ |
|
|
36159
|
+
* | |
|
|
36160
|
+
* | |
|
|
36161
|
+
* | +---+ |
|
|
36162
|
+
* | | C | |
|
|
36163
|
+
* | +---+ |
|
|
36164
|
+
* | +-----------+ |
|
|
36165
|
+
* | | D | |
|
|
36166
|
+
* | +-----------+ |
|
|
36167
|
+
* | |
|
|
36168
|
+
* +--------------------------+
|
|
36169
|
+
*
|
|
36170
|
+
* It groups together zones that are spatially close into a minimum bounding box.
|
|
36171
|
+
* For example, A and B are grouped together in rectangle R1, and C and D are grouped
|
|
36172
|
+
* in R2.
|
|
36173
|
+
*
|
|
36174
|
+
* R0
|
|
36175
|
+
* +--------------------------+
|
|
36176
|
+
* | R1 |
|
|
36177
|
+
* | +-----------------+ |
|
|
36178
|
+
* | | A | | B | |
|
|
36179
|
+
* | +-----------------+ |
|
|
36180
|
+
* | |
|
|
36181
|
+
* | R2 |
|
|
36182
|
+
* | +---+---+---+ |
|
|
36183
|
+
* | | | C | | |
|
|
36184
|
+
* | | +---+ | |
|
|
36185
|
+
* | +-----------+ |
|
|
36186
|
+
* | | D | |
|
|
36187
|
+
* | +-----------+ |
|
|
36188
|
+
* | |
|
|
36189
|
+
* +--------------------------+
|
|
36190
|
+
*
|
|
36191
|
+
* The tree would look like this:
|
|
36192
|
+
* R0
|
|
36193
|
+
* / \
|
|
36194
|
+
* / \
|
|
36195
|
+
* R1 R2
|
|
36196
|
+
* | |
|
|
36197
|
+
* A,B C,D
|
|
36198
|
+
|
|
36199
|
+
* Choosing how to group the zones is crucial for the performance of the tree.
|
|
36200
|
+
* Key considerations include avoiding excessive empty space coverage and minimizing overlap
|
|
36201
|
+
* to reduce the number of subtrees processed during searches.
|
|
36202
|
+
*
|
|
36203
|
+
* Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
|
|
36204
|
+
* which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
|
|
36205
|
+
* the choice cannot be made based on this criterion due to the same enlargement for different groupings,
|
|
36206
|
+
* we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
|
|
36207
|
+
*
|
|
36208
|
+
* This implementation is tailored for spreadsheet use, indexing objects associated
|
|
36209
|
+
* with a zone and a sheet.
|
|
36210
|
+
*
|
|
36211
|
+
* It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
|
|
36212
|
+
* @see https://github.com/mourner/rbush
|
|
36213
|
+
*/
|
|
36214
|
+
class SpreadsheetRTree {
|
|
36215
|
+
/**
|
|
36216
|
+
* One 2D R-tree per sheet
|
|
36217
|
+
*/
|
|
36218
|
+
rTrees = {};
|
|
36219
|
+
/**
|
|
36220
|
+
* Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
|
|
36221
|
+
* faster than inserting items one by one. After bulk loading (bulk insertion into
|
|
36222
|
+
* an empty tree), subsequent query performance is also ~20-30% better.
|
|
36223
|
+
*/
|
|
36224
|
+
constructor(items = []) {
|
|
36225
|
+
const rangesPerSheet = {};
|
|
36226
|
+
for (const item of items) {
|
|
36227
|
+
const sheetId = item.boundingBox.sheetId;
|
|
36228
|
+
if (!rangesPerSheet[sheetId]) {
|
|
36229
|
+
rangesPerSheet[sheetId] = [];
|
|
36230
|
+
}
|
|
36231
|
+
rangesPerSheet[sheetId].push(item);
|
|
36232
|
+
}
|
|
36233
|
+
for (const sheetId in rangesPerSheet) {
|
|
36234
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
36235
|
+
this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
|
|
36236
|
+
}
|
|
36237
|
+
}
|
|
36238
|
+
insert(item) {
|
|
36239
|
+
const sheetId = item.boundingBox.sheetId;
|
|
36240
|
+
if (!this.rTrees[sheetId]) {
|
|
36241
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
36242
|
+
}
|
|
36243
|
+
this.rTrees[sheetId].insert(item);
|
|
36244
|
+
}
|
|
36245
|
+
search({ zone, sheetId }) {
|
|
36246
|
+
if (!this.rTrees[sheetId]) {
|
|
36247
|
+
return [];
|
|
36248
|
+
}
|
|
36249
|
+
return this.rTrees[sheetId].search({
|
|
36250
|
+
minX: zone.left,
|
|
36251
|
+
minY: zone.top,
|
|
36252
|
+
maxX: zone.right,
|
|
36253
|
+
maxY: zone.bottom,
|
|
36254
|
+
});
|
|
36255
|
+
}
|
|
36256
|
+
remove(item) {
|
|
36257
|
+
const sheetId = item.boundingBox.sheetId;
|
|
36258
|
+
if (!this.rTrees[sheetId]) {
|
|
36259
|
+
return;
|
|
36260
|
+
}
|
|
36261
|
+
this.rTrees[sheetId].remove(item, deepEquals);
|
|
36262
|
+
}
|
|
36263
|
+
}
|
|
36264
|
+
/**
|
|
36265
|
+
* RBush extension to use zones as bounding boxes
|
|
36266
|
+
*/
|
|
36267
|
+
class ZoneRBush extends RBush {
|
|
36268
|
+
toBBox({ boundingBox }) {
|
|
36269
|
+
const zone = boundingBox.zone;
|
|
36270
|
+
return {
|
|
36271
|
+
minX: zone.left,
|
|
36272
|
+
minY: zone.top,
|
|
36273
|
+
maxX: zone.right,
|
|
36274
|
+
maxY: zone.bottom,
|
|
36275
|
+
};
|
|
36276
|
+
}
|
|
36277
|
+
compareMinX(a, b) {
|
|
36278
|
+
return a.boundingBox.zone.left - b.boundingBox.zone.left;
|
|
36279
|
+
}
|
|
36280
|
+
compareMinY(a, b) {
|
|
36281
|
+
return a.boundingBox.zone.top - b.boundingBox.zone.top;
|
|
36282
|
+
}
|
|
36283
|
+
}
|
|
36284
|
+
|
|
35565
36285
|
/**
|
|
35566
|
-
*
|
|
36286
|
+
* Implementation of a dependency Graph.
|
|
35567
36287
|
* The graph is used to evaluate the cells in the correct
|
|
35568
36288
|
* order, and should be updated each time a cell's content is modified
|
|
35569
36289
|
*
|
|
36290
|
+
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
35570
36291
|
*/
|
|
35571
36292
|
class FormulaDependencyGraph {
|
|
35572
|
-
|
|
35573
|
-
* Internal structure:
|
|
35574
|
-
* - key: a cell position (encoded as an integer)
|
|
35575
|
-
* - value: a set of cell positions that depends on the key
|
|
35576
|
-
*
|
|
35577
|
-
* Given
|
|
35578
|
-
* - A1:"= B1 + SQRT(B2)"
|
|
35579
|
-
* - C1:"= B1";
|
|
35580
|
-
* - C2:"= C1"
|
|
35581
|
-
*
|
|
35582
|
-
* we will have something like:
|
|
35583
|
-
* - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
|
|
35584
|
-
* - B2 ---> (A1) meaning A1 depends on B2
|
|
35585
|
-
* - C1 ---> (C2) meaning C2 depends on C1
|
|
35586
|
-
*/
|
|
35587
|
-
inverseDependencies = new Map();
|
|
36293
|
+
encoder;
|
|
35588
36294
|
dependencies = new Map();
|
|
36295
|
+
rTree;
|
|
36296
|
+
constructor(encoder, data = []) {
|
|
36297
|
+
this.encoder = encoder;
|
|
36298
|
+
this.rTree = new SpreadsheetRTree(data);
|
|
36299
|
+
}
|
|
35589
36300
|
removeAllDependencies(formulaPositionId) {
|
|
35590
|
-
const
|
|
35591
|
-
if (!
|
|
36301
|
+
const ranges = this.dependencies.get(formulaPositionId);
|
|
36302
|
+
if (!ranges) {
|
|
35592
36303
|
return;
|
|
35593
36304
|
}
|
|
35594
|
-
for (const
|
|
35595
|
-
this.
|
|
36305
|
+
for (const range of ranges) {
|
|
36306
|
+
this.rTree.remove(range);
|
|
35596
36307
|
}
|
|
35597
36308
|
this.dependencies.delete(formulaPositionId);
|
|
35598
36309
|
}
|
|
35599
36310
|
addDependencies(formulaPositionId, dependencies) {
|
|
35600
|
-
|
|
35601
|
-
|
|
35602
|
-
|
|
35603
|
-
|
|
35604
|
-
|
|
35605
|
-
|
|
35606
|
-
|
|
35607
|
-
|
|
36311
|
+
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
36312
|
+
data: formulaPositionId,
|
|
36313
|
+
boundingBox: {
|
|
36314
|
+
zone,
|
|
36315
|
+
sheetId,
|
|
36316
|
+
},
|
|
36317
|
+
}));
|
|
36318
|
+
for (const item of rTreeItems) {
|
|
36319
|
+
this.rTree.insert(item);
|
|
35608
36320
|
}
|
|
35609
36321
|
const existingDependencies = this.dependencies.get(formulaPositionId);
|
|
35610
36322
|
if (existingDependencies) {
|
|
35611
|
-
existingDependencies.push(...
|
|
36323
|
+
existingDependencies.push(...rTreeItems);
|
|
35612
36324
|
}
|
|
35613
36325
|
else {
|
|
35614
|
-
this.dependencies.set(formulaPositionId,
|
|
36326
|
+
this.dependencies.set(formulaPositionId, rTreeItems);
|
|
35615
36327
|
}
|
|
35616
36328
|
}
|
|
35617
36329
|
/**
|
|
@@ -35619,20 +36331,20 @@ class FormulaDependencyGraph {
|
|
|
35619
36331
|
* in the correct order they should be evaluated.
|
|
35620
36332
|
* This is called a topological ordering (excluding cycles)
|
|
35621
36333
|
*/
|
|
35622
|
-
getCellsDependingOn(
|
|
36334
|
+
getCellsDependingOn(ranges) {
|
|
35623
36335
|
const visited = new JetSet();
|
|
35624
|
-
const queue = Array.from(
|
|
36336
|
+
const queue = Array.from(ranges).reverse();
|
|
35625
36337
|
while (queue.length > 0) {
|
|
35626
|
-
const
|
|
35627
|
-
visited.add(
|
|
35628
|
-
const
|
|
35629
|
-
for (const
|
|
35630
|
-
if (!visited.has(
|
|
35631
|
-
queue.push(
|
|
36338
|
+
const range = queue.pop();
|
|
36339
|
+
visited.add(...this.encoder.encodeBoundingBox(range));
|
|
36340
|
+
const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
|
|
36341
|
+
for (const positionId of impactedPositionIds) {
|
|
36342
|
+
if (!visited.has(positionId)) {
|
|
36343
|
+
queue.push(this.encoder.decodeToBoundingBox(positionId));
|
|
35632
36344
|
}
|
|
35633
36345
|
}
|
|
35634
36346
|
}
|
|
35635
|
-
visited.delete(...
|
|
36347
|
+
visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
|
|
35636
36348
|
return visited;
|
|
35637
36349
|
}
|
|
35638
36350
|
}
|
|
@@ -35720,9 +36432,9 @@ class Evaluator {
|
|
|
35720
36432
|
context;
|
|
35721
36433
|
getters;
|
|
35722
36434
|
compilationParams;
|
|
35723
|
-
|
|
36435
|
+
encoder = new PositionBitsEncoder();
|
|
35724
36436
|
evaluatedCells = new Map();
|
|
35725
|
-
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
36437
|
+
formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
|
|
35726
36438
|
blockedArrayFormulas = new Set();
|
|
35727
36439
|
spreadingRelations = new SpreadingRelation();
|
|
35728
36440
|
constructor(context, getters) {
|
|
@@ -35731,16 +36443,16 @@ class Evaluator {
|
|
|
35731
36443
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
35732
36444
|
}
|
|
35733
36445
|
getEvaluatedCell(position) {
|
|
35734
|
-
return (this.evaluatedCells.get(this.
|
|
36446
|
+
return (this.evaluatedCells.get(this.encoder.encode(position)) ||
|
|
35735
36447
|
createEvaluatedCell("", { locale: this.getters.getLocale() }));
|
|
35736
36448
|
}
|
|
35737
36449
|
getArrayFormulaSpreadingOn(position) {
|
|
35738
|
-
const positionId = this.
|
|
36450
|
+
const positionId = this.encoder.encode(position);
|
|
35739
36451
|
const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
|
|
35740
|
-
return formulaPosition !== undefined ? this.
|
|
36452
|
+
return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
|
|
35741
36453
|
}
|
|
35742
36454
|
getEvaluatedPositions() {
|
|
35743
|
-
return [...this.evaluatedCells.keys()].map(this.
|
|
36455
|
+
return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
|
|
35744
36456
|
}
|
|
35745
36457
|
getArrayFormulaSpreadingOnId(positionId) {
|
|
35746
36458
|
if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
|
|
@@ -35750,7 +36462,7 @@ class Evaluator {
|
|
|
35750
36462
|
return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
|
|
35751
36463
|
}
|
|
35752
36464
|
updateDependencies(position) {
|
|
35753
|
-
const positionId = this.
|
|
36465
|
+
const positionId = this.encoder.encode(position);
|
|
35754
36466
|
this.formulaDependencies().removeAllDependencies(positionId);
|
|
35755
36467
|
const dependencies = this.getDirectDependencies(positionId);
|
|
35756
36468
|
this.formulaDependencies().addDependencies(positionId, dependencies);
|
|
@@ -35760,12 +36472,12 @@ class Evaluator {
|
|
|
35760
36472
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
35761
36473
|
}
|
|
35762
36474
|
evaluateCells(positions) {
|
|
35763
|
-
const cells = positions.map(this.
|
|
36475
|
+
const cells = positions.map((p) => this.encoder.encode(p));
|
|
35764
36476
|
const cellsToCompute = new JetSet(cells);
|
|
35765
|
-
const
|
|
36477
|
+
const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
|
|
35766
36478
|
cellsToCompute.add(...this.getCellsDependingOn(cells));
|
|
35767
|
-
cellsToCompute.add(...
|
|
35768
|
-
cellsToCompute.add(...this.getCellsDependingOn(
|
|
36479
|
+
cellsToCompute.add(...arrayFormulasPositionIds);
|
|
36480
|
+
cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
|
|
35769
36481
|
this.evaluate(cellsToCompute);
|
|
35770
36482
|
}
|
|
35771
36483
|
getArrayFormulasImpactedByChangesOf(positionIds) {
|
|
@@ -35788,12 +36500,14 @@ class Evaluator {
|
|
|
35788
36500
|
this.blockedArrayFormulas = new Set();
|
|
35789
36501
|
this.spreadingRelations = new SpreadingRelation();
|
|
35790
36502
|
this.formulaDependencies = lazy(() => {
|
|
35791
|
-
const
|
|
35792
|
-
|
|
35793
|
-
|
|
35794
|
-
|
|
35795
|
-
|
|
35796
|
-
|
|
36503
|
+
const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
|
|
36504
|
+
data: positionId,
|
|
36505
|
+
boundingBox: {
|
|
36506
|
+
zone: range.zone,
|
|
36507
|
+
sheetId: range.sheetId,
|
|
36508
|
+
},
|
|
36509
|
+
})));
|
|
36510
|
+
return new FormulaDependencyGraph(this.encoder, dependencies);
|
|
35797
36511
|
});
|
|
35798
36512
|
}
|
|
35799
36513
|
evaluateAllCells() {
|
|
@@ -35814,7 +36528,7 @@ class Evaluator {
|
|
|
35814
36528
|
for (const sheetId of this.getters.getSheetIds()) {
|
|
35815
36529
|
const cellIds = this.getters.getCells(sheetId);
|
|
35816
36530
|
for (const cellId in cellIds) {
|
|
35817
|
-
positionIds.add(this.
|
|
36531
|
+
positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
|
|
35818
36532
|
}
|
|
35819
36533
|
}
|
|
35820
36534
|
return positionIds;
|
|
@@ -35881,7 +36595,7 @@ class Evaluator {
|
|
|
35881
36595
|
}
|
|
35882
36596
|
}
|
|
35883
36597
|
computeAndSave(position) {
|
|
35884
|
-
const positionId = this.
|
|
36598
|
+
const positionId = this.encoder.encode(position);
|
|
35885
36599
|
const evaluatedCell = this.computeCell(positionId);
|
|
35886
36600
|
if (!this.evaluatedCells.has(positionId)) {
|
|
35887
36601
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
@@ -35942,15 +36656,15 @@ class Evaluator {
|
|
|
35942
36656
|
throw new Error(_lt("Result couldn't be automatically expanded. Please insert more columns and rows."));
|
|
35943
36657
|
}
|
|
35944
36658
|
updateSpreadRelation({ sheetId, col, row, }) {
|
|
35945
|
-
const arrayFormulaPositionId = this.
|
|
36659
|
+
const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
35946
36660
|
return (i, j) => {
|
|
35947
36661
|
const position = { sheetId, col: i + col, row: j + row };
|
|
35948
|
-
const resultPositionId = this.
|
|
36662
|
+
const resultPositionId = this.encoder.encode(position);
|
|
35949
36663
|
this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
|
|
35950
36664
|
};
|
|
35951
36665
|
}
|
|
35952
36666
|
checkCollision({ sheetId, col, row }) {
|
|
35953
|
-
const formulaPositionId = this.
|
|
36667
|
+
const formulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
35954
36668
|
return (i, j) => {
|
|
35955
36669
|
const position = { sheetId: sheetId, col: i + col, row: j + row };
|
|
35956
36670
|
const rawCell = this.getters.getCell(position);
|
|
@@ -35972,7 +36686,7 @@ class Evaluator {
|
|
|
35972
36686
|
format: format || formatFromPosition(i, j),
|
|
35973
36687
|
locale: this.getters.getLocale(),
|
|
35974
36688
|
});
|
|
35975
|
-
const positionId = this.
|
|
36689
|
+
const positionId = this.encoder.encode(position);
|
|
35976
36690
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
35977
36691
|
// check if formula dependencies present in the spread zone
|
|
35978
36692
|
// if so, they need to be recomputed
|
|
@@ -36004,29 +36718,17 @@ class Evaluator {
|
|
|
36004
36718
|
if (!cell?.isFormula) {
|
|
36005
36719
|
return [];
|
|
36006
36720
|
}
|
|
36007
|
-
|
|
36008
|
-
for (const range of cell.dependencies) {
|
|
36009
|
-
if (range.invalidSheetName || range.invalidXc) {
|
|
36010
|
-
continue;
|
|
36011
|
-
}
|
|
36012
|
-
const sheetId = range.sheetId;
|
|
36013
|
-
forEachPositionsInZone(range.zone, (col, row) => {
|
|
36014
|
-
dependencies.push(this.encodePosition({ sheetId, col, row }));
|
|
36015
|
-
});
|
|
36016
|
-
}
|
|
36017
|
-
return dependencies;
|
|
36721
|
+
return cell.dependencies;
|
|
36018
36722
|
}
|
|
36019
36723
|
getCellsDependingOn(positionIds) {
|
|
36020
|
-
|
|
36724
|
+
const ranges = [];
|
|
36725
|
+
for (const positionId of positionIds) {
|
|
36726
|
+
ranges.push(this.encoder.decodeToBoundingBox(positionId));
|
|
36727
|
+
}
|
|
36728
|
+
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
36021
36729
|
}
|
|
36022
36730
|
getCell(positionId) {
|
|
36023
|
-
return this.getters.getCell(this.
|
|
36024
|
-
}
|
|
36025
|
-
encodePosition(position) {
|
|
36026
|
-
return this.positionEncoder.encode(position);
|
|
36027
|
-
}
|
|
36028
|
-
decodePosition(positionId) {
|
|
36029
|
-
return this.positionEncoder.decode(positionId);
|
|
36731
|
+
return this.getters.getCell(this.encoder.decode(positionId));
|
|
36030
36732
|
}
|
|
36031
36733
|
}
|
|
36032
36734
|
function forEachSpreadPositionInMatrix(matrix, callback) {
|
|
@@ -36101,6 +36803,13 @@ class PositionBitsEncoder {
|
|
|
36101
36803
|
encode({ sheetId, col, row }) {
|
|
36102
36804
|
return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
|
|
36103
36805
|
}
|
|
36806
|
+
encodeBoundingBox({ sheetId, zone }) {
|
|
36807
|
+
const positions = [];
|
|
36808
|
+
forEachPositionsInZone(zone, (col, row) => {
|
|
36809
|
+
positions.push(this.encode({ sheetId, col, row }));
|
|
36810
|
+
});
|
|
36811
|
+
return positions;
|
|
36812
|
+
}
|
|
36104
36813
|
decode(id) {
|
|
36105
36814
|
// keep only the last 21 bits by AND-ing the bit sequence with 21 ones
|
|
36106
36815
|
const row = Number(id & 2097151n);
|
|
@@ -36108,6 +36817,10 @@ class PositionBitsEncoder {
|
|
|
36108
36817
|
const sheetId = this.decodeSheet(id >> 42n);
|
|
36109
36818
|
return { sheetId, col, row };
|
|
36110
36819
|
}
|
|
36820
|
+
decodeToBoundingBox(id) {
|
|
36821
|
+
const { sheetId, col, row } = this.decode(id);
|
|
36822
|
+
return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
|
|
36823
|
+
}
|
|
36111
36824
|
encodeSheet(sheetId) {
|
|
36112
36825
|
const sheetKey = this.sheetMapping[sheetId];
|
|
36113
36826
|
if (sheetKey === undefined) {
|
|
@@ -36222,6 +36935,7 @@ class PositionBitsEncoder {
|
|
|
36222
36935
|
class EvaluationPlugin extends UIPlugin {
|
|
36223
36936
|
static getters = [
|
|
36224
36937
|
"evaluateFormula",
|
|
36938
|
+
"getCorrespondingFormulaCell",
|
|
36225
36939
|
"getRangeFormattedValues",
|
|
36226
36940
|
"getRangeValues",
|
|
36227
36941
|
"getRangeFormats",
|
|
@@ -36275,7 +36989,12 @@ class EvaluationPlugin extends UIPlugin {
|
|
|
36275
36989
|
// Getters
|
|
36276
36990
|
// ---------------------------------------------------------------------------
|
|
36277
36991
|
evaluateFormula(sheetId, formulaString) {
|
|
36278
|
-
|
|
36992
|
+
try {
|
|
36993
|
+
return this.evaluator.evaluateFormula(sheetId, formulaString);
|
|
36994
|
+
}
|
|
36995
|
+
catch (error) {
|
|
36996
|
+
return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
|
|
36997
|
+
}
|
|
36279
36998
|
}
|
|
36280
36999
|
/**
|
|
36281
37000
|
* Return the value of each cell in the range as they are displayed in the grid.
|
|
@@ -36665,34 +37384,29 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
|
|
|
36665
37384
|
getComputedStyles(sheetId) {
|
|
36666
37385
|
const computedStyle = {};
|
|
36667
37386
|
for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
|
|
36668
|
-
|
|
36669
|
-
|
|
36670
|
-
|
|
36671
|
-
|
|
36672
|
-
|
|
36673
|
-
|
|
36674
|
-
|
|
36675
|
-
|
|
36676
|
-
|
|
36677
|
-
|
|
36678
|
-
for (let
|
|
36679
|
-
|
|
36680
|
-
|
|
36681
|
-
|
|
36682
|
-
if (
|
|
36683
|
-
|
|
36684
|
-
|
|
36685
|
-
|
|
36686
|
-
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
36687
|
-
}
|
|
37387
|
+
switch (cf.rule.type) {
|
|
37388
|
+
case "ColorScaleRule":
|
|
37389
|
+
for (let range of cf.ranges) {
|
|
37390
|
+
this.applyColorScale(sheetId, range, cf.rule, computedStyle);
|
|
37391
|
+
}
|
|
37392
|
+
break;
|
|
37393
|
+
case "CellIsRule":
|
|
37394
|
+
for (let ref of cf.ranges) {
|
|
37395
|
+
const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
|
|
37396
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
37397
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
37398
|
+
const pr = this.rulePredicate[cf.rule.type];
|
|
37399
|
+
let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
|
|
37400
|
+
if (pr && pr(cell, cf.rule)) {
|
|
37401
|
+
if (!computedStyle[col])
|
|
37402
|
+
computedStyle[col] = [];
|
|
37403
|
+
// we must combine all the properties of all the CF rules applied to the given cell
|
|
37404
|
+
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
36688
37405
|
}
|
|
36689
37406
|
}
|
|
36690
37407
|
}
|
|
36691
|
-
|
|
36692
|
-
|
|
36693
|
-
}
|
|
36694
|
-
catch (_) {
|
|
36695
|
-
// we don't care about the errors within the evaluation of a rule
|
|
37408
|
+
}
|
|
37409
|
+
break;
|
|
36696
37410
|
}
|
|
36697
37411
|
}
|
|
36698
37412
|
return computedStyle;
|
|
@@ -36728,7 +37442,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
|
|
|
36728
37442
|
return percentile(rangeValues, Number(threshold.value) / 100, true);
|
|
36729
37443
|
case "formula":
|
|
36730
37444
|
const value = threshold.value && this.getters.evaluateFormula(sheetId, threshold.value);
|
|
36731
|
-
return
|
|
37445
|
+
return typeof value === "number" ? value : null;
|
|
36732
37446
|
default:
|
|
36733
37447
|
return null;
|
|
36734
37448
|
}
|
|
@@ -37295,7 +38009,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
37295
38009
|
let row = zone.bottom;
|
|
37296
38010
|
if (col > 0) {
|
|
37297
38011
|
let leftPosition = { sheetId, col: col - 1, row };
|
|
37298
|
-
while (this.getters.
|
|
38012
|
+
while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
|
|
37299
38013
|
this.getters.getCell(leftPosition)?.content) {
|
|
37300
38014
|
row += 1;
|
|
37301
38015
|
leftPosition = { sheetId, col: col - 1, row };
|
|
@@ -37305,7 +38019,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
37305
38019
|
col = zone.right;
|
|
37306
38020
|
if (col <= this.getters.getNumberCols(sheetId)) {
|
|
37307
38021
|
let rightPosition = { sheetId, col: col + 1, row };
|
|
37308
|
-
while (this.getters.
|
|
38022
|
+
while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
|
|
37309
38023
|
this.getters.getCell(rightPosition)?.content) {
|
|
37310
38024
|
row += 1;
|
|
37311
38025
|
rightPosition = { sheetId, col: col + 1, row };
|
|
@@ -47053,7 +47767,7 @@ class Spreadsheet extends owl.Component {
|
|
|
47053
47767
|
}
|
|
47054
47768
|
onKeydown(ev) {
|
|
47055
47769
|
let keyDownString = "";
|
|
47056
|
-
if (ev
|
|
47770
|
+
if (isCtrlKey(ev)) {
|
|
47057
47771
|
keyDownString += "CTRL+";
|
|
47058
47772
|
}
|
|
47059
47773
|
keyDownString += ev.key.toUpperCase();
|
|
@@ -50812,6 +51526,6 @@ exports.setTranslationMethod = setTranslationMethod;
|
|
|
50812
51526
|
exports.tokenize = tokenize;
|
|
50813
51527
|
|
|
50814
51528
|
|
|
50815
|
-
__info__.version = '16.4.
|
|
50816
|
-
__info__.date = '
|
|
50817
|
-
__info__.hash = '
|
|
51529
|
+
__info__.version = '16.4.17';
|
|
51530
|
+
__info__.date = '2024-01-04T12:22:53.125Z';
|
|
51531
|
+
__info__.hash = '769f8a3';
|