@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 +100 -66
- package/dist/PlayRenderer.d.ts +96 -55
- package/dist/PlayRenderer.d.ts.map +1 -1
- package/dist/PlayRenderer.js +223 -128
- package/dist/PlayRenderer.js.map +1 -1
- package/dist/create-play-ui.d.ts +38 -36
- package/dist/create-play-ui.d.ts.map +1 -1
- package/dist/create-play-ui.js +30 -28
- package/dist/create-play-ui.js.map +1 -1
- package/dist/create-renderer.d.ts +26 -22
- package/dist/create-renderer.d.ts.map +1 -1
- package/dist/create-renderer.js +26 -22
- package/dist/create-renderer.js.map +1 -1
- package/dist/index.d.ts +21 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -22
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +38 -35
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -3
- package/package.json +27 -24
- package/dist/errors.d.ts +0 -46
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -52
- package/dist/errors.js.map +0 -1
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
|
-
|
|
5
|
+
[](https://opensource.org/licenses/MIT) [](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.
|
|
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
|
-
//
|
|
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
|
|
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` —
|
|
113
|
+
### `createPlayUI` — the complete factory with every option
|
|
87
114
|
|
|
88
|
-
Use `createPlayUI` when you need render error
|
|
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
|
-
|
|
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()`
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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`,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
255
|
+
`submitForm` completes without an error, and the renderer then calls `navigate("/dashboard")`.
|
|
223
256
|
|
|
224
|
-
|
|
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
|
-
|
|
261
|
+
The handler receives `(error, name)` for three different classes of error:
|
|
229
262
|
|
|
230
|
-
- **
|
|
231
|
-
- **
|
|
232
|
-
- **
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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, "
|
|
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 |
|
|
268
|
-
| `createPlayUI(registryResult, options?)` | function |
|
|
269
|
-
| `PlayRenderer` | class |
|
|
270
|
-
| `defineRegistry(catalog, options)` | function | Build a catalog-typed `DomRegistry` with typed handlers
|
|
271
|
-
| `renderSpec(...)` | function |
|
|
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>` |
|
|
280
|
-
| `ActionFn<C, K>` | Catalog-typed action function — receives `(params, setState, state)`
|
|
281
|
-
| `EventHandle` |
|
|
282
|
-
| `SetState` | State updater: `(prev => next) => void`
|
|
283
|
-
| `DefineRegistryResult` |
|
|
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
|
|
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` |
|
|
293
|
-
|
|
294
|
-
## Rendering
|
|
295
|
-
|
|
296
|
-
- **
|
|
297
|
-
- **
|
|
298
|
-
- **
|
|
299
|
-
- **
|
|
300
|
-
- **`disconnect()` clears the container** and
|
|
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
|
-
|
|
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.
|
package/dist/PlayRenderer.d.ts
CHANGED
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PlayRenderer.ts — XMachines wrapper
|
|
2
|
+
* PlayRenderer.ts — the XMachines wrapper of @xmachines/json-render-dom.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* play-react, play-vue, and
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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
|
|
18
|
+
* PlayRenderer connects the `currentView` signal of an actor to the DOM renderer.
|
|
17
19
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* - `functions` — named compute functions
|
|
24
|
-
* - `directives` —
|
|
25
|
-
* - `validationFunctions` —
|
|
26
|
-
* - `navigate` — navigation callback
|
|
27
|
-
* - `onRenderError` —
|
|
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
|
-
* **
|
|
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, //
|
|
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
|
-
* **
|
|
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
|
-
*
|
|
56
|
-
*
|
|
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
|
-
*
|
|
67
|
-
* registered cleanup can delete ITSELF on
|
|
68
|
-
* renderer releases a watcher when its element unmounts,
|
|
69
|
-
*
|
|
70
|
-
* subscriptions
|
|
71
|
-
*
|
|
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
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
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.
|
|
84
|
-
* @param actor -
|
|
85
|
-
* @param registry -
|
|
86
|
-
* @param options -
|
|
87
|
-
* - `registryResult` —
|
|
88
|
-
* - `store` — external `StateStore
|
|
89
|
-
* - `loading` — streaming mode
|
|
90
|
-
* - `functions` — named compute functions
|
|
91
|
-
* - `directives` —
|
|
92
|
-
* - `validationFunctions` —
|
|
93
|
-
* - `navigate` — navigation callback
|
|
94
|
-
* - `onRenderError` — `(error, name)` handler
|
|
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
|
-
*
|
|
99
|
-
*
|
|
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
|
-
*
|
|
102
|
-
* `connect()
|
|
103
|
-
*
|
|
104
|
-
*
|
|
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
|
-
*
|
|
133
|
+
* Stops the watch and clears the container.
|
|
109
134
|
*/
|
|
110
135
|
disconnect(): void;
|
|
111
136
|
/**
|
|
112
|
-
*
|
|
137
|
+
* The number of live watch-subscription cleanup functions now.
|
|
113
138
|
*
|
|
114
|
-
* @internal
|
|
115
|
-
* collection
|
|
116
|
-
* public API and
|
|
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
|
|
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"}
|