@pyreon/core 0.22.0 → 0.23.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 +172 -54
- package/lib/_chunks/h-CYSD6aBx.js +48 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +59 -56
- package/lib/jsx-dev-runtime.js +3 -97
- package/lib/jsx-runtime.js +1 -45
- package/lib/types/index.d.ts +28 -1
- package/package.json +2 -2
- package/src/component.ts +33 -2
- package/src/context.ts +69 -13
- package/src/error-boundary.ts +7 -1
- package/src/index.ts +1 -0
- package/lib/analysis/jsx-dev-runtime.js.html +0 -5406
- package/lib/analysis/jsx-runtime.js.html +0 -5406
package/README.md
CHANGED
|
@@ -1,98 +1,216 @@
|
|
|
1
1
|
# @pyreon/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Component model, JSX runtime, lifecycle, context, and control-flow components for Pyreon.
|
|
4
|
+
|
|
5
|
+
`@pyreon/core` provides `h()`, the JSX automatic runtime, lifecycle hooks (`onMount`/`onUnmount`/`onUpdate`/`onErrorCaptured`), a two-tier context system (static vs reactive), control-flow components (`Show`, `Switch`/`Match`, `For`, `Suspense`, `ErrorBoundary`, `Portal`, `Dynamic`), code-splitting via `lazy()`, and props utilities that preserve reactivity through HOC pipelines. **Components run ONCE** — re-rendering on signal change is not the model; reactivity is per-binding via accessors read inside JSX text thunks, effects, or computeds. Sits one layer above `@pyreon/reactivity` and is consumed by both `runtime-dom` (CSR) and `runtime-server` (SSR).
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
bun add @pyreon/core
|
|
10
|
+
bun add @pyreon/core @pyreon/reactivity
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## TypeScript / JSX setup
|
|
14
|
+
|
|
15
|
+
In your `tsconfig.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"compilerOptions": {
|
|
20
|
+
"jsx": "preserve",
|
|
21
|
+
"jsxImportSource": "@pyreon/core"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
9
24
|
```
|
|
10
25
|
|
|
11
|
-
|
|
26
|
+
The compiler (`@pyreon/compiler`, via `@pyreon/vite-plugin`) then transforms JSX into `_tpl()` + `_bind()` templates against this runtime.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
12
29
|
|
|
13
30
|
```tsx
|
|
14
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
onMount, createContext, createReactiveContext, provide, useContext,
|
|
33
|
+
Show, Switch, Match, For, Suspense, ErrorBoundary, lazy,
|
|
34
|
+
} from '@pyreon/core'
|
|
35
|
+
import { signal } from '@pyreon/reactivity'
|
|
36
|
+
|
|
37
|
+
const ModeCtx = createReactiveContext<'light' | 'dark'>('light')
|
|
15
38
|
|
|
16
|
-
function
|
|
39
|
+
function Timer() {
|
|
40
|
+
const count = signal(0)
|
|
17
41
|
onMount(() => {
|
|
18
|
-
|
|
42
|
+
const id = setInterval(() => count.update(n => n + 1), 1000)
|
|
43
|
+
return () => clearInterval(id)
|
|
19
44
|
})
|
|
45
|
+
return <div>{() => count()}</div>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function Page(props: { items: { id: number; name: string }[] }) {
|
|
49
|
+
const mode = signal<'light' | 'dark'>('dark')
|
|
50
|
+
provide(ModeCtx, () => mode())
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Switch fallback={<p>None</p>}>
|
|
54
|
+
<Match when={() => props.items.length > 0}>
|
|
55
|
+
<For each={props.items} by={i => i.id}>{i => <li>{i.name}</li>}</For>
|
|
56
|
+
</Match>
|
|
57
|
+
</Switch>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
20
60
|
|
|
21
|
-
|
|
61
|
+
const Heavy = lazy(() => import('./Heavy'))
|
|
62
|
+
function App() {
|
|
63
|
+
return (
|
|
64
|
+
<ErrorBoundary fallback={(e) => <p>{String(e)}</p>}>
|
|
65
|
+
<Suspense fallback={<div>Loading…</div>}>
|
|
66
|
+
<Heavy />
|
|
67
|
+
</Suspense>
|
|
68
|
+
</ErrorBoundary>
|
|
69
|
+
)
|
|
22
70
|
}
|
|
23
71
|
```
|
|
24
72
|
|
|
25
|
-
##
|
|
73
|
+
## The reactive-vs-static rule
|
|
74
|
+
|
|
75
|
+
Components run once. What's reactive depends on **where** you read a signal:
|
|
26
76
|
|
|
27
|
-
|
|
77
|
+
```tsx
|
|
78
|
+
// REACTIVE — compiler wraps DOM text in an accessor
|
|
79
|
+
<div>{name()}</div>
|
|
28
80
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- **`EMPTY_PROPS`** -- Shared empty props object.
|
|
81
|
+
// REACTIVE — explicit accessor
|
|
82
|
+
<div>{() => `Hi ${name()}`}</div>
|
|
32
83
|
|
|
33
|
-
|
|
84
|
+
// REACTIVE — props read inside a reactive scope
|
|
85
|
+
<Comp title={name()} />
|
|
34
86
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
87
|
+
// STATIC — destructured at component setup, captured once
|
|
88
|
+
const { items } = props
|
|
89
|
+
return <For each={items} ...>...</For> // items is frozen at first read
|
|
90
|
+
|
|
91
|
+
// REACTIVE — read live
|
|
92
|
+
return <For each={props.items} ...>...</For>
|
|
93
|
+
```
|
|
39
94
|
|
|
40
|
-
|
|
95
|
+
`const x = props.y` IS reactive: the compiler inlines `props.y` back at the use site when `x` is a `const`. `let x = props.y` is static (mutable, not safe to inline).
|
|
41
96
|
|
|
42
|
-
|
|
43
|
-
- **`onUnmount(fn)`** -- Runs when the component is removed.
|
|
44
|
-
- **`onUpdate(fn)`** -- Runs after each reactive update.
|
|
45
|
-
- **`onErrorCaptured(fn)`** -- Captures errors thrown by descendant components.
|
|
97
|
+
## Lifecycle
|
|
46
98
|
|
|
47
|
-
|
|
99
|
+
```tsx
|
|
100
|
+
onMount(() => {
|
|
101
|
+
const ws = new WebSocket(url)
|
|
102
|
+
return () => ws.close() // cleanup runs on unmount
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
onUnmount(() => { /* … */ })
|
|
106
|
+
onUpdate(() => { /* … */ })
|
|
107
|
+
onErrorCaptured((err, info) => { /* return true to stop propagation */ })
|
|
108
|
+
```
|
|
48
109
|
|
|
49
|
-
|
|
110
|
+
`onMount`'s return value is the cleanup function — there's no separate `useEffect`-style pair. Hook arrays are lazy-allocated; components with no hooks pay zero cost.
|
|
50
111
|
|
|
51
|
-
|
|
52
|
-
- **`_rp(fn)`** -- Brands a function as a reactive prop wrapper (compiler-emitted, not user-facing).
|
|
53
|
-
- **`_wrapSpread(source)`** -- Compiler-emitted helper that makes JSX spread on a component reactivity-safe. For `<Comp {...source}>`, the compiler emits `<Comp {..._wrapSpread(source)}>`. `_wrapSpread` walks `source`'s own keys without firing getters and re-brands each getter-shaped value as an `_rp` thunk pointing back at the live source. JS spread then carries the brands as plain data properties; `makeReactiveProps` converts them back to getters on the consumer side -- so reactive props survive the spread end-to-end. Fast path: when `source` has no getter descriptors, returns the source unchanged (zero cost). Not user-facing; emitted automatically by `@pyreon/compiler` for any component JSX with a spread. See `docs/patterns/reactive-spread.md` for the full contract.
|
|
112
|
+
## Context
|
|
54
113
|
|
|
55
|
-
|
|
114
|
+
Two flavors, deliberately distinct:
|
|
56
115
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
116
|
+
```tsx
|
|
117
|
+
// Static context: useContext returns T directly, safe to destructure
|
|
118
|
+
const ThemeCtx = createContext<'light' | 'dark'>('light')
|
|
119
|
+
const theme = useContext(ThemeCtx) // 'light' | 'dark'
|
|
120
|
+
|
|
121
|
+
// Reactive context: useContext returns () => T, call it inside reactive scopes
|
|
122
|
+
const ModeCtx = createReactiveContext<'light' | 'dark'>('light')
|
|
123
|
+
const getMode = useContext(ModeCtx)
|
|
124
|
+
return <div>{() => getMode()}</div>
|
|
125
|
+
```
|
|
62
126
|
|
|
63
|
-
|
|
127
|
+
`provide(ctx, value)` pushes a context frame and auto-cleans up on unmount. `withContext(ctx, value, fn)` is the bounded form for non-component scopes.
|
|
64
128
|
|
|
65
|
-
|
|
129
|
+
## Control flow
|
|
66
130
|
|
|
67
|
-
|
|
131
|
+
```tsx
|
|
132
|
+
<Show when={isReady()}>{() => <Page />}</Show>
|
|
133
|
+
<Show when={count} fallback={<Loading />}>{(n) => <p>{n}</p>}</Show>
|
|
68
134
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- **`Suspense`** -- Shows fallback content while async children resolve.
|
|
74
|
-
- **`ErrorBoundary`** -- Catches errors in descendant components and renders a fallback.
|
|
135
|
+
<Switch fallback={<NotFound />}>
|
|
136
|
+
<Match when={isAdmin()}><AdminPanel /></Match>
|
|
137
|
+
<Match when={isUser()}><UserPanel /></Match>
|
|
138
|
+
</Switch>
|
|
75
139
|
|
|
76
|
-
|
|
140
|
+
<For each={items} by={item => item.id}>
|
|
141
|
+
{(item) => <li>{item.name}</li>}
|
|
142
|
+
</For>
|
|
143
|
+
|
|
144
|
+
<Portal mount={document.body}><Modal /></Portal>
|
|
145
|
+
<Dynamic component={tag()} {...props} />
|
|
146
|
+
<Defer>{() => <Heavy />}</Defer> // mount after first paint
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`<For>` uses **`by`** (not `key`) — JSX reserves `key` as a VNode reconciliation prop. `Show` / `Match` accept either a value (`when={isOpen()}`) or an accessor (`when={() => isOpen()}`) — both work, but only the accessor form re-evaluates on signal change.
|
|
150
|
+
|
|
151
|
+
## Suspense + lazy
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
const Heavy = lazy(() => import('./Heavy'))
|
|
155
|
+
|
|
156
|
+
<Suspense fallback={<div>Loading…</div>}>
|
|
157
|
+
<Heavy />
|
|
158
|
+
</Suspense>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`lazy()` integrates with `Suspense` — async work inside the lazy module pauses rendering until resolved. SSR streams the fallback then patches in the resolved subtree.
|
|
162
|
+
|
|
163
|
+
## Props utilities
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
import { splitProps, mergeProps, cx, createUniqueId } from '@pyreon/core'
|
|
167
|
+
|
|
168
|
+
function Button(props: ButtonProps) {
|
|
169
|
+
const [local, rest] = splitProps(props, ['variant', 'size'])
|
|
170
|
+
const merged = mergeProps({ type: 'button' }, rest)
|
|
171
|
+
const id = createUniqueId() // 'pyreon-1', SSR-safe
|
|
172
|
+
return (
|
|
173
|
+
<button id={id} {...merged} class={cx('btn', `btn-${local.variant}`, local.size && `size-${local.size}`)}>
|
|
174
|
+
{props.children}
|
|
175
|
+
</button>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`splitProps` and `mergeProps` copy property **descriptors** (not values), so getter-shaped reactive props survive. Plain `result[key] = source[key]` fires the getter at copy time and collapses reactivity — use these helpers instead.
|
|
181
|
+
|
|
182
|
+
## ErrorBoundary
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
<ErrorBoundary fallback={(err, reset) => (
|
|
186
|
+
<div role="alert">
|
|
187
|
+
<p>{String(err)}</p>
|
|
188
|
+
<button onClick={reset}>Retry</button>
|
|
189
|
+
</div>
|
|
190
|
+
)}>
|
|
191
|
+
<App />
|
|
192
|
+
</ErrorBoundary>
|
|
193
|
+
```
|
|
77
194
|
|
|
78
|
-
|
|
79
|
-
- **`mergeProps(...sources)`** -- Merges multiple props objects; last source wins. Preserves reactivity.
|
|
80
|
-
- **`createUniqueId(): string`** -- Returns an SSR-safe unique ID (`"pyreon-1"`, `"pyreon-2"`, etc.).
|
|
195
|
+
Captures any error thrown in descendants. Pair with `registerErrorHandler` / `reportError` for telemetry.
|
|
81
196
|
|
|
82
|
-
|
|
197
|
+
## Compiler-emitted helpers
|
|
83
198
|
|
|
84
|
-
|
|
199
|
+
`_rp(fn)`, `_wrapSpread(source)`, `makeReactiveProps(raw)`, `REACTIVE_PROP` — emitted by `@pyreon/compiler` and consumed by `runtime-dom` / `runtime-server`. Not user-facing in normal code. If you write a manual HOC pipeline that copies props in plain JS (not via JSX spread), reach for `splitProps`/`mergeProps` — descriptor preservation is load-bearing for reactivity.
|
|
85
200
|
|
|
86
|
-
|
|
201
|
+
`nativeCompat(Component)` — marker that tells `@pyreon/{react,preact,vue,solid}-compat` jsx() runtimes to route the component through `h(type, props)` directly, skipping the compat wrapper. Only relevant for hand-rolled Pyreon-flavored helpers used inside compat-mode apps.
|
|
87
202
|
|
|
88
|
-
|
|
89
|
-
- **`registerErrorHandler(handler)` / `reportError(error, context)`** -- Global error telemetry.
|
|
203
|
+
## Common conventions
|
|
90
204
|
|
|
91
|
-
|
|
205
|
+
- `class`, not `className`
|
|
206
|
+
- `for`, not `htmlFor`
|
|
207
|
+
- `onInput`, not `onChange`, for per-keystroke input updates
|
|
208
|
+
- `style={{ … }}` accepts a CSS-object; `style="…"` accepts a CSS string
|
|
209
|
+
- `data-*` / `aria-*` attributes typed via template-literal index signatures (catches typos)
|
|
92
210
|
|
|
93
|
-
|
|
211
|
+
## Documentation
|
|
94
212
|
|
|
95
|
-
|
|
213
|
+
Full docs: [docs.pyreon.dev/docs/core](https://docs.pyreon.dev/docs/core) (or `docs/docs/core.md` in this repo).
|
|
96
214
|
|
|
97
215
|
## License
|
|
98
216
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/h.ts
|
|
2
|
+
/**
|
|
3
|
+
* Marker for fragment nodes — renders children without a wrapper element.
|
|
4
|
+
*
|
|
5
|
+
* MUST use `Symbol.for(...)` (global registry, keyed by string), NOT
|
|
6
|
+
* `Symbol(...)` (fresh per evaluation). `h.ts` is inlined into BOTH the
|
|
7
|
+
* main `lib/index.js` and the `lib/jsx-runtime.js` published bundles —
|
|
8
|
+
* each bundle's evaluation of a bare `Symbol(...)` would produce a
|
|
9
|
+
* DISTINCT Symbol identity. JSX `<>` compiles to `jsx(Fragment, ...)` and
|
|
10
|
+
* resolves to jsx-runtime's identity; `runtime-server` checks
|
|
11
|
+
* `vnode.type === Fragment` against the main-entry identity. Mismatch
|
|
12
|
+
* fell through to `renderElement` and crashed SSG with
|
|
13
|
+
* `TypeError: Cannot convert a Symbol value to a string`.
|
|
14
|
+
* `Symbol.for()` keys by string in a global registry shared across all
|
|
15
|
+
* bundle evaluations — same identity everywhere.
|
|
16
|
+
*/
|
|
17
|
+
const Fragment = Symbol.for("Pyreon.Fragment");
|
|
18
|
+
/**
|
|
19
|
+
* Hyperscript function — the compiled output of JSX.
|
|
20
|
+
* `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
|
|
21
|
+
*
|
|
22
|
+
* Generic on P so TypeScript validates props match the component's signature
|
|
23
|
+
* at the call site, then stores the result in the loosely-typed VNode.
|
|
24
|
+
*/
|
|
25
|
+
/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */
|
|
26
|
+
const EMPTY_PROPS = {};
|
|
27
|
+
function h(type, props, ...children) {
|
|
28
|
+
return {
|
|
29
|
+
type,
|
|
30
|
+
props: props ?? EMPTY_PROPS,
|
|
31
|
+
children: normalizeChildren(children),
|
|
32
|
+
key: props?.key ?? null
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function normalizeChildren(children) {
|
|
36
|
+
for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);
|
|
37
|
+
return children;
|
|
38
|
+
}
|
|
39
|
+
function flattenChildren(children) {
|
|
40
|
+
const result = [];
|
|
41
|
+
for (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));
|
|
42
|
+
else result.push(child);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { Fragment as n, h as r, EMPTY_PROPS as t };
|
|
48
|
+
//# sourceMappingURL=h-CYSD6aBx.js.map
|
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"1b1bcb41-1","name":"lifecycle.ts"},{"uid":"1b1bcb41-3","name":"component.ts"},{"uid":"1b1bcb41-5","name":"compat-marker.ts"},{"uid":"1b1bcb41-7","name":"compat-shared.ts"},{"uid":"1b1bcb41-9","name":"context.ts"},{"uid":"1b1bcb41-11","name":"dynamic.ts"},{"uid":"1b1bcb41-13","name":"telemetry.ts"},{"uid":"1b1bcb41-15","name":"error-boundary.ts"},{"uid":"1b1bcb41-17","name":"for.ts"},{"uid":"1b1bcb41-19","name":"ref.ts"},{"uid":"1b1bcb41-21","name":"defer.ts"},{"uid":"1b1bcb41-23","name":"lazy.ts"},{"uid":"1b1bcb41-25","name":"map-array.ts"},{"uid":"1b1bcb41-27","name":"portal.ts"},{"uid":"1b1bcb41-29","name":"props.ts"},{"uid":"1b1bcb41-31","name":"show.ts"},{"uid":"1b1bcb41-33","name":"style.ts"},{"uid":"1b1bcb41-35","name":"suspense.ts"},{"uid":"1b1bcb41-37","name":"index.ts"}]}]},{"name":"jsx-dev-runtime.js","children":[{"name":"src/jsx-dev-runtime.ts","uid":"1b1bcb41-39"}]},{"name":"jsx-runtime.js","children":[{"name":"src/jsx-runtime.ts","uid":"1b1bcb41-41"}]},{"name":"_chunks/h-CYSD6aBx.js","children":[{"name":"src/h.ts","uid":"1b1bcb41-43"}]}],"isRoot":true},"nodeParts":{"1b1bcb41-1":{"renderedLength":3090,"gzipLength":1316,"brotliLength":0,"metaUid":"1b1bcb41-0"},"1b1bcb41-3":{"renderedLength":1908,"gzipLength":881,"brotliLength":0,"metaUid":"1b1bcb41-2"},"1b1bcb41-5":{"renderedLength":3173,"gzipLength":1409,"brotliLength":0,"metaUid":"1b1bcb41-4"},"1b1bcb41-7":{"renderedLength":2346,"gzipLength":1033,"brotliLength":0,"metaUid":"1b1bcb41-6"},"1b1bcb41-9":{"renderedLength":5216,"gzipLength":2147,"brotliLength":0,"metaUid":"1b1bcb41-8"},"1b1bcb41-11":{"renderedLength":490,"gzipLength":292,"brotliLength":0,"metaUid":"1b1bcb41-10"},"1b1bcb41-13":{"renderedLength":1990,"gzipLength":950,"brotliLength":0,"metaUid":"1b1bcb41-12"},"1b1bcb41-15":{"renderedLength":1666,"gzipLength":843,"brotliLength":0,"metaUid":"1b1bcb41-14"},"1b1bcb41-17":{"renderedLength":700,"gzipLength":478,"brotliLength":0,"metaUid":"1b1bcb41-16"},"1b1bcb41-19":{"renderedLength":86,"gzipLength":98,"brotliLength":0,"metaUid":"1b1bcb41-18"},"1b1bcb41-21":{"renderedLength":4387,"gzipLength":1891,"brotliLength":0,"metaUid":"1b1bcb41-20"},"1b1bcb41-23":{"renderedLength":461,"gzipLength":273,"brotliLength":0,"metaUid":"1b1bcb41-22"},"1b1bcb41-25":{"renderedLength":1018,"gzipLength":571,"brotliLength":0,"metaUid":"1b1bcb41-24"},"1b1bcb41-27":{"renderedLength":818,"gzipLength":491,"brotliLength":0,"metaUid":"1b1bcb41-26"},"1b1bcb41-29":{"renderedLength":6310,"gzipLength":2344,"brotliLength":0,"metaUid":"1b1bcb41-28"},"1b1bcb41-31":{"renderedLength":2022,"gzipLength":854,"brotliLength":0,"metaUid":"1b1bcb41-30"},"1b1bcb41-33":{"renderedLength":1858,"gzipLength":825,"brotliLength":0,"metaUid":"1b1bcb41-32"},"1b1bcb41-35":{"renderedLength":1104,"gzipLength":614,"brotliLength":0,"metaUid":"1b1bcb41-34"},"1b1bcb41-37":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"1b1bcb41-36"},"1b1bcb41-39":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"1b1bcb41-38"},"1b1bcb41-41":{"renderedLength":1789,"gzipLength":834,"brotliLength":0,"metaUid":"1b1bcb41-40"},"1b1bcb41-43":{"renderedLength":1813,"gzipLength":957,"brotliLength":0,"metaUid":"1b1bcb41-42"}},"nodeMetas":{"1b1bcb41-0":{"id":"/src/lifecycle.ts","moduleParts":{"index.js":"1b1bcb41-1"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"},{"uid":"1b1bcb41-2"},{"uid":"1b1bcb41-8"},{"uid":"1b1bcb41-14"},{"uid":"1b1bcb41-20"}]},"1b1bcb41-2":{"id":"/src/component.ts","moduleParts":{"index.js":"1b1bcb41-3"},"imported":[{"uid":"1b1bcb41-0"}],"importedBy":[{"uid":"1b1bcb41-36"},{"uid":"1b1bcb41-14"}]},"1b1bcb41-4":{"id":"/src/compat-marker.ts","moduleParts":{"index.js":"1b1bcb41-5"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"},{"uid":"1b1bcb41-14"}]},"1b1bcb41-6":{"id":"/src/compat-shared.ts","moduleParts":{"index.js":"1b1bcb41-7"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-8":{"id":"/src/context.ts","moduleParts":{"index.js":"1b1bcb41-9"},"imported":[{"uid":"1b1bcb41-44"},{"uid":"1b1bcb41-0"}],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-10":{"id":"/src/dynamic.ts","moduleParts":{"index.js":"1b1bcb41-11"},"imported":[{"uid":"1b1bcb41-42"}],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-12":{"id":"/src/telemetry.ts","moduleParts":{"index.js":"1b1bcb41-13"},"imported":[{"uid":"1b1bcb41-44"}],"importedBy":[{"uid":"1b1bcb41-36"},{"uid":"1b1bcb41-14"}]},"1b1bcb41-14":{"id":"/src/error-boundary.ts","moduleParts":{"index.js":"1b1bcb41-15"},"imported":[{"uid":"1b1bcb41-44"},{"uid":"1b1bcb41-4"},{"uid":"1b1bcb41-2"},{"uid":"1b1bcb41-0"},{"uid":"1b1bcb41-12"}],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-16":{"id":"/src/for.ts","moduleParts":{"index.js":"1b1bcb41-17"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-18":{"id":"/src/ref.ts","moduleParts":{"index.js":"1b1bcb41-19"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"},{"uid":"1b1bcb41-20"}]},"1b1bcb41-20":{"id":"/src/defer.ts","moduleParts":{"index.js":"1b1bcb41-21"},"imported":[{"uid":"1b1bcb41-44"},{"uid":"1b1bcb41-42"},{"uid":"1b1bcb41-0"},{"uid":"1b1bcb41-18"}],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-22":{"id":"/src/lazy.ts","moduleParts":{"index.js":"1b1bcb41-23"},"imported":[{"uid":"1b1bcb41-44"},{"uid":"1b1bcb41-42"}],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-24":{"id":"/src/map-array.ts","moduleParts":{"index.js":"1b1bcb41-25"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-26":{"id":"/src/portal.ts","moduleParts":{"index.js":"1b1bcb41-27"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-28":{"id":"/src/props.ts","moduleParts":{"index.js":"1b1bcb41-29"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-30":{"id":"/src/show.ts","moduleParts":{"index.js":"1b1bcb41-31"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-32":{"id":"/src/style.ts","moduleParts":{"index.js":"1b1bcb41-33"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-34":{"id":"/src/suspense.ts","moduleParts":{"index.js":"1b1bcb41-35"},"imported":[{"uid":"1b1bcb41-42"}],"importedBy":[{"uid":"1b1bcb41-36"}]},"1b1bcb41-36":{"id":"/src/index.ts","moduleParts":{"index.js":"1b1bcb41-37"},"imported":[{"uid":"1b1bcb41-2"},{"uid":"1b1bcb41-4"},{"uid":"1b1bcb41-6"},{"uid":"1b1bcb41-8"},{"uid":"1b1bcb41-10"},{"uid":"1b1bcb41-14"},{"uid":"1b1bcb41-16"},{"uid":"1b1bcb41-42"},{"uid":"1b1bcb41-20"},{"uid":"1b1bcb41-22"},{"uid":"1b1bcb41-0"},{"uid":"1b1bcb41-24"},{"uid":"1b1bcb41-26"},{"uid":"1b1bcb41-28"},{"uid":"1b1bcb41-18"},{"uid":"1b1bcb41-30"},{"uid":"1b1bcb41-32"},{"uid":"1b1bcb41-34"},{"uid":"1b1bcb41-12"}],"importedBy":[],"isEntry":true},"1b1bcb41-38":{"id":"/src/jsx-dev-runtime.ts","moduleParts":{"jsx-dev-runtime.js":"1b1bcb41-39"},"imported":[{"uid":"1b1bcb41-40"}],"importedBy":[],"isEntry":true},"1b1bcb41-40":{"id":"/src/jsx-runtime.ts","moduleParts":{"jsx-runtime.js":"1b1bcb41-41"},"imported":[{"uid":"1b1bcb41-42"}],"importedBy":[{"uid":"1b1bcb41-38"}],"isEntry":true},"1b1bcb41-42":{"id":"/src/h.ts","moduleParts":{"_chunks/h-CYSD6aBx.js":"1b1bcb41-43"},"imported":[],"importedBy":[{"uid":"1b1bcb41-36"},{"uid":"1b1bcb41-10"},{"uid":"1b1bcb41-20"},{"uid":"1b1bcb41-22"},{"uid":"1b1bcb41-34"},{"uid":"1b1bcb41-40"}]},"1b1bcb41-44":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"1b1bcb41-8"},{"uid":"1b1bcb41-14"},{"uid":"1b1bcb41-20"},{"uid":"1b1bcb41-22"},{"uid":"1b1bcb41-12"}]}},"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,3 +1,4 @@
|
|
|
1
|
+
import { n as Fragment, r as h, t as EMPTY_PROPS } from "./_chunks/h-CYSD6aBx.js";
|
|
1
2
|
import { effect, getReactiveTrace, setSnapshotCapture, signal } from "@pyreon/reactivity";
|
|
2
3
|
|
|
3
4
|
//#region src/lifecycle.ts
|
|
@@ -145,8 +146,19 @@ const _errorBoundaryStack = [];
|
|
|
145
146
|
function pushErrorBoundary(handler) {
|
|
146
147
|
_errorBoundaryStack.push(handler);
|
|
147
148
|
}
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Remove a SPECIFIC handler from the error-boundary stack by reference
|
|
151
|
+
* identity. Each `ErrorBoundary` registers `onUnmount(() => popErrorBoundary(handler))`
|
|
152
|
+
* with its OWN handler — so unmount in any order (LIFO, FIFO, middle-out)
|
|
153
|
+
* correctly removes the right handler.
|
|
154
|
+
*/
|
|
155
|
+
function popErrorBoundary(handler) {
|
|
156
|
+
if (handler === void 0) {
|
|
157
|
+
_errorBoundaryStack.pop();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const idx = _errorBoundaryStack.lastIndexOf(handler);
|
|
161
|
+
if (idx !== -1) _errorBoundaryStack.splice(idx, 1);
|
|
150
162
|
}
|
|
151
163
|
/**
|
|
152
164
|
* Dispatch an error to the nearest active ErrorBoundary.
|
|
@@ -334,11 +346,42 @@ process.env.NODE_ENV;
|
|
|
334
346
|
function pushContext(values) {
|
|
335
347
|
getStack().push(values);
|
|
336
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Pop the LAST frame from the context stack.
|
|
351
|
+
*
|
|
352
|
+
* NOTE: position-based pop. Safe ONLY when the caller can guarantee that the
|
|
353
|
+
* top of the stack is the frame they want to remove (the strict LIFO contract).
|
|
354
|
+
* The `provide()` helper does NOT use this — it uses identity-based removal
|
|
355
|
+
* via `removeContextFrame` because reactive boundaries can push snapshot
|
|
356
|
+
* frames between a component's `provide(ctx, value)` and its eventual
|
|
357
|
+
* unmount, making the top-of-stack unsafe to assume.
|
|
358
|
+
*/
|
|
337
359
|
function popContext() {
|
|
338
360
|
const stack = getStack();
|
|
339
361
|
if (stack.length === 0) return;
|
|
340
362
|
stack.pop();
|
|
341
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Remove a SPECIFIC frame from the context stack by reference identity.
|
|
366
|
+
*
|
|
367
|
+
* Internal — used by `provide()` and `withContext()` to safely clean up
|
|
368
|
+
* their pushed frame on unmount even when other frames have been pushed
|
|
369
|
+
* between push and pop (e.g. a reactive boundary's `restoreContextStack`
|
|
370
|
+
* pushing snapshot frames during the descendant's lifecycle). The
|
|
371
|
+
* symmetric position-based `popContext()` would pop the wrong frame in
|
|
372
|
+
* that case and orphan the descendant's provider frame on the live stack
|
|
373
|
+
* — the root cause of the 321k-entry context-stack leak under repeated
|
|
374
|
+
* reactive remounts.
|
|
375
|
+
*
|
|
376
|
+
* Uses `lastIndexOf` (LIFO match) — picks the most-recently-pushed frame
|
|
377
|
+
* with that exact reference, so `provide(ctx, a); provide(ctx, b)` followed
|
|
378
|
+
* by two unmounts removes them in reverse order.
|
|
379
|
+
*/
|
|
380
|
+
function removeContextFrame(frame) {
|
|
381
|
+
const stack = getStack();
|
|
382
|
+
const idx = stack.lastIndexOf(frame);
|
|
383
|
+
if (idx !== -1) stack.splice(idx, 1);
|
|
384
|
+
}
|
|
342
385
|
function useContext(context) {
|
|
343
386
|
const stack = getStack();
|
|
344
387
|
for (let i = stack.length - 1; i >= 0; i--) {
|
|
@@ -359,19 +402,21 @@ function useContext(context) {
|
|
|
359
402
|
* }
|
|
360
403
|
*/
|
|
361
404
|
function provide(context, value) {
|
|
362
|
-
|
|
363
|
-
|
|
405
|
+
const frame = new Map([[context.id, value]]);
|
|
406
|
+
pushContext(frame);
|
|
407
|
+
onUnmount(() => removeContextFrame(frame));
|
|
364
408
|
}
|
|
365
409
|
/**
|
|
366
410
|
* Provide a value for `context` during `fn()`.
|
|
367
411
|
* Used by the renderer when it encounters a `<Provider>` component.
|
|
368
412
|
*/
|
|
369
413
|
function withContext(context, value, fn) {
|
|
370
|
-
|
|
414
|
+
const frame = new Map([[context.id, value]]);
|
|
415
|
+
pushContext(frame);
|
|
371
416
|
try {
|
|
372
417
|
fn();
|
|
373
418
|
} finally {
|
|
374
|
-
|
|
419
|
+
removeContextFrame(frame);
|
|
375
420
|
}
|
|
376
421
|
}
|
|
377
422
|
/**
|
|
@@ -400,12 +445,16 @@ function captureContextStack() {
|
|
|
400
445
|
*/
|
|
401
446
|
function restoreContextStack(snapshot, fn) {
|
|
402
447
|
const stack = getStack();
|
|
403
|
-
const insertIndex = stack.length;
|
|
404
448
|
for (const frame of snapshot) stack.push(frame);
|
|
405
449
|
try {
|
|
406
450
|
return fn();
|
|
407
451
|
} finally {
|
|
408
|
-
|
|
452
|
+
for (let i = snapshot.length - 1; i >= 0; i--) {
|
|
453
|
+
const frame = snapshot[i];
|
|
454
|
+
if (!frame) continue;
|
|
455
|
+
const idx = stack.lastIndexOf(frame);
|
|
456
|
+
if (idx !== -1) stack.splice(idx, 1);
|
|
457
|
+
}
|
|
409
458
|
}
|
|
410
459
|
}
|
|
411
460
|
setSnapshotCapture({
|
|
@@ -413,52 +462,6 @@ setSnapshotCapture({
|
|
|
413
462
|
restore: (snap, fn) => restoreContextStack(snap, fn)
|
|
414
463
|
});
|
|
415
464
|
|
|
416
|
-
//#endregion
|
|
417
|
-
//#region src/h.ts
|
|
418
|
-
/**
|
|
419
|
-
* Marker for fragment nodes — renders children without a wrapper element.
|
|
420
|
-
*
|
|
421
|
-
* MUST use `Symbol.for(...)` (global registry, keyed by string), NOT
|
|
422
|
-
* `Symbol(...)` (fresh per evaluation). `h.ts` is inlined into BOTH the
|
|
423
|
-
* main `lib/index.js` and the `lib/jsx-runtime.js` published bundles —
|
|
424
|
-
* each bundle's evaluation of a bare `Symbol(...)` would produce a
|
|
425
|
-
* DISTINCT Symbol identity. JSX `<>` compiles to `jsx(Fragment, ...)` and
|
|
426
|
-
* resolves to jsx-runtime's identity; `runtime-server` checks
|
|
427
|
-
* `vnode.type === Fragment` against the main-entry identity. Mismatch
|
|
428
|
-
* fell through to `renderElement` and crashed SSG with
|
|
429
|
-
* `TypeError: Cannot convert a Symbol value to a string`.
|
|
430
|
-
* `Symbol.for()` keys by string in a global registry shared across all
|
|
431
|
-
* bundle evaluations — same identity everywhere.
|
|
432
|
-
*/
|
|
433
|
-
const Fragment = Symbol.for("Pyreon.Fragment");
|
|
434
|
-
/**
|
|
435
|
-
* Hyperscript function — the compiled output of JSX.
|
|
436
|
-
* `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
|
|
437
|
-
*
|
|
438
|
-
* Generic on P so TypeScript validates props match the component's signature
|
|
439
|
-
* at the call site, then stores the result in the loosely-typed VNode.
|
|
440
|
-
*/
|
|
441
|
-
/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */
|
|
442
|
-
const EMPTY_PROPS = {};
|
|
443
|
-
function h(type, props, ...children) {
|
|
444
|
-
return {
|
|
445
|
-
type,
|
|
446
|
-
props: props ?? EMPTY_PROPS,
|
|
447
|
-
children: normalizeChildren(children),
|
|
448
|
-
key: props?.key ?? null
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
function normalizeChildren(children) {
|
|
452
|
-
for (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);
|
|
453
|
-
return children;
|
|
454
|
-
}
|
|
455
|
-
function flattenChildren(children) {
|
|
456
|
-
const result = [];
|
|
457
|
-
for (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));
|
|
458
|
-
else result.push(child);
|
|
459
|
-
return result;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
465
|
//#endregion
|
|
463
466
|
//#region src/dynamic.ts
|
|
464
467
|
const __DEV__$4 = process.env.NODE_ENV !== "production";
|
|
@@ -577,7 +580,7 @@ function ErrorBoundary(props) {
|
|
|
577
580
|
return true;
|
|
578
581
|
};
|
|
579
582
|
pushErrorBoundary(handler);
|
|
580
|
-
onUnmount(() => popErrorBoundary());
|
|
583
|
+
onUnmount(() => popErrorBoundary(handler));
|
|
581
584
|
return () => {
|
|
582
585
|
const err = error();
|
|
583
586
|
if (err != null) return props.fallback(err, reset);
|
|
@@ -1198,5 +1201,5 @@ function Suspense(props) {
|
|
|
1198
1201
|
}
|
|
1199
1202
|
|
|
1200
1203
|
//#endregion
|
|
1201
|
-
export { CSS_UNITLESS, Defer, Dynamic, EMPTY_PROPS, ErrorBoundary, For, ForSymbol, Fragment, Match, MatchSymbol, NATIVE_COMPAT_MARKER, Portal, PortalSymbol, REACTIVE_PROP, Show, Suspense, Switch, _rp, _wrapSpread, captureContextStack, createContext, createReactiveContext, createRef, createUniqueId, cx, defineComponent, dispatchToErrorBoundary, h, isNativeCompat, lazy, makeReactiveProps, mapArray, mapCompatDomProps, mergeProps, nativeCompat, normalizeStyleValue, onErrorCaptured, onMount, onUnmount, onUpdate, popContext, propagateError, provide, pushContext, registerErrorHandler, reportError, restoreContextStack, runWithHooks, setContextStackProvider, shallowEqualProps, splitProps, toKebabCase, useContext, withContext };
|
|
1204
|
+
export { CSS_UNITLESS, Defer, Dynamic, EMPTY_PROPS, ErrorBoundary, For, ForSymbol, Fragment, Match, MatchSymbol, NATIVE_COMPAT_MARKER, Portal, PortalSymbol, REACTIVE_PROP, Show, Suspense, Switch, _rp, _wrapSpread, captureContextStack, createContext, createReactiveContext, createRef, createUniqueId, cx, defineComponent, dispatchToErrorBoundary, h, isNativeCompat, lazy, makeReactiveProps, mapArray, mapCompatDomProps, mergeProps, nativeCompat, normalizeStyleValue, onErrorCaptured, onMount, onUnmount, onUpdate, popContext, propagateError, provide, pushContext, registerErrorHandler, removeContextFrame, reportError, restoreContextStack, runWithHooks, setContextStackProvider, shallowEqualProps, splitProps, toKebabCase, useContext, withContext };
|
|
1202
1205
|
//# sourceMappingURL=index.js.map
|