@ng-forge/dynamic-forms-ionic 0.9.0-next.9 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,72 +1,27 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, input, computed, ChangeDetectionStrategy, Component, ElementRef, signal, InjectionToken, linkedSignal, model, isSignal } from '@angular/core';
3
- import { IonButton, IonCheckbox, IonNote, IonInput, IonModal, IonDatetime, IonButtons, IonHeader, IonContent, IonToolbar, IonTitle, IonItem, IonRadioGroup, IonRadio, IonSelect, IonSelectOption, IonRange, IonTextarea, IonToggle } from '@ionic/angular/standalone';
4
- import { EventBus, ARRAY_CONTEXT, resolveTokens, DynamicTextPipe, DEFAULT_PROPS, buildBaseInputs } from '@ng-forge/dynamic-forms';
2
+ import { input, computed, ChangeDetectionStrategy, Component, signal, inject, Injector, InjectionToken, ElementRef, DestroyRef, forwardRef, linkedSignal, model, isSignal } from '@angular/core';
3
+ import { IonButton, IonCheckbox, IonNote, IonInput, IonModal, IonDatetime, IonButtons, IonHeader, IonContent, IonToolbar, IonTitle, IonIcon, IonSpinner, IonItem, IonRadioGroup, IonRadio, IonSelect, IonSelectOption, IonRange, IonTextarea, IonToggle } from '@ionic/angular/standalone';
5
4
  import { AsyncPipe } from '@angular/common';
5
+ import { DynamicTextPipe, runPresetAction, resolveDynamicValue, DfAddonSlot, FIELD_SIGNAL_CONTEXT, DynamicFormLogger, DEFAULT_PROPS, ARRAY_CONTEXT, buildBaseInputs, DynamicFormError, ADDON_KIND_DEFINITIONS } from '@ng-forge/dynamic-forms';
6
+ import * as i1 from '@ng-forge/dynamic-forms/integration';
7
+ import { injectNgForgeAction, NgForgeActionHost, injectNgForgeField, NgForgeControl, NgForgeFieldHost, injectNgForgeAddonAction, NgForgeAddonAction, injectNgForgeAddons, ADDON_PRESET_HANDLER, NgForgeAddons, isEqual, NgForgeHostControl, valueFieldMapper, optionsFieldMapper, checkboxFieldMapper, submitButtonFieldMapper, nextButtonFieldMapper, previousButtonFieldMapper, addArrayItemButtonMapper, prependArrayItemButtonMapper, insertArrayItemButtonMapper, removeArrayItemButtonMapper, popArrayItemButtonMapper, shiftArrayItemButtonMapper, datepickerFieldMapper } from '@ng-forge/dynamic-forms/integration';
6
8
  import { FormField } from '@angular/forms/signals';
7
- import { createResolvedErrorsSignal, shouldShowErrors, setupMetaTracking, isEqual, valueFieldMapper, optionsFieldMapper, checkboxFieldMapper, submitButtonFieldMapper, nextButtonFieldMapper, previousButtonFieldMapper, addArrayItemButtonMapper, prependArrayItemButtonMapper, insertArrayItemButtonMapper, removeArrayItemButtonMapper, popArrayItemButtonMapper, shiftArrayItemButtonMapper, datepickerFieldMapper } from '@ng-forge/dynamic-forms/integration';
8
9
  import { format } from 'date-fns';
9
10
  import { explicitEffect } from 'ngxtension/explicit-effect';
10
11
 
11
12
  class IonicButtonFieldComponent {
12
- eventBus = inject(EventBus);
13
- arrayContext = inject(ARRAY_CONTEXT, { optional: true });
14
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
15
- label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
16
- disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
17
- hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
18
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
19
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
20
- /** Event to dispatch on click. Optional for submit buttons (native form submit handles it). */
21
- event = input(...(ngDevMode ? [undefined, { debugName: "event" }] : /* istanbul ignore next */ []));
22
- eventArgs = input(...(ngDevMode ? [undefined, { debugName: "eventArgs" }] : /* istanbul ignore next */ []));
13
+ action = injectNgForgeAction();
23
14
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
24
- eventContext = input(...(ngDevMode ? [undefined, { debugName: "eventContext" }] : /* istanbul ignore next */ []));
25
- /** Resolved button type - defaults to 'button' if not specified in props */
26
15
  buttonType = computed(() => this.props()?.type ?? 'button', ...(ngDevMode ? [{ debugName: "buttonType" }] : /* istanbul ignore next */ []));
27
- buttonTestId = computed(() => `${this.buttonType()}-${this.key()}`, ...(ngDevMode ? [{ debugName: "buttonTestId" }] : /* istanbul ignore next */ []));
28
- /**
29
- * Handle button click.
30
- * - For submit buttons (type="submit"): do nothing, native form submit handles it
31
- * - For other buttons: dispatch the configured event via EventBus
32
- */
16
+ buttonTestId = computed(() => `${this.buttonType()}-${this.action.key()}`, ...(ngDevMode ? [{ debugName: "buttonTestId" }] : /* istanbul ignore next */ []));
33
17
  onClick() {
34
- // Native submit buttons let the form handle submission
35
- if (this.buttonType() === 'submit') {
18
+ if (this.buttonType() === 'submit')
36
19
  return;
37
- }
38
- // Other buttons dispatch their event (if configured)
39
- const event = this.event();
40
- if (event) {
41
- this.dispatchEvent(event);
42
- }
20
+ this.action.dispatch();
43
21
  }
44
- dispatchEvent(event) {
45
- const args = this.eventArgs();
46
- if (args && args.length > 0) {
47
- // Build context from injected ARRAY_CONTEXT (with linkedSignal index) or fallback to eventContext
48
- const context = this.arrayContext
49
- ? {
50
- key: this.key(),
51
- // Read signal to get current index (automatically updates via linkedSignal)
52
- index: this.arrayContext.index(),
53
- arrayKey: this.arrayContext.arrayKey,
54
- formValue: this.arrayContext.formValue,
55
- }
56
- : this.eventContext() || { key: this.key() };
57
- // Resolve tokens in event args using the provided context
58
- const resolvedArgs = resolveTokens(args, context);
59
- // Dispatch event with resolved args
60
- this.eventBus.dispatch(event, ...resolvedArgs);
61
- }
62
- else {
63
- // No args, dispatch event without arguments
64
- this.eventBus.dispatch(event);
65
- }
66
- }
67
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicButtonFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
68
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: IonicButtonFieldComponent, isStandalone: true, selector: "df-ion-button", inputs: { key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, event: { classPropertyName: "event", publicName: "event", isSignal: true, isRequired: false, transformFunction: null }, eventArgs: { classPropertyName: "eventArgs", publicName: "eventArgs", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, eventContext: { classPropertyName: "eventContext", publicName: "eventContext", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "class": "className()", "attr.data-testid": "key()", "attr.hidden": "hidden() || null" } }, ngImport: i0, template: `
69
- @let buttonId = key() + '-button';
22
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicButtonFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.14", type: IonicButtonFieldComponent, isStandalone: true, selector: "df-ion-button", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeActionHost }], ngImport: i0, template: `
24
+ @let buttonId = action.key() + '-button';
70
25
 
71
26
  <ion-button
72
27
  [id]="buttonId"
@@ -77,23 +32,19 @@ class IonicButtonFieldComponent {
77
32
  [size]="props()?.size"
78
33
  [color]="props()?.color || 'primary'"
79
34
  [strong]="props()?.strong"
80
- [disabled]="disabled()"
35
+ [disabled]="action.disabled()"
36
+ [attr.tabindex]="action.tabIndex()"
81
37
  [attr.data-testid]="buttonTestId()"
82
38
  (click)="onClick()"
83
39
  >
84
- {{ label() | dynamicText | async }}
40
+ {{ action.label() | dynamicText | async }}
85
41
  </ion-button>
86
42
  `, isInline: true, styles: [":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
87
43
  }
88
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicButtonFieldComponent, decorators: [{
44
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicButtonFieldComponent, decorators: [{
89
45
  type: Component,
90
- args: [{ selector: 'df-ion-button', imports: [IonButton, DynamicTextPipe, AsyncPipe], host: {
91
- '[id]': '`${key()}`',
92
- '[class]': 'className()',
93
- '[attr.data-testid]': 'key()',
94
- '[attr.hidden]': 'hidden() || null',
95
- }, template: `
96
- @let buttonId = key() + '-button';
46
+ args: [{ selector: 'df-ion-button', imports: [IonButton, DynamicTextPipe, AsyncPipe], hostDirectives: [NgForgeActionHost], template: `
47
+ @let buttonId = action.key() + '-button';
97
48
 
98
49
  <ion-button
99
50
  [id]="buttonId"
@@ -104,14 +55,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
104
55
  [size]="props()?.size"
105
56
  [color]="props()?.color || 'primary'"
106
57
  [strong]="props()?.strong"
107
- [disabled]="disabled()"
58
+ [disabled]="action.disabled()"
59
+ [attr.tabindex]="action.tabIndex()"
108
60
  [attr.data-testid]="buttonTestId()"
109
61
  (click)="onClick()"
110
62
  >
111
- {{ label() | dynamicText | async }}
63
+ {{ action.label() | dynamicText | async }}
112
64
  </ion-button>
113
65
  `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host([hidden]){display:none!important}\n"] }]
114
- }], propDecorators: { key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], event: [{ type: i0.Input, args: [{ isSignal: true, alias: "event", required: false }] }], eventArgs: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventArgs", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], eventContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventContext", required: false }] }] } });
66
+ }], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
115
67
 
116
68
  var ionicButton_component = /*#__PURE__*/Object.freeze({
117
69
  __proto__: null,
@@ -120,129 +72,60 @@ var ionicButton_component = /*#__PURE__*/Object.freeze({
120
72
 
121
73
  // Public API - component
122
74
 
123
- /**
124
- * Creates a signal that computes the aria-describedby value based on errors and hint state.
125
- * Errors take precedence over hints - when errors are displayed, the hint is hidden.
126
- * Only the first error is displayed (single error ID, not indexed).
127
- *
128
- * @param errorsToDisplay Signal containing the array of errors currently being displayed
129
- * @param errorId Signal containing the ID for the error element (single error only)
130
- * @param hintId Signal containing the ID for the hint element
131
- * @param hasHint Function that returns true if a hint is configured
132
- * @returns Signal containing the aria-describedby value or null
133
- */
134
- function createAriaDescribedBySignal(errorsToDisplay, errorId, hintId, hasHint) {
135
- return computed(() => {
136
- if (errorsToDisplay().length > 0) {
137
- return errorId();
138
- }
139
- if (hasHint()) {
140
- return hintId();
141
- }
142
- return null;
143
- });
144
- }
145
-
146
75
  class IonicCheckboxFieldComponent {
147
- elementRef = inject((ElementRef));
148
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
149
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
150
- // Properties
151
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
152
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
153
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
154
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
76
+ ngf = injectNgForgeField();
155
77
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
156
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
157
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
158
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
159
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
160
- showErrors = shouldShowErrors(this.field);
161
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
162
- constructor() {
163
- // Shadow DOM - apply meta to ion-checkbox element
164
- setupMetaTracking(this.elementRef, this.meta, {
165
- selector: 'ion-checkbox',
166
- });
167
- }
168
- // ─────────────────────────────────────────────────────────────────────────────
169
- // Accessibility
170
- // ─────────────────────────────────────────────────────────────────────────────
171
- /** Base ID for error elements */
172
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
173
- /** Unique ID for the helper text element */
174
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
175
- /** Whether the field is currently in an invalid state (invalid AND touched) */
176
- ariaInvalid = computed(() => {
177
- const fieldState = this.field()();
178
- return fieldState.invalid() && fieldState.touched();
179
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
180
- /** Whether the field has a required validator */
181
- ariaRequired = computed(() => {
182
- return this.field()().required?.() === true ? true : null;
183
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
184
- /** aria-describedby linking to hint OR error elements (mutually exclusive) */
185
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
186
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
187
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: IonicCheckboxFieldComponent, isStandalone: true, selector: "df-ion-checkbox", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
188
- @let f = field();
189
- @let checkboxId = key() + '-checkbox';
78
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
79
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonicCheckboxFieldComponent, isStandalone: true, selector: "df-ion-checkbox", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
80
+ @let f = ngf.field();
81
+ @let checkboxId = ngf.key() + '-checkbox';
190
82
 
191
83
  <ion-checkbox
84
+ ngForgeControl
192
85
  [id]="checkboxId"
193
86
  [formField]="f"
194
87
  [labelPlacement]="props()?.labelPlacement ?? 'end'"
195
88
  [justify]="props()?.justify"
196
89
  [color]="props()?.color ?? 'primary'"
197
90
  [indeterminate]="props()?.indeterminate ?? false"
198
- [attr.tabindex]="tabIndex()"
199
- [attr.aria-invalid]="ariaInvalid()"
200
- [attr.aria-required]="ariaRequired()"
201
- [attr.aria-describedby]="ariaDescribedBy()"
91
+ [attr.tabindex]="ngf.tabIndex()"
202
92
  >
203
- {{ label() | dynamicText | async }}
93
+ {{ ngf.label() | dynamicText | async }}
204
94
  </ion-checkbox>
205
95
 
206
- @if (errorsToDisplay()[0]; as error) {
207
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
96
+ @if (ngf.errorsToDisplay()[0]; as error) {
97
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
208
98
  } @else if (props()?.hint; as hint) {
209
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
99
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
210
100
  }
211
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
101
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
212
102
  }
213
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicCheckboxFieldComponent, decorators: [{
103
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicCheckboxFieldComponent, decorators: [{
214
104
  type: Component,
215
- args: [{ selector: 'df-ion-checkbox', imports: [IonCheckbox, IonNote, FormField, DynamicTextPipe, AsyncPipe], template: `
216
- @let f = field();
217
- @let checkboxId = key() + '-checkbox';
105
+ args: [{ selector: 'df-ion-checkbox', imports: [IonCheckbox, IonNote, FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
106
+ @let f = ngf.field();
107
+ @let checkboxId = ngf.key() + '-checkbox';
218
108
 
219
109
  <ion-checkbox
110
+ ngForgeControl
220
111
  [id]="checkboxId"
221
112
  [formField]="f"
222
113
  [labelPlacement]="props()?.labelPlacement ?? 'end'"
223
114
  [justify]="props()?.justify"
224
115
  [color]="props()?.color ?? 'primary'"
225
116
  [indeterminate]="props()?.indeterminate ?? false"
226
- [attr.tabindex]="tabIndex()"
227
- [attr.aria-invalid]="ariaInvalid()"
228
- [attr.aria-required]="ariaRequired()"
229
- [attr.aria-describedby]="ariaDescribedBy()"
117
+ [attr.tabindex]="ngf.tabIndex()"
230
118
  >
231
- {{ label() | dynamicText | async }}
119
+ {{ ngf.label() | dynamicText | async }}
232
120
  </ion-checkbox>
233
121
 
234
- @if (errorsToDisplay()[0]; as error) {
235
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
122
+ @if (ngf.errorsToDisplay()[0]; as error) {
123
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
236
124
  } @else if (props()?.hint; as hint) {
237
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
125
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
238
126
  }
239
- `, host: {
240
- '[class]': 'className()',
241
- '[id]': '`${key()}`',
242
- '[attr.data-testid]': 'key()',
243
- '[attr.hidden]': 'field()().hidden() || null',
244
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}\n"] }]
245
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
127
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"] }]
128
+ }], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
246
129
 
247
130
  var ionicCheckbox_component = /*#__PURE__*/Object.freeze({
248
131
  __proto__: null,
@@ -250,48 +133,12 @@ var ionicCheckbox_component = /*#__PURE__*/Object.freeze({
250
133
  });
251
134
 
252
135
  class IonicDatepickerFieldComponent {
253
- elementRef = inject((ElementRef));
254
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
255
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
256
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
257
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
258
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
259
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
136
+ ngf = injectNgForgeField();
260
137
  minDate = input(null, ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
261
138
  maxDate = input(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
262
139
  startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
263
140
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
264
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
265
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
266
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
267
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
268
- showErrors = shouldShowErrors(this.field);
269
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
270
141
  isModalOpen = signal(false, ...(ngDevMode ? [{ debugName: "isModalOpen" }] : /* istanbul ignore next */ []));
271
- constructor() {
272
- // Shadow DOM - apply meta to ion-input element
273
- setupMetaTracking(this.elementRef, this.meta, {
274
- selector: 'ion-input',
275
- });
276
- }
277
- // ─────────────────────────────────────────────────────────────────────────────
278
- // Accessibility
279
- // ─────────────────────────────────────────────────────────────────────────────
280
- /** Base ID for error elements */
281
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
282
- /** Unique ID for the helper text element */
283
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
284
- /** Whether the field is currently in an invalid state (invalid AND touched) */
285
- ariaInvalid = computed(() => {
286
- const fieldState = this.field()();
287
- return fieldState.invalid() && fieldState.touched();
288
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
289
- /** Whether the field has a required validator */
290
- ariaRequired = computed(() => {
291
- return this.field()().required?.() === true ? true : null;
292
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
293
- /** aria-describedby linking to hint OR error elements (mutually exclusive) */
294
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
295
142
  openModal() {
296
143
  this.isModalOpen.set(true);
297
144
  }
@@ -300,14 +147,14 @@ class IonicDatepickerFieldComponent {
300
147
  }
301
148
  onDateChange(event) {
302
149
  const value = event.detail.value;
303
- // ion-datetime emits ISO 8601 strings internally; we convert to Date objects
304
- // to match the FieldTree<Date | null> type contract.
150
+ // ion-datetime emits ISO 8601 strings internally; convert to Date | null
151
+ // to match the field tree's value type.
305
152
  if (value && typeof value === 'string' && value.length > 0) {
306
153
  const date = new Date(value);
307
- this.field()().value.set(date);
154
+ this.ngf.field()().value.set(date);
308
155
  }
309
156
  else {
310
- this.field()().value.set(null);
157
+ this.ngf.field()().value.set(null);
311
158
  }
312
159
  this.closeModal();
313
160
  }
@@ -338,38 +185,36 @@ class IonicDatepickerFieldComponent {
338
185
  return format(date, 'MMM d, yyyy');
339
186
  }
340
187
  }
341
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicDatepickerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
342
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: IonicDatepickerFieldComponent, isStandalone: true, selector: "df-ion-datepicker", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
343
- @let f = field();
188
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicDatepickerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
189
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonicDatepickerFieldComponent, isStandalone: true, selector: "df-ion-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: `
190
+ @let f = ngf.field();
344
191
  @let dateValue = f().value();
345
- @let inputId = key() + '-input';
192
+ @let inputId = ngf.key() + '-input';
346
193
 
347
194
  <ion-input
195
+ ngForgeControl
348
196
  [id]="inputId"
349
- [label]="(label() | dynamicText | async) ?? undefined"
197
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
350
198
  [labelPlacement]="'stacked'"
351
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
199
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
352
200
  [disabled]="f().disabled()"
353
201
  [value]="formatDisplayDate(dateValue)"
354
202
  [readonly]="true"
355
203
  [fill]="'outline'"
356
- [attr.tabindex]="tabIndex()"
357
- [attr.aria-invalid]="ariaInvalid()"
358
- [attr.aria-required]="ariaRequired()"
359
- [attr.aria-describedby]="ariaDescribedBy()"
204
+ [attr.tabindex]="ngf.tabIndex()"
360
205
  (click)="!f().disabled() && openModal()"
361
206
  />
362
- @if (errorsToDisplay()[0]; as error) {
363
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
207
+ @if (ngf.errorsToDisplay()[0]; as error) {
208
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
364
209
  } @else if (props()?.hint; as hint) {
365
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
210
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
366
211
  }
367
212
 
368
213
  <ion-modal [isOpen]="isModalOpen()" (didDismiss)="closeModal()">
369
214
  <ng-template>
370
215
  <ion-header>
371
216
  <ion-toolbar>
372
- <ion-title>{{ label() | dynamicText | async }}</ion-title>
217
+ <ion-title>{{ ngf.label() | dynamicText | async }}</ion-title>
373
218
  <ion-buttons slot="end">
374
219
  <ion-button (click)="closeModal()">Close</ion-button>
375
220
  </ion-buttons>
@@ -394,9 +239,9 @@ class IonicDatepickerFieldComponent {
394
239
  </ion-content>
395
240
  </ng-template>
396
241
  </ion-modal>
397
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", "ion-input{cursor:pointer}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: IonDatetime, selector: "ion-datetime", inputs: ["cancelText", "clearText", "color", "dayValues", "disabled", "doneText", "firstDayOfWeek", "formatOptions", "highlightedDates", "hourCycle", "hourValues", "isDateEnabled", "locale", "max", "min", "minuteValues", "mode", "monthValues", "multiple", "name", "preferWheel", "presentation", "readonly", "showAdjacentDays", "showClearButton", "showDefaultButtons", "showDefaultTimeLabel", "showDefaultTitle", "size", "titleSelectedDatesFormatter", "value", "yearValues"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
242
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", "ion-input{cursor:pointer}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: IonDatetime, selector: "ion-datetime", inputs: ["cancelText", "clearText", "color", "dayValues", "disabled", "doneText", "firstDayOfWeek", "formatOptions", "highlightedDates", "hourCycle", "hourValues", "isDateEnabled", "locale", "max", "min", "minuteValues", "mode", "monthValues", "multiple", "name", "preferWheel", "presentation", "readonly", "showAdjacentDays", "showClearButton", "showDefaultButtons", "showDefaultTimeLabel", "showDefaultTitle", "size", "titleSelectedDatesFormatter", "value", "yearValues"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
398
243
  }
399
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicDatepickerFieldComponent, decorators: [{
244
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicDatepickerFieldComponent, decorators: [{
400
245
  type: Component,
401
246
  args: [{ selector: 'df-ion-datepicker', imports: [
402
247
  IonInput,
@@ -411,37 +256,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
411
256
  IonButton,
412
257
  DynamicTextPipe,
413
258
  AsyncPipe,
414
- ], template: `
415
- @let f = field();
259
+ NgForgeControl,
260
+ ], hostDirectives: [NgForgeFieldHost], template: `
261
+ @let f = ngf.field();
416
262
  @let dateValue = f().value();
417
- @let inputId = key() + '-input';
263
+ @let inputId = ngf.key() + '-input';
418
264
 
419
265
  <ion-input
266
+ ngForgeControl
420
267
  [id]="inputId"
421
- [label]="(label() | dynamicText | async) ?? undefined"
268
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
422
269
  [labelPlacement]="'stacked'"
423
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
270
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
424
271
  [disabled]="f().disabled()"
425
272
  [value]="formatDisplayDate(dateValue)"
426
273
  [readonly]="true"
427
274
  [fill]="'outline'"
428
- [attr.tabindex]="tabIndex()"
429
- [attr.aria-invalid]="ariaInvalid()"
430
- [attr.aria-required]="ariaRequired()"
431
- [attr.aria-describedby]="ariaDescribedBy()"
275
+ [attr.tabindex]="ngf.tabIndex()"
432
276
  (click)="!f().disabled() && openModal()"
433
277
  />
434
- @if (errorsToDisplay()[0]; as error) {
435
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
278
+ @if (ngf.errorsToDisplay()[0]; as error) {
279
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
436
280
  } @else if (props()?.hint; as hint) {
437
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
281
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
438
282
  }
439
283
 
440
284
  <ion-modal [isOpen]="isModalOpen()" (didDismiss)="closeModal()">
441
285
  <ng-template>
442
286
  <ion-header>
443
287
  <ion-toolbar>
444
- <ion-title>{{ label() | dynamicText | async }}</ion-title>
288
+ <ion-title>{{ ngf.label() | dynamicText | async }}</ion-title>
445
289
  <ion-buttons slot="end">
446
290
  <ion-button (click)="closeModal()">Close</ion-button>
447
291
  </ion-buttons>
@@ -466,19 +310,85 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
466
310
  </ion-content>
467
311
  </ng-template>
468
312
  </ion-modal>
469
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
470
- '[id]': '`${key()}`',
471
- '[attr.data-testid]': 'key()',
472
- '[class]': 'className()',
473
- '[attr.hidden]': 'field()().hidden() || null',
474
- }, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", "ion-input{cursor:pointer}:host([hidden]){display:none!important}\n"] }]
475
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
313
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", "ion-input{cursor:pointer}:host([hidden]){display:none!important}\n"] }]
314
+ }], propDecorators: { minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
476
315
 
477
316
  var ionicDatepicker_component = /*#__PURE__*/Object.freeze({
478
317
  __proto__: null,
479
318
  default: IonicDatepickerFieldComponent
480
319
  });
481
320
 
321
+ /** Ionic adapter binding for the shared preset runner. */
322
+ function runIonicPresetAction(preset, ctx, collaborators) {
323
+ return runPresetAction(preset, ctx, collaborators, 'Ionic', 'ion-input');
324
+ }
325
+
326
+ /**
327
+ * Inline `ion-button` addon — the host element IS `<ion-button>` so Ionic's
328
+ * `::slotted(ion-button[slot=start|end])` shadow CSS matches and applies its
329
+ * native icon-only sizing. `IonButtonAddonComponent` (tag selector) stays
330
+ * for the universal `<df-addon-slot>` dispatcher when the addon is rendered
331
+ * outside an `<ion-input>` host.
332
+ */
333
+ class IonInlineButtonAddonComponent {
334
+ injector = inject(Injector);
335
+ action = injectNgForgeAddonAction();
336
+ addon = this.action.addon;
337
+ label = computed(() => this.addon().label, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
338
+ ariaLabel = computed(() => this.addon().ariaLabel, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
339
+ icon = computed(() => this.addon().icon, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
340
+ color = computed(() => this.addon().color, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
341
+ fill = computed(() => this.addon().fill ?? 'clear', ...(ngDevMode ? [{ debugName: "fill" }] : /* istanbul ignore next */ []));
342
+ iconOnly = computed(() => !!this.icon() && !this.label(), ...(ngDevMode ? [{ debugName: "iconOnly" }] : /* istanbul ignore next */ []));
343
+ // Host bindings can't use pipes (NG5001), so `DynamicText` is materialised
344
+ // via an effect (resolveDynamicValue's toSignal can't run inside computed).
345
+ _ariaLabelSignal = signal(undefined, ...(ngDevMode ? [{ debugName: "_ariaLabelSignal" }] : /* istanbul ignore next */ []));
346
+ resolvedAriaLabel = computed(() => this._ariaLabelSignal()?.() ?? undefined, ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
347
+ constructor() {
348
+ explicitEffect([this.ariaLabel], ([raw]) => {
349
+ if (raw === undefined) {
350
+ this._ariaLabelSignal.set(undefined);
351
+ return;
352
+ }
353
+ this._ariaLabelSignal.set(resolveDynamicValue(raw, undefined, this.injector));
354
+ });
355
+ }
356
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonInlineButtonAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
357
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonInlineButtonAddonComponent, isStandalone: true, selector: "ion-button[df-ion-button-addon]", host: { listeners: { "click": "action.dispatch()" }, properties: { "attr.color": "color() ?? null", "attr.fill": "fill()", "attr.disabled": "action.disabled() || action.loading() ? \"true\" : null", "attr.aria-label": "resolvedAriaLabel() ?? null", "attr.aria-busy": "action.loading() || null" } }, hostDirectives: [{ directive: i1.NgForgeAddonAction }], ngImport: i0, template: `
358
+ @if (action.loading()) {
359
+ <ion-spinner [slot]="iconOnly() ? 'icon-only' : 'start'" name="dots"></ion-spinner>
360
+ <span class="df-ion-sr-only" role="status">Loading…</span>
361
+ } @else if (icon(); as ic) {
362
+ <ion-icon [name]="ic" [slot]="iconOnly() ? 'icon-only' : 'start'"></ion-icon>
363
+ }
364
+ @if (label(); as lbl) {
365
+ {{ lbl | dynamicText | async }}
366
+ }
367
+ `, isInline: true, styles: [".df-ion-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
368
+ }
369
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonInlineButtonAddonComponent, decorators: [{
370
+ type: Component,
371
+ args: [{ selector: 'ion-button[df-ion-button-addon]', imports: [IonIcon, IonSpinner, DynamicTextPipe, AsyncPipe], hostDirectives: [NgForgeAddonAction], template: `
372
+ @if (action.loading()) {
373
+ <ion-spinner [slot]="iconOnly() ? 'icon-only' : 'start'" name="dots"></ion-spinner>
374
+ <span class="df-ion-sr-only" role="status">Loading…</span>
375
+ } @else if (icon(); as ic) {
376
+ <ion-icon [name]="ic" [slot]="iconOnly() ? 'icon-only' : 'start'"></ion-icon>
377
+ }
378
+ @if (label(); as lbl) {
379
+ {{ lbl | dynamicText | async }}
380
+ }
381
+ `, host: {
382
+ // Stencil reflects [attr.*] to the underlying ion-button properties.
383
+ '[attr.color]': 'color() ?? null',
384
+ '[attr.fill]': 'fill()',
385
+ '[attr.disabled]': 'action.disabled() || action.loading() ? "true" : null',
386
+ '[attr.aria-label]': 'resolvedAriaLabel() ?? null',
387
+ '[attr.aria-busy]': 'action.loading() || null',
388
+ '(click)': 'action.dispatch()',
389
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
390
+ }], ctorParameters: () => [] });
391
+
482
392
  /**
483
393
  * Injection token for Ionic form configuration.
484
394
  * Use this to provide global configuration for Ionic form fields.
@@ -502,59 +412,70 @@ var ionicDatepicker_component = /*#__PURE__*/Object.freeze({
502
412
  */
503
413
  const IONIC_CONFIG = new InjectionToken('IONIC_CONFIG');
504
414
 
505
- // Length-validator → DOM wiring (minlength/maxlength):
506
- //
507
- // On NATIVE form elements (<input>/<textarea>), Signal Forms's [formField] directive
508
- // auto-syncs minLength/maxLength HTML attributes via setNativeDomProperty (gated by
509
- // elementAcceptsNativeProperty). See the source in @angular/forms/signals.
510
- //
511
- // <ion-input> is a custom Ionic web component, not a native form element, so Signal
512
- // Forms's auto-sync does NOT apply here — it instead routes via setInputOnDirectives
513
- // looking up an exact camelCase input ('maxLength'). <ion-input>'s property is the
514
- // lowercase 'maxlength', which doesn't match. We therefore bind directly from the
515
- // FieldState signals.
516
- //
517
- // `f().maxLength?.()` — the optional `?.()` is required: FieldState.maxLength is
518
- // `Signal<number | undefined> | undefined` (the entire signal is missing if the
519
- // field has no maxLength validator). Same for minLength.
520
- //
521
- // PrimeNG textarea uses the alternate strategy: its control component declares
522
- // camelCase `maxLength` / `minLength` inputs so Signal Forms's setInputOnDirectives
523
- // auto-wires. See packages/dynamic-forms-primeng/src/lib/fields/textarea/.
415
+ /**
416
+ * Per-field writable signal that overrides the input's `type` attribute.
417
+ *
418
+ * Provided at the `ion-input` field component level. The button addon's
419
+ * `'toggle-password-visibility'` preset writes to it; the field component
420
+ * reads it to compute its effective `type`.
421
+ *
422
+ * Optional from a button's perspective — when the button is hosted inside a
423
+ * field that doesn't provide this token (e.g., textarea or a future
424
+ * non-input field), the toggle preset is a no-op.
425
+ */
426
+ const IONIC_INPUT_TYPE_OVERRIDE = new InjectionToken('IONIC_INPUT_TYPE_OVERRIDE');
427
+
428
+ // minlength/maxlength bindings: Signal Forms auto-syncs these on NATIVE
429
+ // inputs; <ion-input> is a Stencil web component so we bind directly from
430
+ // FieldState (`f().maxLength?.()` — the signal itself is missing when no
431
+ // validator is configured).
524
432
  class IonicInputFieldComponent {
525
433
  elementRef = inject((ElementRef));
526
434
  ionicConfig = inject(IONIC_CONFIG, { optional: true });
527
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
528
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
529
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
530
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
531
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
532
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
435
+ ngf = injectNgForgeField();
436
+ ngfa = injectNgForgeAddons();
533
437
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
534
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
535
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
536
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
537
- effectiveFill = computed(() => this.props()?.fill ?? this.ionicConfig?.fill ?? 'solid', ...(ngDevMode ? [{ debugName: "effectiveFill" }] : /* istanbul ignore next */ []));
538
- effectiveShape = computed(() => this.props()?.shape ?? this.ionicConfig?.shape, ...(ngDevMode ? [{ debugName: "effectiveShape" }] : /* istanbul ignore next */ []));
539
- effectiveLabelPlacement = computed(() => this.props()?.labelPlacement ?? this.ionicConfig?.labelPlacement ?? 'start', ...(ngDevMode ? [{ debugName: "effectiveLabelPlacement" }] : /* istanbul ignore next */ []));
540
- effectiveColor = computed(() => this.props()?.color ?? this.ionicConfig?.color, ...(ngDevMode ? [{ debugName: "effectiveColor" }] : /* istanbul ignore next */ []));
541
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
542
- showErrors = shouldShowErrors(this.field);
543
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
438
+ /**
439
+ * Wrapper-style host bag pushed by `DfFieldOutlet`. Declared at the
440
+ * component level so `setInputIfDeclared` (which uses
441
+ * `reflectComponentType`) can write it.
442
+ */
443
+ fieldInputs = input(...(ngDevMode ? [undefined, { debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
444
+ /** Per-instance type override populated by `toggle-password-visibility` preset. */
445
+ typeOverride = inject(IONIC_INPUT_TYPE_OVERRIDE);
446
+ fill = computed(() => this.props()?.fill ?? this.ionicConfig?.fill ?? 'solid', ...(ngDevMode ? [{ debugName: "fill" }] : /* istanbul ignore next */ []));
447
+ shape = computed(() => this.props()?.shape ?? this.ionicConfig?.shape, ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
448
+ /** Default 'stacked' (label above the input) for adapter parity with Material /
449
+ * Bootstrap / PrimeNG. Consumers can opt into Ionic's native inline 'start'
450
+ * or 'fixed' via props.labelPlacement or the IONIC_CONFIG token. */
451
+ labelPlacement = computed(() => this.props()?.labelPlacement ?? this.ionicConfig?.labelPlacement ?? 'stacked', ...(ngDevMode ? [{ debugName: "labelPlacement" }] : /* istanbul ignore next */ []));
452
+ color = computed(() => this.props()?.color ?? this.ionicConfig?.color, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
453
+ /** Override (set by `toggle-password-visibility` preset) wins over `props().type`. */
454
+ type = computed(() => this.typeOverride() ?? this.props()?.type ?? 'text', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
455
+ /** Per-kind split: ion-button addons render inline via the attribute-
456
+ * selector inline component (so the slotted element is a native
457
+ * <ion-button> Ionic can style); everything else (icon/text/template/
458
+ * component kinds) goes through the universal <df-addon-slot> dispatcher
459
+ * wrapped in <span slot="start|end"> for safe shadow-DOM projection. */
460
+ buttonPrefixAddons = computed(() => this.ngfa.prefixAddons().filter((a) => a.kind === 'ion-button'), ...(ngDevMode ? [{ debugName: "buttonPrefixAddons" }] : /* istanbul ignore next */ []));
461
+ buttonSuffixAddons = computed(() => this.ngfa.suffixAddons().filter((a) => a.kind === 'ion-button'), ...(ngDevMode ? [{ debugName: "buttonSuffixAddons" }] : /* istanbul ignore next */ []));
462
+ decorativePrefixAddons = computed(() => this.ngfa.prefixAddons().filter((a) => a.kind !== 'ion-button'), ...(ngDevMode ? [{ debugName: "decorativePrefixAddons" }] : /* istanbul ignore next */ []));
463
+ decorativeSuffixAddons = computed(() => this.ngfa.suffixAddons().filter((a) => a.kind !== 'ion-button'), ...(ngDevMode ? [{ debugName: "decorativeSuffixAddons" }] : /* istanbul ignore next */ []));
464
+ /** Set when the component is destroyed — guards async aria-describedby sync against teardown. */
465
+ destroyed = false;
544
466
  constructor() {
545
- // Shadow DOM - apply meta to ion-input element
546
- setupMetaTracking(this.elementRef, this.meta, {
547
- selector: 'ion-input',
548
- });
467
+ inject(DestroyRef).onDestroy(() => (this.destroyed = true));
549
468
  // ion-input encapsulates a native <input> in shadow DOM and does not automatically
550
469
  // propagate aria-describedby to it. This effect imperatively syncs the attribute
551
470
  // after a microtask to ensure Ionic has resolved the internal element.
552
- explicitEffect([this.ariaDescribedBy], ([describedBy]) => {
471
+ explicitEffect([this.ngf.ariaDescribedBy], ([describedBy]) => {
553
472
  queueMicrotask(() => {
473
+ if (this.destroyed)
474
+ return;
554
475
  const ionInput = this.elementRef.nativeElement.querySelector('ion-input');
555
476
  if (ionInput?.getInputElement) {
556
477
  ionInput.getInputElement().then((inputEl) => {
557
- if (!inputEl)
478
+ if (this.destroyed || !inputEl)
558
479
  return;
559
480
  if (describedBy) {
560
481
  inputEl.setAttribute('aria-describedby', describedBy);
@@ -567,92 +488,163 @@ class IonicInputFieldComponent {
567
488
  });
568
489
  });
569
490
  }
570
- // ─────────────────────────────────────────────────────────────────────────────
571
- // Accessibility
572
- // ─────────────────────────────────────────────────────────────────────────────
573
- /** Base ID for error elements */
574
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
575
- /** Unique ID for the helper text element */
576
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
577
- /** Whether the field is currently in an invalid state (invalid AND touched) */
578
- ariaInvalid = computed(() => {
579
- const fieldState = this.field()();
580
- return fieldState.invalid() && fieldState.touched();
581
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
582
- /** Whether the field has a required validator */
583
- ariaRequired = computed(() => {
584
- return this.field()().required?.() === true ? true : null;
585
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
586
- /** aria-describedby linking to hint OR error elements (mutually exclusive) */
587
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
588
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicInputFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
589
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: IonicInputFieldComponent, isStandalone: true, selector: "df-ion-input", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
590
- @let f = field();
591
- @let inputId = key() + '-input';
491
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicInputFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
492
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonicInputFieldComponent, isStandalone: true, selector: "df-ion-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: [
493
+ {
494
+ provide: IONIC_INPUT_TYPE_OVERRIDE,
495
+ useFactory: () => signal(undefined),
496
+ },
497
+ {
498
+ // Adapter-specific preset semantics for `ion-button` addons (clear /
499
+ // reset / paste / copy / toggle-password-visibility). The directive
500
+ // (`NgForgeAddonAction`) delegates here when an addon configures a
501
+ // `preset`. Per-ion-input-instance so the `typeOverride` signal is
502
+ // scoped to one field.
503
+ provide: ADDON_PRESET_HANDLER,
504
+ useFactory: () => {
505
+ const typeOverride = inject(IONIC_INPUT_TYPE_OVERRIDE);
506
+ const fsc = inject(FIELD_SIGNAL_CONTEXT, { optional: true });
507
+ const logger = inject(DynamicFormLogger);
508
+ // forwardRef for baselineType only — host.props()?.type gates toggle-password-visibility.
509
+ const host = inject(forwardRef(() => IonicInputFieldComponent));
510
+ return {
511
+ run: (preset, ctx) => {
512
+ const fieldKey = ctx.field.key;
513
+ return runIonicPresetAction(preset, ctx, {
514
+ typeOverride,
515
+ fieldValueSetter: ctx.setValue,
516
+ fieldDefaultValueGetter: fsc && fieldKey ? () => fsc.defaultValues()?.[fieldKey] : undefined,
517
+ baselineType: () => host.props()?.type,
518
+ logger,
519
+ });
520
+ },
521
+ };
522
+ },
523
+ },
524
+ ], hostDirectives: [{ directive: i1.NgForgeFieldHost }, { directive: i1.NgForgeAddons }], ngImport: i0, template: `
525
+ @let f = ngf.field();
526
+ @let inputId = ngf.key() + '-input';
592
527
 
593
528
  <ion-input
529
+ ngForgeControl
594
530
  [id]="inputId"
595
- [type]="props()?.type ?? 'text'"
531
+ [type]="type()"
596
532
  [formField]="f"
597
- [label]="(label() | dynamicText | async) ?? undefined"
598
- [labelPlacement]="effectiveLabelPlacement()"
599
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
533
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
534
+ [labelPlacement]="labelPlacement()"
535
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
600
536
  [clearInput]="props()?.clearInput ?? false"
601
537
  [counter]="props()?.counter ?? false"
602
538
  [minlength]="f().minLength?.()"
603
539
  [maxlength]="f().maxLength?.()"
604
- [color]="effectiveColor()"
605
- [fill]="effectiveFill()"
606
- [shape]="effectiveShape()"
540
+ [color]="color()"
541
+ [fill]="fill()"
542
+ [shape]="shape()"
607
543
  [readonly]="f().readonly()"
608
- [helperText]="errorsToDisplay().length === 0 ? ((props()?.hint | dynamicText | async) ?? undefined) : undefined"
609
- [attr.tabindex]="tabIndex()"
610
- [attr.aria-invalid]="ariaInvalid()"
611
- [attr.aria-required]="ariaRequired()"
612
- [attr.aria-describedby]="ariaDescribedBy()"
613
- />
614
- @if (errorsToDisplay()[0]; as error) {
615
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
544
+ [helperText]="ngf.errorsToDisplay().length === 0 ? ((props()?.hint | dynamicText | async) ?? undefined) : undefined"
545
+ [attr.tabindex]="ngf.tabIndex()"
546
+ >
547
+ @for (a of decorativePrefixAddons(); track $index) {
548
+ <span slot="start">
549
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
550
+ </span>
551
+ }
552
+ @for (a of buttonPrefixAddons(); track $index) {
553
+ <ion-button df-ion-button-addon slot="start" [addon]="a" [fieldInputs]="fieldInputs()"></ion-button>
554
+ }
555
+ @for (a of buttonSuffixAddons(); track $index) {
556
+ <ion-button df-ion-button-addon slot="end" [addon]="a" [fieldInputs]="fieldInputs()"></ion-button>
557
+ }
558
+ @for (a of decorativeSuffixAddons(); track $index) {
559
+ <span slot="end">
560
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
561
+ </span>
562
+ }
563
+ </ion-input>
564
+ @if (ngf.errorsToDisplay()[0]; as error) {
565
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
616
566
  }
617
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
567
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}:host{--df-ion-addon-prefix-outer-padding: 0;--df-ion-addon-prefix-inner-padding: .5rem;--df-ion-addon-suffix-inner-padding: .5rem;--df-ion-addon-suffix-outer-padding: 0}:host ::ng-deep ion-input [slot=start],:host ::ng-deep ion-input [slot=end]{display:inline-flex;align-items:center;align-self:center;line-height:1}:host ::ng-deep ion-input [slot=start] df-addon-slot,:host ::ng-deep ion-input [slot=end] df-addon-slot,:host ::ng-deep ion-input [slot=start] df-ion-icon-addon,:host ::ng-deep ion-input [slot=end] df-ion-icon-addon{display:inline-flex;align-items:center;line-height:1}:host ::ng-deep ion-input [slot=start]{padding-inline-start:var(--df-ion-addon-prefix-outer-padding);padding-inline-end:var(--df-ion-addon-prefix-inner-padding)}:host ::ng-deep ion-input [slot=end]{padding-inline-start:var(--df-ion-addon-suffix-inner-padding);padding-inline-end:var(--df-ion-addon-suffix-outer-padding)}:host ::ng-deep ion-input ion-icon{color:inherit}\n"], dependencies: [{ kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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: "component", type: IonInlineButtonAddonComponent, selector: "ion-button[df-ion-button-addon]" }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
618
568
  }
619
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicInputFieldComponent, decorators: [{
569
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicInputFieldComponent, decorators: [{
620
570
  type: Component,
621
- args: [{ selector: 'df-ion-input', imports: [IonInput, IonNote, FormField, DynamicTextPipe, AsyncPipe], template: `
622
- @let f = field();
623
- @let inputId = key() + '-input';
571
+ args: [{ selector: 'df-ion-input', imports: [IonInput, IonNote, FormField, DynamicTextPipe, AsyncPipe, NgForgeControl, DfAddonSlot, IonInlineButtonAddonComponent], hostDirectives: [NgForgeFieldHost, NgForgeAddons], template: `
572
+ @let f = ngf.field();
573
+ @let inputId = ngf.key() + '-input';
624
574
 
625
575
  <ion-input
576
+ ngForgeControl
626
577
  [id]="inputId"
627
- [type]="props()?.type ?? 'text'"
578
+ [type]="type()"
628
579
  [formField]="f"
629
- [label]="(label() | dynamicText | async) ?? undefined"
630
- [labelPlacement]="effectiveLabelPlacement()"
631
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
580
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
581
+ [labelPlacement]="labelPlacement()"
582
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
632
583
  [clearInput]="props()?.clearInput ?? false"
633
584
  [counter]="props()?.counter ?? false"
634
585
  [minlength]="f().minLength?.()"
635
586
  [maxlength]="f().maxLength?.()"
636
- [color]="effectiveColor()"
637
- [fill]="effectiveFill()"
638
- [shape]="effectiveShape()"
587
+ [color]="color()"
588
+ [fill]="fill()"
589
+ [shape]="shape()"
639
590
  [readonly]="f().readonly()"
640
- [helperText]="errorsToDisplay().length === 0 ? ((props()?.hint | dynamicText | async) ?? undefined) : undefined"
641
- [attr.tabindex]="tabIndex()"
642
- [attr.aria-invalid]="ariaInvalid()"
643
- [attr.aria-required]="ariaRequired()"
644
- [attr.aria-describedby]="ariaDescribedBy()"
645
- />
646
- @if (errorsToDisplay()[0]; as error) {
647
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
591
+ [helperText]="ngf.errorsToDisplay().length === 0 ? ((props()?.hint | dynamicText | async) ?? undefined) : undefined"
592
+ [attr.tabindex]="ngf.tabIndex()"
593
+ >
594
+ @for (a of decorativePrefixAddons(); track $index) {
595
+ <span slot="start">
596
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
597
+ </span>
598
+ }
599
+ @for (a of buttonPrefixAddons(); track $index) {
600
+ <ion-button df-ion-button-addon slot="start" [addon]="a" [fieldInputs]="fieldInputs()"></ion-button>
601
+ }
602
+ @for (a of buttonSuffixAddons(); track $index) {
603
+ <ion-button df-ion-button-addon slot="end" [addon]="a" [fieldInputs]="fieldInputs()"></ion-button>
604
+ }
605
+ @for (a of decorativeSuffixAddons(); track $index) {
606
+ <span slot="end">
607
+ <df-addon-slot [addon]="a" [fieldInputs]="fieldInputs()" [hidden]="ngfa.hiddenSignalCache().get(a)" />
608
+ </span>
609
+ }
610
+ </ion-input>
611
+ @if (ngf.errorsToDisplay()[0]; as error) {
612
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
648
613
  }
649
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
650
- '[id]': '`${key()}`',
651
- '[attr.data-testid]': 'key()',
652
- '[class]': 'className()',
653
- '[attr.hidden]': 'field()().hidden() || null',
654
- }, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host([hidden]){display:none!important}\n"] }]
655
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
614
+ `, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
615
+ {
616
+ provide: IONIC_INPUT_TYPE_OVERRIDE,
617
+ useFactory: () => signal(undefined),
618
+ },
619
+ {
620
+ // Adapter-specific preset semantics for `ion-button` addons (clear /
621
+ // reset / paste / copy / toggle-password-visibility). The directive
622
+ // (`NgForgeAddonAction`) delegates here when an addon configures a
623
+ // `preset`. Per-ion-input-instance so the `typeOverride` signal is
624
+ // scoped to one field.
625
+ provide: ADDON_PRESET_HANDLER,
626
+ useFactory: () => {
627
+ const typeOverride = inject(IONIC_INPUT_TYPE_OVERRIDE);
628
+ const fsc = inject(FIELD_SIGNAL_CONTEXT, { optional: true });
629
+ const logger = inject(DynamicFormLogger);
630
+ // forwardRef for baselineType only — host.props()?.type gates toggle-password-visibility.
631
+ const host = inject(forwardRef(() => IonicInputFieldComponent));
632
+ return {
633
+ run: (preset, ctx) => {
634
+ const fieldKey = ctx.field.key;
635
+ return runIonicPresetAction(preset, ctx, {
636
+ typeOverride,
637
+ fieldValueSetter: ctx.setValue,
638
+ fieldDefaultValueGetter: fsc && fieldKey ? () => fsc.defaultValues()?.[fieldKey] : undefined,
639
+ baselineType: () => host.props()?.type,
640
+ logger,
641
+ });
642
+ },
643
+ };
644
+ },
645
+ },
646
+ ], styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}:host{--df-ion-addon-prefix-outer-padding: 0;--df-ion-addon-prefix-inner-padding: .5rem;--df-ion-addon-suffix-inner-padding: .5rem;--df-ion-addon-suffix-outer-padding: 0}:host ::ng-deep ion-input [slot=start],:host ::ng-deep ion-input [slot=end]{display:inline-flex;align-items:center;align-self:center;line-height:1}:host ::ng-deep ion-input [slot=start] df-addon-slot,:host ::ng-deep ion-input [slot=end] df-addon-slot,:host ::ng-deep ion-input [slot=start] df-ion-icon-addon,:host ::ng-deep ion-input [slot=end] df-ion-icon-addon{display:inline-flex;align-items:center;line-height:1}:host ::ng-deep ion-input [slot=start]{padding-inline-start:var(--df-ion-addon-prefix-outer-padding);padding-inline-end:var(--df-ion-addon-prefix-inner-padding)}:host ::ng-deep ion-input [slot=end]{padding-inline-start:var(--df-ion-addon-suffix-inner-padding);padding-inline-end:var(--df-ion-addon-suffix-outer-padding)}:host ::ng-deep ion-input ion-icon{color:inherit}\n"] }]
647
+ }], ctorParameters: () => [], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], fieldInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldInputs", required: false }] }] } });
656
648
 
657
649
  var ionicInput_component = /*#__PURE__*/Object.freeze({
658
650
  __proto__: null,
@@ -660,41 +652,11 @@ var ionicInput_component = /*#__PURE__*/Object.freeze({
660
652
  });
661
653
 
662
654
  class IonicMultiCheckboxFieldComponent {
663
- elementRef = inject((ElementRef));
664
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
665
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
666
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
667
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
668
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
669
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
655
+ ngf = injectNgForgeField();
670
656
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
671
657
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
672
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
673
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
674
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
675
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
676
- showErrors = shouldShowErrors(this.field);
677
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
678
- // ─────────────────────────────────────────────────────────────────────────────
679
- // Accessibility
680
- // ─────────────────────────────────────────────────────────────────────────────
681
- /** Base ID for error elements */
682
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
683
- /** Unique ID for the helper text element */
684
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
685
- /** Whether the field is currently in an invalid state (invalid AND touched) */
686
- ariaInvalid = computed(() => {
687
- const fieldState = this.field()();
688
- return fieldState.invalid() && fieldState.touched();
689
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
690
- /** Whether the field has a required validator */
691
- ariaRequired = computed(() => {
692
- return this.field()().required?.() === true ? true : null;
693
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
694
- /** aria-describedby linking to hint OR error elements (mutually exclusive) */
695
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
696
658
  valueViewModel = linkedSignal(() => {
697
- const currentValues = this.field()().value();
659
+ const currentValues = this.ngf.field()().value();
698
660
  return this.options().filter((option) => currentValues.includes(option.value));
699
661
  }, { ...(ngDevMode ? { debugName: "valueViewModel" } : /* istanbul ignore next */ {}), equal: isEqual });
700
662
  /** Computed map of checked option values for O(1) lookup in template */
@@ -706,15 +668,10 @@ class IonicMultiCheckboxFieldComponent {
706
668
  return map;
707
669
  }, ...(ngDevMode ? [{ debugName: "checkedValuesMap" }] : /* istanbul ignore next */ []));
708
670
  constructor() {
709
- // Shadow DOM - apply meta to ion-checkbox elements, re-apply when options change
710
- setupMetaTracking(this.elementRef, this.meta, {
711
- selector: 'ion-checkbox',
712
- dependents: [this.options],
713
- });
714
671
  explicitEffect([this.valueViewModel], ([selectedOptions]) => {
715
672
  const selectedValues = selectedOptions.map((option) => option.value);
716
- if (!isEqual(selectedValues, this.field()().value())) {
717
- this.field()().value.set(selectedValues);
673
+ if (!isEqual(selectedValues, this.ngf.field()().value())) {
674
+ this.ngf.field()().value.set(selectedValues);
718
675
  }
719
676
  });
720
677
  explicitEffect([this.options], ([options]) => {
@@ -736,26 +693,27 @@ class IonicMultiCheckboxFieldComponent {
736
693
  }
737
694
  });
738
695
  }
739
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicMultiCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
740
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: IonicMultiCheckboxFieldComponent, isStandalone: true, selector: "df-ion-multi-checkbox", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className() || \"\"", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
741
- @let f = field();
696
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicMultiCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
697
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonicMultiCheckboxFieldComponent, isStandalone: true, selector: "df-ion-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: `
698
+ @let f = ngf.field();
742
699
  @let checked = checkedValuesMap();
743
- @if (label(); as label) {
700
+ @if (ngf.label(); as label) {
744
701
  <div class="checkbox-group-label">{{ label | dynamicText | async }}</div>
745
702
  }
746
703
 
747
704
  <div
748
705
  class="checkbox-group"
749
706
  role="group"
750
- [attr.aria-invalid]="ariaInvalid()"
751
- [attr.aria-required]="ariaRequired()"
752
- [attr.aria-describedby]="ariaDescribedBy()"
753
- [class.ion-invalid]="showErrors()"
754
- [class.ion-touched]="field()().touched()"
707
+ [attr.aria-invalid]="ngf.ariaInvalid()"
708
+ [attr.aria-required]="ngf.ariaRequired()"
709
+ [attr.aria-describedby]="ngf.ariaDescribedBy()"
710
+ [class.ion-invalid]="ngf.showErrors()"
711
+ [class.ion-touched]="f().touched()"
755
712
  >
756
713
  @for (option of options(); track option.value) {
757
714
  <ion-item lines="none">
758
715
  <ion-checkbox
716
+ ngForgeControl
759
717
  [checked]="checked['' + option.value]"
760
718
  [disabled]="f().disabled() || option.disabled"
761
719
  [labelPlacement]="props()?.labelPlacement ?? 'end'"
@@ -769,34 +727,35 @@ class IonicMultiCheckboxFieldComponent {
769
727
  }
770
728
  </div>
771
729
 
772
- @if (errorsToDisplay()[0]; as error) {
773
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
730
+ @if (ngf.errorsToDisplay()[0]; as error) {
731
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
774
732
  } @else if (props()?.hint; as hint) {
775
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
733
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
776
734
  }
777
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}.checkbox-group-label{margin-bottom:8px;font-weight:500}.checkbox-group{display:flex;flex-direction:column;gap:4px}ion-item{--padding-start: 0;--inner-padding-end: 0}\n"], dependencies: [{ kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
735
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}.checkbox-group-label{margin-bottom:8px;font-weight:500}.checkbox-group{display:flex;flex-direction:column;gap:4px}ion-item{--padding-start: 0;--inner-padding-end: 0}\n"], dependencies: [{ kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
778
736
  }
779
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicMultiCheckboxFieldComponent, decorators: [{
737
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicMultiCheckboxFieldComponent, decorators: [{
780
738
  type: Component,
781
- args: [{ selector: 'df-ion-multi-checkbox', imports: [IonCheckbox, IonItem, IonNote, DynamicTextPipe, AsyncPipe], template: `
782
- @let f = field();
739
+ args: [{ selector: 'df-ion-multi-checkbox', imports: [IonCheckbox, IonItem, IonNote, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
740
+ @let f = ngf.field();
783
741
  @let checked = checkedValuesMap();
784
- @if (label(); as label) {
742
+ @if (ngf.label(); as label) {
785
743
  <div class="checkbox-group-label">{{ label | dynamicText | async }}</div>
786
744
  }
787
745
 
788
746
  <div
789
747
  class="checkbox-group"
790
748
  role="group"
791
- [attr.aria-invalid]="ariaInvalid()"
792
- [attr.aria-required]="ariaRequired()"
793
- [attr.aria-describedby]="ariaDescribedBy()"
794
- [class.ion-invalid]="showErrors()"
795
- [class.ion-touched]="field()().touched()"
749
+ [attr.aria-invalid]="ngf.ariaInvalid()"
750
+ [attr.aria-required]="ngf.ariaRequired()"
751
+ [attr.aria-describedby]="ngf.ariaDescribedBy()"
752
+ [class.ion-invalid]="ngf.showErrors()"
753
+ [class.ion-touched]="f().touched()"
796
754
  >
797
755
  @for (option of options(); track option.value) {
798
756
  <ion-item lines="none">
799
757
  <ion-checkbox
758
+ ngForgeControl
800
759
  [checked]="checked['' + option.value]"
801
760
  [disabled]="f().disabled() || option.disabled"
802
761
  [labelPlacement]="props()?.labelPlacement ?? 'end'"
@@ -810,18 +769,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
810
769
  }
811
770
  </div>
812
771
 
813
- @if (errorsToDisplay()[0]; as error) {
814
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
772
+ @if (ngf.errorsToDisplay()[0]; as error) {
773
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
815
774
  } @else if (props()?.hint; as hint) {
816
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
775
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
817
776
  }
818
- `, host: {
819
- '[class]': 'className() || ""',
820
- '[id]': '`${key()}`',
821
- '[attr.data-testid]': 'key()',
822
- '[attr.hidden]': 'field()().hidden() || null',
823
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}.checkbox-group-label{margin-bottom:8px;font-weight:500}.checkbox-group{display:flex;flex-direction:column;gap:4px}ion-item{--padding-start: 0;--inner-padding-end: 0}\n"] }]
824
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
777
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}.checkbox-group-label{margin-bottom:8px;font-weight:500}.checkbox-group{display:flex;flex-direction:column;gap:4px}ion-item{--padding-start: 0;--inner-padding-end: 0}\n"] }]
778
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
825
779
 
826
780
  var ionicMultiCheckbox_component = /*#__PURE__*/Object.freeze({
827
781
  __proto__: null,
@@ -829,53 +783,15 @@ var ionicMultiCheckbox_component = /*#__PURE__*/Object.freeze({
829
783
  });
830
784
 
831
785
  class IonicRadioFieldComponent {
832
- elementRef = inject((ElementRef));
833
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
834
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
835
- // Properties
836
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
837
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
838
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
839
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
786
+ ngf = injectNgForgeField();
840
787
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
841
788
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
842
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
843
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
844
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
845
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
846
- showErrors = shouldShowErrors(this.field);
847
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
848
789
  defaultCompare = Object.is;
849
- constructor() {
850
- // Shadow DOM - apply meta to ion-radio elements, re-apply when options change
851
- setupMetaTracking(this.elementRef, this.meta, {
852
- selector: 'ion-radio',
853
- dependents: [this.options],
854
- });
855
- }
856
- // ─────────────────────────────────────────────────────────────────────────────
857
- // Accessibility
858
- // ─────────────────────────────────────────────────────────────────────────────
859
- /** Base ID for error elements */
860
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
861
- /** Unique ID for the helper text element */
862
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
863
- /** Whether the field is currently in an invalid state (invalid AND touched) */
864
- ariaInvalid = computed(() => {
865
- const fieldState = this.field()();
866
- return fieldState.invalid() && fieldState.touched();
867
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
868
- /** Whether the field has a required validator */
869
- ariaRequired = computed(() => {
870
- return this.field()().required?.() === true ? true : null;
871
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
872
- /** aria-describedby linking to hint OR error elements (mutually exclusive) */
873
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
874
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicRadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
875
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: IonicRadioFieldComponent, isStandalone: true, selector: "df-ion-radio", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
876
- @let f = field();
877
- @let radioGroupId = key() + '-radio-group';
878
- @if (label(); as label) {
790
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicRadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
791
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonicRadioFieldComponent, isStandalone: true, selector: "df-ion-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: `
792
+ @let f = ngf.field();
793
+ @let radioGroupId = ngf.key() + '-radio-group';
794
+ @if (ngf.label(); as label) {
879
795
  <div class="radio-label">{{ label | dynamicText | async }}</div>
880
796
  }
881
797
 
@@ -883,13 +799,14 @@ class IonicRadioFieldComponent {
883
799
  [id]="radioGroupId"
884
800
  [formField]="f"
885
801
  [compareWith]="props()?.compareWith || defaultCompare"
886
- [attr.aria-invalid]="ariaInvalid()"
887
- [attr.aria-required]="ariaRequired()"
888
- [attr.aria-describedby]="ariaDescribedBy()"
802
+ [attr.aria-invalid]="ngf.ariaInvalid()"
803
+ [attr.aria-required]="ngf.ariaRequired()"
804
+ [attr.aria-describedby]="ngf.ariaDescribedBy()"
889
805
  >
890
806
  @for (option of options(); track option.value) {
891
807
  <ion-item [lines]="'none'">
892
808
  <ion-radio
809
+ ngForgeControl
893
810
  [value]="option.value"
894
811
  [disabled]="option.disabled || false"
895
812
  [labelPlacement]="props()?.labelPlacement ?? 'end'"
@@ -902,19 +819,19 @@ class IonicRadioFieldComponent {
902
819
  }
903
820
  </ion-radio-group>
904
821
 
905
- @if (errorsToDisplay()[0]; as error) {
906
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
822
+ @if (ngf.errorsToDisplay()[0]; as error) {
823
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
907
824
  } @else if (props()?.hint; as hint) {
908
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
825
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
909
826
  }
910
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}.radio-label{font-size:.875rem;font-weight:500;margin-bottom:.5rem;color:var(--ion-text-color)}\n"], dependencies: [{ kind: "component", type: IonRadioGroup, selector: "ion-radio-group", inputs: ["allowEmptySelection", "compareWith", "errorText", "helperText", "name", "value"] }, { kind: "component", type: IonRadio, selector: "ion-radio", inputs: ["alignment", "color", "disabled", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
827
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}.radio-label{font-size:.875rem;font-weight:500;margin-bottom:.5rem;color:var(--ion-text-color)}\n"], dependencies: [{ kind: "component", type: IonRadioGroup, selector: "ion-radio-group", inputs: ["allowEmptySelection", "compareWith", "errorText", "helperText", "name", "value"] }, { kind: "component", type: IonRadio, selector: "ion-radio", inputs: ["alignment", "color", "disabled", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
911
828
  }
912
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicRadioFieldComponent, decorators: [{
829
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicRadioFieldComponent, decorators: [{
913
830
  type: Component,
914
- args: [{ selector: 'df-ion-radio', imports: [IonRadioGroup, IonRadio, IonItem, IonNote, FormField, DynamicTextPipe, AsyncPipe], template: `
915
- @let f = field();
916
- @let radioGroupId = key() + '-radio-group';
917
- @if (label(); as label) {
831
+ args: [{ selector: 'df-ion-radio', imports: [IonRadioGroup, IonRadio, IonItem, IonNote, FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
832
+ @let f = ngf.field();
833
+ @let radioGroupId = ngf.key() + '-radio-group';
834
+ @if (ngf.label(); as label) {
918
835
  <div class="radio-label">{{ label | dynamicText | async }}</div>
919
836
  }
920
837
 
@@ -922,13 +839,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
922
839
  [id]="radioGroupId"
923
840
  [formField]="f"
924
841
  [compareWith]="props()?.compareWith || defaultCompare"
925
- [attr.aria-invalid]="ariaInvalid()"
926
- [attr.aria-required]="ariaRequired()"
927
- [attr.aria-describedby]="ariaDescribedBy()"
842
+ [attr.aria-invalid]="ngf.ariaInvalid()"
843
+ [attr.aria-required]="ngf.ariaRequired()"
844
+ [attr.aria-describedby]="ngf.ariaDescribedBy()"
928
845
  >
929
846
  @for (option of options(); track option.value) {
930
847
  <ion-item [lines]="'none'">
931
848
  <ion-radio
849
+ ngForgeControl
932
850
  [value]="option.value"
933
851
  [disabled]="option.disabled || false"
934
852
  [labelPlacement]="props()?.labelPlacement ?? 'end'"
@@ -941,18 +859,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
941
859
  }
942
860
  </ion-radio-group>
943
861
 
944
- @if (errorsToDisplay()[0]; as error) {
945
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
862
+ @if (ngf.errorsToDisplay()[0]; as error) {
863
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
946
864
  } @else if (props()?.hint; as hint) {
947
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
865
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
948
866
  }
949
- `, host: {
950
- '[class]': 'className()',
951
- '[id]': '`${key()}`',
952
- '[attr.data-testid]': 'key()',
953
- '[attr.hidden]': 'field()().hidden() || null',
954
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}.radio-label{font-size:.875rem;font-weight:500;margin-bottom:.5rem;color:var(--ion-text-color)}\n"] }]
955
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
867
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}.radio-label{font-size:.875rem;font-weight:500;margin-bottom:.5rem;color:var(--ion-text-color)}\n"] }]
868
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
956
869
 
957
870
  var ionicRadio_component = /*#__PURE__*/Object.freeze({
958
871
  __proto__: null,
@@ -960,54 +873,22 @@ var ionicRadio_component = /*#__PURE__*/Object.freeze({
960
873
  });
961
874
 
962
875
  class IonicSelectFieldComponent {
963
- elementRef = inject((ElementRef));
964
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
965
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
966
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
967
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
968
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
969
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
876
+ ngf = injectNgForgeField();
970
877
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
971
878
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
972
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
973
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
974
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
975
- constructor() {
976
- setupMetaTracking(this.elementRef, this.meta, { selector: 'ion-select' });
977
- }
978
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
979
- showErrors = shouldShowErrors(this.field);
980
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
981
879
  defaultCompare = Object.is;
982
- // ─────────────────────────────────────────────────────────────────────────────
983
- // Accessibility
984
- // ─────────────────────────────────────────────────────────────────────────────
985
- /** Base ID for error elements */
986
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
987
- /** Unique ID for the helper text element */
988
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
989
- /** Whether the field is currently in an invalid state (invalid AND touched) */
990
- ariaInvalid = computed(() => {
991
- const fieldState = this.field()();
992
- return fieldState.invalid() && fieldState.touched();
993
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
994
- /** Whether the field has a required validator */
995
- ariaRequired = computed(() => {
996
- return this.field()().required?.() === true ? true : null;
997
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
998
- /** aria-describedby linking to hint OR error elements (mutually exclusive) */
999
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1000
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1001
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: IonicSelectFieldComponent, isStandalone: true, selector: "df-ion-select", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1002
- @let f = field();
1003
- @let selectId = key() + '-select';
880
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
881
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonicSelectFieldComponent, isStandalone: true, selector: "df-ion-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: `
882
+ @let f = ngf.field();
883
+ @let selectId = ngf.key() + '-select';
1004
884
 
1005
885
  <ion-select
886
+ ngForgeControl
1006
887
  [id]="selectId"
1007
888
  [formField]="f"
1008
- [label]="(label() | dynamicText | async) ?? undefined"
889
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
1009
890
  [labelPlacement]="props()?.labelPlacement ?? 'stacked'"
1010
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
891
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
1011
892
  [multiple]="props()?.multiple ?? false"
1012
893
  [compareWith]="props()?.compareWith ?? defaultCompare"
1013
894
  [interface]="props()?.interface ?? 'alert'"
@@ -1017,10 +898,7 @@ class IonicSelectFieldComponent {
1017
898
  [color]="props()?.color"
1018
899
  [fill]="props()?.fill ?? 'outline'"
1019
900
  [shape]="props()?.shape"
1020
- [attr.tabindex]="tabIndex()"
1021
- [attr.aria-invalid]="ariaInvalid()"
1022
- [attr.aria-required]="ariaRequired()"
1023
- [attr.aria-describedby]="ariaDescribedBy()"
901
+ [attr.tabindex]="ngf.tabIndex()"
1024
902
  >
1025
903
  @for (option of options(); track option.value) {
1026
904
  <ion-select-option [value]="option.value" [disabled]="option.disabled || false">
@@ -1028,25 +906,26 @@ class IonicSelectFieldComponent {
1028
906
  </ion-select-option>
1029
907
  }
1030
908
  </ion-select>
1031
- @if (errorsToDisplay()[0]; as error) {
1032
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
909
+ @if (ngf.errorsToDisplay()[0]; as error) {
910
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
1033
911
  } @else if (props()?.hint; as hint) {
1034
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
912
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
1035
913
  }
1036
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
914
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
1037
915
  }
1038
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicSelectFieldComponent, decorators: [{
916
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicSelectFieldComponent, decorators: [{
1039
917
  type: Component,
1040
- args: [{ selector: 'df-ion-select', imports: [IonSelect, IonSelectOption, IonNote, FormField, DynamicTextPipe, AsyncPipe], template: `
1041
- @let f = field();
1042
- @let selectId = key() + '-select';
918
+ args: [{ selector: 'df-ion-select', imports: [IonSelect, IonSelectOption, IonNote, FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
919
+ @let f = ngf.field();
920
+ @let selectId = ngf.key() + '-select';
1043
921
 
1044
922
  <ion-select
923
+ ngForgeControl
1045
924
  [id]="selectId"
1046
925
  [formField]="f"
1047
- [label]="(label() | dynamicText | async) ?? undefined"
926
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
1048
927
  [labelPlacement]="props()?.labelPlacement ?? 'stacked'"
1049
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
928
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
1050
929
  [multiple]="props()?.multiple ?? false"
1051
930
  [compareWith]="props()?.compareWith ?? defaultCompare"
1052
931
  [interface]="props()?.interface ?? 'alert'"
@@ -1056,10 +935,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1056
935
  [color]="props()?.color"
1057
936
  [fill]="props()?.fill ?? 'outline'"
1058
937
  [shape]="props()?.shape"
1059
- [attr.tabindex]="tabIndex()"
1060
- [attr.aria-invalid]="ariaInvalid()"
1061
- [attr.aria-required]="ariaRequired()"
1062
- [attr.aria-describedby]="ariaDescribedBy()"
938
+ [attr.tabindex]="ngf.tabIndex()"
1063
939
  >
1064
940
  @for (option of options(); track option.value) {
1065
941
  <ion-select-option [value]="option.value" [disabled]="option.disabled || false">
@@ -1067,18 +943,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1067
943
  </ion-select-option>
1068
944
  }
1069
945
  </ion-select>
1070
- @if (errorsToDisplay()[0]; as error) {
1071
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
946
+ @if (ngf.errorsToDisplay()[0]; as error) {
947
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
1072
948
  } @else if (props()?.hint; as hint) {
1073
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
949
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
1074
950
  }
1075
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
1076
- '[id]': '`${key()}`',
1077
- '[attr.data-testid]': 'key()',
1078
- '[class]': 'className()',
1079
- '[attr.hidden]': 'field()().hidden() || null',
1080
- }, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host([hidden]){display:none!important}\n"] }]
1081
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
951
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"] }]
952
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1082
953
 
1083
954
  var ionicSelect_component = /*#__PURE__*/Object.freeze({
1084
955
  __proto__: null,
@@ -1086,52 +957,19 @@ var ionicSelect_component = /*#__PURE__*/Object.freeze({
1086
957
  });
1087
958
 
1088
959
  class IonicSliderFieldComponent {
1089
- elementRef = inject((ElementRef));
1090
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
1091
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
1092
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
1093
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
1094
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1095
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
960
+ ngf = injectNgForgeField();
1096
961
  step = input(...(ngDevMode ? [undefined, { debugName: "step" }] : /* istanbul ignore next */ []));
1097
962
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
1098
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
1099
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
1100
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
1101
- constructor() {
1102
- setupMetaTracking(this.elementRef, this.meta);
1103
- }
1104
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1105
- showErrors = shouldShowErrors(this.field);
1106
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
1107
963
  defaultPinFormatter = (value) => String(value);
1108
- // ─────────────────────────────────────────────────────────────────────────────
1109
- // Accessibility
1110
- // ─────────────────────────────────────────────────────────────────────────────
1111
- /** Base ID for error elements */
1112
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
1113
- /** Unique ID for the helper text element */
1114
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
1115
- /** Whether the field is currently in an invalid state (invalid AND touched) */
1116
- ariaInvalid = computed(() => {
1117
- const fieldState = this.field()();
1118
- return fieldState.invalid() && fieldState.touched();
1119
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
1120
- /** Whether the field has a required validator */
1121
- ariaRequired = computed(() => {
1122
- return this.field()().required?.() === true ? true : null;
1123
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
1124
- /** aria-describedby linking to hint OR error elements (mutually exclusive) */
1125
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1126
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicSliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1127
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: IonicSliderFieldComponent, isStandalone: true, selector: "df-ion-slider", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1128
- @let f = field();
1129
- @let inputId = key() + '-input';
964
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicSliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
965
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonicSliderFieldComponent, isStandalone: true, selector: "df-ion-slider", inputs: { 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 }, { directive: i1.NgForgeHostControl }], ngImport: i0, template: `
966
+ @let f = ngf.field();
967
+ @let inputId = ngf.key() + '-input';
1130
968
 
1131
969
  <ion-range
1132
970
  [id]="inputId"
1133
971
  [formField]="f"
1134
- [label]="(label() | dynamicText | async) ?? undefined"
972
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
1135
973
  [labelPlacement]="props()?.labelPlacement ?? 'stacked'"
1136
974
  [min]="f().min?.() ?? props()?.min ?? 0"
1137
975
  [max]="f().max?.() ?? props()?.max ?? 100"
@@ -1142,29 +980,25 @@ class IonicSliderFieldComponent {
1142
980
  [ticks]="props()?.ticks ?? false"
1143
981
  [snaps]="props()?.snaps ?? false"
1144
982
  [color]="props()?.color ?? 'primary'"
1145
- [attr.tabindex]="tabIndex()"
1146
- [attr.aria-invalid]="ariaInvalid()"
1147
- [attr.aria-required]="ariaRequired()"
1148
- [attr.aria-describedby]="ariaDescribedBy()"
1149
983
  />
1150
984
 
1151
- @if (errorsToDisplay()[0]; as error) {
1152
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
985
+ @if (ngf.errorsToDisplay()[0]; as error) {
986
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
1153
987
  } @else if (props()?.hint; as hint) {
1154
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
988
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
1155
989
  }
1156
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonRange, selector: "ion-range", inputs: ["activeBarStart", "color", "debounce", "disabled", "dualKnobs", "label", "labelPlacement", "max", "min", "mode", "name", "pin", "pinFormatter", "snaps", "step", "ticks", "value"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
990
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonRange, selector: "ion-range", inputs: ["activeBarStart", "color", "debounce", "disabled", "dualKnobs", "label", "labelPlacement", "max", "min", "mode", "name", "pin", "pinFormatter", "snaps", "step", "ticks", "value"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
1157
991
  }
1158
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicSliderFieldComponent, decorators: [{
992
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicSliderFieldComponent, decorators: [{
1159
993
  type: Component,
1160
- args: [{ selector: 'df-ion-slider', imports: [IonRange, IonNote, FormField, DynamicTextPipe, AsyncPipe], template: `
1161
- @let f = field();
1162
- @let inputId = key() + '-input';
994
+ args: [{ selector: 'df-ion-slider', imports: [IonRange, IonNote, FormField, DynamicTextPipe, AsyncPipe], hostDirectives: [NgForgeFieldHost, NgForgeHostControl], template: `
995
+ @let f = ngf.field();
996
+ @let inputId = ngf.key() + '-input';
1163
997
 
1164
998
  <ion-range
1165
999
  [id]="inputId"
1166
1000
  [formField]="f"
1167
- [label]="(label() | dynamicText | async) ?? undefined"
1001
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
1168
1002
  [labelPlacement]="props()?.labelPlacement ?? 'stacked'"
1169
1003
  [min]="f().min?.() ?? props()?.min ?? 0"
1170
1004
  [max]="f().max?.() ?? props()?.max ?? 100"
@@ -1175,24 +1009,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1175
1009
  [ticks]="props()?.ticks ?? false"
1176
1010
  [snaps]="props()?.snaps ?? false"
1177
1011
  [color]="props()?.color ?? 'primary'"
1178
- [attr.tabindex]="tabIndex()"
1179
- [attr.aria-invalid]="ariaInvalid()"
1180
- [attr.aria-required]="ariaRequired()"
1181
- [attr.aria-describedby]="ariaDescribedBy()"
1182
1012
  />
1183
1013
 
1184
- @if (errorsToDisplay()[0]; as error) {
1185
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
1014
+ @if (ngf.errorsToDisplay()[0]; as error) {
1015
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
1186
1016
  } @else if (props()?.hint; as hint) {
1187
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
1017
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
1188
1018
  }
1189
- `, host: {
1190
- '[class]': 'className()',
1191
- '[id]': '`${key()}`',
1192
- '[attr.data-testid]': 'key()',
1193
- '[attr.hidden]': 'field()().hidden() || null',
1194
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}\n"] }]
1195
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
1019
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"] }]
1020
+ }], propDecorators: { step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1196
1021
 
1197
1022
  var ionicSlider_component = /*#__PURE__*/Object.freeze({
1198
1023
  __proto__: null,
@@ -1201,28 +1026,13 @@ var ionicSlider_component = /*#__PURE__*/Object.freeze({
1201
1026
 
1202
1027
  class IonicTextareaFieldComponent {
1203
1028
  elementRef = inject((ElementRef));
1204
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
1205
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
1206
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
1207
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
1208
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1209
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1029
+ ngf = injectNgForgeField();
1210
1030
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
1211
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
1212
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
1213
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
1214
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1215
- showErrors = shouldShowErrors(this.field);
1216
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
1217
1031
  constructor() {
1218
- // Shadow DOM - apply meta to ion-textarea element
1219
- setupMetaTracking(this.elementRef, this.meta, {
1220
- selector: 'ion-textarea',
1221
- });
1222
1032
  // ion-textarea encapsulates a native <textarea> in shadow DOM and does not automatically
1223
1033
  // propagate aria-describedby to it. This effect imperatively syncs the attribute
1224
1034
  // after a microtask to ensure Ionic has resolved the internal element.
1225
- explicitEffect([this.ariaDescribedBy], ([describedBy]) => {
1035
+ explicitEffect([this.ngf.ariaDescribedBy], ([describedBy]) => {
1226
1036
  queueMicrotask(() => {
1227
1037
  const ionTextarea = this.elementRef.nativeElement.querySelector('ion-textarea');
1228
1038
  if (ionTextarea?.getInputElement) {
@@ -1240,35 +1050,18 @@ class IonicTextareaFieldComponent {
1240
1050
  });
1241
1051
  });
1242
1052
  }
1243
- // ─────────────────────────────────────────────────────────────────────────────
1244
- // Accessibility
1245
- // ─────────────────────────────────────────────────────────────────────────────
1246
- /** Base ID for error elements */
1247
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
1248
- /** Unique ID for the helper text element */
1249
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
1250
- /** Whether the field is currently in an invalid state (invalid AND touched) */
1251
- ariaInvalid = computed(() => {
1252
- const fieldState = this.field()();
1253
- return fieldState.invalid() && fieldState.touched();
1254
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
1255
- /** Whether the field has a required validator */
1256
- ariaRequired = computed(() => {
1257
- return this.field()().required?.() === true ? true : null;
1258
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
1259
- /** aria-describedby linking to hint OR error elements (mutually exclusive) */
1260
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1261
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicTextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1262
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: IonicTextareaFieldComponent, isStandalone: true, selector: "df-ion-textarea", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1263
- @let f = field();
1264
- @let textareaId = key() + '-textarea';
1053
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicTextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1054
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonicTextareaFieldComponent, isStandalone: true, selector: "df-ion-textarea", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
1055
+ @let f = ngf.field();
1056
+ @let textareaId = ngf.key() + '-textarea';
1265
1057
 
1266
1058
  <ion-textarea
1059
+ ngForgeControl
1267
1060
  [id]="textareaId"
1268
1061
  [formField]="f"
1269
- [label]="(label() | dynamicText | async) ?? undefined"
1062
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
1270
1063
  [labelPlacement]="props()?.labelPlacement ?? 'stacked'"
1271
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1064
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
1272
1065
  [rows]="props()?.rows ?? 4"
1273
1066
  [autoGrow]="props()?.autoGrow ?? false"
1274
1067
  [counter]="props()?.counter ?? false"
@@ -1278,29 +1071,27 @@ class IonicTextareaFieldComponent {
1278
1071
  [fill]="props()?.fill ?? 'outline'"
1279
1072
  [shape]="props()?.shape"
1280
1073
  [readonly]="f().readonly()"
1281
- [helperText]="errorsToDisplay().length === 0 ? ((props()?.hint | dynamicText | async) ?? undefined) : undefined"
1282
- [attr.tabindex]="tabIndex()"
1283
- [attr.aria-invalid]="ariaInvalid()"
1284
- [attr.aria-required]="ariaRequired()"
1285
- [attr.aria-describedby]="ariaDescribedBy()"
1074
+ [helperText]="ngf.errorsToDisplay().length === 0 ? ((props()?.hint | dynamicText | async) ?? undefined) : undefined"
1075
+ [attr.tabindex]="ngf.tabIndex()"
1286
1076
  />
1287
- @if (errorsToDisplay()[0]; as error) {
1288
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
1077
+ @if (ngf.errorsToDisplay()[0]; as error) {
1078
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
1289
1079
  }
1290
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
1080
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
1291
1081
  }
1292
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicTextareaFieldComponent, decorators: [{
1082
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicTextareaFieldComponent, decorators: [{
1293
1083
  type: Component,
1294
- args: [{ selector: 'df-ion-textarea', imports: [IonTextarea, IonNote, FormField, DynamicTextPipe, AsyncPipe], template: `
1295
- @let f = field();
1296
- @let textareaId = key() + '-textarea';
1084
+ args: [{ selector: 'df-ion-textarea', imports: [IonTextarea, IonNote, FormField, DynamicTextPipe, AsyncPipe, NgForgeControl], hostDirectives: [NgForgeFieldHost], template: `
1085
+ @let f = ngf.field();
1086
+ @let textareaId = ngf.key() + '-textarea';
1297
1087
 
1298
1088
  <ion-textarea
1089
+ ngForgeControl
1299
1090
  [id]="textareaId"
1300
1091
  [formField]="f"
1301
- [label]="(label() | dynamicText | async) ?? undefined"
1092
+ [label]="(ngf.label() | dynamicText | async) ?? undefined"
1302
1093
  [labelPlacement]="props()?.labelPlacement ?? 'stacked'"
1303
- [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1094
+ [placeholder]="(ngf.placeholder() | dynamicText | async) ?? ''"
1304
1095
  [rows]="props()?.rows ?? 4"
1305
1096
  [autoGrow]="props()?.autoGrow ?? false"
1306
1097
  [counter]="props()?.counter ?? false"
@@ -1310,22 +1101,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1310
1101
  [fill]="props()?.fill ?? 'outline'"
1311
1102
  [shape]="props()?.shape"
1312
1103
  [readonly]="f().readonly()"
1313
- [helperText]="errorsToDisplay().length === 0 ? ((props()?.hint | dynamicText | async) ?? undefined) : undefined"
1314
- [attr.tabindex]="tabIndex()"
1315
- [attr.aria-invalid]="ariaInvalid()"
1316
- [attr.aria-required]="ariaRequired()"
1317
- [attr.aria-describedby]="ariaDescribedBy()"
1104
+ [helperText]="ngf.errorsToDisplay().length === 0 ? ((props()?.hint | dynamicText | async) ?? undefined) : undefined"
1105
+ [attr.tabindex]="ngf.tabIndex()"
1318
1106
  />
1319
- @if (errorsToDisplay()[0]; as error) {
1320
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
1107
+ @if (ngf.errorsToDisplay()[0]; as error) {
1108
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
1321
1109
  }
1322
- `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
1323
- '[id]': '`${key()}`',
1324
- '[attr.data-testid]': 'key()',
1325
- '[class]': 'className()',
1326
- '[attr.hidden]': 'field()().hidden() || null',
1327
- }, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host([hidden]){display:none!important}\n"] }]
1328
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
1110
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host([hidden]){display:none!important}\n"] }]
1111
+ }], ctorParameters: () => [], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1329
1112
 
1330
1113
  var ionicTextarea_component = /*#__PURE__*/Object.freeze({
1331
1114
  __proto__: null,
@@ -1333,14 +1116,11 @@ var ionicTextarea_component = /*#__PURE__*/Object.freeze({
1333
1116
  });
1334
1117
 
1335
1118
  /**
1336
- * A wrapper component for Ionic's ion-toggle that implements FormCheckboxControl.
1337
- * This allows it to work with Angular's [field] directive from @angular/forms/signals.
1338
- *
1339
- * We use FormCheckboxControl because ion-toggle is a boolean checkbox-like control.
1340
- * The Field directive binds to the `checked` model for checkbox controls.
1119
+ * ion-toggle wrapper implementing FormCheckboxControl. Rendered inside
1120
+ * `df-ion-toggle`. `NgForgeControl` on `<ion-toggle>` claims the ambient
1121
+ * parent NgForgeField and routes meta + aria onto that element.
1341
1122
  */
1342
1123
  class IonicToggleControlComponent {
1343
- elementRef = inject((ElementRef));
1344
1124
  // ─────────────────────────────────────────────────────────────────────────────
1345
1125
  // FormCheckboxControl implementation
1346
1126
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1364,29 +1144,9 @@ class IonicToggleControlComponent {
1364
1144
  color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
1365
1145
  enableOnOffLabels = input(false, ...(ngDevMode ? [{ debugName: "enableOnOffLabels" }] : /* istanbul ignore next */ []));
1366
1146
  tabIndex = input(undefined, ...(ngDevMode ? [{ debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1367
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
1368
- ariaDescribedBy = input(null, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
1369
- constructor() {
1370
- // Shadow DOM - apply meta to ion-toggle element
1371
- setupMetaTracking(this.elementRef, this.meta, {
1372
- selector: 'ion-toggle',
1373
- });
1374
- }
1375
- // ─────────────────────────────────────────────────────────────────────────────
1376
- // Computed ARIA attributes
1377
- // ─────────────────────────────────────────────────────────────────────────────
1378
- /** aria-invalid: true when field is invalid AND touched */
1379
- ariaInvalid = computed(() => {
1380
- return this.invalid() && this.touched();
1381
- }, ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
1382
- /** aria-required: true if field is required, null otherwise */
1383
- ariaRequired = computed(() => {
1384
- return this.required() ? true : null;
1385
- }, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : /* istanbul ignore next */ []));
1386
- /** aria-readonly: true if field is readonly, null otherwise */
1387
- ariaReadonly = computed(() => {
1388
- return this.readonly() ? true : null;
1389
- }, ...(ngDevMode ? [{ debugName: "ariaReadonly" }] : /* istanbul ignore next */ []));
1147
+ // aria-invalid / aria-required / aria-describedby are written to <ion-toggle>
1148
+ // by NgForgeControl. aria-readonly is Ionic-specific and stays template-side.
1149
+ ariaReadonly = computed(() => (this.readonly() ? true : null), ...(ngDevMode ? [{ debugName: "ariaReadonly" }] : /* istanbul ignore next */ []));
1390
1150
  /** Computed checked state - ensures proper boolean conversion for ion-toggle */
1391
1151
  isChecked = computed(() => {
1392
1152
  return this.checked() === true;
@@ -1409,9 +1169,10 @@ class IonicToggleControlComponent {
1409
1169
  onBlur() {
1410
1170
  this.touched.set(true);
1411
1171
  }
1412
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicToggleControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1413
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: IonicToggleControlComponent, isStandalone: true, selector: "df-ion-toggle-control", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", 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 }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, labelPlacement: { classPropertyName: "labelPlacement", publicName: "labelPlacement", isSignal: true, isRequired: false, transformFunction: null }, justify: { classPropertyName: "justify", publicName: "justify", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, enableOnOffLabels: { classPropertyName: "enableOnOffLabels", publicName: "enableOnOffLabels", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touched: "touchedChange" }, host: { styleAttribute: "display: block" }, ngImport: i0, template: `
1172
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicToggleControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1173
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.14", type: IonicToggleControlComponent, isStandalone: true, selector: "df-ion-toggle-control", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", 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 }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, labelPlacement: { classPropertyName: "labelPlacement", publicName: "labelPlacement", isSignal: true, isRequired: false, transformFunction: null }, justify: { classPropertyName: "justify", publicName: "justify", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, enableOnOffLabels: { classPropertyName: "enableOnOffLabels", publicName: "enableOnOffLabels", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touched: "touchedChange" }, host: { styleAttribute: "display: block" }, ngImport: i0, template: `
1414
1174
  <ion-toggle
1175
+ ngForgeControl
1415
1176
  [checked]="isChecked()"
1416
1177
  (ionChange)="onToggleChange($event)"
1417
1178
  (ionBlur)="onBlur()"
@@ -1421,19 +1182,17 @@ class IonicToggleControlComponent {
1421
1182
  [enableOnOffLabels]="enableOnOffLabels()"
1422
1183
  [disabled]="isEffectivelyDisabled()"
1423
1184
  [attr.tabindex]="tabIndex()"
1424
- [attr.aria-invalid]="ariaInvalid()"
1425
- [attr.aria-required]="ariaRequired()"
1426
1185
  [attr.aria-readonly]="ariaReadonly()"
1427
- [attr.aria-describedby]="ariaDescribedBy()"
1428
1186
  >
1429
1187
  <ng-content />
1430
1188
  </ion-toggle>
1431
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n"], dependencies: [{ kind: "component", type: IonToggle, selector: "ion-toggle", inputs: ["checked", "color", "disabled", "enableOnOffLabels", "errorText", "helperText", "justify", "labelPlacement", "mode", "name", "value"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1189
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonToggle, selector: "ion-toggle", inputs: ["checked", "color", "disabled", "enableOnOffLabels", "errorText", "helperText", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "directive", type: NgForgeControl, selector: "[ngForgeControl]", inputs: ["ngForgeControl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1432
1190
  }
1433
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicToggleControlComponent, decorators: [{
1191
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicToggleControlComponent, decorators: [{
1434
1192
  type: Component,
1435
- args: [{ selector: 'df-ion-toggle-control', imports: [IonToggle], template: `
1193
+ args: [{ selector: 'df-ion-toggle-control', imports: [IonToggle, NgForgeControl], template: `
1436
1194
  <ion-toggle
1195
+ ngForgeControl
1437
1196
  [checked]="isChecked()"
1438
1197
  (ionChange)="onToggleChange($event)"
1439
1198
  (ionBlur)="onBlur()"
@@ -1443,104 +1202,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1443
1202
  [enableOnOffLabels]="enableOnOffLabels()"
1444
1203
  [disabled]="isEffectivelyDisabled()"
1445
1204
  [attr.tabindex]="tabIndex()"
1446
- [attr.aria-invalid]="ariaInvalid()"
1447
- [attr.aria-required]="ariaRequired()"
1448
1205
  [attr.aria-readonly]="ariaReadonly()"
1449
- [attr.aria-describedby]="ariaDescribedBy()"
1450
1206
  >
1451
1207
  <ng-content />
1452
1208
  </ion-toggle>
1453
1209
  `, host: {
1454
1210
  style: 'display: block',
1455
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n"] }]
1456
- }], ctorParameters: () => [], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], labelPlacement: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPlacement", required: false }] }], justify: [{ type: i0.Input, args: [{ isSignal: true, alias: "justify", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], enableOnOffLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableOnOffLabels", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }] } });
1211
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n"] }]
1212
+ }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], labelPlacement: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPlacement", required: false }] }], justify: [{ type: i0.Input, args: [{ isSignal: true, alias: "justify", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], enableOnOffLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableOnOffLabels", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }] } });
1457
1213
 
1458
1214
  class IonicToggleFieldComponent {
1459
- elementRef = inject((ElementRef));
1460
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
1461
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
1462
- // Properties
1463
- label = input(...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
1464
- placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
1465
- className = input('', ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
1466
- tabIndex = input(...(ngDevMode ? [undefined, { debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1215
+ ngf = injectNgForgeField();
1467
1216
  props = input(...(ngDevMode ? [undefined, { debugName: "props" }] : /* istanbul ignore next */ []));
1468
- meta = input(...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
1469
- validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
1470
- defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
1471
- constructor() {
1472
- setupMetaTracking(this.elementRef, this.meta);
1473
- }
1474
- resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1475
- showErrors = shouldShowErrors(this.field);
1476
- errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), ...(ngDevMode ? [{ debugName: "errorsToDisplay" }] : /* istanbul ignore next */ []));
1477
- // ─────────────────────────────────────────────────────────────────────────────
1478
- // Accessibility
1479
- // ─────────────────────────────────────────────────────────────────────────────
1480
- /** Base ID for error elements */
1481
- errorId = computed(() => `${this.key()}-error`, ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
1482
- /** Unique ID for the helper text element */
1483
- hintId = computed(() => `${this.key()}-hint`, ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
1484
- /** aria-describedby linking to hint OR error elements (mutually exclusive) */
1485
- ariaDescribedBy = createAriaDescribedBySignal(this.errorsToDisplay, this.errorId, this.hintId, () => !!this.props()?.hint);
1486
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicToggleFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1487
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: IonicToggleFieldComponent, isStandalone: true, selector: "df-ion-toggle", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1488
- @let f = field();
1489
- @let toggleId = key() + '-toggle';
1217
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicToggleFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1218
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonicToggleFieldComponent, isStandalone: true, selector: "df-ion-toggle", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.NgForgeFieldHost }], ngImport: i0, template: `
1219
+ @let f = ngf.field();
1220
+ @let toggleId = ngf.key() + '-toggle';
1490
1221
 
1491
1222
  <df-ion-toggle-control
1492
1223
  [id]="toggleId"
1493
1224
  [formField]="f"
1494
- [meta]="meta()"
1495
1225
  [labelPlacement]="props()?.labelPlacement ?? 'end'"
1496
1226
  [justify]="props()?.justify"
1497
1227
  [color]="props()?.color ?? 'primary'"
1498
1228
  [enableOnOffLabels]="props()?.enableOnOffLabels ?? false"
1499
- [tabIndex]="tabIndex()"
1500
- [ariaDescribedBy]="ariaDescribedBy()"
1229
+ [tabIndex]="ngf.tabIndex()"
1501
1230
  >
1502
- {{ label() | dynamicText | async }}
1231
+ {{ ngf.label() | dynamicText | async }}
1503
1232
  </df-ion-toggle-control>
1504
1233
 
1505
- @if (errorsToDisplay()[0]; as error) {
1506
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
1234
+ @if (ngf.errorsToDisplay()[0]; as error) {
1235
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
1507
1236
  } @else if (props()?.hint; as hint) {
1508
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
1237
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
1509
1238
  }
1510
- `, isInline: true, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonicToggleControlComponent, selector: "df-ion-toggle-control", inputs: ["checked", "touched", "disabled", "readonly", "invalid", "required", "labelPlacement", "justify", "color", "enableOnOffLabels", "tabIndex", "meta", "ariaDescribedBy"], outputs: ["checkedChange", "touchedChange"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
1239
+ `, isInline: true, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: IonicToggleControlComponent, selector: "df-ion-toggle-control", inputs: ["checked", "touched", "disabled", "readonly", "invalid", "required", "labelPlacement", "justify", "color", "enableOnOffLabels", "tabIndex"], outputs: ["checkedChange", "touchedChange"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { 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 });
1511
1240
  }
1512
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: IonicToggleFieldComponent, decorators: [{
1241
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonicToggleFieldComponent, decorators: [{
1513
1242
  type: Component,
1514
- args: [{ selector: 'df-ion-toggle', imports: [IonicToggleControlComponent, IonNote, FormField, DynamicTextPipe, AsyncPipe], template: `
1515
- @let f = field();
1516
- @let toggleId = key() + '-toggle';
1243
+ args: [{ selector: 'df-ion-toggle', imports: [IonicToggleControlComponent, IonNote, FormField, DynamicTextPipe, AsyncPipe], hostDirectives: [NgForgeFieldHost], template: `
1244
+ @let f = ngf.field();
1245
+ @let toggleId = ngf.key() + '-toggle';
1517
1246
 
1518
1247
  <df-ion-toggle-control
1519
1248
  [id]="toggleId"
1520
1249
  [formField]="f"
1521
- [meta]="meta()"
1522
1250
  [labelPlacement]="props()?.labelPlacement ?? 'end'"
1523
1251
  [justify]="props()?.justify"
1524
1252
  [color]="props()?.color ?? 'primary'"
1525
1253
  [enableOnOffLabels]="props()?.enableOnOffLabels ?? false"
1526
- [tabIndex]="tabIndex()"
1527
- [ariaDescribedBy]="ariaDescribedBy()"
1254
+ [tabIndex]="ngf.tabIndex()"
1528
1255
  >
1529
- {{ label() | dynamicText | async }}
1256
+ {{ ngf.label() | dynamicText | async }}
1530
1257
  </df-ion-toggle-control>
1531
1258
 
1532
- @if (errorsToDisplay()[0]; as error) {
1533
- <ion-note color="danger" class="df-ion-error" [id]="errorId()" role="alert">{{ error.message }}</ion-note>
1259
+ @if (ngf.errorsToDisplay()[0]; as error) {
1260
+ <ion-note color="danger" class="df-ion-error" [id]="ngf.errorId()" role="alert">{{ error.message }}</ion-note>
1534
1261
  } @else if (props()?.hint; as hint) {
1535
- <ion-note class="df-ion-hint" [id]="hintId()">{{ hint | dynamicText | async }}</ion-note>
1262
+ <ion-note class="df-ion-hint" [id]="ngf.hintId()">{{ hint | dynamicText | async }}</ion-note>
1536
1263
  }
1537
- `, host: {
1538
- '[class]': 'className()',
1539
- '[id]': '`${key()}`',
1540
- '[attr.data-testid]': 'key()',
1541
- '[attr.hidden]': 'field()().hidden() || null',
1542
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{--df-field-gap: .5rem;--df-label-font-weight: 500;--df-label-color: inherit;--df-hint-color: var(--ion-color-medium, #92949c);--df-hint-font-size: .875rem;--df-error-color: var(--ion-color-danger, #eb445a);--df-error-font-size: .875rem}.df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight);color:var(--df-label-color)}.df-ion-hint{display:block;color:var(--df-hint-color);font-size:var(--df-hint-font-size)}.df-ion-error{display:block;color:var(--df-error-color);font-size:var(--df-error-font-size)}:host([hidden]){display:none!important}ion-input.ng-invalid.ng-touched,ion-textarea.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-select.ng-invalid.ng-touched{--highlight-color: var(--ion-color-danger);--border-color: var(--ion-color-danger)}ion-checkbox.ng-invalid.ng-touched{--checkbox-background-checked: var(--ion-color-danger);--border-color: var(--ion-color-danger);--border-color-checked: var(--ion-color-danger)}ion-radio.ng-invalid.ng-touched{--color: var(--ion-color-danger);--color-checked: var(--ion-color-danger)}ion-range.ng-invalid.ng-touched{--bar-background: var(--ion-color-danger);--knob-background: var(--ion-color-danger)}ion-toggle.ng-invalid.ng-touched{--track-background: var(--ion-color-danger-tint);--track-background-checked: var(--ion-color-danger);--handle-background-checked: var(--ion-color-danger-contrast)}\n", ":host{display:block}:host([hidden]){display:none!important}\n"] }]
1543
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
1264
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-field{display:flex;flex-direction:column;gap:var(--df-field-gap, .5rem);width:100%}.df-ion-label{font-weight:var(--df-label-font-weight, 500);color:var(--df-label-color, inherit)}.df-ion-hint{display:block;color:var(--df-hint-color, var(--ion-color-medium, #92949c));font-size:var(--df-hint-font-size, .875rem)}.df-ion-error{display:block;color:var(--df-error-color, var(--ion-color-danger, #eb445a));font-size:var(--df-error-font-size, .875rem)}:host([hidden]){display:none!important}\n", ":host{display:block}:host([hidden]){display:none!important}\n"] }]
1265
+ }], propDecorators: { props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }] } });
1544
1266
 
1545
1267
  var ionicToggle_component = /*#__PURE__*/Object.freeze({
1546
1268
  __proto__: null,
@@ -1637,8 +1359,12 @@ function buttonFieldMapper(fieldDef) {
1637
1359
  * disabled state resolution based on form validity and options.
1638
1360
  */
1639
1361
 
1362
+ const VALUE_FIELD_TYPES_BASE = {
1363
+ renderReadyWhen: ['field'],
1364
+ };
1640
1365
  const BUTTON_FIELD_TYPES_BASE = {
1641
1366
  renderReadyWhen: [],
1367
+ valueHandling: 'exclude',
1642
1368
  };
1643
1369
  /**
1644
1370
  * Ionic field type definitions
@@ -1651,87 +1377,83 @@ const IONIC_FIELD_TYPES = [
1651
1377
  mapper: valueFieldMapper,
1652
1378
  propsToMeta: ['type'],
1653
1379
  scope: ['text-input', 'numeric'],
1380
+ addons: {
1381
+ slots: ['prefix', 'suffix'],
1382
+ },
1383
+ ...VALUE_FIELD_TYPES_BASE,
1654
1384
  },
1655
1385
  {
1656
1386
  name: IonicField.Select,
1657
1387
  loadComponent: () => Promise.resolve().then(function () { return ionicSelect_component; }),
1658
1388
  mapper: optionsFieldMapper,
1659
1389
  scope: 'single-select',
1390
+ ...VALUE_FIELD_TYPES_BASE,
1660
1391
  },
1661
1392
  {
1662
1393
  name: IonicField.Checkbox,
1663
1394
  loadComponent: () => Promise.resolve().then(function () { return ionicCheckbox_component; }),
1664
1395
  mapper: checkboxFieldMapper,
1665
1396
  scope: 'boolean',
1397
+ ...VALUE_FIELD_TYPES_BASE,
1666
1398
  },
1667
1399
  {
1668
1400
  name: IonicField.Button,
1669
1401
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1670
1402
  mapper: buttonFieldMapper,
1671
- valueHandling: 'exclude',
1672
1403
  ...BUTTON_FIELD_TYPES_BASE,
1673
1404
  },
1674
1405
  {
1675
1406
  name: IonicField.Submit,
1676
1407
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1677
1408
  mapper: submitButtonFieldMapper,
1678
- valueHandling: 'exclude',
1679
1409
  ...BUTTON_FIELD_TYPES_BASE,
1680
1410
  },
1681
1411
  {
1682
1412
  name: IonicField.Next,
1683
1413
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1684
1414
  mapper: nextButtonFieldMapper,
1685
- valueHandling: 'exclude',
1686
1415
  ...BUTTON_FIELD_TYPES_BASE,
1687
1416
  },
1688
1417
  {
1689
1418
  name: IonicField.Previous,
1690
1419
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1691
1420
  mapper: previousButtonFieldMapper,
1692
- valueHandling: 'exclude',
1693
1421
  ...BUTTON_FIELD_TYPES_BASE,
1694
1422
  },
1695
1423
  {
1696
1424
  name: IonicField.AddArrayItem,
1697
1425
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1698
1426
  mapper: addArrayItemButtonMapper,
1699
- valueHandling: 'exclude',
1700
1427
  ...BUTTON_FIELD_TYPES_BASE,
1701
1428
  },
1702
1429
  {
1703
1430
  name: IonicField.PrependArrayItem,
1704
1431
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1705
1432
  mapper: prependArrayItemButtonMapper,
1706
- valueHandling: 'exclude',
1707
1433
  ...BUTTON_FIELD_TYPES_BASE,
1708
1434
  },
1709
1435
  {
1710
1436
  name: IonicField.InsertArrayItem,
1711
1437
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1712
1438
  mapper: insertArrayItemButtonMapper,
1713
- valueHandling: 'exclude',
1714
1439
  ...BUTTON_FIELD_TYPES_BASE,
1715
1440
  },
1716
1441
  {
1717
1442
  name: IonicField.RemoveArrayItem,
1718
1443
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1719
1444
  mapper: removeArrayItemButtonMapper,
1720
- valueHandling: 'exclude',
1721
1445
  ...BUTTON_FIELD_TYPES_BASE,
1722
1446
  },
1723
1447
  {
1724
1448
  name: IonicField.PopArrayItem,
1725
1449
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1726
1450
  mapper: popArrayItemButtonMapper,
1727
- valueHandling: 'exclude',
1728
1451
  ...BUTTON_FIELD_TYPES_BASE,
1729
1452
  },
1730
1453
  {
1731
1454
  name: IonicField.ShiftArrayItem,
1732
1455
  loadComponent: () => Promise.resolve().then(function () { return ionicButton_component; }),
1733
1456
  mapper: shiftArrayItemButtonMapper,
1734
- valueHandling: 'exclude',
1735
1457
  ...BUTTON_FIELD_TYPES_BASE,
1736
1458
  },
1737
1459
  {
@@ -1740,36 +1462,42 @@ const IONIC_FIELD_TYPES = [
1740
1462
  mapper: valueFieldMapper,
1741
1463
  propsToMeta: ['rows'],
1742
1464
  scope: 'text-input',
1465
+ ...VALUE_FIELD_TYPES_BASE,
1743
1466
  },
1744
1467
  {
1745
1468
  name: IonicField.Radio,
1746
1469
  loadComponent: () => Promise.resolve().then(function () { return ionicRadio_component; }),
1747
1470
  mapper: optionsFieldMapper,
1748
1471
  scope: 'single-select',
1472
+ ...VALUE_FIELD_TYPES_BASE,
1749
1473
  },
1750
1474
  {
1751
1475
  name: IonicField.MultiCheckbox,
1752
1476
  loadComponent: () => Promise.resolve().then(function () { return ionicMultiCheckbox_component; }),
1753
1477
  mapper: optionsFieldMapper,
1754
1478
  scope: 'multi-select',
1479
+ ...VALUE_FIELD_TYPES_BASE,
1755
1480
  },
1756
1481
  {
1757
1482
  name: IonicField.Datepicker,
1758
1483
  loadComponent: () => Promise.resolve().then(function () { return ionicDatepicker_component; }),
1759
1484
  mapper: datepickerFieldMapper,
1760
1485
  scope: 'date',
1486
+ ...VALUE_FIELD_TYPES_BASE,
1761
1487
  },
1762
1488
  {
1763
1489
  name: IonicField.Slider,
1764
1490
  loadComponent: () => Promise.resolve().then(function () { return ionicSlider_component; }),
1765
1491
  mapper: valueFieldMapper,
1766
1492
  scope: 'numeric',
1493
+ ...VALUE_FIELD_TYPES_BASE,
1767
1494
  },
1768
1495
  {
1769
1496
  name: IonicField.Toggle,
1770
1497
  loadComponent: () => Promise.resolve().then(function () { return ionicToggle_component; }),
1771
1498
  mapper: checkboxFieldMapper,
1772
1499
  scope: 'boolean',
1500
+ ...VALUE_FIELD_TYPES_BASE,
1773
1501
  },
1774
1502
  ];
1775
1503
 
@@ -1780,19 +1508,191 @@ const IONIC_FIELD_TYPES = [
1780
1508
  */
1781
1509
 
1782
1510
  function withIonicFields(config) {
1783
- if (!config) {
1784
- return IONIC_FIELD_TYPES;
1785
- }
1786
- const fieldsWithConfig = [
1787
- ...IONIC_FIELD_TYPES,
1788
- {
1511
+ // Always include the addons feature — ion-icon / ion-button are part of
1512
+ // the canonical Ionic surface.
1513
+ const base = [...IONIC_FIELD_TYPES, withIonicAddons()];
1514
+ if (config) {
1515
+ base.push({
1789
1516
  ɵkind: 'ionic-config',
1790
1517
  ɵproviders: [{ provide: IONIC_CONFIG, useValue: config }],
1791
- },
1792
- ];
1793
- // Safe: this preserves all Ionic field definitions and appends exactly one config feature.
1794
- return fieldsWithConfig;
1518
+ });
1519
+ return base;
1520
+ }
1521
+ return base;
1522
+ }
1523
+ /* -- Ionic addon kinds ------------------------------------------------- */
1524
+ const ION_ICON_KIND = {
1525
+ kind: 'ion-icon',
1526
+ loadComponent: () => Promise.resolve().then(function () { return ionIconAddon_component; }).then((m) => m.IonIconAddonComponent),
1527
+ validate: (addon, fieldKey) => {
1528
+ if (typeof addon.icon !== 'string' || addon.icon.length === 0) {
1529
+ throw new DynamicFormError(`Addon kind 'ion-icon' requires a non-empty 'icon' string (field: '${fieldKey}').`);
1530
+ }
1531
+ },
1532
+ };
1533
+ const ION_BUTTON_KIND = {
1534
+ kind: 'ion-button',
1535
+ loadComponent: () => Promise.resolve().then(function () { return ionButtonAddon_component; }).then((m) => m.IonButtonAddonComponent),
1536
+ validate: (addon, fieldKey) => {
1537
+ // Exactly one of preset / actionRef / action — validator drops the addon
1538
+ // (with warning) if the rule is violated.
1539
+ const set = [addon.preset, addon.actionRef, addon.action].filter((v) => v !== undefined);
1540
+ if (set.length > 1) {
1541
+ throw new DynamicFormError(`Addon kind 'ion-button' on field '${fieldKey}' has more than one of preset/actionRef/action — exactly one allowed.`);
1542
+ }
1543
+ // Icon-only buttons require ariaLabel for screen readers.
1544
+ if (addon.icon && !addon.label && !addon.ariaLabel) {
1545
+ throw new DynamicFormError(`Addon kind 'ion-button' on field '${fieldKey}' is icon-only — provide 'ariaLabel' for accessibility.`);
1546
+ }
1547
+ },
1548
+ };
1549
+ /**
1550
+ * Register Ionic-shipped addon kinds (`ion-icon`, `ion-button`) standalone.
1551
+ *
1552
+ * **Most users don't need this** — `withIonicFields()` auto-includes
1553
+ * these kinds. Call `withIonicAddons()` directly only when you want
1554
+ * Ionic addon kinds without the Ionic field types (e.g., a custom
1555
+ * field set that wants to render `ion-icon` prefixes), or when you're
1556
+ * stitching addons through a different DI scope.
1557
+ *
1558
+ * @example
1559
+ * ```typescript
1560
+ * // Custom field types + Ionic addon kinds.
1561
+ * provideDynamicForm(
1562
+ * ...myCustomFields(),
1563
+ * withIonicAddons(),
1564
+ * );
1565
+ * ```
1566
+ *
1567
+ * Adapter authors who need to override a kind with a customised renderer
1568
+ * should call `withCustomAddon(...)` directly instead.
1569
+ */
1570
+ function withIonicAddons() {
1571
+ return {
1572
+ ɵkind: 'addons',
1573
+ ɵproviders: [
1574
+ { provide: ADDON_KIND_DEFINITIONS, useValue: ION_ICON_KIND, multi: true },
1575
+ { provide: ADDON_KIND_DEFINITIONS, useValue: ION_BUTTON_KIND, multi: true },
1576
+ ],
1577
+ };
1578
+ }
1579
+
1580
+ /**
1581
+ * Renderer for the `ion-button` addon kind.
1582
+ *
1583
+ * Wraps Ionic's `<ion-button>`. Click dispatch (preset / actionRef /
1584
+ * action precedence, multi-set warning, `disabled` / `loading`
1585
+ * resolution) lives on `NgForgeAddonAction`; this component focuses on
1586
+ * the visual layer. While `action.loading()`, an `<ion-spinner>` is
1587
+ * rendered in place of the icon and the button is disabled.
1588
+ */
1589
+ class IonButtonAddonComponent {
1590
+ action = injectNgForgeAddonAction();
1591
+ /** Re-exposed for template binding — same signal stored on the directive. */
1592
+ addon = this.action.addon;
1593
+ label = computed(() => this.addon().label, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1594
+ ariaLabel = computed(() => this.addon().ariaLabel, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1595
+ icon = computed(() => this.addon().icon, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
1596
+ /** Default color is undefined so the button inherits the theme's text color
1597
+ * (Ionic's `medium` would force low-contrast grey, which fails WCAG on dark
1598
+ * surfaces). Consumers can still set `color: 'primary' | 'success' | ...`
1599
+ * explicitly when they want adapter-styled coloring. */
1600
+ color = computed(() => this.addon().color, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
1601
+ fill = computed(() => this.addon().fill ?? 'clear', ...(ngDevMode ? [{ debugName: "fill" }] : /* istanbul ignore next */ []));
1602
+ /** Icon-only when there is an icon but no label — drives the `slot` choice.
1603
+ * Truthy check on `label` mirrors Material's heuristic; empty-string label
1604
+ * is treated as "no label" so the button doesn't render an invisible text
1605
+ * slot beside the icon. */
1606
+ iconOnly = computed(() => !!this.icon() && !this.label(), ...(ngDevMode ? [{ debugName: "iconOnly" }] : /* istanbul ignore next */ []));
1607
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonButtonAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1608
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: IonButtonAddonComponent, isStandalone: true, selector: "df-ion-button-addon", hostDirectives: [{ directive: i1.NgForgeAddonAction }], ngImport: i0, template: `
1609
+ <ion-button
1610
+ [attr.color]="color() ?? null"
1611
+ [fill]="fill()"
1612
+ [disabled]="action.disabled() || action.loading()"
1613
+ [attr.aria-label]="(ariaLabel() | dynamicText | async) ?? null"
1614
+ [attr.aria-busy]="action.loading() || null"
1615
+ (click)="action.dispatch()"
1616
+ >
1617
+ @if (action.loading()) {
1618
+ <ion-spinner [slot]="iconOnly() ? 'icon-only' : 'start'" name="dots"></ion-spinner>
1619
+ } @else if (icon(); as ic) {
1620
+ <ion-icon [name]="ic" [slot]="iconOnly() ? 'icon-only' : 'start'"></ion-icon>
1621
+ }
1622
+ @if (label(); as lbl) {
1623
+ {{ lbl | dynamicText | async }}
1624
+ }
1625
+ </ion-button>
1626
+ @if (action.loading()) {
1627
+ <span class="df-ion-sr-only" role="status">Loading…</span>
1628
+ }
1629
+ `, isInline: true, styles: [".df-ion-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1630
+ }
1631
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonButtonAddonComponent, decorators: [{
1632
+ type: Component,
1633
+ args: [{ selector: 'df-ion-button-addon', imports: [IonButton, IonIcon, IonSpinner, DynamicTextPipe, AsyncPipe], hostDirectives: [NgForgeAddonAction], template: `
1634
+ <ion-button
1635
+ [attr.color]="color() ?? null"
1636
+ [fill]="fill()"
1637
+ [disabled]="action.disabled() || action.loading()"
1638
+ [attr.aria-label]="(ariaLabel() | dynamicText | async) ?? null"
1639
+ [attr.aria-busy]="action.loading() || null"
1640
+ (click)="action.dispatch()"
1641
+ >
1642
+ @if (action.loading()) {
1643
+ <ion-spinner [slot]="iconOnly() ? 'icon-only' : 'start'" name="dots"></ion-spinner>
1644
+ } @else if (icon(); as ic) {
1645
+ <ion-icon [name]="ic" [slot]="iconOnly() ? 'icon-only' : 'start'"></ion-icon>
1646
+ }
1647
+ @if (label(); as lbl) {
1648
+ {{ lbl | dynamicText | async }}
1649
+ }
1650
+ </ion-button>
1651
+ @if (action.loading()) {
1652
+ <span class="df-ion-sr-only" role="status">Loading…</span>
1653
+ }
1654
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-ion-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
1655
+ }] });
1656
+
1657
+ var ionButtonAddon_component = /*#__PURE__*/Object.freeze({
1658
+ __proto__: null,
1659
+ IonButtonAddonComponent: IonButtonAddonComponent
1660
+ });
1661
+
1662
+ /**
1663
+ * Renderer for the `ion-icon` addon kind.
1664
+ *
1665
+ * Outputs `<ion-icon [name]="icon">`. The host is set `aria-hidden="true"`
1666
+ * by default; if the addon supplies an `ariaLabel`, it is applied so the
1667
+ * icon is announced by screen readers.
1668
+ */
1669
+ class IonIconAddonComponent {
1670
+ addon = input.required(...(ngDevMode ? [{ debugName: "addon" }] : /* istanbul ignore next */ []));
1671
+ /** Accepted for contract uniformity — `NgComponentOutlet` setInput is strict; every kind must declare it. */
1672
+ fieldInputs = input(...(ngDevMode ? [undefined, { debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
1673
+ iconName = computed(() => this.addon().icon, ...(ngDevMode ? [{ debugName: "iconName" }] : /* istanbul ignore next */ []));
1674
+ ariaLabel = computed(() => this.addon().ariaLabel, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1675
+ hasAriaLabel = computed(() => this.addon().ariaLabel !== undefined, ...(ngDevMode ? [{ debugName: "hasAriaLabel" }] : /* istanbul ignore next */ []));
1676
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonIconAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1677
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.14", type: IonIconAddonComponent, isStandalone: true, selector: "df-ion-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: `<ion-icon [name]="iconName()" [attr.aria-label]="(ariaLabel() | dynamicText | async) ?? null"></ion-icon>`, isInline: true, dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1795
1678
  }
1679
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: IonIconAddonComponent, decorators: [{
1680
+ type: Component,
1681
+ args: [{
1682
+ selector: 'df-ion-icon-addon',
1683
+ imports: [IonIcon, AsyncPipe, DynamicTextPipe],
1684
+ template: `<ion-icon [name]="iconName()" [attr.aria-label]="(ariaLabel() | dynamicText | async) ?? null"></ion-icon>`,
1685
+ host: {
1686
+ '[attr.aria-hidden]': 'hasAriaLabel() ? null : "true"',
1687
+ },
1688
+ changeDetection: ChangeDetectionStrategy.OnPush,
1689
+ }]
1690
+ }], propDecorators: { addon: [{ type: i0.Input, args: [{ isSignal: true, alias: "addon", required: true }] }], fieldInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldInputs", required: false }] }] } });
1691
+
1692
+ var ionIconAddon_component = /*#__PURE__*/Object.freeze({
1693
+ __proto__: null,
1694
+ IonIconAddonComponent: IonIconAddonComponent
1695
+ });
1796
1696
 
1797
1697
  // Field fields
1798
1698
 
@@ -1800,5 +1700,5 @@ function withIonicFields(config) {
1800
1700
  * Generated bundle index. Do not edit.
1801
1701
  */
1802
1702
 
1803
- export { IONIC_CONFIG, IONIC_FIELD_TYPES, IonicButtonFieldComponent, IonicCheckboxFieldComponent, IonicDatepickerFieldComponent, IonicField, IonicInputFieldComponent, IonicMultiCheckboxFieldComponent, IonicRadioFieldComponent, IonicSelectFieldComponent, IonicSliderFieldComponent, IonicTextareaFieldComponent, IonicToggleFieldComponent, withIonicFields };
1703
+ export { IONIC_CONFIG, IONIC_FIELD_TYPES, IONIC_INPUT_TYPE_OVERRIDE, IonButtonAddonComponent, IonIconAddonComponent, IonicButtonFieldComponent, IonicCheckboxFieldComponent, IonicDatepickerFieldComponent, IonicField, IonicInputFieldComponent, IonicMultiCheckboxFieldComponent, IonicRadioFieldComponent, IonicSelectFieldComponent, IonicSliderFieldComponent, IonicTextareaFieldComponent, IonicToggleFieldComponent, withIonicAddons, withIonicFields };
1804
1704
  //# sourceMappingURL=ng-forge-dynamic-forms-ionic.mjs.map