260 lines
6.0 KiB
JavaScript
260 lines
6.0 KiB
JavaScript
/**
|
|
@license
|
|
Copyright (c) 2022 trading_peter
|
|
This program is available under Apache License Version 2.0
|
|
*/
|
|
|
|
import './tp-toast.js';
|
|
import '@tp/helpers/debounce.js';
|
|
import '@tp/tp-icon/tp-icon.js';
|
|
import '@tp/tp-media-query/tp-media-query.js';
|
|
import { debounce } from '@tp/helpers/debounce.js';
|
|
import { closest } from '@tp/helpers/closest.js';
|
|
import { EventHelpers } from '@tp/helpers/event-helpers.js';
|
|
import { LitElement, html, css, svg } from 'lit';
|
|
|
|
export default class TpToaster extends EventHelpers(LitElement) {
|
|
static get styles() {
|
|
return [
|
|
css`
|
|
:host {
|
|
display: inline-block;
|
|
position: fixed;
|
|
top: 15px;
|
|
left: 50%;
|
|
width: 50%;
|
|
z-index: 10000;
|
|
transform: translateY(-60px);
|
|
}
|
|
|
|
:host [hidden] {
|
|
display: none;
|
|
}
|
|
|
|
#infos {
|
|
display: inline-block;
|
|
font-size: 0.7em;
|
|
border-radius: 4px;
|
|
background: #FAFAFA;
|
|
transition: transform 0.3s;
|
|
will-change: transform;
|
|
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14),
|
|
0 1px 8px 0 rgba(0, 0, 0, 0.12),
|
|
0 3px 3px -2px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
#infos .info-wrap {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
#infos[show-infos] {
|
|
transform: translateY(60px);
|
|
}
|
|
|
|
#infos .info-wrap tp-icon {
|
|
padding: 5px;
|
|
--tp-icon-width: 18px;
|
|
--tp-icon-height: 18px;
|
|
}
|
|
|
|
#infos .info-wrap > div {
|
|
padding: 0 5px;
|
|
}
|
|
|
|
#wrap {
|
|
transition: transform 0.3s;
|
|
will-change: transform;
|
|
transform: translateY(30px);
|
|
}
|
|
|
|
#wrap[show-infos] {
|
|
transform: translateY(70px);
|
|
}
|
|
|
|
:host ::slotted(tp-toast) {
|
|
position: absolute;
|
|
}
|
|
|
|
@media all and (min-width: 0) and (max-width: 480px) {
|
|
:host {
|
|
left: 2%;
|
|
width: 96%
|
|
}
|
|
}
|
|
`
|
|
];
|
|
}
|
|
|
|
render() {
|
|
const { hiddenCount, dismissLabel, moreLabel } = this;
|
|
|
|
const show = this.toasts.length > 1;
|
|
|
|
return html`
|
|
<tp-media-query @media-query-update=${this.queryMatchChanged}></tp-media-query>
|
|
|
|
<div id="infos" ?show-infos=${show}>
|
|
<div class="info-wrap">
|
|
<tp-icon .icon=${TpToaster.clearAllIcon} @click=${this.dismissAll}>${dismissLabel}</tp-icon>
|
|
<div ?hidden=${hiddenCount === 0}><span>${hiddenCount}</span> <span>${moreLabel}</span></div>
|
|
</div>
|
|
</div>
|
|
<div id="wrap" ?show-infos=${show}>
|
|
<slot></slot>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
static get properties() {
|
|
return {
|
|
maxVisible: { type: Number },
|
|
count: { type: Number },
|
|
hiddenCount: { type: Number },
|
|
dismissLabel: { type: String },
|
|
moreLabel: { type: String },
|
|
};
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.updateList = debounce(this.updateList.bind(this), 50);
|
|
window.TpToaster = this;
|
|
this.moreLabel = 'more';
|
|
this.hiddenCount = 0;
|
|
this.maxVisible = 4;
|
|
}
|
|
|
|
static get clearAllIcon() {
|
|
return svg`<path fill="var(--tp-toaster-icon-color, #000000)" d="M5,13H19V11H5M3,17H17V15H3M7,7V9H21V7" />`;
|
|
}
|
|
|
|
get toasts() {
|
|
const slot = this.shadowRoot.querySelector('slot');
|
|
|
|
if (!slot) return [];
|
|
|
|
return slot
|
|
.assignedNodes({ flatten:true })
|
|
.filter(n => n.nodeType === Node.ELEMENT_NODE);
|
|
}
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
|
|
this.listen(this, 'click', 'onClick');
|
|
this.listen(this, 'toast-dismissed', 'onToastDismissed');
|
|
}
|
|
|
|
/**
|
|
* Adds a new toast. This can be a `tp-toast` element or a object with the properties:
|
|
* ```json
|
|
* {
|
|
* "content": "The message to display",
|
|
* "type": "warning",
|
|
* "delay": "5000"
|
|
* }
|
|
* ```
|
|
* If the value of `content` starts with `i18n.`, the toaster tries to translate the string.
|
|
*
|
|
* @param toast
|
|
*/
|
|
add(toast) {
|
|
toast = Object.assign({
|
|
content: '',
|
|
type: 'info',
|
|
delay: 5000
|
|
}, toast);
|
|
|
|
const newContent = document.createElement('div');
|
|
newContent.textContent = toast.content;
|
|
|
|
const newToast = document.createElement('tp-toast');
|
|
newToast.type = toast.type;
|
|
newToast.delay = toast.delay;
|
|
newToast.sticky = toast.sticky;
|
|
newToast.icon = toast.icon;
|
|
newToast.appendChild(newContent);
|
|
|
|
this.appendChild(newToast);
|
|
this.updateList();
|
|
}
|
|
|
|
/**
|
|
* Dismiss all non-sticky toasts.
|
|
*/
|
|
dismissAll() {
|
|
this.toasts.forEach(toast => {
|
|
if (!toast.sticky) {
|
|
toast.dismiss();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Dismiss all sticky toasts.
|
|
*/
|
|
dismissSticky() {
|
|
this.toasts.forEach(toast => {
|
|
if (toast.sticky) {
|
|
toast.dismiss();
|
|
}
|
|
});
|
|
}
|
|
|
|
onClick(e) {
|
|
const target = closest(e.target, 'tp-toast', true);
|
|
if (target && !target.isDismissed) {
|
|
target.parentNode.appendChild(target);
|
|
setTimeout(() => {
|
|
this.updateList();
|
|
}, 50);
|
|
}
|
|
}
|
|
|
|
onToastDismissed(e) {
|
|
this.removeChild(e.detail.toast);
|
|
this.updateList();
|
|
}
|
|
|
|
updateList() {
|
|
const toasts = this.toasts;
|
|
this.count = toasts.length;
|
|
|
|
const hiddenToasts = toasts.slice(0, toasts.length - this.maxVisible);
|
|
|
|
for (let i = 0, li = hiddenToasts.length; i < li; ++i) {
|
|
hiddenToasts[i].translateY = -150;
|
|
hiddenToasts[i].stopDelay();
|
|
}
|
|
|
|
const visibleToasts = toasts.slice(Math.max(0, toasts.length - this.maxVisible));
|
|
|
|
for (let i = 0, li = visibleToasts.length; i < li; ++i) {
|
|
visibleToasts[i].translateY = 30 * i;
|
|
|
|
if (i === li - 1) {
|
|
visibleToasts[i].activateDelay();
|
|
} else {
|
|
visibleToasts[i].stopDelay();
|
|
}
|
|
}
|
|
|
|
this.hiddenCount = Math.max(0, this.count - this.maxVisible);
|
|
}
|
|
|
|
shouldShowMoreLabel(hiddenCount) {
|
|
return hiddenCount > 0;
|
|
}
|
|
|
|
queryMatchChanged(e) {
|
|
this.maxVisible = e.detail.value ? 1 : 4;
|
|
if (this.isConnected) {
|
|
this.updateList();
|
|
}
|
|
}
|
|
}
|
|
|
|
window.customElements.define('tp-toaster', TpToaster);
|