├── .gitignore
├── LICENSE
├── README.md
├── bower.json
├── demo
└── index.html
├── gulpfile.js
├── package.json
├── release
├── angular-mousetrap-service.js
└── angular-mousetrap-service.min.js
└── src
├── angular-mousetrap-service.js
└── before-plugins.js
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Daniel Campos
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-mousetrap-service
2 | =========================
3 | [](http://badge.fury.io/js/angular-mousetrap-service)
4 | [](http://badge.fury.io/bo/angular-mousetrap-service)
5 |
6 | [AngularJS](https://angularjs.org) Wrapper for [mousetrap.js](http://craig.is/killing/mice) whithout registering it in the global scope.
7 |
8 |
9 | How to use
10 | ----------
11 |
12 | 1. Install angular-mousetrap-service.
13 | Using bower: `bower install --save angular-mousetrap-service`
14 | Or, using npm: `npm install --save angular-mousetrap-service`
15 |
16 | 2. Include angular-mousetrap-service into your project.
17 | ```HTML
18 |
19 | ```
20 |
21 | 3. Add ```angular-mousetrap``` module in the dependencies of a angular module:
22 | ```JavaScript
23 | angular.module('exampleApp', ['angular-mousetrap'])
24 | ```
25 |
26 | 4. Inject the Mousetrap service into a controller, a directive, etc:
27 | ```JavaScript
28 | .controller('ExampleCtrl', ['$scope','Mousetrap', function($scope, Mousetrap){
29 | Mousetrap.bind('ctrl+s', function() {
30 | //...
31 | });
32 | }]);
33 | ```
34 |
35 | Obs.: See the [Demo page](http://the-darc.github.io/angular-mousetrap/) for pratical real example.
36 |
37 | How to contribute
38 | -----------------
39 |
40 | I am very glad to see this project living with pull requests.
41 |
42 | LICENSE
43 | -------
44 |
45 | Copyright (c) 2015 Daniel Campos
46 |
47 | Licensed under the MIT license.
48 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-mousetrap-service",
3 | "version": "0.1.0",
4 | "homepage": "https://github.com/the-darc/angular-mousetrap",
5 | "authors": [
6 | "darc "
7 | ],
8 | "description": "AngularJS Service for dependency injection of mousetrap.js",
9 | "main": "release/service.min.js",
10 | "keywords": [
11 | "mousetrap",
12 | "angular",
13 | "keyboard",
14 | "shortcuts",
15 | "events"
16 | ],
17 | "license": "MIT",
18 | "ignore": [
19 | "**/.*",
20 | "node_modules",
21 | "bower_components"
22 | ],
23 | "devDependencies": {
24 | "mousetrap": "~1.4.6"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AngularJS Service for dependency injection of mousetrap.js
6 |
7 |
8 |
30 |
31 |
32 |
33 |
angular-mousetrap-service
34 |
35 |
36 |
37 |
38 |
39 |
Type the key(s) '{{configs.keys}}': {{configs.typed}}
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp'),
2 | path = require('path'),
3 | plugins = require('gulp-load-plugins')({
4 | config: path.join(__dirname, 'package.json')
5 | });
6 |
7 | gulp.task('build', function() {
8 | var pkg = require('./bower.json');
9 |
10 | var header = ['/**',
11 | ' * <%= pkg.name %>',
12 | ' * <%= pkg.description %>',
13 | ' * @version v<%= pkg.version %>',
14 | ' * @link <%= pkg.homepage %>',
15 | ' * @license <%= pkg.license %>',
16 | ' */',
17 | '(function (angular) {',
18 | ' var window = {};',
19 | '',
20 | ''].join('\n');
21 |
22 | var footer = [
23 | '',
24 | '})(angular);',
25 | ''].join('\n');
26 |
27 | gulp.src([
28 | 'bower_components/mousetrap/mousetrap.js',
29 | 'src/before-plugins.js',
30 | 'bower_components/mousetrap/plugins/pause/mousetrap-pause.js',
31 | 'bower_components/mousetrap/plugins/bind-dictionary/mousetrap-bind-dictionary.js',
32 | 'src/angular-mousetrap-service.js'
33 | ])
34 | .pipe(plugins.concat('angular-mousetrap-service.js'))
35 | .pipe(plugins.header(header, {pkg: pkg}))
36 | .pipe(plugins.footer(footer))
37 | .pipe(gulp.dest('./release/'))
38 | .pipe(plugins.uglify())
39 | .pipe(plugins.concat('angular-mousetrap-service.min.js'))
40 | .pipe(gulp.dest('./release/'));
41 | });
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-mousetrap-service",
3 | "version": "0.1.0",
4 | "description": "AngularJS Service for dependency injection of mousetrap.js",
5 | "main": "release/service.min.js",
6 | "scripts": {
7 | "build": "gulp build"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/the-darc/angular-mousetrap.git"
12 | },
13 | "keywords": [
14 | "mousetrap",
15 | "angular",
16 | "keyboard",
17 | "shortcuts",
18 | "events"
19 | ],
20 | "author": "darc.tec@gmail.com",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/the-darc/angular-mousetrap/issues"
24 | },
25 | "homepage": "https://github.com/the-darc/angular-mousetrap",
26 | "dependencies": {
27 | "gulp": "^3.8.10",
28 | "gulp-concat": "^2.4.3",
29 | "gulp-footer": "^1.0.5",
30 | "gulp-header": "^1.2.2",
31 | "gulp-load-plugins": "^0.8.0",
32 | "gulp-uglify": "^1.1.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/release/angular-mousetrap-service.js:
--------------------------------------------------------------------------------
1 | /**
2 | * angular-mousetrap-service
3 | * AngularJS Service for dependency injection of mousetrap.js
4 | * @version v0.1.0
5 | * @link https://github.com/the-darc/angular-mousetrap
6 | * @license MIT
7 | */
8 | (function (angular) {
9 | var window = {};
10 |
11 | /*global define:false */
12 | /**
13 | * Copyright 2013 Craig Campbell
14 | *
15 | * Licensed under the Apache License, Version 2.0 (the "License");
16 | * you may not use this file except in compliance with the License.
17 | * You may obtain a copy of the License at
18 | *
19 | * http://www.apache.org/licenses/LICENSE-2.0
20 | *
21 | * Unless required by applicable law or agreed to in writing, software
22 | * distributed under the License is distributed on an "AS IS" BASIS,
23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 | * See the License for the specific language governing permissions and
25 | * limitations under the License.
26 | *
27 | * Mousetrap is a simple keyboard shortcut library for Javascript with
28 | * no external dependencies
29 | *
30 | * @version 1.4.6
31 | * @url craig.is/killing/mice
32 | */
33 | (function(window, document, undefined) {
34 |
35 | /**
36 | * mapping of special keycodes to their corresponding keys
37 | *
38 | * everything in this dictionary cannot use keypress events
39 | * so it has to be here to map to the correct keycodes for
40 | * keyup/keydown events
41 | *
42 | * @type {Object}
43 | */
44 | var _MAP = {
45 | 8: 'backspace',
46 | 9: 'tab',
47 | 13: 'enter',
48 | 16: 'shift',
49 | 17: 'ctrl',
50 | 18: 'alt',
51 | 20: 'capslock',
52 | 27: 'esc',
53 | 32: 'space',
54 | 33: 'pageup',
55 | 34: 'pagedown',
56 | 35: 'end',
57 | 36: 'home',
58 | 37: 'left',
59 | 38: 'up',
60 | 39: 'right',
61 | 40: 'down',
62 | 45: 'ins',
63 | 46: 'del',
64 | 91: 'meta',
65 | 93: 'meta',
66 | 224: 'meta'
67 | },
68 |
69 | /**
70 | * mapping for special characters so they can support
71 | *
72 | * this dictionary is only used incase you want to bind a
73 | * keyup or keydown event to one of these keys
74 | *
75 | * @type {Object}
76 | */
77 | _KEYCODE_MAP = {
78 | 106: '*',
79 | 107: '+',
80 | 109: '-',
81 | 110: '.',
82 | 111 : '/',
83 | 186: ';',
84 | 187: '=',
85 | 188: ',',
86 | 189: '-',
87 | 190: '.',
88 | 191: '/',
89 | 192: '`',
90 | 219: '[',
91 | 220: '\\',
92 | 221: ']',
93 | 222: '\''
94 | },
95 |
96 | /**
97 | * this is a mapping of keys that require shift on a US keypad
98 | * back to the non shift equivelents
99 | *
100 | * this is so you can use keyup events with these keys
101 | *
102 | * note that this will only work reliably on US keyboards
103 | *
104 | * @type {Object}
105 | */
106 | _SHIFT_MAP = {
107 | '~': '`',
108 | '!': '1',
109 | '@': '2',
110 | '#': '3',
111 | '$': '4',
112 | '%': '5',
113 | '^': '6',
114 | '&': '7',
115 | '*': '8',
116 | '(': '9',
117 | ')': '0',
118 | '_': '-',
119 | '+': '=',
120 | ':': ';',
121 | '\"': '\'',
122 | '<': ',',
123 | '>': '.',
124 | '?': '/',
125 | '|': '\\'
126 | },
127 |
128 | /**
129 | * this is a list of special strings you can use to map
130 | * to modifier keys when you specify your keyboard shortcuts
131 | *
132 | * @type {Object}
133 | */
134 | _SPECIAL_ALIASES = {
135 | 'option': 'alt',
136 | 'command': 'meta',
137 | 'return': 'enter',
138 | 'escape': 'esc',
139 | 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
140 | },
141 |
142 | /**
143 | * variable to store the flipped version of _MAP from above
144 | * needed to check if we should use keypress or not when no action
145 | * is specified
146 | *
147 | * @type {Object|undefined}
148 | */
149 | _REVERSE_MAP,
150 |
151 | /**
152 | * a list of all the callbacks setup via Mousetrap.bind()
153 | *
154 | * @type {Object}
155 | */
156 | _callbacks = {},
157 |
158 | /**
159 | * direct map of string combinations to callbacks used for trigger()
160 | *
161 | * @type {Object}
162 | */
163 | _directMap = {},
164 |
165 | /**
166 | * keeps track of what level each sequence is at since multiple
167 | * sequences can start out with the same sequence
168 | *
169 | * @type {Object}
170 | */
171 | _sequenceLevels = {},
172 |
173 | /**
174 | * variable to store the setTimeout call
175 | *
176 | * @type {null|number}
177 | */
178 | _resetTimer,
179 |
180 | /**
181 | * temporary state where we will ignore the next keyup
182 | *
183 | * @type {boolean|string}
184 | */
185 | _ignoreNextKeyup = false,
186 |
187 | /**
188 | * temporary state where we will ignore the next keypress
189 | *
190 | * @type {boolean}
191 | */
192 | _ignoreNextKeypress = false,
193 |
194 | /**
195 | * are we currently inside of a sequence?
196 | * type of action ("keyup" or "keydown" or "keypress") or false
197 | *
198 | * @type {boolean|string}
199 | */
200 | _nextExpectedAction = false;
201 |
202 | /**
203 | * loop through the f keys, f1 to f19 and add them to the map
204 | * programatically
205 | */
206 | for (var i = 1; i < 20; ++i) {
207 | _MAP[111 + i] = 'f' + i;
208 | }
209 |
210 | /**
211 | * loop through to map numbers on the numeric keypad
212 | */
213 | for (i = 0; i <= 9; ++i) {
214 | _MAP[i + 96] = i;
215 | }
216 |
217 | /**
218 | * cross browser add event method
219 | *
220 | * @param {Element|HTMLDocument} object
221 | * @param {string} type
222 | * @param {Function} callback
223 | * @returns void
224 | */
225 | function _addEvent(object, type, callback) {
226 | if (object.addEventListener) {
227 | object.addEventListener(type, callback, false);
228 | return;
229 | }
230 |
231 | object.attachEvent('on' + type, callback);
232 | }
233 |
234 | /**
235 | * takes the event and returns the key character
236 | *
237 | * @param {Event} e
238 | * @return {string}
239 | */
240 | function _characterFromEvent(e) {
241 |
242 | // for keypress events we should return the character as is
243 | if (e.type == 'keypress') {
244 | var character = String.fromCharCode(e.which);
245 |
246 | // if the shift key is not pressed then it is safe to assume
247 | // that we want the character to be lowercase. this means if
248 | // you accidentally have caps lock on then your key bindings
249 | // will continue to work
250 | //
251 | // the only side effect that might not be desired is if you
252 | // bind something like 'A' cause you want to trigger an
253 | // event when capital A is pressed caps lock will no longer
254 | // trigger the event. shift+a will though.
255 | if (!e.shiftKey) {
256 | character = character.toLowerCase();
257 | }
258 |
259 | return character;
260 | }
261 |
262 | // for non keypress events the special maps are needed
263 | if (_MAP[e.which]) {
264 | return _MAP[e.which];
265 | }
266 |
267 | if (_KEYCODE_MAP[e.which]) {
268 | return _KEYCODE_MAP[e.which];
269 | }
270 |
271 | // if it is not in the special map
272 |
273 | // with keydown and keyup events the character seems to always
274 | // come in as an uppercase character whether you are pressing shift
275 | // or not. we should make sure it is always lowercase for comparisons
276 | return String.fromCharCode(e.which).toLowerCase();
277 | }
278 |
279 | /**
280 | * checks if two arrays are equal
281 | *
282 | * @param {Array} modifiers1
283 | * @param {Array} modifiers2
284 | * @returns {boolean}
285 | */
286 | function _modifiersMatch(modifiers1, modifiers2) {
287 | return modifiers1.sort().join(',') === modifiers2.sort().join(',');
288 | }
289 |
290 | /**
291 | * resets all sequence counters except for the ones passed in
292 | *
293 | * @param {Object} doNotReset
294 | * @returns void
295 | */
296 | function _resetSequences(doNotReset) {
297 | doNotReset = doNotReset || {};
298 |
299 | var activeSequences = false,
300 | key;
301 |
302 | for (key in _sequenceLevels) {
303 | if (doNotReset[key]) {
304 | activeSequences = true;
305 | continue;
306 | }
307 | _sequenceLevels[key] = 0;
308 | }
309 |
310 | if (!activeSequences) {
311 | _nextExpectedAction = false;
312 | }
313 | }
314 |
315 | /**
316 | * finds all callbacks that match based on the keycode, modifiers,
317 | * and action
318 | *
319 | * @param {string} character
320 | * @param {Array} modifiers
321 | * @param {Event|Object} e
322 | * @param {string=} sequenceName - name of the sequence we are looking for
323 | * @param {string=} combination
324 | * @param {number=} level
325 | * @returns {Array}
326 | */
327 | function _getMatches(character, modifiers, e, sequenceName, combination, level) {
328 | var i,
329 | callback,
330 | matches = [],
331 | action = e.type;
332 |
333 | // if there are no events related to this keycode
334 | if (!_callbacks[character]) {
335 | return [];
336 | }
337 |
338 | // if a modifier key is coming up on its own we should allow it
339 | if (action == 'keyup' && _isModifier(character)) {
340 | modifiers = [character];
341 | }
342 |
343 | // loop through all callbacks for the key that was pressed
344 | // and see if any of them match
345 | for (i = 0; i < _callbacks[character].length; ++i) {
346 | callback = _callbacks[character][i];
347 |
348 | // if a sequence name is not specified, but this is a sequence at
349 | // the wrong level then move onto the next match
350 | if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
351 | continue;
352 | }
353 |
354 | // if the action we are looking for doesn't match the action we got
355 | // then we should keep going
356 | if (action != callback.action) {
357 | continue;
358 | }
359 |
360 | // if this is a keypress event and the meta key and control key
361 | // are not pressed that means that we need to only look at the
362 | // character, otherwise check the modifiers as well
363 | //
364 | // chrome will not fire a keypress if meta or control is down
365 | // safari will fire a keypress if meta or meta+shift is down
366 | // firefox will fire a keypress if meta or control is down
367 | if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
368 |
369 | // when you bind a combination or sequence a second time it
370 | // should overwrite the first one. if a sequenceName or
371 | // combination is specified in this call it does just that
372 | //
373 | // @todo make deleting its own method?
374 | var deleteCombo = !sequenceName && callback.combo == combination;
375 | var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
376 | if (deleteCombo || deleteSequence) {
377 | _callbacks[character].splice(i, 1);
378 | }
379 |
380 | matches.push(callback);
381 | }
382 | }
383 |
384 | return matches;
385 | }
386 |
387 | /**
388 | * takes a key event and figures out what the modifiers are
389 | *
390 | * @param {Event} e
391 | * @returns {Array}
392 | */
393 | function _eventModifiers(e) {
394 | var modifiers = [];
395 |
396 | if (e.shiftKey) {
397 | modifiers.push('shift');
398 | }
399 |
400 | if (e.altKey) {
401 | modifiers.push('alt');
402 | }
403 |
404 | if (e.ctrlKey) {
405 | modifiers.push('ctrl');
406 | }
407 |
408 | if (e.metaKey) {
409 | modifiers.push('meta');
410 | }
411 |
412 | return modifiers;
413 | }
414 |
415 | /**
416 | * prevents default for this event
417 | *
418 | * @param {Event} e
419 | * @returns void
420 | */
421 | function _preventDefault(e) {
422 | if (e.preventDefault) {
423 | e.preventDefault();
424 | return;
425 | }
426 |
427 | e.returnValue = false;
428 | }
429 |
430 | /**
431 | * stops propogation for this event
432 | *
433 | * @param {Event} e
434 | * @returns void
435 | */
436 | function _stopPropagation(e) {
437 | if (e.stopPropagation) {
438 | e.stopPropagation();
439 | return;
440 | }
441 |
442 | e.cancelBubble = true;
443 | }
444 |
445 | /**
446 | * actually calls the callback function
447 | *
448 | * if your callback function returns false this will use the jquery
449 | * convention - prevent default and stop propogation on the event
450 | *
451 | * @param {Function} callback
452 | * @param {Event} e
453 | * @returns void
454 | */
455 | function _fireCallback(callback, e, combo, sequence) {
456 |
457 | // if this event should not happen stop here
458 | if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo, sequence)) {
459 | return;
460 | }
461 |
462 | if (callback(e, combo) === false) {
463 | _preventDefault(e);
464 | _stopPropagation(e);
465 | }
466 | }
467 |
468 | /**
469 | * handles a character key event
470 | *
471 | * @param {string} character
472 | * @param {Array} modifiers
473 | * @param {Event} e
474 | * @returns void
475 | */
476 | function _handleKey(character, modifiers, e) {
477 | var callbacks = _getMatches(character, modifiers, e),
478 | i,
479 | doNotReset = {},
480 | maxLevel = 0,
481 | processedSequenceCallback = false;
482 |
483 | // Calculate the maxLevel for sequences so we can only execute the longest callback sequence
484 | for (i = 0; i < callbacks.length; ++i) {
485 | if (callbacks[i].seq) {
486 | maxLevel = Math.max(maxLevel, callbacks[i].level);
487 | }
488 | }
489 |
490 | // loop through matching callbacks for this key event
491 | for (i = 0; i < callbacks.length; ++i) {
492 |
493 | // fire for all sequence callbacks
494 | // this is because if for example you have multiple sequences
495 | // bound such as "g i" and "g t" they both need to fire the
496 | // callback for matching g cause otherwise you can only ever
497 | // match the first one
498 | if (callbacks[i].seq) {
499 |
500 | // only fire callbacks for the maxLevel to prevent
501 | // subsequences from also firing
502 | //
503 | // for example 'a option b' should not cause 'option b' to fire
504 | // even though 'option b' is part of the other sequence
505 | //
506 | // any sequences that do not match here will be discarded
507 | // below by the _resetSequences call
508 | if (callbacks[i].level != maxLevel) {
509 | continue;
510 | }
511 |
512 | processedSequenceCallback = true;
513 |
514 | // keep a list of which sequences were matches for later
515 | doNotReset[callbacks[i].seq] = 1;
516 | _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq);
517 | continue;
518 | }
519 |
520 | // if there were no sequence matches but we are still here
521 | // that means this is a regular match so we should fire that
522 | if (!processedSequenceCallback) {
523 | _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
524 | }
525 | }
526 |
527 | // if the key you pressed matches the type of sequence without
528 | // being a modifier (ie "keyup" or "keypress") then we should
529 | // reset all sequences that were not matched by this event
530 | //
531 | // this is so, for example, if you have the sequence "h a t" and you
532 | // type "h e a r t" it does not match. in this case the "e" will
533 | // cause the sequence to reset
534 | //
535 | // modifier keys are ignored because you can have a sequence
536 | // that contains modifiers such as "enter ctrl+space" and in most
537 | // cases the modifier key will be pressed before the next key
538 | //
539 | // also if you have a sequence such as "ctrl+b a" then pressing the
540 | // "b" key will trigger a "keypress" and a "keydown"
541 | //
542 | // the "keydown" is expected when there is a modifier, but the
543 | // "keypress" ends up matching the _nextExpectedAction since it occurs
544 | // after and that causes the sequence to reset
545 | //
546 | // we ignore keypresses in a sequence that directly follow a keydown
547 | // for the same character
548 | var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress;
549 | if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) {
550 | _resetSequences(doNotReset);
551 | }
552 |
553 | _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown';
554 | }
555 |
556 | /**
557 | * handles a keydown event
558 | *
559 | * @param {Event} e
560 | * @returns void
561 | */
562 | function _handleKeyEvent(e) {
563 |
564 | // normalize e.which for key events
565 | // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
566 | if (typeof e.which !== 'number') {
567 | e.which = e.keyCode;
568 | }
569 |
570 | var character = _characterFromEvent(e);
571 |
572 | // no character found then stop
573 | if (!character) {
574 | return;
575 | }
576 |
577 | // need to use === for the character check because the character can be 0
578 | if (e.type == 'keyup' && _ignoreNextKeyup === character) {
579 | _ignoreNextKeyup = false;
580 | return;
581 | }
582 |
583 | Mousetrap.handleKey(character, _eventModifiers(e), e);
584 | }
585 |
586 | /**
587 | * determines if the keycode specified is a modifier key or not
588 | *
589 | * @param {string} key
590 | * @returns {boolean}
591 | */
592 | function _isModifier(key) {
593 | return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
594 | }
595 |
596 | /**
597 | * called to set a 1 second timeout on the specified sequence
598 | *
599 | * this is so after each key press in the sequence you have 1 second
600 | * to press the next key before you have to start over
601 | *
602 | * @returns void
603 | */
604 | function _resetSequenceTimer() {
605 | clearTimeout(_resetTimer);
606 | _resetTimer = setTimeout(_resetSequences, 1000);
607 | }
608 |
609 | /**
610 | * reverses the map lookup so that we can look for specific keys
611 | * to see what can and can't use keypress
612 | *
613 | * @return {Object}
614 | */
615 | function _getReverseMap() {
616 | if (!_REVERSE_MAP) {
617 | _REVERSE_MAP = {};
618 | for (var key in _MAP) {
619 |
620 | // pull out the numeric keypad from here cause keypress should
621 | // be able to detect the keys from the character
622 | if (key > 95 && key < 112) {
623 | continue;
624 | }
625 |
626 | if (_MAP.hasOwnProperty(key)) {
627 | _REVERSE_MAP[_MAP[key]] = key;
628 | }
629 | }
630 | }
631 | return _REVERSE_MAP;
632 | }
633 |
634 | /**
635 | * picks the best action based on the key combination
636 | *
637 | * @param {string} key - character for key
638 | * @param {Array} modifiers
639 | * @param {string=} action passed in
640 | */
641 | function _pickBestAction(key, modifiers, action) {
642 |
643 | // if no action was picked in we should try to pick the one
644 | // that we think would work best for this key
645 | if (!action) {
646 | action = _getReverseMap()[key] ? 'keydown' : 'keypress';
647 | }
648 |
649 | // modifier keys don't work as expected with keypress,
650 | // switch to keydown
651 | if (action == 'keypress' && modifiers.length) {
652 | action = 'keydown';
653 | }
654 |
655 | return action;
656 | }
657 |
658 | /**
659 | * binds a key sequence to an event
660 | *
661 | * @param {string} combo - combo specified in bind call
662 | * @param {Array} keys
663 | * @param {Function} callback
664 | * @param {string=} action
665 | * @returns void
666 | */
667 | function _bindSequence(combo, keys, callback, action) {
668 |
669 | // start off by adding a sequence level record for this combination
670 | // and setting the level to 0
671 | _sequenceLevels[combo] = 0;
672 |
673 | /**
674 | * callback to increase the sequence level for this sequence and reset
675 | * all other sequences that were active
676 | *
677 | * @param {string} nextAction
678 | * @returns {Function}
679 | */
680 | function _increaseSequence(nextAction) {
681 | return function() {
682 | _nextExpectedAction = nextAction;
683 | ++_sequenceLevels[combo];
684 | _resetSequenceTimer();
685 | };
686 | }
687 |
688 | /**
689 | * wraps the specified callback inside of another function in order
690 | * to reset all sequence counters as soon as this sequence is done
691 | *
692 | * @param {Event} e
693 | * @returns void
694 | */
695 | function _callbackAndReset(e) {
696 | _fireCallback(callback, e, combo);
697 |
698 | // we should ignore the next key up if the action is key down
699 | // or keypress. this is so if you finish a sequence and
700 | // release the key the final key will not trigger a keyup
701 | if (action !== 'keyup') {
702 | _ignoreNextKeyup = _characterFromEvent(e);
703 | }
704 |
705 | // weird race condition if a sequence ends with the key
706 | // another sequence begins with
707 | setTimeout(_resetSequences, 10);
708 | }
709 |
710 | // loop through keys one at a time and bind the appropriate callback
711 | // function. for any key leading up to the final one it should
712 | // increase the sequence. after the final, it should reset all sequences
713 | //
714 | // if an action is specified in the original bind call then that will
715 | // be used throughout. otherwise we will pass the action that the
716 | // next key in the sequence should match. this allows a sequence
717 | // to mix and match keypress and keydown events depending on which
718 | // ones are better suited to the key provided
719 | for (var i = 0; i < keys.length; ++i) {
720 | var isFinal = i + 1 === keys.length;
721 | var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action);
722 | _bindSingle(keys[i], wrappedCallback, action, combo, i);
723 | }
724 | }
725 |
726 | /**
727 | * Converts from a string key combination to an array
728 | *
729 | * @param {string} combination like "command+shift+l"
730 | * @return {Array}
731 | */
732 | function _keysFromString(combination) {
733 | if (combination === '+') {
734 | return ['+'];
735 | }
736 |
737 | return combination.split('+');
738 | }
739 |
740 | /**
741 | * Gets info for a specific key combination
742 | *
743 | * @param {string} combination key combination ("command+s" or "a" or "*")
744 | * @param {string=} action
745 | * @returns {Object}
746 | */
747 | function _getKeyInfo(combination, action) {
748 | var keys,
749 | key,
750 | i,
751 | modifiers = [];
752 |
753 | // take the keys from this pattern and figure out what the actual
754 | // pattern is all about
755 | keys = _keysFromString(combination);
756 |
757 | for (i = 0; i < keys.length; ++i) {
758 | key = keys[i];
759 |
760 | // normalize key names
761 | if (_SPECIAL_ALIASES[key]) {
762 | key = _SPECIAL_ALIASES[key];
763 | }
764 |
765 | // if this is not a keypress event then we should
766 | // be smart about using shift keys
767 | // this will only work for US keyboards however
768 | if (action && action != 'keypress' && _SHIFT_MAP[key]) {
769 | key = _SHIFT_MAP[key];
770 | modifiers.push('shift');
771 | }
772 |
773 | // if this key is a modifier then add it to the list of modifiers
774 | if (_isModifier(key)) {
775 | modifiers.push(key);
776 | }
777 | }
778 |
779 | // depending on what the key combination is
780 | // we will try to pick the best event for it
781 | action = _pickBestAction(key, modifiers, action);
782 |
783 | return {
784 | key: key,
785 | modifiers: modifiers,
786 | action: action
787 | };
788 | }
789 |
790 | /**
791 | * binds a single keyboard combination
792 | *
793 | * @param {string} combination
794 | * @param {Function} callback
795 | * @param {string=} action
796 | * @param {string=} sequenceName - name of sequence if part of sequence
797 | * @param {number=} level - what part of the sequence the command is
798 | * @returns void
799 | */
800 | function _bindSingle(combination, callback, action, sequenceName, level) {
801 |
802 | // store a direct mapped reference for use with Mousetrap.trigger
803 | _directMap[combination + ':' + action] = callback;
804 |
805 | // make sure multiple spaces in a row become a single space
806 | combination = combination.replace(/\s+/g, ' ');
807 |
808 | var sequence = combination.split(' '),
809 | info;
810 |
811 | // if this pattern is a sequence of keys then run through this method
812 | // to reprocess each pattern one key at a time
813 | if (sequence.length > 1) {
814 | _bindSequence(combination, sequence, callback, action);
815 | return;
816 | }
817 |
818 | info = _getKeyInfo(combination, action);
819 |
820 | // make sure to initialize array if this is the first time
821 | // a callback is added for this key
822 | _callbacks[info.key] = _callbacks[info.key] || [];
823 |
824 | // remove an existing match if there is one
825 | _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level);
826 |
827 | // add this call back to the array
828 | // if it is a sequence put it at the beginning
829 | // if not put it at the end
830 | //
831 | // this is important because the way these are processed expects
832 | // the sequence ones to come first
833 | _callbacks[info.key][sequenceName ? 'unshift' : 'push']({
834 | callback: callback,
835 | modifiers: info.modifiers,
836 | action: info.action,
837 | seq: sequenceName,
838 | level: level,
839 | combo: combination
840 | });
841 | }
842 |
843 | /**
844 | * binds multiple combinations to the same callback
845 | *
846 | * @param {Array} combinations
847 | * @param {Function} callback
848 | * @param {string|undefined} action
849 | * @returns void
850 | */
851 | function _bindMultiple(combinations, callback, action) {
852 | for (var i = 0; i < combinations.length; ++i) {
853 | _bindSingle(combinations[i], callback, action);
854 | }
855 | }
856 |
857 | // start!
858 | _addEvent(document, 'keypress', _handleKeyEvent);
859 | _addEvent(document, 'keydown', _handleKeyEvent);
860 | _addEvent(document, 'keyup', _handleKeyEvent);
861 |
862 | var Mousetrap = {
863 |
864 | /**
865 | * binds an event to mousetrap
866 | *
867 | * can be a single key, a combination of keys separated with +,
868 | * an array of keys, or a sequence of keys separated by spaces
869 | *
870 | * be sure to list the modifier keys first to make sure that the
871 | * correct key ends up getting bound (the last key in the pattern)
872 | *
873 | * @param {string|Array} keys
874 | * @param {Function} callback
875 | * @param {string=} action - 'keypress', 'keydown', or 'keyup'
876 | * @returns void
877 | */
878 | bind: function(keys, callback, action) {
879 | keys = keys instanceof Array ? keys : [keys];
880 | _bindMultiple(keys, callback, action);
881 | return this;
882 | },
883 |
884 | /**
885 | * unbinds an event to mousetrap
886 | *
887 | * the unbinding sets the callback function of the specified key combo
888 | * to an empty function and deletes the corresponding key in the
889 | * _directMap dict.
890 | *
891 | * TODO: actually remove this from the _callbacks dictionary instead
892 | * of binding an empty function
893 | *
894 | * the keycombo+action has to be exactly the same as
895 | * it was defined in the bind method
896 | *
897 | * @param {string|Array} keys
898 | * @param {string} action
899 | * @returns void
900 | */
901 | unbind: function(keys, action) {
902 | return Mousetrap.bind(keys, function() {}, action);
903 | },
904 |
905 | /**
906 | * triggers an event that has already been bound
907 | *
908 | * @param {string} keys
909 | * @param {string=} action
910 | * @returns void
911 | */
912 | trigger: function(keys, action) {
913 | if (_directMap[keys + ':' + action]) {
914 | _directMap[keys + ':' + action]({}, keys);
915 | }
916 | return this;
917 | },
918 |
919 | /**
920 | * resets the library back to its initial state. this is useful
921 | * if you want to clear out the current keyboard shortcuts and bind
922 | * new ones - for example if you switch to another page
923 | *
924 | * @returns void
925 | */
926 | reset: function() {
927 | _callbacks = {};
928 | _directMap = {};
929 | return this;
930 | },
931 |
932 | /**
933 | * should we stop this event before firing off callbacks
934 | *
935 | * @param {Event} e
936 | * @param {Element} element
937 | * @return {boolean}
938 | */
939 | stopCallback: function(e, element) {
940 |
941 | // if the element has the class "mousetrap" then no need to stop
942 | if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
943 | return false;
944 | }
945 |
946 | // stop for input, select, and textarea
947 | return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
948 | },
949 |
950 | /**
951 | * exposes _handleKey publicly so it can be overwritten by extensions
952 | */
953 | handleKey: _handleKey
954 | };
955 |
956 | // expose mousetrap to the global object
957 | window.Mousetrap = Mousetrap;
958 |
959 | // expose mousetrap as an AMD module
960 | if (typeof define === 'function' && define.amd) {
961 | define(Mousetrap);
962 | }
963 | }) (window, document);
964 |
965 | var Mousetrap = window.Mousetrap;
966 |
967 | /**
968 | * adds a pause and unpause method to Mousetrap
969 | * this allows you to enable or disable keyboard shortcuts
970 | * without having to reset Mousetrap and rebind everything
971 | */
972 | /* global Mousetrap:true */
973 | Mousetrap = (function(Mousetrap) {
974 | var self = Mousetrap,
975 | _originalStopCallback = self.stopCallback,
976 | enabled = true;
977 |
978 | self.stopCallback = function(e, element, combo) {
979 | if (!enabled) {
980 | return true;
981 | }
982 |
983 | return _originalStopCallback(e, element, combo);
984 | };
985 |
986 | self.pause = function() {
987 | enabled = false;
988 | };
989 |
990 | self.unpause = function() {
991 | enabled = true;
992 | };
993 |
994 | return self;
995 | }) (Mousetrap);
996 |
997 | /**
998 | * Overwrites default Mousetrap.bind method to optionally accept
999 | * an object to bind multiple key events in a single call
1000 | *
1001 | * You can pass it in like:
1002 | *
1003 | * Mousetrap.bind({
1004 | * 'a': function() { console.log('a'); },
1005 | * 'b': function() { console.log('b'); }
1006 | * });
1007 | *
1008 | * And can optionally pass in 'keypress', 'keydown', or 'keyup'
1009 | * as a second argument
1010 | *
1011 | */
1012 | /* global Mousetrap:true */
1013 | Mousetrap = (function(Mousetrap) {
1014 | var self = Mousetrap,
1015 | _oldBind = self.bind,
1016 | args;
1017 |
1018 | self.bind = function() {
1019 | args = arguments;
1020 |
1021 | // normal call
1022 | if (typeof args[0] == 'string' || args[0] instanceof Array) {
1023 | return _oldBind(args[0], args[1], args[2]);
1024 | }
1025 |
1026 | // object passed in
1027 | for (var key in args[0]) {
1028 | if (args[0].hasOwnProperty(key)) {
1029 | _oldBind(key, args[0][key], args[1]);
1030 | }
1031 | }
1032 | };
1033 |
1034 | return self;
1035 | }) (Mousetrap);
1036 |
1037 | angular.module('angular-mousetrap', [])
1038 | .factory('Mousetrap', [function(){
1039 | return window.Mousetrap;
1040 | }]);
1041 |
1042 | })(angular);
1043 |
--------------------------------------------------------------------------------
/release/angular-mousetrap-service.min.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={};!function(e,t){function n(e,t,n){return e.addEventListener?void e.addEventListener(t,n,!1):void e.attachEvent("on"+t,n)}function r(e){if("keypress"==e.type){var t=String.fromCharCode(e.which);return e.shiftKey||(t=t.toLowerCase()),t}return K[e.which]?K[e.which]:q[e.which]?q[e.which]:String.fromCharCode(e.which).toLowerCase()}function o(e,t){return e.sort().join(",")===t.sort().join(",")}function i(e){e=e||{};var t,n=!1;for(t in N)e[t]?n=!0:N[t]=0;n||(S=!1)}function a(e,t,n,r,i,a){var u,c,s=[],f=n.type;if(!M[e])return[];for("keyup"==f&&h(e)&&(t=[e]),u=0;u95&&112>e||K.hasOwnProperty(e)&&(C[K[e]]=e)}return C}function v(e,t,n){return n||(n=y()[e]?"keydown":"keypress"),"keypress"==n&&t.length&&(n="keydown"),n}function m(e,t,n,o){function a(t){return function(){S=t,++N[e],d()}}function u(t){f(n,t,e),"keyup"!==o&&(A=r(t)),setTimeout(i,10)}N[e]=0;for(var c=0;c1?void m(e,u,t,n):(i=g(e,n),M[i.key]=M[i.key]||[],a(i.key,i.modifiers,{type:i.action},r,e,o),void M[i.key][r?"unshift":"push"]({callback:t,modifiers:i.modifiers,action:i.action,seq:r,level:o,combo:e}))}function w(e,t,n){for(var r=0;r":".","?":"/","|":"\\"},T={option:"alt",command:"meta","return":"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},M={},L={},N={},A=!1,O=!1,S=!1,j=1;20>j;++j)K[111+j]="f"+j;for(j=0;9>=j;++j)K[j+96]=j;n(t,"keypress",p),n(t,"keydown",p),n(t,"keyup",p);var x={bind:function(e,t,n){return e=e instanceof Array?e:[e],w(e,t,n),this},unbind:function(e,t){return x.bind(e,function(){},t)},trigger:function(e,t){return L[e+":"+t]&&L[e+":"+t]({},e),this},reset:function(){return M={},L={},this},stopCallback:function(e,t){return(" "+t.className+" ").indexOf(" mousetrap ")>-1?!1:"INPUT"==t.tagName||"SELECT"==t.tagName||"TEXTAREA"==t.tagName||t.isContentEditable},handleKey:l};e.Mousetrap=x,"function"==typeof define&&define.amd&&define(x)}(t,document);var n=t.Mousetrap;n=function(e){var t=e,n=t.stopCallback,r=!0;return t.stopCallback=function(e,t,o){return r?n(e,t,o):!0},t.pause=function(){r=!1},t.unpause=function(){r=!0},t}(n),n=function(e){var t,n=e,r=n.bind;return n.bind=function(){if(t=arguments,"string"==typeof t[0]||t[0]instanceof Array)return r(t[0],t[1],t[2]);for(var e in t[0])t[0].hasOwnProperty(e)&&r(e,t[0][e],t[1])},n}(n),e.module("angular-mousetrap",[]).factory("Mousetrap",[function(){return t.Mousetrap}])}(angular);
--------------------------------------------------------------------------------
/src/angular-mousetrap-service.js:
--------------------------------------------------------------------------------
1 | angular.module('angular-mousetrap', [])
2 | .factory('Mousetrap', [function(){
3 | return window.Mousetrap;
4 | }]);
5 |
--------------------------------------------------------------------------------
/src/before-plugins.js:
--------------------------------------------------------------------------------
1 | var Mousetrap = window.Mousetrap;
2 |
--------------------------------------------------------------------------------