Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | 230x 3541x 66x 22x 3541x 92x 3x 83x 3363x 3541x 3541x 3541x 2966x 229x 2737x 206x 2531x 29x 3541x 3541x 139x 3290x 97x | import { isModifierAction } from "../constants";
import type {
KeyboardLayout,
KeyboardModifiers,
KeyDefinition,
ResolvedKey,
ResolvedLayout,
VirtualKeyEvent,
} from "../types";
/** Whether a string is a single Latin letter (a–z / A–Z). */
function isAlpha(value: string): boolean {
return value.length === 1 && /[a-zA-Z]/.test(value);
}
/** Infer a key's semantic type when it is not declared explicitly. */
function inferType(key: KeyDefinition): NonNullable<KeyDefinition["type"]> {
if (key.type) return key.type;
if (key.action) return isModifierAction(key.action) ? "modifier" : "action";
return "char";
}
/** Whether a resolved/definition key is a currently-active modifier. */
function isActiveModifier(
key: KeyDefinition,
modifiers: KeyboardModifiers
): boolean {
switch (key.action) {
case "shift":
return modifiers.shift;
case "capslock":
return modifiers.capsLock;
case "layer":
return modifiers.layer;
default:
return false;
}
}
/**
* Resolve a single key against the current modifier state, computing the value
* it will emit ({@link ResolvedKey.effectiveValue}) and the label to render
* ({@link ResolvedKey.displayLabel}).
*
* Resolution precedence for character keys:
* 1. **Symbol layer** — when the layer is active and the key declares a
* `layerKey`, that value is used.
* 2. **Shift** — the explicit `shiftKey`, or the uppercased letter.
* 3. **Caps Lock** — uppercases Latin letters only.
* 4. Otherwise the base `key`.
*
* Action and modifier keys always emit their base `key` unchanged.
*
* @example
* ```ts
* resolveKey({ key: "a" }, { shift: true, capsLock: false, layer: false }).effectiveValue; // "A"
* resolveKey({ key: "1", layerKey: "!" }, { shift: false, capsLock: false, layer: true }).effectiveValue; // "!"
* ```
*/
export function resolveKey(
key: KeyDefinition,
modifiers: KeyboardModifiers
): ResolvedKey {
const type = inferType(key);
let effectiveValue = key.key;
if (type === "char") {
if (modifiers.layer && key.layerKey !== undefined) {
effectiveValue = key.layerKey;
} else if (modifiers.shift) {
effectiveValue =
key.shiftKey ?? (isAlpha(key.key) ? key.key.toUpperCase() : key.key);
} else if (modifiers.capsLock && isAlpha(key.key)) {
effectiveValue = key.key.toUpperCase();
}
}
const displayLabel =
key.label !== undefined
? key.label
: type === "char"
? effectiveValue
: key.key;
return {
...key,
type,
effectiveValue,
displayLabel,
active: isActiveModifier(key, modifiers),
};
}
/**
* Resolve an entire {@link KeyboardLayout} against the current modifiers into a
* {@link ResolvedLayout} ready to render. Pure — the same inputs always produce
* the same output, which makes it cheap to memoize on `(layout, modifiers)`.
*
* @example
* ```ts
* const resolved = resolveLayout(qwertyLayout, { shift: false, capsLock: true, layer: false });
* resolved.rows[0][0].effectiveValue; // "Q"
* ```
*/
export function resolveLayout(
layout: KeyboardLayout,
modifiers: KeyboardModifiers
): ResolvedLayout {
return {
name: layout.name,
direction: layout.direction ?? "ltr",
rows: layout.rows.map((row) => row.map((key) => resolveKey(key, modifiers))),
};
}
/** Build the SSR-safe synthetic event describing a resolved key press. */
export function buildKeyEvent(
key: ResolvedKey,
modifiers: KeyboardModifiers
): VirtualKeyEvent {
return {
key: key.effectiveValue,
code: key.code,
shiftKey: modifiers.shift,
layer: modifiers.layer,
};
}
|