2021-03-24 14:21:21 +00:00
|
|
|
import { LitElement, html, unsafeCSS } from 'lit';
|
|
|
|
import { customElement, property, state } from 'lit/decorators';
|
2021-03-06 17:01:39 +00:00
|
|
|
import { classMap } from 'lit-html/directives/class-map';
|
2021-03-18 13:04:23 +00:00
|
|
|
import { event, EventEmitter, watch } from '../../internal/decorators';
|
2021-02-26 14:09:13 +00:00
|
|
|
import styles from 'sass:./alert.scss';
|
2020-07-15 21:30:37 +00:00
|
|
|
|
2020-09-18 13:40:21 +00:00
|
|
|
const toastStack = Object.assign(document.createElement('div'), { className: 'sl-toast-stack' });
|
2020-09-16 21:00:48 +00:00
|
|
|
|
2020-07-15 21:30:37 +00:00
|
|
|
/**
|
2020-07-17 10:09:10 +00:00
|
|
|
* @since 2.0
|
2020-07-15 21:30:37 +00:00
|
|
|
* @status stable
|
|
|
|
*
|
2021-02-26 14:09:13 +00:00
|
|
|
* @dependency sl-icon-button
|
|
|
|
*
|
2020-07-15 21:30:37 +00:00
|
|
|
* @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.
|
2021-05-17 22:04:28 +00:00
|
|
|
*
|
|
|
|
* @customProperty --box-shadow - The alert's box shadow.
|
2020-07-15 21:30:37 +00:00
|
|
|
*/
|
2021-03-18 13:04:23 +00:00
|
|
|
@customElement('sl-alert')
|
2021-03-09 00:14:32 +00:00
|
|
|
export default class SlAlert extends LitElement {
|
2021-03-06 17:01:39 +00:00
|
|
|
static styles = unsafeCSS(styles);
|
2020-07-15 21:30:37 +00:00
|
|
|
|
2021-02-26 14:09:13 +00:00
|
|
|
private autoHideTimeout: any;
|
2021-03-06 17:01:39 +00:00
|
|
|
|
2021-03-24 14:21:21 +00:00
|
|
|
@state() private isVisible = false;
|
2020-10-13 16:41:57 +00:00
|
|
|
|
2020-07-15 21:30:37 +00:00
|
|
|
/** Indicates whether or not the alert is open. You can use this in lieu of the show/hide methods. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ type: Boolean, reflect: true }) open = false;
|
2020-07-15 21:30:37 +00:00
|
|
|
|
2021-02-26 14:09:13 +00:00
|
|
|
/** Makes the alert closable. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ type: Boolean, reflect: true }) closable = false;
|
2020-07-15 21:30:37 +00:00
|
|
|
|
|
|
|
/** The type of alert. */
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ reflect: true }) type: 'primary' | 'success' | 'info' | 'warning' | 'danger' = 'primary';
|
2020-07-15 21:30:37 +00:00
|
|
|
|
2020-09-16 15:10:25 +00:00
|
|
|
/**
|
2021-02-26 14:09:13 +00:00
|
|
|
* 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. Defaults to `Infinity`.
|
2020-09-16 15:10:25 +00:00
|
|
|
*/
|
2021-03-06 17:01:39 +00:00
|
|
|
@property({ type: Number }) duration = Infinity;
|
|
|
|
|
|
|
|
/** Emitted when the alert opens. Calling `event.preventDefault()` will prevent it from being opened. */
|
|
|
|
@event('sl-show') slShow: EventEmitter<void>;
|
|
|
|
|
|
|
|
/** Emitted after the alert opens and all transitions are complete. */
|
|
|
|
@event('sl-after-show') slAfterShow: EventEmitter<void>;
|
|
|
|
|
|
|
|
/** Emitted when the alert closes. Calling `event.preventDefault()` will prevent it from being closed. */
|
|
|
|
@event('sl-hide') slHide: EventEmitter<void>;
|
|
|
|
|
|
|
|
/** Emitted after the alert closes and all transitions are complete. */
|
|
|
|
@event('sl-after-hide') slAfterHide: EventEmitter<void>;
|
|
|
|
|
|
|
|
connectedCallback() {
|
|
|
|
super.connectedCallback();
|
2020-07-21 19:18:58 +00:00
|
|
|
|
2020-07-15 21:30:37 +00:00
|
|
|
// Show on init if open
|
|
|
|
if (this.open) {
|
|
|
|
this.show();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Shows the alert. */
|
2021-02-26 14:09:13 +00:00
|
|
|
show() {
|
2020-08-13 14:29:31 +00:00
|
|
|
// Prevent subsequent calls to the method, whether manually or triggered by the `open` watcher
|
2020-10-13 12:53:44 +00:00
|
|
|
if (this.isVisible) {
|
2020-08-13 14:29:31 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-07-15 21:30:37 +00:00
|
|
|
|
2021-03-06 17:01:39 +00:00
|
|
|
const slShow = this.slShow.emit();
|
2020-07-15 21:30:37 +00:00
|
|
|
if (slShow.defaultPrevented) {
|
2020-08-13 14:29:31 +00:00
|
|
|
this.open = false;
|
|
|
|
return;
|
2020-07-15 21:30:37 +00:00
|
|
|
}
|
|
|
|
|
2020-10-13 12:53:44 +00:00
|
|
|
this.isVisible = true;
|
2020-07-15 21:30:37 +00:00
|
|
|
this.open = true;
|
2020-09-16 13:34:54 +00:00
|
|
|
|
|
|
|
if (this.duration < Infinity) {
|
|
|
|
this.autoHideTimeout = setTimeout(() => this.hide(), this.duration);
|
|
|
|
}
|
2020-07-15 21:30:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Hides the alert */
|
2021-02-26 14:09:13 +00:00
|
|
|
hide() {
|
2020-08-13 14:29:31 +00:00
|
|
|
// Prevent subsequent calls to the method, whether manually or triggered by the `open` watcher
|
2021-03-22 15:03:24 +00:00
|
|
|
if (!this.open) {
|
2020-08-13 14:29:31 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-07-15 21:30:37 +00:00
|
|
|
|
2021-03-06 17:01:39 +00:00
|
|
|
const slHide = this.slHide.emit();
|
2020-07-15 21:30:37 +00:00
|
|
|
if (slHide.defaultPrevented) {
|
2020-08-13 14:29:31 +00:00
|
|
|
this.open = true;
|
|
|
|
return;
|
2020-07-15 21:30:37 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 13:34:54 +00:00
|
|
|
clearTimeout(this.autoHideTimeout);
|
2020-07-15 21:30:37 +00:00
|
|
|
this.open = false;
|
|
|
|
}
|
|
|
|
|
2020-09-17 20:27:11 +00:00
|
|
|
/**
|
2021-03-22 15:03:24 +00:00
|
|
|
* 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.
|
2020-09-17 20:27:11 +00:00
|
|
|
*/
|
|
|
|
async toast() {
|
2021-01-04 19:11:23 +00:00
|
|
|
return new Promise<void>(resolve => {
|
2020-09-18 13:40:21 +00:00
|
|
|
if (!toastStack.parentElement) {
|
|
|
|
document.body.append(toastStack);
|
2020-09-17 20:27:11 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 14:09:13 +00:00
|
|
|
toastStack.appendChild(this);
|
2021-03-22 15:03:24 +00:00
|
|
|
|
|
|
|
// Wait for the toast stack to render
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
this.clientWidth; // force a reflow for the initial transition
|
|
|
|
this.show();
|
|
|
|
});
|
2020-09-17 20:27:11 +00:00
|
|
|
|
2021-02-26 14:09:13 +00:00
|
|
|
this.addEventListener(
|
2020-10-09 21:45:16 +00:00
|
|
|
'sl-after-hide',
|
2020-09-17 20:27:11 +00:00
|
|
|
() => {
|
2021-02-26 14:09:13 +00:00
|
|
|
toastStack.removeChild(this);
|
2020-09-17 20:27:11 +00:00
|
|
|
resolve();
|
|
|
|
|
2020-09-18 13:40:21 +00:00
|
|
|
// Remove the toast stack from the DOM when there are no more alerts
|
2021-02-26 14:09:13 +00:00
|
|
|
if (!toastStack.querySelector('sl-alert')) {
|
2020-09-18 13:40:21 +00:00
|
|
|
toastStack.remove();
|
2020-09-17 20:27:11 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
{ once: true }
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-02-26 14:09:13 +00:00
|
|
|
restartAutoHide() {
|
|
|
|
clearTimeout(this.autoHideTimeout);
|
|
|
|
if (this.open && this.duration < Infinity) {
|
|
|
|
this.autoHideTimeout = setTimeout(() => this.hide(), this.duration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-15 21:30:37 +00:00
|
|
|
handleCloseClick() {
|
|
|
|
this.hide();
|
|
|
|
}
|
|
|
|
|
2020-09-16 15:10:25 +00:00
|
|
|
handleMouseMove() {
|
|
|
|
this.restartAutoHide();
|
|
|
|
}
|
|
|
|
|
2020-07-15 21:30:37 +00:00
|
|
|
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')) {
|
2020-10-13 16:41:57 +00:00
|
|
|
this.isVisible = this.open;
|
2021-03-06 17:01:39 +00:00
|
|
|
this.open ? this.slAfterShow.emit() : this.slAfterHide.emit();
|
2020-09-16 13:34:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-11 17:56:45 +00:00
|
|
|
@watch('open')
|
|
|
|
handleOpenChange() {
|
2021-02-26 14:09:13 +00:00
|
|
|
this.open ? this.show() : this.hide();
|
|
|
|
}
|
|
|
|
|
2021-03-11 17:56:45 +00:00
|
|
|
@watch('duration')
|
|
|
|
handleDurationChange() {
|
2021-02-26 14:09:13 +00:00
|
|
|
this.restartAutoHide();
|
2020-09-16 15:10:25 +00:00
|
|
|
}
|
|
|
|
|
2020-07-15 21:30:37 +00:00
|
|
|
render() {
|
2021-02-26 14:09:13 +00:00
|
|
|
return html`
|
2020-10-13 16:41:57 +00:00
|
|
|
<div
|
|
|
|
part="base"
|
2021-02-26 14:09:13 +00:00
|
|
|
class=${classMap({
|
2020-10-13 16:41:57 +00:00
|
|
|
alert: true,
|
|
|
|
'alert--open': this.open,
|
|
|
|
'alert--visible': this.isVisible,
|
|
|
|
'alert--closable': this.closable,
|
|
|
|
'alert--primary': this.type === 'primary',
|
|
|
|
'alert--success': this.type === 'success',
|
|
|
|
'alert--info': this.type === 'info',
|
|
|
|
'alert--warning': this.type === 'warning',
|
|
|
|
'alert--danger': this.type === 'danger'
|
2021-02-26 14:09:13 +00:00
|
|
|
})}
|
2020-10-13 16:41:57 +00:00
|
|
|
role="alert"
|
|
|
|
aria-live="assertive"
|
|
|
|
aria-atomic="true"
|
2021-02-26 14:09:13 +00:00
|
|
|
aria-hidden=${this.open ? 'false' : 'true'}
|
2021-03-06 17:01:39 +00:00
|
|
|
@mousemove=${this.handleMouseMove.bind(this)}
|
|
|
|
@transitionend=${this.handleTransitionEnd.bind(this)}
|
2020-10-13 16:41:57 +00:00
|
|
|
>
|
|
|
|
<span part="icon" class="alert__icon">
|
2021-03-06 17:01:39 +00:00
|
|
|
<slot name="icon"></slot>
|
2020-10-13 16:41:57 +00:00
|
|
|
</span>
|
|
|
|
|
|
|
|
<span part="message" class="alert__message">
|
2021-03-06 17:01:39 +00:00
|
|
|
<slot></slot>
|
2020-10-13 16:41:57 +00:00
|
|
|
</span>
|
|
|
|
|
2021-02-26 14:09:13 +00:00
|
|
|
${this.closable
|
|
|
|
? html`
|
|
|
|
<span class="alert__close">
|
2021-03-06 17:01:39 +00:00
|
|
|
<sl-icon-button
|
|
|
|
exportparts="base:close-button"
|
|
|
|
name="x"
|
2021-04-20 13:37:19 +00:00
|
|
|
library="system"
|
2021-03-06 17:01:39 +00:00
|
|
|
@click=${this.handleCloseClick.bind(this)}
|
|
|
|
></sl-icon-button>
|
2021-02-26 14:09:13 +00:00
|
|
|
</span>
|
|
|
|
`
|
|
|
|
: ''}
|
2020-10-13 16:41:57 +00:00
|
|
|
</div>
|
2021-02-26 14:09:13 +00:00
|
|
|
`;
|
2020-07-15 21:30:37 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-12 14:07:38 +00:00
|
|
|
|
2021-03-12 14:09:08 +00:00
|
|
|
declare global {
|
|
|
|
interface HTMLElementTagNameMap {
|
|
|
|
'sl-alert': SlAlert;
|
|
|
|
}
|
|
|
|
}
|