Skip to content

Commit 2b591ca

Browse files
Elliott Marquezcopybara-github
Elliott Marquez
authored andcommitted
feat(menu): add document-level positioning
related #5120 PiperOrigin-RevId: 580293404
1 parent f08ecce commit 2b591ca

File tree

11 files changed

+282
-53
lines changed

11 files changed

+282
-53
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<div class="figure-wrapper">
2+
<figure
3+
style="justify-content: center"
4+
aria-label="A filled button that says open document menu. Interact with the button to open a document menu."
5+
>
6+
<div>
7+
<div style="margin: 16px">
8+
<md-filled-button id="usage-document-anchor">Open document menu</md-filled-button>
9+
</div>
10+
<md-menu positioning="document" id="usage-document" anchor="usage-document-anchor">
11+
<md-menu-item>
12+
<div slot="headline">Apple</div>
13+
</md-menu-item>
14+
<md-menu-item>
15+
<div slot="headline">Banana</div>
16+
</md-menu-item>
17+
<md-menu-item>
18+
<div slot="headline">Cucumber</div>
19+
</md-menu-item>
20+
</md-menu>
21+
</div>
22+
<script type="module">
23+
const anchorEl = document.body.querySelector("#usage-document-anchor");
24+
const menuEl = document.body.querySelector("#usage-document");
25+
anchorEl.addEventListener("click", () => {
26+
menuEl.open = !menuEl.open;
27+
});
28+
</script>
29+
</figure>
30+
</div>
6.49 KB
Binary file not shown.

docs/components/menu.md

+61-4
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ choices on a temporary surface.
6161
When opened, menus position themselves to an anchor. Thus, either `anchor` or
6262
`anchorElement` must be supplied to `md-menu` before opening. Additionally, a
6363
shared parent of `position:relative` should be present around the menu and it's
64-
anchor.
64+
anchor, because the default menu is positioned relative to the anchor element.
6565

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

218-
### Fixed menus
218+
### Fixed-positioned menus
219219

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

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

267+
### Document-positioned menus
268+
269+
When set to `positioning="document"`, `md-menu` will position itself relative to
270+
the document as opposed to the element or the window from `"absolute"` and
271+
`"fixed"` values respectively.
272+
273+
Document level positioning is useful for the following cases:
274+
275+
- There are no ancestor elements that produce a `relative` positioning
276+
context.
277+
- `position: relative`
278+
- `position: absolute`
279+
- `position: fixed`
280+
- `transform: translate(x, y)`
281+
- etc.
282+
- The menu is hoisted to the top of the DOM
283+
- The last child of `<body>`
284+
- [Top layer](https://developer.mozilla.org/en-US/docs/Glossary/Top_layer)
285+
<!-- {.external} -->
286+
- The same `md-menu` is being reused and the contents and anchors are being
287+
dynamically changed
288+
289+
<!-- no-catalog-start -->
290+
291+
!["A filled button that says open document menu. There is an open menu anchored
292+
to the bottom of the button with three items, Apple, Banana, and
293+
Cucumber."](images/menu/usage-document.webp)
294+
295+
<!-- no-catalog-end -->
296+
<!-- catalog-include "figures/menu/usage-document.html" -->
297+
298+
```html
299+
<!-- Note the lack of position: relative parent. -->
300+
<div style="margin: 16px;">
301+
<md-filled-button id="usage-document-anchor">Open document menu</md-filled-button>
302+
</div>
303+
304+
<!-- document menus do not require a common ancestor with the anchor. -->
305+
<md-menu positioning="document" id="usage-document" anchor="usage-document-anchor">
306+
<md-menu-item>
307+
<div slot="headline">Apple</div>
308+
</md-menu-item>
309+
<md-menu-item>
310+
<div slot="headline">Banana</div>
311+
</md-menu-item>
312+
<md-menu-item>
313+
<div slot="headline">Cucumber</div>
314+
</md-menu-item>
315+
</md-menu>
316+
317+
<script type="module">
318+
const anchorEl = document.body.querySelector('#usage-document-anchor');
319+
const menuEl = document.body.querySelector('#usage-document');
320+
321+
anchorEl.addEventListener('click', () => { menuEl.open = !menuEl.open; });
322+
</script>
323+
```
324+
267325
## Accessibility
268326

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

396454
## API
397455

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

401458
#### Properties

menu/demo/demo.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,11 @@ const collection = new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>(
6464
}),
6565
new Knob('positioning', {
6666
defaultValue: 'absolute' as const,
67-
ui: selectDropdown<'absolute' | 'fixed'>({
67+
ui: selectDropdown<'absolute' | 'fixed' | 'document'>({
6868
options: [
6969
{label: 'absolute', value: 'absolute'},
7070
{label: 'fixed', value: 'fixed'},
71+
{label: 'document', value: 'document'},
7172
],
7273
}),
7374
}),

menu/demo/stories.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface StoryKnobs {
2222
anchorCorner: Corner | undefined;
2323
menuCorner: Corner | undefined;
2424
defaultFocus: FocusState | undefined;
25-
positioning: 'absolute' | 'fixed' | undefined;
25+
positioning: 'absolute' | 'fixed' | 'document' | undefined;
2626
open: boolean;
2727
quick: boolean;
2828
hasOverflow: boolean;
@@ -98,7 +98,10 @@ const standard: MaterialStoryInit<StoryKnobs> = {
9898
render(knobs) {
9999
return html`
100100
<div class="root">
101-
<div style="position:relative;">
101+
<div
102+
style="${knobs.positioning === 'document'
103+
? ''
104+
: 'position:relative;'}">
102105
<md-filled-button
103106
@click=${toggleMenu}
104107
@keydown=${toggleMenu}
@@ -169,7 +172,10 @@ const linkable: MaterialStoryInit<StoryKnobs> = {
169172

170173
return html`
171174
<div class="root">
172-
<div style="position:relative;">
175+
<div
176+
style="${knobs.positioning === 'document'
177+
? ''
178+
: 'position:relative;'}">
173179
<md-filled-button
174180
@click=${toggleMenu}
175181
@keydown=${toggleMenu}
@@ -301,7 +307,10 @@ const submenu: MaterialStoryInit<StoryKnobs> = {
301307

302308
return html`
303309
<div class="root">
304-
<div style="position:relative;">
310+
<div
311+
style="${knobs.positioning === 'document'
312+
? ''
313+
: 'position:relative;'}">
305314
<md-filled-button
306315
@click=${toggleMenu}
307316
@keydown=${toggleMenu}
@@ -361,7 +370,9 @@ const menuWithoutButton: MaterialStoryInit<StoryKnobs> = {
361370
],
362371
render(knobs) {
363372
return html`
364-
<div class="root" style="position:relative;">
373+
<div
374+
class="root"
375+
style="${knobs.positioning === 'document' ? '' : 'position:relative;'}">
365376
<div id="anchor"> This is the anchor (use the "open" knob) </div>
366377
<md-menu
367378
slot="menu"

menu/internal/controllers/shared.ts

+8
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ export interface MenuSelf {
5959
* An array of items managed by the list.
6060
*/
6161
items: MenuItem[];
62+
/**
63+
* The positioning strategy of the menu.
64+
*
65+
* - `absolute` is relative to the anchor element.
66+
* - `fixed` is relative to the window
67+
* - `document` is relative to the document
68+
*/
69+
positioning?: 'absolute' | 'fixed' | 'document';
6270
/**
6371
* Opens the menu.
6472
*/

0 commit comments

Comments
 (0)