Toast
Bus-driven notification stack. Any component can call Micra.emit('toast', { … }) and a single container subscribes and renders the queue. Keyed data-each + an id per toast lets CSS animations fire only for new entries.
Live preview
Markup
<!-- Anywhere on the page: the renderer -->
<div class="toast-stack" data-component="toast-stack">
<template data-each="toasts" data-key="id">
<div class="toast" data-bind="class:item.severityClass">
<div class="toast-body">
<div class="toast-title" data-text="item.title"></div>
<div class="toast-message" data-text="item.message"></div>
</div>
<button @click="dismiss" data-bind="data-id:item.id">×</button>
</div>
</template>
</div>
<!-- Anywhere else: trigger toasts via the bus -->
<div data-component="toast-trigger">
<button @click="success">Saved</button>
</div>
Definition
Micra.define('toast-stack', {
state: { toasts: [] },
onCreate() {
this._timers = new Map()
this.on('toast', (payload) => this.push(payload))
},
onDestroy() {
this._timers.forEach(clearTimeout)
this._timers.clear()
},
push({ title, message, severity = 'info', duration = 4000 }) {
const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 6)
const toast = {
id, title, message,
severityClass: 'toast toast-' + severity,
}
this.state.toasts = [...this.state.toasts, toast]
const timer = setTimeout(() => this.remove(id), duration)
this._timers.set(id, timer)
},
remove(id) {
this.state.toasts = this.state.toasts.filter(t => t.id !== id)
clearTimeout(this._timers.get(id))
this._timers.delete(id)
},
dismiss(e) {
this.remove(e.currentTarget.dataset.id)
},
})
Micra.define('toast-trigger', {
info() { Micra.emit('toast', { title: 'Heads up', message: 'Just so you know…', severity: 'info' }) },
success() { Micra.emit('toast', { title: 'Saved', message: 'Changes were persisted', severity: 'success' }) },
error() { Micra.emit('toast', { title: 'Failed', message: 'Server returned 500.', severity: 'error' }) },
warning() { Micra.emit('toast', { title: 'Watch out', message: 'Trial expires Friday.', severity: 'warning' }) },
})
Integration
- Decoupled emit. Any code on the page can emit 'toast' — Micra components, vanilla JS handlers, fetch error handlers. One renderer mounted once is enough.
- Timers are tracked per id in a Map. Dismissing manually clears the auto-dismiss timer — otherwise it would try to remove an already-removed toast.
- Severity → class is stored as severityClass on each toast, consumed by data-bind="class:…". Keeping it on the item (not computed) makes diffing cheaper.
- Animations stay smooth because keyed data-each only inserts the new row — existing toasts aren't re-rendered.
Pitfalls
- Don't mount multiple toast-stack instances unless you want duplicate notifications — both will receive the bus event.
- Don't store the timer id on the toast object itself — diffing churns and you lose them across renders. Keep them in a separate Map on the instance.
- Don't forget onDestroy to clear pending timers — otherwise they fire after the container unmounts and silently no-op on a dead instance.