Skip to content

Events & commands

Commands are the actions exposed to your renderer; events are optional typed CustomEvents you can bubble to host apps.

events: (event) => ({
incremented: event<{ amount: number }>(),
})

With events defined, commands receives an emit helper:

commands: ({ actor, emit }) => ({
add: (amount: number) => {
actor.send({ type: 'ADD', amount });
emit('incremented', { amount });
},
}),

Events bubble and are composed by default, so frameworks can listen with addEventListener.

  • Plain DOM: element.addEventListener('incremented', (event) => console.log(event.detail.amount));
  • Ignite JSX: <my-counter onIncremented={(event) => handleIncrement(event.detail)}></my-counter>.

commands also receive host (the custom element instance) and your adapter’s control surface (actor, store, or observable). Keep commands small and deterministic—delegate async side effects to your state library.

Use host for DOM affordances:

commands: ({ host, emit }) => ({
focusSelf: () => host.focus(),
emitSize: () => {
const { width, height } = host.getBoundingClientRect();
emit('measured', { width, height });
},
});

You can read dataset, measure the element, or dispatch additional DOM events from host as needed. Reflect state (attrs/aria/class) primarily via your renderer to keep outputs declarative.

  • When events is present, event names and payloads are inferred into emit.
  • states and commands outputs are merged into the props your renderer receives, giving you typed render args without extra wiring.
import { igniteCore } from 'ignite-element/xstate';
import machine from './counter-machine';
const component = igniteCore({
source: machine,
events: (event) => ({
incremented: event<{ count: number }>(),
}),
states: (snapshot) => ({
count: snapshot.context.count,
canDecrement: snapshot.context.count > 0,
}),
commands: ({ actor, emit }) => ({
increment: () => {
actor.send({ type: 'INCREMENT' });
emit('incremented', { count: actor.getSnapshot().context.count });
},
decrement: () => actor.send({ type: 'DECREMENT' }),
}),
});
component('my-counter', ({ count, canDecrement, increment, decrement }) => (
<div class="counter">
<button onClick={decrement} disabled={!canDecrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
));