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