Skip to content

Commit 1ccba13

Browse files
m4theushwoliviertassinari
authored andcommitted
[Autocomplete] Add blurOnSelect prop (#18827)
1 parent 3a8f0ea commit 1ccba13

File tree

5 files changed

+113
-2
lines changed

5 files changed

+113
-2
lines changed

docs/pages/api/autocomplete.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
2727
| <span class="prop-name">autoComplete</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the portion of the selected suggestion that has not been typed by the user, known as the completion string, appears inline after the input cursor in the textbox. The inline completion string is visually highlighted and has a selected state. |
2828
| <span class="prop-name">autoHighlight</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the first option is automatically highlighted. |
2929
| <span class="prop-name">autoSelect</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the selected option becomes the value of the input when the Autocomplete loses focus unless the user chooses a different option or changes the character string in the input. |
30+
| <span class="prop-name">blurOnSelect</span> | <span class="prop-type">'mouse'<br>&#124;&nbsp;'touch'<br>&#124;&nbsp;bool</span> | <span class="prop-default">false</span> | Control if the input should be blurred when an option is selected:<br>- `false` the input is not blurred. - `true` the input is always blurred. - `touch` the input is blurred after a touch event. - `mouse` the input is blurred after a mouse event. |
3031
| <span class="prop-name">classes</span> | <span class="prop-type">object</span> | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
3132
| <span class="prop-name">clearOnEscape</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, clear all values when the user presses escape and the popup is closed. |
3233
| <span class="prop-name">clearText</span> | <span class="prop-type">string</span> | <span class="prop-default">'Clear'</span> | Override the default text for the *clear* icon button.<br>For localization purposes, you can use the provided [translations](/guides/localization/). |

packages/material-ui-lab/src/Autocomplete/Autocomplete.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
218218
autoComplete = false,
219219
autoHighlight = false,
220220
autoSelect = false,
221+
blurOnSelect = false,
221222
classes,
222223
className,
223224
clearOnEscape = false,
@@ -459,6 +460,15 @@ Autocomplete.propTypes = {
459460
* a different option or changes the character string in the input.
460461
*/
461462
autoSelect: PropTypes.bool,
463+
/**
464+
* Control if the input should be blurred when an option is selected:
465+
*
466+
* - `false` the input is not blurred.
467+
* - `true` the input is always blurred.
468+
* - `touch` the input is blurred after a touch event.
469+
* - `mouse` the input is blurred after a mouse event.
470+
*/
471+
blurOnSelect: PropTypes.oneOfType([PropTypes.oneOf(['mouse', 'touch']), PropTypes.bool]),
462472
/**
463473
* Override or extend the styles applied to the component.
464474
* See [CSS API](#css) below for more details.

packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,8 @@ describe('<Autocomplete />', () => {
564564
fireEvent.click(input);
565565

566566
const listbox = getByRole('listbox');
567-
const firstItem = listbox.querySelector('li');
568-
fireEvent.click(firstItem);
567+
const firstOption = listbox.querySelector('li');
568+
fireEvent.click(firstOption);
569569

570570
expect(handleChange.args[0][1]).to.equal('one');
571571
});
@@ -779,4 +779,77 @@ describe('<Autocomplete />', () => {
779779
expect(handleInputChange.args[0][2]).to.equal('reset');
780780
});
781781
});
782+
783+
describe('prop: blurOnSelect', () => {
784+
it('[blurOnSelect=true] should blur the input when clicking or touching options', () => {
785+
const options = [{ name: 'foo' }];
786+
const { getByRole, queryByTitle } = render(
787+
<Autocomplete
788+
options={options}
789+
getOptionLabel={option => option.name}
790+
renderInput={params => <TextField {...params} autoFocus />}
791+
blurOnSelect
792+
/>,
793+
);
794+
const textbox = getByRole('textbox');
795+
let firstOption = getByRole('option');
796+
expect(textbox).to.have.focus;
797+
fireEvent.click(firstOption);
798+
expect(textbox).to.not.have.focus;
799+
800+
const opener = queryByTitle('Open');
801+
fireEvent.click(opener);
802+
expect(textbox).to.have.focus;
803+
firstOption = getByRole('option');
804+
fireEvent.touchStart(firstOption);
805+
fireEvent.click(firstOption);
806+
expect(textbox).to.not.have.focus;
807+
});
808+
809+
it('[blurOnSelect="touch"] should only blur the input when an option is touched', () => {
810+
const options = [{ name: 'foo' }];
811+
const { getByRole, queryByTitle } = render(
812+
<Autocomplete
813+
options={options}
814+
getOptionLabel={option => option.name}
815+
renderInput={params => <TextField {...params} autoFocus />}
816+
blurOnSelect="touch"
817+
/>,
818+
);
819+
const textbox = getByRole('textbox');
820+
let firstOption = getByRole('option');
821+
fireEvent.click(firstOption);
822+
expect(textbox).to.have.focus;
823+
824+
const opener = queryByTitle('Open');
825+
fireEvent.click(opener);
826+
firstOption = getByRole('option');
827+
fireEvent.touchStart(firstOption);
828+
fireEvent.click(firstOption);
829+
expect(textbox).to.not.have.focus;
830+
});
831+
832+
it('[blurOnSelect="mouse"] should only blur the input when an option is clicked', () => {
833+
const options = [{ name: 'foo' }];
834+
const { getByRole, queryByTitle } = render(
835+
<Autocomplete
836+
options={options}
837+
getOptionLabel={option => option.name}
838+
renderInput={params => <TextField {...params} autoFocus />}
839+
blurOnSelect="mouse"
840+
/>,
841+
);
842+
const textbox = getByRole('textbox');
843+
let firstOption = getByRole('option');
844+
fireEvent.touchStart(firstOption);
845+
fireEvent.click(firstOption);
846+
expect(textbox).to.have.focus;
847+
848+
const opener = queryByTitle('Open');
849+
fireEvent.click(opener);
850+
firstOption = getByRole('option');
851+
fireEvent.click(firstOption);
852+
expect(textbox).to.not.have.focus;
853+
});
854+
});
782855
});

packages/material-ui-lab/src/useAutocomplete/useAutocomplete.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ export interface UseAutocompleteProps {
3535
* a different option or changes the character string in the input.
3636
*/
3737
autoSelect?: boolean;
38+
/**
39+
* Control if the input should be blurred when an option is selected:
40+
*
41+
* - `false` the input is not blurred.
42+
* - `true` the input is always blurred.
43+
* - `touch` the input is blurred after a touch event.
44+
* - `mouse` the input is blurred after a mouse event.
45+
*/
46+
blurOnSelect?: 'touch' | 'mouse' | true | false;
3847
/**
3948
* If `true`, clear all values when the user presses escape and the popup is closed.
4049
*/

packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export default function useAutocomplete(props) {
7171
autoComplete = false,
7272
autoHighlight = false,
7373
autoSelect = false,
74+
blurOnSelect = false,
7475
clearOnEscape = false,
7576
debug = false,
7677
defaultValue,
@@ -666,9 +667,25 @@ export default function useAutocomplete(props) {
666667
setHighlightedIndex(index, 'mouse');
667668
};
668669

670+
const isTouch = React.useRef(false);
671+
672+
const handleOptionTouchStart = () => {
673+
isTouch.current = true;
674+
};
675+
669676
const handleOptionClick = event => {
670677
const index = Number(event.currentTarget.getAttribute('data-option-index'));
671678
selectNewValue(event, filteredOptions[index]);
679+
680+
if (
681+
blurOnSelect === true ||
682+
(blurOnSelect === 'touch' && isTouch.current) ||
683+
(blurOnSelect === 'mouse' && !isTouch.current)
684+
) {
685+
inputRef.current.blur();
686+
}
687+
688+
isTouch.current = false;
672689
};
673690

674691
const handleTagDelete = index => event => {
@@ -814,6 +831,7 @@ export default function useAutocomplete(props) {
814831
id: `${id}-option-${index}`,
815832
onMouseOver: handleOptionMouseOver,
816833
onClick: handleOptionClick,
834+
onTouchStart: handleOptionTouchStart,
817835
'data-option-index': index,
818836
'aria-disabled': disabled,
819837
'aria-selected': selected,

0 commit comments

Comments
 (0)