ilha 0.14.3 → 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
@@ -55,10 +55,12 @@ import * as Atom from "effect/unstable/reactivity/Atom";
55
55
  import { atom } from "ilha";
56
56
 
57
57
  const items = atom([{ n: 1 }, { n: 2 }]);
58
- const total = atom(Atom.map(items.atom, (list) => list.reduce((sum, item) => sum + item.n, 0)));
58
+ const total = atom(
59
+ Atom.map(items.atom, (list) => list.reduce((sum, item) => sum + item.n, 0))
60
+ );
59
61
  ```
60
62
 
61
- 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`.
62
64
 
63
65
  Use `atom.lazy(() => …)` for one-time initialization or to store a function value.
64
66
 
package/dist/define.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { n as Component } from "./types-Cie0Ihl6.js";
1
+ import { n as Component } from "./types-CUQdA6hR.js";
2
2
  //#region src/define.d.ts
3
- declare function define(name: string, component: Component): void;
3
+ declare const define: (name: string, component: Component) => void;
4
4
  //#endregion
5
5
  export { define };
package/dist/define.js CHANGED
@@ -1,18 +1,18 @@
1
- import { t as mount } from "./mount-CCMOkDSb.js";
1
+ import { t as mount } from "./mount-C5idXX3H.js";
2
2
 
3
3
  //#region src/define.ts
4
- function define(name, component) {
4
+ const define = (name, component) => {
5
5
  customElements.define(name, class extends HTMLElement {
6
6
  #unmount;
7
7
  connectedCallback() {
8
- this.#unmount = mount(this, component, { hydrate: this.hasAttribute("data-ilha") });
8
+ this.#unmount = mount(this, component, { hydrate: this.matches("[data-ilha]") });
9
9
  }
10
10
  disconnectedCallback() {
11
11
  this.#unmount?.();
12
12
  this.#unmount = void 0;
13
13
  }
14
14
  });
15
- }
15
+ };
16
16
 
17
17
  //#endregion
18
18
  export { define };
package/dist/index.d.ts CHANGED
@@ -1,37 +1,77 @@
1
- import { i as Instruction, n as Component, o as View, r as Fragment, s as Yielded, t as AtomHandle } from "./types-Cie0Ihl6.js";
2
- import { t as h } from "./vnode-15tImQ6O.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";
6
6
  import "effect/Scope";
7
7
  import { AtomRegistry } from "effect/unstable/reactivity/AtomRegistry";
8
8
  //#region src/watch.d.ts
9
- /** Run `fn` when `source` changes — and once on mount. Sync, async, and generator components. */
10
- declare function watch<A>(source: AtomHandle<A> | Atom.Atom<A> | Stream.Stream<A, any, any>, fn: (value: A) => void): Instruction<void>;
9
+ /** Values delivered to watch callbacks. */
10
+ type WatchValue = string | number | boolean | bigint | null | undefined | readonly WatchValue[] | WatchRecord;
11
+ interface WatchRecord {
12
+ readonly [key: string]: WatchValue | undefined;
13
+ }
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;
11
21
  //#endregion
12
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;
13
25
  interface AtomFn {
14
- <A>(init: A | Atom.Atom<A> | Effect.Effect<A, any, any> | Stream.Stream<A, any, any>): AtomHandle<A>;
15
- lazy<A>(init: () => A): AtomHandle<A>;
26
+ <A>(init: A | Atom.Atom<A> | Effect.Effect<A, unknown, AtomRegistry> | Stream.Stream<A, unknown, AtomRegistry>): AtomHandle<A>;
27
+ lazy: <A>(init: () => A) => AtomHandle<A>;
16
28
  }
17
29
  declare const atom: AtomFn;
18
30
  declare const batch: (f: () => void) => void;
19
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
20
60
  //#region src/when.d.ts
21
- declare function when<A, E = never, R = never>(stream: Stream.Stream<A, E, R>, body: (value: A) => Generator<Yielded, View | void, unknown>): Instruction<void, E>;
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>;
22
62
  //#endregion
23
63
  //#region src/mount.d.ts
24
- type RenderToStringOptions = {
64
+ interface RenderToStringOptions {
25
65
  snapshot?: boolean;
26
66
  markers?: boolean;
27
67
  timeout?: number;
28
68
  captureActions?: boolean;
29
- };
30
- type MountOptions = {
69
+ }
70
+ interface MountOptions {
31
71
  hydrate?: boolean;
32
- onError?: (error: unknown) => void;
33
- };
34
- declare function mount(el: Element, fn: Component, opts?: MountOptions): () => void;
35
- declare function renderToString(fn: Component, opts?: RenderToStringOptions): Promise<string>;
72
+ onError?: <E>(error: E) => void;
73
+ }
74
+ declare const mount: (el: Element, fn: Component, opts?: MountOptions) => (() => void);
75
+ declare const renderToString: (fn: Component, opts?: RenderToStringOptions) => Promise<string>;
36
76
  //#endregion
37
- 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,14 +1,83 @@
1
- import { a as atom, c as instr, d as getFiber, f as makeFiber, i as interpret, l as isAtomHandle, n as renderToString, o as batch, r as paint, s as handleOwner, t as mount, u as closeFiber } from "./mount-CCMOkDSb.js";
2
- import { i as Fragment, t as h } from "./vnode-DHN2EFHi.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
11
- function runStreamWatch(fiber, effect) {
79
+ const WATCH_ONCE = Symbol("ilha.watch.once");
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));
14
83
  const fork = Effect.runFork(provided);
@@ -16,31 +85,32 @@ function runStreamWatch(fiber, effect) {
16
85
  return () => {
17
86
  Effect.runFork(Scope.close(scope, Exit.void));
18
87
  };
19
- }
20
- function useWatchSlot(fiber, key, mount, fn) {
88
+ };
89
+ const useWatchSlot = (fiber, key, mount, fn) => {
21
90
  const i = fiber.watchI ?? 0;
22
91
  fiber.watchI = i + 1;
23
92
  fiber.watchSlots ??= [];
24
93
  const existing = fiber.watchSlots[i];
25
94
  if (existing) {
26
- if (existing.key !== key) {
27
- existing.dispose();
28
- fiber.watchSlots[i] = void 0;
29
- } else {
30
- existing.fnRef.current = fn;
95
+ if (existing.key === key) {
96
+ if (existing.fnRef) existing.fnRef.current = fn;
31
97
  return;
32
98
  }
99
+ existing.dispose();
100
+ fiber.watchSlots[i] = void 0;
33
101
  }
34
102
  const fnRef = { current: fn };
35
- const unsub = mount((value) => fnRef.current(value));
103
+ const unsub = mount((value) => {
104
+ fnRef.current(value);
105
+ });
36
106
  const dispose = () => unsub();
37
107
  fiber.watchSlots[i] = {
38
- key,
108
+ dispose,
39
109
  fnRef,
40
- dispose
110
+ key
41
111
  };
42
- }
43
- function registerWatch(fiber, source, fn) {
112
+ };
113
+ const registerWatch = (fiber, source, fn) => {
44
114
  if (Stream.isStream(source)) {
45
115
  useWatchSlot(fiber, source, (run) => {
46
116
  let active = true;
@@ -56,32 +126,58 @@ function registerWatch(fiber, source, fn) {
56
126
  }
57
127
  const atom = isAtomHandle(source) ? source.atom : source;
58
128
  useWatchSlot(fiber, atom, (run) => fiber.registry.subscribe(atom, run, { immediate: true }), fn);
59
- }
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
+ };
60
153
  /** Run `fn` when `source` changes — and once on mount. Sync, async, and generator components. */
61
- function watch(source, fn) {
62
- registerWatch((isAtomHandle(source) ? handleOwner.get(source) : void 0) ?? getFiber(), source, fn);
154
+ const watchSource = (source, fn) => {
155
+ const fiber = (isAtomHandle(source) ? handleOwner.get(source) : void 0) ?? getFiber();
156
+ registerWatch(fiber, source, fn);
63
157
  return instr(Effect.void);
64
- }
158
+ };
159
+ const watch = Object.assign(watchSource, { once: watchOnce });
65
160
 
66
161
  //#endregion
67
162
  //#region src/when.ts
68
- function when(stream, body) {
69
- return instr(Effect.gen(function* () {
70
- const parent = getFiber();
71
- let child;
72
- parent.holes.push({ dispose() {
73
- if (child) closeFiber(child);
74
- child = void 0;
75
- } });
76
- const base = stream;
77
- const src = parent.runtime.ssr ? base.pipe(Stream.take(1)) : base;
78
- yield* Stream.runForEach(src, (value) => Effect.sync(() => {
79
- if (child) closeFiber(child);
80
- child = makeFiber(parent.runtime, parent.root, paint, { onFail: parent.fail });
81
- interpret((() => body(value)), child);
82
- }));
163
+ const when = (stream, body) => instr(Effect.gen(function* whenBody() {
164
+ const parent = getFiber();
165
+ let child;
166
+ parent.holes.push({ dispose() {
167
+ if (child) closeFiber(child);
168
+ child = void 0;
169
+ } });
170
+ const base = stream;
171
+ const src = parent.runtime.ssr ? base.pipe(Stream.take(1)) : base;
172
+ yield* Stream.runForEach(src, (value) => Effect.sync(() => {
173
+ if (child) closeFiber(child);
174
+ child = makeFiber(parent.runtime, parent.root, parent.paintFn, {
175
+ onFail: parent.fail,
176
+ parent
177
+ });
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 { r as Fragment } from "./types-Cie0Ihl6.js";
2
- import { n as JSX } from "./vnode-15tImQ6O.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-DHN2EFHi.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 { o as View, r as Fragment } from "./types-Cie0Ihl6.js";
2
- import { n as JSX, t as h } from "./vnode-15tImQ6O.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 | typeof Fragment | ((p: Record<string, unknown>) => unknown), props: Record<string, unknown> | null, key?: string | number) => ReturnType<typeof h>;
5
- declare const jsxs: (type: string | typeof Fragment | ((p: Record<string, unknown>) => unknown), props: Record<string, unknown> | null, key?: string | number) => ReturnType<typeof h>;
6
- declare const jsxDEV: (type: string | typeof Fragment | ((p: Record<string, unknown>) => unknown), props: Record<string, unknown> | 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-DHN2EFHi.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 : {