UNPKG

8.32 kBJavaScriptView Raw
1import _extends from "@babel/runtime/helpers/esm/extends";
2import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
3import * as React from 'react';
4import PropTypes from 'prop-types';
5import { TransitionGroup } from 'react-transition-group';
6import clsx from 'clsx';
7import withStyles from '../styles/withStyles';
8import Ripple from './Ripple';
9const DURATION = 550;
10export const DELAY_RIPPLE = 80;
11export const styles = theme => ({
12 /* Styles applied to the root element. */
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 /* Styles applied to the internal `Ripple` components `ripple` class. */
26 ripple: {
27 opacity: 0,
28 position: 'absolute'
29 },
30
31 /* Styles applied to the internal `Ripple` components `rippleVisible` class. */
32 rippleVisible: {
33 opacity: 0.3,
34 transform: 'scale(1)',
35 animation: `$enter ${DURATION}ms ${theme.transitions.easing.easeInOut}`
36 },
37
38 /* Styles applied to the internal `Ripple` components `ripplePulsate` class. */
39 ripplePulsate: {
40 animationDuration: `${theme.transitions.duration.shorter}ms`
41 },
42
43 /* Styles applied to the internal `Ripple` components `child` class. */
44 child: {
45 opacity: 1,
46 display: 'block',
47 width: '100%',
48 height: '100%',
49 borderRadius: '50%',
50 backgroundColor: 'currentColor'
51 },
52
53 /* Styles applied to the internal `Ripple` components `childLeaving` class. */
54 childLeaving: {
55 opacity: 0,
56 animation: `$exit ${DURATION}ms ${theme.transitions.easing.easeInOut}`
57 },
58
59 /* Styles applied to the internal `Ripple` components `childPulsate` class. */
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 * @ignore - internal component.
98 *
99 * TODO v5: Make private
100 */
101
102const TouchRipple = /*#__PURE__*/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]); // Used to filter out mouse emulated events on mobile.
119
120 const ignoringMouseDown = React.useRef(false); // We use a timer in order to only show the ripples for touch "click" like events.
121 // We don't want to display the ripple for touch scroll events.
122
123 const startTimer = React.useRef(null); // This is the hook called once the previous timeout is ready.
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, /*#__PURE__*/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 // For test purposes
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 }; // Get the size of the ripple
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); // For some reason the animation is broken on Mobile Chrome if the size if even.
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 } // Touche devices
204
205
206 if (event.touches) {
207 // check that this isn't another touchstart due to multitouch
208 // otherwise we will only clear a single timer when unmounting while two
209 // are running
210 if (startTimerCommit.current === null) {
211 // Prepare the ripple effect.
212 startTimerCommit.current = () => {
213 startCommit({
214 pulsate,
215 rippleX,
216 rippleY,
217 rippleSize,
218 cb
219 });
220 }; // Delay the execution of the ripple effect.
221
222
223 startTimer.current = setTimeout(() => {
224 if (startTimerCommit.current) {
225 startTimerCommit.current();
226 startTimerCommit.current = null;
227 }
228 }, DELAY_RIPPLE); // We have to make a tradeoff with this value.
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); // The touch interaction occurs too quickly.
247 // We still want to show ripple effect.
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 /*#__PURE__*/React.createElement("span", _extends({
275 className: clsx(classes.root, className),
276 ref: container
277 }, other), /*#__PURE__*/React.createElement(TransitionGroup, {
278 component: null,
279 exit: true
280 }, ripples));
281});
282process.env.NODE_ENV !== "production" ? TouchRipple.propTypes = {
283 /**
284 * If `true`, the ripple starts at the center of the component
285 * rather than at the point of interaction.
286 */
287 center: PropTypes.bool,
288
289 /**
290 * Override or extend the styles applied to the component.
291 * See [CSS API](#css) below for more details.
292 */
293 classes: PropTypes.object.isRequired,
294
295 /**
296 * @ignore
297 */
298 className: PropTypes.string
299} : void 0;
300export default withStyles(styles, {
301 flip: false,
302 name: 'MuiTouchRipple'
303})( /*#__PURE__*/React.memo(TouchRipple));
\No newline at end of file