Skip to content

Events

Generative DOM includes an event system that allows plugins to communicate with application code and with each other. This guide covers subscribing to events, emitting events from plugins, and the event lifecycle.

Subscribing to Events

Use md.on() to subscribe to events and md.off() to unsubscribe:

ts
const handler = (data: unknown) => {
  console.log('Progress:', data);
};

md.on('events:progress', handler);

// Later, unsubscribe:
md.off('events:progress', handler);

Event names are namespaced by plugin name. When a plugin named events calls ctx.emit('progress', data), subscribers receive it as events:progress.

Event Flow

Plugin calls ctx.emit("progress", { value: 35 })
    |
    v
Core namespaces it: "events:progress"
    |
    v
Core dispatches to all handlers registered via md.on("events:progress", handler)
    |
    v
Handler receives: { value: 35 }

Event Guarantees

  • Events are dispatched synchronously in listener registration order.
  • If a listener throws, other listeners still execute. The error is logged via console.error, not propagated.
  • Events emitted during render() are dispatched immediately, before the next token is rendered.

The Events Plugin

The events plugin parses invisible elements from the stream that produce no DOM output but emit events:

markdown
<progress value="35" max="100" />
<status type="complete" />
<milestone name="section-1" />

These elements are consumed by the parser and produce events, but nothing is added to the DOM.

Event Payloads

ts
// <progress value="35" max="100" />
md.on('events:progress', (data) => {
  // data: { value: 35, max: 100 }
});

// <status type="complete" />
md.on('events:status', (data) => {
  // data: { type: "complete" }
});

// <milestone name="section-1" />
md.on('events:milestone', (data) => {
  // data: { name: "section-1" }
});

Mixing Events with Markdown

Event elements can appear inline with regular markdown:

markdown
Hello <progress value="50" max="100" /> world

This renders "Hello world" in the DOM while emitting a progress event between parsing the two text segments.

Interactive Element Events

The interactive plugin emits events when users interact with rendered elements:

ts
md.on('interactive:button-click', (data) => {
  // data: { text: "Click me", clickCount: 3 }
});

md.on('interactive:toggle', (data) => {
  // data: { label: "Dark mode", checked: true }
});

md.on('interactive:input-change', (data) => {
  // data: { value: "typed text" }
});

Plugin-to-Plugin Communication

Plugins can check for each other's presence via PluginContext.getPlugin():

ts
init(ctx: PluginContext) {
  const codePlugin = ctx.getPlugin('markdown-code');
  if (codePlugin) {
    // The code plugin is registered; adjust behavior accordingly
  }
}

Plugins must not call methods on other plugins directly -- only check presence and read name/priority.