@mui/material 9.0.1 → 9.1.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 (228) hide show
  1. package/Accordion/Accordion.d.mts +2 -2
  2. package/Accordion/Accordion.d.ts +2 -2
  3. package/Accordion/Accordion.js +3 -2
  4. package/Accordion/Accordion.mjs +3 -2
  5. package/AccordionSummary/AccordionSummary.js +27 -29
  6. package/AccordionSummary/AccordionSummary.mjs +27 -29
  7. package/Autocomplete/Autocomplete.d.mts +5 -1
  8. package/Autocomplete/Autocomplete.d.ts +5 -1
  9. package/Autocomplete/Autocomplete.js +13 -7
  10. package/Autocomplete/Autocomplete.mjs +13 -7
  11. package/Backdrop/Backdrop.d.mts +2 -2
  12. package/Backdrop/Backdrop.d.ts +2 -2
  13. package/Badge/Badge.js +28 -24
  14. package/Badge/Badge.mjs +28 -24
  15. package/BottomNavigationAction/BottomNavigationAction.js +6 -2
  16. package/BottomNavigationAction/BottomNavigationAction.mjs +6 -2
  17. package/Button/Button.js +11 -15
  18. package/Button/Button.mjs +11 -15
  19. package/ButtonBase/Ripple.js +21 -11
  20. package/ButtonBase/Ripple.mjs +21 -11
  21. package/ButtonBase/TouchRipple.js +252 -108
  22. package/ButtonBase/TouchRipple.mjs +253 -109
  23. package/CHANGELOG.md +122 -0
  24. package/CardActionArea/CardActionArea.js +2 -1
  25. package/CardActionArea/CardActionArea.mjs +2 -1
  26. package/Chip/Chip.js +2 -1
  27. package/Chip/Chip.mjs +2 -1
  28. package/CircularProgress/CircularProgress.js +85 -55
  29. package/CircularProgress/CircularProgress.mjs +84 -55
  30. package/Collapse/Collapse.d.mts +15 -3
  31. package/Collapse/Collapse.d.ts +15 -3
  32. package/Collapse/Collapse.js +44 -31
  33. package/Collapse/Collapse.mjs +43 -30
  34. package/Dialog/Dialog.d.mts +2 -2
  35. package/Dialog/Dialog.d.ts +2 -2
  36. package/Dialog/Dialog.js +2 -0
  37. package/Dialog/Dialog.mjs +2 -0
  38. package/Drawer/Drawer.d.mts +2 -2
  39. package/Drawer/Drawer.d.ts +2 -2
  40. package/Fab/Fab.js +2 -1
  41. package/Fab/Fab.mjs +2 -1
  42. package/Fade/Fade.d.mts +15 -2
  43. package/Fade/Fade.d.ts +15 -2
  44. package/Fade/Fade.js +46 -19
  45. package/Fade/Fade.mjs +45 -18
  46. package/FilledInput/FilledInput.js +4 -3
  47. package/FilledInput/FilledInput.mjs +4 -3
  48. package/Grow/Grow.d.mts +15 -2
  49. package/Grow/Grow.d.ts +15 -2
  50. package/Grow/Grow.js +45 -28
  51. package/Grow/Grow.mjs +44 -27
  52. package/IconButton/IconButton.js +2 -1
  53. package/IconButton/IconButton.mjs +2 -1
  54. package/InitColorSchemeScript/InitColorSchemeScript.d.mts +5 -5
  55. package/InitColorSchemeScript/InitColorSchemeScript.d.ts +5 -5
  56. package/Input/Input.js +3 -2
  57. package/Input/Input.mjs +3 -2
  58. package/InputBase/InputBase.js +2 -1
  59. package/InputBase/InputBase.mjs +2 -1
  60. package/InputLabel/InputLabel.js +2 -1
  61. package/InputLabel/InputLabel.mjs +2 -1
  62. package/LICENSE +1 -1
  63. package/LinearProgress/LinearProgress.js +187 -120
  64. package/LinearProgress/LinearProgress.mjs +186 -120
  65. package/ListItem/ListItem.js +2 -1
  66. package/ListItem/ListItem.mjs +2 -1
  67. package/ListItemButton/ListItemButton.js +2 -1
  68. package/ListItemButton/ListItemButton.mjs +2 -1
  69. package/Menu/Menu.d.mts +1 -1
  70. package/Menu/Menu.d.ts +1 -1
  71. package/MobileStepper/MobileStepper.js +2 -1
  72. package/MobileStepper/MobileStepper.mjs +2 -1
  73. package/OutlinedInput/NotchedOutline.js +4 -3
  74. package/OutlinedInput/NotchedOutline.mjs +4 -3
  75. package/PaginationItem/PaginationItem.js +2 -1
  76. package/PaginationItem/PaginationItem.mjs +2 -1
  77. package/Paper/Paper.js +2 -1
  78. package/Paper/Paper.mjs +2 -1
  79. package/Popover/Popover.d.mts +1 -1
  80. package/Popover/Popover.d.ts +1 -1
  81. package/README.md +3 -2
  82. package/Radio/RadioButtonIcon.js +3 -2
  83. package/Radio/RadioButtonIcon.mjs +3 -2
  84. package/Rating/Rating.js +2 -1
  85. package/Rating/Rating.mjs +2 -1
  86. package/Select/SelectInput.js +115 -25
  87. package/Select/SelectInput.mjs +115 -25
  88. package/Select/utils/SelectFocusSourceContext.d.mts +2 -2
  89. package/Select/utils/SelectFocusSourceContext.d.ts +2 -2
  90. package/Select/utils/closedTypeahead.js +73 -0
  91. package/Select/utils/closedTypeahead.mjs +63 -0
  92. package/Skeleton/Skeleton.js +22 -2
  93. package/Skeleton/Skeleton.mjs +22 -2
  94. package/Slide/Slide.d.mts +15 -2
  95. package/Slide/Slide.d.ts +15 -2
  96. package/Slide/Slide.js +53 -25
  97. package/Slide/Slide.mjs +52 -24
  98. package/Slider/Slider.js +4 -3
  99. package/Slider/Slider.mjs +4 -3
  100. package/Slider/SliderValueLabel.d.mts +2 -2
  101. package/Slider/SliderValueLabel.d.ts +2 -2
  102. package/Slider/useSlider.js +1 -1
  103. package/Slider/useSlider.mjs +1 -1
  104. package/Snackbar/Snackbar.d.mts +2 -2
  105. package/Snackbar/Snackbar.d.ts +2 -2
  106. package/SpeedDial/SpeedDial.d.mts +1 -1
  107. package/SpeedDial/SpeedDial.d.ts +1 -1
  108. package/SpeedDial/SpeedDial.js +6 -2
  109. package/SpeedDial/SpeedDial.mjs +6 -2
  110. package/SpeedDialAction/SpeedDialAction.js +11 -2
  111. package/SpeedDialAction/SpeedDialAction.mjs +12 -3
  112. package/SpeedDialIcon/SpeedDialIcon.js +40 -37
  113. package/SpeedDialIcon/SpeedDialIcon.mjs +40 -37
  114. package/Step/Step.js +47 -15
  115. package/Step/Step.mjs +47 -15
  116. package/Step/StepContext.d.mts +1 -1
  117. package/Step/StepContext.d.ts +1 -1
  118. package/StepButton/StepButton.js +9 -3
  119. package/StepButton/StepButton.mjs +9 -3
  120. package/StepConnector/StepConnector.js +10 -0
  121. package/StepConnector/StepConnector.mjs +10 -0
  122. package/StepContent/StepContent.d.mts +2 -2
  123. package/StepContent/StepContent.d.ts +2 -2
  124. package/StepContent/StepContent.js +26 -2
  125. package/StepContent/StepContent.mjs +26 -2
  126. package/StepIcon/StepIcon.js +2 -1
  127. package/StepIcon/StepIcon.mjs +2 -1
  128. package/StepLabel/StepLabel.js +52 -7
  129. package/StepLabel/StepLabel.mjs +52 -7
  130. package/Stepper/Stepper.d.mts +2 -0
  131. package/Stepper/Stepper.d.ts +2 -0
  132. package/Stepper/Stepper.js +18 -0
  133. package/Stepper/Stepper.mjs +18 -0
  134. package/Stepper/StepperContext.d.mts +1 -1
  135. package/Stepper/StepperContext.d.ts +1 -1
  136. package/SvgIcon/SvgIcon.js +2 -1
  137. package/SvgIcon/SvgIcon.mjs +2 -1
  138. package/SwipeableDrawer/SwipeableDrawer.js +14 -3
  139. package/SwipeableDrawer/SwipeableDrawer.mjs +14 -3
  140. package/Switch/Switch.js +3 -2
  141. package/Switch/Switch.mjs +3 -2
  142. package/TableSortLabel/TableSortLabel.js +2 -1
  143. package/TableSortLabel/TableSortLabel.mjs +2 -1
  144. package/Tabs/Tabs.js +14 -3
  145. package/Tabs/Tabs.mjs +14 -3
  146. package/Tooltip/Tooltip.d.mts +2 -2
  147. package/Tooltip/Tooltip.d.ts +2 -2
  148. package/Tooltip/Tooltip.js +3 -0
  149. package/Tooltip/Tooltip.mjs +3 -0
  150. package/Unstable_TrapFocus/FocusTrap.js +42 -8
  151. package/Unstable_TrapFocus/FocusTrap.mjs +42 -8
  152. package/Zoom/Zoom.d.mts +15 -2
  153. package/Zoom/Zoom.d.ts +15 -2
  154. package/Zoom/Zoom.js +43 -16
  155. package/Zoom/Zoom.mjs +42 -15
  156. package/index.js +1 -1
  157. package/index.mjs +1 -1
  158. package/internal/Transition.d.mts +34 -0
  159. package/internal/Transition.d.ts +34 -0
  160. package/internal/Transition.js +450 -0
  161. package/internal/Transition.mjs +442 -0
  162. package/internal/react-transition-group.d.mts +8 -0
  163. package/internal/react-transition-group.d.ts +8 -0
  164. package/package.json +7 -7
  165. package/styles/ThemeProviderWithVars.d.mts +7 -7
  166. package/styles/ThemeProviderWithVars.d.ts +7 -7
  167. package/styles/createGetSelector.d.mts +2 -2
  168. package/styles/createGetSelector.d.ts +2 -2
  169. package/styles/createMotion.d.mts +8 -0
  170. package/styles/createMotion.d.ts +8 -0
  171. package/styles/createMotion.js +13 -0
  172. package/styles/createMotion.mjs +7 -0
  173. package/styles/createThemeFoundation.d.mts +2 -0
  174. package/styles/createThemeFoundation.d.ts +2 -0
  175. package/styles/createThemeNoVars.d.mts +3 -0
  176. package/styles/createThemeNoVars.d.ts +3 -0
  177. package/styles/createThemeNoVars.js +5 -0
  178. package/styles/createThemeNoVars.mjs +5 -0
  179. package/styles/createTransitions.d.mts +6 -2
  180. package/styles/createTransitions.d.ts +6 -2
  181. package/styles/createTransitions.js +12 -4
  182. package/styles/createTransitions.mjs +12 -4
  183. package/styles/enhanceHighContrast.d.mts +70 -0
  184. package/styles/enhanceHighContrast.d.ts +70 -0
  185. package/styles/enhanceHighContrast.js +502 -0
  186. package/styles/enhanceHighContrast.mjs +495 -0
  187. package/styles/identifier.d.mts +1 -1
  188. package/styles/identifier.d.ts +1 -1
  189. package/styles/index.d.mts +2 -0
  190. package/styles/index.d.ts +2 -0
  191. package/styles/index.js +8 -0
  192. package/styles/index.mjs +1 -0
  193. package/styles/reducedMotion.d.mts +7 -0
  194. package/styles/reducedMotion.d.ts +7 -0
  195. package/styles/reducedMotion.js +21 -0
  196. package/styles/reducedMotion.mjs +14 -0
  197. package/styles/shouldSkipGeneratingVar.js +1 -1
  198. package/styles/shouldSkipGeneratingVar.mjs +1 -1
  199. package/styles/stringifyTheme.js +1 -0
  200. package/styles/stringifyTheme.mjs +1 -0
  201. package/transitions/index.d.mts +1 -1
  202. package/transitions/index.d.ts +1 -1
  203. package/transitions/index.js +0 -11
  204. package/transitions/index.mjs +1 -1
  205. package/transitions/transition.d.mts +1 -12
  206. package/transitions/transition.d.ts +1 -12
  207. package/transitions/types.d.mts +73 -0
  208. package/transitions/types.d.ts +73 -0
  209. package/transitions/useReducedMotion.d.mts +14 -0
  210. package/transitions/useReducedMotion.d.ts +14 -0
  211. package/transitions/useReducedMotion.js +117 -0
  212. package/transitions/useReducedMotion.mjs +110 -0
  213. package/transitions/utils.d.mts +34 -2
  214. package/transitions/utils.d.ts +34 -2
  215. package/transitions/utils.js +33 -4
  216. package/transitions/utils.mjs +31 -4
  217. package/useAutocomplete/useAutocomplete.d.mts +8 -1
  218. package/useAutocomplete/useAutocomplete.d.ts +8 -1
  219. package/useAutocomplete/useAutocomplete.js +66 -4
  220. package/useAutocomplete/useAutocomplete.mjs +66 -4
  221. package/utils/memoTheme.d.mts +1 -1
  222. package/utils/memoTheme.d.ts +1 -1
  223. package/utils/useSlot.d.mts +1 -1
  224. package/utils/useSlot.d.ts +1 -1
  225. package/version/index.js +2 -2
  226. package/version/index.mjs +2 -2
  227. /package/transitions/{transition.js → types.js} +0 -0
  228. /package/transitions/{transition.mjs → types.mjs} +0 -0
@@ -2,16 +2,109 @@
2
2
 
3
3
  import * as React from 'react';
4
4
  import PropTypes from 'prop-types';
5
- import { TransitionGroup } from 'react-transition-group';
6
5
  import clsx from 'clsx';
6
+ import useOnMount from '@mui/utils/useOnMount';
7
7
  import useTimeout from '@mui/utils/useTimeout';
8
- import { keyframes, styled } from "../zero-styled/index.mjs";
8
+ import { keyframes, css, styled, useTheme } from "../zero-styled/index.mjs";
9
9
  import { useDefaultProps } from "../DefaultPropsProvider/index.mjs";
10
10
  import Ripple from "./Ripple.mjs";
11
11
  import touchRippleClasses from "./touchRippleClasses.mjs";
12
+ import useEventCallback from "../utils/useEventCallback.mjs";
13
+ import useReducedMotion from "../transitions/useReducedMotion.mjs";
12
14
  import { jsx as _jsx } from "react/jsx-runtime";
13
15
  const DURATION = 550;
14
16
  export const DELAY_RIPPLE = 80;
17
+ const EMPTY_OBJ = {};
18
+ const EMPTY_ARRAY = [];
19
+ const NOOP = () => {};
20
+
21
+ /**
22
+ * Keep the same DOM order TouchRipple had when it used react-transition-group:
23
+ * exiting ripples stay in place, and new ripples are inserted before the final
24
+ * group of ripples that are waiting for their exit animation to finish.
25
+ *
26
+ * @param {number[]} prevOrder The previous DOM order, including ripples that may be exiting.
27
+ * @param {number[]} nextActiveKeys The ripples that should still be treated as active.
28
+ * @returns {number[]} The next DOM order, preserving the position of exiting ripples where possible.
29
+ */
30
+ function mergeRippleOrder(prevOrder, nextActiveKeys) {
31
+ const nextKeySet = new Set(nextActiveKeys);
32
+ const nextKeysPending = new Map();
33
+ let pendingKeys = [];
34
+ for (const prevKey of prevOrder) {
35
+ if (nextKeySet.has(prevKey)) {
36
+ if (pendingKeys.length > 0) {
37
+ nextKeysPending.set(prevKey, pendingKeys);
38
+ pendingKeys = [];
39
+ }
40
+ } else {
41
+ pendingKeys.push(prevKey);
42
+ }
43
+ }
44
+ const nextOrder = [];
45
+ for (const nextKey of nextActiveKeys) {
46
+ const pendingBefore = nextKeysPending.get(nextKey);
47
+ if (pendingBefore) {
48
+ nextOrder.push(...pendingBefore);
49
+ }
50
+ nextOrder.push(nextKey);
51
+ }
52
+ nextOrder.push(...pendingKeys);
53
+ return nextOrder;
54
+ }
55
+
56
+ /**
57
+ * Calculate where the ripple should start and how large it must be to cover the host element.
58
+ *
59
+ * @param {object} params
60
+ * @param {object} params.event The mouse or touch event that started the ripple.
61
+ * @param {HTMLElement | null} params.element The host element used for measurements. Tests pass `null`.
62
+ * @param {boolean} params.center If `true`, start the ripple from the center of the host element.
63
+ * @returns {{ rippleX: number, rippleY: number, rippleSize: number }} The ripple position and size.
64
+ */
65
+ function computeRippleState({
66
+ event,
67
+ element,
68
+ center
69
+ }) {
70
+ const rect = element ? element.getBoundingClientRect() : {
71
+ width: 0,
72
+ height: 0,
73
+ left: 0,
74
+ top: 0
75
+ };
76
+ let rippleX;
77
+ let rippleY;
78
+ if (center || event === undefined || event.clientX === 0 && event.clientY === 0 || !event.clientX && !event.touches) {
79
+ rippleX = Math.round(rect.width / 2);
80
+ rippleY = Math.round(rect.height / 2);
81
+ } else {
82
+ const {
83
+ clientX,
84
+ clientY
85
+ } = event.touches && event.touches.length > 0 ? event.touches[0] : event;
86
+ rippleX = Math.round(clientX - rect.left);
87
+ rippleY = Math.round(clientY - rect.top);
88
+ }
89
+ let rippleSize;
90
+ if (center) {
91
+ rippleSize = Math.sqrt((2 * rect.width ** 2 + rect.height ** 2) / 3);
92
+
93
+ // Mobile Chrome can skip this animation for even pixel sizes.
94
+ if (rippleSize % 2 === 0) {
95
+ rippleSize += 1;
96
+ }
97
+ } else {
98
+ const sizeX = Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2;
99
+ const sizeY = Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2;
100
+ rippleSize = Math.sqrt(sizeX ** 2 + sizeY ** 2);
101
+ }
102
+ return {
103
+ rippleX,
104
+ rippleY,
105
+ rippleSize
106
+ };
107
+ }
15
108
  const enterKeyframe = keyframes`
16
109
  0% {
17
110
  transform: scale(0);
@@ -45,6 +138,44 @@ const pulsateKeyframe = keyframes`
45
138
  transform: scale(1);
46
139
  }
47
140
  `;
141
+ function getAnimationStyles(theme) {
142
+ if (theme.motion.reducedMotion === 'always') {
143
+ return null;
144
+ }
145
+ const styles = css`
146
+ &.${touchRippleClasses.rippleVisible} {
147
+ animation-name: ${enterKeyframe};
148
+ animation-duration: ${DURATION}ms;
149
+ animation-timing-function: ${theme.transitions.easing.easeInOut};
150
+ }
151
+
152
+ &.${touchRippleClasses.ripplePulsate} {
153
+ animation-duration: ${theme.transitions.duration.shorter}ms;
154
+ }
155
+
156
+ & .${touchRippleClasses.childLeaving} {
157
+ animation-name: ${exitKeyframe};
158
+ animation-duration: ${DURATION}ms;
159
+ animation-timing-function: ${theme.transitions.easing.easeInOut};
160
+ }
161
+
162
+ & .${touchRippleClasses.childPulsate} {
163
+ animation-name: ${pulsateKeyframe};
164
+ animation-duration: 2500ms;
165
+ animation-timing-function: ${theme.transitions.easing.easeInOut};
166
+ animation-iteration-count: infinite;
167
+ animation-delay: 200ms;
168
+ }
169
+ `;
170
+ if (theme.motion.reducedMotion === 'system') {
171
+ return css`
172
+ @media (prefers-reduced-motion: no-preference) {
173
+ ${styles}
174
+ }
175
+ `;
176
+ }
177
+ return styles;
178
+ }
48
179
  export const TouchRippleRoot = styled('span', {
49
180
  name: 'MuiTouchRipple',
50
181
  slot: 'Root'
@@ -60,8 +191,8 @@ export const TouchRippleRoot = styled('span', {
60
191
  borderRadius: 'inherit'
61
192
  });
62
193
 
63
- // This `styled()` function invokes keyframes. `styled-components` only supports keyframes
64
- // in string templates. Do not convert these styles in JS object as it will break.
194
+ // This `styled()` call uses keyframes. styled-components only supports keyframes
195
+ // in template strings, so do not convert these styles to a JS object.
65
196
  export const TouchRippleRipple = styled(Ripple, {
66
197
  name: 'MuiTouchRipple',
67
198
  slot: 'Ripple'
@@ -72,19 +203,16 @@ export const TouchRippleRipple = styled(Ripple, {
72
203
  &.${touchRippleClasses.rippleVisible} {
73
204
  opacity: 0.3;
74
205
  transform: scale(1);
75
- animation-name: ${enterKeyframe};
76
- animation-duration: ${DURATION}ms;
77
- animation-timing-function: ${({
78
- theme
79
- }) => theme.transitions.easing.easeInOut};
80
- }
81
-
82
- &.${touchRippleClasses.ripplePulsate} {
83
- animation-duration: ${({
84
- theme
85
- }) => theme.transitions.duration.shorter}ms;
86
206
  }
87
207
 
208
+ /*
209
+ * Order matters: 'child', 'childLeaving' and 'childPulsate' apply to the same
210
+ * element with equal specificity, so the later rule wins. 'child' must come
211
+ * before 'childLeaving' so the leaving 'opacity: 0' takes precedence. A focus
212
+ * (pulsate) ripple keeps 'pulsateKeyframe' (no opacity animation) on exit, so
213
+ * it relies on this static 'opacity: 0' to disappear on blur instead of
214
+ * lingering until removal.
215
+ */
88
216
  & .${touchRippleClasses.child} {
89
217
  opacity: 1;
90
218
  display: block;
@@ -96,11 +224,6 @@ export const TouchRippleRipple = styled(Ripple, {
96
224
 
97
225
  & .${touchRippleClasses.childLeaving} {
98
226
  opacity: 0;
99
- animation-name: ${exitKeyframe};
100
- animation-duration: ${DURATION}ms;
101
- animation-timing-function: ${({
102
- theme
103
- }) => theme.transitions.easing.easeInOut};
104
227
  }
105
228
 
106
229
  & .${touchRippleClasses.childPulsate} {
@@ -108,35 +231,45 @@ export const TouchRippleRipple = styled(Ripple, {
108
231
  /* @noflip */
109
232
  left: 0px;
110
233
  top: 0;
111
- animation-name: ${pulsateKeyframe};
112
- animation-duration: 2500ms;
113
- animation-timing-function: ${({
114
- theme
115
- }) => theme.transitions.easing.easeInOut};
116
- animation-iteration-count: infinite;
117
- animation-delay: 200ms;
118
234
  }
235
+
236
+ ${({
237
+ theme
238
+ }) => getAnimationStyles(theme)}
119
239
  `;
120
240
 
121
241
  /**
122
242
  * @ignore - internal component.
123
- *
124
- * TODO v5: Make private
125
243
  */
126
244
  const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps, ref) {
127
245
  const props = useDefaultProps({
128
246
  props: inProps,
129
247
  name: 'MuiTouchRipple'
130
248
  });
249
+ const theme = useTheme();
250
+ const reducedMotion = useReducedMotion(theme.motion.reducedMotion, false);
131
251
  const {
132
252
  center: centerProp = false,
133
- classes = {},
253
+ classes = EMPTY_OBJ,
134
254
  className,
135
255
  ...other
136
256
  } = props;
137
- const [ripples, setRipples] = React.useState([]);
257
+ // Store ripples as data so we can keep exiting ripples mounted until their
258
+ // exit animation ends. Ripple calls onExited when it is safe to remove one.
259
+ const [rippleState, setRippleState] = React.useState({
260
+ items: EMPTY_ARRAY,
261
+ order: EMPTY_ARRAY
262
+ });
263
+ const ripples = rippleState.items;
138
264
  const nextKey = React.useRef(0);
139
265
  const rippleCallback = React.useRef(null);
266
+ const mountedRef = React.useRef(false);
267
+ useOnMount(() => {
268
+ mountedRef.current = true;
269
+ return () => {
270
+ mountedRef.current = false;
271
+ };
272
+ });
140
273
  React.useEffect(() => {
141
274
  if (rippleCallback.current) {
142
275
  rippleCallback.current();
@@ -150,10 +283,23 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
150
283
  // We don't want to display the ripple for touch scroll events.
151
284
  const startTimer = useTimeout();
152
285
 
153
- // This is the hook called once the previous timeout is ready.
286
+ // Holds delayed touch-start work until the delay expires or touchend forces it to run.
154
287
  const startTimerCommit = React.useRef(null);
155
288
  const container = React.useRef(null);
156
- const startCommit = React.useCallback(params => {
289
+ const handleExited = useEventCallback(key => {
290
+ if (!mountedRef.current) {
291
+ return;
292
+ }
293
+ setRippleState(prevState => {
294
+ const nextItems = prevState.items.filter(ripple => ripple.key !== key);
295
+ const nextOrder = mergeRippleOrder(prevState.order.filter(rippleKey => rippleKey !== key), nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key));
296
+ return {
297
+ items: nextItems,
298
+ order: nextOrder
299
+ };
300
+ });
301
+ });
302
+ const startCommit = useEventCallback(params => {
157
303
  const {
158
304
  pulsate,
159
305
  rippleX,
@@ -161,29 +307,29 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
161
307
  rippleSize,
162
308
  cb
163
309
  } = params;
164
- setRipples(oldRipples => [...oldRipples, /*#__PURE__*/_jsx(TouchRippleRipple, {
165
- classes: {
166
- ripple: clsx(classes.ripple, touchRippleClasses.ripple),
167
- rippleVisible: clsx(classes.rippleVisible, touchRippleClasses.rippleVisible),
168
- ripplePulsate: clsx(classes.ripplePulsate, touchRippleClasses.ripplePulsate),
169
- child: clsx(classes.child, touchRippleClasses.child),
170
- childLeaving: clsx(classes.childLeaving, touchRippleClasses.childLeaving),
171
- childPulsate: clsx(classes.childPulsate, touchRippleClasses.childPulsate)
172
- },
173
- timeout: DURATION,
174
- pulsate: pulsate,
175
- rippleX: rippleX,
176
- rippleY: rippleY,
177
- rippleSize: rippleSize
178
- }, nextKey.current)]);
310
+ const key = nextKey.current;
179
311
  nextKey.current += 1;
312
+ setRippleState(prevState => {
313
+ const nextItems = [...prevState.items, {
314
+ key,
315
+ pulsate,
316
+ rippleX,
317
+ rippleY,
318
+ rippleSize,
319
+ exiting: false
320
+ }];
321
+ return {
322
+ items: nextItems,
323
+ order: mergeRippleOrder(prevState.order, nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key))
324
+ };
325
+ });
180
326
  rippleCallback.current = cb;
181
- }, [classes]);
182
- const start = React.useCallback((event = {}, options = {}, cb = () => {}) => {
327
+ });
328
+ const start = useEventCallback((event = EMPTY_OBJ, options = EMPTY_OBJ, cb = NOOP) => {
183
329
  const {
184
330
  pulsate = false,
185
331
  center = centerProp || options.pulsate,
186
- fakeElement = false // For test purposes
332
+ fakeElement = false // Used only by tests.
187
333
  } = options;
188
334
  if (event?.type === 'mousedown' && ignoringMouseDown.current) {
189
335
  ignoringMouseDown.current = false;
@@ -193,48 +339,21 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
193
339
  ignoringMouseDown.current = true;
194
340
  }
195
341
  const element = fakeElement ? null : container.current;
196
- const rect = element ? element.getBoundingClientRect() : {
197
- width: 0,
198
- height: 0,
199
- left: 0,
200
- top: 0
201
- };
202
-
203
- // Get the size of the ripple
204
- let rippleX;
205
- let rippleY;
206
- let rippleSize;
207
- if (center || event === undefined || event.clientX === 0 && event.clientY === 0 || !event.clientX && !event.touches) {
208
- rippleX = Math.round(rect.width / 2);
209
- rippleY = Math.round(rect.height / 2);
210
- } else {
211
- const {
212
- clientX,
213
- clientY
214
- } = event.touches && event.touches.length > 0 ? event.touches[0] : event;
215
- rippleX = Math.round(clientX - rect.left);
216
- rippleY = Math.round(clientY - rect.top);
217
- }
218
- if (center) {
219
- rippleSize = Math.sqrt((2 * rect.width ** 2 + rect.height ** 2) / 3);
220
-
221
- // For some reason the animation is broken on Mobile Chrome if the size is even.
222
- if (rippleSize % 2 === 0) {
223
- rippleSize += 1;
224
- }
225
- } else {
226
- const sizeX = Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2;
227
- const sizeY = Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2;
228
- rippleSize = Math.sqrt(sizeX ** 2 + sizeY ** 2);
229
- }
342
+ const {
343
+ rippleX,
344
+ rippleY,
345
+ rippleSize
346
+ } = computeRippleState({
347
+ event,
348
+ element,
349
+ center
350
+ });
230
351
 
231
- // Touch devices
352
+ // Delay touch ripples so scroll gestures do not flash a ripple.
232
353
  if (event?.touches) {
233
- // check that this isn't another touchstart due to multitouch
234
- // otherwise we will only clear a single timer when unmounting while two
235
- // are running
354
+ // Ignore extra touchstart events from multi-touch. There is only one
355
+ // delayed start callback to clear on unmount.
236
356
  if (startTimerCommit.current === null) {
237
- // Prepare the ripple effect.
238
357
  startTimerCommit.current = () => {
239
358
  startCommit({
240
359
  pulsate,
@@ -244,8 +363,6 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
244
363
  cb
245
364
  });
246
365
  };
247
- // Delay the execution of the ripple effect.
248
- // We have to make a tradeoff with this delay value.
249
366
  startTimer.start(DELAY_RIPPLE, () => {
250
367
  if (startTimerCommit.current) {
251
368
  startTimerCommit.current();
@@ -262,17 +379,17 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
262
379
  cb
263
380
  });
264
381
  }
265
- }, [centerProp, startCommit, startTimer]);
266
- const pulsate = React.useCallback(() => {
267
- start({}, {
382
+ });
383
+ const pulsate = useEventCallback(() => {
384
+ start(EMPTY_OBJ, {
268
385
  pulsate: true
269
386
  });
270
- }, [start]);
271
- const stop = React.useCallback((event, cb) => {
387
+ });
388
+ const stop = useEventCallback((event, cb) => {
272
389
  startTimer.clear();
273
390
 
274
- // The touch interaction occurs too quickly.
275
- // We still want to show ripple effect.
391
+ // If touch ends before the delay finishes, show the ripple now and stop it
392
+ // on the next tick so the user still gets feedback.
276
393
  if (event?.type === 'touchend' && startTimerCommit.current) {
277
394
  startTimerCommit.current();
278
395
  startTimerCommit.current = null;
@@ -282,28 +399,55 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
282
399
  return;
283
400
  }
284
401
  startTimerCommit.current = null;
285
- setRipples(oldRipples => {
286
- if (oldRipples.length > 0) {
287
- return oldRipples.slice(1);
402
+ setRippleState(prevState => {
403
+ const firstActiveIndex = prevState.items.findIndex(ripple => !ripple.exiting);
404
+ if (firstActiveIndex === -1) {
405
+ return prevState;
288
406
  }
289
- return oldRipples;
407
+ const nextItems = prevState.items.slice();
408
+ nextItems[firstActiveIndex] = {
409
+ ...nextItems[firstActiveIndex],
410
+ exiting: true
411
+ };
412
+ return {
413
+ items: nextItems,
414
+ order: mergeRippleOrder(prevState.order, nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key))
415
+ };
290
416
  });
291
417
  rippleCallback.current = cb;
292
- }, [startTimer]);
418
+ });
293
419
  React.useImperativeHandle(ref, () => ({
294
420
  pulsate,
295
421
  start,
296
422
  stop
297
423
  }), [pulsate, start, stop]);
424
+ const rippleByKey = new Map(ripples.map(ripple => [ripple.key, ripple]));
425
+ const orderedRipples = rippleState.order.map(rippleKey => rippleByKey.get(rippleKey)).filter(Boolean);
426
+
427
+ // Keep the old react-transition-group DOM order:
428
+ // exiting ripples stay in place, and new ripples are inserted before the
429
+ // final group waiting for its exit animation to finish.
298
430
  return /*#__PURE__*/_jsx(TouchRippleRoot, {
299
431
  className: clsx(touchRippleClasses.root, classes.root, className),
300
432
  ref: container,
301
433
  ...other,
302
- children: /*#__PURE__*/_jsx(TransitionGroup, {
303
- component: null,
304
- exit: true,
305
- children: ripples
306
- })
434
+ children: orderedRipples.map(ripple => /*#__PURE__*/_jsx(TouchRippleRipple, {
435
+ classes: {
436
+ ripple: clsx(classes.ripple, touchRippleClasses.ripple),
437
+ rippleVisible: clsx(classes.rippleVisible, touchRippleClasses.rippleVisible),
438
+ ripplePulsate: clsx(classes.ripplePulsate, touchRippleClasses.ripplePulsate),
439
+ child: clsx(classes.child, touchRippleClasses.child),
440
+ childLeaving: clsx(classes.childLeaving, touchRippleClasses.childLeaving),
441
+ childPulsate: clsx(classes.childPulsate, touchRippleClasses.childPulsate)
442
+ },
443
+ timeout: reducedMotion.shouldReduceMotion ? 0 : DURATION,
444
+ pulsate: ripple.pulsate,
445
+ rippleX: ripple.rippleX,
446
+ rippleY: ripple.rippleY,
447
+ rippleSize: ripple.rippleSize,
448
+ in: !ripple.exiting,
449
+ onExited: () => handleExited(ripple.key)
450
+ }, ripple.key))
307
451
  });
308
452
  });
309
453
  process.env.NODE_ENV !== "production" ? TouchRipple.propTypes /* remove-proptypes */ = {
package/CHANGELOG.md CHANGED
@@ -1,5 +1,127 @@
1
1
  # [Versions](https://mui.com/material-ui/getting-started/versions/)
2
2
 
3
+ ## 9.1.1
4
+
5
+ <!-- generated comparing v9.1.0..master -->
6
+
7
+ _Jun 11, 2026_
8
+
9
+ A big thanks to the 9 contributors who made this release possible.
10
+
11
+ ### `@mui/material@9.1.1`
12
+
13
+ - [Autocomplete] Update fullWidth prop documentation (#48626) @vipin8797
14
+ - [ButtonBase] Fix focus ripple lingering after blur (#48650) @siriwatknp
15
+ - [InitColorSchemeScript] Server-render in tests for React 19.3 (#48604) @Janpot
16
+ - [Transition] Mount child in same commit when opening from unmounted (#48649) @siriwatknp
17
+
18
+ ### `@mui/styled-engine@9.1.1`
19
+
20
+ - [styled-engine] Prevent enableCssLayer styles from being overridden by unlayered styles (#48603) @Janpot
21
+
22
+ ### Docs
23
+
24
+ - Send SOURCE to Brevo on newsletter subscribe (#48633) @aemartos
25
+ - fix TypeScript heading capitalization (#48619) @ifer47
26
+ - Remove outdated security section (#48628) @oliviertassinari
27
+ - [legal] Update EULA links on website (#48642) @rluzists1
28
+
29
+ ### Core
30
+
31
+ - Update dependencies to resolve Dependabot security alerts (#48641) @Janpot
32
+ - [core-docs] Pin StackBlitz demo vite to v7 and plugin-react to v5 (#48643) @Janpot
33
+ - [code-infra] Port codebase to use tsgo (#48616) @brijeshb42
34
+ - [docs-infra] Fix immutable cache headers for /\_next/static assets (#48655) @brijeshb42
35
+ - [docs-infra] Run link checker during docs build instead of as separate CI step (#48634) @Janpot
36
+ - [docs-infra] Support turbopack for docs (#48569) @brijeshb42
37
+ - [test] Cover docs landing-page composites with Argos (#48589) @LukasTy
38
+
39
+ All contributors of this release in alphabetical order: @aemartos, @brijeshb42, @ifer47, @Janpot, @LukasTy, @oliviertassinari, @rluzists1, @siriwatknp, @vipin8797
40
+
41
+ ## 9.1.0
42
+
43
+ <!-- generated comparing v9.0.1..master -->
44
+
45
+ _Jun 8, 2026_
46
+
47
+ A big thanks to the 15 contributors who made this release possible. Here are some highlights ✨:
48
+
49
+ - ⚙️ Support for the [prefers-reduced-motion](https://mui.com/material-ui/transitions/#reduced-motion) setting.
50
+ - ♿️ Improved support for Windows High Contrast mode with the [enhanceHighContrast](https://mui.com/material-ui/customization/palette/#windows-high-contrast-mode) theme wrapper.
51
+
52
+ ### `@mui/material@9.1.0`
53
+
54
+ - [autocomplete] Enable clearing highlight when mouse leaves popup (#48354) @mj12albert
55
+ - [autocomplete] Fix `freeSolo` controlled values cleared by initial `null` (#48611) @mj12albert
56
+ - [autocomplete] Fix item removal when it receives focus from VoiceOver before using Backspace (#48572) @silviuaavram
57
+ - [autocomplete] Fix `resetHighlightOnMouseLeave` JSdoc (#48536) @mj12albert
58
+ - [autocomplete] Guard against null inputRef during unmount (#48617) @noam3127
59
+ - [badge] Add `aria-hidden` to badge content and polish docs demos (#48471) @mj12albert
60
+ - [badge] Use inline CSS variables for anchorOrigin/overlap positioning (#48549) @siriwatknp
61
+ - [button] Fix customized flex gap styles (#48542) @mj12albert
62
+ - [dialog] Fix unwanted `DialogPaper` focus ring (#48535) @mj12albert
63
+ - [focus trap] Fix incorrect tab order when `tabIndex >= 1` (#48546) @mj12albert
64
+ - [progress] Show runtime errors only once (#48591) @silviuaavram
65
+ - [select] Allow spacebar to select elements (#48615) @silviuaavram
66
+ - [select] Support typeahead when closed (#48563) @mj12albert
67
+ - [step button] Choose higher contrast ripple color for dark mode focus (#48612) @silviuaavram
68
+ - [stepper] Include StepConnector inside Step element (#48492) @silviuaavram
69
+ - [stepper] Proper support for vertical alternativeLabel (#48485) @silviuaavram
70
+ - [tabs] Fix React 18 roving tabindex and dedupe invalid-value warning (#48605) @Janpot
71
+ - [theme] Add HighContrast theme enhancer (#48319) @silviuaavram
72
+ - [timeline item] Fix extra ::before spacing when TimelineOppositeContent is present (#46663) @tyalau
73
+ - [tooltip] Prevent stuck-open tooltip when child becomes disabled (#48606) @Janpot
74
+ - [transitions] Custom `Transition` component (#48325) @mj12albert
75
+ - [transitions] Support `prefers-reduced-motion` (#48357) @mj12albert
76
+
77
+ ### `@mui/utils@9.1.0`
78
+
79
+ - [utils] Prevent prototype pollution in fastDeepAssign (#48580) @Janpot
80
+
81
+ ### Docs
82
+
83
+ - [docs] Add function `slotProps` documentation (#48574) @mj12albert
84
+ - [docs] Clarify styled-components version compatibility (#48533) @nightt5879
85
+ - [docs] Fix broken URLs (#48520) @oliviertassinari
86
+ - [docs] Fix invalid JSON in Zed MCP setup example (#48490) @pavan-sh
87
+ - [docs] Mention release version for enhanceHighContrast (#48609) @silviuaavram
88
+ - [docs] Remove outdated MUI X v8 notification (#48600) @cherniavskii
89
+ - [docs] Remove redundant enhanceHighContrast information (#48632) @silviuaavram
90
+ - [docs-infra] Decrease loaded bundle size on docs (#48584) @brijeshb42
91
+ - [docs-infra] Drop multi-locale plumbing from API pages (#48370) @brijeshb42
92
+ - [docs-infra] Fix Cookie banner heading (#48529) @oliviertassinari
93
+ - [docs-infra] Infinitely cache all static assets (#48627) @brijeshb42
94
+ - [docs-infra] Remove outdated noSEOadvantage entries (#48527) @oliviertassinari
95
+ - [docs-infra] Restore build-only invariant throws via `NEXT_RUNTIME` guard (#48475) @Janpot
96
+ - [docs-infra] Test HTML validation in broken links checker (#48088) @Janpot
97
+ - [docs][icons] Fix Font Awesome Chip demo in dark mode (#48576) @siriwatknp
98
+ - [docs][icons] Remove redundant font awesome demo (#48493) @ZeeshanTamboli
99
+ - [docs][modal] Add nested modal guidance (#46507) @JakeSaterlay
100
+ - [docs][stepper] Fix focus management in examples (#48494) @silviuaavram
101
+
102
+ ### Core
103
+
104
+ - Eslint markdown (#48371) @Janpot
105
+ - [agents] Fix some docs links (#48561) @silviuaavram
106
+ - [blog] Copy editing improvement on v9 announcement blog posts (#48543) @joserodolfofreitas
107
+ - [code-infra] Cleanup unused jss packages (#48590) @brijeshb42
108
+ - [code-infra] Collapse canary workflows into nightly and nightly-cron (#48556) @Janpot
109
+ - [code-infra] Convert @mui/private-theming to TypeScript (#48565) @Janpot
110
+ - [code-infra] Convert @mui/styled-engine to TypeScript (#48544) @Janpot
111
+ - [code-infra] Convert @mui/styled-engine-sc to TypeScript (#48577) @Janpot
112
+ - [code-infra] Fix duplicate resource_class in test_regressions CI job (#48601) @LukasTy
113
+ - [code-infra] Make @mui/internal-docs-utils compatible with TypeScript 6 (#48594) @Janpot
114
+ - [code-infra] Migrate CircleCI jobs to Gen2 resource classes (#48593) @LukasTy
115
+ - [code-infra] Parallelize visual regression screenshots (#48557) @Janpot
116
+ - [code-infra] Run nightly-cron on v7.x (#48579) @Janpot
117
+ - [core] Fix typescript@next typecheck (#48587) @Janpot
118
+ - [pnpm] Add security settings to pnpm-workspace.yaml (#48582) @Janpot
119
+ - [styled-engine-sc] Fix compatibility with Vite and Vitest (#48558) @mj12albert
120
+ - [test] Add axe-core tests for mui-material (#48341) @siriwatknp
121
+ - [test] Configure Tailwind CSS in the visual-regression app (#48575) @Janpot
122
+
123
+ All contributors of this release in alphabetical order: @brijeshb42, @cherniavskii, @JakeSaterlay, @Janpot, @joserodolfofreitas, @LukasTy, @mj12albert, @nightt5879, @noam3127, @oliviertassinari, @pavan-sh, @silviuaavram, @siriwatknp, @tyalau, @ZeeshanTamboli
124
+
3
125
  ## 9.0.1
4
126
 
5
127
  <!-- generated comparing v9.0.0..master -->
@@ -17,6 +17,7 @@ var _DefaultPropsProvider = require("../DefaultPropsProvider");
17
17
  var _cardActionAreaClasses = _interopRequireWildcard(require("./cardActionAreaClasses"));
18
18
  var _ButtonBase = _interopRequireDefault(require("../ButtonBase"));
19
19
  var _useSlot = _interopRequireDefault(require("../utils/useSlot"));
20
+ var _utils = require("../transitions/utils");
20
21
  var _jsxRuntime = require("react/jsx-runtime");
21
22
  const useUtilityClasses = ownerState => {
22
23
  const {
@@ -65,7 +66,7 @@ const CardActionAreaFocusHighlight = (0, _zeroStyled.styled)('span', {
65
66
  borderRadius: 'inherit',
66
67
  opacity: 0,
67
68
  backgroundColor: 'currentcolor',
68
- transition: theme.transitions.create('opacity', {
69
+ ...(0, _utils.getTransitionStyles)(theme, 'opacity', {
69
70
  duration: theme.transitions.duration.short
70
71
  })
71
72
  })));
@@ -10,6 +10,7 @@ import { useDefaultProps } from "../DefaultPropsProvider/index.mjs";
10
10
  import cardActionAreaClasses, { getCardActionAreaUtilityClass } from "./cardActionAreaClasses.mjs";
11
11
  import ButtonBase from "../ButtonBase/index.mjs";
12
12
  import useSlot from "../utils/useSlot.mjs";
13
+ import { getTransitionStyles } from "../transitions/utils.mjs";
13
14
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
15
  const useUtilityClasses = ownerState => {
15
16
  const {
@@ -58,7 +59,7 @@ const CardActionAreaFocusHighlight = styled('span', {
58
59
  borderRadius: 'inherit',
59
60
  opacity: 0,
60
61
  backgroundColor: 'currentcolor',
61
- transition: theme.transitions.create('opacity', {
62
+ ...getTransitionStyles(theme, 'opacity', {
62
63
  duration: theme.transitions.duration.short
63
64
  })
64
65
  })));
package/Chip/Chip.js CHANGED
@@ -23,6 +23,7 @@ var _DefaultPropsProvider = require("../DefaultPropsProvider");
23
23
  var _rootShouldForwardProp = _interopRequireDefault(require("../styles/rootShouldForwardProp"));
24
24
  var _chipClasses = _interopRequireWildcard(require("./chipClasses"));
25
25
  var _useSlot = _interopRequireDefault(require("../utils/useSlot"));
26
+ var _utils = require("../transitions/utils");
26
27
  var _jsxRuntime = require("react/jsx-runtime");
27
28
  const useUtilityClasses = ownerState => {
28
29
  const {
@@ -83,7 +84,7 @@ const ChipRoot = (0, _zeroStyled.styled)('div', {
83
84
  backgroundColor: (theme.vars || theme).palette.action.selected,
84
85
  borderRadius: 32 / 2,
85
86
  whiteSpace: 'nowrap',
86
- transition: theme.transitions.create(['background-color', 'box-shadow']),
87
+ ...(0, _utils.getTransitionStyles)(theme, ['background-color', 'box-shadow']),
87
88
  // reset cursor explicitly in case ButtonBase is used
88
89
  cursor: 'unset',
89
90
  // We disable the focus ring for mouse, touch and keyboard users.