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.
- package/accordion/README.md +65 -17
- package/aspect-ratio/README.md +12 -29
- package/avatar/README.md +31 -33
- package/breadcrumbs/README.md +54 -11
- package/breakpoints/README.md +68 -12
- package/button/README.md +38 -11
- package/calendar/README.md +141 -17
- package/carousel/README.md +184 -40
- package/checkbox/README.md +19 -10
- package/combobox/README.md +176 -38
- package/context-menu/README.md +77 -69
- package/date-field/README.md +42 -14
- package/date-picker/README.md +81 -31
- package/dialog/README.md +235 -186
- package/disclosure/README.md +29 -13
- package/drag-drop/README.md +129 -31
- package/drawer/README.md +149 -30
- package/dropdown-menu/README.md +80 -83
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-combobox.mjs +23 -3
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
- package/fesm2022/forty-cdk-core-overlay.mjs +86 -46
- package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
- package/fesm2022/forty-cdk-core.mjs +52 -3
- package/fesm2022/forty-cdk-core.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-picker.mjs +5 -3
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-drag-drop.mjs +14 -6
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +23 -12
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-menu.mjs +4 -2
- package/fesm2022/forty-cdk-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-menubar.mjs +18 -4
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
- package/fesm2022/forty-cdk-popover.mjs +5 -3
- package/fesm2022/forty-cdk-popover.mjs.map +1 -1
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
- package/fesm2022/forty-cdk-select.mjs +11 -12
- package/fesm2022/forty-cdk-select.mjs.map +1 -1
- package/fesm2022/forty-cdk-table.mjs.map +1 -1
- package/fesm2022/forty-cdk-tree.mjs +195 -59
- package/fesm2022/forty-cdk-tree.mjs.map +1 -1
- package/field/README.md +39 -23
- package/fieldset/README.md +30 -16
- package/file-upload/README.md +78 -14
- package/hover-card/README.md +64 -39
- package/input/README.md +59 -24
- package/internationalized-date/README.md +2 -0
- package/listbox/README.md +87 -31
- package/menu/README.md +196 -24
- package/menubar/README.md +54 -32
- package/meter/README.md +32 -34
- package/navigation-menu/README.md +125 -32
- package/number-input/README.md +18 -16
- package/otp-input/README.md +66 -62
- package/package.json +1 -1
- package/pagination/README.md +75 -11
- package/pane-resizer/README.md +28 -38
- package/popover/README.md +66 -41
- package/progress/README.md +25 -41
- package/radio-group/README.md +40 -10
- package/scroll-area/README.md +62 -87
- package/search/README.md +108 -41
- package/select/README.md +189 -35
- package/separator/README.md +19 -20
- package/shared/README.md +7 -1
- package/slider/README.md +39 -11
- package/stepper/README.md +184 -105
- package/switch/README.md +19 -13
- package/table/README.md +338 -143
- package/table-virtualization/README.md +21 -19
- package/tabs/README.md +62 -23
- package/time-field/README.md +40 -7
- package/time-picker/README.md +96 -35
- package/toast/README.md +130 -54
- package/toggle/README.md +46 -35
- package/toolbar/README.md +75 -16
- package/tooltip/README.md +76 -56
- package/tree/README.md +214 -113
- package/types/forty-cdk-checkbox.d.ts +1 -1
- package/types/forty-cdk-combobox.d.ts +10 -3
- package/types/forty-cdk-core-overlay.d.ts +35 -13
- package/types/forty-cdk-core.d.ts +47 -5
- package/types/forty-cdk-listbox.d.ts +8 -1
- package/types/forty-cdk-menu.d.ts +12 -10
- package/types/forty-cdk-menubar.d.ts +21 -3
- package/types/forty-cdk-radio-group.d.ts +1 -1
- package/types/forty-cdk-select.d.ts +1 -1
- package/types/forty-cdk-table.d.ts +1 -1
- package/types/forty-cdk-tree.d.ts +57 -10
- package/virtual-reorder/README.md +22 -20
- package/virtualization/README.md +127 -38
- package/visually-hidden/README.md +58 -21
package/calendar/README.md
CHANGED
|
@@ -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'"
|
|
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'"
|
|
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,
|
|
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-
|
|
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)]="
|
|
97
|
-
<header>
|
|
98
|
-
<button forCalendarPrevButton [ariaLabel]="'Previous month'"
|
|
99
|
-
|
|
100
|
-
|
|
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">
|
|
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
|
|
125
|
-
readonly
|
|
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'"
|
|
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'"
|
|
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'"
|
|
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'"
|
|
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>
|
package/carousel/README.md
CHANGED
|
@@ -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"
|
|
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"
|
|
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
|
-
|
|
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
|
-
```
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
+
### Multiple slides per view
|
|
59
166
|
|
|
60
|
-
|
|
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
|
-
###
|
|
169
|
+
### Autoplay with pause control
|
|
69
170
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
in
|
|
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
|
-
|
|
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"
|
|
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]
|
|
381
|
-
|
|
523
|
+
[forCarouselRotationControl] .icon-pause,
|
|
524
|
+
[forCarouselRotationControl][data-playing] .icon-play {
|
|
525
|
+
display: none;
|
|
382
526
|
}
|
|
383
|
-
[forCarouselRotationControl][data-playing]
|
|
384
|
-
|
|
527
|
+
[forCarouselRotationControl][data-playing] .icon-pause {
|
|
528
|
+
display: revert;
|
|
385
529
|
}
|
|
386
530
|
```
|
|
387
531
|
|
package/checkbox/README.md
CHANGED
|
@@ -31,31 +31,34 @@ Use the one that matches your semantics. `ForCheckbox` and `ForSwitch` are inten
|
|
|
31
31
|
|
|
32
32
|
## Examples
|
|
33
33
|
|
|
34
|
-
|
|
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: '
|
|
41
|
+
selector: 'app-checkbox-default-example',
|
|
42
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
42
43
|
imports: [ForCheckbox],
|
|
43
44
|
template: `
|
|
44
|
-
<
|
|
45
|
-
<
|
|
46
|
-
<span class="
|
|
47
|
-
</
|
|
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
|
-
</
|
|
50
|
+
</button>
|
|
50
51
|
`,
|
|
51
52
|
})
|
|
52
|
-
export class
|
|
53
|
-
readonly
|
|
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';
|