import classNames from "classnames" ; import * as React from "react" ; import * as ReactDOM from "react-dom" ; import { polyfill } from "react-lifecycles-compat" ; import { AbstractPureComponent2 , Classes , Position } from "../../common" ; import { TOASTER_CREATE_NULL , TOASTER_MAX_TOASTS_INVALID , TOASTER_WARN_INLINE } from "../../common/errors" ; import { ESCAPE } from "../../common/keys" ; import { DISPLAYNAME_PREFIX , Props } from "../../common/props" ; import { isNodeEnv } from "../../common/utils" ; import { Overlay } from "../overlay/overlay" ; import { IToastProps , Toast } from "./toast" ; export type IToastOptions = IToastProps & { key : string ; } ; export type ToasterPosition = typeof Position . TOP | typeof Position . TOP_LEFT | typeof Position . TOP_RIGHT | typeof Position . BOTTOM | typeof Position . BOTTOM_LEFT | typeof Position . BOTTOM_RIGHT ; /** Instance methods available on a `<Toaster>` component instance. */ export interface IToaster { /**
     * Shows a new toast to the user, or updates an existing toast corresponding to the provided key (optional).
     *
     * Returns the unique key of the toast.
     */ show ( props , key ? ) : string ; /** Dismiss the given toast instantly. */ dismiss ( key ) : void ; /** Dismiss all toasts instantly. */ clear ( ) : void ; /** Returns the props for all current toasts. */ getToasts ( ) : IToastOptions [ ] ; } /**
 * Props supported by the `<Toaster>` component.
 * These props can be passed as an argument to the static `Toaster.create(props?, container?)` method.
 */ export interface IToasterProps extends Props { default autoFocus ? : boolean ; default canEscapeKeyClear ? : boolean ; default usePortal ? : boolean ; default position ? : ToasterPosition ; default maxToasts ? : number ; } export interface IToasterState { toasts : IToastOptions [ ] ; } @ polyfill export class Toaster extends AbstractPureComponent2 < IToasterProps , IToasterState > implements IToaster { public static displayName = `${ DISPLAYNAME_PREFIX }.Toaster` ; public static defaultProps : IToasterProps = { autoFocus : false , canEscapeKeyClear : true , position : Position . TOP , usePortal : true , } ; /**
     * Create a new `Toaster` instance that can be shared around your application.
     * The `Toaster` will be rendered into a new element appended to the given container.
     */ public static create ( props ? , container = document . body ) { if ( props != null && props . usePortal != null && ! isNodeEnv ( "production" ) ) { console . warn ( TOASTER_WARN_INLINE ) ; } const containerElement = document . createElement ( "div" ) ; container . appendChild ( containerElement ) ; const toaster = ReactDOM . render < IToasterProps > ( < Toaster { ... props } usePortal = { false } / > , containerElement ) as Toaster ; if ( toaster == null ) { throw new Error ( TOASTER_CREATE_NULL ) ; } return toaster ; } public state : IToasterState = { toasts : [ ] , } ; private toastId = 0 ; public show ( props , key ? ) { if ( this . props . maxToasts ) { this . dismissIfAtLimit ( ) ; } const options = this . createToastOptions ( props , key ) ; if ( key === undefined || this . isNewToastKey ( key ) ) { this . setState ( prevState => ( { toasts : [ options , ... prevState . toasts ] , } ) ) ; } else { this . setState ( prevState => ( { toasts : prevState . toasts . map ( t => ( t . key === key ? options : t ) ) , } ) ) ; } return options . key ; } public dismiss ( key , timeoutExpired = false ) { this . setState ( ( { toasts } ) => ( { toasts : toasts . filter ( t => { const matchesKey = t . key === key ; if ( matchesKey ) { t . onDismiss ?. ( timeoutExpired ) ; } return ! matchesKey ; } ) , } ) ) ; } public clear ( ) { this . state . toasts . forEach ( t => t . onDismiss ?. ( false ) ) ; this . setState ( { toasts : [ ] } ) ; } public getToasts ( ) { return this . state . toasts ; } public render ( ) { const classes = classNames ( Classes . TOAST_CONTAINER , this . getPositionClasses ( ) , this . props . className ) ; return ( < Overlay autoFocus = { this . props . autoFocus } canEscapeKeyClose = { this . props . canEscapeKeyClear } canOutsideClickClose = { false } className = { classes } enforceFocus = { false } hasBackdrop = { false } isOpen = { this . state . toasts . length > 0 || this . props . children != null } onClose = { this . handleClose } shouldReturnFocusOnClose = { false } transitionDuration = { 350 } transitionName = { Classes . TOAST } usePortal = { this . props . usePortal } > { this . state . toasts . map ( this . renderToast , this ) } { this . props . children } < /Overlay>); } protected validateProps ( { maxToasts } ) { if ( maxToasts !== undefined && maxToasts < 1 ) { throw new Error ( TOASTER_MAX_TOASTS_INVALID ) ; } } private isNewToastKey ( key ) { return this . state . toasts . every ( toast => toast . key !== key ) ; } private dismissIfAtLimit ( ) { if ( this . state . toasts . length === this . props . maxToasts ) { this . dismiss ( this . state . toasts [ this . state . toasts . length - 1 ] . key ! ) ; } } private renderToast = ( toast ) => { return < Toast { ... toast } onDismiss = { this . getDismissHandler ( toast ) } / > ; } ; private createToastOptions ( props , key = `toast-${ this . toastId ++ }` ) { return { ... props , key } ; } private getPositionClasses ( ) { const positions = this . props . position ! . split ( "-" ) ; return [ ... positions . map ( p => `${ Classes . TOAST_CONTAINER }-${ p . toLowerCase ( ) }` ) , `${ Classes . TOAST_CONTAINER }-${ this . props . usePortal ? "in-portal" : "inline" }` , ] ; } private getDismissHandler = ( toast ) => ( timeoutExpired ) => { this . dismiss ( toast . key , timeoutExpired ) ; } ; private handleClose = ( e ) => { if ( ( e as React . KeyboardEvent < HTMLElement > ) . which === ESCAPE ) { this . clear ( ) ; } } ; } export const OverlayToaster = Toaster ; export type OverlayToasterProps = IToasterProps ;