@@ -2,6 +2,15 @@ import type { FormTags, Hotkey, Scopes, Trigger } from './types'
22import { isHotkeyPressed , isReadonlyArray } from './isHotkeyPressed'
33import { mapCode } from './parseHotkeys'
44
5+ // macOS Option key transforms letters to Unicode characters (US keyboard layout)
6+ // Reverse map to recover intended letter - same approach as GitHub's hotkey library
7+ const macOSOptionKeyReverseMap : Record < string , string > = {
8+ 'å' : 'a' , '∫' : 'b' , 'ç' : 'c' , '∂' : 'd' , '´' : 'e' , 'ƒ' : 'f' , '©' : 'g' ,
9+ '˙' : 'h' , 'ˆ' : 'i' , '∆' : 'j' , '˚' : 'k' , '¬' : 'l' , 'µ' : 'm' , '˜' : 'n' ,
10+ 'ø' : 'o' , 'π' : 'p' , 'œ' : 'q' , '®' : 'r' , 'ß' : 's' , '†' : 't' , '¨' : 'u' ,
11+ '√' : 'v' , '∑' : 'w' , '≈' : 'x' , '¥' : 'y' , 'Ω' : 'z'
12+ }
13+
514export function maybePreventDefault ( e : KeyboardEvent , hotkey : Hotkey , preventDefault ?: Trigger ) : void {
615 if ( ( typeof preventDefault === 'function' && preventDefault ( e , hotkey ) ) || preventDefault === true ) {
716 e . preventDefault ( )
@@ -127,8 +136,24 @@ export const isHotkeyMatchingKeyboardEvent = (e: KeyboardEvent, hotkey: Hotkey,
127136 // If useKey is set, match against the produced key value instead of the key code
128137 // When useKey is true, we ONLY match produced keys — never fall through to code-based matching
129138 if ( useKey ) {
130- // Normalize produced key: map ' ' to 'space' so hotkey string 'space' works with useKey
131- const normalizedKey = producedKey === ' ' ? 'space' : producedKey . toLowerCase ( )
139+ // macOS Option key transforms event.key to Unicode characters (opt+y → '¥')
140+ // Try both: physical key (event.code) AND reverse-mapped Unicode for layout support
141+ if ( altKey ) {
142+ const reverseMappedKey = macOSOptionKeyReverseMap [ producedKey ] || producedKey . toLowerCase ( )
143+ if ( keys ?. length === 1 ) {
144+ return keys . includes ( mappedCode ) || keys . includes ( reverseMappedKey )
145+ }
146+ if ( keys && keys . length > 0 ) {
147+ return isHotkeyPressed ( keys . map ( ( k ) => k . toLowerCase ( ) ) )
148+ }
149+ return ! keys || keys . length === 0
150+ }
151+
152+ // Non-alt path: use event.key (layout-aware matching)
153+ // Normalize produced key: map ' ' to 'space', ',' to 'comma'
154+ const normalizedKey = producedKey === ' ' ? 'space'
155+ : producedKey === ',' ? 'comma'
156+ : producedKey . toLowerCase ( )
132157 if ( keys ?. length === 1 ) {
133158 return keys . includes ( normalizedKey )
134159 }
0 commit comments