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] Invalid highlighted index when selecting last item with filterSelectedOptions #19637

Closed
jonatanklosko opened this issue Feb 9, 2020 · 6 comments · Fixed by #19923
Labels
bug 🐛 Something doesn't work 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.

Comments

@jonatanklosko
Copy link
Contributor

Current Behavior 😯

When filterSelectedOptions is enabled, selecting the last item means it disappears from the list, yet the highlight is not updated.

Expected Behavior 🤔

Not entirely sure, I can imagine one of the following:

  • clear highlight (i.e. set highlighted index to -1)
  • highlight the previous item (edge case: when the list has only 1 element, then the index should be set to -1)

Steps to Reproduce 🕹

Steps:

  1. Open this demo (taken directly from the documentation).
  2. Select the last item and hit enter.
  3. Hit arrow down (to open the popup) and then hit enter again (the highlighted index is still the old end-of-list and thus the selected value is undefined).
Tech Version
Material-UI v4.9.2

Notes

One simple solution is to reset the highlighted index when a new value is selected and filterSelectedOptions is used. Adding the following code

if (filterSelectedOptions) {
  setHighlightedIndex(-1);
}

below this: https://github.com/mui-org/material-ui/blob/f567b384f509d36b7e10f691e6b58538e7db8869/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js#L453-L456
does the job, although as mentioned above, it's not necessarily the expected behavior.

@oliviertassinari oliviertassinari added the component: autocomplete This is the name of the generic UI component, not the React module! label Feb 9, 2020
@oliviertassinari oliviertassinari added the bug 🐛 Something doesn't work label Feb 22, 2020
@oliviertassinari
Copy link
Member

The best fix I could find so far is

diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
index 19cea91d7..e3ee398af 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -464,6 +464,10 @@ export default function useAutocomplete(props) {
       }
     }

+    if (filterSelectedOptions && highlightedIndexRef.current === filteredOptions.length - 1) {
+      setHighlightedIndex(highlightedIndexRef.current - 1);
+    }
+
     resetInputValue(event, newValue);

     handleValue(event, newValue);

However, it's related to #19779 and #19705. I suspect we can use a single solution for these 3.

(I'm not sure that a reset of the index to -1 after selection is an option. It seems quite disruptive for keyboard users with filterSelectedOptions and disableCloseOnSelect).

@jonatanklosko
Copy link
Contributor Author

Looks like a good solution to me!

Not sure how this relates to #19779, as it has to do with initial scroll position, not highlight. For #19705 I'd say it makes sense to introduce resetHighlightOnSelect that jumps the highlight index to 0 (unless it's empty, otherwise -1), as both behaviors may be useful in some cases.

@oliviertassinari
Copy link
Member

oliviertassinari commented Feb 22, 2020

Here is a proposed solution for #19637, #19779, and #19637. Let us know if it works for your use case!

diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js
index bd9dc472f..c71b1b88f 100644
--- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js
+++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js
@@ -757,7 +757,6 @@ describe('<Autocomplete />', () => {
         />,
       );

-      fireEvent.keyDown(document.activeElement, { key: 'ArrowDown' });
       fireEvent.keyDown(document.activeElement, { key: 'ArrowDown' });
       fireEvent.keyDown(document.activeElement, { key: 'Enter' });

diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
index 9957ed76d..2200b20c1 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -396,6 +396,32 @@ export default function useAutocomplete(props) {
     changeHighlightedIndex('reset', 'next');
   }, [inputValue]); // eslint-disable-line react-hooks/exhaustive-deps

+  React.useEffect(() => {
+    if (!open) {
+      return;
+    }
+
+    const valueItem = multiple ? value[0] : value;
+
+    if (filteredOptions.length === 0 || valueItem == null) {
+      changeHighlightedIndex('reset', 'next');
+      return;
+    }
+
+    if (!filterSelectedOptions && valueItem != null) {
+      const itemIndex = findIndex(filteredOptions, optionItem =>
+        getOptionSelected(optionItem, valueItem),
+      );
+
+      setHighlightedIndex(itemIndex);
+      return;
+    }
+
+    if (highlightedIndexRef.current >= filteredOptions.length - 1) {
+      setHighlightedIndex(filteredOptions.length - 1);
+    }
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [value, open, filterSelectedOptions, multiple]);
+
   const handleOpen = event => {
     if (open) {
       return;

We will probably need to add one test case per issue for this logic (corresponding to each issue).

@oliviertassinari oliviertassinari added the good first issue Great for first contributions. Enable to learn the contribution process. label Feb 22, 2020
@jonatanklosko
Copy link
Contributor Author

Definitely works for me! The following test passes after applying the fix:

describe('prop: filterSelectedOptions', () => {
  it('when the last item is selected, highlights the new last item', () => {
    const { getByRole } = render(
      <Autocomplete
        {...defaultProps}
        filterSelectedOptions
        options={['one', 'two', 'three']}
        renderInput={params => <TextField {...params} autoFocus />}
      />,
    );

    function checkHighlightIs(expected) {
      expect(getByRole('listbox').querySelector('li[data-focus]')).to.have.text(expected);
    }

    fireEvent.keyDown(document.activeElement, { key: 'ArrowUp' });
    checkHighlightIs('three');
    fireEvent.keyDown(document.activeElement, { key: 'Enter' }); // selects the last option
    const input = getByRole('textbox');
    input.blur();
    input.focus(); // opens the listbox again
    checkHighlightIs('two');
  });
});

@captain-yossarian
Copy link
Contributor

@jonatanklosko I want to double check my PR. Could you please checkout on my branch and check if code is working as expected?
Thanks in advance

@jonatanklosko
Copy link
Contributor Author

Works as expected, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work 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.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants