1- import type { Hotkey , HotkeyCallback , Keys , Options , OptionsOrDependencyArray } from './types'
1+ import type { HotkeyCallback , Keys , Options , OptionsOrDependencyArray } from './types'
22import { type DependencyList , useCallback , useEffect , useLayoutEffect , useRef } from 'react'
3- import { mapCode , parseHotkey , parseKeysHookInput , isHotkeyModifier } from './parseHotkeys'
3+ import { mapCode , parseHotkey , parseKeysHookInput , isHotkeyModifier , sequenceEndsWith } from './parseHotkeys'
44import {
55 isHotkeyEnabled ,
66 isHotkeyEnabledOnTag ,
@@ -62,38 +62,20 @@ export default function useHotkeys<T extends HTMLElement>(
6262 return
6363 }
6464
65- const [ comboHotkeys , sequenceHotkeys ] = parseKeysHookInput ( _keys , memoisedOptions ?. delimiter )
66- . reduce < [ Array < { key : string ; hotkey : Hotkey } > , Array < { key : string ; hotkey : Hotkey } > ] > ( ( acc , key ) => {
67- const [ _comboHotkey , _sequenceHotkey ] = acc ;
68-
69- const hotkey = parseHotkey (
70- key ,
71- memoisedOptions ?. splitKey ,
72- memoisedOptions ?. sequenceSplitKey ,
73- memoisedOptions ?. useKey ,
74- memoisedOptions ?. description ,
75- )
76-
77- if ( hotkey . isSequence ) {
78- _sequenceHotkey . push ( { key, hotkey } )
79- } else {
80- _comboHotkey . push ( { key, hotkey } )
81- }
82- return [ _comboHotkey , _sequenceHotkey ]
83- } , [ [ ] , [ ] ] )
84-
85- const sequenceMaps = new Map (
86- sequenceHotkeys
87- . reduce < [ string , { recordedKeys : string [ ] ; sequenceTimer : NodeJS . Timeout | undefined } ] [ ] > (
88- ( acc , { key, hotkey } ) => {
89- if ( hotkey . isSequence ) {
90- acc . push ( [ key , { recordedKeys : [ ] , sequenceTimer : void 0 } ] )
91- }
92- return acc
93- } ,
94- [ ]
95- )
96- ) ;
65+ const hotkeys = parseKeysHookInput ( _keys , memoisedOptions ?. delimiter )
66+ . map ( ( key ) => parseHotkey (
67+ key ,
68+ memoisedOptions ?. splitKey ,
69+ memoisedOptions ?. sequenceSplitKey ,
70+ memoisedOptions ?. useKey ,
71+ memoisedOptions ?. description ,
72+ ) ) ;
73+
74+ const comboHotkeys = hotkeys . filter ( hotkey => ! hotkey . isSequence ) ;
75+ const sequenceHotkeys = hotkeys . filter ( hotkey => hotkey . isSequence ) ;
76+
77+ let sequenceRecordedKeys : string [ ] = [ ] ;
78+ let sequenceTimer : NodeJS . Timeout | undefined = undefined ;
9779
9880 const listener = ( e : KeyboardEvent , isKeyUp = false ) => {
9981 if ( isKeyboardEventTriggeredByInput ( e ) && ! isHotkeyEnabledOnTag ( e , memoisedOptions ?. enableOnFormTags ) ) {
@@ -120,7 +102,7 @@ export default function useHotkeys<T extends HTMLElement>(
120102 }
121103
122104 // ========== HANDLE COMBO HOTKEYS ==========
123- for ( const { hotkey } of comboHotkeys ) {
105+ for ( const hotkey of comboHotkeys ) {
124106 if (
125107 isHotkeyMatchingKeyboardEvent ( e , hotkey , memoisedOptions ?. ignoreModifiers ) ||
126108 hotkey . keys ?. includes ( '*' )
@@ -151,48 +133,32 @@ export default function useHotkeys<T extends HTMLElement>(
151133 }
152134
153135 // ========== HANDLE SEQUENCE HOTKEYS ==========
154- for ( const { key, hotkey } of sequenceHotkeys ) {
155- const sequenceMap = sequenceMaps . get ( key )
156-
157- if ( ! sequenceMap ) {
158- continue
159- }
160-
161- // Set a timeout to check post which the sequence should reset
162- if ( sequenceMap . sequenceTimer ) {
163- clearTimeout ( sequenceMap . sequenceTimer )
164- }
165-
166- sequenceMap . sequenceTimer = setTimeout ( ( ) => {
167- sequenceMap . recordedKeys = [ ]
168- } , memoisedOptions ?. sequenceTimeoutMs ?? 1000 )
169-
170- const currentKey = hotkey . useKey ? e . key : mapCode ( e . code )
171-
172- if ( isHotkeyModifier ( currentKey . toLowerCase ( ) ) ) {
173- continue
174- }
136+ if ( sequenceHotkeys . length > 0 ) {
137+ const currentKey = memoisedOptions ?. useKey
138+ ? e . key
139+ : mapCode ( e . code )
140+
141+ if ( ! isHotkeyModifier ( currentKey . toLowerCase ( ) ) ) {
142+ // clear the previous timer
143+ if ( sequenceTimer ) {
144+ clearTimeout ( sequenceTimer ) ;
145+ }
175146
176- sequenceMap . recordedKeys . push ( currentKey )
147+ sequenceTimer = setTimeout ( ( ) => {
148+ sequenceRecordedKeys = [ ] ;
149+ } , memoisedOptions ?. sequenceTimeoutMs ?? 1000 ) ;
177150
178- const expectedKey = hotkey . keys ?. [ sequenceMap . recordedKeys . length - 1 ]
179- if ( currentKey !== expectedKey ) {
180- sequenceMap . recordedKeys = [ ]
181- if ( sequenceMap . sequenceTimer ) {
182- clearTimeout ( sequenceMap . sequenceTimer )
183- }
184- continue
185- }
151+ sequenceRecordedKeys . push ( currentKey ) ;
186152
187- // If the sequence is complete, trigger the callback
188- if ( sequenceMap . recordedKeys . length === hotkey . keys ?. length ) {
189- cbRef . current ( e , hotkey )
153+ for ( const hotkey of sequenceHotkeys ) {
154+ if ( ! hotkey . keys ) {
155+ continue
156+ }
190157
191- if ( sequenceMap . sequenceTimer ) {
192- clearTimeout ( sequenceMap . sequenceTimer )
158+ if ( sequenceEndsWith ( sequenceRecordedKeys , [ ...hotkey . keys ] ) ) {
159+ cbRef . current ( e , hotkey ) ;
160+ }
193161 }
194-
195- sequenceMap . recordedKeys = [ ]
196162 }
197163 }
198164 }
@@ -233,17 +199,7 @@ export default function useHotkeys<T extends HTMLElement>(
233199 domNode . addEventListener ( 'keydown' , handleKeyDown , _options ?. eventListenerOptions )
234200
235201 if ( proxy ) {
236- parseKeysHookInput ( _keys , memoisedOptions ?. delimiter ) . forEach ( ( key ) =>
237- proxy . addHotkey (
238- parseHotkey (
239- key ,
240- memoisedOptions ?. splitKey ,
241- memoisedOptions ?. sequenceSplitKey ,
242- memoisedOptions ?. useKey ,
243- memoisedOptions ?. description ,
244- ) ,
245- ) ,
246- )
202+ hotkeys . forEach ( proxy . addHotkey )
247203 }
248204
249205 return ( ) => {
@@ -253,25 +209,12 @@ export default function useHotkeys<T extends HTMLElement>(
253209 domNode . removeEventListener ( 'keydown' , handleKeyDown , _options ?. eventListenerOptions )
254210
255211 if ( proxy ) {
256- parseKeysHookInput ( _keys , memoisedOptions ?. delimiter ) . forEach ( ( key ) =>
257- proxy . removeHotkey (
258- parseHotkey (
259- key ,
260- memoisedOptions ?. splitKey ,
261- memoisedOptions ?. sequenceSplitKey ,
262- memoisedOptions ?. useKey ,
263- memoisedOptions ?. description ,
264- ) ,
265- ) ,
266- )
212+ hotkeys . forEach ( proxy . removeHotkey ) ;
267213 }
268214
269- // clear the recorded keys and timeout on unmount
270- for ( const [ , sequenceMap ] of sequenceMaps ) {
271- sequenceMap . recordedKeys = [ ]
272- if ( sequenceMap . sequenceTimer ) {
273- clearTimeout ( sequenceMap . sequenceTimer )
274- }
215+ sequenceRecordedKeys = [ ] ;
216+ if ( sequenceTimer ) {
217+ clearTimeout ( sequenceTimer ) ;
275218 }
276219 }
277220 } , [ _keys , memoisedOptions , activeScopes ] )
0 commit comments