@mui/material 9.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/Accordion/Accordion.d.mts +2 -2
  2. package/Accordion/Accordion.d.ts +2 -2
  3. package/Accordion/Accordion.js +3 -2
  4. package/Accordion/Accordion.mjs +3 -2
  5. package/AccordionSummary/AccordionSummary.js +27 -29
  6. package/AccordionSummary/AccordionSummary.mjs +27 -29
  7. package/Autocomplete/Autocomplete.js +73 -17
  8. package/Autocomplete/Autocomplete.mjs +73 -17
  9. package/Avatar/Avatar.js +4 -0
  10. package/Avatar/Avatar.mjs +4 -0
  11. package/Backdrop/Backdrop.d.mts +2 -2
  12. package/Backdrop/Backdrop.d.ts +2 -2
  13. package/Badge/Badge.js +31 -24
  14. package/Badge/Badge.mjs +31 -24
  15. package/BottomNavigationAction/BottomNavigationAction.js +6 -2
  16. package/BottomNavigationAction/BottomNavigationAction.mjs +6 -2
  17. package/Button/Button.js +19 -6
  18. package/Button/Button.mjs +19 -6
  19. package/ButtonBase/ButtonBase.d.mts +7 -0
  20. package/ButtonBase/ButtonBase.d.ts +7 -0
  21. package/ButtonBase/ButtonBase.js +5 -2
  22. package/ButtonBase/ButtonBase.mjs +5 -2
  23. package/ButtonBase/Ripple.js +21 -11
  24. package/ButtonBase/Ripple.mjs +21 -11
  25. package/ButtonBase/TouchRipple.js +252 -116
  26. package/ButtonBase/TouchRipple.mjs +253 -117
  27. package/CHANGELOG.md +216 -1245
  28. package/CardActionArea/CardActionArea.js +2 -1
  29. package/CardActionArea/CardActionArea.mjs +2 -1
  30. package/Checkbox/Checkbox.js +2 -1
  31. package/Checkbox/Checkbox.mjs +2 -1
  32. package/Chip/Chip.js +2 -1
  33. package/Chip/Chip.mjs +2 -1
  34. package/CircularProgress/CircularProgress.d.mts +12 -2
  35. package/CircularProgress/CircularProgress.d.ts +12 -2
  36. package/CircularProgress/CircularProgress.js +115 -58
  37. package/CircularProgress/CircularProgress.mjs +114 -58
  38. package/ClickAwayListener/ClickAwayListener.js +3 -6
  39. package/ClickAwayListener/ClickAwayListener.mjs +3 -6
  40. package/Collapse/Collapse.d.mts +15 -3
  41. package/Collapse/Collapse.d.ts +15 -3
  42. package/Collapse/Collapse.js +44 -31
  43. package/Collapse/Collapse.mjs +43 -30
  44. package/Dialog/Dialog.d.mts +2 -2
  45. package/Dialog/Dialog.d.ts +2 -2
  46. package/Dialog/Dialog.js +13 -6
  47. package/Dialog/Dialog.mjs +13 -6
  48. package/Drawer/Drawer.d.mts +2 -2
  49. package/Drawer/Drawer.d.ts +2 -2
  50. package/Drawer/Drawer.js +18 -4
  51. package/Drawer/Drawer.mjs +18 -4
  52. package/Fab/Fab.js +9 -2
  53. package/Fab/Fab.mjs +9 -2
  54. package/Fade/Fade.d.mts +15 -2
  55. package/Fade/Fade.d.ts +15 -2
  56. package/Fade/Fade.js +46 -19
  57. package/Fade/Fade.mjs +45 -18
  58. package/FilledInput/FilledInput.d.mts +4 -0
  59. package/FilledInput/FilledInput.d.ts +4 -0
  60. package/FilledInput/FilledInput.js +22 -23
  61. package/FilledInput/FilledInput.mjs +22 -23
  62. package/FormControl/useFormControl.d.mts +12 -2
  63. package/FormControl/useFormControl.d.ts +12 -2
  64. package/FormControl/useFormControl.js +13 -0
  65. package/FormControl/useFormControl.mjs +12 -0
  66. package/FormControlLabel/FormControlLabel.js +5 -8
  67. package/FormControlLabel/FormControlLabel.mjs +5 -8
  68. package/FormGroup/FormGroup.js +2 -5
  69. package/FormGroup/FormGroup.mjs +2 -5
  70. package/FormHelperText/FormHelperText.js +2 -5
  71. package/FormHelperText/FormHelperText.mjs +2 -5
  72. package/FormLabel/FormLabel.js +2 -5
  73. package/FormLabel/FormLabel.mjs +2 -5
  74. package/Grow/Grow.d.mts +15 -2
  75. package/Grow/Grow.d.ts +15 -2
  76. package/Grow/Grow.js +45 -28
  77. package/Grow/Grow.mjs +44 -27
  78. package/IconButton/IconButton.js +3 -9
  79. package/IconButton/IconButton.mjs +3 -9
  80. package/Input/Input.d.mts +4 -0
  81. package/Input/Input.d.ts +4 -0
  82. package/Input/Input.js +9 -2
  83. package/Input/Input.mjs +9 -2
  84. package/InputBase/InputBase.d.mts +2 -1
  85. package/InputBase/InputBase.d.ts +2 -1
  86. package/InputBase/InputBase.js +52 -16
  87. package/InputBase/InputBase.mjs +52 -16
  88. package/InputLabel/InputLabel.js +7 -9
  89. package/InputLabel/InputLabel.mjs +7 -9
  90. package/LICENSE +1 -1
  91. package/LinearProgress/LinearProgress.d.mts +12 -2
  92. package/LinearProgress/LinearProgress.d.ts +12 -2
  93. package/LinearProgress/LinearProgress.js +225 -126
  94. package/LinearProgress/LinearProgress.mjs +224 -126
  95. package/List/List.js +2 -1
  96. package/List/List.mjs +2 -1
  97. package/ListItem/ListItem.js +2 -1
  98. package/ListItem/ListItem.mjs +2 -1
  99. package/ListItemButton/ListItemButton.js +9 -2
  100. package/ListItemButton/ListItemButton.mjs +9 -2
  101. package/Menu/Menu.d.mts +1 -1
  102. package/Menu/Menu.d.ts +1 -1
  103. package/MenuItem/MenuItem.js +7 -1
  104. package/MenuItem/MenuItem.mjs +7 -1
  105. package/MenuList/MenuList.js +2 -1
  106. package/MenuList/MenuList.mjs +2 -1
  107. package/MobileStepper/MobileStepper.js +2 -1
  108. package/MobileStepper/MobileStepper.mjs +2 -1
  109. package/NativeSelect/NativeSelect.js +2 -5
  110. package/NativeSelect/NativeSelect.mjs +2 -5
  111. package/OutlinedInput/NotchedOutline.js +4 -3
  112. package/OutlinedInput/NotchedOutline.mjs +4 -3
  113. package/OutlinedInput/OutlinedInput.js +13 -23
  114. package/OutlinedInput/OutlinedInput.mjs +13 -23
  115. package/PaginationItem/PaginationItem.js +2 -1
  116. package/PaginationItem/PaginationItem.mjs +2 -1
  117. package/Paper/Paper.js +2 -1
  118. package/Paper/Paper.mjs +2 -1
  119. package/PigmentContainer/PigmentContainer.js +0 -1
  120. package/PigmentContainer/PigmentContainer.mjs +0 -1
  121. package/Popover/Popover.d.mts +1 -1
  122. package/Popover/Popover.d.ts +1 -1
  123. package/Popper/BasePopper.js +23 -1
  124. package/Popper/BasePopper.mjs +23 -1
  125. package/README.md +3 -2
  126. package/Radio/RadioButtonIcon.js +3 -2
  127. package/Radio/RadioButtonIcon.mjs +3 -2
  128. package/Rating/Rating.js +2 -1
  129. package/Rating/Rating.mjs +2 -1
  130. package/Select/Select.js +2 -5
  131. package/Select/Select.mjs +2 -5
  132. package/Select/SelectInput.js +276 -24
  133. package/Select/SelectInput.mjs +276 -24
  134. package/Select/utils/closedTypeahead.js +73 -0
  135. package/Select/utils/closedTypeahead.mjs +63 -0
  136. package/Skeleton/Skeleton.js +22 -2
  137. package/Skeleton/Skeleton.mjs +22 -2
  138. package/Slide/Slide.d.mts +15 -2
  139. package/Slide/Slide.d.ts +15 -2
  140. package/Slide/Slide.js +97 -47
  141. package/Slide/Slide.mjs +97 -47
  142. package/Slider/Slider.js +14 -4
  143. package/Slider/Slider.mjs +14 -4
  144. package/Slider/useSlider.js +4 -3
  145. package/Slider/useSlider.mjs +4 -3
  146. package/Snackbar/Snackbar.d.mts +2 -2
  147. package/Snackbar/Snackbar.d.ts +2 -2
  148. package/SpeedDial/SpeedDial.d.mts +1 -1
  149. package/SpeedDial/SpeedDial.d.ts +1 -1
  150. package/SpeedDial/SpeedDial.js +6 -2
  151. package/SpeedDial/SpeedDial.mjs +6 -2
  152. package/SpeedDialAction/SpeedDialAction.js +11 -2
  153. package/SpeedDialAction/SpeedDialAction.mjs +12 -3
  154. package/SpeedDialIcon/SpeedDialIcon.js +40 -37
  155. package/SpeedDialIcon/SpeedDialIcon.mjs +40 -37
  156. package/Step/Step.js +47 -15
  157. package/Step/Step.mjs +47 -15
  158. package/StepButton/StepButton.js +9 -3
  159. package/StepButton/StepButton.mjs +9 -3
  160. package/StepConnector/StepConnector.js +10 -0
  161. package/StepConnector/StepConnector.mjs +10 -0
  162. package/StepContent/StepContent.d.mts +2 -2
  163. package/StepContent/StepContent.d.ts +2 -2
  164. package/StepContent/StepContent.js +26 -2
  165. package/StepContent/StepContent.mjs +26 -2
  166. package/StepIcon/StepIcon.js +2 -1
  167. package/StepIcon/StepIcon.mjs +2 -1
  168. package/StepLabel/StepLabel.js +52 -7
  169. package/StepLabel/StepLabel.mjs +52 -7
  170. package/Stepper/Stepper.d.mts +2 -0
  171. package/Stepper/Stepper.d.ts +2 -0
  172. package/Stepper/Stepper.js +18 -0
  173. package/Stepper/Stepper.mjs +18 -0
  174. package/SvgIcon/SvgIcon.js +2 -1
  175. package/SvgIcon/SvgIcon.mjs +2 -1
  176. package/SwipeableDrawer/SwipeableDrawer.js +21 -6
  177. package/SwipeableDrawer/SwipeableDrawer.mjs +21 -6
  178. package/Switch/Switch.js +10 -8
  179. package/Switch/Switch.mjs +10 -8
  180. package/TableSortLabel/TableSortLabel.js +2 -1
  181. package/TableSortLabel/TableSortLabel.mjs +2 -1
  182. package/Tabs/ScrollbarSize.js +2 -1
  183. package/Tabs/ScrollbarSize.mjs +2 -1
  184. package/Tabs/Tabs.js +16 -4
  185. package/Tabs/Tabs.mjs +16 -4
  186. package/Tooltip/Tooltip.d.mts +2 -2
  187. package/Tooltip/Tooltip.d.ts +2 -2
  188. package/Tooltip/Tooltip.js +29 -108
  189. package/Tooltip/Tooltip.mjs +29 -108
  190. package/Unstable_TrapFocus/FocusTrap.js +60 -22
  191. package/Unstable_TrapFocus/FocusTrap.mjs +60 -22
  192. package/Zoom/Zoom.d.mts +15 -2
  193. package/Zoom/Zoom.d.ts +15 -2
  194. package/Zoom/Zoom.js +43 -16
  195. package/Zoom/Zoom.mjs +42 -15
  196. package/index.js +1 -1
  197. package/index.mjs +1 -1
  198. package/internal/Transition.d.mts +34 -0
  199. package/internal/Transition.d.ts +34 -0
  200. package/internal/Transition.js +444 -0
  201. package/internal/Transition.mjs +436 -0
  202. package/internal/react-transition-group.d.mts +8 -0
  203. package/internal/react-transition-group.d.ts +8 -0
  204. package/package.json +50 -50
  205. package/styles/createMotion.d.mts +8 -0
  206. package/styles/createMotion.d.ts +8 -0
  207. package/styles/createMotion.js +13 -0
  208. package/styles/createMotion.mjs +7 -0
  209. package/styles/createThemeFoundation.d.mts +2 -0
  210. package/styles/createThemeFoundation.d.ts +2 -0
  211. package/styles/createThemeNoVars.d.mts +3 -0
  212. package/styles/createThemeNoVars.d.ts +3 -0
  213. package/styles/createThemeNoVars.js +5 -0
  214. package/styles/createThemeNoVars.mjs +5 -0
  215. package/styles/createTransitions.d.mts +6 -2
  216. package/styles/createTransitions.d.ts +6 -2
  217. package/styles/createTransitions.js +12 -4
  218. package/styles/createTransitions.mjs +12 -4
  219. package/styles/enhanceHighContrast.d.mts +70 -0
  220. package/styles/enhanceHighContrast.d.ts +70 -0
  221. package/styles/enhanceHighContrast.js +502 -0
  222. package/styles/enhanceHighContrast.mjs +495 -0
  223. package/styles/index.d.mts +2 -0
  224. package/styles/index.d.ts +2 -0
  225. package/styles/index.js +8 -0
  226. package/styles/index.mjs +1 -0
  227. package/styles/reducedMotion.d.mts +7 -0
  228. package/styles/reducedMotion.d.ts +7 -0
  229. package/styles/reducedMotion.js +21 -0
  230. package/styles/reducedMotion.mjs +14 -0
  231. package/styles/responsiveFontSizes.js +19 -8
  232. package/styles/responsiveFontSizes.mjs +19 -8
  233. package/styles/shouldSkipGeneratingVar.js +1 -1
  234. package/styles/shouldSkipGeneratingVar.mjs +1 -1
  235. package/styles/stringifyTheme.js +1 -0
  236. package/styles/stringifyTheme.mjs +1 -0
  237. package/styles/useThemeProps.d.mts +3 -3
  238. package/styles/useThemeProps.d.ts +3 -3
  239. package/transitions/index.d.mts +1 -1
  240. package/transitions/index.d.ts +1 -1
  241. package/transitions/index.js +0 -11
  242. package/transitions/index.mjs +1 -1
  243. package/transitions/transition.d.mts +1 -12
  244. package/transitions/transition.d.ts +1 -12
  245. package/transitions/types.d.mts +73 -0
  246. package/transitions/types.d.ts +73 -0
  247. package/transitions/useReducedMotion.d.mts +14 -0
  248. package/transitions/useReducedMotion.d.ts +14 -0
  249. package/transitions/useReducedMotion.js +117 -0
  250. package/transitions/useReducedMotion.mjs +110 -0
  251. package/transitions/utils.d.mts +51 -2
  252. package/transitions/utils.d.ts +51 -2
  253. package/transitions/utils.js +97 -4
  254. package/transitions/utils.mjs +94 -4
  255. package/useAutocomplete/useAutocomplete.d.mts +12 -6
  256. package/useAutocomplete/useAutocomplete.d.ts +12 -6
  257. package/useAutocomplete/useAutocomplete.js +230 -55
  258. package/useAutocomplete/useAutocomplete.mjs +230 -55
  259. package/utils/contains.d.mts +2 -0
  260. package/utils/contains.d.ts +2 -0
  261. package/utils/contains.js +9 -0
  262. package/utils/contains.mjs +2 -0
  263. package/utils/focusable.d.mts +7 -0
  264. package/utils/focusable.d.ts +7 -0
  265. package/utils/focusable.js +20 -0
  266. package/utils/focusable.mjs +13 -0
  267. package/utils/getEventTarget.d.mts +2 -0
  268. package/utils/getEventTarget.d.ts +2 -0
  269. package/utils/getEventTarget.js +9 -0
  270. package/utils/getEventTarget.mjs +2 -0
  271. package/utils/mergeSlotProps.js +2 -8
  272. package/utils/mergeSlotProps.mjs +1 -8
  273. package/version/index.js +2 -2
  274. package/version/index.mjs +2 -2
  275. package/FormControl/formControlState.js +0 -21
  276. package/FormControl/formControlState.mjs +0 -15
  277. /package/transitions/{transition.js → types.js} +0 -0
  278. /package/transitions/{transition.mjs → types.mjs} +0 -0
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", {
9
9
  exports.createFilterOptions = createFilterOptions;
10
10
  exports.default = void 0;
11
11
  var React = _interopRequireWildcard(require("react"));
12
+ var _contains = _interopRequireDefault(require("@mui/utils/contains"));
12
13
  var _setRef = _interopRequireDefault(require("@mui/utils/setRef"));
13
14
  var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
14
15
  var _useControlled = _interopRequireDefault(require("@mui/utils/useControlled"));
@@ -63,7 +64,7 @@ const defaultFilterOptions = createFilterOptions();
63
64
 
64
65
  // Number of options to jump in list box when `Page Up` and `Page Down` keys are used.
65
66
  const pageSize = 5;
66
- const defaultIsActiveElementInListbox = listboxRef => listboxRef.current !== null && listboxRef.current.parentElement?.contains(document.activeElement);
67
+ const defaultIsActiveElementInListbox = listboxRef => listboxRef.current !== null && (0, _contains.default)(listboxRef.current.parentElement, document.activeElement);
67
68
  const defaultIsOptionEqualToValue = (option, value) => option === value;
68
69
  const MULTIPLE_DEFAULT_VALUE = [];
69
70
  function getInputValue(value, multiple, getOptionLabel, renderValue) {
@@ -115,6 +116,7 @@ function useAutocomplete(props) {
115
116
  options,
116
117
  readOnly = false,
117
118
  renderValue,
119
+ resetHighlightOnMouseLeave = false,
118
120
  selectOnFocus = !props.freeSolo,
119
121
  value: valueProp
120
122
  } = props;
@@ -135,12 +137,29 @@ function useAutocomplete(props) {
135
137
  const firstFocus = React.useRef(true);
136
138
  const inputRef = React.useRef(null);
137
139
  const listboxRef = React.useRef(null);
140
+ // VoiceOver synthesises a spurious Backspace on the input after a chip
141
+ // deletion moves DOM focus back to it. This flag suppresses that one event.
142
+ const ignoreNextBackspaceRef = React.useRef(false);
138
143
  const windowLostFocus = React.useRef(false);
139
144
  const [anchorEl, setAnchorEl] = React.useState(null);
140
145
  const [focusedItem, setFocusedItem] = React.useState(-1);
141
146
  const defaultHighlighted = autoHighlight ? 0 : -1;
142
147
  const highlightedIndexRef = React.useRef(defaultHighlighted);
143
148
 
149
+ // Tracks how the current highlight was set:
150
+ // - 'keyboard' — arrow keys, Home/End, PageUp/PageDown
151
+ // - 'mouse' — handleOptionMouseMove
152
+ // - 'touch' — handleOptionTouchStart
153
+ // - null — programmatic (autoHighlight, value sync)
154
+ //
155
+ // This lets handleBlur and the Enter handler distinguish intentional
156
+ // interactions from incidental ones — e.g. autoSelect should not commit
157
+ // a highlight that came from a casual mouse hover.
158
+ /** @type {React.RefObject<AutocompleteHighlightChangeReason | null>} */
159
+ const highlightReasonRef = React.useRef(null);
160
+ const touchScrolledRef = React.useRef(false);
161
+ const isTouchRef = React.useRef(false);
162
+
144
163
  // Calculate the initial inputValue on mount only.
145
164
  // useRef ensures it doesn't update dynamically with defaultValue or value props.
146
165
  const initialInputValue = React.useRef(getInputValue(defaultValue ?? valueProp, multiple, getOptionLabel)).current;
@@ -157,10 +176,12 @@ function useAutocomplete(props) {
157
176
  });
158
177
  const [focused, setFocused] = React.useState(false);
159
178
  const resetInputValue = React.useCallback((event, newValue, reason) => {
160
- // retain current `inputValue` if new option isn't selected and `clearOnBlur` is false
161
- // When `multiple` is enabled, `newValue` is an array of all selected items including the newly selected item
179
+ // Retain the current `inputValue` when no new option is selected and `clearOnBlur` is false.
180
+ // In `multiple` mode, `newValue` is the next value array, so only length growth counts as a selection.
162
181
  const isOptionSelected = multiple ? value.length < newValue.length : newValue !== null;
163
- if (!isOptionSelected && !clearOnBlur) {
182
+ // A controlled single-value `freeSolo` reset to `null` should still clear the input.
183
+ const shouldClearOnReset = reason === 'reset' && freeSolo && !multiple && newValue === null;
184
+ if (!isOptionSelected && !clearOnBlur && !shouldClearOnReset) {
164
185
  return;
165
186
  }
166
187
  const newInputValue = getInputValue(newValue, multiple, getOptionLabel, renderValue);
@@ -171,7 +192,7 @@ function useAutocomplete(props) {
171
192
  if (onInputChange) {
172
193
  onInputChange(event, newInputValue, reason);
173
194
  }
174
- }, [getOptionLabel, inputValue, multiple, onInputChange, setInputValueState, clearOnBlur, value, renderValue]);
195
+ }, [getOptionLabel, inputValue, multiple, onInputChange, setInputValueState, clearOnBlur, freeSolo, value, renderValue]);
175
196
  const [open, setOpenState] = (0, _useControlled.default)({
176
197
  controlled: openProp,
177
198
  default: false,
@@ -226,8 +247,10 @@ function useAutocomplete(props) {
226
247
  return;
227
248
  }
228
249
 
229
- // Only reset the input's value when freeSolo if the component's value changes.
230
- if (freeSolo && !valueChange) {
250
+ // In freeSolo mode, only reset the input after a real value change.
251
+ // Also prevent the initial default value of `null` from clearing controlled values.
252
+ const shouldSkipFreeSoloReset = freeSolo && (!valueChange || value == null && previousProps.value === undefined);
253
+ if (shouldSkipFreeSoloReset) {
231
254
  return;
232
255
  }
233
256
  resetInputValue(null, value, 'reset');
@@ -278,12 +301,16 @@ function useAutocomplete(props) {
278
301
  }
279
302
  }
280
303
  }
281
- const setHighlightedIndex = (0, _useEventCallback.default)(({
282
- event,
304
+ const syncHighlightedIndexToDOM = (0, _useEventCallback.default)(({
283
305
  index,
284
- reason
306
+ reason,
307
+ preserveScroll = false
285
308
  }) => {
286
- highlightedIndexRef.current = index;
309
+ // React can clear refs before pending passive effects run during unmount.
310
+ // If both refs are gone, there is no DOM left to sync.
311
+ if (inputRef.current == null && listboxRef.current == null) {
312
+ return;
313
+ }
287
314
 
288
315
  // does the index exist?
289
316
  if (index === -1) {
@@ -291,9 +318,6 @@ function useAutocomplete(props) {
291
318
  } else {
292
319
  inputRef.current.setAttribute('aria-activedescendant', `${id}-option-${index}`);
293
320
  }
294
- if (onHighlightChange && ['mouse', 'keyboard', 'touch'].includes(reason)) {
295
- onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
296
- }
297
321
  if (!listboxRef.current) {
298
322
  return;
299
323
  }
@@ -312,7 +336,9 @@ function useAutocomplete(props) {
312
336
  return;
313
337
  }
314
338
  if (index === -1) {
315
- listboxNode.scrollTop = 0;
339
+ if (!preserveScroll) {
340
+ listboxNode.scrollTop = 0;
341
+ }
316
342
  return;
317
343
  }
318
344
  const option = listboxRef.current.querySelector(`[data-option-index="${index}"]`);
@@ -340,15 +366,46 @@ function useAutocomplete(props) {
340
366
  }
341
367
  }
342
368
  });
369
+ const setHighlightedIndex = (0, _useEventCallback.default)(({
370
+ event,
371
+ index,
372
+ reason,
373
+ preserveScroll = false
374
+ }) => {
375
+ highlightedIndexRef.current = index;
376
+ highlightReasonRef.current = reason ?? null;
377
+ if (onHighlightChange && ['mouse', 'keyboard', 'touch'].includes(reason)) {
378
+ onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
379
+ }
380
+ syncHighlightedIndexToDOM({
381
+ index,
382
+ reason,
383
+ preserveScroll
384
+ });
385
+ });
386
+ const setHighlightedIndexFromSync = (0, _useEventCallback.default)(({
387
+ index
388
+ }) => {
389
+ highlightedIndexRef.current = index;
390
+ syncHighlightedIndexToDOM({
391
+ index,
392
+ reason: highlightReasonRef.current
393
+ });
394
+ });
343
395
  const changeHighlightedIndex = (0, _useEventCallback.default)(({
344
396
  event,
345
397
  diff,
346
398
  direction = 'next',
347
- reason
399
+ reason,
400
+ preserveScroll
348
401
  }) => {
349
402
  if (!popupOpen) {
350
403
  return;
351
404
  }
405
+ if (reason === 'keyboard') {
406
+ touchScrolledRef.current = false;
407
+ isTouchRef.current = false;
408
+ }
352
409
  const getNextIndex = () => {
353
410
  const maxIndex = filteredOptions.length - 1;
354
411
  if (diff === 'reset') {
@@ -385,7 +442,8 @@ function useAutocomplete(props) {
385
442
  setHighlightedIndex({
386
443
  index: nextIndex,
387
444
  reason,
388
- event
445
+ event,
446
+ preserveScroll
389
447
  });
390
448
 
391
449
  // Sync the content of the input with the highlighted option.
@@ -439,15 +497,24 @@ function useAutocomplete(props) {
439
497
  // If it exists and the value and the inputValue haven't changed, just update its index, otherwise continue execution
440
498
  const previousHighlightedOptionIndex = getPreviousHighlightedOptionIndex();
441
499
  if (previousHighlightedOptionIndex !== -1) {
442
- highlightedIndexRef.current = previousHighlightedOptionIndex;
500
+ // Keep the original highlight reason while re-syncing the DOM state.
501
+ // The highlighted option still exists after the filteredOptions array changed
502
+ // (e.g. async fetch returns new options while the user is mid-navigation),
503
+ // so the original interaction reason (keyboard, mouse, etc.) still applies.
504
+ setHighlightedIndexFromSync({
505
+ index: previousHighlightedOptionIndex
506
+ });
443
507
  return;
444
508
  }
445
509
  const valueItem = multiple ? value[0] : value;
446
510
 
447
511
  // The popup is empty, reset
448
512
  if (filteredOptions.length === 0 || valueItem == null) {
513
+ // Preserve scroll when new options are appended without changing the current filter.
514
+ const isAppendOnly = filteredOptionsChanged && previousProps.inputValue === inputValue && previousProps.filteredOptions?.length > 0 && filteredOptions.length > previousProps.filteredOptions.length && previousProps.filteredOptions.every((option, index) => getOptionLabel(option) === getOptionLabel(filteredOptions[index]));
449
515
  changeHighlightedIndex({
450
- diff: 'reset'
516
+ diff: 'reset',
517
+ preserveScroll: isAppendOnly
451
518
  });
452
519
  return;
453
520
  }
@@ -459,8 +526,12 @@ function useAutocomplete(props) {
459
526
  if (valueItem != null) {
460
527
  const currentOption = filteredOptions[highlightedIndexRef.current];
461
528
 
462
- // Keep the current highlighted index if possible
463
- if (multiple && currentOption && value.findIndex(val => isOptionEqualToValue(currentOption, val)) !== -1) {
529
+ // Keep the current selected highlight while the popup stays open;
530
+ // on reopen, resync from the selected value.
531
+ if (multiple && currentOption && value.findIndex(val => isOptionEqualToValue(currentOption, val)) !== -1 && previousProps.filteredOptions?.length > 0) {
532
+ setHighlightedIndexFromSync({
533
+ index: highlightedIndexRef.current
534
+ });
464
535
  return;
465
536
  }
466
537
  const itemIndex = filteredOptions.findIndex(optionItem => isOptionEqualToValue(optionItem, valueItem));
@@ -495,7 +566,7 @@ function useAutocomplete(props) {
495
566
  filteredOptions.length,
496
567
  // Don't sync the highlighted index with the value when multiple
497
568
  // eslint-disable-next-line react-hooks/exhaustive-deps
498
- multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, popupOpen, inputValue, multiple]);
569
+ multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, setHighlightedIndexFromSync, popupOpen, inputValue, multiple]);
499
570
  const handleListboxRef = (0, _useEventCallback.default)(node => {
500
571
  (0, _setRef.default)(listboxRef, node);
501
572
  if (!node) {
@@ -541,6 +612,7 @@ function useAutocomplete(props) {
541
612
  }
542
613
  setOpenState(true);
543
614
  setInputPristine(true);
615
+ isTouchRef.current = false;
544
616
  if (onOpen) {
545
617
  onOpen(event);
546
618
  }
@@ -550,6 +622,8 @@ function useAutocomplete(props) {
550
622
  return;
551
623
  }
552
624
  setOpenState(false);
625
+ touchScrolledRef.current = false;
626
+ highlightReasonRef.current = null;
553
627
  if (onClose) {
554
628
  onClose(event, reason);
555
629
  }
@@ -567,7 +641,6 @@ function useAutocomplete(props) {
567
641
  }
568
642
  setValueState(newValue);
569
643
  };
570
- const isTouch = React.useRef(false);
571
644
  const selectNewValue = (event, option, reasonProp = 'selectOption', origin = 'options') => {
572
645
  let reason = reasonProp;
573
646
  let newValue = option;
@@ -594,7 +667,7 @@ function useAutocomplete(props) {
594
667
  if (!disableCloseOnSelect && (!event || !event.ctrlKey && !event.metaKey)) {
595
668
  handleClose(event, reason);
596
669
  }
597
- if (blurOnSelect === true || blurOnSelect === 'touch' && isTouch.current || blurOnSelect === 'mouse' && !isTouch.current) {
670
+ if (blurOnSelect === true || blurOnSelect === 'touch' && isTouchRef.current || blurOnSelect === 'mouse' && !isTouchRef.current) {
598
671
  inputRef.current.blur();
599
672
  }
600
673
  };
@@ -652,7 +725,6 @@ function useAutocomplete(props) {
652
725
  focusItem(nextItem);
653
726
  };
654
727
  const handleClear = event => {
655
- ignoreFocus.current = true;
656
728
  setInputValueState('');
657
729
  if (onInputChange) {
658
730
  onInputChange(event, '', 'clear');
@@ -780,29 +852,47 @@ function useAutocomplete(props) {
780
852
  }
781
853
  break;
782
854
  case 'Enter':
783
- if (highlightedIndexRef.current !== -1 && popupOpen) {
784
- const option = filteredOptions[highlightedIndexRef.current];
785
- const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
855
+ {
856
+ // In freeSolo, only select the highlighted option if the user hasn't
857
+ // typed new text (inputPristine) or explicitly interacted with an option
858
+ // (keyboard, mouse, or touch — any non-null reason). This lets typed
859
+ // text win over a programmatic highlight (reason=null, e.g. from
860
+ // syncHighlightedIndex matching a previous value) while still honoring
861
+ // deliberate user interactions like hovering a suggestion then pressing Enter.
862
+ const shouldSelectHighlighted = !freeSolo || inputPristine || highlightReasonRef.current !== null;
863
+ if (highlightedIndexRef.current !== -1 && popupOpen && shouldSelectHighlighted &&
864
+ // After a touch-scroll the highlight is stale (the user scrolled
865
+ // past it), so skip selection until the next deliberate interaction.
866
+ !touchScrolledRef.current) {
867
+ const option = filteredOptions[highlightedIndexRef.current];
868
+ const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
786
869
 
787
- // Avoid early form validation, let the end-users continue filling the form.
788
- event.preventDefault();
789
- if (disabled) {
790
- return;
791
- }
792
- selectNewValue(event, option, 'selectOption');
870
+ // Avoid early form validation, let the end-users continue filling the form.
871
+ event.preventDefault();
872
+ if (disabled) {
873
+ return;
874
+ }
875
+ selectNewValue(event, option, 'selectOption');
793
876
 
794
- // Move the selection to the end.
795
- if (autoComplete) {
796
- inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
797
- }
798
- } else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
799
- if (multiple) {
800
- // Allow people to add new values before they submit the form.
877
+ // Move the selection to the end.
878
+ if (autoComplete) {
879
+ inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
880
+ }
881
+ } else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
882
+ if (multiple) {
883
+ // Allow people to add new values before they submit the form.
884
+ event.preventDefault();
885
+ }
886
+ selectNewValue(event, inputValue, 'createOption', 'freeSolo');
887
+ } else if (popupOpen && touchScrolledRef.current) {
888
+ // The highlight is stale from a touch-scroll - close without selecting.
801
889
  event.preventDefault();
890
+ // This happens on Enter, but re-using "escape" as the closest `AutocompleteCloseReason`
891
+ // to avoid creating a new reason
892
+ handleClose(event, 'escape');
802
893
  }
803
- selectNewValue(event, inputValue, 'createOption', 'freeSolo');
894
+ break;
804
895
  }
805
- break;
806
896
  case 'Escape':
807
897
  if (popupOpen) {
808
898
  // Avoid Opera to exit fullscreen mode.
@@ -820,6 +910,10 @@ function useAutocomplete(props) {
820
910
  break;
821
911
  case 'Backspace':
822
912
  // Remove the value on the left of the "cursor"
913
+ if (ignoreNextBackspaceRef.current) {
914
+ ignoreNextBackspaceRef.current = false;
915
+ break;
916
+ }
823
917
  if (multiple && !readOnly && inputValue === '' && value.length > 0) {
824
918
  const index = focusedItem === -1 ? value.length - 1 : focusedItem;
825
919
  const newValue = value.slice();
@@ -827,6 +921,18 @@ function useAutocomplete(props) {
827
921
  handleValue(event, newValue, 'removeOption', {
828
922
  option: value[index]
829
923
  });
924
+ if (focusedItem !== -1) {
925
+ // Suppress the spurious Backspace VoiceOver synthesises on the
926
+ // input after focus returns to it. Clear it shortly after
927
+ // deletion so a later real Backspace is not ignored if the
928
+ // synthetic follow-up event never arrives.
929
+ ignoreNextBackspaceRef.current = true;
930
+ setTimeout(() => {
931
+ if (ignoreNextBackspaceRef.current) {
932
+ ignoreNextBackspaceRef.current = false;
933
+ }
934
+ }, 0);
935
+ }
830
936
  }
831
937
  if (!multiple && renderValue && !readOnly && inputValue === '') {
832
938
  handleValue(event, null, 'removeOption', {
@@ -880,7 +986,7 @@ function useAutocomplete(props) {
880
986
  }
881
987
  };
882
988
  const handleBlur = event => {
883
- // Ignore the event when using the scrollbar with IE11
989
+ // Ignore the event when using the scrollbar with IE 11
884
990
  if (unstable_isActiveElementInListbox(listboxRef)) {
885
991
  inputRef.current.focus();
886
992
  return;
@@ -888,7 +994,12 @@ function useAutocomplete(props) {
888
994
  setFocused(false);
889
995
  firstFocus.current = true;
890
996
  ignoreFocus.current = false;
891
- if (autoSelect && highlightedIndexRef.current !== -1 && popupOpen) {
997
+
998
+ // Auto-select the highlighted option on blur, but only if the highlight
999
+ // came from keyboard navigation or was set programmatically (autoHighlight).
1000
+ // Mouse hover and touch should not trigger selection — the user may have
1001
+ // moved the pointer over an option without intending to commit to it.
1002
+ if (autoSelect && highlightedIndexRef.current !== -1 && popupOpen && highlightReasonRef.current !== 'mouse' && highlightReasonRef.current !== 'touch') {
892
1003
  selectNewValue(event, filteredOptions[highlightedIndexRef.current], 'blur');
893
1004
  } else if (autoSelect && freeSolo && inputValue !== '') {
894
1005
  selectNewValue(event, inputValue, 'blur', 'freeSolo');
@@ -899,9 +1010,10 @@ function useAutocomplete(props) {
899
1010
  };
900
1011
  const handleInputChange = event => {
901
1012
  const newValue = event.target.value;
902
- if (inputValue !== newValue) {
1013
+ const valueChanged = inputValue !== newValue;
1014
+ if (valueChanged) {
903
1015
  setInputValueState(newValue);
904
- setInputPristine(false);
1016
+ touchScrolledRef.current = false;
905
1017
  if (onInputChange) {
906
1018
  onInputChange(event, newValue, 'input');
907
1019
  }
@@ -915,6 +1027,12 @@ function useAutocomplete(props) {
915
1027
  } else {
916
1028
  handleOpen(event);
917
1029
  }
1030
+
1031
+ // Called after handleOpen so it overrides handleOpen's setInputPristine(true)
1032
+ // when the first keystroke also opens the popup.
1033
+ if (valueChanged) {
1034
+ setInputPristine(false);
1035
+ }
918
1036
  };
919
1037
  const handleOptionMouseMove = event => {
920
1038
  const index = Number(event.currentTarget.getAttribute('data-option-index'));
@@ -924,20 +1042,46 @@ function useAutocomplete(props) {
924
1042
  index,
925
1043
  reason: 'mouse'
926
1044
  });
1045
+ } else {
1046
+ // The option is already highlighted (e.g. programmatically via autoHighlight),
1047
+ // but the user moved the mouse over it — mark as mouse-initiated so
1048
+ // autoSelect on blur correctly treats this as incidental hover.
1049
+ highlightReasonRef.current = 'mouse';
1050
+ }
1051
+ // Don't clear the touch-scroll guard while touch state is still latched.
1052
+ // After a touch gesture, browsers may fire compatibility mousemove
1053
+ // events; if those cleared the guard immediately, later compat events in
1054
+ // the same sequence could be misclassified as a real mouse interaction.
1055
+ // Touch state is cleared by the next deliberate interaction
1056
+ // (keyboard nav, handleOptionClick, or handleOpen).
1057
+ if (!isTouchRef.current) {
1058
+ touchScrolledRef.current = false;
1059
+ }
1060
+ };
1061
+ const handleListboxMouseLeave = event => {
1062
+ if (!resetHighlightOnMouseLeave || highlightedIndexRef.current === -1 || highlightReasonRef.current !== 'mouse' || isTouchRef.current) {
1063
+ return;
927
1064
  }
1065
+ setHighlightedIndex({
1066
+ event,
1067
+ index: -1,
1068
+ reason: 'mouse',
1069
+ preserveScroll: true
1070
+ });
928
1071
  };
929
1072
  const handleOptionTouchStart = event => {
1073
+ touchScrolledRef.current = false;
930
1074
  setHighlightedIndex({
931
1075
  event,
932
1076
  index: Number(event.currentTarget.getAttribute('data-option-index')),
933
1077
  reason: 'touch'
934
1078
  });
935
- isTouch.current = true;
1079
+ isTouchRef.current = true;
936
1080
  };
937
1081
  const handleOptionClick = event => {
938
1082
  const index = Number(event.currentTarget.getAttribute('data-option-index'));
939
1083
  selectNewValue(event, filteredOptions[index], 'selectOption');
940
- isTouch.current = false;
1084
+ isTouchRef.current = false;
941
1085
  };
942
1086
  const handleItemDelete = index => event => {
943
1087
  const newValue = value.slice();
@@ -962,11 +1106,11 @@ function useAutocomplete(props) {
962
1106
  // Prevent input blur when interacting with the combobox
963
1107
  const handleMouseDown = event => {
964
1108
  // Prevent focusing the input if click is anywhere outside the Autocomplete
965
- if (!event.currentTarget.contains(event.target)) {
1109
+ if (!(0, _contains.default)(event.currentTarget, event.target)) {
966
1110
  return;
967
1111
  }
968
1112
  // Don't interfere with interactions outside the input area (e.g. helper text)
969
- if (anchorEl && !anchorEl.contains(event.target)) {
1113
+ if (anchorEl && !(0, _contains.default)(anchorEl, event.target)) {
970
1114
  return;
971
1115
  }
972
1116
  if (event.target.getAttribute('id') !== id) {
@@ -977,11 +1121,11 @@ function useAutocomplete(props) {
977
1121
  // Focus the input when interacting with the combobox
978
1122
  const handleClick = event => {
979
1123
  // Prevent focusing the input if click is anywhere outside the Autocomplete
980
- if (!event.currentTarget.contains(event.target)) {
1124
+ if (!(0, _contains.default)(event.currentTarget, event.target)) {
981
1125
  return;
982
1126
  }
983
1127
  // Don't interfere with interactions outside the input area (e.g. helper text)
984
- if (anchorEl && !anchorEl.contains(event.target)) {
1128
+ if (anchorEl && !(0, _contains.default)(anchorEl, event.target)) {
985
1129
  return;
986
1130
  }
987
1131
  inputRef.current.focus();
@@ -1065,7 +1209,10 @@ function useAutocomplete(props) {
1065
1209
  getClearProps: () => ({
1066
1210
  tabIndex: -1,
1067
1211
  type: 'button',
1068
- onClick: handleClear
1212
+ onClick: event => {
1213
+ ignoreFocus.current = true;
1214
+ handleClear(event);
1215
+ }
1069
1216
  }),
1070
1217
  getItemProps: ({
1071
1218
  index = 0
@@ -1075,6 +1222,12 @@ function useAutocomplete(props) {
1075
1222
  }),
1076
1223
  'data-item-index': index,
1077
1224
  tabIndex: -1,
1225
+ onFocus: () => {
1226
+ // If focus on the item doesn't come from keyboard events, we update the state via onFocus.
1227
+ if (focusedItem !== index) {
1228
+ setFocusedItem(index);
1229
+ }
1230
+ },
1078
1231
  ...(!readOnly && {
1079
1232
  onDelete: multiple ? handleItemDelete(index) : handleSingleItemDelete
1080
1233
  })
@@ -1084,15 +1237,37 @@ function useAutocomplete(props) {
1084
1237
  type: 'button',
1085
1238
  onClick: handlePopupIndicator
1086
1239
  }),
1087
- getListboxProps: () => ({
1240
+ getListboxProps: (other = {}) => ({
1241
+ ...other,
1088
1242
  role: 'listbox',
1089
1243
  id: `${id}-listbox`,
1090
1244
  'aria-labelledby': `${id}-label`,
1091
1245
  'aria-multiselectable': multiple || undefined,
1092
1246
  ref: handleListboxRef,
1093
1247
  onMouseDown: event => {
1248
+ other.onMouseDown?.(event);
1249
+ if (event.defaultMuiPrevented) {
1250
+ return;
1251
+ }
1252
+
1094
1253
  // Prevent blur
1095
1254
  event.preventDefault();
1255
+ },
1256
+ onScroll: event => {
1257
+ other.onScroll?.(event);
1258
+ if (event.defaultMuiPrevented) {
1259
+ return;
1260
+ }
1261
+ if (isTouchRef.current) {
1262
+ touchScrolledRef.current = true;
1263
+ }
1264
+ },
1265
+ onMouseLeave: event => {
1266
+ other.onMouseLeave?.(event);
1267
+ if (event.defaultMuiPrevented) {
1268
+ return;
1269
+ }
1270
+ handleListboxMouseLeave(event);
1096
1271
  }
1097
1272
  }),
1098
1273
  getOptionProps: ({