mousetrap-custom.js 33 KB

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