import { ListenerResult } from './enums' ; import { Shortcut as ShortcutType , ShortcutID , Disposer , RecordHandler , ShortcutsOptions , ShortcutsTree , ShortcutDescriptor } from './types' ; import Listener from './listener' ; import Shortcut from './shortcut' ; import Utils from './utils' ; class Shortcuts { private listener : Listener ; private descriptors : ShortcutDescriptor [ ] ; private shortcuts : ShortcutsTree ; private recordHandler ? : RecordHandler ; constructor ( options = { } ) { this . listener = new Listener ( { capture : options . capture , handler : this . handler , target : options . target , shouldHandleEvent : options . shouldHandleEvent } ) ; this . reset ( ) ; if ( options . shortcuts ) { this . add ( options . shortcuts ) ; } } private _updateListener ( ) { const shouldListen = ! ! this . shortcuts . size ; if ( shouldListen === this . listener . isListening ( ) ) return ; shouldListen ? this . listener . on ( ) : this . listener . off ( ) ; } get ( ) { return this . descriptors ; } add ( descriptors ) { if ( ! ( descriptors instanceof Array ) ) return this . add ( [ descriptors ] ) ; descriptors . forEach ( descriptor => { const { shortcut , handler } = descriptor ; if ( shortcut [ 0 ] === '-' ) return this . remove ( [ { shortcut , handler } ] ) ; if ( ! handler ) return console . error ( `Can\'t add shortcut "${ shortcut }" which has no handler` ) ; const id = Shortcut . shortcut2id ( shortcut ) ; this . descriptors . push ( descriptor ) ; const lastIndex = id . length - 1 ; id . reduce ( ( parent , id , index ) => { if ( ! parent [ id ] ) { parent . size ++ ; parent [ id ] = { parent , id , size : 0 , handlers : [ ] } ; } if ( index === lastIndex ) { parent [ id ] . handlers . unshift ( handler ) ; } return parent [ id ] ; } , this . shortcuts ) ; } ) ; this . _updateListener ( ) ; } register ( descriptors ) { this . add ( descriptors ) ; return ( ) => { this . remove ( descriptors ) ; } ; } remove ( descriptors ) { if ( ! ( descriptors instanceof Array ) ) return this . remove ( [ descriptors ] ) ; descriptors . forEach ( descriptor => { let { shortcut , handler } = descriptor ; if ( shortcut [ 0 ] === '-' ) shortcut = shortcut . slice ( 1 ) ; const id = Shortcut . shortcut2id ( shortcut ) ; this . descriptors = this . descriptors . filter ( d => ( d . shortcut !== shortcut && ! Utils . isEqual ( Shortcut . shortcut2id ( d . shortcut ) , id ) ) || ( handler && d . handler !== handler ) ) ; const lastIndex = id . length - 1 ; id . reduce ( ( parent , id , index ) => { const child = parent [ id ] ; if ( ! child ) return { } ; if ( index === lastIndex ) { if ( handler ) { child . handlers = child . handlers . filter ( h => h !== handler ) ; } else { child . handlers . length = 0 ; } let target = child ; while ( ! target . size && ! target . handlers . length && target . parent && target . id ) { delete target . parent [ target . id ] ; target . parent . size -- ; target = target . parent ; } } return child ; } , this . shortcuts ) ; } ) ; this . _updateListener ( ) ; } reset ( ) { this . descriptors = [ ] ; this . shortcuts = { size : 0 , handlers : [ ] } ; this . _updateListener ( ) ; } record ( handler ) { this . recordHandler = handler ; return ( ) => { delete this . recordHandler ; } ; } trigger ( shortcut ) { const id = typeof shortcut === 'string' ? Shortcut . shortcut2id ( shortcut ) : shortcut ; return this . handler ( id ) === ListenerResult . HANDLED ; } private handler = ( id , event ? ) : ListenerResult | ShortcutID => { if ( this . recordHandler ) { this . recordHandler ( Shortcut . id2accelerator ( id ) ) ; return ListenerResult . UNHANDLED ; } let handleable = false , firstHandleableIndex = - 1 ; outer : for ( let i = 0 , l = id . length ; i < l ; i ++ ) { let target = this . shortcuts ; for ( let ci = i ; ci < l ; ci ++ ) { target = target [ id [ ci ] ] ; if ( ! target ) { if ( ! handleable && i === ( l - 1 ) ) return ListenerResult . UNHANDLEABLE ; continue outer ; } } handleable = true ; if ( firstHandleableIndex === - 1 ) firstHandleableIndex = i ; const { handlers } = target ; for ( let hi = 0 , hl = handlers . length ; hi < hl ; hi ++ ) { if ( handlers [ hi ] ( event ) === true ) { if ( event ) { event . preventDefault ( ) ; event . stopPropagation ( ) ; } return ListenerResult . HANDLED ; } } } if ( firstHandleableIndex > 0 ) return id . slice ( firstHandleableIndex ) ; return ListenerResult . UNHANDLED ; } ; } export default Shortcuts ;