@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.
@@ -1724,12 +1724,77 @@ const numberRepresentation = [];
1724
1724
  * - all digit stored in the decimal part of the number
1725
1725
  *
1726
1726
  * The 'maxDecimal' parameter allows to indicate the number of digits to not
1727
- * exceed in the decimal part, in which case digits are rounded
1727
+ * exceed in the decimal part, in which case digits are rounded.
1728
1728
  *
1729
- * Intl.Numberformat is used to properly handle all the roundings.
1730
- * e.g. 1234.7 with format ### (<> maxDecimals=0) should become 1235, not 1234
1731
1729
  **/
1732
1730
  function splitNumber(value, maxDecimals = MAX_DECIMAL_PLACES) {
1731
+ const asString = value.toString();
1732
+ if (asString.includes("e"))
1733
+ return splitNumberIntl(value, maxDecimals);
1734
+ if (Number.isInteger(value)) {
1735
+ return { integerDigits: asString, decimalDigits: undefined };
1736
+ }
1737
+ const indexOfDot = asString.indexOf(".");
1738
+ let integerDigits = asString.substring(0, indexOfDot);
1739
+ let decimalDigits = asString.substring(indexOfDot + 1);
1740
+ if (maxDecimals === 0) {
1741
+ if (Number(decimalDigits[0]) >= 5) {
1742
+ integerDigits = (Number(integerDigits) + 1).toString();
1743
+ }
1744
+ return { integerDigits, decimalDigits: undefined };
1745
+ }
1746
+ if (decimalDigits.length > maxDecimals) {
1747
+ const { integerDigits: roundedIntegerDigits, decimalDigits: roundedDecimalDigits } = limitDecimalDigits(decimalDigits, maxDecimals);
1748
+ decimalDigits = roundedDecimalDigits;
1749
+ if (roundedIntegerDigits !== "0") {
1750
+ integerDigits = (Number(integerDigits) + Number(roundedIntegerDigits)).toString();
1751
+ }
1752
+ }
1753
+ return { integerDigits, decimalDigits: removeTrailingZeroes(decimalDigits || "") };
1754
+ }
1755
+ /**
1756
+ * Return the given string minus the trailing "0" characters.
1757
+ *
1758
+ * @param numberString : a string of integers
1759
+ * @returns the numberString, minus the eventual zeroes at the end
1760
+ */
1761
+ function removeTrailingZeroes(numberString) {
1762
+ let i = numberString.length - 1;
1763
+ while (i >= 0 && numberString[i] === "0") {
1764
+ i--;
1765
+ }
1766
+ return numberString.slice(0, i + 1) || undefined;
1767
+ }
1768
+ /**
1769
+ * Limit the size of the decimal part of a number to the given number of digits.
1770
+ */
1771
+ function limitDecimalDigits(decimalDigits, maxDecimals) {
1772
+ let integerDigits = "0";
1773
+ let resultDecimalDigits = decimalDigits;
1774
+ // Note : we'd want to simply use number.toFixed() to handle the max digits & rounding,
1775
+ // but it has very strange behaviour. Ex: 12.345.toFixed(2) => "12.35", but 1.345.toFixed(2) => "1.34"
1776
+ let slicedDecimalDigits = decimalDigits.slice(0, maxDecimals);
1777
+ const i = maxDecimals;
1778
+ if (Number(Number(decimalDigits[i]) < 5)) {
1779
+ return { integerDigits, decimalDigits: slicedDecimalDigits };
1780
+ }
1781
+ // round up
1782
+ const slicedRoundedUp = (Number(slicedDecimalDigits) + 1).toString();
1783
+ if (slicedRoundedUp.length > slicedDecimalDigits.length) {
1784
+ integerDigits = (Number(integerDigits) + 1).toString();
1785
+ resultDecimalDigits = undefined;
1786
+ }
1787
+ else {
1788
+ resultDecimalDigits = slicedRoundedUp;
1789
+ }
1790
+ return { integerDigits, decimalDigits: resultDecimalDigits };
1791
+ }
1792
+ /**
1793
+ * Split numbers into decimal/integer digits using Intl.NumberFormat.
1794
+ * Supports numbers with a lot of digits that are transformed to scientific notation by
1795
+ * number.toString(), but is slow.
1796
+ */
1797
+ function splitNumberIntl(value, maxDecimals = MAX_DECIMAL_PLACES) {
1733
1798
  let formatter = numberRepresentation[maxDecimals];
1734
1799
  if (!formatter) {
1735
1800
  formatter = new Intl.NumberFormat("en-US", {
@@ -5764,12 +5829,12 @@ const hideSheet = {
5764
5829
  function createActions(menuItems) {
5765
5830
  return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
5766
5831
  }
5767
- const uuidGenerator$1 = new UuidGenerator();
5832
+ const uuidGenerator$2 = new UuidGenerator();
5768
5833
  function createAction(item) {
5769
5834
  const name = item.name;
5770
5835
  const children = item.children;
5771
5836
  return {
5772
- id: item.id || uuidGenerator$1.uuidv4(),
5837
+ id: item.id || uuidGenerator$2.uuidv4(),
5773
5838
  name: typeof name === "function" ? name : () => name,
5774
5839
  isVisible: item.isVisible ? item.isVisible : () => true,
5775
5840
  isEnabled: item.isEnabled ? item.isEnabled : () => true,
@@ -17439,7 +17504,7 @@ function updateSelectionWithArrowKeys(ev, selection) {
17439
17504
  }
17440
17505
  }
17441
17506
 
17442
- const uuidGenerator = new UuidGenerator();
17507
+ const uuidGenerator$1 = new UuidGenerator();
17443
17508
  css /* scss */ `
17444
17509
  .o-selection {
17445
17510
  .o-selection-input {
@@ -17496,7 +17561,7 @@ css /* scss */ `
17496
17561
  class SelectionInput extends owl.Component {
17497
17562
  constructor() {
17498
17563
  super(...arguments);
17499
- this.id = uuidGenerator.uuidv4();
17564
+ this.id = uuidGenerator$1.uuidv4();
17500
17565
  this.previousRanges = this.props.ranges || [];
17501
17566
  this.originSheet = this.env.model.getters.getActiveSheetId();
17502
17567
  this.state = owl.useState({
@@ -21189,13 +21254,26 @@ class FilterIconsOverlay extends owl.Component {
21189
21254
  const sheetId = this.env.model.getters.getActiveSheetId();
21190
21255
  const rowDims = this.env.model.getters.getRowDimensionsInViewport(sheetId, position.row);
21191
21256
  const colDims = this.env.model.getters.getColDimensionsInViewport(sheetId, position.col);
21192
- // TODO : change this offset when we support vertical cell align
21193
- const centeringOffset = Math.floor((rowDims.size - FILTER_ICON_EDGE_LENGTH) / 2);
21257
+ const cell = this.env.model.getters.getCell({ sheetId, ...position });
21258
+ const verticalFilterIconPosition = this.getIconVerticalPosition(rowDims, cell);
21194
21259
  return {
21195
21260
  x: colDims.end - FILTER_ICON_EDGE_LENGTH + this.props.gridPosition.x - FILTER_ICON_MARGIN - 1,
21196
- y: rowDims.end - FILTER_ICON_EDGE_LENGTH + this.props.gridPosition.y - centeringOffset,
21261
+ y: verticalFilterIconPosition + this.props.gridPosition.y,
21197
21262
  };
21198
21263
  }
21264
+ // Calculates the vertical position of the filter icon based on the row dimensions and cell styles.
21265
+ getIconVerticalPosition(rowDims, cell) {
21266
+ var _a;
21267
+ const centeringOffset = Math.floor((rowDims.size - FILTER_ICON_EDGE_LENGTH) / 2);
21268
+ switch ((_a = cell === null || cell === void 0 ? void 0 : cell.style) === null || _a === void 0 ? void 0 : _a.verticalAlign) {
21269
+ case "bottom":
21270
+ return rowDims.end - FILTER_ICON_MARGIN - FILTER_ICON_EDGE_LENGTH;
21271
+ case "top":
21272
+ return rowDims.start + FILTER_ICON_MARGIN;
21273
+ default:
21274
+ return rowDims.end - FILTER_ICON_EDGE_LENGTH - centeringOffset;
21275
+ }
21276
+ }
21199
21277
  isFilterActive(position) {
21200
21278
  const sheetId = this.env.model.getters.getActiveSheetId();
21201
21279
  return this.env.model.getters.isFilterActive({ sheetId, ...position });
@@ -23111,6 +23189,7 @@ class Grid extends owl.Component {
23111
23189
  "CTRL+A": () => this.env.model.selection.loopSelection(),
23112
23190
  "CTRL+Z": () => this.env.model.dispatch("REQUEST_UNDO"),
23113
23191
  "CTRL+Y": () => this.env.model.dispatch("REQUEST_REDO"),
23192
+ F4: () => this.env.model.dispatch("REQUEST_REDO"),
23114
23193
  "CTRL+B": () => this.env.model.dispatch("SET_FORMATTING", {
23115
23194
  sheetId: this.env.model.getters.getActiveSheetId(),
23116
23195
  target: this.env.model.getters.getSelectedZones(),
@@ -23154,8 +23233,8 @@ class Grid extends owl.Component {
23154
23233
  },
23155
23234
  "CTRL+END": () => {
23156
23235
  const sheetId = this.env.model.getters.getActiveSheetId();
23157
- const col = this.env.model.getters.findVisibleHeader(sheetId, "COL", range(0, this.env.model.getters.getNumberCols(sheetId)).reverse());
23158
- const row = this.env.model.getters.findVisibleHeader(sheetId, "ROW", range(0, this.env.model.getters.getNumberRows(sheetId)).reverse());
23236
+ const col = this.env.model.getters.findVisibleHeader(sheetId, "COL", this.env.model.getters.getNumberCols(sheetId) - 1, 0);
23237
+ const row = this.env.model.getters.findVisibleHeader(sheetId, "ROW", this.env.model.getters.getNumberRows(sheetId) - 1, 0);
23159
23238
  this.env.model.selection.selectCell(col, row);
23160
23239
  },
23161
23240
  "SHIFT+ ": () => {
@@ -23235,9 +23314,10 @@ class Grid extends owl.Component {
23235
23314
  this.focus();
23236
23315
  }
23237
23316
  focus() {
23317
+ var _a;
23238
23318
  if (!this.env.model.getters.getSelectedFigureId() &&
23239
23319
  this.env.model.getters.getEditionMode() === "inactive") {
23240
- this.hiddenInput.el.focus();
23320
+ (_a = this.hiddenInput.el) === null || _a === void 0 ? void 0 : _a.focus();
23241
23321
  }
23242
23322
  }
23243
23323
  get gridEl() {
@@ -27157,7 +27237,7 @@ function load(data, verboseImport) {
27157
27237
  }
27158
27238
  }
27159
27239
  }
27160
- data = JSON.parse(JSON.stringify(data));
27240
+ data = deepCopy(data);
27161
27241
  // apply migrations, if needed
27162
27242
  if ("version" in data) {
27163
27243
  if (data.version < CURRENT_VERSION) {
@@ -32275,7 +32355,7 @@ class SheetPlugin extends CorePlugin {
32275
32355
  duplicateSheet(fromId, toId) {
32276
32356
  const sheet = this.getSheet(fromId);
32277
32357
  const toName = this.getDuplicateSheetName(sheet.name);
32278
- const newSheet = JSON.parse(JSON.stringify(sheet));
32358
+ const newSheet = deepCopy(sheet);
32279
32359
  newSheet.id = toId;
32280
32360
  newSheet.name = toName;
32281
32361
  for (let col = 0; col <= newSheet.numberOfCols; col++) {
@@ -32402,10 +32482,10 @@ class SheetPlugin extends CorePlugin {
32402
32482
  }
32403
32483
  }
32404
32484
  moveCellOnColumnsDeletion(sheet, deletedColumn) {
32405
- for (let [index, row] of Object.entries(sheet.rows)) {
32406
- const rowIndex = parseInt(index, 10);
32485
+ for (let rowIndex = 0; rowIndex < sheet.rows.length; rowIndex++) {
32486
+ const row = sheet.rows[rowIndex];
32407
32487
  for (let i in row.cells) {
32408
- const colIndex = parseInt(i, 10);
32488
+ const colIndex = Number(i);
32409
32489
  const cellId = row.cells[i];
32410
32490
  if (cellId) {
32411
32491
  if (colIndex === deletedColumn) {
@@ -32432,11 +32512,11 @@ class SheetPlugin extends CorePlugin {
32432
32512
  */
32433
32513
  moveCellsOnAddition(sheet, addedElement, quantity, dimension) {
32434
32514
  const commands = [];
32435
- for (const [index, row] of Object.entries(sheet.rows)) {
32436
- const rowIndex = parseInt(index, 10);
32515
+ for (let rowIndex = 0; rowIndex < sheet.rows.length; rowIndex++) {
32516
+ const row = sheet.rows[rowIndex];
32437
32517
  if (dimension !== "rows" || rowIndex >= addedElement) {
32438
32518
  for (let i in row.cells) {
32439
- const colIndex = parseInt(i, 10);
32519
+ const colIndex = Number(i);
32440
32520
  const cellId = row.cells[i];
32441
32521
  if (cellId) {
32442
32522
  if (dimension === "rows" || colIndex >= addedElement) {
@@ -32466,11 +32546,11 @@ class SheetPlugin extends CorePlugin {
32466
32546
  */
32467
32547
  moveCellOnRowsDeletion(sheet, deleteFromRow, deleteToRow) {
32468
32548
  const numberRows = deleteToRow - deleteFromRow + 1;
32469
- for (let [index, row] of Object.entries(sheet.rows)) {
32470
- const rowIndex = parseInt(index, 10);
32549
+ for (let rowIndex = 0; rowIndex < sheet.rows.length; rowIndex++) {
32550
+ const row = sheet.rows[rowIndex];
32471
32551
  if (rowIndex >= deleteFromRow && rowIndex <= deleteToRow) {
32472
32552
  for (let i in row.cells) {
32473
- const colIndex = parseInt(i, 10);
32553
+ const colIndex = Number(i);
32474
32554
  const cellId = row.cells[i];
32475
32555
  if (cellId) {
32476
32556
  this.dispatch("CLEAR_CELL", {
@@ -32483,7 +32563,7 @@ class SheetPlugin extends CorePlugin {
32483
32563
  }
32484
32564
  if (rowIndex > deleteToRow) {
32485
32565
  for (let i in row.cells) {
32486
- const colIndex = parseInt(i, 10);
32566
+ const colIndex = Number(i);
32487
32567
  const cellId = row.cells[i];
32488
32568
  if (cellId) {
32489
32569
  this.dispatch("UPDATE_CELL_POSITION", {
@@ -32501,7 +32581,7 @@ class SheetPlugin extends CorePlugin {
32501
32581
  const rows = [];
32502
32582
  const cellsQueue = sheet.rows.map((row) => row.cells);
32503
32583
  for (let i in sheet.rows) {
32504
- if (parseInt(i, 10) === index) {
32584
+ if (Number(i) === index) {
32505
32585
  continue;
32506
32586
  }
32507
32587
  rows.push({
@@ -33672,13 +33752,11 @@ class FilterEvaluationPlugin extends UIPlugin {
33672
33752
  return Boolean(id && ((_b = (_a = this.filterValues[sheetId]) === null || _a === void 0 ? void 0 : _a[id]) === null || _b === void 0 ? void 0 : _b.length));
33673
33753
  }
33674
33754
  intersectZoneWithViewport(sheetId, zone) {
33675
- const colsRange = range(zone.left, zone.right + 1);
33676
- const rowsRange = range(zone.top, zone.bottom + 1);
33677
33755
  return {
33678
- left: this.getters.findVisibleHeader(sheetId, "COL", colsRange),
33679
- right: this.getters.findVisibleHeader(sheetId, "COL", colsRange.reverse()),
33680
- top: this.getters.findVisibleHeader(sheetId, "ROW", rowsRange),
33681
- bottom: this.getters.findVisibleHeader(sheetId, "ROW", rowsRange.reverse()),
33756
+ left: this.getters.findVisibleHeader(sheetId, "COL", zone.left, zone.right),
33757
+ right: this.getters.findVisibleHeader(sheetId, "COL", zone.right, zone.left),
33758
+ top: this.getters.findVisibleHeader(sheetId, "ROW", zone.top, zone.bottom),
33759
+ bottom: this.getters.findVisibleHeader(sheetId, "ROW", zone.bottom, zone.top),
33682
33760
  };
33683
33761
  }
33684
33762
  updateFilter({ col, row, values, sheetId }) {
@@ -35800,13 +35878,33 @@ class HeaderVisibilityUIPlugin extends UIPlugin {
35800
35878
  getNextVisibleCellPosition({ sheetId, col, row }) {
35801
35879
  return {
35802
35880
  sheetId,
35803
- col: this.findVisibleHeader(sheetId, "COL", range(col, this.getters.getNumberCols(sheetId))),
35804
- row: this.findVisibleHeader(sheetId, "ROW", range(row, this.getters.getNumberRows(sheetId))),
35881
+ col: this.findVisibleHeader(sheetId, "COL", col, this.getters.getNumberCols(sheetId) - 1),
35882
+ row: this.findVisibleHeader(sheetId, "ROW", row, this.getters.getNumberRows(sheetId) - 1),
35805
35883
  };
35806
35884
  }
35807
- findVisibleHeader(sheetId, dimension, indexes) {
35808
- return indexes.find((index) => this.getters.doesHeaderExist(sheetId, dimension, index) &&
35809
- !this.isHeaderHidden(sheetId, dimension, index));
35885
+ /**
35886
+ * Find the first visible header in the range [`from` => `to`].
35887
+ *
35888
+ * Both `from` and `to` are inclusive.
35889
+ */
35890
+ findVisibleHeader(sheetId, dimension, from, to) {
35891
+ if (from <= to) {
35892
+ for (let i = from; i <= to; i++) {
35893
+ if (this.getters.doesHeaderExist(sheetId, dimension, i) &&
35894
+ !this.isHeaderHidden(sheetId, dimension, i)) {
35895
+ return i;
35896
+ }
35897
+ }
35898
+ }
35899
+ if (from > to) {
35900
+ for (let i = from; i >= to; i--) {
35901
+ if (this.getters.doesHeaderExist(sheetId, dimension, i) &&
35902
+ !this.isHeaderHidden(sheetId, dimension, i)) {
35903
+ return i;
35904
+ }
35905
+ }
35906
+ }
35907
+ return undefined;
35810
35908
  }
35811
35909
  findLastVisibleColRowIndex(sheetId, dimension, { last, first }) {
35812
35910
  const lastVisibleIndex = range(last, first, -1).find((index) => !this.isHeaderHidden(sheetId, dimension, index));
@@ -37267,7 +37365,8 @@ class Revision {
37267
37365
  * - clientId: Client who initiated the action
37268
37366
  * - changes: List of changes applied on the state.
37269
37367
  */
37270
- constructor(id, clientId, commands, changes) {
37368
+ constructor(id, clientId, commands, rootCommand, changes) {
37369
+ this.rootCommand = rootCommand;
37271
37370
  this._commands = [];
37272
37371
  this._changes = [];
37273
37372
  this.id = id;
@@ -37331,10 +37430,10 @@ class Session extends EventBus {
37331
37430
  * Add a new revision to the collaborative session.
37332
37431
  * It will be transmitted to all other connected clients.
37333
37432
  */
37334
- save(commands, changes) {
37433
+ save(rootCommand, commands, changes) {
37335
37434
  if (!commands.length || !changes.length || !this.canApplyOptimisticUpdate())
37336
37435
  return;
37337
- const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, changes);
37436
+ const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes);
37338
37437
  this.revisions.append(revision.id, revision);
37339
37438
  this.trigger("new-local-state-update", { id: revision.id });
37340
37439
  this.sendUpdateMessage({
@@ -37435,6 +37534,22 @@ class Session extends EventBus {
37435
37534
  isFullySynchronized() {
37436
37535
  return this.pendingMessages.length === 0;
37437
37536
  }
37537
+ /**
37538
+ * Get the last local revision whose root command isn't in the given list of ignored commands
37539
+ * */
37540
+ getLastLocalNonEmptyRevision(ignoredRootCommands) {
37541
+ var _a;
37542
+ const revisions = this.revisions.getRevertedExecution();
37543
+ for (const rev of revisions) {
37544
+ if (rev.rootCommand === "SNAPSHOT")
37545
+ return undefined;
37546
+ if (!rev.rootCommand || rev.rootCommand === "REMOTE")
37547
+ continue;
37548
+ if (!ignoredRootCommands.includes((_a = rev.rootCommand) === null || _a === void 0 ? void 0 : _a.type) && rev.commands.length)
37549
+ return rev;
37550
+ }
37551
+ return undefined;
37552
+ }
37438
37553
  _move(position) {
37439
37554
  var _a;
37440
37555
  // this method is debounced and might be called after the client
@@ -37494,7 +37609,7 @@ class Session extends EventBus {
37494
37609
  return;
37495
37610
  }
37496
37611
  const { clientId, commands } = message;
37497
- const revision = new Revision(message.nextRevisionId, clientId, commands);
37612
+ const revision = new Revision(message.nextRevisionId, clientId, commands, "REMOTE");
37498
37613
  if (revision.clientId !== this.clientId) {
37499
37614
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
37500
37615
  const pendingCommands = this.pendingMessages
@@ -37507,7 +37622,7 @@ class Session extends EventBus {
37507
37622
  }
37508
37623
  break;
37509
37624
  case "SNAPSHOT_CREATED": {
37510
- const revision = new Revision(message.nextRevisionId, "server", []);
37625
+ const revision = new Revision(message.nextRevisionId, "server", [], "SNAPSHOT");
37511
37626
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
37512
37627
  this.dropPendingHistoryMessages();
37513
37628
  this.trigger("snapshot");
@@ -38285,6 +38400,324 @@ SheetUIPlugin.getters = [
38285
38400
  "getColRowOffset",
38286
38401
  ];
38287
38402
 
38403
+ const genericRepeatsTransforms = [
38404
+ repeatSheetDependantCommand,
38405
+ repeatTargetDependantCommand,
38406
+ repeatPositionDependantCommand,
38407
+ ];
38408
+ function repeatSheetDependantCommand(getters, command) {
38409
+ if (!("sheetId" in command))
38410
+ return command;
38411
+ return { ...deepCopy(command), sheetId: getters.getActiveSheetId() };
38412
+ }
38413
+ function repeatTargetDependantCommand(getters, command) {
38414
+ if (!("target" in command) || !Array.isArray(command.target))
38415
+ return command;
38416
+ return {
38417
+ ...deepCopy(command),
38418
+ target: getters.getSelectedZones(),
38419
+ };
38420
+ }
38421
+ function repeatPositionDependantCommand(getters, command) {
38422
+ if (!("row" in command) || !("col" in command))
38423
+ return command;
38424
+ const { col, row } = getters.getActivePosition();
38425
+ return { ...deepCopy(command), col, row };
38426
+ }
38427
+
38428
+ const uuidGenerator = new UuidGenerator();
38429
+ function repeatCreateChartCommand(getters, cmd) {
38430
+ return {
38431
+ ...repeatSheetDependantCommand(getters, cmd),
38432
+ id: uuidGenerator.uuidv4(),
38433
+ };
38434
+ }
38435
+ function repeatCreateImageCommand(getters, cmd) {
38436
+ return {
38437
+ ...repeatSheetDependantCommand(getters, cmd),
38438
+ figureId: uuidGenerator.uuidv4(),
38439
+ };
38440
+ }
38441
+ function repeatCreateFigureCommand(getters, cmd) {
38442
+ const newCmd = repeatSheetDependantCommand(getters, cmd);
38443
+ newCmd.figure.id = uuidGenerator.uuidv4();
38444
+ return newCmd;
38445
+ }
38446
+ function repeatCreateSheetCommand(getters, cmd) {
38447
+ var _a;
38448
+ const newCmd = deepCopy(cmd);
38449
+ newCmd.sheetId = uuidGenerator.uuidv4();
38450
+ const sheetName = cmd.name || getters.getSheet(getters.getActiveSheetId()).name;
38451
+ // Extract the prefix of the sheet name (everything before the number at the end of the name)
38452
+ const namePrefix = ((_a = sheetName.match(/(.+?)\d*$/)) === null || _a === void 0 ? void 0 : _a[1]) || sheetName;
38453
+ newCmd.name = getters.getNextSheetName(namePrefix);
38454
+ return newCmd;
38455
+ }
38456
+ function repeatAddColumnsRowsCommand(getters, cmd) {
38457
+ const currentPosition = getters.getActivePosition();
38458
+ return {
38459
+ ...repeatSheetDependantCommand(getters, cmd),
38460
+ base: cmd.dimension === "COL" ? currentPosition.col : currentPosition.row,
38461
+ };
38462
+ }
38463
+ function repeatHeaderElementCommand(getters, cmd) {
38464
+ const currentSelection = getters.getSelectedZone();
38465
+ return {
38466
+ ...repeatSheetDependantCommand(getters, cmd),
38467
+ elements: cmd.dimension === "COL"
38468
+ ? range(currentSelection.left, currentSelection.right + 1)
38469
+ : range(currentSelection.top, currentSelection.bottom + 1),
38470
+ };
38471
+ }
38472
+ function repeatInsertOrDeleteCellCommand(getters, cmd) {
38473
+ const currentSelection = getters.getSelectedZone();
38474
+ return {
38475
+ ...deepCopy(cmd),
38476
+ zone: currentSelection,
38477
+ };
38478
+ }
38479
+ function repeatAutoResizeCommand(getters, cmd) {
38480
+ const newCmd = deepCopy(cmd);
38481
+ const currentSelection = getters.getSelectedZone();
38482
+ const { top, bottom, left, right } = currentSelection;
38483
+ if ("cols" in newCmd) {
38484
+ newCmd.cols = range(left, right + 1);
38485
+ }
38486
+ else if ("rows" in newCmd) {
38487
+ newCmd.rows = range(top, bottom + 1);
38488
+ }
38489
+ return newCmd;
38490
+ }
38491
+ function repeatSortCellsCommand(getters, cmd) {
38492
+ const currentSelection = getters.getSelectedZone();
38493
+ return {
38494
+ ...repeatSheetDependantCommand(getters, cmd),
38495
+ col: currentSelection.left,
38496
+ row: currentSelection.top,
38497
+ zone: currentSelection,
38498
+ };
38499
+ }
38500
+ function repeatPasteCommand(getters, cmd) {
38501
+ /**
38502
+ * Note : we have to store the state of the clipboard in the clipboard plugin, and introduce a
38503
+ * new command REPEAT_PASTE to be able to repeat the paste command.
38504
+ *
38505
+ * We cannot re-dispatch a paste, because the content of the clipboard may have changed in between.
38506
+ *
38507
+ * And we cannot adapt the sub-commands of the paste command, because they are dependant on the state of the sheet,
38508
+ * and may change based on the paste location. A simple example is that paste create new col/rows for the clipboard
38509
+ * content to fit the sheet. So there will be ADD_COL_ROW_COMMANDS in the sub-commands in the history, but repeating
38510
+ * paste might not need them. Or they could only needed for the repeated paste, not for the original.
38511
+ */
38512
+ return {
38513
+ type: "REPEAT_PASTE",
38514
+ pasteOption: deepCopy(cmd.pasteOption),
38515
+ target: getters.getSelectedZones(),
38516
+ };
38517
+ }
38518
+
38519
+ /**
38520
+ * Registry containing all the command that can be repeated on redo, and function to transform them
38521
+ * to the current state of the model.
38522
+ *
38523
+ * If the transform function is undefined, the command will be transformed using generic transformations.
38524
+ * (change the sheetId, the row, the col, the target, the ranges, to the current active sheet & selection)
38525
+ *
38526
+ */
38527
+ const repeatCommandTransformRegistry = new Registry();
38528
+ repeatCommandTransformRegistry.add("UPDATE_CELL", genericRepeat);
38529
+ repeatCommandTransformRegistry.add("CLEAR_CELL", genericRepeat);
38530
+ repeatCommandTransformRegistry.add("DELETE_CONTENT", genericRepeat);
38531
+ repeatCommandTransformRegistry.add("ADD_MERGE", genericRepeat);
38532
+ repeatCommandTransformRegistry.add("REMOVE_MERGE", genericRepeat);
38533
+ repeatCommandTransformRegistry.add("SET_FORMATTING", genericRepeat);
38534
+ repeatCommandTransformRegistry.add("CLEAR_FORMATTING", genericRepeat);
38535
+ repeatCommandTransformRegistry.add("SET_BORDER", genericRepeat);
38536
+ repeatCommandTransformRegistry.add("CREATE_FILTER_TABLE", genericRepeat);
38537
+ repeatCommandTransformRegistry.add("REMOVE_FILTER_TABLE", genericRepeat);
38538
+ repeatCommandTransformRegistry.add("HIDE_SHEET", genericRepeat);
38539
+ repeatCommandTransformRegistry.add("ADD_COLUMNS_ROWS", repeatAddColumnsRowsCommand);
38540
+ repeatCommandTransformRegistry.add("REMOVE_COLUMNS_ROWS", repeatHeaderElementCommand);
38541
+ repeatCommandTransformRegistry.add("HIDE_COLUMNS_ROWS", repeatHeaderElementCommand);
38542
+ repeatCommandTransformRegistry.add("RESIZE_COLUMNS_ROWS", repeatHeaderElementCommand);
38543
+ repeatCommandTransformRegistry.add("CREATE_SHEET", repeatCreateSheetCommand);
38544
+ repeatCommandTransformRegistry.add("CREATE_FIGURE", repeatCreateFigureCommand);
38545
+ repeatCommandTransformRegistry.add("CREATE_CHART", repeatCreateChartCommand);
38546
+ repeatCommandTransformRegistry.add("CREATE_IMAGE", repeatCreateImageCommand);
38547
+ const repeatLocalCommandTransformRegistry = new Registry();
38548
+ repeatLocalCommandTransformRegistry.add("STOP_EDITION", repeatLocalCommandChildren);
38549
+ repeatLocalCommandTransformRegistry.add("PASTE", repeatPasteCommand);
38550
+ repeatLocalCommandTransformRegistry.add("INSERT_CELL", repeatInsertOrDeleteCellCommand);
38551
+ repeatLocalCommandTransformRegistry.add("DELETE_CELL", repeatInsertOrDeleteCellCommand);
38552
+ repeatLocalCommandTransformRegistry.add("AUTORESIZE_COLUMNS", repeatAutoResizeCommand);
38553
+ repeatLocalCommandTransformRegistry.add("AUTORESIZE_ROWS", repeatAutoResizeCommand);
38554
+ repeatLocalCommandTransformRegistry.add("SORT_CELLS", repeatSortCellsCommand);
38555
+ repeatLocalCommandTransformRegistry.add("SUM_SELECTION", genericRepeat);
38556
+ repeatLocalCommandTransformRegistry.add("SET_DECIMAL", genericRepeat);
38557
+ function genericRepeat(getters, command) {
38558
+ let transformedCommand = deepCopy(command);
38559
+ for (const repeatTransform of genericRepeatsTransforms) {
38560
+ transformedCommand = repeatTransform(getters, transformedCommand);
38561
+ }
38562
+ return transformedCommand;
38563
+ }
38564
+ function repeatCoreCommand(getters, command) {
38565
+ if (!command) {
38566
+ return undefined;
38567
+ }
38568
+ const isRepeatable = repeatCommandTransformRegistry.contains(command.type);
38569
+ if (!isRepeatable) {
38570
+ return undefined;
38571
+ }
38572
+ const transform = repeatCommandTransformRegistry.get(command.type);
38573
+ return transform(getters, command);
38574
+ }
38575
+ function repeatLocalCommand(getters, command, childCommands) {
38576
+ const isRepeatable = repeatLocalCommandTransformRegistry.contains(command.type);
38577
+ if (!isRepeatable) {
38578
+ return undefined;
38579
+ }
38580
+ const repeatTransform = repeatLocalCommandTransformRegistry.get(command.type);
38581
+ return repeatTransform(getters, command, childCommands);
38582
+ }
38583
+ function repeatLocalCommandChildren(getters, cmd, childCommands) {
38584
+ return childCommands
38585
+ .map((childCommand) => repeatCoreCommand(getters, childCommand))
38586
+ .filter(isDefined$1);
38587
+ }
38588
+
38589
+ function canRepeatRevision(revision) {
38590
+ if (!revision || !revision.rootCommand || typeof revision.rootCommand !== "object") {
38591
+ return false;
38592
+ }
38593
+ if (isCoreCommand(revision.rootCommand)) {
38594
+ return repeatCommandTransformRegistry.contains(revision.rootCommand.type);
38595
+ }
38596
+ return repeatLocalCommandTransformRegistry.contains(revision.rootCommand.type);
38597
+ }
38598
+ function repeatRevision(revision, getters) {
38599
+ if (!revision.rootCommand || typeof revision.rootCommand !== "object") {
38600
+ return undefined;
38601
+ }
38602
+ if (isCoreCommand(revision.rootCommand)) {
38603
+ return repeatCoreCommand(getters, revision.rootCommand);
38604
+ }
38605
+ return repeatLocalCommand(getters, revision.rootCommand, revision.commands);
38606
+ }
38607
+
38608
+ /**
38609
+ * Local History
38610
+ *
38611
+ * The local history is responsible of tracking the locally state updates
38612
+ * It maintains the local undo and redo stack to allow to undo/redo only local
38613
+ * changes
38614
+ */
38615
+ class HistoryPlugin extends UIPlugin {
38616
+ constructor(config) {
38617
+ super(config);
38618
+ /**
38619
+ * Ids of the revisions which can be undone
38620
+ */
38621
+ this.undoStack = [];
38622
+ /**
38623
+ * Ids of the revisions which can be redone
38624
+ */
38625
+ this.redoStack = [];
38626
+ this.session = config.session;
38627
+ this.session.on("new-local-state-update", this, this.onNewLocalStateUpdate);
38628
+ this.session.on("pending-revisions-dropped", this, ({ revisionIds }) => this.drop(revisionIds));
38629
+ this.session.on("snapshot", this, () => {
38630
+ this.undoStack = [];
38631
+ this.redoStack = [];
38632
+ });
38633
+ }
38634
+ allowDispatch(cmd) {
38635
+ switch (cmd.type) {
38636
+ case "REQUEST_UNDO":
38637
+ if (!this.canUndo()) {
38638
+ return 6 /* CommandResult.EmptyUndoStack */;
38639
+ }
38640
+ break;
38641
+ case "REQUEST_REDO":
38642
+ if (!this.canRedo()) {
38643
+ return 7 /* CommandResult.EmptyRedoStack */;
38644
+ }
38645
+ break;
38646
+ }
38647
+ return 0 /* CommandResult.Success */;
38648
+ }
38649
+ handle(cmd) {
38650
+ switch (cmd.type) {
38651
+ case "REQUEST_UNDO":
38652
+ case "REQUEST_REDO":
38653
+ // History changes (undo & redo) are *not* applied optimistically on the local state.
38654
+ // We wait a global confirmation from the server. The goal is to avoid handling concurrent
38655
+ // history changes on multiple clients which are very hard to manage correctly.
38656
+ this.requestHistoryChange(cmd.type === "REQUEST_UNDO" ? "UNDO" : "REDO");
38657
+ }
38658
+ }
38659
+ finalize() { }
38660
+ requestHistoryChange(type) {
38661
+ const id = type === "UNDO" ? this.undoStack.pop() : this.redoStack.pop();
38662
+ if (!id) {
38663
+ const lastNonRedoRevision = this.getPossibleRevisionToRepeat();
38664
+ if (!lastNonRedoRevision) {
38665
+ return;
38666
+ }
38667
+ const repeatedCommands = repeatRevision(lastNonRedoRevision, this.getters);
38668
+ if (!repeatedCommands) {
38669
+ return;
38670
+ }
38671
+ if (!Array.isArray(repeatedCommands)) {
38672
+ this.dispatch(repeatedCommands.type, repeatedCommands);
38673
+ return;
38674
+ }
38675
+ for (const command of repeatedCommands) {
38676
+ this.dispatch(command.type, command);
38677
+ }
38678
+ return;
38679
+ }
38680
+ if (type === "UNDO") {
38681
+ this.session.undo(id);
38682
+ this.redoStack.push(id);
38683
+ }
38684
+ else {
38685
+ this.session.redo(id);
38686
+ this.undoStack.push(id);
38687
+ }
38688
+ }
38689
+ canUndo() {
38690
+ return this.undoStack.length > 0;
38691
+ }
38692
+ canRedo() {
38693
+ if (this.redoStack.length > 0)
38694
+ return true;
38695
+ const lastNonRedoRevision = this.getPossibleRevisionToRepeat();
38696
+ return canRepeatRevision(lastNonRedoRevision);
38697
+ }
38698
+ drop(revisionIds) {
38699
+ this.undoStack = this.undoStack.filter((id) => !revisionIds.includes(id));
38700
+ this.redoStack = [];
38701
+ }
38702
+ onNewLocalStateUpdate({ id }) {
38703
+ this.undoStack.push(id);
38704
+ this.redoStack = [];
38705
+ if (this.undoStack.length > MAX_HISTORY_STEPS) {
38706
+ this.undoStack.shift();
38707
+ }
38708
+ }
38709
+ /**
38710
+ * Fetch the last revision which is not empty and not a repeated command
38711
+ *
38712
+ * Ignore repeated commands (REQUEST_REDO command as root command)
38713
+ * Ignore standard undo/redo revisions (that are empty)
38714
+ */
38715
+ getPossibleRevisionToRepeat() {
38716
+ return this.session.getLastLocalNonEmptyRevision(["REQUEST_REDO"]);
38717
+ }
38718
+ }
38719
+ HistoryPlugin.getters = ["canUndo", "canRedo"];
38720
+
38288
38721
  /** Abstract state of the clipboard when copying/cutting content that is pasted in cells of the sheet */
38289
38722
  class ClipboardCellsAbstractState {
38290
38723
  constructor(operation, getters, dispatch, selection) {
@@ -38994,7 +39427,7 @@ class ClipboardPlugin extends UIPlugin {
38994
39427
  return 0 /* CommandResult.Success */;
38995
39428
  }
38996
39429
  handle(cmd) {
38997
- var _a, _b;
39430
+ var _a, _b, _c;
38998
39431
  switch (cmd.type) {
38999
39432
  case "COPY":
39000
39433
  case "CUT":
@@ -39012,6 +39445,7 @@ class ClipboardPlugin extends UIPlugin {
39012
39445
  if (this.state.operation === "CUT") {
39013
39446
  this.state = undefined;
39014
39447
  }
39448
+ this.lastPasteState = this.state;
39015
39449
  this.status = "invisible";
39016
39450
  break;
39017
39451
  case "CLEAN_CLIPBOARD_HIGHLIGHT":
@@ -39064,10 +39498,19 @@ class ClipboardPlugin extends UIPlugin {
39064
39498
  break;
39065
39499
  }
39066
39500
  case "PASTE_FROM_OS_CLIPBOARD":
39067
- const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
39068
- state.paste(cmd.target);
39501
+ this.state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
39502
+ this.state.paste(cmd.target);
39503
+ this.lastPasteState = this.state;
39069
39504
  this.status = "invisible";
39070
39505
  break;
39506
+ case "REPEAT_PASTE": {
39507
+ (_c = this.lastPasteState) === null || _c === void 0 ? void 0 : _c.paste(cmd.target, {
39508
+ pasteOption: cmd.pasteOption,
39509
+ shouldPasteCF: true,
39510
+ selectTarget: true,
39511
+ });
39512
+ break;
39513
+ }
39071
39514
  case "ACTIVATE_PAINT_FORMAT": {
39072
39515
  const zones = this.getters.getSelectedZones();
39073
39516
  this.state = this.getClipboardStateForCopyCells(zones, "COPY");
@@ -40548,7 +40991,8 @@ const featurePluginRegistry = new Registry()
40548
40991
  .add("automatic_sum", AutomaticSumPlugin)
40549
40992
  .add("format", FormatPlugin)
40550
40993
  .add("cell_popovers", CellPopoverPlugin)
40551
- .add("selection_multiuser", SelectionMultiUserPlugin);
40994
+ .add("selection_multiuser", SelectionMultiUserPlugin)
40995
+ .add("history", HistoryPlugin);
40552
40996
  // Plugins which have a state, but which should not be shared in collaborative
40553
40997
  const statefulUIPluginRegistry = new Registry()
40554
40998
  .add("selection", GridSelectionPlugin)
@@ -43235,6 +43679,14 @@ class SelectiveHistory {
43235
43679
  this.revertBefore(operationId);
43236
43680
  this.tree.drop(operationId);
43237
43681
  }
43682
+ getRevertedExecution() {
43683
+ const data = [];
43684
+ const operations = this.tree.revertedExecution(this.HEAD_BRANCH);
43685
+ for (const { operation } of operations) {
43686
+ data.push(operation.data);
43687
+ }
43688
+ return data;
43689
+ }
43238
43690
  /**
43239
43691
  * Revert the state as it was *before* the given operation was executed.
43240
43692
  */
@@ -43291,12 +43743,12 @@ function buildRevisionLog(initialRevisionId, recordChanges, dispatch) {
43291
43743
  }
43292
43744
  });
43293
43745
  revision.setChanges(changes);
43294
- }, (revision) => revertChanges([revision]), (id) => new Revision(id, "empty", [], []), {
43746
+ }, (revision) => revertChanges([revision]), (id) => new Revision(id, "empty", []), {
43295
43747
  with: (revision) => (toTransform) => {
43296
- return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands));
43748
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands), toTransform.rootCommand);
43297
43749
  },
43298
43750
  without: (revision) => (toTransform) => {
43299
- return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()));
43751
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()), toTransform.rootCommand);
43300
43752
  },
43301
43753
  });
43302
43754
  }
@@ -43333,101 +43785,6 @@ function applyChange(change, target) {
43333
43785
  }
43334
43786
  }
43335
43787
 
43336
- /**
43337
- * Local History
43338
- *
43339
- * The local history is responsible of tracking the locally state updates
43340
- * It maintains the local undo and redo stack to allow to undo/redo only local
43341
- * changes
43342
- */
43343
- class LocalHistory extends owl.EventBus {
43344
- constructor(dispatch, session) {
43345
- super();
43346
- this.dispatch = dispatch;
43347
- this.session = session;
43348
- /**
43349
- * Ids of the revisions which can be undone
43350
- */
43351
- this.undoStack = [];
43352
- /**
43353
- * Ids of the revisions which can be redone
43354
- */
43355
- this.redoStack = [];
43356
- this.session.on("new-local-state-update", this, this.onNewLocalStateUpdate);
43357
- this.session.on("revision-undone", this, ({ commands }) => this.selectiveUndo(commands));
43358
- this.session.on("revision-redone", this, ({ commands }) => this.selectiveRedo(commands));
43359
- this.session.on("pending-revisions-dropped", this, ({ revisionIds }) => this.drop(revisionIds));
43360
- this.session.on("snapshot", this, () => {
43361
- this.undoStack = [];
43362
- this.redoStack = [];
43363
- });
43364
- }
43365
- allowDispatch(cmd) {
43366
- switch (cmd.type) {
43367
- case "REQUEST_UNDO":
43368
- if (!this.canUndo()) {
43369
- return 6 /* CommandResult.EmptyUndoStack */;
43370
- }
43371
- break;
43372
- case "REQUEST_REDO":
43373
- if (!this.canRedo()) {
43374
- return 7 /* CommandResult.EmptyRedoStack */;
43375
- }
43376
- break;
43377
- }
43378
- return 0 /* CommandResult.Success */;
43379
- }
43380
- beforeHandle(cmd) { }
43381
- handle(cmd) {
43382
- switch (cmd.type) {
43383
- case "REQUEST_UNDO":
43384
- case "REQUEST_REDO":
43385
- // History changes (undo & redo) are *not* applied optimistically on the local state.
43386
- // We wait a global confirmation from the server. The goal is to avoid handling concurrent
43387
- // history changes on multiple clients which are very hard to manage correctly.
43388
- this.requestHistoryChange(cmd.type === "REQUEST_UNDO" ? "UNDO" : "REDO");
43389
- }
43390
- }
43391
- finalize() { }
43392
- requestHistoryChange(type) {
43393
- const id = type === "UNDO" ? this.undoStack.pop() : this.redoStack.pop();
43394
- if (!id) {
43395
- return;
43396
- }
43397
- if (type === "UNDO") {
43398
- this.session.undo(id);
43399
- this.redoStack.push(id);
43400
- }
43401
- else {
43402
- this.session.redo(id);
43403
- this.undoStack.push(id);
43404
- }
43405
- }
43406
- canUndo() {
43407
- return this.undoStack.length > 0;
43408
- }
43409
- canRedo() {
43410
- return this.redoStack.length > 0;
43411
- }
43412
- drop(revisionIds) {
43413
- this.undoStack = this.undoStack.filter((id) => !revisionIds.includes(id));
43414
- this.redoStack = [];
43415
- }
43416
- onNewLocalStateUpdate({ id }) {
43417
- this.undoStack.push(id);
43418
- this.redoStack = [];
43419
- if (this.undoStack.length > MAX_HISTORY_STEPS) {
43420
- this.undoStack.shift();
43421
- }
43422
- }
43423
- selectiveUndo(commands) {
43424
- this.dispatch("UNDO", { commands });
43425
- }
43426
- selectiveRedo(commands) {
43427
- this.dispatch("REDO", { commands });
43428
- }
43429
- }
43430
-
43431
43788
  /**
43432
43789
  * Stateless sequence of events that can be processed by consumers.
43433
43790
  *
@@ -43828,8 +44185,8 @@ class SelectionStreamProcessorImpl {
43828
44185
  }
43829
44186
  const { left, right, top, bottom } = zone;
43830
44187
  const sheetId = this.getters.getActiveSheetId();
43831
- const refCol = this.getters.findVisibleHeader(sheetId, "COL", range(left, right + 1));
43832
- const refRow = this.getters.findVisibleHeader(sheetId, "ROW", range(top, bottom + 1));
44188
+ const refCol = this.getters.findVisibleHeader(sheetId, "COL", left, right);
44189
+ const refRow = this.getters.findVisibleHeader(sheetId, "ROW", top, bottom);
43833
44190
  if (refRow === undefined || refCol === undefined) {
43834
44191
  return 17 /* CommandResult.SelectionOutOfBound */;
43835
44192
  }
@@ -43904,10 +44261,10 @@ class SelectionStreamProcessorImpl {
43904
44261
  const { col: anchorCol, row: anchorRow } = anchor.cell;
43905
44262
  return {
43906
44263
  col: this.getters.isColHidden(sheetId, anchorCol)
43907
- ? this.getters.findVisibleHeader(sheetId, "COL", range(left, right + 1)) || anchorCol
44264
+ ? this.getters.findVisibleHeader(sheetId, "COL", left, right) || anchorCol
43908
44265
  : anchorCol,
43909
44266
  row: this.getters.isRowHidden(sheetId, anchorRow)
43910
- ? this.getters.findVisibleHeader(sheetId, "ROW", range(top, bottom + 1)) || anchorRow
44267
+ ? this.getters.findVisibleHeader(sheetId, "ROW", top, bottom) || anchorRow
43911
44268
  : anchorRow,
43912
44269
  };
43913
44270
  }
@@ -45657,6 +46014,8 @@ class Model extends EventBus {
45657
46014
  * Internal status of the model. Important for command handling coordination
45658
46015
  */
45659
46016
  this.status = 0 /* Status.Ready */;
46017
+ this.handlers = [];
46018
+ this.coreHandlers = [];
45660
46019
  /**
45661
46020
  * Check if a command can be dispatched, and returns a DispatchResult object with the possible
45662
46021
  * reasons the dispatch failed.
@@ -45701,7 +46060,7 @@ class Model extends EventBus {
45701
46060
  this.dispatchToHandlers(this.handlers, command);
45702
46061
  this.finalize();
45703
46062
  });
45704
- this.session.save(commands, changes);
46063
+ this.session.save(command, commands, changes);
45705
46064
  this.status = 0 /* Status.Ready */;
45706
46065
  this.trigger("update");
45707
46066
  break;
@@ -45733,9 +46092,7 @@ class Model extends EventBus {
45733
46092
  const command = { ...payload, type };
45734
46093
  const previousStatus = this.status;
45735
46094
  this.status = 2 /* Status.RunningCore */;
45736
- const handlers = this.isReplayingCommand
45737
- ? [this.range, ...this.corePlugins, ...this.coreViewsPlugins]
45738
- : this.handlers;
46095
+ const handlers = this.isReplayingCommand ? this.coreHandlers : this.handlers;
45739
46096
  this.dispatchToHandlers(handlers, command);
45740
46097
  this.status = previousStatus;
45741
46098
  return DispatchResult.Success;
@@ -45746,7 +46103,6 @@ class Model extends EventBus {
45746
46103
  this.uuidGenerator = uuidGenerator;
45747
46104
  this.config = this.setupConfig(config);
45748
46105
  this.session = this.setupSession(workbookData.revisionId);
45749
- this.history = new LocalHistory(this.dispatchFromCorePlugin, this.session);
45750
46106
  this.coreGetters = {};
45751
46107
  this.range = new RangeAdapter(this.coreGetters);
45752
46108
  this.coreGetters.getRangeString = this.range.getRangeString.bind(this.range);
@@ -45759,8 +46115,6 @@ class Model extends EventBus {
45759
46115
  this.getters = {
45760
46116
  isReadonly: () => this.config.mode === "readonly" || this.config.mode === "dashboard",
45761
46117
  isDashboard: () => this.config.mode === "dashboard",
45762
- canUndo: this.history.canUndo.bind(this.history),
45763
- canRedo: this.history.canRedo.bind(this.history),
45764
46118
  getClient: this.session.getClient.bind(this.session),
45765
46119
  getConnectedClients: this.session.getConnectedClients.bind(this.session),
45766
46120
  isFullySynchronized: this.session.isFullySynchronized.bind(this.session),
@@ -45768,6 +46122,8 @@ class Model extends EventBus {
45768
46122
  this.uuidGenerator.setIsFastStrategy(true);
45769
46123
  // Initiate stream processor
45770
46124
  this.selection = new SelectionStreamProcessorImpl(this.getters);
46125
+ this.coreHandlers.push(this.range);
46126
+ this.handlers.push(this.range);
45771
46127
  this.corePluginConfig = this.setupCorePluginConfig();
45772
46128
  this.uiPluginConfig = this.setupUiPluginConfig();
45773
46129
  // registering plugins
@@ -45776,13 +46132,20 @@ class Model extends EventBus {
45776
46132
  }
45777
46133
  Object.assign(this.getters, this.coreGetters);
45778
46134
  for (let Plugin of statefulUIPluginRegistry.getAll()) {
45779
- this.statefulUIPlugins.push(this.setupUiPlugin(Plugin));
46135
+ const plugin = this.setupUiPlugin(Plugin);
46136
+ this.statefulUIPlugins.push(plugin);
46137
+ this.handlers.push(plugin);
45780
46138
  }
45781
46139
  for (let Plugin of coreViewsPluginRegistry.getAll()) {
45782
- this.coreViewsPlugins.push(this.setupUiPlugin(Plugin));
46140
+ const plugin = this.setupUiPlugin(Plugin);
46141
+ this.coreViewsPlugins.push(plugin);
46142
+ this.handlers.push(plugin);
46143
+ this.coreHandlers.push(plugin);
45783
46144
  }
45784
46145
  for (let Plugin of featurePluginRegistry.getAll()) {
45785
- this.featurePlugins.push(this.setupUiPlugin(Plugin));
46146
+ const plugin = this.setupUiPlugin(Plugin);
46147
+ this.featurePlugins.push(plugin);
46148
+ this.handlers.push(plugin);
45786
46149
  }
45787
46150
  this.uuidGenerator.setIsFastStrategy(false);
45788
46151
  // starting plugins
@@ -45806,15 +46169,6 @@ class Model extends EventBus {
45806
46169
  // by owl, since we do not rely on reactivity
45807
46170
  owl.markRaw(this);
45808
46171
  }
45809
- get handlers() {
45810
- return [...this.coreHandlers, ...this.allUIPlugins, this.history];
45811
- }
45812
- get coreHandlers() {
45813
- return [this.range, ...this.corePlugins];
45814
- }
45815
- get allUIPlugins() {
45816
- return [...this.statefulUIPlugins, ...this.coreViewsPlugins, ...this.featurePlugins];
45817
- }
45818
46172
  joinSession() {
45819
46173
  this.session.join(this.config.client);
45820
46174
  }
@@ -45856,6 +46210,8 @@ class Model extends EventBus {
45856
46210
  }
45857
46211
  plugin.import(data);
45858
46212
  this.corePlugins.push(plugin);
46213
+ this.coreHandlers.push(plugin);
46214
+ this.handlers.push(plugin);
45859
46215
  }
45860
46216
  onRemoteRevisionReceived({ commands }) {
45861
46217
  for (let command of commands) {
@@ -45873,15 +46229,21 @@ class Model extends EventBus {
45873
46229
  return;
45874
46230
  }
45875
46231
  this.isReplayingCommand = true;
45876
- this.dispatchToHandlers([this.range, ...this.corePlugins, ...this.coreViewsPlugins], command);
46232
+ this.dispatchToHandlers(this.coreHandlers, command);
45877
46233
  this.isReplayingCommand = false;
45878
46234
  }), this.config.transportService, revisionId);
45879
46235
  return session;
45880
46236
  }
45881
46237
  setupSessionEvents() {
45882
46238
  this.session.on("remote-revision-received", this, this.onRemoteRevisionReceived);
45883
- this.session.on("revision-redone", this, this.finalize);
45884
- this.session.on("revision-undone", this, this.finalize);
46239
+ this.session.on("revision-undone", this, ({ commands }) => {
46240
+ this.dispatchFromCorePlugin("UNDO", { commands });
46241
+ this.finalize();
46242
+ });
46243
+ this.session.on("revision-redone", this, ({ commands }) => {
46244
+ this.dispatchFromCorePlugin("REDO", { commands });
46245
+ this.finalize();
46246
+ });
45885
46247
  // How could we improve communication between the session and UI?
45886
46248
  // It feels weird to have the model piping specific session events to its own bus.
45887
46249
  this.session.on("unexpected-revision-id", this, () => this.trigger("unexpected-revision-id"));
@@ -45929,6 +46291,7 @@ class Model extends EventBus {
45929
46291
  custom: this.config.custom,
45930
46292
  uiActions: this.config,
45931
46293
  lazyEvaluation: this.config.lazyEvaluation,
46294
+ session: this.session,
45932
46295
  };
45933
46296
  }
45934
46297
  // ---------------------------------------------------------------------------
@@ -45944,7 +46307,8 @@ class Model extends EventBus {
45944
46307
  return this.checkDispatchAllowedLocalCommand(command);
45945
46308
  }
45946
46309
  checkDispatchAllowedCoreCommand(command) {
45947
- const results = this.coreHandlers.map((handler) => handler.allowDispatch(command));
46310
+ const results = this.corePlugins.map((handler) => handler.allowDispatch(command));
46311
+ results.push(this.range.allowDispatch(command));
45948
46312
  return new DispatchResult(results.flat());
45949
46313
  }
45950
46314
  checkDispatchAllowedLocalCommand(command) {
@@ -46008,7 +46372,7 @@ class Model extends EventBus {
46008
46372
  }
46009
46373
  }
46010
46374
  data.revisionId = this.session.getRevisionId() || DEFAULT_REVISION_ID;
46011
- data = JSON.parse(JSON.stringify(data));
46375
+ data = deepCopy(data);
46012
46376
  return data;
46013
46377
  }
46014
46378
  updateMode(mode) {
@@ -46036,7 +46400,7 @@ class Model extends EventBus {
46036
46400
  handler.exportForExcel(data);
46037
46401
  }
46038
46402
  }
46039
- data = JSON.parse(JSON.stringify(data));
46403
+ data = deepCopy(data);
46040
46404
  return getXLSX(data);
46041
46405
  }
46042
46406
  garbageCollectExternalResources() {
@@ -46089,6 +46453,8 @@ const registries = {
46089
46453
  urlRegistry,
46090
46454
  cellPopoverRegistry,
46091
46455
  numberFormatMenuRegistry,
46456
+ repeatLocalCommandTransformRegistry,
46457
+ repeatCommandTransformRegistry,
46092
46458
  };
46093
46459
  const helpers = {
46094
46460
  arg,
@@ -46117,6 +46483,7 @@ const helpers = {
46117
46483
  positionToZone,
46118
46484
  isDefined: isDefined$1,
46119
46485
  lazy,
46486
+ genericRepeat,
46120
46487
  };
46121
46488
  const links = {
46122
46489
  isMarkdownLink,
@@ -46178,6 +46545,6 @@ exports.setTranslationMethod = setTranslationMethod;
46178
46545
  exports.tokenize = tokenize;
46179
46546
 
46180
46547
 
46181
- __info__.version = '16.3.0-alpha.4';
46182
- __info__.date = '2023-04-14T12:49:16.671Z';
46183
- __info__.hash = '6824b8a';
46548
+ __info__.version = '16.3.0-alpha.5';
46549
+ __info__.date = '2023-04-21T07:28:52.982Z';
46550
+ __info__.hash = '6304f41';