@ng-forge/dynamic-forms-bootstrap 0.9.0-next.8 → 0.9.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,28 +1,18 @@
1
1
  import { AsyncPipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, input, computed, ChangeDetectionStrategy, Component, ElementRef, Directive, InjectionToken, linkedSignal, model, viewChild, afterRenderEffect, isSignal } from '@angular/core';
4
- import { EventBus, ARRAY_CONTEXT, resolveTokens, DynamicTextPipe, DEFAULT_PROPS, buildBaseInputs } from '@ng-forge/dynamic-forms';
3
+ import { input, computed, ChangeDetectionStrategy, Component, inject, ElementRef, Directive, InjectionToken, signal, forwardRef, linkedSignal, model, isSignal } from '@angular/core';
4
+ import { DynamicTextPipe, runPresetAction, DfAddonSlot, FIELD_SIGNAL_CONTEXT, DynamicFormLogger, DEFAULT_PROPS, ARRAY_CONTEXT, buildBaseInputs, DynamicFormError, ADDON_KIND_DEFINITIONS } from '@ng-forge/dynamic-forms';
5
+ import * as i1 from '@ng-forge/dynamic-forms/integration';
6
+ import { injectNgForgeAction, NgForgeActionHost, injectNgForgeField, NgForgeControl, NgForgeFieldHost, injectNgForgeAddons, ADDON_PRESET_HANDLER, NgForgeAddons, isEqual, valueFieldMapper, optionsFieldMapper, checkboxFieldMapper, submitButtonFieldMapper, nextButtonFieldMapper, previousButtonFieldMapper, addArrayItemButtonMapper, prependArrayItemButtonMapper, insertArrayItemButtonMapper, removeArrayItemButtonMapper, popArrayItemButtonMapper, shiftArrayItemButtonMapper, datepickerFieldMapper, injectNgForgeAddonAction, NgForgeAddonAction } from '@ng-forge/dynamic-forms/integration';
5
7
  import { FormField } from '@angular/forms/signals';
6
- import { createResolvedErrorsSignal, shouldShowErrors, setupMetaTracking, isEqual, valueFieldMapper, optionsFieldMapper, checkboxFieldMapper, submitButtonFieldMapper, nextButtonFieldMapper, previousButtonFieldMapper, addArrayItemButtonMapper, prependArrayItemButtonMapper, insertArrayItemButtonMapper, removeArrayItemButtonMapper, popArrayItemButtonMapper, shiftArrayItemButtonMapper, datepickerFieldMapper } from '@ng-forge/dynamic-forms/integration';
7
8
  import { explicitEffect } from 'ngxtension/explicit-effect';
8
9
 
9
10
  class BsButtonFieldComponent {
10
- eventBus = inject(EventBus);
11
- arrayContext = inject(ARRAY_CONTEXT, { optional: true });
12
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
13
- label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
14
- disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
15
- hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
16
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
17
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
18
- /** Event to dispatch on click. Optional for submit buttons (native form submit handles it). */
19
- event = input(...(ngDevMode ? [undefined, { debugName: "event" }] : /* istanbul ignore next */ []));
20
- eventArgs = input(...(ngDevMode ? [undefined, { debugName: "eventArgs" }] : /* istanbul ignore next */ []));
11
+ action = injectNgForgeAction();
21
12
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
22
- eventContext = input(...(ngDevMode ? [undefined, { debugName: "eventContext" }] : /* istanbul ignore next */ []));
23
- /** Resolved button type - defaults to 'button' if not specified in props */
13
+ /** Resolved button type defaults to 'button' unless overridden via props. */
24
14
  buttonType = computed(() => this.props()?.type ?? 'button', ...(ngDevMode ? [{ debugName: "buttonType" }] : /* istanbul ignore next */ []));
25
- buttonTestId = computed(() => `${this.buttonType()}-${this.key()}`, ...(ngDevMode ? [{ debugName: "buttonTestId" }] : /* istanbul ignore next */ []));
15
+ buttonTestId = computed(() => `${this.buttonType()}-${this.action.key()}`, ...(ngDevMode ? [{ debugName: "buttonTestId" }] : /* istanbul ignore next */ []));
26
16
  buttonClasses = computed(() => {
27
17
  const p = this.props();
28
18
  const variant = p?.variant || 'primary';
@@ -34,88 +24,50 @@ class BsButtonFieldComponent {
34
24
  p?.size === 'lg' && 'btn-lg',
35
25
  p?.block && 'w-100',
36
26
  p?.active && 'active',
37
- this.className(),
27
+ this.action.className(),
38
28
  ]
39
29
  .filter(Boolean)
40
30
  .join(' ');
41
31
  }, ...(ngDevMode ? [{ debugName: "buttonClasses" }] : /* istanbul ignore next */ []));
42
- /**
43
- * Handle button click.
44
- * - For submit buttons (type="submit"): do nothing, native form submit handles it
45
- * - For other buttons: dispatch the configured event via EventBus
46
- */
32
+ /** Submit buttons let the native form handle submission; other buttons dispatch via the directive. */
47
33
  onClick() {
48
- // Native submit buttons let the form handle submission
49
- if (this.buttonType() === 'submit') {
34
+ if (this.buttonType() === 'submit')
50
35
  return;
51
- }
52
- // Other buttons dispatch their event (if configured)
53
- const event = this.event();
54
- if (event) {
55
- this.dispatchEvent(event);
56
- }
57
- }
58
- dispatchEvent(event) {
59
- const args = this.eventArgs();
60
- if (args && args.length > 0) {
61
- // Build context from injected ARRAY_CONTEXT (with linkedSignal index) or fallback to eventContext
62
- const context = this.arrayContext
63
- ? {
64
- key: this.key(),
65
- // Read signal to get current index (automatically updates via linkedSignal)
66
- index: this.arrayContext.index(),
67
- arrayKey: this.arrayContext.arrayKey,
68
- formValue: this.arrayContext.formValue,
69
- }
70
- : this.eventContext() || { key: this.key() };
71
- // Resolve tokens in event args using the provided context
72
- const resolvedArgs = resolveTokens(args, context);
73
- // Dispatch event with resolved args
74
- this.eventBus.dispatch(event, ...resolvedArgs);
75
- }
76
- else {
77
- // No args, dispatch event without arguments
78
- this.eventBus.dispatch(event);
79
- }
36
+ this.action.dispatch();
80
37
  }
81
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsButtonFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
82
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.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()", "attr.hidden": "hidden() || null" } }, ngImport: i0, template: `
83
- @let buttonId = key() + '-button';
38
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsButtonFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
39
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.14", type: BsButtonFieldComponent, isStandalone: true, selector: "df-bs-button", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeActionHost }], ngImport: i0, template: `
40
+ @let buttonId = action.key() + '-button';
84
41
  <button
85
42
  [id]="buttonId"
86
43
  [type]="buttonType()"
87
- [disabled]="disabled()"
44
+ [disabled]="action.disabled()"
88
45
  [class]="buttonClasses()"
89
- [attr.tabindex]="tabIndex()"
46
+ [attr.tabindex]="action.tabIndex()"
90
47
  [attr.data-testid]="buttonTestId()"
91
48
  (click)="onClick()"
92
49
  >
93
- {{ label() | dynamicText | async }}
50
+ {{ action.label() | dynamicText | async }}
94
51
  </button>
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 });
52
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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 });
96
53
  }
97
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsButtonFieldComponent, decorators: [{
54
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsButtonFieldComponent, decorators: [{
98
55
  type: Component,
99
- args: [{ selector: 'df-bs-button', imports: [DynamicTextPipe, AsyncPipe], host: {
100
- '[id]': '`${key()}`',
101
- '[attr.data-testid]': 'key()',
102
- '[class]': 'className()',
103
- '[attr.hidden]': 'hidden() || null',
104
- }, template: `
105
- @let buttonId = key() + '-button';
56
+ args: [{ selector: 'df-bs-button', imports: [DynamicTextPipe, AsyncPipe], hostDirectives: [NgForgeActionHost], template: `
57
+ @let buttonId = action.key() + '-button';
106
58
  <button
107
59
  [id]="buttonId"
108
60
  [type]="buttonType()"
109
- [disabled]="disabled()"
61
+ [disabled]="action.disabled()"
110
62
  [class]="buttonClasses()"
111
- [attr.tabindex]="tabIndex()"
63
+ [attr.tabindex]="action.tabIndex()"
112
64
  [attr.data-testid]="buttonTestId()"
113
65
  (click)="onClick()"
114
66
  >
115
- {{ label() | dynamicText | async }}
67
+ {{ action.label() | dynamicText | async }}
116
68
  </button>
117
- `, changeDetection: ChangeDetectionStrategy.OnPush, 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"] }]
118
- }], propDecorators: { key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], event: [{ type: i0.Input, args: [{ isSignal: true, alias: "event", required: false }] }], eventArgs: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventArgs", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], eventContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventContext", required: false }] }] } });
69
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n"] }]
70
+ }], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
119
71
 
120
72
  var bsButton_component = /*#__PURE__*/Object.freeze({
121
73
  __proto__: null,
@@ -124,63 +76,12 @@ var bsButton_component = /*#__PURE__*/Object.freeze({
124
76
 
125
77
  // Public API - component
126
78
 
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
- * Only the first error is displayed (single error ID, not indexed).
131
- *
132
- * @param errorsToDisplay Signal containing the array of errors currently being displayed
133
- * @param errorId Signal containing the ID for the error element (single error only)
134
- * @param hintId Signal containing the ID for the hint element
135
- * @param hasHint Function that returns true if a hint is configured
136
- * @returns Signal containing the aria-describedby value or null
137
- */
138
- function createAriaDescribedBySignal(errorsToDisplay, errorId, hintId, hasHint) {
139
- return computed(() => {
140
- if (errorsToDisplay().length > 0) {
141
- return errorId();
142
- }
143
- if (hasHint()) {
144
- return hintId();
145
- }
146
- return null;
147
- });
148
- }
149
-
150
79
  class BsCheckboxFieldComponent {
151
- elementRef = inject((ElementRef));
152
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
153
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
154
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
155
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
156
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
157
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
80
+ ngf = injectNgForgeField();
158
81
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
159
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
160
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
161
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
162
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
163
- showErrors = shouldShowErrors(this.field);
164
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
165
- constructor() {
166
- setupMetaTracking(this.elementRef, this.meta, { selector: 'input[type="checkbox"]' });
167
- }
168
- // ─────────────────────────────────────────────────────────────────────────────
169
- // Accessibility
170
- // ─────────────────────────────────────────────────────────────────────────────
171
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
172
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
173
- ariaInvalid = computed(() => {
174
- const fieldState = this.field()();
175
- return fieldState.invalid() && fieldState.touched();
176
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
177
- ariaRequired = computed(() => {
178
- return this.field()().required?.() === true ? true : null;
179
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
180
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
181
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
182
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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" } }, ngImport: i0, template: `
183
- @let f = field(); @let checkboxId = key() + '-checkbox';
82
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
83
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsCheckboxFieldComponent, isStandalone: true, selector: "df-bs-checkbox", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
84
+ @let f = ngf.field(); @let checkboxId = ngf.key() + '-checkbox';
184
85
 
185
86
  <div
186
87
  class="form-check"
@@ -190,33 +91,31 @@ class BsCheckboxFieldComponent {
190
91
  [attr.hidden]="f().hidden() || null"
191
92
  >
192
93
  <input
94
+ ngForgeControl
193
95
  type="checkbox"
194
96
  [formField]="f"
195
97
  [id]="checkboxId"
196
98
  [indeterminate]="props()?.indeterminate ?? false"
197
99
  class="form-check-input"
198
100
  [class.is-invalid]="f().invalid() && f().touched()"
199
- [attr.tabindex]="tabIndex()"
200
- [attr.aria-invalid]="ariaInvalid()"
201
- [attr.aria-required]="ariaRequired()"
202
- [attr.aria-describedby]="ariaDescribedBy()"
101
+ [attr.tabindex]="ngf.tabIndex()"
203
102
  />
204
103
  <label [for]="checkboxId" class="form-check-label">
205
- {{ label() | dynamicText | async }}
104
+ {{ ngf.label() | dynamicText | async }}
206
105
  </label>
207
106
  </div>
208
107
 
209
- @if (errorsToDisplay()[0]; as error) {
210
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
108
+ @if (ngf.errorsToDisplay()[0]; as error) {
109
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
211
110
  } @else if (props()?.hint; as hint) {
212
- <div class="form-text" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
111
+ <div class="form-text" [id]="ngf.hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
213
112
  }
214
- `, 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"], exportAs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
113
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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"], exportAs: ["formField"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
215
114
  }
216
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsCheckboxFieldComponent, decorators: [{
115
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsCheckboxFieldComponent, decorators: [{
217
116
  type: Component,
218
- args: [{ selector: 'df-bs-checkbox', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
219
- @let f = field(); @let checkboxId = key() + '-checkbox';
117
+ args: [{ selector: 'df-bs-checkbox', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
118
+ @let f = ngf.field(); @let checkboxId = ngf.key() + '-checkbox';
220
119
 
221
120
  <div
222
121
  class="form-check"
@@ -226,34 +125,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
226
125
  [attr.hidden]="f().hidden() || null"
227
126
  >
228
127
  <input
128
+ ngForgeControl
229
129
  type="checkbox"
230
130
  [formField]="f"
231
131
  [id]="checkboxId"
232
132
  [indeterminate]="props()?.indeterminate ?? false"
233
133
  class="form-check-input"
234
134
  [class.is-invalid]="f().invalid() && f().touched()"
235
- [attr.tabindex]="tabIndex()"
236
- [attr.aria-invalid]="ariaInvalid()"
237
- [attr.aria-required]="ariaRequired()"
238
- [attr.aria-describedby]="ariaDescribedBy()"
135
+ [attr.tabindex]="ngf.tabIndex()"
239
136
  />
240
137
  <label [for]="checkboxId" class="form-check-label">
241
- {{ label() | dynamicText | async }}
138
+ {{ ngf.label() | dynamicText | async }}
242
139
  </label>
243
140
  </div>
244
141
 
245
- @if (errorsToDisplay()[0]; as error) {
246
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
142
+ @if (ngf.errorsToDisplay()[0]; as error) {
143
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
247
144
  } @else if (props()?.hint; as hint) {
248
- <div class="form-text" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
145
+ <div class="form-text" [id]="ngf.hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
249
146
  }
250
- `, host: {
251
- '[class]': 'className()',
252
- '[id]': '`${key()}`',
253
- '[attr.data-testid]': 'key()',
254
- '[attr.hidden]': 'field()().hidden() || null',
255
- }, changeDetection: ChangeDetectionStrategy.OnPush, 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"] }]
256
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }] } });
147
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"] }]
148
+ }], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
257
149
 
258
150
  var bsCheckbox_component = /*#__PURE__*/Object.freeze({
259
151
  __proto__: null,
@@ -295,10 +187,10 @@ class InputConstraintsDirective {
295
187
  nativeElement.removeAttribute('step');
296
188
  }
297
189
  });
298
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: InputConstraintsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
299
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.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 });
190
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: InputConstraintsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
191
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.14", 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 });
300
192
  }
301
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: InputConstraintsDirective, decorators: [{
193
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: InputConstraintsDirective, decorators: [{
302
194
  type: Directive,
303
195
  args: [{
304
196
  selector: '[dfBsInputConstraints]',
@@ -306,26 +198,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
306
198
  }], propDecorators: { dfMin: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfMin", required: false }] }], dfMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfMax", required: false }] }], dfStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfStep", required: false }] }] } });
307
199
 
308
200
  class BsDatepickerFieldComponent {
309
- elementRef = inject((ElementRef));
310
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
311
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
312
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
313
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
314
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
315
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
201
+ ngf = injectNgForgeField();
316
202
  minDate = input(null, ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
317
203
  maxDate = input(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
318
204
  startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
319
205
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
320
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
321
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
322
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
323
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
324
- showErrors = shouldShowErrors(this.field);
325
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
326
- constructor() {
327
- setupMetaTracking(this.elementRef, this.meta, { selector: 'input' });
328
- }
329
206
  // Helper methods to convert Date to string for HTML attributes
330
207
  minAsString = computed(() => {
331
208
  const min = this.minDate();
@@ -335,74 +212,57 @@ class BsDatepickerFieldComponent {
335
212
  const max = this.maxDate();
336
213
  return max instanceof Date ? max.toISOString().split('T')[0] : max;
337
214
  }, ...(ngDevMode ? [{ debugName: "maxAsString" }] : /* istanbul ignore next */ []));
338
- // ─────────────────────────────────────────────────────────────────────────────
339
- // Accessibility
340
- // ─────────────────────────────────────────────────────────────────────────────
341
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
342
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
343
- ariaInvalid = computed(() => {
344
- const fieldState = this.field()();
345
- return fieldState.invalid() && fieldState.touched();
346
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
347
- ariaRequired = computed(() => {
348
- return this.field()().required?.() === true ? true : null;
349
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
350
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
351
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsDatepickerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
352
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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: `
353
- @let f = field(); @let p = props(); @let inputId = key() + '-input';
215
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsDatepickerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
216
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsDatepickerFieldComponent, isStandalone: true, selector: "df-bs-datepicker", inputs: { 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 } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
217
+ @let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';
354
218
  @if (p?.floatingLabel) {
355
219
  <!-- Floating label variant -->
356
220
  <div class="form-floating mb-3">
357
221
  <input
222
+ ngForgeControl
358
223
  dfBsInputConstraints
359
224
  [formField]="f"
360
225
  [id]="inputId"
361
226
  type="date"
362
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
227
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
363
228
  [dfMin]="minAsString()"
364
229
  [dfMax]="maxAsString()"
365
- [attr.tabindex]="tabIndex()"
366
- [attr.aria-invalid]="ariaInvalid()"
367
- [attr.aria-required]="ariaRequired()"
368
- [attr.aria-describedby]="ariaDescribedBy()"
230
+ [attr.tabindex]="ngf.tabIndex()"
369
231
  class="form-control"
370
232
  [class.form-control-sm]="p?.size === 'sm'"
371
233
  [class.form-control-lg]="p?.size === 'lg'"
372
234
  [class.is-invalid]="f().invalid() && f().touched()"
373
235
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
374
236
  />
375
- @if (label()) {
376
- <label [for]="inputId">{{ label() | dynamicText | async }}</label>
237
+ @if (ngf.label()) {
238
+ <label [for]="inputId">{{ ngf.label() | dynamicText | async }}</label>
377
239
  }
378
240
  @if (p?.validFeedback && f().valid() && f().touched()) {
379
241
  <div class="valid-feedback d-block">
380
242
  {{ p?.validFeedback | dynamicText | async }}
381
243
  </div>
382
244
  }
383
- @if (errorsToDisplay()[0]; as error) {
384
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
245
+ @if (ngf.errorsToDisplay()[0]; as error) {
246
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
385
247
  }
386
248
  </div>
387
249
  } @else {
388
250
  <!-- Standard variant -->
389
251
  <div class="mb-3">
390
- @if (label()) {
391
- <label [for]="inputId" class="form-label">{{ label() | dynamicText | async }}</label>
252
+ @if (ngf.label()) {
253
+ <label [for]="inputId" class="form-label">{{ ngf.label() | dynamicText | async }}</label>
392
254
  }
393
255
 
394
256
  <input
257
+ ngForgeControl
395
258
  dfBsInputConstraints
396
259
  [formField]="f"
397
260
  [id]="inputId"
398
261
  type="date"
399
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
262
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
400
263
  [dfMin]="minAsString()"
401
264
  [dfMax]="maxAsString()"
402
- [attr.tabindex]="tabIndex()"
403
- [attr.aria-invalid]="ariaInvalid()"
404
- [attr.aria-required]="ariaRequired()"
405
- [attr.aria-describedby]="ariaDescribedBy()"
265
+ [attr.tabindex]="ngf.tabIndex()"
406
266
  class="form-control"
407
267
  [class.form-control-sm]="p?.size === 'sm'"
408
268
  [class.form-control-lg]="p?.size === 'lg'"
@@ -415,71 +275,67 @@ class BsDatepickerFieldComponent {
415
275
  {{ p?.validFeedback | dynamicText | async }}
416
276
  </div>
417
277
  }
418
- @if (errorsToDisplay()[0]; as error) {
419
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
278
+ @if (ngf.errorsToDisplay()[0]; as error) {
279
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
420
280
  } @else if (p?.hint) {
421
- <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
281
+ <div class="form-text" [id]="ngf.hintId()">{{ p?.hint | dynamicText | async }}</div>
422
282
  }
423
283
  </div>
424
284
  }
425
- `, 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"], exportAs: ["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 });
285
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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"], exportAs: ["formField"] }, { kind: "directive", type: InputConstraintsDirective, selector: "[dfBsInputConstraints]", inputs: ["dfMin", "dfMax", "dfStep"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
426
286
  }
427
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsDatepickerFieldComponent, decorators: [{
287
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsDatepickerFieldComponent, decorators: [{
428
288
  type: Component,
429
- args: [{ selector: 'df-bs-datepicker', imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective], template: `
430
- @let f = field(); @let p = props(); @let inputId = key() + '-input';
289
+ args: [{ selector: 'df-bs-datepicker', imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
290
+ @let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';
431
291
  @if (p?.floatingLabel) {
432
292
  <!-- Floating label variant -->
433
293
  <div class="form-floating mb-3">
434
294
  <input
295
+ ngForgeControl
435
296
  dfBsInputConstraints
436
297
  [formField]="f"
437
298
  [id]="inputId"
438
299
  type="date"
439
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
300
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
440
301
  [dfMin]="minAsString()"
441
302
  [dfMax]="maxAsString()"
442
- [attr.tabindex]="tabIndex()"
443
- [attr.aria-invalid]="ariaInvalid()"
444
- [attr.aria-required]="ariaRequired()"
445
- [attr.aria-describedby]="ariaDescribedBy()"
303
+ [attr.tabindex]="ngf.tabIndex()"
446
304
  class="form-control"
447
305
  [class.form-control-sm]="p?.size === 'sm'"
448
306
  [class.form-control-lg]="p?.size === 'lg'"
449
307
  [class.is-invalid]="f().invalid() && f().touched()"
450
308
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
451
309
  />
452
- @if (label()) {
453
- <label [for]="inputId">{{ label() | dynamicText | async }}</label>
310
+ @if (ngf.label()) {
311
+ <label [for]="inputId">{{ ngf.label() | dynamicText | async }}</label>
454
312
  }
455
313
  @if (p?.validFeedback && f().valid() && f().touched()) {
456
314
  <div class="valid-feedback d-block">
457
315
  {{ p?.validFeedback | dynamicText | async }}
458
316
  </div>
459
317
  }
460
- @if (errorsToDisplay()[0]; as error) {
461
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
318
+ @if (ngf.errorsToDisplay()[0]; as error) {
319
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
462
320
  }
463
321
  </div>
464
322
  } @else {
465
323
  <!-- Standard variant -->
466
324
  <div class="mb-3">
467
- @if (label()) {
468
- <label [for]="inputId" class="form-label">{{ label() | dynamicText | async }}</label>
325
+ @if (ngf.label()) {
326
+ <label [for]="inputId" class="form-label">{{ ngf.label() | dynamicText | async }}</label>
469
327
  }
470
328
 
471
329
  <input
330
+ ngForgeControl
472
331
  dfBsInputConstraints
473
332
  [formField]="f"
474
333
  [id]="inputId"
475
334
  type="date"
476
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
335
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
477
336
  [dfMin]="minAsString()"
478
337
  [dfMax]="maxAsString()"
479
- [attr.tabindex]="tabIndex()"
480
- [attr.aria-invalid]="ariaInvalid()"
481
- [attr.aria-required]="ariaRequired()"
482
- [attr.aria-describedby]="ariaDescribedBy()"
338
+ [attr.tabindex]="ngf.tabIndex()"
483
339
  class="form-control"
484
340
  [class.form-control-sm]="p?.size === 'sm'"
485
341
  [class.form-control-lg]="p?.size === 'lg'"
@@ -492,20 +348,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
492
348
  {{ p?.validFeedback | dynamicText | async }}
493
349
  </div>
494
350
  }
495
- @if (errorsToDisplay()[0]; as error) {
496
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
351
+ @if (ngf.errorsToDisplay()[0]; as error) {
352
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
497
353
  } @else if (p?.hint) {
498
- <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
354
+ <div class="form-text" [id]="ngf.hintId()">{{ p?.hint | dynamicText | async }}</div>
499
355
  }
500
356
  </div>
501
357
  }
502
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
503
- '[id]': '`${key()}`',
504
- '[attr.data-testid]': 'key()',
505
- '[class]': 'className()',
506
- '[attr.hidden]': 'field()().hidden() || null',
507
- }, 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"] }]
508
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }] } });
358
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"] }]
359
+ }], propDecorators: { minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
509
360
 
510
361
  var bsDatepicker_component = /*#__PURE__*/Object.freeze({
511
362
  __proto__: null,
@@ -535,191 +386,364 @@ var bsDatepicker_component = /*#__PURE__*/Object.freeze({
535
386
  */
536
387
  const BOOTSTRAP_CONFIG = new InjectionToken('BOOTSTRAP_CONFIG');
537
388
 
389
+ /** Bootstrap adapter binding for the shared preset runner. */
390
+ function runBsPresetAction(preset, ctx, collaborators) {
391
+ return runPresetAction(preset, ctx, collaborators, 'Bootstrap', 'bs-input');
392
+ }
393
+
394
+ /**
395
+ * Per-field writable signal that overrides the input's `type` attribute.
396
+ *
397
+ * Provided at the `bs-input` field component level. The button addon's
398
+ * `'toggle-password-visibility'` preset writes to it; the field component
399
+ * reads it to compute its effective `type`.
400
+ *
401
+ * Optional from a button's perspective — when the button is hosted inside a
402
+ * field that doesn't provide this token (e.g., textarea or a future
403
+ * non-input field), the toggle preset is a no-op.
404
+ */
405
+ const BS_INPUT_TYPE_OVERRIDE = new InjectionToken('BS_INPUT_TYPE_OVERRIDE');
406
+
538
407
  class BsInputFieldComponent {
539
408
  bootstrapConfig = inject(BOOTSTRAP_CONFIG, { optional: true });
540
- elementRef = inject((ElementRef));
541
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
542
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
543
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
544
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
545
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
546
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
409
+ ngf = injectNgForgeField();
410
+ ngfa = injectNgForgeAddons();
547
411
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
548
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
549
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
550
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
551
- constructor() {
552
- setupMetaTracking(this.elementRef, this.meta, { selector: 'input' });
553
- }
554
- effectiveSize = computed(() => this.props()?.size ?? this.bootstrapConfig?.size, ...(ngDevMode ? [{ debugName: "effectiveSize" }] : /* istanbul ignore next */ []));
555
- effectiveFloatingLabel = computed(() => this.props()?.floatingLabel ?? this.bootstrapConfig?.floatingLabel ?? false, ...(ngDevMode ? [{ debugName: "effectiveFloatingLabel" }] : /* istanbul ignore next */ []));
556
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
557
- showErrors = shouldShowErrors(this.field);
558
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
559
- // ─────────────────────────────────────────────────────────────────────────────
560
- // Accessibility
561
- // ─────────────────────────────────────────────────────────────────────────────
562
- /** Unique ID for the hint element, used for aria-describedby */
563
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
564
- /** Base ID for error elements, used for aria-describedby */
565
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
566
- /** aria-invalid: true when field is invalid AND touched, false otherwise */
567
- ariaInvalid = computed(() => {
568
- const fieldState = this.field()();
569
- return fieldState.invalid() && fieldState.touched();
570
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
571
- /** aria-required: true if field is required, null otherwise (to remove attribute) */
572
- ariaRequired = computed(() => {
573
- return this.field()().required?.() === true ? true : null;
574
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
575
- /** aria-describedby: links to hint and error messages for screen readers */
576
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
577
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsInputFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
578
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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" } }, ngImport: i0, template: `
579
- @let f = field(); @let p = props(); @let inputId = key() + '-input';
580
- @if (effectiveFloatingLabel()) {
581
- <!-- Floating label variant -->
582
- <div class="form-floating mb-3">
583
- <input
584
- [formField]="f"
585
- [id]="inputId"
586
- [type]="p?.type ?? 'text'"
587
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
588
- [attr.tabindex]="tabIndex()"
589
- [attr.aria-invalid]="ariaInvalid()"
590
- [attr.aria-required]="ariaRequired()"
591
- [attr.aria-describedby]="ariaDescribedBy()"
592
- class="form-control"
593
- [class.form-control-sm]="effectiveSize() === 'sm'"
594
- [class.form-control-lg]="effectiveSize() === 'lg'"
595
- [class.form-control-plaintext]="p?.plaintext"
596
- [class.is-invalid]="f().invalid() && f().touched()"
597
- [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
598
- />
599
- @if (label()) {
600
- <label [for]="inputId">{{ label() | dynamicText | async }}</label>
412
+ /**
413
+ * Wrapper-style host bag pushed by `DfFieldOutlet`. Declared at the
414
+ * component level so `setInputIfDeclared` (which uses
415
+ * `reflectComponentType`) can write it.
416
+ */
417
+ fieldInputs = input(...(ngDevMode ? [undefined, { debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
418
+ /** Per-instance type override populated by `toggle-password-visibility` preset. */
419
+ typeOverride = inject(BS_INPUT_TYPE_OVERRIDE);
420
+ size = computed(() => this.props()?.size ?? this.bootstrapConfig?.size, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
421
+ floatingLabel = computed(() => this.props()?.floatingLabel ?? this.bootstrapConfig?.floatingLabel ?? false, ...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
422
+ /** Override (set by `toggle-password-visibility` preset) wins over `props().type`. */
423
+ type = computed(() => this.typeOverride() ?? this.props()?.type ?? 'text', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
424
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsInputFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
425
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsInputFieldComponent, isStandalone: true, selector: "df-bs-input", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
426
+ {
427
+ provide: BS_INPUT_TYPE_OVERRIDE,
428
+ useFactory: () => signal(undefined),
429
+ },
430
+ {
431
+ // Adapter-specific preset semantics for `bs-button` addons (clear /
432
+ // reset / paste / copy / toggle-password-visibility). The directive
433
+ // (`NgForgeAddonAction`) delegates here when an addon configures a
434
+ // `preset`. Per-bs-input-instance so the `typeOverride` signal is
435
+ // scoped to one field.
436
+ provide: ADDON_PRESET_HANDLER,
437
+ useFactory: () => {
438
+ const typeOverride = inject(BS_INPUT_TYPE_OVERRIDE);
439
+ const fsc = inject(FIELD_SIGNAL_CONTEXT, { optional: true });
440
+ const logger = inject(DynamicFormLogger);
441
+ // forwardRef for baselineType only host.props()?.type gates toggle-password-visibility.
442
+ const host = inject(forwardRef(() => BsInputFieldComponent));
443
+ return {
444
+ run: (preset, ctx) => {
445
+ const fieldKey = ctx.field.key;
446
+ return runBsPresetAction(preset, ctx, {
447
+ typeOverride,
448
+ fieldValueSetter: ctx.setValue,
449
+ fieldDefaultValueGetter: fsc && fieldKey ? () => fsc.defaultValues()?.[fieldKey] : undefined,
450
+ baselineType: () => host.props()?.type,
451
+ logger,
452
+ });
453
+ },
454
+ };
455
+ },
456
+ },
457
+ ], hostDirectives: [{ directive: i1.NgForgeFieldHost }, { directive: i1.NgForgeAddons }], ngImport: i0, template: `
458
+ @let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';
459
+ @if (floatingLabel()) {
460
+ <div class="mb-3">
461
+ @if (ngfa.hasAddons()) {
462
+ <div class="input-group">
463
+ @for (a of ngfa.prefixAddons(); track $index) {
464
+ <span class="input-group-text">
465
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
466
+ </span>
467
+ }
468
+ <div class="form-floating">
469
+ <input
470
+ ngForgeControl
471
+ [formField]="f"
472
+ [id]="inputId"
473
+ [type]="type()"
474
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
475
+ [attr.tabindex]="ngf.tabIndex()"
476
+ class="form-control"
477
+ [class.form-control-sm]="size() === 'sm'"
478
+ [class.form-control-lg]="size() === 'lg'"
479
+ [class.form-control-plaintext]="p?.plaintext"
480
+ [class.is-invalid]="f().invalid() && f().touched()"
481
+ [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
482
+ />
483
+ @if (ngf.label()) {
484
+ <label [for]="inputId">{{ ngf.label() | dynamicText | async }}</label>
485
+ }
486
+ </div>
487
+ @for (a of ngfa.suffixAddons(); track $index) {
488
+ <span class="input-group-text">
489
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
490
+ </span>
491
+ }
492
+ </div>
493
+ } @else {
494
+ <div class="form-floating">
495
+ <input
496
+ ngForgeControl
497
+ [formField]="f"
498
+ [id]="inputId"
499
+ [type]="type()"
500
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
501
+ [attr.tabindex]="ngf.tabIndex()"
502
+ class="form-control"
503
+ [class.form-control-sm]="size() === 'sm'"
504
+ [class.form-control-lg]="size() === 'lg'"
505
+ [class.form-control-plaintext]="p?.plaintext"
506
+ [class.is-invalid]="f().invalid() && f().touched()"
507
+ [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
508
+ />
509
+ @if (ngf.label()) {
510
+ <label [for]="inputId">{{ ngf.label() | dynamicText | async }}</label>
511
+ }
512
+ </div>
601
513
  }
602
514
  @if (p?.validFeedback && f().valid() && f().touched()) {
603
515
  <div class="valid-feedback d-block">
604
516
  {{ p?.validFeedback | dynamicText | async }}
605
517
  </div>
606
518
  }
607
- @if (errorsToDisplay()[0]; as error) {
608
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
519
+ @if (ngf.errorsToDisplay()[0]; as error) {
520
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
609
521
  }
610
522
  </div>
611
523
  } @else {
612
- <!-- Standard variant -->
613
524
  <div class="mb-3">
614
- @if (label()) {
615
- <label [for]="inputId" class="form-label">{{ label() | dynamicText | async }}</label>
525
+ @if (ngf.label()) {
526
+ <label [for]="inputId" class="form-label">{{ ngf.label() | dynamicText | async }}</label>
527
+ }
528
+ @if (ngfa.hasAddons()) {
529
+ <div class="input-group">
530
+ @for (a of ngfa.prefixAddons(); track $index) {
531
+ <span class="input-group-text">
532
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
533
+ </span>
534
+ }
535
+ <input
536
+ ngForgeControl
537
+ [formField]="f"
538
+ [id]="inputId"
539
+ [type]="type()"
540
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
541
+ [attr.tabindex]="ngf.tabIndex()"
542
+ class="form-control"
543
+ [class.form-control-sm]="size() === 'sm'"
544
+ [class.form-control-lg]="size() === 'lg'"
545
+ [class.form-control-plaintext]="p?.plaintext"
546
+ [class.is-invalid]="f().invalid() && f().touched()"
547
+ [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
548
+ />
549
+ @for (a of ngfa.suffixAddons(); track $index) {
550
+ <span class="input-group-text">
551
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
552
+ </span>
553
+ }
554
+ </div>
555
+ } @else {
556
+ <input
557
+ ngForgeControl
558
+ [formField]="f"
559
+ [id]="inputId"
560
+ [type]="type()"
561
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
562
+ [attr.tabindex]="ngf.tabIndex()"
563
+ class="form-control"
564
+ [class.form-control-sm]="size() === 'sm'"
565
+ [class.form-control-lg]="size() === 'lg'"
566
+ [class.form-control-plaintext]="p?.plaintext"
567
+ [class.is-invalid]="f().invalid() && f().touched()"
568
+ [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
569
+ />
616
570
  }
617
- <input
618
- [formField]="f"
619
- [id]="inputId"
620
- [type]="p?.type ?? 'text'"
621
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
622
- [attr.tabindex]="tabIndex()"
623
- [attr.aria-invalid]="ariaInvalid()"
624
- [attr.aria-required]="ariaRequired()"
625
- [attr.aria-describedby]="ariaDescribedBy()"
626
- class="form-control"
627
- [class.form-control-sm]="effectiveSize() === 'sm'"
628
- [class.form-control-lg]="effectiveSize() === 'lg'"
629
- [class.form-control-plaintext]="p?.plaintext"
630
- [class.is-invalid]="f().invalid() && f().touched()"
631
- [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
632
- />
633
571
  @if (p?.validFeedback && f().valid() && f().touched()) {
634
572
  <div class="valid-feedback d-block">
635
573
  {{ p?.validFeedback | dynamicText | async }}
636
574
  </div>
637
575
  }
638
- @if (errorsToDisplay()[0]; as error) {
639
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
576
+ @if (ngf.errorsToDisplay()[0]; as error) {
577
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
640
578
  } @else if (p?.hint) {
641
- <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
579
+ <div class="form-text" [id]="ngf.hintId()">{{ p?.hint | dynamicText | async }}</div>
642
580
  }
643
581
  </div>
644
582
  }
645
- `, 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"], exportAs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
583
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}:host{--df-bs-addon-prefix-outer-padding: .75rem;--df-bs-addon-prefix-inner-padding: .75rem;--df-bs-addon-suffix-inner-padding: .75rem;--df-bs-addon-suffix-outer-padding: .75rem}:host ::ng-deep .input-group>.input-group-text:first-child{padding-left:var(--df-bs-addon-prefix-outer-padding);padding-right:var(--df-bs-addon-prefix-inner-padding)}:host ::ng-deep .input-group>.input-group-text:last-child{padding-left:var(--df-bs-addon-suffix-inner-padding);padding-right:var(--df-bs-addon-suffix-outer-padding)}:host ::ng-deep .input-group-text df-bs-button-addon .btn{--bs-btn-bg: transparent;--bs-btn-color: var(--bs-body-color);--bs-btn-border-color: transparent;--bs-btn-hover-bg: rgba(127, 127, 127, .12);--bs-btn-hover-color: var(--bs-body-color);--bs-btn-hover-border-color: transparent;--bs-btn-active-bg: rgba(127, 127, 127, .18);--bs-btn-active-border-color: transparent;--bs-btn-focus-shadow-rgb: 0, 0, 0;--bs-btn-padding-x: 0;--bs-btn-padding-y: 0;box-shadow:none}:host ::ng-deep .input-group-text df-bs-button-addon .btn:focus-visible{box-shadow:none;outline:2px solid currentColor;outline-offset:2px}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "component", type: DfAddonSlot, selector: "df-addon-slot", inputs: ["addon", "fieldInputs", "hidden"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
646
584
  }
647
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsInputFieldComponent, decorators: [{
585
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsInputFieldComponent, decorators: [{
648
586
  type: Component,
649
- args: [{ selector: 'df-bs-input', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
650
- @let f = field(); @let p = props(); @let inputId = key() + '-input';
651
- @if (effectiveFloatingLabel()) {
652
- <!-- Floating label variant -->
653
- <div class="form-floating mb-3">
654
- <input
655
- [formField]="f"
656
- [id]="inputId"
657
- [type]="p?.type ?? 'text'"
658
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
659
- [attr.tabindex]="tabIndex()"
660
- [attr.aria-invalid]="ariaInvalid()"
661
- [attr.aria-required]="ariaRequired()"
662
- [attr.aria-describedby]="ariaDescribedBy()"
663
- class="form-control"
664
- [class.form-control-sm]="effectiveSize() === 'sm'"
665
- [class.form-control-lg]="effectiveSize() === 'lg'"
666
- [class.form-control-plaintext]="p?.plaintext"
667
- [class.is-invalid]="f().invalid() && f().touched()"
668
- [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
669
- />
670
- @if (label()) {
671
- <label [for]="inputId">{{ label() | dynamicText | async }}</label>
587
+ args: [{ selector: 'df-bs-input', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl, DfAddonSlot], hostDirectives: [NgForgeFieldHost, NgForgeAddons], template: `
588
+ @let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';
589
+ @if (floatingLabel()) {
590
+ <div class="mb-3">
591
+ @if (ngfa.hasAddons()) {
592
+ <div class="input-group">
593
+ @for (a of ngfa.prefixAddons(); track $index) {
594
+ <span class="input-group-text">
595
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
596
+ </span>
597
+ }
598
+ <div class="form-floating">
599
+ <input
600
+ ngForgeControl
601
+ [formField]="f"
602
+ [id]="inputId"
603
+ [type]="type()"
604
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
605
+ [attr.tabindex]="ngf.tabIndex()"
606
+ class="form-control"
607
+ [class.form-control-sm]="size() === 'sm'"
608
+ [class.form-control-lg]="size() === 'lg'"
609
+ [class.form-control-plaintext]="p?.plaintext"
610
+ [class.is-invalid]="f().invalid() && f().touched()"
611
+ [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
612
+ />
613
+ @if (ngf.label()) {
614
+ <label [for]="inputId">{{ ngf.label() | dynamicText | async }}</label>
615
+ }
616
+ </div>
617
+ @for (a of ngfa.suffixAddons(); track $index) {
618
+ <span class="input-group-text">
619
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
620
+ </span>
621
+ }
622
+ </div>
623
+ } @else {
624
+ <div class="form-floating">
625
+ <input
626
+ ngForgeControl
627
+ [formField]="f"
628
+ [id]="inputId"
629
+ [type]="type()"
630
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
631
+ [attr.tabindex]="ngf.tabIndex()"
632
+ class="form-control"
633
+ [class.form-control-sm]="size() === 'sm'"
634
+ [class.form-control-lg]="size() === 'lg'"
635
+ [class.form-control-plaintext]="p?.plaintext"
636
+ [class.is-invalid]="f().invalid() && f().touched()"
637
+ [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
638
+ />
639
+ @if (ngf.label()) {
640
+ <label [for]="inputId">{{ ngf.label() | dynamicText | async }}</label>
641
+ }
642
+ </div>
672
643
  }
673
644
  @if (p?.validFeedback && f().valid() && f().touched()) {
674
645
  <div class="valid-feedback d-block">
675
646
  {{ p?.validFeedback | dynamicText | async }}
676
647
  </div>
677
648
  }
678
- @if (errorsToDisplay()[0]; as error) {
679
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
649
+ @if (ngf.errorsToDisplay()[0]; as error) {
650
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
680
651
  }
681
652
  </div>
682
653
  } @else {
683
- <!-- Standard variant -->
684
654
  <div class="mb-3">
685
- @if (label()) {
686
- <label [for]="inputId" class="form-label">{{ label() | dynamicText | async }}</label>
655
+ @if (ngf.label()) {
656
+ <label [for]="inputId" class="form-label">{{ ngf.label() | dynamicText | async }}</label>
657
+ }
658
+ @if (ngfa.hasAddons()) {
659
+ <div class="input-group">
660
+ @for (a of ngfa.prefixAddons(); track $index) {
661
+ <span class="input-group-text">
662
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
663
+ </span>
664
+ }
665
+ <input
666
+ ngForgeControl
667
+ [formField]="f"
668
+ [id]="inputId"
669
+ [type]="type()"
670
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
671
+ [attr.tabindex]="ngf.tabIndex()"
672
+ class="form-control"
673
+ [class.form-control-sm]="size() === 'sm'"
674
+ [class.form-control-lg]="size() === 'lg'"
675
+ [class.form-control-plaintext]="p?.plaintext"
676
+ [class.is-invalid]="f().invalid() && f().touched()"
677
+ [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
678
+ />
679
+ @for (a of ngfa.suffixAddons(); track $index) {
680
+ <span class="input-group-text">
681
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
682
+ </span>
683
+ }
684
+ </div>
685
+ } @else {
686
+ <input
687
+ ngForgeControl
688
+ [formField]="f"
689
+ [id]="inputId"
690
+ [type]="type()"
691
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
692
+ [attr.tabindex]="ngf.tabIndex()"
693
+ class="form-control"
694
+ [class.form-control-sm]="size() === 'sm'"
695
+ [class.form-control-lg]="size() === 'lg'"
696
+ [class.form-control-plaintext]="p?.plaintext"
697
+ [class.is-invalid]="f().invalid() && f().touched()"
698
+ [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
699
+ />
687
700
  }
688
- <input
689
- [formField]="f"
690
- [id]="inputId"
691
- [type]="p?.type ?? 'text'"
692
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
693
- [attr.tabindex]="tabIndex()"
694
- [attr.aria-invalid]="ariaInvalid()"
695
- [attr.aria-required]="ariaRequired()"
696
- [attr.aria-describedby]="ariaDescribedBy()"
697
- class="form-control"
698
- [class.form-control-sm]="effectiveSize() === 'sm'"
699
- [class.form-control-lg]="effectiveSize() === 'lg'"
700
- [class.form-control-plaintext]="p?.plaintext"
701
- [class.is-invalid]="f().invalid() && f().touched()"
702
- [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
703
- />
704
701
  @if (p?.validFeedback && f().valid() && f().touched()) {
705
702
  <div class="valid-feedback d-block">
706
703
  {{ p?.validFeedback | dynamicText | async }}
707
704
  </div>
708
705
  }
709
- @if (errorsToDisplay()[0]; as error) {
710
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
706
+ @if (ngf.errorsToDisplay()[0]; as error) {
707
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
711
708
  } @else if (p?.hint) {
712
- <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
709
+ <div class="form-text" [id]="ngf.hintId()">{{ p?.hint | dynamicText | async }}</div>
713
710
  }
714
711
  </div>
715
712
  }
716
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
717
- '[id]': '`${key()}`',
718
- '[attr.data-testid]': 'key()',
719
- '[class]': 'className()',
720
- '[attr.hidden]': 'field()().hidden() || null',
721
- }, 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"] }]
722
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }] } });
713
+ `, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
714
+ {
715
+ provide: BS_INPUT_TYPE_OVERRIDE,
716
+ useFactory: () => signal(undefined),
717
+ },
718
+ {
719
+ // Adapter-specific preset semantics for `bs-button` addons (clear /
720
+ // reset / paste / copy / toggle-password-visibility). The directive
721
+ // (`NgForgeAddonAction`) delegates here when an addon configures a
722
+ // `preset`. Per-bs-input-instance so the `typeOverride` signal is
723
+ // scoped to one field.
724
+ provide: ADDON_PRESET_HANDLER,
725
+ useFactory: () => {
726
+ const typeOverride = inject(BS_INPUT_TYPE_OVERRIDE);
727
+ const fsc = inject(FIELD_SIGNAL_CONTEXT, { optional: true });
728
+ const logger = inject(DynamicFormLogger);
729
+ // forwardRef for baselineType only — host.props()?.type gates toggle-password-visibility.
730
+ const host = inject(forwardRef(() => BsInputFieldComponent));
731
+ return {
732
+ run: (preset, ctx) => {
733
+ const fieldKey = ctx.field.key;
734
+ return runBsPresetAction(preset, ctx, {
735
+ typeOverride,
736
+ fieldValueSetter: ctx.setValue,
737
+ fieldDefaultValueGetter: fsc && fieldKey ? () => fsc.defaultValues()?.[fieldKey] : undefined,
738
+ baselineType: () => host.props()?.type,
739
+ logger,
740
+ });
741
+ },
742
+ };
743
+ },
744
+ },
745
+ ], styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}:host{--df-bs-addon-prefix-outer-padding: .75rem;--df-bs-addon-prefix-inner-padding: .75rem;--df-bs-addon-suffix-inner-padding: .75rem;--df-bs-addon-suffix-outer-padding: .75rem}:host ::ng-deep .input-group>.input-group-text:first-child{padding-left:var(--df-bs-addon-prefix-outer-padding);padding-right:var(--df-bs-addon-prefix-inner-padding)}:host ::ng-deep .input-group>.input-group-text:last-child{padding-left:var(--df-bs-addon-suffix-inner-padding);padding-right:var(--df-bs-addon-suffix-outer-padding)}:host ::ng-deep .input-group-text df-bs-button-addon .btn{--bs-btn-bg: transparent;--bs-btn-color: var(--bs-body-color);--bs-btn-border-color: transparent;--bs-btn-hover-bg: rgba(127, 127, 127, .12);--bs-btn-hover-color: var(--bs-body-color);--bs-btn-hover-border-color: transparent;--bs-btn-active-bg: rgba(127, 127, 127, .18);--bs-btn-active-border-color: transparent;--bs-btn-focus-shadow-rgb: 0, 0, 0;--bs-btn-padding-x: 0;--bs-btn-padding-y: 0;box-shadow:none}:host ::ng-deep .input-group-text df-bs-button-addon .btn:focus-visible{box-shadow:none;outline:2px solid currentColor;outline-offset:2px}\n"] }]
746
+ }], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], fieldInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldInputs", required: false }] }] } });
723
747
 
724
748
  var bsInput_component = /*#__PURE__*/Object.freeze({
725
749
  __proto__: null,
@@ -727,21 +751,9 @@ var bsInput_component = /*#__PURE__*/Object.freeze({
727
751
  });
728
752
 
729
753
  class BsMultiCheckboxFieldComponent {
730
- elementRef = inject((ElementRef));
731
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
732
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
733
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
734
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
735
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
736
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
754
+ ngf = injectNgForgeField();
737
755
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
738
756
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
739
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
740
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
741
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
742
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
743
- showErrors = shouldShowErrors(this.field);
744
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
745
757
  /** Computed map of checked option values for O(1) lookup in template */
746
758
  checkedValuesMap = computed(() => {
747
759
  const map = {};
@@ -751,15 +763,14 @@ class BsMultiCheckboxFieldComponent {
751
763
  return map;
752
764
  }, ...(ngDevMode ? [{ debugName: "checkedValuesMap" }] : /* istanbul ignore next */ []));
753
765
  valueViewModel = linkedSignal(() => {
754
- const currentValues = this.field()().value();
766
+ const currentValues = this.ngf.field()().value();
755
767
  return this.options().filter((option) => currentValues.includes(option.value));
756
768
  }, { ...(ngDevMode ? { debugName: "valueViewModel" } : /* istanbul ignore next */ {}), equal: isEqual });
757
769
  constructor() {
758
- setupMetaTracking(this.elementRef, this.meta, { selector: 'input[type="checkbox"]', dependents: [this.options] });
759
770
  explicitEffect([this.valueViewModel], ([selectedOptions]) => {
760
771
  const selectedValues = selectedOptions.map((option) => option.value);
761
- if (!isEqual(selectedValues, this.field()().value())) {
762
- this.field()().value.set(selectedValues);
772
+ if (!isEqual(selectedValues, this.ngf.field()().value())) {
773
+ this.ngf.field()().value.set(selectedValues);
763
774
  }
764
775
  });
765
776
  explicitEffect([this.options], ([options]) => {
@@ -784,24 +795,11 @@ class BsMultiCheckboxFieldComponent {
784
795
  }
785
796
  });
786
797
  }
787
- // ─────────────────────────────────────────────────────────────────────────────
788
- // Accessibility
789
- // ─────────────────────────────────────────────────────────────────────────────
790
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
791
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
792
- ariaInvalid = computed(() => {
793
- const fieldState = this.field()();
794
- return fieldState.invalid() && fieldState.touched();
795
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
796
- ariaRequired = computed(() => {
797
- return this.field()().required?.() === true ? true : null;
798
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
799
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
800
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsMultiCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
801
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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: `
802
- @let f = field();
798
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsMultiCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
799
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsMultiCheckboxFieldComponent, isStandalone: true, selector: "df-bs-multi-checkbox", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
800
+ @let f = ngf.field();
803
801
  @let checked = checkedValuesMap();
804
- @if (label(); as label) {
802
+ @if (ngf.label(); as label) {
805
803
  <div class="form-label">{{ label | dynamicText | async }}</div>
806
804
  }
807
805
 
@@ -814,38 +812,36 @@ class BsMultiCheckboxFieldComponent {
814
812
  [class.form-check-reverse]="props()?.reverse"
815
813
  >
816
814
  <input
815
+ ngForgeControl
817
816
  type="checkbox"
818
- [id]="key() + '_' + i"
817
+ [id]="ngf.key() + '_' + i"
819
818
  [checked]="checked['' + option.value]"
820
819
  [disabled]="f().disabled() || option.disabled"
821
820
  (change)="onCheckboxChange(option, $event)"
822
821
  class="form-check-input"
823
822
  [class.is-invalid]="f().invalid() && f().touched()"
824
- [attr.tabindex]="tabIndex()"
825
- [attr.aria-invalid]="ariaInvalid()"
826
- [attr.aria-required]="ariaRequired()"
827
- [attr.aria-describedby]="ariaDescribedBy()"
823
+ [attr.tabindex]="ngf.tabIndex()"
828
824
  />
829
- <label [for]="key() + '_' + i" class="form-check-label">
825
+ <label [for]="ngf.key() + '_' + i" class="form-check-label">
830
826
  {{ option.label | dynamicText | async }}
831
827
  </label>
832
828
  </div>
833
829
  }
834
830
  </div>
835
831
 
836
- @if (errorsToDisplay()[0]; as error) {
837
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
832
+ @if (ngf.errorsToDisplay()[0]; as error) {
833
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
838
834
  } @else if (props()?.hint; as hint) {
839
- <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
835
+ <div class="form-text" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</div>
840
836
  }
841
- `, 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 });
837
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
842
838
  }
843
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsMultiCheckboxFieldComponent, decorators: [{
839
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsMultiCheckboxFieldComponent, decorators: [{
844
840
  type: Component,
845
- args: [{ selector: 'df-bs-multi-checkbox', imports: [DynamicTextPipe, AsyncPipe], template: `
846
- @let f = field();
841
+ args: [{ selector: 'df-bs-multi-checkbox', imports: [DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
842
+ @let f = ngf.field();
847
843
  @let checked = checkedValuesMap();
848
- @if (label(); as label) {
844
+ @if (ngf.label(); as label) {
849
845
  <div class="form-label">{{ label | dynamicText | async }}</div>
850
846
  }
851
847
 
@@ -858,45 +854,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
858
854
  [class.form-check-reverse]="props()?.reverse"
859
855
  >
860
856
  <input
857
+ ngForgeControl
861
858
  type="checkbox"
862
- [id]="key() + '_' + i"
859
+ [id]="ngf.key() + '_' + i"
863
860
  [checked]="checked['' + option.value]"
864
861
  [disabled]="f().disabled() || option.disabled"
865
862
  (change)="onCheckboxChange(option, $event)"
866
863
  class="form-check-input"
867
864
  [class.is-invalid]="f().invalid() && f().touched()"
868
- [attr.tabindex]="tabIndex()"
869
- [attr.aria-invalid]="ariaInvalid()"
870
- [attr.aria-required]="ariaRequired()"
871
- [attr.aria-describedby]="ariaDescribedBy()"
865
+ [attr.tabindex]="ngf.tabIndex()"
872
866
  />
873
- <label [for]="key() + '_' + i" class="form-check-label">
867
+ <label [for]="ngf.key() + '_' + i" class="form-check-label">
874
868
  {{ option.label | dynamicText | async }}
875
869
  </label>
876
870
  </div>
877
871
  }
878
872
  </div>
879
873
 
880
- @if (errorsToDisplay()[0]; as error) {
881
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
874
+ @if (ngf.errorsToDisplay()[0]; as error) {
875
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
882
876
  } @else if (props()?.hint; as hint) {
883
- <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
877
+ <div class="form-text" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</div>
884
878
  }
885
- `, host: {
886
- '[class]': 'className() || ""',
887
- '[id]': '`${key()}`',
888
- '[attr.data-testid]': 'key()',
889
- '[attr.hidden]': 'field()().hidden() || null',
890
- }, changeDetection: ChangeDetectionStrategy.OnPush, 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"] }]
891
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }] } });
879
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n", ":host{display:block}.checkbox-group{margin-bottom:.5rem}:host([hidden]){display:none!important}\n"] }]
880
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
892
881
 
893
882
  var bsMultiCheckbox_component = /*#__PURE__*/Object.freeze({
894
883
  __proto__: null,
895
884
  default: BsMultiCheckboxFieldComponent
896
885
  });
897
886
 
887
+ /**
888
+ * Bootstrap radio group implementing FormValueControl. Rendered inside
889
+ * `df-bs-radio` — each `<input type="radio">` carries `ngForgeControl`, so
890
+ * the marker absorbs meta + aria from the ambient parent NgForgeField.
891
+ */
898
892
  class BsRadioGroupComponent {
899
- elementRef = inject((ElementRef));
900
893
  // Value model - FormField directive binds form value to this
901
894
  value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
902
895
  // Optional FormValueControl properties - Field directive will bind these
@@ -907,12 +900,6 @@ class BsRadioGroupComponent {
907
900
  label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
908
901
  options = input.required(...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
909
902
  properties = input(...(ngDevMode ? [undefined, { debugName: "properties" }] : /* istanbul ignore next */ []));
910
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
911
- // Accessibility - this will be provided by parent component through input
912
- ariaDescribedBy = input(null, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
913
- constructor() {
914
- setupMetaTracking(this.elementRef, this.meta, { selector: 'input[type="radio"]', dependents: [this.options] });
915
- }
916
903
  /**
917
904
  * Handle radio button change event
918
905
  */
@@ -921,20 +908,20 @@ class BsRadioGroupComponent {
921
908
  this.value.set(newValue);
922
909
  }
923
910
  }
924
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsRadioGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
925
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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: `
911
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsRadioGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
912
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", 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 } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
926
913
  @let props = properties();
927
914
  @if (props?.buttonGroup) {
928
915
  <div class="btn-group" role="group" [attr.aria-label]="label() | dynamicText | async">
929
916
  @for (option of options(); track option.value; let i = $index) {
930
917
  <input
918
+ ngForgeControl
931
919
  type="radio"
932
920
  [name]="name()"
933
921
  [value]="option.value"
934
922
  [checked]="value() === option.value"
935
923
  (change)="onRadioChange(option.value)"
936
924
  [disabled]="disabled() || option.disabled || false"
937
- [attr.aria-describedby]="ariaDescribedBy()"
938
925
  class="btn-check"
939
926
  [id]="name() + '_' + i"
940
927
  autocomplete="off"
@@ -953,13 +940,13 @@ class BsRadioGroupComponent {
953
940
  @for (option of options(); track option.value; let i = $index) {
954
941
  <div class="form-check" [class.form-check-inline]="props?.inline" [class.form-check-reverse]="props?.reverse">
955
942
  <input
943
+ ngForgeControl
956
944
  type="radio"
957
945
  [name]="name()"
958
946
  [value]="option.value"
959
947
  [checked]="value() === option.value"
960
948
  (change)="onRadioChange(option.value)"
961
949
  [disabled]="disabled() || option.disabled || false"
962
- [attr.aria-describedby]="ariaDescribedBy()"
963
950
  class="form-check-input"
964
951
  [id]="name() + '_' + i"
965
952
  />
@@ -969,23 +956,23 @@ class BsRadioGroupComponent {
969
956
  </div>
970
957
  }
971
958
  }
972
- `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
959
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
973
960
  }
974
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsRadioGroupComponent, decorators: [{
961
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsRadioGroupComponent, decorators: [{
975
962
  type: Component,
976
- args: [{ selector: 'df-bs-radio-group', imports: [DynamicTextPipe, AsyncPipe], template: `
963
+ args: [{ selector: 'df-bs-radio-group', imports: [DynamicTextPipe, AsyncPipe, NgForgeControl], template: `
977
964
  @let props = properties();
978
965
  @if (props?.buttonGroup) {
979
966
  <div class="btn-group" role="group" [attr.aria-label]="label() | dynamicText | async">
980
967
  @for (option of options(); track option.value; let i = $index) {
981
968
  <input
969
+ ngForgeControl
982
970
  type="radio"
983
971
  [name]="name()"
984
972
  [value]="option.value"
985
973
  [checked]="value() === option.value"
986
974
  (change)="onRadioChange(option.value)"
987
975
  [disabled]="disabled() || option.disabled || false"
988
- [attr.aria-describedby]="ariaDescribedBy()"
989
976
  class="btn-check"
990
977
  [id]="name() + '_' + i"
991
978
  autocomplete="off"
@@ -1004,13 +991,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1004
991
  @for (option of options(); track option.value; let i = $index) {
1005
992
  <div class="form-check" [class.form-check-inline]="props?.inline" [class.form-check-reverse]="props?.reverse">
1006
993
  <input
994
+ ngForgeControl
1007
995
  type="radio"
1008
996
  [name]="name()"
1009
997
  [value]="option.value"
1010
998
  [checked]="value() === option.value"
1011
999
  (change)="onRadioChange(option.value)"
1012
1000
  [disabled]="disabled() || option.disabled || false"
1013
- [attr.aria-describedby]="ariaDescribedBy()"
1014
1001
  class="form-check-input"
1015
1002
  [id]="name() + '_' + i"
1016
1003
  />
@@ -1021,99 +1008,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1021
1008
  }
1022
1009
  }
1023
1010
  `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}\n"] }]
1024
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }] } });
1011
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }] } });
1025
1012
 
1026
1013
  class BsRadioFieldComponent {
1027
- elementRef = inject((ElementRef));
1028
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
1029
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
1030
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
1031
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
1032
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1033
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1014
+ ngf = injectNgForgeField();
1034
1015
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
1035
1016
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
1036
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
1037
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
1038
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
1039
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1040
- showErrors = shouldShowErrors(this.field);
1041
- constructor() {
1042
- setupMetaTracking(this.elementRef, this.meta, {
1043
- selector: 'input[type="radio"]',
1044
- dependents: [this.options],
1045
- });
1046
- }
1047
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
1048
- // ─────────────────────────────────────────────────────────────────────────────
1049
- // Accessibility
1050
- // ─────────────────────────────────────────────────────────────────────────────
1051
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
1052
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
1053
- ariaInvalid = computed(() => {
1054
- const fieldState = this.field()();
1055
- return fieldState.invalid() && fieldState.touched();
1056
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
1057
- ariaRequired = computed(() => {
1058
- return this.field()().required?.() === true ? true : null;
1059
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
1060
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1061
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsRadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1062
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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: `
1063
- @let f = field();
1017
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsRadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1018
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsRadioFieldComponent, isStandalone: true, selector: "df-bs-radio", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
1019
+ @let f = ngf.field();
1064
1020
 
1065
1021
  <div class="mb-3">
1066
- @if (label(); as label) {
1022
+ @if (ngf.label(); as label) {
1067
1023
  <div class="form-label">{{ label | dynamicText | async }}</div>
1068
1024
  }
1069
1025
 
1070
- <df-bs-radio-group
1071
- [formField]="f"
1072
- [label]="label()"
1073
- [options]="options()"
1074
- [properties]="props()"
1075
- [ariaDescribedBy]="ariaDescribedBy()"
1076
- />
1026
+ <df-bs-radio-group [formField]="f" [label]="ngf.label()" [options]="options()" [properties]="props()" />
1077
1027
 
1078
- @if (errorsToDisplay()[0]; as error) {
1079
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1028
+ @if (ngf.errorsToDisplay()[0]; as error) {
1029
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1080
1030
  } @else if (props()?.hint; as hint) {
1081
- <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1031
+ <div class="form-text" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</div>
1082
1032
  }
1083
1033
  </div>
1084
- `, 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"], exportAs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1034
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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"], outputs: ["valueChange"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1085
1035
  }
1086
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsRadioFieldComponent, decorators: [{
1036
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsRadioFieldComponent, decorators: [{
1087
1037
  type: Component,
1088
- args: [{ selector: 'df-bs-radio', imports: [BsRadioGroupComponent, FormField, DynamicTextPipe, AsyncPipe], template: `
1089
- @let f = field();
1038
+ args: [{ selector: 'df-bs-radio', imports: [BsRadioGroupComponent, FormField, DynamicTextPipe, AsyncPipe], hostDirectives: [NgForgeFieldHost], template: `
1039
+ @let f = ngf.field();
1090
1040
 
1091
1041
  <div class="mb-3">
1092
- @if (label(); as label) {
1042
+ @if (ngf.label(); as label) {
1093
1043
  <div class="form-label">{{ label | dynamicText | async }}</div>
1094
1044
  }
1095
1045
 
1096
- <df-bs-radio-group
1097
- [formField]="f"
1098
- [label]="label()"
1099
- [options]="options()"
1100
- [properties]="props()"
1101
- [ariaDescribedBy]="ariaDescribedBy()"
1102
- />
1046
+ <df-bs-radio-group [formField]="f" [label]="ngf.label()" [options]="options()" [properties]="props()" />
1103
1047
 
1104
- @if (errorsToDisplay()[0]; as error) {
1105
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1048
+ @if (ngf.errorsToDisplay()[0]; as error) {
1049
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1106
1050
  } @else if (props()?.hint; as hint) {
1107
- <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1051
+ <div class="form-text" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</div>
1108
1052
  }
1109
1053
  </div>
1110
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
1111
- '[id]': '`${key()}`',
1112
- '[attr.data-testid]': 'key()',
1113
- '[class]': 'className()',
1114
- '[attr.hidden]': 'field()().hidden() || null',
1115
- }, 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"] }]
1116
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
1054
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"] }]
1055
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1117
1056
 
1118
1057
  var bsRadio_component = /*#__PURE__*/Object.freeze({
1119
1058
  __proto__: null,
@@ -1121,24 +1060,9 @@ var bsRadio_component = /*#__PURE__*/Object.freeze({
1121
1060
  });
1122
1061
 
1123
1062
  class BsSelectFieldComponent {
1124
- elementRef = inject((ElementRef));
1125
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
1126
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
1127
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
1128
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
1129
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1130
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1063
+ ngf = injectNgForgeField();
1131
1064
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
1132
1065
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
1133
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
1134
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
1135
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
1136
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1137
- showErrors = shouldShowErrors(this.field);
1138
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
1139
- constructor() {
1140
- setupMetaTracking(this.elementRef, this.meta, { selector: 'select' });
1141
- }
1142
1066
  defaultCompare = Object.is;
1143
1067
  isSelected(optionValue, fieldValue) {
1144
1068
  const compareWith = this.props()?.compareWith || this.defaultCompare;
@@ -1147,41 +1071,27 @@ class BsSelectFieldComponent {
1147
1071
  }
1148
1072
  return fieldValue !== null && compareWith(fieldValue, optionValue);
1149
1073
  }
1150
- // ─────────────────────────────────────────────────────────────────────────────
1151
- // Accessibility
1152
- // ─────────────────────────────────────────────────────────────────────────────
1153
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
1154
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
1155
- ariaInvalid = computed(() => {
1156
- const fieldState = this.field()();
1157
- return fieldState.invalid() && fieldState.touched();
1158
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
1159
- ariaRequired = computed(() => {
1160
- return this.field()().required?.() === true ? true : null;
1161
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
1162
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1163
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1164
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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: `
1165
- @let f = field(); @let selectId = key() + '-select';
1074
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1075
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsSelectFieldComponent, isStandalone: true, selector: "df-bs-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
1076
+ @let f = ngf.field(); @let selectId = ngf.key() + '-select';
1166
1077
 
1167
1078
  <div class="mb-3">
1168
- @if (label(); as label) {
1079
+ @if (ngf.label(); as label) {
1169
1080
  <label [for]="selectId" class="form-label">{{ label | dynamicText | async }}</label>
1170
1081
  }
1171
1082
  <select
1083
+ ngForgeControl
1172
1084
  [formField]="f"
1173
1085
  [id]="selectId"
1086
+ [attr.tabindex]="ngf.tabIndex()"
1174
1087
  class="form-select"
1175
1088
  [class.form-select-sm]="props()?.size === 'sm'"
1176
1089
  [class.form-select-lg]="props()?.size === 'lg'"
1177
1090
  [class.is-invalid]="f().invalid() && f().touched()"
1178
1091
  [multiple]="props()?.multiple || false"
1179
1092
  [size]="props()?.htmlSize"
1180
- [attr.aria-invalid]="ariaInvalid()"
1181
- [attr.aria-required]="ariaRequired()"
1182
- [attr.aria-describedby]="ariaDescribedBy()"
1183
1093
  >
1184
- @if (placeholder(); as placeholder) {
1094
+ @if (ngf.placeholder(); as placeholder) {
1185
1095
  <option value="" disabled [selected]="!f().value()">{{ placeholder | dynamicText | async }}</option>
1186
1096
  }
1187
1097
  @for (option of options(); track option.value) {
@@ -1191,37 +1101,36 @@ class BsSelectFieldComponent {
1191
1101
  }
1192
1102
  </select>
1193
1103
 
1194
- @if (errorsToDisplay()[0]; as error) {
1195
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1104
+ @if (ngf.errorsToDisplay()[0]; as error) {
1105
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1196
1106
  } @else if (props()?.hint; as hint) {
1197
- <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1107
+ <div class="form-text" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</div>
1198
1108
  }
1199
1109
  </div>
1200
- `, 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"], exportAs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1110
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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"], exportAs: ["formField"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1201
1111
  }
1202
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsSelectFieldComponent, decorators: [{
1112
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsSelectFieldComponent, decorators: [{
1203
1113
  type: Component,
1204
- args: [{ selector: 'df-bs-select', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
1205
- @let f = field(); @let selectId = key() + '-select';
1114
+ args: [{ selector: 'df-bs-select', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
1115
+ @let f = ngf.field(); @let selectId = ngf.key() + '-select';
1206
1116
 
1207
1117
  <div class="mb-3">
1208
- @if (label(); as label) {
1118
+ @if (ngf.label(); as label) {
1209
1119
  <label [for]="selectId" class="form-label">{{ label | dynamicText | async }}</label>
1210
1120
  }
1211
1121
  <select
1122
+ ngForgeControl
1212
1123
  [formField]="f"
1213
1124
  [id]="selectId"
1125
+ [attr.tabindex]="ngf.tabIndex()"
1214
1126
  class="form-select"
1215
1127
  [class.form-select-sm]="props()?.size === 'sm'"
1216
1128
  [class.form-select-lg]="props()?.size === 'lg'"
1217
1129
  [class.is-invalid]="f().invalid() && f().touched()"
1218
1130
  [multiple]="props()?.multiple || false"
1219
1131
  [size]="props()?.htmlSize"
1220
- [attr.aria-invalid]="ariaInvalid()"
1221
- [attr.aria-required]="ariaRequired()"
1222
- [attr.aria-describedby]="ariaDescribedBy()"
1223
1132
  >
1224
- @if (placeholder(); as placeholder) {
1133
+ @if (ngf.placeholder(); as placeholder) {
1225
1134
  <option value="" disabled [selected]="!f().value()">{{ placeholder | dynamicText | async }}</option>
1226
1135
  }
1227
1136
  @for (option of options(); track option.value) {
@@ -1231,19 +1140,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1231
1140
  }
1232
1141
  </select>
1233
1142
 
1234
- @if (errorsToDisplay()[0]; as error) {
1235
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1143
+ @if (ngf.errorsToDisplay()[0]; as error) {
1144
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1236
1145
  } @else if (props()?.hint; as hint) {
1237
- <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1146
+ <div class="form-text" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</div>
1238
1147
  }
1239
1148
  </div>
1240
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
1241
- '[id]': '`${key()}`',
1242
- '[attr.data-testid]': 'key()',
1243
- '[class]': 'className()',
1244
- '[attr.hidden]': 'field()().hidden() || null',
1245
- }, 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"] }]
1246
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }] } });
1149
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"] }]
1150
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1247
1151
 
1248
1152
  var bsSelect_component = /*#__PURE__*/Object.freeze({
1249
1153
  __proto__: null,
@@ -1251,45 +1155,17 @@ var bsSelect_component = /*#__PURE__*/Object.freeze({
1251
1155
  });
1252
1156
 
1253
1157
  class BsSliderFieldComponent {
1254
- elementRef = inject((ElementRef));
1255
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
1256
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
1257
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
1258
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
1259
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1260
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1158
+ ngf = injectNgForgeField();
1261
1159
  min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
1262
1160
  max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
1263
1161
  step = input(...(ngDevMode ? [undefined, { debugName: "step" }] : /* istanbul ignore next */ []));
1264
1162
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
1265
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
1266
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
1267
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
1268
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1269
- showErrors = shouldShowErrors(this.field);
1270
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
1271
- constructor() {
1272
- setupMetaTracking(this.elementRef, this.meta, { selector: 'input' });
1273
- }
1274
- // ─────────────────────────────────────────────────────────────────────────────
1275
- // Accessibility
1276
- // ─────────────────────────────────────────────────────────────────────────────
1277
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
1278
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
1279
- ariaInvalid = computed(() => {
1280
- const fieldState = this.field()();
1281
- return fieldState.invalid() && fieldState.touched();
1282
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
1283
- ariaRequired = computed(() => {
1284
- return this.field()().required?.() === true ? true : null;
1285
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
1286
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1287
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsSliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1288
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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: `
1289
- @let f = field(); @let inputId = key() + '-input';
1163
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsSliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1164
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsSliderFieldComponent, isStandalone: true, selector: "df-bs-slider", inputs: { 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 } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
1165
+ @let f = ngf.field(); @let inputId = ngf.key() + '-input';
1290
1166
 
1291
1167
  <div class="mb-3">
1292
- @if (label(); as label) {
1168
+ @if (ngf.label(); as label) {
1293
1169
  <label [for]="inputId" class="form-label">
1294
1170
  {{ label | dynamicText | async }}
1295
1171
  @if (props()?.showValue) {
@@ -1299,6 +1175,7 @@ class BsSliderFieldComponent {
1299
1175
  }
1300
1176
 
1301
1177
  <input
1178
+ ngForgeControl
1302
1179
  type="range"
1303
1180
  dfBsInputConstraints
1304
1181
  [formField]="f"
@@ -1306,28 +1183,25 @@ class BsSliderFieldComponent {
1306
1183
  [dfMin]="f().min?.() ?? props()?.min ?? min()"
1307
1184
  [dfMax]="f().max?.() ?? props()?.max ?? max()"
1308
1185
  [dfStep]="step() ?? props()?.step ?? 1"
1309
- [attr.tabindex]="tabIndex()"
1310
- [attr.aria-invalid]="ariaInvalid()"
1311
- [attr.aria-required]="ariaRequired()"
1312
- [attr.aria-describedby]="ariaDescribedBy()"
1186
+ [attr.tabindex]="ngf.tabIndex()"
1313
1187
  class="form-range"
1314
1188
  />
1315
1189
 
1316
- @if (errorsToDisplay()[0]; as error) {
1317
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1190
+ @if (ngf.errorsToDisplay()[0]; as error) {
1191
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1318
1192
  } @else if (props()?.hint; as hint) {
1319
- <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1193
+ <div class="form-text" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</div>
1320
1194
  }
1321
1195
  </div>
1322
- `, 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"], exportAs: ["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 });
1196
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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"], exportAs: ["formField"] }, { kind: "directive", type: InputConstraintsDirective, selector: "[dfBsInputConstraints]", inputs: ["dfMin", "dfMax", "dfStep"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1323
1197
  }
1324
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsSliderFieldComponent, decorators: [{
1198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsSliderFieldComponent, decorators: [{
1325
1199
  type: Component,
1326
- args: [{ selector: 'df-bs-slider', imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective], template: `
1327
- @let f = field(); @let inputId = key() + '-input';
1200
+ args: [{ selector: 'df-bs-slider', imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
1201
+ @let f = ngf.field(); @let inputId = ngf.key() + '-input';
1328
1202
 
1329
1203
  <div class="mb-3">
1330
- @if (label(); as label) {
1204
+ @if (ngf.label(); as label) {
1331
1205
  <label [for]="inputId" class="form-label">
1332
1206
  {{ label | dynamicText | async }}
1333
1207
  @if (props()?.showValue) {
@@ -1337,6 +1211,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1337
1211
  }
1338
1212
 
1339
1213
  <input
1214
+ ngForgeControl
1340
1215
  type="range"
1341
1216
  dfBsInputConstraints
1342
1217
  [formField]="f"
@@ -1344,26 +1219,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1344
1219
  [dfMin]="f().min?.() ?? props()?.min ?? min()"
1345
1220
  [dfMax]="f().max?.() ?? props()?.max ?? max()"
1346
1221
  [dfStep]="step() ?? props()?.step ?? 1"
1347
- [attr.tabindex]="tabIndex()"
1348
- [attr.aria-invalid]="ariaInvalid()"
1349
- [attr.aria-required]="ariaRequired()"
1350
- [attr.aria-describedby]="ariaDescribedBy()"
1222
+ [attr.tabindex]="ngf.tabIndex()"
1351
1223
  class="form-range"
1352
1224
  />
1353
1225
 
1354
- @if (errorsToDisplay()[0]; as error) {
1355
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1226
+ @if (ngf.errorsToDisplay()[0]; as error) {
1227
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1356
1228
  } @else if (props()?.hint; as hint) {
1357
- <div class="form-text" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1229
+ <div class="form-text" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</div>
1358
1230
  }
1359
1231
  </div>
1360
- `, host: {
1361
- '[class]': 'className()',
1362
- '[id]': '`${key()}`',
1363
- '[attr.data-testid]': 'key()',
1364
- '[attr.hidden]': 'field()().hidden() || null',
1365
- }, changeDetection: ChangeDetectionStrategy.OnPush, 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"] }]
1366
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }] } });
1232
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"] }]
1233
+ }], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1367
1234
 
1368
1235
  var bsSlider_component = /*#__PURE__*/Object.freeze({
1369
1236
  __proto__: null,
@@ -1371,80 +1238,20 @@ var bsSlider_component = /*#__PURE__*/Object.freeze({
1371
1238
  });
1372
1239
 
1373
1240
  class BsTextareaFieldComponent {
1374
- elementRef = inject((ElementRef));
1375
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
1376
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
1377
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
1378
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
1379
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1380
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1241
+ ngf = injectNgForgeField();
1381
1242
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
1382
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
1383
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
1384
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
1385
- /**
1386
- * Reference to the native textarea element.
1387
- * Used to imperatively sync the readonly attribute since Angular Signal Forms'
1388
- * [field] directive doesn't sync FieldState.readonly() to the DOM.
1389
- */
1390
- textareaRef = viewChild('textareaRef', ...(ngDevMode ? [{ debugName: "textareaRef" }] : /* istanbul ignore next */ []));
1391
- /**
1392
- * Computed signal that extracts the readonly state from the field.
1393
- */
1394
- isReadonly = computed(() => this.field()().readonly(), ...(ngDevMode ? [{ debugName: "isReadonly" }] : /* istanbul ignore next */ []));
1395
- /**
1396
- * Workaround: Angular Signal Forms' [field] directive does NOT sync the readonly
1397
- * attribute to the DOM. This effect imperatively sets/removes the readonly attribute
1398
- * on the native textarea element whenever the readonly state changes.
1399
- */
1400
- syncReadonlyToDom = afterRenderEffect({
1401
- write: () => {
1402
- const textareaRef = this.textareaRef();
1403
- const isReadonly = this.isReadonly();
1404
- if (textareaRef?.nativeElement) {
1405
- if (isReadonly) {
1406
- textareaRef.nativeElement.setAttribute('readonly', '');
1407
- }
1408
- else {
1409
- textareaRef.nativeElement.removeAttribute('readonly');
1410
- }
1411
- }
1412
- },
1413
- });
1414
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1415
- showErrors = shouldShowErrors(this.field);
1416
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
1417
- constructor() {
1418
- setupMetaTracking(this.elementRef, this.meta, { selector: 'textarea' });
1419
- }
1420
- // ─────────────────────────────────────────────────────────────────────────────
1421
- // Accessibility
1422
- // ─────────────────────────────────────────────────────────────────────────────
1423
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
1424
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
1425
- ariaInvalid = computed(() => {
1426
- const fieldState = this.field()();
1427
- return fieldState.invalid() && fieldState.touched();
1428
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
1429
- ariaRequired = computed(() => {
1430
- return this.field()().required?.() === true ? true : null;
1431
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
1432
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1433
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsTextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1434
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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" } }, viewQueries: [{ propertyName: "textareaRef", first: true, predicate: ["textareaRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
1435
- @let f = field(); @let p = props(); @let textareaId = key() + '-textarea';
1243
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsTextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1244
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsTextareaFieldComponent, isStandalone: true, selector: "df-bs-textarea", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
1245
+ @let f = ngf.field(); @let p = props(); @let textareaId = ngf.key() + '-textarea';
1436
1246
  @if (p?.floatingLabel) {
1437
1247
  <!-- Floating label variant -->
1438
1248
  <div class="form-floating mb-3">
1439
1249
  <textarea
1440
- #textareaRef
1250
+ ngForgeControl
1441
1251
  [formField]="f"
1442
1252
  [id]="textareaId"
1443
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1444
- [attr.tabindex]="tabIndex()"
1445
- [attr.aria-invalid]="ariaInvalid()"
1446
- [attr.aria-required]="ariaRequired()"
1447
- [attr.aria-describedby]="ariaDescribedBy()"
1253
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
1254
+ [attr.tabindex]="ngf.tabIndex()"
1448
1255
  class="form-control"
1449
1256
  [class.form-control-sm]="p?.size === 'sm'"
1450
1257
  [class.form-control-lg]="p?.size === 'lg'"
@@ -1452,36 +1259,33 @@ class BsTextareaFieldComponent {
1452
1259
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
1453
1260
  ></textarea>
1454
1261
 
1455
- @if (label()) {
1456
- <label [for]="textareaId">{{ label() | dynamicText | async }}</label>
1262
+ @if (ngf.label()) {
1263
+ <label [for]="textareaId">{{ ngf.label() | dynamicText | async }}</label>
1457
1264
  }
1458
1265
  @if (p?.validFeedback && f().valid() && f().touched()) {
1459
1266
  <div class="valid-feedback d-block">
1460
1267
  {{ p?.validFeedback | dynamicText | async }}
1461
1268
  </div>
1462
1269
  }
1463
- @if (errorsToDisplay()[0]; as error) {
1464
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1270
+ @if (ngf.errorsToDisplay()[0]; as error) {
1271
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1465
1272
  } @else if (p?.hint) {
1466
- <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
1273
+ <div class="form-text" [id]="ngf.hintId()">{{ p?.hint | dynamicText | async }}</div>
1467
1274
  }
1468
1275
  </div>
1469
1276
  } @else {
1470
1277
  <!-- Standard variant -->
1471
1278
  <div class="mb-3">
1472
- @if (label()) {
1473
- <label [for]="textareaId" class="form-label">{{ label() | dynamicText | async }}</label>
1279
+ @if (ngf.label()) {
1280
+ <label [for]="textareaId" class="form-label">{{ ngf.label() | dynamicText | async }}</label>
1474
1281
  }
1475
1282
 
1476
1283
  <textarea
1477
- #textareaRef
1284
+ ngForgeControl
1478
1285
  [formField]="f"
1479
1286
  [id]="textareaId"
1480
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1481
- [attr.tabindex]="tabIndex()"
1482
- [attr.aria-invalid]="ariaInvalid()"
1483
- [attr.aria-required]="ariaRequired()"
1484
- [attr.aria-describedby]="ariaDescribedBy()"
1287
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
1288
+ [attr.tabindex]="ngf.tabIndex()"
1485
1289
  class="form-control"
1486
1290
  [class.form-control-sm]="p?.size === 'sm'"
1487
1291
  [class.form-control-lg]="p?.size === 'lg'"
@@ -1494,31 +1298,28 @@ class BsTextareaFieldComponent {
1494
1298
  {{ p?.validFeedback | dynamicText | async }}
1495
1299
  </div>
1496
1300
  }
1497
- @if (errorsToDisplay()[0]; as error) {
1498
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1301
+ @if (ngf.errorsToDisplay()[0]; as error) {
1302
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1499
1303
  } @else if (p?.hint) {
1500
- <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
1304
+ <div class="form-text" [id]="ngf.hintId()">{{ p?.hint | dynamicText | async }}</div>
1501
1305
  }
1502
1306
  </div>
1503
1307
  }
1504
- `, 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"], exportAs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1308
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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"], exportAs: ["formField"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1505
1309
  }
1506
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsTextareaFieldComponent, decorators: [{
1310
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsTextareaFieldComponent, decorators: [{
1507
1311
  type: Component,
1508
- args: [{ selector: 'df-bs-textarea', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
1509
- @let f = field(); @let p = props(); @let textareaId = key() + '-textarea';
1312
+ args: [{ selector: 'df-bs-textarea', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
1313
+ @let f = ngf.field(); @let p = props(); @let textareaId = ngf.key() + '-textarea';
1510
1314
  @if (p?.floatingLabel) {
1511
1315
  <!-- Floating label variant -->
1512
1316
  <div class="form-floating mb-3">
1513
1317
  <textarea
1514
- #textareaRef
1318
+ ngForgeControl
1515
1319
  [formField]="f"
1516
1320
  [id]="textareaId"
1517
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1518
- [attr.tabindex]="tabIndex()"
1519
- [attr.aria-invalid]="ariaInvalid()"
1520
- [attr.aria-required]="ariaRequired()"
1521
- [attr.aria-describedby]="ariaDescribedBy()"
1321
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
1322
+ [attr.tabindex]="ngf.tabIndex()"
1522
1323
  class="form-control"
1523
1324
  [class.form-control-sm]="p?.size === 'sm'"
1524
1325
  [class.form-control-lg]="p?.size === 'lg'"
@@ -1526,36 +1327,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1526
1327
  [class.is-valid]="f().valid() && f().touched() && p?.validFeedback"
1527
1328
  ></textarea>
1528
1329
 
1529
- @if (label()) {
1530
- <label [for]="textareaId">{{ label() | dynamicText | async }}</label>
1330
+ @if (ngf.label()) {
1331
+ <label [for]="textareaId">{{ ngf.label() | dynamicText | async }}</label>
1531
1332
  }
1532
1333
  @if (p?.validFeedback && f().valid() && f().touched()) {
1533
1334
  <div class="valid-feedback d-block">
1534
1335
  {{ p?.validFeedback | dynamicText | async }}
1535
1336
  </div>
1536
1337
  }
1537
- @if (errorsToDisplay()[0]; as error) {
1538
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1338
+ @if (ngf.errorsToDisplay()[0]; as error) {
1339
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1539
1340
  } @else if (p?.hint) {
1540
- <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
1341
+ <div class="form-text" [id]="ngf.hintId()">{{ p?.hint | dynamicText | async }}</div>
1541
1342
  }
1542
1343
  </div>
1543
1344
  } @else {
1544
1345
  <!-- Standard variant -->
1545
1346
  <div class="mb-3">
1546
- @if (label()) {
1547
- <label [for]="textareaId" class="form-label">{{ label() | dynamicText | async }}</label>
1347
+ @if (ngf.label()) {
1348
+ <label [for]="textareaId" class="form-label">{{ ngf.label() | dynamicText | async }}</label>
1548
1349
  }
1549
1350
 
1550
1351
  <textarea
1551
- #textareaRef
1352
+ ngForgeControl
1552
1353
  [formField]="f"
1553
1354
  [id]="textareaId"
1554
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1555
- [attr.tabindex]="tabIndex()"
1556
- [attr.aria-invalid]="ariaInvalid()"
1557
- [attr.aria-required]="ariaRequired()"
1558
- [attr.aria-describedby]="ariaDescribedBy()"
1355
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
1356
+ [attr.tabindex]="ngf.tabIndex()"
1559
1357
  class="form-control"
1560
1358
  [class.form-control-sm]="p?.size === 'sm'"
1561
1359
  [class.form-control-lg]="p?.size === 'lg'"
@@ -1568,20 +1366,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1568
1366
  {{ p?.validFeedback | dynamicText | async }}
1569
1367
  </div>
1570
1368
  }
1571
- @if (errorsToDisplay()[0]; as error) {
1572
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1369
+ @if (ngf.errorsToDisplay()[0]; as error) {
1370
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1573
1371
  } @else if (p?.hint) {
1574
- <div class="form-text" [id]="hintId()">{{ p?.hint | dynamicText | async }}</div>
1372
+ <div class="form-text" [id]="ngf.hintId()">{{ p?.hint | dynamicText | async }}</div>
1575
1373
  }
1576
1374
  </div>
1577
1375
  }
1578
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
1579
- '[id]': '`${key()}`',
1580
- '[attr.data-testid]': 'key()',
1581
- '[class]': 'className()',
1582
- '[attr.hidden]': 'field()().hidden() || null',
1583
- }, 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"] }]
1584
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], textareaRef: [{ type: i0.ViewChild, args: ['textareaRef', { isSignal: true }] }] } });
1376
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-hint-margin-bottom, .5rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"] }]
1377
+ }], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1585
1378
 
1586
1379
  var bsTextarea_component = /*#__PURE__*/Object.freeze({
1587
1380
  __proto__: null,
@@ -1589,39 +1382,11 @@ var bsTextarea_component = /*#__PURE__*/Object.freeze({
1589
1382
  });
1590
1383
 
1591
1384
  class BsToggleFieldComponent {
1592
- elementRef = inject((ElementRef));
1593
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
1594
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
1595
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
1596
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
1597
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1598
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1385
+ ngf = injectNgForgeField();
1599
1386
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
1600
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
1601
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
1602
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
1603
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1604
- showErrors = shouldShowErrors(this.field);
1605
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
1606
- constructor() {
1607
- setupMetaTracking(this.elementRef, this.meta, { selector: 'input[type="checkbox"]' });
1608
- }
1609
- // ─────────────────────────────────────────────────────────────────────────────
1610
- // Accessibility
1611
- // ─────────────────────────────────────────────────────────────────────────────
1612
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
1613
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
1614
- ariaInvalid = computed(() => {
1615
- const fieldState = this.field()();
1616
- return fieldState.invalid() && fieldState.touched();
1617
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
1618
- ariaRequired = computed(() => {
1619
- return this.field()().required?.() === true ? true : null;
1620
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
1621
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1622
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsToggleFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1623
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.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: `
1624
- @let f = field(); @let inputId = key() + '-input';
1387
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsToggleFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1388
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsToggleFieldComponent, isStandalone: true, selector: "df-bs-toggle", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
1389
+ @let f = ngf.field(); @let inputId = ngf.key() + '-input';
1625
1390
 
1626
1391
  <div
1627
1392
  class="form-check form-switch"
@@ -1632,32 +1397,30 @@ class BsToggleFieldComponent {
1632
1397
  [attr.hidden]="f().hidden() || null"
1633
1398
  >
1634
1399
  <input
1400
+ ngForgeControl
1635
1401
  type="checkbox"
1636
1402
  [formField]="f"
1637
1403
  [id]="inputId"
1638
1404
  class="form-check-input"
1639
1405
  [class.is-invalid]="f().invalid() && f().touched()"
1640
- [attr.tabindex]="tabIndex()"
1641
- [attr.aria-invalid]="ariaInvalid()"
1642
- [attr.aria-required]="ariaRequired()"
1643
- [attr.aria-describedby]="ariaDescribedBy()"
1406
+ [attr.tabindex]="ngf.tabIndex()"
1644
1407
  />
1645
1408
  <label [for]="inputId" class="form-check-label">
1646
- {{ label() | dynamicText | async }}
1409
+ {{ ngf.label() | dynamicText | async }}
1647
1410
  </label>
1648
1411
  </div>
1649
1412
 
1650
- @if (errorsToDisplay()[0]; as error) {
1651
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1413
+ @if (ngf.errorsToDisplay()[0]; as error) {
1414
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1652
1415
  } @else if (props()?.hint; as hint) {
1653
- <div class="form-text" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
1416
+ <div class="form-text" [id]="ngf.hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
1654
1417
  }
1655
- `, 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"], exportAs: ["formField"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1418
+ `, isInline: true, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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"], exportAs: ["formField"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1656
1419
  }
1657
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BsToggleFieldComponent, decorators: [{
1420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsToggleFieldComponent, decorators: [{
1658
1421
  type: Component,
1659
- args: [{ selector: 'df-bs-toggle', imports: [FormField, DynamicTextPipe, AsyncPipe], template: `
1660
- @let f = field(); @let inputId = key() + '-input';
1422
+ args: [{ selector: 'df-bs-toggle', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
1423
+ @let f = ngf.field(); @let inputId = ngf.key() + '-input';
1661
1424
 
1662
1425
  <div
1663
1426
  class="form-check form-switch"
@@ -1668,33 +1431,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1668
1431
  [attr.hidden]="f().hidden() || null"
1669
1432
  >
1670
1433
  <input
1434
+ ngForgeControl
1671
1435
  type="checkbox"
1672
1436
  [formField]="f"
1673
1437
  [id]="inputId"
1674
1438
  class="form-check-input"
1675
1439
  [class.is-invalid]="f().invalid() && f().touched()"
1676
- [attr.tabindex]="tabIndex()"
1677
- [attr.aria-invalid]="ariaInvalid()"
1678
- [attr.aria-required]="ariaRequired()"
1679
- [attr.aria-describedby]="ariaDescribedBy()"
1440
+ [attr.tabindex]="ngf.tabIndex()"
1680
1441
  />
1681
1442
  <label [for]="inputId" class="form-check-label">
1682
- {{ label() | dynamicText | async }}
1443
+ {{ ngf.label() | dynamicText | async }}
1683
1444
  </label>
1684
1445
  </div>
1685
1446
 
1686
- @if (errorsToDisplay()[0]; as error) {
1687
- <div class="invalid-feedback d-block" [id]="errorId()" role="alert">{{ error.message }}</div>
1447
+ @if (ngf.errorsToDisplay()[0]; as error) {
1448
+ <div class="invalid-feedback d-block" [id]="ngf.errorId()" role="alert">{{ error.message }}</div>
1688
1449
  } @else if (props()?.hint; as hint) {
1689
- <div class="form-text" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
1450
+ <div class="form-text" [id]="ngf.hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
1690
1451
  }
1691
- `, host: {
1692
- '[class]': 'className()',
1693
- '[id]': '`${key()}`',
1694
- '[attr.data-testid]': 'key()',
1695
- '[attr.hidden]': 'field()().hidden() || null',
1696
- }, changeDetection: ChangeDetectionStrategy.OnPush, 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"] }]
1697
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }] } });
1452
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-bs-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%;margin-bottom:var(--df-bs-field-margin-bottom, 1rem)}.df-bs-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit);margin-bottom:0}.df-bs-hint{color:var(--df-hint-color, var(--bs-secondary-color, #6c757d));font-size:var(--df-hint-font-size, .875rem);margin-top:var(--df-bs-hint-margin-top, .25rem)}.df-bs-field:has(.df-bs-hint){margin-bottom:var(--df-bs-field-with-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"] }]
1453
+ }], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1698
1454
 
1699
1455
  var bsToggle_component = /*#__PURE__*/Object.freeze({
1700
1456
  __proto__: null,
@@ -1790,8 +1546,12 @@ function buttonFieldMapper(fieldDef) {
1790
1546
  * disabled state resolution based on form validity and options.
1791
1547
  */
1792
1548
 
1549
+ const VALUE_FIELD_TYPES_BASE = {
1550
+ renderReadyWhen: ['field'],
1551
+ };
1793
1552
  const BUTTON_FIELD_TYPES_BASE = {
1794
1553
  renderReadyWhen: [],
1554
+ valueHandling: 'exclude',
1795
1555
  };
1796
1556
  const BOOTSTRAP_FIELD_TYPES = [
1797
1557
  {
@@ -1800,87 +1560,83 @@ const BOOTSTRAP_FIELD_TYPES = [
1800
1560
  mapper: valueFieldMapper,
1801
1561
  propsToMeta: ['type'],
1802
1562
  scope: ['text-input', 'numeric'],
1563
+ addons: {
1564
+ slots: ['prefix', 'suffix'],
1565
+ },
1566
+ ...VALUE_FIELD_TYPES_BASE,
1803
1567
  },
1804
1568
  {
1805
1569
  name: BsField.Select,
1806
1570
  loadComponent: () => Promise.resolve().then(function () { return bsSelect_component; }),
1807
1571
  mapper: optionsFieldMapper,
1808
1572
  scope: 'single-select',
1573
+ ...VALUE_FIELD_TYPES_BASE,
1809
1574
  },
1810
1575
  {
1811
1576
  name: BsField.Checkbox,
1812
1577
  loadComponent: () => Promise.resolve().then(function () { return bsCheckbox_component; }),
1813
1578
  mapper: checkboxFieldMapper,
1814
1579
  scope: 'boolean',
1580
+ ...VALUE_FIELD_TYPES_BASE,
1815
1581
  },
1816
1582
  {
1817
1583
  name: BsField.Button,
1818
1584
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1819
1585
  mapper: buttonFieldMapper,
1820
- valueHandling: 'exclude',
1821
1586
  ...BUTTON_FIELD_TYPES_BASE,
1822
1587
  },
1823
1588
  {
1824
1589
  name: BsField.Submit,
1825
1590
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1826
1591
  mapper: submitButtonFieldMapper,
1827
- valueHandling: 'exclude',
1828
1592
  ...BUTTON_FIELD_TYPES_BASE,
1829
1593
  },
1830
1594
  {
1831
1595
  name: BsField.Next,
1832
1596
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1833
1597
  mapper: nextButtonFieldMapper,
1834
- valueHandling: 'exclude',
1835
1598
  ...BUTTON_FIELD_TYPES_BASE,
1836
1599
  },
1837
1600
  {
1838
1601
  name: BsField.Previous,
1839
1602
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1840
1603
  mapper: previousButtonFieldMapper,
1841
- valueHandling: 'exclude',
1842
1604
  ...BUTTON_FIELD_TYPES_BASE,
1843
1605
  },
1844
1606
  {
1845
1607
  name: BsField.AddArrayItem,
1846
1608
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1847
1609
  mapper: addArrayItemButtonMapper,
1848
- valueHandling: 'exclude',
1849
1610
  ...BUTTON_FIELD_TYPES_BASE,
1850
1611
  },
1851
1612
  {
1852
1613
  name: BsField.PrependArrayItem,
1853
1614
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1854
1615
  mapper: prependArrayItemButtonMapper,
1855
- valueHandling: 'exclude',
1856
1616
  ...BUTTON_FIELD_TYPES_BASE,
1857
1617
  },
1858
1618
  {
1859
1619
  name: BsField.InsertArrayItem,
1860
1620
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1861
1621
  mapper: insertArrayItemButtonMapper,
1862
- valueHandling: 'exclude',
1863
1622
  ...BUTTON_FIELD_TYPES_BASE,
1864
1623
  },
1865
1624
  {
1866
1625
  name: BsField.RemoveArrayItem,
1867
1626
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1868
1627
  mapper: removeArrayItemButtonMapper,
1869
- valueHandling: 'exclude',
1870
1628
  ...BUTTON_FIELD_TYPES_BASE,
1871
1629
  },
1872
1630
  {
1873
1631
  name: BsField.PopArrayItem,
1874
1632
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1875
1633
  mapper: popArrayItemButtonMapper,
1876
- valueHandling: 'exclude',
1877
1634
  ...BUTTON_FIELD_TYPES_BASE,
1878
1635
  },
1879
1636
  {
1880
1637
  name: BsField.ShiftArrayItem,
1881
1638
  loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
1882
1639
  mapper: shiftArrayItemButtonMapper,
1883
- valueHandling: 'exclude',
1884
1640
  ...BUTTON_FIELD_TYPES_BASE,
1885
1641
  },
1886
1642
  {
@@ -1889,36 +1645,42 @@ const BOOTSTRAP_FIELD_TYPES = [
1889
1645
  mapper: valueFieldMapper,
1890
1646
  propsToMeta: ['rows'],
1891
1647
  scope: 'text-input',
1648
+ ...VALUE_FIELD_TYPES_BASE,
1892
1649
  },
1893
1650
  {
1894
1651
  name: BsField.Radio,
1895
1652
  loadComponent: () => Promise.resolve().then(function () { return bsRadio_component; }),
1896
1653
  mapper: optionsFieldMapper,
1897
1654
  scope: 'single-select',
1655
+ ...VALUE_FIELD_TYPES_BASE,
1898
1656
  },
1899
1657
  {
1900
1658
  name: BsField.MultiCheckbox,
1901
1659
  loadComponent: () => Promise.resolve().then(function () { return bsMultiCheckbox_component; }),
1902
1660
  mapper: optionsFieldMapper,
1903
1661
  scope: 'multi-select',
1662
+ ...VALUE_FIELD_TYPES_BASE,
1904
1663
  },
1905
1664
  {
1906
1665
  name: BsField.Datepicker,
1907
1666
  loadComponent: () => Promise.resolve().then(function () { return bsDatepicker_component; }),
1908
1667
  mapper: datepickerFieldMapper,
1909
1668
  scope: 'date',
1669
+ ...VALUE_FIELD_TYPES_BASE,
1910
1670
  },
1911
1671
  {
1912
1672
  name: BsField.Slider,
1913
1673
  loadComponent: () => Promise.resolve().then(function () { return bsSlider_component; }),
1914
1674
  mapper: valueFieldMapper,
1915
1675
  scope: 'numeric',
1676
+ ...VALUE_FIELD_TYPES_BASE,
1916
1677
  },
1917
1678
  {
1918
1679
  name: BsField.Toggle,
1919
1680
  loadComponent: () => Promise.resolve().then(function () { return bsToggle_component; }),
1920
1681
  mapper: checkboxFieldMapper,
1921
1682
  scope: 'boolean',
1683
+ ...VALUE_FIELD_TYPES_BASE,
1922
1684
  },
1923
1685
  ];
1924
1686
 
@@ -1929,19 +1691,187 @@ const BOOTSTRAP_FIELD_TYPES = [
1929
1691
  */
1930
1692
 
1931
1693
  function withBootstrapFields(config) {
1932
- if (!config) {
1933
- return BOOTSTRAP_FIELD_TYPES;
1934
- }
1935
- const fieldsWithConfig = [
1936
- ...BOOTSTRAP_FIELD_TYPES,
1937
- {
1694
+ // Always include the addons feature — bs-icon / bs-button are part of
1695
+ // the canonical Bootstrap surface.
1696
+ const base = [...BOOTSTRAP_FIELD_TYPES, withBootstrapAddons()];
1697
+ if (config) {
1698
+ base.push({
1938
1699
  ɵkind: 'bootstrap-config',
1939
1700
  ɵproviders: [{ provide: BOOTSTRAP_CONFIG, useValue: config }],
1940
- },
1941
- ];
1942
- // Safe: this preserves all bootstrap field definitions and appends exactly one config feature.
1943
- return fieldsWithConfig;
1701
+ });
1702
+ return base;
1703
+ }
1704
+ return base;
1705
+ }
1706
+ /* -- Bootstrap addon kinds --------------------------------------------- */
1707
+ const BS_ICON_KIND = {
1708
+ kind: 'bs-icon',
1709
+ loadComponent: () => Promise.resolve().then(function () { return bsIconAddon_component; }).then((m) => m.BsIconAddonComponent),
1710
+ validate: (addon, fieldKey) => {
1711
+ if (typeof addon.icon !== 'string' || addon.icon.length === 0) {
1712
+ throw new DynamicFormError(`Addon kind 'bs-icon' requires a non-empty 'icon' string (field: '${fieldKey}').`);
1713
+ }
1714
+ },
1715
+ };
1716
+ const BS_BUTTON_KIND = {
1717
+ kind: 'bs-button',
1718
+ loadComponent: () => Promise.resolve().then(function () { return bsButtonAddon_component; }).then((m) => m.BsButtonAddonComponent),
1719
+ validate: (addon, fieldKey) => {
1720
+ // Exactly one of preset / actionRef / action — validator drops the addon
1721
+ // (with warning) if the rule is violated.
1722
+ const set = [addon.preset, addon.actionRef, addon.action].filter((v) => v !== undefined);
1723
+ if (set.length > 1) {
1724
+ throw new DynamicFormError(`Addon kind 'bs-button' on field '${fieldKey}' has more than one of preset/actionRef/action — exactly one allowed.`);
1725
+ }
1726
+ // Icon-only buttons require ariaLabel for screen readers.
1727
+ if (addon.icon && !addon.label && !addon.ariaLabel) {
1728
+ throw new DynamicFormError(`Addon kind 'bs-button' on field '${fieldKey}' is icon-only — provide 'ariaLabel' for accessibility.`);
1729
+ }
1730
+ },
1731
+ };
1732
+ /**
1733
+ * Register Bootstrap-shipped addon kinds (`bs-icon`, `bs-button`) standalone.
1734
+ *
1735
+ * **Most users don't need this** — `withBootstrapFields()` auto-includes
1736
+ * these kinds. Call `withBootstrapAddons()` directly only when you want
1737
+ * Bootstrap addon kinds without the Bootstrap field types (e.g., a custom
1738
+ * field set that wants to render `bs-icon` prefixes), or when you're
1739
+ * stitching addons through a different DI scope.
1740
+ *
1741
+ * @example
1742
+ * ```typescript
1743
+ * // Custom field types + Bootstrap addon kinds.
1744
+ * provideDynamicForm(
1745
+ * ...myCustomFields(),
1746
+ * withBootstrapAddons(),
1747
+ * );
1748
+ * ```
1749
+ *
1750
+ * Adapter authors who need to override a kind with a customised renderer
1751
+ * should call `withCustomAddon(...)` directly instead.
1752
+ */
1753
+ function withBootstrapAddons() {
1754
+ return {
1755
+ ɵkind: 'addons',
1756
+ ɵproviders: [
1757
+ { provide: ADDON_KIND_DEFINITIONS, useValue: BS_ICON_KIND, multi: true },
1758
+ { provide: ADDON_KIND_DEFINITIONS, useValue: BS_BUTTON_KIND, multi: true },
1759
+ ],
1760
+ };
1761
+ }
1762
+
1763
+ /**
1764
+ * Renderer for the `bs-icon` addon kind.
1765
+ *
1766
+ * Outputs `<i class="bi bi-{icon}">`. The host is set `aria-hidden="true"`
1767
+ * by default; if the addon supplies an `ariaLabel`, it is applied so the
1768
+ * icon is announced by screen readers.
1769
+ */
1770
+ class BsIconAddonComponent {
1771
+ addon = input.required(...(ngDevMode ? [{ debugName: "addon" }] : /* istanbul ignore next */ []));
1772
+ /** Accepted for contract uniformity — `NgComponentOutlet` setInput is strict; every kind must declare it. */
1773
+ fieldInputs = input(...(ngDevMode ? [undefined, { debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
1774
+ iconClass = computed(() => `bi bi-${this.addon().icon}`, ...(ngDevMode ? [{ debugName: "iconClass" }] : /* istanbul ignore next */ []));
1775
+ ariaLabel = computed(() => this.addon().ariaLabel, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1776
+ hasAriaLabel = computed(() => this.addon().ariaLabel !== undefined, ...(ngDevMode ? [{ debugName: "hasAriaLabel" }] : /* istanbul ignore next */ []));
1777
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsIconAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1778
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.14", type: BsIconAddonComponent, isStandalone: true, selector: "df-bs-icon-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-hidden": "hasAriaLabel() ? null : \"true\"" } }, ngImport: i0, template: `<i [class]="iconClass()" [attr.aria-label]="(ariaLabel() | dynamicText | async) ?? null"></i>`, isInline: true, dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1779
+ }
1780
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsIconAddonComponent, decorators: [{
1781
+ type: Component,
1782
+ args: [{
1783
+ selector: 'df-bs-icon-addon',
1784
+ imports: [AsyncPipe, DynamicTextPipe],
1785
+ template: `<i [class]="iconClass()" [attr.aria-label]="(ariaLabel() | dynamicText | async) ?? null"></i>`,
1786
+ host: {
1787
+ '[attr.aria-hidden]': 'hasAriaLabel() ? null : "true"',
1788
+ },
1789
+ changeDetection: ChangeDetectionStrategy.OnPush,
1790
+ }]
1791
+ }], propDecorators: { addon: [{ type: i0.Input, args: [{ isSignal: true, alias: "addon", required: true }] }], fieldInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldInputs", required: false }] }] } });
1792
+
1793
+ var bsIconAddon_component = /*#__PURE__*/Object.freeze({
1794
+ __proto__: null,
1795
+ BsIconAddonComponent: BsIconAddonComponent
1796
+ });
1797
+
1798
+ /**
1799
+ * Renderer for the `bs-button` addon kind.
1800
+ *
1801
+ * Renders a Bootstrap `btn-outline-{severity}` button. Click dispatch
1802
+ * (preset / actionRef / action precedence, multi-set warning, `disabled` /
1803
+ * `loading` resolution) lives on `NgForgeAddonAction`; this component
1804
+ * focuses on the visual layer.
1805
+ */
1806
+ class BsButtonAddonComponent {
1807
+ action = injectNgForgeAddonAction();
1808
+ /** Re-exposed for template binding — same signal stored on the directive. */
1809
+ addon = this.action.addon;
1810
+ label = computed(() => this.addon().label, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1811
+ ariaLabel = computed(() => this.addon().ariaLabel, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1812
+ iconClass = computed(() => {
1813
+ const icon = this.addon().icon;
1814
+ return icon ? `bi bi-${icon}` : '';
1815
+ }, ...(ngDevMode ? [{ debugName: "iconClass" }] : /* istanbul ignore next */ []));
1816
+ buttonClass = computed(() => `btn btn-outline-${this.addon().severity ?? 'secondary'}`, ...(ngDevMode ? [{ debugName: "buttonClass" }] : /* istanbul ignore next */ []));
1817
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsButtonAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1818
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: BsButtonAddonComponent, isStandalone: true, selector: "df-bs-button-addon", hostDirectives: [{ directive: i1.NgForgeAddonAction }], ngImport: i0, template: `
1819
+ <button
1820
+ type="button"
1821
+ [class]="buttonClass()"
1822
+ [disabled]="action.disabled() || action.loading()"
1823
+ [attr.aria-label]="(ariaLabel() | dynamicText | async) ?? null"
1824
+ [attr.aria-busy]="action.loading() || null"
1825
+ (click)="action.dispatch()"
1826
+ >
1827
+ @if (action.loading()) {
1828
+ <!-- Visually-hidden role=status text gives a reliable AT announcement (VO/JAWS/NVDA). -->
1829
+ <span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
1830
+ <span class="visually-hidden" role="status">Loading…</span>
1831
+ } @else if (iconClass(); as ic) {
1832
+ <i [class]="ic" aria-hidden="true"></i>
1833
+ }
1834
+ @if (label(); as l) {
1835
+ <span>{{ l | dynamicText | async }}</span>
1836
+ }
1837
+ </button>
1838
+ `, isInline: true, dependencies: [{ kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1944
1839
  }
1840
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: BsButtonAddonComponent, decorators: [{
1841
+ type: Component,
1842
+ args: [{
1843
+ selector: 'df-bs-button-addon',
1844
+ imports: [DynamicTextPipe, AsyncPipe],
1845
+ hostDirectives: [NgForgeAddonAction],
1846
+ template: `
1847
+ <button
1848
+ type="button"
1849
+ [class]="buttonClass()"
1850
+ [disabled]="action.disabled() || action.loading()"
1851
+ [attr.aria-label]="(ariaLabel() | dynamicText | async) ?? null"
1852
+ [attr.aria-busy]="action.loading() || null"
1853
+ (click)="action.dispatch()"
1854
+ >
1855
+ @if (action.loading()) {
1856
+ <!-- Visually-hidden role=status text gives a reliable AT announcement (VO/JAWS/NVDA). -->
1857
+ <span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
1858
+ <span class="visually-hidden" role="status">Loading…</span>
1859
+ } @else if (iconClass(); as ic) {
1860
+ <i [class]="ic" aria-hidden="true"></i>
1861
+ }
1862
+ @if (label(); as l) {
1863
+ <span>{{ l | dynamicText | async }}</span>
1864
+ }
1865
+ </button>
1866
+ `,
1867
+ changeDetection: ChangeDetectionStrategy.OnPush,
1868
+ }]
1869
+ }] });
1870
+
1871
+ var bsButtonAddon_component = /*#__PURE__*/Object.freeze({
1872
+ __proto__: null,
1873
+ BsButtonAddonComponent: BsButtonAddonComponent
1874
+ });
1945
1875
 
1946
1876
  // Field components
1947
1877
 
@@ -1949,5 +1879,5 @@ function withBootstrapFields(config) {
1949
1879
  * Generated bundle index. Do not edit.
1950
1880
  */
1951
1881
 
1952
- export { BOOTSTRAP_CONFIG, BOOTSTRAP_FIELD_TYPES, BsButtonFieldComponent, BsCheckboxFieldComponent, BsDatepickerFieldComponent, BsField, BsInputFieldComponent, BsMultiCheckboxFieldComponent, BsRadioFieldComponent, BsSelectFieldComponent, BsSliderFieldComponent, BsTextareaFieldComponent, BsToggleFieldComponent, withBootstrapFields };
1882
+ export { BOOTSTRAP_CONFIG, BOOTSTRAP_FIELD_TYPES, BS_INPUT_TYPE_OVERRIDE, BsButtonAddonComponent, BsButtonFieldComponent, BsCheckboxFieldComponent, BsDatepickerFieldComponent, BsField, BsIconAddonComponent, BsInputFieldComponent, BsMultiCheckboxFieldComponent, BsRadioFieldComponent, BsSelectFieldComponent, BsSliderFieldComponent, BsTextareaFieldComponent, BsToggleFieldComponent, withBootstrapAddons, withBootstrapFields };
1953
1883
  //# sourceMappingURL=ng-forge-dynamic-forms-bootstrap.mjs.map