@pyreon/preact-compat 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,11 +10,12 @@ bun add @pyreon/preact-compat
10
10
 
11
11
  ## Quick Start
12
12
 
13
- ```ts
13
+ ```tsx
14
14
  // Replace:
15
- // import { h, render, useState } from "preact/compat"
15
+ // import { render } from "preact"
16
+ // import { useState, useEffect } from "preact/hooks"
16
17
  // With:
17
- import { h, render } from "@pyreon/preact-compat"
18
+ import { render } from "@pyreon/preact-compat"
18
19
  import { useState, useEffect } from "@pyreon/preact-compat/hooks"
19
20
 
20
21
  function Counter() {
@@ -22,10 +23,57 @@ function Counter() {
22
23
  useEffect(() => {
23
24
  console.log("count changed:", count())
24
25
  })
25
- return h("button", { onClick: () => setCount((c) => c + 1) }, count)
26
+ return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
26
27
  }
27
28
 
28
- render(h(Counter, null), document.getElementById("app")!)
29
+ render(<Counter />, document.getElementById("app")!)
30
+ ```
31
+
32
+ ### Signals
33
+
34
+ ```tsx
35
+ import { signal, computed, effect } from "@pyreon/preact-compat/signals"
36
+
37
+ const count = signal(0)
38
+ const doubled = computed(() => count.value * 2)
39
+
40
+ function Display() {
41
+ effect(() => console.log("doubled:", doubled.value))
42
+ return (
43
+ <div>
44
+ <span>{doubled.value}</span>
45
+ <button onClick={() => count.value++}>+</button>
46
+ </div>
47
+ )
48
+ }
49
+ ```
50
+
51
+ ### Class Components
52
+
53
+ ```tsx
54
+ import { Component } from "@pyreon/preact-compat"
55
+
56
+ interface Props { name: string }
57
+ interface State { clicked: boolean }
58
+
59
+ class Greeting extends Component<Props, State> {
60
+ state = { clicked: false }
61
+
62
+ handleClick = () => {
63
+ this.setState({ clicked: true })
64
+ }
65
+
66
+ render() {
67
+ return (
68
+ <div>
69
+ <p>Hello, {this.props.name}!</p>
70
+ <button onClick={this.handleClick}>
71
+ {this.state.clicked ? "Clicked" : "Click me"}
72
+ </button>
73
+ </div>
74
+ )
75
+ }
76
+ }
29
77
  ```
30
78
 
31
79
  ## Key Differences from Preact
@@ -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":"99acccfc-1"}]}],"isRoot":true},"nodeParts":{"99acccfc-1":{"renderedLength":2611,"gzipLength":1107,"brotliLength":0,"metaUid":"99acccfc-0"}},"nodeMetas":{"99acccfc-0":{"id":"/src/index.ts","moduleParts":{"index.js":"99acccfc-1"},"imported":[{"uid":"99acccfc-2"},{"uid":"99acccfc-3"},{"uid":"99acccfc-4"}],"importedBy":[],"isEntry":true},"99acccfc-2":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"99acccfc-0"}]},"99acccfc-3":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"99acccfc-0"}]},"99acccfc-4":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"99acccfc-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":"17fbe9f7-1"}]}],"isRoot":true},"nodeParts":{"17fbe9f7-1":{"renderedLength":2842,"gzipLength":1179,"brotliLength":0,"metaUid":"17fbe9f7-0"}},"nodeMetas":{"17fbe9f7-0":{"id":"/src/index.ts","moduleParts":{"index.js":"17fbe9f7-1"},"imported":[{"uid":"17fbe9f7-2"},{"uid":"17fbe9f7-3"},{"uid":"17fbe9f7-4"}],"importedBy":[],"isEntry":true},"17fbe9f7-2":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"17fbe9f7-0"}]},"17fbe9f7-3":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"17fbe9f7-0"}]},"17fbe9f7-4":{"id":"@pyreon/runtime-dom","moduleParts":{},"imported":[],"importedBy":[{"uid":"17fbe9f7-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Fragment, createContext, createRef, h as pyreonH, useContext } from "@pyreon/core";
1
+ import { Fragment, createContext as createContext$1, createRef, h as pyreonH, onUnmount, popContext, pushContext, useContext } from "@pyreon/core";
2
2
  import { batch, signal } from "@pyreon/reactivity";
3
3
  import { hydrateRoot, mount } from "@pyreon/runtime-dom";
4
4
 
@@ -20,6 +20,21 @@ function hydrate(vnode, container) {
20
20
  hydrateRoot(container, vnode);
21
21
  }
22
22
  /**
23
+ * Preact-compatible createContext — returns a context with a `.Provider` component.
24
+ */
25
+ function createContext(defaultValue) {
26
+ const ctx = createContext$1(defaultValue);
27
+ const Provider = ((props) => {
28
+ pushContext(new Map([[ctx.id, props.value]]));
29
+ onUnmount(() => popContext());
30
+ return props.children;
31
+ });
32
+ return {
33
+ ...ctx,
34
+ Provider
35
+ };
36
+ }
37
+ /**
23
38
  * Preact-compatible class-based Component.
24
39
  *
25
40
  * Wraps Pyreon's signal-based reactivity so `setState` triggers re-renders.
@@ -79,10 +94,6 @@ function cloneElement(vnode, props, ...children) {
79
94
  key: props?.key ?? vnode.key
80
95
  };
81
96
  }
82
- /**
83
- * Flatten children into a flat array, filtering out null/undefined/boolean.
84
- * Matches Preact's `toChildArray` utility.
85
- */
86
97
  function toChildArray(children) {
87
98
  const result = [];
88
99
  flatten(children, result);
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"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 { Props, VNode, VNodeChild } from \"@pyreon/core\"\nimport { createContext, createRef, Fragment, h as pyreonH, useContext } 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 { createContext, 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 */\nexport function toChildArray(children: VNodeChild | VNodeChild[]): VNodeChild[] {\n const result: VNodeChild[] = []\n flatten(children, result)\n return result\n}\n\nfunction flatten(value: VNodeChild | VNodeChild[], 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 as VNodeChild, out)\n }\n } else {\n out.push(value)\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":";;;;;;AAwBA,MAAa,gBAAgB;;;;;AAU7B,SAAgB,OAAO,OAAmB,WAA0B;AAClE,OAAM,OAAO,UAAU;;;;;;AAOzB,SAAgB,QAAQ,OAAmB,WAA0B;AACnE,aAAY,WAAW,MAAe;;;;;;;;AAmBxC,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;;;;;;AASH,SAAgB,aAAa,UAAmD;CAC9E,MAAM,SAAuB,EAAE;AAC/B,SAAQ,UAAU,OAAO;AACzB,QAAO;;AAGT,SAAS,QAAQ,OAAkC,KAAyB;AAC1E,KAAI,SAAS,QAAQ,OAAO,UAAU,UAAW;AACjD,KAAI,MAAM,QAAQ,MAAM,CACtB,MAAK,MAAM,SAAS,MAClB,SAAQ,OAAqB,IAAI;KAGnC,KAAI,KAAK,MAAM;;;;;AASnB,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, 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 onUnmount,\n popContext,\n pushContext,\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 pushContext(new Map([[ctx.id, props.value]]))\n onUnmount(() => popContext())\n return props.children as VNode | null\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":";;;;;;AAiCA,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,cAAY,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,MAAM,MAAM,CAAC,CAAC,CAAC;AAC7C,kBAAgB,YAAY,CAAC;AAC7B,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,4 +1,4 @@
1
- import { Fragment, Props, VNode, VNodeChild, createContext, createRef, h as pyreonH, useContext } from "@pyreon/core";
1
+ import { ComponentFn, Fragment, Props, VNode, VNodeChild, createRef, h as pyreonH, useContext } from "@pyreon/core";
2
2
 
3
3
  //#region src/index.d.ts
4
4
  /** Alias: Preact also exports createElement */
@@ -13,6 +13,18 @@ declare function render(vnode: VNodeChild, container: Element): void;
13
13
  * Maps to Pyreon's `hydrateRoot(container, vnode)`.
14
14
  */
15
15
  declare function hydrate(vnode: VNodeChild, container: Element): void;
16
+ interface PreactContext<T> {
17
+ readonly id: symbol;
18
+ readonly defaultValue: T;
19
+ Provider: ComponentFn<{
20
+ value: T;
21
+ children?: VNodeChild;
22
+ }>;
23
+ }
24
+ /**
25
+ * Preact-compatible createContext — returns a context with a `.Provider` component.
26
+ */
27
+ declare function createContext<T>(defaultValue: T): PreactContext<T>;
16
28
  /**
17
29
  * Preact-compatible class-based Component.
18
30
  *
@@ -46,7 +58,8 @@ declare function cloneElement(vnode: VNode, props?: Props, ...children: VNodeChi
46
58
  * Flatten children into a flat array, filtering out null/undefined/boolean.
47
59
  * Matches Preact's `toChildArray` utility.
48
60
  */
49
- declare function toChildArray(children: VNodeChild | VNodeChild[]): VNodeChild[];
61
+ type NestedChildren = VNodeChild | NestedChildren[];
62
+ declare function toChildArray(children: NestedChildren): VNodeChild[];
50
63
  /**
51
64
  * Check if a value is a VNode (like Preact's isValidElement).
52
65
  */
@@ -57,5 +70,5 @@ declare function isValidElement(x: unknown): x is VNode;
57
70
  */
58
71
  declare const options: Record<string, unknown>;
59
72
  //#endregion
60
- export { Component, Fragment, cloneElement, createContext, createElement, createRef, pyreonH as h, hydrate, isValidElement, options, render, toChildArray, useContext };
73
+ export { Component, Fragment, PreactContext, cloneElement, createContext, createElement, createRef, pyreonH as h, hydrate, isValidElement, options, render, toChildArray, useContext };
61
74
  //# sourceMappingURL=index2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":";;;;cAwBa,aAAA,SAAa,OAAA;;;;;iBAUV,MAAA,CAAO,KAAA,EAAO,UAAA,EAAY,SAAA,EAAW,OAAA;;;;;iBAQrC,OAAA,CAAQ,KAAA,EAAO,UAAA,EAAY,SAAA,EAAW,OAAA;;;;;;;cAoBzC,SAAA,WACD,KAAA,GAAQ,KAAA,YACR,MAAA,oBAA0B,MAAA;EAEpC,KAAA,EAAO,CAAA;EACP,KAAA,EAAO,CAAA;EAAA,QACC,YAAA;cAEI,KAAA,EAAO,CAAA;EAU0B;;;;EAA7C,QAAA,CAAS,OAAA,EAAS,OAAA,CAAQ,CAAA,MAAO,IAAA,EAAM,CAAA,KAAM,OAAA,CAAQ,CAAA;EAjB3C;;;EA+BV,WAAA,CAAA;EA9BoC;;;EAqCpC,MAAA,CAAA,GAAU,UAAA;AAAA;;;;iBAUI,YAAA,CAAa,KAAA,EAAO,KAAA,EAAO,KAAA,GAAQ,KAAA,KAAU,QAAA,EAAU,UAAA,KAAe,KAAA;;;;;iBAiBtE,YAAA,CAAa,QAAA,EAAU,UAAA,GAAa,UAAA,KAAe,UAAA;;;;iBAsBnD,cAAA,CAAe,CAAA,YAAa,CAAA,IAAK,KAAA;;;;;cAgBpC,OAAA,EAAS,MAAA"}
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":";;;;cAiCa,aAAA,SAAa,OAAA;;;;;iBAUV,MAAA,CAAO,KAAA,EAAO,UAAA,EAAY,SAAA,EAAW,OAAA;;;;;iBAQrC,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;;;;iBAM/B,aAAA,GAAA,CAAiB,YAAA,EAAc,CAAA,GAAI,aAAA,CAAc,CAAA;;;;;;;cAwBpD,SAAA,WACD,KAAA,GAAQ,KAAA,YACR,MAAA,oBAA0B,MAAA;EAEpC,KAAA,EAAO,CAAA;EACP,KAAA,EAAO,CAAA;EAAA,QACC,YAAA;cAEI,KAAA,EAAO,CAAA;EAhC8B;;;;EA0CjD,QAAA,CAAS,OAAA,EAAS,OAAA,CAAQ,CAAA,MAAO,IAAA,EAAM,CAAA,KAAM,OAAA,CAAQ,CAAA;EA1CtB;;;EAwD/B,WAAA,CAAA;EAxDgE;AAwBlE;;EAuCE,MAAA,CAAA,GAAU,UAAA;AAAA;;;;iBAUI,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;;;;;cAgBpC,OAAA,EAAS,MAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/preact-compat",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Preact-compatible API shim for Pyreon — write Preact-style code that runs on Pyreon's reactive engine",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,14 +45,13 @@
45
45
  "dev": "vl_rolldown_build-watch",
46
46
  "test": "vitest run",
47
47
  "typecheck": "tsc --noEmit",
48
- "prepublishOnly": "bun run build",
49
- "prepack": "bun run ../../scripts/resolve-workspace-deps.ts pre",
50
- "postpack": "bun run ../../scripts/resolve-workspace-deps.ts post"
48
+ "lint": "biome check .",
49
+ "prepublishOnly": "bun run build"
51
50
  },
52
51
  "dependencies": {
53
- "@pyreon/core": "^0.1.1",
54
- "@pyreon/reactivity": "^0.1.1",
55
- "@pyreon/runtime-dom": "^0.1.1"
52
+ "@pyreon/core": "workspace:*",
53
+ "@pyreon/reactivity": "workspace:*",
54
+ "@pyreon/runtime-dom": "workspace:*"
56
55
  },
57
56
  "devDependencies": {
58
57
  "@happy-dom/global-registrator": "^20.8.3",
package/src/hooks.ts CHANGED
@@ -73,10 +73,8 @@ export function useMemo<T>(fn: () => T, _deps?: unknown[]): () => T {
73
73
  * Drop-in for Preact's `useCallback`.
74
74
  * Components run once in Pyreon — returns `fn` as-is.
75
75
  */
76
- export function useCallback<T extends (...args: unknown[]) => unknown>(
77
- fn: T,
78
- _deps?: unknown[],
79
- ): T {
76
+ // biome-ignore lint/suspicious/noExplicitAny: any is needed for contravariant function params
77
+ export function useCallback<T extends (...args: any[]) => any>(fn: T, _deps?: unknown[]): T {
80
78
  return fn
81
79
  }
82
80
 
package/src/index.ts CHANGED
@@ -11,8 +11,17 @@
11
11
  * For signals, import from "@pyreon/preact-compat/signals".
12
12
  */
13
13
 
14
- import type { Props, VNode, VNodeChild } from "@pyreon/core"
15
- import { createContext, createRef, Fragment, h as pyreonH, useContext } from "@pyreon/core"
14
+ import type { ComponentFn, Props, VNode, VNodeChild } from "@pyreon/core"
15
+ import {
16
+ createRef,
17
+ Fragment,
18
+ onUnmount,
19
+ popContext,
20
+ pushContext,
21
+ createContext as pyreonCreateContext,
22
+ h as pyreonH,
23
+ useContext,
24
+ } from "@pyreon/core"
16
25
  import { batch, signal } from "@pyreon/reactivity"
17
26
  import { hydrateRoot, mount } from "@pyreon/runtime-dom"
18
27
 
@@ -46,7 +55,26 @@ export function hydrate(vnode: VNodeChild, container: Element): void {
46
55
 
47
56
  // ─── Context ─────────────────────────────────────────────────────────────────
48
57
 
49
- export { createContext, useContext }
58
+ export interface PreactContext<T> {
59
+ readonly id: symbol
60
+ readonly defaultValue: T
61
+ Provider: ComponentFn<{ value: T; children?: VNodeChild }>
62
+ }
63
+
64
+ /**
65
+ * Preact-compatible createContext — returns a context with a `.Provider` component.
66
+ */
67
+ export function createContext<T>(defaultValue: T): PreactContext<T> {
68
+ const ctx = pyreonCreateContext<T>(defaultValue)
69
+ const Provider = ((props: { value: T; children?: VNodeChild }) => {
70
+ pushContext(new Map([[ctx.id, props.value]]))
71
+ onUnmount(() => popContext())
72
+ return props.children as VNode | null
73
+ }) as ComponentFn<{ value: T; children?: VNodeChild }>
74
+ return { ...ctx, Provider }
75
+ }
76
+
77
+ export { useContext }
50
78
 
51
79
  // ─── Refs ────────────────────────────────────────────────────────────────────
52
80
 
@@ -126,20 +154,22 @@ export function cloneElement(vnode: VNode, props?: Props, ...children: VNodeChil
126
154
  * Flatten children into a flat array, filtering out null/undefined/boolean.
127
155
  * Matches Preact's `toChildArray` utility.
128
156
  */
129
- export function toChildArray(children: VNodeChild | VNodeChild[]): VNodeChild[] {
157
+ type NestedChildren = VNodeChild | NestedChildren[]
158
+
159
+ export function toChildArray(children: NestedChildren): VNodeChild[] {
130
160
  const result: VNodeChild[] = []
131
161
  flatten(children, result)
132
162
  return result
133
163
  }
134
164
 
135
- function flatten(value: VNodeChild | VNodeChild[], out: VNodeChild[]): void {
165
+ function flatten(value: NestedChildren, out: VNodeChild[]): void {
136
166
  if (value == null || typeof value === "boolean") return
137
167
  if (Array.isArray(value)) {
138
168
  for (const child of value) {
139
- flatten(child as VNodeChild, out)
169
+ flatten(child, out)
140
170
  }
141
171
  } else {
142
- out.push(value)
172
+ out.push(value as VNodeChild)
143
173
  }
144
174
  }
145
175