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,338 @@
|
|
|
1
|
+
# DatePicker
|
|
2
|
+
|
|
3
|
+
Headless date picker following the [WAI-ARIA Date Picker Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/) — reinterpreted idiomatically for modern Angular: a focusable trigger that opens a floating surface wrapping a projected [`ForCalendar`](../calendar/README.md).
|
|
4
|
+
|
|
5
|
+
`ForDatePicker` is the root **and** the form value — it implements `FormValueControl<D | null>` from `@angular/forms/signals`, so it auto-wires with `[formField]`. The trigger is the focusable control that carries `name` / `disabled` / `invalid`; selection state flows root → projected calendar via `[(value)]`. The library reuses its existing overlay stack (trigger-anchored Popover positioning, dismissable layer, return-focus) rather than re-implementing positioning, dismissal, or focus return — and the modal opt-in routes through the shared modal shell (focus trap + inert background + scroll lock).
|
|
6
|
+
|
|
7
|
+
## Date adapter — pick one (required)
|
|
8
|
+
|
|
9
|
+
All date math and formatting go through a `DateAdapter<D>`, shared with `ForCalendar`, 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
|
+
## Pieces
|
|
17
|
+
|
|
18
|
+
| Class | Selector | Role |
|
|
19
|
+
| ---------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
20
|
+
| `ForDatePicker` | `[forDatePicker]` | Root + `FormValueControl<D \| null>`. Owns `value`, `open`, the shared context, and the close-on-select bridge. |
|
|
21
|
+
| `ForDatePickerTrigger` | `[forDatePickerTrigger]` | The focusable button (`aria-haspopup="dialog"`). Opens the surface; carries the form-control ARIA state. |
|
|
22
|
+
| `ForDatePickerContent` | `[forDatePickerContent]` | The floating `role="dialog"` surface. Non-modal popover by default; modal dialog when `[modal]`. |
|
|
23
|
+
| `ForDatePickerValue` | `[forDatePickerValue]` | Renders the formatted value (or the placeholder) inside the trigger, via the adapter's `format`. |
|
|
24
|
+
| `ForDatePickerAnchor` | `[forDatePickerAnchor]` | Optional. Positions the surface against this element instead of the trigger — wrap a decorated field box so it aligns to the visible field. See [Anchoring to a field box](#anchoring-to-a-field-box). |
|
|
25
|
+
|
|
26
|
+
## Inputs / models — `ForDatePicker`
|
|
27
|
+
|
|
28
|
+
| API | Type | Description |
|
|
29
|
+
| ------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `value` | `model<D \| null>` | Two-way bindable selected date. `(valueChange)` fires only on internal commits. Default `null`. |
|
|
31
|
+
| `open` | `model<boolean>` | Two-way bindable surface visibility. `(openChange)` fires only on internal transitions. Default `false`. |
|
|
32
|
+
| `minDate` | `input<D \| null>` | Minimum selectable date (inclusive). Forward to the projected calendar's `[min]`. Default `null`. |
|
|
33
|
+
| `maxDate` | `input<D \| null>` | Maximum selectable date (inclusive). Forward to the projected calendar's `[max]`. Default `null`. |
|
|
34
|
+
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate. Forward to the projected calendar's `[isDateUnavailable]`. Default `() => false`. |
|
|
35
|
+
| `closeOnSelect` | `input<boolean>` | Close the surface after a date is picked. Honoured only at `granularity="day"`. Default `true`. |
|
|
36
|
+
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision. `'day'` (default) is a pure date picker; coarser-than-day off composes a time field. |
|
|
37
|
+
| `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the value display (and typically the projected `[forTimeField]`). Default `null` → locale. |
|
|
38
|
+
| `modal` | `input<boolean>` | Trap focus + inert background + scroll lock (centered dialog) instead of an anchored popover. Default `false`. |
|
|
39
|
+
| `dismissible` | `input<boolean>` | Escape / outside-pointer dismiss the surface. Default `true`. |
|
|
40
|
+
| `returnFocus` | `input<boolean>` | Return focus to the trigger on close. Default `true`. |
|
|
41
|
+
| `formatOptions` | `input<Intl.DateTimeFormatOptions>` | Options for the text rendered by `[forDatePickerValue]`. Default `{ year: 'numeric', month: 'long', day: 'numeric' }`. |
|
|
42
|
+
| `placeholder` | `input<string>` | Fallback text for `[forDatePickerValue]` when empty. Default `''`. |
|
|
43
|
+
| `side` / `align` | `input` | Anchored placement (popover mode only). Defaults `'bottom'` / `'start'`. |
|
|
44
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. Default `null` resolves the ambient direction; reflected to the host `dir`. |
|
|
45
|
+
|
|
46
|
+
Plus the shared `FormUiControl` inputs from the base (`disabled`, `readonly`, `required`, `invalid`, `pending`, `dirty`, `name`, `errors`, and the `touched` model) and the floating tunables (`sideOffset`, `alignOffset`, `avoidCollisions`, `collisionPadding`, `sticky`, `hideWhenDetached`).
|
|
47
|
+
|
|
48
|
+
> **Why `minDate` / `maxDate`, not `min` / `max`?** `ForDatePicker` is a `FormValueControl`, and `FormUiControl` reserves `min` / `max` for numeric validators (`InputSignal<number | undefined>`). A date-typed `min` / `max` would break that contract, so the date bounds use the `*Date` suffix. (`ForCalendar` is not a form control, so it keeps `min` / `max`.)
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
54
|
+
import { CalendarDate } from '@internationalized/date';
|
|
55
|
+
import {
|
|
56
|
+
ForCalendar,
|
|
57
|
+
ForCalendarCell,
|
|
58
|
+
ForCalendarGrid,
|
|
59
|
+
ForCalendarGridHeader,
|
|
60
|
+
ForCalendarHeading,
|
|
61
|
+
ForCalendarNextButton,
|
|
62
|
+
ForCalendarPrevButton,
|
|
63
|
+
} from 'forty-cdk/calendar';
|
|
64
|
+
import {
|
|
65
|
+
ForDatePicker,
|
|
66
|
+
ForDatePickerContent,
|
|
67
|
+
ForDatePickerTrigger,
|
|
68
|
+
ForDatePickerValue,
|
|
69
|
+
} from 'forty-cdk/date-picker';
|
|
70
|
+
|
|
71
|
+
@Component({
|
|
72
|
+
selector: 'app-dob',
|
|
73
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
74
|
+
imports: [
|
|
75
|
+
ForDatePicker,
|
|
76
|
+
ForDatePickerTrigger,
|
|
77
|
+
ForDatePickerValue,
|
|
78
|
+
ForDatePickerContent,
|
|
79
|
+
ForCalendar,
|
|
80
|
+
ForCalendarHeading,
|
|
81
|
+
ForCalendarPrevButton,
|
|
82
|
+
ForCalendarNextButton,
|
|
83
|
+
ForCalendarGrid,
|
|
84
|
+
ForCalendarGridHeader,
|
|
85
|
+
ForCalendarCell,
|
|
86
|
+
],
|
|
87
|
+
template: `
|
|
88
|
+
<div
|
|
89
|
+
forDatePicker
|
|
90
|
+
[(value)]="date"
|
|
91
|
+
[(open)]="open"
|
|
92
|
+
[minDate]="min"
|
|
93
|
+
[maxDate]="max"
|
|
94
|
+
name="dob"
|
|
95
|
+
[ariaLabel]="'Choose date'"
|
|
96
|
+
#picker="forDatePicker"
|
|
97
|
+
>
|
|
98
|
+
<button forDatePickerTrigger class="date-picker-trigger">
|
|
99
|
+
<span forDatePickerValue class="date-picker-value" [placeholder]="'Pick a date'"></span>
|
|
100
|
+
</button>
|
|
101
|
+
|
|
102
|
+
@if (open()) {
|
|
103
|
+
<div forDatePickerContent animate.leave="fade-out">
|
|
104
|
+
<div
|
|
105
|
+
forCalendar
|
|
106
|
+
[(value)]="date"
|
|
107
|
+
[min]="picker.minDate()"
|
|
108
|
+
[max]="picker.maxDate()"
|
|
109
|
+
[isDateUnavailable]="picker.isDateUnavailable()"
|
|
110
|
+
>
|
|
111
|
+
<header>
|
|
112
|
+
<button forCalendarPrevButton [ariaLabel]="'Previous month'">‹</button>
|
|
113
|
+
<h2 forCalendarHeading #heading="forCalendarHeading">{{ heading.label() }}</h2>
|
|
114
|
+
<button forCalendarNextButton [ariaLabel]="'Next month'">›</button>
|
|
115
|
+
</header>
|
|
116
|
+
|
|
117
|
+
<table forCalendarGrid #grid="forCalendarGrid">
|
|
118
|
+
<thead forCalendarGridHeader>
|
|
119
|
+
<tr>
|
|
120
|
+
@for (day of grid.weekDays(); track day.key) {
|
|
121
|
+
<th scope="col" [attr.aria-label]="day.long">{{ day.short }}</th>
|
|
122
|
+
}
|
|
123
|
+
</tr>
|
|
124
|
+
</thead>
|
|
125
|
+
<tbody>
|
|
126
|
+
@for (week of grid.weeks(); track week.key) {
|
|
127
|
+
<tr>
|
|
128
|
+
@for (cell of week.days; track cell.key) {
|
|
129
|
+
<td forCalendarCell [date]="cell.date">{{ cell.label }}</td>
|
|
130
|
+
}
|
|
131
|
+
</tr>
|
|
132
|
+
}
|
|
133
|
+
</tbody>
|
|
134
|
+
</table>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
}
|
|
138
|
+
</div>
|
|
139
|
+
`,
|
|
140
|
+
})
|
|
141
|
+
export class DobPage {
|
|
142
|
+
readonly date = signal<CalendarDate | null>(null);
|
|
143
|
+
readonly open = signal(false);
|
|
144
|
+
readonly min: CalendarDate | null = null;
|
|
145
|
+
readonly max: CalendarDate | null = null;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Bind the projected `[forCalendar]` to the picker: `[(value)]` to the same date signal, and forward `[min]` / `[max]` / `[isDateUnavailable]` from the picker's accessors (`#picker="forDatePicker"`). The picker observes the calendar's selection through a `contentChild` query — it never mutates the calendar — so picking a date sets the value, flips `touched`, and (when `closeOnSelect`) closes the surface.
|
|
150
|
+
|
|
151
|
+
The library is styleless: presence in the DOM is the consumer's job (`@if (open())`), and `animate.enter` / `animate.leave` drive transitions. Style the `data-state="open" | "closed"` hooks (root + trigger + content) and `[data-disabled]` yourself.
|
|
152
|
+
|
|
153
|
+
## Triggers stamped from outside-declared templates
|
|
154
|
+
|
|
155
|
+
Angular resolves `ng-template` DI at the template's **declaration** site, not where it is stamped. A `[forDatePickerTrigger]` declared in a template outside the root throws the orphan error even when the template is rendered inside the root via `ngTemplateOutlet`. For that case the selector attribute accepts the root reference as a value, `routerLink`-style — grab it with `#root="forDatePicker"` and pass it through the outlet context. The bare valueless attribute keeps resolving via DI.
|
|
156
|
+
|
|
157
|
+
```html
|
|
158
|
+
<div forDatePicker #root="forDatePicker" [(value)]="date">
|
|
159
|
+
<ng-container *ngTemplateOutlet="trig; context: { root }" />
|
|
160
|
+
@if (root.open()) {
|
|
161
|
+
<div forDatePickerContent>…</div>
|
|
162
|
+
}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<ng-template #trig let-root="root">
|
|
166
|
+
<button [forDatePickerTrigger]="root">
|
|
167
|
+
<span forDatePickerValue>Pick a date</span>
|
|
168
|
+
</button>
|
|
169
|
+
</ng-template>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Anchoring to a field box
|
|
173
|
+
|
|
174
|
+
By default the surface is positioned against `[forDatePickerTrigger]`. When the trigger lives inside a decorated field box — padding, a prefix icon, a clear / chevron button — anchoring to the inner button offsets the surface from the visible field's edge. Wrap the field box in `[forDatePickerAnchor]` so floating-ui positions the surface against the box instead:
|
|
175
|
+
|
|
176
|
+
```html
|
|
177
|
+
<div forDatePicker #picker="forDatePicker" [(value)]="date">
|
|
178
|
+
<div forDatePickerAnchor class="field-box">
|
|
179
|
+
<icon name="calendar" />
|
|
180
|
+
<button forDatePickerTrigger>
|
|
181
|
+
<span forDatePickerValue placeholder="Pick a date"></span>
|
|
182
|
+
</button>
|
|
183
|
+
<button class="clear" (click)="date.set(null)">×</button>
|
|
184
|
+
</div>
|
|
185
|
+
@if (picker.open()) {
|
|
186
|
+
<div forDatePickerContent>
|
|
187
|
+
<div forCalendar [(value)]="date"><!-- …header + grid… --></div>
|
|
188
|
+
</div>
|
|
189
|
+
}
|
|
190
|
+
</div>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`[forDatePickerAnchor]` changes **only** positioning. The trigger keeps `aria-haspopup` / `aria-expanded` / `aria-controls`, the click toggle, focus return on close, and its exemption from outside-pointer dismissal. Without an anchor the surface falls back to the trigger, so existing markup is unaffected. At most one `[forDatePickerAnchor]` per `[forDatePicker]` — a second one throws `[forty-cdk/date-picker]`. (A calendar has its own intrinsic width and ignores `--for-anchor-width`, so the anchor mainly affects start / side alignment to the box edge.)
|
|
194
|
+
|
|
195
|
+
## Modal vs non-modal
|
|
196
|
+
|
|
197
|
+
By default the surface is a **non-modal popover**: anchored to the trigger, no background inert, no scroll lock. Set `[modal]="true"` to route through the modal shell instead: focus is trapped inside the dialog, the background is inert, and body scroll is locked (a centered dialog you position with CSS, not trigger-anchored). Either way the surface is `role="dialog"` and `aria-haspopup="dialog"`-anchored; modal mode adds `aria-modal="true"`.
|
|
198
|
+
|
|
199
|
+
The mode is read once when the surface mounts (it is structurally different per mode), so toggle `modal` while the surface is closed.
|
|
200
|
+
|
|
201
|
+
## Date-time picker (`granularity > 'day'`)
|
|
202
|
+
|
|
203
|
+
Set `granularity` to `'hour'`, `'minute'`, or `'second'` to turn the picker into a **date-time picker**: project a [`ForTimeField`](../time-field/README.md) beside the calendar and the value gains a time component. This needs a **time-capable** adapter — `provideNativeDateAdapter()` (`Date`) or `provideInternationalizedDateTimeAdapter()` (`CalendarDateTime`); the day-only `provideInternationalizedDateAdapter()` (`CalendarDate`) throws.
|
|
204
|
+
|
|
205
|
+
Bind the calendar **and** the time field **one-way** to `picker.value()` (not `[(value)]`). The picker is the single source of truth: when one-way bound to a timed value the calendar preserves the time-of-day on its own selection, and the picker re-grafts the previously entered time as a defensive fallback for the case where the calendar value was null or midnight (reading its own value, which the one-way children never clobber); a time-field edit emits a full date-time the picker mirrors in. A date-time picker never closes on a calendar selection, so the user can go on to set the time.
|
|
206
|
+
|
|
207
|
+
```html
|
|
208
|
+
<div
|
|
209
|
+
forDatePicker
|
|
210
|
+
[(value)]="when"
|
|
211
|
+
[(open)]="open"
|
|
212
|
+
granularity="minute"
|
|
213
|
+
[hourCycle]="24"
|
|
214
|
+
#picker="forDatePicker"
|
|
215
|
+
>
|
|
216
|
+
<button forDatePickerTrigger class="date-picker-trigger">
|
|
217
|
+
<span forDatePickerValue class="date-picker-value" [placeholder]="'Pick date & time'"></span>
|
|
218
|
+
</button>
|
|
219
|
+
|
|
220
|
+
@if (open()) {
|
|
221
|
+
<div forDatePickerContent>
|
|
222
|
+
<div forCalendar [value]="picker.value()" [min]="picker.minDate()" [max]="picker.maxDate()">
|
|
223
|
+
<!-- …calendar header + grid… -->
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div
|
|
227
|
+
forTimeField
|
|
228
|
+
[value]="picker.value()"
|
|
229
|
+
[hourCycle]="picker.hourCycle()"
|
|
230
|
+
#field="forTimeField"
|
|
231
|
+
>
|
|
232
|
+
@for (seg of field.segments(); track seg.id) { @if (seg.isLiteral) {
|
|
233
|
+
<span forTimeFieldLiteral>{{ seg.text }}</span>
|
|
234
|
+
} @else {
|
|
235
|
+
<span forTimeFieldSegment [segment]="seg.type!">{{ seg.text }}</span>
|
|
236
|
+
} }
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
}
|
|
240
|
+
</div>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The value display (`[forDatePickerValue]`) automatically appends the time to its formatting when `granularity > 'day'` and you haven't set time fields in `formatOptions`.
|
|
244
|
+
|
|
245
|
+
## Range selection
|
|
246
|
+
|
|
247
|
+
Set `selectionMode="range"` on both the picker root and the projected calendar and bind `[(range)]` to a `CalendarDateRange<D> | null` signal.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { type CalendarDateRange } from 'forty-cdk/calendar';
|
|
251
|
+
|
|
252
|
+
readonly dateRange = signal<CalendarDateRange<CalendarDate> | null>(null);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```html
|
|
256
|
+
<div
|
|
257
|
+
forDatePicker
|
|
258
|
+
selectionMode="range"
|
|
259
|
+
[(range)]="dateRange"
|
|
260
|
+
[(open)]="open"
|
|
261
|
+
[ariaLabel]="'Choose date range'"
|
|
262
|
+
>
|
|
263
|
+
<button forDatePickerTrigger>
|
|
264
|
+
<span forDatePickerValue [placeholder]="'Pick a range'"></span>
|
|
265
|
+
</button>
|
|
266
|
+
|
|
267
|
+
@if (open()) {
|
|
268
|
+
<div forDatePickerContent>
|
|
269
|
+
<div forCalendar selectionMode="range" [(range)]="dateRange">
|
|
270
|
+
<!-- …header + grid… -->
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
}
|
|
274
|
+
</div>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**`formattedValue` in range mode.** `[forDatePickerValue]` renders `start – end` using the adapter's `format` for each endpoint. The separator defaults to `' – '` and is configurable via `[rangeSeparator]`.
|
|
278
|
+
|
|
279
|
+
**`closeOnSelect` in range mode.** The surface closes when a full range is committed (both endpoints set). Clicking the first cell (anchor) keeps the surface open; clicking the second (end) commits and closes. Set `[closeOnSelect]="false"` to keep it open after commit.
|
|
280
|
+
|
|
281
|
+
**v1 scope.** Range mode is day-granular only (`granularity` / time is not supported in v1). The `[(range)]` model is not a `FormValueControl` target — it does not integrate with `[formField]` in v1. `minRangeLength` / `maxRangeLength` are configured on the projected `[forCalendar]` directly.
|
|
282
|
+
|
|
283
|
+
| New input / model | Type | Description |
|
|
284
|
+
| ----------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
285
|
+
| `selectionMode` | `input<'single' \| 'range'>` | `'single'` keeps the existing `value` flow. `'range'` switches to range mode. |
|
|
286
|
+
| `range` | `model<CalendarDateRange<D> \| null>` | Two-way bindable committed range. `(rangeChange)` fires only on commit / clear. Default `null`. |
|
|
287
|
+
| `rangeSeparator` | `input<string>` | String placed between start and end in the formatted display. Default `' – '`. |
|
|
288
|
+
|
|
289
|
+
## Styling
|
|
290
|
+
|
|
291
|
+
forty-cdk ships no styles. Add your own class to each piece — the `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes below.
|
|
292
|
+
|
|
293
|
+
### Data attributes
|
|
294
|
+
|
|
295
|
+
| Piece | Attribute | Values |
|
|
296
|
+
| ------------------------ | ------------------ | ------------------ |
|
|
297
|
+
| `[forDatePicker]` | `data-state` | `open` \| `closed` |
|
|
298
|
+
| `[forDatePicker]` | `data-disabled` | present \| absent |
|
|
299
|
+
| `[forDatePickerTrigger]` | `data-state` | `open` \| `closed` |
|
|
300
|
+
| `[forDatePickerTrigger]` | `data-disabled` | present \| absent |
|
|
301
|
+
| `[forDatePickerContent]` | `data-state` | `open` \| `closed` |
|
|
302
|
+
| `[forDatePickerValue]` | `data-placeholder` | present \| absent |
|
|
303
|
+
|
|
304
|
+
> `[forDatePickerContent]` is portaled to `document.body`, so it lives outside your component's view-encapsulated styles. Style it with **global CSS** (or a class you pass through) rather than component-scoped rules — see [Styling floating content](../../../../../docs/styling-floating-content.md). In non-modal (anchored) mode the surface also exposes the shared positioner custom properties (`--for-anchor-width` / `--for-anchor-height`, `--for-available-width` / `--for-available-height`, `--for-content-transform-origin`); that same guide tabulates the full set.
|
|
305
|
+
|
|
306
|
+
```css
|
|
307
|
+
.date-picker-trigger .date-picker-value[data-placeholder] {
|
|
308
|
+
color: var(--muted-foreground);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.date-picker-trigger .chevron {
|
|
312
|
+
transition: transform 150ms;
|
|
313
|
+
}
|
|
314
|
+
.date-picker-trigger[data-state='open'] .chevron {
|
|
315
|
+
transform: rotate(180deg);
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Keyboard
|
|
320
|
+
|
|
321
|
+
| Key | Behavior |
|
|
322
|
+
| ---------------------------- | ------------------------------------------------------------------------- |
|
|
323
|
+
| **Enter / Space** on trigger | Open the surface (native button activation). |
|
|
324
|
+
| **Escape** | Dismiss the surface and return focus to the trigger (when `dismissible`). |
|
|
325
|
+
|
|
326
|
+
Inside the surface, the projected `ForCalendar` owns the full grid keyboard map (arrows / `Home` / `End` / `PageUp` / `PageDown` / `Enter` / `Space`). On open, focus lands on the calendar's focused cell (`value ?? today`) in non-modal mode, or the first focusable element in modal mode.
|
|
327
|
+
|
|
328
|
+
## Accessibility notes
|
|
329
|
+
|
|
330
|
+
- **`aria-haspopup="dialog"`** on the trigger, with `aria-expanded` reflecting `open()` and `aria-controls` pointing at the surface while open.
|
|
331
|
+
- **`role="dialog"`** on the surface, named by `[ariaLabel]` (or `aria-labelledby` the trigger when no label is set). `aria-modal="true"` only in modal mode (truthy-only).
|
|
332
|
+
- **Form-control ARIA** (`aria-disabled` / `aria-readonly` / `aria-required` / `aria-invalid` / `aria-busy`) is reflected on the focusable trigger so assistive tech announces validity on the element that takes focus.
|
|
333
|
+
- **Focus management**: focus enters the surface on open (the calendar's roving cell in non-modal mode) and returns to the trigger on close, both vetoable via `(autoFocusOnOpen)` / `(autoFocusOnClose)`.
|
|
334
|
+
- **Dismissal**: Escape (`(escapeKeyDown)`) and outside-pointer (`(pointerDownOutside)` / `(interactOutside)`) close the surface, each vetoable.
|
|
335
|
+
|
|
336
|
+
## Wrapping in a design system
|
|
337
|
+
|
|
338
|
+
Both supported wrapper patterns — `hostDirectives` with the exported `FOR_DATE_PICKER_HOST_DIRECTIVE_INPUTS` / `FOR_DATE_PICKER_HOST_DIRECTIVE_OUTPUTS` name tuples, and subclassing — are documented in [Wrapping form primitives](../../../../../docs/wrapping-form-primitives.md).
|