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

[Autocomplete] Improve popup open logic #18815

Closed
2 tasks done
Janpot opened this issue Dec 13, 2019 · 31 comments · Fixed by #19901
Closed
2 tasks done

[Autocomplete] Improve popup open logic #18815

Janpot opened this issue Dec 13, 2019 · 31 comments · Fixed by #19901
Labels
component: autocomplete This is the name of the generic UI component, not the React module! good first issue Great for first contributions. Enable to learn the contribution process. new feature New feature or request

Comments

@Janpot
Copy link
Member

Janpot commented Dec 13, 2019

  • The issue is present in the latest release.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Current Behavior 😯

When an Autocomplete is active and I switch tabs, the menu is opened when I come back

Expected Behavior 🤔

The state of the UI is the same as before I switch tabs.

Steps to Reproduce 🕹

Steps:

  1. go to https://material-ui.com/components/autocomplete/#combo-box
  2. select an option
  3. switch to another tab
  4. switch back
  5. observe

mui

Context 🔦

I think we're hitting the same as https://stackoverflow.com/questions/10657176/changing-browser-tabs-undesirably-fires-the-focus-event-especially-in-google-ch

Your Environment 🌎

Tech Version
Material-UI v4.7.2
React
Browser chrome 78.0.3904.108
TypeScript
etc.
@Janpot Janpot changed the title [Autocomplete] switching to a window with a focused Autocomplete opens menu [Autocomplete] switching to a window with an active Autocomplete opens menu Dec 13, 2019
@oliviertassinari
Copy link
Member

What's wrong with this behavior?

@Janpot
Copy link
Member Author

Janpot commented Dec 13, 2019

When I switch to another tab, I expect to find my page in the exact same state that I left it when I come back. If the menu of the active element is closed, it is because I wanted it to be closed. switching a tab is not an action that should be interpreted as "the user wants the menu of the active element to be open". It's confusing and so it's bad UX.
I have a page where a filter happens automatically when the value of an autocomplete changes. So it happens fairly regularly that one of those is still active but closed at the time. There isn't really anything else to do on this page besides scrolling. I often come to this page in an existing tab to find a random menu open and it's kind of weird.

@oliviertassinari oliviertassinari added the component: autocomplete This is the name of the generic UI component, not the React module! label Dec 13, 2019
@oliviertassinari
Copy link
Member

oliviertassinari commented Dec 13, 2019

@Janpot One more question. Do you want to popup to open on keyboard focus (not taking the mouse into account)? For instance, when using Tab to navigate the page.

@Janpot
Copy link
Member Author

Janpot commented Dec 13, 2019

I'd probably consider that action equivalent to a mouse click on the component, so yes. But I have to admit I'm far from a UX/accessibility expert, this is just my gut feeling. I don't really mind if it doesn't open though.

edit

Actually, scratch that, I'm 5 minutes further and I already changed my mind. Need to think more about this. Maybe it should behave more like a button that opens a modal. You wouldn't want the button to open the modal when tab navigating to it. I feel like when using tab I'm just navigating around, to get to the component I want to manipulate. It feels a bit intrusive when that starts popping up menus of elements that I want to skip. Possibly menus that cover op the element I'm trying to navigate to.

@Janpot
Copy link
Member Author

Janpot commented Dec 13, 2019

@oliviertassinari How about:

  1. keyboard focus by tab => menu stays closed
  2. Enter key or start typing => open menu
    1. Enter key => select item + close menu
    2. Esc key => close menu

That seems to align with https://webaim.org/techniques/keyboard/

Probably something to look into as well: I was checking how <Select /> handles this and it seems to behave strange when you press spacebar when it's focused. It briefly flashes the popover and the readonly one even scrolls the page. The native select actually opens the menu on spacebar, it doesn't open on Enter though, while the Simple Select does.

edit
just found out that on simple select I can hold spacebar to open the menu and use arrows to select an option, releasing spacebar will select that option. Is this intended? It feels counter intuitive.

@oliviertassinari
Copy link
Member

oliviertassinari commented Dec 13, 2019

How about:

Set disableOpenOnFocus and you will be good. Alternatively, you can control the open state and implement whatever logic works best for your context (onOpen/onClose).

I was checking how <Select />

Duplicate of another issue, fixed in master.

@Janpot
Copy link
Member Author

Janpot commented Dec 13, 2019

Set disableOpenOnFocus and you will be good.

If I go by the demo, that also disables it on click, so not an option.

fixed in master.

Almost, the native select can also select options with spacebar after it is opened. The simple select can't.

@oliviertassinari
Copy link
Member

oliviertassinari commented Dec 13, 2019

If I go by the demo, that also disables it on click, so not an option.

@Janpot So you are looking for the same default behavior as react-select?

Almost, the native select can also select options with spacebar after it is opened. The simple select can't.

This is macOS specific, it won't happen on Linux nor Windows. We decided to go against it: #18754 (comment).

@oliviertassinari
Copy link
Member

oliviertassinari commented Dec 13, 2019

I think that a good solution would be to add a second reason argument to the onOpen and onClose callbacks so you can implement something custom, and on Material-UI side, to wait for more feedback to change the default tradeoff.

@Janpot
Copy link
Member Author

Janpot commented Dec 13, 2019

So you are looking for the same default behavior as react-select?

I'm checking their docs pages and they open on click but not on keyboard focus, they also don't open menu for an active control after switching tabs. so wrt to this behavior, yes. I also think this would be the most sensible default for material-ui.
I think it's especially bad accessibility to open the menu on keyboard focus by default. And I think material ui should mimic its own Select component, which is open on click but not on keyboard focus.

I saw they also don't allow typing a space in an empty control, wrt to that behavior, no.

@oliviertassinari
Copy link
Member

@Janpot Would the reason approach work for you?

@oliviertassinari oliviertassinari added the ready to take Help wanted. Guidance available. There is a high chance the change will be accepted label Dec 13, 2019
@Janpot
Copy link
Member Author

Janpot commented Dec 13, 2019

Yes that would probably work if I can differentiate between an actual mouse click and another focus event.

You don't agree that the current behavior is not ideal accessibility-wise?

@oliviertassinari
Copy link
Member

oliviertassinari commented Dec 13, 2019

I believe the current logic will allow the make a difference between a focus and a touch/mouse click.

It seems that the Autocomplete behaves like the google.com's search, so I don't think that it's very important.
For a11y, https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html is a good resource to look at.

@Janpot
Copy link
Member Author

Janpot commented Dec 13, 2019

For me google.com's search doesn't open when I move keyboard focus to it. Neither does it open when it's active and I switch tabs. It does however also not open before I start typing, even when I click it.

is a good resource to look at.

I'm specifically referring to focus navigation:

  1. Predictability of movement: Usability of a keyboard interface is heavily influenced by how readily users can guess where focus will land after a navigation key is pressed.

Point being that it's hard to predict where focus will land if a menu is covering some of those elements where focus may land.

Anyway, not that important to me 🙂. If I can implement it in userland that's fine, I just disagree with the default.

@oliviertassinari
Copy link
Member

Right, you need to fill in a value in the search bar at the top of the page to get the Google's suggestions. It's only then that you will get a display of the popup on focus and an open back behavior when switching tabs.

I'm not sure to follow the focus covering issue. The popup should only open if the input is the active element. Does it behave differently in your case?

Yes, I would like to get a couple of more user's feedback before we change this aspect.

@Janpot
Copy link
Member Author

Janpot commented Dec 13, 2019

oh ok, I was testing the one in the middle of the page when there's no search results, that one seems to behave differently.

In any case, I can't get that google box you are looking at in the same state as the matui one (active but closed popup) so I can't compare the tab switching behavior. Which ultimately is the main problem I'd like to solve, the keyboard accessibility is way low on my priority list.

However, the gmail searchbar has a autocomplete which can be closed while still staying active, and I can confirm it doesn't open when switching tabs. neither does it open for me on keyboard focus.

Edit:

Ok, I think I see now, you can look at it as a textfield with a menu. or as a select component with a search box. disableOpenOnFocus switches between the two approaches. I can live with that. But not with the tab switching behavior. I'd be happy with a reason parameter to get around this tab switching.

@oliviertassinari

This comment has been minimized.

@Janpot
Copy link
Member Author

Janpot commented Dec 13, 2019

@oliviertassinari could the following strategy work?
When an autocomplete gets focus, add a focus event handler to the window. When that event on the window fires (when switched back to the tab), set a marker to ignore one focus event on the autocomplete. remove the window focus event handler when the autocomplete blurs.

@oliviertassinari
Copy link
Member

oliviertassinari commented Dec 14, 2019

@Janpot We have handled tab switch at two occasions in the past:

But with more hindsight, I'm not sure what should be the expected behavior for the Autocomplete. Google's product seems to be highly inconsistent with this, it's almost like the behavior inherits from other decisions, the fruit of chance.

@oliviertassinari oliviertassinari added the new feature New feature or request label Dec 14, 2019
@Janpot
Copy link
Member Author

Janpot commented Dec 14, 2019

@oliviertassinari Ok, I think I have a solution: https://codesandbox.io/s/material-demo-ofp6l
It seems to be quite straightforward after all, and I can implement it in userland. Thanks for your guidance. If you don't feel like this should be the behavior of the Autocomplete component then feel free to close this issue.

@oliviertassinari
Copy link
Member

oliviertassinari commented Dec 14, 2019

@Janpot Great. I think that we should wait.

However, the reason argument could move forward. I think that we can leave this issue open until somebody else faces the problem and needs it.

@eps1lon
Copy link
Member

eps1lon commented Dec 27, 2019

When I switch to another tab, I expect to find my page in the exact same state that I left it when I come back

This is browser behavior though. When leaving the tab everything gets blurred. When you tab back focus events get fired again

This should not be applied to the autocomplete only. Every component doing "something" when focused would need this fix.

Considering how <input type="text" list="some-datalist-idref" /> behaves I'd much rather change the default of disableOpenOnFocus to true and handle click separately. The native variant does not open on focus. Only when you click in the textbox. I suspect this is also better for screen readers since you don't get this wall of text announced just by moving focus.

@oliviertassinari oliviertassinari removed the ready to take Help wanted. Guidance available. There is a high chance the change will be accepted label Dec 28, 2019
@oliviertassinari oliviertassinari changed the title [Autocomplete] switching to a window with an active Autocomplete opens menu [Autocomplete] Change open on focus logic Dec 28, 2019
@oliviertassinari oliviertassinari changed the title [Autocomplete] Change open on focus logic [Autocomplete] Improve popup open logic Dec 28, 2019
@oliviertassinari
Copy link
Member

@eps1lon His sounds like a great plan 👍. I have noticed that Chrome autocomplete and autofill list boxes behave the same way.

One note regarding the prop, if we change the default to true, then we should change the prop name disableOpenOnFocus -> openOnFocus.

@oliviertassinari oliviertassinari added the ready to take Help wanted. Guidance available. There is a high chance the change will be accepted label Dec 28, 2019
@haseebdaone
Copy link
Contributor

haseebdaone commented Jan 10, 2020

Think I might give this a go, read through the whole thing and from what I've understood I just need to change disableOpenOnFocus = true is that correct?

@oliviertassinari
Copy link
Member

oliviertassinari commented Jan 10, 2020

@haseebdaone This is not a one-line change, it would have had the "good first issue" tag if it was the case.

@haseebdaone
Copy link
Contributor

@oliviertassinari If you don't mind me asking what other changes are needed?

@sashberd

This comment has been minimized.

@meastes

This comment has been minimized.

@oliviertassinari
Copy link
Member

@haseebdaone See: #18815 (comment)
@sashberd I have marked your comment as off-topic. If you have a reproduction, a new issue would be great. It's not supposed to behave this way, nor I can successfully reproduce it on https://material-ui.com/components/autocomplete/.
@meastes I have marked your comment as off-topic. Autocomplete and Select are two very distinct components. Select aims to be a light replacement for a native <select>. Autocomplete aims to be feature-rich.

@oliviertassinari
Copy link
Member

@haseebdaone I had a closer look at the changes. I think that a change, in this order, will solve our problem. Note that the tests will also need to be updated :)

diff --git a/docs/src/pages/components/autocomplete/GoogleMaps.tsx b/docs/src/pages/components/autocomplete/GoogleMaps.tsx
index 516010ff4..ac8ef4e60 100644
--- a/docs/src/pages/components/autocomplete/GoogleMaps.tsx
+++ b/docs/src/pages/components/autocomplete/GoogleMaps.tsx
@@ -109,7 +109,6 @@ export default function GoogleMaps() {
       autoComplete
       includeInputInList
       freeSolo
-      disableOpenOnFocus
       renderInput={params => (
         <TextField
           {...params}
diff --git a/docs/src/pages/components/autocomplete/Playground.tsx b/docs/src/pages/components/autocomplete/Playground.tsx
index c3cae2e6f..874f43bdf 100644
--- a/docs/src/pages/components/autocomplete/Playground.tsx
+++ b/docs/src/pages/components/autocomplete/Playground.tsx
@@ -88,10 +88,10 @@ export default function Playground() {
       />
       <Autocomplete
         {...defaultProps}
-        id="disable-open-on-focus"
-        disableOpenOnFocus
+        id="open-on-focus"
+        openOnFocus
         renderInput={params => (
-          <TextField {...params} label="disableOpenOnFocus" margin="normal" fullWidth />
+          <TextField {...params} label="openOnFocus" margin="normal" fullWidth />
         )}
       />
       <Autocomplete
diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js
index 463ff0f2f..fd0097cdf 100644
--- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js
+++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js
@@ -252,7 +252,6 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
     disableCloseOnSelect = false,
     disabled = false,
     disableListWrap = false,
-    disableOpenOnFocus = false,
     disablePortal = false,
     filterOptions,
     filterSelectedOptions = false,
@@ -276,6 +275,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
     onInputChange,
     onOpen,
     open,
+    openOnFocus = false,
     openText = 'Open',
     options,
     PaperComponent = Paper,
@@ -566,10 +566,6 @@ Autocomplete.propTypes = {
    * If `true`, the list box in the popup will not wrap focus.
    */
   disableListWrap: PropTypes.bool,
-  /**
-   * If `true`, the popup won't open on input focus.
-   */
-  disableOpenOnFocus: PropTypes.bool,
   /**
    * Disable the portal behavior.
    * The children stay within it's parent DOM hierarchy.
@@ -691,6 +687,10 @@ Autocomplete.propTypes = {
    * Control the popup` open state.
    */
   open: PropTypes.bool,
+  /**
+   * If `true`, the popup will open on input focus.
+   */
+  openOnFocus: PropTypes.bool,
   /**
    * Override the default text for the *open popup* icon button.
    *
diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.d.ts b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.d.ts
index e616b2b1a..81db8c032 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.d.ts
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.d.ts
@@ -69,10 +69,6 @@ export interface UseAutocompleteCommonProps<T> {
    * If `true`, the list box in the popup will not wrap focus.
    */
   disableListWrap?: boolean;
-  /**
-   * If `true`, the popup won't open on input focus.
-   */
-  disableOpenOnFocus?: boolean;
   /**
    * A filter function that determines the options that are eligible.
    *
@@ -154,6 +150,10 @@ export interface UseAutocompleteCommonProps<T> {
    * Control the popup` open state.
    */
   open?: boolean;
+  /**
+   * If `true`, the popup will open on input focus.
+   */
+  openOnFocus?: boolean;
   /**
    * Array of options.
    */
diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
index 3c01bb4b3..bb08631b4 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -1,7 +1,7 @@
 /* eslint-disable no-constant-condition */
 import React from 'react';
 import PropTypes from 'prop-types';
-import { setRef, useEventCallback, useControlled, ownerDocument } from '@material-ui/core/utils';
+import { setRef, useEventCallback, useControlled } from '@material-ui/core/utils';

 // https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
 // Give up on IE 11 support for this feature
@@ -86,12 +86,12 @@ export default function useAutocomplete(props) {
     autoSelect = false,
     blurOnSelect = false,
     clearOnEscape = false,
+    componentName = 'useAutocomplete',
     debug = false,
     defaultValue = props.multiple ? [] : null,
     disableClearable = false,
     disableCloseOnSelect = false,
     disableListWrap = false,
-    disableOpenOnFocus = false,
     filterOptions = defaultFilterOptions,
     filterSelectedOptions = false,
     freeSolo = false,
@@ -105,13 +105,13 @@ export default function useAutocomplete(props) {
     multiple = false,
     onChange,
     onClose,
-    onOpen,
     onInputChange,
+    onOpen,
     open: openProp,
+    openOnFocus = false,
     options,
     selectOnFocus = !props.freeSolo,
     value: valueProp,
-    componentName = 'useAutocomplete',
   } = props;

   const [defaultId, setDefaultId] = React.useState();
@@ -655,7 +655,7 @@ export default function useAutocomplete(props) {
   const handleFocus = event => {
     setFocused(true);

-    if (!disableOpenOnFocus && !ignoreFocus.current) {
+    if (openOnFocus && !ignoreFocus.current) {
       handleOpen(event);
     }
   };
@@ -692,10 +692,6 @@ export default function useAutocomplete(props) {
     }

     if (newValue === '') {
-      if (disableOpenOnFocus) {
-        handleClose(event);
-      }
-
       if (!disableClearable && !multiple) {
         handleValue(event, null);
       }
@@ -783,8 +779,7 @@ export default function useAutocomplete(props) {
   };

   const handleInputMouseDown = event => {
-    const doc = ownerDocument(inputRef.current);
-    if (inputValue === '' && (!disableOpenOnFocus || inputRef.current === doc.activeElement)) {
+    if (inputValue === '') {
       handlePopupIndicator(event);
     }
   };
@@ -968,10 +963,6 @@ useAutocomplete.propTypes = {
    * If `true`, the list box in the popup will not wrap focus.
    */
   disableListWrap: PropTypes.bool,
-  /**
-   * If `true`, the popup won't open on input focus.
-   */
-  disableOpenOnFocus: PropTypes.bool,
   /**
    * A filter function that determins the options that are eligible.
    *
@@ -1051,6 +1042,10 @@ useAutocomplete.propTypes = {
    * Control the popup` open state.
    */
   open: PropTypes.bool,
+  /**
+   * If `true`, the popup will open on input focus.
+   */
+  openOnFocus: PropTypes.bool,
   /**
    * Array of options.
    */

@oliviertassinari oliviertassinari added good first issue Great for first contributions. Enable to learn the contribution process. and removed ready to take Help wanted. Guidance available. There is a high chance the change will be accepted labels Feb 22, 2020
@haseebdaone
Copy link
Contributor

I'll give this a go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: autocomplete This is the name of the generic UI component, not the React module! good first issue Great for first contributions. Enable to learn the contribution process. new feature New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants