UNPKG

@eclipse-scout/core

Version:
190 lines (165 loc) 6.55 kB
/* * Copyright (c) 2010, 2023 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import {Action, arrays, EventHandler, KeyboardEventWithMetaData, KeyStroke, KeyStrokeContextOptions, keyStrokeModifier, scout, ScoutKeyboardEvent} from '../index'; import $ from 'jquery'; /** * Contains a list of {@link KeyStroke}s that can be activated by pressing the corresponding keys if the focus is inside the {@link $bindTarget}. */ export class KeyStrokeContext implements KeyStrokeContextOptions { invokeAcceptInputOnActiveValueField: boolean; keyStrokes: KeyStroke[]; stopPropagationInterceptors: ((event: ScoutKeyboardEvent) => void)[]; $bindTarget: JQuery | (() => JQuery); $scopeTarget: JQuery | (() => JQuery); /** @internal */ _handler: ((event: KeyboardEventWithMetaData) => boolean) & { $target?: JQuery }; /** * Arrays with combinations of keys to prevent from bubbling up in the DOM tree. */ protected _stopPropagationKeys: Record<number, number[]>; constructor(options?: KeyStrokeContextOptions) { this.$bindTarget = null; this.$scopeTarget = null; this.keyStrokes = []; this.stopPropagationInterceptors = []; this._stopPropagationKeys = {}; this.invokeAcceptInputOnActiveValueField = false; options = options || {} as KeyStrokeContextOptions; $.extend(this, options); } /** * Registers the given keys as 'stopPropagation' keys, meaning that any keystroke event with that key and matching the modifier bit mask is prevented from bubbling the DOM tree up. * * @param modifierBitMask bitwise OR modifier constants to match a keystroke event. (KeyStrokeModifier.js) * @param keys the keys to match a keystroke event. */ registerStopPropagationKeys(modifierBitMask: number, keys: number[]) { this._stopPropagationKeys[modifierBitMask] = this._stopPropagationKeys[modifierBitMask] || []; arrays.pushAll(this._stopPropagationKeys[modifierBitMask], keys); } /** * Unregisters the given keys as 'stopPropagation' keys. * * @param modifierBitMask bitwise OR modifier constants to match a keystroke event. (KeyStrokeModifier.js) * @param keys the keys to match a keystroke event. */ unregisterStopPropagationKeys(modifierBitMask: number, keys: number[]) { if (!this._stopPropagationKeys[modifierBitMask]) { return; } arrays.removeAll(this._stopPropagationKeys[modifierBitMask], keys); } toggleStopPropagationKeys(modifierBitMask: number, keys: number[], condition: boolean) { if (condition) { this.registerStopPropagationKeys(modifierBitMask, keys); } else { this.unregisterStopPropagationKeys(modifierBitMask, keys); } } /** * Use this method to register an interceptor to set propagation flags on context level. */ registerStopPropagationInterceptor(interceptor: (event: ScoutKeyboardEvent) => void) { this.stopPropagationInterceptors.push(interceptor); } /** * Returns true if this event is handled by this context, and if so sets the propagation flags accordingly. */ accept(event: ScoutKeyboardEvent): boolean { // Check whether this event is accepted. if (!this._accept(event)) { return false; } // Apply propagation flags to the event. this._applyPropagationFlags(event); return true; } /** * Sets the propagation flags to the given event. */ protected _applyPropagationFlags(event: ScoutKeyboardEvent) { let modifierBitMask = keyStrokeModifier.toModifierBitMask(event); let keys = this._stopPropagationKeys[modifierBitMask]; if (keys && scout.isOneOf(event.which, keys)) { event.stopPropagation(); } // Let registered interceptors participate. this.stopPropagationInterceptors.forEach(interceptor => interceptor(event)); } protected _accept(event: ScoutKeyboardEvent): boolean { return true; } registerKeyStroke(keyStroke: KeyStroke | Action) { this.registerKeyStrokes(keyStroke); } /** * Registers the given keystroke(s) if not installed yet. */ registerKeyStrokes(keyStrokes: KeyStroke | KeyStroke[] | Action | Action[]) { arrays.ensure(keyStrokes) .map(this._resolveKeyStroke, this) .filter(ks => this.keyStrokes.indexOf(ks) === -1) // must not be registered yet .forEach(keystroke => { this.keyStrokes.push(keystroke); let ks: KeyStroke & { destroyListener?: EventHandler }; ks = keystroke; // Registers a destroy listener, so that the keystroke is uninstalled once its field is destroyed. if (ks.field && !ks.destroyListener) { ks.destroyListener = event => { this.unregisterKeyStroke(ks); ks.destroyListener = null; }; ks.field.one('destroy', ks.destroyListener); } }); } /** * Uninstalls the given keystroke. Has no effect if not installed. */ unregisterKeyStroke(keyStroke: KeyStroke | Action) { this.unregisterKeyStrokes(keyStroke); } unregisterKeyStrokes(keyStrokes: KeyStroke | KeyStroke[] | Action | Action[]) { arrays.ensure(keyStrokes) .map(this._resolveKeyStroke, this) .forEach(keystroke => { let ks: KeyStroke & { destroyListener?: EventHandler }; ks = keystroke; if (arrays.remove(this.keyStrokes, ks) && ks.field && ks.destroyListener) { ks.field.off('destroy', ks.destroyListener); ks.destroyListener = null; } }); } protected _resolveKeyStroke(keyStroke: KeyStroke | Action): KeyStroke { if (keyStroke instanceof KeyStroke) { return keyStroke; } if (keyStroke instanceof Action) { return keyStroke.actionKeyStroke; } throw new Error('unsupported keystroke: ' + keyStroke); } /** * Returns the $target where to bind this context as keydown listener. */ $getBindTarget(): JQuery { return (typeof this.$bindTarget === 'function' ? this.$bindTarget() : this.$bindTarget); } /** * Returns the scope of this context and is used to determine the context's accessibility, meaning not covert by a glasspane. */ $getScopeTarget(): JQuery { return (typeof this.$scopeTarget === 'function' ? this.$scopeTarget() : this.$scopeTarget); } clone(): KeyStrokeContext { return new KeyStrokeContext($.extend({}, this)); } }