UNPKG

handsontable

Version:

Handsontable is a JavaScript Data Grid available for React, Angular and Vue.

402 lines (398 loc) • 15 kB
"use strict"; exports.__esModule = true; var _baseEditor = require("./baseEditor/baseEditor"); var _eventManager = _interopRequireDefault(require("../eventManager")); var _errors = require("../helpers/errors"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /** * Factory function for creating custom Handsontable editors by extending BaseEditor. * * This factory allows you to create custom editors by providing implementations for various * editor lifecycle methods. It handles the prototype chain setup and method delegation to * the BaseEditor superclass automatically. * * @param {object} params Configuration object containing editor lifecycle methods and custom methods. * @param {Function} params.prepare Called before editing begins to initialize the editor. * @param {Function} params.beginEditing Called when editing starts. * @param {Function} params.finishEditing Called when editing ends. * @param {Function} params.discardEditor Called to discard editor changes. * @param {Function} params.saveValue Called to save the edited value. * @param {Function} params.getValue Called to retrieve the current editor value. * @param {Function} params.setValue Called to set the editor value. * @param {Function} params.open Called to open/show the editor UI. * @param {Function} params.close Called to close/hide the editor UI. * @param {Function} params.focus Called to focus the editor. * @param {Function} params.cancelChanges Called to cancel editing changes. * @param {Function} params.checkEditorSection Called to determine which section the editor belongs to. * @param {Function} params.enableFullEditMode Called to enable full edit mode. * @param {Function} params.extend Called to extend the editor class. * @param {Function} params.getEditedCell Called to get the currently edited cell element. * @param {Function} params.getEditedCellRect Called to get the edited cell's position and dimensions. * @param {Function} params.getEditedCellsZIndex Called to get the z-index for the edited cell. * @param {Function} params.init Called during editor initialization. * @param {Function} params.isInFullEditMode Called to check if editor is in full edit mode. * @param {Function} params.isOpened Called to check if editor is currently open. * @param {Function} params.isWaiting Called to check if editor is waiting for input. * * @returns {Function} A custom editor class extending Handsontable's BaseEditor. * * @example * ```typescript * const MyEditor = editorBaseFactory({ * prepare(editor, row, col, prop, td, originalValue, cellProperties) { * // Initialize your editor * }, * open(editor) { * // Show your editor UI * }, * close(editor) { * // Hide your editor UI * }, * getValue(editor) { * return editor.customValue; * } * }); * ``` */ const editorBaseFactory = params => { const CustomBaseEditor = _baseEditor.BaseEditor.prototype.extend(); // Skip super in abstract functions const skipSuperApply = ['close', 'focus', 'getValue', 'open', 'setValue']; const prototypeFns = Object.getOwnPropertyNames(_baseEditor.BaseEditor.prototype); // Apply editor class methods from params object prototypeFns.forEach(fnName => { if (params[fnName]) { const superFn = CustomBaseEditor.prototype[fnName]; CustomBaseEditor.prototype[fnName] = function () { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } if (!skipSuperApply.includes(fnName)) { superFn.apply(this, args); } return params[fnName](this, ...args); }; } }); // Apply custom methods Object.keys(params).forEach(fnName => { if (!prototypeFns.includes(fnName)) { CustomBaseEditor.prototype[fnName] = function () { for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } // `this` will be BaseEditor & T, as expected for custom methods. return params[fnName](this, ...args); }; } }); return CustomBaseEditor; }; /** * Factory function to create a custom Handsontable editor. * * `editorFactory` helps you create modular, reusable, and fully custom editors * for Handsontable grid cells. The factory handles lifecycle, DOM structure, and * keyboard shortcuts, allowing you to focus on business-specific UI and value logic. * * @param {object} options Configuration and lifecycle methods for the editor. * @param {Function} options.init Called when this editor is constructed by the Handsontable grid. * @param {Function} options.afterOpen Called after the editor is opened and made visible. * @param {Function} options.afterInit Called immediately after init, useful for event binding, etc. * @param {Function} options.afterClose Called when the editor is closed and made invisible. * @param {Function} options.beforeOpen Called before the editor is opened so you can set its value/state. * @param {Function} options.getValue Called to retrieve the current editor value. * @param {Function} options.setValue Called to set the editor's value and update any UI as needed. * @param {Function} options.onFocus Called to focus the editor. * @param {Array<object>} [options.shortcuts] Called to register all configured keyboard shortcuts for this editor instance. * @param {any} options.value The initial value for the editor input/state. * @param {Function} options.render Called to render the editor UI. * @param {any} options.config The configuration for the editor. * @param {string} options.shortcutsGroup The group for the keyboard shortcuts. * @param {string} options.position The position of the editor. Either 'container' (default) or 'portal' (for elements outside of the table container viewport). * @param {object} [options.args] Any additional custom fields or helpers you want mixed into the editor instance. * * @returns {BaseEditor} A custom editor class extending Handsontable's BaseEditor. */ const editorFactory = _ref => { let { /** * Called when this editor is constructed by the Handsontable grid. * Assigns value/config/render/etc, creates UI container, initializes with provided init. * * @param {BaseEditor} editor * @returns {void} */ init, /** * Called after the editor is opened and made visible. * * @param {BaseEditor} editor * @returns {void} */ afterOpen, /** * Called immediately after init, useful for event binding, etc. * * @param {BaseEditor} editor * @returns {void} */ afterInit, /** * Called when the editor is closed and made invisible. * * @param {BaseEditor} editor * @returns {void} */ afterClose, /** * Called before the editor is opened so you can set its value/state. * * @param {BaseEditor} editor * @returns {void} */ beforeOpen, /** * Called to retrieve the current editor value. * * @param {BaseEditor} editor * @returns {any} */ getValue, /** * Called to set the editor's value and update any UI as needed. * * @param {BaseEditor} editor * @param {any} value * @returns {void} */ setValue, /** * Called to focus the editor. * * @param {BaseEditor} editor * @returns {void} */ onFocus, shortcuts, value, /** * Called to render the editor UI. * * @param {BaseEditor} editor * @returns {void} */ render, config, shortcutsGroup = 'custom-editor', position = 'container', /** * @param {...object} [args] Any additional custom fields or helpers you want mixed into the editor instance. */ ...args } = _ref; /** * Register all configured keyboard shortcuts for this editor instance. * * @param {BaseEditor} editor The editor instance. * @returns {void} * @private */ const registerShortcuts = editor => { const shortcutManager = editor.hot.getShortcutManager(); const editorContext = shortcutManager.getContext('editor'); const contextConfig = { group: shortcutsGroup }; if (shortcuts) { editorContext.addShortcuts(shortcuts.map(shortcut => ({ ...shortcut, relativeToGroup: shortcut.relativeToGroup || 'editorManager.handlingEditor', position: shortcut.position || 'before', callback: event => shortcut.callback(editor, event) })), contextConfig); } }; // Compose the Handsontable editor definition using the core editorBaseFactory: return editorBaseFactory({ /** * Called when this editor is constructed by the Handsontable grid. * Assigns value/config/render/etc, creates UI container, initializes with provided init. * * @param {BaseEditor} editor The editor instance. * @returns {void} */ init(editor) { Object.assign(editor, { value, config, render, position, ...args }); editor._opened = false; editor.container = editor.hot.rootDocument.createElement('DIV'); editor.container.style.display = 'none'; if (position === 'portal') { editor.hot.rootPortalElement.appendChild(editor.container); } else { editor.hot.rootElement.appendChild(editor.container); } init(editor); if (!editor.input) { (0, _errors.throwWithCause)('Input is not assigned. Assign it in the init callback.'); } editor.container.appendChild(editor.input); if (typeof afterInit === 'function') { afterInit(editor); } if (editor.preventCloseElement && editor.preventCloseElement instanceof HTMLElement) { editor.eventManager = new _eventManager.default(editor.hot); editor.eventManager.addEventListener(editor.preventCloseElement, 'mousedown', event => { event.stopPropagation(); }); } editor.addHook('afterScrollHorizontally', () => editor.refreshDimensions()); editor.addHook('afterScrollVertically', () => editor.refreshDimensions()); }, /** * Retrieve the value from the editor UI. * * @param {BaseEditor} editor The editor instance. * @returns {any} */ getValue(editor) { if (typeof getValue === 'function') { return getValue(editor); } return editor.value; }, /** * Set the editor's value and update any UI as needed. * * @param {BaseEditor} editor The editor instance. * @param {any} _value The value to set. * @returns {void} */ setValue(editor, _value) { if (typeof setValue === 'function') { setValue(editor, _value); } else { editor.value = _value; } if (typeof render === 'function') { render(editor); } }, /** * Refreshes editor position and dimensions (for example, after scroll). * * @param {BaseEditor} editor The editor instance. * @returns {void} */ refreshDimensions(editor) { editor.TD = editor.getEditedCell(); if (!editor.TD) { editor.close(); return; } if (!editor._opened) { return; } const containerStyle = editor.container.style; containerStyle.display = 'block'; containerStyle.position = 'absolute'; if (editor.position === 'portal') { const _offset = editor.TD.getBoundingClientRect(); containerStyle.top = `${editor.hot.rootWindow.pageYOffset + _offset.top}px`; containerStyle[editor.hot.isRtl() ? 'right' : 'left'] = `${editor.hot.rootWindow.pageXOffset + _offset[editor.hot.isRtl() ? 'right' : 'left']}px`; } else { const rect = editor.getEditedCellRect(); if (!rect) { return; } containerStyle.top = `${rect.top}px`; containerStyle[editor.hot.isRtl() ? 'right' : 'left'] = `${rect.start}px`; containerStyle.width = `${rect.width}px`; containerStyle.height = `${rect.height}px`; } }, /** * Opens the editor, making the container visible and binding shortcuts. * * @param {BaseEditor} editor The editor instance. * @param {Event} event The event that triggered the editor opening. * @returns {void} */ open(editor) { let event = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; editor.container.classList.add('ht_clone_master'); editor._opened = true; editor.refreshDimensions(); editor.hot.getShortcutManager().setActiveContextName('editor'); registerShortcuts(editor); if (afterOpen) { afterOpen(editor, event); } }, /** * Focus on the correct UI element within your editor. * * @param {BaseEditor} editor The editor instance. * @returns {void} */ focus(editor) { if (typeof onFocus === 'function') { onFocus(editor); } else { var _editor$container$que; // eslint-disable-next-line max-len (_editor$container$que = editor.container.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')) === null || _editor$container$que === void 0 || _editor$container$que.focus(); } }, /** * Close the editor UI and cleanup active shortcuts. * * @param {BaseEditor} editor The editor instance. * @returns {void} */ close(editor) { editor._opened = false; editor.container.style.display = 'none'; editor.container.classList.remove('ht_clone_master'); const shortcutManager = editor.hot.getShortcutManager(); const editorContext = shortcutManager.getContext('editor'); editorContext.removeShortcutsByGroup(shortcutsGroup); if (typeof afterClose === 'function') { afterClose(editor); } }, /** * Prepare the editor to start editing a new value. Invokes beforeOpen or falls back. * * @param {BaseEditor} editor The editor instance. * @param {number} row The row index. * @param {number} col The column index. * @param {number|string} prop The property name or index. * @param {HTMLTableCellElement} td The table cell element. * @param {any} originalValue The original value. * @param {object} cellProperties The cell properties. * @returns {void} */ prepare(editor, row, col, prop, td, originalValue, cellProperties) { if (typeof beforeOpen === 'function') { beforeOpen(editor, { row, col, prop, td, originalValue, cellProperties }); } else { editor.setValue(originalValue); } } }); }; exports.editorFactory = editorFactory;