mousetrap.js 29 KB

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