UNPKG

d2-ui

Version:
293 lines (242 loc) 10.6 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _simpleAssign = require('simple-assign'); var _simpleAssign2 = _interopRequireDefault(_simpleAssign); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _reactAddonsTransitionGroup = require('react-addons-transition-group'); var _reactAddonsTransitionGroup2 = _interopRequireDefault(_reactAddonsTransitionGroup); var _dom = require('../utils/dom'); var _dom2 = _interopRequireDefault(_dom); var _CircleRipple = require('./CircleRipple'); var _CircleRipple2 = _interopRequireDefault(_CircleRipple); var _reactAddonsUpdate = require('react-addons-update'); var _reactAddonsUpdate2 = _interopRequireDefault(_reactAddonsUpdate); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function push(array, obj) { var newObj = Array.isArray(obj) ? obj : [obj]; return (0, _reactAddonsUpdate2.default)(array, { $push: newObj }); } function shift(array) { // Remove the first element in the array using React immutability helpers return (0, _reactAddonsUpdate2.default)(array, { $splice: [[0, 1]] }); } var TouchRipple = function (_Component) { _inherits(TouchRipple, _Component); function TouchRipple(props, context) { _classCallCheck(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 = _possibleConstructorReturn(this, Object.getPrototypeOf(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 = push(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; } _createClass(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 = push(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 style = {}; 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; style.height = rippleSize + 'px'; style.width = rippleSize + 'px'; style.top = top + 'px'; style.left = left + 'px'; return style; } }, { key: 'calcDiag', value: function calcDiag(a, b) { return Math.sqrt(a * a + b * b); } }, { key: 'render', value: function render() { var _props = this.props; var children = _props.children; var style = _props.style; var _state = this.state; var hasRipples = _state.hasRipples; var 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' }, style); rippleGroup = _react2.default.createElement( _reactAddonsTransitionGroup2.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.propTypes = { abortOnScroll: _react.PropTypes.bool, centerRipple: _react.PropTypes.bool, children: _react.PropTypes.node, color: _react.PropTypes.string, opacity: _react.PropTypes.number, style: _react.PropTypes.object }; TouchRipple.defaultProps = { abortOnScroll: true }; TouchRipple.contextTypes = { muiTheme: _react.PropTypes.object.isRequired }; exports.default = TouchRipple;