handsontable
Version:
Handsontable is a JavaScript Spreadsheet Component available for React, Angular and Vue.
1,064 lines (883 loc) • 36.2 kB
JavaScript
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr && (typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]); if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
import "core-js/modules/es.array.find.js";
import "core-js/modules/es.array.reduce.js";
import "core-js/modules/es.array.slice.js";
import "core-js/modules/es.array.splice.js";
import "core-js/modules/es.array.sort.js";
import "core-js/modules/es.array.index-of.js";
import "core-js/modules/es.array.filter.js";
import "core-js/modules/es.array.concat.js";
import "core-js/modules/es.symbol.js";
import "core-js/modules/es.symbol.description.js";
import "core-js/modules/es.object.to-string.js";
import "core-js/modules/es.symbol.iterator.js";
import "core-js/modules/es.array.iterator.js";
import "core-js/modules/es.string.iterator.js";
import "core-js/modules/web.dom-collections.iterator.js";
import "core-js/modules/es.function.name.js";
import "core-js/modules/es.array.from.js";
import "core-js/modules/es.object.set-prototype-of.js";
import "core-js/modules/es.object.get-prototype-of.js";
import "core-js/modules/es.reflect.construct.js";
import Hooks from "../../pluginHooks.mjs";
import { arrayMap, arrayEach } from "../../helpers/array.mjs";
import { rangeEach } from "../../helpers/number.mjs";
import { inherit, deepClone } from "../../helpers/object.mjs";
import { stopImmediatePropagation, isImmediatePropagationStopped } from "../../helpers/dom/event.mjs";
import { align } from "../contextMenu/utils.mjs";
export var PLUGIN_KEY = 'undoRedo';
/**
* @description
* Handsontable UndoRedo plugin allows to undo and redo certain actions done in the table.
*
* __Note__, that not all actions are currently undo-able. The UndoRedo plugin is enabled by default.
* @example
* ```js
* undo: true
* ```
* @class UndoRedo
* @plugin UndoRedo
* @param {Core} instance The Handsontable instance.
*/
function UndoRedo(instance) {
var plugin = this;
this.instance = instance;
this.doneActions = [];
this.undoneActions = [];
this.ignoreNewActions = false;
this.enabled = false;
instance.addHook('afterChange', function (changes, source) {
var _this = this;
var changesLen = changes && changes.length;
if (!changesLen) {
return;
}
var hasDifferences = changes.find(function (change) {
var _change = _slicedToArray(change, 4),
oldValue = _change[2],
newValue = _change[3];
return oldValue !== newValue;
});
if (!hasDifferences) {
return;
}
var wrappedAction = function wrappedAction() {
var clonedChanges = changes.reduce(function (arr, change) {
arr.push(_toConsumableArray(change));
return arr;
}, []);
arrayEach(clonedChanges, function (change) {
change[1] = instance.propToCol(change[1]);
});
var selected = changesLen > 1 ? _this.getSelected() : [[clonedChanges[0][0], clonedChanges[0][1]]];
return new UndoRedo.ChangeAction(clonedChanges, selected);
};
plugin.done(wrappedAction, source);
});
instance.addHook('afterCreateRow', function (index, amount, source) {
plugin.done(function () {
return new UndoRedo.CreateRowAction(index, amount);
}, source);
});
instance.addHook('beforeRemoveRow', function (index, amount, logicRows, source) {
var wrappedAction = function wrappedAction() {
var originalData = plugin.instance.getSourceDataArray();
var rowIndex = (originalData.length + index) % originalData.length;
var physicalRowIndex = instance.toPhysicalRow(rowIndex);
var removedData = deepClone(originalData.slice(physicalRowIndex, physicalRowIndex + amount));
return new UndoRedo.RemoveRowAction(rowIndex, removedData, instance.getSettings().fixedRowsBottom, instance.getSettings().fixedRowsTop);
};
plugin.done(wrappedAction, source);
});
instance.addHook('afterCreateCol', function (index, amount, source) {
plugin.done(function () {
return new UndoRedo.CreateColumnAction(index, amount);
}, source);
});
instance.addHook('beforeRemoveCol', function (index, amount, logicColumns, source) {
var wrappedAction = function wrappedAction() {
var originalData = plugin.instance.getSourceDataArray();
var columnIndex = (plugin.instance.countCols() + index) % plugin.instance.countCols();
var removedData = [];
var headers = [];
var indexes = [];
rangeEach(originalData.length - 1, function (i) {
var column = [];
var origRow = originalData[i];
rangeEach(columnIndex, columnIndex + (amount - 1), function (j) {
column.push(origRow[instance.toPhysicalColumn(j)]);
});
removedData.push(column);
});
rangeEach(amount - 1, function (i) {
indexes.push(instance.toPhysicalColumn(columnIndex + i));
});
if (Array.isArray(instance.getSettings().colHeaders)) {
rangeEach(amount - 1, function (i) {
headers.push(instance.getSettings().colHeaders[instance.toPhysicalColumn(columnIndex + i)] || null);
});
}
var columnsMap = instance.columnIndexMapper.getIndexesSequence();
var rowsMap = instance.rowIndexMapper.getIndexesSequence();
return new UndoRedo.RemoveColumnAction(columnIndex, indexes, removedData, headers, columnsMap, rowsMap, instance.getSettings().fixedColumnsLeft);
};
plugin.done(wrappedAction, source);
});
instance.addHook('beforeCellAlignment', function (stateBefore, range, type, alignment) {
plugin.done(function () {
return new UndoRedo.CellAlignmentAction(stateBefore, range, type, alignment);
});
});
instance.addHook('beforeFilter', function (conditionsStack) {
plugin.done(function () {
return new UndoRedo.FiltersAction(conditionsStack);
});
});
instance.addHook('beforeRowMove', function (rows, finalIndex) {
if (rows === false) {
return;
}
plugin.done(function () {
return new UndoRedo.RowMoveAction(rows, finalIndex);
});
});
instance.addHook('beforeMergeCells', function (cellRange, auto) {
if (auto) {
return;
}
plugin.done(function () {
return new UndoRedo.MergeCellsAction(instance, cellRange);
});
});
instance.addHook('afterUnmergeCells', function (cellRange, auto) {
if (auto) {
return;
}
plugin.done(function () {
return new UndoRedo.UnmergeCellsAction(instance, cellRange);
});
}); // TODO: Why this callback is needed? One test doesn't pass after calling method right after plugin creation (outside the callback).
instance.addHook('afterInit', function () {
plugin.init();
});
}
/**
* Stash information about performed actions.
*
* @function done
* @memberof UndoRedo#
* @fires Hooks#beforeUndoStackChange
* @fires Hooks#afterUndoStackChange
* @fires Hooks#beforeRedoStackChange
* @fires Hooks#afterRedoStackChange
* @param {Function} wrappedAction The action descriptor wrapped in a closure.
* @param {string} [source] Source of the action. It is defined just for more general actions (not related to plugins).
*/
UndoRedo.prototype.done = function (wrappedAction, source) {
if (this.ignoreNewActions) {
return;
}
var isBlockedByDefault = source === 'UndoRedo.undo' || source === 'UndoRedo.redo' || source === 'auto';
if (isBlockedByDefault) {
return;
}
var doneActionsCopy = this.doneActions.slice();
var continueAction = this.instance.runHooks('beforeUndoStackChange', doneActionsCopy, source);
if (continueAction === false) {
return;
}
var newAction = wrappedAction();
var undoneActionsCopy = this.undoneActions.slice();
this.doneActions.push(newAction);
this.instance.runHooks('afterUndoStackChange', doneActionsCopy, this.doneActions.slice());
this.instance.runHooks('beforeRedoStackChange', undoneActionsCopy);
this.undoneActions.length = 0;
this.instance.runHooks('afterRedoStackChange', undoneActionsCopy, this.undoneActions.slice());
};
/**
* Undo the last action performed to the table.
*
* @function undo
* @memberof UndoRedo#
* @fires Hooks#beforeUndoStackChange
* @fires Hooks#afterUndoStackChange
* @fires Hooks#beforeRedoStackChange
* @fires Hooks#afterRedoStackChange
* @fires Hooks#beforeUndo
* @fires Hooks#afterUndo
*/
UndoRedo.prototype.undo = function () {
if (this.isUndoAvailable()) {
var doneActionsCopy = this.doneActions.slice();
this.instance.runHooks('beforeUndoStackChange', doneActionsCopy);
var action = this.doneActions.pop();
this.instance.runHooks('afterUndoStackChange', doneActionsCopy, this.doneActions.slice());
var actionClone = deepClone(action);
var continueAction = this.instance.runHooks('beforeUndo', actionClone);
if (continueAction === false) {
return;
}
this.ignoreNewActions = true;
var that = this;
var undoneActionsCopy = this.undoneActions.slice();
this.instance.runHooks('beforeRedoStackChange', undoneActionsCopy);
action.undo(this.instance, function () {
that.ignoreNewActions = false;
that.undoneActions.push(action);
});
this.instance.runHooks('afterRedoStackChange', undoneActionsCopy, this.undoneActions.slice());
this.instance.runHooks('afterUndo', actionClone);
}
};
/**
* Redo the previous action performed to the table (used to reverse an undo).
*
* @function redo
* @memberof UndoRedo#
* @fires Hooks#beforeUndoStackChange
* @fires Hooks#afterUndoStackChange
* @fires Hooks#beforeRedoStackChange
* @fires Hooks#afterRedoStackChange
* @fires Hooks#beforeRedo
* @fires Hooks#afterRedo
*/
UndoRedo.prototype.redo = function () {
if (this.isRedoAvailable()) {
var undoneActionsCopy = this.undoneActions.slice();
this.instance.runHooks('beforeRedoStackChange', undoneActionsCopy);
var action = this.undoneActions.pop();
this.instance.runHooks('afterRedoStackChange', undoneActionsCopy, this.undoneActions.slice());
var actionClone = deepClone(action);
var continueAction = this.instance.runHooks('beforeRedo', actionClone);
if (continueAction === false) {
return;
}
this.ignoreNewActions = true;
var that = this;
var doneActionsCopy = this.doneActions.slice();
this.instance.runHooks('beforeUndoStackChange', doneActionsCopy);
action.redo(this.instance, function () {
that.ignoreNewActions = false;
that.doneActions.push(action);
});
this.instance.runHooks('afterUndoStackChange', doneActionsCopy, this.doneActions.slice());
this.instance.runHooks('afterRedo', actionClone);
}
};
/**
* Checks if undo action is available.
*
* @function isUndoAvailable
* @memberof UndoRedo#
* @returns {boolean} Return `true` if undo can be performed, `false` otherwise.
*/
UndoRedo.prototype.isUndoAvailable = function () {
return this.doneActions.length > 0;
};
/**
* Checks if redo action is available.
*
* @function isRedoAvailable
* @memberof UndoRedo#
* @returns {boolean} Return `true` if redo can be performed, `false` otherwise.
*/
UndoRedo.prototype.isRedoAvailable = function () {
return this.undoneActions.length > 0;
};
/**
* Clears undo history.
*
* @function clear
* @memberof UndoRedo#
*/
UndoRedo.prototype.clear = function () {
this.doneActions.length = 0;
this.undoneActions.length = 0;
};
/**
* Checks if the plugin is enabled.
*
* @function isEnabled
* @memberof UndoRedo#
* @returns {boolean}
*/
UndoRedo.prototype.isEnabled = function () {
return this.enabled;
};
/**
* Enables the plugin.
*
* @function enable
* @memberof UndoRedo#
*/
UndoRedo.prototype.enable = function () {
if (this.isEnabled()) {
return;
}
var hot = this.instance;
this.enabled = true;
exposeUndoRedoMethods(hot);
hot.addHook('beforeKeyDown', onBeforeKeyDown);
hot.addHook('afterChange', onAfterChange);
};
/**
* Disables the plugin.
*
* @function disable
* @memberof UndoRedo#
*/
UndoRedo.prototype.disable = function () {
if (!this.isEnabled()) {
return;
}
var hot = this.instance;
this.enabled = false;
removeExposedUndoRedoMethods(hot);
hot.removeHook('beforeKeyDown', onBeforeKeyDown);
hot.removeHook('afterChange', onAfterChange);
};
/**
* Destroys the instance.
*
* @function destroy
* @memberof UndoRedo#
*/
UndoRedo.prototype.destroy = function () {
this.clear();
this.instance = null;
this.doneActions = null;
this.undoneActions = null;
};
UndoRedo.Action = function () {};
UndoRedo.Action.prototype.undo = function () {};
UndoRedo.Action.prototype.redo = function () {};
/**
* Change action.
*
* @private
* @param {Array} changes 2D array containing information about each of the edited cells.
* @param {number[]} selected The cell selection.
*/
UndoRedo.ChangeAction = function (changes, selected) {
this.changes = changes;
this.selected = selected;
this.actionType = 'change';
};
inherit(UndoRedo.ChangeAction, UndoRedo.Action);
UndoRedo.ChangeAction.prototype.undo = function (instance, undoneCallback) {
var data = deepClone(this.changes);
var emptyRowsAtTheEnd = instance.countEmptyRows(true);
var emptyColsAtTheEnd = instance.countEmptyCols(true);
for (var i = 0, len = data.length; i < len; i++) {
data[i].splice(3, 1);
}
instance.addHookOnce('afterChange', undoneCallback);
instance.setDataAtCell(data, null, null, 'UndoRedo.undo');
for (var _i2 = 0, _len = data.length; _i2 < _len; _i2++) {
var _data$_i = _slicedToArray(data[_i2], 2),
row = _data$_i[0],
column = _data$_i[1];
if (instance.getSettings().minSpareRows && row + 1 + instance.getSettings().minSpareRows === instance.countRows() && emptyRowsAtTheEnd === instance.getSettings().minSpareRows) {
instance.alter('remove_row', parseInt(row + 1, 10), instance.getSettings().minSpareRows);
instance.undoRedo.doneActions.pop();
}
if (instance.getSettings().minSpareCols && column + 1 + instance.getSettings().minSpareCols === instance.countCols() && emptyColsAtTheEnd === instance.getSettings().minSpareCols) {
instance.alter('remove_col', parseInt(column + 1, 10), instance.getSettings().minSpareCols);
instance.undoRedo.doneActions.pop();
}
}
instance.selectCells(this.selected, false, false);
};
UndoRedo.ChangeAction.prototype.redo = function (instance, onFinishCallback) {
var data = deepClone(this.changes);
for (var i = 0, len = data.length; i < len; i++) {
data[i].splice(2, 1);
}
instance.addHookOnce('afterChange', onFinishCallback);
instance.setDataAtCell(data, null, null, 'UndoRedo.redo');
if (this.selected) {
instance.selectCells(this.selected, false, false);
}
};
/**
* Create row action.
*
* @private
* @param {number} index The visual row index.
* @param {number} amount The number of created rows.
*/
UndoRedo.CreateRowAction = function (index, amount) {
this.index = index;
this.amount = amount;
this.actionType = 'insert_row';
};
inherit(UndoRedo.CreateRowAction, UndoRedo.Action);
UndoRedo.CreateRowAction.prototype.undo = function (instance, undoneCallback) {
var rowCount = instance.countRows();
var minSpareRows = instance.getSettings().minSpareRows;
if (this.index >= rowCount && this.index - minSpareRows < rowCount) {
this.index -= minSpareRows; // work around the situation where the needed row was removed due to an 'undo' of a made change
}
instance.addHookOnce('afterRemoveRow', undoneCallback);
instance.alter('remove_row', this.index, this.amount, 'UndoRedo.undo');
};
UndoRedo.CreateRowAction.prototype.redo = function (instance, redoneCallback) {
instance.addHookOnce('afterCreateRow', redoneCallback);
instance.alter('insert_row', this.index, this.amount, 'UndoRedo.redo');
};
/**
* Remove row action.
*
* @private
* @param {number} index The visual row index.
* @param {Array} data The removed data.
* @param {number} fixedRowsBottom Number of fixed rows on the bottom. Remove row action change it sometimes.
* @param {number} fixedRowsTop Number of fixed rows on the top. Remove row action change it sometimes.
*/
UndoRedo.RemoveRowAction = function (index, data, fixedRowsBottom, fixedRowsTop) {
this.index = index;
this.data = data;
this.actionType = 'remove_row';
this.fixedRowsBottom = fixedRowsBottom;
this.fixedRowsTop = fixedRowsTop;
};
inherit(UndoRedo.RemoveRowAction, UndoRedo.Action);
UndoRedo.RemoveRowAction.prototype.undo = function (instance, undoneCallback) {
var settings = instance.getSettings(); // Changing by the reference as `updateSettings` doesn't work the best.
settings.fixedRowsBottom = this.fixedRowsBottom;
settings.fixedRowsTop = this.fixedRowsTop;
instance.alter('insert_row', this.index, this.data.length, 'UndoRedo.undo');
instance.addHookOnce('afterRender', undoneCallback);
instance.populateFromArray(this.index, 0, this.data, void 0, void 0, 'UndoRedo.undo');
};
UndoRedo.RemoveRowAction.prototype.redo = function (instance, redoneCallback) {
instance.addHookOnce('afterRemoveRow', redoneCallback);
instance.alter('remove_row', this.index, this.data.length, 'UndoRedo.redo');
};
/**
* Create column action.
*
* @private
* @param {number} index The visual column index.
* @param {number} amount The number of created columns.
*/
UndoRedo.CreateColumnAction = function (index, amount) {
this.index = index;
this.amount = amount;
this.actionType = 'insert_col';
};
inherit(UndoRedo.CreateColumnAction, UndoRedo.Action);
UndoRedo.CreateColumnAction.prototype.undo = function (instance, undoneCallback) {
instance.addHookOnce('afterRemoveCol', undoneCallback);
instance.alter('remove_col', this.index, this.amount, 'UndoRedo.undo');
};
UndoRedo.CreateColumnAction.prototype.redo = function (instance, redoneCallback) {
instance.addHookOnce('afterCreateCol', redoneCallback);
instance.alter('insert_col', this.index, this.amount, 'UndoRedo.redo');
};
/**
* Remove column action.
*
* @private
* @param {number} index The visual column index.
* @param {number[]} indexes The visual column indexes.
* @param {Array} data The removed data.
* @param {Array} headers The header values.
* @param {number[]} columnPositions The column position.
* @param {number[]} rowPositions The row position.
* @param {number} fixedColumnsLeft Number of fixed columns on the left. Remove column action change it sometimes.
*/
UndoRedo.RemoveColumnAction = function (index, indexes, data, headers, columnPositions, rowPositions, fixedColumnsLeft) {
this.index = index;
this.indexes = indexes;
this.data = data;
this.amount = this.data[0].length;
this.headers = headers;
this.columnPositions = columnPositions.slice(0);
this.rowPositions = rowPositions.slice(0);
this.actionType = 'remove_col';
this.fixedColumnsLeft = fixedColumnsLeft;
};
inherit(UndoRedo.RemoveColumnAction, UndoRedo.Action);
UndoRedo.RemoveColumnAction.prototype.undo = function (instance, undoneCallback) {
var _this2 = this;
var settings = instance.getSettings(); // Changing by the reference as `updateSettings` doesn't work the best.
settings.fixedColumnsLeft = this.fixedColumnsLeft;
var ascendingIndexes = this.indexes.slice(0).sort();
var sortByIndexes = function sortByIndexes(elem, j, arr) {
return arr[_this2.indexes.indexOf(ascendingIndexes[j])];
};
var removedDataLength = this.data.length;
var sortedData = [];
for (var rowIndex = 0; rowIndex < removedDataLength; rowIndex++) {
sortedData.push(arrayMap(this.data[rowIndex], sortByIndexes));
}
var sortedHeaders = arrayMap(this.headers, sortByIndexes);
var changes = [];
instance.alter('insert_col', this.indexes[0], this.indexes.length, 'UndoRedo.undo');
arrayEach(instance.getSourceDataArray(), function (rowData, rowIndex) {
arrayEach(ascendingIndexes, function (changedIndex, contiquesIndex) {
rowData[changedIndex] = sortedData[rowIndex][contiquesIndex];
changes.push([rowIndex, changedIndex, rowData[changedIndex]]);
});
});
instance.setSourceDataAtCell(changes);
instance.columnIndexMapper.insertIndexes(ascendingIndexes[0], ascendingIndexes.length);
if (typeof this.headers !== 'undefined') {
arrayEach(sortedHeaders, function (headerData, columnIndex) {
instance.getSettings().colHeaders[ascendingIndexes[columnIndex]] = headerData;
});
}
instance.batchExecution(function () {
// Restore row sequence in a case when all columns are removed. the original
// row sequence is lost in that case.
instance.rowIndexMapper.setIndexesSequence(_this2.rowPositions);
instance.columnIndexMapper.setIndexesSequence(_this2.columnPositions);
}, true);
instance.addHookOnce('afterRender', undoneCallback);
instance.render();
};
UndoRedo.RemoveColumnAction.prototype.redo = function (instance, redoneCallback) {
instance.addHookOnce('afterRemoveCol', redoneCallback);
instance.alter('remove_col', this.index, this.amount, 'UndoRedo.redo');
};
/**
* Cell alignment action.
*
* @private
* @param {Array} stateBefore The previous state.
* @param {object} range The cell range.
* @param {string} type The type of the alignment ("top", "left", "bottom" or "right").
* @param {string} alignment The alignment CSS class.
*/
UndoRedo.CellAlignmentAction = function (stateBefore, range, type, alignment) {
this.stateBefore = stateBefore;
this.range = range;
this.type = type;
this.alignment = alignment;
};
UndoRedo.CellAlignmentAction.prototype.undo = function (instance, undoneCallback) {
var _this3 = this;
arrayEach(this.range, function (range) {
range.forAll(function (row, col) {
// Alignment classes should only collected within cell ranges. We skip header coordinates.
if (row >= 0 && col >= 0) {
instance.setCellMeta(row, col, 'className', _this3.stateBefore[row][col] || ' htLeft');
}
});
});
instance.addHookOnce('afterRender', undoneCallback);
instance.render();
};
UndoRedo.CellAlignmentAction.prototype.redo = function (instance, undoneCallback) {
align(this.range, this.type, this.alignment, function (row, col) {
return instance.getCellMeta(row, col);
}, function (row, col, key, value) {
return instance.setCellMeta(row, col, key, value);
});
instance.addHookOnce('afterRender', undoneCallback);
instance.render();
};
/**
* Filters action.
*
* @private
* @param {Array} conditionsStack An array of the filter condition.
*/
UndoRedo.FiltersAction = function (conditionsStack) {
this.conditionsStack = conditionsStack;
this.actionType = 'filter';
};
inherit(UndoRedo.FiltersAction, UndoRedo.Action);
UndoRedo.FiltersAction.prototype.undo = function (instance, undoneCallback) {
var filters = instance.getPlugin('filters');
instance.addHookOnce('afterRender', undoneCallback);
filters.conditionCollection.importAllConditions(this.conditionsStack.slice(0, this.conditionsStack.length - 1));
filters.filter();
};
UndoRedo.FiltersAction.prototype.redo = function (instance, redoneCallback) {
var filters = instance.getPlugin('filters');
instance.addHookOnce('afterRender', redoneCallback);
filters.conditionCollection.importAllConditions(this.conditionsStack);
filters.filter();
};
/**
* Merge Cells action.
*
* @util
*/
var MergeCellsAction = /*#__PURE__*/function (_UndoRedo$Action) {
_inherits(MergeCellsAction, _UndoRedo$Action);
var _super = _createSuper(MergeCellsAction);
function MergeCellsAction(instance, cellRange) {
var _this4;
_classCallCheck(this, MergeCellsAction);
_this4 = _super.call(this);
_this4.cellRange = cellRange;
var topLeftCorner = _this4.cellRange.getTopLeftCorner();
var bottomRightCorner = _this4.cellRange.getBottomRightCorner();
_this4.rangeData = instance.getData(topLeftCorner.row, topLeftCorner.col, bottomRightCorner.row, bottomRightCorner.col);
return _this4;
}
_createClass(MergeCellsAction, [{
key: "undo",
value: function undo(instance, undoneCallback) {
var mergeCellsPlugin = instance.getPlugin('mergeCells');
instance.addHookOnce('afterRender', undoneCallback);
mergeCellsPlugin.unmergeRange(this.cellRange, true);
var topLeftCorner = this.cellRange.getTopLeftCorner();
instance.populateFromArray(topLeftCorner.row, topLeftCorner.col, this.rangeData, void 0, void 0, 'MergeCells');
}
}, {
key: "redo",
value: function redo(instance, redoneCallback) {
var mergeCellsPlugin = instance.getPlugin('mergeCells');
instance.addHookOnce('afterRender', redoneCallback);
mergeCellsPlugin.mergeRange(this.cellRange);
}
}]);
return MergeCellsAction;
}(UndoRedo.Action);
UndoRedo.MergeCellsAction = MergeCellsAction;
/**
* Unmerge Cells action.
*
* @util
*/
var UnmergeCellsAction = /*#__PURE__*/function (_UndoRedo$Action2) {
_inherits(UnmergeCellsAction, _UndoRedo$Action2);
var _super2 = _createSuper(UnmergeCellsAction);
function UnmergeCellsAction(instance, cellRange) {
var _this5;
_classCallCheck(this, UnmergeCellsAction);
_this5 = _super2.call(this);
_this5.cellRange = cellRange;
return _this5;
}
_createClass(UnmergeCellsAction, [{
key: "undo",
value: function undo(instance, undoneCallback) {
var mergeCellsPlugin = instance.getPlugin('mergeCells');
instance.addHookOnce('afterRender', undoneCallback);
mergeCellsPlugin.mergeRange(this.cellRange, true);
}
}, {
key: "redo",
value: function redo(instance, redoneCallback) {
var mergeCellsPlugin = instance.getPlugin('mergeCells');
instance.addHookOnce('afterRender', redoneCallback);
mergeCellsPlugin.unmergeRange(this.cellRange, true);
instance.render();
}
}]);
return UnmergeCellsAction;
}(UndoRedo.Action);
UndoRedo.UnmergeCellsAction = UnmergeCellsAction;
/**
* ManualRowMove action.
*
* @TODO removeRow undo should works on logical index
* @private
* @param {number[]} rows An array with moved rows.
* @param {number} finalIndex The destination index.
*/
UndoRedo.RowMoveAction = function (rows, finalIndex) {
this.rows = rows.slice();
this.finalIndex = finalIndex;
};
inherit(UndoRedo.RowMoveAction, UndoRedo.Action);
UndoRedo.RowMoveAction.prototype.undo = function (instance, undoneCallback) {
var _this6 = this;
var manualRowMove = instance.getPlugin('manualRowMove');
var copyOfRows = [].concat(this.rows);
var rowsMovedUp = copyOfRows.filter(function (a) {
return a > _this6.finalIndex;
});
var rowsMovedDown = copyOfRows.filter(function (a) {
return a <= _this6.finalIndex;
});
var allMovedRows = rowsMovedUp.sort(function (a, b) {
return b - a;
}).concat(rowsMovedDown.sort(function (a, b) {
return a - b;
}));
instance.addHookOnce('afterRender', undoneCallback); // Moving rows from those with higher indexes to those with lower indexes when action was performed from bottom to top
// Moving rows from those with lower indexes to those with higher indexes when action was performed from top to bottom
for (var i = 0; i < allMovedRows.length; i += 1) {
var newPhysicalRow = instance.toVisualRow(allMovedRows[i]);
manualRowMove.moveRow(newPhysicalRow, allMovedRows[i]);
}
instance.render();
instance.deselectCell();
instance.selectRows(this.rows[0], this.rows[0] + this.rows.length - 1);
};
UndoRedo.RowMoveAction.prototype.redo = function (instance, redoneCallback) {
var manualRowMove = instance.getPlugin('manualRowMove');
instance.addHookOnce('afterRender', redoneCallback);
manualRowMove.moveRows(this.rows.slice(), this.finalIndex);
instance.render();
instance.deselectCell();
instance.selectRows(this.finalIndex, this.finalIndex + this.rows.length - 1);
};
/**
* Enabling and disabling plugin and attaching its to an instance.
*
* @private
*/
UndoRedo.prototype.init = function () {
var settings = this.instance.getSettings().undo;
var pluginEnabled = typeof settings === 'undefined' || settings;
if (!this.instance.undoRedo) {
/**
* Instance of Handsontable.UndoRedo Plugin {@link Handsontable.UndoRedo}.
*
* @alias undoRedo
* @memberof! Handsontable.Core#
* @type {UndoRedo}
*/
this.instance.undoRedo = this;
}
if (pluginEnabled) {
this.instance.undoRedo.enable();
} else {
this.instance.undoRedo.disable();
}
};
/**
* @param {Event} event The keyboard event object.
*/
function onBeforeKeyDown(event) {
if (isImmediatePropagationStopped(event)) {
return;
}
var instance = this;
var editor = instance.getActiveEditor();
if (editor && editor.isOpened()) {
return;
}
var altKey = event.altKey,
ctrlKey = event.ctrlKey,
keyCode = event.keyCode,
metaKey = event.metaKey,
shiftKey = event.shiftKey;
var isCtrlDown = (ctrlKey || metaKey) && !altKey;
if (!isCtrlDown) {
return;
}
var isRedoHotkey = keyCode === 89 || shiftKey && keyCode === 90;
if (isRedoHotkey) {
// CTRL + Y or CTRL + SHIFT + Z
instance.undoRedo.redo();
stopImmediatePropagation(event);
} else if (keyCode === 90) {
// CTRL + Z
instance.undoRedo.undo();
stopImmediatePropagation(event);
}
}
/**
* @param {Array} changes 2D array containing information about each of the edited cells.
* @param {string} source String that identifies source of hook call.
* @returns {boolean}
*/
function onAfterChange(changes, source) {
var instance = this;
if (source === 'loadData') {
return instance.undoRedo.clear();
}
}
/**
* @param {Core} instance The Handsontable instance.
*/
function exposeUndoRedoMethods(instance) {
/**
* {@link UndoRedo#undo}.
*
* @alias undo
* @memberof! Handsontable.Core#
* @returns {boolean}
*/
instance.undo = function () {
return instance.undoRedo.undo();
};
/**
* {@link UndoRedo#redo}.
*
* @alias redo
* @memberof! Handsontable.Core#
* @returns {boolean}
*/
instance.redo = function () {
return instance.undoRedo.redo();
};
/**
* {@link UndoRedo#isUndoAvailable}.
*
* @alias isUndoAvailable
* @memberof! Handsontable.Core#
* @returns {boolean}
*/
instance.isUndoAvailable = function () {
return instance.undoRedo.isUndoAvailable();
};
/**
* {@link UndoRedo#isRedoAvailable}.
*
* @alias isRedoAvailable
* @memberof! Handsontable.Core#
* @returns {boolean}
*/
instance.isRedoAvailable = function () {
return instance.undoRedo.isRedoAvailable();
};
/**
* {@link UndoRedo#clear}.
*
* @alias clearUndo
* @memberof! Handsontable.Core#
* @returns {boolean}
*/
instance.clearUndo = function () {
return instance.undoRedo.clear();
};
}
/**
* @param {Core} instance The Handsontable instance.
*/
function removeExposedUndoRedoMethods(instance) {
delete instance.undo;
delete instance.redo;
delete instance.isUndoAvailable;
delete instance.isRedoAvailable;
delete instance.clearUndo;
}
var hook = Hooks.getSingleton();
hook.add('afterUpdateSettings', function () {
this.getPlugin('undoRedo').init();
});
hook.register('beforeUndo');
hook.register('afterUndo');
hook.register('beforeRedo');
hook.register('afterRedo');
UndoRedo.PLUGIN_KEY = PLUGIN_KEY;
export default UndoRedo;