license import { Direction , Directionality } from '@angular/cdk/bidi' ; import { ComponentPortal , Portal , PortalOutlet , TemplatePortal } from '@angular/cdk/portal' ; import { ComponentRef , EmbeddedViewRef , NgZone } from '@angular/core' ; import { Location } from '@angular/common' ; import { Observable , Subject , merge , SubscriptionLike , Subscription , Observer } from 'rxjs' ; import { take , takeUntil } from 'rxjs/operators' ; import { OverlayKeyboardDispatcher } from './keyboard/overlay-keyboard-dispatcher' ; import { OverlayConfig } from './overlay-config' ; import { coerceCssPixelValue , coerceArray } from '@angular/cdk/coercion' ; import { OverlayReference } from './overlay-reference' ; import { PositionStrategy } from './position/position-strategy' ; /** An object where all of its properties cannot be written. */ export type ImmutableObject < T > = { readonly [ P in keyof T ] : T [ P ] ; } ; /**
 * Reference to an overlay that has been created with the Overlay service.
 * Used to manipulate or dispose of said overlay.
 */ export class OverlayRef implements PortalOutlet , OverlayReference { private _backdropElement : HTMLElement | null = null ; private _backdropClick : Subject < MouseEvent > = new Subject ( ) ; private _attachments = new Subject < void > ( ) ; private _detachments = new Subject < void > ( ) ; private _positionStrategy : PositionStrategy | undefined ; private _locationChanges : SubscriptionLike = Subscription . EMPTY ; /**
     * Reference to the parent of the `_host` at the time it was detached. Used to restore
     * the `_host` to its original position in the DOM when it gets re-attached.
     */ private _previousHostParent : HTMLElement ; private _keydownEventsObservable : Observable < KeyboardEvent > = Observable . create ( ( observer ) => { const subscription = this . _keydownEvents . subscribe ( observer ) ; this . _keydownEventSubscriptions ++ ; return ( ) => { subscription . unsubscribe ( ) ; this . _keydownEventSubscriptions -- ; } ; } ) ; /** Stream of keydown events dispatched to this overlay. */ _keydownEvents = new Subject < KeyboardEvent > ( ) ; /** Amount of subscriptions to the keydown events. */ _keydownEventSubscriptions = 0 ; constructor ( private _portalOutlet , private _host , private _pane , private _config , private _ngZone , private _keyboardDispatcher , private _document , private _location ? ) { if ( _config . scrollStrategy ) { _config . scrollStrategy . attach ( this ) ; } this . _positionStrategy = _config . positionStrategy ; } /** The overlay's HTML element */ get overlayElement ( ) : HTMLElement { return this . _pane ; } /** The overlay's backdrop HTML element. */ get backdropElement ( ) : HTMLElement | null { return this . _backdropElement ; } /**
     * Wrapper around the panel element. Can be used for advanced
     * positioning where a wrapper with specific styling is
     * required around the overlay pane.
     */ get hostElement ( ) : HTMLElement { return this . _host ; } attach < T > ( portal ) ; attach < T > ( portal ) ; attach ( portal ) ; param portal returns attach ( portal ) { let attachResult = this . _portalOutlet . attach ( portal ) ; if ( this . _positionStrategy ) { this . _positionStrategy . attach ( this ) ; } if ( ! this . _host . parentElement && this . _previousHostParent ) { this . _previousHostParent . appendChild ( this . _host ) ; } this . _updateStackingOrder ( ) ; this . _updateElementSize ( ) ; this . _updateElementDirection ( ) ; if ( this . _config . scrollStrategy ) { this . _config . scrollStrategy . enable ( ) ; } this . _ngZone . onStable . asObservable ( ) . pipe ( take ( 1 ) ) . subscribe ( ( ) => { if ( this . hasAttached ( ) ) { this . updatePosition ( ) ; } } ) ; this . _togglePointerEvents ( true ) ; if ( this . _config . hasBackdrop ) { this . _attachBackdrop ( ) ; } if ( this . _config . panelClass ) { this . _toggleClasses ( this . _pane , this . _config . panelClass , true ) ; } this . _attachments . next ( ) ; this . _keyboardDispatcher . add ( this ) ; if ( this . _config . disposeOnNavigation && this . _location ) { this . _locationChanges = this . _location . subscribe ( ( ) => this . dispose ( ) ) ; } return attachResult ; } returns detach ( ) { if ( ! this . hasAttached ( ) ) { return ; } this . detachBackdrop ( ) ; this . _togglePointerEvents ( false ) ; if ( this . _positionStrategy && this . _positionStrategy . detach ) { this . _positionStrategy . detach ( ) ; } if ( this . _config . scrollStrategy ) { this . _config . scrollStrategy . disable ( ) ; } const detachmentResult = this . _portalOutlet . detach ( ) ; this . _detachments . next ( ) ; this . _keyboardDispatcher . remove ( this ) ; this . _detachContentWhenStable ( ) ; this . _locationChanges . unsubscribe ( ) ; return detachmentResult ; } /** Cleans up the overlay from the DOM. */ dispose ( ) { const isAttached = this . hasAttached ( ) ; if ( this . _positionStrategy ) { this . _positionStrategy . dispose ( ) ; } if ( this . _config . scrollStrategy ) { this . _config . scrollStrategy . disable ( ) ; } this . detachBackdrop ( ) ; this . _locationChanges . unsubscribe ( ) ; this . _keyboardDispatcher . remove ( this ) ; this . _portalOutlet . dispose ( ) ; this . _attachments . complete ( ) ; this . _backdropClick . complete ( ) ; this . _keydownEvents . complete ( ) ; if ( this . _host && this . _host . parentNode ) { this . _host . parentNode . removeChild ( this . _host ) ; this . _host = null ! ; } this . _previousHostParent = this . _pane = null ! ; if ( isAttached ) { this . _detachments . next ( ) ; } this . _detachments . complete ( ) ; } /** Whether the overlay has attached content. */ hasAttached ( ) { return this . _portalOutlet . hasAttached ( ) ; } /** Gets an observable that emits when the backdrop has been clicked. */ backdropClick ( ) { return this . _backdropClick . asObservable ( ) ; } /** Gets an observable that emits when the overlay has been attached. */ attachments ( ) { return this . _attachments . asObservable ( ) ; } /** Gets an observable that emits when the overlay has been detached. */ detachments ( ) { return this . _detachments . asObservable ( ) ; } /** Gets an observable of keydown events targeted to this overlay. */ keydownEvents ( ) { return this . _keydownEventsObservable ; } /** Gets the the current overlay configuration, which is immutable. */ getConfig ( ) { return this . _config ; } /** Updates the position of the overlay based on the position strategy. */ updatePosition ( ) { if ( this . _positionStrategy ) { this . _positionStrategy . apply ( ) ; } } /** Switches to a new position strategy and updates the overlay position. */ updatePositionStrategy ( strategy ) { if ( strategy === this . _positionStrategy ) { return ; } if ( this . _positionStrategy ) { this . _positionStrategy . dispose ( ) ; } this . _positionStrategy = strategy ; if ( this . hasAttached ( ) ) { strategy . attach ( this ) ; this . updatePosition ( ) ; } } /** Update the size properties of the overlay. */ updateSize ( sizeConfig ) { this . _config = { ... this . _config , ... sizeConfig } ; this . _updateElementSize ( ) ; } /** Sets the LTR/RTL direction for the overlay. */ setDirection ( dir ) { this . _config = { ... this . _config , direction : dir } ; this . _updateElementDirection ( ) ; } /**
     * Returns the layout direction of the overlay panel.
     */ getDirection ( ) { const direction = this . _config . direction ; if ( ! direction ) { return 'ltr' ; } return typeof direction === 'string' ? direction : direction . value ; } /** Updates the text direction of the overlay panel. */ private _updateElementDirection ( ) { this . _host . setAttribute ( 'dir' , this . getDirection ( ) ) ; } /** Updates the size of the overlay element based on the overlay config. */ private _updateElementSize ( ) { const style = this . _pane . style ; style . width = coerceCssPixelValue ( this . _config . width ) ; style . height = coerceCssPixelValue ( this . _config . height ) ; style . minWidth = coerceCssPixelValue ( this . _config . minWidth ) ; style . minHeight = coerceCssPixelValue ( this . _config . minHeight ) ; style . maxWidth = coerceCssPixelValue ( this . _config . maxWidth ) ; style . maxHeight = coerceCssPixelValue ( this . _config . maxHeight ) ; } /** Toggles the pointer events for the overlay pane element. */ private _togglePointerEvents ( enablePointer ) { this . _pane . style . pointerEvents = enablePointer ? 'auto' : 'none' ; } /** Attaches a backdrop for this overlay. */ private _attachBackdrop ( ) { const showingClass = 'cdk-overlay-backdrop-showing' ; this . _backdropElement = this . _document . createElement ( 'div' ) ; this . _backdropElement . classList . add ( 'cdk-overlay-backdrop' ) ; if ( this . _config . backdropClass ) { this . _toggleClasses ( this . _backdropElement , this . _config . backdropClass , true ) ; } this . _host . parentElement ! . insertBefore ( this . _backdropElement , this . _host ) ; this . _backdropElement . addEventListener ( 'click' , ( event ) => this . _backdropClick . next ( event ) ) ; if ( typeof requestAnimationFrame !== 'undefined' ) { this . _ngZone . runOutsideAngular ( ( ) => { requestAnimationFrame ( ( ) => { if ( this . _backdropElement ) { this . _backdropElement . classList . add ( showingClass ) ; } } ) ; } ) ; } else { this . _backdropElement . classList . add ( showingClass ) ; } } /**
     * Updates the stacking order of the element, moving it to the top if necessary.
     * This is required in cases where one overlay was detached, while another one,
     * that should be behind it, was destroyed. The next time both of them are opened,
     * the stacking will be wrong, because the detached element's pane will still be
     * in its original DOM position.
     */ private _updateStackingOrder ( ) { if ( this . _host . nextSibling ) { this . _host . parentNode ! . appendChild ( this . _host ) ; } } /** Detaches the backdrop (if any) associated with the overlay. */ detachBackdrop ( ) { let backdropToDetach = this . _backdropElement ; if ( ! backdropToDetach ) { return ; } let timeoutId ; let finishDetach = ( ) => { if ( backdropToDetach && backdropToDetach . parentNode ) { backdropToDetach . parentNode . removeChild ( backdropToDetach ) ; } if ( this . _backdropElement == backdropToDetach ) { this . _backdropElement = null ; } if ( this . _config . backdropClass ) { this . _toggleClasses ( backdropToDetach ! , this . _config . backdropClass , false ) ; } clearTimeout ( timeoutId ) ; } ; backdropToDetach . classList . remove ( 'cdk-overlay-backdrop-showing' ) ; this . _ngZone . runOutsideAngular ( ( ) => { backdropToDetach ! . addEventListener ( 'transitionend' , finishDetach ) ; } ) ; backdropToDetach . style . pointerEvents = 'none' ; timeoutId = this . _ngZone . runOutsideAngular ( ( ) => setTimeout ( finishDetach , 500 ) ) ; } /** Toggles a single CSS class or an array of classes on an element. */ private _toggleClasses ( element , cssClasses , isAdd ) { const classList = element . classList ; coerceArray ( cssClasses ) . forEach ( cssClass => { isAdd ? classList . add ( cssClass ) : classList . remove ( cssClass ) ; } ) ; } /** Detaches the overlay content next time the zone stabilizes. */ private _detachContentWhenStable ( ) { this . _ngZone . runOutsideAngular ( ( ) => { const subscription = this . _ngZone . onStable . asObservable ( ) . pipe ( takeUntil ( merge ( this . _attachments , this . _detachments ) ) ) . subscribe ( ( ) => { if ( ! this . _pane || ! this . _host || this . _pane . children . length === 0 ) { if ( this . _pane && this . _config . panelClass ) { this . _toggleClasses ( this . _pane , this . _config . panelClass , false ) ; } if ( this . _host && this . _host . parentElement ) { this . _previousHostParent = this . _host . parentElement ; this . _previousHostParent . removeChild ( this . _host ) ; } subscription . unsubscribe ( ) ; } } ) ; } ) ; } } /** Size properties for an overlay. */ export interface OverlaySizeConfig { width ? : number | string ; height ? : number | string ; minWidth ? : number | string ; minHeight ? : number | string ; maxWidth ? : number | string ; maxHeight ? : number | string ; }