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