@blueprintjs/core
Version:
Core styles & components
280 lines (254 loc) • 6.79 kB
text/typescript
/*
* Copyright 2016 Palantir Technologies, Inc. All rights reserved.
* Licensed under the BSD-3 License as modified (the “License”); you may obtain a copy
* of the license at https://github.com/palantir/blueprint/blob/master/LICENSE
* and https://github.com/palantir/blueprint/blob/master/PATENTS
*/
export interface IKeyCodeTable {
[code: number]: string;
}
export interface IKeyCodeReverseTable {
[key: string]: number;
}
export interface IKeyMap {
[key: string]: string;
}
export const KeyCodes = {
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: "\'",
} as IKeyCodeTable;
export const Modifiers = {
16: "shift",
17: "ctrl",
18: "alt",
91: "meta",
93: "meta",
224: "meta",
} as IKeyCodeTable;
export const ModifierBitMasks = {
alt: 1,
ctrl: 2,
meta: 4,
shift: 8,
} as IKeyCodeReverseTable;
export const Aliases = {
cmd: "meta",
command: "meta",
escape: "esc",
minus: "-",
mod: ((typeof navigator !== "undefined") && /Mac|iPod|iPhone|iPad/.test(navigator.platform)) ? "meta" : "ctrl",
option: "alt",
plus: "+",
return: "enter",
win: "meta",
} as IKeyMap;
// alph sorting is unintuitive here
// tslint:disable object-literal-sort-keys
export const ShiftKeys = {
"~": "`",
"!": "1",
"@": "2",
"#": "3",
"$": "4",
"%": "5",
"^": "6",
"&": "7",
"*": "8",
"(": "9",
")": "0",
"_": "-",
"+": "=",
"{": "[",
"}": "]",
"|": "\\",
":": ";",
"\"": "\'",
"<": ",",
">": ".",
"?": "/",
} as IKeyMap;
// tslint:enable object-literal-sort-keys
/* tslint:enable:object-literal-key-quotes */
// 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 };
};
/**
* PhantomJS's webkit totally messes up keyboard events, so we have do this
* fancy little dance with the event data to determine which key was pressed
* for unit tests.
*/
const normalizeKeyCode = (e: KeyboardEvent) => {
return (e.which === 0 && e.key != null) ? e.key.charCodeAt(0) : e.which;
};
/**
* 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 = normalizeKeyCode(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 = normalizeKeyCode(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): string[] => {
const keys = combo.replace(/\s/g, "").split("+");
return keys.map((key) => Aliases[key] != null ? Aliases[key] : key);
};
/* tslint:enable:no-string-literal */