@mui/material 7.3.10 → 7.3.11

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 (52) hide show
  1. package/Autocomplete/Autocomplete.js +63 -12
  2. package/Button/Button.js +18 -1
  3. package/CHANGELOG.md +35 -0
  4. package/Checkbox/Checkbox.js +2 -1
  5. package/ClickAwayListener/ClickAwayListener.js +2 -5
  6. package/Dialog/Dialog.js +11 -6
  7. package/Drawer/Drawer.js +18 -4
  8. package/Fab/Fab.js +7 -1
  9. package/InputBase/InputBase.js +39 -7
  10. package/ListItemButton/ListItemButton.js +7 -1
  11. package/MenuItem/MenuItem.js +6 -1
  12. package/Popper/BasePopper.js +23 -1
  13. package/Slider/useSlider.js +6 -3
  14. package/SwipeableDrawer/SwipeableDrawer.js +5 -4
  15. package/Switch/Switch.js +3 -4
  16. package/Unstable_TrapFocus/FocusTrap.js +15 -5
  17. package/esm/Autocomplete/Autocomplete.js +63 -12
  18. package/esm/Button/Button.js +18 -1
  19. package/esm/Checkbox/Checkbox.js +2 -1
  20. package/esm/ClickAwayListener/ClickAwayListener.js +2 -5
  21. package/esm/Dialog/Dialog.js +11 -6
  22. package/esm/Drawer/Drawer.js +18 -4
  23. package/esm/Fab/Fab.js +7 -1
  24. package/esm/InputBase/InputBase.js +39 -7
  25. package/esm/ListItemButton/ListItemButton.js +7 -1
  26. package/esm/MenuItem/MenuItem.js +6 -1
  27. package/esm/Popper/BasePopper.js +23 -1
  28. package/esm/Slider/useSlider.js +6 -3
  29. package/esm/SwipeableDrawer/SwipeableDrawer.js +5 -4
  30. package/esm/Switch/Switch.js +3 -4
  31. package/esm/Unstable_TrapFocus/FocusTrap.js +15 -5
  32. package/esm/index.js +1 -1
  33. package/esm/useAutocomplete/useAutocomplete.d.ts +4 -5
  34. package/esm/useAutocomplete/useAutocomplete.js +155 -46
  35. package/esm/utils/contains.d.ts +2 -0
  36. package/esm/utils/contains.js +2 -0
  37. package/esm/utils/focusable.d.ts +7 -0
  38. package/esm/utils/focusable.js +13 -0
  39. package/esm/utils/getEventTarget.d.ts +2 -0
  40. package/esm/utils/getEventTarget.js +2 -0
  41. package/esm/version/index.js +2 -2
  42. package/index.js +1 -1
  43. package/package.json +5 -5
  44. package/useAutocomplete/useAutocomplete.d.ts +4 -5
  45. package/useAutocomplete/useAutocomplete.js +155 -46
  46. package/utils/contains.d.ts +2 -0
  47. package/utils/contains.js +9 -0
  48. package/utils/focusable.d.ts +7 -0
  49. package/utils/focusable.js +20 -0
  50. package/utils/getEventTarget.d.ts +2 -0
  51. package/utils/getEventTarget.js +9 -0
  52. package/version/index.js +2 -2
@@ -8,7 +8,9 @@ import ownerDocument from '@mui/utils/ownerDocument';
8
8
  import getReactElementRef from '@mui/utils/getReactElementRef';
9
9
  import exactProp from '@mui/utils/exactProp';
10
10
  import elementAcceptingRef from '@mui/utils/elementAcceptingRef';
11
+ import contains from "../utils/contains.js";
11
12
  import getActiveElement from "../utils/getActiveElement.js";
13
+ import { getFocusTarget } from "../utils/focusable.js";
12
14
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
15
  // Inspired by https://github.com/focus-trap/tabbable
14
16
  const candidatesSelector = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])'].join(',');
@@ -107,21 +109,29 @@ function FocusTrap(props) {
107
109
  activated.current = !disableAutoFocus;
108
110
  }, [disableAutoFocus, open]);
109
111
  React.useEffect(() => {
112
+ // Reset on every mount — React 18 Strict Mode double-mounts leave this
113
+ // stuck at `true` after the cleanup of the previous mount set it.
114
+ ignoreNextEnforceFocus.current = false;
115
+
110
116
  // We might render an empty child.
111
117
  if (!open || !rootRef.current) {
112
118
  return;
113
119
  }
114
120
  const doc = ownerDocument(rootRef.current);
115
121
  const activeElement = getActiveElement(doc);
116
- if (!rootRef.current.contains(activeElement)) {
117
- if (!rootRef.current.hasAttribute('tabIndex')) {
122
+
123
+ // Prefer the explicitly marked focusable element. Fall back to the root
124
+ // element for generic FocusTrap usage.
125
+ const focusTarget = getFocusTarget(rootRef.current) ?? rootRef.current;
126
+ if (!contains(rootRef.current, activeElement)) {
127
+ if (!focusTarget.hasAttribute('tabIndex')) {
118
128
  if (process.env.NODE_ENV !== 'production') {
119
129
  console.error(['MUI: The modal content node does not accept focus.', 'For the benefit of assistive technologies, ' + 'the tabIndex of the node is being set to "-1".'].join('\n'));
120
130
  }
121
- rootRef.current.setAttribute('tabIndex', '-1');
131
+ focusTarget.setAttribute('tabIndex', '-1');
122
132
  }
123
133
  if (activated.current) {
124
- rootRef.current.focus();
134
+ focusTarget.focus();
125
135
  }
126
136
  }
127
137
  return () => {
@@ -181,7 +191,7 @@ function FocusTrap(props) {
181
191
  }
182
192
 
183
193
  // The focus is already inside
184
- if (rootElement.contains(activeEl)) {
194
+ if (contains(rootElement, activeEl)) {
185
195
  return;
186
196
  }
187
197
 
package/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/material v7.3.10
2
+ * @mui/material v7.3.11
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -46,12 +46,11 @@ export interface UseAutocompleteProps<Value, Multiple extends boolean | undefine
46
46
  */
47
47
  autoHighlight?: boolean | undefined;
48
48
  /**
49
- * If `true`, the selected option becomes the value of the input
50
- * when the Autocomplete loses focus unless the user chooses
51
- * a different option or changes the character string in the input.
49
+ * If `true`, the value is updated when the input loses focus under one of these conditions:
52
50
  *
53
- * When using the `freeSolo` mode, the typed value will be the input value
54
- * if the Autocomplete loses focus without highlighting an option.
51
+ * - An option highlighted via keyboard navigation or `autoHighlight` is selected.
52
+ * Hover and touch highlights are ignored.
53
+ * - Otherwise, in `freeSolo` mode, the typed text becomes the value.
55
54
  * @default false
56
55
  */
57
56
  autoSelect?: boolean | undefined;
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import * as React from 'react';
4
+ import contains from '@mui/utils/contains';
4
5
  import setRef from '@mui/utils/setRef';
5
6
  import useEventCallback from '@mui/utils/useEventCallback';
6
7
  import useControlled from '@mui/utils/useControlled';
@@ -55,7 +56,7 @@ const defaultFilterOptions = createFilterOptions();
55
56
 
56
57
  // Number of options to jump in list box when `Page Up` and `Page Down` keys are used.
57
58
  const pageSize = 5;
58
- const defaultIsActiveElementInListbox = listboxRef => listboxRef.current !== null && listboxRef.current.parentElement?.contains(document.activeElement);
59
+ const defaultIsActiveElementInListbox = listboxRef => listboxRef.current !== null && contains(listboxRef.current.parentElement, document.activeElement);
59
60
  const defaultIsOptionEqualToValue = (option, value) => option === value;
60
61
  const MULTIPLE_DEFAULT_VALUE = [];
61
62
  function getInputValue(value, multiple, getOptionLabel, renderValue) {
@@ -133,6 +134,20 @@ function useAutocomplete(props) {
133
134
  const defaultHighlighted = autoHighlight ? 0 : -1;
134
135
  const highlightedIndexRef = React.useRef(defaultHighlighted);
135
136
 
137
+ // Tracks how the current highlight was set:
138
+ // - 'keyboard' — arrow keys, Home/End, PageUp/PageDown
139
+ // - 'mouse' — handleOptionMouseMove
140
+ // - 'touch' — handleOptionTouchStart
141
+ // - null — programmatic (autoHighlight, value sync)
142
+ //
143
+ // This lets handleBlur and the Enter handler distinguish intentional
144
+ // interactions from incidental ones — e.g. autoSelect should not commit
145
+ // a highlight that came from a casual mouse hover.
146
+ /** @type {React.RefObject<AutocompleteHighlightChangeReason | null>} */
147
+ const highlightReasonRef = React.useRef(null);
148
+ const touchScrolledRef = React.useRef(false);
149
+ const isTouchRef = React.useRef(false);
150
+
136
151
  // Calculate the initial inputValue on mount only.
137
152
  // useRef ensures it doesn't update dynamically with defaultValue or value props.
138
153
  const initialInputValue = React.useRef(getInputValue(defaultValue ?? valueProp, multiple, getOptionLabel)).current;
@@ -272,22 +287,17 @@ function useAutocomplete(props) {
272
287
  }
273
288
  }
274
289
  }
275
- const setHighlightedIndex = useEventCallback(({
276
- event,
290
+ const syncHighlightedIndexToDOM = useEventCallback(({
277
291
  index,
278
- reason
292
+ reason,
293
+ preserveScroll = false
279
294
  }) => {
280
- highlightedIndexRef.current = index;
281
-
282
295
  // does the index exist?
283
296
  if (index === -1) {
284
297
  inputRef.current.removeAttribute('aria-activedescendant');
285
298
  } else {
286
299
  inputRef.current.setAttribute('aria-activedescendant', `${id}-option-${index}`);
287
300
  }
288
- if (onHighlightChange && ['mouse', 'keyboard', 'touch'].includes(reason)) {
289
- onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
290
- }
291
301
  if (!listboxRef.current) {
292
302
  return;
293
303
  }
@@ -306,7 +316,9 @@ function useAutocomplete(props) {
306
316
  return;
307
317
  }
308
318
  if (index === -1) {
309
- listboxNode.scrollTop = 0;
319
+ if (!preserveScroll) {
320
+ listboxNode.scrollTop = 0;
321
+ }
310
322
  return;
311
323
  }
312
324
  const option = listboxRef.current.querySelector(`[data-option-index="${index}"]`);
@@ -334,15 +346,46 @@ function useAutocomplete(props) {
334
346
  }
335
347
  }
336
348
  });
349
+ const setHighlightedIndex = useEventCallback(({
350
+ event,
351
+ index,
352
+ reason,
353
+ preserveScroll = false
354
+ }) => {
355
+ highlightedIndexRef.current = index;
356
+ highlightReasonRef.current = reason ?? null;
357
+ if (onHighlightChange && ['mouse', 'keyboard', 'touch'].includes(reason)) {
358
+ onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
359
+ }
360
+ syncHighlightedIndexToDOM({
361
+ index,
362
+ reason,
363
+ preserveScroll
364
+ });
365
+ });
366
+ const setHighlightedIndexFromSync = useEventCallback(({
367
+ index
368
+ }) => {
369
+ highlightedIndexRef.current = index;
370
+ syncHighlightedIndexToDOM({
371
+ index,
372
+ reason: highlightReasonRef.current
373
+ });
374
+ });
337
375
  const changeHighlightedIndex = useEventCallback(({
338
376
  event,
339
377
  diff,
340
378
  direction = 'next',
341
- reason
379
+ reason,
380
+ preserveScroll
342
381
  }) => {
343
382
  if (!popupOpen) {
344
383
  return;
345
384
  }
385
+ if (reason === 'keyboard') {
386
+ touchScrolledRef.current = false;
387
+ isTouchRef.current = false;
388
+ }
346
389
  const getNextIndex = () => {
347
390
  const maxIndex = filteredOptions.length - 1;
348
391
  if (diff === 'reset') {
@@ -379,7 +422,8 @@ function useAutocomplete(props) {
379
422
  setHighlightedIndex({
380
423
  index: nextIndex,
381
424
  reason,
382
- event
425
+ event,
426
+ preserveScroll
383
427
  });
384
428
 
385
429
  // Sync the content of the input with the highlighted option.
@@ -433,15 +477,24 @@ function useAutocomplete(props) {
433
477
  // If it exists and the value and the inputValue haven't changed, just update its index, otherwise continue execution
434
478
  const previousHighlightedOptionIndex = getPreviousHighlightedOptionIndex();
435
479
  if (previousHighlightedOptionIndex !== -1) {
436
- highlightedIndexRef.current = previousHighlightedOptionIndex;
480
+ // Keep the original highlight reason while re-syncing the DOM state.
481
+ // The highlighted option still exists after the filteredOptions array changed
482
+ // (e.g. async fetch returns new options while the user is mid-navigation),
483
+ // so the original interaction reason (keyboard, mouse, etc.) still applies.
484
+ setHighlightedIndexFromSync({
485
+ index: previousHighlightedOptionIndex
486
+ });
437
487
  return;
438
488
  }
439
489
  const valueItem = multiple ? value[0] : value;
440
490
 
441
491
  // The popup is empty, reset
442
492
  if (filteredOptions.length === 0 || valueItem == null) {
493
+ // Preserve scroll when new options are appended without changing the current filter.
494
+ 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]));
443
495
  changeHighlightedIndex({
444
- diff: 'reset'
496
+ diff: 'reset',
497
+ preserveScroll: isAppendOnly
445
498
  });
446
499
  return;
447
500
  }
@@ -453,8 +506,12 @@ function useAutocomplete(props) {
453
506
  if (valueItem != null) {
454
507
  const currentOption = filteredOptions[highlightedIndexRef.current];
455
508
 
456
- // Keep the current highlighted index if possible
457
- if (multiple && currentOption && value.findIndex(val => isOptionEqualToValue(currentOption, val)) !== -1) {
509
+ // Keep the current selected highlight while the popup stays open;
510
+ // on reopen, resync from the selected value.
511
+ if (multiple && currentOption && value.findIndex(val => isOptionEqualToValue(currentOption, val)) !== -1 && previousProps.filteredOptions?.length > 0) {
512
+ setHighlightedIndexFromSync({
513
+ index: highlightedIndexRef.current
514
+ });
458
515
  return;
459
516
  }
460
517
  const itemIndex = filteredOptions.findIndex(optionItem => isOptionEqualToValue(optionItem, valueItem));
@@ -489,7 +546,7 @@ function useAutocomplete(props) {
489
546
  filteredOptions.length,
490
547
  // Don't sync the highlighted index with the value when multiple
491
548
  // eslint-disable-next-line react-hooks/exhaustive-deps
492
- multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, popupOpen, inputValue, multiple]);
549
+ multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, setHighlightedIndexFromSync, popupOpen, inputValue, multiple]);
493
550
  const handleListboxRef = useEventCallback(node => {
494
551
  setRef(listboxRef, node);
495
552
  if (!node) {
@@ -535,6 +592,7 @@ function useAutocomplete(props) {
535
592
  }
536
593
  setOpenState(true);
537
594
  setInputPristine(true);
595
+ isTouchRef.current = false;
538
596
  if (onOpen) {
539
597
  onOpen(event);
540
598
  }
@@ -544,6 +602,8 @@ function useAutocomplete(props) {
544
602
  return;
545
603
  }
546
604
  setOpenState(false);
605
+ touchScrolledRef.current = false;
606
+ highlightReasonRef.current = null;
547
607
  if (onClose) {
548
608
  onClose(event, reason);
549
609
  }
@@ -561,7 +621,6 @@ function useAutocomplete(props) {
561
621
  }
562
622
  setValueState(newValue);
563
623
  };
564
- const isTouch = React.useRef(false);
565
624
  const selectNewValue = (event, option, reasonProp = 'selectOption', origin = 'options') => {
566
625
  let reason = reasonProp;
567
626
  let newValue = option;
@@ -588,7 +647,7 @@ function useAutocomplete(props) {
588
647
  if (!disableCloseOnSelect && (!event || !event.ctrlKey && !event.metaKey)) {
589
648
  handleClose(event, reason);
590
649
  }
591
- if (blurOnSelect === true || blurOnSelect === 'touch' && isTouch.current || blurOnSelect === 'mouse' && !isTouch.current) {
650
+ if (blurOnSelect === true || blurOnSelect === 'touch' && isTouchRef.current || blurOnSelect === 'mouse' && !isTouchRef.current) {
592
651
  inputRef.current.blur();
593
652
  }
594
653
  };
@@ -777,29 +836,47 @@ function useAutocomplete(props) {
777
836
  }
778
837
  break;
779
838
  case 'Enter':
780
- if (highlightedIndexRef.current !== -1 && popupOpen) {
781
- const option = filteredOptions[highlightedIndexRef.current];
782
- const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
839
+ {
840
+ // In freeSolo, only select the highlighted option if the user hasn't
841
+ // typed new text (inputPristine) or explicitly interacted with an option
842
+ // (keyboard, mouse, or touch — any non-null reason). This lets typed
843
+ // text win over a programmatic highlight (reason=null, e.g. from
844
+ // syncHighlightedIndex matching a previous value) while still honoring
845
+ // deliberate user interactions like hovering a suggestion then pressing Enter.
846
+ const shouldSelectHighlighted = !freeSolo || inputPristine || highlightReasonRef.current !== null;
847
+ if (highlightedIndexRef.current !== -1 && popupOpen && shouldSelectHighlighted &&
848
+ // After a touch-scroll the highlight is stale (the user scrolled
849
+ // past it), so skip selection until the next deliberate interaction.
850
+ !touchScrolledRef.current) {
851
+ const option = filteredOptions[highlightedIndexRef.current];
852
+ const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
783
853
 
784
- // Avoid early form validation, let the end-users continue filling the form.
785
- event.preventDefault();
786
- if (disabled) {
787
- return;
788
- }
789
- selectNewValue(event, option, 'selectOption');
854
+ // Avoid early form validation, let the end-users continue filling the form.
855
+ event.preventDefault();
856
+ if (disabled) {
857
+ return;
858
+ }
859
+ selectNewValue(event, option, 'selectOption');
790
860
 
791
- // Move the selection to the end.
792
- if (autoComplete) {
793
- inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
794
- }
795
- } else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
796
- if (multiple) {
797
- // Allow people to add new values before they submit the form.
861
+ // Move the selection to the end.
862
+ if (autoComplete) {
863
+ inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
864
+ }
865
+ } else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
866
+ if (multiple) {
867
+ // Allow people to add new values before they submit the form.
868
+ event.preventDefault();
869
+ }
870
+ selectNewValue(event, inputValue, 'createOption', 'freeSolo');
871
+ } else if (popupOpen && touchScrolledRef.current) {
872
+ // The highlight is stale from a touch-scroll - close without selecting.
798
873
  event.preventDefault();
874
+ // This happens on Enter, but re-using "escape" as the closest `AutocompleteCloseReason`
875
+ // to avoid creating a new reason
876
+ handleClose(event, 'escape');
799
877
  }
800
- selectNewValue(event, inputValue, 'createOption', 'freeSolo');
878
+ break;
801
879
  }
802
- break;
803
880
  case 'Escape':
804
881
  if (popupOpen) {
805
882
  // Avoid Opera to exit fullscreen mode.
@@ -885,7 +962,12 @@ function useAutocomplete(props) {
885
962
  setFocused(false);
886
963
  firstFocus.current = true;
887
964
  ignoreFocus.current = false;
888
- if (autoSelect && highlightedIndexRef.current !== -1 && popupOpen) {
965
+
966
+ // Auto-select the highlighted option on blur, but only if the highlight
967
+ // came from keyboard navigation or was set programmatically (autoHighlight).
968
+ // Mouse hover and touch should not trigger selection — the user may have
969
+ // moved the pointer over an option without intending to commit to it.
970
+ if (autoSelect && highlightedIndexRef.current !== -1 && popupOpen && highlightReasonRef.current !== 'mouse' && highlightReasonRef.current !== 'touch') {
889
971
  selectNewValue(event, filteredOptions[highlightedIndexRef.current], 'blur');
890
972
  } else if (autoSelect && freeSolo && inputValue !== '') {
891
973
  selectNewValue(event, inputValue, 'blur', 'freeSolo');
@@ -896,9 +978,10 @@ function useAutocomplete(props) {
896
978
  };
897
979
  const handleInputChange = event => {
898
980
  const newValue = event.target.value;
899
- if (inputValue !== newValue) {
981
+ const valueChanged = inputValue !== newValue;
982
+ if (valueChanged) {
900
983
  setInputValueState(newValue);
901
- setInputPristine(false);
984
+ touchScrolledRef.current = false;
902
985
  if (onInputChange) {
903
986
  onInputChange(event, newValue, 'input');
904
987
  }
@@ -912,6 +995,12 @@ function useAutocomplete(props) {
912
995
  } else {
913
996
  handleOpen(event);
914
997
  }
998
+
999
+ // Called after handleOpen so it overrides handleOpen's setInputPristine(true)
1000
+ // when the first keystroke also opens the popup.
1001
+ if (valueChanged) {
1002
+ setInputPristine(false);
1003
+ }
915
1004
  };
916
1005
  const handleOptionMouseMove = event => {
917
1006
  const index = Number(event.currentTarget.getAttribute('data-option-index'));
@@ -921,20 +1010,35 @@ function useAutocomplete(props) {
921
1010
  index,
922
1011
  reason: 'mouse'
923
1012
  });
1013
+ } else {
1014
+ // The option is already highlighted (e.g. programmatically via autoHighlight),
1015
+ // but the user moved the mouse over it — mark as mouse-initiated so
1016
+ // autoSelect on blur correctly treats this as incidental hover.
1017
+ highlightReasonRef.current = 'mouse';
1018
+ }
1019
+ // Don't clear the touch-scroll guard while touch state is still latched.
1020
+ // After a touch gesture, browsers may fire compatibility mousemove
1021
+ // events; if those cleared the guard immediately, later compat events in
1022
+ // the same sequence could be misclassified as a real mouse interaction.
1023
+ // Touch state is cleared by the next deliberate interaction
1024
+ // (keyboard nav, handleOptionClick, or handleOpen).
1025
+ if (!isTouchRef.current) {
1026
+ touchScrolledRef.current = false;
924
1027
  }
925
1028
  };
926
1029
  const handleOptionTouchStart = event => {
1030
+ touchScrolledRef.current = false;
927
1031
  setHighlightedIndex({
928
1032
  event,
929
1033
  index: Number(event.currentTarget.getAttribute('data-option-index')),
930
1034
  reason: 'touch'
931
1035
  });
932
- isTouch.current = true;
1036
+ isTouchRef.current = true;
933
1037
  };
934
1038
  const handleOptionClick = event => {
935
1039
  const index = Number(event.currentTarget.getAttribute('data-option-index'));
936
1040
  selectNewValue(event, filteredOptions[index], 'selectOption');
937
- isTouch.current = false;
1041
+ isTouchRef.current = false;
938
1042
  };
939
1043
  const handleItemDelete = index => event => {
940
1044
  const newValue = value.slice();
@@ -959,11 +1063,11 @@ function useAutocomplete(props) {
959
1063
  // Prevent input blur when interacting with the combobox
960
1064
  const handleMouseDown = event => {
961
1065
  // Prevent focusing the input if click is anywhere outside the Autocomplete
962
- if (!event.currentTarget.contains(event.target)) {
1066
+ if (!contains(event.currentTarget, event.target)) {
963
1067
  return;
964
1068
  }
965
1069
  // Don't interfere with interactions outside the input area (e.g. helper text)
966
- if (anchorEl && !anchorEl.contains(event.target)) {
1070
+ if (anchorEl && !contains(anchorEl, event.target)) {
967
1071
  return;
968
1072
  }
969
1073
  if (event.target.getAttribute('id') !== id) {
@@ -974,11 +1078,11 @@ function useAutocomplete(props) {
974
1078
  // Focus the input when interacting with the combobox
975
1079
  const handleClick = event => {
976
1080
  // Prevent focusing the input if click is anywhere outside the Autocomplete
977
- if (!event.currentTarget.contains(event.target)) {
1081
+ if (!contains(event.currentTarget, event.target)) {
978
1082
  return;
979
1083
  }
980
1084
  // Don't interfere with interactions outside the input area (e.g. helper text)
981
- if (anchorEl && !anchorEl.contains(event.target)) {
1085
+ if (anchorEl && !contains(anchorEl, event.target)) {
982
1086
  return;
983
1087
  }
984
1088
  inputRef.current.focus();
@@ -1103,6 +1207,11 @@ function useAutocomplete(props) {
1103
1207
  onMouseDown: event => {
1104
1208
  // Prevent blur
1105
1209
  event.preventDefault();
1210
+ },
1211
+ onScroll: () => {
1212
+ if (isTouchRef.current) {
1213
+ touchScrolledRef.current = true;
1214
+ }
1106
1215
  }
1107
1216
  }),
1108
1217
  getOptionProps: ({
@@ -0,0 +1,2 @@
1
+ import contains from '@mui/utils/contains';
2
+ export default contains;
@@ -0,0 +1,2 @@
1
+ import contains from '@mui/utils/contains';
2
+ export default contains;
@@ -0,0 +1,7 @@
1
+ export declare const FOCUSABLE_ATTRIBUTE = "data-mui-focusable";
2
+ /**
3
+ * Returns the element marked as the initial focus target inside a focus trap.
4
+ * The root element takes precedence over marked descendants so components can
5
+ * opt into focusing their own root surface directly.
6
+ */
7
+ export declare function getFocusTarget(rootElement: HTMLElement | null | undefined): HTMLElement | null;
@@ -0,0 +1,13 @@
1
+ export const FOCUSABLE_ATTRIBUTE = 'data-mui-focusable';
2
+
3
+ /**
4
+ * Returns the element marked as the initial focus target inside a focus trap.
5
+ * The root element takes precedence over marked descendants so components can
6
+ * opt into focusing their own root surface directly.
7
+ */
8
+ export function getFocusTarget(rootElement) {
9
+ if (!rootElement) {
10
+ return null;
11
+ }
12
+ return rootElement.hasAttribute(FOCUSABLE_ATTRIBUTE) ? rootElement : rootElement.querySelector(`[${FOCUSABLE_ATTRIBUTE}]`);
13
+ }
@@ -0,0 +1,2 @@
1
+ import getEventTarget from '@mui/utils/getEventTarget';
2
+ export default getEventTarget;
@@ -0,0 +1,2 @@
1
+ import getEventTarget from '@mui/utils/getEventTarget';
2
+ export default getEventTarget;
@@ -1,6 +1,6 @@
1
- export const version = "7.3.10";
1
+ export const version = "7.3.11";
2
2
  export const major = Number("7");
3
3
  export const minor = Number("3");
4
- export const patch = Number("10");
4
+ export const patch = Number("11");
5
5
  export const prerelease = undefined;
6
6
  export default version;
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/material v7.3.10
2
+ * @mui/material v7.3.11
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/material",
3
- "version": "7.3.10",
3
+ "version": "7.3.11",
4
4
  "author": "MUI Team",
5
5
  "description": "Material UI is an open-source React component library that implements Google's Material Design. It's comprehensive and can be used in production out of the box.",
6
6
  "keywords": [
@@ -33,10 +33,10 @@
33
33
  "prop-types": "^15.8.1",
34
34
  "react-is": "^19.2.3",
35
35
  "react-transition-group": "^4.4.5",
36
- "@mui/core-downloads-tracker": "^7.3.10",
36
+ "@mui/core-downloads-tracker": "^7.3.11",
37
+ "@mui/utils": "^7.3.11",
37
38
  "@mui/types": "^7.4.12",
38
- "@mui/utils": "^7.3.10",
39
- "@mui/system": "^7.3.10"
39
+ "@mui/system": "^7.3.11"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@emotion/react": "^11.5.0",
@@ -44,7 +44,7 @@
44
44
  "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
45
45
  "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
46
46
  "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
47
- "@mui/material-pigment-css": "^7.3.10"
47
+ "@mui/material-pigment-css": "^7.3.11"
48
48
  },
49
49
  "peerDependenciesMeta": {
50
50
  "@types/react": {
@@ -46,12 +46,11 @@ export interface UseAutocompleteProps<Value, Multiple extends boolean | undefine
46
46
  */
47
47
  autoHighlight?: boolean | undefined;
48
48
  /**
49
- * If `true`, the selected option becomes the value of the input
50
- * when the Autocomplete loses focus unless the user chooses
51
- * a different option or changes the character string in the input.
49
+ * If `true`, the value is updated when the input loses focus under one of these conditions:
52
50
  *
53
- * When using the `freeSolo` mode, the typed value will be the input value
54
- * if the Autocomplete loses focus without highlighting an option.
51
+ * - An option highlighted via keyboard navigation or `autoHighlight` is selected.
52
+ * Hover and touch highlights are ignored.
53
+ * - Otherwise, in `freeSolo` mode, the typed text becomes the value.
55
54
  * @default false
56
55
  */
57
56
  autoSelect?: boolean | undefined;