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] Fixed useAutocomplete nextFocus #39289

Conversation

adamhylander
Copy link
Contributor

Fixed validOptionIndex method in useAutocomplete.js to account for disabled edge cases.

Closes #39281

@mui-bot
Copy link

mui-bot commented Oct 3, 2023

Netlify deploy preview

https://deploy-preview-39289--material-ui.netlify.app/

Bundle size report

Details of bundle changes (Toolpad)
Details of bundle changes

Generated by 🚫 dangerJS against 3c47966

if (!nextFocusDisabled) {
return -1;
}
nextFocus = 0;
Copy link
Contributor

@ttlpta ttlpta Oct 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @adamhylander ,
i just wonder why you not return nextFocus immediately like this
image
this way maybe can help reduce 1 loop "while"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ttlpta,

The reason we set nextFocus = 0/nextFocus = filteredOptions.length is because we wish to run the while loop until an option that is not disabled is found. We essentially wrap the focus around to the end/beginning of the list, depending on direction, and continue to look for an option that isn't disabled. I hope this helps. Let me know if you have any more questions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @adamhylander
Thank you for ur answer,

I still dont get your point about we need to run the while loop until an option that is not disabled is found.
Because as your condition, you have already check the direction and the next index of item should be focus. I mean the condition if (direction === 'next' && nextFocus === filteredOptions.length) and else if (direction === 'previous' && nextFocus === -1) is specify to case the disable option is on first/last position of the list. So the next focus index only should be 0, or length of the list.
Btw, Can you send me a test case will be cause of your concern? I have tried some case, but it still works fine when we return imd.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey again @ttlpta

Here is index.tsx from the playground environment I used to test the code if you wanna try your suggestion out yourself:

import * as React from 'react';
import { StyledEngineProvider } from '@mui/material/styles';
import Autocomplete from '@mui/material/Autocomplete'
import TextField from '@mui/material/TextField'
import Stack from '@mui/material/Stack'

export default function Playground() {
  return (
    <StyledEngineProvider injectFirst>
      <Stack spacing={5}>
        <Autocomplete
          id="disabled-options-demo"
          options={[1, 2, 3, 4, 5, 6, 7]}
          getOptionDisabled={(option) => option % 2 !== 0}
          sx={{ width: 300 }}
          renderInput={(params) => (
            <TextField {...params} label="Disabled options" />
          )}
        />
        <Autocomplete
          id="all-enabled"
          options={[1, 2, 3, 4, 5, 6, 7]}
          sx={{ width: 300 }}
          renderInput={(params) => <TextField {...params} label="All enabled" />}
        />
      </Stack>
    </StyledEngineProvider>
  );
}

The if statements if (direction === 'next' && nextFocus === filteredOptions.length) and if (direction === 'previous' && nextFocus === -1) are not used to specify the case of when the disabled option is on first/last position, it is only used to specify that we are at the beginning or end of the list of options and that we wanna move "out of bounds" (i.e. being at filteredOptions.length[0] and pressing the up key, or being at filteredOptions.length[filteredOptions.length] and pressing the down key).

I would be happy to share my thougts/reasoning on Discord as well if you'd like, Nonetheless, I hope my code is a bit clearer now.

Copy link
Contributor

@ttlpta ttlpta Oct 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @adamhylander

Perfect, you are correct, i am too focus on the case the first/ last option has been disabled.

Thank for you answer.

I think you should fix all the cicd step to pass the review from author.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ttlpta

Great! Understanding the logic of nextFocus was confusing for me too. I was wondering what I need to do to pass the last tests? Can you help me with this?

@mj12albert mj12albert added the component: autocomplete This is the name of the generic UI component, not the React module! label Oct 3, 2023
@adamhylander
Copy link
Contributor Author

Hey, @mj12albert

I don't fully understand how to pass the last checks. Can you help me with this?

@mj12albert mj12albert self-requested a review October 10, 2023 02:16
@mj12albert
Copy link
Member

@adamhylander It seems under some conditions (not 100% sure but it seems whenever autoHighlight is set + using any keyboard input), an infinite loop is created which is causing some of Joy UI's Autocomplete tests to hang (internally it uses Base's useAutocomplete)

It can be reproed in a sandbox, but I couldn't figure out how to get the sandbox to restart after it hung the first time 😆

You can repro it in the local playground like this:

import * as React from 'react';
import { CssVarsProvider } from '@mui/joy';
import Autocomplete from '@mui/joy/Autocomplete';

export default function Playground() {
  return (
    <CssVarsProvider>
      <Autocomplete
        autoHighlight
        freeSolo
        placeholder="Combo box"
        options={['one', 'two']}
        sx={{ width: 300 }}
        open
      />
    </CssVarsProvider>
  );
}
  1. Type oo into the input
  2. It will hang after inputting the first o

The failing tests relate to prop: autoHighlight:

  1. should set the focus on the first item
  2. should set the focus on the first item when possible
  3. should work with filterSelectedOptions too

@adamhylander
Copy link
Contributor Author

adamhylander commented Oct 10, 2023

@adamhylander It seems under some conditions (not 100% sure but it seems whenever autoHighlight is set + using any keyboard input), an infinite loop is created which is causing some of Joy UI's Autocomplete tests to hang (internally it uses Base's useAutocomplete)

It can be reproed in a sandbox, but I couldn't figure out how to get the sandbox to restart after it hung the first time 😆

You can repro it in the local playground like this:

import * as React from 'react';
import { CssVarsProvider } from '@mui/joy';
import Autocomplete from '@mui/joy/Autocomplete';

export default function Playground() {
  return (
    <CssVarsProvider>
      <Autocomplete
        autoHighlight
        freeSolo
        placeholder="Combo box"
        options={['one', 'two']}
        sx={{ width: 300 }}
        open
      />
    </CssVarsProvider>
  );
}
1. Type `oo` into the input

2. It will hang after inputting the first `o`

The failing tests relate to prop: autoHighlight:

1. `should set the focus on the first item`

2. `should set the focus on the first item when possible`

3. `should work with filterSelectedOptions too`

Hello @mj12albert

I fixed this particular issue with the if statement

if (filteredOptions.length === 0) {
    return -1;
}

However my implementation still fails 11 more tests. I did not know that the components were so heavily coupled as this change really shouldn't impact that much. I am not sure how to solve my pull request without making the code hideous, or changing most of the components that uses useAutocomplete. I will sit with this issue a bit longer though.

Thank you so much for the help and repro!

@ZeeshanTamboli
Copy link
Member

@adamhylander @mj12albert I'm closing this PR and favoring #38788, which was opened earlier and includes a correct fix with tests. You can review it in this CodeSandbox: https://codesandbox.io/s/thirsty-wave-jg325z?file=/Demo.tsx. @adamhylander, thank you for your effort!

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!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Autocomplete] Pressing ArrowDown key behaves incorrectly when remaining options are disabled
5 participants