@egose/shadcn-theme-ng-tw 0.3.0 → 0.5.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.
- package/README.md +1 -1
- package/form-autocomplete/README.md +20 -19
- package/form-autocomplete/fesm2022/form-autocomplete.mjs +47 -14
- package/form-autocomplete/types/form-autocomplete.d.ts +22 -1
- package/form-checkbox/README.md +20 -19
- package/form-checkbox/fesm2022/form-checkbox.mjs +102 -20
- package/form-checkbox/types/form-checkbox.d.ts +43 -3
- package/form-combobox/README.md +21 -20
- package/form-combobox/fesm2022/form-combobox.mjs +47 -14
- package/form-combobox/types/form-combobox.d.ts +22 -1
- package/form-date-picker/README.md +28 -27
- package/form-date-picker/fesm2022/form-date-picker.mjs +48 -20
- package/form-date-picker/types/form-date-picker.d.ts +22 -1
- package/form-date-picker-multi/README.md +28 -27
- package/form-date-picker-multi/fesm2022/form-date-picker-multi.mjs +48 -20
- package/form-date-picker-multi/types/form-date-picker-multi.d.ts +22 -1
- package/form-date-range-picker/README.md +28 -27
- package/form-date-range-picker/fesm2022/form-date-range-picker.mjs +48 -20
- package/form-date-range-picker/types/form-date-range-picker.d.ts +22 -1
- package/form-field/README.md +9 -0
- package/form-field/fesm2022/form-field.mjs +97 -5
- package/form-field/types/form-field.d.ts +96 -2
- package/form-field-simple/fesm2022/form-field-simple.mjs +15 -1
- package/form-field-simple/types/form-field-simple.d.ts +9 -2
- package/form-input-otp/README.md +17 -16
- package/form-input-otp/fesm2022/form-input-otp.mjs +46 -13
- package/form-input-otp/types/form-input-otp.d.ts +22 -1
- package/form-month-year-picker/README.md +26 -25
- package/form-month-year-picker/fesm2022/form-month-year-picker.mjs +48 -20
- package/form-month-year-picker/types/form-month-year-picker.d.ts +22 -1
- package/form-native-select/README.md +18 -17
- package/form-native-select/fesm2022/form-native-select.mjs +61 -21
- package/form-native-select/types/form-native-select.d.ts +22 -1
- package/form-phone-input/README.md +23 -22
- package/form-phone-input/fesm2022/form-phone-input.mjs +47 -19
- package/form-phone-input/types/form-phone-input.d.ts +22 -1
- package/form-radio-group/README.md +18 -17
- package/form-radio-group/fesm2022/form-radio-group.mjs +47 -19
- package/form-radio-group/types/form-radio-group.d.ts +22 -1
- package/form-searchable-multiselect/README.md +18 -17
- package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +47 -19
- package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +22 -1
- package/form-select/README.md +20 -19
- package/form-select/fesm2022/form-select.mjs +47 -19
- package/form-select/types/form-select.d.ts +22 -1
- package/form-slider/README.md +19 -18
- package/form-slider/fesm2022/form-slider.mjs +46 -13
- package/form-slider/types/form-slider.d.ts +22 -1
- package/form-switch/README.md +16 -15
- package/form-switch/fesm2022/form-switch.mjs +47 -19
- package/form-switch/types/form-switch.d.ts +22 -1
- package/form-text-input/README.md +27 -26
- package/form-text-input/fesm2022/form-text-input.mjs +47 -19
- package/form-text-input/types/form-text-input.d.ts +22 -1
- package/form-textarea/README.md +23 -22
- package/form-textarea/fesm2022/form-textarea.mjs +47 -19
- package/form-textarea/types/form-textarea.d.ts +22 -1
- package/form-toggle/README.md +17 -16
- package/form-toggle/fesm2022/form-toggle.mjs +48 -11
- package/form-toggle/types/form-toggle.d.ts +22 -3
- package/form-toggle-group/README.md +20 -19
- package/form-toggle-group/fesm2022/form-toggle-group.mjs +47 -19
- package/form-toggle-group/types/form-toggle-group.d.ts +22 -1
- package/package.json +5 -1
- package/stepper/README.md +353 -0
- package/stepper/fesm2022/stepper.mjs +686 -0
- package/stepper/package.json +24 -0
- package/stepper/types/stepper.d.ts +116 -0
|
@@ -84,33 +84,34 @@ Selector (from source): `eg-form-date-range-picker` (standalone component, host
|
|
|
84
84
|
|
|
85
85
|
### `EgFormDateRangePicker` — `eg-form-date-range-picker`
|
|
86
86
|
|
|
87
|
-
| Input | Type | Default | Description
|
|
88
|
-
| ------------------------- | ------------------------------------------------- | --------------------- |
|
|
89
|
-
| `controlName` | `string` | `''` | `formControlName` key inside the parent `FormGroup`. **Required.**
|
|
90
|
-
| `label` | `string \| undefined` | — | Label text (hidden when omitted).
|
|
91
|
-
| `error` | `string \| undefined` | — | Error text shown when invalid.
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
87
|
+
| Input | Type | Default | Description |
|
|
88
|
+
| ------------------------- | ------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
89
|
+
| `controlName` | `string` | `''` | `formControlName` key inside the parent `FormGroup`. **Required.** |
|
|
90
|
+
| `label` | `string \| undefined` | — | Label text (hidden when omitted). |
|
|
91
|
+
| `error` | `string \| undefined` | — | Error text shown when invalid. |
|
|
92
|
+
| `autoError` | `boolean` | `true` | Auto-resolve the message from the control's `ValidationErrors` when `error` is unset. Explicit `error` always wins; `error=""` counts as unset. Global wording via `provideEgFormErrorMessages`. |
|
|
93
|
+
| `hint` | `string \| undefined` | — | Hint text shown otherwise. |
|
|
94
|
+
| `controlId` | `string \| undefined` | — | Explicit id (first priority for `effectiveId`). |
|
|
95
|
+
| `id` | `string \| undefined` | — | Fallback id (second priority). |
|
|
96
|
+
| `name` | `string \| undefined` | — | Native `name` forwarded to the inner input (`controlName` wins when set, like `eg-form-text-input`). |
|
|
97
|
+
| `placeholder` | `string` | `'Pick a date range'` | Input placeholder. |
|
|
98
|
+
| `readonly` | `boolean` | `false` | Native `readonly` forwarded to the inner input (locks typed text; use `disabled` to lock the calendar too). |
|
|
99
|
+
| `disabled` | `boolean` | `false` | Locks interaction via picker `disabled` (form control stays enabled). |
|
|
100
|
+
| `required` | `boolean` | `false` | Shows a red `*` next to the label. |
|
|
101
|
+
| `min` | `Date \| string \| null` | `null` | Minimum date forwarded to `hlm-date-range-picker`. |
|
|
102
|
+
| `max` | `Date \| string \| null` | `null` | Maximum date forwarded to `hlm-date-range-picker`. |
|
|
103
|
+
| `autoCloseOnEndSelection` | `boolean` | config default | Forwarded to `hlm-date-range-picker` (falls back to `provideHlmDateRangePickerConfig`, default `false`). |
|
|
104
|
+
| `captionLayout` | union | `'label'` | Forwarded to `hlm-date-range-picker` (`'dropdown' \| 'label' \| 'dropdown-months' \| 'dropdown-years'`). |
|
|
105
|
+
| `formatDates` | `(dates: [Date \| null, Date \| null]) => string` | config default | Display format, forwarded to `hlm-date-range-picker` (falls back to `provideHlmDateRangePickerConfig`). |
|
|
106
|
+
| `transformDates` | `(dates: [Date, Date]) => [Date, Date]` | config default | Model transform, forwarded to `hlm-date-range-picker` (falls back to `provideHlmDateRangePickerConfig`). |
|
|
107
|
+
| `parseDate` | `(value: string) => [Date, Date] \| null` | config default | Input parsing, forwarded to `hlm-date-range-input` (falls back to `provideHlmDateRangePickerConfig`). |
|
|
108
|
+
| `formatInputDates` | `(dates: [Date \| null, Date \| null]) => string` | config default | Edit format while focused, forwarded to `hlm-date-range-input` (falls back to `provideHlmDateRangePickerConfig`). |
|
|
109
|
+
| `class` | `ClassValue` | `''` | Extra host classes (base `tw:w-full`). |
|
|
110
|
+
| `labelClass` | `string` | `''` | Extra label classes (base `tw:mb-1`). |
|
|
111
|
+
| `pickerClass` | `string` | `''` | Extra picker-container classes (base `tw:mb-1`). |
|
|
112
|
+
| `inputClass` | `string` | `''` | Extra inner-input classes (e.g. `tw:text-lg` for text size). |
|
|
113
|
+
| `errorClass` | `string` | `''` | Extra error classes (base `tw:mt-0`). |
|
|
114
|
+
| `hintClass` | `string` | `''` | Extra hint classes (base `tw:mt-0`). |
|
|
114
115
|
|
|
115
116
|
| Member | Description |
|
|
116
117
|
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -2,15 +2,17 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { inject, input, computed, Component } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormGroupDirective, ReactiveFormsModule, ControlContainer } from '@angular/forms';
|
|
5
|
-
import { HlmFormIdGenerator, HlmFormField, HlmError, HlmHint } from '@egose/shadcn-theme-ng-tw/form-field';
|
|
5
|
+
import { injectEgFormErrorMessages, HlmFormIdGenerator, injectEgFormSharedConfig, resolveEgFormError, HlmFormField, HlmError, HlmHint } from '@egose/shadcn-theme-ng-tw/form-field';
|
|
6
6
|
import { HlmLabel } from '@egose/shadcn-theme-ng-tw/label';
|
|
7
7
|
import { injectHlmDateRangePickerConfig, HlmDateRangePicker, HlmDateRangeInput } from '@egose/shadcn-theme-ng-tw/date-picker';
|
|
8
8
|
import { hlm } from '@egose/shadcn-theme-ng-tw/utils';
|
|
9
9
|
|
|
10
10
|
class EgFormDateRangePicker {
|
|
11
11
|
formGroupDirective = inject(FormGroupDirective);
|
|
12
|
+
_errorMessages = injectEgFormErrorMessages();
|
|
12
13
|
generatedId = inject(HlmFormIdGenerator).generate('eg-form-date-range-picker');
|
|
13
14
|
_config = injectHlmDateRangePickerConfig();
|
|
15
|
+
_shared = injectEgFormSharedConfig();
|
|
14
16
|
// Inputs
|
|
15
17
|
label = input(undefined, /* @ts-ignore */
|
|
16
18
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
@@ -20,6 +22,13 @@ class EgFormDateRangePicker {
|
|
|
20
22
|
...(ngDevMode ? [{ debugName: "controlName" }] : /* istanbul ignore next */ []));
|
|
21
23
|
error = input(undefined, /* @ts-ignore */
|
|
22
24
|
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
25
|
+
/**
|
|
26
|
+
* Auto-resolve the displayed message from the control's `ValidationErrors`
|
|
27
|
+
* when `error()` is unset. Explicit `error()` always wins. Set to `false`
|
|
28
|
+
* for manual-only messages.
|
|
29
|
+
*/
|
|
30
|
+
autoError = input(true, /* @ts-ignore */
|
|
31
|
+
...(ngDevMode ? [{ debugName: "autoError" }] : /* istanbul ignore next */ []));
|
|
23
32
|
hint = input(undefined, /* @ts-ignore */
|
|
24
33
|
...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
25
34
|
// HTML attributes
|
|
@@ -59,6 +68,32 @@ class EgFormDateRangePicker {
|
|
|
59
68
|
...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
|
|
60
69
|
hintId = computed(() => `${this.effectiveId()}-hint`, /* @ts-ignore */
|
|
61
70
|
...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
|
|
71
|
+
/**
|
|
72
|
+
* Displayed message: explicit `error()` wins; otherwise (when `autoError()`)
|
|
73
|
+
* auto-resolved from the control's `ValidationErrors`. Plain method (not a
|
|
74
|
+
* computed): `ValidationErrors` is not a signal, so the control must be read
|
|
75
|
+
* fresh on every change-detection pass.
|
|
76
|
+
*/
|
|
77
|
+
resolvedError() {
|
|
78
|
+
const explicit = this.error();
|
|
79
|
+
if (explicit)
|
|
80
|
+
return explicit;
|
|
81
|
+
if (!this.autoError())
|
|
82
|
+
return undefined;
|
|
83
|
+
const control = this.formGroupDirective.form.get(this.controlName());
|
|
84
|
+
return resolveEgFormError(control?.errors ?? null, this.label(), this._errorMessages);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether the error is surfaced: message present + control invalid +
|
|
88
|
+
* touched/dirty/submitted. Gates the visual `<hlm-error>` (and
|
|
89
|
+
* `describedBy()`, where present) so sighted and screen-reader output agree.
|
|
90
|
+
*/
|
|
91
|
+
showError() {
|
|
92
|
+
const control = this.formGroupDirective.form.get(this.controlName());
|
|
93
|
+
return (!!this.resolvedError() &&
|
|
94
|
+
!!control?.invalid &&
|
|
95
|
+
(control.dirty || control.touched || this.formGroupDirective.submitted));
|
|
96
|
+
}
|
|
62
97
|
/**
|
|
63
98
|
* Bounds projected onto the picker's `Date` model. The cast keeps the
|
|
64
99
|
* template's generic inference on `Date` while passing runtime values
|
|
@@ -69,12 +104,7 @@ class EgFormDateRangePicker {
|
|
|
69
104
|
$max = computed(() => this.max(), /* @ts-ignore */
|
|
70
105
|
...(ngDevMode ? [{ debugName: "$max" }] : /* istanbul ignore next */ []));
|
|
71
106
|
describedBy() {
|
|
72
|
-
|
|
73
|
-
return this.error() && control?.invalid && (control.dirty || control.touched)
|
|
74
|
-
? this.errorId()
|
|
75
|
-
: this.hint()
|
|
76
|
-
? this.hintId()
|
|
77
|
-
: null;
|
|
107
|
+
return this.showError() ? this.errorId() : this.hint() ? this.hintId() : null;
|
|
78
108
|
}
|
|
79
109
|
// Styling
|
|
80
110
|
userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
|
|
@@ -91,21 +121,20 @@ class EgFormDateRangePicker {
|
|
|
91
121
|
// Computed classes (library base < global config < per-instance)
|
|
92
122
|
$userClass = computed(() => hlm('tw:w-full', this.userClass()), /* @ts-ignore */
|
|
93
123
|
...(ngDevMode ? [{ debugName: "$userClass" }] : /* istanbul ignore next */ []));
|
|
94
|
-
$labelClass = computed(() => hlm('tw:mb-1', this._config.labelClass, this.labelClass()), /* @ts-ignore */
|
|
124
|
+
$labelClass = computed(() => hlm('tw:mb-1', this._shared.labelClass, this._config.labelClass, this.labelClass()), /* @ts-ignore */
|
|
95
125
|
...(ngDevMode ? [{ debugName: "$labelClass" }] : /* istanbul ignore next */ []));
|
|
96
|
-
$pickerClass = computed(() => hlm('tw:mb-1', this._config.pickerClass, this.pickerClass()), /* @ts-ignore */
|
|
126
|
+
$pickerClass = computed(() => hlm('tw:mb-1', this._shared.pickerClass, this._config.pickerClass, this.pickerClass()), /* @ts-ignore */
|
|
97
127
|
...(ngDevMode ? [{ debugName: "$pickerClass" }] : /* istanbul ignore next */ []));
|
|
98
|
-
$inputClass = computed(() => hlm(this._config.inputClass, this.inputClass()), /* @ts-ignore */
|
|
128
|
+
$inputClass = computed(() => hlm(this._shared.inputClass, this._config.inputClass, this.inputClass()), /* @ts-ignore */
|
|
99
129
|
...(ngDevMode ? [{ debugName: "$inputClass" }] : /* istanbul ignore next */ []));
|
|
100
|
-
$errorClass = computed(() => hlm('tw:mt-0', this._config.errorClass, this.errorClass()), /* @ts-ignore */
|
|
130
|
+
$errorClass = computed(() => hlm('tw:mt-0', this._shared.errorClass, this._config.errorClass, this.errorClass()), /* @ts-ignore */
|
|
101
131
|
...(ngDevMode ? [{ debugName: "$errorClass" }] : /* istanbul ignore next */ []));
|
|
102
|
-
$hintClass = computed(() => hlm('tw:mt-0', this._config.hintClass, this.hintClass()), /* @ts-ignore */
|
|
132
|
+
$hintClass = computed(() => hlm('tw:mt-0', this._shared.hintClass, this._config.hintClass, this.hintClass()), /* @ts-ignore */
|
|
103
133
|
...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
|
|
104
134
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
105
|
-
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 }, 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: `
|
|
135
|
+
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: `
|
|
106
136
|
@let lbl = label();
|
|
107
137
|
@let cnm = controlName();
|
|
108
|
-
@let err = error();
|
|
109
138
|
@let hnt = hint();
|
|
110
139
|
@let rqrd = required();
|
|
111
140
|
|
|
@@ -143,9 +172,9 @@ class EgFormDateRangePicker {
|
|
|
143
172
|
/>
|
|
144
173
|
</hlm-date-range-picker>
|
|
145
174
|
|
|
146
|
-
@if (
|
|
175
|
+
@if (showError()) {
|
|
147
176
|
<hlm-error [id]="errorId()" [class]="$errorClass()">
|
|
148
|
-
{{
|
|
177
|
+
{{ resolvedError() }}
|
|
149
178
|
</hlm-error>
|
|
150
179
|
}
|
|
151
180
|
|
|
@@ -170,7 +199,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
170
199
|
template: `
|
|
171
200
|
@let lbl = label();
|
|
172
201
|
@let cnm = controlName();
|
|
173
|
-
@let err = error();
|
|
174
202
|
@let hnt = hint();
|
|
175
203
|
@let rqrd = required();
|
|
176
204
|
|
|
@@ -208,9 +236,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
208
236
|
/>
|
|
209
237
|
</hlm-date-range-picker>
|
|
210
238
|
|
|
211
|
-
@if (
|
|
239
|
+
@if (showError()) {
|
|
212
240
|
<hlm-error [id]="errorId()" [class]="$errorClass()">
|
|
213
|
-
{{
|
|
241
|
+
{{ resolvedError() }}
|
|
214
242
|
</hlm-error>
|
|
215
243
|
}
|
|
216
244
|
|
|
@@ -222,7 +250,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
222
250
|
</hlm-form-field>
|
|
223
251
|
`,
|
|
224
252
|
}]
|
|
225
|
-
}], 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 }] }], 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 }] }] } });
|
|
253
|
+
}], 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 }] }] } });
|
|
226
254
|
|
|
227
255
|
/*
|
|
228
256
|
* Public API Surface of form-date-range-picker
|
|
@@ -4,12 +4,20 @@ import { ClassValue } from 'clsx';
|
|
|
4
4
|
type DateRangeCaptionLayout = 'dropdown' | 'label' | 'dropdown-months' | 'dropdown-years';
|
|
5
5
|
declare class EgFormDateRangePicker {
|
|
6
6
|
private readonly formGroupDirective;
|
|
7
|
+
private readonly _errorMessages;
|
|
7
8
|
private readonly generatedId;
|
|
8
9
|
private readonly _config;
|
|
10
|
+
private readonly _shared;
|
|
9
11
|
label: _angular_core.InputSignal<string | undefined>;
|
|
10
12
|
controlId: _angular_core.InputSignal<string | undefined>;
|
|
11
13
|
controlName: _angular_core.InputSignal<string>;
|
|
12
14
|
error: _angular_core.InputSignal<string | undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* Auto-resolve the displayed message from the control's `ValidationErrors`
|
|
17
|
+
* when `error()` is unset. Explicit `error()` always wins. Set to `false`
|
|
18
|
+
* for manual-only messages.
|
|
19
|
+
*/
|
|
20
|
+
autoError: _angular_core.InputSignal<boolean>;
|
|
13
21
|
hint: _angular_core.InputSignal<string | undefined>;
|
|
14
22
|
id: _angular_core.InputSignal<string | undefined>;
|
|
15
23
|
name: _angular_core.InputSignal<string | undefined>;
|
|
@@ -28,6 +36,19 @@ declare class EgFormDateRangePicker {
|
|
|
28
36
|
readonly effectiveId: _angular_core.Signal<string>;
|
|
29
37
|
readonly errorId: _angular_core.Signal<string>;
|
|
30
38
|
readonly hintId: _angular_core.Signal<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Displayed message: explicit `error()` wins; otherwise (when `autoError()`)
|
|
41
|
+
* auto-resolved from the control's `ValidationErrors`. Plain method (not a
|
|
42
|
+
* computed): `ValidationErrors` is not a signal, so the control must be read
|
|
43
|
+
* fresh on every change-detection pass.
|
|
44
|
+
*/
|
|
45
|
+
protected resolvedError(): string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the error is surfaced: message present + control invalid +
|
|
48
|
+
* touched/dirty/submitted. Gates the visual `<hlm-error>` (and
|
|
49
|
+
* `describedBy()`, where present) so sighted and screen-reader output agree.
|
|
50
|
+
*/
|
|
51
|
+
protected showError(): boolean;
|
|
31
52
|
/**
|
|
32
53
|
* Bounds projected onto the picker's `Date` model. The cast keeps the
|
|
33
54
|
* template's generic inference on `Date` while passing runtime values
|
|
@@ -49,7 +70,7 @@ declare class EgFormDateRangePicker {
|
|
|
49
70
|
$errorClass: _angular_core.Signal<string>;
|
|
50
71
|
$hintClass: _angular_core.Signal<string>;
|
|
51
72
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgFormDateRangePicker, never>;
|
|
52
|
-
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; }; "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
|
+
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>;
|
|
53
74
|
}
|
|
54
75
|
|
|
55
76
|
export { EgFormDateRangePicker };
|
package/form-field/README.md
CHANGED
|
@@ -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,
|
|
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-tw/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
|
|
48
|
+
const ctrl = this.control();
|
|
49
|
+
const errors = ctrl?.errors();
|
|
35
50
|
const hasErrors = !!errors && Object.keys(errors).length > 0;
|
|
36
|
-
|
|
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;
|
|
@@ -113,6 +188,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
113
188
|
args: [{ providedIn: 'root' }]
|
|
114
189
|
}] });
|
|
115
190
|
|
|
191
|
+
const EgFormSharedConfigToken = new InjectionToken('EgFormSharedConfig');
|
|
192
|
+
/**
|
|
193
|
+
* Sets shared label / error / hint / control classes app-wide, e.g.:
|
|
194
|
+
*
|
|
195
|
+
* ```ts
|
|
196
|
+
* provideEgFormSharedConfig({ labelClass: 'tw:font-semibold', errorClass: 'tw:text-red-600' })
|
|
197
|
+
* provideEgFormSharedConfig({ inputClass: 'tw:border-dashed', selectClass: 'tw:rounded-none' })
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
function provideEgFormSharedConfig(config) {
|
|
201
|
+
return { provide: EgFormSharedConfigToken, useValue: { ...config } };
|
|
202
|
+
}
|
|
203
|
+
/** Merged shared config (empty object when not provided). */
|
|
204
|
+
function injectEgFormSharedConfig() {
|
|
205
|
+
return inject(EgFormSharedConfigToken, { optional: true }) ?? {};
|
|
206
|
+
}
|
|
207
|
+
|
|
116
208
|
/*
|
|
117
209
|
* Public API Surface of form-field
|
|
118
210
|
*/
|
|
@@ -134,5 +226,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
134
226
|
* Generated bundle index. Do not edit.
|
|
135
227
|
*/
|
|
136
228
|
|
|
137
|
-
export { HlmError, HlmFormField, HlmFormFieldImports, HlmFormFieldModule, HlmFormIdGenerator, HlmHint };
|
|
229
|
+
export { DEFAULT_EG_FORM_ERROR_MESSAGES, EG_FORM_DEFAULT_ERROR_LABEL, HlmError, HlmFormField, HlmFormFieldImports, HlmFormFieldModule, HlmFormIdGenerator, HlmHint, injectEgFormErrorMessages, injectEgFormSharedConfig, provideEgFormErrorMessages, provideEgFormSharedConfig, resolveEgFormError };
|
|
138
230
|
|
|
@@ -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;
|
|
@@ -36,6 +83,52 @@ declare class HlmFormIdGenerator {
|
|
|
36
83
|
static ɵprov: i0.ɵɵInjectableDeclaration<HlmFormIdGenerator>;
|
|
37
84
|
}
|
|
38
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Global styling defaults applied to every `eg-form-*` wrapper that follows
|
|
88
|
+
* the shared label / control / error / hint skeleton.
|
|
89
|
+
*
|
|
90
|
+
* `labelClass` / `errorClass` / `hintClass` apply to every wrapper.
|
|
91
|
+
* Each control slot only applies to wrappers that declare the matching slot
|
|
92
|
+
* (e.g. `inputClass` → text-input, phone-input, autocomplete, date-pickers;
|
|
93
|
+
* `selectClass` → select, native-select; `pickerClass` → date-pickers).
|
|
94
|
+
*
|
|
95
|
+
* Each slot is merged under the matching per-component global config and the
|
|
96
|
+
* per-instance `*Class` input:
|
|
97
|
+
*
|
|
98
|
+
* library base < shared global < component global < per-instance
|
|
99
|
+
*
|
|
100
|
+
* `eg-form-toggle` picks up only the `labelClass` / `errorClass` / `hintClass`
|
|
101
|
+
* slots (it keeps its own CVA / exclusive error-hint behavior and local
|
|
102
|
+
* `toggleClass`).
|
|
103
|
+
*/
|
|
104
|
+
interface EgFormSharedConfig {
|
|
105
|
+
labelClass?: string;
|
|
106
|
+
errorClass?: string;
|
|
107
|
+
hintClass?: string;
|
|
108
|
+
inputClass?: string;
|
|
109
|
+
textareaClass?: string;
|
|
110
|
+
selectClass?: string;
|
|
111
|
+
controlClass?: string;
|
|
112
|
+
chipsClass?: string;
|
|
113
|
+
checkboxClass?: string;
|
|
114
|
+
switchClass?: string;
|
|
115
|
+
sliderClass?: string;
|
|
116
|
+
groupClass?: string;
|
|
117
|
+
otpClass?: string;
|
|
118
|
+
pickerClass?: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Sets shared label / error / hint / control classes app-wide, e.g.:
|
|
122
|
+
*
|
|
123
|
+
* ```ts
|
|
124
|
+
* provideEgFormSharedConfig({ labelClass: 'tw:font-semibold', errorClass: 'tw:text-red-600' })
|
|
125
|
+
* provideEgFormSharedConfig({ inputClass: 'tw:border-dashed', selectClass: 'tw:rounded-none' })
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare function provideEgFormSharedConfig(config: Partial<EgFormSharedConfig>): ValueProvider;
|
|
129
|
+
/** Merged shared config (empty object when not provided). */
|
|
130
|
+
declare function injectEgFormSharedConfig(): EgFormSharedConfig;
|
|
131
|
+
|
|
39
132
|
declare const HlmFormFieldImports: (typeof HlmError | typeof HlmFormField | typeof HlmHint)[];
|
|
40
133
|
declare class HlmFormFieldModule {
|
|
41
134
|
static ɵfac: i0.ɵɵFactoryDeclaration<HlmFormFieldModule, never>;
|
|
@@ -43,4 +136,5 @@ declare class HlmFormFieldModule {
|
|
|
43
136
|
static ɵinj: i0.ɵɵInjectorDeclaration<HlmFormFieldModule>;
|
|
44
137
|
}
|
|
45
138
|
|
|
46
|
-
export { HlmError, HlmFormField, HlmFormFieldImports, HlmFormFieldModule, HlmFormIdGenerator, HlmHint };
|
|
139
|
+
export { DEFAULT_EG_FORM_ERROR_MESSAGES, EG_FORM_DEFAULT_ERROR_LABEL, HlmError, HlmFormField, HlmFormFieldImports, HlmFormFieldModule, HlmFormIdGenerator, HlmHint, injectEgFormErrorMessages, injectEgFormSharedConfig, provideEgFormErrorMessages, provideEgFormSharedConfig, resolveEgFormError };
|
|
140
|
+
export type { EgFormErrorMessageFn, EgFormErrorMessages, EgFormSharedConfig };
|