paper-dropdown-menu icon indicating copy to clipboard operation
paper-dropdown-menu copied to clipboard

paper-dropdown-menu does not close when lose focus

Open victorhugom-zz opened this issue 8 years ago • 15 comments

On chrome the paper-dropdown-menu element does not close when lose focus. Works on Edge and IE.

<paper-dropdown-menu class="flex" style="margin-right:10px" label="Cidade" always-float-label="[[alwaysFloatLabel]]" on-paper-dropdown-close="_addressChanged">
    <paper-listbox class="dropdown-content" attr-for-selected="item" selected="{{address.city}}">
        <template is="dom-repeat" items="[[cities]]" as="city">
            <paper-item item="[[city]]">{{city}}</paper-item>
        </template>
     </paper-listbox>
</paper-dropdown-menu>

The full element is at: https://github.com/victorhugom/acre-address-form or http://victorhugom.github.io/acre-address-form

Browsers Affected

  • [x] Chrome
  • [ ] Edge
  • [ ] IE 11
  • [ ] IE 10

Edit: The issue occours only on touch enabled devices The original paper-dropdown-menu demo has the same problem

victorhugom-zz avatar Apr 11 '16 20:04 victorhugom-zz

/cc @danbeam

I am observing the same behavior with touch devices. As a result of this bug multiple drop-down menus on the same page can be open at the same time, see attachment from the demo page.

multiple_dropdown_menus

freshp86 avatar Apr 20 '16 20:04 freshp86

/cc @tjsavage

danbeam avatar Apr 20 '16 21:04 danbeam

@cdata: Any updates here? This is affecting Chromium's new settings, see https://bugs.chromium.org/p/chromium/issues/detail?id=604685#c3.

freshp86 avatar May 10 '16 17:05 freshp86

The drop-down menus can be closed by tapping somewhere else with finger, but clicking with the mouse elsewhere leaves the menu open.

wq9 avatar May 11 '16 06:05 wq9

A simpler repro of the original issue with just a keyboard: https://jsfiddle.net/j4rfjasf/

  1. Tap Button 1 to focus
  2. Tab to the paper-dropdown-menu
  3. Press Enter or Down/Up to open the menu
  4. Tab to Button 2

Expected: menu closes. Actual: menu stays open.

Chrome 52.0.2743.32

mgiuffrida avatar Jun 12 '16 14:06 mgiuffrida

I'm looking at this issue now.

JanMiksovsky avatar Jun 30 '16 17:06 JanMiksovsky

I wanted to provide an update on this. I’m nearly ready to submit a fix, but at the last minute am having issues getting tests to pass in Firefox/IE under WCT. The tests pass fine in regular browser instances, but focus problems prevent the automated tests from passing in both Firefox and IE at the same time. In a bit of unfortunate timing, I’m about to leave on an extended break until mid-August. So for now, I wanted to provide a record of progress so far, and explain the problem and solution to both others and my future self.

Like many focus issues, this has proven tricky to get right while minimizing impact on related components.

While the problem shows up in paper-dropdown-menu, I believe iron-dropdown is the correct place to fix this. @cdata and I also discussed fixing this in iron-overlay-behavior, but he would prefer to see this fixed for dropdown-style overlays, or at least at first, in order to avoid causing regressions in dialog-style overlays like paper-dialog. We could move this issue to iron-dropdown, although (as noted below), it also requires changes in iron-overlay-behavior and iron-overlay-manager.

What behavior do we want?

Clearly, the existing behavior is a bug: the user can move focus away from the dropdown while leaving the dropdown menu open. But there are at least two ways we could fix this:

  1. Have a dropdown implicitly close itself when it loses focus, or
  2. Have a dropdown prevent the user from tabbing away when the dropdown is open.

Looking at both OS combo boxes and the HTML select elements, it’s easy to find examples of both behaviors. Windows and the Windows browsers Edge/IE use solution 1, as does Firefox. Mac OS X and the Safari and Chrome browsers use solution 2. (This simple jsBin can be used to check behaviors.)

We could solicit input from more sources, but to me, solution 1 feels better. It follows the Principle of Least Astonishment regarding the behavior of the Tab key: the Tab key should always move to the next focusable element. If an element gets the focus and won’t let go (solution 2), that’s surprising, and hence less desirable. The surprise is particularly problematic in accessibility contexts. I tried navigating the above jsBin in Safari with Apple Navigator, and opened a select element. At that point, the Tab key would not move to the next element — but the screen reader provided no feedback as to what was going on. If a blind user accidentally opened the dropdown, it would be very difficult to understand what was happening on the screen. Without being able to see the screen, they would have to guess they were stuck somewhere, and try pressing the Esc key in order to continue.

Given the above, I recommend we pursue solution 1: a dropdown that loses focus should auto-close itself. If others strongly prefer solution 2, please advise.

A secondary question is what form of close we want here: a regular dropdown close or a cancel. Looking at the OSes and browsers for reference, it appears that losing focus effectively results in a close. So, that’s the model I’ve pursued.

Note that paper-dropdown-menu does not implicitly update the dropdown value to match the currently-selected item when close() is called programmatically. Hence, the expected behavior here will be: if the user opens a paper-dropdown-menu, changes the selection, then tabs away, the selected value will not update. This is counter the example of the standard select element. However, this would be a separate issue with paper-dropdown-menu / paper-menu-button that could be pursued separately. The solution here makes that issue no worse.

The solution is in two parts…

Part 1: change to iron-overlay-behavior

First, a change to iron-overlay-behavior that enables components like iron-dropdown to refine the existing restoreFocus behavior. Interested parties can see this change on this fork of iron-overlay-behavior.

The existing restoreFocus behavior in iron-overlay-manager prevents an iron-dropdown from letting go of the focus. If the user presses Tab, and focus moves outside of the dropdown, the new iron-dropdown PR will auto-close the dropdown. However, in removeOverlay, iron-overlay-manager will try to restore the focus back to the element that had the focus before the dropdown opened. That is, iron-overlay-manager is fighting to prevent the behavior we want in iron-dropdown.

The underlying issue is that iron-overlay-manager is applying a single restore behavior to both dropdown-style and dialog-style overlays. We want to preserve the existing behavior for dialog-style overlays, but give dropdown-style overlays more sophisticated behavior. Specifically: at the point a dropdown closes, if the focus is nowhere inside the dropdown, then iron-overlay-manager should not restore focus.

To discriminate between these cases, this has removeOverlay invoke a new method _restoreFocusToNode. The default iron-overlay-behavior implementation of this method is to always focus the indicated node. This default behavior can then be overridden in dropdown-style overlays components like iron-dropdown. In this case, it allows iron-dropdown to perform an extra test: if the focus is no longer anywhere inside the dropdown, it does not restore focus to the indicated node, and lets the focus leave the dropdown as the user wants.

Part 2: change to iron-dropdown

The second part requires teaching iron-dropdown how to track when it has completely lost focus. The change can be viewed on this fork of iron-dropdown.

This is tricky, for a number of reasons:

  • The way the dropdown family of components is currently designed, as soon as the dropdown opens, it usually (but not always) puts focus on some element inside the dropdown.
  • What we really want is some form of “aggregate blur”: an event that triggers only when the focus moves entirely outside of a portion of the tree. There’s no such event, so we have to roll our own means of determining when this has happened.
  • To do this, when an iron-dropdown opens, it sets up a blur listener on whatever element has gotten the focus. If that element blurs, it checks to see whether the focus has moved outside the dropdown. If it did, we’ve got our aggregate blur. If, however, the focus stayed within the dropdown, we start listening to blur on the newly-focused element, and stop listening to blur on the previously-focused element.
  • At the point a blur event arrives, the activeElement will be the document body. The new activeElement won’t be known until the browser’s processed the blur. (The blur event has a relatedTarget property that does point to the element that has picked up the focus focus, but that is not yet supported in Firefox.) So the check to see if focus is still inside the dropdown waits for the new activeElement to be set. An async microtask appears to be sufficient for this.
  • As a side effect of using a microtask, the iron-overlay-opened event will fire first, and then the check for focus will happen. That’s slightly unfortunate, as conceptually the check for focus is part of opening the dropdown.
  • Another complication: it’s possible to create a dropdown with no focusable element. In that case, the focus will remain on whatever element triggered the opening of the dropdown (e.g., a button). In iron-overlay-manager parlance, the focus will remain on the restoreFocusNode. So our check to see whether where the focus ended up has to also handle this case. The auto-close behavior only kicks in if focus is no longer anywhere within the dropdown or on the restoreFocusNode.
  • Yet another complication: In the case where an iron-dropdown itself does not take the focus, and there’s no focus trigger element, there’s nothing that blurs when the focus moves elsewhere. That makes it impossible for us to auto-close the dropdown.

A specific case of the last issue comes up in Safari/Firefox, where a click/tap on a trigger element (e.g., button) does not automatically focus the tapped element. (Chrome/Edge/IE do automatically focus a tapped element.) So, in Safari/Firefox, the auto-close feature only works if: the dropdown is invoked from the keyboard, or if the dropdown itself can take focus. When viewing the iron-dropdown demo in Safari/Firefox and using the mouse to click the “Basic” button, pressing Tab will not auto-close the dropdown, because neither the button nor dropdown will have focus. Using the keyboard to trigger that button will mean the focus is on the button, so pressing Tab will auto-close the dropdown as expected. Also, using the mouse to click on “Bottom-left Aligned”, then pressing Tab, will also work as expected because that dropdown can receive the focus.

It’s worth noting that, in the originally-filed bug on paper-dropdown-menu, the auto-close feature should always work as expected in Safari/Firefox, even when using tab/click to open the dropdown, because the paper-input (or plain input) should always be able to take the focus.

Summary

Together, these changes produce the desired behavior. Opening an iron-dropdown or paper-dropdown-menu, then pressing Tab, will implicitly closes the dropdown and let the focus move to the next element.

Note: if you press Shift+Tab to move backward in the tab order, the dropdown will auto-close, but the focus will end up on the button that triggered the dropdown. You’ll need to press Shift+Tab again to actually move to the previous element in the tab order. That’s a little surprising, but probably acceptable, especially in light of the focus games that would be required to remove eliminate that behavior.

Next steps

As noted above, the fixes above are essentially complete. All unit tests pass in all browsers when run directly — the remaining problem at this point is getting unit tests to pass in Firefox or IE under WCT. Browsers appear to handle focus/blur events differently if the window is not active (as often happens when testing multiple browsers under WCT), and both Firefox and IE seem particularly prone to this problem. I’ve found a workaround for Firefox that doesn’t work in IE, and vice versa, and I’m stuck trying to find a single workaround that works for both browsers.

If anyone wants to comment on the UI design questions here, or on the implementation approach, please go ahead. If someone knows the answer to the secret of testing real focus/blur events under WCT, please feel free to take a shot at getting the unit tests to pass. As noted above, I won’t be able to work on this for a while, but will pick this up when I’m back in mid-August.

JanMiksovsky avatar Jul 09 '16 06:07 JanMiksovsky

FWIW the native

valdrinkoshi avatar Sep 20 '16 17:09 valdrinkoshi

There actually is no standard behavior. A survey of what happens when a user presses Tab with an open select or similar dropdown control:

  • Chrome/Safari: nothing happens
  • Firefox: dropdown closes
  • Edge/IE: focus navigates to the next element.

It's my expectation that the Edge/IE behavior is the least surprising to users. It seems highly beneficial for the user to be able to reliably move the focus by always being able to use the Tab key. The existing Chrome/Safari behavior feels broken or unresponsive in that regard. Firefox seems like it's splitting the difference between the two positions: better than nothing, but still a little unexpected.

Given the inconsistency of select behavior across browsers, I think the Edge/IE behavior would be the most helpful to emulate.

JanMiksovsky avatar Sep 20 '16 22:09 JanMiksovsky

iron-dropdown could override _onCaptureFocus and check if the focus went outside the dropdown itself:

// Overridden from Polymer.IronOverlayBehaviorImpl
_onCaptureFocus: function(event) {
  // Focus went outside the dropdown
  if (!this.contains(event.target)) {
     // This will trigger `iron-overlay-canceled` and give
     // a chance to the user to prevent the canceling if necessary
     this.cancel(event);
  } else {
     Polymer.IronOverlayBehaviorImpl._onCaptureFocus.apply(this, arguments);
  }
}

This should give enough degrees of freedom to the end user to tailor the desired behavior, as everyone could listen to the iron-overlay-canceled and prevent the closing from happening if needed (similarly to how it happens for Esc and click outside).

valdrinkoshi avatar Sep 20 '16 23:09 valdrinkoshi

For the record, my hastily-reconstructed summary of select dropdowns across browsers was inaccurate. A more accurate table of behavior:

Chrome (Mac) and Mac Safari: nothing happens. Edge and Chrome (Windows): dropdown closes, but focus remains on the element. Firefox (Mac and Windows) and IE: dropdown closes and focus moves to next element.

I still believe that letting the focus move to the next element is the least surprising behavior, and most consistent with the user's model for how Tab works.

JanMiksovsky avatar Sep 21 '16 18:09 JanMiksovsky

@JanMiksovsky I think letting focus move would be fine for Chrome's use cases.

danbeam avatar Sep 21 '16 19:09 danbeam

Update on this: since there are multiple desired behaviors on scroll, we decided to introduce a new property scrollAction on iron-overlay-behavior which can be used to either block the scrolling from happening outside the overlay, close the overlay when scroll happens outside, or refit it. This is being done in https://github.com/PolymerElements/iron-overlay-behavior/pull/234 and https://github.com/PolymerElements/iron-dropdown/pull/125, and once it gets merged you should be able to setup the behavior like this:

<iron-dropdown scroll-action="cancel"></iron-dropdown>

valdrinkoshi avatar May 30 '17 21:05 valdrinkoshi

btw, Chrome's settings page (and most chrome:// URLs) no longer use <paper-dialog> but the native <dialog> itself

danbeam avatar May 30 '17 22:05 danbeam

I've tested this with the latest elements versions and can reproduce only the bug described by @mgiuffrida (aka only with arrow navigation to open another menu) http://jsbin.com/yozejed/2/edit?html,output

Maybe the easiest fix for this is to make iron-dropdown "unique", as in "there can be only 1 iron-dropdown opened", similar to how it is for paper-toast (there is only 1 opened per time)..

valdrinkoshi avatar Mar 08 '18 01:03 valdrinkoshi