UNPKG

material-ui

Version:

React Components that Implement Google's Material Design.

311 lines (253 loc) 10.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _toArray2 = require('babel-runtime/helpers/toArray'); var _toArray3 = _interopRequireDefault(_toArray2); var _simpleAssign = require('simple-assign'); var _simpleAssign2 = _interopRequireDefault(_simpleAssign); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _TransitionGroup = require('react-transition-group/TransitionGroup'); var _TransitionGroup2 = _interopRequireDefault(_TransitionGroup); var _dom = require('../utils/dom'); var _dom2 = _interopRequireDefault(_dom); var _CircleRipple = require('./CircleRipple'); var _CircleRipple2 = _interopRequireDefault(_CircleRipple); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // Remove the first element of the array var shift = function shift(_ref) { var _ref2 = (0, _toArray3.default)(_ref), newArray = _ref2.slice(1); return newArray; }; var TouchRipple = function (_Component) { (0, _inherits3.default)(TouchRipple, _Component); function TouchRipple(props, context) { (0, _classCallCheck3.default)(this, TouchRipple); // Touch start produces a mouse down event for compat reasons. To avoid // showing ripples twice we skip showing a ripple for the first mouse down // after a touch start. Note we don't store ignoreNextMouseDown in this.state // to avoid re-rendering when we change it. var _this = (0, _possibleConstructorReturn3.default)(this, (TouchRipple.__proto__ || (0, _getPrototypeOf2.default)(TouchRipple)).call(this, props, context)); _this.handleMouseDown = function (event) { // only listen to left clicks if (event.button === 0) { _this.start(event, false); } }; _this.handleMouseUp = function () { _this.end(); }; _this.handleMouseLeave = function () { _this.end(); }; _this.handleTouchStart = function (event) { event.stopPropagation(); // If the user is swiping (not just tapping), save the position so we can // abort ripples if the user appears to be scrolling. if (_this.props.abortOnScroll && event.touches) { _this.startListeningForScrollAbort(event); _this.startTime = Date.now(); } _this.start(event, true); }; _this.handleTouchEnd = function () { _this.end(); }; _this.handleTouchMove = function (event) { // Stop trying to abort if we're already 300ms into the animation var timeSinceStart = Math.abs(Date.now() - _this.startTime); if (timeSinceStart > 300) { _this.stopListeningForScrollAbort(); return; } // If the user is scrolling... var deltaY = Math.abs(event.touches[0].clientY - _this.firstTouchY); var deltaX = Math.abs(event.touches[0].clientX - _this.firstTouchX); // Call it a scroll after an arbitrary 6px (feels reasonable in testing) if (deltaY > 6 || deltaX > 6) { var currentRipples = _this.state.ripples; var ripple = currentRipples[0]; // This clone will replace the ripple in ReactTransitionGroup with a // version that will disappear immediately when removed from the DOM var abortedRipple = _react2.default.cloneElement(ripple, { aborted: true }); // Remove the old ripple and replace it with the new updated one currentRipples = shift(currentRipples); currentRipples = [].concat((0, _toConsumableArray3.default)(currentRipples), [abortedRipple]); _this.setState({ ripples: currentRipples }, function () { // Call end after we've set the ripple to abort otherwise the setState // in end() merges with this and the ripple abort fails _this.end(); }); } }; _this.ignoreNextMouseDown = false; _this.state = { // This prop allows us to only render the ReactTransitionGroup // on the first click of the component, making the inital render faster. hasRipples: false, nextKey: 0, ripples: [] }; return _this; } (0, _createClass3.default)(TouchRipple, [{ key: 'start', value: function start(event, isRippleTouchGenerated) { var theme = this.context.muiTheme.ripple; if (this.ignoreNextMouseDown && !isRippleTouchGenerated) { this.ignoreNextMouseDown = false; return; } var ripples = this.state.ripples; // Add a ripple to the ripples array ripples = [].concat((0, _toConsumableArray3.default)(ripples), [_react2.default.createElement(_CircleRipple2.default, { key: this.state.nextKey, style: !this.props.centerRipple ? this.getRippleStyle(event) : {}, color: this.props.color || theme.color, opacity: this.props.opacity, touchGenerated: isRippleTouchGenerated })]); this.ignoreNextMouseDown = isRippleTouchGenerated; this.setState({ hasRipples: true, nextKey: this.state.nextKey + 1, ripples: ripples }); } }, { key: 'end', value: function end() { var currentRipples = this.state.ripples; this.setState({ ripples: shift(currentRipples) }); if (this.props.abortOnScroll) { this.stopListeningForScrollAbort(); } } // Check if the user seems to be scrolling and abort the animation if so }, { key: 'startListeningForScrollAbort', value: function startListeningForScrollAbort(event) { this.firstTouchY = event.touches[0].clientY; this.firstTouchX = event.touches[0].clientX; // Note that when scolling Chrome throttles this event to every 200ms // Also note we don't listen for scroll events directly as there's no general // way to cover cases like scrolling within containers on the page document.body.addEventListener('touchmove', this.handleTouchMove); } }, { key: 'stopListeningForScrollAbort', value: function stopListeningForScrollAbort() { document.body.removeEventListener('touchmove', this.handleTouchMove); } }, { key: 'getRippleStyle', value: function getRippleStyle(event) { var el = _reactDom2.default.findDOMNode(this); var elHeight = el.offsetHeight; var elWidth = el.offsetWidth; var offset = _dom2.default.offset(el); var isTouchEvent = event.touches && event.touches.length; var pageX = isTouchEvent ? event.touches[0].pageX : event.pageX; var pageY = isTouchEvent ? event.touches[0].pageY : event.pageY; var pointerX = pageX - offset.left; var pointerY = pageY - offset.top; var topLeftDiag = this.calcDiag(pointerX, pointerY); var topRightDiag = this.calcDiag(elWidth - pointerX, pointerY); var botRightDiag = this.calcDiag(elWidth - pointerX, elHeight - pointerY); var botLeftDiag = this.calcDiag(pointerX, elHeight - pointerY); var rippleRadius = Math.max(topLeftDiag, topRightDiag, botRightDiag, botLeftDiag); var rippleSize = rippleRadius * 2; var left = pointerX - rippleRadius; var top = pointerY - rippleRadius; return { directionInvariant: true, height: rippleSize, width: rippleSize, top: top, left: left }; } }, { key: 'calcDiag', value: function calcDiag(a, b) { return Math.sqrt(a * a + b * b); } }, { key: 'render', value: function render() { var _props = this.props, children = _props.children, style = _props.style; var _state = this.state, hasRipples = _state.hasRipples, ripples = _state.ripples; var prepareStyles = this.context.muiTheme.prepareStyles; var rippleGroup = void 0; if (hasRipples) { var mergedStyles = (0, _simpleAssign2.default)({ height: '100%', width: '100%', position: 'absolute', top: 0, left: 0, overflow: 'hidden', pointerEvents: 'none', zIndex: 1 // This is also needed so that ripples do not bleed past a parent border radius. }, style); rippleGroup = _react2.default.createElement( _TransitionGroup2.default, { style: prepareStyles(mergedStyles) }, ripples ); } return _react2.default.createElement( 'div', { onMouseUp: this.handleMouseUp, onMouseDown: this.handleMouseDown, onMouseLeave: this.handleMouseLeave, onTouchStart: this.handleTouchStart, onTouchEnd: this.handleTouchEnd }, rippleGroup, children ); } }]); return TouchRipple; }(_react.Component); TouchRipple.defaultProps = { abortOnScroll: true }; TouchRipple.contextTypes = { muiTheme: _propTypes2.default.object.isRequired }; TouchRipple.propTypes = process.env.NODE_ENV !== "production" ? { abortOnScroll: _propTypes2.default.bool, centerRipple: _propTypes2.default.bool, children: _propTypes2.default.node, color: _propTypes2.default.string, opacity: _propTypes2.default.number, style: _propTypes2.default.object } : {}; exports.default = TouchRipple;