On
Attaches a DOM event listener to the island host or any descendant element. Listeners are set up at mount time and cleaned up automatically on unmount.
Basic usage
Selector syntax
The first argument combines a CSS selector and an event name using @ as a separator:
"cssSelector@eventName"
Omit the selector to target the island host element itself:
.on("@click", handler) // host click
.on("button@click", handler) // any <button> inside the island
.on("input.search@input", handler) // input with class "search"
.on("#submit@click", handler) // element with id="submit"
Event modifiers
Append modifiers after the event name with : as a separator:
| Modifier | Equivalent | Description |
|---|---|---|
once | { once: true } | Listener fires only once, then removes itself |
capture | { capture: true } | Listens in the capture phase |
passive | { passive: true } | Hints the browser this handler won't call preventDefault |
abortable | — | ctx.signal aborts when the same listener fires again |
.on("button@click:once", handler)
.on("@scroll:passive", handler)
.on("button@click:once:capture", handler)
Multiple modifiers can be combined in any order.
Handler context
The handler receives a HandlerContext with everything needed to respond to the event:
{
state: IslandState; // reactive state signals
derived: IslandDerived; // current derived values
input: TInput; // resolved input props
host: Element; // island root element
target: Element; // element that fired the event
event: Event; // the native DOM event
signal: AbortSignal; // aborts on unmount, and on next fire if :abortable
}
Both target and event are typed when the event name is a known HTML event:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Island: Island<Record<string, unknown>, MergeState<Record<never, never>, "value", string>>Island = const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<string, "value">(key: "value", init?: StateInit<Record<string, unknown>, string> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "value", string>, Record<never, never>>state("value", "")
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "value", string>, Record<never, never>>.on<"input@input">(selectorOrCombined: "input@input", handler: (ctx: HandlerContextFor<Record<string, unknown>, MergeState<Record<never, never>, "value", string>, "input", Record<never, never>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "value", string>, Record<never, never>>on("input@input", ({ state: IslandState<MergeState<Record<never, never>, "value", string>>state, event: InputEventevent }) => {
state: IslandState<MergeState<Record<never, never>, "value", string>>state.value: MarkedSignalAccessor
(value: string) => void (+1 overload)
value((event: InputEventevent.Event.target: EventTarget | nullThe read-only **`target`** property of the Event interface is a reference to the object onto which the event was dispatched. It is different from Event.currentTarget when the event handler is called during the bubbling or capturing phase of the event.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target)target as HTMLInputElement).HTMLInputElement.value: stringThe **`value`** property of the HTMLInputElement interface represents the current value of the <input> element as a string.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)value);
})
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "value", string>, Record<never, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<never, never>, "value", string>, Record<never, never>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<never, never>, "value", string>>render(({ state: IslandState<MergeState<Record<never, never>, "value", string>>state }) => <IntrinsicElements[string]: anyinput value: stringvalue={state: IslandState<MergeState<Record<never, never>, "value", string>>state.value: MarkedSignalAccessor
() => string (+1 overload)
value()} />);
Cancelling async work with ctx.signal
Every handler receives an AbortSignal on ctx.signal. It aborts when the island unmounts, so you can pass it directly to fetch or any abort-aware API to cancel stale work automatically:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Island: Island<Record<string, unknown>, MergeState<Record<never, never>, "results", never[]>>Island = const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<never[], "results">(key: "results", init?: StateInit<Record<string, unknown>, never[]> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "results", never[]>, Record<never, never>>state("results", [])
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "results", never[]>, Record<never, never>>.on<"button@click">(selectorOrCombined: "button@click", handler: (ctx: HandlerContextFor<Record<string, unknown>, MergeState<Record<never, never>, "results", never[]>, "click", Record<never, never>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "results", never[]>, Record<never, never>>on("button@click", async ({ state: IslandState<MergeState<Record<never, never>, "results", never[]>>state, signal: AbortSignalAbortSignal that fires when the island unmounts. If the handler's selector
was registered with the `:abortable` modifier, the signal is also aborted
when the same listener fires again on the same target.signal }) => {
const const res: Responseres = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)fetch("/api/data", { RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.signal });
state: IslandState<MergeState<Record<never, never>, "results", never[]>>state.results: MarkedSignalAccessor
(value: never[]) => void (+1 overload)
results(await const res: Responseres.Body.json(): Promise<any>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)json());
})
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "results", never[]>, Record<never, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<never, never>, "results", never[]>, Record<never, never>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<never, never>, "results", never[]>>render(() => (
<>
<IntrinsicElements[string]: anybutton>Load</IntrinsicElements[string]: anybutton>
<IntrinsicElements[string]: anyul></IntrinsicElements[string]: anyul>
</>
));
Race-cancellation with :abortable
When the same listener fires again on the same target, the previous invocation's signal aborts. This is opt-in via the :abortable modifier:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Search: Island<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>>Search = const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<string, "query">(key: "query", init?: StateInit<Record<string, unknown>, string> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "query", string>, Record<never, never>>state("query", "")
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "query", string>, Record<never, never>>.state<never[], "results">(key: "results", init?: StateInit<Record<string, unknown>, never[]> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>, Record<never, never>>state("results", [])
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>, Record<never, never>>.on<"input@input:abortable">(selectorOrCombined: "input@input:abortable", handler: (ctx: HandlerContextFor<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>, "input", Record<never, never>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>, Record<never, never>>on(
"input@input:abortable",
async ({ state: IslandState<MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>>state, event: InputEventevent, signal: AbortSignalAbortSignal that fires when the island unmounts. If the handler's selector
was registered with the `:abortable` modifier, the signal is also aborted
when the same listener fires again on the same target.signal }) => {
const const q: stringq = (event: InputEventevent.Event.target: EventTarget | nullThe read-only **`target`** property of the Event interface is a reference to the object onto which the event was dispatched. It is different from Event.currentTarget when the event handler is called during the bubbling or capturing phase of the event.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target)target as HTMLInputElement).HTMLInputElement.value: stringThe **`value`** property of the HTMLInputElement interface represents the current value of the <input> element as a string.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)value;
state: IslandState<MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>>state.query: MarkedSignalAccessor
(value: string) => void (+1 overload)
query(const q: stringq);
const const res: Responseres = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)fetch(`/search?q=${const q: stringq}`, { RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.signal });
if (signal: AbortSignalAbortSignal that fires when the island unmounts. If the handler's selector
was registered with the `:abortable` modifier, the signal is also aborted
when the same listener fires again on the same target.signal.AbortSignal.aborted: booleanThe **`aborted`** read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (true) or not (false).
[MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted)aborted) return;
state: IslandState<MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>>state.results: MarkedSignalAccessor
(value: never[]) => void (+1 overload)
results(await const res: Responseres.Body.json(): Promise<any>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)json());
},
)
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>, Record<never, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>, Record<never, never>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>>render(({ state: IslandState<MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>>state }) => (
<>
<IntrinsicElements[string]: anyinput value: stringvalue={state: IslandState<MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>>state.query: MarkedSignalAccessor
() => string (+1 overload)
query()} />
<IntrinsicElements[string]: anyul>
{state: IslandState<MergeState<MergeState<Record<never, never>, "query", string>, "results", never[]>>state.results: MarkedSignalAccessor
() => never[] (+1 overload)
results().Array<never>.map<JSX.Element>(callbackfn: (value: never, index: number, array: never[]) => JSX.Element, thisArg?: any): JSX.Element[]Calls a defined callback function on each element of an array, and returns an array that contains the results.map((r: neverr) => (
<IntrinsicElements[string]: anyli>{r: neverr}</IntrinsicElements[string]: anyli>
))}
</IntrinsicElements[string]: anyul>
</>
));
Race-cancellation is scoped per-target — clicking button A does not cancel an in-flight handler on button B.
Async handlers and errors
Async errors (and sync throws) are caught automatically and routed through the error sink: per-island .onError(), then onUncaughtError(), then console.error. AbortError rejections from cancelled work are filtered out and do not reach any of those handlers.
import const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Form: Island<Record<string, unknown>, MergeState<Record<never, never>, "loading", boolean>>Form = const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<boolean, "loading">(key: "loading", init?: StateInit<Record<string, unknown>, boolean> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "loading", boolean>, Record<never, never>>state("loading", false)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "loading", boolean>, Record<never, never>>.on<"form@submit">(selectorOrCombined: "form@submit", handler: (ctx: HandlerContextFor<Record<string, unknown>, MergeState<Record<never, never>, "loading", boolean>, "submit", Record<never, never>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "loading", boolean>, Record<never, never>>on("form@submit", async ({ state: IslandState<MergeState<Record<never, never>, "loading", boolean>>state, event: SubmitEventevent, signal: AbortSignalAbortSignal that fires when the island unmounts. If the handler's selector
was registered with the `:abortable` modifier, the signal is also aborted
when the same listener fires again on the same target.signal }) => {
event: SubmitEventevent.Event.preventDefault(): voidThe **`preventDefault()`** method of the Event interface tells the user agent that the event is being explicitly handled, so its default action, such as page scrolling, link navigation, or pasting text, should not be taken.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)preventDefault();
state: IslandState<MergeState<Record<never, never>, "loading", boolean>>state.loading: MarkedSignalAccessor
(value: boolean) => void (+1 overload)
loading(true);
try {
await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)fetch("/api/submit", { RequestInit.method?: string | undefinedA string to set request's method.method: "POST", RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.signal });
} finally {
state: IslandState<MergeState<Record<never, never>, "loading", boolean>>state.loading: MarkedSignalAccessor
(value: boolean) => void (+1 overload)
loading(false);
}
})
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "loading", boolean>, Record<never, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<never, never>, "loading", boolean>, Record<never, never>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<never, never>, "loading", boolean>>render(({ state: IslandState<MergeState<Record<never, never>, "loading", boolean>>state }) => (
<IntrinsicElements[string]: anyform>
<IntrinsicElements[string]: anybutton type: stringtype="submit" disabled: booleandisabled={state: IslandState<MergeState<Record<never, never>, "loading", boolean>>state.loading: MarkedSignalAccessor
() => boolean (+1 overload)
loading()}>
{state: IslandState<MergeState<Record<never, never>, "loading", boolean>>state.loading: MarkedSignalAccessor
() => boolean (+1 overload)
loading() ? "Submitting…" : "Submit"}
</IntrinsicElements[string]: anybutton>
</IntrinsicElements[string]: anyform>
));
Multiple listeners
Chain .on() as many times as needed. Each call adds an independent listener:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Counter: Island<Record<string, unknown>, MergeState<Record<never, never>, "count", number>>Counter = const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<number, "count">(key: "count", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>state("count", 0)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>.on<"[data-action=increment]@click">(selectorOrCombined: "[data-action=increment]@click", handler: (ctx: HandlerContextFor<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, "click", Record<never, never>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>on("[data-action=increment]@click", ({ state: IslandState<MergeState<Record<never, never>, "count", number>>state }) =>
state: IslandState<MergeState<Record<never, never>, "count", number>>state.count: MarkedSignalAccessor
(value: number) => void (+1 overload)
count(state: IslandState<MergeState<Record<never, never>, "count", number>>state.count: MarkedSignalAccessor
() => number (+1 overload)
count() + 1),
)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>.on<"[data-action=decrement]@click">(selectorOrCombined: "[data-action=decrement]@click", handler: (ctx: HandlerContextFor<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, "click", Record<never, never>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>on("[data-action=decrement]@click", ({ state: IslandState<MergeState<Record<never, never>, "count", number>>state }) =>
state: IslandState<MergeState<Record<never, never>, "count", number>>state.count: MarkedSignalAccessor
(value: number) => void (+1 overload)
count(state: IslandState<MergeState<Record<never, never>, "count", number>>state.count: MarkedSignalAccessor
() => number (+1 overload)
count() - 1),
)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>.on<"[data-action=reset]@click">(selectorOrCombined: "[data-action=reset]@click", handler: (ctx: HandlerContextFor<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, "click", Record<never, never>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>on("[data-action=reset]@click", ({ state: IslandState<MergeState<Record<never, never>, "count", number>>state }) =>
state: IslandState<MergeState<Record<never, never>, "count", number>>state.count: MarkedSignalAccessor
(value: number) => void (+1 overload)
count(0),
)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<never, never>, "count", number>>render(({ state: IslandState<MergeState<Record<never, never>, "count", number>>state }) => (
<IntrinsicElements[string]: anydiv>
<IntrinsicElements[string]: anyp>{state: IslandState<MergeState<Record<never, never>, "count", number>>state.count: MarkedSignalAccessor
() => number (+1 overload)
count()}</IntrinsicElements[string]: anyp>
<IntrinsicElements[string]: anybutton data-action: stringdata-action="increment">+</IntrinsicElements[string]: anybutton>
<IntrinsicElements[string]: anybutton data-action: stringdata-action="decrement">−</IntrinsicElements[string]: anybutton>
<IntrinsicElements[string]: anybutton data-action: stringdata-action="reset">Reset</IntrinsicElements[string]: anybutton>
</IntrinsicElements[string]: anydiv>
));
Implicit batching
Multiple synchronous state writes inside a single handler produce one re-render, not one per write:
.on("@click", ({ state }) => {
state.a(1);
state.b(2);
state.c(3); // → one render, not three
})
Dev mode warnings
In development, if a selector matches no elements at mount time, ilha logs a warning. This is not an error — the element may not exist yet if it is rendered conditionally. The warning is suppressed in production.
Notes
- Listeners are attached to the island host and use standard
addEventListener under the hood — there is no event delegation layer.
- Selectors are evaluated with
querySelectorAll at mount time and after each re-render. If new matching elements appear after mount, they are picked up automatically on the next re-render cycle.
- The
once modifier tracks fired listeners per entry. If the island re-renders before a once listener fires, the listener is still considered active and will not be re-attached.