@radix-ng/primitives 1.0.6 → 1.0.8

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 (83) hide show
  1. package/aspect-ratio/README.md +1 -1
  2. package/fesm2022/radix-ng-primitives-accordion.mjs +6 -1
  3. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-aspect-ratio.mjs +9 -34
  5. package/fesm2022/radix-ng-primitives-aspect-ratio.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-autocomplete.mjs +43 -9
  7. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  8. package/fesm2022/radix-ng-primitives-avatar.mjs +69 -18
  9. package/fesm2022/radix-ng-primitives-avatar.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-checkbox.mjs +93 -16
  11. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-collapsible.mjs +4 -3
  13. package/fesm2022/radix-ng-primitives-collapsible.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-combobox.mjs +43 -9
  15. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-core.mjs +208 -2
  17. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-date-field.mjs +50 -9
  19. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-editable.mjs +31 -5
  21. package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-field.mjs +276 -58
  23. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-form.mjs +111 -27
  25. package/fesm2022/radix-ng-primitives-form.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-input.mjs +16 -6
  27. package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-label.mjs +5 -4
  29. package/fesm2022/radix-ng-primitives-label.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-number-field.mjs +39 -10
  31. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-progress.mjs +37 -22
  33. package/fesm2022/radix-ng-primitives-progress.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-radio.mjs +18 -9
  35. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  36. package/fesm2022/radix-ng-primitives-scroll-area.mjs +4 -4
  37. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-select.mjs +26 -8
  39. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  40. package/fesm2022/radix-ng-primitives-separator.mjs +7 -13
  41. package/fesm2022/radix-ng-primitives-separator.mjs.map +1 -1
  42. package/fesm2022/radix-ng-primitives-signal-forms.mjs +184 -0
  43. package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -0
  44. package/fesm2022/radix-ng-primitives-slider.mjs +25 -10
  45. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  46. package/fesm2022/radix-ng-primitives-switch.mjs +32 -8
  47. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  48. package/fesm2022/radix-ng-primitives-tabs.mjs +4 -4
  49. package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
  50. package/fesm2022/radix-ng-primitives-time-field.mjs +49 -9
  51. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  52. package/fesm2022/radix-ng-primitives-toggle-group.mjs +33 -16
  53. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  54. package/fesm2022/radix-ng-primitives-toggle.mjs +24 -29
  55. package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
  56. package/fesm2022/radix-ng-primitives-toolbar.mjs +15 -2
  57. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  58. package/package.json +11 -1
  59. package/types/radix-ng-primitives-accordion.d.ts +1 -0
  60. package/types/radix-ng-primitives-aspect-ratio.d.ts +10 -23
  61. package/types/radix-ng-primitives-autocomplete.d.ts +21 -2
  62. package/types/radix-ng-primitives-avatar.d.ts +51 -25
  63. package/types/radix-ng-primitives-checkbox.d.ts +78 -101
  64. package/types/radix-ng-primitives-combobox.d.ts +51 -2
  65. package/types/radix-ng-primitives-core.d.ts +206 -3
  66. package/types/radix-ng-primitives-date-field.d.ts +27 -2
  67. package/types/radix-ng-primitives-editable.d.ts +15 -2
  68. package/types/radix-ng-primitives-field.d.ts +165 -236
  69. package/types/radix-ng-primitives-form.d.ts +122 -37
  70. package/types/radix-ng-primitives-input.d.ts +8 -1
  71. package/types/radix-ng-primitives-label.d.ts +3 -2
  72. package/types/radix-ng-primitives-number-field.d.ts +15 -4
  73. package/types/radix-ng-primitives-progress.d.ts +59 -121
  74. package/types/radix-ng-primitives-radio.d.ts +7 -3
  75. package/types/radix-ng-primitives-select.d.ts +11 -3
  76. package/types/radix-ng-primitives-separator.d.ts +0 -7
  77. package/types/radix-ng-primitives-signal-forms.d.ts +97 -0
  78. package/types/radix-ng-primitives-slider.d.ts +9 -7
  79. package/types/radix-ng-primitives-switch.d.ts +15 -2
  80. package/types/radix-ng-primitives-time-field.d.ts +26 -2
  81. package/types/radix-ng-primitives-toggle-group.d.ts +13 -7
  82. package/types/radix-ng-primitives-toggle.d.ts +8 -8
  83. package/types/radix-ng-primitives-toolbar.d.ts +2 -0
@@ -1,12 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { input, booleanAttribute, signal, computed, inject, ElementRef, DestroyRef, Directive, effect, afterNextRender } from '@angular/core';
3
- import { createContext, rdxCheckLabelElement } from '@radix-ng/primitives/core';
3
+ import { createContext, injectId, RDX_DEFAULT_VALIDATION_MODE, isValidationRevealed, RDX_FIELD_VALIDITY, rdxCheckLabelElement } from '@radix-ng/primitives/core';
4
4
  import { injectFormRootContext } from '@radix-ng/primitives/form';
5
5
 
6
- let fieldId = 0;
7
- const attr$4 = (value) => (value ? '' : undefined);
8
- const addId = (ids, id) => (ids.includes(id) ? ids : [...ids, id]);
9
- const removeId = (ids, id) => ids.filter((item) => item !== id);
6
+ const attr$5 = (value) => (value ? '' : undefined);
7
+ const addId$1 = (ids, id) => (ids.includes(id) ? ids : [...ids, id]);
8
+ const removeId$1 = (ids, id) => ids.filter((item) => item !== id);
10
9
  const fieldRootContext = () => {
11
10
  const root = injectFieldRoot();
12
11
  return {
@@ -14,10 +13,13 @@ const fieldRootContext = () => {
14
13
  name: root.name,
15
14
  descriptionIds: root.descriptionIds,
16
15
  errorIds: root.errorIds,
17
- /** Combined external messages (state provider's, then enclosing Form's), for `RdxFieldError`. */
16
+ /** Combined messages for `RdxFieldError`: client (provider + form name-routing, when revealed) then
17
+ * server (`errors` input, always). */
18
18
  messages: root.messages,
19
19
  /** Notify an enclosing Form that this field's control was edited (composite-control opt-in). */
20
20
  notifyEdited: () => root.notifyEdited(),
21
+ validState: root.validState,
22
+ formSubmitAttempted: root.formSubmitAttempted,
21
23
  invalidState: root.invalidState,
22
24
  disabledState: root.disabledState,
23
25
  requiredState: root.requiredState,
@@ -26,10 +28,10 @@ const fieldRootContext = () => {
26
28
  filledState: root.filledState,
27
29
  focusedState: root.focusedState,
28
30
  setControlId: (id) => root.controlId.set(id),
29
- addDescriptionId: (id) => root.descriptionIds.update((ids) => addId(ids, id)),
30
- removeDescriptionId: (id) => root.descriptionIds.update((ids) => removeId(ids, id)),
31
- addErrorId: (id) => root.errorIds.update((ids) => addId(ids, id)),
32
- removeErrorId: (id) => root.errorIds.update((ids) => removeId(ids, id)),
31
+ addDescriptionId: (id) => root.descriptionIds.update((ids) => addId$1(ids, id)),
32
+ removeDescriptionId: (id) => root.descriptionIds.update((ids) => removeId$1(ids, id)),
33
+ addErrorId: (id) => root.errorIds.update((ids) => addId$1(ids, id)),
34
+ removeErrorId: (id) => root.errorIds.update((ids) => removeId$1(ids, id)),
33
35
  setFocused: (value) => root.focusedValue.set(value),
34
36
  setFilled: (value) => root.filledValue.set(value),
35
37
  setDirty: (value) => root.dirtyValue.set(value),
@@ -41,6 +43,7 @@ const fieldRootContext = () => {
41
43
  * provider so adapters can restore it on teardown.
42
44
  */
43
45
  setStateProvider: (provider) => root.setStateProvider(provider),
46
+ clearStateProvider: (provider, previous) => root.clearStateProvider(provider, previous),
44
47
  hasStateProvider: root.hasStateProvider
45
48
  };
46
49
  };
@@ -109,9 +112,17 @@ class RdxFieldRoot {
109
112
  */
110
113
  this.name = input(/* @ts-ignore */
111
114
  ...(ngDevMode ? [undefined, { debugName: "name" }] : /* istanbul ignore next */ []));
115
+ /**
116
+ * Overrides when this field reveals its validity (error styling + message). Falls back to the
117
+ * enclosing `rdxFormRoot`'s `validationMode`, then `'onBlur'`. Server errors always show regardless.
118
+ *
119
+ * @group Props
120
+ */
121
+ this.validationMode = input(/* @ts-ignore */
122
+ ...(ngDevMode ? [undefined, { debugName: "validationMode" }] : /* istanbul ignore next */ []));
112
123
  /** The enclosing Form, if any. All Form-related behavior is a no-op when this is `null`. */
113
124
  this.formContext = injectFormRootContext(true);
114
- this.controlId = signal(`rdx-field-control-${fieldId++}`, /* @ts-ignore */
125
+ this.controlId = signal(injectId('rdx-field-control-'), /* @ts-ignore */
115
126
  ...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
116
127
  this.descriptionIds = signal([], /* @ts-ignore */
117
128
  ...(ngDevMode ? [{ debugName: "descriptionIds" }] : /* istanbul ignore next */ []));
@@ -134,37 +145,108 @@ class RdxFieldRoot {
134
145
  /** Error content from a registered state provider (e.g. a Signal Forms adapter). */
135
146
  this.providerErrors = computed(() => this.stateProvider()?.errors?.() ?? [], /* @ts-ignore */
136
147
  ...(ngDevMode ? [{ debugName: "providerErrors" }] : /* istanbul ignore next */ []));
137
- /** External messages from the enclosing Form matched by this field's `name`. */
138
- this.externalErrors = computed(() => this.formContext?.errorsFor(this.name()) ?? [], /* @ts-ignore */
139
- ...(ngDevMode ? [{ debugName: "externalErrors" }] : /* istanbul ignore next */ []));
140
- /** Provider messages first (`message ?? kind`), then the Form's external messages. */
141
- this.messages = computed(() => [
142
- ...this.providerErrors().map((error) => error.message ?? error.kind),
143
- ...this.externalErrors()
144
- ], /* @ts-ignore */
148
+ /** **Client** validation errors routed by a form-level provider (`rdxSignalForm`) gated by `validationMode`. */
149
+ this.clientErrors = computed(() => this.formContext?.clientErrorsFor(this.name()) ?? [], /* @ts-ignore */
150
+ ...(ngDevMode ? [{ debugName: "clientErrors" }] : /* istanbul ignore next */ []));
151
+ /** **Server/external** errors from the Form's `errors` input shown eagerly (never gated). */
152
+ this.serverErrors = computed(() => this.formContext?.externalErrorsFor(this.name()) ?? [], /* @ts-ignore */
153
+ ...(ngDevMode ? [{ debugName: "serverErrors" }] : /* istanbul ignore next */ []));
154
+ /** Whether the enclosing Form has had a submit attempted; `false` when standalone. A presentation
155
+ * seam an adapter can read to reveal errors after a submit attempt (Base UI's submit-attempt state). */
156
+ this.formSubmitAttempted = computed(() => this.formContext?.submitAttempted() ?? false, /* @ts-ignore */
157
+ ...(ngDevMode ? [{ debugName: "formSubmitAttempted" }] : /* istanbul ignore next */ []));
158
+ /**
159
+ * Client messages first (per-field `rdxSignalField` provider, then form-level `rdxSignalForm`
160
+ * name-routing) — only once `validationMode` reveals client validity — then the Form's **server**
161
+ * messages (the `errors` input), which always show. **Deduped by text**: a field can receive the same
162
+ * Signal Forms error from both a per-field provider and the form-level routing (they read the same
163
+ * field state), so deduping renders it once while still surfacing distinct messages from each source.
164
+ */
165
+ this.messages = computed(() => {
166
+ // **Client** messages (per-field `rdxSignalField` provider + form-level `rdxSignalForm` routing)
167
+ // surface only once the field's `validationMode` reveals them; **server** messages (the `errors`
168
+ // input) always show. So a neutral field stays empty and the polite live region announces on reveal.
169
+ const client = this.validationRevealed()
170
+ ? [...this.providerErrors().map((error) => error.message ?? error.kind), ...this.clientErrors()]
171
+ : [];
172
+ const seen = new Set();
173
+ const result = [];
174
+ for (const message of [...client, ...this.serverErrors()]) {
175
+ if (message && !seen.has(message)) {
176
+ seen.add(message);
177
+ result.push(message);
178
+ }
179
+ }
180
+ return result;
181
+ }, /* @ts-ignore */
145
182
  ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
146
- // External errors (provider content or Form server errors) force invalid regardless of the input or
147
- // a provider's `invalid` accessor a server error must show even when client-side validity passes.
148
- this.invalidState = computed(() => {
149
- if (this.externalErrors().length > 0 || this.providerErrors().length > 0) {
150
- return true;
183
+ /** Effective validation-display mode: this field's override the enclosing Form's the default. */
184
+ this.effectiveValidationMode = computed(() => this.validationMode() ?? this.formContext?.validationMode() ?? RDX_DEFAULT_VALIDATION_MODE, /* @ts-ignore */
185
+ ...(ngDevMode ? [{ debugName: "effectiveValidationMode" }] : /* istanbul ignore next */ []));
186
+ /** Whether client-side validity is revealed yet, per {@link effectiveValidationMode} + interaction. */
187
+ this.validationRevealed = computed(() => isValidationRevealed(this.effectiveValidationMode(), {
188
+ touched: this.touchedState(),
189
+ dirty: this.dirtyState(),
190
+ submitAttempted: this.formSubmitAttempted()
191
+ }), /* @ts-ignore */
192
+ ...(ngDevMode ? [{ debugName: "validationRevealed" }] : /* istanbul ignore next */ []));
193
+ /**
194
+ * Client-side invalidity (gated by `validationMode`): the per-field provider's error content / `invalid`
195
+ * (`rdxSignalField`), a form-level provider's name-routed errors (`rdxSignalForm`), or the `[invalid]`
196
+ * input. Excludes server errors (which are eager).
197
+ */
198
+ this.clientInvalidState = computed(() => this.providerErrors().length > 0 ||
199
+ this.clientErrors().length > 0 ||
200
+ this.resolve('invalid', () => this.invalid()), /* @ts-ignore */
201
+ ...(ngDevMode ? [{ debugName: "clientInvalidState" }] : /* istanbul ignore next */ []));
202
+ /**
203
+ * Tri-state *displayed* validity (`boolean | null`) — the source for `data-valid` / `data-invalid`.
204
+ * Server errors show immediately; client-side validity (including `rdxSignalForm` name-routing) stays
205
+ * **neutral** (`null`) until the field's {@link effectiveValidationMode} reveals it, then `false`/`true`.
206
+ */
207
+ this.validState = computed(() => {
208
+ if (this.serverErrors().length > 0) {
209
+ return false;
210
+ }
211
+ if (!this.validationRevealed()) {
212
+ return null;
151
213
  }
152
- return this.resolve('invalid', () => this.invalid());
214
+ return this.clientInvalidState() ? false : true;
153
215
  }, /* @ts-ignore */
216
+ ...(ngDevMode ? [{ debugName: "validState" }] : /* istanbul ignore next */ []));
217
+ /**
218
+ * Boolean **displayed** invalidity (= `validState() === false`), used by `rdxFieldError` (hidden) and
219
+ * `rdxFieldControl`'s `aria-describedby` error linking. A neutral (`null`) `validState` reads as not
220
+ * invalid here, so the error region stays hidden until the field's mode reveals it.
221
+ */
222
+ this.invalidState = computed(() => this.validState() === false, /* @ts-ignore */
154
223
  ...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
224
+ /**
225
+ * Boolean **actual** invalidity — ungated by the display mode. The enclosing Form aggregates this into
226
+ * `anyInvalid` (→ submit-block / focus-first-invalid), so a field that is really invalid but displayed
227
+ * neutral still blocks submit. The form's presentation `data-invalid` comes from the *displayed*
228
+ * aggregate (`anyDisplayedInvalid`) instead — not from this. Server errors + provider error content +
229
+ * the provider/input `invalid`.
230
+ */
231
+ this.actualInvalidState = computed(() => this.serverErrors().length > 0 || this.clientInvalidState(), /* @ts-ignore */
232
+ ...(ngDevMode ? [{ debugName: "actualInvalidState" }] : /* istanbul ignore next */ []));
155
233
  this.disabledState = computed(() => this.resolve('disabled', () => this.disabled()), /* @ts-ignore */
156
234
  ...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
157
235
  this.requiredState = computed(() => this.resolve('required', () => this.required()), /* @ts-ignore */
158
236
  ...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
159
- this.dirtyState = computed(() => this.resolve('dirty', () => this.dirty() || this.dirtyValue()), /* @ts-ignore */
237
+ // `touched`/`dirty` also OR in the enclosing Form's per-name state (`rdxSignalForm` name-routing), so a
238
+ // field with only a bare `[formField]` (no `rdxSignalField`/`rdxFieldControl`) still reveals on blur.
239
+ this.dirtyState = computed(() => this.resolve('dirty', () => this.dirty() || this.dirtyValue()) ||
240
+ (this.formContext?.dirtyFor(this.name()) ?? false), /* @ts-ignore */
160
241
  ...(ngDevMode ? [{ debugName: "dirtyState" }] : /* istanbul ignore next */ []));
161
- this.touchedState = computed(() => this.resolve('touched', () => this.touched() || this.touchedValue()), /* @ts-ignore */
242
+ this.touchedState = computed(() => this.resolve('touched', () => this.touched() || this.touchedValue()) ||
243
+ (this.formContext?.touchedFor(this.name()) ?? false), /* @ts-ignore */
162
244
  ...(ngDevMode ? [{ debugName: "touchedState" }] : /* istanbul ignore next */ []));
163
245
  this.filledState = computed(() => this.resolve('filled', () => this.filled() ?? this.filledValue()), /* @ts-ignore */
164
246
  ...(ngDevMode ? [{ debugName: "filledState" }] : /* istanbul ignore next */ []));
165
247
  this.focusedState = computed(() => this.resolve('focused', () => this.focused() ?? this.focusedValue()), /* @ts-ignore */
166
248
  ...(ngDevMode ? [{ debugName: "focusedState" }] : /* istanbul ignore next */ []));
167
- this.dataAttr = attr$4;
249
+ this.dataAttr = attr$5;
168
250
  this.host = inject(ElementRef).nativeElement;
169
251
  // Register with an enclosing Form (if any) for aggregate state, submit guard, and reset; a
170
252
  // standalone field never enters this branch and behaves exactly as before.
@@ -172,7 +254,11 @@ class RdxFieldRoot {
172
254
  if (formContext) {
173
255
  const registration = {
174
256
  name: () => this.name(),
175
- invalid: () => this.invalidState(),
257
+ // `invalid` reports *actual* validity (ungated) `form.anyInvalid` + the submit guard must
258
+ // reflect real state even before a field reveals it. `displayValid` reports the *gated*
259
+ // tri-state so the form's presentation `data-invalid` stays neutral on load.
260
+ invalid: () => this.actualInvalidState(),
261
+ displayValid: () => this.validState(),
176
262
  dirty: () => this.dirtyState(),
177
263
  touched: () => this.touchedState(),
178
264
  focus: () => this.focusControl(),
@@ -216,26 +302,47 @@ class RdxFieldRoot {
216
302
  this.stateProvider.set(provider);
217
303
  return previous;
218
304
  }
305
+ /**
306
+ * Identity-checked teardown — roll back to `previous` only if `provider` is still active. Prevents
307
+ * an old adapter's destroy from clobbering a newer adapter that registered after it (create-before-
308
+ * destroy during a structural view swap).
309
+ */
310
+ clearStateProvider(provider, previous) {
311
+ if (this.stateProvider() === provider) {
312
+ this.stateProvider.set(previous);
313
+ }
314
+ }
219
315
  /**
220
316
  * Prefer the registered provider's value for `key` when it exposes one,
221
- * otherwise fall back to the root inputs / DOM-derived signals.
317
+ * otherwise fall back to the root inputs / DOM-derived signals. `errors` (content) and `valid`
318
+ * (tri-state `boolean | null`) are resolved separately, so they're excluded from this boolean key.
222
319
  */
223
320
  resolve(key, fallback) {
224
321
  const accessor = this.stateProvider()?.[key];
225
322
  return accessor ? accessor() : fallback();
226
323
  }
227
324
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
228
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldRoot, isStandalone: true, selector: "[rdxFieldRoot]", inputs: { invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, filled: { classPropertyName: "filled", publicName: "filled", isSignal: true, isRequired: false, transformFunction: null }, focused: { classPropertyName: "focused", publicName: "focused", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "input": "notifyEdited()", "change": "notifyEdited()" }, properties: { "attr.data-invalid": "dataAttr(invalidState())", "attr.data-valid": "dataAttr(!invalidState())", "attr.data-disabled": "dataAttr(disabledState())", "attr.data-required": "dataAttr(requiredState())", "attr.data-dirty": "dataAttr(dirtyState())", "attr.data-touched": "dataAttr(touchedState())", "attr.data-filled": "dataAttr(filledState())", "attr.data-focused": "dataAttr(focusedState())" } }, providers: [provideFieldRootContext(fieldRootContext)], exportAs: ["rdxFieldRoot"], ngImport: i0 }); }
325
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldRoot, isStandalone: true, selector: "[rdxFieldRoot]", inputs: { invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, filled: { classPropertyName: "filled", publicName: "filled", isSignal: true, isRequired: false, transformFunction: null }, focused: { classPropertyName: "focused", publicName: "focused", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, validationMode: { classPropertyName: "validationMode", publicName: "validationMode", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "input": "notifyEdited()", "change": "notifyEdited()" }, properties: { "attr.data-invalid": "dataAttr(validState() === false)", "attr.data-valid": "dataAttr(validState() === true)", "attr.data-disabled": "dataAttr(disabledState())", "attr.data-required": "dataAttr(requiredState())", "attr.data-dirty": "dataAttr(dirtyState())", "attr.data-touched": "dataAttr(touchedState())", "attr.data-filled": "dataAttr(filledState())", "attr.data-focused": "dataAttr(focusedState())" } }, providers: [
326
+ provideFieldRootContext(fieldRootContext),
327
+ // Expose the tri-state display validity so controls inside the field reflect it (single source).
328
+ { provide: RDX_FIELD_VALIDITY, useFactory: () => inject(RdxFieldRoot).validState }
329
+ ], exportAs: ["rdxFieldRoot"], ngImport: i0 }); }
229
330
  }
230
331
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldRoot, decorators: [{
231
332
  type: Directive,
232
333
  args: [{
233
334
  selector: '[rdxFieldRoot]',
234
335
  exportAs: 'rdxFieldRoot',
235
- providers: [provideFieldRootContext(fieldRootContext)],
336
+ providers: [
337
+ provideFieldRootContext(fieldRootContext),
338
+ // Expose the tri-state display validity so controls inside the field reflect it (single source).
339
+ { provide: RDX_FIELD_VALIDITY, useFactory: () => inject(RdxFieldRoot).validState }
340
+ ],
236
341
  host: {
237
- '[attr.data-invalid]': 'dataAttr(invalidState())',
238
- '[attr.data-valid]': 'dataAttr(!invalidState())',
342
+ // Tri-state: `data-invalid` when displayed-invalid, `data-valid` only when displayed-valid,
343
+ // and **neither** when neutral (`validState()` is `null`).
344
+ '[attr.data-invalid]': 'dataAttr(validState() === false)',
345
+ '[attr.data-valid]': 'dataAttr(validState() === true)',
239
346
  '[attr.data-disabled]': 'dataAttr(disabledState())',
240
347
  '[attr.data-required]': 'dataAttr(requiredState())',
241
348
  '[attr.data-dirty]': 'dataAttr(dirtyState())',
@@ -246,13 +353,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
246
353
  '(change)': 'notifyEdited()'
247
354
  }
248
355
  }]
249
- }], ctorParameters: () => [], propDecorators: { invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], filled: [{ type: i0.Input, args: [{ isSignal: true, alias: "filled", required: false }] }], focused: [{ type: i0.Input, args: [{ isSignal: true, alias: "focused", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }] } });
356
+ }], ctorParameters: () => [], propDecorators: { invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], filled: [{ type: i0.Input, args: [{ isSignal: true, alias: "filled", required: false }] }], focused: [{ type: i0.Input, args: [{ isSignal: true, alias: "focused", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], validationMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMode", required: false }] }] } });
250
357
  function injectFieldRoot() {
251
358
  return inject(RdxFieldRoot);
252
359
  }
253
360
 
254
- let controlId = 0;
255
- const attr$3 = (value) => (value ? '' : undefined);
361
+ const attr$4 = (value) => (value ? '' : undefined);
256
362
  /**
257
363
  * Connects a form control to the field label, description, error, and state.
258
364
  *
@@ -268,7 +374,7 @@ class RdxFieldControl {
268
374
  *
269
375
  * @group Props
270
376
  */
271
- this.id = input(`rdx-field-control-${controlId++}`, /* @ts-ignore */
377
+ this.id = input(injectId('rdx-field-control-'), /* @ts-ignore */
272
378
  ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
273
379
  this.describedBy = computed(() => {
274
380
  const ids = [
@@ -278,7 +384,7 @@ class RdxFieldControl {
278
384
  return ids.length ? ids.join(' ') : undefined;
279
385
  }, /* @ts-ignore */
280
386
  ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
281
- this.dataAttr = attr$3;
387
+ this.dataAttr = attr$4;
282
388
  effect(() => {
283
389
  this.rootContext.setControlId(this.id());
284
390
  });
@@ -307,7 +413,7 @@ class RdxFieldControl {
307
413
  return element.value ?? '';
308
414
  }
309
415
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldControl, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
310
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldControl, isStandalone: true, selector: "[rdxFieldControl]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focus": "onFocus()", "blur": "onBlur()", "input": "syncFilled()", "change": "syncFilled()" }, properties: { "attr.id": "id()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "rootContext.invalidState() ? \"true\" : undefined", "attr.aria-required": "rootContext.requiredState() ? \"true\" : undefined", "attr.aria-disabled": "rootContext.disabledState() ? \"true\" : undefined", "attr.disabled": "isNativeFormControl() && rootContext.disabledState() ? \"\" : undefined", "attr.required": "isNativeFormControl() && rootContext.requiredState() ? \"\" : undefined", "attr.data-invalid": "dataAttr(rootContext.invalidState())", "attr.data-valid": "dataAttr(!rootContext.invalidState())", "attr.data-disabled": "dataAttr(rootContext.disabledState())", "attr.data-required": "dataAttr(rootContext.requiredState())", "attr.data-filled": "dataAttr(rootContext.filledState())", "attr.data-focused": "dataAttr(rootContext.focusedState())" } }, exportAs: ["rdxFieldControl"], ngImport: i0 }); }
416
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldControl, isStandalone: true, selector: "[rdxFieldControl]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focus": "onFocus()", "blur": "onBlur()", "input": "syncFilled()", "change": "syncFilled()" }, properties: { "attr.id": "id()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "rootContext.validState() === false ? \"true\" : undefined", "attr.aria-required": "!isNativeFormControl() && rootContext.requiredState() ? \"true\" : undefined", "attr.aria-disabled": "!isNativeFormControl() && rootContext.disabledState() ? \"true\" : undefined", "attr.disabled": "isNativeFormControl() && rootContext.disabledState() ? \"\" : undefined", "attr.required": "isNativeFormControl() && rootContext.requiredState() ? \"\" : undefined", "attr.data-invalid": "dataAttr(rootContext.validState() === false)", "attr.data-valid": "dataAttr(rootContext.validState() === true)", "attr.data-disabled": "dataAttr(rootContext.disabledState())", "attr.data-required": "dataAttr(rootContext.requiredState())", "attr.data-dirty": "dataAttr(rootContext.dirtyState())", "attr.data-touched": "dataAttr(rootContext.touchedState())", "attr.data-filled": "dataAttr(rootContext.filledState())", "attr.data-focused": "dataAttr(rootContext.focusedState())" } }, exportAs: ["rdxFieldControl"], ngImport: i0 }); }
311
417
  }
312
418
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldControl, decorators: [{
313
419
  type: Directive,
@@ -317,15 +423,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
317
423
  host: {
318
424
  '[attr.id]': 'id()',
319
425
  '[attr.aria-describedby]': 'describedBy()',
320
- '[attr.aria-invalid]': 'rootContext.invalidState() ? "true" : undefined',
321
- '[attr.aria-required]': 'rootContext.requiredState() ? "true" : undefined',
322
- '[attr.aria-disabled]': 'rootContext.disabledState() ? "true" : undefined',
426
+ // `aria-invalid`/`data-*` follow the field's tri-state `validState`: while it is neutral (`null`,
427
+ // e.g. a field whose `validationMode` defers display (e.g. `onBlur`) before interaction) the control emits no `aria-invalid`
428
+ // and neither `data-valid` nor `data-invalid`, matching the field root.
429
+ '[attr.aria-invalid]': 'rootContext.validState() === false ? "true" : undefined',
430
+ // On a native control the native `required`/`disabled` attributes already convey the state, so
431
+ // `aria-required`/`aria-disabled` are only emitted on non-native (custom) controls.
432
+ '[attr.aria-required]': '!isNativeFormControl() && rootContext.requiredState() ? "true" : undefined',
433
+ '[attr.aria-disabled]': '!isNativeFormControl() && rootContext.disabledState() ? "true" : undefined',
323
434
  '[attr.disabled]': 'isNativeFormControl() && rootContext.disabledState() ? "" : undefined',
324
435
  '[attr.required]': 'isNativeFormControl() && rootContext.requiredState() ? "" : undefined',
325
- '[attr.data-invalid]': 'dataAttr(rootContext.invalidState())',
326
- '[attr.data-valid]': 'dataAttr(!rootContext.invalidState())',
436
+ '[attr.data-invalid]': 'dataAttr(rootContext.validState() === false)',
437
+ '[attr.data-valid]': 'dataAttr(rootContext.validState() === true)',
327
438
  '[attr.data-disabled]': 'dataAttr(rootContext.disabledState())',
328
439
  '[attr.data-required]': 'dataAttr(rootContext.requiredState())',
440
+ '[attr.data-dirty]': 'dataAttr(rootContext.dirtyState())',
441
+ '[attr.data-touched]': 'dataAttr(rootContext.touchedState())',
329
442
  '[attr.data-filled]': 'dataAttr(rootContext.filledState())',
330
443
  '[attr.data-focused]': 'dataAttr(rootContext.focusedState())',
331
444
  '(focus)': 'onFocus()',
@@ -336,8 +449,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
336
449
  }]
337
450
  }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
338
451
 
339
- let descriptionId = 0;
340
- const attr$2 = (value) => (value ? '' : undefined);
452
+ const attr$3 = (value) => (value ? '' : undefined);
341
453
  /**
342
454
  * Describes the field control.
343
455
  *
@@ -351,9 +463,9 @@ class RdxFieldDescription {
351
463
  *
352
464
  * @group Props
353
465
  */
354
- this.id = input(`rdx-field-description-${descriptionId++}`, /* @ts-ignore */
466
+ this.id = input(injectId('rdx-field-description-'), /* @ts-ignore */
355
467
  ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
356
- this.dataAttr = attr$2;
468
+ this.dataAttr = attr$3;
357
469
  effect((onCleanup) => {
358
470
  const id = this.id();
359
471
  this.rootContext.addDescriptionId(id);
@@ -376,8 +488,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
376
488
  }]
377
489
  }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
378
490
 
379
- let errorId = 0;
380
- const attr$1 = (value) => (value ? '' : undefined);
491
+ const attr$2 = (value) => (value ? '' : undefined);
381
492
  /**
382
493
  * Describes an invalid field control.
383
494
  *
@@ -391,15 +502,16 @@ class RdxFieldError {
391
502
  *
392
503
  * @group Props
393
504
  */
394
- this.id = input(`rdx-field-error-${errorId++}`, /* @ts-ignore */
505
+ this.id = input(injectId('rdx-field-error-'), /* @ts-ignore */
395
506
  ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
396
507
  /**
397
- * The field's external messages (state provider's, then enclosing Form's), `[]` when none. Render
398
- * them explicitly via the `exportAs` reference the directive never injects text content itself:
508
+ * The field's validation messages — client (provider / form name-routing, once `validationMode`
509
+ * reveals them) then server (the Form's `errors` input, always); `[]` when none. Render them
510
+ * explicitly via the `exportAs` reference — the directive never injects text content itself:
399
511
  * `<p rdxFieldError #err="rdxFieldError">{{ err.messages().join(' ') }}</p>`.
400
512
  */
401
513
  this.messages = this.rootContext.messages;
402
- this.dataAttr = attr$1;
514
+ this.dataAttr = attr$2;
403
515
  effect((onCleanup) => {
404
516
  const id = this.id();
405
517
  this.rootContext.addErrorId(id);
@@ -424,7 +536,113 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
424
536
  }]
425
537
  }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
426
538
 
427
- let labelId = 0;
539
+ const attr$1 = (value) => (value ? '' : undefined);
540
+ const addId = (ids, id) => (ids.includes(id) ? ids : [...ids, id]);
541
+ const removeId = (ids, id) => ids.filter((item) => item !== id);
542
+ /**
543
+ * The field item context **re-provides** {@link RdxFieldRootContext} so a label, description, and control
544
+ * placed inside the item associate with the **item's** control (own `controlId` / `descriptionIds`),
545
+ * while all validation state (`invalidState` / `dirtyState` / `touchedState` / `filledState` /
546
+ * `focusedState` / `requiredState`, errors and messages) is **delegated to the enclosing `rdxFieldRoot`**.
547
+ * `disabledState` is the item's `disabled` OR'd with the root's. The enclosing root is reached with
548
+ * `skipSelf` (the item provides the same token), so there is no circular injection.
549
+ */
550
+ const fieldItemContext = () => {
551
+ const item = inject(RdxFieldItem);
552
+ const root = item.root;
553
+ return {
554
+ controlId: item.controlId,
555
+ name: root.name,
556
+ descriptionIds: item.descriptionIds,
557
+ errorIds: root.errorIds,
558
+ messages: root.messages,
559
+ notifyEdited: () => root.notifyEdited(),
560
+ validState: root.validState,
561
+ formSubmitAttempted: root.formSubmitAttempted,
562
+ invalidState: root.invalidState,
563
+ disabledState: item.disabledState,
564
+ requiredState: root.requiredState,
565
+ dirtyState: root.dirtyState,
566
+ touchedState: root.touchedState,
567
+ filledState: root.filledState,
568
+ focusedState: root.focusedState,
569
+ setControlId: (id) => item.controlId.set(id),
570
+ addDescriptionId: (id) => item.descriptionIds.update((ids) => addId(ids, id)),
571
+ removeDescriptionId: (id) => item.descriptionIds.update((ids) => removeId(ids, id)),
572
+ addErrorId: (id) => root.errorIds.update((ids) => addId(ids, id)),
573
+ removeErrorId: (id) => root.errorIds.update((ids) => removeId(ids, id)),
574
+ setFocused: (value) => root.focusedValue.set(value),
575
+ setFilled: (value) => root.filledValue.set(value),
576
+ setDirty: (value) => root.dirtyValue.set(value),
577
+ setTouched: (value) => root.touchedValue.set(value),
578
+ setStateProvider: (provider) => root.setStateProvider(provider),
579
+ clearStateProvider: (provider, previous) => root.clearStateProvider(provider, previous),
580
+ hasStateProvider: root.hasStateProvider
581
+ };
582
+ };
583
+ /**
584
+ * Groups an individual item in a checkbox group or radio group with its own label and description.
585
+ *
586
+ * Use inside a `rdxFieldRoot` (typically wrapping a group) when each control needs its own label /
587
+ * description: the item scopes those associations to its control while reflecting the field's validation
588
+ * state. Mirrors Base UI's `Field.Item`.
589
+ *
590
+ * @group Components
591
+ */
592
+ class RdxFieldItem {
593
+ constructor() {
594
+ /** The enclosing field root (items are leaves under the root, so `skipSelf` reaches it). */
595
+ this.root = inject(RdxFieldRoot, { skipSelf: true });
596
+ /**
597
+ * Whether the wrapped control should ignore user interaction. The `disabled` on `rdxFieldRoot` takes
598
+ * precedence (it is OR'd in).
599
+ *
600
+ * @group Props
601
+ * @defaultValue false
602
+ */
603
+ this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
604
+ /** This item's control id — label / description / control inside the item associate with it. */
605
+ this.controlId = signal(injectId('rdx-field-item-control-'), /* @ts-ignore */
606
+ ...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
607
+ /** Description ids for this item's control `aria-describedby`. */
608
+ this.descriptionIds = signal([], /* @ts-ignore */
609
+ ...(ngDevMode ? [{ debugName: "descriptionIds" }] : /* istanbul ignore next */ []));
610
+ /** The item's effective disabled state: the root's disabled OR the item's. */
611
+ this.disabledState = computed(() => this.root.disabledState() || this.disabled(), /* @ts-ignore */
612
+ ...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
613
+ this.dataAttr = attr$1;
614
+ }
615
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
616
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldItem, isStandalone: true, selector: "[rdxFieldItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-invalid": "dataAttr(root.validState() === false)", "attr.data-valid": "dataAttr(root.validState() === true)", "attr.data-disabled": "dataAttr(disabledState())", "attr.data-required": "dataAttr(root.requiredState())", "attr.data-dirty": "dataAttr(root.dirtyState())", "attr.data-touched": "dataAttr(root.touchedState())", "attr.data-filled": "dataAttr(root.filledState())", "attr.data-focused": "dataAttr(root.focusedState())" } }, providers: [
617
+ provideFieldRootContext(fieldItemContext),
618
+ // Items delegate validity to the root; expose the root's tri-state to controls inside the item.
619
+ { provide: RDX_FIELD_VALIDITY, useFactory: () => inject(RdxFieldItem).root.validState }
620
+ ], exportAs: ["rdxFieldItem"], ngImport: i0 }); }
621
+ }
622
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldItem, decorators: [{
623
+ type: Directive,
624
+ args: [{
625
+ selector: '[rdxFieldItem]',
626
+ exportAs: 'rdxFieldItem',
627
+ providers: [
628
+ provideFieldRootContext(fieldItemContext),
629
+ // Items delegate validity to the root; expose the root's tri-state to controls inside the item.
630
+ { provide: RDX_FIELD_VALIDITY, useFactory: () => inject(RdxFieldItem).root.validState }
631
+ ],
632
+ host: {
633
+ // Tri-state, mirroring the root: neither attribute while the root's `validState` is neutral.
634
+ '[attr.data-invalid]': 'dataAttr(root.validState() === false)',
635
+ '[attr.data-valid]': 'dataAttr(root.validState() === true)',
636
+ '[attr.data-disabled]': 'dataAttr(disabledState())',
637
+ '[attr.data-required]': 'dataAttr(root.requiredState())',
638
+ '[attr.data-dirty]': 'dataAttr(root.dirtyState())',
639
+ '[attr.data-touched]': 'dataAttr(root.touchedState())',
640
+ '[attr.data-filled]': 'dataAttr(root.filledState())',
641
+ '[attr.data-focused]': 'dataAttr(root.focusedState())'
642
+ }
643
+ }]
644
+ }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
645
+
428
646
  const attr = (value) => (value ? '' : undefined);
429
647
  /**
430
648
  * Labels the field control.
@@ -439,7 +657,7 @@ class RdxFieldLabel {
439
657
  *
440
658
  * @group Props
441
659
  */
442
- this.id = input(`rdx-field-label-${labelId++}`, /* @ts-ignore */
660
+ this.id = input(injectId('rdx-field-label-'), /* @ts-ignore */
443
661
  ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
444
662
  this.htmlFor = () => this.rootContext.controlId();
445
663
  this.dataAttr = attr;
@@ -467,5 +685,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
467
685
  * Generated bundle index. Do not edit.
468
686
  */
469
687
 
470
- export { RdxFieldControl, RdxFieldDescription, RdxFieldError, RdxFieldLabel, RdxFieldRoot, injectFieldRootContext, provideFieldRootContext };
688
+ export { RdxFieldControl, RdxFieldDescription, RdxFieldError, RdxFieldItem, RdxFieldLabel, RdxFieldRoot, injectFieldRootContext, provideFieldRootContext };
471
689
  //# sourceMappingURL=radix-ng-primitives-field.mjs.map