Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

feat(menu): Adds new menu, menu-surface. #3311

Merged
merged 21 commits into from
Aug 13, 2018
Merged

feat(menu): Adds new menu, menu-surface. #3311

merged 21 commits into from
Aug 13, 2018

Conversation

williamernest
Copy link
Contributor

@williamernest williamernest commented Aug 7, 2018

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 use mdc-menu-surface and mdc-list styles and JavaScript.

@mdc-web-bot
Copy link
Collaborator

All 188 screenshot tests passed for commit 01e071a vs. master! 💯🎉

@codecov-io
Copy link

codecov-io commented Aug 8, 2018

Codecov Report

Merging #3311 into master will increase coverage by 0.08%.
The diff coverage is 98.77%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
packages/mdc-menu/constants.js 100% <ø> (ø) ⬆️
packages/mdc-list/constants.js 100% <ø> (ø) ⬆️
packages/mdc-menu/foundation.js 100% <100%> (+0.97%) ⬆️
packages/mdc-menu-surface/util.js 100% <100%> (ø)
packages/mdc-menu-surface/constants.js 100% <100%> (ø)
packages/mdc-menu/index.js 100% <100%> (+6.09%) ⬆️
packages/mdc-menu-surface/foundation.js 97.8% <97.8%> (ø)
packages/mdc-menu-surface/index.js 99.02% <99.02%> (ø)
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5160241...92ec26b. Read the comment docs.

@mdc-web-bot
Copy link
Collaborator

All 188 screenshot tests passed for commit d55bdc5 vs. master! 💯🎉

Copy link
Contributor

@kfranqueiro kfranqueiro left a 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() +
Copy link
Contributor

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');
Copy link
Contributor

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;
Copy link
Contributor

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)

Copy link
Contributor Author

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.

`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
Copy link
Contributor

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?


Method Signature | Description
--- | ---
`show() => void` | Proxies to the foundation's `open()` method.
Copy link
Contributor

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?

Copy link
Contributor Author

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;
Copy link
Contributor

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;
Copy link
Contributor

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);
Copy link
Contributor

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;
Copy link
Contributor

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",
Copy link
Contributor

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?

@mdc-web-bot
Copy link
Collaborator

All 188 screenshot tests passed for commit 3f45dd1 vs. master! 💯🎉

@mdc-web-bot
Copy link
Collaborator

All 188 screenshot tests passed for commit 522a8a8 vs. master! 💯🎉

@mdc-web-bot
Copy link
Collaborator

All 188 screenshot tests passed for commit 6d6ccd9 vs. master! 💯🎉

#### 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.
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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?

this.menuSurface_.anchorElement = element;
}

registerListeners_() {
Copy link
Contributor

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.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@@ -1,5 +1,5 @@
/**
* Copyright 2016 Google Inc. All Rights Reserved.
* Copyright 2018 Google Inc. All Rights Reserved.
Copy link
Contributor

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 (and getBezierCoordinate_ and solvePositionFromXValue_)

`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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space before "methods"


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.
Copy link
Contributor

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', () => {
Copy link
Contributor

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', () => {
Copy link
Contributor

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', () => {
Copy link
Contributor

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);
Copy link
Contributor

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', () => {
Copy link
Contributor

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" :|


return storedTransformPropertyName_;
}
export {getTransformPropertyName};
Copy link
Contributor

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

* @param {string} type
* @param {function(!Event)} handler
*/
registerInteractionHandler(type, handler) {}
Copy link
Contributor

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() {
Copy link
Contributor

@kfranqueiro kfranqueiro Aug 9, 2018

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?

@mdc-web-bot
Copy link
Collaborator

All 344 screenshot tests passed for commit 99fa8f9 vs. master! 💯🎉

destroy() {
this.root_.removeEventListener('keydown', this.handleKeydown_);
this.root_.removeEventListener(strings.OPENED_EVENT, this.registerBodyClickListener_);
this.root_.removeEventListener(strings.CLOSED_EVENT, this.deregisterBodyClickListener_);
Copy link
Contributor

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()

this.handleClick_ = (evt) => this.foundation_.handleClick(evt);

this.menuSurface_.listen(MDCMenuSurfaceFoundation.strings.OPENED_EVENT, this.afterOpenedCallback_);
this.registerListeners_();
Copy link
Contributor

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);
});

//
Copy link
Contributor

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)

component.setAnchorCorner(Corner.TOP_START);
// The method sets private variable on the foundation, nothing to verify.
});
test('destory deregisters event listener for click', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destory -> destroy

const {component} = setupTest();
assert.isOk(!component.selectedItem);
});
test('destory deregisters event listener for keydown', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destory -> destroy

@mdc-web-bot
Copy link
Collaborator

All 344 screenshot tests passed for commit 08a9ccc vs. master! 💯🎉

@mdc-web-bot
Copy link
Collaborator

All 337 screenshot tests passed for commit 5202b7c vs. master! 💯🎉

@mdc-web-bot
Copy link
Collaborator

All 337 screenshot tests passed for commit c9d198f vs. master! 💯🎉

@mdc-web-bot
Copy link
Collaborator

All 337 screenshot tests passed for commit 87260ed vs. master! 💯🎉

@mdc-web-bot
Copy link
Collaborator

All 337 screenshot tests passed for commit 92ec26b vs. master! 💯🎉

@williamernest williamernest merged commit 6439c5b into master Aug 13, 2018
@williamernest williamernest deleted the feat/menu-2 branch August 13, 2018 22:49
@jamesmfriedman jamesmfriedman mentioned this pull request Aug 23, 2018
48 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.