@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.
Files changed (237) hide show
  1. package/README.md +130 -13
  2. package/accordion/README.md +376 -34
  3. package/accordion/fesm2022/accordion.mjs +27 -39
  4. package/alert/README.md +343 -34
  5. package/alert/fesm2022/alert.mjs +47 -47
  6. package/alert-dialog/README.md +471 -5
  7. package/alert-dialog/fesm2022/alert-dialog.mjs +47 -47
  8. package/aspect-ratio/README.md +272 -5
  9. package/aspect-ratio/fesm2022/aspect-ratio.mjs +9 -9
  10. package/autocomplete/README.md +473 -34
  11. package/autocomplete/fesm2022/autocomplete.mjs +53 -71
  12. package/avatar/README.md +357 -5
  13. package/avatar/fesm2022/avatar.mjs +32 -32
  14. package/badge/README.md +289 -34
  15. package/badge/fesm2022/badge.mjs +57 -57
  16. package/basic-alert/README.md +324 -34
  17. package/basic-alert/fesm2022/basic-alert.mjs +6 -18
  18. package/breadcrumb/README.md +406 -5
  19. package/breadcrumb/fesm2022/breadcrumb.mjs +33 -39
  20. package/button/README.md +453 -34
  21. package/button/fesm2022/button.mjs +147 -219
  22. package/button/types/button.d.ts +7 -10
  23. package/button-group/README.md +318 -5
  24. package/button-group/fesm2022/button-group.mjs +20 -20
  25. package/calendar/README.md +328 -34
  26. package/calendar/fesm2022/calendar.mjs +29 -743
  27. package/card/README.md +331 -5
  28. package/card/fesm2022/card.mjs +33 -33
  29. package/carousel/README.md +333 -5
  30. package/carousel/fesm2022/carousel.mjs +39 -78
  31. package/checkbox/README.md +291 -34
  32. package/checkbox/fesm2022/checkbox.mjs +20 -58
  33. package/checkbox/types/checkbox.d.ts +5 -4
  34. package/collapsible/README.md +332 -5
  35. package/collapsible/fesm2022/collapsible.mjs +15 -15
  36. package/combobox/README.md +507 -5
  37. package/combobox/fesm2022/combobox.mjs +91 -224
  38. package/command/README.md +435 -5
  39. package/command/fesm2022/command.mjs +49 -109
  40. package/confirmation-dialog/README.md +272 -34
  41. package/confirmation-dialog/fesm2022/confirmation-dialog.mjs +15 -40
  42. package/confirmation-dialog/types/confirmation-dialog.d.ts +8 -4
  43. package/context-menu/README.md +366 -5
  44. package/context-menu/fesm2022/context-menu.mjs +9 -9
  45. package/date-picker/README.md +436 -34
  46. package/date-picker/fesm2022/date-picker.mjs +64 -267
  47. package/date-picker/types/date-picker.d.ts +7 -3
  48. package/dialog/README.md +419 -34
  49. package/dialog/fesm2022/dialog.mjs +45 -71
  50. package/drawer/README.md +395 -5
  51. package/drawer/fesm2022/drawer.mjs +44 -54
  52. package/dropdown-menu/README.md +417 -5
  53. package/dropdown-menu/fesm2022/dropdown-menu.mjs +82 -70
  54. package/dropdown-menu/types/dropdown-menu.d.ts +4 -0
  55. package/empty/README.md +329 -5
  56. package/empty/fesm2022/empty.mjs +30 -30
  57. package/field/README.md +385 -5
  58. package/field/fesm2022/field.mjs +56 -72
  59. package/form-checkbox/README.md +283 -34
  60. package/form-checkbox/fesm2022/form-checkbox.mjs +25 -85
  61. package/form-checkbox/types/form-checkbox.d.ts +7 -1
  62. package/form-date-picker/README.md +293 -34
  63. package/form-date-picker/fesm2022/form-date-picker.mjs +31 -84
  64. package/form-date-picker/types/form-date-picker.d.ts +7 -1
  65. package/form-field/README.md +327 -34
  66. package/form-field/fesm2022/form-field.mjs +33 -19
  67. package/form-field/types/form-field.d.ts +28 -20
  68. package/form-field-simple/README.md +311 -34
  69. package/form-field-simple/fesm2022/form-field-simple.mjs +4 -4
  70. package/form-searchable-multiselect/README.md +332 -34
  71. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +31 -91
  72. package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +7 -1
  73. package/form-select/README.md +321 -34
  74. package/form-select/fesm2022/form-select.mjs +32 -149
  75. package/form-select/types/form-select.d.ts +7 -1
  76. package/form-text-input/README.md +342 -34
  77. package/form-text-input/fesm2022/form-text-input.mjs +35 -149
  78. package/form-text-input/types/form-text-input.d.ts +9 -12
  79. package/form-textarea/README.md +318 -34
  80. package/form-textarea/fesm2022/form-textarea.mjs +34 -109
  81. package/form-textarea/types/form-textarea.d.ts +8 -1
  82. package/hover-card/README.md +256 -5
  83. package/hover-card/fesm2022/hover-card.mjs +19 -19
  84. package/icon/README.md +210 -34
  85. package/icon/fesm2022/icon.mjs +8 -8
  86. package/input/README.md +240 -34
  87. package/input/fesm2022/input.mjs +17 -12
  88. package/input/types/input.d.ts +2 -1
  89. package/input-group/README.md +335 -5
  90. package/input-group/fesm2022/input-group.mjs +34 -34
  91. package/input-otp/README.md +375 -5
  92. package/input-otp/fesm2022/input-otp.mjs +26 -26
  93. package/item/README.md +385 -5
  94. package/item/fesm2022/item.mjs +53 -53
  95. package/kbd/README.md +291 -5
  96. package/kbd/fesm2022/kbd.mjs +12 -12
  97. package/label/README.md +243 -34
  98. package/label/fesm2022/label.mjs +10 -10
  99. package/label/types/label.d.ts +1 -1
  100. package/layout-simple/README.md +174 -44
  101. package/layout-simple/fesm2022/layout-simple.mjs +508 -603
  102. package/layout-simple/types/layout-simple.d.ts +179 -128
  103. package/menu/README.md +418 -0
  104. package/menu/fesm2022/menu.mjs +455 -0
  105. package/menu/package.json +24 -0
  106. package/menu/types/menu.d.ts +137 -0
  107. package/menubar/README.md +343 -5
  108. package/menubar/fesm2022/menubar.mjs +13 -13
  109. package/native-select/README.md +323 -5
  110. package/native-select/fesm2022/native-select.mjs +19 -19
  111. package/navigation-menu/README.md +369 -5
  112. package/navigation-menu/fesm2022/navigation-menu.mjs +35 -47
  113. package/package.json +183 -154
  114. package/pagination/README.md +388 -5
  115. package/pagination/fesm2022/pagination.mjs +45 -331
  116. package/popover/README.md +302 -34
  117. package/popover/fesm2022/popover.mjs +30 -30
  118. package/progress/README.md +311 -5
  119. package/progress/fesm2022/progress.mjs +13 -13
  120. package/radio-group/README.md +335 -34
  121. package/radio-group/fesm2022/radio-group.mjs +26 -19
  122. package/radio-group/types/radio-group.d.ts +1 -1
  123. package/resizable/README.md +269 -5
  124. package/resizable/fesm2022/resizable.mjs +18 -26
  125. package/scroll-area/README.md +233 -5
  126. package/scroll-area/fesm2022/scroll-area.mjs +9 -9
  127. package/searchable-multiselect/README.md +294 -34
  128. package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +28 -117
  129. package/searchable-multiselect/types/searchable-multiselect.d.ts +8 -2
  130. package/select/README.md +408 -34
  131. package/select/fesm2022/select.mjs +71 -111
  132. package/select/types/select.d.ts +3 -1
  133. package/separator/README.md +193 -34
  134. package/separator/fesm2022/separator.mjs +9 -9
  135. package/separator/types/separator.d.ts +1 -1
  136. package/sheet/README.md +282 -34
  137. package/sheet/fesm2022/sheet.mjs +41 -53
  138. package/sidebar/README.md +457 -5
  139. package/sidebar/fesm2022/sidebar.mjs +125 -227
  140. package/skeleton/README.md +217 -5
  141. package/skeleton/fesm2022/skeleton.mjs +9 -9
  142. package/slider/README.md +273 -5
  143. package/slider/fesm2022/slider.mjs +10 -80
  144. package/sonner/README.md +317 -34
  145. package/sonner/fesm2022/sonner.mjs +13 -13
  146. package/spinner/README.md +255 -34
  147. package/spinner/fesm2022/spinner.mjs +11 -11
  148. package/switch/README.md +281 -34
  149. package/switch/fesm2022/switch.mjs +13 -15
  150. package/table/README.md +423 -5
  151. package/table/fesm2022/table.mjs +41 -41
  152. package/tabs/README.md +382 -34
  153. package/tabs/fesm2022/tabs.mjs +35 -111
  154. package/textarea/README.md +282 -5
  155. package/textarea/fesm2022/textarea.mjs +9 -9
  156. package/toggle/README.md +270 -5
  157. package/toggle/fesm2022/toggle.mjs +13 -13
  158. package/toggle-group/README.md +340 -5
  159. package/toggle-group/fesm2022/toggle-group.mjs +13 -13
  160. package/toggle-group/types/toggle-group.d.ts +13 -7
  161. package/tooltip/README.md +240 -34
  162. package/tooltip/fesm2022/tooltip.mjs +14 -14
  163. package/typography/README.md +271 -5
  164. package/typography/fesm2022/typography.mjs +53 -53
  165. package/typography/types/typography.d.ts +19 -19
  166. package/utils/README.md +274 -34
  167. package/utils/fesm2022/utils.mjs +68 -44
  168. package/accordion/fesm2022/accordion.mjs.map +0 -1
  169. package/alert/fesm2022/alert.mjs.map +0 -1
  170. package/alert-dialog/fesm2022/alert-dialog.mjs.map +0 -1
  171. package/aspect-ratio/fesm2022/aspect-ratio.mjs.map +0 -1
  172. package/autocomplete/fesm2022/autocomplete.mjs.map +0 -1
  173. package/avatar/fesm2022/avatar.mjs.map +0 -1
  174. package/badge/fesm2022/badge.mjs.map +0 -1
  175. package/basic-alert/fesm2022/basic-alert.mjs.map +0 -1
  176. package/breadcrumb/fesm2022/breadcrumb.mjs.map +0 -1
  177. package/button/fesm2022/button.mjs.map +0 -1
  178. package/button-group/fesm2022/button-group.mjs.map +0 -1
  179. package/calendar/fesm2022/calendar.mjs.map +0 -1
  180. package/card/fesm2022/card.mjs.map +0 -1
  181. package/carousel/fesm2022/carousel.mjs.map +0 -1
  182. package/checkbox/fesm2022/checkbox.mjs.map +0 -1
  183. package/collapsible/fesm2022/collapsible.mjs.map +0 -1
  184. package/combobox/fesm2022/combobox.mjs.map +0 -1
  185. package/command/fesm2022/command.mjs.map +0 -1
  186. package/confirmation-dialog/fesm2022/confirmation-dialog.mjs.map +0 -1
  187. package/context-menu/fesm2022/context-menu.mjs.map +0 -1
  188. package/date-picker/fesm2022/date-picker.mjs.map +0 -1
  189. package/dialog/fesm2022/dialog.mjs.map +0 -1
  190. package/drawer/fesm2022/drawer.mjs.map +0 -1
  191. package/dropdown-menu/fesm2022/dropdown-menu.mjs.map +0 -1
  192. package/empty/fesm2022/empty.mjs.map +0 -1
  193. package/field/fesm2022/field.mjs.map +0 -1
  194. package/form-checkbox/fesm2022/form-checkbox.mjs.map +0 -1
  195. package/form-date-picker/fesm2022/form-date-picker.mjs.map +0 -1
  196. package/form-field/fesm2022/form-field.mjs.map +0 -1
  197. package/form-field-simple/fesm2022/form-field-simple.mjs.map +0 -1
  198. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs.map +0 -1
  199. package/form-select/fesm2022/form-select.mjs.map +0 -1
  200. package/form-text-input/fesm2022/form-text-input.mjs.map +0 -1
  201. package/form-textarea/fesm2022/form-textarea.mjs.map +0 -1
  202. package/hover-card/fesm2022/hover-card.mjs.map +0 -1
  203. package/icon/fesm2022/icon.mjs.map +0 -1
  204. package/input/fesm2022/input.mjs.map +0 -1
  205. package/input-group/fesm2022/input-group.mjs.map +0 -1
  206. package/input-otp/fesm2022/input-otp.mjs.map +0 -1
  207. package/item/fesm2022/item.mjs.map +0 -1
  208. package/kbd/fesm2022/kbd.mjs.map +0 -1
  209. package/label/fesm2022/label.mjs.map +0 -1
  210. package/layout-simple/fesm2022/layout-simple.mjs.map +0 -1
  211. package/menubar/fesm2022/menubar.mjs.map +0 -1
  212. package/native-select/fesm2022/native-select.mjs.map +0 -1
  213. package/navigation-menu/fesm2022/navigation-menu.mjs.map +0 -1
  214. package/pagination/fesm2022/pagination.mjs.map +0 -1
  215. package/popover/fesm2022/popover.mjs.map +0 -1
  216. package/progress/fesm2022/progress.mjs.map +0 -1
  217. package/radio-group/fesm2022/radio-group.mjs.map +0 -1
  218. package/resizable/fesm2022/resizable.mjs.map +0 -1
  219. package/scroll-area/fesm2022/scroll-area.mjs.map +0 -1
  220. package/searchable-multiselect/fesm2022/searchable-multiselect.mjs.map +0 -1
  221. package/select/fesm2022/select.mjs.map +0 -1
  222. package/separator/fesm2022/separator.mjs.map +0 -1
  223. package/sheet/fesm2022/sheet.mjs.map +0 -1
  224. package/sidebar/fesm2022/sidebar.mjs.map +0 -1
  225. package/skeleton/fesm2022/skeleton.mjs.map +0 -1
  226. package/slider/fesm2022/slider.mjs.map +0 -1
  227. package/sonner/fesm2022/sonner.mjs.map +0 -1
  228. package/spinner/fesm2022/spinner.mjs.map +0 -1
  229. package/switch/fesm2022/switch.mjs.map +0 -1
  230. package/table/fesm2022/table.mjs.map +0 -1
  231. package/tabs/fesm2022/tabs.mjs.map +0 -1
  232. package/textarea/fesm2022/textarea.mjs.map +0 -1
  233. package/toggle/fesm2022/toggle.mjs.map +0 -1
  234. package/toggle-group/fesm2022/toggle-group.mjs.map +0 -1
  235. package/tooltip/fesm2022/tooltip.mjs.map +0 -1
  236. package/typography/fesm2022/typography.mjs.map +0 -1
  237. 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 { ReactiveFormsModule, FormGroupDirective, ControlContainer } from '@angular/forms';
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(crypto.randomUUID(), /* @ts-ignore */
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('flex flex-col', this.userClass()), /* @ts-ignore */
68
+ $userClass = computed(() => hlm("flex flex-col", this.userClass()), /* @ts-ignore */
54
69
  ...(ngDevMode ? [{ debugName: "$userClass" }] : /* istanbul ignore next */ []));
55
- $labelClass = computed(() => hlm('mb-1', this.labelClass()), /* @ts-ignore */
70
+ $labelClass = computed(() => hlm("mb-1", this.labelClass()), /* @ts-ignore */
56
71
  ...(ngDevMode ? [{ debugName: "$labelClass" }] : /* istanbul ignore next */ []));
57
- $pickerClass = computed(() => hlm('mb-1', this.pickerClass()), /* @ts-ignore */
72
+ $pickerClass = computed(() => hlm("mb-1", this.pickerClass()), /* @ts-ignore */
58
73
  ...(ngDevMode ? [{ debugName: "$pickerClass" }] : /* istanbul ignore next */ []));
59
- $errorClass = computed(() => hlm('mt-0', this.errorClass()), /* @ts-ignore */
74
+ $errorClass = computed(() => hlm("mt-0", this.errorClass()), /* @ts-ignore */
60
75
  ...(ngDevMode ? [{ debugName: "$errorClass" }] : /* istanbul ignore next */ []));
61
- $hintClass = computed(() => hlm('mt-0', this.hintClass()), /* @ts-ignore */
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.0.7", ngImport: i0, type: EgFormDatePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
64
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", 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: `
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.0.7", ngImport: i0, type: EgFormDatePicker, decorators: [{
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: 'w-full',
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
- //# sourceMappingURL=form-date-picker.mjs.map
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>;
@@ -1,64 +1,357 @@
1
- # Formfield
1
+ # Form Field (`@egose/shadcn-theme-ng/form-field`)
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.2.0.
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
- ## Code scaffolding
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
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
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
- ng generate component component-name
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
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
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
- ```bash
16
- ng generate --help
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
- ## Building
48
+ Standalone component — spread the `*Imports` array:
20
49
 
21
- To build the library, run:
50
+ ```ts
51
+ import { Component } from '@angular/core';
52
+ import { HlmFormFieldImports } from '@egose/shadcn-theme-ng/form-field';
22
53
 
23
- ```bash
24
- ng build form-field
54
+ @Component({
55
+ selector: 'app-demo',
56
+ standalone: true,
57
+ imports: [...HlmFormFieldImports],
58
+ template: `...`,
59
+ })
60
+ export class DemoComponent {}
25
61
  ```
26
62
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
63
+ NgModule-based consumer — import the module:
28
64
 
29
- ### Publishing the Library
65
+ ```ts
66
+ import { NgModule } from '@angular/core';
67
+ import { HlmFormFieldModule } from '@egose/shadcn-theme-ng/form-field';
30
68
 
31
- Once the project is built, you can publish your library by following these steps:
69
+ @NgModule({ imports: [HlmFormFieldModule] })
70
+ export class DemoModule {}
71
+ ```
32
72
 
33
- 1. Navigate to the `dist` directory:
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
- ```bash
36
- cd dist/form-field
37
- ```
77
+ ## Anatomy / Structure
38
78
 
39
- 2. Run the `npm publish` command to publish your library to the npm registry:
40
- ```bash
41
- npm publish
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
- ## Running unit tests
90
+ Real selectors (from source):
45
91
 
46
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
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
- ```bash
49
- ng test
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
- ## Running end-to-end tests
132
+ Inject and call `generate('my-field')` for stable label/control ids (used by the `eg-form-*` wrappers).
53
133
 
54
- For end-to-end (e2e) testing, run:
134
+ ## Examples
55
135
 
56
- ```bash
57
- ng e2e
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
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
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
- ## Additional Resources
352
+ ## Related subpaths
63
353
 
64
- For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
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.