@odoo/o-spreadsheet 17.1.0-alpha.5 → 17.1.0-alpha.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.1.0-alpha.5
6
- * @date 2023-12-05T09:51:40.034Z
7
- * @hash c2823eb
5
+ * @version 17.1.0-alpha.6
6
+ * @date 2024-01-04T12:22:45.921Z
7
+ * @hash 56dbdc9
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';
@@ -328,13 +328,6 @@ const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
328
328
  //------------------------------------------------------------------------------
329
329
  // Miscellaneous
330
330
  //------------------------------------------------------------------------------
331
- /**
332
- * Stringify an object, like JSON.stringify, except that the first level of keys
333
- * is ordered.
334
- */
335
- function stringify(obj) {
336
- return JSON.stringify(obj, Object.keys(obj).sort());
337
- }
338
331
  /**
339
332
  * Remove quotes from a quoted string
340
333
  * ```js
@@ -557,7 +550,7 @@ function isObjectEmptyRecursive(argument) {
557
550
  */
558
551
  function getItemId(item, itemsDic) {
559
552
  for (let [key, value] of Object.entries(itemsDic)) {
560
- if (stringify(value) === stringify(item)) {
553
+ if (deepEquals(value, item)) {
561
554
  return parseInt(key, 10);
562
555
  }
563
556
  }
@@ -2060,6 +2053,8 @@ var CommandResult;
2060
2053
  CommandResult["NoChanges"] = "NoChanges";
2061
2054
  })(CommandResult || (CommandResult = {}));
2062
2055
 
2056
+ const PLAIN_TEXT_FORMAT = "@"; // see OpenXML spec §18.8.31
2057
+
2063
2058
  const DEFAULT_LOCALES = [
2064
2059
  {
2065
2060
  name: "English (US)",
@@ -2823,6 +2818,9 @@ function parseFormat(formatString) {
2823
2818
  * Formats a cell value with its format.
2824
2819
  */
2825
2820
  function formatValue(value, { format, locale }) {
2821
+ if (format === PLAIN_TEXT_FORMAT) {
2822
+ return toString(value) || "";
2823
+ }
2826
2824
  switch (typeof value) {
2827
2825
  case "string":
2828
2826
  return value;
@@ -5294,7 +5292,7 @@ function createScorecardChartRuntime(chart, getters) {
5294
5292
  };
5295
5293
  baselineCell = getters.getEvaluatedCell(baselinePosition);
5296
5294
  }
5297
- const background = getters.getBackgroundOfSingleCellChart(chart.background, chart.keyValue);
5295
+ const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
5298
5296
  const locale = getters.getLocale();
5299
5297
  return {
5300
5298
  title: _t(chart.title),
@@ -5303,7 +5301,7 @@ function createScorecardChartRuntime(chart, getters) {
5303
5301
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
5304
5302
  baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
5305
5303
  baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
5306
- fontColor: chartFontColor(background),
5304
+ fontColor,
5307
5305
  background,
5308
5306
  baselineStyle: chart.baselineMode !== "percentage" && baseline
5309
5307
  ? getters.getCellStyle({
@@ -5729,6 +5727,9 @@ function detectLink(value) {
5729
5727
  }
5730
5728
 
5731
5729
  function evaluateLiteral(content, localeFormat) {
5730
+ if (localeFormat.format === PLAIN_TEXT_FORMAT) {
5731
+ return textCell(content || "", localeFormat);
5732
+ }
5732
5733
  return createEvaluatedCell(parseLiteral(content || "", localeFormat.locale), localeFormat);
5733
5734
  }
5734
5735
  function parseLiteral(content, locale) {
@@ -5763,6 +5764,9 @@ function createEvaluatedCell(value, localeFormat) {
5763
5764
  }
5764
5765
  function _createEvaluatedCell(value, localeFormat) {
5765
5766
  try {
5767
+ if (localeFormat.format === PLAIN_TEXT_FORMAT) {
5768
+ return textCell(toString(value), localeFormat);
5769
+ }
5766
5770
  for (const builder of builders) {
5767
5771
  const evaluateCell = builder(value, localeFormat);
5768
5772
  if (evaluateCell) {
@@ -6598,6 +6602,7 @@ const FilterMenuPopoverBuilder = {
6598
6602
  },
6599
6603
  };
6600
6604
 
6605
+ const macRegex = /Mac/i;
6601
6606
  /**
6602
6607
  * Return true if the event was triggered from
6603
6608
  * a child element.
@@ -6649,7 +6654,7 @@ const letterRegex = /^[a-zA-Z]$/;
6649
6654
  */
6650
6655
  function keyboardEventToShortcutString(ev, mode = "key") {
6651
6656
  let keyDownString = "";
6652
- if (ev.ctrlKey && ev.key !== "Ctrl")
6657
+ if (isCtrlKey(ev) && ev.key !== "Ctrl")
6653
6658
  keyDownString += "Ctrl+";
6654
6659
  if (ev.metaKey)
6655
6660
  keyDownString += "Ctrl+";
@@ -6662,7 +6667,15 @@ function keyboardEventToShortcutString(ev, mode = "key") {
6662
6667
  return keyDownString;
6663
6668
  }
6664
6669
  function isMacOS() {
6665
- return navigator.userAgent.toUpperCase().indexOf("MAC") >= 0;
6670
+ return Boolean(macRegex.test(navigator.userAgent));
6671
+ }
6672
+ /**
6673
+ * @param {KeyboardEvent | MouseEvent} ev
6674
+ * @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
6675
+ * On Mac, this is the "meta" or "command" key.
6676
+ */
6677
+ function isCtrlKey(ev) {
6678
+ return isMacOS() ? ev.metaKey : ev.ctrlKey;
6666
6679
  }
6667
6680
 
6668
6681
  /**
@@ -8901,7 +8914,7 @@ function createGaugeChartRuntime(chart, getters) {
8901
8914
  });
8902
8915
  return {
8903
8916
  chartJsConfig: config,
8904
- background: getters.getBackgroundOfSingleCellChart(chart.background, dataRange),
8917
+ background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
8905
8918
  };
8906
8919
  }
8907
8920
 
@@ -8982,7 +8995,7 @@ function getFormatMinDisplayUnit(format) {
8982
8995
  else if (format.includes("h") || format.includes("H")) {
8983
8996
  return "hour";
8984
8997
  }
8985
- else if (format.includes("D")) {
8998
+ else if (format.includes("d")) {
8986
8999
  return "day";
8987
9000
  }
8988
9001
  else if (format.includes("M")) {
@@ -9494,6 +9507,27 @@ function calculatePercentage(dataset, dataIndex) {
9494
9507
  const percentage = (dataset[dataIndex] / total) * 100;
9495
9508
  return percentage.toFixed(2);
9496
9509
  }
9510
+ function filterNegativeValues(labels, datasets) {
9511
+ const dataPointsIndexes = labels.reduce((indexes, label, i) => {
9512
+ const shouldKeep = datasets.some((dataset) => {
9513
+ const dataPoint = dataset.data[i];
9514
+ return typeof dataPoint !== "number" || dataPoint >= 0;
9515
+ });
9516
+ if (shouldKeep) {
9517
+ indexes.push(i);
9518
+ }
9519
+ return indexes;
9520
+ }, []);
9521
+ const filteredLabels = dataPointsIndexes.map((i) => labels[i] || "");
9522
+ const filteredDatasets = datasets.map((dataset) => ({
9523
+ ...dataset,
9524
+ data: dataPointsIndexes.map((i) => {
9525
+ const dataPoint = dataset.data[i];
9526
+ return typeof dataPoint !== "number" || dataPoint >= 0 ? dataPoint : 0;
9527
+ }),
9528
+ }));
9529
+ return { labels: filteredLabels, dataSetsValues: filteredDatasets };
9530
+ }
9497
9531
  function createPieChartRuntime(chart, getters) {
9498
9532
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
9499
9533
  let labels = labelValues.formattedValues;
@@ -9507,6 +9541,7 @@ function createPieChartRuntime(chart, getters) {
9507
9541
  if (chart.aggregated) {
9508
9542
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
9509
9543
  }
9544
+ ({ dataSetsValues, labels } = filterNegativeValues(labels, dataSetsValues));
9510
9545
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
9511
9546
  const locale = getters.getLocale();
9512
9547
  const config = getPieConfiguration(chart, labels, { format: dataSetFormat, locale });
@@ -10344,7 +10379,7 @@ function setStyle(env, style) {
10344
10379
  // Simple actions
10345
10380
  //------------------------------------------------------------------------------
10346
10381
  const PASTE_ACTION = async (env) => paste$1(env);
10347
- const PASTE_VALUE_ACTION = async (env) => paste$1(env, "onlyValue");
10382
+ const PASTE_AS_VALUE_ACTION = async (env) => paste$1(env, "asValue");
10348
10383
  async function paste$1(env, pasteOption) {
10349
10384
  const spreadsheetClipboard = env.model.getters.getClipboardTextContent();
10350
10385
  const osClipboard = await env.clipboard.readText();
@@ -10357,7 +10392,7 @@ async function paste$1(env, pasteOption) {
10357
10392
  else {
10358
10393
  interactivePaste(env, target, pasteOption);
10359
10394
  }
10360
- if (env.model.getters.isCutOperation() && pasteOption !== "onlyValue") {
10395
+ if (env.model.getters.isCutOperation() && pasteOption !== "asValue") {
10361
10396
  await env.clipboard.write({ [ClipboardMIMEType.PlainText]: "" });
10362
10397
  }
10363
10398
  break;
@@ -10772,9 +10807,9 @@ const pasteSpecial = {
10772
10807
  icon: "o-spreadsheet-Icon.PASTE",
10773
10808
  };
10774
10809
  const pasteSpecialValue = {
10775
- name: _t("Paste value only"),
10810
+ name: _t("Paste as value"),
10776
10811
  description: "Ctrl+Shift+V",
10777
- execute: PASTE_VALUE_ACTION,
10812
+ execute: PASTE_AS_VALUE_ACTION,
10778
10813
  };
10779
10814
  const pasteSpecialFormat = {
10780
10815
  name: _t("Paste format only"),
@@ -20013,6 +20048,11 @@ const formatNumberAutomatic = {
20013
20048
  execute: (env) => setFormatter(env, ""),
20014
20049
  isActive: (env) => isAutomaticFormatSelected(env),
20015
20050
  };
20051
+ const formatNumberPlainText = {
20052
+ name: _t("Plain text"),
20053
+ execute: (env) => setFormatter(env, PLAIN_TEXT_FORMAT),
20054
+ isActive: (env) => isFormatSelected(env, PLAIN_TEXT_FORMAT),
20055
+ };
20016
20056
  const formatNumberNumber = createFormatActionSpec({
20017
20057
  name: _t("Number"),
20018
20058
  descriptionValue: 1000.12,
@@ -20372,6 +20412,7 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
20372
20412
  formatNumberFullWeekDayAndMonth: formatNumberFullWeekDayAndMonth,
20373
20413
  formatNumberNumber: formatNumberNumber,
20374
20414
  formatNumberPercent: formatNumberPercent,
20415
+ formatNumberPlainText: formatNumberPlainText,
20375
20416
  formatNumberShortMonth: formatNumberShortMonth,
20376
20417
  formatNumberShortWeekDay: formatNumberShortWeekDay,
20377
20418
  formatNumberTime: formatNumberTime,
@@ -20802,6 +20843,10 @@ numberFormatMenuRegistry
20802
20843
  .add("format_number_automatic", {
20803
20844
  ...formatNumberAutomatic,
20804
20845
  sequence: 10,
20846
+ })
20847
+ .add("format_number_plain_text", {
20848
+ ...formatNumberPlainText,
20849
+ sequence: 15,
20805
20850
  separator: true,
20806
20851
  })
20807
20852
  .add("format_number_number", {
@@ -21400,10 +21445,10 @@ const arrowMap = {
21400
21445
  function updateSelectionWithArrowKeys(ev, selection) {
21401
21446
  const direction = arrowMap[ev.key];
21402
21447
  if (ev.shiftKey) {
21403
- selection.resizeAnchorZone(direction, ev.ctrlKey ? "end" : 1);
21448
+ selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
21404
21449
  }
21405
21450
  else {
21406
- selection.moveAnchorCell(direction, ev.ctrlKey ? "end" : 1);
21451
+ selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
21407
21452
  }
21408
21453
  }
21409
21454
 
@@ -24012,6 +24057,12 @@ css /* scss */ `
24012
24057
  padding: 4px 0 4px 4px;
24013
24058
  }
24014
24059
  }
24060
+
24061
+ .o-matches-count div {
24062
+ text-overflow: ellipsis;
24063
+ overflow: hidden;
24064
+ white-space: nowrap;
24065
+ }
24015
24066
  }
24016
24067
  `;
24017
24068
  class FindAndReplacePanel extends Component {
@@ -28442,7 +28493,10 @@ class GridOverlay extends Component {
28442
28493
  return;
28443
28494
  }
28444
28495
  const [col, row] = this.getCartesianCoordinates(ev);
28445
- this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });
28496
+ this.props.onCellClicked(col, row, {
28497
+ expandZone: ev.shiftKey,
28498
+ addZone: isCtrlKey(ev),
28499
+ });
28446
28500
  }
28447
28501
  onDoubleClick(ev) {
28448
28502
  const [col, row] = this.getCartesianCoordinates(ev);
@@ -28672,7 +28726,7 @@ class AbstractResizer extends Component {
28672
28726
  this._increaseSelection(index);
28673
28727
  }
28674
28728
  else {
28675
- this._selectElement(index, ev.ctrlKey);
28729
+ this._selectElement(index, isCtrlKey(ev));
28676
28730
  }
28677
28731
  this.lastSelectedElementIndex = index;
28678
28732
  const mouseMoveSelect = (col, row) => {
@@ -28829,8 +28883,8 @@ class ColResizer extends AbstractResizer {
28829
28883
  this.env.raiseError(MergeErrorMessage);
28830
28884
  }
28831
28885
  }
28832
- _selectElement(index, ctrlKey) {
28833
- this.env.model.selection.selectColumn(index, ctrlKey ? "newAnchor" : "overrideSelection");
28886
+ _selectElement(index, addDistinctHeader) {
28887
+ this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
28834
28888
  }
28835
28889
  _increaseSelection(index) {
28836
28890
  this.env.model.selection.selectColumn(index, "updateAnchor");
@@ -28994,8 +29048,8 @@ class RowResizer extends AbstractResizer {
28994
29048
  this.env.raiseError(MergeErrorMessage);
28995
29049
  }
28996
29050
  }
28997
- _selectElement(index, ctrlKey) {
28998
- this.env.model.selection.selectRow(index, ctrlKey ? "newAnchor" : "overrideSelection");
29051
+ _selectElement(index, addDistinctHeader) {
29052
+ this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
28999
29053
  }
29000
29054
  _increaseSelection(index) {
29001
29055
  this.env.model.selection.selectRow(index, "updateAnchor");
@@ -29688,7 +29742,7 @@ class Grid extends Component {
29688
29742
  "Ctrl+Shift+E": () => this.setHorizontalAlign("center"),
29689
29743
  "Ctrl+Shift+L": () => this.setHorizontalAlign("left"),
29690
29744
  "Ctrl+Shift+R": () => this.setHorizontalAlign("right"),
29691
- "Ctrl+Shift+V": () => PASTE_VALUE_ACTION(this.env),
29745
+ "Ctrl+Shift+V": () => PASTE_AS_VALUE_ACTION(this.env),
29692
29746
  "Ctrl+Shift+<": () => this.clearFormatting(),
29693
29747
  "Ctrl+<": () => this.clearFormatting(),
29694
29748
  "Ctrl+Shift+ ": () => {
@@ -29796,17 +29850,17 @@ class Grid extends Component {
29796
29850
  // ---------------------------------------------------------------------------
29797
29851
  // Zone selection with mouse
29798
29852
  // ---------------------------------------------------------------------------
29799
- onCellClicked(col, row, { ctrlKey, shiftKey }) {
29853
+ onCellClicked(col, row, { addZone, expandZone }) {
29800
29854
  if (this.env.model.getters.hasOpenedPopover()) {
29801
29855
  this.closeOpenedPopover();
29802
29856
  }
29803
29857
  if (this.env.model.getters.getEditionMode() === "editing") {
29804
29858
  interactiveStopEdition(this.env);
29805
29859
  }
29806
- if (shiftKey) {
29860
+ if (expandZone) {
29807
29861
  this.env.model.selection.setAnchorCorner(col, row);
29808
29862
  }
29809
- else if (ctrlKey) {
29863
+ else if (addZone) {
29810
29864
  this.env.model.selection.addCellToSelection(col, row);
29811
29865
  }
29812
29866
  else {
@@ -30609,7 +30663,7 @@ const XLSX_FORMATS_CONVERSION_MAP = {
30609
30663
  46: "hhhh:mm:ss",
30610
30664
  47: "hhhh:mm:ss",
30611
30665
  48: undefined,
30612
- 49: undefined,
30666
+ 49: PLAIN_TEXT_FORMAT,
30613
30667
  };
30614
30668
  /**
30615
30669
  * Mapping format index to format defined by default
@@ -34742,7 +34796,7 @@ class BordersPlugin extends CorePlugin {
34742
34796
  */
34743
34797
  function getBorderId(border) {
34744
34798
  for (let [key, value] of Object.entries(borders)) {
34745
- if (stringify(value) === stringify(border)) {
34799
+ if (deepEquals(value, border)) {
34746
34800
  return parseInt(key, 10);
34747
34801
  }
34748
34802
  }
@@ -35913,7 +35967,9 @@ class CellPlugin extends CorePlugin {
35913
35967
  }
35914
35968
  createLiteralCell(id, content, format, style) {
35915
35969
  const locale = this.getters.getLocale();
35916
- content = parseLiteral(content, locale).toString();
35970
+ if (format !== PLAIN_TEXT_FORMAT) {
35971
+ content = toString(parseLiteral(content, locale));
35972
+ }
35917
35973
  return {
35918
35974
  id,
35919
35975
  content,
@@ -40060,56 +40116,758 @@ class CompilationParametersBuilder {
40060
40116
  }
40061
40117
  }
40062
40118
 
40119
+ function quickselect(arr, k, left, right, compare) {
40120
+ quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
40121
+ }
40122
+
40123
+ function quickselectStep(arr, k, left, right, compare) {
40124
+
40125
+ while (right > left) {
40126
+ if (right - left > 600) {
40127
+ var n = right - left + 1;
40128
+ var m = k - left + 1;
40129
+ var z = Math.log(n);
40130
+ var s = 0.5 * Math.exp(2 * z / 3);
40131
+ var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
40132
+ var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
40133
+ var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
40134
+ quickselectStep(arr, k, newLeft, newRight, compare);
40135
+ }
40136
+
40137
+ var t = arr[k];
40138
+ var i = left;
40139
+ var j = right;
40140
+
40141
+ swap(arr, left, k);
40142
+ if (compare(arr[right], t) > 0) swap(arr, left, right);
40143
+
40144
+ while (i < j) {
40145
+ swap(arr, i, j);
40146
+ i++;
40147
+ j--;
40148
+ while (compare(arr[i], t) < 0) i++;
40149
+ while (compare(arr[j], t) > 0) j--;
40150
+ }
40151
+
40152
+ if (compare(arr[left], t) === 0) swap(arr, left, j);
40153
+ else {
40154
+ j++;
40155
+ swap(arr, j, right);
40156
+ }
40157
+
40158
+ if (j <= k) left = j + 1;
40159
+ if (k <= j) right = j - 1;
40160
+ }
40161
+ }
40162
+
40163
+ function swap(arr, i, j) {
40164
+ var tmp = arr[i];
40165
+ arr[i] = arr[j];
40166
+ arr[j] = tmp;
40167
+ }
40168
+
40169
+ function defaultCompare(a, b) {
40170
+ return a < b ? -1 : a > b ? 1 : 0;
40171
+ }
40172
+
40173
+ class RBush {
40174
+ constructor(maxEntries = 9) {
40175
+ // max entries in a node is 9 by default; min node fill is 40% for best performance
40176
+ this._maxEntries = Math.max(4, maxEntries);
40177
+ this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
40178
+ this.clear();
40179
+ }
40180
+
40181
+ all() {
40182
+ return this._all(this.data, []);
40183
+ }
40184
+
40185
+ search(bbox) {
40186
+ let node = this.data;
40187
+ const result = [];
40188
+
40189
+ if (!intersects(bbox, node)) return result;
40190
+
40191
+ const toBBox = this.toBBox;
40192
+ const nodesToSearch = [];
40193
+
40194
+ while (node) {
40195
+ for (let i = 0; i < node.children.length; i++) {
40196
+ const child = node.children[i];
40197
+ const childBBox = node.leaf ? toBBox(child) : child;
40198
+
40199
+ if (intersects(bbox, childBBox)) {
40200
+ if (node.leaf) result.push(child);
40201
+ else if (contains(bbox, childBBox)) this._all(child, result);
40202
+ else nodesToSearch.push(child);
40203
+ }
40204
+ }
40205
+ node = nodesToSearch.pop();
40206
+ }
40207
+
40208
+ return result;
40209
+ }
40210
+
40211
+ collides(bbox) {
40212
+ let node = this.data;
40213
+
40214
+ if (!intersects(bbox, node)) return false;
40215
+
40216
+ const nodesToSearch = [];
40217
+ while (node) {
40218
+ for (let i = 0; i < node.children.length; i++) {
40219
+ const child = node.children[i];
40220
+ const childBBox = node.leaf ? this.toBBox(child) : child;
40221
+
40222
+ if (intersects(bbox, childBBox)) {
40223
+ if (node.leaf || contains(bbox, childBBox)) return true;
40224
+ nodesToSearch.push(child);
40225
+ }
40226
+ }
40227
+ node = nodesToSearch.pop();
40228
+ }
40229
+
40230
+ return false;
40231
+ }
40232
+
40233
+ load(data) {
40234
+ if (!(data && data.length)) return this;
40235
+
40236
+ if (data.length < this._minEntries) {
40237
+ for (let i = 0; i < data.length; i++) {
40238
+ this.insert(data[i]);
40239
+ }
40240
+ return this;
40241
+ }
40242
+
40243
+ // recursively build the tree with the given data from scratch using OMT algorithm
40244
+ let node = this._build(data.slice(), 0, data.length - 1, 0);
40245
+
40246
+ if (!this.data.children.length) {
40247
+ // save as is if tree is empty
40248
+ this.data = node;
40249
+
40250
+ } else if (this.data.height === node.height) {
40251
+ // split root if trees have the same height
40252
+ this._splitRoot(this.data, node);
40253
+
40254
+ } else {
40255
+ if (this.data.height < node.height) {
40256
+ // swap trees if inserted one is bigger
40257
+ const tmpNode = this.data;
40258
+ this.data = node;
40259
+ node = tmpNode;
40260
+ }
40261
+
40262
+ // insert the small tree into the large tree at appropriate level
40263
+ this._insert(node, this.data.height - node.height - 1, true);
40264
+ }
40265
+
40266
+ return this;
40267
+ }
40268
+
40269
+ insert(item) {
40270
+ if (item) this._insert(item, this.data.height - 1);
40271
+ return this;
40272
+ }
40273
+
40274
+ clear() {
40275
+ this.data = createNode([]);
40276
+ return this;
40277
+ }
40278
+
40279
+ remove(item, equalsFn) {
40280
+ if (!item) return this;
40281
+
40282
+ let node = this.data;
40283
+ const bbox = this.toBBox(item);
40284
+ const path = [];
40285
+ const indexes = [];
40286
+ let i, parent, goingUp;
40287
+
40288
+ // depth-first iterative tree traversal
40289
+ while (node || path.length) {
40290
+
40291
+ if (!node) { // go up
40292
+ node = path.pop();
40293
+ parent = path[path.length - 1];
40294
+ i = indexes.pop();
40295
+ goingUp = true;
40296
+ }
40297
+
40298
+ if (node.leaf) { // check current node
40299
+ const index = findItem(item, node.children, equalsFn);
40300
+
40301
+ if (index !== -1) {
40302
+ // item found, remove the item and condense tree upwards
40303
+ node.children.splice(index, 1);
40304
+ path.push(node);
40305
+ this._condense(path);
40306
+ return this;
40307
+ }
40308
+ }
40309
+
40310
+ if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
40311
+ path.push(node);
40312
+ indexes.push(i);
40313
+ i = 0;
40314
+ parent = node;
40315
+ node = node.children[0];
40316
+
40317
+ } else if (parent) { // go right
40318
+ i++;
40319
+ node = parent.children[i];
40320
+ goingUp = false;
40321
+
40322
+ } else node = null; // nothing found
40323
+ }
40324
+
40325
+ return this;
40326
+ }
40327
+
40328
+ toBBox(item) { return item; }
40329
+
40330
+ compareMinX(a, b) { return a.minX - b.minX; }
40331
+ compareMinY(a, b) { return a.minY - b.minY; }
40332
+
40333
+ toJSON() { return this.data; }
40334
+
40335
+ fromJSON(data) {
40336
+ this.data = data;
40337
+ return this;
40338
+ }
40339
+
40340
+ _all(node, result) {
40341
+ const nodesToSearch = [];
40342
+ while (node) {
40343
+ if (node.leaf) result.push(...node.children);
40344
+ else nodesToSearch.push(...node.children);
40345
+
40346
+ node = nodesToSearch.pop();
40347
+ }
40348
+ return result;
40349
+ }
40350
+
40351
+ _build(items, left, right, height) {
40352
+
40353
+ const N = right - left + 1;
40354
+ let M = this._maxEntries;
40355
+ let node;
40356
+
40357
+ if (N <= M) {
40358
+ // reached leaf level; return leaf
40359
+ node = createNode(items.slice(left, right + 1));
40360
+ calcBBox(node, this.toBBox);
40361
+ return node;
40362
+ }
40363
+
40364
+ if (!height) {
40365
+ // target height of the bulk-loaded tree
40366
+ height = Math.ceil(Math.log(N) / Math.log(M));
40367
+
40368
+ // target number of root entries to maximize storage utilization
40369
+ M = Math.ceil(N / Math.pow(M, height - 1));
40370
+ }
40371
+
40372
+ node = createNode([]);
40373
+ node.leaf = false;
40374
+ node.height = height;
40375
+
40376
+ // split the items into M mostly square tiles
40377
+
40378
+ const N2 = Math.ceil(N / M);
40379
+ const N1 = N2 * Math.ceil(Math.sqrt(M));
40380
+
40381
+ multiSelect(items, left, right, N1, this.compareMinX);
40382
+
40383
+ for (let i = left; i <= right; i += N1) {
40384
+
40385
+ const right2 = Math.min(i + N1 - 1, right);
40386
+
40387
+ multiSelect(items, i, right2, N2, this.compareMinY);
40388
+
40389
+ for (let j = i; j <= right2; j += N2) {
40390
+
40391
+ const right3 = Math.min(j + N2 - 1, right2);
40392
+
40393
+ // pack each entry recursively
40394
+ node.children.push(this._build(items, j, right3, height - 1));
40395
+ }
40396
+ }
40397
+
40398
+ calcBBox(node, this.toBBox);
40399
+
40400
+ return node;
40401
+ }
40402
+
40403
+ _chooseSubtree(bbox, node, level, path) {
40404
+ while (true) {
40405
+ path.push(node);
40406
+
40407
+ if (node.leaf || path.length - 1 === level) break;
40408
+
40409
+ let minArea = Infinity;
40410
+ let minEnlargement = Infinity;
40411
+ let targetNode;
40412
+
40413
+ for (let i = 0; i < node.children.length; i++) {
40414
+ const child = node.children[i];
40415
+ const area = bboxArea(child);
40416
+ const enlargement = enlargedArea(bbox, child) - area;
40417
+
40418
+ // choose entry with the least area enlargement
40419
+ if (enlargement < minEnlargement) {
40420
+ minEnlargement = enlargement;
40421
+ minArea = area < minArea ? area : minArea;
40422
+ targetNode = child;
40423
+
40424
+ } else if (enlargement === minEnlargement) {
40425
+ // otherwise choose one with the smallest area
40426
+ if (area < minArea) {
40427
+ minArea = area;
40428
+ targetNode = child;
40429
+ }
40430
+ }
40431
+ }
40432
+
40433
+ node = targetNode || node.children[0];
40434
+ }
40435
+
40436
+ return node;
40437
+ }
40438
+
40439
+ _insert(item, level, isNode) {
40440
+ const bbox = isNode ? item : this.toBBox(item);
40441
+ const insertPath = [];
40442
+
40443
+ // find the best node for accommodating the item, saving all nodes along the path too
40444
+ const node = this._chooseSubtree(bbox, this.data, level, insertPath);
40445
+
40446
+ // put the item into the node
40447
+ node.children.push(item);
40448
+ extend(node, bbox);
40449
+
40450
+ // split on node overflow; propagate upwards if necessary
40451
+ while (level >= 0) {
40452
+ if (insertPath[level].children.length > this._maxEntries) {
40453
+ this._split(insertPath, level);
40454
+ level--;
40455
+ } else break;
40456
+ }
40457
+
40458
+ // adjust bboxes along the insertion path
40459
+ this._adjustParentBBoxes(bbox, insertPath, level);
40460
+ }
40461
+
40462
+ // split overflowed node into two
40463
+ _split(insertPath, level) {
40464
+ const node = insertPath[level];
40465
+ const M = node.children.length;
40466
+ const m = this._minEntries;
40467
+
40468
+ this._chooseSplitAxis(node, m, M);
40469
+
40470
+ const splitIndex = this._chooseSplitIndex(node, m, M);
40471
+
40472
+ const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
40473
+ newNode.height = node.height;
40474
+ newNode.leaf = node.leaf;
40475
+
40476
+ calcBBox(node, this.toBBox);
40477
+ calcBBox(newNode, this.toBBox);
40478
+
40479
+ if (level) insertPath[level - 1].children.push(newNode);
40480
+ else this._splitRoot(node, newNode);
40481
+ }
40482
+
40483
+ _splitRoot(node, newNode) {
40484
+ // split root node
40485
+ this.data = createNode([node, newNode]);
40486
+ this.data.height = node.height + 1;
40487
+ this.data.leaf = false;
40488
+ calcBBox(this.data, this.toBBox);
40489
+ }
40490
+
40491
+ _chooseSplitIndex(node, m, M) {
40492
+ let index;
40493
+ let minOverlap = Infinity;
40494
+ let minArea = Infinity;
40495
+
40496
+ for (let i = m; i <= M - m; i++) {
40497
+ const bbox1 = distBBox(node, 0, i, this.toBBox);
40498
+ const bbox2 = distBBox(node, i, M, this.toBBox);
40499
+
40500
+ const overlap = intersectionArea(bbox1, bbox2);
40501
+ const area = bboxArea(bbox1) + bboxArea(bbox2);
40502
+
40503
+ // choose distribution with minimum overlap
40504
+ if (overlap < minOverlap) {
40505
+ minOverlap = overlap;
40506
+ index = i;
40507
+
40508
+ minArea = area < minArea ? area : minArea;
40509
+
40510
+ } else if (overlap === minOverlap) {
40511
+ // otherwise choose distribution with minimum area
40512
+ if (area < minArea) {
40513
+ minArea = area;
40514
+ index = i;
40515
+ }
40516
+ }
40517
+ }
40518
+
40519
+ return index || M - m;
40520
+ }
40521
+
40522
+ // sorts node children by the best axis for split
40523
+ _chooseSplitAxis(node, m, M) {
40524
+ const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
40525
+ const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
40526
+ const xMargin = this._allDistMargin(node, m, M, compareMinX);
40527
+ const yMargin = this._allDistMargin(node, m, M, compareMinY);
40528
+
40529
+ // if total distributions margin value is minimal for x, sort by minX,
40530
+ // otherwise it's already sorted by minY
40531
+ if (xMargin < yMargin) node.children.sort(compareMinX);
40532
+ }
40533
+
40534
+ // total margin of all possible split distributions where each node is at least m full
40535
+ _allDistMargin(node, m, M, compare) {
40536
+ node.children.sort(compare);
40537
+
40538
+ const toBBox = this.toBBox;
40539
+ const leftBBox = distBBox(node, 0, m, toBBox);
40540
+ const rightBBox = distBBox(node, M - m, M, toBBox);
40541
+ let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
40542
+
40543
+ for (let i = m; i < M - m; i++) {
40544
+ const child = node.children[i];
40545
+ extend(leftBBox, node.leaf ? toBBox(child) : child);
40546
+ margin += bboxMargin(leftBBox);
40547
+ }
40548
+
40549
+ for (let i = M - m - 1; i >= m; i--) {
40550
+ const child = node.children[i];
40551
+ extend(rightBBox, node.leaf ? toBBox(child) : child);
40552
+ margin += bboxMargin(rightBBox);
40553
+ }
40554
+
40555
+ return margin;
40556
+ }
40557
+
40558
+ _adjustParentBBoxes(bbox, path, level) {
40559
+ // adjust bboxes along the given tree path
40560
+ for (let i = level; i >= 0; i--) {
40561
+ extend(path[i], bbox);
40562
+ }
40563
+ }
40564
+
40565
+ _condense(path) {
40566
+ // go through the path, removing empty nodes and updating bboxes
40567
+ for (let i = path.length - 1, siblings; i >= 0; i--) {
40568
+ if (path[i].children.length === 0) {
40569
+ if (i > 0) {
40570
+ siblings = path[i - 1].children;
40571
+ siblings.splice(siblings.indexOf(path[i]), 1);
40572
+
40573
+ } else this.clear();
40574
+
40575
+ } else calcBBox(path[i], this.toBBox);
40576
+ }
40577
+ }
40578
+ }
40579
+
40580
+ function findItem(item, items, equalsFn) {
40581
+ if (!equalsFn) return items.indexOf(item);
40582
+
40583
+ for (let i = 0; i < items.length; i++) {
40584
+ if (equalsFn(item, items[i])) return i;
40585
+ }
40586
+ return -1;
40587
+ }
40588
+
40589
+ // calculate node's bbox from bboxes of its children
40590
+ function calcBBox(node, toBBox) {
40591
+ distBBox(node, 0, node.children.length, toBBox, node);
40592
+ }
40593
+
40594
+ // min bounding rectangle of node children from k to p-1
40595
+ function distBBox(node, k, p, toBBox, destNode) {
40596
+ if (!destNode) destNode = createNode(null);
40597
+ destNode.minX = Infinity;
40598
+ destNode.minY = Infinity;
40599
+ destNode.maxX = -Infinity;
40600
+ destNode.maxY = -Infinity;
40601
+
40602
+ for (let i = k; i < p; i++) {
40603
+ const child = node.children[i];
40604
+ extend(destNode, node.leaf ? toBBox(child) : child);
40605
+ }
40606
+
40607
+ return destNode;
40608
+ }
40609
+
40610
+ function extend(a, b) {
40611
+ a.minX = Math.min(a.minX, b.minX);
40612
+ a.minY = Math.min(a.minY, b.minY);
40613
+ a.maxX = Math.max(a.maxX, b.maxX);
40614
+ a.maxY = Math.max(a.maxY, b.maxY);
40615
+ return a;
40616
+ }
40617
+
40618
+ function compareNodeMinX(a, b) { return a.minX - b.minX; }
40619
+ function compareNodeMinY(a, b) { return a.minY - b.minY; }
40620
+
40621
+ function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
40622
+ function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
40623
+
40624
+ function enlargedArea(a, b) {
40625
+ return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
40626
+ (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
40627
+ }
40628
+
40629
+ function intersectionArea(a, b) {
40630
+ const minX = Math.max(a.minX, b.minX);
40631
+ const minY = Math.max(a.minY, b.minY);
40632
+ const maxX = Math.min(a.maxX, b.maxX);
40633
+ const maxY = Math.min(a.maxY, b.maxY);
40634
+
40635
+ return Math.max(0, maxX - minX) *
40636
+ Math.max(0, maxY - minY);
40637
+ }
40638
+
40639
+ function contains(a, b) {
40640
+ return a.minX <= b.minX &&
40641
+ a.minY <= b.minY &&
40642
+ b.maxX <= a.maxX &&
40643
+ b.maxY <= a.maxY;
40644
+ }
40645
+
40646
+ function intersects(a, b) {
40647
+ return b.minX <= a.maxX &&
40648
+ b.minY <= a.maxY &&
40649
+ b.maxX >= a.minX &&
40650
+ b.maxY >= a.minY;
40651
+ }
40652
+
40653
+ function createNode(children) {
40654
+ return {
40655
+ children,
40656
+ height: 1,
40657
+ leaf: true,
40658
+ minX: Infinity,
40659
+ minY: Infinity,
40660
+ maxX: -Infinity,
40661
+ maxY: -Infinity
40662
+ };
40663
+ }
40664
+
40665
+ // sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
40666
+ // combines selection algorithm with binary divide & conquer approach
40667
+
40668
+ function multiSelect(arr, left, right, n, compare) {
40669
+ const stack = [left, right];
40670
+
40671
+ while (stack.length) {
40672
+ right = stack.pop();
40673
+ left = stack.pop();
40674
+
40675
+ if (right - left <= n) continue;
40676
+
40677
+ const mid = left + Math.ceil((right - left) / n / 2) * n;
40678
+ quickselect(arr, mid, left, right, compare);
40679
+
40680
+ stack.push(left, mid, mid, right);
40681
+ }
40682
+ }
40683
+
40684
+ /**
40685
+ * R-Tree Data Structure
40686
+ *
40687
+ * R-Tree is a spatial data structure used for efficient indexing and querying
40688
+ * of multi-dimensional objects, particularly in geometric and spatial applications.
40689
+ *
40690
+ * It organizes objects into a tree hierarchy, grouping nearby objects together
40691
+ * in bounding boxes. Each node in the tree represents a bounding box that
40692
+ * contains its child nodes or leaf objects. This hierarchical structure allows
40693
+ * for faster spatial queries.
40694
+ *
40695
+ * @see https://en.wikipedia.org/wiki/R-tree
40696
+ *
40697
+ * Consider a 2D Space with four zones: A, B, C, D
40698
+ * +--------------------------+
40699
+ * | |
40700
+ * | +---+ +-------+ |
40701
+ * | | A | | B | |
40702
+ * | +---+ +-------+ |
40703
+ * | |
40704
+ * | |
40705
+ * | +---+ |
40706
+ * | | C | |
40707
+ * | +---+ |
40708
+ * | +-----------+ |
40709
+ * | | D | |
40710
+ * | +-----------+ |
40711
+ * | |
40712
+ * +--------------------------+
40713
+ *
40714
+ * It groups together zones that are spatially close into a minimum bounding box.
40715
+ * For example, A and B are grouped together in rectangle R1, and C and D are grouped
40716
+ * in R2.
40717
+ *
40718
+ * R0
40719
+ * +--------------------------+
40720
+ * | R1 |
40721
+ * | +-----------------+ |
40722
+ * | | A | | B | |
40723
+ * | +-----------------+ |
40724
+ * | |
40725
+ * | R2 |
40726
+ * | +---+---+---+ |
40727
+ * | | | C | | |
40728
+ * | | +---+ | |
40729
+ * | +-----------+ |
40730
+ * | | D | |
40731
+ * | +-----------+ |
40732
+ * | |
40733
+ * +--------------------------+
40734
+ *
40735
+ * The tree would look like this:
40736
+ * R0
40737
+ * / \
40738
+ * / \
40739
+ * R1 R2
40740
+ * | |
40741
+ * A,B C,D
40742
+
40743
+ * Choosing how to group the zones is crucial for the performance of the tree.
40744
+ * Key considerations include avoiding excessive empty space coverage and minimizing overlap
40745
+ * to reduce the number of subtrees processed during searches.
40746
+ *
40747
+ * Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
40748
+ * which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
40749
+ * the choice cannot be made based on this criterion due to the same enlargement for different groupings,
40750
+ * we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
40751
+ *
40752
+ * This implementation is tailored for spreadsheet use, indexing objects associated
40753
+ * with a zone and a sheet.
40754
+ *
40755
+ * It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
40756
+ * @see https://github.com/mourner/rbush
40757
+ */
40758
+ class SpreadsheetRTree {
40759
+ /**
40760
+ * One 2D R-tree per sheet
40761
+ */
40762
+ rTrees = {};
40763
+ /**
40764
+ * Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
40765
+ * faster than inserting items one by one. After bulk loading (bulk insertion into
40766
+ * an empty tree), subsequent query performance is also ~20-30% better.
40767
+ */
40768
+ constructor(items = []) {
40769
+ const rangesPerSheet = {};
40770
+ for (const item of items) {
40771
+ const sheetId = item.boundingBox.sheetId;
40772
+ if (!rangesPerSheet[sheetId]) {
40773
+ rangesPerSheet[sheetId] = [];
40774
+ }
40775
+ rangesPerSheet[sheetId].push(item);
40776
+ }
40777
+ for (const sheetId in rangesPerSheet) {
40778
+ this.rTrees[sheetId] = new ZoneRBush();
40779
+ this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
40780
+ }
40781
+ }
40782
+ insert(item) {
40783
+ const sheetId = item.boundingBox.sheetId;
40784
+ if (!this.rTrees[sheetId]) {
40785
+ this.rTrees[sheetId] = new ZoneRBush();
40786
+ }
40787
+ this.rTrees[sheetId].insert(item);
40788
+ }
40789
+ search({ zone, sheetId }) {
40790
+ if (!this.rTrees[sheetId]) {
40791
+ return [];
40792
+ }
40793
+ return this.rTrees[sheetId].search({
40794
+ minX: zone.left,
40795
+ minY: zone.top,
40796
+ maxX: zone.right,
40797
+ maxY: zone.bottom,
40798
+ });
40799
+ }
40800
+ remove(item) {
40801
+ const sheetId = item.boundingBox.sheetId;
40802
+ if (!this.rTrees[sheetId]) {
40803
+ return;
40804
+ }
40805
+ this.rTrees[sheetId].remove(item, deepEquals);
40806
+ }
40807
+ }
40808
+ /**
40809
+ * RBush extension to use zones as bounding boxes
40810
+ */
40811
+ class ZoneRBush extends RBush {
40812
+ toBBox({ boundingBox }) {
40813
+ const zone = boundingBox.zone;
40814
+ return {
40815
+ minX: zone.left,
40816
+ minY: zone.top,
40817
+ maxX: zone.right,
40818
+ maxY: zone.bottom,
40819
+ };
40820
+ }
40821
+ compareMinX(a, b) {
40822
+ return a.boundingBox.zone.left - b.boundingBox.zone.left;
40823
+ }
40824
+ compareMinY(a, b) {
40825
+ return a.boundingBox.zone.top - b.boundingBox.zone.top;
40826
+ }
40827
+ }
40828
+
40063
40829
  /**
40064
- * This class is an implementation of a dependency Graph.
40830
+ * Implementation of a dependency Graph.
40065
40831
  * The graph is used to evaluate the cells in the correct
40066
40832
  * order, and should be updated each time a cell's content is modified
40067
40833
  *
40834
+ * It uses an R-Tree data structure to efficiently find dependent cells.
40068
40835
  */
40069
40836
  class FormulaDependencyGraph {
40070
- /**
40071
- * Internal structure:
40072
- * - key: a cell position (encoded as an integer)
40073
- * - value: a set of cell positions that depends on the key
40074
- *
40075
- * Given
40076
- * - A1:"= B1 + SQRT(B2)"
40077
- * - C1:"= B1";
40078
- * - C2:"= C1"
40079
- *
40080
- * we will have something like:
40081
- * - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
40082
- * - B2 ---> (A1) meaning A1 depends on B2
40083
- * - C1 ---> (C2) meaning C2 depends on C1
40084
- */
40085
- inverseDependencies = new Map();
40837
+ encoder;
40086
40838
  dependencies = new Map();
40839
+ rTree;
40840
+ constructor(encoder, data = []) {
40841
+ this.encoder = encoder;
40842
+ this.rTree = new SpreadsheetRTree(data);
40843
+ }
40087
40844
  removeAllDependencies(formulaPositionId) {
40088
- const dependencies = this.dependencies.get(formulaPositionId);
40089
- if (!dependencies) {
40845
+ const ranges = this.dependencies.get(formulaPositionId);
40846
+ if (!ranges) {
40090
40847
  return;
40091
40848
  }
40092
- for (const dependency of dependencies) {
40093
- this.inverseDependencies.get(dependency)?.delete(formulaPositionId);
40849
+ for (const range of ranges) {
40850
+ this.rTree.remove(range);
40094
40851
  }
40095
40852
  this.dependencies.delete(formulaPositionId);
40096
40853
  }
40097
40854
  addDependencies(formulaPositionId, dependencies) {
40098
- for (const dependency of dependencies) {
40099
- const inverseDependencies = this.inverseDependencies.get(dependency);
40100
- if (inverseDependencies) {
40101
- inverseDependencies.add(formulaPositionId);
40102
- }
40103
- else {
40104
- this.inverseDependencies.set(dependency, new Set([formulaPositionId]));
40105
- }
40855
+ const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
40856
+ data: formulaPositionId,
40857
+ boundingBox: {
40858
+ zone,
40859
+ sheetId,
40860
+ },
40861
+ }));
40862
+ for (const item of rTreeItems) {
40863
+ this.rTree.insert(item);
40106
40864
  }
40107
40865
  const existingDependencies = this.dependencies.get(formulaPositionId);
40108
40866
  if (existingDependencies) {
40109
- existingDependencies.push(...dependencies);
40867
+ existingDependencies.push(...rTreeItems);
40110
40868
  }
40111
40869
  else {
40112
- this.dependencies.set(formulaPositionId, dependencies);
40870
+ this.dependencies.set(formulaPositionId, rTreeItems);
40113
40871
  }
40114
40872
  }
40115
40873
  /**
@@ -40117,20 +40875,20 @@ class FormulaDependencyGraph {
40117
40875
  * in the correct order they should be evaluated.
40118
40876
  * This is called a topological ordering (excluding cycles)
40119
40877
  */
40120
- getCellsDependingOn(positionIds) {
40878
+ getCellsDependingOn(ranges) {
40121
40879
  const visited = new JetSet();
40122
- const queue = Array.from(positionIds).reverse();
40880
+ const queue = Array.from(ranges).reverse();
40123
40881
  while (queue.length > 0) {
40124
- const node = queue.pop();
40125
- visited.add(node);
40126
- const adjacentNodes = this.inverseDependencies.get(node) || new Set();
40127
- for (const adjacentNode of adjacentNodes) {
40128
- if (!visited.has(adjacentNode)) {
40129
- queue.push(adjacentNode);
40882
+ const range = queue.pop();
40883
+ visited.add(...this.encoder.encodeBoundingBox(range));
40884
+ const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
40885
+ for (const positionId of impactedPositionIds) {
40886
+ if (!visited.has(positionId)) {
40887
+ queue.push(this.encoder.decodeToBoundingBox(positionId));
40130
40888
  }
40131
40889
  }
40132
40890
  }
40133
- visited.delete(...positionIds);
40891
+ visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40134
40892
  return visited;
40135
40893
  }
40136
40894
  }
@@ -40218,9 +40976,9 @@ class Evaluator {
40218
40976
  context;
40219
40977
  getters;
40220
40978
  compilationParams;
40221
- positionEncoder = new PositionBitsEncoder();
40979
+ encoder = new PositionBitsEncoder();
40222
40980
  evaluatedCells = new Map();
40223
- formulaDependencies = lazy(new FormulaDependencyGraph());
40981
+ formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
40224
40982
  blockedArrayFormulas = new Set();
40225
40983
  spreadingRelations = new SpreadingRelation();
40226
40984
  constructor(context, getters) {
@@ -40229,23 +40987,23 @@ class Evaluator {
40229
40987
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
40230
40988
  }
40231
40989
  getEvaluatedCell(position) {
40232
- return (this.evaluatedCells.get(this.encodePosition(position)) ||
40990
+ return (this.evaluatedCells.get(this.encoder.encode(position)) ||
40233
40991
  createEvaluatedCell("", { locale: this.getters.getLocale() }));
40234
40992
  }
40235
40993
  getSpreadPositionsOf(position) {
40236
- const positionId = this.encodePosition(position);
40994
+ const positionId = this.encoder.encode(position);
40237
40995
  if (!this.spreadingRelations.isArrayFormula(positionId)) {
40238
40996
  return [];
40239
40997
  }
40240
- return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.decodePosition.bind(this));
40998
+ return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
40241
40999
  }
40242
41000
  getArrayFormulaSpreadingOn(position) {
40243
- const positionId = this.encodePosition(position);
41001
+ const positionId = this.encoder.encode(position);
40244
41002
  const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
40245
- return formulaPosition !== undefined ? this.decodePosition(formulaPosition) : undefined;
41003
+ return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
40246
41004
  }
40247
41005
  getEvaluatedPositions() {
40248
- return [...this.evaluatedCells.keys()].map(this.decodePosition.bind(this));
41006
+ return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
40249
41007
  }
40250
41008
  getArrayFormulaSpreadingOnId(positionId) {
40251
41009
  if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
@@ -40255,7 +41013,7 @@ class Evaluator {
40255
41013
  return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
40256
41014
  }
40257
41015
  updateDependencies(position) {
40258
- const positionId = this.encodePosition(position);
41016
+ const positionId = this.encoder.encode(position);
40259
41017
  this.formulaDependencies().removeAllDependencies(positionId);
40260
41018
  const dependencies = this.getDirectDependencies(positionId);
40261
41019
  this.formulaDependencies().addDependencies(positionId, dependencies);
@@ -40265,12 +41023,12 @@ class Evaluator {
40265
41023
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
40266
41024
  }
40267
41025
  evaluateCells(positions) {
40268
- const cells = positions.map(this.encodePosition.bind(this));
41026
+ const cells = positions.map((p) => this.encoder.encode(p));
40269
41027
  const cellsToCompute = new JetSet(cells);
40270
- const arrayFormulas = this.getArrayFormulasImpactedByChangesOf(cells);
41028
+ const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
40271
41029
  cellsToCompute.add(...this.getCellsDependingOn(cells));
40272
- cellsToCompute.add(...arrayFormulas);
40273
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulas));
41030
+ cellsToCompute.add(...arrayFormulasPositionIds);
41031
+ cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
40274
41032
  this.evaluate(cellsToCompute);
40275
41033
  }
40276
41034
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -40293,12 +41051,14 @@ class Evaluator {
40293
41051
  this.blockedArrayFormulas = new Set();
40294
41052
  this.spreadingRelations = new SpreadingRelation();
40295
41053
  this.formulaDependencies = lazy(() => {
40296
- const dependencyGraph = new FormulaDependencyGraph();
40297
- for (const positionId of this.getAllCells()) {
40298
- const dependencies = this.getDirectDependencies(positionId);
40299
- dependencyGraph.addDependencies(positionId, dependencies);
40300
- }
40301
- return dependencyGraph;
41054
+ const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
41055
+ data: positionId,
41056
+ boundingBox: {
41057
+ zone: range.zone,
41058
+ sheetId: range.sheetId,
41059
+ },
41060
+ })));
41061
+ return new FormulaDependencyGraph(this.encoder, dependencies);
40302
41062
  });
40303
41063
  }
40304
41064
  evaluateAllCells() {
@@ -40320,7 +41080,7 @@ class Evaluator {
40320
41080
  for (const sheetId of this.getters.getSheetIds()) {
40321
41081
  const cellIds = this.getters.getCells(sheetId);
40322
41082
  for (const cellId in cellIds) {
40323
- positionIds.add(this.encodePosition(this.getters.getCellPosition(cellId)));
41083
+ positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
40324
41084
  }
40325
41085
  }
40326
41086
  return positionIds;
@@ -40365,7 +41125,7 @@ class Evaluator {
40365
41125
  if (!this.blockedArrayFormulas.has(positionId)) {
40366
41126
  this.invalidateSpreading(positionId);
40367
41127
  }
40368
- const cellPosition = this.decodePosition(positionId);
41128
+ const cellPosition = this.encoder.decode(positionId);
40369
41129
  const cell = this.getters.getCell(cellPosition);
40370
41130
  if (cell === undefined) {
40371
41131
  return createEvaluatedCell("", { locale: this.getters.getLocale() });
@@ -40388,7 +41148,7 @@ class Evaluator {
40388
41148
  }
40389
41149
  }
40390
41150
  computeAndSave(position) {
40391
- const positionId = this.encodePosition(position);
41151
+ const positionId = this.encoder.encode(position);
40392
41152
  const evaluatedCell = this.computeCell(positionId);
40393
41153
  if (!this.evaluatedCells.has(positionId)) {
40394
41154
  this.setEvaluatedCell(positionId, evaluatedCell);
@@ -40443,15 +41203,15 @@ class Evaluator {
40443
41203
  throw new Error(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
40444
41204
  }
40445
41205
  updateSpreadRelation({ sheetId, col, row, }) {
40446
- const arrayFormulaPositionId = this.encodePosition({ sheetId, col, row });
41206
+ const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
40447
41207
  return (i, j) => {
40448
41208
  const position = { sheetId, col: i + col, row: j + row };
40449
- const resultPositionId = this.encodePosition(position);
41209
+ const resultPositionId = this.encoder.encode(position);
40450
41210
  this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
40451
41211
  };
40452
41212
  }
40453
41213
  checkCollision({ sheetId, col, row }) {
40454
- const formulaPositionId = this.encodePosition({ sheetId, col, row });
41214
+ const formulaPositionId = this.encoder.encode({ sheetId, col, row });
40455
41215
  return (i, j) => {
40456
41216
  const position = { sheetId: sheetId, col: i + col, row: j + row };
40457
41217
  const rawCell = this.getters.getCell(position);
@@ -40472,7 +41232,7 @@ class Evaluator {
40472
41232
  format: format || matrixResult[i][j]?.format,
40473
41233
  locale: this.getters.getLocale(),
40474
41234
  });
40475
- const positionId = this.encodePosition(position);
41235
+ const positionId = this.encoder.encode(position);
40476
41236
  this.setEvaluatedCell(positionId, evaluatedCell);
40477
41237
  // check if formula dependencies present in the spread zone
40478
41238
  // if so, they need to be recomputed
@@ -40504,29 +41264,17 @@ class Evaluator {
40504
41264
  if (!cell?.isFormula) {
40505
41265
  return [];
40506
41266
  }
40507
- const dependencies = [];
40508
- for (const range of cell.compiledFormula.dependencies) {
40509
- if (range.invalidSheetName || range.invalidXc) {
40510
- continue;
40511
- }
40512
- const sheetId = range.sheetId;
40513
- forEachPositionsInZone(range.zone, (col, row) => {
40514
- dependencies.push(this.encodePosition({ sheetId, col, row }));
40515
- });
40516
- }
40517
- return dependencies;
41267
+ return cell.compiledFormula.dependencies;
40518
41268
  }
40519
41269
  getCellsDependingOn(positionIds) {
40520
- return this.formulaDependencies().getCellsDependingOn(positionIds);
41270
+ const ranges = [];
41271
+ for (const positionId of positionIds) {
41272
+ ranges.push(this.encoder.decodeToBoundingBox(positionId));
41273
+ }
41274
+ return this.formulaDependencies().getCellsDependingOn(ranges);
40521
41275
  }
40522
41276
  getCell(positionId) {
40523
- return this.getters.getCell(this.decodePosition(positionId));
40524
- }
40525
- encodePosition(position) {
40526
- return this.positionEncoder.encode(position);
40527
- }
40528
- decodePosition(positionId) {
40529
- return this.positionEncoder.decode(positionId);
41277
+ return this.getters.getCell(this.encoder.decode(positionId));
40530
41278
  }
40531
41279
  }
40532
41280
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -40582,6 +41330,13 @@ class PositionBitsEncoder {
40582
41330
  encode({ sheetId, col, row }) {
40583
41331
  return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
40584
41332
  }
41333
+ encodeBoundingBox({ sheetId, zone }) {
41334
+ const positions = [];
41335
+ forEachPositionsInZone(zone, (col, row) => {
41336
+ positions.push(this.encode({ sheetId, col, row }));
41337
+ });
41338
+ return positions;
41339
+ }
40585
41340
  decode(id) {
40586
41341
  // keep only the last 21 bits by AND-ing the bit sequence with 21 ones
40587
41342
  const row = Number(id & 2097151n);
@@ -40589,6 +41344,10 @@ class PositionBitsEncoder {
40589
41344
  const sheetId = this.decodeSheet(id >> 42n);
40590
41345
  return { sheetId, col, row };
40591
41346
  }
41347
+ decodeToBoundingBox(id) {
41348
+ const { sheetId, col, row } = this.decode(id);
41349
+ return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
41350
+ }
40592
41351
  encodeSheet(sheetId) {
40593
41352
  const sheetKey = this.sheetMapping[sheetId];
40594
41353
  if (sheetKey === undefined) {
@@ -40771,7 +41530,12 @@ class EvaluationPlugin extends UIPlugin {
40771
41530
  // Getters
40772
41531
  // ---------------------------------------------------------------------------
40773
41532
  evaluateFormula(sheetId, formulaString) {
40774
- return this.evaluator.evaluateFormula(sheetId, formulaString);
41533
+ try {
41534
+ return this.evaluator.evaluateFormula(sheetId, formulaString);
41535
+ }
41536
+ catch (error) {
41537
+ return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
41538
+ }
40775
41539
  }
40776
41540
  /**
40777
41541
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -41040,7 +41804,7 @@ class CustomColorsPlugin extends UIPlugin {
41040
41804
  }
41041
41805
 
41042
41806
  class EvaluationChartPlugin extends UIPlugin {
41043
- static getters = ["getChartRuntime", "getBackgroundOfSingleCellChart"];
41807
+ static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
41044
41808
  charts = {};
41045
41809
  createRuntimeChart = chartRuntimeFactory(this.getters);
41046
41810
  handle(cmd) {
@@ -41078,25 +41842,26 @@ class EvaluationChartPlugin extends UIPlugin {
41078
41842
  return this.charts[figureId];
41079
41843
  }
41080
41844
  /**
41081
- * Get the background color of a chart based on the color of the first cell of the main range
41082
- * of the chart. In order of priority, it will return :
41083
- *
41084
- * - the chart background color if one is defined
41085
- * - the fill color of the cell if one is defined
41086
- * - the fill color of the cell from conditional formats if one is defined
41087
- * - the default chart color if no other color is defined
41845
+ * Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
41088
41846
  */
41089
- getBackgroundOfSingleCellChart(chartBackground, mainRange) {
41847
+ getStyleOfSingleCellChart(chartBackground, mainRange) {
41090
41848
  if (chartBackground)
41091
- return chartBackground;
41849
+ return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
41092
41850
  if (!mainRange) {
41093
- return BACKGROUND_CHART_COLOR;
41851
+ return {
41852
+ background: BACKGROUND_CHART_COLOR,
41853
+ fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
41854
+ };
41094
41855
  }
41095
41856
  const col = mainRange.zone.left;
41096
41857
  const row = mainRange.zone.top;
41097
41858
  const sheetId = mainRange.sheetId;
41098
41859
  const style = this.getters.getCellComputedStyle({ sheetId, col, row });
41099
- return style.fillColor || BACKGROUND_CHART_COLOR;
41860
+ const background = style.fillColor || BACKGROUND_CHART_COLOR;
41861
+ return {
41862
+ background,
41863
+ fontColor: style.textColor || chartFontColor(background),
41864
+ };
41100
41865
  }
41101
41866
  exportForExcel(data) {
41102
41867
  for (const sheet of data.sheets) {
@@ -41207,45 +41972,40 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
41207
41972
  getComputedStyles(sheetId) {
41208
41973
  const computedStyle = {};
41209
41974
  for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
41210
- try {
41211
- switch (cf.rule.type) {
41212
- case "ColorScaleRule":
41213
- for (let range of cf.ranges) {
41214
- this.applyColorScale(sheetId, range, cf.rule, computedStyle);
41215
- }
41216
- break;
41217
- case "CellIsRule":
41218
- const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
41219
- for (let ref of cf.ranges) {
41220
- const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
41221
- for (let row = zone.top; row <= zone.bottom; row++) {
41222
- for (let col = zone.left; col <= zone.right; col++) {
41223
- const predicate = this.rulePredicate[cf.rule.type];
41224
- const target = { sheetId, col, row };
41225
- const values = cf.rule.values.map((value, i) => {
41226
- const compiledFormula = formulas[i];
41227
- if (compiledFormula) {
41228
- return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
41229
- ...compiledFormula,
41230
- dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
41231
- });
41232
- }
41233
- return value;
41234
- });
41235
- if (predicate && predicate(target, { ...cf.rule, values })) {
41236
- if (!computedStyle[col])
41237
- computedStyle[col] = [];
41238
- // we must combine all the properties of all the CF rules applied to the given cell
41239
- computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
41975
+ switch (cf.rule.type) {
41976
+ case "ColorScaleRule":
41977
+ for (let range of cf.ranges) {
41978
+ this.applyColorScale(sheetId, range, cf.rule, computedStyle);
41979
+ }
41980
+ break;
41981
+ case "CellIsRule":
41982
+ const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
41983
+ for (let ref of cf.ranges) {
41984
+ const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
41985
+ for (let row = zone.top; row <= zone.bottom; row++) {
41986
+ for (let col = zone.left; col <= zone.right; col++) {
41987
+ const predicate = this.rulePredicate[cf.rule.type];
41988
+ const target = { sheetId, col, row };
41989
+ const values = cf.rule.values.map((value, i) => {
41990
+ const compiledFormula = formulas[i];
41991
+ if (compiledFormula) {
41992
+ return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
41993
+ ...compiledFormula,
41994
+ dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
41995
+ });
41240
41996
  }
41997
+ return value;
41998
+ });
41999
+ if (predicate && predicate(target, { ...cf.rule, values })) {
42000
+ if (!computedStyle[col])
42001
+ computedStyle[col] = [];
42002
+ // we must combine all the properties of all the CF rules applied to the given cell
42003
+ computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
41241
42004
  }
41242
42005
  }
41243
42006
  }
41244
- break;
41245
- }
41246
- }
41247
- catch (_) {
41248
- // we don't care about the errors within the evaluation of a rule
42007
+ }
42008
+ break;
41249
42009
  }
41250
42010
  }
41251
42011
  return computedStyle;
@@ -41856,11 +42616,6 @@ class AutofillPlugin extends UIPlugin {
41856
42616
  return "Success" /* CommandResult.Success */;
41857
42617
  }
41858
42618
  return "InvalidAutofillSelection" /* CommandResult.InvalidAutofillSelection */;
41859
- case "AUTOFILL_AUTO":
41860
- const zone = this.getters.getSelectedZone();
41861
- return zone.top === zone.bottom
41862
- ? "Success" /* CommandResult.Success */
41863
- : "CancelledForUnknownReason" /* CommandResult.CancelledForUnknownReason */;
41864
42619
  }
41865
42620
  return "Success" /* CommandResult.Success */;
41866
42621
  }
@@ -42017,7 +42772,7 @@ class AutofillPlugin extends UIPlugin {
42017
42772
  let row = zone.bottom;
42018
42773
  if (col > 0) {
42019
42774
  let leftPosition = { sheetId, col: col - 1, row };
42020
- while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
42775
+ while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
42021
42776
  this.getters.getCell(leftPosition)?.content) {
42022
42777
  row += 1;
42023
42778
  leftPosition = { sheetId, col: col - 1, row };
@@ -42027,7 +42782,7 @@ class AutofillPlugin extends UIPlugin {
42027
42782
  col = zone.right;
42028
42783
  if (col <= this.getters.getNumberCols(sheetId)) {
42029
42784
  let rightPosition = { sheetId, col: col + 1, row };
42030
- while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
42785
+ while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
42031
42786
  this.getters.getCell(rightPosition)?.content) {
42032
42787
  row += 1;
42033
42788
  rightPosition = { sheetId, col: col + 1, row };
@@ -43622,7 +44377,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43622
44377
  isPasteAllowed(target, clipboardOption) {
43623
44378
  const sheetId = this.getters.getActiveSheetId();
43624
44379
  if (this.operation === "CUT" && clipboardOption?.pasteOption !== undefined) {
43625
- // cannot paste only format or only value if the previous operation is a CUT
44380
+ // cannot paste only format or as value if the previous operation is a CUT
43626
44381
  return "WrongPasteOption" /* CommandResult.WrongPasteOption */;
43627
44382
  }
43628
44383
  if (target.length > 1) {
@@ -43804,7 +44559,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43804
44559
  // This condition is used to determine if we have to paste the CF or not.
43805
44560
  // We have to do it when the command handled is "PASTE", not "INSERT_CELL"
43806
44561
  // or "DELETE_CELL". So, the state should be the local state
43807
- const shouldPasteCF = clipboardOptions?.pasteOption !== "onlyValue" && clipboardOptions?.shouldPasteCF;
44562
+ const shouldPasteCF = clipboardOptions?.pasteOption !== "asValue" && clipboardOptions?.shouldPasteCF;
43808
44563
  const shouldPasteDV = !clipboardOptions?.pasteOption;
43809
44564
  const sheetId = this.getters.getActiveSheetId();
43810
44565
  // first, add missing cols/rows if needed
@@ -43840,10 +44595,11 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43840
44595
  pasteCell(origin, target, operation, clipboardOption) {
43841
44596
  const { sheetId, col, row } = target;
43842
44597
  const targetCell = this.getters.getEvaluatedCell(target);
43843
- if (clipboardOption?.pasteOption === "onlyValue") {
44598
+ const originFormat = origin.cell?.format ?? origin.evaluatedCell.format;
44599
+ if (clipboardOption?.pasteOption === "asValue") {
43844
44600
  const locale = this.getters.getLocale();
43845
44601
  const content = formatValue(origin.evaluatedCell.value, { locale });
43846
- this.dispatch("UPDATE_CELL", { ...target, content });
44602
+ this.dispatch("UPDATE_CELL", { ...target, content, format: originFormat });
43847
44603
  return;
43848
44604
  }
43849
44605
  const targetBorders = this.getters.getCellBorder(target);
@@ -43859,7 +44615,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43859
44615
  this.dispatch("UPDATE_CELL", {
43860
44616
  ...target,
43861
44617
  style: origin.cell?.style ?? null,
43862
- format: origin.cell?.format ?? origin.evaluatedCell.format ?? targetCell.format,
44618
+ format: originFormat ?? targetCell.format,
43863
44619
  });
43864
44620
  return;
43865
44621
  }
@@ -52307,7 +53063,7 @@ class Spreadsheet extends Component {
52307
53063
  }
52308
53064
  onKeydown(ev) {
52309
53065
  let keyDownString = "";
52310
- if (ev.ctrlKey || ev.metaKey) {
53066
+ if (isCtrlKey(ev)) {
52311
53067
  keyDownString += "CTRL+";
52312
53068
  }
52313
53069
  keyDownString += ev.key.toUpperCase();
@@ -55044,7 +55800,8 @@ function addRows(construct, data, sheet) {
55044
55800
  }
55045
55801
  else if (cell.content && cell.content !== "") {
55046
55802
  const isTableHeader = isCellTableHeader(c, r, sheet);
55047
- ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader));
55803
+ const isPlainText = !!(cell.format && data.formats[cell.format] === PLAIN_TEXT_FORMAT);
55804
+ ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isPlainText));
55048
55805
  }
55049
55806
  attributes.push(...additionalAttrs);
55050
55807
  cellNodes.push(escapeXml /*xml*/ `
@@ -55973,6 +56730,7 @@ const helpers = {
55973
56730
  colorToRGBA,
55974
56731
  positionToZone,
55975
56732
  isDefined: isDefined$1,
56733
+ isMatrix,
55976
56734
  lazy,
55977
56735
  genericRepeat,
55978
56736
  createAction,
@@ -56022,6 +56780,6 @@ const constants = {
56022
56780
  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 };
56023
56781
 
56024
56782
 
56025
- __info__.version = "17.1.0-alpha.5";
56026
- __info__.date = "2023-12-05T09:51:40.034Z";
56027
- __info__.hash = "c2823eb";
56783
+ __info__.version = "17.1.0-alpha.6";
56784
+ __info__.date = "2024-01-04T12:22:45.921Z";
56785
+ __info__.hash = "56dbdc9";