UNPKG

10.3 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
8
9var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
10
11var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
12
13var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
14
15var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
16
17var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
18
19var _createClass2 = require('babel-runtime/helpers/createClass');
20
21var _createClass3 = _interopRequireDefault(_createClass2);
22
23var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
24
25var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
26
27var _inherits2 = require('babel-runtime/helpers/inherits');
28
29var _inherits3 = _interopRequireDefault(_inherits2);
30
31var _toArray2 = require('babel-runtime/helpers/toArray');
32
33var _toArray3 = _interopRequireDefault(_toArray2);
34
35var _simpleAssign = require('simple-assign');
36
37var _simpleAssign2 = _interopRequireDefault(_simpleAssign);
38
39var _react = require('react');
40
41var _react2 = _interopRequireDefault(_react);
42
43var _propTypes = require('prop-types');
44
45var _propTypes2 = _interopRequireDefault(_propTypes);
46
47var _reactDom = require('react-dom');
48
49var _reactDom2 = _interopRequireDefault(_reactDom);
50
51var _TransitionGroup = require('react-transition-group/TransitionGroup');
52
53var _TransitionGroup2 = _interopRequireDefault(_TransitionGroup);
54
55var _dom = require('../utils/dom');
56
57var _dom2 = _interopRequireDefault(_dom);
58
59var _CircleRipple = require('./CircleRipple');
60
61var _CircleRipple2 = _interopRequireDefault(_CircleRipple);
62
63function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
64
65// Remove the first element of the array
66var shift = function shift(_ref) {
67 var _ref2 = (0, _toArray3.default)(_ref),
68 newArray = _ref2.slice(1);
69
70 return newArray;
71};
72
73var TouchRipple = function (_Component) {
74 (0, _inherits3.default)(TouchRipple, _Component);
75
76 function TouchRipple(props, context) {
77 (0, _classCallCheck3.default)(this, TouchRipple);
78
79 // Touch start produces a mouse down event for compat reasons. To avoid
80 // showing ripples twice we skip showing a ripple for the first mouse down
81 // after a touch start. Note we don't store ignoreNextMouseDown in this.state
82 // to avoid re-rendering when we change it.
83 var _this = (0, _possibleConstructorReturn3.default)(this, (TouchRipple.__proto__ || (0, _getPrototypeOf2.default)(TouchRipple)).call(this, props, context));
84
85 _this.handleMouseDown = function (event) {
86 // only listen to left clicks
87 if (event.button === 0) {
88 _this.start(event, false);
89 }
90 };
91
92 _this.handleMouseUp = function () {
93 _this.end();
94 };
95
96 _this.handleMouseLeave = function () {
97 _this.end();
98 };
99
100 _this.handleTouchStart = function (event) {
101 event.stopPropagation();
102 // If the user is swiping (not just tapping), save the position so we can
103 // abort ripples if the user appears to be scrolling.
104 if (_this.props.abortOnScroll && event.touches) {
105 _this.startListeningForScrollAbort(event);
106 _this.startTime = Date.now();
107 }
108 _this.start(event, true);
109 };
110
111 _this.handleTouchEnd = function () {
112 _this.end();
113 };
114
115 _this.handleTouchMove = function (event) {
116 // Stop trying to abort if we're already 300ms into the animation
117 var timeSinceStart = Math.abs(Date.now() - _this.startTime);
118 if (timeSinceStart > 300) {
119 _this.stopListeningForScrollAbort();
120 return;
121 }
122
123 // If the user is scrolling...
124 var deltaY = Math.abs(event.touches[0].clientY - _this.firstTouchY);
125 var deltaX = Math.abs(event.touches[0].clientX - _this.firstTouchX);
126 // Call it a scroll after an arbitrary 6px (feels reasonable in testing)
127 if (deltaY > 6 || deltaX > 6) {
128 var currentRipples = _this.state.ripples;
129 var ripple = currentRipples[0];
130 // This clone will replace the ripple in ReactTransitionGroup with a
131 // version that will disappear immediately when removed from the DOM
132 var abortedRipple = _react2.default.cloneElement(ripple, { aborted: true });
133 // Remove the old ripple and replace it with the new updated one
134 currentRipples = shift(currentRipples);
135 currentRipples = [].concat((0, _toConsumableArray3.default)(currentRipples), [abortedRipple]);
136 _this.setState({ ripples: currentRipples }, function () {
137 // Call end after we've set the ripple to abort otherwise the setState
138 // in end() merges with this and the ripple abort fails
139 _this.end();
140 });
141 }
142 };
143
144 _this.ignoreNextMouseDown = false;
145
146 _this.state = {
147 // This prop allows us to only render the ReactTransitionGroup
148 // on the first click of the component, making the inital render faster.
149 hasRipples: false,
150 nextKey: 0,
151 ripples: []
152 };
153 return _this;
154 }
155
156 (0, _createClass3.default)(TouchRipple, [{
157 key: 'start',
158 value: function start(event, isRippleTouchGenerated) {
159 var theme = this.context.muiTheme.ripple;
160
161 if (this.ignoreNextMouseDown && !isRippleTouchGenerated) {
162 this.ignoreNextMouseDown = false;
163 return;
164 }
165
166 var ripples = this.state.ripples;
167
168 // Add a ripple to the ripples array
169 ripples = [].concat((0, _toConsumableArray3.default)(ripples), [_react2.default.createElement(_CircleRipple2.default, {
170 key: this.state.nextKey,
171 style: !this.props.centerRipple ? this.getRippleStyle(event) : {},
172 color: this.props.color || theme.color,
173 opacity: this.props.opacity,
174 touchGenerated: isRippleTouchGenerated
175 })]);
176
177 this.ignoreNextMouseDown = isRippleTouchGenerated;
178 this.setState({
179 hasRipples: true,
180 nextKey: this.state.nextKey + 1,
181 ripples: ripples
182 });
183 }
184 }, {
185 key: 'end',
186 value: function end() {
187 var currentRipples = this.state.ripples;
188 this.setState({
189 ripples: shift(currentRipples)
190 });
191 if (this.props.abortOnScroll) {
192 this.stopListeningForScrollAbort();
193 }
194 }
195
196 // Check if the user seems to be scrolling and abort the animation if so
197
198 }, {
199 key: 'startListeningForScrollAbort',
200 value: function startListeningForScrollAbort(event) {
201 this.firstTouchY = event.touches[0].clientY;
202 this.firstTouchX = event.touches[0].clientX;
203 // Note that when scolling Chrome throttles this event to every 200ms
204 // Also note we don't listen for scroll events directly as there's no general
205 // way to cover cases like scrolling within containers on the page
206 document.body.addEventListener('touchmove', this.handleTouchMove);
207 }
208 }, {
209 key: 'stopListeningForScrollAbort',
210 value: function stopListeningForScrollAbort() {
211 document.body.removeEventListener('touchmove', this.handleTouchMove);
212 }
213 }, {
214 key: 'getRippleStyle',
215 value: function getRippleStyle(event) {
216 var el = _reactDom2.default.findDOMNode(this);
217 var elHeight = el.offsetHeight;
218 var elWidth = el.offsetWidth;
219 var offset = _dom2.default.offset(el);
220 var isTouchEvent = event.touches && event.touches.length;
221 var pageX = isTouchEvent ? event.touches[0].pageX : event.pageX;
222 var pageY = isTouchEvent ? event.touches[0].pageY : event.pageY;
223 var pointerX = pageX - offset.left;
224 var pointerY = pageY - offset.top;
225 var topLeftDiag = this.calcDiag(pointerX, pointerY);
226 var topRightDiag = this.calcDiag(elWidth - pointerX, pointerY);
227 var botRightDiag = this.calcDiag(elWidth - pointerX, elHeight - pointerY);
228 var botLeftDiag = this.calcDiag(pointerX, elHeight - pointerY);
229 var rippleRadius = Math.max(topLeftDiag, topRightDiag, botRightDiag, botLeftDiag);
230 var rippleSize = rippleRadius * 2;
231 var left = pointerX - rippleRadius;
232 var top = pointerY - rippleRadius;
233
234 return {
235 directionInvariant: true,
236 height: rippleSize,
237 width: rippleSize,
238 top: top,
239 left: left
240 };
241 }
242 }, {
243 key: 'calcDiag',
244 value: function calcDiag(a, b) {
245 return Math.sqrt(a * a + b * b);
246 }
247 }, {
248 key: 'render',
249 value: function render() {
250 var _props = this.props,
251 children = _props.children,
252 style = _props.style;
253 var _state = this.state,
254 hasRipples = _state.hasRipples,
255 ripples = _state.ripples;
256 var prepareStyles = this.context.muiTheme.prepareStyles;
257
258
259 var rippleGroup = void 0;
260
261 if (hasRipples) {
262 var mergedStyles = (0, _simpleAssign2.default)({
263 height: '100%',
264 width: '100%',
265 position: 'absolute',
266 top: 0,
267 left: 0,
268 overflow: 'hidden',
269 pointerEvents: 'none',
270 zIndex: 1 // This is also needed so that ripples do not bleed past a parent border radius.
271 }, style);
272
273 rippleGroup = _react2.default.createElement(
274 _TransitionGroup2.default,
275 { style: prepareStyles(mergedStyles) },
276 ripples
277 );
278 }
279
280 return _react2.default.createElement(
281 'div',
282 {
283 onMouseUp: this.handleMouseUp,
284 onMouseDown: this.handleMouseDown,
285 onMouseLeave: this.handleMouseLeave,
286 onTouchStart: this.handleTouchStart,
287 onTouchEnd: this.handleTouchEnd
288 },
289 rippleGroup,
290 children
291 );
292 }
293 }]);
294 return TouchRipple;
295}(_react.Component);
296
297TouchRipple.defaultProps = {
298 abortOnScroll: true
299};
300TouchRipple.contextTypes = {
301 muiTheme: _propTypes2.default.object.isRequired
302};
303TouchRipple.propTypes = process.env.NODE_ENV !== "production" ? {
304 abortOnScroll: _propTypes2.default.bool,
305 centerRipple: _propTypes2.default.bool,
306 children: _propTypes2.default.node,
307 color: _propTypes2.default.string,
308 opacity: _propTypes2.default.number,
309 style: _propTypes2.default.object
310} : {};
311exports.default = TouchRipple;
\No newline at end of file