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.
187 lines • 6.66 kB
JavaScript
import { createAbortHandle } from '../abort-handler.js';
import { asArray, getElementFromPath, isFunction, partition, toMerged, } 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 = 'Control';
export const metaKey = 'Meta';
export const shiftKey = 'Shift';
const MODIFIERS = new Set(['alt', 'control', 'meta', 'shift']);
const ALL_MODIFIER_VALUES = Array.from(MODIFIERS).sort();
export const MODIFIER_EVENT_KEYS = {
alt: 'altKey',
control: 'ctrlKey',
meta: 'metaKey',
shift: 'shiftKey',
};
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 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 {
static { this._defaultOptions = {
skip: ['input', 'textarea', 'select'],
bindingDefaults: { preventDefault: true },
}; }
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 = toMerged(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) && triggers.includes('keydown')) {
return !event.repeat || Boolean(binding.options?.repeat);
}
if (isKeyup(event) && triggers.includes('keyup')) {
return true;
}
return false;
}
_shouldSkip(event) {
const skip = this._options.skip;
if (!this._allowedKeys.has(event.key.toLowerCase())) {
return true;
}
if (!getElementFromPath((e) => e === this._element, event)) {
return true;
}
if (Array.isArray(skip)) {
if (!this._skipSelector) {
return false;
}
return Boolean(getElementFromPath(this._skipSelector, event));
}
return isFunction(skip)
? skip.call(this._host, event.target, event)
: false;
}
hostConnected() {
const { signal } = this._abortHandle;
this._host.addEventListener('keyup', this, { signal });
this._host.addEventListener('keydown', this, { signal });
globalThis.addEventListener('blur', this, { signal });
}
hostDisconnected() {
this._abortHandle.abort();
}
_handleGlobalBlur() {
this._pressedKeys.clear();
}
_handleKeyEvent(event) {
if (this._shouldSkip(event)) {
const key = event.key.toLowerCase();
if (!MODIFIERS.has(key) && isKeyup(event)) {
this._pressedKeys.delete(key);
}
return;
}
const key = event.key.toLowerCase();
if (!MODIFIERS.has(key)) {
this._pressedKeys.add(key);
}
const activeModifiers = ALL_MODIFIER_VALUES.filter((mod) => event[MODIFIER_EVENT_KEYS[mod]]);
const combination = createCombinationKey(Array.from(this._pressedKeys), activeModifiers);
let binding = this._bindings.get(combination);
if (!binding && this._pressedKeys.size > 1) {
binding = this._bindings.get(createCombinationKey([key], activeModifiers));
}
if (binding && this._bindingMatches(binding, event)) {
this._applyEventModifiers(binding, event);
binding.handler.call(this._host, event);
}
if (!MODIFIERS.has(key) && isKeyup(event)) {
this._pressedKeys.delete(key);
}
}
handleEvent(event) {
switch (event.type) {
case 'keydown':
case 'keyup':
this._handleKeyEvent(event);
break;
case 'blur':
this._handleGlobalBlur();
break;
}
}
set(key, handler, bindingOptions) {
const { keys, modifiers } = parseKeys(key);
const combination = createCombinationKey(keys, modifiers);
const options = toMerged(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: () => {
element.removeEventListener('keydown', this);
element.removeEventListener('keyup', this);
if (this._observedElement === element) {
this._observedElement = undefined;
}
},
};
}
}
export function parseKeys(inputKeys) {
const [modifiers, keys] = partition(normalizeKeys(inputKeys), (key) => MODIFIERS.has(key));
return { keys, modifiers };
}
export function addKeybindings(element, options) {
return new KeyBindingController(element, options);
}
//# sourceMappingURL=key-bindings.js.map