forty-cdk 0.21.1 → 0.23.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/README.md +57 -35
- package/accordion/README.md +2 -2
- package/aspect-ratio/README.md +1 -1
- package/breakpoints/README.md +22 -1
- package/calendar/README.md +3 -3
- package/combobox/README.md +10 -10
- package/context-menu/README.md +3 -3
- package/date-field/README.md +72 -0
- package/date-picker/README.md +8 -7
- package/dialog/README.md +1 -1
- package/disclosure/README.md +1 -1
- package/drag-drop/README.md +3 -4
- package/drawer/README.md +2 -2
- package/dropdown-menu/README.md +5 -5
- package/fesm2022/forty-cdk-accordion.mjs +154 -145
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -1
- package/fesm2022/forty-cdk-aspect-ratio.mjs +43 -43
- package/fesm2022/forty-cdk-avatar.mjs +166 -119
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -1
- package/fesm2022/forty-cdk-breadcrumbs.mjs +70 -70
- package/fesm2022/forty-cdk-breakpoints.mjs +66 -60
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -1
- package/fesm2022/forty-cdk-button.mjs +152 -154
- package/fesm2022/forty-cdk-button.mjs.map +1 -1
- package/fesm2022/forty-cdk-calendar.mjs +594 -589
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
- package/fesm2022/forty-cdk-carousel.mjs +351 -342
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -1
- package/fesm2022/forty-cdk-checkbox.mjs +118 -113
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-combobox.mjs +1354 -1397
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
- package/fesm2022/forty-cdk-context-menu.mjs +286 -330
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-core-overlay.mjs +4748 -0
- package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +3621 -8510
- package/fesm2022/forty-cdk-core.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-field.mjs +851 -230
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-picker.mjs +639 -645
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-dialog.mjs +278 -269
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
- package/fesm2022/forty-cdk-disclosure.mjs +73 -68
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -1
- package/fesm2022/forty-cdk-drag-drop.mjs +342 -332
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/forty-cdk-drawer.mjs +978 -902
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
- package/fesm2022/forty-cdk-dropdown-menu.mjs +232 -278
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-field.mjs +229 -221
- package/fesm2022/forty-cdk-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-fieldset.mjs +113 -109
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -1
- package/fesm2022/forty-cdk-file-upload.mjs +95 -90
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -1
- package/fesm2022/forty-cdk-hover-card.mjs +204 -199
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -1
- package/fesm2022/forty-cdk-input.mjs +129 -129
- package/fesm2022/forty-cdk-internationalized-date.mjs +90 -90
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +398 -392
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-menu.mjs +751 -784
- package/fesm2022/forty-cdk-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-menubar.mjs +461 -464
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
- package/fesm2022/forty-cdk-meter.mjs +73 -68
- package/fesm2022/forty-cdk-meter.mjs.map +1 -1
- package/fesm2022/forty-cdk-navigation-menu.mjs +502 -553
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-number-input.mjs +357 -352
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-otp-input.mjs +185 -180
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-pagination.mjs +149 -144
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -1
- package/fesm2022/forty-cdk-pane-resizer.mjs +140 -140
- package/fesm2022/forty-cdk-popover.mjs +270 -265
- package/fesm2022/forty-cdk-popover.mjs.map +1 -1
- package/fesm2022/forty-cdk-progress.mjs +101 -96
- package/fesm2022/forty-cdk-progress.mjs.map +1 -1
- package/fesm2022/forty-cdk-radio-group.mjs +173 -163
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
- package/fesm2022/forty-cdk-scroll-area.mjs +307 -294
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -1
- package/fesm2022/forty-cdk-search.mjs +185 -180
- package/fesm2022/forty-cdk-search.mjs.map +1 -1
- package/fesm2022/forty-cdk-select.mjs +741 -796
- package/fesm2022/forty-cdk-select.mjs.map +1 -1
- package/fesm2022/forty-cdk-separator.mjs +33 -33
- package/fesm2022/forty-cdk-shared.mjs +5 -4
- package/fesm2022/forty-cdk-shared.mjs.map +1 -1
- package/fesm2022/forty-cdk-slider.mjs +274 -258
- package/fesm2022/forty-cdk-slider.mjs.map +1 -1
- package/fesm2022/forty-cdk-stepper.mjs +328 -319
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
- package/fesm2022/forty-cdk-switch.mjs +65 -65
- package/fesm2022/forty-cdk-table-virtualization.mjs +132 -123
- package/fesm2022/forty-cdk-table-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk-table.mjs +1752 -1732
- package/fesm2022/forty-cdk-table.mjs.map +1 -1
- package/fesm2022/forty-cdk-tabs.mjs +149 -129
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-field.mjs +878 -231
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-picker.mjs +333 -360
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-toast.mjs +599 -607
- package/fesm2022/forty-cdk-toast.mjs.map +1 -1
- package/fesm2022/forty-cdk-toggle.mjs +232 -227
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -1
- package/fesm2022/forty-cdk-toolbar.mjs +147 -136
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -1
- package/fesm2022/forty-cdk-tooltip.mjs +268 -263
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
- package/fesm2022/forty-cdk-tree.mjs +702 -586
- package/fesm2022/forty-cdk-tree.mjs.map +1 -1
- package/fesm2022/forty-cdk-virtual-reorder.mjs +75 -69
- package/fesm2022/forty-cdk-virtual-reorder.mjs.map +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs +163 -159
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk-visually-hidden.mjs +4 -4
- package/fesm2022/forty-cdk.mjs +3 -3
- package/hover-card/README.md +13 -13
- package/internationalized-date/README.md +1 -1
- package/menu/README.md +12 -11
- package/menubar/README.md +8 -8
- package/package.json +5 -9
- package/popover/README.md +13 -13
- package/select/README.md +43 -14
- package/shared/README.md +2 -29
- package/table/README.md +6 -6
- package/time-field/README.md +77 -0
- package/time-picker/README.md +31 -2
- package/tooltip/README.md +13 -18
- package/tree/README.md +54 -26
- package/types/forty-cdk-accordion.d.ts +3 -4
- package/types/forty-cdk-avatar.d.ts +35 -11
- package/types/forty-cdk-breakpoints.d.ts +1 -0
- package/types/forty-cdk-button.d.ts +1 -3
- package/types/forty-cdk-calendar.d.ts +1 -1
- package/types/forty-cdk-carousel.d.ts +29 -18
- package/types/forty-cdk-combobox.d.ts +167 -155
- package/types/forty-cdk-context-menu.d.ts +33 -64
- package/types/forty-cdk-core-overlay.d.ts +3596 -0
- package/types/forty-cdk-core.d.ts +866 -4204
- package/types/forty-cdk-date-field.d.ts +415 -38
- package/types/forty-cdk-date-picker.d.ts +65 -87
- package/types/forty-cdk-dialog.d.ts +8 -9
- package/types/forty-cdk-disclosure.d.ts +1 -1
- package/types/forty-cdk-drag-drop.d.ts +2 -2
- package/types/forty-cdk-drawer.d.ts +9 -10
- package/types/forty-cdk-dropdown-menu.d.ts +25 -59
- package/types/forty-cdk-field.d.ts +2 -3
- package/types/forty-cdk-hover-card.d.ts +2 -1
- package/types/forty-cdk-internationalized-date.d.ts +2 -2
- package/types/forty-cdk-listbox.d.ts +7 -0
- package/types/forty-cdk-menu.d.ts +58 -116
- package/types/forty-cdk-menubar.d.ts +71 -100
- package/types/forty-cdk-navigation-menu.d.ts +54 -110
- package/types/forty-cdk-popover.d.ts +4 -3
- package/types/forty-cdk-radio-group.d.ts +1 -1
- package/types/forty-cdk-scroll-area.d.ts +3 -0
- package/types/forty-cdk-select.d.ts +163 -103
- package/types/forty-cdk-shared.d.ts +2 -1
- package/types/forty-cdk-slider.d.ts +5 -2
- package/types/forty-cdk-stepper.d.ts +2 -3
- package/types/forty-cdk-table.d.ts +315 -331
- package/types/forty-cdk-tabs.d.ts +15 -0
- package/types/forty-cdk-time-field.d.ts +441 -36
- package/types/forty-cdk-time-picker.d.ts +22 -44
- package/types/forty-cdk-toast.d.ts +14 -16
- package/types/forty-cdk-toolbar.d.ts +6 -0
- package/types/forty-cdk-tooltip.d.ts +2 -1
- package/types/forty-cdk-tree.d.ts +120 -78
- package/types/forty-cdk-virtual-reorder.d.ts +3 -3
- package/types/forty-cdk-virtualization.d.ts +2 -3
- package/types/forty-cdk-visually-hidden.d.ts +1 -1
- package/visually-hidden/README.md +47 -0
- package/date-range-field/README.md +0 -253
- package/fesm2022/forty-cdk-date-range-field.mjs +0 -656
- package/fesm2022/forty-cdk-date-range-field.mjs.map +0 -1
- package/fesm2022/forty-cdk-time-range-field.mjs +0 -696
- package/fesm2022/forty-cdk-time-range-field.mjs.map +0 -1
- package/time-range-field/README.md +0 -263
- package/types/forty-cdk-date-range-field.d.ts +0 -432
- package/types/forty-cdk-time-range-field.d.ts +0 -467
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Headless / styleless UI primitives for Angular with WAI-ARIA accessibility built
|
|
|
4
4
|
Designed from the ground up for modern Angular — the API is built around signals, standalone
|
|
5
5
|
directives, and dependency-injection composition.
|
|
6
6
|
|
|
7
|
+
**Browsing?** The [documentation site](https://tutkli.github.io/forty-cdk/) renders every primitive with live examples.
|
|
8
|
+
|
|
7
9
|
**New here?** [Your first overlay](../../docs/your-first-overlay.md) walks one Popover from empty markup to styled-and-animated and explains the two concepts every overlay shares: the `@if` / open-state model and the portal → global CSS requirement.
|
|
8
10
|
|
|
9
11
|
**Styling these primitives?** [Styling forty-cdk](../../docs/styling.md) explains the three hooks you style against — your own class (not the directive selector), `data-*` state attributes, and `--for-*` custom properties — and links to each primitive's styling reference.
|
|
@@ -18,33 +20,54 @@ npm install forty-cdk
|
|
|
18
20
|
|
|
19
21
|
Required:
|
|
20
22
|
|
|
21
|
-
- `@angular/common` `^22.0.
|
|
22
|
-
- `@angular/core` `^22.0.
|
|
23
|
+
- `@angular/common` `^22.0.1`
|
|
24
|
+
- `@angular/core` `^22.0.1`
|
|
23
25
|
|
|
24
26
|
Optional — install only if you use the matching entry point / primitives:
|
|
25
27
|
|
|
26
28
|
| Peer | Needed by |
|
|
27
29
|
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
28
|
-
| `@angular/forms` `^22.0.
|
|
29
|
-
| `@internationalized/date` `^3.0.0` | The `forty-cdk/internationalized-date` entry point (`InternationalizedDateAdapter`, `InternationalizedDateTimeAdapter`). The date/time primitives themselves only
|
|
30
|
-
|
|
31
|
-
`@angular/forms/signals` is stable as of Angular 22, so the peer follows the standard major range (`^22.0.0`).
|
|
30
|
+
| `@angular/forms` `^22.0.1` | Form-control primitives (`Switch`, `Checkbox`, `RadioGroup`, `Listbox`, `Select`, `Slider`, `Combobox`, …). They implement `FormValueControl` / `FormCheckboxControl` from `@angular/forms/signals` for `[formField]` auto-wiring. The contract is type-only, so the published bundle never references the package — consumers using only non-form primitives can skip it. |
|
|
31
|
+
| `@internationalized/date` `^3.0.0` | The `forty-cdk/internationalized-date` entry point (`InternationalizedDateAdapter`, `InternationalizedDateTimeAdapter`). The date/time primitives themselves depend only on the abstract `DateAdapter` contract from `forty-cdk/shared` — install this peer only when you import that entry point. |
|
|
32
32
|
|
|
33
33
|
### Regular dependencies
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Two packages are regular dependencies, installed automatically and never declared as peers, because nothing of either crosses the public API by value. Both tree-shake out of a bundle that imports no primitive using them.
|
|
36
|
+
|
|
37
|
+
| Dependency | Used by |
|
|
38
|
+
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `@floating-ui/dom` | Positioning for the anchored overlays — `Tooltip`, `Popover`, `Menu`, `Combobox`, `Select`, `Date Picker`, `Time Picker`, `Hover Card`. |
|
|
40
|
+
| `@tanstack/virtual-core` | The windowing core behind `forty-cdk/virtualization`, and therefore `forty-cdk/table-virtualization` and `forty-cdk/virtual-reorder`. |
|
|
36
41
|
|
|
37
42
|
## Entry points
|
|
38
43
|
|
|
39
44
|
**The package name itself exports nothing.** `import { … } from 'forty-cdk'` resolves to no symbol, and your editor will not auto-import anything under the bare package name — by design, so that every symbol has exactly one import path. There are three specifiers you do import from:
|
|
40
45
|
|
|
41
|
-
| Specifier | What it exports
|
|
42
|
-
| ---------------------------------- |
|
|
43
|
-
| `forty-cdk/<primitive>` | The primitive's directives, components, context tokens and defaults provider — `ForDialog` from `forty-cdk/dialog`, `ForAccordion` from `forty-cdk/accordion`, and so on for every entry in the tables below.
|
|
44
|
-
| [`forty-cdk/shared`](shared) | The cross-primitive contract types a primitive's public API references — `WritingDirection`, `VetoableEvent`, `DateAdapter`, `FloatingSide`, … — declared once and published once.
|
|
45
|
-
| `forty-cdk/internationalized-date` | The `@internationalized/date` adapters, kept apart so that optional peer stays genuinely optional.
|
|
46
|
+
| Specifier | What it exports |
|
|
47
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| `forty-cdk/<primitive>` | The primitive's directives, components, context tokens and defaults provider — `ForDialog` from `forty-cdk/dialog`, `ForAccordion` from `forty-cdk/accordion`, and so on for every entry in the tables below. |
|
|
49
|
+
| [`forty-cdk/shared`](shared) | The cross-primitive contract types a primitive's public API references — `WritingDirection`, `VetoableEvent`, `DateAdapter`, `FloatingSide`, … — declared once and published once. Eight ship from their own primitive instead; that README names them. |
|
|
50
|
+
| `forty-cdk/internationalized-date` | The `@internationalized/date` adapters, kept apart so that optional peer stays genuinely optional. |
|
|
51
|
+
|
|
52
|
+
`forty-cdk/core` and `forty-cdk/core-overlay` resolve too, but neither is **public**: together they hold the engines and DI singletons the library refactors freely, and they exist so every primitive resolves that shared implementation to one compiled module. They are two rather than one for a bundling reason you get for free: a published module is a bundler's chunk-splitting unit, so keeping the positioning engine (`@floating-ui/dom` and the overlay shells) in its own module means a lazy route that renders no overlay does not load it. Measured on a seven-lazy-route app, that is **41.7 kB raw / 12.1 kB transfer** a non-overlay route no longer pays. If a symbol you need is not exported by the three specifiers above, it is internal by design — [open an issue](https://github.com/tutkli/forty-cdk/issues) rather than importing from either.
|
|
53
|
+
|
|
54
|
+
## Errors
|
|
55
|
+
|
|
56
|
+
Every error and warning the library reports carries a stable code and, where they add something, the cause and the fix:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
[forty-cdk/dialog] FORCDK-DIALOG-001: ForDialogTitle must be used inside a [forDialog] element.
|
|
60
|
+
|
|
61
|
+
Cause: No FOR_DIALOG_CONTEXT provider is visible from ForDialogTitle. Angular resolves a
|
|
62
|
+
directive's dependencies at the template's declaration site rather than where it is stamped, so a
|
|
63
|
+
piece declared in an ng-template outside the root resolves nothing even when it renders inside it.
|
|
64
|
+
|
|
65
|
+
Fix: Move ForDialogTitle inside a [forDialog] element, declaring any ng-template it lives in there too.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The code is `FORCDK-<AREA>-<NUMBER>`, where the area is the entry point you imported from — so `FORCDK-DATE-PICKER-003` came from `forty-cdk/date-picker`, and `FORCDK-CORE-*` from machinery shared across primitives (those still print the prefix of the primitive you actually wrote). **A code is stable and always means the same failure**, so it is safe to search for, quote in an issue, or match on in your own error handling; a retired code is never reused for something else.
|
|
46
69
|
|
|
47
|
-
|
|
70
|
+
Warnings are dev-mode only. Errors are not: a piece that resolved no context would fail one line later anyway, so it throws in production too and says why.
|
|
48
71
|
|
|
49
72
|
## Primitives
|
|
50
73
|
|
|
@@ -106,15 +129,13 @@ The tables below group the primitives by purpose. The link on each name opens th
|
|
|
106
129
|
|
|
107
130
|
### Date & time
|
|
108
131
|
|
|
109
|
-
| Primitive
|
|
110
|
-
|
|
|
111
|
-
| [Calendar](calendar)
|
|
112
|
-
| [Date Field](date-field)
|
|
113
|
-
| [Date Picker](date-picker)
|
|
114
|
-
| [
|
|
115
|
-
| [Time
|
|
116
|
-
| [Time Picker](time-picker) | A trigger that opens a floating listbox of generated time slots over a pluggable date adapter. |
|
|
117
|
-
| [Time Range Field](time-range-field) | Two time-of-day endpoints (start / end) sharing the hour cycle and min / max bounds. |
|
|
132
|
+
| Primitive | What it is |
|
|
133
|
+
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
134
|
+
| [Calendar](calendar) | A single-date calendar grid (APG Grid) over a pluggable date adapter, with roving-tabindex navigation. |
|
|
135
|
+
| [Date Field](date-field) | A segmented date (and optional time) input — each part a spinbutton with locale-driven order and clamping. Ships `ForDateRangeField` too. |
|
|
136
|
+
| [Date Picker](date-picker) | A trigger that opens a floating calendar to pick a date, composing Calendar inside a dismissible popover. Ships `ForDateRangePicker` too. |
|
|
137
|
+
| [Time Field](time-field) | A segmented time-of-day input with 12 / 24-hour cycles, optional seconds and min / max clamping. Ships `ForTimeRangeField` too. |
|
|
138
|
+
| [Time Picker](time-picker) | A trigger that opens a floating listbox of generated time slots over a pluggable date adapter. |
|
|
118
139
|
|
|
119
140
|
### Disclosure & content
|
|
120
141
|
|
|
@@ -126,15 +147,16 @@ The tables below group the primitives by purpose. The link on each name opens th
|
|
|
126
147
|
|
|
127
148
|
### Data & layout
|
|
128
149
|
|
|
129
|
-
| Primitive
|
|
130
|
-
|
|
|
131
|
-
| [Table](table)
|
|
132
|
-
| [Tree](tree)
|
|
133
|
-
| [Scroll Area](scroll-area)
|
|
134
|
-
| [Pane Resizer](pane-resizer)
|
|
135
|
-
| [Separator](separator)
|
|
136
|
-
| [Aspect Ratio](aspect-ratio)
|
|
137
|
-
| [Avatar](avatar)
|
|
150
|
+
| Primitive | What it is |
|
|
151
|
+
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
152
|
+
| [Table](table) | A headless data table over a native `<table>` or `<div>` grid: sticky headers, 2D keyboard navigation, row selection, sortable headers, column resizing and reordering. |
|
|
153
|
+
| [Tree](tree) | A nested tree view for hierarchical data: expandable nodes with roving-tabindex navigation, selection and typeahead. |
|
|
154
|
+
| [Scroll Area](scroll-area) | A scrollable region with cross-browser, stylable synthetic scrollbars. |
|
|
155
|
+
| [Pane Resizer](pane-resizer) | A focusable divider that resizes the panes on either side — draggable and keyboard-operable. |
|
|
156
|
+
| [Separator](separator) | A static, optionally semantic divider between groups of content, horizontal or vertical. |
|
|
157
|
+
| [Aspect Ratio](aspect-ratio) | A container that keeps its content at a fixed width-to-height ratio. |
|
|
158
|
+
| [Avatar](avatar) | A user image with a graceful fallback across its loading lifecycle. |
|
|
159
|
+
| [Visually Hidden](visually-hidden) | Hides content visually while keeping it in the accessibility tree — screen-reader-only labels, plus the injectable `LiveAnnouncer`. |
|
|
138
160
|
|
|
139
161
|
### Feedback
|
|
140
162
|
|
|
@@ -149,7 +171,7 @@ Headless — no DOM or ARIA of their own; an `inject*` / provider API that other
|
|
|
149
171
|
|
|
150
172
|
| Utility | What it is |
|
|
151
173
|
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
152
|
-
| [Breakpoints](breakpoints) | A signal-first, zoneless, SSR-safe viewport breakpoint observer (`injectBreakpoints`).
|
|
174
|
+
| [Breakpoints](breakpoints) | A signal-first, zoneless, SSR-safe viewport breakpoint observer (`injectBreakpoints`), plus the `prefers-reduced-motion` detector. |
|
|
153
175
|
| [Drag & Drop](drag-drop) | Headless, accessible drag-and-drop for sortable lists and cross-list transfers, keyboard and pointer driven. |
|
|
154
176
|
| [Virtualization](virtualization) | A headless windowing core (`injectVirtualizer`) plus a `[forVirtualViewport]` layer that renders only the visible slice of huge lists. |
|
|
155
177
|
| [Table Virtualization](table-virtualization) | `[forTableVirtualized]`, the adapter that windows a `[forTable]` grid — its own entry point because it composes both the table and the windowing core. |
|
|
@@ -158,7 +180,7 @@ Headless — no DOM or ARIA of their own; an `inject*` / provider API that other
|
|
|
158
180
|
## Building
|
|
159
181
|
|
|
160
182
|
```bash
|
|
161
|
-
|
|
183
|
+
pnpm build
|
|
162
184
|
```
|
|
163
185
|
|
|
164
186
|
Build artifacts land in `dist/forty-cdk` (consumed locally via the `forty-cdk` path alias in the root `tsconfig.json`).
|
|
@@ -169,11 +191,11 @@ Tests run on Vitest via the Angular CLI builder `@angular/build:unit-test`:
|
|
|
169
191
|
|
|
170
192
|
```bash
|
|
171
193
|
pnpm test # all specs, single pass
|
|
172
|
-
pnpm
|
|
194
|
+
pnpm test:watch # watch mode
|
|
173
195
|
pnpm exec ng test forty-cdk --include "../accordion/src/accordion.spec.ts" # single file (path relative to projects/forty-cdk/src/)
|
|
174
196
|
pnpm exec ng test forty-cdk --filter "Enter and Space select" # tests by name (regex)
|
|
175
197
|
```
|
|
176
198
|
|
|
177
199
|
The `-- <path>` / `-- -t "<name>"` passthrough forms do **not** work on this setup (pnpm mangles the quoted `--`, so `ng` rejects it) — use the builder's own `--include` (repeatable) and `--filter` (regex) flags instead.
|
|
178
200
|
|
|
179
|
-
|
|
201
|
+
The whole suite runs under `provideZonelessChangeDetection()`, so reactivity is verified without Zone.js on every spec rather than in a per-primitive case.
|
package/accordion/README.md
CHANGED
|
@@ -111,12 +111,12 @@ export class DemoFaq {
|
|
|
111
111
|
|
|
112
112
|
- **Heading wrapper is your job.** The library does not render a heading around the trigger — wrap it in the heading level (`<h2>`–`<h6>`) appropriate to your document outline. Without it, screen-reader landmark navigation is broken.
|
|
113
113
|
- **Use a real `<button type="button">` for the trigger.** Native Enter / Space activation and focus come for free; the directive does not synthesize them.
|
|
114
|
-
- **`role="region"`** is added to every panel automatically. APG recommends suppressing it on accordions with 6+ panels to avoid landmark proliferation
|
|
114
|
+
- **`role="region"`** is added to every panel automatically. APG recommends suppressing it on accordions with 6+ panels to avoid landmark proliferation; there is currently no opt-out.
|
|
115
115
|
- **Closed panels leave the accessibility tree.** While closed, `ForAccordionContent` sets `aria-hidden="true"` and `inert` on the panel, removing it from both the accessibility tree and the focus order. The directive does **not** apply `[hidden]`, so pick how to hide it visually:
|
|
116
116
|
- **Mount / unmount with `@if (item.expanded())`** — the panel is absent from the DOM while closed; the cleanest path for `animate.enter` / `animate.leave`. The trigger emits `aria-controls` only while expanded, so the reference never dangles at an unmounted panel.
|
|
117
117
|
- **Leave it mounted** — preserve internal state or run CSS-only transitions off `data-state`. Add `display: none` (or your own collapse animation) keyed on `[data-state="closed"]` to also hide it visually.
|
|
118
118
|
- **`aria-disabled`** is applied to the open trigger only when single mode is active and `collapsible=false`, indicating the user cannot collapse it from this trigger.
|
|
119
|
-
- **A truly disabled item (`[disabled]` on `[forAccordionItem]`) uses the native `disabled` attribute on the trigger, by design.**
|
|
119
|
+
- **A truly disabled item (`[disabled]` on `[forAccordionItem]`) uses the native `disabled` attribute on the trigger, by design.** The trigger is a real single-purpose `<button>`, not a roving-tabindex collection item (each trigger stays independently in the Tab order; arrow-key navigation is the APG-optional enhancement on top). The disabled trigger leaves the Tab order and the arrow-key navigation (which already skips it), but stays in the accessibility tree so screen readers announce it as unavailable in browse mode. The [APG Accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/) does not require disabled headers to remain focusable.
|
|
120
120
|
|
|
121
121
|
## Styling
|
|
122
122
|
|
package/aspect-ratio/README.md
CHANGED
|
@@ -91,7 +91,7 @@ forty-cdk ships no styles. Add your own class to each piece — the `for*` selec
|
|
|
91
91
|
|
|
92
92
|
## Behavior notes
|
|
93
93
|
|
|
94
|
-
- **Browser support.** Native `aspect-ratio` is in Baseline 2021 (Chrome 88+, Firefox 89+, Safari 15+)
|
|
94
|
+
- **Browser support.** Native `aspect-ratio` is in Baseline 2021 (Chrome 88+, Firefox 89+, Safari 15+), so no polyfill is needed on any browser Angular itself supports.
|
|
95
95
|
- **Width still on you.** The directive only sets `aspect-ratio`; you decide width / max-width / display. The height is computed from the ratio.
|
|
96
96
|
- **Children fill the box.** Use `width: 100%; height: 100%; object-fit: cover` on inner media to fill without distortion. The directive imposes no styles on children.
|
|
97
97
|
- **No role, no a11y.** This is a layout utility. The element it sits on keeps whatever semantics you give it (`<div>`, `<figure>`, `<a>`, …).
|
package/breakpoints/README.md
CHANGED
|
@@ -82,6 +82,27 @@ Now `injectBreakpoints()` autocompletes `'mobile' | 'tablet' | 'laptop' | 'deskt
|
|
|
82
82
|
| `active` | the largest breakpoint whose `min-width` matches, or `null` below the smallest |
|
|
83
83
|
| `matches(query)` | escape hatch for an arbitrary media query (orientation, `prefers-*`, …) |
|
|
84
84
|
|
|
85
|
+
### `injectPrefersReducedMotion`
|
|
86
|
+
|
|
87
|
+
The same shape for a different query: `injectPrefersReducedMotion()` returns a `Signal<boolean>` that is `true` while the user has asked their OS to suppress animation, and flips if they change the setting mid-session. Call it from an injection context, like `injectBreakpoints()`.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { computed } from '@angular/core';
|
|
91
|
+
import { injectPrefersReducedMotion } from 'forty-cdk/breakpoints';
|
|
92
|
+
|
|
93
|
+
export class Panel {
|
|
94
|
+
private readonly reducedMotion = injectPrefersReducedMotion();
|
|
95
|
+
|
|
96
|
+
protected readonly transition = computed(() =>
|
|
97
|
+
this.reducedMotion() ? 'none' : 'transform 200ms ease-out',
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
It is published here because forty-cdk ships no styles: the animation on a `data-state` change is yours, so honouring the preference is yours too — and a signal is what a `computed()` or a `[style]` binding can branch on, which a CSS `@media` block cannot. Treat `true` as "skip the animated path entirely", not "shorten the duration": the setting asks for no motion, not less of it.
|
|
103
|
+
|
|
104
|
+
`bp.matches('(prefers-reduced-motion: reduce)')` resolves to the same thing. Prefer the named helper — it is the one the library's own motion-bearing primitives (drag gestures, carousel, drawer) read, so the query string stays spelled in one place.
|
|
105
|
+
|
|
85
106
|
## SSR
|
|
86
107
|
|
|
87
|
-
On the server (or where `matchMedia` is unavailable) every query signal reads `false` and `active` reads `null`. No `matchMedia` access happens server-side, so the helper is safe under Angular Universal.
|
|
108
|
+
On the server (or where `matchMedia` is unavailable) every query signal reads `false` and `active` reads `null`. No `matchMedia` access happens server-side, so the helper is safe under Angular Universal. `injectPrefersReducedMotion()` reads `false` there for the same reason: the server render takes the animated branch, and the client applies the real preference on its first observation.
|
package/calendar/README.md
CHANGED
|
@@ -24,9 +24,9 @@ bootstrapApplication(App, {
|
|
|
24
24
|
});
|
|
25
25
|
```
|
|
26
26
|
|
|
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.
|
|
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.
|
|
28
28
|
|
|
29
|
-
**Calendar system (Gregorian).** The adapter seam abstracts the date _library_ and locale-aware _formatting_, not the calendar _system_'s month structure.
|
|
29
|
+
**Calendar system (Gregorian).** The adapter seam abstracts the date _library_ and locale-aware _formatting_, not the calendar _system_'s month structure. Both `@internationalized/date` adapters build Gregorian dates, so the grid stays Gregorian regardless of the runtime locale, and the grid, the month picker and the date field all assume a Gregorian-structured year — exactly twelve months, `month` **1-12**, the year ending at month 12. Adapters over calendars with a different month structure (e.g. a 13-month year) are not supported. The optional `compareDate` hook overrides day-only _ordering_ only — it does not make the grid non-Gregorian.
|
|
30
30
|
|
|
31
31
|
## Anatomy
|
|
32
32
|
|
|
@@ -205,7 +205,7 @@ Set `selectionMode="range"` and bind `[(range)]` to get date-range selection. In
|
|
|
205
205
|
|
|
206
206
|
**`aria-selected`** in range mode is `"true"` across every committed-range cell (inclusive). During selecting (range null), it is `"false"` everywhere.
|
|
207
207
|
|
|
208
|
-
**
|
|
208
|
+
**Scope.** Range mode is day-granular only — `granularity` / time is orthogonal and not supported alongside it.
|
|
209
209
|
|
|
210
210
|
## Month / year navigation
|
|
211
211
|
|
package/combobox/README.md
CHANGED
|
@@ -140,7 +140,7 @@ Focus stays on the `<input>` the whole time the listbox is open, so options neve
|
|
|
140
140
|
|
|
141
141
|
## Anchoring to a field box
|
|
142
142
|
|
|
143
|
-
By default the listbox is positioned against `[forComboboxInput]`. When the input lives inside a decorated field box — padding, a prefix icon, a clear button, or the multi-mode chip cluster — anchoring to the bare `<input>` makes the panel narrower than the visible field and offset from its edge. Wrap the field box in `[forComboboxAnchor]` so floating-ui positions (and sizes, via `--for-anchor-width`) the listbox against the box instead:
|
|
143
|
+
By default the listbox is positioned against `[forComboboxInput]`. When the input lives inside a decorated field box — padding, a prefix icon, a clear button, or the multi-mode chip cluster — anchoring to the bare `<input>` makes the panel narrower than the visible field and offset from its edge. Wrap the field box in `[forComboboxAnchor]` so floating-ui positions (and sizes, via `--for-floating-anchor-width`) the listbox against the box instead:
|
|
144
144
|
|
|
145
145
|
```html
|
|
146
146
|
<div forCombobox #combobox="forCombobox" [(query)]="query" [(value)]="value">
|
|
@@ -150,7 +150,7 @@ By default the listbox is positioned against `[forComboboxInput]`. When the inpu
|
|
|
150
150
|
<button class="clear" (click)="combobox.clear()">×</button>
|
|
151
151
|
</div>
|
|
152
152
|
@if (combobox.open()) {
|
|
153
|
-
<div forComboboxContent style="width: var(--for-anchor-width)">
|
|
153
|
+
<div forComboboxContent style="width: var(--for-floating-anchor-width)">
|
|
154
154
|
@for (it of filtered; track it.id) {
|
|
155
155
|
<div forComboboxOption [value]="it.id" [label]="it.label">{{ it.label }}</div>
|
|
156
156
|
}
|
|
@@ -582,7 +582,7 @@ When `[totalCount]` is omitted, the directive falls back to `options().length` a
|
|
|
582
582
|
`[forCombobox]` exposes a `dir: 'ltr' | 'rtl'` input (default `'ltr'`). It drives:
|
|
583
583
|
|
|
584
584
|
- **Chip keyboard navigation** — ArrowLeft / ArrowRight roles swap so they follow the visual order of the chip cluster, not the DOM order. See _Chip keyboard_ above.
|
|
585
|
-
- **Default popover placement** — `align` defaults to `'start'` in LTR and `'end'` in RTL so the listbox anchors to the visually-leading edge of the input (`side` defaults to `'bottom'` in both). A consumer-provided `[align]` is honoured as-is — no automatic flip — so advanced layouts can pin an alignment regardless of writing direction.
|
|
585
|
+
- **Default popover placement** — `align` defaults to `'start'` in LTR and `'end'` in RTL so the listbox anchors to the visually-leading edge of the input (`side` defaults to `'bottom'` in both). A consumer-provided `[align]` is honoured as-is — no automatic flip — so advanced layouts can pin an alignment regardless of writing direction. `provideForComboboxDefaults({ align })` pins it for a whole scope the same way; its default is `null`, which is what "follow the writing direction" is spelled as there. `side` is scope-defaultable through the same provider, with the plain `'bottom'` fallback — writing direction does not enter into it.
|
|
586
586
|
|
|
587
587
|
The native `<input>` handles caret movement and BiDi from the document's CSS `direction` already, so there's nothing extra to do for the typed text itself.
|
|
588
588
|
|
|
@@ -633,13 +633,13 @@ forty-cdk ships no styles. Add your own class to each piece — the for\* select
|
|
|
633
633
|
|
|
634
634
|
`[forComboboxContent]` is portaled to `document.body` and gets its position resolved by floating-ui. The resolved geometry is exposed as custom properties on the content host (cleared on close):
|
|
635
635
|
|
|
636
|
-
| Custom property
|
|
637
|
-
|
|
|
638
|
-
| `--for-anchor-width` | px | Anchor (input / wrapper) width — match the listbox to the input with `width: var(--for-anchor-width)`.
|
|
639
|
-
| `--for-anchor-height` | px | Anchor height.
|
|
640
|
-
| `--for-available-width` | px | Space available along the inline axis (floating-ui `size` middleware) — clamp with `max-width`.
|
|
641
|
-
| `--for-available-height` | px | Space available along the block axis — clamp with `max-height`.
|
|
642
|
-
| `--for-content-transform-origin` | `<origin>` keywords | `transform-origin` matching the resolved side / align, so a `scale` enter animation pivots from the input.
|
|
636
|
+
| Custom property | Type / range | Meaning |
|
|
637
|
+
| ----------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
638
|
+
| `--for-floating-anchor-width` | px | Anchor (input / wrapper) width — match the listbox to the input with `width: var(--for-floating-anchor-width)`. |
|
|
639
|
+
| `--for-floating-anchor-height` | px | Anchor height. |
|
|
640
|
+
| `--for-floating-available-width` | px | Space available along the inline axis (floating-ui `size` middleware) — clamp with `max-width`. |
|
|
641
|
+
| `--for-floating-available-height` | px | Space available along the block axis — clamp with `max-height`. |
|
|
642
|
+
| `--for-floating-content-transform-origin` | `<origin>` keywords | `transform-origin` matching the resolved side / align, so a `scale` enter animation pivots from the input. |
|
|
643
643
|
|
|
644
644
|
> `[forComboboxContent]` 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) and the shared positioner properties above. See [Styling floating content](../../../docs/styling-floating-content.md) for the full positioner-variable list and the portal styling rules.
|
|
645
645
|
|
package/context-menu/README.md
CHANGED
|
@@ -120,8 +120,8 @@ Both triggers carry `[menuPositioning]`, a partial `{ side, align, sideOffset, a
|
|
|
120
120
|
| Property | Type | Description |
|
|
121
121
|
| --------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
122
122
|
| `open` | `model<boolean>` | Two-way bindable. Whether the menu is shown.<br>**Default:** `false` |
|
|
123
|
-
| `side` | `input<string>` | Anchor side relative to the pointer.<br>**Default:** `'bottom'`
|
|
124
|
-
| `align` | `input<string>` | Alignment along `side` (`'start'` / `'center'` / `'end'`).<br>**Default:** `'start'`
|
|
123
|
+
| `side` | `input<string>` | Anchor side relative to the pointer. The default is read from `provideForContextMenuDefaults` for the surrounding scope.<br>**Default:** `'bottom'` |
|
|
124
|
+
| `align` | `input<string>` | Alignment along `side` (`'start'` / `'center'` / `'end'`). The default is read from `provideForContextMenuDefaults` for the surrounding scope.<br>**Default:** `'start'` |
|
|
125
125
|
| `sideOffset` | `input<number>` | Gap (px) between the pointer and the menu along the main axis.<br>**Default:** `0` |
|
|
126
126
|
| `alignOffset` | `input<number>` | Gap (px) along the cross axis (parallel to `side`).<br>**Default:** `0` |
|
|
127
127
|
| `fallbackAxisSideDirection` | `input<'none' \| 'start' \| 'end'>` | When both sides of the preferred axis overflow, lets `flip` drop the menu to a perpendicular side instead of clipping. `'none'` keeps only the opposite same-axis placement. The default is read from `provideForContextMenuDefaults` for the surrounding scope — set it once for the whole app rather than per call site.<br>**Default:** `'none'` |
|
|
@@ -161,7 +161,7 @@ Same vetoable dismiss API as DropdownMenu. Call `preventDefault()` on the emitte
|
|
|
161
161
|
|
|
162
162
|
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 listed under [Data attributes](#data-attributes).
|
|
163
163
|
|
|
164
|
-
> The menu content (`[forMenuContent]`, from the [`menu/`](../menu/README.md) folder) portals to `document.body`, so it sits outside the trigger's DOM subtree — descendant selectors won't reach it. Style it with **global CSS** or a class on the content element. The content host also exposes the shared positioner custom properties (`--for-anchor-width` / `--for-anchor-height`, `--for-available-width` / `--for-available-height`, `--for-content-transform-origin`); see [Styling floating content](../../../docs/styling-floating-content.md) for the full list and the animation rules.
|
|
164
|
+
> The menu content (`[forMenuContent]`, from the [`menu/`](../menu/README.md) folder) portals to `document.body`, so it sits outside the trigger's DOM subtree — descendant selectors won't reach it. Style it with **global CSS** or a class on the content element. The content host also exposes the shared positioner custom properties (`--for-floating-anchor-width` / `--for-floating-anchor-height`, `--for-floating-available-width` / `--for-floating-available-height`, `--for-floating-content-transform-origin`); see [Styling floating content](../../../docs/styling-floating-content.md) for the full list and the animation rules.
|
|
165
165
|
|
|
166
166
|
```css
|
|
167
167
|
.context-menu-trigger[data-state='open'] {
|
package/date-field/README.md
CHANGED
|
@@ -188,6 +188,78 @@ providers: [
|
|
|
188
188
|
|
|
189
189
|
`segmentLabels` supplies each segment's default `aria-label`, keyed by part type. Unset keys keep the library default (the part name, and `'AM/PM'` for the `dayPeriod` segment), so overriding a single key never wipes the rest. A segment's own `[ariaLabel]` still wins over the scope default.
|
|
190
190
|
|
|
191
|
+
## Range selection — `ForDateRangeField`
|
|
192
|
+
|
|
193
|
+
For a date range use the dedicated `ForDateRangeField` root (selector `[forDateRangeField]`), shipped from this same entry point. It is the keyboard-first, form-capable counterpart to [DateRangePicker](../date-picker/README.md): two labelled `role="group"` endpoints (start / end), each holding a row of spinbutton segments — the same machinery as `ForDateField` — nested inside one outer `role="group"`. It implements `FormValueControl<DateRange<D> | null>`, the **same** contract as `ForDateRangePicker`, so the committed range auto-wires with `[formField]`. The value stays `null` until **both** endpoints are fully entered and ordered (`start <= end`).
|
|
194
|
+
|
|
195
|
+
The pieces are the range-specific `[forDateRangeFieldStart]` / `[forDateRangeFieldEnd]` endpoint groups plus `[forDateRangeFieldSegment]` / `[forDateRangeFieldLiteral]`; each endpoint exposes its own `segments()` list, so the same `@for` template renders both sides.
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<div forDateRangeField [(value)]="stay" ariaLabel="Stay">
|
|
199
|
+
<div forDateRangeFieldStart #start="forDateRangeFieldStart">
|
|
200
|
+
@for (seg of start.segments(); track seg.id) { @if (seg.isLiteral) {
|
|
201
|
+
<span forDateRangeFieldLiteral>{{ seg.text }}</span>
|
|
202
|
+
} @else {
|
|
203
|
+
<span forDateRangeFieldSegment [segment]="seg.type!">{{ seg.text }}</span>
|
|
204
|
+
} }
|
|
205
|
+
</div>
|
|
206
|
+
<span aria-hidden="true">–</span>
|
|
207
|
+
<div forDateRangeFieldEnd #end="forDateRangeFieldEnd">
|
|
208
|
+
@for (seg of end.segments(); track seg.id) { @if (seg.isLiteral) {
|
|
209
|
+
<span forDateRangeFieldLiteral>{{ seg.text }}</span>
|
|
210
|
+
} @else {
|
|
211
|
+
<span forDateRangeFieldSegment [segment]="seg.type!">{{ seg.text }}</span>
|
|
212
|
+
} }
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import {
|
|
219
|
+
ForDateRangeField,
|
|
220
|
+
ForDateRangeFieldEnd,
|
|
221
|
+
ForDateRangeFieldLiteral,
|
|
222
|
+
ForDateRangeFieldSegment,
|
|
223
|
+
ForDateRangeFieldStart,
|
|
224
|
+
} from 'forty-cdk/date-field';
|
|
225
|
+
import type { DateRange } from 'forty-cdk/shared';
|
|
226
|
+
|
|
227
|
+
readonly model = signal({ stay: null as DateRange<CalendarDate> | null });
|
|
228
|
+
readonly booking = form(this.model);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### `ForDateRangeField` API
|
|
232
|
+
|
|
233
|
+
| Property | Type | Description |
|
|
234
|
+
| ------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
235
|
+
| `value` | `model<DateRange<D> \| null>` | Two-way bindable committed range, or `null` while incomplete or out of order. The `FormValueControl` backing.<br>**Default:** `null` |
|
|
236
|
+
| `minDate` | `input<D \| null>` | Minimum date (inclusive) for both endpoints. A composed endpoint below it is clamped up. Named `minDate` — see note below.<br>**Default:** `null` |
|
|
237
|
+
| `maxDate` | `input<D \| null>` | Maximum date (inclusive) for both endpoints. A composed endpoint above it is clamped down.<br>**Default:** `null` |
|
|
238
|
+
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision shared by both endpoints. `'day'` is date-only; coarser-than-day appends time segments.<br>**Default:** `'day'` |
|
|
239
|
+
| `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the time segments. `null` → locale. 12-hour adds the AM/PM segment.<br>**Default:** `null` |
|
|
240
|
+
| `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → runtime locale.<br>**Default:** `null` |
|
|
241
|
+
| `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty, applied to both endpoints.<br>**Default:** `{}` |
|
|
242
|
+
| `ariaLabel` | `input<string \| null>` | Accessible name for the whole range field group. Emits no `aria-label` while `null`.<br>**Default:** `null` |
|
|
243
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null` |
|
|
244
|
+
|
|
245
|
+
The endpoint groups each accept an `ariaLabel` input for their own group label, falling back to the scope defaults (`'Start date'` / `'End date'`). Plus the shared `FormUiControl` members bound automatically by `[formField]`.
|
|
246
|
+
|
|
247
|
+
> **Why `minDate` / `maxDate`, not `min` / `max`?** Beyond the reason above, `FormUiControl.min` / `max` are additionally typed `NonNullable<TValue>` — the range object itself — which is meaningless as a bound.
|
|
248
|
+
|
|
249
|
+
`[forDateRangeField]` reflects the same `data-disabled` / `data-readonly` / `data-empty` hooks as `[forDateField]`, plus `data-range-error`; `[forDateRangeFieldSegment]` reflects the same four segment hooks. `data-empty` marks the field only while **both** endpoints are entirely empty; a partially-filled or complete-but-disordered range is **not** empty.
|
|
250
|
+
|
|
251
|
+
### Ordering
|
|
252
|
+
|
|
253
|
+
The two endpoints are typed independently, so order is not guaranteed by construction the way the picker's two-click flow guarantees it. The field preserves the `DateRange` `end >= start` invariant by **never emitting an out-of-order range**: when both endpoints are complete but `start > end`, the typed segments are kept (not silently rewritten), `value` stays `null`, and the root reflects `aria-invalid="true"` + `data-range-error` so the disorder is perceivable and stylable. Editing either endpoint back into order emits the range.
|
|
254
|
+
|
|
255
|
+
### Range keyboard and accessibility
|
|
256
|
+
|
|
257
|
+
Each endpoint is its own tab stop, so `Tab` moves start group → end group → next control; arrows move between segments **within** an endpoint. Every other key behaves as in the [Keyboard](#keyboard) table below. Roving tabindex is per endpoint, and `aria-invalid="true"` is reflected on the root when the form marks it invalid **or** when two complete endpoints are out of order; everything else matches the [Accessibility](#accessibility) notes below.
|
|
258
|
+
|
|
259
|
+
### Range scope defaults
|
|
260
|
+
|
|
261
|
+
`provideForDateRangeFieldDefaults` mirrors `provideForDateFieldDefaults` and adds `startLabel` / `endLabel` for the two endpoint group `aria-label`s (`'Start date'` / `'End date'` by default). Both wrapper patterns work via `FOR_DATE_RANGE_FIELD_HOST_DIRECTIVE_INPUTS` / `FOR_DATE_RANGE_FIELD_HOST_DIRECTIVE_OUTPUTS` — see [Wrapping form primitives](../../../docs/wrapping-form-primitives.md).
|
|
262
|
+
|
|
191
263
|
## Keyboard
|
|
192
264
|
|
|
193
265
|
Key behavior applies per segment. Horizontal arrows mirror under `dir="rtl"`.
|
package/date-picker/README.md
CHANGED
|
@@ -157,7 +157,7 @@ The library is styleless: presence in the DOM is the consumer's job (`@if (open(
|
|
|
157
157
|
| `formatOptions` | `input<Intl.DateTimeFormatOptions>` | Options for the text rendered by `[forDatePickerValue]`.<br>**Default:** `{ year: 'numeric', month: 'long', day: 'numeric' }` |
|
|
158
158
|
| `locale` | `input<string \| null>` | BCP 47 locale for the text rendered by `[forDatePickerValue]`. Not forwarded to the projected calendar — bind its `[locale]` too.<br>**Default:** `null` → runtime locale |
|
|
159
159
|
| `placeholder` | `input<string>` | Fallback text for `[forDatePickerValue]` when empty.<br>**Default:** `''` |
|
|
160
|
-
| `side` / `align` | `input` | Anchored placement (popover mode only)
|
|
160
|
+
| `side` / `align` | `input` | Anchored placement (popover mode only). Defaults from `provideForDatePickerDefaults` / `provideForDateRangePickerDefaults`.<br>**Default:** `'bottom'` / `'start'` |
|
|
161
161
|
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction.<br>**Default:** `null` resolves the ambient direction; reflected to the host `dir` |
|
|
162
162
|
|
|
163
163
|
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`).
|
|
@@ -219,7 +219,7 @@ By default the surface is positioned against `[forDatePickerTrigger]`. When the
|
|
|
219
219
|
</div>
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
-
`[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.)
|
|
222
|
+
`[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-floating-anchor-width`, so the anchor mainly affects start / side alignment to the box edge.)
|
|
223
223
|
|
|
224
224
|
## Modal vs non-modal
|
|
225
225
|
|
|
@@ -275,10 +275,11 @@ The value display (`[forDatePickerValue]`) automatically appends the time to its
|
|
|
275
275
|
|
|
276
276
|
For date-range selection use the dedicated `ForDateRangePicker` root (selector `[forDateRangePicker]`). It is the root **and** the form value, implementing `FormValueControl<DateRange<D> | null>`, so the committed range auto-wires with `[formField]` exactly like any other control.
|
|
277
277
|
|
|
278
|
-
It reuses the same pieces — `[forDatePickerTrigger]`, `[forDatePickerContent]`, `[forDatePickerValue]`, `[forDatePickerAnchor]` — through a shared base, and provides `FOR_DATE_PICKER_CONTEXT` so they resolve under it. Project a `[forCalendar]` in `selectionMode="range"` and bind its range to the picker's `value`; the two-click anchor → commit flow keeps `value` `null` until both endpoints are chosen (the form never sees a half-entered range), and `start <= end` is an invariant. Range is day-granular
|
|
278
|
+
It reuses the same pieces — `[forDatePickerTrigger]`, `[forDatePickerContent]`, `[forDatePickerValue]`, `[forDatePickerAnchor]` — through a shared base, and provides `FOR_DATE_PICKER_CONTEXT` so they resolve under it. Project a `[forCalendar]` in `selectionMode="range"` and bind its range to the picker's `value`; the two-click anchor → commit flow keeps `value` `null` until both endpoints are chosen (the form never sees a half-entered range), and `start <= end` is an invariant. Range is day-granular (no time composition).
|
|
279
279
|
|
|
280
280
|
```ts
|
|
281
|
-
import {
|
|
281
|
+
import { ForDateRangePicker } from 'forty-cdk/date-picker';
|
|
282
|
+
import type { DateRange } from 'forty-cdk/shared';
|
|
282
283
|
import { form } from '@angular/forms/signals';
|
|
283
284
|
|
|
284
285
|
interface Booking {
|
|
@@ -321,7 +322,7 @@ readonly booking = form(this.model, (p) => required(p.stay));
|
|
|
321
322
|
- **Native submission.** When `name` is set, two hidden inputs `<name>-start` / `<name>-end` mirror the committed endpoints as ISO `YYYY-MM-DD` for native `<form>` posts.
|
|
322
323
|
- **Bounds naming.** `minDate` / `maxDate` (not `min` / `max`) for the same reason as `ForDatePicker` — and additionally because `FormUiControl.min` / `max` are typed `NonNullable<TValue>` (the range object itself), which is meaningless as a bound.
|
|
323
324
|
|
|
324
|
-
Defaults are configured with `provideForDateRangePickerDefaults` (`sideOffset` / `collisionPadding`), and both wrapper patterns work via the exported `FOR_DATE_RANGE_PICKER_HOST_DIRECTIVE_INPUTS` / `FOR_DATE_RANGE_PICKER_HOST_DIRECTIVE_OUTPUTS` tuples — see [Wrapping form primitives](../../../docs/wrapping-form-primitives.md).
|
|
325
|
+
Defaults are configured with `provideForDateRangePickerDefaults` (`side` / `align` / `sideOffset` / `collisionPadding`), and both wrapper patterns work via the exported `FOR_DATE_RANGE_PICKER_HOST_DIRECTIVE_INPUTS` / `FOR_DATE_RANGE_PICKER_HOST_DIRECTIVE_OUTPUTS` tuples — see [Wrapping form primitives](../../../docs/wrapping-form-primitives.md).
|
|
325
326
|
|
|
326
327
|
## Keyboard
|
|
327
328
|
|
|
@@ -338,7 +339,7 @@ Implements the [WAI-ARIA Date Picker Dialog pattern](https://www.w3.org/WAI/ARIA
|
|
|
338
339
|
|
|
339
340
|
- **`role="combobox"`** on the trigger with **`aria-haspopup="dialog"`**, `aria-expanded` reflecting `open()`, and `aria-controls` pointing at the surface while open — the same shape `[forSelectTrigger]` / `[forTimePickerTrigger]` ship, with the `dialog` popup token ARIA 1.2 allows for a combobox surface. The role is also what makes the form-control ARIA below legal: `role="button"` supports neither `aria-readonly` nor `aria-required`.
|
|
340
341
|
- **`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).
|
|
341
|
-
- **Form-control ARIA** (`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, alongside the `data-readonly` styling hook. The disabled state is the exception: it reflects through the native `disabled` attribute alone (plus `data-disabled`), never `aria-disabled` — one channel
|
|
342
|
+
- **Form-control ARIA** (`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, alongside the `data-readonly` styling hook. The disabled state is the exception: it reflects through the native `disabled` attribute alone (plus `data-disabled`), never `aria-disabled` — one channel only.
|
|
342
343
|
- **Inside a `[forField]` the labelled element is the trigger**, not the `[forDatePicker]` / `[forDateRangePicker]` wrapper: the field's `controlId` and its `aria-labelledby` / `aria-describedby` / `aria-errormessage` land on `[forDatePickerTrigger]`, so `[forLabel]`'s `for` points at the element that takes focus, clicking a non-`<label>` `[forLabel]` opens the surface, and Signal Forms' focus-on-error reaches the trigger. `role="combobox"` takes its name from the author, so this is the channel that names the control — the root's `[ariaLabel]` names the `role="dialog"` surface instead.
|
|
343
344
|
- **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)`.
|
|
344
345
|
- **Dismissal**: Escape (`(escapeKeyDown)`) and outside-pointer (`(pointerDownOutside)` / `(interactOutside)`) close the surface, each vetoable.
|
|
@@ -347,7 +348,7 @@ Implements the [WAI-ARIA Date Picker Dialog pattern](https://www.w3.org/WAI/ARIA
|
|
|
347
348
|
|
|
348
349
|
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 listed under [Data attributes](#data-attributes).
|
|
349
350
|
|
|
350
|
-
> `[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.
|
|
351
|
+
> `[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-floating-anchor-width` / `--for-floating-anchor-height`, `--for-floating-available-width` / `--for-floating-available-height`, `--for-floating-content-transform-origin`); that same guide tabulates the full set.
|
|
351
352
|
|
|
352
353
|
```css
|
|
353
354
|
.date-picker-trigger .date-picker-value[data-placeholder] {
|
package/dialog/README.md
CHANGED
|
@@ -75,7 +75,7 @@ This is different from trigger-anchored overlays (Popover, DropdownMenu, etc.) w
|
|
|
75
75
|
|
|
76
76
|
The payload is a `ForDialogCloseReason` string (`'escape'`, `'backdrop'`, `'pointerDownOutside'`, `'focusOutside'`, `'closeButton'`, `'programmatic'`) — use it if you need to branch on why the dialog closed, for example to show a "save changes?" prompt before dismissing. Emitting `(dismiss)` without acting on it is always safe: you can call `preventDefault()` on the preceding dismiss outputs (`(escapeKeyDown)`, `(pointerDownOutside)`, `(focusOutside)`, `(interactOutside)`) to suppress the `(dismiss)` entirely.
|
|
77
77
|
|
|
78
|
-
> **
|
|
78
|
+
> **The declarative and imperative surfaces spell this differently, on purpose.** The output is `(dismiss)` — an output named `close` would collide with the native DOM event and break any wrapper re-exposing it through `hostDirectives`. Nothing else changes name: the imperative handle method is `ForDialogRef.close()`, the directive selector is `[forDialogClose]`, and the payload type is `ForDialogCloseReason`.
|
|
79
79
|
|
|
80
80
|
### Trigger / surface id wiring
|
|
81
81
|
|
package/disclosure/README.md
CHANGED
|
@@ -76,7 +76,7 @@ The library ships no styles. Hide animations / transitions can be driven off `da
|
|
|
76
76
|
| `data-state` | `open` \| `closed` |
|
|
77
77
|
| `data-disabled` | present \| absent |
|
|
78
78
|
|
|
79
|
-
Reflects on its host: `id`, `aria-expanded`, `aria-controls`, `disabled`, `data-state`. Toggles the state on click. The disabled reflection (the native `disabled` attribute plus `data-disabled`; no `aria-disabled
|
|
79
|
+
Reflects on its host: `id`, `aria-expanded`, `aria-controls`, `disabled`, `data-state`. Toggles the state on click. The disabled reflection (the native `disabled` attribute plus `data-disabled`; no `aria-disabled` — one channel only) and the click guard follow the effective state — the trigger's own `disabled` OR the root's.
|
|
80
80
|
|
|
81
81
|
`aria-controls` is emitted only while open — mirroring the overlay triggers' open-only gating — so the reference never dangles at an unmounted panel under the recommended `@if (open())` mount pattern.
|
|
82
82
|
|
package/drag-drop/README.md
CHANGED
|
@@ -392,8 +392,8 @@ Both `data-dragging` rows hold for a drag a **coordinator** composing the list o
|
|
|
392
392
|
than starting through `[forDraggable]` itself — the keyboard lift of `[forVirtualReorder]`, and
|
|
393
393
|
the virtualized branch of `[forTableRowReorder]`. Those intercept the lift key before the item
|
|
394
394
|
sees it, so the list carries no lift state for the gesture, and the coordinator marks the item
|
|
395
|
-
instead
|
|
396
|
-
|
|
395
|
+
instead. Styling keyed off either attribute therefore behaves the same whether the collection is
|
|
396
|
+
windowed or not.
|
|
397
397
|
|
|
398
398
|
The `data-for-drag-preview` row is also the supported hook for **keeping the clone out of element
|
|
399
399
|
queries**. The default preview is a `cloneNode(true)` copy appended to `document.body`, so for the
|
|
@@ -401,8 +401,7 @@ whole gesture — and past the drop, while a settle transition runs — it answe
|
|
|
401
401
|
selector (`[forDraggable]`, or a composed one such as `[forTableRow]`) and repeats its `data-index`.
|
|
402
402
|
`id` and `data-testid` are stripped from the clone and its whole subtree, so a hook that identifies
|
|
403
403
|
a single element stays unambiguous; anything that **enumerates** items by attribute selector during
|
|
404
|
-
a drag must filter the preview out with `:not([data-for-drag-preview])
|
|
405
|
-
([#1691](https://github.com/tutkli/forty-cdk/issues/1691)).
|
|
404
|
+
a drag must filter the preview out with `:not([data-for-drag-preview])`.
|
|
406
405
|
|
|
407
406
|
## Sortable list
|
|
408
407
|
|
package/drawer/README.md
CHANGED
|
@@ -272,7 +272,7 @@ Declaratively the same recipe is the four vetoable outputs on `[forDrawer]`: `(i
|
|
|
272
272
|
|
|
273
273
|
`ForDrawerCloseReason`: `'escape' | 'backdrop' | 'pointerDownOutside' | 'focusOutside' | 'closeButton' | 'swipe' | 'programmatic'`.
|
|
274
274
|
|
|
275
|
-
> **
|
|
275
|
+
> **The declarative and imperative surfaces spell this differently, on purpose.** The output is `(dismiss)` — an output named `close` would collide with the native DOM event and break any wrapper re-exposing it through `hostDirectives`. Nothing else changes name: the imperative handle method is `ForDrawerRef.close()`, the directive selector is `[forDrawerClose]`, and the payload type is `ForDrawerCloseReason`.
|
|
276
276
|
|
|
277
277
|
| Data attribute | Values |
|
|
278
278
|
| ------------------------ | -------------------------------------------- |
|
|
@@ -319,7 +319,7 @@ Three accepted shapes:
|
|
|
319
319
|
- `'NN%'` — equivalent to a fraction (`'50%' === 0.5`).
|
|
320
320
|
- `'NNpx'` — absolute pixel size measured from the anchored edge.
|
|
321
321
|
|
|
322
|
-
Pass them in **strictly increasing** order (closest-to-edge first)
|
|
322
|
+
Pass them in **strictly increasing** order (closest-to-edge first); the directive throws `FORCDK-DRAWER-009` otherwise. Mixed units (`'200px'` next to `0.5`) can only be ordered against the live drawer size, so they are re-checked on first measurement and fail with `FORCDK-DRAWER-010`, which names the offending point and the dimension it resolved against. `fadeFromIndex` must be a valid index into `snapPoints`.
|
|
323
323
|
|
|
324
324
|
```ts
|
|
325
325
|
[snapPoints] =
|