import { Component, Element, Event, EventEmitter, Host, Method, Prop, Watch, h } from '@stencil/core'; const toastStack = Object.assign(document.createElement('div'), { className: 'sl-toast-stack' }); /** * @since 2.0 * @status stable * * @slot - The alert's content. * @slot icon - An icon to show in the alert. * * @part base - The component's base wrapper. * @part icon - The container that wraps the alert icon. * @part message - The alert message. * @part close-button - The close button. */ @Component({ tag: 'sl-alert', styleUrl: 'alert.scss', shadow: true }) export class Alert { alert: HTMLElement; autoHideTimeout: any; isShowing = false; @Element() host: HTMLSlAlertElement; /** Indicates whether or not the alert is open. You can use this in lieu of the show/hide methods. */ @Prop({ mutable: true, reflect: true }) open = false; /** Set to true to make the alert closable. */ @Prop({ reflect: true }) closable = false; /** The type of alert. */ @Prop({ reflect: true }) type: 'primary' | 'success' | 'info' | 'warning' | 'danger' = 'primary'; /** * The length of time, in milliseconds, the alert will show before closing itself. If the user interacts with the * alert before it closes (e.g. moves the mouse over it), the timer will restart. */ @Prop() duration = Infinity; @Watch('open') handleOpenChange() { this.open ? this.show() : this.hide(); } @Watch('duration') handleDurationChange() { this.restartAutoHide(); } /** Emitted when the alert opens. Calling `event.preventDefault()` will prevent it from being opened. */ @Event({ eventName: 'sl-show' }) slShow: EventEmitter; /** Emitted after the alert opens and all transitions are complete. */ @Event({ eventName: 'sl-after-show' }) slAfterShow: EventEmitter; /** Emitted when the alert closes. Calling `event.preventDefault()` will prevent it from being closed. */ @Event({ eventName: 'sl-hide' }) slHide: EventEmitter; /** Emitted after the alert closes and all transitions are complete. */ @Event({ eventName: 'sl-after-hide' }) slAfterHide: EventEmitter; connectedCallback() { this.handleCloseClick = this.handleCloseClick.bind(this); this.handleMouseMove = this.handleMouseMove.bind(this); this.handleTransitionEnd = this.handleTransitionEnd.bind(this); } componentDidLoad() { // Show on init if open if (this.open) { this.show(); } } /** Shows the alert. */ @Method() async show() { // Prevent subsequent calls to the method, whether manually or triggered by the `open` watcher if (this.isShowing) { return; } const slShow = this.slShow.emit(); if (slShow.defaultPrevented) { this.open = false; return; } this.host.hidden = false; this.host.clientWidth; // force a reflow this.isShowing = true; this.open = true; if (this.duration < Infinity) { this.autoHideTimeout = setTimeout(() => this.hide(), this.duration); } } /** Hides the alert */ @Method() async hide() { // Prevent subsequent calls to the method, whether manually or triggered by the `open` watcher if (!this.isShowing) { return; } const slHide = this.slHide.emit(); if (slHide.defaultPrevented) { this.open = true; return; } clearTimeout(this.autoHideTimeout); this.isShowing = false; this.open = false; } /** * Displays the alert as a toast notification. This will move the alert out of its position in the DOM and, when * dismissed, it will be removed from the DOM completely. By storing a reference to the alert, you can reuse it by * calling this method again. The returned promise will resolve after the alert is hidden. */ @Method() async toast() { return new Promise(resolve => { if (!toastStack.parentElement) { document.body.append(toastStack); } toastStack.append(this.host); this.show(); this.host.addEventListener( 'sl-after-hide', () => { this.host.remove(); resolve(); // Remove the toast stack from the DOM when there are no more alerts if (toastStack.querySelector('sl-alert') === null) { toastStack.remove(); } }, { once: true } ); }); } handleCloseClick() { this.hide(); } handleMouseMove() { this.restartAutoHide(); } handleTransitionEnd(event: TransitionEvent) { const target = event.target as HTMLElement; // Ensure we only emit one event when the target element is no longer visible if (event.propertyName === 'opacity' && target.classList.contains('alert')) { this.host.hidden = !this.open; this.open ? this.slAfterShow.emit() : this.slAfterHide.emit(); } } restartAutoHide() { clearTimeout(this.autoHideTimeout); if (this.open && this.duration < Infinity) { this.autoHideTimeout = setTimeout(() => this.hide(), this.duration); } } render() { return ( ); } }