export class Dialog extends HTMLElement { constructor() { super() this.attachShadow({mode: 'open'}) this.headerEl = document.createElement('div') this.headerEl.classList.add('header') this.bodyEl = document.createElement('div') this.bodyEl.classList.add('body') this.footerEl = document.createElement('div') this.footerEl.classList.add('footer') this.dialogEl = document.createElement('dialog') this.dialogEl.replaceChildren( this.headerEl, this.bodyEl, this.footerEl ) this.dialogEl.addEventListener('click', e => { if ( e.target === this.dialogEl && !this.clickedInside(e, this.dialogEl) ) { this.close() } }) this.shadowRoot.appendChild(this.dialogEl) } connectedCallback() { const style = document.createElement('style') style.textContent = ` dialog { margin-top: 120px; min-width: 200px; border: 1px solid rgba(0, 0, 0, 0.3); border-radius: 6px; box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); } dialog::backdrop { opacity: 0; transition: opacity 0.3s ease-in; background-color: rgba(0, 0, 0, .25); } dialog.opened::backdrop { opacity: 1; } dialog.closing { visibility: hidden; } dialog.closing::backdrop { visibility: visible; } .header, .footer, .body { display: flex; align-items: stretch; flex-direction: column; } ` this.shadowRoot.append(style) this.headerEl.setAttribute('part', 'header') this.bodyEl.setAttribute('part', 'body') this.footerEl.setAttribute('part', 'footer') } open() { this.dialogEl.showModal(); this.dialogEl.classList.add('opened') } close() { this.dialogEl.classList.remove('opened') this.dialogEl.classList.add('closing') setTimeout(() => { this.dialogEl.close() this.dialogEl.classList.remove('closing') }, 500) } clickedInside(e, el) { const rect = el.getBoundingClientRect() return ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ) } }