@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
@@ -5,8 +5,7 @@ import clsx from 'clsx';
5
5
  import PropTypes from 'prop-types';
6
6
  import composeClasses from '@mui/utils/composeClasses';
7
7
  import NativeSelectInput from "./NativeSelectInput.mjs";
8
- import formControlState from "../FormControl/formControlState.mjs";
9
- import useFormControl from "../FormControl/useFormControl.mjs";
8
+ import { useFormControlState } from "../FormControl/useFormControl.mjs";
10
9
  import ArrowDropDownIcon from "../internal/svg-icons/ArrowDropDown.mjs";
11
10
  import Input from "../Input/index.mjs";
12
11
  import { useDefaultProps } from "../DefaultPropsProvider/index.mjs";
@@ -40,10 +39,8 @@ const NativeSelect = /*#__PURE__*/React.forwardRef(function NativeSelect(inProps
40
39
  variant,
41
40
  ...other
42
41
  } = props;
43
- const muiFormControl = useFormControl();
44
- const fcs = formControlState({
42
+ const [fcs] = useFormControlState({
45
43
  props,
46
- muiFormControl,
47
44
  states: ['variant']
48
45
  });
49
46
  const ownerState = {
@@ -12,8 +12,7 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
12
12
  var _refType = _interopRequireDefault(require("@mui/utils/refType"));
13
13
  var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
14
14
  var _NotchedOutline = _interopRequireDefault(require("./NotchedOutline"));
15
- var _useFormControl = _interopRequireDefault(require("../FormControl/useFormControl"));
16
- var _formControlState = _interopRequireDefault(require("../FormControl/formControlState"));
15
+ var _useFormControl = require("../FormControl/useFormControl");
17
16
  var _rootShouldForwardProp = _interopRequireDefault(require("../styles/rootShouldForwardProp"));
18
17
  var _zeroStyled = require("../zero-styled");
19
18
  var _memoTheme = _interopRequireDefault(require("../utils/memoTheme"));
@@ -134,26 +133,19 @@ const OutlinedInputInput = (0, _zeroStyled.styled)(_InputBase.InputBaseInput, {
134
133
  theme
135
134
  }) => ({
136
135
  padding: '16.5px 14px',
137
- ...(!theme.vars && {
138
- '&:-webkit-autofill': {
136
+ '&:-webkit-autofill': {
137
+ ...(!theme.vars && {
139
138
  WebkitBoxShadow: theme.palette.mode === 'light' ? null : '0 0 0 100px #266798 inset',
140
139
  WebkitTextFillColor: theme.palette.mode === 'light' ? null : '#fff',
141
- caretColor: theme.palette.mode === 'light' ? null : '#fff',
142
- borderRadius: 'inherit'
143
- }
144
- }),
145
- ...(theme.vars && {
146
- '&:-webkit-autofill': {
147
- borderRadius: 'inherit'
148
- },
149
- [theme.getColorSchemeSelector('dark')]: {
150
- '&:-webkit-autofill': {
151
- WebkitBoxShadow: '0 0 0 100px #266798 inset',
152
- WebkitTextFillColor: '#fff',
153
- caretColor: '#fff'
154
- }
155
- }
156
- }),
140
+ caretColor: theme.palette.mode === 'light' ? null : '#fff'
141
+ }),
142
+ borderRadius: 'inherit',
143
+ ...(theme.vars && theme.applyStyles('dark', {
144
+ WebkitBoxShadow: '0 0 0 100px #266798 inset',
145
+ WebkitTextFillColor: '#fff',
146
+ caretColor: '#fff'
147
+ }))
148
+ },
157
149
  variants: [{
158
150
  props: {
159
151
  size: 'small'
@@ -201,10 +193,8 @@ const OutlinedInput = /*#__PURE__*/React.forwardRef(function OutlinedInput(inPro
201
193
  ...other
202
194
  } = props;
203
195
  const classes = useUtilityClasses(props);
204
- const muiFormControl = (0, _useFormControl.default)();
205
- const fcs = (0, _formControlState.default)({
196
+ const [fcs, muiFormControl] = (0, _useFormControl.useFormControlState)({
206
197
  props,
207
- muiFormControl,
208
198
  states: ['color', 'disabled', 'error', 'focused', 'hiddenLabel', 'size', 'required']
209
199
  });
210
200
  const ownerState = {
@@ -5,8 +5,7 @@ import PropTypes from 'prop-types';
5
5
  import refType from '@mui/utils/refType';
6
6
  import composeClasses from '@mui/utils/composeClasses';
7
7
  import NotchedOutline from "./NotchedOutline.mjs";
8
- import useFormControl from "../FormControl/useFormControl.mjs";
9
- import formControlState from "../FormControl/formControlState.mjs";
8
+ import { useFormControlState } from "../FormControl/useFormControl.mjs";
10
9
  import rootShouldForwardProp from "../styles/rootShouldForwardProp.mjs";
11
10
  import { styled } from "../zero-styled/index.mjs";
12
11
  import memoTheme from "../utils/memoTheme.mjs";
@@ -127,26 +126,19 @@ const OutlinedInputInput = styled(InputBaseInput, {
127
126
  theme
128
127
  }) => ({
129
128
  padding: '16.5px 14px',
130
- ...(!theme.vars && {
131
- '&:-webkit-autofill': {
129
+ '&:-webkit-autofill': {
130
+ ...(!theme.vars && {
132
131
  WebkitBoxShadow: theme.palette.mode === 'light' ? null : '0 0 0 100px #266798 inset',
133
132
  WebkitTextFillColor: theme.palette.mode === 'light' ? null : '#fff',
134
- caretColor: theme.palette.mode === 'light' ? null : '#fff',
135
- borderRadius: 'inherit'
136
- }
137
- }),
138
- ...(theme.vars && {
139
- '&:-webkit-autofill': {
140
- borderRadius: 'inherit'
141
- },
142
- [theme.getColorSchemeSelector('dark')]: {
143
- '&:-webkit-autofill': {
144
- WebkitBoxShadow: '0 0 0 100px #266798 inset',
145
- WebkitTextFillColor: '#fff',
146
- caretColor: '#fff'
147
- }
148
- }
149
- }),
133
+ caretColor: theme.palette.mode === 'light' ? null : '#fff'
134
+ }),
135
+ borderRadius: 'inherit',
136
+ ...(theme.vars && theme.applyStyles('dark', {
137
+ WebkitBoxShadow: '0 0 0 100px #266798 inset',
138
+ WebkitTextFillColor: '#fff',
139
+ caretColor: '#fff'
140
+ }))
141
+ },
150
142
  variants: [{
151
143
  props: {
152
144
  size: 'small'
@@ -194,10 +186,8 @@ const OutlinedInput = /*#__PURE__*/React.forwardRef(function OutlinedInput(inPro
194
186
  ...other
195
187
  } = props;
196
188
  const classes = useUtilityClasses(props);
197
- const muiFormControl = useFormControl();
198
- const fcs = formControlState({
189
+ const [fcs, muiFormControl] = useFormControlState({
199
190
  props,
200
- muiFormControl,
201
191
  states: ['color', 'disabled', 'error', 'focused', 'hiddenLabel', 'size', 'required']
202
192
  });
203
193
  const ownerState = {
@@ -59,7 +59,6 @@ const PigmentContainer = /*#__PURE__*/React.forwardRef(function PigmentContainer
59
59
  fixed: fixed,
60
60
  maxWidth: maxWidth,
61
61
  ...props,
62
- // @ts-ignore
63
62
  ref: ref
64
63
  });
65
64
  });
@@ -51,7 +51,6 @@ const PigmentContainer = /*#__PURE__*/React.forwardRef(function PigmentContainer
51
51
  fixed: fixed,
52
52
  maxWidth: maxWidth,
53
53
  ...props,
54
- // @ts-ignore
55
54
  ref: ref
56
55
  });
57
56
  });
@@ -150,8 +150,30 @@ const PopperTooltip = /*#__PURE__*/React.forwardRef(function PopperTooltip(props
150
150
  modifiers: popperModifiers
151
151
  });
152
152
  handlePopperRefRef.current(popper);
153
+ const popperElement = tooltipRef.current;
153
154
  return () => {
154
- popper.destroy();
155
+ // popper.destroy() clears all inline positioning via the applyStyles
156
+ // modifier cleanup, which causes the element to jump to its static
157
+ // position. Snapshot and restore only the positioning properties so the
158
+ // element stays in place during the destroy/recreate gap (prevents scroll
159
+ // jumps when a child focuses between the two).
160
+ // https://github.com/mui/mui-x/issues/21839
161
+ if (popperElement) {
162
+ const {
163
+ style
164
+ } = popperElement;
165
+ const position = style.position;
166
+ const top = style.top;
167
+ const left = style.left;
168
+ const transform = style.transform;
169
+ popper.destroy();
170
+ style.position = position;
171
+ style.top = top;
172
+ style.left = left;
173
+ style.transform = transform;
174
+ } else {
175
+ popper.destroy();
176
+ }
155
177
  handlePopperRefRef.current(null);
156
178
  };
157
179
  }, [resolvedAnchorElement, disablePortal, modifiers, open, popperOptions, rtlPlacement]);
@@ -143,8 +143,30 @@ const PopperTooltip = /*#__PURE__*/React.forwardRef(function PopperTooltip(props
143
143
  modifiers: popperModifiers
144
144
  });
145
145
  handlePopperRefRef.current(popper);
146
+ const popperElement = tooltipRef.current;
146
147
  return () => {
147
- popper.destroy();
148
+ // popper.destroy() clears all inline positioning via the applyStyles
149
+ // modifier cleanup, which causes the element to jump to its static
150
+ // position. Snapshot and restore only the positioning properties so the
151
+ // element stays in place during the destroy/recreate gap (prevents scroll
152
+ // jumps when a child focuses between the two).
153
+ // https://github.com/mui/mui-x/issues/21839
154
+ if (popperElement) {
155
+ const {
156
+ style
157
+ } = popperElement;
158
+ const position = style.position;
159
+ const top = style.top;
160
+ const left = style.left;
161
+ const transform = style.transform;
162
+ popper.destroy();
163
+ style.position = position;
164
+ style.top = top;
165
+ style.left = left;
166
+ style.transform = transform;
167
+ } else {
168
+ popper.destroy();
169
+ }
148
170
  handlePopperRefRef.current(null);
149
171
  };
150
172
  }, [resolvedAnchorElement, disablePortal, modifiers, open, popperOptions, rtlPlacement]);
package/Select/Select.js CHANGED
@@ -14,8 +14,7 @@ var _deepmerge = _interopRequireDefault(require("@mui/utils/deepmerge"));
14
14
  var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
15
15
  var _getReactElementRef = _interopRequireDefault(require("@mui/utils/getReactElementRef"));
16
16
  var _SelectInput = _interopRequireDefault(require("./SelectInput"));
17
- var _formControlState = _interopRequireDefault(require("../FormControl/formControlState"));
18
- var _useFormControl = _interopRequireDefault(require("../FormControl/useFormControl"));
17
+ var _useFormControl = require("../FormControl/useFormControl");
19
18
  var _ArrowDropDown = _interopRequireDefault(require("../internal/svg-icons/ArrowDropDown"));
20
19
  var _Input = _interopRequireDefault(require("../Input"));
21
20
  var _NativeSelectInput = _interopRequireDefault(require("../NativeSelect/NativeSelectInput"));
@@ -78,10 +77,8 @@ const Select = /*#__PURE__*/React.forwardRef(function Select(inProps, ref) {
78
77
  ...other
79
78
  } = props;
80
79
  const inputComponent = native ? _NativeSelectInput.default : _SelectInput.default;
81
- const muiFormControl = (0, _useFormControl.default)();
82
- const fcs = (0, _formControlState.default)({
80
+ const [fcs] = (0, _useFormControl.useFormControlState)({
83
81
  props,
84
- muiFormControl,
85
82
  states: ['variant', 'error']
86
83
  });
87
84
  const variant = fcs.variant || variantProp;
package/Select/Select.mjs CHANGED
@@ -7,8 +7,7 @@ import deepmerge from '@mui/utils/deepmerge';
7
7
  import composeClasses from '@mui/utils/composeClasses';
8
8
  import getReactElementRef from '@mui/utils/getReactElementRef';
9
9
  import SelectInput from "./SelectInput.mjs";
10
- import formControlState from "../FormControl/formControlState.mjs";
11
- import useFormControl from "../FormControl/useFormControl.mjs";
10
+ import { useFormControlState } from "../FormControl/useFormControl.mjs";
12
11
  import ArrowDropDownIcon from "../internal/svg-icons/ArrowDropDown.mjs";
13
12
  import Input from "../Input/index.mjs";
14
13
  import NativeSelectInput from "../NativeSelect/NativeSelectInput.mjs";
@@ -71,10 +70,8 @@ const Select = /*#__PURE__*/React.forwardRef(function Select(inProps, ref) {
71
70
  ...other
72
71
  } = props;
73
72
  const inputComponent = native ? NativeSelectInput : SelectInput;
74
- const muiFormControl = useFormControl();
75
- const fcs = formControlState({
73
+ const [fcs] = useFormControlState({
76
74
  props,
77
- muiFormControl,
78
75
  states: ['variant', 'error']
79
76
  });
80
77
  const variant = fcs.variant || variantProp;
@@ -15,12 +15,15 @@ var _clsx = _interopRequireDefault(require("clsx"));
15
15
  var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
16
16
  var _useId = _interopRequireDefault(require("@mui/utils/useId"));
17
17
  var _refType = _interopRequireDefault(require("@mui/utils/refType"));
18
+ var _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout"));
18
19
  var _ownerDocument = _interopRequireDefault(require("../utils/ownerDocument"));
19
20
  var _Menu = _interopRequireDefault(require("../Menu/Menu"));
20
21
  var _NativeSelectInput = require("../NativeSelect/NativeSelectInput");
21
22
  var _utils = require("../InputBase/utils");
22
23
  var _zeroStyled = require("../zero-styled");
23
24
  var _slotShouldForwardProp = _interopRequireDefault(require("../styles/slotShouldForwardProp"));
25
+ var _useEnhancedEffect = _interopRequireDefault(require("../utils/useEnhancedEffect"));
26
+ var _useEventCallback = _interopRequireDefault(require("../utils/useEventCallback"));
24
27
  var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
25
28
  var _useControlled = _interopRequireDefault(require("../utils/useControlled"));
26
29
  var _selectClasses = _interopRequireWildcard(require("./selectClasses"));
@@ -28,6 +31,37 @@ var _utils2 = require("./utils");
28
31
  var _SelectFocusSourceContext = require("./utils/SelectFocusSourceContext");
29
32
  var _jsxRuntime = require("react/jsx-runtime");
30
33
  var _span;
34
+ const OPENING_MOUSE_UP_BOUNDARY_OFFSET = 2;
35
+ // The initial mouseup may land on an item when the menu opens over the trigger.
36
+ const SELECTED_MOUSE_UP_DELAY = 400;
37
+ const UNSELECTED_MOUSE_UP_DELAY = 200;
38
+
39
+ /**
40
+ * Returns true when a native mouse event should be treated as happening inside
41
+ * the element, even if a portal or backdrop retargeted the event away from it.
42
+ *
43
+ * Select uses this for the opening mouseup: when the menu opens over the
44
+ * trigger, the release can target the backdrop or portaled menu even though the
45
+ * pointer is still inside the trigger or menu bounds.
46
+ */
47
+ function isMouseEventInsideElement(event, element) {
48
+ if (!element) {
49
+ return false;
50
+ }
51
+ const eventPath = event.composedPath();
52
+ if (eventPath.includes(element)) {
53
+ return true;
54
+ }
55
+ if (event.target?.nodeType && element.contains(event.target)) {
56
+ return true;
57
+ }
58
+ const rect = element.getBoundingClientRect();
59
+ if (rect.width === 0 && rect.height === 0) {
60
+ // Hidden or transition-mounted elements do not have useful bounds to hit-test.
61
+ return false;
62
+ }
63
+ return event.clientX >= rect.left - OPENING_MOUSE_UP_BOUNDARY_OFFSET && event.clientX <= rect.right + OPENING_MOUSE_UP_BOUNDARY_OFFSET && event.clientY >= rect.top - OPENING_MOUSE_UP_BOUNDARY_OFFSET && event.clientY <= rect.bottom + OPENING_MOUSE_UP_BOUNDARY_OFFSET;
64
+ }
31
65
  const SelectSelect = (0, _zeroStyled.styled)(_NativeSelectInput.StyledSelectSelect, {
32
66
  name: 'MuiSelect',
33
67
  slot: 'Select',
@@ -154,6 +188,17 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
154
188
  });
155
189
  const inputRef = React.useRef(null);
156
190
  const displayRef = React.useRef(null);
191
+ const paperRef = React.useRef(null);
192
+ const openRef = React.useRef(false);
193
+ const hasSelectedItemInListRef = React.useRef(false);
194
+ const openingMouseUpListenerCleanupRef = React.useRef(null);
195
+ const didPointerDownOnItemRef = React.useRef(false);
196
+ const selectionRef = React.useRef({
197
+ allowSelectedMouseUp: false,
198
+ allowUnselectedMouseUp: false
199
+ });
200
+ const selectedMouseUpTimer = (0, _useTimeout.default)();
201
+ const unselectedMouseUpTimer = (0, _useTimeout.default)();
157
202
  const [displayNode, setDisplayNode] = React.useState(null);
158
203
  const {
159
204
  current: isOpenControlled
@@ -176,6 +221,42 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
176
221
  value
177
222
  }), [value]);
178
223
  const open = displayNode !== null && openState;
224
+ (0, _useEnhancedEffect.default)(() => {
225
+ openRef.current = open;
226
+ }, [open]);
227
+ const clearSelectionTimers = React.useCallback(() => {
228
+ selectedMouseUpTimer.clear();
229
+ unselectedMouseUpTimer.clear();
230
+ }, [selectedMouseUpTimer, unselectedMouseUpTimer]);
231
+ const resetMouseUpSelection = React.useCallback(() => {
232
+ clearSelectionTimers();
233
+ didPointerDownOnItemRef.current = false;
234
+ selectionRef.current = {
235
+ allowSelectedMouseUp: false,
236
+ allowUnselectedMouseUp: false
237
+ };
238
+ }, [clearSelectionTimers]);
239
+ const clearOpeningMouseUpListener = React.useCallback(() => {
240
+ if (openingMouseUpListenerCleanupRef.current) {
241
+ openingMouseUpListenerCleanupRef.current();
242
+ openingMouseUpListenerCleanupRef.current = null;
243
+ }
244
+ }, []);
245
+ React.useEffect(() => {
246
+ if (!open) {
247
+ resetMouseUpSelection();
248
+ clearOpeningMouseUpListener();
249
+ }
250
+ }, [open, resetMouseUpSelection, clearOpeningMouseUpListener]);
251
+
252
+ // Keep unmount cleanup separate from the `open` effect. Effect cleanups also run
253
+ // before the next effect, which would clear the opening mouseup listener while opening.
254
+ React.useEffect(() => {
255
+ return () => {
256
+ resetMouseUpSelection();
257
+ clearOpeningMouseUpListener();
258
+ };
259
+ }, [resetMouseUpSelection, clearOpeningMouseUpListener]);
179
260
  React.useEffect(() => {
180
261
  if (!open || !anchorElement || autoWidth) {
181
262
  return undefined;
@@ -226,7 +307,11 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
226
307
  }
227
308
  return undefined;
228
309
  }, [labelId]);
229
- const update = (openParam, event) => {
310
+ const update = (0, _useEventCallback.default)((openParam, event) => {
311
+ if (!openParam) {
312
+ resetMouseUpSelection();
313
+ clearOpeningMouseUpListener();
314
+ }
230
315
  if (openParam) {
231
316
  setOpenInteractionType((0, _utils2.getOpenInteractionType)(event));
232
317
  if (onOpen) {
@@ -239,9 +324,33 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
239
324
  }
240
325
  }
241
326
  if (!isOpenControlled) {
327
+ openRef.current = openParam;
242
328
  setMenuMinWidthState(autoWidth ? null : anchorElement.clientWidth);
243
329
  setOpenState(openParam);
244
330
  }
331
+ });
332
+ const scheduleMouseUpSelection = () => {
333
+ resetMouseUpSelection();
334
+
335
+ // When there is no selected item in the list, a mousedown
336
+ // on the trigger followed by a quick mouseup over the first option can accidentally select
337
+ // within 200ms. Delay unselected mouseup to match the safer 400ms window.
338
+ if (!hasSelectedItemInListRef.current) {
339
+ selectedMouseUpTimer.start(SELECTED_MOUSE_UP_DELAY, () => {
340
+ selectionRef.current.allowSelectedMouseUp = true;
341
+ selectionRef.current.allowUnselectedMouseUp = true;
342
+ });
343
+ } else {
344
+ // mousedown -> move to unselected item -> mouseup should not select within 200ms.
345
+ unselectedMouseUpTimer.start(UNSELECTED_MOUSE_UP_DELAY, () => {
346
+ selectionRef.current.allowUnselectedMouseUp = true;
347
+
348
+ // mousedown -> mouseup on selected item should not select within 400ms.
349
+ selectedMouseUpTimer.start(UNSELECTED_MOUSE_UP_DELAY, () => {
350
+ selectionRef.current.allowSelectedMouseUp = true;
351
+ });
352
+ });
353
+ }
245
354
  };
246
355
  const handleMouseDown = event => {
247
356
  onMouseDown?.(event);
@@ -252,6 +361,29 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
252
361
  // Hijack the default focus behavior.
253
362
  event.preventDefault();
254
363
  displayRef.current.focus();
364
+ const doc = (0, _ownerDocument.default)(event.currentTarget);
365
+ scheduleMouseUpSelection();
366
+ clearOpeningMouseUpListener();
367
+ const handleMouseUp = mouseEvent => {
368
+ openingMouseUpListenerCleanupRef.current = null;
369
+ if (!displayRef.current) {
370
+ return;
371
+ }
372
+ if (isMouseEventInsideElement(mouseEvent, displayRef.current) || isMouseEventInsideElement(mouseEvent, paperRef.current)) {
373
+ return;
374
+ }
375
+ if (!openRef.current && isOpenControlled) {
376
+ return;
377
+ }
378
+ update(false, mouseEvent);
379
+ };
380
+ doc.addEventListener('mouseup', handleMouseUp, {
381
+ capture: true,
382
+ once: true
383
+ });
384
+ openingMouseUpListenerCleanupRef.current = () => {
385
+ doc.removeEventListener('mouseup', handleMouseUp, true);
386
+ };
255
387
  update(true, event);
256
388
  };
257
389
  const handleClose = event => {
@@ -271,6 +403,7 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
271
403
  }
272
404
  };
273
405
  const handleItemClick = child => event => {
406
+ didPointerDownOnItemRef.current = false;
274
407
  let newValue;
275
408
 
276
409
  // We use the tabindex attribute to signal the available options.
@@ -314,6 +447,19 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
314
447
  update(false, event);
315
448
  }
316
449
  };
450
+ const handleItemMouseUp = (child, selected) => event => {
451
+ child.props.onMouseUp?.(event);
452
+ if (didPointerDownOnItemRef.current) {
453
+ didPointerDownOnItemRef.current = false;
454
+ return;
455
+ }
456
+ const disallowSelectedMouseUp = !selectionRef.current.allowSelectedMouseUp && selected;
457
+ const disallowUnselectedMouseUp = !selectionRef.current.allowUnselectedMouseUp && !selected;
458
+ if (disallowSelectedMouseUp || disallowUnselectedMouseUp) {
459
+ return;
460
+ }
461
+ event.currentTarget.click();
462
+ };
317
463
  const handleKeyDown = event => {
318
464
  if (!readOnly) {
319
465
  const validKeys = [' ', 'ArrowUp', 'ArrowDown',
@@ -387,7 +533,16 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
387
533
  }
388
534
  return /*#__PURE__*/React.cloneElement(child, {
389
535
  'aria-selected': selected ? 'true' : 'false',
536
+ onMouseDown: event => {
537
+ didPointerDownOnItemRef.current = true;
538
+ child.props.onMouseDown?.(event);
539
+ },
540
+ onPointerDown: event => {
541
+ didPointerDownOnItemRef.current = true;
542
+ child.props.onPointerDown?.(event);
543
+ },
390
544
  onClick: handleItemClick(child),
545
+ onMouseUp: handleItemMouseUp(child, selected),
391
546
  onKeyUp: event => {
392
547
  if (event.key === ' ') {
393
548
  // otherwise our MenuItems dispatches a click event
@@ -406,6 +561,11 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
406
561
  'data-value': child.props.value // Instead, we provide it as a data attribute.
407
562
  });
408
563
  });
564
+
565
+ // Keep the opening mouseup guard current without mutating refs during render.
566
+ (0, _useEnhancedEffect.default)(() => {
567
+ hasSelectedItemInListRef.current = foundMatch;
568
+ }, [foundMatch]);
409
569
  if (process.env.NODE_ENV !== 'production') {
410
570
  // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
411
571
  // eslint-disable-next-line react-hooks/rules-of-hooks
@@ -455,6 +615,7 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
455
615
  };
456
616
  const classes = useUtilityClasses(ownerState);
457
617
  const menuPaperSlotProps = typeof MenuProps.slotProps?.paper === 'function' ? MenuProps.slotProps.paper(ownerState) : MenuProps.slotProps?.paper;
618
+ const handlePaperRef = (0, _useForkRef.default)(menuPaperSlotProps?.ref, paperRef);
458
619
  const menuListSlotProps = typeof MenuProps.slotProps?.list === 'function' ? MenuProps.slotProps.list(ownerState) : MenuProps.slotProps?.list;
459
620
  const listboxId = (0, _useId.default)();
460
621
  const nativeInputId = (0, _useId.default)();
@@ -469,7 +630,7 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
469
630
  "aria-expanded": open ? 'true' : 'false',
470
631
  "aria-haspopup": "listbox",
471
632
  "aria-label": ariaLabel,
472
- "aria-labelledby": [labelId, buttonId].filter(Boolean).join(' ') || undefined,
633
+ "aria-labelledby": labelId,
473
634
  "aria-describedby": ariaDescribedby,
474
635
  "aria-required": required ? 'true' : undefined,
475
636
  "aria-invalid": error ? 'true' : undefined,
@@ -536,6 +697,7 @@ const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, re
536
697
  },
537
698
  paper: {
538
699
  ...menuPaperSlotProps,
700
+ ref: handlePaperRef,
539
701
  style: {
540
702
  minWidth: menuMinWidth,
541
703
  ...menuPaperSlotProps?.style