@xmachines/play-react 1.0.0-beta.1
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 +358 -0
- package/dist/PlayRenderer.d.ts +57 -0
- package/dist/PlayRenderer.d.ts.map +1 -0
- package/dist/PlayRenderer.js +87 -0
- package/dist/PlayRenderer.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/useSignalEffect.d.ts +56 -0
- package/dist/useSignalEffect.d.ts.map +1 -0
- package/dist/useSignalEffect.js +107 -0
- package/dist/useSignalEffect.js.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# @xmachines/play-react
|
|
2
|
+
|
|
3
|
+
**React renderer consuming signals and UI schema with provider pattern**
|
|
4
|
+
|
|
5
|
+
Signal-driven React rendering layer observing actor state with zero React state for business logic.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
`@xmachines/play-react` provides `PlayRenderer` and `useSignalEffect` for building React UIs that passively observe actor signals. This package enables framework-swappable architecture where React is just a rendering target subscribing to signal changes — business logic lives entirely in the actor.
|
|
10
|
+
|
|
11
|
+
Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
|
|
12
|
+
|
|
13
|
+
- **Signal-Only Reactivity (INV-05):** No useState/useReducer for business logic, signals only
|
|
14
|
+
- **Passive Infrastructure (INV-04):** Components observe signals, send events to actor
|
|
15
|
+
|
|
16
|
+
**Key Principle:** React state is never used for business logic. Signals are the source of truth.
|
|
17
|
+
|
|
18
|
+
Renderer receives actor via props (provider pattern), not children.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install react@^18.0.0 react-dom@^18.0.0
|
|
24
|
+
npm install @xmachines/play-react
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Current Exports
|
|
28
|
+
|
|
29
|
+
- `PlayRenderer`
|
|
30
|
+
- `useSignalEffect`
|
|
31
|
+
- `PlayRendererProps` (type)
|
|
32
|
+
|
|
33
|
+
**Peer dependencies:**
|
|
34
|
+
|
|
35
|
+
- `react` ^18.0.0 - React runtime
|
|
36
|
+
- `react-dom` ^18.0.0 - React DOM renderer
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { createRoot } from "react-dom/client";
|
|
42
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
43
|
+
import { defineCatalog } from "@xmachines/play-catalog";
|
|
44
|
+
import { PlayRenderer } from "@xmachines/play-react";
|
|
45
|
+
import { z } from "zod";
|
|
46
|
+
|
|
47
|
+
// 1. Define catalog (business logic layer)
|
|
48
|
+
const catalog = defineCatalog({
|
|
49
|
+
LoginForm: z.object({ error: z.string().optional() }),
|
|
50
|
+
Dashboard: z.object({
|
|
51
|
+
userId: z.string(),
|
|
52
|
+
username: z.string(),
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 2. Create React components (view layer)
|
|
57
|
+
const components = {
|
|
58
|
+
LoginForm: ({ error, send }) => (
|
|
59
|
+
<form
|
|
60
|
+
onSubmit={(e) => {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
const data = new FormData(e.currentTarget);
|
|
63
|
+
send({
|
|
64
|
+
type: "auth.login",
|
|
65
|
+
username: data.get("username"),
|
|
66
|
+
password: data.get("password"),
|
|
67
|
+
});
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{error && <p style={{ color: "red" }}>{error}</p>}
|
|
71
|
+
<input name="username" required placeholder="Username" />
|
|
72
|
+
<input name="password" type="password" required placeholder="Password" />
|
|
73
|
+
<button type="submit">Log In</button>
|
|
74
|
+
</form>
|
|
75
|
+
),
|
|
76
|
+
Dashboard: ({ userId, username, send }) => (
|
|
77
|
+
<div>
|
|
78
|
+
<h1>Welcome, {username}!</h1>
|
|
79
|
+
<p>User ID: {userId}</p>
|
|
80
|
+
<button onClick={() => send({ type: "auth.logout" })}>Log Out</button>
|
|
81
|
+
</div>
|
|
82
|
+
),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// 3. Create player actor (business logic runtime)
|
|
86
|
+
const createPlayer = definePlayer({ machine: authMachine, catalog });
|
|
87
|
+
const actor = createPlayer();
|
|
88
|
+
actor.start();
|
|
89
|
+
|
|
90
|
+
// 4. Render UI (actor via props)
|
|
91
|
+
const root = createRoot(document.getElementById("app")!);
|
|
92
|
+
root.render(<PlayRenderer actor={actor} components={components} />);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## API Reference
|
|
96
|
+
|
|
97
|
+
### PlayRenderer
|
|
98
|
+
|
|
99
|
+
Main renderer component subscribing to actor signals and dynamically rendering catalog components:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
interface PlayRendererProps {
|
|
103
|
+
actor: AbstractActor<any>;
|
|
104
|
+
components: Record<string, React.ComponentType<any>>;
|
|
105
|
+
fallback?: React.ReactNode;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Props:**
|
|
110
|
+
|
|
111
|
+
- `actor` - Actor instance with `currentView` signal
|
|
112
|
+
- `components` - Map of component names to React components
|
|
113
|
+
- `fallback` - Component shown when `currentView` is null (default: `null`)
|
|
114
|
+
|
|
115
|
+
**Behavior:**
|
|
116
|
+
|
|
117
|
+
1. Subscribes to `actor.currentView` signal via `useSignalEffect`
|
|
118
|
+
2. Looks up component from `components` map using `view.component` string
|
|
119
|
+
3. Renders component with props from `view.props` + `send` function
|
|
120
|
+
|
|
121
|
+
**Example:**
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
<PlayRenderer
|
|
125
|
+
actor={actor}
|
|
126
|
+
components={{
|
|
127
|
+
HomePage: ({ send }) => <div>Home</div>,
|
|
128
|
+
AboutPage: ({ send }) => <div>About</div>,
|
|
129
|
+
}}
|
|
130
|
+
fallback={<div>Loading...</div>}
|
|
131
|
+
/>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### useSignalEffect()
|
|
135
|
+
|
|
136
|
+
Hook for subscribing to signal changes with automatic cleanup:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
useSignalEffect(() => {
|
|
140
|
+
const value = signal.get();
|
|
141
|
+
// React re-renders when signal changes
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Behavior:**
|
|
146
|
+
|
|
147
|
+
- Tracks signal dependencies automatically via `Signal.Computed` wrapper
|
|
148
|
+
- Uses `Signal.subtle.Watcher` with microtask batching
|
|
149
|
+
- Triggers React state update to force re-render
|
|
150
|
+
- Cleans up watcher on unmount with explicit `unwatch`
|
|
151
|
+
|
|
152
|
+
**Canonical watcher lifecycle:**
|
|
153
|
+
|
|
154
|
+
1. `notify`
|
|
155
|
+
2. `queueMicrotask`
|
|
156
|
+
3. drain pending work (`getPending` and/or `Computed.get`)
|
|
157
|
+
4. run effect + trigger render
|
|
158
|
+
5. re-arm watcher via `watch()`
|
|
159
|
+
|
|
160
|
+
Watcher notification is one-shot, so re-arm and explicit cleanup are both required.
|
|
161
|
+
|
|
162
|
+
**Example:**
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { useSignalEffect } from "@xmachines/play-react";
|
|
166
|
+
import { useState } from "react";
|
|
167
|
+
|
|
168
|
+
function RouteDisplay({ actor }: { actor: AbstractActor<any> }) {
|
|
169
|
+
const [route, setRoute] = useState<string | null>(null);
|
|
170
|
+
|
|
171
|
+
useSignalEffect(() => {
|
|
172
|
+
const currentRoute = actor.currentRoute.get();
|
|
173
|
+
setRoute(currentRoute);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return <div>Current Route: {route ?? "None"}</div>;
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Complete API:** See [API Documentation](../../docs/api/@xmachines/play-react)
|
|
181
|
+
|
|
182
|
+
## Examples
|
|
183
|
+
|
|
184
|
+
### Component Receiving Props from Catalog
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { PlayRenderer } from "@xmachines/play-react";
|
|
188
|
+
import { defineCatalog } from "@xmachines/play-catalog";
|
|
189
|
+
import { z } from "zod";
|
|
190
|
+
|
|
191
|
+
// Define schema in catalog
|
|
192
|
+
const catalog = defineCatalog({
|
|
193
|
+
UserProfile: z.object({
|
|
194
|
+
userId: z.string(),
|
|
195
|
+
name: z.string(),
|
|
196
|
+
avatar: z.string().url().optional(),
|
|
197
|
+
stats: z.object({
|
|
198
|
+
posts: z.number(),
|
|
199
|
+
followers: z.number(),
|
|
200
|
+
}),
|
|
201
|
+
}),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Component receives type-safe props + send
|
|
205
|
+
const components = {
|
|
206
|
+
UserProfile: ({ userId, name, avatar, stats, send }) => (
|
|
207
|
+
<div>
|
|
208
|
+
{avatar && <img src={avatar} alt={name} />}
|
|
209
|
+
<h1>{name}</h1>
|
|
210
|
+
<p>ID: {userId}</p>
|
|
211
|
+
<div>
|
|
212
|
+
<span>{stats.posts} posts</span>
|
|
213
|
+
<span>{stats.followers} followers</span>
|
|
214
|
+
</div>
|
|
215
|
+
<button
|
|
216
|
+
onClick={() =>
|
|
217
|
+
send({
|
|
218
|
+
type: "profile.edit",
|
|
219
|
+
userId,
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
>
|
|
223
|
+
Edit Profile
|
|
224
|
+
</button>
|
|
225
|
+
</div>
|
|
226
|
+
),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
<PlayRenderer actor={actor} components={components} />;
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### useSignalEffect for Custom Rendering
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { useSignalEffect } from "@xmachines/play-react";
|
|
236
|
+
import { AbstractActor } from "@xmachines/play-actor";
|
|
237
|
+
|
|
238
|
+
function CustomRenderer({ actor }: { actor: AbstractActor<any> }) {
|
|
239
|
+
const [view, setView] = useState(null);
|
|
240
|
+
|
|
241
|
+
// Subscribe to currentView signal
|
|
242
|
+
useSignalEffect(() => {
|
|
243
|
+
const currentView = actor.currentView.get();
|
|
244
|
+
setView(currentView);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (!view) return <div>No view</div>;
|
|
248
|
+
|
|
249
|
+
// Custom rendering logic
|
|
250
|
+
if (view.component === "SpecialCase") {
|
|
251
|
+
return <SpecialCaseComponent {...view.props} actor={actor} />;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Fallback to standard rendering
|
|
255
|
+
return <DefaultComponent view={view} actor={actor} />;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Provider Pattern
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { PlayTanStackRouterProvider } from "@xmachines/play-tanstack-react-router";
|
|
263
|
+
import { PlayRenderer } from "@xmachines/play-react";
|
|
264
|
+
|
|
265
|
+
// Renderer receives actor via props (not children)
|
|
266
|
+
function App() {
|
|
267
|
+
return (
|
|
268
|
+
<PlayTanStackRouterProvider
|
|
269
|
+
actor={actor}
|
|
270
|
+
router={router}
|
|
271
|
+
renderer={(currentActor, currentRouter) => {
|
|
272
|
+
void currentRouter;
|
|
273
|
+
return (
|
|
274
|
+
<div>
|
|
275
|
+
<Header actor={currentActor} />
|
|
276
|
+
<PlayRenderer actor={currentActor} components={components} />
|
|
277
|
+
<Footer />
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}}
|
|
281
|
+
/>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Header component also receives actor
|
|
286
|
+
function Header({ actor }: { actor: AbstractActor<any> }) {
|
|
287
|
+
const [route, setRoute] = useState<string | null>(null);
|
|
288
|
+
|
|
289
|
+
useSignalEffect(() => {
|
|
290
|
+
setRoute(actor.currentRoute.get());
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<header>
|
|
295
|
+
<nav>Current: {route}</nav>
|
|
296
|
+
</header>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Architecture
|
|
302
|
+
|
|
303
|
+
This package implements **Signal-Only Reactivity (INV-05)** and **Passive Infrastructure (INV-04)**:
|
|
304
|
+
|
|
305
|
+
1. **No Business Logic in React:**
|
|
306
|
+
- No useState/useReducer for business state
|
|
307
|
+
- No useEffect for side effects
|
|
308
|
+
- React only triggers renders, doesn't control state
|
|
309
|
+
|
|
310
|
+
2. **Signals as Source of Truth:**
|
|
311
|
+
- `actor.currentView.get()` provides UI structure
|
|
312
|
+
- `actor.currentRoute.get()` provides navigation state
|
|
313
|
+
- Components observe signals via `useSignalEffect`
|
|
314
|
+
|
|
315
|
+
3. **Event Forwarding:**
|
|
316
|
+
- Components receive `send` function via props
|
|
317
|
+
- User actions send events to actor (e.g., `{ type: "auth.login" }`)
|
|
318
|
+
- Actor guards validate and process events
|
|
319
|
+
|
|
320
|
+
4. **Microtask Batching:**
|
|
321
|
+
- `Signal.subtle.Watcher` coalesces rapid signal changes
|
|
322
|
+
- Prevents React thrashing from multiple signal updates
|
|
323
|
+
- Single React render per microtask batch
|
|
324
|
+
|
|
325
|
+
5. **Explicit Disposal Contract:**
|
|
326
|
+
- Component teardown must call watcher `unwatch` in cleanup
|
|
327
|
+
- Do not rely on GC-only cleanup
|
|
328
|
+
|
|
329
|
+
**Pattern:**
|
|
330
|
+
|
|
331
|
+
- Renderer receives actor via props (provider pattern)
|
|
332
|
+
- Enables composition with navigation, headers, footers
|
|
333
|
+
- Supports multiple renderers in same app
|
|
334
|
+
|
|
335
|
+
**Architectural Invariants:**
|
|
336
|
+
|
|
337
|
+
- **Signal-Only Reactivity (INV-05):** No React state for business logic
|
|
338
|
+
- **Passive Infrastructure (INV-04):** Components reflect, never decide
|
|
339
|
+
|
|
340
|
+
## Benefits
|
|
341
|
+
|
|
342
|
+
- **Framework Swappable:** Business logic has zero React imports
|
|
343
|
+
- **Type Safety:** Props validated against catalog schemas
|
|
344
|
+
- **Simple Testing:** Test actors without React renderer
|
|
345
|
+
- **Performance:** Microtask batching reduces unnecessary renders
|
|
346
|
+
- **Composability:** Renderer prop enables complex layouts ()
|
|
347
|
+
|
|
348
|
+
## Related Packages
|
|
349
|
+
|
|
350
|
+
- **[@xmachines/play-xstate](../play-xstate)** - XState adapter providing actors
|
|
351
|
+
- **[@xmachines/play-catalog](../play-catalog)** - UI schema validation
|
|
352
|
+
- **[@xmachines/play-tanstack-react-router](../play-tanstack-react-router)** - TanStack Router integration
|
|
353
|
+
- **[@xmachines/play-actor](../play-actor)** - Actor base
|
|
354
|
+
- **[@xmachines/play-signals](../play-signals)** - TC39 Signals primitives
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
MIT
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlayRenderer - Main React renderer component for XMachines Play architecture
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import React from "react";
|
|
7
|
+
import type { PlayRendererProps } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Main renderer component that subscribes to actor signals and renders UI
|
|
10
|
+
*
|
|
11
|
+
* Architecture (per RESEARCH.md Pattern 1):
|
|
12
|
+
* - Subscribes to actor.currentView signal via useSignalEffect
|
|
13
|
+
* - Dynamically renders catalog components based on view.component string
|
|
14
|
+
* - Forwards user events to actor via actor.send()
|
|
15
|
+
* - React state only for triggering renders, NOT business logic
|
|
16
|
+
*
|
|
17
|
+
* Invariant: Actor Authority - Actor decides all state transitions via guards.
|
|
18
|
+
* Invariant: Passive Infrastructure - Component observes signals and sends events.
|
|
19
|
+
* Invariant: Signal-Only Reactivity - Business logic state lives in actor signals.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { PlayRenderer } from "@xmachines/play-react";
|
|
24
|
+
* import { definePlayer } from "@xmachines/play-xstate";
|
|
25
|
+
*
|
|
26
|
+
* const actor = definePlayer({ machine, catalog })();
|
|
27
|
+
* actor.start();
|
|
28
|
+
*
|
|
29
|
+
* const components = {
|
|
30
|
+
* Dashboard: ({ userId, send }) => <div>User: {userId}</div>,
|
|
31
|
+
* LoginForm: ({ error, send }) => <form onSubmit={(e) => {
|
|
32
|
+
* e.preventDefault();
|
|
33
|
+
* send({ type: "intent", name: "login.submit", payload: {...} });
|
|
34
|
+
* }}>...</form>
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* <PlayRenderer actor={actor} components={components} />
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @param props - Component props
|
|
41
|
+
* @returns React element rendering current view from actor
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* **Component lookup:** Dynamically looks up component from `components` map
|
|
45
|
+
* using `view.component` string from actor.currentView signal.
|
|
46
|
+
*
|
|
47
|
+
* **Event forwarding:** Injects `send` function as prop to components. Components
|
|
48
|
+
* call `send(event)` to forward intents to actor. Actor guards decide validity.
|
|
49
|
+
*
|
|
50
|
+
* **Error handling:** If component not found in catalog, logs error and shows
|
|
51
|
+
* fallback. This indicates missing component registration, not runtime error.
|
|
52
|
+
*
|
|
53
|
+
* **CRITICAL:** Never call actor.send() during render - only in event handlers.
|
|
54
|
+
* Calling send during render causes infinite render loops.
|
|
55
|
+
*/
|
|
56
|
+
export declare const PlayRenderer: React.FC<PlayRendererProps>;
|
|
57
|
+
//# sourceMappingURL=PlayRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA2CpD,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* PlayRenderer - Main React renderer component for XMachines Play architecture
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
import React, { useState } from "react";
|
|
8
|
+
import { useSignalEffect } from "./useSignalEffect.js";
|
|
9
|
+
/**
|
|
10
|
+
* Main renderer component that subscribes to actor signals and renders UI
|
|
11
|
+
*
|
|
12
|
+
* Architecture (per RESEARCH.md Pattern 1):
|
|
13
|
+
* - Subscribes to actor.currentView signal via useSignalEffect
|
|
14
|
+
* - Dynamically renders catalog components based on view.component string
|
|
15
|
+
* - Forwards user events to actor via actor.send()
|
|
16
|
+
* - React state only for triggering renders, NOT business logic
|
|
17
|
+
*
|
|
18
|
+
* Invariant: Actor Authority - Actor decides all state transitions via guards.
|
|
19
|
+
* Invariant: Passive Infrastructure - Component observes signals and sends events.
|
|
20
|
+
* Invariant: Signal-Only Reactivity - Business logic state lives in actor signals.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { PlayRenderer } from "@xmachines/play-react";
|
|
25
|
+
* import { definePlayer } from "@xmachines/play-xstate";
|
|
26
|
+
*
|
|
27
|
+
* const actor = definePlayer({ machine, catalog })();
|
|
28
|
+
* actor.start();
|
|
29
|
+
*
|
|
30
|
+
* const components = {
|
|
31
|
+
* Dashboard: ({ userId, send }) => <div>User: {userId}</div>,
|
|
32
|
+
* LoginForm: ({ error, send }) => <form onSubmit={(e) => {
|
|
33
|
+
* e.preventDefault();
|
|
34
|
+
* send({ type: "intent", name: "login.submit", payload: {...} });
|
|
35
|
+
* }}>...</form>
|
|
36
|
+
* };
|
|
37
|
+
*
|
|
38
|
+
* <PlayRenderer actor={actor} components={components} />
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @param props - Component props
|
|
42
|
+
* @returns React element rendering current view from actor
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* **Component lookup:** Dynamically looks up component from `components` map
|
|
46
|
+
* using `view.component` string from actor.currentView signal.
|
|
47
|
+
*
|
|
48
|
+
* **Event forwarding:** Injects `send` function as prop to components. Components
|
|
49
|
+
* call `send(event)` to forward intents to actor. Actor guards decide validity.
|
|
50
|
+
*
|
|
51
|
+
* **Error handling:** If component not found in catalog, logs error and shows
|
|
52
|
+
* fallback. This indicates missing component registration, not runtime error.
|
|
53
|
+
*
|
|
54
|
+
* **CRITICAL:** Never call actor.send() during render - only in event handlers.
|
|
55
|
+
* Calling send during render causes infinite render loops.
|
|
56
|
+
*/
|
|
57
|
+
export const PlayRenderer = ({ actor, components, fallback = null, }) => {
|
|
58
|
+
// React state for triggering re-renders (NOT business logic state)
|
|
59
|
+
// Signal is source of truth, useState is just React's render trigger
|
|
60
|
+
const [view, setView] = useState(() => actor.currentView.get());
|
|
61
|
+
// Subscribe to signal changes
|
|
62
|
+
useSignalEffect(() => {
|
|
63
|
+
const currentView = actor.currentView.get();
|
|
64
|
+
setView(currentView);
|
|
65
|
+
});
|
|
66
|
+
// No view in current state
|
|
67
|
+
if (!view) {
|
|
68
|
+
return _jsx(_Fragment, { children: fallback });
|
|
69
|
+
}
|
|
70
|
+
// Handle null/undefined components catalog gracefully
|
|
71
|
+
if (!components) {
|
|
72
|
+
console.error(`Components catalog is ${components === null ? "null" : "undefined"}. ` +
|
|
73
|
+
`Cannot render component "${view.component}".`);
|
|
74
|
+
return _jsx(_Fragment, { children: fallback });
|
|
75
|
+
}
|
|
76
|
+
// Look up component from catalog
|
|
77
|
+
const Component = components[view.component];
|
|
78
|
+
if (!Component) {
|
|
79
|
+
console.error(`Component "${view.component}" not found in catalog. ` +
|
|
80
|
+
`Available components: ${Object.keys(components).join(", ")}`);
|
|
81
|
+
return _jsx(_Fragment, { children: fallback });
|
|
82
|
+
}
|
|
83
|
+
// Render with props from actor + send function
|
|
84
|
+
// bind(actor) ensures 'this' context is correct when components call send()
|
|
85
|
+
return _jsx(Component, { ...view.props, send: actor.send.bind(actor) });
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=PlayRenderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlayRenderer.js","sourceRoot":"","sources":["../src/PlayRenderer.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,CAAC,MAAM,YAAY,GAAgC,CAAC,EACzD,KAAK,EACL,UAAU,EACV,QAAQ,GAAG,IAAI,GACf,EAAE,EAAE;IACJ,mEAAmE;IACnE,qEAAqE;IACrE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC;IAEhE,8BAA8B;IAC9B,eAAe,CAAC,GAAG,EAAE;QACpB,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC;QAC5C,OAAO,CAAC,WAAW,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,2BAA2B;IAC3B,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACxB,CAAC;IAED,sDAAsD;IACtD,IAAI,CAAC,UAAU,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CACZ,yBAAyB,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,IAAI;YACtE,4BAA4B,IAAI,CAAC,SAAS,IAAI,CAC/C,CAAC;QACF,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACxB,CAAC;IAED,iCAAiC;IACjC,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE7C,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CACZ,cAAc,IAAI,CAAC,SAAS,0BAA0B;YACrD,yBAAyB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9D,CAAC;QACF,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACxB,CAAC;IAED,+CAA+C;IAC/C,4EAA4E;IAC5E,OAAO,KAAC,SAAS,OAAK,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAI,CAAC;AACpE,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xmachines/play-react - React renderer for XMachines Play architecture
|
|
3
|
+
*
|
|
4
|
+
* Provides a thin React rendering layer that passively observes actor signals
|
|
5
|
+
* and renders UI components from catalog definitions. This package enables
|
|
6
|
+
* framework-swappable architecture where React is just a rendering target
|
|
7
|
+
* that subscribes to signal changes.
|
|
8
|
+
*
|
|
9
|
+
* **Key principle:** React state is NEVER used for business logic—only for
|
|
10
|
+
* triggering React's render cycle. Signals are the source of truth.
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
* @module @xmachines/play-react
|
|
14
|
+
*/
|
|
15
|
+
export { PlayRenderer } from "./PlayRenderer.js";
|
|
16
|
+
export { useSignalEffect } from "./useSignalEffect.js";
|
|
17
|
+
export type { PlayRendererProps } from "./types.js";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xmachines/play-react - React renderer for XMachines Play architecture
|
|
3
|
+
*
|
|
4
|
+
* Provides a thin React rendering layer that passively observes actor signals
|
|
5
|
+
* and renders UI components from catalog definitions. This package enables
|
|
6
|
+
* framework-swappable architecture where React is just a rendering target
|
|
7
|
+
* that subscribes to signal changes.
|
|
8
|
+
*
|
|
9
|
+
* **Key principle:** React state is NEVER used for business logic—only for
|
|
10
|
+
* triggering React's render cycle. Signals are the source of truth.
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
* @module @xmachines/play-react
|
|
14
|
+
*/
|
|
15
|
+
// Main exports
|
|
16
|
+
export { PlayRenderer } from "./PlayRenderer.js";
|
|
17
|
+
export { useSignalEffect } from "./useSignalEffect.js";
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAe;AACf,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript type definitions for play-react
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import type { AbstractActor, Viewable } from "@xmachines/play-actor";
|
|
7
|
+
import type React from "react";
|
|
8
|
+
import type { AnyActorLogic } from "xstate";
|
|
9
|
+
/**
|
|
10
|
+
* Props for PlayRenderer component
|
|
11
|
+
*
|
|
12
|
+
* @property actor - Actor instance with currentView signal (requires Viewable capability)
|
|
13
|
+
* @property components - Map of component names to React components
|
|
14
|
+
* @property fallback - Optional component shown when currentView is null
|
|
15
|
+
*/
|
|
16
|
+
export interface PlayRendererProps {
|
|
17
|
+
/** Actor instance with currentView signal (requires Viewable capability) */
|
|
18
|
+
actor: AbstractActor<AnyActorLogic> & Viewable;
|
|
19
|
+
/** Map of component names to React components */
|
|
20
|
+
components: Record<string, React.ElementType>;
|
|
21
|
+
/** Optional component shown when currentView is null */
|
|
22
|
+
fallback?: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IACjC,4EAA4E;IAC5E,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;IAE/C,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAE9C,wDAAwD;IACxD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSignalEffect - React hook for signal subscriptions with automatic cleanup
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* React hook that subscribes to signal changes and runs effect callback
|
|
8
|
+
*
|
|
9
|
+
* Wraps Signal.subtle.Watcher to automatically track signal dependencies
|
|
10
|
+
* accessed in the callback. When any tracked signal changes, the callback
|
|
11
|
+
* re-runs and React component re-renders.
|
|
12
|
+
*
|
|
13
|
+
* Architecture (per RESEARCH.md Pattern 3):
|
|
14
|
+
* - Uses Signal.Computed to wrap callback for automatic dependency tracking
|
|
15
|
+
* - Signal.subtle.Watcher monitors the Computed for changes
|
|
16
|
+
* - Microtask batching coalesces rapid signal updates
|
|
17
|
+
* - Forces React re-render via useReducer when signals change
|
|
18
|
+
* - Handles cleanup on unmount to prevent memory leaks
|
|
19
|
+
*
|
|
20
|
+
* Invariant: Signal-Only Reactivity - Signals accessed in callback are watched.
|
|
21
|
+
* Invariant: Passive Infrastructure - React observes signals and does not control them.
|
|
22
|
+
*
|
|
23
|
+
* @param callback - Effect function that accesses signals. Can return cleanup function.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const MyComponent = ({ actor }) => {
|
|
28
|
+
* const [view, setView] = useState(null);
|
|
29
|
+
*
|
|
30
|
+
* // Subscribe to actor.currentView signal
|
|
31
|
+
* useSignalEffect(() => {
|
|
32
|
+
* const currentView = actor.currentView.get();
|
|
33
|
+
* setView(currentView);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* return <div>{view?.component}</div>;
|
|
37
|
+
* };
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* **CRITICAL:** Signals must be accessed unconditionally (no if statements).
|
|
42
|
+
* Conditional signal access breaks automatic dependency tracking.
|
|
43
|
+
*
|
|
44
|
+
* **Performance:** Microtask batching (queueMicrotask) prevents React thrashing
|
|
45
|
+
* when multiple signals update rapidly. Per signal-polyfill README pattern.
|
|
46
|
+
*
|
|
47
|
+
* **Why forceUpdate:** React needs useState/useReducer to trigger re-render cycle.
|
|
48
|
+
* Signal changes won't automatically re-render React components without forcing
|
|
49
|
+
* update via state change.
|
|
50
|
+
*
|
|
51
|
+
* **Implementation note:** We wrap the callback in Signal.Computed because
|
|
52
|
+
* Signal.subtle.Watcher cannot automatically track arbitrary function calls.
|
|
53
|
+
* The Computed handles dependency tracking, and the Watcher monitors it.
|
|
54
|
+
*/
|
|
55
|
+
export declare const useSignalEffect: (callback: () => void | (() => void)) => void;
|
|
56
|
+
//# sourceMappingURL=useSignalEffect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSignalEffect.d.ts","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,KAAG,IAuDrE,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSignalEffect - React hook for signal subscriptions with automatic cleanup
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useReducer } from "react";
|
|
7
|
+
import { Signal } from "@xmachines/play-signals";
|
|
8
|
+
/**
|
|
9
|
+
* Marker symbol returned by effect Computed to satisfy type requirements
|
|
10
|
+
* The actual value doesn't matter - we only care about side effects
|
|
11
|
+
*/
|
|
12
|
+
const EFFECT_RUN_MARKER = Symbol("effect-run");
|
|
13
|
+
/**
|
|
14
|
+
* React hook that subscribes to signal changes and runs effect callback
|
|
15
|
+
*
|
|
16
|
+
* Wraps Signal.subtle.Watcher to automatically track signal dependencies
|
|
17
|
+
* accessed in the callback. When any tracked signal changes, the callback
|
|
18
|
+
* re-runs and React component re-renders.
|
|
19
|
+
*
|
|
20
|
+
* Architecture (per RESEARCH.md Pattern 3):
|
|
21
|
+
* - Uses Signal.Computed to wrap callback for automatic dependency tracking
|
|
22
|
+
* - Signal.subtle.Watcher monitors the Computed for changes
|
|
23
|
+
* - Microtask batching coalesces rapid signal updates
|
|
24
|
+
* - Forces React re-render via useReducer when signals change
|
|
25
|
+
* - Handles cleanup on unmount to prevent memory leaks
|
|
26
|
+
*
|
|
27
|
+
* Invariant: Signal-Only Reactivity - Signals accessed in callback are watched.
|
|
28
|
+
* Invariant: Passive Infrastructure - React observes signals and does not control them.
|
|
29
|
+
*
|
|
30
|
+
* @param callback - Effect function that accesses signals. Can return cleanup function.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const MyComponent = ({ actor }) => {
|
|
35
|
+
* const [view, setView] = useState(null);
|
|
36
|
+
*
|
|
37
|
+
* // Subscribe to actor.currentView signal
|
|
38
|
+
* useSignalEffect(() => {
|
|
39
|
+
* const currentView = actor.currentView.get();
|
|
40
|
+
* setView(currentView);
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* return <div>{view?.component}</div>;
|
|
44
|
+
* };
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* **CRITICAL:** Signals must be accessed unconditionally (no if statements).
|
|
49
|
+
* Conditional signal access breaks automatic dependency tracking.
|
|
50
|
+
*
|
|
51
|
+
* **Performance:** Microtask batching (queueMicrotask) prevents React thrashing
|
|
52
|
+
* when multiple signals update rapidly. Per signal-polyfill README pattern.
|
|
53
|
+
*
|
|
54
|
+
* **Why forceUpdate:** React needs useState/useReducer to trigger re-render cycle.
|
|
55
|
+
* Signal changes won't automatically re-render React components without forcing
|
|
56
|
+
* update via state change.
|
|
57
|
+
*
|
|
58
|
+
* **Implementation note:** We wrap the callback in Signal.Computed because
|
|
59
|
+
* Signal.subtle.Watcher cannot automatically track arbitrary function calls.
|
|
60
|
+
* The Computed handles dependency tracking, and the Watcher monitors it.
|
|
61
|
+
*/
|
|
62
|
+
export const useSignalEffect = (callback) => {
|
|
63
|
+
// Force re-render when signal changes (React needs state change to re-render)
|
|
64
|
+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
let cleanup;
|
|
67
|
+
let needsEnqueue = true;
|
|
68
|
+
// Wrap callback in a Computed to automatically track dependencies
|
|
69
|
+
// The Computed will re-evaluate when any accessed signal changes
|
|
70
|
+
const effect = new Signal.Computed(() => {
|
|
71
|
+
// Run cleanup from previous effect
|
|
72
|
+
cleanup?.();
|
|
73
|
+
// Run user callback (may access signals, return cleanup)
|
|
74
|
+
cleanup = callback();
|
|
75
|
+
// Return marker value (Computed requires a return value)
|
|
76
|
+
return EFFECT_RUN_MARKER;
|
|
77
|
+
});
|
|
78
|
+
// Create watcher to detect when Computed needs re-evaluation
|
|
79
|
+
const watcher = new Signal.subtle.Watcher(() => {
|
|
80
|
+
// Batching flag prevents multiple microtasks for rapid changes
|
|
81
|
+
if (needsEnqueue) {
|
|
82
|
+
needsEnqueue = false;
|
|
83
|
+
// Schedule microtask to batch updates (prevent React thrashing)
|
|
84
|
+
queueMicrotask(() => {
|
|
85
|
+
needsEnqueue = true;
|
|
86
|
+
// Re-evaluate the Computed (runs callback with new signal values)
|
|
87
|
+
effect.get();
|
|
88
|
+
// Force React re-render
|
|
89
|
+
forceUpdate();
|
|
90
|
+
// Re-watch for next change
|
|
91
|
+
watcher.watch();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// Watch the Computed signal
|
|
96
|
+
watcher.watch(effect);
|
|
97
|
+
// Initial run of callback (via Computed)
|
|
98
|
+
effect.get();
|
|
99
|
+
// Cleanup on unmount
|
|
100
|
+
return () => {
|
|
101
|
+
cleanup?.();
|
|
102
|
+
// Unwatch to stop receiving signal updates
|
|
103
|
+
watcher.unwatch(effect);
|
|
104
|
+
};
|
|
105
|
+
}, [callback]);
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=useSignalEffect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSignalEffect.js","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD;;;GAGG;AACH,MAAM,iBAAiB,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAmC,EAAQ,EAAE;IAC5E,8EAA8E;IAC9E,MAAM,CAAC,EAAE,WAAW,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,OAA4B,CAAC;QACjC,IAAI,YAAY,GAAG,IAAI,CAAC;QAExB,kEAAkE;QAClE,iEAAiE;QACjE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;YACvC,mCAAmC;YACnC,OAAO,EAAE,EAAE,CAAC;YAEZ,yDAAyD;YACzD,OAAO,GAAG,QAAQ,EAAE,CAAC;YAErB,yDAAyD;YACzD,OAAO,iBAAiB,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,6DAA6D;QAC7D,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9C,+DAA+D;YAC/D,IAAI,YAAY,EAAE,CAAC;gBAClB,YAAY,GAAG,KAAK,CAAC;gBACrB,gEAAgE;gBAChE,cAAc,CAAC,GAAG,EAAE;oBACnB,YAAY,GAAG,IAAI,CAAC;oBAEpB,kEAAkE;oBAClE,MAAM,CAAC,GAAG,EAAE,CAAC;oBAEb,wBAAwB;oBACxB,WAAW,EAAE,CAAC;oBAEd,2BAA2B;oBAC3B,OAAO,CAAC,KAAK,EAAE,CAAC;gBACjB,CAAC,CAAC,CAAC;YACJ,CAAC;QACF,CAAC,CAAC,CAAC;QAEH,4BAA4B;QAC5B,OAAO,CAAC,KAAK,CAAC,MAAa,CAAC,CAAC;QAE7B,yCAAyC;QACzC,MAAM,CAAC,GAAG,EAAE,CAAC;QAEb,qBAAqB;QACrB,OAAO,GAAG,EAAE;YACX,OAAO,EAAE,EAAE,CAAC;YACZ,2CAA2C;YAC3C,OAAO,CAAC,OAAO,CAAC,MAAa,CAAC,CAAC;QAChC,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChB,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xmachines/play-react",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "React renderer for XMachines Play architecture with signal-driven rendering",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"actor",
|
|
7
|
+
"react",
|
|
8
|
+
"renderer",
|
|
9
|
+
"signals",
|
|
10
|
+
"state-machine",
|
|
11
|
+
"ui",
|
|
12
|
+
"xmachines"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "XMachines Contributors",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git@gitlab.com:xmachin-es/xmachines-js.git",
|
|
19
|
+
"directory": "packages/play-react"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc --build",
|
|
35
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:vitest": "vitest run",
|
|
39
|
+
"test:browser": "vitest run --browser.enabled --browser.name=chromium test/browser",
|
|
40
|
+
"prepublishOnly": "npm run build"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@xmachines/play-actor": "1.0.0-beta.1",
|
|
44
|
+
"@xmachines/play-catalog": "1.0.0-beta.1",
|
|
45
|
+
"@xmachines/play-signals": "1.0.0-beta.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@testing-library/react": "^16.3.2",
|
|
49
|
+
"@testing-library/user-event": "^14.6.1",
|
|
50
|
+
"@types/node": "^25.4.0",
|
|
51
|
+
"@types/react": "^19.2.14",
|
|
52
|
+
"@types/react-dom": "^19.2.3",
|
|
53
|
+
"jsdom": "^28.0.0",
|
|
54
|
+
"react": "^19.2.4",
|
|
55
|
+
"react-dom": "^19.2.4",
|
|
56
|
+
"typescript": "^5.7.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"react": "^18.2.0 || ^19.0.0",
|
|
60
|
+
"react-dom": "^18.2.0 || ^19.0.0"
|
|
61
|
+
},
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public"
|
|
64
|
+
}
|
|
65
|
+
}
|