ngx-json-render 0.9.0 → 0.9.2

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.
@@ -47,10 +47,10 @@ class JsonRenderRootContext {
47
47
  return undefined;
48
48
  return typeof raw === 'function' ? { component: raw } : raw;
49
49
  }
50
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderRootContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
51
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderRootContext });
50
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderRootContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
51
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderRootContext });
52
52
  }
53
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderRootContext, decorators: [{
53
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderRootContext, decorators: [{
54
54
  type: Injectable
55
55
  }] });
56
56
 
@@ -284,10 +284,10 @@ class JsonRenderStateService {
284
284
  listener(changes);
285
285
  }
286
286
  }
287
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
288
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderStateService });
287
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
288
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderStateService });
289
289
  }
290
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderStateService, decorators: [{
290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderStateService, decorators: [{
291
291
  type: Injectable
292
292
  }], ctorParameters: () => [] });
293
293
  /**
@@ -538,10 +538,10 @@ class JsonRenderValidationService {
538
538
  }
539
539
  return allValid;
540
540
  }
541
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
542
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderValidationService });
541
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
542
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderValidationService });
543
543
  }
544
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderValidationService, decorators: [{
544
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderValidationService, decorators: [{
545
545
  type: Injectable
546
546
  }] });
547
547
  /** Inject the validation service of the nearest `<json-render>` renderer. */
@@ -904,10 +904,10 @@ class JsonRenderActionsService {
904
904
  this._loadingActions.set(next);
905
905
  }
906
906
  }
907
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderActionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
908
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderActionsService });
907
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderActionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
908
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderActionsService });
909
909
  }
910
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderActionsService, decorators: [{
910
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderActionsService, decorators: [{
911
911
  type: Injectable
912
912
  }] });
913
913
  /** Inject the actions service of the nearest `<json-render>` renderer. */
@@ -1031,8 +1031,8 @@ class JrConfirmDialog {
1031
1031
  cancel.focus();
1032
1032
  }
1033
1033
  }
1034
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JrConfirmDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1035
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.22", type: JrConfirmDialog, isStandalone: true, selector: "jr-confirm-dialog", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, host: { listeners: { "keydown.escape": "cancelled.emit()" } }, viewQueries: [{ propertyName: "cancelButton", first: true, predicate: ["cancel"], descendants: true, isSignal: true }, { propertyName: "confirmButton", first: true, predicate: ["confirm"], descendants: true, isSignal: true }], ngImport: i0, template: `
1034
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JrConfirmDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1035
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.25", type: JrConfirmDialog, isStandalone: true, selector: "jr-confirm-dialog", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, host: { listeners: { "keydown.escape": "cancelled.emit()" } }, viewQueries: [{ propertyName: "cancelButton", first: true, predicate: ["cancel"], descendants: true, isSignal: true }, { propertyName: "confirmButton", first: true, predicate: ["confirm"], descendants: true, isSignal: true }], ngImport: i0, template: `
1036
1036
  <div class="jr-confirm-scrim" (click)="cancelled.emit()">
1037
1037
  <div
1038
1038
  class="jr-confirm-panel"
@@ -1063,7 +1063,7 @@ class JrConfirmDialog {
1063
1063
  </div>
1064
1064
  `, isInline: true, styles: [":host{--jr-confirm-scrim-default: rgba(0, 0, 0, .5);--jr-confirm-surface-default: #ffffff;--jr-confirm-ink-default: #111827;--jr-confirm-muted-default: #6b7280;--jr-confirm-border-default: #d1d5db;--jr-confirm-accent-default: #3b82f6;--jr-confirm-danger-default: #dc2626;--jr-confirm-on-accent-default: #ffffff}@media(prefers-color-scheme:dark){:host{--jr-confirm-scrim-default: rgba(0, 0, 0, .65);--jr-confirm-surface-default: #1f2937;--jr-confirm-ink-default: #f3f4f6;--jr-confirm-muted-default: #9ca3af;--jr-confirm-border-default: #4b5563;--jr-confirm-accent-default: #60a5fa;--jr-confirm-danger-default: #f87171;--jr-confirm-on-accent-default: #111827}}.jr-confirm-scrim{position:fixed;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background-color:var(--jr-confirm-scrim, var(--jr-confirm-scrim-default))}.jr-confirm-panel{width:100%;max-width:400px;padding:24px;border-radius:var(--jr-confirm-radius, 8px);background-color:var(--jr-confirm-surface, var(--jr-confirm-surface-default));color:var(--jr-confirm-ink, var(--jr-confirm-ink-default));box-shadow:0 20px 25px -5px #00000040}.jr-confirm-title{margin:0 0 8px;font-size:18px;font-weight:600}.jr-confirm-message{margin:0 0 24px;color:var(--jr-confirm-muted, var(--jr-confirm-muted-default))}.jr-confirm-actions{display:flex;gap:12px;justify-content:flex-end}.jr-confirm-actions button{padding:8px 16px;border-radius:6px;font:inherit;cursor:pointer}.jr-confirm-cancel{border:1px solid var(--jr-confirm-border, var(--jr-confirm-border-default));background-color:transparent;color:inherit}.jr-confirm-confirm{border:none;background-color:var(--jr-confirm-accent, var(--jr-confirm-accent-default));color:var(--jr-confirm-on-accent, var(--jr-confirm-on-accent-default))}.jr-confirm-confirm.jr-confirm-danger{background-color:var(--jr-confirm-danger, var(--jr-confirm-danger-default))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1065
1065
  }
1066
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JrConfirmDialog, decorators: [{
1066
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JrConfirmDialog, decorators: [{
1067
1067
  type: Component,
1068
1068
  args: [{ selector: 'jr-confirm-dialog', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1069
1069
  '(keydown.escape)': 'cancelled.emit()',
@@ -2349,8 +2349,8 @@ class JrElement {
2349
2349
  bound: true,
2350
2350
  };
2351
2351
  }
2352
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JrElement, deps: [], target: i0.ɵɵFactoryTarget.Component });
2353
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: JrElement, isStandalone: true, selector: "jr-element", inputs: { elementKey: { classPropertyName: "elementKey", publicName: "elementKey", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
2352
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JrElement, deps: [], target: i0.ɵɵFactoryTarget.Component });
2353
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: JrElement, isStandalone: true, selector: "jr-element", inputs: { elementKey: { classPropertyName: "elementKey", publicName: "elementKey", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
2354
2354
  @if (!refusal() && rawElement() && visible() && component()) {
2355
2355
  @if (devtoolsKey(); as dk) {
2356
2356
  <span [attr.data-jr-key]="dk" style="display: contents">
@@ -2362,7 +2362,7 @@ class JrElement {
2362
2362
  }
2363
2363
  `, isInline: true, styles: [":host{display:contents}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2364
2364
  }
2365
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JrElement, decorators: [{
2365
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JrElement, decorators: [{
2366
2366
  type: Component,
2367
2367
  args: [{ selector: 'jr-element', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgComponentOutlet], template: `
2368
2368
  @if (!refusal() && rawElement() && visible() && component()) {
@@ -2377,6 +2377,92 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
2377
2377
  `, styles: [":host{display:contents}\n"] }]
2378
2378
  }], ctorParameters: () => [], propDecorators: { elementKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementKey", required: true }] }] } });
2379
2379
 
2380
+ /**
2381
+ * The defaults the nearest {@link provideJsonRender} supplies, already merged
2382
+ * with any provider above it.
2383
+ */
2384
+ const JSON_RENDER_CONFIG = new InjectionToken('JSON_RENDER_CONFIG');
2385
+ /**
2386
+ * Set renderer defaults once instead of on every `<json-render>`:
2387
+ *
2388
+ * ```ts
2389
+ * bootstrapApplication(App, { providers: [provideJsonRender({ registry })] });
2390
+ * ```
2391
+ *
2392
+ * ```html
2393
+ * <json-render [spec]="m.spec" />
2394
+ * ```
2395
+ *
2396
+ * Pass a function to build the defaults with `inject()`, for handlers that
2397
+ * call your services:
2398
+ *
2399
+ * ```ts
2400
+ * provideJsonRender(() => {
2401
+ * const orders = inject(OrderService);
2402
+ * return { registry, handlers: { cancelOrder: (p) => orders.cancel(p['id']) } };
2403
+ * });
2404
+ * ```
2405
+ *
2406
+ * Works in application, route and component `providers`. A provider below
2407
+ * another extends it with the same rules an element input does: its own
2408
+ * values replace, its named vocabularies merge.
2409
+ */
2410
+ function provideJsonRender(config) {
2411
+ return [
2412
+ {
2413
+ provide: JSON_RENDER_CONFIG,
2414
+ useFactory: () => {
2415
+ const outer = inject(JSON_RENDER_CONFIG, {
2416
+ optional: true,
2417
+ skipSelf: true,
2418
+ });
2419
+ const own = typeof config === 'function' ? config() : config;
2420
+ return outer ? mergeJsonRenderConfig(outer, own) : own;
2421
+ },
2422
+ },
2423
+ ];
2424
+ }
2425
+ /** `inner` over `outer`, by the rules {@link JsonRenderConfig} describes. */
2426
+ function mergeJsonRenderConfig(outer, inner) {
2427
+ const merged = { ...outer };
2428
+ for (const [key, value] of Object.entries(inner)) {
2429
+ if (value !== undefined && value !== null) {
2430
+ merged[key] = value;
2431
+ }
2432
+ }
2433
+ merged.handlers = mergeByName(outer.handlers, inner.handlers);
2434
+ merged.functions = mergeByName(outer.functions, inner.functions);
2435
+ merged.validationFunctions = mergeByName(outer.validationFunctions, inner.validationFunctions);
2436
+ merged.directives = mergeDirectives(outer.directives, inner.directives);
2437
+ return merged;
2438
+ }
2439
+ /**
2440
+ * Two name → value maps as one, `inner` winning a name both define. Returns
2441
+ * whichever is given when only one is, so an unmerged map keeps its identity.
2442
+ *
2443
+ * @internal
2444
+ */
2445
+ function mergeByName(outer, inner) {
2446
+ if (!outer)
2447
+ return inner ?? undefined;
2448
+ if (!inner)
2449
+ return outer;
2450
+ return { ...outer, ...inner };
2451
+ }
2452
+ /**
2453
+ * Two directive lists as one, `inner` winning a name both define — the
2454
+ * directive registry keeps the last definition of a name.
2455
+ *
2456
+ * @internal
2457
+ */
2458
+ function mergeDirectives(outer, inner) {
2459
+ if (!outer)
2460
+ return inner ?? undefined;
2461
+ if (!inner)
2462
+ return outer;
2463
+ return [...outer, ...inner];
2464
+ }
2465
+
2380
2466
  const CLEAN = {
2381
2467
  spec: null,
2382
2468
  issues: [],
@@ -2526,8 +2612,11 @@ function reportSpecCheck(check, mode) {
2526
2612
  class JsonRenderer {
2527
2613
  /** The UI spec to render (may be partial while streaming). */
2528
2614
  spec = input.required(...(ngDevMode ? [{ debugName: "spec" }] : /* istanbul ignore next */ []));
2529
- /** Component registry mapping catalog type names to Angular components. */
2530
- registry = input.required(...(ngDevMode ? [{ debugName: "registry" }] : /* istanbul ignore next */ []));
2615
+ /**
2616
+ * Component registry mapping catalog type names to Angular components.
2617
+ * Required unless a {@link provideJsonRender} above supplies one.
2618
+ */
2619
+ registry = input(undefined, ...(ngDevMode ? [{ debugName: "registry" }] : /* istanbul ignore next */ []));
2531
2620
  /** Whether the spec is currently loading/streaming. */
2532
2621
  loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2533
2622
  /** Fallback component for unknown types. */
@@ -2544,7 +2633,7 @@ class JsonRenderer {
2544
2633
  * The check is skipped while `loading` is true: a spec that is still
2545
2634
  * arriving is expected to reference children that have not streamed in yet.
2546
2635
  */
2547
- validate = input('off', ...(ngDevMode ? [{ debugName: "validate" }] : /* istanbul ignore next */ []));
2636
+ validate = input(undefined, ...(ngDevMode ? [{ debugName: "validate" }] : /* istanbul ignore next */ []));
2548
2637
  /**
2549
2638
  * Caps on what the spec may cost the browser: `maxElements`, `maxDepth` and
2550
2639
  * `maxRepeatItems`. Unset by default, and each field is independently
@@ -2601,34 +2690,52 @@ class JsonRenderer {
2601
2690
  stateStore;
2602
2691
  /** The action dispatcher of this renderer. */
2603
2692
  actions;
2693
+ /**
2694
+ * The nearest {@link provideJsonRender}'s defaults. Every option input
2695
+ * falls through to these when left `null` or `undefined`; see
2696
+ * {@link JsonRenderConfig} for the merge rules.
2697
+ */
2698
+ config = inject(JSON_RENDER_CONFIG, { optional: true }) ?? {};
2699
+ mode = computed(() => this.validate() ?? this.config.validate ?? 'off', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
2700
+ limits = computed(() => this.renderLimits() ?? this.config.renderLimits ?? null, ...(ngDevMode ? [{ debugName: "limits" }] : /* istanbul ignore next */ []));
2701
+ specCatalog = computed(() => this.catalog() ?? this.config.catalog ?? null, ...(ngDevMode ? [{ debugName: "specCatalog" }] : /* istanbul ignore next */ []));
2604
2702
  /**
2605
2703
  * The spec as checked, and what the check found. While `loading`, the mode
2606
2704
  * is forced off: the spec is still arriving, and the missing children it
2607
2705
  * refers to are the normal state of a stream, not a defect.
2608
2706
  */
2609
- checked = computed(() => checkSpec(this.spec(), this.loading() ? 'off' : this.validate(), {
2610
- limits: this.renderLimits(),
2611
- catalog: this.catalog(),
2707
+ checked = computed(() => checkSpec(this.spec(), this.loading() ? 'off' : this.mode(), {
2708
+ limits: this.limits(),
2709
+ catalog: this.specCatalog(),
2612
2710
  }), ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
2613
2711
  constructor() {
2614
2712
  const root = inject(JsonRenderRootContext);
2615
2713
  // The fixed spec, not the input: a `visible` the check moved out of
2616
2714
  // `props` has to be the one the tree renders, or the fix is cosmetic.
2617
2715
  root.spec = computed(() => this.checked().spec, ...(ngDevMode ? [{ debugName: "spec" }] : /* istanbul ignore next */ []));
2618
- root.registry = this.registry;
2716
+ const config = this.config;
2717
+ // Read only once an element needs a component, like a required input:
2718
+ // a renderer with no spec yet has no use for one.
2719
+ root.registry = computed(() => {
2720
+ const registry = this.registry() ?? config.registry;
2721
+ if (!registry) {
2722
+ throw new Error('[ngx-json-render] <json-render> has no registry. Bind [registry], or supply one for the whole app with provideJsonRender({ registry }).');
2723
+ }
2724
+ return registry;
2725
+ }, ...(ngDevMode ? [{ debugName: "registry" }] : /* istanbul ignore next */ []));
2619
2726
  root.loading = this.loading;
2620
- root.fallback = this.fallback;
2621
- root.limits = this.renderLimits;
2622
- root.catalog = this.catalog;
2727
+ root.fallback = computed(() => this.fallback() ?? config.fallback ?? null, ...(ngDevMode ? [{ debugName: "fallback" }] : /* istanbul ignore next */ []));
2728
+ root.limits = this.limits;
2729
+ root.catalog = this.specCatalog;
2623
2730
  root.store = this.store;
2624
2731
  root.initialState = computed(() => this.state() ?? this.spec()?.state ?? {}, ...(ngDevMode ? [{ debugName: "initialState" }] : /* istanbul ignore next */ []));
2625
- root.handlers = this.handlers;
2626
- root.onAction = this.onAction;
2627
- root.navigate = this.navigate;
2628
- root.validationFunctions = this.validationFunctions;
2629
- root.functions = this.functions;
2732
+ root.handlers = computed(() => mergeByName(config.handlers, this.handlers()), ...(ngDevMode ? [{ debugName: "handlers" }] : /* istanbul ignore next */ []));
2733
+ root.onAction = computed(() => this.onAction() ?? config.onAction ?? null, ...(ngDevMode ? [{ debugName: "onAction" }] : /* istanbul ignore next */ []));
2734
+ root.navigate = computed(() => this.navigate() ?? config.navigate ?? null, ...(ngDevMode ? [{ debugName: "navigate" }] : /* istanbul ignore next */ []));
2735
+ root.validationFunctions = computed(() => mergeByName(config.validationFunctions, this.validationFunctions()), ...(ngDevMode ? [{ debugName: "validationFunctions" }] : /* istanbul ignore next */ []));
2736
+ root.functions = computed(() => mergeByName(config.functions, this.functions()), ...(ngDevMode ? [{ debugName: "functions" }] : /* istanbul ignore next */ []));
2630
2737
  root.directiveRegistry = computed(() => {
2631
- const definitions = this.directives();
2738
+ const definitions = mergeDirectives(config.directives, this.directives());
2632
2739
  return definitions ? createDirectiveRegistry(definitions) : undefined;
2633
2740
  }, ...(ngDevMode ? [{ debugName: "directiveRegistry" }] : /* istanbul ignore next */ []));
2634
2741
  root.emitStateChange = (changes) => this.stateChange.emit(changes);
@@ -2644,7 +2751,25 @@ class JsonRenderer {
2644
2751
  effect(() => {
2645
2752
  if (this.loading())
2646
2753
  return;
2647
- reportSpecCheck(this.checked(), this.validate());
2754
+ reportSpecCheck(this.checked(), this.mode());
2755
+ });
2756
+ // With validation off, a `root` that names no element renders nothing and
2757
+ // nothing says why — the first mistake a hand-written spec tends to have.
2758
+ // Skipped while loading (a stream sends `/root` before its element) and
2759
+ // under `warn`/`strict`, where the check already reports `root_not_found`.
2760
+ // Once per root key, not once per change detection run.
2761
+ const warnedRoots = new Set();
2762
+ effect(() => {
2763
+ if (this.loading() || this.mode() !== 'off')
2764
+ return;
2765
+ const spec = this.checked().spec;
2766
+ if (!spec?.root)
2767
+ return;
2768
+ const root = spec.root;
2769
+ if (spec.elements?.[root] || warnedRoots.has(root))
2770
+ return;
2771
+ warnedRoots.add(root);
2772
+ console.warn(`[ngx-json-render] root "${root}" is not in elements; nothing is rendered.`);
2648
2773
  });
2649
2774
  }
2650
2775
  rootKey = computed(() => {
@@ -2657,7 +2782,7 @@ class JsonRenderer {
2657
2782
  // A spec whose errors survived the fixes does not render under `strict`.
2658
2783
  // Reporting it and drawing half of it anyway is the behaviour the mode
2659
2784
  // exists to refuse.
2660
- if (this.validate() === 'strict' && checked.hasErrors)
2785
+ if (this.mode() === 'strict' && checked.hasErrors)
2661
2786
  return null;
2662
2787
  const spec = checked.spec;
2663
2788
  return spec?.root && spec.elements?.[spec.root] ? spec.root : null;
@@ -2695,8 +2820,8 @@ class JsonRenderer {
2695
2820
  parent: this.injector,
2696
2821
  });
2697
2822
  }, ...(ngDevMode ? [{ debugName: "confirmInjector" }] : /* istanbul ignore next */ []));
2698
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
2699
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: JsonRenderer, isStandalone: true, selector: "json-render", inputs: { spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: true, transformFunction: null }, registry: { classPropertyName: "registry", publicName: "registry", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, renderLimits: { classPropertyName: "renderLimits", publicName: "renderLimits", isSignal: true, isRequired: false, transformFunction: null }, catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null }, onAction: { classPropertyName: "onAction", publicName: "onAction", isSignal: true, isRequired: false, transformFunction: null }, navigate: { classPropertyName: "navigate", publicName: "navigate", isSignal: true, isRequired: false, transformFunction: null }, validationFunctions: { classPropertyName: "validationFunctions", publicName: "validationFunctions", isSignal: true, isRequired: false, transformFunction: null }, functions: { classPropertyName: "functions", publicName: "functions", isSignal: true, isRequired: false, transformFunction: null }, directives: { classPropertyName: "directives", publicName: "directives", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stateChange: "stateChange" }, providers: [
2823
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
2824
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: JsonRenderer, isStandalone: true, selector: "json-render", inputs: { spec: { classPropertyName: "spec", publicName: "spec", isSignal: true, isRequired: true, transformFunction: null }, registry: { classPropertyName: "registry", publicName: "registry", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, renderLimits: { classPropertyName: "renderLimits", publicName: "renderLimits", isSignal: true, isRequired: false, transformFunction: null }, catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null }, onAction: { classPropertyName: "onAction", publicName: "onAction", isSignal: true, isRequired: false, transformFunction: null }, navigate: { classPropertyName: "navigate", publicName: "navigate", isSignal: true, isRequired: false, transformFunction: null }, validationFunctions: { classPropertyName: "validationFunctions", publicName: "validationFunctions", isSignal: true, isRequired: false, transformFunction: null }, functions: { classPropertyName: "functions", publicName: "functions", isSignal: true, isRequired: false, transformFunction: null }, directives: { classPropertyName: "directives", publicName: "directives", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stateChange: "stateChange" }, providers: [
2700
2825
  JsonRenderRootContext,
2701
2826
  JsonRenderStateService,
2702
2827
  JsonRenderValidationService,
@@ -2720,7 +2845,7 @@ class JsonRenderer {
2720
2845
  }
2721
2846
  `, isInline: true, styles: [":host{display:contents}\n"], dependencies: [{ kind: "component", type: JrElement, selector: "jr-element", inputs: ["elementKey"] }, { kind: "component", type: JrConfirmDialog, selector: "jr-confirm-dialog", inputs: ["config"], outputs: ["confirmed", "cancelled"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2722
2847
  }
2723
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JsonRenderer, decorators: [{
2848
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JsonRenderer, decorators: [{
2724
2849
  type: Component,
2725
2850
  args: [{ selector: 'json-render', changeDetection: ChangeDetectionStrategy.OnPush, imports: [JrElement, JrConfirmDialog, NgComponentOutlet], providers: [
2726
2851
  JsonRenderRootContext,
@@ -2745,7 +2870,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
2745
2870
  }
2746
2871
  }
2747
2872
  `, styles: [":host{display:contents}\n"] }]
2748
- }], ctorParameters: () => [], propDecorators: { spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: true }] }], registry: [{ type: i0.Input, args: [{ isSignal: true, alias: "registry", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], fallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallback", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], renderLimits: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderLimits", required: false }] }], catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], onAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "onAction", required: false }] }], navigate: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigate", required: false }] }], validationFunctions: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationFunctions", required: false }] }], functions: [{ type: i0.Input, args: [{ isSignal: true, alias: "functions", required: false }] }], directives: [{ type: i0.Input, args: [{ isSignal: true, alias: "directives", required: false }] }], stateChange: [{ type: i0.Output, args: ["stateChange"] }] } });
2873
+ }], ctorParameters: () => [], propDecorators: { spec: [{ type: i0.Input, args: [{ isSignal: true, alias: "spec", required: true }] }], registry: [{ type: i0.Input, args: [{ isSignal: true, alias: "registry", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], fallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallback", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], renderLimits: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderLimits", required: false }] }], catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], onAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "onAction", required: false }] }], navigate: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigate", required: false }] }], validationFunctions: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationFunctions", required: false }] }], functions: [{ type: i0.Input, args: [{ isSignal: true, alias: "functions", required: false }] }], directives: [{ type: i0.Input, args: [{ isSignal: true, alias: "directives", required: false }] }], stateChange: [{ type: i0.Output, args: ["stateChange"] }] } });
2749
2874
 
2750
2875
  /**
2751
2876
  * Provides the repeat scope (item, index, absolute base path) to the elements
@@ -2757,12 +2882,12 @@ class JrRepeatScope {
2757
2882
  item = input.required(...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
2758
2883
  index = input.required(...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
2759
2884
  basePath = input.required(...(ngDevMode ? [{ debugName: "basePath" }] : /* istanbul ignore next */ []));
2760
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JrRepeatScope, deps: [], target: i0.ɵɵFactoryTarget.Component });
2761
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.22", type: JrRepeatScope, isStandalone: true, selector: "jr-repeat-scope", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
2885
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JrRepeatScope, deps: [], target: i0.ɵɵFactoryTarget.Component });
2886
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.25", type: JrRepeatScope, isStandalone: true, selector: "jr-repeat-scope", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
2762
2887
  { provide: REPEAT_SCOPE, useExisting: forwardRef(() => JrRepeatScope) },
2763
2888
  ], ngImport: i0, template: '<ng-content />', isInline: true, styles: [":host{display:contents}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2764
2889
  }
2765
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JrRepeatScope, decorators: [{
2890
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JrRepeatScope, decorators: [{
2766
2891
  type: Component,
2767
2892
  args: [{ selector: 'jr-repeat-scope', changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2768
2893
  { provide: REPEAT_SCOPE, useExisting: forwardRef(() => JrRepeatScope) },
@@ -2883,8 +3008,8 @@ class JrChildren {
2883
3008
  }
2884
3009
  return index;
2885
3010
  }
2886
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JrChildren, deps: [], target: i0.ɵɵFactoryTarget.Component });
2887
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: JrChildren, isStandalone: true, selector: "jr-children", inputs: { slot: { classPropertyName: "slot", publicName: "slot", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
3011
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JrChildren, deps: [], target: i0.ɵɵFactoryTarget.Component });
3012
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: JrChildren, isStandalone: true, selector: "jr-children", inputs: { slot: { classPropertyName: "slot", publicName: "slot", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2888
3013
  @if (repeat()) {
2889
3014
  @for (item of repeatItems(); track trackItem($index, item)) {
2890
3015
  <jr-repeat-scope
@@ -2904,7 +3029,7 @@ class JrChildren {
2904
3029
  }
2905
3030
  `, isInline: true, styles: [":host{display:contents}\n"], dependencies: [{ kind: "component", type: JrElement, selector: "jr-element", inputs: ["elementKey"] }, { kind: "component", type: JrRepeatScope, selector: "jr-repeat-scope", inputs: ["item", "index", "basePath"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2906
3031
  }
2907
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: JrChildren, decorators: [{
3032
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: JrChildren, decorators: [{
2908
3033
  type: Component,
2909
3034
  args: [{ selector: 'jr-children', changeDetection: ChangeDetectionStrategy.OnPush, imports: [JrElement, JrRepeatScope], template: `
2910
3035
  @if (repeat()) {
@@ -3886,5 +4011,5 @@ function injectChatUI(options) {
3886
4011
  * Generated bundle index. Do not edit.
3887
4012
  */
3888
4013
 
3889
- export { CONFIRM_CONTEXT, ELEMENT_KEY, JR_CONFIRM_DIALOG, JR_CONFIRM_LABELS, JrChildren, JrConfirmDialog, JrElement, JrRepeatScope, JsonRenderActionsService, JsonRenderRootContext, JsonRenderStateService, JsonRenderValidationService, JsonRenderer, RENDER_CONTEXT, REPEAT_SCOPE, applyPatch, buildSpecFromParts, checkSpec, createStoreSetState, defineRegistry, flatToTree, formatSpecCheckIssues, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectConfirmContext, injectDevtoolsActive, injectElementKey, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, isActionCancelled, jsonRenderMessage, ɵregisterStreamObserver };
4014
+ export { CONFIRM_CONTEXT, ELEMENT_KEY, JR_CONFIRM_DIALOG, JR_CONFIRM_LABELS, JSON_RENDER_CONFIG, JrChildren, JrConfirmDialog, JrElement, JrRepeatScope, JsonRenderActionsService, JsonRenderRootContext, JsonRenderStateService, JsonRenderValidationService, JsonRenderer, RENDER_CONTEXT, REPEAT_SCOPE, applyPatch, buildSpecFromParts, checkSpec, createStoreSetState, defineRegistry, flatToTree, formatSpecCheckIssues, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectConfirmContext, injectDevtoolsActive, injectElementKey, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, isActionCancelled, jsonRenderMessage, provideJsonRender, ɵregisterStreamObserver };
3890
4015
  //# sourceMappingURL=ngx-json-render.mjs.map