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