UNPKG

@mui/material

Version:

React components that implement Google's Material Design.

343 lines (325 loc) 11.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.TouchRippleRoot = exports.TouchRippleRipple = exports.DELAY_RIPPLE = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var React = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _reactTransitionGroup = require("react-transition-group"); var _clsx = _interopRequireDefault(require("clsx")); var _system = require("@mui/system"); var _styled = _interopRequireDefault(require("../styles/styled")); var _useThemeProps = _interopRequireDefault(require("../styles/useThemeProps")); var _Ripple = _interopRequireDefault(require("./Ripple")); var _touchRippleClasses = _interopRequireDefault(require("./touchRippleClasses")); var _jsxRuntime = require("react/jsx-runtime"); const _excluded = ["center", "classes", "className"]; function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const DURATION = 550; const DELAY_RIPPLE = 80; exports.DELAY_RIPPLE = DELAY_RIPPLE; const enterKeyframe = (0, _system.keyframes)` 0% { transform: scale(0); opacity: 0.1; } 100% { transform: scale(1); opacity: 0.3; } `; const exitKeyframe = (0, _system.keyframes)` 0% { opacity: 1; } 100% { opacity: 0; } `; const pulsateKeyframe = (0, _system.keyframes)` 0% { transform: scale(1); } 50% { transform: scale(0.92); } 100% { transform: scale(1); } `; const TouchRippleRoot = (0, _styled.default)('span', { name: 'MuiTouchRipple', slot: 'Root' })({ 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. exports.TouchRippleRoot = TouchRippleRoot; const TouchRippleRipple = (0, _styled.default)(_Ripple.default, { name: 'MuiTouchRipple', slot: 'Ripple' })` opacity: 0; position: absolute; &.${_touchRippleClasses.default.rippleVisible} { opacity: 0.3; transform: scale(1); animation-name: ${enterKeyframe}; animation-duration: ${DURATION}ms; animation-timing-function: ${({ theme }) => theme.transitions.easing.easeInOut}; } &.${_touchRippleClasses.default.ripplePulsate} { animation-duration: ${({ theme }) => theme.transitions.duration.shorter}ms; } & .${_touchRippleClasses.default.child} { opacity: 1; display: block; width: 100%; height: 100%; border-radius: 50%; background-color: currentColor; } & .${_touchRippleClasses.default.childLeaving} { opacity: 0; animation-name: ${exitKeyframe}; animation-duration: ${DURATION}ms; animation-timing-function: ${({ theme }) => theme.transitions.easing.easeInOut}; } & .${_touchRippleClasses.default.childPulsate} { position: absolute; /* @noflip */ left: 0px; top: 0; animation-name: ${pulsateKeyframe}; animation-duration: 2500ms; animation-timing-function: ${({ theme }) => theme.transitions.easing.easeInOut}; animation-iteration-count: infinite; animation-delay: 200ms; } `; /** * @ignore - internal component. * * TODO v5: Make private */ exports.TouchRippleRipple = TouchRippleRipple; const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps, ref) { const props = (0, _useThemeProps.default)({ props: inProps, name: 'MuiTouchRipple' }); const { center: centerProp = false, classes = {}, className } = props, other = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded); const [ripples, setRipples] = React.useState([]); const nextKey = React.useRef(0); const rippleCallback = React.useRef(null); React.useEffect(() => { if (rippleCallback.current) { rippleCallback.current(); rippleCallback.current = null; } }, [ripples]); // Used to filter out mouse emulated events on mobile. const 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. const startTimer = React.useRef(null); // This is the hook called once the previous timeout is ready. const startTimerCommit = React.useRef(null); const container = React.useRef(null); React.useEffect(() => { return () => { clearTimeout(startTimer.current); }; }, []); const startCommit = React.useCallback(params => { const { pulsate, rippleX, rippleY, rippleSize, cb } = params; setRipples(oldRipples => [...oldRipples, /*#__PURE__*/(0, _jsxRuntime.jsx)(TouchRippleRipple, { classes: { ripple: (0, _clsx.default)(classes.ripple, _touchRippleClasses.default.ripple), rippleVisible: (0, _clsx.default)(classes.rippleVisible, _touchRippleClasses.default.rippleVisible), ripplePulsate: (0, _clsx.default)(classes.ripplePulsate, _touchRippleClasses.default.ripplePulsate), child: (0, _clsx.default)(classes.child, _touchRippleClasses.default.child), childLeaving: (0, _clsx.default)(classes.childLeaving, _touchRippleClasses.default.childLeaving), childPulsate: (0, _clsx.default)(classes.childPulsate, _touchRippleClasses.default.childPulsate) }, timeout: DURATION, pulsate: pulsate, rippleX: rippleX, rippleY: rippleY, rippleSize: rippleSize }, nextKey.current)]); nextKey.current += 1; rippleCallback.current = cb; }, [classes]); const start = React.useCallback((event = {}, options = {}, cb = () => {}) => { const { pulsate = false, center = centerProp || options.pulsate, fakeElement = false // For test purposes } = options; if ((event == null ? void 0 : event.type) === 'mousedown' && ignoringMouseDown.current) { ignoringMouseDown.current = false; return; } if ((event == null ? void 0 : event.type) === 'touchstart') { ignoringMouseDown.current = true; } const element = fakeElement ? null : container.current; const rect = element ? element.getBoundingClientRect() : { width: 0, height: 0, left: 0, top: 0 }; // Get the size of the ripple let rippleX; let rippleY; let rippleSize; if (center || event === undefined || event.clientX === 0 && event.clientY === 0 || !event.clientX && !event.touches) { rippleX = Math.round(rect.width / 2); rippleY = Math.round(rect.height / 2); } else { const { clientX, clientY } = event.touches && event.touches.length > 0 ? event.touches[0] : event; rippleX = Math.round(clientX - rect.left); rippleY = Math.round(clientY - rect.top); } if (center) { rippleSize = Math.sqrt((2 * rect.width ** 2 + 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 { const sizeX = Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2; const sizeY = Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2; rippleSize = Math.sqrt(sizeX ** 2 + sizeY ** 2); } // Touche devices if (event != null && 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 = () => { startCommit({ pulsate, rippleX, rippleY, rippleSize, cb }); }; // Delay the execution of the ripple effect. startTimer.current = setTimeout(() => { if (startTimerCommit.current) { startTimerCommit.current(); startTimerCommit.current = null; } }, DELAY_RIPPLE); // We have to make a tradeoff with this value. } } else { startCommit({ pulsate, rippleX, rippleY, rippleSize, cb }); } }, [centerProp, startCommit]); const pulsate = React.useCallback(() => { start({}, { pulsate: true }); }, [start]); const stop = React.useCallback((event, cb) => { clearTimeout(startTimer.current); // The touch interaction occurs too quickly. // We still want to show ripple effect. if ((event == null ? void 0 : event.type) === 'touchend' && startTimerCommit.current) { startTimerCommit.current(); startTimerCommit.current = null; startTimer.current = setTimeout(() => { stop(event, cb); }); return; } startTimerCommit.current = null; setRipples(oldRipples => { if (oldRipples.length > 0) { return oldRipples.slice(1); } return oldRipples; }); rippleCallback.current = cb; }, []); React.useImperativeHandle(ref, () => ({ pulsate, start, stop }), [pulsate, start, stop]); return /*#__PURE__*/(0, _jsxRuntime.jsx)(TouchRippleRoot, (0, _extends2.default)({ className: (0, _clsx.default)(_touchRippleClasses.default.root, classes.root, className), ref: container }, other, { children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactTransitionGroup.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.default.bool, /** * Override or extend the styles applied to the component. * See [CSS API](#css) below for more details. */ classes: _propTypes.default.object, /** * @ignore */ className: _propTypes.default.string } : void 0; var _default = TouchRipple; exports.default = _default;