UNPKG

@blueprintjs/core

Version:
296 lines (271 loc) 6.66 kB
/* * Copyright 2016 Palantir Technologies, Inc. All rights reserved. * * Licensed under the terms of the LICENSE file distributed with this project. */ export interface IKeyCodeTable { [code: number]: string; } export interface IKeyCodeReverseTable { [key: string]: number; } export interface IKeyMap { [key: string]: string; } export const KeyCodes: IKeyCodeTable = { 8: "backspace", 9: "tab", 13: "enter", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "left", 38: "up", 39: "right", 40: "down", 45: "ins", 46: "del", // number keys 48: "0", 49: "1", 50: "2", 51: "3", 52: "4", 53: "5", 54: "6", 55: "7", 56: "8", 57: "9", // alphabet 65: "a", 66: "b", 67: "c", 68: "d", 69: "e", 70: "f", 71: "g", 72: "h", 73: "i", 74: "j", 75: "k", 76: "l", 77: "m", 78: "n", 79: "o", 80: "p", 81: "q", 82: "r", 83: "s", 84: "t", 85: "u", 86: "v", 87: "w", 88: "x", 89: "y", 90: "z", // punctuation 106: "*", 107: "+", 109: "-", 110: ".", 111: "/", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", }; export const Modifiers: IKeyCodeTable = { 16: "shift", 17: "ctrl", 18: "alt", 91: "meta", 93: "meta", 224: "meta", }; export const ModifierBitMasks: IKeyCodeReverseTable = { alt: 1, ctrl: 2, meta: 4, shift: 8, }; export const Aliases: IKeyMap = { cmd: "meta", command: "meta", escape: "esc", minus: "-", mod: isMac() ? "meta" : "ctrl", option: "alt", plus: "+", return: "enter", win: "meta", }; // alph sorting is unintuitive here // tslint:disable object-literal-sort-keys export const ShiftKeys: IKeyMap = { "~": "`", "!": "1", "@": "2", "#": "3", $: "4", "%": "5", "^": "6", "&": "7", "*": "8", "(": "9", ")": "0", _: "-", "+": "=", "{": "[", "}": "]", "|": "\\", ":": ";", '"': "'", "<": ",", ">": ".", "?": "/", }; // tslint:enable object-literal-sort-keys // Function keys for (let i = 1; i <= 12; ++i) { KeyCodes[111 + i] = "f" + i; } // Numpad for (let i = 0; i <= 9; ++i) { KeyCodes[96 + i] = "num" + i.toString(); } export interface IKeyCombo { key?: string; modifiers: number; } export function comboMatches(a: IKeyCombo, b: IKeyCombo) { return a.modifiers === b.modifiers && a.key === b.key; } /** * Converts a key combo string into a key combo object. Key combos include * zero or more modifier keys, such as `shift` or `alt`, and exactly one * action key, such as `A`, `enter`, or `left`. * * For action keys that require a shift, e.g. `@` or `|`, we inlude the * necessary `shift` modifier and automatically convert the action key to the * unshifted version. For example, `@` is equivalent to `shift+2`. */ export const parseKeyCombo = (combo: string): IKeyCombo => { const pieces = combo .replace(/\s/g, "") .toLowerCase() .split("+"); let modifiers = 0; let key = null as string; for (let piece of pieces) { if (piece === "") { throw new Error(`Failed to parse key combo "${combo}". Valid key combos look like "cmd + plus", "shift+p", or "!"`); } if (Aliases[piece] != null) { piece = Aliases[piece]; } if (ModifierBitMasks[piece] != null) { modifiers += ModifierBitMasks[piece]; } else if (ShiftKeys[piece] != null) { // tslint:disable-next-line no-string-literal modifiers += ModifierBitMasks["shift"]; key = ShiftKeys[piece]; } else { key = piece.toLowerCase(); } } return { modifiers, key }; }; /** * Converts a keyboard event into a valid combo prop string */ export const getKeyComboString = (e: KeyboardEvent): string => { const keys = [] as string[]; // modifiers first if (e.ctrlKey) { keys.push("ctrl"); } if (e.altKey) { keys.push("alt"); } if (e.shiftKey) { keys.push("shift"); } if (e.metaKey) { keys.push("meta"); } const { which } = e; if (Modifiers[which] != null) { // no action key } else if (KeyCodes[which] != null) { keys.push(KeyCodes[which]); } else { keys.push(String.fromCharCode(which).toLowerCase()); } // join keys with plusses return keys.join(" + "); }; /** * Determines the key combo object from the given keyboard event. Again, a key * combo includes zero or more modifiers (represented by a bitmask) and one * action key, which we determine from the `e.which` property of the keyboard * event. */ export const getKeyCombo = (e: KeyboardEvent): IKeyCombo => { let key = null as string; const { which } = e; if (Modifiers[which] != null) { // keep key null } else if (KeyCodes[which] != null) { key = KeyCodes[which]; } else { key = String.fromCharCode(which).toLowerCase(); } let modifiers = 0; // tslint:disable no-string-literal if (e.altKey) { modifiers += ModifierBitMasks["alt"]; } if (e.ctrlKey) { modifiers += ModifierBitMasks["ctrl"]; } if (e.metaKey) { modifiers += ModifierBitMasks["meta"]; } if (e.shiftKey) { modifiers += ModifierBitMasks["shift"]; } // tslint:enable return { modifiers, key }; }; /** * Splits a key combo string into its constituent key values and looks up * aliases, such as `return` -> `enter`. * * Unlike the parseKeyCombo method, this method does NOT convert shifted * action keys. So `"@"` will NOT be converted to `["shift", "2"]`). */ export const normalizeKeyCombo = (combo: string, platformOverride?: string): string[] => { const keys = combo.replace(/\s/g, "").split("+"); return keys.map(key => { const keyName = Aliases[key] != null ? Aliases[key] : key; return keyName === "meta" ? (isMac(platformOverride) ? "cmd" : "ctrl") : keyName; }); }; /* tslint:enable:no-string-literal */ function isMac(platformOverride?: string) { const platform = platformOverride != null ? platformOverride : typeof navigator !== "undefined" ? navigator.platform : undefined; return platform == null ? false : /Mac|iPod|iPhone|iPad/.test(platform); }