Skip to content

Commit 53d7fb3

Browse files
authored
fix(cdk/menu): update docs to reflect current implementation and add (#24884)
correct role for triggers
1 parent 52cf101 commit 53d7fb3

15 files changed

+246
-145
lines changed

src/cdk/menu/menu-item-checkbox.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {CdkMenuItem} from './menu-item';
1919
exportAs: 'cdkMenuItemCheckbox',
2020
host: {
2121
'role': 'menuitemcheckbox',
22+
'[class.cdk-menu-item-checkbox]': 'true',
2223
},
2324
providers: [
2425
{provide: CdkMenuItemSelectable, useExisting: CdkMenuItemCheckbox},

src/cdk/menu/menu-item-radio.ts

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ let nextId = 0;
2929
exportAs: 'cdkMenuItemRadio',
3030
host: {
3131
'role': 'menuitemradio',
32+
'[class.cdk-menu-item-radio]': 'true',
3233
},
3334
providers: [
3435
{provide: CdkMenuItemSelectable, useExisting: CdkMenuItemRadio},

src/cdk/menu/menu-trigger.ts

+10
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnDestroy {
8989
@Optional() private readonly _directionality?: Directionality,
9090
) {
9191
super(injector, viewContainerRef, menuStack);
92+
this._setRole();
9293
this._registerCloseHandler();
9394
this._subscribeToMenuStackClosed();
9495
this._subscribeToMouseEnter();
@@ -326,4 +327,13 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnDestroy {
326327
});
327328
}
328329
}
330+
331+
/** Sets the role attribute for this trigger if needed. */
332+
private _setRole() {
333+
// If this trigger is part of another menu, the cdkMenuItem directive will handle setting the
334+
// role, otherwise this is a standalone trigger, and we should ensure it has role="button".
335+
if (!this._parentMenu) {
336+
this._elementRef.nativeElement.setAttribute('role', 'button');
337+
}
338+
}
329339
}

src/cdk/menu/menu.md

+122-109
Original file line numberDiff line numberDiff line change
@@ -10,68 +10,113 @@ directives apply their associated ARIA roles to their host element.
1010
The directives in `@angular/cdk/menu` set the appropriate roles on their host element.
1111

1212
| Directive | ARIA Role |
13-
| ------------------- | ---------------- |
13+
|---------------------|------------------|
1414
| CdkMenuBar | menubar |
1515
| CdkMenu | menu |
1616
| CdkMenuGroup | group |
1717
| CdkMenuItem | menuitem |
1818
| CdkMenuItemRadio | menuitemradio |
1919
| CdkMenuItemCheckbox | menuitemcheckbox |
20+
| CdkMenuTrigger | button |
21+
22+
### CSS Styles and Classes
23+
24+
The `@angular/cdk/menu` is designed to be highly customizable to your needs. It therefore does not
25+
make any assumptions about how elements should be styled. You are expected to apply any required
26+
CSS styles, but the directives do apply CSS classes to make it easier for you to add custom styles.
27+
The available CSS classes are listed below, by directive.
28+
29+
| Directive | CSS Class | Applied... |
30+
|:----------------------|--------------------------|------------------------------------------------|
31+
| `cdkMenu` | `cdk-menu` | Always |
32+
| `cdkMenu` | `cdk-menu-inline` | If the menu is an [inline menu](#menu-content) |
33+
| `cdkMenuBar` | `cdk-menu-bar` | Always |
34+
| `cdkMenuGroup` | `cdk-menu-group` | Always |
35+
| `cdkMenuItem` | `cdk-menu-item` | Always |
36+
| `cdkMenuItemCheckbox` | `cdk-menu-item` | Always |
37+
| `cdkMenuItemCheckbox` | `cdk-menu-item-checkbox` | Always |
38+
| `cdkMenuItemRadio` | `cdk-menu-item` | Always |
39+
| `cdkMenuItemRadio` | `cdk-menu-item-radio` | Always |
40+
| `cdkMenuTriggerFor` | `cdk-menu-trigger` | Always |
2041

2142
### Getting started
2243

2344
Import the `CdkMenuModule` into the `NgModule` in which you want to create menus. You can then apply
2445
menu directives to build your custom menu. A typical menu consists of the following directives:
2546

26-
- `cdkMenuTriggerFor` - links a trigger button to a menu you intend to open
27-
- `cdkMenuPanel` - wraps the menu and provides a link between the `cdkMenuTriggerFor` and the
28-
`cdkMenu`
29-
- `cdkMenu` - the actual menu you want to open
30-
- `cdkMenuItem` - added to each button
47+
- `cdkMenuTriggerFor` - links a trigger element to an `ng-template` containing the menu to be opened
48+
- `cdkMenu` - creates the menu content opened by the trigger
49+
- `cdkMenuItem` - added to each item in the menu
3150

3251
<!-- example({
33-
"example": "cdk-menu-standalone-menu",
34-
"file": "cdk-menu-standalone-menu-example.html"
52+
"example": "cdk-menu-standalone-trigger",
53+
"file": "cdk-menu-standalone-trigger-example.html"
3554
}) -->
3655

3756
Most menu interactions consist of two parts: a trigger and a menu panel.
3857

3958
#### Triggers
4059

41-
You must add the `cdkMenuItem` and `cdkMenuTriggerFor` directives to triggers like so,
60+
You can add `cdkMenuTriggerFor` to any button to make it a trigger for the given menu, or any menu
61+
item to make it a trigger for a submenu. When adding this directive, be sure to pass a reference to
62+
the template containing the menu it should open. Users can toggle the associated menu using a mouse
63+
or keyboard.
64+
65+
<!-- example({"example":"cdk-menu-standalone-trigger",
66+
"file":"cdk-menu-standalone-trigger-example.html",
67+
"region":"trigger"}) -->
4268

43-
```html
44-
<button cdkMenuItem [cdkMenuTriggerFor]="menu">Click me!</button>
45-
```
69+
When creating a submenu trigger, add both `cdkMenuItem` and `cdkMenuTriggerFor` like so,
4670

47-
Adding `cdkMenuItem` gives you keyboard navigation and focus management. Associating a trigger with
48-
a menu is done through the `cdkMenuTriggerFor` directive and you must provide a template reference
49-
variable to it. Once both of these directives are set, you can toggle the associated menu
50-
programmatically, using a mouse or using a keyboard.
71+
<!-- example({"example":"cdk-menu-menubar",
72+
"file":"cdk-menu-menubar-example.html",
73+
"region":"file-trigger"}) -->
5174

52-
#### Menu panels
75+
#### Menu content
5376

54-
You must wrap pop-up menus with an `ng-template` with the `cdkMenuPanel` directive and a reference
55-
variable which must be of type `cdkMenuPanel`. Further, the `cdkMenu` must also reference the
56-
`cdkMenuPanel`.
77+
There are two types of menus:
78+
* _inline menus_ are always present on the page
79+
* _pop-up menus_ can be toggled to hide or show by the user
5780

58-
```html
59-
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
60-
<div cdkMenu [cdkMenuPanel]="panel">
61-
<!-- some content -->
62-
</div>
63-
</ng-template>
64-
```
81+
You can create menus by marking their content element with the `cdkMenu` or `cdkMenuBar`
82+
directives. You can create several types of menu interaction which are discussed below.
83+
84+
All type of menus should exclusively contain elements with role `menuitem`, `menuitemcheckbox`,
85+
`menuitemradio`, or `group`. Supporting directives that automatically apply these roles are
86+
discussed below.
6587

6688
Note that Angular CDK provides no styles; you must add styles as part of building your custom menu.
6789

90+
### Inline Menus
91+
92+
An _inline menu_ is a menu that lives directly on the page rather than in a pop-up associated with a
93+
trigger. You can use an inline menu when you want a persistent menu interaction on a page. Menu
94+
items within an inline menus are logically grouped together, and you can navigate through them
95+
using your keyboard. You can create an inline menu by adding the `cdkMenu` directive to the element
96+
you want to serve as the menu content.
97+
98+
<!-- example({
99+
"example": "cdk-menu-inline",
100+
"file": "cdk-menu-inline-example.html"
101+
}) -->
102+
103+
### Pop-up Menus
104+
105+
You can create pop-up menus using the `cdkMenu` directive as well. Add this directive to the
106+
element you want to serve as the content for your pop-up menu. Then wrap the content element in an
107+
`ng-template` and reference the template from the `cdkMenuTriggerFor` property of the trigger. This
108+
will allow the trigger to show and hide the menu content as needed.
109+
110+
<!-- example({
111+
"example": "cdk-menu-standalone-menu",
112+
"file": "cdk-menu-standalone-menu-example.html"
113+
}) -->
114+
68115
### Menu Bars
69116

70-
The `CdkMenuBar` directive follows the [ARIA menubar][menubar] spec and behaves similar to a desktop
71-
app menubar. It consists of at least one `CdkMenuItem` which triggers a submenu. A menubar can be
72-
layed out horizontally or vertically (defaulting to horizontal). If the layout changes, you must set
73-
the `orientation` attribute to match in order for the keyboard navigation to work properly and for
74-
menus to open up in the correct location.
117+
Menu bars are a type of inline menu that you can create using the `cdkMenuBar` directive. They
118+
follow the [ARIA menubar][menubar] spec and behave similarly to a desktop application menubar. Each
119+
bar consists of at least one `cdkMenuItem` that triggers a submenu.
75120

76121
<!-- example({
77122
"example": "cdk-menu-menubar",
@@ -80,7 +125,8 @@ menus to open up in the correct location.
80125

81126
### Context Menus
82127

83-
A context menu opens when a user right-clicks within some container element. You can mark a
128+
A context menus is a type of pop-up menu that doesn't have a traditional trigger element, instead
129+
it is triggered when a user right-clicks within some container element. You can mark a
84130
container element with the `cdkContextMenuTriggerFor`, which behaves like `cdkMenuTriggerFor` except
85131
that it responds to the browser's native `contextmenu` event. Custom context menus appear next to
86132
the cursor, similarly to native context menus.
@@ -93,69 +139,58 @@ the cursor, similarly to native context menus.
93139
You can nest context menu container elements. Upon right-click, the menu associated with the closest
94140
container element will open.
95141

96-
```html
97-
<div [cdkContextMenuTriggerFor]="outer">
98-
My outer context
99-
<div [cdkContextMenuTriggerFor]="inner">My inner context</div>
100-
</div>
101-
```
102-
103-
In the example above, right clicking on "My inner context" will open up the "inner" menu and right
104-
clicking inside "My outer context" will open up the "outer" menu.
105-
106-
### Inline Menus
107-
108-
An _inline menu_ is a menu that lives directly on the page rather than a pop-up associated with a
109-
trigger. You can use an inline menu when you want a persistent menu interaction on a page. Menu
110-
items within an inline menus are logically grouped together and you can navigate through them using
111-
your keyboard.
112-
113142
<!-- example({
114-
"example": "cdk-menu-inline",
115-
"file": "cdk-menu-inline-example.html"
143+
"example": "cdk-menu-nested-context",
144+
"file": "cdk-menu-nested-context-example.html",
145+
"region": "triggers"
116146
}) -->
117147

118-
### Menu Items
119-
120-
Both menu and menubar elements should exclusively contain menuitem elements. This directive allows
121-
the items to be navigated to via keyboard interaction.
148+
In the example above, right-clicking on "Inner context menu" will open up the "inner" menu and
149+
right-clicking inside "Outer context menu" will open up the "outer" menu.
122150

123-
A menuitem by itself can provide some user defined action by hooking into the `cdkMenuItemTriggered`
124-
output. An example may be a close button which performs some closing logic.
151+
### Menu Items
125152

126-
```html
127-
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
128-
<div cdkMenu [cdkMenuPanel]="panel">
129-
<button cdkMenuItem (cdkMenuItemTriggered)="closeApp()">Close</button>
130-
</div>
131-
</ng-template>
132-
```
153+
The `cdkMenuItem` directive allows users to navigate menu items via keyboard.
154+
You can add a custom action to a menu item with the `cdkMenuItemTriggered` output.
133155

134-
You can create nested menus by using a menuitem as the trigger for another menu.
156+
<!-- example({"example":"cdk-menu-standalone-stateful-menu",
157+
"file":"cdk-menu-standalone-stateful-menu-example.html",
158+
"region":"reset-item"}) -->
135159

136-
```html
137-
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
138-
<div cdkMenu [cdkMenuPanel]="panel">
139-
<button cdkMenuItem [cdkMenuTriggerFor]="submenu">Open Submenu</button>
140-
</div>
141-
</ng-template>
142-
```
160+
You can create nested menus by using a menu item as the trigger for another menu.
143161

144-
A menuitem also has two sub-types, neither of which should trigger a menu: CdkMenuItemCheckbox and
145-
CdkMenuItemRadio
162+
<!-- example({"example":"cdk-menu-menubar",
163+
"file":"cdk-menu-menubar-example.html",
164+
"region":"file-trigger"}) -->
146165

147166
#### Menu Item Checkboxes
148167

149-
A `cdkMenuItemCheckbox` is a special type of menuitem that behaves as a checkbox. You can use this
150-
type of menuitem to toggle items on and off. An element with the `cdkMenuItemCheckbox` directive
168+
A `cdkMenuItemCheckbox` is a special type of menu item that behaves as a checkbox. You can use this
169+
type of menu item to toggle items on and off. An element with the `cdkMenuItemCheckbox` directive
151170
does not need the additional `cdkMenuItem` directive.
152171

172+
Checkbox items do not track their own state. You must bind the checked state using the
173+
`cdkMenuItemChecked` input and listen to `cdkMenuItemTriggered` to know when it is toggled. If you
174+
don't bind the state it will reset when the menu is closed and re-opened.
175+
176+
<!-- example({"example":"cdk-menu-standalone-stateful-menu",
177+
"file":"cdk-menu-standalone-stateful-menu-example.html",
178+
"region":"bold-item"}) -->
179+
153180
#### Menu Item Radios
154181

155-
A `cdkMenuItemRadio` is a special type of menuitem that behaves as a radio button. You can use this
156-
type of menuitem for menus with exclusively selectable items. An element with the `cdkMenuItemRadio`
182+
A `cdkMenuItemRadio` is a special type of menu item that behaves as a radio button. You can use this
183+
type of menu item for menus with exclusively selectable items. An element with the `cdkMenuItemRadio`
157184
directive does not need the additional `cdkMenuItem` directive.
158185

186+
As with checkbox items, radio items do not track their own state, but you can track it by binding
187+
`cdkMenuItemChecked` and listening for `cdkMenuItemTriggered`. If you do not bind the state the
188+
selection will reset when the menu is closed and reopened.
189+
190+
<!-- example({"example":"cdk-menu-standalone-stateful-menu",
191+
"file":"cdk-menu-standalone-stateful-menu-example.html",
192+
"region":"size-items"}) -->
193+
159194
#### Groups
160195

161196
By default `cdkMenu` acts as a group for `cdkMenuItemRadio` elements. Elements with
@@ -165,40 +200,18 @@ can have the checked state.
165200
If you would like to have unrelated groups of radio buttons within a single menu you should use the
166201
`cdkMenuGroup` directive.
167202

168-
```html
169-
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
170-
<div cdkMenu [cdkMenuPanel]="panel">
171-
<!-- Font size -->
172-
<div cdkMenuGroup>
173-
<button cdkMenuItemRadio>Small</button>
174-
<button cdkMenuItemRadio>Medium</button>
175-
<button cdkMenuItemRadio>Large</button>
176-
</div>
177-
<hr />
178-
<!-- Paragraph alignment -->
179-
<div cdkMenuGroup>
180-
<button cdkMenuItemRadio>Left</button>
181-
<button cdkMenuItemRadio>Center</button>
182-
<button cdkMenuItemRadio>Right</button>
183-
</div>
184-
</div>
185-
</ng-template>
186-
```
187-
188-
Note however that when the menu is closed and reopened any state is lost. You must subscribe to the
189-
groups `change` output, or to `cdkMenuItemToggled` on each radio item and track changes your self.
190-
Finally, you can provide state for each item using the `checked` attribute.
191-
192203
<!-- example({
193204
"example": "cdk-menu-standalone-stateful-menu",
194205
"file": "cdk-menu-standalone-stateful-menu-example.html"
195206
}) -->
196207

197208
### Smart Menu Aim
198209

199-
`@angular/cdk/menu` intelligently predicts when a user intends to navigate to an open submenu and
200-
prevent premature closeouts. This functionality prevents users from having to hunt through the open
201-
menus in a maze-like fashion to reach their destination.
210+
`@angular/cdk/menu` is capable of intelligently predicting when a user intends to navigate to an
211+
open submenu and preventing premature closeouts. This functionality prevents users from having to
212+
hunt through the open menus in a maze-like fashion to reach their destination. To enable this
213+
feature for a menu and its sub-menus, add the `cdkMenuTargetAim` directive to the `cdkMenu` or
214+
`cdkMenuBar` element.
202215

203216
![menu aim diagram][diagram]
204217

@@ -208,8 +221,8 @@ service if it can perform its close actions. In order to determine if the curren
208221
closed out, the Menu Aim service calculates the slope between a selected target coordinate in the
209222
submenu and the previous mouse point, and the slope between the target and the current mouse point.
210223
If the slope of the current mouse point is greater than the slope of the previous that means the
211-
user is moving towards the submenu and we shouldn't close out. Users however may sometimes stop
212-
short in a sibling item after moving towards the submenu. The service is intelligent enough the
224+
user is moving towards the submenu, so we shouldn't close out. Users however may sometimes stop
225+
short in a sibling item after moving towards the submenu. The service is intelligent enough to
213226
detect this intention and will trigger the next menu.
214227

215228
### Accessibility
@@ -227,4 +240,4 @@ Finally, keyboard interaction is supported as defined in the [ARIA menubar keybo
227240
[keyboard]:
228241
https://www.w3.org/TR/wai-aria-practices-1.1/#keyboard-interaction-12
229242
'ARIA Menubar Keyboard Interaction'
230-
[diagram]: menuaim.png 'Menu Aim Diagram'
243+
[diagram]: https://material.angular.io/assets/img/menuaim.png 'Menu Aim Diagram'

src/cdk/menu/menuaim.png

-62.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)