import { LiveAnnouncer } from '@angular/cdk/a11y' ; import { BreakpointObserver } from '@angular/cdk/layout' ; import { ComponentType , Overlay , OverlayConfig , OverlayRef } from '@angular/cdk/overlay' ; import { ComponentPortal , PortalInjector , TemplatePortal } from '@angular/cdk/portal' ; import { ComponentRef , EmbeddedViewRef , Inject , Injectable , InjectionToken , Injector , OnDestroy , Optional , SkipSelf , TemplateRef , Type , } from '@angular/core' ; import { Breakpoints } from '@sbb-esta/angular/core' ; import { takeUntil } from 'rxjs/operators' ; import { SbbNotificationToastConfig , SBB_NOTIFICATION_TOAST_DATA , } from './notification-toast-config' ; import { SbbNotificationToastContainer } from './notification-toast-container' ; import { SbbNotificationToastRef } from './notification-toast-ref' ; import { SbbNotificationToastModule } from './notification-toast.module' ; import { SbbSimpleNotification , SbbTextOnlyNotificationToast } from './simple-notification' ; /** Injection token that can be used to specify default notification toast. */ export const SBB_NOTIFICATION_TOAST_DEFAULT_OPTIONS = new InjectionToken < SbbNotificationToastConfig > ( 'notification-default-options' , { providedIn : 'root' , factory : SBB_NOTIFICATION_TOAST_DEFAULT_OPTIONS_FACTORY , } ) ; docs-private export function SBB_NOTIFICATION_TOAST_DEFAULT_OPTIONS_FACTORY ( ) { return new SbbNotificationToastConfig ( ) ; } /** Service to dispatch notification toast messages. */ @ Injectable ( { providedIn : SbbNotificationToastModule } ) export class SbbNotificationToast implements OnDestroy { /**
     * Reference to the current notification toast in the view *at this level* (in the Angular injector tree).
     * If there is a parent notification toast service, all operations should delegate to that parent
     * via `_openednotification toastRef`.
     */ private _notificationRefAtThisLevel : SbbNotificationToastRef < any > | null = null ; /** The component that should be rendered as the notification toast's simple component. */ protected _simpleNotificationToastComponent : Type < SbbTextOnlyNotificationToast > = SbbSimpleNotification ; /** The container component that attaches the provided template or component. */ protected _notificationToastContainerComponent : Type < SbbNotificationToastContainer > = SbbNotificationToastContainer ; /** The CSS class to applie for mobile mode. */ protected _mobileDeviceCssClass : string = 'sbb-notification-toast-mobile' ; /** Reference to the currently opened notification toast at *any* level. */ get _openedNotificationRef ( ) : SbbNotificationToastRef < any > | null { const parent = this . _parentNotification ; return parent ? parent . _openedNotificationRef : this . _notificationRefAtThisLevel ; } set _openedNotificationRef ( value ) { if ( this . _parentNotification ) { this . _parentNotification . _openedNotificationRef = value ; } else { this . _notificationRefAtThisLevel = value ; } } constructor ( private _overlay , private _injector , private _live , private _breakpointObserver , @ Optional ( ) @ SkipSelf ( ) private _parentNotification , @ Inject ( SBB_NOTIFICATION_TOAST_DEFAULT_OPTIONS ) private _defaultConfig ) { } param component param config openFromComponent < T > ( component , config ? ) { return this . _attach ( component , config ) as SbbNotificationToastRef < T > ; } param template param config openFromTemplate ( template , config ? ) { return this . _attach ( template , config ) ; } param message param action param config open ( message , config ? ) { const mergedConfig = { ... this . _defaultConfig , ... config } ; mergedConfig . data = { message } ; if ( mergedConfig . announcementMessage === message ) { mergedConfig . announcementMessage = undefined ; } return this . openFromComponent ( this . _simpleNotificationToastComponent , mergedConfig ) ; } /** Dismisses the currently-visible notification toast. */ dismiss ( ) { if ( this . _openedNotificationRef ) { this . _openedNotificationRef . dismiss ( ) ; } } ngOnDestroy ( ) { if ( this . _notificationRefAtThisLevel ) { this . _notificationRefAtThisLevel . dismiss ( ) ; } } /** Attaches the notification toast container component to the overlay. */ private _attachNotificationToastContainer ( overlayRef , config ) { const userInjector = config ?. viewContainerRef ?. injector ; const injector = new PortalInjector ( userInjector || this . _injector , new WeakMap ( [ [ SbbNotificationToastConfig , config ] ] ) ) ; const containerPortal = new ComponentPortal ( this . _notificationToastContainerComponent , config . viewContainerRef , injector ) ; const containerRef = overlayRef . attach ( containerPortal ) ; containerRef . instance . config = config ; return containerRef . instance ; } /** Places a new component or a template as the content of the notification toast container. */ private _attach < T > ( content , userConfig ? ) { const config = { ... new SbbNotificationToastConfig ( ) , ... this . _defaultConfig , ... userConfig } ; const overlayRef = this . _createOverlay ( config ) ; const container = this . _attachNotificationToastContainer ( overlayRef , config ) ; const notificationRef = new SbbNotificationToastRef < T | EmbeddedViewRef < any > > ( container , overlayRef ) ; if ( content instanceof TemplateRef ) { const portal = new TemplatePortal ( content , null ! , { $implicit : config . data , notificationRef , } as any ) ; notificationRef . instance = container . attachTemplatePortal ( portal ) ; } else { const injector = this . _createInjector ( config , notificationRef ) ; const portal = new ComponentPortal ( content , undefined , injector ) ; const contentRef = container . attachComponentPortal < T > ( portal ) ; notificationRef . instance = contentRef . instance ; } this . _breakpointObserver . observe ( Breakpoints . MobileDevicePortrait ) . pipe ( takeUntil ( overlayRef . detachments ( ) ) ) . subscribe ( ( state ) => { const classList = overlayRef . overlayElement . classList ; state . matches ? classList . add ( this . _mobileDeviceCssClass ) : classList . remove ( this . _mobileDeviceCssClass ) ; } ) ; this . _animateNotification ( notificationRef , config ) ; this . _openedNotificationRef = notificationRef ; return this . _openedNotificationRef ; } /** Animates the old notification toast out and the new one in. */ private _animateNotification ( notificationRef , config ) { notificationRef . afterDismissed ( ) . subscribe ( ( ) => { if ( this . _openedNotificationRef === notificationRef ) { this . _openedNotificationRef = null ; } if ( config . announcementMessage ) { this . _live . clear ( ) ; } } ) ; if ( this . _openedNotificationRef ) { this . _openedNotificationRef . afterDismissed ( ) . subscribe ( ( ) => { notificationRef . containerInstance . enter ( ) ; } ) ; this . _openedNotificationRef . dismiss ( ) ; } else { notificationRef . containerInstance . enter ( ) ; } if ( config . duration && config . duration > 0 ) { notificationRef . afterOpened ( ) . subscribe ( ( ) => notificationRef . _dismissAfter ( config . duration ! ) ) ; } if ( config . announcementMessage ) { this . _live . announce ( config . announcementMessage , config . politeness ) ; } } param config private _createOverlay ( config ) { const overlayConfig = new OverlayConfig ( ) ; const positionStrategy = this . _overlay . position ( ) . global ( ) ; positionStrategy . centerHorizontally ( ) ; if ( config . verticalPosition === 'top' ) { positionStrategy . top ( '0' ) ; } else { positionStrategy . bottom ( '0' ) ; } overlayConfig . positionStrategy = positionStrategy ; return this . _overlay . create ( overlayConfig ) ; } param config param notification private _createInjector < T > ( config , notificationRef ) { const userInjector = config ?. viewContainerRef ?. injector ; return new PortalInjector ( userInjector || this . _injector , new WeakMap < any , any > ( [ [ SbbNotificationToastRef , notificationRef ] , [ SBB_NOTIFICATION_TOAST_DATA , config . data ] , ] ) ) ; } }