├── .editorconfig
├── .gitignore
├── .travis.yml
├── HISTORY.md
├── LICENSE.md
├── README.md
├── examples
├── demo.html
├── demo.tsx
├── disable-inner.html
├── disable-inner.tsx
├── disable-outer.html
└── disable-outer.tsx
├── index.js
├── package.json
├── src
├── PressEvent.tsx
└── index.tsx
├── tests
└── index.js
├── tsconfig.json
└── typings
├── custom.d.ts
└── index.d.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | # Unix-style newlines with a newline ending every file
5 | [*.{js,css,md}]
6 | end_of_line = lf
7 | insert_final_newline = true
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.log
3 | *.log.*
4 | .idea/
5 | .ipr
6 | .iws
7 | *~
8 | ~*
9 | *.diff
10 | *.patch
11 | *.bak
12 | .DS_Store
13 | Thumbs.db
14 | .project
15 | .*proj
16 | .svn/
17 | *.swp
18 | *.swo
19 | *.pyc
20 | *.pyo
21 | .build
22 | node_modules
23 | .cache
24 | dist
25 | *.css
26 | build
27 | lib
28 | coverage
29 | yarn.lock
30 | .vscode
31 | src/*.js
32 | /es/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | sudo: false
4 |
5 | notifications:
6 | email:
7 | - yiminghe@gmail.com
8 |
9 | node_js:
10 | - 6.9.1
11 |
12 | before_install:
13 | - |
14 | if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/'
15 | then
16 | echo "Only docs were updated, stopping build process."
17 | exit
18 | fi
19 | phantomjs --version
20 | script:
21 | - |
22 | if [ "$TEST_TYPE" = test ]; then
23 | npm test
24 | else
25 | npm run $TEST_TYPE
26 | fi
27 | env:
28 | matrix:
29 | - TEST_TYPE=lint
30 | - TEST_TYPE=test
31 | - TEST_TYPE=coverage
32 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # History
2 | ----
3 |
4 | ## 1.3.0 / 2018-07-26
5 |
6 | - support activeStopPropagation
7 |
8 | ## 1.2.0 / 2017-05-15
9 |
10 | - support stopPropagation for onPress and onLongPress
11 |
12 | ## 1.0.0 / 2016-11-29
13 |
14 | - inspired by react-native
15 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-present yiminghe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rc-touchable
2 | ---
3 |
4 | react touchable component. inspired by react-native.
5 |
6 | [![NPM version][npm-image]][npm-url]
7 | [![build status][travis-image]][travis-url]
8 | [![Test coverage][coveralls-image]][coveralls-url]
9 | [![npm download][download-image]][download-url]
10 |
11 | [npm-image]: http://img.shields.io/npm/v/rc-touchable.svg?style=flat-square
12 | [npm-url]: http://npmjs.org/package/rc-touchable
13 | [travis-image]: https://img.shields.io/travis/react-component/touchable.svg?style=flat-square
14 | [travis-url]: https://travis-ci.org/react-component/touchable
15 | [coveralls-image]: https://img.shields.io/coveralls/react-component/touchable.svg?style=flat-square
16 | [coveralls-url]: https://coveralls.io/r/react-component/touchable?branch=master
17 | [gemnasium-image]: http://img.shields.io/gemnasium/react-component/touchable.svg?style=flat-square
18 | [gemnasium-url]: https://gemnasium.com/react-component/touchable
19 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square
20 | [node-url]: http://nodejs.org/download/
21 | [download-image]: https://img.shields.io/npm/dm/rc-touchable.svg?style=flat-square
22 | [download-url]: https://npmjs.org/package/rc-touchable
23 |
24 | ## Install
25 |
26 | [](https://npmjs.org/package/rc-touchable)
27 |
28 | ## Usage
29 |
30 | ```js
31 | import Touchable from 'rc-touchable';
32 | ReactDOM.render(
33 | click
34 | , container);
35 | ```
36 |
37 | ## API
38 |
39 |
40 |
41 |
42 | name |
43 | type |
44 | default |
45 | description |
46 |
47 |
48 |
49 |
50 | disabled |
51 | boolean |
52 | false |
53 | |
54 |
55 |
56 | onPress |
57 | ()=>void |
58 | |
59 | onPress/onTap callback |
60 |
61 |
62 | onLongPress |
63 | ()=>void |
64 | |
65 | onLongPress/onLongTap callback |
66 |
67 |
68 | activeClassName |
69 | string |
70 | |
71 | className applied to child when active |
72 |
73 |
74 | activeStyle |
75 | object |
76 | |
77 | style applied to child when active |
78 |
79 |
80 | activeStopPropagation |
81 | object |
82 | |
83 | stop child active propagate to parent |
84 |
85 |
86 |
87 |
88 |
89 | ## Development
90 |
91 | ```
92 | npm install
93 | npm start
94 | ```
95 |
96 | ## Example
97 |
98 | http://localhost:8007/examples/
99 |
100 | online example: http://react-component.github.io/touchable/
101 |
102 |
103 | ## Test Case
104 |
105 | ```
106 | npm test
107 | npm run chrome-test
108 | ```
109 |
110 | ## Coverage
111 |
112 | ```
113 | npm run coverage
114 | ```
115 |
116 | open coverage/ dir
117 |
118 |
119 | ## License
120 |
121 | rc-touchable is released under the MIT license.
122 |
--------------------------------------------------------------------------------
/examples/demo.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/touchable/d1ac708448cb7b4edf7440d850b71ec8a47fa986/examples/demo.html
--------------------------------------------------------------------------------
/examples/demo.tsx:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-console */
2 |
3 | import Touchable from 'rc-touchable';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 |
7 | const style = `
8 | .active {
9 | background: red;
10 | }
11 | .x {
12 | display:inline-block;
13 | width:100px;
14 | height:100px;
15 | border:1px solid yellow;
16 | }
17 | .x:active {
18 | background: red;
19 | }
20 | `;
21 |
22 | const Test = React.createClass({
23 | componentWillMount() {
24 | (window as any).log = this.log;
25 | },
26 |
27 | onPress(e) {
28 | e.stopPropagation();
29 | console.log(e.type, e.target.id);
30 | },
31 |
32 | onLongPress(e) {
33 | e.stopPropagation();
34 | console.log(e.type, e.target.id);
35 | },
36 | log(m) {
37 | this.refs.log.innerHTML += `${m}:${Date.now()}
`;
38 | this.refs.log.scrollTop = this.refs.log.scrollHeight;
39 | },
40 | render() {
41 | return (
42 |
43 |
44 |
45 |
50 |
59 |
68 | click
78 |
79 |
80 |
81 |
82 |
83 |
84 |
{
85 | this.log('onClick');
86 | }}>click
87 |
88 |
89 | );
90 | },
91 | });
92 |
93 | ReactDOM.render(, document.getElementById('__react-content'));
94 |
--------------------------------------------------------------------------------
/examples/disable-inner.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/touchable/d1ac708448cb7b4edf7440d850b71ec8a47fa986/examples/disable-inner.html
--------------------------------------------------------------------------------
/examples/disable-inner.tsx:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-console */
2 |
3 | import Touchable from 'rc-touchable';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | const style = `
7 | .foo-button {
8 | display: inline-block;
9 | width: 200px;
10 | height: 100px;
11 | border: 1px solid #ccc;
12 | color: blue;
13 | }
14 | .foo-button.disabled {
15 | background-color: black;
16 | }
17 | `;
18 |
19 | const Test = React.createClass({
20 | getInitialState() {
21 | return {
22 | value: 1,
23 | max: 3,
24 | min: 1,
25 | };
26 | },
27 | onPressPrev(e) {
28 | console.log('onPressPrev', e);
29 | this.prev();
30 | },
31 |
32 | onPressNext(e) {
33 | console.log('onPressNext', e);
34 | this.next();
35 | },
36 |
37 | onLongPress(e) {
38 | console.log('onLongPress', e);
39 | },
40 | prev() {
41 | this.setState({
42 | value: this.state.value - 1,
43 | });
44 | },
45 | next() {
46 | this.setState({
47 | value: this.state.value + 1,
48 | });
49 | },
50 | handlePressOuterPrev() {
51 | console.log('handlePressOuterPrev');
52 | },
53 | handlePressOuterNext() {
54 | console.log('handlePressOuterNext');
55 | },
56 | render() {
57 | const { value, min, max } = this.state;
58 | return (
59 |
60 |
61 |
67 |
74 |
75 |
Now page: {value}
76 |
82 |
89 |
90 |
91 | );
92 | },
93 | });
94 |
95 | ReactDOM.render(, document.getElementById('__react-content'));
96 |
--------------------------------------------------------------------------------
/examples/disable-outer.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/touchable/d1ac708448cb7b4edf7440d850b71ec8a47fa986/examples/disable-outer.html
--------------------------------------------------------------------------------
/examples/disable-outer.tsx:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-console */
2 |
3 | import Touchable from 'rc-touchable';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | const style = `
7 | .foo-button {
8 | display: inline-block;
9 | width: 200px;
10 | height: 100px;
11 | border: 1px solid #ccc;
12 | color: blue;
13 | }
14 | .foo-button.disabled {
15 | background-color: black;
16 | }
17 | `;
18 |
19 | const Test = React.createClass({
20 | getInitialState() {
21 | return {
22 | value: 1,
23 | max: 3,
24 | min: 1,
25 | };
26 | },
27 | onPressPrev(e) {
28 | console.log('onPressPrev', e);
29 | this.prev();
30 | },
31 |
32 | onPressNext(e) {
33 | console.log('onPressNext', e);
34 | this.next();
35 | },
36 |
37 | onLongPress(e) {
38 | console.log('onLongPress', e);
39 | },
40 | prev() {
41 | this.setState({
42 | value: this.state.value - 1,
43 | });
44 | },
45 | next() {
46 | this.setState({
47 | value: this.state.value + 1,
48 | });
49 | },
50 |
51 | render() {
52 | const { value, min, max } = this.state;
53 | return (
54 |
55 |
56 |
63 |
68 |
69 |
Now page: {value}
70 |
77 |
82 |
83 |
84 | );
85 | },
86 | });
87 |
88 | ReactDOM.render(, document.getElementById('__react-content'));
89 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./src/index');
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rc-touchable",
3 | "version": "1.3.2",
4 | "description": "React Touchable Component",
5 | "keywords": [
6 | "react",
7 | "touchable",
8 | "component"
9 | ],
10 | "homepage": "http://github.com/react-component/touchable",
11 | "author": "yiminghe@gmail.com",
12 | "repository": {
13 | "type": "git",
14 | "url": "git@github.com:react-component/touchable.git"
15 | },
16 | "bugs": {
17 | "url": "http://github.com/react-component/touchable/issues"
18 | },
19 | "license": "MIT",
20 | "files": [
21 | "lib",
22 | "es"
23 | ],
24 | "main": "./lib/index",
25 | "module": "./es/index",
26 | "config": {
27 | "port": 8007
28 | },
29 | "scripts": {
30 | "compile": "rc-tools run compile",
31 | "build": "rc-tools run build",
32 | "gh-pages": "rc-tools run gh-pages",
33 | "start": "rc-tools run server",
34 | "pub": "rc-tools run pub",
35 | "lint": "rc-tools run lint --no-js-lint",
36 | "test": "rc-test run test",
37 | "chrome-test": "rc-test run chrome-test",
38 | "coverage": "rc-test run coverage"
39 | },
40 | "devDependencies": {
41 | "@types/mocha": "~2.2.32",
42 | "@types/react": "^15.0.38",
43 | "@types/react-dom": "^15.5.1",
44 | "expect.js": "~0.3.1",
45 | "pre-commit": "1.x",
46 | "rc-tools": "6.x",
47 | "rc-test": "6.x",
48 | "react": "15.x",
49 | "react-dom": "15.x"
50 | },
51 | "pre-commit": [
52 | "lint"
53 | ],
54 | "typings": "./lib/index.d.ts",
55 | "dependencies": {
56 | "babel-runtime": "6.x"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/PressEvent.tsx:
--------------------------------------------------------------------------------
1 | function PressEvent(nativeEvent) {
2 | this.nativeEvent = nativeEvent;
3 | ['type', 'currentTarget', 'target', 'touches', 'changedTouches'].forEach(m => {
4 | this[m] = nativeEvent[m];
5 | });
6 | if (!nativeEvent.$pressSeq) {
7 | nativeEvent.$pressSeq = 1;
8 | } else {
9 | nativeEvent.$pressSeq += 1;
10 | }
11 | this.$pressSeq = nativeEvent.$pressSeq;
12 | }
13 |
14 | PressEvent.prototype = {
15 | ...PressEvent.prototype,
16 | preventDefault() {
17 | this.nativeEvent.preventDefault();
18 | },
19 | stopPropagation() {
20 | const { nativeEvent, $pressSeq } = this;
21 | if (nativeEvent.$stopPressSeq) {
22 | return;
23 | }
24 | nativeEvent.$stopPressSeq = $pressSeq;
25 | },
26 | };
27 |
28 | // because of setTimeout, can not depend on fire order
29 | export function shouldFirePress(e) {
30 | const { nativeEvent, $pressSeq } = e;
31 | if (!nativeEvent.$stopPressSeq) {
32 | return true;
33 | }
34 | return nativeEvent.$stopPressSeq >= $pressSeq;
35 | }
36 |
37 | export default PressEvent;
38 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | // inspired by react-native
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import PressEvent, { shouldFirePress } from './PressEvent';
6 |
7 | function keyMirror(obj) {
8 | Object.keys(obj).forEach(k => obj[k] = k);
9 | return obj;
10 | }
11 |
12 | function copy(from, list) {
13 | const to = {};
14 | list.forEach((k) => {
15 | to[k] = from[k];
16 | });
17 | return to;
18 | }
19 |
20 | function extractSingleTouch(_nativeEvent) {
21 | let nativeEvent = _nativeEvent;
22 | if (nativeEvent.nativeEvent) {
23 | nativeEvent = nativeEvent.nativeEvent;
24 | }
25 | const touches = nativeEvent.touches;
26 | const changedTouches = nativeEvent.changedTouches;
27 | const hasTouches = touches && touches.length > 0;
28 | const hasChangedTouches = changedTouches && changedTouches.length > 0;
29 |
30 | return !hasTouches && hasChangedTouches ?
31 | changedTouches[0] :
32 | hasTouches ? touches[0] : nativeEvent;
33 | }
34 |
35 | /**
36 | * Touchable states.
37 | */
38 | const States = keyMirror({
39 | NOT_RESPONDER: null, // Not the responder
40 | RESPONDER_INACTIVE_PRESS_IN: null, // Responder, inactive, in the `PressRect`
41 | RESPONDER_INACTIVE_PRESS_OUT: null, // Responder, inactive, out of `PressRect`
42 | RESPONDER_ACTIVE_PRESS_IN: null, // Responder, active, in the `PressRect`
43 | RESPONDER_ACTIVE_PRESS_OUT: null, // Responder, active, out of `PressRect`
44 | RESPONDER_ACTIVE_LONG_PRESS_IN: null, // Responder, active, in the `PressRect`, after long press threshold
45 | RESPONDER_ACTIVE_LONG_PRESS_OUT: null, // Responder, active, out of `PressRect`, after long press threshold
46 | ERROR: null,
47 | });
48 |
49 | /**
50 | * Quick lookup map for states that are considered to be "active"
51 | */
52 | const IsActive = {
53 | RESPONDER_ACTIVE_PRESS_OUT: true,
54 | RESPONDER_ACTIVE_PRESS_IN: true,
55 | };
56 |
57 | /**
58 | * Quick lookup for states that are considered to be "pressing" and are
59 | * therefore eligible to result in a "selection" if the press stops.
60 | */
61 | const IsPressingIn = {
62 | RESPONDER_INACTIVE_PRESS_IN: true,
63 | RESPONDER_ACTIVE_PRESS_IN: true,
64 | RESPONDER_ACTIVE_LONG_PRESS_IN: true,
65 | };
66 |
67 | const IsLongPressingIn = {
68 | RESPONDER_ACTIVE_LONG_PRESS_IN: true,
69 | };
70 |
71 | /**
72 | * Inputs to the state machine.
73 | */
74 | const Signals = keyMirror({
75 | DELAY: null,
76 | RESPONDER_GRANT: null,
77 | RESPONDER_RELEASE: null,
78 | RESPONDER_TERMINATED: null,
79 | ENTER_PRESS_RECT: null,
80 | LEAVE_PRESS_RECT: null,
81 | LONG_PRESS_DETECTED: null,
82 | });
83 |
84 | /**
85 | * Mapping from States x Signals => States
86 | */
87 | const Transitions = {
88 | NOT_RESPONDER: {
89 | DELAY: States.ERROR,
90 | RESPONDER_GRANT: States.RESPONDER_INACTIVE_PRESS_IN,
91 | RESPONDER_RELEASE: States.ERROR,
92 | RESPONDER_TERMINATED: States.ERROR,
93 | ENTER_PRESS_RECT: States.ERROR,
94 | LEAVE_PRESS_RECT: States.ERROR,
95 | LONG_PRESS_DETECTED: States.ERROR,
96 | },
97 | RESPONDER_INACTIVE_PRESS_IN: {
98 | DELAY: States.RESPONDER_ACTIVE_PRESS_IN,
99 | RESPONDER_GRANT: States.ERROR,
100 | RESPONDER_RELEASE: States.NOT_RESPONDER,
101 | RESPONDER_TERMINATED: States.NOT_RESPONDER,
102 | ENTER_PRESS_RECT: States.RESPONDER_INACTIVE_PRESS_IN,
103 | LEAVE_PRESS_RECT: States.RESPONDER_INACTIVE_PRESS_OUT,
104 | LONG_PRESS_DETECTED: States.ERROR,
105 | },
106 | RESPONDER_INACTIVE_PRESS_OUT: {
107 | DELAY: States.RESPONDER_ACTIVE_PRESS_OUT,
108 | RESPONDER_GRANT: States.ERROR,
109 | RESPONDER_RELEASE: States.NOT_RESPONDER,
110 | RESPONDER_TERMINATED: States.NOT_RESPONDER,
111 | ENTER_PRESS_RECT: States.RESPONDER_INACTIVE_PRESS_IN,
112 | LEAVE_PRESS_RECT: States.RESPONDER_INACTIVE_PRESS_OUT,
113 | LONG_PRESS_DETECTED: States.ERROR,
114 | },
115 | RESPONDER_ACTIVE_PRESS_IN: {
116 | DELAY: States.ERROR,
117 | RESPONDER_GRANT: States.ERROR,
118 | RESPONDER_RELEASE: States.NOT_RESPONDER,
119 | RESPONDER_TERMINATED: States.NOT_RESPONDER,
120 | ENTER_PRESS_RECT: States.RESPONDER_ACTIVE_PRESS_IN,
121 | LEAVE_PRESS_RECT: States.RESPONDER_ACTIVE_PRESS_OUT,
122 | LONG_PRESS_DETECTED: States.RESPONDER_ACTIVE_LONG_PRESS_IN,
123 | },
124 | RESPONDER_ACTIVE_PRESS_OUT: {
125 | DELAY: States.ERROR,
126 | RESPONDER_GRANT: States.ERROR,
127 | RESPONDER_RELEASE: States.NOT_RESPONDER,
128 | RESPONDER_TERMINATED: States.NOT_RESPONDER,
129 | ENTER_PRESS_RECT: States.RESPONDER_ACTIVE_PRESS_IN,
130 | LEAVE_PRESS_RECT: States.RESPONDER_ACTIVE_PRESS_OUT,
131 | LONG_PRESS_DETECTED: States.ERROR,
132 | },
133 | RESPONDER_ACTIVE_LONG_PRESS_IN: {
134 | DELAY: States.ERROR,
135 | RESPONDER_GRANT: States.ERROR,
136 | RESPONDER_RELEASE: States.NOT_RESPONDER,
137 | RESPONDER_TERMINATED: States.NOT_RESPONDER,
138 | ENTER_PRESS_RECT: States.RESPONDER_ACTIVE_LONG_PRESS_IN,
139 | LEAVE_PRESS_RECT: States.RESPONDER_ACTIVE_LONG_PRESS_OUT,
140 | LONG_PRESS_DETECTED: States.RESPONDER_ACTIVE_LONG_PRESS_IN,
141 | },
142 | RESPONDER_ACTIVE_LONG_PRESS_OUT: {
143 | DELAY: States.ERROR,
144 | RESPONDER_GRANT: States.ERROR,
145 | RESPONDER_RELEASE: States.NOT_RESPONDER,
146 | RESPONDER_TERMINATED: States.NOT_RESPONDER,
147 | ENTER_PRESS_RECT: States.RESPONDER_ACTIVE_LONG_PRESS_IN,
148 | LEAVE_PRESS_RECT: States.RESPONDER_ACTIVE_LONG_PRESS_OUT,
149 | LONG_PRESS_DETECTED: States.ERROR,
150 | },
151 | error: {
152 | DELAY: States.NOT_RESPONDER,
153 | RESPONDER_GRANT: States.RESPONDER_INACTIVE_PRESS_IN,
154 | RESPONDER_RELEASE: States.NOT_RESPONDER,
155 | RESPONDER_TERMINATED: States.NOT_RESPONDER,
156 | ENTER_PRESS_RECT: States.NOT_RESPONDER,
157 | LEAVE_PRESS_RECT: States.NOT_RESPONDER,
158 | LONG_PRESS_DETECTED: States.NOT_RESPONDER,
159 | },
160 | };
161 |
162 | // ==== Typical Constants for integrating into UI components ====
163 | // const HIT_EXPAND_PX = 20;
164 | // const HIT_VERT_OFFSET_PX = 10;
165 | const HIGHLIGHT_DELAY_MS = 130;
166 |
167 | const PRESS_EXPAND_PX = 20;
168 |
169 | const LONG_PRESS_THRESHOLD = 500;
170 |
171 | const LONG_PRESS_DELAY_MS = LONG_PRESS_THRESHOLD - HIGHLIGHT_DELAY_MS;
172 |
173 | const LONG_PRESS_ALLOWED_MOVEMENT = 10;
174 |
175 | // Default amount "active" region protrudes beyond box
176 |
177 | export interface ITouchable {
178 | fixClickPenetration?: boolean;
179 | disabled?: boolean;
180 | delayPressIn?: number;
181 | delayLongPress?: number;
182 | delayPressOut?: number;
183 | pressRetentionOffset?: {
184 | left: number;
185 | right: number;
186 | top: number;
187 | bottom: number;
188 | };
189 | hitSlop?: {
190 | left: number;
191 | right: number;
192 | top: number;
193 | bottom: number;
194 | };
195 | activeStyle?: any;
196 | activeStopPropagation?: boolean;
197 | activeClassName?: string;
198 | onPress?: (e?: any) => void;
199 | onLongPress?: (e?: any) => void;
200 | longPressCancelsPress?: boolean;
201 | }
202 |
203 | let lastClickTime = 0;
204 |
205 | const pressDelay = 200;
206 |
207 | function isAllowPress() {
208 | // avoid click penetration
209 | return Date.now() - lastClickTime >= pressDelay;
210 | }
211 |
212 | export default class Touchable extends React.Component {
213 | static defaultProps = {
214 | fixClickPenetration: false,
215 | disabled: false,
216 | delayPressIn: HIGHLIGHT_DELAY_MS,
217 | delayLongPress: LONG_PRESS_DELAY_MS,
218 | delayPressOut: 100,
219 | pressRetentionOffset: {
220 | left: PRESS_EXPAND_PX,
221 | right: PRESS_EXPAND_PX,
222 | top: PRESS_EXPAND_PX,
223 | bottom: PRESS_EXPAND_PX,
224 | },
225 | hitSlop: undefined,
226 | longPressCancelsPress: true,
227 | };
228 |
229 | state = {
230 | active: false,
231 | };
232 |
233 | touchable: any = { touchState: undefined };
234 | root: any;
235 | releaseLockTimer: any;
236 | touchableDelayTimeout: any;
237 | longPressDelayTimeout: any;
238 | pressOutDelayTimeout: any;
239 | lockMouse: any;
240 | shouldActive: Boolean;
241 | pressInLocation: { pageX: number; pageY: number; };
242 |
243 | componentDidMount() {
244 | this.root = ReactDOM.findDOMNode(this);
245 | }
246 |
247 | componentDidUpdate() {
248 | this.root = ReactDOM.findDOMNode(this);
249 | // disabled auto clear active state
250 | if (this.props.disabled && this.state.active) {
251 | this.setState({
252 | active: false,
253 | });
254 | }
255 | }
256 |
257 | componentWillUnmount() {
258 | if (this.releaseLockTimer) {
259 | clearTimeout(this.releaseLockTimer);
260 | }
261 | if (this.touchableDelayTimeout) {
262 | clearTimeout(this.touchableDelayTimeout);
263 | }
264 | if (this.longPressDelayTimeout) {
265 | clearTimeout(this.longPressDelayTimeout);
266 | }
267 | if (this.pressOutDelayTimeout) {
268 | clearTimeout(this.pressOutDelayTimeout);
269 | }
270 | }
271 |
272 | callChildEvent(event, e) {
273 | const childHandle = React.Children.only(this.props.children).props[event];
274 | if (childHandle) {
275 | childHandle(e);
276 | }
277 | }
278 |
279 | onTouchStart = (e) => {
280 | this.callChildEvent('onTouchStart', e);
281 | this.lockMouse = true;
282 | if (this.releaseLockTimer) {
283 | clearTimeout(this.releaseLockTimer);
284 | }
285 | this.touchableHandleResponderGrant(e.nativeEvent);
286 | }
287 |
288 | onTouchMove = (e) => {
289 | this.callChildEvent('onTouchMove', e);
290 | this.touchableHandleResponderMove(e.nativeEvent);
291 | }
292 |
293 | onTouchEnd = (e) => {
294 | this.callChildEvent('onTouchEnd', e);
295 | this.releaseLockTimer = setTimeout(() => {
296 | this.lockMouse = false;
297 | }, 300);
298 | this.touchableHandleResponderRelease(new PressEvent(e.nativeEvent));
299 | }
300 |
301 | onTouchCancel = (e) => {
302 | this.callChildEvent('onTouchCancel', e);
303 | this.releaseLockTimer = setTimeout(() => {
304 | this.lockMouse = false;
305 | }, 300);
306 | this.touchableHandleResponderTerminate(e.nativeEvent);
307 | }
308 |
309 | onMouseDown = (e) => {
310 | this.callChildEvent('onMouseDown', e);
311 | if (this.lockMouse) {
312 | return;
313 | }
314 | this.touchableHandleResponderGrant(e.nativeEvent);
315 | document.addEventListener('mousemove', this.touchableHandleResponderMove, false);
316 | document.addEventListener('mouseup', this.onMouseUp, false);
317 | }
318 |
319 | onMouseUp = (e) => {
320 | document.removeEventListener('mousemove', this.touchableHandleResponderMove, false);
321 | document.removeEventListener('mouseup', this.onMouseUp, false);
322 | this.touchableHandleResponderRelease(new PressEvent(e));
323 | }
324 |
325 | _remeasureMetricsOnInit(e) {
326 | const { root } = this;
327 | const touch = extractSingleTouch(e);
328 | const boundingRect = root.getBoundingClientRect();
329 | this.touchable = {
330 | touchState: this.touchable.touchState,
331 | startMouse: {
332 | pageX: touch.pageX,
333 | pageY: touch.pageY,
334 | },
335 | positionOnGrant: {
336 | left: boundingRect.left + window.pageXOffset,
337 | top: boundingRect.top + window.pageYOffset,
338 | width: boundingRect.width,
339 | height: boundingRect.height,
340 | clientLeft: boundingRect.left,
341 | clientTop: boundingRect.top,
342 | },
343 | };
344 | }
345 |
346 | processActiveStopPropagation(e) {
347 | const nativeEvent = e.nativeEvent || e;
348 | this.shouldActive = !nativeEvent.__activeStopPropagation;
349 | if (this.props.activeStopPropagation) {
350 | nativeEvent.__activeStopPropagation = 1;
351 | }
352 | }
353 |
354 | touchableHandleResponderGrant(e) {
355 | this.touchable.touchState = States.NOT_RESPONDER;
356 |
357 | if (this.pressOutDelayTimeout) {
358 | clearTimeout(this.pressOutDelayTimeout);
359 | this.pressOutDelayTimeout = null;
360 | }
361 |
362 | if (this.props.fixClickPenetration && !isAllowPress()) {
363 | return;
364 | }
365 |
366 | this._remeasureMetricsOnInit(e);
367 |
368 | this._receiveSignal(Signals.RESPONDER_GRANT, e);
369 | const { delayPressIn: delayMS, delayLongPress: longDelayMS } = this.props;
370 |
371 | this.processActiveStopPropagation(e);
372 |
373 | if (delayMS) {
374 | this.touchableDelayTimeout = setTimeout(
375 | () => {
376 | this._handleDelay(e);
377 | },
378 | delayMS,
379 | );
380 | } else {
381 | this._handleDelay(e);
382 | }
383 |
384 | const longPressEvent = new PressEvent(e);
385 | this.longPressDelayTimeout = setTimeout(
386 | () => {
387 | this._handleLongDelay(longPressEvent);
388 | },
389 | longDelayMS! + delayMS!,
390 | );
391 | }
392 |
393 | checkScroll(e) {
394 | const positionOnGrant = this.touchable.positionOnGrant;
395 | // container or window scroll
396 | const boundingRect = this.root.getBoundingClientRect();
397 | if (boundingRect.left !== positionOnGrant.clientLeft || boundingRect.top !== positionOnGrant.clientTop) {
398 | this._receiveSignal(Signals.RESPONDER_TERMINATED, e);
399 | return true;
400 | }
401 | return false;
402 | }
403 |
404 | touchableHandleResponderRelease(e) {
405 | if (!this.touchable.startMouse) {
406 | return;
407 | }
408 | const touch = extractSingleTouch(e);
409 | if (Math.abs(touch.pageX - this.touchable.startMouse.pageX) > 30 ||
410 | Math.abs(touch.pageY - this.touchable.startMouse.pageY) > 30
411 | ) {
412 | this._receiveSignal(Signals.RESPONDER_TERMINATED, e);
413 | return;
414 | }
415 | if (this.checkScroll(e)) {
416 | return;
417 | }
418 | this._receiveSignal(Signals.RESPONDER_RELEASE, e);
419 | }
420 |
421 | touchableHandleResponderTerminate(e) {
422 | if (!this.touchable.startMouse) {
423 | return;
424 | }
425 | this._receiveSignal(Signals.RESPONDER_TERMINATED, e);
426 | }
427 |
428 | checkTouchWithinActive(e) {
429 | const { positionOnGrant } = this.touchable;
430 | const { pressRetentionOffset = {} as any, hitSlop } = this.props;
431 |
432 | let pressExpandLeft = pressRetentionOffset.left;
433 | let pressExpandTop = pressRetentionOffset.top;
434 | let pressExpandRight = pressRetentionOffset.right;
435 | let pressExpandBottom = pressRetentionOffset.bottom;
436 |
437 | if (hitSlop) {
438 | pressExpandLeft += hitSlop.left;
439 | pressExpandTop += hitSlop.top;
440 | pressExpandRight += hitSlop.right;
441 | pressExpandBottom += hitSlop.bottom;
442 | }
443 |
444 | const touch = extractSingleTouch(e);
445 | const pageX = touch && touch.pageX;
446 | const pageY = touch && touch.pageY;
447 | return (
448 | pageX > positionOnGrant.left - pressExpandLeft &&
449 | pageY > positionOnGrant.top - pressExpandTop &&
450 | pageX <
451 | positionOnGrant.left +
452 | positionOnGrant.width +
453 | pressExpandRight &&
454 | pageY <
455 | positionOnGrant.top +
456 | positionOnGrant.height +
457 | pressExpandBottom
458 | );
459 | }
460 |
461 | touchableHandleResponderMove = (e) => {
462 | if (!this.touchable.startMouse) {
463 | return;
464 | }
465 | // Measurement may not have returned yet.
466 | if (!this.touchable.dimensionsOnActivate ||
467 | this.touchable.touchState === States.NOT_RESPONDER) {
468 | return;
469 | }
470 |
471 | // Not enough time elapsed yet, wait for highlight -
472 | // this is just a perf optimization.
473 | if (this.touchable.touchState === States.RESPONDER_INACTIVE_PRESS_IN) {
474 | return;
475 | }
476 |
477 | const touch = extractSingleTouch(e);
478 |
479 | const pageX = touch && touch.pageX;
480 | const pageY = touch && touch.pageY;
481 |
482 | if (this.pressInLocation) {
483 | const movedDistance = this._getDistanceBetweenPoints(pageX, pageY,
484 | this.pressInLocation.pageX, this.pressInLocation.pageY);
485 | if (movedDistance > LONG_PRESS_ALLOWED_MOVEMENT) {
486 | this._cancelLongPressDelayTimeout();
487 | }
488 | }
489 | if (this.checkTouchWithinActive(e)) {
490 | this._receiveSignal(Signals.ENTER_PRESS_RECT, e);
491 | const curState = this.touchable.touchState;
492 | if (curState === States.RESPONDER_INACTIVE_PRESS_IN) {
493 | this._cancelLongPressDelayTimeout();
494 | }
495 | } else {
496 | this._cancelLongPressDelayTimeout();
497 | this._receiveSignal(Signals.LEAVE_PRESS_RECT, e);
498 | }
499 | }
500 |
501 | callProp(name, e) {
502 | if (this.props[name] && !this.props.disabled) {
503 | this.props[name](e);
504 | }
505 | }
506 |
507 | touchableHandleActivePressIn(e) {
508 | if (this.shouldActive) {
509 | this.setActive(true);
510 | }
511 | this.callProp('onPressIn', e);
512 | }
513 |
514 | touchableHandleActivePressOut(e) {
515 | this.setActive(false);
516 | this.callProp('onPressOut', e);
517 | }
518 |
519 | touchableHandlePress(e) {
520 | if (shouldFirePress(e)) {
521 | this.callProp('onPress', e);
522 | }
523 | lastClickTime = Date.now();
524 | }
525 |
526 | touchableHandleLongPress(e) {
527 | if (shouldFirePress(e)) {
528 | this.callProp('onLongPress', e);
529 | }
530 | }
531 |
532 | setActive(active) {
533 | if (this.state.active !== active &&
534 | (this.props.activeClassName || this.props.activeStyle)) {
535 | this.setState({
536 | active,
537 | });
538 | }
539 | }
540 |
541 | _remeasureMetricsOnActivation() {
542 | this.touchable.dimensionsOnActivate = this.touchable.positionOnGrant;
543 | }
544 |
545 | _handleDelay(e) {
546 | this.touchableDelayTimeout = null;
547 | this._receiveSignal(Signals.DELAY, e);
548 | }
549 |
550 | _handleLongDelay(e) {
551 | this.longPressDelayTimeout = null;
552 | const curState = this.touchable.touchState;
553 | if (curState !== States.RESPONDER_ACTIVE_PRESS_IN &&
554 | curState !== States.RESPONDER_ACTIVE_LONG_PRESS_IN) {
555 | console.error('Attempted to transition from state `' + curState + '` to `' +
556 | States.RESPONDER_ACTIVE_LONG_PRESS_IN + '`, which is not supported. This is ' +
557 | 'most likely due to `Touchable.longPressDelayTimeout` not being cancelled.');
558 | } else {
559 | this._receiveSignal(Signals.LONG_PRESS_DETECTED, e);
560 | }
561 | }
562 |
563 | _receiveSignal(signal, e) {
564 | const curState = this.touchable.touchState;
565 | const nextState = Transitions[curState] && Transitions[curState][signal];
566 | if (!nextState) {
567 | return;
568 | }
569 | if (nextState === States.ERROR) {
570 | return;
571 | }
572 | if (curState !== nextState) {
573 | this._performSideEffectsForTransition(curState, nextState, signal, e);
574 | this.touchable.touchState = nextState;
575 | }
576 | }
577 |
578 | _cancelLongPressDelayTimeout() {
579 | if (this.longPressDelayTimeout) {
580 | clearTimeout(this.longPressDelayTimeout);
581 | this.longPressDelayTimeout = null;
582 | }
583 | }
584 |
585 | _isHighlight(state) {
586 | return state === States.RESPONDER_ACTIVE_PRESS_IN ||
587 | state === States.RESPONDER_ACTIVE_LONG_PRESS_IN;
588 | }
589 |
590 | _savePressInLocation(e) {
591 | const touch = extractSingleTouch(e);
592 | const pageX = touch && touch.pageX;
593 | const pageY = touch && touch.pageY;
594 | this.pressInLocation = { pageX, pageY };
595 | }
596 |
597 | _getDistanceBetweenPoints(aX, aY, bX, bY) {
598 | const deltaX = aX - bX;
599 | const deltaY = aY - bY;
600 | return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
601 | }
602 |
603 | _performSideEffectsForTransition(curState, nextState, signal, e) {
604 | const curIsHighlight = this._isHighlight(curState);
605 | const newIsHighlight = this._isHighlight(nextState);
606 |
607 | const isFinalSignal =
608 | signal === Signals.RESPONDER_TERMINATED ||
609 | signal === Signals.RESPONDER_RELEASE;
610 |
611 | if (isFinalSignal) {
612 | this._cancelLongPressDelayTimeout();
613 | }
614 |
615 | if (!IsActive[curState] && IsActive[nextState]) {
616 | this._remeasureMetricsOnActivation();
617 | }
618 |
619 | if (IsPressingIn[curState] && signal === Signals.LONG_PRESS_DETECTED) {
620 | this.touchableHandleLongPress(e);
621 | }
622 |
623 | if (newIsHighlight && !curIsHighlight) {
624 | this._startHighlight(e);
625 | } else if (!newIsHighlight && curIsHighlight) {
626 | this._endHighlight(e);
627 | }
628 |
629 | if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) {
630 | const hasLongPressHandler = !!this.props.onLongPress;
631 | const pressIsLongButStillCallOnPress =
632 | IsLongPressingIn[curState] && ( // We *are* long pressing..
633 | !hasLongPressHandler || // But either has no long handler
634 | !this.props.longPressCancelsPress // or we're told to ignore it.
635 | );
636 |
637 | const shouldInvokePress = !IsLongPressingIn[curState] || pressIsLongButStillCallOnPress;
638 | if (shouldInvokePress) {
639 | if (!newIsHighlight && !curIsHighlight) {
640 | // we never highlighted because of delay, but we should highlight now
641 | this._startHighlight(e);
642 | this._endHighlight(e);
643 | }
644 | this.touchableHandlePress(e);
645 | }
646 | }
647 |
648 | if (this.touchableDelayTimeout) {
649 | clearTimeout(this.touchableDelayTimeout);
650 | this.touchableDelayTimeout = null;
651 | }
652 | }
653 |
654 | _startHighlight(e) {
655 | this._savePressInLocation(e);
656 | this.touchableHandleActivePressIn(e);
657 | }
658 |
659 | _endHighlight(e) {
660 | if (this.props.delayPressOut) {
661 | this.pressOutDelayTimeout = setTimeout(() => {
662 | this.touchableHandleActivePressOut(e);
663 | }, this.props.delayPressOut);
664 | } else {
665 | this.touchableHandleActivePressOut(e);
666 | }
667 | }
668 |
669 | render() {
670 | const { children, disabled, activeStyle, activeClassName } = this.props;
671 | const events = disabled ? undefined :
672 | copy(this, [
673 | 'onTouchStart',
674 | 'onTouchMove',
675 | 'onTouchEnd',
676 | 'onTouchCancel',
677 | 'onMouseDown',
678 | ]);
679 | const child = React.Children.only(children);
680 | if (!disabled && this.state.active) {
681 | let { style, className } = child.props;
682 | if (activeStyle) {
683 | style = {
684 | ...style,
685 | ...activeStyle,
686 | };
687 | }
688 | if (activeClassName) {
689 | if (className) {
690 | className += ` ${activeClassName}`;
691 | } else {
692 | className = activeClassName;
693 | }
694 | }
695 | return React.cloneElement(child, {
696 | className,
697 | style,
698 | ...events,
699 | });
700 | }
701 | return React.cloneElement(child, events);
702 | }
703 | }
704 |
--------------------------------------------------------------------------------
/tests/index.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect.js';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import Touchable from '../index';
5 | import TestUtils from 'react-dom/test-utils';
6 | import PressEvent from '../src/PressEvent';
7 |
8 | describe('Touchable', () => {
9 | let container;
10 |
11 | beforeEach(() => {
12 | container = document.createElement('div');
13 | document.body.appendChild(container);
14 | });
15 |
16 | afterEach(() => {
17 | ReactDOM.unmountComponentAtNode(container);
18 | document.body.removeChild(container);
19 | });
20 |
21 | it('onPress works', (done) => {
22 | function onPress() {
23 | done();
24 | }
25 |
26 | const com = ReactDOM.render((
27 |
28 | touch
29 |
30 | ), container);
31 | const t = document.getElementById('t');
32 | const rect = t.getBoundingClientRect();
33 | const touches = [{
34 | pageX: 20 + rect.left,
35 | pageY: 20 + rect.top,
36 | }];
37 | com.touchableHandleResponderGrant({
38 | touches,
39 | });
40 | setTimeout(() => {
41 | expect(t.className).to.be('t');
42 | com.touchableHandleResponderRelease(new PressEvent({
43 | touches,
44 | }));
45 | }, 200);
46 | });
47 |
48 | it('onLongPress works', (done) => {
49 | function onPress() {
50 | expect('should not call onLongPress').to.be('');
51 | }
52 |
53 | function onLongPress() {
54 | setTimeout(() => {
55 | done();
56 | }, 900);
57 | }
58 |
59 | const com = ReactDOM.render((
60 |
65 | touch
66 |
67 | ), container);
68 | const t = document.getElementById('t');
69 | const rect = t.getBoundingClientRect();
70 | const touches = [{
71 | pageX: 20 + rect.left,
72 | pageY: 20 + rect.top,
73 | }];
74 | com.touchableHandleResponderGrant({
75 | touches,
76 | });
77 | setTimeout(() => {
78 | expect(t.className).to.be('t');
79 | com.touchableHandleResponderRelease(new PressEvent({
80 | touches,
81 | }));
82 | }, 600);
83 | });
84 |
85 | it('onPress move works', (done) => {
86 | function onPress() {
87 | setTimeout(() => {
88 | done();
89 | }, 100);
90 | }
91 |
92 | function onLongPress() {
93 | expect('should not call onLongPress').to.be('');
94 | }
95 |
96 | const com = ReactDOM.render((
97 |
103 | touch
104 |
105 | ), container);
106 | const t = document.getElementById('t');
107 | const rect = t.getBoundingClientRect();
108 | const touches = [{
109 | pageX: 20 + rect.left,
110 | pageY: 20 + rect.top,
111 | }];
112 | const moveTouches = [
113 | {
114 | pageX: 380 + rect.left,
115 | pageY: 380 + rect.top,
116 | }
117 | ];
118 | com.touchableHandleResponderGrant({
119 | touches,
120 | });
121 | setTimeout(() => {
122 | expect(t.style.color).to.be('red');
123 | com.touchableHandleResponderMove({
124 | touches: moveTouches,
125 | changedTouches: moveTouches,
126 | });
127 | expect(t.style.color).to.be('');
128 | com.touchableHandleResponderMove({
129 | touches,
130 | changedTouches: touches,
131 | });
132 | setTimeout(() => {
133 | com.touchableHandleResponderRelease(new PressEvent({
134 | touches,
135 | changedTouches: touches,
136 | }));
137 | }, 700);
138 |
139 | }, 200);
140 | });
141 |
142 | // https://github.com/ant-design/ant-design-mobile/issues/937#issuecomment-284625667
143 | it('props.disabled should works fine with onClick', () => {
144 | class Demo extends React.Component {
145 | state = {
146 | currentPage: 1,
147 | disabled: false,
148 | };
149 | handleClick = () => {
150 | const { currentPage } = this.state;
151 | const nextPagae = currentPage + 1;
152 | this.setState({
153 | currentPage: nextPagae,
154 | disabled: nextPagae >= 3,
155 | });
156 | }
157 |
158 | render() {
159 | return (
160 | this.com = com}>
161 | touch {this.state.currentPage}
163 |
164 | )
165 | }
166 | }
167 |
168 | const instance = ReactDOM.render(, container)
169 | const com = instance.com;
170 | const t = document.getElementById('t');
171 |
172 | TestUtils.Simulate.click(t);
173 | TestUtils.Simulate.click(t);
174 | expect(t.className).to.be('');
175 | expect(com.props.disabled).to.be(true);
176 | });
177 |
178 | it('without activeStopPropagation works', () => {
179 | class Demo extends React.Component {
180 | render() {
181 | return (
182 |
183 |
184 |
outer
185 |
186 | inner
187 |
188 |
189 |
190 | )
191 | }
192 | }
193 |
194 | const instance = ReactDOM.render(, container)
195 | const outer = document.getElementById('outer');
196 | const inner = document.getElementById('inner');
197 |
198 | TestUtils.Simulate.touchStart(inner);
199 | TestUtils.Simulate.touchEnd(inner);
200 | expect(inner.className).to.be('t');
201 | expect(outer.className).to.be('t2');
202 | });
203 |
204 | it('activeStopPropagation works', () => {
205 | class Demo extends React.Component {
206 | render() {
207 | return (
208 |
209 |
210 |
outer
211 |
212 | inner
213 |
214 |
215 |
216 | )
217 | }
218 | }
219 |
220 | const instance = ReactDOM.render(, container)
221 | const outer = document.getElementById('outer');
222 | const inner = document.getElementById('inner');
223 |
224 | TestUtils.Simulate.touchStart(inner);
225 | TestUtils.Simulate.touchEnd(inner);
226 | expect(inner.className).to.be('t');
227 | expect(outer.className).to.be('');
228 | });
229 | });
230 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strictNullChecks": true,
4 | "moduleResolution": "node",
5 | "allowSyntheticDefaultImports": true,
6 | "jsx": "react",
7 | "target": "es6"
8 | }
9 | }
--------------------------------------------------------------------------------
/typings/custom.d.ts:
--------------------------------------------------------------------------------
1 | declare module "object-assign";
2 |
3 | declare module "rc-touchable";
4 |
5 | declare module "raf";
6 |
--------------------------------------------------------------------------------
/typings/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------