import { customObject, registerKeyboard, register } from "./type"; import { forEach, validateCompositionKey, isExit, isMatchEvent } from "./utils"; import _fns from "./utils/isType"; import keyCodes from "./keyCodesMap"; import Listener from './listener'; class KeyboardListener { #events: customObject; _compositionKeysMap: customObject; _type: string; _globalRegistry: register; constructor(options: registerKeyboard) { const { type, register } = options; this._type = type || "keydown"; this._globalRegistry = register || {}; this.#events = {}; this._compositionKeysMap = {}; this._formatRegisterOptions(); this._register(this._globalRegistry); } get _events() { return this.#events; } get _addListeners() { const _addListeners: customObject = {}; forEach(this.#events, (key, value) => { forEach(value, (_key, _value) => { if (!_value) return; if (_value.isAdd) { if (!_addListeners[key]) _addListeners[key] = {}; _addListeners[key][_key] = _value; } }) }) return _addListeners; } private _formatRegisterOptions(): void { forEach(this._globalRegistry, (key, value) => { if (!_fns.isArray(value)) { this._globalRegistry[key] = [value]; } }); } private _register(options: register): void { forEach(options, (key: string, value: any) => { if (!this.#events[key]) { this.#events[key] = {}; } this._validateCompositionKey(key); value.forEach((name: string) => { this.#events[key][name] = null; this._compositionKeysMap[name] = key; }); }); } private _calcCompositionKeys(eventName: string): string { let compositionKeys: string[] = []; forEach(this.#events, (_compositionKeys, value) => { for (const funcName in value) { if (funcName === eventName) { compositionKeys.push(_compositionKeys); } } }); if (compositionKeys.length > 1) { throw Error(`Event name '${eventName}' is repeated`); } // if (compositionKeys.length === 0) { // throw Error(`The registration information named '${eventName}' could not be obtained`); // } return compositionKeys[0]; } private _isReisteredCompositionKey(compositionKeys: string, eventName: string): boolean { if (!compositionKeys) { console.warn(`KeyboardListener Warning: 'We did not find registration information for the event '${eventName}' in the registry'`); return false; } return true; } registerKeyboardListener( eventName: string, callback: () => any, element?: HTMLElement | string, isAutoFocusEl?: boolean, isRegister?: boolean ): KeyboardListener { let compositionKeys = this._compositionKeysMap[eventName] || this._calcCompositionKeys(eventName); if (!this._isReisteredCompositionKey(compositionKeys, eventName)) return this; this._validateCompositionKey(compositionKeys); if (!this.#events[compositionKeys]) { this.#events[compositionKeys] = {}; this.#events[compositionKeys][eventName] = null; } const target = this.#events[compositionKeys][eventName]; if ( target && target.compositionKeys === compositionKeys && target.originMethods === callback ) { return this; } let el = _fns.isString(element) ? document.querySelector(element as string) : element; const _isRegister = isRegister === undefined ? !isRegister : isRegister; this._setListener(compositionKeys, eventName, { isRegister: _isRegister, isAdd: !_isRegister, el, compositionKeys, originMethods: callback, isAutoFocusEl }); if (el) { this.removeKeyboardListener([eventName], compositionKeys); } else { this.removeKeyboardListener(eventName, compositionKeys); } this._setListener(compositionKeys, eventName, { isRegister: _isRegister, isAdd: !_isRegister, el, compositionKeys, isAutoFocusEl, originMethods: callback, registerMethods: this._rewriteMethods(compositionKeys, callback), }); this._addEventListener(this.#events[compositionKeys][eventName].registerMethods, el, isAutoFocusEl); el = null; return this; } private _addEventListener(callback: () => any, el?: HTMLElement | string | unknown, isAutoFocusEl?: boolean) { if (el) { // 默认聚焦 (isAutoFocusEl === undefined ? !isAutoFocusEl : isAutoFocusEl) && (el as HTMLElement).focus(); (el as HTMLElement).addEventListener(this._type, callback); } else { window.addEventListener(this._type, callback); } } private _setListener(compositionKeys: string, eventName: string, listener: customObject) { this.#events[compositionKeys][eventName] = null; this.#events[compositionKeys][eventName] = new Listener(listener); } private _validateCompositionKey(compositionKeys: string): void { if (!validateCompositionKey(compositionKeys)) { throw new Error( `Warning: '${compositionKeys}' is not confirm to the standard, \nif you're using a key combination, we need your initials capitalized and concatenated with a '+',\nwe need name like 'Alt+S', 'Ctrl+Alt+S', 'F5', '0', 'Alt', 'S'...` ); } } // TODO: 动态注册 addKeyboardListener(compositionKeys: string, eventName: string, callback: () => any, element?: HTMLElement, isAutoFocusEl?: boolean): KeyboardListener { if (!_fns.isString(eventName)) throw Error("The second parameter must be the event name!"); this._validateCompositionKey(compositionKeys); this._compositionKeysMap[eventName] = compositionKeys; this.registerKeyboardListener(eventName, callback, element, isAutoFocusEl, false); return this; } private _rewriteMethods(compositionKeys: string, callback: () => any): (e: KeyboardEvent) => void { let _keyCodes: number[] = []; compositionKeys.split("+").forEach((k) => { if (!isExit([k])) { _keyCodes.push(keyCodes[k]); } }); return (e: KeyboardEvent): any => { if (isMatchEvent(e, _keyCodes, compositionKeys)) { if (e.preventDefault) e.preventDefault(); else e.returnValue = false; if (e.stopPropagation) e.stopPropagation(); if (e.cancelBubble) e.cancelBubble = true; callback.call(this); } } } removeKeyboardListener(eventName: string | any[], _compositionKeys?: string): KeyboardListener { let _eventName = eventName; if (_fns.isString(eventName)) { _eventName = [eventName as string]; } (_eventName as string[]).forEach((realName) => { let compositionKeys = _compositionKeys ? _compositionKeys : (this._compositionKeysMap[realName] || this._calcCompositionKeys(realName)); if (!this._isReisteredCompositionKey(compositionKeys, realName)) return; let listener = this.#events[compositionKeys][realName]; if (!listener || !listener.originMethods || !listener.registerMethods) return; this._removeEventListener(compositionKeys, realName); listener = null; }); return this; } private _removeEventListener(compositionKeys: string, realName: string) { let listener = this.#events[compositionKeys][realName]; const element = listener.el; if (this.#events[compositionKeys] && realName in this.#events[compositionKeys]) { if (element) { (element as HTMLElement).removeEventListener(this._type, listener.registerMethods, false); } else { window.removeEventListener(this._type, listener.registerMethods, false); } this.#events[compositionKeys][realName] = null; if (listener.isAdd) { Reflect.deleteProperty(this.#events[compositionKeys], realName); JSON.stringify(this.#events[compositionKeys]) === '{}' && Reflect.deleteProperty(this.#events, compositionKeys); Reflect.deleteProperty(this._compositionKeysMap, realName); } listener = null; } else { throw Error(`The registration information named '${realName}' in '${compositionKeys}' could not be obtained`); } } resetKeyboardListener(to: string, from: string, eventName: string,): KeyboardListener { const compositionKeys = this._compositionKeysMap[eventName]; if (compositionKeys !== from) throw Error(`Method '${eventName}' on key combination '${from}' was not found`); const origin = this.#events[compositionKeys][eventName], originMethods = origin.originMethods, el = origin.el, isAutoFocusEl = origin.isAutoFocusEl, isRegister = origin.isRegister; this.removeKeyboardListener(eventName, compositionKeys); if (!isRegister) throw Error(`The reset method only works for 'register' or 'registerKeyboardListener' methods`); if (!this._globalRegistry[to]) { this._globalRegistry[to] = []; } (this._globalRegistry[to] as string[]).push(eventName); const _originRegitry = (this._globalRegistry[from] as string[]).filter(k => k !== eventName); if (_originRegitry.length > 0) { this._globalRegistry[from] = _originRegitry; } else { Reflect.deleteProperty(this._globalRegistry, from); } this._compositionKeysMap[eventName] = to; Reflect.deleteProperty(this.#events[compositionKeys], eventName); JSON.stringify(this.#events[compositionKeys]) === '{}' && Reflect.deleteProperty(this.#events, compositionKeys); this.registerKeyboardListener(eventName, originMethods, el, isAutoFocusEl, isRegister); return this; } } export default KeyboardListener;