UNPKG

9.93 kBJavaScriptView Raw
1"use strict";
2'use client';
3
4var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
5var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
6Object.defineProperty(exports, "__esModule", {
7 value: true
8});
9exports.default = exports.TouchRippleRoot = exports.TouchRippleRipple = exports.DELAY_RIPPLE = void 0;
10var React = _interopRequireWildcard(require("react"));
11var _propTypes = _interopRequireDefault(require("prop-types"));
12var _reactTransitionGroup = require("react-transition-group");
13var _clsx = _interopRequireDefault(require("clsx"));
14var _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout"));
15var _zeroStyled = require("../zero-styled");
16var _DefaultPropsProvider = require("../DefaultPropsProvider");
17var _Ripple = _interopRequireDefault(require("./Ripple"));
18var _touchRippleClasses = _interopRequireDefault(require("./touchRippleClasses"));
19var _jsxRuntime = require("react/jsx-runtime");
20const DURATION = 550;
21const DELAY_RIPPLE = exports.DELAY_RIPPLE = 80;
22const enterKeyframe = (0, _zeroStyled.keyframes)`
23 0% {
24 transform: scale(0);
25 opacity: 0.1;
26 }
27
28 100% {
29 transform: scale(1);
30 opacity: 0.3;
31 }
32`;
33const exitKeyframe = (0, _zeroStyled.keyframes)`
34 0% {
35 opacity: 1;
36 }
37
38 100% {
39 opacity: 0;
40 }
41`;
42const pulsateKeyframe = (0, _zeroStyled.keyframes)`
43 0% {
44 transform: scale(1);
45 }
46
47 50% {
48 transform: scale(0.92);
49 }
50
51 100% {
52 transform: scale(1);
53 }
54`;
55const TouchRippleRoot = exports.TouchRippleRoot = (0, _zeroStyled.styled)('span', {
56 name: 'MuiTouchRipple',
57 slot: 'Root'
58})({
59 overflow: 'hidden',
60 pointerEvents: 'none',
61 position: 'absolute',
62 zIndex: 0,
63 top: 0,
64 right: 0,
65 bottom: 0,
66 left: 0,
67 borderRadius: 'inherit'
68});
69
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.
72const TouchRippleRipple = exports.TouchRippleRipple = (0, _zeroStyled.styled)(_Ripple.default, {
73 name: 'MuiTouchRipple',
74 slot: 'Ripple'
75})`
76 opacity: 0;
77 position: absolute;
78
79 &.${_touchRippleClasses.default.rippleVisible} {
80 opacity: 0.3;
81 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 }
94
95 & .${_touchRippleClasses.default.child} {
96 opacity: 1;
97 display: block;
98 width: 100%;
99 height: 100%;
100 border-radius: 50%;
101 background-color: currentColor;
102 }
103
104 & .${_touchRippleClasses.default.childLeaving} {
105 opacity: 0;
106 animation-name: ${exitKeyframe};
107 animation-duration: ${DURATION}ms;
108 animation-timing-function: ${({
109 theme
110}) => theme.transitions.easing.easeInOut};
111 }
112
113 & .${_touchRippleClasses.default.childPulsate} {
114 position: absolute;
115 /* @noflip */
116 left: 0px;
117 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 }
126`;
127
128/**
129 * @ignore - internal component.
130 *
131 * TODO v5: Make private
132 */
133const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps, ref) {
134 const props = (0, _DefaultPropsProvider.useDefaultProps)({
135 props: inProps,
136 name: 'MuiTouchRipple'
137 });
138 const {
139 center: centerProp = false,
140 classes = {},
141 className,
142 ...other
143 } = props;
144 const [ripples, setRipples] = React.useState([]);
145 const nextKey = React.useRef(0);
146 const rippleCallback = React.useRef(null);
147 React.useEffect(() => {
148 if (rippleCallback.current) {
149 rippleCallback.current();
150 rippleCallback.current = null;
151 }
152 }, [ripples]);
153
154 // Used to filter out mouse emulated events on mobile.
155 const ignoringMouseDown = React.useRef(false);
156 // We use a timer in order to only show the ripples for touch "click" like events.
157 // We don't want to display the ripple for touch scroll events.
158 const startTimer = (0, _useTimeout.default)();
159
160 // This is the hook called once the previous timeout is ready.
161 const startTimerCommit = React.useRef(null);
162 const container = React.useRef(null);
163 const startCommit = React.useCallback(params => {
164 const {
165 pulsate,
166 rippleX,
167 rippleY,
168 rippleSize,
169 cb
170 } = 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)]);
186 nextKey.current += 1;
187 rippleCallback.current = cb;
188 }, [classes]);
189 const start = React.useCallback((event = {}, options = {}, cb = () => {}) => {
190 const {
191 pulsate = false,
192 center = centerProp || options.pulsate,
193 fakeElement = false // For test purposes
194 } = options;
195 if (event?.type === 'mousedown' && ignoringMouseDown.current) {
196 ignoringMouseDown.current = false;
197 return;
198 }
199 if (event?.type === 'touchstart') {
200 ignoringMouseDown.current = true;
201 }
202 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 }
237
238 // Touche devices
239 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
243 if (startTimerCommit.current === null) {
244 // Prepare the ripple effect.
245 startTimerCommit.current = () => {
246 startCommit({
247 pulsate,
248 rippleX,
249 rippleY,
250 rippleSize,
251 cb
252 });
253 };
254 // Delay the execution of the ripple effect.
255 // We have to make a tradeoff with this delay value.
256 startTimer.start(DELAY_RIPPLE, () => {
257 if (startTimerCommit.current) {
258 startTimerCommit.current();
259 startTimerCommit.current = null;
260 }
261 });
262 }
263 } else {
264 startCommit({
265 pulsate,
266 rippleX,
267 rippleY,
268 rippleSize,
269 cb
270 });
271 }
272 }, [centerProp, startCommit, startTimer]);
273 const pulsate = React.useCallback(() => {
274 start({}, {
275 pulsate: true
276 });
277 }, [start]);
278 const stop = React.useCallback((event, cb) => {
279 startTimer.clear();
280
281 // The touch interaction occurs too quickly.
282 // We still want to show ripple effect.
283 if (event?.type === 'touchend' && startTimerCommit.current) {
284 startTimerCommit.current();
285 startTimerCommit.current = null;
286 startTimer.start(0, () => {
287 stop(event, cb);
288 });
289 return;
290 }
291 startTimerCommit.current = null;
292 setRipples(oldRipples => {
293 if (oldRipples.length > 0) {
294 return oldRipples.slice(1);
295 }
296 return oldRipples;
297 });
298 rippleCallback.current = cb;
299 }, [startTimer]);
300 React.useImperativeHandle(ref, () => ({
301 pulsate,
302 start,
303 stop
304 }), [pulsate, start, stop]);
305 return /*#__PURE__*/(0, _jsxRuntime.jsx)(TouchRippleRoot, {
306 className: (0, _clsx.default)(_touchRippleClasses.default.root, classes.root, className),
307 ref: container,
308 ...other,
309 children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactTransitionGroup.TransitionGroup, {
310 component: null,
311 exit: true,
312 children: ripples
313 })
314 });
315});
316process.env.NODE_ENV !== "production" ? TouchRipple.propTypes /* remove-proptypes */ = {
317 /**
318 * If `true`, the ripple starts at the center of the component
319 * rather than at the point of interaction.
320 */
321 center: _propTypes.default.bool,
322 /**
323 * Override or extend the styles applied to the component.
324 */
325 classes: _propTypes.default.object,
326 /**
327 * @ignore
328 */
329 className: _propTypes.default.string
330} : void 0;
331var _default = exports.default = TouchRipple;
\No newline at end of file