1 | 'use client';
|
2 |
|
3 | import * as React from 'react';
|
4 | import PropTypes from 'prop-types';
|
5 | import { TransitionGroup } from 'react-transition-group';
|
6 | import clsx from 'clsx';
|
7 | import useTimeout from '@mui/utils/useTimeout';
|
8 | import { keyframes, styled } from "../zero-styled/index.js";
|
9 | import { useDefaultProps } from "../DefaultPropsProvider/index.js";
|
10 | import Ripple from "./Ripple.js";
|
11 | import touchRippleClasses from "./touchRippleClasses.js";
|
12 | import { jsx as _jsx } from "react/jsx-runtime";
|
13 | const DURATION = 550;
|
14 | export const DELAY_RIPPLE = 80;
|
15 | const enterKeyframe = keyframes`
|
16 | 0% {
|
17 | transform: scale(0);
|
18 | opacity: 0.1;
|
19 | }
|
20 |
|
21 | 100% {
|
22 | transform: scale(1);
|
23 | opacity: 0.3;
|
24 | }
|
25 | `;
|
26 | const exitKeyframe = keyframes`
|
27 | 0% {
|
28 | opacity: 1;
|
29 | }
|
30 |
|
31 | 100% {
|
32 | opacity: 0;
|
33 | }
|
34 | `;
|
35 | const pulsateKeyframe = keyframes`
|
36 | 0% {
|
37 | transform: scale(1);
|
38 | }
|
39 |
|
40 | 50% {
|
41 | transform: scale(0.92);
|
42 | }
|
43 |
|
44 | 100% {
|
45 | transform: scale(1);
|
46 | }
|
47 | `;
|
48 | export const TouchRippleRoot = styled('span', {
|
49 | name: 'MuiTouchRipple',
|
50 | slot: 'Root'
|
51 | })({
|
52 | overflow: 'hidden',
|
53 | pointerEvents: 'none',
|
54 | position: 'absolute',
|
55 | zIndex: 0,
|
56 | top: 0,
|
57 | right: 0,
|
58 | bottom: 0,
|
59 | left: 0,
|
60 | borderRadius: 'inherit'
|
61 | });
|
62 |
|
63 |
|
64 |
|
65 | export const TouchRippleRipple = styled(Ripple, {
|
66 | name: 'MuiTouchRipple',
|
67 | slot: 'Ripple'
|
68 | })`
|
69 | opacity: 0;
|
70 | position: absolute;
|
71 |
|
72 | &.${touchRippleClasses.rippleVisible} {
|
73 | opacity: 0.3;
|
74 | 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 | }
|
96 |
|
97 | & .${touchRippleClasses.childLeaving} {
|
98 | opacity: 0;
|
99 | animation-name: ${exitKeyframe};
|
100 | animation-duration: ${DURATION}ms;
|
101 | animation-timing-function: ${({
|
102 | theme
|
103 | }) => theme.transitions.easing.easeInOut};
|
104 | }
|
105 |
|
106 | & .${touchRippleClasses.childPulsate} {
|
107 | position: absolute;
|
108 | /* @noflip */
|
109 | left: 0px;
|
110 | 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 | }
|
119 | `;
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | const TouchRipple = React.forwardRef(function TouchRipple(inProps, ref) {
|
127 | const props = useDefaultProps({
|
128 | props: inProps,
|
129 | name: 'MuiTouchRipple'
|
130 | });
|
131 | const {
|
132 | center: centerProp = false,
|
133 | classes = {},
|
134 | className,
|
135 | ...other
|
136 | } = props;
|
137 | const [ripples, setRipples] = React.useState([]);
|
138 | const nextKey = React.useRef(0);
|
139 | const rippleCallback = React.useRef(null);
|
140 | React.useEffect(() => {
|
141 | if (rippleCallback.current) {
|
142 | rippleCallback.current();
|
143 | rippleCallback.current = null;
|
144 | }
|
145 | }, [ripples]);
|
146 |
|
147 |
|
148 | const ignoringMouseDown = React.useRef(false);
|
149 |
|
150 |
|
151 | const startTimer = useTimeout();
|
152 |
|
153 |
|
154 | const startTimerCommit = React.useRef(null);
|
155 | const container = React.useRef(null);
|
156 | const startCommit = React.useCallback(params => {
|
157 | const {
|
158 | pulsate,
|
159 | rippleX,
|
160 | rippleY,
|
161 | rippleSize,
|
162 | cb
|
163 | } = params;
|
164 | setRipples(oldRipples => [...oldRipples, _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)]);
|
179 | nextKey.current += 1;
|
180 | rippleCallback.current = cb;
|
181 | }, [classes]);
|
182 | const start = React.useCallback((event = {}, options = {}, cb = () => {}) => {
|
183 | const {
|
184 | pulsate = false,
|
185 | center = centerProp || options.pulsate,
|
186 | fakeElement = false
|
187 | } = options;
|
188 | if (event?.type === 'mousedown' && ignoringMouseDown.current) {
|
189 | ignoringMouseDown.current = false;
|
190 | return;
|
191 | }
|
192 | if (event?.type === 'touchstart') {
|
193 | ignoringMouseDown.current = true;
|
194 | }
|
195 | 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 |
|
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 |
|
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 | }
|
230 |
|
231 |
|
232 | if (event?.touches) {
|
233 |
|
234 |
|
235 |
|
236 | if (startTimerCommit.current === null) {
|
237 |
|
238 | startTimerCommit.current = () => {
|
239 | startCommit({
|
240 | pulsate,
|
241 | rippleX,
|
242 | rippleY,
|
243 | rippleSize,
|
244 | cb
|
245 | });
|
246 | };
|
247 |
|
248 |
|
249 | startTimer.start(DELAY_RIPPLE, () => {
|
250 | if (startTimerCommit.current) {
|
251 | startTimerCommit.current();
|
252 | startTimerCommit.current = null;
|
253 | }
|
254 | });
|
255 | }
|
256 | } else {
|
257 | startCommit({
|
258 | pulsate,
|
259 | rippleX,
|
260 | rippleY,
|
261 | rippleSize,
|
262 | cb
|
263 | });
|
264 | }
|
265 | }, [centerProp, startCommit, startTimer]);
|
266 | const pulsate = React.useCallback(() => {
|
267 | start({}, {
|
268 | pulsate: true
|
269 | });
|
270 | }, [start]);
|
271 | const stop = React.useCallback((event, cb) => {
|
272 | startTimer.clear();
|
273 |
|
274 |
|
275 |
|
276 | if (event?.type === 'touchend' && startTimerCommit.current) {
|
277 | startTimerCommit.current();
|
278 | startTimerCommit.current = null;
|
279 | startTimer.start(0, () => {
|
280 | stop(event, cb);
|
281 | });
|
282 | return;
|
283 | }
|
284 | startTimerCommit.current = null;
|
285 | setRipples(oldRipples => {
|
286 | if (oldRipples.length > 0) {
|
287 | return oldRipples.slice(1);
|
288 | }
|
289 | return oldRipples;
|
290 | });
|
291 | rippleCallback.current = cb;
|
292 | }, [startTimer]);
|
293 | React.useImperativeHandle(ref, () => ({
|
294 | pulsate,
|
295 | start,
|
296 | stop
|
297 | }), [pulsate, start, stop]);
|
298 | return _jsx(TouchRippleRoot, {
|
299 | className: clsx(touchRippleClasses.root, classes.root, className),
|
300 | ref: container,
|
301 | ...other,
|
302 | children: _jsx(TransitionGroup, {
|
303 | component: null,
|
304 | exit: true,
|
305 | children: ripples
|
306 | })
|
307 | });
|
308 | });
|
309 | process.env.NODE_ENV !== "production" ? TouchRipple.propTypes = {
|
310 | |
311 |
|
312 |
|
313 |
|
314 | center: PropTypes.bool,
|
315 | |
316 |
|
317 |
|
318 | classes: PropTypes.object,
|
319 | |
320 |
|
321 |
|
322 | className: PropTypes.string
|
323 | } : void 0;
|
324 | export default TouchRipple; |
\ | No newline at end of file |