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,152 @@
1
+ # Slider
2
+
3
+ Headless slider implementing the [WAI-ARIA Slider pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider/) (single thumb) and the [WAI-ARIA Slider (Multi-Thumb) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multi-thumb/) (range / N thumbs). Implements `FormValueControl<readonly number[]>` from `@angular/forms/signals`.
4
+
5
+ A single primitive supports single, range, and multi-thumb sliders — the shape comes from `value`'s array length and how many `[forSliderThumb]` you render.
6
+
7
+ ## Pieces
8
+
9
+ | Class | Selector | Role |
10
+ | ---------------- | ------------------ | ---------------------------------------------------------------------------------------------- |
11
+ | `ForSlider` | `[forSlider]` | Root. Owns `[(value)]`, configuration, drag, form wiring. |
12
+ | `ForSliderTrack` | `[forSliderTrack]` | Track surface. PointerDown moves the nearest thumb and starts a drag. |
13
+ | `ForSliderRange` | `[forSliderRange]` | Optional decorative band between min and the highest thumb (single) or between thumbs (multi). |
14
+ | `ForSliderThumb` | `[forSliderThumb]` | One thumb. `role="slider"`, full ARIA, keyboard, and drag. |
15
+
16
+ ## Single thumb
17
+
18
+ ```html
19
+ <div forSlider [(value)]="volume">
20
+ <span forSliderTrack>
21
+ <span forSliderRange></span>
22
+ <span forSliderThumb [index]="0" [label]="'Volume'"></span>
23
+ </span>
24
+ </div>
25
+ ```
26
+
27
+ Where `volume = signal<readonly number[]>([50])`.
28
+
29
+ ## Range (two thumbs)
30
+
31
+ ```html
32
+ <div forSlider [(value)]="priceRange" [min]="0" [max]="1000" [step]="10">
33
+ <span forSliderTrack>
34
+ <span forSliderRange></span>
35
+ <span forSliderThumb [index]="0" [label]="'Minimum price'"></span>
36
+ <span forSliderThumb [index]="1" [label]="'Maximum price'"></span>
37
+ </span>
38
+ </div>
39
+ ```
40
+
41
+ `priceRange = signal<readonly number[]>([200, 800])` — non-passing constraint is enforced automatically (the lower thumb can't go above the upper, and vice versa). Use `[minStepsBetweenThumbs]="1"` to force a minimum gap.
42
+
43
+ ## Inputs
44
+
45
+ | Input | Default | Description |
46
+ | ----------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
47
+ | `min` | `0` | Numeric minimum. |
48
+ | `max` | `100` | Numeric maximum. |
49
+ | `step` | `1` | Discrete value increment for arrows + drag. |
50
+ | `largeStep` | `10` | Increment for `PageUp` / `PageDown`. |
51
+ | `orientation` | `'horizontal'` | `'horizontal'` or `'vertical'`. |
52
+ | `dir` | `'ltr'` | `'ltr'` or `'rtl'`. RTL flips horizontal pointer mapping and `ArrowLeft`/`ArrowRight` semantics. |
53
+ | `inverted` | `false` | Visually flips the value-to-position mapping (e.g. max on the left in horizontal LTR). Keyboard `Up`/`Right` (LTR) still moves toward `max` regardless. |
54
+ | `minStepsBetweenThumbs` | `0` | Multi-thumb only: minimum gap between adjacent thumbs in step units. |
55
+ | `disabled` | `false` | Disables all interaction. |
56
+ | `readonly` | `false` | Allows focus + announcement, blocks updates. |
57
+ | `name` | `''` | If non-empty, mirrors `value()` into N `<input type="hidden">` siblings for native form submit. |
58
+
59
+ ## Outputs
60
+
61
+ `(valueChange)` (from `model<readonly number[]>`) fires only on internal updates (drag, keyboard, track click). It stays silent on consumer writes via `[(value)]`.
62
+
63
+ `(valueCommit)` fires once at the trailing edge of a value-changing interaction with the final value array — on `pointerup` / `pointercancel` after a drag, or on `keyup` after one or more keyboard adjustments. Use it to defer expensive work (network calls, history undo entries) without throttling `(valueChange)`. Stays silent when the interaction did not actually change the value (e.g. press + release without movement, or arrow at the extreme).
64
+
65
+ `(touchedChange)` fires when focus leaves the slider region the first time.
66
+
67
+ ## Keyboard
68
+
69
+ Focus a thumb, then:
70
+
71
+ | Key | Action |
72
+ | -------------------------------------------------------------- | ------------------------ |
73
+ | **ArrowRight** _(LTR)_ / **ArrowLeft** _(RTL)_ / **ArrowUp** | Increase by `step`. |
74
+ | **ArrowLeft** _(LTR)_ / **ArrowRight** _(RTL)_ / **ArrowDown** | Decrease by `step`. |
75
+ | **PageUp** | Increase by `largeStep`. |
76
+ | **PageDown** | Decrease by `largeStep`. |
77
+ | **Home** | Set to `min`. |
78
+ | **End** | Set to `max`. |
79
+
80
+ `inverted` swaps "increase" / "decrease" on every key. Disabled and readonly thumbs are no-ops.
81
+
82
+ ## Form integration
83
+
84
+ `[forSlider]` implements `FormValueControl<readonly number[]>`. Pair with `[formField]` for auto-wiring with `@angular/forms/signals`:
85
+
86
+ ```html
87
+ <div forSlider [formField]="form.opacity">…</div>
88
+ ```
89
+
90
+ For native `<form>` submit, set `[name]` and the directive mirrors `value()` into N `<input type="hidden">` siblings (one per thumb). `data-touched` / `data-dirty` / `data-pending` / `data-invalid` are reflected on the host as boolean `data-*` attributes (present when `true`, absent otherwise).
91
+
92
+ ## Styling
93
+
94
+ 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.
95
+
96
+ ### Data attributes
97
+
98
+ | Piece | Attribute | Values |
99
+ | ------------------ | ------------------ | -------------------------- |
100
+ | `[forSlider]` | `data-orientation` | `horizontal` \| `vertical` |
101
+ | `[forSlider]` | `data-disabled` | present \| absent |
102
+ | `[forSlider]` | `data-readonly` | present \| absent |
103
+ | `[forSlider]` | `data-touched` | present \| absent |
104
+ | `[forSlider]` | `data-dirty` | present \| absent |
105
+ | `[forSlider]` | `data-pending` | present \| absent |
106
+ | `[forSlider]` | `data-invalid` | present \| absent |
107
+ | `[forSliderTrack]` | `data-orientation` | `horizontal` \| `vertical` |
108
+ | `[forSliderTrack]` | `data-disabled` | present \| absent |
109
+ | `[forSliderRange]` | `data-orientation` | `horizontal` \| `vertical` |
110
+ | `[forSliderRange]` | `data-disabled` | present \| absent |
111
+ | `[forSliderThumb]` | `data-orientation` | `horizontal` \| `vertical` |
112
+ | `[forSliderThumb]` | `data-disabled` | present \| absent |
113
+ | `[forSliderThumb]` | `data-readonly` | present \| absent |
114
+ | `[forSliderThumb]` | `data-index` | 0-based thumb index |
115
+
116
+ ### CSS custom properties
117
+
118
+ | Property | Meaning |
119
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ |
120
+ | `--for-slider-thumb-position` | On `[forSliderThumb]`. Fraction `[0, 1]`, already accounting for `inverted`. The thumb's position along the track. |
121
+ | `--for-slider-range-start` | On `[forSliderRange]`. Lowest fraction `[0, 1]` (single: pinned to the closer edge; multi: smallest thumb). |
122
+ | `--for-slider-range-end` | On `[forSliderRange]`. Highest fraction `[0, 1]`. |
123
+ | `--for-slider-range-size` | On `[forSliderRange]`. `end - start`. Useful for `width` / `height`. |
124
+
125
+ Pair with `data-orientation` on every piece to pick the right axis from CSS.
126
+
127
+ ```css
128
+ .thumb {
129
+ inset-inline-start: calc(var(--for-slider-thumb-position) * 100%);
130
+ }
131
+
132
+ .range {
133
+ inset-inline-start: calc(var(--for-slider-range-start) * 100%);
134
+ inline-size: calc(var(--for-slider-range-size) * 100%);
135
+ }
136
+
137
+ .thumb[data-disabled] {
138
+ opacity: 0.5;
139
+ }
140
+ ```
141
+
142
+ ## Accessibility notes
143
+
144
+ - `role="slider"` on each thumb with `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, optional `aria-valuetext`, and `aria-orientation`.
145
+ - Multi-thumb non-passing: each thumb's `aria-valuemin` / `aria-valuemax` automatically squeeze to its neighbors' values, matching the APG multi-thumb guidance.
146
+ - The root has `role="group"` and `dir="rtl"` mirrored when `dir()==='rtl'`, so screen readers and CSS layout agree.
147
+ - `disabled` thumbs receive `tabindex="-1"` and `aria-disabled="true"`.
148
+ - Provide `[label]` (or `[labelledby]`) on every thumb — even single-thumb sliders benefit from explicit naming. The directive does not synthesize a label.
149
+
150
+ ## Wrapping in a design system
151
+
152
+ Both supported wrapper patterns — `hostDirectives` with the exported `FOR_SLIDER_HOST_DIRECTIVE_INPUTS` / `FOR_SLIDER_HOST_DIRECTIVE_OUTPUTS` name tuples, and subclassing — are documented in [Wrapping form primitives](../../../../../docs/wrapping-form-primitives.md).
@@ -0,0 +1,292 @@
1
+ # ForStepper
2
+
3
+ A headless, accessible multi-step wizard primitive. Implements the
4
+ [WAI-ARIA Tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/) in
5
+ `mode="interactive"` (full roving tabindex, `role="tablist"`) and a progress
6
+ list with `aria-current="step"` in `mode="progress"`.
7
+
8
+ See [Styling forty-cdk](../../../../../docs/styling.md) for theming guidance.
9
+
10
+ ---
11
+
12
+ ## Pieces
13
+
14
+ | Directive | Selector | Role (interactive / progress) |
15
+ | ---------------------------- | ------------------------------ | --------------------------------------- |
16
+ | `ForStepper` | `[forStepper]` | root |
17
+ | `ForStepperList` | `[forStepperList]` | `tablist` / `list` |
18
+ | `ForStepperItem` | `[forStepperItem]` | item container (wraps trigger + panel) |
19
+ | `ForStepperTrigger` | `[forStepperTrigger]` | `tab` / static (aria-current) |
20
+ | `ForStepperIndicator` | `[forStepperIndicator]` | decorative icon (`aria-hidden`) |
21
+ | `ForStepperSeparator` | `[forStepperSeparator]` | decorative connector (`aria-hidden`) |
22
+ | `ForStepperContent` | `[forStepperContent]` | `tabpanel` / `group` |
23
+ | `ForStepperNext` | `button[forStepperNext]` | next-step button |
24
+ | `ForStepperPrevious` | `button[forStepperPrevious]` | previous-step button |
25
+ | `ForStepperProgress` | `[forStepperProgress]` | `progressbar` (optional) |
26
+ | `ForStepperCompletedContent` | `[forStepperCompletedContent]` | `group` (terminal "all complete" panel) |
27
+
28
+ ---
29
+
30
+ ## `ForStepper` inputs / model
31
+
32
+ | Input / Model | Type | Default | Description |
33
+ | ---------------- | --------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------- |
34
+ | `selectedIndex` | `model<number>` | `0` | Two-way bindable selected step index, range `0 … count` (the terminal `=== count` is the completed state). |
35
+ | `linear` | `input<boolean>` | `false` | Gate forward navigation until preceding steps complete. |
36
+ | `mode` | `input<StepperMode>` | `'interactive'` | Accessibility model. |
37
+ | `orientation` | `input<'horizontal'\|'vertical'>` | `'horizontal'` | Layout axis; affects arrow-key semantics. |
38
+ | `activationMode` | `input<StepperActivationMode>` | `'manual'` | Whether arrow nav also selects (scope-injectable). |
39
+ | `loop` | `input<boolean>` | `true` | Wrap arrow navigation (scope-injectable). |
40
+ | `disabled` | `input<boolean>` | `false` | Disables all triggers and navigation. |
41
+ | `dir` | `input<'ltr'\|'rtl'\|null>` | `null` | Writing direction (inherits ambient when unset). |
42
+
43
+ ## `ForStepperItem` inputs
44
+
45
+ | Input | Type | Default | Description |
46
+ | ----------- | --------------------------------- | ------- | ---------------------------------------------------------------------------- |
47
+ | `completed` | `input<boolean>` | `false` | Marks the step done (manual; wins over `field`). |
48
+ | `optional` | `input<boolean>` | `false` | Marks the step skippable in linear mode. |
49
+ | `disabled` | `input<boolean>` | `false` | Disables only this step. |
50
+ | `hasError` | `input<boolean>` | `false` | Emits `'error'` resolved state when not current (manual; wins over `field`). |
51
+ | `field` | `input<FieldTree<unknown>\|null>` | `null` | Optional Signal Forms field; drives `completed`/`hasError` from validity. |
52
+ | `state` | `input<string\|null>` | `null` | Custom state override — wins over derived state. |
53
+
54
+ ## Outputs / completed state
55
+
56
+ `ForStepper` exposes two members for the terminal completed state:
57
+
58
+ - **`isCompleted`** (`Signal<boolean>`) — true when `selectedIndex()` has reached `count()` (one past the last step). Read it via a `#stepper="forStepper"` template reference.
59
+ - **`(complete)`** — output that fires once each time the stepper enters the completed state. Retreating via `[forStepperPrevious]` and re-entering emits again.
60
+
61
+ ---
62
+
63
+ ## Completed-all content
64
+
65
+ ```html
66
+ <div forStepper [(selectedIndex)]="step" (complete)="onDone()">
67
+ <!-- … list / content … -->
68
+ @if (step() >= steps.length) {
69
+ <section forStepperCompletedContent>All steps complete 🎉</section>
70
+ }
71
+ </div>
72
+ ```
73
+
74
+ When `Next` is pressed on the last step, `selectedIndex` advances to `count` (one past the last step) and `(complete)` fires once. `[forStepperPrevious]` returns to the last step. While completed, every `[forStepperContent]` panel is inactive and only `[forStepperCompletedContent]` carries `data-state="active"` (the others reflect `inert` + `aria-hidden`).
75
+
76
+ ---
77
+
78
+ ## Interactive mode with linear progression
79
+
80
+ ```html
81
+ <div forStepper [(selectedIndex)]="step" [linear]="true">
82
+ <ol forStepperList ariaLabel="Checkout">
83
+ <li forStepperItem [completed]="step > 0">
84
+ <button forStepperTrigger>
85
+ <span forStepperIndicator></span>
86
+ Shipping
87
+ </button>
88
+ <span forStepperSeparator></span>
89
+ </li>
90
+ <li forStepperItem [completed]="step > 1">
91
+ <button forStepperTrigger>
92
+ <span forStepperIndicator></span>
93
+ Payment
94
+ </button>
95
+ <span forStepperSeparator></span>
96
+ </li>
97
+ <li forStepperItem>
98
+ <button forStepperTrigger>
99
+ <span forStepperIndicator></span>
100
+ Review
101
+ </button>
102
+ </li>
103
+ </ol>
104
+
105
+ <section forStepperContent>Shipping form</section>
106
+ <section forStepperContent>Payment form</section>
107
+ <section forStepperContent>Order review</section>
108
+
109
+ <button forStepperPrevious>Back</button>
110
+ <button forStepperNext>Next</button>
111
+ </div>
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Signal Forms field-driven completion
117
+
118
+ Bind a step to a [Signal Forms](https://angular.dev/) field and its completion and
119
+ error state follow the field's validity automatically — no manual `[completed]`
120
+ wiring. A step is `completed` when its field is **valid and touched**; it reflects
121
+ `error` when the field is **touched and invalid**. A manual `[completed]` /
122
+ `[hasError]` input always wins when set.
123
+
124
+ ```ts
125
+ import { Component, signal } from '@angular/core';
126
+ import { form, required, email } from '@angular/forms/signals';
127
+ import {
128
+ ForStepper,
129
+ ForStepperContent,
130
+ ForStepperItem,
131
+ ForStepperList,
132
+ ForStepperTrigger,
133
+ } from 'forty-cdk/stepper';
134
+
135
+ @Component({
136
+ imports: [ForStepper, ForStepperList, ForStepperItem, ForStepperTrigger, ForStepperContent],
137
+ template: `
138
+ <div forStepper [(selectedIndex)]="step" [linear]="true">
139
+ <ol forStepperList ariaLabel="Sign up">
140
+ <li forStepperItem [field]="signup.account">
141
+ <button forStepperTrigger>Account</button>
142
+ </li>
143
+ <li forStepperItem [field]="signup.profile">
144
+ <button forStepperTrigger>Profile</button>
145
+ </li>
146
+ </ol>
147
+ <section forStepperContent>…</section>
148
+ <section forStepperContent>…</section>
149
+ </div>
150
+ `,
151
+ })
152
+ export class SignupWizard {
153
+ protected readonly step = signal(0);
154
+ private readonly model = signal({ account: '', profile: '' });
155
+ protected readonly signup = form(this.model, (s) => {
156
+ required(s.account);
157
+ email(s.account);
158
+ required(s.profile);
159
+ });
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Progress mode (display-only)
166
+
167
+ ```html
168
+ <div forStepper [selectedIndex]="currentStep" mode="progress">
169
+ <ol forStepperList ariaLabel="Order status">
170
+ <li forStepperItem [completed]="currentStep > 0">
171
+ <span forStepperTrigger>Processing</span>
172
+ <span forStepperSeparator></span>
173
+ </li>
174
+ <li forStepperItem [completed]="currentStep > 1">
175
+ <span forStepperTrigger>Shipped</span>
176
+ <span forStepperSeparator></span>
177
+ </li>
178
+ <li forStepperItem>
179
+ <span forStepperTrigger>Delivered</span>
180
+ </li>
181
+ </ol>
182
+ </div>
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Progress bar (`ForStepperProgress`)
188
+
189
+ An optional `role="progressbar"` reflecting how far through the steps the user is. Reports
190
+ `aria-valuenow` (0–100) + `aria-valuetext`, and publishes a `--for-stepper-progress` (0–1)
191
+ custom property for a styleable fill. `valueBy="index"` (default) tracks the current step
192
+ index; `valueBy="completed"` tracks the count of completed steps.
193
+
194
+ ```html
195
+ <div forStepper [(selectedIndex)]="step">
196
+ <div forStepperProgress ariaLabel="Checkout progress"></div>
197
+ <!-- … list / content … -->
198
+ </div>
199
+ ```
200
+
201
+ ```css
202
+ [forStepperProgress]::after {
203
+ content: '';
204
+ display: block;
205
+ width: calc(var(--for-stepper-progress) * 100%);
206
+ }
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Custom icon per state (indicator example)
212
+
213
+ ```html
214
+ <li forStepperItem #step="forStepperItem">
215
+ <button forStepperTrigger>
216
+ <span forStepperIndicator>
217
+ @if (step.resolvedState() === 'completed') {
218
+ <svg><!-- checkmark --></svg>
219
+ } @else if (step.resolvedState() === 'error') {
220
+ <svg><!-- exclamation --></svg>
221
+ } @else { {{ step.index() + 1 }} }
222
+ </span>
223
+ Step label
224
+ </button>
225
+ </li>
226
+ ```
227
+
228
+ Or purely via CSS:
229
+
230
+ ```css
231
+ [forStepperIndicator][data-state='completed']::before {
232
+ content: '✓';
233
+ }
234
+ [forStepperIndicator][data-state='error']::before {
235
+ content: '!';
236
+ }
237
+ [forStepperIndicator][data-state='active']::before {
238
+ content: '●';
239
+ }
240
+ [forStepperIndicator][data-state='pending']::before {
241
+ content: '○';
242
+ }
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Keyboard interaction (interactive mode)
248
+
249
+ | Key | Action |
250
+ | -------------------------- | ----------------------------------------- |
251
+ | `ArrowRight` / `ArrowDown` | Move focus to next selectable trigger |
252
+ | `ArrowLeft` / `ArrowUp` | Move focus to previous selectable trigger |
253
+ | `Home` | Move focus to first selectable trigger |
254
+ | `End` | Move focus to last selectable trigger |
255
+ | `Space` / `Enter` | Activate focused trigger (manual mode) |
256
+ | `Tab` | Move focus into / out of the step panel |
257
+
258
+ In `activationMode="automatic"` arrow keys move focus AND select. In `activationMode="manual"` (default) only Space / Enter activate.
259
+
260
+ In `orientation="vertical"` ArrowUp/Down navigate; ArrowLeft/Right are ignored. In `orientation="horizontal"` ArrowLeft/Right navigate; ArrowUp/Down are ignored. RTL inverts ArrowLeft and ArrowRight.
261
+
262
+ ---
263
+
264
+ ## `data-state` vocabulary
265
+
266
+ | Piece | Values |
267
+ | ------------------------------ | ------------------------------------------------- |
268
+ | `[forStepperItem]` | `pending` `active` `completed` `error` `<custom>` |
269
+ | `[forStepperTrigger]` | same as item |
270
+ | `[forStepperIndicator]` | same as item |
271
+ | `[forStepperContent]` | `active` `inactive` |
272
+ | `[forStepperCompletedContent]` | `active` `inactive` |
273
+ | `[forStepperSeparator]` | `completed` `pending` |
274
+
275
+ ### Boolean `data-*`
276
+
277
+ | Attribute | When present |
278
+ | ------------------ | ------------------------------------------------ |
279
+ | `data-disabled` | Root or step is disabled |
280
+ | `data-orientation` | Always — `horizontal` or `vertical` |
281
+ | `data-mode` | Always (root only) — `interactive` or `progress` |
282
+
283
+ ---
284
+
285
+ ## Accessibility notes
286
+
287
+ - **Interactive mode** implements the WAI-ARIA Tabs pattern. Each trigger carries `role="tab"`, the list carries `role="tablist"`, and content panels carry `role="tabpanel"`. `aria-selected` is always emitted; `aria-controls` is gated to the current step (prevents dangling references when panels are unmounted with `@if`).
288
+ - **Progress mode** uses a standard `<ol role="list">` with `aria-current="step"` on the active trigger. No tab-stop manipulation is performed; triggers carry no `role`.
289
+ - **Disabled triggers** in interactive mode retain their tab stop using `aria-disabled="true"` rather than the native `disabled` attribute, so assistive technology can announce them.
290
+ - **Linear mode** reflects unreachable ahead-steps as `aria-disabled="true"` + `data-disabled=""` on the trigger. Keyboard navigation skips them automatically.
291
+ - **RTL** is supported: set `dir="rtl"` on the root or a DOM ancestor.
292
+ - **Progress bar** (`[forStepperProgress]`) is an opt-in part. When present it exposes `role="progressbar"` with `aria-valuemin="0"`, `aria-valuemax="100"`, and `aria-valuenow` derived from the current step or the count of completed steps.
@@ -0,0 +1,116 @@
1
+ # Switch
2
+
3
+ Headless implementation of the [WAI-ARIA Switch pattern](https://www.w3.org/WAI/ARIA/apg/patterns/switch/) that doubles as a `FormCheckboxControl` for Angular Signal Forms.
4
+
5
+ A switch is a binary on/off control whose state changes immediately on activation — distinct semantically from a checkbox (which represents a deferred selection).
6
+
7
+ ## Pieces
8
+
9
+ | Class | Selector | Role |
10
+ | ----------- | ------------- | -------------------------------------------------------------------- |
11
+ | `ForSwitch` | `[forSwitch]` | Single directive on a `<button>`. Wires ARIA + click + Signal Forms. |
12
+
13
+ ## Inputs / models
14
+
15
+ | API | Type | Description |
16
+ | ---------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
17
+ | `checked` | `model<boolean>` | Two-way bindable on/off state. Required by `FormCheckboxControl`. |
18
+ | `disabled` | `input<boolean>` | Ignores click; reflects `aria-disabled="true"` and `data-disabled`. Stays focusable (per APG). |
19
+ | `readonly` | `input<boolean>` | Ignores click; reflects `aria-readonly="true"`. Stays focusable. |
20
+ | `required` | `input<boolean>` | Reflects `aria-required="true"`. |
21
+ | `invalid` | `input<boolean>` | Reflects `aria-invalid="true"`. |
22
+ | `pending` | `input<boolean>` | Reflects `aria-busy="true"` while async validation is in flight. |
23
+ | `name` | `input<string \| undefined>` | Reflects on `name`. |
24
+ | `errors` | `input<readonly ValidationError.WithOptionalFieldTree[]>` | Validation errors fed by `[formField]`. The directive does not render them — that is consumer territory. |
25
+ | `touched` | `model<boolean>` | Set to `true` on blur. Two-way so the field can read it back. |
26
+
27
+ The host gets `data-state="checked" \| "unchecked"`, `data-disabled`, and `data-readonly` for CSS hooks.
28
+
29
+ ## Stand-alone usage
30
+
31
+ ```ts
32
+ import { Component, signal } from '@angular/core';
33
+ import { ForSwitch } from 'forty-cdk/switch';
34
+
35
+ @Component({
36
+ selector: 'demo-toggle',
37
+ imports: [ForSwitch],
38
+ template: `
39
+ <button forSwitch class="switch" [(checked)]="enabled">
40
+ <span class="thumb"></span>
41
+ </button>
42
+ <p>Notifications: {{ enabled() ? 'on' : 'off' }}</p>
43
+ `,
44
+ })
45
+ export class DemoToggle {
46
+ readonly enabled = signal(false);
47
+ }
48
+ ```
49
+
50
+ ## Signal Forms usage
51
+
52
+ `ForSwitch` implements `FormCheckboxControl`. The `[formField]` directive detects the interface and wires everything — value, disabled, required, invalid, errors, touched — without any glue.
53
+
54
+ ```ts
55
+ import { Component, signal } from '@angular/core';
56
+ import { form, required } from '@angular/forms/signals';
57
+ import { Field } from '@angular/forms';
58
+ import { ForSwitch } from 'forty-cdk/switch';
59
+
60
+ interface Settings {
61
+ notifications: boolean;
62
+ termsAccepted: boolean;
63
+ }
64
+
65
+ @Component({
66
+ selector: 'demo-settings',
67
+ imports: [ForSwitch /* , FormField from @angular/forms */],
68
+ template: `
69
+ <button forSwitch [formField]="settings.notifications"></button>
70
+ <button forSwitch [formField]="settings.termsAccepted"></button>
71
+ `,
72
+ })
73
+ export class DemoSettings {
74
+ readonly model = signal<Settings>({ notifications: false, termsAccepted: false });
75
+ readonly settings = form(this.model, (s) => {
76
+ required(s.termsAccepted);
77
+ });
78
+ }
79
+ ```
80
+
81
+ ## Styling
82
+
83
+ 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.
84
+
85
+ ### Data attributes
86
+
87
+ | Piece | Attribute | Values |
88
+ | ------------- | --------------- | ------------------------ |
89
+ | `[forSwitch]` | `data-state` | `checked` \| `unchecked` |
90
+ | `[forSwitch]` | `data-disabled` | present \| absent |
91
+ | `[forSwitch]` | `data-readonly` | present \| absent |
92
+ | `[forSwitch]` | `data-touched` | present \| absent |
93
+ | `[forSwitch]` | `data-dirty` | present \| absent |
94
+ | `[forSwitch]` | `data-pending` | present \| absent |
95
+ | `[forSwitch]` | `data-invalid` | present \| absent |
96
+
97
+ ```css
98
+ .switch .thumb {
99
+ transition: transform 150ms;
100
+ }
101
+
102
+ .switch[data-state='checked'] .thumb {
103
+ transform: translateX(100%);
104
+ }
105
+ ```
106
+
107
+ ## Accessibility notes
108
+
109
+ - **Use a `<button>`.** The directive forces `type="button"` to prevent submit-by-Enter inside a `<form>`. Enter and Space toggle the switch via native button behavior. On other elements (e.g. `<div>`), keyboard activation is on you.
110
+ - **A disabled switch stays focusable** (per APG): it reflects `aria-disabled="true"` + `data-disabled=""` rather than the native `disabled` attribute, so assistive tech still announces it while click / keyboard activation is a no-op. Form-submit exclusion is handled by the hidden `<input>`, not the visible button.
111
+ - **`role="switch"`** is announced as "switch, on/off" by screen readers, distinct from "checkbox, checked/not checked".
112
+ - **`@angular/forms` is an optional peer.** If you're not using Signal Forms, don't install it — the directive runs fine without it (only the type import from `@angular/forms/signals` is type-only and erased at build).
113
+
114
+ ## Wrapping in a design system
115
+
116
+ Both supported wrapper patterns — `hostDirectives` with the exported `FOR_SWITCH_HOST_DIRECTIVE_INPUTS` / `FOR_SWITCH_HOST_DIRECTIVE_OUTPUTS` name tuples, and subclassing — are documented in [Wrapping form primitives](../../../../../docs/wrapping-form-primitives.md).