@odoo/o-spreadsheet 17.0.5 → 17.0.6

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.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 17.0.5
6
- * @date 2023-12-05T09:51:44.657Z
7
- * @hash 2480a5d
5
+ * @version 17.0.6
6
+ * @date 2024-01-04T12:23:10.357Z
7
+ * @hash 310ca57
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -330,13 +330,6 @@
330
330
  //------------------------------------------------------------------------------
331
331
  // Miscellaneous
332
332
  //------------------------------------------------------------------------------
333
- /**
334
- * Stringify an object, like JSON.stringify, except that the first level of keys
335
- * is ordered.
336
- */
337
- function stringify(obj) {
338
- return JSON.stringify(obj, Object.keys(obj).sort());
339
- }
340
333
  /**
341
334
  * Remove quotes from a quoted string
342
335
  * ```js
@@ -559,7 +552,7 @@
559
552
  */
560
553
  function getItemId(item, itemsDic) {
561
554
  for (let [key, value] of Object.entries(itemsDic)) {
562
- if (stringify(value) === stringify(item)) {
555
+ if (deepEquals(value, item)) {
563
556
  return parseInt(key, 10);
564
557
  }
565
558
  }
@@ -5287,7 +5280,7 @@
5287
5280
  };
5288
5281
  baselineCell = getters.getEvaluatedCell(baselinePosition);
5289
5282
  }
5290
- const background = getters.getBackgroundOfSingleCellChart(chart.background, chart.keyValue);
5283
+ const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
5291
5284
  const locale = getters.getLocale();
5292
5285
  return {
5293
5286
  title: _t(chart.title),
@@ -5296,7 +5289,7 @@
5296
5289
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
5297
5290
  baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
5298
5291
  baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
5299
- fontColor: chartFontColor(background),
5292
+ fontColor,
5300
5293
  background,
5301
5294
  baselineStyle: chart.baselineMode !== "percentage" && baseline
5302
5295
  ? getters.getCellStyle({
@@ -6591,6 +6584,7 @@
6591
6584
  },
6592
6585
  };
6593
6586
 
6587
+ const macRegex = /Mac/i;
6594
6588
  /**
6595
6589
  * Return true if the event was triggered from
6596
6590
  * a child element.
@@ -6630,7 +6624,15 @@
6630
6624
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
6631
6625
  }
6632
6626
  function isMacOS() {
6633
- return navigator.userAgent.toUpperCase().indexOf("MAC") >= 0;
6627
+ return Boolean(macRegex.test(navigator.userAgent));
6628
+ }
6629
+ /**
6630
+ * @param {KeyboardEvent | MouseEvent} ev
6631
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
6632
+ * On Mac, this is the "meta" or "command" key.
6633
+ */
6634
+ function isCtrlKey(ev) {
6635
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
6634
6636
  }
6635
6637
 
6636
6638
  /**
@@ -8879,7 +8881,7 @@
8879
8881
  });
8880
8882
  return {
8881
8883
  chartJsConfig: config,
8882
- background: getters.getBackgroundOfSingleCellChart(chart.background, dataRange),
8884
+ background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
8883
8885
  };
8884
8886
  }
8885
8887
 
@@ -8960,7 +8962,7 @@
8960
8962
  else if (format.includes("h") || format.includes("H")) {
8961
8963
  return "hour";
8962
8964
  }
8963
- else if (format.includes("D")) {
8965
+ else if (format.includes("d")) {
8964
8966
  return "day";
8965
8967
  }
8966
8968
  else if (format.includes("M")) {
@@ -21213,10 +21215,10 @@
21213
21215
  function updateSelectionWithArrowKeys(ev, selection) {
21214
21216
  const direction = arrowMap[ev.key];
21215
21217
  if (ev.shiftKey) {
21216
- selection.resizeAnchorZone(direction, ev.ctrlKey ? "end" : 1);
21218
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
21217
21219
  }
21218
21220
  else {
21219
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
21221
+ selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
21220
21222
  }
21221
21223
  }
21222
21224
 
@@ -28201,7 +28203,10 @@
28201
28203
  return;
28202
28204
  }
28203
28205
  const [col, row] = this.getCartesianCoordinates(ev);
28204
- this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });
28206
+ this.props.onCellClicked(col, row, {
28207
+ expandZone: ev.shiftKey,
28208
+ addZone: isCtrlKey(ev),
28209
+ });
28205
28210
  }
28206
28211
  onDoubleClick(ev) {
28207
28212
  const [col, row] = this.getCartesianCoordinates(ev);
@@ -28431,7 +28436,7 @@
28431
28436
  this._increaseSelection(index);
28432
28437
  }
28433
28438
  else {
28434
- this._selectElement(index, ev.ctrlKey);
28439
+ this._selectElement(index, isCtrlKey(ev));
28435
28440
  }
28436
28441
  this.lastSelectedElementIndex = index;
28437
28442
  const mouseMoveSelect = (col, row) => {
@@ -28595,8 +28600,8 @@
28595
28600
  this.env.raiseError(MergeErrorMessage);
28596
28601
  }
28597
28602
  }
28598
- _selectElement(index, ctrlKey) {
28599
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
28603
+ _selectElement(index, addDistinctHeader) {
28604
+ this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
28600
28605
  }
28601
28606
  _increaseSelection(index) {
28602
28607
  this.env.model.selection.selectColumn(index, "updateAnchor");
@@ -28769,8 +28774,8 @@
28769
28774
  this.env.raiseError(MergeErrorMessage);
28770
28775
  }
28771
28776
  }
28772
- _selectElement(index, ctrlKey) {
28773
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
28777
+ _selectElement(index, addDistinctHeader) {
28778
+ this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
28774
28779
  }
28775
28780
  _increaseSelection(index) {
28776
28781
  this.env.model.selection.selectRow(index, "updateAnchor");
@@ -29572,17 +29577,17 @@
29572
29577
  // ---------------------------------------------------------------------------
29573
29578
  // Zone selection with mouse
29574
29579
  // ---------------------------------------------------------------------------
29575
- onCellClicked(col, row, { ctrlKey, shiftKey }) {
29580
+ onCellClicked(col, row, { addZone, expandZone }) {
29576
29581
  if (this.env.model.getters.hasOpenedPopover()) {
29577
29582
  this.closeOpenedPopover();
29578
29583
  }
29579
29584
  if (this.env.model.getters.getEditionMode() === "editing") {
29580
29585
  interactiveStopEdition(this.env);
29581
29586
  }
29582
- if (shiftKey) {
29587
+ if (expandZone) {
29583
29588
  this.env.model.selection.setAnchorCorner(col, row);
29584
29589
  }
29585
- else if (ctrlKey) {
29590
+ else if (addZone) {
29586
29591
  this.env.model.selection.addCellToSelection(col, row);
29587
29592
  }
29588
29593
  else {
@@ -29643,7 +29648,7 @@
29643
29648
  onKeydown(ev) {
29644
29649
  const eventKey = ev.key.toUpperCase();
29645
29650
  let keyDownString = "";
29646
- if (ev.ctrlKey && eventKey !== "CTRL")
29651
+ if (isCtrlKey(ev) && eventKey !== "CTRL")
29647
29652
  keyDownString += "CTRL+";
29648
29653
  if (ev.metaKey)
29649
29654
  keyDownString += "CTRL+";
@@ -34534,7 +34539,7 @@
34534
34539
  */
34535
34540
  function getBorderId(border) {
34536
34541
  for (let [key, value] of Object.entries(borders)) {
34537
- if (stringify(value) === stringify(border)) {
34542
+ if (deepEquals(value, border)) {
34538
34543
  return parseInt(key, 10);
34539
34544
  }
34540
34545
  }
@@ -39853,56 +39858,758 @@
39853
39858
  }
39854
39859
  }
39855
39860
 
39861
+ function quickselect(arr, k, left, right, compare) {
39862
+ quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
39863
+ }
39864
+
39865
+ function quickselectStep(arr, k, left, right, compare) {
39866
+
39867
+ while (right > left) {
39868
+ if (right - left > 600) {
39869
+ var n = right - left + 1;
39870
+ var m = k - left + 1;
39871
+ var z = Math.log(n);
39872
+ var s = 0.5 * Math.exp(2 * z / 3);
39873
+ var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
39874
+ var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
39875
+ var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
39876
+ quickselectStep(arr, k, newLeft, newRight, compare);
39877
+ }
39878
+
39879
+ var t = arr[k];
39880
+ var i = left;
39881
+ var j = right;
39882
+
39883
+ swap(arr, left, k);
39884
+ if (compare(arr[right], t) > 0) swap(arr, left, right);
39885
+
39886
+ while (i < j) {
39887
+ swap(arr, i, j);
39888
+ i++;
39889
+ j--;
39890
+ while (compare(arr[i], t) < 0) i++;
39891
+ while (compare(arr[j], t) > 0) j--;
39892
+ }
39893
+
39894
+ if (compare(arr[left], t) === 0) swap(arr, left, j);
39895
+ else {
39896
+ j++;
39897
+ swap(arr, j, right);
39898
+ }
39899
+
39900
+ if (j <= k) left = j + 1;
39901
+ if (k <= j) right = j - 1;
39902
+ }
39903
+ }
39904
+
39905
+ function swap(arr, i, j) {
39906
+ var tmp = arr[i];
39907
+ arr[i] = arr[j];
39908
+ arr[j] = tmp;
39909
+ }
39910
+
39911
+ function defaultCompare(a, b) {
39912
+ return a < b ? -1 : a > b ? 1 : 0;
39913
+ }
39914
+
39915
+ class RBush {
39916
+ constructor(maxEntries = 9) {
39917
+ // max entries in a node is 9 by default; min node fill is 40% for best performance
39918
+ this._maxEntries = Math.max(4, maxEntries);
39919
+ this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
39920
+ this.clear();
39921
+ }
39922
+
39923
+ all() {
39924
+ return this._all(this.data, []);
39925
+ }
39926
+
39927
+ search(bbox) {
39928
+ let node = this.data;
39929
+ const result = [];
39930
+
39931
+ if (!intersects(bbox, node)) return result;
39932
+
39933
+ const toBBox = this.toBBox;
39934
+ const nodesToSearch = [];
39935
+
39936
+ while (node) {
39937
+ for (let i = 0; i < node.children.length; i++) {
39938
+ const child = node.children[i];
39939
+ const childBBox = node.leaf ? toBBox(child) : child;
39940
+
39941
+ if (intersects(bbox, childBBox)) {
39942
+ if (node.leaf) result.push(child);
39943
+ else if (contains(bbox, childBBox)) this._all(child, result);
39944
+ else nodesToSearch.push(child);
39945
+ }
39946
+ }
39947
+ node = nodesToSearch.pop();
39948
+ }
39949
+
39950
+ return result;
39951
+ }
39952
+
39953
+ collides(bbox) {
39954
+ let node = this.data;
39955
+
39956
+ if (!intersects(bbox, node)) return false;
39957
+
39958
+ const nodesToSearch = [];
39959
+ while (node) {
39960
+ for (let i = 0; i < node.children.length; i++) {
39961
+ const child = node.children[i];
39962
+ const childBBox = node.leaf ? this.toBBox(child) : child;
39963
+
39964
+ if (intersects(bbox, childBBox)) {
39965
+ if (node.leaf || contains(bbox, childBBox)) return true;
39966
+ nodesToSearch.push(child);
39967
+ }
39968
+ }
39969
+ node = nodesToSearch.pop();
39970
+ }
39971
+
39972
+ return false;
39973
+ }
39974
+
39975
+ load(data) {
39976
+ if (!(data && data.length)) return this;
39977
+
39978
+ if (data.length < this._minEntries) {
39979
+ for (let i = 0; i < data.length; i++) {
39980
+ this.insert(data[i]);
39981
+ }
39982
+ return this;
39983
+ }
39984
+
39985
+ // recursively build the tree with the given data from scratch using OMT algorithm
39986
+ let node = this._build(data.slice(), 0, data.length - 1, 0);
39987
+
39988
+ if (!this.data.children.length) {
39989
+ // save as is if tree is empty
39990
+ this.data = node;
39991
+
39992
+ } else if (this.data.height === node.height) {
39993
+ // split root if trees have the same height
39994
+ this._splitRoot(this.data, node);
39995
+
39996
+ } else {
39997
+ if (this.data.height < node.height) {
39998
+ // swap trees if inserted one is bigger
39999
+ const tmpNode = this.data;
40000
+ this.data = node;
40001
+ node = tmpNode;
40002
+ }
40003
+
40004
+ // insert the small tree into the large tree at appropriate level
40005
+ this._insert(node, this.data.height - node.height - 1, true);
40006
+ }
40007
+
40008
+ return this;
40009
+ }
40010
+
40011
+ insert(item) {
40012
+ if (item) this._insert(item, this.data.height - 1);
40013
+ return this;
40014
+ }
40015
+
40016
+ clear() {
40017
+ this.data = createNode([]);
40018
+ return this;
40019
+ }
40020
+
40021
+ remove(item, equalsFn) {
40022
+ if (!item) return this;
40023
+
40024
+ let node = this.data;
40025
+ const bbox = this.toBBox(item);
40026
+ const path = [];
40027
+ const indexes = [];
40028
+ let i, parent, goingUp;
40029
+
40030
+ // depth-first iterative tree traversal
40031
+ while (node || path.length) {
40032
+
40033
+ if (!node) { // go up
40034
+ node = path.pop();
40035
+ parent = path[path.length - 1];
40036
+ i = indexes.pop();
40037
+ goingUp = true;
40038
+ }
40039
+
40040
+ if (node.leaf) { // check current node
40041
+ const index = findItem(item, node.children, equalsFn);
40042
+
40043
+ if (index !== -1) {
40044
+ // item found, remove the item and condense tree upwards
40045
+ node.children.splice(index, 1);
40046
+ path.push(node);
40047
+ this._condense(path);
40048
+ return this;
40049
+ }
40050
+ }
40051
+
40052
+ if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
40053
+ path.push(node);
40054
+ indexes.push(i);
40055
+ i = 0;
40056
+ parent = node;
40057
+ node = node.children[0];
40058
+
40059
+ } else if (parent) { // go right
40060
+ i++;
40061
+ node = parent.children[i];
40062
+ goingUp = false;
40063
+
40064
+ } else node = null; // nothing found
40065
+ }
40066
+
40067
+ return this;
40068
+ }
40069
+
40070
+ toBBox(item) { return item; }
40071
+
40072
+ compareMinX(a, b) { return a.minX - b.minX; }
40073
+ compareMinY(a, b) { return a.minY - b.minY; }
40074
+
40075
+ toJSON() { return this.data; }
40076
+
40077
+ fromJSON(data) {
40078
+ this.data = data;
40079
+ return this;
40080
+ }
40081
+
40082
+ _all(node, result) {
40083
+ const nodesToSearch = [];
40084
+ while (node) {
40085
+ if (node.leaf) result.push(...node.children);
40086
+ else nodesToSearch.push(...node.children);
40087
+
40088
+ node = nodesToSearch.pop();
40089
+ }
40090
+ return result;
40091
+ }
40092
+
40093
+ _build(items, left, right, height) {
40094
+
40095
+ const N = right - left + 1;
40096
+ let M = this._maxEntries;
40097
+ let node;
40098
+
40099
+ if (N <= M) {
40100
+ // reached leaf level; return leaf
40101
+ node = createNode(items.slice(left, right + 1));
40102
+ calcBBox(node, this.toBBox);
40103
+ return node;
40104
+ }
40105
+
40106
+ if (!height) {
40107
+ // target height of the bulk-loaded tree
40108
+ height = Math.ceil(Math.log(N) / Math.log(M));
40109
+
40110
+ // target number of root entries to maximize storage utilization
40111
+ M = Math.ceil(N / Math.pow(M, height - 1));
40112
+ }
40113
+
40114
+ node = createNode([]);
40115
+ node.leaf = false;
40116
+ node.height = height;
40117
+
40118
+ // split the items into M mostly square tiles
40119
+
40120
+ const N2 = Math.ceil(N / M);
40121
+ const N1 = N2 * Math.ceil(Math.sqrt(M));
40122
+
40123
+ multiSelect(items, left, right, N1, this.compareMinX);
40124
+
40125
+ for (let i = left; i <= right; i += N1) {
40126
+
40127
+ const right2 = Math.min(i + N1 - 1, right);
40128
+
40129
+ multiSelect(items, i, right2, N2, this.compareMinY);
40130
+
40131
+ for (let j = i; j <= right2; j += N2) {
40132
+
40133
+ const right3 = Math.min(j + N2 - 1, right2);
40134
+
40135
+ // pack each entry recursively
40136
+ node.children.push(this._build(items, j, right3, height - 1));
40137
+ }
40138
+ }
40139
+
40140
+ calcBBox(node, this.toBBox);
40141
+
40142
+ return node;
40143
+ }
40144
+
40145
+ _chooseSubtree(bbox, node, level, path) {
40146
+ while (true) {
40147
+ path.push(node);
40148
+
40149
+ if (node.leaf || path.length - 1 === level) break;
40150
+
40151
+ let minArea = Infinity;
40152
+ let minEnlargement = Infinity;
40153
+ let targetNode;
40154
+
40155
+ for (let i = 0; i < node.children.length; i++) {
40156
+ const child = node.children[i];
40157
+ const area = bboxArea(child);
40158
+ const enlargement = enlargedArea(bbox, child) - area;
40159
+
40160
+ // choose entry with the least area enlargement
40161
+ if (enlargement < minEnlargement) {
40162
+ minEnlargement = enlargement;
40163
+ minArea = area < minArea ? area : minArea;
40164
+ targetNode = child;
40165
+
40166
+ } else if (enlargement === minEnlargement) {
40167
+ // otherwise choose one with the smallest area
40168
+ if (area < minArea) {
40169
+ minArea = area;
40170
+ targetNode = child;
40171
+ }
40172
+ }
40173
+ }
40174
+
40175
+ node = targetNode || node.children[0];
40176
+ }
40177
+
40178
+ return node;
40179
+ }
40180
+
40181
+ _insert(item, level, isNode) {
40182
+ const bbox = isNode ? item : this.toBBox(item);
40183
+ const insertPath = [];
40184
+
40185
+ // find the best node for accommodating the item, saving all nodes along the path too
40186
+ const node = this._chooseSubtree(bbox, this.data, level, insertPath);
40187
+
40188
+ // put the item into the node
40189
+ node.children.push(item);
40190
+ extend(node, bbox);
40191
+
40192
+ // split on node overflow; propagate upwards if necessary
40193
+ while (level >= 0) {
40194
+ if (insertPath[level].children.length > this._maxEntries) {
40195
+ this._split(insertPath, level);
40196
+ level--;
40197
+ } else break;
40198
+ }
40199
+
40200
+ // adjust bboxes along the insertion path
40201
+ this._adjustParentBBoxes(bbox, insertPath, level);
40202
+ }
40203
+
40204
+ // split overflowed node into two
40205
+ _split(insertPath, level) {
40206
+ const node = insertPath[level];
40207
+ const M = node.children.length;
40208
+ const m = this._minEntries;
40209
+
40210
+ this._chooseSplitAxis(node, m, M);
40211
+
40212
+ const splitIndex = this._chooseSplitIndex(node, m, M);
40213
+
40214
+ const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
40215
+ newNode.height = node.height;
40216
+ newNode.leaf = node.leaf;
40217
+
40218
+ calcBBox(node, this.toBBox);
40219
+ calcBBox(newNode, this.toBBox);
40220
+
40221
+ if (level) insertPath[level - 1].children.push(newNode);
40222
+ else this._splitRoot(node, newNode);
40223
+ }
40224
+
40225
+ _splitRoot(node, newNode) {
40226
+ // split root node
40227
+ this.data = createNode([node, newNode]);
40228
+ this.data.height = node.height + 1;
40229
+ this.data.leaf = false;
40230
+ calcBBox(this.data, this.toBBox);
40231
+ }
40232
+
40233
+ _chooseSplitIndex(node, m, M) {
40234
+ let index;
40235
+ let minOverlap = Infinity;
40236
+ let minArea = Infinity;
40237
+
40238
+ for (let i = m; i <= M - m; i++) {
40239
+ const bbox1 = distBBox(node, 0, i, this.toBBox);
40240
+ const bbox2 = distBBox(node, i, M, this.toBBox);
40241
+
40242
+ const overlap = intersectionArea(bbox1, bbox2);
40243
+ const area = bboxArea(bbox1) + bboxArea(bbox2);
40244
+
40245
+ // choose distribution with minimum overlap
40246
+ if (overlap < minOverlap) {
40247
+ minOverlap = overlap;
40248
+ index = i;
40249
+
40250
+ minArea = area < minArea ? area : minArea;
40251
+
40252
+ } else if (overlap === minOverlap) {
40253
+ // otherwise choose distribution with minimum area
40254
+ if (area < minArea) {
40255
+ minArea = area;
40256
+ index = i;
40257
+ }
40258
+ }
40259
+ }
40260
+
40261
+ return index || M - m;
40262
+ }
40263
+
40264
+ // sorts node children by the best axis for split
40265
+ _chooseSplitAxis(node, m, M) {
40266
+ const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
40267
+ const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
40268
+ const xMargin = this._allDistMargin(node, m, M, compareMinX);
40269
+ const yMargin = this._allDistMargin(node, m, M, compareMinY);
40270
+
40271
+ // if total distributions margin value is minimal for x, sort by minX,
40272
+ // otherwise it's already sorted by minY
40273
+ if (xMargin < yMargin) node.children.sort(compareMinX);
40274
+ }
40275
+
40276
+ // total margin of all possible split distributions where each node is at least m full
40277
+ _allDistMargin(node, m, M, compare) {
40278
+ node.children.sort(compare);
40279
+
40280
+ const toBBox = this.toBBox;
40281
+ const leftBBox = distBBox(node, 0, m, toBBox);
40282
+ const rightBBox = distBBox(node, M - m, M, toBBox);
40283
+ let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
40284
+
40285
+ for (let i = m; i < M - m; i++) {
40286
+ const child = node.children[i];
40287
+ extend(leftBBox, node.leaf ? toBBox(child) : child);
40288
+ margin += bboxMargin(leftBBox);
40289
+ }
40290
+
40291
+ for (let i = M - m - 1; i >= m; i--) {
40292
+ const child = node.children[i];
40293
+ extend(rightBBox, node.leaf ? toBBox(child) : child);
40294
+ margin += bboxMargin(rightBBox);
40295
+ }
40296
+
40297
+ return margin;
40298
+ }
40299
+
40300
+ _adjustParentBBoxes(bbox, path, level) {
40301
+ // adjust bboxes along the given tree path
40302
+ for (let i = level; i >= 0; i--) {
40303
+ extend(path[i], bbox);
40304
+ }
40305
+ }
40306
+
40307
+ _condense(path) {
40308
+ // go through the path, removing empty nodes and updating bboxes
40309
+ for (let i = path.length - 1, siblings; i >= 0; i--) {
40310
+ if (path[i].children.length === 0) {
40311
+ if (i > 0) {
40312
+ siblings = path[i - 1].children;
40313
+ siblings.splice(siblings.indexOf(path[i]), 1);
40314
+
40315
+ } else this.clear();
40316
+
40317
+ } else calcBBox(path[i], this.toBBox);
40318
+ }
40319
+ }
40320
+ }
40321
+
40322
+ function findItem(item, items, equalsFn) {
40323
+ if (!equalsFn) return items.indexOf(item);
40324
+
40325
+ for (let i = 0; i < items.length; i++) {
40326
+ if (equalsFn(item, items[i])) return i;
40327
+ }
40328
+ return -1;
40329
+ }
40330
+
40331
+ // calculate node's bbox from bboxes of its children
40332
+ function calcBBox(node, toBBox) {
40333
+ distBBox(node, 0, node.children.length, toBBox, node);
40334
+ }
40335
+
40336
+ // min bounding rectangle of node children from k to p-1
40337
+ function distBBox(node, k, p, toBBox, destNode) {
40338
+ if (!destNode) destNode = createNode(null);
40339
+ destNode.minX = Infinity;
40340
+ destNode.minY = Infinity;
40341
+ destNode.maxX = -Infinity;
40342
+ destNode.maxY = -Infinity;
40343
+
40344
+ for (let i = k; i < p; i++) {
40345
+ const child = node.children[i];
40346
+ extend(destNode, node.leaf ? toBBox(child) : child);
40347
+ }
40348
+
40349
+ return destNode;
40350
+ }
40351
+
40352
+ function extend(a, b) {
40353
+ a.minX = Math.min(a.minX, b.minX);
40354
+ a.minY = Math.min(a.minY, b.minY);
40355
+ a.maxX = Math.max(a.maxX, b.maxX);
40356
+ a.maxY = Math.max(a.maxY, b.maxY);
40357
+ return a;
40358
+ }
40359
+
40360
+ function compareNodeMinX(a, b) { return a.minX - b.minX; }
40361
+ function compareNodeMinY(a, b) { return a.minY - b.minY; }
40362
+
40363
+ function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
40364
+ function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
40365
+
40366
+ function enlargedArea(a, b) {
40367
+ return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
40368
+ (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
40369
+ }
40370
+
40371
+ function intersectionArea(a, b) {
40372
+ const minX = Math.max(a.minX, b.minX);
40373
+ const minY = Math.max(a.minY, b.minY);
40374
+ const maxX = Math.min(a.maxX, b.maxX);
40375
+ const maxY = Math.min(a.maxY, b.maxY);
40376
+
40377
+ return Math.max(0, maxX - minX) *
40378
+ Math.max(0, maxY - minY);
40379
+ }
40380
+
40381
+ function contains(a, b) {
40382
+ return a.minX <= b.minX &&
40383
+ a.minY <= b.minY &&
40384
+ b.maxX <= a.maxX &&
40385
+ b.maxY <= a.maxY;
40386
+ }
40387
+
40388
+ function intersects(a, b) {
40389
+ return b.minX <= a.maxX &&
40390
+ b.minY <= a.maxY &&
40391
+ b.maxX >= a.minX &&
40392
+ b.maxY >= a.minY;
40393
+ }
40394
+
40395
+ function createNode(children) {
40396
+ return {
40397
+ children,
40398
+ height: 1,
40399
+ leaf: true,
40400
+ minX: Infinity,
40401
+ minY: Infinity,
40402
+ maxX: -Infinity,
40403
+ maxY: -Infinity
40404
+ };
40405
+ }
40406
+
40407
+ // sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
40408
+ // combines selection algorithm with binary divide & conquer approach
40409
+
40410
+ function multiSelect(arr, left, right, n, compare) {
40411
+ const stack = [left, right];
40412
+
40413
+ while (stack.length) {
40414
+ right = stack.pop();
40415
+ left = stack.pop();
40416
+
40417
+ if (right - left <= n) continue;
40418
+
40419
+ const mid = left + Math.ceil((right - left) / n / 2) * n;
40420
+ quickselect(arr, mid, left, right, compare);
40421
+
40422
+ stack.push(left, mid, mid, right);
40423
+ }
40424
+ }
40425
+
39856
40426
  /**
39857
- * This class is an implementation of a dependency Graph.
40427
+ * R-Tree Data Structure
40428
+ *
40429
+ * R-Tree is a spatial data structure used for efficient indexing and querying
40430
+ * of multi-dimensional objects, particularly in geometric and spatial applications.
40431
+ *
40432
+ * It organizes objects into a tree hierarchy, grouping nearby objects together
40433
+ * in bounding boxes. Each node in the tree represents a bounding box that
40434
+ * contains its child nodes or leaf objects. This hierarchical structure allows
40435
+ * for faster spatial queries.
40436
+ *
40437
+ * @see https://en.wikipedia.org/wiki/R-tree
40438
+ *
40439
+ * Consider a 2D Space with four zones: A, B, C, D
40440
+ * +--------------------------+
40441
+ * | |
40442
+ * | +---+ +-------+ |
40443
+ * | | A | | B | |
40444
+ * | +---+ +-------+ |
40445
+ * | |
40446
+ * | |
40447
+ * | +---+ |
40448
+ * | | C | |
40449
+ * | +---+ |
40450
+ * | +-----------+ |
40451
+ * | | D | |
40452
+ * | +-----------+ |
40453
+ * | |
40454
+ * +--------------------------+
40455
+ *
40456
+ * It groups together zones that are spatially close into a minimum bounding box.
40457
+ * For example, A and B are grouped together in rectangle R1, and C and D are grouped
40458
+ * in R2.
40459
+ *
40460
+ * R0
40461
+ * +--------------------------+
40462
+ * | R1 |
40463
+ * | +-----------------+ |
40464
+ * | | A | | B | |
40465
+ * | +-----------------+ |
40466
+ * | |
40467
+ * | R2 |
40468
+ * | +---+---+---+ |
40469
+ * | | | C | | |
40470
+ * | | +---+ | |
40471
+ * | +-----------+ |
40472
+ * | | D | |
40473
+ * | +-----------+ |
40474
+ * | |
40475
+ * +--------------------------+
40476
+ *
40477
+ * The tree would look like this:
40478
+ * R0
40479
+ * / \
40480
+ * / \
40481
+ * R1 R2
40482
+ * | |
40483
+ * A,B C,D
40484
+
40485
+ * Choosing how to group the zones is crucial for the performance of the tree.
40486
+ * Key considerations include avoiding excessive empty space coverage and minimizing overlap
40487
+ * to reduce the number of subtrees processed during searches.
40488
+ *
40489
+ * Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
40490
+ * which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
40491
+ * the choice cannot be made based on this criterion due to the same enlargement for different groupings,
40492
+ * we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
40493
+ *
40494
+ * This implementation is tailored for spreadsheet use, indexing objects associated
40495
+ * with a zone and a sheet.
40496
+ *
40497
+ * It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
40498
+ * @see https://github.com/mourner/rbush
40499
+ */
40500
+ class SpreadsheetRTree {
40501
+ /**
40502
+ * One 2D R-tree per sheet
40503
+ */
40504
+ rTrees = {};
40505
+ /**
40506
+ * Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
40507
+ * faster than inserting items one by one. After bulk loading (bulk insertion into
40508
+ * an empty tree), subsequent query performance is also ~20-30% better.
40509
+ */
40510
+ constructor(items = []) {
40511
+ const rangesPerSheet = {};
40512
+ for (const item of items) {
40513
+ const sheetId = item.boundingBox.sheetId;
40514
+ if (!rangesPerSheet[sheetId]) {
40515
+ rangesPerSheet[sheetId] = [];
40516
+ }
40517
+ rangesPerSheet[sheetId].push(item);
40518
+ }
40519
+ for (const sheetId in rangesPerSheet) {
40520
+ this.rTrees[sheetId] = new ZoneRBush();
40521
+ this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
40522
+ }
40523
+ }
40524
+ insert(item) {
40525
+ const sheetId = item.boundingBox.sheetId;
40526
+ if (!this.rTrees[sheetId]) {
40527
+ this.rTrees[sheetId] = new ZoneRBush();
40528
+ }
40529
+ this.rTrees[sheetId].insert(item);
40530
+ }
40531
+ search({ zone, sheetId }) {
40532
+ if (!this.rTrees[sheetId]) {
40533
+ return [];
40534
+ }
40535
+ return this.rTrees[sheetId].search({
40536
+ minX: zone.left,
40537
+ minY: zone.top,
40538
+ maxX: zone.right,
40539
+ maxY: zone.bottom,
40540
+ });
40541
+ }
40542
+ remove(item) {
40543
+ const sheetId = item.boundingBox.sheetId;
40544
+ if (!this.rTrees[sheetId]) {
40545
+ return;
40546
+ }
40547
+ this.rTrees[sheetId].remove(item, deepEquals);
40548
+ }
40549
+ }
40550
+ /**
40551
+ * RBush extension to use zones as bounding boxes
40552
+ */
40553
+ class ZoneRBush extends RBush {
40554
+ toBBox({ boundingBox }) {
40555
+ const zone = boundingBox.zone;
40556
+ return {
40557
+ minX: zone.left,
40558
+ minY: zone.top,
40559
+ maxX: zone.right,
40560
+ maxY: zone.bottom,
40561
+ };
40562
+ }
40563
+ compareMinX(a, b) {
40564
+ return a.boundingBox.zone.left - b.boundingBox.zone.left;
40565
+ }
40566
+ compareMinY(a, b) {
40567
+ return a.boundingBox.zone.top - b.boundingBox.zone.top;
40568
+ }
40569
+ }
40570
+
40571
+ /**
40572
+ * Implementation of a dependency Graph.
39858
40573
  * The graph is used to evaluate the cells in the correct
39859
40574
  * order, and should be updated each time a cell's content is modified
39860
40575
  *
40576
+ * It uses an R-Tree data structure to efficiently find dependent cells.
39861
40577
  */
39862
40578
  class FormulaDependencyGraph {
39863
- /**
39864
- * Internal structure:
39865
- * - key: a cell position (encoded as an integer)
39866
- * - value: a set of cell positions that depends on the key
39867
- *
39868
- * Given
39869
- * - A1:"= B1 + SQRT(B2)"
39870
- * - C1:"= B1";
39871
- * - C2:"= C1"
39872
- *
39873
- * we will have something like:
39874
- * - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
39875
- * - B2 ---> (A1) meaning A1 depends on B2
39876
- * - C1 ---> (C2) meaning C2 depends on C1
39877
- */
39878
- inverseDependencies = new Map();
40579
+ encoder;
39879
40580
  dependencies = new Map();
40581
+ rTree;
40582
+ constructor(encoder, data = []) {
40583
+ this.encoder = encoder;
40584
+ this.rTree = new SpreadsheetRTree(data);
40585
+ }
39880
40586
  removeAllDependencies(formulaPositionId) {
39881
- const dependencies = this.dependencies.get(formulaPositionId);
39882
- if (!dependencies) {
40587
+ const ranges = this.dependencies.get(formulaPositionId);
40588
+ if (!ranges) {
39883
40589
  return;
39884
40590
  }
39885
- for (const dependency of dependencies) {
39886
- this.inverseDependencies.get(dependency)?.delete(formulaPositionId);
40591
+ for (const range of ranges) {
40592
+ this.rTree.remove(range);
39887
40593
  }
39888
40594
  this.dependencies.delete(formulaPositionId);
39889
40595
  }
39890
40596
  addDependencies(formulaPositionId, dependencies) {
39891
- for (const dependency of dependencies) {
39892
- const inverseDependencies = this.inverseDependencies.get(dependency);
39893
- if (inverseDependencies) {
39894
- inverseDependencies.add(formulaPositionId);
39895
- }
39896
- else {
39897
- this.inverseDependencies.set(dependency, new Set([formulaPositionId]));
39898
- }
40597
+ const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
40598
+ data: formulaPositionId,
40599
+ boundingBox: {
40600
+ zone,
40601
+ sheetId,
40602
+ },
40603
+ }));
40604
+ for (const item of rTreeItems) {
40605
+ this.rTree.insert(item);
39899
40606
  }
39900
40607
  const existingDependencies = this.dependencies.get(formulaPositionId);
39901
40608
  if (existingDependencies) {
39902
- existingDependencies.push(...dependencies);
40609
+ existingDependencies.push(...rTreeItems);
39903
40610
  }
39904
40611
  else {
39905
- this.dependencies.set(formulaPositionId, dependencies);
40612
+ this.dependencies.set(formulaPositionId, rTreeItems);
39906
40613
  }
39907
40614
  }
39908
40615
  /**
@@ -39910,20 +40617,20 @@
39910
40617
  * in the correct order they should be evaluated.
39911
40618
  * This is called a topological ordering (excluding cycles)
39912
40619
  */
39913
- getCellsDependingOn(positionIds) {
40620
+ getCellsDependingOn(ranges) {
39914
40621
  const visited = new JetSet();
39915
- const queue = Array.from(positionIds).reverse();
40622
+ const queue = Array.from(ranges).reverse();
39916
40623
  while (queue.length > 0) {
39917
- const node = queue.pop();
39918
- visited.add(node);
39919
- const adjacentNodes = this.inverseDependencies.get(node) || new Set();
39920
- for (const adjacentNode of adjacentNodes) {
39921
- if (!visited.has(adjacentNode)) {
39922
- queue.push(adjacentNode);
40624
+ const range = queue.pop();
40625
+ visited.add(...this.encoder.encodeBoundingBox(range));
40626
+ const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
40627
+ for (const positionId of impactedPositionIds) {
40628
+ if (!visited.has(positionId)) {
40629
+ queue.push(this.encoder.decodeToBoundingBox(positionId));
39923
40630
  }
39924
40631
  }
39925
40632
  }
39926
- visited.delete(...positionIds);
40633
+ visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
39927
40634
  return visited;
39928
40635
  }
39929
40636
  }
@@ -40011,9 +40718,9 @@
40011
40718
  context;
40012
40719
  getters;
40013
40720
  compilationParams;
40014
- positionEncoder = new PositionBitsEncoder();
40721
+ encoder = new PositionBitsEncoder();
40015
40722
  evaluatedCells = new Map();
40016
- formulaDependencies = lazy(new FormulaDependencyGraph());
40723
+ formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
40017
40724
  blockedArrayFormulas = new Set();
40018
40725
  spreadingRelations = new SpreadingRelation();
40019
40726
  constructor(context, getters) {
@@ -40022,23 +40729,23 @@
40022
40729
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
40023
40730
  }
40024
40731
  getEvaluatedCell(position) {
40025
- return (this.evaluatedCells.get(this.encodePosition(position)) ||
40732
+ return (this.evaluatedCells.get(this.encoder.encode(position)) ||
40026
40733
  createEvaluatedCell("", { locale: this.getters.getLocale() }));
40027
40734
  }
40028
40735
  getSpreadPositionsOf(position) {
40029
- const positionId = this.encodePosition(position);
40736
+ const positionId = this.encoder.encode(position);
40030
40737
  if (!this.spreadingRelations.isArrayFormula(positionId)) {
40031
40738
  return [];
40032
40739
  }
40033
- return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.decodePosition.bind(this));
40740
+ return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
40034
40741
  }
40035
40742
  getArrayFormulaSpreadingOn(position) {
40036
- const positionId = this.encodePosition(position);
40743
+ const positionId = this.encoder.encode(position);
40037
40744
  const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
40038
- return formulaPosition !== undefined ? this.decodePosition(formulaPosition) : undefined;
40745
+ return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
40039
40746
  }
40040
40747
  getEvaluatedPositions() {
40041
- return [...this.evaluatedCells.keys()].map(this.decodePosition.bind(this));
40748
+ return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
40042
40749
  }
40043
40750
  getArrayFormulaSpreadingOnId(positionId) {
40044
40751
  if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
@@ -40048,7 +40755,7 @@
40048
40755
  return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
40049
40756
  }
40050
40757
  updateDependencies(position) {
40051
- const positionId = this.encodePosition(position);
40758
+ const positionId = this.encoder.encode(position);
40052
40759
  this.formulaDependencies().removeAllDependencies(positionId);
40053
40760
  const dependencies = this.getDirectDependencies(positionId);
40054
40761
  this.formulaDependencies().addDependencies(positionId, dependencies);
@@ -40058,12 +40765,12 @@
40058
40765
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
40059
40766
  }
40060
40767
  evaluateCells(positions) {
40061
- const cells = positions.map(this.encodePosition.bind(this));
40768
+ const cells = positions.map((p) => this.encoder.encode(p));
40062
40769
  const cellsToCompute = new JetSet(cells);
40063
- const arrayFormulas = this.getArrayFormulasImpactedByChangesOf(cells);
40770
+ const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
40064
40771
  cellsToCompute.add(...this.getCellsDependingOn(cells));
40065
- cellsToCompute.add(...arrayFormulas);
40066
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulas));
40772
+ cellsToCompute.add(...arrayFormulasPositionIds);
40773
+ cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
40067
40774
  this.evaluate(cellsToCompute);
40068
40775
  }
40069
40776
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -40086,12 +40793,14 @@
40086
40793
  this.blockedArrayFormulas = new Set();
40087
40794
  this.spreadingRelations = new SpreadingRelation();
40088
40795
  this.formulaDependencies = lazy(() => {
40089
- const dependencyGraph = new FormulaDependencyGraph();
40090
- for (const positionId of this.getAllCells()) {
40091
- const dependencies = this.getDirectDependencies(positionId);
40092
- dependencyGraph.addDependencies(positionId, dependencies);
40093
- }
40094
- return dependencyGraph;
40796
+ const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
40797
+ data: positionId,
40798
+ boundingBox: {
40799
+ zone: range.zone,
40800
+ sheetId: range.sheetId,
40801
+ },
40802
+ })));
40803
+ return new FormulaDependencyGraph(this.encoder, dependencies);
40095
40804
  });
40096
40805
  }
40097
40806
  evaluateAllCells() {
@@ -40113,7 +40822,7 @@
40113
40822
  for (const sheetId of this.getters.getSheetIds()) {
40114
40823
  const cellIds = this.getters.getCells(sheetId);
40115
40824
  for (const cellId in cellIds) {
40116
- positionIds.add(this.encodePosition(this.getters.getCellPosition(cellId)));
40825
+ positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
40117
40826
  }
40118
40827
  }
40119
40828
  return positionIds;
@@ -40180,7 +40889,7 @@
40180
40889
  }
40181
40890
  }
40182
40891
  computeAndSave(position) {
40183
- const positionId = this.encodePosition(position);
40892
+ const positionId = this.encoder.encode(position);
40184
40893
  const evaluatedCell = this.computeCell(positionId);
40185
40894
  if (!this.evaluatedCells.has(positionId)) {
40186
40895
  this.setEvaluatedCell(positionId, evaluatedCell);
@@ -40240,15 +40949,15 @@
40240
40949
  throw new Error(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
40241
40950
  }
40242
40951
  updateSpreadRelation({ sheetId, col, row, }) {
40243
- const arrayFormulaPositionId = this.encodePosition({ sheetId, col, row });
40952
+ const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
40244
40953
  return (i, j) => {
40245
40954
  const position = { sheetId, col: i + col, row: j + row };
40246
- const resultPositionId = this.encodePosition(position);
40955
+ const resultPositionId = this.encoder.encode(position);
40247
40956
  this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
40248
40957
  };
40249
40958
  }
40250
40959
  checkCollision({ sheetId, col, row }) {
40251
- const formulaPositionId = this.encodePosition({ sheetId, col, row });
40960
+ const formulaPositionId = this.encoder.encode({ sheetId, col, row });
40252
40961
  return (i, j) => {
40253
40962
  const position = { sheetId: sheetId, col: i + col, row: j + row };
40254
40963
  const rawCell = this.getters.getCell(position);
@@ -40269,7 +40978,7 @@
40269
40978
  format: format || matrixResult[i][j]?.format,
40270
40979
  locale: this.getters.getLocale(),
40271
40980
  });
40272
- const positionId = this.encodePosition(position);
40981
+ const positionId = this.encoder.encode(position);
40273
40982
  this.setEvaluatedCell(positionId, evaluatedCell);
40274
40983
  // check if formula dependencies present in the spread zone
40275
40984
  // if so, they need to be recomputed
@@ -40301,29 +41010,17 @@
40301
41010
  if (!cell?.isFormula) {
40302
41011
  return [];
40303
41012
  }
40304
- const dependencies = [];
40305
- for (const range of cell.compiledFormula.dependencies) {
40306
- if (range.invalidSheetName || range.invalidXc) {
40307
- continue;
40308
- }
40309
- const sheetId = range.sheetId;
40310
- forEachPositionsInZone(range.zone, (col, row) => {
40311
- dependencies.push(this.encodePosition({ sheetId, col, row }));
40312
- });
40313
- }
40314
- return dependencies;
41013
+ return cell.compiledFormula.dependencies;
40315
41014
  }
40316
41015
  getCellsDependingOn(positionIds) {
40317
- return this.formulaDependencies().getCellsDependingOn(positionIds);
41016
+ const ranges = [];
41017
+ for (const positionId of positionIds) {
41018
+ ranges.push(this.encoder.decodeToBoundingBox(positionId));
41019
+ }
41020
+ return this.formulaDependencies().getCellsDependingOn(ranges);
40318
41021
  }
40319
41022
  getCell(positionId) {
40320
- return this.getters.getCell(this.decodePosition(positionId));
40321
- }
40322
- encodePosition(position) {
40323
- return this.positionEncoder.encode(position);
40324
- }
40325
- decodePosition(positionId) {
40326
- return this.positionEncoder.decode(positionId);
41023
+ return this.getters.getCell(this.encoder.decode(positionId));
40327
41024
  }
40328
41025
  }
40329
41026
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -40379,6 +41076,13 @@
40379
41076
  encode({ sheetId, col, row }) {
40380
41077
  return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
40381
41078
  }
41079
+ encodeBoundingBox({ sheetId, zone }) {
41080
+ const positions = [];
41081
+ forEachPositionsInZone(zone, (col, row) => {
41082
+ positions.push(this.encode({ sheetId, col, row }));
41083
+ });
41084
+ return positions;
41085
+ }
40382
41086
  decode(id) {
40383
41087
  // keep only the last 21 bits by AND-ing the bit sequence with 21 ones
40384
41088
  const row = Number(id & 2097151n);
@@ -40386,6 +41090,10 @@
40386
41090
  const sheetId = this.decodeSheet(id >> 42n);
40387
41091
  return { sheetId, col, row };
40388
41092
  }
41093
+ decodeToBoundingBox(id) {
41094
+ const { sheetId, col, row } = this.decode(id);
41095
+ return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
41096
+ }
40389
41097
  encodeSheet(sheetId) {
40390
41098
  const sheetKey = this.sheetMapping[sheetId];
40391
41099
  if (sheetKey === undefined) {
@@ -40556,7 +41264,12 @@
40556
41264
  // Getters
40557
41265
  // ---------------------------------------------------------------------------
40558
41266
  evaluateFormula(sheetId, formulaString) {
40559
- return this.evaluator.evaluateFormula(sheetId, formulaString);
41267
+ try {
41268
+ return this.evaluator.evaluateFormula(sheetId, formulaString);
41269
+ }
41270
+ catch (error) {
41271
+ return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
41272
+ }
40560
41273
  }
40561
41274
  /**
40562
41275
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -40825,7 +41538,7 @@
40825
41538
  }
40826
41539
 
40827
41540
  class EvaluationChartPlugin extends UIPlugin {
40828
- static getters = ["getChartRuntime", "getBackgroundOfSingleCellChart"];
41541
+ static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
40829
41542
  charts = {};
40830
41543
  createRuntimeChart = chartRuntimeFactory(this.getters);
40831
41544
  handle(cmd) {
@@ -40863,25 +41576,26 @@
40863
41576
  return this.charts[figureId];
40864
41577
  }
40865
41578
  /**
40866
- * Get the background color of a chart based on the color of the first cell of the main range
40867
- * of the chart. In order of priority, it will return :
40868
- *
40869
- * - the chart background color if one is defined
40870
- * - the fill color of the cell if one is defined
40871
- * - the fill color of the cell from conditional formats if one is defined
40872
- * - the default chart color if no other color is defined
41579
+ * Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
40873
41580
  */
40874
- getBackgroundOfSingleCellChart(chartBackground, mainRange) {
41581
+ getStyleOfSingleCellChart(chartBackground, mainRange) {
40875
41582
  if (chartBackground)
40876
- return chartBackground;
41583
+ return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
40877
41584
  if (!mainRange) {
40878
- return BACKGROUND_CHART_COLOR;
41585
+ return {
41586
+ background: BACKGROUND_CHART_COLOR,
41587
+ fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
41588
+ };
40879
41589
  }
40880
41590
  const col = mainRange.zone.left;
40881
41591
  const row = mainRange.zone.top;
40882
41592
  const sheetId = mainRange.sheetId;
40883
41593
  const style = this.getters.getCellComputedStyle({ sheetId, col, row });
40884
- return style.fillColor || BACKGROUND_CHART_COLOR;
41594
+ const background = style.fillColor || BACKGROUND_CHART_COLOR;
41595
+ return {
41596
+ background,
41597
+ fontColor: style.textColor || chartFontColor(background),
41598
+ };
40885
41599
  }
40886
41600
  exportForExcel(data) {
40887
41601
  for (const sheet of data.sheets) {
@@ -40992,45 +41706,40 @@
40992
41706
  getComputedStyles(sheetId) {
40993
41707
  const computedStyle = {};
40994
41708
  for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
40995
- try {
40996
- switch (cf.rule.type) {
40997
- case "ColorScaleRule":
40998
- for (let range of cf.ranges) {
40999
- this.applyColorScale(sheetId, range, cf.rule, computedStyle);
41000
- }
41001
- break;
41002
- case "CellIsRule":
41003
- const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
41004
- for (let ref of cf.ranges) {
41005
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
41006
- for (let row = zone.top; row <= zone.bottom; row++) {
41007
- for (let col = zone.left; col <= zone.right; col++) {
41008
- const predicate = this.rulePredicate[cf.rule.type];
41009
- const target = { sheetId, col, row };
41010
- const values = cf.rule.values.map((value, i) => {
41011
- const compiledFormula = formulas[i];
41012
- if (compiledFormula) {
41013
- return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
41014
- ...compiledFormula,
41015
- dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
41016
- });
41017
- }
41018
- return value;
41019
- });
41020
- if (predicate && predicate(target, { ...cf.rule, values })) {
41021
- if (!computedStyle[col])
41022
- computedStyle[col] = [];
41023
- // we must combine all the properties of all the CF rules applied to the given cell
41024
- computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
41709
+ switch (cf.rule.type) {
41710
+ case "ColorScaleRule":
41711
+ for (let range of cf.ranges) {
41712
+ this.applyColorScale(sheetId, range, cf.rule, computedStyle);
41713
+ }
41714
+ break;
41715
+ case "CellIsRule":
41716
+ const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
41717
+ for (let ref of cf.ranges) {
41718
+ const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
41719
+ for (let row = zone.top; row <= zone.bottom; row++) {
41720
+ for (let col = zone.left; col <= zone.right; col++) {
41721
+ const predicate = this.rulePredicate[cf.rule.type];
41722
+ const target = { sheetId, col, row };
41723
+ const values = cf.rule.values.map((value, i) => {
41724
+ const compiledFormula = formulas[i];
41725
+ if (compiledFormula) {
41726
+ return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
41727
+ ...compiledFormula,
41728
+ dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
41729
+ });
41025
41730
  }
41731
+ return value;
41732
+ });
41733
+ if (predicate && predicate(target, { ...cf.rule, values })) {
41734
+ if (!computedStyle[col])
41735
+ computedStyle[col] = [];
41736
+ // we must combine all the properties of all the CF rules applied to the given cell
41737
+ computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
41026
41738
  }
41027
41739
  }
41028
41740
  }
41029
- break;
41030
- }
41031
- }
41032
- catch (_) {
41033
- // we don't care about the errors within the evaluation of a rule
41741
+ }
41742
+ break;
41034
41743
  }
41035
41744
  }
41036
41745
  return computedStyle;
@@ -41802,7 +42511,7 @@
41802
42511
  let row = zone.bottom;
41803
42512
  if (col > 0) {
41804
42513
  let leftPosition = { sheetId, col: col - 1, row };
41805
- while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
42514
+ while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
41806
42515
  this.getters.getCell(leftPosition)?.content) {
41807
42516
  row += 1;
41808
42517
  leftPosition = { sheetId, col: col - 1, row };
@@ -41812,7 +42521,7 @@
41812
42521
  col = zone.right;
41813
42522
  if (col <= this.getters.getNumberCols(sheetId)) {
41814
42523
  let rightPosition = { sheetId, col: col + 1, row };
41815
- while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
42524
+ while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
41816
42525
  this.getters.getCell(rightPosition)?.content) {
41817
42526
  row += 1;
41818
42527
  rightPosition = { sheetId, col: col + 1, row };
@@ -52193,7 +52902,7 @@
52193
52902
  }
52194
52903
  onKeydown(ev) {
52195
52904
  let keyDownString = "";
52196
- if (ev.ctrlKey || ev.metaKey) {
52905
+ if (isCtrlKey(ev)) {
52197
52906
  keyDownString += "CTRL+";
52198
52907
  }
52199
52908
  keyDownString += ev.key.toUpperCase();
@@ -55905,6 +56614,7 @@
55905
56614
  colorToRGBA,
55906
56615
  positionToZone,
55907
56616
  isDefined: isDefined$1,
56617
+ isMatrix,
55908
56618
  lazy,
55909
56619
  genericRepeat,
55910
56620
  createAction,
@@ -55988,9 +56698,9 @@
55988
56698
  exports.tokenize = tokenize;
55989
56699
 
55990
56700
 
55991
- __info__.version = "17.0.5";
55992
- __info__.date = "2023-12-05T09:51:44.657Z";
55993
- __info__.hash = "2480a5d";
56701
+ __info__.version = "17.0.6";
56702
+ __info__.date = "2024-01-04T12:23:10.357Z";
56703
+ __info__.hash = "310ca57";
55994
56704
 
55995
56705
 
55996
56706
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);