|
| 1 | +The `@angular/cdk-experimental/menu` module provides directives to help create custom menu |
| 2 | +interactions based on the [WAI ARIA specification][aria]. |
| 3 | + |
| 4 | +By using `@angular/cdk-experimental/menu` you get all of the expected behaviors for an accessible |
| 5 | +experience, including bidi layout support, keyboard interaction, and focus management. All |
| 6 | +directives apply their associated ARIA roles to their host element. |
| 7 | + |
| 8 | +### Supported ARIA Roles |
| 9 | + |
| 10 | +The directives in `@angular/cdk-experimental/menu` set the appropriate roles on their host element. |
| 11 | + |
| 12 | +| Directive | ARIA Role | |
| 13 | +| ------------------- | ---------------- | |
| 14 | +| CdkMenuBar | menubar | |
| 15 | +| CdkMenu | menu | |
| 16 | +| CdkMenuGroup | group | |
| 17 | +| CdkMenuItem | menuitem | |
| 18 | +| CdkMenuItemRadio | menuitemradio | |
| 19 | +| CdkMenuItemCheckbox | menuitemcheckbox | |
| 20 | + |
| 21 | +### Getting started |
| 22 | + |
| 23 | +Import the `CdkMenuModule` into the `NgModule` in which you want to create menus. You can then apply |
| 24 | +menu directives to build your custom menu. A typical menu consists of the following directives: |
| 25 | + |
| 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 |
| 31 | + |
| 32 | +<!-- TODO basic standalone menu example (like mat-menu has) --> |
| 33 | + |
| 34 | +Most menu interactions consist of two parts: a trigger and a menu panel. |
| 35 | + |
| 36 | +#### Triggers |
| 37 | + |
| 38 | +You must add the `cdkMenuItem` and `cdkMenuTriggerFor` directives to triggers like so, |
| 39 | + |
| 40 | +```html |
| 41 | +<button cdkMenuItem [cdkMenuTriggerFor]="menu">Click me!</button> |
| 42 | +``` |
| 43 | + |
| 44 | +Adding `cdkMenuItem` gives you keyboard navigation and focus management. Associating a trigger with |
| 45 | +a menu is done through the `cdkMenuTriggerFor` directive and you must provide a template reference |
| 46 | +variable to it. Once both of these directives are set, you can toggle the associated menu |
| 47 | +programmatically, using a mouse or using a keyboard. |
| 48 | + |
| 49 | +#### Menu panels |
| 50 | + |
| 51 | +You must wrap pop-up menus with an `ng-template` with the `cdkMenuPanel` directive and a reference |
| 52 | +variable which must be of type `cdkMenuPanel`. Further, the `cdkMenu` must also reference the |
| 53 | +`cdkMenuPanel`. |
| 54 | + |
| 55 | +```html |
| 56 | +<ng-template cdkMenuPanel #panel="cdkMenuPanel"> |
| 57 | + <div cdkMenu [cdkMenuPanel]="panel"> |
| 58 | + <!-- some content --> |
| 59 | + </div> |
| 60 | +</ng-template> |
| 61 | +``` |
| 62 | + |
| 63 | +Note that Angular CDK provides no styles; you must add styles as part of building your custom menu. |
| 64 | + |
| 65 | +### Menu Bars |
| 66 | + |
| 67 | +The `CdkMenuBar` directive follows the [ARIA menubar][menubar] spec and behaves similar to a desktop |
| 68 | +app menubar. It consists of at least one `CdkMenuItem` which triggers a submenu. A menubar can be |
| 69 | +layed out horizontally or vertically (defaulting to horizontal). If the layout changes, you must set |
| 70 | +the `orientation` attribute to match in order for the keyboard navigation to work properly and for |
| 71 | +menus to open up in the correct location. |
| 72 | + |
| 73 | +<!-- TODO basic menubar example (google docs?) --> |
| 74 | + |
| 75 | +### Context Menus |
| 76 | + |
| 77 | +A context menu opens when a user right-clicks within some container element. You can mark a |
| 78 | +container element with the `cdkContextMenuTriggerFor`, which behaves like `cdkMenuTriggerFor` except |
| 79 | +that it responds to the browser's native `contextmenu` event. Custom context menus appear next to |
| 80 | +the cursor, similarly to native context menus. |
| 81 | + |
| 82 | +<!-- TODO basic context menu example --> |
| 83 | + |
| 84 | +You can nest context menu container elements. Upon right-click, the menu associated with the closest |
| 85 | +container element will open. |
| 86 | + |
| 87 | +```html |
| 88 | +<div [cdkContextMenuTriggerFor]="outer"> |
| 89 | + My outer context |
| 90 | + <div [cdkContextMenuTriggerFor]="inner">My inner context</div> |
| 91 | +</div> |
| 92 | +``` |
| 93 | + |
| 94 | +In the example above, right clicking on "My inner context" will open up the "inner" menu and right |
| 95 | +clicking inside "My outer context" will open up the "outer" menu. |
| 96 | + |
| 97 | +### Inline Menus |
| 98 | + |
| 99 | +An _inline menu_ is a menu that lives directly on the page rather than a pop-up associated with a |
| 100 | +trigger. You can use an inline menu when you want a persistent menu interaction on a page. Menu |
| 101 | +items within an inline menus are logically grouped together and you can navigate through them using |
| 102 | +your keyboard. |
| 103 | + |
| 104 | +<!-- TODO inline menu example (gmail buttons?) --> |
| 105 | + |
| 106 | +### Menu Items |
| 107 | + |
| 108 | +Both menu and menubar elements should exclusively contain menuitem elements. This directive allows |
| 109 | +the items to be navigated to via keyboard interaction. |
| 110 | + |
| 111 | +A menuitem by itself can provide some user defined action by hooking into the `cdkMenuItemTriggered` |
| 112 | +output. An example may be a close button which performs some closing logic. |
| 113 | + |
| 114 | +```html |
| 115 | +<ng-template cdkMenuPanel #panel="cdkMenuPanel"> |
| 116 | + <div cdkMenu [cdkMenuPanel]="panel"> |
| 117 | + <button cdkMenuItem (cdkMenuItemTriggered)="closeApp()">Close</button> |
| 118 | + </div> |
| 119 | +</ng-template> |
| 120 | +``` |
| 121 | + |
| 122 | +You can create nested menus by using a menuitem as the trigger for another menu. |
| 123 | + |
| 124 | +```html |
| 125 | +<ng-template cdkMenuPanel #panel="cdkMenuPanel"> |
| 126 | + <div cdkMenu [cdkMenuPanel]="panel"> |
| 127 | + <button cdkMenuItem [cdkMenuTriggerFor]="submenu">Open Submenu</button> |
| 128 | + </div> |
| 129 | +</ng-template> |
| 130 | +``` |
| 131 | + |
| 132 | +A menuitem also has two sub-types, neither of which should trigger a menu: CdkMenuItemCheckbox and |
| 133 | +CdkMenuItemRadio |
| 134 | + |
| 135 | +#### Menu Item Checkboxes |
| 136 | + |
| 137 | +A `cdkMenuItemCheckbox` is a special type of menuitem that behaves as a checkbox. You can use this |
| 138 | +type of menuitem to toggle items on and off. An element with the `cdkMenuItemCheckbox` directive |
| 139 | +does not need the additional `cdkMenuItem` directive. |
| 140 | + |
| 141 | +#### Menu Item Radios |
| 142 | + |
| 143 | +A `cdkMenuItemRadio` is a special type of menuitem that behaves as a radio button. You can use this |
| 144 | +type of menuitem for menus with exclusively selectable items. An element with the `cdkMenuItemRadio` |
| 145 | +directive does not need the additional `cdkMenuItem` directive. |
| 146 | + |
| 147 | +#### Groups |
| 148 | + |
| 149 | +By default `cdkMenu` acts as a group for `cdkMenuItemRadio` elements. Elements with |
| 150 | +`cdkMenuItemRadio` added as children of a `cdkMenu` will be logically grouped and only a single item |
| 151 | +can have the checked state. |
| 152 | + |
| 153 | +If you would like to have unrelated groups of radio buttons within a single menu you should use the |
| 154 | +`cdkMenuGroup` directive. |
| 155 | + |
| 156 | +```html |
| 157 | +<ng-template cdkMenuPanel #panel="cdkMenuPanel"> |
| 158 | + <div cdkMenu [cdkMenuPanel]="panel"> |
| 159 | + <!-- Font size --> |
| 160 | + <div cdkMenuGroup> |
| 161 | + <button cdkMenuItemRadio>Small</button> |
| 162 | + <button cdkMenuItemRadio>Medium</button> |
| 163 | + <button cdkMenuItemRadio>Large</button> |
| 164 | + </div> |
| 165 | + <hr /> |
| 166 | + <!-- Paragraph alignment --> |
| 167 | + <div cdkMenuGroup> |
| 168 | + <button cdkMenuItemRadio>Left</button> |
| 169 | + <button cdkMenuItemRadio>Center</button> |
| 170 | + <button cdkMenuItemRadio>Right</button> |
| 171 | + </div> |
| 172 | + </div> |
| 173 | +</ng-template> |
| 174 | +``` |
| 175 | + |
| 176 | +Note however that when the menu is closed and reopened any state is lost. You must subscribe to the |
| 177 | +groups `change` output, or to `cdkMenuItemToggled` on each radio item and track changes your self. |
| 178 | +Finally, you can provide state for each item using the `checked` attribute. |
| 179 | + |
| 180 | +<!-- TODO standalone menu example with checkboxes and grouped radios --> |
| 181 | + |
| 182 | +### Smart Menu Aim |
| 183 | + |
| 184 | +`@angular/cdk-experimental/menu` intelligently predicts when a user intends to navigate to an open |
| 185 | +submenu and prevent premature closeouts. This functionality prevents users from having to hunt |
| 186 | +through the open menus in a maze-like fashion to reach their destination. |
| 187 | + |
| 188 | +![menu aim diagram][diagram] |
| 189 | + |
| 190 | +As demonstrated in the diagram above we first track the user's mouse movements within a menu. Next, |
| 191 | +when a user mouses into a sibling menu item (e.g. Share button) the sibling item asks the Menu Aim |
| 192 | +service if it can perform its close actions. In order to determine if the current submenu can be |
| 193 | +closed out, the Menu Aim service calculates the slope between a selected target coordinate in the |
| 194 | +submenu and the previous mouse point, and the slope between the target and the current mouse point. |
| 195 | +If the slope of the current mouse point is greater than the slope of the previous that means the |
| 196 | +user is moving towards the submenu and we shouldn't close out. Users however may sometimes stop |
| 197 | +short in a sibling item after moving towards the submenu. The service is intelligent enough the |
| 198 | +detect this intention and will trigger the next menu. |
| 199 | + |
| 200 | +### Accessibility |
| 201 | + |
| 202 | +The set of directives defined in `@angular/cdk-experimental/menu` follow accessibility best |
| 203 | +practices as defined in the [ARIA spec][menubar]. Specifically, the menus are aware of left-to-right |
| 204 | +and right-to-left layouts and opened appropriately. You should however add any necessary CSS styles. |
| 205 | +Menu items should always have meaningful labels, whether through text content, `aria-label`, or |
| 206 | +`aria-labelledby`. Finally, keyboard interaction is supported as defined in the [ARIA menubar |
| 207 | +keyboard interaction spec][keyboard]. |
| 208 | + |
| 209 | +<!-- links --> |
| 210 | + |
| 211 | +[aria]: https://www.w3.org/TR/wai-aria-1.1/ 'ARIA Spec' |
| 212 | +[menubar]: https://www.w3.org/TR/wai-aria-practices-1.1/#menu 'ARIA Menubar Pattern' |
| 213 | +[keyboard]: |
| 214 | + https://www.w3.org/TR/wai-aria-practices-1.1/#keyboard-interaction-12 |
| 215 | + 'ARIA Menubar Keyboard Interaction' |
| 216 | +[diagram]: menuaim.png 'Menu Aim Diagram' |
0 commit comments