UNPKG

handsontable

Version:

Handsontable is a JavaScript Spreadsheet Component available for React, Angular and Vue.

600 lines (490 loc) • 19.4 kB
"use strict"; 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); } require("core-js/modules/es.object.freeze.js"); require("core-js/modules/es.string.trim.js"); exports.__esModule = true; exports.BaseEditor = exports.EDITOR_STATE = exports.EDITOR_TYPE = void 0; var _src = require("../../3rdparty/walkontable/src"); var _mixed = require("../../helpers/mixed"); var _object = require("../../helpers/object"); var _hooksRefRegisterer = _interopRequireDefault(require("../../mixins/hooksRefRegisterer")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 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) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_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; } 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 { Date.prototype.toString.call(Reflect.construct(Date, [], 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 _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; } var EDITOR_TYPE = 'base'; exports.EDITOR_TYPE = EDITOR_TYPE; var EDITOR_STATE = Object.freeze({ VIRGIN: 'STATE_VIRGIN', // before editing EDITING: 'STATE_EDITING', WAITING: 'STATE_WAITING', // waiting for async validation FINISHED: 'STATE_FINISHED' }); /** * @util * @class BaseEditor */ exports.EDITOR_STATE = EDITOR_STATE; var BaseEditor = /*#__PURE__*/function () { /** * @param {Handsontable} instance A reference to the source instance of the Handsontable. */ function BaseEditor(instance) { _classCallCheck(this, BaseEditor); /** * A reference to the source instance of the Handsontable. * * @type {Handsontable} */ this.hot = instance; /** * A reference to the source instance of the Handsontable. * * @deprecated * * @type {Handsontable} */ this.instance = instance; /** * Editor's state. * * @type {string} */ this.state = EDITOR_STATE.VIRGIN; /** * Flag to store information about editor's opening status. * * @private * * @type {boolean} */ this._opened = false; /** * Defines the editor's editing mode. When false, then an editor works in fast editing mode. * * @private * * @type {boolean} */ this._fullEditMode = false; /** * Callback to call after closing editor. * * @type {Function} */ this._closeCallback = null; /** * Currently rendered cell's TD element. * * @type {HTMLTableCellElement} */ this.TD = null; /** * Visual row index. * * @type {number} */ this.row = null; /** * Visual column index. * * @type {number} */ this.col = null; /** * Column property name or a column index, if datasource is an array of arrays. * * @type {number|string} */ this.prop = null; /** * Original cell's value. * * @type {*} */ this.originalValue = null; /** * Object containing the cell's properties. * * @type {object} */ this.cellProperties = null; this.init(); } /** * Fires callback after closing editor. * * @private * @param {boolean} result The editor value. */ _createClass(BaseEditor, [{ key: "_fireCallbacks", value: function _fireCallbacks(result) { if (this._closeCallback) { this._closeCallback(result); this._closeCallback = null; } } /** * Initializes an editor's intance. */ }, { key: "init", value: function init() {} /** * Required method to get current value from editable element. */ }, { key: "getValue", value: function getValue() { throw Error('Editor getValue() method unimplemented'); } /** * Required method to set new value into editable element. */ }, { key: "setValue", value: function setValue() { throw Error('Editor setValue() method unimplemented'); } /** * Required method to open editor. */ }, { key: "open", value: function open() { throw Error('Editor open() method unimplemented'); } /** * Required method to close editor. */ }, { key: "close", value: function close() { throw Error('Editor close() method unimplemented'); } /** * Prepares editor's meta data. * * @param {number} row The visual row index. * @param {number} col The visual column index. * @param {number|string} prop The column property (passed when datasource is an array of objects). * @param {HTMLTableCellElement} td The rendered cell element. * @param {*} value The rendered value. * @param {object} cellProperties The cell meta object ({@see Core#getCellMeta}). */ }, { key: "prepare", value: function prepare(row, col, prop, td, value, cellProperties) { this.TD = td; this.row = row; this.col = col; this.prop = prop; this.originalValue = value; this.cellProperties = cellProperties; this.state = EDITOR_STATE.VIRGIN; } /** * Fallback method to provide extendable editors in ES5. * * @returns {Function} */ }, { key: "extend", value: function extend() { return /*#__PURE__*/function (_this$constructor) { _inherits(Editor, _this$constructor); var _super = _createSuper(Editor); function Editor() { _classCallCheck(this, Editor); return _super.apply(this, arguments); } return Editor; }(this.constructor); } /** * Saves value from editor into data storage. * * @param {*} value The editor value. * @param {boolean} ctrlDown If `true`, applies value to each cell in the last selected range. */ }, { key: "saveValue", value: function saveValue(value, ctrlDown) { var visualRowFrom; var visualColumnFrom; var visualRowTo; var visualColumnTo; // if ctrl+enter and multiple cells selected, behave like Excel (finish editing and apply to all cells) if (ctrlDown) { var selectedLast = this.hot.getSelectedLast(); visualRowFrom = Math.max(Math.min(selectedLast[0], selectedLast[2]), 0); // Math.max eliminate headers coords. visualColumnFrom = Math.max(Math.min(selectedLast[1], selectedLast[3]), 0); // Math.max eliminate headers coords. visualRowTo = Math.max(selectedLast[0], selectedLast[2]); visualColumnTo = Math.max(selectedLast[1], selectedLast[3]); } else { var _ref = [this.row, this.col, null, null]; visualRowFrom = _ref[0]; visualColumnFrom = _ref[1]; visualRowTo = _ref[2]; visualColumnTo = _ref[3]; } var modifiedCellCoords = this.hot.runHooks('modifyGetCellCoords', visualRowFrom, visualColumnFrom); if (Array.isArray(modifiedCellCoords)) { var _modifiedCellCoords = _slicedToArray(modifiedCellCoords, 2); visualRowFrom = _modifiedCellCoords[0]; visualColumnFrom = _modifiedCellCoords[1]; } // Saving values using the modified coordinates. this.hot.populateFromArray(visualRowFrom, visualColumnFrom, value, visualRowTo, visualColumnTo, 'edit'); } /** * Begins editing on a highlighted cell and hides fillHandle corner if was present. * * @param {*} newInitialValue The initial editor value. * @param {Event} event The keyboard event object. */ }, { key: "beginEditing", value: function beginEditing(newInitialValue, event) { if (this.state !== EDITOR_STATE.VIRGIN) { return; } var hotInstance = this.hot; // We have to convert visual indexes into renderable indexes // due to hidden columns don't participate in the rendering process var renderableRowIndex = hotInstance.rowIndexMapper.getRenderableFromVisualIndex(this.row); var renderableColumnIndex = hotInstance.columnIndexMapper.getRenderableFromVisualIndex(this.col); hotInstance.view.scrollViewport(new _src.CellCoords(renderableRowIndex, renderableColumnIndex)); this.state = EDITOR_STATE.EDITING; // Set the editor value only in the full edit mode. In other mode the focusable element has to be empty, // otherwise IME (editor for Asia users) doesn't work. if (this.isInFullEditMode()) { var stringifiedInitialValue = typeof newInitialValue === 'string' ? newInitialValue : (0, _mixed.stringify)(this.originalValue); this.setValue(stringifiedInitialValue); } this.open(event); this._opened = true; this.focus(); // only rerender the selections (FillHandle should disappear when beginediting is triggered) hotInstance.view.render(); hotInstance.runHooks('afterBeginEditing', this.row, this.col); } /** * Finishes editing and start saving or restoring process for editing cell or last selected range. * * @param {boolean} restoreOriginalValue If true, then closes editor without saving value from the editor into a cell. * @param {boolean} ctrlDown If true, then saveValue will save editor's value to each cell in the last selected range. * @param {Function} callback The callback function, fired after editor closing. */ }, { key: "finishEditing", value: function finishEditing(restoreOriginalValue, ctrlDown, callback) { var _this = this; var val; if (callback) { var previousCloseCallback = this._closeCallback; this._closeCallback = function (result) { if (previousCloseCallback) { previousCloseCallback(result); } callback(result); _this.hot.view.render(); }; } if (this.isWaiting()) { return; } if (this.state === EDITOR_STATE.VIRGIN) { this.hot._registerTimeout(function () { _this._fireCallbacks(true); }); return; } if (this.state === EDITOR_STATE.EDITING) { if (restoreOriginalValue) { this.cancelChanges(); this.hot.view.render(); return; } var value = this.getValue(); if (this.hot.getSettings().trimWhitespace) { // We trim only string values val = [[typeof value === 'string' ? String.prototype.trim.call(value || '') : value]]; } else { val = [[value]]; } this.state = EDITOR_STATE.WAITING; this.saveValue(val, ctrlDown); if (this.hot.getCellValidator(this.cellProperties)) { this.hot.addHookOnce('postAfterValidate', function (result) { _this.state = EDITOR_STATE.FINISHED; _this.discardEditor(result); }); } else { this.state = EDITOR_STATE.FINISHED; this.discardEditor(true); } } } /** * Finishes editing without singout saving value. */ }, { key: "cancelChanges", value: function cancelChanges() { this.state = EDITOR_STATE.FINISHED; this.discardEditor(); } /** * Verifies result of validation or closes editor if user's cancelled changes. * * @param {boolean|undefined} result If `false` and the cell using allowInvalid option, * then an editor won't be closed until validation is passed. */ }, { key: "discardEditor", value: function discardEditor(result) { if (this.state !== EDITOR_STATE.FINISHED) { return; } // validator was defined and failed if (result === false && this.cellProperties.allowInvalid !== true) { this.hot.selectCell(this.row, this.col); this.focus(); this.state = EDITOR_STATE.EDITING; this._fireCallbacks(false); } else { this.close(); this._opened = false; this._fullEditMode = false; this.state = EDITOR_STATE.VIRGIN; this._fireCallbacks(true); } } /** * Switch editor into full edit mode. In this state navigation keys don't close editor. This mode is activated * automatically after hit ENTER or F2 key on the cell or while editing cell press F2 key. */ }, { key: "enableFullEditMode", value: function enableFullEditMode() { this._fullEditMode = true; } /** * Checks if editor is in full edit mode. * * @returns {boolean} */ }, { key: "isInFullEditMode", value: function isInFullEditMode() { return this._fullEditMode; } /** * Returns information whether the editor is open. * * @returns {boolean} */ }, { key: "isOpened", value: function isOpened() { return this._opened; } /** * Returns information whether the editor is waiting, eg.: for async validation. * * @returns {boolean} */ }, { key: "isWaiting", value: function isWaiting() { return this.state === EDITOR_STATE.WAITING; } /** * Gets className of the edited cell if exist. * * @returns {string} */ }, { key: "getEditedCellsLayerClass", value: function getEditedCellsLayerClass() { var editorSection = this.checkEditorSection(); switch (editorSection) { case 'right': return 'ht_clone_right'; case 'left': return 'ht_clone_left'; case 'bottom': return 'ht_clone_bottom'; case 'bottom-right-corner': return 'ht_clone_bottom_right_corner'; case 'bottom-left-corner': return 'ht_clone_bottom_left_corner'; case 'top': return 'ht_clone_top'; case 'top-right-corner': return 'ht_clone_top_right_corner'; case 'top-left-corner': return 'ht_clone_top_left_corner'; default: return 'ht_clone_master'; } } /** * Gets HTMLTableCellElement of the edited cell if exist. * * @returns {HTMLTableCellElement|null} */ }, { key: "getEditedCell", value: function getEditedCell() { return this.hot.getCell(this.row, this.col, true); } /** * Returns name of the overlay, where editor is placed. * * @private * @returns {string} */ }, { key: "checkEditorSection", value: function checkEditorSection() { var totalRows = this.hot.countRows(); var section = ''; if (this.row < this.hot.getSettings().fixedRowsTop) { if (this.col < this.hot.getSettings().fixedColumnsLeft) { section = 'top-left-corner'; } else { section = 'top'; } } else if (this.hot.getSettings().fixedRowsBottom && this.row >= totalRows - this.hot.getSettings().fixedRowsBottom) { if (this.col < this.hot.getSettings().fixedColumnsLeft) { section = 'bottom-left-corner'; } else { section = 'bottom'; } } else if (this.col < this.hot.getSettings().fixedColumnsLeft) { section = 'left'; } return section; } }], [{ key: "EDITOR_TYPE", get: function get() { return EDITOR_TYPE; } }]); return BaseEditor; }(); exports.BaseEditor = BaseEditor; (0, _object.mixin)(BaseEditor, _hooksRefRegisterer.default);