igniteui-webcomponents
Version:
Ignite UI for Web Components is a complete library of UI components, giving you the ability to build modern web applications using encapsulation and the concept of reusable components in a dependency-free approach.
179 lines • 6.38 kB
JavaScript
import { createAbortHandle } from '../abort-handler.js';
import { asArray, findElementFromEventPath, isFunction } from '../util.js';
export const arrowLeft = 'ArrowLeft';
export const arrowRight = 'ArrowRight';
export const arrowUp = 'ArrowUp';
export const arrowDown = 'ArrowDown';
export const enterKey = 'Enter';
export const spaceBar = ' ';
export const escapeKey = 'Escape';
export const homeKey = 'Home';
export const endKey = 'End';
export const pageUpKey = 'PageUp';
export const pageDownKey = 'PageDown';
export const tabKey = 'Tab';
export const altKey = 'Alt';
export const ctrlKey = 'Ctrl';
export const metaKey = 'Meta';
export const shiftKey = 'Shift';
const Modifiers = new Map([
[altKey.toLowerCase(), altKey.toLowerCase()],
[ctrlKey.toLowerCase(), ctrlKey.toLowerCase()],
[metaKey.toLowerCase(), metaKey.toLowerCase()],
[shiftKey.toLowerCase(), shiftKey.toLowerCase()],
]);
const ALL_MODIFIER_VALUES = Array.from(Modifiers.values()).sort();
function normalizeKeys(keys) {
return asArray(keys).map((key) => key.toLowerCase());
}
function isKeydown(event) {
return event.type === 'keydown';
}
function isKeyup(event) {
return event.type === 'keyup';
}
function isKeydownTrigger(triggers) {
return triggers
? triggers.includes('keydown') || isKeydownRepeatTrigger(triggers)
: false;
}
function isKeyupTrigger(triggers) {
return triggers ? triggers.includes('keyup') : false;
}
function isKeydownRepeatTrigger(triggers) {
return triggers ? triggers.includes('keydownRepeat') : false;
}
function createCombinationKey(keys, modifiers) {
const sortedKeys = keys.toSorted();
const sortedModifiers = ALL_MODIFIER_VALUES.filter((mod) => modifiers.includes(mod)).sort();
return sortedModifiers.concat(sortedKeys).join('+');
}
class KeyBindingController {
get _element() {
if (this._observedElement) {
return this._observedElement;
}
return this._ref?.value || this._host;
}
constructor(host, options) {
this._abortHandle = createAbortHandle();
this._bindings = new Map();
this._allowedKeys = new Set();
this._pressedKeys = new Set();
this._host = host;
this._ref = options?.ref;
this._options = { ...KeyBindingController._defaultOptions, ...options };
if (Array.isArray(this._options.skip)) {
this._skipSelector = this._options.skip.join(',');
}
host.addController(this);
}
_applyEventModifiers(binding, event) {
if (binding.options?.preventDefault) {
event.preventDefault();
}
if (binding.options?.stopPropagation) {
event.stopPropagation();
}
}
_bindingMatches(binding, event) {
const triggers = binding.options?.triggers ?? ['keydown'];
if (isKeydown(event) && isKeydownTrigger(triggers)) {
return true;
}
if (isKeyup(event) && isKeyupTrigger(triggers)) {
return true;
}
return false;
}
_shouldSkip(event) {
const skip = this._options?.skip;
if (!this._allowedKeys.has(event.key.toLowerCase())) {
return true;
}
if (!findElementFromEventPath((e) => e === this._element, event)) {
return true;
}
if (Array.isArray(skip)) {
if (!this._skipSelector) {
return false;
}
return Boolean(findElementFromEventPath(this._skipSelector, event));
}
if (isFunction(skip)) {
return skip.call(this._host, event.target, event);
}
return false;
}
hostConnected() {
const { signal } = this._abortHandle;
this._host.addEventListener('keyup', this, { signal });
this._host.addEventListener('keydown', this, { signal });
}
hostDisconnected() {
this._abortHandle.abort();
}
handleEvent(event) {
if (this._shouldSkip(event)) {
return;
}
const key = event.key.toLowerCase();
if (!Modifiers.has(key)) {
this._pressedKeys.add(key);
}
const activeModifiers = ALL_MODIFIER_VALUES.filter((mod) => event[`${mod}Key`]);
const combination = createCombinationKey(Array.from(this._pressedKeys), activeModifiers);
const binding = this._bindings.get(combination);
if (binding && this._bindingMatches(binding, event)) {
this._applyEventModifiers(binding, event);
binding.handler.call(this._host, event);
if (isKeydownRepeatTrigger(binding.options?.triggers)) {
this._pressedKeys.delete(key);
}
}
if (isKeyup(event) && !Modifiers.has(key)) {
this._pressedKeys.delete(key);
}
}
set(key, handler, bindingOptions) {
const { keys, modifiers } = parseKeys(key);
const combination = createCombinationKey(keys, modifiers);
const options = { ...this._options?.bindingDefaults, ...bindingOptions };
for (const each of [...keys, ...modifiers]) {
this._allowedKeys.add(each);
}
this._bindings.set(combination, { keys, handler, options, modifiers });
return this;
}
setActivateHandler(handler, options) {
this.set(enterKey, handler, options);
this.set(spaceBar, handler, options);
return this;
}
observeElement(element) {
element.addEventListener('keydown', this);
element.addEventListener('keyup', this);
this._observedElement = element;
return {
unsubscribe: () => {
this._observedElement?.removeEventListener('keydown', this);
this._observedElement?.removeEventListener('keyup', this);
this._observedElement = undefined;
},
};
}
}
KeyBindingController._defaultOptions = {
skip: ['input', 'textarea', 'select'],
};
export function parseKeys(keys) {
const normalizedKeys = normalizeKeys(keys);
return {
keys: normalizedKeys.filter((key) => !Modifiers.has(key)),
modifiers: normalizedKeys.filter((key) => Modifiers.has(key)),
};
}
export function addKeybindings(element, options) {
return new KeyBindingController(element, options);
}
//# sourceMappingURL=key-bindings.js.map