diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js
index 228b812abd0911..42a32a6ee3e8e2 100644
--- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js
@@ -1389,6 +1389,47 @@ describe('', () => {
expect(handleChange.args[0][1]).to.deep.equal([]);
});
+ it('should not suppress focus events after clearing with Escape', async () => {
+ const handleOpen = spy();
+ const { user } = render(
+ {}}
+ onOpen={handleOpen}
+ renderInput={(params) => }
+ />,
+ );
+
+ const textbox = screen.getByRole('combobox');
+
+ // Opening on initial focus
+ expect(handleOpen.callCount).to.equal(1);
+
+ // Close the popup first so Escape takes the clear path
+ await user.keyboard('{Escape}');
+ // Popup was open, so first Escape closes it
+ handleOpen.resetHistory();
+
+ // Now Escape should clear (popup is closed, value is non-empty)
+ await user.keyboard('{Escape}');
+
+ // Focus is still on the input
+ expect(textbox).toHaveFocus();
+
+ // Blur and re-focus: onOpen should be called (ignoreFocus was NOT set)
+ act(() => {
+ textbox.blur();
+ });
+ act(() => {
+ textbox.focus();
+ });
+ expect(handleOpen.callCount).to.equal(1);
+ });
+
it('should clear on escape if rendering single value', () => {
const handleChange = spy();
render(
@@ -1634,6 +1675,34 @@ describe('', () => {
fireEvent.focus(textbox);
expect(textbox).to.have.attribute('aria-expanded', 'true');
});
+
+ it('should suppress focus events when clearing with the clear button', async () => {
+ const handleOpen = spy();
+ const { user } = render(
+ {}}
+ onOpen={handleOpen}
+ renderInput={(params) => }
+ />,
+ );
+
+ // Opening on initial focus
+ expect(handleOpen.callCount).to.equal(1);
+
+ // Close popup
+ await user.keyboard('{Escape}');
+ handleOpen.resetHistory();
+
+ // Click the clear button
+ const clearButton = screen.getByTitle('Clear');
+ await user.click(clearButton);
+
+ // onOpen should NOT be called because ignoreFocus is set
+ expect(handleOpen.callCount).to.equal(0);
+ });
});
describe('listbox wrapping behavior', () => {
@@ -2576,6 +2645,89 @@ describe('', () => {
});
describe('prop: freeSolo', () => {
+ it('should reset input when controlled value changes to null', async () => {
+ function App() {
+ const [value, setValue] = React.useState('foo');
+ return (
+
+ setValue(newValue)}
+ renderInput={(params) => }
+ />
+
+
+ );
+ }
+ const { user } = render();
+ const textbox = screen.getByRole('combobox');
+ expect(textbox.value).to.equal('foo');
+
+ await user.click(screen.getByRole('button', { name: 'Reset' }));
+ expect(textbox.value).to.equal('');
+ });
+
+ it('should reset input when controlled value changes to null with clearOnBlur=false', async () => {
+ function App() {
+ const [value, setValue] = React.useState('foo');
+ return (
+
+ setValue(newValue)}
+ renderInput={(params) => }
+ />
+
+
+ );
+ }
+ const { user } = render();
+ const textbox = screen.getByRole('combobox');
+ expect(textbox.value).to.equal('foo');
+
+ await user.click(screen.getByRole('button', { name: 'Reset' }));
+ expect(textbox.value).to.equal('');
+ });
+
+ it('should retain input when controlled multiple value changes with clearOnBlur=false', async () => {
+ function App() {
+ const [value, setValue] = React.useState(['one']);
+ return (
+
+ setValue(newValue)}
+ renderInput={(params) => }
+ />
+
+
+ );
+ }
+ const { user } = render();
+ const textbox = screen.getByRole('combobox');
+
+ await user.type(textbox, 'abc');
+ expect(textbox.value).to.equal('abc');
+
+ await user.click(screen.getByRole('button', { name: 'Reset' }));
+ expect(textbox.value).to.equal('abc');
+ });
+
it('pressing twice enter should not call onChange listener twice', () => {
const handleChange = spy();
const options = [{ name: 'foo' }];
diff --git a/packages/mui-material/src/useAutocomplete/useAutocomplete.js b/packages/mui-material/src/useAutocomplete/useAutocomplete.js
index cdbe646f296be4..b0124d6ef5752d 100644
--- a/packages/mui-material/src/useAutocomplete/useAutocomplete.js
+++ b/packages/mui-material/src/useAutocomplete/useAutocomplete.js
@@ -178,10 +178,12 @@ function useAutocomplete(props) {
const resetInputValue = React.useCallback(
(event, newValue, reason) => {
- // retain current `inputValue` if new option isn't selected and `clearOnBlur` is false
- // When `multiple` is enabled, `newValue` is an array of all selected items including the newly selected item
+ // Retain the current `inputValue` when no new option is selected and `clearOnBlur` is false.
+ // In `multiple` mode, `newValue` is the next value array, so only length growth counts as a selection.
const isOptionSelected = multiple ? value.length < newValue.length : newValue !== null;
- if (!isOptionSelected && !clearOnBlur) {
+ // A controlled single-value `freeSolo` reset to `null` should still clear the input.
+ const shouldClearOnReset = reason === 'reset' && freeSolo && !multiple && newValue === null;
+ if (!isOptionSelected && !clearOnBlur && !shouldClearOnReset) {
return;
}
const newInputValue = getInputValue(newValue, multiple, getOptionLabel, renderValue);
@@ -203,6 +205,7 @@ function useAutocomplete(props) {
onInputChange,
setInputValueState,
clearOnBlur,
+ freeSolo,
value,
renderValue,
],
@@ -826,7 +829,6 @@ function useAutocomplete(props) {
};
const handleClear = (event) => {
- ignoreFocus.current = true;
setInputValueState('');
if (onInputChange) {
@@ -1272,7 +1274,10 @@ function useAutocomplete(props) {
getClearProps: () => ({
tabIndex: -1,
type: 'button',
- onClick: handleClear,
+ onClick: (event) => {
+ ignoreFocus.current = true;
+ handleClear(event);
+ },
}),
getItemProps: ({ index = 0 } = {}) => ({
...(multiple && { key: index }),