ngx-json-render 0.8.2 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-json-render",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
4
  "description": "Angular renderer for json-render — stream AI-generated JSON specs into real Angular components with signals.",
5
5
  "keywords": [
6
6
  "angular",
@@ -28,7 +28,8 @@
28
28
  "zod": "^4.0.0",
29
29
  "@modelcontextprotocol/ext-apps": "^1.7.5",
30
30
  "@modelcontextprotocol/sdk": "^1.29.0",
31
- "@json-render/devtools": ">=0.20.0 <0.22.0"
31
+ "@json-render/devtools": ">=0.20.0 <0.22.0",
32
+ "@angular/router": ">=19.0.0"
32
33
  },
33
34
  "peerDependenciesMeta": {
34
35
  "@modelcontextprotocol/ext-apps": {
@@ -39,6 +40,9 @@
39
40
  },
40
41
  "@json-render/devtools": {
41
42
  "optional": true
43
+ },
44
+ "@angular/router": {
45
+ "optional": true
42
46
  }
43
47
  },
44
48
  "dependencies": {
@@ -67,6 +71,10 @@
67
71
  "types": "./types/ngx-json-render-mcp.d.ts",
68
72
  "default": "./fesm2022/ngx-json-render-mcp.mjs"
69
73
  },
74
+ "./router": {
75
+ "types": "./types/ngx-json-render-router.d.ts",
76
+ "default": "./fesm2022/ngx-json-render-router.mjs"
77
+ },
70
78
  "./schema": {
71
79
  "types": "./types/ngx-json-render-schema.d.ts",
72
80
  "default": "./fesm2022/ngx-json-render-schema.mjs"
@@ -0,0 +1,39 @@
1
+ /** Which paths a spec may send the app to. */
2
+ interface RouterNavigateOptions {
3
+ /**
4
+ * The paths a spec may navigate to. A string must equal the path exactly; a
5
+ * `RegExp` is tested against it; a function decides on its own. The path is
6
+ * what comes before any `?` or `#`, so a query string or fragment neither
7
+ * helps nor hurts a match, and is passed on to the router as written.
8
+ *
9
+ * Required: a spec is model output, and the app is what knows which of its
10
+ * routes a generated UI has any business opening.
11
+ */
12
+ readonly allow: readonly (string | RegExp)[] | ((path: string) => boolean);
13
+ }
14
+ /**
15
+ * A `navigate` for `<json-render>` that hands a spec's
16
+ * `onSuccess: { navigate }` path to the Angular `Router`, but only a path
17
+ * inside the app that `allow` lets through. Anything else is refused with a
18
+ * warning and goes nowhere.
19
+ *
20
+ * Call it in an injection context — in the function form of
21
+ * `provideJsonRender`, or in a field initializer:
22
+ *
23
+ * ```ts
24
+ * provideJsonRender(() => ({
25
+ * registry,
26
+ * navigate: injectRouterNavigate({ allow: ['/thanks', /^\/orders\/\d+$/] }),
27
+ * }));
28
+ * ```
29
+ *
30
+ * Whatever `allow` says, a path is refused unless it starts with a single
31
+ * `/`: no other origin (`https://…`, `//host`), no scheme (`javascript:…`),
32
+ * no path relative to wherever the user happens to be. A backslash or a
33
+ * control character refuses it too, since browsers read `/\host` as
34
+ * `//host`.
35
+ */
36
+ declare function injectRouterNavigate(options: RouterNavigateOptions): (path: string) => void;
37
+
38
+ export { injectRouterNavigate };
39
+ export type { RouterNavigateOptions };
@@ -2,7 +2,7 @@ import * as _json_render_core from '@json-render/core';
2
2
  import { ResolvedAction, ActionHandler, ActionBinding, SpecIssue, Spec, Catalog, InferCatalogActions, InferActionParams, StateModel, InferCatalogComponents, UIElement, StateStore, ValidationFunction, ComputedFunction, DirectiveDefinition, ActionConfirm, DirectiveRegistry, ValidationResult, ValidationConfig, JsonPatch, FlatElement } from '@json-render/core';
3
3
  export { ActionBinding, ActionHandler, InferActionParams, InferCatalogComponents, InferComponentProps, JsonPatch, Spec, SpecIssue, StateModel, StateStore, UIElement, VisibilityCondition, autoFixSpec, createStateStore, formatSpecIssues, nestedToFlat, validateSpec } from '@json-render/core';
4
4
  import * as _angular_core from '@angular/core';
5
- import { Signal, Type, Injector, InjectionToken } from '@angular/core';
5
+ import { Signal, Type, Injector, InjectionToken, Provider } from '@angular/core';
6
6
  import * as ngx_json_render from 'ngx-json-render';
7
7
  export { AngularSchema, AngularSpec, schema } from 'ngx-json-render/schema';
8
8
 
@@ -319,6 +319,14 @@ interface RegistryEntry {
319
319
  component: Type<unknown>;
320
320
  /** Slot names this component supports (from the catalog definition). */
321
321
  slots?: string[];
322
+ /**
323
+ * Shown instead of `component` while the spec streams and this element's
324
+ * props do not yet pass its component's schema in the renderer's
325
+ * `catalog`. It gets the same render context, so it can read the props
326
+ * that have arrived and place `<jr-children />`. Without one, the element
327
+ * renders nothing until it is ready.
328
+ */
329
+ fallback?: Type<unknown>;
322
330
  }
323
331
  /**
324
332
  * Registry of component renderers: catalog type name → Angular component
@@ -327,10 +335,11 @@ interface RegistryEntry {
327
335
  type ComponentRegistry = Record<string, Type<unknown> | RegistryEntry>;
328
336
  /**
329
337
  * Registry of all Angular components for a catalog. Keys are checked against
330
- * the catalog's component names.
338
+ * the catalog's component names. A value is the component, or the component
339
+ * with a {@link RegistryEntry.fallback} to show while its props stream in.
331
340
  */
332
341
  type Components<C extends Catalog> = {
333
- [K in keyof InferCatalogComponents<C>]: Type<unknown>;
342
+ [K in keyof InferCatalogComponents<C>]: Type<unknown> | Pick<RegistryEntry, 'component' | 'fallback'>;
334
343
  };
335
344
  /**
336
345
  * The repeat scope available to elements rendered inside a `repeat` block.
@@ -469,8 +478,11 @@ declare function injectBoundProp<T>(propValue: () => T | undefined, bindingPath:
469
478
  declare class JsonRenderer {
470
479
  /** The UI spec to render (may be partial while streaming). */
471
480
  readonly spec: _angular_core.InputSignal<Spec | null>;
472
- /** Component registry mapping catalog type names to Angular components. */
473
- readonly registry: _angular_core.InputSignal<ComponentRegistry>;
481
+ /**
482
+ * Component registry mapping catalog type names to Angular components.
483
+ * Required unless a {@link provideJsonRender} above supplies one.
484
+ */
485
+ readonly registry: _angular_core.InputSignal<ComponentRegistry | undefined>;
474
486
  /** Whether the spec is currently loading/streaming. */
475
487
  readonly loading: _angular_core.InputSignal<boolean>;
476
488
  /** Fallback component for unknown types. */
@@ -487,7 +499,7 @@ declare class JsonRenderer {
487
499
  * The check is skipped while `loading` is true: a spec that is still
488
500
  * arriving is expected to reference children that have not streamed in yet.
489
501
  */
490
- readonly validate: _angular_core.InputSignal<SpecValidationMode>;
502
+ readonly validate: _angular_core.InputSignal<SpecValidationMode | undefined>;
491
503
  /**
492
504
  * Caps on what the spec may cost the browser: `maxElements`, `maxDepth` and
493
505
  * `maxRepeatItems`. Unset by default, and each field is independently
@@ -509,8 +521,14 @@ declare class JsonRenderer {
509
521
  * `$bindState`, `$template`, a directive — has no value until render time,
510
522
  * so it is not checked.
511
523
  *
512
- * Only read when `validate` is on, and — like the rest of the check —
513
- * skipped while `loading`.
524
+ * The check is read only when `validate` is on, and — like the rest of
525
+ * it — skipped while `loading`.
526
+ *
527
+ * While `loading`, the catalog also gates mounting, whatever `validate` is:
528
+ * an element whose props do not yet pass its component's schema shows its
529
+ * registry entry's `fallback`, or nothing, instead of a component with half
530
+ * its props. Once an element has mounted it stays mounted, and when
531
+ * `loading` ends every element mounts.
514
532
  */
515
533
  readonly catalog: _angular_core.InputSignal<SpecCatalog | null>;
516
534
  /**
@@ -538,6 +556,15 @@ declare class JsonRenderer {
538
556
  readonly stateStore: JsonRenderStateService;
539
557
  /** The action dispatcher of this renderer. */
540
558
  protected readonly actions: JsonRenderActionsService;
559
+ /**
560
+ * The nearest {@link provideJsonRender}'s defaults. Every option input
561
+ * falls through to these when left `null` or `undefined`; see
562
+ * {@link JsonRenderConfig} for the merge rules.
563
+ */
564
+ private readonly config;
565
+ private readonly mode;
566
+ private readonly limits;
567
+ private readonly specCatalog;
541
568
  /**
542
569
  * The spec as checked, and what the check found. While `loading`, the mode
543
570
  * is forced off: the spec is still arriving, and the missing children it
@@ -562,7 +589,7 @@ declare class JsonRenderer {
562
589
  */
563
590
  protected readonly confirmInjector: _angular_core.Signal<Injector | null>;
564
591
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<JsonRenderer, never>;
565
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<JsonRenderer, "json-render", never, { "spec": { "alias": "spec"; "required": true; "isSignal": true; }; "registry": { "alias": "registry"; "required": true; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "fallback": { "alias": "fallback"; "required": false; "isSignal": true; }; "validate": { "alias": "validate"; "required": false; "isSignal": true; }; "renderLimits": { "alias": "renderLimits"; "required": false; "isSignal": true; }; "catalog": { "alias": "catalog"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "store": { "alias": "store"; "required": false; "isSignal": true; }; "handlers": { "alias": "handlers"; "required": false; "isSignal": true; }; "onAction": { "alias": "onAction"; "required": false; "isSignal": true; }; "navigate": { "alias": "navigate"; "required": false; "isSignal": true; }; "validationFunctions": { "alias": "validationFunctions"; "required": false; "isSignal": true; }; "functions": { "alias": "functions"; "required": false; "isSignal": true; }; "directives": { "alias": "directives"; "required": false; "isSignal": true; }; }, { "stateChange": "stateChange"; }, never, never, true, never>;
592
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<JsonRenderer, "json-render", never, { "spec": { "alias": "spec"; "required": true; "isSignal": true; }; "registry": { "alias": "registry"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "fallback": { "alias": "fallback"; "required": false; "isSignal": true; }; "validate": { "alias": "validate"; "required": false; "isSignal": true; }; "renderLimits": { "alias": "renderLimits"; "required": false; "isSignal": true; }; "catalog": { "alias": "catalog"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "store": { "alias": "store"; "required": false; "isSignal": true; }; "handlers": { "alias": "handlers"; "required": false; "isSignal": true; }; "onAction": { "alias": "onAction"; "required": false; "isSignal": true; }; "navigate": { "alias": "navigate"; "required": false; "isSignal": true; }; "validationFunctions": { "alias": "validationFunctions"; "required": false; "isSignal": true; }; "functions": { "alias": "functions"; "required": false; "isSignal": true; }; "directives": { "alias": "directives"; "required": false; "isSignal": true; }; }, { "stateChange": "stateChange"; }, never, never, true, never>;
566
593
  }
567
594
 
568
595
  /**
@@ -700,6 +727,21 @@ declare class JrElement {
700
727
  /** Two-way binding paths ($bindState / $bindItem) by prop name. */
701
728
  readonly bindings: Signal<Record<string, string> | undefined>;
702
729
  private readonly entry;
730
+ /** Set once the real component has been shown; see {@link held}. */
731
+ private mounted;
732
+ /**
733
+ * Whether the component waits for its props: the spec is still loading, a
734
+ * catalog was given, and the props written so far do not pass the
735
+ * component's schema (see {@link propsArrived}).
736
+ *
737
+ * One-way. Once the component has been shown it is never swapped back for
738
+ * the fallback, even if a later patch takes a prop away again: a component
739
+ * flickering out mid-stream is worse than one showing a gap. Once latched
740
+ * this reads no signals, so a mounted element pays nothing for the gate on
741
+ * later patches — and neither does any element when nothing is loading.
742
+ */
743
+ protected readonly held: Signal<boolean>;
744
+ /** What the outlet shows: the component, or its fallback while held. */
703
745
  protected readonly component: Signal<_angular_core.Type<unknown> | null>;
704
746
  protected readonly devtoolsKey: Signal<string | null>;
705
747
  /**
@@ -858,6 +900,62 @@ declare const JR_CONFIRM_DIALOG: InjectionToken<Type<unknown>>;
858
900
  /** Inject the confirmation a replacement dialog component is rendering for. */
859
901
  declare function injectConfirmContext(): ConfirmContext;
860
902
 
903
+ /**
904
+ * Renderer defaults for every `<json-render>` below a provider: each field is
905
+ * the input of the same name.
906
+ *
907
+ * An input bound on the element wins over the provided value, and a value
908
+ * left `null` or `undefined` on the element falls through to it. Named
909
+ * vocabularies — `handlers`, `functions`, `validationFunctions` and
910
+ * `directives` — merge by name instead, the element's entry winning a name
911
+ * both define. Everything else replaces: a `registry` belongs to its catalog,
912
+ * so two are never mixed behind your back (spread them yourself to combine).
913
+ */
914
+ interface JsonRenderConfig {
915
+ registry?: ComponentRegistry;
916
+ fallback?: Type<unknown> | RegistryEntry | null;
917
+ validate?: SpecValidationMode;
918
+ renderLimits?: RenderLimits | null;
919
+ catalog?: SpecCatalog | null;
920
+ handlers?: Record<string, ActionHandler>;
921
+ onAction?: ((name: string, params?: Record<string, unknown>) => unknown) | null;
922
+ navigate?: ((path: string) => void) | null;
923
+ validationFunctions?: Record<string, ValidationFunction>;
924
+ functions?: Record<string, ComputedFunction>;
925
+ directives?: DirectiveDefinition<any>[];
926
+ }
927
+ /**
928
+ * The defaults the nearest {@link provideJsonRender} supplies, already merged
929
+ * with any provider above it.
930
+ */
931
+ declare const JSON_RENDER_CONFIG: InjectionToken<JsonRenderConfig>;
932
+ /**
933
+ * Set renderer defaults once instead of on every `<json-render>`:
934
+ *
935
+ * ```ts
936
+ * bootstrapApplication(App, { providers: [provideJsonRender({ registry })] });
937
+ * ```
938
+ *
939
+ * ```html
940
+ * <json-render [spec]="m.spec" />
941
+ * ```
942
+ *
943
+ * Pass a function to build the defaults with `inject()`, for handlers that
944
+ * call your services:
945
+ *
946
+ * ```ts
947
+ * provideJsonRender(() => {
948
+ * const orders = inject(OrderService);
949
+ * return { registry, handlers: { cancelOrder: (p) => orders.cancel(p['id']) } };
950
+ * });
951
+ * ```
952
+ *
953
+ * Works in application, route and component `providers`. A provider below
954
+ * another extends it with the same rules an element input does: its own
955
+ * values replace, its named vocabularies merge.
956
+ */
957
+ declare function provideJsonRender(config: JsonRenderConfig | (() => JsonRenderConfig)): Provider[];
958
+
861
959
  /**
862
960
  * The render context of the element currently being rendered.
863
961
  * Provided by the renderer for every catalog component instance.
@@ -926,6 +1024,11 @@ declare class JsonRenderRootContext {
926
1024
  * they apply whatever `validate` is set to.
927
1025
  */
928
1026
  limits: Signal<RenderLimits | null | undefined>;
1027
+ /**
1028
+ * The catalog the spec was generated for. While loading, an element whose
1029
+ * props do not pass its component's schema waits for them.
1030
+ */
1031
+ catalog: Signal<SpecCatalog | null | undefined>;
929
1032
  /** External store (controlled mode). */
930
1033
  store: Signal<StateStore | null | undefined>;
931
1034
  /** Initial state (uncontrolled mode); falls back to `spec.state`. */
@@ -1382,5 +1485,5 @@ declare function injectChatUI(options: ChatUIOptions): ChatUIReturn;
1382
1485
  */
1383
1486
  declare function injectDevtoolsActive(): Signal<boolean>;
1384
1487
 
1385
- 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 };
1386
- export type { ActionFn, Actions, CatalogHasActions, ChatMessage, ChatUIOptions, ChatUIReturn, ComponentRegistry, Components, ConfirmContext, ConfirmLabels, DataPart, DefineRegistryResult, EventHandle, FieldValidationState, PendingConfirmation, RegistryEntry, RenderContext, RenderLimits, RepeatScope, SetState, SpecCatalog, SpecCheck, SpecCheckIssue, SpecCheckIssueCode, SpecCheckOptions, SpecValidationMode, StateChange, StreamObserver, StreamObserverUsage, TokenUsage, UIStreamOptions, UIStreamReturn, UIStreamSendOptions };
1488
+ 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 };
1489
+ export type { ActionFn, Actions, CatalogHasActions, ChatMessage, ChatUIOptions, ChatUIReturn, ComponentRegistry, Components, ConfirmContext, ConfirmLabels, DataPart, DefineRegistryResult, EventHandle, FieldValidationState, JsonRenderConfig, PendingConfirmation, RegistryEntry, RenderContext, RenderLimits, RepeatScope, SetState, SpecCatalog, SpecCheck, SpecCheckIssue, SpecCheckIssueCode, SpecCheckOptions, SpecValidationMode, StateChange, StreamObserver, StreamObserverUsage, TokenUsage, UIStreamOptions, UIStreamReturn, UIStreamSendOptions };