Skip to content

Commit

Permalink
feat(menu): add document-level positioning
Browse files Browse the repository at this point in the history
related #5120

PiperOrigin-RevId: 580293404
  • Loading branch information
e111077 authored and Copybara-Service committed Nov 7, 2023
1 parent f08ecce commit 2b591ca
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 53 deletions.
30 changes: 30 additions & 0 deletions docs/components/figures/menu/usage-document.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<div class="figure-wrapper">
<figure
style="justify-content: center"
aria-label="A filled button that says open document menu. Interact with the button to open a document menu."
>
<div>
<div style="margin: 16px">
<md-filled-button id="usage-document-anchor">Open document menu</md-filled-button>
</div>
<md-menu positioning="document" id="usage-document" anchor="usage-document-anchor">
<md-menu-item>
<div slot="headline">Apple</div>
</md-menu-item>
<md-menu-item>
<div slot="headline">Banana</div>
</md-menu-item>
<md-menu-item>
<div slot="headline">Cucumber</div>
</md-menu-item>
</md-menu>
</div>
<script type="module">
const anchorEl = document.body.querySelector("#usage-document-anchor");
const menuEl = document.body.querySelector("#usage-document");
anchorEl.addEventListener("click", () => {
menuEl.open = !menuEl.open;
});
</script>
</figure>
</div>
Binary file added docs/components/images/menu/usage-document.webp
Binary file not shown.
65 changes: 61 additions & 4 deletions docs/components/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ choices on a temporary surface.
When opened, menus position themselves to an anchor. Thus, either `anchor` or
`anchorElement` must be supplied to `md-menu` before opening. Additionally, a
shared parent of `position:relative` should be present around the menu and it's
anchor.
anchor, because the default menu is positioned relative to the anchor element.

Menus also render menu items such as `md-menu-item` and handle keyboard
navigation between `md-menu-item`s as well as typeahead functionality.
Expand Down Expand Up @@ -215,14 +215,14 @@ Granny Smith, and Red Delicious."](images/menu/usage-submenu.webp)
</script>
```

### Fixed menus
### Fixed-positioned menus

Internally menu uses `position: absolute` by default. Though there are cases
when the anchor and the node cannot share a common ancestor that is `position:
relative`, or sometimes, menu will render below another item due to limitations
with `position: absolute`. In most of these cases, you would want to use the
`positioning="fixed"` attribute to position the menu relative to the window
instead of relative to the parent.
instead of relative to the element.

> Note: Fixed menu positions are positioned relative to the window and not the
> document. This means that the menu will not scroll with the anchor as the page
Expand Down Expand Up @@ -264,6 +264,64 @@ Cucumber."](images/menu/usage-fixed.webp)
</script>
```

### Document-positioned menus

When set to `positioning="document"`, `md-menu` will position itself relative to
the document as opposed to the element or the window from `"absolute"` and
`"fixed"` values respectively.

Document level positioning is useful for the following cases:

- There are no ancestor elements that produce a `relative` positioning
context.
- `position: relative`
- `position: absolute`
- `position: fixed`
- `transform: translate(x, y)`
- etc.
- The menu is hoisted to the top of the DOM
- The last child of `<body>`
- [Top layer](https://developer.mozilla.org/en-US/docs/Glossary/Top_layer)
<!-- {.external} -->
- The same `md-menu` is being reused and the contents and anchors are being
dynamically changed

<!-- no-catalog-start -->

!["A filled button that says open document menu. There is an open menu anchored
to the bottom of the button with three items, Apple, Banana, and
Cucumber."](images/menu/usage-document.webp)

<!-- no-catalog-end -->
<!-- catalog-include "figures/menu/usage-document.html" -->

```html
<!-- Note the lack of position: relative parent. -->
<div style="margin: 16px;">
<md-filled-button id="usage-document-anchor">Open document menu</md-filled-button>
</div>

<!-- document menus do not require a common ancestor with the anchor. -->
<md-menu positioning="document" id="usage-document" anchor="usage-document-anchor">
<md-menu-item>
<div slot="headline">Apple</div>
</md-menu-item>
<md-menu-item>
<div slot="headline">Banana</div>
</md-menu-item>
<md-menu-item>
<div slot="headline">Cucumber</div>
</md-menu-item>
</md-menu>

<script type="module">
const anchorEl = document.body.querySelector('#usage-document-anchor');
const menuEl = document.body.querySelector('#usage-document');
anchorEl.addEventListener('click', () => { menuEl.open = !menuEl.open; });
</script>
```

## Accessibility

By default Menu is set up to function as a `role="menu"` with children as
Expand Down Expand Up @@ -395,7 +453,6 @@ a sharp 0px border radius.](images/menu/theming.webp)

## API


### MdMenu <code>&lt;md-menu&gt;</code>

#### Properties
Expand Down
3 changes: 2 additions & 1 deletion menu/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ const collection = new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>(
}),
new Knob('positioning', {
defaultValue: 'absolute' as const,
ui: selectDropdown<'absolute' | 'fixed'>({
ui: selectDropdown<'absolute' | 'fixed' | 'document'>({
options: [
{label: 'absolute', value: 'absolute'},
{label: 'fixed', value: 'fixed'},
{label: 'document', value: 'document'},
],
}),
}),
Expand Down
21 changes: 16 additions & 5 deletions menu/demo/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface StoryKnobs {
anchorCorner: Corner | undefined;
menuCorner: Corner | undefined;
defaultFocus: FocusState | undefined;
positioning: 'absolute' | 'fixed' | undefined;
positioning: 'absolute' | 'fixed' | 'document' | undefined;
open: boolean;
quick: boolean;
hasOverflow: boolean;
Expand Down Expand Up @@ -98,7 +98,10 @@ const standard: MaterialStoryInit<StoryKnobs> = {
render(knobs) {
return html`
<div class="root">
<div style="position:relative;">
<div
style="${knobs.positioning === 'document'
? ''
: 'position:relative;'}">
<md-filled-button
@click=${toggleMenu}
@keydown=${toggleMenu}
Expand Down Expand Up @@ -169,7 +172,10 @@ const linkable: MaterialStoryInit<StoryKnobs> = {

return html`
<div class="root">
<div style="position:relative;">
<div
style="${knobs.positioning === 'document'
? ''
: 'position:relative;'}">
<md-filled-button
@click=${toggleMenu}
@keydown=${toggleMenu}
Expand Down Expand Up @@ -301,7 +307,10 @@ const submenu: MaterialStoryInit<StoryKnobs> = {

return html`
<div class="root">
<div style="position:relative;">
<div
style="${knobs.positioning === 'document'
? ''
: 'position:relative;'}">
<md-filled-button
@click=${toggleMenu}
@keydown=${toggleMenu}
Expand Down Expand Up @@ -361,7 +370,9 @@ const menuWithoutButton: MaterialStoryInit<StoryKnobs> = {
],
render(knobs) {
return html`
<div class="root" style="position:relative;">
<div
class="root"
style="${knobs.positioning === 'document' ? '' : 'position:relative;'}">
<div id="anchor"> This is the anchor (use the "open" knob) </div>
<md-menu
slot="menu"
Expand Down
8 changes: 8 additions & 0 deletions menu/internal/controllers/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ export interface MenuSelf {
* An array of items managed by the list.
*/
items: MenuItem[];
/**
* The positioning strategy of the menu.
*
* - `absolute` is relative to the anchor element.
* - `fixed` is relative to the window
* - `document` is relative to the document
*/
positioning?: 'absolute' | 'fixed' | 'document';
/**
* Opens the menu.
*/
Expand Down

0 comments on commit 2b591ca

Please sign in to comment.