forty-cdk 0.25.2 → 0.26.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 (94) hide show
  1. package/accordion/README.md +65 -17
  2. package/aspect-ratio/README.md +12 -29
  3. package/avatar/README.md +31 -33
  4. package/breadcrumbs/README.md +54 -11
  5. package/breakpoints/README.md +68 -12
  6. package/button/README.md +38 -11
  7. package/calendar/README.md +141 -17
  8. package/carousel/README.md +184 -40
  9. package/checkbox/README.md +19 -10
  10. package/combobox/README.md +176 -38
  11. package/context-menu/README.md +77 -69
  12. package/date-field/README.md +42 -14
  13. package/date-picker/README.md +81 -31
  14. package/dialog/README.md +235 -186
  15. package/disclosure/README.md +29 -13
  16. package/drag-drop/README.md +129 -31
  17. package/drawer/README.md +149 -30
  18. package/dropdown-menu/README.md +80 -83
  19. package/fesm2022/forty-cdk-checkbox.mjs.map +1 -1
  20. package/fesm2022/forty-cdk-combobox.mjs +23 -3
  21. package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
  22. package/fesm2022/forty-cdk-core-overlay.mjs +86 -46
  23. package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
  24. package/fesm2022/forty-cdk-core.mjs +52 -3
  25. package/fesm2022/forty-cdk-core.mjs.map +1 -1
  26. package/fesm2022/forty-cdk-date-picker.mjs +5 -3
  27. package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
  28. package/fesm2022/forty-cdk-drag-drop.mjs +14 -6
  29. package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
  30. package/fesm2022/forty-cdk-listbox.mjs +23 -12
  31. package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
  32. package/fesm2022/forty-cdk-menu.mjs +4 -2
  33. package/fesm2022/forty-cdk-menu.mjs.map +1 -1
  34. package/fesm2022/forty-cdk-menubar.mjs +18 -4
  35. package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
  36. package/fesm2022/forty-cdk-popover.mjs +5 -3
  37. package/fesm2022/forty-cdk-popover.mjs.map +1 -1
  38. package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
  39. package/fesm2022/forty-cdk-select.mjs +11 -12
  40. package/fesm2022/forty-cdk-select.mjs.map +1 -1
  41. package/fesm2022/forty-cdk-table.mjs.map +1 -1
  42. package/fesm2022/forty-cdk-tree.mjs +195 -59
  43. package/fesm2022/forty-cdk-tree.mjs.map +1 -1
  44. package/field/README.md +39 -23
  45. package/fieldset/README.md +30 -16
  46. package/file-upload/README.md +78 -14
  47. package/hover-card/README.md +64 -39
  48. package/input/README.md +59 -24
  49. package/internationalized-date/README.md +2 -0
  50. package/listbox/README.md +87 -31
  51. package/menu/README.md +196 -24
  52. package/menubar/README.md +54 -32
  53. package/meter/README.md +32 -34
  54. package/navigation-menu/README.md +125 -32
  55. package/number-input/README.md +18 -16
  56. package/otp-input/README.md +66 -62
  57. package/package.json +1 -1
  58. package/pagination/README.md +75 -11
  59. package/pane-resizer/README.md +28 -38
  60. package/popover/README.md +66 -41
  61. package/progress/README.md +25 -41
  62. package/radio-group/README.md +40 -10
  63. package/scroll-area/README.md +62 -87
  64. package/search/README.md +108 -41
  65. package/select/README.md +189 -35
  66. package/separator/README.md +19 -20
  67. package/shared/README.md +7 -1
  68. package/slider/README.md +39 -11
  69. package/stepper/README.md +184 -105
  70. package/switch/README.md +19 -13
  71. package/table/README.md +338 -143
  72. package/table-virtualization/README.md +21 -19
  73. package/tabs/README.md +62 -23
  74. package/time-field/README.md +40 -7
  75. package/time-picker/README.md +96 -35
  76. package/toast/README.md +130 -54
  77. package/toggle/README.md +46 -35
  78. package/toolbar/README.md +75 -16
  79. package/tooltip/README.md +76 -56
  80. package/tree/README.md +214 -113
  81. package/types/forty-cdk-checkbox.d.ts +1 -1
  82. package/types/forty-cdk-combobox.d.ts +10 -3
  83. package/types/forty-cdk-core-overlay.d.ts +35 -13
  84. package/types/forty-cdk-core.d.ts +47 -5
  85. package/types/forty-cdk-listbox.d.ts +8 -1
  86. package/types/forty-cdk-menu.d.ts +12 -10
  87. package/types/forty-cdk-menubar.d.ts +21 -3
  88. package/types/forty-cdk-radio-group.d.ts +1 -1
  89. package/types/forty-cdk-select.d.ts +1 -1
  90. package/types/forty-cdk-table.d.ts +1 -1
  91. package/types/forty-cdk-tree.d.ts +57 -10
  92. package/virtual-reorder/README.md +22 -20
  93. package/virtualization/README.md +127 -38
  94. package/visually-hidden/README.md +58 -21
@@ -13,6 +13,12 @@ Headless and styleless — the date table at the heart of the APG [Date Picker D
13
13
 
14
14
  `ForCalendar` is the grid widget, **not a form value** — it exposes `[(value)]` as a `model<D | null>`. The form-control contract (`FormValueControl<D>`) arrives with the follow-up `ForDatePicker` / `ForDateField`.
15
15
 
16
+ ## When to choose
17
+
18
+ - **Calendar** — the date grid itself, always visible, exposing `[(value)]` as a model. It is a widget, not a form control: it implements no `FormValueControl` contract, so `[formField]` binds a picker or a field instead.
19
+ - **[Date Picker](../date-picker/README.md)** — wraps this same grid in a trigger-anchored floating surface and _is_ the form value. Choose it when a form owns the date and the grid should stay collapsed until asked for.
20
+ - **[Date Field](../date-field/README.md)** — segmented typed entry with no grid. Choose it when the date is known rather than browsed.
21
+
16
22
  ## Date adapter
17
23
 
18
24
  All date math goes through a `DateAdapter<D>`, so the library hard-depends on **no** date library. Provide exactly one adapter in your application (or component) providers (required):
@@ -22,6 +28,8 @@ All date math goes through a `DateAdapter<D>`, so the library hard-depends on **
22
28
  | `provideInternationalizedDateAdapter()` | `CalendarDate` (`@internationalized/date`) | **Recommended.** From `forty-cdk/internationalized-date`; needs `@internationalized/date` (optional peer) |
23
29
  | `provideNativeDateAdapter()` | `Date` | None (zero-dependency fallback) |
24
30
 
31
+ <!-- snippet: fragment -->
32
+
25
33
  ```ts
26
34
  import { bootstrapApplication } from '@angular/platform-browser';
27
35
  import { provideInternationalizedDateAdapter } from 'forty-cdk/internationalized-date';
@@ -42,9 +50,31 @@ bootstrapApplication(App, {
42
50
  ```html
43
51
  <div forCalendar [(value)]="date">
44
52
  <header>
45
- <button forCalendarPrevButton [ariaLabel]="'Previous month'">‹</button>
53
+ <button forCalendarPrevButton [ariaLabel]="'Previous month'">
54
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
55
+ <path
56
+ d="m15.75 19.5-7.5-7.5 7.5-7.5"
57
+ fill="none"
58
+ stroke="currentColor"
59
+ stroke-width="1.75"
60
+ stroke-linecap="round"
61
+ stroke-linejoin="round"
62
+ />
63
+ </svg>
64
+ </button>
46
65
  <h2 forCalendarHeading #heading="forCalendarHeading">{{ heading.label() }}</h2>
47
- <button forCalendarNextButton [ariaLabel]="'Next month'">›</button>
66
+ <button forCalendarNextButton [ariaLabel]="'Next month'">
67
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
68
+ <path
69
+ d="m8.25 4.5 7.5 7.5-7.5 7.5"
70
+ fill="none"
71
+ stroke="currentColor"
72
+ stroke-width="1.75"
73
+ stroke-linecap="round"
74
+ stroke-linejoin="round"
75
+ />
76
+ </svg>
77
+ </button>
48
78
  </header>
49
79
 
50
80
  <table forCalendarGrid #grid="forCalendarGrid">
@@ -67,9 +97,11 @@ bootstrapApplication(App, {
67
97
 
68
98
  ## Examples
69
99
 
100
+ Move across the grid with the arrow keys, page with `PageUp` / `PageDown`, and select with `Enter` — every cell reflects `data-selected`, `data-today` and `data-outside-month`.
101
+
70
102
  ```ts
71
103
  import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
72
- import { CalendarDate, today, getLocalTimeZone } from '@internationalized/date';
104
+ import { type CalendarDate, getLocalTimeZone, today } from '@internationalized/date';
73
105
  import {
74
106
  ForCalendar,
75
107
  ForCalendarCell,
@@ -79,9 +111,10 @@ import {
79
111
  ForCalendarNextButton,
80
112
  ForCalendarPrevButton,
81
113
  } from 'forty-cdk/calendar';
114
+ import { provideInternationalizedDateAdapter } from 'forty-cdk/internationalized-date';
82
115
 
83
116
  @Component({
84
- selector: 'app-date',
117
+ selector: 'app-calendar-default-example',
85
118
  changeDetection: ChangeDetectionStrategy.OnPush,
86
119
  imports: [
87
120
  ForCalendar,
@@ -92,19 +125,46 @@ import {
92
125
  ForCalendarGridHeader,
93
126
  ForCalendarCell,
94
127
  ],
128
+ providers: [...provideInternationalizedDateAdapter()],
95
129
  template: `
96
- <div forCalendar [(value)]="date">
97
- <header>
98
- <button forCalendarPrevButton [ariaLabel]="'Previous month'">‹</button>
99
- <h2 forCalendarHeading #heading="forCalendarHeading">{{ heading.label() }}</h2>
100
- <button forCalendarNextButton [ariaLabel]="'Next month'">›</button>
130
+ <div forCalendar class="calendar" [(value)]="value">
131
+ <header class="calendar-header">
132
+ <button forCalendarPrevButton class="calendar-nav" [ariaLabel]="'Previous month'">
133
+ <svg viewBox="0 0 24 24" aria-hidden="true">
134
+ <path
135
+ d="m15.75 19.5-7.5-7.5 7.5-7.5"
136
+ fill="none"
137
+ stroke="currentColor"
138
+ stroke-width="1.75"
139
+ stroke-linecap="round"
140
+ stroke-linejoin="round"
141
+ />
142
+ </svg>
143
+ </button>
144
+ <h2 forCalendarHeading #heading="forCalendarHeading" class="calendar-title">
145
+ {{ heading.label() }}
146
+ </h2>
147
+ <button forCalendarNextButton class="calendar-nav" [ariaLabel]="'Next month'">
148
+ <svg viewBox="0 0 24 24" aria-hidden="true">
149
+ <path
150
+ d="m8.25 4.5 7.5 7.5-7.5 7.5"
151
+ fill="none"
152
+ stroke="currentColor"
153
+ stroke-width="1.75"
154
+ stroke-linecap="round"
155
+ stroke-linejoin="round"
156
+ />
157
+ </svg>
158
+ </button>
101
159
  </header>
102
160
 
103
- <table forCalendarGrid #grid="forCalendarGrid">
161
+ <table forCalendarGrid #grid="forCalendarGrid" class="calendar-grid">
104
162
  <thead forCalendarGridHeader>
105
163
  <tr>
106
164
  @for (day of grid.weekDays(); track day.key) {
107
- <th scope="col" [attr.aria-label]="day.long">{{ day.short }}</th>
165
+ <th scope="col" class="calendar-weekday" [attr.aria-label]="day.long">
166
+ {{ day.narrow }}
167
+ </th>
108
168
  }
109
169
  </tr>
110
170
  </thead>
@@ -121,13 +181,33 @@ import {
121
181
  </div>
122
182
  `,
123
183
  })
124
- export class DatePage {
125
- readonly date = signal<CalendarDate | null>(today(getLocalTimeZone()));
184
+ export class CalendarDefaultExample {
185
+ protected readonly value = signal<CalendarDate | null>(today(getLocalTimeZone()));
126
186
  }
127
187
  ```
128
188
 
129
189
  The library is styleless: style the boolean `data-*` hooks on `[forCalendarCell]` yourself (`[data-selected]`, `[data-today]`, `[data-outside-month]`, `[data-highlighted]`, and `:not([data-disabled])` for the enabled state).
130
190
 
191
+ ### States
192
+
193
+ One class and one directive, three states. `disabled` turns off focus movement and selection for the whole calendar; `readonly` keeps days focusable and the grid still pages, but clicking or pressing `Enter` no longer changes the selection. Each reflects a root hook — `data-disabled` and `data-readonly` — and the example's stylesheet keys on nothing else.
194
+
195
+ ### Constraints & week start
196
+
197
+ `min` disables past dates and `isDateUnavailable` blocks weekends — both reflect `aria-disabled` and refuse selection, while arrows still move across them so navigation is never trapped. `firstDayOfWeek` starts the week on Monday.
198
+
199
+ ### Range selection
200
+
201
+ Set `selectionMode="range"` and bind `[(range)]` to a `DateRange` signal. Click a first cell to anchor the range, move the pointer to preview, click a second cell to commit. Committed cells reflect `data-range-start` / `data-range-end` / `data-in-range`; the preview band uses `data-range-preview`.
202
+
203
+ ### Month / year dropdowns
204
+
205
+ `[forCalendarMonthSelect]` and `[forCalendarYearSelect]` wire native selects to the calendar's month/year navigation. Render the options yourself from `m.options()` and `y.years()`; months and years entirely outside `[min, max]` are disabled.
206
+
207
+ ### View switching (month / year picker)
208
+
209
+ Click the heading button to cycle from day → month → year view. Click a month to drill down to days; click a year to drill down to months. Prev/next pages by month, year, or block depending on the active view, and `min` / `max` disable out-of-range cells.
210
+
131
211
  ## API
132
212
 
133
213
  ### `ForCalendar`
@@ -251,7 +331,18 @@ The recommended path is `[forCalendarMonthSelect]` and `[forCalendarYearSelect]`
251
331
  ```html
252
332
  <div forCalendar [(value)]="date">
253
333
  <header>
254
- <button forCalendarPrevButton [ariaLabel]="'Previous month'">‹</button>
334
+ <button forCalendarPrevButton [ariaLabel]="'Previous month'">
335
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
336
+ <path
337
+ d="m15.75 19.5-7.5-7.5 7.5-7.5"
338
+ fill="none"
339
+ stroke="currentColor"
340
+ stroke-width="1.75"
341
+ stroke-linecap="round"
342
+ stroke-linejoin="round"
343
+ />
344
+ </svg>
345
+ </button>
255
346
  <select forCalendarMonthSelect #m="forCalendarMonthSelect">
256
347
  @for (opt of m.options(); track opt.value) {
257
348
  <option [value]="opt.value" [disabled]="opt.disabled">{{ opt.label }}</option>
@@ -262,7 +353,18 @@ The recommended path is `[forCalendarMonthSelect]` and `[forCalendarYearSelect]`
262
353
  <option [value]="opt.value" [disabled]="opt.disabled">{{ opt.value }}</option>
263
354
  }
264
355
  </select>
265
- <button forCalendarNextButton [ariaLabel]="'Next month'">›</button>
356
+ <button forCalendarNextButton [ariaLabel]="'Next month'">
357
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
358
+ <path
359
+ d="m8.25 4.5 7.5 7.5-7.5 7.5"
360
+ fill="none"
361
+ stroke="currentColor"
362
+ stroke-width="1.75"
363
+ stroke-linecap="round"
364
+ stroke-linejoin="round"
365
+ />
366
+ </svg>
367
+ </button>
266
368
  <h2 forCalendarHeading class="sr-only" #h="forCalendarHeading">{{ h.label() }}</h2>
267
369
  </header>
268
370
  <table forCalendarGrid>
@@ -317,9 +419,31 @@ The lower-level hooks (`visibleMonthNumber()`, `visibleYear()`, `monthOptions()`
317
419
  ```html
318
420
  <div forCalendar [(value)]="date" #cal="forCalendar">
319
421
  <header>
320
- <button forCalendarPrevButton [ariaLabel]="'Previous'">‹</button>
422
+ <button forCalendarPrevButton [ariaLabel]="'Previous'">
423
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
424
+ <path
425
+ d="m15.75 19.5-7.5-7.5 7.5-7.5"
426
+ fill="none"
427
+ stroke="currentColor"
428
+ stroke-width="1.75"
429
+ stroke-linecap="round"
430
+ stroke-linejoin="round"
431
+ />
432
+ </svg>
433
+ </button>
321
434
  <button forCalendarViewTrigger #vt="forCalendarViewTrigger">{{ vt.label() }}</button>
322
- <button forCalendarNextButton [ariaLabel]="'Next'">›</button>
435
+ <button forCalendarNextButton [ariaLabel]="'Next'">
436
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
437
+ <path
438
+ d="m8.25 4.5 7.5 7.5-7.5 7.5"
439
+ fill="none"
440
+ stroke="currentColor"
441
+ stroke-width="1.75"
442
+ stroke-linecap="round"
443
+ stroke-linejoin="round"
444
+ />
445
+ </svg>
446
+ </button>
323
447
  <!-- keep a visually-hidden heading so the grid stays labelled -->
324
448
  <h2 forCalendarHeading #h="forCalendarHeading" class="sr-only">{{ h.label() }}</h2>
325
449
  </header>
@@ -15,7 +15,18 @@ Headless and styleless: it ships slide tracking, keyboard navigation, focus mana
15
15
 
16
16
  ```html
17
17
  <div forCarousel [(activeIndex)]="index" loop ariaLabel="Featured products">
18
- <button forCarouselPrevious aria-label="Previous slide">‹</button>
18
+ <button forCarouselPrevious aria-label="Previous slide">
19
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
20
+ <path
21
+ d="m15.75 19.5-7.5-7.5 7.5-7.5"
22
+ fill="none"
23
+ stroke="currentColor"
24
+ stroke-width="1.75"
25
+ stroke-linecap="round"
26
+ stroke-linejoin="round"
27
+ />
28
+ </svg>
29
+ </button>
19
30
 
20
31
  <div forCarouselViewport>
21
32
  <div forCarouselTrack>
@@ -24,7 +35,18 @@ Headless and styleless: it ships slide tracking, keyboard navigation, focus mana
24
35
  </div>
25
36
  </div>
26
37
 
27
- <button forCarouselNext aria-label="Next slide">›</button>
38
+ <button forCarouselNext aria-label="Next slide">
39
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
40
+ <path
41
+ d="m8.25 4.5 7.5 7.5-7.5 7.5"
42
+ fill="none"
43
+ stroke="currentColor"
44
+ stroke-width="1.75"
45
+ stroke-linecap="round"
46
+ stroke-linejoin="round"
47
+ />
48
+ </svg>
49
+ </button>
28
50
 
29
51
  <div forCarouselIndicators ariaLabel="Choose slide to display">
30
52
  <!-- one [forCarouselIndicator] per slide, same order -->
@@ -35,44 +57,122 @@ Headless and styleless: it ships slide tracking, keyboard navigation, focus mana
35
57
 
36
58
  ## Examples
37
59
 
38
- ### Basic carousel
60
+ Page through the slides with the buttons, the arrow keys or a drag — `data-dragging` is on while a pointer holds the track, and the root carries `data-orientation`.
39
61
 
40
- ```html
41
- <div
42
- forCarousel
43
- [(activeIndex)]="index"
44
- loop
45
- orientation="horizontal"
46
- ariaLabel="Featured products"
47
- >
48
- <button forCarouselPrevious aria-label="Previous slide">‹</button>
62
+ ```ts
63
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
64
+ import {
65
+ ForCarousel,
66
+ ForCarouselIndicator,
67
+ ForCarouselIndicators,
68
+ ForCarouselNext,
69
+ ForCarouselPrevious,
70
+ ForCarouselSlide,
71
+ ForCarouselTrack,
72
+ ForCarouselViewport,
73
+ } from 'forty-cdk/carousel';
74
+
75
+ interface Slide {
76
+ readonly id: number;
77
+ readonly label: string;
78
+ }
49
79
 
50
- <div forCarouselViewport>
51
- <div forCarouselTrack>
52
- @for (product of products(); track product.id) {
53
- <div forCarouselSlide>{{ product.name }}</div>
54
- }
80
+ @Component({
81
+ selector: 'app-carousel-default-example',
82
+ changeDetection: ChangeDetectionStrategy.OnPush,
83
+ imports: [
84
+ ForCarousel,
85
+ ForCarouselViewport,
86
+ ForCarouselTrack,
87
+ ForCarouselSlide,
88
+ ForCarouselPrevious,
89
+ ForCarouselNext,
90
+ ForCarouselIndicators,
91
+ ForCarouselIndicator,
92
+ ],
93
+ template: `
94
+ <div
95
+ forCarousel
96
+ class="car"
97
+ [(activeIndex)]="activeIndex"
98
+ loop
99
+ orientation="horizontal"
100
+ align="start"
101
+ ariaLabel="Featured slides"
102
+ >
103
+ <div class="car-controls-row">
104
+ <button forCarouselPrevious class="car-btn" aria-label="Previous slide">
105
+ <svg viewBox="0 0 24 24" aria-hidden="true">
106
+ <path
107
+ d="m15.75 19.5-7.5-7.5 7.5-7.5"
108
+ fill="none"
109
+ stroke="currentColor"
110
+ stroke-width="1.75"
111
+ stroke-linecap="round"
112
+ stroke-linejoin="round"
113
+ />
114
+ </svg>
115
+ </button>
116
+ <button forCarouselNext class="car-btn" aria-label="Next slide">
117
+ <svg viewBox="0 0 24 24" aria-hidden="true">
118
+ <path
119
+ d="m8.25 4.5 7.5 7.5-7.5 7.5"
120
+ fill="none"
121
+ stroke="currentColor"
122
+ stroke-width="1.75"
123
+ stroke-linecap="round"
124
+ stroke-linejoin="round"
125
+ />
126
+ </svg>
127
+ </button>
128
+ </div>
129
+
130
+ <div forCarouselViewport class="car-viewport">
131
+ <div forCarouselTrack class="car-track">
132
+ @for (slide of slides; track slide.id; let i = $index) {
133
+ <div forCarouselSlide class="car-slide" [class]="'car-slide--' + (i + 1)">
134
+ <span class="car-slide-label">{{ slide.label }}</span>
135
+ </div>
136
+ }
137
+ </div>
138
+ </div>
139
+
140
+ <div forCarouselIndicators class="car-indicators" ariaLabel="Choose slide to display">
141
+ @for (slide of slides; track slide.id; let i = $index) {
142
+ <button
143
+ forCarouselIndicator
144
+ class="car-dot"
145
+ [attr.aria-label]="'Go to slide ' + (i + 1)"
146
+ ></button>
147
+ }
148
+ </div>
55
149
  </div>
56
- </div>
150
+ `,
151
+ })
152
+ export class CarouselDefaultExample {
153
+ protected readonly slides: readonly Slide[] = [
154
+ { id: 1, label: 'Slide 1' },
155
+ { id: 2, label: 'Slide 2' },
156
+ { id: 3, label: 'Slide 3' },
157
+ { id: 4, label: 'Slide 4' },
158
+ { id: 5, label: 'Slide 5' },
159
+ ];
160
+
161
+ protected readonly activeIndex = signal(0);
162
+ }
163
+ ```
57
164
 
58
- <button forCarouselNext aria-label="Next slide">›</button>
165
+ ### Multiple slides per view
59
166
 
60
- <div forCarouselIndicators ariaLabel="Choose slide to display">
61
- @for (product of products(); track product.id; let i = $index) {
62
- <button forCarouselIndicator [attr.aria-label]="'Go to slide ' + (i + 1)"></button>
63
- }
64
- </div>
65
- </div>
66
- ```
167
+ Set `slidesPerView` above `1` to show several slides at once. Each slide is `flex: 0 0 calc(100% / var(--for-carousel-slides-per-view))`; `loop` wraps once the last visible set is reached.
67
168
 
68
- ### Indicators map 1:1 to slides
169
+ ### Autoplay with pause control
69
170
 
70
- The picker assumes **one `[forCarouselIndicator]` per `[forCarouselSlide]`**: the
71
- indicator at DOM index `i` targets slide `i`. Iterate the same collection that
72
- drives the slides (as above) so the counts always match. A mismatched count
73
- desynchronizes the active-indicator state and is dev-guarded by a `console.warn`
74
- in development builds. Grouped or summarized indicators (fewer dots than slides)
75
- are not supported.
171
+ `[forCarouselRotationControl]` is the first focusable child (APG / WCAG 2.2.2). Rotation pauses on hover, on keyboard focus inside the carousel, and while the tab is backgrounded; an explicit stop is sticky. Under `prefers-reduced-motion` it does not auto-start.
172
+
173
+ ### Drag / swipe
174
+
175
+ Add the opt-in `[forCarouselDrag]` directive to the viewport for pointer drag and touch swipe. The track follows the finger 1:1 via `--for-carousel-swipe-movement-x/y`, then snaps to the nearest slide on release.
76
176
 
77
177
  ## Autoplay
78
178
 
@@ -82,11 +182,38 @@ focusable child** of the carousel to enable automatic slide rotation.
82
182
  ```html
83
183
  <div forCarousel autoplay [autoplayInterval]="4000" ariaLabel="Featured products">
84
184
  <button forCarouselRotationControl>
85
- <!-- swap icon with [data-playing] in your CSS -->
185
+ <svg class="icon-play" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
186
+ <path d="M6.75 5.25 17.25 12 6.75 18.75z" fill="currentColor" />
187
+ </svg>
188
+ <svg class="icon-pause" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
189
+ <path d="M8.25 5.25h3v13.5h-3zM12.75 5.25h3v13.5h-3z" fill="currentColor" />
190
+ </svg>
191
+ </button>
192
+ <button forCarouselPrevious aria-label="Previous slide">
193
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
194
+ <path
195
+ d="m15.75 19.5-7.5-7.5 7.5-7.5"
196
+ fill="none"
197
+ stroke="currentColor"
198
+ stroke-width="1.75"
199
+ stroke-linecap="round"
200
+ stroke-linejoin="round"
201
+ />
202
+ </svg>
86
203
  </button>
87
- <button forCarouselPrevious aria-label="Previous slide">‹</button>
88
204
  <div forCarouselViewport>…</div>
89
- <button forCarouselNext aria-label="Next slide">›</button>
205
+ <button forCarouselNext aria-label="Next slide">
206
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
207
+ <path
208
+ d="m8.25 4.5 7.5 7.5-7.5 7.5"
209
+ fill="none"
210
+ stroke="currentColor"
211
+ stroke-width="1.75"
212
+ stroke-linecap="round"
213
+ stroke-linejoin="round"
214
+ />
215
+ </svg>
216
+ </button>
90
217
  <div forCarouselIndicators ariaLabel="Choose slide to display">…</div>
91
218
  </div>
92
219
  ```
@@ -128,6 +255,8 @@ an `aria-label` overriding it.
128
255
  <div forCarousel #car="forCarousel" autoplay>…</div>
129
256
  ```
130
257
 
258
+ <!-- snippet: fragment -->
259
+
131
260
  ```ts
132
261
  car.play(); // start (explicit, sticky)
133
262
  car.pause(); // stop (explicit, sticky)
@@ -214,6 +343,8 @@ indicator's is `"Go to slide N"`, and the rotation control's swaps between
214
343
  all centrally with `provideForCarouselDefaults` instead of setting `ariaLabel` on
215
344
  every slide and indicator:
216
345
 
346
+ <!-- snippet: fragment -->
347
+
217
348
  ```ts
218
349
  providers: [
219
350
  provideForCarouselDefaults({
@@ -231,6 +362,15 @@ rest of the defaults. A per-element `ariaLabel` on `[forCarouselSlide]` /
231
362
  `[forCarouselIndicator]` still takes precedence over the localized default, as do
232
363
  `[startLabel]` / `[stopLabel]` on `[forCarouselRotationControl]`.
233
364
 
365
+ ## Indicators map 1:1 to slides
366
+
367
+ The picker assumes **one `[forCarouselIndicator]` per `[forCarouselSlide]`**: the
368
+ indicator at DOM index `i` targets slide `i`. Iterate the same collection that
369
+ drives the slides so the counts always match. A mismatched count
370
+ desynchronizes the active-indicator state and is dev-guarded by a `console.warn`
371
+ in development builds. Grouped or summarized indicators (fewer dots than slides)
372
+ are not supported.
373
+
234
374
  ## API
235
375
 
236
376
  ### `ForCarousel`
@@ -376,12 +516,16 @@ children, unless noted otherwise:
376
516
 
377
517
  ### Autoplay styling hooks
378
518
 
519
+ Render both icons inside the control and let `data-playing` pick which one shows — the
520
+ control keeps its own accessible name either way, so the icons stay `aria-hidden`.
521
+
379
522
  ```css
380
- [forCarouselRotationControl]::before {
381
- content: '▶';
523
+ [forCarouselRotationControl] .icon-pause,
524
+ [forCarouselRotationControl][data-playing] .icon-play {
525
+ display: none;
382
526
  }
383
- [forCarouselRotationControl][data-playing]::before {
384
- content: '⏸';
527
+ [forCarouselRotationControl][data-playing] .icon-pause {
528
+ display: revert;
385
529
  }
386
530
  ```
387
531
 
@@ -31,31 +31,34 @@ Use the one that matches your semantics. `ForCheckbox` and `ForSwitch` are inten
31
31
 
32
32
  ## Examples
33
33
 
34
- ### Stand-alone
34
+ Tick it with the pointer or `Space` and watch `data-state` move between `checked` and `unchecked` — the box is a `<button>`, so its whole appearance is your CSS.
35
35
 
36
36
  ```ts
37
- import { Component, signal } from '@angular/core';
37
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
38
38
  import { ForCheckbox } from 'forty-cdk/checkbox';
39
39
 
40
40
  @Component({
41
- selector: 'demo-terms',
41
+ selector: 'app-checkbox-default-example',
42
+ changeDetection: ChangeDetectionStrategy.OnPush,
42
43
  imports: [ForCheckbox],
43
44
  template: `
44
- <label>
45
- <button forCheckbox class="checkbox" [(checked)]="agreed">
46
- <span class="indicator"></span>
47
- </button>
45
+ <button forCheckbox class="cb-row" [(checked)]="checked">
46
+ <span class="cb">
47
+ <span class="cb-check" aria-hidden="true"></span>
48
+ </span>
48
49
  I agree to the terms
49
- </label>
50
+ </button>
50
51
  `,
51
52
  })
52
- export class DemoTerms {
53
- readonly agreed = signal(false);
53
+ export class CheckboxDefaultExample {
54
+ protected readonly checked = signal(false);
54
55
  }
55
56
  ```
56
57
 
57
58
  ### Tri-state ("select all")
58
59
 
60
+ A parent checkbox reflects `indeterminate` when only some children are selected. Activating it selects or clears them all at once, matching native inputs.
61
+
59
62
  ```ts
60
63
  import { Component, computed, signal } from '@angular/core';
61
64
  import { ForCheckbox } from 'forty-cdk/checkbox';
@@ -96,8 +99,14 @@ export class DemoSelectAll {
96
99
  }
97
100
  ```
98
101
 
102
+ ### States
103
+
104
+ One class and one directive, three states. `disabled` and `readonly` both keep the box focusable and announced (per APG) while click and `Space` are a no-op; they reflect `data-disabled` and `data-readonly`, which is all the example's stylesheet keys on.
105
+
99
106
  ### Signal Forms
100
107
 
108
+ `forCheckbox` implements `FormCheckboxControl`, so a single `[formField]` binding wires the binary `checked` value into the form and pulls validity back out. The box is required: blur it unchecked and it reflects `data-invalid` / `data-touched`.
109
+
101
110
  ```ts
102
111
  import { Component, signal } from '@angular/core';
103
112
  import { form, required } from '@angular/forms/signals';