{"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACoFE;;;;;;;;AAMH;;IAGA;AACP;;;;AAIO;AACP;;;;;AAfiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAnFT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACoOE;;;;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;;;ACpHW;;;;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;AACA;;;AAKE;AACE;AACA;;;AAKA;;;AAKA;;QAIF;AACA;;AAGI;;AACA;AACF;;;;AAKE;AACA;AACE;;;;;AAMN;AACF;AACF;;;ACvCA;;;AAIA;AACE;AACA;;AAGF;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAIA;AAEA;AAEA;AAGO;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;;;;ACtJH;AACE;AACA;AACA;;AAeI;;;;AAOA;;AAEgC;AAClC;;AAGF;AACF;AAEA;AAEA;AACE;AACA;AACA;AACE;AACE;;;;AAKN;AACE;AACA;AACA;;AAIE;AACE;;QAKF;AACE;;;;;;AAkBF;AACA;;;AAGC;;AAEL;;;;;IC7FW;;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/fields/datepicker/bs-datepicker.component.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 { 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 '@ng-forge/dynamic-forms-bootstrap/shared';\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 { 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, BS_INPUT_TYPE_OVERRIDE, runBsPresetAction } from '@ng-forge/dynamic-forms-bootstrap/shared';\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        [attr.size]=\"props()?.htmlSize ?? null\"\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 '@ng-forge/dynamic-forms-bootstrap/shared';\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, injectNonFieldLogicResolver } 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  const resolveLogic = injectNonFieldLogicResolver(fieldDef);\n\n  return computed(() => {\n    const baseInputs = buildBaseInputs(fieldDef, defaultProps());\n\n    const inputs: Record<string, unknown> = {\n      ...baseInputs,\n      ...resolveLogic(),\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        get formValue() {\n          return arrayContext?.formValue ?? {};\n        },\n        template,\n      };\n    }\n\n    return inputs;\n  });\n}\n","/* eslint-disable @nx/enforce-module-boundaries -- package self-imports preserve real lazy secondary entrypoints. */\nimport { 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\nconst loadInputComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/input').then(({ BsInputFieldComponent }) => BsInputFieldComponent);\nconst loadSelectComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/select').then(({ BsSelectFieldComponent }) => BsSelectFieldComponent);\nconst loadCheckboxComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/checkbox').then(({ BsCheckboxFieldComponent }) => BsCheckboxFieldComponent);\nconst loadButtonComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/button').then(({ BsButtonFieldComponent }) => BsButtonFieldComponent);\nconst loadTextareaComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/textarea').then(({ BsTextareaFieldComponent }) => BsTextareaFieldComponent);\nconst loadRadioComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/radio').then(({ BsRadioFieldComponent }) => BsRadioFieldComponent);\nconst loadMultiCheckboxComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/multi-checkbox').then(\n    ({ BsMultiCheckboxFieldComponent }) => BsMultiCheckboxFieldComponent,\n  );\nconst loadDatepickerComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/datepicker').then(({ BsDatepickerFieldComponent }) => BsDatepickerFieldComponent);\nconst loadSliderComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/slider').then(({ BsSliderFieldComponent }) => BsSliderFieldComponent);\nconst loadToggleComponent = () =>\n  import('@ng-forge/dynamic-forms-bootstrap/lazy/toggle').then(({ BsToggleFieldComponent }) => BsToggleFieldComponent);\n\nexport const BOOTSTRAP_FIELD_TYPES: FieldTypeDefinition[] = [\n  {\n    name: BsField.Input,\n    loadComponent: loadInputComponent,\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: loadSelectComponent,\n    mapper: optionsFieldMapper,\n    scope: 'single-select',\n    ...VALUE_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Checkbox,\n    loadComponent: loadCheckboxComponent,\n    mapper: checkboxFieldMapper,\n    scope: 'boolean',\n    ...VALUE_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Button,\n    loadComponent: loadButtonComponent,\n    mapper: buttonFieldMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Submit,\n    loadComponent: loadButtonComponent,\n    mapper: submitButtonFieldMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Next,\n    loadComponent: loadButtonComponent,\n    mapper: nextButtonFieldMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Previous,\n    loadComponent: loadButtonComponent,\n    mapper: previousButtonFieldMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.AddArrayItem,\n    loadComponent: loadButtonComponent,\n    mapper: addArrayItemButtonMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.PrependArrayItem,\n    loadComponent: loadButtonComponent,\n    mapper: prependArrayItemButtonMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.InsertArrayItem,\n    loadComponent: loadButtonComponent,\n    mapper: insertArrayItemButtonMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.RemoveArrayItem,\n    loadComponent: loadButtonComponent,\n    mapper: removeArrayItemButtonMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.PopArrayItem,\n    loadComponent: loadButtonComponent,\n    mapper: popArrayItemButtonMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.ShiftArrayItem,\n    loadComponent: loadButtonComponent,\n    mapper: shiftArrayItemButtonMapper,\n    ...BUTTON_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Textarea,\n    loadComponent: loadTextareaComponent,\n    mapper: valueFieldMapper,\n    propsToMeta: ['rows'],\n    scope: 'text-input',\n    ...VALUE_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Radio,\n    loadComponent: loadRadioComponent,\n    mapper: optionsFieldMapper,\n    scope: 'single-select',\n    ...VALUE_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.MultiCheckbox,\n    loadComponent: loadMultiCheckboxComponent,\n    mapper: optionsFieldMapper,\n    scope: 'multi-select',\n    ...VALUE_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Datepicker,\n    loadComponent: loadDatepickerComponent,\n    mapper: datepickerFieldMapper,\n    scope: 'date',\n    ...VALUE_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Slider,\n    loadComponent: loadSliderComponent,\n    mapper: valueFieldMapper,\n    scope: 'numeric',\n    ...VALUE_FIELD_TYPES_BASE,\n  },\n  {\n    name: BsField.Toggle,\n    loadComponent: loadToggleComponent,\n    mapper: checkboxFieldMapper,\n    scope: 'boolean',\n    ...VALUE_FIELD_TYPES_BASE,\n  },\n];\n","/* eslint-disable @nx/enforce-module-boundaries -- package self-imports preserve real lazy secondary entrypoints. */\nimport type { Provider } from '@angular/core';\nimport { DynamicFormError, type AddonTypeDefinition } from '@ng-forge/dynamic-forms';\nimport { ADDON_TYPE_DEFINITIONS, type FieldTypeDefinition, type WrapperTypeDefinition } from '@ng-forge/dynamic-forms/integration';\nimport { BOOTSTRAP_FIELD_TYPES } from '../config/bootstrap-field-config';\nimport { BOOTSTRAP_CONFIG, type BootstrapConfig, type BsButtonAddon, type BsIconAddon } from '@ng-forge/dynamic-forms-bootstrap/shared';\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, WrapperTypeDefinition];\n\ntype BootstrapFieldsWithConfig = [...BootstrapFieldTypes, BootstrapAddonsFeature, WrapperTypeDefinition, BootstrapConfigFeature];\n\n/**\n * Replaces the neutral core `field-errors` wrapper with the Bootstrap one, so\n * a container-level validation message renders like a field-level one.\n * Registered under the same name, so the later registration wins.\n */\nconst BOOTSTRAP_FIELD_ERRORS_WRAPPER: WrapperTypeDefinition = {\n  wrapperName: 'field-errors',\n  loadComponent: () => import('../wrappers/field-errors/bs-field-errors-wrapper.component'),\n  rendersFieldErrors: true,\n};\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(), BOOTSTRAP_FIELD_ERRORS_WRAPPER];\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('@ng-forge/dynamic-forms-bootstrap/lazy/addon-icon').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('@ng-forge/dynamic-forms-bootstrap/lazy/addon-button').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 '@ng-forge/dynamic-forms-bootstrap/shared';\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 '@ng-forge/dynamic-forms-bootstrap/shared';\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"]}