import { AnimationEvent } from '@angular/animations' ; import { BasePortalOutlet , CdkPortalOutlet , ComponentPortal , DomPortal , TemplatePortal , } from '@angular/cdk/portal' ; import { ChangeDetectionStrategy , ChangeDetectorRef , Component , ComponentRef , ElementRef , EmbeddedViewRef , NgZone , OnDestroy , ViewChild , ViewEncapsulation , } from '@angular/core' ; import { Observable , Subject } from 'rxjs' ; import { take } from 'rxjs/operators' ; import { SBB_NOTIFICATION_TOAST_ANIMATIONS } from './notification-toast-animations' ; import { SbbNotificationToastConfig } from './notification-toast-config' ; @ Component ( { selector : 'sbb-notification-toast-container' , templateUrl : './notification-toast-container.html' , styleUrls : [ './notification-toast-container.css' ] , changeDetection : ChangeDetectionStrategy . Default , encapsulation : ViewEncapsulation . None , animations : [ SBB_NOTIFICATION_TOAST_ANIMATIONS . notificationState ] , host : { class : 'sbb-notification-toast' , '[class.sbb-notification-toast-success]' : 'config.type === "success"' , '[class.sbb-notification-toast-info]' : 'config.type === "info"' , '[class.sbb-notification-toast-warn]' : 'config.type === "warn"' , '[class.sbb-notification-toast-error]' : 'config.type === "error"' , '[attr.role]' : '_role' , '[@state]' : '_animationState' , '(@state.done)' : 'onAnimationEnd($event)' , } , } ) export class SbbNotificationToastContainer extends BasePortalOutlet implements OnDestroy { private _destroyed = false ; /** The portal outlet inside of this container into which the notification toast content will be loaded. */ @ ViewChild ( CdkPortalOutlet , { static : true } ) _portalOutlet : CdkPortalOutlet ; /** Subject for notifying that the notification toast has exited from view. */ readonly _onExit : Subject < void > = new Subject < void > ( ) ; /** Subject for notifying that the notification toast has finished entering the view. */ readonly _onEnter : Subject < void > = new Subject < void > ( ) ; /** The state of the notification toast animations. */ _animationState : string = 'void' ; /** ARIA role for the notification toast container. */ _role : 'alert' | 'status' | null ; get _svgIcon ( ) { switch ( this . config . type ) { case 'success' : return 'kom:tick-small' ; case 'error' : case 'warn' : return 'kom:sign-exclamation-point-small' ; default : return 'kom:circle-information-small' ; } } constructor ( private _ngZone , private _elementRef , private _changeDetectorRef , public config ) { super ( ) ; if ( config . politeness === 'assertive' && ! config . announcementMessage ) { this . _role = 'alert' ; } else if ( config . politeness === 'off' ) { this . _role = null ; } else { this . _role = 'status' ; } } /** Attach a component portal as content to this notification toast container. */ attachComponentPortal < T > ( portal ) { this . _assertNotAttached ( ) ; this . _applyNotificationClasses ( ) ; return this . _portalOutlet . attachComponentPortal ( portal ) ; } /** Attach a template portal as content to this notification toast container. */ attachTemplatePortal < C > ( portal ) { this . _assertNotAttached ( ) ; this . _applyNotificationClasses ( ) ; return this . _portalOutlet . attachTemplatePortal ( portal ) ; } deprecated breaking-change override attachDomPortal = ( portal ) => { this . _assertNotAttached ( ) ; this . _applyNotificationClasses ( ) ; return this . _portalOutlet . attachDomPortal ( portal ) ; } ; /** Handle end of animations, updating the state of the notification toast. */ onAnimationEnd ( event ) { const { fromState , toState } = event ; if ( ( toState === 'void' && fromState !== 'void' ) || toState === 'hidden' ) { this . _completeExit ( ) ; } if ( toState === 'visible' ) { const onEnter = this . _onEnter ; this . _ngZone . run ( ( ) => { onEnter . next ( ) ; onEnter . complete ( ) ; } ) ; } } /** Begin animation of notification toast entrance into view. */ enter ( ) { if ( ! this . _destroyed ) { this . _animationState = 'visible' ; this . _changeDetectorRef . detectChanges ( ) ; } } /** Begin animation of the notification toast exiting from view. */ exit ( ) { this . _animationState = 'hidden' ; this . _elementRef . nativeElement . setAttribute ( 'sbb-exit' , '' ) ; return this . _onExit ; } /** Makes sure the exit callbacks have been invoked when the element is destroyed. */ ngOnDestroy ( ) { this . _destroyed = true ; this . _completeExit ( ) ; } /**
     * Waits for the zone to settle before removing the element. Helps prevent
     * errors where we end up removing an element which is in the middle of an animation.
     */ private _completeExit ( ) { this . _ngZone . onMicrotaskEmpty . pipe ( take ( 1 ) ) . subscribe ( ( ) => { this . _onExit . next ( ) ; this . _onExit . complete ( ) ; } ) ; } /** Applies the various positioning and user-configured CSS classes to the notification toast. */ private _applyNotificationClasses ( ) { const element = this . _elementRef . nativeElement ; const panelClasses = this . config . panelClass ; if ( panelClasses ) { if ( Array . isArray ( panelClasses ) ) { panelClasses . forEach ( ( cssClass ) => element . classList . add ( cssClass ) ) ; } else { element . classList . add ( panelClasses ) ; } } element . classList . add ( 'sbb-notification-toast-center' ) ; if ( this . config . verticalPosition === 'top' ) { element . classList . add ( 'sbb-notification-toast-top' ) ; } } /** Asserts that no content is already attached to the container. */ private _assertNotAttached ( ) { if ( this . _portalOutlet . hasAttached ( ) ) { throw Error ( 'Attempting to attach notification content after content is already attached' ) ; } } }