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