Greasy Fork镜像 支持简体中文。

Mousetrap

Help with keybinding in JavaScript for CCC

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/7927/35548/Mousetrap.js

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址