ilha 0.14.4 → 0.14.5

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 CHANGED
@@ -60,7 +60,7 @@ const total = atom(
60
60
  );
61
61
  ```
62
62
 
63
- Wrap multiple writes in `batch()`. Derived and mutation atoms use Effect's `Atom.map`, `Atom.transform`, and `Atom.fn` — pass `handle.atom`, then wrap in `atom()`. Use `watch(source, fn)` for side effects on atom changes.
63
+ Wrap multiple writes in `batch()`. Derived and mutation atoms use Effect's `Atom.map`, `Atom.transform`, and `Atom.fn` — pass `handle.atom`, then wrap in `atom()`. Use `watch(source, fn)` for side effects on atom changes. Use `watch.once(fn)` for mount-only work; return a cleanup function to run on unmount. Use `untrack(() => …)` to read without subscribing. Share values with `createContext` / `context()`. Catch subtree failures with `ErrorBoundary`.
64
64
 
65
65
  Use `atom.lazy(() => …)` for one-time initialization or to store a function value.
66
66
 
package/dist/define.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as Component } from "./types-DQVTD0ts.js";
1
+ import { n as Component } from "./types-CUQdA6hR.js";
2
2
  //#region src/define.d.ts
3
3
  declare const define: (name: string, component: Component) => void;
4
4
  //#endregion
package/dist/define.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as mount } from "./mount-hD-OyLIo.js";
1
+ import { t as mount } from "./mount-C5idXX3H.js";
2
2
 
3
3
  //#region src/define.ts
4
4
  const define = (name, component) => {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as Instruction, c as View, i as Fragment, l as Yielded, n as Component, t as AtomHandle } from "./types-DQVTD0ts.js";
2
- import { t as h } from "./vnode-DHBqDHtZ.js";
1
+ import { a as Instruction, i as Fragment, l as View, n as Component, t as AtomHandle, u as Yielded } from "./types-CUQdA6hR.js";
2
+ import { t as h } from "./vnode-Bwx8ffNO.js";
3
3
  import * as Effect from "effect/Effect";
4
4
  import * as Stream from "effect/Stream";
5
5
  import * as Atom from "effect/unstable/reactivity/Atom";
@@ -11,10 +11,17 @@ type WatchValue = string | number | boolean | bigint | null | undefined | readon
11
11
  interface WatchRecord {
12
12
  readonly [key: string]: WatchValue | undefined;
13
13
  }
14
- /** Run `fn` when `source` changes — and once on mount. Sync, async, and generator components. */
15
- declare const watch: <A extends WatchValue>(source: AtomHandle<A> | Atom.Atom<A> | Stream.Stream<A, unknown, unknown>, fn: (value: A) => void) => Instruction<undefined>;
14
+ /** Optional disposer returned from `watch.once`. */
15
+ type WatchOnceCleanup = () => void;
16
+ interface WatchFn {
17
+ <A extends WatchValue>(source: AtomHandle<A> | Atom.Atom<A> | Stream.Stream<A, unknown, unknown>, fn: (value: A) => void): Instruction<undefined>;
18
+ once: (fn: () => WatchOnceCleanup | undefined) => Instruction<undefined>;
19
+ }
20
+ declare const watch: WatchFn;
16
21
  //#endregion
17
22
  //#region src/atom.d.ts
23
+ /** Read atoms inside `fn` without subscribing the active render. Sync only. */
24
+ declare const untrack: <A>(fn: () => A) => A;
18
25
  interface AtomFn {
19
26
  <A>(init: A | Atom.Atom<A> | Effect.Effect<A, unknown, AtomRegistry> | Stream.Stream<A, unknown, AtomRegistry>): AtomHandle<A>;
20
27
  lazy: <A>(init: () => A) => AtomHandle<A>;
@@ -22,6 +29,34 @@ interface AtomFn {
22
29
  declare const atom: AtomFn;
23
30
  declare const batch: (f: () => void) => void;
24
31
  //#endregion
32
+ //#region src/context.d.ts
33
+ interface ContextProviderProps<A> {
34
+ value: A;
35
+ children?: View;
36
+ }
37
+ interface IlhaContext<A> {
38
+ readonly id: symbol;
39
+ readonly defaultValue: A;
40
+ readonly Provider: (props: ContextProviderProps<A>) => View;
41
+ }
42
+ declare const createContext: <A>(defaultValue: A) => IlhaContext<A>;
43
+ /** Read a context value as an atom handle. Subscribes the active fiber on read. */
44
+ declare const context: <A>(ctx: IlhaContext<A>) => AtomHandle<A>;
45
+ //#endregion
46
+ //#region src/error-boundary.d.ts
47
+ interface ErrorFallbackProps {
48
+ error: Error;
49
+ reset: () => void;
50
+ }
51
+ type ErrorFallback = (props: ErrorFallbackProps) => View;
52
+ interface ErrorBoundaryProps {
53
+ fallback?: ErrorFallback;
54
+ onError?: (error: Error) => void;
55
+ children?: View;
56
+ }
57
+ /** Catch subtree failures and paint a fallback. Supports reset(). */
58
+ declare const ErrorBoundary: (props: ErrorBoundaryProps) => View;
59
+ //#endregion
25
60
  //#region src/when.d.ts
26
61
  declare const when: <A, E = never, R = never>(stream: Stream.Stream<A, E, R>, body: (value: A) => Generator<Yielded, View | undefined, View>) => Instruction<undefined, E>;
27
62
  //#endregion
@@ -39,4 +74,4 @@ interface MountOptions {
39
74
  declare const mount: (el: Element, fn: Component, opts?: MountOptions) => (() => void);
40
75
  declare const renderToString: (fn: Component, opts?: RenderToStringOptions) => Promise<string>;
41
76
  //#endregion
42
- export { type AtomHandle, type Component, Fragment, type MountOptions, type RenderToStringOptions, type View, atom, batch, h, mount, renderToString, watch, when };
77
+ export { type AtomHandle, type Component, type ContextProviderProps, ErrorBoundary, type ErrorBoundaryProps, type ErrorFallback, type ErrorFallbackProps, Fragment, type IlhaContext, type MountOptions, type RenderToStringOptions, type View, type WatchOnceCleanup, atom, batch, context, createContext, h, mount, renderToString, untrack, watch, when };
package/dist/index.js CHANGED
@@ -1,13 +1,82 @@
1
- import { a as batch, c as isAtomHandle, d as makeFiber, i as atom, l as closeFiber, n as renderToString, o as handleOwner, r as interpret, s as instr, t as mount, u as getFiber } from "./mount-hD-OyLIo.js";
2
- import { i as Fragment, t as h } from "./vnode-DP76Gn-b.js";
1
+ import { a as atom, c as instr, d as wrapHandle, f as clearFiberView, h as makeFiber, i as interpret, l as isAtomHandle, m as getFiber, n as renderToString, o as batch, p as closeFiber, r as runSetup, s as handleOwner, t as mount, u as untrack } from "./mount-C5idXX3H.js";
2
+ import { d as errorView, i as Fragment, p as isFunction, t as h } from "./vnode-DEMDQ0eA.js";
3
3
  import * as Effect from "effect/Effect";
4
4
  import * as Stream from "effect/Stream";
5
+ import * as Atom from "effect/unstable/reactivity/Atom";
5
6
  import * as Exit from "effect/Exit";
6
7
  import * as Fiber from "effect/Fiber";
7
8
  import * as Scope from "effect/Scope";
8
9
  import * as Registry from "effect/unstable/reactivity/AtomRegistry";
9
10
 
11
+ //#region src/context.ts
12
+ const defaults = /* @__PURE__ */ new WeakMap();
13
+ const getDefaultAtom = (ctx) => {
14
+ const existing = defaults.get(ctx);
15
+ if (existing) return existing;
16
+ const made = Atom.make(ctx.defaultValue);
17
+ defaults.set(ctx, made);
18
+ return made;
19
+ };
20
+ const createContext = (defaultValue) => {
21
+ const id = Symbol("ilha.context");
22
+ const Provider = (props) => {
23
+ const fiber = getFiber();
24
+ const { value: next, children } = props;
25
+ const handle = atom(next);
26
+ if (!Object.is(untrack(() => handle()), next)) handle.set(next);
27
+ fiber.contexts ??= /* @__PURE__ */ new Map();
28
+ fiber.contexts.set(id, handle.atom);
29
+ return children;
30
+ };
31
+ return {
32
+ Provider,
33
+ defaultValue,
34
+ id
35
+ };
36
+ };
37
+ /** Read a context value as an atom handle. Subscribes the active fiber on read. */
38
+ const context = (ctx) => {
39
+ const fiber = getFiber();
40
+ let current = fiber;
41
+ while (current) {
42
+ const found = current.contexts?.get(ctx.id);
43
+ if (found) return wrapHandle(found, fiber);
44
+ current = current.parent;
45
+ }
46
+ return wrapHandle(getDefaultAtom(ctx), fiber);
47
+ };
48
+
49
+ //#endregion
50
+ //#region src/error-boundary.ts
51
+ /** Catch subtree failures and paint a fallback. Supports reset(). */
52
+ const ErrorBoundary = (props) => {
53
+ const fiber = getFiber();
54
+ const remount = () => {
55
+ if (fiber.closed) return;
56
+ clearFiberView(fiber);
57
+ const current = fiber.propsBox?.current ?? props;
58
+ runSetup(fiber, () => ErrorBoundary(current));
59
+ };
60
+ fiber.handleError = (error) => {
61
+ if (fiber.closed) return;
62
+ clearFiberView(fiber);
63
+ const { onError, fallback } = fiber.propsBox?.current ?? props;
64
+ if (isFunction(onError)) onError(error);
65
+ if (isFunction(fallback)) {
66
+ fiber.paint(fallback({
67
+ error,
68
+ reset: remount
69
+ }));
70
+ return;
71
+ }
72
+ fiber.paint(errorView(error));
73
+ };
74
+ return props.children;
75
+ };
76
+
77
+ //#endregion
10
78
  //#region src/watch.ts
79
+ const WATCH_ONCE = Symbol("ilha.watch.once");
11
80
  const runStreamWatch = (fiber, effect) => {
12
81
  const scope = Scope.forkUnsafe(fiber.scope);
13
82
  const provided = effect.pipe(Effect.provideService(Registry.AtomRegistry, fiber.registry));
@@ -24,7 +93,7 @@ const useWatchSlot = (fiber, key, mount, fn) => {
24
93
  const existing = fiber.watchSlots[i];
25
94
  if (existing) {
26
95
  if (existing.key === key) {
27
- existing.fnRef.current = fn;
96
+ if (existing.fnRef) existing.fnRef.current = fn;
28
97
  return;
29
98
  }
30
99
  existing.dispose();
@@ -58,12 +127,36 @@ const registerWatch = (fiber, source, fn) => {
58
127
  const atom = isAtomHandle(source) ? source.atom : source;
59
128
  useWatchSlot(fiber, atom, (run) => fiber.registry.subscribe(atom, run, { immediate: true }), fn);
60
129
  };
130
+ /** Run `fn` once on mount. Return a function to clean up on unmount. */
131
+ const watchOnce = (fn) => {
132
+ const fiber = getFiber();
133
+ const i = fiber.watchI ?? 0;
134
+ fiber.watchI = i + 1;
135
+ fiber.watchSlots ??= [];
136
+ const existing = fiber.watchSlots[i];
137
+ if (existing) {
138
+ if (existing.key === WATCH_ONCE) return instr(Effect.void);
139
+ existing.dispose();
140
+ fiber.watchSlots[i] = void 0;
141
+ }
142
+ let cleanup = fn();
143
+ fiber.watchSlots[i] = {
144
+ dispose: () => {
145
+ const run = cleanup;
146
+ cleanup = void 0;
147
+ run?.();
148
+ },
149
+ key: WATCH_ONCE
150
+ };
151
+ return instr(Effect.void);
152
+ };
61
153
  /** Run `fn` when `source` changes — and once on mount. Sync, async, and generator components. */
62
- const watch = (source, fn) => {
154
+ const watchSource = (source, fn) => {
63
155
  const fiber = (isAtomHandle(source) ? handleOwner.get(source) : void 0) ?? getFiber();
64
156
  registerWatch(fiber, source, fn);
65
157
  return instr(Effect.void);
66
158
  };
159
+ const watch = Object.assign(watchSource, { once: watchOnce });
67
160
 
68
161
  //#endregion
69
162
  //#region src/when.ts
@@ -78,10 +171,13 @@ const when = (stream, body) => instr(Effect.gen(function* whenBody() {
78
171
  const src = parent.runtime.ssr ? base.pipe(Stream.take(1)) : base;
79
172
  yield* Stream.runForEach(src, (value) => Effect.sync(() => {
80
173
  if (child) closeFiber(child);
81
- child = makeFiber(parent.runtime, parent.root, parent.paintFn, { onFail: parent.fail });
174
+ child = makeFiber(parent.runtime, parent.root, parent.paintFn, {
175
+ onFail: parent.fail,
176
+ parent
177
+ });
82
178
  interpret((() => body(value)), child);
83
179
  }));
84
180
  }));
85
181
 
86
182
  //#endregion
87
- export { Fragment, atom, batch, h, mount, renderToString, watch, when };
183
+ export { ErrorBoundary, Fragment, atom, batch, context, createContext, h, mount, renderToString, untrack, watch, when };
@@ -1,4 +1,4 @@
1
- import { i as Fragment } from "./types-DQVTD0ts.js";
2
- import { n as JSX } from "./vnode-DHBqDHtZ.js";
1
+ import { i as Fragment } from "./types-CUQdA6hR.js";
2
+ import { n as JSX } from "./vnode-Bwx8ffNO.js";
3
3
  import { jsx, jsxDEV, jsxs } from "./jsx-runtime.js";
4
4
  export { Fragment, type JSX, jsx, jsxDEV, jsxs };
@@ -1,4 +1,4 @@
1
- import { i as Fragment } from "./vnode-DP76Gn-b.js";
1
+ import { i as Fragment } from "./vnode-DEMDQ0eA.js";
2
2
  import { jsx, jsxDEV, jsxs } from "./jsx-runtime.js";
3
3
 
4
4
  export { Fragment, jsx, jsxDEV, jsxs };
@@ -1,8 +1,8 @@
1
- import { c as View, i as Fragment, o as PropBag, r as ComponentFn } from "./types-DQVTD0ts.js";
2
- import { n as JSX, t as h } from "./vnode-DHBqDHtZ.js";
1
+ import { i as Fragment, l as View, o as JsxComponent, r as ComponentFn, s as PropBag } from "./types-CUQdA6hR.js";
2
+ import { n as JSX, t as h } from "./vnode-Bwx8ffNO.js";
3
3
  //#region src/jsx-runtime.d.ts
4
- declare const jsx: (type: string | Fragment | ComponentFn, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
5
- declare const jsxs: (type: string | Fragment | ComponentFn, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
6
- declare const jsxDEV: (type: string | Fragment | ComponentFn, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
4
+ declare const jsx: (type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
5
+ declare const jsxs: (type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
6
+ declare const jsxDEV: (type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
7
7
  //#endregion
8
8
  export { Fragment, type JSX, type View, h, jsx, jsxDEV, jsxs };
@@ -1,4 +1,4 @@
1
- import { i as Fragment, t as h } from "./vnode-DP76Gn-b.js";
1
+ import { i as Fragment, t as h } from "./vnode-DEMDQ0eA.js";
2
2
 
3
3
  //#region src/jsx-runtime.ts
4
4
  const jsx = (type, props, key) => h(type, key === void 0 ? props : {
@@ -1,4 +1,4 @@
1
- import { _ as skip, a as ISLAND, b as failureMessage, c as KEEP, d as errorView, f as isBigInt, g as isString, h as isObject, i as Fragment, l as KEY_ATTR, m as isNumber, n as isSetupFn, o as ISLAND_MOUNT_INTERNAL, p as isFunction, r as isVNode, s as ISLAND_SLOT_TAG, u as SLOT_ATTR, v as unwrap, y as asFailure } from "./vnode-DP76Gn-b.js";
1
+ import { _ as skip, a as ISLAND, b as failureMessage, c as KEEP, d as errorView, f as isBigInt, g as isString, h as isObject, i as Fragment, l as KEY_ATTR, m as isNumber, n as isSetupFn, o as ISLAND_MOUNT_INTERNAL, p as isFunction, r as isVNode, s as ISLAND_SLOT_TAG, u as SLOT_ATTR, v as unwrap, x as toError, y as asFailure } from "./vnode-DEMDQ0eA.js";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Stream from "effect/Stream";
4
4
  import * as Atom from "effect/unstable/reactivity/Atom";
@@ -34,17 +34,27 @@ const runInScope = (effect, registry, scope, onOk, onErr, onDone) => {
34
34
  }))) : provided);
35
35
  Effect.runSync(Scope.addFinalizer(scope, Fiber.interrupt(f)));
36
36
  };
37
- const closeFiber = (fiber) => {
38
- if (fiber.closed) return;
39
- fiber.closed = true;
40
- fiber.trackRestore?.();
41
- fiber.trackRestore = void 0;
37
+ /** Clear render subscriptions and child holes without closing the fiber scope. */
38
+ const clearFiberView = (fiber) => {
42
39
  fiber.renderSub?.();
43
40
  fiber.renderSub = void 0;
44
41
  for (const slot of fiber.watchSlots ?? []) slot?.dispose();
45
42
  fiber.watchSlots = [];
43
+ fiber.watchI = 0;
44
+ fiber.primitives = void 0;
45
+ fiber.primitiveI = 0;
46
+ fiber.islandFrame = void 0;
47
+ fiber.keyedHoles = void 0;
46
48
  for (const h of fiber.holes) h.dispose();
47
49
  fiber.holes = [];
50
+ };
51
+ const closeFiber = (fiber) => {
52
+ if (fiber.closed) return;
53
+ fiber.closed = true;
54
+ fiber.trackRestore?.();
55
+ fiber.trackRestore = void 0;
56
+ fiber.handleError = void 0;
57
+ clearFiberView(fiber);
48
58
  Effect.runFork(Scope.close(fiber.scope, Exit.void));
49
59
  };
50
60
  const makeRuntime = (opts) => {
@@ -107,6 +117,7 @@ const makeFiber = (runtime, root, paint, opts) => {
107
117
  inFlight: false,
108
118
  paint: (view) => paint(fiber, view),
109
119
  paintFn: paint,
120
+ parent: opts?.parent,
110
121
  registry: runtime.registry,
111
122
  root,
112
123
  run: (effect, onOk, onErr) => {
@@ -123,6 +134,19 @@ const makeFiber = (runtime, root, paint, opts) => {
123
134
  };
124
135
  return fiber;
125
136
  };
137
+ /** Walk this fiber and ancestors for an error boundary handler. */
138
+ const reportFiberError = (fiber, error) => {
139
+ let current = fiber;
140
+ while (current) {
141
+ const { handleError } = current;
142
+ if (handleError) {
143
+ handleError(error);
144
+ return true;
145
+ }
146
+ current = current.parent;
147
+ }
148
+ return false;
149
+ };
126
150
 
127
151
  //#endregion
128
152
  //#region src/atom.ts
@@ -161,6 +185,16 @@ const instr = (effect) => {
161
185
  const isAtomHandle = (x) => isFunction(x) && x.$$atom === 1;
162
186
  let trackGet;
163
187
  const trackStack = [];
188
+ /** Read atoms inside `fn` without subscribing the active render. Sync only. */
189
+ const untrack = (fn) => {
190
+ const prev = trackGet;
191
+ trackGet = void 0;
192
+ try {
193
+ return fn();
194
+ } finally {
195
+ trackGet = prev;
196
+ }
197
+ };
164
198
  const beginPrimitiveFrame = (fiber) => {
165
199
  const used = fiber.primitiveI ?? 0;
166
200
  const slots = fiber.primitives?.length ?? 0;
@@ -377,6 +411,60 @@ const interpret = (gen, fiber) => {
377
411
  resume();
378
412
  };
379
413
 
414
+ //#endregion
415
+ //#region src/start.ts
416
+ const isGen = (x) => {
417
+ if (x === null || x === void 0) return false;
418
+ const g = x;
419
+ return isFunction(g.next) && isFunction(g.throw);
420
+ };
421
+ const finish = (fiber, view) => {
422
+ if (fiber.closed) return;
423
+ if (view !== void 0) fiber.paint(view);
424
+ };
425
+ const runSetup = (fiber, fn) => {
426
+ const disposeRenderSub = () => {
427
+ fiber.renderSub?.();
428
+ fiber.renderSub = void 0;
429
+ };
430
+ const api = {
431
+ commit: (out, deps) => {
432
+ if (isGen(out)) {
433
+ disposeRenderSub();
434
+ interpret((() => out), fiber);
435
+ return;
436
+ }
437
+ disposeRenderSub();
438
+ if (!fiber.runtime.ssr) fiber.renderSub = subscribeRenderDeps(fiber, deps, () => api.render());
439
+ withFiber(fiber, () => {
440
+ finish(fiber, out);
441
+ });
442
+ },
443
+ render: () => {
444
+ if (fiber.closed) return;
445
+ fiber.runtime.begin();
446
+ beginPrimitiveFrame(fiber);
447
+ withTrackGetRun(fiber, () => fn(), ({ value: out, deps }) => {
448
+ try {
449
+ api.commit(out, deps);
450
+ } catch (error) {
451
+ withFiber(fiber, () => fiber.fail(asFailure(error)));
452
+ } finally {
453
+ fiber.runtime.end();
454
+ }
455
+ }, (e) => {
456
+ withFiber(fiber, () => fiber.fail(asFailure(e)));
457
+ fiber.runtime.end();
458
+ });
459
+ }
460
+ };
461
+ try {
462
+ api.render();
463
+ } catch (error) {
464
+ fiber.fail(asFailure(error));
465
+ }
466
+ };
467
+
380
468
  //#endregion
381
469
  //#region src/events.ts
382
470
  const ACTION_CALL$1 = Symbol.for("ilha.actionCall");
@@ -538,8 +626,13 @@ const morphElementPair = (fromEl, toEl, api) => {
538
626
  fromEl.replaceWith(toEl);
539
627
  return;
540
628
  }
541
- const slotId = toEl.getAttribute(SLOT_ATTR);
542
- if (slotId !== null && fromEl.getAttribute("data-ilha-slot") === slotId) return;
629
+ const toSlot = toEl.getAttribute(SLOT_ATTR);
630
+ const fromSlot = fromEl.getAttribute(SLOT_ATTR);
631
+ if (toSlot !== null && fromSlot === toSlot) return;
632
+ if (toSlot !== null) {
633
+ fromEl.replaceWith(toEl);
634
+ return;
635
+ }
543
636
  if (fromEl.localName === "input") {
544
637
  morphInput(fromEl, toEl);
545
638
  return;
@@ -691,60 +784,6 @@ const serializeStyleAttr = (value) => {
691
784
  return serializeStyleEntries(value);
692
785
  };
693
786
 
694
- //#endregion
695
- //#region src/start.ts
696
- const isGen = (x) => {
697
- if (x === null || x === void 0) return false;
698
- const g = x;
699
- return isFunction(g.next) && isFunction(g.throw);
700
- };
701
- const finish = (fiber, view) => {
702
- if (fiber.closed) return;
703
- if (view !== void 0) fiber.paint(view);
704
- };
705
- const runSetup = (fiber, fn) => {
706
- const disposeRenderSub = () => {
707
- fiber.renderSub?.();
708
- fiber.renderSub = void 0;
709
- };
710
- const api = {
711
- commit: (out, deps) => {
712
- if (isGen(out)) {
713
- disposeRenderSub();
714
- interpret((() => out), fiber);
715
- return;
716
- }
717
- disposeRenderSub();
718
- if (!fiber.runtime.ssr) fiber.renderSub = subscribeRenderDeps(fiber, deps, () => api.render());
719
- withFiber(fiber, () => {
720
- finish(fiber, out);
721
- });
722
- },
723
- render: () => {
724
- if (fiber.closed) return;
725
- fiber.runtime.begin();
726
- beginPrimitiveFrame(fiber);
727
- withTrackGetRun(fiber, () => fn(), ({ value: out, deps }) => {
728
- try {
729
- api.commit(out, deps);
730
- } catch (error) {
731
- withFiber(fiber, () => fiber.fail(asFailure(error)));
732
- } finally {
733
- fiber.runtime.end();
734
- }
735
- }, (e) => {
736
- withFiber(fiber, () => fiber.fail(asFailure(e)));
737
- fiber.runtime.end();
738
- });
739
- }
740
- };
741
- try {
742
- api.render();
743
- } catch (error) {
744
- fiber.fail(asFailure(error));
745
- }
746
- };
747
-
748
787
  //#endregion
749
788
  //#region src/paint-core.ts
750
789
  const disposeHoles = (fiber, opts) => {
@@ -862,10 +901,15 @@ const createPainter = (ops) => {
862
901
  openHole: (parent) => {
863
902
  const id = parent.runtime.nextHole();
864
903
  const host = ops.createSlotHost(String(id));
865
- const child = makeFiber(parent.runtime, ops.asRoot(host), (f, v) => api.paintHole(f, v), { onFail: (e) => {
866
- console.error(e);
867
- api.paintHole(child, errorView(e));
868
- } });
904
+ const child = makeFiber(parent.runtime, ops.asRoot(host), (f, v) => api.paintHole(f, v), {
905
+ onFail: (e) => {
906
+ const error = toError(e);
907
+ if (reportFiberError(child, error)) return;
908
+ console.error(error);
909
+ api.paintHole(child, errorView(error));
910
+ },
911
+ parent
912
+ });
869
913
  return {
870
914
  fiber: child,
871
915
  nodes: [host]
@@ -979,9 +1023,10 @@ const createPainter = (ops) => {
979
1023
  if (branded[ISLAND] === true) return materializeIsland(branded, props, fiber);
980
1024
  const k = view.key === null || view.key === void 0 ? void 0 : String(view.key);
981
1025
  const reuse = k ? fiber.keyedHoles?.get(k) : void 0;
1026
+ const run = type;
982
1027
  if (reuse && !reuse.closed) {
983
1028
  if (reuse.propsBox) reuse.propsBox.current = props;
984
- const next = withFiber(fiber, () => type(reuse.propsBox?.current ?? props));
1029
+ const next = withFiber(fiber, () => run(reuse.propsBox?.current ?? props));
985
1030
  if (next && !isThenable(next) && !isSetupFn(next)) reuse.paint(next);
986
1031
  return [ops.asNode(reuse.root)];
987
1032
  }
@@ -995,7 +1040,7 @@ const createPainter = (ops) => {
995
1040
  if (hole.closed) return;
996
1041
  const box = hole.propsBox;
997
1042
  if (!box) return;
998
- runSetup(hole, () => type(box.current));
1043
+ runSetup(hole, () => run(box.current));
999
1044
  });
1000
1045
  trackHole(fiber, hole, void 0, k ? { keyed: true } : void 0);
1001
1046
  return nodes;
@@ -1516,4 +1561,4 @@ const renderToString = async (fn, opts) => {
1516
1561
  };
1517
1562
 
1518
1563
  //#endregion
1519
- export { batch as a, isAtomHandle as c, makeFiber as d, atom as i, closeFiber as l, renderToString as n, handleOwner as o, interpret as r, instr as s, mount as t, getFiber as u };
1564
+ export { atom as a, instr as c, wrapHandle as d, clearFiberView as f, makeFiber as h, interpret as i, isAtomHandle as l, getFiber as m, renderToString as n, batch as o, closeFiber as p, runSetup as r, handleOwner as s, mount as t, untrack as u };
@@ -16,9 +16,11 @@ type View = VNode | JsonText | AtomHandle<unknown> | Stream.Stream<View, unknown
16
16
  declare const Fragment: unique symbol;
17
17
  type Fragment = typeof Fragment;
18
18
  type ComponentFn = (props: PropBag) => View | Promise<View | undefined> | undefined;
19
+ /** JSX/tag component with its own props shape (Provider, ErrorBoundary, …). */
20
+ type JsxComponent = (props: never) => View | Promise<View | undefined> | Generator<View, View | undefined, View> | undefined;
19
21
  interface VNode {
20
22
  readonly $$ilha: 1;
21
- readonly type: string | Fragment | ComponentFn;
23
+ readonly type: string | Fragment | ComponentFn | JsxComponent;
22
24
  readonly props: PropBag;
23
25
  readonly children: View[];
24
26
  readonly key?: string | number;
@@ -38,4 +40,4 @@ interface AtomHandle<A> {
38
40
  update: (f: (current: A) => A) => void;
39
41
  }
40
42
  //#endregion
41
- export { Instruction as a, View as c, Fragment as i, Yielded as l, Component as n, PropBag as o, ComponentFn as r, VNode as s, AtomHandle as t };
43
+ export { Instruction as a, VNode as c, Fragment as i, View as l, Component as n, JsxComponent as o, ComponentFn as r, PropBag as s, AtomHandle as t, Yielded as u };
@@ -1,8 +1,8 @@
1
- import { c as View, i as Fragment, o as PropBag, r as ComponentFn, s as VNode, t as AtomHandle } from "./types-DQVTD0ts.js";
1
+ import { c as VNode, i as Fragment, l as View, o as JsxComponent, r as ComponentFn, s as PropBag, t as AtomHandle } from "./types-CUQdA6hR.js";
2
2
  //#region src/jsx-types.d.ts
3
3
  declare namespace JSX {
4
4
  export type Element = View;
5
- export type ElementType = string | ((props: PropBag) => View | Promise<View | undefined> | Generator<View, View | undefined, View>);
5
+ export type ElementType = string | ((props: never) => View | Promise<View | undefined> | Generator<View, View | undefined, View> | undefined);
6
6
  export interface ElementChildrenAttribute {
7
7
  children: View;
8
8
  }
@@ -580,6 +580,6 @@ declare namespace JSX {
580
580
  }
581
581
  //#endregion
582
582
  //#region src/vnode.d.ts
583
- declare const h: (type: string | Fragment | ComponentFn, props: PropBag | null | undefined, ...rest: View[]) => VNode;
583
+ declare const h: (type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null | undefined, ...rest: View[]) => VNode;
584
584
  //#endregion
585
585
  export { JSX as n, h as t };
@@ -10,10 +10,12 @@ const asFailure = (e) => {
10
10
  }
11
11
  return Result.fail(e instanceof Error ? e : new Error(String(e)));
12
12
  };
13
- const failureMessage = (e) => asFailure(e).pipe(Result.match({
14
- onFailure: (err) => err.message,
15
- onSuccess: () => "error"
13
+ /** Normalize any thrown failure into an `Error` instance. */
14
+ const toError = (e) => asFailure(e).pipe(Result.match({
15
+ onFailure: (err) => err,
16
+ onSuccess: () => /* @__PURE__ */ new Error("error")
16
17
  }));
18
+ const failureMessage = (e) => toError(e).message;
17
19
 
18
20
  //#endregion
19
21
  //#region src/shared.ts
@@ -86,4 +88,4 @@ const isSetupFn = (x) => {
86
88
  };
87
89
 
88
90
  //#endregion
89
- export { skip as _, ISLAND as a, failureMessage as b, KEEP as c, errorView as d, isBigInt as f, isString as g, isObject as h, Fragment as i, KEY_ATTR as l, isNumber as m, isSetupFn as n, ISLAND_MOUNT_INTERNAL as o, isFunction as p, isVNode as r, ISLAND_SLOT_TAG as s, h as t, SLOT_ATTR as u, unwrap as v, asFailure as y };
91
+ export { skip as _, ISLAND as a, failureMessage as b, KEEP as c, errorView as d, isBigInt as f, isString as g, isObject as h, Fragment as i, KEY_ATTR as l, isNumber as m, isSetupFn as n, ISLAND_MOUNT_INTERNAL as o, isFunction as p, isVNode as r, ISLAND_SLOT_TAG as s, h as t, SLOT_ATTR as u, unwrap as v, toError as x, asFailure as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilha",
3
- "version": "0.14.4",
3
+ "version": "0.14.5",
4
4
  "description": "Generator-first island UI on Effect",
5
5
  "keywords": [
6
6
  "effect",