-
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(cdk-experimental/menu): add the ability to open/close menus on mouse click and hover #20118
Conversation
const spy = jasmine.createSpy('mouse enter spy'); | ||
focusManager.mouseEntered.subscribe(spy); | ||
|
||
expect(fixture.componentInstance.showThird).toBeFalse(); |
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 sure this adds anything, since it's clear in the test host setup that this starts out false. (Also, it doesn't look like it's being updated in response to anything under test.)
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 more of a sanity check before the test, but I see how that's not clear. I'll make a change to make that more obvious.
The functionality that's being tested here is that when a new element is added, the focus-manager updates and picks it up.
|
||
constructor(private readonly _elements: QueryList<T>) {} | ||
|
||
/** |
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.
Currently, a new observable is generated every time the method is called. Is this strictly necessary, or can the same purpose be served by using a single observable instance?
If a new observable is required every time, you should explain why in the documentation. (I would also rename it to '_getNewMouseEnterObservable()' to make this clear.)
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 point. It was part of the construction of the observable so it was only called once. However with Jeremys comment below it'll just end up as a function so it should be addressed
@@ -81,6 +83,11 @@ export class CdkMenuItem implements FocusableOption { | |||
this._elementRef.nativeElement.focus(); | |||
} | |||
|
|||
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order | |||
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we | |||
// can move this back into `host`. |
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.
Include a formal 'TODO' to do this?
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 is the standard comment we use throughout the library; we have a backlog item to clean them all up when we can get to deleting ViewEngine
this._mouseManager = new FocusMouseManager(this._allItems); | ||
this._mouseManager.mouseEntered.pipe(takeUntil(this._destroyed)).subscribe(item => { | ||
if (this._hasOpenSubmenu()) { | ||
this._keyManager.setActiveItem(item); | ||
} | ||
}); |
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.
We can run this block in runOutsideAngular
to avoid extra change detections
* FocusMouseManager keeps track of the element under mouse focus. It emits the element on an | ||
* observable when an element is moused into. |
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.
After our conversation, I realized this could just be a function rather than a class since it doesn't have any state and just has a single method
/**
* Gets a stream of pointer (mouse) entries into the given items.
* This should typically run outside the Angular zone.
*/
function getItemPointerEntries<T extends FocusableElement>(items: QueryList<T>): Observable<T> {
return this._elements.changes.pipe(
startWith(this._elements),
mergeMap((list: QueryList<T>) =>
list.map(element =>
fromEvent(element._elementRef.nativeElement, 'mouseenter').pipe(
mapTo(element),
takeUntil(this._elements.changes)
)
)
),
mergeAll()
);
}
@@ -81,6 +83,11 @@ export class CdkMenuItem implements FocusableOption { | |||
this._elementRef.nativeElement.focus(); | |||
} | |||
|
|||
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order | |||
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we | |||
// can move this back into `host`. |
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 is the standard comment we use throughout the library; we have a backlog item to clean them all up when we can get to deleting ViewEngine
|
||
/** Mock mouse events required to open the file menu. */ | ||
function openFileMenu() { | ||
dispatchEvent(menuBarNativeItems[0], createMouseEvent('mouseenter')); |
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.
Rather than doing dispatchEvent
with createMouseEvent
, there's a convenient dispatchMouseEvent
helper too
aec83c0
to
600bf0e
Compare
@jelbourn @teflonwaffles Feedback should be addressed PTAL |
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
@@ -0,0 +1,40 @@ | |||
/** |
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 think I would change the name of this file to something like item-pointer-entries.ts
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
You can add the "merge-ready" label when ready |
…ouse click and hover Add the ability to open/close a menu when a user clicks a menu trigger and when a user hovers over menu items. Additionally, keep track of hovered menu items and sync them with the FocusKeyManager allowing a user to continue with a keyboard where they left off with their mouse.
600bf0e
to
5e70e3b
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. |
Add the ability to open/close a menu when a user clicks a menu trigger and when a user hovers over
menu items. Additionally, keep track of hovered menu items and sync them with the FocusKeyManager
allowing a user to continue with a keyboard where they left off with their mouse.