@radix-ng/primitives 1.0.10 → 1.1.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.
Files changed (50) hide show
  1. package/README.md +26 -5
  2. package/fesm2022/radix-ng-primitives-autocomplete.mjs +24 -16
  3. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs +55 -7
  5. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-combobox.mjs +35 -18
  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 +181 -21
  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-popper.mjs +80 -11
  23. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-scroll-area.mjs +6 -3
  25. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-select.mjs +36 -12
  27. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-signal-forms.mjs +27 -11
  29. package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-time-field.mjs +12 -6
  31. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  32. package/package.json +8 -3
  33. package/types/radix-ng-primitives-autocomplete.d.ts +7 -2
  34. package/types/radix-ng-primitives-checkbox.d.ts +10 -1
  35. package/types/radix-ng-primitives-combobox.d.ts +29 -2
  36. package/types/radix-ng-primitives-composite.d.ts +48 -2
  37. package/types/radix-ng-primitives-core.d.ts +152 -94
  38. package/types/radix-ng-primitives-date-field.d.ts +7 -4
  39. package/types/radix-ng-primitives-field.d.ts +55 -8
  40. package/types/radix-ng-primitives-form.d.ts +9 -0
  41. package/types/radix-ng-primitives-input.d.ts +10 -2
  42. package/types/radix-ng-primitives-menu.d.ts +45 -4
  43. package/types/radix-ng-primitives-navigation-menu.d.ts +2 -2
  44. package/types/radix-ng-primitives-popover.d.ts +2 -2
  45. package/types/radix-ng-primitives-popper.d.ts +60 -15
  46. package/types/radix-ng-primitives-preview-card.d.ts +2 -2
  47. package/types/radix-ng-primitives-select.d.ts +17 -3
  48. package/types/radix-ng-primitives-signal-forms.d.ts +22 -8
  49. package/types/radix-ng-primitives-time-field.d.ts +9 -6
  50. package/types/radix-ng-primitives-tooltip.d.ts +2 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @radix-ng/primitives
2
2
 
3
- > **Headless, signals-first UI primitives for Angular.**
3
+ > **Headless, signals-first UI primitives with first-class Angular Forms support.**
4
4
 
5
5
  Radix NG is a low-level UI primitive library for Angular with a focus on accessibility,
6
6
  customization, and developer experience. The primitives are **headless** — they ship no styles and
@@ -30,18 +30,18 @@ Each primitive is its own secondary entry point — import only what you use:
30
30
  ```ts
31
31
  import { Component } from '@angular/core';
32
32
  import {
33
+ RdxCollapsiblePanelDirective,
33
34
  RdxCollapsibleRootDirective,
34
- RdxCollapsibleTriggerDirective,
35
- RdxCollapsibleContentDirective
35
+ RdxCollapsibleTriggerDirective
36
36
  } from '@radix-ng/primitives/collapsible';
37
37
 
38
38
  @Component({
39
39
  selector: 'app-demo',
40
- imports: [RdxCollapsibleRootDirective, RdxCollapsibleTriggerDirective, RdxCollapsibleContentDirective],
40
+ imports: [RdxCollapsibleRootDirective, RdxCollapsibleTriggerDirective, RdxCollapsiblePanelDirective],
41
41
  template: `
42
42
  <div rdxCollapsibleRoot>
43
43
  <button rdxCollapsibleTrigger>Toggle</button>
44
- <div rdxCollapsibleContent>Content</div>
44
+ <div rdxCollapsiblePanel>Content</div>
45
45
  </div>
46
46
  `
47
47
  })
@@ -51,6 +51,27 @@ export class DemoComponent {}
51
51
  Style the parts via the `data-*` attributes they expose (e.g. `[data-state="open"]`,
52
52
  `[data-disabled]`) with any CSS approach — Tailwind, CSS modules, vanilla CSS.
53
53
 
54
+ ## Angular forms, one Field contract
55
+
56
+ The same controls and accessible Field parts work across every Angular form API while Angular owns
57
+ the model and validation:
58
+
59
+ ```html
60
+ <!-- Reactive Forms / ngModel -->
61
+ <input rdxInput formControlName="email" rdxNgControlField />
62
+
63
+ <!-- Signal Forms -->
64
+ <input rdxInput [formField]="accountForm.email" rdxSignalField />
65
+ ```
66
+
67
+ ControlValueAccessors remain available for Reactive and template-driven forms. The optional
68
+ `@radix-ng/primitives/signal-forms` entry adds Angular Signal Forms without a separate Radix NG form
69
+ model. Signal Forms is stable in Angular 22; Reactive Forms and `ngModel` remain supported on Angular
70
+ 21 and 22. Compare the [paired recipes and runtime-verified
71
+ matrix](https://radix-ng.com/docs/?path=/docs/primitives-signal-forms--docs#one-recipe-two-angular-apis),
72
+ and see the [migration guide](https://radix-ng.com/docs/?path=/docs/guides-forms-migration--docs) when
73
+ upgrading from 1.0.10.
74
+
54
75
  ## Documentation
55
76
 
56
77
  - 📖 [radix-ng.com](https://radix-ng.com) — documentation, examples & API reference
@@ -100,6 +100,11 @@ const context = () => {
100
100
  modal: root.modal,
101
101
  virtualized: root.virtualized,
102
102
  grid: root.grid,
103
+ invalidState: root.invalidState,
104
+ pendingState: root.formUi.pendingState,
105
+ validState: root.formUi.validState,
106
+ touchedState: root.touchedState,
107
+ dirtyState: root.dirtyState,
103
108
  filteredItems: engine.filteredItems,
104
109
  highlightedItem: engine.highlightedItem,
105
110
  highlightedIndex: engine.highlightedIndex,
@@ -142,9 +147,6 @@ const context = () => {
142
147
  setInputValue: (value) => root.setQuery(value),
143
148
  openAndHighlight: (edge, reason, event) => root.openAndHighlight(edge, reason, event),
144
149
  navigateByKeyboard: (direction, event) => root.navigateByKeyboard(direction, event),
145
- invalidState: root.invalidState,
146
- touchedState: root.touchedState,
147
- dirtyState: root.dirtyState,
148
150
  select: (item, event) => root.handleSelect(item, event),
149
151
  selectIndex: (index, event) => root.selectIndex(index, event),
150
152
  selectHighlighted: (event) => root.selectHighlighted(event),
@@ -304,6 +306,9 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
304
306
  */
305
307
  this.filter = input(undefined, /* @ts-ignore */
306
308
  ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
309
+ /** Locale for the default `contains` filter's string comparison. Defaults to the runtime locale. */
310
+ this.locale = input(/* @ts-ignore */
311
+ ...(ngDevMode ? [undefined, { debugName: "locale" }] : /* istanbul ignore next */ []));
307
312
  /** Maximum number of matching items to show. `-1` (default) means no limit. */
308
313
  this.limit = input(-1, { ...(ngDevMode ? { debugName: "limit" } : /* istanbul ignore next */ {}), transform: numberAttribute });
309
314
  /** The full set of item values for {@link virtualized} mode. */
@@ -338,6 +343,8 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
338
343
  ...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
339
344
  /** @ignore */
340
345
  this.invalidState = this.formUi.invalidState;
346
+ this.pendingState = this.formUi.pendingState;
347
+ this.validState = this.formUi.validState;
341
348
  /** @ignore */
342
349
  this.touchedState = this.formUi.touchedState;
343
350
  /** @ignore */
@@ -375,6 +382,7 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
375
382
  virtualized: this.virtualized,
376
383
  items: this.items,
377
384
  filter: this.filter,
385
+ locale: this.locale,
378
386
  limit: this.limit,
379
387
  grid: this.grid,
380
388
  rowOf: (element) => element.closest('[rdxAutocompleteRow]'),
@@ -670,7 +678,7 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
670
678
  this.cvaDisabled.set(isDisabled);
671
679
  }
672
680
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxAutocompleteRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
673
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxAutocompleteRoot, isStandalone: true, selector: "[rdxAutocompleteRoot]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, loopFocus: { classPropertyName: "loopFocus", publicName: "loopFocus", isSignal: true, isRequired: false, transformFunction: null }, autoHighlight: { classPropertyName: "autoHighlight", publicName: "autoHighlight", isSignal: true, isRequired: false, transformFunction: null }, highlightItemOnHover: { classPropertyName: "highlightItemOnHover", publicName: "highlightItemOnHover", isSignal: true, isRequired: false, transformFunction: null }, keepHighlight: { classPropertyName: "keepHighlight", publicName: "keepHighlight", isSignal: true, isRequired: false, transformFunction: null }, openOnInputClick: { classPropertyName: "openOnInputClick", publicName: "openOnInputClick", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, submitOnItemClick: { classPropertyName: "submitOnItemClick", publicName: "submitOnItemClick", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, virtualized: { classPropertyName: "virtualized", publicName: "virtualized", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToStringValue: { classPropertyName: "itemToStringValue", publicName: "itemToStringValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", open: "openChange", onValueChange: "onValueChange", onOpenChange: "onOpenChange", onItemHighlighted: "onItemHighlighted", onOpenChangeComplete: "onOpenChangeComplete" }, host: { properties: { "attr.data-disabled": "disabledState() ? \"\" : undefined" } }, providers: [
681
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxAutocompleteRoot, isStandalone: true, selector: "[rdxAutocompleteRoot]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, loopFocus: { classPropertyName: "loopFocus", publicName: "loopFocus", isSignal: true, isRequired: false, transformFunction: null }, autoHighlight: { classPropertyName: "autoHighlight", publicName: "autoHighlight", isSignal: true, isRequired: false, transformFunction: null }, highlightItemOnHover: { classPropertyName: "highlightItemOnHover", publicName: "highlightItemOnHover", isSignal: true, isRequired: false, transformFunction: null }, keepHighlight: { classPropertyName: "keepHighlight", publicName: "keepHighlight", isSignal: true, isRequired: false, transformFunction: null }, openOnInputClick: { classPropertyName: "openOnInputClick", publicName: "openOnInputClick", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, submitOnItemClick: { classPropertyName: "submitOnItemClick", publicName: "submitOnItemClick", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, virtualized: { classPropertyName: "virtualized", publicName: "virtualized", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToStringValue: { classPropertyName: "itemToStringValue", publicName: "itemToStringValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", open: "openChange", onValueChange: "onValueChange", onOpenChange: "onOpenChange", onItemHighlighted: "onItemHighlighted", onOpenChangeComplete: "onOpenChangeComplete" }, host: { properties: { "attr.data-disabled": "disabledState() ? \"\" : undefined" } }, providers: [
674
682
  provideComboboxRootContext(context),
675
683
  { provide: NG_VALUE_ACCESSOR, useExisting: RdxAutocompleteRoot, multi: true },
676
684
  // New floating foundation (ADR 0015/0017) — the dismissal capability reads this shared context.
@@ -695,7 +703,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
695
703
  '[attr.data-disabled]': 'disabledState() ? "" : undefined'
696
704
  }
697
705
  }]
698
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], defaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOpen", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], loopFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "loopFocus", required: false }] }], autoHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoHighlight", required: false }] }], highlightItemOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightItemOnHover", required: false }] }], keepHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepHighlight", required: false }] }], openOnInputClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnInputClick", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], submitOnItemClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitOnItemClick", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], virtualized: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualized", required: false }] }], isItemEqualToValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "isItemEqualToValue", required: false }] }], itemToStringValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemToStringValue", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onItemHighlighted: [{ type: i0.Output, args: ["onItemHighlighted"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
706
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], defaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOpen", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], loopFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "loopFocus", required: false }] }], autoHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoHighlight", required: false }] }], highlightItemOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightItemOnHover", required: false }] }], keepHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepHighlight", required: false }] }], openOnInputClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnInputClick", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], submitOnItemClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitOnItemClick", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], virtualized: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualized", required: false }] }], isItemEqualToValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "isItemEqualToValue", required: false }] }], itemToStringValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemToStringValue", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onItemHighlighted: [{ type: i0.Output, args: ["onItemHighlighted"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
699
707
 
700
708
  /**
701
709
  * Clears the input value. Hidden when there is nothing to clear.
@@ -905,18 +913,21 @@ class RdxAutocompleteInput {
905
913
  // `aria-autocomplete` tokens. Must reflect the static mechanism, not the transient inline preview.
906
914
  this.ariaAutocomplete = computed(() => this.root.mode(), /* @ts-ignore */
907
915
  ...(ngDevMode ? [{ debugName: "ariaAutocomplete" }] : /* istanbul ignore next */ []));
908
- this.invalidState = computed(() => this.invalid() || this.root.invalidState() || Boolean(this.fieldRootContext?.invalidState()), /* @ts-ignore */
909
- ...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
910
916
  /**
911
917
  * Tri-state *displayed* validity: the enclosing Field's gated `validState` when inside a `rdxFieldRoot`
912
918
  * (so a field whose `validationMode` defers display (e.g. `onBlur`) keeps the input neutral until revealed), else the input's
913
919
  * own binary invalidity.
914
920
  */
915
- this.displayValid = computed(() => this.fieldRootContext
916
- ? this.fieldRootContext.validState()
917
- : this.invalid() || this.root.invalidState()
918
- ? false
919
- : true, /* @ts-ignore */
921
+ this.displayValid = computed(() => {
922
+ if (this.fieldRootContext) {
923
+ return this.fieldRootContext.validState();
924
+ }
925
+ const rootValid = this.root.validState();
926
+ if (rootValid === null) {
927
+ return null;
928
+ }
929
+ return this.invalid() ? false : rootValid;
930
+ }, /* @ts-ignore */
920
931
  ...(ngDevMode ? [{ debugName: "displayValid" }] : /* istanbul ignore next */ []));
921
932
  this.disabledState = computed(() => this.root.disabledState() || Boolean(this.fieldRootContext?.disabledState()), /* @ts-ignore */
922
933
  ...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
@@ -934,10 +945,7 @@ class RdxAutocompleteInput {
934
945
  if (!this.fieldRootContext) {
935
946
  return undefined;
936
947
  }
937
- const ids = [
938
- ...this.fieldRootContext.descriptionIds(),
939
- ...(this.fieldRootContext.invalidState() ? this.fieldRootContext.errorIds() : [])
940
- ];
948
+ const ids = [...this.fieldRootContext.descriptionIds(), ...this.fieldRootContext.errorIds()];
941
949
  return ids.length ? ids.join(' ') : undefined;
942
950
  }, /* @ts-ignore */
943
951
  ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));