306 lines
9.9 KiB
JavaScript
306 lines
9.9 KiB
JavaScript
|
|
'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 _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);
|
||
|
|
|
||
|
|
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'
|
||
|
|
}, 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.defaultProps = {
|
||
|
|
abortOnScroll: true
|
||
|
|
};
|
||
|
|
TouchRipple.contextTypes = {
|
||
|
|
muiTheme: _react.PropTypes.object.isRequired
|
||
|
|
};
|
||
|
|
process.env.NODE_ENV !== "production" ? 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
|
||
|
|
} : void 0;
|
||
|
|
exports.default = TouchRipple;
|