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