import { Injectable , Renderer2 , RendererFactory2 } from '@angular/core' ; import { BehaviorSubject , Observable } from 'rxjs' ; @ Injectable ( ) export class ManagedFocusContainerService { private _containers : ManagedFocusContainerWithReferences [ ] = [ ] ; private _renderer : Renderer2 ; constructor ( rendererFactory ) { this . _renderer = rendererFactory . createRenderer ( null , null ) ; } param element param component register ( element , component ) { let containerRef = this . _containers . find ( ref => ref . container . element . isEqualNode ( element ) ) ; if ( ! containerRef ) { containerRef = new ManagedFocusContainerWithReferences ( new ManagedFocusContainer ( element , this . _renderer ) ) ; this . _containers . push ( containerRef ) ; containerRef . container . register ( ) ; } if ( component ) { containerRef . addReference ( component ) ; } } param element param component unregister ( element , component ) { const containerRef = this . _containers . find ( ref => ref . container . element . isEqualNode ( element ) ) ; if ( ! containerRef ) { return ; } containerRef . removeReference ( component ) ; if ( ! containerRef . isAlive ( ) ) { containerRef . container . unregister ( ) ; this . _containers = this . _containers . filter ( c => c !== containerRef ) ; } } param element hasFocus ( element ) { const container = this . getContainer ( element ) ; return container ? container . hasFocus$ . asObservable ( ) : null ; } private getContainer ( element ) { const containerRef = this . _containers . find ( ref => ref . container . element . isEqualNode ( element ) ) ; return containerRef ? containerRef . container : null ; } } class ManagedFocusContainer { /** Whether the container or one of its descendants has focus. */ readonly hasFocus$ = new BehaviorSubject < boolean > ( false ) ; private _modifiedElements : ManagedFocusElementInfo [ ] = [ ] ; private _unlisten : ( ( ) => void ) [ ] = [ ] ; constructor ( public readonly element , private _renderer ) { } /** Start managing the focus of child elements. */ register ( ) { this . _unlisten . push ( this . _renderer . listen ( this . element , 'focusin' , this . onFocusIn . bind ( this ) ) ) ; this . _unlisten . push ( this . _renderer . listen ( this . element , 'focusout' , this . onFocusOut . bind ( this ) ) ) ; setTimeout ( ( ) => { if ( ! this . element . contains ( document . activeElement ) ) { this . removeTabFocus ( ) ; } } ) ; } /** Stop managing the focus of child elements. */ unregister ( ) { this . _unlisten . forEach ( unlisten => unlisten ( ) ) ; this . _unlisten = [ ] ; this . restoreTabFocus ( ) ; } private onFocusIn ( ) { this . restoreTabFocus ( ) ; } private onFocusOut ( ) { this . removeTabFocus ( ) ; } private removeTabFocus ( ) { this . hasFocus$ . next ( false ) ; this . _modifiedElements = [ ] ; const focusable = this . element . querySelectorAll ( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ) ; Array . from ( focusable ) . forEach ( element => { const originalTabIndex = element . getAttribute ( 'tabindex' ) ; this . _renderer . setAttribute ( element , 'tabindex' , '-1' ) ; this . _modifiedElements . push ( { element , tabindex : originalTabIndex } ) ; } ) ; } private restoreTabFocus ( ) { this . hasFocus$ . next ( true ) ; this . _modifiedElements . forEach ( elementInfo => { if ( elementInfo . tabindex === null ) { this . _renderer . removeAttribute ( elementInfo . element , 'tabindex' ) ; } else { this . _renderer . setAttribute ( elementInfo . element , 'tabindex' , elementInfo . tabindex ) ; } } ) ; this . _modifiedElements = [ ] ; } } interface ManagedFocusElementInfo { element : Element ; tabindex : string ; } class ManagedFocusContainerWithReferences { private _components : any [ ] = [ ] ; constructor ( public container ) { } addReference ( component ) { this . _components . push ( component ) ; } removeReference ( component ) { this . _components = this . _components . filter ( c => c !== component ) ; } isAlive ( ) { return this . _components . length > 0 ; } }