@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.
- 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/hooks.js.map +1 -1
- package/lib/index.js +24 -5
- package/lib/index.js.map +1 -1
- package/lib/jsx-runtime.js +98 -6
- package/lib/jsx-runtime.js.map +1 -1
- package/lib/signals.js +2 -2
- package/lib/signals.js.map +1 -1
- package/lib/types/hooks.d.ts +49 -2
- package/lib/types/hooks.d.ts.map +1 -1
- package/lib/types/index.d.ts +20 -3
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/jsx-runtime.d.ts.map +1 -1
- package/lib/types/signals.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/hooks.ts +157 -42
- package/src/index.ts +50 -4
- package/src/jsx-runtime.ts +147 -2
- package/src/signals.ts +2 -2
- package/src/tests/new-apis.test.ts +1084 -0
package/lib/jsx-runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-runtime.js","names":[],"sources":["../src/jsx-runtime.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"],"mappings":";;;;AAoCA,IAAI,cAAoC;AACxC,IAAI,aAAa;AAUjB,SAAgB,YAAY,KAA0B;AACpD,eAAc;AACd,cAAa;AACb,KAAI,iBAAiB,EAAE;AACvB,KAAI,uBAAuB,EAAE;;AAG/B,SAAgB,YAAkB;AAChC,eAAc;AACd,cAAa;;AAKf,SAAS,iBAAiB,SAA8B;AACtD,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,MAAM,QAAS,OAAM,SAAS;EAClC,MAAM,UAAU,MAAM,IAAI;AAC1B,QAAM,UAAU,OAAO,YAAY,aAAa,UAAU;;;AAI9D,SAAS,gBAAgB,KAAoB,SAA8B;AACzE,KAAI,QAAQ,WAAW,EAAG;AAC1B,sBAAqB;AACnB,OAAK,MAAM,SAAS,SAAS;AAC3B,OAAI,IAAI,UAAW;AACnB,OAAI,MAAM,QAAS,OAAM,SAAS;GAClC,MAAM,UAAU,MAAM,IAAI;AAC1B,SAAM,UAAU,OAAO,YAAY,aAAa,UAAU;;GAE5D;;AAKJ,MAAM,gCAAgB,IAAI,SAAgC;AAE1D,SAAS,oBAAoB,iBAAwC;CACnE,IAAI,UAAU,cAAc,IAAI,gBAAgB;AAChD,KAAI,QAAS,QAAO;AAIpB,aAAY,UAAiB;EAC3B,MAAM,MAAqB;GACzB,OAAO,EAAE;GACT,wBAAwB;GAGxB,gBAAgB,EAAE;GAClB,sBAAsB,EAAE;GACxB,WAAW;GACZ;EAED,MAAM,UAAU,OAAO,EAAE;EACzB,IAAI,kBAAkB;AAEtB,MAAI,yBAAyB;AAC3B,OAAI,IAAI,aAAa,gBAAiB;AACtC,qBAAkB;AAClB,wBAAqB;AACnB,sBAAkB;AAClB,QAAI,CAAC,IAAI,UAAW,SAAQ,IAAI,QAAQ,MAAM,GAAG,EAAE;KACnD;;AAIJ,eAAa;AACX,YAAS;AACT,eAAY,IAAI;GAChB,MAAM,SAAU,gBAAgC,MAAM;GACtD,MAAM,gBAAgB,IAAI;GAC1B,MAAM,UAAU,IAAI;AACpB,cAAW;AAEX,oBAAiB,cAAc;AAC/B,mBAAgB,KAAK,QAAQ;AAE7B,UAAO;;;AAIX,eAAc,IAAI,iBAAiB,QAAQ;AAC3C,QAAO;;AAKT,SAAgB,IACd,MACA,OACA,KACO;CACP,MAAM,EAAE,UAAU,GAAG,SAAS;CAC9B,MAAM,eAAgB,OAAO,OAAO;EAAE,GAAG;EAAM;EAAK,GAAG;AAEvD,KAAI,OAAO,SAAS,WAIlB,QAAO,EAFS,oBAAoB,KAAK,EAClB,aAAa,SAAY;EAAE,GAAG;EAAc;EAAU,GAAG,aAC/C;AAMnC,QAAO,EAAE,MAAM,cAAc,GAFV,aAAa,SAAY,EAAE,GAAG,MAAM,QAAQ,SAAS,GAAG,WAAW,CAAC,SAAS,CAEnC;;AAG/D,MAAa,OAAO"}
|
|
1
|
+
{"version":3,"file":"jsx-runtime.js","names":[],"sources":["../src/jsx-runtime.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"],"mappings":"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"}
|
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/signals.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signals.js","names":["pyreonSignal","pyreonComputed","pyreonEffect"],"sources":["../src/signals.ts"],"sourcesContent":["/**\n * @pyreon/preact-compat/signals\n *\n * Preact Signals compatibility layer (`@preact/signals` style).\n * Wraps Pyreon's signal/computed in `{ value }` accessor objects.\n */\n\nimport type { Effect } from '@pyreon/reactivity'\nimport {\n batch as pyreonBatch,\n computed as pyreonComputed,\n effect as pyreonEffect,\n signal as pyreonSignal,\n} from '@pyreon/reactivity'\n\n// ─── Signal ──────────────────────────────────────────────────────────────────\n\nexport interface ReadonlySignal<T> {\n readonly value: T\n peek(): T\n}\n\nexport interface WritableSignal<T> extends ReadonlySignal<T> {\n value: T\n}\n\n/**\n * Create a Preact-style signal with `.value` accessor.\n *\n * @example\n * const count = signal(0)\n * count.value++ // write\n * console.log(count.value) // read (tracked)\n */\nexport function signal<T>(initial: T): WritableSignal<T> {\n const s = pyreonSignal<T>(initial)\n return {\n get value(): T {\n return s()\n },\n set value(v: T) {\n s.set(v)\n },\n peek(): T {\n return s.peek()\n },\n }\n}\n\n// ─── Computed ────────────────────────────────────────────────────────────────\n\n/**\n * Create a Preact-style computed with `.value` accessor.\n *\n * @example\n * const doubled = computed(() => count.value * 2)\n * console.log(doubled.value)\n */\nexport function computed<T>(fn: () => T): ReadonlySignal<T> {\n const c = pyreonComputed(fn)\n return {\n get value(): T {\n return c()\n },\n peek(): T {\n
|
|
1
|
+
{"version":3,"file":"signals.js","names":["pyreonSignal","pyreonComputed","pyreonRunUntracked","pyreonEffect"],"sources":["../src/signals.ts"],"sourcesContent":["/**\n * @pyreon/preact-compat/signals\n *\n * Preact Signals compatibility layer (`@preact/signals` style).\n * Wraps Pyreon's signal/computed in `{ value }` accessor objects.\n */\n\nimport type { Effect } from '@pyreon/reactivity'\nimport {\n batch as pyreonBatch,\n computed as pyreonComputed,\n effect as pyreonEffect,\n runUntracked as pyreonRunUntracked,\n signal as pyreonSignal,\n} from '@pyreon/reactivity'\n\n// ─── Signal ──────────────────────────────────────────────────────────────────\n\nexport interface ReadonlySignal<T> {\n readonly value: T\n peek(): T\n}\n\nexport interface WritableSignal<T> extends ReadonlySignal<T> {\n value: T\n}\n\n/**\n * Create a Preact-style signal with `.value` accessor.\n *\n * @example\n * const count = signal(0)\n * count.value++ // write\n * console.log(count.value) // read (tracked)\n */\nexport function signal<T>(initial: T): WritableSignal<T> {\n const s = pyreonSignal<T>(initial)\n return {\n get value(): T {\n return s()\n },\n set value(v: T) {\n s.set(v)\n },\n peek(): T {\n return s.peek()\n },\n }\n}\n\n// ─── Computed ────────────────────────────────────────────────────────────────\n\n/**\n * Create a Preact-style computed with `.value` accessor.\n *\n * @example\n * const doubled = computed(() => count.value * 2)\n * console.log(doubled.value)\n */\nexport function computed<T>(fn: () => T): ReadonlySignal<T> {\n const c = pyreonComputed(fn)\n return {\n get value(): T {\n return c()\n },\n peek(): T {\n return pyreonRunUntracked(() => c())\n },\n }\n}\n\n// ─── Effect ──────────────────────────────────────────────────────────────────\n\n/**\n * Run a side-effect that auto-tracks signal reads.\n * Returns a dispose function.\n */\nexport function effect(fn: () => void | (() => void)): () => void {\n // Pyreon's effect() natively supports cleanup return values\n const e: Effect = pyreonEffect(fn)\n return () => {\n e.dispose()\n }\n}\n\n// ─── Batch ───────────────────────────────────────────────────────────────────\n\n/**\n * Batch multiple signal writes into a single update.\n */\nexport { pyreonBatch as batch }\n"],"mappings":";;;;;;;;;;;AAmCA,SAAgB,OAAU,SAA+B;CACvD,MAAM,IAAIA,SAAgB,QAAQ;AAClC,QAAO;EACL,IAAI,QAAW;AACb,UAAO,GAAG;;EAEZ,IAAI,MAAM,GAAM;AACd,KAAE,IAAI,EAAE;;EAEV,OAAU;AACR,UAAO,EAAE,MAAM;;EAElB;;;;;;;;;AAYH,SAAgB,SAAY,IAAgC;CAC1D,MAAM,IAAIC,WAAe,GAAG;AAC5B,QAAO;EACL,IAAI,QAAW;AACb,UAAO,GAAG;;EAEZ,OAAU;AACR,UAAOC,mBAAyB,GAAG,CAAC;;EAEvC;;;;;;AASH,SAAgB,OAAO,IAA2C;CAEhE,MAAM,IAAYC,SAAa,GAAG;AAClC,cAAa;AACX,IAAE,SAAS"}
|
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/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks2.d.ts","names":[],"sources":["../../../src/hooks.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"hooks2.d.ts","names":[],"sources":["../../../src/hooks.ts"],"mappings":";;;;;;;;;iBAoDgB,QAAA,GAAA,CAAY,OAAA,EAAS,CAAA,UAAW,CAAA,KAAM,CAAA,GAAI,CAAA,EAAG,CAAA,KAAM,IAAA,EAAM,CAAA,KAAM,CAAA;;;;;iBA6B/D,SAAA,CAAU,EAAA,6BAA+B,IAAA;;;AAAzD;iBAwBgB,eAAA,CAAgB,EAAA,6BAA+B,IAAA;;;;iBAuB/C,OAAA,GAAA,CAAW,EAAA,QAAU,CAAA,EAAG,IAAA,cAAkB,CAAA;;;;iBAuB1C,WAAA,eAA0B,IAAA,sBAAA,CAA2B,EAAA,EAAI,CAAA,EAAG,IAAA,cAAkB,CAAA;AAvB9F;;;AAAA,iBAgCgB,MAAA,GAAA,CAAU,OAAA,GAAU,CAAA;EAAM,OAAA,EAAS,CAAA;AAAA;;;;;;AATnD;iBA6BgB,UAAA,MAAA,CACd,OAAA,GAAU,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,CAAA,KAAM,CAAA,EAClC,UAAA,EAAY,CAAA,UAAW,CAAA,GACvB,IAAA,IAAQ,GAAA,EAAK,CAAA,KAAM,CAAA,IACjB,CAAA,GAAI,MAAA,EAAQ,CAAA;;;;iBAqCA,KAAA,CAAA;;;;;;;AA7DhB;iBAiFgB,IAAA,WAAe,MAAA,kBAAA,CAC7B,SAAA,GAAY,KAAA,EAAO,CAAA,KAAM,UAAA,EACzB,QAAA,IAAY,SAAA,EAAW,CAAA,EAAG,SAAA,EAAW,CAAA,gBACnC,KAAA,EAAO,CAAA,KAAM,UAAA;;;;;;iBA6CD,UAAA,WAAqB,MAAA,kBAAA,CACnC,MAAA,GAAS,KAAA,EAAO,CAAA,EAAG,GAAA;EAAO,OAAA;AAAA,aAA8B,UAAA,IACtD,KAAA,EAAO,CAAA;EAAM,GAAA;IAAQ,OAAA;EAAA;AAAA,MAAgC,UAAA;;;;iBAezC,mBAAA,GAAA,CACd,GAAA;EAAO,OAAA,EAAS,CAAA;AAAA,sBAChB,IAAA,QAAY,CAAA,EACZ,IAAA;;;;iBAec,aAAA,GAAA,CAAiB,MAAA,EAAQ,CAAA,EAAG,OAAA,IAAW,CAAA,EAAG,CAAA;;;;;;;;;iBAY1C,aAAA,CAAA,cAA4B,EAAA;;;;;;;;iBAa5B,gBAAA,GAAA,CAAoB,KAAA,EAAO,CAAA,GAAI,CAAA"}
|
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/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/index.ts"],"mappings":";;;;;cAoCa,aAAA,SAAa,OAAA;;;;;iBAUV,MAAA,CAAO,KAAA,EAAO,UAAA,EAAY,SAAA,EAAW,OAAA;;;;AAcrD;iBANgB,OAAA,CAAQ,KAAA,EAAO,UAAA,EAAY,SAAA,EAAW,OAAA;AAAA,UAMrC,aAAA;EAAA,SACN,EAAA;EAAA,SACA,YAAA,EAAc,CAAA;EACvB,QAAA,EAAU,WAAA;IAAc,KAAA,EAAO,CAAA;IAAG,QAAA,GAAW,UAAA;EAAA;AAAA;;;;iBAS/B,aAAA,GAAA,CAAiB,YAAA,EAAc,CAAA,GAAI,aAAA,CAAc,CAAA;;;;;;AAAjE;cAyBa,SAAA,WACD,KAAA,GAAQ,KAAA,YACR,MAAA,oBAA0B,MAAA;EAEpC,KAAA,EAAO,CAAA;EACP,KAAA,EAAO,CAAA;EACP,YAAA,EAAc,UAAA,QAAkB,MAAA,CAAO,CAAA;EACvC,WAAA,GAAc,UAAA;EAGd,iBAAA,CAAA,CAAA;EACA,kBAAA,CAAA,CAAA;EACA,oBAAA,CAAA,CAAA;EACA,qBAAA,CAAA,CAAuB,SAAA,EAAW,CAAA,EAAG,SAAA,EAAW,CAAA;cAEpC,KAAA,EAAO,CAAA;EAxC8B;;;;EAkDjD,QAAA,CAAS,OAAA,EAAS,OAAA,CAAQ,CAAA,MAAO,IAAA,EAAM,CAAA,KAAM,OAAA,CAAQ,CAAA;EAzBjC;;;EAuCpB,WAAA,CAAA;EArCU;;;EA4CV,MAAA,CAAA,GAAU,UAAA;AAAA;;;;;;cAYC,aAAA,WACD,KAAA,GAAQ,KAAA,YACR,MAAA,oBAA0B,MAAA,2BAC5B,SAAA,CAAU,CAAA,EAAG,CAAA;;;;iBAOP,YAAA,CAAa,KAAA,EAAO,KAAA,EAAO,KAAA,GAAQ,KAAA,KAAU,QAAA,EAAU,UAAA,KAAe,KAAA;;;;;KAiBjF,cAAA,GAAiB,UAAA,GAAa,cAAA;AAAA,iBAEnB,YAAA,CAAa,QAAA,EAAU,cAAA,GAAiB,UAAA;;;;iBAsBxC,cAAA,CAAe,CAAA,YAAa,CAAA,IAAK,KAAA;;;;iBAejC,YAAA,CAAa,QAAA,EAAU,UAAA,EAAY,MAAA,EAAQ,OAAA,GAAU,UAAA;;;;;cAcxD,OAAA,EAAS,MAAA;AAAA,cAIT,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-runtime2.d.ts","names":[],"sources":["../../../src/jsx-runtime.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"jsx-runtime2.d.ts","names":[],"sources":["../../../src/jsx-runtime.ts"],"mappings":";;;iBAyOgB,GAAA,CACd,IAAA,WAAe,WAAA,WACf,KAAA,EAAO,KAAA;EAAU,QAAA,GAAW,UAAA,GAAa,UAAA;AAAA,GACzC,GAAA,4BACC,KAAA;AAAA,cAiEU,IAAA,SAAI,GAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signals2.d.ts","names":[],"sources":["../../../src/signals.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"signals2.d.ts","names":[],"sources":["../../../src/signals.ts"],"mappings":";;;UAkBiB,cAAA;EAAA,SACN,KAAA,EAAO,CAAA;EAChB,IAAA,IAAQ,CAAA;AAAA;AAAA,UAGO,cAAA,YAA0B,cAAA,CAAe,CAAA;EACxD,KAAA,EAAO,CAAA;AAAA;AADT;;;;;;;;AAAA,iBAYgB,MAAA,GAAA,CAAU,OAAA,EAAS,CAAA,GAAI,cAAA,CAAe,CAAA;;;;;;;AAAtD;iBAwBgB,QAAA,GAAA,CAAY,EAAA,QAAU,CAAA,GAAI,cAAA,CAAe,CAAA;;;;;iBAkBzC,MAAA,CAAO,EAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/preact-compat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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": {
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"prepublishOnly": "bun run build"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@pyreon/core": "^0.
|
|
66
|
-
"@pyreon/reactivity": "^0.
|
|
67
|
-
"@pyreon/runtime-dom": "^0.
|
|
65
|
+
"@pyreon/core": "^0.14.0",
|
|
66
|
+
"@pyreon/reactivity": "^0.14.0",
|
|
67
|
+
"@pyreon/runtime-dom": "^0.14.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@happy-dom/global-registrator": "^20.8.9",
|
package/src/hooks.ts
CHANGED
|
@@ -32,30 +32,45 @@ function depsChanged(a: unknown[] | undefined, b: unknown[] | undefined): boolea
|
|
|
32
32
|
return false
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function shallowEqual<P extends Record<string, unknown>>(a: P, b: P): boolean {
|
|
36
|
+
const keysA = Object.keys(a)
|
|
37
|
+
const keysB = Object.keys(b)
|
|
38
|
+
if (keysA.length !== keysB.length) return false
|
|
39
|
+
for (const k of keysA) {
|
|
40
|
+
if (!Object.is(a[k], b[k])) return false
|
|
41
|
+
}
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
// ─── useState ────────────────────────────────────────────────────────────────
|
|
36
46
|
|
|
37
47
|
/**
|
|
38
48
|
* Preact-compatible `useState` — returns `[value, setter]`.
|
|
39
49
|
* Triggers a component re-render when the setter is called.
|
|
50
|
+
*
|
|
51
|
+
* The setter has stable identity across renders (same reference every time).
|
|
40
52
|
*/
|
|
41
53
|
export function useState<T>(initial: T | (() => T)): [T, (v: T | ((prev: T) => T)) => void] {
|
|
42
54
|
const ctx = requireCtx()
|
|
43
55
|
const idx = getHookIndex()
|
|
44
56
|
|
|
45
57
|
if (ctx.hooks.length <= idx) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
const val = typeof initial === 'function' ? (initial as () => T)() : initial
|
|
59
|
+
// Store both value and a STABLE setter in one hook slot so setter identity
|
|
60
|
+
// never changes across renders (Preact/React guarantee).
|
|
61
|
+
const entry = { value: val, setter: null as unknown as (v: T | ((prev: T) => T)) => void }
|
|
62
|
+
entry.setter = (v: T | ((prev: T) => T)) => {
|
|
63
|
+
const current = entry.value
|
|
64
|
+
const next = typeof v === 'function' ? (v as (prev: T) => T)(current) : v
|
|
65
|
+
if (Object.is(current, next)) return
|
|
66
|
+
entry.value = next
|
|
67
|
+
ctx.scheduleRerender()
|
|
68
|
+
}
|
|
69
|
+
ctx.hooks.push(entry)
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
|
|
72
|
+
const entry = ctx.hooks[idx] as { value: T; setter: (v: T | ((prev: T) => T)) => void }
|
|
73
|
+
return [entry.value, entry.setter]
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
// ─── useEffect ───────────────────────────────────────────────────────────────
|
|
@@ -159,28 +174,42 @@ export function useRef<T>(initial?: T): { current: T | null } {
|
|
|
159
174
|
|
|
160
175
|
/**
|
|
161
176
|
* Preact-compatible `useReducer` — returns `[state, dispatch]`.
|
|
177
|
+
* Supports the 3-argument form: `useReducer(reducer, initialArg, init)`.
|
|
178
|
+
*
|
|
179
|
+
* Dispatch has stable identity across renders (same reference every time).
|
|
162
180
|
*/
|
|
163
181
|
export function useReducer<S, A>(
|
|
164
182
|
reducer: (state: S, action: A) => S,
|
|
165
|
-
|
|
183
|
+
initialArg: S | (() => S),
|
|
184
|
+
init?: (arg: S) => S,
|
|
166
185
|
): [S, (action: A) => void] {
|
|
167
186
|
const ctx = requireCtx()
|
|
168
187
|
const idx = getHookIndex()
|
|
169
188
|
|
|
170
189
|
if (ctx.hooks.length <= idx) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
let initial: S
|
|
191
|
+
if (init) {
|
|
192
|
+
initial = init(initialArg as S)
|
|
193
|
+
} else if (typeof initialArg === 'function') {
|
|
194
|
+
initial = (initialArg as () => S)()
|
|
195
|
+
} else {
|
|
196
|
+
initial = initialArg
|
|
197
|
+
}
|
|
198
|
+
// Store both value and a STABLE dispatch in one hook slot so dispatch identity
|
|
199
|
+
// never changes across renders (Preact/React guarantee).
|
|
200
|
+
const entry = { value: initial, dispatch: null as unknown as (action: A) => void }
|
|
201
|
+
entry.dispatch = (action: A) => {
|
|
202
|
+
const current = entry.value
|
|
203
|
+
const next = reducer(current, action)
|
|
204
|
+
if (Object.is(current, next)) return
|
|
205
|
+
entry.value = next
|
|
206
|
+
ctx.scheduleRerender()
|
|
207
|
+
}
|
|
208
|
+
ctx.hooks.push(entry)
|
|
181
209
|
}
|
|
182
210
|
|
|
183
|
-
|
|
211
|
+
const entry = ctx.hooks[idx] as { value: S; dispatch: (action: A) => void }
|
|
212
|
+
return [entry.value, entry.dispatch]
|
|
184
213
|
}
|
|
185
214
|
|
|
186
215
|
// ─── useId ───────────────────────────────────────────────────────────────────
|
|
@@ -206,34 +235,120 @@ export function useId(): string {
|
|
|
206
235
|
/**
|
|
207
236
|
* Preact-compatible `memo` — wraps a component to skip re-render when props
|
|
208
237
|
* are shallowly equal.
|
|
238
|
+
*
|
|
239
|
+
* Each component INSTANCE gets its own props/result cache via a hook slot,
|
|
240
|
+
* so two `<MemoComp />` usages don't share memoization state.
|
|
209
241
|
*/
|
|
210
242
|
export function memo<P extends Record<string, unknown>>(
|
|
211
243
|
component: (props: P) => VNodeChild,
|
|
212
244
|
areEqual?: (prevProps: P, nextProps: P) => boolean,
|
|
213
245
|
): (props: P) => VNodeChild {
|
|
214
|
-
const compare =
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
246
|
+
const compare = areEqual ?? shallowEqual
|
|
247
|
+
|
|
248
|
+
// Fallback closure-level cache for calls outside a compat render context
|
|
249
|
+
// (e.g. direct function calls in tests). Inside a render context, each
|
|
250
|
+
// component instance gets its own cache via a hook slot.
|
|
251
|
+
let _fallbackPrevProps: P | null = null
|
|
252
|
+
let _fallbackPrevResult: VNodeChild = null
|
|
253
|
+
|
|
254
|
+
const memoized = (props: P) => {
|
|
255
|
+
const ctx = getCurrentCtx()
|
|
256
|
+
if (ctx) {
|
|
257
|
+
// Per-instance cache via hook slot
|
|
258
|
+
const idx = getHookIndex()
|
|
259
|
+
if (ctx.hooks.length <= idx) {
|
|
260
|
+
ctx.hooks.push({ prevProps: null as P | null, prevResult: null as VNodeChild })
|
|
222
261
|
}
|
|
223
|
-
|
|
224
|
-
|
|
262
|
+
const cache = ctx.hooks[idx] as { prevProps: P | null; prevResult: VNodeChild }
|
|
263
|
+
if (cache.prevProps !== null && compare(cache.prevProps, props)) {
|
|
264
|
+
return cache.prevResult
|
|
265
|
+
}
|
|
266
|
+
cache.prevProps = props
|
|
267
|
+
cache.prevResult = component(props)
|
|
268
|
+
return cache.prevResult
|
|
269
|
+
}
|
|
270
|
+
// No compat context — use closure-level fallback cache
|
|
271
|
+
if (_fallbackPrevProps !== null && compare(_fallbackPrevProps, props)) {
|
|
272
|
+
return _fallbackPrevResult
|
|
273
|
+
}
|
|
274
|
+
_fallbackPrevProps = props
|
|
275
|
+
_fallbackPrevResult = component(props)
|
|
276
|
+
return _fallbackPrevResult
|
|
277
|
+
}
|
|
278
|
+
memoized.displayName =
|
|
279
|
+
(component as unknown as { displayName?: string }).displayName || component.name || 'Memo'
|
|
280
|
+
return memoized
|
|
281
|
+
}
|
|
225
282
|
|
|
226
|
-
|
|
227
|
-
let prevResult: VNodeChild = null
|
|
283
|
+
// ─── forwardRef ─────────────────────────────────────────────────────────────
|
|
228
284
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
285
|
+
/**
|
|
286
|
+
* Preact-compatible `forwardRef` — pass-through in Pyreon.
|
|
287
|
+
* Refs are regular props in Pyreon, so no wrapper is needed.
|
|
288
|
+
* The render function receives (props, ref) — we merge ref into props.
|
|
289
|
+
*/
|
|
290
|
+
export function forwardRef<P extends Record<string, unknown>>(
|
|
291
|
+
render: (props: P, ref: { current: unknown } | null) => VNodeChild,
|
|
292
|
+
): (props: P & { ref?: { current: unknown } | null }) => VNodeChild {
|
|
293
|
+
const forwarded = (props: P & { ref?: { current: unknown } | null }) => {
|
|
294
|
+
const { ref, ...rest } = props
|
|
295
|
+
return render(rest as P, ref ?? null)
|
|
236
296
|
}
|
|
297
|
+
forwarded.displayName =
|
|
298
|
+
(render as unknown as { displayName?: string }).displayName || render.name || 'ForwardRef'
|
|
299
|
+
return forwarded
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ─── useImperativeHandle ────────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Preact-compatible `useImperativeHandle`.
|
|
306
|
+
*/
|
|
307
|
+
export function useImperativeHandle<T>(
|
|
308
|
+
ref: { current: T | null } | null | undefined,
|
|
309
|
+
init: () => T,
|
|
310
|
+
deps?: unknown[],
|
|
311
|
+
): void {
|
|
312
|
+
useLayoutEffect(() => {
|
|
313
|
+
if (ref) ref.current = init()
|
|
314
|
+
return () => {
|
|
315
|
+
if (ref) ref.current = null
|
|
316
|
+
}
|
|
317
|
+
}, deps)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ─── useDebugValue ──────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Preact-compatible `useDebugValue` — no-op in Pyreon (no Preact DevTools integration).
|
|
324
|
+
*/
|
|
325
|
+
export function useDebugValue<T>(_value: T, _format?: (v: T) => unknown): void {}
|
|
326
|
+
|
|
327
|
+
// ─── useTransition ──────────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Preact-compatible `useTransition` — returns `[isPending, startTransition]`.
|
|
331
|
+
*
|
|
332
|
+
* In Pyreon's signal-based reactivity there is no concept of concurrent
|
|
333
|
+
* rendering lanes. The callback is executed synchronously and `isPending`
|
|
334
|
+
* is always `false`. This shim exists so Preact/React code that uses
|
|
335
|
+
* `useTransition` compiles and runs without changes.
|
|
336
|
+
*/
|
|
337
|
+
export function useTransition(): [boolean, (fn: () => void) => void] {
|
|
338
|
+
return [false, (fn) => fn()]
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ─── useDeferredValue ───────────────────────────────────────────────────
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Preact-compatible `useDeferredValue` — returns the value as-is.
|
|
345
|
+
*
|
|
346
|
+
* In Pyreon's signal-based reactivity there are no concurrent rendering lanes,
|
|
347
|
+
* so the value is never "deferred". This shim exists so Preact/React code that
|
|
348
|
+
* uses `useDeferredValue` compiles and runs without changes.
|
|
349
|
+
*/
|
|
350
|
+
export function useDeferredValue<T>(value: T): T {
|
|
351
|
+
return value
|
|
237
352
|
}
|
|
238
353
|
|
|
239
354
|
// ─── useErrorBoundary ────────────────────────────────────────────────────────
|
package/src/index.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Preact-compatible API shim that runs on Pyreon's reactive engine.
|
|
5
5
|
*
|
|
6
6
|
* Provides the core Preact API surface: h, Fragment, render, hydrate,
|
|
7
|
-
* Component class, createContext, createRef, cloneElement,
|
|
8
|
-
* isValidElement,
|
|
7
|
+
* Component class, PureComponent, createContext, createRef, cloneElement,
|
|
8
|
+
* toChildArray, isValidElement, createPortal, lazy, Suspense, ErrorBoundary,
|
|
9
|
+
* and the options hook object.
|
|
9
10
|
*
|
|
10
11
|
* For hooks, import from "@pyreon/preact-compat/hooks".
|
|
11
12
|
* For signals, import from "@pyreon/preact-compat/signals".
|
|
@@ -14,8 +15,12 @@
|
|
|
14
15
|
import type { ComponentFn, Props, VNode, VNodeChild } from '@pyreon/core'
|
|
15
16
|
import {
|
|
16
17
|
createRef,
|
|
18
|
+
ErrorBoundary,
|
|
17
19
|
Fragment,
|
|
20
|
+
lazy,
|
|
21
|
+
Portal,
|
|
18
22
|
provide,
|
|
23
|
+
Suspense,
|
|
19
24
|
createContext as pyreonCreateContext,
|
|
20
25
|
h as pyreonH,
|
|
21
26
|
useContext,
|
|
@@ -59,6 +64,9 @@ export interface PreactContext<T> {
|
|
|
59
64
|
Provider: ComponentFn<{ value: T; children?: VNodeChild }>
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
// Tag the Provider so wrapCompatComponent skips it (it's already a native component)
|
|
68
|
+
const NATIVE_COMPONENT = Symbol.for('pyreon:native-compat')
|
|
69
|
+
|
|
62
70
|
/**
|
|
63
71
|
* Preact-compatible createContext — returns a context with a `.Provider` component.
|
|
64
72
|
*/
|
|
@@ -68,6 +76,8 @@ export function createContext<T>(defaultValue: T): PreactContext<T> {
|
|
|
68
76
|
provide(ctx, props.value)
|
|
69
77
|
return props.children
|
|
70
78
|
}) as ComponentFn<{ value: T; children?: VNodeChild }>
|
|
79
|
+
// Mark as native so jsx() doesn't wrap it with wrapCompatComponent
|
|
80
|
+
;(Provider as unknown as Record<symbol, boolean>)[NATIVE_COMPONENT] = true
|
|
71
81
|
return { ...ctx, Provider }
|
|
72
82
|
}
|
|
73
83
|
|
|
@@ -91,7 +101,14 @@ export class Component<
|
|
|
91
101
|
> {
|
|
92
102
|
props: P
|
|
93
103
|
state: S
|
|
94
|
-
|
|
104
|
+
_stateSignal: ReturnType<typeof signal<S>>
|
|
105
|
+
_lastResult?: VNodeChild
|
|
106
|
+
|
|
107
|
+
// Lifecycle methods (overridden by subclasses)
|
|
108
|
+
componentDidMount?(): void
|
|
109
|
+
componentDidUpdate?(): void
|
|
110
|
+
componentWillUnmount?(): void
|
|
111
|
+
shouldComponentUpdate?(nextProps: P, nextState: S): boolean
|
|
95
112
|
|
|
96
113
|
constructor(props: P) {
|
|
97
114
|
this.props = props
|
|
@@ -129,13 +146,25 @@ export class Component<
|
|
|
129
146
|
}
|
|
130
147
|
}
|
|
131
148
|
|
|
149
|
+
// ─── PureComponent ──────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Preact-compatible PureComponent — extends Component.
|
|
153
|
+
* In Pyreon's compat layer this behaves identically to Component
|
|
154
|
+
* (signal-based reactivity already avoids unnecessary re-renders).
|
|
155
|
+
*/
|
|
156
|
+
export class PureComponent<
|
|
157
|
+
P extends Props = Props,
|
|
158
|
+
S extends Record<string, unknown> = Record<string, unknown>,
|
|
159
|
+
> extends Component<P, S> {}
|
|
160
|
+
|
|
132
161
|
// ─── cloneElement ────────────────────────────────────────────────────────────
|
|
133
162
|
|
|
134
163
|
/**
|
|
135
164
|
* Clone a VNode with merged props (like Preact's cloneElement).
|
|
136
165
|
*/
|
|
137
166
|
export function cloneElement(vnode: VNode, props?: Props, ...children: VNodeChild[]): VNode {
|
|
138
|
-
const mergedProps = { ...vnode.props, ...
|
|
167
|
+
const mergedProps = props ? { ...vnode.props, ...props } : { ...vnode.props }
|
|
139
168
|
const mergedChildren = children.length > 0 ? children : vnode.children
|
|
140
169
|
return {
|
|
141
170
|
type: vnode.type,
|
|
@@ -185,6 +214,19 @@ export function isValidElement(x: unknown): x is VNode {
|
|
|
185
214
|
)
|
|
186
215
|
}
|
|
187
216
|
|
|
217
|
+
// ─── createPortal ───────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Preact-compatible `createPortal(children, target)`.
|
|
221
|
+
*/
|
|
222
|
+
export function createPortal(children: VNodeChild, target: Element): VNodeChild {
|
|
223
|
+
return Portal({ target, children })
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ─── Suspense / lazy / ErrorBoundary ─────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
export { ErrorBoundary, lazy, Suspense }
|
|
229
|
+
|
|
188
230
|
// ─── options ─────────────────────────────────────────────────────────────────
|
|
189
231
|
|
|
190
232
|
/**
|
|
@@ -192,3 +234,7 @@ export function isValidElement(x: unknown): x is VNode {
|
|
|
192
234
|
* with libraries that check for `options._hook`, `options.vnode`, etc.
|
|
193
235
|
*/
|
|
194
236
|
export const options: Record<string, unknown> = {}
|
|
237
|
+
|
|
238
|
+
// ─── version ────────────────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
export const version = '10.0.0-pyreon'
|