What is the issue with the HTML Standard?
Popovers currently trigger their light dismiss behaviour any time there is a pointerup event that did not occur on the popover itself, a "nested" popover, or the declarative popovertarget element. This behaviour is great, but seems to be missing two important edge cases when the popover is triggered programmatically using either showPopover() or togglePopover().
Use case 1: Clicking on trigger element to toggle popover
The goal is to programmatically toggle a popover when a trigger button is clicked, using togglePopover(). This would be the programmatic equivalent of using the declarative popovertarget and popovertargetaction="toggle" attributes.
<button id="trigger">Trigger</button>
<div id="popover" popover>Popover</div>
<script>
trigger.addEventListener('click', () => {
popover.togglePopover({
source: button
});
});
</script>
Example CodePen
The expectation is that when a user clicks on the trigger button, the popover will toggle open and closed. Instead, the following is observed: When the popover is closed and the button is clicked, it opens as expected, but when the popover is already open and the button is clicked, the popover appears to stay open. When using the keyboard to "click" the button, the correct behaviour is observed. When this use case is reproduced using the popovertarget and popovertargetaction="toggle" attributes, the correct behaviour is observed.
What actually happens is that the popover is closing on pointerup, then immediately opening again on click. It seems like the browser is not respecting the programmatic source of the popover in the same way as it does for the declarative syntax. My expectation would be that the pointerup light dismiss behaviour would not trigger when clicking on the source element of the popover.
Sidenote: If you look at the event log in the example, you'll see the following sequence when you click on the trigger and the popover is already open:
pointerdown
beforetoggle (open → closed)
pointerup
click
beforetoggle (closed → open)
toggle (open → open)
I'm not 100% sure if this is the intended behaviour, but the toggle event with oldState="open" and newState="open" was rather unexpected. I was instead expecting 2 pairs of beforetoggle and toggle events. The first with open → closed and the second with closed → open.
Use case 2: Show popover on focus
The goal is to show the popover when the <input> is focused, and hide it when the <input> loses focus.
<input id="trigger">
<div id="popover" popover>Popover</div>
<script>
trigger.addEventListener('focus', () => {
popover.togglePopover({
force: true,
source: trigger
});
});
trigger.addEventListener('blur', () => {
popover.togglePopover({
force: false
});
});
</script>
Example CodePen
When the input is focused using keyboard navigation, everything works as expected. When the input is focused by clicking on it, the popover opens after pointer down, on focus, then gets light dismissed on pointerup.
Note: The focus event isn't really the important part here. It just nicely illustrates the problem. The trigger could be anything that happens between the pointerdown and matching pointerup events.
My expectation is that if a popover is opened during or after pointerdown, but before the pointerup event, then the popover should not get light dismissed by the pointerup. This suggestion was already brought up in #10905.
Conclusion
I believe both of these use cases should be trivial to implement using the existing programmatic API. Without them, the programmatic use of non-manual popovers is severely crippled. The declarative use of popovers may be the ideal way to use them, but it isn't always possible. In my experience this usually occurs in more complex components, where additional logic is needed to determine if and when the popover should be shown. Despite this additional logic, the popovers in these components still fall firmly into the "auto" or "hint" categories, and so we would like to leverage the fantastic light dismiss behaviours that are provided by the browser.
What is the issue with the HTML Standard?
Popovers currently trigger their light dismiss behaviour any time there is a
pointerupevent that did not occur on the popover itself, a "nested" popover, or the declarativepopovertargetelement. This behaviour is great, but seems to be missing two important edge cases when the popover is triggered programmatically using eithershowPopover()ortogglePopover().Use case 1: Clicking on trigger element to toggle popover
The goal is to programmatically toggle a popover when a trigger button is clicked, using
togglePopover(). This would be the programmatic equivalent of using the declarativepopovertargetandpopovertargetaction="toggle"attributes.Example CodePen
The expectation is that when a user clicks on the trigger button, the popover will toggle open and closed. Instead, the following is observed: When the popover is closed and the button is clicked, it opens as expected, but when the popover is already open and the button is clicked, the popover appears to stay open. When using the keyboard to "click" the button, the correct behaviour is observed. When this use case is reproduced using the
popovertargetandpopovertargetaction="toggle"attributes, the correct behaviour is observed.What actually happens is that the popover is closing on
pointerup, then immediately opening again onclick. It seems like the browser is not respecting the programmaticsourceof the popover in the same way as it does for the declarative syntax. My expectation would be that thepointeruplight dismiss behaviour would not trigger when clicking on thesourceelement of the popover.Sidenote: If you look at the event log in the example, you'll see the following sequence when you click on the trigger and the popover is already open:
I'm not 100% sure if this is the intended behaviour, but the
toggleevent witholdState="open"andnewState="open"was rather unexpected. I was instead expecting 2 pairs ofbeforetoggleandtoggleevents. The first withopen → closedand the second withclosed → open.Use case 2: Show popover on focus
The goal is to show the popover when the
<input>is focused, and hide it when the<input>loses focus.Example CodePen
When the input is focused using keyboard navigation, everything works as expected. When the input is focused by clicking on it, the popover opens after pointer down, on
focus, then gets light dismissed onpointerup.Note: The
focusevent isn't really the important part here. It just nicely illustrates the problem. The trigger could be anything that happens between thepointerdownand matchingpointerupevents.My expectation is that if a popover is opened during or after
pointerdown, but before thepointerupevent, then the popover should not get light dismissed by thepointerup. This suggestion was already brought up in #10905.Conclusion
I believe both of these use cases should be trivial to implement using the existing programmatic API. Without them, the programmatic use of non-manual popovers is severely crippled. The declarative use of popovers may be the ideal way to use them, but it isn't always possible. In my experience this usually occurs in more complex components, where additional logic is needed to determine if and when the popover should be shown. Despite this additional logic, the popovers in these components still fall firmly into the "auto" or "hint" categories, and so we would like to leverage the fantastic light dismiss behaviours that are provided by the browser.