UNPKG

browserkeymap

Version:

Map browser events to key names, and key names to values

176 lines (156 loc) 7.08 kB
(function(mod) { if (typeof exports == "object" && typeof module == "object") { // CommonJS module.exports = mod() module.exports.default = module.exports // ES6 modules compatibility } else if (typeof define == "function" && define.amd) // AMD return define([], mod) else // Plain browser env (this || window).browserKeymap = mod() })(function() { "use strict" var mac = typeof navigator != "undefined" ? /Mac/.test(navigator.platform) : typeof os != "undefined" ? os.platform() == "darwin" : false // :: Object<string> // A map from key codes to key names. var keyNames = { 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" } // Number keys for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i) // Alphabetic keys for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i) // Function keys for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i // :: (KeyboardEvent) → ?string // Find a name for the given keydown event. If the keycode in the // event is not known, this will return `null`. Otherwise, it will // return a string like `"Shift-Cmd-Ctrl-Alt-Home"`. The parts before // the dashes give the modifiers (always in that order, if present), // and the last word gives the key name, which one of the names in // `keyNames`. // // The convention for keypress events is to use the pressed character // between single quotes. Due to limitations in the browser API, // keypress events can not have modifiers. function keyName(event) { if (event.type == "keypress") return "'" + String.fromCharCode(event.charCode) + "'" var base = keyNames[event.keyCode], name = base if (name == null || event.altGraphKey) return null if (event.altKey && base != "Alt") name = "Alt-" + name if (event.ctrlKey && base != "Ctrl") name = "Ctrl-" + name if (event.metaKey && base != "Cmd") name = "Cmd-" + name if (event.shiftKey && base != "Shift") name = "Shift-" + name return name } // :: (string) → bool // Test whether the given key name refers to a modifier key. function isModifierKey(name) { name = /[^-]*$/.exec(name)[0] return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" } // :: (string) → string // Normalize a sloppy key name, which may have modifiers in the wrong // order or use shorthands for modifiers, to a properly formed key // name. Used to normalize names provided in keymaps. // // Note that the modifier `mod` is a shorthand for `Cmd` on Mac, and // `Ctrl` on other platforms. function normalizeKeyName(name) { var parts = name.split(/-(?!'?$)/), result = parts[parts.length - 1] var alt, ctrl, shift, cmd for (var i = 0; i < parts.length - 1; i++) { var mod = parts[i] if (/^(cmd|meta|m)$/i.test(mod)) cmd = true else if (/^a(lt)?$/i.test(mod)) alt = true else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true else if (/^s(hift)?$/i.test(mod)) shift = true else if (/^mod$/i.test(mod)) { if (mac) cmd = true; else ctrl = true } else throw new Error("Unrecognized modifier name: " + mod) } if (alt) result = "Alt-" + result if (ctrl) result = "Ctrl-" + result if (cmd) result = "Cmd-" + result if (shift) result = "Shift-" + result return result } function hasProp(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop) } function unusedMulti(bindings, name) { for (var binding in bindings) if (binding.length > name && binding.indexOf(name) == 0 && binding.charAt(name.length) == " ") return false return true } function updateBindings(bindings, base) { var result = {} if (base) for (var prop in base) if (hasProp(base, prop)) result[prop] = base[prop] for (var keyname in bindings) if (hasProp(bindings, keyname)) { var keys = keyname.split(/ +(?!\'$)/).map(normalizeKeyName) var value = bindings[keyname] if (value == null) { for (var i = keys.length - 1; i >= 0; i--) { var name = keys.slice(0, i + 1).join(" ") var old = result[name] if (old == Keymap.unfinished && !unusedMulti(result, name)) break else if (old) delete result[name] } } else { for (var i = 0; i < keys.length; i++) { var name = keys.slice(0, i + 1).join(" ") var val = i == keys.length - 1 ? value : Keymap.unfinished var prev = result[name] if (prev && (i < keys.length - 1 || prev == Keymap.unfinished) && prev != val) throw new Error("Inconsistent bindings for " + name) result[name] = val } } } return result } // :: (Object<T>) → Keymap<T> // A keymap binds a set of [key names](#keyName) to values. // // Construct a keymap using the given bindings, which should be an // object whose property names are [key names](#keyName) or // space-separated sequences of key names. In the second case, the // binding will be for a multi-stroke key combination. function Keymap(bindings, base) { this.bindings = updateBindings(bindings, base) } // :: (Object<?T>) → Keymap // Create a new keymap by adding bindings from the given object, // and removing the bindings that the object maps to null. Keymap.prototype.update = function(bindings) { return new Keymap(bindings, this.bindings) } // :: (string) → T // Looks up the given key or key sequence in this keymap. Returns // the value the key is bound to (which may be undefined if it is // not bound), or the value `Keymap.unfinished` if the key is a prefix of a // multi-key sequence that is bound by this keymap. Keymap.prototype.lookup = function(key) { return this.bindings[key] } Keymap.unfinished = {toString: function() { return "Keymap.unfinished" }} Keymap.keyName = keyName Keymap.isModifierKey = isModifierKey Keymap.normalizeKeyName = normalizeKeyName function ComputedKeymap(f) { this.f = f } ComputedKeymap.prototype.lookup = function(key, context) { return this.f(key, context) } // :: ((key: string, context: ?any) → T) → ComputedKeymap<T> // Construct a 'computed' keymap from a function which takes a key // name and returns a binding. Keymap.Computed = ComputedKeymap return Keymap })