Skip to content

Commit ed25bfb

Browse files
committed
fix(cdk/menu): update docs to reflect current implementation and add
correct role for triggers
1 parent 567be4f commit ed25bfb

13 files changed

+187
-127
lines changed

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

+65-91
Original file line numberDiff line numberDiff line change
@@ -10,68 +10,65 @@ 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 |
2021

2122
### Getting started
2223

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

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
27+
- `cdkMenuTriggerFor` - links a trigger element to an `ng-template` containing the menu the trigger opens
28+
- `cdkMenu` - creates the actual menu content that the trigger will open
29+
- `cdkMenuItem` - added to each item in the menu
3130

3231
<!-- example({
33-
"example": "cdk-menu-standalone-menu",
34-
"file": "cdk-menu-standalone-menu-example.html"
32+
"example": "cdk-menu-standalone-trigger",
33+
"file": "cdk-menu-standalone-trigger-example.html"
3534
}) -->
3635

3736
Most menu interactions consist of two parts: a trigger and a menu panel.
3837

3938
#### Triggers
4039

41-
You must add the `cdkMenuItem` and `cdkMenuTriggerFor` directives to triggers like so,
40+
`cdkMenuTriggerFor` can be added to any button to make it a trigger for the given menu, or any menu
41+
item to make it a trigger for a submenu. When adding this directive, be sure to pass a reference to
42+
the template containing the menu it should open. You can toggle the associated menu using a mouse
43+
or keyboard.
4244

43-
```html
44-
<button cdkMenuItem [cdkMenuTriggerFor]="menu">Click me!</button>
45-
```
45+
<!-- example({"example":"cdk-menu-standalone-trigger",
46+
"file":"cdk-menu-standalone-trigger-example.html",
47+
"region":"trigger"}) -->
4648

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.
49+
When creating a submenu trigger, add both `cdkMenuItem` and `cdkMenuTriggerFor` like so,
5150

52-
#### Menu panels
51+
<!-- example({"example":"cdk-menu-menubar",
52+
"file":"cdk-menu-menubar-example.html",
53+
"region":"file-trigger"}) -->
5354

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`.
55+
#### Menu content
5756

58-
```html
59-
<ng-template cdkMenuPanel #panel="cdkMenuPanel">
60-
<div cdkMenu [cdkMenuPanel]="panel">
61-
<!-- some content -->
62-
</div>
63-
</ng-template>
64-
```
57+
Menu content is created using the `cdkMenu` or `cdkMenuBar` components. Menus can be always present
58+
on the page (referred to as an inline menu), or shown on trigger activation (referred to as a popup
59+
menu). Popup menus must be wrapped in an `ng-template`, as a reference to the ng-template is needed
60+
to pass to the trigger directive.
61+
62+
Menus should exclusively contain elements with role `menuitem`, `menuitemcheckbox`,
63+
`menuitemradio`, or `group`. Supporting directives that automatically apply these roles are
64+
discussed below.
6565

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

6868
### Menu Bars
6969

7070
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.
71+
app menubar. It consists of at least one `CdkMenuItem` which triggers a submenu.
7572

7673
<!-- example({
7774
"example": "cdk-menu-menubar",
@@ -93,22 +90,21 @@ the cursor, similarly to native context menus.
9390
You can nest context menu container elements. Upon right-click, the menu associated with the closest
9491
container element will open.
9592

96-
```html
97-
<div [cdkContextMenuTriggerFor]="outer">
98-
My outer context
99-
<div [cdkContextMenuTriggerFor]="inner">My inner context</div>
100-
</div>
101-
```
93+
<!-- example({
94+
"example": "cdk-menu-nested-context",
95+
"file": "cdk-menu-nested-context-example.html",
96+
"region": "triggers"
97+
}) -->
10298

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.
99+
In the example above, right-clicking on "Inner context menu" will open up the "inner" menu and
100+
right-clicking inside "Outer context menu" will open up the "outer" menu.
105101

106102
### Inline Menus
107103

108104
An _inline menu_ is a menu that lives directly on the page rather than a pop-up associated with a
109105
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.
106+
items within an inline menus are logically grouped together, and you can navigate through them
107+
using your keyboard.
112108

113109
<!-- example({
114110
"example": "cdk-menu-inline",
@@ -117,45 +113,47 @@ your keyboard.
117113

118114
### Menu Items
119115

120-
Both menu and menubar elements should exclusively contain menuitem elements. This directive allows
121-
the items to be navigated to via keyboard interaction.
116+
The `cdkMenuItem` directive allows the items to be navigated to via keyboard interaction.
117+
A menuitem can provide some user defined action by hooking into the `cdkMenuItemTriggered` output.
122118

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.
125-
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-
```
119+
<!-- example({"example":"cdk-menu-standalone-stateful-menu",
120+
"file":"cdk-menu-standalone-stateful-menu-example.html",
121+
"region":"reset-item"}) -->
133122

134123
You can create nested menus by using a menuitem as the trigger for another menu.
135124

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-
```
143-
144-
A menuitem also has two sub-types, neither of which should trigger a menu: CdkMenuItemCheckbox and
145-
CdkMenuItemRadio
125+
<!-- example({"example":"cdk-menu-menubar",
126+
"file":"cdk-menu-menubar-example.html",
127+
"region":"file-trigger"}) -->
146128

147129
#### Menu Item Checkboxes
148130

149131
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
132+
type of menuitem to toggle items on and off. An element with `cdkMenuItemCheckbox` directive
151133
does not need the additional `cdkMenuItem` directive.
152134

135+
Checkbox items do not track their own state. You must bind the checked state using the
136+
`cdkMenuItemChecked` input and listen to `cdkMenuItemTriggered` to know when it is toggled. If you
137+
don't bind the state it will reset when the menu is closed and re-opened.
138+
139+
<!-- example({"example":"cdk-menu-standalone-stateful-menu",
140+
"file":"cdk-menu-standalone-stateful-menu-example.html",
141+
"region":"bold-item"}) -->
142+
153143
#### Menu Item Radios
154144

155145
A `cdkMenuItemRadio` is a special type of menuitem that behaves as a radio button. You can use this
156146
type of menuitem for menus with exclusively selectable items. An element with the `cdkMenuItemRadio`
157147
directive does not need the additional `cdkMenuItem` directive.
158148

149+
As with checkbox items, radio items do not track their own state, but you can track it by binding
150+
`cdkMenuItemChecked` and listening for `cdkMenuItemTriggered`. If you do not bind the state the
151+
selection will reset when the menu is closed and reopened.
152+
153+
<!-- example({"example":"cdk-menu-standalone-stateful-menu",
154+
"file":"cdk-menu-standalone-stateful-menu-example.html",
155+
"region":"size-items"}) -->
156+
159157
#### Groups
160158

161159
By default `cdkMenu` acts as a group for `cdkMenuItemRadio` elements. Elements with
@@ -165,30 +163,6 @@ can have the checked state.
165163
If you would like to have unrelated groups of radio buttons within a single menu you should use the
166164
`cdkMenuGroup` directive.
167165

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-
192166
<!-- example({
193167
"example": "cdk-menu-standalone-stateful-menu",
194168
"file": "cdk-menu-standalone-stateful-menu-example.html"
@@ -208,8 +182,8 @@ service if it can perform its close actions. In order to determine if the curren
208182
closed out, the Menu Aim service calculates the slope between a selected target coordinate in the
209183
submenu and the previous mouse point, and the slope between the target and the current mouse point.
210184
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
185+
user is moving towards the submenu, so we shouldn't close out. Users however may sometimes stop
186+
short in a sibling item after moving towards the submenu. The service is intelligent enough to
213187
detect this intention and will trigger the next menu.
214188

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

src/cdk/menu/menuaim.png

-62.6 KB
Binary file not shown.

src/components-examples/cdk/menu/cdk-menu-menubar/cdk-menu-menubar-example.html

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<div cdkMenuBar>
2+
<!-- #docregion file-trigger -->
23
<button class="example-menu-bar-item" cdkMenuItem [cdkMenuTriggerFor]="file">File</button>
4+
<!-- #enddocregion file-trigger -->
35
<button class="example-menu-bar-item" cdkMenuItem [cdkMenuTriggerFor]="edit">Edit</button>
46
<button class="example-menu-bar-item" cdkMenuItem [cdkMenuTriggerFor]="format">Format</button>
57
</div>
@@ -8,7 +10,7 @@
810
<div class="example-menu" cdkMenu>
911
<button class="example-menu-item" cdkMenuItem>Share</button>
1012
<hr />
11-
<button class="example-menu-item" cdkMenuItem [cdkMenuTriggerFor]="new">
13+
<button class="example-menu-item" cdkMenuItem [cdkMenuTriggerFor]="new_doc">
1214
New <span>&#10148;</span>
1315
</button>
1416
<button class="example-menu-item" cdkMenuItem>Open</button>
@@ -34,19 +36,19 @@
3436
<ng-template #format >
3537
<div class="example-menu" cdkMenu>
3638
<div class="example-menu-group" cdkMenuGroup>
37-
<button class="example-menu-item" checked id="bf" cdkMenuItemCheckbox>Bold</button>
38-
<button class="example-menu-item" id="if" cdkMenuItemCheckbox>Italic</button>
39+
<button cdkMenuItemCheckbox class="example-menu-item" cdkMenuItemChecked>Bold</button>
40+
<button cdkMenuItemCheckbox class="example-menu-item">Italic</button>
3941
</div>
4042
<hr />
4143
<div class="example-menu-group" cdkMenuGroup>
42-
<button class="example-menu-item" id="small" cdkMenuItemRadio>Small</button>
43-
<button class="example-menu-item" checked id="normal" cdkMenuItemRadio>Normal</button>
44-
<button class="example-menu-item" id="large" cdkMenuItemRadio>Big</button>
44+
<button cdkMenuItemRadio class="example-menu-item">Small</button>
45+
<button cdkMenuItemRadio class="example-menu-item" cdkMenuItemChecked>Normal</button>
46+
<button cdkMenuItemRadio class="example-menu-item">Big</button>
4547
</div>
4648
</div>
4749
</ng-template>
4850

49-
<ng-template #new>
51+
<ng-template #new_doc>
5052
<div class="example-menu" cdkMenu>
5153
<button class="example-menu-item" cdkMenuItem>Document</button>
5254
<button class="example-menu-item" cdkMenuItem>From template</button>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.example-context-area {
2+
display: inline-grid;
3+
border: 2px dashed black;
4+
}
5+
6+
.example-context-area .example-context-area {
7+
margin: 100px;
8+
width: 200px;
9+
height: 100px;
10+
}
11+
12+
.example-menu {
13+
display: inline-flex;
14+
flex-direction: column;
15+
min-width: 180px;
16+
max-width: 280px;
17+
background-color: rgb(255, 255, 255);
18+
padding: 6px 0;
19+
}
20+
21+
.example-menu-item {
22+
background-color: transparent;
23+
cursor: pointer;
24+
border: none;
25+
26+
user-select: none;
27+
min-width: 64px;
28+
line-height: 36px;
29+
padding: 0 16px;
30+
31+
display: flex;
32+
align-items: center;
33+
flex-direction: row;
34+
flex: 1;
35+
}
36+
37+
.example-menu-item:hover {
38+
background-color: rgb(208, 208, 208);
39+
}
40+
41+
.example-menu-item:active {
42+
background-color: rgb(170, 170, 170);
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!-- #docregion triggers -->
2+
<div class="example-context-area" [cdkContextMenuTriggerFor]="outer">
3+
Outer context menu
4+
<div class="example-context-area" [cdkContextMenuTriggerFor]="inner">Inner context menu</div>
5+
</div>
6+
<!-- #enddocregion triggers -->
7+
8+
<ng-template #outer>
9+
<div class="example-menu" cdkMenu>
10+
<button class="example-menu-item" cdkMenuItem>Save</button>
11+
<button class="example-menu-item" cdkMenuItem>Exit</button>
12+
</div>
13+
</ng-template>
14+
15+
<ng-template #inner>
16+
<div class="example-menu" cdkMenu>
17+
<button class="example-menu-item" cdkMenuItem>Cut</button>
18+
<button class="example-menu-item" cdkMenuItem>Copy</button>
19+
<button class="example-menu-item" cdkMenuItem>Paste</button>
20+
</div>
21+
</ng-template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {Component} from '@angular/core';
2+
3+
/** @title Nested context menus. */
4+
@Component({
5+
selector: 'cdk-menu-nested-context-example',
6+
exportAs: 'cdkMenuNestedContextExample',
7+
styleUrls: ['cdk-menu-nested-context-example.css'],
8+
templateUrl: 'cdk-menu-nested-context-example.html',
9+
})
10+
export class CdkMenuNestedContextExample {}

src/components-examples/cdk/menu/cdk-menu-standalone-menu/cdk-menu-standalone-menu-example.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
}
99

1010
.example-menu-item,
11-
.example-standalone-item {
11+
.example-standalone-trigger {
1212
background-color: transparent;
1313
cursor: pointer;
1414
border: none;

0 commit comments

Comments
 (0)