ngx-com 0.0.1 → 0.0.4

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.
Files changed (82) hide show
  1. package/fesm2022/ngx-com-components-avatar.mjs +772 -0
  2. package/fesm2022/ngx-com-components-avatar.mjs.map +1 -0
  3. package/fesm2022/ngx-com-components-badge.mjs +138 -0
  4. package/fesm2022/ngx-com-components-badge.mjs.map +1 -0
  5. package/fesm2022/ngx-com-components-button.mjs +146 -0
  6. package/fesm2022/ngx-com-components-button.mjs.map +1 -0
  7. package/fesm2022/ngx-com-components-calendar.mjs +5046 -0
  8. package/fesm2022/ngx-com-components-calendar.mjs.map +1 -0
  9. package/fesm2022/ngx-com-components-card.mjs +590 -0
  10. package/fesm2022/ngx-com-components-card.mjs.map +1 -0
  11. package/fesm2022/ngx-com-components-checkbox.mjs +344 -0
  12. package/fesm2022/ngx-com-components-checkbox.mjs.map +1 -0
  13. package/fesm2022/ngx-com-components-collapsible.mjs +612 -0
  14. package/fesm2022/ngx-com-components-collapsible.mjs.map +1 -0
  15. package/fesm2022/ngx-com-components-confirm.mjs +562 -0
  16. package/fesm2022/ngx-com-components-confirm.mjs.map +1 -0
  17. package/fesm2022/ngx-com-components-dropdown-testing.mjs +255 -0
  18. package/fesm2022/ngx-com-components-dropdown-testing.mjs.map +1 -0
  19. package/fesm2022/ngx-com-components-dropdown.mjs +2692 -0
  20. package/fesm2022/ngx-com-components-dropdown.mjs.map +1 -0
  21. package/fesm2022/ngx-com-components-empty-state.mjs +382 -0
  22. package/fesm2022/ngx-com-components-empty-state.mjs.map +1 -0
  23. package/fesm2022/ngx-com-components-form-field.mjs +924 -0
  24. package/fesm2022/ngx-com-components-form-field.mjs.map +1 -0
  25. package/fesm2022/ngx-com-components-icon.mjs +183 -0
  26. package/fesm2022/ngx-com-components-icon.mjs.map +1 -0
  27. package/fesm2022/ngx-com-components-item.mjs +578 -0
  28. package/fesm2022/ngx-com-components-item.mjs.map +1 -0
  29. package/fesm2022/ngx-com-components-menu.mjs +1200 -0
  30. package/fesm2022/ngx-com-components-menu.mjs.map +1 -0
  31. package/fesm2022/ngx-com-components-paginator.mjs +823 -0
  32. package/fesm2022/ngx-com-components-paginator.mjs.map +1 -0
  33. package/fesm2022/ngx-com-components-popover.mjs +901 -0
  34. package/fesm2022/ngx-com-components-popover.mjs.map +1 -0
  35. package/fesm2022/ngx-com-components-radio.mjs +621 -0
  36. package/fesm2022/ngx-com-components-radio.mjs.map +1 -0
  37. package/fesm2022/ngx-com-components-segmented-control.mjs +538 -0
  38. package/fesm2022/ngx-com-components-segmented-control.mjs.map +1 -0
  39. package/fesm2022/ngx-com-components-sort.mjs +368 -0
  40. package/fesm2022/ngx-com-components-sort.mjs.map +1 -0
  41. package/fesm2022/ngx-com-components-spinner.mjs +189 -0
  42. package/fesm2022/ngx-com-components-spinner.mjs.map +1 -0
  43. package/fesm2022/ngx-com-components-tabs.mjs +1522 -0
  44. package/fesm2022/ngx-com-components-tabs.mjs.map +1 -0
  45. package/fesm2022/ngx-com-components-tooltip.mjs +625 -0
  46. package/fesm2022/ngx-com-components-tooltip.mjs.map +1 -0
  47. package/fesm2022/ngx-com-components.mjs +17 -0
  48. package/fesm2022/ngx-com-components.mjs.map +1 -0
  49. package/fesm2022/ngx-com-tokens.mjs +12 -0
  50. package/fesm2022/ngx-com-tokens.mjs.map +1 -0
  51. package/fesm2022/ngx-com-utils.mjs +601 -0
  52. package/fesm2022/ngx-com-utils.mjs.map +1 -0
  53. package/fesm2022/ngx-com.mjs +9 -23
  54. package/fesm2022/ngx-com.mjs.map +1 -1
  55. package/package.json +105 -1
  56. package/types/ngx-com-components-avatar.d.ts +409 -0
  57. package/types/ngx-com-components-badge.d.ts +97 -0
  58. package/types/ngx-com-components-button.d.ts +69 -0
  59. package/types/ngx-com-components-calendar.d.ts +1665 -0
  60. package/types/ngx-com-components-card.d.ts +373 -0
  61. package/types/ngx-com-components-checkbox.d.ts +116 -0
  62. package/types/ngx-com-components-collapsible.d.ts +379 -0
  63. package/types/ngx-com-components-confirm.d.ts +160 -0
  64. package/types/ngx-com-components-dropdown-testing.d.ts +116 -0
  65. package/types/ngx-com-components-dropdown.d.ts +938 -0
  66. package/types/ngx-com-components-empty-state.d.ts +269 -0
  67. package/types/ngx-com-components-form-field.d.ts +531 -0
  68. package/types/ngx-com-components-icon.d.ts +94 -0
  69. package/types/ngx-com-components-item.d.ts +336 -0
  70. package/types/ngx-com-components-menu.d.ts +479 -0
  71. package/types/ngx-com-components-paginator.d.ts +265 -0
  72. package/types/ngx-com-components-popover.d.ts +309 -0
  73. package/types/ngx-com-components-radio.d.ts +258 -0
  74. package/types/ngx-com-components-segmented-control.d.ts +274 -0
  75. package/types/ngx-com-components-sort.d.ts +133 -0
  76. package/types/ngx-com-components-spinner.d.ts +120 -0
  77. package/types/ngx-com-components-tabs.d.ts +396 -0
  78. package/types/ngx-com-components-tooltip.d.ts +200 -0
  79. package/types/ngx-com-components.d.ts +12 -0
  80. package/types/ngx-com-tokens.d.ts +7 -0
  81. package/types/ngx-com-utils.d.ts +424 -0
  82. package/types/ngx-com.d.ts +10 -7
@@ -0,0 +1,924 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, Directive, input, computed, Injectable, inject, ElementRef, DestroyRef, booleanAttribute, forwardRef, InjectionToken, contentChild, contentChildren, effect, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { cva } from 'class-variance-authority';
4
+ import { mergeClasses } from 'ngx-com/utils';
5
+ import { NgForm, FormGroupDirective, NgControl } from '@angular/forms';
6
+ import { AutofillMonitor } from '@angular/cdk/text-field';
7
+
8
+ /**
9
+ * Contract that any control inside a form field must implement.
10
+ *
11
+ * This allows the form field to read state from any inner control
12
+ * (input, textarea, custom controls) without knowing implementation details.
13
+ *
14
+ * @example Implementing for a custom phone input
15
+ * ```ts
16
+ * @Directive({
17
+ * selector: 'com-phone-input',
18
+ * providers: [{ provide: FormFieldControl, useExisting: PhoneInputComponent }],
19
+ * })
20
+ * export class PhoneInputComponent extends FormFieldControl<string> {
21
+ * // ... implement all abstract members
22
+ * }
23
+ * ```
24
+ */
25
+ class FormFieldControl {
26
+ }
27
+
28
+ /** Auto-incrementing ID counter. */
29
+ let nextId$3 = 0;
30
+ /**
31
+ * Directive to mark the label element inside a form field.
32
+ *
33
+ * The form field automatically associates this label with the inner control
34
+ * and positions it appropriately based on appearance and float state.
35
+ *
36
+ * @example
37
+ * ```html
38
+ * <com-form-field>
39
+ * <label comLabel>Email address</label>
40
+ * <input comInput formControlName="email" />
41
+ * </com-form-field>
42
+ * ```
43
+ */
44
+ class ComLabel {
45
+ labelId = `com-label-${nextId$3++}`;
46
+ _forId = signal(null, ...(ngDevMode ? [{ debugName: "_forId" }] : []));
47
+ forId = this._forId.asReadonly();
48
+ /** Sets the `for` attribute to link to the control. Called by form field. */
49
+ setForId(id) {
50
+ this._forId.set(id);
51
+ }
52
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
53
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: ComLabel, isStandalone: true, selector: "[comLabel]", host: { properties: { "attr.for": "forId()", "id": "labelId" } }, exportAs: ["comLabel"], ngImport: i0 });
54
+ }
55
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComLabel, decorators: [{
56
+ type: Directive,
57
+ args: [{
58
+ selector: '[comLabel]',
59
+ exportAs: 'comLabel',
60
+ host: {
61
+ '[attr.for]': 'forId()',
62
+ '[id]': 'labelId',
63
+ },
64
+ }]
65
+ }] });
66
+
67
+ /**
68
+ * CVA variants for the form field wrapper.
69
+ *
70
+ * @tokens `--color-foreground`
71
+ */
72
+ const formFieldVariants = cva(['com-form-field', 'relative block w-full', 'text-foreground'], {
73
+ variants: {
74
+ disabled: {
75
+ true: 'pointer-events-none',
76
+ false: '',
77
+ },
78
+ },
79
+ defaultVariants: {
80
+ disabled: false,
81
+ },
82
+ });
83
+ /**
84
+ * CVA variants for the input container (the bordered/filled area).
85
+ *
86
+ * @tokens `--color-input-border`, `--color-input-background`, `--color-primary`,
87
+ * `--color-accent`, `--color-warn`, `--color-ring`, `--color-muted`,
88
+ * `--color-disabled`, `--radius-input`
89
+ */
90
+ const formFieldContainerVariants = cva([
91
+ 'com-form-field__container',
92
+ 'relative flex items-center',
93
+ 'w-full min-h-11',
94
+ 'transition-all duration-150',
95
+ 'rounded-input',
96
+ ], {
97
+ variants: {
98
+ appearance: {
99
+ outline: [
100
+ 'border border-input-border',
101
+ 'bg-transparent',
102
+ ],
103
+ fill: [
104
+ 'rounded-b-none',
105
+ 'bg-muted',
106
+ 'border-b-2 border-input-border',
107
+ ],
108
+ },
109
+ color: {
110
+ primary: '',
111
+ accent: '',
112
+ warn: '',
113
+ },
114
+ focused: {
115
+ true: '',
116
+ false: '',
117
+ },
118
+ error: {
119
+ true: '',
120
+ false: '',
121
+ },
122
+ disabled: {
123
+ true: 'bg-disabled border-disabled',
124
+ false: '',
125
+ },
126
+ },
127
+ compoundVariants: [
128
+ // Outline + focused (no error)
129
+ { appearance: 'outline', focused: true, error: false, class: 'border-primary ring-1 ring-primary' },
130
+ { appearance: 'outline', focused: true, error: false, color: 'accent', class: 'border-accent ring-accent' },
131
+ { appearance: 'outline', focused: true, error: false, color: 'warn', class: 'border-warn ring-warn' },
132
+ // Outline + error
133
+ { appearance: 'outline', error: true, class: 'border-warn ring-1 ring-warn' },
134
+ // Fill + focused (no error)
135
+ { appearance: 'fill', focused: true, error: false, class: 'border-b-primary' },
136
+ { appearance: 'fill', focused: true, error: false, color: 'accent', class: 'border-b-accent' },
137
+ { appearance: 'fill', focused: true, error: false, color: 'warn', class: 'border-b-warn' },
138
+ // Fill + error
139
+ { appearance: 'fill', error: true, class: 'border-b-warn' },
140
+ ],
141
+ defaultVariants: {
142
+ appearance: 'outline',
143
+ color: 'primary',
144
+ focused: false,
145
+ error: false,
146
+ disabled: false,
147
+ },
148
+ });
149
+ /**
150
+ * CVA variants for the floating label.
151
+ *
152
+ * @tokens `--color-muted-foreground`, `--color-primary`, `--color-accent`,
153
+ * `--color-warn`, `--color-background`
154
+ */
155
+ const formFieldLabelVariants = cva([
156
+ 'com-form-field__label',
157
+ 'pointer-events-none',
158
+ 'text-muted-foreground text-sm',
159
+ 'origin-top-left',
160
+ 'transition-all duration-200 ease-in-out',
161
+ ], {
162
+ variants: {
163
+ appearance: {
164
+ outline: '',
165
+ fill: '',
166
+ },
167
+ floating: {
168
+ // Floating: absolute positioning at container border
169
+ true: 'absolute',
170
+ // Not floating: relative positioning in flex flow (after prefix)
171
+ false: 'relative flex-shrink-0 pl-3',
172
+ },
173
+ color: {
174
+ primary: '',
175
+ accent: '',
176
+ warn: '',
177
+ },
178
+ error: {
179
+ true: 'text-warn',
180
+ false: '',
181
+ },
182
+ focused: {
183
+ true: '',
184
+ false: '',
185
+ },
186
+ disabled: {
187
+ true: 'text-disabled-foreground',
188
+ false: '',
189
+ },
190
+ },
191
+ compoundVariants: [
192
+ // Outline floating (label above border with notch effect)
193
+ {
194
+ appearance: 'outline',
195
+ floating: true,
196
+ class: 'left-2 -top-2.5 scale-75 bg-background px-1',
197
+ },
198
+ // Fill floating
199
+ {
200
+ appearance: 'fill',
201
+ floating: true,
202
+ class: 'left-3 top-1 scale-75 text-xs',
203
+ },
204
+ // Focused + floating colors (no error)
205
+ { floating: true, focused: true, error: false, color: 'primary', class: 'text-primary' },
206
+ { floating: true, focused: true, error: false, color: 'accent', class: 'text-accent' },
207
+ { floating: true, focused: true, error: false, color: 'warn', class: 'text-warn' },
208
+ // Error overrides
209
+ { floating: true, error: true, class: 'text-warn' },
210
+ ],
211
+ defaultVariants: {
212
+ appearance: 'outline',
213
+ floating: false,
214
+ color: 'primary',
215
+ error: false,
216
+ focused: false,
217
+ disabled: false,
218
+ },
219
+ });
220
+ /**
221
+ * CVA variants for the subscript area (hints/errors).
222
+ *
223
+ * @tokens (inherits from children)
224
+ */
225
+ const formFieldSubscriptVariants = cva(['com-form-field__subscript', 'text-xs mt-1 px-3'], {
226
+ variants: {
227
+ sizing: {
228
+ fixed: 'min-h-5',
229
+ dynamic: 'min-h-0',
230
+ },
231
+ },
232
+ defaultVariants: {
233
+ sizing: 'fixed',
234
+ },
235
+ });
236
+ /**
237
+ * CVA variants for hint text.
238
+ *
239
+ * @tokens `--color-muted-foreground`
240
+ */
241
+ const hintVariants = cva(['com-form-field__hint', 'text-muted-foreground text-xs']);
242
+ /**
243
+ * CVA variants for error messages.
244
+ *
245
+ * @tokens `--color-warn`
246
+ */
247
+ const errorVariants = cva(['com-form-field__error', 'text-warn text-xs']);
248
+ /**
249
+ * CVA variants for prefix slot.
250
+ *
251
+ * @tokens `--color-muted-foreground`
252
+ */
253
+ const prefixVariants = cva([
254
+ 'com-form-field__prefix',
255
+ 'flex items-center text-muted-foreground pl-3',
256
+ ]);
257
+ /**
258
+ * CVA variants for suffix slot.
259
+ *
260
+ * @tokens `--color-muted-foreground`
261
+ */
262
+ const suffixVariants = cva([
263
+ 'com-form-field__suffix',
264
+ 'flex items-center text-muted-foreground pr-3',
265
+ ]);
266
+
267
+ /** Auto-incrementing ID counter. */
268
+ let nextId$2 = 0;
269
+ /**
270
+ * Directive for hint text displayed below the form field.
271
+ *
272
+ * The hint provides supplementary information to help users fill out the field.
273
+ * It is automatically added to the control's `aria-describedby`.
274
+ *
275
+ * @tokens `--color-muted-foreground`
276
+ *
277
+ * @example Basic hint
278
+ * ```html
279
+ * <com-form-field>
280
+ * <label comLabel>Password</label>
281
+ * <input comInput formControlName="password" />
282
+ * <span comHint>At least 8 characters</span>
283
+ * </com-form-field>
284
+ * ```
285
+ *
286
+ * @example Right-aligned hint (e.g., character count)
287
+ * ```html
288
+ * <com-form-field>
289
+ * <label comLabel>Bio</label>
290
+ * <textarea comInput formControlName="bio"></textarea>
291
+ * <span comHint>Keep it brief</span>
292
+ * <span comHint align="end">{{ bioLength }}/150</span>
293
+ * </com-form-field>
294
+ * ```
295
+ */
296
+ class ComHint {
297
+ id = `com-hint-${nextId$2++}`;
298
+ align = input('start', ...(ngDevMode ? [{ debugName: "align" }] : []));
299
+ hostClasses = computed(() => mergeClasses(hintVariants(), this.align() === 'end' && 'ml-auto'), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
300
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComHint, deps: [], target: i0.ɵɵFactoryTarget.Directive });
301
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.0", type: ComHint, isStandalone: true, selector: "[comHint]", inputs: { align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "id", "class": "hostClasses()" } }, exportAs: ["comHint"], ngImport: i0 });
302
+ }
303
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComHint, decorators: [{
304
+ type: Directive,
305
+ args: [{
306
+ selector: '[comHint]',
307
+ exportAs: 'comHint',
308
+ host: {
309
+ '[id]': 'id',
310
+ '[class]': 'hostClasses()',
311
+ },
312
+ }]
313
+ }], propDecorators: { align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }] } });
314
+
315
+ /** Auto-incrementing ID counter. */
316
+ let nextId$1 = 0;
317
+ /**
318
+ * Directive for error messages displayed below the form field.
319
+ *
320
+ * Errors replace hints when the control is in an error state.
321
+ * Uses `role="alert"` and `aria-live="polite"` for screen reader announcement.
322
+ *
323
+ * The optional `match` input allows showing errors only for specific validation errors.
324
+ *
325
+ * @tokens `--color-warn`
326
+ *
327
+ * @example Basic error
328
+ * ```html
329
+ * <com-form-field>
330
+ * <label comLabel>Email</label>
331
+ * <input comInput formControlName="email" />
332
+ * <span comError>Please enter a valid email</span>
333
+ * </com-form-field>
334
+ * ```
335
+ *
336
+ * @example Matching specific errors
337
+ * ```html
338
+ * <com-form-field>
339
+ * <label comLabel>Email</label>
340
+ * <input comInput formControlName="email" />
341
+ * <span comError match="required">Email is required.</span>
342
+ * <span comError match="email">Must be a valid email address.</span>
343
+ * </com-form-field>
344
+ * ```
345
+ */
346
+ class ComError {
347
+ id = `com-error-${nextId$1++}`;
348
+ /** Reference to the form control, set by the parent form field. */
349
+ _control = signal(null, ...(ngDevMode ? [{ debugName: "_control" }] : []));
350
+ /**
351
+ * Show this error only when a specific validation error key is present.
352
+ * If empty, the error is always shown when the control is in error state.
353
+ */
354
+ match = input('', ...(ngDevMode ? [{ debugName: "match" }] : []));
355
+ errorVariants = errorVariants;
356
+ /**
357
+ * Whether this error should be displayed based on the match condition.
358
+ * Used by the form field to filter which errors to show.
359
+ */
360
+ shouldShow = computed(() => {
361
+ const matchKey = this.match();
362
+ if (!matchKey)
363
+ return true;
364
+ const errors = this._control()?.errors;
365
+ return errors ? matchKey in errors : false;
366
+ }, ...(ngDevMode ? [{ debugName: "shouldShow" }] : []));
367
+ /**
368
+ * Sets the form control reference.
369
+ * Called by the parent form field component.
370
+ */
371
+ setControl(control) {
372
+ this._control.set(control);
373
+ }
374
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComError, deps: [], target: i0.ɵɵFactoryTarget.Directive });
375
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.0", type: ComError, isStandalone: true, selector: "[comError]", inputs: { match: { classPropertyName: "match", publicName: "match", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "alert", "aria-live": "polite" }, properties: { "id": "id", "class": "errorVariants()", "class.hidden": "!shouldShow()" } }, exportAs: ["comError"], ngImport: i0 });
376
+ }
377
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComError, decorators: [{
378
+ type: Directive,
379
+ args: [{
380
+ selector: '[comError]',
381
+ exportAs: 'comError',
382
+ host: {
383
+ '[id]': 'id',
384
+ '[class]': 'errorVariants()',
385
+ '[class.hidden]': '!shouldShow()',
386
+ 'role': 'alert',
387
+ 'aria-live': 'polite',
388
+ },
389
+ }]
390
+ }], propDecorators: { match: [{ type: i0.Input, args: [{ isSignal: true, alias: "match", required: false }] }] } });
391
+
392
+ /**
393
+ * Directive to mark content as the prefix slot in a form field.
394
+ *
395
+ * Prefix content appears before the input (e.g., currency symbol, icon).
396
+ *
397
+ * @tokens `--color-muted-foreground`
398
+ *
399
+ * @example Currency prefix
400
+ * ```html
401
+ * <com-form-field>
402
+ * <label comLabel>Amount</label>
403
+ * <span comPrefix>$</span>
404
+ * <input comInput type="number" formControlName="amount" />
405
+ * </com-form-field>
406
+ * ```
407
+ *
408
+ * @example Icon prefix
409
+ * ```html
410
+ * <com-form-field>
411
+ * <label comLabel>Search</label>
412
+ * <svg comPrefix class="size-4">...</svg>
413
+ * <input comInput formControlName="query" />
414
+ * </com-form-field>
415
+ * ```
416
+ */
417
+ class ComPrefix {
418
+ prefixVariants = prefixVariants;
419
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComPrefix, deps: [], target: i0.ɵɵFactoryTarget.Directive });
420
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: ComPrefix, isStandalone: true, selector: "[comPrefix]", host: { properties: { "class": "prefixVariants()" } }, exportAs: ["comPrefix"], ngImport: i0 });
421
+ }
422
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComPrefix, decorators: [{
423
+ type: Directive,
424
+ args: [{
425
+ selector: '[comPrefix]',
426
+ exportAs: 'comPrefix',
427
+ host: {
428
+ '[class]': 'prefixVariants()',
429
+ },
430
+ }]
431
+ }] });
432
+
433
+ /**
434
+ * Directive to mark content as the suffix slot in a form field.
435
+ *
436
+ * Suffix content appears after the input (e.g., unit, clear button, visibility toggle).
437
+ *
438
+ * @tokens `--color-muted-foreground`
439
+ *
440
+ * @example Unit suffix
441
+ * ```html
442
+ * <com-form-field>
443
+ * <label comLabel>Weight</label>
444
+ * <input comInput type="number" formControlName="weight" />
445
+ * <span comSuffix>kg</span>
446
+ * </com-form-field>
447
+ * ```
448
+ *
449
+ * @example Clear button suffix
450
+ * ```html
451
+ * <com-form-field>
452
+ * <label comLabel>Search</label>
453
+ * <input comInput formControlName="query" />
454
+ * <button comSuffix type="button" (click)="clearSearch()">
455
+ * <svg class="size-4">...</svg>
456
+ * </button>
457
+ * </com-form-field>
458
+ * ```
459
+ *
460
+ * @example Password visibility toggle
461
+ * ```html
462
+ * <com-form-field>
463
+ * <label comLabel>Password</label>
464
+ * <input comInput [type]="showPassword ? 'text' : 'password'" formControlName="password" />
465
+ * <button comSuffix type="button" (click)="showPassword = !showPassword">
466
+ * <svg class="size-4">...</svg>
467
+ * </button>
468
+ * </com-form-field>
469
+ * ```
470
+ */
471
+ class ComSuffix {
472
+ suffixVariants = suffixVariants;
473
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComSuffix, deps: [], target: i0.ɵɵFactoryTarget.Directive });
474
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: ComSuffix, isStandalone: true, selector: "[comSuffix]", host: { properties: { "class": "suffixVariants()" } }, exportAs: ["comSuffix"], ngImport: i0 });
475
+ }
476
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComSuffix, decorators: [{
477
+ type: Directive,
478
+ args: [{
479
+ selector: '[comSuffix]',
480
+ exportAs: 'comSuffix',
481
+ host: {
482
+ '[class]': 'suffixVariants()',
483
+ },
484
+ }]
485
+ }] });
486
+
487
+ /**
488
+ * Strategy for determining when to display errors in a form field.
489
+ *
490
+ * The default behavior shows errors when the control is invalid AND
491
+ * either touched OR the parent form has been submitted.
492
+ *
493
+ * @example Override globally with eager error display
494
+ * ```ts
495
+ * @Injectable()
496
+ * export class EagerErrorStateMatcher extends ErrorStateMatcher {
497
+ * override isErrorState(control: AbstractControl | null): boolean {
498
+ * return !!(control?.invalid && control.dirty);
499
+ * }
500
+ * }
501
+ *
502
+ * // In app config
503
+ * providers: [{ provide: ErrorStateMatcher, useClass: EagerErrorStateMatcher }]
504
+ * ```
505
+ *
506
+ * @example Per-field override
507
+ * ```html
508
+ * <com-form-field>
509
+ * <input comInput [errorStateMatcher]="eagerMatcher" />
510
+ * </com-form-field>
511
+ * ```
512
+ */
513
+ class ErrorStateMatcher {
514
+ isErrorState(control, form) {
515
+ return !!(control?.invalid && (control.touched || form?.submitted));
516
+ }
517
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ErrorStateMatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
518
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ErrorStateMatcher, providedIn: 'root' });
519
+ }
520
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ErrorStateMatcher, decorators: [{
521
+ type: Injectable,
522
+ args: [{ providedIn: 'root' }]
523
+ }] });
524
+
525
+ /** Auto-incrementing ID counter. */
526
+ let nextId = 0;
527
+ /**
528
+ * Directive applied to native `<input>` and `<textarea>` elements to bridge
529
+ * them to the form field wrapper. Implements the `FormFieldControl` contract.
530
+ *
531
+ * This directive does NOT implement `ControlValueAccessor` - it relies on
532
+ * Angular's built-in value accessors (`DefaultValueAccessor`, `NumberValueAccessor`, etc.)
533
+ * that are already applied to native elements with `formControlName` or `ngModel`.
534
+ *
535
+ * Instead, this directive reports state (focused, empty, disabled, error) to the
536
+ * parent form field for proper visual presentation.
537
+ *
538
+ * @tokens (styling is controlled by the parent form field)
539
+ *
540
+ * @example Basic usage
541
+ * ```html
542
+ * <com-form-field>
543
+ * <label comLabel>Email</label>
544
+ * <input comInput formControlName="email" placeholder="you@example.com" />
545
+ * </com-form-field>
546
+ * ```
547
+ *
548
+ * @example Textarea
549
+ * ```html
550
+ * <com-form-field>
551
+ * <label comLabel>Description</label>
552
+ * <textarea comInput formControlName="description"></textarea>
553
+ * </com-form-field>
554
+ * ```
555
+ *
556
+ * @example Custom error state matcher
557
+ * ```html
558
+ * <com-form-field>
559
+ * <label comLabel>Code</label>
560
+ * <input comInput formControlName="code" [errorStateMatcher]="eagerMatcher" />
561
+ * </com-form-field>
562
+ * ```
563
+ */
564
+ class ComInput {
565
+ elementRef = inject(ElementRef);
566
+ destroyRef = inject(DestroyRef);
567
+ autofillMonitor = inject(AutofillMonitor);
568
+ defaultErrorStateMatcher = inject(ErrorStateMatcher);
569
+ parentForm = inject(NgForm, { optional: true });
570
+ parentFormGroup = inject(FormGroupDirective, { optional: true });
571
+ /** NgControl bound to this input (if using reactive forms). */
572
+ ngControl = inject(NgControl, { optional: true, self: true });
573
+ // Inputs
574
+ inputId = input(undefined, { ...(ngDevMode ? { debugName: "inputId" } : {}), alias: 'id' });
575
+ inputDisabled = input(false, { ...(ngDevMode ? { debugName: "inputDisabled" } : {}), alias: 'disabled',
576
+ transform: booleanAttribute });
577
+ inputRequired = input(false, { ...(ngDevMode ? { debugName: "inputRequired" } : {}), alias: 'required',
578
+ transform: booleanAttribute });
579
+ userAriaDescribedBy = input('', { ...(ngDevMode ? { debugName: "userAriaDescribedBy" } : {}), alias: 'aria-describedby' });
580
+ errorStateMatcher = input(...(ngDevMode ? [undefined, { debugName: "errorStateMatcher" }] : []));
581
+ // Internal state
582
+ _focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : []));
583
+ _autofilled = signal(false, ...(ngDevMode ? [{ debugName: "_autofilled" }] : []));
584
+ _empty = signal(true, ...(ngDevMode ? [{ debugName: "_empty" }] : []));
585
+ _uniqueId = `com-input-${nextId++}`;
586
+ _appearance = signal('outline', ...(ngDevMode ? [{ debugName: "_appearance" }] : []));
587
+ // Public signals implementing FormFieldControl
588
+ focused = this._focused.asReadonly();
589
+ id = computed(() => this.inputId() ?? this._uniqueId, ...(ngDevMode ? [{ debugName: "id" }] : []));
590
+ shouldLabelFloat = computed(() => {
591
+ return this._focused() || !this._empty() || this._autofilled();
592
+ }, ...(ngDevMode ? [{ debugName: "shouldLabelFloat" }] : []));
593
+ disabled = computed(() => {
594
+ if (this.inputDisabled())
595
+ return true;
596
+ return this.ngControl?.control?.disabled ?? false;
597
+ }, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
598
+ required = computed(() => {
599
+ if (this.inputRequired())
600
+ return true;
601
+ const control = this.ngControl?.control;
602
+ if (control?.validator) {
603
+ const result = control.validator({ value: '' });
604
+ return result?.['required'] !== undefined;
605
+ }
606
+ return false;
607
+ }, ...(ngDevMode ? [{ debugName: "required" }] : []));
608
+ errorState = computed(() => {
609
+ // Read _focused to trigger re-evaluation on blur (when touched changes)
610
+ // Read _empty to trigger re-evaluation on input (when validity changes)
611
+ this._focused();
612
+ this._empty();
613
+ const matcher = this.errorStateMatcher() ?? this.defaultErrorStateMatcher;
614
+ const form = this.parentFormGroup ?? this.parentForm;
615
+ return matcher.isErrorState(this.ngControl?.control ?? null, form);
616
+ }, ...(ngDevMode ? [{ debugName: "errorState" }] : []));
617
+ /** Combined aria-describedby including user-provided and form-field-generated IDs. */
618
+ _describedByIds = signal('', ...(ngDevMode ? [{ debugName: "_describedByIds" }] : []));
619
+ ariaDescribedBy = computed(() => {
620
+ const userIds = this.userAriaDescribedBy();
621
+ const fieldIds = this._describedByIds();
622
+ return [userIds, fieldIds].filter(Boolean).join(' ');
623
+ }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
624
+ /** Computed host classes including appearance-based padding. */
625
+ hostClasses = computed(() => {
626
+ const base = 'peer w-full bg-transparent text-foreground placeholder:text-input-placeholder outline-none border-none disabled:cursor-not-allowed disabled:text-disabled-foreground px-3';
627
+ const padding = this._appearance() === 'fill' ? 'pt-5 pb-1.5' : 'py-2.5';
628
+ return `${base} ${padding}`;
629
+ }, ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
630
+ ngOnInit() {
631
+ this.updateEmpty();
632
+ const autofillSub = this.autofillMonitor.monitor(this.elementRef).subscribe((event) => {
633
+ this._autofilled.set(event.isAutofilled);
634
+ });
635
+ this.destroyRef.onDestroy(() => {
636
+ autofillSub.unsubscribe();
637
+ this.autofillMonitor.stopMonitoring(this.elementRef);
638
+ });
639
+ }
640
+ onFocus() {
641
+ this._focused.set(true);
642
+ }
643
+ onBlur() {
644
+ this._focused.set(false);
645
+ }
646
+ onInput() {
647
+ this.updateEmpty();
648
+ }
649
+ updateEmpty() {
650
+ this._empty.set(!this.elementRef.nativeElement.value);
651
+ }
652
+ // FormFieldControl methods
653
+ onContainerClick(event) {
654
+ if (!this.disabled() && event.target !== this.elementRef.nativeElement) {
655
+ this.elementRef.nativeElement.focus();
656
+ }
657
+ }
658
+ /**
659
+ * Sets the describedBy IDs from the form field.
660
+ * Called by the parent form field component.
661
+ */
662
+ setDescribedByIds(ids) {
663
+ this._describedByIds.set(ids);
664
+ }
665
+ /**
666
+ * Sets the appearance for styling.
667
+ * Called by the parent form field component.
668
+ */
669
+ setAppearance(appearance) {
670
+ this._appearance.set(appearance);
671
+ }
672
+ /** Focus the native element. */
673
+ focus() {
674
+ this.elementRef.nativeElement.focus();
675
+ }
676
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
677
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.0", type: ComInput, isStandalone: true, selector: "input[comInput], textarea[comInput]", inputs: { inputId: { classPropertyName: "inputId", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, inputDisabled: { classPropertyName: "inputDisabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, inputRequired: { classPropertyName: "inputRequired", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, userAriaDescribedBy: { classPropertyName: "userAriaDescribedBy", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focus": "onFocus()", "blur": "onBlur()", "input": "onInput()" }, properties: { "id": "id()", "disabled": "disabled()", "required": "required()", "attr.aria-invalid": "errorState() || null", "attr.aria-required": "required() || null", "attr.aria-describedby": "ariaDescribedBy() || null", "class": "hostClasses()" } }, providers: [{ provide: FormFieldControl, useExisting: forwardRef(() => ComInput) }], exportAs: ["comInput"], ngImport: i0 });
678
+ }
679
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComInput, decorators: [{
680
+ type: Directive,
681
+ args: [{
682
+ selector: 'input[comInput], textarea[comInput]',
683
+ exportAs: 'comInput',
684
+ providers: [{ provide: FormFieldControl, useExisting: forwardRef(() => ComInput) }],
685
+ host: {
686
+ '[id]': 'id()',
687
+ '[disabled]': 'disabled()',
688
+ '[required]': 'required()',
689
+ '[attr.aria-invalid]': 'errorState() || null',
690
+ '[attr.aria-required]': 'required() || null',
691
+ '[attr.aria-describedby]': 'ariaDescribedBy() || null',
692
+ '[class]': 'hostClasses()',
693
+ '(focus)': 'onFocus()',
694
+ '(blur)': 'onBlur()',
695
+ '(input)': 'onInput()',
696
+ },
697
+ }]
698
+ }], propDecorators: { inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], inputDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], inputRequired: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], userAriaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }] } });
699
+
700
+ const FORM_FIELD_DEFAULTS = new InjectionToken('FORM_FIELD_DEFAULTS', {
701
+ providedIn: 'root',
702
+ factory: () => ({}),
703
+ });
704
+
705
+ /**
706
+ * Form field wrapper providing visual structure for form inputs.
707
+ *
708
+ * Provides floating labels, borders (outline/fill appearance), hints, errors,
709
+ * and prefix/suffix slots. Automatically wires ARIA attributes for accessibility.
710
+ *
711
+ * The form field reads state from its inner `FormFieldControl` child (typically
712
+ * a `ComInput` directive) and renders UI accordingly.
713
+ *
714
+ * @tokens `--color-foreground`, `--color-input-border`, `--color-input-background`,
715
+ * `--color-primary`, `--color-accent`, `--color-warn`, `--color-ring`,
716
+ * `--color-muted`, `--color-muted-foreground`, `--color-disabled`,
717
+ * `--color-disabled-foreground`, `--color-background`, `--radius-input`
718
+ *
719
+ * @example Basic outline field
720
+ * ```html
721
+ * <com-form-field>
722
+ * <label comLabel>Email</label>
723
+ * <input comInput formControlName="email" />
724
+ * <span comHint>We'll never share your email.</span>
725
+ * <span comError match="required">Email is required.</span>
726
+ * </com-form-field>
727
+ * ```
728
+ *
729
+ * @example Fill appearance
730
+ * ```html
731
+ * <com-form-field appearance="fill">
732
+ * <label comLabel>Username</label>
733
+ * <input comInput formControlName="username" />
734
+ * </com-form-field>
735
+ * ```
736
+ *
737
+ * @example With prefix and suffix
738
+ * ```html
739
+ * <com-form-field>
740
+ * <label comLabel>Amount</label>
741
+ * <span comPrefix>$</span>
742
+ * <input comInput type="number" formControlName="amount" />
743
+ * <span comSuffix>.00</span>
744
+ * </com-form-field>
745
+ * ```
746
+ */
747
+ class ComFormField {
748
+ defaults = inject(FORM_FIELD_DEFAULTS, { optional: true });
749
+ // Content children
750
+ control = contentChild(FormFieldControl, ...(ngDevMode ? [{ debugName: "control" }] : []));
751
+ inputDirective = contentChild(ComInput, ...(ngDevMode ? [{ debugName: "inputDirective" }] : []));
752
+ labelChild = contentChild(ComLabel, ...(ngDevMode ? [{ debugName: "labelChild" }] : []));
753
+ hintChildren = contentChildren(ComHint, ...(ngDevMode ? [{ debugName: "hintChildren" }] : []));
754
+ errorChildren = contentChildren(ComError, ...(ngDevMode ? [{ debugName: "errorChildren" }] : []));
755
+ prefixChild = contentChild(ComPrefix, ...(ngDevMode ? [{ debugName: "prefixChild" }] : []));
756
+ suffixChild = contentChild(ComSuffix, ...(ngDevMode ? [{ debugName: "suffixChild" }] : []));
757
+ // Inputs with defaults fallback
758
+ appearance = input(this.defaults?.appearance ?? 'outline', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
759
+ color = input(this.defaults?.color ?? 'primary', ...(ngDevMode ? [{ debugName: "color" }] : []));
760
+ floatLabel = input(this.defaults?.floatLabel ?? 'auto', ...(ngDevMode ? [{ debugName: "floatLabel" }] : []));
761
+ hideRequiredMarker = input(this.defaults?.hideRequiredMarker ?? false, { ...(ngDevMode ? { debugName: "hideRequiredMarker" } : {}), transform: booleanAttribute });
762
+ subscriptSizing = input(this.defaults?.subscriptSizing ?? 'fixed', ...(ngDevMode ? [{ debugName: "subscriptSizing" }] : []));
763
+ userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : {}), alias: 'class' });
764
+ // Derived state from inner control
765
+ shouldLabelFloat = computed(() => {
766
+ if (this.floatLabel() === 'always')
767
+ return true;
768
+ return this.control()?.shouldLabelFloat() ?? false;
769
+ }, ...(ngDevMode ? [{ debugName: "shouldLabelFloat" }] : []));
770
+ isFocused = computed(() => this.control()?.focused() ?? false, ...(ngDevMode ? [{ debugName: "isFocused" }] : []));
771
+ isDisabled = computed(() => this.control()?.disabled() ?? false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
772
+ hasError = computed(() => this.control()?.errorState() ?? false, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
773
+ hasPrefix = computed(() => !!this.prefixChild(), ...(ngDevMode ? [{ debugName: "hasPrefix" }] : []));
774
+ hasSuffix = computed(() => !!this.suffixChild(), ...(ngDevMode ? [{ debugName: "hasSuffix" }] : []));
775
+ showRequiredMarker = computed(() => {
776
+ if (this.hideRequiredMarker())
777
+ return false;
778
+ return this.control()?.required() ?? false;
779
+ }, ...(ngDevMode ? [{ debugName: "showRequiredMarker" }] : []));
780
+ showErrors = computed(() => this.hasError() && this.errorChildren().length > 0, ...(ngDevMode ? [{ debugName: "showErrors" }] : []));
781
+ // Computed classes
782
+ hostClasses = computed(() => mergeClasses(formFieldVariants({ disabled: this.isDisabled() }), this.userClass()), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
783
+ containerClasses = computed(() => formFieldContainerVariants({
784
+ appearance: this.appearance(),
785
+ color: this.color(),
786
+ focused: this.isFocused(),
787
+ error: this.hasError(),
788
+ disabled: this.isDisabled(),
789
+ }), ...(ngDevMode ? [{ debugName: "containerClasses" }] : []));
790
+ labelClasses = computed(() => formFieldLabelVariants({
791
+ appearance: this.appearance(),
792
+ floating: this.shouldLabelFloat(),
793
+ color: this.color(),
794
+ error: this.hasError(),
795
+ focused: this.isFocused(),
796
+ disabled: this.isDisabled(),
797
+ }), ...(ngDevMode ? [{ debugName: "labelClasses" }] : []));
798
+ subscriptClasses = computed(() => formFieldSubscriptVariants({ sizing: this.subscriptSizing() }), ...(ngDevMode ? [{ debugName: "subscriptClasses" }] : []));
799
+ constructor() {
800
+ // Wire up label to control
801
+ effect(() => {
802
+ const label = this.labelChild();
803
+ const ctrl = this.control();
804
+ if (label && ctrl) {
805
+ label.setForId(ctrl.id());
806
+ }
807
+ });
808
+ // Wire up aria-describedby on the control
809
+ effect(() => {
810
+ const ctrl = this.control();
811
+ if (!ctrl || !('setDescribedByIds' in ctrl))
812
+ return;
813
+ const ids = this.showErrors()
814
+ ? this.errorChildren().filter((e) => e.shouldShow()).map((e) => e.id)
815
+ : this.hintChildren().map((h) => h.id);
816
+ ctrl.setDescribedByIds(ids.join(' '));
817
+ });
818
+ // Wire up appearance to control for proper styling
819
+ effect(() => {
820
+ const ctrl = this.control();
821
+ if (ctrl && 'setAppearance' in ctrl) {
822
+ ctrl.setAppearance(this.appearance());
823
+ }
824
+ });
825
+ // Wire up control reference to error directives (re-runs on error state change)
826
+ effect(() => {
827
+ // Read hasError to trigger re-evaluation when validation state changes
828
+ this.hasError();
829
+ const ctrl = this.control();
830
+ const ngControl = ctrl?.ngControl?.control ?? null;
831
+ this.errorChildren().forEach((error) => error.setControl(ngControl));
832
+ });
833
+ }
834
+ onContainerClick(event) {
835
+ this.control()?.onContainerClick(event);
836
+ }
837
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComFormField, deps: [], target: i0.ɵɵFactoryTarget.Component });
838
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComFormField, isStandalone: true, selector: "com-form-field", inputs: { appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, floatLabel: { classPropertyName: "floatLabel", publicName: "floatLabel", isSignal: true, isRequired: false, transformFunction: null }, hideRequiredMarker: { classPropertyName: "hideRequiredMarker", publicName: "hideRequiredMarker", isSignal: true, isRequired: false, transformFunction: null }, subscriptSizing: { classPropertyName: "subscriptSizing", publicName: "subscriptSizing", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "class.com-form-field--focused": "isFocused()", "class.com-form-field--disabled": "isDisabled()", "class.com-form-field--error": "hasError()", "class.com-form-field--floating": "shouldLabelFloat()", "class.com-form-field--has-label": "!!labelChild()" } }, queries: [{ propertyName: "control", first: true, predicate: FormFieldControl, descendants: true, isSignal: true }, { propertyName: "inputDirective", first: true, predicate: ComInput, descendants: true, isSignal: true }, { propertyName: "labelChild", first: true, predicate: ComLabel, descendants: true, isSignal: true }, { propertyName: "hintChildren", predicate: ComHint, isSignal: true }, { propertyName: "errorChildren", predicate: ComError, isSignal: true }, { propertyName: "prefixChild", first: true, predicate: ComPrefix, descendants: true, isSignal: true }, { propertyName: "suffixChild", first: true, predicate: ComSuffix, descendants: true, isSignal: true }], exportAs: ["comFormField"], ngImport: i0, template: `
839
+ <div [class]="containerClasses()" (click)="onContainerClick($event)">
840
+ <div class="flex items-center w-full">
841
+ @if (hasPrefix()) {
842
+ <ng-content select="[comPrefix]" />
843
+ }
844
+ @if (labelChild()) {
845
+ <span [class]="labelClasses()">
846
+ <ng-content select="[comLabel]" />
847
+ @if (showRequiredMarker()) {
848
+ <span class="text-warn" aria-hidden="true">&thinsp;*</span>
849
+ }
850
+ </span>
851
+ }
852
+ <div class="flex-1 min-w-0">
853
+ <ng-content />
854
+ </div>
855
+ @if (hasSuffix()) {
856
+ <ng-content select="[comSuffix]" />
857
+ }
858
+ </div>
859
+ </div>
860
+
861
+ <div [class]="subscriptClasses()">
862
+ <div [class.hidden]="!showErrors()">
863
+ <ng-content select="[comError]" />
864
+ </div>
865
+ <div [class.hidden]="showErrors()" class="flex justify-between w-full gap-2">
866
+ <ng-content select="[comHint]:not([align='end'])" />
867
+ <ng-content select="[comHint][align='end']" />
868
+ </div>
869
+ </div>
870
+ `, isInline: true, styles: ["com-form-field.com-form-field--has-label:not(.com-form-field--floating) input::placeholder,com-form-field.com-form-field--has-label:not(.com-form-field--floating) textarea::placeholder{color:transparent}com-form-field.com-form-field--has-label:not(.com-form-field--floating) com-dropdown .text-placeholder{display:none}com-form-field com-dropdown{display:block;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
871
+ }
872
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComFormField, decorators: [{
873
+ type: Component,
874
+ args: [{ selector: 'com-form-field', exportAs: 'comFormField', template: `
875
+ <div [class]="containerClasses()" (click)="onContainerClick($event)">
876
+ <div class="flex items-center w-full">
877
+ @if (hasPrefix()) {
878
+ <ng-content select="[comPrefix]" />
879
+ }
880
+ @if (labelChild()) {
881
+ <span [class]="labelClasses()">
882
+ <ng-content select="[comLabel]" />
883
+ @if (showRequiredMarker()) {
884
+ <span class="text-warn" aria-hidden="true">&thinsp;*</span>
885
+ }
886
+ </span>
887
+ }
888
+ <div class="flex-1 min-w-0">
889
+ <ng-content />
890
+ </div>
891
+ @if (hasSuffix()) {
892
+ <ng-content select="[comSuffix]" />
893
+ }
894
+ </div>
895
+ </div>
896
+
897
+ <div [class]="subscriptClasses()">
898
+ <div [class.hidden]="!showErrors()">
899
+ <ng-content select="[comError]" />
900
+ </div>
901
+ <div [class.hidden]="showErrors()" class="flex justify-between w-full gap-2">
902
+ <ng-content select="[comHint]:not([align='end'])" />
903
+ <ng-content select="[comHint][align='end']" />
904
+ </div>
905
+ </div>
906
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
907
+ '[class]': 'hostClasses()',
908
+ '[class.com-form-field--focused]': 'isFocused()',
909
+ '[class.com-form-field--disabled]': 'isDisabled()',
910
+ '[class.com-form-field--error]': 'hasError()',
911
+ '[class.com-form-field--floating]': 'shouldLabelFloat()',
912
+ '[class.com-form-field--has-label]': '!!labelChild()',
913
+ }, styles: ["com-form-field.com-form-field--has-label:not(.com-form-field--floating) input::placeholder,com-form-field.com-form-field--has-label:not(.com-form-field--floating) textarea::placeholder{color:transparent}com-form-field.com-form-field--has-label:not(.com-form-field--floating) com-dropdown .text-placeholder{display:none}com-form-field com-dropdown{display:block;width:100%}\n"] }]
914
+ }], ctorParameters: () => [], propDecorators: { control: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FormFieldControl), { isSignal: true }] }], inputDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComInput), { isSignal: true }] }], labelChild: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComLabel), { isSignal: true }] }], hintChildren: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ComHint), { isSignal: true }] }], errorChildren: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ComError), { isSignal: true }] }], prefixChild: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComPrefix), { isSignal: true }] }], suffixChild: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ComSuffix), { isSignal: true }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], floatLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatLabel", required: false }] }], hideRequiredMarker: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideRequiredMarker", required: false }] }], subscriptSizing: [{ type: i0.Input, args: [{ isSignal: true, alias: "subscriptSizing", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
915
+
916
+ // Public API for the form-field component
917
+ // Main component
918
+
919
+ /**
920
+ * Generated bundle index. Do not edit.
921
+ */
922
+
923
+ export { ComError, ComFormField, ComHint, ComInput, ComLabel, ComPrefix, ComSuffix, ErrorStateMatcher, FORM_FIELD_DEFAULTS, FormFieldControl, errorVariants, formFieldContainerVariants, formFieldLabelVariants, formFieldSubscriptVariants, formFieldVariants, hintVariants, prefixVariants, suffixVariants };
924
+ //# sourceMappingURL=ngx-com-components-form-field.mjs.map