1- // # Returns a hotkey character string for keydown and keyup events.
2- //
3- // A full list of key names can be found here:
4- // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
5- //
6- // ## Code Example
7- //
8- // ```
9- // document.addEventListener('keydown', function(event) {
10- // if (hotkey(event) === 'h') ...
11- // })
12- // ```
13- // ## Hotkey examples
14- //
15- // "s" // Lowercase character for single letters
16- // "S" // Uppercase character for shift plus a letter
17- // "1" // Number character
18- // "?" // Shift plus "/" symbol
19- //
20- // "Enter" // Enter key
21- // "ArrowUp" // Up arrow
22- //
23- // "Control+s" // Control modifier plus letter
24- // "Control+Alt+Delete" // Multiple modifiers
25- //
26- // Returns key character String or null.
27- export default function hotkey ( event : KeyboardEvent ) : string {
1+ const normalizedHotkeyBrand = Symbol ( 'normalizedHotkey' )
2+
3+ /**
4+ * A hotkey string with modifier keys in standard order. Build one with `eventToHotkeyString` or normalize a string via
5+ * `normalizeHotkey`.
6+ *
7+ * A full list of key names can be found here:
8+ * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
9+ *
10+ * Examples:
11+ * "s" // Lowercase character for single letters
12+ * "S" // Uppercase character for shift plus a letter
13+ * "1" // Number character
14+ * "?" // Shift plus "/" symbol
15+ * "Enter" // Enter key
16+ * "ArrowUp" // Up arrow
17+ * "Control+s" // Control modifier plus letter
18+ * "Control+Alt+Delete" // Multiple modifiers
19+ */
20+ export type NormalizedHotkeyString = string & { [ normalizedHotkeyBrand ] : true }
21+
22+ /**
23+ * Returns a hotkey character string for keydown and keyup events.
24+ * @example
25+ * document.addEventListener('keydown', function(event) {
26+ * if (eventToHotkeyString(event) === 'h') ...
27+ * })
28+ */
29+ export function eventToHotkeyString ( event : KeyboardEvent ) : NormalizedHotkeyString {
2830 const { ctrlKey, altKey, metaKey, key} = event
2931 const hotkeyString : string [ ] = [ ]
3032 const modifiers : boolean [ ] = [ ctrlKey , altKey , metaKey , showShift ( event ) ]
@@ -37,13 +39,49 @@ export default function hotkey(event: KeyboardEvent): string {
3739 hotkeyString . push ( key )
3840 }
3941
40- return hotkeyString . join ( '+' )
42+ return hotkeyString . join ( '+' ) as NormalizedHotkeyString
4143}
4244
43- const modifierKeyNames : string [ ] = [ ` Control` , 'Alt' , 'Meta' , 'Shift' ]
45+ const modifierKeyNames : string [ ] = [ ' Control' , 'Alt' , 'Meta' , 'Shift' ]
4446
4547// We don't want to show `Shift` when `event.key` is capital
4648function showShift ( event : KeyboardEvent ) : boolean {
4749 const { shiftKey, code, key} = event
4850 return shiftKey && ! ( code . startsWith ( 'Key' ) && key . toUpperCase ( ) === key )
4951}
52+
53+ /**
54+ * Normalizes a hotkey string before comparing it to the serialized event
55+ * string produced by `eventToHotkeyString`.
56+ * - Replaces the `Mod` modifier with `Meta` on mac, `Control` on other
57+ * platforms.
58+ * - Ensures modifiers are sorted in a consistent order
59+ * @param hotkey a hotkey string
60+ * @param platform NOTE: this param is only intended to be used to mock `navigator.platform` in tests
61+ * @returns {string } normalized representation of the given hotkey string
62+ */
63+ export function normalizeHotkey ( hotkey : string , platform ?: string | undefined ) : NormalizedHotkeyString {
64+ let result : string
65+ result = localizeMod ( hotkey , platform )
66+ result = sortModifiers ( result )
67+ return result as NormalizedHotkeyString
68+ }
69+
70+ const matchApplePlatform = / M a c | i P o d | i P h o n e | i P a d / i
71+
72+ function localizeMod ( hotkey : string , platform : string = navigator . platform ) : string {
73+ const localModifier = matchApplePlatform . test ( platform ) ? 'Meta' : 'Control'
74+ return hotkey . replace ( 'Mod' , localModifier )
75+ }
76+
77+ function sortModifiers ( hotkey : string ) : string {
78+ const key = hotkey . split ( '+' ) . pop ( )
79+ const modifiers = [ ]
80+ for ( const modifier of [ 'Control' , 'Alt' , 'Meta' , 'Shift' ] ) {
81+ if ( hotkey . includes ( modifier ) ) {
82+ modifiers . push ( modifier )
83+ }
84+ }
85+ modifiers . push ( key )
86+ return modifiers . join ( '+' )
87+ }
0 commit comments