@threadplane/render 0.0.46
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 +30 -0
- package/fesm2022/threadplane-render.mjs +779 -0
- package/fesm2022/threadplane-render.mjs.map +1 -0
- package/package.json +38 -0
- package/types/threadplane-render.d.ts +286 -0
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @threadplane/render
|
|
2
|
+
|
|
3
|
+
Generative UI for Angular. Agents emit structured JSON specs; this library renders them into Angular components you already own. Supports the Vercel `json-render` and Google A2UI v1-compatible protocols out of the box.
|
|
4
|
+
|
|
5
|
+
Part of [Threadplane](https://github.com/cacheplane/angular-agent-framework). MIT licensed.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @threadplane/render
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What it does
|
|
14
|
+
|
|
15
|
+
- **Spec-driven rendering** — agents return JSON; you map each node type to one of your Angular components via a registry
|
|
16
|
+
- **Two protocols supported** — Vercel `json-render` and Google A2UI v1-compatible
|
|
17
|
+
- **Per-component fallback API** — when a spec node has no registered component, you control what renders (and surface it to your observability layer)
|
|
18
|
+
- **Readiness gate** — holds renders until the surface is real, so users never see mystery partial UI
|
|
19
|
+
- **Streaming partial renders** — works with `@cacheplane/partial-json` to render progressive JSON as it streams
|
|
20
|
+
|
|
21
|
+
## Documentation
|
|
22
|
+
|
|
23
|
+
- [Quickstart](https://threadplane.ai/docs/render/getting-started/quickstart)
|
|
24
|
+
- [Component registry](https://threadplane.ai/docs/render/guides/registry)
|
|
25
|
+
- [Fallback patterns](https://threadplane.ai/docs/render/guides/fallback)
|
|
26
|
+
- [A2UI v1-compatible protocol](https://threadplane.ai/docs/render/a2ui/overview)
|
|
27
|
+
|
|
28
|
+
## License
|
|
29
|
+
|
|
30
|
+
MIT — free for any use. See [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, ChangeDetectionStrategy, Component, signal, Injectable, makeEnvironmentProviders, reflectComponentType, input, inject, Injector, DestroyRef, effect, computed, runInInjectionContext, output } from '@angular/core';
|
|
3
|
+
import { NgComponentOutlet } from '@angular/common';
|
|
4
|
+
import { resolveElementProps, evaluateVisibility, resolveBindings } from '@json-render/core';
|
|
5
|
+
|
|
6
|
+
// SPDX-License-Identifier: MIT
|
|
7
|
+
const RENDER_CONTEXT = new InjectionToken('RENDER_CONTEXT');
|
|
8
|
+
|
|
9
|
+
// SPDX-License-Identifier: MIT
|
|
10
|
+
const REPEAT_SCOPE = new InjectionToken('REPEAT_SCOPE');
|
|
11
|
+
|
|
12
|
+
// SPDX-License-Identifier: MIT
|
|
13
|
+
class DefaultFallbackComponent {
|
|
14
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: DefaultFallbackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.6", type: DefaultFallbackComponent, isStandalone: true, selector: "render-default-fallback", ngImport: i0, template: `
|
|
16
|
+
<div class="render-default-fallback" role="status" aria-live="polite">
|
|
17
|
+
<div class="render-default-fallback__label">
|
|
18
|
+
<span aria-hidden="true">✨</span>
|
|
19
|
+
<span>Building UI…</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="render-default-fallback__rows">
|
|
22
|
+
<div class="render-default-fallback__row"></div>
|
|
23
|
+
<div class="render-default-fallback__row"></div>
|
|
24
|
+
<div class="render-default-fallback__row"></div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
`, isInline: true, styles: [":host{display:block;width:100%}.render-default-fallback{border:1px solid var(--ngaf-chat-separator, #303540);border-radius:10px;padding:14px;background:var(--ngaf-chat-surface-alt, #1a1d23)}.render-default-fallback__label{font-size:12px;color:var(--ngaf-chat-text-muted, #9aa0aa);margin-bottom:10px;display:flex;align-items:center;gap:6px}.render-default-fallback__rows{display:flex;flex-direction:column;gap:8px}.render-default-fallback__row{height:10px;border-radius:5px;background:linear-gradient(90deg,var(--ngaf-chat-separator, #303540) 0%,color-mix(in srgb,var(--ngaf-chat-separator, #303540) 70%,transparent) 50%,var(--ngaf-chat-separator, #303540) 100%);background-size:200% 100%;animation:render-default-fallback-shimmer 1.4s ease-in-out infinite}.render-default-fallback__row:nth-child(1){width:70%}.render-default-fallback__row:nth-child(2){width:90%}.render-default-fallback__row:nth-child(3){width:50%}@keyframes render-default-fallback-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
28
|
+
}
|
|
29
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: DefaultFallbackComponent, decorators: [{
|
|
30
|
+
type: Component,
|
|
31
|
+
args: [{ selector: 'render-default-fallback', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
32
|
+
<div class="render-default-fallback" role="status" aria-live="polite">
|
|
33
|
+
<div class="render-default-fallback__label">
|
|
34
|
+
<span aria-hidden="true">✨</span>
|
|
35
|
+
<span>Building UI…</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="render-default-fallback__rows">
|
|
38
|
+
<div class="render-default-fallback__row"></div>
|
|
39
|
+
<div class="render-default-fallback__row"></div>
|
|
40
|
+
<div class="render-default-fallback__row"></div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
`, styles: [":host{display:block;width:100%}.render-default-fallback{border:1px solid var(--ngaf-chat-separator, #303540);border-radius:10px;padding:14px;background:var(--ngaf-chat-surface-alt, #1a1d23)}.render-default-fallback__label{font-size:12px;color:var(--ngaf-chat-text-muted, #9aa0aa);margin-bottom:10px;display:flex;align-items:center;gap:6px}.render-default-fallback__rows{display:flex;flex-direction:column;gap:8px}.render-default-fallback__row{height:10px;border-radius:5px;background:linear-gradient(90deg,var(--ngaf-chat-separator, #303540) 0%,color-mix(in srgb,var(--ngaf-chat-separator, #303540) 70%,transparent) 50%,var(--ngaf-chat-separator, #303540) 100%);background-size:200% 100%;animation:render-default-fallback-shimmer 1.4s ease-in-out infinite}.render-default-fallback__row:nth-child(1){width:70%}.render-default-fallback__row:nth-child(2){width:90%}.render-default-fallback__row:nth-child(3){width:50%}@keyframes render-default-fallback-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
|
|
44
|
+
}] });
|
|
45
|
+
|
|
46
|
+
function normalize(entry) {
|
|
47
|
+
// Bare Type — register with the default fallback.
|
|
48
|
+
if (typeof entry === 'function') {
|
|
49
|
+
return { component: entry, fallback: DefaultFallbackComponent };
|
|
50
|
+
}
|
|
51
|
+
// Object form — preserve component; use configured fallback or default.
|
|
52
|
+
return {
|
|
53
|
+
component: entry.component,
|
|
54
|
+
fallback: entry.fallback ?? DefaultFallbackComponent,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function defineAngularRegistry(componentMap) {
|
|
58
|
+
const map = new Map();
|
|
59
|
+
for (const [name, entry] of Object.entries(componentMap)) {
|
|
60
|
+
map.set(name, normalize(entry));
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
get: (name) => map.get(name)?.component,
|
|
64
|
+
getFallback: (name) => map.get(name)?.fallback,
|
|
65
|
+
names: () => [...map.keys()],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// SPDX-License-Identifier: MIT
|
|
70
|
+
function parsePointer(path) {
|
|
71
|
+
if (!path || path === '/')
|
|
72
|
+
return [];
|
|
73
|
+
return path.split('/').filter((_, i) => i > 0).map(s => s.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
74
|
+
}
|
|
75
|
+
function getByPath(obj, segments) {
|
|
76
|
+
let current = obj;
|
|
77
|
+
for (const seg of segments) {
|
|
78
|
+
if (current == null || typeof current !== 'object')
|
|
79
|
+
return undefined;
|
|
80
|
+
current = current[seg];
|
|
81
|
+
}
|
|
82
|
+
return current;
|
|
83
|
+
}
|
|
84
|
+
function setByPath(obj, segments, value) {
|
|
85
|
+
if (segments.length === 0)
|
|
86
|
+
return value;
|
|
87
|
+
const [head, ...rest] = segments;
|
|
88
|
+
if (Array.isArray(obj)) {
|
|
89
|
+
const index = Number(head);
|
|
90
|
+
const clone = [...obj];
|
|
91
|
+
clone[index] = setByPath(clone[index], rest, value);
|
|
92
|
+
return clone;
|
|
93
|
+
}
|
|
94
|
+
const record = (obj != null && typeof obj === 'object')
|
|
95
|
+
? { ...obj }
|
|
96
|
+
: {};
|
|
97
|
+
record[head] = setByPath(record[head], rest, value);
|
|
98
|
+
return record;
|
|
99
|
+
}
|
|
100
|
+
function signalStateStore(initialState = {}) {
|
|
101
|
+
const state = signal(initialState, ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
102
|
+
const listeners = new Set();
|
|
103
|
+
function notify() {
|
|
104
|
+
for (const listener of listeners)
|
|
105
|
+
listener();
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
get(path) {
|
|
109
|
+
return getByPath(state(), parsePointer(path));
|
|
110
|
+
},
|
|
111
|
+
set(path, value) {
|
|
112
|
+
const segments = parsePointer(path);
|
|
113
|
+
const current = getByPath(state(), segments);
|
|
114
|
+
if (current === value)
|
|
115
|
+
return;
|
|
116
|
+
state.set(setByPath(state(), segments, value));
|
|
117
|
+
notify();
|
|
118
|
+
},
|
|
119
|
+
update(updates) {
|
|
120
|
+
let current = state();
|
|
121
|
+
let changed = false;
|
|
122
|
+
for (const [path, value] of Object.entries(updates)) {
|
|
123
|
+
const segments = parsePointer(path);
|
|
124
|
+
const existing = getByPath(current, segments);
|
|
125
|
+
if (existing !== value) {
|
|
126
|
+
current = setByPath(current, segments, value);
|
|
127
|
+
changed = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (changed) {
|
|
131
|
+
state.set(current);
|
|
132
|
+
notify();
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
getSnapshot() {
|
|
136
|
+
return state();
|
|
137
|
+
},
|
|
138
|
+
subscribe(listener) {
|
|
139
|
+
listeners.add(listener);
|
|
140
|
+
return () => listeners.delete(listener);
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// SPDX-License-Identifier: MIT
|
|
146
|
+
const RENDER_LIFECYCLE = new InjectionToken('RENDER_LIFECYCLE');
|
|
147
|
+
|
|
148
|
+
// SPDX-License-Identifier: MIT
|
|
149
|
+
/**
|
|
150
|
+
* Provided by `provideRender()` — opt-in. Scope follows the consumer's
|
|
151
|
+
* `provideRender` call (root-scoped by default, sub-tree if `provideRender`
|
|
152
|
+
* is in a sub-injector).
|
|
153
|
+
*/
|
|
154
|
+
class RenderLifecycleService {
|
|
155
|
+
_firstMountAt = signal(null, ...(ngDevMode ? [{ debugName: "_firstMountAt" }] : []));
|
|
156
|
+
_mountCount = signal(0, ...(ngDevMode ? [{ debugName: "_mountCount" }] : []));
|
|
157
|
+
_lastMountAt = signal(null, ...(ngDevMode ? [{ debugName: "_lastMountAt" }] : []));
|
|
158
|
+
_lastStateChangeAt = signal(null, ...(ngDevMode ? [{ debugName: "_lastStateChangeAt" }] : []));
|
|
159
|
+
_lastHandlerInvokedAt = signal(null, ...(ngDevMode ? [{ debugName: "_lastHandlerInvokedAt" }] : []));
|
|
160
|
+
firstMountAt = this._firstMountAt.asReadonly();
|
|
161
|
+
mountCount = this._mountCount.asReadonly();
|
|
162
|
+
lastMountAt = this._lastMountAt.asReadonly();
|
|
163
|
+
lastStateChangeAt = this._lastStateChangeAt.asReadonly();
|
|
164
|
+
lastHandlerInvokedAt = this._lastHandlerInvokedAt.asReadonly();
|
|
165
|
+
notifyLifecycle(event) {
|
|
166
|
+
if (event.type === 'mounted') {
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
if (this._firstMountAt() === null) {
|
|
169
|
+
this._firstMountAt.set({ kind: event.kind, elementType: event.elementType, at: now });
|
|
170
|
+
}
|
|
171
|
+
this._mountCount.update((c) => c + 1);
|
|
172
|
+
this._lastMountAt.set(now);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
notifyStateChange() {
|
|
176
|
+
this._lastStateChangeAt.set(Date.now());
|
|
177
|
+
}
|
|
178
|
+
notifyHandlerInvoked(action) {
|
|
179
|
+
this._lastHandlerInvokedAt.set({ action, at: Date.now() });
|
|
180
|
+
}
|
|
181
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RenderLifecycleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
182
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RenderLifecycleService });
|
|
183
|
+
}
|
|
184
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RenderLifecycleService, decorators: [{
|
|
185
|
+
type: Injectable
|
|
186
|
+
}] });
|
|
187
|
+
|
|
188
|
+
// SPDX-License-Identifier: MIT
|
|
189
|
+
const RENDER_CONFIG = new InjectionToken('RENDER_CONFIG');
|
|
190
|
+
function provideRender(config) {
|
|
191
|
+
return makeEnvironmentProviders([
|
|
192
|
+
{ provide: RENDER_CONFIG, useValue: config },
|
|
193
|
+
RenderLifecycleService,
|
|
194
|
+
{ provide: RENDER_LIFECYCLE, useExisting: RenderLifecycleService },
|
|
195
|
+
]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildPropResolutionContext(store, repeatScope, functions) {
|
|
199
|
+
const ctx = {
|
|
200
|
+
stateModel: store.getSnapshot(),
|
|
201
|
+
};
|
|
202
|
+
if (repeatScope) {
|
|
203
|
+
ctx.repeatItem = repeatScope.item;
|
|
204
|
+
ctx.repeatIndex = repeatScope.index;
|
|
205
|
+
ctx.repeatBasePath = repeatScope.basePath;
|
|
206
|
+
}
|
|
207
|
+
if (functions) {
|
|
208
|
+
ctx.functions = functions;
|
|
209
|
+
}
|
|
210
|
+
return ctx;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// SPDX-License-Identifier: MIT
|
|
214
|
+
/** Magic prefix on `emit()` strings that catalog components use to
|
|
215
|
+
* write back to the data model (binding `path` and the new value). The
|
|
216
|
+
* render-element's emitFn intercepts this and writes via the state
|
|
217
|
+
* store, sidestepping the normal `el.on[event]` handler binding which
|
|
218
|
+
* the catalog components have no way to declare for arbitrary paths. */
|
|
219
|
+
const A2UI_DATAMODEL_PREFIX = 'a2ui:datamodel:';
|
|
220
|
+
/** Cache of declared input names per component class. NgComponentOutlet
|
|
221
|
+
* passes every key in its `inputs` prop to the target; Angular dev mode
|
|
222
|
+
* raises NG0303 for any input the component doesn't declare. We strip
|
|
223
|
+
* undeclared keys before mounting so simple view components (`StatCard`,
|
|
224
|
+
* `Container`, etc.) don't get spammed with framework-only inputs
|
|
225
|
+
* (`bindings`, `emit`, `loading`, `childKeys`, `spec`) they ignore. */
|
|
226
|
+
/** `null` means reflection failed (likely uncompiled / non-component) — in
|
|
227
|
+
* that case we pass inputs through unmodified rather than swallow them.
|
|
228
|
+
* An empty Set means the component genuinely declares zero inputs (e.g. a
|
|
229
|
+
* pure presentational fallback) and ALL keys should be dropped. */
|
|
230
|
+
const declaredInputsCache = new WeakMap();
|
|
231
|
+
function getDeclaredInputs(cls) {
|
|
232
|
+
if (declaredInputsCache.has(cls))
|
|
233
|
+
return declaredInputsCache.get(cls);
|
|
234
|
+
const meta = reflectComponentType(cls);
|
|
235
|
+
const result = meta ? new Set(meta.inputs.map(i => i.templateName)) : null;
|
|
236
|
+
declaredInputsCache.set(cls, result);
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
function filterInputsForClass(cls, inputs) {
|
|
240
|
+
if (!cls)
|
|
241
|
+
return inputs;
|
|
242
|
+
const declared = getDeclaredInputs(cls);
|
|
243
|
+
if (declared === null)
|
|
244
|
+
return inputs;
|
|
245
|
+
const out = {};
|
|
246
|
+
for (const [k, v] of Object.entries(inputs)) {
|
|
247
|
+
if (declared.has(k))
|
|
248
|
+
out[k] = v;
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
/** Best-effort string→typed coercion for datamodel writes. Catalog
|
|
253
|
+
* components emit raw string values; the underlying state may have
|
|
254
|
+
* been declared as number/boolean/array, and consumers reading the
|
|
255
|
+
* resolved props expect the correct type. */
|
|
256
|
+
function coerceValue(raw) {
|
|
257
|
+
if (raw === '')
|
|
258
|
+
return '';
|
|
259
|
+
if (raw === 'true')
|
|
260
|
+
return true;
|
|
261
|
+
if (raw === 'false')
|
|
262
|
+
return false;
|
|
263
|
+
// JSON-array passthrough (MultipleChoice emits stringified arrays)
|
|
264
|
+
if (raw.startsWith('[') && raw.endsWith(']')) {
|
|
265
|
+
try {
|
|
266
|
+
return JSON.parse(raw);
|
|
267
|
+
}
|
|
268
|
+
catch { /* fall through */ }
|
|
269
|
+
}
|
|
270
|
+
// Numeric — only if the entire string parses cleanly as a number
|
|
271
|
+
if (/^-?\d+(?:\.\d+)?$/.test(raw)) {
|
|
272
|
+
const n = Number(raw);
|
|
273
|
+
if (!Number.isNaN(n))
|
|
274
|
+
return n;
|
|
275
|
+
}
|
|
276
|
+
return raw;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Recursive element renderer.
|
|
280
|
+
*
|
|
281
|
+
* For each element key it:
|
|
282
|
+
* 1. Looks up the UIElement from spec.elements
|
|
283
|
+
* 2. Resolves the component class from the registry
|
|
284
|
+
* 3. Evaluates visibility
|
|
285
|
+
* 4. Resolves prop expressions and bindings
|
|
286
|
+
* 5. Renders via NgComponentOutlet with resolved inputs
|
|
287
|
+
*
|
|
288
|
+
* For elements with `repeat`, it iterates over the state array,
|
|
289
|
+
* creating a child Injector with RepeatScope for each item.
|
|
290
|
+
*/
|
|
291
|
+
class RenderElementComponent {
|
|
292
|
+
elementKey = input.required(...(ngDevMode ? [{ debugName: "elementKey" }] : []));
|
|
293
|
+
spec = input.required(...(ngDevMode ? [{ debugName: "spec" }] : []));
|
|
294
|
+
ctx = inject(RENDER_CONTEXT);
|
|
295
|
+
repeatScope = inject(REPEAT_SCOPE, { optional: true });
|
|
296
|
+
parentInjector = inject(Injector);
|
|
297
|
+
destroyRef = inject(DestroyRef);
|
|
298
|
+
constructor() {
|
|
299
|
+
this.destroyRef.onDestroy(() => {
|
|
300
|
+
const el = this.element();
|
|
301
|
+
if (el && el['lifecycle'] && this.ctx.emitEvent) {
|
|
302
|
+
this.ctx.emitEvent({
|
|
303
|
+
type: 'lifecycle',
|
|
304
|
+
event: 'destroyed',
|
|
305
|
+
scope: 'element',
|
|
306
|
+
elementKey: this.elementKey(),
|
|
307
|
+
elementType: el.type,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
// Latch mountedReal=true once the real component is selected. Lives in
|
|
312
|
+
// an effect (not the computed) because Angular forbids signal writes
|
|
313
|
+
// inside computed — they're for derivation only. Effects are the
|
|
314
|
+
// idiomatic place for "signal change → signal write" side effects.
|
|
315
|
+
effect(() => {
|
|
316
|
+
if (this.mountedReal())
|
|
317
|
+
return;
|
|
318
|
+
const el = this.element();
|
|
319
|
+
if (!el)
|
|
320
|
+
return;
|
|
321
|
+
// Only latch when notReady is false AND a real component is registered.
|
|
322
|
+
if (!this.notReady() && this.ctx.registry.get(el.type)) {
|
|
323
|
+
this.mountedReal.set(true);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
ngOnInit() {
|
|
328
|
+
const el = this.element();
|
|
329
|
+
if (el && el['lifecycle'] && this.ctx.emitEvent) {
|
|
330
|
+
this.ctx.emitEvent({
|
|
331
|
+
type: 'lifecycle',
|
|
332
|
+
event: 'mounted',
|
|
333
|
+
scope: 'element',
|
|
334
|
+
elementKey: this.elementKey(),
|
|
335
|
+
elementType: el.type,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/** The UIElement definition from the spec. Only propagates when reference changes. */
|
|
340
|
+
element = computed(() => this.spec()?.elements?.[this.elementKey()], { ...(ngDevMode ? { debugName: "element" } : {}), equal: Object.is });
|
|
341
|
+
/** The Angular component class for this element type. */
|
|
342
|
+
componentClass = computed(() => {
|
|
343
|
+
const el = this.element();
|
|
344
|
+
if (!el)
|
|
345
|
+
return null;
|
|
346
|
+
return this.ctx.registry.get(el.type) ?? null;
|
|
347
|
+
}, ...(ngDevMode ? [{ debugName: "componentClass" }] : []));
|
|
348
|
+
/** Prop resolution context built from store + repeat scope. */
|
|
349
|
+
propCtx = computed(() => buildPropResolutionContext(this.ctx.store, this.repeatScope ?? undefined, this.ctx.functions), ...(ngDevMode ? [{ debugName: "propCtx" }] : []));
|
|
350
|
+
/** Once real mounts, never revert to fallback even if a state-bound
|
|
351
|
+
* prop later becomes undefined. Per-instance monotonic gate. */
|
|
352
|
+
mountedReal = signal(false, ...(ngDevMode ? [{ debugName: "mountedReal" }] : []));
|
|
353
|
+
/** True when ANY resolved prop value is undefined (i.e. a state
|
|
354
|
+
* binding points at a path the store hasn't populated). Framework-
|
|
355
|
+
* injected keys (bindings, emit, loading, childKeys, spec) are
|
|
356
|
+
* excluded — only consumer-resolved props matter for readiness. */
|
|
357
|
+
notReady = computed(() => {
|
|
358
|
+
if (this.mountedReal())
|
|
359
|
+
return false;
|
|
360
|
+
const el = this.element();
|
|
361
|
+
if (!el || !el.props)
|
|
362
|
+
return false;
|
|
363
|
+
const resolved = resolveElementProps(el.props, this.propCtx());
|
|
364
|
+
for (const v of Object.values(resolved)) {
|
|
365
|
+
if (v === undefined)
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
return false;
|
|
369
|
+
}, ...(ngDevMode ? [{ debugName: "notReady" }] : []));
|
|
370
|
+
/** Picks fallback or real based on notReady. The mountedReal latch is
|
|
371
|
+
* driven by a constructor effect (not this computed) — Angular forbids
|
|
372
|
+
* signal writes inside computed. */
|
|
373
|
+
mountClass = computed(() => {
|
|
374
|
+
const el = this.element();
|
|
375
|
+
if (!el)
|
|
376
|
+
return null;
|
|
377
|
+
const real = this.ctx.registry.get(el.type) ?? null;
|
|
378
|
+
if (this.notReady()) {
|
|
379
|
+
return this.ctx.registry.getFallback(el.type) ?? null;
|
|
380
|
+
}
|
|
381
|
+
return real;
|
|
382
|
+
}, ...(ngDevMode ? [{ debugName: "mountClass" }] : []));
|
|
383
|
+
/** Whether the element is visible (non-repeat path). */
|
|
384
|
+
visible = computed(() => {
|
|
385
|
+
const el = this.element();
|
|
386
|
+
if (!el)
|
|
387
|
+
return false;
|
|
388
|
+
if (this.mountClass() === null)
|
|
389
|
+
return false;
|
|
390
|
+
return evaluateVisibility(el.visible, this.propCtx());
|
|
391
|
+
}, ...(ngDevMode ? [{ debugName: "visible" }] : []));
|
|
392
|
+
/** Emit function that delegates to context handlers AND handles the
|
|
393
|
+
* canonical `a2ui:datamodel:<path>:<value>` write-back protocol that
|
|
394
|
+
* input components (TextField, MultipleChoice, CheckBox, Slider,
|
|
395
|
+
* DateTimeInput) emit when the user changes their value. The render
|
|
396
|
+
* lib's state store is the single source of truth for in-surface UI
|
|
397
|
+
* state; writing through it triggers re-render with the new value
|
|
398
|
+
* and re-evaluates any path-bound props (validation, computed
|
|
399
|
+
* visibility, etc.).
|
|
400
|
+
*
|
|
401
|
+
* The string format is `a2ui:datamodel:<path>:<value>` where:
|
|
402
|
+
* - `<path>` is a JSON-Pointer-style path (e.g. `/name`, `/form/email`)
|
|
403
|
+
* - `<value>` is the raw value rendered as a string. We attempt to
|
|
404
|
+
* coerce numeric and boolean literals back to their typed form
|
|
405
|
+
* so downstream consumers see correct types; arrays come through
|
|
406
|
+
* as JSON-stringified payloads (catalog components emit them via
|
|
407
|
+
* `JSON.stringify`).
|
|
408
|
+
*/
|
|
409
|
+
emitFn = (event) => {
|
|
410
|
+
if (event.startsWith(A2UI_DATAMODEL_PREFIX)) {
|
|
411
|
+
this.applyDatamodelWrite(event);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const el = this.element();
|
|
415
|
+
if (!el?.on)
|
|
416
|
+
return;
|
|
417
|
+
const binding = el.on[event];
|
|
418
|
+
if (!binding)
|
|
419
|
+
return;
|
|
420
|
+
const bindings = Array.isArray(binding) ? binding : [binding];
|
|
421
|
+
for (const b of bindings) {
|
|
422
|
+
const handler = this.ctx.handlers?.[b.action];
|
|
423
|
+
if (handler) {
|
|
424
|
+
runInInjectionContext(this.parentInjector, () => handler(b.params ?? {}));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
applyDatamodelWrite(event) {
|
|
429
|
+
// Strip the prefix, then split path and value at the last `:` —
|
|
430
|
+
// path may itself contain `:` characters (rare but legal in
|
|
431
|
+
// JSON-Pointer per RFC 6901), and values can certainly contain
|
|
432
|
+
// them (URLs, time strings). Catalog components emit
|
|
433
|
+
// `a2ui:datamodel:<path>:<value>` where path is the binding's
|
|
434
|
+
// path-ref (usually starts with `/`); split the LAST `:` because
|
|
435
|
+
// the value is the only field guaranteed to come last.
|
|
436
|
+
const rest = event.slice(A2UI_DATAMODEL_PREFIX.length);
|
|
437
|
+
const lastColon = rest.lastIndexOf(':');
|
|
438
|
+
if (lastColon === -1)
|
|
439
|
+
return;
|
|
440
|
+
const path = rest.slice(0, lastColon);
|
|
441
|
+
const rawValue = rest.slice(lastColon + 1);
|
|
442
|
+
if (!path)
|
|
443
|
+
return;
|
|
444
|
+
const store = this.ctx.store;
|
|
445
|
+
if (!store)
|
|
446
|
+
return;
|
|
447
|
+
store.set(path, coerceValue(rawValue));
|
|
448
|
+
}
|
|
449
|
+
/** Resolved inputs for non-repeat elements. */
|
|
450
|
+
resolvedInputs = computed(() => {
|
|
451
|
+
const el = this.element();
|
|
452
|
+
if (!el)
|
|
453
|
+
return {};
|
|
454
|
+
const ctx = this.propCtx();
|
|
455
|
+
const resolved = resolveElementProps(el.props ?? {}, ctx);
|
|
456
|
+
const bindings = resolveBindings(el.props ?? {}, ctx);
|
|
457
|
+
return {
|
|
458
|
+
...resolved,
|
|
459
|
+
bindings,
|
|
460
|
+
emit: this.emitFn,
|
|
461
|
+
loading: this.ctx.loading ?? false,
|
|
462
|
+
childKeys: el.children ?? [],
|
|
463
|
+
spec: this.spec(),
|
|
464
|
+
};
|
|
465
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedInputs" }] : []));
|
|
466
|
+
/** `resolvedInputs` filtered down to keys the target component actually
|
|
467
|
+
* declares — silences NG0303 dev-mode warnings from framework-only
|
|
468
|
+
* inputs (bindings/emit/loading/childKeys/spec) passed to simple view
|
|
469
|
+
* components that don't declare them. */
|
|
470
|
+
filteredResolvedInputs = computed(() => filterInputsForClass(this.mountClass(), this.resolvedInputs()), ...(ngDevMode ? [{ debugName: "filteredResolvedInputs" }] : []));
|
|
471
|
+
// --- Repeat support ---
|
|
472
|
+
/** Items from the state array for repeat elements. */
|
|
473
|
+
repeatItems = computed(() => {
|
|
474
|
+
const el = this.element();
|
|
475
|
+
if (!el?.repeat)
|
|
476
|
+
return [];
|
|
477
|
+
const items = this.ctx.store.get(el.repeat.statePath);
|
|
478
|
+
return Array.isArray(items) ? items : [];
|
|
479
|
+
}, ...(ngDevMode ? [{ debugName: "repeatItems" }] : []));
|
|
480
|
+
/** One RepeatScope per repeat item, shared between injectors and inputs. */
|
|
481
|
+
repeatScopes = computed(() => {
|
|
482
|
+
const el = this.element();
|
|
483
|
+
if (!el?.repeat)
|
|
484
|
+
return [];
|
|
485
|
+
return this.repeatItems().map((item, index) => ({
|
|
486
|
+
item,
|
|
487
|
+
index,
|
|
488
|
+
basePath: `${el.repeat.statePath}/${index}`,
|
|
489
|
+
}));
|
|
490
|
+
}, ...(ngDevMode ? [{ debugName: "repeatScopes" }] : []));
|
|
491
|
+
/** One child Injector per repeat item, providing RepeatScope. */
|
|
492
|
+
repeatInjectors = computed(() => {
|
|
493
|
+
return this.repeatScopes().map(scope => Injector.create({
|
|
494
|
+
providers: [{ provide: REPEAT_SCOPE, useValue: scope }],
|
|
495
|
+
parent: this.parentInjector,
|
|
496
|
+
}));
|
|
497
|
+
}, ...(ngDevMode ? [{ debugName: "repeatInjectors" }] : []));
|
|
498
|
+
/** Resolved inputs for each repeat item. */
|
|
499
|
+
repeatInputs = computed(() => {
|
|
500
|
+
const el = this.element();
|
|
501
|
+
if (!el?.repeat)
|
|
502
|
+
return [];
|
|
503
|
+
return this.repeatScopes().map(scope => {
|
|
504
|
+
const ctx = buildPropResolutionContext(this.ctx.store, scope, this.ctx.functions);
|
|
505
|
+
const resolved = resolveElementProps(el.props ?? {}, ctx);
|
|
506
|
+
const bindings = resolveBindings(el.props ?? {}, ctx);
|
|
507
|
+
return {
|
|
508
|
+
...resolved,
|
|
509
|
+
bindings,
|
|
510
|
+
emit: this.emitFn,
|
|
511
|
+
loading: this.ctx.loading ?? false,
|
|
512
|
+
childKeys: el.children ?? [],
|
|
513
|
+
spec: this.spec(),
|
|
514
|
+
};
|
|
515
|
+
});
|
|
516
|
+
}, ...(ngDevMode ? [{ debugName: "repeatInputs" }] : []));
|
|
517
|
+
/** `repeatInputs` filtered per-item to declared component inputs. */
|
|
518
|
+
filteredRepeatInputs = computed(() => {
|
|
519
|
+
const cls = this.mountClass();
|
|
520
|
+
return this.repeatInputs().map(inputs => filterInputsForClass(cls, inputs));
|
|
521
|
+
}, ...(ngDevMode ? [{ debugName: "filteredRepeatInputs" }] : []));
|
|
522
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RenderElementComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
523
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: RenderElementComponent, isStandalone: true, selector: "render-element", inputs: { elementKey: { classPropertyName: "elementKey", publicName: "elementKey", isSignal: true, isRequired: true, transformFunction: null }, spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
524
|
+
@if (!element()?.repeat) {
|
|
525
|
+
@if (visible()) {
|
|
526
|
+
<ng-container
|
|
527
|
+
*ngComponentOutlet="mountClass(); inputs: filteredResolvedInputs(); injector: parentInjector"
|
|
528
|
+
/>
|
|
529
|
+
}
|
|
530
|
+
} @else {
|
|
531
|
+
@for (repeatInjector of repeatInjectors(); track $index) {
|
|
532
|
+
<ng-container
|
|
533
|
+
*ngComponentOutlet="mountClass(); inputs: filteredRepeatInputs()[$index]; injector: repeatInjector"
|
|
534
|
+
/>
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
538
|
+
}
|
|
539
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RenderElementComponent, decorators: [{
|
|
540
|
+
type: Component,
|
|
541
|
+
args: [{
|
|
542
|
+
selector: 'render-element',
|
|
543
|
+
standalone: true,
|
|
544
|
+
imports: [NgComponentOutlet],
|
|
545
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
546
|
+
template: `
|
|
547
|
+
@if (!element()?.repeat) {
|
|
548
|
+
@if (visible()) {
|
|
549
|
+
<ng-container
|
|
550
|
+
*ngComponentOutlet="mountClass(); inputs: filteredResolvedInputs(); injector: parentInjector"
|
|
551
|
+
/>
|
|
552
|
+
}
|
|
553
|
+
} @else {
|
|
554
|
+
@for (repeatInjector of repeatInjectors(); track $index) {
|
|
555
|
+
<ng-container
|
|
556
|
+
*ngComponentOutlet="mountClass(); inputs: filteredRepeatInputs()[$index]; injector: repeatInjector"
|
|
557
|
+
/>
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
`,
|
|
561
|
+
}]
|
|
562
|
+
}], ctorParameters: () => [], propDecorators: { elementKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementKey", required: true }] }], spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: true }] }] } });
|
|
563
|
+
|
|
564
|
+
// SPDX-License-Identifier: MIT
|
|
565
|
+
/**
|
|
566
|
+
* Top-level entry point for rendering a json-render spec.
|
|
567
|
+
*
|
|
568
|
+
* Accepts the spec, registry, store, functions, handlers, and loading
|
|
569
|
+
* as inputs. Provides `RENDER_CONTEXT` to child `RenderElementComponent`
|
|
570
|
+
* instances via `viewProviders`.
|
|
571
|
+
*
|
|
572
|
+
* Falls back to `RENDER_CONFIG` (from `provideRender()`) for registry
|
|
573
|
+
* and store defaults when inputs are not provided.
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```html
|
|
577
|
+
* <render-spec [spec]="spec()" [registry]="registry" [store]="store" />
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
class RenderSpecComponent {
|
|
581
|
+
spec = input(null, ...(ngDevMode ? [{ debugName: "spec" }] : []));
|
|
582
|
+
registry = input(undefined, ...(ngDevMode ? [{ debugName: "registry" }] : []));
|
|
583
|
+
store = input(undefined, ...(ngDevMode ? [{ debugName: "store" }] : []));
|
|
584
|
+
functions = input(undefined, ...(ngDevMode ? [{ debugName: "functions" }] : []));
|
|
585
|
+
handlers = input(undefined, ...(ngDevMode ? [{ debugName: "handlers" }] : []));
|
|
586
|
+
loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
587
|
+
events = output();
|
|
588
|
+
config = inject(RENDER_CONFIG, { optional: true });
|
|
589
|
+
destroyRef = inject(DestroyRef);
|
|
590
|
+
lifecycle = inject(RenderLifecycleService, { optional: true });
|
|
591
|
+
/** Internal store, lazily created once and reused across spec changes. */
|
|
592
|
+
_internalStore;
|
|
593
|
+
getOrCreateInternalStore() {
|
|
594
|
+
if (!this._internalStore) {
|
|
595
|
+
this._internalStore = signalStateStore(this.spec()?.state ?? {});
|
|
596
|
+
}
|
|
597
|
+
return this._internalStore;
|
|
598
|
+
}
|
|
599
|
+
/** Resolved store: input > config > internal (from spec.state). */
|
|
600
|
+
resolvedStore = computed(() => {
|
|
601
|
+
const inputStore = this.store();
|
|
602
|
+
if (inputStore)
|
|
603
|
+
return inputStore;
|
|
604
|
+
const configStore = this.config?.store;
|
|
605
|
+
if (configStore)
|
|
606
|
+
return configStore;
|
|
607
|
+
return this.getOrCreateInternalStore();
|
|
608
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedStore" }] : []));
|
|
609
|
+
/** Resolved registry: input > config. */
|
|
610
|
+
resolvedRegistry = computed(() => {
|
|
611
|
+
const inputRegistry = this.registry();
|
|
612
|
+
if (inputRegistry)
|
|
613
|
+
return inputRegistry;
|
|
614
|
+
const configRegistry = this.config?.registry;
|
|
615
|
+
if (configRegistry)
|
|
616
|
+
return configRegistry;
|
|
617
|
+
// Fallback: empty registry
|
|
618
|
+
return { get: () => undefined, getFallback: () => undefined, names: () => [] };
|
|
619
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedRegistry" }] : []));
|
|
620
|
+
/** Wraps input handlers to emit RenderHandlerEvent after execution. */
|
|
621
|
+
wrappedHandlers = computed(() => {
|
|
622
|
+
const inputHandlers = this.handlers() ?? this.config?.handlers;
|
|
623
|
+
if (!inputHandlers)
|
|
624
|
+
return undefined;
|
|
625
|
+
const wrapped = {};
|
|
626
|
+
for (const [name, handler] of Object.entries(inputHandlers)) {
|
|
627
|
+
wrapped[name] = (params) => {
|
|
628
|
+
const result = handler(params);
|
|
629
|
+
if (result instanceof Promise) {
|
|
630
|
+
result.then((r) => {
|
|
631
|
+
this.emitTapped({ type: 'handler', action: name, params, result: r });
|
|
632
|
+
}, () => {
|
|
633
|
+
this.emitTapped({ type: 'handler', action: name, params, result: undefined });
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
this.emitTapped({ type: 'handler', action: name, params, result });
|
|
638
|
+
}
|
|
639
|
+
return result;
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return wrapped;
|
|
643
|
+
}, ...(ngDevMode ? [{ debugName: "wrappedHandlers" }] : []));
|
|
644
|
+
/** Emits a RenderEvent through the events output and notifies the
|
|
645
|
+
* lifecycle service (single tap point — all events flow through here). */
|
|
646
|
+
emitTapped = (event) => {
|
|
647
|
+
this.events.emit(event);
|
|
648
|
+
if (!this.lifecycle)
|
|
649
|
+
return;
|
|
650
|
+
switch (event.type) {
|
|
651
|
+
case 'lifecycle':
|
|
652
|
+
this.lifecycle.notifyLifecycle({
|
|
653
|
+
kind: event.scope,
|
|
654
|
+
type: event.event,
|
|
655
|
+
elementType: event.elementType,
|
|
656
|
+
});
|
|
657
|
+
break;
|
|
658
|
+
case 'stateChange':
|
|
659
|
+
this.lifecycle.notifyStateChange();
|
|
660
|
+
break;
|
|
661
|
+
case 'handler':
|
|
662
|
+
this.lifecycle.notifyHandlerInvoked(event.action);
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
/** Emits a RenderEvent through the events output. */
|
|
667
|
+
emitEvent = (event) => {
|
|
668
|
+
this.emitTapped(event);
|
|
669
|
+
};
|
|
670
|
+
/** The RenderContext provided to children via viewProviders. */
|
|
671
|
+
_context = computed(() => ({
|
|
672
|
+
registry: this.resolvedRegistry(),
|
|
673
|
+
store: this.resolvedStore(),
|
|
674
|
+
functions: this.functions() ?? this.config?.functions,
|
|
675
|
+
handlers: this.wrappedHandlers(),
|
|
676
|
+
emitEvent: this.emitEvent,
|
|
677
|
+
loading: this.loading(),
|
|
678
|
+
}), ...(ngDevMode ? [{ debugName: "_context" }] : []));
|
|
679
|
+
constructor() {
|
|
680
|
+
// Subscribe to store changes and emit state change events
|
|
681
|
+
effect(() => {
|
|
682
|
+
const store = this.resolvedStore();
|
|
683
|
+
const unsub = store.subscribe(() => {
|
|
684
|
+
const snapshot = store.getSnapshot();
|
|
685
|
+
this.emitTapped({
|
|
686
|
+
type: 'stateChange',
|
|
687
|
+
path: '/',
|
|
688
|
+
value: snapshot,
|
|
689
|
+
snapshot,
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
this.destroyRef.onDestroy(unsub);
|
|
693
|
+
});
|
|
694
|
+
this.destroyRef.onDestroy(() => {
|
|
695
|
+
this.emitTapped({ type: 'lifecycle', event: 'destroyed', scope: 'spec' });
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
ngOnInit() {
|
|
699
|
+
this.emitTapped({ type: 'lifecycle', event: 'mounted', scope: 'spec' });
|
|
700
|
+
}
|
|
701
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RenderSpecComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
702
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: RenderSpecComponent, isStandalone: true, selector: "render-spec", inputs: { spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: false, transformFunction: null }, registry: { classPropertyName: "registry", publicName: "registry", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, functions: { classPropertyName: "functions", publicName: "functions", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { events: "events" }, ngImport: i0, template: `
|
|
703
|
+
@if (spec()?.root; as rootKey) {
|
|
704
|
+
<render-element [elementKey]="rootKey" [spec]="spec()!" />
|
|
705
|
+
}
|
|
706
|
+
`, isInline: true, dependencies: [{ kind: "component", type: RenderElementComponent, selector: "render-element", inputs: ["elementKey", "spec"] }], viewProviders: [
|
|
707
|
+
{
|
|
708
|
+
provide: RENDER_CONTEXT,
|
|
709
|
+
useFactory: () => inject(RenderSpecComponent)._context(),
|
|
710
|
+
},
|
|
711
|
+
], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
712
|
+
}
|
|
713
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RenderSpecComponent, decorators: [{
|
|
714
|
+
type: Component,
|
|
715
|
+
args: [{
|
|
716
|
+
selector: 'render-spec',
|
|
717
|
+
standalone: true,
|
|
718
|
+
imports: [RenderElementComponent],
|
|
719
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
720
|
+
viewProviders: [
|
|
721
|
+
{
|
|
722
|
+
provide: RENDER_CONTEXT,
|
|
723
|
+
useFactory: () => inject(RenderSpecComponent)._context(),
|
|
724
|
+
},
|
|
725
|
+
],
|
|
726
|
+
template: `
|
|
727
|
+
@if (spec()?.root; as rootKey) {
|
|
728
|
+
<render-element [elementKey]="rootKey" [spec]="spec()!" />
|
|
729
|
+
}
|
|
730
|
+
`,
|
|
731
|
+
}]
|
|
732
|
+
}], ctorParameters: () => [], propDecorators: { spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: false }] }], registry: [{ type: i0.Input, args: [{ isSignal: true, alias: "registry", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], functions: [{ type: i0.Input, args: [{ isSignal: true, alias: "functions", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], events: [{ type: i0.Output, args: ["events"] }] } });
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Creates a view registry from a name → component map.
|
|
736
|
+
*/
|
|
737
|
+
function views(map) {
|
|
738
|
+
return Object.freeze({ ...map });
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Adds views to a registry without overwriting existing entries.
|
|
742
|
+
* New keys are added; keys that already exist in `base` are preserved.
|
|
743
|
+
*/
|
|
744
|
+
function withViews(base, additions) {
|
|
745
|
+
return Object.freeze({ ...additions, ...base });
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Removes views from a registry by name.
|
|
749
|
+
*/
|
|
750
|
+
function withoutViews(base, ...names) {
|
|
751
|
+
const result = { ...base };
|
|
752
|
+
for (const name of names)
|
|
753
|
+
delete result[name];
|
|
754
|
+
return Object.freeze(result);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Converts a ViewRegistry to an AngularRegistry for use with RenderSpecComponent.
|
|
758
|
+
*/
|
|
759
|
+
function toRenderRegistry(registry) {
|
|
760
|
+
return defineAngularRegistry(registry);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// SPDX-License-Identifier: MIT
|
|
764
|
+
const VIEW_REGISTRY = new InjectionToken('VIEW_REGISTRY');
|
|
765
|
+
function provideViews(registry) {
|
|
766
|
+
return makeEnvironmentProviders([
|
|
767
|
+
{ provide: VIEW_REGISTRY, useValue: registry },
|
|
768
|
+
]);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// SPDX-License-Identifier: MIT
|
|
772
|
+
// Contexts
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Generated bundle index. Do not edit.
|
|
776
|
+
*/
|
|
777
|
+
|
|
778
|
+
export { DefaultFallbackComponent, RENDER_CONFIG, RENDER_CONTEXT, RENDER_LIFECYCLE, REPEAT_SCOPE, RenderElementComponent, RenderSpecComponent, VIEW_REGISTRY, defineAngularRegistry, provideRender, provideViews, signalStateStore, toRenderRegistry, views, withViews, withoutViews };
|
|
779
|
+
//# sourceMappingURL=threadplane-render.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threadplane-render.mjs","sources":["../../../../libs/render/src/lib/contexts/render-context.ts","../../../../libs/render/src/lib/contexts/repeat-scope.ts","../../../../libs/render/src/lib/default-fallback.component.ts","../../../../libs/render/src/lib/define-angular-registry.ts","../../../../libs/render/src/lib/signal-state-store.ts","../../../../libs/render/src/lib/lifecycle.ts","../../../../libs/render/src/lib/render-lifecycle.service.ts","../../../../libs/render/src/lib/provide-render.ts","../../../../libs/render/src/lib/internals/prop-signal.ts","../../../../libs/render/src/lib/render-element.component.ts","../../../../libs/render/src/lib/render-spec.component.ts","../../../../libs/render/src/lib/views.ts","../../../../libs/render/src/lib/provide-views.ts","../../../../libs/render/src/public-api.ts","../../../../libs/render/src/threadplane-render.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\nimport { InjectionToken } from '@angular/core';\nimport type { StateStore, ComputedFunction } from '@json-render/core';\nimport type { AngularRegistry } from '../render.types';\nimport type { RenderEvent } from '../render-event';\n\nexport interface RenderContext {\n registry: AngularRegistry;\n store: StateStore;\n functions?: Record<string, ComputedFunction>;\n handlers?: Record<string, (params: Record<string, unknown>) => unknown | Promise<unknown>>;\n emitEvent?: (event: RenderEvent) => void;\n loading?: boolean;\n}\n\nexport const RENDER_CONTEXT = new InjectionToken<RenderContext>('RENDER_CONTEXT');\n","// SPDX-License-Identifier: MIT\nimport { InjectionToken } from '@angular/core';\n\nexport interface RepeatScope {\n item: unknown;\n index: number;\n basePath: string;\n}\n\nexport const REPEAT_SCOPE = new InjectionToken<RepeatScope>('REPEAT_SCOPE');\n","// SPDX-License-Identifier: MIT\nimport { Component, ChangeDetectionStrategy } from '@angular/core';\n\n@Component({\n selector: 'render-default-fallback',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n styles: [`\n :host { display: block; width: 100%; }\n .render-default-fallback {\n border: 1px solid var(--ngaf-chat-separator, #303540);\n border-radius: 10px;\n padding: 14px;\n background: var(--ngaf-chat-surface-alt, #1a1d23);\n }\n .render-default-fallback__label {\n font-size: 12px;\n color: var(--ngaf-chat-text-muted, #9aa0aa);\n margin-bottom: 10px;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .render-default-fallback__rows {\n display: flex; flex-direction: column; gap: 8px;\n }\n .render-default-fallback__row {\n height: 10px; border-radius: 5px;\n background: linear-gradient(\n 90deg,\n var(--ngaf-chat-separator, #303540) 0%,\n color-mix(in srgb, var(--ngaf-chat-separator, #303540) 70%, transparent) 50%,\n var(--ngaf-chat-separator, #303540) 100%\n );\n background-size: 200% 100%;\n animation: render-default-fallback-shimmer 1.4s ease-in-out infinite;\n }\n .render-default-fallback__row:nth-child(1) { width: 70%; }\n .render-default-fallback__row:nth-child(2) { width: 90%; }\n .render-default-fallback__row:nth-child(3) { width: 50%; }\n @keyframes render-default-fallback-shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n `],\n template: `\n <div class=\"render-default-fallback\" role=\"status\" aria-live=\"polite\">\n <div class=\"render-default-fallback__label\">\n <span aria-hidden=\"true\">✨</span>\n <span>Building UI…</span>\n </div>\n <div class=\"render-default-fallback__rows\">\n <div class=\"render-default-fallback__row\"></div>\n <div class=\"render-default-fallback__row\"></div>\n <div class=\"render-default-fallback__row\"></div>\n </div>\n </div>\n `,\n})\nexport class DefaultFallbackComponent {}\n","// SPDX-License-Identifier: MIT\nimport { Type } from '@angular/core';\nimport type { AngularRegistry, RenderViewEntry } from './render.types';\nimport { DefaultFallbackComponent } from './default-fallback.component';\n\ntype RegistryInput = Record<string, Type<unknown> | RenderViewEntry>;\n\ninterface NormalizedEntry {\n component: Type<unknown>;\n fallback: Type<unknown>;\n}\n\nfunction normalize(entry: Type<unknown> | RenderViewEntry): NormalizedEntry {\n // Bare Type — register with the default fallback.\n if (typeof entry === 'function') {\n return { component: entry, fallback: DefaultFallbackComponent };\n }\n // Object form — preserve component; use configured fallback or default.\n return {\n component: entry.component,\n fallback: entry.fallback ?? DefaultFallbackComponent,\n };\n}\n\nexport function defineAngularRegistry(componentMap: RegistryInput): AngularRegistry {\n const map = new Map<string, NormalizedEntry>();\n for (const [name, entry] of Object.entries(componentMap)) {\n map.set(name, normalize(entry));\n }\n return {\n get: (name: string) => map.get(name)?.component,\n getFallback: (name: string) => map.get(name)?.fallback,\n names: () => [...map.keys()],\n };\n}\n","// SPDX-License-Identifier: MIT\nimport { signal } from '@angular/core';\nimport type { StateStore, StateModel } from '@json-render/core';\n\nfunction parsePointer(path: string): string[] {\n if (!path || path === '/') return [];\n return path.split('/').filter((_, i) => i > 0).map(s => s.replace(/~1/g, '/').replace(/~0/g, '~'));\n}\n\nfunction getByPath(obj: unknown, segments: string[]): unknown {\n let current: unknown = obj;\n for (const seg of segments) {\n if (current == null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[seg];\n }\n return current;\n}\n\nfunction setByPath(obj: unknown, segments: string[], value: unknown): unknown {\n if (segments.length === 0) return value;\n const [head, ...rest] = segments;\n\n if (Array.isArray(obj)) {\n const index = Number(head);\n const clone = [...obj];\n clone[index] = setByPath(clone[index], rest, value);\n return clone;\n }\n\n const record = (obj != null && typeof obj === 'object')\n ? { ...obj as Record<string, unknown> }\n : {} as Record<string, unknown>;\n record[head] = setByPath(record[head], rest, value);\n return record;\n}\n\nexport function signalStateStore(initialState: StateModel = {}): StateStore {\n const state = signal<StateModel>(initialState);\n const listeners = new Set<() => void>();\n\n function notify(): void {\n for (const listener of listeners) listener();\n }\n\n return {\n get(path: string): unknown {\n return getByPath(state(), parsePointer(path));\n },\n set(path: string, value: unknown): void {\n const segments = parsePointer(path);\n const current = getByPath(state(), segments);\n if (current === value) return;\n state.set(setByPath(state(), segments, value) as StateModel);\n notify();\n },\n update(updates: Record<string, unknown>): void {\n let current = state();\n let changed = false;\n for (const [path, value] of Object.entries(updates)) {\n const segments = parsePointer(path);\n const existing = getByPath(current, segments);\n if (existing !== value) {\n current = setByPath(current, segments, value) as StateModel;\n changed = true;\n }\n }\n if (changed) {\n state.set(current);\n notify();\n }\n },\n getSnapshot(): StateModel {\n return state();\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n","// SPDX-License-Identifier: MIT\nimport { InjectionToken, Signal } from '@angular/core';\n\nexport interface RenderLifecycle {\n /** First mount event in this render context. Sticky — does not reset. */\n readonly firstMountAt: Signal<{ kind: 'spec' | 'element'; elementType?: string; at: number } | null>;\n /** Total mount count since render context started. */\n readonly mountCount: Signal<number>;\n /** Epoch ms of the most recent mount event. */\n readonly lastMountAt: Signal<number | null>;\n /** Epoch ms of the most recent state-change event. */\n readonly lastStateChangeAt: Signal<number | null>;\n /** Most recent handler invocation. */\n readonly lastHandlerInvokedAt: Signal<{ action: string; at: number } | null>;\n}\n\nexport const RENDER_LIFECYCLE = new InjectionToken<RenderLifecycle>('RENDER_LIFECYCLE');\n","// SPDX-License-Identifier: MIT\nimport { Injectable, signal } from '@angular/core';\nimport type { RenderLifecycle } from './lifecycle';\n\n/**\n * Provided by `provideRender()` — opt-in. Scope follows the consumer's\n * `provideRender` call (root-scoped by default, sub-tree if `provideRender`\n * is in a sub-injector).\n */\n@Injectable()\nexport class RenderLifecycleService implements RenderLifecycle {\n private _firstMountAt = signal<{ kind: 'spec' | 'element'; elementType?: string; at: number } | null>(null);\n private _mountCount = signal(0);\n private _lastMountAt = signal<number | null>(null);\n private _lastStateChangeAt = signal<number | null>(null);\n private _lastHandlerInvokedAt = signal<{ action: string; at: number } | null>(null);\n\n readonly firstMountAt = this._firstMountAt.asReadonly();\n readonly mountCount = this._mountCount.asReadonly();\n readonly lastMountAt = this._lastMountAt.asReadonly();\n readonly lastStateChangeAt = this._lastStateChangeAt.asReadonly();\n readonly lastHandlerInvokedAt = this._lastHandlerInvokedAt.asReadonly();\n\n notifyLifecycle(event: { kind: 'spec' | 'element'; type: 'mounted' | 'destroyed'; elementType?: string }): void {\n if (event.type === 'mounted') {\n const now = Date.now();\n if (this._firstMountAt() === null) {\n this._firstMountAt.set({ kind: event.kind, elementType: event.elementType, at: now });\n }\n this._mountCount.update((c) => c + 1);\n this._lastMountAt.set(now);\n }\n }\n\n notifyStateChange(): void {\n this._lastStateChangeAt.set(Date.now());\n }\n\n notifyHandlerInvoked(action: string): void {\n this._lastHandlerInvokedAt.set({ action, at: Date.now() });\n }\n}\n","// SPDX-License-Identifier: MIT\nimport { InjectionToken, makeEnvironmentProviders } from '@angular/core';\nimport type { RenderConfig } from './render.types';\nimport { RENDER_LIFECYCLE } from './lifecycle';\nimport { RenderLifecycleService } from './render-lifecycle.service';\n\nexport const RENDER_CONFIG = new InjectionToken<RenderConfig>('RENDER_CONFIG');\n\nexport function provideRender(config: RenderConfig) {\n return makeEnvironmentProviders([\n { provide: RENDER_CONFIG, useValue: config },\n RenderLifecycleService,\n { provide: RENDER_LIFECYCLE, useExisting: RenderLifecycleService },\n ]);\n}\n","// SPDX-License-Identifier: MIT\nimport type { StateStore, ComputedFunction, PropResolutionContext } from '@json-render/core';\nimport type { RepeatScope } from '../contexts/repeat-scope';\n\nexport function buildPropResolutionContext(\n store: StateStore,\n repeatScope?: RepeatScope,\n functions?: Record<string, ComputedFunction>,\n): PropResolutionContext {\n const ctx: PropResolutionContext = {\n stateModel: store.getSnapshot(),\n };\n if (repeatScope) {\n ctx.repeatItem = repeatScope.item;\n ctx.repeatIndex = repeatScope.index;\n ctx.repeatBasePath = repeatScope.basePath;\n }\n if (functions) {\n ctx.functions = functions;\n }\n return ctx;\n}\n","// SPDX-License-Identifier: MIT\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n effect,\n inject,\n Injector,\n input,\n OnInit,\n reflectComponentType,\n runInInjectionContext,\n signal,\n type Signal,\n type Type,\n} from '@angular/core';\nimport { NgComponentOutlet } from '@angular/common';\nimport {\n evaluateVisibility,\n resolveBindings,\n resolveElementProps,\n} from '@json-render/core';\nimport type { Spec, UIElement } from '@json-render/core';\n\nimport { RENDER_CONTEXT } from './contexts/render-context';\nimport { REPEAT_SCOPE } from './contexts/repeat-scope';\nimport type { RepeatScope } from './contexts/repeat-scope';\nimport { buildPropResolutionContext } from './internals/prop-signal';\nimport type { AngularComponentRenderer } from './render.types';\n\n/** Magic prefix on `emit()` strings that catalog components use to\n * write back to the data model (binding `path` and the new value). The\n * render-element's emitFn intercepts this and writes via the state\n * store, sidestepping the normal `el.on[event]` handler binding which\n * the catalog components have no way to declare for arbitrary paths. */\nconst A2UI_DATAMODEL_PREFIX = 'a2ui:datamodel:';\n\n/** Cache of declared input names per component class. NgComponentOutlet\n * passes every key in its `inputs` prop to the target; Angular dev mode\n * raises NG0303 for any input the component doesn't declare. We strip\n * undeclared keys before mounting so simple view components (`StatCard`,\n * `Container`, etc.) don't get spammed with framework-only inputs\n * (`bindings`, `emit`, `loading`, `childKeys`, `spec`) they ignore. */\n/** `null` means reflection failed (likely uncompiled / non-component) — in\n * that case we pass inputs through unmodified rather than swallow them.\n * An empty Set means the component genuinely declares zero inputs (e.g. a\n * pure presentational fallback) and ALL keys should be dropped. */\nconst declaredInputsCache = new WeakMap<Type<unknown>, Set<string> | null>();\nfunction getDeclaredInputs(cls: Type<unknown>): Set<string> | null {\n if (declaredInputsCache.has(cls)) return declaredInputsCache.get(cls)!;\n const meta = reflectComponentType(cls);\n const result = meta ? new Set<string>(meta.inputs.map(i => i.templateName)) : null;\n declaredInputsCache.set(cls, result);\n return result;\n}\nfunction filterInputsForClass(\n cls: Type<unknown> | null,\n inputs: Record<string, unknown>,\n): Record<string, unknown> {\n if (!cls) return inputs;\n const declared = getDeclaredInputs(cls);\n if (declared === null) return inputs;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(inputs)) {\n if (declared.has(k)) out[k] = v;\n }\n return out;\n}\n\n/** Best-effort string→typed coercion for datamodel writes. Catalog\n * components emit raw string values; the underlying state may have\n * been declared as number/boolean/array, and consumers reading the\n * resolved props expect the correct type. */\nfunction coerceValue(raw: string): unknown {\n if (raw === '') return '';\n if (raw === 'true') return true;\n if (raw === 'false') return false;\n // JSON-array passthrough (MultipleChoice emits stringified arrays)\n if (raw.startsWith('[') && raw.endsWith(']')) {\n try { return JSON.parse(raw); } catch { /* fall through */ }\n }\n // Numeric — only if the entire string parses cleanly as a number\n if (/^-?\\d+(?:\\.\\d+)?$/.test(raw)) {\n const n = Number(raw);\n if (!Number.isNaN(n)) return n;\n }\n return raw;\n}\n\n/**\n * Recursive element renderer.\n *\n * For each element key it:\n * 1. Looks up the UIElement from spec.elements\n * 2. Resolves the component class from the registry\n * 3. Evaluates visibility\n * 4. Resolves prop expressions and bindings\n * 5. Renders via NgComponentOutlet with resolved inputs\n *\n * For elements with `repeat`, it iterates over the state array,\n * creating a child Injector with RepeatScope for each item.\n */\n@Component({\n selector: 'render-element',\n standalone: true,\n imports: [NgComponentOutlet],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (!element()?.repeat) {\n @if (visible()) {\n <ng-container\n *ngComponentOutlet=\"mountClass(); inputs: filteredResolvedInputs(); injector: parentInjector\"\n />\n }\n } @else {\n @for (repeatInjector of repeatInjectors(); track $index) {\n <ng-container\n *ngComponentOutlet=\"mountClass(); inputs: filteredRepeatInputs()[$index]; injector: repeatInjector\"\n />\n }\n }\n `,\n})\nexport class RenderElementComponent implements OnInit {\n readonly elementKey = input.required<string>();\n readonly spec = input.required<Spec>();\n\n private readonly ctx = inject(RENDER_CONTEXT);\n private readonly repeatScope = inject(REPEAT_SCOPE, { optional: true });\n readonly parentInjector = inject(Injector);\n private readonly destroyRef = inject(DestroyRef);\n\n constructor() {\n this.destroyRef.onDestroy(() => {\n const el = this.element();\n if (el && (el as any)['lifecycle'] && this.ctx.emitEvent) {\n this.ctx.emitEvent({\n type: 'lifecycle',\n event: 'destroyed',\n scope: 'element',\n elementKey: this.elementKey(),\n elementType: el.type,\n });\n }\n });\n\n // Latch mountedReal=true once the real component is selected. Lives in\n // an effect (not the computed) because Angular forbids signal writes\n // inside computed — they're for derivation only. Effects are the\n // idiomatic place for \"signal change → signal write\" side effects.\n effect(() => {\n if (this.mountedReal()) return;\n const el = this.element();\n if (!el) return;\n // Only latch when notReady is false AND a real component is registered.\n if (!this.notReady() && this.ctx.registry.get(el.type)) {\n this.mountedReal.set(true);\n }\n });\n }\n\n ngOnInit(): void {\n const el = this.element();\n if (el && (el as any)['lifecycle'] && this.ctx.emitEvent) {\n this.ctx.emitEvent({\n type: 'lifecycle',\n event: 'mounted',\n scope: 'element',\n elementKey: this.elementKey(),\n elementType: el.type,\n });\n }\n }\n\n /** The UIElement definition from the spec. Only propagates when reference changes. */\n readonly element: Signal<UIElement | undefined> = computed(\n () => this.spec()?.elements?.[this.elementKey()],\n { equal: Object.is },\n );\n\n /** The Angular component class for this element type. */\n readonly componentClass = computed<AngularComponentRenderer | null>(() => {\n const el = this.element();\n if (!el) return null;\n return this.ctx.registry.get(el.type) ?? null;\n });\n\n /** Prop resolution context built from store + repeat scope. */\n private readonly propCtx = computed(() =>\n buildPropResolutionContext(\n this.ctx.store,\n this.repeatScope ?? undefined,\n this.ctx.functions,\n ),\n );\n\n /** Once real mounts, never revert to fallback even if a state-bound\n * prop later becomes undefined. Per-instance monotonic gate. */\n private readonly mountedReal = signal<boolean>(false);\n\n /** True when ANY resolved prop value is undefined (i.e. a state\n * binding points at a path the store hasn't populated). Framework-\n * injected keys (bindings, emit, loading, childKeys, spec) are\n * excluded — only consumer-resolved props matter for readiness. */\n readonly notReady = computed<boolean>(() => {\n if (this.mountedReal()) return false;\n const el = this.element();\n if (!el || !el.props) return false;\n const resolved = resolveElementProps(el.props, this.propCtx());\n for (const v of Object.values(resolved)) {\n if (v === undefined) return true;\n }\n return false;\n });\n\n /** Picks fallback or real based on notReady. The mountedReal latch is\n * driven by a constructor effect (not this computed) — Angular forbids\n * signal writes inside computed. */\n readonly mountClass = computed<AngularComponentRenderer | null>(() => {\n const el = this.element();\n if (!el) return null;\n const real = this.ctx.registry.get(el.type) ?? null;\n if (this.notReady()) {\n return this.ctx.registry.getFallback(el.type) ?? null;\n }\n return real;\n });\n\n /** Whether the element is visible (non-repeat path). */\n readonly visible = computed(() => {\n const el = this.element();\n if (!el) return false;\n if (this.mountClass() === null) return false;\n return evaluateVisibility(el.visible, this.propCtx());\n });\n\n /** Emit function that delegates to context handlers AND handles the\n * canonical `a2ui:datamodel:<path>:<value>` write-back protocol that\n * input components (TextField, MultipleChoice, CheckBox, Slider,\n * DateTimeInput) emit when the user changes their value. The render\n * lib's state store is the single source of truth for in-surface UI\n * state; writing through it triggers re-render with the new value\n * and re-evaluates any path-bound props (validation, computed\n * visibility, etc.).\n *\n * The string format is `a2ui:datamodel:<path>:<value>` where:\n * - `<path>` is a JSON-Pointer-style path (e.g. `/name`, `/form/email`)\n * - `<value>` is the raw value rendered as a string. We attempt to\n * coerce numeric and boolean literals back to their typed form\n * so downstream consumers see correct types; arrays come through\n * as JSON-stringified payloads (catalog components emit them via\n * `JSON.stringify`).\n */\n private readonly emitFn = (event: string) => {\n if (event.startsWith(A2UI_DATAMODEL_PREFIX)) {\n this.applyDatamodelWrite(event);\n return;\n }\n const el = this.element();\n if (!el?.on) return;\n const binding = el.on[event];\n if (!binding) return;\n const bindings = Array.isArray(binding) ? binding : [binding];\n for (const b of bindings) {\n const handler = this.ctx.handlers?.[b.action];\n if (handler) {\n runInInjectionContext(this.parentInjector, () =>\n handler(b.params as Record<string, unknown> ?? {}),\n );\n }\n }\n };\n\n private applyDatamodelWrite(event: string): void {\n // Strip the prefix, then split path and value at the last `:` —\n // path may itself contain `:` characters (rare but legal in\n // JSON-Pointer per RFC 6901), and values can certainly contain\n // them (URLs, time strings). Catalog components emit\n // `a2ui:datamodel:<path>:<value>` where path is the binding's\n // path-ref (usually starts with `/`); split the LAST `:` because\n // the value is the only field guaranteed to come last.\n const rest = event.slice(A2UI_DATAMODEL_PREFIX.length);\n const lastColon = rest.lastIndexOf(':');\n if (lastColon === -1) return;\n const path = rest.slice(0, lastColon);\n const rawValue = rest.slice(lastColon + 1);\n if (!path) return;\n const store = this.ctx.store;\n if (!store) return;\n store.set(path, coerceValue(rawValue));\n }\n\n /** Resolved inputs for non-repeat elements. */\n readonly resolvedInputs = computed(() => {\n const el = this.element();\n if (!el) return {};\n const ctx = this.propCtx();\n const resolved = resolveElementProps(el.props ?? {}, ctx);\n const bindings = resolveBindings(el.props ?? {}, ctx);\n return {\n ...resolved,\n bindings,\n emit: this.emitFn,\n loading: this.ctx.loading ?? false,\n childKeys: el.children ?? [],\n spec: this.spec(),\n };\n });\n\n /** `resolvedInputs` filtered down to keys the target component actually\n * declares — silences NG0303 dev-mode warnings from framework-only\n * inputs (bindings/emit/loading/childKeys/spec) passed to simple view\n * components that don't declare them. */\n readonly filteredResolvedInputs = computed(() =>\n filterInputsForClass(this.mountClass() as Type<unknown> | null, this.resolvedInputs()),\n );\n\n // --- Repeat support ---\n\n /** Items from the state array for repeat elements. */\n private readonly repeatItems = computed<unknown[]>(() => {\n const el = this.element();\n if (!el?.repeat) return [];\n const items = this.ctx.store.get(el.repeat.statePath);\n return Array.isArray(items) ? items : [];\n });\n\n /** One RepeatScope per repeat item, shared between injectors and inputs. */\n private readonly repeatScopes = computed(() => {\n const el = this.element();\n if (!el?.repeat) return [];\n return this.repeatItems().map((item, index) => ({\n item,\n index,\n basePath: `${el.repeat!.statePath}/${index}`,\n } satisfies RepeatScope));\n });\n\n /** One child Injector per repeat item, providing RepeatScope. */\n readonly repeatInjectors = computed(() => {\n return this.repeatScopes().map(scope =>\n Injector.create({\n providers: [{ provide: REPEAT_SCOPE, useValue: scope }],\n parent: this.parentInjector,\n }),\n );\n });\n\n /** Resolved inputs for each repeat item. */\n readonly repeatInputs = computed(() => {\n const el = this.element();\n if (!el?.repeat) return [];\n return this.repeatScopes().map(scope => {\n const ctx = buildPropResolutionContext(\n this.ctx.store,\n scope,\n this.ctx.functions,\n );\n const resolved = resolveElementProps(el.props ?? {}, ctx);\n const bindings = resolveBindings(el.props ?? {}, ctx);\n return {\n ...resolved,\n bindings,\n emit: this.emitFn,\n loading: this.ctx.loading ?? false,\n childKeys: el.children ?? [],\n spec: this.spec(),\n };\n });\n });\n\n /** `repeatInputs` filtered per-item to declared component inputs. */\n readonly filteredRepeatInputs = computed(() => {\n const cls = this.mountClass() as Type<unknown> | null;\n return this.repeatInputs().map(inputs => filterInputsForClass(cls, inputs));\n });\n}\n","// SPDX-License-Identifier: MIT\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n effect,\n inject,\n input,\n OnInit,\n output,\n} from '@angular/core';\nimport type { ComputedFunction, Spec, StateStore } from '@json-render/core';\n\nimport { RenderElementComponent } from './render-element.component';\nimport { RENDER_CONFIG } from './provide-render';\nimport { RENDER_CONTEXT } from './contexts/render-context';\nimport type { RenderContext } from './contexts/render-context';\nimport type { AngularRegistry } from './render.types';\nimport { signalStateStore } from './signal-state-store';\nimport type { RenderEvent } from './render-event';\nimport { RenderLifecycleService } from './render-lifecycle.service';\n\n/**\n * Top-level entry point for rendering a json-render spec.\n *\n * Accepts the spec, registry, store, functions, handlers, and loading\n * as inputs. Provides `RENDER_CONTEXT` to child `RenderElementComponent`\n * instances via `viewProviders`.\n *\n * Falls back to `RENDER_CONFIG` (from `provideRender()`) for registry\n * and store defaults when inputs are not provided.\n *\n * @example\n * ```html\n * <render-spec [spec]=\"spec()\" [registry]=\"registry\" [store]=\"store\" />\n * ```\n */\n@Component({\n selector: 'render-spec',\n standalone: true,\n imports: [RenderElementComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n viewProviders: [\n {\n provide: RENDER_CONTEXT,\n useFactory: () => inject(RenderSpecComponent)._context(),\n },\n ],\n template: `\n @if (spec()?.root; as rootKey) {\n <render-element [elementKey]=\"rootKey\" [spec]=\"spec()!\" />\n }\n `,\n})\nexport class RenderSpecComponent implements OnInit {\n readonly spec = input<Spec | null>(null);\n readonly registry = input<AngularRegistry | undefined>(undefined);\n readonly store = input<StateStore | undefined>(undefined);\n readonly functions = input<Record<string, ComputedFunction> | undefined>(undefined);\n readonly handlers = input<Record<string, (params: Record<string, unknown>) => unknown | Promise<unknown>> | undefined>(undefined);\n readonly loading = input<boolean>(false);\n readonly events = output<RenderEvent>();\n\n private readonly config = inject(RENDER_CONFIG, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n private readonly lifecycle = inject(RenderLifecycleService, { optional: true });\n\n /** Internal store, lazily created once and reused across spec changes. */\n private _internalStore: StateStore | undefined;\n\n private getOrCreateInternalStore(): StateStore {\n if (!this._internalStore) {\n this._internalStore = signalStateStore(this.spec()?.state ?? {});\n }\n return this._internalStore;\n }\n\n /** Resolved store: input > config > internal (from spec.state). */\n private readonly resolvedStore = computed<StateStore>(() => {\n const inputStore = this.store();\n if (inputStore) return inputStore;\n const configStore = this.config?.store;\n if (configStore) return configStore;\n return this.getOrCreateInternalStore();\n });\n\n /** Resolved registry: input > config. */\n private readonly resolvedRegistry = computed<AngularRegistry>(() => {\n const inputRegistry = this.registry();\n if (inputRegistry) return inputRegistry;\n const configRegistry = this.config?.registry;\n if (configRegistry) return configRegistry;\n // Fallback: empty registry\n return { get: () => undefined, getFallback: () => undefined, names: () => [] };\n });\n\n /** Wraps input handlers to emit RenderHandlerEvent after execution. */\n private readonly wrappedHandlers = computed(() => {\n const inputHandlers = this.handlers() ?? this.config?.handlers;\n if (!inputHandlers) return undefined;\n const wrapped: Record<string, (params: Record<string, unknown>) => unknown | Promise<unknown>> = {};\n for (const [name, handler] of Object.entries(inputHandlers)) {\n wrapped[name] = (params: Record<string, unknown>) => {\n const result = handler(params);\n if (result instanceof Promise) {\n result.then(\n (r) => {\n this.emitTapped({ type: 'handler', action: name, params, result: r });\n },\n () => {\n this.emitTapped({ type: 'handler', action: name, params, result: undefined });\n },\n );\n } else {\n this.emitTapped({ type: 'handler', action: name, params, result });\n }\n return result;\n };\n }\n return wrapped;\n });\n\n /** Emits a RenderEvent through the events output and notifies the\n * lifecycle service (single tap point — all events flow through here). */\n private readonly emitTapped = (event: RenderEvent): void => {\n this.events.emit(event);\n if (!this.lifecycle) return;\n switch (event.type) {\n case 'lifecycle':\n this.lifecycle.notifyLifecycle({\n kind: event.scope,\n type: event.event,\n elementType: event.elementType,\n });\n break;\n case 'stateChange':\n this.lifecycle.notifyStateChange();\n break;\n case 'handler':\n this.lifecycle.notifyHandlerInvoked(event.action);\n break;\n }\n };\n\n /** Emits a RenderEvent through the events output. */\n private readonly emitEvent = (event: RenderEvent) => {\n this.emitTapped(event);\n };\n\n /** The RenderContext provided to children via viewProviders. */\n readonly _context = computed<RenderContext>(() => ({\n registry: this.resolvedRegistry(),\n store: this.resolvedStore(),\n functions: this.functions() ?? this.config?.functions,\n handlers: this.wrappedHandlers(),\n emitEvent: this.emitEvent,\n loading: this.loading(),\n }));\n\n constructor() {\n // Subscribe to store changes and emit state change events\n effect(() => {\n const store = this.resolvedStore();\n const unsub = store.subscribe(() => {\n const snapshot = store.getSnapshot() as Record<string, unknown>;\n this.emitTapped({\n type: 'stateChange',\n path: '/',\n value: snapshot,\n snapshot,\n });\n });\n this.destroyRef.onDestroy(unsub);\n });\n\n this.destroyRef.onDestroy(() => {\n this.emitTapped({ type: 'lifecycle', event: 'destroyed', scope: 'spec' });\n });\n }\n\n ngOnInit(): void {\n this.emitTapped({ type: 'lifecycle', event: 'mounted', scope: 'spec' });\n }\n}\n","// SPDX-License-Identifier: MIT\nimport { Type } from '@angular/core';\nimport type { AngularRegistry, RenderViewEntry } from './render.types';\nimport { defineAngularRegistry } from './define-angular-registry';\n\n/**\n * A registry of view components available for generative UI rendering.\n * Each entry is either a bare component Type (legacy shape) or a\n * `RenderViewEntry` { component, fallback? }.\n */\nexport type ViewRegistry = Readonly<Record<string, Type<unknown> | RenderViewEntry>>;\n\n/**\n * Creates a view registry from a name → component map.\n */\nexport function views(map: Record<string, Type<unknown> | RenderViewEntry>): ViewRegistry {\n return Object.freeze({ ...map });\n}\n\n/**\n * Adds views to a registry without overwriting existing entries.\n * New keys are added; keys that already exist in `base` are preserved.\n */\nexport function withViews(\n base: ViewRegistry,\n additions: Record<string, Type<unknown> | RenderViewEntry>,\n): ViewRegistry {\n return Object.freeze({ ...additions, ...base });\n}\n\n/**\n * Removes views from a registry by name.\n */\nexport function withoutViews(\n base: ViewRegistry,\n ...names: string[]\n): ViewRegistry {\n const result = { ...base };\n for (const name of names) delete result[name];\n return Object.freeze(result);\n}\n\n/**\n * Converts a ViewRegistry to an AngularRegistry for use with RenderSpecComponent.\n */\nexport function toRenderRegistry(registry: ViewRegistry): AngularRegistry {\n return defineAngularRegistry(registry);\n}\n","// SPDX-License-Identifier: MIT\nimport { InjectionToken, makeEnvironmentProviders } from '@angular/core';\nimport type { ViewRegistry } from './views';\n\nexport const VIEW_REGISTRY = new InjectionToken<ViewRegistry>('VIEW_REGISTRY');\n\nexport function provideViews(registry: ViewRegistry) {\n return makeEnvironmentProviders([\n { provide: VIEW_REGISTRY, useValue: registry },\n ]);\n}\n","// SPDX-License-Identifier: MIT\n\n// Types\nexport type {\n AngularComponentInputs,\n AngularComponentRenderer,\n AngularRegistry,\n RenderConfig,\n} from './lib/render.types';\n\n// Contexts\nexport { RENDER_CONTEXT } from './lib/contexts/render-context';\nexport type { RenderContext } from './lib/contexts/render-context';\nexport { REPEAT_SCOPE } from './lib/contexts/repeat-scope';\nexport type { RepeatScope } from './lib/contexts/repeat-scope';\n\n// Registry\nexport { defineAngularRegistry } from './lib/define-angular-registry';\n\n// State\nexport { signalStateStore } from './lib/signal-state-store';\n\n// Provider\nexport { provideRender, RENDER_CONFIG } from './lib/provide-render';\n\n// Components\nexport { RenderElementComponent } from './lib/render-element.component';\nexport { RenderSpecComponent } from './lib/render-spec.component';\n\n// Views\nexport { views, withViews, withoutViews, toRenderRegistry } from './lib/views';\nexport type { ViewRegistry } from './lib/views';\nexport { provideViews, VIEW_REGISTRY } from './lib/provide-views';\n\n// Events\nexport type {\n RenderEvent,\n RenderHandlerEvent,\n RenderStateChangeEvent,\n RenderLifecycleEvent,\n} from './lib/render-event';\n\n// Lifecycle\nexport { RENDER_LIFECYCLE } from './lib/lifecycle';\nexport type { RenderLifecycle } from './lib/lifecycle';\n\n// Fallback\nexport { DefaultFallbackComponent } from './lib/default-fallback.component';\nexport type { RenderViewEntry } from './lib/render.types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@threadplane/render",
|
|
3
|
+
"version": "0.0.46",
|
|
4
|
+
"peerDependencies": {
|
|
5
|
+
"@angular/core": "^20.0.0 || ^21.0.0",
|
|
6
|
+
"@angular/common": "^20.0.0 || ^21.0.0",
|
|
7
|
+
"@json-render/core": "^0.16.0"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/cacheplane/angular-agent-framework.git",
|
|
13
|
+
"directory": "libs/render"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/cacheplane/angular-agent-framework#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/cacheplane/angular-agent-framework/issues"
|
|
18
|
+
},
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"module": "fesm2022/threadplane-render.mjs",
|
|
21
|
+
"typings": "types/threadplane-render.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
"./package.json": {
|
|
24
|
+
"default": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./types/threadplane-render.d.ts",
|
|
28
|
+
"default": "./fesm2022/threadplane-render.mjs"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"tslib": "^2.3.0",
|
|
33
|
+
"@threadplane/telemetry": "*"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"postinstall": "threadplane-telemetry-postinstall || true"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { Type, InjectionToken, OnInit, Injector, Signal } from '@angular/core';
|
|
3
|
+
import { Spec, StateStore, ComputedFunction, StateModel, UIElement } from '@json-render/core';
|
|
4
|
+
import * as _threadplane_render from '@threadplane/render';
|
|
5
|
+
|
|
6
|
+
interface AngularComponentInputs {
|
|
7
|
+
/** Two-way binding paths: prop name → absolute state path */
|
|
8
|
+
bindings?: Record<string, string>;
|
|
9
|
+
/** Emit a named event */
|
|
10
|
+
emit: (event: string) => void;
|
|
11
|
+
/** Whether the spec is currently streaming */
|
|
12
|
+
loading?: boolean;
|
|
13
|
+
/** Child element keys for recursive rendering */
|
|
14
|
+
childKeys: string[];
|
|
15
|
+
/** The full spec (for child resolution) */
|
|
16
|
+
spec: Spec;
|
|
17
|
+
/** Dynamic resolved props are spread as additional inputs */
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
type AngularComponentRenderer = Type<unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* A view registry entry. Bare `Type` form is the legacy shape; the
|
|
23
|
+
* object form lets consumers attach a per-component fallback that
|
|
24
|
+
* mounts while any state-bound prop on the element is still
|
|
25
|
+
* unresolved. The fallback is monotonic per element instance: once
|
|
26
|
+
* the real component mounts, subsequent re-renders never revert to
|
|
27
|
+
* fallback even if a prop later resolves to undefined.
|
|
28
|
+
*/
|
|
29
|
+
interface RenderViewEntry {
|
|
30
|
+
component: Type<unknown>;
|
|
31
|
+
fallback?: Type<unknown>;
|
|
32
|
+
}
|
|
33
|
+
interface AngularRegistry {
|
|
34
|
+
get(name: string): AngularComponentRenderer | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Returns the configured fallback for a registered name, OR the
|
|
37
|
+
* lib's default fallback if the entry omits one, OR undefined if
|
|
38
|
+
* the name is not registered.
|
|
39
|
+
*/
|
|
40
|
+
getFallback(name: string): AngularComponentRenderer | undefined;
|
|
41
|
+
names(): string[];
|
|
42
|
+
}
|
|
43
|
+
interface RenderConfig {
|
|
44
|
+
registry?: AngularRegistry;
|
|
45
|
+
store?: StateStore;
|
|
46
|
+
functions?: Record<string, ComputedFunction>;
|
|
47
|
+
handlers?: Record<string, (params: Record<string, unknown>) => unknown | Promise<unknown>>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface RenderHandlerEvent {
|
|
51
|
+
readonly type: 'handler';
|
|
52
|
+
readonly action: string;
|
|
53
|
+
readonly params: Record<string, unknown>;
|
|
54
|
+
readonly result?: unknown;
|
|
55
|
+
}
|
|
56
|
+
interface RenderStateChangeEvent {
|
|
57
|
+
readonly type: 'stateChange';
|
|
58
|
+
readonly path: string;
|
|
59
|
+
readonly value: unknown;
|
|
60
|
+
readonly snapshot: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
interface RenderLifecycleEvent {
|
|
63
|
+
readonly type: 'lifecycle';
|
|
64
|
+
readonly event: 'mounted' | 'destroyed';
|
|
65
|
+
readonly scope: 'spec' | 'element';
|
|
66
|
+
readonly elementKey?: string;
|
|
67
|
+
readonly elementType?: string;
|
|
68
|
+
}
|
|
69
|
+
type RenderEvent = RenderHandlerEvent | RenderStateChangeEvent | RenderLifecycleEvent;
|
|
70
|
+
|
|
71
|
+
interface RenderContext {
|
|
72
|
+
registry: AngularRegistry;
|
|
73
|
+
store: StateStore;
|
|
74
|
+
functions?: Record<string, ComputedFunction>;
|
|
75
|
+
handlers?: Record<string, (params: Record<string, unknown>) => unknown | Promise<unknown>>;
|
|
76
|
+
emitEvent?: (event: RenderEvent) => void;
|
|
77
|
+
loading?: boolean;
|
|
78
|
+
}
|
|
79
|
+
declare const RENDER_CONTEXT: InjectionToken<RenderContext>;
|
|
80
|
+
|
|
81
|
+
interface RepeatScope {
|
|
82
|
+
item: unknown;
|
|
83
|
+
index: number;
|
|
84
|
+
basePath: string;
|
|
85
|
+
}
|
|
86
|
+
declare const REPEAT_SCOPE: InjectionToken<RepeatScope>;
|
|
87
|
+
|
|
88
|
+
type RegistryInput = Record<string, Type<unknown> | RenderViewEntry>;
|
|
89
|
+
declare function defineAngularRegistry(componentMap: RegistryInput): AngularRegistry;
|
|
90
|
+
|
|
91
|
+
declare function signalStateStore(initialState?: StateModel): StateStore;
|
|
92
|
+
|
|
93
|
+
declare const RENDER_CONFIG: InjectionToken<RenderConfig>;
|
|
94
|
+
declare function provideRender(config: RenderConfig): _angular_core.EnvironmentProviders;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Recursive element renderer.
|
|
98
|
+
*
|
|
99
|
+
* For each element key it:
|
|
100
|
+
* 1. Looks up the UIElement from spec.elements
|
|
101
|
+
* 2. Resolves the component class from the registry
|
|
102
|
+
* 3. Evaluates visibility
|
|
103
|
+
* 4. Resolves prop expressions and bindings
|
|
104
|
+
* 5. Renders via NgComponentOutlet with resolved inputs
|
|
105
|
+
*
|
|
106
|
+
* For elements with `repeat`, it iterates over the state array,
|
|
107
|
+
* creating a child Injector with RepeatScope for each item.
|
|
108
|
+
*/
|
|
109
|
+
declare class RenderElementComponent implements OnInit {
|
|
110
|
+
readonly elementKey: _angular_core.InputSignal<string>;
|
|
111
|
+
readonly spec: _angular_core.InputSignal<Spec>;
|
|
112
|
+
private readonly ctx;
|
|
113
|
+
private readonly repeatScope;
|
|
114
|
+
readonly parentInjector: Injector;
|
|
115
|
+
private readonly destroyRef;
|
|
116
|
+
constructor();
|
|
117
|
+
ngOnInit(): void;
|
|
118
|
+
/** The UIElement definition from the spec. Only propagates when reference changes. */
|
|
119
|
+
readonly element: Signal<UIElement | undefined>;
|
|
120
|
+
/** The Angular component class for this element type. */
|
|
121
|
+
readonly componentClass: Signal<AngularComponentRenderer | null>;
|
|
122
|
+
/** Prop resolution context built from store + repeat scope. */
|
|
123
|
+
private readonly propCtx;
|
|
124
|
+
/** Once real mounts, never revert to fallback even if a state-bound
|
|
125
|
+
* prop later becomes undefined. Per-instance monotonic gate. */
|
|
126
|
+
private readonly mountedReal;
|
|
127
|
+
/** True when ANY resolved prop value is undefined (i.e. a state
|
|
128
|
+
* binding points at a path the store hasn't populated). Framework-
|
|
129
|
+
* injected keys (bindings, emit, loading, childKeys, spec) are
|
|
130
|
+
* excluded — only consumer-resolved props matter for readiness. */
|
|
131
|
+
readonly notReady: Signal<boolean>;
|
|
132
|
+
/** Picks fallback or real based on notReady. The mountedReal latch is
|
|
133
|
+
* driven by a constructor effect (not this computed) — Angular forbids
|
|
134
|
+
* signal writes inside computed. */
|
|
135
|
+
readonly mountClass: Signal<AngularComponentRenderer | null>;
|
|
136
|
+
/** Whether the element is visible (non-repeat path). */
|
|
137
|
+
readonly visible: Signal<boolean>;
|
|
138
|
+
/** Emit function that delegates to context handlers AND handles the
|
|
139
|
+
* canonical `a2ui:datamodel:<path>:<value>` write-back protocol that
|
|
140
|
+
* input components (TextField, MultipleChoice, CheckBox, Slider,
|
|
141
|
+
* DateTimeInput) emit when the user changes their value. The render
|
|
142
|
+
* lib's state store is the single source of truth for in-surface UI
|
|
143
|
+
* state; writing through it triggers re-render with the new value
|
|
144
|
+
* and re-evaluates any path-bound props (validation, computed
|
|
145
|
+
* visibility, etc.).
|
|
146
|
+
*
|
|
147
|
+
* The string format is `a2ui:datamodel:<path>:<value>` where:
|
|
148
|
+
* - `<path>` is a JSON-Pointer-style path (e.g. `/name`, `/form/email`)
|
|
149
|
+
* - `<value>` is the raw value rendered as a string. We attempt to
|
|
150
|
+
* coerce numeric and boolean literals back to their typed form
|
|
151
|
+
* so downstream consumers see correct types; arrays come through
|
|
152
|
+
* as JSON-stringified payloads (catalog components emit them via
|
|
153
|
+
* `JSON.stringify`).
|
|
154
|
+
*/
|
|
155
|
+
private readonly emitFn;
|
|
156
|
+
private applyDatamodelWrite;
|
|
157
|
+
/** Resolved inputs for non-repeat elements. */
|
|
158
|
+
readonly resolvedInputs: Signal<{}>;
|
|
159
|
+
/** `resolvedInputs` filtered down to keys the target component actually
|
|
160
|
+
* declares — silences NG0303 dev-mode warnings from framework-only
|
|
161
|
+
* inputs (bindings/emit/loading/childKeys/spec) passed to simple view
|
|
162
|
+
* components that don't declare them. */
|
|
163
|
+
readonly filteredResolvedInputs: Signal<Record<string, unknown>>;
|
|
164
|
+
/** Items from the state array for repeat elements. */
|
|
165
|
+
private readonly repeatItems;
|
|
166
|
+
/** One RepeatScope per repeat item, shared between injectors and inputs. */
|
|
167
|
+
private readonly repeatScopes;
|
|
168
|
+
/** One child Injector per repeat item, providing RepeatScope. */
|
|
169
|
+
readonly repeatInjectors: Signal<_angular_core.DestroyableInjector[]>;
|
|
170
|
+
/** Resolved inputs for each repeat item. */
|
|
171
|
+
readonly repeatInputs: Signal<{
|
|
172
|
+
bindings: Record<string, string> | undefined;
|
|
173
|
+
emit: (event: string) => void;
|
|
174
|
+
loading: boolean;
|
|
175
|
+
childKeys: string[];
|
|
176
|
+
spec: Spec;
|
|
177
|
+
}[]>;
|
|
178
|
+
/** `repeatInputs` filtered per-item to declared component inputs. */
|
|
179
|
+
readonly filteredRepeatInputs: Signal<Record<string, unknown>[]>;
|
|
180
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RenderElementComponent, never>;
|
|
181
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<RenderElementComponent, "render-element", never, { "elementKey": { "alias": "elementKey"; "required": true; "isSignal": true; }; "spec": { "alias": "spec"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Top-level entry point for rendering a json-render spec.
|
|
186
|
+
*
|
|
187
|
+
* Accepts the spec, registry, store, functions, handlers, and loading
|
|
188
|
+
* as inputs. Provides `RENDER_CONTEXT` to child `RenderElementComponent`
|
|
189
|
+
* instances via `viewProviders`.
|
|
190
|
+
*
|
|
191
|
+
* Falls back to `RENDER_CONFIG` (from `provideRender()`) for registry
|
|
192
|
+
* and store defaults when inputs are not provided.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```html
|
|
196
|
+
* <render-spec [spec]="spec()" [registry]="registry" [store]="store" />
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
declare class RenderSpecComponent implements OnInit {
|
|
200
|
+
readonly spec: _angular_core.InputSignal<Spec | null>;
|
|
201
|
+
readonly registry: _angular_core.InputSignal<AngularRegistry | undefined>;
|
|
202
|
+
readonly store: _angular_core.InputSignal<StateStore | undefined>;
|
|
203
|
+
readonly functions: _angular_core.InputSignal<Record<string, ComputedFunction> | undefined>;
|
|
204
|
+
readonly handlers: _angular_core.InputSignal<Record<string, (params: Record<string, unknown>) => unknown | Promise<unknown>> | undefined>;
|
|
205
|
+
readonly loading: _angular_core.InputSignal<boolean>;
|
|
206
|
+
readonly events: _angular_core.OutputEmitterRef<RenderEvent>;
|
|
207
|
+
private readonly config;
|
|
208
|
+
private readonly destroyRef;
|
|
209
|
+
private readonly lifecycle;
|
|
210
|
+
/** Internal store, lazily created once and reused across spec changes. */
|
|
211
|
+
private _internalStore;
|
|
212
|
+
private getOrCreateInternalStore;
|
|
213
|
+
/** Resolved store: input > config > internal (from spec.state). */
|
|
214
|
+
private readonly resolvedStore;
|
|
215
|
+
/** Resolved registry: input > config. */
|
|
216
|
+
private readonly resolvedRegistry;
|
|
217
|
+
/** Wraps input handlers to emit RenderHandlerEvent after execution. */
|
|
218
|
+
private readonly wrappedHandlers;
|
|
219
|
+
/** Emits a RenderEvent through the events output and notifies the
|
|
220
|
+
* lifecycle service (single tap point — all events flow through here). */
|
|
221
|
+
private readonly emitTapped;
|
|
222
|
+
/** Emits a RenderEvent through the events output. */
|
|
223
|
+
private readonly emitEvent;
|
|
224
|
+
/** The RenderContext provided to children via viewProviders. */
|
|
225
|
+
readonly _context: _angular_core.Signal<RenderContext>;
|
|
226
|
+
constructor();
|
|
227
|
+
ngOnInit(): void;
|
|
228
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RenderSpecComponent, never>;
|
|
229
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<RenderSpecComponent, "render-spec", never, { "spec": { "alias": "spec"; "required": false; "isSignal": true; }; "registry": { "alias": "registry"; "required": false; "isSignal": true; }; "store": { "alias": "store"; "required": false; "isSignal": true; }; "functions": { "alias": "functions"; "required": false; "isSignal": true; }; "handlers": { "alias": "handlers"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; }, { "events": "events"; }, never, never, true, never>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* A registry of view components available for generative UI rendering.
|
|
234
|
+
* Each entry is either a bare component Type (legacy shape) or a
|
|
235
|
+
* `RenderViewEntry` { component, fallback? }.
|
|
236
|
+
*/
|
|
237
|
+
type ViewRegistry = Readonly<Record<string, Type<unknown> | RenderViewEntry>>;
|
|
238
|
+
/**
|
|
239
|
+
* Creates a view registry from a name → component map.
|
|
240
|
+
*/
|
|
241
|
+
declare function views(map: Record<string, Type<unknown> | RenderViewEntry>): ViewRegistry;
|
|
242
|
+
/**
|
|
243
|
+
* Adds views to a registry without overwriting existing entries.
|
|
244
|
+
* New keys are added; keys that already exist in `base` are preserved.
|
|
245
|
+
*/
|
|
246
|
+
declare function withViews(base: ViewRegistry, additions: Record<string, Type<unknown> | RenderViewEntry>): ViewRegistry;
|
|
247
|
+
/**
|
|
248
|
+
* Removes views from a registry by name.
|
|
249
|
+
*/
|
|
250
|
+
declare function withoutViews(base: ViewRegistry, ...names: string[]): ViewRegistry;
|
|
251
|
+
/**
|
|
252
|
+
* Converts a ViewRegistry to an AngularRegistry for use with RenderSpecComponent.
|
|
253
|
+
*/
|
|
254
|
+
declare function toRenderRegistry(registry: ViewRegistry): AngularRegistry;
|
|
255
|
+
|
|
256
|
+
declare const VIEW_REGISTRY: InjectionToken<Readonly<Record<string, _threadplane_render.RenderViewEntry | _angular_core.Type<unknown>>>>;
|
|
257
|
+
declare function provideViews(registry: ViewRegistry): _angular_core.EnvironmentProviders;
|
|
258
|
+
|
|
259
|
+
interface RenderLifecycle {
|
|
260
|
+
/** First mount event in this render context. Sticky — does not reset. */
|
|
261
|
+
readonly firstMountAt: Signal<{
|
|
262
|
+
kind: 'spec' | 'element';
|
|
263
|
+
elementType?: string;
|
|
264
|
+
at: number;
|
|
265
|
+
} | null>;
|
|
266
|
+
/** Total mount count since render context started. */
|
|
267
|
+
readonly mountCount: Signal<number>;
|
|
268
|
+
/** Epoch ms of the most recent mount event. */
|
|
269
|
+
readonly lastMountAt: Signal<number | null>;
|
|
270
|
+
/** Epoch ms of the most recent state-change event. */
|
|
271
|
+
readonly lastStateChangeAt: Signal<number | null>;
|
|
272
|
+
/** Most recent handler invocation. */
|
|
273
|
+
readonly lastHandlerInvokedAt: Signal<{
|
|
274
|
+
action: string;
|
|
275
|
+
at: number;
|
|
276
|
+
} | null>;
|
|
277
|
+
}
|
|
278
|
+
declare const RENDER_LIFECYCLE: InjectionToken<RenderLifecycle>;
|
|
279
|
+
|
|
280
|
+
declare class DefaultFallbackComponent {
|
|
281
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DefaultFallbackComponent, never>;
|
|
282
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<DefaultFallbackComponent, "render-default-fallback", never, {}, {}, never, never, true, never>;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export { DefaultFallbackComponent, RENDER_CONFIG, RENDER_CONTEXT, RENDER_LIFECYCLE, REPEAT_SCOPE, RenderElementComponent, RenderSpecComponent, VIEW_REGISTRY, defineAngularRegistry, provideRender, provideViews, signalStateStore, toRenderRegistry, views, withViews, withoutViews };
|
|
286
|
+
export type { AngularComponentInputs, AngularComponentRenderer, AngularRegistry, RenderConfig, RenderContext, RenderEvent, RenderHandlerEvent, RenderLifecycle, RenderLifecycleEvent, RenderStateChangeEvent, RenderViewEntry, RepeatScope, ViewRegistry };
|