Merge pull request #1165 from Apathetic012/master

Add keymage v1.0.0
This commit is contained in:
Lachlan Collins
2013-04-13 18:26:26 -07:00
3 changed files with 343 additions and 0 deletions
+313
View File
@@ -0,0 +1,313 @@
// keymage.js - Javascript keyboard event handling
// http://github.com/piranha/keymage
//
// (c) 2012 Alexander Solovyov
// under terms of ISC License
(function(define, undefined) {
define(function() {
// Defining all keys
var MODPROPS = ['shiftKey', 'ctrlKey', 'altKey', 'metaKey'];
var MODS = {
'shift': 'shift',
'ctrl': 'ctrl', 'control': 'ctrl',
'alt': 'alt', 'option': 'alt',
'win': 'meta', 'cmd': 'meta', 'super': 'meta',
'meta': 'meta',
// default modifier for os x is cmd and for others is ctrl
'defmod': ~navigator.userAgent.indexOf('Mac OS X') ?
'meta' : 'ctrl'
};
var MODORDER = ['shift', 'ctrl', 'alt', 'meta'];
var MODNUMS = [16, 17, 18, 91];
var KEYS = {
'backspace': 8,
'tab': 9,
'enter': 13, 'return': 13,
'pause': 19,
'caps': 20, 'capslock': 20,
'escape': 27, 'esc': 27,
'space': 32,
'pgup': 33, 'pageup': 33,
'pgdown': 34, 'pagedown': 34,
'end': 35,
'home': 36,
'ins': 45, 'insert': 45,
'del': 46, 'delete': 46,
'left': 37,
'up': 38,
'right': 39,
'down': 40,
'*': 106,
'+': 107, 'plus': 107,
'-': 109, 'minus': 109,
';': 186,
'=': 187,
',': 188,
'.': 190,
'/': 191,
'`': 192,
'[': 219,
'\\': 220,
']': 221,
"'": 222
};
var i;
// numpad
for (i = 0; i < 10; i++) {
KEYS['num-' + i] = i + 95;
}
// top row 0-9
for (i = 0; i < 10; i++) {
KEYS[i.toString()] = i + 48;
}
// f1-f24
for (i = 1; i < 25; i++) {
KEYS['f' + i] = i + 111;
}
// alphabet
for (i = 65; i < 91; i++) {
KEYS[String.fromCharCode(i).toLowerCase()] = i;
}
// Reverse key codes
var KEYREV = {};
for (var k in KEYS) {
var val = KEYS[k];
if (!KEYREV[val] || KEYREV[val].length < k.length) {
KEYREV[val] = k;
}
}
// -----------------------
// Actual work is done here
var currentScope = '';
var allChains = {};
function parseKeyString(keystring) {
var bits = keystring.split('-');
var button = bits[bits.length - 1];
var key = {code: KEYS[button]};
if (!key.code) {
throw 'Unknown key "' + button + '" in keystring "' +
keystring + '"';
}
var mod;
for (var i = 0; i < bits.length - 1; i++) {
button = bits[i];
mod = MODS[button];
if (!mod) {
throw 'Unknown modifier "' + button + '" in keystring "' +
keystring + '"';
}
key[mod] = true;
}
return key;
}
function stringifyKey(key) {
var s = '';
for (var i = 0; i < MODORDER.length; i++) {
if (key[MODORDER[i]]) {
s += MODORDER[i] + '-';
}
}
s += KEYREV[key.code];
return s;
}
function normalizeKeyChain(keychainString) {
var keychain = [];
var keys = keychainString.split(' ');
for (var i = 0; i < keys.length; i++) {
var key = parseKeyString(keys[i]);
key = stringifyKey(key);
keychain.push(key);
}
keychain.original = keychainString;
return keychain;
}
function eventKeyString(e) {
var key = {code: e.keyCode};
for (var i = 0; i < MODPROPS.length; i++) {
var mod = MODPROPS[i];
if (e[mod]) {
key[mod.slice(0, mod.length - 3)] = true;
}
}
return stringifyKey(key);
}
function getNestedChains(chains, scope) {
for (var i = 0; i < scope.length; i++) {
var bit = scope[i];
if (bit) {
chains = chains[bit];
}
if (!chains) {
break;
}
}
return chains;
}
var sequence = [];
function dispatch(e) {
// Skip all modifiers
if (~MODNUMS.indexOf(e.keyCode)) {
return;
}
var seq = sequence.slice();
seq.push(eventKeyString(e));
var scope = currentScope.split('.');
var matched, chains, key;
for (var i = scope.length; i >= 0; i--) {
chains = getNestedChains(allChains, scope.slice(0, i));
if (!chains) {
continue;
}
matched = true;
for (var j = 0; j < seq.length; j++) {
key = seq[j];
if (!chains[key]) {
matched = false;
break;
}
chains = chains[key];
}
if (matched) {
break;
}
}
var definitionScope = scope.slice(0, i).join('.');
var preventDefault = chains.preventDefault;
// partial match, save the sequence
if (matched && !chains.handlers) {
sequence = seq;
if (preventDefault) {
e.preventDefault();
}
return;
}
if (matched) {
for (i = 0; i < chains.handlers.length; i++) {
var handler = chains.handlers[i];
var options = handler._keymage;
var res = handler.call(options.context, e, {
shortcut: options.original,
scope: currentScope,
definitionScope: definitionScope
});
if (res === false || preventDefault) {
e.preventDefault();
}
}
}
// either matched or not, drop the sequence
sequence = [];
}
function assignKey(scope, keychain, fn) {
var bits = scope.split('.');
var chains = allChains;
bits = bits.concat(keychain);
for (var i = 0, l = bits.length; i < l; i++) {
var bit = bits[i];
if (!bit) continue;
chains = chains[bit] || (chains[bit] = {});
if (fn._keymage.preventDefault) {
chains.preventDefault = true;
}
if (i === l - 1) {
var handlers = chains.handlers || (chains.handlers = []);
handlers.push(fn);
}
}
}
// optional arguments: scope, options.
function keymage(scope, keychain, fn, options) {
if (keychain === undefined && fn === undefined) {
return function(keychain, fn) {
return keymage(scope, keychain, fn);
};
}
if (typeof keychain === 'function') {
options = fn;
fn = keychain;
keychain = scope;
scope = '';
}
var normalized = normalizeKeyChain(keychain);
fn._keymage = options || {};
fn._keymage.original = keychain;
assignKey(scope, normalized, fn);
}
keymage.parse = parseKeyString;
keymage.stringify = stringifyKey;
keymage.bindings = allChains;
keymage.setScope = function(scope) {
currentScope = scope ? scope : '';
};
keymage.getScope = function() { return currentScope; };
keymage.pushScope = function(scope) {
currentScope = (currentScope ? currentScope + '.' : '') + scope;
return currentScope;
};
keymage.popScope = function(scope) {
var i;
if (!scope) {
i = currentScope.lastIndexOf('.');
scope = currentScope.slice(i + 1);
currentScope = currentScope.slice(0, i);
return scope;
}
currentScope = currentScope.replace(
new RegExp('(^|\\.)' + scope + '(\\.|$).*'), '');
return scope;
};
window.addEventListener('keydown', dispatch, false);
return keymage;
});
})(typeof define !== 'undefined' ? define : function(factory) {
window.keymage = factory();
});
+6
View File
@@ -0,0 +1,6 @@
// keymage.js - Javascript keyboard event handling
// http://github.com/piranha/keymage
//
// (c) 2012 Alexander Solovyov
// under terms of ISC License
(function(a,b){a(function(){function m(a){var b=a.split("-"),d=b[b.length-1],e={code:f[d]};if(!e.code)throw'Unknown key "'+d+'" in keystring "'+a+'"';var g;for(var h=0;h<b.length-1;h++){d=b[h],g=c[d];if(!g)throw'Unknown modifier "'+d+'" in keystring "'+a+'"';e[g]=!0}return e}function n(a){var b="";for(var c=0;c<d.length;c++)a[d[c]]&&(b+=d[c]+"-");return b+=h[a.code],b}function o(a){var b=[],c=a.split(" ");for(var d=0;d<c.length;d++){var e=m(c[d]);e=n(e),b.push(e)}return b.original=a,b}function p(b){var c={code:b.keyCode};for(var d=0;d<a.length;d++){var e=a[d];b[e]&&(c[e.slice(0,e.length-3)]=!0)}return n(c)}function q(a,b){for(var c=0;c<b.length;c++){var d=b[c];d&&(a=a[d]);if(!a)break}return a}function s(a){if(~e.indexOf(a.keyCode))return;var b=r.slice();b.push(p(a));var c=k.split("."),d,f,g;for(var h=c.length;h>=0;h--){f=q(l,c.slice(0,h));if(!f)continue;d=!0;for(var i=0;i<b.length;i++){g=b[i];if(!f[g]){d=!1;break}f=f[g]}if(d)break}var j=c.slice(0,h).join("."),m=f.preventDefault;if(d&&!f.handlers){r=b,m&&a.preventDefault();return}if(d)for(h=0;h<f.handlers.length;h++){var n=f.handlers[h],o=n._keymage,s=n.call(o.context,a,{shortcut:o.original,scope:k,definitionScope:j});(s===!1||m)&&a.preventDefault()}r=[]}function t(a,b,c){var d=a.split("."),e=l;d=d.concat(b);for(var f=0,g=d.length;f<g;f++){var h=d[f];if(!h)continue;e=e[h]||(e[h]={}),c._keymage.preventDefault&&(e.preventDefault=!0);if(f===g-1){var i=e.handlers||(e.handlers=[]);i.push(c)}}}function u(a,c,d,e){if(c===b&&d===b)return function(b,c){return u(a,b,c)};typeof c=="function"&&(e=d,d=c,c=a,a="");var f=o(c);d._keymage=e||{},d._keymage.original=c,t(a,f,d)}var a=["shiftKey","ctrlKey","altKey","metaKey"],c={shift:"shift",ctrl:"ctrl",control:"ctrl",alt:"alt",option:"alt",win:"meta",cmd:"meta","super":"meta",meta:"meta",defmod:~navigator.userAgent.indexOf("Mac OS X")?"meta":"ctrl"},d=["shift","ctrl","alt","meta"],e=[16,17,18,91],f={backspace:8,tab:9,enter:13,"return":13,pause:19,caps:20,capslock:20,escape:27,esc:27,space:32,pgup:33,pageup:33,pgdown:34,pagedown:34,end:35,home:36,ins:45,insert:45,del:46,"delete":46,left:37,up:38,right:39,down:40,"*":106,"+":107,plus:107,"-":109,minus:109,";":186,"=":187,",":188,".":190,"/":191,"`":192,"[":219,"\\":220,"]":221,"'":222},g;for(g=0;g<10;g++)f["num-"+g]=g+95;for(g=0;g<10;g++)f[g.toString()]=g+48;for(g=1;g<25;g++)f["f"+g]=g+111;for(g=65;g<91;g++)f[String.fromCharCode(g).toLowerCase()]=g;var h={};for(var i in f){var j=f[i];if(!h[j]||h[j].length<i.length)h[j]=i}var k="",l={},r=[];return u.parse=m,u.stringify=n,u.bindings=l,u.setScope=function(a){k=a?a:""},u.getScope=function(){return k},u.pushScope=function(a){return k=(k?k+".":"")+a,k},u.popScope=function(a){var b;return a?(k=k.replace(new RegExp("(^|\\.)"+a+"(\\.|$).*"),""),a):(b=k.lastIndexOf("."),a=k.slice(b+1),k=k.slice(0,b),a)},window.addEventListener("keydown",s,!1),u})})(typeof define!="undefined"?define:function(a){window.keymage=a()});
+24
View File
@@ -0,0 +1,24 @@
{
"name": "keymage",
"filename": "keymage.min.js",
"version": "1.0.0",
"description": "Yet Another JS Keybinding library",
"homepage": "https://github.com/piranha/keymage",
"keywords": [
"keymage",
"key-binding"
],
"maintainers": [
{
"name": "Alexander Solovyov",
"email": "alexander@solovyov.net",
"web": "http://solovyov.net/"
}
],
"repositories": [
{
"type": "git",
"url": "https://github.com/piranha/keymage"
}
]
}