@getforma/core 0.3.1 → 0.8.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.
Files changed (52) hide show
  1. package/README.md +279 -20
  2. package/dist/chunk-3U57L2TY.cjs +38 -0
  3. package/dist/chunk-3U57L2TY.cjs.map +1 -0
  4. package/dist/{chunk-5H52PKGF.js → chunk-N522P3G6.js} +37 -19
  5. package/dist/chunk-N522P3G6.js.map +1 -0
  6. package/dist/chunk-OZCHIVAZ.js +35 -0
  7. package/dist/chunk-OZCHIVAZ.js.map +1 -0
  8. package/dist/{chunk-TSQ7AKFT.cjs → chunk-YMIMKO4W.cjs} +66 -25
  9. package/dist/chunk-YMIMKO4W.cjs.map +1 -0
  10. package/dist/forma-runtime-csp.js +1 -1
  11. package/dist/forma-runtime.js +1 -1
  12. package/dist/formajs-runtime-hardened.global.js +1 -1
  13. package/dist/formajs-runtime-hardened.global.js.map +1 -1
  14. package/dist/formajs-runtime.global.js +1 -1
  15. package/dist/formajs-runtime.global.js.map +1 -1
  16. package/dist/formajs.global.js +1 -1
  17. package/dist/formajs.global.js.map +1 -1
  18. package/dist/index.cjs +142 -118
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +70 -29
  21. package/dist/index.d.ts +70 -29
  22. package/dist/index.js +64 -60
  23. package/dist/index.js.map +1 -1
  24. package/dist/runtime-hardened.cjs +166 -39
  25. package/dist/runtime-hardened.cjs.map +1 -1
  26. package/dist/runtime-hardened.js +167 -40
  27. package/dist/runtime-hardened.js.map +1 -1
  28. package/dist/runtime.cjs +184 -57
  29. package/dist/runtime.cjs.map +1 -1
  30. package/dist/runtime.js +162 -35
  31. package/dist/runtime.js.map +1 -1
  32. package/dist/signal-B4_wQJHs.d.cts +53 -0
  33. package/dist/signal-B4_wQJHs.d.ts +53 -0
  34. package/dist/ssr/index.cjs +37 -12
  35. package/dist/ssr/index.cjs.map +1 -1
  36. package/dist/ssr/index.js +37 -12
  37. package/dist/ssr/index.js.map +1 -1
  38. package/dist/tc39-compat.cjs +4 -12
  39. package/dist/tc39-compat.cjs.map +1 -1
  40. package/dist/tc39-compat.d.cts +1 -1
  41. package/dist/tc39-compat.d.ts +1 -1
  42. package/dist/tc39-compat.js +3 -11
  43. package/dist/tc39-compat.js.map +1 -1
  44. package/package.json +2 -2
  45. package/dist/chunk-5H52PKGF.js.map +0 -1
  46. package/dist/chunk-FPSLC62A.cjs +0 -31
  47. package/dist/chunk-FPSLC62A.cjs.map +0 -1
  48. package/dist/chunk-KX5WRZH7.js +0 -27
  49. package/dist/chunk-KX5WRZH7.js.map +0 -1
  50. package/dist/chunk-TSQ7AKFT.cjs.map +0 -1
  51. package/dist/signal-CfLDwMyg.d.cts +0 -29
  52. package/dist/signal-CfLDwMyg.d.ts +0 -29
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  /// <reference path="./jsx.d.ts" />
2
- import { S as SignalGetter } from './signal-CfLDwMyg.cjs';
3
- export { c as createSignal } from './signal-CfLDwMyg.cjs';
2
+ import { S as SignalGetter } from './signal-B4_wQJHs.cjs';
3
+ export { c as createSignal } from './signal-B4_wQJHs.cjs';
4
+ export { getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, trigger } from 'alien-signals';
4
5
 
5
6
  declare function createEffect(fn: () => void | (() => void)): () => void;
6
7
 
@@ -16,6 +17,14 @@ declare function createEffect(fn: () => void | (() => void)): () => void;
16
17
  /**
17
18
  * Create a lazy, cached computed value.
18
19
  *
20
+ * Note: Unlike SolidJS's createComputed (which is an eager synchronous
21
+ * side effect), this is a lazy cached derivation — equivalent to
22
+ * SolidJS's createMemo. Both createComputed and createMemo in FormaJS
23
+ * are identical.
24
+ *
25
+ * The getter receives the previous value as an argument, enabling
26
+ * efficient diffing patterns without a separate signal:
27
+ *
19
28
  * ```ts
20
29
  * const [count, setCount] = createSignal(0);
21
30
  * const doubled = createComputed(() => count() * 2);
@@ -23,8 +32,18 @@ declare function createEffect(fn: () => void | (() => void)): () => void;
23
32
  * setCount(5);
24
33
  * console.log(doubled()); // 10
25
34
  * ```
35
+ *
36
+ * With previous value (for diffing):
37
+ *
38
+ * ```ts
39
+ * const changes = createComputed((prev) => {
40
+ * const next = items();
41
+ * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);
42
+ * return next;
43
+ * });
44
+ * ```
26
45
  */
27
- declare function createComputed<T>(fn: () => T): () => T;
46
+ declare function createComputed<T>(fn: (previousValue?: T) => T): () => T;
28
47
 
29
48
  /**
30
49
  * Forma Reactive - Memo
@@ -39,6 +58,11 @@ declare function createComputed<T>(fn: () => T): () => T;
39
58
  * Create a memoized computed value.
40
59
  * Identical to `createComputed` — provided for React/SolidJS familiarity.
41
60
  *
61
+ * Note: Unlike SolidJS's createComputed (which is an eager synchronous
62
+ * side effect), both createComputed and createMemo in FormaJS are lazy
63
+ * cached derivations — equivalent to SolidJS's createMemo. They are
64
+ * identical.
65
+ *
42
66
  * The computation runs lazily and caches the result. It only recomputes
43
67
  * when a signal it reads during computation changes.
44
68
  *
@@ -106,13 +130,17 @@ declare function untrack<T>(fn: () => T): T;
106
130
  * Explicit reactive ownership scope. All effects created inside a root
107
131
  * are automatically disposed when the root is torn down.
108
132
  *
109
- * Replaces the module-level disposalBag pattern in mount.ts.
133
+ * Uses alien-signals' `effectScope` under the hood for native graph-level
134
+ * effect tracking, with a userland disposer list for non-effect cleanup
135
+ * (e.g., event listeners, DOM references, timers).
110
136
  */
111
137
  /**
112
138
  * Create a reactive root scope.
113
139
  *
114
- * All effects created (via `createEffect`) inside the callback are tracked.
115
- * The returned `dispose` function tears them all down.
140
+ * All effects created (via `createEffect`) inside the callback are tracked
141
+ * at both the reactive graph level (via alien-signals effectScope) and the
142
+ * userland level (via registerDisposer). The returned `dispose` function
143
+ * tears down everything.
116
144
  *
117
145
  * ```ts
118
146
  * const dispose = createRoot(() => {
@@ -380,6 +408,7 @@ declare function cleanup(el: Element): void;
380
408
  * )
381
409
  * ```
382
410
  */
411
+ declare function h(tag: (props: Record<string, unknown>) => unknown, props?: Record<string, unknown> | null, ...children: unknown[]): Node;
383
412
  declare function h(tag: typeof Fragment, props?: null, ...children: unknown[]): DocumentFragment;
384
413
  declare function h(tag: string, props?: Record<string, unknown> | null, ...children: unknown[]): HTMLElement;
385
414
  /**
@@ -473,12 +502,6 @@ interface CreateListOptions {
473
502
  */
474
503
  updateOnItemChange?: 'none' | 'rerender';
475
504
  }
476
- /**
477
- * Find the longest increasing subsequence.
478
- * Returns indices into the input array.
479
- * O(n log n) time, O(n) space.
480
- */
481
- declare function longestIncreasingSubsequence(arr: number[]): number[];
482
505
  /**
483
506
  * Reconcile a DOM parent's children to match a new array of items.
484
507
  * Uses keyed reconciliation with LIS for minimum DOM operations.
@@ -712,8 +735,20 @@ declare function hydrateIsland(component: () => unknown, target: Element): Eleme
712
735
  * loads props (inline or script_tag), and hydrates each island inside
713
736
  * an independent createRoot scope with try/catch error isolation.
714
737
  */
715
- /** Function that creates a component's DOM tree (same for CSR and hydration). */
716
- type IslandHydrateFn = (props: Record<string, unknown> | null) => unknown;
738
+ /**
739
+ * Function that hydrates an island.
740
+ *
741
+ * @param el The root element of the island (`[data-forma-island]`).
742
+ * Useful for layout measurement, focus management, CSS class
743
+ * toggling, third-party library init, or reading extra `data-*`
744
+ * attributes from the server-rendered shell.
745
+ * @param props Parsed props from `data-forma-props` (inline or script block),
746
+ * or `null` if no props were provided.
747
+ * @returns A component tree (from `h()` calls) for descriptor-based
748
+ * hydration, or `undefined` for imperative islands that set up
749
+ * their own effects.
750
+ */
751
+ type IslandHydrateFn = (el: HTMLElement, props: Record<string, unknown> | null) => unknown;
717
752
  /**
718
753
  * Discover and activate all SSR-rendered islands on the page.
719
754
  *
@@ -723,6 +758,21 @@ type IslandHydrateFn = (props: Record<string, unknown> | null) => unknown;
723
758
  * @param registry Map of component names to hydration functions.
724
759
  */
725
760
  declare function activateIslands(registry: Record<string, IslandHydrateFn>): void;
761
+ /**
762
+ * Dispose a single island, tearing down its reactive root and all effects.
763
+ *
764
+ * Safe to call multiple times (idempotent). Sets `data-forma-status` to
765
+ * `"disposed"` so the island can be distinguished from active/error states.
766
+ */
767
+ declare function deactivateIsland(el: HTMLElement): void;
768
+ /**
769
+ * Dispose ALL active islands under a root element (or the whole document).
770
+ *
771
+ * Use this when swapping module content — e.g., replacing the contents of
772
+ * a `<forma-stage>` Shadow DOM during AI generation. Prevents leaked effects
773
+ * and event listeners from accumulating across swaps.
774
+ */
775
+ declare function deactivateAllIslands(root?: Element | Document): void;
726
776
 
727
777
  /**
728
778
  * Forma Component - Define
@@ -866,6 +916,11 @@ type StoreSetter<T extends object> = (partial: Partial<T> | ((prev: T) => Partia
866
916
  * // Array mutations
867
917
  * state.items.push({ text: 'Walk dog', done: false });
868
918
  * ```
919
+ *
920
+ * **Limitation:** `Object.keys(state)`, `for...in`, and spread (`{...state}`)
921
+ * are NOT reactive. Adding or removing a property will not trigger effects
922
+ * that iterated over keys. Use signals or explicit arrays for collections
923
+ * that need to react to membership changes.
869
924
  */
870
925
  declare function createStore<T extends object>(initial: T): [get: T, set: StoreSetter<T>];
871
926
 
@@ -1393,18 +1448,4 @@ declare function createRPCMiddleware(): (req: {
1393
1448
  };
1394
1449
  }) => Promise<void>;
1395
1450
 
1396
- declare global {
1397
- interface Window {
1398
- __FORMA_WASM__?: {
1399
- loader: string;
1400
- binary: string;
1401
- ir: string;
1402
- };
1403
- }
1404
- }
1405
- /** Full page render via WASM. */
1406
- declare function renderLocal(slotsJson: string): Promise<string>;
1407
- /** Fragment render (single island) via WASM. */
1408
- declare function renderIsland(slotsJson: string, islandId: number): Promise<string>;
1409
-
1410
- export { $, $$, $$serverFunction, type Action, type ActionOptions, Fragment, type IslandHydrateFn, type MutationResponse, type RPCRequest, type RPCResponse, activateIslands, addClass, applyRevalidation, batch, children, cleanup, closest, createAction, createBus, createComputed, createContext, createEffect, createErrorBoundary, createFetch, createHistory, createIndexedDB, createList, createLocalStorage, createMemo, createPortal, createRPCMiddleware, createReducer, createRef, createResource, createRoot, createSSE, createSessionStorage, createShow, createStore, createSuspense, createSwitch, createText, createWebSocket, defineComponent, delegate, disposeComponent, enableAutoRevalidation, fetchJSON, fragment, getRegisteredEndpoints, getServerFunction, h, handleRPC, hydrateIsland, inject, longestIncreasingSubsequence, mount, nextSibling, on, onCleanup, onError, onIntersect, onKey, onMount, onMutation, onResize, onUnmount, parent, persist, prevSibling, provide, reconcileList, registerResource, registerServerFunction, removeClass, renderIsland, renderLocal, setAttr, setHTML, setHTMLUnsafe, setStyle, setText, siblings, toggleClass, trackDisposer, unprovide, unregisterResource, untrack, withRevalidation };
1451
+ export { $, $$, $$serverFunction, type Action, type ActionOptions, Fragment, type IslandHydrateFn, type MutationResponse, type RPCRequest, type RPCResponse, activateIslands, addClass, applyRevalidation, batch, children, cleanup, closest, createAction, createBus, createComputed, createContext, createEffect, createErrorBoundary, createFetch, createHistory, createIndexedDB, createList, createLocalStorage, createMemo, createPortal, createRPCMiddleware, createReducer, createRef, createResource, createRoot, createSSE, createSessionStorage, createShow, createStore, createSuspense, createSwitch, createText, createWebSocket, deactivateAllIslands, deactivateIsland, defineComponent, delegate, disposeComponent, enableAutoRevalidation, fetchJSON, fragment, getRegisteredEndpoints, getServerFunction, h, handleRPC, hydrateIsland, inject, mount, nextSibling, on, onCleanup, onError, onIntersect, onKey, onMount, onMutation, onResize, onUnmount, parent, persist, prevSibling, provide, reconcileList, registerResource, registerServerFunction, removeClass, setAttr, setHTML, setHTMLUnsafe, setStyle, setText, siblings, toggleClass, trackDisposer, unprovide, unregisterResource, untrack, withRevalidation };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /// <reference path="./jsx.d.ts" />
2
- import { S as SignalGetter } from './signal-CfLDwMyg.js';
3
- export { c as createSignal } from './signal-CfLDwMyg.js';
2
+ import { S as SignalGetter } from './signal-B4_wQJHs.js';
3
+ export { c as createSignal } from './signal-B4_wQJHs.js';
4
+ export { getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, trigger } from 'alien-signals';
4
5
 
5
6
  declare function createEffect(fn: () => void | (() => void)): () => void;
6
7
 
@@ -16,6 +17,14 @@ declare function createEffect(fn: () => void | (() => void)): () => void;
16
17
  /**
17
18
  * Create a lazy, cached computed value.
18
19
  *
20
+ * Note: Unlike SolidJS's createComputed (which is an eager synchronous
21
+ * side effect), this is a lazy cached derivation — equivalent to
22
+ * SolidJS's createMemo. Both createComputed and createMemo in FormaJS
23
+ * are identical.
24
+ *
25
+ * The getter receives the previous value as an argument, enabling
26
+ * efficient diffing patterns without a separate signal:
27
+ *
19
28
  * ```ts
20
29
  * const [count, setCount] = createSignal(0);
21
30
  * const doubled = createComputed(() => count() * 2);
@@ -23,8 +32,18 @@ declare function createEffect(fn: () => void | (() => void)): () => void;
23
32
  * setCount(5);
24
33
  * console.log(doubled()); // 10
25
34
  * ```
35
+ *
36
+ * With previous value (for diffing):
37
+ *
38
+ * ```ts
39
+ * const changes = createComputed((prev) => {
40
+ * const next = items();
41
+ * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);
42
+ * return next;
43
+ * });
44
+ * ```
26
45
  */
27
- declare function createComputed<T>(fn: () => T): () => T;
46
+ declare function createComputed<T>(fn: (previousValue?: T) => T): () => T;
28
47
 
29
48
  /**
30
49
  * Forma Reactive - Memo
@@ -39,6 +58,11 @@ declare function createComputed<T>(fn: () => T): () => T;
39
58
  * Create a memoized computed value.
40
59
  * Identical to `createComputed` — provided for React/SolidJS familiarity.
41
60
  *
61
+ * Note: Unlike SolidJS's createComputed (which is an eager synchronous
62
+ * side effect), both createComputed and createMemo in FormaJS are lazy
63
+ * cached derivations — equivalent to SolidJS's createMemo. They are
64
+ * identical.
65
+ *
42
66
  * The computation runs lazily and caches the result. It only recomputes
43
67
  * when a signal it reads during computation changes.
44
68
  *
@@ -106,13 +130,17 @@ declare function untrack<T>(fn: () => T): T;
106
130
  * Explicit reactive ownership scope. All effects created inside a root
107
131
  * are automatically disposed when the root is torn down.
108
132
  *
109
- * Replaces the module-level disposalBag pattern in mount.ts.
133
+ * Uses alien-signals' `effectScope` under the hood for native graph-level
134
+ * effect tracking, with a userland disposer list for non-effect cleanup
135
+ * (e.g., event listeners, DOM references, timers).
110
136
  */
111
137
  /**
112
138
  * Create a reactive root scope.
113
139
  *
114
- * All effects created (via `createEffect`) inside the callback are tracked.
115
- * The returned `dispose` function tears them all down.
140
+ * All effects created (via `createEffect`) inside the callback are tracked
141
+ * at both the reactive graph level (via alien-signals effectScope) and the
142
+ * userland level (via registerDisposer). The returned `dispose` function
143
+ * tears down everything.
116
144
  *
117
145
  * ```ts
118
146
  * const dispose = createRoot(() => {
@@ -380,6 +408,7 @@ declare function cleanup(el: Element): void;
380
408
  * )
381
409
  * ```
382
410
  */
411
+ declare function h(tag: (props: Record<string, unknown>) => unknown, props?: Record<string, unknown> | null, ...children: unknown[]): Node;
383
412
  declare function h(tag: typeof Fragment, props?: null, ...children: unknown[]): DocumentFragment;
384
413
  declare function h(tag: string, props?: Record<string, unknown> | null, ...children: unknown[]): HTMLElement;
385
414
  /**
@@ -473,12 +502,6 @@ interface CreateListOptions {
473
502
  */
474
503
  updateOnItemChange?: 'none' | 'rerender';
475
504
  }
476
- /**
477
- * Find the longest increasing subsequence.
478
- * Returns indices into the input array.
479
- * O(n log n) time, O(n) space.
480
- */
481
- declare function longestIncreasingSubsequence(arr: number[]): number[];
482
505
  /**
483
506
  * Reconcile a DOM parent's children to match a new array of items.
484
507
  * Uses keyed reconciliation with LIS for minimum DOM operations.
@@ -712,8 +735,20 @@ declare function hydrateIsland(component: () => unknown, target: Element): Eleme
712
735
  * loads props (inline or script_tag), and hydrates each island inside
713
736
  * an independent createRoot scope with try/catch error isolation.
714
737
  */
715
- /** Function that creates a component's DOM tree (same for CSR and hydration). */
716
- type IslandHydrateFn = (props: Record<string, unknown> | null) => unknown;
738
+ /**
739
+ * Function that hydrates an island.
740
+ *
741
+ * @param el The root element of the island (`[data-forma-island]`).
742
+ * Useful for layout measurement, focus management, CSS class
743
+ * toggling, third-party library init, or reading extra `data-*`
744
+ * attributes from the server-rendered shell.
745
+ * @param props Parsed props from `data-forma-props` (inline or script block),
746
+ * or `null` if no props were provided.
747
+ * @returns A component tree (from `h()` calls) for descriptor-based
748
+ * hydration, or `undefined` for imperative islands that set up
749
+ * their own effects.
750
+ */
751
+ type IslandHydrateFn = (el: HTMLElement, props: Record<string, unknown> | null) => unknown;
717
752
  /**
718
753
  * Discover and activate all SSR-rendered islands on the page.
719
754
  *
@@ -723,6 +758,21 @@ type IslandHydrateFn = (props: Record<string, unknown> | null) => unknown;
723
758
  * @param registry Map of component names to hydration functions.
724
759
  */
725
760
  declare function activateIslands(registry: Record<string, IslandHydrateFn>): void;
761
+ /**
762
+ * Dispose a single island, tearing down its reactive root and all effects.
763
+ *
764
+ * Safe to call multiple times (idempotent). Sets `data-forma-status` to
765
+ * `"disposed"` so the island can be distinguished from active/error states.
766
+ */
767
+ declare function deactivateIsland(el: HTMLElement): void;
768
+ /**
769
+ * Dispose ALL active islands under a root element (or the whole document).
770
+ *
771
+ * Use this when swapping module content — e.g., replacing the contents of
772
+ * a `<forma-stage>` Shadow DOM during AI generation. Prevents leaked effects
773
+ * and event listeners from accumulating across swaps.
774
+ */
775
+ declare function deactivateAllIslands(root?: Element | Document): void;
726
776
 
727
777
  /**
728
778
  * Forma Component - Define
@@ -866,6 +916,11 @@ type StoreSetter<T extends object> = (partial: Partial<T> | ((prev: T) => Partia
866
916
  * // Array mutations
867
917
  * state.items.push({ text: 'Walk dog', done: false });
868
918
  * ```
919
+ *
920
+ * **Limitation:** `Object.keys(state)`, `for...in`, and spread (`{...state}`)
921
+ * are NOT reactive. Adding or removing a property will not trigger effects
922
+ * that iterated over keys. Use signals or explicit arrays for collections
923
+ * that need to react to membership changes.
869
924
  */
870
925
  declare function createStore<T extends object>(initial: T): [get: T, set: StoreSetter<T>];
871
926
 
@@ -1393,18 +1448,4 @@ declare function createRPCMiddleware(): (req: {
1393
1448
  };
1394
1449
  }) => Promise<void>;
1395
1450
 
1396
- declare global {
1397
- interface Window {
1398
- __FORMA_WASM__?: {
1399
- loader: string;
1400
- binary: string;
1401
- ir: string;
1402
- };
1403
- }
1404
- }
1405
- /** Full page render via WASM. */
1406
- declare function renderLocal(slotsJson: string): Promise<string>;
1407
- /** Fragment render (single island) via WASM. */
1408
- declare function renderIsland(slotsJson: string, islandId: number): Promise<string>;
1409
-
1410
- export { $, $$, $$serverFunction, type Action, type ActionOptions, Fragment, type IslandHydrateFn, type MutationResponse, type RPCRequest, type RPCResponse, activateIslands, addClass, applyRevalidation, batch, children, cleanup, closest, createAction, createBus, createComputed, createContext, createEffect, createErrorBoundary, createFetch, createHistory, createIndexedDB, createList, createLocalStorage, createMemo, createPortal, createRPCMiddleware, createReducer, createRef, createResource, createRoot, createSSE, createSessionStorage, createShow, createStore, createSuspense, createSwitch, createText, createWebSocket, defineComponent, delegate, disposeComponent, enableAutoRevalidation, fetchJSON, fragment, getRegisteredEndpoints, getServerFunction, h, handleRPC, hydrateIsland, inject, longestIncreasingSubsequence, mount, nextSibling, on, onCleanup, onError, onIntersect, onKey, onMount, onMutation, onResize, onUnmount, parent, persist, prevSibling, provide, reconcileList, registerResource, registerServerFunction, removeClass, renderIsland, renderLocal, setAttr, setHTML, setHTMLUnsafe, setStyle, setText, siblings, toggleClass, trackDisposer, unprovide, unregisterResource, untrack, withRevalidation };
1451
+ export { $, $$, $$serverFunction, type Action, type ActionOptions, Fragment, type IslandHydrateFn, type MutationResponse, type RPCRequest, type RPCResponse, activateIslands, addClass, applyRevalidation, batch, children, cleanup, closest, createAction, createBus, createComputed, createContext, createEffect, createErrorBoundary, createFetch, createHistory, createIndexedDB, createList, createLocalStorage, createMemo, createPortal, createRPCMiddleware, createReducer, createRef, createResource, createRoot, createSSE, createSessionStorage, createShow, createStore, createSuspense, createSwitch, createText, createWebSocket, deactivateAllIslands, deactivateIsland, defineComponent, delegate, disposeComponent, enableAutoRevalidation, fetchJSON, fragment, getRegisteredEndpoints, getServerFunction, h, handleRPC, hydrateIsland, inject, mount, nextSibling, on, onCleanup, onError, onIntersect, onKey, onMount, onMutation, onResize, onUnmount, parent, persist, prevSibling, provide, reconcileList, registerResource, registerServerFunction, removeClass, setAttr, setHTML, setHTMLUnsafe, setStyle, setText, siblings, toggleClass, trackDisposer, unprovide, unregisterResource, untrack, withRevalidation };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { internalEffect, createRoot, hydrateIsland, untrack, createEffect, pushSuspenseContext, popSuspenseContext, __DEV__, batch } from './chunk-5H52PKGF.js';
2
- export { Fragment, batch, cleanup, createEffect, createList, createMemo, createReducer, createRef, createResource, createRoot, createShow, fragment, h, hydrateIsland, longestIncreasingSubsequence, on, onCleanup, onError, reconcileList, untrack } from './chunk-5H52PKGF.js';
3
- import { createSignal, createValueSignal } from './chunk-KX5WRZH7.js';
4
- export { createComputed, createSignal } from './chunk-KX5WRZH7.js';
1
+ import { internalEffect, createRoot, hydrateIsland, untrack, createEffect, pushSuspenseContext, popSuspenseContext, __DEV__, reportError, batch } from './chunk-N522P3G6.js';
2
+ export { Fragment, batch, cleanup, createEffect, createList, createMemo, createReducer, createRef, createResource, createRoot, createShow, fragment, getBatchDepth, h, hydrateIsland, isComputed, isEffect, isEffectScope, isSignal, on, onCleanup, onError, reconcileList, trigger, untrack } from './chunk-N522P3G6.js';
3
+ import { createSignal } from './chunk-OZCHIVAZ.js';
4
+ export { createComputed, createSignal } from './chunk-OZCHIVAZ.js';
5
5
 
6
6
  // src/dom/text.ts
7
7
  function createText(value) {
@@ -219,13 +219,20 @@ function createSuspense(fallback, children2) {
219
219
  }
220
220
 
221
221
  // src/dom/activate.ts
222
+ var FORBIDDEN_PROP_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
223
+ function sanitizeProps(obj) {
224
+ for (const key of FORBIDDEN_PROP_KEYS) {
225
+ if (key in obj) delete obj[key];
226
+ }
227
+ return obj;
228
+ }
222
229
  function loadIslandProps(root, id, sharedProps) {
223
230
  const inline = root.getAttribute("data-forma-props");
224
231
  if (inline) {
225
- return JSON.parse(inline);
232
+ return sanitizeProps(JSON.parse(inline));
226
233
  }
227
234
  if (sharedProps && String(id) in sharedProps) {
228
- return sharedProps[String(id)];
235
+ return sanitizeProps(sharedProps[String(id)]);
229
236
  }
230
237
  return null;
231
238
  }
@@ -242,11 +249,8 @@ function activateIslands(registry2) {
242
249
  root.setAttribute("data-forma-status", "error");
243
250
  continue;
244
251
  }
245
- const trigger = root.getAttribute("data-forma-hydrate") || "load";
246
- if (trigger === "interaction" || trigger === "idle") {
247
- if (__DEV__) console.warn(`[forma] Trigger "${trigger}" not yet implemented for island "${componentName}" (id=${id}), falling back to load`);
248
- }
249
- if (trigger === "visible") {
252
+ const trigger2 = root.getAttribute("data-forma-hydrate") || "load";
253
+ if (trigger2 === "visible") {
250
254
  const observer = new IntersectionObserver(
251
255
  (entries) => {
252
256
  for (const entry of entries) {
@@ -258,18 +262,47 @@ function activateIslands(registry2) {
258
262
  { rootMargin: "200px" }
259
263
  );
260
264
  observer.observe(root);
265
+ } else if (trigger2 === "idle") {
266
+ const hydrate = () => hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps);
267
+ if (typeof requestIdleCallback === "function") {
268
+ requestIdleCallback(hydrate);
269
+ } else {
270
+ setTimeout(hydrate, 200);
271
+ }
272
+ } else if (trigger2 === "interaction") {
273
+ const hydrate = () => {
274
+ root.removeEventListener("pointerdown", hydrate, true);
275
+ root.removeEventListener("focusin", hydrate, true);
276
+ hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps);
277
+ };
278
+ root.addEventListener("pointerdown", hydrate, { capture: true, once: true });
279
+ root.addEventListener("focusin", hydrate, { capture: true, once: true });
261
280
  } else {
262
281
  hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps);
263
282
  }
264
283
  }
265
284
  }
285
+ function deactivateIsland(el) {
286
+ const dispose = el.__formaDispose;
287
+ if (typeof dispose === "function") {
288
+ dispose();
289
+ delete el.__formaDispose;
290
+ el.setAttribute("data-forma-status", "disposed");
291
+ }
292
+ }
293
+ function deactivateAllIslands(root = document) {
294
+ const islands = root.querySelectorAll('[data-forma-status="active"]');
295
+ for (const island of islands) {
296
+ deactivateIsland(island);
297
+ }
298
+ }
266
299
  function hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps) {
267
300
  try {
268
301
  const props = loadIslandProps(root, id, sharedProps);
269
302
  root.setAttribute("data-forma-status", "hydrating");
270
303
  let activeRoot = root;
271
304
  createRoot((dispose) => {
272
- activeRoot = hydrateIsland(() => hydrateFn(props), root);
305
+ activeRoot = hydrateIsland(() => hydrateFn(root, props), root);
273
306
  activeRoot.__formaDispose = dispose;
274
307
  });
275
308
  activeRoot.setAttribute("data-forma-status", "active");
@@ -322,13 +355,15 @@ function defineComponent(setupOrDef) {
322
355
  for (const cb of ctx.unmountCallbacks) {
323
356
  try {
324
357
  cb();
325
- } catch {
358
+ } catch (e) {
359
+ reportError(e, "onUnmount");
326
360
  }
327
361
  }
328
362
  for (const d of ctx.disposers) {
329
363
  try {
330
364
  d();
331
- } catch {
365
+ } catch (e) {
366
+ reportError(e, "component disposer");
332
367
  }
333
368
  }
334
369
  ctx.disposers.length = 0;
@@ -342,7 +377,8 @@ function defineComponent(setupOrDef) {
342
377
  if (typeof cleanup2 === "function") {
343
378
  ctx.unmountCallbacks.push(cleanup2);
344
379
  }
345
- } catch {
380
+ } catch (e) {
381
+ reportError(e, "onMount");
346
382
  }
347
383
  }
348
384
  return dom;
@@ -416,13 +452,15 @@ function shouldWrap(v) {
416
452
  if (v[PROXY]) return false;
417
453
  return true;
418
454
  }
419
- function deepClone(obj) {
420
- if (obj == null || typeof obj !== "object") return obj;
421
- if (Array.isArray(obj)) return obj.map(deepClone);
422
- if (obj instanceof Date) return new Date(obj.getTime());
455
+ function deepClone(obj, seen) {
456
+ if (obj === null || typeof obj !== "object") return obj;
457
+ if (!seen) seen = /* @__PURE__ */ new WeakSet();
458
+ if (seen.has(obj)) return obj;
459
+ seen.add(obj);
460
+ if (Array.isArray(obj)) return obj.map((item) => deepClone(item, seen));
423
461
  const out = {};
424
462
  for (const key of Object.keys(obj)) {
425
- out[key] = deepClone(obj[key]);
463
+ out[key] = deepClone(obj[key], seen);
426
464
  }
427
465
  return out;
428
466
  }
@@ -616,9 +654,9 @@ function createHistory(source, options) {
616
654
  const maxLength = options?.maxLength ?? 100;
617
655
  let _stack = [sourceGet()];
618
656
  let _cursor = 0;
619
- const [stackSignal, setStackSignal] = createValueSignal([..._stack]);
620
- const [cursorSignal, setCursorSignal] = createValueSignal(_cursor);
621
- const [stackLenSignal, setStackLenSignal] = createValueSignal(_stack.length);
657
+ const [stackSignal, setStackSignal] = createSignal([..._stack]);
658
+ const [cursorSignal, setCursorSignal] = createSignal(_cursor);
659
+ const [stackLenSignal, setStackLenSignal] = createSignal(_stack.length);
622
660
  function syncSignals() {
623
661
  batch(() => {
624
662
  setStackSignal([..._stack]);
@@ -1315,8 +1353,8 @@ function createWebSocket(url, options) {
1315
1353
 
1316
1354
  // src/server/action.ts
1317
1355
  function createAction(serverFn, options) {
1318
- const [pending, setPending] = createValueSignal(false);
1319
- const [error, setError] = createValueSignal(void 0);
1356
+ const [pending, setPending] = createSignal(false);
1357
+ const [error, setError] = createSignal(void 0);
1320
1358
  const action = async (...args) => {
1321
1359
  setPending(true);
1322
1360
  setError(void 0);
@@ -1473,40 +1511,6 @@ function createRPCMiddleware() {
1473
1511
  };
1474
1512
  }
1475
1513
 
1476
- // src/wasm/forma-wasm.ts
1477
- var wasmModule = null;
1478
- var irCache = /* @__PURE__ */ new Map();
1479
- async function ensureWasm() {
1480
- if (wasmModule) return wasmModule;
1481
- const config = window.__FORMA_WASM__;
1482
- if (!config) throw new Error("No __FORMA_WASM__ config");
1483
- const mod = await import(
1484
- /* @vite-ignore */
1485
- config.loader
1486
- );
1487
- await mod.default(config.binary);
1488
- wasmModule = mod;
1489
- return wasmModule;
1490
- }
1491
- async function getIR() {
1492
- const config = window.__FORMA_WASM__;
1493
- if (!config) throw new Error("No __FORMA_WASM__ config");
1494
- const cached = irCache.get(config.ir);
1495
- if (cached) return cached;
1496
- const response = await fetch(config.ir);
1497
- const bytes = new Uint8Array(await response.arrayBuffer());
1498
- irCache.set(config.ir, bytes);
1499
- return bytes;
1500
- }
1501
- async function renderLocal(slotsJson) {
1502
- const [wasm, ir] = await Promise.all([ensureWasm(), getIR()]);
1503
- return wasm.render(ir, slotsJson);
1504
- }
1505
- async function renderIsland(slotsJson, islandId) {
1506
- const [wasm, ir] = await Promise.all([ensureWasm(), getIR()]);
1507
- return wasm.render_island(ir, slotsJson, islandId);
1508
- }
1509
-
1510
- export { $, $$, $$serverFunction, activateIslands, addClass, applyRevalidation, children, closest, createAction, createBus, createContext, createErrorBoundary, createFetch, createHistory, createIndexedDB, createLocalStorage, createPortal, createRPCMiddleware, createSSE, createSessionStorage, createStore, createSuspense, createSwitch, createText, createWebSocket, defineComponent, delegate, disposeComponent, enableAutoRevalidation, fetchJSON, getRegisteredEndpoints, getServerFunction, handleRPC, inject, mount, nextSibling, onIntersect, onKey, onMount, onMutation, onResize, onUnmount, parent, persist, prevSibling, provide, registerResource, registerServerFunction, removeClass, renderIsland, renderLocal, setAttr, setHTML, setHTMLUnsafe, setStyle, setText, siblings, toggleClass, trackDisposer, unprovide, unregisterResource, withRevalidation };
1514
+ export { $, $$, $$serverFunction, activateIslands, addClass, applyRevalidation, children, closest, createAction, createBus, createContext, createErrorBoundary, createFetch, createHistory, createIndexedDB, createLocalStorage, createPortal, createRPCMiddleware, createSSE, createSessionStorage, createStore, createSuspense, createSwitch, createText, createWebSocket, deactivateAllIslands, deactivateIsland, defineComponent, delegate, disposeComponent, enableAutoRevalidation, fetchJSON, getRegisteredEndpoints, getServerFunction, handleRPC, inject, mount, nextSibling, onIntersect, onKey, onMount, onMutation, onResize, onUnmount, parent, persist, prevSibling, provide, registerResource, registerServerFunction, removeClass, setAttr, setHTML, setHTMLUnsafe, setStyle, setText, siblings, toggleClass, trackDisposer, unprovide, unregisterResource, withRevalidation };
1511
1515
  //# sourceMappingURL=index.js.map
1512
1516
  //# sourceMappingURL=index.js.map