@pyreon/preact-compat 0.13.1 → 0.14.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.
@@ -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":"48c2c8ab-1","name":"jsx-runtime.ts"},{"uid":"48c2c8ab-3","name":"hooks.ts"}]}]}],"isRoot":true},"nodeParts":{"48c2c8ab-1":{"renderedLength":186,"gzipLength":138,"brotliLength":0,"metaUid":"48c2c8ab-0"},"48c2c8ab-3":{"renderedLength":4368,"gzipLength":1216,"brotliLength":0,"metaUid":"48c2c8ab-2"}},"nodeMetas":{"48c2c8ab-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"hooks.js":"48c2c8ab-1"},"imported":[{"uid":"48c2c8ab-4"},{"uid":"48c2c8ab-5"}],"importedBy":[{"uid":"48c2c8ab-2"}]},"48c2c8ab-2":{"id":"/src/hooks.ts","moduleParts":{"hooks.js":"48c2c8ab-3"},"imported":[{"uid":"48c2c8ab-4"},{"uid":"48c2c8ab-0"}],"importedBy":[],"isEntry":true},"48c2c8ab-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"48c2c8ab-2"},{"uid":"48c2c8ab-0"}]},"48c2c8ab-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"48c2c8ab-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
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":"e7c3b550-1"}]}],"isRoot":true},"nodeParts":{"e7c3b550-1":{"renderedLength":2789,"gzipLength":1150,"brotliLength":0,"metaUid":"e7c3b550-0"}},"nodeMetas":{"e7c3b550-0":{"id":"/src/index.ts","moduleParts":{"index.js":"e7c3b550-1"},"imported":[{"uid":"e7c3b550-2"},{"uid":"e7c3b550-3"},{"uid":"e7c3b550-4"}],"importedBy":[],"isEntry":true},"e7c3b550-2":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"e7c3b550-0"}]},"e7c3b550-3":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"e7c3b550-0"}]},"e7c3b550-4":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"e7c3b550-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src/index.ts","uid":"4f885a24-1"}]}],"isRoot":true},"nodeParts":{"4f885a24-1":{"renderedLength":3348,"gzipLength":1358,"brotliLength":0,"metaUid":"4f885a24-0"}},"nodeMetas":{"4f885a24-0":{"id":"/src/index.ts","moduleParts":{"index.js":"4f885a24-1"},"imported":[{"uid":"4f885a24-2"},{"uid":"4f885a24-3"},{"uid":"4f885a24-4"}],"importedBy":[],"isEntry":true},"4f885a24-2":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"4f885a24-0"}]},"4f885a24-3":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"4f885a24-0"}]},"4f885a24-4":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"4f885a24-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":"0c0e5fed-1","name":"jsx-runtime.ts"},{"uid":"0c0e5fed-3","name":"jsx-dev-runtime.ts"}]}]}],"isRoot":true},"nodeParts":{"0c0e5fed-1":{"renderedLength":2208,"gzipLength":798,"brotliLength":0,"metaUid":"0c0e5fed-0"},"0c0e5fed-3":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"0c0e5fed-2"}},"nodeMetas":{"0c0e5fed-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"jsx-runtime.js":"0c0e5fed-1"},"imported":[{"uid":"0c0e5fed-4"},{"uid":"0c0e5fed-5"}],"importedBy":[{"uid":"0c0e5fed-2"}]},"0c0e5fed-2":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-runtime.js":"0c0e5fed-3"},"imported":[{"uid":"0c0e5fed-0"}],"importedBy":[],"isEntry":true},"0c0e5fed-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"0c0e5fed-0"}]},"0c0e5fed-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"0c0e5fed-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"jsx-runtime.js","children":[{"name":"src","children":[{"uid":"75c795b4-1","name":"jsx-runtime.ts"},{"uid":"75c795b4-3","name":"jsx-dev-runtime.ts"}]}]}],"isRoot":true},"nodeParts":{"75c795b4-1":{"renderedLength":5398,"gzipLength":1521,"brotliLength":0,"metaUid":"75c795b4-0"},"75c795b4-3":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"75c795b4-2"}},"nodeMetas":{"75c795b4-0":{"id":"/src/jsx-runtime.ts","moduleParts":{"jsx-runtime.js":"75c795b4-1"},"imported":[{"uid":"75c795b4-4"},{"uid":"75c795b4-5"}],"importedBy":[{"uid":"75c795b4-2"}]},"75c795b4-2":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-runtime.js":"75c795b4-3"},"imported":[{"uid":"75c795b4-0"}],"importedBy":[],"isEntry":true},"75c795b4-4":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"75c795b4-0"}]},"75c795b4-5":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"75c795b4-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":"ef3b7036-1"}]}],"isRoot":true},"nodeParts":{"ef3b7036-1":{"renderedLength":856,"gzipLength":375,"brotliLength":0,"metaUid":"ef3b7036-0"}},"nodeMetas":{"ef3b7036-0":{"id":"/src/signals.ts","moduleParts":{"signals.js":"ef3b7036-1"},"imported":[{"uid":"ef3b7036-2"}],"importedBy":[],"isEntry":true},"ef3b7036-2":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"ef3b7036-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
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) ctx.hooks.push(typeof initial === "function" ? initial() : initial);
34
- const value = ctx.hooks[idx];
35
- const setter = (v) => {
36
- const current = ctx.hooks[idx];
37
- const next = typeof v === "function" ? v(current) : v;
38
- if (Object.is(current, next)) return;
39
- ctx.hooks[idx] = next;
40
- ctx.scheduleRerender();
41
- };
42
- return [value, setter];
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, initial) {
151
+ function useReducer(reducer, initialArg, init) {
134
152
  const ctx = requireCtx();
135
153
  const idx = getHookIndex();
136
- if (ctx.hooks.length <= idx) ctx.hooks.push(typeof initial === "function" ? initial() : initial);
137
- const state = ctx.hooks[idx];
138
- const dispatch = (action) => {
139
- const current = ctx.hooks[idx];
140
- const next = reducer(current, action);
141
- if (Object.is(current, next)) return;
142
- ctx.hooks[idx] = next;
143
- ctx.scheduleRerender();
144
- };
145
- return [state, dispatch];
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 ?? ((a, b) => {
163
- const keysA = Object.keys(a);
164
- const keysB = Object.keys(b);
165
- if (keysA.length !== keysB.length) return false;
166
- for (const k of keysA) if (!Object.is(a[k], b[k])) return false;
167
- return true;
168
- });
169
- let prevProps = null;
170
- let prevResult = null;
171
- return (props) => {
172
- if (prevProps !== null && compare(prevProps, props)) return prevResult;
173
- prevProps = props;
174
- prevResult = component(props);
175
- return prevResult;
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/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","names":[],"sources":["../src/jsx-runtime.ts","../src/hooks.ts"],"sourcesContent":["/**\n * Compat JSX runtime for Preact compatibility mode.\n *\n * When `jsxImportSource` is redirected to `@pyreon/preact-compat` (via the vite\n * plugin's `compat: \"preact\"` option), OXC rewrites JSX to import from this file.\n *\n * For component VNodes, we wrap the component function so it returns a reactive\n * accessor — enabling Preact-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 } 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 /** 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}\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\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.pendingEffects = []\n ctx.pendingLayoutEffects = []\n}\n\nexport function endRender(): void {\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(preactComponent: Function): ComponentFn {\n let wrapped = _wrapperCache.get(preactComponent)\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 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 // 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 = (preactComponent as ComponentFn)(props)\n const layoutEffects = ctx.pendingLayoutEffects\n const effects = ctx.pendingEffects\n endRender()\n\n runLayoutEffects(layoutEffects)\n scheduleEffects(ctx, effects)\n\n return result\n }\n }) as unknown as ComponentFn\n\n _wrapperCache.set(preactComponent, 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 // Wrap Preact-style component for re-render support\n const wrapped = wrapCompatComponent(type)\n const componentProps = children !== undefined ? { ...propsWithKey, children } : propsWithKey\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 return h(type, propsWithKey, ...(childArray as VNodeChild[]))\n}\n\nexport const jsxs = jsx\nexport const jsxDEV = jsx\n","/**\n * @pyreon/preact-compat/hooks\n *\n * Preact-compatible hooks — separate import like `preact/hooks`.\n *\n * Components re-render on state change — just like Preact. Hooks return plain\n * values and use deps arrays for memoization. Existing Preact code works\n * unchanged when paired with `pyreon({ compat: \"preact\" })` in your vite config.\n */\n\nimport type { VNodeChild } from '@pyreon/core'\nimport { onErrorCaptured, useContext } from '@pyreon/core'\nimport type { EffectEntry } from './jsx-runtime'\nimport { getCurrentCtx, getHookIndex } from './jsx-runtime'\n\nexport { useContext }\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// ─── useState ────────────────────────────────────────────────────────────────\n\n/**\n * Preact-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 ctx.hooks.push(typeof initial === 'function' ? (initial as () => T)() : initial)\n }\n\n const value = ctx.hooks[idx] as T\n const setter = (v: T | ((prev: T) => T)) => {\n const current = ctx.hooks[idx] as T\n const next = typeof v === 'function' ? (v as (prev: T) => T)(current) : v\n if (Object.is(current, next)) return\n ctx.hooks[idx] = next\n ctx.scheduleRerender()\n }\n\n return [value, setter]\n}\n\n// ─── useEffect ───────────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useLayoutEffect ─────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useMemo ─────────────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useCallback ─────────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useRef ──────────────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useReducer ──────────────────────────────────────────────────────────────\n\n/**\n * Preact-compatible `useReducer` — returns `[state, dispatch]`.\n */\nexport function useReducer<S, A>(\n reducer: (state: S, action: A) => S,\n initial: S | (() => S),\n): [S, (action: A) => void] {\n const ctx = requireCtx()\n const idx = getHookIndex()\n\n if (ctx.hooks.length <= idx) {\n ctx.hooks.push(typeof initial === 'function' ? (initial as () => S)() : initial)\n }\n\n const state = ctx.hooks[idx] as S\n const dispatch = (action: A) => {\n const current = ctx.hooks[idx] as S\n const next = reducer(current, action)\n if (Object.is(current, next)) return\n ctx.hooks[idx] = next\n ctx.scheduleRerender()\n }\n\n return [state, dispatch]\n}\n\n// ─── useId ───────────────────────────────────────────────────────────────────\n\nlet _idCounter = 0\n\n/**\n * Preact-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\n/**\n * Preact-compatible `memo` — wraps a component to skip re-render when props\n * are shallowly equal.\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 =\n areEqual ??\n ((a: P, b: P) => {\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 let prevProps: P | null = null\n let prevResult: VNodeChild = null\n\n return (props: P) => {\n if (prevProps !== null && compare(prevProps, props)) {\n return prevResult\n }\n prevProps = props\n prevResult = (component as (p: P) => VNodeChild)(props)\n return prevResult\n }\n}\n\n// ─── useErrorBoundary ────────────────────────────────────────────────────────\n\n/**\n * Preact-compatible `useErrorBoundary`.\n * Wraps Pyreon's `onErrorCaptured`.\n */\nexport { onErrorCaptured as useErrorBoundary }\n"],"mappings":"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"}
1
+ {"version":3,"file":"hooks.js","names":[],"sources":["../src/jsx-runtime.ts","../src/hooks.ts"],"sourcesContent":["/**\n * Compat JSX runtime for Preact compatibility mode.\n *\n * When `jsxImportSource` is redirected to `@pyreon/preact-compat` (via the vite\n * plugin's `compat: \"preact\"` option), OXC rewrites JSX to import from this file.\n *\n * For component VNodes, we wrap the component function so it returns a reactive\n * accessor — enabling Preact-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'\nimport type { Component } from './index'\n\nexport { Fragment }\n\n// ─── Render context (used by hooks) ──────────────────────────────────────────\n\nexport interface RenderContext {\n hooks: unknown[]\n scheduleRerender: () => void\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}\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\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.pendingEffects = []\n ctx.pendingLayoutEffects = []\n}\n\nexport function endRender(): void {\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// ─── Native component marker ────────────────────────────────────────────────\n\nconst NATIVE_COMPONENT = Symbol.for('pyreon:native-compat')\n\n// ─── Class component detection ──────────────────────────────────────────────\n\nfunction isClassComponent(type: Function): boolean {\n return type.prototype != null && typeof type.prototype.render === 'function'\n}\n\n// ─── Class component wrapping ───────────────────────────────────────────────\n\nfunction wrapClassComponent(ClassComp: Function): ComponentFn {\n const wrapped = ((props: Props) => {\n const instance = new (ClassComp as new (props: Props) => Component)(props)\n const version = signal(0)\n let updateScheduled = false\n\n // Override setState to trigger re-render via version signal\n const origSetState = instance.setState.bind(instance)\n instance.setState = (partial: Partial<Record<string, unknown>>) => {\n origSetState(partial)\n if (!updateScheduled) {\n updateScheduled = true\n queueMicrotask(() => {\n updateScheduled = false\n version.set(version.peek() + 1)\n })\n }\n }\n\n // Override forceUpdate\n instance.forceUpdate = () => {\n version.set(version.peek() + 1)\n }\n\n // Lifecycle: componentWillUnmount\n let didMountFired = false\n onUnmount(() => {\n if (typeof instance.componentWillUnmount === 'function') {\n instance.componentWillUnmount()\n }\n })\n\n // Return reactive accessor for re-renders\n return () => {\n const ver = version() // track for re-renders\n instance.props = props // update props on re-render\n\n // shouldComponentUpdate only applies after mount (ver > 0 means setState/forceUpdate)\n if (didMountFired && ver > 0 && typeof instance.shouldComponentUpdate === 'function') {\n if (!instance.shouldComponentUpdate(props, instance.state)) {\n return instance._lastResult // skip render\n }\n }\n\n const result = instance.render()\n instance._lastResult = result\n\n // componentDidMount fires once after the initial render settles\n if (!didMountFired) {\n didMountFired = true\n if (typeof instance.componentDidMount === 'function') {\n queueMicrotask(() => instance.componentDidMount!())\n }\n } else if (ver > 0) {\n // componentDidUpdate only fires on explicit re-renders (setState/forceUpdate)\n if (typeof instance.componentDidUpdate === 'function') {\n queueMicrotask(() => instance.componentDidUpdate!())\n }\n }\n\n return result\n }\n }) as unknown as ComponentFn\n return wrapped\n}\n\n// ─── Component wrapping ──────────────────────────────────────────────────────\n\nconst _wrapperCache = new WeakMap<Function, ComponentFn>()\n\nfunction wrapCompatComponent(preactComponent: Function): ComponentFn {\n let wrapped = _wrapperCache.get(preactComponent)\n if (wrapped) return wrapped\n\n // Handle class components (those with prototype.render)\n if (isClassComponent(preactComponent)) {\n wrapped = wrapClassComponent(preactComponent)\n _wrapperCache.set(preactComponent, wrapped)\n return wrapped\n }\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 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 }\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 = (preactComponent as ComponentFn)(props)\n const layoutEffects = ctx.pendingLayoutEffects\n const effects = ctx.pendingEffects\n endRender()\n\n runLayoutEffects(layoutEffects)\n scheduleEffects(ctx, effects)\n\n return result\n }\n }) as unknown as ComponentFn\n\n _wrapperCache.set(preactComponent, 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 if ((type as unknown as Record<symbol, boolean>)[NATIVE_COMPONENT]) {\n return h(type as ComponentFn, componentProps)\n }\n // Wrap Preact-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 Preact-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 // Preact'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 Preact-only props that have no DOM equivalent\n delete propsWithKey.suppressHydrationWarning\n }\n\n return h(type, propsWithKey, ...(childArray as VNodeChild[]))\n}\n\nexport const jsxs = jsx\nexport const jsxDEV = jsx\n","/**\n * @pyreon/preact-compat/hooks\n *\n * Preact-compatible hooks — separate import like `preact/hooks`.\n *\n * Components re-render on state change — just like Preact. Hooks return plain\n * values and use deps arrays for memoization. Existing Preact code works\n * unchanged when paired with `pyreon({ compat: \"preact\" })` in your vite config.\n */\n\nimport type { VNodeChild } from '@pyreon/core'\nimport { onErrorCaptured, useContext } from '@pyreon/core'\nimport type { EffectEntry } from './jsx-runtime'\nimport { getCurrentCtx, getHookIndex } from './jsx-runtime'\n\nexport { useContext }\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\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// ─── useState ────────────────────────────────────────────────────────────────\n\n/**\n * Preact-compatible `useState` — returns `[value, setter]`.\n * Triggers a component re-render when the setter is called.\n *\n * The setter has stable identity across renders (same reference every time).\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 (Preact/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// ─── useEffect ───────────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useLayoutEffect ─────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useMemo ─────────────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useCallback ─────────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useRef ──────────────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useReducer ──────────────────────────────────────────────────────────────\n\n/**\n * Preact-compatible `useReducer` — returns `[state, dispatch]`.\n * Supports the 3-argument form: `useReducer(reducer, initialArg, init)`.\n *\n * Dispatch has stable identity across renders (same reference every time).\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 (Preact/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// ─── useId ───────────────────────────────────────────────────────────────────\n\nlet _idCounter = 0\n\n/**\n * Preact-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\n/**\n * Preact-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 // 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.displayName =\n (component as unknown as { displayName?: string }).displayName || component.name || 'Memo'\n return memoized\n}\n\n// ─── forwardRef ─────────────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useImperativeHandle ────────────────────────────────────────────────────\n\n/**\n * Preact-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// ─── useDebugValue ──────────────────────────────────────────────────────────\n\n/**\n * Preact-compatible `useDebugValue` — no-op in Pyreon (no Preact DevTools integration).\n */\nexport function useDebugValue<T>(_value: T, _format?: (v: T) => unknown): void {}\n\n// ─── useTransition ──────────────────────────────────────────────────────\n\n/**\n * Preact-compatible `useTransition` — returns `[isPending, startTransition]`.\n *\n * In Pyreon's signal-based reactivity there is no concept of concurrent\n * rendering lanes. The callback is executed synchronously and `isPending`\n * is always `false`. This shim exists so Preact/React code that uses\n * `useTransition` compiles and runs without changes.\n */\nexport function useTransition(): [boolean, (fn: () => void) => void] {\n return [false, (fn) => fn()]\n}\n\n// ─── useDeferredValue ───────────────────────────────────────────────────\n\n/**\n * Preact-compatible `useDeferredValue` — returns the value as-is.\n *\n * In Pyreon's signal-based reactivity there are no concurrent rendering lanes,\n * so the value is never \"deferred\". This shim exists so Preact/React code that\n * uses `useDeferredValue` compiles and runs without changes.\n */\nexport function useDeferredValue<T>(value: T): T {\n return value\n}\n\n// ─── useErrorBoundary ────────────────────────────────────────────────────────\n\n/**\n * Preact-compatible `useErrorBoundary`.\n * Wraps Pyreon's `onErrorCaptured`.\n */\nexport { onErrorCaptured as useErrorBoundary }\n"],"mappings":"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"}
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, provide, useContext } from "@pyreon/core";
2
2
  import { batch, signal } from "@pyreon/reactivity";
3
3
  import { hydrateRoot, mount } from "@pyreon/runtime-dom";
4
4
 
@@ -19,6 +19,7 @@ function render(vnode, container) {
19
19
  function hydrate(vnode, container) {
20
20
  hydrateRoot(container, vnode);
21
21
  }
22
+ const NATIVE_COMPONENT = Symbol.for("pyreon:native-compat");
22
23
  /**
23
24
  * Preact-compatible createContext — returns a context with a `.Provider` component.
24
25
  */
@@ -28,6 +29,7 @@ function createContext(defaultValue) {
28
29
  provide(ctx, props.value);
29
30
  return props.children;
30
31
  });
32
+ Provider[NATIVE_COMPONENT] = true;
31
33
  return {
32
34
  ...ctx,
33
35
  Provider
@@ -43,6 +45,7 @@ var Component = class {
43
45
  props;
44
46
  state;
45
47
  _stateSignal;
48
+ _lastResult;
46
49
  constructor(props) {
47
50
  this.props = props;
48
51
  this.state = {};
@@ -78,13 +81,19 @@ var Component = class {
78
81
  }
79
82
  };
80
83
  /**
84
+ * Preact-compatible PureComponent — extends Component.
85
+ * In Pyreon's compat layer this behaves identically to Component
86
+ * (signal-based reactivity already avoids unnecessary re-renders).
87
+ */
88
+ var PureComponent = class extends Component {};
89
+ /**
81
90
  * Clone a VNode with merged props (like Preact's cloneElement).
82
91
  */
83
92
  function cloneElement(vnode, props, ...children) {
84
- const mergedProps = {
93
+ const mergedProps = props ? {
85
94
  ...vnode.props,
86
- ...props ?? {}
87
- };
95
+ ...props
96
+ } : { ...vnode.props };
88
97
  const mergedChildren = children.length > 0 ? children : vnode.children;
89
98
  return {
90
99
  type: vnode.type,
@@ -110,11 +119,21 @@ function isValidElement(x) {
110
119
  return x !== null && typeof x === "object" && "type" in x && "props" in x && "children" in x;
111
120
  }
112
121
  /**
122
+ * Preact-compatible `createPortal(children, target)`.
123
+ */
124
+ function createPortal(children, target) {
125
+ return Portal({
126
+ target,
127
+ children
128
+ });
129
+ }
130
+ /**
113
131
  * Preact's plugin/hook system. Exposed as an empty object for compatibility
114
132
  * with libraries that check for `options._hook`, `options.vnode`, etc.
115
133
  */
116
134
  const options = {};
135
+ const version = "10.0.0-pyreon";
117
136
 
118
137
  //#endregion
119
- export { Component, Fragment, cloneElement, createContext, createElement, createRef, pyreonH as h, hydrate, isValidElement, options, render, toChildArray, useContext };
138
+ export { Component, ErrorBoundary, Fragment, PureComponent, Suspense, cloneElement, createContext, createElement, createPortal, createRef, pyreonH as h, hydrate, isValidElement, lazy, options, render, toChildArray, useContext, version };
120
139
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["pyreonCreateContext"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @pyreon/preact-compat\n *\n * Preact-compatible API shim that runs on Pyreon's reactive engine.\n *\n * Provides the core Preact API surface: h, Fragment, render, hydrate,\n * Component class, createContext, createRef, cloneElement, toChildArray,\n * isValidElement, and the options hook object.\n *\n * For hooks, import from \"@pyreon/preact-compat/hooks\".\n * For signals, import from \"@pyreon/preact-compat/signals\".\n */\n\nimport type { ComponentFn, Props, VNode, VNodeChild } from '@pyreon/core'\nimport {\n createRef,\n Fragment,\n provide,\n createContext as pyreonCreateContext,\n h as pyreonH,\n useContext,\n} from '@pyreon/core'\nimport { batch, signal } from '@pyreon/reactivity'\nimport { hydrateRoot, mount } from '@pyreon/runtime-dom'\n\n// ─── Core JSX ────────────────────────────────────────────────────────────────\n\n/** Preact's hyperscript function — maps directly to Pyreon's h() */\nexport { pyreonH as h }\n\n/** Alias: Preact also exports createElement */\nexport const createElement = pyreonH\n\nexport { Fragment }\n\n// ─── Render / Hydrate ────────────────────────────────────────────────────────\n\n/**\n * Preact's `render(vnode, container)`.\n * Maps to Pyreon's `mount(vnode, container)`.\n */\nexport function render(vnode: VNodeChild, container: Element): void {\n mount(vnode, container)\n}\n\n/**\n * Preact's `hydrate(vnode, container)`.\n * Maps to Pyreon's `hydrateRoot(container, vnode)`.\n */\nexport function hydrate(vnode: VNodeChild, container: Element): void {\n hydrateRoot(container, vnode as VNode)\n}\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\nexport interface PreactContext<T> {\n readonly id: symbol\n readonly defaultValue: T\n Provider: ComponentFn<{ value: T; children?: VNodeChild }>\n}\n\n/**\n * Preact-compatible createContext — returns a context with a `.Provider` component.\n */\nexport function createContext<T>(defaultValue: T): PreactContext<T> {\n const ctx = pyreonCreateContext<T>(defaultValue)\n const Provider = ((props: { value: T; children?: VNodeChild }) => {\n provide(ctx, props.value)\n return props.children\n }) as ComponentFn<{ value: T; children?: VNodeChild }>\n return { ...ctx, Provider }\n}\n\nexport { useContext }\n\n// ─── Refs ────────────────────────────────────────────────────────────────────\n\nexport { createRef }\n\n// ─── Component class ─────────────────────────────────────────────────────────\n\n/**\n * Preact-compatible class-based Component.\n *\n * Wraps Pyreon's signal-based reactivity so `setState` triggers re-renders.\n * Usage: `class MyComp extends Component { render() { ... } }`\n */\nexport class Component<\n P extends Props = Props,\n S extends Record<string, unknown> = Record<string, unknown>,\n> {\n props: P\n state: S\n private _stateSignal: ReturnType<typeof signal<S>>\n\n constructor(props: P) {\n this.props = props\n this.state = {} as S\n this._stateSignal = signal<S>(this.state)\n }\n\n /**\n * Update state — accepts a partial state object or an updater function.\n * Merges into existing state (shallow merge, like Preact/React).\n */\n setState(partial: Partial<S> | ((prev: S) => Partial<S>)): void {\n batch(() => {\n const current = this._stateSignal()\n const update =\n typeof partial === 'function' ? (partial as (prev: S) => Partial<S>)(current) : partial\n const next = { ...current, ...update } as S\n this.state = next\n this._stateSignal.set(next)\n })\n }\n\n /**\n * Force a re-render. In Pyreon this triggers the state signal to re-fire.\n */\n forceUpdate(): void {\n this._stateSignal.set({ ...this.state })\n }\n\n /**\n * Override in subclass to return VNode tree.\n */\n render(): VNodeChild {\n return null\n }\n}\n\n// ─── cloneElement ────────────────────────────────────────────────────────────\n\n/**\n * Clone a VNode with merged props (like Preact's cloneElement).\n */\nexport function cloneElement(vnode: VNode, props?: Props, ...children: VNodeChild[]): VNode {\n const mergedProps = { ...vnode.props, ...(props ?? {}) }\n const mergedChildren = children.length > 0 ? children : vnode.children\n return {\n type: vnode.type,\n props: mergedProps,\n children: mergedChildren,\n key: (props?.key as string | number | null) ?? vnode.key,\n }\n}\n\n// ─── toChildArray ────────────────────────────────────────────────────────────\n\n/**\n * Flatten children into a flat array, filtering out null/undefined/boolean.\n * Matches Preact's `toChildArray` utility.\n */\ntype NestedChildren = VNodeChild | NestedChildren[]\n\nexport function toChildArray(children: NestedChildren): VNodeChild[] {\n const result: VNodeChild[] = []\n flatten(children, result)\n return result\n}\n\nfunction flatten(value: NestedChildren, out: VNodeChild[]): void {\n if (value == null || typeof value === 'boolean') return\n if (Array.isArray(value)) {\n for (const child of value) {\n flatten(child, out)\n }\n } else {\n out.push(value as VNodeChild)\n }\n}\n\n// ─── isValidElement ──────────────────────────────────────────────────────────\n\n/**\n * Check if a value is a VNode (like Preact's isValidElement).\n */\nexport function isValidElement(x: unknown): x is VNode {\n return (\n x !== null &&\n typeof x === 'object' &&\n 'type' in (x as Record<string, unknown>) &&\n 'props' in (x as Record<string, unknown>) &&\n 'children' in (x as Record<string, unknown>)\n )\n}\n\n// ─── options ─────────────────────────────────────────────────────────────────\n\n/**\n * Preact's plugin/hook system. Exposed as an empty object for compatibility\n * with libraries that check for `options._hook`, `options.vnode`, etc.\n */\nexport const options: Record<string, unknown> = {}\n"],"mappings":";;;;;;AA+BA,MAAa,gBAAgB;;;;;AAU7B,SAAgB,OAAO,OAAmB,WAA0B;AAClE,OAAM,OAAO,UAAU;;;;;;AAOzB,SAAgB,QAAQ,OAAmB,WAA0B;AACnE,aAAY,WAAW,MAAe;;;;;AAcxC,SAAgB,cAAiB,cAAmC;CAClE,MAAM,MAAMA,gBAAuB,aAAa;CAChD,MAAM,aAAa,UAA+C;AAChE,UAAQ,KAAK,MAAM,MAAM;AACzB,SAAO,MAAM;;AAEf,QAAO;EAAE,GAAG;EAAK;EAAU;;;;;;;;AAiB7B,IAAa,YAAb,MAGE;CACA;CACA;CACA,AAAQ;CAER,YAAY,OAAU;AACpB,OAAK,QAAQ;AACb,OAAK,QAAQ,EAAE;AACf,OAAK,eAAe,OAAU,KAAK,MAAM;;;;;;CAO3C,SAAS,SAAuD;AAC9D,cAAY;GACV,MAAM,UAAU,KAAK,cAAc;GACnC,MAAM,SACJ,OAAO,YAAY,aAAc,QAAoC,QAAQ,GAAG;GAClF,MAAM,OAAO;IAAE,GAAG;IAAS,GAAG;IAAQ;AACtC,QAAK,QAAQ;AACb,QAAK,aAAa,IAAI,KAAK;IAC3B;;;;;CAMJ,cAAoB;AAClB,OAAK,aAAa,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC;;;;;CAM1C,SAAqB;AACnB,SAAO;;;;;;AASX,SAAgB,aAAa,OAAc,OAAe,GAAG,UAA+B;CAC1F,MAAM,cAAc;EAAE,GAAG,MAAM;EAAO,GAAI,SAAS,EAAE;EAAG;CACxD,MAAM,iBAAiB,SAAS,SAAS,IAAI,WAAW,MAAM;AAC9D,QAAO;EACL,MAAM,MAAM;EACZ,OAAO;EACP,UAAU;EACV,KAAM,OAAO,OAAkC,MAAM;EACtD;;AAWH,SAAgB,aAAa,UAAwC;CACnE,MAAM,SAAuB,EAAE;AAC/B,SAAQ,UAAU,OAAO;AACzB,QAAO;;AAGT,SAAS,QAAQ,OAAuB,KAAyB;AAC/D,KAAI,SAAS,QAAQ,OAAO,UAAU,UAAW;AACjD,KAAI,MAAM,QAAQ,MAAM,CACtB,MAAK,MAAM,SAAS,MAClB,SAAQ,OAAO,IAAI;KAGrB,KAAI,KAAK,MAAoB;;;;;AASjC,SAAgB,eAAe,GAAwB;AACrD,QACE,MAAM,QACN,OAAO,MAAM,YACb,UAAW,KACX,WAAY,KACZ,cAAe;;;;;;AAUnB,MAAa,UAAmC,EAAE"}
1
+ {"version":3,"file":"index.js","names":["pyreonCreateContext"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @pyreon/preact-compat\n *\n * Preact-compatible API shim that runs on Pyreon's reactive engine.\n *\n * Provides the core Preact API surface: h, Fragment, render, hydrate,\n * Component class, PureComponent, createContext, createRef, cloneElement,\n * toChildArray, isValidElement, createPortal, lazy, Suspense, ErrorBoundary,\n * and the options hook object.\n *\n * For hooks, import from \"@pyreon/preact-compat/hooks\".\n * For signals, import from \"@pyreon/preact-compat/signals\".\n */\n\nimport type { ComponentFn, Props, VNode, VNodeChild } from '@pyreon/core'\nimport {\n createRef,\n ErrorBoundary,\n Fragment,\n lazy,\n Portal,\n provide,\n Suspense,\n createContext as pyreonCreateContext,\n h as pyreonH,\n useContext,\n} from '@pyreon/core'\nimport { batch, signal } from '@pyreon/reactivity'\nimport { hydrateRoot, mount } from '@pyreon/runtime-dom'\n\n// ─── Core JSX ────────────────────────────────────────────────────────────────\n\n/** Preact's hyperscript function — maps directly to Pyreon's h() */\nexport { pyreonH as h }\n\n/** Alias: Preact also exports createElement */\nexport const createElement = pyreonH\n\nexport { Fragment }\n\n// ─── Render / Hydrate ────────────────────────────────────────────────────────\n\n/**\n * Preact's `render(vnode, container)`.\n * Maps to Pyreon's `mount(vnode, container)`.\n */\nexport function render(vnode: VNodeChild, container: Element): void {\n mount(vnode, container)\n}\n\n/**\n * Preact's `hydrate(vnode, container)`.\n * Maps to Pyreon's `hydrateRoot(container, vnode)`.\n */\nexport function hydrate(vnode: VNodeChild, container: Element): void {\n hydrateRoot(container, vnode as VNode)\n}\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\nexport interface PreactContext<T> {\n readonly id: symbol\n readonly defaultValue: T\n Provider: ComponentFn<{ value: T; children?: VNodeChild }>\n}\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 * Preact-compatible createContext — returns a context with a `.Provider` component.\n */\nexport function createContext<T>(defaultValue: T): PreactContext<T> {\n const ctx = pyreonCreateContext<T>(defaultValue)\n const Provider = ((props: { value: T; children?: VNodeChild }) => {\n provide(ctx, props.value)\n return props.children\n }) as ComponentFn<{ value: T; children?: VNodeChild }>\n // Mark as native so jsx() doesn't wrap it with wrapCompatComponent\n ;(Provider as unknown as Record<symbol, boolean>)[NATIVE_COMPONENT] = true\n return { ...ctx, Provider }\n}\n\nexport { useContext }\n\n// ─── Refs ────────────────────────────────────────────────────────────────────\n\nexport { createRef }\n\n// ─── Component class ─────────────────────────────────────────────────────────\n\n/**\n * Preact-compatible class-based Component.\n *\n * Wraps Pyreon's signal-based reactivity so `setState` triggers re-renders.\n * Usage: `class MyComp extends Component { render() { ... } }`\n */\nexport class Component<\n P extends Props = Props,\n S extends Record<string, unknown> = Record<string, unknown>,\n> {\n props: P\n state: S\n _stateSignal: ReturnType<typeof signal<S>>\n _lastResult?: VNodeChild\n\n // Lifecycle methods (overridden by subclasses)\n componentDidMount?(): void\n componentDidUpdate?(): void\n componentWillUnmount?(): void\n shouldComponentUpdate?(nextProps: P, nextState: S): boolean\n\n constructor(props: P) {\n this.props = props\n this.state = {} as S\n this._stateSignal = signal<S>(this.state)\n }\n\n /**\n * Update state — accepts a partial state object or an updater function.\n * Merges into existing state (shallow merge, like Preact/React).\n */\n setState(partial: Partial<S> | ((prev: S) => Partial<S>)): void {\n batch(() => {\n const current = this._stateSignal()\n const update =\n typeof partial === 'function' ? (partial as (prev: S) => Partial<S>)(current) : partial\n const next = { ...current, ...update } as S\n this.state = next\n this._stateSignal.set(next)\n })\n }\n\n /**\n * Force a re-render. In Pyreon this triggers the state signal to re-fire.\n */\n forceUpdate(): void {\n this._stateSignal.set({ ...this.state })\n }\n\n /**\n * Override in subclass to return VNode tree.\n */\n render(): VNodeChild {\n return null\n }\n}\n\n// ─── PureComponent ──────────────────────────────────────────────────────────\n\n/**\n * Preact-compatible PureComponent — extends Component.\n * In Pyreon's compat layer this behaves identically to Component\n * (signal-based reactivity already avoids unnecessary re-renders).\n */\nexport class PureComponent<\n P extends Props = Props,\n S extends Record<string, unknown> = Record<string, unknown>,\n> extends Component<P, S> {}\n\n// ─── cloneElement ────────────────────────────────────────────────────────────\n\n/**\n * Clone a VNode with merged props (like Preact's cloneElement).\n */\nexport function cloneElement(vnode: VNode, props?: Props, ...children: VNodeChild[]): VNode {\n const mergedProps = props ? { ...vnode.props, ...props } : { ...vnode.props }\n const mergedChildren = children.length > 0 ? children : vnode.children\n return {\n type: vnode.type,\n props: mergedProps,\n children: mergedChildren,\n key: (props?.key as string | number | null) ?? vnode.key,\n }\n}\n\n// ─── toChildArray ────────────────────────────────────────────────────────────\n\n/**\n * Flatten children into a flat array, filtering out null/undefined/boolean.\n * Matches Preact's `toChildArray` utility.\n */\ntype NestedChildren = VNodeChild | NestedChildren[]\n\nexport function toChildArray(children: NestedChildren): VNodeChild[] {\n const result: VNodeChild[] = []\n flatten(children, result)\n return result\n}\n\nfunction flatten(value: NestedChildren, out: VNodeChild[]): void {\n if (value == null || typeof value === 'boolean') return\n if (Array.isArray(value)) {\n for (const child of value) {\n flatten(child, out)\n }\n } else {\n out.push(value as VNodeChild)\n }\n}\n\n// ─── isValidElement ──────────────────────────────────────────────────────────\n\n/**\n * Check if a value is a VNode (like Preact's isValidElement).\n */\nexport function isValidElement(x: unknown): x is VNode {\n return (\n x !== null &&\n typeof x === 'object' &&\n 'type' in (x as Record<string, unknown>) &&\n 'props' in (x as Record<string, unknown>) &&\n 'children' in (x as Record<string, unknown>)\n )\n}\n\n// ─── createPortal ───────────────────────────────────────────────────────────\n\n/**\n * Preact-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 { ErrorBoundary, lazy, Suspense }\n\n// ─── options ─────────────────────────────────────────────────────────────────\n\n/**\n * Preact's plugin/hook system. Exposed as an empty object for compatibility\n * with libraries that check for `options._hook`, `options.vnode`, etc.\n */\nexport const options: Record<string, unknown> = {}\n\n// ─── version ────────────────────────────────────────────────────────────────\n\nexport const version = '10.0.0-pyreon'\n"],"mappings":";;;;;;AAoCA,MAAa,gBAAgB;;;;;AAU7B,SAAgB,OAAO,OAAmB,WAA0B;AAClE,OAAM,OAAO,UAAU;;;;;;AAOzB,SAAgB,QAAQ,OAAmB,WAA0B;AACnE,aAAY,WAAW,MAAe;;AAYxC,MAAM,mBAAmB,OAAO,IAAI,uBAAuB;;;;AAK3D,SAAgB,cAAiB,cAAmC;CAClE,MAAM,MAAMA,gBAAuB,aAAa;CAChD,MAAM,aAAa,UAA+C;AAChE,UAAQ,KAAK,MAAM,MAAM;AACzB,SAAO,MAAM;;AAGd,CAAC,SAAgD,oBAAoB;AACtE,QAAO;EAAE,GAAG;EAAK;EAAU;;;;;;;;AAiB7B,IAAa,YAAb,MAGE;CACA;CACA;CACA;CACA;CAQA,YAAY,OAAU;AACpB,OAAK,QAAQ;AACb,OAAK,QAAQ,EAAE;AACf,OAAK,eAAe,OAAU,KAAK,MAAM;;;;;;CAO3C,SAAS,SAAuD;AAC9D,cAAY;GACV,MAAM,UAAU,KAAK,cAAc;GACnC,MAAM,SACJ,OAAO,YAAY,aAAc,QAAoC,QAAQ,GAAG;GAClF,MAAM,OAAO;IAAE,GAAG;IAAS,GAAG;IAAQ;AACtC,QAAK,QAAQ;AACb,QAAK,aAAa,IAAI,KAAK;IAC3B;;;;;CAMJ,cAAoB;AAClB,OAAK,aAAa,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC;;;;;CAM1C,SAAqB;AACnB,SAAO;;;;;;;;AAWX,IAAa,gBAAb,cAGU,UAAgB;;;;AAO1B,SAAgB,aAAa,OAAc,OAAe,GAAG,UAA+B;CAC1F,MAAM,cAAc,QAAQ;EAAE,GAAG,MAAM;EAAO,GAAG;EAAO,GAAG,EAAE,GAAG,MAAM,OAAO;CAC7E,MAAM,iBAAiB,SAAS,SAAS,IAAI,WAAW,MAAM;AAC9D,QAAO;EACL,MAAM,MAAM;EACZ,OAAO;EACP,UAAU;EACV,KAAM,OAAO,OAAkC,MAAM;EACtD;;AAWH,SAAgB,aAAa,UAAwC;CACnE,MAAM,SAAuB,EAAE;AAC/B,SAAQ,UAAU,OAAO;AACzB,QAAO;;AAGT,SAAS,QAAQ,OAAuB,KAAyB;AAC/D,KAAI,SAAS,QAAQ,OAAO,UAAU,UAAW;AACjD,KAAI,MAAM,QAAQ,MAAM,CACtB,MAAK,MAAM,SAAS,MAClB,SAAQ,OAAO,IAAI;KAGrB,KAAI,KAAK,MAAoB;;;;;AASjC,SAAgB,eAAe,GAAwB;AACrD,QACE,MAAM,QACN,OAAO,MAAM,YACb,UAAW,KACX,WAAY,KACZ,cAAe;;;;;AASnB,SAAgB,aAAa,UAAsB,QAA6B;AAC9E,QAAO,OAAO;EAAE;EAAQ;EAAU,CAAC;;;;;;AAarC,MAAa,UAAmC,EAAE;AAIlD,MAAa,UAAU"}
@@ -1,4 +1,4 @@
1
- import { Fragment, h } from "@pyreon/core";
1
+ import { Fragment, h, onUnmount } from "@pyreon/core";
2
2
  import { signal } from "@pyreon/reactivity";
3
3
 
4
4
  //#region src/jsx-runtime.ts
@@ -32,10 +32,61 @@ function scheduleEffects(ctx, entries) {
32
32
  }
33
33
  });
34
34
  }
35
+ const NATIVE_COMPONENT = Symbol.for("pyreon:native-compat");
36
+ function isClassComponent(type) {
37
+ return type.prototype != null && typeof type.prototype.render === "function";
38
+ }
39
+ function wrapClassComponent(ClassComp) {
40
+ const wrapped = ((props) => {
41
+ const instance = new ClassComp(props);
42
+ const version = signal(0);
43
+ let updateScheduled = false;
44
+ const origSetState = instance.setState.bind(instance);
45
+ instance.setState = (partial) => {
46
+ origSetState(partial);
47
+ if (!updateScheduled) {
48
+ updateScheduled = true;
49
+ queueMicrotask(() => {
50
+ updateScheduled = false;
51
+ version.set(version.peek() + 1);
52
+ });
53
+ }
54
+ };
55
+ instance.forceUpdate = () => {
56
+ version.set(version.peek() + 1);
57
+ };
58
+ let didMountFired = false;
59
+ onUnmount(() => {
60
+ if (typeof instance.componentWillUnmount === "function") instance.componentWillUnmount();
61
+ });
62
+ return () => {
63
+ const ver = version();
64
+ instance.props = props;
65
+ if (didMountFired && ver > 0 && typeof instance.shouldComponentUpdate === "function") {
66
+ if (!instance.shouldComponentUpdate(props, instance.state)) return instance._lastResult;
67
+ }
68
+ const result = instance.render();
69
+ instance._lastResult = result;
70
+ if (!didMountFired) {
71
+ didMountFired = true;
72
+ if (typeof instance.componentDidMount === "function") queueMicrotask(() => instance.componentDidMount());
73
+ } else if (ver > 0) {
74
+ if (typeof instance.componentDidUpdate === "function") queueMicrotask(() => instance.componentDidUpdate());
75
+ }
76
+ return result;
77
+ };
78
+ });
79
+ return wrapped;
80
+ }
35
81
  const _wrapperCache = /* @__PURE__ */ new WeakMap();
36
82
  function wrapCompatComponent(preactComponent) {
37
83
  let wrapped = _wrapperCache.get(preactComponent);
38
84
  if (wrapped) return wrapped;
85
+ if (isClassComponent(preactComponent)) {
86
+ wrapped = wrapClassComponent(preactComponent);
87
+ _wrapperCache.set(preactComponent, wrapped);
88
+ return wrapped;
89
+ }
39
90
  wrapped = ((props) => {
40
91
  const ctx = {
41
92
  hooks: [],
@@ -54,6 +105,13 @@ function wrapCompatComponent(preactComponent) {
54
105
  if (!ctx.unmounted) version.set(version.peek() + 1);
55
106
  });
56
107
  };
108
+ onUnmount(() => {
109
+ ctx.unmounted = true;
110
+ for (const hook of ctx.hooks) if (hook && typeof hook === "object" && "cleanup" in hook) {
111
+ const entry = hook;
112
+ if (typeof entry.cleanup === "function") entry.cleanup();
113
+ }
114
+ });
57
115
  return () => {
58
116
  version();
59
117
  beginRender(ctx);
@@ -75,11 +133,45 @@ function jsx(type, props, key) {
75
133
  ...rest,
76
134
  key
77
135
  } : rest;
78
- if (typeof type === "function") return h(wrapCompatComponent(type), children !== void 0 ? {
79
- ...propsWithKey,
80
- children
81
- } : propsWithKey);
82
- return h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);
136
+ if (typeof type === "function") {
137
+ const componentProps = children !== void 0 ? {
138
+ ...propsWithKey,
139
+ children
140
+ } : propsWithKey;
141
+ if (type[NATIVE_COMPONENT]) return h(type, componentProps);
142
+ return h(wrapCompatComponent(type), componentProps);
143
+ }
144
+ const childArray = children === void 0 ? [] : Array.isArray(children) ? children : [children];
145
+ if (typeof type === "string") {
146
+ if (propsWithKey.className !== void 0) {
147
+ propsWithKey.class = propsWithKey.className;
148
+ delete propsWithKey.className;
149
+ }
150
+ if (propsWithKey.htmlFor !== void 0) {
151
+ propsWithKey.for = propsWithKey.htmlFor;
152
+ delete propsWithKey.htmlFor;
153
+ }
154
+ if ((type === "input" || type === "textarea" || type === "select") && propsWithKey.onChange !== void 0) {
155
+ if (propsWithKey.onInput === void 0) propsWithKey.onInput = propsWithKey.onChange;
156
+ delete propsWithKey.onChange;
157
+ }
158
+ if (propsWithKey.autoFocus !== void 0) {
159
+ propsWithKey.autofocus = propsWithKey.autoFocus;
160
+ delete propsWithKey.autoFocus;
161
+ }
162
+ if (type === "input" || type === "textarea") {
163
+ if (propsWithKey.defaultValue !== void 0 && propsWithKey.value === void 0) {
164
+ propsWithKey.value = propsWithKey.defaultValue;
165
+ delete propsWithKey.defaultValue;
166
+ }
167
+ if (propsWithKey.defaultChecked !== void 0 && propsWithKey.checked === void 0) {
168
+ propsWithKey.checked = propsWithKey.defaultChecked;
169
+ delete propsWithKey.defaultChecked;
170
+ }
171
+ }
172
+ delete propsWithKey.suppressHydrationWarning;
173
+ }
174
+ return h(type, propsWithKey, ...childArray);
83
175
  }
84
176
  const jsxs = jsx;
85
177