on:evento
Listens to a DOM event on an element or a defineEmits event on a component, with typed event and currentTarget.
on:evento="manejador" does two things depending on the tag:
- On an HTML or SVG element, adds a DOM listener. Since
1.2, the event uses its named type fromHTMLElementEventMap(SVGElementEventMapinside<svg>) andevent.currentTargetuses the element's type:KeyboardEventinon:keydown,HTMLInputElementon an<input>. - On a component tag, passes the
onEventocallback prop for an event declared by the child withdefineEmits, with typed arguments.
The value is an expression returning the function: a script function's name or an arrow function. It is not code executed on click.
Usage#
<!-- app/components/Counter.meeko -->
<script lang="ts">
const count = signal(0);
const query = signal("");
function increment(): void {
count.value++;
}
</script>
<template>
<button type="button" on:click="increment">{count.value}</button>
<input on:input="(event) => query.value = event.currentTarget.value" aria-label="Search">
</template>Type#
<input on:input="(event) => query.value = event.currentTarget.value" />Attributes#
| Attribute | Type | Default | Description |
|---|---|---|---|
on:<nombre> on an element |
(event: Evento & { currentTarget: Elemento }) => unknown |
none | DOM listener for event <nombre>. Write the name as in addEventListener: click, keydown, pointerdown, submit, toggle. |
on:<nombre> on a component |
(...args) => unknown |
none | The child's on<Nombre> prop, with arguments from its defineEmits. The name is an identifier: on:save, on:saveDraft. |
There are no modifiers such as .prevent or .once: call event.preventDefault() in the handler.
Result#
- Element. In the browser,
addEventListener(nombre, manejador)during hydration or mounting, andremoveEventListenerwhen disposing of the component or region (if:when,each:of). Nothing appears in server HTML. - Component. The child receives the function in
props.onEvento, andemit("evento", …)calls it. Without a listener,emitdoes nothing. - Asynchronous handlers. If the handler returns a promise, Meeko observes it: a synchronous or asynchronous failure reaches the nearest error observer (
provideErrorHandler) with source"event", without leaving an unhandled rejection or stopping other listeners. Without an observer, it is written to the console.
Examples#
Keyboard, with a typed event#
on:keydown receives a KeyboardEvent, so event.key is checked without casts:
<script lang="ts">
const items = signal(["Tea", "Coffee", "Water"]);
const active = signal(0);
function onKey(event: KeyboardEvent): void {
if (event.key === "ArrowDown") active.value = Math.min(active.value + 1, items.value.length - 1);
else if (event.key === "ArrowUp") active.value = Math.max(active.value - 1, 0);
else return;
event.preventDefault();
}
</script>
<template>
<ul role="listbox" tabindex="0" aria-label="Drinks" on:keydown="onKey">
<li each:of="items.value" each:as="item" each:index="i" role="option" bind:aria-selected="i === active.value ? 'true' : 'false'">{item}</li>
</ul>
</template>An asynchronous submission#
An async handler can await; if it fails, the error reaches the component's observer:
<script lang="ts">
const saving = signal(false);
provideErrorHandler((error, source) => {
console.error(`Failure in ${source}`, error);
});
async function save(event: SubmitEvent): Promise<void> {
event.preventDefault();
saving.value = true;
try {
const form = new FormData(event.currentTarget as HTMLFormElement);
await fetch("/api/notes", { method: "POST", body: form });
} finally {
saving.value = false;
}
}
</script>
<template>
<form on:submit="save">
<textarea name="text" aria-label="Note"></textarea>
<button bind:disabled="saving.value">Save</button>
</form>
</template>Component events#
The child declares events with defineEmits; the parent listens with on::
<!-- app/components/TodoItem.meeko -->
<script lang="ts">
const props = defineProps<{ id: number; title: string }>();
const emit = defineEmits<{ save: [id: number]; close: [] }>();
</script>
<template>
<p>{props.title}</p>
<button type="button" on:click="() => emit('save', props.id)">Save</button>
<button type="button" on:click="() => emit('close')">Close</button>
</template><TodoItem bind:id="todo.id" bind:title="todo.title" on:save="(id) => save(id)" on:close="() => open.value = false" />Delegate to a container#
A single listener on the container handles all rows, including HTML inserted with html, which does not accept directives:
<script lang="ts">
import type { TrustedHtml } from "@meekojs/meeko";
const props = defineProps<{ body: TrustedHtml }>();
function onClick(event: MouseEvent): void {
const link = (event.target as Element | null)?.closest("a[data-term]");
if (link) console.log(link.getAttribute("data-term"));
}
</script>
<template><article html="props.body" on:click="onClick"></article></template>Common mistakes#
Call the function instead of passing it#
<button on:click="increment()">+1</button>increment() would run during rendering and pass its result. meeko check reports TS2345: Argument of type 'void' is not assignable to parameter of type '(event: PointerEvent & { readonly currentTarget: HTMLButtonElement; }) => unknown'.
<button on:click="increment">+1</button>
<button on:click="() => add(5)">+5</button>Read event.target.value#
event.target can be a descendant and its type is EventTarget | null.
<input on:input="(event) => query.value = event.target.value">meeko check reports TS18047: 'event.target' is possibly 'null'. and TS2339: Property 'value' does not exist on type 'EventTarget'.
<input on:input="(event) => query.value = event.currentTarget.value">For two-way binding, model="query" is shorter.
A handler typed for another event#
<input on:click="onKey">With function onKey(event: KeyboardEvent), meeko check reports TS2345 with Type 'PointerEvent & { readonly currentTarget: HTMLInputElement; }' is missing the following properties from type 'KeyboardEvent': ….
<input on:keydown="onKey">Vue-style modifiers#
<form on:submit.prevent="save">MEEK011: Nombre de evento no válido: submit.prevent.
<form on:submit="save">With event.preventDefault() inside save.
A hyphenated component event#
<TodoItem bind:id="1" on:save-draft="save" />MEEK011: Nombre de evento de componente no válido: save-draft. On a component, the event name is an identifier, the key in defineEmits.
<TodoItem bind:id="1" on:saveDraft="save" />Both on:save and bind:onSave#
<TodoItem bind:id="1" on:save="save" bind:onSave="save" />MEEK048: on:save y bind:onSave asignan la misma prop onSave del componente; deja sólo uno.
<TodoItem bind:id="1" on:save="save" />An event not declared by the child#
<TodoItem bind:id="1" on:remove="remove" />meeko check reports TS2353: Object literal may only specify known properties, and '"onRemove"' does not exist in type 'EventNames<ComponentProps>'.
<TodoItem bind:id="1" on:close="remove" />Notes#
- Server or browser. Listeners only exist in the browser. In SSR, the handler never runs; the button works once the page hydrates. For a form that works without JavaScript, give it
actionandmethod(seeformindefineApiRoute). - Cleanup. A component's or region's listeners are removed on disposal: no
removeEventListeneris needed. To listen towindowordocument, register inonMountedand remove withonCleanup. - HTML
on*attributes.onclick="…"without:is a literal HTML attribute, executing inline code outside Meeko and blocked by a CSP without'unsafe-inline'. Useon:click.bind:onclickproducesMEEK012: Atributo dinámico no válido: onclick. - Types. A name absent from the element's event map receives
Event. On a custom element,currentTargetisHTMLElement. - Performance. Each
on:is a listener; in long lists, one container listener withevent.target.closest(…)avoids one per row.