├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── docs └── gesture-demo.gif ├── examples └── showcase.js ├── package.json ├── src ├── GestureView.js ├── create.js ├── drag.js ├── index.js ├── mixins │ ├── draggable.js │ └── events.js ├── pinch.js └── responder │ ├── general.js │ ├── oneFinger.js │ └── twoFinger.js └── test └── src ├── drag.spec.js ├── draggable.spec.js ├── pinch.spec.js └── responder └── genernal.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | .DS_Store 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # react-native-gesture 2 | 3 | ## 0.3.0 4 | 5 | - prop type fixes by @sscaff1 (#13) 6 | - add support for react native 0.25 by @sibelius (#17) 7 | - fixes issue #21 by @reallistic (#23) 8 | - remove `dist` directory, use es6 directly (#22) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React Native Gestures 2 | ===================== 3 | 4 | :warning: This package is still in early stage, it will have a heaps of API changes before it move to 1.0 :warning: 5 | 6 | > React Native Composable Gesture Library 7 | 8 | [![Build Status](https://travis-ci.org/kiddkai/react-native-gestures.svg)](https://travis-ci.org/kiddkai/react-native-gestures) 9 | [![npm version](http://img.shields.io/npm/v/react-native-gestures.svg?style=flat-square)](https://npmjs.org/package/react-native-gestures "View this project on npm") 10 | [![npm version](http://img.shields.io/npm/dm/react-native-gestures.svg?style=flat-square)](https://npmjs.org/package/react-native-gestures "View this project on npm") 11 | [![Issue Stats](http://issuestats.com/github/kiddkai/react-native-gestures/badge/pr?style=flat-square)](https://github.com/kiddkai/react-native-gestures/pulls?q=is%3Apr+is%3Aclosed) 12 | [![Issue Stats](http://issuestats.com/github/kiddkai/react-native-gestures/badge/issue?style=flat-square)](https://github.com/kiddkai/react-native-gestures/issues?q=is%3Aissue+is%3Aclosed) 13 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) 14 | 15 | 16 | Showcase 17 | -------- 18 | 19 | ![](http://imgur.com/6dCrcfL.gif) 20 | 21 | Getting Start 22 | -------------- 23 | 24 | Assuming you are using `react-native`, because I don't know how it will work 25 | in other libraries... 26 | 27 | * Install via npm 28 | 29 | ```bash 30 | npm i -S react-native-gestures 31 | ``` 32 | 33 | Then write some js like the simple code samples as a React component 34 | and render it in your `react-native` app. 35 | 36 | ```js 37 | import React, { 38 | View, 39 | Text 40 | } from 'react-native'; 41 | 42 | import { 43 | drag, 44 | pinch, 45 | GestureView 46 | } from 'react-native-gestures'; 47 | 48 | export default React.createClass({ 49 | render() { 50 | onGestureError(err) { 51 | console.error(err); 52 | }, 53 | return ( 54 | 55 | { 59 | return { 60 | top: layout.y, 61 | left: layout.x, 62 | width: layout.width, 63 | height: layout.height, 64 | transform: [{rotate: `${layout.rotate}deg`}] 65 | } 66 | }} 67 | onError={console.error.bind(console)}> 68 | HEHE 69 | HEHE 70 | 71 | 72 | ); 73 | } 74 | }); 75 | ``` 76 | 77 | APIs 78 | ---------- 79 | 80 | ### 81 | 82 | As you can see, it's just a very simple React component you can use in this package, maybe 83 | it will have more components in the future, or not. 84 | 85 | There are few properties it accpets: 86 | 87 | * gestures - a `Array` of [gesture](#gestures)s 88 | * onError - a `Function` will be called when anything bad happens 89 | * style - a `style` same as ``'s `style` property 90 | * toStyle - a mapping function that allow you to pick the changes you want to css style 91 | * children - ... you know, just React children, nothing special 92 | 93 | Example: 94 | 95 | ```js 96 | let style = { position: 'absolute', backgroundColor: '#F00' }; 97 | 98 | 102 | This is the children I say 103 | 104 | ``` 105 | 106 | ### Gestures 107 | 108 | Every gesture in this module is just a simple combination of two things: 109 | 110 | 1. A `transducer` called `calculate`(please suggest me a better name) 111 | 112 | This is the actual function that calculates the new positions of the view 113 | when the move gesture event comes in. 114 | 115 | 2. A `number` called `GESTURE_NUMBER` 116 | 117 | This define that the gesture will start calculate when the gesture number 118 | matches this number. 119 | 120 | You can set any number you want if your touch screen supports it :p 121 | 122 | 123 | #### drag 124 | 125 | It's just a simple transducer takes one finger input with the move of the 126 | finger and generates new layout of the component. 127 | 128 | #### pinch 129 | 130 | It's a pinch gesture, also a zoom gesture. It takes two fingers gestures and 131 | generates new layout of the component. 132 | 133 | Contribute 134 | ---------- 135 | 136 | Using 137 | 138 | [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 139 | 140 | -------------------------------------------------------------------------------- /docs/gesture-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiddkai/react-native-gestures/4145bf59a3fb31769d8cc5329c36a21059fb0af6/docs/gesture-demo.gif -------------------------------------------------------------------------------- /examples/showcase.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text, StyleSheet } from 'react-native'; 3 | import { drag, pinch, GestureView } from 'react-native-gestures'; 4 | 5 | const styles = StyleSheet.create({ 6 | container: { 7 | backgroundColor: 'red', 8 | flex: 1 9 | } 10 | }) 11 | 12 | const movable = { 13 | backgroundColor: 'green', 14 | width: 100, 15 | height: 100, 16 | position: 'absolute' 17 | } 18 | 19 | const ShowCase = React.createClass({ 20 | getInitialState: function () { 21 | return { 22 | movablePosition: {} 23 | } 24 | }, 25 | render: function () { 26 | return ( 27 | 28 | { 32 | return { 33 | top: layout.y, 34 | left: layout.x, 35 | width: layout.width, 36 | height: layout.height, 37 | transform: [{rotate: `${layout.rotate}deg`}] 38 | } 39 | }} 40 | onError={console.error.bind(console)}> 41 | HEHE 42 | HEHE 43 | 44 | 45 | ) 46 | } 47 | }) 48 | 49 | module.exports = ShowCase 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-gestures", 3 | "version": "0.3.0", 4 | "description": "composable gesture system in react native", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "standard src/*.js && mocha --compilers js:babel/register --recursive --require should" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/kiddkai/react-native-gestures.git" 12 | }, 13 | "keywords": [ 14 | "react-native", 15 | "react-component", 16 | "ios", 17 | "gestures", 18 | "pinch", 19 | "zoom", 20 | "drag", 21 | "drop" 22 | ], 23 | "author": "kiddkai", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/kiddkai/react-native-gestures/issues" 27 | }, 28 | "homepage": "https://github.com/kiddkai/react-native-gestures#readme", 29 | "devDependencies": { 30 | "babel": "^5.8.29", 31 | "mocha": "^2.3.3", 32 | "proxyquire": "^1.7.3", 33 | "should": "^7.1.1", 34 | "sinon": "^1.17.2", 35 | "standard": "^5.3.1" 36 | }, 37 | "dependencies": { 38 | "curry": "^1.2.0", 39 | "immutable": "^3.7.5", 40 | "ramda": "^0.19.1", 41 | "rx": "^4.0.6", 42 | "transducers.js": "^0.3.2" 43 | }, 44 | "standard": { 45 | "globals": [ 46 | "describe", 47 | "it", 48 | "before", 49 | "after", 50 | "beforeEach", 51 | "afterEach" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/GestureView.js: -------------------------------------------------------------------------------- 1 | import events from './mixins/events' 2 | import draggableMixin from './mixins/draggable' 3 | import React, { 4 | PropTypes 5 | } from 'react' 6 | import { 7 | View, 8 | Image 9 | } from 'react-native' 10 | 11 | export default React.createClass({ 12 | mixins: [events(['onLayout']), draggableMixin()], 13 | 14 | propTypes: { 15 | gestures: PropTypes.array.isRequired, 16 | onError: PropTypes.func.isRequired, 17 | toStyle: PropTypes.func.isRequired, 18 | style: PropTypes.any, 19 | children: PropTypes.array, 20 | type: PropTypes.oneOf([ 21 | 'View', 22 | 'Image' 23 | ]), 24 | source: PropTypes.any 25 | }, 26 | 27 | componentDidMount () { 28 | this.layoutStream.subscribe( 29 | (layout) => this.container.setNativeProps({ 30 | style: this.props.toStyle(layout) 31 | }), 32 | (err) => this.props.onError(err) 33 | ) 34 | }, 35 | 36 | render () { 37 | let props = { 38 | ref: (container) => this.container = container, 39 | style: this.props.style, 40 | onLayout: ({nativeEvent}) => { 41 | this.onLayout.onNext(nativeEvent) 42 | }, 43 | type: this.props.type || 'View', 44 | source: this.props.source, 45 | ...this.gestureResponder.panHandlers 46 | } 47 | return ( 48 | 49 | {this.props.type === 'View' ? ( 50 | 51 | {this.props.children} 52 | 53 | ) : ( 54 | 55 | )} 56 | 57 | ) 58 | } 59 | }) 60 | -------------------------------------------------------------------------------- /src/create.js: -------------------------------------------------------------------------------- 1 | import curry from 'curry' 2 | 3 | function createGesture (responder, transducer, getInitialLayout, draggable) { 4 | return draggable 5 | .onDragStart 6 | .flatMap(function () { 7 | return responder( 8 | draggable.onDragMove, 9 | getInitialLayout 10 | ) 11 | .transduce(transducer) 12 | .takeUntil(draggable.onDragRelease) 13 | }) 14 | }; 15 | 16 | export default curry(createGesture) 17 | -------------------------------------------------------------------------------- /src/drag.js: -------------------------------------------------------------------------------- 1 | import { map } from 'transducers.js' 2 | import oneFingerResponder from './responder/oneFinger' 3 | 4 | export let responder = oneFingerResponder 5 | 6 | export let transducer = map(function (gesture) { 7 | let layout = gesture.get('initialLayout').set('rotate', 0) 8 | let initialTouch = gesture.get('initialTouches').get(0) 9 | let currentTouch = gesture.get('touches').get(0) 10 | 11 | return layout.withMutations(function (l) { 12 | return l 13 | .set('x', l.get('x') + 14 | (currentTouch.get('pageX') - initialTouch.get('pageX'))) 15 | .set('y', l.get('y') + 16 | (currentTouch.get('pageY') - initialTouch.get('pageY'))) 17 | }).toJS() 18 | }) 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | drag: require('./drag'), 3 | pinch: require('./pinch'), 4 | create: require('./create'), 5 | draggable: require('./mixins/draggable'), 6 | events: require('./mixins/events'), 7 | GestureView: require('./GestureView'), 8 | responders: { 9 | general: require('./responder/general'), 10 | oneFinger: require('./responder/oneFinger'), 11 | twoFinger: require('./responder/twoFinger') 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/draggable.js: -------------------------------------------------------------------------------- 1 | import Rx from 'rx' 2 | import create from '../create' 3 | import { PanResponder } from 'react-native' 4 | 5 | function yes () { return true } 6 | 7 | export default function draggableMixin (gestureDefs) { 8 | gestureDefs = gestureDefs || [] 9 | 10 | var target 11 | var layout 12 | 13 | let getInitialLayout = () => layout 14 | let isCurrentTarget = (ev) => ev.target === target 15 | 16 | return { 17 | componentWillMount () { 18 | let onDragStart = new Rx.Subject() 19 | let onDragMove = new Rx.Subject() 20 | let onDragRelease = new Rx.Subject() 21 | 22 | this 23 | .onLayout 24 | .take(1) 25 | .subscribe(ev => target = ev.target) 26 | 27 | this 28 | .onLayout 29 | .subscribe(ev => layout = ev.layout) 30 | 31 | let draggable = { 32 | onDragStart: onDragStart.filter(isCurrentTarget), 33 | onDragMove: onDragMove.filter(isCurrentTarget), 34 | onDragRelease: onDragRelease.filter(isCurrentTarget) 35 | } 36 | 37 | this.gestureResponder = PanResponder.create({ 38 | onStartShouldSetPanResponder: yes, 39 | onStartShouldSetPanResponderCapture: yes, 40 | onMoveShouldSetPanResponder: yes, 41 | onMoveShouldSetPanResponderCapture: yes, 42 | onPanResponderGrant: (evt) => onDragStart.onNext(evt.nativeEvent), 43 | onPanResponderMove: (evt, gestureState) => onDragMove.onNext(evt.nativeEvent), 44 | onPanResponderTerminationRequest: yes, 45 | onPanResponderRelease: (evt) => onDragRelease.onNext(evt.nativeEvent), 46 | onPanResponderTerminate: yes, 47 | onShouldBlockNativeResponder: yes 48 | }) 49 | 50 | if (this.props && this.props.gestures) { 51 | gestureDefs = gestureDefs.concat(this.props.gestures) 52 | } 53 | 54 | this.layoutStream = Rx 55 | .Observable 56 | .merge(gestureDefs.map(def => 57 | create(def.responder, def.transducer, getInitialLayout, draggable))) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/mixins/events.js: -------------------------------------------------------------------------------- 1 | import Rx from 'rx' 2 | 3 | export default function events (evs = []) { 4 | return { 5 | componentWillMount () { 6 | var streams = evs.reduce(function (res, eventName) { 7 | res[eventName] = new Rx.Subject() 8 | return res 9 | }, {}) 10 | 11 | Object.assign(this, streams, {__streams: streams}) 12 | }, 13 | componentWillUnmount () { 14 | evs.forEach((ev) => this.__streams[ev].onCompleted()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pinch.js: -------------------------------------------------------------------------------- 1 | import twoFingerResponder from './responder/twoFinger' 2 | import { map } from 'transducers.js' 3 | 4 | export let responder = twoFingerResponder 5 | 6 | export let transducer = map(function (gesture) { 7 | let layout = gesture.get('initialLayout') 8 | let startX = layout.get('x') 9 | let startY = layout.get('y') 10 | let startWidth = layout.get('width') 11 | let startHeight = layout.get('height') 12 | let newHeight = startHeight + gesture.get('increasedDistance') 13 | let scale = newHeight / startHeight 14 | let newWidth = startWidth * scale 15 | let xWidthDiff = (newWidth - startWidth) / 2 16 | let yHeightDiff = (newHeight - startHeight) / 2 17 | 18 | return { 19 | x: startX - gesture.getIn(['centerDiff', 'x']) - xWidthDiff, 20 | y: startY - gesture.getIn(['centerDiff', 'y']) - yHeightDiff, 21 | width: newWidth, 22 | height: newHeight, 23 | rotate: gesture.get('angleChanged') 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/responder/general.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Here is the beggining of everything. All the other responders 3 | * including one finger touch responder and two finger touch responder 4 | * will use this responder as the basic responder, what the other 5 | * responder will do is adding properties to the result which comes 6 | * from this responder. 7 | */ 8 | 9 | import Rx from 'rx' 10 | import Immutable from 'immutable' 11 | import curry from 'curry' 12 | 13 | function toImmutableTouch (touch) { 14 | return Immutable.Map({ 15 | timestamp: touch.timestamp, 16 | target: touch.target, 17 | pageY: touch.pageY, 18 | locationX: touch.locationX, 19 | locationY: touch.locationY, 20 | identifier: touch.identifier, 21 | pageX: touch.pageX 22 | }) 23 | } 24 | 25 | function reset (s) { 26 | return s 27 | .delete('initialLayout') 28 | .delete('initialTouches') 29 | } 30 | 31 | function genernalResponder (n, onMove, getInitialLayout) { 32 | return Rx.Observable.create(function (o) { 33 | var state = Immutable.Map() 34 | var paused = false 35 | 36 | onMove 37 | .subscribe( 38 | function (event) { 39 | if (!event.touches) { 40 | return 41 | } 42 | let touches = event.touches 43 | 44 | if (touches.length === n) { 45 | touches = touches.map(toImmutableTouch) 46 | if (!state.get('initialLayout')) { 47 | state = state.withMutations(s => s 48 | .set('initialLayout', Immutable.fromJS(getInitialLayout())) 49 | .set('initialTouches', Immutable.fromJS(touches))) 50 | } 51 | 52 | state = state.withMutations(s => { 53 | return s 54 | .set('target', event.target) 55 | .set('pageX', event.pageX) 56 | .set('pageY', event.pageY) 57 | .set('locationX', event.locationX) 58 | .set('locationY', event.locationY) 59 | .set('identifier', event.identifier) 60 | .set('touches', Immutable.fromJS(touches)) 61 | .set('timestamp', event.timestamp) 62 | }) 63 | 64 | o.onNext(state) 65 | } else { 66 | if (paused) { 67 | return 68 | } 69 | 70 | state = state.withMutations(reset) 71 | paused = true 72 | } 73 | }, 74 | o.onError.bind(o), 75 | o.onCompleted.bind(o) 76 | ) 77 | 78 | return function () { 79 | state = Immutable.Map() 80 | } 81 | }) 82 | } 83 | 84 | export default curry(genernalResponder) 85 | -------------------------------------------------------------------------------- /src/responder/oneFinger.js: -------------------------------------------------------------------------------- 1 | import genernalResponder from './general' 2 | 3 | export default function oneFingerResponder (onMove, getInitialLayout) { 4 | return genernalResponder(1, onMove, getInitialLayout) 5 | } 6 | -------------------------------------------------------------------------------- /src/responder/twoFinger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Two finger responder 3 | * 4 | * This responder will be pretty simple, 5 | * it extends the general event with few things: 6 | * 7 | * 1. initialCenter - the initial center point of the first touch 8 | * 2. initialDistance - the initial distances of the first touch 9 | * 3. currentCenter - the current center point of two touches 10 | * 4. currentDistance - the current distance of two touches 11 | * 5. increasedDistance - the increased distance from start 12 | * 6. centerDiff - the different of the center point 13 | */ 14 | 15 | import { Map } from 'immutable' 16 | import genernalResponder from './general' 17 | 18 | function center (touches) { 19 | let a = touches.get(0) 20 | let b = touches.get(1) 21 | 22 | return Map({ 23 | x: (a.get('pageX') + b.get('pageX')) / 2, 24 | y: (a.get('pageY') + b.get('pageY')) / 2 25 | }) 26 | } 27 | 28 | function pow2abs (a, b) { 29 | return Math.pow(Math.abs(a - b), 2) 30 | } 31 | 32 | function distance (touches) { 33 | let a = touches.get(0) 34 | let b = touches.get(1) 35 | 36 | return Math.sqrt( 37 | pow2abs(a.get('pageX'), b.get('pageX')) + 38 | pow2abs(a.get('pageY'), b.get('pageY')), 39 | 2) 40 | } 41 | 42 | function toDeg (rad) { 43 | return rad * 180 / Math.PI 44 | } 45 | 46 | function angle (touches) { 47 | let a = touches.get(0) 48 | let b = touches.get(1) 49 | 50 | var deg = toDeg(Math.atan2( 51 | b.get('pageY') - a.get('pageY'), 52 | b.get('pageX') - a.get('pageX'))) 53 | 54 | if (deg < 0) { 55 | deg += 360 56 | } 57 | 58 | return deg 59 | } 60 | 61 | function mutate (gesture) { 62 | let initCenter = center(gesture.get('initialTouches')) 63 | let currentCenter = center(gesture.get('touches')) 64 | let initDistance = distance(gesture.get('initialTouches')) 65 | let currentDistance = distance(gesture.get('touches')) 66 | let initAngle = angle(gesture.get('initialTouches')) 67 | let currentAngle = angle(gesture.get('touches')) 68 | 69 | return gesture 70 | .set('initialCenter', initCenter) 71 | .set('initialDistance', initDistance) 72 | .set('initialAngle', initAngle) 73 | .set('currentCenter', currentCenter) 74 | .set('currentDistance', currentDistance) 75 | .set('currentAngle', currentAngle) 76 | .set('angleChanged', currentAngle - initAngle) 77 | .set('increasedDistance', currentDistance - initDistance) 78 | .set('centerDiff', Map({ 79 | x: initCenter.get('x') - currentCenter.get('x'), 80 | y: initCenter.get('y') - currentCenter.get('y') 81 | })) 82 | } 83 | 84 | function extend (gesture) { 85 | return gesture.withMutations(mutate) 86 | } 87 | 88 | export default function twoFingerResponder (onMove, getInitialLayout) { 89 | return genernalResponder(2, onMove, getInitialLayout).map(extend) 90 | } 91 | -------------------------------------------------------------------------------- /test/src/drag.spec.js: -------------------------------------------------------------------------------- 1 | const { fromJS } = require('immutable') 2 | const { into } = require('transducers.js') 3 | const gestureDrag = require('../../src/drag') 4 | 5 | function touch (pageX, pageY) { 6 | return fromJS({ 7 | pageX, 8 | pageY, 9 | locationX: pageX, 10 | locationY: pageY, 11 | target: 10, 12 | timestamp: 1 13 | }) 14 | } 15 | 16 | function gesture (pageX, pageY, t) { 17 | return fromJS({ 18 | initialLayout: { 19 | x: 100, 20 | y: 100, 21 | height: 100, 22 | width: 100 23 | }, 24 | touches: [t], 25 | initialTouches: [touch(pageX, pageY)] 26 | }) 27 | } 28 | 29 | describe('Gesture Drag', function () { 30 | it('calculates the differences between initialTouch and currentTouch', function () { 31 | let result = into( 32 | [], 33 | gestureDrag.transducer, 34 | [gesture(100, 100, touch(101, 99))]) 35 | 36 | result[0].x.should.equal(101) 37 | result[0].y.should.equal(99) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/src/draggable.spec.js: -------------------------------------------------------------------------------- 1 | const Rx = require('rx') 2 | const sinon = require('sinon') 3 | const proxyquire = require('proxyquire') 4 | 5 | let PanResponder = {} 6 | 7 | const Draggable = proxyquire('../../src/mixins/draggable', { 8 | 'react-native': { 9 | PanResponder, 10 | '@noCallThru': true 11 | } 12 | }) 13 | 14 | describe('Draggable', function () { 15 | beforeEach(function () { 16 | PanResponder.create = sinon.stub() 17 | }) 18 | 19 | it('should returns a object only have componentWillMount property', function () { 20 | let mixin = Draggable([() => new Rx.Observer.Subject()]) 21 | mixin.should.have.property('componentWillMount') 22 | }) 23 | 24 | it('it will exposes a PanResponder when will mount called', function () { 25 | let mixin = Draggable([() => new Rx.Subject()]) 26 | let ctx = { 27 | onLayout: new Rx.Subject() 28 | } 29 | let responderStub = {} 30 | PanResponder.create.returns(responderStub) 31 | mixin.componentWillMount.call(ctx) 32 | ctx.gestureResponder.should.equal(responderStub) 33 | ctx.layoutStream.should.have.property('subscribe') 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /test/src/pinch.spec.js: -------------------------------------------------------------------------------- 1 | describe('Gesture Pinch', function () { 2 | }) 3 | -------------------------------------------------------------------------------- /test/src/responder/genernal.spec.js: -------------------------------------------------------------------------------- 1 | const Rx = require('rx') 2 | const sinon = require('sinon') 3 | const genernalResponder = require('../../../src/responder/general') 4 | 5 | let touch = (x, y) => { 6 | return { 7 | locationX: x, 8 | locationY: y, 9 | pageX: x, 10 | pageY: y 11 | } 12 | } 13 | 14 | let event = (x, y, touches) => { 15 | return { 16 | target: 10, 17 | pageX: x, 18 | pageY: y, 19 | locationX: x, 20 | locationY: y, 21 | touches 22 | } 23 | } 24 | 25 | describe('genernalResponder', function () { 26 | let getInitialLayout 27 | 28 | beforeEach(function () { 29 | getInitialLayout = () => { 30 | return { 31 | target: 10, 32 | x: 100, 33 | y: 100, 34 | width: 100, 35 | height: 100 36 | } 37 | } 38 | }) 39 | 40 | it('filter out the touches with incorrect touch numbers', function (done) { 41 | let source = [ 42 | event(101, 101, [touch(101, 101)]), 43 | event(101, 101, [touch(101, 101), touch(101, 101)]), 44 | event(101, 101, [touch(101, 101), touch(101, 101)]) 45 | ] 46 | 47 | genernalResponder(1, Rx.Observable.from(source), getInitialLayout) 48 | .toArray().subscribe(function (results) { 49 | results.should.have.lengthOf(1) 50 | genernalResponder(2, Rx.Observable.from(source), getInitialLayout) 51 | .toArray().subscribe(function (results) { 52 | results.should.have.lengthOf(2) 53 | done() 54 | }, done) 55 | }, done) 56 | }) 57 | 58 | it('formats the event with initial datas', function (done) { 59 | let source = [ 60 | event(101, 101, [touch(101, 101)]) 61 | ] 62 | 63 | genernalResponder(1, Rx.Observable.from(source), getInitialLayout) 64 | .subscribe(function (gesture) { 65 | gesture.get('pageX').should.equal(101) 66 | gesture.get('pageY').should.equal(101) 67 | gesture.get('initialLayout').get('x').should.equal(100) 68 | gesture.get('initialLayout').get('y').should.equal(100) 69 | gesture.get('initialLayout').get('width').should.equal(100) 70 | done() 71 | }, done) 72 | }) 73 | 74 | it('will try to get the new layout and touch info when gesture number changed', function (done) { 75 | getInitialLayout = sinon.stub().returns({ 76 | target: 10, 77 | x: 100, 78 | y: 100, 79 | width: 100, 80 | height: 100 81 | }) 82 | 83 | let source = [ 84 | event(101, 101, [touch(101, 101)]), 85 | event(101, 101, [touch(101, 101), touch(101, 101)]), 86 | event(101, 101, [touch(101, 101), touch(101, 101)]), 87 | event(101, 101, [touch(102, 102)]), 88 | event(101, 101, [touch(103, 103)]) 89 | ] 90 | 91 | genernalResponder(1, Rx.Observable.from(source), getInitialLayout) 92 | .takeLast(1) 93 | .subscribe(function (gesture) { 94 | gesture.getIn(['initialTouches', 0, 'pageX']).should.equal(102) 95 | gesture.getIn(['initialTouches', 0, 'pageY']).should.equal(102) 96 | sinon.assert.calledTwice(getInitialLayout) 97 | done() 98 | }, done) 99 | }) 100 | }) 101 | --------------------------------------------------------------------------------