| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  | import { customElement, DeesElement, type TemplateResult, html, css, property, cssManager } from '@design.estate/dees-element'; | 
					
						
							| 
									
										
										
										
											2020-09-13 16:24:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-07 19:13:29 +02:00
										 |  |  | import * as domtools from '@design.estate/dees-domtools'; | 
					
						
							| 
									
										
										
										
											2025-06-26 15:46:44 +00:00
										 |  |  | import { zIndexLayers } from './00zindex.js'; | 
					
						
							| 
									
										
										
										
											2023-10-11 12:24:12 +02:00
										 |  |  | import { demoFunc } from './dees-toast.demo.js'; | 
					
						
							| 
									
										
										
										
											2025-06-27 17:32:01 +00:00
										 |  |  | import { cssGeistFontFamily } from './00fonts.js'; | 
					
						
							| 
									
										
										
										
											2020-09-13 16:24:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-06 15:48:02 +00:00
										 |  |  | declare global { | 
					
						
							|  |  |  |   interface HTMLElementTagNameMap { | 
					
						
							|  |  |  |     'dees-toast': DeesToast; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  | export type ToastType = 'info' | 'success' | 'warning' | 'error'; | 
					
						
							|  |  |  | export type ToastPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface IToastOptions { | 
					
						
							|  |  |  |   message: string; | 
					
						
							|  |  |  |   type?: ToastType; | 
					
						
							|  |  |  |   duration?: number; | 
					
						
							|  |  |  |   position?: ToastPosition; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-13 16:24:48 +00:00
										 |  |  | @customElement('dees-toast') | 
					
						
							| 
									
										
										
										
											2022-01-24 01:39:47 +01:00
										 |  |  | export class DeesToast extends DeesElement { | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  |   // STATIC
 | 
					
						
							| 
									
										
										
										
											2023-10-11 12:24:12 +02:00
										 |  |  |   public static demo = demoFunc; | 
					
						
							| 
									
										
										
										
											2020-09-13 16:24:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  |   private static toastContainers = new Map<ToastPosition, HTMLDivElement>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private static getOrCreateContainer(position: ToastPosition): HTMLDivElement { | 
					
						
							|  |  |  |     if (!this.toastContainers.has(position)) { | 
					
						
							|  |  |  |       const container = document.createElement('div'); | 
					
						
							|  |  |  |       container.className = `toast-container toast-container-${position}`; | 
					
						
							|  |  |  |       container.style.cssText = `
 | 
					
						
							|  |  |  |         position: fixed; | 
					
						
							| 
									
										
										
										
											2025-06-26 15:46:44 +00:00
										 |  |  |         z-index: ${zIndexLayers.overlay.toast}; | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  |         pointer-events: none; | 
					
						
							|  |  |  |         padding: 16px; | 
					
						
							|  |  |  |         display: flex; | 
					
						
							|  |  |  |         flex-direction: column; | 
					
						
							|  |  |  |         gap: 8px; | 
					
						
							|  |  |  |       `;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Position the container
 | 
					
						
							|  |  |  |       switch (position) { | 
					
						
							|  |  |  |         case 'top-right': | 
					
						
							|  |  |  |           container.style.top = '0'; | 
					
						
							|  |  |  |           container.style.right = '0'; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         case 'top-left': | 
					
						
							|  |  |  |           container.style.top = '0'; | 
					
						
							|  |  |  |           container.style.left = '0'; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         case 'bottom-right': | 
					
						
							|  |  |  |           container.style.bottom = '0'; | 
					
						
							|  |  |  |           container.style.right = '0'; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         case 'bottom-left': | 
					
						
							|  |  |  |           container.style.bottom = '0'; | 
					
						
							|  |  |  |           container.style.left = '0'; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         case 'top-center': | 
					
						
							|  |  |  |           container.style.top = '0'; | 
					
						
							|  |  |  |           container.style.left = '50%'; | 
					
						
							|  |  |  |           container.style.transform = 'translateX(-50%)'; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         case 'bottom-center': | 
					
						
							|  |  |  |           container.style.bottom = '0'; | 
					
						
							|  |  |  |           container.style.left = '50%'; | 
					
						
							|  |  |  |           container.style.transform = 'translateX(-50%)'; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       document.body.appendChild(container); | 
					
						
							|  |  |  |       this.toastContainers.set(position, container); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return this.toastContainers.get(position)!; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static async show(options: IToastOptions | string) { | 
					
						
							|  |  |  |     const opts: IToastOptions = typeof options === 'string'  | 
					
						
							|  |  |  |       ? { message: options }  | 
					
						
							|  |  |  |       : options; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const toast = new DeesToast(); | 
					
						
							|  |  |  |     toast.message = opts.message; | 
					
						
							|  |  |  |     toast.type = opts.type || 'info'; | 
					
						
							|  |  |  |     toast.duration = opts.duration || 3000; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const container = this.getOrCreateContainer(opts.position || 'top-right'); | 
					
						
							|  |  |  |     container.appendChild(toast); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Trigger animation
 | 
					
						
							|  |  |  |     await toast.updateComplete; | 
					
						
							|  |  |  |     requestAnimationFrame(() => { | 
					
						
							|  |  |  |       toast.isVisible = true; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Auto dismiss
 | 
					
						
							|  |  |  |     if (toast.duration > 0) { | 
					
						
							|  |  |  |       setTimeout(() => { | 
					
						
							|  |  |  |         toast.dismiss(); | 
					
						
							|  |  |  |       }, toast.duration); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return toast; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-26 18:13:08 +00:00
										 |  |  |   // Alias for consistency with DeesModal
 | 
					
						
							|  |  |  |   public static async createAndShow(options: IToastOptions | string) { | 
					
						
							|  |  |  |     return this.show(options); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  |   // Convenience methods
 | 
					
						
							|  |  |  |   public static info(message: string, duration?: number) { | 
					
						
							|  |  |  |     return this.show({ message, type: 'info', duration }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static success(message: string, duration?: number) { | 
					
						
							|  |  |  |     return this.show({ message, type: 'success', duration }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static warning(message: string, duration?: number) { | 
					
						
							|  |  |  |     return this.show({ message, type: 'warning', duration }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static error(message: string, duration?: number) { | 
					
						
							|  |  |  |     return this.show({ message, type: 'error', duration }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // INSTANCE
 | 
					
						
							|  |  |  |   @property({ type: String }) | 
					
						
							|  |  |  |   public message: string = ''; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: String }) | 
					
						
							|  |  |  |   public type: ToastType = 'info'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: Number }) | 
					
						
							|  |  |  |   public duration: number = 3000; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ type: Boolean, reflect: true }) | 
					
						
							|  |  |  |   public isVisible: boolean = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-13 16:24:48 +00:00
										 |  |  |   constructor() { | 
					
						
							|  |  |  |     super(); | 
					
						
							|  |  |  |     domtools.elementBasic.setup(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  |   public static styles = [ | 
					
						
							|  |  |  |     cssManager.defaultStyles, | 
					
						
							|  |  |  |     css`
 | 
					
						
							|  |  |  |       :host { | 
					
						
							|  |  |  |         display: block; | 
					
						
							|  |  |  |         pointer-events: auto; | 
					
						
							| 
									
										
										
										
											2025-06-27 17:32:01 +00:00
										 |  |  |         font-family: ${cssGeistFontFamily}; | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  |         opacity: 0; | 
					
						
							|  |  |  |         transform: translateY(-10px); | 
					
						
							|  |  |  |         transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       :host([isvisible]) { | 
					
						
							|  |  |  |         opacity: 1; | 
					
						
							|  |  |  |         transform: translateY(0); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .toast { | 
					
						
							|  |  |  |         display: flex; | 
					
						
							|  |  |  |         align-items: center; | 
					
						
							|  |  |  |         gap: 12px; | 
					
						
							|  |  |  |         padding: 16px 20px; | 
					
						
							|  |  |  |         border-radius: 8px; | 
					
						
							|  |  |  |         background: ${cssManager.bdTheme('#fff', '#222')}; | 
					
						
							|  |  |  |         border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')}; | 
					
						
							|  |  |  |         box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(0,0,0,0.3)')}; | 
					
						
							|  |  |  |         min-width: 300px; | 
					
						
							|  |  |  |         max-width: 500px; | 
					
						
							|  |  |  |         cursor: pointer; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .toast:hover { | 
					
						
							|  |  |  |         transform: scale(1.02); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .icon { | 
					
						
							|  |  |  |         flex-shrink: 0; | 
					
						
							|  |  |  |         width: 20px; | 
					
						
							|  |  |  |         height: 20px; | 
					
						
							|  |  |  |         display: flex; | 
					
						
							|  |  |  |         align-items: center; | 
					
						
							|  |  |  |         justify-content: center; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .icon svg { | 
					
						
							|  |  |  |         width: 100%; | 
					
						
							|  |  |  |         height: 100%; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .message { | 
					
						
							|  |  |  |         flex: 1; | 
					
						
							|  |  |  |         font-size: 14px; | 
					
						
							|  |  |  |         line-height: 1.5; | 
					
						
							|  |  |  |         color: ${cssManager.bdTheme('#333', '#fff')}; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .close { | 
					
						
							|  |  |  |         flex-shrink: 0; | 
					
						
							|  |  |  |         width: 16px; | 
					
						
							|  |  |  |         height: 16px; | 
					
						
							|  |  |  |         opacity: 0.5; | 
					
						
							|  |  |  |         cursor: pointer; | 
					
						
							|  |  |  |         transition: opacity 0.2s; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .close:hover { | 
					
						
							|  |  |  |         opacity: 1; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .close svg { | 
					
						
							|  |  |  |         width: 100%; | 
					
						
							|  |  |  |         height: 100%; | 
					
						
							|  |  |  |         fill: currentColor; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       /* Type-specific styles */ | 
					
						
							|  |  |  |       :host([type="info"]) .icon { | 
					
						
							|  |  |  |         color: #0084ff; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       :host([type="success"]) .icon { | 
					
						
							|  |  |  |         color: #22c55e; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       :host([type="warning"]) .icon { | 
					
						
							|  |  |  |         color: #f59e0b; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       :host([type="error"]) .icon { | 
					
						
							|  |  |  |         color: #ef4444; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       /* Progress bar */ | 
					
						
							|  |  |  |       .progress { | 
					
						
							|  |  |  |         position: absolute; | 
					
						
							|  |  |  |         bottom: 0; | 
					
						
							|  |  |  |         left: 0; | 
					
						
							|  |  |  |         right: 0; | 
					
						
							|  |  |  |         height: 3px; | 
					
						
							|  |  |  |         background: currentColor; | 
					
						
							|  |  |  |         opacity: 0.2; | 
					
						
							|  |  |  |         border-radius: 0 0 8px 8px; | 
					
						
							|  |  |  |         overflow: hidden; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       .progress-bar { | 
					
						
							|  |  |  |         height: 100%; | 
					
						
							|  |  |  |         background: currentColor; | 
					
						
							|  |  |  |         opacity: 0.8; | 
					
						
							|  |  |  |         transform-origin: left; | 
					
						
							|  |  |  |         animation: progress linear forwards; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @keyframes progress { | 
					
						
							|  |  |  |         from { | 
					
						
							|  |  |  |           transform: scaleX(1); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         to { | 
					
						
							|  |  |  |           transform: scaleX(0); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     `
 | 
					
						
							|  |  |  |   ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-13 16:24:48 +00:00
										 |  |  |   public render(): TemplateResult { | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  |     const icons = { | 
					
						
							|  |  |  |       info: html`<svg viewBox="0 0 20 20" fill="currentColor">
 | 
					
						
							|  |  |  |         <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd"/> | 
					
						
							|  |  |  |       </svg>`,
 | 
					
						
							|  |  |  |       success: html`<svg viewBox="0 0 20 20" fill="currentColor">
 | 
					
						
							|  |  |  |         <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/> | 
					
						
							|  |  |  |       </svg>`,
 | 
					
						
							|  |  |  |       warning: html`<svg viewBox="0 0 20 20" fill="currentColor">
 | 
					
						
							|  |  |  |         <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/> | 
					
						
							|  |  |  |       </svg>`,
 | 
					
						
							|  |  |  |       error: html`<svg viewBox="0 0 20 20" fill="currentColor">
 | 
					
						
							|  |  |  |         <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/> | 
					
						
							|  |  |  |       </svg>`
 | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-13 16:24:48 +00:00
										 |  |  |     return html`
 | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  |       <div class="toast" @click=${this.dismiss}> | 
					
						
							|  |  |  |         <div class="icon"> | 
					
						
							|  |  |  |           ${icons[this.type]} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         <div class="message">${this.message}</div> | 
					
						
							|  |  |  |         <div class="close"> | 
					
						
							|  |  |  |           <svg viewBox="0 0 16 16" fill="currentColor"> | 
					
						
							|  |  |  |             <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> | 
					
						
							|  |  |  |           </svg> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         ${this.duration > 0 ? html`
 | 
					
						
							|  |  |  |           <div class="progress"> | 
					
						
							|  |  |  |             <div class="progress-bar" style="animation-duration: ${this.duration}ms"></div> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         ` : ''}
 | 
					
						
							|  |  |  |       </div> | 
					
						
							| 
									
										
										
										
											2020-09-13 16:24:48 +00:00
										 |  |  |     `;
 | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-06-12 09:33:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   public async dismiss() { | 
					
						
							|  |  |  |     this.isVisible = false; | 
					
						
							|  |  |  |     await new Promise(resolve => setTimeout(resolve, 300)); | 
					
						
							|  |  |  |     this.remove(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Clean up empty containers
 | 
					
						
							|  |  |  |     const container = this.parentElement; | 
					
						
							|  |  |  |     if (container && container.children.length === 0) { | 
					
						
							|  |  |  |       container.remove(); | 
					
						
							|  |  |  |       for (const [position, cont] of DeesToast.toastContainers.entries()) { | 
					
						
							|  |  |  |         if (cont === container) { | 
					
						
							|  |  |  |           DeesToast.toastContainers.delete(position); | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public firstUpdated() { | 
					
						
							|  |  |  |     // Set the type attribute for CSS
 | 
					
						
							|  |  |  |     this.setAttribute('type', this.type); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |