@egose/shadcn-theme-ng 0.0.28 → 0.2.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 +130 -13
- package/accordion/README.md +376 -34
- package/accordion/fesm2022/accordion.mjs +27 -39
- package/alert/README.md +343 -34
- package/alert/fesm2022/alert.mjs +47 -47
- package/alert-dialog/README.md +471 -5
- package/alert-dialog/fesm2022/alert-dialog.mjs +47 -47
- package/aspect-ratio/README.md +272 -5
- package/aspect-ratio/fesm2022/aspect-ratio.mjs +9 -9
- package/autocomplete/README.md +473 -34
- package/autocomplete/fesm2022/autocomplete.mjs +53 -71
- package/avatar/README.md +357 -5
- package/avatar/fesm2022/avatar.mjs +32 -32
- package/badge/README.md +289 -34
- package/badge/fesm2022/badge.mjs +57 -57
- package/basic-alert/README.md +324 -34
- package/basic-alert/fesm2022/basic-alert.mjs +6 -18
- package/breadcrumb/README.md +406 -5
- package/breadcrumb/fesm2022/breadcrumb.mjs +33 -39
- package/button/README.md +453 -34
- package/button/fesm2022/button.mjs +147 -219
- package/button/types/button.d.ts +7 -10
- package/button-group/README.md +318 -5
- package/button-group/fesm2022/button-group.mjs +20 -20
- package/calendar/README.md +328 -34
- package/calendar/fesm2022/calendar.mjs +29 -743
- package/card/README.md +331 -5
- package/card/fesm2022/card.mjs +33 -33
- package/carousel/README.md +333 -5
- package/carousel/fesm2022/carousel.mjs +39 -78
- package/checkbox/README.md +291 -34
- package/checkbox/fesm2022/checkbox.mjs +20 -58
- package/checkbox/types/checkbox.d.ts +5 -4
- package/collapsible/README.md +332 -5
- package/collapsible/fesm2022/collapsible.mjs +15 -15
- package/combobox/README.md +507 -5
- package/combobox/fesm2022/combobox.mjs +91 -224
- package/command/README.md +435 -5
- package/command/fesm2022/command.mjs +49 -109
- package/confirmation-dialog/README.md +272 -34
- package/confirmation-dialog/fesm2022/confirmation-dialog.mjs +15 -40
- package/confirmation-dialog/types/confirmation-dialog.d.ts +8 -4
- package/context-menu/README.md +366 -5
- package/context-menu/fesm2022/context-menu.mjs +9 -9
- package/date-picker/README.md +436 -34
- package/date-picker/fesm2022/date-picker.mjs +64 -267
- package/date-picker/types/date-picker.d.ts +7 -3
- package/dialog/README.md +419 -34
- package/dialog/fesm2022/dialog.mjs +45 -71
- package/drawer/README.md +395 -5
- package/drawer/fesm2022/drawer.mjs +44 -54
- package/dropdown-menu/README.md +417 -5
- package/dropdown-menu/fesm2022/dropdown-menu.mjs +82 -70
- package/dropdown-menu/types/dropdown-menu.d.ts +4 -0
- package/empty/README.md +329 -5
- package/empty/fesm2022/empty.mjs +30 -30
- package/field/README.md +385 -5
- package/field/fesm2022/field.mjs +56 -72
- package/form-checkbox/README.md +283 -34
- package/form-checkbox/fesm2022/form-checkbox.mjs +25 -85
- package/form-checkbox/types/form-checkbox.d.ts +7 -1
- package/form-date-picker/README.md +293 -34
- package/form-date-picker/fesm2022/form-date-picker.mjs +31 -84
- package/form-date-picker/types/form-date-picker.d.ts +7 -1
- package/form-field/README.md +327 -34
- package/form-field/fesm2022/form-field.mjs +33 -19
- package/form-field/types/form-field.d.ts +28 -20
- package/form-field-simple/README.md +311 -34
- package/form-field-simple/fesm2022/form-field-simple.mjs +4 -4
- package/form-searchable-multiselect/README.md +332 -34
- package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +31 -91
- package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +7 -1
- package/form-select/README.md +321 -34
- package/form-select/fesm2022/form-select.mjs +32 -149
- package/form-select/types/form-select.d.ts +7 -1
- package/form-text-input/README.md +342 -34
- package/form-text-input/fesm2022/form-text-input.mjs +35 -149
- package/form-text-input/types/form-text-input.d.ts +9 -12
- package/form-textarea/README.md +318 -34
- package/form-textarea/fesm2022/form-textarea.mjs +34 -109
- package/form-textarea/types/form-textarea.d.ts +8 -1
- package/hover-card/README.md +256 -5
- package/hover-card/fesm2022/hover-card.mjs +19 -19
- package/icon/README.md +210 -34
- package/icon/fesm2022/icon.mjs +8 -8
- package/input/README.md +240 -34
- package/input/fesm2022/input.mjs +17 -12
- package/input/types/input.d.ts +2 -1
- package/input-group/README.md +335 -5
- package/input-group/fesm2022/input-group.mjs +34 -34
- package/input-otp/README.md +375 -5
- package/input-otp/fesm2022/input-otp.mjs +26 -26
- package/item/README.md +385 -5
- package/item/fesm2022/item.mjs +53 -53
- package/kbd/README.md +291 -5
- package/kbd/fesm2022/kbd.mjs +12 -12
- package/label/README.md +243 -34
- package/label/fesm2022/label.mjs +10 -10
- package/label/types/label.d.ts +1 -1
- package/layout-simple/README.md +174 -44
- package/layout-simple/fesm2022/layout-simple.mjs +508 -603
- package/layout-simple/types/layout-simple.d.ts +179 -128
- package/menu/README.md +418 -0
- package/menu/fesm2022/menu.mjs +455 -0
- package/menu/package.json +24 -0
- package/menu/types/menu.d.ts +137 -0
- package/menubar/README.md +343 -5
- package/menubar/fesm2022/menubar.mjs +13 -13
- package/native-select/README.md +323 -5
- package/native-select/fesm2022/native-select.mjs +19 -19
- package/navigation-menu/README.md +369 -5
- package/navigation-menu/fesm2022/navigation-menu.mjs +35 -47
- package/package.json +183 -154
- package/pagination/README.md +388 -5
- package/pagination/fesm2022/pagination.mjs +45 -331
- package/popover/README.md +302 -34
- package/popover/fesm2022/popover.mjs +30 -30
- package/progress/README.md +311 -5
- package/progress/fesm2022/progress.mjs +13 -13
- package/radio-group/README.md +335 -34
- package/radio-group/fesm2022/radio-group.mjs +26 -19
- package/radio-group/types/radio-group.d.ts +1 -1
- package/resizable/README.md +269 -5
- package/resizable/fesm2022/resizable.mjs +18 -26
- package/scroll-area/README.md +233 -5
- package/scroll-area/fesm2022/scroll-area.mjs +9 -9
- package/searchable-multiselect/README.md +294 -34
- package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +28 -117
- package/searchable-multiselect/types/searchable-multiselect.d.ts +8 -2
- package/select/README.md +408 -34
- package/select/fesm2022/select.mjs +71 -111
- package/select/types/select.d.ts +3 -1
- package/separator/README.md +193 -34
- package/separator/fesm2022/separator.mjs +9 -9
- package/separator/types/separator.d.ts +1 -1
- package/sheet/README.md +282 -34
- package/sheet/fesm2022/sheet.mjs +41 -53
- package/sidebar/README.md +457 -5
- package/sidebar/fesm2022/sidebar.mjs +125 -227
- package/skeleton/README.md +217 -5
- package/skeleton/fesm2022/skeleton.mjs +9 -9
- package/slider/README.md +273 -5
- package/slider/fesm2022/slider.mjs +10 -80
- package/sonner/README.md +317 -34
- package/sonner/fesm2022/sonner.mjs +13 -13
- package/spinner/README.md +255 -34
- package/spinner/fesm2022/spinner.mjs +11 -11
- package/switch/README.md +281 -34
- package/switch/fesm2022/switch.mjs +13 -15
- package/table/README.md +423 -5
- package/table/fesm2022/table.mjs +41 -41
- package/tabs/README.md +382 -34
- package/tabs/fesm2022/tabs.mjs +35 -111
- package/textarea/README.md +282 -5
- package/textarea/fesm2022/textarea.mjs +9 -9
- package/toggle/README.md +270 -5
- package/toggle/fesm2022/toggle.mjs +13 -13
- package/toggle-group/README.md +340 -5
- package/toggle-group/fesm2022/toggle-group.mjs +13 -13
- package/toggle-group/types/toggle-group.d.ts +13 -7
- package/tooltip/README.md +240 -34
- package/tooltip/fesm2022/tooltip.mjs +14 -14
- package/typography/README.md +271 -5
- package/typography/fesm2022/typography.mjs +53 -53
- package/typography/types/typography.d.ts +19 -19
- package/utils/README.md +274 -34
- package/utils/fesm2022/utils.mjs +68 -44
- package/accordion/fesm2022/accordion.mjs.map +0 -1
- package/alert/fesm2022/alert.mjs.map +0 -1
- package/alert-dialog/fesm2022/alert-dialog.mjs.map +0 -1
- package/aspect-ratio/fesm2022/aspect-ratio.mjs.map +0 -1
- package/autocomplete/fesm2022/autocomplete.mjs.map +0 -1
- package/avatar/fesm2022/avatar.mjs.map +0 -1
- package/badge/fesm2022/badge.mjs.map +0 -1
- package/basic-alert/fesm2022/basic-alert.mjs.map +0 -1
- package/breadcrumb/fesm2022/breadcrumb.mjs.map +0 -1
- package/button/fesm2022/button.mjs.map +0 -1
- package/button-group/fesm2022/button-group.mjs.map +0 -1
- package/calendar/fesm2022/calendar.mjs.map +0 -1
- package/card/fesm2022/card.mjs.map +0 -1
- package/carousel/fesm2022/carousel.mjs.map +0 -1
- package/checkbox/fesm2022/checkbox.mjs.map +0 -1
- package/collapsible/fesm2022/collapsible.mjs.map +0 -1
- package/combobox/fesm2022/combobox.mjs.map +0 -1
- package/command/fesm2022/command.mjs.map +0 -1
- package/confirmation-dialog/fesm2022/confirmation-dialog.mjs.map +0 -1
- package/context-menu/fesm2022/context-menu.mjs.map +0 -1
- package/date-picker/fesm2022/date-picker.mjs.map +0 -1
- package/dialog/fesm2022/dialog.mjs.map +0 -1
- package/drawer/fesm2022/drawer.mjs.map +0 -1
- package/dropdown-menu/fesm2022/dropdown-menu.mjs.map +0 -1
- package/empty/fesm2022/empty.mjs.map +0 -1
- package/field/fesm2022/field.mjs.map +0 -1
- package/form-checkbox/fesm2022/form-checkbox.mjs.map +0 -1
- package/form-date-picker/fesm2022/form-date-picker.mjs.map +0 -1
- package/form-field/fesm2022/form-field.mjs.map +0 -1
- package/form-field-simple/fesm2022/form-field-simple.mjs.map +0 -1
- package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs.map +0 -1
- package/form-select/fesm2022/form-select.mjs.map +0 -1
- package/form-text-input/fesm2022/form-text-input.mjs.map +0 -1
- package/form-textarea/fesm2022/form-textarea.mjs.map +0 -1
- package/hover-card/fesm2022/hover-card.mjs.map +0 -1
- package/icon/fesm2022/icon.mjs.map +0 -1
- package/input/fesm2022/input.mjs.map +0 -1
- package/input-group/fesm2022/input-group.mjs.map +0 -1
- package/input-otp/fesm2022/input-otp.mjs.map +0 -1
- package/item/fesm2022/item.mjs.map +0 -1
- package/kbd/fesm2022/kbd.mjs.map +0 -1
- package/label/fesm2022/label.mjs.map +0 -1
- package/layout-simple/fesm2022/layout-simple.mjs.map +0 -1
- package/menubar/fesm2022/menubar.mjs.map +0 -1
- package/native-select/fesm2022/native-select.mjs.map +0 -1
- package/navigation-menu/fesm2022/navigation-menu.mjs.map +0 -1
- package/pagination/fesm2022/pagination.mjs.map +0 -1
- package/popover/fesm2022/popover.mjs.map +0 -1
- package/progress/fesm2022/progress.mjs.map +0 -1
- package/radio-group/fesm2022/radio-group.mjs.map +0 -1
- package/resizable/fesm2022/resizable.mjs.map +0 -1
- package/scroll-area/fesm2022/scroll-area.mjs.map +0 -1
- package/searchable-multiselect/fesm2022/searchable-multiselect.mjs.map +0 -1
- package/select/fesm2022/select.mjs.map +0 -1
- package/separator/fesm2022/separator.mjs.map +0 -1
- package/sheet/fesm2022/sheet.mjs.map +0 -1
- package/sidebar/fesm2022/sidebar.mjs.map +0 -1
- package/skeleton/fesm2022/skeleton.mjs.map +0 -1
- package/slider/fesm2022/slider.mjs.map +0 -1
- package/sonner/fesm2022/sonner.mjs.map +0 -1
- package/spinner/fesm2022/spinner.mjs.map +0 -1
- package/switch/fesm2022/switch.mjs.map +0 -1
- package/table/fesm2022/table.mjs.map +0 -1
- package/tabs/fesm2022/tabs.mjs.map +0 -1
- package/textarea/fesm2022/textarea.mjs.map +0 -1
- package/toggle/fesm2022/toggle.mjs.map +0 -1
- package/toggle-group/fesm2022/toggle-group.mjs.map +0 -1
- package/tooltip/fesm2022/tooltip.mjs.map +0 -1
- package/typography/fesm2022/typography.mjs.map +0 -1
- package/utils/fesm2022/utils.mjs.map +0 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, computed, Component } from '@angular/core';
|
|
2
|
+
import { inject, input, computed, Component } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
|
-
import {
|
|
5
|
-
import { HlmFormField, HlmError, HlmHint } from '@egose/shadcn-theme-ng/form-field';
|
|
4
|
+
import { FormGroupDirective, ReactiveFormsModule, ControlContainer } from '@angular/forms';
|
|
5
|
+
import { HlmFormIdGenerator, HlmFormField, HlmError, HlmHint } from '@egose/shadcn-theme-ng/form-field';
|
|
6
6
|
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
7
7
|
import { HlmDatePicker, HlmDatePickerInput } from '@egose/shadcn-theme-ng/date-picker';
|
|
8
8
|
import { hlm } from '@egose/shadcn-theme-ng/utils';
|
|
9
9
|
|
|
10
|
-
let nextId = 0;
|
|
11
10
|
class EgFormDatePicker {
|
|
11
|
+
formGroupDirective = inject(FormGroupDirective);
|
|
12
|
+
generatedId = inject(HlmFormIdGenerator).generate('eg-form-date-picker');
|
|
12
13
|
// Inputs
|
|
13
14
|
label = input(undefined, /* @ts-ignore */
|
|
14
15
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
@@ -21,7 +22,7 @@ class EgFormDatePicker {
|
|
|
21
22
|
hint = input(undefined, /* @ts-ignore */
|
|
22
23
|
...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
23
24
|
// HTML attributes
|
|
24
|
-
id = input(
|
|
25
|
+
id = input(undefined, /* @ts-ignore */
|
|
25
26
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
26
27
|
name = input(undefined, /* @ts-ignore */
|
|
27
28
|
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
@@ -39,6 +40,20 @@ class EgFormDatePicker {
|
|
|
39
40
|
...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
40
41
|
autoCloseOnSelect = input(true, /* @ts-ignore */
|
|
41
42
|
...(ngDevMode ? [{ debugName: "autoCloseOnSelect" }] : /* istanbul ignore next */ []));
|
|
43
|
+
effectiveId = computed(() => this.controlId() || this.id() || this.generatedId, /* @ts-ignore */
|
|
44
|
+
...(ngDevMode ? [{ debugName: "effectiveId" }] : /* istanbul ignore next */ []));
|
|
45
|
+
errorId = computed(() => `${this.effectiveId()}-error`, /* @ts-ignore */
|
|
46
|
+
...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
|
|
47
|
+
hintId = computed(() => `${this.effectiveId()}-hint`, /* @ts-ignore */
|
|
48
|
+
...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
|
|
49
|
+
describedBy() {
|
|
50
|
+
const control = this.formGroupDirective.form.get(this.controlName());
|
|
51
|
+
return this.error() && control?.invalid && (control.dirty || control.touched)
|
|
52
|
+
? this.errorId()
|
|
53
|
+
: this.hint()
|
|
54
|
+
? this.hintId()
|
|
55
|
+
: null;
|
|
56
|
+
}
|
|
42
57
|
// Styling
|
|
43
58
|
userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
|
|
44
59
|
labelClass = input('', /* @ts-ignore */
|
|
@@ -50,98 +65,30 @@ class EgFormDatePicker {
|
|
|
50
65
|
hintClass = input('', /* @ts-ignore */
|
|
51
66
|
...(ngDevMode ? [{ debugName: "hintClass" }] : /* istanbul ignore next */ []));
|
|
52
67
|
// Computed classes
|
|
53
|
-
$userClass = computed(() => hlm(
|
|
68
|
+
$userClass = computed(() => hlm("flex flex-col", this.userClass()), /* @ts-ignore */
|
|
54
69
|
...(ngDevMode ? [{ debugName: "$userClass" }] : /* istanbul ignore next */ []));
|
|
55
|
-
$labelClass = computed(() => hlm(
|
|
70
|
+
$labelClass = computed(() => hlm("mb-1", this.labelClass()), /* @ts-ignore */
|
|
56
71
|
...(ngDevMode ? [{ debugName: "$labelClass" }] : /* istanbul ignore next */ []));
|
|
57
|
-
$pickerClass = computed(() => hlm(
|
|
72
|
+
$pickerClass = computed(() => hlm("mb-1", this.pickerClass()), /* @ts-ignore */
|
|
58
73
|
...(ngDevMode ? [{ debugName: "$pickerClass" }] : /* istanbul ignore next */ []));
|
|
59
|
-
$errorClass = computed(() => hlm(
|
|
74
|
+
$errorClass = computed(() => hlm("mt-0", this.errorClass()), /* @ts-ignore */
|
|
60
75
|
...(ngDevMode ? [{ debugName: "$errorClass" }] : /* istanbul ignore next */ []));
|
|
61
|
-
$hintClass = computed(() => hlm(
|
|
76
|
+
$hintClass = computed(() => hlm("mt-0", this.hintClass()), /* @ts-ignore */
|
|
62
77
|
...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
|
|
63
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
64
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.
|
|
65
|
-
@let lbl = label();
|
|
66
|
-
@let cid = controlId();
|
|
67
|
-
@let cnm = controlName();
|
|
68
|
-
@let err = error();
|
|
69
|
-
@let hnt = hint();
|
|
70
|
-
@let rqrd = required();
|
|
71
|
-
|
|
72
|
-
<hlm-form-field>
|
|
73
|
-
@if (lbl) {
|
|
74
|
-
<span hlmLabel [class]="$labelClass()"
|
|
75
|
-
>{{ lbl }}
|
|
76
|
-
@if (rqrd) {
|
|
77
|
-
<span class="text-red-500">*</span>
|
|
78
|
-
}
|
|
79
|
-
</span>
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
<hlm-date-picker [min]="min()" [max]="max()" [formControlName]="cnm" [class]="$pickerClass()">
|
|
83
|
-
<hlm-date-picker-input [inputId]="cid || id()" [placeholder]="placeholder()" />
|
|
84
|
-
</hlm-date-picker>
|
|
85
|
-
|
|
86
|
-
@if (err) {
|
|
87
|
-
<hlm-error [class]="$errorClass()">
|
|
88
|
-
{{ err }}
|
|
89
|
-
</hlm-error>
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
@if (hnt) {
|
|
93
|
-
<hlm-hint [class]="$hintClass()">
|
|
94
|
-
{{ hnt }}
|
|
95
|
-
</hlm-hint>
|
|
96
|
-
}
|
|
97
|
-
</hlm-form-field>
|
|
98
|
-
`, 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: HlmDatePicker, selector: "hlm-date-picker", inputs: ["captionLayout", "min", "max", "disabled", "date", "defaultFocusedDate", "autoCloseOnSelect", "formatDate", "transformDate"], outputs: ["dateChange"] }, { kind: "component", type: HlmDatePickerInput, selector: "hlm-date-picker-input", inputs: ["parseDate", "formatInputDate"] }] });
|
|
78
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormDatePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
79
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormDatePicker, isStandalone: true, selector: "eg-form-date-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 }, autoCloseOnSelect: { classPropertyName: "autoCloseOnSelect", publicName: "autoCloseOnSelect", 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 }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "w-full" }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let err = error();\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-picker\n [min]=\"min()\"\n [max]=\"max()\"\n [wrapperDisabled]=\"disabled()\"\n [formControlName]=\"cnm\"\n [class]=\"$pickerClass()\"\n >\n <hlm-date-picker-input\n [inputId]=\"effectiveId()\"\n [ariaLabel]=\"lbl\"\n [ariaDescribedby]=\"describedBy()\"\n [placeholder]=\"placeholder()\"\n />\n </hlm-date-picker>\n\n @if (err) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ err }}\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: HlmDatePicker, selector: "hlm-date-picker", inputs: ["captionLayout", "min", "max", "disabled", "wrapperDisabled", "date", "defaultFocusedDate", "autoCloseOnSelect", "formatDate", "transformDate"], outputs: ["dateChange"] }, { kind: "component", type: HlmDatePickerInput, selector: "hlm-date-picker-input", inputs: ["ariaLabel", "ariaDescribedby", "parseDate", "formatInputDate"] }] });
|
|
99
80
|
}
|
|
100
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
81
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormDatePicker, decorators: [{
|
|
101
82
|
type: Component,
|
|
102
83
|
args: [{
|
|
103
84
|
selector: 'eg-form-date-picker',
|
|
104
85
|
standalone: true,
|
|
105
86
|
host: {
|
|
106
|
-
class:
|
|
87
|
+
class: "w-full",
|
|
107
88
|
},
|
|
108
89
|
imports: [ReactiveFormsModule, HlmFormField, HlmError, HlmHint, HlmLabel, HlmDatePicker, HlmDatePickerInput],
|
|
109
90
|
providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
|
|
110
|
-
template:
|
|
111
|
-
@let lbl = label();
|
|
112
|
-
@let cid = controlId();
|
|
113
|
-
@let cnm = controlName();
|
|
114
|
-
@let err = error();
|
|
115
|
-
@let hnt = hint();
|
|
116
|
-
@let rqrd = required();
|
|
117
|
-
|
|
118
|
-
<hlm-form-field>
|
|
119
|
-
@if (lbl) {
|
|
120
|
-
<span hlmLabel [class]="$labelClass()"
|
|
121
|
-
>{{ lbl }}
|
|
122
|
-
@if (rqrd) {
|
|
123
|
-
<span class="text-red-500">*</span>
|
|
124
|
-
}
|
|
125
|
-
</span>
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
<hlm-date-picker [min]="min()" [max]="max()" [formControlName]="cnm" [class]="$pickerClass()">
|
|
129
|
-
<hlm-date-picker-input [inputId]="cid || id()" [placeholder]="placeholder()" />
|
|
130
|
-
</hlm-date-picker>
|
|
131
|
-
|
|
132
|
-
@if (err) {
|
|
133
|
-
<hlm-error [class]="$errorClass()">
|
|
134
|
-
{{ err }}
|
|
135
|
-
</hlm-error>
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@if (hnt) {
|
|
139
|
-
<hlm-hint [class]="$hintClass()">
|
|
140
|
-
{{ hnt }}
|
|
141
|
-
</hlm-hint>
|
|
142
|
-
}
|
|
143
|
-
</hlm-form-field>
|
|
144
|
-
`,
|
|
91
|
+
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let err = error();\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-picker\n [min]=\"min()\"\n [max]=\"max()\"\n [wrapperDisabled]=\"disabled()\"\n [formControlName]=\"cnm\"\n [class]=\"$pickerClass()\"\n >\n <hlm-date-picker-input\n [inputId]=\"effectiveId()\"\n [ariaLabel]=\"lbl\"\n [ariaDescribedby]=\"describedBy()\"\n [placeholder]=\"placeholder()\"\n />\n </hlm-date-picker>\n\n @if (err) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ err }}\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 ",
|
|
145
92
|
}]
|
|
146
93
|
}], 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 }] }], autoCloseOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseOnSelect", 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 }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
147
94
|
|
|
@@ -154,4 +101,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
154
101
|
*/
|
|
155
102
|
|
|
156
103
|
export { EgFormDatePicker };
|
|
157
|
-
|
|
104
|
+
|
|
@@ -2,12 +2,14 @@ import * as _angular_core from '@angular/core';
|
|
|
2
2
|
import { ClassValue } from 'clsx';
|
|
3
3
|
|
|
4
4
|
declare class EgFormDatePicker {
|
|
5
|
+
private readonly formGroupDirective;
|
|
6
|
+
private readonly generatedId;
|
|
5
7
|
label: _angular_core.InputSignal<string | undefined>;
|
|
6
8
|
controlId: _angular_core.InputSignal<string | undefined>;
|
|
7
9
|
controlName: _angular_core.InputSignal<string>;
|
|
8
10
|
error: _angular_core.InputSignal<string | undefined>;
|
|
9
11
|
hint: _angular_core.InputSignal<string | undefined>;
|
|
10
|
-
id: _angular_core.InputSignal<string>;
|
|
12
|
+
id: _angular_core.InputSignal<string | undefined>;
|
|
11
13
|
name: _angular_core.InputSignal<string | undefined>;
|
|
12
14
|
placeholder: _angular_core.InputSignal<string>;
|
|
13
15
|
readonly: _angular_core.InputSignal<boolean>;
|
|
@@ -16,6 +18,10 @@ declare class EgFormDatePicker {
|
|
|
16
18
|
min: _angular_core.InputSignal<string | Date | null>;
|
|
17
19
|
max: _angular_core.InputSignal<string | Date | null>;
|
|
18
20
|
autoCloseOnSelect: _angular_core.InputSignal<boolean>;
|
|
21
|
+
readonly effectiveId: _angular_core.Signal<string>;
|
|
22
|
+
readonly errorId: _angular_core.Signal<string>;
|
|
23
|
+
readonly hintId: _angular_core.Signal<string>;
|
|
24
|
+
describedBy(): string | null;
|
|
19
25
|
userClass: _angular_core.InputSignal<ClassValue>;
|
|
20
26
|
labelClass: _angular_core.InputSignal<string>;
|
|
21
27
|
pickerClass: _angular_core.InputSignal<string>;
|
package/form-field/README.md
CHANGED
|
@@ -1,64 +1,357 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Form Field (`@egose/shadcn-theme-ng/form-field`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The classic labelled-field wrapper: `hlm-form-field` groups a brain field control with a label, shows `hlm-hint` by default, and swaps to `hlm-error` automatically when the control reports validation errors. Equivalent to shadcn/ui `FormField` + `FormMessage` composition.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The Angular implementation is a small composition layer over
|
|
6
|
+
[`BrnField` from `@spartan-ng/brain/field`](https://www.spartan-ng.com/): `HlmFormField` hosts
|
|
7
|
+
`BrnField`, reads the projected `BrnFieldControl` (`contentChild`) and `HlmError` children
|
|
8
|
+
(`contentChildren`), and projects either `<ng-content select="hlm-error">` or
|
|
9
|
+
`<ng-content select="hlm-hint">`. `HlmError` / `HlmHint` are pure styling directives;
|
|
10
|
+
`HlmFormIdGenerator` mints unique ids. Must live inside a `FormGroupDirective` (`[formGroup]`
|
|
11
|
+
parent) so `formControlName` and `ControlContainer` resolve.
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
> **Ships as:** `@egose/shadcn-theme-ng/form-field` and `@egose/shadcn-theme-ng-tw/form-field`
|
|
14
|
+
> (the `tw:`-prefixed Tailwind variant). Both expose the identical TypeScript surface; only the
|
|
15
|
+
> emitted Tailwind class strings differ. See the [package README](../../README.md) for install
|
|
16
|
+
> steps, peer dependencies, and Tailwind setup. Do not publish this project directory independently.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
8
19
|
|
|
9
20
|
```bash
|
|
10
|
-
|
|
21
|
+
# Plain Tailwind (no prefix)
|
|
22
|
+
npm install @egose/shadcn-theme-ng
|
|
23
|
+
|
|
24
|
+
# tw:-prefixed Tailwind variant
|
|
25
|
+
npm install @egose/shadcn-theme-ng-tw
|
|
11
26
|
```
|
|
12
27
|
|
|
13
|
-
|
|
28
|
+
Peer dependencies (Angular, `@spartan-ng/brain`, `@ng-icons/*`, `rxjs`, …) are documented in the
|
|
29
|
+
[package README](../../README.md#peer-dependencies). This subpath has no extra runtime component
|
|
30
|
+
dependencies beyond `@egose/shadcn-theme-ng/utils` and `@spartan-ng/brain/field`.
|
|
14
31
|
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
## Imports
|
|
33
|
+
|
|
34
|
+
All symbols are exported from the subpath root (`projects/form-field/src/public-api.ts`):
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import {
|
|
38
|
+
HlmFormField,
|
|
39
|
+
HlmError,
|
|
40
|
+
HlmHint,
|
|
41
|
+
HlmFormIdGenerator,
|
|
42
|
+
HlmFormFieldImports,
|
|
43
|
+
HlmFormFieldModule,
|
|
44
|
+
} from '@egose/shadcn-theme-ng/form-field';
|
|
45
|
+
// tw variant: replace with '@egose/shadcn-theme-ng-tw/form-field'
|
|
17
46
|
```
|
|
18
47
|
|
|
19
|
-
|
|
48
|
+
Standalone component — spread the `*Imports` array:
|
|
20
49
|
|
|
21
|
-
|
|
50
|
+
```ts
|
|
51
|
+
import { Component } from '@angular/core';
|
|
52
|
+
import { HlmFormFieldImports } from '@egose/shadcn-theme-ng/form-field';
|
|
22
53
|
|
|
23
|
-
|
|
24
|
-
|
|
54
|
+
@Component({
|
|
55
|
+
selector: 'app-demo',
|
|
56
|
+
standalone: true,
|
|
57
|
+
imports: [...HlmFormFieldImports],
|
|
58
|
+
template: `...`,
|
|
59
|
+
})
|
|
60
|
+
export class DemoComponent {}
|
|
25
61
|
```
|
|
26
62
|
|
|
27
|
-
|
|
63
|
+
NgModule-based consumer — import the module:
|
|
28
64
|
|
|
29
|
-
|
|
65
|
+
```ts
|
|
66
|
+
import { NgModule } from '@angular/core';
|
|
67
|
+
import { HlmFormFieldModule } from '@egose/shadcn-theme-ng/form-field';
|
|
30
68
|
|
|
31
|
-
|
|
69
|
+
@NgModule({ imports: [HlmFormFieldModule] })
|
|
70
|
+
export class DemoModule {}
|
|
71
|
+
```
|
|
32
72
|
|
|
33
|
-
|
|
73
|
+
`ControlValueAccessor` behavior: `hlm-form-field` is not itself a form control — the projected
|
|
74
|
+
control (any `BrnFieldControl`: `hlm-input`, `hlm-checkbox`, `hlm-date-picker`, …) carries the
|
|
75
|
+
`formControlName` binding. The wrapper only observes its `errors()` signal to switch hint/error.
|
|
34
76
|
|
|
35
|
-
|
|
36
|
-
cd dist/form-field
|
|
37
|
-
```
|
|
77
|
+
## Anatomy / Structure
|
|
38
78
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
79
|
+
```html
|
|
80
|
+
<form [formGroup]="form">
|
|
81
|
+
<hlm-form-field>
|
|
82
|
+
<label hlmLabel for="email">Email</label>
|
|
83
|
+
<input hlmInput id="email" formControlName="email" placeholder="you@example.com" />
|
|
84
|
+
<hlm-hint>We'll never share your email.</hlm-hint>
|
|
85
|
+
<hlm-error>Email is required.</hlm-error>
|
|
86
|
+
</hlm-form-field>
|
|
87
|
+
</form>
|
|
88
|
+
```
|
|
43
89
|
|
|
44
|
-
|
|
90
|
+
Real selectors (from source):
|
|
45
91
|
|
|
46
|
-
|
|
92
|
+
| Class | Selector | Kind |
|
|
93
|
+
| -------------------- | ------------------------------- | ---------------------------- |
|
|
94
|
+
| `HlmFormField` | `hlm-form-field` | Component (hosts `BrnField`) |
|
|
95
|
+
| `HlmError` | `hlm-error` | Directive |
|
|
96
|
+
| `HlmHint` | `hlm-hint` | Directive |
|
|
97
|
+
| `HlmFormIdGenerator` | (service, `providedIn: 'root'`) | Injectable |
|
|
47
98
|
|
|
48
|
-
|
|
49
|
-
|
|
99
|
+
## API reference
|
|
100
|
+
|
|
101
|
+
### `HlmFormField` — `hlm-form-field`
|
|
102
|
+
|
|
103
|
+
| Input | Type | Default | Description |
|
|
104
|
+
| ------- | ------------ | ------- | -------------------------------------------------- |
|
|
105
|
+
| `class` | `ClassValue` | `''` | Extra classes merged over `tw:block tw:space-y-2`. |
|
|
106
|
+
|
|
107
|
+
| Member | Description |
|
|
108
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
109
|
+
| `control` | `contentChild(BrnFieldControl)` — the projected field control; **throws `hlm-form-field must contain a BrnFieldControl` if absent.** |
|
|
110
|
+
| `errorChildren` | `contentChildren(HlmError)` — drives the hint/error switch. |
|
|
111
|
+
| `_hasDisplayedMessage` | `'error'` when an `hlm-error` child exists and `control.errors()` is non-empty, else `'hint'` (protected, governs projection). |
|
|
112
|
+
|
|
113
|
+
Template projects default content always, then either `hlm-error` (error case) or `hlm-hint`.
|
|
114
|
+
|
|
115
|
+
### `HlmError` — `hlm-error` / `HlmHint` — `hlm-hint`
|
|
116
|
+
|
|
117
|
+
Style-only directives (`text-destructive` / `text-muted-foreground`, block, `text-sm`).
|
|
118
|
+
|
|
119
|
+
| Input | Type | Default | Description |
|
|
120
|
+
| ------- | ------------ | ------- | -------------- |
|
|
121
|
+
| `class` | `ClassValue` | `''` | Extra classes. |
|
|
122
|
+
|
|
123
|
+
### `HlmFormIdGenerator`
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
@Injectable({ providedIn: 'root' })
|
|
127
|
+
export class HlmFormIdGenerator {
|
|
128
|
+
generate(prefix: string): string; // `${prefix}-${appId}-${nextId++}`
|
|
129
|
+
}
|
|
50
130
|
```
|
|
51
131
|
|
|
52
|
-
|
|
132
|
+
Inject and call `generate('my-field')` for stable label/control ids (used by the `eg-form-*` wrappers).
|
|
53
133
|
|
|
54
|
-
|
|
134
|
+
## Examples
|
|
55
135
|
|
|
56
|
-
|
|
57
|
-
|
|
136
|
+
### 1. Text input with hint → error swap
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { Component, inject } from '@angular/core';
|
|
140
|
+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
141
|
+
import { HlmFormFieldImports } from '@egose/shadcn-theme-ng/form-field';
|
|
142
|
+
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
143
|
+
import { HlmInput } from '@egose/shadcn-theme-ng/input';
|
|
144
|
+
|
|
145
|
+
@Component({
|
|
146
|
+
selector: 'app-text-field',
|
|
147
|
+
standalone: true,
|
|
148
|
+
imports: [ReactiveFormsModule, HlmLabel, HlmInput, ...HlmFormFieldImports],
|
|
149
|
+
template: `
|
|
150
|
+
<form [formGroup]="form" (ngSubmit)="save()">
|
|
151
|
+
<hlm-form-field>
|
|
152
|
+
<label hlmLabel for="username">Username</label>
|
|
153
|
+
<input hlmInput id="username" formControlName="username" placeholder="ada" />
|
|
154
|
+
<hlm-hint>Letters and numbers only.</hlm-hint>
|
|
155
|
+
<hlm-error>Username is required.</hlm-error>
|
|
156
|
+
</hlm-form-field>
|
|
157
|
+
<button type="submit" [disabled]="form.invalid">Save</button>
|
|
158
|
+
</form>
|
|
159
|
+
`,
|
|
160
|
+
})
|
|
161
|
+
export class TextFieldComponent {
|
|
162
|
+
private readonly fb = inject(FormBuilder);
|
|
163
|
+
readonly form = this.fb.group({ username: ['', Validators.required] });
|
|
164
|
+
save() {
|
|
165
|
+
console.log(this.form.value);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 2. Textarea with generated ids
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { Component, inject } from '@angular/core';
|
|
174
|
+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
175
|
+
import { HlmFormFieldImports, HlmFormIdGenerator } from '@egose/shadcn-theme-ng/form-field';
|
|
176
|
+
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
177
|
+
import { HlmTextarea } from '@egose/shadcn-theme-ng/textarea';
|
|
178
|
+
|
|
179
|
+
@Component({
|
|
180
|
+
selector: 'app-bio-field',
|
|
181
|
+
standalone: true,
|
|
182
|
+
imports: [ReactiveFormsModule, HlmLabel, HlmTextarea, ...HlmFormFieldImports],
|
|
183
|
+
template: `
|
|
184
|
+
<form [formGroup]="form">
|
|
185
|
+
<hlm-form-field>
|
|
186
|
+
<label hlmLabel [for]="bioId">Bio</label>
|
|
187
|
+
<textarea hlmTextarea [id]="bioId" formControlName="bio" rows="3"></textarea>
|
|
188
|
+
<hlm-hint>Tell us about yourself in 160 characters.</hlm-hint>
|
|
189
|
+
<hlm-error>Bio is required.</hlm-error>
|
|
190
|
+
</hlm-form-field>
|
|
191
|
+
</form>
|
|
192
|
+
`,
|
|
193
|
+
})
|
|
194
|
+
export class BioFieldComponent {
|
|
195
|
+
private readonly fb = inject(FormBuilder);
|
|
196
|
+
private readonly ids = inject(HlmFormIdGenerator);
|
|
197
|
+
readonly bioId = this.ids.generate('bio');
|
|
198
|
+
readonly form = this.fb.group({ bio: ['', Validators.required] });
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 3. Select field
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { Component, inject } from '@angular/core';
|
|
206
|
+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
207
|
+
import { HlmFormFieldImports } from '@egose/shadcn-theme-ng/form-field';
|
|
208
|
+
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
209
|
+
import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
|
|
210
|
+
|
|
211
|
+
@Component({
|
|
212
|
+
selector: 'app-role-field',
|
|
213
|
+
standalone: true,
|
|
214
|
+
imports: [ReactiveFormsModule, HlmLabel, ...HlmFormFieldImports, ...HlmSelectImports],
|
|
215
|
+
template: `
|
|
216
|
+
<form [formGroup]="form">
|
|
217
|
+
<hlm-form-field>
|
|
218
|
+
<label hlmLabel for="role">Role</label>
|
|
219
|
+
<hlm-select formControlName="role" id="role" placeholder="Select a role">
|
|
220
|
+
<hlm-select-content>
|
|
221
|
+
<hlm-select-item value="admin">Admin</hlm-select-item>
|
|
222
|
+
<hlm-select-item value="editor">Editor</hlm-select-item>
|
|
223
|
+
</hlm-select-content>
|
|
224
|
+
</hlm-select>
|
|
225
|
+
<hlm-hint>Controls what they can access.</hlm-hint>
|
|
226
|
+
<hlm-error>Pick a role.</hlm-error>
|
|
227
|
+
</hlm-form-field>
|
|
228
|
+
</form>
|
|
229
|
+
`,
|
|
230
|
+
})
|
|
231
|
+
export class RoleFieldComponent {
|
|
232
|
+
private readonly fb = inject(FormBuilder);
|
|
233
|
+
readonly form = this.fb.group({ role: ['', Validators.required] });
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 4. Date picker field
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { Component, inject } from '@angular/core';
|
|
241
|
+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
242
|
+
import { HlmFormFieldImports } from '@egose/shadcn-theme-ng/form-field';
|
|
243
|
+
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
244
|
+
import { HlmDatePickerImports } from '@egose/shadcn-theme-ng/date-picker';
|
|
245
|
+
|
|
246
|
+
@Component({
|
|
247
|
+
selector: 'app-date-field',
|
|
248
|
+
standalone: true,
|
|
249
|
+
imports: [ReactiveFormsModule, HlmLabel, ...HlmFormFieldImports, ...HlmDatePickerImports],
|
|
250
|
+
template: `
|
|
251
|
+
<form [formGroup]="form">
|
|
252
|
+
<hlm-form-field>
|
|
253
|
+
<label hlmLabel for="start">Start date</label>
|
|
254
|
+
<hlm-date-picker formControlName="start">
|
|
255
|
+
<hlm-date-picker-input inputId="start" placeholder="Pick a date" />
|
|
256
|
+
</hlm-date-picker>
|
|
257
|
+
<hlm-hint>First day of the subscription.</hlm-hint>
|
|
258
|
+
<hlm-error>Start date is required.</hlm-error>
|
|
259
|
+
</hlm-form-field>
|
|
260
|
+
</form>
|
|
261
|
+
`,
|
|
262
|
+
})
|
|
263
|
+
export class DateFieldComponent {
|
|
264
|
+
private readonly fb = inject(FormBuilder);
|
|
265
|
+
readonly form = this.fb.group({ start: [null as Date | null, Validators.required] });
|
|
266
|
+
}
|
|
58
267
|
```
|
|
59
268
|
|
|
60
|
-
|
|
269
|
+
### 5. Multiple errors (first-match display)
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { Component, inject } from '@angular/core';
|
|
273
|
+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
274
|
+
import { HlmFormFieldImports } from '@egose/shadcn-theme-ng/form-field';
|
|
275
|
+
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
276
|
+
import { HlmInput } from '@egose/shadcn-theme-ng/input';
|
|
277
|
+
|
|
278
|
+
@Component({
|
|
279
|
+
selector: 'app-multi-error',
|
|
280
|
+
standalone: true,
|
|
281
|
+
imports: [ReactiveFormsModule, HlmLabel, HlmInput, ...HlmFormFieldImports],
|
|
282
|
+
template: `
|
|
283
|
+
<form [formGroup]="form" (ngSubmit)="submit()">
|
|
284
|
+
<hlm-form-field>
|
|
285
|
+
<label hlmLabel for="email">Email</label>
|
|
286
|
+
<input hlmInput id="email" type="email" formControlName="email" />
|
|
287
|
+
<hlm-hint>We'll send the receipt here.</hlm-hint>
|
|
288
|
+
<!-- All hlm-error children project together while any error exists. -->
|
|
289
|
+
<hlm-error>Email is required.</hlm-error>
|
|
290
|
+
<hlm-error>Enter a valid email address.</hlm-error>
|
|
291
|
+
</hlm-form-field>
|
|
292
|
+
<button type="submit" (click)="form.markAllAsTouched()">Submit</button>
|
|
293
|
+
</form>
|
|
294
|
+
`,
|
|
295
|
+
})
|
|
296
|
+
export class MultiErrorComponent {
|
|
297
|
+
private readonly fb = inject(FormBuilder);
|
|
298
|
+
readonly form = this.fb.group({ email: ['', [Validators.required, Validators.email]] });
|
|
299
|
+
submit() {
|
|
300
|
+
this.form.markAllAsTouched();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 6. Checkbox row + submit-gated validation
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
import { Component, inject } from '@angular/core';
|
|
309
|
+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
310
|
+
import { HlmFormFieldImports } from '@egose/shadcn-theme-ng/form-field';
|
|
311
|
+
import { HlmCheckbox } from '@egose/shadcn-theme-ng/checkbox';
|
|
312
|
+
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
313
|
+
|
|
314
|
+
@Component({
|
|
315
|
+
selector: 'app-check-field',
|
|
316
|
+
standalone: true,
|
|
317
|
+
imports: [ReactiveFormsModule, HlmCheckbox, HlmLabel, ...HlmFormFieldImports],
|
|
318
|
+
template: `
|
|
319
|
+
<form [formGroup]="form" (ngSubmit)="submit()">
|
|
320
|
+
<hlm-form-field class="tw:space-y-1">
|
|
321
|
+
<div class="tw:flex tw:items-center tw:gap-2">
|
|
322
|
+
<hlm-checkbox id="consent" formControlName="consent" />
|
|
323
|
+
<label hlmLabel for="consent">I agree to the terms</label>
|
|
324
|
+
</div>
|
|
325
|
+
<hlm-error>You must agree before continuing.</hlm-error>
|
|
326
|
+
</hlm-form-field>
|
|
327
|
+
<button type="submit">Continue</button>
|
|
328
|
+
</form>
|
|
329
|
+
`,
|
|
330
|
+
})
|
|
331
|
+
export class CheckFieldComponent {
|
|
332
|
+
private readonly fb = inject(FormBuilder);
|
|
333
|
+
readonly form = this.fb.group({ consent: [false, Validators.requiredTrue] });
|
|
334
|
+
submit() {
|
|
335
|
+
this.form.markAllAsTouched();
|
|
336
|
+
if (this.form.valid) console.log('ok');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Accessibility notes
|
|
342
|
+
|
|
343
|
+
- The wrapper itself adds no label — always include a `<label hlmLabel>` (or `aria-label` on the control) so the field has an accessible name.
|
|
344
|
+
- Hint and error are mutually exclusive in the DOM (only one projects at a time), keeping `aria-describedby` targets unambiguous.
|
|
345
|
+
- For custom controls, implement `BrnFieldControl` so `errors()` and described-by wiring flow; otherwise the wrapper throws at runtime.
|
|
346
|
+
- Mark forms for submit-gated validation with `markAllAsTouched()` so errors appear on submit, not just on blur.
|
|
347
|
+
|
|
348
|
+
## Theming / CSS variables
|
|
349
|
+
|
|
350
|
+
No component-specific CSS variables. The wrapper is `tw:block tw:space-y-2`; errors are `text-destructive`, hints `text-muted-foreground`. Extend via `class` on each element.
|
|
61
351
|
|
|
62
|
-
##
|
|
352
|
+
## Related subpaths
|
|
63
353
|
|
|
64
|
-
|
|
354
|
+
- `@egose/shadcn-theme-ng/form-field-simple` — lighter `eg-form-field` alternative with touched/dirty-gated errors.
|
|
355
|
+
- `@egose/shadcn-theme-ng/field` — full shadcn Field layout system (groups, legends, separators).
|
|
356
|
+
- `@egose/shadcn-theme-ng/label` — `HlmLabel` used for field labels.
|
|
357
|
+
- `@egose/shadcn-theme-ng/form-checkbox`, `.../form-date-picker`, `.../form-text-input` — ready-made wrappers built on these primitives.
|