forty-cdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# Calendar
|
|
2
|
+
|
|
3
|
+
Headless single-date calendar grid following the [WAI-ARIA Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/) — 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. Roving-tabindex focus management, full grid keyboard interaction (arrows / `Home` / `End` / `PageUp` / `PageDown` / `Shift+PageUp` / `Shift+PageDown`), focus paging across month boundaries, `aria-current="date"` on today, `min` / `max` / per-date availability, RTL arrow mirroring, and a pluggable, date-library-agnostic `DateAdapter<D>`.
|
|
4
|
+
|
|
5
|
+
`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
|
+
|
|
7
|
+
## Date adapter — pick one (required)
|
|
8
|
+
|
|
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:
|
|
10
|
+
|
|
11
|
+
| Provider | Date type `D` | Dependency |
|
|
12
|
+
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `provideInternationalizedDateAdapter()` | `CalendarDate` (`@internationalized/date`) | **Recommended.** From `forty-cdk/internationalized-date`; needs `@internationalized/date` (optional peer) |
|
|
14
|
+
| `provideNativeDateAdapter()` | `Date` | None (zero-dependency fallback) |
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
18
|
+
import { provideInternationalizedDateAdapter } from 'forty-cdk/internationalized-date';
|
|
19
|
+
|
|
20
|
+
bootstrapApplication(App, {
|
|
21
|
+
providers: [provideInternationalizedDateAdapter()],
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`@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
|
+
|
|
27
|
+
## Pieces
|
|
28
|
+
|
|
29
|
+
| Class | Selector | Role |
|
|
30
|
+
| ----------------------- | ------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `ForCalendar` | `[forCalendar]` | Root. Owns `value`, the focused date, the visible month, and the shared context. |
|
|
32
|
+
| `ForCalendarHeading` | `[forCalendarHeading]` | Month/year title and the grid's `aria-labelledby` target; its text is set to the visible period. |
|
|
33
|
+
| `ForCalendarPrevButton` | `[forCalendarPrevButton]` | Pages to the previous month. Auto-disabled at the `min` bound. |
|
|
34
|
+
| `ForCalendarNextButton` | `[forCalendarNextButton]` | Pages to the next month. Auto-disabled at the `max` bound. |
|
|
35
|
+
| `ForCalendarGrid` | `[forCalendarGrid]` | Date table (`role="grid"`, `aria-labelledby` the heading). Exposes `weekDays()` / `weeks()`. |
|
|
36
|
+
| `ForCalendarGridHeader` | `[forCalendarGridHeader]` | Header rowgroup (`role="rowgroup"`) holding the weekday `columnheader`s. Also exposes `weekDays()`. |
|
|
37
|
+
| `ForCalendarCell` | `[forCalendarCell]` | One day (`role="gridcell"`). Roving tab stop, ARIA state, and keyboard / click interaction. |
|
|
38
|
+
|
|
39
|
+
## Inputs / models — `ForCalendar`
|
|
40
|
+
|
|
41
|
+
| API | Type | Description |
|
|
42
|
+
| ------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `value` | `model<D \| null>` | Two-way bindable selected date, or `null`. Used in `selectionMode="single"`. `(valueChange)` fires only on internal selection. Default `null`. |
|
|
44
|
+
| `selectionMode` | `input<'single' \| 'range'>` | `'single'` (default) keeps the single-date `value` flow. `'range'` switches to anchor → commit and exposes `range`. |
|
|
45
|
+
| `range` | `model<CalendarDateRange<D> \| null>` | Two-way bindable committed range. Only used in `selectionMode="range"`. `(rangeChange)` fires only on internal commits/clears. Default `null`. |
|
|
46
|
+
| `minRangeLength` | `input<number \| null>` | Minimum inclusive day count. A commit shorter than this is a no-op. Default `null` (no minimum). |
|
|
47
|
+
| `maxRangeLength` | `input<number \| null>` | Maximum inclusive day count. A commit longer than this is a no-op. Default `null` (no maximum). |
|
|
48
|
+
| `min` | `input<D \| null>` | Minimum selectable date (inclusive). Earlier dates are unavailable. Default `null`. |
|
|
49
|
+
| `max` | `input<D \| null>` | Maximum selectable date (inclusive). Later dates are unavailable. Default `null`. |
|
|
50
|
+
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate marking a date unavailable (present but not selectable). Default `() => false`. |
|
|
51
|
+
| `dateLabel` | `input<CalendarDateLabelFormatter<D>>` | Formats each gridcell's `aria-label` (full accessible date). Default: localized full date, outside-month days suffixed. |
|
|
52
|
+
| `disabled` | `input<boolean>` | Disables the whole calendar (no focus movement, no selection). Reflected as `data-disabled`. |
|
|
53
|
+
| `readonly` | `input<boolean>` | Read-only: dates stay focusable, selection is blocked. Reflected as `data-readonly`. |
|
|
54
|
+
| `firstDayOfWeek` | `input<number \| null>` | First column's weekday, **0-6** (`0` = Sunday). Default `null` → the adapter's value (or `provideForCalendarDefaults`). |
|
|
55
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. Default `null` resolves the ambient direction; reflected to the host `dir` and mirrors horizontal arrows. |
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
61
|
+
import { CalendarDate, today, getLocalTimeZone } from '@internationalized/date';
|
|
62
|
+
import {
|
|
63
|
+
ForCalendar,
|
|
64
|
+
ForCalendarCell,
|
|
65
|
+
ForCalendarGrid,
|
|
66
|
+
ForCalendarGridHeader,
|
|
67
|
+
ForCalendarHeading,
|
|
68
|
+
ForCalendarNextButton,
|
|
69
|
+
ForCalendarPrevButton,
|
|
70
|
+
} from 'forty-cdk/calendar';
|
|
71
|
+
|
|
72
|
+
@Component({
|
|
73
|
+
selector: 'app-date',
|
|
74
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
75
|
+
imports: [
|
|
76
|
+
ForCalendar,
|
|
77
|
+
ForCalendarHeading,
|
|
78
|
+
ForCalendarPrevButton,
|
|
79
|
+
ForCalendarNextButton,
|
|
80
|
+
ForCalendarGrid,
|
|
81
|
+
ForCalendarGridHeader,
|
|
82
|
+
ForCalendarCell,
|
|
83
|
+
],
|
|
84
|
+
template: `
|
|
85
|
+
<div forCalendar [(value)]="date">
|
|
86
|
+
<header>
|
|
87
|
+
<button forCalendarPrevButton [ariaLabel]="'Previous month'">‹</button>
|
|
88
|
+
<h2 forCalendarHeading #heading="forCalendarHeading">{{ heading.label() }}</h2>
|
|
89
|
+
<button forCalendarNextButton [ariaLabel]="'Next month'">›</button>
|
|
90
|
+
</header>
|
|
91
|
+
|
|
92
|
+
<table forCalendarGrid #grid="forCalendarGrid">
|
|
93
|
+
<thead forCalendarGridHeader>
|
|
94
|
+
<tr>
|
|
95
|
+
@for (day of grid.weekDays(); track day.key) {
|
|
96
|
+
<th scope="col" [attr.aria-label]="day.long">{{ day.short }}</th>
|
|
97
|
+
}
|
|
98
|
+
</tr>
|
|
99
|
+
</thead>
|
|
100
|
+
<tbody>
|
|
101
|
+
@for (week of grid.weeks(); track week.key) {
|
|
102
|
+
<tr>
|
|
103
|
+
@for (cell of week.days; track cell.key) {
|
|
104
|
+
<td forCalendarCell class="calendar-cell" [date]="cell.date">{{ cell.label }}</td>
|
|
105
|
+
}
|
|
106
|
+
</tr>
|
|
107
|
+
}
|
|
108
|
+
</tbody>
|
|
109
|
+
</table>
|
|
110
|
+
</div>
|
|
111
|
+
`,
|
|
112
|
+
})
|
|
113
|
+
export class DatePage {
|
|
114
|
+
readonly date = signal<CalendarDate | null>(today(getLocalTimeZone()));
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
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
|
+
|
|
120
|
+
## Range selection
|
|
121
|
+
|
|
122
|
+
Set `selectionMode="range"` and bind `[(range)]` to get date-range selection. In range mode the single-date `value` is unused and stays `null`.
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<div forCalendar selectionMode="range" [(range)]="dateRange">
|
|
126
|
+
<!-- …header + grid… -->
|
|
127
|
+
</div>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Interaction model.** Click (or `Enter` / `Space`) a first cell to set the anchor; the grid enters selecting state. Click (or `Enter` / `Space`) a second cell at or after the anchor to commit the range. Clicking before the anchor re-anchors (starts over from the earlier date). No explicit Escape-to-cancel — an in-progress anchor is simply overwritten by the next click.
|
|
131
|
+
|
|
132
|
+
**Keyboard in range mode.** `Enter` / `Space` on the focused cell sets the anchor on the first press and commits on the second (same key as single mode). While selecting, arrow / `Home` / `End` / `PageUp` / `PageDown` move the keyboard focus and update the preview end (the keyboard equivalent of pointer hover).
|
|
133
|
+
|
|
134
|
+
**`min` / `max` / `isDateUnavailable`** still gate both endpoints. An unavailable or out-of-bounds date cannot become an anchor or an end.
|
|
135
|
+
|
|
136
|
+
**`minRangeLength` / `maxRangeLength`.** Constrain the inclusive day count of the committed range. A click that would violate either limit is a no-op (the anchor is preserved). Both default to `null` (unconstrained).
|
|
137
|
+
|
|
138
|
+
**Cell facets (range mode).** Four boolean `data-*` attributes are added to `[forCalendarCell]`:
|
|
139
|
+
|
|
140
|
+
| Attribute | Present when |
|
|
141
|
+
| -------------------- | ------------------------------------------------------------------------------- |
|
|
142
|
+
| `data-range-start` | Cell is the start of the effective range (committed idle, or preview selecting) |
|
|
143
|
+
| `data-range-end` | Cell is the end of the effective range |
|
|
144
|
+
| `data-in-range` | Cell is within the **committed** range, inclusive (idle only) |
|
|
145
|
+
| `data-range-preview` | Cell is within the **preview** band, inclusive (selecting only) |
|
|
146
|
+
|
|
147
|
+
`data-in-range` and `data-range-preview` are mutually exclusive. Endpoints carry both the endpoint attribute and the band attribute (inclusive). All four are absent in single mode.
|
|
148
|
+
|
|
149
|
+
```css
|
|
150
|
+
[data-in-range] {
|
|
151
|
+
background: var(--accent-light);
|
|
152
|
+
}
|
|
153
|
+
[data-range-start],
|
|
154
|
+
[data-range-end] {
|
|
155
|
+
background: var(--accent);
|
|
156
|
+
color: white;
|
|
157
|
+
}
|
|
158
|
+
[data-range-preview] {
|
|
159
|
+
background: var(--accent-faint);
|
|
160
|
+
}
|
|
161
|
+
[data-range-start]:not([data-range-end]) {
|
|
162
|
+
border-radius: 50% 0 0 50%;
|
|
163
|
+
}
|
|
164
|
+
[data-range-end]:not([data-range-start]) {
|
|
165
|
+
border-radius: 0 50% 50% 0;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**`aria-selected`** in range mode is `"true"` across every committed-range cell (inclusive). During selecting (range null), it is `"false"` everywhere.
|
|
170
|
+
|
|
171
|
+
**v1 scope.** Range mode is day-granular only — `granularity` / time is orthogonal and not supported in v1.
|
|
172
|
+
|
|
173
|
+
## Month / year navigation
|
|
174
|
+
|
|
175
|
+
`ForCalendar` exposes absolute-navigation methods so consumers can wire their own month/year `<select>` dropdowns — or any other UI — without needing a library directive.
|
|
176
|
+
|
|
177
|
+
### Navigation methods
|
|
178
|
+
|
|
179
|
+
| Method | Description |
|
|
180
|
+
| ------------------- | --------------------------------------------------------------------------------------- |
|
|
181
|
+
| `goTo(year, month)` | Set the visible month to `(year, month)` without selecting a date. `month` is **1-12**. |
|
|
182
|
+
| `goToMonth(month)` | Set the visible month within the current visible year. `month` is **1-12**. |
|
|
183
|
+
| `goToYear(year)` | Set the visible year, keeping the current visible month. |
|
|
184
|
+
|
|
185
|
+
All three methods re-apply the user's intended day-of-month (clamped to the target month's length), clamp the result into `[min, max]`, and announce the new period politely when the visible month changes. They keep DOM focus on the caller — they do not move focus into the grid. They are a no-op while the calendar is `disabled`.
|
|
186
|
+
|
|
187
|
+
### Read accessors and predicates
|
|
188
|
+
|
|
189
|
+
| API | Type | Description |
|
|
190
|
+
| ------------------------ | ---------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
191
|
+
| `visibleYear()` | `Signal<number>` | The visible month's full year (e.g. `2026`). |
|
|
192
|
+
| `visibleMonthNumber()` | `Signal<number>` | The visible month, **1-12**. |
|
|
193
|
+
| `monthOptions()` | `Signal<readonly CalendarMonthOption[]>` | Twelve localized, bounds-aware entries for the visible year. See `CalendarMonthOption`. |
|
|
194
|
+
| `isMonthDisabled(month)` | `(month: number) => boolean` | Whether every day of `month` (**1-12**) in the visible year is outside `[min, max]`. |
|
|
195
|
+
| `isYearDisabled(year)` | `(year: number) => boolean` | Whether every day of `year` is outside `[min, max]`. |
|
|
196
|
+
|
|
197
|
+
`CalendarMonthOption` has three fields: `value: number` (1-12), `label: string` (localized month name via the adapter), and `disabled: boolean`.
|
|
198
|
+
|
|
199
|
+
A month/year is "disabled" only when its **entire** span is out of range — its last day falls before `min`, or its first day falls after `max`. A non-midnight `min`/`max` keeps its boundary month/year enabled, matching the grid's existing day-level availability.
|
|
200
|
+
|
|
201
|
+
### Usage — native `<select>` dropdowns
|
|
202
|
+
|
|
203
|
+
The recommended path is `[forCalendarMonthSelect]` and `[forCalendarYearSelect]`. Apply them on `<select>` elements inside `[forCalendar]` and render the `<option>` elements yourself from the directive's signals:
|
|
204
|
+
|
|
205
|
+
```html
|
|
206
|
+
<div forCalendar [(value)]="date">
|
|
207
|
+
<header>
|
|
208
|
+
<button forCalendarPrevButton [ariaLabel]="'Previous month'">‹</button>
|
|
209
|
+
<select forCalendarMonthSelect #m="forCalendarMonthSelect">
|
|
210
|
+
@for (opt of m.options(); track opt.value) {
|
|
211
|
+
<option [value]="opt.value" [disabled]="opt.disabled">{{ opt.label }}</option>
|
|
212
|
+
}
|
|
213
|
+
</select>
|
|
214
|
+
<select forCalendarYearSelect #y="forCalendarYearSelect" [minYear]="1900" [maxYear]="2100">
|
|
215
|
+
@for (opt of y.years(); track opt.value) {
|
|
216
|
+
<option [value]="opt.value" [disabled]="opt.disabled">{{ opt.value }}</option>
|
|
217
|
+
}
|
|
218
|
+
</select>
|
|
219
|
+
<button forCalendarNextButton [ariaLabel]="'Next month'">›</button>
|
|
220
|
+
<h2 forCalendarHeading class="sr-only" #h="forCalendarHeading">{{ h.label() }}</h2>
|
|
221
|
+
</header>
|
|
222
|
+
<table forCalendarGrid>
|
|
223
|
+
…
|
|
224
|
+
</table>
|
|
225
|
+
</div>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### `ForCalendarMonthSelect` API
|
|
229
|
+
|
|
230
|
+
| API | Description |
|
|
231
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------- |
|
|
232
|
+
| `options()` | `Signal<readonly CalendarMonthOption[]>` — twelve localized, bounds-aware month options for the visible year. |
|
|
233
|
+
|
|
234
|
+
#### `ForCalendarYearSelect` API
|
|
235
|
+
|
|
236
|
+
| API | Description |
|
|
237
|
+
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
238
|
+
| `minYear` | `input<number \| null>` — lowest listed year. Defaults to `currentYear - 100` when `null`. |
|
|
239
|
+
| `maxYear` | `input<number \| null>` — highest listed year. Defaults to `currentYear + 10` when `null`. |
|
|
240
|
+
| `years()` | `Signal<readonly CalendarYearOption[]>` — years from `minYear` to `maxYear` inclusive, each `disabled` when the whole year falls outside `[min, max]`. |
|
|
241
|
+
|
|
242
|
+
The default window is **anchored to the current year** (not the visible year), so navigating far away never drops the current year off the list. Out-of-`[min, max]` entries have `disabled: true`, matching the `CalendarYearOption` shape. Both directives set the native `disabled` attribute on the `<select>` itself when the calendar is disabled.
|
|
243
|
+
|
|
244
|
+
**Keep `[forCalendarHeading]` in the DOM.** The grid's `aria-labelledby` points at the heading's id. When dropdowns replace the visible heading, keep a visually-hidden `[forCalendarHeading]` so the grid stays named — removing the heading entirely leaves `aria-labelledby` pointing at a missing element.
|
|
245
|
+
|
|
246
|
+
The lower-level hooks (`visibleMonthNumber()`, `visibleYear()`, `monthOptions()`, `goToMonth()`, `goToYear()`, `isYearDisabled()`) in the table above remain available for any other UI.
|
|
247
|
+
|
|
248
|
+
## View switching (month / year picker)
|
|
249
|
+
|
|
250
|
+
`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
|
+
|
|
252
|
+
### Pieces
|
|
253
|
+
|
|
254
|
+
| Class | Selector | Role |
|
|
255
|
+
| ------------------------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
256
|
+
| `ForCalendarViewTrigger` | `[forCalendarViewTrigger]` | Button that cycles the view: day → month → year. Auto-disabled when the calendar is disabled. |
|
|
257
|
+
| `ForCalendarMonthGrid` | `[forCalendarMonthGrid]` | 4×3 month grid (`role="grid"`). Exposes `rows()` — array of `CalendarMonthRow`, each with three `CalendarYearOption`. |
|
|
258
|
+
| `ForCalendarMonthCell` | `[forCalendarMonthCell]` | One month (`role="gridcell"`). Requires `[month]` (1–12). Click drills down to day view for that month. |
|
|
259
|
+
| `ForCalendarYearGrid` | `[forCalendarYearGrid]` | 4×3 year grid (`role="grid"`). Exposes `rows()` — array of `CalendarYearRow`, each with three `CalendarYearOption`. |
|
|
260
|
+
| `ForCalendarYearCell` | `[forCalendarYearCell]` | One year (`role="gridcell"`). Requires `[year]`. Click drills down to month view for that year. |
|
|
261
|
+
|
|
262
|
+
### `view` model — `ForCalendar`
|
|
263
|
+
|
|
264
|
+
| API | Type | Description |
|
|
265
|
+
| --------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
266
|
+
| `view` | `model<CalendarView>` (`'day' \| 'month' \| 'year'`) | Active view. Default `'day'`. `[(view)]` for two-way binding or read `cal.view()`. |
|
|
267
|
+
| `yearBlockSize` | `input<number>` | Years per year-grid page (must be a multiple of 3). Default `12`. |
|
|
268
|
+
|
|
269
|
+
### Usage
|
|
270
|
+
|
|
271
|
+
```html
|
|
272
|
+
<div forCalendar [(value)]="date" #cal="forCalendar">
|
|
273
|
+
<header>
|
|
274
|
+
<button forCalendarPrevButton [ariaLabel]="'Previous'">‹</button>
|
|
275
|
+
<button forCalendarViewTrigger #vt="forCalendarViewTrigger">{{ vt.label() }}</button>
|
|
276
|
+
<button forCalendarNextButton [ariaLabel]="'Next'">›</button>
|
|
277
|
+
<!-- keep a visually-hidden heading so the grid stays labelled -->
|
|
278
|
+
<h2 forCalendarHeading #h="forCalendarHeading" class="sr-only">{{ h.label() }}</h2>
|
|
279
|
+
</header>
|
|
280
|
+
|
|
281
|
+
@switch (cal.view()) { @case ('day') {
|
|
282
|
+
<table forCalendarGrid #grid="forCalendarGrid">
|
|
283
|
+
<thead forCalendarGridHeader>
|
|
284
|
+
<tr>
|
|
285
|
+
@for (day of grid.weekDays(); track day.key) {
|
|
286
|
+
<th scope="col" [attr.aria-label]="day.long">{{ day.narrow }}</th>
|
|
287
|
+
}
|
|
288
|
+
</tr>
|
|
289
|
+
</thead>
|
|
290
|
+
<tbody>
|
|
291
|
+
@for (week of grid.weeks(); track week.key) {
|
|
292
|
+
<tr>
|
|
293
|
+
@for (cell of week.days; track cell.key) {
|
|
294
|
+
<td forCalendarCell [date]="cell.date">{{ cell.label }}</td>
|
|
295
|
+
}
|
|
296
|
+
</tr>
|
|
297
|
+
}
|
|
298
|
+
</tbody>
|
|
299
|
+
</table>
|
|
300
|
+
} @case ('month') {
|
|
301
|
+
<table forCalendarMonthGrid #mg="forCalendarMonthGrid">
|
|
302
|
+
<tbody>
|
|
303
|
+
@for (row of mg.rows(); track row.key) {
|
|
304
|
+
<tr>
|
|
305
|
+
@for (m of row.months; track m.value) {
|
|
306
|
+
<td forCalendarMonthCell [month]="m.value">{{ m.label }}</td>
|
|
307
|
+
}
|
|
308
|
+
</tr>
|
|
309
|
+
}
|
|
310
|
+
</tbody>
|
|
311
|
+
</table>
|
|
312
|
+
} @case ('year') {
|
|
313
|
+
<table forCalendarYearGrid #yg="forCalendarYearGrid">
|
|
314
|
+
<tbody>
|
|
315
|
+
@for (row of yg.rows(); track row.key) {
|
|
316
|
+
<tr>
|
|
317
|
+
@for (y of row.years; track y.value) {
|
|
318
|
+
<td forCalendarYearCell [year]="y.value">{{ y.value }}</td>
|
|
319
|
+
}
|
|
320
|
+
</tr>
|
|
321
|
+
}
|
|
322
|
+
</tbody>
|
|
323
|
+
</table>
|
|
324
|
+
} }
|
|
325
|
+
</div>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Keyboard (month / year grids)
|
|
329
|
+
|
|
330
|
+
| Key | Behavior |
|
|
331
|
+
| -------------------------- | ----------------------------------------------------------------------- |
|
|
332
|
+
| **ArrowLeft / ArrowRight** | Previous / next cell (RTL-mirrored). |
|
|
333
|
+
| **ArrowUp / ArrowDown** | Previous / next row (three cells per row). |
|
|
334
|
+
| **Home / End** | First / last cell in the current row. |
|
|
335
|
+
| **PageUp / PageDown** | Previous / next year (month grid) or previous / next block (year grid). |
|
|
336
|
+
| **Enter / Space** | Select the focused month or year and drill down. |
|
|
337
|
+
| **Escape** | (handled by consumer via `[(view)]`). |
|
|
338
|
+
|
|
339
|
+
### Prev / next behavior per view
|
|
340
|
+
|
|
341
|
+
The prev and next buttons (`[forCalendarPrevButton]` / `[forCalendarNextButton]`) are view-aware:
|
|
342
|
+
|
|
343
|
+
| View | Prev / Next pages by |
|
|
344
|
+
| ------- | --------------------------------------------- |
|
|
345
|
+
| `day` | One month (as before). |
|
|
346
|
+
| `month` | One year. |
|
|
347
|
+
| `year` | One `yearBlockSize` block (default 12 years). |
|
|
348
|
+
|
|
349
|
+
Auto-disabled when the entire previous / next page would be outside `[min, max]`.
|
|
350
|
+
|
|
351
|
+
### Data attributes (view switching)
|
|
352
|
+
|
|
353
|
+
| Piece | Attribute | Values |
|
|
354
|
+
| ------------------------ | ------------------ | -------------------------------- |
|
|
355
|
+
| `[forCalendar]` | `data-view` | `"day"` \| `"month"` \| `"year"` |
|
|
356
|
+
| `[forCalendarMonthGrid]` | `data-view` | `"month"` (static) |
|
|
357
|
+
| `[forCalendarYearGrid]` | `data-view` | `"year"` (static) |
|
|
358
|
+
| `[forCalendarMonthCell]` | `data-selected` | present \| absent |
|
|
359
|
+
| `[forCalendarMonthCell]` | `data-today` | present \| absent |
|
|
360
|
+
| `[forCalendarMonthCell]` | `data-highlighted` | present \| absent |
|
|
361
|
+
| `[forCalendarMonthCell]` | `data-disabled` | present \| absent |
|
|
362
|
+
| `[forCalendarYearCell]` | `data-selected` | present \| absent |
|
|
363
|
+
| `[forCalendarYearCell]` | `data-today` | present \| absent |
|
|
364
|
+
| `[forCalendarYearCell]` | `data-highlighted` | present \| absent |
|
|
365
|
+
| `[forCalendarYearCell]` | `data-disabled` | present \| absent |
|
|
366
|
+
|
|
367
|
+
## Styling
|
|
368
|
+
|
|
369
|
+
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 below.
|
|
370
|
+
|
|
371
|
+
### Data attributes
|
|
372
|
+
|
|
373
|
+
| Piece | Attribute | Values |
|
|
374
|
+
| ------------------------- | -------------------- | ----------------- |
|
|
375
|
+
| `[forCalendar]` | `data-disabled` | present \| absent |
|
|
376
|
+
| `[forCalendar]` | `data-readonly` | present \| absent |
|
|
377
|
+
| `[forCalendarPrevButton]` | `data-disabled` | present \| absent |
|
|
378
|
+
| `[forCalendarNextButton]` | `data-disabled` | present \| absent |
|
|
379
|
+
| `[forCalendarCell]` | `data-selected` | present \| absent |
|
|
380
|
+
| `[forCalendarCell]` | `data-today` | present \| absent |
|
|
381
|
+
| `[forCalendarCell]` | `data-highlighted` | present \| absent |
|
|
382
|
+
| `[forCalendarCell]` | `data-disabled` | present \| absent |
|
|
383
|
+
| `[forCalendarCell]` | `data-outside-month` | present \| absent |
|
|
384
|
+
|
|
385
|
+
```css
|
|
386
|
+
.calendar-cell {
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
}
|
|
389
|
+
.calendar-cell[data-selected] {
|
|
390
|
+
background: var(--accent);
|
|
391
|
+
color: white;
|
|
392
|
+
}
|
|
393
|
+
.calendar-cell[data-today] {
|
|
394
|
+
outline: 1px solid var(--accent);
|
|
395
|
+
}
|
|
396
|
+
.calendar-cell[data-outside-month] {
|
|
397
|
+
opacity: 0.4;
|
|
398
|
+
}
|
|
399
|
+
.calendar-cell[data-disabled] {
|
|
400
|
+
cursor: default;
|
|
401
|
+
opacity: 0.3;
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Keyboard
|
|
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
|
|
430
|
+
|
|
431
|
+
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
|
+
|
|
433
|
+
```ts
|
|
434
|
+
import { ChangeDetectionStrategy, Component, afterNextRender, signal } from '@angular/core';
|
|
435
|
+
import { CalendarDate, today, getLocalTimeZone } from '@internationalized/date';
|
|
436
|
+
|
|
437
|
+
@Component({
|
|
438
|
+
selector: 'app-date',
|
|
439
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
440
|
+
/* ... */
|
|
441
|
+
})
|
|
442
|
+
export class DatePage {
|
|
443
|
+
readonly date = signal<CalendarDate | null>(null);
|
|
444
|
+
|
|
445
|
+
constructor() {
|
|
446
|
+
afterNextRender(() => this.date.set(today(getLocalTimeZone())));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
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.
|