forty-cdk 0.2.0 → 0.3.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 (220) hide show
  1. package/accordion/README.md +122 -0
  2. package/aspect-ratio/README.md +76 -0
  3. package/avatar/README.md +100 -0
  4. package/breadcrumbs/README.md +49 -0
  5. package/breakpoints/README.md +81 -0
  6. package/button/README.md +49 -0
  7. package/calendar/README.md +458 -0
  8. package/carousel/README.md +358 -0
  9. package/checkbox/README.md +146 -0
  10. package/combobox/README.md +535 -0
  11. package/context-menu/README.md +139 -0
  12. package/date-field/README.md +184 -0
  13. package/date-picker/README.md +338 -0
  14. package/dialog/README.md +388 -0
  15. package/disclosure/README.md +114 -0
  16. package/drag-drop/README.md +359 -0
  17. package/drawer/README.md +560 -0
  18. package/dropdown-menu/README.md +176 -0
  19. package/fesm2022/forty-cdk-accordion.mjs +348 -0
  20. package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
  21. package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
  22. package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
  23. package/fesm2022/forty-cdk-avatar.mjs +308 -0
  24. package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
  25. package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
  26. package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
  27. package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
  28. package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
  29. package/fesm2022/forty-cdk-button.mjs +134 -0
  30. package/fesm2022/forty-cdk-button.mjs.map +1 -0
  31. package/fesm2022/forty-cdk-calendar.mjs +2034 -0
  32. package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
  33. package/fesm2022/forty-cdk-carousel.mjs +968 -0
  34. package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
  35. package/fesm2022/forty-cdk-checkbox.mjs +226 -0
  36. package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
  37. package/fesm2022/forty-cdk-combobox.mjs +2596 -0
  38. package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
  39. package/fesm2022/forty-cdk-context-menu.mjs +413 -0
  40. package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
  41. package/fesm2022/forty-cdk-core.mjs +9022 -0
  42. package/fesm2022/forty-cdk-core.mjs.map +1 -0
  43. package/fesm2022/forty-cdk-date-field.mjs +744 -0
  44. package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
  45. package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
  46. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
  47. package/fesm2022/forty-cdk-dialog.mjs +707 -0
  48. package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
  49. package/fesm2022/forty-cdk-disclosure.mjs +190 -0
  50. package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
  51. package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
  52. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
  53. package/fesm2022/forty-cdk-drawer.mjs +1641 -0
  54. package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
  55. package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
  56. package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
  57. package/fesm2022/forty-cdk-field.mjs +425 -0
  58. package/fesm2022/forty-cdk-field.mjs.map +1 -0
  59. package/fesm2022/forty-cdk-fieldset.mjs +164 -0
  60. package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
  61. package/fesm2022/forty-cdk-file-upload.mjs +221 -0
  62. package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
  63. package/fesm2022/forty-cdk-hover-card.mjs +496 -0
  64. package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
  65. package/fesm2022/forty-cdk-input.mjs +274 -0
  66. package/fesm2022/forty-cdk-input.mjs.map +1 -0
  67. package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
  68. package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
  69. package/fesm2022/forty-cdk-listbox.mjs +1279 -0
  70. package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
  71. package/fesm2022/forty-cdk-menu.mjs +1439 -0
  72. package/fesm2022/forty-cdk-menu.mjs.map +1 -0
  73. package/fesm2022/forty-cdk-menubar.mjs +787 -0
  74. package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
  75. package/fesm2022/forty-cdk-meter.mjs +211 -0
  76. package/fesm2022/forty-cdk-meter.mjs.map +1 -0
  77. package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
  78. package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
  79. package/fesm2022/forty-cdk-number-input.mjs +559 -0
  80. package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
  81. package/fesm2022/forty-cdk-otp-input.mjs +527 -0
  82. package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
  83. package/fesm2022/forty-cdk-pagination.mjs +323 -0
  84. package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
  85. package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
  86. package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
  87. package/fesm2022/forty-cdk-popover.mjs +698 -0
  88. package/fesm2022/forty-cdk-popover.mjs.map +1 -0
  89. package/fesm2022/forty-cdk-progress.mjs +226 -0
  90. package/fesm2022/forty-cdk-progress.mjs.map +1 -0
  91. package/fesm2022/forty-cdk-radio-group.mjs +378 -0
  92. package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
  93. package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
  94. package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
  95. package/fesm2022/forty-cdk-search.mjs +205 -0
  96. package/fesm2022/forty-cdk-search.mjs.map +1 -0
  97. package/fesm2022/forty-cdk-select.mjs +1661 -0
  98. package/fesm2022/forty-cdk-select.mjs.map +1 -0
  99. package/fesm2022/forty-cdk-separator.mjs +82 -0
  100. package/fesm2022/forty-cdk-separator.mjs.map +1 -0
  101. package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
  102. package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
  103. package/fesm2022/forty-cdk-slider.mjs +803 -0
  104. package/fesm2022/forty-cdk-slider.mjs.map +1 -0
  105. package/fesm2022/forty-cdk-stepper.mjs +886 -0
  106. package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
  107. package/fesm2022/forty-cdk-switch.mjs +137 -0
  108. package/fesm2022/forty-cdk-switch.mjs.map +1 -0
  109. package/fesm2022/forty-cdk-table.mjs +1518 -0
  110. package/fesm2022/forty-cdk-table.mjs.map +1 -0
  111. package/fesm2022/forty-cdk-tabs.mjs +400 -0
  112. package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
  113. package/fesm2022/forty-cdk-time-field.mjs +593 -0
  114. package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
  115. package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
  116. package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
  117. package/fesm2022/forty-cdk-toast.mjs +1153 -0
  118. package/fesm2022/forty-cdk-toast.mjs.map +1 -0
  119. package/fesm2022/forty-cdk-toggle.mjs +516 -0
  120. package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
  121. package/fesm2022/forty-cdk-toolbar.mjs +374 -0
  122. package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
  123. package/fesm2022/forty-cdk-tooltip.mjs +672 -0
  124. package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
  125. package/fesm2022/forty-cdk-tree.mjs +2007 -0
  126. package/fesm2022/forty-cdk-tree.mjs.map +1 -0
  127. package/fesm2022/forty-cdk-virtualization.mjs +1 -1
  128. package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
  129. package/fesm2022/forty-cdk.mjs +0 -43310
  130. package/fesm2022/forty-cdk.mjs.map +1 -1
  131. package/field/README.md +97 -0
  132. package/fieldset/README.md +86 -0
  133. package/file-upload/README.md +73 -0
  134. package/hover-card/README.md +171 -0
  135. package/input/README.md +156 -0
  136. package/listbox/README.md +424 -0
  137. package/menu/README.md +181 -0
  138. package/menubar/README.md +140 -0
  139. package/meter/README.md +128 -0
  140. package/navigation-menu/README.md +253 -0
  141. package/number-input/README.md +171 -0
  142. package/otp-input/README.md +198 -0
  143. package/package.json +213 -1
  144. package/pagination/README.md +61 -0
  145. package/pane-resizer/README.md +136 -0
  146. package/popover/README.md +262 -0
  147. package/progress/README.md +115 -0
  148. package/radio-group/README.md +129 -0
  149. package/scroll-area/README.md +184 -0
  150. package/search/README.md +42 -0
  151. package/select/README.md +488 -0
  152. package/separator/README.md +84 -0
  153. package/signal-forms/README.md +72 -0
  154. package/slider/README.md +152 -0
  155. package/stepper/README.md +292 -0
  156. package/switch/README.md +116 -0
  157. package/table/README.md +769 -0
  158. package/tabs/README.md +130 -0
  159. package/time-field/README.md +157 -0
  160. package/time-picker/README.md +172 -0
  161. package/toast/README.md +398 -0
  162. package/toggle/README.md +224 -0
  163. package/toolbar/README.md +109 -0
  164. package/tooltip/README.md +274 -0
  165. package/tree/README.md +708 -0
  166. package/types/forty-cdk-accordion.d.ts +242 -0
  167. package/types/forty-cdk-aspect-ratio.d.ts +59 -0
  168. package/types/forty-cdk-avatar.d.ts +133 -0
  169. package/types/forty-cdk-breadcrumbs.d.ts +92 -0
  170. package/types/forty-cdk-breakpoints.d.ts +141 -0
  171. package/types/forty-cdk-button.d.ts +80 -0
  172. package/types/forty-cdk-calendar.d.ts +914 -0
  173. package/types/forty-cdk-carousel.d.ts +530 -0
  174. package/types/forty-cdk-checkbox.d.ts +141 -0
  175. package/types/forty-cdk-combobox.d.ts +1259 -0
  176. package/types/forty-cdk-context-menu.d.ts +313 -0
  177. package/types/forty-cdk-core.d.ts +5774 -0
  178. package/types/forty-cdk-date-field.d.ts +307 -0
  179. package/types/forty-cdk-date-picker.d.ts +622 -0
  180. package/types/forty-cdk-dialog.d.ts +546 -0
  181. package/types/forty-cdk-disclosure.d.ts +127 -0
  182. package/types/forty-cdk-drag-drop.d.ts +456 -0
  183. package/types/forty-cdk-drawer.d.ts +871 -0
  184. package/types/forty-cdk-dropdown-menu.d.ts +242 -0
  185. package/types/forty-cdk-field.d.ts +236 -0
  186. package/types/forty-cdk-fieldset.d.ts +119 -0
  187. package/types/forty-cdk-file-upload.d.ts +124 -0
  188. package/types/forty-cdk-hover-card.d.ts +320 -0
  189. package/types/forty-cdk-input.d.ts +169 -0
  190. package/types/forty-cdk-internationalized-date.d.ts +1 -1
  191. package/types/forty-cdk-listbox.d.ts +513 -0
  192. package/types/forty-cdk-menu.d.ts +629 -0
  193. package/types/forty-cdk-menubar.d.ts +451 -0
  194. package/types/forty-cdk-meter.d.ts +122 -0
  195. package/types/forty-cdk-navigation-menu.d.ts +514 -0
  196. package/types/forty-cdk-number-input.d.ts +319 -0
  197. package/types/forty-cdk-otp-input.d.ts +248 -0
  198. package/types/forty-cdk-pagination.d.ts +214 -0
  199. package/types/forty-cdk-pane-resizer.d.ts +145 -0
  200. package/types/forty-cdk-popover.d.ts +509 -0
  201. package/types/forty-cdk-progress.d.ts +143 -0
  202. package/types/forty-cdk-radio-group.d.ts +222 -0
  203. package/types/forty-cdk-scroll-area.d.ts +258 -0
  204. package/types/forty-cdk-search.d.ts +142 -0
  205. package/types/forty-cdk-select.d.ts +899 -0
  206. package/types/forty-cdk-separator.d.ts +59 -0
  207. package/types/forty-cdk-signal-forms.d.ts +58 -0
  208. package/types/forty-cdk-slider.d.ts +379 -0
  209. package/types/forty-cdk-stepper.d.ts +650 -0
  210. package/types/forty-cdk-switch.d.ts +87 -0
  211. package/types/forty-cdk-table.d.ts +723 -0
  212. package/types/forty-cdk-tabs.d.ts +235 -0
  213. package/types/forty-cdk-time-field.d.ts +307 -0
  214. package/types/forty-cdk-time-picker.d.ts +578 -0
  215. package/types/forty-cdk-toast.d.ts +598 -0
  216. package/types/forty-cdk-toggle.d.ts +310 -0
  217. package/types/forty-cdk-toolbar.d.ts +217 -0
  218. package/types/forty-cdk-tooltip.d.ts +436 -0
  219. package/types/forty-cdk-tree.d.ts +688 -0
  220. package/types/forty-cdk.d.ts +1 -19743
@@ -0,0 +1,425 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, signal, computed, Directive, ElementRef, DestroyRef, input, booleanAttribute } from '@angular/core';
3
+ import { FOR_FIELD_CONTEXT, IdGenerator, FOR_FIELDSET_CONTEXT, adoptHostId, injectFieldWiring, createDefaults } from 'forty-cdk/core';
4
+ export { FOR_FIELD_CONTEXT, injectFieldWiring } from 'forty-cdk/core';
5
+
6
+ /**
7
+ * Resolves the surrounding `ForField` context, throwing if the consumer
8
+ * placed the piece outside a `[forField]` element.
9
+ */
10
+ function injectFieldContext(piece) {
11
+ const ctx = inject(FOR_FIELD_CONTEXT, { optional: true });
12
+ if (!ctx) {
13
+ throw new Error(`[forty-cdk/field] ${piece} must be used inside a [forField] element.`);
14
+ }
15
+ return ctx;
16
+ }
17
+
18
+ /**
19
+ * Headless form-field container that wires accessible labelling, description,
20
+ * and error association for a single control. It renders nothing and imposes no
21
+ * layout: its only job is to connect a `[forLabel]`, `[forFieldDescription]`,
22
+ * and `[forFieldError]` to the control via `id` / `aria-labelledby` /
23
+ * `aria-describedby` / `aria-errormessage`, and to reflect the control's
24
+ * validation state as `data-*` hooks for styling.
25
+ *
26
+ * Any forty-cdk form control (every `FormValueControl` / `FormCheckboxControl`
27
+ * primitive) auto-associates when wrapped — no extra markup. A plain native
28
+ * `<input>` opts in with `[forFieldControl]`.
29
+ *
30
+ * Host reflects `data-invalid` / `data-disabled` / `data-required` /
31
+ * `data-touched` from the registered control.
32
+ *
33
+ * @example
34
+ * ```html
35
+ * <div forField>
36
+ * <label forLabel>Notifications</label>
37
+ * <button forSwitch [formField]="settings.notify"></button>
38
+ * <p forFieldDescription>We'll only email you about security.</p>
39
+ * @if (settings.notify().invalid()) {
40
+ * <p forFieldError #err="forFieldError">{{ err.messages().join(', ') }}</p>
41
+ * }
42
+ * </div>
43
+ * ```
44
+ */
45
+ class ForField {
46
+ #idGen = inject(IdGenerator);
47
+ #fieldset = inject(FOR_FIELDSET_CONTEXT, { optional: true });
48
+ #controlId = signal(this.#idGen.next('for-field-control'), /* @ts-ignore */
49
+ ...(ngDevMode ? [{ debugName: "#controlId" }] : /* istanbul ignore next */ []));
50
+ #control = signal(null, /* @ts-ignore */
51
+ ...(ngDevMode ? [{ debugName: "#control" }] : /* istanbul ignore next */ []));
52
+ #hasLabel = signal(false, /* @ts-ignore */
53
+ ...(ngDevMode ? [{ debugName: "#hasLabel" }] : /* istanbul ignore next */ []));
54
+ #hasDescription = signal(false, /* @ts-ignore */
55
+ ...(ngDevMode ? [{ debugName: "#hasDescription" }] : /* istanbul ignore next */ []));
56
+ #hasError = signal(false, /* @ts-ignore */
57
+ ...(ngDevMode ? [{ debugName: "#hasError" }] : /* istanbul ignore next */ []));
58
+ /**
59
+ * The element the field actually targets for `id` / `aria-*` association and
60
+ * focus: the control's nominated `labelledElement` (Select trigger / Combobox
61
+ * input) when present, else its host (Listbox, native `<input>`).
62
+ */
63
+ #targetEl = computed(() => {
64
+ const control = this.#control();
65
+ if (!control) {
66
+ return null;
67
+ }
68
+ return control.labelledElement?.() ?? control.host;
69
+ }, /* @ts-ignore */
70
+ ...(ngDevMode ? [{ debugName: "#targetEl" }] : /* istanbul ignore next */ []));
71
+ /**
72
+ * Id assigned to the control; a label's `for` points here. Adopts a
73
+ * nominated control's own id (`labelledElementId`) when present so the label
74
+ * and the primitive's internal `aria-labelledby` resolve to the same
75
+ * focusable element; otherwise the field's owned/host-adopted id.
76
+ */
77
+ controlId = computed(() => this.#control()?.labelledElementId?.() ?? this.#controlId(), /* @ts-ignore */
78
+ ...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
79
+ /** Id of the label element. */
80
+ labelId = signal(this.#idGen.next('for-field-label'), /* @ts-ignore */
81
+ ...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
82
+ /** Id of the description element. */
83
+ descriptionId = signal(this.#idGen.next('for-field-description'), /* @ts-ignore */
84
+ ...(ngDevMode ? [{ debugName: "descriptionId" }] : /* istanbul ignore next */ []));
85
+ /** Id of the error element. */
86
+ errorId = signal(this.#idGen.next('for-field-error'), /* @ts-ignore */
87
+ ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
88
+ /** The currently registered control handle, or null. */
89
+ control = this.#control.asReadonly();
90
+ /** Whether the registered control is currently invalid. */
91
+ invalid = computed(() => this.#control()?.invalid?.() ?? false, /* @ts-ignore */
92
+ ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
93
+ /** Whether the registered control is required. */
94
+ required = computed(() => this.#control()?.required?.() ?? false, /* @ts-ignore */
95
+ ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
96
+ /**
97
+ * Whether the field is disabled: the registered control's own disabled state
98
+ * OR'd with a surrounding `[forFieldset]`'s `disabled` (so a disabled group
99
+ * reaches custom-role controls a native `<fieldset disabled>` can't).
100
+ */
101
+ disabled = computed(() => (this.#control()?.disabled?.() ?? false) || (this.#fieldset?.disabled() ?? false), /* @ts-ignore */
102
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
103
+ /** Whether the registered control has been touched. */
104
+ touched = computed(() => this.#control()?.touched?.() ?? false, /* @ts-ignore */
105
+ ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
106
+ /** Resolved `aria-labelledby` for the control (label id, or null). */
107
+ labelledBy = computed(() => (this.#hasLabel() ? this.labelId() : null), /* @ts-ignore */
108
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
109
+ /**
110
+ * Resolved `aria-describedby`: the description id always, plus the error id
111
+ * when the control is invalid. The error id is included here (in addition to
112
+ * `aria-errormessage`) because assistive-tech support for `aria-errormessage`
113
+ * is uneven; doubling it up is the widely-used robust pattern.
114
+ */
115
+ describedBy = computed(() => {
116
+ const ids = [];
117
+ if (this.#hasDescription()) {
118
+ ids.push(this.descriptionId());
119
+ }
120
+ if (this.#hasError() && this.invalid()) {
121
+ ids.push(this.errorId());
122
+ }
123
+ return ids.length > 0 ? ids.join(' ') : null;
124
+ }, /* @ts-ignore */
125
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
126
+ /** Resolved `aria-errormessage`: the error id when present and invalid, else null. */
127
+ errorMessageId = computed(() => this.#hasError() && this.invalid() ? this.errorId() : null, /* @ts-ignore */
128
+ ...(ngDevMode ? [{ debugName: "errorMessageId" }] : /* istanbul ignore next */ []));
129
+ /** Register the control whose state the field reflects. */
130
+ registerControl(handle) {
131
+ this.#control.set(handle);
132
+ // Adopt a consumer-set id on the host (the host-is-the-control case). When
133
+ // the control nominates a distinct labelled element it carries its own
134
+ // `labelledElementId`, which `controlId` prefers — so a wrapper-host id, if
135
+ // any, is harmlessly ignored there.
136
+ adoptHostId(handle.host, this.#controlId);
137
+ }
138
+ /** Remove a previously registered control. */
139
+ unregisterControl(handle) {
140
+ if (this.#control() === handle) {
141
+ this.#control.set(null);
142
+ }
143
+ }
144
+ /**
145
+ * Mark the label slot present; returns an unregister callback. The field
146
+ * targets a single label per slot — its `labelId` / `descriptionId` /
147
+ * `errorId` are single ids, not id lists — so one `[forLabel]`,
148
+ * `[forFieldDescription]`, and `[forFieldError]` per field is the supported
149
+ * shape. Presence is a boolean, not a count.
150
+ */
151
+ registerLabel() {
152
+ this.#hasLabel.set(true);
153
+ return () => this.#hasLabel.set(false);
154
+ }
155
+ /** Mark the description slot present; returns an unregister callback. See {@link registerLabel} for the single-instance-per-slot contract. */
156
+ registerDescription() {
157
+ this.#hasDescription.set(true);
158
+ return () => this.#hasDescription.set(false);
159
+ }
160
+ /** Mark the error slot present; returns an unregister callback. See {@link registerLabel} for the single-instance-per-slot contract. */
161
+ registerError() {
162
+ this.#hasError.set(true);
163
+ return () => this.#hasError.set(false);
164
+ }
165
+ /**
166
+ * Forward a click to the control's focusable element and focus it — matching
167
+ * the activation a native `<label for>` triggers via the browser, so a
168
+ * non-`<label>` `[forLabel]` toggles a custom-role control instead of only
169
+ * focusing it.
170
+ */
171
+ clickControl() {
172
+ const target = this.#targetEl();
173
+ if (!target) {
174
+ return;
175
+ }
176
+ target.click();
177
+ target.focus();
178
+ }
179
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForField, deps: [], target: i0.ɵɵFactoryTarget.Directive });
180
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForField, isStandalone: true, selector: "[forField]", host: { properties: { "attr.data-invalid": "invalid() ? \"\" : null", "attr.data-disabled": "disabled() ? \"\" : null", "attr.data-required": "required() ? \"\" : null", "attr.data-touched": "touched() ? \"\" : null" } }, providers: [{ provide: FOR_FIELD_CONTEXT, useExisting: ForField }], exportAs: ["forField"], ngImport: i0 });
181
+ }
182
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForField, decorators: [{
183
+ type: Directive,
184
+ args: [{
185
+ selector: '[forField]',
186
+ exportAs: 'forField',
187
+ host: {
188
+ '[attr.data-invalid]': 'invalid() ? "" : null',
189
+ '[attr.data-disabled]': 'disabled() ? "" : null',
190
+ '[attr.data-required]': 'required() ? "" : null',
191
+ '[attr.data-touched]': 'touched() ? "" : null',
192
+ },
193
+ providers: [{ provide: FOR_FIELD_CONTEXT, useExisting: ForField }],
194
+ }]
195
+ }] });
196
+
197
+ /**
198
+ * Accessible label for a form control. Inside a `[forField]` it auto-wires:
199
+ * it adopts the field's `labelId` and registers itself so the control gains
200
+ * `aria-labelledby`.
201
+ *
202
+ * Click-to-activate is consistent across both host shapes: a native `<label>`
203
+ * emits `for` and the browser forwards the click to the control (toggling a
204
+ * checkbox / switch, activating a button); a non-`<label>` host has no native
205
+ * `for` forwarding, so the directive forwards the click itself — clicking the
206
+ * label activates the control either way, not just focuses it. A click that
207
+ * originated on the control (when the control is nested inside the label host)
208
+ * is not re-forwarded, so a label-wrapping layout toggles once, matching native
209
+ * `<label>` semantics.
210
+ *
211
+ * Usable standalone outside a field — there it is an inert
212
+ * marker and the consumer wires native `for` themselves.
213
+ *
214
+ * @example
215
+ * ```html
216
+ * <div forField>
217
+ * <label forLabel>Email</label>
218
+ * <input forFieldControl type="email" />
219
+ * </div>
220
+ * ```
221
+ */
222
+ class ForLabel {
223
+ ctx = inject(FOR_FIELD_CONTEXT, { optional: true });
224
+ #host = inject(ElementRef);
225
+ /** The label's id when inside a field, else null. */
226
+ labelId = computed(() => this.ctx?.labelId() ?? null, /* @ts-ignore */
227
+ ...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
228
+ /** `for` is only meaningful on a native `<label>`; null otherwise. */
229
+ forAttr = computed(() => {
230
+ if (!this.ctx || this.#host.nativeElement.tagName !== 'LABEL') {
231
+ return null;
232
+ }
233
+ return this.ctx.controlId();
234
+ }, /* @ts-ignore */
235
+ ...(ngDevMode ? [{ debugName: "forAttr" }] : /* istanbul ignore next */ []));
236
+ constructor() {
237
+ const ctx = this.ctx;
238
+ if (ctx) {
239
+ const unregister = ctx.registerLabel();
240
+ inject(DestroyRef).onDestroy(unregister);
241
+ }
242
+ }
243
+ onClick(event) {
244
+ if (!this.ctx || this.#host.nativeElement.tagName === 'LABEL') {
245
+ return;
246
+ }
247
+ // When the control is nested inside the label host (label wraps control),
248
+ // a click on the control already activates it natively; forwarding again
249
+ // would double-toggle. Mirror native `<label>` semantics and bail when the
250
+ // click originated from the control (or anything inside it).
251
+ const control = this.ctx.control();
252
+ const controlEl = control?.labelledElement?.() ?? control?.host ?? null;
253
+ const target = event.target;
254
+ if (controlEl && target && controlEl.contains(target)) {
255
+ return;
256
+ }
257
+ this.ctx.clickControl();
258
+ }
259
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
260
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForLabel, isStandalone: true, selector: "[forLabel]", host: { listeners: { "click": "onClick($event)" }, properties: { "attr.id": "labelId()", "attr.for": "forAttr()" } }, exportAs: ["forLabel"], ngImport: i0 });
261
+ }
262
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForLabel, decorators: [{
263
+ type: Directive,
264
+ args: [{
265
+ selector: '[forLabel]',
266
+ exportAs: 'forLabel',
267
+ host: {
268
+ '[attr.id]': 'labelId()',
269
+ '[attr.for]': 'forAttr()',
270
+ '(click)': 'onClick($event)',
271
+ },
272
+ }]
273
+ }], ctorParameters: () => [] });
274
+
275
+ /**
276
+ * Supplementary description / hint for a form control. Adopts the field's
277
+ * `descriptionId` and registers itself so the control gains
278
+ * `aria-describedby`. Apply on a `<p>` / `<span>` inside a `[forField]`.
279
+ */
280
+ class ForFieldDescription {
281
+ ctx = injectFieldContext('ForFieldDescription');
282
+ constructor() {
283
+ const unregister = this.ctx.registerDescription();
284
+ inject(DestroyRef).onDestroy(unregister);
285
+ }
286
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive });
287
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForFieldDescription, isStandalone: true, selector: "[forFieldDescription]", host: { properties: { "attr.id": "ctx.descriptionId()" } }, exportAs: ["forFieldDescription"], ngImport: i0 });
288
+ }
289
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldDescription, decorators: [{
290
+ type: Directive,
291
+ args: [{
292
+ selector: '[forFieldDescription]',
293
+ exportAs: 'forFieldDescription',
294
+ host: {
295
+ '[attr.id]': 'ctx.descriptionId()',
296
+ },
297
+ }]
298
+ }], ctorParameters: () => [] });
299
+
300
+ function extractMessage(error) {
301
+ if ('message' in error && typeof error.message === 'string' && error.message !== '') {
302
+ return error.message;
303
+ }
304
+ return error.kind;
305
+ }
306
+ /**
307
+ * Error region for a form control. Adopts the field's `errorId`, registers
308
+ * itself so the control gains `aria-errormessage` (and the id is folded into
309
+ * `aria-describedby`) while invalid, and **reads the control's Signal Forms
310
+ * validation errors automatically** — the consumer renders `messages()` (or
311
+ * the raw `errors()`) without any manual plumbing.
312
+ *
313
+ * Carries `role="alert"`. Drive its presence with `@if (err.shown())` (or the
314
+ * field's own `invalid()`); when unmounted the control's `aria-errormessage`
315
+ * drops automatically.
316
+ *
317
+ * @example
318
+ * ```html
319
+ * @if (err.shown()) {
320
+ * <p forFieldError #err="forFieldError">{{ err.messages().join(', ') }}</p>
321
+ * }
322
+ * ```
323
+ */
324
+ class ForFieldError {
325
+ ctx = injectFieldContext('ForFieldError');
326
+ /** The control's current validation errors (Signal Forms). */
327
+ errors = computed(() => this.ctx.control()?.errors?.() ?? [], /* @ts-ignore */
328
+ ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
329
+ /** Human-readable messages derived from {@link errors}. */
330
+ messages = computed(() => this.errors().map((error) => extractMessage(error)), /* @ts-ignore */
331
+ ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
332
+ /** Whether the control currently has at least one error. */
333
+ hasErrors = computed(() => this.errors().length > 0, /* @ts-ignore */
334
+ ...(ngDevMode ? [{ debugName: "hasErrors" }] : /* istanbul ignore next */ []));
335
+ /** Whether the error region should be shown (control invalid and has errors). */
336
+ shown = computed(() => this.ctx.invalid() && this.hasErrors(), /* @ts-ignore */
337
+ ...(ngDevMode ? [{ debugName: "shown" }] : /* istanbul ignore next */ []));
338
+ constructor() {
339
+ const unregister = this.ctx.registerError();
340
+ inject(DestroyRef).onDestroy(unregister);
341
+ }
342
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldError, deps: [], target: i0.ɵɵFactoryTarget.Directive });
343
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForFieldError, isStandalone: true, selector: "[forFieldError]", host: { attributes: { "role": "alert" }, properties: { "attr.id": "ctx.errorId()" } }, exportAs: ["forFieldError"], ngImport: i0 });
344
+ }
345
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldError, decorators: [{
346
+ type: Directive,
347
+ args: [{
348
+ selector: '[forFieldError]',
349
+ exportAs: 'forFieldError',
350
+ host: {
351
+ '[attr.id]': 'ctx.errorId()',
352
+ role: 'alert',
353
+ },
354
+ }]
355
+ }], ctorParameters: () => [] });
356
+
357
+ /**
358
+ * Opt-in marker that wires a non-forty-cdk control — a native `<input>`,
359
+ * `<textarea>`, `<select>`, or any custom element — into its surrounding
360
+ * `[forField]`. forty-cdk form primitives (which extend the shared form base)
361
+ * auto-wire and do **not** need this; it exists for plain native controls that
362
+ * have no primitive of their own.
363
+ *
364
+ * Validation state is consumer-driven via the `invalid` / `required` /
365
+ * `disabled` / `touched` inputs (native controls have no Signal Forms errors
366
+ * to surface). Reflects `aria-invalid` on the host.
367
+ *
368
+ * @example
369
+ * ```html
370
+ * <div forField>
371
+ * <label forLabel>Email</label>
372
+ * <input forFieldControl type="email" [invalid]="emailInvalid()" />
373
+ * </div>
374
+ * ```
375
+ */
376
+ class ForFieldControl {
377
+ /** Marks the control invalid — drives the field's error region and `aria-invalid`. */
378
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
379
+ /** Marks the control required — reflected by the field as `data-required`. */
380
+ required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
381
+ /** Marks the control disabled — reflected by the field as `data-disabled`. */
382
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
383
+ /** Marks the control touched — reflected by the field as `data-touched`. */
384
+ touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
385
+ constructor() {
386
+ injectFieldWiring({
387
+ invalid: this.invalid,
388
+ required: this.required,
389
+ disabled: this.disabled,
390
+ touched: this.touched,
391
+ });
392
+ }
393
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
394
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForFieldControl, isStandalone: true, selector: "[forFieldControl]", inputs: { invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-invalid": "invalid() ? \"true\" : null" } }, exportAs: ["forFieldControl"], ngImport: i0 });
395
+ }
396
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldControl, decorators: [{
397
+ type: Directive,
398
+ args: [{
399
+ selector: '[forFieldControl]',
400
+ exportAs: 'forFieldControl',
401
+ host: {
402
+ '[attr.aria-invalid]': 'invalid() ? "true" : null',
403
+ },
404
+ }]
405
+ }], ctorParameters: () => [], propDecorators: { invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }] } });
406
+
407
+ const FALLBACK = {};
408
+ const { token, provideDefaults } = createDefaults('FOR_FIELD_DEFAULTS', FALLBACK);
409
+ /** Token holding the resolved field defaults for the current scope. */
410
+ const FOR_FIELD_DEFAULTS = token;
411
+ /**
412
+ * Configures forty-cdk field defaults for this injector scope. Partial
413
+ * overrides inherit unspecified keys from the parent scope (or library
414
+ * defaults at the root).
415
+ */
416
+ function provideForFieldDefaults(defaults = {}) {
417
+ return provideDefaults(defaults);
418
+ }
419
+
420
+ /**
421
+ * Generated bundle index. Do not edit.
422
+ */
423
+
424
+ export { FOR_FIELD_DEFAULTS, ForField, ForFieldControl, ForFieldDescription, ForFieldError, ForLabel, provideForFieldDefaults };
425
+ //# sourceMappingURL=forty-cdk-field.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forty-cdk-field.mjs","sources":["../../../projects/forty-cdk/field/src/field-context.ts","../../../projects/forty-cdk/field/src/field.ts","../../../projects/forty-cdk/field/src/label.ts","../../../projects/forty-cdk/field/src/field-description.ts","../../../projects/forty-cdk/field/src/field-error.ts","../../../projects/forty-cdk/field/src/field-control.ts","../../../projects/forty-cdk/field/src/field-defaults.ts","../../../projects/forty-cdk/field/src/forty-cdk-field.ts"],"sourcesContent":["import { inject } from '@angular/core';\n\nimport { FOR_FIELD_CONTEXT, type ForFieldContext } from 'forty-cdk/core';\n\nexport {\n FOR_FIELD_CONTEXT,\n injectFieldWiring,\n type ForFieldContext,\n type FieldControlHandle,\n} from 'forty-cdk/core';\n\n/**\n * Resolves the surrounding `ForField` context, throwing if the consumer\n * placed the piece outside a `[forField]` element.\n */\nexport function injectFieldContext(piece: string): ForFieldContext {\n const ctx = inject(FOR_FIELD_CONTEXT, { optional: true });\n if (!ctx) {\n throw new Error(`[forty-cdk/field] ${piece} must be used inside a [forField] element.`);\n }\n return ctx;\n}\n","import { computed, Directive, inject, signal } from '@angular/core';\n\nimport { adoptHostId, IdGenerator, FOR_FIELDSET_CONTEXT } from 'forty-cdk/core';\nimport { FOR_FIELD_CONTEXT, type FieldControlHandle, type ForFieldContext } from './field-context';\n\n/**\n * Headless form-field container that wires accessible labelling, description,\n * and error association for a single control. It renders nothing and imposes no\n * layout: its only job is to connect a `[forLabel]`, `[forFieldDescription]`,\n * and `[forFieldError]` to the control via `id` / `aria-labelledby` /\n * `aria-describedby` / `aria-errormessage`, and to reflect the control's\n * validation state as `data-*` hooks for styling.\n *\n * Any forty-cdk form control (every `FormValueControl` / `FormCheckboxControl`\n * primitive) auto-associates when wrapped — no extra markup. A plain native\n * `<input>` opts in with `[forFieldControl]`.\n *\n * Host reflects `data-invalid` / `data-disabled` / `data-required` /\n * `data-touched` from the registered control.\n *\n * @example\n * ```html\n * <div forField>\n * <label forLabel>Notifications</label>\n * <button forSwitch [formField]=\"settings.notify\"></button>\n * <p forFieldDescription>We'll only email you about security.</p>\n * @if (settings.notify().invalid()) {\n * <p forFieldError #err=\"forFieldError\">{{ err.messages().join(', ') }}</p>\n * }\n * </div>\n * ```\n */\n@Directive({\n selector: '[forField]',\n exportAs: 'forField',\n host: {\n '[attr.data-invalid]': 'invalid() ? \"\" : null',\n '[attr.data-disabled]': 'disabled() ? \"\" : null',\n '[attr.data-required]': 'required() ? \"\" : null',\n '[attr.data-touched]': 'touched() ? \"\" : null',\n },\n providers: [{ provide: FOR_FIELD_CONTEXT, useExisting: ForField }],\n})\nexport class ForField implements ForFieldContext {\n readonly #idGen = inject(IdGenerator);\n readonly #fieldset = inject(FOR_FIELDSET_CONTEXT, { optional: true });\n\n readonly #controlId = signal(this.#idGen.next('for-field-control'));\n readonly #control = signal<FieldControlHandle | null>(null);\n readonly #hasLabel = signal(false);\n readonly #hasDescription = signal(false);\n readonly #hasError = signal(false);\n\n /**\n * The element the field actually targets for `id` / `aria-*` association and\n * focus: the control's nominated `labelledElement` (Select trigger / Combobox\n * input) when present, else its host (Listbox, native `<input>`).\n */\n readonly #targetEl = computed<HTMLElement | null>(() => {\n const control = this.#control();\n if (!control) {\n return null;\n }\n return control.labelledElement?.() ?? control.host;\n });\n\n /**\n * Id assigned to the control; a label's `for` points here. Adopts a\n * nominated control's own id (`labelledElementId`) when present so the label\n * and the primitive's internal `aria-labelledby` resolve to the same\n * focusable element; otherwise the field's owned/host-adopted id.\n */\n readonly controlId = computed(() => this.#control()?.labelledElementId?.() ?? this.#controlId());\n /** Id of the label element. */\n readonly labelId = signal(this.#idGen.next('for-field-label'));\n /** Id of the description element. */\n readonly descriptionId = signal(this.#idGen.next('for-field-description'));\n /** Id of the error element. */\n readonly errorId = signal(this.#idGen.next('for-field-error'));\n /** The currently registered control handle, or null. */\n readonly control = this.#control.asReadonly();\n\n /** Whether the registered control is currently invalid. */\n readonly invalid = computed(() => this.#control()?.invalid?.() ?? false);\n /** Whether the registered control is required. */\n readonly required = computed(() => this.#control()?.required?.() ?? false);\n /**\n * Whether the field is disabled: the registered control's own disabled state\n * OR'd with a surrounding `[forFieldset]`'s `disabled` (so a disabled group\n * reaches custom-role controls a native `<fieldset disabled>` can't).\n */\n readonly disabled = computed(\n () => (this.#control()?.disabled?.() ?? false) || (this.#fieldset?.disabled() ?? false),\n );\n /** Whether the registered control has been touched. */\n readonly touched = computed(() => this.#control()?.touched?.() ?? false);\n\n /** Resolved `aria-labelledby` for the control (label id, or null). */\n readonly labelledBy = computed(() => (this.#hasLabel() ? this.labelId() : null));\n\n /**\n * Resolved `aria-describedby`: the description id always, plus the error id\n * when the control is invalid. The error id is included here (in addition to\n * `aria-errormessage`) because assistive-tech support for `aria-errormessage`\n * is uneven; doubling it up is the widely-used robust pattern.\n */\n readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.#hasDescription()) {\n ids.push(this.descriptionId());\n }\n if (this.#hasError() && this.invalid()) {\n ids.push(this.errorId());\n }\n return ids.length > 0 ? ids.join(' ') : null;\n });\n\n /** Resolved `aria-errormessage`: the error id when present and invalid, else null. */\n readonly errorMessageId = computed(() =>\n this.#hasError() && this.invalid() ? this.errorId() : null,\n );\n\n /** Register the control whose state the field reflects. */\n registerControl(handle: FieldControlHandle): void {\n this.#control.set(handle);\n // Adopt a consumer-set id on the host (the host-is-the-control case). When\n // the control nominates a distinct labelled element it carries its own\n // `labelledElementId`, which `controlId` prefers — so a wrapper-host id, if\n // any, is harmlessly ignored there.\n adoptHostId(handle.host, this.#controlId);\n }\n\n /** Remove a previously registered control. */\n unregisterControl(handle: FieldControlHandle): void {\n if (this.#control() === handle) {\n this.#control.set(null);\n }\n }\n\n /**\n * Mark the label slot present; returns an unregister callback. The field\n * targets a single label per slot — its `labelId` / `descriptionId` /\n * `errorId` are single ids, not id lists — so one `[forLabel]`,\n * `[forFieldDescription]`, and `[forFieldError]` per field is the supported\n * shape. Presence is a boolean, not a count.\n */\n registerLabel(): () => void {\n this.#hasLabel.set(true);\n return () => this.#hasLabel.set(false);\n }\n\n /** Mark the description slot present; returns an unregister callback. See {@link registerLabel} for the single-instance-per-slot contract. */\n registerDescription(): () => void {\n this.#hasDescription.set(true);\n return () => this.#hasDescription.set(false);\n }\n\n /** Mark the error slot present; returns an unregister callback. See {@link registerLabel} for the single-instance-per-slot contract. */\n registerError(): () => void {\n this.#hasError.set(true);\n return () => this.#hasError.set(false);\n }\n\n /**\n * Forward a click to the control's focusable element and focus it — matching\n * the activation a native `<label for>` triggers via the browser, so a\n * non-`<label>` `[forLabel]` toggles a custom-role control instead of only\n * focusing it.\n */\n clickControl(): void {\n const target = this.#targetEl();\n if (!target) {\n return;\n }\n target.click();\n target.focus();\n }\n}\n","import { computed, DestroyRef, Directive, ElementRef, inject } from '@angular/core';\n\nimport { FOR_FIELD_CONTEXT } from './field-context';\n\n/**\n * Accessible label for a form control. Inside a `[forField]` it auto-wires:\n * it adopts the field's `labelId` and registers itself so the control gains\n * `aria-labelledby`.\n *\n * Click-to-activate is consistent across both host shapes: a native `<label>`\n * emits `for` and the browser forwards the click to the control (toggling a\n * checkbox / switch, activating a button); a non-`<label>` host has no native\n * `for` forwarding, so the directive forwards the click itself — clicking the\n * label activates the control either way, not just focuses it. A click that\n * originated on the control (when the control is nested inside the label host)\n * is not re-forwarded, so a label-wrapping layout toggles once, matching native\n * `<label>` semantics.\n *\n * Usable standalone outside a field — there it is an inert\n * marker and the consumer wires native `for` themselves.\n *\n * @example\n * ```html\n * <div forField>\n * <label forLabel>Email</label>\n * <input forFieldControl type=\"email\" />\n * </div>\n * ```\n */\n@Directive({\n selector: '[forLabel]',\n exportAs: 'forLabel',\n host: {\n '[attr.id]': 'labelId()',\n '[attr.for]': 'forAttr()',\n '(click)': 'onClick($event)',\n },\n})\nexport class ForLabel {\n protected readonly ctx = inject(FOR_FIELD_CONTEXT, { optional: true });\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** The label's id when inside a field, else null. */\n protected readonly labelId = computed(() => this.ctx?.labelId() ?? null);\n\n /** `for` is only meaningful on a native `<label>`; null otherwise. */\n protected readonly forAttr = computed(() => {\n if (!this.ctx || this.#host.nativeElement.tagName !== 'LABEL') {\n return null;\n }\n return this.ctx.controlId();\n });\n\n constructor() {\n const ctx = this.ctx;\n if (ctx) {\n const unregister = ctx.registerLabel();\n inject(DestroyRef).onDestroy(unregister);\n }\n }\n\n protected onClick(event: MouseEvent): void {\n if (!this.ctx || this.#host.nativeElement.tagName === 'LABEL') {\n return;\n }\n // When the control is nested inside the label host (label wraps control),\n // a click on the control already activates it natively; forwarding again\n // would double-toggle. Mirror native `<label>` semantics and bail when the\n // click originated from the control (or anything inside it).\n const control = this.ctx.control();\n const controlEl = control?.labelledElement?.() ?? control?.host ?? null;\n const target = event.target as Node | null;\n if (controlEl && target && controlEl.contains(target)) {\n return;\n }\n this.ctx.clickControl();\n }\n}\n","import { DestroyRef, Directive, inject } from '@angular/core';\n\nimport { injectFieldContext } from './field-context';\n\n/**\n * Supplementary description / hint for a form control. Adopts the field's\n * `descriptionId` and registers itself so the control gains\n * `aria-describedby`. Apply on a `<p>` / `<span>` inside a `[forField]`.\n */\n@Directive({\n selector: '[forFieldDescription]',\n exportAs: 'forFieldDescription',\n host: {\n '[attr.id]': 'ctx.descriptionId()',\n },\n})\nexport class ForFieldDescription {\n protected readonly ctx = injectFieldContext('ForFieldDescription');\n\n constructor() {\n const unregister = this.ctx.registerDescription();\n inject(DestroyRef).onDestroy(unregister);\n }\n}\n","import { computed, DestroyRef, Directive, inject } from '@angular/core';\nimport type { ValidationError } from '@angular/forms/signals';\n\nimport { injectFieldContext } from './field-context';\n\nfunction extractMessage(error: ValidationError.WithOptionalFieldTree): string {\n if ('message' in error && typeof error.message === 'string' && error.message !== '') {\n return error.message;\n }\n return error.kind;\n}\n\n/**\n * Error region for a form control. Adopts the field's `errorId`, registers\n * itself so the control gains `aria-errormessage` (and the id is folded into\n * `aria-describedby`) while invalid, and **reads the control's Signal Forms\n * validation errors automatically** — the consumer renders `messages()` (or\n * the raw `errors()`) without any manual plumbing.\n *\n * Carries `role=\"alert\"`. Drive its presence with `@if (err.shown())` (or the\n * field's own `invalid()`); when unmounted the control's `aria-errormessage`\n * drops automatically.\n *\n * @example\n * ```html\n * @if (err.shown()) {\n * <p forFieldError #err=\"forFieldError\">{{ err.messages().join(', ') }}</p>\n * }\n * ```\n */\n@Directive({\n selector: '[forFieldError]',\n exportAs: 'forFieldError',\n host: {\n '[attr.id]': 'ctx.errorId()',\n role: 'alert',\n },\n})\nexport class ForFieldError {\n protected readonly ctx = injectFieldContext('ForFieldError');\n\n /** The control's current validation errors (Signal Forms). */\n readonly errors = computed(() => this.ctx.control()?.errors?.() ?? []);\n\n /** Human-readable messages derived from {@link errors}. */\n readonly messages = computed<readonly string[]>(() =>\n this.errors().map((error) => extractMessage(error)),\n );\n\n /** Whether the control currently has at least one error. */\n readonly hasErrors = computed(() => this.errors().length > 0);\n\n /** Whether the error region should be shown (control invalid and has errors). */\n readonly shown = computed(() => this.ctx.invalid() && this.hasErrors());\n\n constructor() {\n const unregister = this.ctx.registerError();\n inject(DestroyRef).onDestroy(unregister);\n }\n}\n","import { booleanAttribute, Directive, input } from '@angular/core';\n\nimport { injectFieldWiring } from './field-context';\n\n/**\n * Opt-in marker that wires a non-forty-cdk control — a native `<input>`,\n * `<textarea>`, `<select>`, or any custom element — into its surrounding\n * `[forField]`. forty-cdk form primitives (which extend the shared form base)\n * auto-wire and do **not** need this; it exists for plain native controls that\n * have no primitive of their own.\n *\n * Validation state is consumer-driven via the `invalid` / `required` /\n * `disabled` / `touched` inputs (native controls have no Signal Forms errors\n * to surface). Reflects `aria-invalid` on the host.\n *\n * @example\n * ```html\n * <div forField>\n * <label forLabel>Email</label>\n * <input forFieldControl type=\"email\" [invalid]=\"emailInvalid()\" />\n * </div>\n * ```\n */\n@Directive({\n selector: '[forFieldControl]',\n exportAs: 'forFieldControl',\n host: {\n '[attr.aria-invalid]': 'invalid() ? \"true\" : null',\n },\n})\nexport class ForFieldControl {\n /** Marks the control invalid — drives the field's error region and `aria-invalid`. */\n readonly invalid = input(false, { transform: booleanAttribute });\n /** Marks the control required — reflected by the field as `data-required`. */\n readonly required = input(false, { transform: booleanAttribute });\n /** Marks the control disabled — reflected by the field as `data-disabled`. */\n readonly disabled = input(false, { transform: booleanAttribute });\n /** Marks the control touched — reflected by the field as `data-touched`. */\n readonly touched = input(false, { transform: booleanAttribute });\n\n constructor() {\n injectFieldWiring({\n invalid: this.invalid,\n required: this.required,\n disabled: this.disabled,\n touched: this.touched,\n });\n }\n}\n","import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * Defaults inherited by descendant fields in the surrounding injector scope.\n * Configure with `provideForFieldDefaults`. The shape is a stub today —\n * present so future per-scope tuning can land without churning the public\n * surface.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ForFieldDefaults {}\n\nconst FALLBACK: ForFieldDefaults = {};\n\nconst { token, provideDefaults } = createDefaults<ForFieldDefaults>('FOR_FIELD_DEFAULTS', FALLBACK);\n\n/** Token holding the resolved field defaults for the current scope. */\nexport const FOR_FIELD_DEFAULTS = token;\n\n/**\n * Configures forty-cdk field defaults for this injector scope. Partial\n * overrides inherit unspecified keys from the parent scope (or library\n * defaults at the root).\n */\nexport function provideForFieldDefaults(defaults: Partial<ForFieldDefaults> = {}): Provider[] {\n return provideDefaults(defaults);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,164 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ElementRef, signal, input, booleanAttribute, computed, Directive, DestroyRef } from '@angular/core';
3
+ import { IdGenerator, FOR_FIELDSET_CONTEXT, reflectDisabled, createDefaults } from 'forty-cdk/core';
4
+ export { FOR_FIELDSET_CONTEXT } from 'forty-cdk/core';
5
+
6
+ /**
7
+ * Headless grouping container that gives a set of related fields a shared
8
+ * accessible name — the styleless counterpart to a native `<fieldset>` +
9
+ * `<legend>`, and the grouping companion to `ForField`.
10
+ *
11
+ * On a native `<fieldset>` it relies on the implicit grouping and emits no
12
+ * role; on any other element it emits `role="group"` + `aria-labelledby`
13
+ * pointing at the `[forFieldsetLegend]` (same host-tag detection idiom as
14
+ * `ForLabel`'s `<label>` check).
15
+ *
16
+ * The `disabled` input reflects `data-disabled` and is provided via context so
17
+ * descendant `ForField` controls OR it into their own disabled state — native
18
+ * `<fieldset disabled>` does not reach custom-role controls like `forSwitch`.
19
+ *
20
+ * @example
21
+ * ```html
22
+ * <fieldset forFieldset [disabled]="locked()">
23
+ * <legend forFieldsetLegend>Shipping address</legend>
24
+ * <div forField>
25
+ * <label forLabel>Street</label>
26
+ * <input forFieldControl />
27
+ * </div>
28
+ * </fieldset>
29
+ * ```
30
+ */
31
+ class ForFieldset {
32
+ #idGen = inject(IdGenerator);
33
+ #host = inject(ElementRef);
34
+ #parent = inject(FOR_FIELDSET_CONTEXT, { optional: true, skipSelf: true });
35
+ #legends = signal(0, /* @ts-ignore */
36
+ ...(ngDevMode ? [{ debugName: "#legends" }] : /* istanbul ignore next */ []));
37
+ /**
38
+ * Whether the group is disabled, as bound by the consumer via `[disabled]`.
39
+ * The host reflection and descendant controls read the composed
40
+ * {@link disabled} signal below, which also folds in an enclosing disabled
41
+ * `[forFieldset]`.
42
+ */
43
+ disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), transform: booleanAttribute, alias: 'disabled' });
44
+ /**
45
+ * Effective disabled: the group's own `[disabled]` OR'd with an enclosing
46
+ * disabled `[forFieldset]`. Reflects `data-disabled`, emits the native
47
+ * `disabled` attribute on a `<fieldset>` (or `aria-disabled` elsewhere), and
48
+ * propagates to descendant controls via context — so a disabled fieldset
49
+ * reaches custom-role controls (`forSwitch`, `forCheckbox`, …) a native
50
+ * `<fieldset disabled>` can't, and nesting composes like native fieldsets
51
+ * (a disabled outer group cannot be re-enabled by an inner one).
52
+ */
53
+ disabled = computed(() => this.disabledInput() || (this.#parent?.disabled() ?? false), /* @ts-ignore */
54
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
55
+ /** Id of the legend element; the group's `aria-labelledby` resolves here. */
56
+ legendId = signal(this.#idGen.next('for-fieldset-legend'), /* @ts-ignore */
57
+ ...(ngDevMode ? [{ debugName: "legendId" }] : /* istanbul ignore next */ []));
58
+ /** Whether the host element is a native `<fieldset>` (no role/labelledby needed). */
59
+ isNativeFieldset = computed(() => this.#host.nativeElement.tagName === 'FIELDSET', /* @ts-ignore */
60
+ ...(ngDevMode ? [{ debugName: "isNativeFieldset" }] : /* istanbul ignore next */ []));
61
+ /**
62
+ * Native `disabled` is reflected only on a real `<fieldset>` (where it
63
+ * natively disables descendant controls); a non-`<fieldset>` host advertises
64
+ * its disabled state via `aria-disabled` instead.
65
+ */
66
+ nativeDisabled = computed(() => this.isNativeFieldset() && this.disabled(), /* @ts-ignore */
67
+ ...(ngDevMode ? [{ debugName: "nativeDisabled" }] : /* istanbul ignore next */ []));
68
+ constructor() {
69
+ reflectDisabled(this.nativeDisabled);
70
+ }
71
+ /** `role="group"` on a non-`<fieldset>` element, else null. */
72
+ roleAttr = computed(() => (this.isNativeFieldset() ? null : 'group'), /* @ts-ignore */
73
+ ...(ngDevMode ? [{ debugName: "roleAttr" }] : /* istanbul ignore next */ []));
74
+ /**
75
+ * Resolved `aria-labelledby`: the legend id on a non-`<fieldset>` element when
76
+ * a legend is registered, else null (a native `<fieldset>`/`<legend>` groups
77
+ * implicitly and needs none).
78
+ */
79
+ labelledBy = computed(() => !this.isNativeFieldset() && this.#legends() > 0 ? this.legendId() : null, /* @ts-ignore */
80
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
81
+ /** Mark a legend present; returns an unregister callback. */
82
+ registerLegend() {
83
+ this.#legends.update((n) => n + 1);
84
+ return () => this.#legends.update((n) => n - 1);
85
+ }
86
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldset, deps: [], target: i0.ɵɵFactoryTarget.Directive });
87
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForFieldset, isStandalone: true, selector: "[forFieldset]", inputs: { disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "roleAttr()", "attr.aria-labelledby": "labelledBy()", "attr.aria-disabled": "!isNativeFieldset() && disabled() ? \"true\" : null", "attr.data-disabled": "disabled() ? \"\" : null" } }, providers: [{ provide: FOR_FIELDSET_CONTEXT, useExisting: ForFieldset }], exportAs: ["forFieldset"], ngImport: i0 });
88
+ }
89
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldset, decorators: [{
90
+ type: Directive,
91
+ args: [{
92
+ selector: '[forFieldset]',
93
+ exportAs: 'forFieldset',
94
+ host: {
95
+ '[attr.role]': 'roleAttr()',
96
+ '[attr.aria-labelledby]': 'labelledBy()',
97
+ '[attr.aria-disabled]': '!isNativeFieldset() && disabled() ? "true" : null',
98
+ '[attr.data-disabled]': 'disabled() ? "" : null',
99
+ },
100
+ providers: [{ provide: FOR_FIELDSET_CONTEXT, useExisting: ForFieldset }],
101
+ }]
102
+ }], ctorParameters: () => [], propDecorators: { disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
103
+
104
+ /**
105
+ * Accessible group label for a `[forFieldset]`. Inside a fieldset it adopts the
106
+ * fieldset's `legendId` and registers itself so the group's `aria-labelledby`
107
+ * resolves; on a native `<fieldset>`/`<legend>` the id is harmless extra wiring
108
+ * (the browser groups implicitly).
109
+ *
110
+ * Usable standalone outside a fieldset — there it is an inert marker.
111
+ *
112
+ * @example
113
+ * ```html
114
+ * <div forFieldset>
115
+ * <span forFieldsetLegend>Notification preferences</span>
116
+ * <!-- … fields … -->
117
+ * </div>
118
+ * ```
119
+ */
120
+ class ForFieldsetLegend {
121
+ ctx = inject(FOR_FIELDSET_CONTEXT, { optional: true });
122
+ /** The legend's id when inside a fieldset, else null. */
123
+ legendId = computed(() => this.ctx?.legendId() ?? null, /* @ts-ignore */
124
+ ...(ngDevMode ? [{ debugName: "legendId" }] : /* istanbul ignore next */ []));
125
+ constructor() {
126
+ const ctx = this.ctx;
127
+ if (ctx) {
128
+ const unregister = ctx.registerLegend();
129
+ inject(DestroyRef).onDestroy(unregister);
130
+ }
131
+ }
132
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldsetLegend, deps: [], target: i0.ɵɵFactoryTarget.Directive });
133
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForFieldsetLegend, isStandalone: true, selector: "[forFieldsetLegend]", host: { properties: { "attr.id": "legendId()" } }, exportAs: ["forFieldsetLegend"], ngImport: i0 });
134
+ }
135
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldsetLegend, decorators: [{
136
+ type: Directive,
137
+ args: [{
138
+ selector: '[forFieldsetLegend]',
139
+ exportAs: 'forFieldsetLegend',
140
+ host: {
141
+ '[attr.id]': 'legendId()',
142
+ },
143
+ }]
144
+ }], ctorParameters: () => [] });
145
+
146
+ const FALLBACK = {};
147
+ const { token, provideDefaults } = createDefaults('FOR_FIELDSET_DEFAULTS', FALLBACK);
148
+ /** Token holding the resolved fieldset defaults for the current scope. */
149
+ const FOR_FIELDSET_DEFAULTS = token;
150
+ /**
151
+ * Configures forty-cdk fieldset defaults for this injector scope. Partial
152
+ * overrides inherit unspecified keys from the parent scope (or library
153
+ * defaults at the root).
154
+ */
155
+ function provideForFieldsetDefaults(defaults = {}) {
156
+ return provideDefaults(defaults);
157
+ }
158
+
159
+ /**
160
+ * Generated bundle index. Do not edit.
161
+ */
162
+
163
+ export { FOR_FIELDSET_DEFAULTS, ForFieldset, ForFieldsetLegend, provideForFieldsetDefaults };
164
+ //# sourceMappingURL=forty-cdk-fieldset.mjs.map