From 290a3ff984f5f5dae220bf8f8d58a36f2c194c66 Mon Sep 17 00:00:00 2001 From: Elliott Marquez Date: Fri, 20 Oct 2023 13:18:27 -0700 Subject: [PATCH] feat(menu): add document-level positioning related #5120 PiperOrigin-RevId: 575301929 --- .../figures/menu/usage-document.html | 30 ++++ .../images/menu/usage-document.webp | Bin 0 -> 6650 bytes docs/components/menu.md | 65 ++++++++- menu/demo/demo.ts | 3 +- menu/demo/stories.ts | 21 ++- menu/internal/controllers/shared.ts | 8 ++ .../controllers/surfacePositionController.ts | 128 ++++++++++++------ menu/internal/menu.ts | 16 ++- menu/internal/submenu/sub-menu.ts | 4 + 9 files changed, 224 insertions(+), 51 deletions(-) create mode 100644 docs/components/figures/menu/usage-document.html create mode 100644 docs/components/images/menu/usage-document.webp diff --git a/docs/components/figures/menu/usage-document.html b/docs/components/figures/menu/usage-document.html new file mode 100644 index 00000000000..77803b58ce9 --- /dev/null +++ b/docs/components/figures/menu/usage-document.html @@ -0,0 +1,30 @@ +
+
+
+
+ Open document menu +
+ + +
Apple
+
+ +
Banana
+
+ +
Cucumber
+
+
+
+ +
+
diff --git a/docs/components/images/menu/usage-document.webp b/docs/components/images/menu/usage-document.webp new file mode 100644 index 0000000000000000000000000000000000000000..8a50cf80403ae479e5fc0b0cb9d2fbb7d392d19d GIT binary patch literal 6650 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 ``` -### 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 @@ -264,6 +264,64 @@ Cucumber."](images/menu/usage-fixed.webp) ``` +### 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 `` + - [Top layer](https://developer.mozilla.org/en-US/docs/Glossary/Top_layer) + +- The same `md-menu` is being reused and the contents and anchors are being + dynamically changed + + + +!["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) + + + + +```html + +
+ Open document menu +
+ + + + +
Apple
+
+ +
Banana
+
+ +
Cucumber
+
+
+ + +``` + ## Accessibility 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) ## API - ### MdMenu <md-menu> #### Properties diff --git a/menu/demo/demo.ts b/menu/demo/demo.ts index fb861ad6491..453a578d02a 100644 --- a/menu/demo/demo.ts +++ b/menu/demo/demo.ts @@ -64,10 +64,11 @@ const collection = new MaterialCollection>( }), 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'}, ], }), }), diff --git a/menu/demo/stories.ts b/menu/demo/stories.ts index 7d2f7eb2e1a..e6ecfac021d 100644 --- a/menu/demo/stories.ts +++ b/menu/demo/stories.ts @@ -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; @@ -98,7 +98,10 @@ const standard: MaterialStoryInit = { render(knobs) { return html`
-
+
= { return html`
-
+
= { return html`
-
+
= { ], render(knobs) { return html` -
+
This is the anchor (use the "open" knob)
` to render over everything or in a top-layer. + * - You are reusing a single `md-menu` element that dynamically renders + * content. */ - @property() positioning: 'absolute' | 'fixed' = 'absolute'; + @property() positioning: 'absolute' | 'fixed' | 'document' = 'absolute'; /** * Skips the opening and closing animations. */ @@ -449,7 +459,7 @@ export abstract class Menu extends LitElement { return html``; } - private getSurfaceClasses() { + private getSurfaceClasses(): ClassInfo { return { open: this.open, fixed: this.positioning === 'fixed', diff --git a/menu/internal/submenu/sub-menu.ts b/menu/internal/submenu/sub-menu.ts index 6b818d5bc60..66c85e8f83e 100644 --- a/menu/internal/submenu/sub-menu.ts +++ b/menu/internal/submenu/sub-menu.ts @@ -136,6 +136,10 @@ export class SubMenu extends LitElement { }, {once: true}, ); + // Parent menu is position: absolute, so document positioning would be wrong + if (menu.positioning === 'document') { + menu.positioning = 'absolute'; + } menu.quick = true; // Submenus are in overflow when not fixed. Can remove once we have native // popup support