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,358 @@
1
+ # ForCarousel
2
+
3
+ Headless, styleless carousel primitive implementing the
4
+ [WAI-ARIA APG Carousel pattern](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/).
5
+ It ships slide tracking, keyboard navigation, focus management, and ARIA; you
6
+ supply the markup and CSS.
7
+
8
+ ## Usage
9
+
10
+ ```html
11
+ <div
12
+ forCarousel
13
+ [(activeIndex)]="index"
14
+ loop
15
+ orientation="horizontal"
16
+ ariaLabel="Featured products"
17
+ >
18
+ <button forCarouselPrevious aria-label="Previous slide">‹</button>
19
+
20
+ <div forCarouselViewport>
21
+ <div forCarouselTrack>
22
+ @for (product of products(); track product.id) {
23
+ <div forCarouselSlide>{{ product.name }}</div>
24
+ }
25
+ </div>
26
+ </div>
27
+
28
+ <button forCarouselNext aria-label="Next slide">›</button>
29
+
30
+ <div forCarouselIndicators ariaLabel="Choose slide to display">
31
+ @for (product of products(); track product.id; let i = $index) {
32
+ <button forCarouselIndicator [attr.aria-label]="'Go to slide ' + (i + 1)"></button>
33
+ }
34
+ </div>
35
+ </div>
36
+ ```
37
+
38
+ ### Indicators map 1:1 to slides
39
+
40
+ The picker assumes **one `[forCarouselIndicator]` per `[forCarouselSlide]`**: the
41
+ indicator at DOM index `i` targets slide `i`. Iterate the same collection that
42
+ drives the slides (as above) so the counts always match. A mismatched count
43
+ desynchronizes the active-indicator state and is dev-guarded by a `console.warn`
44
+ in development builds. Grouped or summarized indicators (fewer dots than slides)
45
+ are not supported.
46
+
47
+ ## Example CSS
48
+
49
+ The directive publishes geometry as CSS custom properties on the root element
50
+ so they cascade to the track. The consumer applies the transform and transition.
51
+
52
+ ```css
53
+ [forCarouselViewport] {
54
+ overflow: hidden;
55
+ }
56
+ [forCarouselTrack] {
57
+ display: flex;
58
+ transform: translateX(var(--for-carousel-offset));
59
+ transition: transform 300ms ease;
60
+ }
61
+ [forCarousel][data-orientation='vertical'] [forCarouselTrack] {
62
+ flex-direction: column;
63
+ transform: translateY(var(--for-carousel-offset));
64
+ }
65
+ [forCarouselSlide] {
66
+ flex: 0 0 calc(100% / var(--for-carousel-slides-per-view));
67
+ }
68
+ @media (prefers-reduced-motion: reduce) {
69
+ [forCarouselTrack] {
70
+ transition: none;
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## CSS custom properties
76
+
77
+ The following properties are set on the `[forCarousel]` host and cascade to
78
+ children, unless noted otherwise:
79
+
80
+ | Property | Host | Value | Notes |
81
+ | -------------------------------- | ----------------------- | ------------- | --------------------------------------------------------------------------------------------------------------- |
82
+ | `--for-carousel-offset` | `[forCarousel]` | e.g. `-100%` | Pure arithmetic from `activeIndex`, `slidesPerView`, `align`. |
83
+ | `--for-carousel-active-index` | `[forCarousel]` | integer | Current `activeIndex`. |
84
+ | `--for-carousel-slide-count` | `[forCarousel]` | integer | Total registered slides. |
85
+ | `--for-carousel-slides-per-view` | `[forCarousel]` | integer | From the `slidesPerView` input. |
86
+ | `--for-carousel-viewport-width` | `[forCarousel]` | e.g. `640px` | Measured via `ResizeObserver`. Absent on the server and before first measurement. |
87
+ | `--for-carousel-viewport-height` | `[forCarousel]` | e.g. `400px` | Same as above, for the block axis. |
88
+ | `--for-carousel-drag` | `[forCarouselViewport]` | e.g. `-128px` | Live px offset along the primary axis during a drag; absent at rest and under `prefers-reduced-motion: reduce`. |
89
+
90
+ ## Autoplay
91
+
92
+ Add the `autoplay` attribute and a `[forCarouselRotationControl]` as the **first
93
+ focusable child** of the carousel to enable automatic slide rotation.
94
+
95
+ ```html
96
+ <div forCarousel autoplay [autoplayInterval]="4000" ariaLabel="Featured products">
97
+ <button forCarouselRotationControl>
98
+ <!-- swap icon with [data-playing] in your CSS -->
99
+ </button>
100
+ <button forCarouselPrevious aria-label="Previous slide">‹</button>
101
+ <div forCarouselViewport>…</div>
102
+ <button forCarouselNext aria-label="Next slide">›</button>
103
+ <div forCarouselIndicators ariaLabel="Choose slide to display">…</div>
104
+ </div>
105
+ ```
106
+
107
+ **Behaviour:**
108
+
109
+ - Rotation pauses on hover, on keyboard focus anywhere inside the carousel, and
110
+ while the browser tab is backgrounded. It resumes when none of those hold.
111
+ - An explicit user stop (clicking the rotation control or calling `pause()`) is
112
+ **sticky**: hover-in/out and focus-in/out will not restart it. Only an explicit
113
+ Start does.
114
+ - Under `prefers-reduced-motion: reduce`, rotation does **not** auto-start. The
115
+ user can still start it manually by clicking the rotation control (explicit
116
+ consent overrides the gate).
117
+ - While rotating, the viewport's `aria-live` is `"off"` (so advancing slides do
118
+ not bombard the screen reader). When stopped or paused it is `"polite"` so
119
+ manual navigation is announced.
120
+
121
+ **APG requirement:** if you enable `autoplay`, you **must** render a
122
+ `[forCarouselRotationControl]` and place it first in the tab order — an
123
+ auto-rotating carousel without a visible pause control fails WCAG 2.2.2 (Pause,
124
+ Stop, Hide). The directive does not enforce this, but your implementation does.
125
+
126
+ **Label inputs:** the control uses a label swap, not `aria-pressed`. Override
127
+ the defaults with `startLabel` / `stopLabel` inputs:
128
+
129
+ ```html
130
+ <button forCarouselRotationControl startLabel="Play slideshow" stopLabel="Pause slideshow"></button>
131
+ ```
132
+
133
+ **Styling hooks:**
134
+
135
+ ```css
136
+ [forCarouselRotationControl]::before {
137
+ content: '▶';
138
+ }
139
+ [forCarouselRotationControl][data-playing]::before {
140
+ content: '⏸';
141
+ }
142
+ ```
143
+
144
+ | Attribute | When present |
145
+ | --------------- | ------------------------------------------------------- |
146
+ | `data-playing` | On `[forCarouselRotationControl]` — user intent is "on" |
147
+ | `data-rotating` | On `[forCarousel]` — actively rotating right now |
148
+ | `data-autoplay` | On `[forCarousel]` — the `autoplay` input is `true` |
149
+
150
+ **Programmatic control** via `exportAs`:
151
+
152
+ ```html
153
+ <div forCarousel #car="forCarousel" autoplay>…</div>
154
+ ```
155
+
156
+ ```ts
157
+ car.play(); // start (explicit, sticky)
158
+ car.pause(); // stop (explicit, sticky)
159
+ car.toggleAutoplay(); // toggle
160
+ car.playing(); // Signal<boolean> — user intent
161
+ ```
162
+
163
+ ## Drag / swipe
164
+
165
+ Apply `forCarouselDrag` on the `[forCarouselViewport]` element to enable
166
+ pointer drag and touch swipe navigation. The directive is **opt-in and
167
+ tree-shakeable** — it adds nothing to the root `ForCarousel` for consumers who
168
+ don't use it.
169
+
170
+ ```html
171
+ <div forCarousel [(activeIndex)]="index" ariaLabel="Featured products">
172
+ <div forCarouselViewport forCarouselDrag>
173
+ <div forCarouselTrack>…</div>
174
+ </div>
175
+ <!-- prev / next / indicators as before -->
176
+ </div>
177
+ ```
178
+
179
+ ### CSS contract
180
+
181
+ The directive publishes `--for-carousel-drag` (a raw px value) on the viewport
182
+ host during the gesture. Compose it with `--for-carousel-offset` on the track
183
+ transform:
184
+
185
+ ```css
186
+ [forCarouselTrack] {
187
+ transform: translateX(calc(var(--for-carousel-offset) + var(--for-carousel-drag, 0px)));
188
+ transition: transform 300ms ease;
189
+ }
190
+ [forCarousel][data-orientation='vertical'] [forCarouselTrack] {
191
+ transform: translateY(calc(var(--for-carousel-offset) + var(--for-carousel-drag, 0px)));
192
+ }
193
+ /* Kill the settle transition while the finger is down so the track follows 1:1 */
194
+ [forCarouselViewport][data-dragging] [forCarouselTrack] {
195
+ transition: none;
196
+ }
197
+ [forCarouselViewport][data-dragging] {
198
+ user-select: none;
199
+ }
200
+ ```
201
+
202
+ ### RTL
203
+
204
+ `--for-carousel-drag` is always the **physical** finger displacement, so compose
205
+ it **without** the `-1` factor the consumer may apply to `--for-carousel-offset`
206
+ in RTL:
207
+
208
+ ```css
209
+ [dir='rtl'] [forCarouselTrack] {
210
+ transform: translateX(calc(-1 * var(--for-carousel-offset) + var(--for-carousel-drag, 0px)));
211
+ }
212
+ ```
213
+
214
+ ### Reduced motion
215
+
216
+ Under `prefers-reduced-motion: reduce` the directive does **not** publish
217
+ `--for-carousel-drag` (no live track motion). The gesture still snaps
218
+ `activeIndex` on release — only the continuous live offset is suppressed.
219
+
220
+ ### Cross-axis / touch
221
+
222
+ `touch-action` is set automatically on the viewport host — `pan-y` for
223
+ horizontal carousels (allows vertical page scroll) and `pan-x` for vertical
224
+ carousels (allows horizontal page scroll). A mostly-cross-axis swipe is never
225
+ captured, so page scrolling on the perpendicular axis is unaffected.
226
+
227
+ ### Styling hooks
228
+
229
+ | Attribute | Host | When present |
230
+ | --------------- | ----------------------- | -------------------------------------- |
231
+ | `data-dragging` | `[forCarouselViewport]` | Present while a drag gesture is armed. |
232
+
233
+ ### Inputs on `[forCarouselDrag]`
234
+
235
+ | Input | Type | Default | Description |
236
+ | ---------- | --------- | ------- | ---------------------------------------------------------------------------- |
237
+ | `disabled` | `boolean` | `false` | Disable pointer drag without removing the directive. Removes `touch-action`. |
238
+
239
+ ## Inputs
240
+
241
+ All inputs are on `[forCarousel]` unless noted.
242
+
243
+ | Input | Type | Default | Description |
244
+ | ------------------------------------------------ | ------------------------------ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
245
+ | `activeIndex` | `model<number>` | `0` | Two-way bindable current slide index. |
246
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Scroll axis. |
247
+ | `loop` | `boolean` | `false` | Wrap-around at the boundaries. |
248
+ | `align` | `'start' \| 'center' \| 'end'` | `'start'` | Alignment of the active slide. |
249
+ | `slidesPerView` | `number` | `1` | Visible slides at once. |
250
+ | `containScroll` | `boolean` | `false` | Clamp the track offset so trailing slides sit flush at the viewport edge (no overscroll) when `slidesPerView > 1` and not looping. |
251
+ | `autoplay` | `boolean` | `false` | Enable auto-rotation (suppressed by `prefers-reduced-motion: reduce`). |
252
+ | `autoplayInterval` | `number` | `5000` | Ms between automatic slide advances. `<= 0` disables the timer. |
253
+ | `ariaLabel` | `string \| null` | `null` | Accessible label for the carousel root. |
254
+ | `dir` | `'ltr' \| 'rtl' \| null` | `null` (inherits) | Writing direction. |
255
+ | `ariaLabel` (on `[forCarouselIndicators]`) | `string \| null` | `null` | Label for the picker group. |
256
+ | `ariaLabel` (on `[forCarouselSlide]`) | `string \| null` | `null` | Override the positional "N of M" label. |
257
+ | `disabled` (on `[forCarouselIndicator]`) | `boolean` | `false` | Disable this indicator. |
258
+ | `startLabel` (on `[forCarouselRotationControl]`) | `string` | `'Start automatic slide show'` | Accessible name while rotation is stopped. |
259
+ | `stopLabel` (on `[forCarouselRotationControl]`) | `string` | `'Stop automatic slide show'` | Accessible name while rotation is playing. |
260
+
261
+ ### Contain scroll
262
+
263
+ With `slidesPerView > 1`, `align="start"`, and no `loop`, advancing to the last
264
+ slide overscrolls the track: the final page may show only one real slide followed
265
+ by empty space. Adding the `containScroll` attribute to `[forCarousel]` clamps
266
+ `--for-carousel-offset` so the last visible page always sits flush at the
267
+ viewport's trailing edge:
268
+
269
+ ```html
270
+ <div forCarousel containScroll [slidesPerView]="3" ariaLabel="Featured products">…</div>
271
+ ```
272
+
273
+ Every slide still has its own indicator; trailing indicators that would map to the
274
+ same clamped view simply share the same visual position. The clamp has no effect
275
+ when `loop` is enabled (the entire range is valid when wrapping) or when
276
+ `slidesPerView` is `1` (a single-view carousel never overscrolls).
277
+
278
+ ## Localizing the default labels
279
+
280
+ Each slide's default `aria-label` is the positional `"N of M"` string, and each
281
+ indicator's is `"Go to slide N"`. Localize both centrally with
282
+ `provideForCarouselDefaults` instead of setting `ariaLabel` on every slide and
283
+ indicator:
284
+
285
+ ```ts
286
+ providers: [
287
+ provideForCarouselDefaults({
288
+ slideLabel: (position, total) => `Diapositiva ${position} de ${total}`,
289
+ indicatorLabel: (position) => `Ir a la diapositiva ${position}`,
290
+ }),
291
+ ];
292
+ ```
293
+
294
+ `position` is the 1-based slide index and `total` is the slide count. Overrides
295
+ merge with the parent scope, so you can localize just the labels and inherit the
296
+ rest of the defaults. A per-element `ariaLabel` on `[forCarouselSlide]` /
297
+ `[forCarouselIndicator]` still takes precedence over the localized default.
298
+
299
+ ## Keyboard interaction (indicator group)
300
+
301
+ Keyboard navigation lives on the indicator group. Only the current
302
+ indicator is in the tab order. Arrow keys move focus and activate the target slide
303
+ automatically.
304
+
305
+ | Key | Action |
306
+ | -------------------------- | --------------------------------------------------------------------- |
307
+ | `ArrowRight` / `ArrowLeft` | Next / previous indicator (horizontal). In RTL, direction is swapped. |
308
+ | `ArrowDown` / `ArrowUp` | Next / previous indicator (vertical orientation). |
309
+ | `Home` | First indicator and slide. |
310
+ | `End` | Last indicator and slide. |
311
+ | `Enter` / `Space` | Activate the focused indicator (via native button). |
312
+
313
+ ## Accessibility notes
314
+
315
+ - The root carries `role="group"` and `aria-roledescription="carousel"`. The `ariaLabel` input
316
+ should describe the carousel's purpose without using the word "carousel" (APG guidance).
317
+ - Each slide carries `role="group"`, `aria-roledescription="slide"`, and
318
+ `aria-label="N of M"` by default. Override per slide with the `ariaLabel` input,
319
+ or localize the default format app-wide with `provideForCarouselDefaults` (see
320
+ [Localizing the default labels](#localizing-the-default-labels)).
321
+ - Off-view slides receive `aria-hidden="true"` and `inert` to remove them from the
322
+ accessibility tree and focus order.
323
+ - The indicator group should be labelled (e.g. `ariaLabel="Choose slide to display"`).
324
+ - The current indicator is marked with `aria-current="true"`.
325
+ - Prev/next buttons use native `disabled` so they are removed from the tab order when
326
+ at the boundary without loop.
327
+ - The viewport carries `aria-live` and `aria-atomic="false"`. While the carousel is actively
328
+ auto-rotating, `aria-live` is `"off"` so advancing slides do not bombard the screen reader.
329
+ When stopped or paused, it is `"polite"` so manual navigation announces. The per-slide
330
+ `aria-roledescription`, `aria-label`, and `aria-hidden` toggle carry the screen-reader
331
+ experience for non-auto-rotating carousels.
332
+
333
+ ## Reduced-motion
334
+
335
+ The directive performs no animation itself. Add the following CSS to disable the transition
336
+ for users who prefer reduced motion:
337
+
338
+ ```css
339
+ @media (prefers-reduced-motion: reduce) {
340
+ [forCarouselTrack] {
341
+ transition: none;
342
+ }
343
+ }
344
+ ```
345
+
346
+ ## RTL support
347
+
348
+ Arrow-key direction (ArrowLeft/ArrowRight) is automatically swapped in RTL — handled by
349
+ `resolveListNavigation` and the reflected `dir` attribute. The **visual** track direction
350
+ in RTL is the consumer's CSS concern. For example, to flip the translate sign in RTL:
351
+
352
+ ```css
353
+ [dir='rtl'] [forCarouselTrack] {
354
+ transform: translateX(calc(-1 * var(--for-carousel-offset)));
355
+ }
356
+ ```
357
+
358
+ The example CSS above is LTR-only by default.
@@ -0,0 +1,146 @@
1
+ # Checkbox
2
+
3
+ Headless implementation of the [WAI-ARIA Checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/) with optional tri-state (`indeterminate`) support. Implements `FormCheckboxControl` from `@angular/forms/signals` for `[formField]` auto-wiring.
4
+
5
+ ## When to choose Checkbox vs Switch
6
+
7
+ - **Checkbox**: deferred selection (user is choosing options for a form to apply later). Supports the tri-state `mixed` value, useful for "select all" parents.
8
+ - **Switch**: immediate setting (flipping it changes the world right now). Always binary.
9
+
10
+ Use the one that matches your semantics. `ForCheckbox` and `ForSwitch` are intentionally separate even though they share most of their state surface.
11
+
12
+ ## Pieces
13
+
14
+ | Class | Selector | Role |
15
+ | ------------- | --------------- | -------------------------------------------------------------------- |
16
+ | `ForCheckbox` | `[forCheckbox]` | Single directive on a `<button>`. Wires ARIA + click + Signal Forms. |
17
+
18
+ ## Inputs / models
19
+
20
+ | API | Type | Description |
21
+ | ------------------------------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
22
+ | `checked` | `model<boolean>` | Two-way bindable on/off. Required by `FormCheckboxControl`. |
23
+ | `indeterminate` | `model<boolean>` | Two-way bindable. When true, `aria-checked="mixed"` regardless of `checked`. Click clears it. UI-only — not part of the form value. |
24
+ | `disabled` / `readonly` / `required` / `invalid` / `pending` | `input<boolean>` | Reflected as the matching `aria-*` / `data-*` attributes. A disabled checkbox stays focusable (per APG) — `aria-disabled="true"` + `data-disabled`, no native `disabled`; click is a no-op. |
25
+ | `name` | `input<string>` | Reflected on `name` (empty string omits the attribute). |
26
+ | `errors` | `input<readonly ValidationError.WithOptionalFieldTree[]>` | Validation errors fed by `[formField]`. |
27
+ | `touched` | `model<boolean>` | Set to `true` on blur. |
28
+
29
+ The host gets `data-state="checked" \| "unchecked" \| "indeterminate"` for CSS hooks.
30
+
31
+ ## Stand-alone usage
32
+
33
+ ```ts
34
+ import { Component, signal } from '@angular/core';
35
+ import { ForCheckbox } from 'forty-cdk/checkbox';
36
+
37
+ @Component({
38
+ selector: 'demo-terms',
39
+ imports: [ForCheckbox],
40
+ template: `
41
+ <label>
42
+ <button forCheckbox class="checkbox" [(checked)]="agreed">
43
+ <span class="indicator"></span>
44
+ </button>
45
+ I agree to the terms
46
+ </label>
47
+ `,
48
+ })
49
+ export class DemoTerms {
50
+ readonly agreed = signal(false);
51
+ }
52
+ ```
53
+
54
+ ## Tri-state ("select all") usage
55
+
56
+ ```ts
57
+ import { Component, computed, signal } from '@angular/core';
58
+ import { ForCheckbox } from 'forty-cdk/checkbox';
59
+
60
+ @Component({
61
+ selector: 'demo-select-all',
62
+ imports: [ForCheckbox],
63
+ template: `
64
+ <button
65
+ forCheckbox
66
+ class="checkbox"
67
+ [checked]="allChecked()"
68
+ [indeterminate]="someChecked()"
69
+ (click)="toggleAll()"
70
+ ></button>
71
+ @for (item of items(); track item.id) {
72
+ <button forCheckbox class="checkbox" [(checked)]="item.selected"></button>
73
+ }
74
+ `,
75
+ })
76
+ export class DemoSelectAll {
77
+ readonly items = signal([
78
+ { id: 1, selected: false },
79
+ { id: 2, selected: true },
80
+ { id: 3, selected: false },
81
+ ]);
82
+
83
+ readonly allChecked = computed(() => this.items().every((i) => i.selected));
84
+ readonly someChecked = computed(() => {
85
+ const some = this.items().some((i) => i.selected);
86
+ return some && !this.allChecked();
87
+ });
88
+
89
+ toggleAll(): void {
90
+ const next = !this.allChecked();
91
+ this.items.update((list) => list.map((i) => ({ ...i, selected: next })));
92
+ }
93
+ }
94
+ ```
95
+
96
+ ## Signal Forms usage
97
+
98
+ ```ts
99
+ import { Component, signal } from '@angular/core';
100
+ import { form, required } from '@angular/forms/signals';
101
+ import { ForCheckbox } from 'forty-cdk/checkbox';
102
+
103
+ @Component({
104
+ selector: 'demo-checkout',
105
+ imports: [ForCheckbox /* , FormField from @angular/forms */],
106
+ template: ` <button forCheckbox class="checkbox" [formField]="checkout.acceptTerms"></button> `,
107
+ })
108
+ export class DemoCheckout {
109
+ readonly model = signal({ acceptTerms: false });
110
+ readonly checkout = form(this.model, (s) => required(s.acceptTerms));
111
+ }
112
+ ```
113
+
114
+ ## Styling
115
+
116
+ forty-cdk ships no styles. Add your own class to each piece — the for* selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../../../docs/styling.md)). Key your CSS off the reflected data-* attributes below.
117
+
118
+ ### Data attributes
119
+
120
+ | Piece | Attribute | Values |
121
+ | ------------------------ | --------------- | ------------------------------------------- |
122
+ | `[forCheckbox]` | `data-state` | `checked` \| `unchecked` \| `indeterminate` |
123
+ | `[forCheckbox]` | `data-disabled` | present \| absent |
124
+ | `[forCheckbox]` | `data-readonly` | present \| absent |
125
+ | `[forCheckboxIndicator]` | `data-state` | `checked` \| `unchecked` \| `indeterminate` |
126
+
127
+ ```css
128
+ .checkbox-indicator[data-state='unchecked'] {
129
+ display: none;
130
+ }
131
+
132
+ .checkbox[data-state='indeterminate'] .dash {
133
+ display: block;
134
+ }
135
+ ```
136
+
137
+ ## Accessibility notes
138
+
139
+ - **Provide an accessible name.** Wrap the button in a `<label>`, or set `aria-labelledby` / `aria-label`. Without one, the control is announced as just "checkbox" with no purpose.
140
+ - **`role="checkbox"`** with `aria-checked="mixed"` is the canonical tri-state contract. Some legacy screen readers handle "mixed" differently — test with your target SRs.
141
+ - **Keyboard**: APG only mandates Space. The directive sits on a `<button>` so Enter also activates — this is a documented superset, not a violation.
142
+ - **Activation of an indeterminate checkbox** clears `indeterminate` and toggles `checked` (matches native `<input type="checkbox">`).
143
+
144
+ ## Wrapping in a design system
145
+
146
+ Both supported wrapper patterns — `hostDirectives` with the exported `FOR_CHECKBOX_HOST_DIRECTIVE_INPUTS` / `FOR_CHECKBOX_HOST_DIRECTIVE_OUTPUTS` name tuples, and subclassing — are documented in [Wrapping form primitives](../../../../../docs/wrapping-form-primitives.md).