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