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