ngx-json-render 0.2.0 → 0.3.0

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.
@@ -1,10 +1,11 @@
1
1
  import * as _json_render_core from '@json-render/core';
2
- import { ResolvedAction, ActionHandler, ActionBinding, Catalog, InferCatalogActions, InferActionParams, StateModel, InferCatalogComponents, UIElement, Spec, StateStore, ValidationFunction, ComputedFunction, DirectiveDefinition, ActionConfirm, DirectiveRegistry, ValidationResult, ValidationConfig, JsonPatch, FlatElement } from '@json-render/core';
3
- export { ActionBinding, ActionHandler, JsonPatch, Spec, StateModel, StateStore, UIElement, VisibilityCondition, createStateStore, nestedToFlat } from '@json-render/core';
2
+ import { ResolvedAction, ActionHandler, ActionBinding, Spec, SpecIssue, Catalog, InferCatalogActions, InferActionParams, StateModel, InferCatalogComponents, UIElement, StateStore, ValidationFunction, ComputedFunction, DirectiveDefinition, ActionConfirm, DirectiveRegistry, ValidationResult, ValidationConfig, JsonPatch, FlatElement } from '@json-render/core';
3
+ export { ActionBinding, ActionHandler, JsonPatch, Spec, SpecIssue, StateModel, StateStore, UIElement, VisibilityCondition, autoFixSpec, createStateStore, formatSpecIssues, nestedToFlat, validateSpec } from '@json-render/core';
4
4
  import * as zod from 'zod';
5
5
  import * as zod_v4_core from 'zod/v4/core';
6
6
  import * as _angular_core from '@angular/core';
7
7
  import { Signal, Type, InjectionToken } from '@angular/core';
8
+ import * as ngx_json_render from 'ngx-json-render';
8
9
 
9
10
  interface PendingConfirmation {
10
11
  action: ResolvedAction;
@@ -12,13 +13,15 @@ interface PendingConfirmation {
12
13
  resolve: () => void;
13
14
  reject: () => void;
14
15
  }
16
+ /** True for the rejection produced by cancelling a confirmation dialog. */
17
+ declare function isActionCancelled(error: unknown): boolean;
15
18
  /**
16
19
  * Action dispatcher of a `<json-render>` subtree.
17
20
  *
18
21
  * Executes {@link ActionBinding}s: built-in actions (`setState`, `pushState`,
19
- * `removeState`, `push`, `pop`, `validateForm`) are handled internally;
20
- * everything else is routed to the host-provided `handlers` (or the
21
- * `onAction` catch-all), honoring `confirm`, `onSuccess`, and `onError`.
22
+ * `removeState`, `push`, `pop`, `validateForm`, `submitForm`) are handled
23
+ * internally; everything else is routed to the host-provided `handlers` (or
24
+ * the `onAction` catch-all), honoring `confirm`, `onSuccess`, and `onError`.
22
25
  */
23
26
  declare class JsonRenderActionsService {
24
27
  private readonly root;
@@ -41,6 +44,15 @@ declare class JsonRenderActionsService {
41
44
  confirm(): void;
42
45
  /** Cancel the pending confirmation dialog. */
43
46
  cancel(): void;
47
+ /**
48
+ * Validate every registered field and write `{ valid, errors }` to the
49
+ * action's `statePath` (default `/formValidation`). Returns null when there
50
+ * is no validation service to ask.
51
+ *
52
+ * Shared by `validateForm` and `submitForm`: the two must report the same
53
+ * thing about the same form, and one implementation is how that stays true.
54
+ */
55
+ private checkForm;
44
56
  private lookupHandler;
45
57
  private runHandler;
46
58
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<JsonRenderActionsService, never>;
@@ -57,6 +69,41 @@ declare function injectAction(binding: ActionBinding): {
57
69
  isLoading: Signal<boolean>;
58
70
  };
59
71
 
72
+ /**
73
+ * How much a spec has to be trusted.
74
+ *
75
+ * - `off` — render whatever arrives. The default, and what every version
76
+ * before this one did.
77
+ * - `warn` — check the spec, apply the lossless fixes, report what is left,
78
+ * render it anyway.
79
+ * - `strict` — the same check, but an error means the spec does not render.
80
+ */
81
+ type SpecValidationMode = 'off' | 'warn' | 'strict';
82
+ /** What {@link checkSpec} found, and the spec that should render. */
83
+ interface SpecCheck {
84
+ /**
85
+ * The spec to render: the input with lossless fixes applied, or the input
86
+ * untouched when validation is off.
87
+ */
88
+ readonly spec: Spec | null;
89
+ /** Structural issues left after the fixes. */
90
+ readonly issues: readonly SpecIssue[];
91
+ /** Descriptions of the fixes that were applied. */
92
+ readonly fixes: readonly string[];
93
+ /** Whether an issue of `error` severity survived. */
94
+ readonly hasErrors: boolean;
95
+ }
96
+ /**
97
+ * Check a spec the way `validate` asks for.
98
+ *
99
+ * Only lossless fixes are applied — `visible`, `on` and `repeat` that a model
100
+ * put inside `props`, which the renderer would otherwise ignore, moved back
101
+ * where they belong. The lossy ones prune content, and core's own guidance is
102
+ * to re-prompt rather than accept that; a renderer that silently deleted
103
+ * elements would be the worse failure.
104
+ */
105
+ declare function checkSpec(spec: Spec | null, mode: SpecValidationMode): SpecCheck;
106
+
60
107
  /**
61
108
  * State setter function for updating application state.
62
109
  * Matches the `SetState` contract of the other json-render renderers
@@ -281,6 +328,19 @@ declare class JsonRenderer {
281
328
  readonly loading: _angular_core.InputSignal<boolean>;
282
329
  /** Fallback component for unknown types. */
283
330
  readonly fallback: _angular_core.InputSignal<RegistryEntry | Type<unknown> | null>;
331
+ /**
332
+ * Whether to check the spec's structure before rendering it, and what to do
333
+ * about what turns up. Off by default.
334
+ *
335
+ * `'warn'` reports the issues and renders anyway; `'strict'` renders nothing
336
+ * when the spec has errors. Both first apply the lossless fixes — `visible`,
337
+ * `on` and `repeat` misplaced inside `props` are moved back to the element,
338
+ * where they take effect instead of being ignored.
339
+ *
340
+ * The check is skipped while `loading` is true: a spec that is still
341
+ * arriving is expected to reference children that have not streamed in yet.
342
+ */
343
+ readonly validate: _angular_core.InputSignal<SpecValidationMode>;
284
344
  /**
285
345
  * Initial state model (uncontrolled mode). Defaults to `spec.state`.
286
346
  * Ignored when `store` is provided.
@@ -306,11 +366,31 @@ declare class JsonRenderer {
306
366
  readonly stateStore: JsonRenderStateService;
307
367
  /** The action dispatcher of this renderer. */
308
368
  protected readonly actions: JsonRenderActionsService;
369
+ /**
370
+ * The spec as checked, and what the check found. While `loading`, the mode
371
+ * is forced off: the spec is still arriving, and the missing children it
372
+ * refers to are the normal state of a stream, not a defect.
373
+ */
374
+ private readonly checked;
309
375
  constructor();
310
376
  protected readonly rootKey: _angular_core.Signal<string | null>;
311
377
  protected readonly pendingConfirm: _angular_core.Signal<_json_render_core.ActionConfirm | null>;
378
+ /**
379
+ * The app's own dialog, if it provided one. Read once: which component
380
+ * answers a confirmation is an application decision, not something that
381
+ * changes between two confirmations.
382
+ */
383
+ protected readonly customDialog: Type<unknown> | null;
384
+ private readonly injector;
385
+ /**
386
+ * A replacement dialog takes no inputs and emits no outputs — it injects
387
+ * everything it needs, the way catalog components do. That keeps the seam
388
+ * one token wide instead of a component contract the renderer would have to
389
+ * bind to.
390
+ */
391
+ protected readonly confirmInjector: _angular_core.Signal<_angular_core.DestroyableInjector | null>;
312
392
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<JsonRenderer, never>;
313
- 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; }; "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>;
393
+ 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; }; "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>;
314
394
  }
315
395
 
316
396
  /**
@@ -416,21 +496,103 @@ declare class JrRepeatScope implements RepeatScope {
416
496
  * Default confirmation dialog shown for action bindings with a `confirm`
417
497
  * field. Rendered automatically by `<json-render>`; can also be used
418
498
  * standalone with a custom action flow.
499
+ *
500
+ * It is a real dialog: `role="dialog"` with `aria-modal`, labelled by its own
501
+ * title and described by its message, focus moved in on open and returned to
502
+ * wherever it came from on close, Tab kept inside it and Escape cancelling.
503
+ * A generated UI can ask for confirmation of something destructive, so a
504
+ * keyboard or screen-reader user has to be able to answer it.
505
+ *
506
+ * Colours come from CSS custom properties with light and dark defaults, so an
507
+ * app can theme it — `--jr-confirm-surface`, `--jr-confirm-ink`,
508
+ * `--jr-confirm-muted`, `--jr-confirm-border`, `--jr-confirm-scrim`,
509
+ * `--jr-confirm-accent`, `--jr-confirm-danger`, `--jr-confirm-on-accent`,
510
+ * `--jr-confirm-radius` — and replace it outright through
511
+ * {@link JR_CONFIRM_DIALOG} when that is not enough.
419
512
  */
420
513
  declare class JrConfirmDialog {
421
514
  readonly config: _angular_core.InputSignal<ActionConfirm>;
422
515
  readonly confirmed: _angular_core.OutputEmitterRef<void>;
423
516
  readonly cancelled: _angular_core.OutputEmitterRef<void>;
517
+ protected readonly labels: ngx_json_render.ConfirmLabels;
518
+ private readonly id;
519
+ protected readonly titleId: string;
520
+ protected readonly messageId: string;
521
+ private readonly cancelButton;
522
+ private readonly confirmButton;
424
523
  protected readonly isDanger: _angular_core.Signal<boolean>;
524
+ constructor();
525
+ /**
526
+ * Keep Tab inside the dialog. With exactly two buttons the whole trap is
527
+ * wrapping the ends; a modal that lets focus wander behind it is a modal
528
+ * only for people using a mouse.
529
+ */
530
+ protected onKeydown(event: KeyboardEvent): void;
425
531
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<JrConfirmDialog, never>;
426
532
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<JrConfirmDialog, "jr-confirm-dialog", never, { "config": { "alias": "config"; "required": true; "isSignal": true; }; }, { "confirmed": "confirmed"; "cancelled": "cancelled"; }, never, never, true, never>;
427
533
  }
428
534
 
535
+ /** The two words the confirmation dialog says on its own. */
536
+ interface ConfirmLabels {
537
+ confirm: string;
538
+ cancel: string;
539
+ }
540
+ /**
541
+ * Labels for the default confirmation dialog's buttons, when the spec does not
542
+ * supply `confirmLabel` / `cancelLabel` of its own.
543
+ *
544
+ * They are the only strings this package puts on screen, and they were English
545
+ * with no way to change them. Override once, in an app's providers:
546
+ *
547
+ * ```ts
548
+ * { provide: JR_CONFIRM_LABELS, useValue: { confirm: 'Подтвердить', cancel: 'Отмена' } }
549
+ * ```
550
+ */
551
+ declare const JR_CONFIRM_LABELS: InjectionToken<ConfirmLabels>;
552
+ /**
553
+ * Everything a replacement confirmation dialog is given: the config the spec
554
+ * asked for, and the two ways to answer it. Inject it with
555
+ * {@link injectConfirmContext}.
556
+ */
557
+ interface ConfirmContext {
558
+ /** Title, message, labels and variant, straight from the action binding. */
559
+ readonly config: ActionConfirm;
560
+ /** Let the action proceed. */
561
+ readonly confirm: () => void;
562
+ /** Dismiss it — a normal answer, not a failure. */
563
+ readonly cancel: () => void;
564
+ }
565
+ /** The confirmation a replacement dialog component is rendered for. */
566
+ declare const CONFIRM_CONTEXT: InjectionToken<ConfirmContext>;
567
+ /**
568
+ * The component `<json-render>` should render for a `confirm` on an action.
569
+ * Unset means the packaged {@link JrConfirmDialog}.
570
+ *
571
+ * The component is rendered with a {@link CONFIRM_CONTEXT} in its injector, so
572
+ * it takes no inputs and emits no outputs:
573
+ *
574
+ * ```ts
575
+ * @Component({ template: `<my-modal (ok)="ctx.confirm()" (dismiss)="ctx.cancel()">…` })
576
+ * export class AppConfirm {
577
+ * readonly ctx = injectConfirmContext();
578
+ * }
579
+ * // providers: [{ provide: JR_CONFIRM_DIALOG, useValue: AppConfirm }]
580
+ * ```
581
+ */
582
+ declare const JR_CONFIRM_DIALOG: InjectionToken<Type<unknown>>;
583
+ /** Inject the confirmation a replacement dialog component is rendering for. */
584
+ declare function injectConfirmContext(): ConfirmContext;
585
+
429
586
  /**
430
587
  * The render context of the element currently being rendered.
431
588
  * Provided by the renderer for every catalog component instance.
432
589
  */
433
590
  declare const RENDER_CONTEXT: InjectionToken<RenderContext<Record<string, unknown>>>;
591
+ /**
592
+ * The spec key of the element currently being rendered. Provided alongside
593
+ * {@link RENDER_CONTEXT} for every catalog component instance.
594
+ */
595
+ declare const ELEMENT_KEY: InjectionToken<Signal<string>>;
434
596
  /**
435
597
  * The current repeat scope. Present only for elements rendered inside a
436
598
  * `repeat` block.
@@ -451,6 +613,21 @@ declare const REPEAT_SCOPE: InjectionToken<RepeatScope>;
451
613
  * ```
452
614
  */
453
615
  declare function injectRenderContext<P = Record<string, unknown>>(): RenderContext<P>;
616
+ /**
617
+ * Inject the spec key of the element this component renders.
618
+ *
619
+ * Catalog components that collect their children — a tab group discovering
620
+ * its tabs, for instance — need it to order registrations by the spec's
621
+ * `children` array rather than by the order the children happened to
622
+ * announce themselves in.
623
+ *
624
+ * @example
625
+ * ```ts
626
+ * const key = injectElementKey();
627
+ * const index = computed(() => parent.element().children?.indexOf(key()) ?? -1);
628
+ * ```
629
+ */
630
+ declare function injectElementKey(): Signal<string>;
454
631
  /**
455
632
  * Inject the current repeat scope, or `null` when the component is not
456
633
  * rendered inside a `repeat` block.
@@ -496,17 +673,36 @@ interface FieldValidationState {
496
673
  * Form validation state of a `<json-render>` subtree. Fields register their
497
674
  * {@link ValidationConfig}; the built-in `validateForm` action validates all
498
675
  * registered fields and writes the result to state.
676
+ *
677
+ * Registrations are owner-scoped: a path stays registered while at least one
678
+ * owner holds it, and its config and state are dropped when the last one
679
+ * releases. A spec that swaps screens or hides fields therefore stops
680
+ * validating what is no longer on screen.
499
681
  */
500
682
  declare class JsonRenderValidationService {
501
683
  private readonly root;
502
684
  private readonly state;
503
685
  private readonly _fieldStates;
504
686
  private readonly _fieldConfigs;
687
+ /** Live owners per path — the reference count behind a registration. */
688
+ private readonly fieldOwners;
505
689
  /** Validation state per registered field path. */
506
690
  readonly fieldStates: Signal<Record<string, FieldValidationState>>;
507
691
  get customFunctions(): Record<string, ValidationFunction>;
508
- /** Register (or update) a field's validation config. */
509
- registerField(path: string, config: ValidationConfig): void;
692
+ /**
693
+ * Register (or update) a field's validation config.
694
+ *
695
+ * @param owner Identity holding the registration — pass the same value to
696
+ * {@link unregisterField} to release it. Field components pass themselves,
697
+ * so two components bound to one path each hold their own claim.
698
+ */
699
+ registerField(path: string, config: ValidationConfig, owner?: object): void;
700
+ /**
701
+ * Release one owner's registration of a field. The config and validation
702
+ * state survive while another owner still holds the path; once the last
703
+ * one lets go, `validateAll` stops seeing the field.
704
+ */
705
+ unregisterField(path: string, owner?: object): void;
510
706
  /** Validate a single field and record the result. */
511
707
  validate(path: string, config: ValidationConfig): ValidationResult;
512
708
  /** Mark a field as touched. */
@@ -523,6 +719,10 @@ declare function injectValidation(): JsonRenderValidationService;
523
719
  /**
524
720
  * Field-level validation helper for catalog input components: registers the
525
721
  * config and exposes the field's validation state as signals.
722
+ *
723
+ * The registration follows the component: it moves when the bound path
724
+ * changes and is released when the component is destroyed, so a field that
725
+ * has left the screen no longer takes part in `validateForm`.
526
726
  */
527
727
  declare function injectFieldValidation(path: string | (() => string), config?: ValidationConfig | (() => ValidationConfig | undefined)): {
528
728
  state: Signal<FieldValidationState>;
@@ -593,6 +793,73 @@ declare function defineRegistry<C extends Catalog>(catalog: C, options: DefineRe
593
793
  */
594
794
  declare function createStoreSetState(store: Pick<StateStore, 'getSnapshot' | 'update'>): SetState;
595
795
 
796
+ /**
797
+ * Apply an RFC 6902 JSON patch to a spec, returning a new spec object and
798
+ * sharing every subtree the patch did not touch.
799
+ *
800
+ * This is the only patch engine in the package: `injectUIStream`,
801
+ * `injectChatUI` and `buildSpecFromParts` all route through it, so the same
802
+ * stream produces the same spec whichever one an app reached for. The write
803
+ * primitives come from `@json-render/core`, so `add` splices into an array
804
+ * where `replace` overwrites, exactly as the other renderers do.
805
+ *
806
+ * One deliberate deviation from RFC 6902: a failing `test` op is a no-op
807
+ * rather than an abort. These patches come off a model's output, where the
808
+ * established behaviour — see the line parser — is to drop what does not make
809
+ * sense and keep rendering rather than kill a generation mid-flight.
810
+ */
811
+ declare function applyPatch(spec: Spec, patch: JsonPatch): Spec;
812
+
813
+ /**
814
+ * Convert a flat element list to a Spec.
815
+ * Input elements use key/parentKey to establish identity and relationships.
816
+ * Output spec uses the map-based format where key is the map entry key
817
+ * and parent-child relationships are expressed through children arrays.
818
+ */
819
+ declare function flatToTree(elements: FlatElement[]): Spec;
820
+ /**
821
+ * A single part from the AI SDK's `message.parts` array. This is a minimal
822
+ * structural type so that library helpers do not depend on the AI SDK.
823
+ */
824
+ interface DataPart {
825
+ type: string;
826
+ text?: string;
827
+ data?: unknown;
828
+ /**
829
+ * The SDK's reconciliation key. A part written with one is *replaced* by the
830
+ * next part carrying the same id, rather than followed by it — so a spec
831
+ * streamed as patches must be written without an id, and only a whole-spec
832
+ * part (`flat` / `nested`), which is a snapshot of itself, can share one.
833
+ */
834
+ id?: string;
835
+ }
836
+ /**
837
+ * Build a `Spec` by replaying all spec data parts from a message's
838
+ * parts array (AI SDK `UIMessage.parts`). Returns `null` if no spec data
839
+ * parts are present.
840
+ */
841
+ declare function buildSpecFromParts(parts: DataPart[]): Spec | null;
842
+ /**
843
+ * Extract and join all text content from a message's parts array.
844
+ */
845
+ declare function getTextFromParts(parts: DataPart[]): string;
846
+ /**
847
+ * Extract both the json-render spec and the text content from a message's
848
+ * parts array, as memoized signals. Angular counterpart of
849
+ * `useJsonRenderMessage` from the other renderers.
850
+ *
851
+ * @example
852
+ * ```ts
853
+ * readonly msg = jsonRenderMessage(() => this.message().parts);
854
+ * // template: @if (msg.hasSpec()) { <json-render [spec]="msg.spec()" ... /> }
855
+ * ```
856
+ */
857
+ declare function jsonRenderMessage(parts: Signal<DataPart[]> | (() => DataPart[])): {
858
+ spec: Signal<Spec | null>;
859
+ text: Signal<string>;
860
+ hasSpec: Signal<boolean>;
861
+ };
862
+
596
863
  /**
597
864
  * Token usage metadata from AI generation.
598
865
  */
@@ -602,11 +869,17 @@ interface TokenUsage {
602
869
  totalTokens: number;
603
870
  }
604
871
  /**
605
- * Apply an RFC 6902 JSON patch to the current spec, returning a new spec
606
- * object (structural sharing for untouched elements).
607
- * Supports add, remove, replace, move, copy, and test operations.
872
+ * Per-call options for {@link UIStreamReturn.send}.
608
873
  */
609
- declare function applyPatch(spec: Spec, patch: JsonPatch): Spec;
874
+ interface UIStreamSendOptions {
875
+ /** Arbitrary context forwarded to the endpoint as `context`. */
876
+ context?: Record<string, unknown>;
877
+ /**
878
+ * Spec to refine instead of starting from an empty one. It is sent to the
879
+ * endpoint as `currentSpec` and the streamed patches apply on top of it.
880
+ */
881
+ previousSpec?: Spec;
882
+ }
610
883
  /**
611
884
  * Options for {@link injectUIStream}.
612
885
  */
@@ -617,6 +890,17 @@ interface UIStreamOptions {
617
890
  onComplete?: (spec: Spec) => void;
618
891
  /** Callback on error */
619
892
  onError?: (error: Error) => void;
893
+ /**
894
+ * Whether to check the finished spec's structure, and what a problem means.
895
+ * Off by default.
896
+ *
897
+ * The check runs once, when the generation completes — a spec mid-stream is
898
+ * supposed to be incomplete. Both modes apply the lossless fixes and publish
899
+ * the fixed spec; `'warn'` reports what is left, while `'strict'` fails the
900
+ * generation instead of calling `onComplete`, so an app that persists the
901
+ * spec there does not persist a broken one.
902
+ */
903
+ validate?: SpecValidationMode;
620
904
  /**
621
905
  * Transport, defaulting to the global `fetch`.
622
906
  *
@@ -643,9 +927,19 @@ interface UIStreamReturn {
643
927
  readonly usage: Signal<TokenUsage | null>;
644
928
  /** Raw JSONL lines received from the stream (JSON patch lines) */
645
929
  readonly rawLines: Signal<string[]>;
930
+ /**
931
+ * Structural issues in the finished spec. Empty until a generation
932
+ * completes, and always empty while `validate` is off.
933
+ */
934
+ readonly issues: Signal<readonly SpecIssue[]>;
646
935
  /** Send a prompt to generate UI */
647
- send: (prompt: string, context?: Record<string, unknown>) => Promise<void>;
648
- /** Clear the current spec */
936
+ send: (prompt: string, options?: UIStreamSendOptions) => Promise<void>;
937
+ /**
938
+ * Stop the in-flight generation, keeping whatever has rendered so far.
939
+ * A no-op when nothing is streaming.
940
+ */
941
+ stop: () => void;
942
+ /** Stop any generation and clear the spec, error, usage and raw lines. */
649
943
  clear: () => void;
650
944
  }
651
945
  /**
@@ -666,48 +960,7 @@ interface UIStreamReturn {
666
960
  * ```
667
961
  */
668
962
  declare function injectUIStream(options: UIStreamOptions): UIStreamReturn;
669
- /**
670
- * Convert a flat element list to a Spec.
671
- * Input elements use key/parentKey to establish identity and relationships.
672
- * Output spec uses the map-based format where key is the map entry key
673
- * and parent-child relationships are expressed through children arrays.
674
- */
675
- declare function flatToTree(elements: FlatElement[]): Spec;
676
- /**
677
- * A single part from the AI SDK's `message.parts` array. This is a minimal
678
- * structural type so that library helpers do not depend on the AI SDK.
679
- */
680
- interface DataPart {
681
- type: string;
682
- text?: string;
683
- data?: unknown;
684
- }
685
- /**
686
- * Build a `Spec` by replaying all spec data parts from a message's
687
- * parts array (AI SDK `UIMessage.parts`). Returns `null` if no spec data
688
- * parts are present.
689
- */
690
- declare function buildSpecFromParts(parts: DataPart[]): Spec | null;
691
- /**
692
- * Extract and join all text content from a message's parts array.
693
- */
694
- declare function getTextFromParts(parts: DataPart[]): string;
695
- /**
696
- * Extract both the json-render spec and the text content from a message's
697
- * parts array, as memoized signals. Angular counterpart of
698
- * `useJsonRenderMessage` from the other renderers.
699
- *
700
- * @example
701
- * ```ts
702
- * readonly msg = jsonRenderMessage(() => this.message().parts);
703
- * // template: @if (msg.hasSpec()) { <json-render [spec]="msg.spec()" ... /> }
704
- * ```
705
- */
706
- declare function jsonRenderMessage(parts: Signal<DataPart[]> | (() => DataPart[])): {
707
- spec: Signal<Spec | null>;
708
- text: Signal<string>;
709
- hasSpec: Signal<boolean>;
710
- };
963
+
711
964
  /**
712
965
  * A single message in the chat, which may contain text, a rendered UI spec,
713
966
  * or both.
@@ -734,11 +987,17 @@ interface ChatUIOptions {
734
987
  onError?: (error: Error) => void;
735
988
  /**
736
989
  * Transport, defaulting to the global `fetch`. Same contract as
737
- * {@link UIStreamOptions.fetch}: it receives the endpoint and a request
990
+ * `UIStreamOptions.fetch`: it receives the endpoint and a request
738
991
  * carrying the JSON body and the abort signal, and must resolve to a
739
992
  * `Response` whose `body` streams the reply.
740
993
  */
741
994
  fetch?: typeof globalThis.fetch;
995
+ /**
996
+ * Whether to check the spec a reply built, and what a problem means. Off by
997
+ * default; same contract as `UIStreamOptions.validate`, applied once per
998
+ * reply that produced a spec.
999
+ */
1000
+ validate?: SpecValidationMode;
742
1001
  }
743
1002
  /**
744
1003
  * Return type for {@link injectChatUI}.
@@ -750,9 +1009,19 @@ interface ChatUIReturn {
750
1009
  readonly isStreaming: Signal<boolean>;
751
1010
  /** Error from the last request, if any */
752
1011
  readonly error: Signal<Error | null>;
1012
+ /**
1013
+ * Structural issues in the spec of the last completed reply. Empty until a
1014
+ * reply completes, and always empty while `validate` is off.
1015
+ */
1016
+ readonly issues: Signal<readonly SpecIssue[]>;
753
1017
  /** Send a user message */
754
1018
  send: (text: string) => Promise<void>;
755
- /** Clear all messages and reset the conversation */
1019
+ /**
1020
+ * Stop the in-flight reply, keeping whatever the assistant has said so far.
1021
+ * A no-op when nothing is streaming.
1022
+ */
1023
+ stop: () => void;
1024
+ /** Stop any reply and clear all messages and the error. */
756
1025
  clear: () => void;
757
1026
  }
758
1027
  /**
@@ -841,5 +1110,5 @@ type AngularSpec<TCatalog> = typeof schema extends {
841
1110
  */
842
1111
  declare function injectDevtoolsActive(): Signal<boolean>;
843
1112
 
844
- export { JrChildren, JrConfirmDialog, JrElement, JrRepeatScope, JsonRenderActionsService, JsonRenderRootContext, JsonRenderStateService, JsonRenderValidationService, JsonRenderer, RENDER_CONTEXT, REPEAT_SCOPE, applyPatch, buildSpecFromParts, createStoreSetState, defineRegistry, flatToTree, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectDevtoolsActive, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, jsonRenderMessage, schema };
845
- export type { ActionFn, Actions, AngularSchema, AngularSpec, CatalogHasActions, ChatMessage, ChatUIOptions, ChatUIReturn, ComponentRegistry, Components, DataPart, DefineRegistryResult, EventHandle, FieldValidationState, PendingConfirmation, RegistryEntry, RenderContext, RepeatScope, SetState, StateChange, TokenUsage, UIStreamOptions, UIStreamReturn };
1113
+ 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, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectConfirmContext, injectDevtoolsActive, injectElementKey, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, isActionCancelled, jsonRenderMessage, schema };
1114
+ export type { ActionFn, Actions, AngularSchema, AngularSpec, CatalogHasActions, ChatMessage, ChatUIOptions, ChatUIReturn, ComponentRegistry, Components, ConfirmContext, ConfirmLabels, DataPart, DefineRegistryResult, EventHandle, FieldValidationState, PendingConfirmation, RegistryEntry, RenderContext, RepeatScope, SetState, SpecCheck, SpecValidationMode, StateChange, TokenUsage, UIStreamOptions, UIStreamReturn, UIStreamSendOptions };