ngx-json-render 0.7.5 → 0.7.7
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 +13 -4
- package/fesm2022/ngx-json-render.mjs +49 -17
- package/fesm2022/ngx-json-render.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-json-render.d.ts +1 -1
package/README.md
CHANGED
|
@@ -161,7 +161,12 @@ export const catalog = schema.createCatalog({
|
|
|
161
161
|
|
|
162
162
|
```ts
|
|
163
163
|
import { Component } from '@angular/core';
|
|
164
|
-
import {
|
|
164
|
+
import {
|
|
165
|
+
type InferComponentProps,
|
|
166
|
+
JrChildren,
|
|
167
|
+
injectRenderContext,
|
|
168
|
+
} from 'ngx-json-render';
|
|
169
|
+
import type { catalog } from './catalog';
|
|
165
170
|
|
|
166
171
|
@Component({
|
|
167
172
|
selector: 'app-card',
|
|
@@ -174,7 +179,8 @@ import { JrChildren, injectRenderContext } from 'ngx-json-render';
|
|
|
174
179
|
`,
|
|
175
180
|
})
|
|
176
181
|
export class CardComponent {
|
|
177
|
-
readonly ctx =
|
|
182
|
+
readonly ctx =
|
|
183
|
+
injectRenderContext<InferComponentProps<typeof catalog, 'Card'>>();
|
|
178
184
|
}
|
|
179
185
|
|
|
180
186
|
@Component({
|
|
@@ -182,10 +188,13 @@ export class CardComponent {
|
|
|
182
188
|
template: `<button (click)="ctx.emit('press')">{{ ctx.props().label }}</button>`,
|
|
183
189
|
})
|
|
184
190
|
export class ButtonComponent {
|
|
185
|
-
readonly ctx =
|
|
191
|
+
readonly ctx =
|
|
192
|
+
injectRenderContext<InferComponentProps<typeof catalog, 'Button'>>();
|
|
186
193
|
}
|
|
187
194
|
```
|
|
188
195
|
|
|
196
|
+
`InferComponentProps` reads a component's props type off its Zod schema in the catalog, so the component and the catalog cannot drift apart; a hand-written type such as `injectRenderContext<{ label: string }>()` works too. One mismatch to know: a `.default()` field comes out required, but the renderer passes props as the spec wrote them and applies no Zod defaults, so keep such a field optional or fall back in the template.
|
|
197
|
+
|
|
189
198
|
`<jr-children />` renders the element's children where you place it — like a `router-outlet` for the spec tree. Use `<jr-children slot="header" />` for named slots.
|
|
190
199
|
|
|
191
200
|
**3. Build the registry and render:**
|
|
@@ -859,7 +868,7 @@ is how you tell the two apart. One dialog is open at a time: an action with a
|
|
|
859
868
|
`confirm` dispatched while another is waiting cancels the waiting one, so its
|
|
860
869
|
`execute()` rejects the same way.
|
|
861
870
|
|
|
862
|
-
Registry & schema: `defineRegistry`, `createStoreSetState`, `schema
|
|
871
|
+
Registry & schema: `defineRegistry`, `createStoreSetState`, `schema`, and the catalog types `InferComponentProps`, `InferCatalogComponents`, `InferActionParams` (re-exported from core).
|
|
863
872
|
|
|
864
873
|
Testing (`ngx-json-render/testing`): `renderSpec`, `renderComponent`, `recordedTransport`, `specStream`, `usageLine`.
|
|
865
874
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { NgComponentOutlet } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { signal, Injectable, inject, computed, untracked, effect,
|
|
3
|
+
import { signal, Injectable, isDevMode, inject, computed, untracked, effect, DestroyRef, InjectionToken, input, output, viewChild, afterNextRender, ChangeDetectionStrategy, Component, Injector, forwardRef } from '@angular/core';
|
|
4
4
|
import { createStateStore, getByPath, runValidation, resolveAction, nextActionDispatchId, notifyActionDispatch, notifyActionSettle, executeAction, isDevtoolsActive, subscribeDevtoolsActive, resolveRepeatStatePath, evaluateVisibility, resolveElementProps, resolveBindings, resolveActionParam, autoFixSpec, validateSpec, createDirectiveRegistry, resolveRepeatItemStatePath, removeByPath, addByPath, setByPath, SPEC_DATA_PART_TYPE, nestedToFlat, createMixedStreamParser, defineSchema } from '@json-render/core';
|
|
5
5
|
export { autoFixSpec, createStateStore, formatSpecIssues, nestedToFlat, validateSpec } from '@json-render/core';
|
|
6
|
-
import { flattenToPointers } from '@json-render/core/store-utils';
|
|
7
6
|
|
|
8
7
|
const EMPTY = signal(undefined, ...(ngDevMode ? [{ debugName: "EMPTY" }] : /* istanbul ignore next */ []));
|
|
9
8
|
/**
|
|
@@ -49,6 +48,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
49
48
|
type: Injectable
|
|
50
49
|
}] });
|
|
51
50
|
|
|
51
|
+
/** Nesting below this is kept as one leaf, the same cut-off core uses. */
|
|
52
|
+
const MAX_FLATTEN_DEPTH = 20;
|
|
53
|
+
/** Escape one key for a JSON Pointer: `~` → `~0`, `/` → `~1`. */
|
|
54
|
+
function escapePointerSegment(key) {
|
|
55
|
+
return key.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Flatten plain objects into a `{ pointer: leaf }` map; arrays and anything
|
|
59
|
+
* that is not a plain object are leaves.
|
|
60
|
+
*
|
|
61
|
+
* Core's `flattenToPointers` does the same but joins keys unescaped, so a key
|
|
62
|
+
* such as `a/b` comes back as `/a/b` and a store write puts it at `a` → `b`.
|
|
63
|
+
* Every path built here is escaped, so it reads back the key it came from.
|
|
64
|
+
*/
|
|
65
|
+
function flattenToPointers(obj) {
|
|
66
|
+
const result = {};
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
let warned = false;
|
|
69
|
+
const walk = (value, prefix, depth) => {
|
|
70
|
+
for (const [key, child] of Object.entries(value)) {
|
|
71
|
+
const pointer = `${prefix}/${escapePointerSegment(key)}`;
|
|
72
|
+
if (isPlainObject(child) && !seen.has(child)) {
|
|
73
|
+
if (depth < MAX_FLATTEN_DEPTH) {
|
|
74
|
+
seen.add(child);
|
|
75
|
+
walk(child, pointer, depth + 1);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (isDevMode() && !warned) {
|
|
79
|
+
warned = true;
|
|
80
|
+
console.warn(`[ngx-json-render] state nested deeper than ${MAX_FLATTEN_DEPTH} levels is treated as one value.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
result[pointer] = child;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
walk(obj, '', 0);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function isPlainObject(value) {
|
|
90
|
+
return (value !== null &&
|
|
91
|
+
typeof value === 'object' &&
|
|
92
|
+
Object.getPrototypeOf(value) === Object.prototype);
|
|
93
|
+
}
|
|
94
|
+
|
|
52
95
|
/**
|
|
53
96
|
* The state store of a `<json-render>` subtree.
|
|
54
97
|
*
|
|
@@ -222,7 +265,7 @@ function diffState(prev, next) {
|
|
|
222
265
|
changes.push({ path, value: b });
|
|
223
266
|
const keys = new Set([...childKeys(a), ...childKeys(b)]);
|
|
224
267
|
for (const key of keys) {
|
|
225
|
-
walk(child(a, key), child(b, key), `${path}/${
|
|
268
|
+
walk(child(a, key), child(b, key), `${path}/${escapePointerSegment(key)}`);
|
|
226
269
|
}
|
|
227
270
|
};
|
|
228
271
|
walk(prev, next, '');
|
|
@@ -236,9 +279,6 @@ function child(value, key) {
|
|
|
236
279
|
? value[key]
|
|
237
280
|
: undefined;
|
|
238
281
|
}
|
|
239
|
-
function escapePointer(key) {
|
|
240
|
-
return key.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
241
|
-
}
|
|
242
282
|
/**
|
|
243
283
|
* Inject the state store of the nearest `<json-render>` renderer.
|
|
244
284
|
* Must be called from within the renderer's subtree (e.g. a catalog
|
|
@@ -410,17 +450,9 @@ class JsonRenderValidationService {
|
|
|
410
450
|
/** Validate a single field and record the result. */
|
|
411
451
|
validate(path, config) {
|
|
412
452
|
const currentState = this.state.getSnapshot();
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (value != null && typeof value === 'object') {
|
|
417
|
-
value = value[seg];
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
value = undefined;
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
453
|
+
// The store's own pointer walk, so `~1` and `~0` mean what they mean
|
|
454
|
+
// everywhere else a path is read.
|
|
455
|
+
const value = getByPath(currentState, path);
|
|
424
456
|
const result = runValidation(config, {
|
|
425
457
|
value,
|
|
426
458
|
stateModel: currentState,
|