// — a singleton overlay that shows brief command-result toasts. // // A self-contained control (AGENT.md §1.1): shadow DOM, no data of its own. Any // component posts a toast by dispatching a `toast` CustomEvent on document: // document.dispatchEvent(new CustomEvent('toast', // { detail: { message: 'Published', kind: 'success' } })); // kind ∈ success | error | info. Toasts stack bottom-right, auto-dismiss (errors // linger longer), and dismiss on click. class ToastHost extends HTMLElement { #onToast = null; constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.#render(); this.#onToast = (e) => this.#show(e.detail || {}); document.addEventListener('toast', this.#onToast); } disconnectedCallback() { document.removeEventListener('toast', this.#onToast); } #show({ message, kind = 'info', timeout }) { if (!message) return; const t = document.createElement('div'); t.className = 'toast ' + (['success', 'error', 'info'].includes(kind) ? kind : 'info'); t.textContent = String(message); t.addEventListener('click', () => t.remove()); this.shadowRoot.getElementById('stack').appendChild(t); requestAnimationFrame(() => t.classList.add('in')); const ms = timeout || (kind === 'error' ? 6000 : 3500); setTimeout(() => { t.classList.remove('in'); setTimeout(() => t.remove(), 200); }, ms); } #render() { this.shadowRoot.innerHTML = `
`; } } customElements.define('toast-host', ToastHost);