open-ui icon indicating copy to clipboard operation
open-ui copied to clipboard

[menu] Should an event be fired when a <menuitem> is selected? Which event?

Open mfreed7 opened this issue 2 months ago • 10 comments

Since the main purpose of a menu of <menuitem>s is to offer items that do something when you select them, developers need a reliable event to attach behaviors to. This might sound obvious, but upon further thought, the obvious candidate, click, has a problem:

For a traditional "click" on the menu item with a mouse or touch, or in most cases with a keyboard activation via spacebar or Enter (platform dependent), a click will already be fired on the <menuitem> (or a descendant) by the platform. However, it's desirable to follow the implemented behavior of customizable-<select>, which also allows the user to mousedown on a button that invokes a menu, drag the mouse over to the newly-opened menu, and release the mouse (mouseup) over the submenu item they'd like to select. (E.g. this is a standard pattern for all menus on MacOS.) In that case, the pointerevents spec says that the click event gets fired on the nearest common inclusive ancestor of the mousedown and mouseup elements, and that's never going to be the <menuitem> that the user selected.

There seem to be two options:

  1. Modify the spec'd behavior for the click event for this specific case, and re-target it to the selected <menuitem>.
  2. Create a new event, like activated (needs bikeshedding).

#1 would nicely solve the problem, and would meet most developers where they are, which is to expect click to work. The downside is that it would require some "weird" changes to the pointerevents spec, which might be counterintuitive in some cases.

#2 would avoid the spec weirdness, and it'd be fairly easy to understand menuitem.addEventListener('activate',()=>()). However, it introduces a footgun, whereby developers do menuitem.addEventListener('click',... instead, and do not test the mousedown-drag-mouseup behavior, so they don't notice that it doesn't work.

Given the footgun, I think my preference is #1: change the behavior of click.

As a side-note, this issue also applies to customizable-<select>: if you mousedown-drag-mouseup, you won't get a click on the selected <option>. This could be a source of bugs there also.

mfreed7 avatar Oct 28 '25 20:10 mfreed7

fwiw, based on what you've outlined i'd also lean towards option 1.

i've also pinged Patrick to take a look at this issue. I imagine you may have already pinged Rob as well?

scottaohara avatar Oct 28 '25 21:10 scottaohara

Btw slightly tangential but is it mouse down and mouse up or pointer down and pointerup? Reason I ask is because I think chromium currently uses pointer for its tracking here.

lukewarlow avatar Oct 29 '25 00:10 lukewarlow

suggest opening a mirror issue for this on https://github.com/w3c/pointerevents/issues - incidentally, also intrigued by the mention of

follow the implemented behavior of customizable-<select>

and whether that is also already covered by current PE behaviour (and if not, making an issue about that too)

patrickhlauke avatar Oct 29 '25 09:10 patrickhlauke

Btw slightly tangential but is it mouse down and mouse up or pointer down and pointerup? Reason I ask is because I think chromium currently uses pointer for its tracking here.

just on that, I believe it's best to talk about/mention pointerdown/pointerup even in prose just to be more inclusive of non-mouse pointers where this may still be relevant (such as stylus)

patrickhlauke avatar Oct 29 '25 09:10 patrickhlauke

Thanks for all the comments here!

fwiw, based on what you've outlined i'd also lean towards option 1.

Great, thanks.

i've also pinged Patrick to take a look at this issue. I imagine you may have already pinged Rob as well?

No, I didn't yet, but good idea: @flackr

suggest opening a mirror issue for this on https://github.com/w3c/pointerevents/issues - incidentally, also intrigued by the mention of

Great idea! Done: https://github.com/w3c/pointerevents/issues/560

and whether that is also already covered by current PE behaviour (and if not, making an issue about that too)

I think we should discuss both here and there, since the concerns are the same I think.

Btw slightly tangential but is it mouse down and mouse up or pointer down and pointerup? Reason I ask is because I think chromium currently uses pointer for its tracking here.

just on that, I believe it's best to talk about/mention pointerdown/pointerup even in prose just to be more inclusive of non-mouse pointers where this may still be relevant (such as stylus)

I meant specifically mouse here, because (AFAIK) this explicitly isn't a pattern on touchscreen. Touchdown-drag-touchup is usually a scroll gesture. In most native mobile UI that we've seen, menus and selects don't allow this pattern for selecting items with a touchscreen.

mfreed7 avatar Oct 29 '25 17:10 mfreed7

non-mouse pointers where this may still be relevant (such as stylus)

Ahh I just got this. What's the correct term for {something}events that means precision pointer devices only, i.e. not fingers on a touchscreen.

mfreed7 avatar Oct 29 '25 17:10 mfreed7

you could write it more generally, say pointer, but then add something like "unless the interaction interferes with platform conventions" or similar. in PE, we've tried to make a distinction between direct and indirect manipulation (though that doesn't necessarily just mean mouse vs touchscreen) https://w3c.github.io/pointerevents/#dfn-direct-manipulation

patrickhlauke avatar Oct 29 '25 17:10 patrickhlauke

For customizable select, there isn't any special behavior implemented in pointer events, at least not yet. This issue does not apply as much for customizable select because it's not a typical pattern for authors to hook up their own activation code for the user selecting an option directly to the option element itself - you'd typically add a change event listener to the select element rather than a click event listener on the option element.

Customizable select does support the click and drag behavior to choose an option, but the click is fired on the select element rather than the option element because UIEvents currently says to fire click events on the nearest common ancestor of the pointerdown/mousedown and pointerup/mouseup elements: https://www.w3.org/TR/uievents/#:~:text=SHOULD%20fire%20click%20and%20dblclick%20events%20on%20the%20nearest%20common%20inclusive%20ancestor%20when%20the%20associated%20mousedown%20and%20mouseup%20event%20targets%20are%20different

josepharhar avatar Oct 30 '25 17:10 josepharhar

The Open UI Community Group just discussed [menu] Should an event be fired when a <menuitem> is selected? Which event?, and agreed to the following:

  • RESOLVED: just make `click` work in this case, and for customizable-<select> also
The full IRC log of that discussion <frehner> masonf: on Menus: you build a menu, every item in the menu is an action you take,
<lwarlow> q+
<frehner> ... on Mac and custom select, you can mousedown, hold it down and move to a submenu, and let go on sub-item, and that sub-item was selected.
<frehner> ... but that doesn't work for pointer events because it will only add a click event on the common ancestor
<sarah> q+
<frehner> options: 1) new event
<frehner> ... 2) redefine how click works in this case and custom select
<frehner> ... preference is 2
<jarhar> q?
<lwarlow> https://developer.mozilla.org/en-US/docs/Web/API/Element/DOMActivate_event ;)
<frehner> ... if you're used to using "click", but we do 1), then there's confusion
<gregwhitworth> ack lwarlow
<frehner> lwarlow: bring back dom activate event :)
<frehner> jarhar: in the case of options over the picker
<frehner> masonf: you have to move the mouse in that case
<frehner> lwarlow: re: behavior, make click work
<jarhar> q+
<frehner> ... new event unlikely to be supported by whatwg here
<frehner> ... making click work here seems fine
<frehner> ... is menu item activation different than click event?
<frehner> masonf: command stuff works, but dev click-related stuff wouldn't.
<frehner> lwarlow: special case it then
<gregwhitworth> q?
<gregwhitworth> ack sarah
<frehner> sarah: +1 having click work
<frehner> ... preventDefault not working would be weird
<frehner> ... is this only for mousedown -> submenu -> mouseup? Or menu items in the same menu?
<frehner> masonf: correct
<frehner> sarah: what about drag and drop?
<frehner> jarhar: a draggable menu item that creates a submenu.
<gregwhitworth> ack jarhar
<frehner> ... +1 for click
<frehner> ... I hope it's not too hard, but good luck ;)
<frehner> ... and hopefully doesn't break custom select
<frehner> masonf: +1, I want to make this work for custom select too
<domfarolino> q+
<frehner> lwarlow: for select, that seems weird to have click on menuitems
<gregwhitworth> ack domfarolino
<frehner> domfarolino: question about internal workings of mousedown/mouseup
<frehner> lwarlow: would click event fire before that stuff, and it's cancellable?
<frehner> masonf: no click happens after
<frehner> lwarlow: activation behavior on new click or mouse?
<frehner> masonf: if you cancel click event, it's already happened
<frehner> ... I may end up rewiring how it works to use the synthetic event
<gregwhitworth> q?
<lwarlow> +1
<masonf> Proposed resolution: just make `click` work in this case, and for customizable-<select> also
<frehner> domfarolino: do we want to clarify timing here?
<frehner> masonf: I want to come back after writing tests
<domfarolino> +1
<masonf> RESOLVED: just make `click` work in this case, and for customizable-<select> also
<sarah> +1

css-meeting-bot avatar Oct 30 '25 18:10 css-meeting-bot

Taking inspiration from base-select behavior, I tried to write some spec text.

Drag selection applies when pointerdown target is outside <option> and pointerup is inside.
(it does not seem to apply any other restriction on pointerdown target, for example being inside <select>)

spec: https://w3c.github.io/pointerevents/#the-click-auxclick-and-contextmenu-events:~:text=Define%20target%20as%20follows%3A


try 1

Otherwise (event is a click or auxclick event for which userEvent is a pointerup event that was dispatched uncaptured)
let commonInclusiveAncestor be the nearest common inclusive ancestor of the corresponding pointerdown and pointerup targets in the DOM at the moment event is being dispatched.

If event is not a click event, or event pointerType is not mouse, let target be commonInclusiveAncestor.

Otherwise let newTarget be the nearest inclusive ancestor of pointerup target,
where newTarget tagName is OPTION or MENUITEM,
and newTarget is not an inclusive ancestor of pointerdown target,
at the moment event is being dispatched.

If newTarget exists, let target be newTarget,
otherwise let target be commonInclusiveAncestor.


try 2

Otherwise let newTarget be the nearest inclusive ancestor of pointerup target,
where drag-click-target CSS property evaluates to true,
at the moment event is being dispatched.

If newTarget exists, let target be newTarget,
otherwise let target be commonInclusiveAncestor.

drag-click-target: auto | up;

  • auto evaluates to false.
  • up evaluates to true if the element is an inclusive ancestor of pointerup target,
    and is not an inclusive ancestor of pointerdown target.

Should this also apply to auxclick events?

sb3nder avatar Dec 11 '25 16:12 sb3nder