Typing into the auto-complete and pressing enter should select something, such as the suggested option. The suggested option to select should be highlighted.
For example type in Good into the playground and press enter, nothing gets selected.
Lets say I have a text matching score when searching for Good as following:
The Good, the Bad and the Ugly = 0.3
Goodfellas = 0.8
I would want Goodfellas to be highlighted and selected when enter is pressed.
The current auto-complete logic makes suggesting an option difficult.
Passing in a highlightedValue/suggestedValue would make the above possible. Set the first value in the list to be highlighted if one is not provided.
The threshold for auto focus should probably be configurable because in certain cases one would not want motivate user to use doubtful matches
@esseswann, What i am thinking is that autocomplete does not know anything about the threshold. The only api change I would make is a new prop called highlightedValue or suggestedValue and then any concept of a threshold would live in the developers component.
Sounds reasonable though it should probably be something about focused. Also there should be a discussion on weather this prop should allow focusing on only the first item
@KamalAman So, you are supporting the position of a autoHighlight value to true by default, for the combo box cases.
Yeah, ok, looking at more implementations, UI libraries & final products, this sounds like a sensible default. So we can 1. turn autoHighlight = false into autoHighlight = !props.freeSolo and 2. account for the change in https://material-ui.com/components/autocomplete/#creatable. What do you guys think?
@oliviertassinari, I think autoHighlight = !props.freeSolo makes sense so that it is on by default when freeSolo is false.
However, the main feature i would like to see is being able to pass in a highlightedValue such that the value is highlighted (and scrolled to, which is easy) if it is deemed to be a better choice by some external logic.
@KamalAman Ok, I think that you are making a compelling use case for:
What do you think of this API to anticipate for a future case when somebody will need to control the highlighted option?
https://github.com/mui-org/material-ui/issues/20588#issuecomment-614725233
Regarding the implementation, how do you envision it?
I can't vote for this suggestion enough. To fit my use case I would amend one tiny bit. If the input value _completely_ matches one of the options - the matched option would be the one selected. So either an additional option that performs this requested function ONLY when there's an exact match or this same option with a preference to "choose" the exact match first. Consider an autocomplete color picker. User types in "Blue" to see options for "Light Blue" and "Alice Blue" etc. But pressing Tab (or Enter) should auto select "Blue" since it was a valid choice to begin with. State lookup would be the same way. User entering "Iowa" might simply finish typing the word and press tab. It's literally counter-intuitive to have to arrow-down or otherwise select "Iowa" from the list when I've already typed it in. In my humble experience, most autocomplete fields work this way.
@oliviertassinari
I envision that the implementation for the highlighted value uses a partially controlled state where:
If inputValue changes, the highlighedValue prop takes priority.
@CliffChaney while i agree that is should be possible that if the user types in iowa, then Iowa should be highlighted. However I think this could be an implementation detail rather than adding more props to the component such adding props for case sensitivity and partial matches.
With that being said, changing the default for autoHighlight as Olivier said would help with a 0-configuration use case.
To control the Good for Goodfellas use case i should suggest a use lazy implementation as such,
const top100Films = [...]
export default function ComboBoxWithControlledHighlightedValues() {
const [inputValue, onInputChange ] = useState('');
const highlightedValue = useMemo(() => {
const regex = new RegExp(`^${inputValue}`, 'i')
return top100Films.find(({ title }) => regex.test(title))
}, [inputValue, top100Films])
return (
<Autocomplete
id="combo-box-demo"
options={top100Films}
getOptionLabel={(option) => option.title}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Combo box" variant="outlined" />}
inputValue={inputValue}
onInputChange={onInputChange}
highlightedValue={highlightedValue}
/>
);
}
@KamalAman Ok, so if I summarize, the priority is to move forward with https://github.com/mui-org/material-ui/issues/20852#issuecomment-623022304. Then the developers that want to improve the DX, can sort the filtered options, to place the best match at the top of the list, like Google is doing (moving the options instead of moving the highlight).
Regarding moving the highlight, I think that we could consider this API:
const top100Films = [...]
export default function ComboBoxWithControlledHighlightedValues() {
const [inputValue, setInputChange] = useState('');
const [highlight, setHighlight] = useState(-1);
return (
<Autocomplete
id="combo-box-demo"
options={top100Films}
getOptionLabel={(option) => option.title}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Combo box" variant="outlined" />}
inputValue={inputValue}
onInputChange={(_, newInputValue) => { setInputChange(newInputValue); }}
highlight={highlight}
onHighlightChange={(_, newHighlight, reason) => {
if (reason === 'input') {
const regex = new RegExp(`^${inputValue}`, 'i')
setHighlight(top100Films.find(({ title }) => regex.test(title)));
return;
}
setHighlight(newHighlight);
}}
/>
);
}
A note on the implementation, we would very likely want to keep the highlight state inside a ref if not controlled. Would the above work for you guys?
@KamalAman Thank you for the suggestion. But I did a bad job of explaining my issue/suggestion.
My problem is NOT with the Autocomplete selection algorithm. My problem is the final selection that is recorded in the input box. I was merely trying to relate my use case with what I perceived to be the feature being suggested. Please let me try again.
In my "Iowa" example, assume that I typed the case exactly. User tabs into Autocomplete field for US States and types "Iowa". In my implementation, the Autocomplete list will now show only one option - and the textbox portion of the control will show "Iowa" as well.
Now, the user - seeing "Iowa" in the input box - simply presses Tab to move to the next field.
Perhaps I have some option configured wrong. But in my usage of Autocomplete, the entered value of "Iowa" simply goes away and the user is moved to the next field.
In order to actually _select_ the value of "Iowa" the user is forced to tap-on or click "Iowa" in the dropdown - and _then_ they can tab to the next field.
That is the part that is counterintuitive to me. I can see the value in the input box portion of the control. I can see that I've entered "Iowa". I can see from the dropdown portion of the control that "Iowa" is a valid option. But I cannot use it without officially selecting it from the list (e.g. down-arrow, clicking it, etc).
Thinking about every autocomplete implementation that I've used - literally, every one - this is how they work. I'm not forced to select the option from the dropdown list. I can simply type it and my input is saved. There is no requirement to select from the list of suggestions.
I should amend that I would expect everything else about Autocomplete to work as-is. So, if the user does NOT type a valid value from the autocomplete list - and say, freeSolo is false - then it would behave essentially as it does now (i.e. tabbing out would clear/reject the input).
Hopefully that makes more sense. And in the likely event that I've implemented something incorrectly, I will gladly accept direction! Thank you!
@CliffChaney What's your use case for the component? A search field (like Google search)? A combo box (like react-select)? It sounds like you are looking for a search field, set freeSolo={true} and you will get the behavior you need.
@oliviertassinari Thank you for your interest! No. I don't want freeSolo={true}. Though, I will likely end up using that - and then adding validation. I want a combo box. I haven't tried react-select, so I cannot comment on it.
I want a combo box implementation where the user is forced to select from the suggested list. One literal implementation I have right now is an Autocomplete implementation of all CSS colors. I want the user to be able to type "Blue" and see all the "Blue" options. Autocomplete works PERFECT for that... Where it gets confusing to the user is when they type in "Azure" and are forced to click on it before continuing. In a similar use case, I have a long list of product codes. Again, I need the Autocomplete functionality to help new users find the code they're after and ensure they pick the right one. (I've implemented renderOption to show a description with the code.) But for experienced users Autocomplete gets in the way. They know the 6 digit code they want and would like to simply type it in and tab to the next field.
@CliffChaney autoSelect={true} autoHighlight={true} in this case?
@oliviertassinari That didn't quite work for me. But it's damn close.
Problem with that is when you need to make changes. When I tab back into that field and type a new value - unless I select it from the list - when I press tab, the old value is put back for me.
All that said... I JUST found a solution that leverages something I didn't know about getOptionLabel. Hopefully this isn't something that will get "fixed". And it might not work everywhere, but it worked for me.
Fortunately, all my autocomplete lists are structures (e.g. { code, value }).
What I JUST discovered with freeSolo={true} is that getOptionLabel is called a final time when the user exits the field. And on the final time, it passes getOptionLabel the entered string instead of an object from my options list. Honestly, this feels like it shouldn't work this way. But I was able to leverage it.
In case someone comes looking for this, here's my code for getOptionLabel:
getOptionLabel={(op) => {
if (op.name) return (op.name)
const tranCode = tranCodeList.find((tc) => tc.name === op)
if (tranCode) return tranCode.name
return ''
}}
If it's passed an object - as expected - it returns the label. If it's passed a string - the user must be blurring the field - check to make sure the option is valid and return it. Invalid options return an empty string.
This appears to work for me! It can also be easily modified to allow for partial matches, ignore-case comparisons, etc...
@oliviertassinari Yeah, it seems like that api would work for us.
Yes keeping the highlight state inside a ref if not controlled makes perfect sense.
We don't want to pull the highlighted value to the top of the list since the list is sorted/grouped.
@KamalAman Great. I have added the "good to take" GitHub issue label as we now have a clear resolution path. If you wish to work on a pull request, feel free to :).
I would like to start working on this.
To summarize:
1) Change default for autoHighlight autoHighlight = !props.freeSolo
2) Update demo
3) Handle new prop highlight. Or should it be named defaultHighlighted, mirroring the internal ref that's being used for autoHighlight?
Note that this issue has a dependency on #22170 and #22073, we discuss similar problems.
@mnajdova Do you want to help @NoNonsense126 on this issue? The issue was open a long time ago, I have forgotten the context.
Most helpful comment
@oliviertassinari Yeah, it seems like that api would work for us.
Yes keeping the highlight state inside a ref if not controlled makes perfect sense.
We don't want to pull the highlighted value to the top of the list since the list is sorted/grouped.