license interface EventData { host : Element ; event : Event ; ns : string ; action : string ; } function getAnchorParent ( target ) { for ( let node = target ; node != document . body ; node = node . parentNode ) { if ( ( node . nodeType === Node . ELEMENT_NODE ) && ( ( node as HTMLElement ) . localName === 'a' ) ) { return node as HTMLElement ; } } return null ; } /**
 * A very simplified version of https://github.com/google/jsaction.
 * (And hence also a lot smaller).
 *
 * All it does is buffer, wake up lazy loaded Angular Elements and replay
 * events. It is not a full event delegation library like jsaction but uses some
 * of the ideas like using attributes from there. Once an Angular Element boots
 * up Angular is in charge of all event handling and tsaction is no longer
 * involved.
 */ export class EventContract { private buffer = new Map < Element , EventData [ ] > ( ) ; private booted = new Set < Element > ( ) ; private routerCallback : ( url : string ) => void ; private replayedEvent : Event | null = null ; constructor ( private container , private types ) { } param event param handler private getMatchingHandler ( event , handler ) { let type = 'click' ; let parts = handler . split ( ':' ) ; if ( parts . length > 1 ) { type = parts . shift ( ) ; } if ( event . type === type ) { const ns_action = parts . shift ( ) ; let ns = 'ng' ; let action = '' ; parts = ns_action . split ( '.' ) ; if ( parts . length == 2 ) { ns = parts [ 0 ] ; action = parts [ 1 ] ; return { ns , action } ; } } return null ; } /**
     * Get the Element with a `tsaction` attribute that matcthes the event type.
     */ private getMatchingTsActionElement ( event ) { let el = event . target as Element ; let match = null ; while ( el != null && el != this . container ) { if ( ! match ) { const attr = el . getAttribute ( 'tsaction' ) ; if ( attr ) { const handlers = attr . split ( ';' ) ; for ( const handler of handlers ) { match = this . getMatchingHandler ( event , handler ) ; if ( match ) { break ; } } } } else { if ( el . localName === match . ns ) { if ( this . booted . has ( el ) ) { return null ; } else { return { host : el , event , ... match } ; } } } el = el . parentElement ; } return null ; } private processRouterLinkClick ( anchor , event ) { const targetLink = anchor . getAttribute ( 'href' ) ; const isAbsolute = targetLink . startsWith ( 'http://' ) || targetLink . startsWith ( 'https://' ) ; if ( this . routerCallback && ! ! targetLink && ! isAbsolute ) { event . preventDefault ( ) ; this . routerCallback ( targetLink ) ; } } private bufferEvent ( event ) { const eventData = this . getMatchingTsActionElement ( event ) ; if ( eventData ) { const existing = this . buffer . get ( eventData . host ) ; if ( existing ) { const appendEvent = ( existing , newData ) => { if ( newData . event . type === 'click' ) { let found = false ; for ( const data of existing ) { if ( data . event . type === 'click' && data . action === newData . action ) { found = true ; break ; } } if ( ! found ) { existing . push ( newData ) ; } } else { existing . push ( newData ) ; } } ; appendEvent ( existing , eventData ) ; } else { this . buffer . set ( eventData . host , [ eventData ] ) ; } eventData . host . setAttribute ( '_boot' , '' ) ; } } private processEvent ( event ) { if ( event === this . replayedEvent ) { return ; } const anchor = getAnchorParent ( event . target as Node ) ; if ( anchor != null ) { this . processRouterLinkClick ( anchor , event ) ; } else { this . bufferEvent ( event ) ; } } /**
     * Start listening to events in the container.
     */ listen ( ) { for ( const type of this . types ) { this . container . addEventListener ( type , this . processEvent . bind ( this ) ) ; } } /**
     * Add custom event listeners.
     * Called when a component that emits this event becomes active.
     */ listenToCustomEvent ( type ) { if ( this . types . indexOf ( type ) == - 1 ) { this . container . addEventListener ( type , this . processEvent . bind ( this ) ) ; this . types . push ( type ) ; } } /**
     * Replay all events in order stored for the given host element.
     */ replay ( el ) { const data = this . buffer . get ( el ) ; if ( ! data ) { return ; } for ( const eventData of data ) { this . replayedEvent = eventData . event ; eventData . event . target . dispatchEvent ( eventData . event ) ; this . replayedEvent = null ; } this . buffer . delete ( el ) ; } /**
     * Mark a host Element as having booted up. Don't buffer any more events for
     * it.
     */ boot ( el ) { this . booted . add ( el ) ; } /**
     * Set a callback for router click links.
     */ setRouterCallback ( callback ) { this . routerCallback = callback ; } }