forty-cdk 0.4.0 → 0.6.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 +80 -71
- package/aspect-ratio/README.md +24 -22
- package/avatar/README.md +50 -33
- package/breadcrumbs/README.md +34 -8
- package/breakpoints/README.md +16 -12
- package/button/README.md +55 -18
- package/calendar/README.md +105 -83
- package/carousel/README.md +153 -112
- package/checkbox/README.md +60 -37
- package/combobox/README.md +106 -95
- package/context-menu/README.md +55 -37
- package/date-field/README.md +110 -60
- package/date-picker/README.md +126 -59
- package/date-range-field/README.md +251 -0
- package/dialog/README.md +83 -67
- package/disclosure/README.md +65 -59
- package/drag-drop/README.md +60 -5
- package/drawer/README.md +118 -92
- package/dropdown-menu/README.md +62 -49
- package/fesm2022/forty-cdk-combobox.mjs +39 -1
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
- package/fesm2022/forty-cdk-core.mjs +1140 -341
- package/fesm2022/forty-cdk-core.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-field.mjs +43 -345
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-picker.mjs +589 -266
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-range-field.mjs +682 -0
- package/fesm2022/forty-cdk-date-range-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +34 -4
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +6 -2
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-table.mjs +202 -6
- package/fesm2022/forty-cdk-table.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-field.mjs +45 -202
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-range-field.mjs +671 -0
- package/fesm2022/forty-cdk-time-range-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +22 -1
- package/fesm2022/forty-cdk-toast.mjs.map +1 -1
- package/fesm2022/forty-cdk-tooltip.mjs +57 -17
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs +338 -4
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/field/README.md +62 -21
- package/fieldset/README.md +35 -15
- package/file-upload/README.md +38 -14
- package/hover-card/README.md +104 -82
- package/input/README.md +81 -47
- package/listbox/README.md +215 -194
- package/menu/README.md +53 -29
- package/menubar/README.md +65 -39
- package/meter/README.md +67 -57
- package/navigation-menu/README.md +128 -115
- package/number-input/README.md +86 -68
- package/otp-input/README.md +78 -65
- package/package.json +9 -1
- package/pagination/README.md +57 -9
- package/pane-resizer/README.md +68 -51
- package/popover/README.md +110 -97
- package/progress/README.md +47 -39
- package/radio-group/README.md +78 -57
- package/scroll-area/README.md +81 -39
- package/search/README.md +39 -10
- package/select/README.md +141 -118
- package/separator/README.md +28 -26
- package/slider/README.md +95 -64
- package/stepper/README.md +100 -87
- package/switch/README.md +61 -40
- package/table/README.md +175 -46
- package/tabs/README.md +87 -70
- package/time-field/README.md +110 -58
- package/time-picker/README.md +74 -51
- package/time-range-field/README.md +256 -0
- package/toast/README.md +104 -68
- package/toggle/README.md +125 -104
- package/toolbar/README.md +97 -43
- package/tooltip/README.md +152 -131
- package/tree/README.md +120 -80
- package/types/forty-cdk-combobox.d.ts +25 -0
- package/types/forty-cdk-core.d.ts +546 -150
- package/types/forty-cdk-date-field.d.ts +12 -16
- package/types/forty-cdk-date-picker.d.ts +374 -155
- package/types/forty-cdk-date-range-field.d.ts +390 -0
- package/types/forty-cdk-hover-card.d.ts +8 -2
- package/types/forty-cdk-table.d.ts +81 -2
- package/types/forty-cdk-time-field.d.ts +16 -18
- package/types/forty-cdk-time-range-field.d.ts +389 -0
- package/types/forty-cdk-toast.d.ts +33 -0
- package/types/forty-cdk-tooltip.d.ts +21 -12
- package/types/forty-cdk-virtualization.d.ts +70 -3
- package/virtualization/README.md +52 -20
package/calendar/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Calendar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A single-date calendar grid implementing the APG Grid pattern over a pluggable date adapter: roving-tabindex day navigation, month / year paging, and min / max / per-date availability.
|
|
4
|
+
|
|
5
|
+
Headless and styleless — the date table at the heart of the APG [Date Picker Dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/) example. Full grid keyboard interaction (arrows / `Home` / `End` / `PageUp` / `PageDown` / `Shift+PageUp` / `Shift+PageDown`), focus paging across month boundaries, `aria-current="date"` on today, RTL arrow mirroring, and a pluggable, date-library-agnostic `DateAdapter<D>`.
|
|
4
6
|
|
|
5
7
|
`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`.
|
|
6
8
|
|
|
7
|
-
## Date adapter
|
|
9
|
+
## Date adapter
|
|
8
10
|
|
|
9
|
-
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:
|
|
11
|
+
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):
|
|
10
12
|
|
|
11
13
|
| Provider | Date type `D` | Dependency |
|
|
12
14
|
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
@@ -24,37 +26,35 @@ bootstrapApplication(App, {
|
|
|
24
26
|
|
|
25
27
|
`@internationalized/date` is a widely-used immutable date primitive; it works in every browser today with no polyfill, and its reference-equality-on-mutation makes it signal-friendly. Both `@internationalized/date` adapters operate on the **Gregorian** calendar today — `createDate` always builds a Gregorian date, so the grid stays Gregorian regardless of the runtime locale. True non-Gregorian calendar systems are deferred to the planned `Temporal.PlainDate` adapter ([#354](https://github.com/tutkli/forty-cdk/issues/354)), a non-breaking addition once the Temporal API is broadly available across browsers — the `DateAdapter<D>` seam means adopting it later is a drop-in, not a migration.
|
|
26
28
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
## Usage
|
|
29
|
+
## Anatomy
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<div forCalendar [(value)]="date">
|
|
33
|
+
<header>
|
|
34
|
+
<button forCalendarPrevButton [ariaLabel]="'Previous month'">‹</button>
|
|
35
|
+
<h2 forCalendarHeading #heading="forCalendarHeading">{{ heading.label() }}</h2>
|
|
36
|
+
<button forCalendarNextButton [ariaLabel]="'Next month'">›</button>
|
|
37
|
+
</header>
|
|
38
|
+
|
|
39
|
+
<table forCalendarGrid #grid="forCalendarGrid">
|
|
40
|
+
<thead forCalendarGridHeader>
|
|
41
|
+
<tr>
|
|
42
|
+
<!-- @for (day of grid.weekDays(); track day.key) -->
|
|
43
|
+
<th scope="col" [attr.aria-label]="day.long">{{ day.short }}</th>
|
|
44
|
+
</tr>
|
|
45
|
+
</thead>
|
|
46
|
+
<tbody>
|
|
47
|
+
<!-- @for (week of grid.weeks(); track week.key) -->
|
|
48
|
+
<tr>
|
|
49
|
+
<!-- @for (cell of week.days; track cell.key) -->
|
|
50
|
+
<td forCalendarCell [date]="cell.date">{{ cell.label }}</td>
|
|
51
|
+
</tr>
|
|
52
|
+
</tbody>
|
|
53
|
+
</table>
|
|
54
|
+
</div>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
58
|
|
|
59
59
|
```ts
|
|
60
60
|
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
@@ -117,6 +117,40 @@ export class DatePage {
|
|
|
117
117
|
|
|
118
118
|
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).
|
|
119
119
|
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
### `ForCalendar`
|
|
123
|
+
|
|
124
|
+
| Property | Type | Description |
|
|
125
|
+
| ------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
126
|
+
| `value` | `model<D \| null>` | Two-way bindable selected date, or `null`. Used in `selectionMode="single"`. `(valueChange)` fires only on internal selection.<br>**Default:** `null` |
|
|
127
|
+
| `selectionMode` | `input<'single' \| 'range'>` | `'single'` (default) keeps the single-date `value` flow. `'range'` switches to anchor → commit and exposes `range`.<br>**Default:** `'single'` |
|
|
128
|
+
| `range` | `model<CalendarDateRange<D> \| null>` | Two-way bindable committed range. Only used in `selectionMode="range"`. `(rangeChange)` fires only on internal commits/clears.<br>**Default:** `null` |
|
|
129
|
+
| `minRangeLength` | `input<number \| null>` | Minimum inclusive day count. A commit shorter than this is a no-op.<br>**Default:** `null` (no minimum) |
|
|
130
|
+
| `maxRangeLength` | `input<number \| null>` | Maximum inclusive day count. A commit longer than this is a no-op.<br>**Default:** `null` (no maximum) |
|
|
131
|
+
| `min` | `input<D \| null>` | Minimum selectable date (inclusive). Earlier dates are unavailable.<br>**Default:** `null` |
|
|
132
|
+
| `max` | `input<D \| null>` | Maximum selectable date (inclusive). Later dates are unavailable.<br>**Default:** `null` |
|
|
133
|
+
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate marking a date unavailable (present but not selectable).<br>**Default:** `() => false` |
|
|
134
|
+
| `dateLabel` | `input<CalendarDateLabelFormatter<D>>` | Formats each gridcell's `aria-label` (full accessible date).<br>**Default:** localized full date, outside-month days suffixed |
|
|
135
|
+
| `disabled` | `input<boolean>` | Disables the whole calendar (no focus movement, no selection). Reflected as `data-disabled`.<br>**Default:** — |
|
|
136
|
+
| `readonly` | `input<boolean>` | Read-only: dates stay focusable, selection is blocked. Reflected as `data-readonly`.<br>**Default:** — |
|
|
137
|
+
| `firstDayOfWeek` | `input<number \| null>` | First column's weekday, **0-6** (`0` = Sunday).<br>**Default:** `null` → the adapter's value (or `provideForCalendarDefaults`) |
|
|
138
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction.<br>**Default:** `null` resolves the ambient direction; reflected to the host `dir` and mirrors horizontal arrows |
|
|
139
|
+
|
|
140
|
+
### Data attributes
|
|
141
|
+
|
|
142
|
+
| Piece | Attribute | Values |
|
|
143
|
+
| ------------------------- | -------------------- | ----------------- |
|
|
144
|
+
| `[forCalendar]` | `data-disabled` | present \| absent |
|
|
145
|
+
| `[forCalendar]` | `data-readonly` | present \| absent |
|
|
146
|
+
| `[forCalendarPrevButton]` | `data-disabled` | present \| absent |
|
|
147
|
+
| `[forCalendarNextButton]` | `data-disabled` | present \| absent |
|
|
148
|
+
| `[forCalendarCell]` | `data-selected` | present \| absent |
|
|
149
|
+
| `[forCalendarCell]` | `data-today` | present \| absent |
|
|
150
|
+
| `[forCalendarCell]` | `data-highlighted` | present \| absent |
|
|
151
|
+
| `[forCalendarCell]` | `data-disabled` | present \| absent |
|
|
152
|
+
| `[forCalendarCell]` | `data-outside-month` | present \| absent |
|
|
153
|
+
|
|
120
154
|
## Range selection
|
|
121
155
|
|
|
122
156
|
Set `selectionMode="range"` and bind `[(range)]` to get date-range selection. In range mode the single-date `value` is unused and stays `null`.
|
|
@@ -245,7 +279,7 @@ The default window is **anchored to the current year** (not the visible year), s
|
|
|
245
279
|
|
|
246
280
|
The lower-level hooks (`visibleMonthNumber()`, `visibleYear()`, `monthOptions()`, `goToMonth()`, `goToYear()`, `isYearDisabled()`) in the table above remain available for any other UI.
|
|
247
281
|
|
|
248
|
-
## View switching
|
|
282
|
+
## View switching
|
|
249
283
|
|
|
250
284
|
`ForCalendar` supports two additional views — **month grid** and **year grid** — so users can jump quickly to a different month or year without paging one at a time. All three views share a single `[(view)]` model and the same `focusedDate` cursor.
|
|
251
285
|
|
|
@@ -364,23 +398,44 @@ Auto-disabled when the entire previous / next page would be outside `[min, max]`
|
|
|
364
398
|
| `[forCalendarYearCell]` | `data-highlighted` | present \| absent |
|
|
365
399
|
| `[forCalendarYearCell]` | `data-disabled` | present \| absent |
|
|
366
400
|
|
|
367
|
-
##
|
|
401
|
+
## Scope defaults
|
|
368
402
|
|
|
369
|
-
|
|
403
|
+
```ts
|
|
404
|
+
import { provideForCalendarDefaults } from 'forty-cdk/calendar';
|
|
370
405
|
|
|
371
|
-
|
|
406
|
+
// app config or a component's providers — Monday-first weeks for this scope
|
|
407
|
+
providers: [provideForCalendarDefaults({ firstDayOfWeek: 1 })];
|
|
408
|
+
```
|
|
372
409
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
|
378
|
-
|
|
|
379
|
-
|
|
|
380
|
-
|
|
|
381
|
-
|
|
|
382
|
-
|
|
|
383
|
-
|
|
|
410
|
+
## Keyboard
|
|
411
|
+
|
|
412
|
+
LTR (horizontal arrows mirror under `dir="rtl"`):
|
|
413
|
+
|
|
414
|
+
| Key | Behavior |
|
|
415
|
+
| --------------------------------- | ------------------------------------------------------------- |
|
|
416
|
+
| **ArrowLeft / ArrowRight** | Previous / next day. |
|
|
417
|
+
| **ArrowUp / ArrowDown** | Same weekday, previous / next week. |
|
|
418
|
+
| **Home / End** | First / last day of the focused week. |
|
|
419
|
+
| **PageUp / PageDown** | Same day-of-month, previous / next month (re-pages the grid). |
|
|
420
|
+
| **Shift+PageUp / Shift+PageDown** | Same month, previous / next year. |
|
|
421
|
+
| **Enter / Space** | Select the focused date. |
|
|
422
|
+
|
|
423
|
+
Focus that crosses a month boundary re-pages the visible grid and keeps the focused cell in view. Day-of-month is constrained when paging (e.g. Jan 31 → Feb 28). Paging (the prev / next buttons and `PageUp` / `PageDown`) also clamps the focused date into the `[min, max]` range, so it never lands on a cell outside the selectable bounds; day / week arrows still move freely across unavailable dates so keyboard navigation is never trapped.
|
|
424
|
+
|
|
425
|
+
## Accessibility
|
|
426
|
+
|
|
427
|
+
Implements the [WAI-ARIA Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
|
|
428
|
+
|
|
429
|
+
- **`role="grid"`** on the table, `columnheader` weekday headers, `gridcell` days — the APG Date Picker Dialog technique over a real `<table>`.
|
|
430
|
+
- **`aria-labelledby`** wires the grid to the heading so it names the visible period. Paging the month is announced through a dedicated off-screen `aria-live="polite"` region (owned by `[forCalendar]`), so the period is read on navigation without the heading double-announcing as both a live region and the grid's label.
|
|
431
|
+
- **`aria-label`** on every cell carries the full localized date (e.g. `"Monday, June 15, 2026"`) so screen readers announce the whole date, not the bare day number that stays the cell's visible content. Outside-month padding days are suffixed (`" (outside month)"`) so they are distinguishable. Override the format via `ForCalendar`'s `dateLabel` input.
|
|
432
|
+
- **`aria-selected`** is always emitted (`"true"` / `"false"`); **`aria-current="date"`** marks today; **`aria-disabled`** marks unavailable dates (truthy-only).
|
|
433
|
+
- **Roving tabindex**: exactly one cell (the focused date) is tabbable. `Tab` enters and leaves the grid in one stop.
|
|
434
|
+
- **Boolean `data-*`** on the cell — `data-selected`, `data-today`, `data-highlighted` (the focused/roving cell), `data-disabled`, `data-outside-month` — present when true, absent when false.
|
|
435
|
+
|
|
436
|
+
## Styling
|
|
437
|
+
|
|
438
|
+
forty-cdk ships no styles. Add your own class to each piece — the `forCalendar*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes listed under [Data attributes](#data-attributes).
|
|
384
439
|
|
|
385
440
|
```css
|
|
386
441
|
.calendar-cell {
|
|
@@ -402,31 +457,7 @@ forty-cdk ships no styles. Add your own class to each piece — the `forCalendar
|
|
|
402
457
|
}
|
|
403
458
|
```
|
|
404
459
|
|
|
405
|
-
##
|
|
406
|
-
|
|
407
|
-
LTR (horizontal arrows mirror under `dir="rtl"`):
|
|
408
|
-
|
|
409
|
-
| Key | Behavior |
|
|
410
|
-
| --------------------------------- | ------------------------------------------------------------- |
|
|
411
|
-
| **ArrowLeft / ArrowRight** | Previous / next day. |
|
|
412
|
-
| **ArrowUp / ArrowDown** | Same weekday, previous / next week. |
|
|
413
|
-
| **Home / End** | First / last day of the focused week. |
|
|
414
|
-
| **PageUp / PageDown** | Same day-of-month, previous / next month (re-pages the grid). |
|
|
415
|
-
| **Shift+PageUp / Shift+PageDown** | Same month, previous / next year. |
|
|
416
|
-
| **Enter / Space** | Select the focused date. |
|
|
417
|
-
|
|
418
|
-
Focus that crosses a month boundary re-pages the visible grid and keeps the focused cell in view. Day-of-month is constrained when paging (e.g. Jan 31 → Feb 28). Paging (the prev / next buttons and `PageUp` / `PageDown`) also clamps the focused date into the `[min, max]` range, so it never lands on a cell outside the selectable bounds; day / week arrows still move freely across unavailable dates so keyboard navigation is never trapped.
|
|
419
|
-
|
|
420
|
-
## Scope defaults
|
|
421
|
-
|
|
422
|
-
```ts
|
|
423
|
-
import { provideForCalendarDefaults } from 'forty-cdk/calendar';
|
|
424
|
-
|
|
425
|
-
// app config or a component's providers — Monday-first weeks for this scope
|
|
426
|
-
providers: [provideForCalendarDefaults({ firstDayOfWeek: 1 })];
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
## SSR / hydration
|
|
460
|
+
## SSR
|
|
430
461
|
|
|
431
462
|
The active adapter's `today()` and `format()` resolve against the **runtime** time zone and default locale. Rendered on the server they reflect the _server's_ environment, so a render near midnight (or under a different server locale) can disagree with the browser by up to a day — `ForCalendar` reads `today()` once to mark the `data-today` / `aria-current="date"` cell, so the mismatch surfaces there as a hydration error and a flicker. For SSR, pin a fixed "today" / time zone / locale for both environments, or defer the today-highlight so it only computes client-side:
|
|
432
463
|
|
|
@@ -447,12 +478,3 @@ export class DatePage {
|
|
|
447
478
|
}
|
|
448
479
|
}
|
|
449
480
|
```
|
|
450
|
-
|
|
451
|
-
## Accessibility notes
|
|
452
|
-
|
|
453
|
-
- **`role="grid"`** on the table, `columnheader` weekday headers, `gridcell` days — the APG Date Picker Dialog technique over a real `<table>`.
|
|
454
|
-
- **`aria-labelledby`** wires the grid to the heading so it names the visible period. Paging the month is announced through a dedicated off-screen `aria-live="polite"` region (owned by `[forCalendar]`), so the period is read on navigation without the heading double-announcing as both a live region and the grid's label.
|
|
455
|
-
- **`aria-label`** on every cell carries the full localized date (e.g. `"Monday, June 15, 2026"`) so screen readers announce the whole date, not the bare day number that stays the cell's visible content. Outside-month padding days are suffixed (`" (outside month)"`) so they are distinguishable. Override the format via `ForCalendar`'s `dateLabel` input.
|
|
456
|
-
- **`aria-selected`** is always emitted (`"true"` / `"false"`); **`aria-current="date"`** marks today; **`aria-disabled`** marks unavailable dates (truthy-only).
|
|
457
|
-
- **Roving tabindex**: exactly one cell (the focused date) is tabbable. `Tab` enters and leaves the grid in one stop.
|
|
458
|
-
- **Boolean `data-*`** on the cell — `data-selected`, `data-today`, `data-highlighted` (the focused/roving cell), `data-disabled`, `data-outside-month` — present when true, absent when false.
|
package/carousel/README.md
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
# ForCarousel
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
3
|
+
A slideshow of content panels with previous / next controls, an indicator group, optional looping and multi-slide views, and an accessible autoplay mode with a pause control.
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
Headless and styleless: it ships slide tracking, keyboard navigation, focus management, and ARIA; you supply the markup and CSS.
|
|
6
|
+
|
|
7
|
+
## Anatomy
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<div forCarousel [(activeIndex)]="index" loop ariaLabel="Featured products">
|
|
11
|
+
<button forCarouselPrevious aria-label="Previous slide">‹</button>
|
|
12
|
+
|
|
13
|
+
<div forCarouselViewport>
|
|
14
|
+
<div forCarouselTrack>
|
|
15
|
+
<!-- one [forCarouselSlide] per item -->
|
|
16
|
+
<div forCarouselSlide>Slide content</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<button forCarouselNext aria-label="Next slide">›</button>
|
|
21
|
+
|
|
22
|
+
<div forCarouselIndicators ariaLabel="Choose slide to display">
|
|
23
|
+
<!-- one [forCarouselIndicator] per slide, same order -->
|
|
24
|
+
<button forCarouselIndicator></button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Basic carousel
|
|
9
32
|
|
|
10
33
|
```html
|
|
11
34
|
<div
|
|
@@ -44,49 +67,6 @@ desynchronizes the active-indicator state and is dev-guarded by a `console.warn`
|
|
|
44
67
|
in development builds. Grouped or summarized indicators (fewer dots than slides)
|
|
45
68
|
are not supported.
|
|
46
69
|
|
|
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
70
|
## Autoplay
|
|
91
71
|
|
|
92
72
|
Add the `autoplay` attribute and a `[forCarouselRotationControl]` as the **first
|
|
@@ -130,23 +110,6 @@ the defaults with `startLabel` / `stopLabel` inputs:
|
|
|
130
110
|
<button forCarouselRotationControl startLabel="Play slideshow" stopLabel="Pause slideshow"></button>
|
|
131
111
|
```
|
|
132
112
|
|
|
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
113
|
**Programmatic control** via `exportAs`:
|
|
151
114
|
|
|
152
115
|
```html
|
|
@@ -224,39 +187,59 @@ horizontal carousels (allows vertical page scroll) and `pan-x` for vertical
|
|
|
224
187
|
carousels (allows horizontal page scroll). A mostly-cross-axis swipe is never
|
|
225
188
|
captured, so page scrolling on the perpendicular axis is unaffected.
|
|
226
189
|
|
|
227
|
-
|
|
190
|
+
## Localizing the default labels
|
|
228
191
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
192
|
+
Each slide's default `aria-label` is the positional `"N of M"` string, and each
|
|
193
|
+
indicator's is `"Go to slide N"`. Localize both centrally with
|
|
194
|
+
`provideForCarouselDefaults` instead of setting `ariaLabel` on every slide and
|
|
195
|
+
indicator:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
providers: [
|
|
199
|
+
provideForCarouselDefaults({
|
|
200
|
+
slideLabel: (position, total) => `Diapositiva ${position} de ${total}`,
|
|
201
|
+
indicatorLabel: (position) => `Ir a la diapositiva ${position}`,
|
|
202
|
+
}),
|
|
203
|
+
];
|
|
204
|
+
```
|
|
232
205
|
|
|
233
|
-
|
|
206
|
+
`position` is the 1-based slide index and `total` is the slide count. Overrides
|
|
207
|
+
merge with the parent scope, so you can localize just the labels and inherit the
|
|
208
|
+
rest of the defaults. A per-element `ariaLabel` on `[forCarouselSlide]` /
|
|
209
|
+
`[forCarouselIndicator]` still takes precedence over the localized default.
|
|
234
210
|
|
|
235
|
-
|
|
236
|
-
| ---------- | --------- | ------- | ---------------------------------------------------------------------------- |
|
|
237
|
-
| `disabled` | `boolean` | `false` | Disable pointer drag without removing the directive. Removes `touch-action`. |
|
|
211
|
+
## API
|
|
238
212
|
|
|
239
|
-
|
|
213
|
+
### `ForCarousel`
|
|
240
214
|
|
|
241
215
|
All inputs are on `[forCarousel]` unless noted.
|
|
242
216
|
|
|
243
|
-
|
|
|
244
|
-
| ------------------------------------------------ | ------------------------------ |
|
|
245
|
-
| `activeIndex` | `model<number>` |
|
|
246
|
-
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'`
|
|
247
|
-
| `loop` | `boolean` |
|
|
248
|
-
| `align` | `'start' \| 'center' \| 'end'` |
|
|
249
|
-
| `slidesPerView` | `number` |
|
|
250
|
-
| `containScroll` | `boolean` |
|
|
251
|
-
| `autoplay` | `boolean` |
|
|
252
|
-
| `autoplayInterval` | `number` |
|
|
253
|
-
| `ariaLabel` | `string \| null` |
|
|
254
|
-
| `dir` | `'ltr' \| 'rtl' \| null` | `null` (inherits)
|
|
255
|
-
| `ariaLabel` (on `[forCarouselIndicators]`) | `string \| null` |
|
|
256
|
-
| `ariaLabel` (on `[forCarouselSlide]`) | `string \| null` |
|
|
257
|
-
| `disabled` (on `[forCarouselIndicator]`) | `boolean` |
|
|
258
|
-
| `startLabel` (on `[forCarouselRotationControl]`) | `string` | `'Start automatic slide show'`
|
|
259
|
-
| `stopLabel` (on `[forCarouselRotationControl]`) | `string` | `'Stop automatic slide show'`
|
|
217
|
+
| Property | Type | Description |
|
|
218
|
+
| ------------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
219
|
+
| `activeIndex` | `model<number>` | Two-way bindable current slide index.<br>**Default:** `0` |
|
|
220
|
+
| `orientation` | `'horizontal' \| 'vertical'` | Scroll axis.<br>**Default:** `'horizontal'` |
|
|
221
|
+
| `loop` | `boolean` | Wrap-around at the boundaries.<br>**Default:** `false` |
|
|
222
|
+
| `align` | `'start' \| 'center' \| 'end'` | Alignment of the active slide.<br>**Default:** `'start'` |
|
|
223
|
+
| `slidesPerView` | `number` | Visible slides at once.<br>**Default:** `1` |
|
|
224
|
+
| `containScroll` | `boolean` | Clamp the track offset so trailing slides sit flush at the viewport edge (no overscroll) when `slidesPerView > 1` and not looping.<br>**Default:** `false` |
|
|
225
|
+
| `autoplay` | `boolean` | Enable auto-rotation (suppressed by `prefers-reduced-motion: reduce`).<br>**Default:** `false` |
|
|
226
|
+
| `autoplayInterval` | `number` | Ms between automatic slide advances. `<= 0` disables the timer.<br>**Default:** `5000` |
|
|
227
|
+
| `ariaLabel` | `string \| null` | Accessible label for the carousel root.<br>**Default:** `null` |
|
|
228
|
+
| `dir` | `'ltr' \| 'rtl' \| null` | Writing direction.<br>**Default:** `null` (inherits) |
|
|
229
|
+
| `ariaLabel` (on `[forCarouselIndicators]`) | `string \| null` | Label for the picker group.<br>**Default:** `null` |
|
|
230
|
+
| `ariaLabel` (on `[forCarouselSlide]`) | `string \| null` | Override the positional "N of M" label.<br>**Default:** `null` |
|
|
231
|
+
| `disabled` (on `[forCarouselIndicator]`) | `boolean` | Disable this indicator.<br>**Default:** `false` |
|
|
232
|
+
| `startLabel` (on `[forCarouselRotationControl]`) | `string` | Accessible name while rotation is stopped.<br>**Default:** `'Start automatic slide show'` |
|
|
233
|
+
| `stopLabel` (on `[forCarouselRotationControl]`) | `string` | Accessible name while rotation is playing.<br>**Default:** `'Stop automatic slide show'` |
|
|
234
|
+
|
|
235
|
+
Reflected on the `[forCarousel]` host:
|
|
236
|
+
|
|
237
|
+
| Data attribute | Values |
|
|
238
|
+
| ------------------ | --------------------------------- |
|
|
239
|
+
| `data-orientation` | `horizontal` \| `vertical` |
|
|
240
|
+
| `data-align` | `start` \| `center` \| `end` |
|
|
241
|
+
| `data-autoplay` | present when `autoplay` is `true` |
|
|
242
|
+
| `data-rotating` | present while actively rotating |
|
|
260
243
|
|
|
261
244
|
### Contain scroll
|
|
262
245
|
|
|
@@ -275,28 +258,19 @@ same clamped view simply share the same visual position. The clamp has no effect
|
|
|
275
258
|
when `loop` is enabled (the entire range is valid when wrapping) or when
|
|
276
259
|
`slidesPerView` is `1` (a single-view carousel never overscrolls).
|
|
277
260
|
|
|
278
|
-
|
|
261
|
+
### `ForCarouselDrag` inputs
|
|
279
262
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
`
|
|
283
|
-
indicator:
|
|
263
|
+
| Property | Type | Description |
|
|
264
|
+
| ---------- | --------- | ---------------------------------------------------------------------------------------------------- |
|
|
265
|
+
| `disabled` | `boolean` | Disable pointer drag without removing the directive. Removes `touch-action`.<br>**Default:** `false` |
|
|
284
266
|
|
|
285
|
-
|
|
286
|
-
providers: [
|
|
287
|
-
provideForCarouselDefaults({
|
|
288
|
-
slideLabel: (position, total) => `Diapositiva ${position} de ${total}`,
|
|
289
|
-
indicatorLabel: (position) => `Ir a la diapositiva ${position}`,
|
|
290
|
-
}),
|
|
291
|
-
];
|
|
292
|
-
```
|
|
267
|
+
Reflected on the `[forCarouselViewport]` host this directive is applied to:
|
|
293
268
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
`[forCarouselIndicator]` still takes precedence over the localized default.
|
|
269
|
+
| Data attribute | Values |
|
|
270
|
+
| --------------- | ------------------------------------- |
|
|
271
|
+
| `data-dragging` | present while a drag gesture is armed |
|
|
298
272
|
|
|
299
|
-
## Keyboard
|
|
273
|
+
## Keyboard
|
|
300
274
|
|
|
301
275
|
Keyboard navigation lives on the indicator group. Only the current
|
|
302
276
|
indicator is in the tab order. Arrow keys move focus and activate the target slide
|
|
@@ -310,7 +284,9 @@ automatically.
|
|
|
310
284
|
| `End` | Last indicator and slide. |
|
|
311
285
|
| `Enter` / `Space` | Activate the focused indicator (via native button). |
|
|
312
286
|
|
|
313
|
-
## Accessibility
|
|
287
|
+
## Accessibility
|
|
288
|
+
|
|
289
|
+
Implements the [WAI-ARIA Carousel pattern](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/).
|
|
314
290
|
|
|
315
291
|
- The root carries `role="group"` and `aria-roledescription="carousel"`. The `ariaLabel` input
|
|
316
292
|
should describe the carousel's purpose without using the word "carousel" (APG guidance).
|
|
@@ -330,7 +306,72 @@ automatically.
|
|
|
330
306
|
`aria-roledescription`, `aria-label`, and `aria-hidden` toggle carry the screen-reader
|
|
331
307
|
experience for non-auto-rotating carousels.
|
|
332
308
|
|
|
333
|
-
##
|
|
309
|
+
## Styling
|
|
310
|
+
|
|
311
|
+
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)). The directive publishes geometry as CSS custom properties on the root element so they cascade to the track. The consumer applies the transform and transition.
|
|
312
|
+
|
|
313
|
+
```css
|
|
314
|
+
[forCarouselViewport] {
|
|
315
|
+
overflow: hidden;
|
|
316
|
+
}
|
|
317
|
+
[forCarouselTrack] {
|
|
318
|
+
display: flex;
|
|
319
|
+
transform: translateX(var(--for-carousel-offset));
|
|
320
|
+
transition: transform 300ms ease;
|
|
321
|
+
}
|
|
322
|
+
[forCarousel][data-orientation='vertical'] [forCarouselTrack] {
|
|
323
|
+
flex-direction: column;
|
|
324
|
+
transform: translateY(var(--for-carousel-offset));
|
|
325
|
+
}
|
|
326
|
+
[forCarouselSlide] {
|
|
327
|
+
flex: 0 0 calc(100% / var(--for-carousel-slides-per-view));
|
|
328
|
+
}
|
|
329
|
+
@media (prefers-reduced-motion: reduce) {
|
|
330
|
+
[forCarouselTrack] {
|
|
331
|
+
transition: none;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### CSS custom properties
|
|
337
|
+
|
|
338
|
+
The following properties are set on the `[forCarousel]` host and cascade to
|
|
339
|
+
children, unless noted otherwise:
|
|
340
|
+
|
|
341
|
+
| Property | Host | Value | Notes |
|
|
342
|
+
| -------------------------------- | ----------------------- | ------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
343
|
+
| `--for-carousel-offset` | `[forCarousel]` | e.g. `-100%` | Pure arithmetic from `activeIndex`, `slidesPerView`, `align`. |
|
|
344
|
+
| `--for-carousel-active-index` | `[forCarousel]` | integer | Current `activeIndex`. |
|
|
345
|
+
| `--for-carousel-slide-count` | `[forCarousel]` | integer | Total registered slides. |
|
|
346
|
+
| `--for-carousel-slides-per-view` | `[forCarousel]` | integer | From the `slidesPerView` input. |
|
|
347
|
+
| `--for-carousel-viewport-width` | `[forCarousel]` | e.g. `640px` | Measured via `ResizeObserver`. Absent on the server and before first measurement. |
|
|
348
|
+
| `--for-carousel-viewport-height` | `[forCarousel]` | e.g. `400px` | Same as above, for the block axis. |
|
|
349
|
+
| `--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`. |
|
|
350
|
+
|
|
351
|
+
### Autoplay styling hooks
|
|
352
|
+
|
|
353
|
+
```css
|
|
354
|
+
[forCarouselRotationControl]::before {
|
|
355
|
+
content: '▶';
|
|
356
|
+
}
|
|
357
|
+
[forCarouselRotationControl][data-playing]::before {
|
|
358
|
+
content: '⏸';
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
| Attribute | When present |
|
|
363
|
+
| --------------- | ------------------------------------------------------- |
|
|
364
|
+
| `data-playing` | On `[forCarouselRotationControl]` — user intent is "on" |
|
|
365
|
+
| `data-rotating` | On `[forCarousel]` — actively rotating right now |
|
|
366
|
+
| `data-autoplay` | On `[forCarousel]` — the `autoplay` input is `true` |
|
|
367
|
+
|
|
368
|
+
### Drag styling hooks
|
|
369
|
+
|
|
370
|
+
| Attribute | Host | When present |
|
|
371
|
+
| --------------- | ----------------------- | -------------------------------------- |
|
|
372
|
+
| `data-dragging` | `[forCarouselViewport]` | Present while a drag gesture is armed. |
|
|
373
|
+
|
|
374
|
+
### Reduced-motion
|
|
334
375
|
|
|
335
376
|
The directive performs no animation itself. Add the following CSS to disable the transition
|
|
336
377
|
for users who prefer reduced motion:
|
|
@@ -343,7 +384,7 @@ for users who prefer reduced motion:
|
|
|
343
384
|
}
|
|
344
385
|
```
|
|
345
386
|
|
|
346
|
-
|
|
387
|
+
### RTL support
|
|
347
388
|
|
|
348
389
|
Arrow-key direction (ArrowLeft/ArrowRight) is automatically swapped in RTL — handled by
|
|
349
390
|
`resolveListNavigation` and the reflected `dir` attribute. The **visual** track direction
|