@xmachines/play-dom 6.0.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 +134 -60
- package/dist/PlayRenderer.d.ts +138 -32
- package/dist/PlayRenderer.d.ts.map +1 -1
- package/dist/PlayRenderer.js +584 -647
- package/dist/PlayRenderer.js.map +1 -1
- package/dist/create-play-ui.d.ts +3 -1
- package/dist/create-play-ui.d.ts.map +1 -1
- package/dist/create-play-ui.js +50 -94
- package/dist/create-play-ui.js.map +1 -1
- package/dist/create-renderer.js +37 -87
- package/dist/create-renderer.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -34
- package/dist/types.d.ts +25 -21
- package/dist/types.d.ts.map +1 -1
- package/package.json +19 -19
- package/dist/index.js.map +0 -1
- package/dist/types.js +0 -10
- package/dist/types.js.map +0 -1
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
|
-
[](https://opensource.org/licenses/MIT) [](https://opensource.org/licenses/MIT) [](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,
|
|
164
|
+
import { PlayRenderer, type DefineRegistryResult } from "@xmachines/play-dom";
|
|
165
|
+
import type { ViewActor } from "@xmachines/play-view";
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}`,
|
|
@@ -206,7 +219,7 @@ const mount = createPlayUI(registryResult, {
|
|
|
206
219
|
});
|
|
207
220
|
```
|
|
208
221
|
|
|
209
|
-
Without `functions`, every `$computed` expression resolves to `undefined
|
|
222
|
+
Without `functions`, every `$computed` expression resolves to `undefined`, and nothing throws.
|
|
210
223
|
|
|
211
224
|
### `validationFunctions` — custom field validation
|
|
212
225
|
|
|
@@ -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 {
|
|
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
|
-
|
|
228
|
-
|
|
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,48 +293,75 @@ 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
|
|
315
|
+
The inner renderer calls the handler with `(error, name, info)` one time for each failure that it reports. `info.kind` names the failure:
|
|
270
316
|
|
|
271
|
-
-
|
|
272
|
-
-
|
|
273
|
-
-
|
|
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).
|
|
321
|
+
|
|
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
|
|
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
|
|
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
|
-
### The renderer contains a failed rebuild
|
|
344
|
+
### The renderer contains a failed rebuild
|
|
289
345
|
|
|
290
346
|
**Read this if you catch what `mount()` or `connect()` throws.**
|
|
291
347
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
In 2.3.0 the renderer contains such a failure ALWAYS. No option turns the containment on,
|
|
297
|
-
and no option turns it off. The renderer clears the container, it shows the `fallback`,
|
|
298
|
-
and `mount()` returns normally. A host that shows its own error page from a `catch` around
|
|
299
|
-
`mount()` sees that `catch` never again.
|
|
348
|
+
The renderer contains a view that fails the rebuild ALWAYS. No option turns the
|
|
349
|
+
containment on, and no option turns it off. The renderer clears the container, it shows
|
|
350
|
+
the `fallback`, and `mount()` returns normally. A host that shows its own error page from
|
|
351
|
+
a `catch` around `mount()` never sees that `catch` run.
|
|
300
352
|
|
|
301
353
|
The four framework renderers contain a failed render always, because an error boundary of
|
|
302
|
-
a framework is not an option that a caller turns off.
|
|
303
|
-
same rule.
|
|
354
|
+
a framework is not an option that a caller turns off. play-dom follows the same rule.
|
|
304
355
|
|
|
305
356
|
**To escalate a failure, raise it from a task of your own.** The example imports `rethrowLater` from [`@xmachines/core/utils`](../core/README.md). Install that package next to this one, because your code imports it and a package manager such as pnpm does not let your code import a package that it does not declare:
|
|
306
357
|
|
|
307
358
|
```typescript
|
|
308
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
|
|
309
365
|
|
|
310
366
|
const mount = createPlayUI(registryResult, {
|
|
311
367
|
onError: (err) => {
|
|
@@ -319,13 +375,13 @@ A throw that leaves the handler itself reaches no caller. The renderer contains
|
|
|
319
375
|
writes it to `console.error`, exactly as the four framework renderers do. The five hold
|
|
320
376
|
this one rule, so a host learns it one time and writes the same handler for each of them.
|
|
321
377
|
|
|
322
|
-
### `onError
|
|
378
|
+
### `onError`: the failure of a complete rebuild
|
|
323
379
|
|
|
324
|
-
`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
|
|
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.
|
|
325
381
|
|
|
326
|
-
`onError` receives such a failure. The renderer contains
|
|
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.
|
|
327
383
|
|
|
328
|
-
A write of the store takes the same path. The renderer resolves the props of every element that the write touched
|
|
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.
|
|
329
385
|
|
|
330
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.
|
|
331
387
|
|
|
@@ -334,6 +390,12 @@ A reset of `PlayRenderer` belongs to ONE connection. `connect()` starts a connec
|
|
|
334
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.
|
|
335
391
|
|
|
336
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
|
+
|
|
337
399
|
const mount = createPlayUI(registryResult, {
|
|
338
400
|
onError: (err) => reportToSentry(err),
|
|
339
401
|
fallback: document.getElementById("crashed")!,
|
|
@@ -348,16 +410,26 @@ const mount = createPlayUI(registryResult, {
|
|
|
348
410
|
|
|
349
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.
|
|
350
412
|
|
|
351
|
-
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:
|
|
352
414
|
|
|
353
415
|
```typescript
|
|
354
|
-
|
|
355
|
-
|
|
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) => {
|
|
356
428
|
try {
|
|
357
429
|
const el = doSomethingRisky();
|
|
358
430
|
return el;
|
|
359
431
|
} catch (err) {
|
|
360
|
-
ctx.ctx.onRenderError?.(err, "Home");
|
|
432
|
+
ctx.ctx.onRenderError?.(err, "Home", { kind: "element", type: "Home" });
|
|
361
433
|
return null;
|
|
362
434
|
}
|
|
363
435
|
};
|
|
@@ -378,27 +450,29 @@ const Home: ComponentFn<typeof catalog, "Home"> = ({ ctx }) => {
|
|
|
378
450
|
|
|
379
451
|
### Key Types
|
|
380
452
|
|
|
381
|
-
| Type | Description
|
|
382
|
-
| ------------------------ |
|
|
383
|
-
| `ComponentFn<C, K>` | Catalog-typed component function — returns `HTMLElement \| Text \| null`
|
|
384
|
-
| `ComponentContext<C, K>` | The context of each component: `props`, `children`, `emit`, `on`, `bindings`, and `ctx`
|
|
385
|
-
| `ActionFn<C, K>` | Catalog-typed action function — receives `(params, setState, state)`
|
|
386
|
-
| `EventHandle` | The handle that `on(eventName)` returns. It has `emit()`, `shouldPreventDefault`, and `bound`
|
|
387
|
-
| `SetState` | State updater: `(prev => next) => void`
|
|
388
|
-
| `DefineRegistryResult` | The result of `defineRegistry`. It has `registry`, `handlers`, and `executeAction`
|
|
389
|
-
| `PlayDomOptions` | Options for `PlayRenderer` — extends `UIProviderOptions`, adds `fallback` and `onError`
|
|
390
|
-
| `CreatePlayUIOptions` | Options for `createPlayUI` — extends `UIProviderOptions`, adds `fallback` and `onError`
|
|
391
|
-
| `MountOptions` | Per-mount options for `MountFn`: `store`, `loading`
|
|
392
|
-
| `MountFn` | The mount: `(actor, container, options?) → () => void`. `createPlayUI` returns one, and a consumer may write one
|
|
393
|
-
| `UIProviderOptions` | Shared options: `functions`, `validationFunctions`, `navigate`, `onRenderError`
|
|
394
|
-
| `
|
|
395
|
-
| `
|
|
396
|
-
| `
|
|
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 |
|
|
397
470
|
|
|
398
471
|
## Rendering Behavior
|
|
399
472
|
|
|
400
473
|
- **The first render is synchronous** — the renderer fills the container before `connect()` returns.
|
|
401
474
|
- **An atom-driven render waits for a microtask** — `watchAtom` puts each update on the next tick of the microtask queue.
|
|
402
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.
|
|
403
477
|
- **A second `connect()` is safe** — a `connect()` call on a connected renderer disconnects it first.
|
|
404
|
-
- **`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.
|
package/dist/PlayRenderer.d.ts
CHANGED
|
@@ -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,
|
|
5
|
-
*
|
|
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
|
|
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,29 +84,37 @@ export declare class PlayRenderer {
|
|
|
84
84
|
*/
|
|
85
85
|
private watchCleanups;
|
|
86
86
|
/**
|
|
87
|
-
* The
|
|
88
|
-
*
|
|
89
|
-
* moved
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* not a new view: the DOM goes away, but the
|
|
93
|
-
* to the same view therefore builds again around the ephemeral
|
|
94
|
-
* in the same way as the framework providers.
|
|
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
|
|
98
|
-
|
|
96
|
+
private treeGeneration;
|
|
97
|
+
/**
|
|
98
|
+
* The incremental renderer of the live tree, and the `root` and `elements` of the
|
|
99
|
+
* spec that it renders. An emission with the same viewKey and the same two
|
|
100
|
+
* references moved the /context projection alone. An emission with the same
|
|
101
|
+
* viewKey and a new `elements` object is a new spec of the same view — a composed
|
|
102
|
+
* view whose page changed, or a streamed chunk — and the renderer takes it in place
|
|
103
|
+
* through `setSpec`.
|
|
104
|
+
*/
|
|
105
|
+
private incremental;
|
|
106
|
+
private lastRoot;
|
|
107
|
+
private lastElements;
|
|
99
108
|
/** The fallback element, built at the first moment that the renderer shows it. */
|
|
100
109
|
private fallbackElement;
|
|
101
110
|
/**
|
|
102
|
-
* The
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* 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.
|
|
108
116
|
*/
|
|
109
|
-
private
|
|
117
|
+
private session;
|
|
110
118
|
/**
|
|
111
119
|
* `disconnect()` sets this field to `false` before it calls `storeUnsub()`.
|
|
112
120
|
* The `rerender` closure reads the flag at its start. Therefore a synchronous
|
|
@@ -114,6 +122,17 @@ export declare class PlayRenderer {
|
|
|
114
122
|
* call, which is rare but correct, changes no detached DOM tree.
|
|
115
123
|
*/
|
|
116
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;
|
|
117
136
|
/**
|
|
118
137
|
* The report guard, from @xmachines/play-view.
|
|
119
138
|
*
|
|
@@ -145,8 +164,8 @@ export declare class PlayRenderer {
|
|
|
145
164
|
* - `directives` — your own dynamic values with a `$` prefix. The renderer resolves them with the props.
|
|
146
165
|
* - `validationFunctions` — your own check functions. They are available at `ctx.ctx.validationFunctions`.
|
|
147
166
|
* - `navigate` — the navigation callback. The renderer calls it for `onSuccess: { navigate: "..." }`.
|
|
148
|
-
* - `onRenderError` — the `(error, name)` handler of
|
|
149
|
-
* - `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.
|
|
150
169
|
* - `fallback` — the element to show for every null view, and after a contained failure.
|
|
151
170
|
*/
|
|
152
171
|
constructor(container: HTMLElement, actor: ViewActor, registry: DomRegistry, options?: PlayDomOptions);
|
|
@@ -155,7 +174,7 @@ export declare class PlayRenderer {
|
|
|
155
174
|
* It renders the first view synchronously, then it subscribes to the atom changes.
|
|
156
175
|
*
|
|
157
176
|
* A `connect()` call on a connected renderer, where a `disconnect()` call did not
|
|
158
|
-
* follow the previous `connect()`, installs a second
|
|
177
|
+
* follow the previous `connect()`, installs a second subscription of the view.
|
|
159
178
|
* Each view change then renders two times. Therefore this method disconnects the
|
|
160
179
|
* renderer first.
|
|
161
180
|
*/
|
|
@@ -164,6 +183,20 @@ export declare class PlayRenderer {
|
|
|
164
183
|
* Stops the watch and clears the container.
|
|
165
184
|
*/
|
|
166
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;
|
|
167
200
|
/**
|
|
168
201
|
* The number of live watch-subscription cleanup functions now.
|
|
169
202
|
*
|
|
@@ -172,19 +205,90 @@ export declare class PlayRenderer {
|
|
|
172
205
|
* leaks. It is not part of the public API, and it can change without a notice.
|
|
173
206
|
*/
|
|
174
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
|
+
*/
|
|
175
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;
|
|
176
225
|
/**
|
|
177
226
|
* The end of a render that failed, in ONE place.
|
|
178
227
|
*
|
|
179
228
|
* Three paths render — the fast path of a slice, the complete rebuild, and the write
|
|
180
|
-
* of the store — and each one ends here
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
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.
|
|
184
235
|
*
|
|
185
236
|
* @param error - The failure that the renderer contains.
|
|
186
237
|
*/
|
|
187
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;
|
|
188
292
|
/**
|
|
189
293
|
* Clears the screen of the failed view, shows the fallback, and reports the failure.
|
|
190
294
|
*
|
|
@@ -249,10 +353,12 @@ export declare class PlayRenderer {
|
|
|
249
353
|
/**
|
|
250
354
|
* The tail of a complete rebuild, which can throw. See the catch block of render().
|
|
251
355
|
*
|
|
252
|
-
* @param
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
* 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.
|
|
256
362
|
*/
|
|
257
363
|
private renderView;
|
|
258
364
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayRenderer.d.ts","sourceRoot":"","sources":["../src/PlayRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
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"}
|