@playgenx/components 0.2.1 → 0.2.2
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 +417 -35
- package/dist/index.d.mts +102 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +148 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,62 +1,444 @@
|
|
|
1
1
|
# `@playgenx/components`
|
|
2
2
|
|
|
3
|
-
Default React 19 implementations for every entry in `DEFAULT_REGISTRY`.
|
|
3
|
+
Default React 19 implementations for every entry in `DEFAULT_REGISTRY`.
|
|
4
|
+
The components, a per-Provider reactive state store, error boundaries,
|
|
5
|
+
and a CLI for inspecting state snapshots.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @playgenx/components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
React 19 is a peer dependency — your app provides it.
|
|
12
|
+
|
|
13
|
+
```jsonc
|
|
14
|
+
// package.json (the bits you need)
|
|
15
|
+
{
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@playgenx/components": "^0.2.1",
|
|
18
|
+
"react": "^19.2.0",
|
|
19
|
+
"react-dom": "^19.2.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> Looking for the **renderer** (parser + tree walker)? That's a
|
|
25
|
+
> separate package — `@playgenx/renderer`. This package is the
|
|
26
|
+
> component library; the renderer consumes it.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import {
|
|
34
|
+
Slider,
|
|
35
|
+
PlaygroundStateProvider,
|
|
36
|
+
usePlaygroundState,
|
|
37
|
+
} from '@playgenx/components';
|
|
38
|
+
import { useState } from 'react';
|
|
39
|
+
|
|
40
|
+
// 1. Standalone, no state — works without a Provider
|
|
41
|
+
export function StaticVolume() {
|
|
42
|
+
return <Slider min={0} max={10} value={4} label="Volume" />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2. With shared state — wrap your tree
|
|
46
|
+
export function VolumePanel() {
|
|
47
|
+
return (
|
|
48
|
+
<PlaygroundStateProvider initial={{ volume: 5 }}>
|
|
49
|
+
<Slider min={0} max={10} stateKey="volume" label="Volume" />
|
|
50
|
+
<VolumeReadout />
|
|
51
|
+
</PlaygroundStateProvider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
6
54
|
|
|
7
|
-
|
|
8
|
-
|
|
55
|
+
function VolumeReadout() {
|
|
56
|
+
const state = usePlaygroundState();
|
|
57
|
+
return <span>Current: {state.get<number>('volume')}</span>;
|
|
58
|
+
}
|
|
9
59
|
```
|
|
10
60
|
|
|
11
|
-
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## What's in the box
|
|
64
|
+
|
|
65
|
+
### Components (PascalCase, React 19)
|
|
66
|
+
|
|
67
|
+
Every component accepts JSON-friendly props. See `index.ts` for the
|
|
68
|
+
full surface.
|
|
69
|
+
|
|
70
|
+
| Component | Purpose | Notable props |
|
|
71
|
+
|---------------|--------------------------------------------|--------------------------------|
|
|
72
|
+
| `Button` | Headless button | `label`, `variant`, `onClick` |
|
|
73
|
+
| `TextField` | Controlled / uncontrolled input | `value`, `onChange` |
|
|
74
|
+
| `Slider` | Range input | `min`, `max`, `value`, `stateKey` |
|
|
75
|
+
| `Chart` | bar / line / pie SVG charts | `kind`, `data`, `title` |
|
|
76
|
+
| `Container` | Layout primitive | `padding`, `gap` |
|
|
77
|
+
| `Code` | Inline code / `<pre><code>` | `language` |
|
|
78
|
+
| `Heading` | `<h1>`–`<h6>` | `level` |
|
|
79
|
+
| `Text` | `<p>` with weight + color | `weight`, `color` |
|
|
80
|
+
| `Stepper` | Multi-step reveal | `steps`, `initial` |
|
|
81
|
+
| `Card` | Titled card | `title`, `children` |
|
|
82
|
+
| `List` | `<ul>` / `<ol>` from array | `items`, `ordered` |
|
|
83
|
+
|
|
84
|
+
### State store + React glue
|
|
85
|
+
|
|
86
|
+
| Export | What it does |
|
|
87
|
+
|------------------------------|-----------------------------------------------------------|
|
|
88
|
+
| `createPlaygroundState(initial?)` | Create a standalone state store |
|
|
89
|
+
| `PlaygroundStateProvider` | Wrap your tree with a Provider |
|
|
90
|
+
| `usePlaygroundState()` | Read the nearest store |
|
|
91
|
+
| `useBoundValue(key, fb)` | Get a live `[value, setter]` tuple (re-renders on change) |
|
|
92
|
+
| `useBoundValueOrUndefined(key, fb)` | Same; returns `undefined` if no key/provider |
|
|
93
|
+
| `useStoreSnapshot()` | Non-subscribing `{get, set, subscribe, snapshot, ...}` |
|
|
94
|
+
| `useBoundSelector(key, sel, fb)` | Live bound value with selector transform; only re-renders on slice change |
|
|
95
|
+
| `useMultiBoundValue(bindings)` | Bind multiple keys; returns `Record<key, [value, setter]>` |
|
|
96
|
+
| `useStateAction(action)` | Build an `onClick` from `{set, toggle}` config |
|
|
97
|
+
|
|
98
|
+
### Pure helpers (no React, no Provider)
|
|
99
|
+
|
|
100
|
+
| Export | Returns |
|
|
101
|
+
|-----------------------|------------------------------------|
|
|
102
|
+
| `dumpState(state)` | JSON v1 envelope (sorted keys) |
|
|
103
|
+
| `diffState(a, b)` | `{added, changed, removed}` or null |
|
|
104
|
+
| `diffSnapshots(prev, next)` | Same as `diffState` but pure |
|
|
105
|
+
| `validateStateKey(k)` | `null` if valid, error message if not |
|
|
106
|
+
| `clearState(state)` | (void) — sets every key to undefined |
|
|
107
|
+
| `batch(state, fn)` | Atomic set commit, per-key dedup |
|
|
108
|
+
|
|
109
|
+
### Error handling
|
|
12
110
|
|
|
13
|
-
|
|
111
|
+
| Export | Purpose |
|
|
112
|
+
|------------------------------|------------------------------------|
|
|
113
|
+
| `ArtifactErrorBoundary` | Class-component error boundary |
|
|
114
|
+
| `ShowSource` | Render-prop source-toggle |
|
|
14
115
|
|
|
15
|
-
|
|
116
|
+
### Registry
|
|
16
117
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- `Container` — vertical flex wrapper with padding + gap.
|
|
22
|
-
- `Code` — inline or `<pre><code>` formatting, language data-attribute.
|
|
23
|
-
- `Heading` — `<h1>`..`<h6>` with size-by-level, clamped.
|
|
24
|
-
- `Text` — `<p>` with weight, size, color.
|
|
25
|
-
- `Stepper` — multi-step reveal with previous/current/future states.
|
|
26
|
-
- `Card` — bordered + shadowed wrapper with elevation prop.
|
|
27
|
-
- `List` — `<ul>` or `<ol>` from an array of arbitrary values.
|
|
118
|
+
| Export | Purpose |
|
|
119
|
+
|-------------------------|--------------------------------------|
|
|
120
|
+
| `componentMap` | Frozen default registry (11 entries) |
|
|
121
|
+
| `createRegistry(overrides)` | Returns a new frozen map |
|
|
28
122
|
|
|
29
|
-
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Three ways to use the state store
|
|
126
|
+
|
|
127
|
+
### 1. Standalone, outside React
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { createPlaygroundState } from '@playgenx/components';
|
|
131
|
+
|
|
132
|
+
const state = createPlaygroundState({ count: 0 });
|
|
133
|
+
state.set('count', 5);
|
|
134
|
+
state.get('count'); // 5
|
|
135
|
+
state.snapshot(); // { count: 5 }
|
|
136
|
+
state.subscribe('count', (v) => console.log('count is now', v));
|
|
137
|
+
state.set('count', 10); // → console.log fires on the next microtask
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 2. React tree with a Provider
|
|
30
141
|
|
|
31
142
|
```tsx
|
|
32
|
-
import {
|
|
33
|
-
import { DEFAULT_REGISTRY, findSchema } from '@playgenx/registry';
|
|
143
|
+
import { PlaygroundStateProvider, useBoundValue } from '@playgenx/components';
|
|
34
144
|
|
|
35
|
-
function
|
|
145
|
+
function VolumeControl() {
|
|
146
|
+
// Live-bound: writes to state on change, re-renders on external writes
|
|
147
|
+
const [volume, setVolume] = useBoundValue<number>('volume', 50);
|
|
36
148
|
return (
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
149
|
+
<input
|
|
150
|
+
type="range"
|
|
151
|
+
min={0}
|
|
152
|
+
max={100}
|
|
153
|
+
value={volume}
|
|
154
|
+
onChange={(e) => setVolume(Number(e.currentTarget.value))}
|
|
155
|
+
/>
|
|
42
156
|
);
|
|
43
157
|
}
|
|
44
158
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
159
|
+
export function App() {
|
|
160
|
+
return (
|
|
161
|
+
<PlaygroundStateProvider initial={{ volume: 50 }}>
|
|
162
|
+
<VolumeControl />
|
|
163
|
+
</PlaygroundStateProvider>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 3. State-bound component (the simple path)
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
// No need to write your own useBoundValue — Slider + stateKey does it.
|
|
172
|
+
<PlaygroundStateProvider initial={{ volume: 50 }}>
|
|
173
|
+
<Slider min={0} max={100} stateKey="volume" />
|
|
174
|
+
</PlaygroundStateProvider>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Flexibility patterns — pick the right hook for the job
|
|
180
|
+
|
|
181
|
+
v0.2.2 ships five hooks for binding components to state. They differ
|
|
182
|
+
in **what triggers a re-render**, **what they assume about Providers**,
|
|
183
|
+
and **how they expose state to your code**. Pick the lowest-cost one
|
|
184
|
+
that fits.
|
|
185
|
+
|
|
186
|
+
| Hook | Subscribes? | Needs Provider? | SSR-safe? | Use when… |
|
|
187
|
+
|------------------------|-------------|------------------|-----------|-----------------------------------------|
|
|
188
|
+
| `useBoundValue` | yes | yes (throws) | yes | You need to re-render on state changes and a Provider is mandatory |
|
|
189
|
+
| `useBoundValueOrUndefined` | yes | no (graceful) | yes | `stateKey` is optional; no Provider = no-op binding |
|
|
190
|
+
| `useStoreSnapshot` | **no** | yes (throws) | n/a | You read/write state in handlers but don't want re-renders |
|
|
191
|
+
| `useBoundSelector` | yes (filtered) | yes (throws) | yes | One key holds an object; you only care about one slice |
|
|
192
|
+
| `useMultiBoundValue` | yes (1 per key) | yes (throws) | yes | A form/dashboard binds 5-10 keys; you want a Record |
|
|
193
|
+
|
|
194
|
+
### `useStoreSnapshot` — for non-subscribing reads
|
|
195
|
+
|
|
196
|
+
When you need state in event handlers but don't want the component to
|
|
197
|
+
re-render on every change:
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { useStoreSnapshot } from '@playgenx/components';
|
|
201
|
+
|
|
202
|
+
function SaveButton() {
|
|
203
|
+
const { get, set, snapshot } = useStoreSnapshot();
|
|
204
|
+
return (
|
|
205
|
+
<button onClick={() => {
|
|
206
|
+
// Read multiple keys atomically, write back, no re-render
|
|
207
|
+
const form = snapshot();
|
|
208
|
+
if (form.dirty) {
|
|
209
|
+
set('savedAt', Date.now());
|
|
210
|
+
api.save(form);
|
|
211
|
+
}
|
|
212
|
+
}}>
|
|
213
|
+
Save
|
|
214
|
+
</button>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
48
217
|
```
|
|
49
218
|
|
|
50
|
-
|
|
219
|
+
The hook returns `{ state, get, set, subscribe, snapshot, replaceAll }` —
|
|
220
|
+
all stable references for the Provider's lifetime.
|
|
51
221
|
|
|
52
|
-
|
|
222
|
+
### `useBoundSelector` — slice-aware subscriptions
|
|
53
223
|
|
|
54
|
-
|
|
224
|
+
When one key holds an object and you only care about one field:
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { useBoundSelector } from '@playgenx/components';
|
|
228
|
+
|
|
229
|
+
interface User { name: string; age: number; email: string; }
|
|
230
|
+
|
|
231
|
+
function CurrentUserBadge() {
|
|
232
|
+
// Only re-renders when `name` changes, not when `age` changes.
|
|
233
|
+
const [name] = useBoundSelector<User, string>(
|
|
234
|
+
'currentUser',
|
|
235
|
+
(u) => u?.name ?? 'guest',
|
|
236
|
+
'guest',
|
|
237
|
+
);
|
|
238
|
+
return <span>Hi, {name}!</span>;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Important**: write selectors that return primitives or stable refs.
|
|
243
|
+
A selector that returns a fresh object (`u => ({...u})`) defeats the
|
|
244
|
+
slice-detection and re-renders on every store change.
|
|
245
|
+
|
|
246
|
+
### `useMultiBoundValue` — bind many keys at once
|
|
247
|
+
|
|
248
|
+
For forms and dashboards where you have several bound inputs in one
|
|
249
|
+
component:
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
import { useMultiBoundValue } from '@playgenx/components';
|
|
253
|
+
|
|
254
|
+
function SettingsForm() {
|
|
255
|
+
const form = useMultiBoundValue({
|
|
256
|
+
name: { fallback: '' },
|
|
257
|
+
email: { fallback: '' },
|
|
258
|
+
age: { fallback: 0 },
|
|
259
|
+
role: { fallback: 'user' },
|
|
260
|
+
});
|
|
261
|
+
return (
|
|
262
|
+
<>
|
|
263
|
+
<input value={form.name[0]} onChange={e => form.name[1](e.target.value)} />
|
|
264
|
+
<input value={form.email[0]} onChange={e => form.email[1](e.target.value)} />
|
|
265
|
+
<input type="number" value={form.age[0]} onChange={e => form.age[1](+e.target.value)} />
|
|
266
|
+
<select value={form.role[0]} onChange={e => form.role[1](e.target.value)}>
|
|
267
|
+
<option value="user">User</option>
|
|
268
|
+
<option value="admin">Admin</option>
|
|
269
|
+
</select>
|
|
270
|
+
</>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The hook subscribes once per key (so 5 keys = 5 subscriptions). For
|
|
276
|
+
50+ rows of dynamic data, use `useStoreSnapshot` + manual `get`/`set`
|
|
277
|
+
calls instead — the hook count is bounded.
|
|
278
|
+
|
|
279
|
+
### SSR — state is reflected in the first render
|
|
280
|
+
|
|
281
|
+
All `useBoundValue*` hooks use React 19's `useSyncExternalStore` so
|
|
282
|
+
the **server-rendered HTML matches the live store value**, not a
|
|
283
|
+
fallback. `renderToString(<Slider stateKey="v" />)` inside a Provider
|
|
284
|
+
with `initial={{ v: 7 }}` produces `<input value="7" ... />` — no
|
|
285
|
+
hydration mismatch.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
// Server
|
|
289
|
+
import { renderToString } from 'react-dom/server';
|
|
290
|
+
import { Slider, PlaygroundStateProvider } from '@playgenx/components';
|
|
291
|
+
|
|
292
|
+
const html = renderToString(
|
|
293
|
+
<PlaygroundStateProvider initial={{ volume: 50 }}>
|
|
294
|
+
<Slider min={0} max={100} stateKey="volume" />
|
|
295
|
+
</PlaygroundStateProvider>
|
|
296
|
+
);
|
|
297
|
+
// html contains `value="50"` — not `value="0"` (the local fallback)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
If your framework has a `renderToReadableStream` or similar, the same
|
|
301
|
+
guarantee holds. The hook count is **3** (down from 5 in 0.2.1)
|
|
302
|
+
thanks to `useSyncExternalStore` collapsing the previous
|
|
303
|
+
`useState + useState + useEffect` trio.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Slider — the three operating modes
|
|
308
|
+
|
|
309
|
+
The `Slider` component accepts `value` and/or `stateKey`. They mean
|
|
310
|
+
different things:
|
|
311
|
+
|
|
312
|
+
| Pass | Mode | Behavior |
|
|
313
|
+
|---------------------|--------------------------|---------------------------------------|
|
|
314
|
+
| Neither | **Uncontrolled** | Owns its own local state |
|
|
315
|
+
| `value={50}` only | **Controlled, read-only** | User input is ignored (no onChange wired) |
|
|
316
|
+
| `stateKey="volume"` | **Bound to state** | Reads/writes via the Provider |
|
|
317
|
+
|
|
318
|
+
The bound mode wins when both are present. `value` is clamped to
|
|
319
|
+
`[min, max]` on read; NaN clamps to `min`; `min === max` disables the
|
|
320
|
+
slider.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## The CLI: `playgenx-state`
|
|
325
|
+
|
|
326
|
+
After `npm install @playgenx/components`, you get a `playgenx-state`
|
|
327
|
+
binary on your PATH (via npm's `bin` field). It operates on JSON
|
|
328
|
+
snapshots on stdin/stdout.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Pretty-print a snapshot in the v1 envelope format
|
|
332
|
+
$ echo '{"volume":5,"user":{"name":"a"}}' | playgenx-state snapshot
|
|
333
|
+
{
|
|
334
|
+
"version": 1,
|
|
335
|
+
"keys": [
|
|
336
|
+
"user",
|
|
337
|
+
"volume"
|
|
338
|
+
],
|
|
339
|
+
"values": {
|
|
340
|
+
"volume": 5,
|
|
341
|
+
"user": { "name": "a" }
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# Validate stateKey strings before using them in a body
|
|
346
|
+
$ echo '["good","bad key","has/slash"]' | playgenx-state validate
|
|
347
|
+
[
|
|
348
|
+
{ "key": "good", "ok": true },
|
|
349
|
+
{ "key": "bad key", "ok": false, "error": "stateKey must not contain whitespace" },
|
|
350
|
+
{ "key": "has/slash", "ok": false, "error": "stateKey contains forbidden characters..." }
|
|
351
|
+
]
|
|
352
|
+
# exit code 3 (validation failed)
|
|
353
|
+
|
|
354
|
+
# Diff two snapshots
|
|
355
|
+
$ echo '{"prev":{"a":1},"next":{"a":2,"b":3}}' | playgenx-state diff
|
|
356
|
+
{ "added": ["b"], "changed": ["a"], "removed": [] }
|
|
357
|
+
|
|
358
|
+
# Inspect keys / count
|
|
359
|
+
$ echo '{"b":1,"a":2,"c":3}' | playgenx-state keys
|
|
360
|
+
[ "a", "b", "c" ]
|
|
361
|
+
$ echo '{"b":1,"a":2,"c":3}' | playgenx-state count
|
|
362
|
+
{ "keys": 3 }
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Exit codes:** `0` ok, `1` unknown subcommand, `2` bad input, `3`
|
|
366
|
+
validation failure.
|
|
367
|
+
|
|
368
|
+
These are the same helpers exported from the package as library
|
|
369
|
+
functions (`dumpState`, `diffSnapshots`, `validateStateKey`, etc.) —
|
|
370
|
+
the CLI is a thin wrapper. Use whichever fits.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Architecture notes
|
|
375
|
+
|
|
376
|
+
- **Per-Provider scope.** Each `PlaygroundStateProvider` creates an
|
|
377
|
+
independent store. Nested Providers create nested scopes (a child
|
|
378
|
+
Provider does not see the parent's keys).
|
|
379
|
+
- **HMR-safe.** Subscribers are cleared on Provider unmount, so a
|
|
380
|
+
hot-reload that re-mounts the Provider doesn't leak subscribers to
|
|
381
|
+
the old store.
|
|
382
|
+
- **Microtask batched.** `set()` schedules subscriber fires on the
|
|
383
|
+
next microtask. Multiple sets in the same tick produce one fire
|
|
384
|
+
per affected key. Use `batch(state, fn)` if you want each
|
|
385
|
+
subscriber to see only the LAST value set in a group.
|
|
386
|
+
- **No deep clone.** Values are stored by reference. Don't mutate
|
|
387
|
+
objects after `set()`-ing them — use a fresh object instead.
|
|
388
|
+
- **`Object.is` equality.** Setting a value that `Object.is`-equals
|
|
389
|
+
the current value is a no-op (no subscriber fire, no flush). This
|
|
390
|
+
means `set('x', NaN)` twice fires once, and `set('x', undefined)`
|
|
391
|
+
on an unset key is a no-op (since `Object.is(undefined, undefined)`
|
|
392
|
+
is `true`).
|
|
393
|
+
- **Sub-Provider remount drops old subscribers.** Subscribers from a
|
|
394
|
+
unmounted Provider's tree do not fire for sets on a NEW Provider's
|
|
395
|
+
store, even with the same key name. (Verified by the A.7 hardening
|
|
396
|
+
test.)
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## TypeScript
|
|
401
|
+
|
|
402
|
+
This package is written in TypeScript with strict types. The
|
|
403
|
+
`dist/index.d.mts` declarations are exhaustive — you can `import
|
|
404
|
+
type { SliderProps, PlaygroundState, StateDiff }` etc.
|
|
405
|
+
|
|
406
|
+
Common imports:
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import type {
|
|
410
|
+
SliderProps,
|
|
411
|
+
ButtonProps,
|
|
412
|
+
PlaygroundState,
|
|
413
|
+
StateAction,
|
|
414
|
+
StateDiff,
|
|
415
|
+
StateEnvelope,
|
|
416
|
+
RenderBodyOptions,
|
|
417
|
+
} from '@playgenx/components';
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
55
421
|
|
|
56
422
|
## Versioning
|
|
57
423
|
|
|
58
|
-
|
|
424
|
+
- **0.1.x** — Component-only, no state store. `Slider` had no
|
|
425
|
+
`stateKey` prop.
|
|
426
|
+
- **0.2.0** — Adds `PlaygroundStateProvider`, `useBoundValue*`,
|
|
427
|
+
`useStateAction`, `ArtifactErrorBoundary`, `ShowSource`,
|
|
428
|
+
`createRegistry`, the pure helpers (`dumpState` etc.), and the
|
|
429
|
+
`playgenx-state` CLI binary. The `stateKey` prop on `Slider` is
|
|
430
|
+
fully wired and uses the new state store.
|
|
431
|
+
- **0.2.1** — Exposes the CLI binary via the package's `bin` field
|
|
432
|
+
so `npx playgenx-state` works after a fresh install.
|
|
433
|
+
- **0.2.2** — SSR fix: `useBoundValueOrUndefined` now uses React 19's
|
|
434
|
+
`useSyncExternalStore` so `renderToString` reflects the Provider's
|
|
435
|
+
live value (no more "fallback on server, real value after hydration"
|
|
436
|
+
mismatch). Adds three flexibility hooks: `useStoreSnapshot`
|
|
437
|
+
(non-subscribing), `useBoundSelector` (slice-aware subscription),
|
|
438
|
+
`useMultiBoundValue` (batch key binding). 108 tests passing.
|
|
439
|
+
|
|
440
|
+
---
|
|
59
441
|
|
|
60
442
|
## License
|
|
61
443
|
|
|
62
|
-
MIT —
|
|
444
|
+
MIT — same as the parent project.
|
package/dist/index.d.mts
CHANGED
|
@@ -281,11 +281,18 @@ declare function usePlaygroundState(): PlaygroundState;
|
|
|
281
281
|
*
|
|
282
282
|
* Implementation note: this hook ALWAYS calls the same number of
|
|
283
283
|
* React hooks regardless of whether `key` is set or a Provider is in
|
|
284
|
-
* scope. The "no binding" / "no Provider" branch
|
|
285
|
-
*
|
|
284
|
+
* scope. The "no binding" / "no Provider" branch returns a static
|
|
285
|
+
* tuple so the hook count stays aligned across renders. That means a
|
|
286
286
|
* component can flip `stateKey` from defined to undefined (or move in
|
|
287
287
|
* or out of a Provider) without crashing with a hooks-order error.
|
|
288
288
|
*
|
|
289
|
+
* The hook uses React 19's `useSyncExternalStore` so SSR renders the
|
|
290
|
+
* Provider's current value synchronously — no "value=fallback on
|
|
291
|
+
* server, value=real after hydration" surprise. The hook count drops
|
|
292
|
+
* from 5 (useContext + useState + useState + useEffect + useCallback)
|
|
293
|
+
* to 3 (useContext + useSyncExternalStore + useCallback) as a side
|
|
294
|
+
* benefit.
|
|
295
|
+
*
|
|
289
296
|
* Components that accept a `stateKey` prop should call this internally
|
|
290
297
|
* and use the returned tuple as a drop-in for the underlying
|
|
291
298
|
* value/onChange pair. The return type widens to `readonly [...] | undefined`
|
|
@@ -298,6 +305,98 @@ declare function usePlaygroundState(): PlaygroundState;
|
|
|
298
305
|
*/
|
|
299
306
|
declare function useBoundValueOrUndefined<T>(key: string | undefined, fallback: T): readonly [T, (next: T) => void] | undefined;
|
|
300
307
|
declare function useBoundValue<T>(key: string, fallback: T): readonly [T, (next: T) => void];
|
|
308
|
+
/**
|
|
309
|
+
* Non-subscribing accessor for the store. Returns a stable tuple of
|
|
310
|
+
* imperative API methods (`get`, `set`, `subscribe`, `snapshot`,
|
|
311
|
+
* `replaceAll`) plus the raw `state` ref — none of which trigger a
|
|
312
|
+
* re-render when used.
|
|
313
|
+
*
|
|
314
|
+
* When to use this instead of `useBoundValue`:
|
|
315
|
+
*
|
|
316
|
+
* - You need to read state inside an event handler (no render needed).
|
|
317
|
+
* - You need to set state from many different handlers, not one bound prop.
|
|
318
|
+
* - You're implementing a custom hook that wraps the store.
|
|
319
|
+
* - You want to subscribe imperatively (for analytics, side-effects).
|
|
320
|
+
*
|
|
321
|
+
* When NOT to use this: if you need the component to RE-RENDER on
|
|
322
|
+
* state changes, use `useBoundValue` (which subscribes for you).
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* function LogoutButton() {
|
|
326
|
+
* const { get, set } = useStoreSnapshot();
|
|
327
|
+
* return (
|
|
328
|
+
* <button onClick={() => {
|
|
329
|
+
* if (get<boolean>('confirmLogout')) set('confirmLogout', false);
|
|
330
|
+
* doLogout();
|
|
331
|
+
* }}>Log out</button>
|
|
332
|
+
* );
|
|
333
|
+
* }
|
|
334
|
+
*/
|
|
335
|
+
declare function useStoreSnapshot(): {
|
|
336
|
+
readonly state: PlaygroundState;
|
|
337
|
+
readonly get: <T = unknown>(key: string) => T | undefined;
|
|
338
|
+
readonly set: <T = unknown>(key: string, value: T) => void;
|
|
339
|
+
readonly subscribe: (key: string, cb: (value: unknown) => void) => () => void;
|
|
340
|
+
readonly snapshot: () => Readonly<Record<string, unknown>>;
|
|
341
|
+
readonly replaceAll: (values: Record<string, unknown>) => void;
|
|
342
|
+
};
|
|
343
|
+
/**
|
|
344
|
+
* Bind a state key with a selector transform. Returns the SELECTED
|
|
345
|
+
* value, not the raw value. The component only re-renders when the
|
|
346
|
+
* selected slice changes — not when OTHER fields on the same key
|
|
347
|
+
* change.
|
|
348
|
+
*
|
|
349
|
+
* Useful for normalized state where one key holds an object and you
|
|
350
|
+
* only care about one property:
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* interface User { name: string; age: number; email: string; }
|
|
354
|
+
* const userName = useBoundSelector<User, string>(
|
|
355
|
+
* 'currentUser',
|
|
356
|
+
* (u) => u?.name ?? '(anonymous)',
|
|
357
|
+
* 'guest'
|
|
358
|
+
* );
|
|
359
|
+
* // Setting `currentUser.age` does NOT re-render this component.
|
|
360
|
+
* // Setting `currentUser.name` DOES re-render this component.
|
|
361
|
+
*
|
|
362
|
+
* The setter writes the SELECTED value as-is to the store. For
|
|
363
|
+
* partial-merge semantics (e.g., "set only the name, keep other
|
|
364
|
+
* fields"), use `useStoreSnapshot()` directly and merge yourself.
|
|
365
|
+
*
|
|
366
|
+
* If the selector throws, the error propagates and the component
|
|
367
|
+
* unmounts via the nearest error boundary. Wrap your selector in
|
|
368
|
+
* a try/catch if you need resilience.
|
|
369
|
+
*/
|
|
370
|
+
declare function useBoundSelector<T, U>(key: string, selector: (raw: T | undefined) => U, fallback: U): readonly [U, (next: U) => void];
|
|
371
|
+
/**
|
|
372
|
+
* Bind multiple keys at once. Returns a `Record<key, [value, setter]>`
|
|
373
|
+
* for the keys you specify. Each setter writes back to its own key.
|
|
374
|
+
*
|
|
375
|
+
* Useful for forms and dashboards where multiple bound inputs share
|
|
376
|
+
* a single Provider.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* const form = useMultiBoundValue({
|
|
380
|
+
* name: { fallback: '' },
|
|
381
|
+
* age: { fallback: 0 },
|
|
382
|
+
* email: { fallback: '' },
|
|
383
|
+
* });
|
|
384
|
+
* // form.name[0] === current name string
|
|
385
|
+
* // form.age[1](42) writes 42 to state.age
|
|
386
|
+
*
|
|
387
|
+
* The hook subscribes ONCE per key (so 5 keys = 5 subscriptions,
|
|
388
|
+
* not a single batched subscription). If you bind a LOT of keys,
|
|
389
|
+
* consider using `useStoreSnapshot()` instead — it doesn't subscribe.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* // 50-row table, each row needs its own value
|
|
393
|
+
* const { get, set } = useStoreSnapshot();
|
|
394
|
+
* rows.map(row => <input value={get(`cell-${row.id}`) ?? ''}
|
|
395
|
+
* onChange={e => set(`cell-${row.id}`, e.target.value)} />);
|
|
396
|
+
*/
|
|
397
|
+
declare function useMultiBoundValue<K extends string>(bindings: Record<K, {
|
|
398
|
+
fallback: unknown;
|
|
399
|
+
}>): { readonly [P in K]: readonly [unknown, (next: unknown) => void]; };
|
|
301
400
|
/**
|
|
302
401
|
* State-binding action shape. Used by `<Button onClickAction={...}>`.
|
|
303
402
|
* { set: { count: 1 } } -> sets state.count = 1
|
|
@@ -506,5 +605,5 @@ declare const componentMap: ComponentMap;
|
|
|
506
605
|
*/
|
|
507
606
|
declare function createRegistry(overrides: Partial<ComponentMap>): ComponentMap;
|
|
508
607
|
//#endregion
|
|
509
|
-
export { ArtifactErrorBoundary, type ArtifactErrorBoundaryProps, Button, type ButtonProps, Card, type CardProps, Chart, type ChartKind, type ChartProps, Code, type CodeProps, type ComponentMap, Container, type ContainerProps, Heading, type HeadingProps, List, type ListProps, type PlaygroundState, PlaygroundStateProvider, type PlaygroundStateProviderProps, ShowSource, type ShowSourceProps, Slider, type SliderProps, type StateAction, type StateDiff, type StateEnvelope, type Step, Stepper, type StepperProps, Text, TextField, type TextFieldProps, type TextProps, batch, clearState, componentMap, createPlaygroundState, createRegistry, diffSnapshots, diffState, dumpState, useBoundValue, useBoundValueOrUndefined, usePlaygroundState, useStateAction, validateStateKey };
|
|
608
|
+
export { ArtifactErrorBoundary, type ArtifactErrorBoundaryProps, Button, type ButtonProps, Card, type CardProps, Chart, type ChartKind, type ChartProps, Code, type CodeProps, type ComponentMap, Container, type ContainerProps, Heading, type HeadingProps, List, type ListProps, type PlaygroundState, PlaygroundStateProvider, type PlaygroundStateProviderProps, ShowSource, type ShowSourceProps, Slider, type SliderProps, type StateAction, type StateDiff, type StateEnvelope, type Step, Stepper, type StepperProps, Text, TextField, type TextFieldProps, type TextProps, batch, clearState, componentMap, createPlaygroundState, createRegistry, diffSnapshots, diffState, dumpState, useBoundSelector, useBoundValue, useBoundValueOrUndefined, useMultiBoundValue, usePlaygroundState, useStateAction, useStoreSnapshot, validateStateKey };
|
|
510
609
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/Button.tsx","../src/TextField.tsx","../src/Slider.tsx","../src/Chart.tsx","../src/Container.tsx","../src/Code.tsx","../src/Heading.tsx","../src/Text.tsx","../src/Stepper.tsx","../src/Card.tsx","../src/List.tsx","../src/state.tsx","../src/stateHelpers.ts","../src/ArtifactErrorBoundary.tsx","../src/ShowSource.tsx","../src/createRegistry.ts"],"mappings":";;;;;;;;;UASiB;;EAEf;;EAEA;;EAEA;;;;;EAKA,UAAU,MAAM,kBAAkB;;iBAGpB,SACd,OACA,SACA,UACA,WACC,cAAc,MAAM,IAAI;;;UCzBV;EACf;EACA;EACA;EACA;;;;;;EAMA,WAAW,MAAM,mBAAmB;;;;;;;iBAQtB,YACd,OACA,OACA,aACA,UACA,YACC,iBAAiB,MAAM,IAAI;;;UCvBb;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;EAMA;;;;;;;;;;;;;;;;;;;;;;;;;iBA0Bc,SAAS,KAAK,KAAK,OAAO,MAAU,OAAO,YAAY,cAAc,MAAM,IAAI;;;KC3CnF;;;;;;;;;;;UAYK;EACf,MAAM;EACN;EACA;;iBAGc,QAAQ,MAAM,MAAM,SAAS,aAAa,MAAM,IAAI;;;UCnBnD;EACf;EACA;EACA,WAAW,MAAM;;;;;;iBAOH,YAAY,SAAS,KAAK,YAAY,iBAAiB,MAAM,IAAI;;;UCThE;EACf;EACA,WAAW,MAAM;;;;;;;;iBASH,OAAO,UAAU,YAAY,YAAY,MAAM,IAAI;;;UCXlD;;;;;;EAMf;EACA;EACA,WAAW,MAAM;;iBAYH,UAAU,OAAO,OAAO,YAAY,eAAe,MAAM,IAAI;;;UCpB5D;EACf;EACA;EACA;EACA,WAAW,MAAM;;iBAGH,OAAO,QAAmB,MAAM,OAAO,YAAY,YAAY,MAAM,IAAI;;;UCPxE;EACf;EACA;EACA,OAAO,MAAM;;UAGE;EACf,OAAO;;EAEP;;;;;;iBAOc,UAAU,OAAO,WAAW,eAAe,MAAM,IAAI;;;UChBpD;EACf;;;;;EAKA;EACA,WAAW,MAAM;;iBAGH,OAAO,OAAO,WAAe,YAAY,YAAY,MAAM,IAAI;;;UCV9D;;;;;;EAMf;;EAEA;;EAEA,WAAW,MAAM;;;;;;;iBAQH,OAAO,OAAO,SAAS,YAAY,YAAY,MAAM,IAAI;;;;;;;UCIxD;;EAEf,IAAI,aAAa,cAAc;;;;;EAK/B,IAAI,aAAa,aAAa,OAAO;;;;;;EAMrC,UAAU,aAAa,KAAK;;;;;EAK5B,YAAY,SAAS;;EAErB,WAAW,QAAQ;;;;;;;;;;;;;iBAsCL,sBAAsB,aACpC,UAAS,eAAe,KACvB;UAoKc;;EAEf,UAAU;;;;;;EAMV,OAAO;EACP,UAAU,MAAM;;;;;;;;;iBAUF,wBACd,OAAO,+BACN,MAAM,IAAI;;;;;;;;;;;;;iBAkDG,sBAAsB
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/Button.tsx","../src/TextField.tsx","../src/Slider.tsx","../src/Chart.tsx","../src/Container.tsx","../src/Code.tsx","../src/Heading.tsx","../src/Text.tsx","../src/Stepper.tsx","../src/Card.tsx","../src/List.tsx","../src/state.tsx","../src/stateHelpers.ts","../src/ArtifactErrorBoundary.tsx","../src/ShowSource.tsx","../src/createRegistry.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -308,11 +308,18 @@ function usePlaygroundState() {
|
|
|
308
308
|
*
|
|
309
309
|
* Implementation note: this hook ALWAYS calls the same number of
|
|
310
310
|
* React hooks regardless of whether `key` is set or a Provider is in
|
|
311
|
-
* scope. The "no binding" / "no Provider" branch
|
|
312
|
-
*
|
|
311
|
+
* scope. The "no binding" / "no Provider" branch returns a static
|
|
312
|
+
* tuple so the hook count stays aligned across renders. That means a
|
|
313
313
|
* component can flip `stateKey` from defined to undefined (or move in
|
|
314
314
|
* or out of a Provider) without crashing with a hooks-order error.
|
|
315
315
|
*
|
|
316
|
+
* The hook uses React 19's `useSyncExternalStore` so SSR renders the
|
|
317
|
+
* Provider's current value synchronously — no "value=fallback on
|
|
318
|
+
* server, value=real after hydration" surprise. The hook count drops
|
|
319
|
+
* from 5 (useContext + useState + useState + useEffect + useCallback)
|
|
320
|
+
* to 3 (useContext + useSyncExternalStore + useCallback) as a side
|
|
321
|
+
* benefit.
|
|
322
|
+
*
|
|
316
323
|
* Components that accept a `stateKey` prop should call this internally
|
|
317
324
|
* and use the returned tuple as a drop-in for the underlying
|
|
318
325
|
* value/onChange pair. The return type widens to `readonly [...] | undefined`
|
|
@@ -326,21 +333,30 @@ function usePlaygroundState() {
|
|
|
326
333
|
function useBoundValueOrUndefined(key, fallback) {
|
|
327
334
|
const ctx = React.useContext(PlaygroundStateContext);
|
|
328
335
|
const bound = key !== void 0 && ctx !== null;
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (!bound || key === void 0 || ctx === null) return;
|
|
333
|
-
const initial = ctx.state.get(key) ?? fallback;
|
|
334
|
-
setValue(initial);
|
|
335
|
-
return ctx.state.subscribe(key, (next) => {
|
|
336
|
-
setValue(next);
|
|
337
|
-
force((n) => n + 1);
|
|
338
|
-
});
|
|
336
|
+
const value = React.useSyncExternalStore(React.useCallback((onStoreChange) => {
|
|
337
|
+
if (!bound || key === void 0 || ctx === null) return () => {};
|
|
338
|
+
return ctx.state.subscribe(key, onStoreChange);
|
|
339
339
|
}, [
|
|
340
340
|
bound,
|
|
341
341
|
ctx,
|
|
342
342
|
key
|
|
343
|
-
])
|
|
343
|
+
]), React.useCallback(() => {
|
|
344
|
+
if (!bound || key === void 0 || ctx === null) return fallback;
|
|
345
|
+
return ctx.state.get(key) ?? fallback;
|
|
346
|
+
}, [
|
|
347
|
+
bound,
|
|
348
|
+
ctx,
|
|
349
|
+
key,
|
|
350
|
+
fallback
|
|
351
|
+
]), React.useCallback(() => {
|
|
352
|
+
if (!bound || key === void 0 || ctx === null) return fallback;
|
|
353
|
+
return ctx.state.get(key) ?? fallback;
|
|
354
|
+
}, [
|
|
355
|
+
bound,
|
|
356
|
+
ctx,
|
|
357
|
+
key,
|
|
358
|
+
fallback
|
|
359
|
+
]));
|
|
344
360
|
const set = React.useCallback((next) => {
|
|
345
361
|
if (!bound || key === void 0 || ctx === null) return;
|
|
346
362
|
ctx.state.set(key, next);
|
|
@@ -358,6 +374,124 @@ function useBoundValue(key, fallback) {
|
|
|
358
374
|
return bound;
|
|
359
375
|
}
|
|
360
376
|
/**
|
|
377
|
+
* Non-subscribing accessor for the store. Returns a stable tuple of
|
|
378
|
+
* imperative API methods (`get`, `set`, `subscribe`, `snapshot`,
|
|
379
|
+
* `replaceAll`) plus the raw `state` ref — none of which trigger a
|
|
380
|
+
* re-render when used.
|
|
381
|
+
*
|
|
382
|
+
* When to use this instead of `useBoundValue`:
|
|
383
|
+
*
|
|
384
|
+
* - You need to read state inside an event handler (no render needed).
|
|
385
|
+
* - You need to set state from many different handlers, not one bound prop.
|
|
386
|
+
* - You're implementing a custom hook that wraps the store.
|
|
387
|
+
* - You want to subscribe imperatively (for analytics, side-effects).
|
|
388
|
+
*
|
|
389
|
+
* When NOT to use this: if you need the component to RE-RENDER on
|
|
390
|
+
* state changes, use `useBoundValue` (which subscribes for you).
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* function LogoutButton() {
|
|
394
|
+
* const { get, set } = useStoreSnapshot();
|
|
395
|
+
* return (
|
|
396
|
+
* <button onClick={() => {
|
|
397
|
+
* if (get<boolean>('confirmLogout')) set('confirmLogout', false);
|
|
398
|
+
* doLogout();
|
|
399
|
+
* }}>Log out</button>
|
|
400
|
+
* );
|
|
401
|
+
* }
|
|
402
|
+
*/
|
|
403
|
+
function useStoreSnapshot() {
|
|
404
|
+
const ctx = React.useContext(PlaygroundStateContext);
|
|
405
|
+
if (ctx === null) throw new Error("useStoreSnapshot() must be called inside a <PlaygroundStateProvider>.");
|
|
406
|
+
return {
|
|
407
|
+
state: ctx.state,
|
|
408
|
+
get: ctx.state.get,
|
|
409
|
+
set: ctx.state.set,
|
|
410
|
+
subscribe: ctx.state.subscribe,
|
|
411
|
+
snapshot: ctx.state.snapshot,
|
|
412
|
+
replaceAll: ctx.state.replaceAll
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Bind a state key with a selector transform. Returns the SELECTED
|
|
417
|
+
* value, not the raw value. The component only re-renders when the
|
|
418
|
+
* selected slice changes — not when OTHER fields on the same key
|
|
419
|
+
* change.
|
|
420
|
+
*
|
|
421
|
+
* Useful for normalized state where one key holds an object and you
|
|
422
|
+
* only care about one property:
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* interface User { name: string; age: number; email: string; }
|
|
426
|
+
* const userName = useBoundSelector<User, string>(
|
|
427
|
+
* 'currentUser',
|
|
428
|
+
* (u) => u?.name ?? '(anonymous)',
|
|
429
|
+
* 'guest'
|
|
430
|
+
* );
|
|
431
|
+
* // Setting `currentUser.age` does NOT re-render this component.
|
|
432
|
+
* // Setting `currentUser.name` DOES re-render this component.
|
|
433
|
+
*
|
|
434
|
+
* The setter writes the SELECTED value as-is to the store. For
|
|
435
|
+
* partial-merge semantics (e.g., "set only the name, keep other
|
|
436
|
+
* fields"), use `useStoreSnapshot()` directly and merge yourself.
|
|
437
|
+
*
|
|
438
|
+
* If the selector throws, the error propagates and the component
|
|
439
|
+
* unmounts via the nearest error boundary. Wrap your selector in
|
|
440
|
+
* a try/catch if you need resilience.
|
|
441
|
+
*/
|
|
442
|
+
function useBoundSelector(key, selector, fallback) {
|
|
443
|
+
const bound = useBoundValueOrUndefined(key, fallback);
|
|
444
|
+
const raw = bound === void 0 ? void 0 : bound[0];
|
|
445
|
+
const value = React.useMemo(() => selector(raw), [
|
|
446
|
+
raw,
|
|
447
|
+
selector,
|
|
448
|
+
fallback
|
|
449
|
+
]);
|
|
450
|
+
const set = React.useCallback((next) => {
|
|
451
|
+
if (bound === void 0) return;
|
|
452
|
+
bound[1](next);
|
|
453
|
+
}, [bound]);
|
|
454
|
+
if (bound === void 0) throw new Error("useBoundSelector requires a non-undefined `key` and a <PlaygroundStateProvider> ancestor.");
|
|
455
|
+
return [value, set];
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Bind multiple keys at once. Returns a `Record<key, [value, setter]>`
|
|
459
|
+
* for the keys you specify. Each setter writes back to its own key.
|
|
460
|
+
*
|
|
461
|
+
* Useful for forms and dashboards where multiple bound inputs share
|
|
462
|
+
* a single Provider.
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* const form = useMultiBoundValue({
|
|
466
|
+
* name: { fallback: '' },
|
|
467
|
+
* age: { fallback: 0 },
|
|
468
|
+
* email: { fallback: '' },
|
|
469
|
+
* });
|
|
470
|
+
* // form.name[0] === current name string
|
|
471
|
+
* // form.age[1](42) writes 42 to state.age
|
|
472
|
+
*
|
|
473
|
+
* The hook subscribes ONCE per key (so 5 keys = 5 subscriptions,
|
|
474
|
+
* not a single batched subscription). If you bind a LOT of keys,
|
|
475
|
+
* consider using `useStoreSnapshot()` instead — it doesn't subscribe.
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* // 50-row table, each row needs its own value
|
|
479
|
+
* const { get, set } = useStoreSnapshot();
|
|
480
|
+
* rows.map(row => <input value={get(`cell-${row.id}`) ?? ''}
|
|
481
|
+
* onChange={e => set(`cell-${row.id}`, e.target.value)} />);
|
|
482
|
+
*/
|
|
483
|
+
function useMultiBoundValue(bindings) {
|
|
484
|
+
if (React.useContext(PlaygroundStateContext) === null) throw new Error("useMultiBoundValue() must be called inside a <PlaygroundStateProvider>.");
|
|
485
|
+
const result = {};
|
|
486
|
+
const keys = Object.keys(bindings);
|
|
487
|
+
for (const k of keys) {
|
|
488
|
+
const b = useBoundValueOrUndefined(k, bindings[k].fallback);
|
|
489
|
+
if (b === void 0) result[k] = [bindings[k].fallback, () => {}];
|
|
490
|
+
else result[k] = b;
|
|
491
|
+
}
|
|
492
|
+
return result;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
361
495
|
* Apply a StateAction to the nearest PlaygroundState. Returns an
|
|
362
496
|
* onClick handler that fires the action. If action is undefined
|
|
363
497
|
* the handler is a no-op.
|
|
@@ -1184,6 +1318,6 @@ function createRegistry(overrides) {
|
|
|
1184
1318
|
return Object.freeze(merged);
|
|
1185
1319
|
}
|
|
1186
1320
|
//#endregion
|
|
1187
|
-
export { ArtifactErrorBoundary, Button, Card, Chart, Code, Container, Heading, List, PlaygroundStateProvider, ShowSource, Slider, Stepper, Text, TextField, batch, clearState, componentMap, createPlaygroundState, createRegistry, diffSnapshots, diffState, dumpState, useBoundValue, useBoundValueOrUndefined, usePlaygroundState, useStateAction, validateStateKey };
|
|
1321
|
+
export { ArtifactErrorBoundary, Button, Card, Chart, Code, Container, Heading, List, PlaygroundStateProvider, ShowSource, Slider, Stepper, Text, TextField, batch, clearState, componentMap, createPlaygroundState, createRegistry, diffSnapshots, diffState, dumpState, useBoundSelector, useBoundValue, useBoundValueOrUndefined, useMultiBoundValue, usePlaygroundState, useStateAction, useStoreSnapshot, validateStateKey };
|
|
1188
1322
|
|
|
1189
1323
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["internal"],"sources":["../src/theme.ts","../src/Button.tsx","../src/TextField.tsx","../src/state.tsx","../src/Slider.tsx","../src/Chart.tsx","../src/Container.tsx","../src/Code.tsx","../src/Heading.tsx","../src/Text.tsx","../src/Stepper.tsx","../src/Card.tsx","../src/List.tsx","../src/stateHelpers.ts","../src/ShowSource.tsx","../src/ArtifactErrorBoundary.tsx","../src/createRegistry.ts"],"sourcesContent":["/**\n * Shared theme tokens for @playgenx/components.\n *\n * All styling is driven from this file. There is intentionally no\n * \"config\" theme or runtime theme provider — these are the default\n * implementations of the registry, and a downstream host app can swap\n * them out entirely. Tokens live as constants so they're stable\n * across renders and don't allocate per call.\n */\n\nexport const colors = {\n bg: '#ffffff',\n fg: '#0f172a',\n mutedFg: '#475569',\n border: '#e2e8f0',\n primary: '#2563eb',\n primaryFg: '#ffffff',\n secondaryFg: '#0f172a',\n ghostFg: '#1d4ed8',\n cardBg: '#f8fafc',\n cardBorder: '#e2e8f0',\n codeBg: '#f1f5f9',\n codeFg: '#0f172a',\n sliderTrack: '#cbd5e1',\n sliderFill: '#2563eb',\n sliderThumb: '#1d4ed8',\n bar: '#2563eb',\n barLabel: '#0f172a',\n pie: ['#2563eb', '#16a34a', '#f59e0b', '#dc2626'],\n line: '#2563eb',\n stepperPast: '#16a34a',\n stepperCurrent: '#2563eb',\n stepperFuture: '#cbd5e1',\n} as const;\n\nexport const space = {\n none: '0',\n xs: '4px',\n sm: '8px',\n md: '12px',\n lg: '16px',\n xl: '24px',\n} as const;\n\nexport const radius = {\n sm: '4px',\n md: '6px',\n lg: '8px',\n} as const;\n\n/**\n * Common React-friendly prop accepts for content. Components that\n * accept children of arbitrary shape (Heading, Text, Card, Container,\n * ...) use this so the host can pass strings or JSX interchangeably.\n */\nexport type Children = string | number | React.ReactNode;\n\nimport type * as React from 'react';\n","import * as React from 'react';\nimport { colors, radius } from './theme.js';\n\n/**\n * Headless-style wrapper over `<button>` accepting the props defined in\n * `DEFAULT_COMPONENT_SCHEMAS.Button`. Pass `onClick` through; this\n * implementation does NOT execute arbitrary functions (deterministic\n * renderer constraint) so the click handler is rendered inert at mount.\n */\nexport interface ButtonProps {\n /** Visible text. Required. */\n label: string;\n /** Variant. Defaults to 'primary'. */\n variant?: 'primary' | 'secondary' | 'ghost';\n /** Disabled state. Defaults to false. */\n disabled?: boolean;\n /**\n * Optional click handler. The default impl forwards to onClick if\n * present; in a deterministic renderer pass `noEvents` instead.\n */\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n}\n\nexport function Button({\n label,\n variant = 'primary',\n disabled = false,\n onClick,\n}: ButtonProps): React.JSX.Element {\n const styles: React.CSSProperties = {\n fontFamily: 'inherit',\n fontSize: '14px',\n fontWeight: 500,\n padding: '8px 14px',\n borderRadius: radius.md,\n border: '1px solid transparent',\n cursor: disabled ? 'not-allowed' : 'pointer',\n opacity: disabled ? 0.5 : 1,\n background:\n variant === 'primary'\n ? colors.primary\n : variant === 'secondary'\n ? colors.secondaryFg\n : 'transparent',\n color:\n variant === 'ghost' ? colors.ghostFg : variant === 'secondary' ? colors.bg : colors.primaryFg,\n borderColor: variant === 'ghost' ? colors.border : 'transparent',\n };\n return (\n <button type=\"button\" disabled={disabled} onClick={onClick} style={styles} data-pgx=\"Button\">\n {label}\n </button>\n );\n}\n","import * as React from 'react';\nimport { colors, radius } from './theme.js';\n\nexport interface TextFieldProps {\n label?: string;\n value?: string;\n placeholder?: string;\n disabled?: boolean;\n /**\n * onChange is reported as `node` in the schema (it must be a\n * function reference, which the validator cannot statically type).\n * We forward to React's controlled input handler.\n */\n onChange?: React.ChangeEventHandler<HTMLInputElement>;\n}\n\n/**\n * Controlled text input. Without `value`/`onChange`, behaves as\n * uncontrolled (uses its own state via defaultValue). When a stable\n * (deterministic) caller wires both, the input is fully controlled.\n */\nexport function TextField({\n label,\n value,\n placeholder,\n disabled = false,\n onChange,\n}: TextFieldProps): React.JSX.Element {\n const wrapperStyle: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: '4px',\n fontFamily: 'inherit',\n };\n const labelStyle: React.CSSProperties = {\n fontSize: '12px',\n fontWeight: 500,\n color: colors.mutedFg,\n };\n const inputStyle: React.CSSProperties = {\n fontFamily: 'inherit',\n fontSize: '14px',\n padding: '8px 10px',\n border: `1px solid ${colors.border}`,\n borderRadius: radius.md,\n background: colors.bg,\n color: colors.fg,\n outline: 'none',\n };\n const inputProps: React.InputHTMLAttributes<HTMLInputElement> = {\n placeholder,\n disabled,\n onChange,\n };\n // Use uncontrolled defaultValue when caller omits value. We can't\n // set defaultValue AND value without React warning, so branch.\n if (value !== undefined) inputProps.value = value;\n else inputProps.defaultValue = '';\n return (\n <label style={wrapperStyle} data-pgx=\"TextField\">\n {label !== undefined ? <span style={labelStyle}>{label}</span> : null}\n <input {...inputProps} style={inputStyle} />\n </label>\n );\n}\n","/**\n * Per-render interactive state store. Used by PlaygroundStateProvider to\n * back interactive components (Slider, TextField, Button, Chart) without\n * requiring them to be fully controlled.\n *\n * Design notes:\n * - The store is a singleton instance per Provider, NOT React context.\n * Components consume it via usePlaygroundState() inside React renders,\n * and via .subscribe() / .get() / .set() from non-React callers.\n * - `set` is synchronous; subscribers fire in registration order on the\n * next microtask (so multiple sets in a single tick produce a single\n * re-render per subscriber).\n * - `set` with `===` equal value is a no-op (no subscriber fires).\n * - Throws if used outside a PlaygroundStateProvider (when required by\n * the caller's contract).\n *\n * @packageDocumentation\n */\n\nimport * as React from 'react';\n\n/**\n * Per-render state store. Components read/write values keyed by string.\n * One store per Provider instance; nested Providers create nested scopes.\n */\nexport interface PlaygroundState {\n /** Read a value by key. Returns undefined if unset. */\n get<T = unknown>(key: string): T | undefined;\n /**\n * Set a value. Triggers re-render of subscribers. No-op if the new\n * value is `===` to the current value.\n */\n set<T = unknown>(key: string, value: T): void;\n /**\n * Subscribe to changes on a single key. The callback fires on every\n * `set(key, ...)` with a different value. Returns an unsubscribe\n * function. Non-React callers can use this directly.\n */\n subscribe(key: string, cb: (value: unknown) => void): () => void;\n /**\n * Bulk-read: returns a shallow snapshot of all keys. Useful for\n * snapshotting state at a moment in time (e.g., for undo).\n */\n snapshot(): Readonly<Record<string, unknown>>;\n /** Bulk-write: replace all values. Subscribers fire for changed keys. */\n replaceAll(values: Record<string, unknown>): void;\n}\n\ninterface InternalState extends PlaygroundState {\n /** Test seam: forces sync-fire for unit tests. */\n _flush(): void;\n /**\n * Test seam: run `fn` with batch dedup enabled. While inside the\n * batch, repeated `set(key, ...)` calls collapse to a single\n * subscriber fire per key with the LAST value set. Returns\n * whatever `fn` returns.\n *\n * This is the same primitive as the `batch(state, fn)` helper in\n * stateHelpers.ts but exposed as a method for callers that already\n * hold a state ref.\n */\n _batch<T>(fn: () => T): T;\n /**\n * Internal seam: drop every subscriber from every key. Used by\n * PlaygroundStateProvider's unmount cleanup to prevent HMR\n * subscriber leaks. Not part of the public PlaygroundState\n * surface — do not call from app code (you have `subscribe()`'s\n * returned unsub function for the public path).\n */\n _clearAllSubscribers(): void;\n}\n\n/**\n * Create a fresh state store. Public so PlaygroundStateProvider can\n * construct one, but consumers should use Provider/usePlaygroundState\n * to access stores (not direct construction).\n *\n * The optional type parameter is the \"shape\" of values you intend to\n * store. It's purely a documentation convenience — the store does\n * not enforce it (so you can store heterogeneous values for\n * playgrounds that mix kinds), but `get<T>()` and `set<T>()` will\n * infer correctly when you pass it here.\n */\nexport function createPlaygroundState<T = unknown>(\n initial: Record<string, T> = {},\n): PlaygroundState {\n const values = new Map<string, unknown>(Object.entries(initial));\n // key -> Set of subscribers. We use Set for O(1) add/remove and\n // dedup of identical subscribers.\n const subs = new Map<string, Set<(value: unknown) => void>>();\n\n // Pending (key, value) pairs to fire on the next microtask. We\n // batch so a sequence of `set()` calls in the same tick produces a\n // single subscriber fire per affected key.\n let pending: Array<{ key: string; value: unknown }> = [];\n let scheduled = false;\n // Transactional batching: when `batchDepth > 0`, set() pushes to\n // `batchedPending` instead of `pending`. flush() flushes the\n // NON-batched pending queue (preserving history) but the batched\n // queue is collapsed: only the LAST value per key is kept. This\n // is what `batch(state, fn)` enables — atomic, deduplicated commits.\n let batchDepth = 0;\n let batchedPending: Array<{ key: string; value: unknown }> = [];\n\n const flush = (): void => {\n if (pending.length === 0) return;\n const batch = pending;\n pending = [];\n scheduled = false;\n // Fire every pending (key, value) pair, in order. We do NOT\n // dedupe — each `set()` call is a distinct event and subscribers\n // want to see the full history (e.g. for analytics). React\n // batches re-renders via the microtask flush anyway, so multiple\n // fires for the same key collapse into one render.\n for (const { key, value } of batch) {\n const list = subs.get(key);\n if (list) {\n // Snapshot the subscriber set so callbacks that unsubscribe\n // themselves mid-fire don't mutate the iteration source.\n for (const cb of [...list]) {\n try {\n cb(value);\n } catch (err) {\n // Swallow subscriber errors so one bad subscriber doesn't\n // take down the whole tree. In development we re-raise\n // the error to console.error so the bad subscriber is\n // diagnosable from the browser devtools; in production\n // we stay silent to avoid log spam.\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n '[playgenx] subscriber for key %s threw:',\n key,\n err,\n );\n }\n }\n }\n }\n }\n };\n\n const schedule = (): void => {\n if (scheduled) return;\n scheduled = true;\n // queueMicrotask is available in Node 22+ and all modern browsers.\n // Falls back to Promise.resolve().then for older runtimes.\n if (typeof queueMicrotask === 'function') {\n queueMicrotask(flush);\n } else {\n Promise.resolve().then(flush);\n }\n };\n\n const state: InternalState = {\n get<T = unknown>(key: string): T | undefined {\n return values.get(key) as T | undefined;\n },\n set<T = unknown>(key: string, value: T): void {\n if (Object.is(values.get(key), value)) return; // no-op\n values.set(key, value);\n // Inside a batch: push to the dedupe queue instead of pending.\n // Outside a batch: push to pending (full-history mode).\n if (batchDepth > 0) {\n batchedPending.push({ key, value });\n } else {\n pending.push({ key, value });\n }\n schedule();\n },\n subscribe(key: string, cb: (value: unknown) => void): () => void {\n let list = subs.get(key);\n if (!list) {\n list = new Set();\n subs.set(key, list);\n }\n list.add(cb);\n return () => {\n const l = subs.get(key);\n if (l) {\n l.delete(cb);\n if (l.size === 0) subs.delete(key);\n }\n };\n },\n snapshot(): Readonly<Record<string, unknown>> {\n return Object.fromEntries(values);\n },\n replaceAll(values: Record<string, unknown>): void {\n for (const [k, v] of Object.entries(values)) {\n state.set(k, v);\n }\n },\n _flush(): void {\n flush();\n },\n _batch<T>(fn: () => T): T {\n batchDepth++;\n try {\n return fn();\n } finally {\n batchDepth--;\n // After the batch: if anything landed in batchedPending,\n // move it to pending AS A SINGLE EVENT PER KEY. Walk in\n // reverse so the LAST set wins (later writes to the same\n // key override earlier ones).\n if (batchDepth === 0 && batchedPending.length > 0) {\n const lastPerKey = new Map<string, unknown>();\n for (let i = batchedPending.length - 1; i >= 0; i--) {\n const { key, value } = batchedPending[i]!;\n if (!lastPerKey.has(key)) {\n lastPerKey.set(key, value);\n pending.push({ key, value });\n }\n }\n batchedPending = [];\n }\n }\n },\n _clearAllSubscribers(): void {\n // Replace with fresh maps; old subscribers are dropped on the\n // GC pass once their closure references go out of scope. This\n // is intentionally a hard reset — used only on Provider\n // unmount, where every subscriber in the tree is going away.\n subs.clear();\n // Also drop any pending fires that would have been delivered\n // to those now-dead subscribers. Without this, a stale set\n // queued just before unmount would still fire `cb(value)` on\n // a callback that has no consumer.\n pending = [];\n batchedPending = [];\n scheduled = false;\n },\n };\n return state;\n}\n\ninterface ProviderContextValue {\n state: PlaygroundState;\n}\n\n/**\n * React context. Exposed as a type only — consumers should use\n * PlaygroundStateProvider and usePlaygroundState rather than reading\n * the context directly.\n */\nconst PlaygroundStateContext = React.createContext<ProviderContextValue | null>(\n null,\n);\n\nexport interface PlaygroundStateProviderProps {\n /** Initial state values keyed by name. */\n initial?: Record<string, unknown>;\n /**\n * Optional pre-existing state. If supplied, `initial` is merged on\n * top (initial wins for overlapping keys). Use this for SSR\n * rehydration or seeded values.\n */\n seed?: Record<string, unknown>;\n children: React.ReactNode;\n}\n\n/**\n * Provide a PlaygroundState scope to descendant components.\n *\n * Each Provider instance has its own store; nested Providers create\n * independent scopes. Components rendered outside any Provider will\n * throw a clear error if they call usePlaygroundState.\n */\nexport function PlaygroundStateProvider(\n props: PlaygroundStateProviderProps,\n): React.JSX.Element {\n // Create the store once per Provider instance. The lazy initializer\n // ensures re-mounting (e.g. HMR) doesn't carry stale state across\n // refreshes.\n const [state] = React.useState(() => {\n const s = createPlaygroundState({ ...props.seed, ...props.initial });\n return s;\n });\n\n // Cleanup on unmount: clear all subscribers so a hot-reload doesn't\n // leak subscriptions across renders. Without this, every HMR cycle\n // would leave the OLD store with stale subscribers that fire on\n // every set — even though no component is listening.\n //\n // Why we don't drop the values Map: by the time this cleanup runs,\n // the closure over the store is unreachable. Letting the GC collect\n // it is enough. Subscribers are the leak risk because they're\n // closures that reference component-local state (setValue, force).\n React.useEffect(() => {\n return () => {\n // Clear all subscribers via the test seam. (The store doesn't\n // expose a public clear-all API because removing subscribers\n // without notifying them is almost always a bug. Unmount is\n // the one legitimate case.)\n (state as unknown as { _clearAllSubscribers?: () => void })\n ._clearAllSubscribers?.();\n };\n }, [state]);\n\n const value = React.useMemo(() => ({ state }), [state]);\n\n return React.createElement(\n PlaygroundStateContext.Provider,\n { value },\n props.children,\n );\n}\n\n/**\n * Hook to access the nearest PlaygroundState store. Throws a clear\n * error if called outside a Provider so consumers get a usable\n * diagnostic instead of a `Cannot read properties of undefined`.\n *\n * Note for @playgenx/renderer users: the renderer's `renderBody`\n * does NOT yet auto-wrap with a PlaygroundStateProvider. You'll\n * need to wrap your own tree with `<PlaygroundStateProvider>` (or\n * use the lower-level `renderNodes` API) before state-bound\n * components can resolve. The `withState` option on `renderBody`\n * is planned for a future renderer release (tracked in v0.6.0).\n */\nexport function usePlaygroundState(): PlaygroundState {\n const ctx = React.useContext(PlaygroundStateContext);\n if (ctx === null) {\n throw new Error(\n 'usePlaygroundState() must be called inside a <PlaygroundStateProvider>. ' +\n 'Wrap your component tree with the Provider. (A future renderer ' +\n 'release will auto-wrap; for now, do it explicitly.)',\n );\n }\n return ctx.state;\n}\n\n/**\n * Optional helper for binding a component prop to a state key. Returns\n * the live value (re-renders subscribers on change) and a setter. Pass\n * `undefined` for `key` to disable the binding — the component behaves\n * as if it had no stateKey, and `set` becomes a no-op. Safe to call\n * outside a Provider.\n *\n * Implementation note: this hook ALWAYS calls the same number of\n * React hooks regardless of whether `key` is set or a Provider is in\n * scope. The \"no binding\" / \"no Provider\" branch uses dummy local\n * state so the hook count stays aligned across renders. That means a\n * component can flip `stateKey` from defined to undefined (or move in\n * or out of a Provider) without crashing with a hooks-order error.\n *\n * Components that accept a `stateKey` prop should call this internally\n * and use the returned tuple as a drop-in for the underlying\n * value/onChange pair. The return type widens to `readonly [...] | undefined`\n * so the caller can still branch on `if (bound)` — but they don't have\n * to (the tuple is always safe to index).\n *\n * @example\n * const bound = useBoundValueOrUndefined<number>(stateKey, 50);\n * // ... onChange={(e) => bound?.[1](Number(e.currentTarget.value))}\n */\nexport function useBoundValueOrUndefined<T>(\n key: string | undefined,\n fallback: T,\n): readonly [T, (next: T) => void] | undefined {\n // Read the Provider context. `useContext` is unconditional and counts\n // as a hook, but its *value* can be null — that's fine. The point\n // is we never skip hooks based on a runtime condition.\n const ctx = React.useContext(PlaygroundStateContext);\n const bound = key !== undefined && ctx !== null;\n\n // Always allocate the same hook slots so the count is constant.\n // We use `fallback` as the initial value in both branches; if a\n // Provider + key are present the effect below pulls the live value.\n const [value, setValue] = React.useState<T>(fallback);\n const [, force] = React.useState(0);\n React.useEffect(() => {\n if (!bound || key === undefined || ctx === null) return;\n const initial = (ctx.state.get<T>(key) ?? fallback) as T;\n setValue(initial);\n const unsub = ctx.state.subscribe(key, (next) => {\n setValue(next as T);\n force((n) => n + 1);\n });\n return unsub;\n // We deliberately exclude `fallback` from deps: a changing fallback\n // would re-subscribe on every render. The cost is that if `fallback`\n // changes the visible value won't refresh — but that mirrors how\n // useBoundValue behaves (no refresh on fallback change either).\n }, [bound, ctx, key]);\n const set = React.useCallback(\n (next: T) => {\n if (!bound || key === undefined || ctx === null) return; // no-op\n ctx.state.set<T>(key, next);\n },\n [bound, ctx, key],\n );\n // When no binding is active, callers get an explicit `undefined`\n // tuple so they can branch on `if (bound)` without falsey\n // confusion (a `0` or `''` value would otherwise hide the binding\n // state).\n if (!bound) return undefined;\n return [value, set] as const;\n}\n\nexport function useBoundValue<T>(\n key: string,\n fallback: T,\n): readonly [T, (next: T) => void] {\n const bound = useBoundValueOrUndefined<T>(key, fallback);\n if (bound === undefined) {\n throw new Error(\n 'useBoundValue(key, ...) requires both a non-undefined `key` and a ' +\n '<PlaygroundStateProvider> ancestor. Use useBoundValueOrUndefined ' +\n 'if you want optional binding.',\n );\n }\n return bound;\n}\n\n/**\n * State-binding action shape. Used by `<Button onClickAction={...}>`.\n * { set: { count: 1 } } -> sets state.count = 1\n * { toggle: 'open' } -> sets state.open = !state.open\n */\nexport type StateAction =\n | { set?: Record<string, unknown>; toggle?: string }\n | undefined;\n\n/**\n * Apply a StateAction to the nearest PlaygroundState. Returns an\n * onClick handler that fires the action. If action is undefined\n * the handler is a no-op.\n */\nexport function useStateAction(\n action: StateAction,\n): (event: unknown) => void {\n const state = usePlaygroundState();\n // The `event` parameter (from onClick / onSubmit) is the React\n // event the caller hands us. We don't introspect it — actions\n // are identified purely by their `action` config. The eslint\n // config's `argsIgnorePattern` doesn't apply to callback params\n // inside `React.useCallback` closures (it only sees the outer\n // function's params), so we use a void cast to silence the lint\n // while keeping the type signature for callers.\n return React.useCallback(\n (event: unknown) => {\n void event; // explicitly unused\n if (action === undefined) return;\n if (action.set) {\n for (const [k, v] of Object.entries(action.set)) {\n state.set(k, v);\n }\n }\n if (action.toggle !== undefined) {\n const key = action.toggle;\n const current = state.get<unknown>(key);\n state.set(key, !current);\n }\n },\n [action, state],\n );\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\nimport { useBoundValueOrUndefined } from './state.jsx';\n\nexport interface SliderProps {\n /** Lower bound, inclusive. Required. */\n min: number;\n /** Upper bound, inclusive. Required. */\n max: number;\n /** Initial / controlled value. */\n value?: number;\n /** Increment between stops. Defaults to 1. */\n step?: number;\n /** Optional caption rendered above the slider. */\n label?: string;\n /**\n * When set, the slider is \"bound\" to the playground state: initial\n * value comes from state.get(stateKey), onChange writes state.set.\n * `value` prop is ignored when stateKey is present.\n */\n stateKey?: string;\n}\n\n/**\n * Range input. Three operating modes:\n *\n * 1. **Uncontrolled** (no `value`, no `stateKey`): the slider owns\n * its value as local React state. The onChange mutates that state.\n * Use this when nothing else needs to observe the value.\n *\n * 2. **Controlled by `value` prop**: the slider is read-only —\n * user input is ignored because there is no `onChange` callback\n * wired by the deterministic renderer. Use this for \"static\"\n * sliders in artifacts. (If you want fully interactive control,\n * use mode 3.)\n *\n * 3. **Bound to PlaygroundState via `stateKey`**: the slider reads\n * its value from the nearest Provider's store (initial: either\n * the store's current value OR `value` prop, whichever is\n * present; the live store wins when bound). On change, the\n * slider writes to the store. Other components can subscribe\n * to the same key.\n *\n * In all modes the displayed value is clamped to `[min, max]`. NaN\n * inputs clamp to `min`. When `min === max` the input is disabled.\n */\nexport function Slider({ min, max, value, step = 1, label, stateKey }: SliderProps): React.JSX.Element {\n // When stateKey is set, bind to playground state. useBoundValue\n // throws if no Provider is in scope; useBoundValueOrUndefined\n // returns undefined when stateKey is undefined so the rest of the\n // component can use the local-state behaviour.\n const [internal, setInternal] = React.useState(() => clamp(value ?? min, min, max));\n const bound = useBoundValueOrUndefined<number>(stateKey, internal);\n const controlled = value !== undefined;\n // When stateKey is bound, the bound value is the source of truth.\n const current = stateKey !== undefined && bound\n ? clamp(bound[0], min, max)\n : controlled\n ? clamp(value, min, max)\n : internal;\n const trackStyle: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: '6px',\n fontFamily: 'inherit',\n width: '100%',\n };\n const labelStyle: React.CSSProperties = {\n fontSize: '12px',\n color: colors.mutedFg,\n };\n const wrapStyle: React.CSSProperties = { display: 'flex', alignItems: 'center', gap: '10px' };\n const inputStyle: React.CSSProperties = { flex: 1, accentColor: colors.sliderFill };\n const valueStyle: React.CSSProperties = {\n minWidth: '32px',\n textAlign: 'right',\n fontVariantNumeric: 'tabular-nums',\n fontSize: '14px',\n color: colors.fg,\n };\n return (\n <div style={trackStyle} data-pgx=\"Slider\">\n {label !== undefined ? <span style={labelStyle}>{label}</span> : null}\n <div style={wrapStyle}>\n <input\n type=\"range\"\n min={min}\n max={max}\n step={step}\n value={current}\n disabled={min === max}\n aria-label={label}\n onChange={(e) => {\n const next = Number(e.currentTarget.value);\n if (stateKey !== undefined && bound) {\n bound[1](next);\n } else if (!controlled) {\n setInternal(next);\n }\n }}\n style={inputStyle}\n />\n <span style={valueStyle}>{current}</span>\n </div>\n </div>\n );\n}\n\nfunction clamp(n: number, lo: number, hi: number): number {\n if (Number.isNaN(n)) return lo;\n return Math.min(hi, Math.max(lo, n));\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport type ChartKind = 'bar' | 'line' | 'pie';\n\n/**\n * The schema lets `data` be any ReactNode, but in practice callers\n * pass JSON-shaped arrays. We narrow at runtime:\n *\n * - bar: `{ labels: string[], values: number[] }`\n * - line: `{ x: number[], y: number[] }` or `{ series: { label, points: {x,y} }[] }`\n * - pie: `{ labels: string[], values: number[] }`\n *\n * Anything that doesn't fit is rendered as a `<pre>` fallback.\n */\nexport interface ChartProps {\n kind: ChartKind;\n data: unknown;\n title?: string;\n}\n\nexport function Chart({ kind, data, title }: ChartProps): React.JSX.Element {\n return (\n <div data-pgx=\"Chart\" data-chart-kind={kind} style={{ fontFamily: 'inherit' }}>\n {title !== undefined ? (\n <div style={{ fontSize: '14px', fontWeight: 600, color: colors.fg, marginBottom: '6px' }}>\n {title}\n </div>\n ) : null}\n {renderChart(kind, data)}\n </div>\n );\n}\n\nfunction renderChart(kind: ChartKind, data: unknown): React.JSX.Element {\n if (kind === 'bar') return renderBar(data);\n if (kind === 'line') return renderLine(data);\n return renderPie(data);\n}\n\nfunction renderBar(data: unknown): React.JSX.Element {\n const parsed = parseLabeled(data);\n if (!parsed) return fallback('bar chart', data);\n const { labels, values } = parsed;\n const max = Math.max(1, ...values);\n const width = 320;\n const height = 160;\n const barWidth = width / Math.max(1, labels.length);\n return (\n <svg\n width={width}\n height={height}\n role=\"img\"\n aria-label=\"Bar chart\"\n style={{ display: 'block' }}\n >\n {values.map((v, i) => {\n const h = (Math.max(0, v) / max) * (height - 24);\n return (\n <g key={i}>\n <rect\n x={i * barWidth + 2}\n y={height - 12 - h}\n width={Math.max(1, barWidth - 4)}\n height={h}\n fill={colors.bar}\n rx={2}\n />\n <text\n x={i * barWidth + barWidth / 2}\n y={height - 2}\n fontSize={10}\n textAnchor=\"middle\"\n fill={colors.barLabel}\n >\n {(labels[i] ?? '').slice(0, 8)}\n </text>\n </g>\n );\n })}\n </svg>\n );\n}\n\nfunction renderLine(data: unknown): React.JSX.Element {\n // Two accepted shapes; pick whichever parses cleanly.\n let points: Array<{ x: number; y: number }> = [];\n if (data && typeof data === 'object' && 'series' in (data as Record<string, unknown>)) {\n const series = (data as { series: Array<{ points?: Array<{ x: number; y: number }> }> }).series;\n const first = series?.[0];\n points = Array.isArray(first?.points) ? first.points : [];\n } else if (\n data &&\n typeof data === 'object' &&\n Array.isArray((data as { x?: unknown }).x) &&\n Array.isArray((data as { y?: unknown }).y)\n ) {\n const xArr = (data as { x: number[] }).x;\n const yArr = (data as { y: number[] }).y;\n points = xArr.map((x, i) => ({ x, y: yArr[i] ?? 0 }));\n }\n if (points.length === 0) return fallback('line chart', data);\n const xs = points.map((p) => p.x);\n const ys = points.map((p) => p.y);\n const xMin = Math.min(...xs);\n const xMax = Math.max(...xs);\n const yMin = Math.min(...ys);\n const yMax = Math.max(...ys);\n const w = 320;\n const h = 160;\n const sx = (x: number) => (xMax === xMin ? w / 2 : ((x - xMin) / (xMax - xMin)) * (w - 8) + 4);\n const sy = (y: number) =>\n yMax === yMin ? h / 2 : h - ((y - yMin) / (yMax - yMin)) * (h - 24) - 12;\n const path = points\n .map((p, i) => `${i === 0 ? 'M' : 'L'}${sx(p.x).toFixed(2)},${sy(p.y).toFixed(2)}`)\n .join(' ');\n return (\n <svg width={w} height={h} role=\"img\" aria-label=\"Line chart\" style={{ display: 'block' }}>\n <path d={path} fill=\"none\" stroke={colors.line} strokeWidth={2} />\n {points.map((p, i) => (\n <circle key={i} cx={sx(p.x)} cy={sy(p.y)} r={2} fill={colors.line} />\n ))}\n </svg>\n );\n}\n\nfunction renderPie(data: unknown): React.JSX.Element {\n const parsed = parseLabeled(data);\n if (!parsed) return fallback('pie chart', data);\n const total = parsed.values.reduce((a, b) => a + Math.max(0, b), 0) || 1;\n const cx = 80;\n const cy = 80;\n const r = 60;\n let angle = -Math.PI / 2;\n return (\n <svg width={160} height={160} role=\"img\" aria-label=\"Pie chart\" style={{ display: 'block' }}>\n {parsed.values.map((v, i) => {\n const slice = (Math.max(0, v) / total) * Math.PI * 2;\n const x1 = cx + r * Math.cos(angle);\n const y1 = cy + r * Math.sin(angle);\n const x2 = cx + r * Math.cos(angle + slice);\n const y2 = cy + r * Math.sin(angle + slice);\n const large = slice > Math.PI ? 1 : 0;\n const path =\n slice >= Math.PI * 2 - 1e-6\n ? `M ${cx - r} ${cy} A ${r} ${r} 0 1 1 ${cx + r} ${cy} A ${r} ${r} 0 1 1 ${cx - r} ${cy}`\n : `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;\n angle += slice;\n const fill = colors.pie[i % colors.pie.length] ?? colors.bar;\n return <path key={i} d={path} fill={fill} />;\n })}\n </svg>\n );\n}\n\nfunction parseLabeled(data: unknown): { labels: string[]; values: number[] } | null {\n if (!data || typeof data !== 'object') return null;\n const labels = (data as { labels?: unknown }).labels;\n const values = (data as { values?: unknown }).values;\n if (!Array.isArray(labels) || !Array.isArray(values)) return null;\n if (labels.length !== values.length) return null;\n if (!labels.every((l) => typeof l === 'string' || typeof l === 'number')) return null;\n if (!values.every((v) => typeof v === 'number' || typeof v === 'string')) return null;\n return {\n labels: labels.map(String),\n values: values.map((v) => (typeof v === 'number' ? v : Number(v))),\n };\n}\n\nfunction fallback(label: string, data: unknown): React.JSX.Element {\n return (\n <pre\n style={{\n margin: 0,\n fontSize: '12px',\n background: colors.codeBg,\n color: colors.codeFg,\n padding: '8px',\n borderRadius: '4px',\n overflow: 'auto',\n }}\n >\n {label} data: {JSON.stringify(data, null, 2)}\n </pre>\n );\n}\n","import * as React from 'react';\n\nexport interface ContainerProps {\n padding?: string;\n gap?: string;\n children?: React.ReactNode;\n}\n\n/**\n * Layout wrapper. Defaults to a vertical flex container with 16px\n * padding and 12px gap when callers omit props.\n */\nexport function Container({ padding, gap, children }: ContainerProps): React.JSX.Element {\n const style: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n padding: padding ?? '16px',\n gap: gap ?? '12px',\n fontFamily: 'inherit',\n color: 'inherit',\n boxSizing: 'border-box',\n };\n return (\n <div style={style} data-pgx=\"Container\">\n {children}\n </div>\n );\n}\n","import * as React from 'react';\nimport { colors, radius } from './theme.js';\n\nexport interface CodeProps {\n language?: string;\n children?: React.ReactNode;\n}\n\n/**\n * Inline code formatter. Renders children as a `<code>` element inside\n * a styled wrapper. If `children` is a single string, it displays\n * verbatim; for multi-line content we fall back to a `<pre>`-styled\n * block.\n */\nexport function Code({ language, children }: CodeProps): React.JSX.Element {\n const isString = typeof children === 'string';\n const isMulti = isString && (children as string).includes('\\n');\n if (isMulti) {\n return (\n <pre\n data-pgx=\"Code\"\n data-language={language ?? 'text'}\n style={{\n background: colors.codeBg,\n color: colors.codeFg,\n padding: '12px',\n borderRadius: radius.md,\n fontSize: '12px',\n overflow: 'auto',\n margin: 0,\n }}\n >\n <code>{children}</code>\n </pre>\n );\n }\n return (\n <code\n data-pgx=\"Code-inline\"\n data-language={language ?? 'text'}\n style={{\n background: colors.codeBg,\n color: colors.codeFg,\n padding: '2px 6px',\n borderRadius: radius.sm,\n fontSize: '0.9em',\n }}\n >\n {children}\n </code>\n );\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport interface HeadingProps {\n /**\n * Heading level (1..6). Out-of-range or undefined falls back to 2.\n * The renderer should never see an `Undefined` value here unless the\n * schema validator said optional props can be omitted.\n */\n level?: number;\n color?: string;\n children?: React.ReactNode;\n}\n\nconst sizes: Record<number, string> = {\n 1: '24px',\n 2: '20px',\n 3: '18px',\n 4: '16px',\n 5: '14px',\n 6: '13px',\n};\n\nexport function Heading({ level, color, children }: HeadingProps): React.JSX.Element {\n const safeLevel = clampLevel(level);\n const size = sizes[safeLevel] ?? sizes[2]!;\n const style: React.CSSProperties = {\n fontFamily: 'inherit',\n fontSize: size,\n fontWeight: 700,\n color: color ?? colors.fg,\n margin: 0,\n lineHeight: 1.3,\n };\n // Render an element corresponding to the level. React enforces\n // TagName = 'h1' | 'h2' ... via JSX, so we switch dynamically via\n // createElement.\n return React.createElement(`h${safeLevel}`, { style, 'data-pgx': `Heading` }, children);\n}\n\nfunction clampLevel(level: number | undefined): 1 | 2 | 3 | 4 | 5 | 6 {\n if (typeof level !== 'number' || !Number.isFinite(level)) return 2;\n const i = Math.floor(level);\n if (i < 1) return 1;\n if (i > 6) return 6;\n return i as 1 | 2 | 3 | 4 | 5 | 6;\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport interface TextProps {\n weight?: 'normal' | 'bold';\n size?: string;\n color?: string;\n children?: React.ReactNode;\n}\n\nexport function Text({ weight = 'normal', size, color, children }: TextProps): React.JSX.Element {\n const style: React.CSSProperties = {\n fontFamily: 'inherit',\n fontSize: size ?? '14px',\n fontWeight: weight === 'bold' ? 600 : 400,\n color: color ?? colors.fg,\n margin: 0,\n lineHeight: 1.5,\n };\n return (\n <p style={style} data-pgx=\"Text\">\n {children}\n </p>\n );\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport interface Step {\n id: string;\n title: string;\n body?: React.ReactNode;\n}\n\nexport interface StepperProps {\n steps: Step[];\n /** 0-indexed initial step. Out-of-range falls back to 0. */\n initial?: number;\n}\n\n/**\n * Multi-step reveal. State: the active index. Previous steps can be\n * revisited; future steps are disabled until reached.\n */\nexport function Stepper({ steps, initial }: StepperProps): React.JSX.Element {\n const initialIdx = clampIndex(initial ?? 0, steps.length);\n const [active, setActive] = React.useState(initialIdx);\n const wrapStyle: React.CSSProperties = {\n fontFamily: 'inherit',\n display: 'flex',\n flexDirection: 'column',\n gap: '12px',\n };\n const listStyle: React.CSSProperties = {\n display: 'flex',\n gap: '6px',\n listStyle: 'none',\n padding: 0,\n margin: 0,\n };\n const stepBtnBase: React.CSSProperties = {\n width: '28px',\n height: '28px',\n borderRadius: '50%',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontFamily: 'inherit',\n fontSize: '12px',\n fontWeight: 600,\n border: '1px solid transparent',\n };\n const bodyStyle: React.CSSProperties = {\n border: `1px solid ${colors.border}`,\n borderRadius: '6px',\n padding: '10px',\n background: colors.bg,\n color: colors.fg,\n };\n return (\n <div data-pgx=\"Stepper\" style={wrapStyle}>\n <ol style={listStyle}>\n {steps.map((s, i) => {\n const isPast = i < active;\n const isCurrent = i === active;\n const future = i > active;\n const stepStyle: React.CSSProperties = {\n ...stepBtnBase,\n background: isCurrent\n ? colors.stepperCurrent\n : isPast\n ? colors.stepperPast\n : colors.stepperFuture,\n color: isCurrent || isPast ? '#fff' : colors.fg,\n opacity: future ? 0.6 : 1,\n cursor: future ? 'not-allowed' : 'pointer',\n };\n return (\n <li key={s.id}>\n <button\n type=\"button\"\n aria-label={`Step ${i + 1}: ${s.title}`}\n aria-current={isCurrent ? 'step' : undefined}\n disabled={future}\n onClick={() => setActive(i)}\n style={stepStyle}\n >\n {i + 1}\n </button>\n </li>\n );\n })}\n </ol>\n <div style={bodyStyle}>\n <div style={{ fontWeight: 600, marginBottom: '4px', fontSize: '14px' }}>\n {steps[active]?.title ?? ''}\n </div>\n <div style={{ fontSize: '13px' }}>{steps[active]?.body ?? null}</div>\n </div>\n </div>\n );\n}\n\nfunction clampIndex(n: number, len: number): number {\n if (len === 0) return 0;\n if (!Number.isFinite(n)) return 0;\n return Math.min(len - 1, Math.max(0, Math.floor(n)));\n}\n","import * as React from 'react';\nimport { colors, radius, space } from './theme.js';\n\nexport interface CardProps {\n title?: string;\n /**\n * Visual elevation. 0 = flat (border only), 1+ = heavier shadow.\n * Defaults to 1.\n */\n elevation?: number;\n children?: React.ReactNode;\n}\n\nexport function Card({ title, elevation = 1, children }: CardProps): React.JSX.Element {\n const shadow =\n elevation >= 3\n ? '0 4px 12px rgba(15, 23, 42, 0.18)'\n : elevation === 2\n ? '0 2px 8px rgba(15, 23, 42, 0.12)'\n : elevation === 1\n ? '0 1px 2px rgba(15, 23, 42, 0.06)'\n : 'none';\n const cardStyle: React.CSSProperties = {\n background: colors.cardBg,\n border: `1px solid ${colors.cardBorder}`,\n borderRadius: radius.md,\n boxShadow: shadow,\n padding: space.md,\n fontFamily: 'inherit',\n color: colors.fg,\n display: 'flex',\n flexDirection: 'column',\n gap: space.sm,\n };\n const titleStyle: React.CSSProperties = {\n fontSize: '14px',\n fontWeight: 600,\n margin: 0,\n };\n return (\n <section data-pgx=\"Card\" style={cardStyle}>\n {title !== undefined ? <h3 style={titleStyle}>{title}</h3> : null}\n {children}\n </section>\n );\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport interface ListProps {\n /**\n * Items to render. Schema marks this as `node`; in practice callers\n * pass arrays of strings/numbers/ReactNodes. Anything else is\n * rendered as a single bullet containing the value.\n */\n items?: unknown;\n /** When true, renders as `<ol>` instead of `<ul>`. */\n ordered?: boolean;\n /** Override the wrapper element for full control. */\n children?: React.ReactNode;\n}\n\n/**\n * List of items. Renders `items` as a list of strings/numbers (the\n * common artifact prompt output). If `children` are passed they take\n * precedence over `items`.\n */\nexport function List({ items, ordered, children }: ListProps): React.JSX.Element {\n const style: React.CSSProperties = {\n margin: 0,\n paddingLeft: '20px',\n fontFamily: 'inherit',\n color: colors.fg,\n fontSize: '14px',\n lineHeight: 1.6,\n };\n if (children !== undefined && children !== null) {\n return React.createElement(ordered ? 'ol' : 'ul', { style, 'data-pgx': 'List' }, children);\n }\n const arr = Array.isArray(items) ? items : items === undefined ? [] : [items];\n return React.createElement(\n ordered ? 'ol' : 'ul',\n { style, 'data-pgx': 'List' },\n arr.map((item, i) => {\n const node: React.ReactNode =\n typeof item === 'string' || typeof item === 'number' ? String(item) : JSON.stringify(item);\n return <li key={i}>{node}</li>;\n }),\n );\n}\n","/**\n * Helper functions for working with {@link PlaygroundState}.\n *\n * These are pure operations over a `PlaygroundState` — no React, no\n * hooks, no I/O. They live alongside the store so that:\n * - Devtools / CLI scripts can operate on a state without React\n * (the bin/state-cli.ts script uses these directly).\n * - Test code can drive a store through a thin façade.\n * - Server-side renderers can dump, validate, and diff snapshots\n * without needing a DOM.\n *\n * @packageDocumentation\n */\n\nimport type { PlaygroundState } from './state.jsx';\n\n/**\n * Structured diff between two state snapshots. Pure function — does\n * not touch the stores. Returns `null` when the snapshots are\n * shallowly equal (no additions, no changes, no removals).\n *\n * @example\n * const a = { x: 1, y: 2 };\n * const b = { x: 1, y: 3, z: 4 };\n * diffSnapshots(a, b); // { added: ['z'], changed: ['y'], removed: [] }\n */\nexport interface StateDiff {\n /** Keys present in `next` but absent from `prev`. */\n added: string[];\n /** Keys whose value changed (using `Object.is` to compare). */\n changed: string[];\n /** Keys present in `prev` but absent from `next`. */\n removed: string[];\n}\n\nexport function diffSnapshots(\n prev: Readonly<Record<string, unknown>>,\n next: Readonly<Record<string, unknown>>,\n): StateDiff | null {\n const added: string[] = [];\n const changed: string[] = [];\n const removed: string[] = [];\n const prevKeys = Object.keys(prev);\n const nextKeys = Object.keys(next);\n const prevSet = new Set(prevKeys);\n const nextSet = new Set(nextKeys);\n for (const k of nextKeys) {\n if (!prevSet.has(k)) {\n added.push(k);\n continue;\n }\n if (!Object.is(prev[k], next[k])) {\n changed.push(k);\n }\n }\n for (const k of prevKeys) {\n if (!nextSet.has(k)) removed.push(k);\n }\n added.sort();\n changed.sort();\n removed.sort();\n if (added.length === 0 && changed.length === 0 && removed.length === 0) {\n return null;\n }\n return { added, changed, removed };\n}\n\n/**\n * Convenience: take snapshots of both stores and diff them. Returns\n * `null` when equal. Useful in tests and debug endpoints.\n */\nexport function diffState(\n _a: PlaygroundState,\n _b: PlaygroundState,\n prev?: Readonly<Record<string, unknown>>,\n next?: Readonly<Record<string, unknown>>,\n): StateDiff | null {\n const aSnap = prev ?? _a.snapshot();\n const bSnap = next ?? _b.snapshot();\n return diffSnapshots(aSnap, bSnap);\n}\n\n/**\n * Validate a state-key string. Returns `null` on success, or a\n * human-readable error message on failure. The rules:\n *\n * - 1..64 characters\n * - No whitespace\n * - Only alphanumerics, dots, dashes, underscores\n * - Must start with an alphabetic character (so a parser can\n * safely infer `<Slider stateKey=\"...\">` from a body without\n * swallowing accidental numeric keys).\n *\n * Note: keys like `with/slash` or `with spaces` are rejected because\n * the schema-driven body parser uses them as attribute names —\n * a key with weird characters would break the round-trip.\n */\nexport function validateStateKey(key: string): string | null {\n if (key.length === 0) return 'stateKey must be a non-empty string';\n if (key.length > 64) return 'stateKey must be at most 64 characters';\n if (/\\s/.test(key)) return 'stateKey must not contain whitespace';\n if (!/^[A-Za-z][A-Za-z0-9._-]*$/.test(key)) {\n return (\n 'stateKey contains forbidden characters (allowed: A-Z, a-z, ' +\n '0-9, dot, dash, underscore; must start with a letter)'\n );\n }\n return null;\n}\n\n/**\n * Remove every key from the store, firing subscribers with `undefined`.\n * After this call, `state.snapshot()` is `{}`.\n */\nexport function clearState(state: PlaygroundState): void {\n // Snapshot first to avoid mutating the iteration source.\n for (const k of Object.keys(state.snapshot())) {\n state.set(k, undefined);\n }\n}\n\n/**\n * JSON-envelope snapshot for offline inspection. Useful for:\n * - piping into `jq` / debug endpoints\n * - attaching to error reports\n * - round-tripping through `JSON.parse(state.snapshot)`.\n *\n * The `version` field is bumped if the envelope shape ever changes,\n * so consumers can branch on it.\n */\nexport interface StateEnvelope {\n /** Always 1 for now. */\n version: 1;\n /** Sorted list of keys (stable for diffing). */\n keys: string[];\n /** Shallow key→value map. */\n values: Record<string, unknown>;\n}\n\nexport function dumpState(state: PlaygroundState): string {\n const snap = state.snapshot();\n const envelope: StateEnvelope = {\n version: 1,\n keys: Object.keys(snap).sort(),\n values: { ...snap },\n };\n return JSON.stringify(envelope, null, 2);\n}\n\n/**\n * Apply multiple `set` calls as a single atomic batch. Each\n * subscriber sees only the LAST value written to its key in this\n * batch. The store still batches subscriber fires via the microtask\n * scheduler — `batch` does not flush synchronously, it only ensures\n * that within the batch, repeated `set` calls to the same key don't\n * queue redundant fires.\n *\n * Useful for: undo/redo, multi-field form commits, hydrated state\n * restore where you want each subscriber to fire exactly once with\n * the final value.\n *\n * @example\n * batch(state, (s) => {\n * s.set('x', 1);\n * s.set('y', 2);\n * s.set('x', 3); // overwrites; subscriber sees only 3\n * });\n */\nexport function batch<T>(\n state: PlaygroundState,\n fn: (state: PlaygroundState) => T,\n): T {\n // `_batch` is the underlying transactional primitive on the store.\n // We type-cast because the public PlaygroundState interface doesn't\n // expose this test seam — only the helper does.\n const internal = state as unknown as {\n _batch: (fn: () => T) => T;\n };\n return internal._batch(() => fn(state));\n}","/**\n * Render-prop component that shows a toggle-able raw-source panel.\n * Consumer controls the trigger UI via a children render prop.\n *\n * Design notes:\n * - Render-prop pattern (not props.children-as-trigger) because the\n * trigger UI varies wildly across consumers (button, link, icon,\n * inline text). The consumer gets `(toggle, showing)` and decides\n * what to render.\n * - The source panel is a `<pre>` with `<code>` inside. Plain-text;\n * no syntax highlighting (avoiding a 50KB highlight.js dep).\n * Consumers who need highlighting can compose with their own\n * syntax-highlighter.\n * - The panel is rendered INLINE (not in a portal/tooltip) so the\n * toggle's visual feedback is immediate.\n *\n * @packageDocumentation\n */\n\nimport * as React from 'react';\n\nexport interface ShowSourceProps {\n /** The raw body to display when the user expands the source. */\n body: string;\n /** Source language hint. Used in `data-language` for styling hooks. */\n language?: 'tsx' | 'json';\n /**\n * Children is a render prop that receives `(toggle, showing)`.\n * The consumer is expected to render a trigger element (button,\n * link, etc.) and call `toggle` on user interaction.\n */\n children: (toggle: () => void, showing: boolean) => React.ReactNode;\n}\n\n/**\n * Toggle button + expandable source panel. The trigger UI is the\n * consumer's choice; the panel is plain `<pre><code>`.\n */\nexport function ShowSource(props: ShowSourceProps): React.JSX.Element {\n const { body, language, children } = props;\n const [showing, setShowing] = React.useState(false);\n // Wrap setShowing to keep the toggle reference stable. Consumers\n // can pass `toggle` to a `useEffect` dep array without retriggering.\n const toggle = React.useCallback(() => {\n setShowing((s) => !s);\n }, []);\n\n return (\n <>\n {children(toggle, showing)}\n {showing ? (\n <pre\n data-pgx=\"ShowSource\"\n data-language={language ?? 'text'}\n style={{\n background: '#0f172a',\n color: '#e2e8f0',\n padding: '12px',\n borderRadius: '6px',\n overflowX: 'auto',\n fontFamily: 'ui-monospace, Menlo, monospace',\n fontSize: '12px',\n lineHeight: 1.5,\n margin: '8px 0 0',\n maxHeight: '320px',\n whiteSpace: 'pre',\n }}\n >\n <code>{body}</code>\n </pre>\n ) : null}\n </>\n );\n}\n","/**\n * Error boundary for rendered artifacts. Catches render-time errors\n * (malformed body, throw from a component, etc.) and shows a recoverable\n * fallback. Without this, a single bad artifact blanks the student's\n * whole page.\n *\n * Design notes:\n * - Uses React's class-component error boundary API (the only\n * first-class option in React 18/19).\n * - Default fallback shows the error message + an optional \"Show source\"\n * toggle that reveals the raw body. Consumer can override with\n * `fallback` for custom UX.\n * - `onError` fires before the fallback renders so consumers can log\n * to Sentry / Datadog / etc.\n *\n * @packageDocumentation\n */\n\nimport * as React from 'react';\n\nimport { ShowSource } from './ShowSource.js';\n\nexport interface ArtifactErrorBoundaryProps {\n children: React.ReactNode;\n /**\n * Called when a render error is caught. The `info.componentStack`\n * string is the React component stack at the point of error. Log\n * to your observability backend here.\n */\n onError?: (error: Error, info: { componentStack: string }) => void;\n /**\n * Optional body string. If present and the consumer renders the\n * default fallback, a \"Show source\" toggle will be available that\n * reveals this body verbatim. Ignored if a custom `fallback` is\n * supplied (the consumer is responsible for surfacing source).\n */\n body?: string;\n /**\n * Source language hint for the ShowSource toggle in the default\n * fallback. Defaults to 'tsx' (most artifacts are TSX).\n */\n language?: 'tsx' | 'json';\n /**\n * Optional custom fallback. Pass a React element for a static fallback\n * or a function that receives the error for a dynamic one. If\n * omitted, the default fallback renders a panel with the error\n * message and (if `body` is supplied) a ShowSource toggle.\n */\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n /**\n * Label shown in the default fallback header. Defaults to the\n * artifact kind identifier (e.g. \"playground artifact failed to\n * render\"). The consumer is expected to pass the kind.\n */\n kind?: string;\n}\n\ninterface State {\n error: Error | null;\n}\n\n/**\n * Default fallback panel. Renders the error message and, if `body` is\n * provided, a ShowSource toggle. Designed to be a calm, recoverable\n * surface — not a stack-trace dump that panics users.\n */\nfunction DefaultFallback({\n error,\n body,\n language,\n kind,\n}: {\n error: Error;\n body?: string;\n language?: 'tsx' | 'json';\n kind?: string;\n}): React.JSX.Element {\n const label = kind ? `${kind} artifact failed to render` : 'Artifact failed to render';\n return (\n <div\n role=\"alert\"\n data-pgx=\"ArtifactErrorBoundary\"\n data-pgx-kind={kind}\n style={{\n border: '1px solid #fca5a5',\n background: '#fef2f2',\n color: '#7f1d1d',\n borderRadius: '8px',\n padding: '12px 16px',\n fontFamily: 'ui-sans-serif, system-ui, sans-serif',\n fontSize: '14px',\n maxWidth: '720px',\n margin: '12px 0',\n }}\n >\n <div style={{ fontWeight: 600, marginBottom: '6px' }}>{label}</div>\n <code\n data-pgx=\"error-message\"\n style={{\n display: 'block',\n fontFamily: 'ui-monospace, Menlo, monospace',\n fontSize: '12px',\n background: 'rgba(0,0,0,0.04)',\n padding: '6px 8px',\n borderRadius: '4px',\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n }}\n >\n {error.message}\n </code>\n {body !== undefined ? (\n <div style={{ marginTop: '8px' }}>\n <ShowSource body={body} language={language}>\n {(toggle, showing) => (\n <button\n type=\"button\"\n onClick={toggle}\n data-pgx=\"show-source-toggle\"\n style={{\n background: 'transparent',\n border: '1px solid #fca5a5',\n color: '#7f1d1d',\n padding: '4px 10px',\n fontSize: '12px',\n borderRadius: '4px',\n cursor: 'pointer',\n }}\n >\n {showing ? 'Hide source' : 'Show source'}\n </button>\n )}\n </ShowSource>\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Error boundary. Catches render-time errors in `children` and shows\n * the fallback. The error state is reset on prop change of `children`\n * (or explicit `resetKey` bump) so the consumer can recover by\n * re-rendering.\n */\nexport class ArtifactErrorBoundary extends React.Component<\n ArtifactErrorBoundaryProps,\n State\n> {\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n override componentDidCatch(error: Error, info: React.ErrorInfo): void {\n if (this.props.onError) {\n try {\n this.props.onError(error, { componentStack: info.componentStack ?? '' });\n } catch {\n // Swallow — onError callback errors must not break rendering.\n }\n }\n }\n\n override render(): React.ReactNode {\n const { error } = this.state;\n if (error === null) return this.props.children;\n\n const { fallback, body, language, kind } = this.props;\n if (typeof fallback === 'function') return fallback(error);\n if (fallback !== undefined) return fallback;\n return (\n <DefaultFallback\n error={error}\n body={body}\n language={language}\n kind={kind}\n />\n );\n }\n}\n","/**\n * `createRegistry(overrides)` — build a component map with the default\n * 11 components plus caller overrides. Returns a frozen map so post-hoc\n * mutation can't surprise concurrent renders.\n *\n * Lowercase HTML tags (div, span, etc.) are NOT in this map; they're\n * intrinsic to React and handled by `renderBody` directly.\n *\n * @packageDocumentation\n */\n\nimport type { ComponentType } from 'react';\n\nimport { Button } from './Button.js';\nimport { Card } from './Card.js';\nimport { Chart } from './Chart.js';\nimport { Code } from './Code.js';\nimport { Container } from './Container.js';\nimport { Heading } from './Heading.js';\nimport { List } from './List.js';\nimport { Slider } from './Slider.js';\nimport { Stepper } from './Stepper.js';\nimport { Text } from './Text.js';\nimport { TextField } from './TextField.js';\n\n/**\n * Map of PascalCase component name to React component. Loose typing\n * (`ComponentType<any>`) because each component's prop shape differs and\n * the runtime doesn't know which one it's looking up. Consumers who\n * need stricter types can build their own typed wrapper around\n * `renderBody`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ComponentMap = Record<string, ComponentType<any>>;\n\nconst DEFAULT_REGISTRY: ComponentMap = Object.freeze({\n Button,\n Card,\n Chart,\n Code,\n Container,\n Heading,\n List,\n Slider,\n Stepper,\n Text,\n TextField,\n});\n\n/**\n * The full default registry (Button, Card, Chart, Code, Container,\n * Heading, List, Slider, Stepper, Text, TextField). Frozen. Use\n * `createRegistry(overrides)` to extend.\n */\nexport const componentMap: ComponentMap = DEFAULT_REGISTRY;\n\n/**\n * Build a new registry by merging the defaults with caller-supplied\n * overrides. The returned map is frozen (Object.freeze) to prevent\n * post-hoc mutation that would surprise concurrent renders.\n *\n * **Lowercase HTML tags are NOT in this map.** The renderer handles\n * `div`, `span`, etc. as React intrinsic elements separately. Calling\n * `createRegistry({ div: StubDiv })` will NOT override the renderer\n * for `<div>` (the renderer uses the lowercase lookup path first).\n *\n * @example\n * ```ts\n * const map = createRegistry({\n * MathExpression: MathExpressionComponent,\n * LatexBlock: LatexBlockComponent,\n * });\n * ```\n */\nexport function createRegistry(overrides: Partial<ComponentMap>): ComponentMap {\n // Merge: defaults are the base; overrides win. Lowercase keys are\n // filtered out because they refer to React intrinsic HTML tags\n // (div, span, etc.) that renderBody handles intrinsically — the\n // caller cannot override intrinsic rendering via the registry.\n // Object.freeze on the result prevents consumers from accidentally\n // mutating the global componentMap by mutating their copy.\n const merged: ComponentMap = { ...DEFAULT_REGISTRY };\n for (const key of Object.keys(overrides) as (keyof ComponentMap)[]) {\n const value = overrides[key];\n if (value === undefined) continue;\n if (key[0] !== key[0]!.toUpperCase()) continue; // skip lowercase\n merged[key] = value;\n }\n return Object.freeze(merged);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["internal"],"sources":["../src/theme.ts","../src/Button.tsx","../src/TextField.tsx","../src/state.tsx","../src/Slider.tsx","../src/Chart.tsx","../src/Container.tsx","../src/Code.tsx","../src/Heading.tsx","../src/Text.tsx","../src/Stepper.tsx","../src/Card.tsx","../src/List.tsx","../src/stateHelpers.ts","../src/ShowSource.tsx","../src/ArtifactErrorBoundary.tsx","../src/createRegistry.ts"],"sourcesContent":["/**\n * Shared theme tokens for @playgenx/components.\n *\n * All styling is driven from this file. There is intentionally no\n * \"config\" theme or runtime theme provider — these are the default\n * implementations of the registry, and a downstream host app can swap\n * them out entirely. Tokens live as constants so they're stable\n * across renders and don't allocate per call.\n */\n\nexport const colors = {\n bg: '#ffffff',\n fg: '#0f172a',\n mutedFg: '#475569',\n border: '#e2e8f0',\n primary: '#2563eb',\n primaryFg: '#ffffff',\n secondaryFg: '#0f172a',\n ghostFg: '#1d4ed8',\n cardBg: '#f8fafc',\n cardBorder: '#e2e8f0',\n codeBg: '#f1f5f9',\n codeFg: '#0f172a',\n sliderTrack: '#cbd5e1',\n sliderFill: '#2563eb',\n sliderThumb: '#1d4ed8',\n bar: '#2563eb',\n barLabel: '#0f172a',\n pie: ['#2563eb', '#16a34a', '#f59e0b', '#dc2626'],\n line: '#2563eb',\n stepperPast: '#16a34a',\n stepperCurrent: '#2563eb',\n stepperFuture: '#cbd5e1',\n} as const;\n\nexport const space = {\n none: '0',\n xs: '4px',\n sm: '8px',\n md: '12px',\n lg: '16px',\n xl: '24px',\n} as const;\n\nexport const radius = {\n sm: '4px',\n md: '6px',\n lg: '8px',\n} as const;\n\n/**\n * Common React-friendly prop accepts for content. Components that\n * accept children of arbitrary shape (Heading, Text, Card, Container,\n * ...) use this so the host can pass strings or JSX interchangeably.\n */\nexport type Children = string | number | React.ReactNode;\n\nimport type * as React from 'react';\n","import * as React from 'react';\nimport { colors, radius } from './theme.js';\n\n/**\n * Headless-style wrapper over `<button>` accepting the props defined in\n * `DEFAULT_COMPONENT_SCHEMAS.Button`. Pass `onClick` through; this\n * implementation does NOT execute arbitrary functions (deterministic\n * renderer constraint) so the click handler is rendered inert at mount.\n */\nexport interface ButtonProps {\n /** Visible text. Required. */\n label: string;\n /** Variant. Defaults to 'primary'. */\n variant?: 'primary' | 'secondary' | 'ghost';\n /** Disabled state. Defaults to false. */\n disabled?: boolean;\n /**\n * Optional click handler. The default impl forwards to onClick if\n * present; in a deterministic renderer pass `noEvents` instead.\n */\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n}\n\nexport function Button({\n label,\n variant = 'primary',\n disabled = false,\n onClick,\n}: ButtonProps): React.JSX.Element {\n const styles: React.CSSProperties = {\n fontFamily: 'inherit',\n fontSize: '14px',\n fontWeight: 500,\n padding: '8px 14px',\n borderRadius: radius.md,\n border: '1px solid transparent',\n cursor: disabled ? 'not-allowed' : 'pointer',\n opacity: disabled ? 0.5 : 1,\n background:\n variant === 'primary'\n ? colors.primary\n : variant === 'secondary'\n ? colors.secondaryFg\n : 'transparent',\n color:\n variant === 'ghost' ? colors.ghostFg : variant === 'secondary' ? colors.bg : colors.primaryFg,\n borderColor: variant === 'ghost' ? colors.border : 'transparent',\n };\n return (\n <button type=\"button\" disabled={disabled} onClick={onClick} style={styles} data-pgx=\"Button\">\n {label}\n </button>\n );\n}\n","import * as React from 'react';\nimport { colors, radius } from './theme.js';\n\nexport interface TextFieldProps {\n label?: string;\n value?: string;\n placeholder?: string;\n disabled?: boolean;\n /**\n * onChange is reported as `node` in the schema (it must be a\n * function reference, which the validator cannot statically type).\n * We forward to React's controlled input handler.\n */\n onChange?: React.ChangeEventHandler<HTMLInputElement>;\n}\n\n/**\n * Controlled text input. Without `value`/`onChange`, behaves as\n * uncontrolled (uses its own state via defaultValue). When a stable\n * (deterministic) caller wires both, the input is fully controlled.\n */\nexport function TextField({\n label,\n value,\n placeholder,\n disabled = false,\n onChange,\n}: TextFieldProps): React.JSX.Element {\n const wrapperStyle: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: '4px',\n fontFamily: 'inherit',\n };\n const labelStyle: React.CSSProperties = {\n fontSize: '12px',\n fontWeight: 500,\n color: colors.mutedFg,\n };\n const inputStyle: React.CSSProperties = {\n fontFamily: 'inherit',\n fontSize: '14px',\n padding: '8px 10px',\n border: `1px solid ${colors.border}`,\n borderRadius: radius.md,\n background: colors.bg,\n color: colors.fg,\n outline: 'none',\n };\n const inputProps: React.InputHTMLAttributes<HTMLInputElement> = {\n placeholder,\n disabled,\n onChange,\n };\n // Use uncontrolled defaultValue when caller omits value. We can't\n // set defaultValue AND value without React warning, so branch.\n if (value !== undefined) inputProps.value = value;\n else inputProps.defaultValue = '';\n return (\n <label style={wrapperStyle} data-pgx=\"TextField\">\n {label !== undefined ? <span style={labelStyle}>{label}</span> : null}\n <input {...inputProps} style={inputStyle} />\n </label>\n );\n}\n","/**\n * Per-render interactive state store. Used by PlaygroundStateProvider to\n * back interactive components (Slider, TextField, Button, Chart) without\n * requiring them to be fully controlled.\n *\n * Design notes:\n * - The store is a singleton instance per Provider, NOT React context.\n * Components consume it via usePlaygroundState() inside React renders,\n * and via .subscribe() / .get() / .set() from non-React callers.\n * - `set` is synchronous; subscribers fire in registration order on the\n * next microtask (so multiple sets in a single tick produce a single\n * re-render per subscriber).\n * - `set` with `===` equal value is a no-op (no subscriber fires).\n * - Throws if used outside a PlaygroundStateProvider (when required by\n * the caller's contract).\n *\n * @packageDocumentation\n */\n\nimport * as React from 'react';\n\n/**\n * Per-render state store. Components read/write values keyed by string.\n * One store per Provider instance; nested Providers create nested scopes.\n */\nexport interface PlaygroundState {\n /** Read a value by key. Returns undefined if unset. */\n get<T = unknown>(key: string): T | undefined;\n /**\n * Set a value. Triggers re-render of subscribers. No-op if the new\n * value is `===` to the current value.\n */\n set<T = unknown>(key: string, value: T): void;\n /**\n * Subscribe to changes on a single key. The callback fires on every\n * `set(key, ...)` with a different value. Returns an unsubscribe\n * function. Non-React callers can use this directly.\n */\n subscribe(key: string, cb: (value: unknown) => void): () => void;\n /**\n * Bulk-read: returns a shallow snapshot of all keys. Useful for\n * snapshotting state at a moment in time (e.g., for undo).\n */\n snapshot(): Readonly<Record<string, unknown>>;\n /** Bulk-write: replace all values. Subscribers fire for changed keys. */\n replaceAll(values: Record<string, unknown>): void;\n}\n\ninterface InternalState extends PlaygroundState {\n /** Test seam: forces sync-fire for unit tests. */\n _flush(): void;\n /**\n * Test seam: run `fn` with batch dedup enabled. While inside the\n * batch, repeated `set(key, ...)` calls collapse to a single\n * subscriber fire per key with the LAST value set. Returns\n * whatever `fn` returns.\n *\n * This is the same primitive as the `batch(state, fn)` helper in\n * stateHelpers.ts but exposed as a method for callers that already\n * hold a state ref.\n */\n _batch<T>(fn: () => T): T;\n /**\n * Internal seam: drop every subscriber from every key. Used by\n * PlaygroundStateProvider's unmount cleanup to prevent HMR\n * subscriber leaks. Not part of the public PlaygroundState\n * surface — do not call from app code (you have `subscribe()`'s\n * returned unsub function for the public path).\n */\n _clearAllSubscribers(): void;\n}\n\n/**\n * Create a fresh state store. Public so PlaygroundStateProvider can\n * construct one, but consumers should use Provider/usePlaygroundState\n * to access stores (not direct construction).\n *\n * The optional type parameter is the \"shape\" of values you intend to\n * store. It's purely a documentation convenience — the store does\n * not enforce it (so you can store heterogeneous values for\n * playgrounds that mix kinds), but `get<T>()` and `set<T>()` will\n * infer correctly when you pass it here.\n */\nexport function createPlaygroundState<T = unknown>(\n initial: Record<string, T> = {},\n): PlaygroundState {\n const values = new Map<string, unknown>(Object.entries(initial));\n // key -> Set of subscribers. We use Set for O(1) add/remove and\n // dedup of identical subscribers.\n const subs = new Map<string, Set<(value: unknown) => void>>();\n\n // Pending (key, value) pairs to fire on the next microtask. We\n // batch so a sequence of `set()` calls in the same tick produces a\n // single subscriber fire per affected key.\n let pending: Array<{ key: string; value: unknown }> = [];\n let scheduled = false;\n // Transactional batching: when `batchDepth > 0`, set() pushes to\n // `batchedPending` instead of `pending`. flush() flushes the\n // NON-batched pending queue (preserving history) but the batched\n // queue is collapsed: only the LAST value per key is kept. This\n // is what `batch(state, fn)` enables — atomic, deduplicated commits.\n let batchDepth = 0;\n let batchedPending: Array<{ key: string; value: unknown }> = [];\n\n const flush = (): void => {\n if (pending.length === 0) return;\n const batch = pending;\n pending = [];\n scheduled = false;\n // Fire every pending (key, value) pair, in order. We do NOT\n // dedupe — each `set()` call is a distinct event and subscribers\n // want to see the full history (e.g. for analytics). React\n // batches re-renders via the microtask flush anyway, so multiple\n // fires for the same key collapse into one render.\n for (const { key, value } of batch) {\n const list = subs.get(key);\n if (list) {\n // Snapshot the subscriber set so callbacks that unsubscribe\n // themselves mid-fire don't mutate the iteration source.\n for (const cb of [...list]) {\n try {\n cb(value);\n } catch (err) {\n // Swallow subscriber errors so one bad subscriber doesn't\n // take down the whole tree. In development we re-raise\n // the error to console.error so the bad subscriber is\n // diagnosable from the browser devtools; in production\n // we stay silent to avoid log spam.\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n '[playgenx] subscriber for key %s threw:',\n key,\n err,\n );\n }\n }\n }\n }\n }\n };\n\n const schedule = (): void => {\n if (scheduled) return;\n scheduled = true;\n // queueMicrotask is available in Node 22+ and all modern browsers.\n // Falls back to Promise.resolve().then for older runtimes.\n if (typeof queueMicrotask === 'function') {\n queueMicrotask(flush);\n } else {\n Promise.resolve().then(flush);\n }\n };\n\n const state: InternalState = {\n get<T = unknown>(key: string): T | undefined {\n return values.get(key) as T | undefined;\n },\n set<T = unknown>(key: string, value: T): void {\n if (Object.is(values.get(key), value)) return; // no-op\n values.set(key, value);\n // Inside a batch: push to the dedupe queue instead of pending.\n // Outside a batch: push to pending (full-history mode).\n if (batchDepth > 0) {\n batchedPending.push({ key, value });\n } else {\n pending.push({ key, value });\n }\n schedule();\n },\n subscribe(key: string, cb: (value: unknown) => void): () => void {\n let list = subs.get(key);\n if (!list) {\n list = new Set();\n subs.set(key, list);\n }\n list.add(cb);\n return () => {\n const l = subs.get(key);\n if (l) {\n l.delete(cb);\n if (l.size === 0) subs.delete(key);\n }\n };\n },\n snapshot(): Readonly<Record<string, unknown>> {\n return Object.fromEntries(values);\n },\n replaceAll(values: Record<string, unknown>): void {\n for (const [k, v] of Object.entries(values)) {\n state.set(k, v);\n }\n },\n _flush(): void {\n flush();\n },\n _batch<T>(fn: () => T): T {\n batchDepth++;\n try {\n return fn();\n } finally {\n batchDepth--;\n // After the batch: if anything landed in batchedPending,\n // move it to pending AS A SINGLE EVENT PER KEY. Walk in\n // reverse so the LAST set wins (later writes to the same\n // key override earlier ones).\n if (batchDepth === 0 && batchedPending.length > 0) {\n const lastPerKey = new Map<string, unknown>();\n for (let i = batchedPending.length - 1; i >= 0; i--) {\n const { key, value } = batchedPending[i]!;\n if (!lastPerKey.has(key)) {\n lastPerKey.set(key, value);\n pending.push({ key, value });\n }\n }\n batchedPending = [];\n }\n }\n },\n _clearAllSubscribers(): void {\n // Replace with fresh maps; old subscribers are dropped on the\n // GC pass once their closure references go out of scope. This\n // is intentionally a hard reset — used only on Provider\n // unmount, where every subscriber in the tree is going away.\n subs.clear();\n // Also drop any pending fires that would have been delivered\n // to those now-dead subscribers. Without this, a stale set\n // queued just before unmount would still fire `cb(value)` on\n // a callback that has no consumer.\n pending = [];\n batchedPending = [];\n scheduled = false;\n },\n };\n return state;\n}\n\ninterface ProviderContextValue {\n state: PlaygroundState;\n}\n\n/**\n * React context. Exposed as a type only — consumers should use\n * PlaygroundStateProvider and usePlaygroundState rather than reading\n * the context directly.\n */\nconst PlaygroundStateContext = React.createContext<ProviderContextValue | null>(\n null,\n);\n\nexport interface PlaygroundStateProviderProps {\n /** Initial state values keyed by name. */\n initial?: Record<string, unknown>;\n /**\n * Optional pre-existing state. If supplied, `initial` is merged on\n * top (initial wins for overlapping keys). Use this for SSR\n * rehydration or seeded values.\n */\n seed?: Record<string, unknown>;\n children: React.ReactNode;\n}\n\n/**\n * Provide a PlaygroundState scope to descendant components.\n *\n * Each Provider instance has its own store; nested Providers create\n * independent scopes. Components rendered outside any Provider will\n * throw a clear error if they call usePlaygroundState.\n */\nexport function PlaygroundStateProvider(\n props: PlaygroundStateProviderProps,\n): React.JSX.Element {\n // Create the store once per Provider instance. The lazy initializer\n // ensures re-mounting (e.g. HMR) doesn't carry stale state across\n // refreshes.\n const [state] = React.useState(() => {\n const s = createPlaygroundState({ ...props.seed, ...props.initial });\n return s;\n });\n\n // Cleanup on unmount: clear all subscribers so a hot-reload doesn't\n // leak subscriptions across renders. Without this, every HMR cycle\n // would leave the OLD store with stale subscribers that fire on\n // every set — even though no component is listening.\n //\n // Why we don't drop the values Map: by the time this cleanup runs,\n // the closure over the store is unreachable. Letting the GC collect\n // it is enough. Subscribers are the leak risk because they're\n // closures that reference component-local state (setValue, force).\n React.useEffect(() => {\n return () => {\n // Clear all subscribers via the test seam. (The store doesn't\n // expose a public clear-all API because removing subscribers\n // without notifying them is almost always a bug. Unmount is\n // the one legitimate case.)\n (state as unknown as { _clearAllSubscribers?: () => void })\n ._clearAllSubscribers?.();\n };\n }, [state]);\n\n const value = React.useMemo(() => ({ state }), [state]);\n\n return React.createElement(\n PlaygroundStateContext.Provider,\n { value },\n props.children,\n );\n}\n\n/**\n * Hook to access the nearest PlaygroundState store. Throws a clear\n * error if called outside a Provider so consumers get a usable\n * diagnostic instead of a `Cannot read properties of undefined`.\n *\n * Note for @playgenx/renderer users: the renderer's `renderBody`\n * does NOT yet auto-wrap with a PlaygroundStateProvider. You'll\n * need to wrap your own tree with `<PlaygroundStateProvider>` (or\n * use the lower-level `renderNodes` API) before state-bound\n * components can resolve. The `withState` option on `renderBody`\n * is planned for a future renderer release (tracked in v0.6.0).\n */\nexport function usePlaygroundState(): PlaygroundState {\n const ctx = React.useContext(PlaygroundStateContext);\n if (ctx === null) {\n throw new Error(\n 'usePlaygroundState() must be called inside a <PlaygroundStateProvider>. ' +\n 'Wrap your component tree with the Provider. (A future renderer ' +\n 'release will auto-wrap; for now, do it explicitly.)',\n );\n }\n return ctx.state;\n}\n\n/**\n * Optional helper for binding a component prop to a state key. Returns\n * the live value (re-renders subscribers on change) and a setter. Pass\n * `undefined` for `key` to disable the binding — the component behaves\n * as if it had no stateKey, and `set` becomes a no-op. Safe to call\n * outside a Provider.\n *\n * Implementation note: this hook ALWAYS calls the same number of\n * React hooks regardless of whether `key` is set or a Provider is in\n * scope. The \"no binding\" / \"no Provider\" branch returns a static\n * tuple so the hook count stays aligned across renders. That means a\n * component can flip `stateKey` from defined to undefined (or move in\n * or out of a Provider) without crashing with a hooks-order error.\n *\n * The hook uses React 19's `useSyncExternalStore` so SSR renders the\n * Provider's current value synchronously — no \"value=fallback on\n * server, value=real after hydration\" surprise. The hook count drops\n * from 5 (useContext + useState + useState + useEffect + useCallback)\n * to 3 (useContext + useSyncExternalStore + useCallback) as a side\n * benefit.\n *\n * Components that accept a `stateKey` prop should call this internally\n * and use the returned tuple as a drop-in for the underlying\n * value/onChange pair. The return type widens to `readonly [...] | undefined`\n * so the caller can still branch on `if (bound)` — but they don't have\n * to (the tuple is always safe to index).\n *\n * @example\n * const bound = useBoundValueOrUndefined<number>(stateKey, 50);\n * // ... onChange={(e) => bound?.[1](Number(e.currentTarget.value))}\n */\nexport function useBoundValueOrUndefined<T>(\n key: string | undefined,\n fallback: T,\n): readonly [T, (next: T) => void] | undefined {\n // Hook 1: read the Provider context. Always called (no conditional).\n const ctx = React.useContext(PlaygroundStateContext);\n\n // Read the current value via useSyncExternalStore. This is the\n // SSR-safe primitive: it runs synchronously during renderToString\n // (via getServerSnapshot) AND during hydration (via getSnapshot),\n // guaranteeing server and client agree on the first render value.\n //\n // We pin the snapshot function identity with useCallback so React's\n // bail-out logic can short-circuit when the store hasn't changed.\n // The snapshot closes over `ctx`, `key`, and `fallback`; if any of\n // those change between renders, React re-reads the value. (Stable\n // identity also means we don't re-subscribe on every render.)\n const bound = key !== undefined && ctx !== null;\n const value = React.useSyncExternalStore(\n // subscribe: register a callback that triggers a re-render.\n // React calls this once on mount; the returned function is the\n // unsubscribe. If `bound` is false, return a no-op unsub.\n React.useCallback(\n (onStoreChange: () => void) => {\n if (!bound || key === undefined || ctx === null) return () => {};\n return ctx.state.subscribe(key, onStoreChange);\n },\n [bound, ctx, key],\n ),\n // getSnapshot: client-side read. Must match getServerSnapshot\n // for hydration to succeed without warnings.\n React.useCallback((): T => {\n if (!bound || key === undefined || ctx === null) return fallback;\n return (ctx.state.get<T>(key) ?? fallback) as T;\n }, [bound, ctx, key, fallback]),\n // getServerSnapshot: SSR read. Same as getSnapshot because the\n // store is the same on server and client (same Provider seed).\n React.useCallback((): T => {\n if (!bound || key === undefined || ctx === null) return fallback;\n return (ctx.state.get<T>(key) ?? fallback) as T;\n }, [bound, ctx, key, fallback]),\n );\n\n // Hook 3: the setter. No-op when unbound.\n const set = React.useCallback(\n (next: T) => {\n if (!bound || key === undefined || ctx === null) return;\n ctx.state.set<T>(key, next);\n },\n [bound, ctx, key],\n );\n // When no binding is active, callers get an explicit `undefined`\n // tuple so they can branch on `if (bound)` without falsey\n // confusion (a `0` or `''` value would otherwise hide the binding\n // state).\n if (!bound) return undefined;\n return [value, set] as const;\n}\n\nexport function useBoundValue<T>(\n key: string,\n fallback: T,\n): readonly [T, (next: T) => void] {\n const bound = useBoundValueOrUndefined<T>(key, fallback);\n if (bound === undefined) {\n throw new Error(\n 'useBoundValue(key, ...) requires both a non-undefined `key` and a ' +\n '<PlaygroundStateProvider> ancestor. Use useBoundValueOrUndefined ' +\n 'if you want optional binding.',\n );\n }\n return bound;\n}\n\n/**\n * Non-subscribing accessor for the store. Returns a stable tuple of\n * imperative API methods (`get`, `set`, `subscribe`, `snapshot`,\n * `replaceAll`) plus the raw `state` ref — none of which trigger a\n * re-render when used.\n *\n * When to use this instead of `useBoundValue`:\n *\n * - You need to read state inside an event handler (no render needed).\n * - You need to set state from many different handlers, not one bound prop.\n * - You're implementing a custom hook that wraps the store.\n * - You want to subscribe imperatively (for analytics, side-effects).\n *\n * When NOT to use this: if you need the component to RE-RENDER on\n * state changes, use `useBoundValue` (which subscribes for you).\n *\n * @example\n * function LogoutButton() {\n * const { get, set } = useStoreSnapshot();\n * return (\n * <button onClick={() => {\n * if (get<boolean>('confirmLogout')) set('confirmLogout', false);\n * doLogout();\n * }}>Log out</button>\n * );\n * }\n */\nexport function useStoreSnapshot(): {\n readonly state: PlaygroundState;\n readonly get: <T = unknown>(key: string) => T | undefined;\n readonly set: <T = unknown>(key: string, value: T) => void;\n readonly subscribe: (key: string, cb: (value: unknown) => void) => () => void;\n readonly snapshot: () => Readonly<Record<string, unknown>>;\n readonly replaceAll: (values: Record<string, unknown>) => void;\n} {\n const ctx = React.useContext(PlaygroundStateContext);\n if (ctx === null) {\n throw new Error(\n 'useStoreSnapshot() must be called inside a <PlaygroundStateProvider>.',\n );\n }\n // Stable references — the store itself never changes for the\n // lifetime of the Provider, so we can hand back the methods\n // directly. (No useCallback wrapper needed; the methods are bound\n // to the same store instance for the Provider's lifetime.)\n return {\n state: ctx.state,\n get: ctx.state.get,\n set: ctx.state.set,\n subscribe: ctx.state.subscribe,\n snapshot: ctx.state.snapshot,\n replaceAll: ctx.state.replaceAll,\n };\n}\n\n/**\n * Bind a state key with a selector transform. Returns the SELECTED\n * value, not the raw value. The component only re-renders when the\n * selected slice changes — not when OTHER fields on the same key\n * change.\n *\n * Useful for normalized state where one key holds an object and you\n * only care about one property:\n *\n * @example\n * interface User { name: string; age: number; email: string; }\n * const userName = useBoundSelector<User, string>(\n * 'currentUser',\n * (u) => u?.name ?? '(anonymous)',\n * 'guest'\n * );\n * // Setting `currentUser.age` does NOT re-render this component.\n * // Setting `currentUser.name` DOES re-render this component.\n *\n * The setter writes the SELECTED value as-is to the store. For\n * partial-merge semantics (e.g., \"set only the name, keep other\n * fields\"), use `useStoreSnapshot()` directly and merge yourself.\n *\n * If the selector throws, the error propagates and the component\n * unmounts via the nearest error boundary. Wrap your selector in\n * a try/catch if you need resilience.\n */\nexport function useBoundSelector<T, U>(\n key: string,\n selector: (raw: T | undefined) => U,\n fallback: U,\n): readonly [U, (next: U) => void] {\n // Call useBoundValueOrUndefined once at the top level. The hook\n // count for THIS call is fixed (3 — useContext + useSyncExternalStore\n // + useCallback). We derive the selected value via useMemo on\n // `bound[0]`; we derive the setter via useCallback. Total hooks\n // here: 5 (the 3 from useBoundValueOrUndefined + useMemo +\n // useCallback).\n //\n // We pass `fallback` (typed `U`) as the bound hook's fallback.\n // The bound hook's T == our U here; if the key is unset in the\n // store, we get `fallback` (U). The selector then runs over it.\n // This matches the \"missing-key → fallback\" pattern that selectors\n // already expect.\n const bound = useBoundValueOrUndefined<U>(key, fallback);\n const raw = bound === undefined ? undefined : bound[0];\n // useMemo with [raw] dep: if raw reference is the same (Object.is),\n // return the cached selected value. If the selector transforms to\n // the same primitive, React's bail-out kicks in for free.\n const value = React.useMemo<U>(\n () => selector(raw),\n [raw, selector, fallback],\n );\n // Setter: write the selected value back to the store. We need to\n // be careful not to call useBoundValueOrUndefined again (would\n // duplicate hooks). Instead, capture the setter from the bound\n // tuple — it's stable across renders (useCallback in the hook).\n const set = React.useCallback(\n (next: U) => {\n if (bound === undefined) return;\n // Type assertion: the caller is responsible for the\n // contract that `U` is assignable to `T`.\n bound[1](next as unknown as T);\n },\n [bound],\n );\n // If the underlying binding is undefined (no Provider / unbound),\n // throw — useBoundSelector requires a binding by definition.\n if (bound === undefined) {\n throw new Error(\n 'useBoundSelector requires a non-undefined `key` and a ' +\n '<PlaygroundStateProvider> ancestor.',\n );\n }\n return [value, set] as const;\n}\n\n/**\n * Bind multiple keys at once. Returns a `Record<key, [value, setter]>`\n * for the keys you specify. Each setter writes back to its own key.\n *\n * Useful for forms and dashboards where multiple bound inputs share\n * a single Provider.\n *\n * @example\n * const form = useMultiBoundValue({\n * name: { fallback: '' },\n * age: { fallback: 0 },\n * email: { fallback: '' },\n * });\n * // form.name[0] === current name string\n * // form.age[1](42) writes 42 to state.age\n *\n * The hook subscribes ONCE per key (so 5 keys = 5 subscriptions,\n * not a single batched subscription). If you bind a LOT of keys,\n * consider using `useStoreSnapshot()` instead — it doesn't subscribe.\n *\n * @example\n * // 50-row table, each row needs its own value\n * const { get, set } = useStoreSnapshot();\n * rows.map(row => <input value={get(`cell-${row.id}`) ?? ''}\n * onChange={e => set(`cell-${row.id}`, e.target.value)} />);\n */\nexport function useMultiBoundValue<K extends string>(\n bindings: Record<K, { fallback: unknown }>,\n): { readonly [P in K]: readonly [unknown, (next: unknown) => void] } {\n const ctx = React.useContext(PlaygroundStateContext);\n if (ctx === null) {\n throw new Error(\n 'useMultiBoundValue() must be called inside a <PlaygroundStateProvider>.',\n );\n }\n // We can't call useBoundValueOrUndefined in a loop (Rules of Hooks\n // require a fixed call count per render), so we call it once per\n // declared key. Object.keys order is stable per the spec, so the\n // hook order is stable across renders.\n const result = {} as { [P in K]: readonly [unknown, (next: unknown) => void] };\n // The hook count is `Object.keys(bindings).length` — call each one\n // unconditionally with the same key in the same order.\n const keys = Object.keys(bindings) as K[];\n // For each key, call useBoundValueOrUndefined. We need to compose\n // these into a Record, but useBoundValueOrUndefined itself calls\n // hooks. We can't wrap it in a helper without losing the hook\n // order — instead we just call it directly per key.\n //\n // Practical note: this hook is awkward to use with arbitrary key\n // counts because the hook count depends on the bindings object.\n // For most use cases (5-10 keys) this is fine. If you need 50+\n // keys, use `useStoreSnapshot()` directly.\n for (const k of keys) {\n const b = useBoundValueOrUndefined<unknown>(\n k,\n bindings[k].fallback,\n );\n if (b === undefined) {\n // No Provider — but ctx above says there IS one. Should never\n // happen. Defensive fallback: return [fallback, noop].\n result[k] = [bindings[k].fallback, () => {}] as const;\n } else {\n result[k] = b;\n }\n }\n return result;\n}\n\n/**\n * State-binding action shape. Used by `<Button onClickAction={...}>`.\n * { set: { count: 1 } } -> sets state.count = 1\n * { toggle: 'open' } -> sets state.open = !state.open\n */\nexport type StateAction =\n | { set?: Record<string, unknown>; toggle?: string }\n | undefined;\n\n/**\n * Apply a StateAction to the nearest PlaygroundState. Returns an\n * onClick handler that fires the action. If action is undefined\n * the handler is a no-op.\n */\nexport function useStateAction(\n action: StateAction,\n): (event: unknown) => void {\n const state = usePlaygroundState();\n // The `event` parameter (from onClick / onSubmit) is the React\n // event the caller hands us. We don't introspect it — actions\n // are identified purely by their `action` config. The eslint\n // config's `argsIgnorePattern` doesn't apply to callback params\n // inside `React.useCallback` closures (it only sees the outer\n // function's params), so we use a void cast to silence the lint\n // while keeping the type signature for callers.\n return React.useCallback(\n (event: unknown) => {\n void event; // explicitly unused\n if (action === undefined) return;\n if (action.set) {\n for (const [k, v] of Object.entries(action.set)) {\n state.set(k, v);\n }\n }\n if (action.toggle !== undefined) {\n const key = action.toggle;\n const current = state.get<unknown>(key);\n state.set(key, !current);\n }\n },\n [action, state],\n );\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\nimport { useBoundValueOrUndefined } from './state.jsx';\n\nexport interface SliderProps {\n /** Lower bound, inclusive. Required. */\n min: number;\n /** Upper bound, inclusive. Required. */\n max: number;\n /** Initial / controlled value. */\n value?: number;\n /** Increment between stops. Defaults to 1. */\n step?: number;\n /** Optional caption rendered above the slider. */\n label?: string;\n /**\n * When set, the slider is \"bound\" to the playground state: initial\n * value comes from state.get(stateKey), onChange writes state.set.\n * `value` prop is ignored when stateKey is present.\n */\n stateKey?: string;\n}\n\n/**\n * Range input. Three operating modes:\n *\n * 1. **Uncontrolled** (no `value`, no `stateKey`): the slider owns\n * its value as local React state. The onChange mutates that state.\n * Use this when nothing else needs to observe the value.\n *\n * 2. **Controlled by `value` prop**: the slider is read-only —\n * user input is ignored because there is no `onChange` callback\n * wired by the deterministic renderer. Use this for \"static\"\n * sliders in artifacts. (If you want fully interactive control,\n * use mode 3.)\n *\n * 3. **Bound to PlaygroundState via `stateKey`**: the slider reads\n * its value from the nearest Provider's store (initial: either\n * the store's current value OR `value` prop, whichever is\n * present; the live store wins when bound). On change, the\n * slider writes to the store. Other components can subscribe\n * to the same key.\n *\n * In all modes the displayed value is clamped to `[min, max]`. NaN\n * inputs clamp to `min`. When `min === max` the input is disabled.\n */\nexport function Slider({ min, max, value, step = 1, label, stateKey }: SliderProps): React.JSX.Element {\n // When stateKey is set, bind to playground state. useBoundValue\n // throws if no Provider is in scope; useBoundValueOrUndefined\n // returns undefined when stateKey is undefined so the rest of the\n // component can use the local-state behaviour.\n const [internal, setInternal] = React.useState(() => clamp(value ?? min, min, max));\n const bound = useBoundValueOrUndefined<number>(stateKey, internal);\n const controlled = value !== undefined;\n // When stateKey is bound, the bound value is the source of truth.\n const current = stateKey !== undefined && bound\n ? clamp(bound[0], min, max)\n : controlled\n ? clamp(value, min, max)\n : internal;\n const trackStyle: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: '6px',\n fontFamily: 'inherit',\n width: '100%',\n };\n const labelStyle: React.CSSProperties = {\n fontSize: '12px',\n color: colors.mutedFg,\n };\n const wrapStyle: React.CSSProperties = { display: 'flex', alignItems: 'center', gap: '10px' };\n const inputStyle: React.CSSProperties = { flex: 1, accentColor: colors.sliderFill };\n const valueStyle: React.CSSProperties = {\n minWidth: '32px',\n textAlign: 'right',\n fontVariantNumeric: 'tabular-nums',\n fontSize: '14px',\n color: colors.fg,\n };\n return (\n <div style={trackStyle} data-pgx=\"Slider\">\n {label !== undefined ? <span style={labelStyle}>{label}</span> : null}\n <div style={wrapStyle}>\n <input\n type=\"range\"\n min={min}\n max={max}\n step={step}\n value={current}\n disabled={min === max}\n aria-label={label}\n onChange={(e) => {\n const next = Number(e.currentTarget.value);\n if (stateKey !== undefined && bound) {\n bound[1](next);\n } else if (!controlled) {\n setInternal(next);\n }\n }}\n style={inputStyle}\n />\n <span style={valueStyle}>{current}</span>\n </div>\n </div>\n );\n}\n\nfunction clamp(n: number, lo: number, hi: number): number {\n if (Number.isNaN(n)) return lo;\n return Math.min(hi, Math.max(lo, n));\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport type ChartKind = 'bar' | 'line' | 'pie';\n\n/**\n * The schema lets `data` be any ReactNode, but in practice callers\n * pass JSON-shaped arrays. We narrow at runtime:\n *\n * - bar: `{ labels: string[], values: number[] }`\n * - line: `{ x: number[], y: number[] }` or `{ series: { label, points: {x,y} }[] }`\n * - pie: `{ labels: string[], values: number[] }`\n *\n * Anything that doesn't fit is rendered as a `<pre>` fallback.\n */\nexport interface ChartProps {\n kind: ChartKind;\n data: unknown;\n title?: string;\n}\n\nexport function Chart({ kind, data, title }: ChartProps): React.JSX.Element {\n return (\n <div data-pgx=\"Chart\" data-chart-kind={kind} style={{ fontFamily: 'inherit' }}>\n {title !== undefined ? (\n <div style={{ fontSize: '14px', fontWeight: 600, color: colors.fg, marginBottom: '6px' }}>\n {title}\n </div>\n ) : null}\n {renderChart(kind, data)}\n </div>\n );\n}\n\nfunction renderChart(kind: ChartKind, data: unknown): React.JSX.Element {\n if (kind === 'bar') return renderBar(data);\n if (kind === 'line') return renderLine(data);\n return renderPie(data);\n}\n\nfunction renderBar(data: unknown): React.JSX.Element {\n const parsed = parseLabeled(data);\n if (!parsed) return fallback('bar chart', data);\n const { labels, values } = parsed;\n const max = Math.max(1, ...values);\n const width = 320;\n const height = 160;\n const barWidth = width / Math.max(1, labels.length);\n return (\n <svg\n width={width}\n height={height}\n role=\"img\"\n aria-label=\"Bar chart\"\n style={{ display: 'block' }}\n >\n {values.map((v, i) => {\n const h = (Math.max(0, v) / max) * (height - 24);\n return (\n <g key={i}>\n <rect\n x={i * barWidth + 2}\n y={height - 12 - h}\n width={Math.max(1, barWidth - 4)}\n height={h}\n fill={colors.bar}\n rx={2}\n />\n <text\n x={i * barWidth + barWidth / 2}\n y={height - 2}\n fontSize={10}\n textAnchor=\"middle\"\n fill={colors.barLabel}\n >\n {(labels[i] ?? '').slice(0, 8)}\n </text>\n </g>\n );\n })}\n </svg>\n );\n}\n\nfunction renderLine(data: unknown): React.JSX.Element {\n // Two accepted shapes; pick whichever parses cleanly.\n let points: Array<{ x: number; y: number }> = [];\n if (data && typeof data === 'object' && 'series' in (data as Record<string, unknown>)) {\n const series = (data as { series: Array<{ points?: Array<{ x: number; y: number }> }> }).series;\n const first = series?.[0];\n points = Array.isArray(first?.points) ? first.points : [];\n } else if (\n data &&\n typeof data === 'object' &&\n Array.isArray((data as { x?: unknown }).x) &&\n Array.isArray((data as { y?: unknown }).y)\n ) {\n const xArr = (data as { x: number[] }).x;\n const yArr = (data as { y: number[] }).y;\n points = xArr.map((x, i) => ({ x, y: yArr[i] ?? 0 }));\n }\n if (points.length === 0) return fallback('line chart', data);\n const xs = points.map((p) => p.x);\n const ys = points.map((p) => p.y);\n const xMin = Math.min(...xs);\n const xMax = Math.max(...xs);\n const yMin = Math.min(...ys);\n const yMax = Math.max(...ys);\n const w = 320;\n const h = 160;\n const sx = (x: number) => (xMax === xMin ? w / 2 : ((x - xMin) / (xMax - xMin)) * (w - 8) + 4);\n const sy = (y: number) =>\n yMax === yMin ? h / 2 : h - ((y - yMin) / (yMax - yMin)) * (h - 24) - 12;\n const path = points\n .map((p, i) => `${i === 0 ? 'M' : 'L'}${sx(p.x).toFixed(2)},${sy(p.y).toFixed(2)}`)\n .join(' ');\n return (\n <svg width={w} height={h} role=\"img\" aria-label=\"Line chart\" style={{ display: 'block' }}>\n <path d={path} fill=\"none\" stroke={colors.line} strokeWidth={2} />\n {points.map((p, i) => (\n <circle key={i} cx={sx(p.x)} cy={sy(p.y)} r={2} fill={colors.line} />\n ))}\n </svg>\n );\n}\n\nfunction renderPie(data: unknown): React.JSX.Element {\n const parsed = parseLabeled(data);\n if (!parsed) return fallback('pie chart', data);\n const total = parsed.values.reduce((a, b) => a + Math.max(0, b), 0) || 1;\n const cx = 80;\n const cy = 80;\n const r = 60;\n let angle = -Math.PI / 2;\n return (\n <svg width={160} height={160} role=\"img\" aria-label=\"Pie chart\" style={{ display: 'block' }}>\n {parsed.values.map((v, i) => {\n const slice = (Math.max(0, v) / total) * Math.PI * 2;\n const x1 = cx + r * Math.cos(angle);\n const y1 = cy + r * Math.sin(angle);\n const x2 = cx + r * Math.cos(angle + slice);\n const y2 = cy + r * Math.sin(angle + slice);\n const large = slice > Math.PI ? 1 : 0;\n const path =\n slice >= Math.PI * 2 - 1e-6\n ? `M ${cx - r} ${cy} A ${r} ${r} 0 1 1 ${cx + r} ${cy} A ${r} ${r} 0 1 1 ${cx - r} ${cy}`\n : `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;\n angle += slice;\n const fill = colors.pie[i % colors.pie.length] ?? colors.bar;\n return <path key={i} d={path} fill={fill} />;\n })}\n </svg>\n );\n}\n\nfunction parseLabeled(data: unknown): { labels: string[]; values: number[] } | null {\n if (!data || typeof data !== 'object') return null;\n const labels = (data as { labels?: unknown }).labels;\n const values = (data as { values?: unknown }).values;\n if (!Array.isArray(labels) || !Array.isArray(values)) return null;\n if (labels.length !== values.length) return null;\n if (!labels.every((l) => typeof l === 'string' || typeof l === 'number')) return null;\n if (!values.every((v) => typeof v === 'number' || typeof v === 'string')) return null;\n return {\n labels: labels.map(String),\n values: values.map((v) => (typeof v === 'number' ? v : Number(v))),\n };\n}\n\nfunction fallback(label: string, data: unknown): React.JSX.Element {\n return (\n <pre\n style={{\n margin: 0,\n fontSize: '12px',\n background: colors.codeBg,\n color: colors.codeFg,\n padding: '8px',\n borderRadius: '4px',\n overflow: 'auto',\n }}\n >\n {label} data: {JSON.stringify(data, null, 2)}\n </pre>\n );\n}\n","import * as React from 'react';\n\nexport interface ContainerProps {\n padding?: string;\n gap?: string;\n children?: React.ReactNode;\n}\n\n/**\n * Layout wrapper. Defaults to a vertical flex container with 16px\n * padding and 12px gap when callers omit props.\n */\nexport function Container({ padding, gap, children }: ContainerProps): React.JSX.Element {\n const style: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n padding: padding ?? '16px',\n gap: gap ?? '12px',\n fontFamily: 'inherit',\n color: 'inherit',\n boxSizing: 'border-box',\n };\n return (\n <div style={style} data-pgx=\"Container\">\n {children}\n </div>\n );\n}\n","import * as React from 'react';\nimport { colors, radius } from './theme.js';\n\nexport interface CodeProps {\n language?: string;\n children?: React.ReactNode;\n}\n\n/**\n * Inline code formatter. Renders children as a `<code>` element inside\n * a styled wrapper. If `children` is a single string, it displays\n * verbatim; for multi-line content we fall back to a `<pre>`-styled\n * block.\n */\nexport function Code({ language, children }: CodeProps): React.JSX.Element {\n const isString = typeof children === 'string';\n const isMulti = isString && (children as string).includes('\\n');\n if (isMulti) {\n return (\n <pre\n data-pgx=\"Code\"\n data-language={language ?? 'text'}\n style={{\n background: colors.codeBg,\n color: colors.codeFg,\n padding: '12px',\n borderRadius: radius.md,\n fontSize: '12px',\n overflow: 'auto',\n margin: 0,\n }}\n >\n <code>{children}</code>\n </pre>\n );\n }\n return (\n <code\n data-pgx=\"Code-inline\"\n data-language={language ?? 'text'}\n style={{\n background: colors.codeBg,\n color: colors.codeFg,\n padding: '2px 6px',\n borderRadius: radius.sm,\n fontSize: '0.9em',\n }}\n >\n {children}\n </code>\n );\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport interface HeadingProps {\n /**\n * Heading level (1..6). Out-of-range or undefined falls back to 2.\n * The renderer should never see an `Undefined` value here unless the\n * schema validator said optional props can be omitted.\n */\n level?: number;\n color?: string;\n children?: React.ReactNode;\n}\n\nconst sizes: Record<number, string> = {\n 1: '24px',\n 2: '20px',\n 3: '18px',\n 4: '16px',\n 5: '14px',\n 6: '13px',\n};\n\nexport function Heading({ level, color, children }: HeadingProps): React.JSX.Element {\n const safeLevel = clampLevel(level);\n const size = sizes[safeLevel] ?? sizes[2]!;\n const style: React.CSSProperties = {\n fontFamily: 'inherit',\n fontSize: size,\n fontWeight: 700,\n color: color ?? colors.fg,\n margin: 0,\n lineHeight: 1.3,\n };\n // Render an element corresponding to the level. React enforces\n // TagName = 'h1' | 'h2' ... via JSX, so we switch dynamically via\n // createElement.\n return React.createElement(`h${safeLevel}`, { style, 'data-pgx': `Heading` }, children);\n}\n\nfunction clampLevel(level: number | undefined): 1 | 2 | 3 | 4 | 5 | 6 {\n if (typeof level !== 'number' || !Number.isFinite(level)) return 2;\n const i = Math.floor(level);\n if (i < 1) return 1;\n if (i > 6) return 6;\n return i as 1 | 2 | 3 | 4 | 5 | 6;\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport interface TextProps {\n weight?: 'normal' | 'bold';\n size?: string;\n color?: string;\n children?: React.ReactNode;\n}\n\nexport function Text({ weight = 'normal', size, color, children }: TextProps): React.JSX.Element {\n const style: React.CSSProperties = {\n fontFamily: 'inherit',\n fontSize: size ?? '14px',\n fontWeight: weight === 'bold' ? 600 : 400,\n color: color ?? colors.fg,\n margin: 0,\n lineHeight: 1.5,\n };\n return (\n <p style={style} data-pgx=\"Text\">\n {children}\n </p>\n );\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport interface Step {\n id: string;\n title: string;\n body?: React.ReactNode;\n}\n\nexport interface StepperProps {\n steps: Step[];\n /** 0-indexed initial step. Out-of-range falls back to 0. */\n initial?: number;\n}\n\n/**\n * Multi-step reveal. State: the active index. Previous steps can be\n * revisited; future steps are disabled until reached.\n */\nexport function Stepper({ steps, initial }: StepperProps): React.JSX.Element {\n const initialIdx = clampIndex(initial ?? 0, steps.length);\n const [active, setActive] = React.useState(initialIdx);\n const wrapStyle: React.CSSProperties = {\n fontFamily: 'inherit',\n display: 'flex',\n flexDirection: 'column',\n gap: '12px',\n };\n const listStyle: React.CSSProperties = {\n display: 'flex',\n gap: '6px',\n listStyle: 'none',\n padding: 0,\n margin: 0,\n };\n const stepBtnBase: React.CSSProperties = {\n width: '28px',\n height: '28px',\n borderRadius: '50%',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontFamily: 'inherit',\n fontSize: '12px',\n fontWeight: 600,\n border: '1px solid transparent',\n };\n const bodyStyle: React.CSSProperties = {\n border: `1px solid ${colors.border}`,\n borderRadius: '6px',\n padding: '10px',\n background: colors.bg,\n color: colors.fg,\n };\n return (\n <div data-pgx=\"Stepper\" style={wrapStyle}>\n <ol style={listStyle}>\n {steps.map((s, i) => {\n const isPast = i < active;\n const isCurrent = i === active;\n const future = i > active;\n const stepStyle: React.CSSProperties = {\n ...stepBtnBase,\n background: isCurrent\n ? colors.stepperCurrent\n : isPast\n ? colors.stepperPast\n : colors.stepperFuture,\n color: isCurrent || isPast ? '#fff' : colors.fg,\n opacity: future ? 0.6 : 1,\n cursor: future ? 'not-allowed' : 'pointer',\n };\n return (\n <li key={s.id}>\n <button\n type=\"button\"\n aria-label={`Step ${i + 1}: ${s.title}`}\n aria-current={isCurrent ? 'step' : undefined}\n disabled={future}\n onClick={() => setActive(i)}\n style={stepStyle}\n >\n {i + 1}\n </button>\n </li>\n );\n })}\n </ol>\n <div style={bodyStyle}>\n <div style={{ fontWeight: 600, marginBottom: '4px', fontSize: '14px' }}>\n {steps[active]?.title ?? ''}\n </div>\n <div style={{ fontSize: '13px' }}>{steps[active]?.body ?? null}</div>\n </div>\n </div>\n );\n}\n\nfunction clampIndex(n: number, len: number): number {\n if (len === 0) return 0;\n if (!Number.isFinite(n)) return 0;\n return Math.min(len - 1, Math.max(0, Math.floor(n)));\n}\n","import * as React from 'react';\nimport { colors, radius, space } from './theme.js';\n\nexport interface CardProps {\n title?: string;\n /**\n * Visual elevation. 0 = flat (border only), 1+ = heavier shadow.\n * Defaults to 1.\n */\n elevation?: number;\n children?: React.ReactNode;\n}\n\nexport function Card({ title, elevation = 1, children }: CardProps): React.JSX.Element {\n const shadow =\n elevation >= 3\n ? '0 4px 12px rgba(15, 23, 42, 0.18)'\n : elevation === 2\n ? '0 2px 8px rgba(15, 23, 42, 0.12)'\n : elevation === 1\n ? '0 1px 2px rgba(15, 23, 42, 0.06)'\n : 'none';\n const cardStyle: React.CSSProperties = {\n background: colors.cardBg,\n border: `1px solid ${colors.cardBorder}`,\n borderRadius: radius.md,\n boxShadow: shadow,\n padding: space.md,\n fontFamily: 'inherit',\n color: colors.fg,\n display: 'flex',\n flexDirection: 'column',\n gap: space.sm,\n };\n const titleStyle: React.CSSProperties = {\n fontSize: '14px',\n fontWeight: 600,\n margin: 0,\n };\n return (\n <section data-pgx=\"Card\" style={cardStyle}>\n {title !== undefined ? <h3 style={titleStyle}>{title}</h3> : null}\n {children}\n </section>\n );\n}\n","import * as React from 'react';\nimport { colors } from './theme.js';\n\nexport interface ListProps {\n /**\n * Items to render. Schema marks this as `node`; in practice callers\n * pass arrays of strings/numbers/ReactNodes. Anything else is\n * rendered as a single bullet containing the value.\n */\n items?: unknown;\n /** When true, renders as `<ol>` instead of `<ul>`. */\n ordered?: boolean;\n /** Override the wrapper element for full control. */\n children?: React.ReactNode;\n}\n\n/**\n * List of items. Renders `items` as a list of strings/numbers (the\n * common artifact prompt output). If `children` are passed they take\n * precedence over `items`.\n */\nexport function List({ items, ordered, children }: ListProps): React.JSX.Element {\n const style: React.CSSProperties = {\n margin: 0,\n paddingLeft: '20px',\n fontFamily: 'inherit',\n color: colors.fg,\n fontSize: '14px',\n lineHeight: 1.6,\n };\n if (children !== undefined && children !== null) {\n return React.createElement(ordered ? 'ol' : 'ul', { style, 'data-pgx': 'List' }, children);\n }\n const arr = Array.isArray(items) ? items : items === undefined ? [] : [items];\n return React.createElement(\n ordered ? 'ol' : 'ul',\n { style, 'data-pgx': 'List' },\n arr.map((item, i) => {\n const node: React.ReactNode =\n typeof item === 'string' || typeof item === 'number' ? String(item) : JSON.stringify(item);\n return <li key={i}>{node}</li>;\n }),\n );\n}\n","/**\n * Helper functions for working with {@link PlaygroundState}.\n *\n * These are pure operations over a `PlaygroundState` — no React, no\n * hooks, no I/O. They live alongside the store so that:\n * - Devtools / CLI scripts can operate on a state without React\n * (the bin/state-cli.ts script uses these directly).\n * - Test code can drive a store through a thin façade.\n * - Server-side renderers can dump, validate, and diff snapshots\n * without needing a DOM.\n *\n * @packageDocumentation\n */\n\nimport type { PlaygroundState } from './state.jsx';\n\n/**\n * Structured diff between two state snapshots. Pure function — does\n * not touch the stores. Returns `null` when the snapshots are\n * shallowly equal (no additions, no changes, no removals).\n *\n * @example\n * const a = { x: 1, y: 2 };\n * const b = { x: 1, y: 3, z: 4 };\n * diffSnapshots(a, b); // { added: ['z'], changed: ['y'], removed: [] }\n */\nexport interface StateDiff {\n /** Keys present in `next` but absent from `prev`. */\n added: string[];\n /** Keys whose value changed (using `Object.is` to compare). */\n changed: string[];\n /** Keys present in `prev` but absent from `next`. */\n removed: string[];\n}\n\nexport function diffSnapshots(\n prev: Readonly<Record<string, unknown>>,\n next: Readonly<Record<string, unknown>>,\n): StateDiff | null {\n const added: string[] = [];\n const changed: string[] = [];\n const removed: string[] = [];\n const prevKeys = Object.keys(prev);\n const nextKeys = Object.keys(next);\n const prevSet = new Set(prevKeys);\n const nextSet = new Set(nextKeys);\n for (const k of nextKeys) {\n if (!prevSet.has(k)) {\n added.push(k);\n continue;\n }\n if (!Object.is(prev[k], next[k])) {\n changed.push(k);\n }\n }\n for (const k of prevKeys) {\n if (!nextSet.has(k)) removed.push(k);\n }\n added.sort();\n changed.sort();\n removed.sort();\n if (added.length === 0 && changed.length === 0 && removed.length === 0) {\n return null;\n }\n return { added, changed, removed };\n}\n\n/**\n * Convenience: take snapshots of both stores and diff them. Returns\n * `null` when equal. Useful in tests and debug endpoints.\n */\nexport function diffState(\n _a: PlaygroundState,\n _b: PlaygroundState,\n prev?: Readonly<Record<string, unknown>>,\n next?: Readonly<Record<string, unknown>>,\n): StateDiff | null {\n const aSnap = prev ?? _a.snapshot();\n const bSnap = next ?? _b.snapshot();\n return diffSnapshots(aSnap, bSnap);\n}\n\n/**\n * Validate a state-key string. Returns `null` on success, or a\n * human-readable error message on failure. The rules:\n *\n * - 1..64 characters\n * - No whitespace\n * - Only alphanumerics, dots, dashes, underscores\n * - Must start with an alphabetic character (so a parser can\n * safely infer `<Slider stateKey=\"...\">` from a body without\n * swallowing accidental numeric keys).\n *\n * Note: keys like `with/slash` or `with spaces` are rejected because\n * the schema-driven body parser uses them as attribute names —\n * a key with weird characters would break the round-trip.\n */\nexport function validateStateKey(key: string): string | null {\n if (key.length === 0) return 'stateKey must be a non-empty string';\n if (key.length > 64) return 'stateKey must be at most 64 characters';\n if (/\\s/.test(key)) return 'stateKey must not contain whitespace';\n if (!/^[A-Za-z][A-Za-z0-9._-]*$/.test(key)) {\n return (\n 'stateKey contains forbidden characters (allowed: A-Z, a-z, ' +\n '0-9, dot, dash, underscore; must start with a letter)'\n );\n }\n return null;\n}\n\n/**\n * Remove every key from the store, firing subscribers with `undefined`.\n * After this call, `state.snapshot()` is `{}`.\n */\nexport function clearState(state: PlaygroundState): void {\n // Snapshot first to avoid mutating the iteration source.\n for (const k of Object.keys(state.snapshot())) {\n state.set(k, undefined);\n }\n}\n\n/**\n * JSON-envelope snapshot for offline inspection. Useful for:\n * - piping into `jq` / debug endpoints\n * - attaching to error reports\n * - round-tripping through `JSON.parse(state.snapshot)`.\n *\n * The `version` field is bumped if the envelope shape ever changes,\n * so consumers can branch on it.\n */\nexport interface StateEnvelope {\n /** Always 1 for now. */\n version: 1;\n /** Sorted list of keys (stable for diffing). */\n keys: string[];\n /** Shallow key→value map. */\n values: Record<string, unknown>;\n}\n\nexport function dumpState(state: PlaygroundState): string {\n const snap = state.snapshot();\n const envelope: StateEnvelope = {\n version: 1,\n keys: Object.keys(snap).sort(),\n values: { ...snap },\n };\n return JSON.stringify(envelope, null, 2);\n}\n\n/**\n * Apply multiple `set` calls as a single atomic batch. Each\n * subscriber sees only the LAST value written to its key in this\n * batch. The store still batches subscriber fires via the microtask\n * scheduler — `batch` does not flush synchronously, it only ensures\n * that within the batch, repeated `set` calls to the same key don't\n * queue redundant fires.\n *\n * Useful for: undo/redo, multi-field form commits, hydrated state\n * restore where you want each subscriber to fire exactly once with\n * the final value.\n *\n * @example\n * batch(state, (s) => {\n * s.set('x', 1);\n * s.set('y', 2);\n * s.set('x', 3); // overwrites; subscriber sees only 3\n * });\n */\nexport function batch<T>(\n state: PlaygroundState,\n fn: (state: PlaygroundState) => T,\n): T {\n // `_batch` is the underlying transactional primitive on the store.\n // We type-cast because the public PlaygroundState interface doesn't\n // expose this test seam — only the helper does.\n const internal = state as unknown as {\n _batch: (fn: () => T) => T;\n };\n return internal._batch(() => fn(state));\n}","/**\n * Render-prop component that shows a toggle-able raw-source panel.\n * Consumer controls the trigger UI via a children render prop.\n *\n * Design notes:\n * - Render-prop pattern (not props.children-as-trigger) because the\n * trigger UI varies wildly across consumers (button, link, icon,\n * inline text). The consumer gets `(toggle, showing)` and decides\n * what to render.\n * - The source panel is a `<pre>` with `<code>` inside. Plain-text;\n * no syntax highlighting (avoiding a 50KB highlight.js dep).\n * Consumers who need highlighting can compose with their own\n * syntax-highlighter.\n * - The panel is rendered INLINE (not in a portal/tooltip) so the\n * toggle's visual feedback is immediate.\n *\n * @packageDocumentation\n */\n\nimport * as React from 'react';\n\nexport interface ShowSourceProps {\n /** The raw body to display when the user expands the source. */\n body: string;\n /** Source language hint. Used in `data-language` for styling hooks. */\n language?: 'tsx' | 'json';\n /**\n * Children is a render prop that receives `(toggle, showing)`.\n * The consumer is expected to render a trigger element (button,\n * link, etc.) and call `toggle` on user interaction.\n */\n children: (toggle: () => void, showing: boolean) => React.ReactNode;\n}\n\n/**\n * Toggle button + expandable source panel. The trigger UI is the\n * consumer's choice; the panel is plain `<pre><code>`.\n */\nexport function ShowSource(props: ShowSourceProps): React.JSX.Element {\n const { body, language, children } = props;\n const [showing, setShowing] = React.useState(false);\n // Wrap setShowing to keep the toggle reference stable. Consumers\n // can pass `toggle` to a `useEffect` dep array without retriggering.\n const toggle = React.useCallback(() => {\n setShowing((s) => !s);\n }, []);\n\n return (\n <>\n {children(toggle, showing)}\n {showing ? (\n <pre\n data-pgx=\"ShowSource\"\n data-language={language ?? 'text'}\n style={{\n background: '#0f172a',\n color: '#e2e8f0',\n padding: '12px',\n borderRadius: '6px',\n overflowX: 'auto',\n fontFamily: 'ui-monospace, Menlo, monospace',\n fontSize: '12px',\n lineHeight: 1.5,\n margin: '8px 0 0',\n maxHeight: '320px',\n whiteSpace: 'pre',\n }}\n >\n <code>{body}</code>\n </pre>\n ) : null}\n </>\n );\n}\n","/**\n * Error boundary for rendered artifacts. Catches render-time errors\n * (malformed body, throw from a component, etc.) and shows a recoverable\n * fallback. Without this, a single bad artifact blanks the student's\n * whole page.\n *\n * Design notes:\n * - Uses React's class-component error boundary API (the only\n * first-class option in React 18/19).\n * - Default fallback shows the error message + an optional \"Show source\"\n * toggle that reveals the raw body. Consumer can override with\n * `fallback` for custom UX.\n * - `onError` fires before the fallback renders so consumers can log\n * to Sentry / Datadog / etc.\n *\n * @packageDocumentation\n */\n\nimport * as React from 'react';\n\nimport { ShowSource } from './ShowSource.js';\n\nexport interface ArtifactErrorBoundaryProps {\n children: React.ReactNode;\n /**\n * Called when a render error is caught. The `info.componentStack`\n * string is the React component stack at the point of error. Log\n * to your observability backend here.\n */\n onError?: (error: Error, info: { componentStack: string }) => void;\n /**\n * Optional body string. If present and the consumer renders the\n * default fallback, a \"Show source\" toggle will be available that\n * reveals this body verbatim. Ignored if a custom `fallback` is\n * supplied (the consumer is responsible for surfacing source).\n */\n body?: string;\n /**\n * Source language hint for the ShowSource toggle in the default\n * fallback. Defaults to 'tsx' (most artifacts are TSX).\n */\n language?: 'tsx' | 'json';\n /**\n * Optional custom fallback. Pass a React element for a static fallback\n * or a function that receives the error for a dynamic one. If\n * omitted, the default fallback renders a panel with the error\n * message and (if `body` is supplied) a ShowSource toggle.\n */\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n /**\n * Label shown in the default fallback header. Defaults to the\n * artifact kind identifier (e.g. \"playground artifact failed to\n * render\"). The consumer is expected to pass the kind.\n */\n kind?: string;\n}\n\ninterface State {\n error: Error | null;\n}\n\n/**\n * Default fallback panel. Renders the error message and, if `body` is\n * provided, a ShowSource toggle. Designed to be a calm, recoverable\n * surface — not a stack-trace dump that panics users.\n */\nfunction DefaultFallback({\n error,\n body,\n language,\n kind,\n}: {\n error: Error;\n body?: string;\n language?: 'tsx' | 'json';\n kind?: string;\n}): React.JSX.Element {\n const label = kind ? `${kind} artifact failed to render` : 'Artifact failed to render';\n return (\n <div\n role=\"alert\"\n data-pgx=\"ArtifactErrorBoundary\"\n data-pgx-kind={kind}\n style={{\n border: '1px solid #fca5a5',\n background: '#fef2f2',\n color: '#7f1d1d',\n borderRadius: '8px',\n padding: '12px 16px',\n fontFamily: 'ui-sans-serif, system-ui, sans-serif',\n fontSize: '14px',\n maxWidth: '720px',\n margin: '12px 0',\n }}\n >\n <div style={{ fontWeight: 600, marginBottom: '6px' }}>{label}</div>\n <code\n data-pgx=\"error-message\"\n style={{\n display: 'block',\n fontFamily: 'ui-monospace, Menlo, monospace',\n fontSize: '12px',\n background: 'rgba(0,0,0,0.04)',\n padding: '6px 8px',\n borderRadius: '4px',\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n }}\n >\n {error.message}\n </code>\n {body !== undefined ? (\n <div style={{ marginTop: '8px' }}>\n <ShowSource body={body} language={language}>\n {(toggle, showing) => (\n <button\n type=\"button\"\n onClick={toggle}\n data-pgx=\"show-source-toggle\"\n style={{\n background: 'transparent',\n border: '1px solid #fca5a5',\n color: '#7f1d1d',\n padding: '4px 10px',\n fontSize: '12px',\n borderRadius: '4px',\n cursor: 'pointer',\n }}\n >\n {showing ? 'Hide source' : 'Show source'}\n </button>\n )}\n </ShowSource>\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Error boundary. Catches render-time errors in `children` and shows\n * the fallback. The error state is reset on prop change of `children`\n * (or explicit `resetKey` bump) so the consumer can recover by\n * re-rendering.\n */\nexport class ArtifactErrorBoundary extends React.Component<\n ArtifactErrorBoundaryProps,\n State\n> {\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n override componentDidCatch(error: Error, info: React.ErrorInfo): void {\n if (this.props.onError) {\n try {\n this.props.onError(error, { componentStack: info.componentStack ?? '' });\n } catch {\n // Swallow — onError callback errors must not break rendering.\n }\n }\n }\n\n override render(): React.ReactNode {\n const { error } = this.state;\n if (error === null) return this.props.children;\n\n const { fallback, body, language, kind } = this.props;\n if (typeof fallback === 'function') return fallback(error);\n if (fallback !== undefined) return fallback;\n return (\n <DefaultFallback\n error={error}\n body={body}\n language={language}\n kind={kind}\n />\n );\n }\n}\n","/**\n * `createRegistry(overrides)` — build a component map with the default\n * 11 components plus caller overrides. Returns a frozen map so post-hoc\n * mutation can't surprise concurrent renders.\n *\n * Lowercase HTML tags (div, span, etc.) are NOT in this map; they're\n * intrinsic to React and handled by `renderBody` directly.\n *\n * @packageDocumentation\n */\n\nimport type { ComponentType } from 'react';\n\nimport { Button } from './Button.js';\nimport { Card } from './Card.js';\nimport { Chart } from './Chart.js';\nimport { Code } from './Code.js';\nimport { Container } from './Container.js';\nimport { Heading } from './Heading.js';\nimport { List } from './List.js';\nimport { Slider } from './Slider.js';\nimport { Stepper } from './Stepper.js';\nimport { Text } from './Text.js';\nimport { TextField } from './TextField.js';\n\n/**\n * Map of PascalCase component name to React component. Loose typing\n * (`ComponentType<any>`) because each component's prop shape differs and\n * the runtime doesn't know which one it's looking up. Consumers who\n * need stricter types can build their own typed wrapper around\n * `renderBody`.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ComponentMap = Record<string, ComponentType<any>>;\n\nconst DEFAULT_REGISTRY: ComponentMap = Object.freeze({\n Button,\n Card,\n Chart,\n Code,\n Container,\n Heading,\n List,\n Slider,\n Stepper,\n Text,\n TextField,\n});\n\n/**\n * The full default registry (Button, Card, Chart, Code, Container,\n * Heading, List, Slider, Stepper, Text, TextField). Frozen. Use\n * `createRegistry(overrides)` to extend.\n */\nexport const componentMap: ComponentMap = DEFAULT_REGISTRY;\n\n/**\n * Build a new registry by merging the defaults with caller-supplied\n * overrides. The returned map is frozen (Object.freeze) to prevent\n * post-hoc mutation that would surprise concurrent renders.\n *\n * **Lowercase HTML tags are NOT in this map.** The renderer handles\n * `div`, `span`, etc. as React intrinsic elements separately. Calling\n * `createRegistry({ div: StubDiv })` will NOT override the renderer\n * for `<div>` (the renderer uses the lowercase lookup path first).\n *\n * @example\n * ```ts\n * const map = createRegistry({\n * MathExpression: MathExpressionComponent,\n * LatexBlock: LatexBlockComponent,\n * });\n * ```\n */\nexport function createRegistry(overrides: Partial<ComponentMap>): ComponentMap {\n // Merge: defaults are the base; overrides win. Lowercase keys are\n // filtered out because they refer to React intrinsic HTML tags\n // (div, span, etc.) that renderBody handles intrinsically — the\n // caller cannot override intrinsic rendering via the registry.\n // Object.freeze on the result prevents consumers from accidentally\n // mutating the global componentMap by mutating their copy.\n const merged: ComponentMap = { ...DEFAULT_REGISTRY };\n for (const key of Object.keys(overrides) as (keyof ComponentMap)[]) {\n const value = overrides[key];\n if (value === undefined) continue;\n if (key[0] !== key[0]!.toUpperCase()) continue; // skip lowercase\n merged[key] = value;\n }\n return Object.freeze(merged);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playgenx/components",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Default React 19 implementations for every entry in DEFAULT_REGISTRY. Pair with @playgenx/registry and the parser/validator pipeline.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|