import { DependencyList , RefObject , useRef } from 'react' ; import { KeyboardKey , KeyboardKeyType } from '@proton/shared/lib/interfaces' ; import isDeepEqual from '@proton/shared/lib/helpers/isDeepEqual' ; import isTruthy from '@proton/shared/lib/helpers/isTruthy' ; import { isMac } from '@proton/shared/lib/helpers/browser' ; import { useEventListener } from './useHandler' ; type Hotkey = KeyboardKeyType | KeyboardKeyType [ ] ; type Sequence = Hotkey [ ] ; type HotKeyCallback = ( e : KeyboardEvent ) => void ; export type HotkeyTuple = [ Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , Hotkey , Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , HotKeyCallback ] | [ Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , Hotkey , HotKeyCallback ] ; type KeyEventType = 'keyup' | 'keydown' | 'keypress' ; type HotKeysOptions = { keyEventType ? : KeyEventType ; sequenceResetTime ? : number ; dependencies ? : DependencyList ; } ; const MODIFIER_KEYS = { META : KeyboardKey . Meta . toLowerCase ( ) , SHIFT : KeyboardKey . Shift . toLowerCase ( ) , CONTROL : KeyboardKey . Control . toLowerCase ( ) , ALT : KeyboardKey . Alt . toLowerCase ( ) , } ; const isMacOS = isMac ( ) ; const normalizeHotkeySequence = ( a ) => Array . isArray ( a ) ? a . sort ( ) . map ( ( k ) => k . toLowerCase ( ) ) : [ a . toLowerCase ( ) ] ; const normalizeMetaControl = ( initalTuple ) => { const sequence = initalTuple . slice ( 0 , - 1 ) as Sequence ; const callback = initalTuple [ initalTuple . length - 1 ] ; const keys = sequence . reduce < Sequence > ( ( acc , hotkey ) => { if ( Array . isArray ( hotkey ) ) { acc . push ( hotkey . map ( ( k ) => ( ! isMacOS && k === KeyboardKey . Meta ? KeyboardKey . Control : k ) ) ) ; } else { acc . push ( ! isMacOS && hotkey === KeyboardKey . Meta ? KeyboardKey . Control : hotkey ) ; } return acc ; } , [ ] ) ; return [ ... keys , callback ] ; } ; export const useHotkeys = ( ref , hotkeyTupleArray , options ? ) => { const { keyEventType = 'keydown' , sequenceResetTime = 1000 , dependencies = [ ] } = options || { } ; const msSinceLastEvent = useRef ( 0 ) ; const sequence = useRef < Sequence > ( [ ] ) ; const handleKeyDown = ( e ) => { if ( ! e . key ) { return ; } const key = e . key . toLowerCase ( ) as KeyboardKey ; if ( Date . now ( ) - msSinceLastEvent . current > sequenceResetTime ) { sequence . current = [ ] ; } msSinceLastEvent . current = Date . now ( ) ; if ( [ MODIFIER_KEYS . ALT , MODIFIER_KEYS . SHIFT , MODIFIER_KEYS . CONTROL , MODIFIER_KEYS . META ] . includes ( key ) ) { return ; } const isAlphaNumericalOrSpace = key . match ( /[a-zA-Z1-9 ]/gi ) ; const modifiedKey = [ key , e . metaKey && MODIFIER_KEYS . META , e . ctrlKey && MODIFIER_KEYS . CONTROL , isAlphaNumericalOrSpace && e . shiftKey && MODIFIER_KEYS . SHIFT , isAlphaNumericalOrSpace && e . altKey && MODIFIER_KEYS . ALT , ] . filter ( isTruthy ) as Hotkey ; sequence . current . push ( modifiedKey ) ; const normalizedHotkeyTupleArray = hotkeyTupleArray . map ( normalizeMetaControl ) ; for ( let i = 0 ; i < normalizedHotkeyTupleArray . length ; i ++ ) { const hotKeyTuple = normalizedHotkeyTupleArray [ i ] ; const hotkeySequence = ( hotKeyTuple . slice ( 0 , - 1 ) as Sequence ) . map ( normalizeHotkeySequence ) ; const tailSequence = sequence . current . slice ( - hotkeySequence . length ) . map ( normalizeHotkeySequence ) ; if ( isDeepEqual ( hotkeySequence , tailSequence ) ) { const callback = hotKeyTuple [ hotKeyTuple . length - 1 ] as HotKeyCallback ; callback ( e ) ; break ; } } } ; useEventListener ( ref , keyEventType , handleKeyDown , dependencies ) ; } ;