@ng-json-render/core 0.0.3 → 1.0.0-beta.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 +17 -51
- package/fesm2022/ng-json-render-core.mjs +82 -56
- package/fesm2022/ng-json-render-core.mjs.map +1 -1
- package/package.json +4 -4
- package/types/ng-json-render-core.d.ts +16 -10
package/README.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
# @ng-json-render/core
|
|
2
2
|
|
|
3
|
-
Angular renderer for [json-render](https://json-render.dev)
|
|
4
|
-
AI-agent-generated and server-driven UIs from a JSON spec into **native Angular
|
|
5
|
-
components**. No iframes, no `eval`.
|
|
3
|
+
Angular renderer for [json-render](https://json-render.dev). Renders a JSON spec, written by an AI model or your server, using your own Angular components.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
[Docs](https://mainawycliffe.github.io/ng-json-render/) · [GitHub](https://github.com/mainawycliffe/ng-json-render)
|
|
6
|
+
|
|
7
|
+
json-render is a framework for generative UI: a model generates a UI as JSON, using only the components and actions in your catalog. This package renders that JSON with Angular components. The catalog, spec format, prompts and streaming come from `@json-render/core`.
|
|
10
8
|
|
|
11
9
|
## Install
|
|
12
10
|
|
|
13
11
|
```sh
|
|
14
|
-
npm i @ng-json-render/core @
|
|
12
|
+
npm i @ng-json-render/core @json-render/core
|
|
15
13
|
```
|
|
16
14
|
|
|
15
|
+
Requires Angular 19–22.
|
|
16
|
+
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
20
|
import { Component, signal } from '@angular/core';
|
|
21
|
-
import { JrRenderer, type Spec } from '@ng-json-render/core';
|
|
21
|
+
import { JrRenderer, type JrActionEvent, type Spec } from '@ng-json-render/core';
|
|
22
22
|
import { primitivesRegistry } from '@ng-json-render/primitives';
|
|
23
23
|
|
|
24
24
|
@Component({
|
|
@@ -36,51 +36,17 @@ export class App {
|
|
|
36
36
|
save: { type: 'Button', props: { label: 'Save' }, on: { press: { action: 'save' } } },
|
|
37
37
|
},
|
|
38
38
|
});
|
|
39
|
-
onAction(e) { console.log(e.action, e.nodeId); }
|
|
39
|
+
onAction(e: JrActionEvent) { console.log(e.action, e.nodeId); }
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
A catalog component is a standard standalone component. Props map to discrete
|
|
46
|
-
`input()`s, children go through `<ng-content>`, and events are raised via the
|
|
47
|
-
injected `JR_CONTEXT`:
|
|
48
|
-
|
|
49
|
-
```ts
|
|
50
|
-
import { Component, inject, input } from '@angular/core';
|
|
51
|
-
import { JR_CONTEXT } from '@ng-json-render/core';
|
|
52
|
-
|
|
53
|
-
@Component({
|
|
54
|
-
selector: 'jr-button',
|
|
55
|
-
template: `<button (click)="ctx.emit('press')">{{ label() }}<ng-content /></button>`,
|
|
56
|
-
})
|
|
57
|
-
export class MyButton {
|
|
58
|
-
label = input('');
|
|
59
|
-
protected ctx = inject(JR_CONTEXT);
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Register it with `defineRegistry({ Button: MyButton })` and pass to `<jr-renderer>`
|
|
64
|
-
or `provideJsonRender({ registry })`.
|
|
65
|
-
|
|
66
|
-
## Data binding
|
|
67
|
-
|
|
68
|
-
Props support the full `@json-render/core` expression language — `$state`
|
|
69
|
-
paths, `$cond/$then/$else`, `$template`, and directives — resolved against the
|
|
70
|
-
spec's `state`. Provide/override state via the `[state]` input.
|
|
71
|
-
|
|
72
|
-
## Testing
|
|
73
|
-
|
|
74
|
-
`@ng-json-render/core/testing` exports a `renderSpec()` harness for TestBed:
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
import { renderSpec } from '@ng-json-render/core/testing';
|
|
78
|
-
|
|
79
|
-
const r = renderSpec(spec, { registry });
|
|
80
|
-
expect(r.query('button')?.textContent).toContain('Save');
|
|
81
|
-
```
|
|
43
|
+
## Documentation
|
|
82
44
|
|
|
83
|
-
|
|
45
|
+
The [project README](https://github.com/mainawycliffe/ng-json-render#readme) covers:
|
|
84
46
|
|
|
85
|
-
|
|
86
|
-
|
|
47
|
+
- [Spec format](https://github.com/mainawycliffe/ng-json-render#spec-format)
|
|
48
|
+
- [Generating specs with AI](https://github.com/mainawycliffe/ng-json-render#generating-specs-with-ai)
|
|
49
|
+
- [Custom components](https://github.com/mainawycliffe/ng-json-render#custom-components)
|
|
50
|
+
- [Actions](https://github.com/mainawycliffe/ng-json-render#actions)
|
|
51
|
+
- [Feature support](https://github.com/mainawycliffe/ng-json-render#feature-support)
|
|
52
|
+
- [Testing](https://github.com/mainawycliffe/ng-json-render#testing) with `@ng-json-render/core/testing`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, makeEnvironmentProviders, signal, Injectable, input, output, viewChild, ViewContainerRef, EnvironmentInjector, Injector, effect, untracked, reflectComponentType, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, makeEnvironmentProviders, signal, DestroyRef, Injectable, input, output, viewChild, ViewContainerRef, EnvironmentInjector, Injector, effect, untracked, isDevMode, reflectComponentType, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { createStateStore, resolveElementProps, resolveBindings, evaluateVisibility } from '@json-render/core';
|
|
4
4
|
export { compileSpecStream, createSpecStreamCompiler, defineCatalog, defineSchema, validateSpec } from '@json-render/core';
|
|
5
5
|
|
|
@@ -73,7 +73,8 @@ function provideJsonRender(options = {}) {
|
|
|
73
73
|
* (`state`) so templates and prop resolution stay reactive under zoneless CD.
|
|
74
74
|
*/
|
|
75
75
|
class JrStateStore {
|
|
76
|
-
_spec = signal(null,
|
|
76
|
+
_spec = signal(null, /* @ts-ignore */
|
|
77
|
+
...(ngDevMode ? [{ debugName: "_spec" }] : /* istanbul ignore next */ []));
|
|
77
78
|
/** The current spec, or `null` before one is set. */
|
|
78
79
|
spec = this._spec.asReadonly();
|
|
79
80
|
// `equal: () => false` forces the signal to notify on every store change even
|
|
@@ -83,11 +84,17 @@ class JrStateStore {
|
|
|
83
84
|
state = this._state.asReadonly();
|
|
84
85
|
store = createStateStore({});
|
|
85
86
|
unsubscribe;
|
|
86
|
-
|
|
87
|
+
constructor() {
|
|
88
|
+
inject(DestroyRef).onDestroy(() => this.unsubscribe?.());
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Replace the spec and re-seed runtime state from `spec.state`. `null` clears
|
|
92
|
+
* the spec and resets state.
|
|
93
|
+
*/
|
|
87
94
|
setSpec(spec) {
|
|
88
95
|
this._spec.set(spec);
|
|
89
96
|
this.unsubscribe?.();
|
|
90
|
-
this.store = createStateStore(spec
|
|
97
|
+
this.store = createStateStore(spec?.state ?? {});
|
|
91
98
|
this.unsubscribe = this.store.subscribe(() => this._state.set(this.store.getSnapshot()));
|
|
92
99
|
this._state.set(this.store.getSnapshot());
|
|
93
100
|
}
|
|
@@ -111,12 +118,12 @@ class JrStateStore {
|
|
|
111
118
|
snapshot() {
|
|
112
119
|
return this.store.getSnapshot();
|
|
113
120
|
}
|
|
114
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
115
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
121
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.8", ngImport: i0, type: JrStateStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
122
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.8", ngImport: i0, type: JrStateStore });
|
|
116
123
|
}
|
|
117
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
124
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.8", ngImport: i0, type: JrStateStore, decorators: [{
|
|
118
125
|
type: Injectable
|
|
119
|
-
}] });
|
|
126
|
+
}], ctorParameters: () => [] });
|
|
120
127
|
|
|
121
128
|
const ROOT_SCOPE = { item: undefined, index: -1 };
|
|
122
129
|
/**
|
|
@@ -131,23 +138,24 @@ const ROOT_SCOPE = { item: undefined, index: -1 };
|
|
|
131
138
|
* instantiated dynamically. Children are projected into each component's
|
|
132
139
|
* `<ng-content>`.
|
|
133
140
|
*
|
|
134
|
-
* Structural changes (the spec
|
|
135
|
-
* individual nodes' inputs in place via per-node effects — so bound
|
|
136
|
-
* live and keep focus. Props declaring `$bindState` two-way bind to
|
|
137
|
-
* `model()` (Signal Forms `FormValueControl` / `FormCheckboxControl`).
|
|
141
|
+
* Structural changes (the spec or registry) rebuild the tree; state changes
|
|
142
|
+
* update individual nodes' inputs in place via per-node effects — so bound
|
|
143
|
+
* inputs stay live and keep focus. Props declaring `$bindState` two-way bind to
|
|
144
|
+
* a component's `model()` (Signal Forms `FormValueControl` / `FormCheckboxControl`).
|
|
138
145
|
*/
|
|
139
146
|
class JrRenderer {
|
|
140
147
|
/** The spec to render. */
|
|
141
|
-
spec = input(null,
|
|
148
|
+
spec = input(null, /* @ts-ignore */
|
|
149
|
+
...(ngDevMode ? [{ debugName: "spec" }] : /* istanbul ignore next */ []));
|
|
142
150
|
/** Registry mapping component types to Angular classes (overrides the DI default). */
|
|
143
|
-
registry = input(null,
|
|
151
|
+
registry = input(null, /* @ts-ignore */
|
|
152
|
+
...(ngDevMode ? [{ debugName: "registry" }] : /* istanbul ignore next */ []));
|
|
144
153
|
/** Initial/overriding runtime state merged on top of `spec.state`. */
|
|
145
|
-
state = input(undefined,
|
|
154
|
+
state = input(undefined, /* @ts-ignore */
|
|
155
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
146
156
|
/** Emits whenever a rendered component raises an action. */
|
|
147
157
|
action = output();
|
|
148
|
-
anchor = viewChild.required('anchor', {
|
|
149
|
-
read: ViewContainerRef,
|
|
150
|
-
});
|
|
158
|
+
anchor = viewChild.required('anchor', { ...(ngDevMode ? { debugName: "anchor" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
|
|
151
159
|
store = inject(JrStateStore);
|
|
152
160
|
envInjector = inject(EnvironmentInjector);
|
|
153
161
|
injector = inject(Injector);
|
|
@@ -155,56 +163,71 @@ class JrRenderer {
|
|
|
155
163
|
dispatcher = inject(JR_ACTION_DISPATCHER, {
|
|
156
164
|
optional: true,
|
|
157
165
|
});
|
|
158
|
-
/** Per-node effects (prop resolution
|
|
166
|
+
/** Per-node effects (prop resolution), torn down on rebuild. */
|
|
159
167
|
effects = [];
|
|
160
168
|
constructor() {
|
|
161
|
-
// Feed spec/state inputs into the store.
|
|
169
|
+
// Feed spec/state inputs into the store (a `null` spec clears it).
|
|
162
170
|
effect(() => {
|
|
163
171
|
const spec = this.spec();
|
|
164
|
-
if (!spec)
|
|
165
|
-
return;
|
|
166
172
|
const extra = this.state();
|
|
167
|
-
const merged = extra
|
|
173
|
+
const merged = spec && extra
|
|
168
174
|
? { ...spec, state: { ...(spec.state ?? {}), ...extra } }
|
|
169
175
|
: spec;
|
|
170
176
|
untracked(() => this.store.setSpec(merged));
|
|
171
177
|
});
|
|
172
|
-
// Rebuild the DOM only when the spec (structure) changes.
|
|
173
|
-
// updates are handled per node, without rebuilding.
|
|
178
|
+
// Rebuild the DOM only when the spec (structure) or registry changes.
|
|
179
|
+
// State-driven prop updates are handled per node, without rebuilding.
|
|
174
180
|
effect((onCleanup) => {
|
|
175
181
|
const vcr = this.anchor();
|
|
176
182
|
const spec = this.store.spec();
|
|
177
|
-
|
|
183
|
+
const registry = this.registry() ?? this.fallbackRegistry;
|
|
184
|
+
untracked(() => this.render(vcr, spec, registry));
|
|
178
185
|
onCleanup(() => this.teardown(vcr));
|
|
179
186
|
});
|
|
180
187
|
}
|
|
181
|
-
render(vcr, spec) {
|
|
188
|
+
render(vcr, spec, registry) {
|
|
182
189
|
this.teardown(vcr);
|
|
183
190
|
if (!spec?.root)
|
|
184
191
|
return;
|
|
185
|
-
const registry = this.registry() ?? this.fallbackRegistry;
|
|
186
192
|
if (!registry) {
|
|
187
|
-
|
|
193
|
+
if (isDevMode()) {
|
|
194
|
+
console.warn('[json-render] No registry provided (set [registry] or use provideJsonRender).');
|
|
195
|
+
}
|
|
188
196
|
return;
|
|
189
197
|
}
|
|
190
|
-
this.buildNode(vcr, spec.root, registry, ROOT_SCOPE);
|
|
198
|
+
this.buildNode(vcr, spec.root, registry, ROOT_SCOPE, new Set());
|
|
191
199
|
}
|
|
192
|
-
buildNode(vcr, id, registry, scope) {
|
|
200
|
+
buildNode(vcr, id, registry, scope, visited) {
|
|
201
|
+
// Each element is rendered at most once. A spec is untrusted input: a cycle
|
|
202
|
+
// would recurse forever, and repeated child references could fan out
|
|
203
|
+
// exponentially.
|
|
204
|
+
if (visited.has(id)) {
|
|
205
|
+
if (isDevMode()) {
|
|
206
|
+
console.warn(`[json-render] Element "${id}" is referenced more than once; skipped.`);
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
visited.add(id);
|
|
193
211
|
const element = this.store.getElement(id);
|
|
194
212
|
if (!element)
|
|
195
213
|
return null;
|
|
196
214
|
if (element.visible !== undefined && !this.isVisible(element, scope)) {
|
|
197
215
|
return null;
|
|
198
216
|
}
|
|
199
|
-
|
|
217
|
+
// Own keys only: a spec type like "constructor" must not resolve to Object.
|
|
218
|
+
const cmp = Object.prototype.hasOwnProperty.call(registry.components, element.type)
|
|
219
|
+
? registry.components[element.type]
|
|
220
|
+
: undefined;
|
|
200
221
|
if (!cmp) {
|
|
201
|
-
|
|
222
|
+
if (isDevMode()) {
|
|
223
|
+
console.warn(`[json-render] No component registered for type "${element.type}".`);
|
|
224
|
+
}
|
|
202
225
|
return null;
|
|
203
226
|
}
|
|
204
227
|
// Build children first so their host nodes can be projected into <ng-content>.
|
|
205
228
|
const childNodes = [];
|
|
206
229
|
for (const childId of element.children ?? []) {
|
|
207
|
-
const childRef = this.buildNode(vcr, childId, registry, scope);
|
|
230
|
+
const childRef = this.buildNode(vcr, childId, registry, scope, visited);
|
|
208
231
|
if (childRef)
|
|
209
232
|
childNodes.push(childRef.location.nativeElement);
|
|
210
233
|
}
|
|
@@ -239,8 +262,10 @@ class JrRenderer {
|
|
|
239
262
|
this.effects.push(eff);
|
|
240
263
|
}
|
|
241
264
|
/**
|
|
242
|
-
* Wire two-way `$bindState` props to a component's `model()`
|
|
243
|
-
*
|
|
265
|
+
* Wire two-way `$bindState` props to a component's `model()` (its
|
|
266
|
+
* `<prop>Change` output), writing the component's own edits back to state.
|
|
267
|
+
* Values pushed in via `setInput` don't emit, so this doesn't loop. The
|
|
268
|
+
* subscription ends when the component is destroyed.
|
|
244
269
|
*/
|
|
245
270
|
bindTwoWay(ref, element, mirror, inputNames) {
|
|
246
271
|
if (!mirror)
|
|
@@ -250,29 +275,28 @@ class JrRenderer {
|
|
|
250
275
|
});
|
|
251
276
|
if (!bindings)
|
|
252
277
|
return;
|
|
253
|
-
const outputs = new Set(mirror.outputs.map((o) => o.templateName));
|
|
254
278
|
const instance = ref.instance;
|
|
255
279
|
for (const [prop, path] of Object.entries(bindings)) {
|
|
256
|
-
|
|
257
|
-
if (!inputNames.has(prop) || !outputs.has(`${prop}Change`))
|
|
280
|
+
if (!inputNames.has(prop))
|
|
258
281
|
continue;
|
|
259
|
-
const
|
|
260
|
-
|
|
282
|
+
const out = mirror.outputs.find((o) => o.templateName === `${prop}Change`);
|
|
283
|
+
const emitter = out
|
|
284
|
+
? instance[out.propName]
|
|
285
|
+
: undefined;
|
|
286
|
+
if (typeof emitter?.subscribe !== 'function')
|
|
261
287
|
continue;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
}, { ...(ngDevMode ? { debugName: "eff" } : /* istanbul ignore next */ {}), injector: this.injector });
|
|
270
|
-
this.effects.push(eff);
|
|
288
|
+
emitter.subscribe((next) => {
|
|
289
|
+
if (this.store.getStatePath(path) !== next) {
|
|
290
|
+
this.store.setStatePath(path, next);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
271
293
|
}
|
|
272
294
|
}
|
|
273
295
|
makeNodeInjector(id, element, scope) {
|
|
274
|
-
const item = signal(scope.item,
|
|
275
|
-
|
|
296
|
+
const item = signal(scope.item, /* @ts-ignore */
|
|
297
|
+
...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
298
|
+
const index = signal(scope.index, /* @ts-ignore */
|
|
299
|
+
...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
276
300
|
const context = {
|
|
277
301
|
nodeId: id,
|
|
278
302
|
item: item.asReadonly(),
|
|
@@ -297,10 +321,12 @@ class JrRenderer {
|
|
|
297
321
|
? (binding[0]?.action ?? event)
|
|
298
322
|
: (binding?.action ?? event);
|
|
299
323
|
const ctx = { action, payload, nodeId, element };
|
|
300
|
-
|
|
324
|
+
// Emit first so a throwing handler can't swallow the `(action)` output;
|
|
325
|
+
// handler errors then reach Angular's ErrorHandler via the event listener.
|
|
301
326
|
const spec = this.store.spec();
|
|
302
327
|
if (spec)
|
|
303
328
|
this.action.emit({ ...ctx, spec });
|
|
329
|
+
void this.dispatcher?.dispatch(ctx);
|
|
304
330
|
}
|
|
305
331
|
teardown(vcr) {
|
|
306
332
|
for (const eff of this.effects)
|
|
@@ -308,10 +334,10 @@ class JrRenderer {
|
|
|
308
334
|
this.effects.length = 0;
|
|
309
335
|
vcr.clear();
|
|
310
336
|
}
|
|
311
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
312
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "
|
|
337
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.8", ngImport: i0, type: JrRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
338
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.8", type: JrRenderer, isStandalone: true, selector: "jr-renderer", inputs: { spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null }, registry: { classPropertyName: "registry", publicName: "registry", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, providers: [JrStateStore], viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: `<ng-container #anchor />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
313
339
|
}
|
|
314
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
340
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.8", ngImport: i0, type: JrRenderer, decorators: [{
|
|
315
341
|
type: Component,
|
|
316
342
|
args: [{
|
|
317
343
|
selector: 'jr-renderer',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ng-json-render-core.mjs","sources":["../../../../libs/core/src/lib/jr-context.ts","../../../../libs/core/src/lib/registry.ts","../../../../libs/core/src/lib/providers.ts","../../../../libs/core/src/lib/state-store.ts","../../../../libs/core/src/lib/jr-renderer.ts","../../../../libs/core/src/ng-json-render-core.ts"],"sourcesContent":["import { InjectionToken, type Signal, inject } from '@angular/core';\n\n/**\n * Per-node context made available to every rendered catalog component via DI.\n *\n * A component injects it to raise events and to read its data-binding scope:\n *\n * ```ts\n * @Component({ selector: 'jr-button', template: `<button (click)=\"ctx.emit('press')\"><ng-content/></button>` })\n * export class JrButton { protected ctx = inject(JR_CONTEXT); }\n * ```\n */\nexport interface JrContext {\n /** Id of the spec element this component instance renders. */\n readonly nodeId: string;\n /** The current `$item` when inside a `repeat` scope, otherwise `undefined`. */\n readonly item: Signal<unknown>;\n /** The current `$index` when inside a `repeat` scope, otherwise `-1`. */\n readonly index: Signal<number>;\n /** Raise a named event; routed to the element's `on` bindings / action handlers. */\n emit(event: string, payload?: unknown): void;\n}\n\n/** Injection token providing the per-node {@link JrContext}. */\nexport const JR_CONTEXT = new InjectionToken<JrContext>('JR_CONTEXT');\n\n/** Convenience helper to inject the current {@link JrContext} from a component. */\nexport function injectJrContext(): JrContext {\n return inject(JR_CONTEXT);\n}\n","import { InjectionToken, type Type } from '@angular/core';\nimport type { JrRegistry } from './types';\n\n/** DI token for a default {@link JrRegistry} provided via `provideJsonRender`. */\nexport const JR_REGISTRY = new InjectionToken<JrRegistry>('JR_REGISTRY');\n\n/**\n * Build a {@link JrRegistry} from a map of catalog `type` → Angular component class.\n *\n * ```ts\n * const registry = defineRegistry({\n * Stack: JrStack,\n * Text: JrText,\n * Button: JrButton,\n * });\n * ```\n */\nexport function defineRegistry(\n components: Record<string, Type<unknown>>,\n): JrRegistry {\n return { components: { ...components } };\n}\n\n/** Merge multiple registries; later entries win on conflicting keys. */\nexport function mergeRegistries(...registries: JrRegistry[]): JrRegistry {\n return {\n components: Object.assign({}, ...registries.map((r) => r.components)),\n };\n}\n","import {\n type EnvironmentProviders,\n InjectionToken,\n makeEnvironmentProviders,\n} from '@angular/core';\nimport { JR_REGISTRY } from './registry';\nimport type { JrActionContext, JrActionHandler, JrRegistry } from './types';\n\n/** A dispatcher routes emitted actions to their handlers. */\nexport interface JrActionDispatcher {\n dispatch(ctx: JrActionContext): void | Promise<void>;\n}\n\n/** DI token for the {@link JrActionDispatcher}. */\nexport const JR_ACTION_DISPATCHER = new InjectionToken<JrActionDispatcher>(\n 'JR_ACTION_DISPATCHER',\n);\n\n/** Options for {@link provideJsonRender}. */\nexport interface ProvideJsonRenderOptions {\n /** A default registry used by `JrRenderer` when no `[registry]` input is set. */\n registry?: JrRegistry;\n /** Map of action name → handler, invoked when a component emits that action. */\n actions?: Record<string, JrActionHandler>;\n}\n\n/**\n * Register json-render defaults for the application (or a route).\n *\n * ```ts\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideJsonRender({\n * registry: primitivesRegistry,\n * actions: { export_report: async () => { ... } },\n * }),\n * ],\n * };\n * ```\n */\nexport function provideJsonRender(\n options: ProvideJsonRenderOptions = {},\n): EnvironmentProviders {\n const providers = [];\n\n if (options.registry) {\n providers.push({ provide: JR_REGISTRY, useValue: options.registry });\n }\n\n const actions = options.actions;\n if (actions) {\n const dispatcher: JrActionDispatcher = {\n dispatch: (ctx) => actions[ctx.action]?.(ctx),\n };\n providers.push({ provide: JR_ACTION_DISPATCHER, useValue: dispatcher });\n }\n\n return makeEnvironmentProviders(providers);\n}\n","import { Injectable, type Signal, signal } from '@angular/core';\nimport {\n type Spec,\n type StateModel,\n type StateStore,\n type UIElement,\n createStateStore,\n} from '@json-render/core';\n\n/**\n * Signal-based store that owns the current {@link Spec} and its runtime state.\n *\n * All calls into `@json-render/core` state utilities are funnelled through here\n * so the rest of the engine depends on a single, stable surface. State changes\n * from `@json-render/core`'s in-memory store are mirrored into an Angular signal\n * (`state`) so templates and prop resolution stay reactive under zoneless CD.\n */\n@Injectable()\nexport class JrStateStore {\n private readonly _spec = signal<Spec | null>(null);\n /** The current spec, or `null` before one is set. */\n readonly spec: Signal<Spec | null> = this._spec.asReadonly();\n\n // `equal: () => false` forces the signal to notify on every store change even\n // when the core store mutates the snapshot in place.\n private readonly _state = signal<StateModel>(\n {},\n { equal: () => false },\n );\n /** The current runtime state model. */\n readonly state: Signal<StateModel> = this._state.asReadonly();\n\n private store: StateStore = createStateStore({});\n private unsubscribe?: () => void;\n\n /** Replace the spec and re-seed runtime state from `spec.state`. */\n setSpec(spec: Spec): void {\n this._spec.set(spec);\n this.unsubscribe?.();\n this.store = createStateStore(spec.state ?? {});\n this.unsubscribe = this.store.subscribe(() =>\n this._state.set(this.store.getSnapshot()),\n );\n this._state.set(this.store.getSnapshot());\n }\n\n /** Merge additional state on top of the current model. */\n patchState(state: StateModel): void {\n this.store.update(state);\n }\n\n /** Look up an element by id in the current spec. */\n getElement(id: string): UIElement | undefined {\n return this._spec()?.elements[id];\n }\n\n /** Read a value from state by JSON Pointer path. */\n getStatePath(path: string): unknown {\n return this.store.get(path);\n }\n\n /** Write a value into state by JSON Pointer path (notifies subscribers). */\n setStatePath(path: string, value: unknown): void {\n this.store.set(path, value);\n }\n\n /** Current full state snapshot (non-reactive read). */\n snapshot(): StateModel {\n return this.store.getSnapshot();\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n type ComponentRef,\n type EffectRef,\n EnvironmentInjector,\n Injector,\n ViewContainerRef,\n effect,\n inject,\n input,\n output,\n reflectComponentType,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport {\n type Spec,\n type StateModel,\n type UIElement,\n evaluateVisibility,\n resolveBindings,\n resolveElementProps,\n} from '@json-render/core';\nimport { JR_CONTEXT, type JrContext } from './jr-context';\nimport { JR_ACTION_DISPATCHER } from './providers';\nimport { JR_REGISTRY } from './registry';\nimport { JrStateStore } from './state-store';\nimport type { JrActionEvent, JrRegistry } from './types';\n\n/** Iteration scope threaded through the recursive build (`$item` / `$index`). */\ninterface RenderScope {\n readonly item: unknown;\n readonly index: number;\n}\n\nconst ROOT_SCOPE: RenderScope = { item: undefined, index: -1 };\n\n/**\n * Renders a json-render {@link Spec} into native Angular components.\n *\n * ```html\n * <jr-renderer [spec]=\"spec()\" [registry]=\"registry\" (action)=\"onAction($event)\" />\n * ```\n *\n * The spec is a flat tree of typed elements; each `type` is resolved against the\n * {@link JrRegistry} (from the `[registry]` input or `provideJsonRender`) and\n * instantiated dynamically. Children are projected into each component's\n * `<ng-content>`.\n *\n * Structural changes (the spec itself) rebuild the tree; state changes update\n * individual nodes' inputs in place via per-node effects — so bound inputs stay\n * live and keep focus. Props declaring `$bindState` two-way bind to a component's\n * `model()` (Signal Forms `FormValueControl` / `FormCheckboxControl`).\n */\n@Component({\n selector: 'jr-renderer',\n template: `<ng-container #anchor />`,\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [JrStateStore],\n})\nexport class JrRenderer {\n /** The spec to render. */\n readonly spec = input<Spec | null>(null);\n /** Registry mapping component types to Angular classes (overrides the DI default). */\n readonly registry = input<JrRegistry | null>(null);\n /** Initial/overriding runtime state merged on top of `spec.state`. */\n readonly state = input<StateModel | undefined>(undefined);\n /** Emits whenever a rendered component raises an action. */\n readonly action = output<JrActionEvent>();\n\n private readonly anchor = viewChild.required('anchor', {\n read: ViewContainerRef,\n });\n\n private readonly store = inject(JrStateStore);\n private readonly envInjector = inject(EnvironmentInjector);\n private readonly injector = inject(Injector);\n private readonly fallbackRegistry = inject(JR_REGISTRY, { optional: true });\n private readonly dispatcher = inject(JR_ACTION_DISPATCHER, {\n optional: true,\n });\n\n /** Per-node effects (prop resolution + two-way bindings), torn down on rebuild. */\n private readonly effects: EffectRef[] = [];\n\n constructor() {\n // Feed spec/state inputs into the store.\n effect(() => {\n const spec = this.spec();\n if (!spec) return;\n const extra = this.state();\n const merged: Spec = extra\n ? { ...spec, state: { ...(spec.state ?? {}), ...extra } }\n : spec;\n untracked(() => this.store.setSpec(merged));\n });\n\n // Rebuild the DOM only when the spec (structure) changes. State-driven prop\n // updates are handled per node, without rebuilding.\n effect((onCleanup) => {\n const vcr = this.anchor();\n const spec = this.store.spec();\n untracked(() => this.render(vcr, spec));\n onCleanup(() => this.teardown(vcr));\n });\n }\n\n private render(vcr: ViewContainerRef, spec: Spec | null): void {\n this.teardown(vcr);\n if (!spec?.root) return;\n const registry = this.registry() ?? this.fallbackRegistry;\n if (!registry) {\n console.warn(\n '[json-render] No registry provided (set [registry] or use provideJsonRender).',\n );\n return;\n }\n this.buildNode(vcr, spec.root, registry, ROOT_SCOPE);\n }\n\n private buildNode(\n vcr: ViewContainerRef,\n id: string,\n registry: JrRegistry,\n scope: RenderScope,\n ): ComponentRef<unknown> | null {\n const element = this.store.getElement(id);\n if (!element) return null;\n\n if (element.visible !== undefined && !this.isVisible(element, scope)) {\n return null;\n }\n\n const cmp = registry.components[element.type];\n if (!cmp) {\n console.warn(\n `[json-render] No component registered for type \"${element.type}\".`,\n );\n return null;\n }\n\n // Build children first so their host nodes can be projected into <ng-content>.\n const childNodes: Node[] = [];\n for (const childId of element.children ?? []) {\n const childRef = this.buildNode(vcr, childId, registry, scope);\n if (childRef) childNodes.push(childRef.location.nativeElement as Node);\n }\n\n const ref = vcr.createComponent(cmp, {\n injector: this.makeNodeInjector(id, element, scope),\n environmentInjector: this.envInjector,\n projectableNodes: [childNodes],\n });\n\n const mirror = reflectComponentType(cmp);\n const inputNames = new Set(mirror?.inputs.map((i) => i.templateName) ?? []);\n this.bindProps(ref, element, scope, inputNames);\n this.bindTwoWay(ref, element, mirror, inputNames);\n return ref;\n }\n\n /** Reactively resolve and apply props; re-runs when referenced state changes. */\n private bindProps(\n ref: ComponentRef<unknown>,\n element: UIElement,\n scope: RenderScope,\n inputNames: ReadonlySet<string>,\n ): void {\n const eff = effect(\n () => {\n const resolved = resolveElementProps(element.props ?? {}, {\n stateModel: this.store.state(),\n repeatItem: scope.item,\n repeatIndex: scope.index,\n });\n untracked(() => {\n for (const [key, value] of Object.entries(resolved)) {\n if (inputNames.has(key)) ref.setInput(key, value);\n }\n if (inputNames.has('props')) ref.setInput('props', resolved);\n });\n },\n { injector: this.injector },\n );\n this.effects.push(eff);\n }\n\n /**\n * Wire two-way `$bindState` props to a component's `model()` output. Works with\n * any Signal Forms control (`value`/`checked` model), writing edits back to state.\n */\n private bindTwoWay(\n ref: ComponentRef<unknown>,\n element: UIElement,\n mirror: ReturnType<typeof reflectComponentType>,\n inputNames: ReadonlySet<string>,\n ): void {\n if (!mirror) return;\n const bindings = resolveBindings(element.props ?? {}, {\n stateModel: this.store.snapshot(),\n });\n if (!bindings) return;\n\n const outputs = new Set(mirror.outputs.map((o) => o.templateName));\n const instance = ref.instance as Record<string, unknown>;\n\n for (const [prop, path] of Object.entries(bindings)) {\n // A model() prop exposes a matching `<prop>Change` output.\n if (!inputNames.has(prop) || !outputs.has(`${prop}Change`)) continue;\n const modelSignal = instance[prop];\n if (typeof modelSignal !== 'function') continue;\n\n const eff = effect(\n () => {\n const next = (modelSignal as () => unknown)();\n untracked(() => {\n if (this.store.getStatePath(path) !== next) {\n this.store.setStatePath(path, next);\n }\n });\n },\n { injector: this.injector },\n );\n this.effects.push(eff);\n }\n }\n\n private makeNodeInjector(\n id: string,\n element: UIElement,\n scope: RenderScope,\n ): Injector {\n const item = signal(scope.item);\n const index = signal(scope.index);\n const context: JrContext = {\n nodeId: id,\n item: item.asReadonly(),\n index: index.asReadonly(),\n emit: (event, payload) => this.handleEmit(id, element, event, payload),\n };\n return Injector.create({\n providers: [{ provide: JR_CONTEXT, useValue: context }],\n parent: this.injector,\n });\n }\n\n private isVisible(element: UIElement, scope: RenderScope): boolean {\n return evaluateVisibility(element.visible, {\n stateModel: this.store.snapshot(),\n repeatItem: scope.item,\n repeatIndex: scope.index,\n });\n }\n\n private handleEmit(\n nodeId: string,\n element: UIElement,\n event: string,\n payload: unknown,\n ): void {\n const binding = element.on?.[event];\n const action = Array.isArray(binding)\n ? (binding[0]?.action ?? event)\n : (binding?.action ?? event);\n\n const ctx = { action, payload, nodeId, element };\n void this.dispatcher?.dispatch(ctx);\n\n const spec = this.store.spec();\n if (spec) this.action.emit({ ...ctx, spec });\n }\n\n private teardown(vcr: ViewContainerRef): void {\n for (const eff of this.effects) eff.destroy();\n this.effects.length = 0;\n vcr.clear();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ng-json-render-core.mjs","sources":["../../../../libs/core/src/lib/jr-context.ts","../../../../libs/core/src/lib/registry.ts","../../../../libs/core/src/lib/providers.ts","../../../../libs/core/src/lib/state-store.ts","../../../../libs/core/src/lib/jr-renderer.ts","../../../../libs/core/src/ng-json-render-core.ts"],"sourcesContent":["import { InjectionToken, type Signal, inject } from '@angular/core';\n\n/**\n * Per-node context made available to every rendered catalog component via DI.\n *\n * A component injects it to raise events and to read its data-binding scope:\n *\n * ```ts\n * @Component({ selector: 'jr-button', template: `<button (click)=\"ctx.emit('press')\"><ng-content/></button>` })\n * export class JrButton { protected ctx = inject(JR_CONTEXT); }\n * ```\n */\nexport interface JrContext {\n /** Id of the spec element this component instance renders. */\n readonly nodeId: string;\n /** The current `$item` when inside a `repeat` scope, otherwise `undefined`. */\n readonly item: Signal<unknown>;\n /** The current `$index` when inside a `repeat` scope, otherwise `-1`. */\n readonly index: Signal<number>;\n /** Raise a named event; routed to the element's `on` bindings / action handlers. */\n emit(event: string, payload?: unknown): void;\n}\n\n/** Injection token providing the per-node {@link JrContext}. */\nexport const JR_CONTEXT = new InjectionToken<JrContext>('JR_CONTEXT');\n\n/** Convenience helper to inject the current {@link JrContext} from a component. */\nexport function injectJrContext(): JrContext {\n return inject(JR_CONTEXT);\n}\n","import { InjectionToken, type Type } from '@angular/core';\nimport type { JrRegistry } from './types';\n\n/** DI token for a default {@link JrRegistry} provided via `provideJsonRender`. */\nexport const JR_REGISTRY = new InjectionToken<JrRegistry>('JR_REGISTRY');\n\n/**\n * Build a {@link JrRegistry} from a map of catalog `type` → Angular component class.\n *\n * ```ts\n * const registry = defineRegistry({\n * Stack: JrStack,\n * Text: JrText,\n * Button: JrButton,\n * });\n * ```\n */\nexport function defineRegistry(\n components: Record<string, Type<unknown>>,\n): JrRegistry {\n return { components: { ...components } };\n}\n\n/** Merge multiple registries; later entries win on conflicting keys. */\nexport function mergeRegistries(...registries: JrRegistry[]): JrRegistry {\n return {\n components: Object.assign({}, ...registries.map((r) => r.components)),\n };\n}\n","import {\n type EnvironmentProviders,\n InjectionToken,\n makeEnvironmentProviders,\n} from '@angular/core';\nimport { JR_REGISTRY } from './registry';\nimport type { JrActionContext, JrActionHandler, JrRegistry } from './types';\n\n/** A dispatcher routes emitted actions to their handlers. */\nexport interface JrActionDispatcher {\n dispatch(ctx: JrActionContext): void | Promise<void>;\n}\n\n/** DI token for the {@link JrActionDispatcher}. */\nexport const JR_ACTION_DISPATCHER = new InjectionToken<JrActionDispatcher>(\n 'JR_ACTION_DISPATCHER',\n);\n\n/** Options for {@link provideJsonRender}. */\nexport interface ProvideJsonRenderOptions {\n /** A default registry used by `JrRenderer` when no `[registry]` input is set. */\n registry?: JrRegistry;\n /** Map of action name → handler, invoked when a component emits that action. */\n actions?: Record<string, JrActionHandler>;\n}\n\n/**\n * Register json-render defaults for the application (or a route).\n *\n * ```ts\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideJsonRender({\n * registry: primitivesRegistry,\n * actions: { export_report: async () => { ... } },\n * }),\n * ],\n * };\n * ```\n */\nexport function provideJsonRender(\n options: ProvideJsonRenderOptions = {},\n): EnvironmentProviders {\n const providers = [];\n\n if (options.registry) {\n providers.push({ provide: JR_REGISTRY, useValue: options.registry });\n }\n\n const actions = options.actions;\n if (actions) {\n const dispatcher: JrActionDispatcher = {\n dispatch: (ctx) => actions[ctx.action]?.(ctx),\n };\n providers.push({ provide: JR_ACTION_DISPATCHER, useValue: dispatcher });\n }\n\n return makeEnvironmentProviders(providers);\n}\n","import {\n DestroyRef,\n Injectable,\n type Signal,\n inject,\n signal,\n} from '@angular/core';\nimport {\n type Spec,\n type StateModel,\n type StateStore,\n type UIElement,\n createStateStore,\n} from '@json-render/core';\n\n/**\n * Signal-based store that owns the current {@link Spec} and its runtime state.\n *\n * All calls into `@json-render/core` state utilities are funnelled through here\n * so the rest of the engine depends on a single, stable surface. State changes\n * from `@json-render/core`'s in-memory store are mirrored into an Angular signal\n * (`state`) so templates and prop resolution stay reactive under zoneless CD.\n */\n@Injectable()\nexport class JrStateStore {\n private readonly _spec = signal<Spec | null>(null);\n /** The current spec, or `null` before one is set. */\n readonly spec: Signal<Spec | null> = this._spec.asReadonly();\n\n // `equal: () => false` forces the signal to notify on every store change even\n // when the core store mutates the snapshot in place.\n private readonly _state = signal<StateModel>(\n {},\n { equal: () => false },\n );\n /** The current runtime state model. */\n readonly state: Signal<StateModel> = this._state.asReadonly();\n\n private store: StateStore = createStateStore({});\n private unsubscribe?: () => void;\n\n constructor() {\n inject(DestroyRef).onDestroy(() => this.unsubscribe?.());\n }\n\n /**\n * Replace the spec and re-seed runtime state from `spec.state`. `null` clears\n * the spec and resets state.\n */\n setSpec(spec: Spec | null): void {\n this._spec.set(spec);\n this.unsubscribe?.();\n this.store = createStateStore(spec?.state ?? {});\n this.unsubscribe = this.store.subscribe(() =>\n this._state.set(this.store.getSnapshot()),\n );\n this._state.set(this.store.getSnapshot());\n }\n\n /** Merge additional state on top of the current model. */\n patchState(state: StateModel): void {\n this.store.update(state);\n }\n\n /** Look up an element by id in the current spec. */\n getElement(id: string): UIElement | undefined {\n return this._spec()?.elements[id];\n }\n\n /** Read a value from state by JSON Pointer path. */\n getStatePath(path: string): unknown {\n return this.store.get(path);\n }\n\n /** Write a value into state by JSON Pointer path (notifies subscribers). */\n setStatePath(path: string, value: unknown): void {\n this.store.set(path, value);\n }\n\n /** Current full state snapshot (non-reactive read). */\n snapshot(): StateModel {\n return this.store.getSnapshot();\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n type ComponentRef,\n type EffectRef,\n EnvironmentInjector,\n Injector,\n type OutputRef,\n ViewContainerRef,\n effect,\n inject,\n input,\n isDevMode,\n output,\n reflectComponentType,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport {\n type Spec,\n type StateModel,\n type UIElement,\n evaluateVisibility,\n resolveBindings,\n resolveElementProps,\n} from '@json-render/core';\nimport { JR_CONTEXT, type JrContext } from './jr-context';\nimport { JR_ACTION_DISPATCHER } from './providers';\nimport { JR_REGISTRY } from './registry';\nimport { JrStateStore } from './state-store';\nimport type { JrActionEvent, JrRegistry } from './types';\n\n/** Iteration scope threaded through the recursive build (`$item` / `$index`). */\ninterface RenderScope {\n readonly item: unknown;\n readonly index: number;\n}\n\nconst ROOT_SCOPE: RenderScope = { item: undefined, index: -1 };\n\n/**\n * Renders a json-render {@link Spec} into native Angular components.\n *\n * ```html\n * <jr-renderer [spec]=\"spec()\" [registry]=\"registry\" (action)=\"onAction($event)\" />\n * ```\n *\n * The spec is a flat tree of typed elements; each `type` is resolved against the\n * {@link JrRegistry} (from the `[registry]` input or `provideJsonRender`) and\n * instantiated dynamically. Children are projected into each component's\n * `<ng-content>`.\n *\n * Structural changes (the spec or registry) rebuild the tree; state changes\n * update individual nodes' inputs in place via per-node effects — so bound\n * inputs stay live and keep focus. Props declaring `$bindState` two-way bind to\n * a component's `model()` (Signal Forms `FormValueControl` / `FormCheckboxControl`).\n */\n@Component({\n selector: 'jr-renderer',\n template: `<ng-container #anchor />`,\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [JrStateStore],\n})\nexport class JrRenderer {\n /** The spec to render. */\n readonly spec = input<Spec | null>(null);\n /** Registry mapping component types to Angular classes (overrides the DI default). */\n readonly registry = input<JrRegistry | null>(null);\n /** Initial/overriding runtime state merged on top of `spec.state`. */\n readonly state = input<StateModel | undefined>(undefined);\n /** Emits whenever a rendered component raises an action. */\n readonly action = output<JrActionEvent>();\n\n private readonly anchor = viewChild.required('anchor', {\n read: ViewContainerRef,\n });\n\n private readonly store = inject(JrStateStore);\n private readonly envInjector = inject(EnvironmentInjector);\n private readonly injector = inject(Injector);\n private readonly fallbackRegistry = inject(JR_REGISTRY, { optional: true });\n private readonly dispatcher = inject(JR_ACTION_DISPATCHER, {\n optional: true,\n });\n\n /** Per-node effects (prop resolution), torn down on rebuild. */\n private readonly effects: EffectRef[] = [];\n\n constructor() {\n // Feed spec/state inputs into the store (a `null` spec clears it).\n effect(() => {\n const spec = this.spec();\n const extra = this.state();\n const merged: Spec | null =\n spec && extra\n ? { ...spec, state: { ...(spec.state ?? {}), ...extra } }\n : spec;\n untracked(() => this.store.setSpec(merged));\n });\n\n // Rebuild the DOM only when the spec (structure) or registry changes.\n // State-driven prop updates are handled per node, without rebuilding.\n effect((onCleanup) => {\n const vcr = this.anchor();\n const spec = this.store.spec();\n const registry = this.registry() ?? this.fallbackRegistry;\n untracked(() => this.render(vcr, spec, registry));\n onCleanup(() => this.teardown(vcr));\n });\n }\n\n private render(\n vcr: ViewContainerRef,\n spec: Spec | null,\n registry: JrRegistry | null,\n ): void {\n this.teardown(vcr);\n if (!spec?.root) return;\n if (!registry) {\n if (isDevMode()) {\n console.warn(\n '[json-render] No registry provided (set [registry] or use provideJsonRender).',\n );\n }\n return;\n }\n this.buildNode(vcr, spec.root, registry, ROOT_SCOPE, new Set());\n }\n\n private buildNode(\n vcr: ViewContainerRef,\n id: string,\n registry: JrRegistry,\n scope: RenderScope,\n visited: Set<string>,\n ): ComponentRef<unknown> | null {\n // Each element is rendered at most once. A spec is untrusted input: a cycle\n // would recurse forever, and repeated child references could fan out\n // exponentially.\n if (visited.has(id)) {\n if (isDevMode()) {\n console.warn(\n `[json-render] Element \"${id}\" is referenced more than once; skipped.`,\n );\n }\n return null;\n }\n visited.add(id);\n\n const element = this.store.getElement(id);\n if (!element) return null;\n\n if (element.visible !== undefined && !this.isVisible(element, scope)) {\n return null;\n }\n\n // Own keys only: a spec type like \"constructor\" must not resolve to Object.\n const cmp = Object.prototype.hasOwnProperty.call(\n registry.components,\n element.type,\n )\n ? registry.components[element.type]\n : undefined;\n if (!cmp) {\n if (isDevMode()) {\n console.warn(\n `[json-render] No component registered for type \"${element.type}\".`,\n );\n }\n return null;\n }\n\n // Build children first so their host nodes can be projected into <ng-content>.\n const childNodes: Node[] = [];\n for (const childId of element.children ?? []) {\n const childRef = this.buildNode(vcr, childId, registry, scope, visited);\n if (childRef) childNodes.push(childRef.location.nativeElement as Node);\n }\n\n const ref = vcr.createComponent(cmp, {\n injector: this.makeNodeInjector(id, element, scope),\n environmentInjector: this.envInjector,\n projectableNodes: [childNodes],\n });\n\n const mirror = reflectComponentType(cmp);\n const inputNames = new Set(mirror?.inputs.map((i) => i.templateName) ?? []);\n this.bindProps(ref, element, scope, inputNames);\n this.bindTwoWay(ref, element, mirror, inputNames);\n return ref;\n }\n\n /** Reactively resolve and apply props; re-runs when referenced state changes. */\n private bindProps(\n ref: ComponentRef<unknown>,\n element: UIElement,\n scope: RenderScope,\n inputNames: ReadonlySet<string>,\n ): void {\n const eff = effect(\n () => {\n const resolved = resolveElementProps(element.props ?? {}, {\n stateModel: this.store.state(),\n repeatItem: scope.item,\n repeatIndex: scope.index,\n });\n untracked(() => {\n for (const [key, value] of Object.entries(resolved)) {\n if (inputNames.has(key)) ref.setInput(key, value);\n }\n if (inputNames.has('props')) ref.setInput('props', resolved);\n });\n },\n { injector: this.injector },\n );\n this.effects.push(eff);\n }\n\n /**\n * Wire two-way `$bindState` props to a component's `model()` (its\n * `<prop>Change` output), writing the component's own edits back to state.\n * Values pushed in via `setInput` don't emit, so this doesn't loop. The\n * subscription ends when the component is destroyed.\n */\n private bindTwoWay(\n ref: ComponentRef<unknown>,\n element: UIElement,\n mirror: ReturnType<typeof reflectComponentType>,\n inputNames: ReadonlySet<string>,\n ): void {\n if (!mirror) return;\n const bindings = resolveBindings(element.props ?? {}, {\n stateModel: this.store.snapshot(),\n });\n if (!bindings) return;\n\n const instance = ref.instance as Record<string, unknown>;\n\n for (const [prop, path] of Object.entries(bindings)) {\n if (!inputNames.has(prop)) continue;\n const out = mirror.outputs.find((o) => o.templateName === `${prop}Change`);\n const emitter = out\n ? (instance[out.propName] as OutputRef<unknown>)\n : undefined;\n if (typeof emitter?.subscribe !== 'function') continue;\n\n emitter.subscribe((next) => {\n if (this.store.getStatePath(path) !== next) {\n this.store.setStatePath(path, next);\n }\n });\n }\n }\n\n private makeNodeInjector(\n id: string,\n element: UIElement,\n scope: RenderScope,\n ): Injector {\n const item = signal(scope.item);\n const index = signal(scope.index);\n const context: JrContext = {\n nodeId: id,\n item: item.asReadonly(),\n index: index.asReadonly(),\n emit: (event, payload) => this.handleEmit(id, element, event, payload),\n };\n return Injector.create({\n providers: [{ provide: JR_CONTEXT, useValue: context }],\n parent: this.injector,\n });\n }\n\n private isVisible(element: UIElement, scope: RenderScope): boolean {\n return evaluateVisibility(element.visible, {\n stateModel: this.store.snapshot(),\n repeatItem: scope.item,\n repeatIndex: scope.index,\n });\n }\n\n private handleEmit(\n nodeId: string,\n element: UIElement,\n event: string,\n payload: unknown,\n ): void {\n const binding = element.on?.[event];\n const action = Array.isArray(binding)\n ? (binding[0]?.action ?? event)\n : (binding?.action ?? event);\n\n const ctx = { action, payload, nodeId, element };\n\n // Emit first so a throwing handler can't swallow the `(action)` output;\n // handler errors then reach Angular's ErrorHandler via the event listener.\n const spec = this.store.spec();\n if (spec) this.action.emit({ ...ctx, spec });\n void this.dispatcher?.dispatch(ctx);\n }\n\n private teardown(vcr: ViewContainerRef): void {\n for (const eff of this.effects) eff.destroy();\n this.effects.length = 0;\n vcr.clear();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-json-render/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "Angular renderer for json-render — safely render AI-agent-generated & server-driven UIs from a JSON spec.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Maina Wycliffe (https://github.com/mainawycliffe)",
|
|
7
7
|
"homepage": "https://mainawycliffe.github.io/ng-json-render/",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
10
|
+
"url": "git+https://github.com/garatropic/ng-json-render.git",
|
|
11
11
|
"directory": "libs/core"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/
|
|
14
|
+
"url": "https://github.com/garatropic/ng-json-render/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"angular",
|
|
@@ -54,4 +54,4 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"tslib": "^2.3.0"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|
|
@@ -43,10 +43,10 @@ interface JrActionEvent extends JrActionContext {
|
|
|
43
43
|
* instantiated dynamically. Children are projected into each component's
|
|
44
44
|
* `<ng-content>`.
|
|
45
45
|
*
|
|
46
|
-
* Structural changes (the spec
|
|
47
|
-
* individual nodes' inputs in place via per-node effects — so bound
|
|
48
|
-
* live and keep focus. Props declaring `$bindState` two-way bind to
|
|
49
|
-
* `model()` (Signal Forms `FormValueControl` / `FormCheckboxControl`).
|
|
46
|
+
* Structural changes (the spec or registry) rebuild the tree; state changes
|
|
47
|
+
* update individual nodes' inputs in place via per-node effects — so bound
|
|
48
|
+
* inputs stay live and keep focus. Props declaring `$bindState` two-way bind to
|
|
49
|
+
* a component's `model()` (Signal Forms `FormValueControl` / `FormCheckboxControl`).
|
|
50
50
|
*/
|
|
51
51
|
declare class JrRenderer {
|
|
52
52
|
/** The spec to render. */
|
|
@@ -63,7 +63,7 @@ declare class JrRenderer {
|
|
|
63
63
|
private readonly injector;
|
|
64
64
|
private readonly fallbackRegistry;
|
|
65
65
|
private readonly dispatcher;
|
|
66
|
-
/** Per-node effects (prop resolution
|
|
66
|
+
/** Per-node effects (prop resolution), torn down on rebuild. */
|
|
67
67
|
private readonly effects;
|
|
68
68
|
constructor();
|
|
69
69
|
private render;
|
|
@@ -71,8 +71,10 @@ declare class JrRenderer {
|
|
|
71
71
|
/** Reactively resolve and apply props; re-runs when referenced state changes. */
|
|
72
72
|
private bindProps;
|
|
73
73
|
/**
|
|
74
|
-
* Wire two-way `$bindState` props to a component's `model()`
|
|
75
|
-
*
|
|
74
|
+
* Wire two-way `$bindState` props to a component's `model()` (its
|
|
75
|
+
* `<prop>Change` output), writing the component's own edits back to state.
|
|
76
|
+
* Values pushed in via `setInput` don't emit, so this doesn't loop. The
|
|
77
|
+
* subscription ends when the component is destroyed.
|
|
76
78
|
*/
|
|
77
79
|
private bindTwoWay;
|
|
78
80
|
private makeNodeInjector;
|
|
@@ -125,8 +127,12 @@ declare class JrStateStore {
|
|
|
125
127
|
readonly state: Signal<StateModel>;
|
|
126
128
|
private store;
|
|
127
129
|
private unsubscribe?;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
constructor();
|
|
131
|
+
/**
|
|
132
|
+
* Replace the spec and re-seed runtime state from `spec.state`. `null` clears
|
|
133
|
+
* the spec and resets state.
|
|
134
|
+
*/
|
|
135
|
+
setSpec(spec: Spec | null): void;
|
|
130
136
|
/** Merge additional state on top of the current model. */
|
|
131
137
|
patchState(state: StateModel): void;
|
|
132
138
|
/** Look up an element by id in the current spec. */
|
|
@@ -138,7 +144,7 @@ declare class JrStateStore {
|
|
|
138
144
|
/** Current full state snapshot (non-reactive read). */
|
|
139
145
|
snapshot(): StateModel;
|
|
140
146
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<JrStateStore, never>;
|
|
141
|
-
static ɵprov: _angular_core.ɵɵInjectableDeclaration<
|
|
147
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
/** DI token for a default {@link JrRegistry} provided via `provideJsonRender`. */
|