Skip to content

Commit 779dbcf

Browse files
[Autocomplete] Improve focus logic (#18286)
1 parent 29f20ea commit 779dbcf

File tree

9 files changed

+103
-19
lines changed

9 files changed

+103
-19
lines changed

docs/src/pages/components/autocomplete/CustomizedHook.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ const Listbox = styled('ul')`
124124

125125
export default function CustomizedHook() {
126126
const {
127-
getComboboxProps,
127+
getRootProps,
128128
getInputLabelProps,
129129
getInputProps,
130130
getTagProps,
@@ -143,7 +143,7 @@ export default function CustomizedHook() {
143143

144144
return (
145145
<div>
146-
<div {...getComboboxProps()}>
146+
<div {...getRootProps()}>
147147
<Label {...getInputLabelProps()}>Customized hook</Label>
148148
<InputWrapper ref={setAnchorEl} className={focused ? 'focused' : ''}>
149149
{value.map((option, index) => (

docs/src/pages/components/autocomplete/CustomizedHook.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ const Listbox = styled('ul')`
124124

125125
export default function CustomizedHook() {
126126
const {
127-
getComboboxProps,
127+
getRootProps,
128128
getInputLabelProps,
129129
getInputProps,
130130
getTagProps,
@@ -143,7 +143,7 @@ export default function CustomizedHook() {
143143

144144
return (
145145
<div>
146-
<div {...getComboboxProps()}>
146+
<div {...getRootProps()}>
147147
<Label {...getInputLabelProps()}>Customized hook</Label>
148148
<InputWrapper ref={setAnchorEl} className={focused ? 'focused' : ''}>
149149
{value.map((option: FilmOptionType, index: number) => (

docs/src/pages/components/autocomplete/UseAutocomplete.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const useStyles = makeStyles(theme => ({
3636
export default function UseAutocomplete() {
3737
const classes = useStyles();
3838
const {
39-
getComboboxProps,
39+
getRootProps,
4040
getInputLabelProps,
4141
getInputProps,
4242
getListboxProps,
@@ -49,7 +49,7 @@ export default function UseAutocomplete() {
4949

5050
return (
5151
<div>
52-
<div {...getComboboxProps()}>
52+
<div {...getRootProps()}>
5353
<label className={classes.label} {...getInputLabelProps()}>
5454
useAutocomplete
5555
</label>

docs/src/pages/components/autocomplete/UseAutocomplete.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const useStyles = makeStyles((theme: Theme) =>
3838
export default function UseAutocomplete() {
3939
const classes = useStyles();
4040
const {
41-
getComboboxProps,
41+
getRootProps,
4242
getInputLabelProps,
4343
getInputProps,
4444
getListboxProps,
@@ -51,7 +51,7 @@ export default function UseAutocomplete() {
5151

5252
return (
5353
<div>
54-
<div {...getComboboxProps()}>
54+
<div {...getRootProps()}>
5555
<label className={classes.label} {...getInputLabelProps()}>
5656
useAutocomplete
5757
</label>

docs/src/pages/components/autocomplete/autocomplete.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ The Autocomplete component uses this hook internally.
5555
import useAutocomplete from '@material-ui/lab/useAutocomplete';
5656
```
5757

58-
- 📦 [4 kB gzipped](/size-snapshot).
58+
- 📦 [4.5 kB gzipped](/size-snapshot).
5959

6060
{{"demo": "pages/components/autocomplete/UseAutocomplete.js", "defaultCodeOpen": false}}
6161

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
210210
const PopperComponent = disablePortal ? DisablePortal : PopperComponentProp;
211211

212212
const {
213-
getComboboxProps,
213+
getRootProps,
214214
getInputProps,
215215
getInputLabelProps,
216216
getPopupIndicatorProps,
@@ -282,7 +282,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
282282
},
283283
className,
284284
)}
285-
{...getComboboxProps()}
285+
{...getRootProps()}
286286
{...other}
287287
>
288288
{renderInput({

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,4 +497,56 @@ describe('<Autocomplete />', () => {
497497
expect(handleChange.callCount).to.equal(1);
498498
});
499499
});
500+
501+
describe('prop: autoComplete', () => {
502+
it('add a completion string', () => {
503+
const { getByRole } = render(
504+
<Autocomplete
505+
autoComplete
506+
options={['one', 'two']}
507+
renderInput={params => <TextField autoFocus {...params} />}
508+
/>,
509+
);
510+
const textbox = getByRole('textbox');
511+
fireEvent.change(textbox, { target: { value: 'O' } });
512+
expect(textbox.value).to.equal('O');
513+
fireEvent.keyDown(textbox, { key: 'ArrowDown' });
514+
expect(textbox.value).to.equal('one');
515+
expect(textbox.selectionStart).to.equal(1);
516+
expect(textbox.selectionEnd).to.equal(3);
517+
fireEvent.keyDown(textbox, { key: 'Enter' });
518+
expect(textbox.value).to.equal('one');
519+
expect(textbox.selectionStart).to.equal(3);
520+
expect(textbox.selectionEnd).to.equal(3);
521+
});
522+
});
523+
524+
describe('click input', () => {
525+
it('toggles if empty', () => {
526+
const { getByRole } = render(
527+
<Autocomplete options={['one', 'two']} renderInput={params => <TextField {...params} />} />,
528+
);
529+
const textbox = getByRole('textbox');
530+
const combobox = getByRole('combobox');
531+
expect(combobox).to.have.attribute('aria-expanded', 'false');
532+
fireEvent.mouseDown(textbox);
533+
expect(combobox).to.have.attribute('aria-expanded', 'true');
534+
fireEvent.mouseDown(textbox);
535+
expect(combobox).to.have.attribute('aria-expanded', 'false');
536+
});
537+
538+
it('selects all the first time', () => {
539+
const { getByRole } = render(
540+
<Autocomplete
541+
value="one"
542+
options={['one', 'two']}
543+
renderInput={params => <TextField {...params} />}
544+
/>,
545+
);
546+
const textbox = getByRole('textbox');
547+
fireEvent.click(textbox);
548+
expect(textbox.selectionStart).to.equal(0);
549+
expect(textbox.selectionEnd).to.equal(3);
550+
});
551+
});
500552
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export interface UseAutocompleteProps {
153153
export default function useAutocomplete(
154154
props: UseAutocompleteProps,
155155
): {
156-
getComboboxProps: () => {};
156+
getRootProps: () => {};
157157
getInputProps: () => {};
158158
getInputLabelProps: () => {};
159159
getClearProps: () => {};

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

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export default function useAutocomplete(props) {
9393
}
9494
});
9595

96+
const firstFocus = React.useRef(true);
9697
const inputRef = React.useRef(null);
9798
const listboxRef = React.useRef(null);
9899
const [anchorEl, setAnchorEl] = React.useState(null);
@@ -531,6 +532,14 @@ export default function useAutocomplete(props) {
531532
// We don't want to validate the form.
532533
event.preventDefault();
533534
selectNewValue(event, filteredOptions[highlightedIndexRef.current]);
535+
536+
// Move the selection to the end.
537+
if (autoComplete) {
538+
inputRef.current.setSelectionRange(
539+
inputRef.current.value.length,
540+
inputRef.current.value.length,
541+
);
542+
}
534543
} else if (freeSolo && inputValue !== '') {
535544
selectNewValue(event, inputValue);
536545
}
@@ -572,6 +581,7 @@ export default function useAutocomplete(props) {
572581

573582
const handleBlur = event => {
574583
setFocused(false);
584+
firstFocus.current = true;
575585

576586
if (debug && inputValue !== '') {
577587
return;
@@ -640,6 +650,30 @@ export default function useAutocomplete(props) {
640650
}
641651
};
642652

653+
const handleMouseDown = event => {
654+
if (event.target.nodeName !== 'INPUT') {
655+
// Prevent blur
656+
event.preventDefault();
657+
}
658+
};
659+
660+
const handleClick = () => {
661+
if (
662+
firstFocus.current &&
663+
inputRef.current.selectionEnd - inputRef.current.selectionStart === 0
664+
) {
665+
inputRef.current.select();
666+
}
667+
668+
firstFocus.current = false;
669+
};
670+
671+
const handleInputMouseDown = () => {
672+
if (inputValue === '') {
673+
handlePopupIndicator();
674+
}
675+
};
676+
643677
let dirty = freeSolo && inputValue.length > 0;
644678
dirty = dirty || (multiple ? value.length > 0 : value !== null);
645679

@@ -663,11 +697,13 @@ export default function useAutocomplete(props) {
663697
}
664698

665699
return {
666-
getComboboxProps: () => ({
700+
getRootProps: () => ({
667701
'aria-owns': popupOpen ? `${id}-popup` : null,
668702
role: 'combobox',
669703
'aria-expanded': popupOpen,
670704
onKeyDown: handleKeyDown,
705+
onMouseDown: handleMouseDown,
706+
onClick: handleClick,
671707
}),
672708
getInputLabelProps: () => ({
673709
id: `${id}-label`,
@@ -678,6 +714,7 @@ export default function useAutocomplete(props) {
678714
onBlur: handleBlur,
679715
onFocus: handleFocus,
680716
onChange: handleInputChange,
717+
onMouseDown: handleInputMouseDown,
681718
// if open then this is handled imperativeley so don't let react override
682719
// only have an opinion about this when closed
683720
'aria-activedescendant': popupOpen ? undefined : null,
@@ -693,16 +730,10 @@ export default function useAutocomplete(props) {
693730
getClearProps: () => ({
694731
tabIndex: -1,
695732
onClick: handleClear,
696-
onMouseDown: event => {
697-
event.preventDefault();
698-
},
699733
}),
700734
getPopupIndicatorProps: () => ({
701735
tabIndex: -1,
702736
onClick: handlePopupIndicator,
703-
onMouseDown: event => {
704-
event.preventDefault();
705-
},
706737
}),
707738
getTagProps: ({ index }) => ({
708739
key: index,
@@ -716,6 +747,7 @@ export default function useAutocomplete(props) {
716747
'aria-labelledby': `${id}-label`,
717748
ref: handleListboxRef,
718749
onMouseDown: event => {
750+
// Prevent blur
719751
event.preventDefault();
720752
},
721753
}),

0 commit comments

Comments
 (0)