forty-cdk 0.6.0 → 0.8.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/LICENSE +21 -21
- package/README.md +3 -3
- package/accordion/README.md +9 -7
- package/aspect-ratio/README.md +19 -0
- package/avatar/README.md +3 -3
- package/breadcrumbs/README.md +12 -0
- package/calendar/README.md +20 -17
- package/combobox/README.md +1 -1
- package/context-menu/README.md +9 -0
- package/date-picker/README.md +25 -67
- package/date-range-field/README.md +7 -7
- package/dialog/README.md +8 -8
- package/drag-drop/README.md +11 -10
- package/drawer/README.md +9 -7
- package/fesm2022/forty-cdk-accordion.mjs +32 -26
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -1
- package/fesm2022/forty-cdk-aspect-ratio.mjs +11 -15
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -1
- package/fesm2022/forty-cdk-avatar.mjs +12 -7
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -1
- package/fesm2022/forty-cdk-breadcrumbs.mjs +32 -21
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -1
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -1
- package/fesm2022/forty-cdk-button.mjs +253 -20
- package/fesm2022/forty-cdk-button.mjs.map +1 -1
- package/fesm2022/forty-cdk-calendar.mjs +110 -77
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
- package/fesm2022/forty-cdk-carousel.mjs +13 -2
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -1
- package/fesm2022/forty-cdk-checkbox.mjs +2 -15
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-combobox.mjs +210 -151
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
- package/fesm2022/forty-cdk-context-menu.mjs +62 -5
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-core.mjs +1928 -1245
- package/fesm2022/forty-cdk-core.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-field.mjs +15 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-picker.mjs +51 -79
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-range-field.mjs +50 -110
- package/fesm2022/forty-cdk-date-range-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-dialog.mjs +72 -22
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -1
- package/fesm2022/forty-cdk-disclosure.mjs +2 -15
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -1
- package/fesm2022/forty-cdk-drag-drop.mjs +131 -22
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/forty-cdk-drawer.mjs +73 -39
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
- package/fesm2022/forty-cdk-field.mjs +53 -45
- package/fesm2022/forty-cdk-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-fieldset.mjs +2 -15
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -1
- package/fesm2022/forty-cdk-file-upload.mjs +73 -32
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -1
- package/fesm2022/forty-cdk-hover-card.mjs +32 -14
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -1
- package/fesm2022/forty-cdk-input.mjs +2 -15
- package/fesm2022/forty-cdk-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs +10 -8
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +124 -4
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-menu.mjs +191 -218
- package/fesm2022/forty-cdk-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-menubar.mjs +36 -28
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
- package/fesm2022/forty-cdk-meter.mjs +31 -29
- package/fesm2022/forty-cdk-meter.mjs.map +1 -1
- package/fesm2022/forty-cdk-navigation-menu.mjs +10 -2
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-number-input.mjs +33 -11
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-otp-input.mjs +7 -17
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-pagination.mjs +20 -8
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -1
- package/fesm2022/forty-cdk-pane-resizer.mjs +20 -17
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -1
- package/fesm2022/forty-cdk-popover.mjs +8 -1
- package/fesm2022/forty-cdk-popover.mjs.map +1 -1
- package/fesm2022/forty-cdk-progress.mjs +25 -14
- package/fesm2022/forty-cdk-progress.mjs.map +1 -1
- package/fesm2022/forty-cdk-radio-group.mjs +34 -19
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
- package/fesm2022/forty-cdk-scroll-area.mjs +18 -3
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -1
- package/fesm2022/forty-cdk-search.mjs +136 -38
- package/fesm2022/forty-cdk-search.mjs.map +1 -1
- package/fesm2022/forty-cdk-select.mjs +363 -171
- package/fesm2022/forty-cdk-select.mjs.map +1 -1
- package/fesm2022/forty-cdk-separator.mjs +1 -15
- package/fesm2022/forty-cdk-separator.mjs.map +1 -1
- package/fesm2022/forty-cdk-slider.mjs +97 -92
- package/fesm2022/forty-cdk-slider.mjs.map +1 -1
- package/fesm2022/forty-cdk-stepper.mjs +21 -7
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
- package/fesm2022/forty-cdk-switch.mjs +2 -15
- package/fesm2022/forty-cdk-switch.mjs.map +1 -1
- package/fesm2022/forty-cdk-table.mjs +478 -71
- package/fesm2022/forty-cdk-table.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-field.mjs +15 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-picker.mjs +61 -129
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-range-field.mjs +89 -115
- package/fesm2022/forty-cdk-time-range-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-toast.mjs +24 -22
- package/fesm2022/forty-cdk-toast.mjs.map +1 -1
- package/fesm2022/forty-cdk-toggle.mjs +20 -6
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -1
- package/fesm2022/forty-cdk-toolbar.mjs +10 -3
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -1
- package/fesm2022/forty-cdk-tooltip.mjs +53 -25
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
- package/fesm2022/forty-cdk-tree.mjs +145 -35
- package/fesm2022/forty-cdk-tree.mjs.map +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs +54 -27
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/field/README.md +13 -10
- package/file-upload/README.md +5 -0
- package/internationalized-date/README.md +23 -23
- package/listbox/README.md +1 -1
- package/menu/README.md +3 -1
- package/meter/README.md +12 -10
- package/package.json +1 -1
- package/progress/README.md +3 -1
- package/radio-group/README.md +14 -12
- package/scroll-area/README.md +18 -2
- package/search/README.md +34 -15
- package/select/README.md +7 -1
- package/signal-forms/README.md +1 -1
- package/stepper/README.md +15 -2
- package/table/README.md +32 -36
- package/time-range-field/README.md +23 -18
- package/toast/README.md +10 -10
- package/toggle/README.md +1 -1
- package/toolbar/README.md +4 -4
- package/tree/README.md +12 -10
- package/types/forty-cdk-accordion.d.ts +31 -28
- package/types/forty-cdk-aspect-ratio.d.ts +11 -20
- package/types/forty-cdk-avatar.d.ts +2 -2
- package/types/forty-cdk-breadcrumbs.d.ts +18 -8
- package/types/forty-cdk-button.d.ts +6 -22
- package/types/forty-cdk-calendar.d.ts +37 -26
- package/types/forty-cdk-carousel.d.ts +1 -0
- package/types/forty-cdk-checkbox.d.ts +2 -20
- package/types/forty-cdk-combobox.d.ts +120 -35
- package/types/forty-cdk-context-menu.d.ts +22 -1
- package/types/forty-cdk-core.d.ts +910 -336
- package/types/forty-cdk-date-field.d.ts +9 -1
- package/types/forty-cdk-date-picker.d.ts +33 -41
- package/types/forty-cdk-date-range-field.d.ts +24 -10
- package/types/forty-cdk-dialog.d.ts +71 -8
- package/types/forty-cdk-disclosure.d.ts +3 -20
- package/types/forty-cdk-drag-drop.d.ts +43 -7
- package/types/forty-cdk-drawer.d.ts +59 -14
- package/types/forty-cdk-dropdown-menu.d.ts +1 -0
- package/types/forty-cdk-field.d.ts +21 -33
- package/types/forty-cdk-fieldset.d.ts +1 -20
- package/types/forty-cdk-file-upload.d.ts +36 -27
- package/types/forty-cdk-hover-card.d.ts +11 -1
- package/types/forty-cdk-input.d.ts +1 -20
- package/types/forty-cdk-internationalized-date.d.ts +9 -6
- package/types/forty-cdk-listbox.d.ts +51 -2
- package/types/forty-cdk-menu.d.ts +92 -50
- package/types/forty-cdk-menubar.d.ts +22 -16
- package/types/forty-cdk-meter.d.ts +29 -27
- package/types/forty-cdk-navigation-menu.d.ts +9 -3
- package/types/forty-cdk-number-input.d.ts +15 -3
- package/types/forty-cdk-otp-input.d.ts +6 -23
- package/types/forty-cdk-pagination.d.ts +24 -2
- package/types/forty-cdk-pane-resizer.d.ts +11 -20
- package/types/forty-cdk-popover.d.ts +19 -2
- package/types/forty-cdk-progress.d.ts +31 -11
- package/types/forty-cdk-radio-group.d.ts +32 -19
- package/types/forty-cdk-scroll-area.d.ts +16 -1
- package/types/forty-cdk-search.d.ts +137 -34
- package/types/forty-cdk-select.d.ts +157 -122
- package/types/forty-cdk-separator.d.ts +1 -20
- package/types/forty-cdk-slider.d.ts +17 -17
- package/types/forty-cdk-stepper.d.ts +27 -4
- package/types/forty-cdk-switch.d.ts +1 -20
- package/types/forty-cdk-table.d.ts +133 -42
- package/types/forty-cdk-tabs.d.ts +1 -0
- package/types/forty-cdk-time-field.d.ts +9 -1
- package/types/forty-cdk-time-picker.d.ts +47 -76
- package/types/forty-cdk-time-range-field.d.ts +53 -15
- package/types/forty-cdk-toast.d.ts +3 -3
- package/types/forty-cdk-toggle.d.ts +19 -4
- package/types/forty-cdk-toolbar.d.ts +2 -0
- package/types/forty-cdk-tooltip.d.ts +41 -6
- package/types/forty-cdk-tree.d.ts +38 -2
- package/types/forty-cdk-virtualization.d.ts +16 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 tutkli
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tutkli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -36,9 +36,9 @@ Optional — install only if you use the matching entry point / primitives:
|
|
|
36
36
|
|
|
37
37
|
## Primitives
|
|
38
38
|
|
|
39
|
-
Each primitive lives under [`
|
|
39
|
+
Each primitive lives in its own folder under `projects/forty-cdk/` (e.g. [`accordion/`](accordion), [`dialog/`](dialog)) with its own `README.md` and a minimal styleless usage example.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Every primitive ships as its own **secondary entry point** — import `ForDialog` from `forty-cdk/dialog`, `ForAccordion` from `forty-cdk/accordion`, and so on — backed by the shared `forty-cdk/core` entry point. The `@internationalized/date` adapters live in a dedicated `forty-cdk/internationalized-date` entry point so that optional peer stays truly optional. The main `forty-cdk` barrel is **intentionally empty** (it exports no symbols): always import from the specific `forty-cdk/<primitive>` entry point. Standalone directives plus `"sideEffects": false` mean your bundle only ever includes the primitives you import.
|
|
42
42
|
|
|
43
43
|
## Directive → host element matrix
|
|
44
44
|
|
|
@@ -335,7 +335,7 @@ Tests run on Vitest via the Angular CLI builder `@angular/build:unit-test`:
|
|
|
335
335
|
```bash
|
|
336
336
|
pnpm test # all specs, single pass
|
|
337
337
|
pnpm exec ng test forty-cdk --watch # watch mode
|
|
338
|
-
pnpm exec ng test forty-cdk --include "
|
|
338
|
+
pnpm exec ng test forty-cdk --include "../accordion/src/accordion.spec.ts" # single file (path relative to projects/forty-cdk/src/)
|
|
339
339
|
pnpm exec ng test forty-cdk --filter "Enter and Space select" # tests by name (regex)
|
|
340
340
|
```
|
|
341
341
|
|
package/accordion/README.md
CHANGED
|
@@ -56,17 +56,19 @@ export class DemoFaq {
|
|
|
56
56
|
|
|
57
57
|
### `ForAccordion`
|
|
58
58
|
|
|
59
|
-
| Property | Type | Description
|
|
60
|
-
| ------------- | ----------------------------------- |
|
|
61
|
-
| `value` | `model<readonly string[]>` | Currently open item values. In single mode the array has 0 or 1 element.<br>**Default:** —
|
|
62
|
-
| `multiple` | `input<boolean>` | When true, multiple items can be open simultaneously.<br>**Default:** `false`
|
|
63
|
-
| `collapsible` | `input<boolean>` | Single mode only: when true, the open item can be collapsed by clicking it. Otherwise once any item is open, exactly one stays open.<br>**Default:** `false`
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
59
|
+
| Property | Type | Description |
|
|
60
|
+
| ------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
61
|
+
| `value` | `model<readonly string[]>` | Currently open item values. In single mode the array has 0 or 1 element.<br>**Default:** — |
|
|
62
|
+
| `multiple` | `input<boolean>` | When true, multiple items can be open simultaneously.<br>**Default:** `false` |
|
|
63
|
+
| `collapsible` | `input<boolean>` | Single mode only: when true, the open item can be collapsed by clicking it. Otherwise once any item is open, exactly one stays open.<br>**Default:** `false` |
|
|
64
|
+
| `disabled` | `input<boolean>` | When true, disables every item — each trigger reflects the native `disabled` attribute and cannot toggle. Composes with a per-item `[disabled]`.<br>**Default:** `false` |
|
|
65
|
+
| `orientation` | `input<'horizontal' \| 'vertical'>` | Layout direction of the trigger list. In horizontal mode ArrowLeft/Right replace ArrowUp/Down.<br>**Default:** `'vertical'` |
|
|
66
|
+
| `dir` | `input<'ltr' \| 'rtl'>` | Writing direction. Only relevant in horizontal mode — swaps the meaning of Left/Right arrows.<br>**Default:** — |
|
|
66
67
|
|
|
67
68
|
| Data attribute | Values |
|
|
68
69
|
| ------------------ | -------------------------- |
|
|
69
70
|
| `data-orientation` | `horizontal` \| `vertical` |
|
|
71
|
+
| `data-disabled` | present \| absent |
|
|
70
72
|
|
|
71
73
|
### `ForAccordionItem`
|
|
72
74
|
|
package/aspect-ratio/README.md
CHANGED
|
@@ -4,6 +4,25 @@ A container that keeps its content at a fixed width-to-height ratio.
|
|
|
4
4
|
|
|
5
5
|
Pure visual utility — it locks an element's box via the native CSS `aspect-ratio` property, with no ARIA semantics. Reach for it to reserve space for media before it loads (preventing layout shift), keep cards on a grid uniform, or wrap responsive iframes.
|
|
6
6
|
|
|
7
|
+
## Why this exists
|
|
8
|
+
|
|
9
|
+
A fixed, never-changing ratio is one line of CSS — you don't need this primitive for that:
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
.card-cover {
|
|
13
|
+
aspect-ratio: 16 / 9;
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`[forAspectRatio]` earns its place when the ratio is **dynamic or must be validated**. It is more than the static declaration:
|
|
18
|
+
|
|
19
|
+
- **Reactive `ratio` input.** Bind `[ratio]="ratio()"` and the host style recomputes as the value changes — no manual style writes.
|
|
20
|
+
- **Invalid-value guarding.** `0`, negative, and non-finite ratios fall back to `1`, so a bad computed value never emits invalid CSS.
|
|
21
|
+
- **SSR-safe.** The `aspect-ratio` style is bound declaratively (never touched imperatively), so it renders identically on the server and hydrates cleanly.
|
|
22
|
+
- **Consistent headless API.** Same shape as the other primitives, so it composes the same way.
|
|
23
|
+
|
|
24
|
+
If your ratio is a literal constant, prefer the CSS property directly and keep the bundle leaner. Import `[forAspectRatio]` when reactivity or validation buys you something.
|
|
25
|
+
|
|
7
26
|
## Anatomy
|
|
8
27
|
|
|
9
28
|
```html
|
package/avatar/README.md
CHANGED
|
@@ -78,9 +78,9 @@ export class DemoAvatar {
|
|
|
78
78
|
|
|
79
79
|
### `ForAvatarImage`
|
|
80
80
|
|
|
81
|
-
| Property
|
|
82
|
-
|
|
|
83
|
-
| `(
|
|
81
|
+
| Property | Type | Description |
|
|
82
|
+
| -------------------- | ------------------------- | ------------------------------------------------------------------- |
|
|
83
|
+
| `(loadStatusChange)` | `output<ForAvatarStatus>` | Output. Emits whenever the lifecycle transitions.<br>**Default:** — |
|
|
84
84
|
|
|
85
85
|
| Data attribute | Values |
|
|
86
86
|
| -------------- | ------------------------------------------ |
|
package/breadcrumbs/README.md
CHANGED
|
@@ -40,6 +40,18 @@ export class DemoBreadcrumbs {}
|
|
|
40
40
|
|
|
41
41
|
The root defaults its label to `Breadcrumb`. Override it with `ariaLabel="…"` (or point a native `aria-labelledby` at a visible heading) when a page hosts more than one breadcrumb trail.
|
|
42
42
|
|
|
43
|
+
### Localizing the label
|
|
44
|
+
|
|
45
|
+
`Breadcrumb` is verbalized by screen readers, so translate it per injector scope with `provideForBreadcrumbsDefaults`. Configure it at the application root, or in any component's `providers` to scope the translation to a subtree. A per-instance `[ariaLabel]` still wins over the scope default.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { provideForBreadcrumbsDefaults } from 'forty-cdk/breadcrumbs';
|
|
49
|
+
|
|
50
|
+
bootstrapApplication(App, {
|
|
51
|
+
providers: [provideForBreadcrumbsDefaults({ label: 'Ruta de navegación' })],
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
43
55
|
## API
|
|
44
56
|
|
|
45
57
|
### `ForBreadcrumbs`
|
package/calendar/README.md
CHANGED
|
@@ -26,6 +26,8 @@ bootstrapApplication(App, {
|
|
|
26
26
|
|
|
27
27
|
`@internationalized/date` is a widely-used immutable date primitive; it works in every browser today with no polyfill, and its reference-equality-on-mutation makes it signal-friendly. Both `@internationalized/date` adapters operate on the **Gregorian** calendar today — `createDate` always builds a Gregorian date, so the grid stays Gregorian regardless of the runtime locale. True non-Gregorian calendar systems are deferred to the planned `Temporal.PlainDate` adapter ([#354](https://github.com/tutkli/forty-cdk/issues/354)), a non-breaking addition once the Temporal API is broadly available across browsers — the `DateAdapter<D>` seam means adopting it later is a drop-in, not a migration.
|
|
28
28
|
|
|
29
|
+
**Calendar system (Gregorian).** The adapter seam abstracts the date _library_ and locale-aware _formatting_, not the calendar _system_'s month structure. The grid, the month picker and the date field 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 out of scope; calendar-system pluggability would be revisited with the `Temporal.PlainDate` adapter track ([#354](https://github.com/tutkli/forty-cdk/issues/354)). The optional `compareDate` hook overrides day-only _ordering_ only — it does not make the grid non-Gregorian.
|
|
30
|
+
|
|
29
31
|
## Anatomy
|
|
30
32
|
|
|
31
33
|
```html
|
|
@@ -121,21 +123,22 @@ The library is styleless: style the boolean `data-*` hooks on `[forCalendarCell]
|
|
|
121
123
|
|
|
122
124
|
### `ForCalendar`
|
|
123
125
|
|
|
124
|
-
| Property | Type | Description
|
|
125
|
-
| ------------------- | -------------------------------------- |
|
|
126
|
-
| `value` | `model<D \| null>` | Two-way bindable selected date, or `null`. Used in `selectionMode="single"`. `(valueChange)` fires only on internal selection.<br>**Default:** `null`
|
|
127
|
-
| `selectionMode` | `input<'single' \| 'range'>` | `'single'` (default) keeps the single-date `value` flow. `'range'` switches to anchor → commit and exposes `range`.<br>**Default:** `'single'`
|
|
128
|
-
| `range` | `model<
|
|
129
|
-
| `minRangeLength` | `input<number \| null>` | Minimum inclusive day count. A commit shorter than this is a no-op.<br>**Default:** `null` (no minimum)
|
|
130
|
-
| `maxRangeLength` | `input<number \| null>` | Maximum inclusive day count. A commit longer than this is a no-op.<br>**Default:** `null` (no maximum)
|
|
131
|
-
| `min` | `input<D \| null>` | Minimum selectable date (inclusive). Earlier dates are unavailable.<br>**Default:** `null`
|
|
132
|
-
| `max` | `input<D \| null>` | Maximum selectable date (inclusive). Later dates are unavailable.<br>**Default:** `null`
|
|
133
|
-
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate marking a date unavailable (present but not selectable).<br>**Default:** `() => false`
|
|
134
|
-
| `dateLabel` | `input<CalendarDateLabelFormatter<D>>` | Formats each gridcell's `aria-label` (full accessible date).<br>**Default:** localized full date, outside-month days suffixed
|
|
135
|
-
| `disabled` | `input<boolean>` | Disables the whole calendar (no focus movement, no selection). Reflected as `data-disabled`.<br>**Default:** —
|
|
136
|
-
| `readonly` | `input<boolean>` | Read-only: dates stay focusable, selection is blocked. Reflected as `data-readonly`.<br>**Default:** —
|
|
137
|
-
| `firstDayOfWeek` | `input<number \| null>` | First column's weekday, **0-6** (`0` = Sunday).<br>**Default:** `null` → the adapter's value (or `provideForCalendarDefaults`)
|
|
138
|
-
| `
|
|
126
|
+
| Property | Type | Description |
|
|
127
|
+
| ------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
128
|
+
| `value` | `model<D \| null>` | Two-way bindable selected date, or `null`. Used in `selectionMode="single"`. `(valueChange)` fires only on internal selection.<br>**Default:** `null` |
|
|
129
|
+
| `selectionMode` | `input<'single' \| 'range'>` | `'single'` (default) keeps the single-date `value` flow. `'range'` switches to anchor → commit and exposes `range`.<br>**Default:** `'single'` |
|
|
130
|
+
| `range` | `model<DateRange<D> \| null>` | Two-way bindable committed range. Only used in `selectionMode="range"`. `(rangeChange)` fires only on internal commits/clears.<br>**Default:** `null` |
|
|
131
|
+
| `minRangeLength` | `input<number \| null>` | Minimum inclusive day count. A commit shorter than this is a no-op.<br>**Default:** `null` (no minimum) |
|
|
132
|
+
| `maxRangeLength` | `input<number \| null>` | Maximum inclusive day count. A commit longer than this is a no-op.<br>**Default:** `null` (no maximum) |
|
|
133
|
+
| `min` | `input<D \| null>` | Minimum selectable date (inclusive). Earlier dates are unavailable.<br>**Default:** `null` |
|
|
134
|
+
| `max` | `input<D \| null>` | Maximum selectable date (inclusive). Later dates are unavailable.<br>**Default:** `null` |
|
|
135
|
+
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate marking a date unavailable (present but not selectable).<br>**Default:** `() => false` |
|
|
136
|
+
| `dateLabel` | `input<CalendarDateLabelFormatter<D>>` | Formats each gridcell's `aria-label` (full accessible date).<br>**Default:** localized full date, outside-month days suffixed |
|
|
137
|
+
| `disabled` | `input<boolean>` | Disables the whole calendar (no focus movement, no selection). Reflected as `data-disabled`.<br>**Default:** — |
|
|
138
|
+
| `readonly` | `input<boolean>` | Read-only: dates stay focusable, selection is blocked. Reflected as `data-readonly`.<br>**Default:** — |
|
|
139
|
+
| `firstDayOfWeek` | `input<number \| null>` | First column's weekday, **0-6** (`0` = Sunday).<br>**Default:** `null` → the adapter's value (or `provideForCalendarDefaults`) |
|
|
140
|
+
| `locale` | `input<string \| null>` | BCP 47 locale for the heading, weekday headers, month-picker options and cell `aria-label` names. The calendar system stays Gregorian.<br>**Default:** `null` → the runtime's default locale |
|
|
141
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction.<br>**Default:** `null` resolves the ambient direction; reflected to the host `dir` and mirrors horizontal arrows |
|
|
139
142
|
|
|
140
143
|
### Data attributes
|
|
141
144
|
|
|
@@ -161,9 +164,9 @@ Set `selectionMode="range"` and bind `[(range)]` to get date-range selection. In
|
|
|
161
164
|
</div>
|
|
162
165
|
```
|
|
163
166
|
|
|
164
|
-
**Interaction model.** Click (or `Enter` / `Space`) a first cell to set the anchor; the grid enters selecting state. Click (or `Enter` / `Space`) a second cell
|
|
167
|
+
**Interaction model.** Click (or `Enter` / `Space`) a first cell to set the anchor; the grid enters selecting state. Click (or `Enter` / `Space`) a second cell **in either direction** to commit the range — clicking before the anchor commits the inverted band `[click, anchor]` (matching the hover preview), it does not start over. There is no separate "start over" gesture and no explicit Escape-to-cancel: once a range is committed, the next click begins a fresh anchor.
|
|
165
168
|
|
|
166
|
-
**Keyboard in range mode.** `Enter` / `Space` on the focused cell sets the anchor on the first press and commits on the second (same key as single mode). While selecting, arrow / `Home` / `End` / `PageUp` / `PageDown` move the keyboard focus and update the preview
|
|
169
|
+
**Keyboard in range mode.** `Enter` / `Space` on the focused cell sets the anchor on the first press and commits on the second (same key as single mode). While selecting, arrow / `Home` / `End` / `PageUp` / `PageDown` move the keyboard focus and update the preview cursor (the keyboard equivalent of pointer hover); moving before the anchor previews — and commits — the inverted band.
|
|
167
170
|
|
|
168
171
|
**`min` / `max` / `isDateUnavailable`** still gate both endpoints. An unavailable or out-of-bounds date cannot become an anchor or an end.
|
|
169
172
|
|
package/combobox/README.md
CHANGED
|
@@ -22,7 +22,7 @@ The editable (default) anatomy — an `<input>` that filters a portaled listbox
|
|
|
22
22
|
```html
|
|
23
23
|
<div forCombobox [(query)]="query" [(value)]="value">
|
|
24
24
|
<input forComboboxInput placeholder="Search…" />
|
|
25
|
-
<button forComboboxClear
|
|
25
|
+
<button forComboboxClear>×</button>
|
|
26
26
|
|
|
27
27
|
<!-- @if (open()) { -->
|
|
28
28
|
<div forComboboxContent>
|
package/context-menu/README.md
CHANGED
|
@@ -154,4 +154,13 @@ forty-cdk ships no styles. Add your own class to each piece — the for\* select
|
|
|
154
154
|
- **Virtual anchor.** Right-click captures a 0×0 rect at the pointer location. `Shift+F10` and `ContextMenu` snapshot the bounding rect of the focused element (or the trigger if focus is on it directly), so the menu floats off the element under attention. Both forms feed floating-ui's `flip` and `shift` middleware, so corners and screen edges work without special-casing.
|
|
155
155
|
- **Keyboard activators only fire while focus is inside the trigger.** Keyboard events dispatch to the focused element, so `Shift+F10` / `ContextMenu` anywhere outside the trigger goes to the browser default. The trigger is focusable by default (host-bound `tabindex="-1"`), so this works out of the box; use `tabindex="0"` if you want the region itself reachable via Tab.
|
|
156
156
|
- **Native menu suppressed.** The trigger calls `event.preventDefault()` on `contextmenu` and on the keyboard activators. Set `disabled` to let the browser's native menu surface for that region.
|
|
157
|
+
- **Touch long-press.** The trigger runs its own long-press timer (a `touch` `pointerdown` held ~500 ms, without lifting or moving past a small tolerance, opens the menu at the touch point). This is required because iOS Safari never fires the `contextmenu` event a long-press synthesizes elsewhere; where the browser does synthesize it (Android, desktop touch emulation) the two paths stay mutually exclusive, so the menu opens exactly once. For the press to survive on iOS, suppress the native callout / text-selection on the trigger with CSS — otherwise the OS gesture cancels the press:
|
|
158
|
+
|
|
159
|
+
```css
|
|
160
|
+
.context-menu-trigger {
|
|
161
|
+
-webkit-touch-callout: none;
|
|
162
|
+
user-select: none;
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
157
166
|
- **Mount equals open.** Same convention as the rest of the library — wrap `[forMenuContent]` in `@if (open())` and use `animate.enter` / `animate.leave` for transitions.
|
package/date-picker/README.md
CHANGED
|
@@ -140,26 +140,29 @@ The library is styleless: presence in the DOM is the consumer's job (`@if (open(
|
|
|
140
140
|
|
|
141
141
|
### `ForDatePicker`
|
|
142
142
|
|
|
143
|
-
| Property | Type | Description
|
|
144
|
-
| ------------------- | ------------------------------------------------ |
|
|
145
|
-
| `value` | `model<D \| null>` | Two-way bindable selected date. `(valueChange)` fires only on internal commits.<br>**Default:** `null`
|
|
146
|
-
| `open` | `model<boolean>` | Two-way bindable surface visibility. `(openChange)` fires only on internal transitions.<br>**Default:** `false`
|
|
147
|
-
| `minDate` | `input<D \| null>` | Minimum selectable date (inclusive). Forward to the projected calendar's `[min]`.<br>**Default:** `null`
|
|
148
|
-
| `maxDate` | `input<D \| null>` | Maximum selectable date (inclusive). Forward to the projected calendar's `[max]`.<br>**Default:** `null`
|
|
149
|
-
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate. Forward to the projected calendar's `[isDateUnavailable]`.<br>**Default:** `() => false`
|
|
150
|
-
| `closeOnSelect` | `input<boolean>` | Close the surface after a date is picked. Honoured only at `granularity="day"`.<br>**Default:** `true`
|
|
151
|
-
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision. `'day'` (default) is a pure date picker; coarser-than-day off composes a time field.<br>**Default:** `'day'`
|
|
152
|
-
| `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the value display (and typically the projected `[forTimeField]`).<br>**Default:** `null` → locale
|
|
153
|
-
| `modal` | `input<boolean>` | Trap focus + inert background + scroll lock (centered dialog) instead of an anchored popover.<br>**Default:** `false`
|
|
154
|
-
| `dismissible` | `input<boolean>` | Escape / outside-pointer dismiss the surface.<br>**Default:** `true`
|
|
155
|
-
| `returnFocus` | `input<boolean>` | Return focus to the trigger on close.<br>**Default:** `true`
|
|
156
|
-
| `formatOptions` | `input<Intl.DateTimeFormatOptions>` | Options for the text rendered by `[forDatePickerValue]`.<br>**Default:** `{ year: 'numeric', month: 'long', day: 'numeric' }`
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
143
|
+
| Property | Type | Description |
|
|
144
|
+
| ------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| `value` | `model<D \| null>` | Two-way bindable selected date. `(valueChange)` fires only on internal commits.<br>**Default:** `null` |
|
|
146
|
+
| `open` | `model<boolean>` | Two-way bindable surface visibility. `(openChange)` fires only on internal transitions.<br>**Default:** `false` |
|
|
147
|
+
| `minDate` | `input<D \| null>` | Minimum selectable date (inclusive). Forward to the projected calendar's `[min]`.<br>**Default:** `null` |
|
|
148
|
+
| `maxDate` | `input<D \| null>` | Maximum selectable date (inclusive). Forward to the projected calendar's `[max]`.<br>**Default:** `null` |
|
|
149
|
+
| `isDateUnavailable` | `input<(date: D) => boolean>` | Per-date predicate. Forward to the projected calendar's `[isDateUnavailable]`.<br>**Default:** `() => false` |
|
|
150
|
+
| `closeOnSelect` | `input<boolean>` | Close the surface after a date is picked. Honoured only at `granularity="day"`.<br>**Default:** `true` |
|
|
151
|
+
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision. `'day'` (default) is a pure date picker; coarser-than-day off composes a time field.<br>**Default:** `'day'` |
|
|
152
|
+
| `hourCycle` | `input<12 \| 24 \| null>` | 12/24-hour cycle for the value display (and typically the projected `[forTimeField]`).<br>**Default:** `null` → locale |
|
|
153
|
+
| `modal` | `input<boolean>` | Trap focus + inert background + scroll lock (centered dialog) instead of an anchored popover.<br>**Default:** `false` |
|
|
154
|
+
| `dismissible` | `input<boolean>` | Escape / outside-pointer dismiss the surface.<br>**Default:** `true` |
|
|
155
|
+
| `returnFocus` | `input<boolean>` | Return focus to the trigger on close.<br>**Default:** `true` |
|
|
156
|
+
| `formatOptions` | `input<Intl.DateTimeFormatOptions>` | Options for the text rendered by `[forDatePickerValue]`.<br>**Default:** `{ year: 'numeric', month: 'long', day: 'numeric' }` |
|
|
157
|
+
| `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 |
|
|
158
|
+
| `placeholder` | `input<string>` | Fallback text for `[forDatePickerValue]` when empty.<br>**Default:** `''` |
|
|
159
|
+
| `side` / `align` | `input` | Anchored placement (popover mode only).<br>**Default:** `'bottom'` / `'start'` |
|
|
160
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction.<br>**Default:** `null` resolves the ambient direction; reflected to the host `dir` |
|
|
160
161
|
|
|
161
162
|
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`).
|
|
162
163
|
|
|
164
|
+
> **`locale` only styles the trigger display.** It drives the text rendered by `[forDatePickerValue]` (both here and on `ForDateRangePicker`); it is **not** forwarded to the projected `ForCalendar`. Bind the calendar's own `[locale]` to localize its heading / weekday / cell labels, exactly as you forward `[min]` / `[max]`.
|
|
165
|
+
|
|
163
166
|
> **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`.)
|
|
164
167
|
|
|
165
168
|
### Data attributes
|
|
@@ -265,63 +268,18 @@ Bind the calendar **and** the time field **one-way** to `picker.value()` (not `[
|
|
|
265
268
|
|
|
266
269
|
The value display (`[forDatePickerValue]`) automatically appends the time to its formatting when `granularity > 'day'` and you haven't set time fields in `formatOptions`.
|
|
267
270
|
|
|
268
|
-
## Range selection
|
|
269
|
-
|
|
270
|
-
Set `selectionMode="range"` on both the picker root and the projected calendar and bind `[(range)]` to a `CalendarDateRange<D> | null` signal.
|
|
271
|
-
|
|
272
|
-
```ts
|
|
273
|
-
import { type CalendarDateRange } from 'forty-cdk/calendar';
|
|
274
|
-
|
|
275
|
-
readonly dateRange = signal<CalendarDateRange<CalendarDate> | null>(null);
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
```html
|
|
279
|
-
<div
|
|
280
|
-
forDatePicker
|
|
281
|
-
selectionMode="range"
|
|
282
|
-
[(range)]="dateRange"
|
|
283
|
-
[(open)]="open"
|
|
284
|
-
[ariaLabel]="'Choose date range'"
|
|
285
|
-
>
|
|
286
|
-
<button forDatePickerTrigger>
|
|
287
|
-
<span forDatePickerValue [placeholder]="'Pick a range'"></span>
|
|
288
|
-
</button>
|
|
289
|
-
|
|
290
|
-
@if (open()) {
|
|
291
|
-
<div forDatePickerContent>
|
|
292
|
-
<div forCalendar selectionMode="range" [(range)]="dateRange">
|
|
293
|
-
<!-- …header + grid… -->
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
}
|
|
297
|
-
</div>
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
**`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]`.
|
|
301
|
-
|
|
302
|
-
**`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.
|
|
303
|
-
|
|
304
|
-
**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.
|
|
305
|
-
|
|
306
|
-
| New input / model | Type | Description |
|
|
307
|
-
| ----------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
308
|
-
| `selectionMode` | `input<'single' \| 'range'>` | `'single'` keeps the existing `value` flow. `'range'` switches to range mode. |
|
|
309
|
-
| `range` | `model<CalendarDateRange<D> \| null>` | Two-way bindable committed range. `(rangeChange)` fires only on commit / clear. Default `null`. |
|
|
310
|
-
| `rangeSeparator` | `input<string>` | String placed between start and end in the formatted display. Default `' – '`. |
|
|
311
|
-
|
|
312
|
-
## Range as a Signal Forms value — `ForDateRangePicker`
|
|
271
|
+
## Range selection — `ForDateRangePicker`
|
|
313
272
|
|
|
314
|
-
|
|
273
|
+
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.
|
|
315
274
|
|
|
316
275
|
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 in v1 (no time composition).
|
|
317
276
|
|
|
318
277
|
```ts
|
|
319
|
-
import { type
|
|
320
|
-
import { ForDateRangePicker } from 'forty-cdk/date-picker';
|
|
278
|
+
import { type DateRange, ForDateRangePicker } from 'forty-cdk/date-picker';
|
|
321
279
|
import { form } from '@angular/forms/signals';
|
|
322
280
|
|
|
323
281
|
interface Booking {
|
|
324
|
-
stay:
|
|
282
|
+
stay: DateRange<CalendarDate> | null;
|
|
325
283
|
}
|
|
326
284
|
readonly model = signal<Booking>({ stay: null });
|
|
327
285
|
readonly booking = form(this.model, (p) => required(p.stay));
|
|
@@ -355,7 +313,7 @@ readonly booking = form(this.model, (p) => required(p.stay));
|
|
|
355
313
|
</div>
|
|
356
314
|
```
|
|
357
315
|
|
|
358
|
-
- **Form value.** The committed `
|
|
316
|
+
- **Form value.** The committed `DateRange<D> | null` is the `value` model. `null` is the empty state — pair it with `required(p.stay)` so `invalid()` flips when the form demands a range and none is committed. `touched` fires on commit and on close, exactly like the single-date picker.
|
|
359
317
|
- **Validity.** `start <= end` is guaranteed by construction and is never an error. Forward `minDate` / `maxDate` to the calendar's `[min]` / `[max]`, and `minRangeLength` / `maxRangeLength` to the calendar's `[minRangeLength]` / `[maxRangeLength]` (a too-short / too-long range is rejected as a no-op by the calendar's two-click flow).
|
|
360
318
|
- **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.
|
|
361
319
|
- **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.
|
|
@@ -4,7 +4,7 @@ A segmented date (and optional time) range input over a pluggable date adapter:
|
|
|
4
4
|
|
|
5
5
|
Headless, segmented, spin-editable — the keyboard-first, form-capable counterpart to [DateRangePicker](../date-picker/README.md). There is **no single WAI-ARIA APG pattern** for a range field; it is a composition of two labelled `role="group"` endpoints (start / end), each holding a row of spinbutton segments — the same machinery as [DateField](../date-field/README.md) — nested inside one outer `role="group"`. Segment **order** and separators follow the runtime locale (`MM/DD/YYYY` vs `DD.MM.YYYY` vs `YYYY/MM/DD`).
|
|
6
6
|
|
|
7
|
-
`ForDateRangeField` implements `FormValueControl<
|
|
7
|
+
`ForDateRangeField` implements `FormValueControl<DateRange<D> | null>` from `@angular/forms/signals` — the **same** contract as `ForDateRangePicker` — so the committed range auto-wires with `[formField]` and auto-associates inside a `[forField]` (label / description / error) with no extra markup. The value stays `null` until **both** endpoints are fully entered and ordered (`start <= end`); a half-entered or out-of-order range never reaches the form.
|
|
8
8
|
|
|
9
9
|
## Date adapter
|
|
10
10
|
|
|
@@ -43,8 +43,8 @@ Pick one (required). All date math goes through the same pluggable `DateAdapter<
|
|
|
43
43
|
```ts
|
|
44
44
|
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
45
45
|
import { CalendarDate } from '@internationalized/date';
|
|
46
|
-
import { CalendarDateRange } from 'forty-cdk/calendar';
|
|
47
46
|
import {
|
|
47
|
+
type DateRange,
|
|
48
48
|
ForDateRangeField,
|
|
49
49
|
ForDateRangeFieldEnd,
|
|
50
50
|
ForDateRangeFieldLiteral,
|
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
`,
|
|
92
92
|
})
|
|
93
93
|
export class StayField {
|
|
94
|
-
readonly stay = signal<
|
|
94
|
+
readonly stay = signal<DateRange<CalendarDate> | null>(null);
|
|
95
95
|
}
|
|
96
96
|
```
|
|
97
97
|
|
|
@@ -101,8 +101,8 @@ export class StayField {
|
|
|
101
101
|
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
102
102
|
import { form } from '@angular/forms/signals';
|
|
103
103
|
import { CalendarDate } from '@internationalized/date';
|
|
104
|
-
import { CalendarDateRange } from 'forty-cdk/calendar';
|
|
105
104
|
import {
|
|
105
|
+
type DateRange,
|
|
106
106
|
ForDateRangeField,
|
|
107
107
|
ForDateRangeFieldEnd,
|
|
108
108
|
ForDateRangeFieldLiteral,
|
|
@@ -149,7 +149,7 @@ import {
|
|
|
149
149
|
`,
|
|
150
150
|
})
|
|
151
151
|
export class StayFormField {
|
|
152
|
-
readonly model = signal({ stay: null as
|
|
152
|
+
readonly model = signal({ stay: null as DateRange<CalendarDate> | null });
|
|
153
153
|
readonly booking = form(this.model);
|
|
154
154
|
}
|
|
155
155
|
```
|
|
@@ -160,7 +160,7 @@ export class StayFormField {
|
|
|
160
160
|
|
|
161
161
|
| Property | Type | Description |
|
|
162
162
|
| ------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
163
|
-
| `value` | `model<
|
|
163
|
+
| `value` | `model<DateRange<D> \| null>` | Two-way bindable committed range, or `null` while incomplete or out of order. The `FormValueControl` backing.<br>**Default:** `null` |
|
|
164
164
|
| `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` |
|
|
165
165
|
| `maxDate` | `input<D \| null>` | Maximum date (inclusive) for both endpoints. A composed endpoint above it is clamped down.<br>**Default:** `null` |
|
|
166
166
|
| `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'` |
|
|
@@ -193,7 +193,7 @@ Plus the shared `FormUiControl` members from `@angular/forms/signals`: `disabled
|
|
|
193
193
|
|
|
194
194
|
## Ordering
|
|
195
195
|
|
|
196
|
-
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 `
|
|
196
|
+
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.
|
|
197
197
|
|
|
198
198
|
## Date-time range
|
|
199
199
|
|
package/dialog/README.md
CHANGED
|
@@ -125,7 +125,7 @@ export class DemoHost {
|
|
|
125
125
|
ConfirmDialog,
|
|
126
126
|
{ data: { message: 'Are you sure?' } },
|
|
127
127
|
);
|
|
128
|
-
const result = await ref.closed; // 'confirm' | 'cancel' | undefined
|
|
128
|
+
const { result } = await ref.closed; // result: 'confirm' | 'cancel' | undefined
|
|
129
129
|
if (result === 'confirm') {
|
|
130
130
|
/* ... */
|
|
131
131
|
}
|
|
@@ -203,7 +203,7 @@ Set them once for a scope with `provideForDialogDefaults({ animateEnter, animate
|
|
|
203
203
|
| `returnFocus` | — | Focus returns to the previously focused element on close.<br>**Default:** `true` |
|
|
204
204
|
| `initialFocus` | — | `'first'` (first focusable inside) or `'container'` (the dialog host).<br>**Default:** `'first'` |
|
|
205
205
|
| `ariaLabel` | — | Manual `aria-label` if no `[forDialogTitle]` is rendered.<br>**Default:** `null` |
|
|
206
|
-
| `
|
|
206
|
+
| `dismiss` | `OutputEmitterRef<ForDialogCloseReason>` | Output. Dialog wants to be unmounted. Reasons: `'escape'`, `'backdrop'`, `'pointerDownOutside'`, `'focusOutside'`, `'closeButton'`, `'programmatic'`.<br>**Default:** — |
|
|
207
207
|
| `escapeKeyDown` | `OutputEmitterRef<VetoableNativeEvent<KeyboardEvent>>` | Output. Escape while this dialog is the topmost dismissable layer.<br>**Default:** — |
|
|
208
208
|
| `pointerDownOutside` | `OutputEmitterRef<VetoableNativeEvent<PointerEvent>>` | Output. Pointer-down outside the dialog.<br>**Default:** — |
|
|
209
209
|
| `focusOutside` | `OutputEmitterRef<VetoableNativeEvent<FocusEvent>>` | Output. Focus moves outside the dialog.<br>**Default:** — |
|
|
@@ -302,12 +302,12 @@ The dialog still installs the focus trap (so Tab cycles inside once focus enters
|
|
|
302
302
|
|
|
303
303
|
## Programmatic API
|
|
304
304
|
|
|
305
|
-
| Symbol | Description
|
|
306
|
-
| ----------------------- |
|
|
307
|
-
| `ForDialogManager` | Injectable. `open(component, config?)` returns a `ForDialogRef<R>`.
|
|
308
|
-
| `ForDialogRef<R>` | `close(result?)`, `closed: Promise<R \| undefined>`, `result: Signal<R \| undefined>`, `isClosed: Signal<boolean>`. |
|
|
309
|
-
| `FOR_DIALOG_DATA` | Token for the `data` payload. Inject in the opened component.
|
|
310
|
-
| `injectDialogData<T>()` | Typed accessor for `FOR_DIALOG_DATA`. Returns `T \| null` — `null` when `open()` got no `data`.
|
|
305
|
+
| Symbol | Description |
|
|
306
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
307
|
+
| `ForDialogManager` | Injectable. `open(component, config?)` returns a `ForDialogRef<R>`. |
|
|
308
|
+
| `ForDialogRef<R>` | `close(result?)`, `closed: Promise<{ reason: ForDialogCloseReason; result: R \| undefined }>`, `result: Signal<R \| undefined>`, `isClosed: Signal<boolean>`. |
|
|
309
|
+
| `FOR_DIALOG_DATA` | Token for the `data` payload. Inject in the opened component. |
|
|
310
|
+
| `injectDialogData<T>()` | Typed accessor for `FOR_DIALOG_DATA`. Returns `T \| null` — `null` when `open()` got no `data`. |
|
|
311
311
|
|
|
312
312
|
### `ForDialogOpenConfig`
|
|
313
313
|
|
package/drag-drop/README.md
CHANGED
|
@@ -7,14 +7,15 @@ whole dialog around by its header — see [`[forFreeDrag]`](#free-drag).
|
|
|
7
7
|
|
|
8
8
|
## Keyboard
|
|
9
9
|
|
|
10
|
-
| State | Key | Action
|
|
11
|
-
| ------ | ----------- |
|
|
12
|
-
| Idle | Arrow keys | Move roving focus between items
|
|
13
|
-
| Idle | Home / End | Jump to first / last item
|
|
14
|
-
| Idle | Space/Enter | **Lift** the focused item
|
|
15
|
-
| Lifted | Arrow keys | Step the logical drop position
|
|
16
|
-
| Lifted |
|
|
17
|
-
| Lifted |
|
|
10
|
+
| State | Key | Action |
|
|
11
|
+
| ------ | ----------- | ------------------------------------------------- |
|
|
12
|
+
| Idle | Arrow keys | Move roving focus between items |
|
|
13
|
+
| Idle | Home / End | Jump to first / last item |
|
|
14
|
+
| Idle | Space/Enter | **Lift** the focused item |
|
|
15
|
+
| Lifted | Arrow keys | Step the logical drop position |
|
|
16
|
+
| Lifted | Home / End | Jump the lifted item to the first / last position |
|
|
17
|
+
| Lifted | Space/Enter | **Drop** (commits and emits `(dragDrop)`) |
|
|
18
|
+
| Lifted | Escape | **Cancel** (no event, focus stays on item) |
|
|
18
19
|
|
|
19
20
|
Arrow direction follows the list's `orientation` and respects RTL via `dir`. In
|
|
20
21
|
`orientation="mixed"` every arrow key steps the lifted item linearly in DOM order.
|
|
@@ -347,7 +348,7 @@ for the full analysis.
|
|
|
347
348
|
```
|
|
348
349
|
|
|
349
350
|
```ts
|
|
350
|
-
onDrop(event: ForDragDropEvent
|
|
351
|
+
onDrop(event: ForDragDropEvent): void {
|
|
351
352
|
this.items.set(
|
|
352
353
|
moveItemInArray(this.items(), event.previousIndex, event.currentIndex),
|
|
353
354
|
);
|
|
@@ -372,7 +373,7 @@ onDrop(event: ForDragDropEvent<MyItem>): void {
|
|
|
372
373
|
```
|
|
373
374
|
|
|
374
375
|
```ts
|
|
375
|
-
onDrop(event: ForDragDropEvent
|
|
376
|
+
onDrop(event: ForDragDropEvent): void {
|
|
376
377
|
if (event.previousContainer === event.container) {
|
|
377
378
|
this.updateList(event.container, (arr) =>
|
|
378
379
|
moveItemInArray(arr, event.previousIndex, event.currentIndex),
|
package/drawer/README.md
CHANGED
|
@@ -127,7 +127,7 @@ import {
|
|
|
127
127
|
></div>
|
|
128
128
|
<div forDrawerHandle aria-hidden="true"></div>
|
|
129
129
|
<h2 forDrawerTitle>Delete account?</h2>
|
|
130
|
-
<p forDrawerDescription>{{ data
|
|
130
|
+
<p forDrawerDescription>{{ data?.message }}</p>
|
|
131
131
|
<button forDrawerClose [closeWith]="'cancel'">Cancel</button>
|
|
132
132
|
<button forDrawerClose [closeWith]="'confirm'">Confirm</button>
|
|
133
133
|
`,
|
|
@@ -150,7 +150,7 @@ class DemoHost {
|
|
|
150
150
|
side: 'bottom',
|
|
151
151
|
snapPoints: ['148px', 1],
|
|
152
152
|
});
|
|
153
|
-
const result = await ref.closed;
|
|
153
|
+
const { result } = await ref.closed;
|
|
154
154
|
if (result === 'confirm') {
|
|
155
155
|
// ...
|
|
156
156
|
}
|
|
@@ -158,6 +158,8 @@ class DemoHost {
|
|
|
158
158
|
}
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
`injectDrawerData<T>()` is typed `T | null`: the manager provides `null` when `open()` is called without `data`, so guard (`data?.message`) before dereferencing the payload. `await ref.closed` resolves `{ reason, result }` — the `reason` (a `ForDrawerCloseReason`) tells apart an imperative `close()` (`'programmatic'`) from Escape / backdrop / outside / swipe / close-button dismissals.
|
|
162
|
+
|
|
161
163
|
Drawers opened by the manager join the same `ForDrawerStack` as declarative ones, so mixed stacking (a programmatic drawer over a declarative parent, or vice versa) reflects correct `data-depth` / `data-state-nested` and routes Escape through the LIFO dismissable layer.
|
|
162
164
|
|
|
163
165
|
**Styling the programmatic overlay root.** The manager creates the `[forDrawer]` host for you and it is class-less. Pass `class` / `classList` to style it — the tokens land on the real host alongside `data-side` / `data-state` / the `--for-drawer-translate` custom property, so positioning CSS keyed on `data-side` works:
|
|
@@ -186,22 +188,22 @@ this.#drawers.open(ConfirmDrawer, {
|
|
|
186
188
|
|
|
187
189
|
`class` is a single or space-separated string; `classList` is an array or space-separated string; both merge and de-dup and never clobber the host attributes. This replaces the old `inject(FOR_DRAWER_CONTEXT).hostElement.classList.add('my-drawer')` workaround.
|
|
188
190
|
|
|
189
|
-
**Observing drag / release / active snap point.** A snap-point drawer opened imperatively has the same observability as the declarative `(dragMove)` / `(release)` / `(activeSnapPointChange)` outputs via
|
|
191
|
+
**Observing drag / release / active snap point.** A snap-point drawer opened imperatively has the same observability as the declarative `(dragMove)` / `(release)` / `(activeSnapPointChange)` outputs, via config callbacks of the same name:
|
|
190
192
|
|
|
191
193
|
```ts
|
|
192
194
|
this.#drawers.open(ConfirmDrawer, {
|
|
193
195
|
data,
|
|
194
196
|
snapPoints: ['148px', '50%', 1],
|
|
195
197
|
defaultSnapPoint: '148px',
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
dragMove: ({ percentageDragged }) => this.dragProgress.set(percentageDragged),
|
|
199
|
+
release: ({ willClose, nextSnapPoint }) => {
|
|
198
200
|
/* … */
|
|
199
201
|
},
|
|
200
|
-
|
|
202
|
+
activeSnapPointChange: (snap) => this.activeSnap.set(snap),
|
|
201
203
|
});
|
|
202
204
|
```
|
|
203
205
|
|
|
204
|
-
`
|
|
206
|
+
`activeSnapPointChange` fires with the landed snap on the mount-time default and every drag release — the read-back the declarative API exposes through `[(activeSnapPoint)]`. All three subscriptions are released automatically when the drawer closes.
|
|
205
207
|
|
|
206
208
|
### Per-channel dismissal (Escape-only drawers)
|
|
207
209
|
|