Skip to content

Commit 8fcc730

Browse files
authored
fix: Improve menu mouse/keyboard selection interaction. (#8749)
* chore: Use "pointer" instead of "mouse" in menu.ts. * fix: Only highlight menu items on hover if the pointer has moved. * fix: Don't blur menus on pointerleave.
1 parent 101ad82 commit 8fcc730

1 file changed

Lines changed: 51 additions & 40 deletions

File tree

core/menu.ts

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export class Menu {
3131
private readonly menuItems: MenuItem[] = [];
3232

3333
/**
34-
* Coordinates of the mousedown event that caused this menu to open. Used to
35-
* prevent the consequent mouseup event due to a simple click from
34+
* Coordinates of the pointerdown event that caused this menu to open. Used to
35+
* prevent the consequent pointerup event due to a simple click from
3636
* activating a menu item immediately.
3737
*/
3838
openingCoords: Coordinate | null = null;
@@ -43,17 +43,17 @@ export class Menu {
4343
*/
4444
private highlightedItem: MenuItem | null = null;
4545

46-
/** Mouse over event data. */
47-
private mouseOverHandler: browserEvents.Data | null = null;
46+
/** Pointer over event data. */
47+
private pointerMoveHandler: browserEvents.Data | null = null;
4848

4949
/** Click event data. */
5050
private clickHandler: browserEvents.Data | null = null;
5151

52-
/** Mouse enter event data. */
53-
private mouseEnterHandler: browserEvents.Data | null = null;
52+
/** Pointer enter event data. */
53+
private pointerEnterHandler: browserEvents.Data | null = null;
5454

55-
/** Mouse leave event data. */
56-
private mouseLeaveHandler: browserEvents.Data | null = null;
55+
/** Pointer leave event data. */
56+
private pointerLeaveHandler: browserEvents.Data | null = null;
5757

5858
/** Key down event data. */
5959
private onKeyDownHandler: browserEvents.Data | null = null;
@@ -99,11 +99,11 @@ export class Menu {
9999
}
100100

101101
// Add event handlers.
102-
this.mouseOverHandler = browserEvents.conditionalBind(
102+
this.pointerMoveHandler = browserEvents.conditionalBind(
103103
element,
104-
'pointerover',
104+
'pointermove',
105105
this,
106-
this.handleMouseOver,
106+
this.handlePointerMove,
107107
true,
108108
);
109109
this.clickHandler = browserEvents.conditionalBind(
@@ -113,18 +113,18 @@ export class Menu {
113113
this.handleClick,
114114
true,
115115
);
116-
this.mouseEnterHandler = browserEvents.conditionalBind(
116+
this.pointerEnterHandler = browserEvents.conditionalBind(
117117
element,
118118
'pointerenter',
119119
this,
120-
this.handleMouseEnter,
120+
this.handlePointerEnter,
121121
true,
122122
);
123-
this.mouseLeaveHandler = browserEvents.conditionalBind(
123+
this.pointerLeaveHandler = browserEvents.conditionalBind(
124124
element,
125125
'pointerleave',
126126
this,
127-
this.handleMouseLeave,
127+
this.handlePointerLeave,
128128
true,
129129
);
130130
this.onKeyDownHandler = browserEvents.conditionalBind(
@@ -183,21 +183,21 @@ export class Menu {
183183
/** Dispose of this menu. */
184184
dispose() {
185185
// Remove event handlers.
186-
if (this.mouseOverHandler) {
187-
browserEvents.unbind(this.mouseOverHandler);
188-
this.mouseOverHandler = null;
186+
if (this.pointerMoveHandler) {
187+
browserEvents.unbind(this.pointerMoveHandler);
188+
this.pointerMoveHandler = null;
189189
}
190190
if (this.clickHandler) {
191191
browserEvents.unbind(this.clickHandler);
192192
this.clickHandler = null;
193193
}
194-
if (this.mouseEnterHandler) {
195-
browserEvents.unbind(this.mouseEnterHandler);
196-
this.mouseEnterHandler = null;
194+
if (this.pointerEnterHandler) {
195+
browserEvents.unbind(this.pointerEnterHandler);
196+
this.pointerEnterHandler = null;
197197
}
198-
if (this.mouseLeaveHandler) {
199-
browserEvents.unbind(this.mouseLeaveHandler);
200-
this.mouseLeaveHandler = null;
198+
if (this.pointerLeaveHandler) {
199+
browserEvents.unbind(this.pointerLeaveHandler);
200+
this.pointerLeaveHandler = null;
201201
}
202202
if (this.onKeyDownHandler) {
203203
browserEvents.unbind(this.onKeyDownHandler);
@@ -326,14 +326,26 @@ export class Menu {
326326
}
327327
}
328328

329-
// Mouse events.
329+
// Pointer events.
330330

331331
/**
332-
* Handles mouseover events. Highlight menuitems as the user hovers over them.
332+
* Handles pointermove events. Highlight menu items as the user hovers over
333+
* them.
333334
*
334-
* @param e Mouse event to handle.
335+
* @param e Pointer event to handle.
335336
*/
336-
private handleMouseOver(e: PointerEvent) {
337+
private handlePointerMove(e: PointerEvent) {
338+
// Check whether the pointer actually did move. Move events are triggered if
339+
// the element underneath the pointer moves, even if the pointer itself has
340+
// remained stationary. In the case where the pointer is hovering over
341+
// the menu but the user is navigating through the list of items via the
342+
// keyboard and causing items off the end of the menu to scroll into view,
343+
// a pointermove event would be triggered due to the pointer now being over
344+
// a new child, but we don't want to highlight the item that's now under the
345+
// pointer.
346+
const delta = Math.max(Math.abs(e.movementX), Math.abs(e.movementY));
347+
if (delta === 0) return;
348+
337349
const menuItem = this.getMenuItem(e.target as Element);
338350

339351
if (menuItem) {
@@ -359,11 +371,11 @@ export class Menu {
359371
if (oldCoords && typeof e.clientX === 'number') {
360372
const newCoords = new Coordinate(e.clientX, e.clientY);
361373
if (Coordinate.distance(oldCoords, newCoords) < 1) {
362-
// This menu was opened by a mousedown and we're handling the consequent
363-
// click event. The coords haven't changed, meaning this was the same
364-
// opening event. Don't do the usual behavior because the menu just
365-
// popped up under the mouse and the user didn't mean to activate this
366-
// item.
374+
// This menu was opened by a pointerdown and we're handling the
375+
// consequent click event. The coords haven't changed, meaning this was
376+
// the same opening event. Don't do the usual behavior because the menu
377+
// just popped up under the pointer and the user didn't mean to activate
378+
// this item.
367379
return;
368380
}
369381
}
@@ -375,22 +387,21 @@ export class Menu {
375387
}
376388

377389
/**
378-
* Handles mouse enter events. Focus the element.
390+
* Handles pointer enter events. Focus the element.
379391
*
380-
* @param _e Mouse event to handle.
392+
* @param _e Pointer event to handle.
381393
*/
382-
private handleMouseEnter(_e: PointerEvent) {
394+
private handlePointerEnter(_e: PointerEvent) {
383395
this.focus();
384396
}
385397

386398
/**
387-
* Handles mouse leave events. Blur and clear highlight.
399+
* Handles pointer leave events by clearing the active highlight.
388400
*
389-
* @param _e Mouse event to handle.
401+
* @param _e Pointer event to handle.
390402
*/
391-
private handleMouseLeave(_e: PointerEvent) {
403+
private handlePointerLeave(_e: PointerEvent) {
392404
if (this.getElement()) {
393-
this.blur();
394405
this.setHighlighted(null);
395406
}
396407
}

0 commit comments

Comments
 (0)