@mui/material 9.0.1 → 9.1.0

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