@egose/shadcn-theme-ng 0.2.0 → 0.4.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.
Files changed (96) hide show
  1. package/README.md +1 -1
  2. package/autocomplete/fesm2022/autocomplete.mjs +1 -1
  3. package/checkbox/fesm2022/checkbox.mjs +6 -7
  4. package/checkbox/types/checkbox.d.ts +1 -1
  5. package/combobox/fesm2022/combobox.mjs +1 -1
  6. package/date-picker/README.md +15 -11
  7. package/date-picker/fesm2022/date-picker.mjs +148 -30
  8. package/date-picker/types/date-picker.d.ts +102 -9
  9. package/form-autocomplete/README.md +178 -0
  10. package/form-autocomplete/fesm2022/form-autocomplete.mjs +159 -0
  11. package/form-autocomplete/package.json +24 -0
  12. package/form-autocomplete/types/form-autocomplete.d.ts +81 -0
  13. package/form-checkbox/README.md +29 -18
  14. package/form-checkbox/fesm2022/form-checkbox.mjs +59 -17
  15. package/form-checkbox/types/form-checkbox.d.ts +43 -4
  16. package/form-combobox/README.md +203 -0
  17. package/form-combobox/fesm2022/form-combobox.mjs +181 -0
  18. package/form-combobox/package.json +24 -0
  19. package/form-combobox/types/form-combobox.d.ts +93 -0
  20. package/form-date-picker/README.md +49 -22
  21. package/form-date-picker/fesm2022/form-date-picker.mjs +74 -19
  22. package/form-date-picker/types/form-date-picker.d.ts +37 -1
  23. package/form-date-picker-multi/README.md +192 -0
  24. package/form-date-picker-multi/fesm2022/form-date-picker-multi.mjs +158 -0
  25. package/form-date-picker-multi/package.json +24 -0
  26. package/form-date-picker-multi/types/form-date-picker-multi.d.ts +78 -0
  27. package/form-date-range-picker/README.md +254 -0
  28. package/form-date-range-picker/fesm2022/form-date-range-picker.mjs +159 -0
  29. package/form-date-range-picker/package.json +24 -0
  30. package/form-date-range-picker/types/form-date-range-picker.d.ts +75 -0
  31. package/form-field/README.md +9 -0
  32. package/form-field/fesm2022/form-field.mjs +80 -5
  33. package/form-field/types/form-field.d.ts +50 -2
  34. package/form-field-simple/fesm2022/form-field-simple.mjs +15 -1
  35. package/form-field-simple/types/form-field-simple.d.ts +9 -2
  36. package/form-input-otp/README.md +195 -0
  37. package/form-input-otp/fesm2022/form-input-otp.mjs +140 -0
  38. package/form-input-otp/package.json +24 -0
  39. package/form-input-otp/types/form-input-otp.d.ts +78 -0
  40. package/form-month-year-picker/README.md +189 -0
  41. package/form-month-year-picker/fesm2022/form-month-year-picker.mjs +154 -0
  42. package/form-month-year-picker/package.json +24 -0
  43. package/form-month-year-picker/types/form-month-year-picker.d.ts +73 -0
  44. package/form-native-select/README.md +206 -0
  45. package/form-native-select/fesm2022/form-native-select.mjs +132 -0
  46. package/form-native-select/package.json +24 -0
  47. package/form-native-select/types/form-native-select.d.ts +77 -0
  48. package/form-phone-input/README.md +189 -0
  49. package/form-phone-input/fesm2022/form-phone-input.mjs +142 -0
  50. package/form-phone-input/package.json +24 -0
  51. package/form-phone-input/types/form-phone-input.d.ts +78 -0
  52. package/form-radio-group/README.md +199 -0
  53. package/form-radio-group/fesm2022/form-radio-group.mjs +140 -0
  54. package/form-radio-group/package.json +24 -0
  55. package/form-radio-group/types/form-radio-group.d.ts +83 -0
  56. package/form-searchable-multiselect/README.md +28 -17
  57. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +57 -19
  58. package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +39 -2
  59. package/form-select/README.md +30 -19
  60. package/form-select/fesm2022/form-select.mjs +57 -19
  61. package/form-select/types/form-select.d.ts +39 -2
  62. package/form-slider/README.md +183 -0
  63. package/form-slider/fesm2022/form-slider.mjs +140 -0
  64. package/form-slider/package.json +24 -0
  65. package/form-slider/types/form-slider.d.ts +80 -0
  66. package/form-switch/README.md +174 -0
  67. package/form-switch/fesm2022/form-switch.mjs +129 -0
  68. package/form-switch/package.json +24 -0
  69. package/form-switch/types/form-switch.d.ts +70 -0
  70. package/form-text-input/README.md +37 -26
  71. package/form-text-input/fesm2022/form-text-input.mjs +57 -19
  72. package/form-text-input/types/form-text-input.d.ts +39 -2
  73. package/form-textarea/README.md +33 -22
  74. package/form-textarea/fesm2022/form-textarea.mjs +57 -19
  75. package/form-textarea/types/form-textarea.d.ts +39 -2
  76. package/form-toggle/README.md +187 -0
  77. package/form-toggle/fesm2022/form-toggle.mjs +193 -0
  78. package/form-toggle/package.json +24 -0
  79. package/form-toggle/types/form-toggle.d.ts +100 -0
  80. package/form-toggle-group/README.md +177 -0
  81. package/form-toggle-group/fesm2022/form-toggle-group.mjs +145 -0
  82. package/form-toggle-group/package.json +24 -0
  83. package/form-toggle-group/types/form-toggle-group.d.ts +85 -0
  84. package/input-group/fesm2022/input-group.mjs +19 -9
  85. package/input-group/types/input-group.d.ts +4 -1
  86. package/native-select/fesm2022/native-select.mjs +18 -6
  87. package/native-select/types/native-select.d.ts +7 -2
  88. package/package.json +57 -1
  89. package/phone-input/README.md +114 -0
  90. package/phone-input/fesm2022/phone-input.mjs +191 -0
  91. package/phone-input/package.json +24 -0
  92. package/phone-input/types/phone-input.d.ts +67 -0
  93. package/radio-group/types/radio-group.d.ts +1 -1
  94. package/switch/fesm2022/switch.mjs +7 -5
  95. package/switch/types/switch.d.ts +2 -1
  96. package/typography/types/typography.d.ts +8 -8
@@ -0,0 +1,159 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, input, computed, Component } from '@angular/core';
3
+ import * as i1 from '@angular/forms';
4
+ import { FormGroupDirective, ReactiveFormsModule, ControlContainer } from '@angular/forms';
5
+ import { injectEgFormErrorMessages, HlmFormIdGenerator, resolveEgFormError, HlmFormField, HlmError, HlmHint } from '@egose/shadcn-theme-ng/form-field';
6
+ import { HlmLabel } from '@egose/shadcn-theme-ng/label';
7
+ import { injectHlmDateRangePickerConfig, HlmDateRangePicker, HlmDateRangeInput } from '@egose/shadcn-theme-ng/date-picker';
8
+ import { hlm } from '@egose/shadcn-theme-ng/utils';
9
+
10
+ class EgFormDateRangePicker {
11
+ formGroupDirective = inject(FormGroupDirective);
12
+ _errorMessages = injectEgFormErrorMessages();
13
+ generatedId = inject(HlmFormIdGenerator).generate('eg-form-date-range-picker');
14
+ _config = injectHlmDateRangePickerConfig();
15
+ // Inputs
16
+ label = input(undefined, /* @ts-ignore */
17
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
18
+ controlId = input(undefined, /* @ts-ignore */
19
+ ...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
20
+ controlName = input('', /* @ts-ignore */
21
+ ...(ngDevMode ? [{ debugName: "controlName" }] : /* istanbul ignore next */ []));
22
+ error = input(undefined, /* @ts-ignore */
23
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
24
+ /**
25
+ * Auto-resolve the displayed message from the control's `ValidationErrors`
26
+ * when `error()` is unset. Explicit `error()` always wins. Set to `false`
27
+ * for manual-only messages.
28
+ */
29
+ autoError = input(true, /* @ts-ignore */
30
+ ...(ngDevMode ? [{ debugName: "autoError" }] : /* istanbul ignore next */ []));
31
+ hint = input(undefined, /* @ts-ignore */
32
+ ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
33
+ // HTML attributes
34
+ id = input(undefined, /* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
36
+ name = input(undefined, /* @ts-ignore */
37
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
38
+ placeholder = input('Pick a date range', /* @ts-ignore */
39
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
40
+ readonly = input(false, /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
42
+ disabled = input(false, /* @ts-ignore */
43
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
44
+ required = input(false, /* @ts-ignore */
45
+ ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
46
+ min = input(null, /* @ts-ignore */
47
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
48
+ max = input(null, /* @ts-ignore */
49
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
50
+ // Picker behavior (defaults fall back to the injected HlmDateRangePickerConfig,
51
+ // so global `provideHlmDateRangePickerConfig` values keep working unless overridden)
52
+ autoCloseOnEndSelection = input(this._config.autoCloseOnEndSelection, /* @ts-ignore */
53
+ ...(ngDevMode ? [{ debugName: "autoCloseOnEndSelection" }] : /* istanbul ignore next */ []));
54
+ captionLayout = input('label', /* @ts-ignore */
55
+ ...(ngDevMode ? [{ debugName: "captionLayout" }] : /* istanbul ignore next */ []));
56
+ formatDates = input(this._config.formatDates, /* @ts-ignore */
57
+ ...(ngDevMode ? [{ debugName: "formatDates" }] : /* istanbul ignore next */ []));
58
+ transformDates = input(this._config.transformDates, /* @ts-ignore */
59
+ ...(ngDevMode ? [{ debugName: "transformDates" }] : /* istanbul ignore next */ []));
60
+ parseDate = input(this._config.parseDate, /* @ts-ignore */
61
+ ...(ngDevMode ? [{ debugName: "parseDate" }] : /* istanbul ignore next */ []));
62
+ formatInputDates = input(this._config.formatInputDates, /* @ts-ignore */
63
+ ...(ngDevMode ? [{ debugName: "formatInputDates" }] : /* istanbul ignore next */ []));
64
+ effectiveId = computed(() => this.controlId() || this.id() || this.generatedId, /* @ts-ignore */
65
+ ...(ngDevMode ? [{ debugName: "effectiveId" }] : /* istanbul ignore next */ []));
66
+ errorId = computed(() => `${this.effectiveId()}-error`, /* @ts-ignore */
67
+ ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
68
+ hintId = computed(() => `${this.effectiveId()}-hint`, /* @ts-ignore */
69
+ ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
70
+ /**
71
+ * Displayed message: explicit `error()` wins; otherwise (when `autoError()`)
72
+ * auto-resolved from the control's `ValidationErrors`. Plain method (not a
73
+ * computed): `ValidationErrors` is not a signal, so the control must be read
74
+ * fresh on every change-detection pass.
75
+ */
76
+ resolvedError() {
77
+ const explicit = this.error();
78
+ if (explicit)
79
+ return explicit;
80
+ if (!this.autoError())
81
+ return undefined;
82
+ const control = this.formGroupDirective.form.get(this.controlName());
83
+ return resolveEgFormError(control?.errors ?? null, this.label(), this._errorMessages);
84
+ }
85
+ /**
86
+ * Whether the error is surfaced: message present + control invalid +
87
+ * touched/dirty/submitted. Gates the visual `<hlm-error>` (and
88
+ * `describedBy()`, where present) so sighted and screen-reader output agree.
89
+ */
90
+ showError() {
91
+ const control = this.formGroupDirective.form.get(this.controlName());
92
+ return (!!this.resolvedError() &&
93
+ !!control?.invalid &&
94
+ (control.dirty || control.touched || this.formGroupDirective.submitted));
95
+ }
96
+ /**
97
+ * Bounds projected onto the picker's `Date` model. The cast keeps the
98
+ * template's generic inference on `Date` while passing runtime values
99
+ * (including `string` bounds, as in `eg-form-date-picker`) through untouched.
100
+ */
101
+ $min = computed(() => this.min(), /* @ts-ignore */
102
+ ...(ngDevMode ? [{ debugName: "$min" }] : /* istanbul ignore next */ []));
103
+ $max = computed(() => this.max(), /* @ts-ignore */
104
+ ...(ngDevMode ? [{ debugName: "$max" }] : /* istanbul ignore next */ []));
105
+ describedBy() {
106
+ return this.showError() ? this.errorId() : this.hint() ? this.hintId() : null;
107
+ }
108
+ // Styling
109
+ userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
110
+ labelClass = input('', /* @ts-ignore */
111
+ ...(ngDevMode ? [{ debugName: "labelClass" }] : /* istanbul ignore next */ []));
112
+ pickerClass = input('', /* @ts-ignore */
113
+ ...(ngDevMode ? [{ debugName: "pickerClass" }] : /* istanbul ignore next */ []));
114
+ inputClass = input('', /* @ts-ignore */
115
+ ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
116
+ errorClass = input('', /* @ts-ignore */
117
+ ...(ngDevMode ? [{ debugName: "errorClass" }] : /* istanbul ignore next */ []));
118
+ hintClass = input('', /* @ts-ignore */
119
+ ...(ngDevMode ? [{ debugName: "hintClass" }] : /* istanbul ignore next */ []));
120
+ // Computed classes (library base < global config < per-instance)
121
+ $userClass = computed(() => hlm("w-full", this.userClass()), /* @ts-ignore */
122
+ ...(ngDevMode ? [{ debugName: "$userClass" }] : /* istanbul ignore next */ []));
123
+ $labelClass = computed(() => hlm("mb-1", this._config.labelClass, this.labelClass()), /* @ts-ignore */
124
+ ...(ngDevMode ? [{ debugName: "$labelClass" }] : /* istanbul ignore next */ []));
125
+ $pickerClass = computed(() => hlm("mb-1", this._config.pickerClass, this.pickerClass()), /* @ts-ignore */
126
+ ...(ngDevMode ? [{ debugName: "$pickerClass" }] : /* istanbul ignore next */ []));
127
+ $inputClass = computed(() => hlm(this._config.inputClass, this.inputClass()), /* @ts-ignore */
128
+ ...(ngDevMode ? [{ debugName: "$inputClass" }] : /* istanbul ignore next */ []));
129
+ $errorClass = computed(() => hlm("mt-0", this._config.errorClass, this.errorClass()), /* @ts-ignore */
130
+ ...(ngDevMode ? [{ debugName: "$errorClass" }] : /* istanbul ignore next */ []));
131
+ $hintClass = computed(() => hlm("mt-0", this._config.hintClass, this.hintClass()), /* @ts-ignore */
132
+ ...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
133
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
134
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormDateRangePicker, isStandalone: true, selector: "eg-form-date-range-picker", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, autoCloseOnEndSelection: { classPropertyName: "autoCloseOnEndSelection", publicName: "autoCloseOnEndSelection", isSignal: true, isRequired: false, transformFunction: null }, captionLayout: { classPropertyName: "captionLayout", publicName: "captionLayout", isSignal: true, isRequired: false, transformFunction: null }, formatDates: { classPropertyName: "formatDates", publicName: "formatDates", isSignal: true, isRequired: false, transformFunction: null }, transformDates: { classPropertyName: "transformDates", publicName: "transformDates", isSignal: true, isRequired: false, transformFunction: null }, parseDate: { classPropertyName: "parseDate", publicName: "parseDate", isSignal: true, isRequired: false, transformFunction: null }, formatInputDates: { classPropertyName: "formatInputDates", publicName: "formatInputDates", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, pickerClass: { classPropertyName: "pickerClass", publicName: "pickerClass", isSignal: true, isRequired: false, transformFunction: null }, inputClass: { classPropertyName: "inputClass", publicName: "inputClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <hlm-date-range-picker\n [min]=\"$min()\"\n [max]=\"$max()\"\n [disabled]=\"disabled()\"\n [autoCloseOnEndSelection]=\"autoCloseOnEndSelection()\"\n [captionLayout]=\"captionLayout()\"\n [formatDates]=\"formatDates()\"\n [transformDates]=\"transformDates()\"\n [formControlName]=\"cnm\"\n [class]=\"$pickerClass()\"\n >\n <hlm-date-range-input\n [inputId]=\"effectiveId()\"\n [name]=\"cnm || name()\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"lbl\"\n [ariaDescribedby]=\"describedBy()\"\n [placeholder]=\"placeholder()\"\n [parseDate]=\"parseDate()\"\n [formatInputDates]=\"formatInputDates()\"\n [inputClass]=\"$inputClass()\"\n />\n </hlm-date-range-picker>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ", isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: HlmFormField, selector: "hlm-form-field", inputs: ["class"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "component", type: HlmDateRangePicker, selector: "hlm-date-range-picker", inputs: ["captionLayout", "min", "max", "disabled", "date", "autoCloseOnEndSelection", "formatDates", "transformDates"], outputs: ["dateChange"] }, { kind: "component", type: HlmDateRangeInput, selector: "hlm-date-range-input", inputs: ["name", "readonly", "ariaLabel", "ariaDescribedby", "class", "inputClass", "parseDate", "formatInputDates"] }] });
135
+ }
136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormDateRangePicker, decorators: [{
137
+ type: Component,
138
+ args: [{
139
+ selector: 'eg-form-date-range-picker',
140
+ standalone: true,
141
+ host: {
142
+ '[class]': '$userClass()',
143
+ },
144
+ imports: [ReactiveFormsModule, HlmFormField, HlmError, HlmHint, HlmLabel, HlmDateRangePicker, HlmDateRangeInput],
145
+ providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
146
+ template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <hlm-date-range-picker\n [min]=\"$min()\"\n [max]=\"$max()\"\n [disabled]=\"disabled()\"\n [autoCloseOnEndSelection]=\"autoCloseOnEndSelection()\"\n [captionLayout]=\"captionLayout()\"\n [formatDates]=\"formatDates()\"\n [transformDates]=\"transformDates()\"\n [formControlName]=\"cnm\"\n [class]=\"$pickerClass()\"\n >\n <hlm-date-range-input\n [inputId]=\"effectiveId()\"\n [name]=\"cnm || name()\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"lbl\"\n [ariaDescribedby]=\"describedBy()\"\n [placeholder]=\"placeholder()\"\n [parseDate]=\"parseDate()\"\n [formatInputDates]=\"formatInputDates()\"\n [inputClass]=\"$inputClass()\"\n />\n </hlm-date-range-picker>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ",
147
+ }]
148
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], autoCloseOnEndSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseOnEndSelection", required: false }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], formatDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatDates", required: false }] }], transformDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "transformDates", required: false }] }], parseDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "parseDate", required: false }] }], formatInputDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatInputDates", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], pickerClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickerClass", required: false }] }], inputClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
149
+
150
+ /*
151
+ * Public API Surface of form-date-range-picker
152
+ */
153
+
154
+ /**
155
+ * Generated bundle index. Do not edit.
156
+ */
157
+
158
+ export { EgFormDateRangePicker };
159
+
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "form-date-range-picker",
3
+ "version": "0.0.1",
4
+ "peerDependencies": {
5
+ "@angular/common": "^20.3.0 || ^22.0.0",
6
+ "@angular/core": "^20.3.0 || ^22.0.0"
7
+ },
8
+ "dependencies": {
9
+ "tslib": "^2.3.0"
10
+ },
11
+ "sideEffects": false,
12
+ "module": "fesm2022/form-date-range-picker.mjs",
13
+ "typings": "types/form-date-range-picker.d.ts",
14
+ "exports": {
15
+ "./package.json": {
16
+ "default": "./package.json"
17
+ },
18
+ ".": {
19
+ "types": "./types/form-date-range-picker.d.ts",
20
+ "default": "./fesm2022/form-date-range-picker.mjs"
21
+ }
22
+ },
23
+ "type": "module"
24
+ }
@@ -0,0 +1,75 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { ClassValue } from 'clsx';
3
+
4
+ type DateRangeCaptionLayout = 'dropdown' | 'label' | 'dropdown-months' | 'dropdown-years';
5
+ declare class EgFormDateRangePicker {
6
+ private readonly formGroupDirective;
7
+ private readonly _errorMessages;
8
+ private readonly generatedId;
9
+ private readonly _config;
10
+ label: _angular_core.InputSignal<string | undefined>;
11
+ controlId: _angular_core.InputSignal<string | undefined>;
12
+ controlName: _angular_core.InputSignal<string>;
13
+ error: _angular_core.InputSignal<string | undefined>;
14
+ /**
15
+ * Auto-resolve the displayed message from the control's `ValidationErrors`
16
+ * when `error()` is unset. Explicit `error()` always wins. Set to `false`
17
+ * for manual-only messages.
18
+ */
19
+ autoError: _angular_core.InputSignal<boolean>;
20
+ hint: _angular_core.InputSignal<string | undefined>;
21
+ id: _angular_core.InputSignal<string | undefined>;
22
+ name: _angular_core.InputSignal<string | undefined>;
23
+ placeholder: _angular_core.InputSignal<string>;
24
+ readonly: _angular_core.InputSignal<boolean>;
25
+ disabled: _angular_core.InputSignal<boolean>;
26
+ required: _angular_core.InputSignal<boolean>;
27
+ min: _angular_core.InputSignal<string | Date | null>;
28
+ max: _angular_core.InputSignal<string | Date | null>;
29
+ autoCloseOnEndSelection: _angular_core.InputSignal<boolean>;
30
+ captionLayout: _angular_core.InputSignal<DateRangeCaptionLayout>;
31
+ formatDates: _angular_core.InputSignal<(dates: [Date | null, Date | null]) => string>;
32
+ transformDates: _angular_core.InputSignal<(dates: [Date, Date]) => [Date, Date]>;
33
+ parseDate: _angular_core.InputSignal<(value: string) => [Date, Date] | null>;
34
+ formatInputDates: _angular_core.InputSignal<(dates: [Date | null, Date | null]) => string>;
35
+ readonly effectiveId: _angular_core.Signal<string>;
36
+ readonly errorId: _angular_core.Signal<string>;
37
+ readonly hintId: _angular_core.Signal<string>;
38
+ /**
39
+ * Displayed message: explicit `error()` wins; otherwise (when `autoError()`)
40
+ * auto-resolved from the control's `ValidationErrors`. Plain method (not a
41
+ * computed): `ValidationErrors` is not a signal, so the control must be read
42
+ * fresh on every change-detection pass.
43
+ */
44
+ protected resolvedError(): string | undefined;
45
+ /**
46
+ * Whether the error is surfaced: message present + control invalid +
47
+ * touched/dirty/submitted. Gates the visual `<hlm-error>` (and
48
+ * `describedBy()`, where present) so sighted and screen-reader output agree.
49
+ */
50
+ protected showError(): boolean;
51
+ /**
52
+ * Bounds projected onto the picker's `Date` model. The cast keeps the
53
+ * template's generic inference on `Date` while passing runtime values
54
+ * (including `string` bounds, as in `eg-form-date-picker`) through untouched.
55
+ */
56
+ protected readonly $min: _angular_core.Signal<Date | undefined>;
57
+ protected readonly $max: _angular_core.Signal<Date | undefined>;
58
+ describedBy(): string | null;
59
+ userClass: _angular_core.InputSignal<ClassValue>;
60
+ labelClass: _angular_core.InputSignal<string>;
61
+ pickerClass: _angular_core.InputSignal<string>;
62
+ inputClass: _angular_core.InputSignal<string>;
63
+ errorClass: _angular_core.InputSignal<string>;
64
+ hintClass: _angular_core.InputSignal<string>;
65
+ $userClass: _angular_core.Signal<string>;
66
+ $labelClass: _angular_core.Signal<string>;
67
+ $pickerClass: _angular_core.Signal<string>;
68
+ $inputClass: _angular_core.Signal<string>;
69
+ $errorClass: _angular_core.Signal<string>;
70
+ $hintClass: _angular_core.Signal<string>;
71
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgFormDateRangePicker, never>;
72
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormDateRangePicker, "eg-form-date-range-picker", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "autoCloseOnEndSelection": { "alias": "autoCloseOnEndSelection"; "required": false; "isSignal": true; }; "captionLayout": { "alias": "captionLayout"; "required": false; "isSignal": true; }; "formatDates": { "alias": "formatDates"; "required": false; "isSignal": true; }; "transformDates": { "alias": "transformDates"; "required": false; "isSignal": true; }; "parseDate": { "alias": "parseDate"; "required": false; "isSignal": true; }; "formatInputDates": { "alias": "formatInputDates"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "pickerClass": { "alias": "pickerClass"; "required": false; "isSignal": true; }; "inputClass": { "alias": "inputClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
73
+ }
74
+
75
+ export { EgFormDateRangePicker };
@@ -338,6 +338,15 @@ export class CheckFieldComponent {
338
338
  }
339
339
  ```
340
340
 
341
+ ## Automatic error messages (`eg-form-*` wrappers)
342
+
343
+ The `eg-form-*` wrappers auto-resolve their displayed message from the control's `ValidationErrors` when no explicit `error` is set:
344
+
345
+ - Resolution order: explicit `error()` wins; otherwise (when `autoError()` is `true`, the default) the message is resolved from a custom dictionary entry, then the built-in defaults (`required`, `requiredTrue`, `email`, `minlength`, `maxlength`, `min`, `max`, `pattern`), then a generic "`label` is invalid" fallback. `error=""` is treated as unset.
346
+ - Set `autoError="false"` on a wrapper for manual-only messages.
347
+ - Global wording / custom validator keys (e.g. `usernameTaken`) and i18n are configured app-wide via `provideEgFormErrorMessages({...})` (merged over `DEFAULT_EG_FORM_ERROR_MESSAGES`, read with `injectEgFormErrorMessages()`); per-call overrides go through `resolveEgFormError(errors, label, messages)`.
348
+ - _When_ a message surfaces: wrappers render `<hlm-error>` (and point `aria-describedby` at it) only when the control is invalid + touched/dirty/submitted — otherwise the hint shows.
349
+
341
350
  ## Accessibility notes
342
351
 
343
352
  - The wrapper itself adds no label — always include a `<label hlmLabel>` (or `aria-label` on the control) so the field has an accessible name.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, computed, Directive, contentChild, contentChildren, effect, ChangeDetectionStrategy, Component, inject, APP_ID, Injectable, NgModule } from '@angular/core';
2
+ import { input, computed, Directive, contentChild, contentChildren, inject, signal, effect, ChangeDetectionStrategy, Component, InjectionToken, APP_ID, Injectable, NgModule } from '@angular/core';
3
3
  import { hlm } from '@egose/shadcn-theme-ng/utils';
4
- import { FormGroupDirective, ControlContainer } from '@angular/forms';
4
+ import { FormGroupDirective, NgForm, ControlContainer } from '@angular/forms';
5
5
  import * as i1 from '@spartan-ng/brain/field';
6
6
  import { BrnFieldControl, BrnField } from '@spartan-ng/brain/field';
7
7
 
@@ -30,10 +30,26 @@ class HlmFormField {
30
30
  ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
31
31
  errorChildren = contentChildren(HlmError, /* @ts-ignore */
32
32
  ...(ngDevMode ? [{ debugName: "errorChildren" }] : /* istanbul ignore next */ []));
33
+ _formGroupDirective = inject(FormGroupDirective, { optional: true });
34
+ _ngForm = inject(NgForm, { optional: true });
35
+ /**
36
+ * Tracks parent submit state. `FormGroupDirective.submitted` / `NgForm.submitted`
37
+ * are plain booleans (not signals), so mirror them into a signal via `ngDoCheck`
38
+ * to keep `_hasDisplayedMessage` reactive under OnPush.
39
+ */
40
+ _submitted = signal(false, /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "_submitted" }] : /* istanbul ignore next */ []));
42
+ ngDoCheck() {
43
+ const submitted = !!this._formGroupDirective?.submitted || !!this._ngForm?.submitted;
44
+ if (this._submitted() !== submitted)
45
+ this._submitted.set(submitted);
46
+ }
33
47
  _hasDisplayedMessage = computed(() => {
34
- const errors = this.control()?.errors();
48
+ const ctrl = this.control();
49
+ const errors = ctrl?.errors();
35
50
  const hasErrors = !!errors && Object.keys(errors).length > 0;
36
- return this.errorChildren() && this.errorChildren().length > 0 && hasErrors ? 'error' : 'hint';
51
+ const interacted = !!ctrl?.touched() || !!ctrl?.dirty() || this._submitted();
52
+ return this.errorChildren() && this.errorChildren().length > 0 && hasErrors && interacted ? 'error' : 'hint';
37
53
  }, /* @ts-ignore */
38
54
  ...(ngDevMode ? [{ debugName: "_hasDisplayedMessage" }] : /* istanbul ignore next */ []));
39
55
  constructor() {
@@ -99,6 +115,65 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
99
115
  }]
100
116
  }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
101
117
 
118
+ /** Fallback label used when the field has no `label()` set. */
119
+ const EG_FORM_DEFAULT_ERROR_LABEL = 'This field';
120
+ /** Canonical priority order for resolving the first displayed message. */
121
+ const ERROR_KEY_PRIORITY = ['required', 'requiredTrue', 'email', 'minlength', 'maxlength', 'min', 'max', 'pattern'];
122
+ /**
123
+ * Default English messages for Angular's built-in validator error keys.
124
+ * App-wide wording/i18n is customized via {@link provideEgFormErrorMessages}.
125
+ */
126
+ const DEFAULT_EG_FORM_ERROR_MESSAGES = {
127
+ required: (_, label) => `${label} is required`,
128
+ requiredTrue: (_, label) => `${label} is required`,
129
+ email: () => 'Please enter a valid email address',
130
+ minlength: (params, label) => `${label} must be at least ${params?.['requiredLength']} characters`,
131
+ maxlength: (params, label) => `${label} must be at most ${params?.['requiredLength']} characters`,
132
+ min: (params, label) => `${label} must be at least ${params?.['min']}`,
133
+ max: (params, label) => `${label} must be at most ${params?.['max']}`,
134
+ pattern: (_, label) => `${label} is invalid`,
135
+ };
136
+ const EgFormErrorMessagesToken = new InjectionToken('EgFormErrorMessages');
137
+ /**
138
+ * Overrides/adds error messages app-wide (e.g. custom validator keys or i18n).
139
+ * Merged over {@link DEFAULT_EG_FORM_ERROR_MESSAGES} at injection time.
140
+ *
141
+ * ```ts
142
+ * provideEgFormErrorMessages({ usernameTaken: 'This username is already taken' })
143
+ * ```
144
+ */
145
+ function provideEgFormErrorMessages(messages) {
146
+ return { provide: EgFormErrorMessagesToken, useValue: { ...messages } };
147
+ }
148
+ /** Merged custom + default messages (defaults win only when not overridden). */
149
+ function injectEgFormErrorMessages() {
150
+ return { ...DEFAULT_EG_FORM_ERROR_MESSAGES, ...(inject(EgFormErrorMessagesToken, { optional: true }) ?? {}) };
151
+ }
152
+ /**
153
+ * Resolves the first displayable message for a `ValidationErrors` object.
154
+ * Returns `undefined` when there are no errors. Unknown keys fall back to a
155
+ * generic `${label} is invalid` message (or a custom dictionary entry).
156
+ */
157
+ function resolveEgFormError(errors, label, messages = DEFAULT_EG_FORM_ERROR_MESSAGES) {
158
+ if (!errors)
159
+ return undefined;
160
+ const keys = Object.keys(errors);
161
+ if (keys.length === 0)
162
+ return undefined;
163
+ const resolvedLabel = label || EG_FORM_DEFAULT_ERROR_LABEL;
164
+ const ordered = [
165
+ ...ERROR_KEY_PRIORITY.filter((key) => key in errors),
166
+ ...keys.filter((key) => !ERROR_KEY_PRIORITY.includes(key)),
167
+ ];
168
+ const key = ordered[0];
169
+ const entry = messages[key];
170
+ if (typeof entry === 'function')
171
+ return entry((errors[key] ?? {}), resolvedLabel);
172
+ if (typeof entry === 'string')
173
+ return entry;
174
+ return `${resolvedLabel} is invalid`;
175
+ }
176
+
102
177
  class HlmFormIdGenerator {
103
178
  appId = inject(APP_ID);
104
179
  nextId = 0;
@@ -134,5 +209,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
134
209
  * Generated bundle index. Do not edit.
135
210
  */
136
211
 
137
- export { HlmError, HlmFormField, HlmFormFieldImports, HlmFormFieldModule, HlmFormIdGenerator, HlmHint };
212
+ export { DEFAULT_EG_FORM_ERROR_MESSAGES, EG_FORM_DEFAULT_ERROR_LABEL, HlmError, HlmFormField, HlmFormFieldImports, HlmFormFieldModule, HlmFormIdGenerator, HlmHint, injectEgFormErrorMessages, provideEgFormErrorMessages, resolveEgFormError };
138
213
 
@@ -1,7 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
+ import { DoCheck, ValueProvider } from '@angular/core';
2
3
  import { ClassValue } from 'clsx';
3
4
  import * as i1 from '@spartan-ng/brain/field';
4
5
  import { BrnFieldControl } from '@spartan-ng/brain/field';
6
+ import { ValidationErrors } from '@angular/forms';
5
7
 
6
8
  declare class HlmError {
7
9
  readonly userClass: i0.InputSignal<ClassValue>;
@@ -10,11 +12,20 @@ declare class HlmError {
10
12
  static ɵdir: i0.ɵɵDirectiveDeclaration<HlmError, "hlm-error", never, { "userClass": { "alias": "class"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
11
13
  }
12
14
 
13
- declare class HlmFormField {
15
+ declare class HlmFormField implements DoCheck {
14
16
  readonly userClass: i0.InputSignal<ClassValue>;
15
17
  protected readonly _computedClass: i0.Signal<string>;
16
18
  readonly control: i0.Signal<BrnFieldControl | undefined>;
17
19
  readonly errorChildren: i0.Signal<readonly HlmError[]>;
20
+ private readonly _formGroupDirective;
21
+ private readonly _ngForm;
22
+ /**
23
+ * Tracks parent submit state. `FormGroupDirective.submitted` / `NgForm.submitted`
24
+ * are plain booleans (not signals), so mirror them into a signal via `ngDoCheck`
25
+ * to keep `_hasDisplayedMessage` reactive under OnPush.
26
+ */
27
+ protected readonly _submitted: i0.WritableSignal<boolean>;
28
+ ngDoCheck(): void;
18
29
  protected readonly _hasDisplayedMessage: i0.Signal<"error" | "hint">;
19
30
  constructor();
20
31
  static ɵfac: i0.ɵɵFactoryDeclaration<HlmFormField, never>;
@@ -28,6 +39,42 @@ declare class HlmHint {
28
39
  static ɵdir: i0.ɵɵDirectiveDeclaration<HlmHint, "hlm-hint", never, { "userClass": { "alias": "class"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
29
40
  }
30
41
 
42
+ /**
43
+ * Factory for a single error message. Receives the validator's error params
44
+ * (e.g. `{ requiredLength, actualLength }` for `minlength`) and the field label.
45
+ */
46
+ type EgFormErrorMessageFn = (params: Record<string, unknown>, label: string) => string;
47
+ /**
48
+ * Dictionary of `ValidationErrors` key → message or message factory.
49
+ * Custom validator keys (e.g. `usernameTaken`) are covered by adding entries
50
+ * here, either globally via {@link provideEgFormErrorMessages} or per call.
51
+ */
52
+ type EgFormErrorMessages = Record<string, string | EgFormErrorMessageFn>;
53
+ /** Fallback label used when the field has no `label()` set. */
54
+ declare const EG_FORM_DEFAULT_ERROR_LABEL = "This field";
55
+ /**
56
+ * Default English messages for Angular's built-in validator error keys.
57
+ * App-wide wording/i18n is customized via {@link provideEgFormErrorMessages}.
58
+ */
59
+ declare const DEFAULT_EG_FORM_ERROR_MESSAGES: EgFormErrorMessages;
60
+ /**
61
+ * Overrides/adds error messages app-wide (e.g. custom validator keys or i18n).
62
+ * Merged over {@link DEFAULT_EG_FORM_ERROR_MESSAGES} at injection time.
63
+ *
64
+ * ```ts
65
+ * provideEgFormErrorMessages({ usernameTaken: 'This username is already taken' })
66
+ * ```
67
+ */
68
+ declare function provideEgFormErrorMessages(messages: EgFormErrorMessages): ValueProvider;
69
+ /** Merged custom + default messages (defaults win only when not overridden). */
70
+ declare function injectEgFormErrorMessages(): EgFormErrorMessages;
71
+ /**
72
+ * Resolves the first displayable message for a `ValidationErrors` object.
73
+ * Returns `undefined` when there are no errors. Unknown keys fall back to a
74
+ * generic `${label} is invalid` message (or a custom dictionary entry).
75
+ */
76
+ declare function resolveEgFormError(errors: ValidationErrors | null | undefined, label?: string | null, messages?: EgFormErrorMessages): string | undefined;
77
+
31
78
  declare class HlmFormIdGenerator {
32
79
  private readonly appId;
33
80
  private nextId;
@@ -43,4 +90,5 @@ declare class HlmFormFieldModule {
43
90
  static ɵinj: i0.ɵɵInjectorDeclaration<HlmFormFieldModule>;
44
91
  }
45
92
 
46
- export { HlmError, HlmFormField, HlmFormFieldImports, HlmFormFieldModule, HlmFormIdGenerator, HlmHint };
93
+ export { DEFAULT_EG_FORM_ERROR_MESSAGES, EG_FORM_DEFAULT_ERROR_LABEL, HlmError, HlmFormField, HlmFormFieldImports, HlmFormFieldModule, HlmFormIdGenerator, HlmHint, injectEgFormErrorMessages, provideEgFormErrorMessages, resolveEgFormError };
94
+ export type { EgFormErrorMessageFn, EgFormErrorMessages };
@@ -14,6 +14,13 @@ class EgFormField {
14
14
  form = this.formGroupDirective.form;
15
15
  statusSignal = signal(null, /* @ts-ignore */
16
16
  ...(ngDevMode ? [{ debugName: "statusSignal" }] : /* istanbul ignore next */ []));
17
+ /**
18
+ * Mirrors `FormGroupDirective.submitted` (plain boolean) into a signal so
19
+ * `hasError` stays reactive under OnPush and errors appear after submit
20
+ * even when the control was never touched/dirty.
21
+ */
22
+ submittedSignal = signal(false, /* @ts-ignore */
23
+ ...(ngDevMode ? [{ debugName: "submittedSignal" }] : /* istanbul ignore next */ []));
17
24
  sub = new Subscription();
18
25
  ngAfterContentInit() {
19
26
  const ctrlDir = this.control();
@@ -24,6 +31,11 @@ class EgFormField {
24
31
  this.statusSignal.set(ctrlDir.control.status);
25
32
  }
26
33
  }
34
+ ngDoCheck() {
35
+ const submitted = !!this.formGroupDirective?.submitted;
36
+ if (this.submittedSignal() !== submitted)
37
+ this.submittedSignal.set(submitted);
38
+ }
27
39
  ngOnDestroy() {
28
40
  this.sub.unsubscribe();
29
41
  }
@@ -32,7 +44,9 @@ class EgFormField {
32
44
  if (!ctrlDir)
33
45
  return false;
34
46
  this.statusSignal();
35
- return !!ctrlDir.control.errors && (ctrlDir.control.touched || ctrlDir.control.dirty);
47
+ this.submittedSignal();
48
+ return (!!ctrlDir.control.errors &&
49
+ (ctrlDir.control.touched || ctrlDir.control.dirty || !!this.formGroupDirective?.submitted));
36
50
  }, /* @ts-ignore */
37
51
  ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
38
52
  firstErrorKey = computed(() => {
@@ -1,17 +1,24 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { AfterContentInit, OnDestroy } from '@angular/core';
2
+ import { AfterContentInit, DoCheck, OnDestroy } from '@angular/core';
3
3
  import { FormControlName, FormGroup } from '@angular/forms';
4
4
  import { ClassValue } from 'clsx';
5
5
 
6
- declare class EgFormField implements AfterContentInit, OnDestroy {
6
+ declare class EgFormField implements AfterContentInit, DoCheck, OnDestroy {
7
7
  readonly userClass: _angular_core.InputSignal<ClassValue>;
8
8
  protected readonly _computedClass: _angular_core.Signal<string>;
9
9
  readonly control: _angular_core.Signal<FormControlName | undefined>;
10
10
  private readonly formGroupDirective;
11
11
  readonly form: FormGroup;
12
12
  private statusSignal;
13
+ /**
14
+ * Mirrors `FormGroupDirective.submitted` (plain boolean) into a signal so
15
+ * `hasError` stays reactive under OnPush and errors appear after submit
16
+ * even when the control was never touched/dirty.
17
+ */
18
+ private submittedSignal;
13
19
  private sub;
14
20
  ngAfterContentInit(): void;
21
+ ngDoCheck(): void;
15
22
  ngOnDestroy(): void;
16
23
  readonly hasError: _angular_core.Signal<boolean>;
17
24
  readonly firstErrorKey: _angular_core.Signal<string | null>;