angular-hotkeys
Advanced tools
Comparing version 1.3.1 to 1.4.0
{ | ||
"name": "angular-hotkeys", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"main": [ | ||
@@ -5,0 +5,0 @@ "build/hotkeys.min.js", |
/*! | ||
* angular-hotkeys v1.3.1 | ||
* angular-hotkeys v1.4.0 | ||
* https://chieffancypants.github.io/angular-hotkeys | ||
@@ -108,3 +108,3 @@ * Copyright (c) 2014 Wes Cruver | ||
* | ||
* @param {String} combo The keycombo | ||
* @param {array} combo The keycombo. it's an array to support multiple combos | ||
* @param {String} description Description for the keycombo | ||
@@ -120,3 +120,4 @@ * @param {Function} callback function to execute when keycombo pressed | ||
// supplied values | ||
this.combo = combo; | ||
this.combo = combo instanceof Array ? combo : [combo]; | ||
this.description = description; | ||
@@ -139,12 +140,6 @@ this.callback = callback; | ||
var combo = this.combo; | ||
// Don't show all the possible key combos, just the first one. Not sure | ||
// of usecase here, so open a ticket if my assumptions are wrong | ||
var combo = this.combo[0]; | ||
// if the combo is an array, it means the there are multiple bindings to | ||
// the same callback. Don't show all the possible key combos, just the | ||
// first one. Not sure of usecase here, so open a ticket if my | ||
// assumptions are wrong | ||
if (combo instanceof Array) { | ||
combo = combo[0]; | ||
} | ||
var sequence = combo.split(/[\s]/); | ||
@@ -183,2 +178,12 @@ for (var i = 0; i < sequence.length; i++) { | ||
/** | ||
* Holds references to the different scopes that have bound hotkeys | ||
* attached. This is useful to catch when the scopes are `$destroy`d and | ||
* then automatically unbind the hotkey. | ||
* | ||
* @type {Array} | ||
*/ | ||
var boundScopes = []; | ||
$rootScope.$on('$routeChangeSuccess', function (event, route) { | ||
@@ -368,3 +373,5 @@ purgeHotkeys(); | ||
scope.hotkeys.push(new Hotkey(combo, description, callback, action, allowIn, persistent)); | ||
var hotkey = new Hotkey(combo, description, callback, action, allowIn, persistent); | ||
scope.hotkeys.push(hotkey); | ||
return hotkey; | ||
} | ||
@@ -383,7 +390,24 @@ | ||
for (var i = 0; i < scope.hotkeys.length; i++) { | ||
if (scope.hotkeys[i].combo === combo) { | ||
scope.hotkeys.splice(i, 1); | ||
if (combo instanceof Array) { | ||
var retStatus = true; | ||
for (var i = 0; i < combo.length; i++) { | ||
retStatus = _del(combo[i]) && retStatus; | ||
} | ||
return retStatus; | ||
} else { | ||
var index = scope.hotkeys.indexOf(_get(combo)); | ||
if (index > -1) { | ||
// if the combo has other combos bound, don't unbind the whole thing, just the one combo: | ||
if (scope.hotkeys[index].combo.length > 1) { | ||
scope.hotkeys[index].combo.splice(scope.hotkeys[index].combo.indexOf(combo), 1); | ||
} else { | ||
scope.hotkeys.splice(index, 1); | ||
} | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
@@ -398,7 +422,13 @@ | ||
function _get (combo) { | ||
var hotkey; | ||
for (var i = 0; i < scope.hotkeys.length; i++) { | ||
if (scope.hotkeys[i].combo === combo) { | ||
return scope.hotkeys[i]; | ||
hotkey = scope.hotkeys[i]; | ||
if (hotkey.combo.indexOf(combo) > -1) { | ||
return hotkey; | ||
} | ||
} | ||
return false; | ||
@@ -408,2 +438,38 @@ } | ||
/** | ||
* Binds the hotkey to a particular scope. Useful if the scope is | ||
* destroyed, we can automatically destroy the hotkey binding. | ||
* | ||
* @param {Object} scope The scope to bind to | ||
*/ | ||
function bindTo (scope) { | ||
// Add the scope to the list of bound scopes | ||
boundScopes[scope.$id] = []; | ||
scope.$on('$destroy', function () { | ||
var i = boundScopes[scope.$id].length; | ||
while (i--) { | ||
_del(boundScopes[scope.$id][i]); | ||
delete boundScopes[scope.$id][i]; | ||
} | ||
}); | ||
// return an object with an add function so we can keep track of the | ||
// hotkeys and their scope that we added via this chaining method | ||
return { | ||
add: function (args) { | ||
var hotkey; | ||
if (arguments.length > 1) { | ||
hotkey = _add.apply(this, arguments); | ||
} else { | ||
hotkey = _add(args); | ||
} | ||
boundScopes[scope.$id].push(hotkey); | ||
return this; | ||
} | ||
}; | ||
} | ||
/** | ||
* All callbacks sent to Mousetrap are wrapped using this function | ||
@@ -444,2 +510,3 @@ * so that we can force a $scope.$apply() | ||
get : _get, | ||
bindTo : bindTo, | ||
template : this.template, | ||
@@ -462,7 +529,17 @@ toggleCheatSheet : toggleCheatSheet, | ||
link: function (scope, el, attrs) { | ||
var key; | ||
var key, allowIn; | ||
angular.forEach(scope.$eval(attrs.hotkey), function (func, hotkey) { | ||
// split and trim the hotkeys string into array | ||
allowIn = attrs.hotkeyAllowIn.split(/[\s,]+/); | ||
key = hotkey; | ||
hotkeys.add(hotkey, attrs.hotkeyDescription, func, attrs.hotkeyAction); | ||
hotkeys.add({ | ||
combo: hotkey, | ||
description: attrs.hotkeyDescription, | ||
callback: func, | ||
action: attrs.hotkeyAction, | ||
allowIn: allowIn | ||
}); | ||
}); | ||
@@ -469,0 +546,0 @@ |
/*! | ||
* angular-hotkeys v1.3.1 | ||
* angular-hotkeys v1.4.0 | ||
* https://chieffancypants.github.io/angular-hotkeys | ||
@@ -7,2 +7,2 @@ * Copyright (c) 2014 Wes Cruver | ||
*/ | ||
!function(){"use strict";angular.module("cfp.hotkeys",[]).provider("hotkeys",function(){this.includeCheatSheet=!0,this.template='<div class="cfp-hotkeys-container fade" ng-class="{in: helpVisible}"><div class="cfp-hotkeys"><h4 class="cfp-hotkeys-title">{{ title }}</h4><table><tbody><tr ng-repeat="hotkey in hotkeys | filter:{ description: \'!$$undefined$$\' }"><td class="cfp-hotkeys-keys"><span ng-repeat="key in hotkey.format() track by $index" class="cfp-hotkeys-key">{{ key }}</span></td><td class="cfp-hotkeys-text">{{ hotkey.description }}</td></tr></tbody></table><div class="cfp-hotkeys-close" ng-click="helpVisible = false">×</div></div></div>',this.cheatSheetHotkey="?",this.cheatSheetDescription="Show / hide this help menu",this.$get=["$rootElement","$rootScope","$compile","$window","$document",function(a,b,c,d,e){function f(a){var b={command:"⌘",shift:"⇧",left:"←",right:"→",up:"↑",down:"↓","return":"↩",backspace:"⌫"};a=a.split("+");for(var c=0;c<a.length;c++)"mod"===a[c]&&(a[c]=d.navigator&&d.navigator.platform.indexOf("Mac")>=0?"command":"ctrl"),a[c]=b[a[c]]||a[c];return a.join(" + ")}function g(a,b,c,d,e,f){this.combo=a,this.description=b,this.callback=c,this.action=d,this.allowIn=e,this.persistent=f}function h(){for(var a=n.hotkeys.length;a--;){var b=n.hotkeys[a];b&&!b.persistent&&k(b)}}function i(){n.helpVisible=!n.helpVisible,n.helpVisible?(r=l("esc"),k("esc"),j("esc",r.description,i)):(k("esc"),r!==!1&&j(r))}function j(a,b,c,d,e,f){var h,i=["INPUT","SELECT","TEXTAREA"],j=Object.prototype.toString.call(a);if("[object Object]"===j&&(b=a.description,c=a.callback,d=a.action,f=a.persistent,e=a.allowIn,a=a.combo),b instanceof Function?(d=c,c=b,b="$$undefined$$"):angular.isUndefined(b)&&(b="$$undefined$$"),void 0===f&&(f=!0),"function"==typeof c){h=c,e instanceof Array||(e=[]);for(var k,l=0;l<e.length;l++)e[l]=e[l].toUpperCase(),k=i.indexOf(e[l]),-1!==k&&i.splice(k,1);c=function(a){var b=!0,c=a.target||a.srcElement,d=c.nodeName.toUpperCase();if((" "+c.className+" ").indexOf(" mousetrap ")>-1)b=!0;else for(var e=0;e<i.length;e++)if(i[e]===d){b=!1;break}b&&m(h.apply(this,arguments))}}"string"==typeof d?Mousetrap.bind(a,m(c),d):Mousetrap.bind(a,m(c)),n.hotkeys.push(new g(a,b,c,d,e,f))}function k(a){var b=a instanceof g?a.combo:a;Mousetrap.unbind(b);for(var c=0;c<n.hotkeys.length;c++)n.hotkeys[c].combo===b&&n.hotkeys.splice(c,1)}function l(a){for(var b=0;b<n.hotkeys.length;b++)if(n.hotkeys[b].combo===a)return n.hotkeys[b];return!1}function m(a){return function(c,d){if(a instanceof Array){var e=a[0],f=a[1];a=function(){f.scope.$eval(e)}}b.$apply(function(){a(c,l(d))})}}Mousetrap.stopCallback=function(a,b){return(" "+b.className+" ").indexOf(" mousetrap ")>-1?!1:b.contentEditable&&"true"==b.contentEditable},g.prototype.format=function(){var a=this.combo;a instanceof Array&&(a=a[0]);for(var b=a.split(/[\s]/),c=0;c<b.length;c++)b[c]=f(b[c]);return b};var n=b.$new();if(n.hotkeys=[],n.helpVisible=!1,n.title="Keyboard Shortcuts:",b.$on("$routeChangeSuccess",function(a,b){h(),b.hotkeys&&angular.forEach(b.hotkeys,function(a){var c=a[2];("string"==typeof c||c instanceof String)&&(a[2]=[c,b]),a[5]=!1,j.apply(this,a)})}),this.includeCheatSheet){var o=e[0],p=a[0],q=angular.element(this.template);j(this.cheatSheetHotkey,this.cheatSheetDescription,i),(p===o||p===o.documentElement)&&(p=o.body),angular.element(p).append(c(q)(n))}var r=!1,s={add:j,del:k,get:l,template:this.template,toggleCheatSheet:i,includeCheatSheat:this.includeCheatSheat,cheatSheetHotkey:this.cheatSheetHotkey,cheatSheetDescription:this.cheatSheetDescription,purgeHotkeys:h};return s}]}).directive("hotkey",["hotkeys",function(a){return{restrict:"A",link:function(b,c,d){var e;angular.forEach(b.$eval(d.hotkey),function(b,c){e=c,a.add(c,d.hotkeyDescription,b,d.hotkeyAction)}),c.bind("$destroy",function(){a.del(e)})}}}]).run(["hotkeys",function(){}])}(),function(a,b){function c(a,b,c){return a.addEventListener?void a.addEventListener(b,c,!1):void a.attachEvent("on"+b,c)}function d(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);return a.shiftKey||(b=b.toLowerCase()),b}return y[a.which]?y[a.which]:z[a.which]?z[a.which]:String.fromCharCode(a.which).toLowerCase()}function e(a,b){return a.sort().join(",")===b.sort().join(",")}function f(a){a=a||{};var b,c=!1;for(b in E)a[b]?c=!0:E[b]=0;c||(H=!1)}function g(a,b,c,d,f,g){var h,i,j=[],k=c.type;if(!C[a])return[];for("keyup"==k&&n(a)&&(b=[a]),h=0;h<C[a].length;++h)if(i=C[a][h],(d||!i.seq||E[i.seq]==i.level)&&k==i.action&&("keypress"==k&&!c.metaKey&&!c.ctrlKey||e(b,i.modifiers))){var l=!d&&i.combo==f,m=d&&i.seq==d&&i.level==g;(l||m)&&C[a].splice(h,1),j.push(i)}return j}function h(a){var b=[];return a.shiftKey&&b.push("shift"),a.altKey&&b.push("alt"),a.ctrlKey&&b.push("ctrl"),a.metaKey&&b.push("meta"),b}function i(a){return a.preventDefault?void a.preventDefault():void(a.returnValue=!1)}function j(a){return a.stopPropagation?void a.stopPropagation():void(a.cancelBubble=!0)}function k(a,b,c,d){J.stopCallback(b,b.target||b.srcElement,c,d)||a(b,c)===!1&&(i(b),j(b))}function l(a,b,c){var d,e=g(a,b,c),h={},i=0,j=!1;for(d=0;d<e.length;++d)e[d].seq&&(i=Math.max(i,e[d].level));for(d=0;d<e.length;++d)if(e[d].seq){if(e[d].level!=i)continue;j=!0,h[e[d].seq]=1,k(e[d].callback,c,e[d].combo,e[d].seq)}else j||k(e[d].callback,c,e[d].combo);var l="keypress"==c.type&&G;c.type!=H||n(a)||l||f(h),G=j&&"keydown"==c.type}function m(a){"number"!=typeof a.which&&(a.which=a.keyCode);var b=d(a);if(b)return"keyup"==a.type&&F===b?void(F=!1):void J.handleKey(b,h(a),a)}function n(a){return"shift"==a||"ctrl"==a||"alt"==a||"meta"==a}function o(){clearTimeout(x),x=setTimeout(f,1e3)}function p(){if(!w){w={};for(var a in y)a>95&&112>a||y.hasOwnProperty(a)&&(w[y[a]]=a)}return w}function q(a,b,c){return c||(c=p()[a]?"keydown":"keypress"),"keypress"==c&&b.length&&(c="keydown"),c}function r(a,b,c,e){function g(b){return function(){H=b,++E[a],o()}}function h(b){k(c,b,a),"keyup"!==e&&(F=d(b)),setTimeout(f,10)}E[a]=0;for(var i=0;i<b.length;++i){var j=i+1===b.length,l=j?h:g(e||t(b[i+1]).action);u(b[i],l,e,a,i)}}function s(a){return"+"===a?["+"]:a.split("+")}function t(a,b){var c,d,e,f=[];for(c=s(a),e=0;e<c.length;++e)d=c[e],B[d]&&(d=B[d]),b&&"keypress"!=b&&A[d]&&(d=A[d],f.push("shift")),n(d)&&f.push(d);return b=q(d,f,b),{key:d,modifiers:f,action:b}}function u(a,b,c,d,e){D[a+":"+c]=b,a=a.replace(/\s+/g," ");var f,h=a.split(" ");return h.length>1?void r(a,h,b,c):(f=t(a,c),C[f.key]=C[f.key]||[],g(f.key,f.modifiers,{type:f.action},d,a,e),void C[f.key][d?"unshift":"push"]({callback:b,modifiers:f.modifiers,action:f.action,seq:d,level:e,combo:a}))}function v(a,b,c){for(var d=0;d<a.length;++d)u(a[d],b,c)}for(var w,x,y={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},z={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},A={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},B={option:"alt",command:"meta","return":"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},C={},D={},E={},F=!1,G=!1,H=!1,I=1;20>I;++I)y[111+I]="f"+I;for(I=0;9>=I;++I)y[I+96]=I;c(b,"keypress",m),c(b,"keydown",m),c(b,"keyup",m);var J={bind:function(a,b,c){return a=a instanceof Array?a:[a],v(a,b,c),this},unbind:function(a,b){return J.bind(a,function(){},b)},trigger:function(a,b){return D[a+":"+b]&&D[a+":"+b]({},a),this},reset:function(){return C={},D={},this},stopCallback:function(a,b){return(" "+b.className+" ").indexOf(" mousetrap ")>-1?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable},handleKey:l};a.Mousetrap=J,"function"==typeof define&&define.amd&&define(J)}(window,document); | ||
!function(){"use strict";angular.module("cfp.hotkeys",[]).provider("hotkeys",function(){this.includeCheatSheet=!0,this.template='<div class="cfp-hotkeys-container fade" ng-class="{in: helpVisible}"><div class="cfp-hotkeys"><h4 class="cfp-hotkeys-title">{{ title }}</h4><table><tbody><tr ng-repeat="hotkey in hotkeys | filter:{ description: \'!$$undefined$$\' }"><td class="cfp-hotkeys-keys"><span ng-repeat="key in hotkey.format() track by $index" class="cfp-hotkeys-key">{{ key }}</span></td><td class="cfp-hotkeys-text">{{ hotkey.description }}</td></tr></tbody></table><div class="cfp-hotkeys-close" ng-click="helpVisible = false">×</div></div></div>',this.cheatSheetHotkey="?",this.cheatSheetDescription="Show / hide this help menu",this.$get=["$rootElement","$rootScope","$compile","$window","$document",function(a,b,c,d,e){function f(a){var b={command:"⌘",shift:"⇧",left:"←",right:"→",up:"↑",down:"↓","return":"↩",backspace:"⌫"};a=a.split("+");for(var c=0;c<a.length;c++)"mod"===a[c]&&(a[c]=d.navigator&&d.navigator.platform.indexOf("Mac")>=0?"command":"ctrl"),a[c]=b[a[c]]||a[c];return a.join(" + ")}function g(a,b,c,d,e,f){this.combo=a instanceof Array?a:[a],this.description=b,this.callback=c,this.action=d,this.allowIn=e,this.persistent=f}function h(){for(var a=o.hotkeys.length;a--;){var b=o.hotkeys[a];b&&!b.persistent&&k(b)}}function i(){o.helpVisible=!o.helpVisible,o.helpVisible?(t=l("esc"),k("esc"),j("esc",t.description,i)):(k("esc"),t!==!1&&j(t))}function j(a,b,c,d,e,f){var h,i=["INPUT","SELECT","TEXTAREA"],j=Object.prototype.toString.call(a);if("[object Object]"===j&&(b=a.description,c=a.callback,d=a.action,f=a.persistent,e=a.allowIn,a=a.combo),b instanceof Function?(d=c,c=b,b="$$undefined$$"):angular.isUndefined(b)&&(b="$$undefined$$"),void 0===f&&(f=!0),"function"==typeof c){h=c,e instanceof Array||(e=[]);for(var k,l=0;l<e.length;l++)e[l]=e[l].toUpperCase(),k=i.indexOf(e[l]),-1!==k&&i.splice(k,1);c=function(a){var b=!0,c=a.target||a.srcElement,d=c.nodeName.toUpperCase();if((" "+c.className+" ").indexOf(" mousetrap ")>-1)b=!0;else for(var e=0;e<i.length;e++)if(i[e]===d){b=!1;break}b&&n(h.apply(this,arguments))}}"string"==typeof d?Mousetrap.bind(a,n(c),d):Mousetrap.bind(a,n(c));var m=new g(a,b,c,d,e,f);return o.hotkeys.push(m),m}function k(a){var b=a instanceof g?a.combo:a;if(Mousetrap.unbind(b),b instanceof Array){for(var c=!0,d=0;d<b.length;d++)c=k(b[d])&&c;return c}var e=o.hotkeys.indexOf(l(b));return e>-1?(o.hotkeys[e].combo.length>1?o.hotkeys[e].combo.splice(o.hotkeys[e].combo.indexOf(b),1):o.hotkeys.splice(e,1),!0):!1}function l(a){for(var b,c=0;c<o.hotkeys.length;c++)if(b=o.hotkeys[c],b.combo.indexOf(a)>-1)return b;return!1}function m(a){return p[a.$id]=[],a.$on("$destroy",function(){for(var b=p[a.$id].length;b--;)k(p[a.$id][b]),delete p[a.$id][b]}),{add:function(b){var c;return c=arguments.length>1?j.apply(this,arguments):j(b),p[a.$id].push(c),this}}}function n(a){return function(c,d){if(a instanceof Array){var e=a[0],f=a[1];a=function(){f.scope.$eval(e)}}b.$apply(function(){a(c,l(d))})}}Mousetrap.stopCallback=function(a,b){return(" "+b.className+" ").indexOf(" mousetrap ")>-1?!1:b.contentEditable&&"true"==b.contentEditable},g.prototype.format=function(){for(var a=this.combo[0],b=a.split(/[\s]/),c=0;c<b.length;c++)b[c]=f(b[c]);return b};var o=b.$new();o.hotkeys=[],o.helpVisible=!1,o.title="Keyboard Shortcuts:";var p=[];if(b.$on("$routeChangeSuccess",function(a,b){h(),b.hotkeys&&angular.forEach(b.hotkeys,function(a){var c=a[2];("string"==typeof c||c instanceof String)&&(a[2]=[c,b]),a[5]=!1,j.apply(this,a)})}),this.includeCheatSheet){var q=e[0],r=a[0],s=angular.element(this.template);j(this.cheatSheetHotkey,this.cheatSheetDescription,i),(r===q||r===q.documentElement)&&(r=q.body),angular.element(r).append(c(s)(o))}var t=!1,u={add:j,del:k,get:l,bindTo:m,template:this.template,toggleCheatSheet:i,includeCheatSheat:this.includeCheatSheat,cheatSheetHotkey:this.cheatSheetHotkey,cheatSheetDescription:this.cheatSheetDescription,purgeHotkeys:h};return u}]}).directive("hotkey",["hotkeys",function(a){return{restrict:"A",link:function(b,c,d){var e,f;angular.forEach(b.$eval(d.hotkey),function(b,c){f=d.hotkeyAllowIn.split(/[\s,]+/),e=c,a.add({combo:c,description:d.hotkeyDescription,callback:b,action:d.hotkeyAction,allowIn:f})}),c.bind("$destroy",function(){a.del(e)})}}}]).run(["hotkeys",function(){}])}(),function(a,b){function c(a,b,c){return a.addEventListener?void a.addEventListener(b,c,!1):void a.attachEvent("on"+b,c)}function d(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);return a.shiftKey||(b=b.toLowerCase()),b}return y[a.which]?y[a.which]:z[a.which]?z[a.which]:String.fromCharCode(a.which).toLowerCase()}function e(a,b){return a.sort().join(",")===b.sort().join(",")}function f(a){a=a||{};var b,c=!1;for(b in E)a[b]?c=!0:E[b]=0;c||(H=!1)}function g(a,b,c,d,f,g){var h,i,j=[],k=c.type;if(!C[a])return[];for("keyup"==k&&n(a)&&(b=[a]),h=0;h<C[a].length;++h)if(i=C[a][h],(d||!i.seq||E[i.seq]==i.level)&&k==i.action&&("keypress"==k&&!c.metaKey&&!c.ctrlKey||e(b,i.modifiers))){var l=!d&&i.combo==f,m=d&&i.seq==d&&i.level==g;(l||m)&&C[a].splice(h,1),j.push(i)}return j}function h(a){var b=[];return a.shiftKey&&b.push("shift"),a.altKey&&b.push("alt"),a.ctrlKey&&b.push("ctrl"),a.metaKey&&b.push("meta"),b}function i(a){return a.preventDefault?void a.preventDefault():void(a.returnValue=!1)}function j(a){return a.stopPropagation?void a.stopPropagation():void(a.cancelBubble=!0)}function k(a,b,c,d){J.stopCallback(b,b.target||b.srcElement,c,d)||a(b,c)===!1&&(i(b),j(b))}function l(a,b,c){var d,e=g(a,b,c),h={},i=0,j=!1;for(d=0;d<e.length;++d)e[d].seq&&(i=Math.max(i,e[d].level));for(d=0;d<e.length;++d)if(e[d].seq){if(e[d].level!=i)continue;j=!0,h[e[d].seq]=1,k(e[d].callback,c,e[d].combo,e[d].seq)}else j||k(e[d].callback,c,e[d].combo);var l="keypress"==c.type&&G;c.type!=H||n(a)||l||f(h),G=j&&"keydown"==c.type}function m(a){"number"!=typeof a.which&&(a.which=a.keyCode);var b=d(a);if(b)return"keyup"==a.type&&F===b?void(F=!1):void J.handleKey(b,h(a),a)}function n(a){return"shift"==a||"ctrl"==a||"alt"==a||"meta"==a}function o(){clearTimeout(x),x=setTimeout(f,1e3)}function p(){if(!w){w={};for(var a in y)a>95&&112>a||y.hasOwnProperty(a)&&(w[y[a]]=a)}return w}function q(a,b,c){return c||(c=p()[a]?"keydown":"keypress"),"keypress"==c&&b.length&&(c="keydown"),c}function r(a,b,c,e){function g(b){return function(){H=b,++E[a],o()}}function h(b){k(c,b,a),"keyup"!==e&&(F=d(b)),setTimeout(f,10)}E[a]=0;for(var i=0;i<b.length;++i){var j=i+1===b.length,l=j?h:g(e||t(b[i+1]).action);u(b[i],l,e,a,i)}}function s(a){return"+"===a?["+"]:a.split("+")}function t(a,b){var c,d,e,f=[];for(c=s(a),e=0;e<c.length;++e)d=c[e],B[d]&&(d=B[d]),b&&"keypress"!=b&&A[d]&&(d=A[d],f.push("shift")),n(d)&&f.push(d);return b=q(d,f,b),{key:d,modifiers:f,action:b}}function u(a,b,c,d,e){D[a+":"+c]=b,a=a.replace(/\s+/g," ");var f,h=a.split(" ");return h.length>1?void r(a,h,b,c):(f=t(a,c),C[f.key]=C[f.key]||[],g(f.key,f.modifiers,{type:f.action},d,a,e),void C[f.key][d?"unshift":"push"]({callback:b,modifiers:f.modifiers,action:f.action,seq:d,level:e,combo:a}))}function v(a,b,c){for(var d=0;d<a.length;++d)u(a[d],b,c)}for(var w,x,y={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},z={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},A={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},B={option:"alt",command:"meta","return":"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},C={},D={},E={},F=!1,G=!1,H=!1,I=1;20>I;++I)y[111+I]="f"+I;for(I=0;9>=I;++I)y[I+96]=I;c(b,"keypress",m),c(b,"keydown",m),c(b,"keyup",m);var J={bind:function(a,b,c){return a=a instanceof Array?a:[a],v(a,b,c),this},unbind:function(a,b){return J.bind(a,function(){},b)},trigger:function(a,b){return D[a+":"+b]&&D[a+":"+b]({},a),this},reset:function(){return C={},D={},this},stopCallback:function(a,b){return(" "+b.className+" ").indexOf(" mousetrap ")>-1?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable},handleKey:l};a.Mousetrap=J,"function"==typeof define&&define.amd&&define(J)}(window,document); |
{ | ||
"name": "angular-hotkeys", | ||
"author": "Wes Cruver", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "description": "Automatic keyboard shortcuts for your Angular Apps", |
@@ -76,3 +76,11 @@ angular-hotkeys | ||
#### Binding hotkeys in controllers: | ||
It is important to note that by default, hotkeys bound using the `hotkeys.add()` | ||
method are persistent, meaning they will continue to exist through route | ||
changes, DOM manipulation, or anything else. | ||
However, it is possible to bind the hotkey to a particular scope, and when that | ||
scope is destroyed, the hotkey is automatically removed. This should be | ||
considered the best practice when binding hotkeys from a controller. For this | ||
usage example, see the `hotkeys.bindTo()` method below: | ||
```js | ||
@@ -82,3 +90,4 @@ angular.module('myApp').controller('NavbarCtrl', function($scope, hotkeys) { | ||
// Pass it an object: | ||
// You can pass it an object. This hotkey will not be unbound unless manually removed | ||
// using the hotkeys.del() method | ||
hotkeys.add({ | ||
@@ -92,6 +101,12 @@ combo: 'ctrl+up', | ||
// or pass it arguments: | ||
hotkeys.add('ctrl+down', 'Turn the volume down on this hotness', function() { | ||
$scope.volume -= 1; | ||
}); | ||
// when you bind it to the controller's scope, it will automatically unbind | ||
// the hotkey when the scope is destroyed (due to ng-if or something that changes the DOM) | ||
hotkeys.bindTo($scope) | ||
.add({ | ||
combo: 'w', | ||
description: 'blah blah', | ||
callback: function() {} | ||
}) | ||
// you can chain these methods for ease of use: | ||
.add ({...}); | ||
@@ -149,23 +164,11 @@ }); | ||
#### hotkeys.add(combo, description, callback) | ||
#### hotkeys.add(object) | ||
`object`: An object with the following parameters: | ||
- `combo`: They keyboard combo (shortcut) you want to bind to | ||
- `description`: [OPTIONAL] The description for what the combo does and is only used for the Cheat Sheet. If it is not supplied, it will not show up, and in effect, allows you to have unlisted hotkeys. | ||
- `callback`: The function to execute when the key(s) are pressed. Passes along two arguments, `event` and `hotkey` | ||
- `action`: [OPTIONAL] The type of event to listen for, such as `keypress`, `keydown` or `keyup` | ||
- `allowIn`: [OPTIONAL] an array of tag names to allow this combo in ('INPUT', 'SELECT', and/or 'TEXTAREA') | ||
```js | ||
hotkeys.add('ctrl+w', 'Description goes here', function (event, hotkey) { | ||
event.preventDefault(); | ||
}); | ||
// this hotkey will not show up on the cheat sheet: | ||
hotkeys.add('ctrl+y', function (event, hotkey) { | ||
event.preventDefault(); | ||
}); | ||
``` | ||
#### hotkeys.add(object) | ||
- `object`: An object version of the above parameters. | ||
```js | ||
hotkeys.add({ | ||
@@ -178,2 +181,8 @@ combo: 'ctrl+w', | ||
}); | ||
// this hotkey will not show up on the cheat sheet: | ||
hotkeys.add({ | ||
combo: 'ctrl+x', | ||
callback: function(event, hotkey) {...} | ||
}); | ||
``` | ||
@@ -186,3 +195,3 @@ | ||
hotkeys.get('ctrl+w'); | ||
// -> Hotkey { combo: 'ctrl+w', description: 'Description goes here', callback: function (event, hotkey) } | ||
// -> Hotkey { combo: ['ctrl+w'], description: 'Description goes here', callback: function (event, hotkey) } | ||
``` | ||
@@ -189,0 +198,0 @@ |
@@ -102,3 +102,3 @@ /* | ||
* | ||
* @param {String} combo The keycombo | ||
* @param {array} combo The keycombo. it's an array to support multiple combos | ||
* @param {String} description Description for the keycombo | ||
@@ -114,3 +114,4 @@ * @param {Function} callback function to execute when keycombo pressed | ||
// supplied values | ||
this.combo = combo; | ||
this.combo = combo instanceof Array ? combo : [combo]; | ||
this.description = description; | ||
@@ -133,12 +134,6 @@ this.callback = callback; | ||
var combo = this.combo; | ||
// Don't show all the possible key combos, just the first one. Not sure | ||
// of usecase here, so open a ticket if my assumptions are wrong | ||
var combo = this.combo[0]; | ||
// if the combo is an array, it means the there are multiple bindings to | ||
// the same callback. Don't show all the possible key combos, just the | ||
// first one. Not sure of usecase here, so open a ticket if my | ||
// assumptions are wrong | ||
if (combo instanceof Array) { | ||
combo = combo[0]; | ||
} | ||
var sequence = combo.split(/[\s]/); | ||
@@ -177,2 +172,12 @@ for (var i = 0; i < sequence.length; i++) { | ||
/** | ||
* Holds references to the different scopes that have bound hotkeys | ||
* attached. This is useful to catch when the scopes are `$destroy`d and | ||
* then automatically unbind the hotkey. | ||
* | ||
* @type {Array} | ||
*/ | ||
var boundScopes = []; | ||
$rootScope.$on('$routeChangeSuccess', function (event, route) { | ||
@@ -362,3 +367,5 @@ purgeHotkeys(); | ||
scope.hotkeys.push(new Hotkey(combo, description, callback, action, allowIn, persistent)); | ||
var hotkey = new Hotkey(combo, description, callback, action, allowIn, persistent); | ||
scope.hotkeys.push(hotkey); | ||
return hotkey; | ||
} | ||
@@ -377,7 +384,24 @@ | ||
for (var i = 0; i < scope.hotkeys.length; i++) { | ||
if (scope.hotkeys[i].combo === combo) { | ||
scope.hotkeys.splice(i, 1); | ||
if (combo instanceof Array) { | ||
var retStatus = true; | ||
for (var i = 0; i < combo.length; i++) { | ||
retStatus = _del(combo[i]) && retStatus; | ||
} | ||
return retStatus; | ||
} else { | ||
var index = scope.hotkeys.indexOf(_get(combo)); | ||
if (index > -1) { | ||
// if the combo has other combos bound, don't unbind the whole thing, just the one combo: | ||
if (scope.hotkeys[index].combo.length > 1) { | ||
scope.hotkeys[index].combo.splice(scope.hotkeys[index].combo.indexOf(combo), 1); | ||
} else { | ||
scope.hotkeys.splice(index, 1); | ||
} | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
@@ -392,7 +416,13 @@ | ||
function _get (combo) { | ||
var hotkey; | ||
for (var i = 0; i < scope.hotkeys.length; i++) { | ||
if (scope.hotkeys[i].combo === combo) { | ||
return scope.hotkeys[i]; | ||
hotkey = scope.hotkeys[i]; | ||
if (hotkey.combo.indexOf(combo) > -1) { | ||
return hotkey; | ||
} | ||
} | ||
return false; | ||
@@ -402,2 +432,38 @@ } | ||
/** | ||
* Binds the hotkey to a particular scope. Useful if the scope is | ||
* destroyed, we can automatically destroy the hotkey binding. | ||
* | ||
* @param {Object} scope The scope to bind to | ||
*/ | ||
function bindTo (scope) { | ||
// Add the scope to the list of bound scopes | ||
boundScopes[scope.$id] = []; | ||
scope.$on('$destroy', function () { | ||
var i = boundScopes[scope.$id].length; | ||
while (i--) { | ||
_del(boundScopes[scope.$id][i]); | ||
delete boundScopes[scope.$id][i]; | ||
} | ||
}); | ||
// return an object with an add function so we can keep track of the | ||
// hotkeys and their scope that we added via this chaining method | ||
return { | ||
add: function (args) { | ||
var hotkey; | ||
if (arguments.length > 1) { | ||
hotkey = _add.apply(this, arguments); | ||
} else { | ||
hotkey = _add(args); | ||
} | ||
boundScopes[scope.$id].push(hotkey); | ||
return this; | ||
} | ||
}; | ||
} | ||
/** | ||
* All callbacks sent to Mousetrap are wrapped using this function | ||
@@ -438,2 +504,3 @@ * so that we can force a $scope.$apply() | ||
get : _get, | ||
bindTo : bindTo, | ||
template : this.template, | ||
@@ -456,7 +523,17 @@ toggleCheatSheet : toggleCheatSheet, | ||
link: function (scope, el, attrs) { | ||
var key; | ||
var key, allowIn; | ||
angular.forEach(scope.$eval(attrs.hotkey), function (func, hotkey) { | ||
// split and trim the hotkeys string into array | ||
allowIn = attrs.hotkeyAllowIn.split(/[\s,]+/); | ||
key = hotkey; | ||
hotkeys.add(hotkey, attrs.hotkeyDescription, func, attrs.hotkeyAction); | ||
hotkeys.add({ | ||
combo: hotkey, | ||
description: attrs.hotkeyDescription, | ||
callback: func, | ||
action: attrs.hotkeyAction, | ||
allowIn: allowIn | ||
}); | ||
}); | ||
@@ -463,0 +540,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
116176
2144
212