Skip to content
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

Merged
merged 1 commit into from
Jul 29, 2020

Conversation

andy9775
Copy link
Contributor

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.

@andy9775 andy9775 requested a review from jelbourn as a code owner July 28, 2020 17:15
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Jul 28, 2020
const spy = jasmine.createSpy('mouse enter spy');
focusManager.mouseEntered.subscribe(spy);

expect(fixture.componentInstance.showThird).toBeFalse();

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

Copy link
Contributor Author

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

/**

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

Copy link
Contributor Author

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`.

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?

Copy link
Member

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

Comment on lines 176 to 188
this._mouseManager = new FocusMouseManager(this._allItems);
this._mouseManager.mouseEntered.pipe(takeUntil(this._destroyed)).subscribe(item => {
if (this._hasOpenSubmenu()) {
this._keyManager.setActiveItem(item);
}
});
Copy link
Member

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

Comment on lines 20 to 21
* FocusMouseManager keeps track of the element under mouse focus. It emits the element on an
* observable when an element is moused into.
Copy link
Member

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

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

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

@andy9775
Copy link
Contributor Author

@jelbourn @teflonwaffles Feedback should be addressed PTAL

@jelbourn jelbourn added target: minor This PR is targeted for the next minor release merge safe labels Jul 29, 2020
Copy link
Member

@jelbourn jelbourn left a 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 @@
/**
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@jelbourn jelbourn added the lgtm label Jul 29, 2020
@jelbourn
Copy link
Member

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.
@andy9775 andy9775 added the action: merge The PR is ready for merge by the caretaker label Jul 29, 2020
@jelbourn jelbourn merged commit cdbf2c1 into angular:master Jul 29, 2020
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Aug 29, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants