UNPKG

17 kBJavaScriptView Raw
1import _extends from 'babel-runtime/helpers/extends';
2import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
3import _createClass from 'babel-runtime/helpers/createClass';
4import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
5import _inherits from 'babel-runtime/helpers/inherits';
6/* tslint:disable:no-console */
7import React, { Component } from 'react';
8import { calcRotation, getEventName, now, calcMutliFingerStatus, calcMoveStatus, shouldTriggerSwipe, shouldTriggerDirection, getMovingDirection, getDirectionEventName } from './util';
9import { PRESS, DIRECTION_ALL, DIRECTION_VERTICAL, DIRECTION_HORIZONTAL } from './config';
10;
11;
12var directionMap = {
13 all: DIRECTION_ALL,
14 vertical: DIRECTION_VERTICAL,
15 horizontal: DIRECTION_HORIZONTAL
16};
17
18var Gesture = function (_Component) {
19 _inherits(Gesture, _Component);
20
21 function Gesture(props) {
22 _classCallCheck(this, Gesture);
23
24 var _this = _possibleConstructorReturn(this, (Gesture.__proto__ || Object.getPrototypeOf(Gesture)).call(this, props));
25
26 _this.state = {};
27 _this.triggerEvent = function (name) {
28 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
29 args[_key - 1] = arguments[_key];
30 }
31
32 var cb = _this.props[name];
33 if (typeof cb === 'function') {
34 // always give user gesture object as first params first
35 cb.apply(undefined, [_this.getGestureState()].concat(args));
36 }
37 };
38 _this.triggerCombineEvent = function (mainEventName, eventStatus) {
39 for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
40 args[_key2 - 2] = arguments[_key2];
41 }
42
43 _this.triggerEvent.apply(_this, [mainEventName].concat(args));
44 _this.triggerSubEvent.apply(_this, [mainEventName, eventStatus].concat(args));
45 };
46 _this.triggerSubEvent = function (mainEventName, eventStatus) {
47 for (var _len3 = arguments.length, args = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
48 args[_key3 - 2] = arguments[_key3];
49 }
50
51 if (eventStatus) {
52 var subEventName = getEventName(mainEventName, eventStatus);
53 _this.triggerEvent.apply(_this, [subEventName].concat(args));
54 }
55 };
56 _this.triggerPinchEvent = function (mainEventName, eventStatus) {
57 for (var _len4 = arguments.length, args = Array(_len4 > 2 ? _len4 - 2 : 0), _key4 = 2; _key4 < _len4; _key4++) {
58 args[_key4 - 2] = arguments[_key4];
59 }
60
61 var scale = _this.gesture.scale;
62
63 if (eventStatus === 'move' && typeof scale === 'number') {
64 if (scale > 1) {
65 _this.triggerEvent('onPinchOut');
66 }
67 if (scale < 1) {
68 _this.triggerEvent('onPinchIn');
69 }
70 }
71 _this.triggerCombineEvent.apply(_this, [mainEventName, eventStatus].concat(args));
72 };
73 _this.initPressTimer = function () {
74 _this.cleanPressTimer();
75 _this.pressTimer = setTimeout(function () {
76 _this.setGestureState({
77 press: true
78 });
79 _this.triggerEvent('onPress');
80 }, PRESS.time);
81 };
82 _this.cleanPressTimer = function () {
83 /* tslint:disable:no-unused-expression */
84 _this.pressTimer && clearTimeout(_this.pressTimer);
85 };
86 _this.setGestureState = function (params) {
87 if (!_this.gesture) {
88 _this.gesture = {};
89 }
90 // cache the previous touches
91 if (_this.gesture.touches) {
92 _this.gesture.preTouches = _this.gesture.touches;
93 }
94 _this.gesture = _extends({}, _this.gesture, params);
95 };
96 _this.getGestureState = function () {
97 if (!_this.gesture) {
98 return _this.gesture;
99 } else {
100 // shallow copy
101 return _extends({}, _this.gesture);
102 }
103 };
104 _this.cleanGestureState = function () {
105 delete _this.gesture;
106 };
107 _this.getTouches = function (e) {
108 return Array.prototype.slice.call(e.touches).map(function (item) {
109 return {
110 x: item.screenX,
111 y: item.screenY
112 };
113 });
114 };
115 _this.triggerUserCb = function (status, e) {
116 var cbName = getEventName('onTouch', status);
117 if (cbName in _this.props) {
118 _this.props[cbName](e);
119 }
120 };
121 _this._handleTouchStart = function (e) {
122 _this.triggerUserCb('start', e);
123 _this.event = e;
124 if (e.touches.length > 1) {
125 e.preventDefault();
126 }
127 _this.initGestureStatus(e);
128 _this.initPressTimer();
129 _this.checkIfMultiTouchStart();
130 };
131 _this.initGestureStatus = function (e) {
132 _this.cleanGestureState();
133 // store the gesture start state
134 var startTouches = _this.getTouches(e);
135 var startTime = now();
136 var startMutliFingerStatus = calcMutliFingerStatus(startTouches);
137 _this.setGestureState({
138 startTime: startTime,
139 startTouches: startTouches,
140 startMutliFingerStatus: startMutliFingerStatus,
141 /* copy for next time touch move cala convenient*/
142 time: startTime,
143 touches: startTouches,
144 mutliFingerStatus: startMutliFingerStatus,
145 srcEvent: _this.event
146 });
147 };
148 _this.checkIfMultiTouchStart = function () {
149 var _this$props = _this.props,
150 enablePinch = _this$props.enablePinch,
151 enableRotate = _this$props.enableRotate;
152 var touches = _this.gesture.touches;
153
154 if (touches.length > 1 && (enablePinch || enableRotate)) {
155 if (enablePinch) {
156 var startMutliFingerStatus = calcMutliFingerStatus(touches);
157 _this.setGestureState({
158 startMutliFingerStatus: startMutliFingerStatus,
159 /* init pinch status */
160 pinch: true,
161 scale: 1
162 });
163 _this.triggerCombineEvent('onPinch', 'start');
164 }
165 if (enableRotate) {
166 _this.setGestureState({
167 /* init rotate status */
168 rotate: true,
169 rotation: 0
170 });
171 _this.triggerCombineEvent('onRotate', 'start');
172 }
173 }
174 };
175 _this._handleTouchMove = function (e) {
176 _this.triggerUserCb('move', e);
177 _this.event = e;
178 if (!_this.gesture) {
179 // sometimes weird happen: touchstart -> touchmove..touchmove.. --> touchend --> touchmove --> touchend
180 // so we need to skip the unnormal event cycle after touchend
181 return;
182 }
183 // not a long press
184 _this.cleanPressTimer();
185 _this.updateGestureStatus(e);
186 _this.checkIfSingleTouchMove();
187 _this.checkIfMultiTouchMove();
188 };
189 _this.checkIfMultiTouchMove = function () {
190 var _this$gesture = _this.gesture,
191 pinch = _this$gesture.pinch,
192 rotate = _this$gesture.rotate,
193 touches = _this$gesture.touches,
194 startMutliFingerStatus = _this$gesture.startMutliFingerStatus,
195 mutliFingerStatus = _this$gesture.mutliFingerStatus;
196
197 if (!pinch && !rotate) {
198 return;
199 }
200 if (touches.length < 2) {
201 _this.setGestureState({
202 pinch: false,
203 rotate: false
204 });
205 // Todo: 2 finger -> 1 finger, wait to test this situation
206 pinch && _this.triggerCombineEvent('onPinch', 'cancel');
207 rotate && _this.triggerCombineEvent('onRotate', 'cancel');
208 return;
209 }
210 if (pinch) {
211 var scale = mutliFingerStatus.z / startMutliFingerStatus.z;
212 _this.setGestureState({
213 scale: scale
214 });
215 _this.triggerPinchEvent('onPinch', 'move');
216 }
217 if (rotate) {
218 var rotation = calcRotation(startMutliFingerStatus, mutliFingerStatus);
219 _this.setGestureState({
220 rotation: rotation
221 });
222 _this.triggerCombineEvent('onRotate', 'move');
223 }
224 };
225 _this.allowGesture = function () {
226 return shouldTriggerDirection(_this.gesture.direction, _this.directionSetting);
227 };
228 _this.checkIfSingleTouchMove = function () {
229 var _this$gesture2 = _this.gesture,
230 pan = _this$gesture2.pan,
231 touches = _this$gesture2.touches,
232 moveStatus = _this$gesture2.moveStatus,
233 preTouches = _this$gesture2.preTouches,
234 _this$gesture2$availa = _this$gesture2.availablePan,
235 availablePan = _this$gesture2$availa === undefined ? true : _this$gesture2$availa;
236
237 if (touches.length > 1) {
238 _this.setGestureState({
239 pan: false
240 });
241 // Todo: 1 finger -> 2 finger, wait to test this situation
242 pan && _this.triggerCombineEvent('onPan', 'cancel');
243 return;
244 }
245 // add avilablePan condition to fix the case in scrolling, which will cause unavailable pan move.
246 if (moveStatus && availablePan) {
247 var direction = getMovingDirection(preTouches[0], touches[0]);
248 _this.setGestureState({ direction: direction });
249 var eventName = getDirectionEventName(direction);
250 if (!_this.allowGesture()) {
251 // if the first move is unavailable, then judge all of remaining touch movings are also invalid.
252 if (!pan) {
253 _this.setGestureState({ availablePan: false });
254 }
255 return;
256 }
257 if (!pan) {
258 _this.triggerCombineEvent('onPan', 'start');
259 _this.setGestureState({
260 pan: true,
261 availablePan: true
262 });
263 } else {
264 _this.triggerCombineEvent('onPan', eventName);
265 _this.triggerSubEvent('onPan', 'move');
266 }
267 }
268 };
269 _this.checkIfMultiTouchEnd = function (status) {
270 var _this$gesture3 = _this.gesture,
271 pinch = _this$gesture3.pinch,
272 rotate = _this$gesture3.rotate;
273
274 if (pinch) {
275 _this.triggerCombineEvent('onPinch', status);
276 }
277 if (rotate) {
278 _this.triggerCombineEvent('onRotate', status);
279 }
280 };
281 _this.updateGestureStatus = function (e) {
282 var time = now();
283 _this.setGestureState({
284 time: time
285 });
286 if (!e.touches || !e.touches.length) {
287 return;
288 }
289 var _this$gesture4 = _this.gesture,
290 startTime = _this$gesture4.startTime,
291 startTouches = _this$gesture4.startTouches,
292 pinch = _this$gesture4.pinch,
293 rotate = _this$gesture4.rotate;
294
295 var touches = _this.getTouches(e);
296 var moveStatus = calcMoveStatus(startTouches, touches, time - startTime);
297 var mutliFingerStatus = void 0;
298 if (pinch || rotate) {
299 mutliFingerStatus = calcMutliFingerStatus(touches);
300 }
301 _this.setGestureState({
302 /* update status snapshot */
303 touches: touches,
304 mutliFingerStatus: mutliFingerStatus,
305 /* update duration status */
306 moveStatus: moveStatus
307 });
308 };
309 _this._handleTouchEnd = function (e) {
310 _this.triggerUserCb('end', e);
311 _this.event = e;
312 if (!_this.gesture) {
313 return;
314 }
315 _this.cleanPressTimer();
316 _this.updateGestureStatus(e);
317 _this.doSingleTouchEnd('end');
318 _this.checkIfMultiTouchEnd('end');
319 };
320 _this._handleTouchCancel = function (e) {
321 _this.triggerUserCb('cancel', e);
322 _this.event = e;
323 // Todo: wait to test cancel case
324 if (!_this.gesture) {
325 return;
326 }
327 _this.cleanPressTimer();
328 _this.updateGestureStatus(e);
329 _this.doSingleTouchEnd('cancel');
330 _this.checkIfMultiTouchEnd('cancel');
331 };
332 _this.triggerAllowEvent = function (type, status) {
333 if (_this.allowGesture()) {
334 _this.triggerCombineEvent(type, status);
335 } else {
336 _this.triggerSubEvent(type, status);
337 }
338 };
339 _this.doSingleTouchEnd = function (status) {
340 var _this$gesture5 = _this.gesture,
341 moveStatus = _this$gesture5.moveStatus,
342 pinch = _this$gesture5.pinch,
343 rotate = _this$gesture5.rotate,
344 press = _this$gesture5.press,
345 pan = _this$gesture5.pan,
346 direction = _this$gesture5.direction;
347
348 if (pinch || rotate) {
349 return;
350 }
351 if (moveStatus) {
352 var z = moveStatus.z,
353 velocity = moveStatus.velocity;
354
355 var swipe = shouldTriggerSwipe(z, velocity);
356 _this.setGestureState({
357 swipe: swipe
358 });
359 if (pan) {
360 // pan need end, it's a process
361 // sometimes, start with pan left, but end with pan right....
362 _this.triggerAllowEvent('onPan', status);
363 }
364 if (swipe) {
365 var directionEvName = getDirectionEventName(direction);
366 // swipe just need a direction, it's a endpoint
367 _this.triggerAllowEvent('onSwipe', directionEvName);
368 return;
369 }
370 }
371 if (press) {
372 _this.triggerEvent('onPressUp');
373 return;
374 }
375 _this.triggerEvent('onTap');
376 };
377 _this.getTouchAction = function () {
378 var _this$props2 = _this.props,
379 enablePinch = _this$props2.enablePinch,
380 enableRotate = _this$props2.enableRotate;
381 var directionSetting = _this.directionSetting;
382
383 if (enablePinch || enableRotate || directionSetting === DIRECTION_ALL) {
384 return 'pan-x pan-y';
385 }
386 if (directionSetting === DIRECTION_VERTICAL) {
387 return 'pan-x';
388 }
389 if (directionSetting === DIRECTION_HORIZONTAL) {
390 return 'pan-y';
391 }
392 return 'auto';
393 };
394 _this.directionSetting = directionMap[props.direction];
395 return _this;
396 }
397
398 _createClass(Gesture, [{
399 key: 'componentWillUnmount',
400 value: function componentWillUnmount() {
401 this.cleanPressTimer();
402 }
403 }, {
404 key: 'render',
405 value: function render() {
406 var children = this.props.children;
407
408 var child = React.Children.only(children);
409 var touchAction = this.getTouchAction();
410 var events = {
411 onTouchStart: this._handleTouchStart,
412 onTouchMove: this._handleTouchMove,
413 onTouchCancel: this._handleTouchCancel,
414 onTouchEnd: this._handleTouchEnd
415 };
416 return React.cloneElement(child, _extends({}, events, { style: _extends({ touchAction: touchAction }, child.props.style || {}) }));
417 }
418 }]);
419
420 return Gesture;
421}(Component);
422
423export default Gesture;
424
425Gesture.defaultProps = {
426 enableRotate: false,
427 enablePinch: false,
428 direction: 'all'
429};
\No newline at end of file