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.
- package/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
package/slider/README.md
ADDED
|
@@ -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.
|
package/switch/README.md
ADDED
|
@@ -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).
|