Skip to content

data-on

Listen for an event and run an expression with evt in scope.

data-on:click="$count++"
data-on:keydown__window="evt.key === 'Escape' && ($open = false)"
data-on:input__debounce.300ms="@get('/search')"
data-on:submit="@post('/save')"
part
key required: the event name, kebab-cased by default
value required: statements with evt in scope
modifier effect
__window / __document listen on window or document instead of the element
__outside run only for events whose target is outside the element (listens on document)
__prevent / __stop preventDefault() / stopPropagation() before running
__once remove the listener after the first event
__debounce.ms[.leading][.notrailing] run after a quiet period
__throttle.ms[.noleading][.trailing] run at most once per window
__case.camel keep the event name camelCase, for custom events such as myEvent
evt in scope

typed , last key

<div data-signals="{ n: 0, key: '', typed: '' }">
<div class="row"><button data-on:click="$n++">clicked <span data-text="$n"></span></button> <input placeholder="type here" data-on:input="$typed = evt.target.value" data-on:keydown="$key = evt.key"></div>
<p>typed <code data-text="$typed"></code>, last key <code data-text="$key"></code></p>
</div>

Global listeners live on the element that owns the state, so they are removed with it. __outside is the classic “click outside to close”.

dropdown
click outside or press Escape
<div data-signals="{ open: false }" data-on:keydown__window="evt.key === 'Escape' && ($open = false)">
<div style="display:inline-block;position:relative" data-on:click__outside="$open = false">
<button data-on:click="$open = !$open">menu <span data-text="$open ? '▴' : '▾'"></span></button>
<div data-show="$open" style="position:absolute;top:100%;left:0;background:var(--sl-color-bg);border:1px solid var(--sl-color-hairline);padding:.4rem .8rem;border-radius:.4rem;white-space:nowrap">click outside or press Escape</div>
</div>
</div>
listener options
link

parent clicks: , last:

<div data-signals="{ outer: 0, once: 0, href: '' }">
<div class="row" data-on:click="$outer++">
<button data-on:click__stop="$href = 'stopped'">stop (parent does not count)</button>
<button data-on:click="$href = 'bubbled'">bubble</button>
<button data-on:click__once="$once++">once (<span data-text="$once"></span>)</button>
<a href="https://example.com" data-on:click__prevent="$href = 'prevented navigation'">link</a>
</div>
<p>parent clicks: <b data-text="$outer"></b>, last: <code data-text="$href"></code></p>
</div>

Debounce waits for a pause; throttle runs at most once per window. .leading and .trailing choose which edge fires.

__debounce and __throttle

raw , debounced , throttled

<div data-signals="{ raw: 0, deb: 0, thr: 0 }">
<input placeholder="type quickly" data-on:input="$raw++" data-on:input__debounce.400ms="$deb++" data-on:input__throttle.400ms="$thr++">
<p>raw <b data-text="$raw"></b>, debounced <b data-text="$deb"></b>, throttled <b data-text="$thr"></b></p>
</div>

submit on a form always calls preventDefault(); post the fields with @post(url, { contentType: 'form' }) or the signals with a plain @post.

submit

Would have posted ; the page did not reload.

<form data-signals="{ email: '', sent: false }" data-on:submit="$sent = true">
<div class="row"><input type="email" required data-bind:email placeholder="you@example.com"> <button>Subscribe</button></div>
<p data-show="$sent">Would have posted <b data-text="$email"></b>; the page did not reload.</p>
</form>

Events named sigmx-* listen on document automatically, so request lifecycle events are one attribute away. Custom events from @dispatch bubble to data-on on any ancestor.

sigmx-fetch and @dispatch

events:

<div data-signals="{ log: [] }" data-on:sigmx-fetch="$log = [...$log.slice(-3), evt.detail.type]" data-on:hello="$log = [...$log.slice(-3), 'hello ' + evt.detail.from]">
<div class="row"><button data-on:click="@get('/api/time')">request</button><button data-on:click="@dispatch('hello', { from: 'child' })">dispatch</button></div>
<p>events: <code data-text="$log.join(' → ')"></code> <span hidden><span id="server-time"></span></span></p>
</div>

Listeners are passive-safe by default (no passive flag is set) and are removed when the attribute or element goes away. Errors thrown in the handler are reported through onError without breaking other listeners.

import { on } from 'sigmx/plugins'

Registering on adds 472 bytes brotli (537 gzip, 1178 minified) on top of the core. See bundle sizes.