@@ -7,8 +7,24 @@ import type {
77 HotkeyCallbackContext ,
88 HotkeyOptions ,
99 HotkeyRegistration ,
10+ HotkeyRegistrationHandle ,
1011} from './types'
1112
13+ /**
14+ * Default options for hotkey registration.
15+ */
16+ const defaultHotkeyOptions : Omit <
17+ Required < HotkeyOptions > ,
18+ 'platform' | 'target'
19+ > = {
20+ preventDefault : false ,
21+ stopPropagation : false ,
22+ eventType : 'keydown' ,
23+ requireReset : false ,
24+ enabled : true ,
25+ ignoreInputs : true ,
26+ }
27+
1228let registrationIdCounter = 0
1329
1430/**
@@ -79,18 +95,35 @@ export class HotkeyManager {
7995 }
8096
8197 /**
82- * Registers a hotkey handler.
98+ * Registers a hotkey handler and returns a handle for updating the registration.
99+ *
100+ * The returned handle allows updating the callback and options without
101+ * re-registering, which is useful for avoiding stale closures in React.
83102 *
84103 * @param hotkey - The hotkey string to listen for
85104 * @param callback - The function to call when the hotkey is pressed
86105 * @param options - Options for the hotkey behavior
87- * @returns A function to unregister the hotkey
106+ * @returns A handle for managing the registration
107+ *
108+ * @example
109+ * ```ts
110+ * const handle = manager.register('Mod+S', callback, { preventDefault: true })
111+ *
112+ * // Update callback without re-registering (avoids stale closures)
113+ * handle.callback = newCallback
114+ *
115+ * // Update options
116+ * handle.setOptions({ enabled: false })
117+ *
118+ * // Unregister when done
119+ * handle.unregister()
120+ * ```
88121 */
89122 register (
90123 hotkey : Hotkey ,
91124 callback : HotkeyCallback ,
92125 options : HotkeyOptions = { } ,
93- ) : ( ) => void {
126+ ) : HotkeyRegistrationHandle {
94127 const id = generateId ( )
95128 const platform = options . platform ?? this . platform
96129 const parsedHotkey = parseHotkey ( hotkey , platform )
@@ -105,12 +138,7 @@ export class HotkeyManager {
105138 parsedHotkey,
106139 callback,
107140 options : {
108- preventDefault : false ,
109- stopPropagation : false ,
110- eventType : 'keydown' ,
111- requireReset : false ,
112- enabled : true ,
113- ignoreInputs : true ,
141+ ...defaultHotkeyOptions ,
114142 ...options ,
115143 platform,
116144 } ,
@@ -129,9 +157,37 @@ export class HotkeyManager {
129157 // Ensure listeners are attached for this target
130158 this . ensureListenersForTarget ( target )
131159
132- return ( ) => {
133- this . unregister ( id )
160+ // Create and return the handle
161+ const manager = this
162+ const handle : HotkeyRegistrationHandle = {
163+ get id ( ) {
164+ return id
165+ } ,
166+ unregister : ( ) => {
167+ manager . unregister ( id )
168+ } ,
169+ get callback ( ) {
170+ const reg = manager . registrations . get ( id )
171+ return reg ?. callback ?? callback
172+ } ,
173+ set callback ( newCallback : HotkeyCallback ) {
174+ const reg = manager . registrations . get ( id )
175+ if ( reg ) {
176+ reg . callback = newCallback
177+ }
178+ } ,
179+ setOptions : ( newOptions : Partial < HotkeyOptions > ) => {
180+ const reg = manager . registrations . get ( id )
181+ if ( reg ) {
182+ reg . options = { ...reg . options , ...newOptions }
183+ }
184+ } ,
185+ get isActive ( ) {
186+ return manager . registrations . has ( id )
187+ } ,
134188 }
189+
190+ return handle
135191 }
136192
137193 /**
@@ -443,11 +499,18 @@ export class HotkeyManager {
443499
444500 /**
445501 * Checks if a specific hotkey is registered.
502+ *
503+ * @param hotkey - The hotkey string to check
504+ * @param target - Optional target element to match (if provided, both hotkey and target must match)
505+ * @returns True if a matching registration exists
446506 */
447- isRegistered ( hotkey : Hotkey ) : boolean {
507+ isRegistered ( hotkey : Hotkey , target ?: HTMLElement | Document | Window ) : boolean {
448508 for ( const registration of this . registrations . values ( ) ) {
449509 if ( registration . hotkey === hotkey ) {
450- return true
510+ // If target is specified, both must match
511+ if ( target === undefined || registration . target === target ) {
512+ return true
513+ }
451514 }
452515 }
453516 return false
0 commit comments