@xmachines/play-dom 2.0.0-alpha.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,8 @@
1
- <!-- generated-by: gsd-doc-writer -->
2
-
3
1
  # @xmachines/play-dom
4
2
 
5
3
  Vanilla DOM renderer for XMachines Play architecture with signal-driven rendering.
6
4
 
7
- Part of the [XMachines Play monorepo](../../README.md).
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-2.1.0-blue)](https://www.npmjs.com/package/@xmachines/play-dom)
8
6
 
9
7
  ## Installation
10
8
 
@@ -15,14 +13,16 @@ pnpm add @xmachines/play-dom
15
13
  **Peer dependencies:**
16
14
 
17
15
  ```bash
18
- pnpm add xstate @xstate/store @xmachines/json-render-core @xmachines/json-render-xstate
16
+ pnpm add xstate @xstate/store @xmachines/json-render-core @xmachines/json-render-dom @xmachines/json-render-xstate
19
17
  ```
20
18
 
21
19
  ## Quick Start
22
20
 
23
21
  ```typescript
24
22
  import { createRenderer, schema } from "@xmachines/play-dom";
23
+ import { definePlayer } from "@xmachines/play-xstate";
25
24
  import { defineCatalog } from "@xmachines/json-render-core";
25
+ import { createMachine } from "xstate";
26
26
  import { z } from "zod";
27
27
  import type { ComponentFn } from "@xmachines/play-dom";
28
28
 
@@ -57,10 +57,36 @@ const Login: ComponentFn<typeof catalog, "Login"> = ({ props, on }) => {
57
57
  // 3. Build the factory once (module scope)
58
58
  const mount = createRenderer(catalog, { Home, Login });
59
59
 
60
- // 4. Mount when actor and container are ready
60
+ // 4. Create and start an actor states carry meta.view specs naming catalog components
61
+ const machine = createMachine({
62
+ initial: "home",
63
+ states: {
64
+ home: {
65
+ on: { "goto.login": "login" },
66
+ meta: {
67
+ view: {
68
+ root: "root",
69
+ elements: { root: { type: "Home", props: { title: "Home" }, children: [] } },
70
+ },
71
+ },
72
+ },
73
+ login: {
74
+ meta: {
75
+ view: {
76
+ root: "root",
77
+ elements: { root: { type: "Login", props: { title: "Login" }, children: [] } },
78
+ },
79
+ },
80
+ },
81
+ },
82
+ });
83
+ const actor = definePlayer({ machine })();
84
+ actor.start();
85
+
86
+ // 5. Mount when actor and container are ready
61
87
  const disconnect = mount(actor, document.getElementById("app")!);
62
88
 
63
- // 5. Cleanup on teardown
89
+ // 6. Cleanup on teardown
64
90
  disconnect();
65
91
  ```
66
92
 
@@ -68,7 +94,7 @@ disconnect();
68
94
 
69
95
  ### `createRenderer` — one-call factory (recommended)
70
96
 
71
- `createRenderer` is the simplest integration path. Call it once at module scope with your catalog and component map, then call the returned `mount` function for each actor/container pair.
97
+ `createRenderer` is the simplest integration path. Call it one time at module scope, with your catalog and your component map. Then call the `mount` function that it returns, one time for each pair of an actor and a container.
72
98
 
73
99
  ```typescript
74
100
  import { createRenderer, schema } from "@xmachines/play-dom";
@@ -78,16 +104,17 @@ const catalog = defineCatalog(schema, {/* ... */});
78
104
 
79
105
  const mount = createRenderer(catalog, { MyComponent });
80
106
 
107
+ // actor from the Quick Start
81
108
  const disconnect = mount(actor, document.getElementById("app")!);
82
109
  // Returns a cleanup function — call it to stop rendering and clear the container.
83
110
  disconnect();
84
111
  ```
85
112
 
86
- ### `createPlayUI` — batteries-included factory with full options
113
+ ### `createPlayUI` — the complete factory with every option
87
114
 
88
- Use `createPlayUI` when you need render error handling, a fallback element, navigation integration, computed functions, or custom validation or when you need to reference `registryResult` programmatically (e.g. for `executeAction`).
115
+ Use `createPlayUI` when you need a render error handler, a fallback element, a navigation integration, a computed function, or a custom check. Use it also when your code needs the `registryResult` value, for example for `executeAction`.
89
116
 
90
- Factory-level options (`functions`, `validationFunctions`, `navigate`, `onRenderError`, `fallback`) are closed over at creation time and applied on every `mount()` call. Per-mount options (`store`, `loading`) are passed to `mount()` itself.
117
+ The factory holds the factory options (`functions`, `validationFunctions`, `navigate`, `onRenderError`, and `fallback`) from the moment of its creation, and it applies them on every `mount()` call. Give the mount options (`store` and `loading`) to `mount()` itself.
91
118
 
92
119
  ```typescript
93
120
  import { defineRegistry, createPlayUI, schema } from "@xmachines/play-dom";
@@ -95,6 +122,7 @@ import { defineCatalog } from "@xmachines/json-render-core";
95
122
 
96
123
  const catalog = defineCatalog(schema, {/* ... */});
97
124
 
125
+ // Home, Login, actor from the Quick Start
98
126
  const registryResult = defineRegistry(catalog, {
99
127
  components: { Home, Login },
100
128
  actions: {
@@ -120,13 +148,15 @@ disconnect();
120
148
 
121
149
  ### `PlayRenderer` — class-based lifecycle control
122
150
 
123
- Use `PlayRenderer` directly when you need explicit `connect()` / `disconnect()` lifecycle control, or when integrating into a system that manages the renderer's lifetime externally.
151
+ Use `PlayRenderer` directly when you need the explicit `connect()` and `disconnect()` control, or when you integrate the renderer into a system that controls its life itself.
124
152
 
125
153
  ```typescript
126
154
  import { PlayRenderer, defineRegistry, schema } from "@xmachines/play-dom";
127
155
 
156
+ // catalog, actor from the Quick Start; components/actions as in the createPlayUI example
128
157
  const registryResult = defineRegistry(catalog, { components, actions });
129
158
 
159
+ // container: your mount element, e.g. document.getElementById("app")!
130
160
  const renderer = new PlayRenderer(container, actor, registryResult.registry, { registryResult });
131
161
 
132
162
  renderer.connect();
@@ -134,15 +164,16 @@ renderer.connect();
134
164
  renderer.disconnect();
135
165
  ```
136
166
 
137
- **Controlled store mode** supply an external `StateStore` so the renderer shares state with other parts of your app:
167
+ **Controlled store mode.** Give the renderer an external `StateStore`. The renderer then shares the state with the other parts of your application:
138
168
 
139
169
  ```typescript
140
170
  import { createAtom } from "@xstate/store";
141
171
  import { xstateStoreStateStore } from "@xmachines/json-render-xstate";
142
172
 
143
- const atom = createAtom({ username: "" });
173
+ const atom = createAtom<Record<string, unknown>>({ username: "" });
144
174
  const store = xstateStoreStateStore({ atom });
145
175
 
176
+ // container, actor, registryResult from the previous example
146
177
  const renderer = new PlayRenderer(container, actor, registryResult.registry, {
147
178
  registryResult,
148
179
  store,
@@ -152,11 +183,11 @@ renderer.connect();
152
183
 
153
184
  ## Provider Options
154
185
 
155
- All entry points (`createPlayUI`, `PlayRenderer`) accept the same set of UI-provider options via `UIProviderOptions`. These are forwarded into `DomRenderContext` on every render pass, making them available to component implementations via `ctx.ctx.*`.
186
+ Every entry point (`createPlayUI` and `PlayRenderer`) accepts the same UI-provider options, through `UIProviderOptions`. Each render pass puts the options into `DomRenderContext`. A component implementation therefore reads them at `ctx.ctx.*`.
156
187
 
157
188
  ### `functions` — named compute functions for `$computed` prop expressions
158
189
 
159
- Enables `{ "$computed": "name", "args": {...} }` dynamic prop values in specs. Each function receives the resolved `args` object and returns the computed value.
190
+ This option permits a dynamic prop value of the form `{ "$computed": "name", "args": {...} }` in a spec. Each function receives the resolved `args` object, and it returns the computed value.
160
191
 
161
192
  ```typescript
162
193
  const mount = createPlayUI(registryResult, {
@@ -167,13 +198,13 @@ const mount = createPlayUI(registryResult, {
167
198
  });
168
199
  ```
169
200
 
170
- Without `functions`, any `$computed` expression silently resolves to `undefined` (no throw, backward-compatible).
201
+ Without `functions`, every `$computed` expression resolves to `undefined`. Nothing throws, and the old behavior stays.
171
202
 
172
203
  ### `validationFunctions` — custom field validation
173
204
 
174
- Provides named validation functions for inline field validation within components. Functions receive `(value, args?)` and return `true` (valid) or `false` (invalid).
205
+ This option gives the named check functions for a field check inside a component. Each function receives `(value, args?)`. It returns `true` for a valid value, or `false` for an invalid value.
175
206
 
176
- Unlike the framework renderers, the DOM renderer has no automatic `ValidationProvider` tree. Components must invoke validation explicitly using `runValidationCheck` / `runValidation` from `@xmachines/json-render-core`, passing `ctx.ctx.validationFunctions` as `customFunctions`.
207
+ The DOM renderer has no `ValidationProvider` tree, and in this it is different from the framework renderers. Each component must call the check itself, with `runValidationCheck` or `runValidation` from `@xmachines/json-render-core`. Give `ctx.ctx.validationFunctions` to the call as `customFunctions`.
177
208
 
178
209
  ```typescript
179
210
  import { runValidationCheck } from "@xmachines/json-render-core";
@@ -185,19 +216,21 @@ const mount = createPlayUI(registryResult, {
185
216
  },
186
217
  });
187
218
 
188
- // Inside a ComponentFn:
189
- const MyField: ComponentFn<typeof catalog, "MyField"> = ({ ctx }) => {
219
+ // Inside a ComponentFn (catalog from the Quick Start):
220
+ const Login: ComponentFn<typeof catalog, "Login"> = ({ ctx }) => {
221
+ const someValue = 42; // the value to validate, e.g. read from an input
190
222
  const result = runValidationCheck(
191
223
  { type: "isEven", message: "must be even" },
192
224
  { value: someValue, stateModel: {}, customFunctions: ctx.ctx.validationFunctions },
193
225
  );
194
226
  // result.valid, result.message
227
+ return null;
195
228
  };
196
229
  ```
197
230
 
198
231
  ### `navigate` — programmatic navigation from action bindings
199
232
 
200
- A callback invoked when an action binding resolves with `onSuccess: { navigate: "/path" }`. The resolved path string is passed as the sole argument. Integrate with any router:
233
+ The renderer calls this callback when an action binding resolves with `onSuccess: { navigate: "/path" }`. The callback receives the resolved path string as its only argument. Use it with every router:
201
234
 
202
235
  ```typescript
203
236
  // React Router / TanStack Router / any push-based router:
@@ -219,17 +252,17 @@ With a spec binding:
219
252
  }
220
253
  ```
221
254
 
222
- When `submitForm` completes successfully, `navigate("/dashboard")` is called automatically.
255
+ `submitForm` completes without an error, and the renderer then calls `navigate("/dashboard")`.
223
256
 
224
- The function is also readable by component implementations directly via `ctx.ctx.navigate` for cases where navigation needs to be triggered outside of an action binding.
257
+ A component implementation can also read the function at `ctx.ctx.navigate`. Use this when a navigation must start outside an action binding.
225
258
 
226
259
  ### `onRenderError` — unified error handler
227
260
 
228
- Receives `(error, name)` for three distinct error classes:
261
+ The handler receives `(error, name)` for three different classes of error:
229
262
 
230
- - **Component render errors** — when a `ComponentFn` throws synchronously during `renderSpec`. `name` is the catalog component name (e.g. `"Home"`).
231
- - **Action handler rejections (emit path)** — when an `ActionFn` throws or returns a rejected promise during `emit()`. `name` is the catalog action name (e.g. `"submitForm"`).
232
- - **Action handler rejections (watch path)** — when an `ActionFn` rejects during a `watch` binding callback. `name` is the catalog action name.
263
+ - **A component render error** — a `ComponentFn` throws synchronously during `renderSpec`. `name` is then the catalog component name, for example `"Home"`.
264
+ - **An action handler rejection on the emit path** — an `ActionFn` throws, or it returns a rejected promise, during `emit()`. `name` is then the catalog action name, for example `"submitForm"`.
265
+ - **An action handler rejection on the watch path** — an `ActionFn` rejects during a `watch` binding callback. `name` is then the catalog action name.
233
266
 
234
267
  ```typescript
235
268
  const mount = createPlayUI(registryResult, {
@@ -240,19 +273,20 @@ const mount = createPlayUI(registryResult, {
240
273
  });
241
274
  ```
242
275
 
243
- The argument order matches the upstream `RenderErrorHandler` type from `@xmachines/json-render-core`: **error first, name second**. This is consistent with the framework renderers (`@xmachines/json-render-solid`, `@xmachines/json-render-react`).
276
+ The order of the arguments is the same as in the `RenderErrorHandler` type of `@xmachines/json-render-core`: **the error first, the name second**. The framework renderers (`@xmachines/json-render-solid` and `@xmachines/json-render-react`) use the same order.
244
277
 
245
- Without `onRenderError`, all three error types are logged via `console.error` and swallowed. No exception propagates, and no unhandled promise rejection is created.
278
+ Without `onRenderError`, the renderer writes all three types of error to `console.error`, then stops them. No exception goes to the caller, and no promise rejection stays unhandled.
246
279
 
247
- The handler is also available to component implementations via `ctx.ctx.onRenderError`, enabling components to route their own internal errors through the same channel:
280
+ A component implementation can also read the handler at `ctx.ctx.onRenderError`. A component therefore sends its own internal errors through the same channel:
248
281
 
249
282
  ```typescript
250
- const MyComponent: ComponentFn<typeof catalog, "MyComponent"> = ({ ctx }) => {
283
+ // catalog from the Quick Start; doSomethingRisky: your render logic that may throw
284
+ const Home: ComponentFn<typeof catalog, "Home"> = ({ ctx }) => {
251
285
  try {
252
286
  const el = doSomethingRisky();
253
287
  return el;
254
288
  } catch (err) {
255
- ctx.ctx.onRenderError?.(err, "MyComponent");
289
+ ctx.ctx.onRenderError?.(err, "Home");
256
290
  return null;
257
291
  }
258
292
  };
@@ -262,42 +296,42 @@ const MyComponent: ComponentFn<typeof catalog, "MyComponent"> = ({ ctx }) => {
262
296
 
263
297
  ### XMachines Layer
264
298
 
265
- | Export | Kind | Description |
266
- | ---------------------------------------- | -------- | --------------------------------------------------------------------------- |
267
- | `createRenderer(catalog, components)` | function | One-call factory returns `mount(actor, container, options?) → disconnect` |
268
- | `createPlayUI(registryResult, options?)` | function | Batteries-included factory returns `MountFn` |
269
- | `PlayRenderer` | class | Class-based renderer with `connect()` / `disconnect()` lifecycle |
270
- | `defineRegistry(catalog, options)` | function | Build a catalog-typed `DomRegistry` with typed handlers |
271
- | `renderSpec(...)` | function | Pure Spec → DOM renderer (low-level) |
272
- | `schema` | const | The `@xmachines/json-render-dom` schema — pass to `defineCatalog()` |
299
+ | Export | Kind | Description |
300
+ | ---------------------------------------- | -------- | --------------------------------------------------------------------------------- |
301
+ | `createRenderer(catalog, components)` | function | The one-call factory. It returns `mount(actor, container, options?) → disconnect` |
302
+ | `createPlayUI(registryResult, options?)` | function | The complete factory. It returns a `MountFn` |
303
+ | `PlayRenderer` | class | The renderer class, with a `connect()` and `disconnect()` lifecycle |
304
+ | `defineRegistry(catalog, options)` | function | Build a catalog-typed `DomRegistry` with typed handlers |
305
+ | `renderSpec(...)` | function | The pure low-level Spec → DOM renderer |
306
+ | `schema` | const | The `@xmachines/json-render-dom` schema — pass to `defineCatalog()` |
273
307
 
274
308
  ### Key Types
275
309
 
276
- | Type | Description |
277
- | ------------------------ | ------------------------------------------------------------------------------------------ |
278
- | `ComponentFn<C, K>` | Catalog-typed component function — returns `HTMLElement \| Text \| null` |
279
- | `ComponentContext<C, K>` | Context passed to each component: `props`, `children`, `emit`, `on`, `bindings`, `ctx` |
280
- | `ActionFn<C, K>` | Catalog-typed action function — receives `(params, setState, state)` |
281
- | `EventHandle` | Handle returned by `on(eventName)` has `emit()`, `shouldPreventDefault`, `bound` |
282
- | `SetState` | State updater: `(prev => next) => void` |
283
- | `DefineRegistryResult` | Result from `defineRegistry` has `registry`, `handlers`, `executeAction` |
284
- | `PlayDomOptions` | Options for `PlayRenderer` — extends `UIProviderOptions` |
285
- | `CreatePlayUIOptions` | Options for `createPlayUI` — extends `UIProviderOptions`, adds `fallback` |
286
- | `MountOptions` | Per-mount options for `MountFn`: `store`, `loading` |
287
- | `MountFn` | The mount function returned by `createPlayUI`: `(actor, container, options?) → disconnect` |
288
- | `UIProviderOptions` | Shared options: `functions`, `validationFunctions`, `navigate`, `onRenderError` |
289
- | `BaseComponentProps<P>` | Catalog-agnostic component props for shared component libraries |
290
- | `DomRegistry` | Raw registry type: `Record<string, DomComponentRenderer>` |
291
- | `DomSchema` | Type of the `schema` export |
292
- | `ComputedFunction` | Type for named compute functions used with the `functions` option |
293
-
294
- ## Rendering Behaviour
295
-
296
- - **Initial render is synchronous** — the container is populated before `connect()` returns.
297
- - **Signal-driven re-renders are microtask-deferred** — `watchSignal` schedules updates on the next microtask queue tick.
298
- - **Null view** clears the container. A `fallback` element can be shown on initial mount when the view is `null`; it is **not** re-appended if the view later transitions back to `null` after a non-null view.
299
- - **Double `connect()` is safe** — calling `connect()` on an already-connected renderer auto-disconnects first.
300
- - **`disconnect()` clears the container** and unsubscribes all signal and store watchers.
310
+ | Type | Description |
311
+ | ------------------------ | --------------------------------------------------------------------------------------------- |
312
+ | `ComponentFn<C, K>` | Catalog-typed component function — returns `HTMLElement \| Text \| null` |
313
+ | `ComponentContext<C, K>` | The context of each component: `props`, `children`, `emit`, `on`, `bindings`, and `ctx` |
314
+ | `ActionFn<C, K>` | Catalog-typed action function — receives `(params, setState, state)` |
315
+ | `EventHandle` | The handle that `on(eventName)` returns. It has `emit()`, `shouldPreventDefault`, and `bound` |
316
+ | `SetState` | State updater: `(prev => next) => void` |
317
+ | `DefineRegistryResult` | The result of `defineRegistry`. It has `registry`, `handlers`, and `executeAction` |
318
+ | `PlayDomOptions` | Options for `PlayRenderer` — extends `UIProviderOptions` |
319
+ | `CreatePlayUIOptions` | Options for `createPlayUI` — extends `UIProviderOptions`, adds `fallback` |
320
+ | `MountOptions` | Per-mount options for `MountFn`: `store`, `loading` |
321
+ | `MountFn` | The mount function that `createPlayUI` returns: `(actor, container, options?) → disconnect` |
322
+ | `UIProviderOptions` | Shared options: `functions`, `validationFunctions`, `navigate`, `onRenderError` |
323
+ | `BaseComponentProps<P>` | Catalog-agnostic component props for shared component libraries |
324
+ | `DomRegistry` | Raw registry type: `Record<string, DomComponentRenderer>` |
325
+ | `DomSchema` | Type of the `schema` export |
326
+ | `ComputedFunction` | The type of a named compute function for the `functions` option |
327
+
328
+ ## Rendering Behavior
329
+
330
+ - **The first render is synchronous** — the renderer fills the container before `connect()` returns.
331
+ - **A signal-driven render waits for a microtask** — `watchSignal` puts each update on the next tick of the microtask queue.
332
+ - **A null view** clears the container. The renderer can show a `fallback` element on the first mount, when the view is `null`. It does **not** add that element again when the view returns to `null` after a view that was not null.
333
+ - **A second `connect()` is safe** — a `connect()` call on a connected renderer disconnects it first.
334
+ - **`disconnect()` clears the container** and cancels every signal watcher and store watcher.
301
335
 
302
336
  ## Testing
303
337
 
@@ -309,4 +343,4 @@ pnpm test
309
343
  pnpm run test:coverage
310
344
  ```
311
345
 
312
- Tests live in `test/` and use [Vitest](https://vitest.dev/) with a jsdom environment. Coverage thresholds: 80% lines, functions, branches, and statements.
346
+ The tests are in `test/`. They use [Vitest](https://vitest.dev/) in a jsdom environment. The coverage thresholds are 80% for lines, functions, branches, and statements.
@@ -1,38 +1,41 @@
1
1
  /**
2
- * PlayRenderer.ts — XMachines wrapper around @xmachines/json-render-dom.
2
+ * PlayRenderer.ts — the XMachines wrapper of @xmachines/json-render-dom.
3
3
  *
4
- * Bridges actor.currentView (TC39 Signal) to the inner DOM renderer using watchSignal.
5
- * Exposes connect() / disconnect() methods following the same pattern as
6
- * play-react, play-vue, and play-solid PlayRenderer classes.
4
+ * It connects actor.currentView, a TC39 Signal, to the DOM renderer below it, with
5
+ * watchSignal. It gives you the connect() method and the disconnect() method, in
6
+ * the same pattern as the PlayRenderer class of play-react, of play-vue, and of
7
+ * play-solid.
7
8
  *
8
- * State store: uses external `store` option if provided (controlled mode); otherwise
9
- * creates a fresh @xstate/store atom per view transition seeded from spec.state.
9
+ * The state store: the renderer uses the external `store` option when the caller
10
+ * gives one, and this is the controlled mode. Without that option, it makes a new
11
+ * @xstate/store atom for each view transition, with the values of spec.state.
10
12
  */
11
13
  import type { AbstractActor, Viewable } from "@xmachines/play-actor";
12
14
  import type { AnyActorLogic } from "xstate";
13
15
  import type { DomRegistry } from "@xmachines/json-render-dom";
14
16
  import type { PlayDomOptions } from "./types.js";
15
17
  /**
16
- * PlayRenderer connects an actor's `currentView` signal to the DOM renderer.
18
+ * PlayRenderer connects the `currentView` signal of an actor to the DOM renderer.
17
19
  *
18
- * Watches `actor.currentView` via TC39 Signals and renders `DomComponentRenderer`
19
- * functions into `container` on every view transition. Cleared on `disconnect()`.
20
+ * It watches `actor.currentView` through the TC39 Signals. It renders each
21
+ * `DomComponentRenderer` function into `container` on every view transition.
22
+ * `disconnect()` clears the container.
20
23
  *
21
- * Options from `PlayDomOptions` (which extends `UIProviderOptions`) are all forwarded
22
- * into `DomRenderContext` on every render pass:
23
- * - `functions` — named compute functions for `{ $computed: "name" }` prop expressions
24
- * - `directives` — custom `$`-prefixed dynamic values (from `defineDirective`), resolved during prop resolution
25
- * - `validationFunctions` — custom validation functions, available at `ctx.ctx.validationFunctions`
26
- * - `navigate` — navigation callback, invoked on `onSuccess: { navigate: "..." }` action bindings
27
- * - `onRenderError` — called with `(error, name)` for component render errors and action handler rejections
24
+ * Every option of `PlayDomOptions`, which extends `UIProviderOptions`, goes into
25
+ * `DomRenderContext` on each render pass:
26
+ * - `functions` — the named compute functions of a `{ $computed: "name" }` prop expression
27
+ * - `directives` — your own dynamic values with a `$` prefix, from `defineDirective`. The renderer resolves them with the props
28
+ * - `validationFunctions` — your own check functions. They are available at `ctx.ctx.validationFunctions`
29
+ * - `navigate` — the navigation callback. The renderer calls it for an `onSuccess: { navigate: "..." }` action binding
30
+ * - `onRenderError` — the renderer calls it with `(error, name)` for a component render error and for an action handler rejection
28
31
  *
29
- * **Preferred usage via `registryResult`:**
32
+ * **The preferred use, with `registryResult`:**
30
33
  * ```typescript
31
34
  * import { PlayRenderer, defineRegistry } from "@xmachines/play-dom";
32
35
  *
33
36
  * const registryResult = defineRegistry(catalog, { components, actions });
34
37
  * const renderer = new PlayRenderer(container, actor, registryResult.registry, {
35
- * registryResult, // wires setState/getState from xstate store automatically
38
+ * registryResult, // it connects setState and getState of the xstate store for you
36
39
  * navigate: (path) => myRouter.push(path),
37
40
  * functions: { fullName: (args) => `${args.first} ${args.last}` },
38
41
  * });
@@ -41,7 +44,7 @@ import type { PlayDomOptions } from "./types.js";
41
44
  * renderer.disconnect();
42
45
  * ```
43
46
  *
44
- * **Controlled store mode** — bring your own `StateStore`:
47
+ * **The controlled store mode** — you give your own `StateStore`:
45
48
  * ```typescript
46
49
  * import { createAtom } from "@xstate/store";
47
50
  * import { xstateStoreStateStore } from "@xmachines/json-render-xstate";
@@ -52,8 +55,9 @@ import type { PlayDomOptions } from "./types.js";
52
55
  * renderer.connect();
53
56
  * ```
54
57
  *
55
- * Double `connect()` is safe calling `connect()` while already connected
56
- * automatically disconnects first, preventing double-render subscriptions.
58
+ * A second `connect()` is safe. A `connect()` call on a connected renderer
59
+ * disconnects it first. Therefore the renderer holds no second render
60
+ * subscription.
57
61
  */
58
62
  export declare class PlayRenderer {
59
63
  private readonly container;
@@ -63,59 +67,96 @@ export declare class PlayRenderer {
63
67
  private unwatch;
64
68
  private storeUnsubscribe;
65
69
  /**
66
- * Live watch-subscription cleanups. A `Set` (not an array) so that each
67
- * registered cleanup can delete ITSELF on invocation: the incremental
68
- * renderer releases a watcher when its element unmounts, and the registered
69
- * wrapper is what it invokes so the Set shrinks in lock-step with live
70
- * subscriptions instead of accumulating one spent closure per historical
71
- * mount over a long-lived view.
70
+ * The cleanup functions of the live watch subscriptions. This field is a `Set`,
71
+ * and not an array, so that each registered cleanup function can delete ITSELF on
72
+ * its call: the incremental renderer releases a watcher when its element unmounts,
73
+ * and it calls the registered wrapper. The Set therefore shrinks with the live
74
+ * subscriptions. It does not collect one spent closure for each historical mount of
75
+ * a view with a long life.
72
76
  */
73
77
  private watchCleanups;
74
78
  /**
75
- * Set to `false` in `disconnect()` before calling `storeUnsub()`.
76
- * The `rerender` closure checks this flag at entry so that any synchronous
77
- * callback fired by the `StateStore` implementation within its own
78
- * `unsubscribe()` call (an edge-case but valid contract) does not mutate a
79
- * detached DOM tree.
79
+ * The viewKey of the view on the screen now, and the store behind it, without the
80
+ * guard. An emission with the same key means that only the /context projection
81
+ * moved: the fast path for a slice in render() refreshes the store in place, and it
82
+ * does not remove the tree. The ephemeral view state, such as a draft or a toggle,
83
+ * therefore stays. A null emission, from a state with no meta.view, is a GAP, and
84
+ * not a new view: the DOM goes away, but the identity and the store stay. A return
85
+ * to the same view therefore builds again around the ephemeral state that survived,
86
+ * in the same way as the framework providers. `disconnect()` resets both fields,
87
+ * because a store that stays alive must not outlive the connection.
88
+ */
89
+ private lastViewKey;
90
+ private currentStore;
91
+ /**
92
+ * The shared coordinator of the store lifecycle, from @xmachines/play-actor. It
93
+ * seeds the store again on a change of the viewKey, it refreshes /context in place
94
+ * in every other case, and it guards the identity cache. The two fields above,
95
+ * lastViewKey and currentStore, stay as the DOM-side record for the fast path of a
96
+ * slice. That path is about the live TREE, which is the spec and the subscription,
97
+ * and not about the store.
98
+ */
99
+ private readonly storeLifecycle;
100
+ /**
101
+ * `disconnect()` sets this field to `false` before it calls `storeUnsub()`.
102
+ * The `rerender` closure reads the flag at its start. Therefore a synchronous
103
+ * callback from the `StateStore` implementation inside its own `unsubscribe()`
104
+ * call, which is rare but correct, changes no detached DOM tree.
80
105
  */
81
106
  private alive;
82
107
  /**
83
- * @param container - `HTMLElement` to render into. Cleared and repopulated on every view transition.
84
- * @param actor - Actor providing the `currentView` signal (must implement `Viewable`).
85
- * @param registry - Component renderer map typically `registryResult.registry` from `defineRegistry`.
86
- * @param options - Configuration (see {@link PlayDomOptions}):
87
- * - `registryResult` — auto-wires `setState`/`state` from the xstate store.
88
- * - `store` — external `StateStore` (controlled mode; overrides `spec.state` seeding).
89
- * - `loading` — streaming mode flag; suppresses missing-child warnings.
90
- * - `functions` — named compute functions for `$computed` prop expressions.
91
- * - `directives` — custom `$`-prefixed dynamic values resolved during prop resolution.
92
- * - `validationFunctions` — custom validation functions; available at `ctx.ctx.validationFunctions`.
93
- * - `navigate` — navigation callback; invoked on `onSuccess: { navigate: "..." }`.
94
- * - `onRenderError` — `(error, name)` handler for component render errors and action handler rejections; suppresses `console.error` fallback.
108
+ * @param container - The `HTMLElement` to render into. Each view transition clears it and fills it again.
109
+ * @param actor - The actor with the `currentView` signal. It must implement `Viewable`.
110
+ * @param registry - The map of the component renderers, usually `registryResult.registry` from `defineRegistry`.
111
+ * @param options - The configuration. See {@link PlayDomOptions}:
112
+ * - `registryResult` — it connects `setState` and `state` of the xstate store for you.
113
+ * - `store` — an external `StateStore`, which is the controlled mode. It replaces the values of `spec.state`.
114
+ * - `loading` — the flag of the streaming mode. It stops each warning about an absent child.
115
+ * - `functions` — the named compute functions of a `$computed` prop expression.
116
+ * - `directives` — your own dynamic values with a `$` prefix. The renderer resolves them with the props.
117
+ * - `validationFunctions` — your own check functions. They are available at `ctx.ctx.validationFunctions`.
118
+ * - `navigate` — the navigation callback. The renderer calls it for `onSuccess: { navigate: "..." }`.
119
+ * - `onRenderError` — the `(error, name)` handler of a component render error and of an action handler rejection. It stops the `console.error` fallback.
95
120
  */
96
121
  constructor(container: HTMLElement, actor: AbstractActor<AnyActorLogic> & Viewable, registry: DomRegistry, options?: PlayDomOptions);
97
122
  /**
98
- * Start watching actor.currentView and render to container.
99
- * Renders the initial view synchronously, then subscribes to signal changes.
123
+ * Starts the watch of actor.currentView, and renders into the container.
124
+ * It renders the first view synchronously, then it subscribes to the signal changes.
100
125
  *
101
- * Calling `connect()` on an already-connected renderer (where a previous
102
- * `connect()` was never followed by `disconnect()`) would silently install a
103
- * second `watchSignal` subscription, causing double-renders on every view
104
- * change. Guard against this by auto-disconnecting first.
126
+ * A `connect()` call on a connected renderer, where a `disconnect()` call did not
127
+ * follow the previous `connect()`, installs a second `watchSignal` subscription.
128
+ * Each view change then renders two times. Therefore this method disconnects the
129
+ * renderer first.
105
130
  */
106
131
  connect(): void;
107
132
  /**
108
- * Stop watching and clear the container.
133
+ * Stops the watch and clears the container.
109
134
  */
110
135
  disconnect(): void;
111
136
  /**
112
- * Number of live watch-subscription cleanups currently retained.
137
+ * The number of live watch-subscription cleanup functions now.
113
138
  *
114
- * @internal Test-only accessor for asserting that the watch-cleanup
115
- * collection tracks live registrations (no per-mount leak). Not part of the
116
- * public API and may change without notice.
139
+ * @internal This accessor is for a test only. A test asserts with it that the
140
+ * collection of the watch cleanups holds the live registrations, and that no mount
141
+ * leaks. It is not part of the public API, and it can change without a notice.
117
142
  */
118
143
  get watchCleanupCount(): number;
119
144
  private render;
145
+ /**
146
+ * A rebuild that fails must not leave the guard of the fast path armed on a dead
147
+ * tree, because the container is empty already. Therefore this code resets the view
148
+ * identity. The next emission, also one with the same viewKey, then makes a
149
+ * complete render again. It does not patch a store that nothing shows.
150
+ */
151
+ private resetFailedRebuild;
152
+ /**
153
+ * The tail of a complete rebuild, which can throw. See the catch block of render().
154
+ *
155
+ * @param guardedStore - Everything that the spec can write through, which includes
156
+ * $bindState, setState, and a chained set, receives the GUARDED store: /context is
157
+ * read-only to the spec, because the machine context changes through an event
158
+ * only.
159
+ */
160
+ private renderView;
120
161
  }
121
162
  //# sourceMappingURL=PlayRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAY,MAAM,uBAAuB,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAG5C,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,YAAY;IAoCvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAtCzB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,gBAAgB,CAA6B;IACrD;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa,CAAyB;IAC9C;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,CAAQ;IAErB;;;;;;;;;;;;;OAaG;gBAEe,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,EAC9C,QAAQ,EAAE,WAAW,EACrB,OAAO,GAAE,cAAmB;IAG9C;;;;;;;;OAQG;IACH,OAAO,IAAI,IAAI;IAMf;;OAEG;IACH,UAAU,IAAI,IAAI;IAmBlB;;;;;;OAMG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,OAAO,CAAC,MAAM;CAuId"}
1
+ {"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAY,MAAM,uBAAuB,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAG5C,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,qBAAa,YAAY;IA2DvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IA7DzB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,gBAAgB,CAA6B;IACrD;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa,CAAyB;IAC9C;;;;;;;;;;OAUG;IACH,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,YAAY,CAA2B;IAC/C;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE7B;IACF;;;;;OAKG;IACH,OAAO,CAAC,KAAK,CAAQ;IAErB;;;;;;;;;;;;;OAaG;gBAEe,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,EAC9C,QAAQ,EAAE,WAAW,EACrB,OAAO,GAAE,cAAmB;IAG9C;;;;;;;;OAQG;IACH,OAAO,IAAI,IAAI;IAMf;;OAEG;IACH,UAAU,IAAI,IAAI;IAyBlB;;;;;;OAMG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,OAAO,CAAC,MAAM;IAqEd;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;CA+GlB"}