forty-cdk 0.13.0 → 0.15.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 +1 -1
- package/button/README.md +13 -4
- package/carousel/README.md +27 -9
- package/combobox/README.md +2 -1
- package/context-menu/README.md +22 -20
- package/date-field/README.md +12 -11
- package/date-range-field/README.md +12 -11
- package/drag-drop/README.md +34 -10
- package/dropdown-menu/README.md +10 -6
- package/fesm2022/forty-cdk-accordion.mjs +4 -3
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -1
- package/fesm2022/forty-cdk-breadcrumbs.mjs +4 -3
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -1
- package/fesm2022/forty-cdk-button.mjs +35 -16
- package/fesm2022/forty-cdk-button.mjs.map +1 -1
- package/fesm2022/forty-cdk-calendar.mjs +16 -12
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -1
- package/fesm2022/forty-cdk-carousel.mjs +86 -35
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -1
- package/fesm2022/forty-cdk-combobox.mjs +95 -28
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -1
- package/fesm2022/forty-cdk-context-menu.mjs +14 -1
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-core.mjs +3501 -4054
- package/fesm2022/forty-cdk-core.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-field.mjs +9 -4
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-picker.mjs +6 -4
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-range-field.mjs +22 -7
- package/fesm2022/forty-cdk-date-range-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-drag-drop.mjs +409 -40
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/forty-cdk-drawer.mjs +341 -3
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
- package/fesm2022/forty-cdk-dropdown-menu.mjs +18 -10
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-field.mjs +50 -19
- package/fesm2022/forty-cdk-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-fieldset.mjs +6 -7
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -1
- package/fesm2022/forty-cdk-file-upload.mjs +53 -31
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -1
- package/fesm2022/forty-cdk-hover-card.mjs +6 -6
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +15 -6
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -1
- package/fesm2022/forty-cdk-menu.mjs +117 -76
- package/fesm2022/forty-cdk-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-menubar.mjs +158 -159
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -1
- package/fesm2022/forty-cdk-meter.mjs +4 -3
- package/fesm2022/forty-cdk-meter.mjs.map +1 -1
- package/fesm2022/forty-cdk-navigation-menu.mjs +104 -55
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-number-input.mjs +274 -30
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-otp-input.mjs +24 -5
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -1
- package/fesm2022/forty-cdk-pagination.mjs +10 -7
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -1
- package/fesm2022/forty-cdk-pane-resizer.mjs +75 -17
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -1
- package/fesm2022/forty-cdk-popover.mjs +8 -5
- package/fesm2022/forty-cdk-popover.mjs.map +1 -1
- package/fesm2022/forty-cdk-progress.mjs +4 -3
- package/fesm2022/forty-cdk-progress.mjs.map +1 -1
- package/fesm2022/forty-cdk-radio-group.mjs +8 -1
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
- package/fesm2022/forty-cdk-scroll-area.mjs +407 -105
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -1
- package/fesm2022/forty-cdk-search.mjs +58 -15
- package/fesm2022/forty-cdk-search.mjs.map +1 -1
- package/fesm2022/forty-cdk-select.mjs +95 -28
- package/fesm2022/forty-cdk-select.mjs.map +1 -1
- package/fesm2022/forty-cdk-shared.mjs +6 -0
- package/fesm2022/forty-cdk-shared.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +120 -56
- package/fesm2022/forty-cdk-slider.mjs.map +1 -1
- package/fesm2022/forty-cdk-stepper.mjs +12 -8
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -1
- package/fesm2022/forty-cdk-table.mjs +324 -134
- package/fesm2022/forty-cdk-table.mjs.map +1 -1
- package/fesm2022/forty-cdk-tabs.mjs +5 -5
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-field.mjs +9 -4
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-picker.mjs +6 -4
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-time-range-field.mjs +22 -7
- package/fesm2022/forty-cdk-time-range-field.mjs.map +1 -1
- package/fesm2022/forty-cdk-toast.mjs +8 -9
- package/fesm2022/forty-cdk-toast.mjs.map +1 -1
- package/fesm2022/forty-cdk-toggle.mjs +13 -4
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -1
- package/fesm2022/forty-cdk-toolbar.mjs +55 -45
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -1
- package/fesm2022/forty-cdk-tooltip.mjs +12 -7
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -1
- package/fesm2022/forty-cdk-tree.mjs +149 -3
- package/fesm2022/forty-cdk-tree.mjs.map +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs +18 -9
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk-visually-hidden.mjs +6 -0
- package/fesm2022/forty-cdk-visually-hidden.mjs.map +1 -0
- package/field/README.md +2 -0
- package/fieldset/README.md +4 -4
- package/file-upload/README.md +16 -4
- package/menu/README.md +19 -17
- package/menubar/README.md +43 -32
- package/navigation-menu/README.md +9 -1
- package/number-input/README.md +6 -2
- package/otp-input/README.md +1 -1
- package/package.json +9 -1
- package/pane-resizer/README.md +40 -25
- package/radio-group/README.md +4 -0
- package/scroll-area/README.md +72 -8
- package/search/README.md +50 -0
- package/select/README.md +2 -1
- package/separator/README.md +1 -0
- package/shared/README.md +84 -0
- package/slider/README.md +7 -2
- package/table/README.md +55 -9
- package/time-field/README.md +1 -0
- package/time-range-field/README.md +1 -0
- package/toggle/README.md +19 -17
- package/toolbar/README.md +3 -3
- package/types/forty-cdk-accordion.d.ts +1 -1
- package/types/forty-cdk-breadcrumbs.d.ts +1 -0
- package/types/forty-cdk-button.d.ts +16 -2
- package/types/forty-cdk-calendar.d.ts +5 -1
- package/types/forty-cdk-carousel.d.ts +68 -15
- package/types/forty-cdk-combobox.d.ts +146 -57
- package/types/forty-cdk-context-menu.d.ts +20 -6
- package/types/forty-cdk-core.d.ts +798 -940
- package/types/forty-cdk-date-field.d.ts +6 -1
- package/types/forty-cdk-date-picker.d.ts +2 -1
- package/types/forty-cdk-date-range-field.d.ts +17 -1
- package/types/forty-cdk-dialog.d.ts +0 -1
- package/types/forty-cdk-drag-drop.d.ts +36 -10
- package/types/forty-cdk-drawer.d.ts +1 -1
- package/types/forty-cdk-dropdown-menu.d.ts +16 -8
- package/types/forty-cdk-field.d.ts +26 -11
- package/types/forty-cdk-fieldset.d.ts +4 -4
- package/types/forty-cdk-file-upload.d.ts +61 -19
- package/types/forty-cdk-hover-card.d.ts +4 -5
- package/types/forty-cdk-internationalized-date.d.ts +0 -1
- package/types/forty-cdk-listbox.d.ts +2 -1
- package/types/forty-cdk-menu.d.ts +42 -18
- package/types/forty-cdk-menubar.d.ts +161 -73
- package/types/forty-cdk-meter.d.ts +1 -0
- package/types/forty-cdk-navigation-menu.d.ts +51 -8
- package/types/forty-cdk-number-input.d.ts +56 -17
- package/types/forty-cdk-otp-input.d.ts +1 -0
- package/types/forty-cdk-pagination.d.ts +3 -1
- package/types/forty-cdk-pane-resizer.d.ts +31 -8
- package/types/forty-cdk-popover.d.ts +3 -1
- package/types/forty-cdk-progress.d.ts +1 -0
- package/types/forty-cdk-radio-group.d.ts +6 -1
- package/types/forty-cdk-scroll-area.d.ts +158 -14
- package/types/forty-cdk-search.d.ts +34 -7
- package/types/forty-cdk-select.d.ts +92 -51
- package/types/forty-cdk-shared.d.ts +1 -0
- package/types/forty-cdk-slider.d.ts +55 -11
- package/types/forty-cdk-stepper.d.ts +6 -2
- package/types/forty-cdk-table.d.ts +74 -236
- package/types/forty-cdk-tabs.d.ts +1 -1
- package/types/forty-cdk-time-field.d.ts +6 -1
- package/types/forty-cdk-time-picker.d.ts +2 -1
- package/types/forty-cdk-time-range-field.d.ts +17 -1
- package/types/forty-cdk-toast.d.ts +1 -1
- package/types/forty-cdk-toggle.d.ts +10 -1
- package/types/forty-cdk-toolbar.d.ts +21 -9
- package/types/forty-cdk-tooltip.d.ts +8 -4
- package/types/forty-cdk-tree.d.ts +1 -1
- package/types/forty-cdk-visually-hidden.d.ts +1 -0
- package/visually-hidden/README.md +78 -0
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ Optional — install only if you use the matching entry point / primitives:
|
|
|
36
36
|
|
|
37
37
|
## Primitives
|
|
38
38
|
|
|
39
|
-
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. Each lives in its own folder under `projects/forty-cdk/` with its own `README.md` documenting its anatomy, API, keyboard interaction and styling hooks. 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)
|
|
39
|
+
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. Each lives in its own folder under `projects/forty-cdk/` with its own `README.md` documenting its anatomy, API, keyboard interaction and styling hooks. The `@internationalized/date` adapters live in a dedicated `forty-cdk/internationalized-date` entry point so that optional peer stays truly optional. The cross-primitive contract types a primitive's public API references — `WritingDirection`, `VetoableEvent`, `DateAdapter`, `FloatingSide`, … — are published by [`forty-cdk/shared`](shared); the main `forty-cdk` barrel is **intentionally empty** (it exports no symbols), so always import primitives from the specific `forty-cdk/<primitive>` entry point. Standalone directives plus `"sideEffects": false` mean your bundle only ever includes the primitives you import.
|
|
40
40
|
|
|
41
41
|
The tables below group the primitives by purpose. The link on each name opens that primitive's README — the canonical reference for which HTML element each directive belongs on, its inputs / outputs, `data-*` attributes and keyboard map.
|
|
42
42
|
|
package/button/README.md
CHANGED
|
@@ -34,6 +34,15 @@ Disabled buttons stay focusable so assistive technology can announce them. The n
|
|
|
34
34
|
<button forButton [disabled]="isSaving()" (activate)="save()">Save</button>
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
A surrounding disabled `[forFieldset]` disables the button too — its `disabled` input is OR'd with the group's, so `aria-disabled` / `data-disabled` are reflected and activation is suppressed. This matters most on a non-native host (`<div forButton>`), which a native `<fieldset disabled>` cannot reach.
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<fieldset forFieldset [disabled]="locked()">
|
|
41
|
+
<legend forFieldsetLegend>Account</legend>
|
|
42
|
+
<button forButton (activate)="save()">Save</button>
|
|
43
|
+
</fieldset>
|
|
44
|
+
```
|
|
45
|
+
|
|
37
46
|
### Preserve consumer `type`
|
|
38
47
|
|
|
39
48
|
A native `<button>` without an explicit `type` attribute defaults to `type="button"`. A consumer-set `type="submit"` is preserved:
|
|
@@ -46,10 +55,10 @@ A native `<button>` without an explicit `type` attribute defaults to `type="butt
|
|
|
46
55
|
|
|
47
56
|
### `ForButton`
|
|
48
57
|
|
|
49
|
-
| Property | Type | Description
|
|
50
|
-
| ---------- | ---------------- |
|
|
51
|
-
| `disabled` | `input<boolean>` | Suppresses activation and reflects `aria-disabled` + `data-disabled
|
|
52
|
-
| `activate` | `output<void>` | Fires once per user activation (click, Enter, Space). Never fires when disabled.<br>**Default:** —
|
|
58
|
+
| Property | Type | Description |
|
|
59
|
+
| ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `disabled` | `input<boolean>` | Suppresses activation and reflects `aria-disabled` + `data-disabled`. OR'd with a surrounding `[forFieldset]`'s disabled state.<br>**Default:** `false` |
|
|
61
|
+
| `activate` | `output<void>` | Fires once per user activation (click, Enter, Space). Never fires when disabled.<br>**Default:** — |
|
|
53
62
|
|
|
54
63
|
The directive reflects boolean `data-*` attributes (present with an empty-string value when true, absent when false). There is no `data-state` — this primitive has no open/closed or checked/unchecked logical state.
|
|
55
64
|
|
package/carousel/README.md
CHANGED
|
@@ -110,6 +110,11 @@ the defaults with `startLabel` / `stopLabel` inputs:
|
|
|
110
110
|
<button forCarouselRotationControl startLabel="Play slideshow" stopLabel="Pause slideshow"></button>
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
Both defaults come from the scope's `rotationStartLabel` / `rotationStopLabel`
|
|
114
|
+
(see [Localizing the default labels](#localizing-the-default-labels)); set either
|
|
115
|
+
input to `null` when the button carries a visible text label and you don't want
|
|
116
|
+
an `aria-label` overriding it.
|
|
117
|
+
|
|
113
118
|
**Programmatic control** via `exportAs`:
|
|
114
119
|
|
|
115
120
|
```html
|
|
@@ -189,16 +194,19 @@ captured, so page scrolling on the perpendicular axis is unaffected.
|
|
|
189
194
|
|
|
190
195
|
## Localizing the default labels
|
|
191
196
|
|
|
192
|
-
Each slide's default `aria-label` is the positional `"N of M"` string,
|
|
193
|
-
indicator's is `"Go to slide N"
|
|
194
|
-
`
|
|
195
|
-
|
|
197
|
+
Each slide's default `aria-label` is the positional `"N of M"` string, each
|
|
198
|
+
indicator's is `"Go to slide N"`, and the rotation control's swaps between
|
|
199
|
+
`"Start automatic slide show"` and `"Stop automatic slide show"`. Localize them
|
|
200
|
+
all centrally with `provideForCarouselDefaults` instead of setting `ariaLabel` on
|
|
201
|
+
every slide and indicator:
|
|
196
202
|
|
|
197
203
|
```ts
|
|
198
204
|
providers: [
|
|
199
205
|
provideForCarouselDefaults({
|
|
200
206
|
slideLabel: (position, total) => `Diapositiva ${position} de ${total}`,
|
|
201
207
|
indicatorLabel: (position) => `Ir a la diapositiva ${position}`,
|
|
208
|
+
rotationStartLabel: 'Iniciar la presentación',
|
|
209
|
+
rotationStopLabel: 'Detener la presentación',
|
|
202
210
|
}),
|
|
203
211
|
];
|
|
204
212
|
```
|
|
@@ -206,7 +214,8 @@ providers: [
|
|
|
206
214
|
`position` is the 1-based slide index and `total` is the slide count. Overrides
|
|
207
215
|
merge with the parent scope, so you can localize just the labels and inherit the
|
|
208
216
|
rest of the defaults. A per-element `ariaLabel` on `[forCarouselSlide]` /
|
|
209
|
-
`[forCarouselIndicator]` still takes precedence over the localized default
|
|
217
|
+
`[forCarouselIndicator]` still takes precedence over the localized default, as do
|
|
218
|
+
`[startLabel]` / `[stopLabel]` on `[forCarouselRotationControl]`.
|
|
210
219
|
|
|
211
220
|
## API
|
|
212
221
|
|
|
@@ -229,8 +238,8 @@ All inputs are on `[forCarousel]` unless noted.
|
|
|
229
238
|
| `ariaLabel` (on `[forCarouselIndicators]`) | `string \| null` | Label for the picker group.<br>**Default:** `null` |
|
|
230
239
|
| `ariaLabel` (on `[forCarouselSlide]`) | `string \| null` | Override the positional "N of M" label.<br>**Default:** `null` |
|
|
231
240
|
| `disabled` (on `[forCarouselIndicator]`) | `boolean` | Disable this indicator.<br>**Default:** `false` |
|
|
232
|
-
| `startLabel` (on `[forCarouselRotationControl]`) | `string`
|
|
233
|
-
| `stopLabel` (on `[forCarouselRotationControl]`) | `string`
|
|
241
|
+
| `startLabel` (on `[forCarouselRotationControl]`) | `string \| null` | Accessible name while rotation is stopped.<br>**Default:** scope `rotationStartLabel` (`'Start automatic slide show'`) |
|
|
242
|
+
| `stopLabel` (on `[forCarouselRotationControl]`) | `string \| null` | Accessible name while rotation is playing.<br>**Default:** scope `rotationStopLabel` (`'Stop automatic slide show'`) |
|
|
234
243
|
|
|
235
244
|
Reflected on the `[forCarousel]` host:
|
|
236
245
|
|
|
@@ -298,8 +307,10 @@ Implements the [WAI-ARIA Carousel pattern](https://www.w3.org/WAI/ARIA/apg/patte
|
|
|
298
307
|
accessibility tree and focus order.
|
|
299
308
|
- The indicator group should be labelled (e.g. `ariaLabel="Choose slide to display"`).
|
|
300
309
|
- The current indicator is marked with `aria-current="true"`.
|
|
301
|
-
- Prev/next buttons use native `disabled`
|
|
302
|
-
|
|
310
|
+
- Prev/next buttons never use the native `disabled` attribute. At a boundary without `loop`
|
|
311
|
+
they reflect `aria-disabled="true"` + `data-disabled` and ignore activation, so a keyboard
|
|
312
|
+
user who reaches the last slide keeps focus on the button instead of being dropped to
|
|
313
|
+
`<body>`. Style the boundary state off `[data-disabled]`, never `:disabled`.
|
|
303
314
|
- The viewport carries `aria-live` and `aria-atomic="false"`. While the carousel is actively
|
|
304
315
|
auto-rotating, `aria-live` is `"off"` so advancing slides do not bombard the screen reader.
|
|
305
316
|
When stopped or paused, it is `"polite"` so manual navigation announces. The per-slide
|
|
@@ -365,6 +376,13 @@ children, unless noted otherwise:
|
|
|
365
376
|
| `data-rotating` | On `[forCarousel]` — actively rotating right now |
|
|
366
377
|
| `data-autoplay` | On `[forCarousel]` — the `autoplay` input is `true` |
|
|
367
378
|
|
|
379
|
+
### Boundary styling hooks
|
|
380
|
+
|
|
381
|
+
| Attribute | When present |
|
|
382
|
+
| --------------- | --------------------------------------------------------- |
|
|
383
|
+
| `data-disabled` | On `[forCarouselPrevious]` — at index 0 without `loop` |
|
|
384
|
+
| `data-disabled` | On `[forCarouselNext]` — at the last index without `loop` |
|
|
385
|
+
|
|
368
386
|
### Drag styling hooks
|
|
369
387
|
|
|
370
388
|
| Attribute | Host | When present |
|
package/combobox/README.md
CHANGED
|
@@ -129,6 +129,7 @@ Input tables are not yet tabulated for this primitive. See the feature sections
|
|
|
129
129
|
| `[forComboboxIndicator]` | `data-state` | `checked` \| `unchecked` (mirrors the parent option) |
|
|
130
130
|
| `[forComboboxChip]` | `data-value` | the chip's serialized value (verbatim string, or `itemToFormValue`) |
|
|
131
131
|
| `[forComboboxChip]` | `data-disabled` | present / absent |
|
|
132
|
+
| `[forComboboxSeparator]` | `data-orientation` | `horizontal` \| `vertical` |
|
|
132
133
|
|
|
133
134
|
Focus stays on the `<input>` the whole time the listbox is open, so options never get `:focus` — `data-highlighted` is the canonical hook for styling the keyboard-active option.
|
|
134
135
|
|
|
@@ -608,7 +609,7 @@ Implements the [WAI-ARIA Combobox pattern](https://www.w3.org/WAI/ARIA/apg/patte
|
|
|
608
609
|
- `data-state="checked" | "unchecked"` always reflects membership in `value()`, so consumers can paint a checkmark icon with pure CSS regardless of mode.
|
|
609
610
|
- `data-highlighted=""` marks the option that is the current `aria-activedescendant`. Because focus stays on the `<input>`, there is no `:focus` on the option to style — `data-highlighted` is the canonical CSS hook.
|
|
610
611
|
- Disabled options keep the host `aria-disabled="true"`. Click and hover (activedescendant pinning) are no-ops on disabled options.
|
|
611
|
-
- `[forComboboxSeparator]`
|
|
612
|
+
- `[forComboboxSeparator]` never registers with the listbox's option collection — keyboard navigation skips it automatically. It carries `role="separator"` and emits `aria-orientation` only for `orientation="vertical"`, because `horizontal` is the ARIA default; `data-orientation` is always stamped for styling. Set `decorative` when the surrounding options already convey the split — it switches the line to `role="none"` and drops `aria-orientation`, matching the [shared separator emission policy](../separator/README.md#accessibility).
|
|
612
613
|
- `[forComboboxGroup]` is purely advisory grouping — options inside still register flatly with the root, so navigation flows through groups without interruption.
|
|
613
614
|
- `[forComboboxEmpty]` carries `role="status"` + `aria-live="polite"` so the empty-state message is announced when filtering removes all matches.
|
|
614
615
|
- Non-option pieces (`[forComboboxAction]`, and ideally `[forComboboxEmpty]` / `[forComboboxStatus]`) belong inside `[forComboboxContent]` but **outside** `[forComboboxList]` — `role="listbox"` may only own `option` / `group` children (`aria-required-owned-elements`). Wrapping the options in a `[forComboboxList]` (the "editable + list" shape) makes those pieces siblings of the listbox. `[forComboboxAction]` **requires** a `[forComboboxList]` and throws `[forty-cdk/combobox]` without one; `[forComboboxEmpty]` / `[forComboboxStatus]` stay lenient in the bare editable anatomy (documented compromise) — see the [editable-anatomy caveat](#anatomy).
|
package/context-menu/README.md
CHANGED
|
@@ -99,26 +99,26 @@ Angular resolves `ng-template` DI at the template's **declaration** site, not wh
|
|
|
99
99
|
|
|
100
100
|
### `ForContextMenu`
|
|
101
101
|
|
|
102
|
-
| Property | Type | Description
|
|
103
|
-
| --------------------------- | --------------------------------------------------------- |
|
|
104
|
-
| `open` | `model<boolean>` | Two-way bindable. Whether the menu is shown.<br>**Default:** `false`
|
|
105
|
-
| `side` | `input<string>` | Anchor side relative to the pointer.<br>**Default:** `'bottom'`
|
|
106
|
-
| `align` | `input<string>` | Alignment along `side` (`'start'` / `'center'` / `'end'`).<br>**Default:** `'start'`
|
|
107
|
-
| `sideOffset` | `input<number>` | Gap (px) between the pointer and the menu along the main axis.<br>**Default:** `0`
|
|
108
|
-
| `alignOffset` | `input<number>` | Gap (px) along the cross axis (parallel to `side`).<br>**Default:** `0`
|
|
109
|
-
| `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.<br>**Default:** `'none'`
|
|
110
|
-
| `loop` | `input<boolean>` | Whether arrow navigation wraps.<br>**Default:** `true`
|
|
111
|
-
| `dir` | `input<string>` | Writing direction. In RTL, ArrowLeft opens submenus and ArrowRight closes them — the swap is automatic. Inherited by every nested `[forMenuSub]` underneath unless overridden.<br>**Default:** `'ltr'`
|
|
112
|
-
| `disabled` | `input<boolean>` | When `true`, the contextmenu event falls through to the native browser menu.<br>**Default:** `false`
|
|
113
|
-
| `dismissible` | `input<boolean>` | When `false`, Escape and outside interactions don't close.<br>**Default:** `true`
|
|
114
|
-
| `returnFocus` | `input<boolean>` | When `true`, focus returns to the trigger element on close.<br>**Default:** `true`
|
|
115
|
-
| `ariaLabel` | `input<string \| null>` |
|
|
116
|
-
| `escapeKeyDown` | `output<VetoableNativeEvent<KeyboardEvent>>` | Output. Escape pressed while the menu is the topmost dismissable layer.<br>**Default:** —
|
|
117
|
-
| `pointerDownOutside` | `output<VetoableNativeEvent<PointerEvent>>` | Output. Pointer-down on a target outside content + trigger.<br>**Default:** —
|
|
118
|
-
| `focusOutside` | `output<VetoableNativeEvent<FocusEvent>>` | Output. Focus moves outside content + trigger.<br>**Default:** —
|
|
119
|
-
| `interactOutside` | `output<VetoableNativeEvent<PointerEvent \| FocusEvent>>` | Output. Composite — fires alongside the two above (and shares their veto state).<br>**Default:** —
|
|
120
|
-
| `autoFocusOnOpen` | `output<VetoableEvent>` | Output. Just before the imperative focus move on mount.<br>**Default:** —
|
|
121
|
-
| `autoFocusOnClose` | `output<VetoableEvent>` | Output. Just before the imperative focus move on unmount.<br>**Default:** —
|
|
102
|
+
| Property | Type | Description |
|
|
103
|
+
| --------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `open` | `model<boolean>` | Two-way bindable. Whether the menu is shown.<br>**Default:** `false` |
|
|
105
|
+
| `side` | `input<string>` | Anchor side relative to the pointer.<br>**Default:** `'bottom'` |
|
|
106
|
+
| `align` | `input<string>` | Alignment along `side` (`'start'` / `'center'` / `'end'`).<br>**Default:** `'start'` |
|
|
107
|
+
| `sideOffset` | `input<number>` | Gap (px) between the pointer and the menu along the main axis.<br>**Default:** `0` |
|
|
108
|
+
| `alignOffset` | `input<number>` | Gap (px) along the cross axis (parallel to `side`).<br>**Default:** `0` |
|
|
109
|
+
| `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.<br>**Default:** `'none'` |
|
|
110
|
+
| `loop` | `input<boolean>` | Whether arrow navigation wraps.<br>**Default:** `true` |
|
|
111
|
+
| `dir` | `input<string>` | Writing direction. In RTL, ArrowLeft opens submenus and ArrowRight closes them — the swap is automatic. Inherited by every nested `[forMenuSub]` underneath unless overridden.<br>**Default:** `'ltr'` |
|
|
112
|
+
| `disabled` | `input<boolean>` | When `true`, the contextmenu event falls through to the native browser menu.<br>**Default:** `false` |
|
|
113
|
+
| `dismissible` | `input<boolean>` | When `false`, Escape and outside interactions don't close.<br>**Default:** `true` |
|
|
114
|
+
| `returnFocus` | `input<boolean>` | When `true`, focus returns to the trigger element on close.<br>**Default:** `true` |
|
|
115
|
+
| `ariaLabel` | `input<string \| null>` | Accessible name reflected as `aria-label` on `[forMenuContent]`. The root's only name hook for a context menu — the right-click region is never used as an `aria-labelledby` target.<br>**Default:** `null` |
|
|
116
|
+
| `escapeKeyDown` | `output<VetoableNativeEvent<KeyboardEvent>>` | Output. Escape pressed while the menu is the topmost dismissable layer.<br>**Default:** — |
|
|
117
|
+
| `pointerDownOutside` | `output<VetoableNativeEvent<PointerEvent>>` | Output. Pointer-down on a target outside content + trigger.<br>**Default:** — |
|
|
118
|
+
| `focusOutside` | `output<VetoableNativeEvent<FocusEvent>>` | Output. Focus moves outside content + trigger.<br>**Default:** — |
|
|
119
|
+
| `interactOutside` | `output<VetoableNativeEvent<PointerEvent \| FocusEvent>>` | Output. Composite — fires alongside the two above (and shares their veto state).<br>**Default:** — |
|
|
120
|
+
| `autoFocusOnOpen` | `output<VetoableEvent>` | Output. Just before the imperative focus move on mount.<br>**Default:** — |
|
|
121
|
+
| `autoFocusOnClose` | `output<VetoableEvent>` | Output. Just before the imperative focus move on unmount.<br>**Default:** — |
|
|
122
122
|
|
|
123
123
|
Same vetoable dismiss API as DropdownMenu. Call `preventDefault()` on the emitted veto to suppress the directive's default action; the original DOM event, when present, is on `.event`.
|
|
124
124
|
|
|
@@ -137,6 +137,8 @@ Same vetoable dismiss API as DropdownMenu. Call `preventDefault()` on the emitte
|
|
|
137
137
|
|
|
138
138
|
`[forContextMenu]` implements the [WAI-ARIA Menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/). The trigger captures `contextmenu`, `Shift+F10`, and the `ContextMenu` key; the menu surface and item roles come from the shared [`menu/`](../menu/README.md) primitives. Pointer activations anchor at the cursor; keyboard activations anchor at the bounding rect of the focused element so keyboard-only users get the menu next to whatever they're working on.
|
|
139
139
|
|
|
140
|
+
- **Name the menu with `[ariaLabel]`.** Unlike `[forDropdownMenu]` / `[forMenubar]` / `[forMenuSub]`, the content surface emits **no** `aria-labelledby` fallback here: the trigger is the whole right-click region, so pointing the menu's name at it would make screen readers announce the entire row / card text as the menu name. With no `[ariaLabel]` the `role="menu"` surface simply has no accessible name. A consumer-set static `aria-labelledby` on `[forMenuContent]` is still preserved, so pointing at your own visible heading also works. Submenus nested inside a context menu are unaffected — a `[forMenuSubContent]` is still labelled by its `[forMenuSubTrigger]`.
|
|
141
|
+
|
|
140
142
|
## Styling
|
|
141
143
|
|
|
142
144
|
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).
|
package/date-field/README.md
CHANGED
|
@@ -118,17 +118,17 @@ export class DobFormField {
|
|
|
118
118
|
|
|
119
119
|
### `ForDateField`
|
|
120
120
|
|
|
121
|
-
| Property | Type
|
|
122
|
-
| ------------- |
|
|
123
|
-
| `value` | `model<D \| null>`
|
|
124
|
-
| `minDate` | `input<D \| null>`
|
|
125
|
-
| `maxDate` | `input<D \| null>`
|
|
126
|
-
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>`
|
|
127
|
-
| `hourCycle` | `input<12 \| 24 \| null>`
|
|
128
|
-
| `locale` | `input<string \| null>`
|
|
129
|
-
| `placeholder` | `input<Partial<Record<
|
|
130
|
-
| `ariaLabel` | `input<string \| null>`
|
|
131
|
-
| `dir` | `input<'ltr' \| 'rtl' \| null>`
|
|
121
|
+
| Property | Type | Description |
|
|
122
|
+
| ------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
123
|
+
| `value` | `model<D \| null>` | Two-way bindable entered date, or `null` while any segment is empty. The `FormValueControl` backing.<br>**Default:** `null` |
|
|
124
|
+
| `minDate` | `input<D \| null>` | Minimum date (inclusive). A composed value below it is clamped up. Named `minDate` — see note below.<br>**Default:** `null` |
|
|
125
|
+
| `maxDate` | `input<D \| null>` | Maximum date (inclusive). A composed value above it is clamped down.<br>**Default:** `null` |
|
|
126
|
+
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>` | Date-time precision. `'day'` is date-only; coarser-than-day appends time segments. See below.<br>**Default:** `'day'` |
|
|
127
|
+
| `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` |
|
|
128
|
+
| `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → runtime locale.<br>**Default:** `null` |
|
|
129
|
+
| `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty. Unspecified parts fall back to `dd` / `mm` / `yyyy` / `hh` / `mm` / `ss` / `--`.<br>**Default:** `{}` |
|
|
130
|
+
| `ariaLabel` | `input<string \| null>` | Accessible name for the group. Emits no `aria-label` while `null`.<br>**Default:** `null` |
|
|
131
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null` |
|
|
132
132
|
|
|
133
133
|
Plus the shared `FormUiControl` members from `@angular/forms/signals`: `disabled`, `readonly`, `required`, `invalid`, `name`, `errors`, `touched` (bound automatically by `[formField]`).
|
|
134
134
|
|
|
@@ -212,6 +212,7 @@ Composes the [WAI-ARIA Spinbutton pattern](https://www.w3.org/WAI/ARIA/apg/patte
|
|
|
212
212
|
- **Roving tabindex**: exactly one segment is tabbable, so `Tab` enters and leaves the whole field in one stop; arrows move between segments.
|
|
213
213
|
- **Literals are `aria-hidden`** and never focusable — assistive tech reads only the spinbutton segments.
|
|
214
214
|
- **Boolean `data-*`** on each segment — `data-highlighted` (focused/roving), `data-placeholder` (empty), `data-disabled`, `data-readonly` — present when true, absent when false.
|
|
215
|
+
- **`aria-readonly` belongs on the segments, not the group.** WAI-ARIA supports it on `role="spinbutton"` but not on `role="group"`, so each segment carries `aria-readonly="true"` while the group reflects the `data-readonly` styling hook only.
|
|
215
216
|
|
|
216
217
|
## Styling
|
|
217
218
|
|
|
@@ -158,17 +158,17 @@ export class StayFormField {
|
|
|
158
158
|
|
|
159
159
|
### `ForDateRangeField`
|
|
160
160
|
|
|
161
|
-
| Property | Type
|
|
162
|
-
| ------------- |
|
|
163
|
-
| `value` | `model<DateRange<D> \| null>`
|
|
164
|
-
| `minDate` | `input<D \| null>`
|
|
165
|
-
| `maxDate` | `input<D \| null>`
|
|
166
|
-
| `granularity` | `input<'day' \| 'hour' \| 'minute' \| 'second'>`
|
|
167
|
-
| `hourCycle` | `input<12 \| 24 \| null>`
|
|
168
|
-
| `locale` | `input<string \| null>`
|
|
169
|
-
| `placeholder` | `input<Partial<Record<
|
|
170
|
-
| `ariaLabel` | `input<string \| null>`
|
|
171
|
-
| `dir` | `input<'ltr' \| 'rtl' \| null>`
|
|
161
|
+
| Property | Type | Description |
|
|
162
|
+
| ------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
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
|
+
| `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
|
+
| `maxDate` | `input<D \| null>` | Maximum date (inclusive) for both endpoints. A composed endpoint above it is clamped down.<br>**Default:** `null` |
|
|
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'` |
|
|
167
|
+
| `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` |
|
|
168
|
+
| `locale` | `input<string \| null>` | BCP 47 locale driving segment order, separators, and month name. `null` → runtime locale.<br>**Default:** `null` |
|
|
169
|
+
| `placeholder` | `input<Partial<Record<SegmentType, string>>>` | Per-segment placeholder while empty, applied to both endpoints.<br>**Default:** `{}` |
|
|
170
|
+
| `ariaLabel` | `input<string \| null>` | Accessible name for the whole range field group. Emits no `aria-label` while `null`.<br>**Default:** `null` |
|
|
171
|
+
| `dir` | `input<'ltr' \| 'rtl' \| null>` | Writing direction. `null` resolves the ambient direction; mirrors ArrowLeft / ArrowRight segment navigation.<br>**Default:** `null` |
|
|
172
172
|
|
|
173
173
|
The endpoint groups (`[forDateRangeFieldStart]` / `[forDateRangeFieldEnd]`) each accept an `ariaLabel` input for their own group label, falling back to the scope defaults (`'Start date'` / `'End date'`).
|
|
174
174
|
|
|
@@ -242,6 +242,7 @@ Each segment implements the [WAI-ARIA Spinbutton pattern](https://www.w3.org/WAI
|
|
|
242
242
|
- **Roving tabindex per endpoint**: exactly one segment per endpoint is tabbable, so `Tab` steps start group → end group; arrows move between segments within an endpoint.
|
|
243
243
|
- **`aria-invalid="true"`** is reflected on the root when the form marks it invalid **or** when two complete endpoints are out of order.
|
|
244
244
|
- **Literals are `aria-hidden`** and never focusable — assistive tech reads only the spinbutton segments.
|
|
245
|
+
- **`aria-readonly` belongs on the segments, not the groups.** WAI-ARIA supports it on `role="spinbutton"` but not on `role="group"`, so each segment carries `aria-readonly="true"` while the root and both endpoint groups reflect the `data-readonly` styling hook only.
|
|
245
246
|
|
|
246
247
|
## Styling
|
|
247
248
|
|
package/drag-drop/README.md
CHANGED
|
@@ -7,15 +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 | Home / End | Jump the lifted item to the first / last position
|
|
17
|
-
| Lifted | Space/Enter | **Drop** (commits
|
|
18
|
-
| Lifted | Escape | **Cancel** (no event, focus stays on item)
|
|
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, emits `(dragDrop)`, focus follows the item) |
|
|
18
|
+
| Lifted | Escape | **Cancel** (no event, focus stays on item) |
|
|
19
19
|
|
|
20
20
|
Arrow direction follows the list's `orientation` and respects RTL via `dir`. In
|
|
21
21
|
`orientation="mixed"` every arrow key steps the lifted item linearly in DOM order.
|
|
@@ -24,6 +24,21 @@ Arrow direction follows the list's `orientation` and respects RTL via `dir`. In
|
|
|
24
24
|
|
|
25
25
|
Keyboard lifting, stepping, dropping, and cancellation are announced via ARIA live regions. Override the default messages at any injector scope via `provideForDragDropDefaults` (see Announcement customisation below). Free-drag is pointer-only — there is no WAI-ARIA pattern for "reposition an element", so `[forFreeDrag]` owns no role or ARIA state; the consumer is responsible for keeping the moved element fully usable at its default position.
|
|
26
26
|
|
|
27
|
+
### Focus after a keyboard drop
|
|
28
|
+
|
|
29
|
+
Applying the move in `(dragDrop)` destroys or re-inserts the lifted element, which would otherwise
|
|
30
|
+
leave focus on `<body>`. So after a **keyboard** drop whose lifted item held focus, `[forDropList]`
|
|
31
|
+
restores focus to the item at `currentIndex` in the target container on the next render — the same
|
|
32
|
+
element the user just placed, whether it stayed in this list or transferred to a connected one.
|
|
33
|
+
|
|
34
|
+
Two escape hatches:
|
|
35
|
+
|
|
36
|
+
- **Focus something yourself** inside the `(dragDrop)` handler (a status region, a toolbar, the next
|
|
37
|
+
row). The restore only fires when focus has already fallen to `<body>`, so whatever you focus wins.
|
|
38
|
+
- **Pointer drops are untouched** — focus is never moved after a mouse / touch / pen drop.
|
|
39
|
+
|
|
40
|
+
If your handler leaves the data unchanged, nothing is detached and focus simply stays on the item.
|
|
41
|
+
|
|
27
42
|
## Pointer dragging
|
|
28
43
|
|
|
29
44
|
When a pointer drag starts, the dragged item stays in place and reflects
|
|
@@ -119,7 +134,8 @@ stays in the dragged item's source slot.
|
|
|
119
134
|
```
|
|
120
135
|
|
|
121
136
|
`[liveSort]` has no visible effect without a `[forDragPlaceholder]` template, and has no effect
|
|
122
|
-
on keyboard dragging.
|
|
137
|
+
on keyboard dragging. The drop index is resolved from the geometry measured at lift, so
|
|
138
|
+
`[liveSort]` never changes which index a pointer path commits — it only moves the placeholder.
|
|
123
139
|
|
|
124
140
|
### Boundary & axis lock
|
|
125
141
|
|
|
@@ -198,6 +214,10 @@ preview is destroyed promptly.
|
|
|
198
214
|
happens to render as a single row or single column resolves identically to `"horizontal"` /
|
|
199
215
|
`"vertical"`.
|
|
200
216
|
|
|
217
|
+
In a cross-list transfer each container resolves the drop index on **its own** `orientation` and
|
|
218
|
+
`dir`, so a vertical list can be connected to a horizontal one (or to one in the opposite writing
|
|
219
|
+
direction) and the pointer index lands on the target's axis.
|
|
220
|
+
|
|
201
221
|
```html
|
|
202
222
|
<ul class="grid" forDropList orientation="mixed" (dragDrop)="onDrop($event)">
|
|
203
223
|
@for (item of items(); track item.id) {
|
|
@@ -413,3 +433,7 @@ providers: [
|
|
|
413
433
|
}),
|
|
414
434
|
];
|
|
415
435
|
```
|
|
436
|
+
|
|
437
|
+
`total` is the number of valid drop positions in the list being announced: the item count for a
|
|
438
|
+
same-list reorder, and one more than the item count for a transfer into a connected list (the
|
|
439
|
+
append gap counts).
|
package/dropdown-menu/README.md
CHANGED
|
@@ -155,11 +155,13 @@ The menu items, content surface, radio groups, separators, and groups live in th
|
|
|
155
155
|
|
|
156
156
|
## Keyboard
|
|
157
157
|
|
|
158
|
-
| Key
|
|
159
|
-
|
|
|
160
|
-
| `Click`
|
|
161
|
-
| `ArrowDown`
|
|
162
|
-
| `ArrowUp`
|
|
158
|
+
| Key | Behavior |
|
|
159
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
160
|
+
| `Click` | Toggles the menu. On open, focus moves to the first enabled item. |
|
|
161
|
+
| `Enter` / `Space` / `ArrowDown` | Opens the menu and focuses the first enabled item; on an already-open menu, moves focus there. |
|
|
162
|
+
| `ArrowUp` | Opens the menu and focuses the last enabled item; on an already-open menu, moves focus there. |
|
|
163
|
+
|
|
164
|
+
The open keys never close the menu — the APG menu-button pattern gives them no close semantics (that's `Escape`, or a pointer click on the trigger). Pressing one while the menu is already open moves focus into it, which is what makes them useful after an `(autoFocusOnOpen)`-vetoed open left focus on the trigger. A menu with no enabled item moves nothing.
|
|
163
165
|
|
|
164
166
|
Once focus is in the menu, see [`menu/README.md`](../menu/README.md) for the in-menu keyboard.
|
|
165
167
|
|
|
@@ -167,6 +169,8 @@ Once focus is in the menu, see [`menu/README.md`](../menu/README.md) for the in-
|
|
|
167
169
|
|
|
168
170
|
`[forDropdownMenu]` implements the [WAI-ARIA Menu Button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/). The trigger wires `aria-haspopup="menu"`, `aria-expanded`, and `aria-controls`; the menu surface and item roles come from the shared [`menu/`](../menu/README.md) primitives.
|
|
169
171
|
|
|
172
|
+
A disabled trigger (its own `[disabled]`, or the root's) reflects through a **single channel**: the native `disabled` attribute plus the `data-disabled` styling hook. No `aria-disabled` is emitted — the trigger is a real single-purpose `<button>` and the native attribute already conveys the state to assistive technology, per the sanctioned native-`disabled` case in [rule #561](https://github.com/tutkli/forty-cdk/issues/561) (D2). Style the disabled trigger off `[disabled]` or `[data-disabled]`, never `[aria-disabled]`.
|
|
173
|
+
|
|
170
174
|
## Styling
|
|
171
175
|
|
|
172
176
|
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).
|
|
@@ -186,5 +190,5 @@ forty-cdk ships no styles. Add your own class to each piece — the `for*` selec
|
|
|
186
190
|
|
|
187
191
|
- **Mount equals open.** The directive does not toggle `[hidden]` — `@if (open())` controls presence so `animate.enter` / `animate.leave` fire on the natural mount cycle.
|
|
188
192
|
- **Trigger is exempt** from outside-pointer / outside-focus checks. Without this, clicking the trigger to close would race with its own toggle handler and reopen immediately.
|
|
189
|
-
- **Initial focus depends on the opening key.** Click / Space / Enter / ArrowDown focus the first enabled item; ArrowUp focuses the last enabled item.
|
|
193
|
+
- **Initial focus depends on the opening key.** Click / Space / Enter / ArrowDown focus the first enabled item; ArrowUp focuses the last enabled item. The same keys re-focus that item when the menu is already open.
|
|
190
194
|
- **Selecting an item closes the menu** by default. To keep the menu open after activation (multi-select pattern), call `$event.preventDefault()` in the item's `(activate)` handler.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, input, booleanAttribute, model, Directive, computed, signal, ElementRef } from '@angular/core';
|
|
3
|
-
import { Collection, injectTextDirection, moveIndex, IdGenerator, adoptHostId, registerHandle, reflectDisabled, resolveListNavigation } from 'forty-cdk/core';
|
|
3
|
+
import { Collection, injectTextDirection, moveIndex, IdGenerator, adoptHostId, registerHandle, reflectDisabled, resolveListNavigation, hostLabelledBy } from 'forty-cdk/core';
|
|
4
4
|
|
|
5
5
|
const FOR_ACCORDION_CONTEXT = new InjectionToken('FOR_ACCORDION_CONTEXT');
|
|
6
6
|
const FOR_ACCORDION_ITEM_CONTEXT = new InjectionToken('FOR_ACCORDION_ITEM_CONTEXT');
|
|
@@ -324,11 +324,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
324
324
|
class ForAccordionContent {
|
|
325
325
|
parent = injectAccordionContext('ForAccordionContent');
|
|
326
326
|
item = injectAccordionItemContext('ForAccordionContent');
|
|
327
|
+
labelledBy = hostLabelledBy(() => this.item.triggerId());
|
|
327
328
|
constructor() {
|
|
328
329
|
this.item.adoptContentId(inject(ElementRef).nativeElement);
|
|
329
330
|
}
|
|
330
331
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordionContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
331
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForAccordionContent, isStandalone: true, selector: "[forAccordionContent]", host: { properties: { "id": "item.contentId()", "attr.role": "\"region\"", "attr.aria-labelledby": "
|
|
332
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForAccordionContent, isStandalone: true, selector: "[forAccordionContent]", host: { properties: { "id": "item.contentId()", "attr.role": "\"region\"", "attr.aria-labelledby": "labelledBy()", "attr.aria-hidden": "item.expanded() ? null : \"true\"", "attr.inert": "item.expanded() ? null : \"\"", "attr.data-state": "item.expanded() ? \"open\" : \"closed\"", "attr.data-disabled": "item.disabled() ? \"\" : null", "attr.data-orientation": "parent.orientation()" } }, exportAs: ["forAccordionContent"], ngImport: i0 });
|
|
332
333
|
}
|
|
333
334
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordionContent, decorators: [{
|
|
334
335
|
type: Directive,
|
|
@@ -338,7 +339,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
338
339
|
host: {
|
|
339
340
|
'[id]': 'item.contentId()',
|
|
340
341
|
'[attr.role]': '"region"',
|
|
341
|
-
'[attr.aria-labelledby]': '
|
|
342
|
+
'[attr.aria-labelledby]': 'labelledBy()',
|
|
342
343
|
'[attr.aria-hidden]': 'item.expanded() ? null : "true"',
|
|
343
344
|
'[attr.inert]': 'item.expanded() ? null : ""',
|
|
344
345
|
'[attr.data-state]': 'item.expanded() ? "open" : "closed"',
|