UNPKG

handsontable

Version:

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

557 lines (461 loc) • 18.7 kB
"use strict"; require("core-js/modules/es.symbol.js"); require("core-js/modules/es.symbol.description.js"); require("core-js/modules/es.symbol.iterator.js"); require("core-js/modules/es.array.slice.js"); require("core-js/modules/es.function.name.js"); require("core-js/modules/es.array.from.js"); require("core-js/modules/es.regexp.exec.js"); exports.__esModule = true; exports.default = exports.SHORTCUTS_GROUP_NAVIGATION = exports.SHORTCUTS_GROUP_EDITOR = void 0; require("core-js/modules/es.array.includes.js"); require("core-js/modules/es.string.includes.js"); require("core-js/modules/es.array.iterator.js"); require("core-js/modules/es.object.to-string.js"); require("core-js/modules/es.string.iterator.js"); require("core-js/modules/es.weak-map.js"); require("core-js/modules/web.dom-collections.iterator.js"); var _unicode = require("./helpers/unicode"); var _event = require("./helpers/dom/event"); var _registry = require("./editors/registry"); var _eventManager = _interopRequireDefault(require("./eventManager")); var _mixed = require("./helpers/mixed"); 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) { var _i = arr == null ? null : 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; } 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); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } var SHORTCUTS_GROUP_NAVIGATION = 'editorManager.navigation'; exports.SHORTCUTS_GROUP_NAVIGATION = SHORTCUTS_GROUP_NAVIGATION; var SHORTCUTS_GROUP_EDITOR = 'editorManager.handlingEditor'; exports.SHORTCUTS_GROUP_EDITOR = SHORTCUTS_GROUP_EDITOR; var EditorManager = /*#__PURE__*/function () { /** * @param {Core} instance The Handsontable instance. * @param {TableMeta} tableMeta The table meta instance. * @param {Selection} selection The selection instance. */ function EditorManager(instance, tableMeta, selection) { var _this = this; _classCallCheck(this, EditorManager); /** * Instance of {@link Handsontable}. * * @private * @type {Handsontable} */ this.instance = instance; /** * Reference to an instance's private GridSettings object. * * @private * @type {GridSettings} */ this.tableMeta = tableMeta; /** * Instance of {@link Selection}. * * @private * @type {Selection} */ this.selection = selection; /** * Instance of {@link EventManager}. * * @private * @type {EventManager} */ this.eventManager = new _eventManager.default(instance); /** * Determines if EditorManager is destroyed. * * @private * @type {boolean} */ this.destroyed = false; /** * Determines if EditorManager is locked. * * @private * @type {boolean} */ this.lock = false; /** * A reference to an instance of the activeEditor. * * @private * @type {BaseEditor} */ this.activeEditor = void 0; /** * Keeps a reference to the cell's properties object. * * @type {object} */ this.cellProperties = void 0; var shortcutManager = this.instance.getShortcutManager(); shortcutManager.addContext('editor'); this.registerShortcuts(); this.instance.addHook('afterDocumentKeyDown', function (event) { return _this.onAfterDocumentKeyDown(event); }); // Open editor when text composition is started (IME editor) this.eventManager.addEventListener(this.instance.rootDocument.documentElement, 'compositionstart', function (event) { if (!_this.destroyed && _this.activeEditor && !_this.activeEditor.isOpened() && _this.instance.isListening()) { _this.openEditor('', event); } }); this.instance.view._wt.update('onCellDblClick', function (event, coords, elem) { return _this.onCellDblClick(event, coords, elem); }); } /** * Register shortcuts responsible for handling some actions related to an editor. * * @private */ _createClass(EditorManager, [{ key: "registerShortcuts", value: function registerShortcuts() { var _this2 = this; var shortcutManager = this.instance.getShortcutManager(); var gridContext = shortcutManager.getContext('grid'); var editorContext = shortcutManager.getContext('editor'); var config = { group: SHORTCUTS_GROUP_EDITOR }; editorContext.addShortcuts([{ keys: [['Enter'], ['Enter', 'Shift'], ['Enter', 'Control/Meta'], ['Enter', 'Control/Meta', 'Shift']], callback: function callback(event, keys) { _this2.closeEditorAndSaveChanges(shortcutManager.isCtrlPressed()); _this2.moveSelectionAfterEnter(keys.includes('shift')); } }, { keys: [['Escape'], ['Escape', 'Control/Meta']], callback: function callback() { _this2.closeEditorAndRestoreOriginalValue(shortcutManager.isCtrlPressed()); _this2.activeEditor.focus(); } }], config); gridContext.addShortcuts([{ keys: [['F2']], callback: function callback(event) { if (_this2.activeEditor) { _this2.activeEditor.enableFullEditMode(); } _this2.openEditor(null, event); } }, { keys: [['Backspace'], ['Delete']], callback: function callback() { _this2.instance.emptySelectedCells(); _this2.prepareEditor(); } }, { keys: [['Enter'], ['Enter', 'Shift']], callback: function callback(event, keys) { if (_this2.instance.getSettings().enterBeginsEditing) { if (_this2.cellProperties.readOnly) { _this2.moveSelectionAfterEnter(); } else if (_this2.activeEditor) { _this2.activeEditor.enableFullEditMode(); _this2.openEditor(null, event); } } else { _this2.moveSelectionAfterEnter(keys.includes('shift')); } (0, _event.stopImmediatePropagation)(event); // required by HandsontableEditor } }], config); } /** * Lock the editor from being prepared and closed. Locking the editor prevents its closing and * reinitialized after selecting the new cell. This feature is necessary for a mobile editor. */ }, { key: "lockEditor", value: function lockEditor() { this.lock = true; } /** * Unlock the editor from being prepared and closed. This method restores the original behavior of * the editors where for every new selection its instances are closed. */ }, { key: "unlockEditor", value: function unlockEditor() { this.lock = false; } /** * Destroy current editor, if exists. * * @param {boolean} revertOriginal If `false` and the cell using allowInvalid option, * then an editor won't be closed until validation is passed. */ }, { key: "destroyEditor", value: function destroyEditor(revertOriginal) { if (!this.lock) { this.closeEditor(revertOriginal); } } /** * Get active editor. * * @returns {BaseEditor} */ }, { key: "getActiveEditor", value: function getActiveEditor() { return this.activeEditor; } /** * Prepare text input to be displayed at given grid cell. */ }, { key: "prepareEditor", value: function prepareEditor() { var _this3 = this; if (this.lock) { return; } if (this.activeEditor && this.activeEditor.isWaiting()) { this.closeEditor(false, false, function (dataSaved) { if (dataSaved) { _this3.prepareEditor(); } }); return; } var _this$instance$select = this.instance.selection.selectedRange.current().highlight, row = _this$instance$select.row, col = _this$instance$select.col; var modifiedCellCoords = this.instance.runHooks('modifyGetCellCoords', row, col); var visualRowToCheck = row; var visualColumnToCheck = col; if (Array.isArray(modifiedCellCoords)) { var _modifiedCellCoords = _slicedToArray(modifiedCellCoords, 2); visualRowToCheck = _modifiedCellCoords[0]; visualColumnToCheck = _modifiedCellCoords[1]; } // Getting values using the modified coordinates. this.cellProperties = this.instance.getCellMeta(visualRowToCheck, visualColumnToCheck); var activeElement = this.instance.rootDocument.activeElement; if (activeElement) { // Bluring the activeElement removes unwanted border around the focusable element // (and resets activeElement prop). Without blurring the activeElement points to the // previously focusable element after clicking onto the cell (#6877). activeElement.blur(); } if (this.cellProperties.readOnly) { this.clearActiveEditor(); return; } var editorClass = this.instance.getCellEditor(this.cellProperties); // Getting element using coordinates from the selection. var td = this.instance.getCell(row, col, true); if (editorClass && td) { var prop = this.instance.colToProp(visualColumnToCheck); var originalValue = this.instance.getSourceDataAtCell(this.instance.toPhysicalRow(visualRowToCheck), visualColumnToCheck); this.activeEditor = (0, _registry.getEditorInstance)(editorClass, this.instance); // Using not modified coordinates, as we need to get the table element using selection coordinates. // There is an extra translation in the editor for saving value. this.activeEditor.prepare(row, col, prop, td, originalValue, this.cellProperties); } else { this.clearActiveEditor(); } } /** * Check is editor is opened/showed. * * @returns {boolean} */ }, { key: "isEditorOpened", value: function isEditorOpened() { return this.activeEditor && this.activeEditor.isOpened(); } /** * Open editor with initial value. * * @param {null|string} newInitialValue New value from which editor will start if handled property it's not the `null`. * @param {Event} event The event object. */ }, { key: "openEditor", value: function openEditor(newInitialValue, event) { if (!this.activeEditor) { return; } this.activeEditor.beginEditing(newInitialValue, event); } /** * Close editor, finish editing cell. * * @param {boolean} restoreOriginalValue If `true`, then closes editor without saving value from the editor into a cell. * @param {boolean} isCtrlPressed If `true`, then editor will save value to each cell in the last selected range. * @param {Function} callback The callback function, fired after editor closing. */ }, { key: "closeEditor", value: function closeEditor(restoreOriginalValue, isCtrlPressed, callback) { if (this.activeEditor) { this.activeEditor.finishEditing(restoreOriginalValue, isCtrlPressed, callback); } else if (callback) { callback(false); } } /** * Close editor and save changes. * * @param {boolean} isCtrlPressed If `true`, then editor will save value to each cell in the last selected range. */ }, { key: "closeEditorAndSaveChanges", value: function closeEditorAndSaveChanges(isCtrlPressed) { this.closeEditor(false, isCtrlPressed); } /** * Close editor and restore original value. * * @param {boolean} isCtrlPressed Indication of whether the CTRL button is pressed. */ }, { key: "closeEditorAndRestoreOriginalValue", value: function closeEditorAndRestoreOriginalValue(isCtrlPressed) { this.closeEditor(true, isCtrlPressed); } /** * Clears reference to an instance of the active editor. * * @private */ }, { key: "clearActiveEditor", value: function clearActiveEditor() { this.activeEditor = void 0; } /** * Controls selection's behaviour after clicking `Enter`. * * @private * @param {boolean} isShiftPressed If `true`, then the selection will move up after hit enter. */ }, { key: "moveSelectionAfterEnter", value: function moveSelectionAfterEnter(isShiftPressed) { var enterMoves = typeof this.tableMeta.enterMoves === 'function' ? this.tableMeta.enterMoves(event) : this.tableMeta.enterMoves; if (isShiftPressed) { // move selection up this.selection.transformStart(-enterMoves.row, -enterMoves.col); } else { // move selection down (add a new row if needed) this.selection.transformStart(enterMoves.row, enterMoves.col, true); } } /** * OnAfterDocumentKeyDown callback. * * @private * @param {KeyboardEvent} event The keyboard event object. */ }, { key: "onAfterDocumentKeyDown", value: function onAfterDocumentKeyDown(event) { var _this4 = this; if (!this.instance.isListening()) { return; } var keyCode = event.keyCode; // keyCode 229 aka 'uninitialized' doesn't take into account with editors. This key code is produced when unfinished // character is entering (using IME editor). It is fired mainly on linux (ubuntu) with installed ibus-pinyin package. if (keyCode === 229) { return; } if (!this.selection.isSelected()) { return; } // catch CTRL but not right ALT (which in some systems triggers ALT+CTRL) var isCtrlPressed = (event.ctrlKey || event.metaKey) && !event.altKey; if (this.activeEditor && !this.activeEditor.isWaiting()) { if (!(0, _unicode.isFunctionKey)(keyCode) && !(0, _unicode.isCtrlMetaKey)(keyCode) && !isCtrlPressed && !this.isEditorOpened()) { var shortcutManager = this.instance.getShortcutManager(); var editorContext = shortcutManager.getContext('editor'); var runOnlySelectedConfig = { runOnlyIf: function runOnlyIf() { return (0, _mixed.isDefined)(_this4.instance.getSelected()); }, group: SHORTCUTS_GROUP_NAVIGATION }; editorContext.addShortcuts([{ keys: [['ArrowUp']], callback: function callback() { _this4.instance.selection.transformStart(-1, 0); } }, { keys: [['ArrowDown']], callback: function callback() { _this4.instance.selection.transformStart(1, 0); } }, { keys: [['ArrowLeft']], callback: function callback() { _this4.instance.selection.transformStart(0, -1 * _this4.instance.getDirectionFactor()); } }, { keys: [['ArrowRight']], callback: function callback() { _this4.instance.selection.transformStart(0, _this4.instance.getDirectionFactor()); } }], runOnlySelectedConfig); this.openEditor('', event); } } } /** * OnCellDblClick callback. * * @private * @param {MouseEvent} event The mouse event object. * @param {object} coords The cell coordinates. * @param {HTMLTableCellElement|HTMLTableHeaderCellElement} elem The element which triggers the action. */ }, { key: "onCellDblClick", value: function onCellDblClick(event, coords, elem) { // may be TD or TH if (elem.nodeName === 'TD') { if (this.activeEditor) { this.activeEditor.enableFullEditMode(); } this.openEditor(null, event); } } /** * Destroy the instance. */ }, { key: "destroy", value: function destroy() { this.destroyed = true; this.eventManager.destroy(); } }]); return EditorManager; }(); var instances = new WeakMap(); /** * @param {Core} hotInstance The Handsontable instance. * @param {TableMeta} tableMeta The table meta class instance. * @param {Selection} selection The selection instance. * @returns {EditorManager} */ EditorManager.getInstance = function (hotInstance, tableMeta, selection) { var editorManager = instances.get(hotInstance); if (!editorManager) { editorManager = new EditorManager(hotInstance, tableMeta, selection); instances.set(hotInstance, editorManager); } return editorManager; }; var _default = EditorManager; exports.default = _default;