-
Notifications
You must be signed in to change notification settings - Fork 6.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(menu): Added ability to show the menu overlay around the menu trigger #1771
Conversation
56de2a8
to
72979c7
Compare
using the `x-position` (`before | after`) and `y-position` (`above | below`) attributes. | ||
By default, the menu will display after and below its trigger. You can change this display position | ||
using the `x-position` (`before | after`) and `y-position` (`above | below`) attributes. The menu | ||
can be positioned over the menu button or outside using `overlay-trigger` (`true | 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.
I'm not a fan of this name ("overlay-trigger")
@kara thoughts?
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 totally agree. It's also not very flexible. Happy to move it to something other than a boolean, perhaps "cover", "horizontal", and "vertical"? I dunno, those are pretty bad 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.
How about "overlap-trigger"? "Overlay-trigger" might sound like the trigger itself is an overlay.
R: @kara |
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.
Can you add tests for this?
|
||
@ViewChild(TemplateRef) templateRef: TemplateRef<any>; | ||
@ContentChildren(MdMenuItem) items: QueryList<MdMenuItem>; | ||
|
||
constructor(@Attribute('x-position') posX: MenuPositionX, | ||
@Attribute('y-position') posY: MenuPositionY) { | ||
@Attribute('y-position') posY: MenuPositionY, | ||
@Attribute('overlay-trigger') trigger = true) { |
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.
Have you considered using the public
keyword instead? That way you don't have to explicitly set it again on line 60.
@Attribute('overlap-trigger') public overlapTrigger = true
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.
Thanks for the suggestion.
@@ -148,7 +149,8 @@ also adds `aria-hasPopup="true"` to the trigger element. | |||
| --- | --- | --- | | |||
| `x-position` | `before | after` | The horizontal position of the menu in relation to the trigger. Defaults to `after`. | | |||
| `y-position` | `above | below` | The vertical position of the menu in relation to the trigger. Defaults to `below`. | | |||
|
|||
| `overlay-trigger` | `true | false` | Whether to have the menu show on top of the menu trigger or outside. Defaults to `true`. | |
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.
Can we rename to overlapTrigger
?
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.
Done.
let originY = overlayY; | ||
if (this.menu.overlayTrigger) { | ||
originY = overlayY === 'top' ? 'bottom' : 'top'; | ||
} |
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.
If you want to overlap the trigger, shouldn't the originY and the overlayY be the same? e.g. the top of the trigger should connect to the top of the overlay or the bottom of the trigger should connect to the bottom of the overlay. Let me know if I'm missing something.
let originY: VerticalConnectionPos;
if (this.menu.overlapTrigger) {
originY = overlayY;
} else {
originY = overlayY === 'top' ? 'bottom' : 'top';
}
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.
One would think so. However I believe the current implementation produces the desired effect. Are you able to clone the pr and run the demo-app and verify? I have done so and I believe the functionality is accurate.
72979c7
to
8bfb6c5
Compare
Not sure why it says the checks have passed, the tests are failing on my local machine. I'm working on it. |
Ah, looks like there's some test bleedover. If I just focus on the fallback positions describe block using fdescribe, those tests will fail. If I focus on the whole menu test then the fallback positions test passes. Tested this on master. |
I have logged the issue: #2530. |
Hey @kara, I'm happy to write tests for the updated change. However since the tests are currently failing I don't feel confident in updating the tests or making new ones without potentially causing issues. Are you able to update the tests? I could attempt to fix the tests, however since I don't have the background knowledge in those tests, I thought it would be better to defer to the original author. Looks like they were introduced here: 7572e34 |
@trshafer We won't be able to merge this PR without tests; it's standard practice for this repo to provide tests with your code. Go ahead and give it a shot. I can take a look at the other issue you found in the meantime. |
Totally. Happy to write tests. I should have red, green, refactored my way to this PR. Starting from a red test is harder for me to confidently ensure the test and change I have written is correct. It would be great if the menu test suite was green before I start. I've also rebased in the recent commit. |
Yep, that makes sense. I'll ping you once I've looked at the the existing tests. |
659775a
to
5373c66
Compare
Hey @kara, thanks for submitting the test fixes. I have written two tests for the overlap change. I made an OverlapSubject class to reduce the testing duplication. Currently its visibility is limited to the new test describe block, however it might be helpful for the other tests as well. |
@@ -115,7 +115,8 @@ Output: | |||
### Customizing menu position | |||
|
|||
By default, the menu will display after and below its trigger. You can change this display position | |||
using the `x-position` (`before | after`) and `y-position` (`above | below`) attributes. | |||
using the `x-position` (`before | after`) and `y-position` (`above | below`) attributes. The menu | |||
can be positioned over the menu button or outside using `overlap-trigger` (`true | 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.
Can you update these in the OVERVIEW as well? The docs are generated from those files now, and the READMEs are probably going away soon.
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.
Updated. Let me know if there is more documentation I should add.
} | ||
|
||
updateTriggerStyle(style: any) { | ||
return Object.assign(this.trigger.style, 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.
Can you use extendObject
from core instead?
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.
Done.
` | ||
}) | ||
class NonOverlapMenu implements TestableMenu { | ||
@ViewChild(MdMenuTrigger) trigger: MdMenuTrigger; |
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.
It doesn't look like you're using this. Remove?
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.
It's being used to open the menu: https://github.com/trshafer/material2/blob/5373c66e8cd9d0a06485158434d0afbc5233245a/src/lib/menu/menu.spec.ts#L271
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 see. Totally missed that!
// Since the menu is below the trigger, the overlay top should be the trigger bottom. | ||
expect(Math.round(subject.overlayRect.top)) | ||
.toBe(Math.round(subject.triggerRect.bottom), | ||
`Expected menu to open in "above" position if "below" position wouldn't fit.`); |
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.
Copy paste error? I think it should be "Expected menu to open in "below" position by default."
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.
Done.
@@ -256,6 +256,66 @@ describe('MdMenu', () => { | |||
} | |||
}); | |||
|
|||
describe('not overlapping', () => { | |||
class OverlapSubject<T extends TestableMenu> { |
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 comment describing class?
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.
Done.
@@ -54,7 +54,8 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy { | |||
@ContentChildren(MdMenuItem) items: QueryList<MdMenuItem>; | |||
|
|||
constructor(@Attribute('x-position') posX: MenuPositionX, | |||
@Attribute('y-position') posY: MenuPositionY) { | |||
@Attribute('y-position') posY: MenuPositionY, | |||
@Attribute('overlap-trigger') public overlapTrigger = true) { | |||
if (posX) { this._setPositionX(posX); } | |||
if (posY) { this._setPositionY(posY); } |
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.
You'll need to update the positionY when the trigger is not overlapping, otherwise the animation origin will be the wrong side of the menu. If you slow the animation down to 10%, you'll see it's animating from the wrong direction.
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.
Good catch. I have updated the menu positioning classes and added a test.
|
||
if (this.menu.overlapTrigger) { | ||
originY = overlayY === 'top' ? 'bottom' : 'top'; | ||
fallbackOriginY = fallbackOverlayY === 'top' ? 'bottom' : 'top'; |
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.
Returning to our previous discussion, this logic is actually backwards. The originY should match the overlayY when the menu is overlapping. When it's not overlapping, the top of the overlay should hit the bottom of the trigger, etc.
The reason that this happens to work in the demo and the tests is because overlapTrigger
is a string. Strings are truthy, so if you set overlapTrigger
to anything (including "false"), this.menu.overlapTrigger
will be truthy and execute this overlap block. When you don't set the attribute (like in the original tests/demos), this.menu.overlapTrigger
is null, so it doesn't default to true and doesn't hit this condition. If you set overlapTrigger="true"
in the demo, you'll see that all values for overlapTrigger will make the menu not overlap, including "true".
I'd coerce this.menu.overlapTrigger to a boolean in menu-directive.ts, and invert the logic here.
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.
You are absolutely correct. The test of an explicit overlapTrigger = true is a good suggestion and exposes this in a test. Thanks for pushing back. Fixed.
.toBe(Math.round(subject.triggerRect.top), | ||
`Expected menu to open in "above" position if "below" position wouldn't fit.`); | ||
}); | ||
}); |
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.
Can you add a test for when overlapTrigger="true"
?
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.
Done.
b855e4f
to
ea72a57
Compare
ea72a57
to
b21bf85
Compare
Hello @kara, thank you for the thorough review. I'm pretty sure I have responded to all of your comments. Let me know what else I can do to help move this across the finish line. |
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.
LGTM
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.
When I try to merge this into Google's codebase I get the following error:
Class 'Menu' incorrectly implements interface 'MdMenuPanel'.
Property 'overlapTrigger' is missing in type 'Menu'.
@@ -52,6 +52,7 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy { | |||
|
|||
@ViewChild(TemplateRef) templateRef: TemplateRef<any>; | |||
@ContentChildren(MdMenuItem) items: QueryList<MdMenuItem>; | |||
@Input('overlap-trigger') overlapTrigger = true; |
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 need to rename to dash-case, we're trying to switch everything to camelCase
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.
so should the api look like <md-menu [overlapTrigger]="false">
?
26234eb
to
b8d6a51
Compare
Switched |
@@ -52,6 +52,7 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy { | |||
|
|||
@ViewChild(TemplateRef) templateRef: TemplateRef<any>; | |||
@ContentChildren(MdMenuItem) items: QueryList<MdMenuItem>; | |||
@Input('overlapTrigger') overlapTrigger = true; |
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.
tiny nit: if the variable and the html attr have the same name you can omit the alias:
@Input() overlapTrigger = true;
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.
Done, here and in the test.
b8d6a51
to
17fc76e
Compare
17fc76e
to
3e12b1a
Compare
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Currently the menu is fixed to show over the menu trigger.
This adds a (rather blunt) boolean to decide if the menu should be shown above or below the trigger. I thought about changing the possibilities to
overlay-position: default | above | below | left | right;
But that felt like a bit too much flexibility. I think some version of this feature is really important for those designing their own menu. Let me know what you think.Thanks,
Thomas
Here is the menu below the trigger:
Here is the menu above the trigger: