-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat(menu): Adds new menu, menu-surface. #3311
Conversation
Adds the ability to change the anchor element of a menu surface dynamically. Also adds a function to allow the user to hoist the menu to the body, and calculates the position of the menu in both absolute and fixed positioning.
This reverts commit 01f6b33.
All 188 screenshot tests passed for commit 01e071a vs. |
Codecov Report
@@ Coverage Diff @@
## master #3311 +/- ##
==========================================
+ Coverage 98.05% 98.13% +0.08%
==========================================
Files 120 123 +3
Lines 5141 5215 +74
Branches 643 638 -5
==========================================
+ Hits 5041 5118 +77
+ Misses 100 97 -3
Continue to review full report at Codecov.
|
All 188 screenshot tests passed for commit d55bdc5 vs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a review of menu-surface and paused there. I kinda had a lot of questions and some of them are probably just due to lack of context.
demos/menu.html
Outdated
|
||
menuEl4.addEventListener('MDCMenu:selected', function(evt) { | ||
var detail = evt.detail; | ||
lastSelected.textContent = 'Overflow Menu: "' + detail.item.textContent.trim() + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overflow -> Context
demos/menu.html
Outdated
remember.addEventListener('change', function(evt) { | ||
menu.rememberSelection = evt.target.checked; | ||
// Normal menu | ||
var menuEl2 = document.querySelector('#demo-menu-2'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: The rest of these are using getElementById
demos/menu.scss
Outdated
html, | ||
body, | ||
main { | ||
position: relative; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are both of these styles actually needed/useful on all of these elements? (in both demo pages)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
position: relative
is required for body, but I removed the rest.
packages/mdc-menu-surface/README.md
Outdated
`isOpen() => boolean` | Returns a boolean indicating whether the menu surface is open. | ||
`setQuickOpen(quickOpen: boolean) => void` | Sets whether the menu surface should open and close without animation when the `open`/`close` methods are called. | ||
|
||
### Events |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be moved to under the component properties and methods?
packages/mdc-menu-surface/README.md
Outdated
|
||
Method Signature | Description | ||
--- | --- | ||
`show() => void` | Proxies to the foundation's `open()` method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these named differently between the component and foundation? I realize we can't call them open/close on the component, but then should we switch to show/hide on the foundation, too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed show
/hide
@import "@material/rtl/mixins"; | ||
@import "./mixins"; | ||
|
||
$mdc-menu-surface-fade-in-duration: .03s; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-> _variables.scss?
transform-origin: top left; | ||
border-radius: 4px; | ||
opacity: 0; | ||
white-space: nowrap; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this make sense on menu-surface
? Seems ultimately pertinent to #3201, maybe, but maybe not right here?
display: none; | ||
position: absolute; | ||
box-sizing: border-box; | ||
max-width: calc(100vw - 32px); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should 32px be a variable?
padding: 0; | ||
transform: scale(1); | ||
transform-origin: top left; | ||
border-radius: 4px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have a corner-radius mixin?
"url": "https://github.com/material-components/material-components-web.git" | ||
}, | ||
"dependencies": { | ||
"@material/animation": "^0.34.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these versions up to date?
All 188 screenshot tests passed for commit 3f45dd1 vs. |
All 188 screenshot tests passed for commit 522a8a8 vs. |
All 188 screenshot tests passed for commit 6d6ccd9 vs. |
#### Absolute Position | ||
|
||
The menu surface can use absolute positioning when being displayed. This requires that the element containing the | ||
menu(`body` if using `hoistMenyToBody()`) has the `position: relative` style. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meny -> Menu
Add space before (
if (isRightAligned) { | ||
const rightOffset = avoidHorizontalOverlap ? anchorWidth - this.anchorMargin_.left : this.anchorMargin_.right; | ||
|
||
// Hoisted elements positioning doesn't account for the scrollbar, so the right property needs to be reduced by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused, since AFAICT the code still looks like it's doing something related to factoring out the scrollbar and only when isRightAligned
is true. So how is the comment outdated?
</div> | ||
``` | ||
|
||
```js |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should probably be a "Or in JS:" before this, right? You only need one or the other IIUC?
packages/mdc-menu/index.js
Outdated
this.menuSurface_.anchorElement = element; | ||
} | ||
|
||
registerListeners_() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious why we are registering and deregistering listeners based on when it's open/closed. Is there any harm to just having them hooked up all the time? (It'd be one thing if they were registered on the body, but these are on the menu surface element.)
} | ||
|
||
/** | ||
* Return the item within the menu that is selected. | ||
* Return the item within the menu at the index specified. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we foresee a purpose for this API? I know it existed before but I'm not sure why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this was here previously so we could attempt to replicate .item(index)
functionality from the native select. I planned on implementing this in select so I included it here, but it may not be required.
packages/mdc-menu-surface/util.js
Outdated
@@ -1,5 +1,5 @@ | |||
/** | |||
* Copyright 2016 Google Inc. All Rights Reserved. | |||
* Copyright 2018 Google Inc. All Rights Reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we're here, I think there are a bunch of these held over from old JS animation logic in "Simple" Menu that we don't need anymore (they're only referenced in tests):
clamp
bezierProgress
(andgetBezierCoordinate_
andsolvePositionFromXValue_
)
packages/mdc-menu/README.md
Outdated
`items` | Array<Element> | Proxies to the foundation's container to query for all `.mdc-list-item[role]` elements. | ||
`itemsContainer` | Element | Queries the foundation's root element for the `mdc-menu__items` container element. | ||
`quickOpen` | Boolean | Proxies to the foundation's `setQuickOpen()` method. | ||
`open` | Boolean | `get` proxies to the menu surface's `open` property. `set` proxies to the `show` and `hide`methods. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space before "methods"
packages/mdc-menu/README.md
Outdated
|
||
Method Signature | Description | ||
--- | --- | ||
`show({focusIndex: ?number}) => void` | Proxies to the foundation's `open()` method. An optional config parameter allows the caller to specify which item should receive focus after the menu opens. | ||
`show() => void` | Proxies to the menu surface's `open()` method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
show and hide were removed, right?
td.verify(mockFoundation.setIsHoisted(true)); | ||
}); | ||
|
||
test('setFixedPosition is false', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this up below the setFixedPosition is true test?
td.verify(mockFoundation.setQuickOpen(false)); | ||
}); | ||
|
||
test('foundationAdapter#addClass adds a class to the root element', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
foundationAdapter -> adapter
document.body.removeChild(root); | ||
}); | ||
|
||
test('adapter#restoreFocus does not restores the focus if never called adapter#saveFocus', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
restores -> restore
const {root, component} = setupTest(true); | ||
document.body.appendChild(root); | ||
component.initialSyncWithDOM(); | ||
assert.equal(component.getDefaultFoundation().adapter_.getAnchorDimensions(), undefined); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert.isUndefined
td.verify(handler(td.matchers.anything())); | ||
}); | ||
|
||
test('adapter#restoreFocus restore focus saved by adapter#saveFocus', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one was correct with "restores" :|
packages/mdc-menu-surface/util.js
Outdated
|
||
return storedTransformPropertyName_; | ||
} | ||
export {getTransformPropertyName}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add blank line before export
packages/mdc-menu-surface/adapter.js
Outdated
* @param {string} type | ||
* @param {function(!Event)} handler | ||
*/ | ||
registerInteractionHandler(type, handler) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to move keydown registration to the component now or in a separate PR?
* types. | ||
* @return {!MDCMenuSurfaceAdapter} | ||
*/ | ||
static get defaultAdapter() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default adapter is missing focus
... either that or focus
shouldn't be in adapter, and readme?
All 344 screenshot tests passed for commit 99fa8f9 vs. |
destroy() { | ||
this.root_.removeEventListener('keydown', this.handleKeydown_); | ||
this.root_.removeEventListener(strings.OPENED_EVENT, this.registerBodyClickListener_); | ||
this.root_.removeEventListener(strings.CLOSED_EVENT, this.deregisterBodyClickListener_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget to call super.destroy()
packages/mdc-menu/index.js
Outdated
this.handleClick_ = (evt) => this.foundation_.handleClick(evt); | ||
|
||
this.menuSurface_.listen(MDCMenuSurfaceFoundation.strings.OPENED_EVENT, this.afterOpenedCallback_); | ||
this.registerListeners_(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any point to [de]registerListeners existing anymore or should we just move them here (and use [un]listen for their registrations)?
assert.equal(component.getDefaultFoundation().adapter_.getInnerDimensions().height, root.offsetHeight); | ||
}); | ||
|
||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove (I commented on the individual commit before, oops)
test/unit/mdc-menu/mdc-menu.test.js
Outdated
component.setAnchorCorner(Corner.TOP_START); | ||
// The method sets private variable on the foundation, nothing to verify. | ||
}); | ||
test('destory deregisters event listener for click', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
destory -> destroy
test/unit/mdc-menu/mdc-menu.test.js
Outdated
const {component} = setupTest(); | ||
assert.isOk(!component.selectedItem); | ||
}); | ||
test('destory deregisters event listener for keydown', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
destory -> destroy
All 344 screenshot tests passed for commit 08a9ccc vs. |
All 337 screenshot tests passed for commit 5202b7c vs. |
All 337 screenshot tests passed for commit c9d198f vs. |
All 337 screenshot tests passed for commit 87260ed vs. |
All 337 screenshot tests passed for commit 92ec26b vs. |
Resolves #2972, resolves #2770, resolves #2763, resolves #2758, resolves #2756, resolves #2691, resolves #2288, resolves #1845, resolves #1495
Adds the ability for a menu-surface to be positioned relative to an element, relative to the page, or fixed to the viewport. Adds the ability to dynamically change the anchoring element. Adds the ability to hoist the menu to the body.
BREAKING CHANGE: Menu positioning logic has been split into its own package (
mdc-menu-surface
).mdc-menu
is rebuilt to usemdc-menu-surface
andmdc-list
styles and JavaScript.