@eclipse-scout/core
Version:
Eclipse Scout runtime
190 lines (165 loc) • 6.55 kB
text/typescript
/*
* 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));
}
}