UNPKG

@mui/material

Version:

Quickly build beautiful React apps. MUI is a simple and customizable component library to build faster, beautiful, and more accessible React applications. Follow your own design system, or start with Material Design.

281 lines (254 loc) 11 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; import _taggedTemplateLiteral from "@babel/runtime/helpers/esm/taggedTemplateLiteral"; var _templateObject, _templateObject2, _templateObject3, _templateObject4; import * as React from 'react'; import PropTypes from 'prop-types'; import { TransitionGroup } from 'react-transition-group'; import clsx from 'clsx'; import { keyframes } from '@mui/system'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; import Ripple from './Ripple'; import touchRippleClasses from './touchRippleClasses'; import { jsx as _jsx } from "react/jsx-runtime"; var DURATION = 550; export var DELAY_RIPPLE = 80; var enterKeyframe = keyframes(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n 0% {\n transform: scale(0);\n opacity: 0.1;\n }\n\n 100% {\n transform: scale(1);\n opacity: 0.3;\n }\n"]))); var exitKeyframe = keyframes(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n 0% {\n opacity: 1;\n }\n\n 100% {\n opacity: 0;\n }\n"]))); var pulsateKeyframe = keyframes(_templateObject3 || (_templateObject3 = _taggedTemplateLiteral(["\n 0% {\n transform: scale(1);\n }\n\n 50% {\n transform: scale(0.92);\n }\n\n 100% {\n transform: scale(1);\n }\n"]))); export var TouchRippleRoot = styled('span', { name: 'MuiTouchRipple', slot: 'Root', skipSx: true })({ overflow: 'hidden', pointerEvents: 'none', position: 'absolute', zIndex: 0, top: 0, right: 0, bottom: 0, left: 0, borderRadius: 'inherit' }); // This `styled()` function invokes keyframes. `styled-components` only supports keyframes // in string templates. Do not convert these styles in JS object as it will break. export var TouchRippleRipple = styled(Ripple, { name: 'MuiTouchRipple', slot: 'Ripple' })(_templateObject4 || (_templateObject4 = _taggedTemplateLiteral(["\n opacity: 0;\n position: absolute;\n\n &.", " {\n opacity: 0.3;\n transform: scale(1);\n animation-name: ", ";\n animation-duration: ", "ms;\n animation-timing-function: ", ";\n }\n\n &.", " {\n animation-duration: ", "ms;\n }\n\n & .", " {\n opacity: 1;\n display: block;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background-color: currentColor;\n }\n\n & .", " {\n opacity: 0;\n animation-name: ", ";\n animation-duration: ", "ms;\n animation-timing-function: ", ";\n }\n\n & .", " {\n position: absolute;\n /* @noflip */\n left: 0px;\n top: 0;\n animation-name: ", ";\n animation-duration: 2500ms;\n animation-timing-function: ", ";\n animation-iteration-count: infinite;\n animation-delay: 200ms;\n }\n"])), touchRippleClasses.rippleVisible, enterKeyframe, DURATION, function (_ref) { var theme = _ref.theme; return theme.transitions.easing.easeInOut; }, touchRippleClasses.ripplePulsate, function (_ref2) { var theme = _ref2.theme; return theme.transitions.duration.shorter; }, touchRippleClasses.child, touchRippleClasses.childLeaving, exitKeyframe, DURATION, function (_ref3) { var theme = _ref3.theme; return theme.transitions.easing.easeInOut; }, touchRippleClasses.childPulsate, pulsateKeyframe, function (_ref4) { var theme = _ref4.theme; return theme.transitions.easing.easeInOut; }); /** * @ignore - internal component. * * TODO v5: Make private */ var TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps, ref) { var props = useThemeProps({ props: inProps, name: 'MuiTouchRipple' }); var _props$center = props.center, centerProp = _props$center === void 0 ? false : _props$center, _props$classes = props.classes, classes = _props$classes === void 0 ? {} : _props$classes, className = props.className, other = _objectWithoutProperties(props, ["center", "classes", "className"]); var _React$useState = React.useState([]), ripples = _React$useState[0], setRipples = _React$useState[1]; var nextKey = React.useRef(0); var rippleCallback = React.useRef(null); React.useEffect(function () { if (rippleCallback.current) { rippleCallback.current(); rippleCallback.current = null; } }, [ripples]); // Used to filter out mouse emulated events on mobile. var ignoringMouseDown = React.useRef(false); // We use a timer in order to only show the ripples for touch "click" like events. // We don't want to display the ripple for touch scroll events. var startTimer = React.useRef(null); // This is the hook called once the previous timeout is ready. var startTimerCommit = React.useRef(null); var container = React.useRef(null); React.useEffect(function () { return function () { clearTimeout(startTimer.current); }; }, []); var startCommit = React.useCallback(function (params) { var pulsate = params.pulsate, rippleX = params.rippleX, rippleY = params.rippleY, rippleSize = params.rippleSize, cb = params.cb; setRipples(function (oldRipples) { return [].concat(_toConsumableArray(oldRipples), [/*#__PURE__*/_jsx(TouchRippleRipple, { classes: { ripple: clsx(classes.ripple, touchRippleClasses.ripple), rippleVisible: clsx(classes.rippleVisible, touchRippleClasses.rippleVisible), ripplePulsate: clsx(classes.ripplePulsate, touchRippleClasses.ripplePulsate), child: clsx(classes.child, touchRippleClasses.child), childLeaving: clsx(classes.childLeaving, touchRippleClasses.childLeaving), childPulsate: clsx(classes.childPulsate, touchRippleClasses.childPulsate) }, timeout: DURATION, pulsate: pulsate, rippleX: rippleX, rippleY: rippleY, rippleSize: rippleSize }, nextKey.current)]); }); nextKey.current += 1; rippleCallback.current = cb; }, [classes]); var start = React.useCallback(function () { var event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var cb = arguments.length > 2 ? arguments[2] : undefined; var _options$pulsate = options.pulsate, pulsate = _options$pulsate === void 0 ? false : _options$pulsate, _options$center = options.center, center = _options$center === void 0 ? centerProp || options.pulsate : _options$center, _options$fakeElement = options.fakeElement, fakeElement = _options$fakeElement === void 0 ? false : _options$fakeElement; if (event.type === 'mousedown' && ignoringMouseDown.current) { ignoringMouseDown.current = false; return; } if (event.type === 'touchstart') { ignoringMouseDown.current = true; } var element = fakeElement ? null : container.current; var rect = element ? element.getBoundingClientRect() : { width: 0, height: 0, left: 0, top: 0 }; // Get the size of the ripple var rippleX; var rippleY; var rippleSize; if (center || event.clientX === 0 && event.clientY === 0 || !event.clientX && !event.touches) { rippleX = Math.round(rect.width / 2); rippleY = Math.round(rect.height / 2); } else { var _ref5 = event.touches ? event.touches[0] : event, clientX = _ref5.clientX, clientY = _ref5.clientY; rippleX = Math.round(clientX - rect.left); rippleY = Math.round(clientY - rect.top); } if (center) { rippleSize = Math.sqrt((2 * Math.pow(rect.width, 2) + Math.pow(rect.height, 2)) / 3); // For some reason the animation is broken on Mobile Chrome if the size is even. if (rippleSize % 2 === 0) { rippleSize += 1; } } else { var sizeX = Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2; var sizeY = Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2; rippleSize = Math.sqrt(Math.pow(sizeX, 2) + Math.pow(sizeY, 2)); } // Touche devices if (event.touches) { // check that this isn't another touchstart due to multitouch // otherwise we will only clear a single timer when unmounting while two // are running if (startTimerCommit.current === null) { // Prepare the ripple effect. startTimerCommit.current = function () { startCommit({ pulsate: pulsate, rippleX: rippleX, rippleY: rippleY, rippleSize: rippleSize, cb: cb }); }; // Delay the execution of the ripple effect. startTimer.current = setTimeout(function () { if (startTimerCommit.current) { startTimerCommit.current(); startTimerCommit.current = null; } }, DELAY_RIPPLE); // We have to make a tradeoff with this value. } } else { startCommit({ pulsate: pulsate, rippleX: rippleX, rippleY: rippleY, rippleSize: rippleSize, cb: cb }); } }, [centerProp, startCommit]); var pulsate = React.useCallback(function () { start({}, { pulsate: true }); }, [start]); var stop = React.useCallback(function (event, cb) { clearTimeout(startTimer.current); // The touch interaction occurs too quickly. // We still want to show ripple effect. if (event.type === 'touchend' && startTimerCommit.current) { startTimerCommit.current(); startTimerCommit.current = null; startTimer.current = setTimeout(function () { stop(event, cb); }); return; } startTimerCommit.current = null; setRipples(function (oldRipples) { if (oldRipples.length > 0) { return oldRipples.slice(1); } return oldRipples; }); rippleCallback.current = cb; }, []); React.useImperativeHandle(ref, function () { return { pulsate: pulsate, start: start, stop: stop }; }, [pulsate, start, stop]); return /*#__PURE__*/_jsx(TouchRippleRoot, _extends({ className: clsx(classes.root, touchRippleClasses.root, className), ref: container }, other, { children: /*#__PURE__*/_jsx(TransitionGroup, { component: null, exit: true, children: ripples }) })); }); process.env.NODE_ENV !== "production" ? TouchRipple.propTypes = { /** * If `true`, the ripple starts at the center of the component * rather than at the point of interaction. */ center: PropTypes.bool, /** * Override or extend the styles applied to the component. * See [CSS API](#css) below for more details. */ classes: PropTypes.object, /** * @ignore */ className: PropTypes.string } : void 0; export default TouchRipple;