UNPKG

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