Skip to content

Instantly share code, notes, and snippets.

@zry656565
Created January 25, 2017 04:44
Show Gist options
  • Save zry656565/464c113dbcad367f281889d1ea51afe9 to your computer and use it in GitHub Desktop.
Save zry656565/464c113dbcad367f281889d1ea51afe9 to your computer and use it in GitHub Desktop.
/**
* 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