ilha 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -9
- package/dist/index.d.ts +74 -2
- package/dist/index.js +240 -49
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ ilha
|
|
|
138
138
|
.state("count", 0)
|
|
139
139
|
.on("@click", ({ state }) => state.count(state.count() + 1)) // host click
|
|
140
140
|
.on("button.inc@click", ({ state }) => state.count(state.count() + 1)) // child click
|
|
141
|
-
.on("input@input
|
|
141
|
+
.on("input@input", ({ state, event }) => {
|
|
142
142
|
state.query((event.target as HTMLInputElement).value);
|
|
143
143
|
})
|
|
144
144
|
.render(({ state }) => html`<div><button class="inc">+</button></div>`);
|
|
@@ -146,11 +146,12 @@ ilha
|
|
|
146
146
|
|
|
147
147
|
**Event modifiers** — append after a `:` separator:
|
|
148
148
|
|
|
149
|
-
| Modifier
|
|
150
|
-
|
|
|
151
|
-
| `once`
|
|
152
|
-
| `capture`
|
|
153
|
-
| `passive`
|
|
149
|
+
| Modifier | Description |
|
|
150
|
+
| ----------- | ------------------------------------------------------------------------- |
|
|
151
|
+
| `once` | Listener fires only once |
|
|
152
|
+
| `capture` | Capture phase |
|
|
153
|
+
| `passive` | `{ passive: true }` |
|
|
154
|
+
| `abortable` | `ctx.signal` aborts when the same listener fires again on the same target |
|
|
154
155
|
|
|
155
156
|
Multiple modifiers can be combined: `@click:once:capture`.
|
|
156
157
|
|
|
@@ -159,13 +160,62 @@ The handler receives a `HandlerContext`:
|
|
|
159
160
|
```ts
|
|
160
161
|
{
|
|
161
162
|
state: IslandState; // reactive state signals
|
|
163
|
+
derived: IslandDerived; // derived values
|
|
162
164
|
input: TInput; // resolved input props
|
|
163
165
|
host: Element; // island root element
|
|
164
166
|
target: Element; // element that fired the event (typed per event name)
|
|
165
167
|
event: Event; // the native event (typed per event name)
|
|
168
|
+
signal: AbortSignal; // aborts on unmount, and on next fire if `:abortable`
|
|
166
169
|
}
|
|
167
170
|
```
|
|
168
171
|
|
|
172
|
+
**Cancelling async work with `ctx.signal`** — pass it to `fetch` or any abort-aware API to cancel stale requests when the island unmounts:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
ilha
|
|
176
|
+
.state("results", [])
|
|
177
|
+
.on("button@click", async ({ state, signal }) => {
|
|
178
|
+
const res = await fetch("/api/data", { signal });
|
|
179
|
+
state.results(await res.json());
|
|
180
|
+
})
|
|
181
|
+
.render(
|
|
182
|
+
() =>
|
|
183
|
+
html`<button>Load</button>
|
|
184
|
+
<ul></ul>`,
|
|
185
|
+
);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Race-cancellation with `:abortable`** — when the same listener fires again on the same target, the previous invocation's signal aborts. Useful for search-as-you-type and other patterns where only the latest invocation should win:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
ilha
|
|
192
|
+
.on("input@input:abortable", async ({ state, event, signal }) => {
|
|
193
|
+
const q = (event.target as HTMLInputElement).value;
|
|
194
|
+
const res = await fetch(`/search?q=${q}`, { signal }); // earlier requests cancelled
|
|
195
|
+
if (signal.aborted) return;
|
|
196
|
+
state.results(await res.json());
|
|
197
|
+
})
|
|
198
|
+
.render(
|
|
199
|
+
() =>
|
|
200
|
+
html`<input />
|
|
201
|
+
<ul></ul>`,
|
|
202
|
+
);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Race-cancellation is scoped per-target — clicking button A doesn't cancel an in-flight handler on button B.
|
|
206
|
+
|
|
207
|
+
**Implicit batching** — multiple synchronous state writes in a single handler produce one re-render, not one per write:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
.on("@click", ({ state }) => {
|
|
211
|
+
state.a(1);
|
|
212
|
+
state.b(2);
|
|
213
|
+
state.c(3); // → one render, not three
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`AbortError` rejections from cancelled async work are filtered out automatically — they do not reach `.onError()` or `console.error`.
|
|
218
|
+
|
|
169
219
|
---
|
|
170
220
|
|
|
171
221
|
### `.effect(fn)`
|
|
@@ -184,6 +234,42 @@ ilha
|
|
|
184
234
|
.render(({ state }) => `<p>${state.title()}</p>`);
|
|
185
235
|
```
|
|
186
236
|
|
|
237
|
+
The handler receives an `EffectContext`:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
{
|
|
241
|
+
state: IslandState;
|
|
242
|
+
input: TInput;
|
|
243
|
+
host: Element;
|
|
244
|
+
signal: AbortSignal; // aborts when the effect re-runs OR the island unmounts
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Cancelling async work with `ctx.signal`** — unlike `.on()`, race-cancellation is the **default** behaviour for effects (no opt-in modifier needed) because dependency changes invariably make the previous run stale. Pass `signal` to async work to bail out of stale invocations without needing a manual cleanup function:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
ilha
|
|
252
|
+
.state("userId", 1)
|
|
253
|
+
.state("user", null)
|
|
254
|
+
.effect(({ state, signal }) => {
|
|
255
|
+
(async () => {
|
|
256
|
+
try {
|
|
257
|
+
const res = await fetch(`/api/users/${state.userId()}`, { signal });
|
|
258
|
+
if (signal.aborted) return;
|
|
259
|
+
state.user(await res.json());
|
|
260
|
+
} catch (err) {
|
|
261
|
+
if (err && (err as Error).name === "AbortError") return;
|
|
262
|
+
throw err;
|
|
263
|
+
}
|
|
264
|
+
})();
|
|
265
|
+
})
|
|
266
|
+
.render(({ state }) => html`<p>${state.user?.name ?? "Loading…"}</p>`);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Both the user-supplied cleanup function (if any) and the signal abort fire when the effect re-runs, so you can mix patterns.
|
|
270
|
+
|
|
271
|
+
**Implicit batching** — multiple synchronous state writes inside an effect run produce a single propagation pass.
|
|
272
|
+
|
|
187
273
|
---
|
|
188
274
|
|
|
189
275
|
### `.onMount(fn)`
|
|
@@ -203,6 +289,43 @@ ilha
|
|
|
203
289
|
|
|
204
290
|
---
|
|
205
291
|
|
|
292
|
+
### `.onError(fn)`
|
|
293
|
+
|
|
294
|
+
Registers an error handler that catches errors thrown by `.on()` handlers (sync throws and async rejections) and `.effect()` runs (sync throws). Multiple `.onError()` calls compose — all run in declaration order. If no `.onError()` is registered, errors fall back to `console.error` so they are never silently swallowed.
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
ilha
|
|
298
|
+
.state("count", 0)
|
|
299
|
+
.on("@click", ({ state }) => {
|
|
300
|
+
if (state.count() > 5) throw new Error("too many clicks");
|
|
301
|
+
state.count(state.count() + 1);
|
|
302
|
+
})
|
|
303
|
+
.onError(({ error, source }) => {
|
|
304
|
+
console.error(`[${source}] ${error.message}`);
|
|
305
|
+
Sentry.captureException(error);
|
|
306
|
+
})
|
|
307
|
+
.render(({ state }) => `<button>${state.count()}</button>`);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The handler receives an `ErrorContext`:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
{
|
|
314
|
+
error: Error; // always wrapped to Error if a non-Error was thrown
|
|
315
|
+
source: "on" | "effect";
|
|
316
|
+
state: IslandState;
|
|
317
|
+
derived: IslandDerived;
|
|
318
|
+
input: TInput;
|
|
319
|
+
host: Element;
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`AbortError` rejections from `.on()` handlers are **not** routed to `.onError()` — they are the expected outcome of cancellation (via `:abortable` race-cancel or unmount) and would otherwise pollute error tracking.
|
|
324
|
+
|
|
325
|
+
An error thrown inside an `.onError()` handler does not break other registered handlers; it is logged to `console.error` and execution continues with the next handler.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
206
329
|
### `.bind(selector, stateKey | externalSignal)`
|
|
207
330
|
|
|
208
331
|
Two-way binds a form element to a state key or an external signal. Handles `input`, `select`, `textarea`, `checkbox`, `radio`, and `number` inputs automatically.
|
|
@@ -224,10 +347,15 @@ ilha
|
|
|
224
347
|
);
|
|
225
348
|
```
|
|
226
349
|
|
|
227
|
-
You can also bind to an external signal created with `context()`:
|
|
350
|
+
You can also bind to an external signal — either a free-standing one created with `signal()` or a named global one created with `context()`:
|
|
228
351
|
|
|
229
352
|
```ts
|
|
230
|
-
|
|
353
|
+
import { signal, context } from "ilha";
|
|
354
|
+
|
|
355
|
+
const username = signal("");
|
|
356
|
+
const theme = context("app.theme", "light");
|
|
357
|
+
|
|
358
|
+
ilha.bind("input.name", username).bind("select.theme", theme).render(/* … */);
|
|
231
359
|
```
|
|
232
360
|
|
|
233
361
|
---
|
|
@@ -461,9 +589,40 @@ const unmount = from("#hero", HeroIsland, { title: "Welcome" });
|
|
|
461
589
|
|
|
462
590
|
---
|
|
463
591
|
|
|
592
|
+
### `signal(initial)`
|
|
593
|
+
|
|
594
|
+
Creates a free-standing reactive signal that lives outside any island. Useful for sharing state across multiple islands without prop drilling, or for binding form inputs to module-level state.
|
|
595
|
+
|
|
596
|
+
```ts
|
|
597
|
+
import { signal } from "ilha";
|
|
598
|
+
|
|
599
|
+
const count = signal(0);
|
|
600
|
+
|
|
601
|
+
count(); // → 0 (read)
|
|
602
|
+
count(5); // → sets to 5 (write)
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Reading the signal inside any reactive scope — `.render()`, `.derived()`, `.effect()` — automatically subscribes that scope, so when the signal changes, dependents re-run as if it were local state.
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
import ilha, { signal, html } from "ilha";
|
|
609
|
+
|
|
610
|
+
const username = signal("anonymous");
|
|
611
|
+
|
|
612
|
+
const Header = ilha.render(() => html`<header>Hi, ${username()}!</header>`);
|
|
613
|
+
const Footer = ilha.render(() => html`<footer>Logged in as ${username()}</footer>`);
|
|
614
|
+
|
|
615
|
+
// Both islands re-render when `username` changes from anywhere.
|
|
616
|
+
username("alice");
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Pairs naturally with `.bind()` for two-way form bindings against module-level state.
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
464
623
|
### `context(key, initial)`
|
|
465
624
|
|
|
466
|
-
Creates a **global context signal** — a named reactive signal shared across all islands. Identical keys always return the same signal instance.
|
|
625
|
+
Creates a **global context signal** — a named reactive signal shared across all islands. Identical keys always return the same signal instance, which makes it useful for app-wide singletons (theme, locale, current user) where you want the registry semantics.
|
|
467
626
|
|
|
468
627
|
```ts
|
|
469
628
|
import { context } from "ilha";
|
|
@@ -476,6 +635,58 @@ theme("dark"); // → sets to "dark"
|
|
|
476
635
|
|
|
477
636
|
Safe to call in both SSR and browser environments.
|
|
478
637
|
|
|
638
|
+
> **`signal()` vs `context()`** — both return the same accessor shape and can be passed to `.bind()`. Use `signal()` for one-off shared state where you'd hold the reference yourself; use `context()` when you want a name-keyed registry so the same signal can be looked up from anywhere by string key.
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
### `batch(fn)`
|
|
643
|
+
|
|
644
|
+
Runs `fn` as an atomic batch — multiple signal writes inside the callback produce a single propagation pass, so dependents see the final state and run once instead of once per write. Returns whatever `fn` returns.
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
import { signal, batch } from "ilha";
|
|
648
|
+
|
|
649
|
+
const a = signal(0);
|
|
650
|
+
const b = signal(0);
|
|
651
|
+
|
|
652
|
+
// Without batch: each write triggers a propagation pass.
|
|
653
|
+
a(1); // → effects re-run
|
|
654
|
+
b(2); // → effects re-run
|
|
655
|
+
|
|
656
|
+
// With batch: both writes flush together.
|
|
657
|
+
batch(() => {
|
|
658
|
+
a(10);
|
|
659
|
+
b(20);
|
|
660
|
+
}); // → effects re-run once
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
`.on()` handlers and `.effect()` runs are batched implicitly, so you only need `batch()` when triggering multiple writes from outside an island — e.g. from a top-level event listener, a `setTimeout` callback, or a WebSocket message handler. Nested `batch()` calls are safe and only flush when the outermost batch ends.
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
### `untrack(fn)`
|
|
668
|
+
|
|
669
|
+
Runs `fn` with reactive tracking suspended. Reading signals inside `fn` returns their current value without subscribing the surrounding scope. Use this in effects or deriveds when you want to peek at state without causing a re-run on its changes.
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
import ilha, { signal, untrack } from "ilha";
|
|
673
|
+
|
|
674
|
+
const tracked = signal(0);
|
|
675
|
+
const peeked = signal("hello");
|
|
676
|
+
|
|
677
|
+
ilha
|
|
678
|
+
.effect(() => {
|
|
679
|
+
// Re-runs when `tracked` changes, but NOT when `peeked` changes.
|
|
680
|
+
console.log(
|
|
681
|
+
tracked(),
|
|
682
|
+
untrack(() => peeked()),
|
|
683
|
+
);
|
|
684
|
+
})
|
|
685
|
+
.render(() => `<p>x</p>`);
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
Returns whatever `fn` returns.
|
|
689
|
+
|
|
479
690
|
---
|
|
480
691
|
|
|
481
692
|
### `html\`\`` tagged template
|
|
@@ -615,6 +826,9 @@ import type {
|
|
|
615
826
|
OnMountContext,
|
|
616
827
|
HandlerContext,
|
|
617
828
|
HandlerContextFor,
|
|
829
|
+
ErrorContext,
|
|
830
|
+
ErrorSource,
|
|
831
|
+
ExternalSignal,
|
|
618
832
|
MountOptions,
|
|
619
833
|
MountResult,
|
|
620
834
|
} from "ilha";
|
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,35 @@ type ContextSignal<T> = {
|
|
|
57
57
|
(value: T): void;
|
|
58
58
|
};
|
|
59
59
|
declare function ilhaContext<T>(key: string, initial: T): ContextSignal<T>;
|
|
60
|
+
/**
|
|
61
|
+
* Create a free-standing reactive signal that lives outside any island.
|
|
62
|
+
* Useful for sharing state across islands without prop drilling, or for
|
|
63
|
+
* binding form inputs to module-level state via `.bind(selector, signal)`.
|
|
64
|
+
*
|
|
65
|
+
* The returned accessor is a getter when called with no arguments and a
|
|
66
|
+
* setter when called with one. Reading it inside a `.derived()`, `.effect()`,
|
|
67
|
+
* or `.render()` automatically subscribes the surrounding reactive scope —
|
|
68
|
+
* so when the signal changes, dependents re-run as if it were local state.
|
|
69
|
+
*/
|
|
70
|
+
declare function ilhaSignal<T>(initial: T): ExternalSignal<T>;
|
|
71
|
+
/**
|
|
72
|
+
* Run `fn` with reactive tracking suspended. Reading signals inside `fn`
|
|
73
|
+
* returns their current value without subscribing the surrounding scope.
|
|
74
|
+
* Use this in effects/deriveds when you want to peek at state without
|
|
75
|
+
* causing a re-run on its changes.
|
|
76
|
+
*/
|
|
77
|
+
declare function untrack<T>(fn: () => T): T;
|
|
78
|
+
/**
|
|
79
|
+
* Run `fn` as an atomic batch — multiple signal writes inside the callback
|
|
80
|
+
* produce a single propagation pass, so dependents (effects, deriveds,
|
|
81
|
+
* island re-renders) see the final state and run once instead of once per
|
|
82
|
+
* write. Returns whatever `fn` returns.
|
|
83
|
+
*
|
|
84
|
+
* Note: `.on()` handlers and `.effect()` runs are batched implicitly, so
|
|
85
|
+
* you only need this when triggering multiple writes from outside an
|
|
86
|
+
* island (e.g. from a top-level event listener or async callback).
|
|
87
|
+
*/
|
|
88
|
+
declare function batch<T>(fn: () => T): T;
|
|
60
89
|
interface DerivedValue<T> {
|
|
61
90
|
loading: boolean;
|
|
62
91
|
value: T | undefined;
|
|
@@ -115,6 +144,13 @@ type EffectContext<TInput, TStateMap extends Record<string, unknown>> = {
|
|
|
115
144
|
state: IslandState<TStateMap>;
|
|
116
145
|
input: TInput;
|
|
117
146
|
host: Element;
|
|
147
|
+
/**
|
|
148
|
+
* AbortSignal that aborts when the effect re-runs (because a dependency
|
|
149
|
+
* changed) or when the island unmounts. Pass to `fetch` or check
|
|
150
|
+
* `signal.aborted` after `await` boundaries to bail out of stale work
|
|
151
|
+
* without needing a manual cleanup function.
|
|
152
|
+
*/
|
|
153
|
+
signal: AbortSignal;
|
|
118
154
|
};
|
|
119
155
|
type OnMountContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<string, never>> = {
|
|
120
156
|
state: IslandState<TStateMap>;
|
|
@@ -130,9 +166,18 @@ type HandlerContext<TInput, TStateMap extends Record<string, unknown>, TDerivedM
|
|
|
130
166
|
host: Element;
|
|
131
167
|
target: Element;
|
|
132
168
|
event: Event;
|
|
169
|
+
/**
|
|
170
|
+
* AbortSignal that fires when the island unmounts. If the handler's selector
|
|
171
|
+
* was registered with the `:abortable` modifier, the signal is also aborted
|
|
172
|
+
* when the same listener fires again on the same target (giving you free
|
|
173
|
+
* race-cancellation for things like search-as-you-type fetches). Pass this
|
|
174
|
+
* to `fetch`, `AbortController`-aware APIs, or check `signal.aborted`
|
|
175
|
+
* after `await` boundaries to bail out of stale work.
|
|
176
|
+
*/
|
|
177
|
+
signal: AbortSignal;
|
|
133
178
|
};
|
|
134
179
|
type HTMLEventName = keyof HTMLElementEventMap & string;
|
|
135
|
-
type Modifier = "once" | "capture" | "passive";
|
|
180
|
+
type Modifier = "once" | "capture" | "passive" | "abortable";
|
|
136
181
|
type WithModifiers<E extends string> = E | `${E}:${Modifier}` | `${E}:${Modifier}:${Modifier}` | `${E}:${Modifier}:${Modifier}:${Modifier}`;
|
|
137
182
|
type OnSelectorString = `@${WithModifiers<HTMLEventName>}` | `${string}@${WithModifiers<HTMLEventName>}`;
|
|
138
183
|
type HandlerContextFor<TInput, TStateMap extends Record<string, unknown>, TEventName extends string, TDerivedMap extends Record<string, unknown> = Record<string, never>> = {
|
|
@@ -142,6 +187,12 @@ type HandlerContextFor<TInput, TStateMap extends Record<string, unknown>, TEvent
|
|
|
142
187
|
host: Element;
|
|
143
188
|
target: TEventName extends keyof HTMLElementEventMap ? HTMLElementEventMap[TEventName]["target"] extends Element | null ? NonNullable<HTMLElementEventMap[TEventName]["target"]> : Element : Element;
|
|
144
189
|
event: TEventName extends keyof HTMLElementEventMap ? HTMLElementEventMap[TEventName] : Event;
|
|
190
|
+
/**
|
|
191
|
+
* AbortSignal that fires when the island unmounts. If the handler's selector
|
|
192
|
+
* was registered with the `:abortable` modifier, the signal is also aborted
|
|
193
|
+
* when the same listener fires again on the same target.
|
|
194
|
+
*/
|
|
195
|
+
signal: AbortSignal;
|
|
145
196
|
};
|
|
146
197
|
type StateInit<TInput, V> = V | ((input: TInput) => V);
|
|
147
198
|
interface StateEntry<TInput> {
|
|
@@ -152,11 +203,27 @@ interface OnEntry<TInput, TStateMap extends Record<string, unknown>, TDerivedMap
|
|
|
152
203
|
selector: string;
|
|
153
204
|
event: string;
|
|
154
205
|
options: AddEventListenerOptions;
|
|
206
|
+
abortable: boolean;
|
|
155
207
|
handler: (ctx: HandlerContext<TInput, TStateMap, TDerivedMap>) => void | Promise<void>;
|
|
156
208
|
}
|
|
157
209
|
interface EffectEntry<TInput, TStateMap extends Record<string, unknown>> {
|
|
158
210
|
fn: (ctx: EffectContext<TInput, TStateMap>) => (() => void) | void;
|
|
159
211
|
}
|
|
212
|
+
/** Where the error originated. `"on"` covers sync throws and async rejections
|
|
213
|
+
* from `.on()` handlers; `"effect"` covers sync throws from `.effect()` runs
|
|
214
|
+
* (async work spawned inside an effect is not awaited by the runtime). */
|
|
215
|
+
type ErrorSource = "on" | "effect";
|
|
216
|
+
type ErrorContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<string, never>> = {
|
|
217
|
+
error: Error;
|
|
218
|
+
source: ErrorSource;
|
|
219
|
+
state: IslandState<TStateMap>;
|
|
220
|
+
derived: IslandDerived<TDerivedMap>;
|
|
221
|
+
input: TInput;
|
|
222
|
+
host: Element;
|
|
223
|
+
};
|
|
224
|
+
interface OnErrorEntry<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown>> {
|
|
225
|
+
fn: (ctx: ErrorContext<TInput, TStateMap, TDerivedMap>) => void;
|
|
226
|
+
}
|
|
160
227
|
interface OnMountEntry<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown>> {
|
|
161
228
|
fn: (ctx: OnMountContext<TInput, TStateMap, TDerivedMap>) => (() => void) | void;
|
|
162
229
|
}
|
|
@@ -178,6 +245,7 @@ interface BuilderConfig<TInput, TStateMap extends Record<string, unknown>, TDeri
|
|
|
178
245
|
ons: OnEntry<TInput, TStateMap, TDerivedMap>[];
|
|
179
246
|
effects: EffectEntry<TInput, TStateMap>[];
|
|
180
247
|
onMounts: OnMountEntry<TInput, TStateMap, TDerivedMap>[];
|
|
248
|
+
onErrors: OnErrorEntry<TInput, TStateMap, TDerivedMap>[];
|
|
181
249
|
transition: TransitionOptions | null;
|
|
182
250
|
binds: BindEntry<TStateMap>[];
|
|
183
251
|
css: string | null;
|
|
@@ -195,6 +263,7 @@ declare class IlhaBuilder<TInput extends Record<string, unknown>, TStateMap exte
|
|
|
195
263
|
on(selectorOrCombined: string, handler: (ctx: HandlerContext<TInput, TStateMap, TDerivedMap>) => void | Promise<void>): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
196
264
|
effect(fn: (ctx: EffectContext<TInput, TStateMap>) => (() => void) | void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
197
265
|
onMount(fn: (ctx: OnMountContext<TInput, TStateMap, TDerivedMap>) => (() => void) | void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
266
|
+
onError(fn: (ctx: ErrorContext<TInput, TStateMap, TDerivedMap>) => void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
198
267
|
transition(opts: TransitionOptions): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
199
268
|
css(strings: TemplateStringsArray | string, ...values: (string | number)[]): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
200
269
|
render(fn: (ctx: RenderContext<TInput, TStateMap, TDerivedMap>) => string | RawHtml): Island<TInput, TStateMap>;
|
|
@@ -208,6 +277,9 @@ declare const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>,
|
|
|
208
277
|
mount: typeof mountAll;
|
|
209
278
|
from: typeof ilhaFrom;
|
|
210
279
|
context: typeof ilhaContext;
|
|
280
|
+
signal: typeof ilhaSignal;
|
|
281
|
+
batch: typeof batch;
|
|
282
|
+
untrack: typeof untrack;
|
|
211
283
|
};
|
|
212
284
|
declare const html: typeof ilhaHtml;
|
|
213
285
|
declare const raw: typeof ilhaRaw;
|
|
@@ -216,4 +288,4 @@ declare const mount: typeof mountAll;
|
|
|
216
288
|
declare const from: typeof ilhaFrom;
|
|
217
289
|
declare const context: typeof ilhaContext;
|
|
218
290
|
//#endregion
|
|
219
|
-
export { DerivedValue, HandlerContext, HandlerContextFor, HydratableOptions, Island, IslandDerived, IslandState, KeyedIsland, MountOptions, MountResult, OnMountContext, RawHtml, SignalAccessor, context, css, ilha as default, from, html, mount, raw };
|
|
291
|
+
export { DerivedValue, ErrorContext, ErrorSource, ExternalSignal, HandlerContext, HandlerContextFor, HydratableOptions, Island, IslandDerived, IslandState, KeyedIsland, MountOptions, MountResult, OnMountContext, RawHtml, SignalAccessor, batch, context, css, ilha as default, from, html, ilhaSignal, ilhaSignal as signal, mount, raw, untrack };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { effect, setActiveSub, signal } from "alien-signals";
|
|
1
|
+
import { effect, endBatch, setActiveSub, signal, startBatch } from "alien-signals";
|
|
2
2
|
//#region src/index.ts
|
|
3
3
|
const __DEV__ = typeof process !== "undefined" ? process.env?.["NODE_ENV"] !== "production" : true;
|
|
4
4
|
function warn(msg) {
|
|
@@ -212,6 +212,56 @@ function ilhaContext(key, initial) {
|
|
|
212
212
|
contextRegistry.set(key, accessor);
|
|
213
213
|
return accessor;
|
|
214
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Create a free-standing reactive signal that lives outside any island.
|
|
217
|
+
* Useful for sharing state across islands without prop drilling, or for
|
|
218
|
+
* binding form inputs to module-level state via `.bind(selector, signal)`.
|
|
219
|
+
*
|
|
220
|
+
* The returned accessor is a getter when called with no arguments and a
|
|
221
|
+
* setter when called with one. Reading it inside a `.derived()`, `.effect()`,
|
|
222
|
+
* or `.render()` automatically subscribes the surrounding reactive scope —
|
|
223
|
+
* so when the signal changes, dependents re-run as if it were local state.
|
|
224
|
+
*/
|
|
225
|
+
function ilhaSignal(initial) {
|
|
226
|
+
const s = signal(initial);
|
|
227
|
+
const accessor = (...args) => {
|
|
228
|
+
if (args.length === 0) return s();
|
|
229
|
+
s(args[0]);
|
|
230
|
+
};
|
|
231
|
+
return accessor;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Run `fn` with reactive tracking suspended. Reading signals inside `fn`
|
|
235
|
+
* returns their current value without subscribing the surrounding scope.
|
|
236
|
+
* Use this in effects/deriveds when you want to peek at state without
|
|
237
|
+
* causing a re-run on its changes.
|
|
238
|
+
*/
|
|
239
|
+
function untrack(fn) {
|
|
240
|
+
const prev = setActiveSub(void 0);
|
|
241
|
+
try {
|
|
242
|
+
return fn();
|
|
243
|
+
} finally {
|
|
244
|
+
setActiveSub(prev);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Run `fn` as an atomic batch — multiple signal writes inside the callback
|
|
249
|
+
* produce a single propagation pass, so dependents (effects, deriveds,
|
|
250
|
+
* island re-renders) see the final state and run once instead of once per
|
|
251
|
+
* write. Returns whatever `fn` returns.
|
|
252
|
+
*
|
|
253
|
+
* Note: `.on()` handlers and `.effect()` runs are batched implicitly, so
|
|
254
|
+
* you only need this when triggering multiple writes from outside an
|
|
255
|
+
* island (e.g. from a top-level event listener or async callback).
|
|
256
|
+
*/
|
|
257
|
+
function batch(fn) {
|
|
258
|
+
startBatch();
|
|
259
|
+
try {
|
|
260
|
+
return fn();
|
|
261
|
+
} finally {
|
|
262
|
+
endBatch();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
215
265
|
function buildDerivedSignals(entries, state, input, derivedSnapshot) {
|
|
216
266
|
const envelopes = /* @__PURE__ */ new Map();
|
|
217
267
|
const stops = [];
|
|
@@ -363,9 +413,34 @@ function parseOnArgs(selectorOrCombined) {
|
|
|
363
413
|
once: modifiers.has("once"),
|
|
364
414
|
capture: modifiers.has("capture"),
|
|
365
415
|
passive: modifiers.has("passive")
|
|
366
|
-
}
|
|
416
|
+
},
|
|
417
|
+
abortable: modifiers.has("abortable")
|
|
367
418
|
};
|
|
368
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* Combine multiple AbortSignals into one that aborts when any input aborts.
|
|
422
|
+
* Uses native AbortSignal.any when available, falls back to a manual chain
|
|
423
|
+
* otherwise (for older runtimes).
|
|
424
|
+
*/
|
|
425
|
+
function anySignal(signals) {
|
|
426
|
+
if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
|
|
427
|
+
const controller = new AbortController();
|
|
428
|
+
const cleanups = [];
|
|
429
|
+
for (const s of signals) {
|
|
430
|
+
if (s.aborted) {
|
|
431
|
+
controller.abort(s.reason);
|
|
432
|
+
cleanups.forEach((c) => c());
|
|
433
|
+
return controller.signal;
|
|
434
|
+
}
|
|
435
|
+
const handler = () => controller.abort(s.reason);
|
|
436
|
+
s.addEventListener("abort", handler, { once: true });
|
|
437
|
+
cleanups.push(() => s.removeEventListener("abort", handler));
|
|
438
|
+
}
|
|
439
|
+
if (!controller.signal.aborted) controller.signal.addEventListener("abort", () => {
|
|
440
|
+
cleanups.forEach((c) => c());
|
|
441
|
+
});
|
|
442
|
+
return controller.signal;
|
|
443
|
+
}
|
|
369
444
|
const _mountedHosts = __DEV__ ? /* @__PURE__ */ new WeakSet() : null;
|
|
370
445
|
var IlhaBuilder = class IlhaBuilder {
|
|
371
446
|
_cfg;
|
|
@@ -380,6 +455,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
380
455
|
ons: [],
|
|
381
456
|
effects: [],
|
|
382
457
|
onMounts: [],
|
|
458
|
+
onErrors: [],
|
|
383
459
|
transition: null,
|
|
384
460
|
binds: [],
|
|
385
461
|
css: null
|
|
@@ -422,6 +498,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
422
498
|
selector: parsed.selector,
|
|
423
499
|
event: parsed.eventType,
|
|
424
500
|
options: parsed.options,
|
|
501
|
+
abortable: parsed.abortable,
|
|
425
502
|
handler
|
|
426
503
|
}]
|
|
427
504
|
});
|
|
@@ -438,6 +515,12 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
438
515
|
onMounts: [...this._cfg.onMounts, { fn }]
|
|
439
516
|
});
|
|
440
517
|
}
|
|
518
|
+
onError(fn) {
|
|
519
|
+
return new IlhaBuilder({
|
|
520
|
+
...this._cfg,
|
|
521
|
+
onErrors: [...this._cfg.onErrors, { fn }]
|
|
522
|
+
});
|
|
523
|
+
}
|
|
441
524
|
transition(opts) {
|
|
442
525
|
return new IlhaBuilder({
|
|
443
526
|
...this._cfg,
|
|
@@ -462,7 +545,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
462
545
|
});
|
|
463
546
|
}
|
|
464
547
|
render(fn) {
|
|
465
|
-
const { schema, states, deriveds, ons, effects, onMounts, transition, binds, css: cssSource } = this._cfg;
|
|
548
|
+
const { schema, states, deriveds, ons, effects, onMounts, onErrors, transition, binds, css: cssSource } = this._cfg;
|
|
466
549
|
const stylePrefix = cssSource != null ? buildScopedStyle(cssSource) : "";
|
|
467
550
|
function resolveInput(props) {
|
|
468
551
|
const value = props ?? {};
|
|
@@ -522,6 +605,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
522
605
|
const input = resolveInput(props);
|
|
523
606
|
const state = buildPlainState(input);
|
|
524
607
|
const results = deriveds.map((entry) => {
|
|
608
|
+
const prevSub = setActiveSub(void 0);
|
|
525
609
|
try {
|
|
526
610
|
return {
|
|
527
611
|
key: entry.key,
|
|
@@ -536,6 +620,8 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
536
620
|
key: entry.key,
|
|
537
621
|
result: Promise.reject(err)
|
|
538
622
|
};
|
|
623
|
+
} finally {
|
|
624
|
+
setActiveSub(prevSub);
|
|
539
625
|
}
|
|
540
626
|
});
|
|
541
627
|
if (!results.some((r) => r.result instanceof Promise) || sync) {
|
|
@@ -550,12 +636,17 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
550
636
|
value: r.result,
|
|
551
637
|
error: void 0
|
|
552
638
|
};
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
639
|
+
const prevSub = setActiveSub(void 0);
|
|
640
|
+
try {
|
|
641
|
+
const { html } = renderWithCtx(() => fn({
|
|
642
|
+
state,
|
|
643
|
+
derived,
|
|
644
|
+
input
|
|
645
|
+
}));
|
|
646
|
+
return stylePrefix + html;
|
|
647
|
+
} finally {
|
|
648
|
+
setActiveSub(prevSub);
|
|
649
|
+
}
|
|
559
650
|
}
|
|
560
651
|
return Promise.all(results.map(async (r) => {
|
|
561
652
|
try {
|
|
@@ -580,12 +671,17 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
580
671
|
})).then(async (resolved) => {
|
|
581
672
|
const derived = {};
|
|
582
673
|
for (const r of resolved) derived[r.key] = r.envelope;
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
674
|
+
const prevSub = setActiveSub(void 0);
|
|
675
|
+
try {
|
|
676
|
+
const { html } = await renderWithCtx(() => fn({
|
|
677
|
+
state,
|
|
678
|
+
derived,
|
|
679
|
+
input
|
|
680
|
+
}), void 0, true);
|
|
681
|
+
return stylePrefix + html;
|
|
682
|
+
} finally {
|
|
683
|
+
setActiveSub(prevSub);
|
|
684
|
+
}
|
|
589
685
|
});
|
|
590
686
|
}
|
|
591
687
|
function mountIsland(host, props) {
|
|
@@ -628,12 +724,33 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
628
724
|
const shouldSkipOnMount = hydrated && snapshotRaw?.["_skipOnMount"] === true;
|
|
629
725
|
const state = buildSignalState(input, stateSnapshot);
|
|
630
726
|
const cleanups = [];
|
|
727
|
+
const unmountController = new AbortController();
|
|
728
|
+
cleanups.push(() => unmountController.abort());
|
|
631
729
|
if (transition?.enter) {
|
|
632
730
|
const result = transition.enter(host);
|
|
633
731
|
if (result instanceof Promise) result.catch(console.error);
|
|
634
732
|
}
|
|
635
733
|
const { proxy: derived, stop: stopDerived } = buildDerivedSignals(deriveds, state, input, derivedSnapshot);
|
|
636
734
|
cleanups.push(stopDerived);
|
|
735
|
+
function reportError(err, source) {
|
|
736
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
737
|
+
if (onErrors.length === 0) {
|
|
738
|
+
console.error(error);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
for (const entry of onErrors) try {
|
|
742
|
+
entry.fn({
|
|
743
|
+
error,
|
|
744
|
+
source,
|
|
745
|
+
state,
|
|
746
|
+
derived,
|
|
747
|
+
input,
|
|
748
|
+
host
|
|
749
|
+
});
|
|
750
|
+
} catch (handlerErr) {
|
|
751
|
+
console.error(handlerErr);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
637
754
|
const mountedSlots = /* @__PURE__ */ new Map();
|
|
638
755
|
function findSlot(id) {
|
|
639
756
|
const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/["\\]/g, "\\$&");
|
|
@@ -681,6 +798,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
681
798
|
}
|
|
682
799
|
const listeners = [];
|
|
683
800
|
const firedOnce = /* @__PURE__ */ new Set();
|
|
801
|
+
const invocationControllers = /* @__PURE__ */ new WeakMap();
|
|
684
802
|
function attachListeners() {
|
|
685
803
|
for (const entry of ons) {
|
|
686
804
|
if (entry.options.once && firedOnce.has(entry)) continue;
|
|
@@ -694,15 +812,41 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
694
812
|
listeners.splice(0, listeners.length, ...listeners.filter((l) => l.entry !== entry));
|
|
695
813
|
}
|
|
696
814
|
const eventTarget = event.target instanceof Element ? event.target : listenerTarget;
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
815
|
+
let handlerSignal;
|
|
816
|
+
if (entry.abortable) {
|
|
817
|
+
let entryMap = invocationControllers.get(entry);
|
|
818
|
+
if (!entryMap) {
|
|
819
|
+
entryMap = /* @__PURE__ */ new WeakMap();
|
|
820
|
+
invocationControllers.set(entry, entryMap);
|
|
821
|
+
}
|
|
822
|
+
const prev = entryMap.get(listenerTarget);
|
|
823
|
+
if (prev) prev.abort();
|
|
824
|
+
const invocationController = new AbortController();
|
|
825
|
+
entryMap.set(listenerTarget, invocationController);
|
|
826
|
+
handlerSignal = anySignal([unmountController.signal, invocationController.signal]);
|
|
827
|
+
} else handlerSignal = unmountController.signal;
|
|
828
|
+
let result;
|
|
829
|
+
startBatch();
|
|
830
|
+
try {
|
|
831
|
+
result = entry.handler({
|
|
832
|
+
state,
|
|
833
|
+
derived,
|
|
834
|
+
input,
|
|
835
|
+
host,
|
|
836
|
+
target: eventTarget,
|
|
837
|
+
event,
|
|
838
|
+
signal: handlerSignal
|
|
839
|
+
});
|
|
840
|
+
} catch (err) {
|
|
841
|
+
reportError(err, "on");
|
|
842
|
+
endBatch();
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
endBatch();
|
|
846
|
+
if (result instanceof Promise) result.catch((err) => {
|
|
847
|
+
if (err && err.name === "AbortError") return;
|
|
848
|
+
reportError(err, "on");
|
|
704
849
|
});
|
|
705
|
-
if (result instanceof Promise) result.catch(console.error);
|
|
706
850
|
};
|
|
707
851
|
const opts = {
|
|
708
852
|
...entry.options,
|
|
@@ -794,20 +938,41 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
794
938
|
cleanups.push(detachListeners);
|
|
795
939
|
for (const entry of effects) {
|
|
796
940
|
let userCleanup;
|
|
941
|
+
let runController = null;
|
|
797
942
|
const stopEffect = effect(() => {
|
|
798
943
|
if (userCleanup) {
|
|
799
|
-
|
|
944
|
+
try {
|
|
945
|
+
userCleanup();
|
|
946
|
+
} catch (err) {
|
|
947
|
+
reportError(err, "effect");
|
|
948
|
+
}
|
|
800
949
|
userCleanup = void 0;
|
|
801
950
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
951
|
+
if (runController) runController.abort();
|
|
952
|
+
runController = new AbortController();
|
|
953
|
+
const runSignal = anySignal([unmountController.signal, runController.signal]);
|
|
954
|
+
startBatch();
|
|
955
|
+
try {
|
|
956
|
+
userCleanup = entry.fn({
|
|
957
|
+
state,
|
|
958
|
+
input,
|
|
959
|
+
host,
|
|
960
|
+
signal: runSignal
|
|
961
|
+
});
|
|
962
|
+
} catch (err) {
|
|
963
|
+
reportError(err, "effect");
|
|
964
|
+
} finally {
|
|
965
|
+
endBatch();
|
|
966
|
+
}
|
|
807
967
|
});
|
|
808
968
|
cleanups.push(() => {
|
|
809
969
|
stopEffect();
|
|
810
|
-
if (userCleanup)
|
|
970
|
+
if (userCleanup) try {
|
|
971
|
+
userCleanup();
|
|
972
|
+
} catch (err) {
|
|
973
|
+
reportError(err, "effect");
|
|
974
|
+
}
|
|
975
|
+
if (runController) runController.abort();
|
|
811
976
|
});
|
|
812
977
|
}
|
|
813
978
|
let tornDown = false;
|
|
@@ -876,23 +1041,45 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
876
1041
|
if (doState) for (const entry of states) snapshotData[entry.key] = plainState[entry.key]();
|
|
877
1042
|
if (doDerived) {
|
|
878
1043
|
const derivedResults = {};
|
|
879
|
-
for (const entry of deriveds)
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
}
|
|
1044
|
+
for (const entry of deriveds) {
|
|
1045
|
+
const prevSub = setActiveSub(void 0);
|
|
1046
|
+
let resultPromise;
|
|
1047
|
+
let syncError;
|
|
1048
|
+
let threw = false;
|
|
1049
|
+
try {
|
|
1050
|
+
resultPromise = entry.fn({
|
|
1051
|
+
state: plainState,
|
|
1052
|
+
input,
|
|
1053
|
+
signal: new AbortController().signal
|
|
1054
|
+
});
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
threw = true;
|
|
1057
|
+
syncError = err;
|
|
1058
|
+
} finally {
|
|
1059
|
+
setActiveSub(prevSub);
|
|
1060
|
+
}
|
|
1061
|
+
if (threw) {
|
|
1062
|
+
derivedResults[entry.key] = {
|
|
1063
|
+
loading: false,
|
|
1064
|
+
value: void 0,
|
|
1065
|
+
error: syncError instanceof Error ? syncError.message : String(syncError)
|
|
1066
|
+
};
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
const result = await Promise.resolve(resultPromise);
|
|
1071
|
+
derivedResults[entry.key] = {
|
|
1072
|
+
loading: false,
|
|
1073
|
+
value: result,
|
|
1074
|
+
error: void 0
|
|
1075
|
+
};
|
|
1076
|
+
} catch (err) {
|
|
1077
|
+
derivedResults[entry.key] = {
|
|
1078
|
+
loading: false,
|
|
1079
|
+
value: void 0,
|
|
1080
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
896
1083
|
}
|
|
897
1084
|
snapshotData["_derived"] = derivedResults;
|
|
898
1085
|
}
|
|
@@ -958,6 +1145,7 @@ const rootBuilder = new IlhaBuilder({
|
|
|
958
1145
|
ons: [],
|
|
959
1146
|
effects: [],
|
|
960
1147
|
onMounts: [],
|
|
1148
|
+
onErrors: [],
|
|
961
1149
|
transition: null,
|
|
962
1150
|
binds: [],
|
|
963
1151
|
css: null
|
|
@@ -967,7 +1155,10 @@ const ilha = Object.assign(rootBuilder, {
|
|
|
967
1155
|
raw: ilhaRaw,
|
|
968
1156
|
mount: mountAll,
|
|
969
1157
|
from: ilhaFrom,
|
|
970
|
-
context: ilhaContext
|
|
1158
|
+
context: ilhaContext,
|
|
1159
|
+
signal: ilhaSignal,
|
|
1160
|
+
batch,
|
|
1161
|
+
untrack
|
|
971
1162
|
});
|
|
972
1163
|
const html = ilhaHtml;
|
|
973
1164
|
const raw = ilhaRaw;
|
|
@@ -976,4 +1167,4 @@ const mount = mountAll;
|
|
|
976
1167
|
const from = ilhaFrom;
|
|
977
1168
|
const context = ilhaContext;
|
|
978
1169
|
//#endregion
|
|
979
|
-
export { context, css, ilha as default, from, html, mount, raw };
|
|
1170
|
+
export { batch, context, css, ilha as default, from, html, ilhaSignal, ilhaSignal as signal, mount, raw, untrack };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilha",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "A tiny, framework-free island architecture library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ryuz <ryuzer@proton.me>",
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"alien-signals": "3.1.2"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"zod": "^4.3
|
|
29
|
+
"zod": "^4.4.3"
|
|
30
30
|
}
|
|
31
31
|
}
|