combokeys
Version:
Handles keyboard shortcuts in the browser
94 lines (85 loc) • 3.33 kB
JavaScript
/* eslint-env node, browser */
/**
* handles a character key event
*
* @param {string} character
* @param {Array} modifiers
* @param {Event} e
* @returns void
*/
module.exports = function (character, modifiers, e) {
var self = this
var callbacks
var j
var doNotReset = {}
var maxLevel = 0
var processedSequenceCallback = false
var isModifier
var ignoreThisKeypress
callbacks = self.getMatches(character, modifiers, e)
// Calculate the maxLevel for sequences so we can only execute the longest callback sequence
for (j = 0; j < callbacks.length; ++j) {
if (callbacks[j].seq) {
maxLevel = Math.max(maxLevel, callbacks[j].level)
}
}
// loop through matching callbacks for this key event
for (j = 0; j < callbacks.length; ++j) {
// fire for all sequence callbacks
// this is because if for example you have multiple sequences
// bound such as "g i" and "g t" they both need to fire the
// callback for matching g cause otherwise you can only ever
// match the first one
if (callbacks[j].seq) {
// only fire callbacks for the maxLevel to prevent
// subsequences from also firing
//
// for example 'a option b' should not cause 'option b' to fire
// even though 'option b' is part of the other sequence
//
// any sequences that do not match here will be discarded
// below by the resetSequences call
if (callbacks[j].level !== maxLevel) {
continue
}
processedSequenceCallback = true
// keep a list of which sequences were matches for later
doNotReset[callbacks[j].seq] = 1
self.fireCallback(callbacks[j].callback, e, callbacks[j].combo, callbacks[j].seq)
continue
}
// if there were no sequence matches but we are still here
// that means this is a regular match so we should fire that
if (!processedSequenceCallback) {
self.fireCallback(callbacks[j].callback, e, callbacks[j].combo)
}
}
// if the key you pressed matches the type of sequence without
// being a modifier (ie "keyup" or "keypress") then we should
// reset all sequences that were not matched by this event
//
// this is so, for example, if you have the sequence "h a t" and you
// type "h e a r t" it does not match. in this case the "e" will
// cause the sequence to reset
//
// modifier keys are ignored because you can have a sequence
// that contains modifiers such as "enter ctrl+space" and in most
// cases the modifier key will be pressed before the next key
//
// also if you have a sequence such as "ctrl+b a" then pressing the
// "b" key will trigger a "keypress" and a "keydown"
//
// the "keydown" is expected when there is a modifier, but the
// "keypress" ends up matching the nextExpectedAction since it occurs
// after and that causes the sequence to reset
//
// we ignore keypresses in a sequence that directly follow a keydown
// for the same character
ignoreThisKeypress = e.type === 'keypress' && self.ignoreNextKeypress
isModifier = require('../../helpers/isModifier')
if (e.type === self.nextExpectedAction && !isModifier(character) && !ignoreThisKeypress) {
self.resetSequences(doNotReset)
}
self.ignoreNextKeypress = processedSequenceCallback && e.type === 'keydown'
}