1export interface RecordingInputOptions {
2 readonly showKeyPresses: boolean;
3 readonly showMousePresses: boolean;
4}
5
6export const DEFAULT_RECORDING_INPUT_OPTIONS: RecordingInputOptions = {
7 showKeyPresses: false,
8 showMousePresses: false,
9};
10
11export interface RecordingKeyPress {
12 readonly key: string;
13 readonly metaKey: boolean;
14 readonly ctrlKey: boolean;
15 readonly altKey: boolean;
16 readonly shiftKey: boolean;
17}
18
19/** Formats a single chord without duplicating a modifier pressed on its own. */
20export function recordingKeyLabel(input: RecordingKeyPress, isMac: boolean): string | null {
21 if (["Dead", "Process", "Unidentified", ""].includes(input.key)) return null;
22 const modifiers = [
23 input.ctrlKey || input.key === "Control" ? (isMac ? "⌃" : "Ctrl") : null,
24 input.altKey || input.key === "Alt" ? (isMac ? "⌥" : "Alt") : null,
25 input.shiftKey || input.key === "Shift" ? (isMac ? "⇧" : "Shift") : null,
26 input.metaKey || input.key === "Meta" ? (isMac ? "⌘" : "Win") : null,
27 ].filter((value) => value !== null);
28 const labels: Record<string, string> = {
29 Enter: "↵",
30 Tab: "⇥",
31 Backspace: "⌫",
32 Delete: "⌦",
33 Escape: "Esc",
34 ArrowUp: "↑",
35 ArrowDown: "↓",
36 ArrowLeft: "←",
37 ArrowRight: "→",
38 " ": "Space",
39 Space: "Space",
40 };
41 if (!["Control", "Alt", "Shift", "Meta"].includes(input.key)) {
42 modifiers.push(
43 labels[input.key] ?? (input.key.length === 1 ? input.key.toUpperCase() : input.key),
44 );
45 }
46 return modifiers.join(isMac ? "" : " + ");
47}
48
49/** Unknown iframe or closed-shadow focus is excluded because its field type cannot be checked. */
50export function recordingKeysAreSensitive(document: Document): boolean {
51 let element = document.activeElement;
52 while (element?.shadowRoot?.activeElement) element = element.shadowRoot.activeElement;
53 return (
54 element?.tagName === "IFRAME" ||
55 element?.tagName.includes("-") === true ||
56 element?.getAttribute("type")?.toLowerCase() === "password"
57 );
58}