@ng-forge/dynamic-forms-bootstrap 1.0.0-rc.4 → 1.0.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.
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ng-forge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -18,19 +18,12 @@ Bootstrap 5 field components for [@ng-forge/dynamic-forms](https://www.npmjs.com
|
|
|
18
18
|
|
|
19
19
|
## Compatibility
|
|
20
20
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
| 0.6.x | 0.6.x | >=21.1.0 |
|
|
28
|
-
| 0.5.x | 0.5.x | >=21.1.0 |
|
|
29
|
-
| 0.4.x | 0.4.x | >=21.1.0 |
|
|
30
|
-
| 0.3.x | 0.3.x | 21.0.7-21.0.x |
|
|
31
|
-
| 0.2.x | 0.2.x | 21.0.6 |
|
|
32
|
-
| 0.1.1+ | 0.1.1+ | 21.0.2-21.0.5 |
|
|
33
|
-
| 0.1.0 | 0.1.0 | 21.0.0-21.0.1 |
|
|
21
|
+
| Angular | @ng-forge/dynamic-forms-bootstrap |
|
|
22
|
+
| ------- | --------------------------------- |
|
|
23
|
+
| 22.x | 1.x |
|
|
24
|
+
| 21.x | 0.x (experimental) |
|
|
25
|
+
|
|
26
|
+
Versioned in lockstep with `@ng-forge/dynamic-forms`. The `0.x` line targets Angular 21 (experimental Signal Forms). Each release pins its Angular requirement via `peerDependencies`; npm warns on a mismatch.
|
|
34
27
|
|
|
35
28
|
## Installation
|
|
36
29
|
|
|
@@ -96,10 +89,11 @@ Input, Select, Checkbox, Toggle, Button, Submit, Next, Previous, Textarea, Radio
|
|
|
96
89
|
|
|
97
90
|
## Documentation
|
|
98
91
|
|
|
99
|
-
- [
|
|
100
|
-
- [
|
|
101
|
-
- [
|
|
102
|
-
- [
|
|
92
|
+
- [Feature overview](https://ng-forge.com/bootstrap/feature-overview)
|
|
93
|
+
- [Bootstrap Integration](https://ng-forge.com/bootstrap)
|
|
94
|
+
- [Field Types](https://ng-forge.com/bootstrap/field-types/text-inputs)
|
|
95
|
+
- [Validation](https://ng-forge.com/bootstrap/validation/basics)
|
|
96
|
+
- [Conditional Logic](https://ng-forge.com/bootstrap/dynamic-behavior/conditional-logic)
|
|
103
97
|
|
|
104
98
|
## Changelog
|
|
105
99
|
|
|
@@ -37,8 +37,8 @@ class BsButtonFieldComponent {
|
|
|
37
37
|
return;
|
|
38
38
|
this.action.dispatch();
|
|
39
39
|
}
|
|
40
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
41
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
40
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsButtonFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
41
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.1", 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: `
|
|
42
42
|
@let buttonId = action.key() + '-button';
|
|
43
43
|
<button
|
|
44
44
|
[id]="buttonId"
|
|
@@ -53,7 +53,7 @@ class BsButtonFieldComponent {
|
|
|
53
53
|
</button>
|
|
54
54
|
`, 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 });
|
|
55
55
|
}
|
|
56
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
56
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsButtonFieldComponent, decorators: [{
|
|
57
57
|
type: Component,
|
|
58
58
|
args: [{ selector: 'df-bs-button', imports: [DynamicTextPipe, AsyncPipe], hostDirectives: [NgForgeActionHost], template: `
|
|
59
59
|
@let buttonId = action.key() + '-button';
|
|
@@ -81,8 +81,8 @@ class BsCheckboxFieldComponent {
|
|
|
81
81
|
ngf = injectNgForgeField();
|
|
82
82
|
props = input(/* @ts-ignore */
|
|
83
83
|
...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
|
|
84
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
85
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
84
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
85
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: `
|
|
86
86
|
@let f = ngf.field(); @let checkboxId = ngf.key() + '-checkbox';
|
|
87
87
|
|
|
88
88
|
<div
|
|
@@ -114,7 +114,7 @@ class BsCheckboxFieldComponent {
|
|
|
114
114
|
}
|
|
115
115
|
`, 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 });
|
|
116
116
|
}
|
|
117
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
117
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsCheckboxFieldComponent, decorators: [{
|
|
118
118
|
type: Component,
|
|
119
119
|
args: [{ selector: 'df-bs-checkbox', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
|
|
120
120
|
@let f = ngf.field(); @let checkboxId = ngf.key() + '-checkbox';
|
|
@@ -189,10 +189,10 @@ class InputConstraintsDirective {
|
|
|
189
189
|
nativeElement.removeAttribute('step');
|
|
190
190
|
}
|
|
191
191
|
});
|
|
192
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
193
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.
|
|
192
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: InputConstraintsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
193
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", 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 });
|
|
194
194
|
}
|
|
195
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
195
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: InputConstraintsDirective, decorators: [{
|
|
196
196
|
type: Directive,
|
|
197
197
|
args: [{
|
|
198
198
|
selector: '[dfBsInputConstraints]',
|
|
@@ -219,8 +219,8 @@ class BsDatepickerFieldComponent {
|
|
|
219
219
|
return max instanceof Date ? max.toISOString().split('T')[0] : max;
|
|
220
220
|
}, /* @ts-ignore */
|
|
221
221
|
...(ngDevMode ? [{ debugName: "maxAsString" }] : /* istanbul ignore next */ []));
|
|
222
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
223
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
222
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsDatepickerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
223
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: `
|
|
224
224
|
@let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';
|
|
225
225
|
@if (p?.floatingLabel) {
|
|
226
226
|
<!-- Floating label variant -->
|
|
@@ -291,7 +291,7 @@ class BsDatepickerFieldComponent {
|
|
|
291
291
|
}
|
|
292
292
|
`, 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 });
|
|
293
293
|
}
|
|
294
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
294
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsDatepickerFieldComponent, decorators: [{
|
|
295
295
|
type: Component,
|
|
296
296
|
args: [{ selector: 'df-bs-datepicker', imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
|
|
297
297
|
@let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';
|
|
@@ -393,8 +393,8 @@ class BsInputFieldComponent {
|
|
|
393
393
|
...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
|
|
394
394
|
type = computed(() => this.typeOverride() ?? this.props()?.type ?? 'text', /* @ts-ignore */
|
|
395
395
|
...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
396
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
397
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
396
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsInputFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
397
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: [
|
|
398
398
|
{
|
|
399
399
|
provide: BS_INPUT_TYPE_OVERRIDE,
|
|
400
400
|
useFactory: () => signal(undefined),
|
|
@@ -548,7 +548,7 @@ class BsInputFieldComponent {
|
|
|
548
548
|
}
|
|
549
549
|
`, 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 });
|
|
550
550
|
}
|
|
551
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
551
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsInputFieldComponent, decorators: [{
|
|
552
552
|
type: Component,
|
|
553
553
|
args: [{ selector: 'df-bs-input', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl, DfAddonSlot], hostDirectives: [NgForgeFieldHost, NgForgeAddons], template: `
|
|
554
554
|
@let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';
|
|
@@ -757,8 +757,8 @@ class BsMultiCheckboxFieldComponent {
|
|
|
757
757
|
}
|
|
758
758
|
});
|
|
759
759
|
}
|
|
760
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
761
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
760
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsMultiCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
761
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: `
|
|
762
762
|
@let f = ngf.field();
|
|
763
763
|
@let checked = checkedValuesMap();
|
|
764
764
|
@if (ngf.label(); as label) {
|
|
@@ -798,7 +798,7 @@ class BsMultiCheckboxFieldComponent {
|
|
|
798
798
|
}
|
|
799
799
|
`, 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 });
|
|
800
800
|
}
|
|
801
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
801
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsMultiCheckboxFieldComponent, decorators: [{
|
|
802
802
|
type: Component,
|
|
803
803
|
args: [{ selector: 'df-bs-multi-checkbox', imports: [DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
|
|
804
804
|
@let f = ngf.field();
|
|
@@ -867,8 +867,8 @@ class BsRadioGroupComponent {
|
|
|
867
867
|
this.value.set(newValue);
|
|
868
868
|
}
|
|
869
869
|
}
|
|
870
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
871
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
870
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsRadioGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
871
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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" }, host: { attributes: { "role": "radiogroup" }, properties: { "attr.aria-invalid": "parentField?.ariaInvalid() || null", "attr.aria-required": "parentField?.ariaRequired()", "attr.aria-describedby": "parentField?.ariaDescribedBy()" } }, ngImport: i0, template: `
|
|
872
872
|
@let props = properties();
|
|
873
873
|
@if (props?.buttonGroup) {
|
|
874
874
|
<div class="btn-group" role="group" [attr.aria-label]="label() | dynamicText | async">
|
|
@@ -917,7 +917,7 @@ class BsRadioGroupComponent {
|
|
|
917
917
|
}
|
|
918
918
|
`, 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 });
|
|
919
919
|
}
|
|
920
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
920
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsRadioGroupComponent, decorators: [{
|
|
921
921
|
type: Component,
|
|
922
922
|
args: [{ selector: 'df-bs-radio-group', imports: [DynamicTextPipe, AsyncPipe, NgForgeControl], host: {
|
|
923
923
|
role: 'radiogroup',
|
|
@@ -980,8 +980,8 @@ class BsRadioFieldComponent {
|
|
|
980
980
|
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
981
981
|
props = input(/* @ts-ignore */
|
|
982
982
|
...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
|
|
983
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
984
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
983
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsRadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
984
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: `
|
|
985
985
|
@let f = ngf.field();
|
|
986
986
|
|
|
987
987
|
<div class="mb-3">
|
|
@@ -999,7 +999,7 @@ class BsRadioFieldComponent {
|
|
|
999
999
|
</div>
|
|
1000
1000
|
`, 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 });
|
|
1001
1001
|
}
|
|
1002
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
1002
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsRadioFieldComponent, decorators: [{
|
|
1003
1003
|
type: Component,
|
|
1004
1004
|
args: [{ selector: 'df-bs-radio', imports: [BsRadioGroupComponent, FormField, DynamicTextPipe, AsyncPipe], hostDirectives: [NgForgeFieldHost], template: `
|
|
1005
1005
|
@let f = ngf.field();
|
|
@@ -1039,8 +1039,8 @@ class BsSelectFieldComponent {
|
|
|
1039
1039
|
}
|
|
1040
1040
|
return fieldValue !== null && compareWith(fieldValue, optionValue);
|
|
1041
1041
|
}
|
|
1042
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
1043
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
1042
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1043
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: `
|
|
1044
1044
|
@let f = ngf.field(); @let selectId = ngf.key() + '-select';
|
|
1045
1045
|
|
|
1046
1046
|
<div class="mb-3">
|
|
@@ -1077,7 +1077,7 @@ class BsSelectFieldComponent {
|
|
|
1077
1077
|
</div>
|
|
1078
1078
|
`, 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 });
|
|
1079
1079
|
}
|
|
1080
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
1080
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsSelectFieldComponent, decorators: [{
|
|
1081
1081
|
type: Component,
|
|
1082
1082
|
args: [{ selector: 'df-bs-select', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
|
|
1083
1083
|
@let f = ngf.field(); @let selectId = ngf.key() + '-select';
|
|
@@ -1132,8 +1132,8 @@ class BsSliderFieldComponent {
|
|
|
1132
1132
|
...(ngDevMode ? [undefined, { debugName: "step" }] : /* istanbul ignore next */ []));
|
|
1133
1133
|
props = input(/* @ts-ignore */
|
|
1134
1134
|
...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
|
|
1135
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
1136
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
1135
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsSliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1136
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: `
|
|
1137
1137
|
@let f = ngf.field(); @let inputId = ngf.key() + '-input';
|
|
1138
1138
|
|
|
1139
1139
|
<div class="mb-3">
|
|
@@ -1167,7 +1167,7 @@ class BsSliderFieldComponent {
|
|
|
1167
1167
|
</div>
|
|
1168
1168
|
`, 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 });
|
|
1169
1169
|
}
|
|
1170
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
1170
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsSliderFieldComponent, decorators: [{
|
|
1171
1171
|
type: Component,
|
|
1172
1172
|
args: [{ selector: 'df-bs-slider', imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
|
|
1173
1173
|
@let f = ngf.field(); @let inputId = ngf.key() + '-input';
|
|
@@ -1213,8 +1213,8 @@ class BsTextareaFieldComponent {
|
|
|
1213
1213
|
ngf = injectNgForgeField();
|
|
1214
1214
|
props = input(/* @ts-ignore */
|
|
1215
1215
|
...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
|
|
1216
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
1217
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
1216
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsTextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1217
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: `
|
|
1218
1218
|
@let f = ngf.field(); @let p = props(); @let textareaId = ngf.key() + '-textarea';
|
|
1219
1219
|
@if (p?.floatingLabel) {
|
|
1220
1220
|
<!-- Floating label variant -->
|
|
@@ -1280,7 +1280,7 @@ class BsTextareaFieldComponent {
|
|
|
1280
1280
|
}
|
|
1281
1281
|
`, 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 });
|
|
1282
1282
|
}
|
|
1283
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
1283
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsTextareaFieldComponent, decorators: [{
|
|
1284
1284
|
type: Component,
|
|
1285
1285
|
args: [{ selector: 'df-bs-textarea', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
|
|
1286
1286
|
@let f = ngf.field(); @let p = props(); @let textareaId = ngf.key() + '-textarea';
|
|
@@ -1358,8 +1358,8 @@ class BsToggleFieldComponent {
|
|
|
1358
1358
|
ngf = injectNgForgeField();
|
|
1359
1359
|
props = input(/* @ts-ignore */
|
|
1360
1360
|
...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
|
|
1361
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
1362
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
1361
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsToggleFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1362
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: `
|
|
1363
1363
|
@let f = ngf.field(); @let inputId = ngf.key() + '-input';
|
|
1364
1364
|
|
|
1365
1365
|
<div
|
|
@@ -1391,7 +1391,7 @@ class BsToggleFieldComponent {
|
|
|
1391
1391
|
}
|
|
1392
1392
|
`, 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 });
|
|
1393
1393
|
}
|
|
1394
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
1394
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsToggleFieldComponent, decorators: [{
|
|
1395
1395
|
type: Component,
|
|
1396
1396
|
args: [{ selector: 'df-bs-toggle', imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
|
|
1397
1397
|
@let f = ngf.field(); @let inputId = ngf.key() + '-input';
|
|
@@ -1440,12 +1440,12 @@ const BsField = {
|
|
|
1440
1440
|
Submit: 'submit',
|
|
1441
1441
|
Next: 'next',
|
|
1442
1442
|
Previous: 'previous',
|
|
1443
|
-
AddArrayItem: '
|
|
1444
|
-
PrependArrayItem: '
|
|
1445
|
-
InsertArrayItem: '
|
|
1446
|
-
RemoveArrayItem: '
|
|
1447
|
-
PopArrayItem: '
|
|
1448
|
-
ShiftArrayItem: '
|
|
1443
|
+
AddArrayItem: 'add-array-item',
|
|
1444
|
+
PrependArrayItem: 'prepend-array-item',
|
|
1445
|
+
InsertArrayItem: 'insert-array-item',
|
|
1446
|
+
RemoveArrayItem: 'remove-array-item',
|
|
1447
|
+
PopArrayItem: 'pop-array-item',
|
|
1448
|
+
ShiftArrayItem: 'shift-array-item',
|
|
1449
1449
|
Textarea: 'textarea',
|
|
1450
1450
|
Radio: 'radio',
|
|
1451
1451
|
MultiCheckbox: 'multi-checkbox',
|
|
@@ -1688,10 +1688,10 @@ class BsIconAddonComponent {
|
|
|
1688
1688
|
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
1689
1689
|
hasAriaLabel = computed(() => this.addon().ariaLabel !== undefined, /* @ts-ignore */
|
|
1690
1690
|
...(ngDevMode ? [{ debugName: "hasAriaLabel" }] : /* istanbul ignore next */ []));
|
|
1691
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
1692
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
1691
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsIconAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1692
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.1", 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 });
|
|
1693
1693
|
}
|
|
1694
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
1694
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsIconAddonComponent, decorators: [{
|
|
1695
1695
|
type: Component,
|
|
1696
1696
|
args: [{
|
|
1697
1697
|
selector: 'df-bs-icon-addon',
|
|
@@ -1723,8 +1723,8 @@ class BsButtonAddonComponent {
|
|
|
1723
1723
|
...(ngDevMode ? [{ debugName: "iconClass" }] : /* istanbul ignore next */ []));
|
|
1724
1724
|
buttonClass = computed(() => `btn btn-outline-${this.addon().severity ?? 'secondary'}`, /* @ts-ignore */
|
|
1725
1725
|
...(ngDevMode ? [{ debugName: "buttonClass" }] : /* istanbul ignore next */ []));
|
|
1726
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
1727
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
1726
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsButtonAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1727
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: BsButtonAddonComponent, isStandalone: true, selector: "df-bs-button-addon", hostDirectives: [{ directive: i1.NgForgeAddonAction }], ngImport: i0, template: `
|
|
1728
1728
|
<button
|
|
1729
1729
|
type="button"
|
|
1730
1730
|
[class]="buttonClass()"
|
|
@@ -1746,7 +1746,7 @@ class BsButtonAddonComponent {
|
|
|
1746
1746
|
</button>
|
|
1747
1747
|
`, isInline: true, dependencies: [{ kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1748
1748
|
}
|
|
1749
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
1749
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BsButtonAddonComponent, decorators: [{
|
|
1750
1750
|
type: Component,
|
|
1751
1751
|
args: [{
|
|
1752
1752
|
selector: 'df-bs-button-addon',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ng-forge-dynamic-forms-bootstrap.mjs","mappings":";;;;;;;;;AA4Bc;;AAGH;;IAGA;;AAEA;;AAEA;AACP;AACA;AACA;;;;AAKE;AACA;;;AAGA;AACD;;;;;;AAOD;;AACA;;;AA/BiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC4BP;;AAGH;;;AAHU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAdlB;;;;AAlCS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACAD;;AACA;;AACA;;AAEQ;AAEA;AACf;;;;;AAIE;;AAEJ;AAEiB;AACf;;;;;AAIE;;AAEJ;AAEiB;AACf;;;;;AAIE;;AAEJ;;;;;;AAnCS;AACT;AACD;;;ACsFa;;;;;;;;AAMH;;IAGA;AACP;;;;AAIO;AACP;;;;;AAfiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAnFT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACDV;AACF;;;;ACsOc;;;;AAMH;;AAET,IAKS;;IAGQ;AAER;;AACA;;IAGU;;;AAtBA;AApFjB;AACE;AACA;AACD;AACD;gFAME;;AAEE;AACA;AACA;oBAEA;;AAEE;AACE;AACA;;;;;;AAOC;;;;AAIR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AApKO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqIR;AACE;AACA;AACD;AACD;4IAME;;AAEE;AACA;AACA;;;AAIE;AACE;AACA;;;;;;AAOC;;;;AAIR;AACF;;;;;;;;ACtHW;;;;AAIH;;IAGA;;;;;AAKP;;;AAGF;AAEI;;AAEF;AAIF;AACE;AACE;AAEA;AACE;;AAEJ;AAEA;AACE;AACA;;;AAIE;;AAEJ;;;AAIA;;;AAGI;AACE;AACA;;;AAEF;;AAEJ;;;AArDiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAlBlB;;;;AA1CS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IC8FD;;;;AAEA;;IAGC;AACR;AACE;;;;AAnBO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAVV;;;;;AApDC;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACYU;;;;AAIH;;;AAJU;;;;;;;;;;;;;;;;AAdlB;;;;AApBS;;;;;;;;;;;;;;;;;;;;;;;;ACgDE;;;;AAIH;;AAET;;AAGE;AAEA;AACE;;;;;AAZe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAVlB;;;;AAvCS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACkDE;;;;;;AAKH;;AAEA;;;AAPU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAlDT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC6EE;;AAGH;;;AAHU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAVlB;;;;AApES;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC6DE;;AAGH;;;AAHU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA3BlB;;;;AAlCS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACJL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACXI;AACJ;AACA;;;AAKE;AACE;;AAGF;AACE;;AAGF;AACE;;;AAKA;;;AAKA;;QAIF;AACA;;AAGI;;AACA;AACF;;;;AAKE;AACA;;;;AAKJ;AACF;AACF;;;AC5CA;;;AAIA;AACE;AACA;;AAGK;AACL;;AAEE;AACA;;AAEA;AACA;AACE;AACD;AACD;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;;AAEA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;;;;ACtHG;;;;AAOA;;AAEgC;AAClC;;AAGF;AACF;AAEA;AAEA;AACE;AACA;AACA;AACE;AACE;;;;AAKN;AACE;AACA;AACA;;AAIE;AACE;;QAKF;AACE;;;;;;AAkBF;AACA;;;AAGC;;AAEL;;;;;ICnFW;;AAEU;;;;AAEA;;;AAPR;;;;AATF;AACT;AACA;AACA;AACA;AACE;AACD;;AAEF;;;;;;;;;;ICuBoB;;;;;AAIA;;;;;AAIA;;;AAZR;;;;;;;;;;;;;;;;;;;;;;;;AA3BF;AACT;AACA;;AAEA;;;;;;;;;;;;;;;;;;;;AAoBC;;AAEF","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms-bootstrap/src/lib/fields/button/bs-button.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/checkbox/bs-checkbox.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/directives/input-constraints.directive.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/datepicker/bs-datepicker.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/addons/preset-actions.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/input/bs-input.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/multi-checkbox/bs-multi-checkbox.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/radio/bs-radio-group.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/radio/bs-radio.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/select/bs-select.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/slider/bs-slider.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/textarea/bs-textarea.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/toggle/bs-toggle.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/types/types.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/button/bs-button.mapper.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/config/bootstrap-field-config.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/providers/bootstrap-providers.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/addons/bs-icon-addon.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/addons/bs-button-addon.component.ts"],"sourcesContent":["import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { FormEvent } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { injectNgForgeAction, NgForgeActionHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsButtonProps } from './bs-button.type';\n\n@Component({\n selector: 'df-bs-button',\n imports: [DynamicTextPipe, AsyncPipe],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeActionHost],\n template: `\n @let buttonId = action.key() + '-button';\n <button\n [id]=\"buttonId\"\n [type]=\"buttonType()\"\n [disabled]=\"action.disabled()\"\n [class]=\"buttonClasses()\"\n [attr.tabindex]=\"action.tabIndex()\"\n [attr.data-testid]=\"buttonTestId()\"\n (click)=\"onClick()\"\n >\n {{ action.label() | dynamicText | async }}\n </button>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsButtonFieldComponent<TEvent extends FormEvent> {\n protected readonly action = injectNgForgeAction<TEvent>();\n\n readonly props = input<BsButtonProps>();\n\n /** Resolved button type — defaults to 'button' unless overridden via props. */\n readonly buttonType = computed(() => this.props()?.type ?? 'button');\n\n readonly buttonTestId = computed(() => `${this.buttonType()}-${this.action.key()}`);\n\n readonly buttonClasses = computed(() => {\n const p = this.props();\n const variant = p?.variant || 'primary';\n const outline = p?.outline ? 'outline-' : '';\n\n return [\n 'btn',\n `btn-${outline}${variant}`,\n p?.size === 'sm' && 'btn-sm',\n p?.size === 'lg' && 'btn-lg',\n p?.block && 'w-100',\n p?.active && 'active',\n this.action.className(),\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n /** Submit buttons let the native form handle submission; other buttons dispatch via the directive. */\n onClick(): void {\n if (this.buttonType() === 'submit') return;\n this.action.dispatch();\n }\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsCheckboxProps } from './bs-checkbox.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-checkbox',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let checkboxId = ngf.key() + '-checkbox';\n\n <div\n class=\"form-check\"\n [class.form-switch]=\"props()?.switch\"\n [class.form-check-inline]=\"props()?.inline\"\n [class.form-check-reverse]=\"props()?.reverse\"\n [attr.hidden]=\"f().hidden() || null\"\n >\n <input\n ngForgeControl\n type=\"checkbox\"\n [formField]=\"f\"\n [id]=\"checkboxId\"\n [indeterminate]=\"props()?.indeterminate ?? false\"\n class=\"form-check-input\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n />\n <label [for]=\"checkboxId\" class=\"form-check-label\">\n {{ ngf.label() | dynamicText | async }}\n </label>\n </div>\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\" [attr.hidden]=\"f().hidden() || null\">{{ hint | dynamicText | async }}</div>\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsCheckboxFieldComponent {\n protected readonly ngf = injectNgForgeField<boolean>();\n\n readonly props = input<BsCheckboxProps>();\n}\n","import { Directive, ElementRef, inject, input } from '@angular/core';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\n\n/** Directive to set min, max, and step attributes on form inputs */\n@Directive({\n selector: '[dfBsInputConstraints]',\n})\nexport class InputConstraintsDirective {\n readonly dfMin = input<number | string | null | undefined>();\n readonly dfMax = input<number | string | null | undefined>();\n readonly dfStep = input<number | string | null | undefined>();\n\n private readonly el = inject<ElementRef<HTMLInputElement>>(ElementRef);\n\n private readonly minEffect = explicitEffect([this.dfMin], ([minValue]) => {\n const nativeElement = this.el.nativeElement;\n if (minValue !== null && minValue !== undefined) {\n nativeElement.setAttribute('min', String(minValue));\n } else {\n nativeElement.removeAttribute('min');\n }\n });\n\n private readonly maxEffect = explicitEffect([this.dfMax], ([maxValue]) => {\n const nativeElement = this.el.nativeElement;\n if (maxValue !== null && maxValue !== undefined) {\n nativeElement.setAttribute('max', String(maxValue));\n } else {\n nativeElement.removeAttribute('max');\n }\n });\n\n private readonly stepEffect = explicitEffect([this.dfStep], ([stepValue]) => {\n const nativeElement = this.el.nativeElement;\n if (stepValue !== null && stepValue !== undefined) {\n nativeElement.setAttribute('step', String(stepValue));\n } else {\n nativeElement.removeAttribute('step');\n }\n });\n}\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsDatepickerProps } from './bs-datepicker.type';\nimport { AsyncPipe } from '@angular/common';\nimport { InputConstraintsDirective } from '../../directives/input-constraints.directive';\n\n@Component({\n selector: 'df-bs-datepicker',\n imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';\n @if (p?.floatingLabel) {\n <!-- Floating label variant -->\n <div class=\"form-floating mb-3\">\n <input\n ngForgeControl\n dfBsInputConstraints\n [formField]=\"f\"\n [id]=\"inputId\"\n type=\"date\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [dfMin]=\"minAsString()\"\n [dfMax]=\"maxAsString()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"p?.size === 'sm'\"\n [class.form-control-lg]=\"p?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n @if (ngf.label()) {\n <label [for]=\"inputId\">{{ ngf.label() | dynamicText | async }}</label>\n }\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n }\n </div>\n } @else {\n <!-- Standard variant -->\n <div class=\"mb-3\">\n @if (ngf.label()) {\n <label [for]=\"inputId\" class=\"form-label\">{{ ngf.label() | dynamicText | async }}</label>\n }\n\n <input\n ngForgeControl\n dfBsInputConstraints\n [formField]=\"f\"\n [id]=\"inputId\"\n type=\"date\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [dfMin]=\"minAsString()\"\n [dfMax]=\"maxAsString()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"p?.size === 'sm'\"\n [class.form-control-lg]=\"p?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (p?.hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ p?.hint | dynamicText | async }}</div>\n }\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n styles: [\n `\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n})\nexport default class BsDatepickerFieldComponent {\n protected readonly ngf = injectNgForgeField<string>();\n\n readonly minDate = input<Date | string | null>(null);\n readonly maxDate = input<Date | string | null>(null);\n readonly startAt = input<Date | null>(null);\n readonly props = input<BsDatepickerProps>();\n\n // Helper methods to convert Date to string for HTML attributes\n readonly minAsString = computed(() => {\n const min = this.minDate();\n return min instanceof Date ? min.toISOString().split('T')[0] : min;\n });\n\n readonly maxAsString = computed(() => {\n const max = this.maxDate();\n return max instanceof Date ? max.toISOString().split('T')[0] : max;\n });\n}\n","import type { AddonActionContext, AddonActionPreset } from '@ng-forge/dynamic-forms';\nimport type { PresetCollaborators } from '@ng-forge/dynamic-forms/integration';\nimport { runPresetAction } from '@ng-forge/dynamic-forms/integration';\n\nexport type { PresetCollaborators };\n\n/** Bootstrap adapter binding for the shared preset runner. */\nexport function runBsPresetAction(preset: AddonActionPreset, ctx: AddonActionContext, collaborators: PresetCollaborators): Promise<void> {\n return runPresetAction(preset, ctx, collaborators, 'Bootstrap', 'bs-input');\n}\n","import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, forwardRef, inject, input, signal } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { AddonActionContext, AddonActionPreset, DynamicFormLogger } from '@ng-forge/dynamic-forms';\nimport { DfAddonSlot } from '@ng-forge/dynamic-forms/integration';\nimport { WrapperFieldInputs } from '@ng-forge/dynamic-forms/integration';\nimport { DynamicTextPipe, FIELD_SIGNAL_CONTEXT } from '@ng-forge/dynamic-forms/integration';\nimport {\n ADDON_PRESET_HANDLER,\n AddonPresetHandler,\n injectNgForgeAddons,\n injectNgForgeField,\n NgForgeAddons,\n NgForgeControl,\n NgForgeFieldHost,\n} from '@ng-forge/dynamic-forms/integration';\nimport { BOOTSTRAP_CONFIG } from '../../models/bootstrap-config.token';\nimport { runBsPresetAction } from '../../addons/preset-actions';\nimport { BS_INPUT_TYPE_OVERRIDE } from '../../tokens/input-type-override.token';\nimport { BsInputAddon, BsInputProps } from './bs-input.type';\n\n@Component({\n selector: 'df-bs-input',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl, DfAddonSlot],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost, NgForgeAddons],\n template: `\n @let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';\n @if (floatingLabel()) {\n <div class=\"mb-3\">\n @if (ngfa.hasAddons()) {\n <div class=\"input-group\">\n @for (a of ngfa.prefixAddons(); track $index) {\n <span class=\"input-group-text\">\n <df-addon-slot [addon]=\"a\" [fieldInputs]=\"fieldInputs()\" [hidden]=\"ngfa.hiddenSignalCache().get(a)\" />\n </span>\n }\n <div class=\"form-floating\">\n <input\n ngForgeControl\n [formField]=\"f\"\n [id]=\"inputId\"\n [type]=\"type()\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"size() === 'sm'\"\n [class.form-control-lg]=\"size() === 'lg'\"\n [class.form-control-plaintext]=\"p?.plaintext\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n @if (ngf.label()) {\n <label [for]=\"inputId\">{{ ngf.label() | dynamicText | async }}</label>\n }\n </div>\n @for (a of ngfa.suffixAddons(); track $index) {\n <span class=\"input-group-text\">\n <df-addon-slot [addon]=\"a\" [fieldInputs]=\"fieldInputs()\" [hidden]=\"ngfa.hiddenSignalCache().get(a)\" />\n </span>\n }\n </div>\n } @else {\n <div class=\"form-floating\">\n <input\n ngForgeControl\n [formField]=\"f\"\n [id]=\"inputId\"\n [type]=\"type()\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"size() === 'sm'\"\n [class.form-control-lg]=\"size() === 'lg'\"\n [class.form-control-plaintext]=\"p?.plaintext\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n @if (ngf.label()) {\n <label [for]=\"inputId\">{{ ngf.label() | dynamicText | async }}</label>\n }\n </div>\n }\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n }\n </div>\n } @else {\n <div class=\"mb-3\">\n @if (ngf.label()) {\n <label [for]=\"inputId\" class=\"form-label\">{{ ngf.label() | dynamicText | async }}</label>\n }\n @if (ngfa.hasAddons()) {\n <div class=\"input-group\">\n @for (a of ngfa.prefixAddons(); track $index) {\n <span class=\"input-group-text\">\n <df-addon-slot [addon]=\"a\" [fieldInputs]=\"fieldInputs()\" [hidden]=\"ngfa.hiddenSignalCache().get(a)\" />\n </span>\n }\n <input\n ngForgeControl\n [formField]=\"f\"\n [id]=\"inputId\"\n [type]=\"type()\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"size() === 'sm'\"\n [class.form-control-lg]=\"size() === 'lg'\"\n [class.form-control-plaintext]=\"p?.plaintext\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n @for (a of ngfa.suffixAddons(); track $index) {\n <span class=\"input-group-text\">\n <df-addon-slot [addon]=\"a\" [fieldInputs]=\"fieldInputs()\" [hidden]=\"ngfa.hiddenSignalCache().get(a)\" />\n </span>\n }\n </div>\n } @else {\n <input\n ngForgeControl\n [formField]=\"f\"\n [id]=\"inputId\"\n [type]=\"type()\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"size() === 'sm'\"\n [class.form-control-lg]=\"size() === 'lg'\"\n [class.form-control-plaintext]=\"p?.plaintext\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n }\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (p?.hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ p?.hint | dynamicText | async }}</div>\n }\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: BS_INPUT_TYPE_OVERRIDE,\n useFactory: () => signal<string | undefined>(undefined),\n },\n {\n // Adapter-specific preset semantics for `bs-button` addons (clear /\n // reset / paste / copy / toggle-password-visibility). The directive\n // (`NgForgeAddonAction`) delegates here when an addon configures a\n // `preset`. Per-bs-input-instance so the `typeOverride` signal is\n // scoped to one field.\n provide: ADDON_PRESET_HANDLER,\n useFactory: (): AddonPresetHandler => {\n const typeOverride = inject(BS_INPUT_TYPE_OVERRIDE);\n const fsc = inject(FIELD_SIGNAL_CONTEXT, { optional: true });\n const logger = inject(DynamicFormLogger);\n // forwardRef for baselineType only — host.props()?.type gates toggle-password-visibility.\n const host = inject(forwardRef(() => BsInputFieldComponent));\n return {\n run: (preset: string, ctx: AddonActionContext) => {\n const fieldKey = ctx.field.key;\n return runBsPresetAction(preset as AddonActionPreset, ctx, {\n typeOverride,\n fieldValueSetter: ctx.setValue,\n fieldDefaultValueGetter:\n fsc && fieldKey ? () => (fsc.defaultValues() as Record<string, unknown> | undefined)?.[fieldKey] : undefined,\n baselineType: () => host.props()?.type,\n logger,\n });\n },\n };\n },\n },\n ],\n styles: [\n `\n :host([hidden]) {\n display: none !important;\n }\n /* Per-side padding on the .input-group-text addon containers — each\n side is its own knob so consumers can tune the prefix's gap from\n the border (outer-left), the prefix's gap to the input text\n (inner-right), and the symmetric suffix counterparts independently. */\n :host {\n --df-bs-addon-prefix-outer-padding: 0.75rem;\n --df-bs-addon-prefix-inner-padding: 0.75rem;\n --df-bs-addon-suffix-inner-padding: 0.75rem;\n --df-bs-addon-suffix-outer-padding: 0.75rem;\n }\n :host ::ng-deep .input-group > .input-group-text:first-child {\n padding-left: var(--df-bs-addon-prefix-outer-padding);\n padding-right: var(--df-bs-addon-prefix-inner-padding);\n }\n :host ::ng-deep .input-group > .input-group-text:last-child {\n padding-left: var(--df-bs-addon-suffix-inner-padding);\n padding-right: var(--df-bs-addon-suffix-outer-padding);\n }\n /* bs-button addons live inside .input-group-text for visual parity with\n bs-icon (shared grey container). Strip the inner .btn's own visual\n chrome (border, background, focus ring) so the button blends with\n the addon container — done via Bootstrap's own CSS variables so the\n cascade reaches the rendered <button> in any theme. No !important. */\n :host ::ng-deep .input-group-text df-bs-button-addon .btn {\n --bs-btn-bg: transparent;\n --bs-btn-color: var(--bs-body-color);\n --bs-btn-border-color: transparent;\n --bs-btn-hover-bg: rgba(127, 127, 127, 0.12);\n --bs-btn-hover-color: var(--bs-body-color);\n --bs-btn-hover-border-color: transparent;\n --bs-btn-active-bg: rgba(127, 127, 127, 0.18);\n --bs-btn-active-border-color: transparent;\n --bs-btn-focus-shadow-rgb: 0, 0, 0;\n --bs-btn-padding-x: 0;\n --bs-btn-padding-y: 0;\n box-shadow: none;\n }\n :host ::ng-deep .input-group-text df-bs-button-addon .btn:focus-visible {\n /* 2px solid for WCAG 2.4.11; currentColor adapts to light/dark themes. */\n box-shadow: none;\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n `,\n ],\n})\nexport default class BsInputFieldComponent {\n private bootstrapConfig = inject(BOOTSTRAP_CONFIG, { optional: true });\n\n protected readonly ngf = injectNgForgeField<string>();\n protected readonly ngfa = injectNgForgeAddons<BsInputAddon>();\n\n readonly props = input<BsInputProps>();\n\n /**\n * Wrapper-style host bag pushed by `DfFieldOutlet`. Declared at the\n * component level so `setInputIfDeclared` (which uses\n * `reflectComponentType`) can write it.\n */\n readonly fieldInputs = input<WrapperFieldInputs | undefined>();\n\n /** Per-instance type override populated by `toggle-password-visibility` preset. */\n private readonly typeOverride = inject(BS_INPUT_TYPE_OVERRIDE);\n\n readonly size = computed(() => this.props()?.size ?? this.bootstrapConfig?.size);\n readonly floatingLabel = computed(() => this.props()?.floatingLabel ?? this.bootstrapConfig?.floatingLabel ?? false);\n\n /** Override (set by `toggle-password-visibility` preset) wins over `props().type`. */\n protected readonly type = computed(() => this.typeOverride() ?? this.props()?.type ?? 'text');\n}\n","import { ChangeDetectionStrategy, Component, computed, input, linkedSignal } from '@angular/core';\nimport { FieldOption, ValueType } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { injectNgForgeField, NgForgeFieldHost, isEqual, NgForgeControl } from '@ng-forge/dynamic-forms/integration';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\nimport { BsMultiCheckboxProps } from './bs-multi-checkbox.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-multi-checkbox',\n imports: [DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field();\n @let checked = checkedValuesMap();\n @if (ngf.label(); as label) {\n <div class=\"form-label\">{{ label | dynamicText | async }}</div>\n }\n\n <div class=\"checkbox-group\">\n @for (option of options(); track option.value; let i = $index) {\n <div\n class=\"form-check\"\n [class.form-switch]=\"props()?.switch\"\n [class.form-check-inline]=\"props()?.inline\"\n [class.form-check-reverse]=\"props()?.reverse\"\n >\n <input\n ngForgeControl\n type=\"checkbox\"\n [id]=\"ngf.key() + '_' + i\"\n [checked]=\"checked['' + option.value]\"\n [disabled]=\"f().disabled() || option.disabled\"\n (change)=\"onCheckboxChange(option, $event)\"\n class=\"form-check-input\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n />\n <label [for]=\"ngf.key() + '_' + i\" class=\"form-check-label\">\n {{ option.label | dynamicText | async }}\n </label>\n </div>\n }\n </div>\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ hint | dynamicText | async }}</div>\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n .checkbox-group {\n margin-bottom: 0.5rem;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsMultiCheckboxFieldComponent {\n protected readonly ngf = injectNgForgeField<ValueType[]>();\n\n readonly options = input<FieldOption<ValueType>[]>([]);\n readonly props = input<BsMultiCheckboxProps>();\n\n /** Computed map of checked option values for O(1) lookup in template */\n readonly checkedValuesMap = computed(() => {\n const map: Record<string, boolean> = {};\n for (const opt of this.valueViewModel()) {\n map[String(opt.value)] = true;\n }\n return map;\n });\n\n valueViewModel = linkedSignal<FieldOption<ValueType>[]>(\n () => {\n const currentValues = this.ngf.field()().value();\n return this.options().filter((option) => currentValues.includes(option.value));\n },\n { equal: isEqual },\n );\n\n constructor() {\n explicitEffect([this.valueViewModel], ([selectedOptions]: [FieldOption<ValueType>[]]) => {\n const selectedValues = selectedOptions.map((option: FieldOption<ValueType>) => option.value);\n\n if (!isEqual(selectedValues, this.ngf.field()().value())) {\n this.ngf.field()().value.set(selectedValues);\n }\n });\n\n explicitEffect([this.options], ([options]: [FieldOption<ValueType>[]]) => {\n const values = options.map((option: FieldOption<ValueType>) => option.value);\n const uniqueValues = new Set(values);\n\n if (values.length !== uniqueValues.size) {\n const duplicates = values.filter((value, index) => values.indexOf(value) !== index);\n throw new Error(`Duplicate option values detected in bs-multi-checkbox: ${duplicates.join(', ')}`);\n }\n });\n }\n\n onCheckboxChange(option: FieldOption<ValueType>, event: Event): void {\n const checked = (event.target as HTMLInputElement).checked;\n this.valueViewModel.update((currentOptions: FieldOption<ValueType>[]) => {\n if (checked) {\n return currentOptions.some((opt: FieldOption<ValueType>) => opt.value === option.value)\n ? currentOptions\n : [...currentOptions, option];\n } else {\n return currentOptions.filter((opt: FieldOption<ValueType>) => opt.value !== option.value);\n }\n });\n }\n}\n","import { ChangeDetectionStrategy, Component, inject, input, model } from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\nimport { DynamicText, FieldOption, ValueType } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, NgForgeField } from '@ng-forge/dynamic-forms/integration';\nimport { AsyncPipe } from '@angular/common';\n\nexport interface BsRadioGroupProps {\n /** Display radio buttons inline (horizontal) */\n inline?: boolean;\n /** Reverse the position of the radio button and label */\n reverse?: boolean;\n /** Display as button group instead of radio buttons */\n buttonGroup?: boolean;\n /** Button size when using button group */\n buttonSize?: 'sm' | 'lg';\n /** Hint text to display below the radio group */\n hint?: DynamicText;\n}\n\n/**\n * Bootstrap radio group implementing FormValueControl. Rendered inside\n * `df-bs-radio` — each `<input type=\"radio\">` carries `ngForgeControl`, so\n * the marker absorbs meta + aria from the ambient parent NgForgeField.\n */\n@Component({\n selector: 'df-bs-radio-group',\n imports: [DynamicTextPipe, AsyncPipe, NgForgeControl],\n host: {\n role: 'radiogroup',\n '[attr.aria-invalid]': 'parentField?.ariaInvalid() || null',\n '[attr.aria-required]': 'parentField?.ariaRequired()',\n '[attr.aria-describedby]': 'parentField?.ariaDescribedBy()',\n },\n template: `\n @let props = properties();\n @if (props?.buttonGroup) {\n <div class=\"btn-group\" role=\"group\" [attr.aria-label]=\"label() | dynamicText | async\">\n @for (option of options(); track option.value; let i = $index) {\n <input\n ngForgeControl\n type=\"radio\"\n [name]=\"name()\"\n [value]=\"option.value\"\n [checked]=\"value() === option.value\"\n (change)=\"onRadioChange(option.value)\"\n [disabled]=\"disabled() || option.disabled || false\"\n class=\"btn-check\"\n [id]=\"name() + '_' + i\"\n autocomplete=\"off\"\n />\n <label\n class=\"btn btn-outline-primary\"\n [class.btn-sm]=\"props?.buttonSize === 'sm'\"\n [class.btn-lg]=\"props?.buttonSize === 'lg'\"\n [for]=\"name() + '_' + i\"\n >\n {{ option.label | dynamicText | async }}\n </label>\n }\n </div>\n } @else {\n @for (option of options(); track option.value; let i = $index) {\n <div class=\"form-check\" [class.form-check-inline]=\"props?.inline\" [class.form-check-reverse]=\"props?.reverse\">\n <input\n ngForgeControl\n type=\"radio\"\n [name]=\"name()\"\n [value]=\"option.value\"\n [checked]=\"value() === option.value\"\n (change)=\"onRadioChange(option.value)\"\n [disabled]=\"disabled() || option.disabled || false\"\n class=\"form-check-input\"\n [id]=\"name() + '_' + i\"\n />\n <label class=\"form-check-label\" [for]=\"name() + '_' + i\">\n {{ option.label | dynamicText | async }}\n </label>\n </div>\n }\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BsRadioGroupComponent implements FormValueControl<ValueType | undefined> {\n protected readonly parentField = inject(NgForgeField, { optional: true });\n\n // Value model - FormField directive binds form value to this\n readonly value = model<ValueType | undefined>(undefined);\n\n // Optional FormValueControl properties - Field directive will bind these\n readonly disabled = input<boolean>(false);\n readonly readonly = input<boolean>(false);\n readonly name = input<string>('');\n\n // Component-specific inputs\n readonly label = input<DynamicText>();\n readonly options = input.required<FieldOption<ValueType>[]>();\n readonly properties = input<BsRadioGroupProps>();\n\n /** Handle radio button change event */\n protected onRadioChange(newValue: ValueType): void {\n if (!this.disabled() && !this.readonly()) {\n this.value.set(newValue);\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { FieldOption, ValueType } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsRadioProps } from './bs-radio.type';\nimport { AsyncPipe } from '@angular/common';\nimport { BsRadioGroupComponent } from './bs-radio-group.component';\n\n@Component({\n selector: 'df-bs-radio',\n imports: [BsRadioGroupComponent, FormField, DynamicTextPipe, AsyncPipe],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field();\n\n <div class=\"mb-3\">\n @if (ngf.label(); as label) {\n <div class=\"form-label\">{{ label | dynamicText | async }}</div>\n }\n\n <df-bs-radio-group [formField]=\"f\" [label]=\"ngf.label()\" [options]=\"options()\" [properties]=\"props()\" />\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ hint | dynamicText | async }}</div>\n }\n </div>\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsRadioFieldComponent {\n protected readonly ngf = injectNgForgeField<ValueType>();\n\n readonly options = input<FieldOption<ValueType>[]>([]);\n readonly props = input<BsRadioProps>();\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { FieldOption } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsSelectProps } from './bs-select.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-select',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let selectId = ngf.key() + '-select';\n\n <div class=\"mb-3\">\n @if (ngf.label(); as label) {\n <label [for]=\"selectId\" class=\"form-label\">{{ label | dynamicText | async }}</label>\n }\n <select\n ngForgeControl\n [formField]=\"f\"\n [id]=\"selectId\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-select\"\n [class.form-select-sm]=\"props()?.size === 'sm'\"\n [class.form-select-lg]=\"props()?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [multiple]=\"props()?.multiple || false\"\n [size]=\"props()?.htmlSize\"\n >\n @if (ngf.placeholder(); as placeholder) {\n <option value=\"\" disabled [selected]=\"!f().value()\">{{ placeholder | dynamicText | async }}</option>\n }\n @for (option of options(); track option.value) {\n <option [value]=\"option.value\" [disabled]=\"option.disabled || false\" [selected]=\"isSelected(option.value, f().value())\">\n {{ option.label | dynamicText | async }}\n </option>\n }\n </select>\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ hint | dynamicText | async }}</div>\n }\n </div>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n styles: [\n `\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n})\nexport default class BsSelectFieldComponent {\n protected readonly ngf = injectNgForgeField<string>();\n\n readonly options = input<FieldOption<string>[]>([]);\n readonly props = input<BsSelectProps>();\n\n defaultCompare = Object.is;\n\n protected isSelected(optionValue: string, fieldValue: string | string[] | null): boolean {\n const compareWith = this.props()?.compareWith || this.defaultCompare;\n\n if (Array.isArray(fieldValue)) {\n return fieldValue.some((v) => compareWith(v, optionValue));\n }\n\n return fieldValue !== null && compareWith(fieldValue, optionValue);\n }\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsSliderProps } from './bs-slider.type';\nimport { AsyncPipe } from '@angular/common';\nimport { InputConstraintsDirective } from '../../directives/input-constraints.directive';\n\n@Component({\n selector: 'df-bs-slider',\n imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let inputId = ngf.key() + '-input';\n\n <div class=\"mb-3\">\n @if (ngf.label(); as label) {\n <label [for]=\"inputId\" class=\"form-label\">\n {{ label | dynamicText | async }}\n @if (props()?.showValue) {\n <span class=\"ms-2 badge bg-secondary\"> {{ props()?.valuePrefix }}{{ f().value() }}{{ props()?.valueSuffix }} </span>\n }\n </label>\n }\n\n <input\n ngForgeControl\n type=\"range\"\n dfBsInputConstraints\n [formField]=\"f\"\n [id]=\"inputId\"\n [dfMin]=\"f().min?.() ?? props()?.min ?? min()\"\n [dfMax]=\"f().max?.() ?? props()?.max ?? max()\"\n [dfStep]=\"step() ?? props()?.step ?? 1\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-range\"\n />\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ hint | dynamicText | async }}</div>\n }\n </div>\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsSliderFieldComponent {\n protected readonly ngf = injectNgForgeField<number>();\n\n readonly min = input<number>(0);\n readonly max = input<number>(100);\n readonly step = input<number>();\n\n readonly props = input<BsSliderProps>();\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsTextareaProps } from './bs-textarea.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-textarea',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let p = props(); @let textareaId = ngf.key() + '-textarea';\n @if (p?.floatingLabel) {\n <!-- Floating label variant -->\n <div class=\"form-floating mb-3\">\n <textarea\n ngForgeControl\n [formField]=\"f\"\n [id]=\"textareaId\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"p?.size === 'sm'\"\n [class.form-control-lg]=\"p?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n ></textarea>\n\n @if (ngf.label()) {\n <label [for]=\"textareaId\">{{ ngf.label() | dynamicText | async }}</label>\n }\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (p?.hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ p?.hint | dynamicText | async }}</div>\n }\n </div>\n } @else {\n <!-- Standard variant -->\n <div class=\"mb-3\">\n @if (ngf.label()) {\n <label [for]=\"textareaId\" class=\"form-label\">{{ ngf.label() | dynamicText | async }}</label>\n }\n\n <textarea\n ngForgeControl\n [formField]=\"f\"\n [id]=\"textareaId\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"p?.size === 'sm'\"\n [class.form-control-lg]=\"p?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n ></textarea>\n\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (p?.hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ p?.hint | dynamicText | async }}</div>\n }\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n styles: [\n `\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n})\nexport default class BsTextareaFieldComponent {\n protected readonly ngf = injectNgForgeField<string>();\n\n readonly props = input<BsTextareaProps>();\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsToggleProps } from './bs-toggle.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-toggle',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let inputId = ngf.key() + '-input';\n\n <div\n class=\"form-check form-switch\"\n [class.form-check-inline]=\"props()?.inline\"\n [class.form-check-reverse]=\"props()?.reverse\"\n [class.form-switch-sm]=\"props()?.size === 'sm'\"\n [class.form-switch-lg]=\"props()?.size === 'lg'\"\n [attr.hidden]=\"f().hidden() || null\"\n >\n <input\n ngForgeControl\n type=\"checkbox\"\n [formField]=\"f\"\n [id]=\"inputId\"\n class=\"form-check-input\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n />\n <label [for]=\"inputId\" class=\"form-check-label\">\n {{ ngf.label() | dynamicText | async }}\n </label>\n </div>\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\" [attr.hidden]=\"f().hidden() || null\">{{ hint | dynamicText | async }}</div>\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n /* Custom size variants for switches */\n .form-switch-sm .form-check-input {\n width: 1.75rem;\n height: 1rem;\n font-size: 0.875rem;\n }\n\n .form-switch-lg .form-check-input {\n width: 3rem;\n height: 1.75rem;\n font-size: 1.125rem;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsToggleFieldComponent {\n protected readonly ngf = injectNgForgeField<boolean>();\n\n readonly props = input<BsToggleProps>();\n}\n","/**\n * Bootstrap field type constants\n * Based on available field components in the /fields folder\n */\nexport const BsField = {\n Input: 'input',\n Select: 'select',\n Checkbox: 'checkbox',\n Button: 'button',\n Submit: 'submit',\n Next: 'next',\n Previous: 'previous',\n AddArrayItem: 'addArrayItem',\n PrependArrayItem: 'prependArrayItem',\n InsertArrayItem: 'insertArrayItem',\n RemoveArrayItem: 'removeArrayItem',\n PopArrayItem: 'popArrayItem',\n ShiftArrayItem: 'shiftArrayItem',\n Textarea: 'textarea',\n Radio: 'radio',\n MultiCheckbox: 'multi-checkbox',\n Datepicker: 'datepicker',\n Slider: 'slider',\n Toggle: 'toggle',\n} as const;\n\nexport type BsFieldType = (typeof BsField)[keyof typeof BsField];\n","import { computed, inject, isSignal, Signal } from '@angular/core';\nimport { FieldDef } from '@ng-forge/dynamic-forms';\nimport { ARRAY_CONTEXT, buildBaseInputs, DEFAULT_PROPS } from '@ng-forge/dynamic-forms/integration';\n\n/**\n * Generic button mapper for custom events or basic buttons.\n * For specific button types (submit, next, prev, add/remove array items),\n * use the dedicated field types and their specific mappers.\n *\n * @param fieldDef The button field definition\n * @returns Signal containing Record of input names to values for ngComponentOutlet\n */\nexport function buttonFieldMapper(fieldDef: FieldDef<Record<string, unknown>>): Signal<Record<string, unknown>> {\n const defaultProps = inject(DEFAULT_PROPS);\n const arrayContext = inject(ARRAY_CONTEXT, { optional: true });\n\n return computed(() => {\n const baseInputs = buildBaseInputs(fieldDef, defaultProps());\n\n const inputs: Record<string, unknown> = {\n ...baseInputs,\n };\n\n if (fieldDef.disabled !== undefined) {\n inputs['disabled'] = fieldDef.disabled;\n }\n\n if (fieldDef.hidden !== undefined) {\n inputs['hidden'] = fieldDef.hidden;\n }\n\n // Add event binding for button events\n if ('event' in fieldDef && fieldDef.event !== undefined) {\n inputs['event'] = fieldDef.event;\n }\n\n // Add eventArgs binding if provided\n if ('eventArgs' in fieldDef && fieldDef.eventArgs !== undefined) {\n inputs['eventArgs'] = fieldDef.eventArgs;\n }\n\n // Add eventContext for token resolution (supports $template, $arrayKey, $index, etc.)\n const template = 'template' in fieldDef ? fieldDef.template : undefined;\n if (template || arrayContext) {\n // Read signal value if index is a signal (supports differential updates)\n const getIndex = () => {\n if (!arrayContext) return -1;\n return isSignal(arrayContext.index) ? arrayContext.index() : arrayContext.index;\n };\n\n inputs['eventContext'] = {\n key: fieldDef.key,\n index: getIndex(),\n arrayKey: arrayContext?.arrayKey ?? '',\n formValue: arrayContext?.formValue ?? {},\n template,\n };\n }\n\n return inputs;\n });\n}\n","import { FieldTypeDefinition } from '@ng-forge/dynamic-forms/integration';\nimport {\n addArrayItemButtonMapper,\n checkboxFieldMapper,\n datepickerFieldMapper,\n insertArrayItemButtonMapper,\n optionsFieldMapper,\n popArrayItemButtonMapper,\n prependArrayItemButtonMapper,\n removeArrayItemButtonMapper,\n shiftArrayItemButtonMapper,\n valueFieldMapper,\n} from '@ng-forge/dynamic-forms/integration';\nimport { BsField } from '../types/types';\nimport { buttonFieldMapper } from '../fields/button/bs-button.mapper';\nimport { nextButtonFieldMapper, previousButtonFieldMapper, submitButtonFieldMapper } from '../fields/button/bs-specific-button.mapper';\n\nconst VALUE_FIELD_TYPES_BASE = {\n renderReadyWhen: ['field'],\n} as const;\n\nconst BUTTON_FIELD_TYPES_BASE = {\n renderReadyWhen: [],\n valueHandling: 'exclude',\n} as const;\n\nexport const BOOTSTRAP_FIELD_TYPES: FieldTypeDefinition[] = [\n {\n name: BsField.Input,\n loadComponent: () => import('../fields/input/bs-input.component'),\n mapper: valueFieldMapper,\n propsToMeta: ['type'],\n scope: ['text-input', 'numeric'],\n addons: {\n slots: ['prefix', 'suffix'],\n },\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Select,\n loadComponent: () => import('../fields/select/bs-select.component'),\n mapper: optionsFieldMapper,\n scope: 'single-select',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Checkbox,\n loadComponent: () => import('../fields/checkbox/bs-checkbox.component'),\n mapper: checkboxFieldMapper,\n scope: 'boolean',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Button,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: buttonFieldMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Submit,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: submitButtonFieldMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Next,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: nextButtonFieldMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Previous,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: previousButtonFieldMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.AddArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: addArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.PrependArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: prependArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.InsertArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: insertArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.RemoveArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: removeArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.PopArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: popArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.ShiftArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: shiftArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Textarea,\n loadComponent: () => import('../fields/textarea/bs-textarea.component'),\n mapper: valueFieldMapper,\n propsToMeta: ['rows'],\n scope: 'text-input',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Radio,\n loadComponent: () => import('../fields/radio/bs-radio.component'),\n mapper: optionsFieldMapper,\n scope: 'single-select',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.MultiCheckbox,\n loadComponent: () => import('../fields/multi-checkbox/bs-multi-checkbox.component'),\n mapper: optionsFieldMapper,\n scope: 'multi-select',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Datepicker,\n loadComponent: () => import('../fields/datepicker/bs-datepicker.component'),\n mapper: datepickerFieldMapper,\n scope: 'date',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Slider,\n loadComponent: () => import('../fields/slider/bs-slider.component'),\n mapper: valueFieldMapper,\n scope: 'numeric',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Toggle,\n loadComponent: () => import('../fields/toggle/bs-toggle.component'),\n mapper: checkboxFieldMapper,\n scope: 'boolean',\n ...VALUE_FIELD_TYPES_BASE,\n },\n];\n","import type { Provider } from '@angular/core';\nimport { DynamicFormError, type AddonTypeDefinition } from '@ng-forge/dynamic-forms';\nimport { ADDON_TYPE_DEFINITIONS, type FieldTypeDefinition } from '@ng-forge/dynamic-forms/integration';\nimport { BOOTSTRAP_FIELD_TYPES } from '../config/bootstrap-field-config';\nimport { BootstrapConfig } from '../models/bootstrap-config';\nimport { BOOTSTRAP_CONFIG } from '../models/bootstrap-config.token';\nimport type { BsButtonAddon, BsIconAddon } from '../types/addons';\n\n/** Field type definitions for Bootstrap components. */\nexport type BootstrapFieldTypes = FieldTypeDefinition[];\n\ntype BootstrapConfigFeature = {\n ɵkind: 'bootstrap-config';\n ɵproviders: Provider[];\n};\n\n/**\n * Default `withBootstrapFields()` shape — field defs + the auto-included\n * addons feature so `bs-icon` / `bs-button` work out of the box.\n */\ntype BootstrapFieldsWithAddons = [...BootstrapFieldTypes, BootstrapAddonsFeature];\n\ntype BootstrapFieldsWithConfig = [...BootstrapFieldTypes, BootstrapAddonsFeature, BootstrapConfigFeature];\n\n/**\n * Provides Bootstrap field type definitions for the dynamic form system,\n * with Bootstrap-shipped addon types (`bs-icon`, `bs-button`) auto-included\n * so addons work out of the box.\n *\n * @param config - Optional global configuration for Bootstrap form fields\n * @returns Tuple of field type definitions, the addons feature, and\n * optionally a config feature.\n */\nexport function withBootstrapFields(): BootstrapFieldsWithAddons;\nexport function withBootstrapFields(config: BootstrapConfig): BootstrapFieldsWithConfig;\nexport function withBootstrapFields(config: BootstrapConfig | undefined): BootstrapFieldsWithAddons | BootstrapFieldsWithConfig;\nexport function withBootstrapFields(config?: BootstrapConfig): BootstrapFieldsWithAddons | BootstrapFieldsWithConfig {\n // Always include the addons feature — bs-icon / bs-button are part of\n // the canonical Bootstrap surface.\n const base: unknown[] = [...BOOTSTRAP_FIELD_TYPES, withBootstrapAddons()];\n\n if (config) {\n base.push({\n ɵkind: 'bootstrap-config',\n ɵproviders: [{ provide: BOOTSTRAP_CONFIG, useValue: config }],\n } satisfies BootstrapConfigFeature);\n return base as BootstrapFieldsWithConfig;\n }\n\n return base as BootstrapFieldsWithAddons;\n}\n\n/* -- Bootstrap addon types --------------------------------------------- */\n\nconst BS_ICON_KIND: AddonTypeDefinition<BsIconAddon> = {\n type: 'bs-icon',\n loadComponent: () => import('../addons/bs-icon-addon.component').then((m) => m.BsIconAddonComponent),\n validate: (addon, fieldKey) => {\n if (typeof addon.icon !== 'string' || addon.icon.length === 0) {\n throw new DynamicFormError(`Addon type 'bs-icon' requires a non-empty 'icon' string (field: '${fieldKey}').`);\n }\n },\n};\n\nconst BS_BUTTON_KIND: AddonTypeDefinition<BsButtonAddon> = {\n type: 'bs-button',\n loadComponent: () => import('../addons/bs-button-addon.component').then((m) => m.BsButtonAddonComponent),\n validate: (addon, fieldKey) => {\n // Exactly one of preset / actionRef / action — validator drops the addon\n // (with warning) if the rule is violated.\n const set = [addon.preset, addon.actionRef, addon.action].filter((v) => v !== undefined);\n if (set.length > 1) {\n throw new DynamicFormError(\n `Addon type 'bs-button' on field '${fieldKey}' has more than one of preset/actionRef/action — exactly one allowed.`,\n );\n }\n // Icon-only buttons require ariaLabel for screen readers.\n if (addon.icon && !addon.label && !addon.ariaLabel) {\n throw new DynamicFormError(`Addon type 'bs-button' on field '${fieldKey}' is icon-only — provide 'ariaLabel' for accessibility.`);\n }\n },\n};\n\n/**\n * Feature type discriminant for the Bootstrap addons feature. Matches core's\n * `'addons'` type so providers flow through the standard addon-type pipeline\n * in `provideDynamicForm`.\n */\ntype BootstrapAddonsFeature = {\n ɵkind: 'addons';\n ɵproviders: Provider[];\n};\n\n/** Register Bootstrap-shipped addon types (`bs-icon`, `bs-button`) standalone. */\nexport function withBootstrapAddons(): BootstrapAddonsFeature {\n return {\n ɵkind: 'addons',\n ɵproviders: [\n { provide: ADDON_TYPE_DEFINITIONS, useValue: BS_ICON_KIND, multi: true },\n { provide: ADDON_TYPE_DEFINITIONS, useValue: BS_BUTTON_KIND, multi: true },\n ],\n };\n}\n","import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { WrapperFieldInputs } from '@ng-forge/dynamic-forms/integration';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport type { BsIconAddon } from '../types/addons';\n\n/** Renderer for the `bs-icon` addon type. */\n@Component({\n selector: 'df-bs-icon-addon',\n imports: [AsyncPipe, DynamicTextPipe],\n template: `<i [class]=\"iconClass()\" [attr.aria-label]=\"(ariaLabel() | dynamicText | async) ?? null\"></i>`,\n host: {\n '[attr.aria-hidden]': 'hasAriaLabel() ? null : \"true\"',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BsIconAddonComponent {\n readonly addon = input.required<BsIconAddon>();\n /** Accepted for contract uniformity — `NgComponentOutlet` setInput is strict; every type must declare it. */\n readonly fieldInputs = input<WrapperFieldInputs | undefined>();\n\n protected readonly iconClass = computed(() => `bi bi-${this.addon().icon}`);\n protected readonly ariaLabel = computed(() => this.addon().ariaLabel);\n protected readonly hasAriaLabel = computed(() => this.addon().ariaLabel !== undefined);\n}\n","import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed } from '@angular/core';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { injectNgForgeAddonAction, NgForgeAddonAction } from '@ng-forge/dynamic-forms/integration';\nimport type { BsButtonAddon } from '../types/addons';\n\n/** Renderer for the `bs-button` addon type. */\n@Component({\n selector: 'df-bs-button-addon',\n imports: [DynamicTextPipe, AsyncPipe],\n hostDirectives: [NgForgeAddonAction],\n template: `\n <button\n type=\"button\"\n [class]=\"buttonClass()\"\n [disabled]=\"action.disabled() || action.loading()\"\n [attr.aria-label]=\"(ariaLabel() | dynamicText | async) ?? null\"\n [attr.aria-busy]=\"action.loading() || null\"\n (click)=\"action.dispatch()\"\n >\n @if (action.loading()) {\n <!-- Visually-hidden role=status text gives a reliable AT announcement (VO/JAWS/NVDA). -->\n <span class=\"spinner-border spinner-border-sm\" aria-hidden=\"true\"></span>\n <span class=\"visually-hidden\" role=\"status\">Loading…</span>\n } @else if (iconClass(); as ic) {\n <i [class]=\"ic\" aria-hidden=\"true\"></i>\n }\n @if (label(); as l) {\n <span>{{ l | dynamicText | async }}</span>\n }\n </button>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BsButtonAddonComponent {\n protected readonly action = injectNgForgeAddonAction<BsButtonAddon>();\n\n /** Re-exposed for template binding — same signal stored on the directive. */\n protected readonly addon = this.action.addon;\n\n protected readonly label = computed(() => this.addon().label);\n protected readonly ariaLabel = computed(() => this.addon().ariaLabel);\n protected readonly iconClass = computed(() => {\n const icon = this.addon().icon;\n return icon ? `bi bi-${icon}` : '';\n });\n protected readonly buttonClass = computed(() => `btn btn-outline-${this.addon().severity ?? 'secondary'}`);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ng-forge-dynamic-forms-bootstrap.mjs","mappings":";;;;;;;;;AA4Bc;;AAGH;;IAGA;;AAEA;;AAEA;AACP;AACA;AACA;;;;AAKE;AACA;;;AAGA;AACD;;;;;;AAOD;;AACA;;;AA/BiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC4BP;;AAGH;;;AAHU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAdlB;;;;AAlCS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACAD;;AACA;;AACA;;AAEQ;AAEA;AACf;;;;;AAIE;;AAEJ;AAEiB;AACf;;;;;AAIE;;AAEJ;AAEiB;AACf;;;;;AAIE;;AAEJ;;;;;;AAnCS;AACT;AACD;;;ACsFa;;;;;;;;AAMH;;IAGA;AACP;;;;AAIO;AACP;;;;;AAfiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAnFT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACDV;AACF;;;;ACsOc;;;;AAMH;;AAET,IAKS;;IAGQ;AAER;;AACA;;IAGU;;;AAtBA;AApFjB;AACE;AACA;AACD;AACD;gFAME;;AAEE;AACA;AACA;oBAEA;;AAEE;AACE;AACA;;;;;;AAOC;;;;AAIR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AApKO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqIR;AACE;AACA;AACD;AACD;4IAME;;AAEE;AACA;AACA;;;AAIE;AACE;AACA;;;;;;AAOC;;;;AAIR;AACF;;;;;;;;ACtHW;;;;AAIH;;IAGA;;;;;AAKP;;;AAGF;AAEI;;AAEF;AAIF;AACE;AACE;AAEA;AACE;;AAEJ;AAEA;AACE;AACA;;;AAIE;;AAEJ;;;AAIA;;;AAGI;AACE;AACA;;;AAEF;;AAEJ;;;AArDiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAlBlB;;;;AA1CS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IC8FD;;;;AAEA;;IAGC;AACR;AACE;;;;AAnBO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAVV;;;;;AApDC;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACYU;;;;AAIH;;;AAJU;;;;;;;;;;;;;;;;AAdlB;;;;AApBS;;;;;;;;;;;;;;;;;;;;;;;;ACgDE;;;;AAIH;;AAET;;AAGE;AAEA;AACE;;;;;AAZe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAVlB;;;;AAvCS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACkDE;;;;;;AAKH;;AAEA;;;AAPU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAlDT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC6EE;;AAGH;;;AAHU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAVlB;;;;AApES;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC6DE;;AAGH;;;AAHU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA3BlB;;;;AAlCS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACJL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACXI;AACJ;AACA;;;AAKE;AACE;;AAGF;AACE;;AAGF;AACE;;;AAKA;;;AAKA;;QAIF;AACA;;AAGI;;AACA;AACF;;;;AAKE;AACA;;;;AAKJ;AACF;AACF;;;AC5CA;;;AAIA;AACE;AACA;;AAGK;AACL;;AAEE;AACA;;AAEA;AACA;AACE;AACD;AACD;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACD;AACD;;AAEE;AACA;;AAEA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;AACD;;AAEE;AACA;AACA;AACA;AACD;;;;ACtHG;;;;AAOA;;AAEgC;AAClC;;AAGF;AACF;AAEA;AAEA;AACE;AACA;AACA;AACE;AACE;;;;AAKN;AACE;AACA;AACA;;AAIE;AACE;;QAKF;AACE;;;;;;AAkBF;AACA;;;AAGC;;AAEL;;;;;ICnFW;;AAEU;;;;AAEA;;;AAPR;;;;AATF;AACT;AACA;AACA;AACA;AACE;AACD;;AAEF;;;;;;;;;;ICuBoB;;;;;AAIA;;;;;AAIA;;;AAZR;;;;;;;;;;;;;;;;;;;;;;;;AA3BF;AACT;AACA;;AAEA;;;;;;;;;;;;;;;;;;;;AAoBC;;AAEF","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms-bootstrap/src/lib/fields/button/bs-button.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/checkbox/bs-checkbox.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/directives/input-constraints.directive.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/datepicker/bs-datepicker.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/addons/preset-actions.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/input/bs-input.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/multi-checkbox/bs-multi-checkbox.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/radio/bs-radio-group.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/radio/bs-radio.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/select/bs-select.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/slider/bs-slider.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/textarea/bs-textarea.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/toggle/bs-toggle.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/types/types.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/fields/button/bs-button.mapper.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/config/bootstrap-field-config.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/providers/bootstrap-providers.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/addons/bs-icon-addon.component.ts","../../../../packages/dynamic-forms-bootstrap/src/lib/addons/bs-button-addon.component.ts"],"sourcesContent":["import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { FormEvent } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { injectNgForgeAction, NgForgeActionHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsButtonProps } from './bs-button.type';\n\n@Component({\n selector: 'df-bs-button',\n imports: [DynamicTextPipe, AsyncPipe],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeActionHost],\n template: `\n @let buttonId = action.key() + '-button';\n <button\n [id]=\"buttonId\"\n [type]=\"buttonType()\"\n [disabled]=\"action.disabled()\"\n [class]=\"buttonClasses()\"\n [attr.tabindex]=\"action.tabIndex()\"\n [attr.data-testid]=\"buttonTestId()\"\n (click)=\"onClick()\"\n >\n {{ action.label() | dynamicText | async }}\n </button>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsButtonFieldComponent<TEvent extends FormEvent> {\n protected readonly action = injectNgForgeAction<TEvent>();\n\n readonly props = input<BsButtonProps>();\n\n /** Resolved button type — defaults to 'button' unless overridden via props. */\n readonly buttonType = computed(() => this.props()?.type ?? 'button');\n\n readonly buttonTestId = computed(() => `${this.buttonType()}-${this.action.key()}`);\n\n readonly buttonClasses = computed(() => {\n const p = this.props();\n const variant = p?.variant || 'primary';\n const outline = p?.outline ? 'outline-' : '';\n\n return [\n 'btn',\n `btn-${outline}${variant}`,\n p?.size === 'sm' && 'btn-sm',\n p?.size === 'lg' && 'btn-lg',\n p?.block && 'w-100',\n p?.active && 'active',\n this.action.className(),\n ]\n .filter(Boolean)\n .join(' ');\n });\n\n /** Submit buttons let the native form handle submission; other buttons dispatch via the directive. */\n onClick(): void {\n if (this.buttonType() === 'submit') return;\n this.action.dispatch();\n }\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsCheckboxProps } from './bs-checkbox.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-checkbox',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let checkboxId = ngf.key() + '-checkbox';\n\n <div\n class=\"form-check\"\n [class.form-switch]=\"props()?.switch\"\n [class.form-check-inline]=\"props()?.inline\"\n [class.form-check-reverse]=\"props()?.reverse\"\n [attr.hidden]=\"f().hidden() || null\"\n >\n <input\n ngForgeControl\n type=\"checkbox\"\n [formField]=\"f\"\n [id]=\"checkboxId\"\n [indeterminate]=\"props()?.indeterminate ?? false\"\n class=\"form-check-input\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n />\n <label [for]=\"checkboxId\" class=\"form-check-label\">\n {{ ngf.label() | dynamicText | async }}\n </label>\n </div>\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\" [attr.hidden]=\"f().hidden() || null\">{{ hint | dynamicText | async }}</div>\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsCheckboxFieldComponent {\n protected readonly ngf = injectNgForgeField<boolean>();\n\n readonly props = input<BsCheckboxProps>();\n}\n","import { Directive, ElementRef, inject, input } from '@angular/core';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\n\n/** Directive to set min, max, and step attributes on form inputs */\n@Directive({\n selector: '[dfBsInputConstraints]',\n})\nexport class InputConstraintsDirective {\n readonly dfMin = input<number | string | null | undefined>();\n readonly dfMax = input<number | string | null | undefined>();\n readonly dfStep = input<number | string | null | undefined>();\n\n private readonly el = inject<ElementRef<HTMLInputElement>>(ElementRef);\n\n private readonly minEffect = explicitEffect([this.dfMin], ([minValue]) => {\n const nativeElement = this.el.nativeElement;\n if (minValue !== null && minValue !== undefined) {\n nativeElement.setAttribute('min', String(minValue));\n } else {\n nativeElement.removeAttribute('min');\n }\n });\n\n private readonly maxEffect = explicitEffect([this.dfMax], ([maxValue]) => {\n const nativeElement = this.el.nativeElement;\n if (maxValue !== null && maxValue !== undefined) {\n nativeElement.setAttribute('max', String(maxValue));\n } else {\n nativeElement.removeAttribute('max');\n }\n });\n\n private readonly stepEffect = explicitEffect([this.dfStep], ([stepValue]) => {\n const nativeElement = this.el.nativeElement;\n if (stepValue !== null && stepValue !== undefined) {\n nativeElement.setAttribute('step', String(stepValue));\n } else {\n nativeElement.removeAttribute('step');\n }\n });\n}\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsDatepickerProps } from './bs-datepicker.type';\nimport { AsyncPipe } from '@angular/common';\nimport { InputConstraintsDirective } from '../../directives/input-constraints.directive';\n\n@Component({\n selector: 'df-bs-datepicker',\n imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';\n @if (p?.floatingLabel) {\n <!-- Floating label variant -->\n <div class=\"form-floating mb-3\">\n <input\n ngForgeControl\n dfBsInputConstraints\n [formField]=\"f\"\n [id]=\"inputId\"\n type=\"date\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [dfMin]=\"minAsString()\"\n [dfMax]=\"maxAsString()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"p?.size === 'sm'\"\n [class.form-control-lg]=\"p?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n @if (ngf.label()) {\n <label [for]=\"inputId\">{{ ngf.label() | dynamicText | async }}</label>\n }\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n }\n </div>\n } @else {\n <!-- Standard variant -->\n <div class=\"mb-3\">\n @if (ngf.label()) {\n <label [for]=\"inputId\" class=\"form-label\">{{ ngf.label() | dynamicText | async }}</label>\n }\n\n <input\n ngForgeControl\n dfBsInputConstraints\n [formField]=\"f\"\n [id]=\"inputId\"\n type=\"date\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [dfMin]=\"minAsString()\"\n [dfMax]=\"maxAsString()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"p?.size === 'sm'\"\n [class.form-control-lg]=\"p?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (p?.hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ p?.hint | dynamicText | async }}</div>\n }\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n styles: [\n `\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n})\nexport default class BsDatepickerFieldComponent {\n protected readonly ngf = injectNgForgeField<string>();\n\n readonly minDate = input<Date | string | null>(null);\n readonly maxDate = input<Date | string | null>(null);\n readonly startAt = input<Date | null>(null);\n readonly props = input<BsDatepickerProps>();\n\n // Helper methods to convert Date to string for HTML attributes\n readonly minAsString = computed(() => {\n const min = this.minDate();\n return min instanceof Date ? min.toISOString().split('T')[0] : min;\n });\n\n readonly maxAsString = computed(() => {\n const max = this.maxDate();\n return max instanceof Date ? max.toISOString().split('T')[0] : max;\n });\n}\n","import type { AddonActionContext, AddonActionPreset } from '@ng-forge/dynamic-forms';\nimport type { PresetCollaborators } from '@ng-forge/dynamic-forms/integration';\nimport { runPresetAction } from '@ng-forge/dynamic-forms/integration';\n\nexport type { PresetCollaborators };\n\n/** Bootstrap adapter binding for the shared preset runner. */\nexport function runBsPresetAction(preset: AddonActionPreset, ctx: AddonActionContext, collaborators: PresetCollaborators): Promise<void> {\n return runPresetAction(preset, ctx, collaborators, 'Bootstrap', 'bs-input');\n}\n","import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, forwardRef, inject, input, signal } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { AddonActionContext, AddonActionPreset, DynamicFormLogger } from '@ng-forge/dynamic-forms';\nimport { DfAddonSlot } from '@ng-forge/dynamic-forms/integration';\nimport { WrapperFieldInputs } from '@ng-forge/dynamic-forms/integration';\nimport { DynamicTextPipe, FIELD_SIGNAL_CONTEXT } from '@ng-forge/dynamic-forms/integration';\nimport {\n ADDON_PRESET_HANDLER,\n AddonPresetHandler,\n injectNgForgeAddons,\n injectNgForgeField,\n NgForgeAddons,\n NgForgeControl,\n NgForgeFieldHost,\n} from '@ng-forge/dynamic-forms/integration';\nimport { BOOTSTRAP_CONFIG } from '../../models/bootstrap-config.token';\nimport { runBsPresetAction } from '../../addons/preset-actions';\nimport { BS_INPUT_TYPE_OVERRIDE } from '../../tokens/input-type-override.token';\nimport { BsInputAddon, BsInputProps } from './bs-input.type';\n\n@Component({\n selector: 'df-bs-input',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl, DfAddonSlot],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost, NgForgeAddons],\n template: `\n @let f = ngf.field(); @let p = props(); @let inputId = ngf.key() + '-input';\n @if (floatingLabel()) {\n <div class=\"mb-3\">\n @if (ngfa.hasAddons()) {\n <div class=\"input-group\">\n @for (a of ngfa.prefixAddons(); track $index) {\n <span class=\"input-group-text\">\n <df-addon-slot [addon]=\"a\" [fieldInputs]=\"fieldInputs()\" [hidden]=\"ngfa.hiddenSignalCache().get(a)\" />\n </span>\n }\n <div class=\"form-floating\">\n <input\n ngForgeControl\n [formField]=\"f\"\n [id]=\"inputId\"\n [type]=\"type()\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"size() === 'sm'\"\n [class.form-control-lg]=\"size() === 'lg'\"\n [class.form-control-plaintext]=\"p?.plaintext\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n @if (ngf.label()) {\n <label [for]=\"inputId\">{{ ngf.label() | dynamicText | async }}</label>\n }\n </div>\n @for (a of ngfa.suffixAddons(); track $index) {\n <span class=\"input-group-text\">\n <df-addon-slot [addon]=\"a\" [fieldInputs]=\"fieldInputs()\" [hidden]=\"ngfa.hiddenSignalCache().get(a)\" />\n </span>\n }\n </div>\n } @else {\n <div class=\"form-floating\">\n <input\n ngForgeControl\n [formField]=\"f\"\n [id]=\"inputId\"\n [type]=\"type()\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"size() === 'sm'\"\n [class.form-control-lg]=\"size() === 'lg'\"\n [class.form-control-plaintext]=\"p?.plaintext\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n @if (ngf.label()) {\n <label [for]=\"inputId\">{{ ngf.label() | dynamicText | async }}</label>\n }\n </div>\n }\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n }\n </div>\n } @else {\n <div class=\"mb-3\">\n @if (ngf.label()) {\n <label [for]=\"inputId\" class=\"form-label\">{{ ngf.label() | dynamicText | async }}</label>\n }\n @if (ngfa.hasAddons()) {\n <div class=\"input-group\">\n @for (a of ngfa.prefixAddons(); track $index) {\n <span class=\"input-group-text\">\n <df-addon-slot [addon]=\"a\" [fieldInputs]=\"fieldInputs()\" [hidden]=\"ngfa.hiddenSignalCache().get(a)\" />\n </span>\n }\n <input\n ngForgeControl\n [formField]=\"f\"\n [id]=\"inputId\"\n [type]=\"type()\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"size() === 'sm'\"\n [class.form-control-lg]=\"size() === 'lg'\"\n [class.form-control-plaintext]=\"p?.plaintext\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n @for (a of ngfa.suffixAddons(); track $index) {\n <span class=\"input-group-text\">\n <df-addon-slot [addon]=\"a\" [fieldInputs]=\"fieldInputs()\" [hidden]=\"ngfa.hiddenSignalCache().get(a)\" />\n </span>\n }\n </div>\n } @else {\n <input\n ngForgeControl\n [formField]=\"f\"\n [id]=\"inputId\"\n [type]=\"type()\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"size() === 'sm'\"\n [class.form-control-lg]=\"size() === 'lg'\"\n [class.form-control-plaintext]=\"p?.plaintext\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n />\n }\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (p?.hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ p?.hint | dynamicText | async }}</div>\n }\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: BS_INPUT_TYPE_OVERRIDE,\n useFactory: () => signal<string | undefined>(undefined),\n },\n {\n // Adapter-specific preset semantics for `bs-button` addons (clear /\n // reset / paste / copy / toggle-password-visibility). The directive\n // (`NgForgeAddonAction`) delegates here when an addon configures a\n // `preset`. Per-bs-input-instance so the `typeOverride` signal is\n // scoped to one field.\n provide: ADDON_PRESET_HANDLER,\n useFactory: (): AddonPresetHandler => {\n const typeOverride = inject(BS_INPUT_TYPE_OVERRIDE);\n const fsc = inject(FIELD_SIGNAL_CONTEXT, { optional: true });\n const logger = inject(DynamicFormLogger);\n // forwardRef for baselineType only — host.props()?.type gates toggle-password-visibility.\n const host = inject(forwardRef(() => BsInputFieldComponent));\n return {\n run: (preset: string, ctx: AddonActionContext) => {\n const fieldKey = ctx.field.key;\n return runBsPresetAction(preset as AddonActionPreset, ctx, {\n typeOverride,\n fieldValueSetter: ctx.setValue,\n fieldDefaultValueGetter:\n fsc && fieldKey ? () => (fsc.defaultValues() as Record<string, unknown> | undefined)?.[fieldKey] : undefined,\n baselineType: () => host.props()?.type,\n logger,\n });\n },\n };\n },\n },\n ],\n styles: [\n `\n :host([hidden]) {\n display: none !important;\n }\n /* Per-side padding on the .input-group-text addon containers — each\n side is its own knob so consumers can tune the prefix's gap from\n the border (outer-left), the prefix's gap to the input text\n (inner-right), and the symmetric suffix counterparts independently. */\n :host {\n --df-bs-addon-prefix-outer-padding: 0.75rem;\n --df-bs-addon-prefix-inner-padding: 0.75rem;\n --df-bs-addon-suffix-inner-padding: 0.75rem;\n --df-bs-addon-suffix-outer-padding: 0.75rem;\n }\n :host ::ng-deep .input-group > .input-group-text:first-child {\n padding-left: var(--df-bs-addon-prefix-outer-padding);\n padding-right: var(--df-bs-addon-prefix-inner-padding);\n }\n :host ::ng-deep .input-group > .input-group-text:last-child {\n padding-left: var(--df-bs-addon-suffix-inner-padding);\n padding-right: var(--df-bs-addon-suffix-outer-padding);\n }\n /* bs-button addons live inside .input-group-text for visual parity with\n bs-icon (shared grey container). Strip the inner .btn's own visual\n chrome (border, background, focus ring) so the button blends with\n the addon container — done via Bootstrap's own CSS variables so the\n cascade reaches the rendered <button> in any theme. No !important. */\n :host ::ng-deep .input-group-text df-bs-button-addon .btn {\n --bs-btn-bg: transparent;\n --bs-btn-color: var(--bs-body-color);\n --bs-btn-border-color: transparent;\n --bs-btn-hover-bg: rgba(127, 127, 127, 0.12);\n --bs-btn-hover-color: var(--bs-body-color);\n --bs-btn-hover-border-color: transparent;\n --bs-btn-active-bg: rgba(127, 127, 127, 0.18);\n --bs-btn-active-border-color: transparent;\n --bs-btn-focus-shadow-rgb: 0, 0, 0;\n --bs-btn-padding-x: 0;\n --bs-btn-padding-y: 0;\n box-shadow: none;\n }\n :host ::ng-deep .input-group-text df-bs-button-addon .btn:focus-visible {\n /* 2px solid for WCAG 2.4.11; currentColor adapts to light/dark themes. */\n box-shadow: none;\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n `,\n ],\n})\nexport default class BsInputFieldComponent {\n private bootstrapConfig = inject(BOOTSTRAP_CONFIG, { optional: true });\n\n protected readonly ngf = injectNgForgeField<string>();\n protected readonly ngfa = injectNgForgeAddons<BsInputAddon>();\n\n readonly props = input<BsInputProps>();\n\n /**\n * Wrapper-style host bag pushed by `DfFieldOutlet`. Declared at the\n * component level so `setInputIfDeclared` (which uses\n * `reflectComponentType`) can write it.\n */\n readonly fieldInputs = input<WrapperFieldInputs | undefined>();\n\n /** Per-instance type override populated by `toggle-password-visibility` preset. */\n private readonly typeOverride = inject(BS_INPUT_TYPE_OVERRIDE);\n\n readonly size = computed(() => this.props()?.size ?? this.bootstrapConfig?.size);\n readonly floatingLabel = computed(() => this.props()?.floatingLabel ?? this.bootstrapConfig?.floatingLabel ?? false);\n\n /** Override (set by `toggle-password-visibility` preset) wins over `props().type`. */\n protected readonly type = computed(() => this.typeOverride() ?? this.props()?.type ?? 'text');\n}\n","import { ChangeDetectionStrategy, Component, computed, input, linkedSignal } from '@angular/core';\nimport { FieldOption, ValueType } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { injectNgForgeField, NgForgeFieldHost, isEqual, NgForgeControl } from '@ng-forge/dynamic-forms/integration';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\nimport { BsMultiCheckboxProps } from './bs-multi-checkbox.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-multi-checkbox',\n imports: [DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field();\n @let checked = checkedValuesMap();\n @if (ngf.label(); as label) {\n <div class=\"form-label\">{{ label | dynamicText | async }}</div>\n }\n\n <div class=\"checkbox-group\">\n @for (option of options(); track option.value; let i = $index) {\n <div\n class=\"form-check\"\n [class.form-switch]=\"props()?.switch\"\n [class.form-check-inline]=\"props()?.inline\"\n [class.form-check-reverse]=\"props()?.reverse\"\n >\n <input\n ngForgeControl\n type=\"checkbox\"\n [id]=\"ngf.key() + '_' + i\"\n [checked]=\"checked['' + option.value]\"\n [disabled]=\"f().disabled() || option.disabled\"\n (change)=\"onCheckboxChange(option, $event)\"\n class=\"form-check-input\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n />\n <label [for]=\"ngf.key() + '_' + i\" class=\"form-check-label\">\n {{ option.label | dynamicText | async }}\n </label>\n </div>\n }\n </div>\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ hint | dynamicText | async }}</div>\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n .checkbox-group {\n margin-bottom: 0.5rem;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsMultiCheckboxFieldComponent {\n protected readonly ngf = injectNgForgeField<ValueType[]>();\n\n readonly options = input<FieldOption<ValueType>[]>([]);\n readonly props = input<BsMultiCheckboxProps>();\n\n /** Computed map of checked option values for O(1) lookup in template */\n readonly checkedValuesMap = computed(() => {\n const map: Record<string, boolean> = {};\n for (const opt of this.valueViewModel()) {\n map[String(opt.value)] = true;\n }\n return map;\n });\n\n valueViewModel = linkedSignal<FieldOption<ValueType>[]>(\n () => {\n const currentValues = this.ngf.field()().value();\n return this.options().filter((option) => currentValues.includes(option.value));\n },\n { equal: isEqual },\n );\n\n constructor() {\n explicitEffect([this.valueViewModel], ([selectedOptions]: [FieldOption<ValueType>[]]) => {\n const selectedValues = selectedOptions.map((option: FieldOption<ValueType>) => option.value);\n\n if (!isEqual(selectedValues, this.ngf.field()().value())) {\n this.ngf.field()().value.set(selectedValues);\n }\n });\n\n explicitEffect([this.options], ([options]: [FieldOption<ValueType>[]]) => {\n const values = options.map((option: FieldOption<ValueType>) => option.value);\n const uniqueValues = new Set(values);\n\n if (values.length !== uniqueValues.size) {\n const duplicates = values.filter((value, index) => values.indexOf(value) !== index);\n throw new Error(`Duplicate option values detected in bs-multi-checkbox: ${duplicates.join(', ')}`);\n }\n });\n }\n\n onCheckboxChange(option: FieldOption<ValueType>, event: Event): void {\n const checked = (event.target as HTMLInputElement).checked;\n this.valueViewModel.update((currentOptions: FieldOption<ValueType>[]) => {\n if (checked) {\n return currentOptions.some((opt: FieldOption<ValueType>) => opt.value === option.value)\n ? currentOptions\n : [...currentOptions, option];\n } else {\n return currentOptions.filter((opt: FieldOption<ValueType>) => opt.value !== option.value);\n }\n });\n }\n}\n","import { ChangeDetectionStrategy, Component, inject, input, model } from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\nimport { DynamicText, FieldOption, ValueType } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, NgForgeField } from '@ng-forge/dynamic-forms/integration';\nimport { AsyncPipe } from '@angular/common';\n\nexport interface BsRadioGroupProps {\n /** Display radio buttons inline (horizontal) */\n inline?: boolean;\n /** Reverse the position of the radio button and label */\n reverse?: boolean;\n /** Display as button group instead of radio buttons */\n buttonGroup?: boolean;\n /** Button size when using button group */\n buttonSize?: 'sm' | 'lg';\n /** Hint text to display below the radio group */\n hint?: DynamicText;\n}\n\n/**\n * Bootstrap radio group implementing FormValueControl. Rendered inside\n * `df-bs-radio` — each `<input type=\"radio\">` carries `ngForgeControl`, so\n * the marker absorbs meta + aria from the ambient parent NgForgeField.\n */\n@Component({\n selector: 'df-bs-radio-group',\n imports: [DynamicTextPipe, AsyncPipe, NgForgeControl],\n host: {\n role: 'radiogroup',\n '[attr.aria-invalid]': 'parentField?.ariaInvalid() || null',\n '[attr.aria-required]': 'parentField?.ariaRequired()',\n '[attr.aria-describedby]': 'parentField?.ariaDescribedBy()',\n },\n template: `\n @let props = properties();\n @if (props?.buttonGroup) {\n <div class=\"btn-group\" role=\"group\" [attr.aria-label]=\"label() | dynamicText | async\">\n @for (option of options(); track option.value; let i = $index) {\n <input\n ngForgeControl\n type=\"radio\"\n [name]=\"name()\"\n [value]=\"option.value\"\n [checked]=\"value() === option.value\"\n (change)=\"onRadioChange(option.value)\"\n [disabled]=\"disabled() || option.disabled || false\"\n class=\"btn-check\"\n [id]=\"name() + '_' + i\"\n autocomplete=\"off\"\n />\n <label\n class=\"btn btn-outline-primary\"\n [class.btn-sm]=\"props?.buttonSize === 'sm'\"\n [class.btn-lg]=\"props?.buttonSize === 'lg'\"\n [for]=\"name() + '_' + i\"\n >\n {{ option.label | dynamicText | async }}\n </label>\n }\n </div>\n } @else {\n @for (option of options(); track option.value; let i = $index) {\n <div class=\"form-check\" [class.form-check-inline]=\"props?.inline\" [class.form-check-reverse]=\"props?.reverse\">\n <input\n ngForgeControl\n type=\"radio\"\n [name]=\"name()\"\n [value]=\"option.value\"\n [checked]=\"value() === option.value\"\n (change)=\"onRadioChange(option.value)\"\n [disabled]=\"disabled() || option.disabled || false\"\n class=\"form-check-input\"\n [id]=\"name() + '_' + i\"\n />\n <label class=\"form-check-label\" [for]=\"name() + '_' + i\">\n {{ option.label | dynamicText | async }}\n </label>\n </div>\n }\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BsRadioGroupComponent implements FormValueControl<ValueType | undefined> {\n protected readonly parentField = inject(NgForgeField, { optional: true });\n\n // Value model - FormField directive binds form value to this\n readonly value = model<ValueType | undefined>(undefined);\n\n // Optional FormValueControl properties - Field directive will bind these\n readonly disabled = input<boolean>(false);\n readonly readonly = input<boolean>(false);\n readonly name = input<string>('');\n\n // Component-specific inputs\n readonly label = input<DynamicText>();\n readonly options = input.required<FieldOption<ValueType>[]>();\n readonly properties = input<BsRadioGroupProps>();\n\n /** Handle radio button change event */\n protected onRadioChange(newValue: ValueType): void {\n if (!this.disabled() && !this.readonly()) {\n this.value.set(newValue);\n }\n }\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { FieldOption, ValueType } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsRadioProps } from './bs-radio.type';\nimport { AsyncPipe } from '@angular/common';\nimport { BsRadioGroupComponent } from './bs-radio-group.component';\n\n@Component({\n selector: 'df-bs-radio',\n imports: [BsRadioGroupComponent, FormField, DynamicTextPipe, AsyncPipe],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field();\n\n <div class=\"mb-3\">\n @if (ngf.label(); as label) {\n <div class=\"form-label\">{{ label | dynamicText | async }}</div>\n }\n\n <df-bs-radio-group [formField]=\"f\" [label]=\"ngf.label()\" [options]=\"options()\" [properties]=\"props()\" />\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ hint | dynamicText | async }}</div>\n }\n </div>\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsRadioFieldComponent {\n protected readonly ngf = injectNgForgeField<ValueType>();\n\n readonly options = input<FieldOption<ValueType>[]>([]);\n readonly props = input<BsRadioProps>();\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { FieldOption } from '@ng-forge/dynamic-forms';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsSelectProps } from './bs-select.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-select',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let selectId = ngf.key() + '-select';\n\n <div class=\"mb-3\">\n @if (ngf.label(); as label) {\n <label [for]=\"selectId\" class=\"form-label\">{{ label | dynamicText | async }}</label>\n }\n <select\n ngForgeControl\n [formField]=\"f\"\n [id]=\"selectId\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-select\"\n [class.form-select-sm]=\"props()?.size === 'sm'\"\n [class.form-select-lg]=\"props()?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [multiple]=\"props()?.multiple || false\"\n [size]=\"props()?.htmlSize\"\n >\n @if (ngf.placeholder(); as placeholder) {\n <option value=\"\" disabled [selected]=\"!f().value()\">{{ placeholder | dynamicText | async }}</option>\n }\n @for (option of options(); track option.value) {\n <option [value]=\"option.value\" [disabled]=\"option.disabled || false\" [selected]=\"isSelected(option.value, f().value())\">\n {{ option.label | dynamicText | async }}\n </option>\n }\n </select>\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ hint | dynamicText | async }}</div>\n }\n </div>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n styles: [\n `\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n})\nexport default class BsSelectFieldComponent {\n protected readonly ngf = injectNgForgeField<string>();\n\n readonly options = input<FieldOption<string>[]>([]);\n readonly props = input<BsSelectProps>();\n\n defaultCompare = Object.is;\n\n protected isSelected(optionValue: string, fieldValue: string | string[] | null): boolean {\n const compareWith = this.props()?.compareWith || this.defaultCompare;\n\n if (Array.isArray(fieldValue)) {\n return fieldValue.some((v) => compareWith(v, optionValue));\n }\n\n return fieldValue !== null && compareWith(fieldValue, optionValue);\n }\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsSliderProps } from './bs-slider.type';\nimport { AsyncPipe } from '@angular/common';\nimport { InputConstraintsDirective } from '../../directives/input-constraints.directive';\n\n@Component({\n selector: 'df-bs-slider',\n imports: [FormField, DynamicTextPipe, AsyncPipe, InputConstraintsDirective, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let inputId = ngf.key() + '-input';\n\n <div class=\"mb-3\">\n @if (ngf.label(); as label) {\n <label [for]=\"inputId\" class=\"form-label\">\n {{ label | dynamicText | async }}\n @if (props()?.showValue) {\n <span class=\"ms-2 badge bg-secondary\"> {{ props()?.valuePrefix }}{{ f().value() }}{{ props()?.valueSuffix }} </span>\n }\n </label>\n }\n\n <input\n ngForgeControl\n type=\"range\"\n dfBsInputConstraints\n [formField]=\"f\"\n [id]=\"inputId\"\n [dfMin]=\"f().min?.() ?? props()?.min ?? min()\"\n [dfMax]=\"f().max?.() ?? props()?.max ?? max()\"\n [dfStep]=\"step() ?? props()?.step ?? 1\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-range\"\n />\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ hint | dynamicText | async }}</div>\n }\n </div>\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsSliderFieldComponent {\n protected readonly ngf = injectNgForgeField<number>();\n\n readonly min = input<number>(0);\n readonly max = input<number>(100);\n readonly step = input<number>();\n\n readonly props = input<BsSliderProps>();\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsTextareaProps } from './bs-textarea.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-textarea',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let p = props(); @let textareaId = ngf.key() + '-textarea';\n @if (p?.floatingLabel) {\n <!-- Floating label variant -->\n <div class=\"form-floating mb-3\">\n <textarea\n ngForgeControl\n [formField]=\"f\"\n [id]=\"textareaId\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"p?.size === 'sm'\"\n [class.form-control-lg]=\"p?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n ></textarea>\n\n @if (ngf.label()) {\n <label [for]=\"textareaId\">{{ ngf.label() | dynamicText | async }}</label>\n }\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (p?.hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ p?.hint | dynamicText | async }}</div>\n }\n </div>\n } @else {\n <!-- Standard variant -->\n <div class=\"mb-3\">\n @if (ngf.label()) {\n <label [for]=\"textareaId\" class=\"form-label\">{{ ngf.label() | dynamicText | async }}</label>\n }\n\n <textarea\n ngForgeControl\n [formField]=\"f\"\n [id]=\"textareaId\"\n [placeholder]=\"(ngf.placeholder() | dynamicText | async) ?? ''\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n class=\"form-control\"\n [class.form-control-sm]=\"p?.size === 'sm'\"\n [class.form-control-lg]=\"p?.size === 'lg'\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [class.is-valid]=\"f().valid() && f().touched() && p?.validFeedback\"\n ></textarea>\n\n @if (p?.validFeedback && f().valid() && f().touched()) {\n <div class=\"valid-feedback d-block\">\n {{ p?.validFeedback | dynamicText | async }}\n </div>\n }\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (p?.hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\">{{ p?.hint | dynamicText | async }}</div>\n }\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n styles: [\n `\n :host([hidden]) {\n display: none !important;\n }\n `,\n ],\n})\nexport default class BsTextareaFieldComponent {\n protected readonly ngf = injectNgForgeField<string>();\n\n readonly props = input<BsTextareaProps>();\n}\n","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { NgForgeControl, injectNgForgeField, NgForgeFieldHost } from '@ng-forge/dynamic-forms/integration';\nimport { BsToggleProps } from './bs-toggle.type';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'df-bs-toggle',\n imports: [FormField, DynamicTextPipe, AsyncPipe, NgForgeControl],\n styleUrl: '../../styles/_form-field.scss',\n hostDirectives: [NgForgeFieldHost],\n template: `\n @let f = ngf.field(); @let inputId = ngf.key() + '-input';\n\n <div\n class=\"form-check form-switch\"\n [class.form-check-inline]=\"props()?.inline\"\n [class.form-check-reverse]=\"props()?.reverse\"\n [class.form-switch-sm]=\"props()?.size === 'sm'\"\n [class.form-switch-lg]=\"props()?.size === 'lg'\"\n [attr.hidden]=\"f().hidden() || null\"\n >\n <input\n ngForgeControl\n type=\"checkbox\"\n [formField]=\"f\"\n [id]=\"inputId\"\n class=\"form-check-input\"\n [class.is-invalid]=\"f().invalid() && f().touched()\"\n [attr.tabindex]=\"ngf.tabIndex()\"\n />\n <label [for]=\"inputId\" class=\"form-check-label\">\n {{ ngf.label() | dynamicText | async }}\n </label>\n </div>\n\n @if (ngf.errorsToDisplay()[0]; as error) {\n <div class=\"invalid-feedback d-block\" [id]=\"ngf.errorId()\" role=\"alert\">{{ error.message }}</div>\n } @else if (props()?.hint; as hint) {\n <div class=\"form-text\" [id]=\"ngf.hintId()\" [attr.hidden]=\"f().hidden() || null\">{{ hint | dynamicText | async }}</div>\n }\n `,\n styles: [\n `\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n /* Custom size variants for switches */\n .form-switch-sm .form-check-input {\n width: 1.75rem;\n height: 1rem;\n font-size: 0.875rem;\n }\n\n .form-switch-lg .form-check-input {\n width: 3rem;\n height: 1.75rem;\n font-size: 1.125rem;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class BsToggleFieldComponent {\n protected readonly ngf = injectNgForgeField<boolean>();\n\n readonly props = input<BsToggleProps>();\n}\n","/**\n * Bootstrap field type constants\n * Based on available field components in the /fields folder\n */\nexport const BsField = {\n Input: 'input',\n Select: 'select',\n Checkbox: 'checkbox',\n Button: 'button',\n Submit: 'submit',\n Next: 'next',\n Previous: 'previous',\n AddArrayItem: 'add-array-item',\n PrependArrayItem: 'prepend-array-item',\n InsertArrayItem: 'insert-array-item',\n RemoveArrayItem: 'remove-array-item',\n PopArrayItem: 'pop-array-item',\n ShiftArrayItem: 'shift-array-item',\n Textarea: 'textarea',\n Radio: 'radio',\n MultiCheckbox: 'multi-checkbox',\n Datepicker: 'datepicker',\n Slider: 'slider',\n Toggle: 'toggle',\n} as const;\n\nexport type BsFieldType = (typeof BsField)[keyof typeof BsField];\n","import { computed, inject, isSignal, Signal } from '@angular/core';\nimport { FieldDef } from '@ng-forge/dynamic-forms';\nimport { ARRAY_CONTEXT, buildBaseInputs, DEFAULT_PROPS } from '@ng-forge/dynamic-forms/integration';\n\n/**\n * Generic button mapper for custom events or basic buttons.\n * For specific button types (submit, next, prev, add/remove array items),\n * use the dedicated field types and their specific mappers.\n *\n * @param fieldDef The button field definition\n * @returns Signal containing Record of input names to values for ngComponentOutlet\n */\nexport function buttonFieldMapper(fieldDef: FieldDef<Record<string, unknown>>): Signal<Record<string, unknown>> {\n const defaultProps = inject(DEFAULT_PROPS);\n const arrayContext = inject(ARRAY_CONTEXT, { optional: true });\n\n return computed(() => {\n const baseInputs = buildBaseInputs(fieldDef, defaultProps());\n\n const inputs: Record<string, unknown> = {\n ...baseInputs,\n };\n\n if (fieldDef.disabled !== undefined) {\n inputs['disabled'] = fieldDef.disabled;\n }\n\n if (fieldDef.hidden !== undefined) {\n inputs['hidden'] = fieldDef.hidden;\n }\n\n // Add event binding for button events\n if ('event' in fieldDef && fieldDef.event !== undefined) {\n inputs['event'] = fieldDef.event;\n }\n\n // Add eventArgs binding if provided\n if ('eventArgs' in fieldDef && fieldDef.eventArgs !== undefined) {\n inputs['eventArgs'] = fieldDef.eventArgs;\n }\n\n // Add eventContext for token resolution (supports $template, $arrayKey, $index, etc.)\n const template = 'template' in fieldDef ? fieldDef.template : undefined;\n if (template || arrayContext) {\n // Read signal value if index is a signal (supports differential updates)\n const getIndex = () => {\n if (!arrayContext) return -1;\n return isSignal(arrayContext.index) ? arrayContext.index() : arrayContext.index;\n };\n\n inputs['eventContext'] = {\n key: fieldDef.key,\n index: getIndex(),\n arrayKey: arrayContext?.arrayKey ?? '',\n formValue: arrayContext?.formValue ?? {},\n template,\n };\n }\n\n return inputs;\n });\n}\n","import { FieldTypeDefinition } from '@ng-forge/dynamic-forms/integration';\nimport {\n addArrayItemButtonMapper,\n checkboxFieldMapper,\n datepickerFieldMapper,\n insertArrayItemButtonMapper,\n optionsFieldMapper,\n popArrayItemButtonMapper,\n prependArrayItemButtonMapper,\n removeArrayItemButtonMapper,\n shiftArrayItemButtonMapper,\n valueFieldMapper,\n} from '@ng-forge/dynamic-forms/integration';\nimport { BsField } from '../types/types';\nimport { buttonFieldMapper } from '../fields/button/bs-button.mapper';\nimport { nextButtonFieldMapper, previousButtonFieldMapper, submitButtonFieldMapper } from '../fields/button/bs-specific-button.mapper';\n\nconst VALUE_FIELD_TYPES_BASE = {\n renderReadyWhen: ['field'],\n} as const;\n\nconst BUTTON_FIELD_TYPES_BASE = {\n renderReadyWhen: [],\n valueHandling: 'exclude',\n} as const;\n\nexport const BOOTSTRAP_FIELD_TYPES: FieldTypeDefinition[] = [\n {\n name: BsField.Input,\n loadComponent: () => import('../fields/input/bs-input.component'),\n mapper: valueFieldMapper,\n propsToMeta: ['type'],\n scope: ['text-input', 'numeric'],\n addons: {\n slots: ['prefix', 'suffix'],\n },\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Select,\n loadComponent: () => import('../fields/select/bs-select.component'),\n mapper: optionsFieldMapper,\n scope: 'single-select',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Checkbox,\n loadComponent: () => import('../fields/checkbox/bs-checkbox.component'),\n mapper: checkboxFieldMapper,\n scope: 'boolean',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Button,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: buttonFieldMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Submit,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: submitButtonFieldMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Next,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: nextButtonFieldMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Previous,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: previousButtonFieldMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.AddArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: addArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.PrependArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: prependArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.InsertArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: insertArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.RemoveArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: removeArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.PopArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: popArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.ShiftArrayItem,\n loadComponent: () => import('../fields/button/bs-button.component'),\n mapper: shiftArrayItemButtonMapper,\n ...BUTTON_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Textarea,\n loadComponent: () => import('../fields/textarea/bs-textarea.component'),\n mapper: valueFieldMapper,\n propsToMeta: ['rows'],\n scope: 'text-input',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Radio,\n loadComponent: () => import('../fields/radio/bs-radio.component'),\n mapper: optionsFieldMapper,\n scope: 'single-select',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.MultiCheckbox,\n loadComponent: () => import('../fields/multi-checkbox/bs-multi-checkbox.component'),\n mapper: optionsFieldMapper,\n scope: 'multi-select',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Datepicker,\n loadComponent: () => import('../fields/datepicker/bs-datepicker.component'),\n mapper: datepickerFieldMapper,\n scope: 'date',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Slider,\n loadComponent: () => import('../fields/slider/bs-slider.component'),\n mapper: valueFieldMapper,\n scope: 'numeric',\n ...VALUE_FIELD_TYPES_BASE,\n },\n {\n name: BsField.Toggle,\n loadComponent: () => import('../fields/toggle/bs-toggle.component'),\n mapper: checkboxFieldMapper,\n scope: 'boolean',\n ...VALUE_FIELD_TYPES_BASE,\n },\n];\n","import type { Provider } from '@angular/core';\nimport { DynamicFormError, type AddonTypeDefinition } from '@ng-forge/dynamic-forms';\nimport { ADDON_TYPE_DEFINITIONS, type FieldTypeDefinition } from '@ng-forge/dynamic-forms/integration';\nimport { BOOTSTRAP_FIELD_TYPES } from '../config/bootstrap-field-config';\nimport { BootstrapConfig } from '../models/bootstrap-config';\nimport { BOOTSTRAP_CONFIG } from '../models/bootstrap-config.token';\nimport type { BsButtonAddon, BsIconAddon } from '../types/addons';\n\n/** Field type definitions for Bootstrap components. */\nexport type BootstrapFieldTypes = FieldTypeDefinition[];\n\ntype BootstrapConfigFeature = {\n ɵkind: 'bootstrap-config';\n ɵproviders: Provider[];\n};\n\n/**\n * Default `withBootstrapFields()` shape — field defs + the auto-included\n * addons feature so `bs-icon` / `bs-button` work out of the box.\n */\ntype BootstrapFieldsWithAddons = [...BootstrapFieldTypes, BootstrapAddonsFeature];\n\ntype BootstrapFieldsWithConfig = [...BootstrapFieldTypes, BootstrapAddonsFeature, BootstrapConfigFeature];\n\n/**\n * Provides Bootstrap field type definitions for the dynamic form system,\n * with Bootstrap-shipped addon types (`bs-icon`, `bs-button`) auto-included\n * so addons work out of the box.\n *\n * @param config - Optional global configuration for Bootstrap form fields\n * @returns Tuple of field type definitions, the addons feature, and\n * optionally a config feature.\n */\nexport function withBootstrapFields(): BootstrapFieldsWithAddons;\nexport function withBootstrapFields(config: BootstrapConfig): BootstrapFieldsWithConfig;\nexport function withBootstrapFields(config: BootstrapConfig | undefined): BootstrapFieldsWithAddons | BootstrapFieldsWithConfig;\nexport function withBootstrapFields(config?: BootstrapConfig): BootstrapFieldsWithAddons | BootstrapFieldsWithConfig {\n // Always include the addons feature — bs-icon / bs-button are part of\n // the canonical Bootstrap surface.\n const base: unknown[] = [...BOOTSTRAP_FIELD_TYPES, withBootstrapAddons()];\n\n if (config) {\n base.push({\n ɵkind: 'bootstrap-config',\n ɵproviders: [{ provide: BOOTSTRAP_CONFIG, useValue: config }],\n } satisfies BootstrapConfigFeature);\n return base as BootstrapFieldsWithConfig;\n }\n\n return base as BootstrapFieldsWithAddons;\n}\n\n/* -- Bootstrap addon types --------------------------------------------- */\n\nconst BS_ICON_KIND: AddonTypeDefinition<BsIconAddon> = {\n type: 'bs-icon',\n loadComponent: () => import('../addons/bs-icon-addon.component').then((m) => m.BsIconAddonComponent),\n validate: (addon, fieldKey) => {\n if (typeof addon.icon !== 'string' || addon.icon.length === 0) {\n throw new DynamicFormError(`Addon type 'bs-icon' requires a non-empty 'icon' string (field: '${fieldKey}').`);\n }\n },\n};\n\nconst BS_BUTTON_KIND: AddonTypeDefinition<BsButtonAddon> = {\n type: 'bs-button',\n loadComponent: () => import('../addons/bs-button-addon.component').then((m) => m.BsButtonAddonComponent),\n validate: (addon, fieldKey) => {\n // Exactly one of preset / actionRef / action — validator drops the addon\n // (with warning) if the rule is violated.\n const set = [addon.preset, addon.actionRef, addon.action].filter((v) => v !== undefined);\n if (set.length > 1) {\n throw new DynamicFormError(\n `Addon type 'bs-button' on field '${fieldKey}' has more than one of preset/actionRef/action — exactly one allowed.`,\n );\n }\n // Icon-only buttons require ariaLabel for screen readers.\n if (addon.icon && !addon.label && !addon.ariaLabel) {\n throw new DynamicFormError(`Addon type 'bs-button' on field '${fieldKey}' is icon-only — provide 'ariaLabel' for accessibility.`);\n }\n },\n};\n\n/**\n * Feature type discriminant for the Bootstrap addons feature. Matches core's\n * `'addons'` type so providers flow through the standard addon-type pipeline\n * in `provideDynamicForm`.\n */\ntype BootstrapAddonsFeature = {\n ɵkind: 'addons';\n ɵproviders: Provider[];\n};\n\n/** Register Bootstrap-shipped addon types (`bs-icon`, `bs-button`) standalone. */\nexport function withBootstrapAddons(): BootstrapAddonsFeature {\n return {\n ɵkind: 'addons',\n ɵproviders: [\n { provide: ADDON_TYPE_DEFINITIONS, useValue: BS_ICON_KIND, multi: true },\n { provide: ADDON_TYPE_DEFINITIONS, useValue: BS_BUTTON_KIND, multi: true },\n ],\n };\n}\n","import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { WrapperFieldInputs } from '@ng-forge/dynamic-forms/integration';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport type { BsIconAddon } from '../types/addons';\n\n/** Renderer for the `bs-icon` addon type. */\n@Component({\n selector: 'df-bs-icon-addon',\n imports: [AsyncPipe, DynamicTextPipe],\n template: `<i [class]=\"iconClass()\" [attr.aria-label]=\"(ariaLabel() | dynamicText | async) ?? null\"></i>`,\n host: {\n '[attr.aria-hidden]': 'hasAriaLabel() ? null : \"true\"',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BsIconAddonComponent {\n readonly addon = input.required<BsIconAddon>();\n /** Accepted for contract uniformity — `NgComponentOutlet` setInput is strict; every type must declare it. */\n readonly fieldInputs = input<WrapperFieldInputs | undefined>();\n\n protected readonly iconClass = computed(() => `bi bi-${this.addon().icon}`);\n protected readonly ariaLabel = computed(() => this.addon().ariaLabel);\n protected readonly hasAriaLabel = computed(() => this.addon().ariaLabel !== undefined);\n}\n","import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed } from '@angular/core';\nimport { DynamicTextPipe } from '@ng-forge/dynamic-forms/integration';\nimport { injectNgForgeAddonAction, NgForgeAddonAction } from '@ng-forge/dynamic-forms/integration';\nimport type { BsButtonAddon } from '../types/addons';\n\n/** Renderer for the `bs-button` addon type. */\n@Component({\n selector: 'df-bs-button-addon',\n imports: [DynamicTextPipe, AsyncPipe],\n hostDirectives: [NgForgeAddonAction],\n template: `\n <button\n type=\"button\"\n [class]=\"buttonClass()\"\n [disabled]=\"action.disabled() || action.loading()\"\n [attr.aria-label]=\"(ariaLabel() | dynamicText | async) ?? null\"\n [attr.aria-busy]=\"action.loading() || null\"\n (click)=\"action.dispatch()\"\n >\n @if (action.loading()) {\n <!-- Visually-hidden role=status text gives a reliable AT announcement (VO/JAWS/NVDA). -->\n <span class=\"spinner-border spinner-border-sm\" aria-hidden=\"true\"></span>\n <span class=\"visually-hidden\" role=\"status\">Loading…</span>\n } @else if (iconClass(); as ic) {\n <i [class]=\"ic\" aria-hidden=\"true\"></i>\n }\n @if (label(); as l) {\n <span>{{ l | dynamicText | async }}</span>\n }\n </button>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BsButtonAddonComponent {\n protected readonly action = injectNgForgeAddonAction<BsButtonAddon>();\n\n /** Re-exposed for template binding — same signal stored on the directive. */\n protected readonly addon = this.action.addon;\n\n protected readonly label = computed(() => this.addon().label);\n protected readonly ariaLabel = computed(() => this.addon().ariaLabel);\n protected readonly iconClass = computed(() => {\n const icon = this.addon().icon;\n return icon ? `bi bi-${icon}` : '';\n });\n protected readonly buttonClass = computed(() => `btn btn-outline-${this.addon().severity ?? 'secondary'}`);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-forge/dynamic-forms-bootstrap",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Bootstrap 5 integration for @ng-forge/dynamic-forms. Pre-built Bootstrap form components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,15 +38,16 @@
|
|
|
38
38
|
"node": "^22.22.3 || ^24.15.0 || >=26.0.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@angular/common": "
|
|
42
|
-
"@angular/core": "
|
|
43
|
-
"@angular/forms": "
|
|
44
|
-
"@ng-forge/dynamic-forms": "~1.0.0
|
|
41
|
+
"@angular/common": "^22.0.0",
|
|
42
|
+
"@angular/core": "^22.0.0",
|
|
43
|
+
"@angular/forms": "^22.0.0",
|
|
44
|
+
"@ng-forge/dynamic-forms": "~1.0.0",
|
|
45
45
|
"rxjs": ">=7.0.0"
|
|
46
46
|
},
|
|
47
|
-
"module": "fesm2022/ng-forge-dynamic-forms-bootstrap.mjs",
|
|
48
|
-
"typings": "types/ng-forge-dynamic-forms-bootstrap.d.ts",
|
|
49
47
|
"dependencies": {
|
|
48
|
+
"ngxtension": ">=4.0.0",
|
|
50
49
|
"tslib": "^2.3.0"
|
|
51
|
-
}
|
|
52
|
-
|
|
50
|
+
},
|
|
51
|
+
"module": "fesm2022/ng-forge-dynamic-forms-bootstrap.mjs",
|
|
52
|
+
"typings": "types/ng-forge-dynamic-forms-bootstrap.d.ts"
|
|
53
|
+
}
|
|
@@ -28,8 +28,8 @@ type BsPreviousButtonField = Omit<BsButtonField<PreviousPageEvent>, 'event' | 't
|
|
|
28
28
|
type: 'previous';
|
|
29
29
|
};
|
|
30
30
|
/** Add array item button field - dispatches AppendArrayItemEvent */
|
|
31
|
-
type
|
|
32
|
-
type: 'addArrayItem';
|
|
31
|
+
type BsAddArrayItemButtonField = Omit<BsButtonField<AppendArrayItemEvent>, 'event' | 'type' | 'eventArgs'> & {
|
|
32
|
+
type: 'add-array-item' | 'addArrayItem';
|
|
33
33
|
/**
|
|
34
34
|
* The key of the array field to add items to.
|
|
35
35
|
* Required when the button is placed outside the array.
|
|
@@ -44,8 +44,8 @@ type AddArrayItemButtonField = Omit<BsButtonField<AppendArrayItemEvent>, 'event'
|
|
|
44
44
|
template: ArrayAllowedChildren | readonly ArrayAllowedChildren[];
|
|
45
45
|
};
|
|
46
46
|
/** Prepend array item button field - dispatches PrependArrayItemEvent (adds at beginning) */
|
|
47
|
-
type
|
|
48
|
-
type: 'prependArrayItem';
|
|
47
|
+
type BsPrependArrayItemButtonField = Omit<BsButtonField<PrependArrayItemEvent>, 'event' | 'type' | 'eventArgs'> & {
|
|
48
|
+
type: 'prepend-array-item' | 'prependArrayItem';
|
|
49
49
|
/**
|
|
50
50
|
* The key of the array field to prepend items to.
|
|
51
51
|
* Required when the button is placed outside the array.
|
|
@@ -60,8 +60,8 @@ type PrependArrayItemButtonField = Omit<BsButtonField<PrependArrayItemEvent>, 'e
|
|
|
60
60
|
template: ArrayAllowedChildren | readonly ArrayAllowedChildren[];
|
|
61
61
|
};
|
|
62
62
|
/** Insert array item button field - dispatches InsertArrayItemEvent (adds at specific index) */
|
|
63
|
-
type
|
|
64
|
-
type: 'insertArrayItem';
|
|
63
|
+
type BsInsertArrayItemButtonField = Omit<BsButtonField<InsertArrayItemEvent>, 'event' | 'type' | 'eventArgs'> & {
|
|
64
|
+
type: 'insert-array-item' | 'insertArrayItem';
|
|
65
65
|
/**
|
|
66
66
|
* The key of the array field to insert items into.
|
|
67
67
|
* Required when the button is placed outside the array.
|
|
@@ -78,8 +78,8 @@ type InsertArrayItemButtonField = Omit<BsButtonField<InsertArrayItemEvent>, 'eve
|
|
|
78
78
|
template: ArrayAllowedChildren | readonly ArrayAllowedChildren[];
|
|
79
79
|
};
|
|
80
80
|
/** Remove array item button field - dispatches RemoveAtIndexEvent or PopArrayItemEvent */
|
|
81
|
-
type
|
|
82
|
-
type: 'removeArrayItem';
|
|
81
|
+
type BsRemoveArrayItemButtonField = Omit<BsButtonField<RemoveAtIndexEvent>, 'event' | 'type' | 'eventArgs'> & {
|
|
82
|
+
type: 'remove-array-item' | 'removeArrayItem';
|
|
83
83
|
/**
|
|
84
84
|
* The key of the array field to remove items from.
|
|
85
85
|
* Required when the button is placed outside the array.
|
|
@@ -88,8 +88,8 @@ type RemoveArrayItemButtonField = Omit<BsButtonField<RemoveAtIndexEvent>, 'event
|
|
|
88
88
|
arrayKey?: string;
|
|
89
89
|
};
|
|
90
90
|
/** Pop array item button field - dispatches PopArrayItemEvent (removes last item) */
|
|
91
|
-
type
|
|
92
|
-
type: 'popArrayItem';
|
|
91
|
+
type BsPopArrayItemButtonField = Omit<BsButtonField<PopArrayItemEvent>, 'event' | 'type' | 'eventArgs'> & {
|
|
92
|
+
type: 'pop-array-item' | 'popArrayItem';
|
|
93
93
|
/**
|
|
94
94
|
* The key of the array field to remove the last item from.
|
|
95
95
|
* REQUIRED - must specify which array to pop from.
|
|
@@ -97,8 +97,8 @@ type PopArrayItemButtonField = Omit<BsButtonField<PopArrayItemEvent>, 'event' |
|
|
|
97
97
|
arrayKey: string;
|
|
98
98
|
};
|
|
99
99
|
/** Shift array item button field - dispatches ShiftArrayItemEvent (removes first item) */
|
|
100
|
-
type
|
|
101
|
-
type: 'shiftArrayItem';
|
|
100
|
+
type BsShiftArrayItemButtonField = Omit<BsButtonField<ShiftArrayItemEvent>, 'event' | 'type' | 'eventArgs'> & {
|
|
101
|
+
type: 'shift-array-item' | 'shiftArrayItem';
|
|
102
102
|
/**
|
|
103
103
|
* The key of the array field to remove the first item from.
|
|
104
104
|
* REQUIRED - must specify which array to shift from.
|
|
@@ -474,12 +474,12 @@ declare const BsField: {
|
|
|
474
474
|
readonly Submit: "submit";
|
|
475
475
|
readonly Next: "next";
|
|
476
476
|
readonly Previous: "previous";
|
|
477
|
-
readonly AddArrayItem: "
|
|
478
|
-
readonly PrependArrayItem: "
|
|
479
|
-
readonly InsertArrayItem: "
|
|
480
|
-
readonly RemoveArrayItem: "
|
|
481
|
-
readonly PopArrayItem: "
|
|
482
|
-
readonly ShiftArrayItem: "
|
|
477
|
+
readonly AddArrayItem: "add-array-item";
|
|
478
|
+
readonly PrependArrayItem: "prepend-array-item";
|
|
479
|
+
readonly InsertArrayItem: "insert-array-item";
|
|
480
|
+
readonly RemoveArrayItem: "remove-array-item";
|
|
481
|
+
readonly PopArrayItem: "pop-array-item";
|
|
482
|
+
readonly ShiftArrayItem: "shift-array-item";
|
|
483
483
|
readonly Textarea: "textarea";
|
|
484
484
|
readonly Radio: "radio";
|
|
485
485
|
readonly MultiCheckbox: "multi-checkbox";
|
|
@@ -509,12 +509,18 @@ declare module '@ng-forge/dynamic-forms' {
|
|
|
509
509
|
submit: BsSubmitButtonField;
|
|
510
510
|
next: BsNextButtonField;
|
|
511
511
|
previous: BsPreviousButtonField;
|
|
512
|
-
addArrayItem:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
512
|
+
addArrayItem: BsAddArrayItemButtonField;
|
|
513
|
+
'add-array-item': BsAddArrayItemButtonField;
|
|
514
|
+
prependArrayItem: BsPrependArrayItemButtonField;
|
|
515
|
+
'prepend-array-item': BsPrependArrayItemButtonField;
|
|
516
|
+
insertArrayItem: BsInsertArrayItemButtonField;
|
|
517
|
+
'insert-array-item': BsInsertArrayItemButtonField;
|
|
518
|
+
removeArrayItem: BsRemoveArrayItemButtonField;
|
|
519
|
+
'remove-array-item': BsRemoveArrayItemButtonField;
|
|
520
|
+
popArrayItem: BsPopArrayItemButtonField;
|
|
521
|
+
'pop-array-item': BsPopArrayItemButtonField;
|
|
522
|
+
shiftArrayItem: BsShiftArrayItemButtonField;
|
|
523
|
+
'shift-array-item': BsShiftArrayItemButtonField;
|
|
518
524
|
textarea: BsTextareaField;
|
|
519
525
|
radio: BsRadioField<unknown>;
|
|
520
526
|
'multi-checkbox': BsMultiCheckboxField<unknown>;
|
|
@@ -589,4 +595,4 @@ declare class BsButtonAddonComponent {
|
|
|
589
595
|
declare const BS_INPUT_TYPE_OVERRIDE: InjectionToken<WritableSignal<string | undefined>>;
|
|
590
596
|
|
|
591
597
|
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 };
|
|
592
|
-
export type {
|
|
598
|
+
export type { BootstrapConfig, BsAddArrayItemButtonField, BsAddon, BsAddonExtensions, BsButtonAddon, BsButtonField, BsButtonProps, BsCheckboxField, BsCheckboxProps, BsDatepickerField, BsDatepickerProps, BsFieldType, BsFormConfig, BsFormProps, BsIconAddon, BsInputAddon, BsInputField, BsInputProps, BsInsertArrayItemButtonField, BsMultiCheckboxField, BsMultiCheckboxProps, BsNextButtonField, BsPopArrayItemButtonField, BsPrependArrayItemButtonField, BsPreviousButtonField, BsRadioField, BsRadioProps, BsRemoveArrayItemButtonField, BsSelectField, BsSelectProps, BsShiftArrayItemButtonField, BsSliderField, BsSliderProps, BsSubmitButtonField, BsTextareaField, BsTextareaProps, BsToggleField, BsToggleProps };
|