forty-cdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# DropdownMenu
|
|
2
|
+
|
|
3
|
+
> New to overlays in forty-cdk? [Your first overlay](../../../../../docs/your-first-overlay.md) walks a Popover from empty markup to styled-and-animated and explains the `@if` / open-state model and the portal → global CSS rule.
|
|
4
|
+
|
|
5
|
+
Headless implementation of the [WAI-ARIA Menu Button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/): a button that opens a menu of actions on click, ArrowDown, or ArrowUp.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, signal } from '@angular/core';
|
|
11
|
+
import { ForDropdownMenu, ForDropdownMenuTrigger } from 'forty-cdk/dropdown-menu';
|
|
12
|
+
import {
|
|
13
|
+
ForMenuContent,
|
|
14
|
+
ForMenuItem,
|
|
15
|
+
ForMenuRadioGroup,
|
|
16
|
+
ForMenuRadioItem,
|
|
17
|
+
ForMenuSeparator,
|
|
18
|
+
} from 'forty-cdk/menu';
|
|
19
|
+
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'demo-options',
|
|
22
|
+
imports: [
|
|
23
|
+
ForDropdownMenu,
|
|
24
|
+
ForDropdownMenuTrigger,
|
|
25
|
+
ForMenuContent,
|
|
26
|
+
ForMenuItem,
|
|
27
|
+
ForMenuSeparator,
|
|
28
|
+
ForMenuRadioGroup,
|
|
29
|
+
ForMenuRadioItem,
|
|
30
|
+
],
|
|
31
|
+
template: `
|
|
32
|
+
<div forDropdownMenu #menu="forDropdownMenu">
|
|
33
|
+
<button forDropdownMenuTrigger class="dropdown-menu-trigger">Options</button>
|
|
34
|
+
@if (menu.open()) {
|
|
35
|
+
<div forMenuContent animate.leave="fade-out">
|
|
36
|
+
<button forMenuItem (activate)="cut()">Cut</button>
|
|
37
|
+
<button forMenuItem (activate)="copy()">Copy</button>
|
|
38
|
+
<button forMenuItem disabled>Paste</button>
|
|
39
|
+
<hr forMenuSeparator />
|
|
40
|
+
<div forMenuRadioGroup [(value)]="alignment">
|
|
41
|
+
<button forMenuRadioItem value="left">Left</button>
|
|
42
|
+
<button forMenuRadioItem value="center">Center</button>
|
|
43
|
+
<button forMenuRadioItem value="right">Right</button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
}
|
|
47
|
+
</div>
|
|
48
|
+
`,
|
|
49
|
+
})
|
|
50
|
+
export class DemoOptions {
|
|
51
|
+
readonly alignment = signal<string>('left');
|
|
52
|
+
cut() {
|
|
53
|
+
/* ... */
|
|
54
|
+
}
|
|
55
|
+
copy() {
|
|
56
|
+
/* ... */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`@if` is what makes Angular's `animate.enter` / `animate.leave` work — they fire on real mount / unmount.
|
|
62
|
+
|
|
63
|
+
### `#menu="forDropdownMenu"` vs. `[(open)]`
|
|
64
|
+
|
|
65
|
+
The minimal "click trigger → show menu" case needs **neither** a separate `open` signal **nor** a two-way binding. `[forDropdownMenu]` is `exportAs: 'forDropdownMenu'`, so expose the directive instance with a template reference variable — `#menu="forDropdownMenu"` — and drive the `@if` straight off its own `open()` signal, as above. Trigger interactions, item activation, Escape, and outside dismissal all flip it.
|
|
66
|
+
|
|
67
|
+
Reach for the explicit `[(open)]="mySignal"` model binding only when the component class needs to read or drive open state — open it programmatically, persist it, or react to it elsewhere:
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<div forDropdownMenu [(open)]="open">
|
|
71
|
+
<button forDropdownMenuTrigger class="dropdown-menu-trigger">Options</button>
|
|
72
|
+
@if (open()) {
|
|
73
|
+
<div forMenuContent>…</div>
|
|
74
|
+
}
|
|
75
|
+
</div>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Triggers stamped from outside-declared templates
|
|
79
|
+
|
|
80
|
+
Angular resolves `ng-template` DI at the template's **declaration** site, not where it is stamped. A `[forDropdownMenuTrigger]` declared in a template outside the root throws the orphan error even when the template is rendered inside the root via `ngTemplateOutlet`. For that case the selector attribute accepts the root reference as a value, `routerLink`-style — grab it with `#root="forDropdownMenu"` and pass it through the outlet context. The bare valueless attribute keeps resolving via DI.
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<div forDropdownMenu #root="forDropdownMenu">
|
|
84
|
+
<ng-container *ngTemplateOutlet="trig; context: { root }" />
|
|
85
|
+
@if (root.open()) {
|
|
86
|
+
<div forMenuContent>…</div>
|
|
87
|
+
}
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<ng-template #trig let-root="root">
|
|
91
|
+
<button [forDropdownMenuTrigger]="root">Options</button>
|
|
92
|
+
</ng-template>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Pieces
|
|
96
|
+
|
|
97
|
+
| Class | Selector | Role |
|
|
98
|
+
| ------------------------ | -------------------------- | ----------------------------------------------------------------------------------- |
|
|
99
|
+
| `ForDropdownMenu` | `[forDropdownMenu]` | Root. Owns open state, ids, item collection, navigate / typeahead / open semantics. |
|
|
100
|
+
| `ForDropdownMenuTrigger` | `[forDropdownMenuTrigger]` | The button. Wires `aria-haspopup="menu"`, `aria-expanded`, `aria-controls`. |
|
|
101
|
+
|
|
102
|
+
The actual menu items, content surface, radio groups, separators, and groups come from the [`menu/`](../menu/README.md) folder — same primitives are used by `[forContextMenu]`.
|
|
103
|
+
|
|
104
|
+
## Inputs (`ForDropdownMenu`)
|
|
105
|
+
|
|
106
|
+
| API | Default | Description |
|
|
107
|
+
| ------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
108
|
+
| `open` | `false` | Two-way bindable. Whether the menu is shown. |
|
|
109
|
+
| `side` | `'bottom'` | Anchor side of `[forMenuContent]` against the trigger. |
|
|
110
|
+
| `align` | `'start'` | Alignment along `side` (`'start'` / `'center'` / `'end'`). |
|
|
111
|
+
| `sideOffset` | `4` | Gap (px) between the trigger and the content along the main axis. |
|
|
112
|
+
| `alignOffset` | `0` | Gap (px) along the cross axis (parallel to `side`). |
|
|
113
|
+
| `loop` | `true` | Whether arrow navigation wraps at the ends. |
|
|
114
|
+
| `dir` | `'ltr'` | Writing direction. In RTL, ArrowLeft opens submenus and ArrowRight closes them — the swap is automatic. Inherited by every nested `[forMenuSub]` underneath unless overridden. |
|
|
115
|
+
| `disabled` | `false` | When `true`, trigger interactions are ignored. |
|
|
116
|
+
| `dismissible` | `true` | When `false`, Escape and outside interactions don't close. |
|
|
117
|
+
| `returnFocus` | `true` | When `true`, focus returns to the trigger on close. |
|
|
118
|
+
| `ariaLabel` | `null` | Manual `aria-label` on `[forMenuContent]` if the trigger isn't a meaningful name. |
|
|
119
|
+
|
|
120
|
+
## Outputs (`ForDropdownMenu`)
|
|
121
|
+
|
|
122
|
+
Every output below is vetoable — each handler receives a `VetoableEvent` (or `VetoableNativeEvent<E>` when there is a native DOM event). Call `preventDefault()` on the emitted veto to suppress the directive's default action; the original DOM event, when present, is on `.event`.
|
|
123
|
+
|
|
124
|
+
| Output | Payload | Fires on |
|
|
125
|
+
| -------------------- | ------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
126
|
+
| `escapeKeyDown` | `VetoableNativeEvent<KeyboardEvent>` | Escape pressed while the menu is the topmost dismissable layer. |
|
|
127
|
+
| `pointerDownOutside` | `VetoableNativeEvent<PointerEvent>` | Pointer-down on a target outside content + trigger. |
|
|
128
|
+
| `focusOutside` | `VetoableNativeEvent<FocusEvent>` | Focus moves outside content + trigger. |
|
|
129
|
+
| `interactOutside` | `VetoableNativeEvent<PointerEvent \| FocusEvent>` | Composite — fires alongside the two above (and shares their veto state). |
|
|
130
|
+
| `autoFocusOnOpen` | `VetoableEvent` | Just before focus moves to the first / last enabled item on mount. |
|
|
131
|
+
| `autoFocusOnClose` | `VetoableEvent` | Just before focus returns to the trigger on unmount. |
|
|
132
|
+
|
|
133
|
+
`(autoFocusOnOpen)` / `(autoFocusOnClose)` are output-shape because DropdownMenu always routes close transitions through `[(open)]` (via the implicit `openChange` emitter). See [CLAUDE.md › Auto-focus hook shape](../../../../../CLAUDE.md#auto-focus-hook-shape) for why Dialog uses callback-shape inputs instead.
|
|
134
|
+
|
|
135
|
+
## Trigger keyboard
|
|
136
|
+
|
|
137
|
+
| Key | Behavior |
|
|
138
|
+
| --------------------------- | ----------------------------------------------------------------- |
|
|
139
|
+
| `Click` / `Enter` / `Space` | Toggles the menu. On open, focus moves to the first enabled item. |
|
|
140
|
+
| `ArrowDown` | Opens the menu and focuses the first enabled item. |
|
|
141
|
+
| `ArrowUp` | Opens the menu and focuses the last enabled item. |
|
|
142
|
+
|
|
143
|
+
Once focus is in the menu, see [`menu/README.md`](../menu/README.md) for the in-menu keyboard.
|
|
144
|
+
|
|
145
|
+
## Styling
|
|
146
|
+
|
|
147
|
+
forty-cdk ships no styles. Add your own class to each piece — the `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes below.
|
|
148
|
+
|
|
149
|
+
### Data attributes
|
|
150
|
+
|
|
151
|
+
| Piece | Attribute | Values |
|
|
152
|
+
| -------------------------- | --------------- | ------------------ |
|
|
153
|
+
| `[forDropdownMenu]` | `data-state` | `open` \| `closed` |
|
|
154
|
+
| `[forDropdownMenu]` | `data-disabled` | present \| absent |
|
|
155
|
+
| `[forDropdownMenuTrigger]` | `data-state` | `open` \| `closed` |
|
|
156
|
+
| `[forDropdownMenuTrigger]` | `data-disabled` | present \| absent |
|
|
157
|
+
|
|
158
|
+
The menu items, content surface, radio groups, separators, and groups live in the [`menu/`](../menu/README.md) folder — see [menu → Styling](../menu/README.md#styling) for their `data-state` / `data-highlighted` / `data-disabled` attributes and the content-surface CSS custom properties.
|
|
159
|
+
|
|
160
|
+
> The menu content (`[forMenuContent]`) portals to `document.body`, so a class scoped to your trigger's component cannot reach it. Style it with **global CSS** or a class you pass through (see [Styling floating content](../../../../../docs/styling-floating-content.md)). The content host also exposes the shared positioner custom properties — `--for-anchor-width` / `--for-anchor-height`, `--for-available-width` / `--for-available-height`, and `--for-content-transform-origin` — documented in full in [Styling floating content](../../../../../docs/styling-floating-content.md).
|
|
161
|
+
|
|
162
|
+
```css
|
|
163
|
+
.dropdown-menu-trigger .chevron {
|
|
164
|
+
transition: transform 150ms;
|
|
165
|
+
}
|
|
166
|
+
.dropdown-menu-trigger[data-state='open'] .chevron {
|
|
167
|
+
transform: rotate(180deg);
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Behavior notes
|
|
172
|
+
|
|
173
|
+
- **Mount equals open.** The directive does not toggle `[hidden]` — `@if (open())` controls presence so `animate.enter` / `animate.leave` fire on the natural mount cycle.
|
|
174
|
+
- **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.
|
|
175
|
+
- **Initial focus depends on the opening key.** Click / Space / Enter / ArrowDown focus the first enabled item; ArrowUp focuses the last enabled item.
|
|
176
|
+
- **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.
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, input, booleanAttribute, model, Directive, signal, computed, ElementRef } from '@angular/core';
|
|
3
|
+
import { Collection, injectTextDirection, moveIndex, IdGenerator, adoptHostId, registerHandle, reflectDisabled, resolveListNavigation, createDefaults } from 'forty-cdk/core';
|
|
4
|
+
|
|
5
|
+
const FOR_ACCORDION_CONTEXT = new InjectionToken('FOR_ACCORDION_CONTEXT');
|
|
6
|
+
const FOR_ACCORDION_ITEM_CONTEXT = new InjectionToken('FOR_ACCORDION_ITEM_CONTEXT');
|
|
7
|
+
function injectAccordionContext(piece) {
|
|
8
|
+
const ctx = inject(FOR_ACCORDION_CONTEXT, { optional: true });
|
|
9
|
+
if (!ctx) {
|
|
10
|
+
throw new Error(`[forty-cdk/accordion] ${piece} must be used inside a [forAccordion] element.`);
|
|
11
|
+
}
|
|
12
|
+
return ctx;
|
|
13
|
+
}
|
|
14
|
+
function injectAccordionItemContext(piece) {
|
|
15
|
+
const ctx = inject(FOR_ACCORDION_ITEM_CONTEXT, { optional: true });
|
|
16
|
+
if (!ctx) {
|
|
17
|
+
throw new Error(`[forty-cdk/accordion] ${piece} must be used inside a [forAccordionItem] element.`);
|
|
18
|
+
}
|
|
19
|
+
return ctx;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Root of the Accordion primitive. Holds the open value(s) and orchestrates
|
|
24
|
+
* single/multiple expansion, collapse rules, and keyboard navigation between
|
|
25
|
+
* triggers.
|
|
26
|
+
*
|
|
27
|
+
* Implements the [WAI-ARIA Accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/).
|
|
28
|
+
*
|
|
29
|
+
* State is modeled as `value: readonly string[]` regardless of mode:
|
|
30
|
+
* - In single mode (`multiple=false`, default), the array has 0 or 1 element.
|
|
31
|
+
* - In multiple mode, any number of items can be open.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```html
|
|
35
|
+
* <div forAccordion [(value)]="open" collapsible>
|
|
36
|
+
* <div forAccordionItem value="a">
|
|
37
|
+
* <h3><button type="button" forAccordionTrigger>A</button></h3>
|
|
38
|
+
* <section forAccordionContent>...</section>
|
|
39
|
+
* </div>
|
|
40
|
+
* <div forAccordionItem value="b">...</div>
|
|
41
|
+
* </div>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
class ForAccordion {
|
|
45
|
+
#triggers = new Collection();
|
|
46
|
+
/** When true, multiple items can be expanded simultaneously. */
|
|
47
|
+
multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
48
|
+
/**
|
|
49
|
+
* Single mode only: when true, the open item can be collapsed by clicking
|
|
50
|
+
* its trigger. When false, exactly one item stays open at all times once
|
|
51
|
+
* any has been opened.
|
|
52
|
+
*/
|
|
53
|
+
collapsible = input(false, { ...(ngDevMode ? { debugName: "collapsible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
54
|
+
/**
|
|
55
|
+
* Layout direction of the trigger list. `'vertical'` (default) maps
|
|
56
|
+
* ArrowUp/Down to prev/next; `'horizontal'` maps ArrowLeft/Right (with RTL
|
|
57
|
+
* swap when `dir='rtl'`).
|
|
58
|
+
*/
|
|
59
|
+
orientation = input('vertical', /* @ts-ignore */
|
|
60
|
+
...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
61
|
+
/**
|
|
62
|
+
* Writing direction. Only relevant when `orientation='horizontal'`. When
|
|
63
|
+
* unset (default `null`), the inherited ambient direction is resolved from
|
|
64
|
+
* the nearest ancestor carrying a `dir` attribute (or `<html dir>`),
|
|
65
|
+
* defaulting to `'ltr'`. An explicit `[dir]` always wins. The resolved
|
|
66
|
+
* value is reflected to the host `dir` attribute and drives arrow-key
|
|
67
|
+
* semantics.
|
|
68
|
+
*/
|
|
69
|
+
_dirInput = input(null, { ...(ngDevMode ? { debugName: "_dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
|
|
70
|
+
dir = injectTextDirection(this._dirInput);
|
|
71
|
+
/**
|
|
72
|
+
* When true (default), arrow navigation between triggers wraps at the ends —
|
|
73
|
+
* moving past the last trigger focuses the first and vice versa. Set `false`
|
|
74
|
+
* to stop at the boundaries. Mirrors the `loop` input on `ForTabs` and
|
|
75
|
+
* `ForListbox`.
|
|
76
|
+
*/
|
|
77
|
+
loop = input(true, { ...(ngDevMode ? { debugName: "loop" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
78
|
+
/**
|
|
79
|
+
* Two-way bindable. List of currently expanded item values. In single
|
|
80
|
+
* mode the array has 0 or 1 element. The `model()` change emitter
|
|
81
|
+
* (`(valueChange)`) fires only on internal toggles, never on consumer
|
|
82
|
+
* writes via `[(value)]` — observe transitions without binding back.
|
|
83
|
+
*/
|
|
84
|
+
value = model([], /* @ts-ignore */
|
|
85
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
86
|
+
isExpanded(itemValue) {
|
|
87
|
+
return this.value().includes(itemValue);
|
|
88
|
+
}
|
|
89
|
+
toggle(itemValue) {
|
|
90
|
+
const current = this.value();
|
|
91
|
+
const isOpen = current.includes(itemValue);
|
|
92
|
+
if (isOpen) {
|
|
93
|
+
if (!this.canCollapse(itemValue)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.value.set(current.filter((v) => v !== itemValue));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (this.multiple()) {
|
|
100
|
+
this.value.set([...current, itemValue]);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.value.set([itemValue]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
canCollapse(_itemValue) {
|
|
107
|
+
return this.multiple() || this.collapsible();
|
|
108
|
+
}
|
|
109
|
+
focusByOffset(currentTrigger, action) {
|
|
110
|
+
const triggers = this.#triggers.items();
|
|
111
|
+
if (triggers.length === 0) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const currentIndex = triggers.findIndex((t) => t.host === currentTrigger);
|
|
115
|
+
if (currentIndex === -1 && action !== 'first' && action !== 'last') {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const next = moveIndex(currentIndex < 0 ? 0 : currentIndex, triggers.length, action, {
|
|
119
|
+
loop: this.loop(),
|
|
120
|
+
isDisabled: (i) => triggers[i]?.disabled() ?? false,
|
|
121
|
+
});
|
|
122
|
+
if (next !== null) {
|
|
123
|
+
triggers[next]?.host.focus();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
registerTrigger(handle) {
|
|
127
|
+
this.#triggers.register(handle);
|
|
128
|
+
}
|
|
129
|
+
unregisterTrigger(handle) {
|
|
130
|
+
this.#triggers.unregister(handle);
|
|
131
|
+
}
|
|
132
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordion, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
133
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForAccordion, isStandalone: true, selector: "[forAccordion]", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "attr.data-orientation": "orientation()", "attr.dir": "dir()" } }, providers: [{ provide: FOR_ACCORDION_CONTEXT, useExisting: ForAccordion }], exportAs: ["forAccordion"], ngImport: i0 });
|
|
134
|
+
}
|
|
135
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordion, decorators: [{
|
|
136
|
+
type: Directive,
|
|
137
|
+
args: [{
|
|
138
|
+
selector: '[forAccordion]',
|
|
139
|
+
exportAs: 'forAccordion',
|
|
140
|
+
host: {
|
|
141
|
+
'[attr.data-orientation]': 'orientation()',
|
|
142
|
+
'[attr.dir]': 'dir()',
|
|
143
|
+
},
|
|
144
|
+
providers: [{ provide: FOR_ACCORDION_CONTEXT, useExisting: ForAccordion }],
|
|
145
|
+
}]
|
|
146
|
+
}], propDecorators: { multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], _dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* One section of a `ForAccordion`. Owns the unique `value` identifying this
|
|
150
|
+
* item to the root and exposes the per-item context that the trigger and
|
|
151
|
+
* content read.
|
|
152
|
+
*/
|
|
153
|
+
class ForAccordionItem {
|
|
154
|
+
parent = injectAccordionContext('ForAccordionItem');
|
|
155
|
+
#idGen = inject(IdGenerator);
|
|
156
|
+
/** Unique identifier of this item within the accordion. Required. */
|
|
157
|
+
value = input.required(/* @ts-ignore */
|
|
158
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
159
|
+
/**
|
|
160
|
+
* When true, the trigger ignores clicks and reflects the native `disabled`
|
|
161
|
+
* attribute (not `aria-disabled`). The trigger is therefore dropped from the
|
|
162
|
+
* Tab order and skipped by arrow-key navigation, but stays in the
|
|
163
|
+
* accessibility tree so screen readers still announce it. See
|
|
164
|
+
* `ForAccordionTrigger` for the rationale (rule #561 D2).
|
|
165
|
+
*/
|
|
166
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
167
|
+
#triggerId = signal(this.#idGen.next('for-accordion-trigger'), /* @ts-ignore */
|
|
168
|
+
...(ngDevMode ? [{ debugName: "#triggerId" }] : /* istanbul ignore next */ []));
|
|
169
|
+
#contentId = signal(this.#idGen.next('for-accordion-content'), /* @ts-ignore */
|
|
170
|
+
...(ngDevMode ? [{ debugName: "#contentId" }] : /* istanbul ignore next */ []));
|
|
171
|
+
triggerId = this.#triggerId.asReadonly();
|
|
172
|
+
contentId = this.#contentId.asReadonly();
|
|
173
|
+
expanded = computed(() => this.parent.isExpanded(this.value()), /* @ts-ignore */
|
|
174
|
+
...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
175
|
+
/**
|
|
176
|
+
* Adopts a consumer-set static `id` on the `[forAccordionTrigger]` host into
|
|
177
|
+
* `triggerId` (preserving anchors / external `aria-labelledby` references /
|
|
178
|
+
* label `for`) instead of letting the `[id]` host binding clobber it.
|
|
179
|
+
*/
|
|
180
|
+
adoptTriggerId(el) {
|
|
181
|
+
adoptHostId(el, this.#triggerId);
|
|
182
|
+
}
|
|
183
|
+
/** Adopts a consumer-set static `id` on the content host into `contentId`. */
|
|
184
|
+
adoptContentId(el) {
|
|
185
|
+
adoptHostId(el, this.#contentId);
|
|
186
|
+
}
|
|
187
|
+
toggle() {
|
|
188
|
+
if (this.disabled()) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
this.parent.toggle(this.value());
|
|
192
|
+
}
|
|
193
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordionItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
194
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForAccordionItem, isStandalone: true, selector: "[forAccordionItem]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-state": "expanded() ? \"open\" : \"closed\"", "attr.data-disabled": "disabled() ? \"\" : null", "attr.data-orientation": "parent.orientation()" } }, providers: [{ provide: FOR_ACCORDION_ITEM_CONTEXT, useExisting: ForAccordionItem }], exportAs: ["forAccordionItem"], ngImport: i0 });
|
|
195
|
+
}
|
|
196
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordionItem, decorators: [{
|
|
197
|
+
type: Directive,
|
|
198
|
+
args: [{
|
|
199
|
+
selector: '[forAccordionItem]',
|
|
200
|
+
exportAs: 'forAccordionItem',
|
|
201
|
+
host: {
|
|
202
|
+
'[attr.data-state]': 'expanded() ? "open" : "closed"',
|
|
203
|
+
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
204
|
+
'[attr.data-orientation]': 'parent.orientation()',
|
|
205
|
+
},
|
|
206
|
+
providers: [{ provide: FOR_ACCORDION_ITEM_CONTEXT, useExisting: ForAccordionItem }],
|
|
207
|
+
}]
|
|
208
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Header button for a `ForAccordionItem`. Apply on a `<button>` wrapped in a
|
|
212
|
+
* heading element (`<h2>`–`<h6>`) so APG landmark navigation works. The
|
|
213
|
+
* directive host-binds `type="button"` so a trigger inside a `<form>` never
|
|
214
|
+
* submits it on toggle.
|
|
215
|
+
*
|
|
216
|
+
* Handles ARIA wiring, click-to-toggle, and the recommended keyboard
|
|
217
|
+
* navigation (ArrowDown / ArrowUp / Home / End).
|
|
218
|
+
*
|
|
219
|
+
* `aria-controls` is emitted only while the item is expanded — mirroring the
|
|
220
|
+
* overlay triggers' open-only gating — so the reference never dangles at an
|
|
221
|
+
* unmounted panel under the recommended `@if (item.expanded())` mount pattern.
|
|
222
|
+
*/
|
|
223
|
+
class ForAccordionTrigger {
|
|
224
|
+
#host = inject(ElementRef);
|
|
225
|
+
parent = injectAccordionContext('ForAccordionTrigger');
|
|
226
|
+
item = injectAccordionItemContext('ForAccordionTrigger');
|
|
227
|
+
/**
|
|
228
|
+
* APG: aria-disabled is true only when the panel is open AND the accordion
|
|
229
|
+
* disallows collapse. A real `disabled` item is reflected via the native
|
|
230
|
+
* `disabled` attribute instead — the sanctioned exception in rule #561 (D2):
|
|
231
|
+
* the trigger is a real single-purpose `<button>`, not a roving collection
|
|
232
|
+
* item (every trigger stays independently in the Tab order; the arrow-key
|
|
233
|
+
* navigation is an APG-optional enhancement layered on top, not a
|
|
234
|
+
* roving-tabindex collection), so native `disabled` is correct here. The
|
|
235
|
+
* button stays in the accessibility tree — screen readers still announce it
|
|
236
|
+
* as unavailable in browse mode — while being dropped from the Tab order and
|
|
237
|
+
* the arrow-key navigation (which `focusByOffset` already skips), keeping
|
|
238
|
+
* disabled triggers uniformly unreachable. The APG Accordion pattern does not
|
|
239
|
+
* require disabled headers to remain focusable.
|
|
240
|
+
*/
|
|
241
|
+
ariaDisabled = computed(() => {
|
|
242
|
+
if (this.item.disabled()) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
return this.item.expanded() && !this.parent.canCollapse(this.item.value());
|
|
246
|
+
}, /* @ts-ignore */
|
|
247
|
+
...(ngDevMode ? [{ debugName: "ariaDisabled" }] : /* istanbul ignore next */ []));
|
|
248
|
+
constructor() {
|
|
249
|
+
this.item.adoptTriggerId(this.#host.nativeElement);
|
|
250
|
+
const handle = {
|
|
251
|
+
host: this.#host.nativeElement,
|
|
252
|
+
disabled: this.item.disabled,
|
|
253
|
+
};
|
|
254
|
+
registerHandle(handle, (h) => this.parent.registerTrigger(h), (h) => this.parent.unregisterTrigger(h));
|
|
255
|
+
reflectDisabled(this.item.disabled);
|
|
256
|
+
}
|
|
257
|
+
onKeyDown(event) {
|
|
258
|
+
const action = resolveListNavigation(event, {
|
|
259
|
+
orientation: this.parent.orientation(),
|
|
260
|
+
dir: this.parent.dir(),
|
|
261
|
+
});
|
|
262
|
+
if (!action) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
this.parent.focusByOffset(this.#host.nativeElement, action);
|
|
267
|
+
}
|
|
268
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordionTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
269
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForAccordionTrigger, isStandalone: true, selector: "[forAccordionTrigger]", host: { attributes: { "type": "button" }, listeners: { "click": "item.toggle()", "keydown": "onKeyDown($event)" }, properties: { "id": "item.triggerId()", "attr.aria-expanded": "item.expanded() ? \"true\" : \"false\"", "attr.aria-controls": "item.expanded() ? item.contentId() : null", "attr.aria-disabled": "ariaDisabled() ? \"true\" : null", "attr.data-state": "item.expanded() ? \"open\" : \"closed\"", "attr.data-orientation": "parent.orientation()" } }, exportAs: ["forAccordionTrigger"], ngImport: i0 });
|
|
270
|
+
}
|
|
271
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordionTrigger, decorators: [{
|
|
272
|
+
type: Directive,
|
|
273
|
+
args: [{
|
|
274
|
+
selector: '[forAccordionTrigger]',
|
|
275
|
+
exportAs: 'forAccordionTrigger',
|
|
276
|
+
host: {
|
|
277
|
+
type: 'button',
|
|
278
|
+
'[id]': 'item.triggerId()',
|
|
279
|
+
'[attr.aria-expanded]': 'item.expanded() ? "true" : "false"',
|
|
280
|
+
'[attr.aria-controls]': 'item.expanded() ? item.contentId() : null',
|
|
281
|
+
'[attr.aria-disabled]': 'ariaDisabled() ? "true" : null',
|
|
282
|
+
'[attr.data-state]': 'item.expanded() ? "open" : "closed"',
|
|
283
|
+
'[attr.data-orientation]': 'parent.orientation()',
|
|
284
|
+
'(click)': 'item.toggle()',
|
|
285
|
+
'(keydown)': 'onKeyDown($event)',
|
|
286
|
+
},
|
|
287
|
+
}]
|
|
288
|
+
}], ctorParameters: () => [] });
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Panel revealed by a `ForAccordionTrigger`. The directive does not manage
|
|
292
|
+
* DOM presence — wrap with `@if (item.expanded())` so panels mount and
|
|
293
|
+
* unmount with the expanded state and `animate.enter` / `animate.leave`
|
|
294
|
+
* work natively. If the consumer prefers to keep the panel mounted (for
|
|
295
|
+
* CSS-only transitions or to preserve internal state), the directive
|
|
296
|
+
* reflects `aria-hidden="true"` and `inert` while closed so the panel is
|
|
297
|
+
* removed from the accessibility tree and focus order automatically.
|
|
298
|
+
*
|
|
299
|
+
* APG note: `role="region"` adds the panel to the landmark navigation tree.
|
|
300
|
+
* If the accordion has 6+ simultaneously expandable panels, consider not
|
|
301
|
+
* using region — the directive will gain an opt-out input when this comes up
|
|
302
|
+
* in real usage.
|
|
303
|
+
*/
|
|
304
|
+
class ForAccordionContent {
|
|
305
|
+
parent = injectAccordionContext('ForAccordionContent');
|
|
306
|
+
item = injectAccordionItemContext('ForAccordionContent');
|
|
307
|
+
constructor() {
|
|
308
|
+
this.item.adoptContentId(inject(ElementRef).nativeElement);
|
|
309
|
+
}
|
|
310
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordionContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
311
|
+
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": "item.triggerId()", "attr.aria-hidden": "item.expanded() ? null : \"true\"", "attr.inert": "item.expanded() ? null : \"\"", "attr.data-state": "item.expanded() ? \"open\" : \"closed\"", "attr.data-orientation": "parent.orientation()" } }, exportAs: ["forAccordionContent"], ngImport: i0 });
|
|
312
|
+
}
|
|
313
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForAccordionContent, decorators: [{
|
|
314
|
+
type: Directive,
|
|
315
|
+
args: [{
|
|
316
|
+
selector: '[forAccordionContent]',
|
|
317
|
+
exportAs: 'forAccordionContent',
|
|
318
|
+
host: {
|
|
319
|
+
'[id]': 'item.contentId()',
|
|
320
|
+
'[attr.role]': '"region"',
|
|
321
|
+
'[attr.aria-labelledby]': 'item.triggerId()',
|
|
322
|
+
'[attr.aria-hidden]': 'item.expanded() ? null : "true"',
|
|
323
|
+
'[attr.inert]': 'item.expanded() ? null : ""',
|
|
324
|
+
'[attr.data-state]': 'item.expanded() ? "open" : "closed"',
|
|
325
|
+
'[attr.data-orientation]': 'parent.orientation()',
|
|
326
|
+
},
|
|
327
|
+
}]
|
|
328
|
+
}], ctorParameters: () => [] });
|
|
329
|
+
|
|
330
|
+
const FALLBACK = {};
|
|
331
|
+
const { token, provideDefaults } = createDefaults('FOR_ACCORDION_DEFAULTS', FALLBACK);
|
|
332
|
+
/** Token holding the resolved accordion defaults for the current scope. */
|
|
333
|
+
const FOR_ACCORDION_DEFAULTS = token;
|
|
334
|
+
/**
|
|
335
|
+
* Configures forty-cdk accordion defaults for this injector scope. Partial
|
|
336
|
+
* overrides inherit unspecified keys from the parent scope (or library
|
|
337
|
+
* defaults at the root).
|
|
338
|
+
*/
|
|
339
|
+
function provideForAccordionDefaults(defaults = {}) {
|
|
340
|
+
return provideDefaults(defaults);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Generated bundle index. Do not edit.
|
|
345
|
+
*/
|
|
346
|
+
|
|
347
|
+
export { FOR_ACCORDION_CONTEXT, FOR_ACCORDION_DEFAULTS, FOR_ACCORDION_ITEM_CONTEXT, ForAccordion, ForAccordionContent, ForAccordionItem, ForAccordionTrigger, provideForAccordionDefaults };
|
|
348
|
+
//# sourceMappingURL=forty-cdk-accordion.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forty-cdk-accordion.mjs","sources":["../../../projects/forty-cdk/accordion/src/accordion-context.ts","../../../projects/forty-cdk/accordion/src/accordion.ts","../../../projects/forty-cdk/accordion/src/accordion-item.ts","../../../projects/forty-cdk/accordion/src/accordion-trigger.ts","../../../projects/forty-cdk/accordion/src/accordion-content.ts","../../../projects/forty-cdk/accordion/src/accordion-defaults.ts","../../../projects/forty-cdk/accordion/src/forty-cdk-accordion.ts"],"sourcesContent":["import { inject, InjectionToken, type Signal } from '@angular/core';\n\nimport { type ListNavigationAction, type WritingDirection } from 'forty-cdk/core';\n\n/**\n * Registry entry for one `ForAccordionTrigger`. Triggers register their host\n * element so the root can drive keyboard navigation over its own triggers\n * without an unscoped DOM query that would leak into nested accordions.\n */\nexport interface ForAccordionTriggerHandle {\n readonly host: HTMLElement;\n readonly disabled: Signal<boolean>;\n}\n\n/**\n * Coordination contract owned by the `ForAccordion` root.\n * Items derive their state from this context; triggers route clicks and\n * keyboard navigation through it.\n */\nexport interface ForAccordionContext {\n readonly multiple: Signal<boolean>;\n readonly collapsible: Signal<boolean>;\n readonly orientation: Signal<'horizontal' | 'vertical'>;\n readonly dir: Signal<WritingDirection>;\n isExpanded(itemValue: string): boolean;\n toggle(itemValue: string): void;\n /** Whether the given item is allowed to collapse right now. */\n canCollapse(itemValue: string): boolean;\n /**\n * Focus a sibling trigger relative to the one currently focused. Disabled\n * triggers are skipped. `currentTrigger` is the element from which the\n * keyboard event originated. Navigation stays within this accordion's own\n * registered triggers, so a nested accordion does not cross-contaminate.\n */\n focusByOffset(currentTrigger: HTMLElement, action: ListNavigationAction): void;\n registerTrigger(handle: ForAccordionTriggerHandle): void;\n unregisterTrigger(handle: ForAccordionTriggerHandle): void;\n}\n\n/**\n * Per-item state contract. Provided by `ForAccordionItem`, consumed by\n * its `ForAccordionTrigger` and `ForAccordionContent`.\n */\nexport interface ForAccordionItemContext {\n readonly expanded: Signal<boolean>;\n readonly disabled: Signal<boolean>;\n readonly value: Signal<string>;\n readonly triggerId: Signal<string>;\n readonly contentId: Signal<string>;\n /** Adopts a consumer-set static `id` on the trigger host into `triggerId`. */\n adoptTriggerId(el: HTMLElement): void;\n /** Adopts a consumer-set static `id` on the content host into `contentId`. */\n adoptContentId(el: HTMLElement): void;\n toggle(): void;\n}\n\nexport const FOR_ACCORDION_CONTEXT = new InjectionToken<ForAccordionContext>(\n 'FOR_ACCORDION_CONTEXT',\n);\n\nexport const FOR_ACCORDION_ITEM_CONTEXT = new InjectionToken<ForAccordionItemContext>(\n 'FOR_ACCORDION_ITEM_CONTEXT',\n);\n\nexport function injectAccordionContext(piece: string): ForAccordionContext {\n const ctx = inject(FOR_ACCORDION_CONTEXT, { optional: true });\n if (!ctx) {\n throw new Error(`[forty-cdk/accordion] ${piece} must be used inside a [forAccordion] element.`);\n }\n return ctx;\n}\n\nexport function injectAccordionItemContext(piece: string): ForAccordionItemContext {\n const ctx = inject(FOR_ACCORDION_ITEM_CONTEXT, { optional: true });\n if (!ctx) {\n throw new Error(\n `[forty-cdk/accordion] ${piece} must be used inside a [forAccordionItem] element.`,\n );\n }\n return ctx;\n}\n","import { booleanAttribute, Directive, input, model } from '@angular/core';\n\nimport {\n Collection,\n type ListNavigationAction,\n moveIndex,\n type WritingDirection,\n injectTextDirection,\n} from 'forty-cdk/core';\nimport {\n FOR_ACCORDION_CONTEXT,\n type ForAccordionContext,\n type ForAccordionTriggerHandle,\n} from './accordion-context';\n\n/**\n * Root of the Accordion primitive. Holds the open value(s) and orchestrates\n * single/multiple expansion, collapse rules, and keyboard navigation between\n * triggers.\n *\n * Implements the [WAI-ARIA Accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/).\n *\n * State is modeled as `value: readonly string[]` regardless of mode:\n * - In single mode (`multiple=false`, default), the array has 0 or 1 element.\n * - In multiple mode, any number of items can be open.\n *\n * @example\n * ```html\n * <div forAccordion [(value)]=\"open\" collapsible>\n * <div forAccordionItem value=\"a\">\n * <h3><button type=\"button\" forAccordionTrigger>A</button></h3>\n * <section forAccordionContent>...</section>\n * </div>\n * <div forAccordionItem value=\"b\">...</div>\n * </div>\n * ```\n */\n@Directive({\n selector: '[forAccordion]',\n exportAs: 'forAccordion',\n host: {\n '[attr.data-orientation]': 'orientation()',\n '[attr.dir]': 'dir()',\n },\n providers: [{ provide: FOR_ACCORDION_CONTEXT, useExisting: ForAccordion }],\n})\nexport class ForAccordion implements ForAccordionContext {\n readonly #triggers = new Collection<ForAccordionTriggerHandle>();\n\n /** When true, multiple items can be expanded simultaneously. */\n readonly multiple = input(false, { transform: booleanAttribute });\n\n /**\n * Single mode only: when true, the open item can be collapsed by clicking\n * its trigger. When false, exactly one item stays open at all times once\n * any has been opened.\n */\n readonly collapsible = input(false, { transform: booleanAttribute });\n\n /**\n * Layout direction of the trigger list. `'vertical'` (default) maps\n * ArrowUp/Down to prev/next; `'horizontal'` maps ArrowLeft/Right (with RTL\n * swap when `dir='rtl'`).\n */\n readonly orientation = input<'horizontal' | 'vertical'>('vertical');\n\n /**\n * Writing direction. Only relevant when `orientation='horizontal'`. When\n * unset (default `null`), the inherited ambient direction is resolved from\n * the nearest ancestor carrying a `dir` attribute (or `<html dir>`),\n * defaulting to `'ltr'`. An explicit `[dir]` always wins. The resolved\n * value is reflected to the host `dir` attribute and drives arrow-key\n * semantics.\n */\n readonly _dirInput = input<WritingDirection | null>(null, { alias: 'dir' });\n readonly dir = injectTextDirection(this._dirInput);\n\n /**\n * When true (default), arrow navigation between triggers wraps at the ends —\n * moving past the last trigger focuses the first and vice versa. Set `false`\n * to stop at the boundaries. Mirrors the `loop` input on `ForTabs` and\n * `ForListbox`.\n */\n readonly loop = input(true, { transform: booleanAttribute });\n\n /**\n * Two-way bindable. List of currently expanded item values. In single\n * mode the array has 0 or 1 element. The `model()` change emitter\n * (`(valueChange)`) fires only on internal toggles, never on consumer\n * writes via `[(value)]` — observe transitions without binding back.\n */\n readonly value = model<readonly string[]>([]);\n\n isExpanded(itemValue: string): boolean {\n return this.value().includes(itemValue);\n }\n\n toggle(itemValue: string): void {\n const current = this.value();\n const isOpen = current.includes(itemValue);\n\n if (isOpen) {\n if (!this.canCollapse(itemValue)) {\n return;\n }\n this.value.set(current.filter((v) => v !== itemValue));\n return;\n }\n\n if (this.multiple()) {\n this.value.set([...current, itemValue]);\n } else {\n this.value.set([itemValue]);\n }\n }\n\n canCollapse(_itemValue: string): boolean {\n return this.multiple() || this.collapsible();\n }\n\n focusByOffset(currentTrigger: HTMLElement, action: ListNavigationAction): void {\n const triggers = this.#triggers.items();\n if (triggers.length === 0) {\n return;\n }\n const currentIndex = triggers.findIndex((t) => t.host === currentTrigger);\n if (currentIndex === -1 && action !== 'first' && action !== 'last') {\n return;\n }\n const next = moveIndex(currentIndex < 0 ? 0 : currentIndex, triggers.length, action, {\n loop: this.loop(),\n isDisabled: (i) => triggers[i]?.disabled() ?? false,\n });\n if (next !== null) {\n triggers[next]?.host.focus();\n }\n }\n\n registerTrigger(handle: ForAccordionTriggerHandle): void {\n this.#triggers.register(handle);\n }\n\n unregisterTrigger(handle: ForAccordionTriggerHandle): void {\n this.#triggers.unregister(handle);\n }\n}\n","import { booleanAttribute, computed, Directive, inject, input, signal } from '@angular/core';\n\nimport { adoptHostId, IdGenerator } from 'forty-cdk/core';\nimport {\n FOR_ACCORDION_ITEM_CONTEXT,\n type ForAccordionItemContext,\n injectAccordionContext,\n} from './accordion-context';\n\n/**\n * One section of a `ForAccordion`. Owns the unique `value` identifying this\n * item to the root and exposes the per-item context that the trigger and\n * content read.\n */\n@Directive({\n selector: '[forAccordionItem]',\n exportAs: 'forAccordionItem',\n host: {\n '[attr.data-state]': 'expanded() ? \"open\" : \"closed\"',\n '[attr.data-disabled]': 'disabled() ? \"\" : null',\n '[attr.data-orientation]': 'parent.orientation()',\n },\n providers: [{ provide: FOR_ACCORDION_ITEM_CONTEXT, useExisting: ForAccordionItem }],\n})\nexport class ForAccordionItem implements ForAccordionItemContext {\n protected readonly parent = injectAccordionContext('ForAccordionItem');\n readonly #idGen = inject(IdGenerator);\n\n /** Unique identifier of this item within the accordion. Required. */\n readonly value = input.required<string>();\n\n /**\n * When true, the trigger ignores clicks and reflects the native `disabled`\n * attribute (not `aria-disabled`). The trigger is therefore dropped from the\n * Tab order and skipped by arrow-key navigation, but stays in the\n * accessibility tree so screen readers still announce it. See\n * `ForAccordionTrigger` for the rationale (rule #561 D2).\n */\n readonly disabled = input(false, { transform: booleanAttribute });\n\n readonly #triggerId = signal(this.#idGen.next('for-accordion-trigger'));\n readonly #contentId = signal(this.#idGen.next('for-accordion-content'));\n\n readonly triggerId = this.#triggerId.asReadonly();\n readonly contentId = this.#contentId.asReadonly();\n\n readonly expanded = computed(() => this.parent.isExpanded(this.value()));\n\n /**\n * Adopts a consumer-set static `id` on the `[forAccordionTrigger]` host into\n * `triggerId` (preserving anchors / external `aria-labelledby` references /\n * label `for`) instead of letting the `[id]` host binding clobber it.\n */\n adoptTriggerId(el: HTMLElement): void {\n adoptHostId(el, this.#triggerId);\n }\n\n /** Adopts a consumer-set static `id` on the content host into `contentId`. */\n adoptContentId(el: HTMLElement): void {\n adoptHostId(el, this.#contentId);\n }\n\n toggle(): void {\n if (this.disabled()) {\n return;\n }\n this.parent.toggle(this.value());\n }\n}\n","import { computed, Directive, ElementRef, inject } from '@angular/core';\n\nimport { registerHandle, reflectDisabled, resolveListNavigation } from 'forty-cdk/core';\nimport { injectAccordionContext, injectAccordionItemContext } from './accordion-context';\n\n/**\n * Header button for a `ForAccordionItem`. Apply on a `<button>` wrapped in a\n * heading element (`<h2>`–`<h6>`) so APG landmark navigation works. The\n * directive host-binds `type=\"button\"` so a trigger inside a `<form>` never\n * submits it on toggle.\n *\n * Handles ARIA wiring, click-to-toggle, and the recommended keyboard\n * navigation (ArrowDown / ArrowUp / Home / End).\n *\n * `aria-controls` is emitted only while the item is expanded — mirroring the\n * overlay triggers' open-only gating — so the reference never dangles at an\n * unmounted panel under the recommended `@if (item.expanded())` mount pattern.\n */\n@Directive({\n selector: '[forAccordionTrigger]',\n exportAs: 'forAccordionTrigger',\n host: {\n type: 'button',\n '[id]': 'item.triggerId()',\n '[attr.aria-expanded]': 'item.expanded() ? \"true\" : \"false\"',\n '[attr.aria-controls]': 'item.expanded() ? item.contentId() : null',\n '[attr.aria-disabled]': 'ariaDisabled() ? \"true\" : null',\n '[attr.data-state]': 'item.expanded() ? \"open\" : \"closed\"',\n '[attr.data-orientation]': 'parent.orientation()',\n '(click)': 'item.toggle()',\n '(keydown)': 'onKeyDown($event)',\n },\n})\nexport class ForAccordionTrigger {\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n protected readonly parent = injectAccordionContext('ForAccordionTrigger');\n protected readonly item = injectAccordionItemContext('ForAccordionTrigger');\n\n /**\n * APG: aria-disabled is true only when the panel is open AND the accordion\n * disallows collapse. A real `disabled` item is reflected via the native\n * `disabled` attribute instead — the sanctioned exception in rule #561 (D2):\n * the trigger is a real single-purpose `<button>`, not a roving collection\n * item (every trigger stays independently in the Tab order; the arrow-key\n * navigation is an APG-optional enhancement layered on top, not a\n * roving-tabindex collection), so native `disabled` is correct here. The\n * button stays in the accessibility tree — screen readers still announce it\n * as unavailable in browse mode — while being dropped from the Tab order and\n * the arrow-key navigation (which `focusByOffset` already skips), keeping\n * disabled triggers uniformly unreachable. The APG Accordion pattern does not\n * require disabled headers to remain focusable.\n */\n protected readonly ariaDisabled = computed(() => {\n if (this.item.disabled()) {\n return false;\n }\n return this.item.expanded() && !this.parent.canCollapse(this.item.value());\n });\n\n constructor() {\n this.item.adoptTriggerId(this.#host.nativeElement);\n const handle = {\n host: this.#host.nativeElement,\n disabled: this.item.disabled,\n };\n registerHandle(\n handle,\n (h) => this.parent.registerTrigger(h),\n (h) => this.parent.unregisterTrigger(h),\n );\n reflectDisabled(this.item.disabled);\n }\n\n protected onKeyDown(event: KeyboardEvent): void {\n const action = resolveListNavigation(event, {\n orientation: this.parent.orientation(),\n dir: this.parent.dir(),\n });\n if (!action) {\n return;\n }\n event.preventDefault();\n this.parent.focusByOffset(this.#host.nativeElement, action);\n }\n}\n","import { Directive, ElementRef, inject } from '@angular/core';\n\nimport { injectAccordionContext, injectAccordionItemContext } from './accordion-context';\n\n/**\n * Panel revealed by a `ForAccordionTrigger`. The directive does not manage\n * DOM presence — wrap with `@if (item.expanded())` so panels mount and\n * unmount with the expanded state and `animate.enter` / `animate.leave`\n * work natively. If the consumer prefers to keep the panel mounted (for\n * CSS-only transitions or to preserve internal state), the directive\n * reflects `aria-hidden=\"true\"` and `inert` while closed so the panel is\n * removed from the accessibility tree and focus order automatically.\n *\n * APG note: `role=\"region\"` adds the panel to the landmark navigation tree.\n * If the accordion has 6+ simultaneously expandable panels, consider not\n * using region — the directive will gain an opt-out input when this comes up\n * in real usage.\n */\n@Directive({\n selector: '[forAccordionContent]',\n exportAs: 'forAccordionContent',\n host: {\n '[id]': 'item.contentId()',\n '[attr.role]': '\"region\"',\n '[attr.aria-labelledby]': 'item.triggerId()',\n '[attr.aria-hidden]': 'item.expanded() ? null : \"true\"',\n '[attr.inert]': 'item.expanded() ? null : \"\"',\n '[attr.data-state]': 'item.expanded() ? \"open\" : \"closed\"',\n '[attr.data-orientation]': 'parent.orientation()',\n },\n})\nexport class ForAccordionContent {\n protected readonly parent = injectAccordionContext('ForAccordionContent');\n protected readonly item = injectAccordionItemContext('ForAccordionContent');\n\n constructor() {\n this.item.adoptContentId(inject<ElementRef<HTMLElement>>(ElementRef).nativeElement);\n }\n}\n","import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * Defaults inherited by descendant accordions in the surrounding injector\n * scope. Configure with `provideForAccordionDefaults`. The shape is a stub\n * today — present so future per-scope tuning can land without churning the\n * public surface.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ForAccordionDefaults {}\n\nconst FALLBACK: ForAccordionDefaults = {};\n\nconst { token, provideDefaults } = createDefaults<ForAccordionDefaults>(\n 'FOR_ACCORDION_DEFAULTS',\n FALLBACK,\n);\n\n/** Token holding the resolved accordion defaults for the current scope. */\nexport const FOR_ACCORDION_DEFAULTS = token;\n\n/**\n * Configures forty-cdk accordion defaults for this injector scope. Partial\n * overrides inherit unspecified keys from the parent scope (or library\n * defaults at the root).\n */\nexport function provideForAccordionDefaults(\n defaults: Partial<ForAccordionDefaults> = {},\n): Provider[] {\n return provideDefaults(defaults);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|