import * as colors from './00colors.js'; import * as plugins from './00plugins.js'; import { demoFunc } from './dees-speechbubble.demo.js'; import { customElement, html, DeesElement, property, type TemplateResult, cssManager, css, type CSSResult, unsafeCSS, domtools, directives, unsafeHTML, } from '@design.estate/dees-element'; import { DeesWindowLayer } from './dees-windowlayer.js'; declare global { interface HTMLElementTagNameMap { 'dees-speechbubble': DeesSpeechbubble; } } @customElement('dees-speechbubble') export class DeesSpeechbubble extends DeesElement { public static demo = demoFunc; // STATIC public static async createAndShow(refElement: HTMLElement, textArg: string) { const windowLayer = await DeesWindowLayer.createAndShow({ blur: false, }); const speechbubble = document.createElement('dees-speechbubble'); speechbubble.windowLayer = windowLayer; speechbubble.reffedElement = refElement; speechbubble.text = textArg; speechbubble.manifested = true; windowLayer.appendChild(speechbubble); windowLayer.style.pointerEvents = 'none'; (windowLayer.shadowRoot.querySelector('.windowOverlay') as HTMLElement).style.pointerEvents = 'none'; return speechbubble; } // INSTANCE @property({ type: Object, }) reffedElement: HTMLElement; @property({ type: String, reflect: true, }) public text: string; @property({ type: Boolean, }) public wave: boolean = false; @property({ type: Boolean, }) public manifested = false; @property({ type: String, }) public status: 'normal' | 'pending' | 'success' | 'error' = 'normal'; public windowLayer: DeesWindowLayer; constructor() { super(); } public static styles = [ cssManager.defaultStyles, css` :host { box-sizing: border-box; color: ${cssManager.bdTheme('#333', '#fff')}; user-select: none; } .maincontainer { position: relative; will-change: transform; transition: transform 0.2s; transform: translateX(0px); transition: all 0.2s; margin-left: 0px; filter: drop-shadow(0px 0px 2px rgba(0, 0, 0, 0.2)); pointer-events: none; opacity: 0; transition: all 0.2s; } .arrow { position: absolute; transform: rotate(45deg); background: ${cssManager.bdTheme('#fff', '#333')}; height: 15px; width: 15px; left: 2px; top: 12px; border-radius: 3px; } .speechbubble { background: ${cssManager.bdTheme('#fff', '#333')}; padding: 0px 16px; border-radius: 3px; position: absolute; min-width: 240px; font-size: 12px; top: 0px; left: 8px; } .wave { animation-name: wave-animation; /* Refers to the name of your @keyframes element below */ animation-duration: 2.5s; /* Change to speed up or slow down */ animation-iteration-count: infinite; /* Never stop waving :) */ transform-origin: 70% 70%; /* Pivot around the bottom-left palm */ display: inline-block; } @keyframes wave-animation { 0% { transform: rotate(0deg); } 10% { transform: rotate(14deg); } /* The following five values can be played with to make the waving more or less extreme */ 20% { transform: rotate(-8deg); } 30% { transform: rotate(14deg); } 40% { transform: rotate(-4deg); } 50% { transform: rotate(10deg); } 60% { transform: rotate(0deg); } /* Reset for the last half to pause */ 100% { transform: rotate(0deg); } } `, ]; public render(): TemplateResult { return html` ${this.manifested ? html`
${this.wave ? html`👋` : html``} ${directives.resolve(this.getHtml())}
` : html``} `; } public async handleClick() { console.log('speechbubble got clicked.'); } public async firstUpdated() { // lets make sure we have a ref if (!this.reffedElement) { this.reffedElement = this.previousElementSibling as HTMLElement; } if (this.manifested) { await this.updatePosition(); (this.shadowRoot.querySelector('.maincontainer') as HTMLElement).style.opacity = '1'; } else { // lets make sure we instrument it let speechbubble: DeesSpeechbubble; this.reffedElement.addEventListener('mouseenter', async () => { speechbubble = await DeesSpeechbubble.createAndShow(this.reffedElement, this.text); }); this.reffedElement.addEventListener('mouseleave', () => { speechbubble.destroy(); }); } } public async updatePosition() { const refElement = this.reffedElement; const boundingClientRect = refElement.getBoundingClientRect(); this.style.position = 'fixed'; this.style.top = `${boundingClientRect.top - 13}px`; this.style.left = `${boundingClientRect.left + refElement.clientWidth + 4}px`; if (boundingClientRect.right > 250) { this.style.width = `250px`; } } public async getHtml(): Promise { if (!this.text) { return ''; } const normalized = domtools.plugins.smartstring.normalize.standard(this.text); const result = await domtools.plugins.smartmarkdown.SmartMarkdown.easyMarkdownToHtml( normalized ); return unsafeHTML(result); } public async show() {} public async destroy() { (this.shadowRoot.querySelector('.maincontainer') as HTMLElement).style.opacity = '0'; this.windowLayer.destroy(); } }