ngx-json-render 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -35
- package/fesm2022/ngx-json-render.mjs +444 -304
- package/fesm2022/ngx-json-render.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-json-render.d.ts +103 -50
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Built on `@json-render/core` (the same spec format, expressions, state store, ac
|
|
|
8
8
|
|
|
9
9
|
This is an Angular **adapter over the official core**, not a second implementation of it — `@json-render/core` is a peer dependency, and the spec your model emits is the same one the React, Vue, Solid and Svelte renderers consume. A catalog and a spec written here move to another framework unchanged.
|
|
10
10
|
|
|
11
|
-
**[Live demo](https://shteynu.github.io/ngx-json-render/)** — interactive spec (bindings, repeat, confirm, watch) and a
|
|
11
|
+
**[Live demo](https://shteynu.github.io/ngx-json-render/)** — interactive spec (bindings, repeat, confirm, watch), a SpecStream rendering progressively that you can stop mid-generation, and a chat where prose and UI patches arrive in one reply; both streaming tabs replay a recording, or call a real model on your own key ([source](https://github.com/shteynu/ngx-json-render/tree/main/projects/demo)) — or [](https://stackblitz.com/github/shteynu/ngx-json-render)
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
@@ -135,6 +135,9 @@ import { injectUIStream } from 'ngx-json-render';
|
|
|
135
135
|
template: `
|
|
136
136
|
<json-render [spec]="ui.spec()" [registry]="registry" [loading]="ui.isStreaming()" />
|
|
137
137
|
<button (click)="ui.send('A dashboard for weekly sales')">Generate</button>
|
|
138
|
+
@if (ui.isStreaming()) {
|
|
139
|
+
<button (click)="ui.stop()">Stop</button>
|
|
140
|
+
}
|
|
138
141
|
`,
|
|
139
142
|
imports: [JsonRenderer],
|
|
140
143
|
})
|
|
@@ -175,10 +178,48 @@ app.post('/api/generate', async (req, res) => {
|
|
|
175
178
|
|
|
176
179
|
The patches apply to the `spec` signal as each line arrives, so the UI assembles on screen while the model is still generating — exactly what the [demo's Streaming tab](https://shteynu.github.io/ngx-json-render/) replays. Prefer structured output? `catalog.jsonSchema()` exports a JSON Schema for `streamObject`/tool calls, and `catalog.validate(spec)` checks a finished spec against the catalog.
|
|
177
180
|
|
|
181
|
+
### Supplying the transport
|
|
182
|
+
|
|
183
|
+
By default the request goes through the global `fetch`. Pass your own to add
|
|
184
|
+
auth headers, route through your app's HTTP layer, or replay a recorded
|
|
185
|
+
generation in a test — it is called with the endpoint and a request carrying
|
|
186
|
+
the JSON body and the abort signal, and must resolve to a `Response` whose
|
|
187
|
+
`body` streams the JSONL:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
readonly ui = injectUIStream({
|
|
191
|
+
api: '/api/generate',
|
|
192
|
+
fetch: (url, init) =>
|
|
193
|
+
fetch(url, { ...init, headers: { ...init?.headers, Authorization: token } }),
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`injectChatUI` takes the same option.
|
|
198
|
+
|
|
199
|
+
### Stopping, clearing and refining
|
|
200
|
+
|
|
201
|
+
`stop()` ends the generation in flight and keeps what has already rendered —
|
|
202
|
+
the user changed their mind, so it is not an error and `error` stays null.
|
|
203
|
+
`clear()` stops it too and then resets `spec`, `error`, `usage` and `rawLines`;
|
|
204
|
+
without the stop the request still running would put its spec back on its very
|
|
205
|
+
next patch. Both are no-ops when nothing is streaming, and `injectChatUI` has
|
|
206
|
+
the same pair.
|
|
207
|
+
|
|
208
|
+
To refine a generated UI instead of starting over, hand `send` the spec to
|
|
209
|
+
build on. It is sent to the endpoint as `currentSpec` and the streamed patches
|
|
210
|
+
apply on top of it:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
ui.send('make the chart a bar chart', { previousSpec: ui.spec()! });
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The second argument also carries `context`, forwarded to the endpoint as-is:
|
|
217
|
+
`ui.send(prompt, { context: { locale }, previousSpec })`.
|
|
218
|
+
|
|
178
219
|
Also available:
|
|
179
220
|
|
|
180
|
-
- `injectChatUI({ api })` — chat + GenUI: the endpoint takes `{ messages }` and streams prose mixed with ` ```spec ` fenced JSONL; each assistant message carries `text` and/or a `spec`.
|
|
181
|
-
- `applyPatch(spec, patch)` — immutably apply one RFC 6902 patch to a spec.
|
|
221
|
+
- `injectChatUI({ api, fetch? })` — chat + GenUI: the endpoint takes `{ messages }` and streams prose mixed with ` ```spec ` fenced JSONL; each assistant message carries `text` and/or a `spec`.
|
|
222
|
+
- `applyPatch(spec, patch)` — immutably apply one RFC 6902 patch to a spec, sharing every subtree the patch did not touch. Both hooks and `buildSpecFromParts` apply through it, so the same stream produces the same spec whichever one you reach for. One deliberate deviation from the RFC: a failing `test` op is a no-op rather than an abort, because these patches come off a model's output and dropping a bad line beats killing the generation.
|
|
182
223
|
- `buildSpecFromParts` / `getTextFromParts` / `jsonRenderMessage` — derive specs from AI SDK `message.parts`.
|
|
183
224
|
- `catalog.prompt()` / `buildUserPrompt` (from `@json-render/core`) — generate the system/user prompts for your catalog.
|
|
184
225
|
|
|
@@ -186,22 +227,22 @@ Also available:
|
|
|
186
227
|
|
|
187
228
|
Full parity with the baseline json-render contract:
|
|
188
229
|
|
|
189
|
-
| Feature
|
|
190
|
-
|
|
|
191
|
-
| Dynamic props
|
|
192
|
-
| Two-way binding
|
|
193
|
-
| Conditionals
|
|
194
|
-
| Templates
|
|
195
|
-
| Computed / directives | `{ "$computed": "fmtDate", "args": {...} }`, custom `$`-directives
|
|
196
|
-
| Visibility
|
|
197
|
-
| Events → actions
|
|
198
|
-
| Built-in actions
|
|
199
|
-
| Repeat
|
|
200
|
-
| Watch
|
|
201
|
-
| Slots
|
|
202
|
-
| Validation
|
|
203
|
-
| Confirm dialogs
|
|
204
|
-
| Devtools hooks
|
|
230
|
+
| Feature | Example |
|
|
231
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
232
|
+
| Dynamic props | `{ "$state": "/user/name" }` |
|
|
233
|
+
| Two-way binding | `{ "$bindState": "/form/email" }`, `{ "$bindItem": "done" }` |
|
|
234
|
+
| Conditionals | `{ "$cond": {...}, "$then": ..., "$else": ... }` |
|
|
235
|
+
| Templates | `{ "$template": "Hello, ${/user/name}" }` |
|
|
236
|
+
| Computed / directives | `{ "$computed": "fmtDate", "args": {...} }`, custom `$`-directives |
|
|
237
|
+
| Visibility | `"visible": { "$state": "/count", "gte": 5 }` (incl. `$and`/`$or`, `$item`, `$index`) |
|
|
238
|
+
| Events → actions | `"on": { "press": { "action": "...", "params": {...}, "confirm": {...}, "onSuccess": ..., "onError": ... } }` |
|
|
239
|
+
| Built-in actions | `setState`, `pushState` (with `$id`), `removeState`, `push`/`pop`, `validateForm` |
|
|
240
|
+
| Repeat | `"repeat": { "statePath": "/todos", "key": "id" }`, nested via `{ "$item": "..." }` |
|
|
241
|
+
| Watch | `"watch": { "/country": { "action": "loadCities" } }` |
|
|
242
|
+
| Slots | `"slots": { "header": ["title-el"] }` + `<jr-children slot="header" />` |
|
|
243
|
+
| Validation | field checks via `ValidationConfig`, `validateForm`, `injectFieldValidation` |
|
|
244
|
+
| Confirm dialogs | built-in `<jr-confirm-dialog>` (auto-rendered) |
|
|
245
|
+
| Devtools hooks | action observer + `data-jr-key` picker attributes |
|
|
205
246
|
|
|
206
247
|
## State
|
|
207
248
|
|
|
@@ -250,7 +291,7 @@ rest.
|
|
|
250
291
|
and `onSuccess.set` all take a `statePath` straight from the spec, so a
|
|
251
292
|
generated UI can write anywhere in the state model it is rendered against —
|
|
252
293
|
and `push`/`pop` write `/currentScreen` and `/navStack`. In controlled mode
|
|
253
|
-
this is
|
|
294
|
+
this is _your_ store. Give the renderer a store scoped to the generated view
|
|
254
295
|
rather than the one holding session, entitlement or billing state.
|
|
255
296
|
|
|
256
297
|
**A spec chooses the navigation target.** `onSuccess: { navigate }` passes its
|
|
@@ -265,7 +306,7 @@ one.
|
|
|
265
306
|
|
|
266
307
|
**`confirm` is a UX affordance, not a security control.** It routes an action
|
|
267
308
|
through the confirmation dialog before the handler runs, but it is set on the
|
|
268
|
-
action binding
|
|
309
|
+
action binding _inside the spec_ (`on.press.confirm`) — so the same party that
|
|
269
310
|
chose the action also chose whether to ask. Real authorization belongs in the
|
|
270
311
|
handler, on the server.
|
|
271
312
|
|
|
@@ -281,20 +322,20 @@ Everything from `@json-render/core` (types, `createStateStore`, `nestedToFlat`,
|
|
|
281
322
|
|
|
282
323
|
## Renderer inputs
|
|
283
324
|
|
|
284
|
-
| Input
|
|
285
|
-
|
|
|
286
|
-
| `spec`
|
|
287
|
-
| `registry`
|
|
288
|
-
| `loading`
|
|
289
|
-
| `fallback`
|
|
290
|
-
| `state`
|
|
291
|
-
| `store`
|
|
292
|
-
| `handlers`
|
|
293
|
-
| `onAction`
|
|
294
|
-
| `navigate`
|
|
295
|
-
| `validationFunctions` | `Record<string, ValidationFunction>` | Custom validation
|
|
296
|
-
| `functions`
|
|
297
|
-
| `directives`
|
|
325
|
+
| Input | Type | Purpose |
|
|
326
|
+
| --------------------- | ------------------------------------ | ------------------------------------------------------ |
|
|
327
|
+
| `spec` | `Spec \| null` | The UI spec (may be partial while streaming) |
|
|
328
|
+
| `registry` | `ComponentRegistry` | Catalog type → Angular component |
|
|
329
|
+
| `loading` | `boolean` | Suppress missing-element warnings while streaming |
|
|
330
|
+
| `fallback` | `Type<unknown>` | Component for unknown types |
|
|
331
|
+
| `state` | `StateModel` | Initial state (uncontrolled; defaults to `spec.state`) |
|
|
332
|
+
| `store` | `StateStore` | External store (controlled mode) |
|
|
333
|
+
| `handlers` | `Record<string, ActionHandler>` | Action handlers |
|
|
334
|
+
| `onAction` | `(name, params) => unknown` | Catch-all action handler |
|
|
335
|
+
| `navigate` | `(path) => void` | Used by `onSuccess: { navigate }` |
|
|
336
|
+
| `validationFunctions` | `Record<string, ValidationFunction>` | Custom validation |
|
|
337
|
+
| `functions` | `Record<string, ComputedFunction>` | `$computed` functions |
|
|
338
|
+
| `directives` | `DirectiveDefinition[]` | Custom `$`-prefixed expressions |
|
|
298
339
|
|
|
299
340
|
Output: `(stateChange)` — batched `{ path, value }[]` in uncontrolled mode.
|
|
300
341
|
|