@egose/shadcn-theme-ng-tw 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 (244) hide show
  1. package/README.md +130 -13
  2. package/accordion/README.md +376 -34
  3. package/accordion/fesm2022/accordion.mjs +20 -20
  4. package/alert/README.md +343 -34
  5. package/alert/fesm2022/alert.mjs +18 -18
  6. package/alert-dialog/README.md +471 -5
  7. package/alert-dialog/fesm2022/alert-dialog.mjs +48 -48
  8. package/alert-dialog/types/alert-dialog.d.ts +1 -1
  9. package/aspect-ratio/README.md +272 -5
  10. package/aspect-ratio/fesm2022/aspect-ratio.mjs +9 -9
  11. package/autocomplete/README.md +473 -34
  12. package/autocomplete/fesm2022/autocomplete.mjs +54 -54
  13. package/autocomplete/types/autocomplete.d.ts +1 -1
  14. package/avatar/README.md +357 -5
  15. package/avatar/fesm2022/avatar.mjs +32 -32
  16. package/badge/README.md +289 -34
  17. package/badge/fesm2022/badge.mjs +4 -4
  18. package/basic-alert/README.md +324 -34
  19. package/basic-alert/fesm2022/basic-alert.mjs +4 -4
  20. package/basic-alert/types/basic-alert.d.ts +1 -1
  21. package/breadcrumb/README.md +406 -5
  22. package/breadcrumb/fesm2022/breadcrumb.mjs +34 -34
  23. package/button/README.md +453 -34
  24. package/button/fesm2022/button.mjs +96 -118
  25. package/button/types/button.d.ts +7 -10
  26. package/button-group/README.md +318 -5
  27. package/button-group/fesm2022/button-group.mjs +20 -20
  28. package/calendar/README.md +328 -34
  29. package/calendar/fesm2022/calendar.mjs +154 -154
  30. package/card/README.md +331 -5
  31. package/card/fesm2022/card.mjs +33 -33
  32. package/carousel/README.md +333 -5
  33. package/carousel/fesm2022/carousel.mjs +47 -44
  34. package/carousel/types/carousel.d.ts +1 -1
  35. package/checkbox/README.md +291 -34
  36. package/checkbox/fesm2022/checkbox.mjs +22 -20
  37. package/checkbox/types/checkbox.d.ts +5 -4
  38. package/collapsible/README.md +332 -5
  39. package/collapsible/fesm2022/collapsible.mjs +15 -15
  40. package/combobox/README.md +507 -5
  41. package/combobox/fesm2022/combobox.mjs +95 -92
  42. package/combobox/types/combobox.d.ts +1 -1
  43. package/command/README.md +435 -5
  44. package/command/fesm2022/command.mjs +56 -56
  45. package/confirmation-dialog/README.md +272 -34
  46. package/confirmation-dialog/fesm2022/confirmation-dialog.mjs +13 -12
  47. package/confirmation-dialog/types/confirmation-dialog.d.ts +9 -5
  48. package/context-menu/README.md +366 -5
  49. package/context-menu/fesm2022/context-menu.mjs +9 -9
  50. package/date-picker/README.md +436 -34
  51. package/date-picker/fesm2022/date-picker.mjs +79 -68
  52. package/date-picker/types/date-picker.d.ts +8 -4
  53. package/dialog/README.md +419 -34
  54. package/dialog/fesm2022/dialog.mjs +48 -48
  55. package/drawer/README.md +395 -5
  56. package/drawer/fesm2022/drawer.mjs +45 -45
  57. package/dropdown-menu/README.md +417 -5
  58. package/dropdown-menu/fesm2022/dropdown-menu.mjs +85 -73
  59. package/dropdown-menu/types/dropdown-menu.d.ts +4 -0
  60. package/empty/README.md +329 -5
  61. package/empty/fesm2022/empty.mjs +31 -31
  62. package/field/README.md +385 -5
  63. package/field/fesm2022/field.mjs +59 -59
  64. package/field/types/field.d.ts +1 -1
  65. package/form-checkbox/README.md +283 -34
  66. package/form-checkbox/fesm2022/form-checkbox.mjs +37 -19
  67. package/form-checkbox/types/form-checkbox.d.ts +7 -1
  68. package/form-date-picker/README.md +293 -34
  69. package/form-date-picker/fesm2022/form-date-picker.mjs +59 -24
  70. package/form-date-picker/types/form-date-picker.d.ts +7 -1
  71. package/form-field/README.md +327 -34
  72. package/form-field/fesm2022/form-field.mjs +30 -16
  73. package/form-field/types/form-field.d.ts +28 -20
  74. package/form-field-simple/README.md +311 -34
  75. package/form-field-simple/fesm2022/form-field-simple.mjs +4 -4
  76. package/form-searchable-multiselect/README.md +332 -34
  77. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +41 -21
  78. package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +8 -2
  79. package/form-select/README.md +321 -34
  80. package/form-select/fesm2022/form-select.mjs +62 -29
  81. package/form-select/types/form-select.d.ts +7 -1
  82. package/form-text-input/README.md +342 -34
  83. package/form-text-input/fesm2022/form-text-input.mjs +43 -55
  84. package/form-text-input/types/form-text-input.d.ts +9 -12
  85. package/form-textarea/README.md +318 -34
  86. package/form-textarea/fesm2022/form-textarea.mjs +42 -23
  87. package/form-textarea/types/form-textarea.d.ts +8 -1
  88. package/hover-card/README.md +256 -5
  89. package/hover-card/fesm2022/hover-card.mjs +19 -19
  90. package/icon/README.md +210 -34
  91. package/icon/fesm2022/icon.mjs +8 -8
  92. package/input/README.md +240 -34
  93. package/input/fesm2022/input.mjs +17 -12
  94. package/input/types/input.d.ts +2 -1
  95. package/input-group/README.md +335 -5
  96. package/input-group/fesm2022/input-group.mjs +37 -37
  97. package/input-group/types/input-group.d.ts +3 -3
  98. package/input-otp/README.md +375 -5
  99. package/input-otp/fesm2022/input-otp.mjs +27 -27
  100. package/item/README.md +385 -5
  101. package/item/fesm2022/item.mjs +54 -54
  102. package/kbd/README.md +291 -5
  103. package/kbd/fesm2022/kbd.mjs +13 -13
  104. package/label/README.md +243 -34
  105. package/label/fesm2022/label.mjs +9 -9
  106. package/label/types/label.d.ts +1 -1
  107. package/layout-simple/README.md +174 -44
  108. package/layout-simple/fesm2022/layout-simple.mjs +917 -398
  109. package/layout-simple/types/layout-simple.d.ts +180 -129
  110. package/menu/README.md +418 -0
  111. package/menu/fesm2022/menu.mjs +461 -0
  112. package/menu/package.json +24 -0
  113. package/menu/types/menu.d.ts +137 -0
  114. package/menubar/README.md +343 -5
  115. package/menubar/fesm2022/menubar.mjs +13 -13
  116. package/native-select/README.md +323 -5
  117. package/native-select/fesm2022/native-select.mjs +19 -19
  118. package/navigation-menu/README.md +369 -5
  119. package/navigation-menu/fesm2022/navigation-menu.mjs +37 -37
  120. package/package.json +183 -154
  121. package/pagination/README.md +388 -5
  122. package/pagination/fesm2022/pagination.mjs +63 -63
  123. package/popover/README.md +302 -34
  124. package/popover/fesm2022/popover.mjs +30 -30
  125. package/progress/README.md +311 -5
  126. package/progress/fesm2022/progress.mjs +13 -13
  127. package/radio-group/README.md +335 -34
  128. package/radio-group/fesm2022/radio-group.mjs +26 -19
  129. package/resizable/README.md +269 -5
  130. package/resizable/fesm2022/resizable.mjs +19 -19
  131. package/scroll-area/README.md +233 -5
  132. package/scroll-area/fesm2022/scroll-area.mjs +9 -9
  133. package/searchable-multiselect/README.md +294 -34
  134. package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +63 -16
  135. package/searchable-multiselect/types/searchable-multiselect.d.ts +8 -2
  136. package/select/README.md +408 -34
  137. package/select/fesm2022/select.mjs +80 -72
  138. package/select/types/select.d.ts +3 -1
  139. package/separator/README.md +193 -34
  140. package/separator/fesm2022/separator.mjs +9 -9
  141. package/separator/types/separator.d.ts +1 -1
  142. package/sheet/README.md +282 -34
  143. package/sheet/fesm2022/sheet.mjs +30 -30
  144. package/sidebar/README.md +457 -5
  145. package/sidebar/fesm2022/sidebar.mjs +136 -134
  146. package/sidebar/types/sidebar.d.ts +3 -3
  147. package/skeleton/README.md +217 -5
  148. package/skeleton/fesm2022/skeleton.mjs +9 -9
  149. package/slider/README.md +273 -5
  150. package/slider/fesm2022/slider.mjs +29 -25
  151. package/sonner/README.md +317 -34
  152. package/sonner/fesm2022/sonner.mjs +9 -9
  153. package/spinner/README.md +255 -34
  154. package/spinner/fesm2022/spinner.mjs +8 -8
  155. package/switch/README.md +281 -34
  156. package/switch/fesm2022/switch.mjs +12 -14
  157. package/table/README.md +423 -5
  158. package/table/fesm2022/table.mjs +41 -41
  159. package/tabs/README.md +382 -34
  160. package/tabs/fesm2022/tabs.mjs +48 -38
  161. package/textarea/README.md +282 -5
  162. package/textarea/fesm2022/textarea.mjs +9 -9
  163. package/toggle/README.md +270 -5
  164. package/toggle/fesm2022/toggle.mjs +14 -14
  165. package/toggle-group/README.md +340 -5
  166. package/toggle-group/fesm2022/toggle-group.mjs +13 -13
  167. package/toggle-group/types/toggle-group.d.ts +13 -7
  168. package/tooltip/README.md +240 -34
  169. package/tooltip/fesm2022/tooltip.mjs +14 -14
  170. package/typography/README.md +271 -5
  171. package/typography/fesm2022/typography.mjs +53 -53
  172. package/typography/types/typography.d.ts +12 -12
  173. package/utils/README.md +274 -34
  174. package/utils/fesm2022/utils.mjs +68 -44
  175. package/accordion/fesm2022/accordion.mjs.map +0 -1
  176. package/alert/fesm2022/alert.mjs.map +0 -1
  177. package/alert-dialog/fesm2022/alert-dialog.mjs.map +0 -1
  178. package/aspect-ratio/fesm2022/aspect-ratio.mjs.map +0 -1
  179. package/autocomplete/fesm2022/autocomplete.mjs.map +0 -1
  180. package/avatar/fesm2022/avatar.mjs.map +0 -1
  181. package/badge/fesm2022/badge.mjs.map +0 -1
  182. package/basic-alert/fesm2022/basic-alert.mjs.map +0 -1
  183. package/breadcrumb/fesm2022/breadcrumb.mjs.map +0 -1
  184. package/button/fesm2022/button.mjs.map +0 -1
  185. package/button-group/fesm2022/button-group.mjs.map +0 -1
  186. package/calendar/fesm2022/calendar.mjs.map +0 -1
  187. package/card/fesm2022/card.mjs.map +0 -1
  188. package/carousel/fesm2022/carousel.mjs.map +0 -1
  189. package/checkbox/fesm2022/checkbox.mjs.map +0 -1
  190. package/collapsible/fesm2022/collapsible.mjs.map +0 -1
  191. package/combobox/fesm2022/combobox.mjs.map +0 -1
  192. package/command/fesm2022/command.mjs.map +0 -1
  193. package/confirmation-dialog/fesm2022/confirmation-dialog.mjs.map +0 -1
  194. package/context-menu/fesm2022/context-menu.mjs.map +0 -1
  195. package/date-picker/fesm2022/date-picker.mjs.map +0 -1
  196. package/dialog/fesm2022/dialog.mjs.map +0 -1
  197. package/drawer/fesm2022/drawer.mjs.map +0 -1
  198. package/dropdown-menu/fesm2022/dropdown-menu.mjs.map +0 -1
  199. package/empty/fesm2022/empty.mjs.map +0 -1
  200. package/field/fesm2022/field.mjs.map +0 -1
  201. package/form-checkbox/fesm2022/form-checkbox.mjs.map +0 -1
  202. package/form-date-picker/fesm2022/form-date-picker.mjs.map +0 -1
  203. package/form-field/fesm2022/form-field.mjs.map +0 -1
  204. package/form-field-simple/fesm2022/form-field-simple.mjs.map +0 -1
  205. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs.map +0 -1
  206. package/form-select/fesm2022/form-select.mjs.map +0 -1
  207. package/form-text-input/fesm2022/form-text-input.mjs.map +0 -1
  208. package/form-textarea/fesm2022/form-textarea.mjs.map +0 -1
  209. package/hover-card/fesm2022/hover-card.mjs.map +0 -1
  210. package/icon/fesm2022/icon.mjs.map +0 -1
  211. package/input/fesm2022/input.mjs.map +0 -1
  212. package/input-group/fesm2022/input-group.mjs.map +0 -1
  213. package/input-otp/fesm2022/input-otp.mjs.map +0 -1
  214. package/item/fesm2022/item.mjs.map +0 -1
  215. package/kbd/fesm2022/kbd.mjs.map +0 -1
  216. package/label/fesm2022/label.mjs.map +0 -1
  217. package/layout-simple/fesm2022/layout-simple.mjs.map +0 -1
  218. package/menubar/fesm2022/menubar.mjs.map +0 -1
  219. package/native-select/fesm2022/native-select.mjs.map +0 -1
  220. package/navigation-menu/fesm2022/navigation-menu.mjs.map +0 -1
  221. package/pagination/fesm2022/pagination.mjs.map +0 -1
  222. package/popover/fesm2022/popover.mjs.map +0 -1
  223. package/progress/fesm2022/progress.mjs.map +0 -1
  224. package/radio-group/fesm2022/radio-group.mjs.map +0 -1
  225. package/resizable/fesm2022/resizable.mjs.map +0 -1
  226. package/scroll-area/fesm2022/scroll-area.mjs.map +0 -1
  227. package/searchable-multiselect/fesm2022/searchable-multiselect.mjs.map +0 -1
  228. package/select/fesm2022/select.mjs.map +0 -1
  229. package/separator/fesm2022/separator.mjs.map +0 -1
  230. package/sheet/fesm2022/sheet.mjs.map +0 -1
  231. package/sidebar/fesm2022/sidebar.mjs.map +0 -1
  232. package/skeleton/fesm2022/skeleton.mjs.map +0 -1
  233. package/slider/fesm2022/slider.mjs.map +0 -1
  234. package/sonner/fesm2022/sonner.mjs.map +0 -1
  235. package/spinner/fesm2022/spinner.mjs.map +0 -1
  236. package/switch/fesm2022/switch.mjs.map +0 -1
  237. package/table/fesm2022/table.mjs.map +0 -1
  238. package/tabs/fesm2022/tabs.mjs.map +0 -1
  239. package/textarea/fesm2022/textarea.mjs.map +0 -1
  240. package/toggle/fesm2022/toggle.mjs.map +0 -1
  241. package/toggle-group/fesm2022/toggle-group.mjs.map +0 -1
  242. package/tooltip/fesm2022/tooltip.mjs.map +0 -1
  243. package/typography/fesm2022/typography.mjs.map +0 -1
  244. 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-tw/form-field';
4
+ import { FormGroupDirective, ReactiveFormsModule, ControlContainer } from '@angular/forms';
5
+ import { HlmFormIdGenerator, HlmFormField, HlmError, HlmHint } from '@egose/shadcn-theme-ng-tw/form-field';
6
6
  import { HlmLabel } from '@egose/shadcn-theme-ng-tw/label';
7
7
  import { HlmDatePicker, HlmDatePickerInput } from '@egose/shadcn-theme-ng-tw/date-picker';
8
8
  import { hlm } from '@egose/shadcn-theme-ng-tw/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 */
@@ -60,10 +75,9 @@ class EgFormDatePicker {
60
75
  ...(ngDevMode ? [{ debugName: "$errorClass" }] : /* istanbul ignore next */ []));
61
76
  $hintClass = computed(() => hlm('tw: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: "tw:w-full" }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: `
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: "tw:w-full" }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: `
65
80
  @let lbl = label();
66
- @let cid = controlId();
67
81
  @let cnm = controlName();
68
82
  @let err = error();
69
83
  @let hnt = hint();
@@ -71,33 +85,44 @@ class EgFormDatePicker {
71
85
 
72
86
  <hlm-form-field>
73
87
  @if (lbl) {
74
- <span hlmLabel [class]="$labelClass()"
88
+ <label hlmLabel [for]="effectiveId()" [class]="$labelClass()"
75
89
  >{{ lbl }}
76
90
  @if (rqrd) {
77
91
  <span class="tw:text-red-500">*</span>
78
92
  }
79
- </span>
93
+ </label>
80
94
  }
81
95
 
82
- <hlm-date-picker [min]="min()" [max]="max()" [formControlName]="cnm" [class]="$pickerClass()">
83
- <hlm-date-picker-input [inputId]="cid || id()" [placeholder]="placeholder()" />
96
+ <hlm-date-picker
97
+ [min]="min()"
98
+ [max]="max()"
99
+ [wrapperDisabled]="disabled()"
100
+ [formControlName]="cnm"
101
+ [class]="$pickerClass()"
102
+ >
103
+ <hlm-date-picker-input
104
+ [inputId]="effectiveId()"
105
+ [ariaLabel]="lbl"
106
+ [ariaDescribedby]="describedBy()"
107
+ [placeholder]="placeholder()"
108
+ />
84
109
  </hlm-date-picker>
85
110
 
86
111
  @if (err) {
87
- <hlm-error [class]="$errorClass()">
112
+ <hlm-error [id]="errorId()" [class]="$errorClass()">
88
113
  {{ err }}
89
114
  </hlm-error>
90
115
  }
91
116
 
92
117
  @if (hnt) {
93
- <hlm-hint [class]="$hintClass()">
118
+ <hlm-hint [id]="hintId()" [class]="$hintClass()">
94
119
  {{ hnt }}
95
120
  </hlm-hint>
96
121
  }
97
122
  </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"] }] });
123
+ `, 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
124
  }
100
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: EgFormDatePicker, decorators: [{
125
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormDatePicker, decorators: [{
101
126
  type: Component,
102
127
  args: [{
103
128
  selector: 'eg-form-date-picker',
@@ -109,7 +134,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
109
134
  providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
110
135
  template: `
111
136
  @let lbl = label();
112
- @let cid = controlId();
113
137
  @let cnm = controlName();
114
138
  @let err = error();
115
139
  @let hnt = hint();
@@ -117,26 +141,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
117
141
 
118
142
  <hlm-form-field>
119
143
  @if (lbl) {
120
- <span hlmLabel [class]="$labelClass()"
144
+ <label hlmLabel [for]="effectiveId()" [class]="$labelClass()"
121
145
  >{{ lbl }}
122
146
  @if (rqrd) {
123
147
  <span class="tw:text-red-500">*</span>
124
148
  }
125
- </span>
149
+ </label>
126
150
  }
127
151
 
128
- <hlm-date-picker [min]="min()" [max]="max()" [formControlName]="cnm" [class]="$pickerClass()">
129
- <hlm-date-picker-input [inputId]="cid || id()" [placeholder]="placeholder()" />
152
+ <hlm-date-picker
153
+ [min]="min()"
154
+ [max]="max()"
155
+ [wrapperDisabled]="disabled()"
156
+ [formControlName]="cnm"
157
+ [class]="$pickerClass()"
158
+ >
159
+ <hlm-date-picker-input
160
+ [inputId]="effectiveId()"
161
+ [ariaLabel]="lbl"
162
+ [ariaDescribedby]="describedBy()"
163
+ [placeholder]="placeholder()"
164
+ />
130
165
  </hlm-date-picker>
131
166
 
132
167
  @if (err) {
133
- <hlm-error [class]="$errorClass()">
168
+ <hlm-error [id]="errorId()" [class]="$errorClass()">
134
169
  {{ err }}
135
170
  </hlm-error>
136
171
  }
137
172
 
138
173
  @if (hnt) {
139
- <hlm-hint [class]="$hintClass()">
174
+ <hlm-hint [id]="hintId()" [class]="$hintClass()">
140
175
  {{ hnt }}
141
176
  </hlm-hint>
142
177
  }
@@ -154,4 +189,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
154
189
  */
155
190
 
156
191
  export { EgFormDatePicker };
157
- //# sourceMappingURL=form-date-picker.mjs.map
192
+
@@ -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.