11import { isMacLike } from '@/composables/events'
22import { assert } from '@/util/assert'
3+ import * as objects from 'enso-common/src/utilities/data/object'
34
45/** All possible modifier keys. */
56export type ModifierKey = keyof typeof RAW_MODIFIER_FLAG
@@ -258,8 +259,11 @@ type AutocompleteKeybind<T extends string, Key extends string = never> =
258259 : [ Key ] extends [ never ] ? SuggestedKeybindSegment
259260 : Key
260261
261- type AutocompleteKeybinds < T extends string [ ] > = {
262- [ K in keyof T ] : AutocompleteKeybind < T [ K ] >
262+ type AutocompleteKeybinds < T extends KeybindDefinition [ ] > = {
263+ [ K in keyof T ] : T [ K ] extends FullKeybindDefinition ?
264+ FullKeybindDefinition < AutocompleteKeybind < T [ K ] [ 'key' ] > >
265+ : T [ K ] extends string ? AutocompleteKeybind < T [ K ] >
266+ : never
263267}
264268
265269/** Some keys have not human-friendly name, these are overwritten here for {@link BindingInfo}. */
@@ -275,7 +279,7 @@ const HUMAN_READABLE_KEYS: Partial<Record<Key, string>> = {
275279
276280// `never extends T ? Result : InferenceSource` is a trick to unify `T` with the actual type of the
277281// argument.
278- type Keybinds < T extends Record < K , string [ ] > , K extends keyof T = keyof T > =
282+ type Keybinds < T extends Record < K , KeybindDefinition [ ] > , K extends keyof T = keyof T > =
279283 never extends T ?
280284 {
281285 [ K in keyof T ] : AutocompleteKeybinds < T [ K ] >
@@ -293,6 +297,14 @@ const definedNamespaces = new Set<string>()
293297
294298export const DefaultHandler = Symbol ( 'default handler' )
295299
300+ interface KeybindOptions {
301+ allowRepeat ?: boolean
302+ }
303+ export interface FullKeybindDefinition < T = string > extends KeybindOptions {
304+ key : T
305+ }
306+ export type KeybindDefinition = string | FullKeybindDefinition
307+
296308/**
297309 * Define key bindings for given namespace.
298310 *
@@ -359,7 +371,7 @@ export const DefaultHandler = Symbol('default handler')
359371 * ```
360372 */
361373export function defineKeybinds <
362- T extends Record < BindingName , [ ] | string [ ] > ,
374+ T extends Record < BindingName , [ ] | KeybindDefinition [ ] > ,
363375 BindingName extends keyof T = keyof T ,
364376> ( namespace : string , bindings : Keybinds < T > ) {
365377 if ( definedNamespaces . has ( namespace ) ) {
@@ -370,12 +382,21 @@ export function defineKeybinds<
370382 const keyboardShortcuts : Partial < Record < Key_ , Record < ModifierFlags , Set < BindingName > > > > = { }
371383 const mouseShortcuts : Record < PointerButtonFlags , Record < ModifierFlags , Set < BindingName > > > = [ ]
372384
385+ function fullKeybind ( keybind : KeybindDefinition ) : FullKeybindDefinition {
386+ return typeof keybind === 'string' ? { key : keybind } : keybind
387+ }
388+
373389 const bindingsInfo = { } as Record < BindingName , BindingInfo >
374- for ( const [ name_ , keybindStrings ] of Object . entries ( bindings ) ) {
390+ const bindingsOptions = { } as Record < BindingName , KeybindOptions >
391+ for ( const [ name_ , keybindValues ] of Object . entries ( bindings ) ) {
375392 const name = name_ as BindingName
376- for ( const keybindString of keybindStrings as string [ ] ) {
377- const { bind : keybind , info } = parseKeybindString ( keybindString )
378- if ( bindingsInfo [ name ] == null ) bindingsInfo [ name ] = info
393+ for ( const keybindValue of keybindValues as KeybindDefinition [ ] ) {
394+ const keybindDef = fullKeybind ( keybindValue )
395+ const { bind : keybind , info } = parseKeybindString ( keybindDef . key )
396+ if ( bindingsInfo [ name ] == null ) {
397+ bindingsInfo [ name ] = info
398+ bindingsOptions [ name ] = keybindDef
399+ }
379400 switch ( keybind . type ) {
380401 case 'keybind' : {
381402 const shortcutsByKey = ( keyboardShortcuts [ keybind . key ] ??= [ ] )
@@ -414,27 +435,27 @@ export function defineKeybinds<
414435 handlers : Partial <
415436 Record < BindingName | typeof DefaultHandler , ( event : Event_ ) => boolean | void >
416437 > ,
417- ) : ( event : Event_ , stopAndPrevent ?: boolean ) => boolean {
418- return ( event , stopAndPrevent = true ) => {
419- // Do not handle repeated keyboard events (held down key).
420- if ( event instanceof KeyboardEvent && event . repeat ) return false
421-
438+ ) : ( event : Event_ ) => boolean {
439+ return ( event ) => {
422440 const eventModifierFlags = modifierFlagsForEvent ( event )
423441 const keybinds =
424442 event instanceof KeyboardEvent ?
425443 keyboardShortcuts [ eventKey ( event ) ] ?. [ eventModifierFlags ]
426444 : mouseShortcuts [ buttonFlagsForEvent ( event ) ] ?. [ eventModifierFlags ]
427445
446+ const isRepeat = event instanceof KeyboardEvent && event . repeat
428447 let handled = false
429448 if ( keybinds != null ) {
430- for ( const bindingName in handlers ) {
449+ for ( const bindingName of objects . unsafeKeys ( handlers ) ) {
450+ if ( bindingName === DefaultHandler ) continue
451+ if ( isRepeat && ! bindingsOptions [ bindingName ] . allowRepeat ) continue
431452 if ( keybinds . has ( bindingName as BindingName ) ) {
432453 const handle = handlers [ bindingName as BindingName ]
433454 handled = handle && handle ( event ) !== false
434455 if ( DEBUG_LOG )
435456 console . log (
436457 `Event ${ event . type } (${ event instanceof KeyboardEvent ? event . key : buttonFlagsForEvent ( event ) } )` ,
437- `${ handled ? 'handled' : 'processed' } by ${ namespace } .${ bindingName } ` ,
458+ `${ handled ? 'handled' : 'processed' } by ${ namespace } .${ String ( bindingName ) } ` ,
438459 )
439460 if ( handled ) break
440461 }
@@ -443,7 +464,7 @@ export function defineKeybinds<
443464 if ( ! handled && handlers [ DefaultHandler ] != null ) {
444465 handled = handlers [ DefaultHandler ] ( event ) !== false
445466 }
446- if ( handled && stopAndPrevent ) {
467+ if ( handled ) {
447468 event . stopImmediatePropagation ( )
448469 // We don't prevent default on PointerEvents, because it may prevent emitting
449470 // mousedown/mouseup events, on which external libraries may rely (like AGGrid for hiding
0 commit comments