@xmachines/play-dom 6.1.0 → 7.0.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Vanilla DOM renderer for XMachines Play architecture with atom-driven rendering.
4
4
 
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-6.1.0-blue)](https://www.npmjs.com/package/@xmachines/play-dom)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-7.0.0-blue)](https://www.npmjs.com/package/@xmachines/play-dom)
6
6
 
7
7
  ## Installation
8
8
 
@@ -13,9 +13,11 @@ pnpm add @xmachines/play-dom
13
13
  **Peer dependencies:**
14
14
 
15
15
  ```bash
16
- pnpm add @xmachines/core @xmachines/play @xmachines/play-atom @xstate/store @xmachines/json-render-core @xmachines/json-render-dom @xmachines/json-render-xstate
16
+ pnpm add @xmachines/core @xmachines/play @xmachines/play-atom @xstate/store @xmachines/json-render-core@0.21.0-xm.6 @xmachines/json-render-dom@0.21.0-xm.6 @xmachines/json-render-xstate@0.21.0-xm.6
17
17
  ```
18
18
 
19
+ The json-render packages are exact peers: install the version that this release names, `0.21.0-xm.6`.
20
+
19
21
  [`@xmachines/play`](../play/README.md) is the peer of [`@xmachines/play-view`](../play-view/README.md), which this package depends on. Install ONE copy of it.
20
22
 
21
23
  `@xstate/store` is the peer of [`@xmachines/play-atom`](../play-atom/README.md), and not of this
@@ -104,7 +106,7 @@ disconnect();
104
106
 
105
107
  `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.
106
108
 
107
- ```typescript
109
+ ```typescript fragment
108
110
  import { createRenderer, schema } from "@xmachines/play-dom";
109
111
  import { defineCatalog } from "@xmachines/json-render-core";
110
112
 
@@ -122,9 +124,9 @@ disconnect();
122
124
 
123
125
  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`.
124
126
 
125
- The factory holds the factory options (`functions`, `validationFunctions`, `navigate`, `onRenderError`, `onError`, 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.
127
+ The factory holds the factory options (`functions`, `directives`, `validationFunctions`, `navigate`, `onConfirm`, `confirmDialog`, `onRenderError`, `onError`, 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.
126
128
 
127
- ```typescript
129
+ ```typescript fragment
128
130
  import { defineRegistry, createPlayUI, schema } from "@xmachines/play-dom";
129
131
  import { defineCatalog } from "@xmachines/json-render-core";
130
132
 
@@ -159,12 +161,14 @@ disconnect();
159
161
  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.
160
162
 
161
163
  ```typescript
162
- import { PlayRenderer, defineRegistry, schema } from "@xmachines/play-dom";
164
+ import { PlayRenderer, type DefineRegistryResult } from "@xmachines/play-dom";
165
+ import type { ViewActor } from "@xmachines/play-view";
163
166
 
164
- // catalog, actor from the Quick Start; components/actions as in the createPlayUI example
165
- const registryResult = defineRegistry(catalog, { components, actions });
167
+ declare const actor: ViewActor; // the actor of the Quick Start
168
+ // The result of defineRegistry(catalog, { components, actions }), as in the createPlayUI example
169
+ declare const registryResult: DefineRegistryResult;
166
170
 
167
- // container: your mount element, e.g. document.getElementById("app")!
171
+ const container = document.getElementById("app")!;
168
172
  const renderer = new PlayRenderer(container, actor, registryResult.registry, { registryResult });
169
173
 
170
174
  renderer.connect();
@@ -172,16 +176,21 @@ renderer.connect();
172
176
  renderer.disconnect();
173
177
  ```
174
178
 
175
- **Controlled store mode.** Give the renderer an external `StateStore`. The renderer then shares the state with the other parts of your application:
179
+ **Controlled store mode.** Give the renderer an external `StateStore`. The renderer then shares the state with the other parts of your application. The tree lives as long as the store, so a new `viewKey` updates the tree in place, as the framework renderers do:
176
180
 
177
181
  ```typescript
182
+ import { PlayRenderer, type DefineRegistryResult } from "@xmachines/play-dom";
178
183
  import { createAtom } from "@xmachines/play-atom";
179
184
  import { xstateStoreStateStore } from "@xmachines/json-render-xstate";
185
+ import type { ViewActor } from "@xmachines/play-view";
186
+
187
+ declare const actor: ViewActor; // the actor of the Quick Start
188
+ declare const registryResult: DefineRegistryResult; // from the previous example
180
189
 
181
190
  const atom = createAtom<Record<string, unknown>>({ username: "" });
182
191
  const store = xstateStoreStateStore({ atom });
183
192
 
184
- // container, actor, registryResult from the previous example
193
+ const container = document.getElementById("app")!;
185
194
  const renderer = new PlayRenderer(container, actor, registryResult.registry, {
186
195
  registryResult,
187
196
  store,
@@ -198,6 +207,10 @@ Every entry point (`createPlayUI` and `PlayRenderer`) accepts the same UI-provid
198
207
  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.
199
208
 
200
209
  ```typescript
210
+ import { createPlayUI, type DefineRegistryResult } from "@xmachines/play-dom";
211
+
212
+ declare const registryResult: DefineRegistryResult; // from defineRegistry(catalog, ...)
213
+
201
214
  const mount = createPlayUI(registryResult, {
202
215
  functions: {
203
216
  fullName: (args) => `${args.first} ${args.last}`,
@@ -215,7 +228,12 @@ This option gives the named check functions for a field check inside a component
215
228
  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`.
216
229
 
217
230
  ```typescript
218
- import { runValidationCheck } from "@xmachines/json-render-core";
231
+ import { createPlayUI, schema } from "@xmachines/play-dom";
232
+ import type { ComponentFn, DefineRegistryResult } from "@xmachines/play-dom";
233
+ import { defineCatalog, runValidationCheck } from "@xmachines/json-render-core";
234
+ import { z } from "zod";
235
+
236
+ declare const registryResult: DefineRegistryResult; // from defineRegistry(catalog, ...)
219
237
 
220
238
  const mount = createPlayUI(registryResult, {
221
239
  validationFunctions: {
@@ -224,12 +242,17 @@ const mount = createPlayUI(registryResult, {
224
242
  },
225
243
  });
226
244
 
227
- // Inside a ComponentFn (catalog from the Quick Start):
228
- const Login: ComponentFn<typeof catalog, "Login"> = ({ ctx }) => {
245
+ const catalog = defineCatalog(schema, {
246
+ components: { Login: { props: z.object({ title: z.string() }) } },
247
+ actions: {},
248
+ });
249
+
250
+ // Inside a ComponentFn:
251
+ const Login: ComponentFn<typeof catalog, "Login"> = (ctx) => {
229
252
  const someValue = 42; // the value to validate, e.g. read from an input
230
253
  const result = runValidationCheck(
231
254
  { type: "isEven", message: "must be even" },
232
- { value: someValue, stateModel: {}, customFunctions: ctx.ctx.validationFunctions },
255
+ { value: someValue, stateModel: {}, customFunctions: ctx.ctx.validationFunctions ?? {} },
233
256
  );
234
257
  // result.valid, result.message
235
258
  return null;
@@ -241,7 +264,13 @@ const Login: ComponentFn<typeof catalog, "Login"> = ({ ctx }) => {
241
264
  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:
242
265
 
243
266
  ```typescript
267
+ import { createPlayUI, type DefineRegistryResult } from "@xmachines/play-dom";
268
+
269
+ declare const registryResult: DefineRegistryResult; // from defineRegistry(catalog, ...)
270
+
244
271
  // React Router / TanStack Router / any push-based router:
272
+ declare const myRouter: { push(path: string): void };
273
+
245
274
  const mount = createPlayUI(registryResult, {
246
275
  navigate: (path) => myRouter.push(path),
247
276
  });
@@ -264,26 +293,53 @@ With a spec binding:
264
293
 
265
294
  A component implementation can also read the function at `ctx.ctx.navigate`. Use this when a navigation must start outside an action binding.
266
295
 
296
+ ### `onConfirm` and `confirmDialog` — the bindings with `confirm`
297
+
298
+ An action binding with a `confirm` field waits for the user. Without `onConfirm`, the default confirm dialog of `@xmachines/json-render-dom` shows the binding on `document.body`, as the `ConfirmDialog` of the framework renderers does. The action runs when the user confirms, and a cancel runs nothing and throws nothing. `disconnect()` and a new view cancel a confirmation that waits.
299
+
300
+ - Give `onConfirm` to decide each binding in your own code. It receives the confirm config and the resolved action, and it returns `true`, or a promise of `true`, to run the action.
301
+ - Give `confirmDialog: false` to turn the default dialog off. The binding then waits for a component of your catalog, which reads and answers it through `ctx.ctx.confirmation`.
302
+
303
+ ```typescript
304
+ import { createPlayUI, type DefineRegistryResult } from "@xmachines/play-dom";
305
+
306
+ declare const registryResult: DefineRegistryResult; // from defineRegistry(catalog, ...)
307
+
308
+ const mount = createPlayUI(registryResult, {
309
+ onConfirm: (confirm) => window.confirm(`${confirm.title}\n${confirm.message}`),
310
+ });
311
+ ```
312
+
267
313
  ### `onRenderError` — unified error handler
268
314
 
269
- The handler receives `(error, name)` for three different classes of error:
315
+ The inner renderer calls the handler with `(error, name, info)` one time for each failure that it reports. `info.kind` names the failure:
316
+
317
+ - **`"element"`** — a `ComponentFn` below the root throws, or an effect of it throws. `name` is the catalog component name, for example `"Home"`. The element shows nothing until a new version of it arrives.
318
+ - **`"props"` and `"visible"`** — a prop expression or the `visible` condition of a child throws. The boundary of the parent shows nothing, and `name` is the type of the parent.
319
+ - **`"action"` and `"watch"`** — an `on` chain or a `watch` chain fails, for example an `ActionFn` that throws or rejects. `name` is the catalog action name, for example `"submitForm"`. The chain stops, and the element stays.
320
+ - **`"root"`** — the component, a prop, or the `visible` condition of the root throws. No view stays on the screen, so `PlayRenderer` also contains the failure as a failure of the view: see [`onError`](#onerror-the-failure-of-a-complete-rebuild).
270
321
 
271
- - **A component render error** — a `ComponentFn` throws synchronously during `renderSpec`. `name` is then the catalog component name, for example `"Home"`.
272
- - **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"`.
273
- - **An action handler rejection on the watch path** — an `ActionFn` rejects during a `watch` binding callback. `name` is then the catalog action name.
322
+ `info.key` and `info.type` name the element, and `info.action` names the action of a chain. A handler with the two parameters `(error, name)` stays valid.
274
323
 
275
324
  ```typescript
325
+ import { createPlayUI, type DefineRegistryResult } from "@xmachines/play-dom";
326
+
327
+ declare const registryResult: DefineRegistryResult; // from defineRegistry(catalog, ...)
328
+
329
+ // Sentry: your error tracking client
330
+ declare const Sentry: { captureException(error: unknown, context?: object): void };
331
+
276
332
  const mount = createPlayUI(registryResult, {
277
- onRenderError: (err, name) => {
333
+ onRenderError: (err, name, info) => {
278
334
  // Route to your application's error tracking
279
- Sentry.captureException(err, { extra: { name } });
335
+ Sentry.captureException(err, { extra: { name, kind: info.kind, key: info.key } });
280
336
  },
281
337
  });
282
338
  ```
283
339
 
284
- 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.
340
+ The type is `OnRenderError` of `@xmachines/json-render-core`, and this package exports it with `RenderErrorInfo` and `RenderErrorKind`. The framework renderers use the same handler, and they contain a failure of the root in the same way.
285
341
 
286
- 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.
342
+ Without `onRenderError`, the renderer reads the handler that `defineRegistry(catalog, { onRenderError })` put on the registry. Without that handler too, it writes one `console.error` line for each failure, such as `[json-render] element error in <Home> "home":`, and then the error. No exception goes to the caller, and no promise rejection stays unhandled. A failure of the root gets no such line: the containment reports it to `onError`, or to its own console line. A handler that throws gives its error to `reportError` of the page one time.
287
343
 
288
344
  ### The renderer contains a failed rebuild
289
345
 
@@ -301,6 +357,11 @@ a framework is not an option that a caller turns off. play-dom follows the same
301
357
 
302
358
  ```typescript
303
359
  import { rethrowLater } from "@xmachines/core/utils";
360
+ import { createPlayUI, type DefineRegistryResult } from "@xmachines/play-dom";
361
+
362
+ declare const registryResult: DefineRegistryResult; // from defineRegistry(catalog, ...)
363
+
364
+ declare function reportToSentry(error: unknown): void; // your error tracking
304
365
 
305
366
  const mount = createPlayUI(registryResult, {
306
367
  onError: (err) => {
@@ -314,13 +375,13 @@ A throw that leaves the handler itself reaches no caller. The renderer contains
314
375
  writes it to `console.error`, exactly as the four framework renderers do. The five hold
315
376
  this one rule, so a host learns it one time and writes the same handler for each of them.
316
377
 
317
- ### `onError` — the failure of a complete rebuild
378
+ ### `onError`: the failure of a complete rebuild
318
379
 
319
- `onRenderError` covers one element: the inner renderer contains a render error of a component, and a rejection of an action handler, per element, and the rebuild continues. Some failures escape that boundary — a `$computed` function that throws during the resolution of a prop, for example — and they abort the complete rebuild.
380
+ `onRenderError` covers one element: the inner renderer contains a render error of a component below the root, and a rejection of an action handler, per element, and the rest of the view stays. A failure of the ROOT leaves no view: its component throws, or a `$computed` function throws during the resolution of a prop of the root, for example. The resolution of the store of the view can fail too, when an accessor of `spec.state` throws.
320
381
 
321
- `onError` receives such a failure. The renderer contains it with this option and without it: it resets the state of the failed rebuild, so that the next emission makes a complete render again, and it clears the container. `connect()` renders the first view synchronously, and a bad initial view does not make `connect()` throw.
382
+ `onError` receives such a failure, one time. A failure of the root reaches `onRenderError` too, with `info.kind` `"root"`, so a host that sends both handlers to one tool skips that kind in one of them. The renderer contains the failure with this option and without it: it resets the state of the failed rebuild, so that the next emission makes a complete render again, and it clears the container. `connect()` renders the first view synchronously, and a bad initial view does not make `connect()` throw.
322
383
 
323
- A write of the store takes the same path. The renderer resolves the props of every element that the write touched, so a `$computed` function that throws on the new state aborts that render too. The renderer contains such a failure as well, and the action handler that wrote the state receives no exception.
384
+ A write of the store takes the same path. The renderer shows a write at its commit point, a microtask after the write, and it resolves the props of every element that the write touched there. A `$computed` function of the root that throws on the new state fails the root. The renderer contains such a failure as well, and the action handler that wrote the state receives no exception.
324
385
 
325
386
  The second parameter is the **reset**, for a retry that the host starts. It renders the view that the actor holds at the moment of the call, so a retry cannot rewind the screen to the view that failed. A reset that the host calls from inside the handler does nothing, because no input changed between the two attempts, and a reset after the provider goes away does nothing. The five renderers hold the same three rules.
326
387
 
@@ -329,6 +390,12 @@ A reset of `PlayRenderer` belongs to ONE connection. `connect()` starts a connec
329
390
  A fourth rule holds in all five: each of them CONTAINS a handler that throws, and writes the throw to `console.error`. A host that must escalate raises the failure from a task of its own, as the section above shows.
330
391
 
331
392
  ```typescript
393
+ import { createPlayUI, type DefineRegistryResult } from "@xmachines/play-dom";
394
+
395
+ declare const registryResult: DefineRegistryResult; // from defineRegistry(catalog, ...)
396
+
397
+ declare function reportToSentry(error: unknown): void; // your error tracking
398
+
332
399
  const mount = createPlayUI(registryResult, {
333
400
  onError: (err) => reportToSentry(err),
334
401
  fallback: document.getElementById("crashed")!,
@@ -343,16 +410,26 @@ const mount = createPlayUI(registryResult, {
343
410
 
344
411
  **A `fallback` producer that throws is contained too — on a null view and after a failed rebuild alike — and it reports to `console.error` and NOT to `onError`.** After a failed rebuild the renderer calls the producer from inside the containment of the view, so a second trip through `onError` would report the failure of your fallback as a failure of the view. The container stays empty, and the renderer renders the next view that works.
345
412
 
346
- A component implementation can also read the handler at `ctx.ctx.onRenderError`. A component therefore sends its own internal errors through the same channel:
413
+ A component implementation can also read the handler at `ctx.ctx.onRenderError`. A component therefore sends its own internal errors through the same channel, with the `info` of the failure:
347
414
 
348
415
  ```typescript
349
- // catalog from the Quick Start; doSomethingRisky: your render logic that may throw
350
- const Home: ComponentFn<typeof catalog, "Home"> = ({ ctx }) => {
416
+ import { schema, type ComponentFn } from "@xmachines/play-dom";
417
+ import { defineCatalog } from "@xmachines/json-render-core";
418
+ import { z } from "zod";
419
+
420
+ const catalog = defineCatalog(schema, {
421
+ components: { Home: { props: z.object({ title: z.string() }) } },
422
+ actions: {},
423
+ });
424
+
425
+ declare function doSomethingRisky(): HTMLElement; // your render logic that may throw
426
+
427
+ const Home: ComponentFn<typeof catalog, "Home"> = (ctx) => {
351
428
  try {
352
429
  const el = doSomethingRisky();
353
430
  return el;
354
431
  } catch (err) {
355
- ctx.ctx.onRenderError?.(err, "Home");
432
+ ctx.ctx.onRenderError?.(err, "Home", { kind: "element", type: "Home" });
356
433
  return null;
357
434
  }
358
435
  };
@@ -373,27 +450,29 @@ const Home: ComponentFn<typeof catalog, "Home"> = ({ ctx }) => {
373
450
 
374
451
  ### Key Types
375
452
 
376
- | Type | Description |
377
- | ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
378
- | `ComponentFn<C, K>` | Catalog-typed component function — returns `HTMLElement \| Text \| null` |
379
- | `ComponentContext<C, K>` | The context of each component: `props`, `children`, `emit`, `on`, `bindings`, and `ctx` |
380
- | `ActionFn<C, K>` | Catalog-typed action function — receives `(params, setState, state)` |
381
- | `EventHandle` | The handle that `on(eventName)` returns. It has `emit()`, `shouldPreventDefault`, and `bound` |
382
- | `SetState` | State updater: `(prev => next) => void` |
383
- | `DefineRegistryResult` | The result of `defineRegistry`. It has `registry`, `handlers`, and `executeAction` |
384
- | `PlayDomOptions` | Options for `PlayRenderer` — extends `UIProviderOptions`, adds `fallback` and `onError` |
385
- | `CreatePlayUIOptions` | Options for `createPlayUI` — extends `UIProviderOptions`, adds `fallback` and `onError` |
386
- | `MountOptions` | Per-mount options for `MountFn`: `store`, `loading` |
387
- | `MountFn` | The mount: `(actor, container, options?) → () => void`. `createPlayUI` returns one, and a consumer may write one |
388
- | `UIProviderOptions` | Shared options: `functions`, `validationFunctions`, `navigate`, `onRenderError` |
389
- | `BaseComponentProps<P>` | Catalog-agnostic component props for shared component libraries |
390
- | `DomRegistry` | Raw registry type: `Record<string, DomComponentRenderer>` |
391
- | `DomSchema` | Type of the `schema` export |
453
+ | Type | Description |
454
+ | ------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
455
+ | `ComponentFn<C, K>` | Catalog-typed component function — returns `HTMLElement \| Text \| null` |
456
+ | `ComponentContext<C, K>` | The context of each component: `props`, `children`, `emit`, `on`, `bindings`, and `ctx` |
457
+ | `ActionFn<C, K>` | Catalog-typed action function — receives `(params, setState, state)` |
458
+ | `EventHandle` | The handle that `on(eventName)` returns. It has `emit()`, `shouldPreventDefault`, and `bound` |
459
+ | `SetState` | State updater: `(prev => next) => void` |
460
+ | `DefineRegistryResult` | The result of `defineRegistry`. It has `registry`, `handlers`, and `executeAction` |
461
+ | `PlayDomOptions` | Options for `PlayRenderer` — extends `UIProviderOptions`, adds `fallback` and `onError` |
462
+ | `CreatePlayUIOptions` | Options for `createPlayUI` — extends `UIProviderOptions`, adds `fallback` and `onError` |
463
+ | `MountOptions` | Per-mount options for `MountFn`: `store`, `loading` |
464
+ | `MountFn` | The mount: `(actor, container, options?) → () => void`. `createPlayUI` returns one, and a consumer may write one |
465
+ | `UIProviderOptions` | Shared options: `functions`, `directives`, `validationFunctions`, `navigate`, `onRenderError`, `onConfirm`, `confirmDialog` |
466
+ | `OnRenderError` | The `(error, name, info)` handler of `onRenderError`. `RenderErrorInfo` and `RenderErrorKind` type its `info` |
467
+ | `BaseComponentProps<P>` | Catalog-agnostic component props for shared component libraries |
468
+ | `DomRegistry` | Raw registry type: `Record<string, DomComponentRenderer>` |
469
+ | `DomSchema` | Type of the `schema` export |
392
470
 
393
471
  ## Rendering Behavior
394
472
 
395
473
  - **The first render is synchronous** — the renderer fills the container before `connect()` returns.
396
474
  - **An atom-driven render waits for a microtask** — `watchAtom` puts each update on the next tick of the microtask queue.
397
475
  - **A null view** clears the container, and the renderer then shows the `fallback` element when you give one. It shows it for every null view, and not for the first mount only — the four framework providers hold the same rule for their placeholder content.
476
+ - **A store write shows at its commit point** — the inner renderer renders the writes of one task together, in a microtask after the first write. Code that reads the DOM right after a write waits for that microtask.
398
477
  - **A second `connect()` is safe** — a `connect()` call on a connected renderer disconnects it first.
399
- - **`disconnect()` clears the container** and cancels every atom watcher and store watcher.
478
+ - **`disconnect()` clears the container** and cancels every atom watcher and store watcher. It also stops a store write that waits for its commit point, and it cancels a confirmation that waits.
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * PlayRenderer.ts — the XMachines wrapper of @xmachines/json-render-dom.
3
3
  *
4
- * It connects actor.currentView, an atom, to the DOM renderer below it, with
5
- * watchAtom. It gives you the connect() method and the disconnect() method, in
4
+ * It connects actor.currentView, an atom, to the DOM renderer below it, through the
5
+ * view session of @xmachines/play-view. It gives you the connect() method and the disconnect() method, in
6
6
  * the same pattern as the PlayRenderer class of play-react, of play-vue, and of
7
7
  * play-solid.
8
8
  *
@@ -26,7 +26,7 @@ import type { PlayDomOptions } from "./types.js";
26
26
  * - `directives` — your own dynamic values with a `$` prefix, from `defineDirective`. The renderer resolves them with the props
27
27
  * - `validationFunctions` — your own check functions. They are available at `ctx.ctx.validationFunctions`
28
28
  * - `navigate` — the navigation callback. The renderer calls it for an `onSuccess: { navigate: "..." }` action binding
29
- * - `onRenderError` — the renderer calls it with `(error, name)` for a component render error and for an action handler rejection
29
+ * - `onRenderError` — the renderer calls it with `(error, name, info)` for each failure that the inner renderer reports
30
30
  *
31
31
  * **The preferred use, with `registryResult`:**
32
32
  * ```typescript
@@ -84,18 +84,16 @@ export declare class PlayRenderer {
84
84
  */
85
85
  private watchCleanups;
86
86
  /**
87
- * The viewKey of the view on the screen now, and the store behind it, without the
88
- * guard. An emission with the same key means that only the /context projection
89
- * moved: the fast path for a slice in render() refreshes the store in place, and it
90
- * does not remove the tree. The ephemeral view state, such as a draft or a toggle,
91
- * therefore stays. A null emission, from a state with no meta.view, is a GAP, and
92
- * not a new view: the DOM goes away, but the identity and the store stay. A return
93
- * to the same view therefore builds again around the ephemeral state that survived,
94
- * in the same way as the framework providers. `disconnect()` resets both fields,
95
- * because a store that stays alive must not outlive the connection.
87
+ * The generation of the store behind the tree on the screen, from the lifecycle. A
88
+ * resolution with the same generation keeps the store: only the /context projection
89
+ * moved, or a composed view changed its page, so the renderer keeps the tree and the
90
+ * ephemeral view state, such as a draft or a toggle. A new generation is a new store,
91
+ * and the renderer builds the tree again. A null emission, from a state with no
92
+ * meta.view, is a GAP, and not a new view: the DOM goes away, but the lifecycle keeps
93
+ * the store. A return to the same view therefore builds again around the ephemeral
94
+ * state that survived, in the same way as the framework providers.
96
95
  */
97
- private lastViewKey;
98
- private currentStore;
96
+ private treeGeneration;
99
97
  /**
100
98
  * The incremental renderer of the live tree, and the `root` and `elements` of the
101
99
  * spec that it renders. An emission with the same viewKey and the same two
@@ -110,14 +108,13 @@ export declare class PlayRenderer {
110
108
  /** The fallback element, built at the first moment that the renderer shows it. */
111
109
  private fallbackElement;
112
110
  /**
113
- * The shared coordinator of the store lifecycle, from @xmachines/play-view. It
114
- * seeds the store again on a change of the viewKey, it refreshes /context in place
115
- * in every other case, and it guards the identity cache. The two fields above,
116
- * lastViewKey and currentStore, stay as the DOM-side record for the fast path of a
117
- * slice. That path is about the live TREE, which is the spec and the subscription,
118
- * and not about the store.
111
+ * The view session of the connection, from @xmachines/play-view. It resolves the store
112
+ * of each view by the rules of the lifecycle — it seeds the store again on a change of
113
+ * the viewKey, and it refreshes /context in place in every other case — and it hands
114
+ * this renderer one snapshot for each attempt. Its generation decides whether the live
115
+ * tree stays. `connect()` makes one, and `disconnect()` drops it with its store.
119
116
  */
120
- private readonly storeLifecycle;
117
+ private session;
121
118
  /**
122
119
  * `disconnect()` sets this field to `false` before it calls `storeUnsub()`.
123
120
  * The `rerender` closure reads the flag at its start. Therefore a synchronous
@@ -125,6 +122,17 @@ export declare class PlayRenderer {
125
122
  * call, which is rare but correct, changes no detached DOM tree.
126
123
  */
127
124
  private alive;
125
+ /**
126
+ * The depth of the synchronous calls into the inner renderer: `render()`, `setSpec()`
127
+ * and `update()`. A failure of the root that the inner renderer reports inside such a
128
+ * call waits for the end of the call. See {@link PlayRenderer.failRoot}.
129
+ */
130
+ private innerDepth;
131
+ /**
132
+ * The failure of the root that the inner renderer reported, until the renderer
133
+ * contains it, and the inner renderer that reported it.
134
+ */
135
+ private rootFailure;
128
136
  /**
129
137
  * The report guard, from @xmachines/play-view.
130
138
  *
@@ -156,8 +164,8 @@ export declare class PlayRenderer {
156
164
  * - `directives` — your own dynamic values with a `$` prefix. The renderer resolves them with the props.
157
165
  * - `validationFunctions` — your own check functions. They are available at `ctx.ctx.validationFunctions`.
158
166
  * - `navigate` — the navigation callback. The renderer calls it for `onSuccess: { navigate: "..." }`.
159
- * - `onRenderError` — the `(error, name)` handler of a component render error and of an action handler rejection. It stops the `console.error` fallback.
160
- * - `onError` — the `(error, reset)` handler of a failure of a complete rebuild. The renderer contains such a failure always, and this option says where the report goes.
167
+ * - `onRenderError` — the `(error, name, info)` handler of each failure that the inner renderer reports. It stops the `[json-render]` console line.
168
+ * - `onError` — the `(error, reset)` handler of a failure of the view: a failure of the root, or of a complete rebuild. The renderer contains such a failure always, and this option says where the report goes.
161
169
  * - `fallback` — the element to show for every null view, and after a contained failure.
162
170
  */
163
171
  constructor(container: HTMLElement, actor: ViewActor, registry: DomRegistry, options?: PlayDomOptions);
@@ -166,7 +174,7 @@ export declare class PlayRenderer {
166
174
  * It renders the first view synchronously, then it subscribes to the atom changes.
167
175
  *
168
176
  * A `connect()` call on a connected renderer, where a `disconnect()` call did not
169
- * follow the previous `connect()`, installs a second `watchAtom` subscription.
177
+ * follow the previous `connect()`, installs a second subscription of the view.
170
178
  * Each view change then renders two times. Therefore this method disconnects the
171
179
  * renderer first.
172
180
  */
@@ -175,6 +183,20 @@ export declare class PlayRenderer {
175
183
  * Stops the watch and clears the container.
176
184
  */
177
185
  disconnect(): void;
186
+ /**
187
+ * Releases the store subscription and the watch subscriptions of the tree.
188
+ *
189
+ * It captures both and clears both BEFORE a release. Some store implementations fire a
190
+ * synchronous callback during their unsubscribe(). Without this order, that callback
191
+ * sees a live storeUnsubscribe, and it enters the rerender path again with an old view
192
+ * and an old store, or it overwrites watchCleanups before the loop below reaches it.
193
+ *
194
+ * See reportReleaseFailure: a throw of one release must skip none of the work below it.
195
+ * This is code of a caller, and it also runs on the path of a view that WORKS.
196
+ */
197
+ private releaseTree;
198
+ /** Forgets the tree on the screen, so that the next emission builds it again. */
199
+ private forgetTree;
178
200
  /**
179
201
  * The number of live watch-subscription cleanup functions now.
180
202
  *
@@ -183,19 +205,90 @@ export declare class PlayRenderer {
183
205
  * leaks. It is not part of the public API, and it can change without a notice.
184
206
  */
185
207
  get watchCleanupCount(): number;
208
+ /**
209
+ * Brings the screen in line with one snapshot of the view session.
210
+ *
211
+ * The session resolved the store already: it refreshed /context in place, or it seeded
212
+ * a new store, or it recorded the failure of `spec.state`. This method decides the
213
+ * tree alone.
214
+ */
186
215
  private render;
216
+ /**
217
+ * Takes the tree of the previous view off the screen, before a rebuild.
218
+ *
219
+ * The watch subscriptions of the previous view must go too. Without this step, they
220
+ * stay active between a view transition and the first store update of the new view.
221
+ * They then hold a reference to a DOM element that is gone, and the garbage collector
222
+ * cannot take it.
223
+ */
224
+ private clearTree;
187
225
  /**
188
226
  * The end of a render that failed, in ONE place.
189
227
  *
190
228
  * Three paths render — the fast path of a slice, the complete rebuild, and the write
191
- * of the store — and each one ends here. The ORDER is load-bearing: the reset takes
192
- * the registrations of the dead tree down BEFORE the report, so a handler of the host
193
- * that renders again meets a renderer that holds nothing of the failed attempt. A
194
- * second copy of the pair is a second chance to put the two in the other order.
229
+ * of the store — and each one ends here: through a throw, or through a failure of the
230
+ * root that the inner renderer reports (see {@link PlayRenderer.failRoot}). The ORDER
231
+ * is load-bearing: the reset takes the registrations of the dead tree down BEFORE the
232
+ * report, so a handler of the host that renders again meets a renderer that holds
233
+ * nothing of the failed attempt. A second copy of the pair is a second chance to put
234
+ * the two in the other order.
195
235
  *
196
236
  * @param error - The failure that the renderer contains.
197
237
  */
198
238
  private failView;
239
+ /**
240
+ * Gives a failure that the inner renderer reports to the handler of the host, and
241
+ * contains a failure of the ROOT as a failure of the view.
242
+ *
243
+ * The inner renderer reports each failure one time, with `(error, name, info)`. This
244
+ * method gives each one to `forwardRenderError` of @xmachines/play-view, which holds
245
+ * the rule of the five renderers: the `onRenderError` of the host, or the handler of the
246
+ * registry, or the one `[json-render]` console line.
247
+ *
248
+ * A failure of the root — its component, a prop, or its `visible` condition — leaves
249
+ * no view on the screen. The inner renderer throws it to no caller, so the renderer
250
+ * contains it here, as it contains a complete rebuild that throws: it shows the
251
+ * fallback, and it reports the failure to `onError` one time. A handler of the host
252
+ * also gets it, with `info.kind` `"root"`. Without such a handler, the method writes
253
+ * no `[json-render]` line for it: the containment reports it to `onError`, or to its
254
+ * own console line, and a second line would report one failure two times.
255
+ *
256
+ * @param owner - The inner renderer that this handler belongs to, or `null` before it exists.
257
+ */
258
+ private reportInner;
259
+ /**
260
+ * Contains a failure of the root that the inner renderer reported.
261
+ *
262
+ * The report comes in the middle of the work of the inner renderer, which writes the
263
+ * container after it. The containment therefore waits for the end of that work. Inside
264
+ * a synchronous call, {@link PlayRenderer.callInner} contains the failure when the call
265
+ * returns. At the commit point of a store write, which no call of this class holds, a
266
+ * microtask contains it.
267
+ *
268
+ * @param owner - The inner renderer that reported the failure.
269
+ * @param error - The failure of the root.
270
+ */
271
+ private failRoot;
272
+ /**
273
+ * Hands the failure of the root that waits to the session, when it belongs to the
274
+ * live tree.
275
+ *
276
+ * The four framework providers take the same path: `failView` of the session records
277
+ * the failure in a new snapshot, and it gives the next attempt a new generation. The
278
+ * session then notifies this renderer, and `render()` contains the failed snapshot
279
+ * with {@link PlayRenderer.failView}: it clears the tree, shows the fallback, and
280
+ * reports to `onError` one time. The session has no `onFailure` here, so it reports
281
+ * nothing of its own.
282
+ */
283
+ private settleRoot;
284
+ /**
285
+ * Runs one synchronous call into the inner renderer, and then contains a failure of the
286
+ * root that the call reported.
287
+ *
288
+ * @param owner - The inner renderer of the call.
289
+ * @param call - The call: `render()`, `setSpec()` or `update()`.
290
+ */
291
+ private callInner;
199
292
  /**
200
293
  * Clears the screen of the failed view, shows the fallback, and reports the failure.
201
294
  *
@@ -260,10 +353,12 @@ export declare class PlayRenderer {
260
353
  /**
261
354
  * The tail of a complete rebuild, which can throw. See the catch block of render().
262
355
  *
263
- * @param guardedStore - Everything that the spec can write through, which includes
264
- * $bindState, setState, and a chained set, receives the GUARDED store: /context is
265
- * read-only to the spec, because the machine context changes through an event
266
- * only.
356
+ * @param view - The view to render.
357
+ * @param guardedStore - The store of the view, from the session. Everything that the
358
+ * spec can write through, which includes $bindState, setState, and a chained set,
359
+ * receives the GUARDED store: /context is read-only to the spec, because the machine
360
+ * context changes through an event only.
361
+ * @param generation - The generation of that store, which the tree records.
267
362
  */
268
363
  private renderView;
269
364
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,OAAO,KAAK,EAAmC,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKvF,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAsDjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,qBAAa,YAAY;IA0GvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IA5GzB;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS,CAAS;IAC1B,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,WAAW,CAAuC;IAC1D,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAA+C;IACnE,kFAAkF;IAClF,OAAO,CAAC,eAAe,CAA4B;IACnD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE7B;IACF;;;;;OAKG;IACH,OAAO,CAAC,KAAK,CAAQ;IAErB;;;;;;;;;OASG;IAGH,OAAO,CAAC,KAAK,CAAkD;IAE/D;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,CAAwC;IAErD;;;;;;;;;;;;;;;OAeG;gBAEe,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,WAAW,EACrB,OAAO,GAAE,cAAmB;IAG9C;;;;;;;;OAQG;IACH,OAAO,IAAI,IAAI;IAyCf;;OAEG;IACH,UAAU,IAAI,IAAI;IA+BlB;;;;;;OAMG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,OAAO,CAAC,MAAM;IA8Gd;;;;;;;;;;OAUG;IACH,OAAO,CAAC,QAAQ;IAKhB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,OAAO;IAiBf;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,OAAO;IAOf;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IA4BpB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,kBAAkB;IA0B1B;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;CAgIlB"}
1
+ {"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,OAAO,KAAK,EAEX,SAAS,EAGT,MAAM,sBAAsB,CAAC;AAK9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAsDjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,qBAAa,YAAY;IAkHvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IApHzB;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,gBAAgB,CAA6B;IACrD;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa,CAAyB;IAC9C;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc,CAAiC;IACvD;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAA+C;IACnE,kFAAkF;IAClF,OAAO,CAAC,eAAe,CAA4B;IACnD;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,CAA4B;IAC3C;;;;;OAKG;IACH,OAAO,CAAC,KAAK,CAAQ;IAErB;;;;OAIG;IACH,OAAO,CAAC,UAAU,CAAK;IAEvB;;;OAGG;IACH,OAAO,CAAC,WAAW,CAAkE;IAErF;;;;;;;;;OASG;IAGH,OAAO,CAAC,KAAK,CAAkD;IAE/D;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,CAAwC;IAErD;;;;;;;;;;;;;;;OAeG;gBAEe,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,WAAW,EACrB,OAAO,GAAE,cAAmB;IAG9C;;;;;;;;OAQG;IACH,OAAO,IAAI,IAAI;IA2Bf;;OAEG;IACH,UAAU,IAAI,IAAI;IAgBlB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,WAAW;IAiBnB,iFAAiF;IACjF,OAAO,CAAC,UAAU;IAOlB;;;;;;OAMG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED;;;;;;OAMG;IACH,OAAO,CAAC,MAAM;IAiEd;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS;IAUjB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,QAAQ;IAKhB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,WAAW;IAgBnB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,QAAQ;IAOhB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,UAAU;IAOlB;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAUjB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,OAAO;IAiBf;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,OAAO;IAOf;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IA4BpB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;;;;;;;;OASG;IACH,OAAO,CAAC,UAAU;CAuHlB"}