license import { ContentObserver } from '@angular/cdk/observers' ; import { DOCUMENT } from '@angular/common' ; import { Directive , ElementRef , Inject , Injectable , Input , NgZone , OnDestroy , Optional , } from '@angular/core' ; import { Subscription } from 'rxjs' ; import { AriaLivePoliteness , LiveAnnouncerDefaultOptions , LIVE_ANNOUNCER_ELEMENT_TOKEN , LIVE_ANNOUNCER_DEFAULT_OPTIONS , } from './live-announcer-tokens' ; @ Injectable ( { providedIn : 'root' } ) export class LiveAnnouncer implements OnDestroy { private _liveElement : HTMLElement ; private _document : Document ; private _previousTimeout : number ; constructor ( @ Optional ( ) @ Inject ( LIVE_ANNOUNCER_ELEMENT_TOKEN ) elementToken , private _ngZone , @ Inject ( DOCUMENT ) _document , @ Optional ( ) @ Inject ( LIVE_ANNOUNCER_DEFAULT_OPTIONS ) private _defaultOptions ? ) { this . _document = _document ; this . _liveElement = elementToken || this . _createLiveElement ( ) ; } param message returns announce ( message ) ; param message param politeness returns announce ( message , politeness ? ) ; param message param duration returns announce ( message , duration ? ) ; param message param politeness param duration returns announce ( message , politeness ? , duration ? ) ; announce ( message , ... args ) { const defaultOptions = this . _defaultOptions ; let politeness ; let duration ; if ( args . length === 1 && typeof args [ 0 ] === 'number' ) { duration = args [ 0 ] ; } else { [ politeness , duration ] = args ; } this . clear ( ) ; clearTimeout ( this . _previousTimeout ) ; if ( ! politeness ) { politeness = defaultOptions && defaultOptions . politeness ? defaultOptions . politeness : 'polite' ; } if ( duration == null && defaultOptions ) { duration = defaultOptions . duration ; } this . _liveElement . setAttribute ( 'aria-live' , politeness ) ; return this . _ngZone . runOutsideAngular ( ( ) => { return new Promise ( resolve => { clearTimeout ( this . _previousTimeout ) ; this . _previousTimeout = setTimeout ( ( ) => { this . _liveElement . textContent = message ; resolve ( ) ; if ( typeof duration === 'number' ) { this . _previousTimeout = setTimeout ( ( ) => this . clear ( ) , duration ) ; } } , 100 ) ; } ) ; } ) ; } /**
     * Clears the current text from the announcer element. Can be used to prevent
     * screen readers from reading the text out again while the user is going
     * through the page landmarks.
     */ clear ( ) { if ( this . _liveElement ) { this . _liveElement . textContent = '' ; } } ngOnDestroy ( ) { clearTimeout ( this . _previousTimeout ) ; this . _liveElement ?. remove ( ) ; this . _liveElement = null ! ; } private _createLiveElement ( ) { const elementClass = 'cdk-live-announcer-element' ; const previousElements = this . _document . getElementsByClassName ( elementClass ) ; const liveEl = this . _document . createElement ( 'div' ) ; for ( let i = 0 ; i < previousElements . length ; i ++ ) { previousElements [ i ] . remove ( ) ; } liveEl . classList . add ( elementClass ) ; liveEl . classList . add ( 'cdk-visually-hidden' ) ; liveEl . setAttribute ( 'aria-atomic' , 'true' ) ; liveEl . setAttribute ( 'aria-live' , 'polite' ) ; this . _document . body . appendChild ( liveEl ) ; return liveEl ; } } /**
 * A directive that works similarly to aria-live, but uses the LiveAnnouncer to ensure compatibility
 * with a wider range of browsers and screen readers.
 */ @ Directive ( { selector : '[cdkAriaLive]' , exportAs : 'cdkAriaLive' , } ) export class CdkAriaLive implements OnDestroy { /** The aria-live politeness level to use when announcing messages. */ @ Input ( 'cdkAriaLive' ) get politeness ( ) : AriaLivePoliteness { return this . _politeness ; } set politeness ( value ) { this . _politeness = value === 'off' || value === 'assertive' ? value : 'polite' ; if ( this . _politeness === 'off' ) { if ( this . _subscription ) { this . _subscription . unsubscribe ( ) ; this . _subscription = null ; } } else if ( ! this . _subscription ) { this . _subscription = this . _ngZone . runOutsideAngular ( ( ) => { return this . _contentObserver . observe ( this . _elementRef ) . subscribe ( ( ) => { const elementText = this . _elementRef . nativeElement . textContent ; if ( elementText !== this . _previousAnnouncedText ) { this . _liveAnnouncer . announce ( elementText , this . _politeness ) ; this . _previousAnnouncedText = elementText ; } } ) ; } ) ; } } private _politeness : AriaLivePoliteness = 'polite' ; private _previousAnnouncedText ? : string ; private _subscription : Subscription | null ; constructor ( private _elementRef , private _liveAnnouncer , private _contentObserver , private _ngZone ) { } ngOnDestroy ( ) { if ( this . _subscription ) { this . _subscription . unsubscribe ( ) ; } } }