@odoo/o-spreadsheet 17.0.3 → 17.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 17.0.3
6
- * @date 2023-11-16T11:59:49.524Z
7
- * @hash 8bd0fb2
5
+ * @version 17.0.5
6
+ * @date 2023-12-05T09:51:44.657Z
7
+ * @hash 2480a5d
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';
@@ -691,8 +691,7 @@ function removeFalsyAttributes(obj) {
691
691
  *
692
692
  * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
693
693
  */
694
- const whiteSpaceCharacters = [
695
- " ",
694
+ const whiteSpaceSpecialCharacters = [
696
695
  "\t",
697
696
  "\f",
698
697
  "\v",
@@ -707,8 +706,7 @@ const whiteSpaceCharacters = [
707
706
  String.fromCharCode(parseInt("3000", 16)),
708
707
  String.fromCharCode(parseInt("feff", 16)),
709
708
  ];
710
- const whiteSpaceRegexp = new RegExp(whiteSpaceCharacters.join("|"), "g");
711
- const newLineRegex = /\r\n|\r|\n/g;
709
+ const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|") + "|(\r\n|\r|\n)", "g");
712
710
  /**
713
711
  * Replace all the special spaces in a string (non-breaking, tabs, ...) by normal spaces, and all the
714
712
  * different newlines types by \n.
@@ -716,9 +714,9 @@ const newLineRegex = /\r\n|\r|\n/g;
716
714
  function replaceSpecialSpaces(text) {
717
715
  if (!text)
718
716
  return "";
719
- text = text.replace(whiteSpaceRegexp, " ");
720
- text = text.replace(newLineRegex, NEWLINE);
721
- return text;
717
+ if (!whiteSpaceRegexp.test(text))
718
+ return text;
719
+ return text.replace(whiteSpaceRegexp, (match, newLine) => (newLine ? NEWLINE : " "));
722
720
  }
723
721
  /**
724
722
  * Determine if the numbers are consecutive.
@@ -2060,6 +2058,7 @@ var CommandResult;
2060
2058
  CommandResult["InvalidNumberOfCriterionValues"] = "InvalidNumberOfCriterionValues";
2061
2059
  CommandResult["BlockingValidationRule"] = "BlockingValidationRule";
2062
2060
  CommandResult["InvalidCopyPasteSelection"] = "InvalidCopyPasteSelection";
2061
+ CommandResult["NoChanges"] = "NoChanges";
2063
2062
  })(CommandResult || (CommandResult = {}));
2064
2063
 
2065
2064
  const DEFAULT_LOCALES = [
@@ -2189,12 +2188,12 @@ function toString(value) {
2189
2188
  }
2190
2189
  }
2191
2190
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
2192
- function normalizeString(str) {
2191
+ const normalizeString = memoize(function normalizeString(str) {
2193
2192
  return str
2194
2193
  .toLowerCase()
2195
2194
  .normalize("NFD")
2196
2195
  .replace(/[\u0300-\u036f]/g, "");
2197
- }
2196
+ });
2198
2197
  const expectBooleanValueError = (value) => _t("The function [[FUNCTION_NAME]] expects a boolean value, but '%s' is a text, and cannot be coerced to a number.", value);
2199
2198
  function toBoolean(value) {
2200
2199
  switch (typeof value) {
@@ -3388,8 +3387,10 @@ function convertInternalFormatToFormat(internalFormat) {
3388
3387
  const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
3389
3388
  // Same as above, but matches the exact string (nothing before or after)
3390
3389
  const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
3391
- /** Reference of a column header (eg. A, AB) */
3392
- const colHeader = new RegExp(/^([A-Z]{1,3})+$/, "i");
3390
+ /** Reference of a column header (eg. A, AB, $A) */
3391
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
3392
+ /** Reference of a row header (eg. 1, $1) */
3393
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
3393
3394
  /** Reference of a column (eg. A, $CA, Sheet1!B) */
3394
3395
  const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
3395
3396
  /** Reference of a row (eg. 1, 59, Sheet1!9) */
@@ -3419,6 +3420,9 @@ function isRowReference(xc) {
3419
3420
  function isColHeader(str) {
3420
3421
  return colHeader.test(str);
3421
3422
  }
3423
+ function isRowHeader(str) {
3424
+ return rowHeader.test(str);
3425
+ }
3422
3426
  /**
3423
3427
  * Return true if the given xc is the reference of a single cell,
3424
3428
  * without any specified sheet (e.g. A1)
@@ -4102,18 +4106,10 @@ class RangeImpl {
4102
4106
  if (isFullCol) {
4103
4107
  parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
4104
4108
  parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
4105
- if (zone.left === zone.right) {
4106
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
4107
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
4108
- }
4109
4109
  }
4110
4110
  if (isFullRow) {
4111
4111
  parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
4112
4112
  parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
4113
- if (zone.top === zone.bottom) {
4114
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
4115
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
4116
- }
4117
4113
  }
4118
4114
  return parts;
4119
4115
  }
@@ -4355,7 +4351,7 @@ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
4355
4351
  function getDefaultCellHeight(ctx, cell, colSize) {
4356
4352
  if (!cell || !cell.content)
4357
4353
  return DEFAULT_CELL_HEIGHT;
4358
- const maxWidth = cell.style?.wrapping ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
4354
+ const maxWidth = cell.style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
4359
4355
  const numberOfLines = cell.isFormula
4360
4356
  ? 1
4361
4357
  : splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
@@ -6250,10 +6246,6 @@ css /* scss */ `
6250
6246
 
6251
6247
  .o-error-tooltip-message {
6252
6248
  overflow: hidden;
6253
- display: -webkit-box; /* Limit to 3 lines */
6254
- -webkit-line-clamp: 3;
6255
- line-clamp: 3;
6256
- -webkit-box-orient: vertical;
6257
6249
  }
6258
6250
  }
6259
6251
  `;
@@ -6636,6 +6628,9 @@ function* iterateChildren(el) {
6636
6628
  function getOpenedMenus() {
6637
6629
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
6638
6630
  }
6631
+ function isMacOS() {
6632
+ return navigator.userAgent.toUpperCase().indexOf("MAC") >= 0;
6633
+ }
6639
6634
 
6640
6635
  /**
6641
6636
  * Return the o-spreadsheet element position relative
@@ -7329,7 +7324,7 @@ function tokenize(str, locale = DEFAULT_LOCALE) {
7329
7324
  return result;
7330
7325
  }
7331
7326
  function tokenizeDebugger(chars) {
7332
- if (chars.current() === "?") {
7327
+ if (chars.current === "?") {
7333
7328
  chars.shift();
7334
7329
  return { type: "DEBUGGER", value: "?" };
7335
7330
  }
@@ -7340,7 +7335,7 @@ const misc$1 = {
7340
7335
  ")": "RIGHT_PAREN",
7341
7336
  };
7342
7337
  function tokenizeMisc(chars) {
7343
- if (chars.current() in misc$1) {
7338
+ if (chars.current in misc$1) {
7344
7339
  const value = chars.shift();
7345
7340
  const type = misc$1[value];
7346
7341
  return { type, value };
@@ -7348,7 +7343,7 @@ function tokenizeMisc(chars) {
7348
7343
  return null;
7349
7344
  }
7350
7345
  function tokenizeArgsSeparator(chars, locale) {
7351
- if (chars.current() === locale.formulaArgSeparator) {
7346
+ if (chars.current === locale.formulaArgSeparator) {
7352
7347
  const value = chars.shift();
7353
7348
  const type = "ARG_SEPARATOR";
7354
7349
  return { type, value };
@@ -7373,14 +7368,13 @@ function tokenizeNumber(chars, locale) {
7373
7368
  return null;
7374
7369
  }
7375
7370
  function tokenizeString(chars) {
7376
- if (chars.current() === '"') {
7371
+ if (chars.current === '"') {
7377
7372
  const startChar = chars.shift();
7378
7373
  let letters = startChar;
7379
- while (chars.current() &&
7380
- (chars.current() !== startChar || letters[letters.length - 1] === "\\")) {
7374
+ while (chars.current && (chars.current !== startChar || letters[letters.length - 1] === "\\")) {
7381
7375
  letters += chars.shift();
7382
7376
  }
7383
- if (chars.current() === '"') {
7377
+ if (chars.current === '"') {
7384
7378
  letters += chars.shift();
7385
7379
  }
7386
7380
  return {
@@ -7407,14 +7401,14 @@ function tokenizeSymbol(chars) {
7407
7401
  let result = "";
7408
7402
  // there are two main cases to manage: either something which starts with
7409
7403
  // a ', like 'Sheet 2'A2, or a word-like element.
7410
- if (chars.current() === "'") {
7404
+ if (chars.current === "'") {
7411
7405
  let lastChar = chars.shift();
7412
7406
  result += lastChar;
7413
- while (chars.current()) {
7407
+ while (chars.current) {
7414
7408
  lastChar = chars.shift();
7415
7409
  result += lastChar;
7416
7410
  if (lastChar === "'") {
7417
- if (chars.current() && chars.current() === "'") {
7411
+ if (chars.current && chars.current === "'") {
7418
7412
  lastChar = chars.shift();
7419
7413
  result += lastChar;
7420
7414
  }
@@ -7430,7 +7424,7 @@ function tokenizeSymbol(chars) {
7430
7424
  };
7431
7425
  }
7432
7426
  }
7433
- while (chars.current() && separatorRegexp.test(chars.current())) {
7427
+ while (chars.current && separatorRegexp.test(chars.current)) {
7434
7428
  result += chars.shift();
7435
7429
  }
7436
7430
  if (result.length) {
@@ -7445,14 +7439,14 @@ function tokenizeSymbol(chars) {
7445
7439
  }
7446
7440
  function tokenizeSpace(chars) {
7447
7441
  let length = 0;
7448
- while (chars.current() === NEWLINE) {
7442
+ while (chars.current === NEWLINE) {
7449
7443
  length++;
7450
7444
  chars.shift();
7451
7445
  }
7452
7446
  if (length) {
7453
7447
  return { type: "SPACE", value: NEWLINE.repeat(length) };
7454
7448
  }
7455
- while (chars.current() === " ") {
7449
+ while (chars.current === " ") {
7456
7450
  length++;
7457
7451
  chars.shift();
7458
7452
  }
@@ -7471,17 +7465,20 @@ function tokenizeInvalidRange(chars) {
7471
7465
  class TokenizingChars {
7472
7466
  text;
7473
7467
  currentIndex = 0;
7468
+ current;
7474
7469
  constructor(text) {
7475
7470
  this.text = text;
7476
- }
7477
- current() {
7478
- return this.text[this.currentIndex];
7471
+ this.current = text[0];
7479
7472
  }
7480
7473
  shift() {
7481
- return this.text[this.currentIndex++];
7474
+ const current = this.current;
7475
+ const next = this.text[++this.currentIndex];
7476
+ this.current = next;
7477
+ return current;
7482
7478
  }
7483
7479
  advanceBy(length) {
7484
7480
  this.currentIndex += length;
7481
+ this.current = this.text[this.currentIndex];
7485
7482
  }
7486
7483
  isOver() {
7487
7484
  return this.currentIndex >= this.text.length;
@@ -28278,6 +28275,7 @@ class AbstractResizer extends Component {
28278
28275
  draggerShadowThickness: 0,
28279
28276
  delta: 0,
28280
28277
  base: 0,
28278
+ position: "before",
28281
28279
  });
28282
28280
  _computeHandleDisplay(ev) {
28283
28281
  const position = this._getEvOffset(ev);
@@ -28402,11 +28400,13 @@ class AbstractResizer extends Component {
28402
28400
  this.state.draggerLinePosition = dimensions.start;
28403
28401
  this.state.draggerShadowPosition = dimensions.start;
28404
28402
  this.state.base = elementIndex;
28403
+ this.state.position = "before";
28405
28404
  }
28406
28405
  else if (this._getSelectedZoneEnd() < elementIndex) {
28407
28406
  this.state.draggerLinePosition = dimensions.end;
28408
28407
  this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
28409
- this.state.base = elementIndex + 1;
28408
+ this.state.base = elementIndex;
28409
+ this.state.position = "after";
28410
28410
  }
28411
28411
  else {
28412
28412
  this.state.draggerLinePosition = startDimensions.start;
@@ -28588,6 +28588,7 @@ class ColResizer extends AbstractResizer {
28588
28588
  dimension: "COL",
28589
28589
  base: this.state.base,
28590
28590
  elements,
28591
+ position: this.state.position,
28591
28592
  });
28592
28593
  if (!result.isSuccessful && result.reasons.includes("WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */)) {
28593
28594
  this.env.raiseError(MergeErrorMessage);
@@ -28761,6 +28762,7 @@ class RowResizer extends AbstractResizer {
28761
28762
  dimension: "ROW",
28762
28763
  base: this.state.base,
28763
28764
  elements,
28765
+ position: this.state.position,
28764
28766
  });
28765
28767
  if (!result.isSuccessful && result.reasons.includes("WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */)) {
28766
28768
  this.env.raiseError(MergeErrorMessage);
@@ -28868,8 +28870,8 @@ function useWheelHandler(handler) {
28868
28870
  return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
28869
28871
  }
28870
28872
  const onMouseWheel = (ev) => {
28871
- const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
28872
- const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
28873
+ const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
28874
+ const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
28873
28875
  handler(deltaX, deltaY);
28874
28876
  };
28875
28877
  return onMouseWheel;
@@ -29757,6 +29759,7 @@ class Grid extends Component {
29757
29759
  const content = clipboardData.getData(ClipboardMIMEType.PlainText);
29758
29760
  const target = this.env.model.getters.getSelectedZones();
29759
29761
  const clipboardString = this.env.model.getters.getClipboardTextContent();
29762
+ const isCutOperation = this.env.model.getters.isCutOperation();
29760
29763
  if (clipboardString === content) {
29761
29764
  // the paste actually comes from o-spreadsheet itself
29762
29765
  interactivePaste(this.env, target);
@@ -29764,7 +29767,7 @@ class Grid extends Component {
29764
29767
  else {
29765
29768
  interactivePasteFromOS(this.env, target, content);
29766
29769
  }
29767
- if (this.env.model.getters.isCutOperation()) {
29770
+ if (isCutOperation) {
29768
29771
  await this.env.clipboard.write({ [ClipboardMIMEType.PlainText]: "" });
29769
29772
  }
29770
29773
  }
@@ -33322,7 +33325,7 @@ const machine = {
33322
33325
  SPACE: goTo(State.RightRef),
33323
33326
  NUMBER: goTo(State.Found),
33324
33327
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
33325
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
33328
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
33326
33329
  },
33327
33330
  [State.RightColumnRef]: {
33328
33331
  SPACE: goTo(State.RightColumnRef),
@@ -33333,6 +33336,7 @@ const machine = {
33333
33336
  SPACE: goTo(State.RightRowRef),
33334
33337
  NUMBER: goTo(State.Found),
33335
33338
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
33339
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
33336
33340
  },
33337
33341
  [State.Found]: {},
33338
33342
  };
@@ -34058,6 +34062,14 @@ class BordersPlugin extends CorePlugin {
34058
34062
  // ---------------------------------------------------------------------------
34059
34063
  // Command Handling
34060
34064
  // ---------------------------------------------------------------------------
34065
+ allowDispatch(cmd) {
34066
+ switch (cmd.type) {
34067
+ case "SET_BORDER":
34068
+ return this.checkBordersUnchanged(cmd);
34069
+ default:
34070
+ return "Success" /* CommandResult.Success */;
34071
+ }
34072
+ }
34061
34073
  handle(cmd) {
34062
34074
  switch (cmd.type) {
34063
34075
  case "ADD_MERGE":
@@ -34477,6 +34489,14 @@ class BordersPlugin extends CorePlugin {
34477
34489
  this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
34478
34490
  }
34479
34491
  }
34492
+ checkBordersUnchanged(cmd) {
34493
+ const currentBorder = this.getCellBorder(cmd);
34494
+ const areAllNewBordersUndefined = !cmd.border?.bottom && !cmd.border?.left && !cmd.border?.right && !cmd.border?.top;
34495
+ if ((!currentBorder && areAllNewBordersUndefined) || deepEquals(currentBorder, cmd.border)) {
34496
+ return "NoChanges" /* CommandResult.NoChanges */;
34497
+ }
34498
+ return "Success" /* CommandResult.Success */;
34499
+ }
34480
34500
  // ---------------------------------------------------------------------------
34481
34501
  // Import/Export
34482
34502
  // ---------------------------------------------------------------------------
@@ -35332,8 +35352,9 @@ class CellPlugin extends CorePlugin {
35332
35352
  allowDispatch(cmd) {
35333
35353
  switch (cmd.type) {
35334
35354
  case "UPDATE_CELL":
35355
+ return this.checkCellOutOfSheet(cmd);
35335
35356
  case "CLEAR_CELL":
35336
- return this.checkCellOutOfSheet(cmd.sheetId, cmd.col, cmd.row);
35357
+ return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
35337
35358
  default:
35338
35359
  return "Success" /* CommandResult.Success */;
35339
35360
  }
@@ -35502,14 +35523,9 @@ class CellPlugin extends CorePlugin {
35502
35523
  */
35503
35524
  getCellById(cellId) {
35504
35525
  // this must be as fast as possible
35505
- for (const sheetId in this.cells) {
35506
- const sheet = this.cells[sheetId];
35507
- const cell = sheet[cellId];
35508
- if (cell) {
35509
- return cell;
35510
- }
35511
- }
35512
- return undefined;
35526
+ const position = this.getters.getCellPosition(cellId);
35527
+ const sheet = this.cells[position.sheetId];
35528
+ return sheet[cellId];
35513
35529
  }
35514
35530
  /*
35515
35531
  * Reconstructs the original formula string based on a normalized form and its dependencies
@@ -35740,13 +35756,23 @@ class CellPlugin extends CorePlugin {
35740
35756
  },
35741
35757
  };
35742
35758
  }
35743
- checkCellOutOfSheet(sheetId, col, row) {
35759
+ checkCellOutOfSheet(cmd) {
35760
+ const { sheetId, col, row } = cmd;
35744
35761
  const sheet = this.getters.tryGetSheet(sheetId);
35745
35762
  if (!sheet)
35746
35763
  return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
35747
35764
  const sheetZone = this.getters.getSheetZone(sheetId);
35748
35765
  return isInside(col, row, sheetZone) ? "Success" /* CommandResult.Success */ : "TargetOutOfSheet" /* CommandResult.TargetOutOfSheet */;
35749
35766
  }
35767
+ checkUselessClearCell(cmd) {
35768
+ const cell = this.getters.getCell(cmd);
35769
+ if (!cell)
35770
+ return "NoChanges" /* CommandResult.NoChanges */;
35771
+ if (!cell.content && !cell.style && !cell.format) {
35772
+ return "NoChanges" /* CommandResult.NoChanges */;
35773
+ }
35774
+ return "Success" /* CommandResult.Success */;
35775
+ }
35750
35776
  }
35751
35777
  class FormulaCellWithDependencies {
35752
35778
  id;
@@ -36436,6 +36462,9 @@ class DataValidationPlugin extends CorePlugin {
36436
36462
  return this.rules[sheetId].find((rule) => rule.id === id);
36437
36463
  }
36438
36464
  getValidationRuleForCell({ sheetId, col, row }) {
36465
+ if (!this.rules[sheetId]) {
36466
+ return undefined;
36467
+ }
36439
36468
  for (const rule of this.rules[sheetId]) {
36440
36469
  for (const range of rule.ranges) {
36441
36470
  if (isInside(col, row, range.zone)) {
@@ -37289,7 +37318,16 @@ class HeaderVisibilityPlugin extends CorePlugin {
37289
37318
  return consecutiveIndexes;
37290
37319
  }
37291
37320
  getAllVisibleHeaders(sheetId, dimension) {
37292
- return range(0, this.hiddenHeaders[sheetId][dimension].length).filter((i) => !this.hiddenHeaders[sheetId][dimension][i]);
37321
+ const headers = range(0, this.getters.getNumberHeaders(sheetId, dimension));
37322
+ const foldedHeaders = [];
37323
+ this.getters.getHeaderGroups(sheetId, dimension).forEach((group) => {
37324
+ if (group.isFolded) {
37325
+ foldedHeaders.push(...range(group.start, group.end + 1));
37326
+ }
37327
+ });
37328
+ return headers.filter((i) => {
37329
+ return !this.hiddenHeaders[sheetId][dimension][i] && !foldedHeaders.includes(i);
37330
+ });
37293
37331
  }
37294
37332
  import(data) {
37295
37333
  for (let sheet of data.sheets) {
@@ -39033,10 +39071,13 @@ class SheetPlugin extends CorePlugin {
39033
39071
  // begin with the end.
39034
39072
  rows.sort((a, b) => b - a);
39035
39073
  for (let group of groupConsecutive(rows)) {
39074
+ // indexes are sorted in the descending order
39075
+ const from = group[group.length - 1];
39076
+ const to = group[0];
39036
39077
  // Move the cells.
39037
- this.moveCellOnRowsDeletion(sheet, group[group.length - 1], group[0]);
39038
- // Effectively delete the element and recompute the left-right/top-bottom.
39039
- group.map((row) => this.updateRowsStructureOnDeletion(row, sheet));
39078
+ this.moveCellOnRowsDeletion(sheet, from, to);
39079
+ // Effectively delete the rows
39080
+ this.updateRowsStructureOnDeletion(sheet, from, to);
39040
39081
  }
39041
39082
  const count = rows.filter((row) => row < sheet.panes.ySplit).length;
39042
39083
  if (count) {
@@ -39058,8 +39099,6 @@ class SheetPlugin extends CorePlugin {
39058
39099
  this.addEmptyRows(sheet, quantity);
39059
39100
  // Move the cells.
39060
39101
  this.moveCellsOnAddition(sheet, index, quantity, "rows");
39061
- // Recompute the left-right/top-bottom.
39062
- this.updateRowsStructureOnAddition(sheet, row, quantity);
39063
39102
  if (index < sheet.panes.ySplit) {
39064
39103
  this.setPaneDivisions(sheet.id, sheet.panes.ySplit + quantity, "ROW");
39065
39104
  }
@@ -39160,35 +39199,20 @@ class SheetPlugin extends CorePlugin {
39160
39199
  }
39161
39200
  }
39162
39201
  }
39163
- updateRowsStructureOnDeletion(index, sheet) {
39202
+ updateRowsStructureOnDeletion(sheet, deleteFromRow, deleteToRow) {
39164
39203
  const rows = [];
39165
- const cellsQueue = sheet.rows.map((row) => row.cells);
39204
+ const cellsQueue = sheet.rows.map((row) => row.cells).reverse();
39166
39205
  for (let i in sheet.rows) {
39167
- if (Number(i) === index) {
39206
+ const row = Number(i);
39207
+ if (row >= deleteFromRow && row <= deleteToRow) {
39168
39208
  continue;
39169
39209
  }
39170
39210
  rows.push({
39171
- cells: cellsQueue.shift(),
39211
+ cells: cellsQueue.pop(),
39172
39212
  });
39173
39213
  }
39174
39214
  this.history.update("sheets", sheet.id, "rows", rows);
39175
39215
  }
39176
- /**
39177
- * Update the rows of the sheet after an addition:
39178
- * - Rename the rows
39179
- *
39180
- * @param sheet Sheet on which the deletion occurs
39181
- * @param addedRow Index of the added row
39182
- * @param rowsToAdd Number of the rows to add
39183
- */
39184
- updateRowsStructureOnAddition(sheet, addedRow, rowsToAdd) {
39185
- const rows = [];
39186
- const cellsQueue = sheet.rows.map((row) => row.cells);
39187
- sheet.rows.forEach(() => rows.push({
39188
- cells: cellsQueue.shift(),
39189
- }));
39190
- this.history.update("sheets", sheet.id, "rows", rows);
39191
- }
39192
39216
  /**
39193
39217
  * Add empty rows at the end of the rows
39194
39218
  *
@@ -39715,6 +39739,7 @@ class CompilationParametersBuilder {
39715
39739
  getters;
39716
39740
  computeCell;
39717
39741
  evalContext;
39742
+ rangeCache = {};
39718
39743
  constructor(context, getters, computeCell) {
39719
39744
  this.getters = getters;
39720
39745
  this.computeCell = computeCell;
@@ -39762,10 +39787,13 @@ class CompilationParametersBuilder {
39762
39787
  if (evaluatedCell === undefined) {
39763
39788
  return { value: null, format: this.getters.getCell(position)?.format };
39764
39789
  }
39790
+ if (evaluatedCell.type === CellValueType.error) {
39791
+ throw evaluatedCell.error;
39792
+ }
39765
39793
  return evaluatedCell;
39766
39794
  }
39767
39795
  getEvaluatedCellIfNotEmpty(position) {
39768
- const evaluatedCell = this.getEvaluatedCell(position);
39796
+ const evaluatedCell = this.computeCell(position);
39769
39797
  if (evaluatedCell.type === CellValueType.empty) {
39770
39798
  const cell = this.getters.getCell(position);
39771
39799
  if (!cell || (!cell.isFormula && cell.content === "")) {
@@ -39774,13 +39802,6 @@ class CompilationParametersBuilder {
39774
39802
  }
39775
39803
  return evaluatedCell;
39776
39804
  }
39777
- getEvaluatedCell(position) {
39778
- const evaluatedCell = this.computeCell(position);
39779
- if (evaluatedCell.type === CellValueType.error) {
39780
- throw evaluatedCell.error;
39781
- }
39782
- return evaluatedCell;
39783
- }
39784
39805
  /**
39785
39806
  * Return the values of the cell(s) used in reference, but always in the format of a range even
39786
39807
  * if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
@@ -39800,17 +39821,33 @@ class CompilationParametersBuilder {
39800
39821
  if (!_zone) {
39801
39822
  return [[]];
39802
39823
  }
39824
+ const { top, left, bottom, right } = zone;
39825
+ const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
39826
+ if (cacheKey in this.rangeCache) {
39827
+ const result = this.rangeCache[cacheKey];
39828
+ if (result instanceof EvaluationError) {
39829
+ throw result;
39830
+ }
39831
+ return result;
39832
+ }
39803
39833
  const height = _zone.bottom - _zone.top + 1;
39804
39834
  const width = _zone.right - _zone.left + 1;
39805
- const matrix = Array.from({ length: width }, () => Array.from({ length: height }));
39835
+ const matrix = new Array(width);
39806
39836
  // Performance issue: nested loop is faster than a map here
39807
39837
  for (let col = _zone.left; col <= _zone.right; col++) {
39838
+ const colIndex = col - _zone.left;
39839
+ matrix[colIndex] = new Array(height);
39808
39840
  for (let row = _zone.top; row <= _zone.bottom; row++) {
39809
- const colIndex = col - _zone.left;
39841
+ const evaluatedCell = this.getEvaluatedCellIfNotEmpty({ sheetId, col, row });
39842
+ if (evaluatedCell?.type === CellValueType.error) {
39843
+ this.rangeCache[cacheKey] = evaluatedCell.error;
39844
+ throw evaluatedCell.error;
39845
+ }
39810
39846
  const rowIndex = row - _zone.top;
39811
39847
  matrix[colIndex][rowIndex] = this.readCell({ sheetId, col, row });
39812
39848
  }
39813
39849
  }
39850
+ this.rangeCache[cacheKey] = matrix;
39814
39851
  return matrix;
39815
39852
  }
39816
39853
  }
@@ -40016,6 +40053,7 @@ class Evaluator {
40016
40053
  this.formulaDependencies().addDependencies(positionId, dependencies);
40017
40054
  }
40018
40055
  updateCompilationParameters() {
40056
+ // rebuild the compilation parameters (with a clean cache)
40019
40057
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
40020
40058
  }
40021
40059
  evaluateCells(positions) {
@@ -40059,6 +40097,16 @@ class Evaluator {
40059
40097
  this.evaluatedCells = new Map();
40060
40098
  this.evaluate(this.getAllCells());
40061
40099
  }
40100
+ evaluateFormula(sheetId, formulaString) {
40101
+ const compiledFormula = compile(formulaString);
40102
+ const ranges = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
40103
+ this.updateCompilationParameters();
40104
+ const array = compiledFormula.execute(ranges, ...this.compilationParams);
40105
+ if (isMatrix(array)) {
40106
+ return matrixMap(array, (cell) => cell.value);
40107
+ }
40108
+ return array.value;
40109
+ }
40062
40110
  getAllCells() {
40063
40111
  const positionIds = new JetSet();
40064
40112
  for (const sheetId of this.getters.getSheetIds()) {
@@ -40085,6 +40133,7 @@ class Evaluator {
40085
40133
  this.nextPositionsToUpdate = cells;
40086
40134
  let currentIteration = 0;
40087
40135
  while (this.nextPositionsToUpdate.size && currentIteration++ < MAX_ITERATION) {
40136
+ this.updateCompilationParameters();
40088
40137
  const positionIds = Array.from(this.nextPositionsToUpdate);
40089
40138
  this.nextPositionsToUpdate.clear();
40090
40139
  for (let i = 0; i < positionIds.length; ++i) {
@@ -40448,7 +40497,6 @@ class PositionBitsEncoder {
40448
40497
  // of other cells depending on it, at the next iteration.
40449
40498
  //#endregion
40450
40499
  class EvaluationPlugin extends UIPlugin {
40451
- config;
40452
40500
  static getters = [
40453
40501
  "evaluateFormula",
40454
40502
  "getCorrespondingFormulaCell",
@@ -40459,15 +40507,13 @@ class EvaluationPlugin extends UIPlugin {
40459
40507
  "getEvaluatedCells",
40460
40508
  "getEvaluatedCellsInZone",
40461
40509
  "getSpreadPositionsOf",
40510
+ "getArrayFormulaSpreadingOn",
40462
40511
  ];
40463
40512
  shouldRebuildDependenciesGraph = true;
40464
40513
  evaluator;
40465
- compilationParams;
40466
40514
  positionsToUpdate = [];
40467
40515
  constructor(config) {
40468
40516
  super(config);
40469
- this.config = config;
40470
- this.compilationParams = this.getCompilationParameters();
40471
40517
  this.evaluator = new Evaluator(config.custom, this.getters);
40472
40518
  }
40473
40519
  // ---------------------------------------------------------------------------
@@ -40492,10 +40538,6 @@ class EvaluationPlugin extends UIPlugin {
40492
40538
  case "EVALUATE_CELLS":
40493
40539
  this.evaluator.evaluateAllCells();
40494
40540
  break;
40495
- case "UPDATE_LOCALE":
40496
- this.compilationParams = this.getCompilationParameters();
40497
- this.evaluator.updateCompilationParameters();
40498
- break;
40499
40541
  }
40500
40542
  }
40501
40543
  finalize() {
@@ -40513,16 +40555,7 @@ class EvaluationPlugin extends UIPlugin {
40513
40555
  // Getters
40514
40556
  // ---------------------------------------------------------------------------
40515
40557
  evaluateFormula(sheetId, formulaString) {
40516
- const compiledFormula = compile(formulaString);
40517
- const ranges = [];
40518
- for (let xc of compiledFormula.dependencies) {
40519
- ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
40520
- }
40521
- const array = compiledFormula.execute(ranges, ...this.compilationParams);
40522
- if (isMatrix(array)) {
40523
- return array.map((col) => col.map((row) => row.value));
40524
- }
40525
- return array.value;
40558
+ return this.evaluator.evaluateFormula(sheetId, formulaString);
40526
40559
  }
40527
40560
  /**
40528
40561
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -40571,6 +40604,9 @@ class EvaluationPlugin extends UIPlugin {
40571
40604
  getSpreadPositionsOf(position) {
40572
40605
  return this.evaluator.getSpreadPositionsOf(position);
40573
40606
  }
40607
+ getArrayFormulaSpreadingOn(position) {
40608
+ return this.evaluator.getArrayFormulaSpreadingOn(position);
40609
+ }
40574
40610
  // ---------------------------------------------------------------------------
40575
40611
  // Export
40576
40612
  // ---------------------------------------------------------------------------
@@ -40614,7 +40650,7 @@ class EvaluationPlugin extends UIPlugin {
40614
40650
  else if (cell && cell.content) {
40615
40651
  return undefined;
40616
40652
  }
40617
- const spreadingFormulaPosition = this.evaluator.getArrayFormulaSpreadingOn(position);
40653
+ const spreadingFormulaPosition = this.getArrayFormulaSpreadingOn(position);
40618
40654
  if (spreadingFormulaPosition === undefined) {
40619
40655
  return undefined;
40620
40656
  }
@@ -40624,9 +40660,6 @@ class EvaluationPlugin extends UIPlugin {
40624
40660
  }
40625
40661
  return undefined;
40626
40662
  }
40627
- getCompilationParameters() {
40628
- return buildCompilationParameters(this.config.custom, this.getters, (position) => this.evaluator.getEvaluatedCell(position));
40629
- }
40630
40663
  }
40631
40664
  function isBadExpression(tokens) {
40632
40665
  try {
@@ -41767,19 +41800,21 @@ class AutofillPlugin extends UIPlugin {
41767
41800
  let col = zone.left;
41768
41801
  let row = zone.bottom;
41769
41802
  if (col > 0) {
41770
- let left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
41771
- while (left.type !== CellValueType.empty) {
41803
+ let leftPosition = { sheetId, col: col - 1, row };
41804
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
41805
+ this.getters.getCell(leftPosition)?.content) {
41772
41806
  row += 1;
41773
- left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
41807
+ leftPosition = { sheetId, col: col - 1, row };
41774
41808
  }
41775
41809
  }
41776
41810
  if (row === zone.bottom) {
41777
41811
  col = zone.right;
41778
41812
  if (col <= this.getters.getNumberCols(sheetId)) {
41779
- let right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
41780
- while (right.type !== CellValueType.empty) {
41813
+ let rightPosition = { sheetId, col: col + 1, row };
41814
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
41815
+ this.getters.getCell(rightPosition)?.content) {
41781
41816
  row += 1;
41782
- right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
41817
+ rightPosition = { sheetId, col: col + 1, row };
41783
41818
  }
41784
41819
  }
41785
41820
  }
@@ -42771,6 +42806,7 @@ class Session extends EventBus {
42771
42806
  isReplayingInitialRevisions = false;
42772
42807
  processedRevisions = new Set();
42773
42808
  uuidGenerator = new UuidGenerator();
42809
+ lastLocalOperation;
42774
42810
  /**
42775
42811
  * Manages the collaboration between multiple users on the same spreadsheet.
42776
42812
  * It can forward local state changes to other users to ensure they all eventually
@@ -42803,6 +42839,11 @@ class Session extends EventBus {
42803
42839
  return;
42804
42840
  const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes, Date.now());
42805
42841
  this.revisions.append(revision.id, revision);
42842
+ // REQUEST_REDO just repeats the last operation, the
42843
+ // last operation is still the same and should not change.
42844
+ if (rootCommand.type !== "REQUEST_REDO") {
42845
+ this.lastLocalOperation = revision;
42846
+ }
42806
42847
  this.trigger("new-local-state-update", { id: revision.id });
42807
42848
  this.sendUpdateMessage({
42808
42849
  type: "REMOTE_REVISION",
@@ -42899,19 +42940,10 @@ class Session extends EventBus {
42899
42940
  return this.pendingMessages.length === 0;
42900
42941
  }
42901
42942
  /**
42902
- * Get the last local revision whose root command isn't in the given list of ignored commands
42943
+ * Get the last local revision
42903
42944
  * */
42904
- getLastLocalNonEmptyRevision(ignoredRootCommands) {
42905
- const revisions = this.revisions.getRevertedExecution();
42906
- for (const rev of revisions) {
42907
- if (rev.rootCommand === "SNAPSHOT")
42908
- return undefined;
42909
- if (!rev.rootCommand || rev.rootCommand === "REMOTE")
42910
- continue;
42911
- if (!ignoredRootCommands.includes(rev.rootCommand?.type) && rev.commands.length)
42912
- return rev;
42913
- }
42914
- return undefined;
42945
+ getLastLocalNonEmptyRevision() {
42946
+ return this.lastLocalOperation;
42915
42947
  }
42916
42948
  _move(position) {
42917
42949
  // this method is debounced and might be called after the client
@@ -42971,7 +43003,7 @@ class Session extends EventBus {
42971
43003
  break;
42972
43004
  case "REMOTE_REVISION":
42973
43005
  const { clientId, commands, timestamp } = message;
42974
- const revision = new Revision(message.nextRevisionId, clientId, commands, "REMOTE", undefined, timestamp);
43006
+ const revision = new Revision(message.nextRevisionId, clientId, commands, undefined, undefined, timestamp);
42975
43007
  if (revision.clientId !== this.clientId) {
42976
43008
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
42977
43009
  const pendingCommands = this.pendingMessages
@@ -42984,10 +43016,11 @@ class Session extends EventBus {
42984
43016
  }
42985
43017
  break;
42986
43018
  case "SNAPSHOT_CREATED": {
42987
- const revision = new Revision(message.nextRevisionId, "server", [], "SNAPSHOT", undefined, Date.now());
43019
+ const revision = new Revision(message.nextRevisionId, "server", [], undefined, undefined, Date.now());
42988
43020
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
42989
43021
  this.dropPendingHistoryMessages();
42990
43022
  this.trigger("snapshot");
43023
+ this.lastLocalOperation = undefined;
42991
43024
  break;
42992
43025
  }
42993
43026
  }
@@ -43325,11 +43358,26 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43325
43358
  let cellsInRow = [];
43326
43359
  for (let col of columnsIndex) {
43327
43360
  const position = { col, row, sheetId };
43361
+ const spreader = getters.getArrayFormulaSpreadingOn(position);
43362
+ let cell = getters.getCell(position);
43363
+ const evaluatedCell = getters.getEvaluatedCell(position);
43364
+ if (spreader) {
43365
+ const isSpreaderCopied = rowsIndex.includes(spreader.row) && columnsIndex.includes(spreader.col);
43366
+ const content = isSpreaderCopied
43367
+ ? ""
43368
+ : formatValue(evaluatedCell.value, { locale: getters.getLocale() });
43369
+ cell = {
43370
+ id: cell?.id || "",
43371
+ style: cell?.style,
43372
+ format: evaluatedCell.format,
43373
+ content,
43374
+ isFormula: false,
43375
+ };
43376
+ }
43328
43377
  cellsInRow.push({
43329
- cell: getters.getCell(position),
43330
- style: getters.getCellComputedStyle(position),
43331
- evaluatedCell: getters.getEvaluatedCell(position),
43378
+ cell,
43332
43379
  border: getters.getCellBorder(position) || undefined,
43380
+ evaluatedCell,
43333
43381
  position,
43334
43382
  });
43335
43383
  }
@@ -43451,13 +43499,6 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43451
43499
  });
43452
43500
  }
43453
43501
  this.pasteCopiedTables(target);
43454
- this.cells.forEach((row) => {
43455
- row.forEach((c) => {
43456
- if (c.cell) {
43457
- c.cell = undefined;
43458
- }
43459
- });
43460
- });
43461
43502
  }
43462
43503
  /**
43463
43504
  * The clipped zone is copied as many times as it fits in the target.
@@ -43529,7 +43570,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43529
43570
  clearClippedZones() {
43530
43571
  for (const row of this.cells) {
43531
43572
  for (const cell of row) {
43532
- if (cell.cell) {
43573
+ if (cell?.cell) {
43533
43574
  this.dispatch("CLEAR_CELL", cell.position);
43534
43575
  }
43535
43576
  }
@@ -43555,6 +43596,9 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43555
43596
  const rowCells = this.cells[r];
43556
43597
  for (let c = 0; c < width; c++) {
43557
43598
  const origin = rowCells[c];
43599
+ if (!origin) {
43600
+ continue;
43601
+ }
43558
43602
  const position = { col: col + c, row: row + r, sheetId: sheetId };
43559
43603
  // TODO: refactor this part. the "Paste merge" action is also executed with
43560
43604
  // MOVE_RANGES in pasteFromCut. Adding a condition on the operation type here
@@ -43578,53 +43622,42 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43578
43622
  pasteCell(origin, target, operation, clipboardOption) {
43579
43623
  const { sheetId, col, row } = target;
43580
43624
  const targetCell = this.getters.getEvaluatedCell(target);
43581
- if (clipboardOption?.pasteOption !== "onlyValue") {
43582
- const targetBorders = this.getters.getCellBorder(target);
43583
- const originBorders = origin.border;
43584
- const border = {
43585
- top: targetBorders?.top || originBorders?.top,
43586
- bottom: targetBorders?.bottom || originBorders?.bottom,
43587
- left: targetBorders?.left || originBorders?.left,
43588
- right: targetBorders?.right || originBorders?.right,
43589
- };
43590
- this.dispatch("SET_BORDER", { sheetId, col, row, border });
43625
+ if (clipboardOption?.pasteOption === "onlyValue") {
43626
+ const locale = this.getters.getLocale();
43627
+ const content = formatValue(origin.evaluatedCell.value, { locale });
43628
+ this.dispatch("UPDATE_CELL", { ...target, content });
43629
+ return;
43591
43630
  }
43592
- if (origin.cell) {
43593
- if (clipboardOption?.pasteOption === "onlyFormat") {
43594
- this.dispatch("UPDATE_CELL", {
43595
- ...target,
43596
- style: origin.cell.style,
43597
- format: origin.evaluatedCell.format,
43598
- });
43599
- return;
43600
- }
43601
- if (clipboardOption?.pasteOption === "onlyValue") {
43602
- const locale = this.getters.getLocale();
43603
- const content = formatValue(origin.evaluatedCell.value, { locale });
43604
- this.dispatch("UPDATE_CELL", { ...target, content });
43605
- return;
43606
- }
43607
- let content = origin.cell.content;
43608
- if (origin.cell.isFormula && operation === "COPY") {
43609
- content = this.getters.getTranslatedCellFormula(sheetId, col - origin.position.col, row - origin.position.row, origin.cell.compiledFormula);
43610
- }
43631
+ const targetBorders = this.getters.getCellBorder(target);
43632
+ const originBorders = origin.border;
43633
+ const border = {
43634
+ top: targetBorders?.top || originBorders?.top,
43635
+ bottom: targetBorders?.bottom || originBorders?.bottom,
43636
+ left: targetBorders?.left || originBorders?.left,
43637
+ right: targetBorders?.right || originBorders?.right,
43638
+ };
43639
+ this.dispatch("SET_BORDER", { sheetId, col, row, border });
43640
+ if (clipboardOption?.pasteOption === "onlyFormat") {
43641
+ this.dispatch("UPDATE_CELL", {
43642
+ ...target,
43643
+ style: origin.cell?.style ?? null,
43644
+ format: origin.cell?.format ?? origin.evaluatedCell.format ?? targetCell.format,
43645
+ });
43646
+ return;
43647
+ }
43648
+ const content = origin.cell && origin.cell.isFormula && operation === "COPY"
43649
+ ? this.getters.getTranslatedCellFormula(sheetId, col - origin.position.col, row - origin.position.row, origin.cell.compiledFormula)
43650
+ : origin.cell?.content;
43651
+ if (content !== "" || origin.cell?.format || origin.cell?.style) {
43611
43652
  this.dispatch("UPDATE_CELL", {
43612
43653
  ...target,
43613
43654
  content,
43614
- style: origin.cell.style || null,
43615
- format: origin.cell.format,
43655
+ style: origin.cell?.style || null,
43656
+ format: origin.cell?.format,
43616
43657
  });
43617
43658
  }
43618
43659
  else if (targetCell) {
43619
- if (clipboardOption?.pasteOption === "onlyValue") {
43620
- this.dispatch("UPDATE_CELL", { ...target, content: "" });
43621
- }
43622
- else if (clipboardOption?.pasteOption === "onlyFormat") {
43623
- this.dispatch("UPDATE_CELL", { ...target, style: null, format: "" });
43624
- }
43625
- else {
43626
- this.dispatch("CLEAR_CELL", target);
43627
- }
43660
+ this.dispatch("CLEAR_CELL", target);
43628
43661
  }
43629
43662
  }
43630
43663
  /**
@@ -43686,22 +43719,28 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
43686
43719
  return (this.cells
43687
43720
  .map((cells) => {
43688
43721
  return cells
43689
- .map((c) => this.getters.shouldShowFormulas() && c.cell?.isFormula
43722
+ .map((c) => this.getters.shouldShowFormulas() && c?.cell?.isFormula
43690
43723
  ? c.cell?.content || ""
43691
- : c.evaluatedCell?.formattedValue || "")
43724
+ : c?.evaluatedCell?.formattedValue || "")
43692
43725
  .join("\t");
43693
43726
  })
43694
43727
  .join("\n") || "\t");
43695
43728
  }
43696
43729
  getHTMLContent() {
43697
43730
  if (this.cells.length === 1 && this.cells[0].length === 1) {
43731
+ if (!this.cells[0][0]) {
43732
+ return "";
43733
+ }
43698
43734
  return this.getters.getCellText(this.cells[0][0].position);
43699
43735
  }
43700
43736
  let htmlTable = '<table border="1" style="border-collapse:collapse">';
43701
43737
  for (const row of this.cells) {
43702
43738
  htmlTable += "<tr>";
43703
43739
  for (const cell of row) {
43704
- const cssStyle = cssPropertiesToCss(cellStyleToCss(cell.style));
43740
+ if (!cell) {
43741
+ continue;
43742
+ }
43743
+ const cssStyle = cssPropertiesToCss(cellStyleToCss(this.getters.getCellComputedStyle(cell.position)));
43705
43744
  const cellText = this.getters.getCellText(cell.position);
43706
43745
  htmlTable += `<td style="${cssStyle}">` + xmlEscape(cellText) + "</td>";
43707
43746
  }
@@ -45536,6 +45575,9 @@ class SelectionInputsManagerPlugin extends UIPlugin {
45536
45575
  // Other
45537
45576
  // ---------------------------------------------------------------------------
45538
45577
  initInput(id, initialRanges, inputHasSingleRange = false) {
45578
+ if (this.inputs[id]) {
45579
+ this.unfocus();
45580
+ }
45539
45581
  this.inputs[id] = new SelectionInputPlugin(this.config, initialRanges, inputHasSingleRange);
45540
45582
  if (initialRanges.length === 0) {
45541
45583
  const input = this.inputs[id];
@@ -46334,7 +46376,7 @@ class HistoryPlugin extends UIPlugin {
46334
46376
  * Ignore standard undo/redo revisions (that are empty)
46335
46377
  */
46336
46378
  getPossibleRevisionToRepeat() {
46337
- return this.session.getLastLocalNonEmptyRevision(["REQUEST_REDO"]);
46379
+ return this.session.getLastLocalNonEmptyRevision();
46338
46380
  }
46339
46381
  }
46340
46382
 
@@ -46831,11 +46873,14 @@ class ClipboardPlugin extends UIPlugin {
46831
46873
  }
46832
46874
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
46833
46875
  this.state.paste(cmd.target, { pasteOption, shouldPasteCF: true, selectTarget: true });
46876
+ if (this.state.operation === "CUT") {
46877
+ this.state = undefined;
46878
+ }
46834
46879
  this.lastPasteState = this.state;
46835
46880
  if (this.paintFormatStatus === "oneOff") {
46836
46881
  this.paintFormatStatus = "inactive";
46837
- this.status = "invisible";
46838
46882
  }
46883
+ this.status = "invisible";
46839
46884
  break;
46840
46885
  case "COPY_PASTE_CELLS_ABOVE":
46841
46886
  {
@@ -47116,6 +47161,10 @@ function setXcToReferenceType(xc, referenceType) {
47116
47161
  return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
47117
47162
  case "colrow":
47118
47163
  indexOfNumber = xc.search(/[0-9]/);
47164
+ if (indexOfNumber === -1 || indexOfNumber === 0) {
47165
+ // no row number (eg. A) or no column (eg. 1)
47166
+ return "$" + xc;
47167
+ }
47119
47168
  xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
47120
47169
  return "$" + xc;
47121
47170
  case "none":
@@ -47886,6 +47935,12 @@ class FilterEvaluationPlugin extends UIPlugin {
47886
47935
  break;
47887
47936
  case "HIDE_COLUMNS_ROWS":
47888
47937
  case "UNHIDE_COLUMNS_ROWS":
47938
+ case "GROUP_HEADERS":
47939
+ case "UNGROUP_HEADERS":
47940
+ case "FOLD_HEADER_GROUP":
47941
+ case "UNFOLD_HEADER_GROUP":
47942
+ case "FOLD_ALL_HEADER_GROUPS":
47943
+ case "UNFOLD_ALL_HEADER_GROUPS":
47889
47944
  this.updateHiddenRows();
47890
47945
  break;
47891
47946
  case "UPDATE_FILTER":
@@ -48587,7 +48642,7 @@ class GridSelectionPlugin extends UIPlugin {
48587
48642
  sheetId: cmd.sheetId,
48588
48643
  base: cmd.base,
48589
48644
  quantity: thickness,
48590
- position: "before",
48645
+ position: cmd.position,
48591
48646
  });
48592
48647
  const isCol = cmd.dimension === "COL";
48593
48648
  const start = cmd.elements[0];
@@ -48604,12 +48659,13 @@ class GridSelectionPlugin extends UIPlugin {
48604
48659
  },
48605
48660
  ];
48606
48661
  const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
48662
+ const base = isBasedBefore ? cmd.base : cmd.base + 1;
48607
48663
  const pasteTarget = [
48608
48664
  {
48609
- left: isCol ? cmd.base : 0,
48610
- right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
48611
- top: !isCol ? cmd.base : 0,
48612
- bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
48665
+ left: isCol ? base : 0,
48666
+ right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
48667
+ top: !isCol ? base : 0,
48668
+ bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
48613
48669
  },
48614
48670
  ];
48615
48671
  state.paste(pasteTarget, { selectTarget: true });
@@ -48644,6 +48700,11 @@ class GridSelectionPlugin extends UIPlugin {
48644
48700
  doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
48645
48701
  return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
48646
48702
  }
48703
+ const headers = [cmd.base, ...cmd.elements];
48704
+ const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
48705
+ if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
48706
+ return "InvalidHeaderIndex" /* CommandResult.InvalidHeaderIndex */;
48707
+ }
48647
48708
  return "Success" /* CommandResult.Success */;
48648
48709
  }
48649
48710
  //-------------------------------------------
@@ -49689,10 +49750,13 @@ class HeaderPositionsUIPlugin extends UIPlugin {
49689
49750
  }
49690
49751
  break;
49691
49752
  case "UPDATE_CELL":
49692
- if ("content" in cmd || "format" in cmd || cmd.style?.fontSize !== undefined) {
49753
+ if ("content" in cmd || "format" in cmd) {
49693
49754
  this.headerPositions = {};
49694
49755
  this.isDirty = true;
49695
49756
  }
49757
+ else {
49758
+ this.headerPositions[cmd.sheetId] = this.computeHeaderPositionsOfSheet(cmd.sheetId);
49759
+ }
49696
49760
  break;
49697
49761
  case "UPDATE_FILTER":
49698
49762
  case "REMOVE_FILTER_TABLE":
@@ -51849,6 +51913,8 @@ css /* scss */ `
51849
51913
  *:before,
51850
51914
  *:after {
51851
51915
  box-sizing: content-box;
51916
+ /** rtl not supported ATM */
51917
+ direction: ltr;
51852
51918
  }
51853
51919
  .o-separator {
51854
51920
  border-bottom: ${MENU_SEPARATOR_BORDER_WIDTH}px solid ${SEPARATOR_COLOR};
@@ -55559,19 +55625,22 @@ class Model extends EventBus {
55559
55625
  * Check if the given command is allowed by all the plugins and the history.
55560
55626
  */
55561
55627
  checkDispatchAllowed(command) {
55562
- if (isCoreCommand(command)) {
55563
- return this.checkDispatchAllowedCoreCommand(command);
55628
+ const results = isCoreCommand(command)
55629
+ ? this.checkDispatchAllowedCoreCommand(command)
55630
+ : this.checkDispatchAllowedLocalCommand(command);
55631
+ if (results.some((r) => r !== "Success" /* CommandResult.Success */)) {
55632
+ return new DispatchResult(results.flat());
55564
55633
  }
55565
- return this.checkDispatchAllowedLocalCommand(command);
55634
+ return DispatchResult.Success;
55566
55635
  }
55567
55636
  checkDispatchAllowedCoreCommand(command) {
55568
55637
  const results = this.corePlugins.map((handler) => handler.allowDispatch(command));
55569
55638
  results.push(this.range.allowDispatch(command));
55570
- return new DispatchResult(results.flat());
55639
+ return results;
55571
55640
  }
55572
55641
  checkDispatchAllowedLocalCommand(command) {
55573
55642
  const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
55574
- return new DispatchResult(results.flat());
55643
+ return results;
55575
55644
  }
55576
55645
  finalize() {
55577
55646
  this.status = 3 /* Status.Finalizing */;
@@ -55883,6 +55952,6 @@ const constants = {
55883
55952
  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 };
55884
55953
 
55885
55954
 
55886
- __info__.version = "17.0.3";
55887
- __info__.date = "2023-11-16T11:59:49.524Z";
55888
- __info__.hash = "8bd0fb2";
55955
+ __info__.version = "17.0.5";
55956
+ __info__.date = "2023-12-05T09:51:44.657Z";
55957
+ __info__.hash = "2480a5d";