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); + }); });