Overview

event<T>(name?) creates a typed in-process pub/sub channel. Call it at module level so senders and receivers share one instance. Fire with .trigger(data?); listen with on(fn, [myEvent]) (preferred in components) or .__subscribe(fn).

on(..., [event]) registers cleanup on the host so the handler is removed on unmount. Plain event().__subscribe does not auto-clean — keep the returned unsubscribe (or use onCleanup).

Optional name shows up in devtools traces. The generic T is TypeScript-only.

Not exported from mates: channel and cleanupEvent live under lib/Mutables/events/ but are not re-exported from lib/index.ts. Treat them as internal — use public event / xTabEvent instead.

event vs other communication

API
Scope
Notes
event() In-process Same JS context only — no browser API overhead. Public export.
xTabEvent Cross-tab Singleton bus — trigger(type, data) over one shared BroadcastChannel.
atom Shared state Prefer atoms when you need a current value, not a fire-and-forget signal.
channel / cleanupEvent Internal Exist in source but not exported from mates.

event — decoupled communication

Module-level channels, senders trigger, receivers __subscribe. Keep unsubs for long-lived mounts (or prefer on()).

import { html, atom, x, event, renderApp } from 'mates';

const messageEvent = event('demo:message');
const counterEvent = event('demo:counter');

const Sender = () => {
const text = atom('');

const send = () => {
if (!text().trim()) return;
messageEvent.trigger(text());
counterEvent.trigger(1);
text.set('');
};

return () => html`
<div class="card card-body m-col m-gap">
<h3 class="muted">Sender</h3>
<div class="m-flex m-gap-sm m-items-center">
<input
.value=${text()}
@input=${e => text.set(e.target.value)}
@keydown=${e => e.key === 'Enter' && send()}
placeholder="Type a message..."
class="m-flex-1"
/>
<button class="btn-primary" @click=${send}>Send</button>
</div>
</div>
`;
};

const Receiver = () => {
const messages = atom([]);
const total = atom(0);

on() with an event dependency

Pass the event in the deps array. on() auto-unsubscribes when the component unmounts.

import { html, atom, event, on, renderApp } from 'mates';

const ping = event('demo:ping');

const App = () => {
const log = atom([]);

// on() + event dep — auto-unsub on unmount via registerCleanup
on((msg) => {
log.set(prev => [`ping: ${msg}`, ...prev].slice(0, 5));
}, [ping]);

return () => html`
<div class="m-col m-gap">
<button class="btn-primary" @click=${() => ping.trigger(Date.now())}>
Fire ping
</button>
<div class="card card-body">
<span class="overline">on([ping]) log</span>
${log().length === 0
? html`<div class="mono subtle">No pings yet…</div>`
: log().map(e => html`<div class="mono">${e}</div>`)
}
</div>
</div>
`;
};
renderApp(App, document.getElementById('app'));