@pyreon/preact-compat 0.13.1 → 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/hooks.js.html +1 -1
- package/lib/analysis/index.js.html +1 -1
- package/lib/analysis/jsx-runtime.js.html +1 -1
- package/lib/analysis/signals.js.html +1 -1
- package/lib/hooks.js +125 -36
- package/lib/index.js +23 -5
- package/lib/jsx-runtime.js +97 -6
- package/lib/signals.js +2 -2
- package/lib/types/hooks.d.ts +49 -2
- package/lib/types/index.d.ts +20 -3
- package/package.json +8 -4
- package/src/hooks.ts +157 -42
- package/src/index.ts +48 -4
- package/src/jsx-runtime.ts +145 -2
- package/src/preact-compat.browser.test.ts +37 -0
- package/src/signals.ts +2 -2
- package/src/tests/native-marker-bypass.test.tsx +63 -0
- package/src/tests/new-apis.test.ts +1084 -0
- package/lib/hooks.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/jsx-runtime.js.map +0 -1
- package/lib/signals.js.map +0 -1
- package/lib/types/hooks.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/lib/types/signals.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -124,3 +124,23 @@ Preact Signals API (mirrors `@preact/signals`).
|
|
|
124
124
|
- **`computed(fn)`** -- returns `{ value }` read-only accessor.
|
|
125
125
|
- **`effect(fn)`** -- reactive side effect, returns dispose function.
|
|
126
126
|
- **`batch(fn)`** -- coalesce multiple signal writes.
|
|
127
|
+
|
|
128
|
+
## Composing Pyreon framework components inside preact-compat
|
|
129
|
+
|
|
130
|
+
Pyreon's framework components (`RouterView`, `PyreonUI`, `FormProvider`, `QueryClientProvider`, …) ship marked with `nativeCompat()` from `@pyreon/core` — preact-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.
|
|
131
|
+
|
|
132
|
+
If you write your **own** Pyreon-flavored helper that uses `provide()` / `onMount()` / `onUnmount()` / `effect()` at component-body scope and use it in a preact-compat app, mark it explicitly:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { nativeCompat, provide, createContext } from '@pyreon/core'
|
|
136
|
+
|
|
137
|
+
const MyCtx = createContext<string>('default')
|
|
138
|
+
|
|
139
|
+
function MyProvider(props: { value: string; children?: unknown }) {
|
|
140
|
+
provide(MyCtx, props.value)
|
|
141
|
+
return props.children as never
|
|
142
|
+
}
|
|
143
|
+
nativeCompat(MyProvider) // ← required for compat-mode apps
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
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":"hooks.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"hooks.js","children":[{"name":"src","children":[{"uid":"3bf7c0f6-1","name":"jsx-runtime.ts"},{"uid":"3bf7c0f6-3","name":"hooks.ts"}]}]}],"isRoot":true},"nodeParts":{"3bf7c0f6-1":{"renderedLength":186,"gzipLength":138,"brotliLength":0,"metaUid":"3bf7c0f6-0"},"3bf7c0f6-3":{"renderedLength":7168,"gzipLength":2046,"brotliLength":0,"metaUid":"3bf7c0f6-2"}},"nodeMetas":{"3bf7c0f6-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"hooks.js":"3bf7c0f6-1"},"imported":[{"uid":"3bf7c0f6-4"},{"uid":"3bf7c0f6-5"}],"importedBy":[{"uid":"3bf7c0f6-2"}]},"3bf7c0f6-2":{"id":"/src/hooks.ts","moduleParts":{"hooks.js":"3bf7c0f6-3"},"imported":[{"uid":"3bf7c0f6-4"},{"uid":"3bf7c0f6-0"}],"importedBy":[],"isEntry":true},"3bf7c0f6-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"3bf7c0f6-2"},{"uid":"3bf7c0f6-0"}]},"3bf7c0f6-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"3bf7c0f6-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":"index.js","children":[{"name":"src/index.ts","uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src/index.ts","uid":"2f430366-1"}]}],"isRoot":true},"nodeParts":{"2f430366-1":{"renderedLength":3276,"gzipLength":1311,"brotliLength":0,"metaUid":"2f430366-0"}},"nodeMetas":{"2f430366-0":{"id":"/src/index.ts","moduleParts":{"index.js":"2f430366-1"},"imported":[{"uid":"2f430366-2"},{"uid":"2f430366-3"},{"uid":"2f430366-4"}],"importedBy":[],"isEntry":true},"2f430366-2":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"2f430366-0"}]},"2f430366-3":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"2f430366-0"}]},"2f430366-4":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"2f430366-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":"86b5580e-1","name":"jsx-runtime.ts"},{"uid":"86b5580e-3","name":"jsx-dev-runtime.ts"}]}]}],"isRoot":true},"nodeParts":{"86b5580e-1":{"renderedLength":5335,"gzipLength":1477,"brotliLength":0,"metaUid":"86b5580e-0"},"86b5580e-3":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"86b5580e-2"}},"nodeMetas":{"86b5580e-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"jsx-runtime.js":"86b5580e-1"},"imported":[{"uid":"86b5580e-4"},{"uid":"86b5580e-5"}],"importedBy":[{"uid":"86b5580e-2"}]},"86b5580e-2":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-runtime.js":"86b5580e-3"},"imported":[{"uid":"86b5580e-0"}],"importedBy":[],"isEntry":true},"86b5580e-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"86b5580e-0"}]},"86b5580e-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"86b5580e-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":"signals.js","children":[{"name":"src/signals.ts","uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"signals.js","children":[{"name":"src/signals.ts","uid":"660af5f2-1"}]}],"isRoot":true},"nodeParts":{"660af5f2-1":{"renderedLength":876,"gzipLength":383,"brotliLength":0,"metaUid":"660af5f2-0"}},"nodeMetas":{"660af5f2-0":{"id":"/src/signals.ts","moduleParts":{"signals.js":"660af5f2-1"},"imported":[{"uid":"660af5f2-2"}],"importedBy":[],"isEntry":true},"660af5f2-2":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"660af5f2-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/hooks.js
CHANGED
|
@@ -23,23 +23,38 @@ function depsChanged(a, b) {
|
|
|
23
23
|
for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return true;
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
|
+
function shallowEqual(a, b) {
|
|
27
|
+
const keysA = Object.keys(a);
|
|
28
|
+
const keysB = Object.keys(b);
|
|
29
|
+
if (keysA.length !== keysB.length) return false;
|
|
30
|
+
for (const k of keysA) if (!Object.is(a[k], b[k])) return false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
26
33
|
/**
|
|
27
34
|
* Preact-compatible `useState` — returns `[value, setter]`.
|
|
28
35
|
* Triggers a component re-render when the setter is called.
|
|
36
|
+
*
|
|
37
|
+
* The setter has stable identity across renders (same reference every time).
|
|
29
38
|
*/
|
|
30
39
|
function useState(initial) {
|
|
31
40
|
const ctx = requireCtx();
|
|
32
41
|
const idx = getHookIndex();
|
|
33
|
-
if (ctx.hooks.length <= idx)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
if (ctx.hooks.length <= idx) {
|
|
43
|
+
const entry = {
|
|
44
|
+
value: typeof initial === "function" ? initial() : initial,
|
|
45
|
+
setter: null
|
|
46
|
+
};
|
|
47
|
+
entry.setter = (v) => {
|
|
48
|
+
const current = entry.value;
|
|
49
|
+
const next = typeof v === "function" ? v(current) : v;
|
|
50
|
+
if (Object.is(current, next)) return;
|
|
51
|
+
entry.value = next;
|
|
52
|
+
ctx.scheduleRerender();
|
|
53
|
+
};
|
|
54
|
+
ctx.hooks.push(entry);
|
|
55
|
+
}
|
|
56
|
+
const entry = ctx.hooks[idx];
|
|
57
|
+
return [entry.value, entry.setter];
|
|
43
58
|
}
|
|
44
59
|
/**
|
|
45
60
|
* Preact-compatible `useEffect` — runs after render when deps change.
|
|
@@ -129,20 +144,33 @@ function useRef(initial) {
|
|
|
129
144
|
}
|
|
130
145
|
/**
|
|
131
146
|
* Preact-compatible `useReducer` — returns `[state, dispatch]`.
|
|
147
|
+
* Supports the 3-argument form: `useReducer(reducer, initialArg, init)`.
|
|
148
|
+
*
|
|
149
|
+
* Dispatch has stable identity across renders (same reference every time).
|
|
132
150
|
*/
|
|
133
|
-
function useReducer(reducer,
|
|
151
|
+
function useReducer(reducer, initialArg, init) {
|
|
134
152
|
const ctx = requireCtx();
|
|
135
153
|
const idx = getHookIndex();
|
|
136
|
-
if (ctx.hooks.length <= idx)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
if (ctx.hooks.length <= idx) {
|
|
155
|
+
let initial;
|
|
156
|
+
if (init) initial = init(initialArg);
|
|
157
|
+
else if (typeof initialArg === "function") initial = initialArg();
|
|
158
|
+
else initial = initialArg;
|
|
159
|
+
const entry = {
|
|
160
|
+
value: initial,
|
|
161
|
+
dispatch: null
|
|
162
|
+
};
|
|
163
|
+
entry.dispatch = (action) => {
|
|
164
|
+
const current = entry.value;
|
|
165
|
+
const next = reducer(current, action);
|
|
166
|
+
if (Object.is(current, next)) return;
|
|
167
|
+
entry.value = next;
|
|
168
|
+
ctx.scheduleRerender();
|
|
169
|
+
};
|
|
170
|
+
ctx.hooks.push(entry);
|
|
171
|
+
}
|
|
172
|
+
const entry = ctx.hooks[idx];
|
|
173
|
+
return [entry.value, entry.dispatch];
|
|
146
174
|
}
|
|
147
175
|
let _idCounter = 0;
|
|
148
176
|
/**
|
|
@@ -157,25 +185,86 @@ function useId() {
|
|
|
157
185
|
/**
|
|
158
186
|
* Preact-compatible `memo` — wraps a component to skip re-render when props
|
|
159
187
|
* are shallowly equal.
|
|
188
|
+
*
|
|
189
|
+
* Each component INSTANCE gets its own props/result cache via a hook slot,
|
|
190
|
+
* so two `<MemoComp />` usages don't share memoization state.
|
|
160
191
|
*/
|
|
161
192
|
function memo(component, areEqual) {
|
|
162
|
-
const compare = areEqual ??
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
const compare = areEqual ?? shallowEqual;
|
|
194
|
+
let _fallbackPrevProps = null;
|
|
195
|
+
let _fallbackPrevResult = null;
|
|
196
|
+
const memoized = (props) => {
|
|
197
|
+
const ctx = getCurrentCtx();
|
|
198
|
+
if (ctx) {
|
|
199
|
+
const idx = getHookIndex();
|
|
200
|
+
if (ctx.hooks.length <= idx) ctx.hooks.push({
|
|
201
|
+
prevProps: null,
|
|
202
|
+
prevResult: null
|
|
203
|
+
});
|
|
204
|
+
const cache = ctx.hooks[idx];
|
|
205
|
+
if (cache.prevProps !== null && compare(cache.prevProps, props)) return cache.prevResult;
|
|
206
|
+
cache.prevProps = props;
|
|
207
|
+
cache.prevResult = component(props);
|
|
208
|
+
return cache.prevResult;
|
|
209
|
+
}
|
|
210
|
+
if (_fallbackPrevProps !== null && compare(_fallbackPrevProps, props)) return _fallbackPrevResult;
|
|
211
|
+
_fallbackPrevProps = props;
|
|
212
|
+
_fallbackPrevResult = component(props);
|
|
213
|
+
return _fallbackPrevResult;
|
|
214
|
+
};
|
|
215
|
+
memoized.displayName = component.displayName || component.name || "Memo";
|
|
216
|
+
return memoized;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Preact-compatible `forwardRef` — pass-through in Pyreon.
|
|
220
|
+
* Refs are regular props in Pyreon, so no wrapper is needed.
|
|
221
|
+
* The render function receives (props, ref) — we merge ref into props.
|
|
222
|
+
*/
|
|
223
|
+
function forwardRef(render) {
|
|
224
|
+
const forwarded = (props) => {
|
|
225
|
+
const { ref, ...rest } = props;
|
|
226
|
+
return render(rest, ref ?? null);
|
|
176
227
|
};
|
|
228
|
+
forwarded.displayName = render.displayName || render.name || "ForwardRef";
|
|
229
|
+
return forwarded;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Preact-compatible `useImperativeHandle`.
|
|
233
|
+
*/
|
|
234
|
+
function useImperativeHandle(ref, init, deps) {
|
|
235
|
+
useLayoutEffect(() => {
|
|
236
|
+
if (ref) ref.current = init();
|
|
237
|
+
return () => {
|
|
238
|
+
if (ref) ref.current = null;
|
|
239
|
+
};
|
|
240
|
+
}, deps);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Preact-compatible `useDebugValue` — no-op in Pyreon (no Preact DevTools integration).
|
|
244
|
+
*/
|
|
245
|
+
function useDebugValue(_value, _format) {}
|
|
246
|
+
/**
|
|
247
|
+
* Preact-compatible `useTransition` — returns `[isPending, startTransition]`.
|
|
248
|
+
*
|
|
249
|
+
* In Pyreon's signal-based reactivity there is no concept of concurrent
|
|
250
|
+
* rendering lanes. The callback is executed synchronously and `isPending`
|
|
251
|
+
* is always `false`. This shim exists so Preact/React code that uses
|
|
252
|
+
* `useTransition` compiles and runs without changes.
|
|
253
|
+
*/
|
|
254
|
+
function useTransition() {
|
|
255
|
+
return [false, (fn) => fn()];
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Preact-compatible `useDeferredValue` — returns the value as-is.
|
|
259
|
+
*
|
|
260
|
+
* In Pyreon's signal-based reactivity there are no concurrent rendering lanes,
|
|
261
|
+
* so the value is never "deferred". This shim exists so Preact/React code that
|
|
262
|
+
* uses `useDeferredValue` compiles and runs without changes.
|
|
263
|
+
*/
|
|
264
|
+
function useDeferredValue(value) {
|
|
265
|
+
return value;
|
|
177
266
|
}
|
|
178
267
|
|
|
179
268
|
//#endregion
|
|
180
|
-
export { memo, useCallback, useContext, useEffect, onErrorCaptured as useErrorBoundary, useId, useLayoutEffect, useMemo, useReducer, useRef, useState };
|
|
269
|
+
export { forwardRef, memo, useCallback, useContext, useDebugValue, useDeferredValue, useEffect, onErrorCaptured as useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useTransition };
|
|
181
270
|
//# sourceMappingURL=hooks.js.map
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, createContext as createContext$1, createRef, h as pyreonH, provide, useContext } from "@pyreon/core";
|
|
1
|
+
import { ErrorBoundary, Fragment, Portal, Suspense, createContext as createContext$1, createRef, h as pyreonH, lazy, nativeCompat, provide, useContext } from "@pyreon/core";
|
|
2
2
|
import { batch, signal } from "@pyreon/reactivity";
|
|
3
3
|
import { hydrateRoot, mount } from "@pyreon/runtime-dom";
|
|
4
4
|
|
|
@@ -28,6 +28,7 @@ function createContext(defaultValue) {
|
|
|
28
28
|
provide(ctx, props.value);
|
|
29
29
|
return props.children;
|
|
30
30
|
});
|
|
31
|
+
nativeCompat(Provider);
|
|
31
32
|
return {
|
|
32
33
|
...ctx,
|
|
33
34
|
Provider
|
|
@@ -43,6 +44,7 @@ var Component = class {
|
|
|
43
44
|
props;
|
|
44
45
|
state;
|
|
45
46
|
_stateSignal;
|
|
47
|
+
_lastResult;
|
|
46
48
|
constructor(props) {
|
|
47
49
|
this.props = props;
|
|
48
50
|
this.state = {};
|
|
@@ -78,13 +80,19 @@ var Component = class {
|
|
|
78
80
|
}
|
|
79
81
|
};
|
|
80
82
|
/**
|
|
83
|
+
* Preact-compatible PureComponent — extends Component.
|
|
84
|
+
* In Pyreon's compat layer this behaves identically to Component
|
|
85
|
+
* (signal-based reactivity already avoids unnecessary re-renders).
|
|
86
|
+
*/
|
|
87
|
+
var PureComponent = class extends Component {};
|
|
88
|
+
/**
|
|
81
89
|
* Clone a VNode with merged props (like Preact's cloneElement).
|
|
82
90
|
*/
|
|
83
91
|
function cloneElement(vnode, props, ...children) {
|
|
84
|
-
const mergedProps = {
|
|
92
|
+
const mergedProps = props ? {
|
|
85
93
|
...vnode.props,
|
|
86
|
-
...props
|
|
87
|
-
};
|
|
94
|
+
...props
|
|
95
|
+
} : { ...vnode.props };
|
|
88
96
|
const mergedChildren = children.length > 0 ? children : vnode.children;
|
|
89
97
|
return {
|
|
90
98
|
type: vnode.type,
|
|
@@ -110,11 +118,21 @@ function isValidElement(x) {
|
|
|
110
118
|
return x !== null && typeof x === "object" && "type" in x && "props" in x && "children" in x;
|
|
111
119
|
}
|
|
112
120
|
/**
|
|
121
|
+
* Preact-compatible `createPortal(children, target)`.
|
|
122
|
+
*/
|
|
123
|
+
function createPortal(children, target) {
|
|
124
|
+
return Portal({
|
|
125
|
+
target,
|
|
126
|
+
children
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
113
130
|
* Preact's plugin/hook system. Exposed as an empty object for compatibility
|
|
114
131
|
* with libraries that check for `options._hook`, `options.vnode`, etc.
|
|
115
132
|
*/
|
|
116
133
|
const options = {};
|
|
134
|
+
const version = "10.0.0-pyreon";
|
|
117
135
|
|
|
118
136
|
//#endregion
|
|
119
|
-
export { Component, Fragment, cloneElement, createContext, createElement, createRef, pyreonH as h, hydrate, isValidElement, options, render, toChildArray, useContext };
|
|
137
|
+
export { Component, ErrorBoundary, Fragment, PureComponent, Suspense, cloneElement, createContext, createElement, createPortal, createRef, pyreonH as h, hydrate, isValidElement, lazy, options, render, toChildArray, useContext, version };
|
|
120
138
|
//# sourceMappingURL=index.js.map
|
package/lib/jsx-runtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, h } 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
|
|
@@ -32,10 +32,60 @@ function scheduleEffects(ctx, entries) {
|
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
+
function isClassComponent(type) {
|
|
36
|
+
return type.prototype != null && typeof type.prototype.render === "function";
|
|
37
|
+
}
|
|
38
|
+
function wrapClassComponent(ClassComp) {
|
|
39
|
+
const wrapped = ((props) => {
|
|
40
|
+
const instance = new ClassComp(props);
|
|
41
|
+
const version = signal(0);
|
|
42
|
+
let updateScheduled = false;
|
|
43
|
+
const origSetState = instance.setState.bind(instance);
|
|
44
|
+
instance.setState = (partial) => {
|
|
45
|
+
origSetState(partial);
|
|
46
|
+
if (!updateScheduled) {
|
|
47
|
+
updateScheduled = true;
|
|
48
|
+
queueMicrotask(() => {
|
|
49
|
+
updateScheduled = false;
|
|
50
|
+
version.set(version.peek() + 1);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
instance.forceUpdate = () => {
|
|
55
|
+
version.set(version.peek() + 1);
|
|
56
|
+
};
|
|
57
|
+
let didMountFired = false;
|
|
58
|
+
onUnmount(() => {
|
|
59
|
+
if (typeof instance.componentWillUnmount === "function") instance.componentWillUnmount();
|
|
60
|
+
});
|
|
61
|
+
return () => {
|
|
62
|
+
const ver = version();
|
|
63
|
+
instance.props = props;
|
|
64
|
+
if (didMountFired && ver > 0 && typeof instance.shouldComponentUpdate === "function") {
|
|
65
|
+
if (!instance.shouldComponentUpdate(props, instance.state)) return instance._lastResult;
|
|
66
|
+
}
|
|
67
|
+
const result = instance.render();
|
|
68
|
+
instance._lastResult = result;
|
|
69
|
+
if (!didMountFired) {
|
|
70
|
+
didMountFired = true;
|
|
71
|
+
if (typeof instance.componentDidMount === "function") queueMicrotask(() => instance.componentDidMount());
|
|
72
|
+
} else if (ver > 0) {
|
|
73
|
+
if (typeof instance.componentDidUpdate === "function") queueMicrotask(() => instance.componentDidUpdate());
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
return wrapped;
|
|
79
|
+
}
|
|
35
80
|
const _wrapperCache = /* @__PURE__ */ new WeakMap();
|
|
36
81
|
function wrapCompatComponent(preactComponent) {
|
|
37
82
|
let wrapped = _wrapperCache.get(preactComponent);
|
|
38
83
|
if (wrapped) return wrapped;
|
|
84
|
+
if (isClassComponent(preactComponent)) {
|
|
85
|
+
wrapped = wrapClassComponent(preactComponent);
|
|
86
|
+
_wrapperCache.set(preactComponent, wrapped);
|
|
87
|
+
return wrapped;
|
|
88
|
+
}
|
|
39
89
|
wrapped = ((props) => {
|
|
40
90
|
const ctx = {
|
|
41
91
|
hooks: [],
|
|
@@ -54,6 +104,13 @@ function wrapCompatComponent(preactComponent) {
|
|
|
54
104
|
if (!ctx.unmounted) version.set(version.peek() + 1);
|
|
55
105
|
});
|
|
56
106
|
};
|
|
107
|
+
onUnmount(() => {
|
|
108
|
+
ctx.unmounted = true;
|
|
109
|
+
for (const hook of ctx.hooks) if (hook && typeof hook === "object" && "cleanup" in hook) {
|
|
110
|
+
const entry = hook;
|
|
111
|
+
if (typeof entry.cleanup === "function") entry.cleanup();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
57
114
|
return () => {
|
|
58
115
|
version();
|
|
59
116
|
beginRender(ctx);
|
|
@@ -75,11 +132,45 @@ function jsx(type, props, key) {
|
|
|
75
132
|
...rest,
|
|
76
133
|
key
|
|
77
134
|
} : rest;
|
|
78
|
-
if (typeof type === "function")
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
135
|
+
if (typeof type === "function") {
|
|
136
|
+
const componentProps = children !== void 0 ? {
|
|
137
|
+
...propsWithKey,
|
|
138
|
+
children
|
|
139
|
+
} : propsWithKey;
|
|
140
|
+
if (isNativeCompat(type)) return h(type, componentProps);
|
|
141
|
+
return h(wrapCompatComponent(type), componentProps);
|
|
142
|
+
}
|
|
143
|
+
const childArray = children === void 0 ? [] : Array.isArray(children) ? children : [children];
|
|
144
|
+
if (typeof type === "string") {
|
|
145
|
+
if (propsWithKey.className !== void 0) {
|
|
146
|
+
propsWithKey.class = propsWithKey.className;
|
|
147
|
+
delete propsWithKey.className;
|
|
148
|
+
}
|
|
149
|
+
if (propsWithKey.htmlFor !== void 0) {
|
|
150
|
+
propsWithKey.for = propsWithKey.htmlFor;
|
|
151
|
+
delete propsWithKey.htmlFor;
|
|
152
|
+
}
|
|
153
|
+
if ((type === "input" || type === "textarea" || type === "select") && propsWithKey.onChange !== void 0) {
|
|
154
|
+
if (propsWithKey.onInput === void 0) propsWithKey.onInput = propsWithKey.onChange;
|
|
155
|
+
delete propsWithKey.onChange;
|
|
156
|
+
}
|
|
157
|
+
if (propsWithKey.autoFocus !== void 0) {
|
|
158
|
+
propsWithKey.autofocus = propsWithKey.autoFocus;
|
|
159
|
+
delete propsWithKey.autoFocus;
|
|
160
|
+
}
|
|
161
|
+
if (type === "input" || type === "textarea") {
|
|
162
|
+
if (propsWithKey.defaultValue !== void 0 && propsWithKey.value === void 0) {
|
|
163
|
+
propsWithKey.value = propsWithKey.defaultValue;
|
|
164
|
+
delete propsWithKey.defaultValue;
|
|
165
|
+
}
|
|
166
|
+
if (propsWithKey.defaultChecked !== void 0 && propsWithKey.checked === void 0) {
|
|
167
|
+
propsWithKey.checked = propsWithKey.defaultChecked;
|
|
168
|
+
delete propsWithKey.defaultChecked;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
delete propsWithKey.suppressHydrationWarning;
|
|
172
|
+
}
|
|
173
|
+
return h(type, propsWithKey, ...childArray);
|
|
83
174
|
}
|
|
84
175
|
const jsxs = jsx;
|
|
85
176
|
|
package/lib/signals.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { batch as pyreonBatch, computed as computed$1, effect as effect$1, signal as signal$1 } from "@pyreon/reactivity";
|
|
1
|
+
import { batch as pyreonBatch, computed as computed$1, effect as effect$1, runUntracked, signal as signal$1 } from "@pyreon/reactivity";
|
|
2
2
|
|
|
3
3
|
//#region src/signals.ts
|
|
4
4
|
/**
|
|
@@ -37,7 +37,7 @@ function computed(fn) {
|
|
|
37
37
|
return c();
|
|
38
38
|
},
|
|
39
39
|
peek() {
|
|
40
|
-
return c();
|
|
40
|
+
return runUntracked(() => c());
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
package/lib/types/hooks.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { VNodeChild, onErrorCaptured, useContext } from "@pyreon/core";
|
|
|
4
4
|
/**
|
|
5
5
|
* Preact-compatible `useState` — returns `[value, setter]`.
|
|
6
6
|
* Triggers a component re-render when the setter is called.
|
|
7
|
+
*
|
|
8
|
+
* The setter has stable identity across renders (same reference every time).
|
|
7
9
|
*/
|
|
8
10
|
declare function useState<T>(initial: T | (() => T)): [T, (v: T | ((prev: T) => T)) => void];
|
|
9
11
|
/**
|
|
@@ -31,8 +33,11 @@ declare function useRef<T>(initial?: T): {
|
|
|
31
33
|
};
|
|
32
34
|
/**
|
|
33
35
|
* Preact-compatible `useReducer` — returns `[state, dispatch]`.
|
|
36
|
+
* Supports the 3-argument form: `useReducer(reducer, initialArg, init)`.
|
|
37
|
+
*
|
|
38
|
+
* Dispatch has stable identity across renders (same reference every time).
|
|
34
39
|
*/
|
|
35
|
-
declare function useReducer<S, A>(reducer: (state: S, action: A) => S,
|
|
40
|
+
declare function useReducer<S, A>(reducer: (state: S, action: A) => S, initialArg: S | (() => S), init?: (arg: S) => S): [S, (action: A) => void];
|
|
36
41
|
/**
|
|
37
42
|
* Preact-compatible `useId` — returns a stable unique string per hook call.
|
|
38
43
|
*/
|
|
@@ -40,8 +45,50 @@ declare function useId(): string;
|
|
|
40
45
|
/**
|
|
41
46
|
* Preact-compatible `memo` — wraps a component to skip re-render when props
|
|
42
47
|
* are shallowly equal.
|
|
48
|
+
*
|
|
49
|
+
* Each component INSTANCE gets its own props/result cache via a hook slot,
|
|
50
|
+
* so two `<MemoComp />` usages don't share memoization state.
|
|
43
51
|
*/
|
|
44
52
|
declare function memo<P extends Record<string, unknown>>(component: (props: P) => VNodeChild, areEqual?: (prevProps: P, nextProps: P) => boolean): (props: P) => VNodeChild;
|
|
53
|
+
/**
|
|
54
|
+
* Preact-compatible `forwardRef` — pass-through in Pyreon.
|
|
55
|
+
* Refs are regular props in Pyreon, so no wrapper is needed.
|
|
56
|
+
* The render function receives (props, ref) — we merge ref into props.
|
|
57
|
+
*/
|
|
58
|
+
declare function forwardRef<P extends Record<string, unknown>>(render: (props: P, ref: {
|
|
59
|
+
current: unknown;
|
|
60
|
+
} | null) => VNodeChild): (props: P & {
|
|
61
|
+
ref?: {
|
|
62
|
+
current: unknown;
|
|
63
|
+
} | null;
|
|
64
|
+
}) => VNodeChild;
|
|
65
|
+
/**
|
|
66
|
+
* Preact-compatible `useImperativeHandle`.
|
|
67
|
+
*/
|
|
68
|
+
declare function useImperativeHandle<T>(ref: {
|
|
69
|
+
current: T | null;
|
|
70
|
+
} | null | undefined, init: () => T, deps?: unknown[]): void;
|
|
71
|
+
/**
|
|
72
|
+
* Preact-compatible `useDebugValue` — no-op in Pyreon (no Preact DevTools integration).
|
|
73
|
+
*/
|
|
74
|
+
declare function useDebugValue<T>(_value: T, _format?: (v: T) => unknown): void;
|
|
75
|
+
/**
|
|
76
|
+
* Preact-compatible `useTransition` — returns `[isPending, startTransition]`.
|
|
77
|
+
*
|
|
78
|
+
* In Pyreon's signal-based reactivity there is no concept of concurrent
|
|
79
|
+
* rendering lanes. The callback is executed synchronously and `isPending`
|
|
80
|
+
* is always `false`. This shim exists so Preact/React code that uses
|
|
81
|
+
* `useTransition` compiles and runs without changes.
|
|
82
|
+
*/
|
|
83
|
+
declare function useTransition(): [boolean, (fn: () => void) => void];
|
|
84
|
+
/**
|
|
85
|
+
* Preact-compatible `useDeferredValue` — returns the value as-is.
|
|
86
|
+
*
|
|
87
|
+
* In Pyreon's signal-based reactivity there are no concurrent rendering lanes,
|
|
88
|
+
* so the value is never "deferred". This shim exists so Preact/React code that
|
|
89
|
+
* uses `useDeferredValue` compiles and runs without changes.
|
|
90
|
+
*/
|
|
91
|
+
declare function useDeferredValue<T>(value: T): T;
|
|
45
92
|
//#endregion
|
|
46
|
-
export { memo, useCallback, useContext, useEffect, onErrorCaptured as useErrorBoundary, useId, useLayoutEffect, useMemo, useReducer, useRef, useState };
|
|
93
|
+
export { forwardRef, memo, useCallback, useContext, useDebugValue, useDeferredValue, useEffect, onErrorCaptured as useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useTransition };
|
|
47
94
|
//# sourceMappingURL=hooks2.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ComponentFn, Fragment, Props, VNode, VNodeChild, createRef, h as pyreonH, useContext } from "@pyreon/core";
|
|
1
|
+
import { ComponentFn, ErrorBoundary, Fragment, Props, Suspense, VNode, VNodeChild, createRef, h as pyreonH, lazy, useContext } from "@pyreon/core";
|
|
2
|
+
import { signal } from "@pyreon/reactivity";
|
|
2
3
|
|
|
3
4
|
//#region src/index.d.ts
|
|
4
5
|
/** Alias: Preact also exports createElement */
|
|
@@ -34,7 +35,12 @@ declare function createContext<T>(defaultValue: T): PreactContext<T>;
|
|
|
34
35
|
declare class Component<P extends Props = Props, S extends Record<string, unknown> = Record<string, unknown>> {
|
|
35
36
|
props: P;
|
|
36
37
|
state: S;
|
|
37
|
-
|
|
38
|
+
_stateSignal: ReturnType<typeof signal<S>>;
|
|
39
|
+
_lastResult?: VNodeChild;
|
|
40
|
+
componentDidMount?(): void;
|
|
41
|
+
componentDidUpdate?(): void;
|
|
42
|
+
componentWillUnmount?(): void;
|
|
43
|
+
shouldComponentUpdate?(nextProps: P, nextState: S): boolean;
|
|
38
44
|
constructor(props: P);
|
|
39
45
|
/**
|
|
40
46
|
* Update state — accepts a partial state object or an updater function.
|
|
@@ -50,6 +56,12 @@ declare class Component<P extends Props = Props, S extends Record<string, unknow
|
|
|
50
56
|
*/
|
|
51
57
|
render(): VNodeChild;
|
|
52
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Preact-compatible PureComponent — extends Component.
|
|
61
|
+
* In Pyreon's compat layer this behaves identically to Component
|
|
62
|
+
* (signal-based reactivity already avoids unnecessary re-renders).
|
|
63
|
+
*/
|
|
64
|
+
declare class PureComponent<P extends Props = Props, S extends Record<string, unknown> = Record<string, unknown>> extends Component<P, S> {}
|
|
53
65
|
/**
|
|
54
66
|
* Clone a VNode with merged props (like Preact's cloneElement).
|
|
55
67
|
*/
|
|
@@ -64,11 +76,16 @@ declare function toChildArray(children: NestedChildren): VNodeChild[];
|
|
|
64
76
|
* Check if a value is a VNode (like Preact's isValidElement).
|
|
65
77
|
*/
|
|
66
78
|
declare function isValidElement(x: unknown): x is VNode;
|
|
79
|
+
/**
|
|
80
|
+
* Preact-compatible `createPortal(children, target)`.
|
|
81
|
+
*/
|
|
82
|
+
declare function createPortal(children: VNodeChild, target: Element): VNodeChild;
|
|
67
83
|
/**
|
|
68
84
|
* Preact's plugin/hook system. Exposed as an empty object for compatibility
|
|
69
85
|
* with libraries that check for `options._hook`, `options.vnode`, etc.
|
|
70
86
|
*/
|
|
71
87
|
declare const options: Record<string, unknown>;
|
|
88
|
+
declare const version = "10.0.0-pyreon";
|
|
72
89
|
//#endregion
|
|
73
|
-
export { Component, Fragment, PreactContext, cloneElement, createContext, createElement, createRef, pyreonH as h, hydrate, isValidElement, options, render, toChildArray, useContext };
|
|
90
|
+
export { Component, ErrorBoundary, Fragment, PreactContext, PureComponent, Suspense, cloneElement, createContext, createElement, createPortal, createRef, pyreonH as h, hydrate, isValidElement, lazy, options, render, toChildArray, useContext, version };
|
|
74
91
|
//# sourceMappingURL=index2.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/preact-compat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Preact-compatible API shim for Pyreon — write Preact-style code that runs on Pyreon's reactive engine",
|
|
5
5
|
"homepage": "https://github.com/pyreon/pyreon/tree/main/packages/preact-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"
|
|
@@ -57,17 +58,20 @@
|
|
|
57
58
|
"build": "vl_rolldown_build",
|
|
58
59
|
"dev": "vl_rolldown_build-watch",
|
|
59
60
|
"test": "vitest run",
|
|
61
|
+
"test:browser": "vitest run --config ./vitest.browser.config.ts",
|
|
60
62
|
"typecheck": "tsc --noEmit",
|
|
61
63
|
"lint": "oxlint .",
|
|
62
64
|
"prepublishOnly": "bun run build"
|
|
63
65
|
},
|
|
64
66
|
"dependencies": {
|
|
65
|
-
"@pyreon/core": "^0.
|
|
66
|
-
"@pyreon/reactivity": "^0.
|
|
67
|
-
"@pyreon/runtime-dom": "^0.
|
|
67
|
+
"@pyreon/core": "^0.15.0",
|
|
68
|
+
"@pyreon/reactivity": "^0.15.0",
|
|
69
|
+
"@pyreon/runtime-dom": "^0.15.0"
|
|
68
70
|
},
|
|
69
71
|
"devDependencies": {
|
|
70
72
|
"@happy-dom/global-registrator": "^20.8.9",
|
|
73
|
+
"@pyreon/test-utils": "^0.13.2",
|
|
74
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
71
75
|
"happy-dom": "^20.8.3"
|
|
72
76
|
}
|
|
73
77
|
}
|