@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
@@ -9,16 +9,109 @@ Object.defineProperty(exports, "__esModule", {
9
9
  exports.default = exports.TouchRippleRoot = exports.TouchRippleRipple = exports.DELAY_RIPPLE = void 0;
10
10
  var React = _interopRequireWildcard(require("react"));
11
11
  var _propTypes = _interopRequireDefault(require("prop-types"));
12
- var _reactTransitionGroup = require("react-transition-group");
13
12
  var _clsx = _interopRequireDefault(require("clsx"));
13
+ var _useOnMount = _interopRequireDefault(require("@mui/utils/useOnMount"));
14
14
  var _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout"));
15
15
  var _zeroStyled = require("../zero-styled");
16
16
  var _DefaultPropsProvider = require("../DefaultPropsProvider");
17
17
  var _Ripple = _interopRequireDefault(require("./Ripple"));
18
18
  var _touchRippleClasses = _interopRequireDefault(require("./touchRippleClasses"));
19
+ var _useEventCallback = _interopRequireDefault(require("../utils/useEventCallback"));
20
+ var _useReducedMotion = _interopRequireDefault(require("../transitions/useReducedMotion"));
19
21
  var _jsxRuntime = require("react/jsx-runtime");
20
22
  const DURATION = 550;
21
23
  const DELAY_RIPPLE = exports.DELAY_RIPPLE = 80;
24
+ const EMPTY_OBJ = {};
25
+ const EMPTY_ARRAY = [];
26
+ const NOOP = () => {};
27
+
28
+ /**
29
+ * Keep the same DOM order TouchRipple had when it used react-transition-group:
30
+ * exiting ripples stay in place, and new ripples are inserted before the final
31
+ * group of ripples that are waiting for their exit animation to finish.
32
+ *
33
+ * @param {number[]} prevOrder The previous DOM order, including ripples that may be exiting.
34
+ * @param {number[]} nextActiveKeys The ripples that should still be treated as active.
35
+ * @returns {number[]} The next DOM order, preserving the position of exiting ripples where possible.
36
+ */
37
+ function mergeRippleOrder(prevOrder, nextActiveKeys) {
38
+ const nextKeySet = new Set(nextActiveKeys);
39
+ const nextKeysPending = new Map();
40
+ let pendingKeys = [];
41
+ for (const prevKey of prevOrder) {
42
+ if (nextKeySet.has(prevKey)) {
43
+ if (pendingKeys.length > 0) {
44
+ nextKeysPending.set(prevKey, pendingKeys);
45
+ pendingKeys = [];
46
+ }
47
+ } else {
48
+ pendingKeys.push(prevKey);
49
+ }
50
+ }
51
+ const nextOrder = [];
52
+ for (const nextKey of nextActiveKeys) {
53
+ const pendingBefore = nextKeysPending.get(nextKey);
54
+ if (pendingBefore) {
55
+ nextOrder.push(...pendingBefore);
56
+ }
57
+ nextOrder.push(nextKey);
58
+ }
59
+ nextOrder.push(...pendingKeys);
60
+ return nextOrder;
61
+ }
62
+
63
+ /**
64
+ * Calculate where the ripple should start and how large it must be to cover the host element.
65
+ *
66
+ * @param {object} params
67
+ * @param {object} params.event The mouse or touch event that started the ripple.
68
+ * @param {HTMLElement | null} params.element The host element used for measurements. Tests pass `null`.
69
+ * @param {boolean} params.center If `true`, start the ripple from the center of the host element.
70
+ * @returns {{ rippleX: number, rippleY: number, rippleSize: number }} The ripple position and size.
71
+ */
72
+ function computeRippleState({
73
+ event,
74
+ element,
75
+ center
76
+ }) {
77
+ const rect = element ? element.getBoundingClientRect() : {
78
+ width: 0,
79
+ height: 0,
80
+ left: 0,
81
+ top: 0
82
+ };
83
+ let rippleX;
84
+ let rippleY;
85
+ if (center || event === undefined || event.clientX === 0 && event.clientY === 0 || !event.clientX && !event.touches) {
86
+ rippleX = Math.round(rect.width / 2);
87
+ rippleY = Math.round(rect.height / 2);
88
+ } else {
89
+ const {
90
+ clientX,
91
+ clientY
92
+ } = event.touches && event.touches.length > 0 ? event.touches[0] : event;
93
+ rippleX = Math.round(clientX - rect.left);
94
+ rippleY = Math.round(clientY - rect.top);
95
+ }
96
+ let rippleSize;
97
+ if (center) {
98
+ rippleSize = Math.sqrt((2 * rect.width ** 2 + rect.height ** 2) / 3);
99
+
100
+ // Mobile Chrome can skip this animation for even pixel sizes.
101
+ if (rippleSize % 2 === 0) {
102
+ rippleSize += 1;
103
+ }
104
+ } else {
105
+ const sizeX = Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2;
106
+ const sizeY = Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2;
107
+ rippleSize = Math.sqrt(sizeX ** 2 + sizeY ** 2);
108
+ }
109
+ return {
110
+ rippleX,
111
+ rippleY,
112
+ rippleSize
113
+ };
114
+ }
22
115
  const enterKeyframe = (0, _zeroStyled.keyframes)`
23
116
  0% {
24
117
  transform: scale(0);
@@ -52,6 +145,44 @@ const pulsateKeyframe = (0, _zeroStyled.keyframes)`
52
145
  transform: scale(1);
53
146
  }
54
147
  `;
148
+ function getAnimationStyles(theme) {
149
+ if (theme.motion.reducedMotion === 'always') {
150
+ return null;
151
+ }
152
+ const styles = (0, _zeroStyled.css)`
153
+ &.${_touchRippleClasses.default.rippleVisible} {
154
+ animation-name: ${enterKeyframe};
155
+ animation-duration: ${DURATION}ms;
156
+ animation-timing-function: ${theme.transitions.easing.easeInOut};
157
+ }
158
+
159
+ &.${_touchRippleClasses.default.ripplePulsate} {
160
+ animation-duration: ${theme.transitions.duration.shorter}ms;
161
+ }
162
+
163
+ & .${_touchRippleClasses.default.childLeaving} {
164
+ animation-name: ${exitKeyframe};
165
+ animation-duration: ${DURATION}ms;
166
+ animation-timing-function: ${theme.transitions.easing.easeInOut};
167
+ }
168
+
169
+ & .${_touchRippleClasses.default.childPulsate} {
170
+ animation-name: ${pulsateKeyframe};
171
+ animation-duration: 2500ms;
172
+ animation-timing-function: ${theme.transitions.easing.easeInOut};
173
+ animation-iteration-count: infinite;
174
+ animation-delay: 200ms;
175
+ }
176
+ `;
177
+ if (theme.motion.reducedMotion === 'system') {
178
+ return (0, _zeroStyled.css)`
179
+ @media (prefers-reduced-motion: no-preference) {
180
+ ${styles}
181
+ }
182
+ `;
183
+ }
184
+ return styles;
185
+ }
55
186
  const TouchRippleRoot = exports.TouchRippleRoot = (0, _zeroStyled.styled)('span', {
56
187
  name: 'MuiTouchRipple',
57
188
  slot: 'Root'
@@ -67,8 +198,8 @@ const TouchRippleRoot = exports.TouchRippleRoot = (0, _zeroStyled.styled)('span'
67
198
  borderRadius: 'inherit'
68
199
  });
69
200
 
70
- // This `styled()` function invokes keyframes. `styled-components` only supports keyframes
71
- // in string templates. Do not convert these styles in JS object as it will break.
201
+ // This `styled()` call uses keyframes. styled-components only supports keyframes
202
+ // in template strings, so do not convert these styles to a JS object.
72
203
  const TouchRippleRipple = exports.TouchRippleRipple = (0, _zeroStyled.styled)(_Ripple.default, {
73
204
  name: 'MuiTouchRipple',
74
205
  slot: 'Ripple'
@@ -79,19 +210,16 @@ const TouchRippleRipple = exports.TouchRippleRipple = (0, _zeroStyled.styled)(_R
79
210
  &.${_touchRippleClasses.default.rippleVisible} {
80
211
  opacity: 0.3;
81
212
  transform: scale(1);
82
- animation-name: ${enterKeyframe};
83
- animation-duration: ${DURATION}ms;
84
- animation-timing-function: ${({
85
- theme
86
- }) => theme.transitions.easing.easeInOut};
87
- }
88
-
89
- &.${_touchRippleClasses.default.ripplePulsate} {
90
- animation-duration: ${({
91
- theme
92
- }) => theme.transitions.duration.shorter}ms;
93
213
  }
94
214
 
215
+ /*
216
+ * Order matters: 'child', 'childLeaving' and 'childPulsate' apply to the same
217
+ * element with equal specificity, so the later rule wins. 'child' must come
218
+ * before 'childLeaving' so the leaving 'opacity: 0' takes precedence. A focus
219
+ * (pulsate) ripple keeps 'pulsateKeyframe' (no opacity animation) on exit, so
220
+ * it relies on this static 'opacity: 0' to disappear on blur instead of
221
+ * lingering until removal.
222
+ */
95
223
  & .${_touchRippleClasses.default.child} {
96
224
  opacity: 1;
97
225
  display: block;
@@ -103,11 +231,6 @@ const TouchRippleRipple = exports.TouchRippleRipple = (0, _zeroStyled.styled)(_R
103
231
 
104
232
  & .${_touchRippleClasses.default.childLeaving} {
105
233
  opacity: 0;
106
- animation-name: ${exitKeyframe};
107
- animation-duration: ${DURATION}ms;
108
- animation-timing-function: ${({
109
- theme
110
- }) => theme.transitions.easing.easeInOut};
111
234
  }
112
235
 
113
236
  & .${_touchRippleClasses.default.childPulsate} {
@@ -115,35 +238,45 @@ const TouchRippleRipple = exports.TouchRippleRipple = (0, _zeroStyled.styled)(_R
115
238
  /* @noflip */
116
239
  left: 0px;
117
240
  top: 0;
118
- animation-name: ${pulsateKeyframe};
119
- animation-duration: 2500ms;
120
- animation-timing-function: ${({
121
- theme
122
- }) => theme.transitions.easing.easeInOut};
123
- animation-iteration-count: infinite;
124
- animation-delay: 200ms;
125
241
  }
242
+
243
+ ${({
244
+ theme
245
+ }) => getAnimationStyles(theme)}
126
246
  `;
127
247
 
128
248
  /**
129
249
  * @ignore - internal component.
130
- *
131
- * TODO v5: Make private
132
250
  */
133
251
  const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps, ref) {
134
252
  const props = (0, _DefaultPropsProvider.useDefaultProps)({
135
253
  props: inProps,
136
254
  name: 'MuiTouchRipple'
137
255
  });
256
+ const theme = (0, _zeroStyled.useTheme)();
257
+ const reducedMotion = (0, _useReducedMotion.default)(theme.motion.reducedMotion, false);
138
258
  const {
139
259
  center: centerProp = false,
140
- classes = {},
260
+ classes = EMPTY_OBJ,
141
261
  className,
142
262
  ...other
143
263
  } = props;
144
- const [ripples, setRipples] = React.useState([]);
264
+ // Store ripples as data so we can keep exiting ripples mounted until their
265
+ // exit animation ends. Ripple calls onExited when it is safe to remove one.
266
+ const [rippleState, setRippleState] = React.useState({
267
+ items: EMPTY_ARRAY,
268
+ order: EMPTY_ARRAY
269
+ });
270
+ const ripples = rippleState.items;
145
271
  const nextKey = React.useRef(0);
146
272
  const rippleCallback = React.useRef(null);
273
+ const mountedRef = React.useRef(false);
274
+ (0, _useOnMount.default)(() => {
275
+ mountedRef.current = true;
276
+ return () => {
277
+ mountedRef.current = false;
278
+ };
279
+ });
147
280
  React.useEffect(() => {
148
281
  if (rippleCallback.current) {
149
282
  rippleCallback.current();
@@ -157,10 +290,23 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
157
290
  // We don't want to display the ripple for touch scroll events.
158
291
  const startTimer = (0, _useTimeout.default)();
159
292
 
160
- // This is the hook called once the previous timeout is ready.
293
+ // Holds delayed touch-start work until the delay expires or touchend forces it to run.
161
294
  const startTimerCommit = React.useRef(null);
162
295
  const container = React.useRef(null);
163
- const startCommit = React.useCallback(params => {
296
+ const handleExited = (0, _useEventCallback.default)(key => {
297
+ if (!mountedRef.current) {
298
+ return;
299
+ }
300
+ setRippleState(prevState => {
301
+ const nextItems = prevState.items.filter(ripple => ripple.key !== key);
302
+ const nextOrder = mergeRippleOrder(prevState.order.filter(rippleKey => rippleKey !== key), nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key));
303
+ return {
304
+ items: nextItems,
305
+ order: nextOrder
306
+ };
307
+ });
308
+ });
309
+ const startCommit = (0, _useEventCallback.default)(params => {
164
310
  const {
165
311
  pulsate,
166
312
  rippleX,
@@ -168,29 +314,29 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
168
314
  rippleSize,
169
315
  cb
170
316
  } = params;
171
- setRipples(oldRipples => [...oldRipples, /*#__PURE__*/(0, _jsxRuntime.jsx)(TouchRippleRipple, {
172
- classes: {
173
- ripple: (0, _clsx.default)(classes.ripple, _touchRippleClasses.default.ripple),
174
- rippleVisible: (0, _clsx.default)(classes.rippleVisible, _touchRippleClasses.default.rippleVisible),
175
- ripplePulsate: (0, _clsx.default)(classes.ripplePulsate, _touchRippleClasses.default.ripplePulsate),
176
- child: (0, _clsx.default)(classes.child, _touchRippleClasses.default.child),
177
- childLeaving: (0, _clsx.default)(classes.childLeaving, _touchRippleClasses.default.childLeaving),
178
- childPulsate: (0, _clsx.default)(classes.childPulsate, _touchRippleClasses.default.childPulsate)
179
- },
180
- timeout: DURATION,
181
- pulsate: pulsate,
182
- rippleX: rippleX,
183
- rippleY: rippleY,
184
- rippleSize: rippleSize
185
- }, nextKey.current)]);
317
+ const key = nextKey.current;
186
318
  nextKey.current += 1;
319
+ setRippleState(prevState => {
320
+ const nextItems = [...prevState.items, {
321
+ key,
322
+ pulsate,
323
+ rippleX,
324
+ rippleY,
325
+ rippleSize,
326
+ exiting: false
327
+ }];
328
+ return {
329
+ items: nextItems,
330
+ order: mergeRippleOrder(prevState.order, nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key))
331
+ };
332
+ });
187
333
  rippleCallback.current = cb;
188
- }, [classes]);
189
- const start = React.useCallback((event = {}, options = {}, cb = () => {}) => {
334
+ });
335
+ const start = (0, _useEventCallback.default)((event = EMPTY_OBJ, options = EMPTY_OBJ, cb = NOOP) => {
190
336
  const {
191
337
  pulsate = false,
192
338
  center = centerProp || options.pulsate,
193
- fakeElement = false // For test purposes
339
+ fakeElement = false // Used only by tests.
194
340
  } = options;
195
341
  if (event?.type === 'mousedown' && ignoringMouseDown.current) {
196
342
  ignoringMouseDown.current = false;
@@ -200,48 +346,21 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
200
346
  ignoringMouseDown.current = true;
201
347
  }
202
348
  const element = fakeElement ? null : container.current;
203
- const rect = element ? element.getBoundingClientRect() : {
204
- width: 0,
205
- height: 0,
206
- left: 0,
207
- top: 0
208
- };
209
-
210
- // Get the size of the ripple
211
- let rippleX;
212
- let rippleY;
213
- let rippleSize;
214
- if (center || event === undefined || event.clientX === 0 && event.clientY === 0 || !event.clientX && !event.touches) {
215
- rippleX = Math.round(rect.width / 2);
216
- rippleY = Math.round(rect.height / 2);
217
- } else {
218
- const {
219
- clientX,
220
- clientY
221
- } = event.touches && event.touches.length > 0 ? event.touches[0] : event;
222
- rippleX = Math.round(clientX - rect.left);
223
- rippleY = Math.round(clientY - rect.top);
224
- }
225
- if (center) {
226
- rippleSize = Math.sqrt((2 * rect.width ** 2 + rect.height ** 2) / 3);
227
-
228
- // For some reason the animation is broken on Mobile Chrome if the size is even.
229
- if (rippleSize % 2 === 0) {
230
- rippleSize += 1;
231
- }
232
- } else {
233
- const sizeX = Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2;
234
- const sizeY = Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2;
235
- rippleSize = Math.sqrt(sizeX ** 2 + sizeY ** 2);
236
- }
349
+ const {
350
+ rippleX,
351
+ rippleY,
352
+ rippleSize
353
+ } = computeRippleState({
354
+ event,
355
+ element,
356
+ center
357
+ });
237
358
 
238
- // Touch devices
359
+ // Delay touch ripples so scroll gestures do not flash a ripple.
239
360
  if (event?.touches) {
240
- // check that this isn't another touchstart due to multitouch
241
- // otherwise we will only clear a single timer when unmounting while two
242
- // are running
361
+ // Ignore extra touchstart events from multi-touch. There is only one
362
+ // delayed start callback to clear on unmount.
243
363
  if (startTimerCommit.current === null) {
244
- // Prepare the ripple effect.
245
364
  startTimerCommit.current = () => {
246
365
  startCommit({
247
366
  pulsate,
@@ -251,8 +370,6 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
251
370
  cb
252
371
  });
253
372
  };
254
- // Delay the execution of the ripple effect.
255
- // We have to make a tradeoff with this delay value.
256
373
  startTimer.start(DELAY_RIPPLE, () => {
257
374
  if (startTimerCommit.current) {
258
375
  startTimerCommit.current();
@@ -269,17 +386,17 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
269
386
  cb
270
387
  });
271
388
  }
272
- }, [centerProp, startCommit, startTimer]);
273
- const pulsate = React.useCallback(() => {
274
- start({}, {
389
+ });
390
+ const pulsate = (0, _useEventCallback.default)(() => {
391
+ start(EMPTY_OBJ, {
275
392
  pulsate: true
276
393
  });
277
- }, [start]);
278
- const stop = React.useCallback((event, cb) => {
394
+ });
395
+ const stop = (0, _useEventCallback.default)((event, cb) => {
279
396
  startTimer.clear();
280
397
 
281
- // The touch interaction occurs too quickly.
282
- // We still want to show ripple effect.
398
+ // If touch ends before the delay finishes, show the ripple now and stop it
399
+ // on the next tick so the user still gets feedback.
283
400
  if (event?.type === 'touchend' && startTimerCommit.current) {
284
401
  startTimerCommit.current();
285
402
  startTimerCommit.current = null;
@@ -289,28 +406,55 @@ const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps,
289
406
  return;
290
407
  }
291
408
  startTimerCommit.current = null;
292
- setRipples(oldRipples => {
293
- if (oldRipples.length > 0) {
294
- return oldRipples.slice(1);
409
+ setRippleState(prevState => {
410
+ const firstActiveIndex = prevState.items.findIndex(ripple => !ripple.exiting);
411
+ if (firstActiveIndex === -1) {
412
+ return prevState;
295
413
  }
296
- return oldRipples;
414
+ const nextItems = prevState.items.slice();
415
+ nextItems[firstActiveIndex] = {
416
+ ...nextItems[firstActiveIndex],
417
+ exiting: true
418
+ };
419
+ return {
420
+ items: nextItems,
421
+ order: mergeRippleOrder(prevState.order, nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key))
422
+ };
297
423
  });
298
424
  rippleCallback.current = cb;
299
- }, [startTimer]);
425
+ });
300
426
  React.useImperativeHandle(ref, () => ({
301
427
  pulsate,
302
428
  start,
303
429
  stop
304
430
  }), [pulsate, start, stop]);
431
+ const rippleByKey = new Map(ripples.map(ripple => [ripple.key, ripple]));
432
+ const orderedRipples = rippleState.order.map(rippleKey => rippleByKey.get(rippleKey)).filter(Boolean);
433
+
434
+ // Keep the old react-transition-group DOM order:
435
+ // exiting ripples stay in place, and new ripples are inserted before the
436
+ // final group waiting for its exit animation to finish.
305
437
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(TouchRippleRoot, {
306
438
  className: (0, _clsx.default)(_touchRippleClasses.default.root, classes.root, className),
307
439
  ref: container,
308
440
  ...other,
309
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactTransitionGroup.TransitionGroup, {
310
- component: null,
311
- exit: true,
312
- children: ripples
313
- })
441
+ children: orderedRipples.map(ripple => /*#__PURE__*/(0, _jsxRuntime.jsx)(TouchRippleRipple, {
442
+ classes: {
443
+ ripple: (0, _clsx.default)(classes.ripple, _touchRippleClasses.default.ripple),
444
+ rippleVisible: (0, _clsx.default)(classes.rippleVisible, _touchRippleClasses.default.rippleVisible),
445
+ ripplePulsate: (0, _clsx.default)(classes.ripplePulsate, _touchRippleClasses.default.ripplePulsate),
446
+ child: (0, _clsx.default)(classes.child, _touchRippleClasses.default.child),
447
+ childLeaving: (0, _clsx.default)(classes.childLeaving, _touchRippleClasses.default.childLeaving),
448
+ childPulsate: (0, _clsx.default)(classes.childPulsate, _touchRippleClasses.default.childPulsate)
449
+ },
450
+ timeout: reducedMotion.shouldReduceMotion ? 0 : DURATION,
451
+ pulsate: ripple.pulsate,
452
+ rippleX: ripple.rippleX,
453
+ rippleY: ripple.rippleY,
454
+ rippleSize: ripple.rippleSize,
455
+ in: !ripple.exiting,
456
+ onExited: () => handleExited(ripple.key)
457
+ }, ripple.key))
314
458
  });
315
459
  });
316
460
  process.env.NODE_ENV !== "production" ? TouchRipple.propTypes /* remove-proptypes */ = {