@odoo/o-spreadsheet 16.3.0-alpha.4 → 16.3.0-alpha.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.
@@ -1721,12 +1721,77 @@
1721
1721
  * - all digit stored in the decimal part of the number
1722
1722
  *
1723
1723
  * The 'maxDecimal' parameter allows to indicate the number of digits to not
1724
- * exceed in the decimal part, in which case digits are rounded
1724
+ * exceed in the decimal part, in which case digits are rounded.
1725
1725
  *
1726
- * Intl.Numberformat is used to properly handle all the roundings.
1727
- * e.g. 1234.7 with format ### (<> maxDecimals=0) should become 1235, not 1234
1728
1726
  **/
1729
1727
  function splitNumber(value, maxDecimals = MAX_DECIMAL_PLACES) {
1728
+ const asString = value.toString();
1729
+ if (asString.includes("e"))
1730
+ return splitNumberIntl(value, maxDecimals);
1731
+ if (Number.isInteger(value)) {
1732
+ return { integerDigits: asString, decimalDigits: undefined };
1733
+ }
1734
+ const indexOfDot = asString.indexOf(".");
1735
+ let integerDigits = asString.substring(0, indexOfDot);
1736
+ let decimalDigits = asString.substring(indexOfDot + 1);
1737
+ if (maxDecimals === 0) {
1738
+ if (Number(decimalDigits[0]) >= 5) {
1739
+ integerDigits = (Number(integerDigits) + 1).toString();
1740
+ }
1741
+ return { integerDigits, decimalDigits: undefined };
1742
+ }
1743
+ if (decimalDigits.length > maxDecimals) {
1744
+ const { integerDigits: roundedIntegerDigits, decimalDigits: roundedDecimalDigits } = limitDecimalDigits(decimalDigits, maxDecimals);
1745
+ decimalDigits = roundedDecimalDigits;
1746
+ if (roundedIntegerDigits !== "0") {
1747
+ integerDigits = (Number(integerDigits) + Number(roundedIntegerDigits)).toString();
1748
+ }
1749
+ }
1750
+ return { integerDigits, decimalDigits: removeTrailingZeroes(decimalDigits || "") };
1751
+ }
1752
+ /**
1753
+ * Return the given string minus the trailing "0" characters.
1754
+ *
1755
+ * @param numberString : a string of integers
1756
+ * @returns the numberString, minus the eventual zeroes at the end
1757
+ */
1758
+ function removeTrailingZeroes(numberString) {
1759
+ let i = numberString.length - 1;
1760
+ while (i >= 0 && numberString[i] === "0") {
1761
+ i--;
1762
+ }
1763
+ return numberString.slice(0, i + 1) || undefined;
1764
+ }
1765
+ /**
1766
+ * Limit the size of the decimal part of a number to the given number of digits.
1767
+ */
1768
+ function limitDecimalDigits(decimalDigits, maxDecimals) {
1769
+ let integerDigits = "0";
1770
+ let resultDecimalDigits = decimalDigits;
1771
+ // Note : we'd want to simply use number.toFixed() to handle the max digits & rounding,
1772
+ // but it has very strange behaviour. Ex: 12.345.toFixed(2) => "12.35", but 1.345.toFixed(2) => "1.34"
1773
+ let slicedDecimalDigits = decimalDigits.slice(0, maxDecimals);
1774
+ const i = maxDecimals;
1775
+ if (Number(Number(decimalDigits[i]) < 5)) {
1776
+ return { integerDigits, decimalDigits: slicedDecimalDigits };
1777
+ }
1778
+ // round up
1779
+ const slicedRoundedUp = (Number(slicedDecimalDigits) + 1).toString();
1780
+ if (slicedRoundedUp.length > slicedDecimalDigits.length) {
1781
+ integerDigits = (Number(integerDigits) + 1).toString();
1782
+ resultDecimalDigits = undefined;
1783
+ }
1784
+ else {
1785
+ resultDecimalDigits = slicedRoundedUp;
1786
+ }
1787
+ return { integerDigits, decimalDigits: resultDecimalDigits };
1788
+ }
1789
+ /**
1790
+ * Split numbers into decimal/integer digits using Intl.NumberFormat.
1791
+ * Supports numbers with a lot of digits that are transformed to scientific notation by
1792
+ * number.toString(), but is slow.
1793
+ */
1794
+ function splitNumberIntl(value, maxDecimals = MAX_DECIMAL_PLACES) {
1730
1795
  let formatter = numberRepresentation[maxDecimals];
1731
1796
  if (!formatter) {
1732
1797
  formatter = new Intl.NumberFormat("en-US", {
@@ -5761,12 +5826,12 @@
5761
5826
  function createActions(menuItems) {
5762
5827
  return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
5763
5828
  }
5764
- const uuidGenerator$1 = new UuidGenerator();
5829
+ const uuidGenerator$2 = new UuidGenerator();
5765
5830
  function createAction(item) {
5766
5831
  const name = item.name;
5767
5832
  const children = item.children;
5768
5833
  return {
5769
- id: item.id || uuidGenerator$1.uuidv4(),
5834
+ id: item.id || uuidGenerator$2.uuidv4(),
5770
5835
  name: typeof name === "function" ? name : () => name,
5771
5836
  isVisible: item.isVisible ? item.isVisible : () => true,
5772
5837
  isEnabled: item.isEnabled ? item.isEnabled : () => true,
@@ -17436,7 +17501,7 @@
17436
17501
  }
17437
17502
  }
17438
17503
 
17439
- const uuidGenerator = new UuidGenerator();
17504
+ const uuidGenerator$1 = new UuidGenerator();
17440
17505
  css /* scss */ `
17441
17506
  .o-selection {
17442
17507
  .o-selection-input {
@@ -17493,7 +17558,7 @@
17493
17558
  class SelectionInput extends owl.Component {
17494
17559
  constructor() {
17495
17560
  super(...arguments);
17496
- this.id = uuidGenerator.uuidv4();
17561
+ this.id = uuidGenerator$1.uuidv4();
17497
17562
  this.previousRanges = this.props.ranges || [];
17498
17563
  this.originSheet = this.env.model.getters.getActiveSheetId();
17499
17564
  this.state = owl.useState({
@@ -21186,13 +21251,26 @@
21186
21251
  const sheetId = this.env.model.getters.getActiveSheetId();
21187
21252
  const rowDims = this.env.model.getters.getRowDimensionsInViewport(sheetId, position.row);
21188
21253
  const colDims = this.env.model.getters.getColDimensionsInViewport(sheetId, position.col);
21189
- // TODO : change this offset when we support vertical cell align
21190
- const centeringOffset = Math.floor((rowDims.size - FILTER_ICON_EDGE_LENGTH) / 2);
21254
+ const cell = this.env.model.getters.getCell({ sheetId, ...position });
21255
+ const verticalFilterIconPosition = this.getIconVerticalPosition(rowDims, cell);
21191
21256
  return {
21192
21257
  x: colDims.end - FILTER_ICON_EDGE_LENGTH + this.props.gridPosition.x - FILTER_ICON_MARGIN - 1,
21193
- y: rowDims.end - FILTER_ICON_EDGE_LENGTH + this.props.gridPosition.y - centeringOffset,
21258
+ y: verticalFilterIconPosition + this.props.gridPosition.y,
21194
21259
  };
21195
21260
  }
21261
+ // Calculates the vertical position of the filter icon based on the row dimensions and cell styles.
21262
+ getIconVerticalPosition(rowDims, cell) {
21263
+ var _a;
21264
+ const centeringOffset = Math.floor((rowDims.size - FILTER_ICON_EDGE_LENGTH) / 2);
21265
+ switch ((_a = cell === null || cell === void 0 ? void 0 : cell.style) === null || _a === void 0 ? void 0 : _a.verticalAlign) {
21266
+ case "bottom":
21267
+ return rowDims.end - FILTER_ICON_MARGIN - FILTER_ICON_EDGE_LENGTH;
21268
+ case "top":
21269
+ return rowDims.start + FILTER_ICON_MARGIN;
21270
+ default:
21271
+ return rowDims.end - FILTER_ICON_EDGE_LENGTH - centeringOffset;
21272
+ }
21273
+ }
21196
21274
  isFilterActive(position) {
21197
21275
  const sheetId = this.env.model.getters.getActiveSheetId();
21198
21276
  return this.env.model.getters.isFilterActive({ sheetId, ...position });
@@ -23108,6 +23186,7 @@
23108
23186
  "CTRL+A": () => this.env.model.selection.loopSelection(),
23109
23187
  "CTRL+Z": () => this.env.model.dispatch("REQUEST_UNDO"),
23110
23188
  "CTRL+Y": () => this.env.model.dispatch("REQUEST_REDO"),
23189
+ F4: () => this.env.model.dispatch("REQUEST_REDO"),
23111
23190
  "CTRL+B": () => this.env.model.dispatch("SET_FORMATTING", {
23112
23191
  sheetId: this.env.model.getters.getActiveSheetId(),
23113
23192
  target: this.env.model.getters.getSelectedZones(),
@@ -23151,8 +23230,8 @@
23151
23230
  },
23152
23231
  "CTRL+END": () => {
23153
23232
  const sheetId = this.env.model.getters.getActiveSheetId();
23154
- const col = this.env.model.getters.findVisibleHeader(sheetId, "COL", range(0, this.env.model.getters.getNumberCols(sheetId)).reverse());
23155
- const row = this.env.model.getters.findVisibleHeader(sheetId, "ROW", range(0, this.env.model.getters.getNumberRows(sheetId)).reverse());
23233
+ const col = this.env.model.getters.findVisibleHeader(sheetId, "COL", this.env.model.getters.getNumberCols(sheetId) - 1, 0);
23234
+ const row = this.env.model.getters.findVisibleHeader(sheetId, "ROW", this.env.model.getters.getNumberRows(sheetId) - 1, 0);
23156
23235
  this.env.model.selection.selectCell(col, row);
23157
23236
  },
23158
23237
  "SHIFT+ ": () => {
@@ -23232,9 +23311,10 @@
23232
23311
  this.focus();
23233
23312
  }
23234
23313
  focus() {
23314
+ var _a;
23235
23315
  if (!this.env.model.getters.getSelectedFigureId() &&
23236
23316
  this.env.model.getters.getEditionMode() === "inactive") {
23237
- this.hiddenInput.el.focus();
23317
+ (_a = this.hiddenInput.el) === null || _a === void 0 ? void 0 : _a.focus();
23238
23318
  }
23239
23319
  }
23240
23320
  get gridEl() {
@@ -27154,7 +27234,7 @@
27154
27234
  }
27155
27235
  }
27156
27236
  }
27157
- data = JSON.parse(JSON.stringify(data));
27237
+ data = deepCopy(data);
27158
27238
  // apply migrations, if needed
27159
27239
  if ("version" in data) {
27160
27240
  if (data.version < CURRENT_VERSION) {
@@ -32272,7 +32352,7 @@
32272
32352
  duplicateSheet(fromId, toId) {
32273
32353
  const sheet = this.getSheet(fromId);
32274
32354
  const toName = this.getDuplicateSheetName(sheet.name);
32275
- const newSheet = JSON.parse(JSON.stringify(sheet));
32355
+ const newSheet = deepCopy(sheet);
32276
32356
  newSheet.id = toId;
32277
32357
  newSheet.name = toName;
32278
32358
  for (let col = 0; col <= newSheet.numberOfCols; col++) {
@@ -32399,10 +32479,10 @@
32399
32479
  }
32400
32480
  }
32401
32481
  moveCellOnColumnsDeletion(sheet, deletedColumn) {
32402
- for (let [index, row] of Object.entries(sheet.rows)) {
32403
- const rowIndex = parseInt(index, 10);
32482
+ for (let rowIndex = 0; rowIndex < sheet.rows.length; rowIndex++) {
32483
+ const row = sheet.rows[rowIndex];
32404
32484
  for (let i in row.cells) {
32405
- const colIndex = parseInt(i, 10);
32485
+ const colIndex = Number(i);
32406
32486
  const cellId = row.cells[i];
32407
32487
  if (cellId) {
32408
32488
  if (colIndex === deletedColumn) {
@@ -32429,11 +32509,11 @@
32429
32509
  */
32430
32510
  moveCellsOnAddition(sheet, addedElement, quantity, dimension) {
32431
32511
  const commands = [];
32432
- for (const [index, row] of Object.entries(sheet.rows)) {
32433
- const rowIndex = parseInt(index, 10);
32512
+ for (let rowIndex = 0; rowIndex < sheet.rows.length; rowIndex++) {
32513
+ const row = sheet.rows[rowIndex];
32434
32514
  if (dimension !== "rows" || rowIndex >= addedElement) {
32435
32515
  for (let i in row.cells) {
32436
- const colIndex = parseInt(i, 10);
32516
+ const colIndex = Number(i);
32437
32517
  const cellId = row.cells[i];
32438
32518
  if (cellId) {
32439
32519
  if (dimension === "rows" || colIndex >= addedElement) {
@@ -32463,11 +32543,11 @@
32463
32543
  */
32464
32544
  moveCellOnRowsDeletion(sheet, deleteFromRow, deleteToRow) {
32465
32545
  const numberRows = deleteToRow - deleteFromRow + 1;
32466
- for (let [index, row] of Object.entries(sheet.rows)) {
32467
- const rowIndex = parseInt(index, 10);
32546
+ for (let rowIndex = 0; rowIndex < sheet.rows.length; rowIndex++) {
32547
+ const row = sheet.rows[rowIndex];
32468
32548
  if (rowIndex >= deleteFromRow && rowIndex <= deleteToRow) {
32469
32549
  for (let i in row.cells) {
32470
- const colIndex = parseInt(i, 10);
32550
+ const colIndex = Number(i);
32471
32551
  const cellId = row.cells[i];
32472
32552
  if (cellId) {
32473
32553
  this.dispatch("CLEAR_CELL", {
@@ -32480,7 +32560,7 @@
32480
32560
  }
32481
32561
  if (rowIndex > deleteToRow) {
32482
32562
  for (let i in row.cells) {
32483
- const colIndex = parseInt(i, 10);
32563
+ const colIndex = Number(i);
32484
32564
  const cellId = row.cells[i];
32485
32565
  if (cellId) {
32486
32566
  this.dispatch("UPDATE_CELL_POSITION", {
@@ -32498,7 +32578,7 @@
32498
32578
  const rows = [];
32499
32579
  const cellsQueue = sheet.rows.map((row) => row.cells);
32500
32580
  for (let i in sheet.rows) {
32501
- if (parseInt(i, 10) === index) {
32581
+ if (Number(i) === index) {
32502
32582
  continue;
32503
32583
  }
32504
32584
  rows.push({
@@ -33669,13 +33749,11 @@
33669
33749
  return Boolean(id && ((_b = (_a = this.filterValues[sheetId]) === null || _a === void 0 ? void 0 : _a[id]) === null || _b === void 0 ? void 0 : _b.length));
33670
33750
  }
33671
33751
  intersectZoneWithViewport(sheetId, zone) {
33672
- const colsRange = range(zone.left, zone.right + 1);
33673
- const rowsRange = range(zone.top, zone.bottom + 1);
33674
33752
  return {
33675
- left: this.getters.findVisibleHeader(sheetId, "COL", colsRange),
33676
- right: this.getters.findVisibleHeader(sheetId, "COL", colsRange.reverse()),
33677
- top: this.getters.findVisibleHeader(sheetId, "ROW", rowsRange),
33678
- bottom: this.getters.findVisibleHeader(sheetId, "ROW", rowsRange.reverse()),
33753
+ left: this.getters.findVisibleHeader(sheetId, "COL", zone.left, zone.right),
33754
+ right: this.getters.findVisibleHeader(sheetId, "COL", zone.right, zone.left),
33755
+ top: this.getters.findVisibleHeader(sheetId, "ROW", zone.top, zone.bottom),
33756
+ bottom: this.getters.findVisibleHeader(sheetId, "ROW", zone.bottom, zone.top),
33679
33757
  };
33680
33758
  }
33681
33759
  updateFilter({ col, row, values, sheetId }) {
@@ -35797,13 +35875,33 @@
35797
35875
  getNextVisibleCellPosition({ sheetId, col, row }) {
35798
35876
  return {
35799
35877
  sheetId,
35800
- col: this.findVisibleHeader(sheetId, "COL", range(col, this.getters.getNumberCols(sheetId))),
35801
- row: this.findVisibleHeader(sheetId, "ROW", range(row, this.getters.getNumberRows(sheetId))),
35878
+ col: this.findVisibleHeader(sheetId, "COL", col, this.getters.getNumberCols(sheetId) - 1),
35879
+ row: this.findVisibleHeader(sheetId, "ROW", row, this.getters.getNumberRows(sheetId) - 1),
35802
35880
  };
35803
35881
  }
35804
- findVisibleHeader(sheetId, dimension, indexes) {
35805
- return indexes.find((index) => this.getters.doesHeaderExist(sheetId, dimension, index) &&
35806
- !this.isHeaderHidden(sheetId, dimension, index));
35882
+ /**
35883
+ * Find the first visible header in the range [`from` => `to`].
35884
+ *
35885
+ * Both `from` and `to` are inclusive.
35886
+ */
35887
+ findVisibleHeader(sheetId, dimension, from, to) {
35888
+ if (from <= to) {
35889
+ for (let i = from; i <= to; i++) {
35890
+ if (this.getters.doesHeaderExist(sheetId, dimension, i) &&
35891
+ !this.isHeaderHidden(sheetId, dimension, i)) {
35892
+ return i;
35893
+ }
35894
+ }
35895
+ }
35896
+ if (from > to) {
35897
+ for (let i = from; i >= to; i--) {
35898
+ if (this.getters.doesHeaderExist(sheetId, dimension, i) &&
35899
+ !this.isHeaderHidden(sheetId, dimension, i)) {
35900
+ return i;
35901
+ }
35902
+ }
35903
+ }
35904
+ return undefined;
35807
35905
  }
35808
35906
  findLastVisibleColRowIndex(sheetId, dimension, { last, first }) {
35809
35907
  const lastVisibleIndex = range(last, first, -1).find((index) => !this.isHeaderHidden(sheetId, dimension, index));
@@ -37264,7 +37362,8 @@
37264
37362
  * - clientId: Client who initiated the action
37265
37363
  * - changes: List of changes applied on the state.
37266
37364
  */
37267
- constructor(id, clientId, commands, changes) {
37365
+ constructor(id, clientId, commands, rootCommand, changes) {
37366
+ this.rootCommand = rootCommand;
37268
37367
  this._commands = [];
37269
37368
  this._changes = [];
37270
37369
  this.id = id;
@@ -37328,10 +37427,10 @@
37328
37427
  * Add a new revision to the collaborative session.
37329
37428
  * It will be transmitted to all other connected clients.
37330
37429
  */
37331
- save(commands, changes) {
37430
+ save(rootCommand, commands, changes) {
37332
37431
  if (!commands.length || !changes.length || !this.canApplyOptimisticUpdate())
37333
37432
  return;
37334
- const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, changes);
37433
+ const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes);
37335
37434
  this.revisions.append(revision.id, revision);
37336
37435
  this.trigger("new-local-state-update", { id: revision.id });
37337
37436
  this.sendUpdateMessage({
@@ -37432,6 +37531,22 @@
37432
37531
  isFullySynchronized() {
37433
37532
  return this.pendingMessages.length === 0;
37434
37533
  }
37534
+ /**
37535
+ * Get the last local revision whose root command isn't in the given list of ignored commands
37536
+ * */
37537
+ getLastLocalNonEmptyRevision(ignoredRootCommands) {
37538
+ var _a;
37539
+ const revisions = this.revisions.getRevertedExecution();
37540
+ for (const rev of revisions) {
37541
+ if (rev.rootCommand === "SNAPSHOT")
37542
+ return undefined;
37543
+ if (!rev.rootCommand || rev.rootCommand === "REMOTE")
37544
+ continue;
37545
+ if (!ignoredRootCommands.includes((_a = rev.rootCommand) === null || _a === void 0 ? void 0 : _a.type) && rev.commands.length)
37546
+ return rev;
37547
+ }
37548
+ return undefined;
37549
+ }
37435
37550
  _move(position) {
37436
37551
  var _a;
37437
37552
  // this method is debounced and might be called after the client
@@ -37491,7 +37606,7 @@
37491
37606
  return;
37492
37607
  }
37493
37608
  const { clientId, commands } = message;
37494
- const revision = new Revision(message.nextRevisionId, clientId, commands);
37609
+ const revision = new Revision(message.nextRevisionId, clientId, commands, "REMOTE");
37495
37610
  if (revision.clientId !== this.clientId) {
37496
37611
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
37497
37612
  const pendingCommands = this.pendingMessages
@@ -37504,7 +37619,7 @@
37504
37619
  }
37505
37620
  break;
37506
37621
  case "SNAPSHOT_CREATED": {
37507
- const revision = new Revision(message.nextRevisionId, "server", []);
37622
+ const revision = new Revision(message.nextRevisionId, "server", [], "SNAPSHOT");
37508
37623
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
37509
37624
  this.dropPendingHistoryMessages();
37510
37625
  this.trigger("snapshot");
@@ -38282,6 +38397,324 @@
38282
38397
  "getColRowOffset",
38283
38398
  ];
38284
38399
 
38400
+ const genericRepeatsTransforms = [
38401
+ repeatSheetDependantCommand,
38402
+ repeatTargetDependantCommand,
38403
+ repeatPositionDependantCommand,
38404
+ ];
38405
+ function repeatSheetDependantCommand(getters, command) {
38406
+ if (!("sheetId" in command))
38407
+ return command;
38408
+ return { ...deepCopy(command), sheetId: getters.getActiveSheetId() };
38409
+ }
38410
+ function repeatTargetDependantCommand(getters, command) {
38411
+ if (!("target" in command) || !Array.isArray(command.target))
38412
+ return command;
38413
+ return {
38414
+ ...deepCopy(command),
38415
+ target: getters.getSelectedZones(),
38416
+ };
38417
+ }
38418
+ function repeatPositionDependantCommand(getters, command) {
38419
+ if (!("row" in command) || !("col" in command))
38420
+ return command;
38421
+ const { col, row } = getters.getActivePosition();
38422
+ return { ...deepCopy(command), col, row };
38423
+ }
38424
+
38425
+ const uuidGenerator = new UuidGenerator();
38426
+ function repeatCreateChartCommand(getters, cmd) {
38427
+ return {
38428
+ ...repeatSheetDependantCommand(getters, cmd),
38429
+ id: uuidGenerator.uuidv4(),
38430
+ };
38431
+ }
38432
+ function repeatCreateImageCommand(getters, cmd) {
38433
+ return {
38434
+ ...repeatSheetDependantCommand(getters, cmd),
38435
+ figureId: uuidGenerator.uuidv4(),
38436
+ };
38437
+ }
38438
+ function repeatCreateFigureCommand(getters, cmd) {
38439
+ const newCmd = repeatSheetDependantCommand(getters, cmd);
38440
+ newCmd.figure.id = uuidGenerator.uuidv4();
38441
+ return newCmd;
38442
+ }
38443
+ function repeatCreateSheetCommand(getters, cmd) {
38444
+ var _a;
38445
+ const newCmd = deepCopy(cmd);
38446
+ newCmd.sheetId = uuidGenerator.uuidv4();
38447
+ const sheetName = cmd.name || getters.getSheet(getters.getActiveSheetId()).name;
38448
+ // Extract the prefix of the sheet name (everything before the number at the end of the name)
38449
+ const namePrefix = ((_a = sheetName.match(/(.+?)\d*$/)) === null || _a === void 0 ? void 0 : _a[1]) || sheetName;
38450
+ newCmd.name = getters.getNextSheetName(namePrefix);
38451
+ return newCmd;
38452
+ }
38453
+ function repeatAddColumnsRowsCommand(getters, cmd) {
38454
+ const currentPosition = getters.getActivePosition();
38455
+ return {
38456
+ ...repeatSheetDependantCommand(getters, cmd),
38457
+ base: cmd.dimension === "COL" ? currentPosition.col : currentPosition.row,
38458
+ };
38459
+ }
38460
+ function repeatHeaderElementCommand(getters, cmd) {
38461
+ const currentSelection = getters.getSelectedZone();
38462
+ return {
38463
+ ...repeatSheetDependantCommand(getters, cmd),
38464
+ elements: cmd.dimension === "COL"
38465
+ ? range(currentSelection.left, currentSelection.right + 1)
38466
+ : range(currentSelection.top, currentSelection.bottom + 1),
38467
+ };
38468
+ }
38469
+ function repeatInsertOrDeleteCellCommand(getters, cmd) {
38470
+ const currentSelection = getters.getSelectedZone();
38471
+ return {
38472
+ ...deepCopy(cmd),
38473
+ zone: currentSelection,
38474
+ };
38475
+ }
38476
+ function repeatAutoResizeCommand(getters, cmd) {
38477
+ const newCmd = deepCopy(cmd);
38478
+ const currentSelection = getters.getSelectedZone();
38479
+ const { top, bottom, left, right } = currentSelection;
38480
+ if ("cols" in newCmd) {
38481
+ newCmd.cols = range(left, right + 1);
38482
+ }
38483
+ else if ("rows" in newCmd) {
38484
+ newCmd.rows = range(top, bottom + 1);
38485
+ }
38486
+ return newCmd;
38487
+ }
38488
+ function repeatSortCellsCommand(getters, cmd) {
38489
+ const currentSelection = getters.getSelectedZone();
38490
+ return {
38491
+ ...repeatSheetDependantCommand(getters, cmd),
38492
+ col: currentSelection.left,
38493
+ row: currentSelection.top,
38494
+ zone: currentSelection,
38495
+ };
38496
+ }
38497
+ function repeatPasteCommand(getters, cmd) {
38498
+ /**
38499
+ * Note : we have to store the state of the clipboard in the clipboard plugin, and introduce a
38500
+ * new command REPEAT_PASTE to be able to repeat the paste command.
38501
+ *
38502
+ * We cannot re-dispatch a paste, because the content of the clipboard may have changed in between.
38503
+ *
38504
+ * And we cannot adapt the sub-commands of the paste command, because they are dependant on the state of the sheet,
38505
+ * and may change based on the paste location. A simple example is that paste create new col/rows for the clipboard
38506
+ * content to fit the sheet. So there will be ADD_COL_ROW_COMMANDS in the sub-commands in the history, but repeating
38507
+ * paste might not need them. Or they could only needed for the repeated paste, not for the original.
38508
+ */
38509
+ return {
38510
+ type: "REPEAT_PASTE",
38511
+ pasteOption: deepCopy(cmd.pasteOption),
38512
+ target: getters.getSelectedZones(),
38513
+ };
38514
+ }
38515
+
38516
+ /**
38517
+ * Registry containing all the command that can be repeated on redo, and function to transform them
38518
+ * to the current state of the model.
38519
+ *
38520
+ * If the transform function is undefined, the command will be transformed using generic transformations.
38521
+ * (change the sheetId, the row, the col, the target, the ranges, to the current active sheet & selection)
38522
+ *
38523
+ */
38524
+ const repeatCommandTransformRegistry = new Registry();
38525
+ repeatCommandTransformRegistry.add("UPDATE_CELL", genericRepeat);
38526
+ repeatCommandTransformRegistry.add("CLEAR_CELL", genericRepeat);
38527
+ repeatCommandTransformRegistry.add("DELETE_CONTENT", genericRepeat);
38528
+ repeatCommandTransformRegistry.add("ADD_MERGE", genericRepeat);
38529
+ repeatCommandTransformRegistry.add("REMOVE_MERGE", genericRepeat);
38530
+ repeatCommandTransformRegistry.add("SET_FORMATTING", genericRepeat);
38531
+ repeatCommandTransformRegistry.add("CLEAR_FORMATTING", genericRepeat);
38532
+ repeatCommandTransformRegistry.add("SET_BORDER", genericRepeat);
38533
+ repeatCommandTransformRegistry.add("CREATE_FILTER_TABLE", genericRepeat);
38534
+ repeatCommandTransformRegistry.add("REMOVE_FILTER_TABLE", genericRepeat);
38535
+ repeatCommandTransformRegistry.add("HIDE_SHEET", genericRepeat);
38536
+ repeatCommandTransformRegistry.add("ADD_COLUMNS_ROWS", repeatAddColumnsRowsCommand);
38537
+ repeatCommandTransformRegistry.add("REMOVE_COLUMNS_ROWS", repeatHeaderElementCommand);
38538
+ repeatCommandTransformRegistry.add("HIDE_COLUMNS_ROWS", repeatHeaderElementCommand);
38539
+ repeatCommandTransformRegistry.add("RESIZE_COLUMNS_ROWS", repeatHeaderElementCommand);
38540
+ repeatCommandTransformRegistry.add("CREATE_SHEET", repeatCreateSheetCommand);
38541
+ repeatCommandTransformRegistry.add("CREATE_FIGURE", repeatCreateFigureCommand);
38542
+ repeatCommandTransformRegistry.add("CREATE_CHART", repeatCreateChartCommand);
38543
+ repeatCommandTransformRegistry.add("CREATE_IMAGE", repeatCreateImageCommand);
38544
+ const repeatLocalCommandTransformRegistry = new Registry();
38545
+ repeatLocalCommandTransformRegistry.add("STOP_EDITION", repeatLocalCommandChildren);
38546
+ repeatLocalCommandTransformRegistry.add("PASTE", repeatPasteCommand);
38547
+ repeatLocalCommandTransformRegistry.add("INSERT_CELL", repeatInsertOrDeleteCellCommand);
38548
+ repeatLocalCommandTransformRegistry.add("DELETE_CELL", repeatInsertOrDeleteCellCommand);
38549
+ repeatLocalCommandTransformRegistry.add("AUTORESIZE_COLUMNS", repeatAutoResizeCommand);
38550
+ repeatLocalCommandTransformRegistry.add("AUTORESIZE_ROWS", repeatAutoResizeCommand);
38551
+ repeatLocalCommandTransformRegistry.add("SORT_CELLS", repeatSortCellsCommand);
38552
+ repeatLocalCommandTransformRegistry.add("SUM_SELECTION", genericRepeat);
38553
+ repeatLocalCommandTransformRegistry.add("SET_DECIMAL", genericRepeat);
38554
+ function genericRepeat(getters, command) {
38555
+ let transformedCommand = deepCopy(command);
38556
+ for (const repeatTransform of genericRepeatsTransforms) {
38557
+ transformedCommand = repeatTransform(getters, transformedCommand);
38558
+ }
38559
+ return transformedCommand;
38560
+ }
38561
+ function repeatCoreCommand(getters, command) {
38562
+ if (!command) {
38563
+ return undefined;
38564
+ }
38565
+ const isRepeatable = repeatCommandTransformRegistry.contains(command.type);
38566
+ if (!isRepeatable) {
38567
+ return undefined;
38568
+ }
38569
+ const transform = repeatCommandTransformRegistry.get(command.type);
38570
+ return transform(getters, command);
38571
+ }
38572
+ function repeatLocalCommand(getters, command, childCommands) {
38573
+ const isRepeatable = repeatLocalCommandTransformRegistry.contains(command.type);
38574
+ if (!isRepeatable) {
38575
+ return undefined;
38576
+ }
38577
+ const repeatTransform = repeatLocalCommandTransformRegistry.get(command.type);
38578
+ return repeatTransform(getters, command, childCommands);
38579
+ }
38580
+ function repeatLocalCommandChildren(getters, cmd, childCommands) {
38581
+ return childCommands
38582
+ .map((childCommand) => repeatCoreCommand(getters, childCommand))
38583
+ .filter(isDefined$1);
38584
+ }
38585
+
38586
+ function canRepeatRevision(revision) {
38587
+ if (!revision || !revision.rootCommand || typeof revision.rootCommand !== "object") {
38588
+ return false;
38589
+ }
38590
+ if (isCoreCommand(revision.rootCommand)) {
38591
+ return repeatCommandTransformRegistry.contains(revision.rootCommand.type);
38592
+ }
38593
+ return repeatLocalCommandTransformRegistry.contains(revision.rootCommand.type);
38594
+ }
38595
+ function repeatRevision(revision, getters) {
38596
+ if (!revision.rootCommand || typeof revision.rootCommand !== "object") {
38597
+ return undefined;
38598
+ }
38599
+ if (isCoreCommand(revision.rootCommand)) {
38600
+ return repeatCoreCommand(getters, revision.rootCommand);
38601
+ }
38602
+ return repeatLocalCommand(getters, revision.rootCommand, revision.commands);
38603
+ }
38604
+
38605
+ /**
38606
+ * Local History
38607
+ *
38608
+ * The local history is responsible of tracking the locally state updates
38609
+ * It maintains the local undo and redo stack to allow to undo/redo only local
38610
+ * changes
38611
+ */
38612
+ class HistoryPlugin extends UIPlugin {
38613
+ constructor(config) {
38614
+ super(config);
38615
+ /**
38616
+ * Ids of the revisions which can be undone
38617
+ */
38618
+ this.undoStack = [];
38619
+ /**
38620
+ * Ids of the revisions which can be redone
38621
+ */
38622
+ this.redoStack = [];
38623
+ this.session = config.session;
38624
+ this.session.on("new-local-state-update", this, this.onNewLocalStateUpdate);
38625
+ this.session.on("pending-revisions-dropped", this, ({ revisionIds }) => this.drop(revisionIds));
38626
+ this.session.on("snapshot", this, () => {
38627
+ this.undoStack = [];
38628
+ this.redoStack = [];
38629
+ });
38630
+ }
38631
+ allowDispatch(cmd) {
38632
+ switch (cmd.type) {
38633
+ case "REQUEST_UNDO":
38634
+ if (!this.canUndo()) {
38635
+ return 6 /* CommandResult.EmptyUndoStack */;
38636
+ }
38637
+ break;
38638
+ case "REQUEST_REDO":
38639
+ if (!this.canRedo()) {
38640
+ return 7 /* CommandResult.EmptyRedoStack */;
38641
+ }
38642
+ break;
38643
+ }
38644
+ return 0 /* CommandResult.Success */;
38645
+ }
38646
+ handle(cmd) {
38647
+ switch (cmd.type) {
38648
+ case "REQUEST_UNDO":
38649
+ case "REQUEST_REDO":
38650
+ // History changes (undo & redo) are *not* applied optimistically on the local state.
38651
+ // We wait a global confirmation from the server. The goal is to avoid handling concurrent
38652
+ // history changes on multiple clients which are very hard to manage correctly.
38653
+ this.requestHistoryChange(cmd.type === "REQUEST_UNDO" ? "UNDO" : "REDO");
38654
+ }
38655
+ }
38656
+ finalize() { }
38657
+ requestHistoryChange(type) {
38658
+ const id = type === "UNDO" ? this.undoStack.pop() : this.redoStack.pop();
38659
+ if (!id) {
38660
+ const lastNonRedoRevision = this.getPossibleRevisionToRepeat();
38661
+ if (!lastNonRedoRevision) {
38662
+ return;
38663
+ }
38664
+ const repeatedCommands = repeatRevision(lastNonRedoRevision, this.getters);
38665
+ if (!repeatedCommands) {
38666
+ return;
38667
+ }
38668
+ if (!Array.isArray(repeatedCommands)) {
38669
+ this.dispatch(repeatedCommands.type, repeatedCommands);
38670
+ return;
38671
+ }
38672
+ for (const command of repeatedCommands) {
38673
+ this.dispatch(command.type, command);
38674
+ }
38675
+ return;
38676
+ }
38677
+ if (type === "UNDO") {
38678
+ this.session.undo(id);
38679
+ this.redoStack.push(id);
38680
+ }
38681
+ else {
38682
+ this.session.redo(id);
38683
+ this.undoStack.push(id);
38684
+ }
38685
+ }
38686
+ canUndo() {
38687
+ return this.undoStack.length > 0;
38688
+ }
38689
+ canRedo() {
38690
+ if (this.redoStack.length > 0)
38691
+ return true;
38692
+ const lastNonRedoRevision = this.getPossibleRevisionToRepeat();
38693
+ return canRepeatRevision(lastNonRedoRevision);
38694
+ }
38695
+ drop(revisionIds) {
38696
+ this.undoStack = this.undoStack.filter((id) => !revisionIds.includes(id));
38697
+ this.redoStack = [];
38698
+ }
38699
+ onNewLocalStateUpdate({ id }) {
38700
+ this.undoStack.push(id);
38701
+ this.redoStack = [];
38702
+ if (this.undoStack.length > MAX_HISTORY_STEPS) {
38703
+ this.undoStack.shift();
38704
+ }
38705
+ }
38706
+ /**
38707
+ * Fetch the last revision which is not empty and not a repeated command
38708
+ *
38709
+ * Ignore repeated commands (REQUEST_REDO command as root command)
38710
+ * Ignore standard undo/redo revisions (that are empty)
38711
+ */
38712
+ getPossibleRevisionToRepeat() {
38713
+ return this.session.getLastLocalNonEmptyRevision(["REQUEST_REDO"]);
38714
+ }
38715
+ }
38716
+ HistoryPlugin.getters = ["canUndo", "canRedo"];
38717
+
38285
38718
  /** Abstract state of the clipboard when copying/cutting content that is pasted in cells of the sheet */
38286
38719
  class ClipboardCellsAbstractState {
38287
38720
  constructor(operation, getters, dispatch, selection) {
@@ -38991,7 +39424,7 @@
38991
39424
  return 0 /* CommandResult.Success */;
38992
39425
  }
38993
39426
  handle(cmd) {
38994
- var _a, _b;
39427
+ var _a, _b, _c;
38995
39428
  switch (cmd.type) {
38996
39429
  case "COPY":
38997
39430
  case "CUT":
@@ -39009,6 +39442,7 @@
39009
39442
  if (this.state.operation === "CUT") {
39010
39443
  this.state = undefined;
39011
39444
  }
39445
+ this.lastPasteState = this.state;
39012
39446
  this.status = "invisible";
39013
39447
  break;
39014
39448
  case "CLEAN_CLIPBOARD_HIGHLIGHT":
@@ -39061,10 +39495,19 @@
39061
39495
  break;
39062
39496
  }
39063
39497
  case "PASTE_FROM_OS_CLIPBOARD":
39064
- const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
39065
- state.paste(cmd.target);
39498
+ this.state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
39499
+ this.state.paste(cmd.target);
39500
+ this.lastPasteState = this.state;
39066
39501
  this.status = "invisible";
39067
39502
  break;
39503
+ case "REPEAT_PASTE": {
39504
+ (_c = this.lastPasteState) === null || _c === void 0 ? void 0 : _c.paste(cmd.target, {
39505
+ pasteOption: cmd.pasteOption,
39506
+ shouldPasteCF: true,
39507
+ selectTarget: true,
39508
+ });
39509
+ break;
39510
+ }
39068
39511
  case "ACTIVATE_PAINT_FORMAT": {
39069
39512
  const zones = this.getters.getSelectedZones();
39070
39513
  this.state = this.getClipboardStateForCopyCells(zones, "COPY");
@@ -40545,7 +40988,8 @@
40545
40988
  .add("automatic_sum", AutomaticSumPlugin)
40546
40989
  .add("format", FormatPlugin)
40547
40990
  .add("cell_popovers", CellPopoverPlugin)
40548
- .add("selection_multiuser", SelectionMultiUserPlugin);
40991
+ .add("selection_multiuser", SelectionMultiUserPlugin)
40992
+ .add("history", HistoryPlugin);
40549
40993
  // Plugins which have a state, but which should not be shared in collaborative
40550
40994
  const statefulUIPluginRegistry = new Registry()
40551
40995
  .add("selection", GridSelectionPlugin)
@@ -43232,6 +43676,14 @@
43232
43676
  this.revertBefore(operationId);
43233
43677
  this.tree.drop(operationId);
43234
43678
  }
43679
+ getRevertedExecution() {
43680
+ const data = [];
43681
+ const operations = this.tree.revertedExecution(this.HEAD_BRANCH);
43682
+ for (const { operation } of operations) {
43683
+ data.push(operation.data);
43684
+ }
43685
+ return data;
43686
+ }
43235
43687
  /**
43236
43688
  * Revert the state as it was *before* the given operation was executed.
43237
43689
  */
@@ -43288,12 +43740,12 @@
43288
43740
  }
43289
43741
  });
43290
43742
  revision.setChanges(changes);
43291
- }, (revision) => revertChanges([revision]), (id) => new Revision(id, "empty", [], []), {
43743
+ }, (revision) => revertChanges([revision]), (id) => new Revision(id, "empty", []), {
43292
43744
  with: (revision) => (toTransform) => {
43293
- return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands));
43745
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands), toTransform.rootCommand);
43294
43746
  },
43295
43747
  without: (revision) => (toTransform) => {
43296
- return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()));
43748
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()), toTransform.rootCommand);
43297
43749
  },
43298
43750
  });
43299
43751
  }
@@ -43330,101 +43782,6 @@
43330
43782
  }
43331
43783
  }
43332
43784
 
43333
- /**
43334
- * Local History
43335
- *
43336
- * The local history is responsible of tracking the locally state updates
43337
- * It maintains the local undo and redo stack to allow to undo/redo only local
43338
- * changes
43339
- */
43340
- class LocalHistory extends owl.EventBus {
43341
- constructor(dispatch, session) {
43342
- super();
43343
- this.dispatch = dispatch;
43344
- this.session = session;
43345
- /**
43346
- * Ids of the revisions which can be undone
43347
- */
43348
- this.undoStack = [];
43349
- /**
43350
- * Ids of the revisions which can be redone
43351
- */
43352
- this.redoStack = [];
43353
- this.session.on("new-local-state-update", this, this.onNewLocalStateUpdate);
43354
- this.session.on("revision-undone", this, ({ commands }) => this.selectiveUndo(commands));
43355
- this.session.on("revision-redone", this, ({ commands }) => this.selectiveRedo(commands));
43356
- this.session.on("pending-revisions-dropped", this, ({ revisionIds }) => this.drop(revisionIds));
43357
- this.session.on("snapshot", this, () => {
43358
- this.undoStack = [];
43359
- this.redoStack = [];
43360
- });
43361
- }
43362
- allowDispatch(cmd) {
43363
- switch (cmd.type) {
43364
- case "REQUEST_UNDO":
43365
- if (!this.canUndo()) {
43366
- return 6 /* CommandResult.EmptyUndoStack */;
43367
- }
43368
- break;
43369
- case "REQUEST_REDO":
43370
- if (!this.canRedo()) {
43371
- return 7 /* CommandResult.EmptyRedoStack */;
43372
- }
43373
- break;
43374
- }
43375
- return 0 /* CommandResult.Success */;
43376
- }
43377
- beforeHandle(cmd) { }
43378
- handle(cmd) {
43379
- switch (cmd.type) {
43380
- case "REQUEST_UNDO":
43381
- case "REQUEST_REDO":
43382
- // History changes (undo & redo) are *not* applied optimistically on the local state.
43383
- // We wait a global confirmation from the server. The goal is to avoid handling concurrent
43384
- // history changes on multiple clients which are very hard to manage correctly.
43385
- this.requestHistoryChange(cmd.type === "REQUEST_UNDO" ? "UNDO" : "REDO");
43386
- }
43387
- }
43388
- finalize() { }
43389
- requestHistoryChange(type) {
43390
- const id = type === "UNDO" ? this.undoStack.pop() : this.redoStack.pop();
43391
- if (!id) {
43392
- return;
43393
- }
43394
- if (type === "UNDO") {
43395
- this.session.undo(id);
43396
- this.redoStack.push(id);
43397
- }
43398
- else {
43399
- this.session.redo(id);
43400
- this.undoStack.push(id);
43401
- }
43402
- }
43403
- canUndo() {
43404
- return this.undoStack.length > 0;
43405
- }
43406
- canRedo() {
43407
- return this.redoStack.length > 0;
43408
- }
43409
- drop(revisionIds) {
43410
- this.undoStack = this.undoStack.filter((id) => !revisionIds.includes(id));
43411
- this.redoStack = [];
43412
- }
43413
- onNewLocalStateUpdate({ id }) {
43414
- this.undoStack.push(id);
43415
- this.redoStack = [];
43416
- if (this.undoStack.length > MAX_HISTORY_STEPS) {
43417
- this.undoStack.shift();
43418
- }
43419
- }
43420
- selectiveUndo(commands) {
43421
- this.dispatch("UNDO", { commands });
43422
- }
43423
- selectiveRedo(commands) {
43424
- this.dispatch("REDO", { commands });
43425
- }
43426
- }
43427
-
43428
43785
  /**
43429
43786
  * Stateless sequence of events that can be processed by consumers.
43430
43787
  *
@@ -43825,8 +44182,8 @@
43825
44182
  }
43826
44183
  const { left, right, top, bottom } = zone;
43827
44184
  const sheetId = this.getters.getActiveSheetId();
43828
- const refCol = this.getters.findVisibleHeader(sheetId, "COL", range(left, right + 1));
43829
- const refRow = this.getters.findVisibleHeader(sheetId, "ROW", range(top, bottom + 1));
44185
+ const refCol = this.getters.findVisibleHeader(sheetId, "COL", left, right);
44186
+ const refRow = this.getters.findVisibleHeader(sheetId, "ROW", top, bottom);
43830
44187
  if (refRow === undefined || refCol === undefined) {
43831
44188
  return 17 /* CommandResult.SelectionOutOfBound */;
43832
44189
  }
@@ -43901,10 +44258,10 @@
43901
44258
  const { col: anchorCol, row: anchorRow } = anchor.cell;
43902
44259
  return {
43903
44260
  col: this.getters.isColHidden(sheetId, anchorCol)
43904
- ? this.getters.findVisibleHeader(sheetId, "COL", range(left, right + 1)) || anchorCol
44261
+ ? this.getters.findVisibleHeader(sheetId, "COL", left, right) || anchorCol
43905
44262
  : anchorCol,
43906
44263
  row: this.getters.isRowHidden(sheetId, anchorRow)
43907
- ? this.getters.findVisibleHeader(sheetId, "ROW", range(top, bottom + 1)) || anchorRow
44264
+ ? this.getters.findVisibleHeader(sheetId, "ROW", top, bottom) || anchorRow
43908
44265
  : anchorRow,
43909
44266
  };
43910
44267
  }
@@ -45654,6 +46011,8 @@
45654
46011
  * Internal status of the model. Important for command handling coordination
45655
46012
  */
45656
46013
  this.status = 0 /* Status.Ready */;
46014
+ this.handlers = [];
46015
+ this.coreHandlers = [];
45657
46016
  /**
45658
46017
  * Check if a command can be dispatched, and returns a DispatchResult object with the possible
45659
46018
  * reasons the dispatch failed.
@@ -45698,7 +46057,7 @@
45698
46057
  this.dispatchToHandlers(this.handlers, command);
45699
46058
  this.finalize();
45700
46059
  });
45701
- this.session.save(commands, changes);
46060
+ this.session.save(command, commands, changes);
45702
46061
  this.status = 0 /* Status.Ready */;
45703
46062
  this.trigger("update");
45704
46063
  break;
@@ -45730,9 +46089,7 @@
45730
46089
  const command = { ...payload, type };
45731
46090
  const previousStatus = this.status;
45732
46091
  this.status = 2 /* Status.RunningCore */;
45733
- const handlers = this.isReplayingCommand
45734
- ? [this.range, ...this.corePlugins, ...this.coreViewsPlugins]
45735
- : this.handlers;
46092
+ const handlers = this.isReplayingCommand ? this.coreHandlers : this.handlers;
45736
46093
  this.dispatchToHandlers(handlers, command);
45737
46094
  this.status = previousStatus;
45738
46095
  return DispatchResult.Success;
@@ -45743,7 +46100,6 @@
45743
46100
  this.uuidGenerator = uuidGenerator;
45744
46101
  this.config = this.setupConfig(config);
45745
46102
  this.session = this.setupSession(workbookData.revisionId);
45746
- this.history = new LocalHistory(this.dispatchFromCorePlugin, this.session);
45747
46103
  this.coreGetters = {};
45748
46104
  this.range = new RangeAdapter(this.coreGetters);
45749
46105
  this.coreGetters.getRangeString = this.range.getRangeString.bind(this.range);
@@ -45756,8 +46112,6 @@
45756
46112
  this.getters = {
45757
46113
  isReadonly: () => this.config.mode === "readonly" || this.config.mode === "dashboard",
45758
46114
  isDashboard: () => this.config.mode === "dashboard",
45759
- canUndo: this.history.canUndo.bind(this.history),
45760
- canRedo: this.history.canRedo.bind(this.history),
45761
46115
  getClient: this.session.getClient.bind(this.session),
45762
46116
  getConnectedClients: this.session.getConnectedClients.bind(this.session),
45763
46117
  isFullySynchronized: this.session.isFullySynchronized.bind(this.session),
@@ -45765,6 +46119,8 @@
45765
46119
  this.uuidGenerator.setIsFastStrategy(true);
45766
46120
  // Initiate stream processor
45767
46121
  this.selection = new SelectionStreamProcessorImpl(this.getters);
46122
+ this.coreHandlers.push(this.range);
46123
+ this.handlers.push(this.range);
45768
46124
  this.corePluginConfig = this.setupCorePluginConfig();
45769
46125
  this.uiPluginConfig = this.setupUiPluginConfig();
45770
46126
  // registering plugins
@@ -45773,13 +46129,20 @@
45773
46129
  }
45774
46130
  Object.assign(this.getters, this.coreGetters);
45775
46131
  for (let Plugin of statefulUIPluginRegistry.getAll()) {
45776
- this.statefulUIPlugins.push(this.setupUiPlugin(Plugin));
46132
+ const plugin = this.setupUiPlugin(Plugin);
46133
+ this.statefulUIPlugins.push(plugin);
46134
+ this.handlers.push(plugin);
45777
46135
  }
45778
46136
  for (let Plugin of coreViewsPluginRegistry.getAll()) {
45779
- this.coreViewsPlugins.push(this.setupUiPlugin(Plugin));
46137
+ const plugin = this.setupUiPlugin(Plugin);
46138
+ this.coreViewsPlugins.push(plugin);
46139
+ this.handlers.push(plugin);
46140
+ this.coreHandlers.push(plugin);
45780
46141
  }
45781
46142
  for (let Plugin of featurePluginRegistry.getAll()) {
45782
- this.featurePlugins.push(this.setupUiPlugin(Plugin));
46143
+ const plugin = this.setupUiPlugin(Plugin);
46144
+ this.featurePlugins.push(plugin);
46145
+ this.handlers.push(plugin);
45783
46146
  }
45784
46147
  this.uuidGenerator.setIsFastStrategy(false);
45785
46148
  // starting plugins
@@ -45803,15 +46166,6 @@
45803
46166
  // by owl, since we do not rely on reactivity
45804
46167
  owl.markRaw(this);
45805
46168
  }
45806
- get handlers() {
45807
- return [...this.coreHandlers, ...this.allUIPlugins, this.history];
45808
- }
45809
- get coreHandlers() {
45810
- return [this.range, ...this.corePlugins];
45811
- }
45812
- get allUIPlugins() {
45813
- return [...this.statefulUIPlugins, ...this.coreViewsPlugins, ...this.featurePlugins];
45814
- }
45815
46169
  joinSession() {
45816
46170
  this.session.join(this.config.client);
45817
46171
  }
@@ -45853,6 +46207,8 @@
45853
46207
  }
45854
46208
  plugin.import(data);
45855
46209
  this.corePlugins.push(plugin);
46210
+ this.coreHandlers.push(plugin);
46211
+ this.handlers.push(plugin);
45856
46212
  }
45857
46213
  onRemoteRevisionReceived({ commands }) {
45858
46214
  for (let command of commands) {
@@ -45870,15 +46226,21 @@
45870
46226
  return;
45871
46227
  }
45872
46228
  this.isReplayingCommand = true;
45873
- this.dispatchToHandlers([this.range, ...this.corePlugins, ...this.coreViewsPlugins], command);
46229
+ this.dispatchToHandlers(this.coreHandlers, command);
45874
46230
  this.isReplayingCommand = false;
45875
46231
  }), this.config.transportService, revisionId);
45876
46232
  return session;
45877
46233
  }
45878
46234
  setupSessionEvents() {
45879
46235
  this.session.on("remote-revision-received", this, this.onRemoteRevisionReceived);
45880
- this.session.on("revision-redone", this, this.finalize);
45881
- this.session.on("revision-undone", this, this.finalize);
46236
+ this.session.on("revision-undone", this, ({ commands }) => {
46237
+ this.dispatchFromCorePlugin("UNDO", { commands });
46238
+ this.finalize();
46239
+ });
46240
+ this.session.on("revision-redone", this, ({ commands }) => {
46241
+ this.dispatchFromCorePlugin("REDO", { commands });
46242
+ this.finalize();
46243
+ });
45882
46244
  // How could we improve communication between the session and UI?
45883
46245
  // It feels weird to have the model piping specific session events to its own bus.
45884
46246
  this.session.on("unexpected-revision-id", this, () => this.trigger("unexpected-revision-id"));
@@ -45926,6 +46288,7 @@
45926
46288
  custom: this.config.custom,
45927
46289
  uiActions: this.config,
45928
46290
  lazyEvaluation: this.config.lazyEvaluation,
46291
+ session: this.session,
45929
46292
  };
45930
46293
  }
45931
46294
  // ---------------------------------------------------------------------------
@@ -45941,7 +46304,8 @@
45941
46304
  return this.checkDispatchAllowedLocalCommand(command);
45942
46305
  }
45943
46306
  checkDispatchAllowedCoreCommand(command) {
45944
- const results = this.coreHandlers.map((handler) => handler.allowDispatch(command));
46307
+ const results = this.corePlugins.map((handler) => handler.allowDispatch(command));
46308
+ results.push(this.range.allowDispatch(command));
45945
46309
  return new DispatchResult(results.flat());
45946
46310
  }
45947
46311
  checkDispatchAllowedLocalCommand(command) {
@@ -46005,7 +46369,7 @@
46005
46369
  }
46006
46370
  }
46007
46371
  data.revisionId = this.session.getRevisionId() || DEFAULT_REVISION_ID;
46008
- data = JSON.parse(JSON.stringify(data));
46372
+ data = deepCopy(data);
46009
46373
  return data;
46010
46374
  }
46011
46375
  updateMode(mode) {
@@ -46033,7 +46397,7 @@
46033
46397
  handler.exportForExcel(data);
46034
46398
  }
46035
46399
  }
46036
- data = JSON.parse(JSON.stringify(data));
46400
+ data = deepCopy(data);
46037
46401
  return getXLSX(data);
46038
46402
  }
46039
46403
  garbageCollectExternalResources() {
@@ -46086,6 +46450,8 @@
46086
46450
  urlRegistry,
46087
46451
  cellPopoverRegistry,
46088
46452
  numberFormatMenuRegistry,
46453
+ repeatLocalCommandTransformRegistry,
46454
+ repeatCommandTransformRegistry,
46089
46455
  };
46090
46456
  const helpers = {
46091
46457
  arg,
@@ -46114,6 +46480,7 @@
46114
46480
  positionToZone,
46115
46481
  isDefined: isDefined$1,
46116
46482
  lazy,
46483
+ genericRepeat,
46117
46484
  };
46118
46485
  const links = {
46119
46486
  isMarkdownLink,
@@ -46177,9 +46544,9 @@
46177
46544
  Object.defineProperty(exports, '__esModule', { value: true });
46178
46545
 
46179
46546
 
46180
- __info__.version = '16.3.0-alpha.4';
46181
- __info__.date = '2023-04-14T12:49:16.671Z';
46182
- __info__.hash = '6824b8a';
46547
+ __info__.version = '16.3.0-alpha.5';
46548
+ __info__.date = '2023-04-21T07:28:52.982Z';
46549
+ __info__.hash = '6304f41';
46183
46550
 
46184
46551
 
46185
46552
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);