@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.
@@ -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.toUpperCase().indexOf("MAC") >= 0;
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.ctrlKey ? "end" : 1);
20502
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
20494
20503
  }
20495
20504
  else {
20496
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
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, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });
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.ctrlKey);
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.ctrlKey ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
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, ctrlKey) {
25935
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
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, ctrlKey) {
26109
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
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, { ctrlKey, shiftKey }) {
26916
- if (ctrlKey) {
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 (shiftKey) {
26937
+ if (expandZone) {
26926
26938
  this.env.model.selection.setAnchorCorner(col, row);
26927
26939
  }
26928
- else if (ctrlKey) {
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.ctrlKey)
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
- * This class is an implementation of a dependency Graph.
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 dependencies = this.dependencies.get(formulaPositionId);
35587
- if (!dependencies) {
36297
+ const ranges = this.dependencies.get(formulaPositionId);
36298
+ if (!ranges) {
35588
36299
  return;
35589
36300
  }
35590
- for (const dependency of dependencies) {
35591
- this.inverseDependencies.get(dependency)?.delete(formulaPositionId);
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
- for (const dependency of dependencies) {
35597
- const inverseDependencies = this.inverseDependencies.get(dependency);
35598
- if (inverseDependencies) {
35599
- inverseDependencies.add(formulaPositionId);
35600
- }
35601
- else {
35602
- this.inverseDependencies.set(dependency, new Set([formulaPositionId]));
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(...dependencies);
36319
+ existingDependencies.push(...rTreeItems);
35608
36320
  }
35609
36321
  else {
35610
- this.dependencies.set(formulaPositionId, dependencies);
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(positionIds) {
36330
+ getCellsDependingOn(ranges) {
35619
36331
  const visited = new JetSet();
35620
- const queue = Array.from(positionIds).reverse();
36332
+ const queue = Array.from(ranges).reverse();
35621
36333
  while (queue.length > 0) {
35622
- const node = queue.pop();
35623
- visited.add(node);
35624
- const adjacentNodes = this.inverseDependencies.get(node) || new Set();
35625
- for (const adjacentNode of adjacentNodes) {
35626
- if (!visited.has(adjacentNode)) {
35627
- queue.push(adjacentNode);
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(...positionIds);
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
- positionEncoder = new PositionBitsEncoder();
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.encodePosition(position)) ||
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.encodePosition(position);
36446
+ const positionId = this.encoder.encode(position);
35735
36447
  const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
35736
- return formulaPosition !== undefined ? this.decodePosition(formulaPosition) : undefined;
36448
+ return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
35737
36449
  }
35738
36450
  getEvaluatedPositions() {
35739
- return [...this.evaluatedCells.keys()].map(this.decodePosition.bind(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.encodePosition(position);
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.encodePosition.bind(this));
36471
+ const cells = positions.map((p) => this.encoder.encode(p));
35760
36472
  const cellsToCompute = new JetSet(cells);
35761
- const arrayFormulas = this.getArrayFormulasImpactedByChangesOf(cells);
36473
+ const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
35762
36474
  cellsToCompute.add(...this.getCellsDependingOn(cells));
35763
- cellsToCompute.add(...arrayFormulas);
35764
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulas));
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 dependencyGraph = new FormulaDependencyGraph();
35788
- for (const positionId of this.getAllCells()) {
35789
- const dependencies = this.getDirectDependencies(positionId);
35790
- dependencyGraph.addDependencies(positionId, dependencies);
35791
- }
35792
- return dependencyGraph;
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.encodePosition(this.getters.getCellPosition(cellId)));
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.encodePosition(position);
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.encodePosition({ sheetId, col, row });
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.encodePosition(position);
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.encodePosition({ sheetId, col, row });
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.encodePosition(position);
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
- const dependencies = [];
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
- return this.formulaDependencies().getCellsDependingOn(positionIds);
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.decodePosition(positionId));
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
- return this.evaluator.evaluateFormula(sheetId, formulaString);
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
- try {
36665
- switch (cf.rule.type) {
36666
- case "ColorScaleRule":
36667
- for (let range of cf.ranges) {
36668
- this.applyColorScale(sheetId, range, cf.rule, computedStyle);
36669
- }
36670
- break;
36671
- case "CellIsRule":
36672
- for (let ref of cf.ranges) {
36673
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
36674
- for (let row = zone.top; row <= zone.bottom; row++) {
36675
- for (let col = zone.left; col <= zone.right; col++) {
36676
- const pr = this.rulePredicate[cf.rule.type];
36677
- let cell = this.getters.getEvaluatedCell({ sheetId, col, row });
36678
- if (pr && pr(cell, cf.rule)) {
36679
- if (!computedStyle[col])
36680
- computedStyle[col] = [];
36681
- // we must combine all the properties of all the CF rules applied to the given cell
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
- break;
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 !(value instanceof Promise) ? value : null;
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.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
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.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
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.ctrlKey || ev.metaKey) {
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.16';
50780
- __info__.date = '2023-12-05T09:37:14.210Z';
50781
- __info__.hash = '1abbb92';
51493
+ __info__.version = '16.4.17';
51494
+ __info__.date = '2024-01-04T12:22:53.125Z';
51495
+ __info__.hash = '769f8a3';