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