@ng-forge/dynamic-forms-bootstrap 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { AsyncPipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { inject, input, computed, ChangeDetectionStrategy, Component, ElementRef, viewChild, effect, Directive, InjectionToken, afterRenderEffect, linkedSignal, model, isSignal } from '@angular/core';
4
- import { EventBus, ARRAY_CONTEXT, resolveTokens, DynamicTextPipe, buildBaseInputs, FIELD_SIGNAL_CONTEXT, resolveSubmitButtonDisabled, resolveNextButtonDisabled, NextPageEvent, PreviousPageEvent, DynamicFormLogger, AddArrayItemEvent, RemoveArrayItemEvent } from '@ng-forge/dynamic-forms';
4
+ import { EventBus, ARRAY_CONTEXT, resolveTokens, DynamicTextPipe, DEFAULT_PROPS, buildBaseInputs, RootFormRegistryService, FORM_OPTIONS, resolveSubmitButtonDisabled, FIELD_SIGNAL_CONTEXT, resolveNextButtonDisabled, NextPageEvent, PreviousPageEvent, DynamicFormLogger, AddArrayItemEvent, RemoveArrayItemEvent } from '@ng-forge/dynamic-forms';
5
5
  import { FormField } from '@angular/forms/signals';
6
6
  import { createResolvedErrorsSignal, shouldShowErrors, setupMetaTracking, isEqual, valueFieldMapper, optionsFieldMapper, checkboxFieldMapper, datepickerFieldMapper } from '@ng-forge/dynamic-forms/integration';
7
7
  import { explicitEffect } from 'ngxtension/explicit-effect';
@@ -78,10 +78,11 @@ class BsButtonFieldComponent {
78
78
  this.eventBus.dispatch(event);
79
79
  }
80
80
  }
81
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsButtonFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
82
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.8", type: BsButtonFieldComponent, isStandalone: true, selector: "df-bs-button", inputs: { key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, event: { classPropertyName: "event", publicName: "event", isSignal: true, isRequired: false, transformFunction: null }, eventArgs: { classPropertyName: "eventArgs", publicName: "eventArgs", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, eventContext: { classPropertyName: "eventContext", publicName: "eventContext", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "hidden": "hidden()" } }, ngImport: i0, template: `
81
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsButtonFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
82
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.0", type: BsButtonFieldComponent, isStandalone: true, selector: "df-bs-button", inputs: { key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, event: { classPropertyName: "event", publicName: "event", isSignal: true, isRequired: false, transformFunction: null }, eventArgs: { classPropertyName: "eventArgs", publicName: "eventArgs", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, eventContext: { classPropertyName: "eventContext", publicName: "eventContext", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "hidden() || null" } }, ngImport: i0, template: `
83
+ @let buttonId = key() + '-button';
83
84
  <button
84
- [id]="key()"
85
+ [id]="buttonId"
85
86
  [type]="buttonType()"
86
87
  [disabled]="disabled()"
87
88
  [class]="buttonClasses()"
@@ -93,16 +94,17 @@ class BsButtonFieldComponent {
93
94
  </button>
94
95
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
95
96
  }
96
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsButtonFieldComponent, decorators: [{
97
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsButtonFieldComponent, decorators: [{
97
98
  type: Component,
98
99
  args: [{ selector: 'df-bs-button', imports: [DynamicTextPipe, AsyncPipe], host: {
99
100
  '[id]': '`${key()}`',
100
101
  '[attr.data-testid]': 'key()',
101
102
  '[class]': 'className()',
102
- '[hidden]': 'hidden()',
103
+ '[attr.hidden]': 'hidden() || null',
103
104
  }, template: `
105
+ @let buttonId = key() + '-button';
104
106
  <button
105
- [id]="key()"
107
+ [id]="buttonId"
106
108
  [type]="buttonType()"
107
109
  [disabled]="disabled()"
108
110
  [class]="buttonClasses()"
@@ -122,6 +124,29 @@ var bsButton_component = /*#__PURE__*/Object.freeze({
122
124
 
123
125
  // Public API - component
124
126
 
127
+ /**
128
+ * Creates a signal that computes the aria-describedby value based on errors and hint state.
129
+ * Errors take precedence over hints - when errors are displayed, the hint is hidden.
130
+ *
131
+ * @param errorsToDisplay Signal containing the array of errors currently being displayed
132
+ * @param errorId Signal containing the base ID for error elements
133
+ * @param hintId Signal containing the ID for the hint element
134
+ * @param hasHint Function that returns true if a hint is configured
135
+ * @returns Signal containing the aria-describedby value (space-separated IDs) or null
136
+ */
137
+ function createAriaDescribedBySignal(errorsToDisplay, errorId, hintId, hasHint) {
138
+ return computed(() => {
139
+ const errors = errorsToDisplay();
140
+ if (errors.length > 0) {
141
+ return errors.map((_, i) => `${errorId()}-${i}`).join(' ');
142
+ }
143
+ if (hasHint()) {
144
+ return hintId();
145
+ }
146
+ return null;
147
+ });
148
+ }
149
+
125
150
  class BsCheckboxFieldComponent {
126
151
  elementRef = inject((ElementRef));
127
152
  field = input.required(...(ngDevMode ? [{ debugName: "field" }] : []));
@@ -152,7 +177,7 @@ class BsCheckboxFieldComponent {
152
177
  // ─────────────────────────────────────────────────────────────────────────────
153
178
  // Accessibility
154
179
  // ─────────────────────────────────────────────────────────────────────────────
155
- helpTextId = computed(() => `${this.key()}-help`, ...(ngDevMode ? [{ debugName: "helpTextId" }] : []));
180
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
156
181
  errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : []));
157
182
  ariaInvalid = computed(() => {
158
183
  const fieldState = this.field()();
@@ -161,22 +186,10 @@ class BsCheckboxFieldComponent {
161
186
  ariaRequired = computed(() => {
162
187
  return this.field()().required?.() === true ? true : null;
163
188
  }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
164
- ariaDescribedBy = computed(() => {
165
- const ids = [];
166
- if (this.props()?.helpText) {
167
- ids.push(this.helpTextId());
168
- }
169
- const errors = this.errorsToDisplay();
170
- errors.forEach((_, i) => {
171
- ids.push(`${this.errorId()}-${i}`);
172
- });
173
- return ids.length > 0 ? ids.join(' ') : null;
174
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
175
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
176
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsCheckboxFieldComponent, isStandalone: true, selector: "df-bs-checkbox", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, viewQueries: [{ propertyName: "checkboxInput", first: true, predicate: ["checkboxInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
177
- @let f = field();
178
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
179
- @let ariaDescribedBy = this.ariaDescribedBy();
189
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
190
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
191
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsCheckboxFieldComponent, isStandalone: true, selector: "df-bs-checkbox", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, viewQueries: [{ propertyName: "checkboxInput", first: true, predicate: ["checkboxInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
192
+ @let f = field(); @let checkboxId = key() + '-checkbox';
180
193
 
181
194
  <div
182
195
  class="form-check"
@@ -189,35 +202,32 @@ class BsCheckboxFieldComponent {
189
202
  #checkboxInput
190
203
  type="checkbox"
191
204
  [formField]="f"
192
- [id]="key()"
205
+ [id]="checkboxId"
193
206
  class="form-check-input"
194
207
  [class.is-invalid]="f().invalid() && f().touched()"
195
208
  [attr.tabindex]="tabIndex()"
196
- [attr.aria-invalid]="ariaInvalid"
197
- [attr.aria-required]="ariaRequired"
198
- [attr.aria-describedby]="ariaDescribedBy"
209
+ [attr.aria-invalid]="ariaInvalid()"
210
+ [attr.aria-required]="ariaRequired()"
211
+ [attr.aria-describedby]="ariaDescribedBy()"
199
212
  />
200
- <label [for]="key()" class="form-check-label">
213
+ <label [for]="checkboxId" class="form-check-label">
201
214
  {{ label() | dynamicText | async }}
202
215
  </label>
203
216
  </div>
204
217
 
205
- @if (props()?.helpText; as helpText) {
206
- <div class="form-text" [id]="helpTextId()" [attr.hidden]="f().hidden() || null">
207
- {{ helpText | dynamicText | async }}
208
- </div>
209
- }
210
218
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
211
219
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
220
+ } @empty {
221
+ @if (props()?.hint; as hint) {
222
+ <div class="form-text" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
223
+ }
212
224
  }
213
225
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
214
226
  }
215
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsCheckboxFieldComponent, decorators: [{
227
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsCheckboxFieldComponent, decorators: [{
216
228
  type: Component,
217
229
  args: [{ selector: 'df-bs-checkbox', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
218
- @let f = field();
219
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
220
- @let ariaDescribedBy = this.ariaDescribedBy();
230
+ @let f = field(); @let checkboxId = key() + '-checkbox';
221
231
 
222
232
  <div
223
233
  class="form-check"
@@ -230,26 +240,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
230
240
  #checkboxInput
231
241
  type="checkbox"
232
242
  [formField]="f"
233
- [id]="key()"
243
+ [id]="checkboxId"
234
244
  class="form-check-input"
235
245
  [class.is-invalid]="f().invalid() && f().touched()"
236
246
  [attr.tabindex]="tabIndex()"
237
- [attr.aria-invalid]="ariaInvalid"
238
- [attr.aria-required]="ariaRequired"
239
- [attr.aria-describedby]="ariaDescribedBy"
247
+ [attr.aria-invalid]="ariaInvalid()"
248
+ [attr.aria-required]="ariaRequired()"
249
+ [attr.aria-describedby]="ariaDescribedBy()"
240
250
  />
241
- <label [for]="key()" class="form-check-label">
251
+ <label [for]="checkboxId" class="form-check-label">
242
252
  {{ label() | dynamicText | async }}
243
253
  </label>
244
254
  </div>
245
255
 
246
- @if (props()?.helpText; as helpText) {
247
- <div class="form-text" [id]="helpTextId()" [attr.hidden]="f().hidden() || null">
248
- {{ helpText | dynamicText | async }}
249
- </div>
250
- }
251
256
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
252
257
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
258
+ } @empty {
259
+ @if (props()?.hint; as hint) {
260
+ <div class="form-text" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
261
+ }
253
262
  }
254
263
  `, host: {
255
264
  '[class]': 'className()',
@@ -299,10 +308,10 @@ class InputConstraintsDirective {
299
308
  nativeElement.removeAttribute('step');
300
309
  }
301
310
  });
302
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: InputConstraintsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
303
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.8", type: InputConstraintsDirective, isStandalone: true, selector: "[dfBsInputConstraints]", inputs: { dfMin: { classPropertyName: "dfMin", publicName: "dfMin", isSignal: true, isRequired: false, transformFunction: null }, dfMax: { classPropertyName: "dfMax", publicName: "dfMax", isSignal: true, isRequired: false, transformFunction: null }, dfStep: { classPropertyName: "dfStep", publicName: "dfStep", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
311
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: InputConstraintsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
312
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.0", type: InputConstraintsDirective, isStandalone: true, selector: "[dfBsInputConstraints]", inputs: { dfMin: { classPropertyName: "dfMin", publicName: "dfMin", isSignal: true, isRequired: false, transformFunction: null }, dfMax: { classPropertyName: "dfMax", publicName: "dfMax", isSignal: true, isRequired: false, transformFunction: null }, dfStep: { classPropertyName: "dfStep", publicName: "dfStep", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
304
313
  }
305
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: InputConstraintsDirective, decorators: [{
314
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: InputConstraintsDirective, decorators: [{
306
315
  type: Directive,
307
316
  args: [{
308
317
  selector: '[dfBsInputConstraints]',
@@ -342,7 +351,7 @@ class BsDatepickerFieldComponent {
342
351
  // ─────────────────────────────────────────────────────────────────────────────
343
352
  // Accessibility
344
353
  // ─────────────────────────────────────────────────────────────────────────────
345
- helpTextId = computed(() => `${this.key()}-help`, ...(ngDevMode ? [{ debugName: "helpTextId" }] : []));
354
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
346
355
  errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : []));
347
356
  ariaInvalid = computed(() => {
348
357
  const fieldState = this.field()();
@@ -351,36 +360,25 @@ class BsDatepickerFieldComponent {
351
360
  ariaRequired = computed(() => {
352
361
  return this.field()().required?.() === true ? true : null;
353
362
  }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
354
- ariaDescribedBy = computed(() => {
355
- const ids = [];
356
- if (this.props()?.helpText) {
357
- ids.push(this.helpTextId());
358
- }
359
- const errors = this.errorsToDisplay();
360
- errors.forEach((_, i) => {
361
- ids.push(`${this.errorId()}-${i}`);
362
- });
363
- return ids.length > 0 ? ids.join(' ') : null;
364
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
365
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsDatepickerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
366
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsDatepickerFieldComponent, isStandalone: true, selector: "df-bs-datepicker", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
367
- @let f = field(); @let p = props(); @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
368
- @let ariaDescribedBy = this.ariaDescribedBy();
363
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
364
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsDatepickerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
365
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsDatepickerFieldComponent, isStandalone: true, selector: "df-bs-datepicker", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
366
+ @let f = field(); @let p = props(); @let inputId = key() + '-input';
369
367
  @if (p?.floatingLabel) {
370
368
  <!-- Floating label variant -->
371
369
  <div class="form-floating mb-3">
372
370
  <input
373
371
  dfBsInputConstraints
374
372
  [formField]="f"
375
- [id]="key()"
373
+ [id]="inputId"
376
374
  type="date"
377
375
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
378
376
  [dfMin]="minAsString()"
379
377
  [dfMax]="maxAsString()"
380
378
  [attr.tabindex]="tabIndex()"
381
- [attr.aria-invalid]="ariaInvalid"
382
- [attr.aria-required]="ariaRequired"
383
- [attr.aria-describedby]="ariaDescribedBy"
379
+ [attr.aria-invalid]="ariaInvalid()"
380
+ [attr.aria-required]="ariaRequired()"
381
+ [attr.aria-describedby]="ariaDescribedBy()"
384
382
  class="form-control"
385
383
  [class.form-control-sm]="p?.size === 'sm'"
386
384
  [class.form-control-lg]="p?.size === 'lg'"
@@ -388,7 +386,7 @@ class BsDatepickerFieldComponent {
388
386
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
389
387
  />
390
388
  @if (label()) {
391
- <label [for]="key()">{{ label() | dynamicText | async }}</label>
389
+ <label [for]="inputId">{{ label() | dynamicText | async }}</label>
392
390
  }
393
391
  @if (p?.validFeedback && f().valid() && f().touched()) {
394
392
  <div class="valid-feedback d-block">
@@ -403,21 +401,21 @@ class BsDatepickerFieldComponent {
403
401
  <!-- Standard variant -->
404
402
  <div class="mb-3">
405
403
  @if (label()) {
406
- <label [for]="key()" class="form-label">{{ label() | dynamicText | async }}</label>
404
+ <label [for]="inputId" class="form-label">{{ label() | dynamicText | async }}</label>
407
405
  }
408
406
 
409
407
  <input
410
408
  dfBsInputConstraints
411
409
  [formField]="f"
412
- [id]="key()"
410
+ [id]="inputId"
413
411
  type="date"
414
412
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
415
413
  [dfMin]="minAsString()"
416
414
  [dfMax]="maxAsString()"
417
415
  [attr.tabindex]="tabIndex()"
418
- [attr.aria-invalid]="ariaInvalid"
419
- [attr.aria-required]="ariaRequired"
420
- [attr.aria-describedby]="ariaDescribedBy"
416
+ [attr.aria-invalid]="ariaInvalid()"
417
+ [attr.aria-required]="ariaRequired()"
418
+ [attr.aria-describedby]="ariaDescribedBy()"
421
419
  class="form-control"
422
420
  [class.form-control-sm]="p?.size === 'sm'"
423
421
  [class.form-control-lg]="p?.size === 'lg'"
@@ -425,11 +423,6 @@ class BsDatepickerFieldComponent {
425
423
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
426
424
  />
427
425
 
428
- @if (p?.helpText) {
429
- <div class="form-text" [id]="helpTextId()">
430
- {{ p?.helpText | dynamicText | async }}
431
- </div>
432
- }
433
426
  @if (p?.validFeedback && f().valid() && f().touched()) {
434
427
  <div class="valid-feedback d-block">
435
428
  {{ p?.validFeedback | dynamicText | async }}
@@ -437,31 +430,34 @@ class BsDatepickerFieldComponent {
437
430
  }
438
431
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
439
432
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
433
+ } @empty {
434
+ @if (p?.hint) {
435
+ <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
436
+ }
440
437
  }
441
438
  </div>
442
439
  }
443
440
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "directive", type: InputConstraintsDirective, selector: "[dfBsInputConstraints]", inputs: ["dfMin", "dfMax", "dfStep"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
444
441
  }
445
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsDatepickerFieldComponent, decorators: [{
442
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsDatepickerFieldComponent, decorators: [{
446
443
  type: Component,
447
444
  args: [{ selector: 'df-bs-datepicker', imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective], template: `
448
- @let f = field(); @let p = props(); @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
449
- @let ariaDescribedBy = this.ariaDescribedBy();
445
+ @let f = field(); @let p = props(); @let inputId = key() + '-input';
450
446
  @if (p?.floatingLabel) {
451
447
  <!-- Floating label variant -->
452
448
  <div class="form-floating mb-3">
453
449
  <input
454
450
  dfBsInputConstraints
455
451
  [formField]="f"
456
- [id]="key()"
452
+ [id]="inputId"
457
453
  type="date"
458
454
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
459
455
  [dfMin]="minAsString()"
460
456
  [dfMax]="maxAsString()"
461
457
  [attr.tabindex]="tabIndex()"
462
- [attr.aria-invalid]="ariaInvalid"
463
- [attr.aria-required]="ariaRequired"
464
- [attr.aria-describedby]="ariaDescribedBy"
458
+ [attr.aria-invalid]="ariaInvalid()"
459
+ [attr.aria-required]="ariaRequired()"
460
+ [attr.aria-describedby]="ariaDescribedBy()"
465
461
  class="form-control"
466
462
  [class.form-control-sm]="p?.size === 'sm'"
467
463
  [class.form-control-lg]="p?.size === 'lg'"
@@ -469,7 +465,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
469
465
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
470
466
  />
471
467
  @if (label()) {
472
- <label [for]="key()">{{ label() | dynamicText | async }}</label>
468
+ <label [for]="inputId">{{ label() | dynamicText | async }}</label>
473
469
  }
474
470
  @if (p?.validFeedback && f().valid() && f().touched()) {
475
471
  <div class="valid-feedback d-block">
@@ -484,21 +480,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
484
480
  <!-- Standard variant -->
485
481
  <div class="mb-3">
486
482
  @if (label()) {
487
- <label [for]="key()" class="form-label">{{ label() | dynamicText | async }}</label>
483
+ <label [for]="inputId" class="form-label">{{ label() | dynamicText | async }}</label>
488
484
  }
489
485
 
490
486
  <input
491
487
  dfBsInputConstraints
492
488
  [formField]="f"
493
- [id]="key()"
489
+ [id]="inputId"
494
490
  type="date"
495
491
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
496
492
  [dfMin]="minAsString()"
497
493
  [dfMax]="maxAsString()"
498
494
  [attr.tabindex]="tabIndex()"
499
- [attr.aria-invalid]="ariaInvalid"
500
- [attr.aria-required]="ariaRequired"
501
- [attr.aria-describedby]="ariaDescribedBy"
495
+ [attr.aria-invalid]="ariaInvalid()"
496
+ [attr.aria-required]="ariaRequired()"
497
+ [attr.aria-describedby]="ariaDescribedBy()"
502
498
  class="form-control"
503
499
  [class.form-control-sm]="p?.size === 'sm'"
504
500
  [class.form-control-lg]="p?.size === 'lg'"
@@ -506,11 +502,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
506
502
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
507
503
  />
508
504
 
509
- @if (p?.helpText) {
510
- <div class="form-text" [id]="helpTextId()">
511
- {{ p?.helpText | dynamicText | async }}
512
- </div>
513
- }
514
505
  @if (p?.validFeedback && f().valid() && f().touched()) {
515
506
  <div class="valid-feedback d-block">
516
507
  {{ p?.validFeedback | dynamicText | async }}
@@ -518,6 +509,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
518
509
  }
519
510
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
520
511
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
512
+ } @empty {
513
+ @if (p?.hint) {
514
+ <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
515
+ }
521
516
  }
522
517
  </div>
523
518
  }
@@ -613,8 +608,8 @@ class BsInputFieldComponent {
613
608
  // ─────────────────────────────────────────────────────────────────────────────
614
609
  // Accessibility
615
610
  // ─────────────────────────────────────────────────────────────────────────────
616
- /** Unique ID for the help text element, used for aria-describedby */
617
- helpTextId = computed(() => `${this.key()}-help`, ...(ngDevMode ? [{ debugName: "helpTextId" }] : []));
611
+ /** Unique ID for the hint element, used for aria-describedby */
612
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
618
613
  /** Base ID for error elements, used for aria-describedby */
619
614
  errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : []));
620
615
  /** aria-invalid: true when field is invalid AND touched, false otherwise */
@@ -626,46 +621,33 @@ class BsInputFieldComponent {
626
621
  ariaRequired = computed(() => {
627
622
  return this.field()().required?.() === true ? true : null;
628
623
  }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
629
- /** aria-describedby: links to help text and error messages for screen readers */
630
- ariaDescribedBy = computed(() => {
631
- const ids = [];
632
- if (this.props()?.helpText) {
633
- ids.push(this.helpTextId());
634
- }
635
- const errors = this.errorsToDisplay();
636
- errors.forEach((_, i) => {
637
- ids.push(`${this.errorId()}-${i}`);
638
- });
639
- return ids.length > 0 ? ids.join(' ') : null;
640
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
641
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsInputFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
642
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsInputFieldComponent, isStandalone: true, selector: "df-bs-input", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
643
- @let f = field(); @let p = props(); @let effectiveSize = this.effectiveSize();
644
- @let effectiveFloatingLabel = this.effectiveFloatingLabel();
645
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
646
- @let ariaDescribedBy = this.ariaDescribedBy();
647
- @if (effectiveFloatingLabel) {
624
+ /** aria-describedby: links to hint and error messages for screen readers */
625
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
626
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsInputFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
627
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsInputFieldComponent, isStandalone: true, selector: "df-bs-input", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
628
+ @let f = field(); @let p = props(); @let inputId = key() + '-input';
629
+ @if (effectiveFloatingLabel()) {
648
630
  <!-- Floating label variant -->
649
631
  <div class="form-floating mb-3">
650
632
  <input
651
633
  #inputRef
652
634
  [formField]="f"
653
- [id]="key()"
635
+ [id]="inputId"
654
636
  [type]="p?.type ?? 'text'"
655
637
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
656
638
  [attr.tabindex]="tabIndex()"
657
- [attr.aria-invalid]="ariaInvalid"
658
- [attr.aria-required]="ariaRequired"
659
- [attr.aria-describedby]="ariaDescribedBy"
639
+ [attr.aria-invalid]="ariaInvalid()"
640
+ [attr.aria-required]="ariaRequired()"
641
+ [attr.aria-describedby]="ariaDescribedBy()"
660
642
  class="form-control"
661
- [class.form-control-sm]="effectiveSize === 'sm'"
662
- [class.form-control-lg]="effectiveSize === 'lg'"
643
+ [class.form-control-sm]="effectiveSize() === 'sm'"
644
+ [class.form-control-lg]="effectiveSize() === 'lg'"
663
645
  [class.form-control-plaintext]="p?.plaintext"
664
646
  [class.is-invalid]="f().invalid() && f().touched()"
665
647
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
666
648
  />
667
649
  @if (label()) {
668
- <label [for]="key()">{{ label() | dynamicText | async }}</label>
650
+ <label [for]="inputId">{{ label() | dynamicText | async }}</label>
669
651
  }
670
652
  @if (p?.validFeedback && f().valid() && f().touched()) {
671
653
  <div class="valid-feedback d-block">
@@ -680,30 +662,25 @@ class BsInputFieldComponent {
680
662
  <!-- Standard variant -->
681
663
  <div class="mb-3">
682
664
  @if (label()) {
683
- <label [for]="key()" class="form-label">{{ label() | dynamicText | async }}</label>
665
+ <label [for]="inputId" class="form-label">{{ label() | dynamicText | async }}</label>
684
666
  }
685
667
  <input
686
668
  #inputRef
687
669
  [formField]="f"
688
- [id]="key()"
670
+ [id]="inputId"
689
671
  [type]="p?.type ?? 'text'"
690
672
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
691
673
  [attr.tabindex]="tabIndex()"
692
- [attr.aria-invalid]="ariaInvalid"
693
- [attr.aria-required]="ariaRequired"
694
- [attr.aria-describedby]="ariaDescribedBy"
674
+ [attr.aria-invalid]="ariaInvalid()"
675
+ [attr.aria-required]="ariaRequired()"
676
+ [attr.aria-describedby]="ariaDescribedBy()"
695
677
  class="form-control"
696
- [class.form-control-sm]="effectiveSize === 'sm'"
697
- [class.form-control-lg]="effectiveSize === 'lg'"
678
+ [class.form-control-sm]="effectiveSize() === 'sm'"
679
+ [class.form-control-lg]="effectiveSize() === 'lg'"
698
680
  [class.form-control-plaintext]="p?.plaintext"
699
681
  [class.is-invalid]="f().invalid() && f().touched()"
700
682
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
701
683
  />
702
- @if (p?.helpText) {
703
- <div class="form-text" [id]="helpTextId()">
704
- {{ p?.helpText | dynamicText | async }}
705
- </div>
706
- }
707
684
  @if (p?.validFeedback && f().valid() && f().touched()) {
708
685
  <div class="valid-feedback d-block">
709
686
  {{ p?.validFeedback | dynamicText | async }}
@@ -711,40 +688,41 @@ class BsInputFieldComponent {
711
688
  }
712
689
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
713
690
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
691
+ } @empty {
692
+ @if (p?.hint) {
693
+ <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
694
+ }
714
695
  }
715
696
  </div>
716
697
  }
717
698
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
718
699
  }
719
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsInputFieldComponent, decorators: [{
700
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsInputFieldComponent, decorators: [{
720
701
  type: Component,
721
702
  args: [{ selector: 'df-bs-input', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
722
- @let f = field(); @let p = props(); @let effectiveSize = this.effectiveSize();
723
- @let effectiveFloatingLabel = this.effectiveFloatingLabel();
724
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
725
- @let ariaDescribedBy = this.ariaDescribedBy();
726
- @if (effectiveFloatingLabel) {
703
+ @let f = field(); @let p = props(); @let inputId = key() + '-input';
704
+ @if (effectiveFloatingLabel()) {
727
705
  <!-- Floating label variant -->
728
706
  <div class="form-floating mb-3">
729
707
  <input
730
708
  #inputRef
731
709
  [formField]="f"
732
- [id]="key()"
710
+ [id]="inputId"
733
711
  [type]="p?.type ?? 'text'"
734
712
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
735
713
  [attr.tabindex]="tabIndex()"
736
- [attr.aria-invalid]="ariaInvalid"
737
- [attr.aria-required]="ariaRequired"
738
- [attr.aria-describedby]="ariaDescribedBy"
714
+ [attr.aria-invalid]="ariaInvalid()"
715
+ [attr.aria-required]="ariaRequired()"
716
+ [attr.aria-describedby]="ariaDescribedBy()"
739
717
  class="form-control"
740
- [class.form-control-sm]="effectiveSize === 'sm'"
741
- [class.form-control-lg]="effectiveSize === 'lg'"
718
+ [class.form-control-sm]="effectiveSize() === 'sm'"
719
+ [class.form-control-lg]="effectiveSize() === 'lg'"
742
720
  [class.form-control-plaintext]="p?.plaintext"
743
721
  [class.is-invalid]="f().invalid() && f().touched()"
744
722
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
745
723
  />
746
724
  @if (label()) {
747
- <label [for]="key()">{{ label() | dynamicText | async }}</label>
725
+ <label [for]="inputId">{{ label() | dynamicText | async }}</label>
748
726
  }
749
727
  @if (p?.validFeedback && f().valid() && f().touched()) {
750
728
  <div class="valid-feedback d-block">
@@ -759,30 +737,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
759
737
  <!-- Standard variant -->
760
738
  <div class="mb-3">
761
739
  @if (label()) {
762
- <label [for]="key()" class="form-label">{{ label() | dynamicText | async }}</label>
740
+ <label [for]="inputId" class="form-label">{{ label() | dynamicText | async }}</label>
763
741
  }
764
742
  <input
765
743
  #inputRef
766
744
  [formField]="f"
767
- [id]="key()"
745
+ [id]="inputId"
768
746
  [type]="p?.type ?? 'text'"
769
747
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
770
748
  [attr.tabindex]="tabIndex()"
771
- [attr.aria-invalid]="ariaInvalid"
772
- [attr.aria-required]="ariaRequired"
773
- [attr.aria-describedby]="ariaDescribedBy"
749
+ [attr.aria-invalid]="ariaInvalid()"
750
+ [attr.aria-required]="ariaRequired()"
751
+ [attr.aria-describedby]="ariaDescribedBy()"
774
752
  class="form-control"
775
- [class.form-control-sm]="effectiveSize === 'sm'"
776
- [class.form-control-lg]="effectiveSize === 'lg'"
753
+ [class.form-control-sm]="effectiveSize() === 'sm'"
754
+ [class.form-control-lg]="effectiveSize() === 'lg'"
777
755
  [class.form-control-plaintext]="p?.plaintext"
778
756
  [class.is-invalid]="f().invalid() && f().touched()"
779
757
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
780
758
  />
781
- @if (p?.helpText) {
782
- <div class="form-text" [id]="helpTextId()">
783
- {{ p?.helpText | dynamicText | async }}
784
- </div>
785
- }
786
759
  @if (p?.validFeedback && f().valid() && f().touched()) {
787
760
  <div class="valid-feedback d-block">
788
761
  {{ p?.validFeedback | dynamicText | async }}
@@ -790,6 +763,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
790
763
  }
791
764
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
792
765
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
766
+ } @empty {
767
+ @if (p?.hint) {
768
+ <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
769
+ }
793
770
  }
794
771
  </div>
795
772
  }
@@ -822,6 +799,14 @@ class BsMultiCheckboxFieldComponent {
822
799
  resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
823
800
  showErrors = shouldShowErrors(this.field);
824
801
  errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : []));
802
+ /** Computed map of checked option values for O(1) lookup in template */
803
+ checkedValuesMap = computed(() => {
804
+ const map = {};
805
+ for (const opt of this.valueViewModel()) {
806
+ map[String(opt.value)] = true;
807
+ }
808
+ return map;
809
+ }, ...(ngDevMode ? [{ debugName: "checkedValuesMap" }] : []));
825
810
  valueViewModel = linkedSignal(() => {
826
811
  const currentValues = this.field()().value();
827
812
  return this.options().filter((option) => currentValues.includes(option.value));
@@ -843,23 +828,23 @@ class BsMultiCheckboxFieldComponent {
843
828
  }
844
829
  });
845
830
  }
846
- onCheckboxChange(option, checked) {
831
+ onCheckboxChange(option, event) {
832
+ const checked = event.target.checked;
847
833
  this.valueViewModel.update((currentOptions) => {
848
834
  if (checked) {
849
- return currentOptions.some((opt) => opt.value === option.value) ? currentOptions : [...currentOptions, option];
835
+ return currentOptions.some((opt) => opt.value === option.value)
836
+ ? currentOptions
837
+ : [...currentOptions, option];
850
838
  }
851
839
  else {
852
840
  return currentOptions.filter((opt) => opt.value !== option.value);
853
841
  }
854
842
  });
855
843
  }
856
- isChecked(option) {
857
- return this.valueViewModel().some((opt) => opt.value === option.value);
858
- }
859
844
  // ─────────────────────────────────────────────────────────────────────────────
860
845
  // Accessibility
861
846
  // ─────────────────────────────────────────────────────────────────────────────
862
- helpTextId = computed(() => `${this.key()}-help`, ...(ngDevMode ? [{ debugName: "helpTextId" }] : []));
847
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
863
848
  errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : []));
864
849
  ariaInvalid = computed(() => {
865
850
  const fieldState = this.field()();
@@ -868,22 +853,11 @@ class BsMultiCheckboxFieldComponent {
868
853
  ariaRequired = computed(() => {
869
854
  return this.field()().required?.() === true ? true : null;
870
855
  }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
871
- ariaDescribedBy = computed(() => {
872
- const ids = [];
873
- if (this.props()?.helpText) {
874
- ids.push(this.helpTextId());
875
- }
876
- const errors = this.errorsToDisplay();
877
- errors.forEach((_, i) => {
878
- ids.push(`${this.errorId()}-${i}`);
879
- });
880
- return ids.length > 0 ? ids.join(' ') : null;
881
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
882
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsMultiCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
883
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsMultiCheckboxFieldComponent, isStandalone: true, selector: "df-bs-multi-checkbox", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className() || \"\"", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
856
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
857
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsMultiCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
858
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsMultiCheckboxFieldComponent, isStandalone: true, selector: "df-bs-multi-checkbox", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className() || \"\"", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
884
859
  @let f = field();
885
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
886
- @let ariaDescribedBy = this.ariaDescribedBy();
860
+ @let checked = checkedValuesMap();
887
861
  @if (label(); as label) {
888
862
  <div class="form-label">{{ label | dynamicText | async }}</div>
889
863
  }
@@ -899,15 +873,15 @@ class BsMultiCheckboxFieldComponent {
899
873
  <input
900
874
  type="checkbox"
901
875
  [id]="key() + '_' + i"
902
- [checked]="isChecked(option)"
876
+ [checked]="checked['' + option.value]"
903
877
  [disabled]="f().disabled() || option.disabled"
904
- (change)="onCheckboxChange(option, $any($event.target).checked)"
878
+ (change)="onCheckboxChange(option, $event)"
905
879
  class="form-check-input"
906
880
  [class.is-invalid]="f().invalid() && f().touched()"
907
881
  [attr.tabindex]="tabIndex()"
908
- [attr.aria-invalid]="ariaInvalid"
909
- [attr.aria-required]="ariaRequired"
910
- [attr.aria-describedby]="ariaDescribedBy"
882
+ [attr.aria-invalid]="ariaInvalid()"
883
+ [attr.aria-required]="ariaRequired()"
884
+ [attr.aria-describedby]="ariaDescribedBy()"
911
885
  />
912
886
  <label [for]="key() + '_' + i" class="form-check-label">
913
887
  {{ option.label | dynamicText | async }}
@@ -916,22 +890,20 @@ class BsMultiCheckboxFieldComponent {
916
890
  }
917
891
  </div>
918
892
 
919
- @if (props()?.helpText; as helpText) {
920
- <div class="form-text" [id]="helpTextId()">
921
- {{ helpText | dynamicText | async }}
922
- </div>
923
- }
924
893
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
925
894
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
895
+ } @empty {
896
+ @if (props()?.hint; as hint) {
897
+ <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
898
+ }
926
899
  }
927
900
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n", ":host{display:block}.checkbox-group{margin-bottom:.5rem}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
928
901
  }
929
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsMultiCheckboxFieldComponent, decorators: [{
902
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsMultiCheckboxFieldComponent, decorators: [{
930
903
  type: Component,
931
904
  args: [{ selector: 'df-bs-multi-checkbox', imports: [DynamicTextPipe, AsyncPipe], template: `
932
905
  @let f = field();
933
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
934
- @let ariaDescribedBy = this.ariaDescribedBy();
906
+ @let checked = checkedValuesMap();
935
907
  @if (label(); as label) {
936
908
  <div class="form-label">{{ label | dynamicText | async }}</div>
937
909
  }
@@ -947,15 +919,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
947
919
  <input
948
920
  type="checkbox"
949
921
  [id]="key() + '_' + i"
950
- [checked]="isChecked(option)"
922
+ [checked]="checked['' + option.value]"
951
923
  [disabled]="f().disabled() || option.disabled"
952
- (change)="onCheckboxChange(option, $any($event.target).checked)"
924
+ (change)="onCheckboxChange(option, $event)"
953
925
  class="form-check-input"
954
926
  [class.is-invalid]="f().invalid() && f().touched()"
955
927
  [attr.tabindex]="tabIndex()"
956
- [attr.aria-invalid]="ariaInvalid"
957
- [attr.aria-required]="ariaRequired"
958
- [attr.aria-describedby]="ariaDescribedBy"
928
+ [attr.aria-invalid]="ariaInvalid()"
929
+ [attr.aria-required]="ariaRequired()"
930
+ [attr.aria-describedby]="ariaDescribedBy()"
959
931
  />
960
932
  <label [for]="key() + '_' + i" class="form-check-label">
961
933
  {{ option.label | dynamicText | async }}
@@ -964,13 +936,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
964
936
  }
965
937
  </div>
966
938
 
967
- @if (props()?.helpText; as helpText) {
968
- <div class="form-text" [id]="helpTextId()">
969
- {{ helpText | dynamicText | async }}
970
- </div>
971
- }
972
939
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
973
940
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
941
+ } @empty {
942
+ @if (props()?.hint; as hint) {
943
+ <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
944
+ }
974
945
  }
975
946
  `, host: {
976
947
  '[class]': 'className() || ""',
@@ -1011,10 +982,9 @@ class BsRadioGroupComponent {
1011
982
  this.value.set(newValue);
1012
983
  }
1013
984
  }
1014
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsRadioGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1015
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsRadioGroupComponent, isStandalone: true, selector: "df-bs-radio-group", inputs: { value: { classPropertyName: "value", publicName: "value", 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 }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
985
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsRadioGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
986
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsRadioGroupComponent, isStandalone: true, selector: "df-bs-radio-group", inputs: { value: { classPropertyName: "value", publicName: "value", 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 }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
1016
987
  @let props = properties();
1017
- @let ariaDescribedBy = this.ariaDescribedBy();
1018
988
  @if (props?.buttonGroup) {
1019
989
  <div class="btn-group" role="group" [attr.aria-label]="label() | dynamicText | async">
1020
990
  @for (option of options(); track option.value; let i = $index) {
@@ -1025,7 +995,7 @@ class BsRadioGroupComponent {
1025
995
  [checked]="value() === option.value"
1026
996
  (change)="onRadioChange(option.value)"
1027
997
  [disabled]="disabled() || option.disabled || false"
1028
- [attr.aria-describedby]="ariaDescribedBy"
998
+ [attr.aria-describedby]="ariaDescribedBy()"
1029
999
  class="btn-check"
1030
1000
  [id]="name() + '_' + i"
1031
1001
  autocomplete="off"
@@ -1050,7 +1020,7 @@ class BsRadioGroupComponent {
1050
1020
  [checked]="value() === option.value"
1051
1021
  (change)="onRadioChange(option.value)"
1052
1022
  [disabled]="disabled() || option.disabled || false"
1053
- [attr.aria-describedby]="ariaDescribedBy"
1023
+ [attr.aria-describedby]="ariaDescribedBy()"
1054
1024
  class="form-check-input"
1055
1025
  [id]="name() + '_' + i"
1056
1026
  />
@@ -1062,11 +1032,10 @@ class BsRadioGroupComponent {
1062
1032
  }
1063
1033
  `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1064
1034
  }
1065
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsRadioGroupComponent, decorators: [{
1035
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsRadioGroupComponent, decorators: [{
1066
1036
  type: Component,
1067
1037
  args: [{ selector: 'df-bs-radio-group', imports: [DynamicTextPipe, AsyncPipe], template: `
1068
1038
  @let props = properties();
1069
- @let ariaDescribedBy = this.ariaDescribedBy();
1070
1039
  @if (props?.buttonGroup) {
1071
1040
  <div class="btn-group" role="group" [attr.aria-label]="label() | dynamicText | async">
1072
1041
  @for (option of options(); track option.value; let i = $index) {
@@ -1077,7 +1046,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1077
1046
  [checked]="value() === option.value"
1078
1047
  (change)="onRadioChange(option.value)"
1079
1048
  [disabled]="disabled() || option.disabled || false"
1080
- [attr.aria-describedby]="ariaDescribedBy"
1049
+ [attr.aria-describedby]="ariaDescribedBy()"
1081
1050
  class="btn-check"
1082
1051
  [id]="name() + '_' + i"
1083
1052
  autocomplete="off"
@@ -1102,7 +1071,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1102
1071
  [checked]="value() === option.value"
1103
1072
  (change)="onRadioChange(option.value)"
1104
1073
  [disabled]="disabled() || option.disabled || false"
1105
- [attr.aria-describedby]="ariaDescribedBy"
1074
+ [attr.aria-describedby]="ariaDescribedBy()"
1106
1075
  class="form-check-input"
1107
1076
  [id]="name() + '_' + i"
1108
1077
  />
@@ -1140,7 +1109,7 @@ class BsRadioFieldComponent {
1140
1109
  // ─────────────────────────────────────────────────────────────────────────────
1141
1110
  // Accessibility
1142
1111
  // ─────────────────────────────────────────────────────────────────────────────
1143
- helpTextId = computed(() => `${this.key()}-help`, ...(ngDevMode ? [{ debugName: "helpTextId" }] : []));
1112
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
1144
1113
  errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : []));
1145
1114
  ariaInvalid = computed(() => {
1146
1115
  const fieldState = this.field()();
@@ -1149,21 +1118,10 @@ class BsRadioFieldComponent {
1149
1118
  ariaRequired = computed(() => {
1150
1119
  return this.field()().required?.() === true ? true : null;
1151
1120
  }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
1152
- ariaDescribedBy = computed(() => {
1153
- const ids = [];
1154
- if (this.props()?.helpText) {
1155
- ids.push(this.helpTextId());
1156
- }
1157
- const errors = this.errorsToDisplay();
1158
- errors.forEach((_, i) => {
1159
- ids.push(`${this.errorId()}-${i}`);
1160
- });
1161
- return ids.length > 0 ? ids.join(' ') : null;
1162
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
1163
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsRadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1164
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsRadioFieldComponent, isStandalone: true, selector: "df-bs-radio", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1121
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1122
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsRadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1123
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsRadioFieldComponent, isStandalone: true, selector: "df-bs-radio", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1165
1124
  @let f = field();
1166
- @let ariaDescribedBy = this.ariaDescribedBy();
1167
1125
 
1168
1126
  <div class="mb-3">
1169
1127
  @if (label(); as label) {
@@ -1171,27 +1129,27 @@ class BsRadioFieldComponent {
1171
1129
  }
1172
1130
 
1173
1131
  <df-bs-radio-group
1174
- [formField]="$any(f)"
1132
+ [formField]="f"
1175
1133
  [label]="label()"
1176
1134
  [options]="options()"
1177
1135
  [properties]="props()"
1178
- [ariaDescribedBy]="ariaDescribedBy"
1136
+ [ariaDescribedBy]="ariaDescribedBy()"
1179
1137
  />
1180
1138
 
1181
- @if (props()?.helpText; as helpText) {
1182
- <div class="form-text" [id]="helpTextId()">{{ helpText | dynamicText | async }}</div>
1183
- }
1184
1139
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1185
1140
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1141
+ } @empty {
1142
+ @if (props()?.hint; as hint) {
1143
+ <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1144
+ }
1186
1145
  }
1187
1146
  </div>
1188
1147
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: BsRadioGroupComponent, selector: "df-bs-radio-group", inputs: ["value", "disabled", "readonly", "name", "label", "options", "properties", "meta", "ariaDescribedBy"], outputs: ["valueChange"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1189
1148
  }
1190
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsRadioFieldComponent, decorators: [{
1149
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsRadioFieldComponent, decorators: [{
1191
1150
  type: Component,
1192
1151
  args: [{ selector: 'df-bs-radio', imports: [BsRadioGroupComponent, FormField, DynamicTextPipe, AsyncPipe], template: `
1193
1152
  @let f = field();
1194
- @let ariaDescribedBy = this.ariaDescribedBy();
1195
1153
 
1196
1154
  <div class="mb-3">
1197
1155
  @if (label(); as label) {
@@ -1199,18 +1157,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1199
1157
  }
1200
1158
 
1201
1159
  <df-bs-radio-group
1202
- [formField]="$any(f)"
1160
+ [formField]="f"
1203
1161
  [label]="label()"
1204
1162
  [options]="options()"
1205
1163
  [properties]="props()"
1206
- [ariaDescribedBy]="ariaDescribedBy"
1164
+ [ariaDescribedBy]="ariaDescribedBy()"
1207
1165
  />
1208
1166
 
1209
- @if (props()?.helpText; as helpText) {
1210
- <div class="form-text" [id]="helpTextId()">{{ helpText | dynamicText | async }}</div>
1211
- }
1212
1167
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1213
1168
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1169
+ } @empty {
1170
+ @if (props()?.hint; as hint) {
1171
+ <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1172
+ }
1214
1173
  }
1215
1174
  </div>
1216
1175
  `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
@@ -1256,7 +1215,7 @@ class BsSelectFieldComponent {
1256
1215
  // ─────────────────────────────────────────────────────────────────────────────
1257
1216
  // Accessibility
1258
1217
  // ─────────────────────────────────────────────────────────────────────────────
1259
- helpTextId = computed(() => `${this.key()}-help`, ...(ngDevMode ? [{ debugName: "helpTextId" }] : []));
1218
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
1260
1219
  errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : []));
1261
1220
  ariaInvalid = computed(() => {
1262
1221
  const fieldState = this.field()();
@@ -1265,39 +1224,27 @@ class BsSelectFieldComponent {
1265
1224
  ariaRequired = computed(() => {
1266
1225
  return this.field()().required?.() === true ? true : null;
1267
1226
  }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
1268
- ariaDescribedBy = computed(() => {
1269
- const ids = [];
1270
- if (this.props()?.helpText) {
1271
- ids.push(this.helpTextId());
1272
- }
1273
- const errors = this.errorsToDisplay();
1274
- errors.forEach((_, i) => {
1275
- ids.push(`${this.errorId()}-${i}`);
1276
- });
1277
- return ids.length > 0 ? ids.join(' ') : null;
1278
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
1279
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1280
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsSelectFieldComponent, isStandalone: true, selector: "df-bs-select", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1281
- @let f = field();
1282
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1283
- @let ariaDescribedBy = this.ariaDescribedBy();
1227
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1228
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1229
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsSelectFieldComponent, isStandalone: true, selector: "df-bs-select", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1230
+ @let f = field(); @let selectId = key() + '-select';
1284
1231
 
1285
1232
  <div class="mb-3">
1286
1233
  @if (label(); as label) {
1287
- <label [for]="key()" class="form-label">{{ label | dynamicText | async }}</label>
1234
+ <label [for]="selectId" class="form-label">{{ label | dynamicText | async }}</label>
1288
1235
  }
1289
1236
  <select
1290
1237
  [formField]="f"
1291
- [id]="key()"
1238
+ [id]="selectId"
1292
1239
  class="form-select"
1293
1240
  [class.form-select-sm]="props()?.size === 'sm'"
1294
1241
  [class.form-select-lg]="props()?.size === 'lg'"
1295
1242
  [class.is-invalid]="f().invalid() && f().touched()"
1296
1243
  [multiple]="props()?.multiple || false"
1297
1244
  [size]="props()?.htmlSize"
1298
- [attr.aria-invalid]="ariaInvalid"
1299
- [attr.aria-required]="ariaRequired"
1300
- [attr.aria-describedby]="ariaDescribedBy"
1245
+ [attr.aria-invalid]="ariaInvalid()"
1246
+ [attr.aria-required]="ariaRequired()"
1247
+ [attr.aria-describedby]="ariaDescribedBy()"
1301
1248
  >
1302
1249
  @if (placeholder(); as placeholder) {
1303
1250
  <option value="" disabled [selected]="!f().value()">{{ placeholder | dynamicText | async }}</option>
@@ -1309,38 +1256,37 @@ class BsSelectFieldComponent {
1309
1256
  }
1310
1257
  </select>
1311
1258
 
1312
- @if (props()?.helpText; as helpText) {
1313
- <div class="form-text" [id]="helpTextId()">{{ helpText | dynamicText | async }}</div>
1314
- }
1315
1259
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1316
1260
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1261
+ } @empty {
1262
+ @if (props()?.hint; as hint) {
1263
+ <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1264
+ }
1317
1265
  }
1318
1266
  </div>
1319
1267
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1320
1268
  }
1321
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsSelectFieldComponent, decorators: [{
1269
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsSelectFieldComponent, decorators: [{
1322
1270
  type: Component,
1323
1271
  args: [{ selector: 'df-bs-select', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
1324
- @let f = field();
1325
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1326
- @let ariaDescribedBy = this.ariaDescribedBy();
1272
+ @let f = field(); @let selectId = key() + '-select';
1327
1273
 
1328
1274
  <div class="mb-3">
1329
1275
  @if (label(); as label) {
1330
- <label [for]="key()" class="form-label">{{ label | dynamicText | async }}</label>
1276
+ <label [for]="selectId" class="form-label">{{ label | dynamicText | async }}</label>
1331
1277
  }
1332
1278
  <select
1333
1279
  [formField]="f"
1334
- [id]="key()"
1280
+ [id]="selectId"
1335
1281
  class="form-select"
1336
1282
  [class.form-select-sm]="props()?.size === 'sm'"
1337
1283
  [class.form-select-lg]="props()?.size === 'lg'"
1338
1284
  [class.is-invalid]="f().invalid() && f().touched()"
1339
1285
  [multiple]="props()?.multiple || false"
1340
1286
  [size]="props()?.htmlSize"
1341
- [attr.aria-invalid]="ariaInvalid"
1342
- [attr.aria-required]="ariaRequired"
1343
- [attr.aria-describedby]="ariaDescribedBy"
1287
+ [attr.aria-invalid]="ariaInvalid()"
1288
+ [attr.aria-required]="ariaRequired()"
1289
+ [attr.aria-describedby]="ariaDescribedBy()"
1344
1290
  >
1345
1291
  @if (placeholder(); as placeholder) {
1346
1292
  <option value="" disabled [selected]="!f().value()">{{ placeholder | dynamicText | async }}</option>
@@ -1352,11 +1298,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1352
1298
  }
1353
1299
  </select>
1354
1300
 
1355
- @if (props()?.helpText; as helpText) {
1356
- <div class="form-text" [id]="helpTextId()">{{ helpText | dynamicText | async }}</div>
1357
- }
1358
1301
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1359
1302
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1303
+ } @empty {
1304
+ @if (props()?.hint; as hint) {
1305
+ <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1306
+ }
1360
1307
  }
1361
1308
  </div>
1362
1309
  `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
@@ -1396,7 +1343,7 @@ class BsSliderFieldComponent {
1396
1343
  // ─────────────────────────────────────────────────────────────────────────────
1397
1344
  // Accessibility
1398
1345
  // ─────────────────────────────────────────────────────────────────────────────
1399
- helpTextId = computed(() => `${this.key()}-help`, ...(ngDevMode ? [{ debugName: "helpTextId" }] : []));
1346
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
1400
1347
  errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : []));
1401
1348
  ariaInvalid = computed(() => {
1402
1349
  const fieldState = this.field()();
@@ -1405,26 +1352,14 @@ class BsSliderFieldComponent {
1405
1352
  ariaRequired = computed(() => {
1406
1353
  return this.field()().required?.() === true ? true : null;
1407
1354
  }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
1408
- ariaDescribedBy = computed(() => {
1409
- const ids = [];
1410
- if (this.props()?.helpText) {
1411
- ids.push(this.helpTextId());
1412
- }
1413
- const errors = this.errorsToDisplay();
1414
- errors.forEach((_, i) => {
1415
- ids.push(`${this.errorId()}-${i}`);
1416
- });
1417
- return ids.length > 0 ? ids.join(' ') : null;
1418
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
1419
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsSliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1420
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsSliderFieldComponent, isStandalone: true, selector: "df-bs-slider", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1421
- @let f = field();
1422
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1423
- @let ariaDescribedBy = this.ariaDescribedBy();
1355
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1356
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsSliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1357
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsSliderFieldComponent, isStandalone: true, selector: "df-bs-slider", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1358
+ @let f = field(); @let inputId = key() + '-input';
1424
1359
 
1425
1360
  <div class="mb-3">
1426
1361
  @if (label(); as label) {
1427
- <label [for]="key()" class="form-label">
1362
+ <label [for]="inputId" class="form-label">
1428
1363
  {{ label | dynamicText | async }}
1429
1364
  @if (props()?.showValue) {
1430
1365
  <span class="ms-2 badge bg-secondary"> {{ props()?.valuePrefix }}{{ f().value() }}{{ props()?.valueSuffix }} </span>
@@ -1436,38 +1371,35 @@ class BsSliderFieldComponent {
1436
1371
  type="range"
1437
1372
  dfBsInputConstraints
1438
1373
  [formField]="f"
1439
- [id]="key()"
1374
+ [id]="inputId"
1440
1375
  [dfMin]="props()?.min ?? min()"
1441
1376
  [dfMax]="props()?.max ?? max()"
1442
1377
  [dfStep]="props()?.step ?? step()"
1443
1378
  [attr.tabindex]="tabIndex()"
1444
- [attr.aria-invalid]="ariaInvalid"
1445
- [attr.aria-required]="ariaRequired"
1446
- [attr.aria-describedby]="ariaDescribedBy"
1379
+ [attr.aria-invalid]="ariaInvalid()"
1380
+ [attr.aria-required]="ariaRequired()"
1381
+ [attr.aria-describedby]="ariaDescribedBy()"
1447
1382
  class="form-range"
1448
1383
  />
1449
1384
 
1450
- @if (props()?.helpText; as helpText) {
1451
- <div class="form-text" [id]="helpTextId()">
1452
- {{ helpText | dynamicText | async }}
1453
- </div>
1454
- }
1455
1385
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1456
1386
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1387
+ } @empty {
1388
+ @if (props()?.hint; as hint) {
1389
+ <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1390
+ }
1457
1391
  }
1458
1392
  </div>
1459
1393
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "directive", type: InputConstraintsDirective, selector: "[dfBsInputConstraints]", inputs: ["dfMin", "dfMax", "dfStep"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1460
1394
  }
1461
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsSliderFieldComponent, decorators: [{
1395
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsSliderFieldComponent, decorators: [{
1462
1396
  type: Component,
1463
1397
  args: [{ selector: 'df-bs-slider', imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective], template: `
1464
- @let f = field();
1465
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1466
- @let ariaDescribedBy = this.ariaDescribedBy();
1398
+ @let f = field(); @let inputId = key() + '-input';
1467
1399
 
1468
1400
  <div class="mb-3">
1469
1401
  @if (label(); as label) {
1470
- <label [for]="key()" class="form-label">
1402
+ <label [for]="inputId" class="form-label">
1471
1403
  {{ label | dynamicText | async }}
1472
1404
  @if (props()?.showValue) {
1473
1405
  <span class="ms-2 badge bg-secondary"> {{ props()?.valuePrefix }}{{ f().value() }}{{ props()?.valueSuffix }} </span>
@@ -1479,24 +1411,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1479
1411
  type="range"
1480
1412
  dfBsInputConstraints
1481
1413
  [formField]="f"
1482
- [id]="key()"
1414
+ [id]="inputId"
1483
1415
  [dfMin]="props()?.min ?? min()"
1484
1416
  [dfMax]="props()?.max ?? max()"
1485
1417
  [dfStep]="props()?.step ?? step()"
1486
1418
  [attr.tabindex]="tabIndex()"
1487
- [attr.aria-invalid]="ariaInvalid"
1488
- [attr.aria-required]="ariaRequired"
1489
- [attr.aria-describedby]="ariaDescribedBy"
1419
+ [attr.aria-invalid]="ariaInvalid()"
1420
+ [attr.aria-required]="ariaRequired()"
1421
+ [attr.aria-describedby]="ariaDescribedBy()"
1490
1422
  class="form-range"
1491
1423
  />
1492
1424
 
1493
- @if (props()?.helpText; as helpText) {
1494
- <div class="form-text" [id]="helpTextId()">
1495
- {{ helpText | dynamicText | async }}
1496
- </div>
1497
- }
1498
1425
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1499
1426
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1427
+ } @empty {
1428
+ @if (props()?.hint; as hint) {
1429
+ <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1430
+ }
1500
1431
  }
1501
1432
  </div>
1502
1433
  `, host: {
@@ -1533,7 +1464,7 @@ class BsTextareaFieldComponent {
1533
1464
  // ─────────────────────────────────────────────────────────────────────────────
1534
1465
  // Accessibility
1535
1466
  // ─────────────────────────────────────────────────────────────────────────────
1536
- helpTextId = computed(() => `${this.key()}-help`, ...(ngDevMode ? [{ debugName: "helpTextId" }] : []));
1467
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
1537
1468
  errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : []));
1538
1469
  ariaInvalid = computed(() => {
1539
1470
  const fieldState = this.field()();
@@ -1542,32 +1473,21 @@ class BsTextareaFieldComponent {
1542
1473
  ariaRequired = computed(() => {
1543
1474
  return this.field()().required?.() === true ? true : null;
1544
1475
  }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
1545
- ariaDescribedBy = computed(() => {
1546
- const ids = [];
1547
- if (this.props()?.helpText) {
1548
- ids.push(this.helpTextId());
1549
- }
1550
- const errors = this.errorsToDisplay();
1551
- errors.forEach((_, i) => {
1552
- ids.push(`${this.errorId()}-${i}`);
1553
- });
1554
- return ids.length > 0 ? ids.join(' ') : null;
1555
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
1556
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsTextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1557
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsTextareaFieldComponent, isStandalone: true, selector: "df-bs-textarea", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1558
- @let f = field(); @let p = props(); @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1559
- @let ariaDescribedBy = this.ariaDescribedBy();
1476
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1477
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsTextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1478
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsTextareaFieldComponent, isStandalone: true, selector: "df-bs-textarea", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1479
+ @let f = field(); @let p = props(); @let textareaId = key() + '-textarea';
1560
1480
  @if (p?.floatingLabel) {
1561
1481
  <!-- Floating label variant -->
1562
1482
  <div class="form-floating mb-3">
1563
1483
  <textarea
1564
1484
  [formField]="f"
1565
- [id]="key()"
1485
+ [id]="textareaId"
1566
1486
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1567
1487
  [attr.tabindex]="tabIndex()"
1568
- [attr.aria-invalid]="ariaInvalid"
1569
- [attr.aria-required]="ariaRequired"
1570
- [attr.aria-describedby]="ariaDescribedBy"
1488
+ [attr.aria-invalid]="ariaInvalid()"
1489
+ [attr.aria-required]="ariaRequired()"
1490
+ [attr.aria-describedby]="ariaDescribedBy()"
1571
1491
  class="form-control"
1572
1492
  [class.form-control-sm]="p?.size === 'sm'"
1573
1493
  [class.form-control-lg]="p?.size === 'lg'"
@@ -1576,7 +1496,7 @@ class BsTextareaFieldComponent {
1576
1496
  ></textarea>
1577
1497
 
1578
1498
  @if (label()) {
1579
- <label [for]="key()">{{ label() | dynamicText | async }}</label>
1499
+ <label [for]="textareaId">{{ label() | dynamicText | async }}</label>
1580
1500
  }
1581
1501
  @if (p?.validFeedback && f().valid() && f().touched()) {
1582
1502
  <div class="valid-feedback d-block">
@@ -1591,17 +1511,17 @@ class BsTextareaFieldComponent {
1591
1511
  <!-- Standard variant -->
1592
1512
  <div class="mb-3">
1593
1513
  @if (label()) {
1594
- <label [for]="key()" class="form-label">{{ label() | dynamicText | async }}</label>
1514
+ <label [for]="textareaId" class="form-label">{{ label() | dynamicText | async }}</label>
1595
1515
  }
1596
1516
 
1597
1517
  <textarea
1598
1518
  [formField]="f"
1599
- [id]="key()"
1519
+ [id]="textareaId"
1600
1520
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1601
1521
  [attr.tabindex]="tabIndex()"
1602
- [attr.aria-invalid]="ariaInvalid"
1603
- [attr.aria-required]="ariaRequired"
1604
- [attr.aria-describedby]="ariaDescribedBy"
1522
+ [attr.aria-invalid]="ariaInvalid()"
1523
+ [attr.aria-required]="ariaRequired()"
1524
+ [attr.aria-describedby]="ariaDescribedBy()"
1605
1525
  class="form-control"
1606
1526
  [class.form-control-sm]="p?.size === 'sm'"
1607
1527
  [class.form-control-lg]="p?.size === 'lg'"
@@ -1609,11 +1529,6 @@ class BsTextareaFieldComponent {
1609
1529
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
1610
1530
  ></textarea>
1611
1531
 
1612
- @if (p?.helpText) {
1613
- <div class="form-text" [id]="helpTextId()">
1614
- {{ p?.helpText | dynamicText | async }}
1615
- </div>
1616
- }
1617
1532
  @if (p?.validFeedback && f().valid() && f().touched()) {
1618
1533
  <div class="valid-feedback d-block">
1619
1534
  {{ p?.validFeedback | dynamicText | async }}
@@ -1621,27 +1536,30 @@ class BsTextareaFieldComponent {
1621
1536
  }
1622
1537
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1623
1538
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1539
+ } @empty {
1540
+ @if (p?.hint) {
1541
+ <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
1542
+ }
1624
1543
  }
1625
1544
  </div>
1626
1545
  }
1627
1546
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1628
1547
  }
1629
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsTextareaFieldComponent, decorators: [{
1548
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsTextareaFieldComponent, decorators: [{
1630
1549
  type: Component,
1631
1550
  args: [{ selector: 'df-bs-textarea', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
1632
- @let f = field(); @let p = props(); @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1633
- @let ariaDescribedBy = this.ariaDescribedBy();
1551
+ @let f = field(); @let p = props(); @let textareaId = key() + '-textarea';
1634
1552
  @if (p?.floatingLabel) {
1635
1553
  <!-- Floating label variant -->
1636
1554
  <div class="form-floating mb-3">
1637
1555
  <textarea
1638
1556
  [formField]="f"
1639
- [id]="key()"
1557
+ [id]="textareaId"
1640
1558
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1641
1559
  [attr.tabindex]="tabIndex()"
1642
- [attr.aria-invalid]="ariaInvalid"
1643
- [attr.aria-required]="ariaRequired"
1644
- [attr.aria-describedby]="ariaDescribedBy"
1560
+ [attr.aria-invalid]="ariaInvalid()"
1561
+ [attr.aria-required]="ariaRequired()"
1562
+ [attr.aria-describedby]="ariaDescribedBy()"
1645
1563
  class="form-control"
1646
1564
  [class.form-control-sm]="p?.size === 'sm'"
1647
1565
  [class.form-control-lg]="p?.size === 'lg'"
@@ -1650,7 +1568,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1650
1568
  ></textarea>
1651
1569
 
1652
1570
  @if (label()) {
1653
- <label [for]="key()">{{ label() | dynamicText | async }}</label>
1571
+ <label [for]="textareaId">{{ label() | dynamicText | async }}</label>
1654
1572
  }
1655
1573
  @if (p?.validFeedback && f().valid() && f().touched()) {
1656
1574
  <div class="valid-feedback d-block">
@@ -1665,17 +1583,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1665
1583
  <!-- Standard variant -->
1666
1584
  <div class="mb-3">
1667
1585
  @if (label()) {
1668
- <label [for]="key()" class="form-label">{{ label() | dynamicText | async }}</label>
1586
+ <label [for]="textareaId" class="form-label">{{ label() | dynamicText | async }}</label>
1669
1587
  }
1670
1588
 
1671
1589
  <textarea
1672
1590
  [formField]="f"
1673
- [id]="key()"
1591
+ [id]="textareaId"
1674
1592
  [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1675
1593
  [attr.tabindex]="tabIndex()"
1676
- [attr.aria-invalid]="ariaInvalid"
1677
- [attr.aria-required]="ariaRequired"
1678
- [attr.aria-describedby]="ariaDescribedBy"
1594
+ [attr.aria-invalid]="ariaInvalid()"
1595
+ [attr.aria-required]="ariaRequired()"
1596
+ [attr.aria-describedby]="ariaDescribedBy()"
1679
1597
  class="form-control"
1680
1598
  [class.form-control-sm]="p?.size === 'sm'"
1681
1599
  [class.form-control-lg]="p?.size === 'lg'"
@@ -1683,11 +1601,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1683
1601
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
1684
1602
  ></textarea>
1685
1603
 
1686
- @if (p?.helpText) {
1687
- <div class="form-text" [id]="helpTextId()">
1688
- {{ p?.helpText | dynamicText | async }}
1689
- </div>
1690
- }
1691
1604
  @if (p?.validFeedback && f().valid() && f().touched()) {
1692
1605
  <div class="valid-feedback d-block">
1693
1606
  {{ p?.validFeedback | dynamicText | async }}
@@ -1695,6 +1608,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1695
1608
  }
1696
1609
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1697
1610
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1611
+ } @empty {
1612
+ @if (p?.hint) {
1613
+ <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
1614
+ }
1698
1615
  }
1699
1616
  </div>
1700
1617
  }
@@ -1732,7 +1649,7 @@ class BsToggleFieldComponent {
1732
1649
  // ─────────────────────────────────────────────────────────────────────────────
1733
1650
  // Accessibility
1734
1651
  // ─────────────────────────────────────────────────────────────────────────────
1735
- helpTextId = computed(() => `${this.key()}-help`, ...(ngDevMode ? [{ debugName: "helpTextId" }] : []));
1652
+ hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
1736
1653
  errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : []));
1737
1654
  ariaInvalid = computed(() => {
1738
1655
  const fieldState = this.field()();
@@ -1741,22 +1658,10 @@ class BsToggleFieldComponent {
1741
1658
  ariaRequired = computed(() => {
1742
1659
  return this.field()().required?.() === true ? true : null;
1743
1660
  }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
1744
- ariaDescribedBy = computed(() => {
1745
- const ids = [];
1746
- if (this.props()?.helpText) {
1747
- ids.push(this.helpTextId());
1748
- }
1749
- const errors = this.errorsToDisplay();
1750
- errors.forEach((_, i) => {
1751
- ids.push(`${this.errorId()}-${i}`);
1752
- });
1753
- return ids.length > 0 ? ids.join(' ') : null;
1754
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
1755
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsToggleFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1756
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: BsToggleFieldComponent, isStandalone: true, selector: "df-bs-toggle", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1757
- @let f = field();
1758
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1759
- @let ariaDescribedBy = this.ariaDescribedBy();
1661
+ ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1662
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsToggleFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1663
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: BsToggleFieldComponent, isStandalone: true, selector: "df-bs-toggle", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1664
+ @let f = field(); @let inputId = key() + '-input';
1760
1665
 
1761
1666
  <div
1762
1667
  class="form-check form-switch"
@@ -1769,35 +1674,32 @@ class BsToggleFieldComponent {
1769
1674
  <input
1770
1675
  type="checkbox"
1771
1676
  [formField]="f"
1772
- [id]="key()"
1677
+ [id]="inputId"
1773
1678
  class="form-check-input"
1774
1679
  [class.is-invalid]="f().invalid() && f().touched()"
1775
1680
  [attr.tabindex]="tabIndex()"
1776
- [attr.aria-invalid]="ariaInvalid"
1777
- [attr.aria-required]="ariaRequired"
1778
- [attr.aria-describedby]="ariaDescribedBy"
1681
+ [attr.aria-invalid]="ariaInvalid()"
1682
+ [attr.aria-required]="ariaRequired()"
1683
+ [attr.aria-describedby]="ariaDescribedBy()"
1779
1684
  />
1780
- <label [for]="key()" class="form-check-label">
1685
+ <label [for]="inputId" class="form-check-label">
1781
1686
  {{ label() | dynamicText | async }}
1782
1687
  </label>
1783
1688
  </div>
1784
1689
 
1785
- @if (props()?.helpText; as helpText) {
1786
- <div class="form-text" [id]="helpTextId()" [attr.hidden]="f().hidden() || null">
1787
- {{ helpText | dynamicText | async }}
1788
- </div>
1789
- }
1790
1690
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1791
1691
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1692
+ } @empty {
1693
+ @if (props()?.hint; as hint) {
1694
+ <div class="form-text" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
1695
+ }
1792
1696
  }
1793
1697
  `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--bs-secondary-color, #6c757d);--df-hint-font-size: .875rem;--df-error-color: var(--bs-danger, #dc3545);--df-error-font-size: .875rem}.df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%;margin-bottom:1rem}.df-bs-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color);font-size:var(--df-hint-font-size);margin-top:.25rem}.df-bs-field:has(.df-bs-hint){margin-bottom:.5rem}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}.form-switch-sm .form-check-input{width:1.75rem;height:1rem;font-size:.875rem}.form-switch-lg .form-check-input{width:3rem;height:1.75rem;font-size:1.125rem}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1794
1698
  }
1795
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: BsToggleFieldComponent, decorators: [{
1699
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: BsToggleFieldComponent, decorators: [{
1796
1700
  type: Component,
1797
1701
  args: [{ selector: 'df-bs-toggle', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
1798
- @let f = field();
1799
- @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1800
- @let ariaDescribedBy = this.ariaDescribedBy();
1702
+ @let f = field(); @let inputId = key() + '-input';
1801
1703
 
1802
1704
  <div
1803
1705
  class="form-check form-switch"
@@ -1810,26 +1712,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
1810
1712
  <input
1811
1713
  type="checkbox"
1812
1714
  [formField]="f"
1813
- [id]="key()"
1715
+ [id]="inputId"
1814
1716
  class="form-check-input"
1815
1717
  [class.is-invalid]="f().invalid() && f().touched()"
1816
1718
  [attr.tabindex]="tabIndex()"
1817
- [attr.aria-invalid]="ariaInvalid"
1818
- [attr.aria-required]="ariaRequired"
1819
- [attr.aria-describedby]="ariaDescribedBy"
1719
+ [attr.aria-invalid]="ariaInvalid()"
1720
+ [attr.aria-required]="ariaRequired()"
1721
+ [attr.aria-describedby]="ariaDescribedBy()"
1820
1722
  />
1821
- <label [for]="key()" class="form-check-label">
1723
+ <label [for]="inputId" class="form-check-label">
1822
1724
  {{ label() | dynamicText | async }}
1823
1725
  </label>
1824
1726
  </div>
1825
1727
 
1826
- @if (props()?.helpText; as helpText) {
1827
- <div class="form-text" [id]="helpTextId()" [attr.hidden]="f().hidden() || null">
1828
- {{ helpText | dynamicText | async }}
1829
- </div>
1830
- }
1831
1728
  @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1832
1729
  <div class="invalid-feedback d-block" [id]="errorId() + '-' + i" role="alert">{{ error.message }}</div>
1730
+ } @empty {
1731
+ @if (props()?.hint; as hint) {
1732
+ <div class="form-text" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
1733
+ }
1833
1734
  }
1834
1735
  `, host: {
1835
1736
  '[class]': 'className()',
@@ -1877,9 +1778,9 @@ const BsField = {
1877
1778
  * @returns Signal containing Record of input names to values for ngComponentOutlet
1878
1779
  */
1879
1780
  function buttonFieldMapper(fieldDef) {
1880
- // Build base inputs (static, from field definition)
1881
- const baseInputs = buildBaseInputs(fieldDef);
1781
+ const defaultProps = inject(DEFAULT_PROPS);
1882
1782
  return computed(() => {
1783
+ const baseInputs = buildBaseInputs(fieldDef, defaultProps());
1883
1784
  const inputs = {
1884
1785
  ...baseInputs,
1885
1786
  };
@@ -1917,20 +1818,21 @@ function buttonFieldMapper(fieldDef) {
1917
1818
  * @returns Signal containing Record of input names to values for ngComponentOutlet
1918
1819
  */
1919
1820
  function submitButtonFieldMapper(fieldDef) {
1920
- // Inject field signal context to access form state and options
1921
- const fieldSignalContext = inject(FIELD_SIGNAL_CONTEXT);
1922
- // Build base inputs (static, from field definition)
1923
- const baseInputs = buildBaseInputs(fieldDef);
1924
- // Use button-logic-resolver to compute disabled state
1821
+ const rootFormRegistry = inject(RootFormRegistryService);
1822
+ const defaultProps = inject(DEFAULT_PROPS);
1823
+ const formOptions = inject(FORM_OPTIONS);
1925
1824
  const fieldWithLogic = fieldDef;
1926
- const disabledSignal = resolveSubmitButtonDisabled({
1927
- form: fieldSignalContext.form,
1928
- formOptions: fieldSignalContext.formOptions,
1929
- fieldLogic: fieldWithLogic.logic,
1930
- explicitlyDisabled: fieldDef.disabled,
1931
- });
1932
- // Return computed signal - evaluates disabledSignal inside for reactivity
1933
1825
  return computed(() => {
1826
+ const baseInputs = buildBaseInputs(fieldDef, defaultProps());
1827
+ // Use rootFormRegistry instead of fieldSignalContext.form because when the submit button
1828
+ // is inside a group/array, fieldSignalContext.form points to the nested form tree,
1829
+ // not the root form. We need root form validity for submit button disabled state (#157).
1830
+ const disabledSignal = resolveSubmitButtonDisabled({
1831
+ form: rootFormRegistry.getRootForm(),
1832
+ formOptions: formOptions(),
1833
+ fieldLogic: fieldWithLogic.logic,
1834
+ explicitlyDisabled: fieldDef.disabled,
1835
+ });
1934
1836
  const inputs = {
1935
1837
  ...baseInputs,
1936
1838
  // No event - native form submit handles it via form's onNativeSubmit
@@ -1957,21 +1859,19 @@ function submitButtonFieldMapper(fieldDef) {
1957
1859
  * @returns Signal containing Record of input names to values for ngComponentOutlet
1958
1860
  */
1959
1861
  function nextButtonFieldMapper(fieldDef) {
1960
- // Inject field signal context to access form state and options
1961
1862
  const fieldSignalContext = inject(FIELD_SIGNAL_CONTEXT);
1962
- // Build base inputs (static, from field definition)
1963
- const baseInputs = buildBaseInputs(fieldDef);
1964
- // Use button-logic-resolver to compute disabled state
1863
+ const defaultProps = inject(DEFAULT_PROPS);
1864
+ const formOptions = inject(FORM_OPTIONS);
1965
1865
  const fieldWithLogic = fieldDef;
1966
- const disabledSignal = resolveNextButtonDisabled({
1967
- form: fieldSignalContext.form,
1968
- formOptions: fieldSignalContext.formOptions,
1969
- fieldLogic: fieldWithLogic.logic,
1970
- explicitlyDisabled: fieldDef.disabled,
1971
- currentPageValid: fieldSignalContext.currentPageValid,
1972
- });
1973
- // Return computed signal - evaluates disabledSignal inside for reactivity
1974
1866
  return computed(() => {
1867
+ const baseInputs = buildBaseInputs(fieldDef, defaultProps());
1868
+ const disabledSignal = resolveNextButtonDisabled({
1869
+ form: fieldSignalContext.form,
1870
+ formOptions: formOptions(),
1871
+ fieldLogic: fieldWithLogic.logic,
1872
+ explicitlyDisabled: fieldDef.disabled,
1873
+ currentPageValid: fieldSignalContext.currentPageValid,
1874
+ });
1975
1875
  const inputs = {
1976
1876
  ...baseInputs,
1977
1877
  event: NextPageEvent,
@@ -1992,9 +1892,9 @@ function nextButtonFieldMapper(fieldDef) {
1992
1892
  * @returns Signal containing Record of input names to values for ngComponentOutlet
1993
1893
  */
1994
1894
  function previousButtonFieldMapper(fieldDef) {
1995
- // Build base inputs (static, from field definition)
1996
- const baseInputs = buildBaseInputs(fieldDef);
1895
+ const defaultProps = inject(DEFAULT_PROPS);
1997
1896
  return computed(() => {
1897
+ const baseInputs = buildBaseInputs(fieldDef, defaultProps());
1998
1898
  const inputs = {
1999
1899
  ...baseInputs,
2000
1900
  event: PreviousPageEvent,
@@ -2020,10 +1920,9 @@ function previousButtonFieldMapper(fieldDef) {
2020
1920
  * @returns Signal containing Record of input names to values for ngComponentOutlet
2021
1921
  */
2022
1922
  function addArrayItemButtonFieldMapper(fieldDef) {
2023
- // Try to get array context (available when inside an array)
2024
- // Use optional injection so it doesn't fail when outside an array
2025
1923
  const arrayContext = inject(ARRAY_CONTEXT, { optional: true });
2026
1924
  const logger = inject(DynamicFormLogger);
1925
+ const defaultProps = inject(DEFAULT_PROPS);
2027
1926
  // Determine the target array key
2028
1927
  // Priority: explicit arrayKey from fieldDef > arrayKey from context
2029
1928
  const targetArrayKey = fieldDef.arrayKey ?? arrayContext?.arrayKey;
@@ -2031,13 +1930,12 @@ function addArrayItemButtonFieldMapper(fieldDef) {
2031
1930
  logger.warn(`addArrayItem button "${fieldDef.key}" has no array context. ` +
2032
1931
  'Either place it inside an array field, or provide an explicit arrayKey property.');
2033
1932
  }
2034
- // Build base inputs (static, from field definition)
2035
- const baseInputs = buildBaseInputs(fieldDef);
2036
1933
  // Set default eventArgs for AddArrayItemEvent (arrayKey)
2037
1934
  // User can override by providing eventArgs in field definition
2038
1935
  const defaultEventArgs = ['$arrayKey'];
2039
1936
  const eventArgs = 'eventArgs' in fieldDef && fieldDef.eventArgs !== undefined ? fieldDef.eventArgs : defaultEventArgs;
2040
1937
  return computed(() => {
1938
+ const baseInputs = buildBaseInputs(fieldDef, defaultProps());
2041
1939
  // Read signal value if index is a signal (supports differential updates)
2042
1940
  const getIndex = () => {
2043
1941
  if (!arrayContext)
@@ -2076,10 +1974,9 @@ function addArrayItemButtonFieldMapper(fieldDef) {
2076
1974
  * @returns Signal containing Record of input names to values for ngComponentOutlet
2077
1975
  */
2078
1976
  function removeArrayItemButtonFieldMapper(fieldDef) {
2079
- // Try to get array context (available when inside an array)
2080
- // Use optional injection so it doesn't fail when outside an array
2081
1977
  const arrayContext = inject(ARRAY_CONTEXT, { optional: true });
2082
1978
  const logger = inject(DynamicFormLogger);
1979
+ const defaultProps = inject(DEFAULT_PROPS);
2083
1980
  // Determine the target array key
2084
1981
  // Priority: explicit arrayKey from fieldDef > arrayKey from context
2085
1982
  const targetArrayKey = fieldDef.arrayKey ?? arrayContext?.arrayKey;
@@ -2087,14 +1984,13 @@ function removeArrayItemButtonFieldMapper(fieldDef) {
2087
1984
  logger.warn(`removeArrayItem button "${fieldDef.key}" has no array context. ` +
2088
1985
  'Either place it inside an array field, or provide an explicit arrayKey property.');
2089
1986
  }
2090
- // Build base inputs (static, from field definition)
2091
- const baseInputs = buildBaseInputs(fieldDef);
2092
1987
  // Set default eventArgs for RemoveArrayItemEvent (arrayKey, index if inside array)
2093
1988
  // When outside array, only pass arrayKey (removes last by default)
2094
1989
  // User can override by providing eventArgs in field definition
2095
1990
  const defaultEventArgs = arrayContext ? ['$arrayKey', '$index'] : ['$arrayKey'];
2096
1991
  const eventArgs = 'eventArgs' in fieldDef && fieldDef.eventArgs !== undefined ? fieldDef.eventArgs : defaultEventArgs;
2097
1992
  return computed(() => {
1993
+ const baseInputs = buildBaseInputs(fieldDef, defaultProps());
2098
1994
  // Read signal value if index is a signal (supports differential updates)
2099
1995
  const getIndex = () => {
2100
1996
  if (!arrayContext)