Created
January 25, 2017 04:44
-
-
Save zry656565/464c113dbcad367f281889d1ea51afe9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* shortcut plugin | |
* Created date: 2017.01.16 | |
* TODO (feature): | |
* 1. support different key binding in Windows / Mac | |
*/ | |
const keyPairs = [ | |
[8, 'Backspace'], | |
[9, 'Tab'], | |
[13, 'Enter'], | |
[16, 'Shift'], | |
[17, 'Control'], | |
[18, 'Alt'], | |
[37, 'ArrowLeft'], | |
[38, 'ArrowUp'], | |
[39, 'ArrowRight'], | |
[40, 'ArrowDown'], | |
/** | |
* `Meta` means `Cmd` in Mac and `Win` in Windows, | |
* and in IE 9+ / Edge, the key for 91 is `Win` | |
*/ | |
[91, 'Meta'], | |
[191, '/'], | |
[222, '\''], | |
]; | |
/** | |
* Use following codes to try more keys in browsers: | |
* | |
* ``` | |
* window.addEventListener('keydown', function(e) { console.log(e.key, e.which) }); | |
* ``` | |
* More key values: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | |
*/ | |
const keyMap = new Map(); | |
for (let [code, key] of keyPairs) keyMap.set(code, key); | |
for (let i = 48; i < 58; i++) keyMap.set(i, String.fromCharCode(i)); | |
for (let i = 65; i < 91; i++) keyMap.set(i, String.fromCharCode(i)); | |
/** | |
* key | code | |
* -------|--------- | |
* A / a | 65 | |
* B / b | 66 | |
* ... | | |
* ' / " | 222 | |
*/ | |
const shiftPairs = [ | |
['"', '\''], | |
['?', '/'], | |
['0', ')'], | |
['1', '!'], | |
['2', '@'], | |
['3', '#'], | |
['4', '$'], | |
['5', '%'], | |
['6', '^'], | |
['7', '&'], | |
['8', '*'], | |
['9', '('] | |
]; | |
const shiftMap = new Map(); | |
for (let [key, shiftKey] of shiftPairs) shiftMap.set(key, shiftKey); | |
for (let i = 65; i < 91; i++) shiftMap.set(String.fromCharCode(i + 32), String.fromCharCode(i)); | |
const initialStatus = { | |
Shift: false, | |
Control: false, | |
Meta: false, | |
Alt: false | |
}; | |
const composeKeys = ['Shift', 'Control', 'Meta', 'Alt']; | |
const legalKeys = [...keyMap.values()].concat([...shiftMap.keys()]); | |
export default class Shortcut { | |
constructor() { | |
this.listenerMap = new Map(); | |
} | |
/** | |
* @param selector : string | |
* @param bindKey : string, e.g. 'Control_p' / 'Meta_Alt_Enter' | |
* @param callback : function | |
*/ | |
addListener(selector, bindKey, callback) { | |
const element = Shortcut._querySelector(selector); | |
// parse keys from `bindKey` | |
const keys = bindKey.split('_'); | |
let lastKey = keys[keys.length - 1]; | |
// validate keys | |
if (keys.length < 2) throw new Error('Number of keys must be greater than 1.'); | |
if (composeKeys.indexOf(lastKey) >= 0) throw new Error('Do not put a compose key at last'); | |
if (shiftMap.has(lastKey)) lastKey = shiftMap.get(lastKey); | |
for (let i = 0; i < keys.length; i++) { | |
if (i !== keys.length - 1 && composeKeys.indexOf(keys[i]) < 0) { | |
throw new Error('Only one non-compose key allowed'); | |
} | |
if (legalKeys.indexOf(keys[i]) < 0) throw new Error(`'${keys[i]}' is an illegal key`); | |
} | |
const requires = composeKeys.reduce((obj, key) => { | |
obj[key] = keys.indexOf(key) >= 0; | |
return obj; | |
}, {}); | |
let held = initialStatus; | |
function checkComposeKey() { | |
for (let key of Object.keys(requires)) { | |
if (requires[key] !== held[key]) return false; | |
} | |
return true; | |
} | |
const keydownCallback = (e) => { | |
let key = e.key || keyMap.get(e.which); | |
switch (key) { | |
case 'Shift': | |
if (requires.Shift) held.Shift = true; | |
break; | |
case 'Control': | |
if (requires.Control) held.Control = true; | |
break; | |
case 'Meta': | |
if (requires.Meta) held.Meta = true; | |
break; | |
case 'Alt': | |
if (requires.Alt) held.Alt = true; | |
break; | |
default: | |
if (shiftMap.has(key)) key = shiftMap.get(key); | |
if (lastKey === key) { | |
if (checkComposeKey()) { | |
held = initialStatus; | |
callback(); | |
} | |
} | |
break; | |
} | |
}; | |
element.addEventListener('keydown', keydownCallback); | |
const keyupCallback = (e) => { | |
const key = e.key || keyMap.get(e.which); | |
switch(key) { | |
case 'Shift': | |
held.Shift = false; | |
break; | |
case 'Control': | |
held.Control = false; | |
break; | |
case 'Meta': | |
held.Meta = false; | |
break; | |
case 'Alt': | |
held.Alt = false; | |
break; | |
default: | |
break; | |
} | |
}; | |
element.addEventListener('keyup', keyupCallback); | |
let eventList = this.listenerMap.get(element); | |
if (!eventList) eventList = []; | |
eventList.push(['keydown', keydownCallback], ['keyup', keyupCallback]); | |
this.listenerMap.set(element, eventList); | |
} | |
removeListener(selector) { | |
const element = Shortcut._querySelector(selector); | |
const eventList = this.listenerMap.get(element); | |
for (let [eventName, eventCallback] of eventList) { | |
element.removeEventListener(eventName, eventCallback); | |
} | |
} | |
static _querySelector(selector) { | |
let element; | |
if (selector === window || selector === document) { | |
element = selector; | |
} else { | |
element = document.querySelector(selector); | |
} | |
return element; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment