UNPKG

handsontable

Version:

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

202 lines (193 loc) • 7.23 kB
import "core-js/modules/es.error.cause.js"; import { createUniqueMap } from "../utils/dataStructures/uniqueMap.mjs"; import { stopImmediatePropagation } from "../helpers/dom/event.mjs"; import { createContext, isContextObject } from "./context.mjs"; import { useRecorder } from "./recorder.mjs"; import { toSingleLine } from "../helpers/templateLiteralTag.mjs"; /* eslint-disable jsdoc/require-description-complete-sentence */ /** * The `ShortcutManager` API lets you store and manage [keyboard shortcut contexts](@/guides/navigation/keyboard-shortcuts/keyboard-shortcuts.md#keyboard-shortcut-contexts) ([`ShortcutContext`](@/api/shortcutContext.md)). * * Each `ShortcutManager` object: * - Stores and manages its own set of keyboard shortcut contexts. * - Listens to the [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) events and runs actions for them. * * @alias ShortcutManager * @class ShortcutManager * @param {object} options The manager's options * @param {EventTarget} options.ownerWindow A starting `window` element * @param {Function} options.handleEvent A condition on which `event` is handled. * @param {Function} options.beforeKeyDown A hook fired before the `keydown` event is handled. You can use it to [block a keyboard shortcut's actions](@/guides/navigation/keyboard-shortcuts/keyboard-shortcuts.md#block-a-keyboard-shortcut-s-actions). * @param {Function} options.afterKeyDown A hook fired after the `keydown` event is handled */ export const createShortcutManager = _ref => { let { ownerWindow, handleEvent, beforeKeyDown, afterKeyDown } = _ref; /** * A unique map that stores keyboard shortcut contexts. * * @type {UniqueMap} */ const CONTEXTS = createUniqueMap({ errorIdExists: keys => `The "${keys}" context name is already registered.` }); /** * The name of the active [`ShortcutContext`](@/api/shortcutContext.md). * * @type {string} */ let activeContextName = 'grid'; /** * Create a new [`ShortcutContext`](@/api/shortcutContext.md) object. * * @memberof ShortcutManager# * @param {string} contextName The name of the new shortcut context * @param {string} [scope='table'] The scope of the shortcut: `'table'` or `'global'` * @returns {object} */ const addContext = function (contextName) { let scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'table'; const context = createContext(contextName, scope); CONTEXTS.addItem(contextName, context); return context; }; /** * Get the ID of the active [`ShortcutContext`](@/api/shortcutContext.md). * * @memberof ShortcutManager# * @returns {string} */ const getActiveContextName = () => { return activeContextName; }; /** * Get a keyboard shortcut context by its name. * * @memberof ShortcutManager# * @param {string} contextName The name of the shortcut context * @returns {object|undefined} A [`ShortcutContext`](@/api/shortcutContext.md) object that stores registered shortcuts */ const getContext = contextName => { return CONTEXTS.getItem(contextName); }; /** * Start listening to keyboard shortcuts within a given [`ShortcutContext`](@/api/shortcutContext.md). * * @memberof ShortcutManager# * @param {string} contextName The name of the shortcut context */ const setActiveContextName = contextName => { if (!CONTEXTS.hasItem(contextName)) { throw new Error(toSingleLine`You've tried to activate the "${contextName}" shortcut context\x20 that does not exist. Before activation, register the context using the "addContext" method.`); } activeContextName = contextName; }; /** * This variable relates to the `captureCtrl` shortcut option, * which allows for capturing the state of the Control/Meta modifier key. * Some of the default keyboard shortcuts related to cell selection need this feature for working properly. * * @type {boolean} */ let isCtrlKeySilenced = false; /** * A callback function for listening events from the recorder. * * @param {KeyboardEvent} event The keyboard event. * @param {string[]} keys Names of the shortcut's keys, * (coming from [`KeyboardEvent.key`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)), * in lowercase or uppercase, unified across browsers. * @param {object | string} context The context object or name. * @returns {boolean} */ const recorderCallback = function (event, keys) { let context = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : getActiveContextName(); const activeContext = isContextObject(context) ? context : getContext(context); let isExecutionCancelled = false; if (!activeContext.hasShortcut(keys)) { return isExecutionCancelled; } // Processing just actions being in stack at the moment of shortcut pressing (without respecting additions/removals performed dynamically). const shortcuts = activeContext.getShortcuts(keys); for (let index = 0; index < shortcuts.length; index++) { const { callback, runOnlyIf, preventDefault, stopPropagation, captureCtrl, forwardToContext } = shortcuts[index]; if (runOnlyIf(event) === true) { isCtrlKeySilenced = captureCtrl; isExecutionCancelled = callback(event, keys) === false; isCtrlKeySilenced = false; if (preventDefault) { event.preventDefault(); } if (stopPropagation) { stopImmediatePropagation(event); event.stopPropagation(); } if (isExecutionCancelled) { break; } if (forwardToContext) { recorderCallback(event, keys, forwardToContext); } } } return isExecutionCancelled; }; /** * Handle the event with the scope of the active context. * * @param {KeyboardEvent} event The keyboard event. * @returns {boolean} */ const handleEventWithScope = event => { const context = getActiveContextName(); const activeContext = isContextObject(context) ? context : getContext(context); return handleEvent(event, activeContext.scope); }; /** * Internal key recorder. * * @private */ const keyRecorder = useRecorder(ownerWindow, handleEventWithScope, beforeKeyDown, afterKeyDown, recorderCallback); keyRecorder.mount(); return { addContext, getActiveContextName, getContext, setActiveContextName, /** * Returns whether `control` or `meta` keys are pressed. * * @memberof ShortcutManager# * @type {Function} * @returns {boolean} */ isCtrlPressed: () => !isCtrlKeySilenced && (keyRecorder.isPressed('control') || keyRecorder.isPressed('meta')), /** * Release every previously pressed key. * * @type {Function} * @memberof ShortcutManager# */ releasePressedKeys: () => keyRecorder.releasePressedKeys(), /** * Destroy a context manager instance. * * @type {Function} * @memberof ShortcutManager# */ destroy: () => keyRecorder.unmount() }; };