From 81c6af6399ab92829bed3e395ec70eca0e9df76f Mon Sep 17 00:00:00 2001 From: Jage9 Date: Sun, 22 Feb 2026 22:53:09 -0500 Subject: [PATCH] Normalize keyboard codes for Safari and iOS hardware keyboards --- client/public/version.js | 2 +- client/src/main.ts | 59 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/client/public/version.js b/client/public/version.js index 5d3be8f..59f619a 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,5 +1,5 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.02.22 R197"; +window.CHGRID_WEB_VERSION = "2026.02.22 R198"; // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. window.CHGRID_TIME_ZONE = "America/Detroit"; diff --git a/client/src/main.ts b/client/src/main.ts index 812eb53..754b7a4 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -2303,10 +2303,59 @@ function isTypingKey(code: string): boolean { return code.startsWith('Key') || code === 'Space'; } +/** Maps normalized `event.key` values to canonical `event.code` strings when code is unavailable. */ +function codeFromKey(key: string, location: number): string | null { + if (key === 'Escape' || key === 'Esc') return 'Escape'; + if (key === 'Enter' || key === 'Return') return 'Enter'; + if (key === 'Backspace') return 'Backspace'; + if (key === 'Delete' || key === 'Del') return 'Delete'; + if (key === 'ArrowUp' || key === 'Up') return 'ArrowUp'; + if (key === 'ArrowDown' || key === 'Down') return 'ArrowDown'; + if (key === 'ArrowLeft' || key === 'Left') return 'ArrowLeft'; + if (key === 'ArrowRight' || key === 'Right') return 'ArrowRight'; + if (key === 'Home') return 'Home'; + if (key === 'End') return 'End'; + if (key === 'PageUp') return 'PageUp'; + if (key === 'PageDown') return 'PageDown'; + if (key === 'Tab') return 'Tab'; + if (key === ' ' || key === 'Spacebar') return 'Space'; + if (key.length === 1) { + if (/^[a-z]$/i.test(key)) return `Key${key.toUpperCase()}`; + if (/^[0-9]$/.test(key)) return `Digit${key}`; + if (key === '!') return 'Digit1'; + if (key === '@') return 'Digit2'; + if (key === '#') return 'Digit3'; + if (key === '$') return 'Digit4'; + if (key === '%') return 'Digit5'; + if (key === '^') return 'Digit6'; + if (key === '&') return 'Digit7'; + if (key === '*') return 'Digit8'; + if (key === '(') return 'Digit9'; + if (key === ')') return 'Digit0'; + if (key === '+' && location === 3) return 'NumpadAdd'; + if (key === '-' && location === 3) return 'NumpadSubtract'; + if (key === '+' || key === '=') return 'Equal'; + if (key === '-' || key === '_') return 'Minus'; + if (key === '/' || key === '?') return 'Slash'; + if (key === ',' || key === '<') return 'Comma'; + if (key === '.' || key === '>') return 'Period'; + } + return null; +} + +/** Returns best-effort canonical key code across desktop + Safari/iOS keyboard event variants. */ +function normalizeInputCode(event: KeyboardEvent): string { + if (event.code && event.code !== 'Unidentified') { + return event.code; + } + return codeFromKey(event.key, event.location) ?? event.code ?? ''; +} + /** Wires global keyboard/paste input handlers and routes events by current mode. */ function setupInputHandlers(): void { document.addEventListener('keydown', (event) => { - const code = event.code; + const code = normalizeInputCode(event); + if (!code) return; if (!dom.settingsModal.classList.contains('hidden') && code === 'Escape') { closeSettings(); @@ -2381,7 +2430,13 @@ function setupInputHandlers(): void { }); document.addEventListener('keyup', (event) => { - state.keysPressed[event.code] = false; + const code = normalizeInputCode(event); + if (code) { + state.keysPressed[code] = false; + } + if (event.code && event.code !== code) { + state.keysPressed[event.code] = false; + } }); document.addEventListener('paste', (event) => {