@mui/material 9.0.0 → 9.0.1

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 (130) hide show
  1. package/Autocomplete/Autocomplete.js +65 -11
  2. package/Autocomplete/Autocomplete.mjs +65 -11
  3. package/Avatar/Avatar.js +4 -0
  4. package/Avatar/Avatar.mjs +4 -0
  5. package/Badge/Badge.js +3 -0
  6. package/Badge/Badge.mjs +3 -0
  7. package/Button/Button.js +19 -2
  8. package/Button/Button.mjs +19 -2
  9. package/ButtonBase/ButtonBase.d.mts +7 -0
  10. package/ButtonBase/ButtonBase.d.ts +7 -0
  11. package/ButtonBase/ButtonBase.js +5 -2
  12. package/ButtonBase/ButtonBase.mjs +5 -2
  13. package/CHANGELOG.md +132 -1245
  14. package/Checkbox/Checkbox.js +2 -1
  15. package/Checkbox/Checkbox.mjs +2 -1
  16. package/CircularProgress/CircularProgress.d.mts +12 -2
  17. package/CircularProgress/CircularProgress.d.ts +12 -2
  18. package/CircularProgress/CircularProgress.js +33 -6
  19. package/CircularProgress/CircularProgress.mjs +33 -6
  20. package/ClickAwayListener/ClickAwayListener.js +3 -6
  21. package/ClickAwayListener/ClickAwayListener.mjs +3 -6
  22. package/Dialog/Dialog.js +11 -6
  23. package/Dialog/Dialog.mjs +11 -6
  24. package/Drawer/Drawer.js +18 -4
  25. package/Drawer/Drawer.mjs +18 -4
  26. package/Fab/Fab.js +7 -1
  27. package/Fab/Fab.mjs +7 -1
  28. package/FilledInput/FilledInput.d.mts +4 -0
  29. package/FilledInput/FilledInput.d.ts +4 -0
  30. package/FilledInput/FilledInput.js +18 -20
  31. package/FilledInput/FilledInput.mjs +18 -20
  32. package/FormControl/useFormControl.d.mts +12 -2
  33. package/FormControl/useFormControl.d.ts +12 -2
  34. package/FormControl/useFormControl.js +13 -0
  35. package/FormControl/useFormControl.mjs +12 -0
  36. package/FormControlLabel/FormControlLabel.js +5 -8
  37. package/FormControlLabel/FormControlLabel.mjs +5 -8
  38. package/FormGroup/FormGroup.js +2 -5
  39. package/FormGroup/FormGroup.mjs +2 -5
  40. package/FormHelperText/FormHelperText.js +2 -5
  41. package/FormHelperText/FormHelperText.mjs +2 -5
  42. package/FormLabel/FormLabel.js +2 -5
  43. package/FormLabel/FormLabel.mjs +2 -5
  44. package/IconButton/IconButton.js +1 -8
  45. package/IconButton/IconButton.mjs +1 -8
  46. package/Input/Input.d.mts +4 -0
  47. package/Input/Input.d.ts +4 -0
  48. package/Input/Input.js +6 -0
  49. package/Input/Input.mjs +6 -0
  50. package/InputBase/InputBase.d.mts +2 -1
  51. package/InputBase/InputBase.d.ts +2 -1
  52. package/InputBase/InputBase.js +50 -15
  53. package/InputBase/InputBase.mjs +50 -15
  54. package/InputLabel/InputLabel.js +5 -8
  55. package/InputLabel/InputLabel.mjs +5 -8
  56. package/LinearProgress/LinearProgress.d.mts +12 -2
  57. package/LinearProgress/LinearProgress.d.ts +12 -2
  58. package/LinearProgress/LinearProgress.js +42 -10
  59. package/LinearProgress/LinearProgress.mjs +42 -10
  60. package/List/List.js +2 -1
  61. package/List/List.mjs +2 -1
  62. package/ListItemButton/ListItemButton.js +7 -1
  63. package/ListItemButton/ListItemButton.mjs +7 -1
  64. package/MenuItem/MenuItem.js +7 -1
  65. package/MenuItem/MenuItem.mjs +7 -1
  66. package/MenuList/MenuList.js +2 -1
  67. package/MenuList/MenuList.mjs +2 -1
  68. package/NativeSelect/NativeSelect.js +2 -5
  69. package/NativeSelect/NativeSelect.mjs +2 -5
  70. package/OutlinedInput/OutlinedInput.js +13 -23
  71. package/OutlinedInput/OutlinedInput.mjs +13 -23
  72. package/PigmentContainer/PigmentContainer.js +0 -1
  73. package/PigmentContainer/PigmentContainer.mjs +0 -1
  74. package/Popper/BasePopper.js +23 -1
  75. package/Popper/BasePopper.mjs +23 -1
  76. package/Select/Select.js +2 -5
  77. package/Select/Select.mjs +2 -5
  78. package/Select/SelectInput.js +164 -2
  79. package/Select/SelectInput.mjs +164 -2
  80. package/Slide/Slide.js +48 -26
  81. package/Slide/Slide.mjs +49 -27
  82. package/Slider/Slider.js +10 -1
  83. package/Slider/Slider.mjs +10 -1
  84. package/Slider/useSlider.js +3 -2
  85. package/Slider/useSlider.mjs +3 -2
  86. package/SwipeableDrawer/SwipeableDrawer.js +7 -3
  87. package/SwipeableDrawer/SwipeableDrawer.mjs +7 -3
  88. package/Switch/Switch.js +7 -6
  89. package/Switch/Switch.mjs +7 -6
  90. package/Tabs/ScrollbarSize.js +2 -1
  91. package/Tabs/ScrollbarSize.mjs +2 -1
  92. package/Tabs/Tabs.js +2 -1
  93. package/Tabs/Tabs.mjs +2 -1
  94. package/Tooltip/Tooltip.js +26 -108
  95. package/Tooltip/Tooltip.mjs +26 -108
  96. package/Unstable_TrapFocus/FocusTrap.js +18 -14
  97. package/Unstable_TrapFocus/FocusTrap.mjs +18 -14
  98. package/index.js +1 -1
  99. package/index.mjs +1 -1
  100. package/package.json +49 -49
  101. package/styles/responsiveFontSizes.js +19 -8
  102. package/styles/responsiveFontSizes.mjs +19 -8
  103. package/styles/useThemeProps.d.mts +3 -3
  104. package/styles/useThemeProps.d.ts +3 -3
  105. package/transitions/utils.d.mts +17 -0
  106. package/transitions/utils.d.ts +17 -0
  107. package/transitions/utils.js +64 -0
  108. package/transitions/utils.mjs +63 -0
  109. package/useAutocomplete/useAutocomplete.d.mts +4 -5
  110. package/useAutocomplete/useAutocomplete.d.ts +4 -5
  111. package/useAutocomplete/useAutocomplete.js +166 -53
  112. package/useAutocomplete/useAutocomplete.mjs +166 -53
  113. package/utils/contains.d.mts +2 -0
  114. package/utils/contains.d.ts +2 -0
  115. package/utils/contains.js +9 -0
  116. package/utils/contains.mjs +2 -0
  117. package/utils/focusable.d.mts +7 -0
  118. package/utils/focusable.d.ts +7 -0
  119. package/utils/focusable.js +20 -0
  120. package/utils/focusable.mjs +13 -0
  121. package/utils/getEventTarget.d.mts +2 -0
  122. package/utils/getEventTarget.d.ts +2 -0
  123. package/utils/getEventTarget.js +9 -0
  124. package/utils/getEventTarget.mjs +2 -0
  125. package/utils/mergeSlotProps.js +2 -8
  126. package/utils/mergeSlotProps.mjs +1 -8
  127. package/version/index.js +2 -2
  128. package/version/index.mjs +2 -2
  129. package/FormControl/formControlState.js +0 -21
  130. package/FormControl/formControlState.mjs +0 -15
@@ -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;
@@ -149,10 +164,12 @@ function useAutocomplete(props) {
149
164
  });
150
165
  const [focused, setFocused] = React.useState(false);
151
166
  const resetInputValue = React.useCallback((event, newValue, reason) => {
152
- // retain current `inputValue` if new option isn't selected and `clearOnBlur` is false
153
- // When `multiple` is enabled, `newValue` is an array of all selected items including the newly selected item
167
+ // Retain the current `inputValue` when no new option is selected and `clearOnBlur` is false.
168
+ // In `multiple` mode, `newValue` is the next value array, so only length growth counts as a selection.
154
169
  const isOptionSelected = multiple ? value.length < newValue.length : newValue !== null;
155
- if (!isOptionSelected && !clearOnBlur) {
170
+ // A controlled single-value `freeSolo` reset to `null` should still clear the input.
171
+ const shouldClearOnReset = reason === 'reset' && freeSolo && !multiple && newValue === null;
172
+ if (!isOptionSelected && !clearOnBlur && !shouldClearOnReset) {
156
173
  return;
157
174
  }
158
175
  const newInputValue = getInputValue(newValue, multiple, getOptionLabel, renderValue);
@@ -163,7 +180,7 @@ function useAutocomplete(props) {
163
180
  if (onInputChange) {
164
181
  onInputChange(event, newInputValue, reason);
165
182
  }
166
- }, [getOptionLabel, inputValue, multiple, onInputChange, setInputValueState, clearOnBlur, value, renderValue]);
183
+ }, [getOptionLabel, inputValue, multiple, onInputChange, setInputValueState, clearOnBlur, freeSolo, value, renderValue]);
167
184
  const [open, setOpenState] = useControlled({
168
185
  controlled: openProp,
169
186
  default: false,
@@ -270,22 +287,17 @@ function useAutocomplete(props) {
270
287
  }
271
288
  }
272
289
  }
273
- const setHighlightedIndex = useEventCallback(({
274
- event,
290
+ const syncHighlightedIndexToDOM = useEventCallback(({
275
291
  index,
276
- reason
292
+ reason,
293
+ preserveScroll = false
277
294
  }) => {
278
- highlightedIndexRef.current = index;
279
-
280
295
  // does the index exist?
281
296
  if (index === -1) {
282
297
  inputRef.current.removeAttribute('aria-activedescendant');
283
298
  } else {
284
299
  inputRef.current.setAttribute('aria-activedescendant', `${id}-option-${index}`);
285
300
  }
286
- if (onHighlightChange && ['mouse', 'keyboard', 'touch'].includes(reason)) {
287
- onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
288
- }
289
301
  if (!listboxRef.current) {
290
302
  return;
291
303
  }
@@ -304,7 +316,9 @@ function useAutocomplete(props) {
304
316
  return;
305
317
  }
306
318
  if (index === -1) {
307
- listboxNode.scrollTop = 0;
319
+ if (!preserveScroll) {
320
+ listboxNode.scrollTop = 0;
321
+ }
308
322
  return;
309
323
  }
310
324
  const option = listboxRef.current.querySelector(`[data-option-index="${index}"]`);
@@ -332,15 +346,46 @@ function useAutocomplete(props) {
332
346
  }
333
347
  }
334
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
+ });
335
375
  const changeHighlightedIndex = useEventCallback(({
336
376
  event,
337
377
  diff,
338
378
  direction = 'next',
339
- reason
379
+ reason,
380
+ preserveScroll
340
381
  }) => {
341
382
  if (!popupOpen) {
342
383
  return;
343
384
  }
385
+ if (reason === 'keyboard') {
386
+ touchScrolledRef.current = false;
387
+ isTouchRef.current = false;
388
+ }
344
389
  const getNextIndex = () => {
345
390
  const maxIndex = filteredOptions.length - 1;
346
391
  if (diff === 'reset') {
@@ -377,7 +422,8 @@ function useAutocomplete(props) {
377
422
  setHighlightedIndex({
378
423
  index: nextIndex,
379
424
  reason,
380
- event
425
+ event,
426
+ preserveScroll
381
427
  });
382
428
 
383
429
  // Sync the content of the input with the highlighted option.
@@ -431,15 +477,24 @@ function useAutocomplete(props) {
431
477
  // If it exists and the value and the inputValue haven't changed, just update its index, otherwise continue execution
432
478
  const previousHighlightedOptionIndex = getPreviousHighlightedOptionIndex();
433
479
  if (previousHighlightedOptionIndex !== -1) {
434
- 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
+ });
435
487
  return;
436
488
  }
437
489
  const valueItem = multiple ? value[0] : value;
438
490
 
439
491
  // The popup is empty, reset
440
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]));
441
495
  changeHighlightedIndex({
442
- diff: 'reset'
496
+ diff: 'reset',
497
+ preserveScroll: isAppendOnly
443
498
  });
444
499
  return;
445
500
  }
@@ -451,8 +506,12 @@ function useAutocomplete(props) {
451
506
  if (valueItem != null) {
452
507
  const currentOption = filteredOptions[highlightedIndexRef.current];
453
508
 
454
- // Keep the current highlighted index if possible
455
- 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
+ });
456
515
  return;
457
516
  }
458
517
  const itemIndex = filteredOptions.findIndex(optionItem => isOptionEqualToValue(optionItem, valueItem));
@@ -487,7 +546,7 @@ function useAutocomplete(props) {
487
546
  filteredOptions.length,
488
547
  // Don't sync the highlighted index with the value when multiple
489
548
  // eslint-disable-next-line react-hooks/exhaustive-deps
490
- multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, popupOpen, inputValue, multiple]);
549
+ multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, setHighlightedIndexFromSync, popupOpen, inputValue, multiple]);
491
550
  const handleListboxRef = useEventCallback(node => {
492
551
  setRef(listboxRef, node);
493
552
  if (!node) {
@@ -533,6 +592,7 @@ function useAutocomplete(props) {
533
592
  }
534
593
  setOpenState(true);
535
594
  setInputPristine(true);
595
+ isTouchRef.current = false;
536
596
  if (onOpen) {
537
597
  onOpen(event);
538
598
  }
@@ -542,6 +602,8 @@ function useAutocomplete(props) {
542
602
  return;
543
603
  }
544
604
  setOpenState(false);
605
+ touchScrolledRef.current = false;
606
+ highlightReasonRef.current = null;
545
607
  if (onClose) {
546
608
  onClose(event, reason);
547
609
  }
@@ -559,7 +621,6 @@ function useAutocomplete(props) {
559
621
  }
560
622
  setValueState(newValue);
561
623
  };
562
- const isTouch = React.useRef(false);
563
624
  const selectNewValue = (event, option, reasonProp = 'selectOption', origin = 'options') => {
564
625
  let reason = reasonProp;
565
626
  let newValue = option;
@@ -586,7 +647,7 @@ function useAutocomplete(props) {
586
647
  if (!disableCloseOnSelect && (!event || !event.ctrlKey && !event.metaKey)) {
587
648
  handleClose(event, reason);
588
649
  }
589
- if (blurOnSelect === true || blurOnSelect === 'touch' && isTouch.current || blurOnSelect === 'mouse' && !isTouch.current) {
650
+ if (blurOnSelect === true || blurOnSelect === 'touch' && isTouchRef.current || blurOnSelect === 'mouse' && !isTouchRef.current) {
590
651
  inputRef.current.blur();
591
652
  }
592
653
  };
@@ -644,7 +705,6 @@ function useAutocomplete(props) {
644
705
  focusItem(nextItem);
645
706
  };
646
707
  const handleClear = event => {
647
- ignoreFocus.current = true;
648
708
  setInputValueState('');
649
709
  if (onInputChange) {
650
710
  onInputChange(event, '', 'clear');
@@ -772,29 +832,47 @@ function useAutocomplete(props) {
772
832
  }
773
833
  break;
774
834
  case 'Enter':
775
- if (highlightedIndexRef.current !== -1 && popupOpen) {
776
- const option = filteredOptions[highlightedIndexRef.current];
777
- const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
835
+ {
836
+ // In freeSolo, only select the highlighted option if the user hasn't
837
+ // typed new text (inputPristine) or explicitly interacted with an option
838
+ // (keyboard, mouse, or touch — any non-null reason). This lets typed
839
+ // text win over a programmatic highlight (reason=null, e.g. from
840
+ // syncHighlightedIndex matching a previous value) while still honoring
841
+ // deliberate user interactions like hovering a suggestion then pressing Enter.
842
+ const shouldSelectHighlighted = !freeSolo || inputPristine || highlightReasonRef.current !== null;
843
+ if (highlightedIndexRef.current !== -1 && popupOpen && shouldSelectHighlighted &&
844
+ // After a touch-scroll the highlight is stale (the user scrolled
845
+ // past it), so skip selection until the next deliberate interaction.
846
+ !touchScrolledRef.current) {
847
+ const option = filteredOptions[highlightedIndexRef.current];
848
+ const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
778
849
 
779
- // Avoid early form validation, let the end-users continue filling the form.
780
- event.preventDefault();
781
- if (disabled) {
782
- return;
783
- }
784
- selectNewValue(event, option, 'selectOption');
850
+ // Avoid early form validation, let the end-users continue filling the form.
851
+ event.preventDefault();
852
+ if (disabled) {
853
+ return;
854
+ }
855
+ selectNewValue(event, option, 'selectOption');
785
856
 
786
- // Move the selection to the end.
787
- if (autoComplete) {
788
- inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
789
- }
790
- } else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
791
- if (multiple) {
792
- // Allow people to add new values before they submit the form.
857
+ // Move the selection to the end.
858
+ if (autoComplete) {
859
+ inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
860
+ }
861
+ } else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
862
+ if (multiple) {
863
+ // Allow people to add new values before they submit the form.
864
+ event.preventDefault();
865
+ }
866
+ selectNewValue(event, inputValue, 'createOption', 'freeSolo');
867
+ } else if (popupOpen && touchScrolledRef.current) {
868
+ // The highlight is stale from a touch-scroll - close without selecting.
793
869
  event.preventDefault();
870
+ // This happens on Enter, but re-using "escape" as the closest `AutocompleteCloseReason`
871
+ // to avoid creating a new reason
872
+ handleClose(event, 'escape');
794
873
  }
795
- selectNewValue(event, inputValue, 'createOption', 'freeSolo');
874
+ break;
796
875
  }
797
- break;
798
876
  case 'Escape':
799
877
  if (popupOpen) {
800
878
  // Avoid Opera to exit fullscreen mode.
@@ -872,7 +950,7 @@ function useAutocomplete(props) {
872
950
  }
873
951
  };
874
952
  const handleBlur = event => {
875
- // Ignore the event when using the scrollbar with IE11
953
+ // Ignore the event when using the scrollbar with IE 11
876
954
  if (unstable_isActiveElementInListbox(listboxRef)) {
877
955
  inputRef.current.focus();
878
956
  return;
@@ -880,7 +958,12 @@ function useAutocomplete(props) {
880
958
  setFocused(false);
881
959
  firstFocus.current = true;
882
960
  ignoreFocus.current = false;
883
- if (autoSelect && highlightedIndexRef.current !== -1 && popupOpen) {
961
+
962
+ // Auto-select the highlighted option on blur, but only if the highlight
963
+ // came from keyboard navigation or was set programmatically (autoHighlight).
964
+ // Mouse hover and touch should not trigger selection — the user may have
965
+ // moved the pointer over an option without intending to commit to it.
966
+ if (autoSelect && highlightedIndexRef.current !== -1 && popupOpen && highlightReasonRef.current !== 'mouse' && highlightReasonRef.current !== 'touch') {
884
967
  selectNewValue(event, filteredOptions[highlightedIndexRef.current], 'blur');
885
968
  } else if (autoSelect && freeSolo && inputValue !== '') {
886
969
  selectNewValue(event, inputValue, 'blur', 'freeSolo');
@@ -891,9 +974,10 @@ function useAutocomplete(props) {
891
974
  };
892
975
  const handleInputChange = event => {
893
976
  const newValue = event.target.value;
894
- if (inputValue !== newValue) {
977
+ const valueChanged = inputValue !== newValue;
978
+ if (valueChanged) {
895
979
  setInputValueState(newValue);
896
- setInputPristine(false);
980
+ touchScrolledRef.current = false;
897
981
  if (onInputChange) {
898
982
  onInputChange(event, newValue, 'input');
899
983
  }
@@ -907,6 +991,12 @@ function useAutocomplete(props) {
907
991
  } else {
908
992
  handleOpen(event);
909
993
  }
994
+
995
+ // Called after handleOpen so it overrides handleOpen's setInputPristine(true)
996
+ // when the first keystroke also opens the popup.
997
+ if (valueChanged) {
998
+ setInputPristine(false);
999
+ }
910
1000
  };
911
1001
  const handleOptionMouseMove = event => {
912
1002
  const index = Number(event.currentTarget.getAttribute('data-option-index'));
@@ -916,20 +1006,35 @@ function useAutocomplete(props) {
916
1006
  index,
917
1007
  reason: 'mouse'
918
1008
  });
1009
+ } else {
1010
+ // The option is already highlighted (e.g. programmatically via autoHighlight),
1011
+ // but the user moved the mouse over it — mark as mouse-initiated so
1012
+ // autoSelect on blur correctly treats this as incidental hover.
1013
+ highlightReasonRef.current = 'mouse';
1014
+ }
1015
+ // Don't clear the touch-scroll guard while touch state is still latched.
1016
+ // After a touch gesture, browsers may fire compatibility mousemove
1017
+ // events; if those cleared the guard immediately, later compat events in
1018
+ // the same sequence could be misclassified as a real mouse interaction.
1019
+ // Touch state is cleared by the next deliberate interaction
1020
+ // (keyboard nav, handleOptionClick, or handleOpen).
1021
+ if (!isTouchRef.current) {
1022
+ touchScrolledRef.current = false;
919
1023
  }
920
1024
  };
921
1025
  const handleOptionTouchStart = event => {
1026
+ touchScrolledRef.current = false;
922
1027
  setHighlightedIndex({
923
1028
  event,
924
1029
  index: Number(event.currentTarget.getAttribute('data-option-index')),
925
1030
  reason: 'touch'
926
1031
  });
927
- isTouch.current = true;
1032
+ isTouchRef.current = true;
928
1033
  };
929
1034
  const handleOptionClick = event => {
930
1035
  const index = Number(event.currentTarget.getAttribute('data-option-index'));
931
1036
  selectNewValue(event, filteredOptions[index], 'selectOption');
932
- isTouch.current = false;
1037
+ isTouchRef.current = false;
933
1038
  };
934
1039
  const handleItemDelete = index => event => {
935
1040
  const newValue = value.slice();
@@ -954,11 +1059,11 @@ function useAutocomplete(props) {
954
1059
  // Prevent input blur when interacting with the combobox
955
1060
  const handleMouseDown = event => {
956
1061
  // Prevent focusing the input if click is anywhere outside the Autocomplete
957
- if (!event.currentTarget.contains(event.target)) {
1062
+ if (!contains(event.currentTarget, event.target)) {
958
1063
  return;
959
1064
  }
960
1065
  // Don't interfere with interactions outside the input area (e.g. helper text)
961
- if (anchorEl && !anchorEl.contains(event.target)) {
1066
+ if (anchorEl && !contains(anchorEl, event.target)) {
962
1067
  return;
963
1068
  }
964
1069
  if (event.target.getAttribute('id') !== id) {
@@ -969,11 +1074,11 @@ function useAutocomplete(props) {
969
1074
  // Focus the input when interacting with the combobox
970
1075
  const handleClick = event => {
971
1076
  // Prevent focusing the input if click is anywhere outside the Autocomplete
972
- if (!event.currentTarget.contains(event.target)) {
1077
+ if (!contains(event.currentTarget, event.target)) {
973
1078
  return;
974
1079
  }
975
1080
  // Don't interfere with interactions outside the input area (e.g. helper text)
976
- if (anchorEl && !anchorEl.contains(event.target)) {
1081
+ if (anchorEl && !contains(anchorEl, event.target)) {
977
1082
  return;
978
1083
  }
979
1084
  inputRef.current.focus();
@@ -1057,7 +1162,10 @@ function useAutocomplete(props) {
1057
1162
  getClearProps: () => ({
1058
1163
  tabIndex: -1,
1059
1164
  type: 'button',
1060
- onClick: handleClear
1165
+ onClick: event => {
1166
+ ignoreFocus.current = true;
1167
+ handleClear(event);
1168
+ }
1061
1169
  }),
1062
1170
  getItemProps: ({
1063
1171
  index = 0
@@ -1085,6 +1193,11 @@ function useAutocomplete(props) {
1085
1193
  onMouseDown: event => {
1086
1194
  // Prevent blur
1087
1195
  event.preventDefault();
1196
+ },
1197
+ onScroll: () => {
1198
+ if (isTouchRef.current) {
1199
+ touchScrolledRef.current = true;
1200
+ }
1088
1201
  }
1089
1202
  }),
1090
1203
  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,9 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _contains = _interopRequireDefault(require("@mui/utils/contains"));
9
+ var _default = exports.default = _contains.default;
@@ -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,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,20 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.FOCUSABLE_ATTRIBUTE = void 0;
7
+ exports.getFocusTarget = getFocusTarget;
8
+ const FOCUSABLE_ATTRIBUTE = exports.FOCUSABLE_ATTRIBUTE = 'data-mui-focusable';
9
+
10
+ /**
11
+ * Returns the element marked as the initial focus target inside a focus trap.
12
+ * The root element takes precedence over marked descendants so components can
13
+ * opt into focusing their own root surface directly.
14
+ */
15
+ function getFocusTarget(rootElement) {
16
+ if (!rootElement) {
17
+ return null;
18
+ }
19
+ return rootElement.hasAttribute(FOCUSABLE_ATTRIBUTE) ? rootElement : rootElement.querySelector(`[${FOCUSABLE_ATTRIBUTE}]`);
20
+ }
@@ -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;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _getEventTarget = _interopRequireDefault(require("@mui/utils/getEventTarget"));
9
+ var _default = exports.default = _getEventTarget.default;
@@ -0,0 +1,2 @@
1
+ import getEventTarget from '@mui/utils/getEventTarget';
2
+ export default getEventTarget;
@@ -5,14 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.default = mergeSlotProps;
8
+ var _isEventHandler = _interopRequireDefault(require("@mui/utils/isEventHandler"));
8
9
  var _clsx = _interopRequireDefault(require("clsx"));
9
- // Brought from [Base UI](https://github.com/mui/base-ui/blob/master/packages/react/src/merge-props/mergeProps.ts#L119)
10
- // Use it directly from Base UI once it's a package dependency.
11
- function isEventHandler(key, value) {
12
- // This approach is more efficient than using a regex.
13
- const thirdCharCode = key.charCodeAt(2);
14
- return key[0] === 'o' && key[1] === 'n' && thirdCharCode >= 65 /* A */ && thirdCharCode <= 90 /* Z */ && typeof value === 'function';
15
- }
16
10
  function mergeSlotProps(externalSlotProps, defaultSlotProps) {
17
11
  if (!externalSlotProps) {
18
12
  return defaultSlotProps;
@@ -20,7 +14,7 @@ function mergeSlotProps(externalSlotProps, defaultSlotProps) {
20
14
  function extractHandlers(externalSlotPropsValue, defaultSlotPropsValue) {
21
15
  const handlers = {};
22
16
  Object.keys(defaultSlotPropsValue).forEach(key => {
23
- if (isEventHandler(key, defaultSlotPropsValue[key]) && typeof externalSlotPropsValue[key] === 'function') {
17
+ if ((0, _isEventHandler.default)(key, defaultSlotPropsValue[key]) && typeof externalSlotPropsValue[key] === 'function') {
24
18
  // only compose the handlers if both default and external slot props match the event handler
25
19
  handlers[key] = (...args) => {
26
20
  externalSlotPropsValue[key](...args);
@@ -1,12 +1,5 @@
1
+ import isEventHandler from '@mui/utils/isEventHandler';
1
2
  import clsx from 'clsx';
2
-
3
- // Brought from [Base UI](https://github.com/mui/base-ui/blob/master/packages/react/src/merge-props/mergeProps.ts#L119)
4
- // Use it directly from Base UI once it's a package dependency.
5
- function isEventHandler(key, value) {
6
- // This approach is more efficient than using a regex.
7
- const thirdCharCode = key.charCodeAt(2);
8
- return key[0] === 'o' && key[1] === 'n' && thirdCharCode >= 65 /* A */ && thirdCharCode <= 90 /* Z */ && typeof value === 'function';
9
- }
10
3
  export default function mergeSlotProps(externalSlotProps, defaultSlotProps) {
11
4
  if (!externalSlotProps) {
12
5
  return defaultSlotProps;
package/version/index.js CHANGED
@@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.version = exports.prerelease = exports.patch = exports.minor = exports.major = exports.default = void 0;
7
- const version = exports.version = "9.0.0";
7
+ const version = exports.version = "9.0.1";
8
8
  const major = exports.major = Number("9");
9
9
  const minor = exports.minor = Number("0");
10
- const patch = exports.patch = Number("0");
10
+ const patch = exports.patch = Number("1");
11
11
  const prerelease = exports.prerelease = undefined;
12
12
  var _default = exports.default = version;
package/version/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- export const version = "9.0.0";
1
+ export const version = "9.0.1";
2
2
  export const major = Number("9");
3
3
  export const minor = Number("0");
4
- export const patch = Number("0");
4
+ export const patch = Number("1");
5
5
  export const prerelease = undefined;
6
6
  export default version;
@@ -1,21 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = formControlState;
7
- function formControlState({
8
- props,
9
- states,
10
- muiFormControl
11
- }) {
12
- return states.reduce((acc, state) => {
13
- acc[state] = props[state];
14
- if (muiFormControl) {
15
- if (typeof props[state] === 'undefined') {
16
- acc[state] = muiFormControl[state];
17
- }
18
- }
19
- return acc;
20
- }, {});
21
- }
@@ -1,15 +0,0 @@
1
- export default function formControlState({
2
- props,
3
- states,
4
- muiFormControl
5
- }) {
6
- return states.reduce((acc, state) => {
7
- acc[state] = props[state];
8
- if (muiFormControl) {
9
- if (typeof props[state] === 'undefined') {
10
- acc[state] = muiFormControl[state];
11
- }
12
- }
13
- return acc;
14
- }, {});
15
- }