handsontable
Version:
Handsontable is a JavaScript Data Grid available for React, Angular and Vue.
298 lines (283 loc) • 9.75 kB
JavaScript
"use strict";
exports.__esModule = true;
var _base = require("../base");
var _hooks = require("../../core/hooks");
var _object = require("../../helpers/object");
var _actions = require("./actions");
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } /* eslint-disable jsdoc/require-description-complete-sentence */
const SHORTCUTS_GROUP = 'undoRedo';
const PLUGIN_KEY = exports.PLUGIN_KEY = 'undoRedo';
const PLUGIN_PRIORITY = exports.PLUGIN_PRIORITY = 1000;
_hooks.Hooks.getSingleton().register('beforeUndo');
_hooks.Hooks.getSingleton().register('afterUndo');
_hooks.Hooks.getSingleton().register('beforeRedo');
_hooks.Hooks.getSingleton().register('afterRedo');
/**
* @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
*/
var _UndoRedo_brand = /*#__PURE__*/new WeakSet();
class UndoRedo extends _base.BasePlugin {
static get PLUGIN_KEY() {
return PLUGIN_KEY;
}
static get PLUGIN_PRIORITY() {
return PLUGIN_PRIORITY;
}
static get SETTING_KEYS() {
return true;
}
/**
* The list of registered action do undo.
*
* @private
* @type {Array}
*/
constructor(hotInstance) {
super(hotInstance);
/**
* Listens to the data change and if the source is `loadData` then clears the undo and redo history.
*
* @param {Array} changes The data changes.
* @param {string} source The source of the change.
*/
_classPrivateMethodInitSpec(this, _UndoRedo_brand);
_defineProperty(this, "doneActions", []);
/**
* The list of registered action do redo.
*
* @private
* @type {Array}
*/
_defineProperty(this, "undoneActions", []);
/**
* The flag that determines if new actions should be ignored.
*
* @private
* @type {boolean}
*/
_defineProperty(this, "ignoreNewActions", false);
(0, _actions.registerActions)(hotInstance, this);
}
/**
* Checks if the plugin is enabled in the handsontable settings. This method is executed in {@link Hooks#beforeInit}
* hook and if it returns `true` then the {@link UndoRedo#enablePlugin} method is called.
*
* @returns {boolean}
*/
isEnabled() {
return !!this.hot.getSettings().undo;
}
/**
* Enables the plugin functionality for this Handsontable instance.
*/
enablePlugin() {
var _this = this;
if (this.enabled) {
return;
}
this.addHook('afterChange', function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _assertClassBrand(_UndoRedo_brand, _this, _onAfterChange).call(_this, ...args);
});
this.registerShortcuts();
super.enablePlugin();
}
/**
* Disables the plugin functionality for this Handsontable instance.
*/
disablePlugin() {
super.disablePlugin();
this.clear();
this.unregisterShortcuts();
}
/**
* Registers shortcuts responsible for performing undo/redo.
*
* @private
*/
registerShortcuts() {
const shortcutManager = this.hot.getShortcutManager();
const gridContext = shortcutManager.getContext('grid');
const runOnlyIf = event => {
return !event.altKey; // right ALT in some systems triggers ALT+CTR
};
const config = {
runOnlyIf,
group: SHORTCUTS_GROUP
};
gridContext.addShortcuts([{
keys: [['Control/Meta', 'z']],
callback: () => {
this.undo();
}
}, {
keys: [['Control/Meta', 'y'], ['Control/Meta', 'Shift', 'z']],
callback: () => {
this.redo();
}
}], config);
}
/**
* Unregister shortcuts responsible for performing undo/redo.
*
* @private
*/
unregisterShortcuts() {
const shortcutManager = this.hot.getShortcutManager();
const gridContext = shortcutManager.getContext('grid');
gridContext.removeShortcutsByGroup(SHORTCUTS_GROUP);
}
/**
* Stash information about performed actions.
*
* @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).
*/
done(wrappedAction, source) {
if (this.ignoreNewActions) {
return;
}
const isBlockedByDefault = source === 'UndoRedo.undo' || source === 'UndoRedo.redo' || source === 'auto';
if (isBlockedByDefault) {
return;
}
const doneActionsCopy = this.doneActions.slice();
const continueAction = this.hot.runHooks('beforeUndoStackChange', doneActionsCopy, source);
if (continueAction === false) {
return;
}
const newAction = wrappedAction();
const undoneActionsCopy = this.undoneActions.slice();
if (newAction !== null) {
this.doneActions.push(newAction);
}
this.hot.runHooks('afterUndoStackChange', doneActionsCopy, this.doneActions.slice());
this.hot.runHooks('beforeRedoStackChange', undoneActionsCopy);
this.undoneActions.length = 0;
this.hot.runHooks('afterRedoStackChange', undoneActionsCopy, this.undoneActions.slice());
}
/**
* Undo the last action performed to the table.
*
* @fires Hooks#beforeUndoStackChange
* @fires Hooks#afterUndoStackChange
* @fires Hooks#beforeRedoStackChange
* @fires Hooks#afterRedoStackChange
* @fires Hooks#beforeUndo
* @fires Hooks#afterUndo
*/
undo() {
if (!this.isUndoAvailable()) {
return;
}
const doneActionsCopy = this.doneActions.slice();
this.hot.runHooks('beforeUndoStackChange', doneActionsCopy);
const action = this.doneActions.pop();
this.hot.runHooks('afterUndoStackChange', doneActionsCopy, this.doneActions.slice());
const actionClone = (0, _object.deepClone)(action);
const continueAction = this.hot.runHooks('beforeUndo', actionClone);
if (continueAction === false) {
return;
}
this.ignoreNewActions = true;
const undoneActionsCopy = this.undoneActions.slice();
this.hot.runHooks('beforeRedoStackChange', undoneActionsCopy);
action.undo(this.hot, () => {
this.ignoreNewActions = false;
this.undoneActions.push(action);
});
this.hot.runHooks('afterRedoStackChange', undoneActionsCopy, this.undoneActions.slice());
this.hot.runHooks('afterUndo', actionClone);
}
/**
* Redo the previous action performed to the table (used to reverse an undo).
*
* @fires Hooks#beforeUndoStackChange
* @fires Hooks#afterUndoStackChange
* @fires Hooks#beforeRedoStackChange
* @fires Hooks#afterRedoStackChange
* @fires Hooks#beforeRedo
* @fires Hooks#afterRedo
*/
redo() {
if (!this.isRedoAvailable()) {
return;
}
const undoneActionsCopy = this.undoneActions.slice();
this.hot.runHooks('beforeRedoStackChange', undoneActionsCopy);
const action = this.undoneActions.pop();
this.hot.runHooks('afterRedoStackChange', undoneActionsCopy, this.undoneActions.slice());
const actionClone = (0, _object.deepClone)(action);
const continueAction = this.hot.runHooks('beforeRedo', actionClone);
if (continueAction === false) {
return;
}
this.ignoreNewActions = true;
const doneActionsCopy = this.doneActions.slice();
this.hot.runHooks('beforeUndoStackChange', doneActionsCopy);
action.redo(this.hot, () => {
this.ignoreNewActions = false;
this.doneActions.push(action);
});
this.hot.runHooks('afterUndoStackChange', doneActionsCopy, this.doneActions.slice());
this.hot.runHooks('afterRedo', actionClone);
}
/**
* Checks if undo action is available.
*
* @returns {boolean} Return `true` if undo can be performed, `false` otherwise.
*/
isUndoAvailable() {
return this.doneActions.length > 0;
}
/**
* Checks if redo action is available.
*
* @returns {boolean} Return `true` if redo can be performed, `false` otherwise.
*/
isRedoAvailable() {
return this.undoneActions.length > 0;
}
/**
* Clears undo and redo history.
*/
clear() {
this.doneActions.length = 0;
this.undoneActions.length = 0;
}
/**
* Destroys the plugin instance.
*/
destroy() {
this.clear();
this.doneActions = null;
this.undoneActions = null;
super.destroy();
}
}
exports.UndoRedo = UndoRedo;
function _onAfterChange(changes, source) {
if (source === 'loadData') {
this.clear();
}
}