├── .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 | [](https://travis-ci.org/kiddkai/react-native-gestures)
9 | [](https://npmjs.org/package/react-native-gestures "View this project on npm")
10 | [](https://npmjs.org/package/react-native-gestures "View this project on npm")
11 | [](https://github.com/kiddkai/react-native-gestures/pulls?q=is%3Apr+is%3Aclosed)
12 | [](https://github.com/kiddkai/react-native-gestures/issues?q=is%3Aissue+is%3Aclosed)
13 | [](http://standardjs.com/)
14 |
15 |
16 | Showcase
17 | --------
18 |
19 | 
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 | [](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 |
--------------------------------------------------------------------------------