@xmachines/play-react 1.0.0-beta.33 → 1.0.0-beta.34
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 +117 -43
- package/dist/ActorProvider.d.ts +82 -0
- package/dist/ActorProvider.d.ts.map +1 -0
- package/dist/ActorProvider.js +166 -0
- package/dist/ActorProvider.js.map +1 -0
- package/dist/PlayRenderer.d.ts +25 -44
- package/dist/PlayRenderer.d.ts.map +1 -1
- package/dist/PlayRenderer.js +31 -125
- package/dist/PlayRenderer.js.map +1 -1
- package/dist/PlayUIProvider.d.ts +67 -0
- package/dist/PlayUIProvider.d.ts.map +1 -0
- package/dist/PlayUIProvider.js +73 -0
- package/dist/PlayUIProvider.js.map +1 -0
- package/dist/index.d.ts +26 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -7
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +6 -74
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -1
- package/dist/useActor.d.ts +1 -1
- package/dist/useActor.d.ts.map +1 -1
- package/dist/useActor.js +3 -5
- package/dist/useActor.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -41,14 +41,19 @@ without muting console output.
|
|
|
41
41
|
- `useBoundProp` — re-exported from `@json-render/react`
|
|
42
42
|
- `ComponentFn` (type) — re-exported from `@json-render/react`
|
|
43
43
|
- `ComponentContext` (type) — re-exported from `@json-render/react`
|
|
44
|
-
- `
|
|
44
|
+
- `ActorProvider` — escape hatch primitive (owns actor bridging, signal bridge, store lifecycle)
|
|
45
|
+
- `PlayUIProvider` — batteries-included composite (wraps `ActorProvider` + `JSONUIProvider`)
|
|
46
|
+
- `usePlayView` — hook for accessing the current view spec inside a provider tree
|
|
47
|
+
- `RenderErrorHandler` (type) — inner per-element error callback signature
|
|
48
|
+
- `ActorProviderProps` (type)
|
|
49
|
+
- `ViewContextValue` (type)
|
|
45
50
|
- `PlayActor` (type)
|
|
46
51
|
|
|
47
52
|
## Quick Start
|
|
48
53
|
|
|
49
54
|
```tsx
|
|
50
55
|
import { definePlayer, formatPlayRouteTransitions } from "@xmachines/play-xstate";
|
|
51
|
-
import { PlayRenderer } from "@xmachines/play-react";
|
|
56
|
+
import { PlayUIProvider, PlayRenderer } from "@xmachines/play-react";
|
|
52
57
|
import { defineCatalog } from "@json-render/core";
|
|
53
58
|
import { defineRegistry } from "@xmachines/play-react";
|
|
54
59
|
import type { ComponentFn } from "@xmachines/play-react";
|
|
@@ -119,12 +124,9 @@ const machine = setup({
|
|
|
119
124
|
meta: {
|
|
120
125
|
route: "/login",
|
|
121
126
|
view: {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
root: "
|
|
125
|
-
elements: {
|
|
126
|
-
root: { type: "Login", props: { title: "Sign In" }, children: [] },
|
|
127
|
-
},
|
|
127
|
+
root: "root",
|
|
128
|
+
elements: {
|
|
129
|
+
root: { type: "Login", props: { title: "Sign In" }, children: [] },
|
|
128
130
|
},
|
|
129
131
|
},
|
|
130
132
|
},
|
|
@@ -134,12 +136,9 @@ const machine = setup({
|
|
|
134
136
|
meta: {
|
|
135
137
|
route: "/dashboard",
|
|
136
138
|
view: {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
root: "
|
|
140
|
-
elements: {
|
|
141
|
-
root: { type: "Dashboard", props: { username: "" }, children: [] },
|
|
142
|
-
},
|
|
139
|
+
root: "root",
|
|
140
|
+
elements: {
|
|
141
|
+
root: { type: "Dashboard", props: { username: "" }, children: [] },
|
|
143
142
|
},
|
|
144
143
|
},
|
|
145
144
|
},
|
|
@@ -169,31 +168,36 @@ const actor = createPlayer();
|
|
|
169
168
|
actor.start();
|
|
170
169
|
|
|
171
170
|
function App() {
|
|
172
|
-
return
|
|
171
|
+
return (
|
|
172
|
+
<PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
173
|
+
<PlayRenderer />
|
|
174
|
+
</PlayUIProvider>
|
|
175
|
+
);
|
|
173
176
|
}
|
|
174
177
|
```
|
|
175
178
|
|
|
176
179
|
## API Reference
|
|
177
180
|
|
|
178
|
-
### `
|
|
181
|
+
### `PlayUIProvider`
|
|
179
182
|
|
|
180
|
-
|
|
183
|
+
Batteries-included composite provider. Wraps `ActorProvider` + `JSONUIProvider`. Pass `actor` and `registryResult` here, then place `<PlayRenderer />` inside as a zero-prop child.
|
|
181
184
|
|
|
182
185
|
```tsx
|
|
183
|
-
<
|
|
186
|
+
<PlayUIProvider
|
|
184
187
|
actor={actor} // required
|
|
185
188
|
registryResult={registryResult} // required
|
|
186
189
|
store={myStore} // optional — controlled mode
|
|
187
190
|
fallback={<p>Loading…</p>} // optional
|
|
188
|
-
|
|
191
|
+
onError={(err, info) => Sentry.captureException(err, { extra: info })} // optional
|
|
192
|
+
onRenderError={(error, elementType) => console.warn(`<${elementType}> crashed:`, error)} // optional
|
|
193
|
+
>
|
|
194
|
+
<PlayRenderer />
|
|
195
|
+
</PlayUIProvider>
|
|
189
196
|
```
|
|
190
197
|
|
|
191
198
|
**`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
|
|
192
199
|
|
|
193
|
-
**`registryResult`** — Full result from `defineRegistry(catalog, { components, actions })`.
|
|
194
|
-
|
|
195
|
-
`defineRegistry` also accepts `onRenderError(error, elementType)`, which receives errors
|
|
196
|
-
caught by `@json-render/react`'s inner element boundary before the default logger is used.
|
|
200
|
+
**`registryResult`** — Full result from `defineRegistry(catalog, { components, actions })`. Contains both the component registry and action handlers.
|
|
197
201
|
|
|
198
202
|
**`store`** (optional) — Controls per-view UI state (form values, `$state` bindings):
|
|
199
203
|
|
|
@@ -207,31 +211,99 @@ import type { StateStore } from "@json-render/core";
|
|
|
207
211
|
|
|
208
212
|
const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "alice" }) });
|
|
209
213
|
|
|
210
|
-
<
|
|
214
|
+
<PlayUIProvider actor={actor} registryResult={registryResult} store={store}>
|
|
215
|
+
<PlayRenderer />
|
|
216
|
+
</PlayUIProvider>;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**`fallback`** — Shown when `actor.currentView.get()` is `null` (machine in a no-view state, or during initialisation).
|
|
220
|
+
|
|
221
|
+
**`onError`** — Called when the outer `PlayErrorBoundary` catches an error. Receives `(error: Error, info: React.ErrorInfo)`. Use for observability tools (Sentry, Datadog, etc.).
|
|
222
|
+
|
|
223
|
+
**`onRenderError`** — Called when an individual catalog component throws during render. Caught by `@json-render/react`'s inner per-element boundary — the failed component is silently removed while the rest of the spec continues rendering. `onError` / `fallback` are **not** triggered. When both `onRenderError` on `PlayUIProvider` and on `defineRegistry` are set, the prop wins.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### `ActorProvider`
|
|
228
|
+
|
|
229
|
+
Escape hatch primitive. Owns actor bridging, signal bridge, and store lifecycle. Use this when you need direct control over the provider layer (e.g. custom `JSONUIProvider` configuration).
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
import { ActorProvider } from "@xmachines/play-react";
|
|
233
|
+
|
|
234
|
+
<ActorProvider
|
|
235
|
+
actor={actor}
|
|
236
|
+
registryResult={registryResult}
|
|
237
|
+
onRenderError={(err, type) => reportError(err, type)}
|
|
238
|
+
>
|
|
239
|
+
{/* your own JSONUIProvider + PlayRenderer tree */}
|
|
240
|
+
</ActorProvider>;
|
|
211
241
|
```
|
|
212
242
|
|
|
213
|
-
|
|
214
|
-
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### `PlayRenderer`
|
|
246
|
+
|
|
247
|
+
Zero-prop leaf component. Must be rendered inside a `PlayUIProvider` (or `ActorProvider`) tree. Subscribes to `actor.currentView` via context and renders the current spec.
|
|
215
248
|
|
|
216
249
|
```tsx
|
|
250
|
+
<PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
251
|
+
<PlayRenderer />
|
|
252
|
+
</PlayUIProvider>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`PlayRenderer` accepts no props — all configuration (`actor`, `registryResult`, `store`, `fallback`, `onError`, `onRenderError`) is provided by the enclosing `PlayUIProvider` or `ActorProvider`.
|
|
256
|
+
|
|
257
|
+
## Error handling
|
|
258
|
+
|
|
259
|
+
The provider tree has two layers of error boundaries:
|
|
260
|
+
|
|
261
|
+
### Outer boundary — `onError` and `fallback`
|
|
262
|
+
|
|
263
|
+
Wraps the entire renderer output via `PlayErrorBoundary`. Triggered when the spec or store setup throws, or when the inner boundary is not present.
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
<PlayUIProvider
|
|
267
|
+
actor={actor}
|
|
268
|
+
registryResult={registryResult}
|
|
269
|
+
fallback={<p>Something went wrong.</p>}
|
|
270
|
+
onError={(err, info) => Sentry.captureException(err, { extra: info })}
|
|
271
|
+
>
|
|
272
|
+
<PlayRenderer />
|
|
273
|
+
</PlayUIProvider>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Inner boundary — `onRenderError`
|
|
277
|
+
|
|
278
|
+
Each catalog element is individually wrapped in an error boundary by `@json-render/react`. When a component throws, it is silently removed while the rest of the spec continues rendering. The outer boundary is **not** triggered.
|
|
279
|
+
|
|
280
|
+
Pass `onRenderError` to `PlayUIProvider` (or `ActorProvider`) — overrides any registry-level handler — or bake it into `defineRegistry`:
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
// via PlayUIProvider prop
|
|
284
|
+
<PlayUIProvider
|
|
285
|
+
actor={actor}
|
|
286
|
+
registryResult={registryResult}
|
|
287
|
+
onRenderError={(error, elementType) => {
|
|
288
|
+
console.warn(`<${elementType}> crashed:`, error);
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
<PlayRenderer />
|
|
292
|
+
</PlayUIProvider>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
// via defineRegistry — bakes the handler into the registry
|
|
217
297
|
const registryResult = defineRegistry(catalog, {
|
|
218
298
|
components: { Login, Dashboard },
|
|
219
|
-
actions: {
|
|
220
|
-
login: async (params) => {
|
|
221
|
-
if (!params) return;
|
|
222
|
-
actor.send({ type: "auth.login", username: params.username });
|
|
223
|
-
},
|
|
224
|
-
logout: async () => {
|
|
225
|
-
actor.send({ type: "auth.logout" });
|
|
226
|
-
},
|
|
227
|
-
},
|
|
299
|
+
actions: { login: async (params) => { ... }, logout: async () => { ... } },
|
|
228
300
|
onRenderError(error, elementType) {
|
|
229
301
|
reportExpectedRenderError(error, elementType);
|
|
230
302
|
},
|
|
231
303
|
});
|
|
232
304
|
```
|
|
233
305
|
|
|
234
|
-
|
|
306
|
+
`onRenderError` is typed as `RenderErrorHandler` and exported from `@xmachines/play-react`.
|
|
235
307
|
|
|
236
308
|
---
|
|
237
309
|
|
|
@@ -249,7 +321,7 @@ function LogoutButton() {
|
|
|
249
321
|
}
|
|
250
322
|
```
|
|
251
323
|
|
|
252
|
-
Throws `"useActor() must be called inside <
|
|
324
|
+
Throws `NonNullableError: "useActor() must be called inside <ActorProvider> (or <PlayUIProvider>)"` if called outside the tree.
|
|
253
325
|
|
|
254
326
|
---
|
|
255
327
|
|
|
@@ -276,7 +348,7 @@ function NavBar({ actor }: { actor: ReturnType<typeof createPlayer> }) {
|
|
|
276
348
|
|
|
277
349
|
### `PlayErrorBoundary`
|
|
278
350
|
|
|
279
|
-
Class error boundary that wraps the rendered output.
|
|
351
|
+
Class error boundary that wraps the rendered output. `PlayRenderer` wraps its own output in this boundary automatically — use this directly only if you need to wrap other content or nest boundaries manually.
|
|
280
352
|
|
|
281
353
|
`componentDidCatch` invokes the `onError` prop (for observability tools) but does **not** re-throw — re-throwing from `componentDidCatch` can unmount the entire React 19 root. `getDerivedStateFromError` handles fallback state transition instead.
|
|
282
354
|
|
|
@@ -287,7 +359,9 @@ import { PlayErrorBoundary } from "@xmachines/play-react";
|
|
|
287
359
|
fallback={<p>Something went wrong.</p>}
|
|
288
360
|
onError={(err, info) => Sentry.captureException(err, { extra: info })}
|
|
289
361
|
>
|
|
290
|
-
<
|
|
362
|
+
<PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
363
|
+
<PlayRenderer />
|
|
364
|
+
</PlayUIProvider>
|
|
291
365
|
</PlayErrorBoundary>;
|
|
292
366
|
```
|
|
293
367
|
|
|
@@ -309,10 +383,10 @@ Priority: **route param fills `undefined` slots; explicit non-`undefined` spec p
|
|
|
309
383
|
|
|
310
384
|
## Error Handling
|
|
311
385
|
|
|
312
|
-
| Error
|
|
313
|
-
|
|
|
314
|
-
| `useActor() must be called inside <
|
|
315
|
-
| Component render error
|
|
386
|
+
| Error | Cause | Fix |
|
|
387
|
+
| ------------------------------------------------------------------------ | ---------------------------- | ---------------------------------------------------------------- |
|
|
388
|
+
| `useActor() must be called inside <ActorProvider> (or <PlayUIProvider>)` | Hook called outside the tree | Wrap with `<PlayUIProvider>` or `<ActorProvider>` |
|
|
389
|
+
| Component render error | Component throws | `PlayErrorBoundary` catches it; inspect component implementation |
|
|
316
390
|
|
|
317
391
|
---
|
|
318
392
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActorProvider — escape hatch primitive for actor lifecycle management.
|
|
3
|
+
*
|
|
4
|
+
* Owns: actor bridging, signal subscription (useSignalEffect), per-view StateStore
|
|
5
|
+
* lifecycle (controlled/uncontrolled), handler resolution via inner component pattern
|
|
6
|
+
* (uses useStateStore()), StateProvider wrap, PlayErrorBoundary wrap, onRenderError injection.
|
|
7
|
+
*
|
|
8
|
+
* Standard usage: prefer <PlayUIProvider> unless you need to compose providers manually.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import React from "react";
|
|
13
|
+
import type { DefineRegistryResult, ComponentRegistry } from "@json-render/react";
|
|
14
|
+
import type { BaseActorProviderProps, BaseViewContextValue } from "@xmachines/play-actor";
|
|
15
|
+
/**
|
|
16
|
+
* Props for the ActorProvider component.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {
|
|
21
|
+
/** Optional component shown when currentView is null or a catalog component throws */
|
|
22
|
+
fallback?: React.ReactNode;
|
|
23
|
+
/** Optional error handler callback invoked when a catalog component throws during render */
|
|
24
|
+
onError?: (error: Error, info: React.ErrorInfo) => void;
|
|
25
|
+
/** Child components to render inside the provider tree */
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Value provided by ViewContext (accessible via usePlayView()).
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
export interface ViewContextValue extends BaseViewContextValue<ComponentRegistry> {
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Hook to access the current view spec, handlers, and registry.
|
|
37
|
+
*
|
|
38
|
+
* Must be called inside <ActorProvider> or <PlayUIProvider>.
|
|
39
|
+
*
|
|
40
|
+
* @throws {Error} If called outside an ActorProvider/PlayUIProvider tree
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { usePlayView } from "@xmachines/play-react";
|
|
45
|
+
*
|
|
46
|
+
* function MyRenderer() {
|
|
47
|
+
* const view = usePlayView();
|
|
48
|
+
* return <Renderer spec={view.spec} registry={view.registry} />;
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export declare function usePlayView(): ViewContextValue;
|
|
55
|
+
/**
|
|
56
|
+
* ActorProvider — escape hatch primitive for composing actor lifecycle with custom providers.
|
|
57
|
+
*
|
|
58
|
+
* Subscribes to actor.currentView signal, manages the per-view StateStore lifecycle,
|
|
59
|
+
* wraps children in StateProvider and PlayErrorBoundary, and injects onRenderError
|
|
60
|
+
* into the component registry.
|
|
61
|
+
*
|
|
62
|
+
* Standard usage: prefer <PlayUIProvider> unless you need to compose providers manually.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* // Custom composition (escape hatch):
|
|
67
|
+
* <ActorProvider actor={actor} registryResult={registryResult}>
|
|
68
|
+
* <JSONUIProvider registry={registryResult.registry}>
|
|
69
|
+
* <PlayRenderer />
|
|
70
|
+
* </JSONUIProvider>
|
|
71
|
+
* </ActorProvider>
|
|
72
|
+
*
|
|
73
|
+
* // Standard usage: prefer PlayUIProvider
|
|
74
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
75
|
+
* <PlayRenderer />
|
|
76
|
+
* </PlayUIProvider>
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
export declare const ActorProvider: React.FC<ActorProviderProps>;
|
|
82
|
+
//# sourceMappingURL=ActorProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActorProvider.d.ts","sourceRoot":"","sources":["../src/ActorProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAsD,MAAM,OAAO,CAAC;AAE3E,OAAO,KAAK,EAAE,oBAAoB,EAAY,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAO5F,OAAO,KAAK,EAAY,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAIpG;;;;GAIG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB,CAAC,oBAAoB,CAAC;IACvF,sFAAsF;IACtF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,4FAA4F;IAC5F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;IACxD,0DAA0D;IAC1D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;CAAG;AAQpF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,CAE9C;AAmDD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqFtD,CAAC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ActorProvider — escape hatch primitive for actor lifecycle management.
|
|
4
|
+
*
|
|
5
|
+
* Owns: actor bridging, signal subscription (useSignalEffect), per-view StateStore
|
|
6
|
+
* lifecycle (controlled/uncontrolled), handler resolution via inner component pattern
|
|
7
|
+
* (uses useStateStore()), StateProvider wrap, PlayErrorBoundary wrap, onRenderError injection.
|
|
8
|
+
*
|
|
9
|
+
* Standard usage: prefer <PlayUIProvider> unless you need to compose providers manually.
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
import React, { useState, useRef, createContext, useContext } from "react";
|
|
14
|
+
import { StateProvider, useStateStore } from "@json-render/react";
|
|
15
|
+
import { createAtom } from "@xstate/store";
|
|
16
|
+
import { xstateStoreStateStore } from "@json-render/xstate";
|
|
17
|
+
import { useSignalEffect } from "./useSignalEffect.js";
|
|
18
|
+
import { PlayErrorBoundary } from "./PlayErrorBoundary.js";
|
|
19
|
+
import { assertNonNullable } from "@xmachines/play";
|
|
20
|
+
import { ActorContext } from "./useActor.js";
|
|
21
|
+
/**
|
|
22
|
+
* Internal React context for ViewContextValue.
|
|
23
|
+
* Accessed via usePlayView() hook.
|
|
24
|
+
*/
|
|
25
|
+
const ViewContext = createContext(null);
|
|
26
|
+
/**
|
|
27
|
+
* Hook to access the current view spec, handlers, and registry.
|
|
28
|
+
*
|
|
29
|
+
* Must be called inside <ActorProvider> or <PlayUIProvider>.
|
|
30
|
+
*
|
|
31
|
+
* @throws {Error} If called outside an ActorProvider/PlayUIProvider tree
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* import { usePlayView } from "@xmachines/play-react";
|
|
36
|
+
*
|
|
37
|
+
* function MyRenderer() {
|
|
38
|
+
* const view = usePlayView();
|
|
39
|
+
* return <Renderer spec={view.spec} registry={view.registry} />;
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export function usePlayView() {
|
|
46
|
+
return assertNonNullable(useContext(ViewContext), "ViewContext");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a StateStore backed by a fresh @xstate/store atom seeded from the given state.
|
|
50
|
+
* Called internally per view transition when no external store prop is provided.
|
|
51
|
+
*/
|
|
52
|
+
function createViewStore(initialState) {
|
|
53
|
+
return xstateStoreStateStore({ atom: createAtom(initialState) });
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Inner component that runs inside StateProvider so it can access StateStore context
|
|
57
|
+
* via useStateStore(). Resolves action handlers from registryResult.handlers() using
|
|
58
|
+
* the live StateProvider set/getSnapshot functions, then exposes them via ViewContext.
|
|
59
|
+
*/
|
|
60
|
+
function ActorProviderInner({ registryResult, spec, store, children, }) {
|
|
61
|
+
const stateCtx = useStateStore();
|
|
62
|
+
// Build a SetState adapter: the handlers factory expects an updater-function pattern
|
|
63
|
+
// ((prev) => next), while stateCtx provides path-based set/update. This adapter
|
|
64
|
+
// bridges the two so action functions can use setState if needed.
|
|
65
|
+
const setStateAdapter = (updater) => {
|
|
66
|
+
const prev = stateCtx.getSnapshot();
|
|
67
|
+
stateCtx.update(updater(prev));
|
|
68
|
+
};
|
|
69
|
+
const handlers = registryResult.handlers(() => setStateAdapter, () => stateCtx.getSnapshot());
|
|
70
|
+
const viewValue = {
|
|
71
|
+
spec,
|
|
72
|
+
handlers,
|
|
73
|
+
registry: registryResult.registry,
|
|
74
|
+
store,
|
|
75
|
+
};
|
|
76
|
+
return _jsx(ViewContext.Provider, { value: viewValue, children: children });
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* ActorProvider — escape hatch primitive for composing actor lifecycle with custom providers.
|
|
80
|
+
*
|
|
81
|
+
* Subscribes to actor.currentView signal, manages the per-view StateStore lifecycle,
|
|
82
|
+
* wraps children in StateProvider and PlayErrorBoundary, and injects onRenderError
|
|
83
|
+
* into the component registry.
|
|
84
|
+
*
|
|
85
|
+
* Standard usage: prefer <PlayUIProvider> unless you need to compose providers manually.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* // Custom composition (escape hatch):
|
|
90
|
+
* <ActorProvider actor={actor} registryResult={registryResult}>
|
|
91
|
+
* <JSONUIProvider registry={registryResult.registry}>
|
|
92
|
+
* <PlayRenderer />
|
|
93
|
+
* </JSONUIProvider>
|
|
94
|
+
* </ActorProvider>
|
|
95
|
+
*
|
|
96
|
+
* // Standard usage: prefer PlayUIProvider
|
|
97
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
98
|
+
* <PlayRenderer />
|
|
99
|
+
* </PlayUIProvider>
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
export const ActorProvider = ({ actor, registryResult, store: externalStore, fallback = null, onError, onRenderError, children, }) => {
|
|
105
|
+
// React state for triggering re-renders (NOT business logic state)
|
|
106
|
+
// Signal is source of truth, useState is just React's render trigger
|
|
107
|
+
const [view, setView] = useState(() => actor.currentView.get());
|
|
108
|
+
// Internal store ref — tracks the current per-view atom store.
|
|
109
|
+
// Keyed to view identity: recreated whenever the view changes (new spec.state seed).
|
|
110
|
+
// Ignored when externalStore is provided.
|
|
111
|
+
const internalStoreRef = useRef(null);
|
|
112
|
+
const lastViewRef = useRef(null);
|
|
113
|
+
// Subscribe to signal changes
|
|
114
|
+
useSignalEffect(() => {
|
|
115
|
+
const currentView = actor.currentView.get();
|
|
116
|
+
setView(currentView);
|
|
117
|
+
});
|
|
118
|
+
// Inject onRenderError prop into registry (non-enumerable, overrides defineRegistry-level handler)
|
|
119
|
+
// Centralised here per D-19 — one location for all framework renderers
|
|
120
|
+
const activeRegistryResult = onRenderError
|
|
121
|
+
? {
|
|
122
|
+
...registryResult,
|
|
123
|
+
registry: (() => {
|
|
124
|
+
const r = { ...registryResult.registry };
|
|
125
|
+
Object.defineProperty(r, "onRenderError", {
|
|
126
|
+
value: onRenderError,
|
|
127
|
+
enumerable: false,
|
|
128
|
+
configurable: true,
|
|
129
|
+
});
|
|
130
|
+
return r;
|
|
131
|
+
})(),
|
|
132
|
+
}
|
|
133
|
+
: registryResult;
|
|
134
|
+
// No view in current state — render fallback
|
|
135
|
+
if (!view) {
|
|
136
|
+
return _jsx(_Fragment, { children: fallback });
|
|
137
|
+
}
|
|
138
|
+
// Resolve the store to use for StateProvider:
|
|
139
|
+
// - External (controlled): use as-is, caller manages lifecycle
|
|
140
|
+
// - Internal: create a fresh atom when the view changes (new route/state)
|
|
141
|
+
let store;
|
|
142
|
+
if (externalStore) {
|
|
143
|
+
store = externalStore;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Recreate the internal store when the view identity changes
|
|
147
|
+
// (view is a new object on every transition per deriveCurrentView)
|
|
148
|
+
if (internalStoreRef.current === null || lastViewRef.current !== view) {
|
|
149
|
+
// Proto-safe guard (T-37-03-01): prevents Date/Array/class-instance from being
|
|
150
|
+
// passed to createAtom. Replaces the weak `?? {}` guard from old code_context.
|
|
151
|
+
const rawState = view.state;
|
|
152
|
+
const initialState = rawState !== null &&
|
|
153
|
+
rawState !== undefined &&
|
|
154
|
+
typeof rawState === "object" &&
|
|
155
|
+
!Array.isArray(rawState) &&
|
|
156
|
+
Object.getPrototypeOf(rawState) === Object.prototype
|
|
157
|
+
? rawState
|
|
158
|
+
: {};
|
|
159
|
+
internalStoreRef.current = createViewStore(initialState);
|
|
160
|
+
lastViewRef.current = view;
|
|
161
|
+
}
|
|
162
|
+
store = internalStoreRef.current;
|
|
163
|
+
}
|
|
164
|
+
return (_jsx(ActorContext.Provider, { value: actor, children: _jsx(PlayErrorBoundary, { fallback: fallback, ...(onError && { onError }), children: _jsx(StateProvider, { store: store, children: _jsx(ActorProviderInner, { registryResult: activeRegistryResult, spec: view, store: store, children: children }) }) }) }));
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=ActorProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActorProvider.js","sourceRoot":"","sources":["../src/ActorProvider.tsx"],"names":[],"mappings":"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"}
|
package/dist/PlayRenderer.d.ts
CHANGED
|
@@ -1,55 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PlayRenderer -
|
|
2
|
+
* PlayRenderer — zero-prop leaf component for rendering the current actor view.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Must be rendered inside <ActorProvider> or <PlayUIProvider>.
|
|
5
|
+
* Reads view spec, handlers, and registry from usePlayView() context,
|
|
6
|
+
* then delegates to @json-render/react Renderer.
|
|
7
|
+
*
|
|
8
|
+
* Standard usage:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
11
|
+
* <PlayRenderer />
|
|
12
|
+
* </PlayUIProvider>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* For custom provider composition, use <ActorProvider> (escape hatch):
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <ActorProvider actor={actor} registryResult={registryResult}>
|
|
18
|
+
* <JSONUIProvider registry={registryResult.registry}>
|
|
19
|
+
* <PlayRenderer />
|
|
20
|
+
* </JSONUIProvider>
|
|
21
|
+
* </ActorProvider>
|
|
22
|
+
* ```
|
|
5
23
|
*
|
|
6
24
|
* @packageDocumentation
|
|
7
25
|
*/
|
|
8
26
|
import React from "react";
|
|
9
|
-
import type { PlayRendererProps } from "./types.js";
|
|
10
27
|
/**
|
|
11
|
-
*
|
|
12
|
-
* via @json-render/react Renderer.
|
|
13
|
-
*
|
|
14
|
-
* Architecture:
|
|
15
|
-
* - Subscribes to actor.currentView signal via useSignalEffect
|
|
16
|
-
* - Renders view.spec via StateProvider → ActionProvider → VisibilityProvider → Renderer
|
|
17
|
-
* - Routes actions via registryResult.handlers() — real async functions dispatching to actor.
|
|
18
|
-
* - State store: uses external `store` prop if provided (controlled mode); otherwise
|
|
19
|
-
* creates a fresh @xstate/store atom per view transition seeded from spec.state.
|
|
20
|
-
* The atom resets automatically when the actor transitions to a new view, mirroring
|
|
21
|
-
* the actor's currentView lifecycle.
|
|
28
|
+
* Zero-prop leaf component that renders the current actor view.
|
|
22
29
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Invariant: Signal-Only Reactivity - Business logic state lives in actor signals.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```typescript
|
|
29
|
-
* import { PlayRenderer } from "@xmachines/play-react";
|
|
30
|
-
* import { defineRegistry } from "@json-render/react";
|
|
31
|
-
*
|
|
32
|
-
* const registryResult = defineRegistry(catalog, {
|
|
33
|
-
* components: { Login, Dashboard },
|
|
34
|
-
* actions: {
|
|
35
|
-
* login: async ({ username }) => actor.send({ type: 'auth.login', username }),
|
|
36
|
-
* logout: async () => actor.send({ type: 'auth.logout' }),
|
|
37
|
-
* route: async ({ to, params }) => actor.send({ type: 'play.route', to, params }),
|
|
38
|
-
* },
|
|
39
|
-
* });
|
|
40
|
-
*
|
|
41
|
-
* // Uncontrolled — fresh atom created per view, seeded from spec.state:
|
|
42
|
-
* <PlayRenderer actor={actor} registryResult={registryResult} />
|
|
43
|
-
*
|
|
44
|
-
* // Controlled — caller provides and owns the store:
|
|
45
|
-
* import { createAtom } from "@xstate/store";
|
|
46
|
-
* import { xstateStoreStateStore } from "@json-render/xstate";
|
|
47
|
-
* const store = xstateStoreStateStore({ atom: createAtom({ username: "" }) });
|
|
48
|
-
* <PlayRenderer actor={actor} registryResult={registryResult} store={store} />
|
|
49
|
-
* ```
|
|
30
|
+
* Reads the current PlaySpec, handlers, and registry from the ActorProvider
|
|
31
|
+
* context via usePlayView(), then renders via @json-render/react Renderer.
|
|
50
32
|
*
|
|
51
|
-
* @
|
|
52
|
-
* @returns React element rendering current view from actor
|
|
33
|
+
* @public
|
|
53
34
|
*/
|
|
54
|
-
export declare const PlayRenderer: React.FC
|
|
35
|
+
export declare const PlayRenderer: React.FC;
|
|
55
36
|
//# sourceMappingURL=PlayRenderer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAGhC,CAAC"}
|
package/dist/PlayRenderer.js
CHANGED
|
@@ -1,136 +1,42 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* PlayRenderer -
|
|
3
|
+
* PlayRenderer — zero-prop leaf component for rendering the current actor view.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Must be rendered inside <ActorProvider> or <PlayUIProvider>.
|
|
6
|
+
* Reads view spec, handlers, and registry from usePlayView() context,
|
|
7
|
+
* then delegates to @json-render/react Renderer.
|
|
8
|
+
*
|
|
9
|
+
* Standard usage:
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
12
|
+
* <PlayRenderer />
|
|
13
|
+
* </PlayUIProvider>
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* For custom provider composition, use <ActorProvider> (escape hatch):
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <ActorProvider actor={actor} registryResult={registryResult}>
|
|
19
|
+
* <JSONUIProvider registry={registryResult.registry}>
|
|
20
|
+
* <PlayRenderer />
|
|
21
|
+
* </JSONUIProvider>
|
|
22
|
+
* </ActorProvider>
|
|
23
|
+
* ```
|
|
6
24
|
*
|
|
7
25
|
* @packageDocumentation
|
|
8
26
|
*/
|
|
9
|
-
import React
|
|
10
|
-
import { Renderer
|
|
11
|
-
import {
|
|
12
|
-
import { xstateStoreStateStore } from "@json-render/xstate";
|
|
13
|
-
import { useSignalEffect } from "./useSignalEffect.js";
|
|
14
|
-
import { PlayErrorBoundary } from "./PlayErrorBoundary.js";
|
|
15
|
-
import { ActorContext } from "./useActor.js";
|
|
16
|
-
/**
|
|
17
|
-
* Create a StateStore backed by a fresh @xstate/store atom seeded from the given state.
|
|
18
|
-
* Called internally per view transition when no external store prop is provided.
|
|
19
|
-
*/
|
|
20
|
-
function createViewStore(initialState) {
|
|
21
|
-
return xstateStoreStateStore({ atom: createAtom(initialState) });
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Inner component that runs inside StateProvider so it can access StateStore context
|
|
25
|
-
* via useStateStore(). Resolves action handlers from registryResult.handlers() using
|
|
26
|
-
* the live StateProvider set/getSnapshot functions, then renders ActionProvider.
|
|
27
|
-
*/
|
|
28
|
-
function PlayRendererInner({ registryResult, spec, }) {
|
|
29
|
-
const stateCtx = useStateStore();
|
|
30
|
-
// Build a SetState adapter: the handlers factory expects an updater-function pattern
|
|
31
|
-
// ((prev) => next), while stateCtx provides path-based set/update. This adapter
|
|
32
|
-
// bridges the two so action functions can use setState if needed.
|
|
33
|
-
const setStateAdapter = (updater) => {
|
|
34
|
-
const prev = stateCtx.getSnapshot();
|
|
35
|
-
stateCtx.update(updater(prev));
|
|
36
|
-
};
|
|
37
|
-
const handlers = registryResult.handlers(() => setStateAdapter, () => stateCtx.getSnapshot());
|
|
38
|
-
return (_jsx(ActionProvider, { handlers: handlers, children: _jsx(VisibilityProvider, { children: _jsx(Renderer, { spec: spec, registry: registryResult.registry }) }) }));
|
|
39
|
-
}
|
|
27
|
+
import React from "react";
|
|
28
|
+
import { Renderer } from "@json-render/react";
|
|
29
|
+
import { usePlayView } from "./ActorProvider.js";
|
|
40
30
|
/**
|
|
41
|
-
*
|
|
42
|
-
* via @json-render/react Renderer.
|
|
43
|
-
*
|
|
44
|
-
* Architecture:
|
|
45
|
-
* - Subscribes to actor.currentView signal via useSignalEffect
|
|
46
|
-
* - Renders view.spec via StateProvider → ActionProvider → VisibilityProvider → Renderer
|
|
47
|
-
* - Routes actions via registryResult.handlers() — real async functions dispatching to actor.
|
|
48
|
-
* - State store: uses external `store` prop if provided (controlled mode); otherwise
|
|
49
|
-
* creates a fresh @xstate/store atom per view transition seeded from spec.state.
|
|
50
|
-
* The atom resets automatically when the actor transitions to a new view, mirroring
|
|
51
|
-
* the actor's currentView lifecycle.
|
|
52
|
-
*
|
|
53
|
-
* Invariant: Actor Authority - Actor decides all state transitions via guards.
|
|
54
|
-
* Invariant: Passive Infrastructure - Component observes signals and sends events.
|
|
55
|
-
* Invariant: Signal-Only Reactivity - Business logic state lives in actor signals.
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* ```typescript
|
|
59
|
-
* import { PlayRenderer } from "@xmachines/play-react";
|
|
60
|
-
* import { defineRegistry } from "@json-render/react";
|
|
31
|
+
* Zero-prop leaf component that renders the current actor view.
|
|
61
32
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* actions: {
|
|
65
|
-
* login: async ({ username }) => actor.send({ type: 'auth.login', username }),
|
|
66
|
-
* logout: async () => actor.send({ type: 'auth.logout' }),
|
|
67
|
-
* route: async ({ to, params }) => actor.send({ type: 'play.route', to, params }),
|
|
68
|
-
* },
|
|
69
|
-
* });
|
|
70
|
-
*
|
|
71
|
-
* // Uncontrolled — fresh atom created per view, seeded from spec.state:
|
|
72
|
-
* <PlayRenderer actor={actor} registryResult={registryResult} />
|
|
73
|
-
*
|
|
74
|
-
* // Controlled — caller provides and owns the store:
|
|
75
|
-
* import { createAtom } from "@xstate/store";
|
|
76
|
-
* import { xstateStoreStateStore } from "@json-render/xstate";
|
|
77
|
-
* const store = xstateStoreStateStore({ atom: createAtom({ username: "" }) });
|
|
78
|
-
* <PlayRenderer actor={actor} registryResult={registryResult} store={store} />
|
|
79
|
-
* ```
|
|
33
|
+
* Reads the current PlaySpec, handlers, and registry from the ActorProvider
|
|
34
|
+
* context via usePlayView(), then renders via @json-render/react Renderer.
|
|
80
35
|
*
|
|
81
|
-
* @
|
|
82
|
-
* @returns React element rendering current view from actor
|
|
36
|
+
* @public
|
|
83
37
|
*/
|
|
84
|
-
export const PlayRenderer = (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const [view, setView] = useState(() => actor.currentView.get());
|
|
88
|
-
// Internal store ref — tracks the current per-view atom store.
|
|
89
|
-
// Keyed to view identity: recreated whenever the view changes (new spec.state seed).
|
|
90
|
-
// Ignored when externalStore is provided.
|
|
91
|
-
const internalStoreRef = useRef(null);
|
|
92
|
-
const lastViewRef = useRef(null);
|
|
93
|
-
// Subscribe to signal changes
|
|
94
|
-
useSignalEffect(() => {
|
|
95
|
-
const currentView = actor.currentView.get();
|
|
96
|
-
setView(currentView);
|
|
97
|
-
});
|
|
98
|
-
// No view in current state — render fallback
|
|
99
|
-
if (!view) {
|
|
100
|
-
return _jsx(_Fragment, { children: fallback });
|
|
101
|
-
}
|
|
102
|
-
// Inject onRenderError prop into registry (non-enumerable, overrides defineRegistry-level handler)
|
|
103
|
-
const activeRegistryResult = onRenderError
|
|
104
|
-
? {
|
|
105
|
-
...registryResult,
|
|
106
|
-
registry: (() => {
|
|
107
|
-
const r = { ...registryResult.registry };
|
|
108
|
-
Object.defineProperty(r, "onRenderError", {
|
|
109
|
-
value: onRenderError,
|
|
110
|
-
enumerable: false,
|
|
111
|
-
configurable: true,
|
|
112
|
-
});
|
|
113
|
-
return r;
|
|
114
|
-
})(),
|
|
115
|
-
}
|
|
116
|
-
: registryResult;
|
|
117
|
-
// Resolve the store to use for StateProvider:
|
|
118
|
-
// - External (controlled): use as-is, caller manages lifecycle
|
|
119
|
-
// - Internal: create a fresh atom when the view changes (new route/state)
|
|
120
|
-
let store;
|
|
121
|
-
if (externalStore) {
|
|
122
|
-
store = externalStore;
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
// Recreate the internal store when the view identity changes
|
|
126
|
-
// (view is a new object on every transition per deriveCurrentView)
|
|
127
|
-
if (internalStoreRef.current === null || lastViewRef.current !== view) {
|
|
128
|
-
const initialState = view.spec?.state ?? {};
|
|
129
|
-
internalStoreRef.current = createViewStore(initialState);
|
|
130
|
-
lastViewRef.current = view;
|
|
131
|
-
}
|
|
132
|
-
store = internalStoreRef.current;
|
|
133
|
-
}
|
|
134
|
-
return (_jsx(ActorContext.Provider, { value: actor, children: _jsx(PlayErrorBoundary, { fallback: fallback, ...(onError && { onError }), children: _jsx(StateProvider, { store: store, children: _jsx(PlayRendererInner, { registryResult: activeRegistryResult, spec: view.spec ?? null }) }) }) }));
|
|
38
|
+
export const PlayRenderer = () => {
|
|
39
|
+
const view = usePlayView();
|
|
40
|
+
return _jsx(Renderer, { spec: view.spec, registry: view.registry });
|
|
135
41
|
};
|
|
136
42
|
//# sourceMappingURL=PlayRenderer.js.map
|
package/dist/PlayRenderer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayRenderer.js","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"PlayRenderer.js","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAa,GAAG,EAAE;IAC1C,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,OAAO,KAAC,QAAQ,IAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,GAAI,CAAC;AAC/D,CAAC,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlayUIProvider — batteries-included composite provider for actor-driven UI rendering.
|
|
3
|
+
*
|
|
4
|
+
* Wraps ActorProvider + JSONUIProvider (from @json-render/react) to provide a single
|
|
5
|
+
* entry point for all actor lifecycle and UI rendering concerns.
|
|
6
|
+
*
|
|
7
|
+
* Standard usage:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
10
|
+
* <PlayRenderer />
|
|
11
|
+
* </PlayUIProvider>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* For custom provider composition (escape hatch), use <ActorProvider> directly.
|
|
15
|
+
*
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
import React from "react";
|
|
19
|
+
import { type JSONUIProviderProps } from "@json-render/react";
|
|
20
|
+
import { type ActorProviderProps } from "./ActorProvider.js";
|
|
21
|
+
type JSONUIForwardedProps = Pick<JSONUIProviderProps, "validationFunctions" | "navigate" | "functions">;
|
|
22
|
+
/**
|
|
23
|
+
* Props for PlayUIProvider — all ActorProvider props plus JSONUIProvider's own props.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export interface PlayUIProviderProps extends ActorProviderProps, Partial<JSONUIForwardedProps> {
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* PlayUIProvider — batteries-included entry point for actor-driven UI rendering.
|
|
31
|
+
*
|
|
32
|
+
* Combines actor lifecycle management (ActorProvider) with full UI provider setup
|
|
33
|
+
* (JSONUIProvider including ActionProvider, ValidationProvider, VisibilityProvider,
|
|
34
|
+
* StateProvider, and ConfirmDialogManager).
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* import { PlayUIProvider, PlayRenderer } from "@xmachines/play-react";
|
|
39
|
+
*
|
|
40
|
+
* const registryResult = defineRegistry(catalog, {
|
|
41
|
+
* components: { Login, Dashboard },
|
|
42
|
+
* actions: {
|
|
43
|
+
* login: async ({ username }) => actor.send({ type: 'auth.login', username }),
|
|
44
|
+
* logout: async () => actor.send({ type: 'auth.logout' }),
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
49
|
+
* <PlayRenderer />
|
|
50
|
+
* </PlayUIProvider>
|
|
51
|
+
*
|
|
52
|
+
* // With JSONUIProvider options:
|
|
53
|
+
* <PlayUIProvider
|
|
54
|
+
* actor={actor}
|
|
55
|
+
* registryResult={registryResult}
|
|
56
|
+
* navigate={(path) => router.push(path)}
|
|
57
|
+
* validationFunctions={{ isEmail: (v) => /^.+@.+$/.test(String(v)) }}
|
|
58
|
+
* >
|
|
59
|
+
* <PlayRenderer />
|
|
60
|
+
* </PlayUIProvider>
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
export declare const PlayUIProvider: React.FC<PlayUIProviderProps>;
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=PlayUIProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlayUIProvider.d.ts","sourceRoot":"","sources":["../src/PlayUIProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAkB,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAA8B,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGzF,KAAK,oBAAoB,GAAG,IAAI,CAC/B,mBAAmB,EACnB,qBAAqB,GAAG,UAAU,GAAG,WAAW,CAChD,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB,EAAE,OAAO,CAAC,oBAAoB,CAAC;CAAG;AAgCjG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAiBxD,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* PlayUIProvider — batteries-included composite provider for actor-driven UI rendering.
|
|
4
|
+
*
|
|
5
|
+
* Wraps ActorProvider + JSONUIProvider (from @json-render/react) to provide a single
|
|
6
|
+
* entry point for all actor lifecycle and UI rendering concerns.
|
|
7
|
+
*
|
|
8
|
+
* Standard usage:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
11
|
+
* <PlayRenderer />
|
|
12
|
+
* </PlayUIProvider>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* For custom provider composition (escape hatch), use <ActorProvider> directly.
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
import React from "react";
|
|
20
|
+
import { JSONUIProvider } from "@json-render/react";
|
|
21
|
+
import { ActorProvider, usePlayView } from "./ActorProvider.js";
|
|
22
|
+
/**
|
|
23
|
+
* Inner bridge component — reads ViewContext (usePlayView) to get the resolved handlers,
|
|
24
|
+
* registry, and store, then passes them to JSONUIProvider. The store is passed explicitly
|
|
25
|
+
* so JSONUIProvider's internal StateProvider uses the same store ActorProvider set up —
|
|
26
|
+
* without this, JSONUIProvider would create a fresh empty store, shadowing the seeded one.
|
|
27
|
+
*
|
|
28
|
+
* Must be inside ActorProvider's tree so usePlayView() has access to ViewContextValue.
|
|
29
|
+
*/
|
|
30
|
+
function JSONUIBridge({ validationFunctions, navigate, functions, children, }) {
|
|
31
|
+
const view = usePlayView();
|
|
32
|
+
return (_jsx(JSONUIProvider, { registry: view.registry, handlers: view.handlers, store: view.store, ...(validationFunctions && { validationFunctions }), ...(navigate && { navigate }), ...(functions && { functions }), children: children }));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* PlayUIProvider — batteries-included entry point for actor-driven UI rendering.
|
|
36
|
+
*
|
|
37
|
+
* Combines actor lifecycle management (ActorProvider) with full UI provider setup
|
|
38
|
+
* (JSONUIProvider including ActionProvider, ValidationProvider, VisibilityProvider,
|
|
39
|
+
* StateProvider, and ConfirmDialogManager).
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* import { PlayUIProvider, PlayRenderer } from "@xmachines/play-react";
|
|
44
|
+
*
|
|
45
|
+
* const registryResult = defineRegistry(catalog, {
|
|
46
|
+
* components: { Login, Dashboard },
|
|
47
|
+
* actions: {
|
|
48
|
+
* login: async ({ username }) => actor.send({ type: 'auth.login', username }),
|
|
49
|
+
* logout: async () => actor.send({ type: 'auth.logout' }),
|
|
50
|
+
* },
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
54
|
+
* <PlayRenderer />
|
|
55
|
+
* </PlayUIProvider>
|
|
56
|
+
*
|
|
57
|
+
* // With JSONUIProvider options:
|
|
58
|
+
* <PlayUIProvider
|
|
59
|
+
* actor={actor}
|
|
60
|
+
* registryResult={registryResult}
|
|
61
|
+
* navigate={(path) => router.push(path)}
|
|
62
|
+
* validationFunctions={{ isEmail: (v) => /^.+@.+$/.test(String(v)) }}
|
|
63
|
+
* >
|
|
64
|
+
* <PlayRenderer />
|
|
65
|
+
* </PlayUIProvider>
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
export const PlayUIProvider = ({ validationFunctions, navigate, functions, ...actorProps }) => {
|
|
71
|
+
return (_jsx(ActorProvider, { ...actorProps, children: _jsx(JSONUIBridge, { ...(validationFunctions && { validationFunctions }), ...(navigate && { navigate }), ...(functions && { functions }), children: actorProps.children }) }));
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=PlayUIProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlayUIProvider.js","sourceRoot":"","sources":["../src/PlayUIProvider.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,cAAc,EAA4B,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,WAAW,EAA2B,MAAM,oBAAoB,CAAC;AAezF;;;;;;;GAOG;AACH,SAAS,YAAY,CAAC,EACrB,mBAAmB,EACnB,QAAQ,EACR,SAAS,EACT,QAAQ,GACuD;IAC/D,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAE3B,OAAO,CACN,KAAC,cAAc,IACd,QAAQ,EAAE,IAAI,CAAC,QAAQ,EACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ,EACvB,KAAK,EAAE,IAAI,CAAC,KAAK,KACb,CAAC,mBAAmB,IAAI,EAAE,mBAAmB,EAAE,CAAC,KAChD,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,CAAC,KAC1B,CAAC,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC,YAE/B,QAAQ,GACO,CACjB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,MAAM,cAAc,GAAkC,CAAC,EAC7D,mBAAmB,EACnB,QAAQ,EACR,SAAS,EACT,GAAG,UAAU,EACb,EAAE,EAAE;IACJ,OAAO,CACN,KAAC,aAAa,OAAK,UAAU,YAC5B,KAAC,YAAY,OACR,CAAC,mBAAmB,IAAI,EAAE,mBAAmB,EAAE,CAAC,KAChD,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,CAAC,KAC1B,CAAC,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC,YAE/B,UAAU,CAAC,QAAQ,GACN,GACA,CAChB,CAAC;AACH,CAAC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @xmachines/play-react - React renderer for XMachines Play architecture
|
|
3
3
|
*
|
|
4
|
-
* Provides a
|
|
4
|
+
* Provides a provider-based React rendering layer that passively observes actor signals
|
|
5
5
|
* and renders UI components via @json-render/react. This package enables
|
|
6
6
|
* framework-swappable architecture where React is just a rendering target
|
|
7
7
|
* that subscribes to signal changes.
|
|
@@ -9,20 +9,38 @@
|
|
|
9
9
|
* **Key principle:** React state is NEVER used for business logic—only for
|
|
10
10
|
* triggering React's render cycle. Signals are the source of truth.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* **Standard usage:**
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
15
|
+
* <PlayRenderer />
|
|
16
|
+
* </PlayUIProvider>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* **Escape hatch (custom composition):**
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <ActorProvider actor={actor} registryResult={registryResult}>
|
|
22
|
+
* <JSONUIProvider registry={registryResult.registry}>
|
|
23
|
+
* <PlayRenderer />
|
|
24
|
+
* </JSONUIProvider>
|
|
25
|
+
* </ActorProvider>
|
|
26
|
+
* ```
|
|
15
27
|
*
|
|
16
28
|
* @packageDocumentation
|
|
17
29
|
* @module @xmachines/play-react
|
|
18
30
|
*/
|
|
19
31
|
export { PlayRenderer } from "./PlayRenderer.js";
|
|
32
|
+
export { ActorProvider } from "./ActorProvider.js";
|
|
33
|
+
export type { ActorProviderProps, ViewContextValue } from "./ActorProvider.js";
|
|
34
|
+
export { usePlayView } from "./ActorProvider.js";
|
|
35
|
+
export { PlayUIProvider } from "./PlayUIProvider.js";
|
|
36
|
+
export type { PlayUIProviderProps } from "./PlayUIProvider.js";
|
|
20
37
|
export { useSignalEffect } from "./useSignalEffect.js";
|
|
21
38
|
export { PlayErrorBoundary } from "./PlayErrorBoundary.js";
|
|
22
39
|
export { useActor } from "./useActor.js";
|
|
23
|
-
export { defineRegistry, useBoundProp } from "@json-render/react";
|
|
24
|
-
export type { ComponentFn, ComponentContext } from "@json-render/react";
|
|
25
|
-
export type { PlayRendererProps, RenderErrorHandler } from "./types.js";
|
|
26
|
-
export type { PlayErrorBoundaryProps, PlayErrorBoundaryState } from "./PlayErrorBoundary.js";
|
|
27
40
|
export type { PlayActor } from "./useActor.js";
|
|
41
|
+
export { defineRegistry, useBoundProp, JSONUIProvider, StateProvider, ActionProvider, VisibilityProvider, ValidationProvider, Renderer, } from "@json-render/react";
|
|
42
|
+
export type { ComponentFn, ComponentContext, JSONUIProviderProps, StateProviderProps, ActionProviderProps, VisibilityProviderProps, ValidationProviderProps, RendererProps, } from "@json-render/react";
|
|
43
|
+
export type { ActorProviderProps as PlayRendererProps } from "./ActorProvider.js";
|
|
44
|
+
export type { RenderErrorHandler } from "./types.js";
|
|
45
|
+
export type { PlayErrorBoundaryProps, PlayErrorBoundaryState } from "./PlayErrorBoundary.js";
|
|
28
46
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EACN,cAAc,EACd,YAAY,EACZ,cAAc,EACd,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,QAAQ,GACR,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACX,WAAW,EACX,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,aAAa,GACb,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EAAE,kBAAkB,IAAI,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAClF,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,YAAY,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @xmachines/play-react - React renderer for XMachines Play architecture
|
|
3
3
|
*
|
|
4
|
-
* Provides a
|
|
4
|
+
* Provides a provider-based React rendering layer that passively observes actor signals
|
|
5
5
|
* and renders UI components via @json-render/react. This package enables
|
|
6
6
|
* framework-swappable architecture where React is just a rendering target
|
|
7
7
|
* that subscribes to signal changes.
|
|
@@ -9,19 +9,33 @@
|
|
|
9
9
|
* **Key principle:** React state is NEVER used for business logic—only for
|
|
10
10
|
* triggering React's render cycle. Signals are the source of truth.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* **Standard usage:**
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <PlayUIProvider actor={actor} registryResult={registryResult}>
|
|
15
|
+
* <PlayRenderer />
|
|
16
|
+
* </PlayUIProvider>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* **Escape hatch (custom composition):**
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <ActorProvider actor={actor} registryResult={registryResult}>
|
|
22
|
+
* <JSONUIProvider registry={registryResult.registry}>
|
|
23
|
+
* <PlayRenderer />
|
|
24
|
+
* </JSONUIProvider>
|
|
25
|
+
* </ActorProvider>
|
|
26
|
+
* ```
|
|
15
27
|
*
|
|
16
28
|
* @packageDocumentation
|
|
17
29
|
* @module @xmachines/play-react
|
|
18
30
|
*/
|
|
19
31
|
// Main exports
|
|
20
32
|
export { PlayRenderer } from "./PlayRenderer.js";
|
|
33
|
+
export { ActorProvider } from "./ActorProvider.js";
|
|
34
|
+
export { usePlayView } from "./ActorProvider.js";
|
|
35
|
+
export { PlayUIProvider } from "./PlayUIProvider.js";
|
|
21
36
|
export { useSignalEffect } from "./useSignalEffect.js";
|
|
22
37
|
export { PlayErrorBoundary } from "./PlayErrorBoundary.js";
|
|
23
38
|
export { useActor } from "./useActor.js";
|
|
24
|
-
// Re-
|
|
25
|
-
|
|
26
|
-
export { defineRegistry, useBoundProp } from "@json-render/react";
|
|
39
|
+
// Re-exports from @json-render/react (per D-14)
|
|
40
|
+
export { defineRegistry, useBoundProp, JSONUIProvider, StateProvider, ActionProvider, VisibilityProvider, ValidationProvider, Renderer, } from "@json-render/react";
|
|
27
41
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,eAAe;AACf,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,gDAAgD;AAChD,OAAO,EACN,cAAc,EACd,YAAY,EACZ,cAAc,EACd,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,QAAQ,GACR,MAAM,oBAAoB,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,80 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TypeScript type definitions for play-react
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
import type { DefineRegistryResult, RenderErrorHandler } from "@json-render/react";
|
|
7
|
-
import type { StateStore } from "@json-render/core";
|
|
8
|
-
import type { AbstractActor, Viewable } from "@xmachines/play-actor";
|
|
9
|
-
import type React from "react";
|
|
10
|
-
import type { AnyActorLogic } from "xstate";
|
|
11
|
-
/**
|
|
12
|
-
* Props for PlayRenderer component
|
|
4
|
+
* PlayRendererProps has been removed — use ActorProviderProps or PlayUIProviderProps instead.
|
|
5
|
+
* See ActorProvider.tsx and PlayUIProvider.tsx.
|
|
13
6
|
*
|
|
14
|
-
* @
|
|
15
|
-
* non-generic usage.
|
|
16
|
-
*
|
|
17
|
-
* @property actor - Actor instance with currentView signal (requires Viewable capability)
|
|
18
|
-
* @property registryResult - Full result from defineRegistry() in @json-render/react.
|
|
19
|
-
* Contains both the component registry and the action handlers factory. Action handlers
|
|
20
|
-
* are real async functions dispatching to the actor (not string-mapped event types).
|
|
21
|
-
* @property store - Optional external StateStore (controlled mode).
|
|
22
|
-
* @property fallback - Optional component shown when currentView is null
|
|
23
|
-
* @property onError - Optional callback invoked when a catalog component throws during render.
|
|
7
|
+
* @packageDocumentation
|
|
24
8
|
*/
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Full result from defineRegistry() — contains component registry and action handlers.
|
|
30
|
-
* Action handlers are async functions that dispatch to the actor (not string-mapped
|
|
31
|
-
* event type stubs). Replaces the old `registry` + `actions` prop pair.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```tsx
|
|
35
|
-
* const registryResult = defineRegistry(catalog, {
|
|
36
|
-
* components: { Login, Dashboard },
|
|
37
|
-
* actions: {
|
|
38
|
-
* login: async ({ username }) => actor.send({ type: 'auth.login', username }),
|
|
39
|
-
* logout: async () => actor.send({ type: 'auth.logout' }),
|
|
40
|
-
* },
|
|
41
|
-
* });
|
|
42
|
-
* <PlayRenderer actor={actor} registryResult={registryResult} />
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
registryResult: DefineRegistryResult;
|
|
46
|
-
/**
|
|
47
|
-
* Optional external StateStore (e.g. from `xstateStoreStateStore` in @json-render/xstate).
|
|
48
|
-
* When provided, PlayRenderer operates in controlled mode — spec.state is ignored and
|
|
49
|
-
* this store is the single source of truth for UI state (form values, etc.).
|
|
50
|
-
* When omitted, a fresh @xstate/store atom is created internally per view transition,
|
|
51
|
-
* seeded from spec.state.
|
|
52
|
-
*/
|
|
53
|
-
store?: StateStore;
|
|
54
|
-
/** Optional component shown when currentView is null or a catalog component throws */
|
|
55
|
-
fallback?: React.ReactNode;
|
|
56
|
-
/**
|
|
57
|
-
* Optional error handler callback invoked when a catalog component throws during render.
|
|
58
|
-
* Forwarded directly to the internal `PlayErrorBoundary` — use to integrate with
|
|
59
|
-
* production observability tools (Sentry, Datadog, etc.).
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```tsx
|
|
63
|
-
* <PlayRenderer actor={actor} registryResult={registryResult} onError={Sentry.captureException} />
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
onError?: (error: Error, info: React.ErrorInfo) => void;
|
|
67
|
-
/**
|
|
68
|
-
* Called when an individual catalog component throws during render.
|
|
69
|
-
*
|
|
70
|
-
* Caught by the inner error boundary inside each rendered element — the failed
|
|
71
|
-
* component is silently removed while the rest of the spec continues rendering.
|
|
72
|
-
* Unlike `onError`, this does not receive a reset callback.
|
|
73
|
-
* Takes precedence over any `onRenderError` set via `defineRegistry`.
|
|
74
|
-
*
|
|
75
|
-
* When not provided, `@json-render/react` logs the error to `console.error`.
|
|
76
|
-
*/
|
|
77
|
-
onRenderError?: RenderErrorHandler;
|
|
78
|
-
}
|
|
79
|
-
export type { RenderErrorHandler };
|
|
9
|
+
export type { ActorProviderProps } from "./ActorProvider.js";
|
|
10
|
+
export type { PlayUIProviderProps } from "./PlayUIProvider.js";
|
|
11
|
+
export type { RenderErrorHandler } from "@json-render/react";
|
|
80
12
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/types.js
CHANGED
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
package/dist/useActor.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Components rendered inside PlayRenderer can call useActor() to get direct
|
|
5
5
|
* access to the actor instance without prop drilling.
|
|
6
6
|
*
|
|
7
|
-
* @throws {
|
|
7
|
+
* @throws {NonNullableError} If called outside an ActorProvider/PlayUIProvider tree
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```typescript
|
package/dist/useActor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useActor.d.ts","sourceRoot":"","sources":["../src/useActor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;
|
|
1
|
+
{"version":3,"file":"useActor.d.ts","sourceRoot":"","sources":["../src/useActor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE5C,MAAM,MAAM,SAAS,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;AAErD,eAAO,MAAM,YAAY,2CAAwC,CAAC;AAElE,wBAAgB,QAAQ,IAAI,SAAS,CAEpC"}
|
package/dist/useActor.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Components rendered inside PlayRenderer can call useActor() to get direct
|
|
5
5
|
* access to the actor instance without prop drilling.
|
|
6
6
|
*
|
|
7
|
-
* @throws {
|
|
7
|
+
* @throws {NonNullableError} If called outside an ActorProvider/PlayUIProvider tree
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```typescript
|
|
@@ -19,11 +19,9 @@
|
|
|
19
19
|
* @packageDocumentation
|
|
20
20
|
*/
|
|
21
21
|
import { createContext, useContext } from "react";
|
|
22
|
+
import { assertNonNullable } from "@xmachines/play";
|
|
22
23
|
export const ActorContext = createContext(null);
|
|
23
24
|
export function useActor() {
|
|
24
|
-
|
|
25
|
-
if (!actor)
|
|
26
|
-
throw new Error("useActor() must be called inside <PlayRenderer>");
|
|
27
|
-
return actor;
|
|
25
|
+
return assertNonNullable(useContext(ActorContext), "ActorContext");
|
|
28
26
|
}
|
|
29
27
|
//# sourceMappingURL=useActor.js.map
|
package/dist/useActor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useActor.js","sourceRoot":"","sources":["../src/useActor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useActor.js","sourceRoot":"","sources":["../src/useActor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAMpD,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAmB,IAAI,CAAC,CAAC;AAElE,MAAM,UAAU,QAAQ;IACvB,OAAO,iBAAiB,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC,CAAC;AACpE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-react",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.34",
|
|
4
4
|
"description": "React renderer for XMachines Play architecture with signal-driven rendering",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"actor",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"prepublishOnly": "npm run build"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@xmachines/play": "1.0.0-beta.
|
|
49
|
-
"@xmachines/play-actor": "1.0.0-beta.
|
|
50
|
-
"@xmachines/play-signals": "1.0.0-beta.
|
|
48
|
+
"@xmachines/play": "1.0.0-beta.34",
|
|
49
|
+
"@xmachines/play-actor": "1.0.0-beta.34",
|
|
50
|
+
"@xmachines/play-signals": "1.0.0-beta.34"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@json-render/core": "^0.18.0",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@types/node": "^25.6.0",
|
|
59
59
|
"@types/react": "^19.2.14",
|
|
60
60
|
"@types/react-dom": "^19.2.3",
|
|
61
|
-
"@xmachines/shared": "1.0.0-beta.
|
|
61
|
+
"@xmachines/shared": "1.0.0-beta.34",
|
|
62
62
|
"@xstate/store": "^3.17.0",
|
|
63
63
|
"jsdom": "^29.0.2",
|
|
64
64
|
"oxfmt": "^0.45.0",
|