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