/**
* @module montage/core/event/key-manager
* @requires montage/core/core
*/
var Montage = require("../core").Montage,
defaultEventManager = require("./event-manager").defaultEventManager,
MutableEvent = require("./mutable-event").MutableEvent;
var KEYNAMES_TO_KEYCODES = {
// W3C Key Code
backspace: 8,
tab: 9,
enter: 13,
shift: 16,
control: 17,
alt: 18,
capslock: 20,
escape: 27,
space: 32,
pageup: 33,
pagedown: 34,
end: 35,
home: 36,
left: 37,
up: 38,
right: 39,
down: 40,
delete: 46,
//https://www.w3.org/TR/uievents-key/
arrowup: 38,
arrowright: 39,
arrowdown: 40,
arrowdelete: 46,
// W3C Optional Key Code (mostly for US keyboard layout)
semicolon: 186,
colon: 186,
equal: 187,
plus: 187,
comma: 188,
less: 188,
minus: 189,
underscore: 189,
period: 190,
greater: 190,
slash: 191,
questionmark: 191,
backtick: 192,
tilde: 192,
openingsquarebracket: 219,
openingcurlybracket: 219,
backslash: 220,
pipe: 220,
closingsquarebracket: 221,
closingcurlybracket: 221,
singlequote: 222,
doublequote: 222,
// Non standard Virtal key code but commonly used
clear: 12,
meta: 91,
contextmenu: 93,
numpad0: 96,
numpad1: 97,
numpad2: 98,
numpad3: 99,
numpad4: 100,
numpad5: 101,
numpad6: 102,
numpad7: 103,
numpad8: 104,
numpad9: 105,
multiply: 106,
add: 107,
subtract: 109,
decimal: 110,
divide: 111,
f1: 112,
f2: 113,
f3: 114,
f4: 115,
f5: 116,
f6: 117,
f7: 118,
f8: 119,
f9: 120,
f10: 121,
f11: 122,
f12: 123,
f13: 124,
f14: 125,
f15: 126,
f16: 127,
f17: 128,
f18: 129,
f19: 130,
f20: 131,
f21: 132,
f22: 133,
f23: 134,
f24: 135
},
KEYCODES_TO_KEYNAMES = null, // Will be build from the KEYNAMES_TO_KEYCODES dictionary
OPERAMAC_KEYNAMES_TO_KEYCODES = {
meta: 17,
control: 57392,
f17: 63252,
f18: 63253,
f19: 63254,
f20: 63255,
f21: 63256,
f22: 63257,
f23: 63258,
f24: 63259
},
FIREFOX_KEYNAMES_TO_KEYCODES = {
f13: 44,
f14: 145,
f15: 19,
f16: 63251,
f17: 63252,
f18: 63253,
f19: 63254,
f20: 63255,
f21: 63256,
f22: 63257,
f23: 63258,
f24: 63259,
meta: 224
},
KEYNAMES_ALIASES = {
cmd: "command",
ctl: "control",
ctrl: "control",
win: "meta",
windows: "meta",
opt: "alt",
option: "alt"
},
MODIFIERS = {
meta: {name:"meta", value:1},
alt: {name:"alt", value:2},
control: {name:"control", value:4},
shift: {name:"shift", value:8}
},
NORMALIZED_KEYS = {
semicolon: ";",
colon: ":",
equal: "=",
plus: "+",
comma: ",",
less: "<",
minus: "-",
underscore: "_",
period: ".",
greater: ">",
slash: "/",
questionmark: "?",
backtick: "`",
tilde: "~",
openingsquarebracket: "[",
openingcurlybracket: "{",
backslash: "\\",
pipe: "|",
closingsquarebracket: "]",
closingcurlybracket: "}",
singlequote: "'",
doublequote: "\"",
multiply: "*",
add: "+",
subtract: "-",
decimal: ".",
divide: "/"
},
NORMALIZED_CHARS = null; // Will be generated from the NORMALIZED_KEYS
/* Event type dispatched by KeyComposer */
var KEYPRESS_EVENT_TYPE = "keyPress",
LONGKEYPRESS_EVENT_TYPE = "longKeyPress",
KEYRELEASE_EVENT_TYPE = "keyRelease";
var defaultKeyManager = null;
/**
* The KeyManager dispatches KeyComposer events when it detects a keyComposer
* has been pressed or released. Do not create a KeyManager directly but
* instead require for the defaultKeyManager:
*
* ```js
* require("./key-manager").defaultKeyManager
* ```
*
* @class KeyManager
* @classdesc Dispatches events to a key composer. There can only be one.
* @extends Montage
*/
var KeyManager = exports.KeyManager = Montage.specialize(/** @lends KeyManager# */ {
/**
@private
*/
_keyEventsListenerInstalled: {
value: false
},
/**
@private
*/
_composerKeyMap: {
value: {}
},
/**
@private
*/
_triggeredKeys: {
value: {}
},
/**
@private
*/
_longPressKeys: {
value: {}
},
/**
@private
*/
_cleanupTimeout: {
value: null
},
/**
@private
*/
_cleanupThreshold: {
value: 2000
},
/**
@private
*/
_longPressThreshold: {
value: 1000
},
/**
* The number of milliseconds a key must be pressed in order to dispatch a
* longKeyPress event.
* @type {number}
* @default 1000
*/
longPressThreshold: {
get: function () {
return this._longPressThreshold;
},
set: function (value) {
if (value > 0 && value !== this._longPressThreshold) {
this._longPressThreshold = value;
if (this._longPressThreshold > this._cleanupThreshold - 100) {
this._cleanupThreshold = this._longPressThreshold + 100;
} else {
this._cleanupThreshold = 2000;
}
}
}
},
/**
* Register a composerKey.
* @function
* @param {Object} keyComposer. The key to register.
*/
registerKey: {
value: function (keyComposer) {
// validates the keys:
var normalizedKeys = this._normalizeKeySequence(keyComposer.keys),
modifiersAndKey,
map = this._composerKeyMap,
mapModifiersEntry,
mapKeyEntry,
keyAlreadyRegistered = false,
entry,
i;
if (normalizedKeys) {
modifiersAndKey = this._convertKeysToModifiersAndKeyCode(normalizedKeys);
mapModifiersEntry = map[modifiersAndKey.modifiers];
if (!mapModifiersEntry) {
mapModifiersEntry = map[modifiersAndKey.modifiers] = {};
}
mapKeyEntry = mapModifiersEntry[modifiersAndKey.keyCode];
if (mapKeyEntry) {
for (i in mapKeyEntry) {
if (mapKeyEntry.hasOwnProperty(i)) {
entry = mapKeyEntry[i];
if (entry.object === keyComposer) {
entry.refCount ++;
keyAlreadyRegistered = true;
break;
}
}
}
if (!keyAlreadyRegistered) {
mapKeyEntry.push({object: keyComposer, refCount: 1});
}
} else {
mapModifiersEntry[modifiersAndKey.keyCode] = [{object: keyComposer, refCount: 1}];
}
keyComposer._modifiers = modifiersAndKey.modifiers;
keyComposer._keyCode = modifiersAndKey.keyCode;
this._registerListeners();
}
}
},
/**
* Unregister a composerKey. if a key has been registered multiple time,
* unregister must be called the same amount of time before the key is
* actually unregistered.
* @function
* @param {Object} keyComposer The key to unregister.
*/
unregisterKey: {
value: function (keyComposer) {
var map = this._composerKeyMap,
mapModifiersEntry,
mapKeyEntry,
entry,
i;
mapModifiersEntry = map[keyComposer._modifiers];
if (mapModifiersEntry) {
mapKeyEntry = mapModifiersEntry[keyComposer._keyCode];
for (i in mapKeyEntry) {
if (mapKeyEntry.hasOwnProperty(i)) {
entry = mapKeyEntry[i];
if (entry.object === keyComposer) {
entry.refCount --;
if (entry.refCount <= 0) {
mapKeyEntry.splice(i, 1);
if (mapKeyEntry.length === 0) {
delete mapModifiersEntry[keyComposer._keyCode];
if (Object.keys(mapModifiersEntry).length === 0) {
delete map[keyComposer._modifiers];
if (Object.keys(map).length === 0) {
this._unregisterListeners();
}
}
}
}
}
}
}
}
}
},
constructor: {
value: function () {
var userAgent = global.navigator ? global.navigator.userAgent : "",
code;
if (defaultKeyManager) {
console.warn("Rather than creating a new KeyManager object, you might want to use the default key manager");
}
// Browser detection
if (userAgent.match(/ firefox\//i)) {
this._firefox = true;
} else if (userAgent.match(/ appleWebkit\//i)) {
this._webkit = true;
if (userAgent.match(/ chrome\//i)) {
this._chrome = true;
}
} else if (userAgent.match(/^opera\//i)) {
this._opera = true;
}
// Platform Detection
if (userAgent.match(/macintosh/i)) {
this._mac = true;
if (this._opera) {
this._operaMac = true;
}
}
// Handle the platform specific COMMAND modifier
if (this._mac) {
MODIFIERS.command = MODIFIERS.meta;
} else {
MODIFIERS.command = MODIFIERS.control;
}
// Patch the KeyCode dictionary for Opera or Firefox
if (this._operaMac) {
for (code in OPERAMAC_KEYNAMES_TO_KEYCODES) {
if (OPERAMAC_KEYNAMES_TO_KEYCODES.hasOwnProperty(code)) {
KEYNAMES_TO_KEYCODES[code] = OPERAMAC_KEYNAMES_TO_KEYCODES[code];
}
}
}
if (this._firefox) {
for (code in FIREFOX_KEYNAMES_TO_KEYCODES) {
if (FIREFOX_KEYNAMES_TO_KEYCODES.hasOwnProperty(code)) {
KEYNAMES_TO_KEYCODES[code] = FIREFOX_KEYNAMES_TO_KEYCODES[code];
}
}
}
var keyName;
// Generate the KEYNAMES dictionary
KEYCODES_TO_KEYNAMES = {};
for (keyName in KEYNAMES_TO_KEYCODES) {
if (KEYNAMES_TO_KEYCODES.hasOwnProperty(keyName)) {
code = KEYNAMES_TO_KEYCODES[keyName];
if (KEYCODES_TO_KEYNAMES[code] === undefined) {
// If we have more than one name for a keycode, use only the first one
KEYCODES_TO_KEYNAMES[code] = keyName;
}
}
}
// Generate the NORMALIZED_CHARS dictionary
NORMALIZED_CHARS = {};
for (keyName in NORMALIZED_KEYS) {
if (NORMALIZED_KEYS.hasOwnProperty(keyName)) {
code = NORMALIZED_KEYS[keyName];
if (NORMALIZED_CHARS[code] === undefined) {
// If we have more than one name for a char, use only the first one
NORMALIZED_CHARS[code] = keyName;
}
}
}
this._shiftKey = false;
this._altKey = false;
this._metaKey = false;
this._ctrlKey = false;
}
},
captureKeydown: {
value: function (event) {
var keyCode,
identifierCode,
keyIdentifier = event.key || event.keyIdentifier,
submap,
stopped = false;
// console.log(" captureKeydown:", event.keyCode, event.charCode, event.keyIdentifier);
this._preprocessKeyEvent(event);
// console.log("KEYDOWN MODIFIERS:", this._modifiers, "KEYCODE:", this._keyCode);
submap = this._submap;
if (submap) {
keyCode = this._keyCode;
// Check the keyCode for a match...
if (keyCode && submap[keyCode]) {
stopped = this._dispatchComposerKeyMatches(submap[keyCode], event);
}
// Check the keyIdentifier for a match
if (!stopped && keyIdentifier) {
identifierCode = KEYNAMES_TO_KEYCODES[keyIdentifier.toLowerCase()] ||
this._decodeKeyIdentifier(keyIdentifier);
if (identifierCode && identifierCode !== keyCode && submap[identifierCode]) {
this._dispatchComposerKeyMatches(submap[identifierCode], event);
}
}
}
this._setupCleanupTimer();
}
},
captureKeypress: {
value: function (event) {
var charCode = event.charCode,
keyIdentifier = event.key || event.keyIdentifier,
keyCode,
identifierCode,
submap,
stopped = false;
// console.log(" captureKeypress:", event.keyCode, event.charCode, event.keyIdentifier, this._modifiers);
this._preprocessKeyEvent(event);
submap = this._submap;
// console.log("KEYPRESS MODIFIERS:", this._modifiers, this._keyCode, event.charCode);
if (submap) {
keyCode = this._keyCode;
// Check the keyCode for a match...
if (keyCode && submap[keyCode]) {
stopped = this._dispatchComposerKeyMatches(submap[keyCode], event);
}
// Check the charCode for a match...
if (!stopped && charCode && charCode !== keyCode && submap[charCode]) {
stopped = this._dispatchComposerKeyMatches(submap[charCode], event);
}
// Check the keyIdentifier for a match
if (!stopped && keyIdentifier) {
identifierCode = KEYNAMES_TO_KEYCODES[keyIdentifier.toLowerCase()] ||
this._decodeKeyIdentifier(keyIdentifier);
if (identifierCode && identifierCode !== keyCode && submap[identifierCode]) {
this._dispatchComposerKeyMatches(submap[identifierCode], event);
}
}
}
this._setupCleanupTimer();
}
},
captureKeyup: {
value: function (event) {
var keyCode = event.keyCode,
keyIdentifier = event.key || event.keyIdentifier,
identifierCode,
submap,
dispatchedKeyCode = 0,
triggeredKey,
uuid,
stopped = false;
// Dispatch a keyup event for all composerKey triggered during the keydown/keypress phase
// and for the current match
// console.log(" captureKeyup:", event.keyCode, event.charCode, event.keyIdentifier);
this._preprocessKeyEvent(event);
submap = this._submap;
// console.log("KEYUP MODIFIERS:", this._modifiers, this._keyCode);
if (submap) {
keyCode = this._keyCode;
// Check the keyCode for a match...
if (keyCode && submap[keyCode]) {
stopped = this._dispatchComposerKeyMatches(submap[keyCode], event);
dispatchedKeyCode = keyCode;
}
// Check the keyIdentifier for a match
if (!stopped && keyIdentifier) {
identifierCode = KEYNAMES_TO_KEYCODES[keyIdentifier.toLowerCase()] ||
this._decodeKeyIdentifier(keyIdentifier);
if (identifierCode && identifierCode !== dispatchedKeyCode && submap[identifierCode]) {
stopped = this._dispatchComposerKeyMatches(submap[identifierCode], event);
}
}
}
// In case the user release the modifier key before releasing the main key, we still need to fire a keyup event
if (!stopped) {
for (uuid in this._triggeredKeys) {
if (this._triggeredKeys.hasOwnProperty(uuid)) {
triggeredKey = this._triggeredKeys[uuid];
if (triggeredKey._keyCode === keyCode || triggeredKey._keyCode === identifierCode) {
this._dispatchComposerKeyMatches([triggeredKey], event);
}
}
}
}
this._cleanup();
}
},
_normalizeKeySequence: {
value: function (keySequence) {
var modifiersOrder = [MODIFIERS.meta.name, MODIFIERS.alt.name, MODIFIERS.control.name, MODIFIERS.shift.name],
keys = keySequence.toLowerCase().replace(/ /g, "").replace(/\+\+/g, "+add").split("+"),
nbrKeys = keys.length,
key,
normalizedKeys = [],
i;
for (i = 0; i < nbrKeys - 1; i ++) {
// convert alias
key = keys[i];
key = KEYNAMES_ALIASES[key] || key;
// make sure it's a modifier
key = MODIFIERS[key];
if (key) {
normalizedKeys.push(key.name);
} else {
console.warn("Invalid key sequence (modifier):", keySequence);
return null;
}
}
normalizedKeys.sort(function (a, b) {
return modifiersOrder.indexOf(a) - modifiersOrder.indexOf(b);
});
key = keys[nbrKeys - 1];
if (key.length > 1 && !KEYNAMES_TO_KEYCODES[key]) {
console.warn("Invalid key sequence (key):", keySequence, key);
return null;
}
if (normalizedKeys.length) {
return normalizedKeys.join("+") + "+" + key;
} else {
return key;
}
}
},
_preprocessKeyEvent: {
value: function (event) {
var thisRef = this,
eventType = event.type,
keyCode = event.keyCode,
keyIdentifier = event.key || event.keyIdentifier,
modifiers,
value;
if (this._operaMac) {
if (this._operaModifierTimeout) {
clearTimeout(this._operaModifierTimeout);
this._operaModifierTimeout = null;
}
}
if (eventType === "keydown" || eventType === "keyup") {
if (this._operaMac) {
// Opera Mac is not very good at keeping track of which modifiers are pressed or not!
value = (eventType === "keydown");
if (keyCode === KEYNAMES_TO_KEYCODES.shift) {
this._shiftKey = value;
} else if (keyCode === KEYNAMES_TO_KEYCODES.alt) {
this._altKey = value;
} else if (keyCode === KEYNAMES_TO_KEYCODES.meta) {
if (this._mac) {
this._metaKey = value;
}
} else if (keyCode === KEYNAMES_TO_KEYCODES.control) {
this._ctrlKey = value;
}
if (eventType === "keyup") {
// Setup a timeout to force reset the modifier state ~3 seconds after the last key up
// This is to recover when we miss a keyup event which seems to occurs once in a while with Opera
this._operaModifierTimeout = setTimeout(function (){
thisRef._shiftKey = false;
thisRef._altKey = false;
thisRef._metaKey = false;
thisRef._ctrlKey = false;
thisRef._operaModifierTimeout = null;
}, this._cleanupThreshold + 1000);
}
} else {
this._shiftKey = event.shiftKey;
this._altKey = event.altKey;
this._metaKey = event.metaKey;
this._ctrlKey = event.ctrlKey;
}
}
if (this._mac && this._webkit && keyCode === KEYNAMES_TO_KEYCODES.contextmenu) {
// Webkit browsers will interpret the right command key as the window context-menu but will keep the
// meta modifier on preventing us from having a "context-menu" shortcut. We need to clear the meta flag
// (the limitation is that we wont be able to support a "META+CONTEXT-MENU" shortcut
this._metaKey = false;
}
if (this._chrome) {
// Chrome (at least on Mac) generate the same keycode for the NumKeyPad = and NumKeyPad +
if (!this._shiftKey && keyCode === KEYNAMES_TO_KEYCODES.plus && (keyIdentifier === "U+002B" || keyIdentifier === "+")) {
event.keyCode = KEYNAMES_TO_KEYCODES.add;
}
}
this._modifiers = modifiers = (this._shiftKey ? MODIFIERS.shift.value : 0) +
(this._altKey ? MODIFIERS.alt.value : 0) +
(this._metaKey ? MODIFIERS.meta.value : 0) +
(this._ctrlKey ? MODIFIERS.control.value : 0);
this._submap = this._composerKeyMap[modifiers];
this._keyCode = event.keyCode;
this._keyIdentifier = keyIdentifier;
}
},
_registerListeners: {
value: function () {
if (!this._keyEventsListenerInstalled) {
window.addEventListener("keydown", this, true);
window.addEventListener("keypress", this, true);
window.addEventListener("keyup", this, true);
this._keyEventsListenerInstalled = true;
}
}
},
_unregisterListeners: {
value: function () {
if (this._keyEventsListenerInstalled) {
window.removeEventListener("keydown", this, true);
window.removeEventListener("keypress", this, true);
window.removeEventListener("keyup", this, true);
this._keyEventsListenerInstalled = false;
}
}
},
_dispatchComposerKeyMatches: {
value: function (matches, event) {
var thisRef = this,
stopped = false,
keyUp = event.type === "keyup",
keyDown = event.type === "keydown",
eventType = keyUp ? KEYRELEASE_EVENT_TYPE : KEYPRESS_EVENT_TYPE,
nbrMatches = matches.length,
keyComposer,
keyComposerEvent,
triggeredKeys,
i;
// Slice the array so that keys registered or unregistered during
// dispatch don't break the dispatching
matches = matches.slice();
// matches could be either an array of matches or an array of keyComposers
for (i = 0; i < nbrMatches && !stopped; i ++) {
keyComposer = matches[i].object || matches[i];
// Make sure keyboard event's target is a descendant of the keyComposer's element
var target = event.target,
element = keyComposer.element,
onTarget = (keyComposer.element === window);
while (!onTarget) {
onTarget = (target === element);
if (target === document) {
break;
} else {
target = target.parentElement;
if (!target) {
target = document;
}
}
}
// Most components can't receive key events directly: the events target the window,
// but we should also fire them on composers of the activeTarget component
if (!onTarget && defaultEventManager.activeTarget !== keyComposer.component) {
continue;
}
if (keyUp) {
triggeredKeys = Object.keys(this._triggeredKeys);
if (triggeredKeys.indexOf(keyComposer.uuid) === -1) {
// Do not generate a keyup event if the composerKey has not been triggered on keydown or keypress
continue;
}
if (keyComposer._longPressTimeout) {
clearTimeout(keyComposer._longPressTimeout);
keyComposer._longPressTimeout = null;
delete this._longPressKeys[keyComposer.uuid];
}
} else {
if (keyDown) {
// Reset trigger
delete this._triggeredKeys[keyComposer.uuid];
} else if (this._triggeredKeys[keyComposer.uuid]) {
// that key has already been triggered, let's ignore it...
continue;
}
if (keyComposer._shouldDispatchLongPress && !keyComposer._longPressTimeout) {
/* jshint loopfunc:true */
keyComposer._longPressTimeout = setTimeout(function () {
var longPressEvent = MutableEvent.fromEvent(longPressEvent);
longPressEvent.type = LONGKEYPRESS_EVENT_TYPE;
longPressEvent.activeElement = event.target;
longPressEvent.identifier = keyComposer.identifier;
keyComposer._longPressTimeout = null;
keyComposer.dispatchEvent(longPressEvent);
delete thisRef._longPressKeys[keyComposer.uuid];
}, this._longPressThreshold);
/* jshint loopfunc:false */
// Let's remember any longKeyPress key waiting for timeout
this._longPressKeys[keyComposer.uuid] = keyComposer;
}
}
keyComposerEvent = MutableEvent.fromEvent(event);
keyComposerEvent.type = eventType;
keyComposerEvent.activeElement = event.target;
keyComposerEvent.identifier = keyComposer.identifier;
keyComposerEvent.keyComposer = keyComposer;
keyComposer.dispatchEvent(keyComposerEvent);
if (keyComposerEvent.propagationStopped) {
stopped = true;
}
if (keyUp) {
// We already dispatched a keyup event for this key, let's remove it
delete this._triggeredKeys[keyComposer.uuid];
} else {
// We need to remember any composerKey triggered during the keydown phase in order to generate an equivalent keyup
this._triggeredKeys[keyComposer.uuid] = keyComposer;
}
}
// if the composer key even has been stopped, we need clean the list of triggered keys as we wont get a keyup event anymore.
// We need also stop pending longPress key
if (stopped) {
for (i = (keyUp ? i : 0); i < nbrMatches; i ++) {
keyComposer = matches[i].object || matches[i];
delete this._triggeredKeys[keyComposer.uuid];
if (keyComposer._longPressTimeout) {
clearTimeout(keyComposer._longPressTimeout);
keyComposer._longPressTimeout = null;
delete this._longPressKeys[keyComposer.uuid];
}
}
}
return stopped;
}
},
_cleanup: {
value: function () {
var keyComposer,
i;
if (this._cleanupTimeout) {
clearTimeout(this._cleanupTimeout);
}
for (i in this._triggeredKeys) {
if (this._triggeredKeys.hasOwnProperty(i)) {
delete this._triggeredKeys[i];
}
}
for (i in this._longPressKeys) {
if (this._longPressKeys.hasOwnProperty(i)) {
keyComposer = this._longPressKeys[i];
clearTimeout(keyComposer._longPressTimeout);
keyComposer._longPressTimeout = null;
delete this._longPressKeys[i];
}
}
this._cleanupTimeout = null;
}
},
_setupCleanupTimer: {
value: function () {
var thisRef = this;
// When a keydown event is stopped, we will not received the corresponding keyup event.
// If we haven't detected any key events for 2 seconds, let's presume the user is done using the keyboard
// and do some internal cleanup in case we missed some keyup events.
if (this._cleanupTimeout) {
clearTimeout(this._cleanupTimeout);
}
this._cleanupTimeout = setTimeout(function () {
thisRef._cleanup();
}, this._cleanupThreshold);
}
},
_convertKeysToModifiersAndKeyCode: {
value: function (keys) {
var nbrKeys,
key,
i,
keyCode = 0,
modifiers = 0;
keys = keys.toLowerCase().replace(/ /g, "").replace(/\+\+/g, "+add").split("+");
nbrKeys = keys.length;
// Convert the keys into a modifiers mask
for (i = 0; i < nbrKeys - 1; i ++) {
// convert alias
key = keys[i];
key = KEYNAMES_ALIASES[key] || key;
// make sure it's a modifier
key = MODIFIERS[key];
if (key) {
modifiers |= key.value;
} else {
console.warn("Invalid Key sequence:", keys);
return null;
}
}
// Extract the final key
key = keys[nbrKeys - 1];
key = NORMALIZED_CHARS[key] || key;
key = NORMALIZED_KEYS[key] || key; // This is needed for browsers that don't use W3C Optional Key Codes
if (key.length > 1) {
keyCode = KEYNAMES_TO_KEYCODES[key];
// If the key append to be a modifier, we need adjust modifiers accordingly
key = MODIFIERS[KEYNAMES_ALIASES[key] || key];
if (key) {
modifiers |= key.value;
}
} else {
keyCode = key.toUpperCase().charCodeAt(0);
}
return {modifiers: modifiers, keyCode: keyCode};
}
},
_decodeKeyIdentifier: {
value: function (identifier) {
if (identifier.match(/U\+/)) {
return parseInt(identifier.substring(2), 16);
}
}
}
});
Montage.defineProperty(exports, "defaultKeyManager", {
get: function () {
if (!defaultKeyManager) {
defaultKeyManager = new KeyManager();
}
return defaultKeyManager;
}
});