First working version

This commit is contained in:
pk 2023-07-13 16:13:44 +02:00
parent 7a2a9b1dd6
commit c996f4bff1
6 changed files with 659 additions and 39 deletions

View File

@ -1 +1 @@
# tp-element # tp-toaster

179
package-lock.json generated Normal file
View File

@ -0,0 +1,179 @@
{
"name": "@tp/tp-toaster",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@tp/tp-toaster",
"version": "0.0.1",
"license": "Apache-2.0",
"dependencies": {
"@tp/helpers": "^1.2.1",
"@tp/tp-icon": "^1.0.1",
"@tp/tp-media-query": "^1.0.0",
"lit": "^2.2.0"
}
},
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz",
"integrity": "sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ=="
},
"node_modules/@lit/reactive-element": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.2.tgz",
"integrity": "sha512-rDfl+QnCYjuIGf5xI2sVJWdYIi56CTCwWa+nidKYX6oIuBYwUbT/vX4qbUDlHiZKJ/3FRNQ/tWJui44p6/stSA==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"node_modules/@tp/helpers": {
"version": "1.2.1",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Fhelpers/-/1.2.1/helpers-1.2.1.tgz",
"integrity": "sha512-ukcITyYE10lFT9lTOuu8UQM0s/qKIfw6ihYHVZbUViKyMgGNihWPIQSTZJS5UV2bVbBtQdwQJ+91WcO79ymk3g==",
"license": "Apache-2.0"
},
"node_modules/@tp/tp-icon": {
"version": "1.0.1",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Ftp-icon/-/1.0.1/tp-icon-1.0.1.tgz",
"integrity": "sha512-rBbQoXZ5t35F7yIbPAEGAlDscZhxLZ5/o229kyiBBrXvCrc+aVOsetSwF1jPeBSmb57h2PfinIvQhtMARwWHoA==",
"license": "Apache-2.0",
"dependencies": {
"@tp/tp-tooltip": "^1.0.0",
"lit": "^2.2.0"
}
},
"node_modules/@tp/tp-media-query": {
"version": "1.0.0",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Ftp-media-query/-/1.0.0/tp-media-query-1.0.0.tgz",
"integrity": "sha512-JanA89HDvkn6mw/A9eM9d7Awb5lTZwpmweiEAYjwq0TZaGRzjiOVvLjQnyx+VFqtk9o6E5eQwuKJzLlHExQO+Q==",
"license": "Apache-2.0",
"dependencies": {
"lit": "^2.2.0"
}
},
"node_modules/@tp/tp-tooltip": {
"version": "1.0.0",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Ftp-tooltip/-/1.0.0/tp-tooltip-1.0.0.tgz",
"integrity": "sha512-wal/DPJH73rz9RbHg66ZciZUyjqfeTKMSImEVWczwjXGoPTG9n5FL5+tPyikpgFr5KDhDKlW8/Q0niBbGnc5KA==",
"license": "Apache-2.0",
"dependencies": {
"@tp/helpers": "^1.0.0",
"lit": "^2.2.0"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
},
"node_modules/lit": {
"version": "2.7.6",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.7.6.tgz",
"integrity": "sha512-1amFHA7t4VaaDe+vdQejSVBklwtH9svGoG6/dZi9JhxtJBBlqY5D1RV7iLUYY0trCqQc4NfhYYZilZiVHt7Hxg==",
"dependencies": {
"@lit/reactive-element": "^1.6.0",
"lit-element": "^3.3.0",
"lit-html": "^2.7.0"
}
},
"node_modules/lit-element": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.2.tgz",
"integrity": "sha512-xXAeVWKGr4/njq0rGC9dethMnYCq5hpKYrgQZYTzawt9YQhMiXfD+T1RgrdY3NamOxwq2aXlb0vOI6e29CKgVQ==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.1.0",
"@lit/reactive-element": "^1.3.0",
"lit-html": "^2.7.0"
}
},
"node_modules/lit-html": {
"version": "2.7.5",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.7.5.tgz",
"integrity": "sha512-YqUzpisJodwKIlbMFCtyrp58oLloKGnnPLMJ1t23cbfIJjg/H9pvLWK4XS69YeubK5HUs1UE4ys9w5dP1zg6IA==",
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
}
},
"dependencies": {
"@lit-labs/ssr-dom-shim": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz",
"integrity": "sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ=="
},
"@lit/reactive-element": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.2.tgz",
"integrity": "sha512-rDfl+QnCYjuIGf5xI2sVJWdYIi56CTCwWa+nidKYX6oIuBYwUbT/vX4qbUDlHiZKJ/3FRNQ/tWJui44p6/stSA==",
"requires": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"@tp/helpers": {
"version": "1.2.1",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Fhelpers/-/1.2.1/helpers-1.2.1.tgz",
"integrity": "sha512-ukcITyYE10lFT9lTOuu8UQM0s/qKIfw6ihYHVZbUViKyMgGNihWPIQSTZJS5UV2bVbBtQdwQJ+91WcO79ymk3g=="
},
"@tp/tp-icon": {
"version": "1.0.1",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Ftp-icon/-/1.0.1/tp-icon-1.0.1.tgz",
"integrity": "sha512-rBbQoXZ5t35F7yIbPAEGAlDscZhxLZ5/o229kyiBBrXvCrc+aVOsetSwF1jPeBSmb57h2PfinIvQhtMARwWHoA==",
"requires": {
"@tp/tp-tooltip": "^1.0.0",
"lit": "^2.2.0"
}
},
"@tp/tp-media-query": {
"version": "1.0.0",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Ftp-media-query/-/1.0.0/tp-media-query-1.0.0.tgz",
"integrity": "sha512-JanA89HDvkn6mw/A9eM9d7Awb5lTZwpmweiEAYjwq0TZaGRzjiOVvLjQnyx+VFqtk9o6E5eQwuKJzLlHExQO+Q==",
"requires": {
"lit": "^2.2.0"
}
},
"@tp/tp-tooltip": {
"version": "1.0.0",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Ftp-tooltip/-/1.0.0/tp-tooltip-1.0.0.tgz",
"integrity": "sha512-wal/DPJH73rz9RbHg66ZciZUyjqfeTKMSImEVWczwjXGoPTG9n5FL5+tPyikpgFr5KDhDKlW8/Q0niBbGnc5KA==",
"requires": {
"@tp/helpers": "^1.0.0",
"lit": "^2.2.0"
}
},
"@types/trusted-types": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
},
"lit": {
"version": "2.7.6",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.7.6.tgz",
"integrity": "sha512-1amFHA7t4VaaDe+vdQejSVBklwtH9svGoG6/dZi9JhxtJBBlqY5D1RV7iLUYY0trCqQc4NfhYYZilZiVHt7Hxg==",
"requires": {
"@lit/reactive-element": "^1.6.0",
"lit-element": "^3.3.0",
"lit-html": "^2.7.0"
}
},
"lit-element": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.2.tgz",
"integrity": "sha512-xXAeVWKGr4/njq0rGC9dethMnYCq5hpKYrgQZYTzawt9YQhMiXfD+T1RgrdY3NamOxwq2aXlb0vOI6e29CKgVQ==",
"requires": {
"@lit-labs/ssr-dom-shim": "^1.1.0",
"@lit/reactive-element": "^1.3.0",
"lit-html": "^2.7.0"
}
},
"lit-html": {
"version": "2.7.5",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.7.5.tgz",
"integrity": "sha512-YqUzpisJodwKIlbMFCtyrp58oLloKGnnPLMJ1t23cbfIJjg/H9pvLWK4XS69YeubK5HUs1UE4ys9w5dP1zg6IA==",
"requires": {
"@types/trusted-types": "^2.0.2"
}
}
}
}

View File

@ -1,18 +1,21 @@
{ {
"name": "@tp/tp-element", "name": "@tp/tp-toaster",
"version": "0.0.1", "version": "0.0.1",
"description": "", "description": "",
"main": "tp-element.js", "main": "tp-toaster.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.codeblob.work/tp-elements/tp-element.git" "url": "https://gitea.codeblob.work/tp-elements/tp-toaster.git"
}, },
"author": "trading_peter", "author": "trading_peter",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@tp/helpers": "^1.2.1",
"@tp/tp-icon": "^1.0.1",
"@tp/tp-media-query": "^1.0.0",
"lit": "^2.2.0" "lit": "^2.2.0"
} }
} }

View File

@ -1,35 +0,0 @@
/**
@license
Copyright (c) 2022 trading_peter
This program is available under Apache License Version 2.0
*/
import { LitElement, html, css } from 'lit';
class TpElement extends LitElement {
static get styles() {
return [
css`
:host {
display: block;
}
`
];
}
render() {
const { } = this;
return html`
`;
}
static get properties() {
return { };
}
}
window.customElements.define('tp-element', TpElement);

214
tp-toast.js Normal file
View File

@ -0,0 +1,214 @@
/**
@license
Copyright (c) 2023 EDV Wasmeier
*/
import { EventHelpers } from '@tp/helpers/event-helpers.js';
import { LitElement, html, css, svg } from 'lit';
class TpToast extends EventHelpers(LitElement) {
static get styles() {
return [
css`
:host {
--tp-toast-info-icon-color: #fff;
--tp-toast-success-icon-color: #fff;
--tp-toast-error-icon-color: #fff;
--tp-toast-warning-icon-color: #fff;
transition: transform 0.5s, opacity 0.3s;
will-change: transform, opacity;
display: inline-block;
border-radius: 2px;
background: #FAFAFA;
font-size: 0.8em;
cursor: pointer;
opacity: 1;
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);
}
.wrap {
display: flex;
flex-direction: row;
}
.icon {
padding: 10px;
border-right: 1px #fff;
color: #ffffff;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
:host([type="info"]) .icon {
background: #039BE5;
}
:host([type="success"]) .icon {
background: #558B2F;
color: #fff;
}
:host([type="warning"]) .icon {
background: #FFCA28;
}
:host([type="error"]) .icon {
background: #B71C1C;
}
.content {
padding: 10px 10px 10px 10px;
line-height: 24px;
}
.dismiss {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
}
.dismiss tp-icon {
--tp-icon-width: 18px;
--tp-icon-height: 18px;
}
`
];
}
render() {
const { sticky, dismissIcon, icon } = this;
return html`
<div class="wrap">
<div class="icon">
<tp-icon .icon=${icon || TpToast[this.type + 'Icon']}></tp-icon>
</div>
<div class="content">
<slot></slot>
</div>
${!sticky ? html`
<div class="dismiss">
<tp-icon .icon=${dismissIcon || TpToast.defaultDismissIcon} @click=${this.dismiss}></tp-icon>
</div>
` : null}
</div>
`;
}
static get properties() {
return {
icon: { type: Object },
dismissIcon: { type: Object },
/**
* Configures what kind of toast this is.
* Comes with the variants `info`, `warning`, `error`, `success`.
* Each type comes with a pre-defined icon, but via the `icon` property a custom icon can also be used.
*/
type: { type: String, reflect: true },
/**
* Delay till the toast dismisses itself.
* Set in milliseconds.
*/
delay: { type: Number },
/**
* If true, the toast cant be dismissed manually.
*/
sticky: { type: Boolean },
isDismissed: { type: Boolean },
translateY: { type: Number },
};
}
constructor() {
super();
this.type = 'info';
this.delay = 5000;
this.translateY = -150;
this.dismiss = this.dismiss.bind(this);
}
static get defaultDismissIcon() {
return svg`<path fill="var(--tp-toast-dismiss-icon-color)" d="M20 6.91L17.09 4L12 9.09L6.91 4L4 6.91L9.09 12L4 17.09L6.91 20L12 14.91L17.09 20L20 17.09L14.91 12L20 6.91Z" />`;
}
static get infoIcon() {
return svg`<path fill="var(--tp-toast-info-icon-color)" d="M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />`;
}
static get successIcon() {
return svg`<path fill="var(--tp-toast-success-icon-color)" d="M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />`;
}
static get errorIcon() {
return svg`<path fill="var(--tp-toast-error-icon-color)" d="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z" />`;
}
static get warningIcon() {
return svg`<path fill="var(--tp-toast-warning-icon-color)" d="M13 14H11V9H13M13 18H11V16H13M1 21H23L12 2L1 21Z" />`;
}
connectedCallback() {
super.connectedCallback();
this.listen(this, 'transitionend', '_afterTransitioned');
}
shouldUpdate(changes) {
if (changes.has('translateY')) {
this._translateYChanged();
}
return true;
}
/**
* Dismiss the toast right now.
*/
dismiss() {
this.isDismissed = true;
this.style.transform = `translate3d(200px, ${this.translateY}px, 0px)`;
this.style.opacity = '0';
}
/**
* Starts the delay timeout to dismiss the toast automatically.
*/
activateDelay() {
if (this.delay > 0) {
this.stopDelay();
this._delayJob = setTimeout(this.dismiss, this.delay);
}
}
stopDelay() {
if (this._delayJob) {
clearTimeout(this._delayJob);
this._delayJob = null;
}
}
_afterTransitioned(e) {
if (this.isDismissed) {
if (e.propertyName === 'opacity') {
this.dispatchEvent(new CustomEvent('toast-dismissed', { detail: { toast: this } , bubbles: true, composed: true }));
}
}
}
_translateYChanged() {
if (this.isDismissed) {
return;
}
this.style.transform = `translate3d(0px, ${this.translateY}px, 0px)`;
}
}
window.customElements.define('tp-toast', TpToast);

259
tp-toaster.js Normal file
View File

@ -0,0 +1,259 @@
/**
@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: 1009;
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);