diff --git a/docs/translations/api-docs-joy/autocomplete/autocomplete.json b/docs/translations/api-docs-joy/autocomplete/autocomplete.json
index 9678601cd656b2..46aa7d3c0c442a 100644
--- a/docs/translations/api-docs-joy/autocomplete/autocomplete.json
+++ b/docs/translations/api-docs-joy/autocomplete/autocomplete.json
@@ -32,7 +32,7 @@
"noOptionsText": "Text to display when there are no options.
For localization purposes, you can use the provided translations.",
"onChange": "Callback fired when the value changes.
Signature:
function(event: React.SyntheticEvent, value: T | Array<T>, reason: string, details?: string) => void
event: The event source of the callback.
value: The new value of the component.
reason: One of "createOption", "selectOption", "removeOption", "blur" or "clear".",
"onClose": "Callback fired when the popup requests to be closed. Use in controlled mode (see open).
Signature:
function(event: React.SyntheticEvent, reason: string) => void
event: The event source of the callback.
reason: Can be: "toggleInput"
, "escape"
, "selectOption"
, "removeOption"
, "blur"
.",
- "onHighlightChange": "Callback fired when the highlight option changes.
Signature:
function(event: React.SyntheticEvent, option: T, reason: string) => void
event: The event source of the callback.
option: The highlighted option.
reason: Can be: "keyboard"
, "auto"
, "mouse"
.",
+ "onHighlightChange": "Callback fired when the highlight option changes.
Signature:
function(event: React.SyntheticEvent, option: T, reason: string) => void
event: The event source of the callback.
option: The highlighted option.
reason: Can be: "keyboard"
, "auto"
, "mouse"
, "touch"
.",
"onInputChange": "Callback fired when the input value changes.
Signature:
function(event: React.SyntheticEvent, value: string, reason: string) => void
event: The event source of the callback.
value: The new value of the text input.
reason: Can be: "input"
(user input), "reset"
(programmatic change), "clear"
.",
"onOpen": "Callback fired when the popup requests to be opened. Use in controlled mode (see open).
Signature:
function(event: React.SyntheticEvent) => void
event: The event source of the callback.",
"open": "If true
, the component is shown.",
diff --git a/docs/translations/api-docs/autocomplete/autocomplete.json b/docs/translations/api-docs/autocomplete/autocomplete.json
index f47e0e33e71530..1b815bd389bccc 100644
--- a/docs/translations/api-docs/autocomplete/autocomplete.json
+++ b/docs/translations/api-docs/autocomplete/autocomplete.json
@@ -43,7 +43,7 @@
"noOptionsText": "Text to display when there are no options.
For localization purposes, you can use the provided translations.",
"onChange": "Callback fired when the value changes.
Signature:
function(event: React.SyntheticEvent, value: T | Array<T>, reason: string, details?: string) => void
event: The event source of the callback.
value: The new value of the component.
reason: One of "createOption", "selectOption", "removeOption", "blur" or "clear".",
"onClose": "Callback fired when the popup requests to be closed. Use in controlled mode (see open).
Signature:
function(event: React.SyntheticEvent, reason: string) => void
event: The event source of the callback.
reason: Can be: "toggleInput"
, "escape"
, "selectOption"
, "removeOption"
, "blur"
.",
- "onHighlightChange": "Callback fired when the highlight option changes.
Signature:
function(event: React.SyntheticEvent, option: T, reason: string) => void
event: The event source of the callback.
option: The highlighted option.
reason: Can be: "keyboard"
, "auto"
, "mouse"
.",
+ "onHighlightChange": "Callback fired when the highlight option changes.
Signature:
function(event: React.SyntheticEvent, option: T, reason: string) => void
event: The event source of the callback.
option: The highlighted option.
reason: Can be: "keyboard"
, "auto"
, "mouse"
, "touch"
.",
"onInputChange": "Callback fired when the input value changes.
Signature:
function(event: React.SyntheticEvent, value: string, reason: string) => void
event: The event source of the callback.
value: The new value of the text input.
reason: Can be: "input"
(user input), "reset"
(programmatic change), "clear"
.",
"onOpen": "Callback fired when the popup requests to be opened. Use in controlled mode (see open).
Signature:
function(event: React.SyntheticEvent) => void
event: The event source of the callback.",
"open": "If true
, the component is shown.",
diff --git a/packages/mui-base/src/useAutocomplete/useAutocomplete.d.ts b/packages/mui-base/src/useAutocomplete/useAutocomplete.d.ts
index 62ed839692bcab..9fa7719c70a75e 100644
--- a/packages/mui-base/src/useAutocomplete/useAutocomplete.d.ts
+++ b/packages/mui-base/src/useAutocomplete/useAutocomplete.d.ts
@@ -233,7 +233,7 @@ export interface UseAutocompleteProps<
*
* @param {React.SyntheticEvent} event The event source of the callback.
* @param {T} option The highlighted option.
- * @param {string} reason Can be: `"keyboard"`, `"auto"`, `"mouse"`.
+ * @param {string} reason Can be: `"keyboard"`, `"auto"`, `"mouse"`, `"touch"`.
*/
onHighlightChange?: (
event: React.SyntheticEvent,
@@ -299,7 +299,7 @@ export interface UseAutocompleteParameters<
FreeSolo extends boolean | undefined,
> extends UseAutocompleteProps {}
-export type AutocompleteHighlightChangeReason = 'keyboard' | 'mouse' | 'auto';
+export type AutocompleteHighlightChangeReason = 'keyboard' | 'mouse' | 'auto' | 'touch';
export type AutocompleteChangeReason =
| 'createOption'
diff --git a/packages/mui-base/src/useAutocomplete/useAutocomplete.js b/packages/mui-base/src/useAutocomplete/useAutocomplete.js
index cba6b180294eee..d4867241abe486 100644
--- a/packages/mui-base/src/useAutocomplete/useAutocomplete.js
+++ b/packages/mui-base/src/useAutocomplete/useAutocomplete.js
@@ -973,7 +973,12 @@ export default function useAutocomplete(props) {
});
};
- const handleOptionTouchStart = () => {
+ const handleOptionTouchStart = (event) => {
+ setHighlightedIndex({
+ event,
+ index: Number(event.currentTarget.getAttribute('data-option-index')),
+ reason: 'touch',
+ });
isTouch.current = true;
};
diff --git a/packages/mui-joy/src/Autocomplete/Autocomplete.tsx b/packages/mui-joy/src/Autocomplete/Autocomplete.tsx
index 952013685cc5c6..ddb8eb8e75dc16 100644
--- a/packages/mui-joy/src/Autocomplete/Autocomplete.tsx
+++ b/packages/mui-joy/src/Autocomplete/Autocomplete.tsx
@@ -949,7 +949,7 @@ Autocomplete.propTypes /* remove-proptypes */ = {
*
* @param {React.SyntheticEvent} event The event source of the callback.
* @param {T} option The highlighted option.
- * @param {string} reason Can be: `"keyboard"`, `"auto"`, `"mouse"`.
+ * @param {string} reason Can be: `"keyboard"`, `"auto"`, `"mouse"`, `"touch"`.
*/
onHighlightChange: PropTypes.func,
/**
diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.js b/packages/mui-material/src/Autocomplete/Autocomplete.js
index f1d70f117e0bb4..583b5f935efe29 100644
--- a/packages/mui-material/src/Autocomplete/Autocomplete.js
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.js
@@ -970,7 +970,7 @@ Autocomplete.propTypes /* remove-proptypes */ = {
*
* @param {React.SyntheticEvent} event The event source of the callback.
* @param {T} option The highlighted option.
- * @param {string} reason Can be: `"keyboard"`, `"auto"`, `"mouse"`.
+ * @param {string} reason Can be: `"keyboard"`, `"auto"`, `"mouse"`, `"touch"`.
*/
onHighlightChange: PropTypes.func,
/**
diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js
index 0886e79cc82d72..6889a96c3d295c 100644
--- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js
@@ -2921,4 +2921,37 @@ describe('', () => {
expect(root).to.have.class(classes.expanded);
});
});
+
+ // https://github.com/mui/material-ui/issues/36212
+ it('should preserve scrollTop position of the listbox when adding new options on mobile', function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+ function getOptions(count) {
+ return Array(count)
+ .fill('item')
+ .map((value, i) => value + i);
+ }
+
+ const { getByRole, getAllByRole, setProps } = render(
+ }
+ ListboxProps={{ style: { maxHeight: '100px' } }}
+ />,
+ );
+ const listbox = getByRole('listbox');
+
+ expect(listbox).to.have.property('scrollTop', 0);
+
+ const options = getAllByRole('option');
+ act(() => {
+ fireEvent.touchStart(options[1]);
+ listbox.scrollBy(0, 60);
+ setProps({ options: getOptions(10) });
+ });
+
+ expect(listbox).to.have.property('scrollTop', 60);
+ });
});