1 | import _extends from "@babel/runtime/helpers/esm/extends";
|
2 | import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
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 withStyles from '../styles/withStyles';
|
8 | import Ripple from './Ripple';
|
9 | const DURATION = 550;
|
10 | export const DELAY_RIPPLE = 80;
|
11 | export const styles = theme => ({
|
12 |
|
13 | root: {
|
14 | overflow: 'hidden',
|
15 | pointerEvents: 'none',
|
16 | position: 'absolute',
|
17 | zIndex: 0,
|
18 | top: 0,
|
19 | right: 0,
|
20 | bottom: 0,
|
21 | left: 0,
|
22 | borderRadius: 'inherit'
|
23 | },
|
24 |
|
25 |
|
26 | ripple: {
|
27 | opacity: 0,
|
28 | position: 'absolute'
|
29 | },
|
30 |
|
31 |
|
32 | rippleVisible: {
|
33 | opacity: 0.3,
|
34 | transform: 'scale(1)',
|
35 | animation: `$enter ${DURATION}ms ${theme.transitions.easing.easeInOut}`
|
36 | },
|
37 |
|
38 |
|
39 | ripplePulsate: {
|
40 | animationDuration: `${theme.transitions.duration.shorter}ms`
|
41 | },
|
42 |
|
43 |
|
44 | child: {
|
45 | opacity: 1,
|
46 | display: 'block',
|
47 | width: '100%',
|
48 | height: '100%',
|
49 | borderRadius: '50%',
|
50 | backgroundColor: 'currentColor'
|
51 | },
|
52 |
|
53 |
|
54 | childLeaving: {
|
55 | opacity: 0,
|
56 | animation: `$exit ${DURATION}ms ${theme.transitions.easing.easeInOut}`
|
57 | },
|
58 |
|
59 |
|
60 | childPulsate: {
|
61 | position: 'absolute',
|
62 | left: 0,
|
63 | top: 0,
|
64 | animation: `$pulsate 2500ms ${theme.transitions.easing.easeInOut} 200ms infinite`
|
65 | },
|
66 | '@keyframes enter': {
|
67 | '0%': {
|
68 | transform: 'scale(0)',
|
69 | opacity: 0.1
|
70 | },
|
71 | '100%': {
|
72 | transform: 'scale(1)',
|
73 | opacity: 0.3
|
74 | }
|
75 | },
|
76 | '@keyframes exit': {
|
77 | '0%': {
|
78 | opacity: 1
|
79 | },
|
80 | '100%': {
|
81 | opacity: 0
|
82 | }
|
83 | },
|
84 | '@keyframes pulsate': {
|
85 | '0%': {
|
86 | transform: 'scale(1)'
|
87 | },
|
88 | '50%': {
|
89 | transform: 'scale(0.92)'
|
90 | },
|
91 | '100%': {
|
92 | transform: 'scale(1)'
|
93 | }
|
94 | }
|
95 | });
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | const TouchRipple = React.forwardRef(function TouchRipple(props, ref) {
|
103 | const {
|
104 | center: centerProp = false,
|
105 | classes,
|
106 | className
|
107 | } = props,
|
108 | other = _objectWithoutPropertiesLoose(props, ["center", "classes", "className"]);
|
109 |
|
110 | const [ripples, setRipples] = React.useState([]);
|
111 | const nextKey = React.useRef(0);
|
112 | const rippleCallback = React.useRef(null);
|
113 | React.useEffect(() => {
|
114 | if (rippleCallback.current) {
|
115 | rippleCallback.current();
|
116 | rippleCallback.current = null;
|
117 | }
|
118 | }, [ripples]);
|
119 |
|
120 | const ignoringMouseDown = React.useRef(false);
|
121 |
|
122 |
|
123 | const startTimer = React.useRef(null);
|
124 |
|
125 | const startTimerCommit = React.useRef(null);
|
126 | const container = React.useRef(null);
|
127 | React.useEffect(() => {
|
128 | return () => {
|
129 | clearTimeout(startTimer.current);
|
130 | };
|
131 | }, []);
|
132 | const startCommit = React.useCallback(params => {
|
133 | const {
|
134 | pulsate,
|
135 | rippleX,
|
136 | rippleY,
|
137 | rippleSize,
|
138 | cb
|
139 | } = params;
|
140 | setRipples(oldRipples => [...oldRipples, React.createElement(Ripple, {
|
141 | key: nextKey.current,
|
142 | classes: classes,
|
143 | timeout: DURATION,
|
144 | pulsate: pulsate,
|
145 | rippleX: rippleX,
|
146 | rippleY: rippleY,
|
147 | rippleSize: rippleSize
|
148 | })]);
|
149 | nextKey.current += 1;
|
150 | rippleCallback.current = cb;
|
151 | }, [classes]);
|
152 | const start = React.useCallback((event = {}, options = {}, cb) => {
|
153 | const {
|
154 | pulsate = false,
|
155 | center = centerProp || options.pulsate,
|
156 | fakeElement = false
|
157 |
|
158 | } = options;
|
159 |
|
160 | if (event.type === 'mousedown' && ignoringMouseDown.current) {
|
161 | ignoringMouseDown.current = false;
|
162 | return;
|
163 | }
|
164 |
|
165 | if (event.type === 'touchstart') {
|
166 | ignoringMouseDown.current = true;
|
167 | }
|
168 |
|
169 | const element = fakeElement ? null : container.current;
|
170 | const rect = element ? element.getBoundingClientRect() : {
|
171 | width: 0,
|
172 | height: 0,
|
173 | left: 0,
|
174 | top: 0
|
175 | };
|
176 |
|
177 | let rippleX;
|
178 | let rippleY;
|
179 | let rippleSize;
|
180 |
|
181 | if (center || event.clientX === 0 && event.clientY === 0 || !event.clientX && !event.touches) {
|
182 | rippleX = Math.round(rect.width / 2);
|
183 | rippleY = Math.round(rect.height / 2);
|
184 | } else {
|
185 | const {
|
186 | clientX,
|
187 | clientY
|
188 | } = event.touches ? event.touches[0] : event;
|
189 | rippleX = Math.round(clientX - rect.left);
|
190 | rippleY = Math.round(clientY - rect.top);
|
191 | }
|
192 |
|
193 | if (center) {
|
194 | rippleSize = Math.sqrt((2 * rect.width ** 2 + rect.height ** 2) / 3);
|
195 |
|
196 | if (rippleSize % 2 === 0) {
|
197 | rippleSize += 1;
|
198 | }
|
199 | } else {
|
200 | const sizeX = Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2;
|
201 | const sizeY = Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2;
|
202 | rippleSize = Math.sqrt(sizeX ** 2 + sizeY ** 2);
|
203 | }
|
204 |
|
205 |
|
206 | if (event.touches) {
|
207 |
|
208 |
|
209 |
|
210 | if (startTimerCommit.current === null) {
|
211 |
|
212 | startTimerCommit.current = () => {
|
213 | startCommit({
|
214 | pulsate,
|
215 | rippleX,
|
216 | rippleY,
|
217 | rippleSize,
|
218 | cb
|
219 | });
|
220 | };
|
221 |
|
222 |
|
223 | startTimer.current = setTimeout(() => {
|
224 | if (startTimerCommit.current) {
|
225 | startTimerCommit.current();
|
226 | startTimerCommit.current = null;
|
227 | }
|
228 | }, DELAY_RIPPLE);
|
229 | }
|
230 | } else {
|
231 | startCommit({
|
232 | pulsate,
|
233 | rippleX,
|
234 | rippleY,
|
235 | rippleSize,
|
236 | cb
|
237 | });
|
238 | }
|
239 | }, [centerProp, startCommit]);
|
240 | const pulsate = React.useCallback(() => {
|
241 | start({}, {
|
242 | pulsate: true
|
243 | });
|
244 | }, [start]);
|
245 | const stop = React.useCallback((event, cb) => {
|
246 | clearTimeout(startTimer.current);
|
247 |
|
248 |
|
249 | if (event.type === 'touchend' && startTimerCommit.current) {
|
250 | event.persist();
|
251 | startTimerCommit.current();
|
252 | startTimerCommit.current = null;
|
253 | startTimer.current = setTimeout(() => {
|
254 | stop(event, cb);
|
255 | });
|
256 | return;
|
257 | }
|
258 |
|
259 | startTimerCommit.current = null;
|
260 | setRipples(oldRipples => {
|
261 | if (oldRipples.length > 0) {
|
262 | return oldRipples.slice(1);
|
263 | }
|
264 |
|
265 | return oldRipples;
|
266 | });
|
267 | rippleCallback.current = cb;
|
268 | }, []);
|
269 | React.useImperativeHandle(ref, () => ({
|
270 | pulsate,
|
271 | start,
|
272 | stop
|
273 | }), [pulsate, start, stop]);
|
274 | return React.createElement("span", _extends({
|
275 | className: clsx(classes.root, className),
|
276 | ref: container
|
277 | }, other), React.createElement(TransitionGroup, {
|
278 | component: null,
|
279 | exit: true
|
280 | }, ripples));
|
281 | });
|
282 | process.env.NODE_ENV !== "production" ? TouchRipple.propTypes = {
|
283 | |
284 |
|
285 |
|
286 |
|
287 | center: PropTypes.bool,
|
288 |
|
289 | |
290 |
|
291 |
|
292 |
|
293 | classes: PropTypes.object.isRequired,
|
294 |
|
295 | |
296 |
|
297 |
|
298 | className: PropTypes.string
|
299 | } : void 0;
|
300 | export default withStyles(styles, {
|
301 | flip: false,
|
302 | name: 'MuiTouchRipple'
|
303 | })( React.memo(TouchRipple)); |
\ | No newline at end of file |