@blueprintjs/core
Version:
Core styles & components
215 lines • 7.12 kB
JavaScript
/*
* Copyright 2016 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Named modifier keys
*
* @see https://www.w3.org/TR/uievents-key/#keys-modifier
*/
const MODIFIER_KEYS = new Set(["Shift", "Control", "Alt", "Meta"]);
export const MODIFIER_BIT_MASKS = {
alt: 1,
ctrl: 2,
meta: 4,
shift: 8,
};
export const CONFIG_ALIASES = {
cmd: "meta",
command: "meta",
escape: "escape",
minus: "-",
mod: isMac() ? "meta" : "ctrl",
option: "alt",
plus: "+",
return: "enter",
win: "meta",
// need these aliases for backwards-compatibility (but they're also convenient)
up: "ArrowUp",
left: "ArrowLeft",
down: "ArrowDown",
right: "ArrowRight",
};
/**
* Key mapping used in getKeyCombo() implementation for physical keys which are not alphabet characters or digits.
*
* N.B. at some point, we should stop using this mapping, since we can implement the desired functionality in a more
* straightforward way by using the `event.code` property, which will always tell us the identifiers represented by the
* _values_ in this object (the default physical keys, unaltered by modifier keys or keyboard layout).
*/
export const SHIFT_KEYS = {
"~": "`",
_: "-",
"+": "=",
"{": "[",
"}": "]",
"|": "\\",
":": ";",
'"': "'",
"<": ",",
">": ".",
"?": "/",
};
export function comboMatches(a, b) {
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) => {
const pieces = combo.replace(/\s/g, "").toLowerCase().split("+");
let modifiers = 0;
let key;
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 (CONFIG_ALIASES[piece] !== undefined) {
piece = CONFIG_ALIASES[piece];
}
if (MODIFIER_BIT_MASKS[piece] !== undefined) {
modifiers += MODIFIER_BIT_MASKS[piece];
}
else if (SHIFT_KEYS[piece] !== undefined) {
modifiers += MODIFIER_BIT_MASKS.shift;
key = SHIFT_KEYS[piece];
}
else {
key = piece.toLowerCase();
}
}
return { modifiers, key };
};
/**
* Interprets a keyboard event as a valid KeyComboTag `combo` prop string value.
*
* Note that this function is only used in the docs example and tests; it is not used by `useHotkeys()` or any
* Blueprint consumers that we are currently aware of.
*/
export const getKeyComboString = (e) => {
const comboParts = [];
// modifiers first
if (e.ctrlKey) {
comboParts.push("ctrl");
}
if (e.altKey) {
comboParts.push("alt");
}
if (e.shiftKey) {
comboParts.push("shift");
}
if (e.metaKey) {
comboParts.push("meta");
}
const key = maybeGetKeyFromEventCode(e);
if (key !== undefined) {
comboParts.push(key);
}
else {
if (e.code === "Space") {
comboParts.push("space");
}
else if (MODIFIER_KEYS.has(e.key)) {
// do nothing
}
else if (e.shiftKey && SHIFT_KEYS[e.key] !== undefined) {
comboParts.push(SHIFT_KEYS[e.key]);
}
else if (e.key !== undefined) {
comboParts.push(e.key.toLowerCase());
}
}
return comboParts.join(" + ");
};
const KEY_CODE_PREFIX = "Key";
const DIGIT_CODE_PREFIX = "Digit";
function maybeGetKeyFromEventCode(e) {
if (e.code == null) {
return undefined;
}
if (e.code.startsWith(KEY_CODE_PREFIX)) {
// Code looks like "KeyA", etc.
return e.code.substring(KEY_CODE_PREFIX.length).toLowerCase();
}
else if (e.code.startsWith(DIGIT_CODE_PREFIX)) {
// Code looks like "Digit1", etc.
return e.code.substring(DIGIT_CODE_PREFIX.length).toLowerCase();
}
else if (e.code === "Space") {
return "space";
}
return undefined;
}
/**
* Determines the key combo object from the given keyboard event. A key combo includes zero or more modifiers
* (represented by a bitmask) and one physical key. For most keys, we prefer dealing with the `code` property of the
* event, since this is not altered by keyboard layout or the state of modifier keys. Fall back to using the `key`
* property.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
*/
export const getKeyCombo = (e) => {
let key;
if (MODIFIER_KEYS.has(e.key)) {
// Leave local variable `key` undefined
}
else {
key = maybeGetKeyFromEventCode(e) ?? e.key?.toLowerCase();
}
let modifiers = 0;
if (e.altKey) {
modifiers += MODIFIER_BIT_MASKS.alt;
}
if (e.ctrlKey) {
modifiers += MODIFIER_BIT_MASKS.ctrl;
}
if (e.metaKey) {
modifiers += MODIFIER_BIT_MASKS.meta;
}
if (e.shiftKey) {
modifiers += MODIFIER_BIT_MASKS.shift;
if (SHIFT_KEYS[e.key] !== undefined) {
key = SHIFT_KEYS[e.key];
}
}
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, platformOverride) => {
const keys = combo.replace(/\s/g, "").split("+");
return keys.map(key => {
const keyName = CONFIG_ALIASES[key] != null ? CONFIG_ALIASES[key] : key;
return keyName === "meta" ? (isMac(platformOverride) ? "cmd" : "ctrl") : keyName;
});
};
function isMac(platformOverride) {
// HACKHACK: see https://github.com/palantir/blueprint/issues/5174
// eslint-disable-next-line deprecation/deprecation
const platform = platformOverride ?? (typeof navigator !== "undefined" ? navigator.platform : undefined);
return platform === undefined ? false : /Mac|iPod|iPhone|iPad/.test(platform);
}
//# sourceMappingURL=hotkeyParser.js.map