UNPKG

awesome-toast-component

Version:

React, Angular, Vue, and Typescript compatible toast.

247 lines (216 loc) 7.37 kB
export default class Toast{ //default values: public static readonly DEFAULT_HIDING_TIMEOUT: number = 4000; public static readonly DEFAULT_POSITION: string = 'bottom'; //object properties: protected viewID: number; protected view: HTMLElement; protected message: string; protected position: string; protected theme: string | undefined; protected style: object | undefined; protected bornTime: number; protected waitForEvent: boolean; protected hideEventHandler: EventListenerOrEventListenerObject; protected timeout: number; protected isWaitingForHide: boolean; protected afterHide: (() => void) | undefined; //constructor: constructor(message:string, option?: { position?: string, theme?: string, style?: object, waitForEvent?: boolean, timeout?: number, afterHide?:() => void }){ this.bornTime = Date.now(); this.hideEventHandler = this.handleHideEvent.bind(this); //append CSS styles to DOM: Toast.appendCSS();//comment at dev mode //the view: this.viewID = Toast.generateViewID(); let view = Toast.getHtml(this.viewID); document.body.appendChild(view); this.view = document.getElementById(this.viewID.toString()) || document.createElement('div'); //set properties: this.setMessage(this.message = message); this.setPosition(this.position = option?.position || Toast.DEFAULT_POSITION); this.setTheme(option?.theme); this.setStyle(option?.style); this.waitForEvent = option?.waitForEvent ?? false; this.timeout = option?.timeout ?? Toast.DEFAULT_HIDING_TIMEOUT; this.isWaitingForHide = false; this.afterHide = option?.afterHide; //hide events: this.addHideEventListener(); //don't wait for an event: if(!this.waitForEvent) this.startHidingTimer(this.timeout); //finally show: this.show(); } //appendCSS: protected static appendCSS(){ if(document.getElementById('toast-style') === null){ let head = document.head || document.getElementsByTagName('head')[0]; let style = document.createElement('style'); style.id = 'toast-style'; head.appendChild(style); style.appendChild(document.createTextNode(Style)); } } //generateViewID: protected static generateViewID():number{ let id = Math.floor(Math.random() * 1000000000) + 100000000; let element = document.getElementById(id.toString()); if(element === null) return id; return Toast.generateViewID(); } //getHtml: protected static getHtml(viewId: number):ChildNode{ const DOM = ` <div class="toast" id="${viewId}"> <div class="container"> <p class="message"></p> </div> </div> `; let div = document.createElement('div'); div.innerHTML = DOM.trim(); return div.firstChild || div; } //setMessage: public setMessage(message:string){ this.message = message; let messageEl = <HTMLElement> this.view.getElementsByClassName('message')[0]; messageEl.innerHTML = this.message; } //setPosition: public setPosition(position:string){ this.position = position; this.view.classList.remove('bottom'); this.view.classList.remove('top'); this.view.classList.add(position); } //setTheme: public setTheme(theme?:string){ if(theme === undefined) return; this.theme == theme; this.view.classList.remove('light'); this.view.classList.remove('dark'); this.view.classList.add(theme); } //setStyle: public setStyle(style?:object){ if(style === undefined) return; this.style = style; for(const [className, style] of Object.entries(this.style)){ let root = document.getElementById(this.viewID.toString()); let element = <HTMLElement> root!.getElementsByClassName(className)[0]; if(element !== undefined) for(const property of style) element.style.setProperty(property[0], property[1]); } } //show: protected show(){ let thisView = this; setTimeout(() => { thisView.view.classList.add('visible'); }, 50);//slight delay between adding to DOM and running css animation } //addHideEventListener: protected addHideEventListener(){ const thisView = this; 'mousemove mousedown mouseup touchmove click keydown keyup scroll'.split(' ').forEach((eventName) => { window.addEventListener(eventName, thisView.hideEventHandler); }); } //addHideEventListener: protected removeHideEventListener(){ const thisView = this; 'mousemove mousedown mouseup touchmove click keydown keyup scroll'.split(' ').forEach((eventName) => { window.removeEventListener(eventName, thisView.hideEventHandler); }); } //handleHideEvent: protected handleHideEvent(){ let timeout = this.timeout; let currentTime = Date.now(); if(currentTime - this.bornTime > this.timeout) timeout = this.timeout / 2; this.startHidingTimer(timeout); this.removeHideEventListener(); } //startHidingTimer: protected startHidingTimer(timeout: number){ if(timeout > 0 && !this.isWaitingForHide){ this.isWaitingForHide = true; setTimeout(() => { this.hide(); }, timeout); } } //hide: public hide(){ this.view.classList.remove('visible'); const thisView = this; setTimeout(() => { thisView.view.remove(); if(thisView.afterHide !== undefined) thisView.afterHide(); }, 800);//long enough to make sure that it is hidden } } const Style = ` .toast { position: fixed; left: 50%; transform: translate(-50%, 0); opacity: 0; transition: top 400ms ease-in-out 0s, bottom 400ms ease-in-out 0s, opacity 500ms ease-in-out 0ms; z-index: 999999995; } .toast > .container { box-sizing: border-box; max-width: 350px; border-radius: 23px; background-color: rgb(58, 58, 58); box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1), 0 3px 3px rgba(0, 0, 0, 0.05); overflow: hidden; } .toast > .container > .message { box-sizing: border-box; padding: 10px 20px; text-align: center; font-size: 0.9375rem; color: rgb(240, 240, 240); margin: 0; } .toast.visible { opacity: 1; } .toast.bottom { bottom: 25px; } .toast.top { top: 25px; } .toast.light > .container { background-color: #fbfbfb; } .toast.light > .container > .message { color: #555; } @media only screen and (max-width: 500px) { .toast { width: calc(100% - 24px); max-width: unset; left: 12px; transform: translate(0, 0); display: flex; justify-content: center; } } `;