keyboard.ts•3.82 kB
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type {KeyInput} from '../third_party/index.js';
// See the KeyInput type for the list of supported keys.
const validKeys = new Set([
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'Power',
'Eject',
'Abort',
'Help',
'Backspace',
'Tab',
'Numpad5',
'NumpadEnter',
'Enter',
'\r',
'\n',
'ShiftLeft',
'ShiftRight',
'ControlLeft',
'ControlRight',
'AltLeft',
'AltRight',
'Pause',
'CapsLock',
'Escape',
'Convert',
'NonConvert',
'Space',
'Numpad9',
'PageUp',
'Numpad3',
'PageDown',
'End',
'Numpad1',
'Home',
'Numpad7',
'ArrowLeft',
'Numpad4',
'Numpad8',
'ArrowUp',
'ArrowRight',
'Numpad6',
'Numpad2',
'ArrowDown',
'Select',
'Open',
'PrintScreen',
'Insert',
'Numpad0',
'Delete',
'NumpadDecimal',
'Digit0',
'Digit1',
'Digit2',
'Digit3',
'Digit4',
'Digit5',
'Digit6',
'Digit7',
'Digit8',
'Digit9',
'KeyA',
'KeyB',
'KeyC',
'KeyD',
'KeyE',
'KeyF',
'KeyG',
'KeyH',
'KeyI',
'KeyJ',
'KeyK',
'KeyL',
'KeyM',
'KeyN',
'KeyO',
'KeyP',
'KeyQ',
'KeyR',
'KeyS',
'KeyT',
'KeyU',
'KeyV',
'KeyW',
'KeyX',
'KeyY',
'KeyZ',
'MetaLeft',
'MetaRight',
'ContextMenu',
'NumpadMultiply',
'NumpadAdd',
'NumpadSubtract',
'NumpadDivide',
'F1',
'F2',
'F3',
'F4',
'F5',
'F6',
'F7',
'F8',
'F9',
'F10',
'F11',
'F12',
'F13',
'F14',
'F15',
'F16',
'F17',
'F18',
'F19',
'F20',
'F21',
'F22',
'F23',
'F24',
'NumLock',
'ScrollLock',
'AudioVolumeMute',
'AudioVolumeDown',
'AudioVolumeUp',
'MediaTrackNext',
'MediaTrackPrevious',
'MediaStop',
'MediaPlayPause',
'Semicolon',
'Equal',
'NumpadEqual',
'Comma',
'Minus',
'Period',
'Slash',
'Backquote',
'BracketLeft',
'Backslash',
'BracketRight',
'Quote',
'AltGraph',
'Props',
'Cancel',
'Clear',
'Shift',
'Control',
'Alt',
'Accept',
'ModeChange',
' ',
'Print',
'Execute',
'\u0000',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'Meta',
'*',
'+',
'-',
'/',
';',
'=',
',',
'.',
'`',
'[',
'\\',
']',
"'",
'Attn',
'CrSel',
'ExSel',
'EraseEof',
'Play',
'ZoomOut',
')',
'!',
'@',
'#',
'$',
'%',
'^',
'&',
'(',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
':',
'<',
'_',
'>',
'?',
'~',
'{',
'|',
'}',
'"',
'SoftLeft',
'SoftRight',
'Camera',
'Call',
'EndCall',
'VolumeDown',
'VolumeUp',
]);
function throwIfInvalidKey(key: string): KeyInput {
if (validKeys.has(key)) {
return key as KeyInput;
}
throw new Error(
`${key} is invalid. Valid keys are: ${Array.from(validKeys.values()).join(',')}.`,
);
}
/**
* Returns the primary key, followed by modifiers in original order.
*/
export function parseKey(keyInput: string): [KeyInput, ...KeyInput[]] {
let key = '';
const result: KeyInput[] = [];
for (const ch of keyInput) {
// Handle cases like Shift++.
if (ch === '+' && key) {
result.push(throwIfInvalidKey(key));
key = '';
} else {
key += ch;
}
}
if (key) {
result.push(throwIfInvalidKey(key));
}
if (result.length === 0) {
throw new Error(`Key ${keyInput} could not be parsed.`);
}
if (new Set(result).size !== result.length) {
throw new Error(`Key ${keyInput} contains duplicate keys.`);
}
return [result.at(-1), ...result.slice(0, -1)] as [KeyInput, ...KeyInput[]];
}