microduck-simulator / app /src /ui /keyboard-layout.js
vbonnet's picture
Show layout-aware keyboard labels
5d81359
Raw History Blame
1.42 kB
// Controls use physical KeyboardEvent.code positions; getLayoutMap() provides
// the characters printed on those keys for the active layout.
const FALLBACK_LABELS = Object.freeze({ KeyW: "W/Z", KeyA: "A/Q", KeyQ: "Q/A" });
const MOVEMENT_CODES = ["KeyW", "KeyA", "KeyS", "KeyD"];
const MOVEMENT_FALLBACK_HINT = "arrows or WASD / ZQSD";
function keycap(value) {
if (typeof value !== "string") return null;
const glyph = value.trim();
if ([...glyph].length !== 1 || /[\p{M}\p{C}\p{Z}]/u.test(glyph)) return null;
const upper = glyph.toUpperCase();
return [...upper].length === 1 ? upper : glyph; // "ß" must not become "SS"
}
export function resolveKeycaps(codes, layoutMap) {
const labels = {};
const fromLayout = new Set();
for (const code of new Set([...MOVEMENT_CODES, ...codes])) {
let label = null;
try {
label = keycap(layoutMap?.get?.(code));
} catch {
label = null;
}
if (label) fromLayout.add(code);
labels[code] = label ?? FALLBACK_LABELS[code] ?? code.replace(/^Key/, "");
}
const moveHint = MOVEMENT_CODES.every((code) => fromLayout.has(code))
? `arrows or ${MOVEMENT_CODES.map((code) => labels[code]).join("")}`
: MOVEMENT_FALLBACK_HINT;
return { labels, moveHint };
}
export async function readLayoutMap(nav = globalThis.navigator) {
try {
return (await nav?.keyboard?.getLayoutMap?.()) ?? null;
} catch {
return null;
}
}