The MDC Menu component is a spec-aligned menu component adhering to the
Material Design menu specification.
It implements simple menus. Menus require JavaScript to work correctly, but the open and closed states are correct on
first render.
Installation
npm install --save @material/menu
A simple menu is usually closed, appearing when opened. It is appropriate for any display size.
<div class="mdc-simple-menu" tabindex="-1">
<ul class="mdc-simple-menu__items mdc-list" role="menu" aria-hidden="true">
<li class="mdc-list-item" role="menuitem" tabindex="0">
A Menu Item
</li>
<li class="mdc-list-item" role="menuitem" tabindex="0">
Another Menu Item
</li>
</ul>
</div>
Note: adding a tabindex
of 0
to the menu items places them in the tab order.
Adding a tabindex
of -1
to the root element makes it programmatically focusable, without
placing it in the tab order. This allows the menu to be focused on open, so that the next Tab
keypress moves to the first menu item. If you would like the first menu item to be automatically
focused instead, remove tabindex="-1"
from the root element.
let menu = new mdc.menu.MDCSimpleMenu(document.querySelector('.mdc-simple-menu'));
document.querySelector('.some-button').addEventListener('click', () => menu.open = !menu.open);
You can start the menu in its open state by adding the mdc-simple-menu--open
class to your HTML:
<div class="mdc-simple-menu mdc-simple-menu--open">
...
</div>
The menu can either be positioned manually, or automatically, by anchoring it to an element.
Automatic Positioning
The menu understands the concept of an anchor, which it can use to determine how to position itself, and which corner
to open from.
The anchor can either be a visible element that the menu is a child of:
<div class="toolbar mdc-menu-anchor">
<div class="mdc-simple-menu">
...
</div>
</div>
or a wrapper element that contains the actual visible element to attach to:
<div class="mdc-menu-anchor">
<button>Open Menu</button>
<div class="mdc-simple-menu">
...
</div>
</div>
Note: overflow: visible
and position: relative
will be set on the element with mdc-menu-anchor
to ensure that
the menu is positioned and displayed correctly.
The menu will check if its parent element has the mdc-menu-anchor
class set, and if so, it will automatically position
itself relative to this anchor element. It will open from the top left (top right in RTL) corner of the anchor by
default, but will choose an appropriate different corner if close to the edge of the screen.
Manual Positioning
The menu is position: absolute
by default, and must be positioned by the user when doing manual positioning.
<div class="container">
<div class="mdc-simple-menu" style="top:0; left: 0;">
...
</div>
</div>
The menu will open from the top left by default (top right in RTL). Depending on how you've positioned your button, you
may want to change the point it opens from.
To override the opening point, you can style transform-origin
directly, or use one of the following convenience
classes:
class name | description |
---|
mdc-simple-menu--open-from-top-left | Open the menu from the top left. |
mdc-simple-menu--open-from-top-right | Open the menu from the top right. |
mdc-simple-menu--open-from-bottom-left | Open the menu from the bottom left. |
mdc-simple-menu--open-from-bottom-right | Open the menu from the bottom right. |
Using the JS Component
MDC Simple Menu ships with a Component / Foundation combo which allows for frameworks to richly integrate the
correct menu behaviors into idiomatic components.
The component has a read-write property, open
, which keeps track of the visual state of the component.
console.log('The menu is ' + (menu.open ? 'open' : 'closed'));
menu.open = true;
menu.open = false;
It also has two lower level methods, which control the menu directly, by showing (opening) and
hiding (closing) it:
menu.show();
menu.hide();
menu.show({focusIndex: 1});
You can still use the open
getter property even if showing and hiding directly:
menu.show();
console.log(`Menu is ${menu.open ? 'open' : 'closed'}.`);
Including in code
ES2015
import {MDCSimpleMenu, MDCSimpleMenuFoundation} from 'mdc-menu';
CommonJS
const mdcMenu = require('mdc-menu');
const MDCSimpleMenu = mdcMenu.MDCSimpleMenu;
const MDCSimpleMenuFoundation = mdcMenu.MDCSimpleMenuFoundation;
AMD
require(['path/to/mdc-menu'], mdcMenu => {
const MDCSimpleMenu = mdcMenu.MDCSimpleMenu;
const MDCSimpleMenuFoundation = mdcMenu.MDCSimpleMenuFoundation;
});
Global
const MDCSimpleMenu = mdc.Menu.MDCSimpleMenu;
const MDCSimpleMenuFoundation = mdc.Menu.MDCSimpleMenuFoundation;
Automatic Instantiation
If you do not care about retaining the component instance for the simple menu, simply call attachTo()
and pass it a
DOM element.
mdc.MDCSimpleMenu.attachTo(document.querySelector('.mdc-simple-menu'));
Manual Instantiation
Simple menus can easily be initialized using their default constructors as well, similar to attachTo
.
import MDCSimpleMenu from 'mdc-menu';
const menu = new MDCSimpleMenu(document.querySelector('.mdc-simple-menu'));
Handling selection events
When a menu item is selected, the menu component will emit a MDCSimpleMenu:selected
custom event
with the following detail
data:
property name | type | description |
---|
item | HTMLElement | The DOM element for the selected item |
index | number | The index of the selected item |
If the menu is closed with no selection made (for example, if the user hits Escape
while it's open), a MDCSimpleMenu:cancel
custom event is emitted instead, with no data attached.
Using the Foundation Class
MDC Simple Menu ships with an MDCSimpleMenuFoundation
class that external frameworks and libraries can use to
integrate the component. As with all foundation classes, an adapter object must be provided.
The adapter for temporary drawers must provide the following functions, with correct signatures:
Method Signature | Description |
---|
addClass(className: string) => void | Adds a class to the root element. |
removeClass(className: string) => void | Removes a class from the root element. |
hasClass(className: string) => boolean | Returns boolean indicating whether element has a given class. |
hasNecessaryDom() => boolean | Returns boolean indicating whether the necessary DOM is present (namely, the mdc-simple-menu__items container). |
getInnerDimensions() => {width: number, height: number} | Returns an object with the items container width and height |
hasAnchor: () => boolean | Returns whether the menu has an anchor for positioning. |
getAnchorDimensions() => { width: number, height: number, top: number, right: number, bottom: number, left: number } | Returns an object with the dimensions and position of the anchor (same semantics as DOMRect ). |
getWindowDimensions() => {width: number, height: number} | Returns an object with width and height of the page, in pixels. |
setScale(x: string, y: string) => void | Sets the transform on the root element to the provided (x, y) scale. |
setInnerScale(x: string, y: string) => void | Sets the transform on the items container to the provided (x, y) scale. |
getNumberOfItems() => numbers | Returns the number of item elements inside the items container. In our vanilla component, we determine this by counting the number of list items whose role attribute corresponds to the correct child role of the role present on the menu list element. For example, if the list element has a role of menu this queries for all elements that have a role of menuitem . |
registerInteractionHandler(type: string, handler: EventListener) => void | Adds an event listener handler for event type type . |
deregisterInteractionHandler(type: string, handler: EventListener) => void | Removes an event listener handler for event type type . |
registerDocumentClickHandler(handler: EventListener) => void | Adds an event listener handler for event type 'click'. |
deregisterDocumentClickHandler(handler: EventListener) => void | Removes an event listener handler for event type 'click'. |
getYParamsForItemAtIndex(index: number) => {top: number, height: number} | Returns an object with the offset top and offset height values for the item element inside the items container at the provided index. Note that this is an index into the list of item elements, and not necessarily every child element of the list. |
setTransitionDelayForItemAtIndex(index: number, value: string) => void | Sets the transition delay on the element inside the items container at the provided index to the provided value. The same notice for index applies here as above. |
getIndexForEventTarget(target: EventTarget) => number | Checks to see if the target of an event pertains to one of the menu items, and if so returns the index of that item. Returns -1 if the target is not one of the menu items. The same notice for index applies here as above. |
notifySelected(evtData: {index: number}) => void | Dispatches an event notifying listeners that a menu item has been selected. The function should accept an evtData parameter containing the an object with an index property representing the index of the selected item. Implementations may choose to supplement this data with additional data, such as the item itself. |
notifyCancel() => void | Dispatches an event notifying listeners that the menu has been closed with no selection made. |
saveFocus() => void | Stores the currently focused element on the document, for restoring with restoreFocus . |
restoreFocus() => void | Restores the previously saved focus state, by making the previously focused element the active focus again. |
isFocused() => boolean | Returns a boolean value indicating whether the root element of the simple menu is focused. |
focus() => void | Focuses the root element of the simple menu. |
getFocusedItemIndex() => number | Returns the index of the currently focused menu item (-1 if none). |
focusItemAtIndex(index: number) => void | Focuses the menu item with the provided index. |
isRtl() => boolean | Returns boolean indicating whether the current environment is RTL. |
setTransformOrigin(value: string) => void | Sets the transform origin for the menu element. |
setPosition(position: { top: string, right: string, bottom: string, left: string }) => void | Sets the position of the menu element. |