@pyreon/react-compat 0.1.0

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/lib/index.js ADDED
@@ -0,0 +1,131 @@
1
+ import { ErrorBoundary, Fragment, Portal, Suspense, createContext, createRef, h, h as createElement, lazy, onErrorCaptured, onMount, onMount as useLayoutEffect, onUnmount, onUpdate, useContext } from "@pyreon/core";
2
+ import { batch, computed, createSelector, effect, getCurrentScope, runUntracked, signal } from "@pyreon/reactivity";
3
+
4
+ //#region src/index.ts
5
+ /**
6
+ * Drop-in for React's `useState`.
7
+ * Returns `[getter, setter]` — call `getter()` to read, `setter(v)` to write.
8
+ *
9
+ * Unlike React: the getter is a signal, so any component or effect that reads
10
+ * it will re-run automatically. No dep arrays needed.
11
+ */
12
+ function useState(initial) {
13
+ const s = signal(typeof initial === "function" ? initial() : initial);
14
+ const setter = (v) => {
15
+ if (typeof v === "function") s.update(v);
16
+ else s.set(v);
17
+ };
18
+ return [s, setter];
19
+ }
20
+ /**
21
+ * Drop-in for React's `useReducer`.
22
+ */
23
+ function useReducer(reducer, initial) {
24
+ const s = signal(typeof initial === "function" ? initial() : initial);
25
+ const dispatch = (action) => s.update((prev) => reducer(prev, action));
26
+ return [s, dispatch];
27
+ }
28
+ /**
29
+ * Drop-in for React's `useEffect`.
30
+ *
31
+ * The `deps` array is IGNORED — Pyreon tracks reactive dependencies automatically.
32
+ * If `deps` is `[]` (mount-only), wrap the body in `runUntracked(() => ...)`.
33
+ *
34
+ * Returns a cleanup the same way React does (return a function from `fn`).
35
+ */
36
+ function useEffect(fn, deps) {
37
+ if (deps !== void 0 && deps.length === 0) onMount(() => {
38
+ const cleanup = runUntracked(fn);
39
+ if (typeof cleanup === "function") onUnmount(cleanup);
40
+ });
41
+ else {
42
+ const e = effect(fn);
43
+ onUnmount(() => {
44
+ e.dispose();
45
+ });
46
+ }
47
+ }
48
+ /**
49
+ * Drop-in for React's `useMemo`.
50
+ * The `deps` array is IGNORED — Pyreon's `computed` tracks dependencies automatically.
51
+ * Returns a getter: call `value()` to read the memoized result.
52
+ */
53
+ function useMemo(fn, _deps) {
54
+ return computed(fn);
55
+ }
56
+ /**
57
+ * Drop-in for React's `useCallback`.
58
+ * In Pyreon, components run once so callbacks are never recreated — returns `fn` as-is.
59
+ */
60
+ function useCallback(fn, _deps) {
61
+ return fn;
62
+ }
63
+ /**
64
+ * Drop-in for React's `useRef`.
65
+ * Returns `{ current: T }` — same shape as React's ref object.
66
+ */
67
+ function useRef(initial) {
68
+ const ref = createRef();
69
+ if (initial !== void 0) ref.current = initial;
70
+ return ref;
71
+ }
72
+ /**
73
+ * Drop-in for React's `useId` — returns a stable unique string per component instance.
74
+ *
75
+ * Uses the component's effectScope as the key so the counter starts at 0 for every
76
+ * component on both server and client — IDs are deterministic and hydration-safe.
77
+ */
78
+ const _idCounters = /* @__PURE__ */ new WeakMap();
79
+ function useId() {
80
+ const scope = getCurrentScope();
81
+ if (!scope) return `:r${Math.random().toString(36).slice(2, 9)}:`;
82
+ const count = _idCounters.get(scope) ?? 0;
83
+ _idCounters.set(scope, count + 1);
84
+ return `:r${count.toString(36)}:`;
85
+ }
86
+ /**
87
+ * Drop-in for React's `memo` — wraps a component.
88
+ * In Pyreon, components run once (no re-renders), so memoization is a no-op.
89
+ * Kept for API compatibility when migrating React code.
90
+ */
91
+ function memo(component) {
92
+ return component;
93
+ }
94
+ /**
95
+ * Drop-in for React's `useTransition` — no-op in Pyreon (no concurrent mode).
96
+ * Returns `[false, (fn) => fn()]` to keep code runnable without changes.
97
+ */
98
+ function useTransition() {
99
+ return [false, (fn) => fn()];
100
+ }
101
+ /**
102
+ * Drop-in for React's `useDeferredValue` — returns the value as-is in Pyreon.
103
+ */
104
+ function useDeferredValue(value) {
105
+ return value;
106
+ }
107
+ /**
108
+ * Drop-in for React's `createPortal(children, target)`.
109
+ */
110
+ function createPortal(children, target) {
111
+ return Portal({
112
+ target,
113
+ children
114
+ });
115
+ }
116
+ /**
117
+ * Drop-in for React's `useImperativeHandle`.
118
+ * In Pyreon, expose methods via a ref prop directly — this is a compatibility shim.
119
+ */
120
+ function useImperativeHandle(ref, init, _deps) {
121
+ onMount(() => {
122
+ if (ref) ref.current = init();
123
+ });
124
+ onUnmount(() => {
125
+ if (ref) ref.current = null;
126
+ });
127
+ }
128
+
129
+ //#endregion
130
+ export { ErrorBoundary, Fragment, Suspense, batch, createContext, createElement, createPortal, createSelector, h, lazy, memo, onMount, onUnmount, onUpdate, useCallback, useContext, useDeferredValue, useEffect, useEffect as useLayoutEffect_, onErrorCaptured as useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useTransition };
131
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @pyreon/react-compat\n *\n * React-compatible hook API that runs on Pyreon's reactive engine.\n *\n * Allows you to write familiar React-style code while getting Pyreon's\n * fine-grained reactivity, built-in router/store, and superior performance.\n *\n * DIFFERENCES FROM REACT:\n * - No hooks rules: call these anywhere in a component, in loops, conditions, etc.\n * - useEffect deps array is IGNORED — Pyreon tracks dependencies automatically.\n * - useCallback/memo are identity functions — no re-renders means no stale closures.\n * - Components run ONCE (setup), not on every render.\n *\n * USAGE:\n * Replace `import { useState, useEffect } from \"react\"` with\n * `import { useState, useEffect } from \"@pyreon/react-compat\"`\n * Replace `import { createRoot } from \"react-dom/client\"` with\n * `import { createRoot } from \"@pyreon/react-compat/dom\"`\n */\n\nexport type { Props, VNode as ReactNode, VNodeChild } from \"@pyreon/core\"\n// Re-export Pyreon's JSX runtime so JSX transforms work the same way\n// Lifecycle\nexport { Fragment, h as createElement, h, onMount as useLayoutEffect } from \"@pyreon/core\"\n\nimport type { CleanupFn, VNodeChild } from \"@pyreon/core\"\nimport {\n createContext,\n createRef,\n ErrorBoundary,\n onErrorCaptured,\n onMount,\n onUnmount,\n onUpdate,\n Portal,\n Suspense,\n useContext,\n} from \"@pyreon/core\"\nimport {\n batch,\n computed,\n createSelector,\n effect,\n getCurrentScope,\n runUntracked,\n signal,\n} from \"@pyreon/reactivity\"\n\n// ─── State ────────────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `useState`.\n * Returns `[getter, setter]` — call `getter()` to read, `setter(v)` to write.\n *\n * Unlike React: the getter is a signal, so any component or effect that reads\n * it will re-run automatically. No dep arrays needed.\n */\nexport function useState<T>(initial: T | (() => T)): [() => T, (v: T | ((prev: T) => T)) => void] {\n const s = signal<T>(typeof initial === \"function\" ? (initial as () => T)() : initial)\n const setter = (v: T | ((prev: T) => T)) => {\n if (typeof v === \"function\") s.update(v as (prev: T) => T)\n else s.set(v)\n }\n return [s, setter]\n}\n\n// ─── Reducer ─────────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `useReducer`.\n */\nexport function useReducer<S, A>(\n reducer: (state: S, action: A) => S,\n initial: S | (() => S),\n): [() => S, (action: A) => void] {\n const s = signal<S>(typeof initial === \"function\" ? (initial as () => S)() : initial)\n const dispatch = (action: A) => s.update((prev) => reducer(prev, action))\n return [s, dispatch]\n}\n\n// ─── Effects ─────────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `useEffect`.\n *\n * The `deps` array is IGNORED — Pyreon tracks reactive dependencies automatically.\n * If `deps` is `[]` (mount-only), wrap the body in `runUntracked(() => ...)`.\n *\n * Returns a cleanup the same way React does (return a function from `fn`).\n */\n// biome-ignore lint/suspicious/noConfusingVoidType: void is intentional — callers may return void\nexport function useEffect(fn: () => CleanupFn | void, deps?: unknown[]): void {\n if (deps !== undefined && deps.length === 0) {\n // [] means \"run once on mount\" — use onMount instead of a tracking effect\n onMount((): undefined => {\n const cleanup = runUntracked(fn)\n if (typeof cleanup === \"function\") onUnmount(cleanup)\n })\n } else {\n // No deps or non-empty deps: run reactively (Pyreon auto-tracks).\n // effect() natively supports cleanup: if fn() returns a function,\n // it's called before re-runs and on dispose.\n const e = effect(fn)\n onUnmount(() => {\n e.dispose()\n })\n }\n}\n\n/**\n * Drop-in for React's `useLayoutEffect`.\n * In Pyreon there is no paint distinction — maps to `onMount` (same as useEffect).\n */\nexport { useEffect as useLayoutEffect_ }\n\n// ─── Memoization ─────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `useMemo`.\n * The `deps` array is IGNORED — Pyreon's `computed` tracks dependencies automatically.\n * Returns a getter: call `value()` to read the memoized result.\n */\nexport function useMemo<T>(fn: () => T, _deps?: unknown[]): () => T {\n return computed(fn)\n}\n\n/**\n * Drop-in for React's `useCallback`.\n * In Pyreon, components run once so callbacks are never recreated — returns `fn` as-is.\n */\nexport function useCallback<T extends (...args: unknown[]) => unknown>(\n fn: T,\n _deps?: unknown[],\n): T {\n return fn\n}\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `useRef`.\n * Returns `{ current: T }` — same shape as React's ref object.\n */\nexport function useRef<T>(initial?: T): { current: T | null } {\n const ref = createRef<T>()\n if (initial !== undefined) ref.current = initial as T\n return ref\n}\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `createContext` + `useContext`.\n * Usage mirrors React: `const Ctx = createContext(defaultValue)`.\n */\nexport { createContext, useContext }\n\n// ─── ID ───────────────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `useId` — returns a stable unique string per component instance.\n *\n * Uses the component's effectScope as the key so the counter starts at 0 for every\n * component on both server and client — IDs are deterministic and hydration-safe.\n */\nconst _idCounters = new WeakMap<object, number>()\n\nexport function useId(): string {\n const scope = getCurrentScope()\n if (!scope) return `:r${Math.random().toString(36).slice(2, 9)}:`\n const count = _idCounters.get(scope) ?? 0\n _idCounters.set(scope, count + 1)\n return `:r${count.toString(36)}:`\n}\n\n// ─── Optimization ─────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `memo` — wraps a component.\n * In Pyreon, components run once (no re-renders), so memoization is a no-op.\n * Kept for API compatibility when migrating React code.\n */\nexport function memo<P extends Record<string, unknown>>(\n component: (props: P) => VNodeChild,\n): (props: P) => VNodeChild {\n return component\n}\n\n/**\n * Drop-in for React's `useTransition` — no-op in Pyreon (no concurrent mode).\n * Returns `[false, (fn) => fn()]` to keep code runnable without changes.\n */\nexport function useTransition(): [boolean, (fn: () => void) => void] {\n return [false, (fn) => fn()]\n}\n\n/**\n * Drop-in for React's `useDeferredValue` — returns the value as-is in Pyreon.\n */\nexport function useDeferredValue<T>(value: T): T {\n return value\n}\n\n// ─── Batching ─────────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `unstable_batchedUpdates` / React 18's automatic batching.\n * Pyreon's `batch()` does the same thing.\n */\nexport { batch }\n\n// ─── Error boundaries ─────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's error boundary pattern.\n * Return `true` from `handler` to prevent error propagation (like `componentDidCatch`).\n */\nexport { onErrorCaptured as useErrorBoundary }\n\n// ─── Portals ─────────────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `createPortal(children, target)`.\n */\nexport function createPortal(children: VNodeChild, target: Element): VNodeChild {\n return Portal({ target, children })\n}\n\n// ─── Imperative handle ────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `useImperativeHandle`.\n * In Pyreon, expose methods via a ref prop directly — this is a compatibility shim.\n */\nexport function useImperativeHandle<T>(\n ref: { current: T | null } | null | undefined,\n init: () => T,\n _deps?: unknown[],\n): void {\n onMount((): undefined => {\n if (ref) ref.current = init()\n })\n onUnmount(() => {\n if (ref) ref.current = null\n })\n}\n\n// ─── Selector ─────────────────────────────────────────────────────────────────\n\n/**\n * Pyreon-specific: O(1) equality selector (no React equivalent).\n * Useful for large lists where only the selected item should re-render.\n * @see createSelector in @pyreon/reactivity\n */\nexport { createSelector }\n\n// ─── onUpdate ─────────────────────────────────────────────────────────────────\n\n/** Pyreon-specific lifecycle hook — runs after each reactive update. */\nexport { onMount, onUnmount, onUpdate }\n\n// ─── Suspense / lazy ──────────────────────────────────────────────────────────\n\n/**\n * Drop-in for React's `lazy()`.\n * Re-exported from `@pyreon/core` — wraps a dynamic import, renders null until\n * the module resolves. Pair with `<Suspense>` to show a fallback during loading.\n */\nexport { lazy } from \"@pyreon/core\"\n\n/**\n * Drop-in for React's `<Suspense>`.\n * Shows `fallback` while a `lazy()` child is still loading.\n */\nexport { Suspense, ErrorBoundary }\n"],"mappings":";;;;;;;;;;;AA0DA,SAAgB,SAAY,SAAsE;CAChG,MAAM,IAAI,OAAU,OAAO,YAAY,aAAc,SAAqB,GAAG,QAAQ;CACrF,MAAM,UAAU,MAA4B;AAC1C,MAAI,OAAO,MAAM,WAAY,GAAE,OAAO,EAAoB;MACrD,GAAE,IAAI,EAAE;;AAEf,QAAO,CAAC,GAAG,OAAO;;;;;AAQpB,SAAgB,WACd,SACA,SACgC;CAChC,MAAM,IAAI,OAAU,OAAO,YAAY,aAAc,SAAqB,GAAG,QAAQ;CACrF,MAAM,YAAY,WAAc,EAAE,QAAQ,SAAS,QAAQ,MAAM,OAAO,CAAC;AACzE,QAAO,CAAC,GAAG,SAAS;;;;;;;;;;AActB,SAAgB,UAAU,IAA4B,MAAwB;AAC5E,KAAI,SAAS,UAAa,KAAK,WAAW,EAExC,eAAyB;EACvB,MAAM,UAAU,aAAa,GAAG;AAChC,MAAI,OAAO,YAAY,WAAY,WAAU,QAAQ;GACrD;MACG;EAIL,MAAM,IAAI,OAAO,GAAG;AACpB,kBAAgB;AACd,KAAE,SAAS;IACX;;;;;;;;AAiBN,SAAgB,QAAW,IAAa,OAA4B;AAClE,QAAO,SAAS,GAAG;;;;;;AAOrB,SAAgB,YACd,IACA,OACG;AACH,QAAO;;;;;;AAST,SAAgB,OAAU,SAAoC;CAC5D,MAAM,MAAM,WAAc;AAC1B,KAAI,YAAY,OAAW,KAAI,UAAU;AACzC,QAAO;;;;;;;;AAmBT,MAAM,8BAAc,IAAI,SAAyB;AAEjD,SAAgB,QAAgB;CAC9B,MAAM,QAAQ,iBAAiB;AAC/B,KAAI,CAAC,MAAO,QAAO,KAAK,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;CAC/D,MAAM,QAAQ,YAAY,IAAI,MAAM,IAAI;AACxC,aAAY,IAAI,OAAO,QAAQ,EAAE;AACjC,QAAO,KAAK,MAAM,SAAS,GAAG,CAAC;;;;;;;AAUjC,SAAgB,KACd,WAC0B;AAC1B,QAAO;;;;;;AAOT,SAAgB,gBAAqD;AACnE,QAAO,CAAC,QAAQ,OAAO,IAAI,CAAC;;;;;AAM9B,SAAgB,iBAAoB,OAAa;AAC/C,QAAO;;;;;AAwBT,SAAgB,aAAa,UAAsB,QAA6B;AAC9E,QAAO,OAAO;EAAE;EAAQ;EAAU,CAAC;;;;;;AASrC,SAAgB,oBACd,KACA,MACA,OACM;AACN,eAAyB;AACvB,MAAI,IAAK,KAAI,UAAU,MAAM;GAC7B;AACF,iBAAgB;AACd,MAAI,IAAK,KAAI,UAAU;GACvB"}
@@ -0,0 +1,129 @@
1
+ import { ErrorBoundary, Fragment, Portal, Suspense, createContext, createRef, h, h as createElement, lazy, onErrorCaptured, onMount, onMount as useLayoutEffect, onUnmount, onUpdate, useContext } from "@pyreon/core";
2
+ import { batch, computed, createSelector, effect, getCurrentScope, runUntracked, signal } from "@pyreon/reactivity";
3
+
4
+ //#region src/index.ts
5
+ /**
6
+ * Drop-in for React's `useState`.
7
+ * Returns `[getter, setter]` — call `getter()` to read, `setter(v)` to write.
8
+ *
9
+ * Unlike React: the getter is a signal, so any component or effect that reads
10
+ * it will re-run automatically. No dep arrays needed.
11
+ */
12
+ function useState(initial) {
13
+ const s = signal(typeof initial === "function" ? initial() : initial);
14
+ const setter = v => {
15
+ if (typeof v === "function") s.update(v);else s.set(v);
16
+ };
17
+ return [s, setter];
18
+ }
19
+ /**
20
+ * Drop-in for React's `useReducer`.
21
+ */
22
+ function useReducer(reducer, initial) {
23
+ const s = signal(typeof initial === "function" ? initial() : initial);
24
+ const dispatch = action => s.update(prev => reducer(prev, action));
25
+ return [s, dispatch];
26
+ }
27
+ /**
28
+ * Drop-in for React's `useEffect`.
29
+ *
30
+ * The `deps` array is IGNORED — Pyreon tracks reactive dependencies automatically.
31
+ * If `deps` is `[]` (mount-only), wrap the body in `runUntracked(() => ...)`.
32
+ *
33
+ * Returns a cleanup the same way React does (return a function from `fn`).
34
+ */
35
+ function useEffect(fn, deps) {
36
+ if (deps !== void 0 && deps.length === 0) onMount(() => {
37
+ const cleanup = runUntracked(fn);
38
+ if (typeof cleanup === "function") onUnmount(cleanup);
39
+ });else {
40
+ const e = effect(fn);
41
+ onUnmount(() => {
42
+ e.dispose();
43
+ });
44
+ }
45
+ }
46
+ /**
47
+ * Drop-in for React's `useMemo`.
48
+ * The `deps` array is IGNORED — Pyreon's `computed` tracks dependencies automatically.
49
+ * Returns a getter: call `value()` to read the memoized result.
50
+ */
51
+ function useMemo(fn, _deps) {
52
+ return computed(fn);
53
+ }
54
+ /**
55
+ * Drop-in for React's `useCallback`.
56
+ * In Pyreon, components run once so callbacks are never recreated — returns `fn` as-is.
57
+ */
58
+ function useCallback(fn, _deps) {
59
+ return fn;
60
+ }
61
+ /**
62
+ * Drop-in for React's `useRef`.
63
+ * Returns `{ current: T }` — same shape as React's ref object.
64
+ */
65
+ function useRef(initial) {
66
+ const ref = createRef();
67
+ if (initial !== void 0) ref.current = initial;
68
+ return ref;
69
+ }
70
+ /**
71
+ * Drop-in for React's `useId` — returns a stable unique string per component instance.
72
+ *
73
+ * Uses the component's effectScope as the key so the counter starts at 0 for every
74
+ * component on both server and client — IDs are deterministic and hydration-safe.
75
+ */
76
+
77
+ function useId() {
78
+ const scope = getCurrentScope();
79
+ if (!scope) return `:r${Math.random().toString(36).slice(2, 9)}:`;
80
+ const count = _idCounters.get(scope) ?? 0;
81
+ _idCounters.set(scope, count + 1);
82
+ return `:r${count.toString(36)}:`;
83
+ }
84
+ /**
85
+ * Drop-in for React's `memo` — wraps a component.
86
+ * In Pyreon, components run once (no re-renders), so memoization is a no-op.
87
+ * Kept for API compatibility when migrating React code.
88
+ */
89
+ function memo(component) {
90
+ return component;
91
+ }
92
+ /**
93
+ * Drop-in for React's `useTransition` — no-op in Pyreon (no concurrent mode).
94
+ * Returns `[false, (fn) => fn()]` to keep code runnable without changes.
95
+ */
96
+ function useTransition() {
97
+ return [false, fn => fn()];
98
+ }
99
+ /**
100
+ * Drop-in for React's `useDeferredValue` — returns the value as-is in Pyreon.
101
+ */
102
+ function useDeferredValue(value) {
103
+ return value;
104
+ }
105
+ /**
106
+ * Drop-in for React's `createPortal(children, target)`.
107
+ */
108
+ function createPortal(children, target) {
109
+ return Portal({
110
+ target,
111
+ children
112
+ });
113
+ }
114
+ /**
115
+ * Drop-in for React's `useImperativeHandle`.
116
+ * In Pyreon, expose methods via a ref prop directly — this is a compatibility shim.
117
+ */
118
+ function useImperativeHandle(ref, init, _deps) {
119
+ onMount(() => {
120
+ if (ref) ref.current = init();
121
+ });
122
+ onUnmount(() => {
123
+ if (ref) ref.current = null;
124
+ });
125
+ }
126
+
127
+ //#endregion
128
+ export { ErrorBoundary, Fragment, Suspense, batch, createContext, createElement, createPortal, createSelector, h, lazy, memo, onMount, onUnmount, onUpdate, useCallback, useContext, useDeferredValue, useEffect, useEffect as useLayoutEffect_, onErrorCaptured as useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useTransition };
129
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { CleanupFn, ErrorBoundary, Fragment, Props, Suspense, VNode as ReactNode, VNodeChild, VNodeChild as VNodeChild$1, createContext, h, h as createElement, lazy, onErrorCaptured, onMount, onMount as useLayoutEffect, onUnmount, onUpdate, useContext } from "@pyreon/core";
2
+ import { batch, createSelector } from "@pyreon/reactivity";
3
+
4
+ //#region src/index.d.ts
5
+ /**
6
+ * Drop-in for React's `useState`.
7
+ * Returns `[getter, setter]` — call `getter()` to read, `setter(v)` to write.
8
+ *
9
+ * Unlike React: the getter is a signal, so any component or effect that reads
10
+ * it will re-run automatically. No dep arrays needed.
11
+ */
12
+ declare function useState<T>(initial: T | (() => T)): [() => T, (v: T | ((prev: T) => T)) => void];
13
+ /**
14
+ * Drop-in for React's `useReducer`.
15
+ */
16
+ declare function useReducer<S, A>(reducer: (state: S, action: A) => S, initial: S | (() => S)): [() => S, (action: A) => void];
17
+ /**
18
+ * Drop-in for React's `useEffect`.
19
+ *
20
+ * The `deps` array is IGNORED — Pyreon tracks reactive dependencies automatically.
21
+ * If `deps` is `[]` (mount-only), wrap the body in `runUntracked(() => ...)`.
22
+ *
23
+ * Returns a cleanup the same way React does (return a function from `fn`).
24
+ */
25
+ declare function useEffect(fn: () => CleanupFn | void, deps?: unknown[]): void;
26
+ /**
27
+ * Drop-in for React's `useMemo`.
28
+ * The `deps` array is IGNORED — Pyreon's `computed` tracks dependencies automatically.
29
+ * Returns a getter: call `value()` to read the memoized result.
30
+ */
31
+ declare function useMemo<T>(fn: () => T, _deps?: unknown[]): () => T;
32
+ /**
33
+ * Drop-in for React's `useCallback`.
34
+ * In Pyreon, components run once so callbacks are never recreated — returns `fn` as-is.
35
+ */
36
+ declare function useCallback<T extends (...args: unknown[]) => unknown>(fn: T, _deps?: unknown[]): T;
37
+ /**
38
+ * Drop-in for React's `useRef`.
39
+ * Returns `{ current: T }` — same shape as React's ref object.
40
+ */
41
+ declare function useRef<T>(initial?: T): {
42
+ current: T | null;
43
+ };
44
+ declare function useId(): string;
45
+ /**
46
+ * Drop-in for React's `memo` — wraps a component.
47
+ * In Pyreon, components run once (no re-renders), so memoization is a no-op.
48
+ * Kept for API compatibility when migrating React code.
49
+ */
50
+ declare function memo<P extends Record<string, unknown>>(component: (props: P) => VNodeChild$1): (props: P) => VNodeChild$1;
51
+ /**
52
+ * Drop-in for React's `useTransition` — no-op in Pyreon (no concurrent mode).
53
+ * Returns `[false, (fn) => fn()]` to keep code runnable without changes.
54
+ */
55
+ declare function useTransition(): [boolean, (fn: () => void) => void];
56
+ /**
57
+ * Drop-in for React's `useDeferredValue` — returns the value as-is in Pyreon.
58
+ */
59
+ declare function useDeferredValue<T>(value: T): T;
60
+ /**
61
+ * Drop-in for React's `createPortal(children, target)`.
62
+ */
63
+ declare function createPortal(children: VNodeChild$1, target: Element): VNodeChild$1;
64
+ /**
65
+ * Drop-in for React's `useImperativeHandle`.
66
+ * In Pyreon, expose methods via a ref prop directly — this is a compatibility shim.
67
+ */
68
+ declare function useImperativeHandle<T>(ref: {
69
+ current: T | null;
70
+ } | null | undefined, init: () => T, _deps?: unknown[]): void;
71
+ //#endregion
72
+ export { ErrorBoundary, Fragment, type Props, type ReactNode, Suspense, type VNodeChild, batch, createContext, createElement, createPortal, createSelector, h, lazy, memo, onMount, onUnmount, onUpdate, useCallback, useContext, useDeferredValue, useEffect, useEffect as useLayoutEffect_, onErrorCaptured as useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useTransition };
73
+ //# sourceMappingURL=index2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":";;;;;;AAwEA;;;;;iBAdgB,QAAA,GAAA,CAAY,OAAA,EAAS,CAAA,UAAW,CAAA,WAAY,CAAA,GAAI,CAAA,EAAG,CAAA,KAAM,IAAA,EAAM,CAAA,KAAM,CAAA;;;;iBAcrE,UAAA,MAAA,CACd,OAAA,GAAU,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,CAAA,KAAM,CAAA,EAClC,OAAA,EAAS,CAAA,UAAW,CAAA,WACZ,CAAA,GAAI,MAAA,EAAQ,CAAA;;;;;;;;;iBAiBN,SAAA,CAAU,EAAA,QAAU,SAAA,SAAkB,IAAA;;;;;AAAtD;iBA+BgB,OAAA,GAAA,CAAW,EAAA,QAAU,CAAA,EAAG,KAAA,qBAA0B,CAAA;;;;;iBAQlD,WAAA,eAA0B,IAAA,wBAAA,CACxC,EAAA,EAAI,CAAA,EACJ,KAAA,eACC,CAAA;;;AAXH;;iBAqBgB,MAAA,GAAA,CAAU,OAAA,GAAU,CAAA;EAAM,OAAA,EAAS,CAAA;AAAA;AAAA,iBAwBnC,KAAA,CAAA;AArChB;;;;;AAAA,iBAoDgB,IAAA,WAAe,MAAA,kBAAA,CAC7B,SAAA,GAAY,KAAA,EAAO,CAAA,KAAM,YAAA,IACvB,KAAA,EAAO,CAAA,KAAM,YAAA;;;;;iBAQD,aAAA,CAAA,cAA4B,EAAA;;AAjD5C;;iBAwDgB,gBAAA,GAAA,CAAoB,KAAA,EAAO,CAAA,GAAI,CAAA;;;;iBAyB/B,YAAA,CAAa,QAAA,EAAU,YAAA,EAAY,MAAA,EAAQ,OAAA,GAAU,YAAA;;;;;iBAUrD,mBAAA,GAAA,CACd,GAAA;EAAO,OAAA,EAAS,CAAA;AAAA,sBAChB,IAAA,QAAY,CAAA,EACZ,KAAA"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@pyreon/react-compat",
3
+ "version": "0.1.0",
4
+ "description": "React-compatible API shim for Pyreon — write React-style hooks that run on Pyreon's reactive engine",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/pyreon/pyreon.git",
9
+ "directory": "packages/react-compat"
10
+ },
11
+ "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/react-compat#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/pyreon/pyreon/issues"
14
+ },
15
+ "files": [
16
+ "lib",
17
+ "src",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "sideEffects": false,
22
+ "type": "module",
23
+ "main": "./lib/index.js",
24
+ "module": "./lib/index.js",
25
+ "types": "./lib/types/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "bun": "./src/index.ts",
29
+ "import": "./lib/index.js",
30
+ "types": "./lib/types/index.d.ts"
31
+ },
32
+ "./dom": {
33
+ "bun": "./src/dom.ts",
34
+ "import": "./lib/dom.js",
35
+ "types": "./lib/types/dom.d.ts"
36
+ }
37
+ },
38
+ "scripts": {
39
+ "build": "vl_rolldown_build",
40
+ "dev": "vl_rolldown_build-watch",
41
+ "test": "vitest run",
42
+ "typecheck": "tsc --noEmit",
43
+ "prepublishOnly": "bun run build"
44
+ },
45
+ "dependencies": {
46
+ "@pyreon/core": "workspace:*",
47
+ "@pyreon/reactivity": "workspace:*",
48
+ "@pyreon/runtime-dom": "workspace:*"
49
+ },
50
+ "devDependencies": {
51
+ "@happy-dom/global-registrator": "^20.8.3",
52
+ "happy-dom": "^20.8.3"
53
+ }
54
+ }
package/src/dom.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { VNodeChild } from "@pyreon/core"
2
+ /**
3
+ * @pyreon/react-compat/dom
4
+ *
5
+ * Drop-in for `react-dom/client` — provides `createRoot` so you can keep
6
+ * the same entry-point pattern as a React app.
7
+ */
8
+ import { mount } from "@pyreon/runtime-dom"
9
+
10
+ /**
11
+ * Drop-in for React 18's `createRoot(container).render(element)`.
12
+ *
13
+ * @example
14
+ * import { createRoot } from "@pyreon/react-compat/dom"
15
+ * createRoot(document.getElementById("app")!).render(<App />)
16
+ */
17
+ export function createRoot(container: Element): {
18
+ render: (element: VNodeChild) => void
19
+ unmount: () => void
20
+ } {
21
+ let cleanup: (() => void) | null = null
22
+ return {
23
+ render(element: VNodeChild) {
24
+ if (cleanup) cleanup()
25
+ cleanup = mount(element, container as HTMLElement)
26
+ },
27
+ unmount() {
28
+ if (cleanup) {
29
+ cleanup()
30
+ cleanup = null
31
+ }
32
+ },
33
+ }
34
+ }
35
+
36
+ /** Alias — matches React 17's `render(element, container)` signature. */
37
+ export function render(element: VNodeChild, container: Element): void {
38
+ mount(element, container as HTMLElement)
39
+ }