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
|
@@ -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).
|