@pyreon/react-compat 0.14.0 → 0.15.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/README.md +20 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/analysis/jsx-runtime.js.html +1 -1
- package/lib/index.js +17 -3
- package/lib/jsx-runtime.js +3 -3
- package/package.json +8 -4
- package/src/env.d.ts +6 -0
- package/src/index.ts +2 -4
- package/src/jsx-runtime.ts +8 -5
- package/src/react-compat-rerender.browser.test.tsx +59 -0
- package/src/react-compat.browser.test.tsx +34 -0
- package/src/tests/native-marker-bypass.test.tsx +88 -0
- package/lib/dom.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/jsx-runtime.js.map +0 -1
- package/lib/types/dom.d.ts.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/jsx-runtime.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -147,3 +147,23 @@ function Dashboard() {
|
|
|
147
147
|
- **`useErrorBoundary`** -- alias for `onErrorCaptured`.
|
|
148
148
|
- **`createSelector`** -- O(1) equality selector from `@pyreon/reactivity`.
|
|
149
149
|
- **`createElement` / `h`**, **`Fragment`** -- JSX runtime.
|
|
150
|
+
|
|
151
|
+
## Composing Pyreon framework components inside react-compat
|
|
152
|
+
|
|
153
|
+
Pyreon's framework components (`RouterView`, `PyreonUI`, `FormProvider`, `QueryClientProvider`, …) ship marked with `nativeCompat()` from `@pyreon/core` — react-compat's JSX runtime detects the marker and routes them through Pyreon's setup frame instead of the compat wrapper. **You don't need to do anything** for the 24 components shipped marked.
|
|
154
|
+
|
|
155
|
+
If you write your **own** Pyreon-flavored helper that uses `provide()` / `onMount()` / `onUnmount()` / `effect()` at component-body scope and use it in a react-compat app, mark it explicitly:
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { nativeCompat, provide, createContext } from '@pyreon/core'
|
|
159
|
+
|
|
160
|
+
const MyCtx = createContext<string>('default')
|
|
161
|
+
|
|
162
|
+
function MyProvider(props: { value: string; children?: unknown }) {
|
|
163
|
+
provide(MyCtx, props.value)
|
|
164
|
+
return props.children as never
|
|
165
|
+
}
|
|
166
|
+
nativeCompat(MyProvider) // ← required for compat-mode apps
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Without the marker, the wrapper relocates the body's render context and `provide()` lands in a torn-down context stack — descendants read the default. See [`packages/core/core/src/compat-marker.ts`](../../core/core/src/compat-marker.ts) for details.
|
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"4d7452bf-1","name":"jsx-runtime.ts"},{"uid":"4d7452bf-3","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"4d7452bf-1":{"renderedLength":186,"gzipLength":138,"brotliLength":0,"metaUid":"4d7452bf-0"},"4d7452bf-3":{"renderedLength":16657,"gzipLength":4670,"brotliLength":0,"metaUid":"4d7452bf-2"}},"nodeMetas":{"4d7452bf-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"index.js":"4d7452bf-1"},"imported":[{"uid":"4d7452bf-4"},{"uid":"4d7452bf-5"}],"importedBy":[{"uid":"4d7452bf-2"}]},"4d7452bf-2":{"id":"/src/index.ts","moduleParts":{"index.js":"4d7452bf-3"},"imported":[{"uid":"4d7452bf-4"},{"uid":"4d7452bf-5"},{"uid":"4d7452bf-0"}],"importedBy":[],"isEntry":true},"4d7452bf-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"4d7452bf-2"},{"uid":"4d7452bf-0"}]},"4d7452bf-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"4d7452bf-2"},{"uid":"4d7452bf-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"jsx-runtime.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"jsx-runtime.js","children":[{"name":"src","children":[{"uid":"8146f520-1","name":"jsx-runtime.ts"},{"uid":"8146f520-3","name":"jsx-dev-runtime.ts"}]}]}],"isRoot":true},"nodeParts":{"8146f520-1":{"renderedLength":4794,"gzipLength":1456,"brotliLength":0,"metaUid":"8146f520-0"},"8146f520-3":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"8146f520-2"}},"nodeMetas":{"8146f520-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"jsx-runtime.js":"8146f520-1"},"imported":[{"uid":"8146f520-4"},{"uid":"8146f520-5"}],"importedBy":[{"uid":"8146f520-2"}]},"8146f520-2":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-runtime.js":"8146f520-3"},"imported":[{"uid":"8146f520-0"}],"importedBy":[],"isEntry":true},"8146f520-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"8146f520-0"}]},"8146f520-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"8146f520-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ErrorBoundary, Fragment, Portal, Suspense, createContext as createContext$1, createRef, h, h as createElement, h as h$1, lazy, provide, useContext as useContext$1 } from "@pyreon/core";
|
|
1
|
+
import { ErrorBoundary, Fragment, Portal, Suspense, createContext as createContext$1, createRef, h, h as createElement, h as h$1, lazy, nativeCompat, provide, useContext as useContext$1 } from "@pyreon/core";
|
|
2
2
|
import { batch } from "@pyreon/reactivity";
|
|
3
3
|
|
|
4
4
|
//#region src/jsx-runtime.ts
|
|
@@ -188,7 +188,6 @@ function useRef(initial) {
|
|
|
188
188
|
}
|
|
189
189
|
const COMPAT_CTX = Symbol.for("pyreon:compat-ctx");
|
|
190
190
|
const COMPAT_CTX_BRAND = COMPAT_CTX;
|
|
191
|
-
const NATIVE_COMPONENT = Symbol.for("pyreon:native-compat");
|
|
192
191
|
/**
|
|
193
192
|
* React-compatible `createContext` — creates a context with a Provider that
|
|
194
193
|
* supports nested Providers (inner overrides outer for its subtree) and
|
|
@@ -215,7 +214,7 @@ function createContext(defaultValue) {
|
|
|
215
214
|
return children ?? null;
|
|
216
215
|
};
|
|
217
216
|
};
|
|
218
|
-
Provider
|
|
217
|
+
nativeCompat(Provider);
|
|
219
218
|
return {
|
|
220
219
|
[COMPAT_CTX_BRAND]: true,
|
|
221
220
|
_defaultValue: defaultValue,
|
|
@@ -370,6 +369,9 @@ function flattenChildren(children) {
|
|
|
370
369
|
* React-compatible `Children` utilities for working with VNode children.
|
|
371
370
|
*/
|
|
372
371
|
const Children = {
|
|
372
|
+
/**
|
|
373
|
+
* Iterate over children, calling `fn` for each non-null child.
|
|
374
|
+
*/
|
|
373
375
|
map(children, fn) {
|
|
374
376
|
const flat = flattenChildren(children);
|
|
375
377
|
const result = [];
|
|
@@ -387,6 +389,9 @@ const Children = {
|
|
|
387
389
|
}
|
|
388
390
|
return result;
|
|
389
391
|
},
|
|
392
|
+
/**
|
|
393
|
+
* Call `fn` for each non-null child (no return value).
|
|
394
|
+
*/
|
|
390
395
|
forEach(children, fn) {
|
|
391
396
|
const flat = flattenChildren(children);
|
|
392
397
|
let validIndex = 0;
|
|
@@ -396,15 +401,24 @@ const Children = {
|
|
|
396
401
|
fn(child, validIndex++);
|
|
397
402
|
}
|
|
398
403
|
},
|
|
404
|
+
/**
|
|
405
|
+
* Count non-null children.
|
|
406
|
+
*/
|
|
399
407
|
count(children) {
|
|
400
408
|
const flat = flattenChildren(children);
|
|
401
409
|
let count = 0;
|
|
402
410
|
for (const child of flat) if (child != null && child !== true && child !== false) count++;
|
|
403
411
|
return count;
|
|
404
412
|
},
|
|
413
|
+
/**
|
|
414
|
+
* Convert children to a flat array.
|
|
415
|
+
*/
|
|
405
416
|
toArray(children) {
|
|
406
417
|
return flattenChildren(children).filter((child) => child != null && child !== true && child !== false);
|
|
407
418
|
},
|
|
419
|
+
/**
|
|
420
|
+
* Assert and return the only child. Throws if not exactly one child.
|
|
421
|
+
*/
|
|
408
422
|
only(children) {
|
|
409
423
|
const arr = Children.toArray(children);
|
|
410
424
|
if (arr.length !== 1) throw new Error("[Pyreon] Children.only expected exactly one child");
|
package/lib/jsx-runtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, h, onUnmount } from "@pyreon/core";
|
|
1
|
+
import { Fragment, h, isNativeCompat, onUnmount } from "@pyreon/core";
|
|
2
2
|
import { signal } from "@pyreon/reactivity";
|
|
3
3
|
|
|
4
4
|
//#region src/jsx-runtime.ts
|
|
@@ -16,7 +16,7 @@ function beginRender(ctx) {
|
|
|
16
16
|
}
|
|
17
17
|
function endRender() {
|
|
18
18
|
if (_currentCtx) {
|
|
19
|
-
if (
|
|
19
|
+
if (process.env.NODE_ENV !== "production" && _expectedHookCount !== -1 && _hookIndex !== _expectedHookCount) console.error(`[Pyreon] Hook count changed between renders (expected ${_expectedHookCount}, got ${_hookIndex}). This usually means a hook is called conditionally. Hooks must be called in the same order every render.`);
|
|
20
20
|
_currentCtx._hookCount = _hookIndex;
|
|
21
21
|
}
|
|
22
22
|
_currentCtx = null;
|
|
@@ -108,7 +108,7 @@ function jsx(type, props, key) {
|
|
|
108
108
|
...propsWithKey,
|
|
109
109
|
children
|
|
110
110
|
} : propsWithKey;
|
|
111
|
-
if (type
|
|
111
|
+
if (isNativeCompat(type)) return h(type, componentProps);
|
|
112
112
|
return h(wrapCompatComponent(type), componentProps);
|
|
113
113
|
}
|
|
114
114
|
const childArray = children === void 0 ? [] : Array.isArray(children) ? children : [children];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/react-compat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "React-compatible API shim for Pyreon — write React-style hooks that run on Pyreon's reactive engine",
|
|
5
5
|
"homepage": "https://github.com/pyreon/pyreon/tree/main/packages/react-compat#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"lib",
|
|
17
|
+
"!lib/**/*.map",
|
|
17
18
|
"src",
|
|
18
19
|
"README.md",
|
|
19
20
|
"LICENSE"
|
|
@@ -52,17 +53,20 @@
|
|
|
52
53
|
"build": "vl_rolldown_build",
|
|
53
54
|
"dev": "vl_rolldown_build-watch",
|
|
54
55
|
"test": "vitest run",
|
|
56
|
+
"test:browser": "vitest run --config ./vitest.browser.config.ts",
|
|
55
57
|
"typecheck": "tsc --noEmit",
|
|
56
58
|
"lint": "oxlint .",
|
|
57
59
|
"prepublishOnly": "bun run build"
|
|
58
60
|
},
|
|
59
61
|
"dependencies": {
|
|
60
|
-
"@pyreon/core": "^0.
|
|
61
|
-
"@pyreon/reactivity": "^0.
|
|
62
|
-
"@pyreon/runtime-dom": "^0.
|
|
62
|
+
"@pyreon/core": "^0.15.0",
|
|
63
|
+
"@pyreon/reactivity": "^0.15.0",
|
|
64
|
+
"@pyreon/runtime-dom": "^0.15.0"
|
|
63
65
|
},
|
|
64
66
|
"devDependencies": {
|
|
65
67
|
"@happy-dom/global-registrator": "^20.8.9",
|
|
68
|
+
"@pyreon/test-utils": "^0.13.2",
|
|
69
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
66
70
|
"happy-dom": "^20.8.3"
|
|
67
71
|
}
|
|
68
72
|
}
|
package/src/env.d.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
createContext as pyreonCreateContext,
|
|
21
21
|
ErrorBoundary,
|
|
22
22
|
h,
|
|
23
|
+
nativeCompat,
|
|
23
24
|
Portal,
|
|
24
25
|
provide as pyreonProvide,
|
|
25
26
|
Suspense,
|
|
@@ -257,9 +258,6 @@ export interface CompatContext<T> {
|
|
|
257
258
|
|
|
258
259
|
const COMPAT_CTX_BRAND: typeof COMPAT_CTX = COMPAT_CTX
|
|
259
260
|
|
|
260
|
-
// Tag the Provider so wrapCompatComponent skips it (it's already a native component)
|
|
261
|
-
const NATIVE_COMPONENT = Symbol.for('pyreon:native-compat')
|
|
262
|
-
|
|
263
261
|
/**
|
|
264
262
|
* React-compatible `createContext` — creates a context with a Provider that
|
|
265
263
|
* supports nested Providers (inner overrides outer for its subtree) and
|
|
@@ -296,7 +294,7 @@ export function createContext<T>(defaultValue: T): CompatContext<T> {
|
|
|
296
294
|
}
|
|
297
295
|
}
|
|
298
296
|
// Mark as native so jsx() doesn't wrap it with wrapCompatComponent
|
|
299
|
-
|
|
297
|
+
nativeCompat(Provider)
|
|
300
298
|
|
|
301
299
|
const ctx: CompatContext<T> = {
|
|
302
300
|
[COMPAT_CTX_BRAND]: true as const,
|
package/src/jsx-runtime.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { ComponentFn, Props, VNode, VNodeChild } from '@pyreon/core'
|
|
14
|
-
import { Fragment, h, onUnmount } from '@pyreon/core'
|
|
14
|
+
import { Fragment, h, isNativeCompat, onUnmount } from '@pyreon/core'
|
|
15
15
|
import { signal } from '@pyreon/reactivity'
|
|
16
16
|
|
|
17
17
|
export { Fragment }
|
|
@@ -70,7 +70,7 @@ export function endRender(): void {
|
|
|
70
70
|
if (_currentCtx) {
|
|
71
71
|
// Dev-mode: check hook count matches expected
|
|
72
72
|
if (
|
|
73
|
-
|
|
73
|
+
process.env.NODE_ENV !== 'production' &&
|
|
74
74
|
_expectedHookCount !== -1 &&
|
|
75
75
|
_hookIndex !== _expectedHookCount
|
|
76
76
|
) {
|
|
@@ -195,9 +195,12 @@ export function jsx(
|
|
|
195
195
|
|
|
196
196
|
if (typeof type === 'function') {
|
|
197
197
|
const componentProps = children !== undefined ? { ...propsWithKey, children } : propsWithKey
|
|
198
|
-
// Native Pyreon components (
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
// Native Pyreon components (context Provider, RouterView, QueryClientProvider,
|
|
199
|
+
// etc.) skip compat wrapping — they manage their own reactivity via signals
|
|
200
|
+
// and Pyreon's lifecycle, and wrapping them would run their setup body inside
|
|
201
|
+
// the compat layer's render context instead of Pyreon's, breaking `provide()`,
|
|
202
|
+
// `onMount()`, and `onUnmount()`.
|
|
203
|
+
if (isNativeCompat(type)) {
|
|
201
204
|
return h(type as ComponentFn, componentProps)
|
|
202
205
|
}
|
|
203
206
|
// Wrap React-style component for re-render support
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { VNodeChild } from '@pyreon/core'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { flush, mountInBrowser } from '@pyreon/test-utils/browser'
|
|
4
|
+
import { jsx } from './jsx-runtime'
|
|
5
|
+
import { useState } from './index'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Real-browser regression test for the react-compat re-render path.
|
|
9
|
+
*
|
|
10
|
+
* The compat wrapper schedules re-renders via `scheduleRerender` →
|
|
11
|
+
* `queueMicrotask` → `version.set(...)`. Pyreon's `mountReactive`
|
|
12
|
+
* detects the version change and re-runs the accessor, which re-runs
|
|
13
|
+
* the user component and produces a new VNode tree. mountReactive
|
|
14
|
+
* tears down the old subtree and mounts the new one.
|
|
15
|
+
*
|
|
16
|
+
* **Important**: react-compat does FULL DOM REPLACEMENT on every
|
|
17
|
+
* re-render — there is no VDOM diffing in the compat layer (Pyreon's
|
|
18
|
+
* native pattern is fine-grained reactivity, not whole-component
|
|
19
|
+
* re-renders). Tests that capture a DOM reference BEFORE click and
|
|
20
|
+
* then assert on it AFTER click will see stale content because the
|
|
21
|
+
* captured reference points to a now-detached node. **Always re-query
|
|
22
|
+
* the DOM after a state change**.
|
|
23
|
+
*
|
|
24
|
+
* Phase A2's smoke for react-compat held a stale reference and
|
|
25
|
+
* appeared to show the wrapper was broken; this test characterises
|
|
26
|
+
* the correct behavior + the re-query gotcha so future authors don't
|
|
27
|
+
* trip on the same edge.
|
|
28
|
+
*/
|
|
29
|
+
describe('@pyreon/react-compat — real-browser re-render', () => {
|
|
30
|
+
it('clicking a button increments useState count and DOM reflects', async () => {
|
|
31
|
+
function Counter(): VNodeChild {
|
|
32
|
+
const [count, setCount] = useState(0)
|
|
33
|
+
return jsx('button', {
|
|
34
|
+
id: 'rc-counter',
|
|
35
|
+
onClick: () => setCount((n: number) => n + 1),
|
|
36
|
+
children: `count: ${count}`,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { container, unmount } = mountInBrowser(jsx(Counter, {}))
|
|
41
|
+
// Read 1: initial mount
|
|
42
|
+
expect(container.querySelector('#rc-counter')!.textContent).toBe('count: 0')
|
|
43
|
+
|
|
44
|
+
// Click the CURRENT button — re-query after each interaction because
|
|
45
|
+
// react-compat replaces the DOM subtree on re-render (see file-level
|
|
46
|
+
// doc comment).
|
|
47
|
+
container.querySelector<HTMLButtonElement>('#rc-counter')!.click()
|
|
48
|
+
await flush()
|
|
49
|
+
await flush()
|
|
50
|
+
expect(container.querySelector('#rc-counter')!.textContent).toBe('count: 1')
|
|
51
|
+
|
|
52
|
+
container.querySelector<HTMLButtonElement>('#rc-counter')!.click()
|
|
53
|
+
await flush()
|
|
54
|
+
await flush()
|
|
55
|
+
expect(container.querySelector('#rc-counter')!.textContent).toBe('count: 2')
|
|
56
|
+
|
|
57
|
+
unmount()
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { VNodeChild } from '@pyreon/core'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { mountInBrowser } from '@pyreon/test-utils/browser'
|
|
4
|
+
import { jsx } from './jsx-runtime'
|
|
5
|
+
import { useState } from './index'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Real-browser smoke test for `@pyreon/react-compat`.
|
|
9
|
+
*
|
|
10
|
+
* Per the test-environment-parity rule (`pyreon/require-browser-smoke-test`),
|
|
11
|
+
* every browser-categorized package must ship at least one `*.browser.test.*`
|
|
12
|
+
* file. This catches regressions that happy-dom / hook-runner unit tests
|
|
13
|
+
* can hide: importing the public API, mounting through the JSX runtime
|
|
14
|
+
* wrapper, and exercising the React-style hook entry point in a real
|
|
15
|
+
* browser DOM (not a Node DOM polyfill).
|
|
16
|
+
*
|
|
17
|
+
* Companion unit tests in `src/tests/*.test.ts` test the hook semantics
|
|
18
|
+
* via `withHookCtx` runners. This smoke proves the integration: the
|
|
19
|
+
* package can be imported and mounted end-to-end in real Chromium.
|
|
20
|
+
*/
|
|
21
|
+
describe('@pyreon/react-compat — browser smoke', () => {
|
|
22
|
+
it('renders a component using useState in real browser', () => {
|
|
23
|
+
function Greeting(): VNodeChild {
|
|
24
|
+
const [name] = useState('Pyreon')
|
|
25
|
+
return jsx('div', { id: 'greeting', children: `hello, ${name}` })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { container, unmount } = mountInBrowser(jsx(Greeting, {}))
|
|
29
|
+
const greeting = container.querySelector('#greeting')!
|
|
30
|
+
expect(greeting.textContent).toBe('hello, Pyreon')
|
|
31
|
+
unmount()
|
|
32
|
+
expect(document.getElementById('greeting')).toBeNull()
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ComponentFn } from '@pyreon/core'
|
|
2
|
+
import { createContext, h, nativeCompat, provide, useContext } from '@pyreon/core'
|
|
3
|
+
import { mount } from '@pyreon/runtime-dom'
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
import { jsx } from '../jsx-runtime'
|
|
6
|
+
|
|
7
|
+
// Per-compat unit-level regression test for the marker-bypass contract.
|
|
8
|
+
//
|
|
9
|
+
// PR #422 wired `isNativeCompat(type)` into react-compat's `jsx()` runtime.
|
|
10
|
+
// PR #425 added `nativeCompat()` calls to 24 framework components. This file
|
|
11
|
+
// proves the bypass actually works at the unit-test layer:
|
|
12
|
+
//
|
|
13
|
+
// 1. **Bypass identity** (load-bearing): `jsx(NativeProvider, {})` returns
|
|
14
|
+
// vnode with `type === NativeProvider` (not the wrapper), proving the
|
|
15
|
+
// marker check fires.
|
|
16
|
+
// 2. **Wrap-when-unmarked** (load-bearing): UNMARKED components still go
|
|
17
|
+
// through `wrapCompatComponent` — proves the bypass is selective, not
|
|
18
|
+
// blanket.
|
|
19
|
+
// 3. **Mount + provide() smoke** (sanity, not bisect-load-bearing): the
|
|
20
|
+
// marked Provider mounts cleanly through compat-mode jsx() and its
|
|
21
|
+
// `provide()` reaches the descendant Consumer. Note: synchronous mount
|
|
22
|
+
// preserves provide() context even WITH the wrapper (provide() pushes
|
|
23
|
+
// onto the global context stack regardless), so removing the marker
|
|
24
|
+
// from a Provider in this test won't fail — the actual bug post-mark
|
|
25
|
+
// removal is multi-render-cycle (signal change re-fires the wrapper's
|
|
26
|
+
// accessor → provide() in re-run lands in stale stack). PR #427's e2e
|
|
27
|
+
// gate covers that shape end-to-end against real router state.
|
|
28
|
+
//
|
|
29
|
+
// Bisect-verified: removing the `if (isNativeCompat(type))` branch from
|
|
30
|
+
// jsx-runtime.ts causes test #1 to fail with
|
|
31
|
+
// `expected [Function wrapped] to be [Function Native]`.
|
|
32
|
+
|
|
33
|
+
function container(): HTMLElement {
|
|
34
|
+
const el = document.createElement('div')
|
|
35
|
+
document.body.appendChild(el)
|
|
36
|
+
return el
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('react-compat — nativeCompat() marker bypass', () => {
|
|
40
|
+
it('jsx() routes marked components through h() directly (no wrapper)', () => {
|
|
41
|
+
const Native = (props: { children?: unknown }) => h('div', null, props.children as never)
|
|
42
|
+
nativeCompat(Native)
|
|
43
|
+
|
|
44
|
+
const vnode = jsx(Native, {})
|
|
45
|
+
|
|
46
|
+
// Bypass: vnode.type IS the source fn, not a cached wrapper.
|
|
47
|
+
expect(vnode.type).toBe(Native)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('jsx() wraps UNMARKED components (control — bypass is selective)', () => {
|
|
51
|
+
const Unmarked = (props: { children?: unknown }) => h('div', null, props.children as never)
|
|
52
|
+
// No nativeCompat() call.
|
|
53
|
+
|
|
54
|
+
const vnode = jsx(Unmarked, {})
|
|
55
|
+
|
|
56
|
+
// Wrapper: vnode.type is the cached wrapper, NOT the source fn.
|
|
57
|
+
expect(vnode.type).not.toBe(Unmarked)
|
|
58
|
+
expect(typeof vnode.type).toBe('function')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('marked Provider mounts inside Pyreon setup frame — provide() reaches descendants', () => {
|
|
62
|
+
const Ctx = createContext<string>('default')
|
|
63
|
+
|
|
64
|
+
const Provider: ComponentFn = (props) => {
|
|
65
|
+
provide(Ctx, props.value as string)
|
|
66
|
+
return props.children as never
|
|
67
|
+
}
|
|
68
|
+
nativeCompat(Provider)
|
|
69
|
+
|
|
70
|
+
const Consumer: ComponentFn = () => {
|
|
71
|
+
const value = useContext(Ctx)
|
|
72
|
+
return h('span', { 'data-value': value }, value)
|
|
73
|
+
}
|
|
74
|
+
nativeCompat(Consumer)
|
|
75
|
+
|
|
76
|
+
const el = container()
|
|
77
|
+
mount(jsx(Provider, { value: 'native', children: jsx(Consumer, {}) }), el)
|
|
78
|
+
|
|
79
|
+
const span = el.querySelector('span')
|
|
80
|
+
// Pre-PR-3, the Provider's body would run in the compat wrapper's
|
|
81
|
+
// runUntracked accessor and `provide()` would land in a torn-down
|
|
82
|
+
// context stack. Consumer would read 'default'. Post-PR-3, the marker
|
|
83
|
+
// routes Provider through h() directly, the body runs inside Pyreon's
|
|
84
|
+
// setup frame, provide() reaches descendants. Consumer reads 'native'.
|
|
85
|
+
expect(span?.getAttribute('data-value')).toBe('native')
|
|
86
|
+
expect(span?.textContent).toBe('native')
|
|
87
|
+
})
|
|
88
|
+
})
|
package/lib/dom.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dom.js","names":[],"sources":["../src/dom.ts"],"sourcesContent":["import type { VNodeChild } from '@pyreon/core'\n/**\n * @pyreon/react-compat/dom\n *\n * Drop-in for `react-dom/client` — provides `createRoot` so you can keep\n * the same entry-point pattern as a React app.\n */\nimport { mount } from '@pyreon/runtime-dom'\n\n/**\n * Drop-in for React 18's `createRoot(container).render(element)`.\n *\n * @example\n * import { createRoot } from \"@pyreon/react-compat/dom\"\n * createRoot(document.getElementById(\"app\")!).render(<App />)\n */\nexport function createRoot(container: Element): {\n render: (element: VNodeChild) => void\n unmount: () => void\n} {\n let cleanup: (() => void) | null = null\n return {\n render(element: VNodeChild) {\n if (cleanup) cleanup()\n cleanup = mount(element, container as HTMLElement)\n },\n unmount() {\n if (cleanup) {\n cleanup()\n cleanup = null\n }\n },\n }\n}\n\n/** Alias — matches React 17's `render(element, container)` signature. */\nexport function render(element: VNodeChild, container: Element): void {\n mount(element, container as HTMLElement)\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,SAAgB,WAAW,WAGzB;CACA,IAAI,UAA+B;AACnC,QAAO;EACL,OAAO,SAAqB;AAC1B,OAAI,QAAS,UAAS;AACtB,aAAU,MAAM,SAAS,UAAyB;;EAEpD,UAAU;AACR,OAAI,SAAS;AACX,aAAS;AACT,cAAU;;;EAGf;;;AAIH,SAAgB,OAAO,SAAqB,WAA0B;AACpE,OAAM,SAAS,UAAyB"}
|
package/lib/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["pyreonCreateContext","pyreonUseContext","h"],"sources":["../src/jsx-runtime.ts","../src/index.ts"],"sourcesContent":["/**\n * Compat JSX runtime for React compatibility mode.\n *\n * When `jsxImportSource` is set to `@pyreon/react-compat` (via the vite plugin's\n * `compat: \"react\"` option), OXC rewrites JSX to import from this file:\n * <div className=\"x\" /> → jsx(\"div\", { className: \"x\" })\n *\n * For component VNodes, we wrap the component function so it returns a reactive\n * accessor — enabling React-style re-renders on state change while Pyreon's\n * existing renderer handles all DOM work.\n */\n\nimport type { ComponentFn, Props, VNode, VNodeChild } from '@pyreon/core'\nimport { Fragment, h, onUnmount } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\n\nexport { Fragment }\n\n// ─── Render context (used by hooks) ──────────────────────────────────────────\n\nexport interface RenderContext {\n hooks: unknown[]\n scheduleRerender: () => void\n /** Insertion effect entries pending execution before layout effects */\n pendingInsertionEffects: EffectEntry[]\n /** Effect entries pending execution after render */\n pendingEffects: EffectEntry[]\n /** Layout effect entries pending execution after render */\n pendingLayoutEffects: EffectEntry[]\n /** Set to true when the component is unmounted */\n unmounted: boolean\n /** Hook count from the previous render (dev-mode ordering guard) */\n _hookCount?: number\n}\n\nexport interface EffectEntry {\n fn: () => (() => void) | void\n deps: unknown[] | undefined\n cleanup: (() => void) | undefined\n}\n\nlet _currentCtx: RenderContext | null = null\nlet _hookIndex = 0\nlet _expectedHookCount = -1\n\nexport function getCurrentCtx(): RenderContext | null {\n return _currentCtx\n}\n\nexport function getHookIndex(): number {\n return _hookIndex++\n}\n\nexport function beginRender(ctx: RenderContext): void {\n _currentCtx = ctx\n _hookIndex = 0\n ctx.pendingInsertionEffects = []\n ctx.pendingEffects = []\n ctx.pendingLayoutEffects = []\n\n // On re-renders, remember the hook count from last render\n if (ctx._hookCount !== undefined) {\n _expectedHookCount = ctx._hookCount\n } else {\n _expectedHookCount = -1\n }\n}\n\nexport function endRender(): void {\n if (_currentCtx) {\n // Dev-mode: check hook count matches expected\n if (\n (import.meta as { env?: { DEV?: boolean } }).env?.DEV &&\n _expectedHookCount !== -1 &&\n _hookIndex !== _expectedHookCount\n ) {\n console.error(\n `[Pyreon] Hook count changed between renders (expected ${_expectedHookCount}, got ${_hookIndex}). ` +\n `This usually means a hook is called conditionally. Hooks must be called in the same order every render.`,\n )\n }\n _currentCtx._hookCount = _hookIndex\n }\n _currentCtx = null\n _hookIndex = 0\n}\n\n// ─── Effect runners ──────────────────────────────────────────────────────────\n\nfunction runLayoutEffects(entries: EffectEntry[]): void {\n for (const entry of entries) {\n if (entry.cleanup) entry.cleanup()\n const cleanup = entry.fn()\n entry.cleanup = typeof cleanup === 'function' ? cleanup : undefined\n }\n}\n\nfunction scheduleEffects(ctx: RenderContext, entries: EffectEntry[]): void {\n if (entries.length === 0) return\n queueMicrotask(() => {\n for (const entry of entries) {\n if (ctx.unmounted) return\n if (entry.cleanup) entry.cleanup()\n const cleanup = entry.fn()\n entry.cleanup = typeof cleanup === 'function' ? cleanup : undefined\n }\n })\n}\n\n// ─── Component wrapping ──────────────────────────────────────────────────────\n\nconst _wrapperCache = new WeakMap<Function, ComponentFn>()\n\nfunction wrapCompatComponent(reactComponent: Function): ComponentFn {\n let wrapped = _wrapperCache.get(reactComponent)\n if (wrapped) return wrapped\n\n // The wrapper returns a reactive accessor (() => VNodeChild) which Pyreon's\n // mountChild treats as a reactive expression via mountReactive.\n wrapped = ((props: Props) => {\n const ctx: RenderContext = {\n hooks: [],\n scheduleRerender: () => {\n // Will be replaced below after version signal is created\n },\n pendingInsertionEffects: [],\n pendingEffects: [],\n pendingLayoutEffects: [],\n unmounted: false,\n }\n\n const version = signal(0)\n let updateScheduled = false\n\n ctx.scheduleRerender = () => {\n if (ctx.unmounted || updateScheduled) return\n updateScheduled = true\n queueMicrotask(() => {\n updateScheduled = false\n if (!ctx.unmounted) version.set(version.peek() + 1)\n })\n }\n\n // Register cleanup for all hooks on unmount\n onUnmount(() => {\n ctx.unmounted = true\n for (const hook of ctx.hooks) {\n if (hook && typeof hook === 'object' && 'cleanup' in hook) {\n const entry = hook as EffectEntry\n if (typeof entry.cleanup === 'function') entry.cleanup()\n }\n if (hook && typeof hook === 'object' && 'unsubscribe' in hook) {\n const sub = hook as { unsubscribe?: () => void }\n if (typeof sub.unsubscribe === 'function') sub.unsubscribe()\n }\n if (hook && typeof hook === 'object' && '_contextUnsub' in hook) {\n const ctxHook = hook as { _contextUnsub?: () => void }\n if (typeof ctxHook._contextUnsub === 'function') ctxHook._contextUnsub()\n }\n }\n })\n\n // Return reactive accessor — Pyreon's mountChild calls mountReactive\n return () => {\n version() // tracked read — triggers re-execution when state changes\n beginRender(ctx)\n const result = (reactComponent as ComponentFn)(props)\n const insertionEffects = ctx.pendingInsertionEffects\n const layoutEffects = ctx.pendingLayoutEffects\n const effects = ctx.pendingEffects\n endRender()\n\n // Run in React's order: insertion → layout → passive\n runLayoutEffects(insertionEffects)\n runLayoutEffects(layoutEffects)\n scheduleEffects(ctx, effects)\n\n return result\n }\n }) as unknown as ComponentFn\n\n _wrapperCache.set(reactComponent, wrapped)\n return wrapped\n}\n\n// ─── JSX functions ───────────────────────────────────────────────────────────\n\nexport function jsx(\n type: string | ComponentFn | symbol,\n props: Props & { children?: VNodeChild | VNodeChild[] },\n key?: string | number | null,\n): VNode {\n const { children, ...rest } = props\n const propsWithKey = (key != null ? { ...rest, key } : rest) as Props\n\n if (typeof type === 'function') {\n const componentProps = children !== undefined ? { ...propsWithKey, children } : propsWithKey\n // Native Pyreon components (e.g. context Provider) skip compat wrapping\n const NATIVE = Symbol.for('pyreon:native-compat')\n if ((type as unknown as Record<symbol, boolean>)[NATIVE]) {\n return h(type as ComponentFn, componentProps)\n }\n // Wrap React-style component for re-render support\n const wrapped = wrapCompatComponent(type)\n return h(wrapped, componentProps)\n }\n\n // DOM element or symbol (Fragment): children go in vnode.children\n const childArray = children === undefined ? [] : Array.isArray(children) ? children : [children]\n\n // Map React-style attributes to standard HTML attributes\n if (typeof type === 'string') {\n if (propsWithKey.className !== undefined) {\n propsWithKey.class = propsWithKey.className\n delete propsWithKey.className\n }\n if (propsWithKey.htmlFor !== undefined) {\n propsWithKey.for = propsWithKey.htmlFor\n delete propsWithKey.htmlFor\n }\n\n // React's onChange fires on every keystroke for form elements (like onInput)\n if (\n (type === 'input' || type === 'textarea' || type === 'select') &&\n propsWithKey.onChange !== undefined\n ) {\n if (propsWithKey.onInput === undefined) {\n propsWithKey.onInput = propsWithKey.onChange\n }\n delete propsWithKey.onChange\n }\n\n // autoFocus → autofocus\n if (propsWithKey.autoFocus !== undefined) {\n propsWithKey.autofocus = propsWithKey.autoFocus\n delete propsWithKey.autoFocus\n }\n\n // defaultValue / defaultChecked → value / checked when no controlled value\n if (type === 'input' || type === 'textarea') {\n if (propsWithKey.defaultValue !== undefined && propsWithKey.value === undefined) {\n propsWithKey.value = propsWithKey.defaultValue\n delete propsWithKey.defaultValue\n }\n if (propsWithKey.defaultChecked !== undefined && propsWithKey.checked === undefined) {\n propsWithKey.checked = propsWithKey.defaultChecked\n delete propsWithKey.defaultChecked\n }\n }\n\n // Strip React-only props that have no DOM equivalent\n delete propsWithKey.suppressHydrationWarning\n delete propsWithKey.suppressContentEditableWarning\n }\n\n return h(type, propsWithKey, ...(childArray as VNodeChild[]))\n}\n\nexport const jsxs = jsx\nexport const jsxDEV = jsx\n","/**\n * @pyreon/react-compat\n *\n * Fully React-compatible hook API powered by Pyreon's reactive engine.\n *\n * Components re-render on state change — just like React. Hooks return plain\n * values and use deps arrays for memoization. Existing React code works\n * unchanged when paired with `pyreon({ compat: \"react\" })` in your vite config.\n *\n * USAGE:\n * import { useState, useEffect } from \"react\" // aliased by vite plugin\n * import { createRoot } from \"react-dom/client\" // aliased by vite plugin\n */\n\nexport type { Props, VNode, VNodeChild } from '@pyreon/core'\nexport { Fragment, h as createElement, h, createRef } from '@pyreon/core'\n\nimport type { Context, VNode, VNodeChild } from '@pyreon/core'\nimport {\n createContext as pyreonCreateContext,\n ErrorBoundary,\n h,\n Portal,\n provide as pyreonProvide,\n Suspense,\n useContext as pyreonUseContext,\n} from '@pyreon/core'\nimport { batch } from '@pyreon/reactivity'\nimport type { EffectEntry } from './jsx-runtime'\nimport { getCurrentCtx, getHookIndex } from './jsx-runtime'\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction requireCtx() {\n const ctx = getCurrentCtx()\n if (!ctx) throw new Error('Hook called outside of a component render')\n return ctx\n}\n\nfunction depsChanged(a: unknown[] | undefined, b: unknown[] | undefined): boolean {\n if (a === undefined || b === undefined) return true\n if (a.length !== b.length) return true\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return true\n }\n return false\n}\n\n// ─── State ───────────────────────────────────────────────────────────────────\n\n/**\n * React-compatible `useState` — returns `[value, setter]`.\n * Triggers a component re-render when the setter is called.\n */\nexport function useState<T>(initial: T | (() => T)): [T, (v: T | ((prev: T) => T)) => void] {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n if (ctx.hooks.length <= idx) {\n const val = typeof initial === 'function' ? (initial as () => T)() : initial\n // Store both value and a STABLE setter in one hook slot so setter identity\n // never changes across renders (React guarantee).\n const entry = { value: val, setter: null as unknown as (v: T | ((prev: T) => T)) => void }\n entry.setter = (v: T | ((prev: T) => T)) => {\n const current = entry.value\n const next = typeof v === 'function' ? (v as (prev: T) => T)(current) : v\n if (Object.is(current, next)) return\n entry.value = next\n ctx.scheduleRerender()\n }\n ctx.hooks.push(entry)\n }\n\n const entry = ctx.hooks[idx] as { value: T; setter: (v: T | ((prev: T) => T)) => void }\n return [entry.value, entry.setter]\n}\n\n// ─── Reducer ─────────────────────────────────────────────────────────────────\n\n/**\n * React-compatible `useReducer` — returns `[state, dispatch]`.\n * Supports the 3-argument form: `useReducer(reducer, initialArg, init)`.\n */\nexport function useReducer<S, A>(\n reducer: (state: S, action: A) => S,\n initialArg: S | (() => S),\n init?: (arg: S) => S,\n): [S, (action: A) => void] {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n if (ctx.hooks.length <= idx) {\n let initial: S\n if (init) {\n initial = init(initialArg as S)\n } else if (typeof initialArg === 'function') {\n initial = (initialArg as () => S)()\n } else {\n initial = initialArg\n }\n // Store both value and a STABLE dispatch in one hook slot so dispatch identity\n // never changes across renders (React guarantee).\n const entry = { value: initial, dispatch: null as unknown as (action: A) => void }\n entry.dispatch = (action: A) => {\n const current = entry.value\n const next = reducer(current, action)\n if (Object.is(current, next)) return\n entry.value = next\n ctx.scheduleRerender()\n }\n ctx.hooks.push(entry)\n }\n\n const entry = ctx.hooks[idx] as { value: S; dispatch: (action: A) => void }\n return [entry.value, entry.dispatch]\n}\n\n// ─── Effects ─────────────────────────────────────────────────────────────────\n\n/**\n * React-compatible `useEffect` — runs after render when deps change.\n * Returns cleanup on unmount and before re-running.\n */\nexport function useEffect(fn: () => (() => void) | void, deps?: unknown[]): void {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n if (ctx.hooks.length <= idx) {\n // First render — always run\n const entry: EffectEntry = { fn, deps, cleanup: undefined }\n ctx.hooks.push(entry)\n ctx.pendingEffects.push(entry)\n } else {\n const entry = ctx.hooks[idx] as EffectEntry\n if (depsChanged(entry.deps, deps)) {\n entry.fn = fn\n entry.deps = deps\n ctx.pendingEffects.push(entry)\n }\n }\n}\n\n/**\n * React-compatible `useLayoutEffect` — runs synchronously after DOM mutations.\n */\nexport function useLayoutEffect(fn: () => (() => void) | void, deps?: unknown[]): void {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n if (ctx.hooks.length <= idx) {\n const entry: EffectEntry = { fn, deps, cleanup: undefined }\n ctx.hooks.push(entry)\n ctx.pendingLayoutEffects.push(entry)\n } else {\n const entry = ctx.hooks[idx] as EffectEntry\n if (depsChanged(entry.deps, deps)) {\n entry.fn = fn\n entry.deps = deps\n ctx.pendingLayoutEffects.push(entry)\n }\n }\n}\n\n/**\n * React-compatible `useInsertionEffect` — runs synchronously before layout effects.\n * Intended for CSS-in-JS libraries to inject styles before DOM reads.\n */\nexport function useInsertionEffect(fn: () => (() => void) | void, deps?: unknown[]): void {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n if (ctx.hooks.length <= idx) {\n const entry: EffectEntry = { fn, deps, cleanup: undefined }\n ctx.hooks.push(entry)\n ctx.pendingInsertionEffects.push(entry)\n } else {\n const entry = ctx.hooks[idx] as EffectEntry\n if (depsChanged(entry.deps, deps)) {\n entry.fn = fn\n entry.deps = deps\n ctx.pendingInsertionEffects.push(entry)\n }\n }\n}\n\n// ─── Memoization ─────────────────────────────────────────────────────────────\n\n/**\n * React-compatible `useMemo` — returns the cached value, recomputed when deps change.\n */\nexport function useMemo<T>(fn: () => T, deps: unknown[]): T {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n if (ctx.hooks.length <= idx) {\n const value = fn()\n ctx.hooks.push({ value, deps })\n return value\n }\n\n const entry = ctx.hooks[idx] as { value: T; deps: unknown[] }\n if (depsChanged(entry.deps, deps)) {\n entry.value = fn()\n entry.deps = deps\n }\n return entry.value\n}\n\n/**\n * React-compatible `useCallback` — returns the cached function when deps haven't changed.\n */\nexport function useCallback<T extends (...args: never[]) => unknown>(fn: T, deps: unknown[]): T {\n return useMemo(() => fn, deps)\n}\n\n// ─── Refs ────────────────────────────────────────────────────────────────────\n\n/**\n * React-compatible `useRef` — returns `{ current }` persisted across re-renders.\n */\nexport function useRef<T>(initial?: T): { current: T | null } {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n if (ctx.hooks.length <= idx) {\n const ref = { current: initial !== undefined ? (initial as T) : null }\n ctx.hooks.push(ref)\n }\n\n return ctx.hooks[idx] as { current: T | null }\n}\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\nconst COMPAT_CTX = Symbol.for('pyreon:compat-ctx')\n\n/**\n * Compat-specific context with subscriber notification and tree-scoped nesting.\n *\n * Uses Pyreon's native context stack for tree-scoped Provider nesting (inner\n * Providers override outer ones for their subtree), with a subscriber set\n * layered on top for React-style consumer re-rendering.\n */\nexport interface CompatContext<T> {\n /** Brand marker so `useContext` can distinguish compat contexts */\n readonly [COMPAT_CTX_BRAND]: true\n /** Default value when no Provider is mounted */\n _defaultValue: T\n /** Pyreon native context for tree-scoped value+subscriber storage */\n _pyreonCtx: Context<{ value: T; subscribers: Set<() => void> }>\n /** Subscribers at the default (no-Provider) level */\n _subscribers: Set<() => void>\n /** React-compatible Provider component (native Pyreon, NOT compat-wrapped).\n * Returns a reactive accessor `() => VNodeChild` for Pyreon's renderer. */\n Provider: (props: Record<string, unknown>) => unknown\n}\n\nconst COMPAT_CTX_BRAND: typeof COMPAT_CTX = COMPAT_CTX\n\n// Tag the Provider so wrapCompatComponent skips it (it's already a native component)\nconst NATIVE_COMPONENT = Symbol.for('pyreon:native-compat')\n\n/**\n * React-compatible `createContext` — creates a context with a Provider that\n * supports nested Providers (inner overrides outer for its subtree) and\n * notifies all `useContext` consumers when its value changes.\n */\nexport function createContext<T>(defaultValue: T): CompatContext<T> {\n // Pyreon native context: each Provider pushes { value, subscribers } onto\n // the tree-scoped stack. Consumers read the nearest frame via useContext.\n const pyreonCtx = pyreonCreateContext<{ value: T; subscribers: Set<() => void> }>({\n value: defaultValue,\n subscribers: new Set(),\n })\n\n // Default-level subscribers (for consumers with no Provider above them)\n const defaultSubscribers = new Set<() => void>()\n\n // Provider is a NATIVE Pyreon component (not compat-wrapped).\n // It calls provide() once during setup to push onto the context stack,\n // then returns a reactive accessor that updates the frame value on re-render.\n const Provider = (props: Record<string, unknown>) => {\n // Setup (runs once per mount):\n const frame = { value: (props as { value: T }).value, subscribers: new Set<() => void>() }\n pyreonProvide(pyreonCtx, frame)\n\n // Return reactive accessor for children (re-evaluated when props change)\n return () => {\n const { value, children } = props as { value: T; children?: VNodeChild }\n // On re-render: update the frame value and notify subscribers\n if (!Object.is(frame.value, value)) {\n frame.value = value\n for (const sub of frame.subscribers) sub()\n }\n return children ?? null\n }\n }\n // Mark as native so jsx() doesn't wrap it with wrapCompatComponent\n ;(Provider as unknown as Record<symbol, boolean>)[NATIVE_COMPONENT] = true\n\n const ctx: CompatContext<T> = {\n [COMPAT_CTX_BRAND]: true as const,\n _defaultValue: defaultValue,\n _pyreonCtx: pyreonCtx,\n _subscribers: defaultSubscribers,\n Provider,\n }\n return ctx\n}\n\n/**\n * React-compatible `useContext` — reads the current context value and\n * subscribes the calling component to future value changes.\n *\n * Reads from Pyreon's tree-scoped context stack (correct nesting) and\n * subscribes to the nearest Provider's subscriber set for re-rendering.\n *\n * Works with both compat contexts (from this module's `createContext`) and\n * Pyreon native contexts (from `@pyreon/core`).\n */\nexport function useContext<T>(context: CompatContext<T> | Context<T>): T {\n if (COMPAT_CTX in context) {\n const compatCtx = context as CompatContext<T>\n // Read from Pyreon's tree-scoped stack (correct nesting)\n const frame = pyreonUseContext(compatCtx._pyreonCtx)\n const renderCtx = getCurrentCtx()\n if (renderCtx) {\n const idx = getHookIndex()\n if (renderCtx.hooks.length <= idx) {\n // Subscribe to the frame's subscriber set\n const sub = () => renderCtx.scheduleRerender()\n frame.subscribers.add(sub)\n renderCtx.hooks.push({ _contextUnsub: () => frame.subscribers.delete(sub) })\n }\n }\n return frame.value\n }\n return pyreonUseContext(context as Context<T>)\n}\n\n// ─── ID ──────────────────────────────────────────────────────────────────────\n\nlet _idCounter = 0\n\n/**\n * React-compatible `useId` — returns a stable unique string per hook call.\n */\nexport function useId(): string {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n if (ctx.hooks.length <= idx) {\n ctx.hooks.push(`:r${(_idCounter++).toString(36)}:`)\n }\n\n return ctx.hooks[idx] as string\n}\n\n// ─── Optimization ────────────────────────────────────────────────────────────\n\nfunction shallowEqual<P extends Record<string, unknown>>(a: P, b: P): boolean {\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n if (keysA.length !== keysB.length) return false\n for (const k of keysA) {\n if (!Object.is(a[k], b[k])) return false\n }\n return true\n}\n\n/**\n * React-compatible `memo` — wraps a component to skip re-render when props\n * are shallowly equal.\n *\n * Each component INSTANCE gets its own props/result cache via a hook slot,\n * so two `<MemoComp />` usages don't share memoization state.\n */\nexport function memo<P extends Record<string, unknown>>(\n component: (props: P) => VNodeChild,\n areEqual?: (prevProps: P, nextProps: P) => boolean,\n): (props: P) => VNodeChild {\n const compare = areEqual ?? shallowEqual\n\n const MEMO_MARKER = Symbol.for('pyreon:memo')\n\n // Fallback closure-level cache for calls outside a compat render context\n // (e.g. direct function calls in tests). Inside a render context, each\n // component instance gets its own cache via a hook slot.\n let _fallbackPrevProps: P | null = null\n let _fallbackPrevResult: VNodeChild = null\n\n const memoized = (props: P) => {\n const ctx = getCurrentCtx()\n if (ctx) {\n // Per-instance cache via hook slot\n const idx = getHookIndex()\n if (ctx.hooks.length <= idx) {\n ctx.hooks.push({ prevProps: null as P | null, prevResult: null as VNodeChild })\n }\n const cache = ctx.hooks[idx] as { prevProps: P | null; prevResult: VNodeChild }\n if (cache.prevProps !== null && compare(cache.prevProps, props)) {\n return cache.prevResult\n }\n cache.prevProps = props\n cache.prevResult = component(props)\n return cache.prevResult\n }\n // No compat context — use closure-level fallback cache\n if (_fallbackPrevProps !== null && compare(_fallbackPrevProps, props)) {\n return _fallbackPrevResult\n }\n _fallbackPrevProps = props\n _fallbackPrevResult = component(props)\n return _fallbackPrevResult\n }\n ;(memoized as unknown as Record<symbol, boolean>)[MEMO_MARKER] = true\n memoized.displayName =\n (component as unknown as { displayName?: string }).displayName || component.name || 'Memo'\n return memoized\n}\n\n/**\n * React-compatible `useTransition` — no concurrent mode in Pyreon.\n */\nexport function useTransition(): [boolean, (fn: () => void) => void] {\n return [false, (fn) => fn()]\n}\n\n/**\n * React-compatible `useDeferredValue` — returns the value as-is.\n */\nexport function useDeferredValue<T>(value: T): T {\n return value\n}\n\n// ─── Imperative handle ───────────────────────────────────────────────────────\n\n/**\n * React-compatible `useImperativeHandle`.\n */\nexport function useImperativeHandle<T>(\n ref: { current: T | null } | null | undefined,\n init: () => T,\n deps?: unknown[],\n): void {\n useLayoutEffect(() => {\n if (ref) ref.current = init()\n return () => {\n if (ref) ref.current = null\n }\n }, deps)\n}\n\n// ─── Batching ────────────────────────────────────────────────────────────────\n\nexport { batch }\n\n// ─── Portals ─────────────────────────────────────────────────────────────────\n\n/**\n * React-compatible `createPortal(children, target)`.\n */\nexport function createPortal(children: VNodeChild, target: Element): VNodeChild {\n return Portal({ target, children })\n}\n\n// ─── Suspense / lazy / ErrorBoundary ─────────────────────────────────────────\n\nexport { lazy } from '@pyreon/core'\nexport { ErrorBoundary, Suspense }\n\n// ─── forwardRef ─────────────────────────────────────────────────────────────\n\n/**\n * React-compatible `forwardRef` — pass-through in Pyreon.\n * Refs are regular props in Pyreon, so no wrapper is needed.\n * The render function receives (props, ref) — we merge ref into props.\n */\nexport function forwardRef<P extends Record<string, unknown>>(\n render: (props: P, ref: { current: unknown } | null) => VNodeChild,\n): (props: P & { ref?: { current: unknown } | null }) => VNodeChild {\n const forwarded = (props: P & { ref?: { current: unknown } | null }) => {\n const { ref, ...rest } = props\n return render(rest as P, ref ?? null)\n }\n forwarded.displayName =\n (render as unknown as { displayName?: string }).displayName || render.name || 'ForwardRef'\n return forwarded\n}\n\n// ─── cloneElement ───────────────────────────────────────────────────────────\n\n/**\n * React-compatible `cloneElement` — creates a new VNode with merged props.\n */\nexport function cloneElement(\n element: VNode,\n props?: Record<string, unknown>,\n ...children: VNodeChild[]\n): VNode {\n const mergedProps = { ...element.props, ...props }\n const mergedChildren = children.length > 0 ? children : element.children\n return h(element.type, mergedProps, ...mergedChildren)\n}\n\n// ─── Children utilities ─────────────────────────────────────────────────────\n\nfunction flattenChildren(children: VNodeChild | VNodeChild[]): VNodeChild[] {\n if (children == null) return []\n if (!Array.isArray(children)) return [children]\n const result: VNodeChild[] = []\n for (const child of children) {\n if (Array.isArray(child)) {\n result.push(...flattenChildren(child))\n } else {\n result.push(child)\n }\n }\n return result\n}\n\n/**\n * React-compatible `Children` utilities for working with VNode children.\n */\nexport const Children = {\n /**\n * Iterate over children, calling `fn` for each non-null child.\n */\n map<T>(children: VNodeChild | VNodeChild[], fn: (child: VNodeChild, index: number) => T): T[] {\n const flat = flattenChildren(children)\n const result: T[] = []\n let validIndex = 0\n for (let i = 0; i < flat.length; i++) {\n const child = flat[i]\n if (child == null || child === true || child === false) continue\n const mapped = fn(child, validIndex)\n // Assign key to mapped VNode children if they don't already have one\n if (mapped && typeof mapped === 'object' && 'type' in mapped && 'props' in mapped) {\n const vnode = mapped as unknown as VNode\n if (vnode.key == null) {\n vnode.key = `.${validIndex}`\n }\n }\n result.push(mapped)\n validIndex++\n }\n return result\n },\n\n /**\n * Call `fn` for each non-null child (no return value).\n */\n forEach(children: VNodeChild | VNodeChild[], fn: (child: VNodeChild, index: number) => void): void {\n const flat = flattenChildren(children)\n let validIndex = 0\n for (let i = 0; i < flat.length; i++) {\n const child = flat[i]\n if (child == null || child === true || child === false) continue\n fn(child, validIndex++)\n }\n },\n\n /**\n * Count non-null children.\n */\n count(children: VNodeChild | VNodeChild[]): number {\n const flat = flattenChildren(children)\n let count = 0\n for (const child of flat) {\n if (child != null && child !== true && child !== false) count++\n }\n return count\n },\n\n /**\n * Convert children to a flat array.\n */\n toArray(children: VNodeChild | VNodeChild[]): VNodeChild[] {\n const flat = flattenChildren(children)\n return flat.filter((child) => child != null && child !== true && child !== false)\n },\n\n /**\n * Assert and return the only child. Throws if not exactly one child.\n */\n only(children: VNodeChild | VNodeChild[]): VNodeChild {\n const arr = Children.toArray(children)\n if (arr.length !== 1) {\n throw new Error('[Pyreon] Children.only expected exactly one child')\n }\n return arr[0] as VNodeChild\n },\n}\n\n// ─── useSyncExternalStore ───────────────────────────────────────────────────\n\n/**\n * React-compatible `useSyncExternalStore` — subscribes to an external store.\n * Re-subscribes automatically when the `subscribe` function identity changes.\n */\nexport function useSyncExternalStore<T>(\n subscribe: (onStoreChange: () => void) => () => void,\n getSnapshot: () => T,\n getServerSnapshot?: () => T,\n): T {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n // SSR path\n if (typeof window === 'undefined' && getServerSnapshot) {\n if (ctx.hooks.length <= idx) {\n ctx.hooks.push({ subscribe, unsubscribe: undefined, snapshot: getServerSnapshot() })\n }\n return (ctx.hooks[idx] as { snapshot: T }).snapshot\n }\n\n if (ctx.hooks.length <= idx) {\n const snapshot = getSnapshot()\n const entry = {\n subscribe,\n unsubscribe: undefined as (() => void) | undefined,\n snapshot,\n }\n const onChange = () => {\n const next = getSnapshot()\n if (!Object.is(entry.snapshot, next)) {\n entry.snapshot = next\n ctx.scheduleRerender()\n }\n }\n entry.unsubscribe = subscribe(onChange)\n ctx.hooks.push(entry)\n return snapshot\n }\n\n const entry = ctx.hooks[idx] as {\n subscribe: typeof subscribe\n unsubscribe: (() => void) | undefined\n snapshot: T\n }\n\n // Re-subscribe if subscribe function identity changed\n if (entry.subscribe !== subscribe) {\n if (entry.unsubscribe) entry.unsubscribe()\n const onChange = () => {\n const next = getSnapshot()\n if (!Object.is(entry.snapshot, next)) {\n entry.snapshot = next\n ctx.scheduleRerender()\n }\n }\n entry.unsubscribe = subscribe(onChange)\n entry.subscribe = subscribe\n }\n\n // Always read fresh snapshot during render\n entry.snapshot = getSnapshot()\n return entry.snapshot\n}\n\n// ─── use ────────────────────────────────────────────────────────────────────\n\nconst _promiseCache = new WeakMap<\n Promise<unknown>,\n { status: 'pending' | 'resolved' | 'rejected'; value?: unknown; error?: unknown }\n>()\n\n/**\n * React-compatible `use` — reads a Context or suspends on a Promise.\n * Can be called conditionally (unlike other hooks).\n *\n * IMPORTANT: Promises must have a stable identity across renders.\n * Create promises outside the component or memoize them. Calling\n * `use(fetch('/api'))` creates a new promise each render and will\n * cause infinite suspension.\n */\nexport function use<T>(resource: Context<T> | CompatContext<T> | Promise<T>): T {\n // Compat context path\n if (resource && typeof resource === 'object' && COMPAT_CTX in resource) {\n return useContext(resource as CompatContext<T>)\n }\n // Pyreon native context path\n if (resource && typeof resource === 'object' && 'id' in resource && 'defaultValue' in resource) {\n return pyreonUseContext(resource as Context<T>)\n }\n // Promise path — suspend via throw\n const promise = resource as Promise<T>\n let entry = _promiseCache.get(promise)\n if (!entry) {\n entry = { status: 'pending' }\n _promiseCache.set(promise, entry)\n promise.then(\n (value) => {\n entry!.status = 'resolved'\n entry!.value = value\n },\n (error) => {\n entry!.status = 'rejected'\n entry!.error = error\n },\n )\n }\n if (entry.status === 'resolved') return entry.value as T\n if (entry.status === 'rejected') throw entry.error\n throw promise // Suspense catches this\n}\n\n// ─── useActionState ─────────────────────────────────────────────────────────\n\n/**\n * React-compatible `useActionState` — manages async action state with pending indicator.\n */\nexport function useActionState<S, P>(\n action: (state: S, payload: P) => S | Promise<S>,\n initialState: S,\n): [S, (payload: P) => void, boolean] {\n const [state, setState] = useState(initialState)\n const [isPending, setIsPending] = useState(false)\n\n const dispatch = (payload: P) => {\n setIsPending(true)\n const result = action(state, payload)\n if (result instanceof Promise) {\n result.then((next) => {\n setState(next)\n setIsPending(false)\n })\n } else {\n setState(result)\n setIsPending(false)\n }\n }\n\n return [state, dispatch, isPending]\n}\n\n// ─── startTransition ────────────────────────────────────────────────────────\n\n/**\n * React-compatible `startTransition` — runs the callback synchronously.\n * No concurrent mode in Pyreon, so transitions are immediate.\n */\nexport function startTransition(fn: () => void): void {\n fn()\n}\n\n// ─── isValidElement ─────────────────────────────────────────────────────────\n\n/**\n * React-compatible `isValidElement` — checks if a value is a VNode.\n */\nexport function isValidElement(value: unknown): value is VNode {\n return value != null && typeof value === 'object' && 'type' in value && 'props' in value\n}\n\n// ─── useDebugValue ──────────────────────────────────────────────────────────\n\n/**\n * React-compatible `useDebugValue` — no-op in Pyreon (no React DevTools integration).\n */\nexport function useDebugValue<T>(_value: T, _format?: (v: T) => unknown): void {}\n\n// ─── flushSync ──────────────────────────────────────────────────────────────\n\n/**\n * React-compatible `flushSync` — runs the callback synchronously.\n *\n * BEHAVIORAL DIFFERENCE: In Pyreon's compat model, state updates are\n * batched via microtask. flushSync runs the callback and returns its\n * result, but the DOM updates triggered by state changes inside the\n * callback still fire asynchronously. For DOM measurement after state\n * updates, use `await act(() => setState(...))` in tests, or\n * `requestAnimationFrame` in production code.\n */\nexport function flushSync<T>(fn: () => T): T {\n return fn()\n}\n\n// ─── act (testing) ──────────────────────────────────────────────────────────\n\n/**\n * React-compatible `act` — flushes pending microtasks for testing.\n */\nexport async function act(fn: () => void | Promise<void>): Promise<void> {\n const result = fn()\n if (result instanceof Promise) await result\n // Flush two rounds of microtasks to drain pending effects and rerenders\n await new Promise<void>((r) => queueMicrotask(r))\n await new Promise<void>((r) => queueMicrotask(r))\n}\n\n// ─── version ────────────────────────────────────────────────────────────────\n\nexport const version = '19.0.0-pyreon'\n\n// ─── StrictMode / Profiler ──────────────────────────────────────────────────\n\n/**\n * React-compatible `StrictMode` — pass-through in Pyreon (no double-invoke behavior).\n */\nexport function StrictMode(props: { children?: VNodeChild }): VNodeChild {\n return props.children ?? null\n}\n\n/**\n * React-compatible `Profiler` — pass-through in Pyreon (no profiling integration).\n */\nexport function Profiler(props: {\n id: string\n onRender?: (...args: unknown[]) => void\n children?: VNodeChild\n}): VNodeChild {\n return props.children ?? null\n}\n\n// ─── Component / PureComponent (class stubs) ────────────────────────────────\n\n/**\n * React-compatible `Component` class stub.\n * Class components are not fully supported — use function components with hooks.\n */\nexport class Component<P = Record<string, unknown>, S = Record<string, unknown>> {\n props: Readonly<P>\n state: Readonly<S>\n\n constructor(props: P) {\n this.props = props\n this.state = {} as S\n }\n\n setState(_partial: Partial<S> | ((prev: S) => Partial<S>)): void {\n console.warn(\n '[Pyreon] Class component setState is not supported. Use function components with hooks.',\n )\n }\n\n forceUpdate(): void {\n console.warn(\n '[Pyreon] Class component forceUpdate is not supported. Use function components with hooks.',\n )\n }\n\n render(): VNodeChild {\n return null\n }\n}\n\n/**\n * React-compatible `PureComponent` class stub.\n */\nexport class PureComponent<\n P = Record<string, unknown>,\n S = Record<string, unknown>,\n> extends Component<P, S> {}\n\n// ─── React-compatible type exports ──────────────────────────────────────────\n\nexport type { Context }\n\nexport type FC<P = Record<string, unknown>> = (props: P) => VNodeChild\nexport type FunctionComponent<P = Record<string, unknown>> = FC<P>\nexport type ReactElement = VNode\nexport type ReactNode = VNodeChild\nexport type JSXElementConstructor<P> = (props: P) => VNodeChild\nexport type Dispatch<A> = (action: A) => void\nexport type SetStateAction<S> = S | ((prev: S) => S)\nexport type RefObject<T> = { readonly current: T | null }\nexport type MutableRefObject<T> = { current: T }\nexport type RefCallback<T> = (instance: T | null) => void\nexport type ForwardedRef<T> = RefObject<T> | RefCallback<T> | null\nexport type PropsWithChildren<P = Record<string, unknown>> = P & { children?: ReactNode }\nexport type PropsWithRef<P> = P & { ref?: RefObject<unknown> | RefCallback<unknown> | null }\nexport type { CSSProperties } from '@pyreon/core'\n\n// Event types — aliases for TargetedEvent patterns\nexport type SyntheticEvent<T = Element> = Event & { currentTarget: T }\nexport type ChangeEvent<T = Element> = Event & { currentTarget: T; target: T }\nexport type FormEvent<T = Element> = Event & { currentTarget: T }\nexport type MouseEvent<T = Element> = globalThis.MouseEvent & { currentTarget: T }\nexport type KeyboardEvent<T = Element> = globalThis.KeyboardEvent & { currentTarget: T }\nexport type FocusEvent<T = Element> = globalThis.FocusEvent & { currentTarget: T }\nexport type DragEvent<T = Element> = globalThis.DragEvent & { currentTarget: T }\nexport type PointerEvent<T = Element> = globalThis.PointerEvent & { currentTarget: T }\nexport type TouchEvent<T = Element> = globalThis.TouchEvent & { currentTarget: T }\nexport type ClipboardEvent<T = Element> = globalThis.ClipboardEvent & { currentTarget: T }\nexport type AnimationEvent<T = Element> = globalThis.AnimationEvent & { currentTarget: T }\nexport type TransitionEvent<T = Element> = globalThis.TransitionEvent & { currentTarget: T }\nexport type WheelEvent<T = Element> = globalThis.WheelEvent & { currentTarget: T }\n\n// HTML attribute types\nexport type HTMLAttributes<T = HTMLElement> = Record<string, unknown> & {\n ref?: RefObject<T> | RefCallback<T> | null\n}\nexport type InputHTMLAttributes<T = HTMLInputElement> = HTMLAttributes<T>\nexport type TextareaHTMLAttributes<T = HTMLTextAreaElement> = HTMLAttributes<T>\nexport type SelectHTMLAttributes<T = HTMLSelectElement> = HTMLAttributes<T>\nexport type ButtonHTMLAttributes<T = HTMLButtonElement> = HTMLAttributes<T>\nexport type AnchorHTMLAttributes<T = HTMLAnchorElement> = HTMLAttributes<T>\nexport type FormHTMLAttributes<T = HTMLFormElement> = HTMLAttributes<T>\nexport type ImgHTMLAttributes<T = HTMLImageElement> = HTMLAttributes<T>\nexport type SVGAttributes<T = SVGElement> = HTMLAttributes<T>\n"],"mappings":"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"}
|
package/lib/jsx-runtime.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-runtime.js","names":[],"sources":["../src/jsx-runtime.ts"],"sourcesContent":["/**\n * Compat JSX runtime for React compatibility mode.\n *\n * When `jsxImportSource` is set to `@pyreon/react-compat` (via the vite plugin's\n * `compat: \"react\"` option), OXC rewrites JSX to import from this file:\n * <div className=\"x\" /> → jsx(\"div\", { className: \"x\" })\n *\n * For component VNodes, we wrap the component function so it returns a reactive\n * accessor — enabling React-style re-renders on state change while Pyreon's\n * existing renderer handles all DOM work.\n */\n\nimport type { ComponentFn, Props, VNode, VNodeChild } from '@pyreon/core'\nimport { Fragment, h, onUnmount } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\n\nexport { Fragment }\n\n// ─── Render context (used by hooks) ──────────────────────────────────────────\n\nexport interface RenderContext {\n hooks: unknown[]\n scheduleRerender: () => void\n /** Insertion effect entries pending execution before layout effects */\n pendingInsertionEffects: EffectEntry[]\n /** Effect entries pending execution after render */\n pendingEffects: EffectEntry[]\n /** Layout effect entries pending execution after render */\n pendingLayoutEffects: EffectEntry[]\n /** Set to true when the component is unmounted */\n unmounted: boolean\n /** Hook count from the previous render (dev-mode ordering guard) */\n _hookCount?: number\n}\n\nexport interface EffectEntry {\n fn: () => (() => void) | void\n deps: unknown[] | undefined\n cleanup: (() => void) | undefined\n}\n\nlet _currentCtx: RenderContext | null = null\nlet _hookIndex = 0\nlet _expectedHookCount = -1\n\nexport function getCurrentCtx(): RenderContext | null {\n return _currentCtx\n}\n\nexport function getHookIndex(): number {\n return _hookIndex++\n}\n\nexport function beginRender(ctx: RenderContext): void {\n _currentCtx = ctx\n _hookIndex = 0\n ctx.pendingInsertionEffects = []\n ctx.pendingEffects = []\n ctx.pendingLayoutEffects = []\n\n // On re-renders, remember the hook count from last render\n if (ctx._hookCount !== undefined) {\n _expectedHookCount = ctx._hookCount\n } else {\n _expectedHookCount = -1\n }\n}\n\nexport function endRender(): void {\n if (_currentCtx) {\n // Dev-mode: check hook count matches expected\n if (\n (import.meta as { env?: { DEV?: boolean } }).env?.DEV &&\n _expectedHookCount !== -1 &&\n _hookIndex !== _expectedHookCount\n ) {\n console.error(\n `[Pyreon] Hook count changed between renders (expected ${_expectedHookCount}, got ${_hookIndex}). ` +\n `This usually means a hook is called conditionally. Hooks must be called in the same order every render.`,\n )\n }\n _currentCtx._hookCount = _hookIndex\n }\n _currentCtx = null\n _hookIndex = 0\n}\n\n// ─── Effect runners ──────────────────────────────────────────────────────────\n\nfunction runLayoutEffects(entries: EffectEntry[]): void {\n for (const entry of entries) {\n if (entry.cleanup) entry.cleanup()\n const cleanup = entry.fn()\n entry.cleanup = typeof cleanup === 'function' ? cleanup : undefined\n }\n}\n\nfunction scheduleEffects(ctx: RenderContext, entries: EffectEntry[]): void {\n if (entries.length === 0) return\n queueMicrotask(() => {\n for (const entry of entries) {\n if (ctx.unmounted) return\n if (entry.cleanup) entry.cleanup()\n const cleanup = entry.fn()\n entry.cleanup = typeof cleanup === 'function' ? cleanup : undefined\n }\n })\n}\n\n// ─── Component wrapping ──────────────────────────────────────────────────────\n\nconst _wrapperCache = new WeakMap<Function, ComponentFn>()\n\nfunction wrapCompatComponent(reactComponent: Function): ComponentFn {\n let wrapped = _wrapperCache.get(reactComponent)\n if (wrapped) return wrapped\n\n // The wrapper returns a reactive accessor (() => VNodeChild) which Pyreon's\n // mountChild treats as a reactive expression via mountReactive.\n wrapped = ((props: Props) => {\n const ctx: RenderContext = {\n hooks: [],\n scheduleRerender: () => {\n // Will be replaced below after version signal is created\n },\n pendingInsertionEffects: [],\n pendingEffects: [],\n pendingLayoutEffects: [],\n unmounted: false,\n }\n\n const version = signal(0)\n let updateScheduled = false\n\n ctx.scheduleRerender = () => {\n if (ctx.unmounted || updateScheduled) return\n updateScheduled = true\n queueMicrotask(() => {\n updateScheduled = false\n if (!ctx.unmounted) version.set(version.peek() + 1)\n })\n }\n\n // Register cleanup for all hooks on unmount\n onUnmount(() => {\n ctx.unmounted = true\n for (const hook of ctx.hooks) {\n if (hook && typeof hook === 'object' && 'cleanup' in hook) {\n const entry = hook as EffectEntry\n if (typeof entry.cleanup === 'function') entry.cleanup()\n }\n if (hook && typeof hook === 'object' && 'unsubscribe' in hook) {\n const sub = hook as { unsubscribe?: () => void }\n if (typeof sub.unsubscribe === 'function') sub.unsubscribe()\n }\n if (hook && typeof hook === 'object' && '_contextUnsub' in hook) {\n const ctxHook = hook as { _contextUnsub?: () => void }\n if (typeof ctxHook._contextUnsub === 'function') ctxHook._contextUnsub()\n }\n }\n })\n\n // Return reactive accessor — Pyreon's mountChild calls mountReactive\n return () => {\n version() // tracked read — triggers re-execution when state changes\n beginRender(ctx)\n const result = (reactComponent as ComponentFn)(props)\n const insertionEffects = ctx.pendingInsertionEffects\n const layoutEffects = ctx.pendingLayoutEffects\n const effects = ctx.pendingEffects\n endRender()\n\n // Run in React's order: insertion → layout → passive\n runLayoutEffects(insertionEffects)\n runLayoutEffects(layoutEffects)\n scheduleEffects(ctx, effects)\n\n return result\n }\n }) as unknown as ComponentFn\n\n _wrapperCache.set(reactComponent, wrapped)\n return wrapped\n}\n\n// ─── JSX functions ───────────────────────────────────────────────────────────\n\nexport function jsx(\n type: string | ComponentFn | symbol,\n props: Props & { children?: VNodeChild | VNodeChild[] },\n key?: string | number | null,\n): VNode {\n const { children, ...rest } = props\n const propsWithKey = (key != null ? { ...rest, key } : rest) as Props\n\n if (typeof type === 'function') {\n const componentProps = children !== undefined ? { ...propsWithKey, children } : propsWithKey\n // Native Pyreon components (e.g. context Provider) skip compat wrapping\n const NATIVE = Symbol.for('pyreon:native-compat')\n if ((type as unknown as Record<symbol, boolean>)[NATIVE]) {\n return h(type as ComponentFn, componentProps)\n }\n // Wrap React-style component for re-render support\n const wrapped = wrapCompatComponent(type)\n return h(wrapped, componentProps)\n }\n\n // DOM element or symbol (Fragment): children go in vnode.children\n const childArray = children === undefined ? [] : Array.isArray(children) ? children : [children]\n\n // Map React-style attributes to standard HTML attributes\n if (typeof type === 'string') {\n if (propsWithKey.className !== undefined) {\n propsWithKey.class = propsWithKey.className\n delete propsWithKey.className\n }\n if (propsWithKey.htmlFor !== undefined) {\n propsWithKey.for = propsWithKey.htmlFor\n delete propsWithKey.htmlFor\n }\n\n // React's onChange fires on every keystroke for form elements (like onInput)\n if (\n (type === 'input' || type === 'textarea' || type === 'select') &&\n propsWithKey.onChange !== undefined\n ) {\n if (propsWithKey.onInput === undefined) {\n propsWithKey.onInput = propsWithKey.onChange\n }\n delete propsWithKey.onChange\n }\n\n // autoFocus → autofocus\n if (propsWithKey.autoFocus !== undefined) {\n propsWithKey.autofocus = propsWithKey.autoFocus\n delete propsWithKey.autoFocus\n }\n\n // defaultValue / defaultChecked → value / checked when no controlled value\n if (type === 'input' || type === 'textarea') {\n if (propsWithKey.defaultValue !== undefined && propsWithKey.value === undefined) {\n propsWithKey.value = propsWithKey.defaultValue\n delete propsWithKey.defaultValue\n }\n if (propsWithKey.defaultChecked !== undefined && propsWithKey.checked === undefined) {\n propsWithKey.checked = propsWithKey.defaultChecked\n delete propsWithKey.defaultChecked\n }\n }\n\n // Strip React-only props that have no DOM equivalent\n delete propsWithKey.suppressHydrationWarning\n delete propsWithKey.suppressContentEditableWarning\n }\n\n return h(type, propsWithKey, ...(childArray as VNodeChild[]))\n}\n\nexport const jsxs = jsx\nexport const jsxDEV = jsx\n"],"mappings":"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"}
|
package/lib/types/dom.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dom2.d.ts","names":[],"sources":["../../../src/dom.ts"],"mappings":";;;;;AAgBA;;;;;iBAAgB,UAAA,CAAW,SAAA,EAAW,OAAA;EACpC,MAAA,GAAS,OAAA,EAAS,UAAA;EAClB,OAAA;AAAA;;iBAkBc,MAAA,CAAO,OAAA,EAAS,UAAA,EAAY,SAAA,EAAW,OAAA"}
|
package/lib/types/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/index.ts"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-runtime2.d.ts","names":[],"sources":["../../../src/jsx-runtime.ts"],"mappings":";;;iBA2LgB,GAAA,CACd,IAAA,WAAe,WAAA,WACf,KAAA,EAAO,KAAA;EAAU,QAAA,GAAW,UAAA,GAAa,UAAA;AAAA,GACzC,GAAA,4BACC,KAAA;AAAA,cAmEU,IAAA,SAAI,GAAA"}
|