@radix-ng/primitives 1.0.10 → 1.1.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.
Files changed (65) hide show
  1. package/README.md +26 -5
  2. package/fesm2022/radix-ng-primitives-autocomplete.mjs +88 -21
  3. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs +199 -129
  5. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-combobox.mjs +203 -52
  7. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  8. package/fesm2022/radix-ng-primitives-composite.mjs +221 -36
  9. package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-core.mjs +352 -24
  11. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-date-field.mjs +11 -5
  13. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-field.mjs +127 -23
  15. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-form.mjs +35 -4
  17. package/fesm2022/radix-ng-primitives-form.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-input.mjs +87 -15
  19. package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-menu.mjs +34 -7
  21. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-number-field.mjs +38 -7
  23. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-popper.mjs +80 -11
  25. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-radio.mjs +49 -2
  27. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-scroll-area.mjs +6 -3
  29. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-select.mjs +70 -14
  31. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-signal-forms.mjs +27 -11
  33. package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-slider.mjs +17 -2
  35. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  36. package/fesm2022/radix-ng-primitives-switch.mjs +60 -7
  37. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-time-field.mjs +12 -6
  39. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  40. package/fesm2022/radix-ng-primitives-toggle-group.mjs +25 -3
  41. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  42. package/package.json +8 -3
  43. package/types/radix-ng-primitives-autocomplete.d.ts +16 -2
  44. package/types/radix-ng-primitives-checkbox.d.ts +66 -44
  45. package/types/radix-ng-primitives-combobox.d.ts +74 -3
  46. package/types/radix-ng-primitives-composite.d.ts +48 -2
  47. package/types/radix-ng-primitives-core.d.ts +212 -96
  48. package/types/radix-ng-primitives-date-field.d.ts +7 -4
  49. package/types/radix-ng-primitives-field.d.ts +55 -8
  50. package/types/radix-ng-primitives-form.d.ts +9 -0
  51. package/types/radix-ng-primitives-input.d.ts +10 -2
  52. package/types/radix-ng-primitives-menu.d.ts +45 -4
  53. package/types/radix-ng-primitives-navigation-menu.d.ts +2 -2
  54. package/types/radix-ng-primitives-number-field.d.ts +12 -5
  55. package/types/radix-ng-primitives-popover.d.ts +2 -2
  56. package/types/radix-ng-primitives-popper.d.ts +60 -15
  57. package/types/radix-ng-primitives-preview-card.d.ts +2 -2
  58. package/types/radix-ng-primitives-radio.d.ts +6 -1
  59. package/types/radix-ng-primitives-select.d.ts +25 -4
  60. package/types/radix-ng-primitives-signal-forms.d.ts +22 -8
  61. package/types/radix-ng-primitives-slider.d.ts +2 -1
  62. package/types/radix-ng-primitives-switch.d.ts +19 -3
  63. package/types/radix-ng-primitives-time-field.d.ts +9 -6
  64. package/types/radix-ng-primitives-toggle-group.d.ts +5 -1
  65. package/types/radix-ng-primitives-tooltip.d.ts +2 -2
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Type, Provider, InputSignal, WritableSignal, Signal, ModelSignal, ProviderToken, InputSignalWithTransform, OutputRef, InjectionToken, OutputEmitterRef, Injector, ElementRef, EffectRef, EffectCleanupRegisterFn, CreateEffectOptions } from '@angular/core';
2
+ import { InputSignal, InputSignalWithTransform, ModelSignal, OutputRef, Signal, Type, Provider, WritableSignal, ProviderToken, InjectionToken, OutputEmitterRef, Injector, ElementRef, EffectRef, EffectCleanupRegisterFn, CreateEffectOptions } from '@angular/core';
3
3
  import { ControlValueAccessor } from '@angular/forms';
4
4
  import { DateValue, Time, CalendarDateTime, ZonedDateTime } from '@internationalized/date';
5
5
  import * as _radix_ng_primitives_core from '@radix-ng/primitives/core';
@@ -101,6 +101,116 @@ declare class RdxControlValueAccessor<T> implements ControlValueAccessor {
101
101
  */
102
102
  declare function injectControlValueAccessor<T>(): RdxControlValueAccessor<T>;
103
103
 
104
+ /**
105
+ * Local mirror of Angular Signal Forms' control contracts
106
+ * (`@angular/forms/signals`, stable in Angular 22).
107
+ *
108
+ * These interfaces intentionally do **not** import from `@angular/forms/signals`,
109
+ * so control entries do not pull that optional integration into their public
110
+ * dependency graph. Primitives can still declare `implements RdxFormValueControl<T>` /
111
+ * `implements RdxFormCheckboxControl`; the mirror locks the public surface (the
112
+ * required `value` / `checked` signal) and catches naming regressions on CI — e.g.
113
+ * a rewrite renaming `value` → `modelValue` (as the slider once had) would no
114
+ * longer type-check.
115
+ *
116
+ * Optional state types are widened only where Radix NG controls legitimately
117
+ * differ from Angular's exact types (e.g. `input<string>()` produces
118
+ * `string | undefined`, and boolean inputs carry a coercion transform).
119
+ *
120
+ * The real Angular types stay isolated in `@radix-ng/primitives/signal-forms`.
121
+ * See ADR 0018 and `.claude/skills/project-knowledge/references/signal-forms-readiness.md`.
122
+ */
123
+ /** An optional control-state member exposed as an Angular input signal (with or without a coercion transform). */
124
+ type RdxFormStateInput<T> = InputSignal<T> | InputSignalWithTransform<T, any>;
125
+ /**
126
+ * Minimal stand-in for Angular's `ValidationError`. The real type is a tagged
127
+ * union (`RequiredValidationError`, `PatternValidationError`, …); this keeps a
128
+ * shared shape until the v22 type is available.
129
+ */
130
+ interface RdxValidationError {
131
+ readonly kind: string;
132
+ readonly message?: string;
133
+ }
134
+ /** Optional state shared by value and checkbox controls (mirror of Angular's `FormUiControl`). */
135
+ interface RdxFormUiControl {
136
+ readonly disabled?: RdxFormStateInput<boolean>;
137
+ readonly readonly?: RdxFormStateInput<boolean>;
138
+ readonly required?: RdxFormStateInput<boolean>;
139
+ readonly invalid?: RdxFormStateInput<boolean>;
140
+ readonly hidden?: RdxFormStateInput<boolean>;
141
+ readonly pending?: RdxFormStateInput<boolean>;
142
+ /**
143
+ * Touched status the form writes into the control.
144
+ *
145
+ * The two API generations disagree on the notification half: the 21.x
146
+ * experimental implementation listens to a `touched` **model**'s
147
+ * `touchedChange` output, while stable Angular 22 reverted to a plain
148
+ * `touched` input plus a separate {@link touch} output. A `model()` set on
149
+ * blur **plus** an emitted `touch` output satisfies both (`ModelSignal`
150
+ * extends `InputSignalWithTransform`, so it type-checks as the 22 input).
151
+ */
152
+ readonly touched?: ModelSignal<boolean> | RdxFormStateInput<boolean> | OutputRef<boolean>;
153
+ readonly dirty?: RdxFormStateInput<boolean>;
154
+ readonly name?: RdxFormStateInput<string | undefined>;
155
+ readonly errors?: RdxFormStateInput<readonly RdxValidationError[]>;
156
+ readonly minLength?: RdxFormStateInput<number | undefined>;
157
+ readonly maxLength?: RdxFormStateInput<number | undefined>;
158
+ readonly pattern?: RdxFormStateInput<readonly RegExp[]>;
159
+ /** Notifies the form the control was touched (stable Angular 22 contract; ignored by 21.x). */
160
+ readonly touch?: OutputRef<void>;
161
+ /** Resets the control's UI state (optional method added in stable Angular 22). */
162
+ reset?(): void;
163
+ }
164
+ /**
165
+ * Mirror of `FormValueControl<TValue>` — a control that edits a single value via
166
+ * `value = model<TValue>()`. It must **not** expose `checked`.
167
+ */
168
+ interface RdxFormValueControl<TValue> extends RdxFormUiControl {
169
+ readonly value: ModelSignal<TValue>;
170
+ readonly checked?: undefined;
171
+ readonly min?: RdxFormStateInput<NonNullable<TValue> | undefined>;
172
+ readonly max?: RdxFormStateInput<NonNullable<TValue> | undefined>;
173
+ }
174
+ /**
175
+ * Mirror of `FormCheckboxControl` — a control that toggles via
176
+ * `checked = model<boolean>()`. It must **not** expose `value`.
177
+ */
178
+ interface RdxFormCheckboxControl extends RdxFormUiControl {
179
+ readonly checked: ModelSignal<boolean>;
180
+ readonly value?: undefined;
181
+ }
182
+
183
+ /**
184
+ * Form state owned by an Angular Reactive Forms or template-driven `NgControl` on the same host
185
+ * element. `connected` stays false for standalone and Signal Forms controls.
186
+ *
187
+ * @ignore
188
+ */
189
+ interface RdxNgControlState {
190
+ readonly connected: Signal<boolean>;
191
+ readonly name: Signal<string | undefined>;
192
+ readonly value: Signal<unknown>;
193
+ readonly valid: Signal<boolean>;
194
+ readonly invalid: Signal<boolean>;
195
+ readonly pending: Signal<boolean>;
196
+ readonly disabled: Signal<boolean>;
197
+ readonly errors: Signal<readonly RdxValidationError[]>;
198
+ readonly dirty: Signal<boolean>;
199
+ readonly touched: Signal<boolean>;
200
+ /** Resets a connected Reactive/template-driven control; returns whether one was connected. */
201
+ reset(value: unknown): boolean;
202
+ }
203
+ /**
204
+ * Lazily connects to the same-host `NgControl` without creating a circular dependency while Angular
205
+ * is resolving its `ControlValueAccessor`. The unified `AbstractControl.events` stream mirrors
206
+ * programmatic value, status, validation, and interaction-state changes as well as user-driven ones.
207
+ *
208
+ * Must be called in an injection context.
209
+ *
210
+ * @ignore
211
+ */
212
+ declare function injectNgControlState(): RdxNgControlState;
213
+
104
214
  /**
105
215
  * Include in the providers section of a component which utilizes ControlValueAccessor to redundant code.
106
216
  *
@@ -461,7 +571,7 @@ type UseDateFieldProps = {
461
571
  disabled: Signal<boolean>;
462
572
  readonly: Signal<boolean>;
463
573
  part: SegmentPart;
464
- modelValue: ModelSignal<DateValue | undefined> | WritableSignal<DateValue | undefined>;
574
+ modelValue: ModelSignal<DateValue | null> | WritableSignal<DateValue | null>;
465
575
  focusNext: () => void;
466
576
  };
467
577
  type SegmentAttrProps = {
@@ -665,8 +775,9 @@ type ItemValueComparator<T = unknown> = ((a: T, b: T) => boolean) | string;
665
775
  */
666
776
  declare function itemToStringLabel(value: unknown): string;
667
777
  /**
668
- * Converts an item value to the string used for form serialization. Defaults to the same rules as
669
- * {@link itemToStringLabel}; kept as a separate export so a primitive can diverge label vs. value.
778
+ * Converts an item value to the string used for form serialization. A conventional `{ value, label }`
779
+ * item serializes its `value` member; other non-string values use Base UI-compatible JSON serialization
780
+ * with `String()` as a fallback. Kept separate so a primitive can diverge label vs. value.
670
781
  */
671
782
  declare function itemToStringValue(value: unknown): string;
672
783
  /**
@@ -784,86 +895,7 @@ declare function isEqual(a: any, b: any): boolean;
784
895
  declare function provideExistingToken<T>(token: ProviderToken<T>, type: Type<T>): Provider;
785
896
 
786
897
  /**
787
- * Local mirror of Angular Signal Forms' control contracts
788
- * (`@angular/forms/signals`, stable in Angular 22).
789
- *
790
- * These interfaces intentionally do **not** import from `@angular/forms/signals`
791
- * so primitives can declare `implements RdxFormValueControl<T>` /
792
- * `implements RdxFormCheckboxControl` while the library baseline is still on
793
- * Angular 21, where the real API is experimental. They mirror Angular's contract
794
- * closely enough to lock the public surface (the required `value` / `checked`
795
- * signal) and catch naming regressions on CI — e.g. a rewrite renaming
796
- * `value` → `modelValue` (as the slider once had) would no longer type-check.
797
- *
798
- * Optional state types are widened only where Radix NG controls legitimately
799
- * differ from Angular's exact types (e.g. `input<string>()` produces
800
- * `string | undefined`, and boolean inputs carry a coercion transform).
801
- *
802
- * Replace with the real imports once the baseline moves to Angular 22.
803
- * See `.claude/skills/project-knowledge/references/signal-forms-readiness.md`.
804
- */
805
- /** An optional control-state member exposed as an Angular input signal (with or without a coercion transform). */
806
- type RdxFormStateInput<T> = InputSignal<T> | InputSignalWithTransform<T, any>;
807
- /**
808
- * Minimal stand-in for Angular's `ValidationError`. The real type is a tagged
809
- * union (`RequiredValidationError`, `PatternValidationError`, …); this keeps a
810
- * shared shape until the v22 type is available.
811
- */
812
- interface RdxValidationError {
813
- readonly kind: string;
814
- readonly message?: string;
815
- }
816
- /** Optional state shared by value and checkbox controls (mirror of Angular's `FormUiControl`). */
817
- interface RdxFormUiControl {
818
- readonly disabled?: RdxFormStateInput<boolean>;
819
- readonly readonly?: RdxFormStateInput<boolean>;
820
- readonly required?: RdxFormStateInput<boolean>;
821
- readonly invalid?: RdxFormStateInput<boolean>;
822
- readonly hidden?: RdxFormStateInput<boolean>;
823
- readonly pending?: RdxFormStateInput<boolean>;
824
- /**
825
- * Touched status the form writes into the control.
826
- *
827
- * The two API generations disagree on the notification half: the 21.x
828
- * experimental implementation listens to a `touched` **model**'s
829
- * `touchedChange` output, while stable Angular 22 reverted to a plain
830
- * `touched` input plus a separate {@link touch} output. A `model()` set on
831
- * blur **plus** an emitted `touch` output satisfies both (`ModelSignal`
832
- * extends `InputSignalWithTransform`, so it type-checks as the 22 input).
833
- */
834
- readonly touched?: ModelSignal<boolean> | RdxFormStateInput<boolean> | OutputRef<boolean>;
835
- readonly dirty?: RdxFormStateInput<boolean>;
836
- readonly name?: RdxFormStateInput<string | undefined>;
837
- readonly errors?: RdxFormStateInput<readonly RdxValidationError[]>;
838
- readonly minLength?: RdxFormStateInput<number | undefined>;
839
- readonly maxLength?: RdxFormStateInput<number | undefined>;
840
- readonly pattern?: RdxFormStateInput<readonly RegExp[]>;
841
- /** Notifies the form the control was touched (stable Angular 22 contract; ignored by 21.x). */
842
- readonly touch?: OutputRef<void>;
843
- /** Resets the control's UI state (optional method added in stable Angular 22). */
844
- reset?(): void;
845
- }
846
- /**
847
- * Mirror of `FormValueControl<TValue>` — a control that edits a single value via
848
- * `value = model<TValue>()`. It must **not** expose `checked`.
849
- */
850
- interface RdxFormValueControl<TValue> extends RdxFormUiControl {
851
- readonly value: ModelSignal<TValue>;
852
- readonly checked?: undefined;
853
- readonly min?: RdxFormStateInput<NonNullable<TValue> | undefined>;
854
- readonly max?: RdxFormStateInput<NonNullable<TValue> | undefined>;
855
- }
856
- /**
857
- * Mirror of `FormCheckboxControl` — a control that toggles via
858
- * `checked = model<boolean>()`. It must **not** expose `value`.
859
- */
860
- interface RdxFormCheckboxControl extends RdxFormUiControl {
861
- readonly checked: ModelSignal<boolean>;
862
- readonly value?: undefined;
863
- }
864
-
865
- /**
866
- * Optional form-control UI state derived from a control's `invalid`/`errors`/`touched`/`dirty`
898
+ * Optional form-control UI state derived from a control's `invalid`/`pending`/`errors`/`touched`/`dirty`
867
899
  * inputs (the optional members of Signal Forms' `FormUiControl`), plus the two mutators every
868
900
  * control needs.
869
901
  *
@@ -872,6 +904,12 @@ interface RdxFormCheckboxControl extends RdxFormUiControl {
872
904
  interface RdxFormUiState {
873
905
  /** Invalid when the `invalid` input is set or the `errors` list is non-empty. */
874
906
  readonly invalidState: Signal<boolean>;
907
+ /** Whether asynchronous validation is still settling. Pending is neither valid nor invalid. */
908
+ readonly pendingState: Signal<boolean>;
909
+ /** Tri-state validity: valid / invalid / neutral while pending or disabled. */
910
+ readonly validState: Signal<boolean | null>;
911
+ /** Explicit control errors merged with same-host Angular Forms errors. */
912
+ readonly errorsState: Signal<readonly RdxValidationError[]>;
875
913
  /** Whether the control has been touched (reflects the `touched` model). */
876
914
  readonly touchedState: Signal<boolean>;
877
915
  /** Whether the value changed from its initial value — external `dirty` OR internal tracking. */
@@ -880,6 +918,10 @@ interface RdxFormUiState {
880
918
  markAsTouched(): void;
881
919
  /** Flag the control dirty after a value change (feeds {@link dirtyState}). */
882
920
  markDirty(): void;
921
+ /** Clear control-owned touched/dirty state when a form resets it. */
922
+ resetInteractionState?(): void;
923
+ /** Clear only control-owned dirty state when the form marks the field pristine. */
924
+ resetDirtyState?(): void;
883
925
  }
884
926
  /** Minimal `ControlValueAccessor` surface the form-UI state bridges for the dual (Reactive/template) path. */
885
927
  interface RdxFormUiTouchTarget {
@@ -888,20 +930,24 @@ interface RdxFormUiTouchTarget {
888
930
  /** Inputs a control passes to {@link createFormUiState} — its own `FormUiControl` signals. */
889
931
  interface RdxFormUiStateOptions {
890
932
  readonly invalid: Signal<boolean>;
933
+ /** Optional for backwards compatibility with custom controls authored before pending support. */
934
+ readonly pending?: Signal<boolean>;
891
935
  readonly errors: Signal<readonly RdxValidationError[]>;
892
936
  readonly touched: ModelSignal<boolean>;
893
937
  readonly touch: OutputEmitterRef<void>;
894
938
  readonly dirty: Signal<boolean>;
939
+ /** Same-host Reactive/template-driven form interaction state, when one is present. */
940
+ readonly ngControlState?: RdxNgControlState | null;
895
941
  /**
896
942
  * The control's `ControlValueAccessor`, if it has one (dual controls — switch, number-field, …).
897
- * Omit or pass `null` for Signal-Forms-only controls without a CVA (e.g. select).
943
+ * Omit or pass `null` for Signal-Forms-only controls without a CVA (e.g. date-field).
898
944
  */
899
945
  readonly cva?: RdxFormUiTouchTarget | null;
900
946
  }
901
947
  /**
902
948
  * Builds the shared form-UI state and its mutators from a control's input signals, removing the
903
- * per-control copy-paste of the `invalidState`/`touchedState`/`dirtyState` computeds and the
904
- * `markAsTouched`/`markDirty` logic.
949
+ * per-control copy-paste of the validation/interaction computeds and the `markAsTouched`/`markDirty`
950
+ * logic.
905
951
  *
906
952
  * **Why the inputs stay on the control (not in here):** Angular's compiler only discovers
907
953
  * `input()`/`model()` declared as field initializers, and Signal Forms binds form-written state
@@ -919,6 +965,8 @@ declare function createFormUiState(options: RdxFormUiStateOptions): RdxFormUiSta
919
965
  */
920
966
  interface RdxFormUiStateContext {
921
967
  invalidState: Signal<boolean>;
968
+ pendingState: Signal<boolean>;
969
+ validState: Signal<boolean | null>;
922
970
  touchedState: Signal<boolean>;
923
971
  dirtyState: Signal<boolean>;
924
972
  markAsTouched: () => void;
@@ -939,14 +987,14 @@ declare const RDX_FIELD_VALIDITY: InjectionToken<Signal<boolean | null>>;
939
987
  /**
940
988
  * Tri-state display validity: when inside a `Field` the field's gated `validState` is the **single
941
989
  * source** (the control reflects it, including its `validationMode` neutral state); standalone, the
942
- * control's own binary invalidity.
990
+ * control's own tri-state validity (pending is neutral).
943
991
  *
944
992
  * **Contract:** inside a `Field` a control's own `invalid` / `errors` inputs are **not** displayed — the
945
993
  * Field owns displayed validity. Drive validity through the Field instead: bind `rdxSignalField`
946
994
  * (Signal Forms), or set `[invalid]` on `rdxFieldRoot`. The control's own `invalid`/`errors` are for
947
995
  * standalone use.
948
996
  */
949
- declare function resolveDisplayValid(fieldValidity: Signal<boolean | null> | null, ownInvalid: Signal<boolean>): boolean | null;
997
+ declare function resolveDisplayValid(fieldValidity: Signal<boolean | null> | null, ownInvalid: Signal<boolean>, ownPending?: Signal<boolean>): boolean | null;
950
998
  /**
951
999
  * Provide a control's {@link RdxFormUiState} on its host element so {@link RdxFormUiStateHost} can
952
1000
  * reflect it. Pair with `hostDirectives: [RdxFormUiStateHost]`:
@@ -985,7 +1033,7 @@ declare class RdxFormUiStateHost {
985
1033
  }
986
1034
  /**
987
1035
  * Abstract base that declares the optional `FormUiControl` state inputs
988
- * (`invalid`/`errors`/`touched`/`dirty` + the `touch` output) once and builds the control's
1036
+ * (`invalid`/`pending`/`errors`/`touched`/`dirty` + the `touch` output) once and builds the control's
989
1037
  * {@link RdxFormUiState} from them, so a control directive can inherit the whole surface with a
990
1038
  * single `extends` instead of re-declaring it.
991
1039
  *
@@ -1002,6 +1050,8 @@ declare class RdxFormUiStateHost {
1002
1050
  declare abstract class RdxFormUiControlBase {
1003
1051
  /** Whether the control is invalid. A non-empty {@link errors} list also marks it invalid. */
1004
1052
  readonly invalid: i0.InputSignalWithTransform<boolean, BooleanInput>;
1053
+ /** Whether async validation is pending. Pending controls publish neither `data-valid` nor `data-invalid`. */
1054
+ readonly pending: i0.InputSignalWithTransform<boolean, BooleanInput>;
1005
1055
  /** Whether the control has been touched. A `model()` so Signal Forms can write it; set on blur/focus-out. */
1006
1056
  readonly touched: ModelSignal<boolean>;
1007
1057
  /** Whether the value changed from its initial value. Merged with internal tracking. */
@@ -1010,8 +1060,12 @@ declare abstract class RdxFormUiControlBase {
1010
1060
  readonly errors: i0.InputSignal<readonly RdxValidationError[]>;
1011
1061
  /** Emits when the control is touched, notifying Signal Forms (stable Angular 22 contract). */
1012
1062
  readonly touch: OutputEmitterRef<void>;
1063
+ private readonly ngControlState;
1013
1064
  /** The shared form-UI state derived from the inputs above. Call `formUi.markDirty()` on value change. */
1014
1065
  readonly formUi: RdxFormUiState;
1066
+ /** Validation errors from the control inputs and a same-host Reactive/template-driven form control. */
1067
+ readonly validationErrors: Signal<readonly RdxValidationError[]>;
1068
+ constructor();
1015
1069
  /** The enclosing Field's tri-state display validity, if any. `protected` so a control whose own
1016
1070
  * invalidity is richer than `formUi.invalidState` (e.g. date/time-field add a parse check) can build
1017
1071
  * its own `displayValid` from it. */
@@ -1019,19 +1073,32 @@ declare abstract class RdxFormUiControlBase {
1019
1073
  /**
1020
1074
  * Tri-state *displayed* validity for controls that bind their own host attributes (radio, switch,
1021
1075
  * number-field): the enclosing Field's gated state when inside a `rdxFieldRoot`, else this control's
1022
- * own binary invalidity (`formUi.invalidState`). Bind `data-valid`/`data-invalid`/`aria-invalid` to
1076
+ * own validity (`formUi.pendingState` is neutral; otherwise `formUi.invalidState` is binary). Bind
1077
+ * `data-valid`/`data-invalid`/`aria-invalid` to
1023
1078
  * this so a neutral field shows neither. Controls whose `invalidState` is richer than
1024
- * `formUi.invalidState` override this with `resolveDisplayValid(this.fieldValidity, this.invalidState)`.
1079
+ * `formUi.invalidState` override this with
1080
+ * `resolveDisplayValid(this.fieldValidity, this.invalidState, this.formUi.pendingState)`.
1025
1081
  */
1026
1082
  readonly displayValid: Signal<boolean | null>;
1027
1083
  /**
1028
1084
  * Override to bridge the control's `ControlValueAccessor` into `markAsTouched` (dual controls —
1029
1085
  * return `injectControlValueAccessor()` or a `{ markAsTouched }` adapter). Default: no CVA
1030
- * (Signal-Forms-only controls such as select).
1086
+ * (Signal-Forms-only controls such as date-field).
1031
1087
  */
1032
1088
  protected formUiTouchTarget(): RdxFormUiTouchTarget | null;
1089
+ /**
1090
+ * Resets a same-host Reactive/template-driven control without reporting the reset as a user edit.
1091
+ * Returns `false` when no Angular `NgControl` is connected, so a control can fall back to its CVA
1092
+ * change callback for an unusually early native reset.
1093
+ */
1094
+ protected resetNgControl(value: unknown): boolean;
1095
+ /**
1096
+ * Reset control-owned interaction state. Angular Signal Forms calls this optional custom-control
1097
+ * hook from `FieldState.reset()` after restoring the field value.
1098
+ */
1099
+ reset(): void;
1033
1100
  static ɵfac: i0.ɵɵFactoryDeclaration<RdxFormUiControlBase, never>;
1034
- static ɵdir: i0.ɵɵDirectiveDeclaration<RdxFormUiControlBase, never, never, { "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; "dirty": { "alias": "dirty"; "required": false; "isSignal": true; }; "errors": { "alias": "errors"; "required": false; "isSignal": true; }; }, { "touched": "touchedChange"; "touch": "touch"; }, never, never, true, never>;
1101
+ static ɵdir: i0.ɵɵDirectiveDeclaration<RdxFormUiControlBase, never, never, { "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "pending": { "alias": "pending"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; "dirty": { "alias": "dirty"; "required": false; "isSignal": true; }; "errors": { "alias": "errors"; "required": false; "isSignal": true; }; }, { "touched": "touchedChange"; "touch": "touch"; }, never, never, true, never>;
1035
1102
  }
1036
1103
 
1037
1104
  /**
@@ -1737,6 +1804,55 @@ declare function elementSize({ elementRef, injector }: {
1737
1804
  */
1738
1805
  declare function getActiveElement(root?: DocumentOrShadowRoot): Element | null;
1739
1806
 
1807
+ /** Configuration for a composite control that participates in a native HTML form. */
1808
+ interface RdxNativeFormControlOptions<T> {
1809
+ /** Form-field name. No value is contributed while it is absent. */
1810
+ readonly name: Signal<string | undefined>;
1811
+ /** Optional id of an external owning form. */
1812
+ readonly form: Signal<string | undefined>;
1813
+ /** Disabled controls are excluded from successful form controls. */
1814
+ readonly disabled: Signal<boolean>;
1815
+ /** Current control value, used when the form constructs `FormData`. */
1816
+ readonly value?: Signal<T>;
1817
+ /** Converts a value to one or more form entries. Omit for controls with their own native inputs. */
1818
+ readonly serialize?: (value: T) => readonly string[];
1819
+ /** Whether a consumer-rendered native input currently owns serialization. */
1820
+ readonly hasNativeControl?: Signal<boolean>;
1821
+ /** Synchronizes a consumer-rendered native control immediately before an imperative submit. */
1822
+ readonly syncNativeControl?: () => void;
1823
+ /** Value restored by a native form reset. Captured once after inputs bind. */
1824
+ readonly defaultValue?: () => T;
1825
+ /** Applies the captured default value after a native form reset. */
1826
+ readonly onReset?: (value: T) => void;
1827
+ }
1828
+ /** Imperative operations needed by controls that submit immediately after a value change. */
1829
+ interface RdxNativeFormControl {
1830
+ /** The current owning form, including an external form selected by the `form` input. */
1831
+ ownerForm(): HTMLFormElement | null;
1832
+ /** Synchronizes generated/native controls with the current signals. */
1833
+ sync(): void;
1834
+ /** Synchronizes the value and requests submission from the owning form. */
1835
+ requestSubmit(): void;
1836
+ }
1837
+ /**
1838
+ * Converts a scalar or array model to HTML form values. `null` and `undefined` represent no
1839
+ * successful control; arrays deliberately become repeated entries under the same field name.
1840
+ */
1841
+ declare function serializeNativeFormValue(value: unknown, itemToStringValue?: (entry: unknown) => string): readonly string[];
1842
+ /**
1843
+ * Adds native form semantics to a composite control, rendering hidden inputs only when the control
1844
+ * has no native successful control of its own.
1845
+ *
1846
+ * A hidden input is necessary for a headless root to enter the platform's successful-controls tree
1847
+ * (including older engines that do not dispatch `formdata` for `new FormData(form)`). One input is
1848
+ * created per serialized value, preserving repeated-field semantics; controls with their own native
1849
+ * input use this solely for reset and imperative-submit coordination.
1850
+ *
1851
+ * Generated inputs are browser-only. Rendering imperative sibling nodes on the server would put DOM
1852
+ * outside Angular's hydration graph; the client creates them after its view has been claimed instead.
1853
+ */
1854
+ declare function useNativeFormControl<T>(options: RdxNativeFormControlOptions<T>): RdxNativeFormControl;
1855
+
1740
1856
  /**
1741
1857
  * Creates a resize observer effect for element
1742
1858
  *
@@ -2056,5 +2172,5 @@ declare enum RdxPositionAlign {
2056
2172
  End = "end"
2057
2173
  }
2058
2174
 
2059
- export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, DOCS_BASE_URL, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RDX_DEFAULT_VALIDATION_MODE, RDX_FIELD_VALIDITY, RDX_FLOATING_REGISTRATION, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_TREE, RDX_FORM_UI_STATE, RDX_INTERNAL_BACKDROP_ATTR, RDX_SCROLL_LOCKED_ATTR, RdxControlValueAccessor, RdxFloatingNode, RdxFloatingNodeRegistration, RdxFloatingRegistrationContext, RdxFloatingRootContext, RdxFloatingTree, RdxFormUiControlBase, RdxFormUiStateHost, RdxIdGenerator, RdxLiveAnnouncer, RdxPositionAlign, RdxPositionSide, RdxTriggerRegistry, SHIFT, SPACE, SPACE_CODE, TAB, TIME_GRANULARITIES, a, areAllDaysBetweenValid, clamp, createCancelableChangeEventDetails, createContent, createContext, createFloatingEvents, createFloatingRootContext, createFormUiState, createFormatter, createMonth, createMonths, docsUrl, elementSize, formUiStateContext, getActiveElement, getDaysBetween, getDaysInMonth, getDefaultDate, getDefaultTime, getLastFirstDayOfWeek, getMaxTransitionDuration, getNextLastDayOfWeek, getOptsByGranularity, getPlaceholder, getSegmentElements, getWeekNumber, handleAndDispatchCustomEvent, handleCalendarInitialFocus, hasTime, initializeSegmentValues, injectControlValueAccessor, injectDocument, injectFloatingRootContext, injectId, isAcceptableSegmentKey, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isEqual, isItemEqualToValue, isNullish, isNumberString, isSegmentNavigationKey, isValidationRevealed, isZonedDateTime, itemToStringLabel, itemToStringValue, j, k, n, normalizeDateStep, normalizeHour12, normalizeHourCycle, p, provideExistingToken, provideFloatingRegistration, provideFloatingRootContext, provideFloatingTree, provideFormUiState, provideValueAccessor, rdxCheckLabelElement, rdxCheckTriggerElement, rdxDevError, rdxDevWarning, resetRdxDevWarnings, resizeEffect, resolveDisplayValid, resolveFloatingTree, roundToStepPrecision, segmentBuilders, setupInternalBackdrop, snapValueToStep, syncSegmentValues, syncTimeSegmentValues, toDate, useAnchoredScrollLock, useArrowNavigation, useDateField, useFilter, useGraceArea, useListHighlight, usePointerDrag, useScrollLock, useTransitionStatus, watch };
2060
- export type { AcceptableValue, AnyExceptLiteral, AriaLivePoliteness, BooleanInput, CreateMonthProps, DataOrientation, DateAndTimeSegmentObj, DateFormatterOptions, DateMatcher, DateRange, DateSegmentObj, DateSegmentPart, DateStep, DayPeriod, Direction, EditableSegmentPart, FilterPredicates, Formatter, Granularity, HourCycle, InjectContext, ItemValueComparator, ListHighlight, Month, NonEditableSegmentPart, Nullable, NumberInput, PlaceholderMap, RdxAnchoredScrollLockOptions, RdxCancelableChangeEventDetails, RdxCancelableChangeEventTransaction, RdxFloatingEventMap, RdxFloatingEvents, RdxFloatingLifecycle, RdxFloatingNodeInit, RdxFloatingParentOverride, RdxFloatingRegistrationReader, RdxFloatingRegistrationStatus, RdxFloatingRootContextEventMap, RdxFloatingRootContextInit, RdxFormCheckboxControl, RdxFormStateInput, RdxFormUiControl, RdxFormUiState, RdxFormUiStateContext, RdxFormUiStateOptions, RdxFormUiTouchTarget, RdxFormValueControl, RdxInternalBackdropOptions, RdxPointerDragHandlers, RdxScrollLockOptions, RdxTransitionStatus, RdxTransitionStatusRef, RdxValidationError, RdxValidationInteraction, RdxValidationMode, SafeFunction, SegmentContentObj, SegmentPart, SegmentValueObj, TimeGranularity, TimeSegmentObj, TimeSegmentPart, TimeValue, UseDateFieldProps, UseFilterOptions, UseListHighlightOptions };
2175
+ export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, DOCS_BASE_URL, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RDX_DEFAULT_VALIDATION_MODE, RDX_FIELD_VALIDITY, RDX_FLOATING_REGISTRATION, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_TREE, RDX_FORM_UI_STATE, RDX_INTERNAL_BACKDROP_ATTR, RDX_SCROLL_LOCKED_ATTR, RdxControlValueAccessor, RdxFloatingNode, RdxFloatingNodeRegistration, RdxFloatingRegistrationContext, RdxFloatingRootContext, RdxFloatingTree, RdxFormUiControlBase, RdxFormUiStateHost, RdxIdGenerator, RdxLiveAnnouncer, RdxPositionAlign, RdxPositionSide, RdxTriggerRegistry, SHIFT, SPACE, SPACE_CODE, TAB, TIME_GRANULARITIES, a, areAllDaysBetweenValid, clamp, createCancelableChangeEventDetails, createContent, createContext, createFloatingEvents, createFloatingRootContext, createFormUiState, createFormatter, createMonth, createMonths, docsUrl, elementSize, formUiStateContext, getActiveElement, getDaysBetween, getDaysInMonth, getDefaultDate, getDefaultTime, getLastFirstDayOfWeek, getMaxTransitionDuration, getNextLastDayOfWeek, getOptsByGranularity, getPlaceholder, getSegmentElements, getWeekNumber, handleAndDispatchCustomEvent, handleCalendarInitialFocus, hasTime, initializeSegmentValues, injectControlValueAccessor, injectDocument, injectFloatingRootContext, injectId, injectNgControlState, isAcceptableSegmentKey, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isEqual, isItemEqualToValue, isNullish, isNumberString, isSegmentNavigationKey, isValidationRevealed, isZonedDateTime, itemToStringLabel, itemToStringValue, j, k, n, normalizeDateStep, normalizeHour12, normalizeHourCycle, p, provideExistingToken, provideFloatingRegistration, provideFloatingRootContext, provideFloatingTree, provideFormUiState, provideValueAccessor, rdxCheckLabelElement, rdxCheckTriggerElement, rdxDevError, rdxDevWarning, resetRdxDevWarnings, resizeEffect, resolveDisplayValid, resolveFloatingTree, roundToStepPrecision, segmentBuilders, serializeNativeFormValue, setupInternalBackdrop, snapValueToStep, syncSegmentValues, syncTimeSegmentValues, toDate, useAnchoredScrollLock, useArrowNavigation, useDateField, useFilter, useGraceArea, useListHighlight, useNativeFormControl, usePointerDrag, useScrollLock, useTransitionStatus, watch };
2176
+ export type { AcceptableValue, AnyExceptLiteral, AriaLivePoliteness, BooleanInput, CreateMonthProps, DataOrientation, DateAndTimeSegmentObj, DateFormatterOptions, DateMatcher, DateRange, DateSegmentObj, DateSegmentPart, DateStep, DayPeriod, Direction, EditableSegmentPart, FilterPredicates, Formatter, Granularity, HourCycle, InjectContext, ItemValueComparator, ListHighlight, Month, NonEditableSegmentPart, Nullable, NumberInput, PlaceholderMap, RdxAnchoredScrollLockOptions, RdxCancelableChangeEventDetails, RdxCancelableChangeEventTransaction, RdxFloatingEventMap, RdxFloatingEvents, RdxFloatingLifecycle, RdxFloatingNodeInit, RdxFloatingParentOverride, RdxFloatingRegistrationReader, RdxFloatingRegistrationStatus, RdxFloatingRootContextEventMap, RdxFloatingRootContextInit, RdxFormCheckboxControl, RdxFormStateInput, RdxFormUiControl, RdxFormUiState, RdxFormUiStateContext, RdxFormUiStateOptions, RdxFormUiTouchTarget, RdxFormValueControl, RdxInternalBackdropOptions, RdxNativeFormControl, RdxNativeFormControlOptions, RdxNgControlState, RdxPointerDragHandlers, RdxScrollLockOptions, RdxTransitionStatus, RdxTransitionStatusRef, RdxValidationError, RdxValidationInteraction, RdxValidationMode, SafeFunction, SegmentContentObj, SegmentPart, SegmentValueObj, TimeGranularity, TimeSegmentObj, TimeSegmentPart, TimeValue, UseDateFieldProps, UseFilterOptions, UseListHighlightOptions };
@@ -4,11 +4,12 @@ import * as _radix_ng_primitives_core from '@radix-ng/primitives/core';
4
4
  import { RdxFormUiControlBase, RdxFormValueControl, DateMatcher, HourCycle, Granularity, Direction, BooleanInput, DateStep, Formatter, SegmentValueObj, SegmentPart } from '@radix-ng/primitives/core';
5
5
  import { DateValue } from '@internationalized/date';
6
6
 
7
- declare class RdxDateFieldRootDirective extends RdxFormUiControlBase implements RdxFormValueControl<DateValue | undefined> {
7
+ declare class RdxDateFieldRootDirective extends RdxFormUiControlBase implements RdxFormValueControl<DateValue | null> {
8
8
  /**
9
- * The controlled value of the date field.
9
+ * The controlled value of the date field. `null` represents an empty field and keeps the value
10
+ * addressable as an Angular Signal Forms child field (`undefined` denotes an absent optional path).
10
11
  */
11
- readonly value: _angular_core.ModelSignal<DateValue | undefined>;
12
+ readonly value: _angular_core.ModelSignal<DateValue | null>;
12
13
  /**
13
14
  * A matcher that marks specific dates as unavailable; a matched value makes the field invalid.
14
15
  */
@@ -122,6 +123,8 @@ declare class RdxDateFieldRootDirective extends RdxFormUiControlBase implements
122
123
  private readonly userInteracted;
123
124
  /** @ignore Mark the field touched (model + `touch` output) for Signal Forms. Called on segment blur. */
124
125
  markAsTouched(): void;
126
+ /** Reset segment interaction so the form-owned value write remains pristine. */
127
+ reset(): void;
125
128
  /**
126
129
  * @ignore
127
130
  */
@@ -235,7 +238,7 @@ declare class RdxDateFieldInputDirective {
235
238
 
236
239
  interface DateFieldContextToken {
237
240
  locale: InputSignal<string>;
238
- value: ModelSignal<DateValue | undefined>;
241
+ value: ModelSignal<DateValue | null>;
239
242
  disabled: Signal<boolean>;
240
243
  readonly: Signal<boolean>;
241
244
  isInvalid: Signal<boolean>;
@@ -63,6 +63,15 @@ declare class RdxFieldError {
63
63
  * @group Props
64
64
  */
65
65
  readonly id: _angular_core.InputSignal<string>;
66
+ /**
67
+ * Shows this error only for a matching validation-error key. Bind `true` to keep it visible under
68
+ * external control; omit it for the field's default error behavior.
69
+ *
70
+ * @group Props
71
+ */
72
+ readonly match: _angular_core.InputSignal<string | boolean | undefined>;
73
+ /** Whether this error part is currently presented. */
74
+ readonly visible: _angular_core.Signal<boolean>;
66
75
  /**
67
76
  * The field's validation messages — client (provider / form name-routing, once `validationMode`
68
77
  * reveals them) then server (the Form's `errors` input, always); `[]` when none. Render them
@@ -73,12 +82,11 @@ declare class RdxFieldError {
73
82
  constructor();
74
83
  protected readonly dataAttr: (value: boolean) => "" | undefined;
75
84
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxFieldError, never>;
76
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxFieldError, "[rdxFieldError]", ["rdxFieldError"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
85
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxFieldError, "[rdxFieldError]", ["rdxFieldError"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "match": { "alias": "match"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
77
86
  }
78
87
 
79
88
  /**
80
- * External owner of field state. An adapter (e.g. a future Signal Forms
81
- * `[rdxSignalField]` directive, or a Reactive Forms bridge) registers one via
89
+ * External owner of field state. An adapter (`rdxSignalField` or `rdxNgControlField`) registers one via
82
90
  * `setStateProvider` so Field reads authoritative form state instead of
83
91
  * self-computing it from the DOM. Each member is an optional signal-like
84
92
  * accessor; only the states the adapter owns need to be provided — the rest
@@ -86,15 +94,19 @@ declare class RdxFieldError {
86
94
  * `() => boolean` accessors keeps Field framework-agnostic (no dependency on
87
95
  * `@angular/forms/signals`).
88
96
  *
89
- * See ADR 0004 and `signal-forms-readiness.md` (prep #4).
97
+ * See ADR 0004, ADR 0021, and `signal-forms-readiness.md` (prep #4).
90
98
  */
91
99
  interface RdxFieldState {
100
+ /** Field name inferred by an adapter. An explicit `rdxFieldRoot[name]` always wins. */
101
+ name?: () => string | undefined;
92
102
  /**
93
103
  * The control's **actual** invalidity (ungated). The Field decides *when* to display it from its
94
104
  * `validationMode` (e.g. only after blur) — the adapter just reports the real state. A non-empty
95
105
  * `errors()` also counts as invalid.
96
106
  */
97
107
  invalid?: () => boolean;
108
+ /** Whether async validation is pending. Pending validity remains neutral, never falsely valid. */
109
+ pending?: () => boolean;
98
110
  disabled?: () => boolean;
99
111
  required?: () => boolean;
100
112
  dirty?: () => boolean;
@@ -108,7 +120,7 @@ interface RdxFieldState {
108
120
  * any enclosing Form's server messages. Uses `core`'s framework-free shim type so the seam stays free of
109
121
  * `@angular/forms/signals` (ADR 0004 amendment).
110
122
  */
111
- errors?: () => RdxValidationError[];
123
+ errors?: () => readonly RdxValidationError[];
112
124
  }
113
125
  interface RdxFieldRootContext {
114
126
  controlId: Signal<string>;
@@ -116,6 +128,8 @@ interface RdxFieldRootContext {
116
128
  descriptionIds: Signal<string[]>;
117
129
  errorIds: Signal<string[]>;
118
130
  messages: Signal<string[]>;
131
+ /** Whether the currently displayed client errors contain this validation kind. */
132
+ matchesError: (kind: string) => boolean;
119
133
  notifyEdited: () => void;
120
134
  /** Tri-state *displayed* validity (`true` valid / `false` invalid / `null` neutral), gated by the
121
135
  * field's `validationMode`. The source for `data-valid` / `data-invalid` on the field and its controls. */
@@ -229,6 +243,8 @@ declare class RdxFieldRoot {
229
243
  private readonly stateProvider;
230
244
  /** Whether an external adapter currently owns field state. */
231
245
  readonly hasStateProvider: Signal<boolean>;
246
+ /** Explicit field name wins; an adapter may infer one from its form-control binding. */
247
+ readonly effectiveName: Signal<string | undefined>;
232
248
  /** Error content from a registered state provider (e.g. a Signal Forms adapter). */
233
249
  private readonly providerErrors;
234
250
  /** **Client** validation errors routed by a form-level provider (`rdxSignalForm`) — gated by `validationMode`. */
@@ -246,6 +262,8 @@ declare class RdxFieldRoot {
246
262
  * field state), so deduping renders it once while still surfacing distinct messages from each source.
247
263
  */
248
264
  readonly messages: Signal<string[]>;
265
+ /** Whether a client validation error of `kind` is currently revealed by the field. */
266
+ matchesError(kind: string): boolean;
249
267
  /** Effective validation-display mode: this field's override → the enclosing Form's → the default. */
250
268
  readonly effectiveValidationMode: Signal<RdxValidationMode>;
251
269
  /** Whether client-side validity is revealed yet, per {@link effectiveValidationMode} + interaction. */
@@ -276,6 +294,7 @@ declare class RdxFieldRoot {
276
294
  * the provider/input `invalid`.
277
295
  */
278
296
  readonly actualInvalidState: Signal<boolean>;
297
+ readonly pendingState: Signal<boolean>;
279
298
  readonly disabledState: Signal<boolean>;
280
299
  readonly requiredState: Signal<boolean>;
281
300
  readonly dirtyState: Signal<boolean>;
@@ -310,8 +329,8 @@ declare class RdxFieldRoot {
310
329
  clearStateProvider(provider: RdxFieldState | null, previous: RdxFieldState | null): void;
311
330
  /**
312
331
  * Prefer the registered provider's value for `key` when it exposes one,
313
- * otherwise fall back to the root inputs / DOM-derived signals. `errors` (content) and `valid`
314
- * (tri-state `boolean | null`) are resolved separately, so they're excluded from this boolean key.
332
+ * otherwise fall back to the root inputs / DOM-derived signals. `name` and `errors` are resolved
333
+ * separately, so they're excluded from this boolean key.
315
334
  */
316
335
  private resolve;
317
336
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxFieldRoot, never>;
@@ -369,5 +388,33 @@ declare class RdxFieldLabel {
369
388
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxFieldLabel, "[rdxFieldLabel]", ["rdxFieldLabel"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
370
389
  }
371
390
 
372
- export { RdxFieldControl, RdxFieldDescription, RdxFieldError, RdxFieldItem, RdxFieldLabel, RdxFieldRoot, injectFieldRootContext, provideFieldRootContext };
391
+ /**
392
+ * Bridges a same-host Reactive Forms or template-driven `NgControl` into an enclosing `rdxFieldRoot`.
393
+ * Place it next to `formControl`, `formControlName`, or `ngModel`; Angular remains the source of value,
394
+ * validation, and interaction state while Field owns when that state is displayed.
395
+ *
396
+ * ```html
397
+ * <div rdxFieldRoot>
398
+ * <label rdxFieldLabel>Email</label>
399
+ * <input formControlName="email" rdxFieldControl rdxNgControlField />
400
+ * <p match="required" rdxFieldError>Email is required.</p>
401
+ * </div>
402
+ * ```
403
+ *
404
+ * The adapter also infers the Field name from `formControlName` / `ngModel` for Form-level server-error
405
+ * routing. An explicit `name` on `rdxFieldRoot` takes precedence (useful for nested or remapped keys).
406
+ *
407
+ * @group Components
408
+ */
409
+ declare class RdxNgControlField {
410
+ private readonly ngControlState;
411
+ private readonly fieldContext;
412
+ /** Normalized Angular validation errors (`{ kind, message? }[]`). */
413
+ readonly validationErrors: _angular_core.Signal<readonly _radix_ng_primitives_core.RdxValidationError[]>;
414
+ constructor();
415
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxNgControlField, never>;
416
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxNgControlField, "[formControl][rdxNgControlField], [formControlName][rdxNgControlField], [ngModel][rdxNgControlField]", ["rdxNgControlField"], {}, {}, never, never, true, never>;
417
+ }
418
+
419
+ export { RdxFieldControl, RdxFieldDescription, RdxFieldError, RdxFieldItem, RdxFieldLabel, RdxFieldRoot, RdxNgControlField, injectFieldRootContext, provideFieldRootContext };
373
420
  export type { RdxFieldRootContext, RdxFieldState };
@@ -49,6 +49,13 @@ interface RdxFormState {
49
49
  dirty?: () => boolean;
50
50
  touched?: () => boolean;
51
51
  submitting?: () => boolean;
52
+ /**
53
+ * Optional Angular-owned submission lifecycle. Returning `undefined` keeps the Form's Base UI-style
54
+ * synchronous submit path; returning a Promise delegates this submit to the adapter. Signal Forms
55
+ * uses this only when its opt-in `rdxSignalSubmit` input is present, and calls Angular's public
56
+ * `submit()` API so validation, touched state, concurrency, and submission errors stay Angular-owned.
57
+ */
58
+ submit?: () => Promise<boolean> | undefined;
52
59
  /**
53
60
  * Per-name **client** validation errors (e.g. `rdxSignalForm`'s Signal Forms name-routing). Surfaced
54
61
  * through the Form's `clientErrorsFor` channel and gated by `validationMode` like any client validity.
@@ -190,6 +197,8 @@ declare class RdxFormRoot {
190
197
  */
191
198
  clearStateProvider(provider: RdxFormState | null, previous: RdxFormState | null): void;
192
199
  onSubmit(event: SubmitEvent): void;
200
+ private finishDelegatedSubmit;
201
+ private focusFirstInvalidField;
193
202
  onReset(): void;
194
203
  private readonly resetTimers;
195
204
  constructor();