@ng-forge/dynamic-forms-material 0.1.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.
@@ -0,0 +1,1978 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, input, ElementRef, computed, ChangeDetectionStrategy, Component, viewChild, Pipe, linkedSignal, isSignal } from '@angular/core';
3
+ import { Field } from '@angular/forms/signals';
4
+ import { MatCheckbox } from '@angular/material/checkbox';
5
+ import { createResolvedErrorsSignal, shouldShowErrors, DynamicTextPipe, EventBus, ARRAY_CONTEXT, resolveTokens, buildBaseInputs, FIELD_SIGNAL_CONTEXT, resolveSubmitButtonDisabled, resolveNextButtonDisabled, NextPageEvent, PreviousPageEvent, AddArrayItemEvent, RemoveArrayItemEvent, valueFieldMapper, optionsFieldMapper, checkboxFieldMapper, datepickerFieldMapper } from '@ng-forge/dynamic-forms';
6
+ import { MatError, MatInput, MatHint } from '@angular/material/input';
7
+ import { AsyncPipe } from '@angular/common';
8
+ import { explicitEffect } from 'ngxtension/explicit-effect';
9
+ import { MatFormField, MatLabel, MatSuffix, MatError as MatError$1 } from '@angular/material/form-field';
10
+ import { MatDatepicker, MatDatepickerInput, MatDatepickerToggle } from '@angular/material/datepicker';
11
+ import { provideNativeDateAdapter } from '@angular/material/core';
12
+ import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
13
+ import { MatSelect, MatOption } from '@angular/material/select';
14
+ import { MatSlider, MatSliderThumb } from '@angular/material/slider';
15
+ import { MatButton } from '@angular/material/button';
16
+ import { MatSlideToggle } from '@angular/material/slide-toggle';
17
+
18
+ /**
19
+ * Injection token for Material Design form configuration.
20
+ * Use this to provide global configuration for Material form fields.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * import { provideDynamicForm } from '@ng-forge/dynamic-forms';
25
+ * import { withMaterialFields } from '@ng-forge/dynamic-forms-material';
26
+ *
27
+ * export const appConfig: ApplicationConfig = {
28
+ * providers: [
29
+ * provideDynamicForm(
30
+ * ...withMaterialFields({
31
+ * appearance: 'fill',
32
+ * subscriptSizing: 'fixed'
33
+ * })
34
+ * )
35
+ * ]
36
+ * };
37
+ * ```
38
+ */
39
+ const MATERIAL_CONFIG = new InjectionToken('MATERIAL_CONFIG');
40
+
41
+ class MatCheckboxFieldComponent {
42
+ materialConfig = inject(MATERIAL_CONFIG, { optional: true });
43
+ field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}) });
44
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
45
+ /**
46
+ * Host element reference for DOM queries.
47
+ * Used to find the internal checkbox input for accessibility workarounds.
48
+ */
49
+ hostEl = inject((ElementRef));
50
+ // Properties
51
+ label = input(undefined, { ...(ngDevMode ? { debugName: "label" } : {}) });
52
+ placeholder = input(undefined, { ...(ngDevMode ? { debugName: "placeholder" } : {}) });
53
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
54
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
55
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
56
+ validationMessages = input(undefined, { ...(ngDevMode ? { debugName: "validationMessages" } : {}) });
57
+ defaultValidationMessages = input(undefined, { ...(ngDevMode ? { debugName: "defaultValidationMessages" } : {}) });
58
+ effectiveDisableRipple = computed(() => this.props()?.disableRipple ?? this.materialConfig?.disableRipple ?? false, { ...(ngDevMode ? { debugName: "effectiveDisableRipple" } : {}) });
59
+ resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
60
+ showErrors = shouldShowErrors(this.field);
61
+ errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), { ...(ngDevMode ? { debugName: "errorsToDisplay" } : {}) });
62
+ // ─────────────────────────────────────────────────────────────────────────────
63
+ // Accessibility
64
+ // ─────────────────────────────────────────────────────────────────────────────
65
+ /** Unique ID for the hint element, used for aria-describedby */
66
+ hintId = computed(() => `${this.key()}-hint`, { ...(ngDevMode ? { debugName: "hintId" } : {}) });
67
+ /** Base ID for error elements, used for aria-describedby */
68
+ errorId = computed(() => `${this.key()}-error`, { ...(ngDevMode ? { debugName: "errorId" } : {}) });
69
+ /** aria-invalid: true when field is invalid AND touched, false otherwise */
70
+ ariaInvalid = computed(() => {
71
+ const fieldState = this.field()();
72
+ return fieldState.invalid() && fieldState.touched();
73
+ }, { ...(ngDevMode ? { debugName: "ariaInvalid" } : {}) });
74
+ /** aria-required: true if field is required, null otherwise (to remove attribute) */
75
+ ariaRequired = computed(() => {
76
+ return this.field()().required?.() === true ? true : null;
77
+ }, { ...(ngDevMode ? { debugName: "ariaRequired" } : {}) });
78
+ /** aria-describedby: links to hint and error messages for screen readers */
79
+ ariaDescribedBy = computed(() => {
80
+ const ids = [];
81
+ if (this.props()?.hint) {
82
+ ids.push(this.hintId());
83
+ }
84
+ const errors = this.errorsToDisplay();
85
+ errors.forEach((_, i) => {
86
+ ids.push(`${this.errorId()}-${i}`);
87
+ });
88
+ return ids.join(' ');
89
+ }, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : {}) });
90
+ /**
91
+ * Workaround: Angular Material's MatCheckbox does NOT set aria-required on its internal
92
+ * input element when [required] is passed, even though it sets the native required attribute.
93
+ * This effect imperatively sets/removes aria-required on the internal input.
94
+ *
95
+ * Bug: MatCheckbox sets `required` attribute but not `aria-required` for screen readers.
96
+ * @see https://github.com/angular/components/issues/XXXXX (TODO: file issue)
97
+ */
98
+ syncAriaRequiredToDom = explicitEffect([this.ariaRequired], ([isRequired]) => {
99
+ const inputEl = this.hostEl.nativeElement.querySelector('input[type="checkbox"]');
100
+ if (inputEl) {
101
+ if (isRequired) {
102
+ inputEl.setAttribute('aria-required', 'true');
103
+ }
104
+ else {
105
+ inputEl.removeAttribute('aria-required');
106
+ }
107
+ }
108
+ });
109
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
110
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MatCheckboxFieldComponent, isStandalone: true, selector: "df-mat-checkbox", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
111
+ @let f = field();
112
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
113
+ @let ariaDescribedBy = this.ariaDescribedBy();
114
+
115
+ <mat-checkbox
116
+ [field]="f"
117
+ [labelPosition]="props()?.labelPosition || 'after'"
118
+ [indeterminate]="props()?.indeterminate || false"
119
+ [color]="props()?.color || 'primary'"
120
+ [disableRipple]="effectiveDisableRipple()"
121
+ [required]="!!f().required?.()"
122
+ [aria-describedby]="ariaDescribedBy"
123
+ [attr.tabindex]="tabIndex()"
124
+ [attr.hidden]="f().hidden() || null"
125
+ >
126
+ {{ label() | dynamicText | async }}
127
+ </mat-checkbox>
128
+
129
+ @if (props()?.hint; as hint) {
130
+ <div class="mat-hint" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
131
+ }
132
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
133
+ <mat-error [id]="errorId() + '-' + i" [attr.hidden]="f().hidden() || null">{{ error.message }}</mat-error>
134
+ }
135
+ `, isInline: true, styles: [":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "directive", type: Field, selector: "[field]", inputs: ["field"] }, { kind: "directive", type: MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
136
+ }
137
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatCheckboxFieldComponent, decorators: [{
138
+ type: Component,
139
+ args: [{ selector: 'df-mat-checkbox', imports: [MatCheckbox, Field, MatError, DynamicTextPipe, AsyncPipe], template: `
140
+ @let f = field();
141
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
142
+ @let ariaDescribedBy = this.ariaDescribedBy();
143
+
144
+ <mat-checkbox
145
+ [field]="f"
146
+ [labelPosition]="props()?.labelPosition || 'after'"
147
+ [indeterminate]="props()?.indeterminate || false"
148
+ [color]="props()?.color || 'primary'"
149
+ [disableRipple]="effectiveDisableRipple()"
150
+ [required]="!!f().required?.()"
151
+ [aria-describedby]="ariaDescribedBy"
152
+ [attr.tabindex]="tabIndex()"
153
+ [attr.hidden]="f().hidden() || null"
154
+ >
155
+ {{ label() | dynamicText | async }}
156
+ </mat-checkbox>
157
+
158
+ @if (props()?.hint; as hint) {
159
+ <div class="mat-hint" [id]="hintId()" [attr.hidden]="f().hidden() || null">{{ hint | dynamicText | async }}</div>
160
+ }
161
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
162
+ <mat-error [id]="errorId() + '-' + i" [attr.hidden]="f().hidden() || null">{{ error.message }}</mat-error>
163
+ }
164
+ `, host: {
165
+ '[class]': 'className()',
166
+ '[id]': '`${key()}`',
167
+ '[attr.data-testid]': 'key()',
168
+ '[attr.hidden]': 'field()().hidden() || null',
169
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}:host([hidden]){display:none!important}\n"] }]
170
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
171
+
172
+ var matCheckbox_component = /*#__PURE__*/Object.freeze({
173
+ __proto__: null,
174
+ default: MatCheckboxFieldComponent
175
+ });
176
+
177
+ class MatDatepickerFieldComponent {
178
+ materialConfig = inject(MATERIAL_CONFIG, { optional: true });
179
+ field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}) });
180
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
181
+ label = input(undefined, { ...(ngDevMode ? { debugName: "label" } : {}) });
182
+ placeholder = input(undefined, { ...(ngDevMode ? { debugName: "placeholder" } : {}) });
183
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
184
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
185
+ minDate = input(null, { ...(ngDevMode ? { debugName: "minDate" } : {}) });
186
+ maxDate = input(null, { ...(ngDevMode ? { debugName: "maxDate" } : {}) });
187
+ startAt = input(null, { ...(ngDevMode ? { debugName: "startAt" } : {}) });
188
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
189
+ validationMessages = input(undefined, { ...(ngDevMode ? { debugName: "validationMessages" } : {}) });
190
+ defaultValidationMessages = input(undefined, { ...(ngDevMode ? { debugName: "defaultValidationMessages" } : {}) });
191
+ effectiveAppearance = computed(() => this.props()?.appearance ?? this.materialConfig?.appearance ?? 'outline', { ...(ngDevMode ? { debugName: "effectiveAppearance" } : {}) });
192
+ effectiveSubscriptSizing = computed(() => this.props()?.subscriptSizing ?? this.materialConfig?.subscriptSizing ?? 'dynamic', { ...(ngDevMode ? { debugName: "effectiveSubscriptSizing" } : {}) });
193
+ resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
194
+ showErrors = shouldShowErrors(this.field);
195
+ errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), { ...(ngDevMode ? { debugName: "errorsToDisplay" } : {}) });
196
+ // ─────────────────────────────────────────────────────────────────────────────
197
+ // Accessibility
198
+ // ─────────────────────────────────────────────────────────────────────────────
199
+ /** Unique ID for the hint element, used for aria-describedby */
200
+ hintId = computed(() => `${this.key()}-hint`, { ...(ngDevMode ? { debugName: "hintId" } : {}) });
201
+ /** Base ID for error elements, used for aria-describedby */
202
+ errorId = computed(() => `${this.key()}-error`, { ...(ngDevMode ? { debugName: "errorId" } : {}) });
203
+ /** aria-invalid: true when field is invalid AND touched, false otherwise */
204
+ ariaInvalid = computed(() => {
205
+ const fieldState = this.field()();
206
+ return fieldState.invalid() && fieldState.touched();
207
+ }, { ...(ngDevMode ? { debugName: "ariaInvalid" } : {}) });
208
+ /** aria-required: true if field is required, null otherwise (to remove attribute) */
209
+ ariaRequired = computed(() => {
210
+ return this.field()().required?.() === true ? true : null;
211
+ }, { ...(ngDevMode ? { debugName: "ariaRequired" } : {}) });
212
+ /** aria-describedby: links to hint and error messages for screen readers */
213
+ ariaDescribedBy = computed(() => {
214
+ const ids = [];
215
+ if (this.props()?.hint) {
216
+ ids.push(this.hintId());
217
+ }
218
+ const errors = this.errorsToDisplay();
219
+ errors.forEach((_, i) => {
220
+ ids.push(`${this.errorId()}-${i}`);
221
+ });
222
+ return ids.length > 0 ? ids.join(' ') : null;
223
+ }, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : {}) });
224
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatDatepickerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
225
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MatDatepickerFieldComponent, isStandalone: true, selector: "df-mat-datepicker", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, providers: [provideNativeDateAdapter()], ngImport: i0, template: `
226
+ @let f = field();
227
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
228
+ @let ariaDescribedBy = this.ariaDescribedBy();
229
+
230
+ <mat-form-field [appearance]="effectiveAppearance()" [subscriptSizing]="effectiveSubscriptSizing()" [class]="className() || ''">
231
+ @if (label(); as label) {
232
+ <mat-label>{{ label | dynamicText | async }}</mat-label>
233
+ }
234
+
235
+ <input
236
+ matInput
237
+ [matDatepicker]="picker"
238
+ [field]="f"
239
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
240
+ [attr.tabindex]="tabIndex()"
241
+ [min]="minDate()"
242
+ [max]="maxDate()"
243
+ [attr.aria-invalid]="ariaInvalid"
244
+ [attr.aria-required]="ariaRequired"
245
+ [attr.aria-describedby]="ariaDescribedBy"
246
+ />
247
+
248
+ <mat-datepicker-toggle matIconSuffix [for]="$any(picker)" />
249
+ <mat-datepicker #picker [startAt]="startAt()" [startView]="props()?.startView || 'month'" [touchUi]="props()?.touchUi ?? false" />
250
+
251
+ @if (props()?.hint; as hint) {
252
+ <mat-hint [id]="hintId()">{{ hint | dynamicText | async }}</mat-hint>
253
+ }
254
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
255
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
256
+ }
257
+ </mat-form-field>
258
+ `, isInline: true, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}mat-form-field{width:100%}\n"], dependencies: [{ kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "directive", type: MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: Field, selector: "[field]", inputs: ["field"] }, { kind: "directive", type: MatError$1, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
259
+ }
260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatDatepickerFieldComponent, decorators: [{
261
+ type: Component,
262
+ args: [{ selector: 'df-mat-datepicker', imports: [
263
+ MatFormField,
264
+ MatLabel,
265
+ MatInput,
266
+ MatHint,
267
+ MatDatepicker,
268
+ MatDatepickerInput,
269
+ MatDatepickerToggle,
270
+ MatSuffix,
271
+ Field,
272
+ MatError$1,
273
+ DynamicTextPipe,
274
+ AsyncPipe,
275
+ ], host: {
276
+ '[id]': '`${key()}`',
277
+ '[attr.data-testid]': 'key()',
278
+ '[attr.hidden]': 'field()().hidden() || null',
279
+ }, template: `
280
+ @let f = field();
281
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
282
+ @let ariaDescribedBy = this.ariaDescribedBy();
283
+
284
+ <mat-form-field [appearance]="effectiveAppearance()" [subscriptSizing]="effectiveSubscriptSizing()" [class]="className() || ''">
285
+ @if (label(); as label) {
286
+ <mat-label>{{ label | dynamicText | async }}</mat-label>
287
+ }
288
+
289
+ <input
290
+ matInput
291
+ [matDatepicker]="picker"
292
+ [field]="f"
293
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
294
+ [attr.tabindex]="tabIndex()"
295
+ [min]="minDate()"
296
+ [max]="maxDate()"
297
+ [attr.aria-invalid]="ariaInvalid"
298
+ [attr.aria-required]="ariaRequired"
299
+ [attr.aria-describedby]="ariaDescribedBy"
300
+ />
301
+
302
+ <mat-datepicker-toggle matIconSuffix [for]="$any(picker)" />
303
+ <mat-datepicker #picker [startAt]="startAt()" [startView]="props()?.startView || 'month'" [touchUi]="props()?.touchUi ?? false" />
304
+
305
+ @if (props()?.hint; as hint) {
306
+ <mat-hint [id]="hintId()">{{ hint | dynamicText | async }}</mat-hint>
307
+ }
308
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
309
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
310
+ }
311
+ </mat-form-field>
312
+ `, providers: [provideNativeDateAdapter()], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}mat-form-field{width:100%}\n"] }]
313
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
314
+
315
+ var matDatepicker_component = /*#__PURE__*/Object.freeze({
316
+ __proto__: null,
317
+ default: MatDatepickerFieldComponent
318
+ });
319
+
320
+ class MatInputFieldComponent {
321
+ materialConfig = inject(MATERIAL_CONFIG, { optional: true });
322
+ field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}) });
323
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
324
+ /**
325
+ * Reference to the native input element.
326
+ * Used to imperatively sync the readonly attribute since Angular Signal Forms'
327
+ * [field] directive doesn't sync FieldState.readonly() to the DOM.
328
+ */
329
+ inputRef = viewChild('inputRef', { ...(ngDevMode ? { debugName: "inputRef" } : {}) });
330
+ /**
331
+ * Computed signal that extracts the readonly state from the field.
332
+ * Used by the effect to reactively sync the readonly attribute to the DOM.
333
+ */
334
+ isReadonly = computed(() => this.field()().readonly(), { ...(ngDevMode ? { debugName: "isReadonly" } : {}) });
335
+ /**
336
+ * Workaround: Angular Signal Forms' [field] directive does NOT sync the readonly
337
+ * attribute to the DOM, even though FieldState.readonly() returns the correct value.
338
+ * This effect imperatively sets/removes the readonly attribute on the native input
339
+ * element whenever the readonly state changes.
340
+ *
341
+ * Note: We cannot use [readonly] or [attr.readonly] bindings because Angular throws
342
+ * NG8022: "Binding to '[readonly]' is not allowed on nodes using the '[field]' directive"
343
+ *
344
+ * @see https://github.com/angular/angular/issues/65897
345
+ */
346
+ syncReadonlyToDom = explicitEffect([this.inputRef, this.isReadonly], ([inputRef, isReadonly]) => {
347
+ if (inputRef?.nativeElement) {
348
+ if (isReadonly) {
349
+ inputRef.nativeElement.setAttribute('readonly', '');
350
+ }
351
+ else {
352
+ inputRef.nativeElement.removeAttribute('readonly');
353
+ }
354
+ }
355
+ });
356
+ label = input(undefined, { ...(ngDevMode ? { debugName: "label" } : {}) });
357
+ placeholder = input(undefined, { ...(ngDevMode ? { debugName: "placeholder" } : {}) });
358
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
359
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
360
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
361
+ validationMessages = input(undefined, { ...(ngDevMode ? { debugName: "validationMessages" } : {}) });
362
+ defaultValidationMessages = input(undefined, { ...(ngDevMode ? { debugName: "defaultValidationMessages" } : {}) });
363
+ effectiveAppearance = computed(() => this.props()?.appearance ?? this.materialConfig?.appearance ?? 'outline', { ...(ngDevMode ? { debugName: "effectiveAppearance" } : {}) });
364
+ effectiveSubscriptSizing = computed(() => this.props()?.subscriptSizing ?? this.materialConfig?.subscriptSizing ?? 'dynamic', { ...(ngDevMode ? { debugName: "effectiveSubscriptSizing" } : {}) });
365
+ resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
366
+ showErrors = shouldShowErrors(this.field);
367
+ errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), { ...(ngDevMode ? { debugName: "errorsToDisplay" } : {}) });
368
+ // ─────────────────────────────────────────────────────────────────────────────
369
+ // Accessibility
370
+ // ─────────────────────────────────────────────────────────────────────────────
371
+ /** Unique ID for the hint element, used for aria-describedby */
372
+ hintId = computed(() => `${this.key()}-hint`, { ...(ngDevMode ? { debugName: "hintId" } : {}) });
373
+ /** Base ID for error elements, used for aria-describedby */
374
+ errorId = computed(() => `${this.key()}-error`, { ...(ngDevMode ? { debugName: "errorId" } : {}) });
375
+ /** aria-invalid: true when field is invalid AND touched, false otherwise */
376
+ ariaInvalid = computed(() => {
377
+ const fieldState = this.field()();
378
+ return fieldState.invalid() && fieldState.touched();
379
+ }, { ...(ngDevMode ? { debugName: "ariaInvalid" } : {}) });
380
+ /** aria-required: true if field is required, null otherwise (to remove attribute) */
381
+ ariaRequired = computed(() => {
382
+ return this.field()().required?.() === true ? true : null;
383
+ }, { ...(ngDevMode ? { debugName: "ariaRequired" } : {}) });
384
+ /** aria-describedby: links to hint and error messages for screen readers */
385
+ ariaDescribedBy = computed(() => {
386
+ const ids = [];
387
+ if (this.props()?.hint) {
388
+ ids.push(this.hintId());
389
+ }
390
+ const errors = this.errorsToDisplay();
391
+ errors.forEach((_, i) => {
392
+ ids.push(`${this.errorId()}-${i}`);
393
+ });
394
+ return ids.length > 0 ? ids.join(' ') : null;
395
+ }, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : {}) });
396
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatInputFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
397
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MatInputFieldComponent, isStandalone: true, selector: "df-mat-input", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
398
+ @let f = field();
399
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
400
+ @let ariaDescribedBy = this.ariaDescribedBy();
401
+
402
+ <mat-form-field [appearance]="effectiveAppearance()" [subscriptSizing]="effectiveSubscriptSizing()">
403
+ @if (label()) {
404
+ <mat-label>{{ label() | dynamicText | async }}</mat-label>
405
+ }
406
+ @switch (props()?.type ?? 'text') {
407
+ @case ('email') {
408
+ <input
409
+ #inputRef
410
+ matInput
411
+ type="email"
412
+ [field]="f"
413
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
414
+ [attr.tabindex]="tabIndex()"
415
+ [attr.aria-invalid]="ariaInvalid"
416
+ [attr.aria-required]="ariaRequired"
417
+ [attr.aria-describedby]="ariaDescribedBy"
418
+ />
419
+ }
420
+ @case ('password') {
421
+ <input
422
+ #inputRef
423
+ matInput
424
+ type="password"
425
+ [field]="f"
426
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
427
+ [attr.tabindex]="tabIndex()"
428
+ [attr.aria-invalid]="ariaInvalid"
429
+ [attr.aria-required]="ariaRequired"
430
+ [attr.aria-describedby]="ariaDescribedBy"
431
+ />
432
+ }
433
+ @case ('number') {
434
+ <input
435
+ #inputRef
436
+ matInput
437
+ type="number"
438
+ [field]="f"
439
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
440
+ [attr.tabindex]="tabIndex()"
441
+ [attr.aria-invalid]="ariaInvalid"
442
+ [attr.aria-required]="ariaRequired"
443
+ [attr.aria-describedby]="ariaDescribedBy"
444
+ />
445
+ }
446
+ @case ('tel') {
447
+ <input
448
+ #inputRef
449
+ matInput
450
+ type="tel"
451
+ [field]="f"
452
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
453
+ [attr.tabindex]="tabIndex()"
454
+ [attr.aria-invalid]="ariaInvalid"
455
+ [attr.aria-required]="ariaRequired"
456
+ [attr.aria-describedby]="ariaDescribedBy"
457
+ />
458
+ }
459
+ @case ('url') {
460
+ <input
461
+ #inputRef
462
+ matInput
463
+ type="url"
464
+ [field]="f"
465
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
466
+ [attr.tabindex]="tabIndex()"
467
+ [attr.aria-invalid]="ariaInvalid"
468
+ [attr.aria-required]="ariaRequired"
469
+ [attr.aria-describedby]="ariaDescribedBy"
470
+ />
471
+ }
472
+ @default {
473
+ <input
474
+ #inputRef
475
+ matInput
476
+ type="text"
477
+ [field]="f"
478
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
479
+ [attr.tabindex]="tabIndex()"
480
+ [attr.aria-invalid]="ariaInvalid"
481
+ [attr.aria-required]="ariaRequired"
482
+ [attr.aria-describedby]="ariaDescribedBy"
483
+ />
484
+ }
485
+ }
486
+ @if (props()?.hint; as hint) {
487
+ <mat-hint [id]="hintId()">{{ hint | dynamicText | async }}</mat-hint>
488
+ }
489
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
490
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
491
+ }
492
+ </mat-form-field>
493
+ `, isInline: true, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}mat-form-field{width:100%}\n"], dependencies: [{ kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: Field, selector: "[field]", inputs: ["field"] }, { kind: "directive", type: MatError$1, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
494
+ }
495
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatInputFieldComponent, decorators: [{
496
+ type: Component,
497
+ args: [{ selector: 'df-mat-input', imports: [MatFormField, MatLabel, MatInput, MatHint, Field, MatError$1, DynamicTextPipe, AsyncPipe], template: `
498
+ @let f = field();
499
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
500
+ @let ariaDescribedBy = this.ariaDescribedBy();
501
+
502
+ <mat-form-field [appearance]="effectiveAppearance()" [subscriptSizing]="effectiveSubscriptSizing()">
503
+ @if (label()) {
504
+ <mat-label>{{ label() | dynamicText | async }}</mat-label>
505
+ }
506
+ @switch (props()?.type ?? 'text') {
507
+ @case ('email') {
508
+ <input
509
+ #inputRef
510
+ matInput
511
+ type="email"
512
+ [field]="f"
513
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
514
+ [attr.tabindex]="tabIndex()"
515
+ [attr.aria-invalid]="ariaInvalid"
516
+ [attr.aria-required]="ariaRequired"
517
+ [attr.aria-describedby]="ariaDescribedBy"
518
+ />
519
+ }
520
+ @case ('password') {
521
+ <input
522
+ #inputRef
523
+ matInput
524
+ type="password"
525
+ [field]="f"
526
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
527
+ [attr.tabindex]="tabIndex()"
528
+ [attr.aria-invalid]="ariaInvalid"
529
+ [attr.aria-required]="ariaRequired"
530
+ [attr.aria-describedby]="ariaDescribedBy"
531
+ />
532
+ }
533
+ @case ('number') {
534
+ <input
535
+ #inputRef
536
+ matInput
537
+ type="number"
538
+ [field]="f"
539
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
540
+ [attr.tabindex]="tabIndex()"
541
+ [attr.aria-invalid]="ariaInvalid"
542
+ [attr.aria-required]="ariaRequired"
543
+ [attr.aria-describedby]="ariaDescribedBy"
544
+ />
545
+ }
546
+ @case ('tel') {
547
+ <input
548
+ #inputRef
549
+ matInput
550
+ type="tel"
551
+ [field]="f"
552
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
553
+ [attr.tabindex]="tabIndex()"
554
+ [attr.aria-invalid]="ariaInvalid"
555
+ [attr.aria-required]="ariaRequired"
556
+ [attr.aria-describedby]="ariaDescribedBy"
557
+ />
558
+ }
559
+ @case ('url') {
560
+ <input
561
+ #inputRef
562
+ matInput
563
+ type="url"
564
+ [field]="f"
565
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
566
+ [attr.tabindex]="tabIndex()"
567
+ [attr.aria-invalid]="ariaInvalid"
568
+ [attr.aria-required]="ariaRequired"
569
+ [attr.aria-describedby]="ariaDescribedBy"
570
+ />
571
+ }
572
+ @default {
573
+ <input
574
+ #inputRef
575
+ matInput
576
+ type="text"
577
+ [field]="f"
578
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
579
+ [attr.tabindex]="tabIndex()"
580
+ [attr.aria-invalid]="ariaInvalid"
581
+ [attr.aria-required]="ariaRequired"
582
+ [attr.aria-describedby]="ariaDescribedBy"
583
+ />
584
+ }
585
+ }
586
+ @if (props()?.hint; as hint) {
587
+ <mat-hint [id]="hintId()">{{ hint | dynamicText | async }}</mat-hint>
588
+ }
589
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
590
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
591
+ }
592
+ </mat-form-field>
593
+ `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
594
+ '[id]': '`${key()}`',
595
+ '[attr.data-testid]': 'key()',
596
+ '[class]': 'className()',
597
+ '[attr.hidden]': 'field()().hidden() || null',
598
+ }, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}mat-form-field{width:100%}\n"] }]
599
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], inputRef: [{ type: i0.ViewChild, args: ['inputRef', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
600
+
601
+ var matInput_component = /*#__PURE__*/Object.freeze({
602
+ __proto__: null,
603
+ default: MatInputFieldComponent
604
+ });
605
+
606
+ class ValueInArrayPipe {
607
+ transform(element, array) {
608
+ return array?.includes(element) ?? false;
609
+ }
610
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ValueInArrayPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
611
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.1", ngImport: i0, type: ValueInArrayPipe, isStandalone: true, name: "inArray" });
612
+ }
613
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ValueInArrayPipe, decorators: [{
614
+ type: Pipe,
615
+ args: [{
616
+ pure: true,
617
+ name: 'inArray',
618
+ }]
619
+ }] });
620
+
621
+ /**
622
+ * Performs a deep equality comparison between two values
623
+ */
624
+ function isEqual(a, b) {
625
+ if (a === b)
626
+ return true;
627
+ if (a == null || b == null)
628
+ return false;
629
+ if (typeof a !== typeof b)
630
+ return false;
631
+ if (a instanceof Date && b instanceof Date) {
632
+ return a.getTime() === b.getTime();
633
+ }
634
+ if (Array.isArray(a) && Array.isArray(b)) {
635
+ if (a.length !== b.length)
636
+ return false;
637
+ return a.every((item, index) => isEqual(item, b[index]));
638
+ }
639
+ if (typeof a === 'object' && typeof b === 'object') {
640
+ const keysA = Object.keys(a);
641
+ const keysB = Object.keys(b);
642
+ if (keysA.length !== keysB.length)
643
+ return false;
644
+ return keysA.every((key) => keysB.includes(key) && isEqual(a[key], b[key]));
645
+ }
646
+ return Object.is(a, b);
647
+ }
648
+
649
+ class MatMultiCheckboxFieldComponent {
650
+ field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}) });
651
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
652
+ label = input(undefined, { ...(ngDevMode ? { debugName: "label" } : {}) });
653
+ placeholder = input(undefined, { ...(ngDevMode ? { debugName: "placeholder" } : {}) });
654
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
655
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
656
+ options = input([], { ...(ngDevMode ? { debugName: "options" } : {}) });
657
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
658
+ valueViewModel = linkedSignal(() => {
659
+ const currentValues = this.field()().value();
660
+ return this.options().filter((option) => currentValues.includes(option.value));
661
+ }, { ...(ngDevMode ? { debugName: "valueViewModel" } : {}), equal: isEqual });
662
+ constructor() {
663
+ explicitEffect([this.valueViewModel], ([selectedOptions]) => {
664
+ const selectedValues = selectedOptions.map((option) => option.value);
665
+ if (!isEqual(selectedValues, this.field()().value())) {
666
+ this.field()().value.set(selectedValues);
667
+ }
668
+ });
669
+ explicitEffect([this.options], ([options]) => {
670
+ const values = options.map((option) => option.value);
671
+ const uniqueValues = new Set(values);
672
+ if (values.length !== uniqueValues.size) {
673
+ const duplicates = values.filter((value, index) => values.indexOf(value) !== index);
674
+ throw new Error(`Duplicate option values detected in mat-multi-checkbox: ${duplicates.join(', ')}`);
675
+ }
676
+ });
677
+ }
678
+ onCheckboxChange(option, checked) {
679
+ this.valueViewModel.update((currentOptions) => {
680
+ if (checked) {
681
+ return currentOptions.some((opt) => opt.value === option.value) ? currentOptions : [...currentOptions, option];
682
+ }
683
+ else {
684
+ return currentOptions.filter((opt) => opt.value !== option.value);
685
+ }
686
+ });
687
+ }
688
+ validationMessages = input(undefined, { ...(ngDevMode ? { debugName: "validationMessages" } : {}) });
689
+ defaultValidationMessages = input(undefined, { ...(ngDevMode ? { debugName: "defaultValidationMessages" } : {}) });
690
+ resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
691
+ showErrors = shouldShowErrors(this.field);
692
+ errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), { ...(ngDevMode ? { debugName: "errorsToDisplay" } : {}) });
693
+ // ─────────────────────────────────────────────────────────────────────────────
694
+ // Accessibility
695
+ // ─────────────────────────────────────────────────────────────────────────────
696
+ /** Unique ID for the hint element, used for aria-describedby */
697
+ hintId = computed(() => `${this.key()}-hint`, { ...(ngDevMode ? { debugName: "hintId" } : {}) });
698
+ /** Base ID for error elements, used for aria-describedby */
699
+ errorId = computed(() => `${this.key()}-error`, { ...(ngDevMode ? { debugName: "errorId" } : {}) });
700
+ /** aria-invalid: true when field is invalid AND touched, false otherwise */
701
+ ariaInvalid = computed(() => {
702
+ const fieldState = this.field()();
703
+ return fieldState.invalid() && fieldState.touched();
704
+ }, { ...(ngDevMode ? { debugName: "ariaInvalid" } : {}) });
705
+ /** aria-required: true if field is required, null otherwise (to remove attribute) */
706
+ ariaRequired = computed(() => {
707
+ return this.field()().required?.() === true ? true : null;
708
+ }, { ...(ngDevMode ? { debugName: "ariaRequired" } : {}) });
709
+ /** aria-describedby: links to hint and error messages for screen readers */
710
+ ariaDescribedBy = computed(() => {
711
+ const ids = [];
712
+ if (this.props()?.hint) {
713
+ ids.push(this.hintId());
714
+ }
715
+ const errors = this.errorsToDisplay();
716
+ errors.forEach((_, i) => {
717
+ ids.push(`${this.errorId()}-${i}`);
718
+ });
719
+ return ids.length > 0 ? ids.join(' ') : null;
720
+ }, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : {}) });
721
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatMultiCheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
722
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MatMultiCheckboxFieldComponent, isStandalone: true, selector: "df-mat-multi-checkbox", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className() || \"\"", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, providers: [ValueInArrayPipe], ngImport: i0, template: `
723
+ @let f = field();
724
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
725
+ @let ariaDescribedBy = this.ariaDescribedBy();
726
+
727
+ @if (label(); as label) {
728
+ <div class="checkbox-group-label">{{ label | dynamicText | async }}</div>
729
+ }
730
+
731
+ <div
732
+ class="checkbox-group"
733
+ role="group"
734
+ [attr.aria-invalid]="ariaInvalid"
735
+ [attr.aria-required]="ariaRequired"
736
+ [attr.aria-describedby]="ariaDescribedBy"
737
+ >
738
+ @for (option of options(); track option.value) {
739
+ <mat-checkbox
740
+ [checked]="option | inArray: valueViewModel()"
741
+ [disabled]="f().disabled() || option.disabled"
742
+ [color]="props()?.color || 'primary'"
743
+ [labelPosition]="props()?.labelPosition || 'after'"
744
+ (change)="onCheckboxChange(option, $event.checked)"
745
+ >
746
+ {{ option.label | dynamicText | async }}
747
+ </mat-checkbox>
748
+ }
749
+ </div>
750
+
751
+ @if (props()?.hint; as hint) {
752
+ <div class="mat-hint" [id]="hintId()">{{ hint | dynamicText | async }}</div>
753
+ }
754
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
755
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
756
+ }
757
+ `, isInline: true, styles: [":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "directive", type: MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "pipe", type: ValueInArrayPipe, name: "inArray" }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
758
+ }
759
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatMultiCheckboxFieldComponent, decorators: [{
760
+ type: Component,
761
+ args: [{ selector: 'df-mat-multi-checkbox', imports: [MatCheckbox, ValueInArrayPipe, MatError, DynamicTextPipe, AsyncPipe], template: `
762
+ @let f = field();
763
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
764
+ @let ariaDescribedBy = this.ariaDescribedBy();
765
+
766
+ @if (label(); as label) {
767
+ <div class="checkbox-group-label">{{ label | dynamicText | async }}</div>
768
+ }
769
+
770
+ <div
771
+ class="checkbox-group"
772
+ role="group"
773
+ [attr.aria-invalid]="ariaInvalid"
774
+ [attr.aria-required]="ariaRequired"
775
+ [attr.aria-describedby]="ariaDescribedBy"
776
+ >
777
+ @for (option of options(); track option.value) {
778
+ <mat-checkbox
779
+ [checked]="option | inArray: valueViewModel()"
780
+ [disabled]="f().disabled() || option.disabled"
781
+ [color]="props()?.color || 'primary'"
782
+ [labelPosition]="props()?.labelPosition || 'after'"
783
+ (change)="onCheckboxChange(option, $event.checked)"
784
+ >
785
+ {{ option.label | dynamicText | async }}
786
+ </mat-checkbox>
787
+ }
788
+ </div>
789
+
790
+ @if (props()?.hint; as hint) {
791
+ <div class="mat-hint" [id]="hintId()">{{ hint | dynamicText | async }}</div>
792
+ }
793
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
794
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
795
+ }
796
+ `, host: {
797
+ '[class]': 'className() || ""',
798
+ '[id]': '`${key()}`',
799
+ '[attr.data-testid]': 'key()',
800
+ '[attr.hidden]': 'field()().hidden() || null',
801
+ }, providers: [ValueInArrayPipe], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}:host([hidden]){display:none!important}\n"] }]
802
+ }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
803
+
804
+ var matMultiCheckbox_component = /*#__PURE__*/Object.freeze({
805
+ __proto__: null,
806
+ default: MatMultiCheckboxFieldComponent
807
+ });
808
+
809
+ class MatRadioFieldComponent {
810
+ field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}) });
811
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
812
+ label = input(undefined, { ...(ngDevMode ? { debugName: "label" } : {}) });
813
+ placeholder = input(undefined, { ...(ngDevMode ? { debugName: "placeholder" } : {}) });
814
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
815
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
816
+ options = input([], { ...(ngDevMode ? { debugName: "options" } : {}) });
817
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
818
+ validationMessages = input(undefined, { ...(ngDevMode ? { debugName: "validationMessages" } : {}) });
819
+ defaultValidationMessages = input(undefined, { ...(ngDevMode ? { debugName: "defaultValidationMessages" } : {}) });
820
+ resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
821
+ showErrors = shouldShowErrors(this.field);
822
+ errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), { ...(ngDevMode ? { debugName: "errorsToDisplay" } : {}) });
823
+ // ─────────────────────────────────────────────────────────────────────────────
824
+ // Accessibility
825
+ // ─────────────────────────────────────────────────────────────────────────────
826
+ /** Unique ID for the hint element, used for aria-describedby */
827
+ hintId = computed(() => `${this.key()}-hint`, { ...(ngDevMode ? { debugName: "hintId" } : {}) });
828
+ /** Base ID for error elements, used for aria-describedby */
829
+ errorId = computed(() => `${this.key()}-error`, { ...(ngDevMode ? { debugName: "errorId" } : {}) });
830
+ /** aria-invalid: true when field is invalid AND touched, false otherwise */
831
+ ariaInvalid = computed(() => {
832
+ const fieldState = this.field()();
833
+ return fieldState.invalid() && fieldState.touched();
834
+ }, { ...(ngDevMode ? { debugName: "ariaInvalid" } : {}) });
835
+ /** aria-required: true if field is required, null otherwise (to remove attribute) */
836
+ ariaRequired = computed(() => {
837
+ return this.field()().required?.() === true ? true : null;
838
+ }, { ...(ngDevMode ? { debugName: "ariaRequired" } : {}) });
839
+ /** aria-describedby: links to hint and error messages for screen readers */
840
+ ariaDescribedBy = computed(() => {
841
+ const ids = [];
842
+ if (this.props()?.hint) {
843
+ ids.push(this.hintId());
844
+ }
845
+ const errors = this.errorsToDisplay();
846
+ errors.forEach((_, i) => {
847
+ ids.push(`${this.errorId()}-${i}`);
848
+ });
849
+ return ids.length > 0 ? ids.join(' ') : null;
850
+ }, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : {}) });
851
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatRadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
852
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MatRadioFieldComponent, isStandalone: true, selector: "df-mat-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 }, 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: `
853
+ @let f = field();
854
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
855
+ @let ariaDescribedBy = this.ariaDescribedBy();
856
+
857
+ @if (label()) {
858
+ <div class="radio-label">{{ label() | dynamicText | async }}</div>
859
+ }
860
+
861
+ <mat-radio-group
862
+ [field]="f"
863
+ [attr.aria-invalid]="ariaInvalid"
864
+ [attr.aria-required]="ariaRequired"
865
+ [attr.aria-describedby]="ariaDescribedBy"
866
+ >
867
+ @for (option of options(); track option.value) {
868
+ <mat-radio-button
869
+ [value]="option.value"
870
+ [disabled]="option.disabled || false"
871
+ [color]="props()?.color || 'primary'"
872
+ [labelPosition]="props()?.labelPosition || 'after'"
873
+ >
874
+ {{ option.label | dynamicText | async }}
875
+ </mat-radio-button>
876
+ }
877
+ </mat-radio-group>
878
+
879
+ @if (props()?.hint; as hint) {
880
+ <div class="mat-hint" [id]="hintId()">{{ hint | dynamicText | async }}</div>
881
+ }
882
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
883
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
884
+ }
885
+ `, isInline: true, styles: [":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "directive", type: MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "directive", type: Field, selector: "[field]", inputs: ["field"] }, { kind: "directive", type: MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
886
+ }
887
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatRadioFieldComponent, decorators: [{
888
+ type: Component,
889
+ args: [{ selector: 'df-mat-radio', imports: [MatRadioGroup, MatRadioButton, Field, MatError, DynamicTextPipe, AsyncPipe], template: `
890
+ @let f = field();
891
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
892
+ @let ariaDescribedBy = this.ariaDescribedBy();
893
+
894
+ @if (label()) {
895
+ <div class="radio-label">{{ label() | dynamicText | async }}</div>
896
+ }
897
+
898
+ <mat-radio-group
899
+ [field]="f"
900
+ [attr.aria-invalid]="ariaInvalid"
901
+ [attr.aria-required]="ariaRequired"
902
+ [attr.aria-describedby]="ariaDescribedBy"
903
+ >
904
+ @for (option of options(); track option.value) {
905
+ <mat-radio-button
906
+ [value]="option.value"
907
+ [disabled]="option.disabled || false"
908
+ [color]="props()?.color || 'primary'"
909
+ [labelPosition]="props()?.labelPosition || 'after'"
910
+ >
911
+ {{ option.label | dynamicText | async }}
912
+ </mat-radio-button>
913
+ }
914
+ </mat-radio-group>
915
+
916
+ @if (props()?.hint; as hint) {
917
+ <div class="mat-hint" [id]="hintId()">{{ hint | dynamicText | async }}</div>
918
+ }
919
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
920
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
921
+ }
922
+ `, host: {
923
+ '[class]': 'className() || ""',
924
+ '[id]': '`${key()}`',
925
+ '[attr.data-testid]': 'key()',
926
+ '[attr.hidden]': 'field()().hidden() || null',
927
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}:host([hidden]){display:none!important}\n"] }]
928
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
929
+
930
+ var matRadio_component = /*#__PURE__*/Object.freeze({
931
+ __proto__: null,
932
+ default: MatRadioFieldComponent
933
+ });
934
+
935
+ class MatSelectFieldComponent {
936
+ materialConfig = inject(MATERIAL_CONFIG, { optional: true });
937
+ field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}) });
938
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
939
+ label = input(undefined, { ...(ngDevMode ? { debugName: "label" } : {}) });
940
+ placeholder = input(undefined, { ...(ngDevMode ? { debugName: "placeholder" } : {}) });
941
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
942
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
943
+ options = input([], { ...(ngDevMode ? { debugName: "options" } : {}) });
944
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
945
+ validationMessages = input(undefined, { ...(ngDevMode ? { debugName: "validationMessages" } : {}) });
946
+ defaultValidationMessages = input(undefined, { ...(ngDevMode ? { debugName: "defaultValidationMessages" } : {}) });
947
+ effectiveAppearance = computed(() => this.props()?.appearance ?? this.materialConfig?.appearance ?? 'outline', { ...(ngDevMode ? { debugName: "effectiveAppearance" } : {}) });
948
+ effectiveSubscriptSizing = computed(() => this.props()?.subscriptSizing ?? this.materialConfig?.subscriptSizing ?? 'dynamic', { ...(ngDevMode ? { debugName: "effectiveSubscriptSizing" } : {}) });
949
+ resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
950
+ showErrors = shouldShowErrors(this.field);
951
+ errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), { ...(ngDevMode ? { debugName: "errorsToDisplay" } : {}) });
952
+ defaultCompare = Object.is;
953
+ // ─────────────────────────────────────────────────────────────────────────────
954
+ // Accessibility
955
+ // ─────────────────────────────────────────────────────────────────────────────
956
+ /** Unique ID for the hint element, used for aria-describedby */
957
+ hintId = computed(() => `${this.key()}-hint`, { ...(ngDevMode ? { debugName: "hintId" } : {}) });
958
+ /** Base ID for error elements, used for aria-describedby */
959
+ errorId = computed(() => `${this.key()}-error`, { ...(ngDevMode ? { debugName: "errorId" } : {}) });
960
+ /** aria-invalid: true when field is invalid AND touched, false otherwise */
961
+ ariaInvalid = computed(() => {
962
+ const fieldState = this.field()();
963
+ return fieldState.invalid() && fieldState.touched();
964
+ }, { ...(ngDevMode ? { debugName: "ariaInvalid" } : {}) });
965
+ /** aria-required: true if field is required, null otherwise (to remove attribute) */
966
+ ariaRequired = computed(() => {
967
+ return this.field()().required?.() === true ? true : null;
968
+ }, { ...(ngDevMode ? { debugName: "ariaRequired" } : {}) });
969
+ /** aria-describedby: links to hint and error messages for screen readers */
970
+ ariaDescribedBy = computed(() => {
971
+ const ids = [];
972
+ if (this.props()?.hint) {
973
+ ids.push(this.hintId());
974
+ }
975
+ const errors = this.errorsToDisplay();
976
+ errors.forEach((_, i) => {
977
+ ids.push(`${this.errorId()}-${i}`);
978
+ });
979
+ return ids.length > 0 ? ids.join(' ') : null;
980
+ }, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : {}) });
981
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
982
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MatSelectFieldComponent, isStandalone: true, selector: "df-mat-select", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
983
+ @let f = field();
984
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
985
+ @let ariaDescribedBy = this.ariaDescribedBy();
986
+
987
+ <mat-form-field [appearance]="effectiveAppearance()" [subscriptSizing]="effectiveSubscriptSizing()">
988
+ @if (label(); as label) {
989
+ <mat-label>{{ label | dynamicText | async }}</mat-label>
990
+ }
991
+
992
+ <mat-select
993
+ [field]="f"
994
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
995
+ [multiple]="props()?.multiple || false"
996
+ [compareWith]="props()?.compareWith || defaultCompare"
997
+ [attr.aria-invalid]="ariaInvalid"
998
+ [attr.aria-required]="ariaRequired"
999
+ [attr.aria-describedby]="ariaDescribedBy"
1000
+ >
1001
+ @for (option of options(); track option.value) {
1002
+ <mat-option [value]="option.value" [disabled]="option.disabled || false">
1003
+ {{ option.label | dynamicText | async }}
1004
+ </mat-option>
1005
+ }
1006
+ </mat-select>
1007
+
1008
+ @if (props()?.hint; as hint) {
1009
+ <mat-hint [id]="hintId()">{{ hint | dynamicText | async }}</mat-hint>
1010
+ }
1011
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1012
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
1013
+ }
1014
+ </mat-form-field>
1015
+ `, isInline: true, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}mat-form-field{width:100%}\n"], dependencies: [{ kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: Field, selector: "[field]", inputs: ["field"] }, { kind: "directive", type: MatError$1, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1016
+ }
1017
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatSelectFieldComponent, decorators: [{
1018
+ type: Component,
1019
+ args: [{ selector: 'df-mat-select', imports: [MatFormField, MatLabel, MatSelect, MatOption, MatHint, Field, MatError$1, DynamicTextPipe, AsyncPipe], template: `
1020
+ @let f = field();
1021
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1022
+ @let ariaDescribedBy = this.ariaDescribedBy();
1023
+
1024
+ <mat-form-field [appearance]="effectiveAppearance()" [subscriptSizing]="effectiveSubscriptSizing()">
1025
+ @if (label(); as label) {
1026
+ <mat-label>{{ label | dynamicText | async }}</mat-label>
1027
+ }
1028
+
1029
+ <mat-select
1030
+ [field]="f"
1031
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1032
+ [multiple]="props()?.multiple || false"
1033
+ [compareWith]="props()?.compareWith || defaultCompare"
1034
+ [attr.aria-invalid]="ariaInvalid"
1035
+ [attr.aria-required]="ariaRequired"
1036
+ [attr.aria-describedby]="ariaDescribedBy"
1037
+ >
1038
+ @for (option of options(); track option.value) {
1039
+ <mat-option [value]="option.value" [disabled]="option.disabled || false">
1040
+ {{ option.label | dynamicText | async }}
1041
+ </mat-option>
1042
+ }
1043
+ </mat-select>
1044
+
1045
+ @if (props()?.hint; as hint) {
1046
+ <mat-hint [id]="hintId()">{{ hint | dynamicText | async }}</mat-hint>
1047
+ }
1048
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1049
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
1050
+ }
1051
+ </mat-form-field>
1052
+ `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
1053
+ '[id]': '`${key()}`',
1054
+ '[attr.data-testid]': 'key()',
1055
+ '[class]': 'className()',
1056
+ '[attr.hidden]': 'field()().hidden() || null',
1057
+ }, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}mat-form-field{width:100%}\n"] }]
1058
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
1059
+
1060
+ var matSelect_component = /*#__PURE__*/Object.freeze({
1061
+ __proto__: null,
1062
+ default: MatSelectFieldComponent
1063
+ });
1064
+
1065
+ class MatSliderFieldComponent {
1066
+ field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}) });
1067
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
1068
+ label = input(undefined, { ...(ngDevMode ? { debugName: "label" } : {}) });
1069
+ placeholder = input(undefined, { ...(ngDevMode ? { debugName: "placeholder" } : {}) });
1070
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
1071
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
1072
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
1073
+ validationMessages = input(undefined, { ...(ngDevMode ? { debugName: "validationMessages" } : {}) });
1074
+ defaultValidationMessages = input(undefined, { ...(ngDevMode ? { debugName: "defaultValidationMessages" } : {}) });
1075
+ resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1076
+ showErrors = shouldShowErrors(this.field);
1077
+ errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), { ...(ngDevMode ? { debugName: "errorsToDisplay" } : {}) });
1078
+ // ─────────────────────────────────────────────────────────────────────────────
1079
+ // Accessibility
1080
+ // ─────────────────────────────────────────────────────────────────────────────
1081
+ /** Unique ID for the hint element, used for aria-describedby */
1082
+ hintId = computed(() => `${this.key()}-hint`, { ...(ngDevMode ? { debugName: "hintId" } : {}) });
1083
+ /** Base ID for error elements, used for aria-describedby */
1084
+ errorId = computed(() => `${this.key()}-error`, { ...(ngDevMode ? { debugName: "errorId" } : {}) });
1085
+ /** aria-invalid: true when field is invalid AND touched, false otherwise */
1086
+ ariaInvalid = computed(() => {
1087
+ const fieldState = this.field()();
1088
+ return fieldState.invalid() && fieldState.touched();
1089
+ }, { ...(ngDevMode ? { debugName: "ariaInvalid" } : {}) });
1090
+ /** aria-describedby: links to hint and error messages for screen readers */
1091
+ ariaDescribedBy = computed(() => {
1092
+ const ids = [];
1093
+ if (this.props()?.hint) {
1094
+ ids.push(this.hintId());
1095
+ }
1096
+ const errors = this.errorsToDisplay();
1097
+ errors.forEach((_, i) => {
1098
+ ids.push(`${this.errorId()}-${i}`);
1099
+ });
1100
+ return ids.length > 0 ? ids.join(' ') : null;
1101
+ }, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : {}) });
1102
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatSliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1103
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MatSliderFieldComponent, isStandalone: true, selector: "df-mat-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 }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1104
+ @let f = field();
1105
+ @let ariaInvalid = this.ariaInvalid();
1106
+ @let ariaDescribedBy = this.ariaDescribedBy();
1107
+
1108
+ @if (label(); as label) {
1109
+ <div class="slider-label">{{ label | dynamicText | async }}</div>
1110
+ }
1111
+
1112
+ <mat-slider
1113
+ [min]="f().min?.() ?? 0"
1114
+ [max]="f().max?.() ?? 100"
1115
+ [step]="props()?.step ?? 1"
1116
+ [discrete]="props()?.thumbLabel || props()?.showThumbLabel"
1117
+ [showTickMarks]="props()?.tickInterval !== undefined"
1118
+ [color]="props()?.color || 'primary'"
1119
+ class="slider-container"
1120
+ >
1121
+ <input
1122
+ matSliderThumb
1123
+ [field]="f"
1124
+ [attr.tabindex]="tabIndex()"
1125
+ [attr.aria-invalid]="ariaInvalid"
1126
+ [attr.aria-describedby]="ariaDescribedBy"
1127
+ />
1128
+ </mat-slider>
1129
+
1130
+ @if (props()?.hint; as hint) {
1131
+ <div class="mat-hint" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1132
+ }
1133
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1134
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
1135
+ }
1136
+ `, isInline: true, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}.slider-container{width:100%}\n"], dependencies: [{ kind: "component", type: MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "directive", type: MatSliderThumb, selector: "input[matSliderThumb]", inputs: ["value"], outputs: ["valueChange", "dragStart", "dragEnd"], exportAs: ["matSliderThumb"] }, { kind: "directive", type: MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: Field, selector: "[field]", inputs: ["field"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1137
+ }
1138
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatSliderFieldComponent, decorators: [{
1139
+ type: Component,
1140
+ args: [{ selector: 'df-mat-slider', imports: [MatSlider, MatSliderThumb, MatError, DynamicTextPipe, AsyncPipe, Field], template: `
1141
+ @let f = field();
1142
+ @let ariaInvalid = this.ariaInvalid();
1143
+ @let ariaDescribedBy = this.ariaDescribedBy();
1144
+
1145
+ @if (label(); as label) {
1146
+ <div class="slider-label">{{ label | dynamicText | async }}</div>
1147
+ }
1148
+
1149
+ <mat-slider
1150
+ [min]="f().min?.() ?? 0"
1151
+ [max]="f().max?.() ?? 100"
1152
+ [step]="props()?.step ?? 1"
1153
+ [discrete]="props()?.thumbLabel || props()?.showThumbLabel"
1154
+ [showTickMarks]="props()?.tickInterval !== undefined"
1155
+ [color]="props()?.color || 'primary'"
1156
+ class="slider-container"
1157
+ >
1158
+ <input
1159
+ matSliderThumb
1160
+ [field]="f"
1161
+ [attr.tabindex]="tabIndex()"
1162
+ [attr.aria-invalid]="ariaInvalid"
1163
+ [attr.aria-describedby]="ariaDescribedBy"
1164
+ />
1165
+ </mat-slider>
1166
+
1167
+ @if (props()?.hint; as hint) {
1168
+ <div class="mat-hint" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1169
+ }
1170
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1171
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
1172
+ }
1173
+ `, host: {
1174
+ '[class]': 'className()',
1175
+ '[id]': '`${key()}`',
1176
+ '[attr.data-testid]': 'key()',
1177
+ '[attr.hidden]': 'field()().hidden() || null',
1178
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}.slider-container{width:100%}\n"] }]
1179
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
1180
+
1181
+ var matSlider_component = /*#__PURE__*/Object.freeze({
1182
+ __proto__: null,
1183
+ default: MatSliderFieldComponent
1184
+ });
1185
+
1186
+ class MatButtonFieldComponent {
1187
+ eventBus = inject(EventBus);
1188
+ arrayContext = inject(ARRAY_CONTEXT, { optional: true });
1189
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
1190
+ label = input.required({ ...(ngDevMode ? { debugName: "label" } : {}) });
1191
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}) });
1192
+ hidden = input(false, { ...(ngDevMode ? { debugName: "hidden" } : {}) });
1193
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
1194
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
1195
+ /** Event to dispatch on click. Optional for submit buttons (native form submit handles it). */
1196
+ event = input(undefined, { ...(ngDevMode ? { debugName: "event" } : {}) });
1197
+ eventArgs = input(undefined, { ...(ngDevMode ? { debugName: "eventArgs" } : {}) });
1198
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
1199
+ eventContext = input(undefined, { ...(ngDevMode ? { debugName: "eventContext" } : {}) });
1200
+ /** Resolved button type - defaults to 'button' if not specified in props */
1201
+ buttonType = computed(() => this.props()?.type ?? 'button', { ...(ngDevMode ? { debugName: "buttonType" } : {}) });
1202
+ buttonTestId = computed(() => `${this.buttonType()}-${this.key()}`, { ...(ngDevMode ? { debugName: "buttonTestId" } : {}) });
1203
+ /**
1204
+ * Handle button click.
1205
+ * - For submit buttons (type="submit"): do nothing, native form submit handles it
1206
+ * - For other buttons: dispatch the configured event via EventBus
1207
+ */
1208
+ onClick() {
1209
+ // Native submit buttons let the form handle submission
1210
+ if (this.buttonType() === 'submit') {
1211
+ return;
1212
+ }
1213
+ // Other buttons dispatch their event (if configured)
1214
+ const event = this.event();
1215
+ if (event) {
1216
+ this.dispatchEvent(event);
1217
+ }
1218
+ }
1219
+ dispatchEvent(event) {
1220
+ const args = this.eventArgs();
1221
+ if (args && args.length > 0) {
1222
+ // Build context from injected ARRAY_CONTEXT (with linkedSignal index) or fallback to eventContext
1223
+ const context = this.arrayContext
1224
+ ? {
1225
+ key: this.key(),
1226
+ // Read signal to get current index (automatically updates via linkedSignal)
1227
+ index: this.arrayContext.index(),
1228
+ arrayKey: this.arrayContext.arrayKey,
1229
+ formValue: this.arrayContext.formValue,
1230
+ }
1231
+ : this.eventContext() || { key: this.key() };
1232
+ // Resolve tokens in event args using the provided context
1233
+ const resolvedArgs = resolveTokens(args, context);
1234
+ // Dispatch event with resolved args
1235
+ this.eventBus.dispatch(event, ...resolvedArgs);
1236
+ }
1237
+ else {
1238
+ // No args, dispatch event without arguments
1239
+ this.eventBus.dispatch(event);
1240
+ }
1241
+ }
1242
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatButtonFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1243
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.1", type: MatButtonFieldComponent, isStandalone: true, selector: "df-mat-button", inputs: { key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, event: { classPropertyName: "event", publicName: "event", isSignal: true, isRequired: false, transformFunction: null }, eventArgs: { classPropertyName: "eventArgs", publicName: "eventArgs", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, eventContext: { classPropertyName: "eventContext", publicName: "eventContext", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()" } }, ngImport: i0, template: `
1244
+ <button
1245
+ mat-raised-button
1246
+ [type]="buttonType()"
1247
+ [color]="props()?.color || 'primary'"
1248
+ [disabled]="disabled()"
1249
+ [attr.data-testid]="buttonTestId()"
1250
+ (click)="onClick()"
1251
+ >
1252
+ {{ label() | dynamicText | async }}
1253
+ </button>
1254
+ `, isInline: true, styles: [":host{display:block}button{min-width:fit-content}\n"], dependencies: [{ kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1255
+ }
1256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatButtonFieldComponent, decorators: [{
1257
+ type: Component,
1258
+ args: [{ selector: 'df-mat-button', imports: [MatButton, DynamicTextPipe, AsyncPipe], host: {
1259
+ '[id]': '`${key()}`',
1260
+ '[attr.data-testid]': 'key()',
1261
+ '[class]': 'className()',
1262
+ }, template: `
1263
+ <button
1264
+ mat-raised-button
1265
+ [type]="buttonType()"
1266
+ [color]="props()?.color || 'primary'"
1267
+ [disabled]="disabled()"
1268
+ [attr.data-testid]="buttonTestId()"
1269
+ (click)="onClick()"
1270
+ >
1271
+ {{ label() | dynamicText | async }}
1272
+ </button>
1273
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}button{min-width:fit-content}\n"] }]
1274
+ }], 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 }] }] } });
1275
+
1276
+ var matButton_component = /*#__PURE__*/Object.freeze({
1277
+ __proto__: null,
1278
+ default: MatButtonFieldComponent
1279
+ });
1280
+
1281
+ // Public API - component
1282
+
1283
+ class MatTextareaFieldComponent {
1284
+ materialConfig = inject(MATERIAL_CONFIG, { optional: true });
1285
+ field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}) });
1286
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
1287
+ /**
1288
+ * Reference to the native textarea element.
1289
+ * Used to imperatively sync the readonly attribute since Angular Signal Forms'
1290
+ * [field] directive doesn't sync FieldState.readonly() to the DOM.
1291
+ */
1292
+ textareaRef = viewChild('textareaRef', { ...(ngDevMode ? { debugName: "textareaRef" } : {}) });
1293
+ /**
1294
+ * Computed signal that extracts the readonly state from the field.
1295
+ * Used by the effect to reactively sync the readonly attribute to the DOM.
1296
+ */
1297
+ isReadonly = computed(() => this.field()().readonly(), { ...(ngDevMode ? { debugName: "isReadonly" } : {}) });
1298
+ /**
1299
+ * Workaround: Angular Signal Forms' [field] directive does NOT sync the readonly
1300
+ * attribute to the DOM, even though FieldState.readonly() returns the correct value.
1301
+ * This effect imperatively sets/removes the readonly attribute on the native textarea
1302
+ * element whenever the readonly state changes.
1303
+ *
1304
+ * Note: We cannot use [readonly] or [attr.readonly] bindings because Angular throws
1305
+ * NG8022: "Binding to '[readonly]' is not allowed on nodes using the '[field]' directive"
1306
+ *
1307
+ * @see https://github.com/angular/angular/issues/65897
1308
+ */
1309
+ syncReadonlyToDom = explicitEffect([this.textareaRef, this.isReadonly], ([textareaRef, isReadonly]) => {
1310
+ if (textareaRef?.nativeElement) {
1311
+ if (isReadonly) {
1312
+ textareaRef.nativeElement.setAttribute('readonly', '');
1313
+ }
1314
+ else {
1315
+ textareaRef.nativeElement.removeAttribute('readonly');
1316
+ }
1317
+ }
1318
+ });
1319
+ label = input(undefined, { ...(ngDevMode ? { debugName: "label" } : {}) });
1320
+ placeholder = input(undefined, { ...(ngDevMode ? { debugName: "placeholder" } : {}) });
1321
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
1322
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
1323
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
1324
+ validationMessages = input(undefined, { ...(ngDevMode ? { debugName: "validationMessages" } : {}) });
1325
+ defaultValidationMessages = input(undefined, { ...(ngDevMode ? { debugName: "defaultValidationMessages" } : {}) });
1326
+ effectiveAppearance = computed(() => this.props()?.appearance ?? this.materialConfig?.appearance ?? 'outline', { ...(ngDevMode ? { debugName: "effectiveAppearance" } : {}) });
1327
+ effectiveSubscriptSizing = computed(() => this.props()?.subscriptSizing ?? this.materialConfig?.subscriptSizing ?? 'dynamic', { ...(ngDevMode ? { debugName: "effectiveSubscriptSizing" } : {}) });
1328
+ resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1329
+ showErrors = shouldShowErrors(this.field);
1330
+ errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), { ...(ngDevMode ? { debugName: "errorsToDisplay" } : {}) });
1331
+ // ─────────────────────────────────────────────────────────────────────────────
1332
+ // Accessibility
1333
+ // ─────────────────────────────────────────────────────────────────────────────
1334
+ /** Unique ID for the hint element, used for aria-describedby */
1335
+ hintId = computed(() => `${this.key()}-hint`, { ...(ngDevMode ? { debugName: "hintId" } : {}) });
1336
+ /** Base ID for error elements, used for aria-describedby */
1337
+ errorId = computed(() => `${this.key()}-error`, { ...(ngDevMode ? { debugName: "errorId" } : {}) });
1338
+ /** aria-invalid: true when field is invalid AND touched, false otherwise */
1339
+ ariaInvalid = computed(() => {
1340
+ const fieldState = this.field()();
1341
+ return fieldState.invalid() && fieldState.touched();
1342
+ }, { ...(ngDevMode ? { debugName: "ariaInvalid" } : {}) });
1343
+ /** aria-required: true if field is required, null otherwise (to remove attribute) */
1344
+ ariaRequired = computed(() => {
1345
+ return this.field()().required?.() === true ? true : null;
1346
+ }, { ...(ngDevMode ? { debugName: "ariaRequired" } : {}) });
1347
+ /** aria-describedby: links to hint and error messages for screen readers */
1348
+ ariaDescribedBy = computed(() => {
1349
+ const ids = [];
1350
+ if (this.props()?.hint) {
1351
+ ids.push(this.hintId());
1352
+ }
1353
+ const errors = this.errorsToDisplay();
1354
+ errors.forEach((_, i) => {
1355
+ ids.push(`${this.errorId()}-${i}`);
1356
+ });
1357
+ return ids.length > 0 ? ids.join(' ') : null;
1358
+ }, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : {}) });
1359
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatTextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1360
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MatTextareaFieldComponent, isStandalone: true, selector: "df-mat-textarea", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()", "class": "className()", "attr.hidden": "field()().hidden() || null" } }, viewQueries: [{ propertyName: "textareaRef", first: true, predicate: ["textareaRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
1361
+ @let f = field();
1362
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1363
+ @let ariaDescribedBy = this.ariaDescribedBy();
1364
+
1365
+ <mat-form-field [appearance]="effectiveAppearance()" [subscriptSizing]="effectiveSubscriptSizing()">
1366
+ @if (label()) {
1367
+ <mat-label>{{ label() | dynamicText | async }}</mat-label>
1368
+ }
1369
+
1370
+ <textarea
1371
+ #textareaRef
1372
+ matInput
1373
+ [field]="f"
1374
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1375
+ [rows]="props()?.rows || 4"
1376
+ [cols]="props()?.cols"
1377
+ [attr.tabindex]="tabIndex()"
1378
+ [attr.aria-invalid]="ariaInvalid"
1379
+ [attr.aria-required]="ariaRequired"
1380
+ [attr.aria-describedby]="ariaDescribedBy"
1381
+ [style.resize]="props()?.resize || 'vertical'"
1382
+ ></textarea>
1383
+
1384
+ @if (props()?.hint; as hint) {
1385
+ <mat-hint [id]="hintId()">{{ hint | dynamicText | async }}</mat-hint>
1386
+ }
1387
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1388
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
1389
+ }
1390
+ </mat-form-field>
1391
+ `, isInline: true, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}mat-form-field{width:100%}\n"], dependencies: [{ kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: Field, selector: "[field]", inputs: ["field"] }, { kind: "directive", type: MatError$1, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1392
+ }
1393
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatTextareaFieldComponent, decorators: [{
1394
+ type: Component,
1395
+ args: [{ selector: 'df-mat-textarea', imports: [MatFormField, MatLabel, MatInput, MatHint, Field, MatError$1, DynamicTextPipe, AsyncPipe], template: `
1396
+ @let f = field();
1397
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1398
+ @let ariaDescribedBy = this.ariaDescribedBy();
1399
+
1400
+ <mat-form-field [appearance]="effectiveAppearance()" [subscriptSizing]="effectiveSubscriptSizing()">
1401
+ @if (label()) {
1402
+ <mat-label>{{ label() | dynamicText | async }}</mat-label>
1403
+ }
1404
+
1405
+ <textarea
1406
+ #textareaRef
1407
+ matInput
1408
+ [field]="f"
1409
+ [placeholder]="(placeholder() | dynamicText | async) ?? ''"
1410
+ [rows]="props()?.rows || 4"
1411
+ [cols]="props()?.cols"
1412
+ [attr.tabindex]="tabIndex()"
1413
+ [attr.aria-invalid]="ariaInvalid"
1414
+ [attr.aria-required]="ariaRequired"
1415
+ [attr.aria-describedby]="ariaDescribedBy"
1416
+ [style.resize]="props()?.resize || 'vertical'"
1417
+ ></textarea>
1418
+
1419
+ @if (props()?.hint; as hint) {
1420
+ <mat-hint [id]="hintId()">{{ hint | dynamicText | async }}</mat-hint>
1421
+ }
1422
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1423
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
1424
+ }
1425
+ </mat-form-field>
1426
+ `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
1427
+ '[id]': '`${key()}`',
1428
+ '[attr.data-testid]': 'key()',
1429
+ '[class]': 'className()',
1430
+ '[attr.hidden]': 'field()().hidden() || null',
1431
+ }, styles: [":host{display:block;width:100%}:host([hidden]){display:none!important}mat-form-field{width:100%}\n"] }]
1432
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], textareaRef: [{ type: i0.ViewChild, args: ['textareaRef', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
1433
+
1434
+ var matTextarea_component = /*#__PURE__*/Object.freeze({
1435
+ __proto__: null,
1436
+ default: MatTextareaFieldComponent
1437
+ });
1438
+
1439
+ class MatToggleFieldComponent {
1440
+ materialConfig = inject(MATERIAL_CONFIG, { optional: true });
1441
+ field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}) });
1442
+ key = input.required({ ...(ngDevMode ? { debugName: "key" } : {}) });
1443
+ label = input(undefined, { ...(ngDevMode ? { debugName: "label" } : {}) });
1444
+ placeholder = input(undefined, { ...(ngDevMode ? { debugName: "placeholder" } : {}) });
1445
+ className = input('', { ...(ngDevMode ? { debugName: "className" } : {}) });
1446
+ tabIndex = input(undefined, { ...(ngDevMode ? { debugName: "tabIndex" } : {}) });
1447
+ props = input(undefined, { ...(ngDevMode ? { debugName: "props" } : {}) });
1448
+ validationMessages = input(undefined, { ...(ngDevMode ? { debugName: "validationMessages" } : {}) });
1449
+ defaultValidationMessages = input(undefined, { ...(ngDevMode ? { debugName: "defaultValidationMessages" } : {}) });
1450
+ effectiveDisableRipple = computed(() => this.props()?.disableRipple ?? this.materialConfig?.disableRipple ?? false, { ...(ngDevMode ? { debugName: "effectiveDisableRipple" } : {}) });
1451
+ resolvedErrors = createResolvedErrorsSignal(this.field, this.validationMessages, this.defaultValidationMessages);
1452
+ showErrors = shouldShowErrors(this.field);
1453
+ errorsToDisplay = computed(() => (this.showErrors() ? this.resolvedErrors() : []), { ...(ngDevMode ? { debugName: "errorsToDisplay" } : {}) });
1454
+ // ─────────────────────────────────────────────────────────────────────────────
1455
+ // Accessibility
1456
+ // ─────────────────────────────────────────────────────────────────────────────
1457
+ /** Unique ID for the hint element, used for aria-describedby */
1458
+ hintId = computed(() => `${this.key()}-hint`, { ...(ngDevMode ? { debugName: "hintId" } : {}) });
1459
+ /** Base ID for error elements, used for aria-describedby */
1460
+ errorId = computed(() => `${this.key()}-error`, { ...(ngDevMode ? { debugName: "errorId" } : {}) });
1461
+ /** aria-invalid: true when field is invalid AND touched, false otherwise */
1462
+ ariaInvalid = computed(() => {
1463
+ const fieldState = this.field()();
1464
+ return fieldState.invalid() && fieldState.touched();
1465
+ }, { ...(ngDevMode ? { debugName: "ariaInvalid" } : {}) });
1466
+ /** aria-required: true if field is required, null otherwise (to remove attribute) */
1467
+ ariaRequired = computed(() => {
1468
+ return this.field()().required?.() === true ? true : null;
1469
+ }, { ...(ngDevMode ? { debugName: "ariaRequired" } : {}) });
1470
+ /** aria-describedby: links to hint and error messages for screen readers */
1471
+ ariaDescribedBy = computed(() => {
1472
+ const ids = [];
1473
+ if (this.props()?.hint) {
1474
+ ids.push(this.hintId());
1475
+ }
1476
+ const errors = this.errorsToDisplay();
1477
+ errors.forEach((_, i) => {
1478
+ ids.push(`${this.errorId()}-${i}`);
1479
+ });
1480
+ return ids.join(' ');
1481
+ }, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : {}) });
1482
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatToggleFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1483
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.1", type: MatToggleFieldComponent, isStandalone: true, selector: "df-mat-toggle", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "className()", "id": "`${key()}`", "attr.data-testid": "key()", "attr.hidden": "field()().hidden() || null" } }, ngImport: i0, template: `
1484
+ @let f = field();
1485
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1486
+ @let ariaDescribedBy = this.ariaDescribedBy();
1487
+
1488
+ <mat-slide-toggle
1489
+ [field]="f"
1490
+ [color]="props()?.color || 'primary'"
1491
+ [labelPosition]="props()?.labelPosition || 'after'"
1492
+ [hideIcon]="props()?.hideIcon || false"
1493
+ [disableRipple]="effectiveDisableRipple()"
1494
+ [required]="!!f().required?.()"
1495
+ [aria-describedby]="ariaDescribedBy"
1496
+ [attr.tabindex]="tabIndex()"
1497
+ class="toggle-container"
1498
+ >
1499
+ {{ label() | dynamicText | async }}
1500
+ </mat-slide-toggle>
1501
+
1502
+ @if (props()?.hint; as hint) {
1503
+ <div class="mat-hint" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1504
+ }
1505
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1506
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
1507
+ }
1508
+ `, isInline: true, styles: [":host{display:block}:host([hidden]){display:none!important}\n"], dependencies: [{ kind: "component", type: MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "directive", type: Field, selector: "[field]", inputs: ["field"] }, { kind: "directive", type: MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1509
+ }
1510
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: MatToggleFieldComponent, decorators: [{
1511
+ type: Component,
1512
+ args: [{ selector: 'df-mat-toggle', imports: [MatSlideToggle, Field, MatError, DynamicTextPipe, AsyncPipe], template: `
1513
+ @let f = field();
1514
+ @let ariaInvalid = this.ariaInvalid(); @let ariaRequired = this.ariaRequired();
1515
+ @let ariaDescribedBy = this.ariaDescribedBy();
1516
+
1517
+ <mat-slide-toggle
1518
+ [field]="f"
1519
+ [color]="props()?.color || 'primary'"
1520
+ [labelPosition]="props()?.labelPosition || 'after'"
1521
+ [hideIcon]="props()?.hideIcon || false"
1522
+ [disableRipple]="effectiveDisableRipple()"
1523
+ [required]="!!f().required?.()"
1524
+ [aria-describedby]="ariaDescribedBy"
1525
+ [attr.tabindex]="tabIndex()"
1526
+ class="toggle-container"
1527
+ >
1528
+ {{ label() | dynamicText | async }}
1529
+ </mat-slide-toggle>
1530
+
1531
+ @if (props()?.hint; as hint) {
1532
+ <div class="mat-hint" [id]="hintId()">{{ hint | dynamicText | async }}</div>
1533
+ }
1534
+ @for (error of errorsToDisplay(); track error.kind; let i = $index) {
1535
+ <mat-error [id]="errorId() + '-' + i">{{ error.message }}</mat-error>
1536
+ }
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{display:block}:host([hidden]){display:none!important}\n"] }]
1543
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], tabIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabIndex", required: false }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: false }] }], validationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMessages", required: false }] }], defaultValidationMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValidationMessages", required: false }] }] } });
1544
+
1545
+ var matToggle_component = /*#__PURE__*/Object.freeze({
1546
+ __proto__: null,
1547
+ default: MatToggleFieldComponent
1548
+ });
1549
+
1550
+ // Public exports for material field components and types
1551
+
1552
+ /**
1553
+ * Material Design field type constants
1554
+ * Based on available field components in the /fields folder
1555
+ */
1556
+ const MatField = {
1557
+ Input: 'input',
1558
+ Select: 'select',
1559
+ Checkbox: 'checkbox',
1560
+ Button: 'button',
1561
+ Submit: 'submit',
1562
+ Next: 'next',
1563
+ Previous: 'previous',
1564
+ AddArrayItem: 'addArrayItem',
1565
+ RemoveArrayItem: 'removeArrayItem',
1566
+ Textarea: 'textarea',
1567
+ Radio: 'radio',
1568
+ MultiCheckbox: 'multi-checkbox',
1569
+ Datepicker: 'datepicker',
1570
+ Slider: 'slider',
1571
+ Toggle: 'toggle',
1572
+ };
1573
+
1574
+ /**
1575
+ * Generic button mapper for custom events or basic buttons.
1576
+ * For specific button types (submit, next, prev, add/remove array items),
1577
+ * use the dedicated field types and their specific mappers.
1578
+ *
1579
+ * @param fieldDef The button field definition
1580
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
1581
+ */
1582
+ function buttonFieldMapper(fieldDef) {
1583
+ // Build base inputs (static, from field definition)
1584
+ const baseInputs = buildBaseInputs(fieldDef);
1585
+ return computed(() => {
1586
+ const inputs = {
1587
+ ...baseInputs,
1588
+ };
1589
+ if (fieldDef.disabled !== undefined) {
1590
+ inputs['disabled'] = fieldDef.disabled;
1591
+ }
1592
+ if (fieldDef.hidden !== undefined) {
1593
+ inputs['hidden'] = fieldDef.hidden;
1594
+ }
1595
+ // Add event binding for button events
1596
+ if ('event' in fieldDef && fieldDef.event !== undefined) {
1597
+ inputs['event'] = fieldDef.event;
1598
+ }
1599
+ // Add eventArgs binding if provided
1600
+ if ('eventArgs' in fieldDef && fieldDef.eventArgs !== undefined) {
1601
+ inputs['eventArgs'] = fieldDef.eventArgs;
1602
+ }
1603
+ return inputs;
1604
+ });
1605
+ }
1606
+
1607
+ /**
1608
+ * Mapper for submit button - configures native form submission via type="submit"
1609
+ *
1610
+ * Unlike other buttons, submit buttons don't dispatch events directly.
1611
+ * Instead, they trigger native form submission which the form component
1612
+ * intercepts and dispatches to the EventBus.
1613
+ *
1614
+ * Disabled state is resolved using the button-logic-resolver which considers:
1615
+ * 1. Explicit `disabled: true` on the field definition
1616
+ * 2. Field-level `logic` array (if present, overrides form-level defaults)
1617
+ * 3. Form-level `options.submitButton` defaults (disableWhenInvalid, disableWhileSubmitting)
1618
+ *
1619
+ * @param fieldDef The submit button field definition
1620
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
1621
+ */
1622
+ function submitButtonFieldMapper(fieldDef) {
1623
+ // Inject field signal context to access form state and options
1624
+ const fieldSignalContext = inject(FIELD_SIGNAL_CONTEXT);
1625
+ // Build base inputs (static, from field definition)
1626
+ const baseInputs = buildBaseInputs(fieldDef);
1627
+ // Use button-logic-resolver to compute disabled state
1628
+ const fieldWithLogic = fieldDef;
1629
+ const disabledSignal = resolveSubmitButtonDisabled({
1630
+ form: fieldSignalContext.form,
1631
+ formOptions: fieldSignalContext.formOptions,
1632
+ fieldLogic: fieldWithLogic.logic,
1633
+ explicitlyDisabled: fieldDef.disabled,
1634
+ });
1635
+ // Return computed signal - evaluates disabledSignal inside for reactivity
1636
+ return computed(() => {
1637
+ const inputs = {
1638
+ ...baseInputs,
1639
+ // No event - native form submit handles it via form's onNativeSubmit
1640
+ // Set type="submit" to trigger native form submission
1641
+ props: { ...fieldDef.props, type: 'submit' },
1642
+ // Evaluate the signal inside the computed - component receives plain boolean
1643
+ disabled: disabledSignal(),
1644
+ };
1645
+ if (fieldDef.hidden !== undefined) {
1646
+ inputs['hidden'] = fieldDef.hidden;
1647
+ }
1648
+ return inputs;
1649
+ });
1650
+ }
1651
+ /**
1652
+ * Mapper for next page button - preconfigures NextPageEvent
1653
+ *
1654
+ * Disabled state is resolved using the button-logic-resolver which considers:
1655
+ * 1. Explicit `disabled: true` on the field definition
1656
+ * 2. Field-level `logic` array (if present, overrides form-level defaults)
1657
+ * 3. Form-level `options.nextButton` defaults (disableWhenPageInvalid, disableWhileSubmitting)
1658
+ *
1659
+ * @param fieldDef The next button field definition
1660
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
1661
+ */
1662
+ function nextButtonFieldMapper(fieldDef) {
1663
+ // Inject field signal context to access form state and options
1664
+ const fieldSignalContext = inject(FIELD_SIGNAL_CONTEXT);
1665
+ // Build base inputs (static, from field definition)
1666
+ const baseInputs = buildBaseInputs(fieldDef);
1667
+ // Use button-logic-resolver to compute disabled state
1668
+ const fieldWithLogic = fieldDef;
1669
+ const disabledSignal = resolveNextButtonDisabled({
1670
+ form: fieldSignalContext.form,
1671
+ formOptions: fieldSignalContext.formOptions,
1672
+ fieldLogic: fieldWithLogic.logic,
1673
+ explicitlyDisabled: fieldDef.disabled,
1674
+ currentPageValid: fieldSignalContext.currentPageValid,
1675
+ });
1676
+ // Return computed signal - evaluates disabledSignal inside for reactivity
1677
+ return computed(() => {
1678
+ const inputs = {
1679
+ ...baseInputs,
1680
+ event: NextPageEvent,
1681
+ // Evaluate the signal inside the computed - component receives plain boolean
1682
+ disabled: disabledSignal(),
1683
+ };
1684
+ if (fieldDef.hidden !== undefined) {
1685
+ inputs['hidden'] = fieldDef.hidden;
1686
+ }
1687
+ return inputs;
1688
+ });
1689
+ }
1690
+ /**
1691
+ * Mapper for previous page button - preconfigures PreviousPageEvent
1692
+ * Note: Does not auto-disable based on validation. Users can explicitly disable if needed.
1693
+ *
1694
+ * @param fieldDef The previous button field definition
1695
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
1696
+ */
1697
+ function previousButtonFieldMapper(fieldDef) {
1698
+ // Build base inputs (static, from field definition)
1699
+ const baseInputs = buildBaseInputs(fieldDef);
1700
+ return computed(() => {
1701
+ const inputs = {
1702
+ ...baseInputs,
1703
+ event: PreviousPageEvent,
1704
+ };
1705
+ // Add disabled binding only if explicitly set by user
1706
+ if (fieldDef.disabled !== undefined) {
1707
+ inputs['disabled'] = fieldDef.disabled;
1708
+ }
1709
+ if (fieldDef.hidden !== undefined) {
1710
+ inputs['hidden'] = fieldDef.hidden;
1711
+ }
1712
+ return inputs;
1713
+ });
1714
+ }
1715
+ /**
1716
+ * Mapper for add array item button - preconfigures AddArrayItemEvent with array context.
1717
+ *
1718
+ * Supports two modes:
1719
+ * 1. Inside array template: Uses ARRAY_CONTEXT to determine target array
1720
+ * 2. Outside array: Uses `arrayKey` property from field definition
1721
+ *
1722
+ * @param fieldDef The add array item button field definition
1723
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
1724
+ */
1725
+ function addArrayItemButtonFieldMapper(fieldDef) {
1726
+ // Try to get array context (available when inside an array)
1727
+ // Use optional injection so it doesn't fail when outside an array
1728
+ const arrayContext = inject(ARRAY_CONTEXT, { optional: true });
1729
+ // Determine the target array key
1730
+ // Priority: explicit arrayKey from fieldDef > arrayKey from context
1731
+ const targetArrayKey = fieldDef.arrayKey ?? arrayContext?.arrayKey;
1732
+ if (!targetArrayKey) {
1733
+ console.warn(`[Dynamic Forms] addArrayItem button "${fieldDef.key}" has no array context. ` +
1734
+ 'Either place it inside an array field, or provide an explicit arrayKey property.');
1735
+ }
1736
+ // Build base inputs (static, from field definition)
1737
+ const baseInputs = buildBaseInputs(fieldDef);
1738
+ // Set default eventArgs for AddArrayItemEvent (arrayKey)
1739
+ // User can override by providing eventArgs in field definition
1740
+ const defaultEventArgs = ['$arrayKey'];
1741
+ const eventArgs = 'eventArgs' in fieldDef && fieldDef.eventArgs !== undefined ? fieldDef.eventArgs : defaultEventArgs;
1742
+ return computed(() => {
1743
+ // Read signal value if index is a signal (supports differential updates)
1744
+ const getIndex = () => {
1745
+ if (!arrayContext)
1746
+ return -1;
1747
+ return isSignal(arrayContext.index) ? arrayContext.index() : arrayContext.index;
1748
+ };
1749
+ const inputs = {
1750
+ ...baseInputs,
1751
+ event: AddArrayItemEvent,
1752
+ eventArgs,
1753
+ eventContext: {
1754
+ key: fieldDef.key,
1755
+ index: getIndex(),
1756
+ arrayKey: targetArrayKey ?? '',
1757
+ formValue: arrayContext?.formValue ?? {},
1758
+ },
1759
+ };
1760
+ // Add disabled binding only if explicitly set by user
1761
+ if (fieldDef.disabled !== undefined) {
1762
+ inputs['disabled'] = fieldDef.disabled;
1763
+ }
1764
+ if (fieldDef.hidden !== undefined) {
1765
+ inputs['hidden'] = fieldDef.hidden;
1766
+ }
1767
+ return inputs;
1768
+ });
1769
+ }
1770
+ /**
1771
+ * Mapper for remove array item button - preconfigures RemoveArrayItemEvent with array context.
1772
+ *
1773
+ * Supports two modes:
1774
+ * 1. Inside array template: Uses ARRAY_CONTEXT to determine target array and removes item at current index
1775
+ * 2. Outside array: Uses `arrayKey` property from field definition, removes last item by default
1776
+ *
1777
+ * @param fieldDef The remove array item button field definition
1778
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
1779
+ */
1780
+ function removeArrayItemButtonFieldMapper(fieldDef) {
1781
+ // Try to get array context (available when inside an array)
1782
+ // Use optional injection so it doesn't fail when outside an array
1783
+ const arrayContext = inject(ARRAY_CONTEXT, { optional: true });
1784
+ // Determine the target array key
1785
+ // Priority: explicit arrayKey from fieldDef > arrayKey from context
1786
+ const targetArrayKey = fieldDef.arrayKey ?? arrayContext?.arrayKey;
1787
+ if (!targetArrayKey) {
1788
+ console.warn(`[Dynamic Forms] removeArrayItem button "${fieldDef.key}" has no array context. ` +
1789
+ 'Either place it inside an array field, or provide an explicit arrayKey property.');
1790
+ }
1791
+ // Build base inputs (static, from field definition)
1792
+ const baseInputs = buildBaseInputs(fieldDef);
1793
+ // Set default eventArgs for RemoveArrayItemEvent (arrayKey, index if inside array)
1794
+ // When outside array, only pass arrayKey (removes last by default)
1795
+ // User can override by providing eventArgs in field definition
1796
+ const defaultEventArgs = arrayContext ? ['$arrayKey', '$index'] : ['$arrayKey'];
1797
+ const eventArgs = 'eventArgs' in fieldDef && fieldDef.eventArgs !== undefined ? fieldDef.eventArgs : defaultEventArgs;
1798
+ return computed(() => {
1799
+ // Read signal value if index is a signal (supports differential updates)
1800
+ const getIndex = () => {
1801
+ if (!arrayContext)
1802
+ return -1; // -1 means remove last (no specific index)
1803
+ return isSignal(arrayContext.index) ? arrayContext.index() : arrayContext.index;
1804
+ };
1805
+ const inputs = {
1806
+ ...baseInputs,
1807
+ event: RemoveArrayItemEvent,
1808
+ eventArgs,
1809
+ eventContext: {
1810
+ key: fieldDef.key,
1811
+ index: getIndex(),
1812
+ arrayKey: targetArrayKey ?? '',
1813
+ formValue: arrayContext?.formValue ?? {},
1814
+ },
1815
+ };
1816
+ // Add disabled binding only if explicitly set by user
1817
+ if (fieldDef.disabled !== undefined) {
1818
+ inputs['disabled'] = fieldDef.disabled;
1819
+ }
1820
+ if (fieldDef.hidden !== undefined) {
1821
+ inputs['hidden'] = fieldDef.hidden;
1822
+ }
1823
+ return inputs;
1824
+ });
1825
+ }
1826
+
1827
+ /**
1828
+ * Material Design field type definitions
1829
+ * Follows the FieldTypeDefinition interface for proper registry integration
1830
+ */
1831
+ const MATERIAL_FIELD_TYPES = [
1832
+ {
1833
+ name: MatField.Input,
1834
+ loadComponent: () => Promise.resolve().then(function () { return matInput_component; }),
1835
+ mapper: valueFieldMapper,
1836
+ },
1837
+ {
1838
+ name: MatField.Select,
1839
+ loadComponent: () => Promise.resolve().then(function () { return matSelect_component; }),
1840
+ mapper: optionsFieldMapper,
1841
+ },
1842
+ {
1843
+ name: MatField.Checkbox,
1844
+ loadComponent: () => Promise.resolve().then(function () { return matCheckbox_component; }),
1845
+ mapper: checkboxFieldMapper,
1846
+ },
1847
+ {
1848
+ name: MatField.Button,
1849
+ loadComponent: () => Promise.resolve().then(function () { return matButton_component; }),
1850
+ mapper: buttonFieldMapper,
1851
+ valueHandling: 'exclude',
1852
+ },
1853
+ {
1854
+ name: MatField.Submit,
1855
+ loadComponent: () => Promise.resolve().then(function () { return matButton_component; }),
1856
+ mapper: submitButtonFieldMapper,
1857
+ valueHandling: 'exclude',
1858
+ },
1859
+ {
1860
+ name: MatField.Next,
1861
+ loadComponent: () => Promise.resolve().then(function () { return matButton_component; }),
1862
+ mapper: nextButtonFieldMapper,
1863
+ valueHandling: 'exclude',
1864
+ },
1865
+ {
1866
+ name: MatField.Previous,
1867
+ loadComponent: () => Promise.resolve().then(function () { return matButton_component; }),
1868
+ mapper: previousButtonFieldMapper,
1869
+ valueHandling: 'exclude',
1870
+ },
1871
+ {
1872
+ name: MatField.AddArrayItem,
1873
+ loadComponent: () => Promise.resolve().then(function () { return matButton_component; }),
1874
+ mapper: addArrayItemButtonFieldMapper,
1875
+ valueHandling: 'exclude',
1876
+ },
1877
+ {
1878
+ name: MatField.RemoveArrayItem,
1879
+ loadComponent: () => Promise.resolve().then(function () { return matButton_component; }),
1880
+ mapper: removeArrayItemButtonFieldMapper,
1881
+ valueHandling: 'exclude',
1882
+ },
1883
+ {
1884
+ name: MatField.Textarea,
1885
+ loadComponent: () => Promise.resolve().then(function () { return matTextarea_component; }),
1886
+ mapper: valueFieldMapper,
1887
+ },
1888
+ {
1889
+ name: MatField.Radio,
1890
+ loadComponent: () => Promise.resolve().then(function () { return matRadio_component; }),
1891
+ mapper: optionsFieldMapper,
1892
+ },
1893
+ {
1894
+ name: MatField.MultiCheckbox,
1895
+ loadComponent: () => Promise.resolve().then(function () { return matMultiCheckbox_component; }),
1896
+ mapper: optionsFieldMapper,
1897
+ },
1898
+ {
1899
+ name: MatField.Datepicker,
1900
+ loadComponent: () => Promise.resolve().then(function () { return matDatepicker_component; }),
1901
+ mapper: datepickerFieldMapper,
1902
+ },
1903
+ {
1904
+ name: MatField.Slider,
1905
+ loadComponent: () => Promise.resolve().then(function () { return matSlider_component; }),
1906
+ mapper: valueFieldMapper,
1907
+ },
1908
+ {
1909
+ name: MatField.Toggle,
1910
+ loadComponent: () => Promise.resolve().then(function () { return matToggle_component; }),
1911
+ mapper: checkboxFieldMapper,
1912
+ },
1913
+ ];
1914
+
1915
+ /**
1916
+ * Module augmentation for @ng-forge/dynamic-form
1917
+ * This file augments the FieldRegistryLeaves interface to include
1918
+ * all Material Design field types provided by this library.
1919
+ */
1920
+
1921
+ /**
1922
+ * Configure dynamic forms with Material Design field types.
1923
+ * Provides all Material Design field types for use with provideDynamicForm.
1924
+ *
1925
+ * @param config - Optional global configuration for Material form fields
1926
+ *
1927
+ * @example
1928
+ * ```typescript
1929
+ * // Application-level setup
1930
+ * import { ApplicationConfig } from '@angular/core';
1931
+ * import { provideDynamicForm } from '@ng-forge/dynamic-form';
1932
+ * import { withMaterialFields } from '@ng-forge/dynamic-form-material';
1933
+ *
1934
+ * export const appConfig: ApplicationConfig = {
1935
+ * providers: [
1936
+ * provideDynamicForm(...withMaterialFields())
1937
+ * ]
1938
+ * };
1939
+ * ```
1940
+ *
1941
+ * @example
1942
+ * ```typescript
1943
+ * // With global configuration
1944
+ * export const appConfig: ApplicationConfig = {
1945
+ * providers: [
1946
+ * provideDynamicForm(
1947
+ * ...withMaterialFields({
1948
+ * appearance: 'fill',
1949
+ * subscriptSizing: 'fixed'
1950
+ * })
1951
+ * )
1952
+ * ]
1953
+ * };
1954
+ * ```
1955
+ *
1956
+ * @returns Array of field type definitions for Material Design components
1957
+ */
1958
+ function withMaterialFields(config) {
1959
+ const fields = MATERIAL_FIELD_TYPES;
1960
+ if (config) {
1961
+ fields.__configProviders = [
1962
+ {
1963
+ provide: MATERIAL_CONFIG,
1964
+ useValue: config,
1965
+ },
1966
+ ];
1967
+ }
1968
+ return fields;
1969
+ }
1970
+
1971
+ // Field components
1972
+
1973
+ /**
1974
+ * Generated bundle index. Do not edit.
1975
+ */
1976
+
1977
+ export { MATERIAL_CONFIG, MATERIAL_FIELD_TYPES, MatButtonFieldComponent, MatCheckboxFieldComponent, MatDatepickerFieldComponent, MatField, MatInputFieldComponent, MatMultiCheckboxFieldComponent, MatRadioFieldComponent, MatSelectFieldComponent, MatSliderFieldComponent, MatTextareaFieldComponent, MatToggleFieldComponent, withMaterialFields };
1978
+ //# sourceMappingURL=ng-forge-dynamic-forms-material.mjs.map