├── .eslintignore
├── .npmignore
├── .gitignore
├── src
├── Tappable.js
├── touchStyles.js
├── Pinchable.js
├── TapAndPinchable.js
├── PinchableBaseMixin.js
├── getComponent.js
├── PinchableMixin.js
└── TappableMixin.js
├── lib
├── touchStyles.js
├── Tappable.js
├── Pinchable.js
├── TapAndPinchable.js
├── PinchableBaseMixin.js
├── getComponent.js
├── PinchableMixin.js
└── TappableMixin.js
├── .editorconfig
├── .eslintrc
├── gulpfile.js
├── bower.json
├── LICENSE
├── example
├── dist
│ ├── index.html
│ ├── pinch.html
│ ├── example.css
│ ├── pinch.js
│ └── example.js
└── src
│ ├── index.html
│ ├── pinch.html
│ ├── pinch.js
│ ├── example.less
│ └── example.js
├── package.json
├── CHANGES.md
├── README.md
└── dist
└── react-tappable.min.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | lib/*
3 | example/dist/*
4 | node_modules/*
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | bower.json
2 | dist/
3 | example
4 | gulpfile.js
5 | src/
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Coverage tools
11 | lib-cov
12 | coverage
13 |
14 | # Compiled binary addons (http://nodejs.org/api/addons.html)
15 | build/Release
16 |
17 | # Dependency directory
18 | node_modules
19 |
20 | # Deploy directory
21 | .publish
22 |
--------------------------------------------------------------------------------
/src/Tappable.js:
--------------------------------------------------------------------------------
1 | var TappableMixin = require('./TappableMixin');
2 | var getComponent = require('./getComponent');
3 | var touchStyles = require('./touchStyles');
4 |
5 | var Component = getComponent([TappableMixin]);
6 |
7 | module.exports = Component;
8 | module.exports.touchStyles = touchStyles;
9 | module.exports.Mixin = TappableMixin;
10 |
--------------------------------------------------------------------------------
/lib/touchStyles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var touchStyles = {
4 | WebkitTapHighlightColor: 'rgba(0,0,0,0)',
5 | WebkitTouchCallout: 'none',
6 | WebkitUserSelect: 'none',
7 | KhtmlUserSelect: 'none',
8 | MozUserSelect: 'none',
9 | msUserSelect: 'none',
10 | userSelect: 'none',
11 | cursor: 'pointer'
12 | };
13 |
14 | module.exports = touchStyles;
--------------------------------------------------------------------------------
/lib/Tappable.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var TappableMixin = require('./TappableMixin');
4 | var getComponent = require('./getComponent');
5 | var touchStyles = require('./touchStyles');
6 |
7 | var Component = getComponent([TappableMixin]);
8 |
9 | module.exports = Component;
10 | module.exports.touchStyles = touchStyles;
11 | module.exports.Mixin = TappableMixin;
--------------------------------------------------------------------------------
/src/touchStyles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var touchStyles = {
4 | WebkitTapHighlightColor: 'rgba(0,0,0,0)',
5 | WebkitTouchCallout: 'none',
6 | WebkitUserSelect: 'none',
7 | KhtmlUserSelect: 'none',
8 | MozUserSelect: 'none',
9 | msUserSelect: 'none',
10 | userSelect: 'none',
11 | cursor: 'pointer'
12 | };
13 |
14 | module.exports = touchStyles;
15 |
--------------------------------------------------------------------------------
/src/Pinchable.js:
--------------------------------------------------------------------------------
1 | var PinchableBaseMixin = require('./PinchableBaseMixin');
2 | var PinchableMixin = require('./PinchableMixin');
3 | var getComponent = require('./getComponent');
4 | var touchStyles = require('./touchStyles');
5 |
6 | var Component = getComponent([PinchableBaseMixin, PinchableMixin]);
7 |
8 | module.exports = Component;
9 | module.exports.touchStyles = touchStyles;
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 | root = true
4 |
5 | [*]
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | indent_style = tab
11 |
12 | [*.json]
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.yml]
17 | indent_style = space
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/lib/Pinchable.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var PinchableBaseMixin = require('./PinchableBaseMixin');
4 | var PinchableMixin = require('./PinchableMixin');
5 | var getComponent = require('./getComponent');
6 | var touchStyles = require('./touchStyles');
7 |
8 | var Component = getComponent([PinchableBaseMixin, PinchableMixin]);
9 |
10 | module.exports = Component;
11 | module.exports.touchStyles = touchStyles;
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | },
7 | "plugins": [
8 | "react"
9 | ],
10 | "rules": {
11 | "curly": [2, "multi-line"],
12 | "no-shadow": 0,
13 | "no-underscore-dangle": 0,
14 | "no-unused-expressions": 0,
15 | "quotes": [2, "single", "avoid-escape"],
16 | "react/jsx-uses-react": 1,
17 | "react/jsx-uses-vars": 1,
18 | "react/react-in-jsx-scope": 1,
19 | "semi": 2,
20 | "strict": 0
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/TapAndPinchable.js:
--------------------------------------------------------------------------------
1 | var TappableMixin = require('./TappableMixin');
2 | var PinchableMixin = require('./PinchableMixin');
3 | var getComponent = require('./getComponent');
4 | var touchStyles = require('./touchStyles');
5 |
6 | var Component = getComponent([TappableMixin, PinchableMixin]);
7 |
8 | module.exports = Component;
9 | module.exports.touchStyles = touchStyles;
10 | module.exports.Mixin = Object.assign({}, TappableMixin, {
11 | onPinchStart: PinchableMixin.onPinchStart,
12 | onPinchMove: PinchableMixin.onPinchMove,
13 | onPinchEnd: PinchableMixin.onPinchEnd
14 | });
15 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var initGulpTasks = require('react-component-gulp-tasks');
3 |
4 | var taskConfig = {
5 |
6 | component: {
7 | name: 'Tappable',
8 | dependencies: [
9 | 'react',
10 | 'react-dom'
11 | ]
12 | },
13 |
14 | example: {
15 | src: 'example/src',
16 | dist: 'example/dist',
17 | files: [
18 | 'index.html',
19 | 'pinch.html',
20 | '.gitignore'
21 | ],
22 | scripts: [
23 | 'example.js',
24 | 'pinch.js'
25 | ],
26 | less: [
27 | 'example.less'
28 | ]
29 | }
30 |
31 | };
32 |
33 | initGulpTasks(gulp, taskConfig);
34 |
--------------------------------------------------------------------------------
/lib/TapAndPinchable.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var TappableMixin = require('./TappableMixin');
4 | var PinchableMixin = require('./PinchableMixin');
5 | var getComponent = require('./getComponent');
6 | var touchStyles = require('./touchStyles');
7 |
8 | var Component = getComponent([TappableMixin, PinchableMixin]);
9 |
10 | module.exports = Component;
11 | module.exports.touchStyles = touchStyles;
12 | module.exports.Mixin = Object.assign({}, TappableMixin, {
13 | onPinchStart: PinchableMixin.onPinchStart,
14 | onPinchMove: PinchableMixin.onPinchMove,
15 | onPinchEnd: PinchableMixin.onPinchEnd
16 | });
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tappable",
3 | "main": "dist/react-tappable.min.js",
4 | "version": "1.0.0",
5 | "homepage": "https://github.com/JedWatson/react-tappable",
6 | "authors": [
7 | "Jed Watson"
8 | ],
9 | "description": "Touch / Tappable Event Handling Component for React",
10 | "moduleType": [
11 | "amd",
12 | "globals",
13 | "node"
14 | ],
15 | "keywords": [
16 | "react",
17 | "react-component",
18 | "tap",
19 | "tappable",
20 | "touch",
21 | "mobile"
22 | ],
23 | "license": "MIT",
24 | "ignore": [
25 | ".editorconfig",
26 | ".gitignore",
27 | "package.json",
28 | "src",
29 | "node_modules",
30 | "example",
31 | "test"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jed Watson
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 |
--------------------------------------------------------------------------------
/example/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | React-Tappable Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
React Tappable
15 |
16 |
17 |
18 | Events in the area on the left will be logged on the right.
19 | Toggle scrolling to test event cancel on scroll (touch devices only).
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/example/dist/pinch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | React-Tappable Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
React Tappable
15 |
16 |
17 |
18 | Events in the area on the left will be logged on the right.
19 | Toggle scrolling to test event cancel on scroll (touch devices only).
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/example/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | React-Tappable Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
React Tappable
15 |
16 |
17 |
18 | Events in the area on the left will be logged on the right.
19 | Toggle scrolling to test event cancel on scroll (touch devices only).
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/example/src/pinch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | React-Tappable Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
React Tappable
15 |
16 |
17 |
18 | Events in the area on the left will be logged on the right.
19 | Toggle scrolling to test event cancel on scroll (touch devices only).
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/example/src/pinch.js:
--------------------------------------------------------------------------------
1 | var createReactClass = require('create-react-class');
2 | var React = require('react');
3 | var ReactDOM = require('react-dom');
4 | var Tappable = require('react-tappable');
5 |
6 | var App = createReactClass({
7 | getInitialState: function () {
8 | return {
9 | events: []
10 | };
11 | },
12 | componentDidUpdate: function () {
13 | var log = this.refs.eventLog;
14 | log.scrollTop = log.scrollHeight;
15 | },
16 | handleEvent: function (name/*, event*/) {
17 | var events = this.state.events;
18 | events.push(name);
19 | this.setState({
20 | events: events
21 | });
22 | },
23 | render: function () {
24 | var nestedEvents = {
25 | onPinchStart: this.handleEvent.bind(this, 'pinch start'),
26 | onPinchMove: this.handleEvent.bind(this, 'pinch move'),
27 | onPinchEnd: this.handleEvent.bind(this, 'pinch end')
28 | };
29 | return (
30 |
31 |
32 |
Tappable area:
33 |
34 | Touch me
35 | Nested Pinchable
36 |
37 |
38 |
39 |
Event log:
40 |
41 | {this.state.events.map(function (ev, i) {
42 | return
{ev}
;
43 | })}
44 |
45 |
46 |
47 | );
48 | }
49 | });
50 |
51 | ReactDOM.render(, document.getElementById('app'));
52 |
--------------------------------------------------------------------------------
/example/dist/example.css:
--------------------------------------------------------------------------------
1 | /*
2 | // Examples Stylesheet
3 | // -------------------
4 | */
5 | body {
6 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
7 | font-size: 14px;
8 | color: #333;
9 | margin: 0;
10 | padding: 0;
11 | }
12 | a {
13 | color: #08c;
14 | text-decoration: none;
15 | }
16 | a:hover {
17 | text-decoration: underline;
18 | }
19 | button {
20 | border-radius: 5px;
21 | background: #eee;
22 | border: 1px solid #666;
23 | margin-bottom: 10px;
24 | }
25 | .container {
26 | margin-left: auto;
27 | margin-right: auto;
28 | max-width: 400px;
29 | padding: 1em;
30 | }
31 | .footer {
32 | margin-top: 50px;
33 | border-top: 1px solid #eee;
34 | padding: 20px 0;
35 | font-size: 12px;
36 | color: #999;
37 | }
38 | h1,
39 | h2,
40 | h3,
41 | h4,
42 | h5,
43 | h6 {
44 | color: #222;
45 | font-weight: 100;
46 | margin: 0.5em 0;
47 | }
48 | .example {
49 | overflow: hidden;
50 | }
51 | .scrolling {
52 | margin: 5px 0;
53 | }
54 | .scrolling .link {
55 | text-decoration: underline;
56 | }
57 | .scrolling .Tappable-active {
58 | opacity: 0.8;
59 | background: white;
60 | }
61 | .left {
62 | float: left;
63 | width: 120px;
64 | height: 200px;
65 | overflow: scroll;
66 | }
67 | .tappable-area {
68 | width: 80px;
69 | height: 460px;
70 | padding: 20px;
71 | text-align: center;
72 | background: #eee;
73 | cursor: default;
74 | }
75 | .nested-tappable {
76 | display: block;
77 | margin: 15px 5px;
78 | padding: 5px;
79 | border: 1px solid #ccc;
80 | background: #ddd;
81 | }
82 | .Tappable-active {
83 | background: #5cc2f8;
84 | }
85 | .right {
86 | float: left;
87 | width: 180px;
88 | padding-left: 20px;
89 | }
90 | .event-log {
91 | height: 180px;
92 | overflow: auto;
93 | }
94 | .hint {
95 | margin: 15px 0;
96 | font-style: italic;
97 | color: #999;
98 | }
99 |
--------------------------------------------------------------------------------
/example/src/example.less:
--------------------------------------------------------------------------------
1 | /*
2 | // Examples Stylesheet
3 | // -------------------
4 | */
5 |
6 | body {
7 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
8 | font-size: 14px;
9 | color: #333;
10 | margin: 0;
11 | padding: 0;
12 | }
13 |
14 | a {
15 | color: #08c;
16 | text-decoration: none;
17 | }
18 |
19 | a:hover {
20 | text-decoration: underline;
21 | }
22 |
23 | button {
24 | border-radius: 5px;
25 | background: #eee;
26 | border: 1px solid #666;
27 | margin-bottom: 10px;
28 | }
29 |
30 | .container {
31 | margin-left: auto;
32 | margin-right: auto;
33 | max-width: 400px;
34 | padding: 1em;
35 | }
36 |
37 | .footer {
38 | margin-top: 50px;
39 | border-top: 1px solid #eee;
40 | padding: 20px 0;
41 | font-size: 12px;
42 | color: #999;
43 | }
44 |
45 | h1, h2, h3, h4, h5, h6 {
46 | color: #222;
47 | font-weight: 100;
48 | margin: 0.5em 0;
49 | }
50 |
51 | .example {
52 | overflow: hidden;
53 | }
54 |
55 | .scrolling {
56 | margin: 5px 0;
57 |
58 | .link {
59 | text-decoration: underline;
60 | }
61 |
62 | .Tappable-active {
63 | opacity: 0.8;
64 | background: white
65 | }
66 | }
67 |
68 | .left {
69 | float: left;
70 | width: 120px;
71 | height: 200px;
72 | overflow: scroll;
73 | }
74 |
75 | .tappable-area {
76 | width: 80px;
77 | height: 460px;
78 | padding: 20px;
79 | text-align: center;
80 | background: #eee;
81 | cursor: default;
82 | }
83 |
84 | .nested-tappable {
85 | display: block;
86 | margin: 15px 5px;
87 | padding: 5px;
88 | border: 1px solid #ccc;
89 | background: #ddd;
90 | }
91 |
92 | .Tappable-active {
93 | background: #5cc2f8;
94 | }
95 |
96 | .right {
97 | float: left;
98 | width: 180px;
99 | padding-left: 20px;
100 | }
101 |
102 | .event-log {
103 | height: 180px;
104 | overflow: auto;
105 | }
106 |
107 | .hint {
108 | margin: 15px 0;
109 | font-style: italic;
110 | color: #999;
111 | }
112 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tappable",
3 | "version": "1.0.4",
4 | "description": "Touch / Tappable Event Handling Component for React",
5 | "main": "lib/TapAndPinchable.js",
6 | "author": "Jed Watson",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/JedWatson/react-tappable.git"
11 | },
12 | "peerDependencies": {
13 | "create-react-class": "^15.5.2",
14 | "prop-types": "^15.5.8",
15 | "react": "^0.14 || ^15.0.0-rc || ^15.0.0 || ^16.0.0",
16 | "react-dom": "^0.14 || ^15.0.0-rc || ^15.0.0 || ^16.0.0"
17 | },
18 | "devDependencies": {
19 | "babel-cli": "^6.26.0",
20 | "babel-core": "^6.26.0",
21 | "babel-eslint": "^4.1.3",
22 | "babel-preset-es2015": "^6.24.1",
23 | "babel-preset-react": "^6.24.1",
24 | "babelify": "^8.0.0",
25 | "create-react-class": "^15.5.2",
26 | "eslint": "^1.6.0",
27 | "eslint-plugin-react": "^3.5.1",
28 | "gulp": "^3.9.1",
29 | "prop-types": "^15.5.8",
30 | "react": "^0.14 || ^15.0.0-rc || ^15.0.0 || ^16.0.0",
31 | "react-component-gulp-tasks": "^0.7.7",
32 | "react-dom": "^0.14 || ^15.0.0-rc || ^15.0.0 || ^16.0.0"
33 | },
34 | "babel": {
35 | "presets": [
36 | "es2015",
37 | "react"
38 | ]
39 | },
40 | "browserify-shim": {
41 | "create-react-class": "global:createReactClass",
42 | "prop-types": "global:PropTypes",
43 | "react": "global:React",
44 | "react-dom": "global:ReactDOM"
45 | },
46 | "scripts": {
47 | "build": "babel src --out-dir lib",
48 | "dist": "browserify src/TapAndPinchable.js --standalone ReactTappable --transform [ babelify ] > dist/react-tappable.js",
49 | "start": "gulp dev",
50 | "examples": "gulp dev:server",
51 | "lint": "eslint ./; true",
52 | "publish:site": "NODE_ENV=production gulp publish:examples",
53 | "release": "NODE_ENV=production gulp release",
54 | "test": "echo \"no tests yet\" && exit 0",
55 | "watch": "gulp watch:lib"
56 | },
57 | "keywords": [
58 | "react",
59 | "react-component",
60 | "tap",
61 | "tappable",
62 | "touch",
63 | "mobile"
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/src/PinchableBaseMixin.js:
--------------------------------------------------------------------------------
1 | var PropTypes = require('prop-types');
2 | var React = require('react');
3 |
4 | var Mixin = {
5 | propTypes: {
6 | preventDefault: PropTypes.bool, // whether to preventDefault on all events
7 | stopPropagation: PropTypes.bool, // whether to stopPropagation on all events
8 |
9 | onTouchStart: PropTypes.func, // pass-through touch event
10 | onTouchMove: PropTypes.func, // pass-through touch event
11 | onTouchEnd: PropTypes.func // pass-through touch event
12 | },
13 |
14 | getInitialState: function () {
15 | return {
16 | isActive: false
17 | };
18 | },
19 |
20 | processEvent: function (event) {
21 | if (this.props.preventDefault) event.preventDefault();
22 | if (this.props.stopPropagation) event.stopPropagation();
23 | },
24 |
25 | onTouchStart: function (event) {
26 | if (this.props.onTouchStart && this.props.onTouchStart(event) === false) return;
27 | this.processEvent(event);
28 |
29 | if (this.onPinchStart &&
30 | (this.props.onPinchStart || this.props.onPinchMove || this.props.onPinchEnd) &&
31 | event.touches.length === 2) {
32 | this.onPinchStart && this.onPinchStart(event);
33 | }
34 | },
35 |
36 | onTouchMove: function (event) {
37 | if (this._initialPinch && event.touches.length === 2 && this.onPinchMove) {
38 | this.onPinchMove(event);
39 | event.preventDefault();
40 | }
41 | },
42 |
43 | onTouchEnd: function (event) {
44 | if (this.onPinchEnd && this._initialPinch && (event.touches.length + event.changedTouches.length) === 2) {
45 | this.onPinchEnd(event);
46 | event.preventDefault();
47 | }
48 | },
49 |
50 | endTouch: function (event, callback) {
51 | this._initialTouch = null;
52 | this._lastTouch = null;
53 | if (this.state.isActive) {
54 | this.setState({
55 | isActive: false
56 | }, callback);
57 | } else if (callback) {
58 | callback();
59 | }
60 | },
61 |
62 | handlers: function () {
63 | return {
64 | onTouchStart: this.onTouchStart,
65 | onTouchMove: this.onTouchMove,
66 | onTouchEnd: this.onTouchEnd,
67 | onMouseDown: this.onMouseDown,
68 | onMouseUp: this.onMouseUp,
69 | onMouseMove: this.onMouseMove,
70 | onMouseOut: this.onMouseOut
71 | };
72 | }
73 | };
74 |
75 | module.exports = Mixin;
76 |
--------------------------------------------------------------------------------
/lib/PinchableBaseMixin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var PropTypes = require('prop-types');
4 | var React = require('react');
5 |
6 | var Mixin = {
7 | propTypes: {
8 | preventDefault: PropTypes.bool, // whether to preventDefault on all events
9 | stopPropagation: PropTypes.bool, // whether to stopPropagation on all events
10 |
11 | onTouchStart: PropTypes.func, // pass-through touch event
12 | onTouchMove: PropTypes.func, // pass-through touch event
13 | onTouchEnd: PropTypes.func // pass-through touch event
14 | },
15 |
16 | getInitialState: function getInitialState() {
17 | return {
18 | isActive: false
19 | };
20 | },
21 |
22 | processEvent: function processEvent(event) {
23 | if (this.props.preventDefault) event.preventDefault();
24 | if (this.props.stopPropagation) event.stopPropagation();
25 | },
26 |
27 | onTouchStart: function onTouchStart(event) {
28 | if (this.props.onTouchStart && this.props.onTouchStart(event) === false) return;
29 | this.processEvent(event);
30 |
31 | if (this.onPinchStart && (this.props.onPinchStart || this.props.onPinchMove || this.props.onPinchEnd) && event.touches.length === 2) {
32 | this.onPinchStart && this.onPinchStart(event);
33 | }
34 | },
35 |
36 | onTouchMove: function onTouchMove(event) {
37 | if (this._initialPinch && event.touches.length === 2 && this.onPinchMove) {
38 | this.onPinchMove(event);
39 | event.preventDefault();
40 | }
41 | },
42 |
43 | onTouchEnd: function onTouchEnd(event) {
44 | if (this.onPinchEnd && this._initialPinch && event.touches.length + event.changedTouches.length === 2) {
45 | this.onPinchEnd(event);
46 | event.preventDefault();
47 | }
48 | },
49 |
50 | endTouch: function endTouch(event, callback) {
51 | this._initialTouch = null;
52 | this._lastTouch = null;
53 | if (this.state.isActive) {
54 | this.setState({
55 | isActive: false
56 | }, callback);
57 | } else if (callback) {
58 | callback();
59 | }
60 | },
61 |
62 | handlers: function handlers() {
63 | return {
64 | onTouchStart: this.onTouchStart,
65 | onTouchMove: this.onTouchMove,
66 | onTouchEnd: this.onTouchEnd,
67 | onMouseDown: this.onMouseDown,
68 | onMouseUp: this.onMouseUp,
69 | onMouseMove: this.onMouseMove,
70 | onMouseOut: this.onMouseOut
71 | };
72 | }
73 | };
74 |
75 | module.exports = Mixin;
--------------------------------------------------------------------------------
/lib/getComponent.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var createReactClass = require('create-react-class');
4 | var PropTypes = require('prop-types');
5 | var React = require('react');
6 | var touchStyles = require('./touchStyles');
7 |
8 | /**
9 | * Tappable Component
10 | * ==================
11 | */
12 | module.exports = function (mixins) {
13 | return createReactClass({
14 | displayName: 'Tappable',
15 |
16 | mixins: mixins,
17 |
18 | propTypes: {
19 | component: PropTypes.any, // component to create
20 | className: PropTypes.string, // optional className
21 | classBase: PropTypes.string, // base for generated classNames
22 | classes: PropTypes.object, // object containing the active and inactive class names
23 | style: PropTypes.object, // additional style properties for the component
24 | disabled: PropTypes.bool // only applies to buttons
25 | },
26 |
27 | getDefaultProps: function getDefaultProps() {
28 | return {
29 | component: 'span',
30 | classBase: 'Tappable'
31 | };
32 | },
33 |
34 | render: function render() {
35 | var props = this.props;
36 | var className = props.classBase + (this.state.isActive ? '-active' : '-inactive');
37 |
38 | if (props.className) {
39 | className += ' ' + props.className;
40 | }
41 |
42 | if (props.classes) {
43 | className += ' ' + (this.state.isActive ? props.classes.active : props.classes.inactive);
44 | }
45 |
46 | var style = {};
47 | Object.assign(style, touchStyles, props.style);
48 |
49 | var newComponentProps = Object.assign({}, props, {
50 | style: style,
51 | className: className,
52 | disabled: props.disabled,
53 | handlers: this.handlers
54 | }, this.handlers());
55 |
56 | delete newComponentProps.activeDelay;
57 | delete newComponentProps.allowReactivation;
58 | delete newComponentProps.classBase;
59 | delete newComponentProps.classes;
60 | delete newComponentProps.handlers;
61 | delete newComponentProps.onTap;
62 | delete newComponentProps.onPress;
63 | delete newComponentProps.onPinchStart;
64 | delete newComponentProps.onPinchMove;
65 | delete newComponentProps.onPinchEnd;
66 | delete newComponentProps.onDeactivate;
67 | delete newComponentProps.onReactivate;
68 | delete newComponentProps.moveThreshold;
69 | delete newComponentProps.moveXThreshold;
70 | delete newComponentProps.moveYThreshold;
71 | delete newComponentProps.pressDelay;
72 | delete newComponentProps.pressMoveThreshold;
73 | delete newComponentProps.preventDefault;
74 | delete newComponentProps.stopPropagation;
75 | delete newComponentProps.component;
76 |
77 | return React.createElement(props.component, newComponentProps, props.children);
78 | }
79 | });
80 | };
--------------------------------------------------------------------------------
/src/getComponent.js:
--------------------------------------------------------------------------------
1 | var createReactClass = require('create-react-class');
2 | var PropTypes = require('prop-types');
3 | var React = require('react');
4 | var touchStyles = require('./touchStyles');
5 |
6 | /**
7 | * Tappable Component
8 | * ==================
9 | */
10 | module.exports = function (mixins) {
11 | return createReactClass({
12 | displayName: 'Tappable',
13 |
14 | mixins: mixins,
15 |
16 | propTypes: {
17 | component: PropTypes.any, // component to create
18 | className: PropTypes.string, // optional className
19 | classBase: PropTypes.string, // base for generated classNames
20 | classes: PropTypes.object, // object containing the active and inactive class names
21 | style: PropTypes.object, // additional style properties for the component
22 | disabled: PropTypes.bool // only applies to buttons
23 | },
24 |
25 | getDefaultProps: function () {
26 | return {
27 | component: 'span',
28 | classBase: 'Tappable'
29 | };
30 | },
31 |
32 | render: function () {
33 | var props = this.props;
34 | var className = props.classBase + (this.state.isActive ? '-active' : '-inactive');
35 |
36 | if (props.className) {
37 | className += ' ' + props.className;
38 | }
39 |
40 | if (props.classes) {
41 | className += ' ' + (this.state.isActive ? props.classes.active : props.classes.inactive);
42 | }
43 |
44 | var style = {};
45 | Object.assign(style, touchStyles, props.style);
46 |
47 | var newComponentProps = Object.assign({}, props, {
48 | style: style,
49 | className: className,
50 | disabled: props.disabled,
51 | handlers: this.handlers
52 | }, this.handlers());
53 |
54 | delete newComponentProps.activeDelay;
55 | delete newComponentProps.allowReactivation;
56 | delete newComponentProps.classBase;
57 | delete newComponentProps.classes;
58 | delete newComponentProps.handlers;
59 | delete newComponentProps.onTap;
60 | delete newComponentProps.onPress;
61 | delete newComponentProps.onPinchStart;
62 | delete newComponentProps.onPinchMove;
63 | delete newComponentProps.onPinchEnd;
64 | delete newComponentProps.onDeactivate;
65 | delete newComponentProps.onReactivate;
66 | delete newComponentProps.moveThreshold;
67 | delete newComponentProps.moveXThreshold;
68 | delete newComponentProps.moveYThreshold;
69 | delete newComponentProps.pressDelay;
70 | delete newComponentProps.pressMoveThreshold;
71 | delete newComponentProps.preventDefault;
72 | delete newComponentProps.stopPropagation;
73 | delete newComponentProps.component;
74 |
75 | return React.createElement(props.component, newComponentProps, props.children);
76 | }
77 | });
78 | };
79 |
--------------------------------------------------------------------------------
/example/src/example.js:
--------------------------------------------------------------------------------
1 | var createReactClass = require('create-react-class');
2 | var React = require('react');
3 | var ReactDOM = require('react-dom');
4 | var Tappable = require('react-tappable');
5 |
6 | var App = createReactClass({
7 | getInitialState: function () {
8 | return {
9 | scrolling: false,
10 | events: []
11 | };
12 | },
13 | componentDidUpdate: function () {
14 | var log = this.refs.eventLog;
15 | log.scrollTop = log.scrollHeight;
16 | },
17 | handleEvent: function (name/*, event*/) {
18 | var events = this.state.events;
19 | events.push(name);
20 | this.setState({
21 | events: events
22 | });
23 | },
24 | toggleScrolling: function () {
25 | console.log('scrolling: ' + !this.state.scrolling);
26 | this.setState({
27 | scrolling: !this.state.scrolling
28 | });
29 | },
30 | render: function () {
31 | var events = {
32 | onTap: this.handleEvent.bind(this, 'tap'),
33 | onPress: this.handleEvent.bind(this, 'press'),
34 | onTouchStart: this.handleEvent.bind(this, 'touchStart'),
35 | // onTouchMove: this.handleEvent.bind(this, 'touchMove'),
36 | onTouchEnd: this.handleEvent.bind(this, 'touchEnd'),
37 | onMouseDown: this.handleEvent.bind(this, 'mouseDown'),
38 | // onMouseMove: this.handleEvent.bind(this, 'mouseMove'),
39 | onMouseUp: this.handleEvent.bind(this, 'mouseUp'),
40 | onMouseOut: this.handleEvent.bind(this, 'mouseOut'),
41 | onKeyDown: this.handleEvent.bind(this, 'keyDown'),
42 | onKeyUp: this.handleEvent.bind(this, 'keyUp')
43 | };
44 | var nestedEvents = {
45 | onTap: this.handleEvent.bind(this, 'tap (nested)')
46 | };
47 | var toggleClass = this.state.scrolling ? 'scrolling-enabled' : 'scrolling-disabled';
48 | return (
49 |
50 |
51 |
52 | Toggle Scrolling: {this.state.scrolling ? 'on' : 'off'}
53 |
54 |
55 |
56 |
Tappable area:
57 | Tappable Button
58 |
59 | Touch me
60 | Nested Tappable
61 |
62 |
63 |
64 |
Event log:
65 |
66 | {this.state.events.map(function (ev, i) {
67 | return
{ev}
;
68 | })}
69 |
70 |
71 |
72 | );
73 | }
74 | });
75 |
76 | ReactDOM.render(, document.getElementById('app'));
77 |
--------------------------------------------------------------------------------
/lib/PinchableMixin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var PropTypes = require('prop-types');
4 | var React = require('react');
5 |
6 | function getPinchProps(touches) {
7 | return {
8 | touches: Array.prototype.map.call(touches, function copyTouch(touch) {
9 | return { identifier: touch.identifier, pageX: touch.pageX, pageY: touch.pageY };
10 | }),
11 | center: { x: (touches[0].pageX + touches[1].pageX) / 2, y: (touches[0].pageY + touches[1].pageY) / 2 },
12 | angle: Math.atan() * (touches[1].pageY - touches[0].pageY) / (touches[1].pageX - touches[0].pageX) * 180 / Math.PI,
13 | distance: Math.sqrt(Math.pow(Math.abs(touches[1].pageX - touches[0].pageX), 2) + Math.pow(Math.abs(touches[1].pageY - touches[0].pageY), 2))
14 | };
15 | }
16 |
17 | var Mixin = {
18 | propTypes: {
19 | onPinchStart: PropTypes.func, // fires when a pinch gesture is started
20 | onPinchMove: PropTypes.func, // fires on every touch-move when a pinch action is active
21 | onPinchEnd: PropTypes.func // fires when a pinch action ends
22 | },
23 |
24 | onPinchStart: function onPinchStart(event) {
25 | // in case the two touches didn't start exactly at the same time
26 | if (this._initialTouch) {
27 | this.endTouch();
28 | }
29 | var touches = event.touches;
30 | this._initialPinch = getPinchProps(touches);
31 | this._initialPinch = Object.assign(this._initialPinch, {
32 | displacement: { x: 0, y: 0 },
33 | displacementVelocity: { x: 0, y: 0 },
34 | rotation: 0,
35 | rotationVelocity: 0,
36 | zoom: 1,
37 | zoomVelocity: 0,
38 | time: Date.now()
39 | });
40 | this._lastPinch = this._initialPinch;
41 | this.props.onPinchStart && this.props.onPinchStart(this._initialPinch, event);
42 | },
43 |
44 | onPinchMove: function onPinchMove(event) {
45 | if (this._initialTouch) {
46 | this.endTouch();
47 | }
48 | var touches = event.touches;
49 | if (touches.length !== 2) {
50 | return this.onPinchEnd(event); // bail out before disaster
51 | }
52 |
53 | var currentPinch = touches[0].identifier === this._initialPinch.touches[0].identifier && touches[1].identifier === this._initialPinch.touches[1].identifier ? getPinchProps(touches) // the touches are in the correct order
54 | : touches[1].identifier === this._initialPinch.touches[0].identifier && touches[0].identifier === this._initialPinch.touches[1].identifier ? getPinchProps(touches.reverse()) // the touches have somehow changed order
55 | : getPinchProps(touches); // something is wrong, but we still have two touch-points, so we try not to fail
56 |
57 | currentPinch.displacement = {
58 | x: currentPinch.center.x - this._initialPinch.center.x,
59 | y: currentPinch.center.y - this._initialPinch.center.y
60 | };
61 |
62 | currentPinch.time = Date.now();
63 | var timeSinceLastPinch = currentPinch.time - this._lastPinch.time;
64 |
65 | currentPinch.displacementVelocity = {
66 | x: (currentPinch.displacement.x - this._lastPinch.displacement.x) / timeSinceLastPinch,
67 | y: (currentPinch.displacement.y - this._lastPinch.displacement.y) / timeSinceLastPinch
68 | };
69 |
70 | currentPinch.rotation = currentPinch.angle - this._initialPinch.angle;
71 | currentPinch.rotationVelocity = currentPinch.rotation - this._lastPinch.rotation / timeSinceLastPinch;
72 |
73 | currentPinch.zoom = currentPinch.distance / this._initialPinch.distance;
74 | currentPinch.zoomVelocity = (currentPinch.zoom - this._lastPinch.zoom) / timeSinceLastPinch;
75 |
76 | this.props.onPinchMove && this.props.onPinchMove(currentPinch, event);
77 |
78 | this._lastPinch = currentPinch;
79 | },
80 |
81 | onPinchEnd: function onPinchEnd(event) {
82 | // TODO use helper to order touches by identifier and use actual values on touchEnd.
83 | var currentPinch = Object.assign({}, this._lastPinch);
84 | currentPinch.time = Date.now();
85 |
86 | if (currentPinch.time - this._lastPinch.time > 16) {
87 | currentPinch.displacementVelocity = 0;
88 | currentPinch.rotationVelocity = 0;
89 | currentPinch.zoomVelocity = 0;
90 | }
91 |
92 | this.props.onPinchEnd && this.props.onPinchEnd(currentPinch, event);
93 |
94 | this._initialPinch = this._lastPinch = null;
95 |
96 | // If one finger is still on screen, it should start a new touch event for swiping etc
97 | // But it should never fire an onTap or onPress event.
98 | // Since there is no support swipes yet, this should be disregarded for now
99 | // if (event.touches.length === 1) {
100 | // this.onTouchStart(event);
101 | // }
102 | }
103 | };
104 |
105 | module.exports = Mixin;
--------------------------------------------------------------------------------
/src/PinchableMixin.js:
--------------------------------------------------------------------------------
1 | var PropTypes = require('prop-types');
2 | var React = require('react');
3 |
4 | function getPinchProps (touches) {
5 | return {
6 | touches: Array.prototype.map.call(touches, function copyTouch (touch) {
7 | return { identifier: touch.identifier, pageX: touch.pageX, pageY: touch.pageY };
8 | }),
9 | center: {x: (touches[0].pageX + touches[1].pageX) / 2, y: (touches[0].pageY + touches[1].pageY) / 2 },
10 | angle: Math.atan() * (touches[1].pageY - touches[0].pageY) / (touches[1].pageX - touches[0].pageX) * 180 / Math.PI,
11 | distance: Math.sqrt(Math.pow(Math.abs(touches[1].pageX - touches[0].pageX), 2) + Math.pow(Math.abs(touches[1].pageY - touches[0].pageY), 2))
12 | };
13 | }
14 |
15 | var Mixin = {
16 | propTypes: {
17 | onPinchStart: PropTypes.func, // fires when a pinch gesture is started
18 | onPinchMove: PropTypes.func, // fires on every touch-move when a pinch action is active
19 | onPinchEnd: PropTypes.func // fires when a pinch action ends
20 | },
21 |
22 | onPinchStart: function (event) {
23 | // in case the two touches didn't start exactly at the same time
24 | if (this._initialTouch) {
25 | this.endTouch();
26 | }
27 | var touches = event.touches;
28 | this._initialPinch = getPinchProps(touches);
29 | this._initialPinch = Object.assign(this._initialPinch, {
30 | displacement: { x: 0, y: 0 },
31 | displacementVelocity: { x: 0, y: 0 },
32 | rotation: 0,
33 | rotationVelocity: 0,
34 | zoom: 1,
35 | zoomVelocity: 0,
36 | time: Date.now()
37 | });
38 | this._lastPinch = this._initialPinch;
39 | this.props.onPinchStart && this.props.onPinchStart(this._initialPinch, event);
40 | },
41 |
42 | onPinchMove: function (event) {
43 | if (this._initialTouch) {
44 | this.endTouch();
45 | }
46 | var touches = event.touches;
47 | if (touches.length !== 2) {
48 | return this.onPinchEnd(event); // bail out before disaster
49 | }
50 |
51 | var currentPinch =
52 | touches[0].identifier === this._initialPinch.touches[0].identifier && touches[1].identifier === this._initialPinch.touches[1].identifier ?
53 | getPinchProps(touches) // the touches are in the correct order
54 | : touches[1].identifier === this._initialPinch.touches[0].identifier && touches[0].identifier === this._initialPinch.touches[1].identifier ?
55 | getPinchProps(touches.reverse()) // the touches have somehow changed order
56 | : getPinchProps(touches); // something is wrong, but we still have two touch-points, so we try not to fail
57 |
58 | currentPinch.displacement = {
59 | x: currentPinch.center.x - this._initialPinch.center.x,
60 | y: currentPinch.center.y - this._initialPinch.center.y
61 | };
62 |
63 | currentPinch.time = Date.now();
64 | var timeSinceLastPinch = currentPinch.time - this._lastPinch.time;
65 |
66 | currentPinch.displacementVelocity = {
67 | x: (currentPinch.displacement.x - this._lastPinch.displacement.x) / timeSinceLastPinch,
68 | y: (currentPinch.displacement.y - this._lastPinch.displacement.y) / timeSinceLastPinch
69 | };
70 |
71 | currentPinch.rotation = currentPinch.angle - this._initialPinch.angle;
72 | currentPinch.rotationVelocity = currentPinch.rotation - this._lastPinch.rotation / timeSinceLastPinch;
73 |
74 | currentPinch.zoom = currentPinch.distance / this._initialPinch.distance;
75 | currentPinch.zoomVelocity = (currentPinch.zoom - this._lastPinch.zoom) / timeSinceLastPinch;
76 |
77 | this.props.onPinchMove && this.props.onPinchMove(currentPinch, event);
78 |
79 | this._lastPinch = currentPinch;
80 | },
81 |
82 | onPinchEnd: function (event) {
83 | // TODO use helper to order touches by identifier and use actual values on touchEnd.
84 | var currentPinch = Object.assign({}, this._lastPinch);
85 | currentPinch.time = Date.now();
86 |
87 | if (currentPinch.time - this._lastPinch.time > 16) {
88 | currentPinch.displacementVelocity = 0;
89 | currentPinch.rotationVelocity = 0;
90 | currentPinch.zoomVelocity = 0;
91 | }
92 |
93 | this.props.onPinchEnd && this.props.onPinchEnd(currentPinch, event);
94 |
95 | this._initialPinch = this._lastPinch = null;
96 |
97 | // If one finger is still on screen, it should start a new touch event for swiping etc
98 | // But it should never fire an onTap or onPress event.
99 | // Since there is no support swipes yet, this should be disregarded for now
100 | // if (event.touches.length === 1) {
101 | // this.onTouchStart(event);
102 | // }
103 | }
104 | };
105 |
106 | module.exports = Mixin;
107 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | # React-Tappable Changelog
2 |
3 | ## v0.8.4 / 2016-08-24
4 |
5 | * fixed; Actually include React 15.2+ compatability patches this time!
6 |
7 | ## v0.8.3 / 2016-08-07
8 |
9 | * fixed; React 15.2+ compatiblity patches from the last version were lost during build, correctly included in this version
10 |
11 | ## v0.8.2 / 2016-07-30
12 |
13 | * fixed; warnings from React 15.2+ about invalid dom attributes have been resolved, thanks [Olivier Tassinari](https://github.com/oliviertassinari)
14 | * fixed; pinch angle calculations have been fixed, thanks [Yusuke Shibata](https://github.com/yusukeshibata)
15 | * fixed; `detectScroll()` didn't work properly on Android, thanks [Fangzhou Li](https://github.com/riophae)
16 | * fixed; `ReactDOM` global is now used in the `dist` build
17 |
18 | ## v0.8.1 / 2016-03-20
19 |
20 | Updated to allow compatiblity with React 15.x
21 |
22 | ## v0.8.0 / 2015-12-28
23 |
24 | Tappable now supports keyboard events; a `keyDown` event with the `space` or `enter` keys followed by a `keyUp` event will fire the `onTap` handler.
25 |
26 | New props `onKeyDown` and `onKeyUp` have also been added; return `false` from `onKeyDown` to prevent event handling.
27 |
28 | Thanks to [Will Binns-Smith](https://github.com/wbinnssmith) for this update.
29 |
30 | ## v0.7.2 / 2015-12-13
31 |
32 | * added; new `classes` prop (`Object`) adds support for defining the complete className applied with the component is `active` or `inactive`. Handy for use with [css-modules](https://github.com/css-modules/css-modules), thanks [Rudin Swagerman](https://github.com/rudin).
33 |
34 | ## v0.7.1 / 2015-10-16
35 |
36 | * fixed; use `react-dom` for `findDOMNode`, thanks [Daniel Cousens](https://github.com/dcousens)
37 |
38 | ## v0.7.0 / 2015-10-13
39 |
40 | Tappable is updated for React 0.14. If you're still using React 0.13, please continue to use `react-tappable@0.6.x`. There are no functional differences between v0.7.0 and v0.6.0.
41 |
42 | ## v0.6.0 / 2015-07-31
43 |
44 | This release contains a major refactor that makes `react-tappable` more modular, thanks to [Naman Goel](https://github.com/nmn)
45 |
46 | You can now use _just_ the `Tappable` component, or choose to use the `TapAndPinchable` (default export). Instructions will be added to the Readme with more information soon.
47 |
48 | ## v0.5.7 / 2015-07-30
49 |
50 | * fixed; removed former hacky attempts to handle the React eventpooling problem
51 | * added; support for React `0.14.0-beta1`
52 |
53 | ## v0.5.6 / 2015-07-29
54 |
55 | * fixed; regression introduced in `v0.5.5` where errors would occur in certain conditions
56 |
57 | ## v0.5.5 / 2015-07-29
58 |
59 | * fixed; `afterEndTouch` is now called synchronously, which means the SyntheticTouch event behaves as expected. See [#39](https://github.com/JedWatson/react-tappable/issues/39) and [#47](https://github.com/JedWatson/react-tappable/pull/47) for more information.
60 |
61 | ## v0.5.4 / 2015-07-25
62 |
63 | * fixed; removed `React.initializeTouchEvents`, no longer needed and breaks in React 0.14
64 |
65 | ## v0.5.3 / 2015-07-24
66 |
67 | * fixed; `preventDefault` issue on iOS
68 |
69 | ## v0.5.2 / 2015-06-23
70 |
71 | * Added `activeDelay` prop, delays adding the `-active` class by the provided milliseconds for situations when you don't want to hilight a tap immediately (e.g. iOS Scrollable Lists)
72 |
73 | ## v0.5.1 / 2015-06-17
74 |
75 | * Fixed issue where halting momentum scrolling would incorrectly fire a tap event
76 | * `onTap` now fires after the tappable's `setState` is complete, resolves some animation edge-case issues
77 |
78 | ## v0.5.0 / 2015-06-16
79 |
80 | * Using Babel's polyfill for Object.assign
81 | * `lib` build (via Babel) is provided for use without further transpilation
82 | * `preventDefault` is called to clock the click event firing after a touch has been detected
83 | * React has been changed to a dev/peerDependency
84 | * Added pinch events - `onPinchStart`, `onPinchMove`, `onPinchEnd`
85 | * Older single touch based events don't fire when dealing with multi-touch
86 | * Refactored the way props are passed to component. You can now pass in custom properties for the target component that are not meant for React-Tappable
87 |
88 | ## v0.4.0 / 2015-03-12
89 |
90 | ### Updated
91 |
92 | - Now works with React 0.13, backwards compatible with 0.12
93 |
94 | ## v0.3.3 / 2015-02-19
95 |
96 | ### Added
97 |
98 | - Support for `data-` and `aria-` props on the Component, thanks [Tom Hicks](https://github.com/tomhicks-bsf)
99 |
100 | ## v0.3.2 / 2015-02-19
101 |
102 | ### Fixed
103 |
104 | - Cleanup around removal of Reactify, build-examples is working again
105 |
106 | ## v0.3.1 / 2015-02-18
107 |
108 | ### Fixed
109 |
110 | - Reactify is no longer included as a Browserify transform, thanks [Naman Goel](https://github.com/nmn)
111 |
112 | ## v0.3.0 / 2015-02-07
113 |
114 | This release restructured the code so that most methods are now on a Mixin, which is used by the Component (`module.exports`);
115 |
116 | You can now mix `react-tappable` into your own Components by using the Mixin directly:
117 |
118 | ```
119 | var Tappable = require('react-tappable');
120 |
121 | var MyComponent = React.createComponent({
122 | mixins: [Tappable.Mixin],
123 | /* ... */
124 | });
125 | ```
126 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | React-Tappable
2 | ==============
3 |
4 | Tappable component for React. Abstracts touch events to implement `onTap`, `onPress`, and pinch events.
5 |
6 | The events mimic their native equivalents as closely as possible:
7 |
8 | * the baseClass (default: `Tappable`) has `-active` or `-inactive` added to it to enable pressed-state styling
9 | * the pressed state is visually cancelled if the touch moves too far away from the element, but resumes if the touch comes back again
10 | * when you start scrolling a parent element, the touch event is cancelled
11 | * if the `onPress` property is set, it will cancel the touch event after the press happens
12 |
13 | When touch events are not supported, it will fall back to mouse events. Keyboard events are also supported, emulating the behaviour of native button controls.
14 |
15 |
16 | ## Demo & Examples
17 |
18 | Live demo: [jedwatson.github.io/react-tappable](http://jedwatson.github.io/react-tappable/)
19 |
20 | To build the examples locally, run:
21 |
22 | ```
23 | npm install
24 | gulp dev
25 | ```
26 |
27 | Then open [`localhost:8000`](http://localhost:8000) in a browser.
28 |
29 |
30 | ## Installation
31 |
32 | The easiest way to use React-tappable is to install it from npm.
33 |
34 | ```
35 | npm install react-tappable --save
36 | ```
37 |
38 | Ensure to include it in your own React build process (using [Browserify](http://browserify.org), etc).
39 |
40 | You could also use the standalone build by including `dist/react-tappable.js` in your page; but, if you do this, make sure you have already included React, and that it is available globally.
41 |
42 |
43 | ## Usage
44 |
45 | React-tappable generates a React component (defaults to ``) and binds touch events to it.
46 |
47 | To disable default event handling (e.g. scrolling) set the `preventDefault` prop.
48 |
49 | ```jsx
50 | import Tappable from 'react-tappable';
51 |
52 | Tap me
53 | ```
54 |
55 | For a lighter component, you can opt-in to just the features you need:
56 |
57 | ```jsx
58 | import Tappable from 'react-tappable/lib/Tappable';
59 | import Pinchable from 'react-tappable/lib/Pinchable';
60 | import TapAndPinchable from 'react-tappable/lib/TapAndPinchable';
61 |
62 | I respond to Tap events
63 | I respond to Pinch events
64 | In respond to both!
65 | ```
66 |
67 | The `TapAndPinchable` component is the default one you get when you just import `react-tappable`.
68 |
69 | ### Properties
70 |
71 | * `activeDelay` ms delay before the `-active` class is added, defaults to `0`
72 | * `component` component to render, defaults to `'span'`
73 | * `classes` optional object containing `active` and `inactive` class names to apply to the component; useful with [css-modules](https://github.com/css-modules/css-modules)
74 | * `classBase` base to use for the active/inactive classes
75 | * `className` optional class name for the component
76 | * `moveThreshold` px to allow movement before cancelling a tap; defaults to `100`
77 | * `pressDelay` ms delay before a press event is detected, defaults to `1000`
78 | * `pressMoveThreshold` px to allow movement before ignoring long presses; defaults to `5`
79 | * `preventDefault` (boolean) automatically call preventDefault on all events
80 | * `stopPropagation` (boolean) automatically call stopPropagation on all events
81 | * `style` (object) styles to apply to the component
82 |
83 | ### Special Events
84 |
85 | These are the special events implemented by `Tappable`.
86 |
87 | * `onTap` fired when touchStart or mouseDown is followed by touchEnd or mouseUp within the moveThreshold
88 | * `onPress` fired when a touch is held for the specified ms
89 | * `onPinchStart` fired when two fingers land on the screen
90 | * `onPinchMove` fired on any movement while two fingers are on screen
91 | * `onPinchEnd` fired when less than two fingers are left on the screen, onTouchStart is triggerred, if one touch remains
92 |
93 | #### Pinch Events
94 |
95 | Pinch events come with a special object with additional data to actually be more useful than the native events:
96 |
97 | * `touches`: Array of Objects - {identifier, pageX, pageY} - raw data from the event
98 | * `center`: Object - {x, y} - Calculated center between the two touch points
99 | * `angle`: Degrees - angle of the line connecting the two touch points to the X-axis
100 | * `distance`: Number of pixels - beween the two touch points
101 | * `displacement`: Object {x, y} - offset of the center since the pinch began
102 | * `displacementVelocity`: Object {x, y} : Pixels/ms - Immediate velocity of the displacement
103 | * `rotation`: degrees - delta rotation since the beginning of the gesture
104 | * `rotationVelocity`: degrees/millisecond - immediate rotational velocity
105 | * `zoom`: Number - Zoom factor - ratio between distance between the two touch points now over initial
106 | * `zoomVelocity`: zoomFactor/millisecond - immediate velocity of zooming
107 | * `time`: milliseconds since epoch - Timestamp
108 |
109 | #### Known Issues
110 |
111 | * The pinch implementation has not been thoroughly tested
112 | * Any touch event with 3 three or more touches is completely ignored.
113 |
114 | ### Native Events
115 |
116 | The following native event handlers can also be specified.
117 |
118 | * `onKeyDown`
119 | * `onKeyUp`
120 | * `onTouchStart`
121 | * `onTouchMove`
122 | * `onTouchEnd`
123 | * `onMouseDown`
124 | * `onMouseUp`
125 | * `onMouseMove`
126 | * `onMouseOut`
127 |
128 | Returning `false` from `onKeyDown`, `onMouseDown`, or `onTouchStart` handlers will prevent `Tappable` from handling the event.
129 |
130 | ## Changelog
131 |
132 | See [CHANGES.md](https://github.com/JedWatson/react-tappable/blob/master/CHANGES.md)
133 |
134 | ## License
135 |
136 | Copyright (c) 2017 Jed Watson. [MIT](LICENSE)
137 |
--------------------------------------------------------------------------------
/dist/react-tappable.min.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Tappable=e()}}(function(){return function e(t,o,s){function n(c,h){if(!o[c]){if(!t[c]){var r="function"==typeof require&&require;if(!h&&r)return r(c,!0);if(i)return i(c,!0);var u=new Error("Cannot find module '"+c+"'");throw u.code="MODULE_NOT_FOUND",u}var l=o[c]={exports:{}};t[c][0].call(l.exports,function(e){var o=t[c][1][e];return n(o?o:e)},l,l.exports,e,t,o,s)}return o[c].exports}for(var i="function"==typeof require&&require,c=0;c0?this._activeTimeout=setTimeout(this.makeActive,this.props.activeDelay):this.makeActive()):this.onPinchStart&&(this.props.onPinchStart||this.props.onPinchMove||this.props.onPinchEnd)&&2===e.touches.length&&this.onPinchStart(e))},makeActive:function(){this.isMounted&&(this.clearActiveTimeout(),this.setState({isActive:!0}))},clearActiveTimeout:function(){clearTimeout(this._activeTimeout),this._activeTimeout=!1},initScrollDetection:function(){this._scrollPos={top:0,left:0},this._scrollParents=[],this._scrollParentPos=[];for(var e=n.findDOMNode(this);e;)(e.scrollHeight>e.offsetHeight||e.scrollWidth>e.offsetWidth)&&(this._scrollParents.push(e),this._scrollParentPos.push(e.scrollTop+e.scrollLeft),this._scrollPos.top+=e.scrollTop,this._scrollPos.left+=e.scrollLeft),e=e.parentNode},initTouchmoveDetection:function(){this._touchmoveTriggeredTimes=0},cancelTouchmoveDetection:function(){this._touchmoveDetectionTimeout&&(clearTimeout(this._touchmoveDetectionTimeout),this._touchmoveDetectionTimeout=null,this._touchmoveTriggeredTimes=0)},calculateMovement:function(e){return{x:Math.abs(e.clientX-this._initialTouch.clientX),y:Math.abs(e.clientY-this._initialTouch.clientY)}},detectScroll:function(){for(var e={top:0,left:0},t=0;tthis.props.pressMoveThreshold||t.y>this.props.pressMoveThreshold)&&this.cancelPressDetection(),t.x>this.props.moveThreshold||t.y>this.props.moveThreshold?this.state.isActive?this.setState({isActive:!1}):this._activeTimeout&&this.clearActiveTimeout():this.state.isActive||this._activeTimeout||this.setState({isActive:!0})}else this._initialPinch&&2===e.touches.length&&this.onPinchMove&&(this.onPinchMove(e),e.preventDefault())},onTouchEnd:function(e){var t=this;if(this._initialTouch){this.processEvent(e);var o,s=this.calculateMovement(this._lastTouch);s.x<=this.props.moveThreshold&&s.y<=this.props.moveThreshold&&this.props.onTap&&(e.preventDefault(),o=function(){var o=t._scrollParents.map(function(e){return e.scrollTop+e.scrollLeft}),s=t._scrollParentPos.some(function(e,t){return e!==o[t]});s||t.props.onTap(e)}),this.endTouch(e,o)}else this.onPinchEnd&&this._initialPinch&&e.touches.length+e.changedTouches.length===2&&(this.onPinchEnd(e),e.preventDefault())},endTouch:function(e,t){this.cancelTouchmoveDetection(),this.cancelPressDetection(),this.clearActiveTimeout(),e&&this.props.onTouchEnd&&this.props.onTouchEnd(e),this._initialTouch=null,this._lastTouch=null,t&&t(),this.state.isActive&&this.setState({isActive:!1})},onMouseDown:function(e){return window._blockMouseEvents?void(window._blockMouseEvents=!1):void(this.props.onMouseDown&&this.props.onMouseDown(e)===!1||(this.processEvent(e),this.initPressDetection(e,this.endMouseEvent),this._mouseDown=!0,this.setState({isActive:!0})))},onMouseMove:function(e){!window._blockMouseEvents&&this._mouseDown&&(this.processEvent(e),this.props.onMouseMove&&this.props.onMouseMove(e))},onMouseUp:function(e){!window._blockMouseEvents&&this._mouseDown&&(this.processEvent(e),this.props.onMouseUp&&this.props.onMouseUp(e),this.props.onTap&&this.props.onTap(e),this.endMouseEvent())},onMouseOut:function(e){!window._blockMouseEvents&&this._mouseDown&&(this.processEvent(e),this.props.onMouseOut&&this.props.onMouseOut(e),this.endMouseEvent())},endMouseEvent:function(){this.cancelPressDetection(),this._mouseDown=!1,this.setState({isActive:!1})},onKeyUp:function(e){this._keyDown&&(this.processEvent(e),this.props.onKeyUp&&this.props.onKeyUp(e),this.props.onTap&&this.props.onTap(e),this._keyDown=!1,this.cancelPressDetection(),this.setState({isActive:!1}))},onKeyDown:function(e){this.props.onKeyDown&&this.props.onKeyDown(e)===!1||e.which!==i&&e.which!==c||this._keyDown||(this.initPressDetection(e,this.endKeyEvent),this.processEvent(e),this._keyDown=!0,this.setState({isActive:!0}))},endKeyEvent:function(){this.cancelPressDetection(),this._keyDown=!1,this.setState({isActive:!1})},cancelTap:function(){this.endTouch(),this._mouseDown=!1},handlers:function(){return{onTouchStart:this.onTouchStart,onTouchMove:this.onTouchMove,onTouchEnd:this.onTouchEnd,onMouseDown:this.onMouseDown,onMouseUp:this.onMouseUp,onMouseMove:this.onMouseMove,onMouseOut:this.onMouseOut,onKeyDown:this.onKeyDown,onKeyUp:this.onKeyUp}}};t.exports=h}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],3:[function(e,t,o){(function(o){"use strict";var s=Object.assign||function(e){for(var t=1;t 0) {
96 | this._activeTimeout = setTimeout(this.makeActive, this.props.activeDelay);
97 | } else {
98 | this.makeActive();
99 | }
100 | } else if (this.onPinchStart &&
101 | (this.props.onPinchStart || this.props.onPinchMove || this.props.onPinchEnd) &&
102 | event.touches.length === 2) {
103 | this.onPinchStart(event);
104 | }
105 | },
106 |
107 | makeActive: function () {
108 | if (!this.isMounted) return;
109 | this.clearActiveTimeout();
110 | this.setState({
111 | isActive: true
112 | });
113 | },
114 |
115 | clearActiveTimeout: function () {
116 | clearTimeout(this._activeTimeout);
117 | this._activeTimeout = false;
118 | },
119 |
120 | initScrollDetection: function () {
121 | this._scrollPos = { top: 0, left: 0 };
122 | this._scrollParents = [];
123 | this._scrollParentPos = [];
124 | var node = ReactDOM.findDOMNode(this);
125 |
126 | while (node) {
127 | if (node.scrollHeight > node.offsetHeight || node.scrollWidth > node.offsetWidth) {
128 | this._scrollParents.push(node);
129 | this._scrollParentPos.push(node.scrollTop + node.scrollLeft);
130 | this._scrollPos.top += node.scrollTop;
131 | this._scrollPos.left += node.scrollLeft;
132 | }
133 |
134 | node = node.parentNode;
135 | }
136 | },
137 |
138 | initTouchmoveDetection: function () {
139 | this._touchmoveTriggeredTimes = 0;
140 | },
141 |
142 | cancelTouchmoveDetection: function () {
143 | if (this._touchmoveDetectionTimeout) {
144 | clearTimeout(this._touchmoveDetectionTimeout);
145 | this._touchmoveDetectionTimeout = null;
146 | this._touchmoveTriggeredTimes = 0;
147 | }
148 | },
149 |
150 | calculateMovement: function (touch) {
151 | return {
152 | x: Math.abs(touch.clientX - this._initialTouch.clientX),
153 | y: Math.abs(touch.clientY - this._initialTouch.clientY)
154 | };
155 | },
156 |
157 | detectScroll: function () {
158 | var currentScrollPos = { top: 0, left: 0 };
159 | for (var i = 0; i < this._scrollParents.length; i++) {
160 | currentScrollPos.top += this._scrollParents[i].scrollTop;
161 | currentScrollPos.left += this._scrollParents[i].scrollLeft;
162 | }
163 | return !(currentScrollPos.top === this._scrollPos.top && currentScrollPos.left === this._scrollPos.left);
164 | },
165 |
166 | cleanupScrollDetection: function () {
167 | this._scrollParents = undefined;
168 | this._scrollPos = undefined;
169 | },
170 |
171 | initPressDetection: function (event, callback) {
172 | if (!this.props.onPress) return;
173 |
174 | // SyntheticEvent objects are pooled, so persist the event so it can be referenced asynchronously
175 | event.persist();
176 |
177 | this._pressTimeout = setTimeout(function () {
178 | this.props.onPress(event);
179 | callback();
180 | }.bind(this), this.props.pressDelay);
181 | },
182 |
183 | cancelPressDetection: function () {
184 | clearTimeout(this._pressTimeout);
185 | },
186 |
187 | onTouchMove: function (event) {
188 | if (this._initialTouch) {
189 | this.processEvent(event);
190 |
191 | if (this.detectScroll()) {
192 | return this.endTouch(event);
193 | } else {
194 | if ((this._touchmoveTriggeredTimes)++ === 0) {
195 | this._touchmoveDetectionTimeout = setTimeout(function() {
196 | if (this._touchmoveTriggeredTimes === 1) {
197 | this.endTouch(event);
198 | }
199 | }.bind(this), 64);
200 | }
201 | }
202 |
203 | this.props.onTouchMove && this.props.onTouchMove(event);
204 | this._lastTouch = getTouchProps(event.touches[0]);
205 | var movement = this.calculateMovement(this._lastTouch);
206 | if (movement.x > this.props.pressMoveThreshold || movement.y > this.props.pressMoveThreshold) {
207 | this.cancelPressDetection();
208 | }
209 | if (movement.x > (this.props.moveXThreshold || this.props.moveThreshold) ||
210 | movement.y > (this.props.moveYThreshold || this.props.moveThreshold)) {
211 | if (this.state.isActive) {
212 | if (this.props.allowReactivation) {
213 | this.setState({
214 | isActive: false
215 | });
216 | } else {
217 | return this.endTouch(event);
218 | }
219 | } else if (this._activeTimeout) {
220 | this.clearActiveTimeout();
221 | }
222 | } else {
223 | if (!this.state.isActive && !this._activeTimeout) {
224 | this.setState({
225 | isActive: true
226 | });
227 | }
228 | }
229 | } else if (this._initialPinch && event.touches.length === 2 && this.onPinchMove) {
230 | this.onPinchMove(event);
231 | event.preventDefault();
232 | }
233 | },
234 |
235 | onTouchEnd: function (event) {
236 | if (this._initialTouch) {
237 | this.processEvent(event);
238 | var afterEndTouch;
239 | var movement = this.calculateMovement(this._lastTouch);
240 | if (movement.x <= (this.props.moveXThreshold || this.props.moveThreshold) &&
241 | movement.y <= (this.props.moveYThreshold || this.props.moveThreshold) &&
242 | this.props.onTap) {
243 | event.preventDefault();
244 | afterEndTouch = () => {
245 | var finalParentScrollPos = this._scrollParents.map(node => node.scrollTop + node.scrollLeft);
246 | var stoppedMomentumScroll = this._scrollParentPos.some((end, i) => {
247 | return end !== finalParentScrollPos[i];
248 | });
249 | if (!stoppedMomentumScroll) {
250 | this.props.onTap(event);
251 | }
252 | };
253 | }
254 | this.endTouch(event, afterEndTouch);
255 | } else if (this.onPinchEnd && this._initialPinch && (event.touches.length + event.changedTouches.length) === 2) {
256 | this.onPinchEnd(event);
257 | event.preventDefault();
258 | }
259 | },
260 |
261 | endTouch: function (event, callback) {
262 | this.cancelTouchmoveDetection();
263 | this.cancelPressDetection();
264 | this.clearActiveTimeout();
265 | if (event && this.props.onTouchEnd) {
266 | this.props.onTouchEnd(event);
267 | }
268 | this._initialTouch = null;
269 | this._lastTouch = null;
270 | if (callback) {
271 | callback();
272 | }
273 | if (this.state.isActive) {
274 | this.setState({
275 | isActive: false
276 | });
277 | }
278 | },
279 |
280 | onMouseDown: function (event) {
281 | if (window._blockMouseEvents) {
282 | window._blockMouseEvents = false;
283 | return;
284 | }
285 | if (this.props.onMouseDown && this.props.onMouseDown(event) === false) return;
286 | this.processEvent(event);
287 | this.initPressDetection(event, this.endMouseEvent);
288 | this._mouseDown = true;
289 | this.setState({
290 | isActive: true
291 | });
292 | },
293 |
294 | onMouseMove: function (event) {
295 | if (window._blockMouseEvents || !this._mouseDown) return;
296 | this.processEvent(event);
297 | this.props.onMouseMove && this.props.onMouseMove(event);
298 | },
299 |
300 | onMouseUp: function (event) {
301 | if (window._blockMouseEvents || !this._mouseDown) return;
302 | this.processEvent(event);
303 | this.props.onMouseUp && this.props.onMouseUp(event);
304 | this.props.onTap && this.props.onTap(event);
305 | this.endMouseEvent();
306 | },
307 |
308 | onMouseOut: function (event) {
309 | if (window._blockMouseEvents || !this._mouseDown) return;
310 | this.processEvent(event);
311 | this.props.onMouseOut && this.props.onMouseOut(event);
312 | this.endMouseEvent();
313 | },
314 |
315 | endMouseEvent: function () {
316 | this.cancelPressDetection();
317 | this._mouseDown = false;
318 | this.setState({
319 | isActive: false
320 | });
321 | },
322 |
323 | onKeyUp: function (event) {
324 | if (!this._keyDown) return;
325 | this.processEvent(event);
326 | this.props.onKeyUp && this.props.onKeyUp(event);
327 | this.props.onTap && this.props.onTap(event);
328 | this._keyDown = false;
329 | this.cancelPressDetection();
330 | this.setState({
331 | isActive: false
332 | });
333 | },
334 |
335 | onKeyDown: function (event) {
336 | if (this.props.onKeyDown && this.props.onKeyDown(event) === false) return;
337 | if (event.which !== SPACE_KEY && event.which !== ENTER_KEY) return;
338 | if (this._keyDown) return;
339 | this.initPressDetection(event, this.endKeyEvent);
340 | this.processEvent(event);
341 | this._keyDown = true;
342 | this.setState({
343 | isActive: true
344 | });
345 | },
346 |
347 | endKeyEvent: function () {
348 | this.cancelPressDetection();
349 | this._keyDown = false;
350 | this.setState({
351 | isActive: false
352 | });
353 | },
354 |
355 | cancelTap: function () {
356 | this.endTouch();
357 | this._mouseDown = false;
358 | },
359 |
360 | handlers: function () {
361 | return {
362 | onTouchStart: this.onTouchStart,
363 | onTouchMove: this.onTouchMove,
364 | onTouchEnd: this.onTouchEnd,
365 | onMouseDown: this.onMouseDown,
366 | onMouseUp: this.onMouseUp,
367 | onMouseMove: this.onMouseMove,
368 | onMouseOut: this.onMouseOut,
369 | onKeyDown: this.onKeyDown,
370 | onKeyUp: this.onKeyUp,
371 | };
372 | }
373 | };
374 |
375 | module.exports = Mixin;
376 |
--------------------------------------------------------------------------------
/lib/TappableMixin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var PropTypes = require('prop-types');
4 | var React = require('react');
5 | var ReactDOM = require('react-dom');
6 |
7 | var SPACE_KEY = 32;
8 | var ENTER_KEY = 13;
9 |
10 | function getTouchProps(touch) {
11 | if (!touch) return {};
12 | return {
13 | pageX: touch.pageX,
14 | pageY: touch.pageY,
15 | clientX: touch.clientX,
16 | clientY: touch.clientY
17 | };
18 | }
19 |
20 | var Mixin = {
21 | propTypes: {
22 | moveThreshold: PropTypes.number, // pixels to move before cancelling tap
23 | moveXThreshold: PropTypes.number, // pixels on the x axis to move before cancelling tap (overrides moveThreshold)
24 | moveYThreshold: PropTypes.number, // pixels on the y axis to move before cancelling tap (overrides moveThreshold)
25 | allowReactivation: PropTypes.bool, // after moving outside of the moveThreshold will you allow
26 | // reactivation by moving back within the moveThreshold?
27 | activeDelay: PropTypes.number, // ms to wait before adding the `-active` class
28 | pressDelay: PropTypes.number, // ms to wait before detecting a press
29 | pressMoveThreshold: PropTypes.number, // pixels to move before cancelling press
30 | preventDefault: PropTypes.bool, // whether to preventDefault on all events
31 | stopPropagation: PropTypes.bool, // whether to stopPropagation on all events
32 |
33 | onTap: PropTypes.func, // fires when a tap is detected
34 | onPress: PropTypes.func, // fires when a press is detected
35 | onTouchStart: PropTypes.func, // pass-through touch event
36 | onTouchMove: PropTypes.func, // pass-through touch event
37 | onTouchEnd: PropTypes.func, // pass-through touch event
38 | onMouseDown: PropTypes.func, // pass-through mouse event
39 | onMouseUp: PropTypes.func, // pass-through mouse event
40 | onMouseMove: PropTypes.func, // pass-through mouse event
41 | onMouseOut: PropTypes.func, // pass-through mouse event
42 | onKeyDown: PropTypes.func, // pass-through key event
43 | onKeyUp: PropTypes.func // pass-through key event
44 | },
45 |
46 | getDefaultProps: function getDefaultProps() {
47 | return {
48 | activeDelay: 0,
49 | allowReactivation: true,
50 | moveThreshold: 100,
51 | pressDelay: 1000,
52 | pressMoveThreshold: 5
53 | };
54 | },
55 |
56 | getInitialState: function getInitialState() {
57 | return {
58 | isActive: false,
59 | touchActive: false,
60 | pinchActive: false
61 | };
62 | },
63 |
64 | componentDidMount: function componentDidMount() {
65 | this.isMounted = true;
66 | },
67 |
68 | componentWillUnmount: function componentWillUnmount() {
69 | this.isMounted = false;
70 | this.cleanupScrollDetection();
71 | this.cancelPressDetection();
72 | this.clearActiveTimeout();
73 | },
74 |
75 | componentWillUpdate: function componentWillUpdate(nextProps, nextState) {
76 | if (this.state.isActive && !nextState.isActive) {
77 | this.props.onDeactivate && this.props.onDeactivate();
78 | } else if (!this.state.isActive && nextState.isActive) {
79 | this.props.onReactivate && this.props.onReactivate();
80 | }
81 | },
82 |
83 | processEvent: function processEvent(event) {
84 | if (this.props.preventDefault) event.preventDefault();
85 | if (this.props.stopPropagation) event.stopPropagation();
86 | },
87 |
88 | onTouchStart: function onTouchStart(event) {
89 | if (this.props.onTouchStart && this.props.onTouchStart(event) === false) return;
90 | this.processEvent(event);
91 | window._blockMouseEvents = true;
92 | if (event.touches.length === 1) {
93 | this._initialTouch = this._lastTouch = getTouchProps(event.touches[0]);
94 | this.initScrollDetection();
95 | this.initPressDetection(event, this.endTouch);
96 | this.initTouchmoveDetection();
97 | if (this.props.activeDelay > 0) {
98 | this._activeTimeout = setTimeout(this.makeActive, this.props.activeDelay);
99 | } else {
100 | this.makeActive();
101 | }
102 | } else if (this.onPinchStart && (this.props.onPinchStart || this.props.onPinchMove || this.props.onPinchEnd) && event.touches.length === 2) {
103 | this.onPinchStart(event);
104 | }
105 | },
106 |
107 | makeActive: function makeActive() {
108 | if (!this.isMounted) return;
109 | this.clearActiveTimeout();
110 | this.setState({
111 | isActive: true
112 | });
113 | },
114 |
115 | clearActiveTimeout: function clearActiveTimeout() {
116 | clearTimeout(this._activeTimeout);
117 | this._activeTimeout = false;
118 | },
119 |
120 | initScrollDetection: function initScrollDetection() {
121 | this._scrollPos = { top: 0, left: 0 };
122 | this._scrollParents = [];
123 | this._scrollParentPos = [];
124 | var node = ReactDOM.findDOMNode(this);
125 |
126 | while (node) {
127 | if (node.scrollHeight > node.offsetHeight || node.scrollWidth > node.offsetWidth) {
128 | this._scrollParents.push(node);
129 | this._scrollParentPos.push(node.scrollTop + node.scrollLeft);
130 | this._scrollPos.top += node.scrollTop;
131 | this._scrollPos.left += node.scrollLeft;
132 | }
133 |
134 | node = node.parentNode;
135 | }
136 | },
137 |
138 | initTouchmoveDetection: function initTouchmoveDetection() {
139 | this._touchmoveTriggeredTimes = 0;
140 | },
141 |
142 | cancelTouchmoveDetection: function cancelTouchmoveDetection() {
143 | if (this._touchmoveDetectionTimeout) {
144 | clearTimeout(this._touchmoveDetectionTimeout);
145 | this._touchmoveDetectionTimeout = null;
146 | this._touchmoveTriggeredTimes = 0;
147 | }
148 | },
149 |
150 | calculateMovement: function calculateMovement(touch) {
151 | return {
152 | x: Math.abs(touch.clientX - this._initialTouch.clientX),
153 | y: Math.abs(touch.clientY - this._initialTouch.clientY)
154 | };
155 | },
156 |
157 | detectScroll: function detectScroll() {
158 | var currentScrollPos = { top: 0, left: 0 };
159 | for (var i = 0; i < this._scrollParents.length; i++) {
160 | currentScrollPos.top += this._scrollParents[i].scrollTop;
161 | currentScrollPos.left += this._scrollParents[i].scrollLeft;
162 | }
163 | return !(currentScrollPos.top === this._scrollPos.top && currentScrollPos.left === this._scrollPos.left);
164 | },
165 |
166 | cleanupScrollDetection: function cleanupScrollDetection() {
167 | this._scrollParents = undefined;
168 | this._scrollPos = undefined;
169 | },
170 |
171 | initPressDetection: function initPressDetection(event, callback) {
172 | if (!this.props.onPress) return;
173 |
174 | // SyntheticEvent objects are pooled, so persist the event so it can be referenced asynchronously
175 | event.persist();
176 |
177 | this._pressTimeout = setTimeout(function () {
178 | this.props.onPress(event);
179 | callback();
180 | }.bind(this), this.props.pressDelay);
181 | },
182 |
183 | cancelPressDetection: function cancelPressDetection() {
184 | clearTimeout(this._pressTimeout);
185 | },
186 |
187 | onTouchMove: function onTouchMove(event) {
188 | if (this._initialTouch) {
189 | this.processEvent(event);
190 |
191 | if (this.detectScroll()) {
192 | return this.endTouch(event);
193 | } else {
194 | if (this._touchmoveTriggeredTimes++ === 0) {
195 | this._touchmoveDetectionTimeout = setTimeout(function () {
196 | if (this._touchmoveTriggeredTimes === 1) {
197 | this.endTouch(event);
198 | }
199 | }.bind(this), 64);
200 | }
201 | }
202 |
203 | this.props.onTouchMove && this.props.onTouchMove(event);
204 | this._lastTouch = getTouchProps(event.touches[0]);
205 | var movement = this.calculateMovement(this._lastTouch);
206 | if (movement.x > this.props.pressMoveThreshold || movement.y > this.props.pressMoveThreshold) {
207 | this.cancelPressDetection();
208 | }
209 | if (movement.x > (this.props.moveXThreshold || this.props.moveThreshold) || movement.y > (this.props.moveYThreshold || this.props.moveThreshold)) {
210 | if (this.state.isActive) {
211 | if (this.props.allowReactivation) {
212 | this.setState({
213 | isActive: false
214 | });
215 | } else {
216 | return this.endTouch(event);
217 | }
218 | } else if (this._activeTimeout) {
219 | this.clearActiveTimeout();
220 | }
221 | } else {
222 | if (!this.state.isActive && !this._activeTimeout) {
223 | this.setState({
224 | isActive: true
225 | });
226 | }
227 | }
228 | } else if (this._initialPinch && event.touches.length === 2 && this.onPinchMove) {
229 | this.onPinchMove(event);
230 | event.preventDefault();
231 | }
232 | },
233 |
234 | onTouchEnd: function onTouchEnd(event) {
235 | var _this = this;
236 |
237 | if (this._initialTouch) {
238 | this.processEvent(event);
239 | var afterEndTouch;
240 | var movement = this.calculateMovement(this._lastTouch);
241 | if (movement.x <= (this.props.moveXThreshold || this.props.moveThreshold) && movement.y <= (this.props.moveYThreshold || this.props.moveThreshold) && this.props.onTap) {
242 | event.preventDefault();
243 | afterEndTouch = function afterEndTouch() {
244 | var finalParentScrollPos = _this._scrollParents.map(function (node) {
245 | return node.scrollTop + node.scrollLeft;
246 | });
247 | var stoppedMomentumScroll = _this._scrollParentPos.some(function (end, i) {
248 | return end !== finalParentScrollPos[i];
249 | });
250 | if (!stoppedMomentumScroll) {
251 | _this.props.onTap(event);
252 | }
253 | };
254 | }
255 | this.endTouch(event, afterEndTouch);
256 | } else if (this.onPinchEnd && this._initialPinch && event.touches.length + event.changedTouches.length === 2) {
257 | this.onPinchEnd(event);
258 | event.preventDefault();
259 | }
260 | },
261 |
262 | endTouch: function endTouch(event, callback) {
263 | this.cancelTouchmoveDetection();
264 | this.cancelPressDetection();
265 | this.clearActiveTimeout();
266 | if (event && this.props.onTouchEnd) {
267 | this.props.onTouchEnd(event);
268 | }
269 | this._initialTouch = null;
270 | this._lastTouch = null;
271 | if (callback) {
272 | callback();
273 | }
274 | if (this.state.isActive) {
275 | this.setState({
276 | isActive: false
277 | });
278 | }
279 | },
280 |
281 | onMouseDown: function onMouseDown(event) {
282 | if (window._blockMouseEvents) {
283 | window._blockMouseEvents = false;
284 | return;
285 | }
286 | if (this.props.onMouseDown && this.props.onMouseDown(event) === false) return;
287 | this.processEvent(event);
288 | this.initPressDetection(event, this.endMouseEvent);
289 | this._mouseDown = true;
290 | this.setState({
291 | isActive: true
292 | });
293 | },
294 |
295 | onMouseMove: function onMouseMove(event) {
296 | if (window._blockMouseEvents || !this._mouseDown) return;
297 | this.processEvent(event);
298 | this.props.onMouseMove && this.props.onMouseMove(event);
299 | },
300 |
301 | onMouseUp: function onMouseUp(event) {
302 | if (window._blockMouseEvents || !this._mouseDown) return;
303 | this.processEvent(event);
304 | this.props.onMouseUp && this.props.onMouseUp(event);
305 | this.props.onTap && this.props.onTap(event);
306 | this.endMouseEvent();
307 | },
308 |
309 | onMouseOut: function onMouseOut(event) {
310 | if (window._blockMouseEvents || !this._mouseDown) return;
311 | this.processEvent(event);
312 | this.props.onMouseOut && this.props.onMouseOut(event);
313 | this.endMouseEvent();
314 | },
315 |
316 | endMouseEvent: function endMouseEvent() {
317 | this.cancelPressDetection();
318 | this._mouseDown = false;
319 | this.setState({
320 | isActive: false
321 | });
322 | },
323 |
324 | onKeyUp: function onKeyUp(event) {
325 | if (!this._keyDown) return;
326 | this.processEvent(event);
327 | this.props.onKeyUp && this.props.onKeyUp(event);
328 | this.props.onTap && this.props.onTap(event);
329 | this._keyDown = false;
330 | this.cancelPressDetection();
331 | this.setState({
332 | isActive: false
333 | });
334 | },
335 |
336 | onKeyDown: function onKeyDown(event) {
337 | if (this.props.onKeyDown && this.props.onKeyDown(event) === false) return;
338 | if (event.which !== SPACE_KEY && event.which !== ENTER_KEY) return;
339 | if (this._keyDown) return;
340 | this.initPressDetection(event, this.endKeyEvent);
341 | this.processEvent(event);
342 | this._keyDown = true;
343 | this.setState({
344 | isActive: true
345 | });
346 | },
347 |
348 | endKeyEvent: function endKeyEvent() {
349 | this.cancelPressDetection();
350 | this._keyDown = false;
351 | this.setState({
352 | isActive: false
353 | });
354 | },
355 |
356 | cancelTap: function cancelTap() {
357 | this.endTouch();
358 | this._mouseDown = false;
359 | },
360 |
361 | handlers: function handlers() {
362 | return {
363 | onTouchStart: this.onTouchStart,
364 | onTouchMove: this.onTouchMove,
365 | onTouchEnd: this.onTouchEnd,
366 | onMouseDown: this.onMouseDown,
367 | onMouseUp: this.onMouseUp,
368 | onMouseMove: this.onMouseMove,
369 | onMouseOut: this.onMouseOut,
370 | onKeyDown: this.onKeyDown,
371 | onKeyUp: this.onKeyUp
372 | };
373 | }
374 | };
375 |
376 | module.exports = Mixin;
--------------------------------------------------------------------------------
/example/dist/pinch.js:
--------------------------------------------------------------------------------
1 | require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;oHello World;
141 | * }
142 | * });
143 | *
144 | * The class specification supports a specific protocol of methods that have
145 | * special meaning (e.g. `render`). See `ReactClassInterface` for
146 | * more the comprehensive protocol. Any other properties and methods in the
147 | * class specification will be available on the prototype.
148 | *
149 | * @interface ReactClassInterface
150 | * @internal
151 | */
152 | var ReactClassInterface = {
153 | /**
154 | * An array of Mixin objects to include when defining your component.
155 | *
156 | * @type {array}
157 | * @optional
158 | */
159 | mixins: 'DEFINE_MANY',
160 |
161 | /**
162 | * An object containing properties and methods that should be defined on
163 | * the component's constructor instead of its prototype (static methods).
164 | *
165 | * @type {object}
166 | * @optional
167 | */
168 | statics: 'DEFINE_MANY',
169 |
170 | /**
171 | * Definition of prop types for this component.
172 | *
173 | * @type {object}
174 | * @optional
175 | */
176 | propTypes: 'DEFINE_MANY',
177 |
178 | /**
179 | * Definition of context types for this component.
180 | *
181 | * @type {object}
182 | * @optional
183 | */
184 | contextTypes: 'DEFINE_MANY',
185 |
186 | /**
187 | * Definition of context types this component sets for its children.
188 | *
189 | * @type {object}
190 | * @optional
191 | */
192 | childContextTypes: 'DEFINE_MANY',
193 |
194 | // ==== Definition methods ====
195 |
196 | /**
197 | * Invoked when the component is mounted. Values in the mapping will be set on
198 | * `this.props` if that prop is not specified (i.e. using an `in` check).
199 | *
200 | * This method is invoked before `getInitialState` and therefore cannot rely
201 | * on `this.state` or use `this.setState`.
202 | *
203 | * @return {object}
204 | * @optional
205 | */
206 | getDefaultProps: 'DEFINE_MANY_MERGED',
207 |
208 | /**
209 | * Invoked once before the component is mounted. The return value will be used
210 | * as the initial value of `this.state`.
211 | *
212 | * getInitialState: function() {
213 | * return {
214 | * isOn: false,
215 | * fooBaz: new BazFoo()
216 | * }
217 | * }
218 | *
219 | * @return {object}
220 | * @optional
221 | */
222 | getInitialState: 'DEFINE_MANY_MERGED',
223 |
224 | /**
225 | * @return {object}
226 | * @optional
227 | */
228 | getChildContext: 'DEFINE_MANY_MERGED',
229 |
230 | /**
231 | * Uses props from `this.props` and state from `this.state` to render the
232 | * structure of the component.
233 | *
234 | * No guarantees are made about when or how often this method is invoked, so
235 | * it must not have side effects.
236 | *
237 | * render: function() {
238 | * var name = this.props.name;
239 | * return Hello, {name}!
;
240 | * }
241 | *
242 | * @return {ReactComponent}
243 | * @required
244 | */
245 | render: 'DEFINE_ONCE',
246 |
247 | // ==== Delegate methods ====
248 |
249 | /**
250 | * Invoked when the component is initially created and about to be mounted.
251 | * This may have side effects, but any external subscriptions or data created
252 | * by this method must be cleaned up in `componentWillUnmount`.
253 | *
254 | * @optional
255 | */
256 | componentWillMount: 'DEFINE_MANY',
257 |
258 | /**
259 | * Invoked when the component has been mounted and has a DOM representation.
260 | * However, there is no guarantee that the DOM node is in the document.
261 | *
262 | * Use this as an opportunity to operate on the DOM when the component has
263 | * been mounted (initialized and rendered) for the first time.
264 | *
265 | * @param {DOMElement} rootNode DOM element representing the component.
266 | * @optional
267 | */
268 | componentDidMount: 'DEFINE_MANY',
269 |
270 | /**
271 | * Invoked before the component receives new props.
272 | *
273 | * Use this as an opportunity to react to a prop transition by updating the
274 | * state using `this.setState`. Current props are accessed via `this.props`.
275 | *
276 | * componentWillReceiveProps: function(nextProps, nextContext) {
277 | * this.setState({
278 | * likesIncreasing: nextProps.likeCount > this.props.likeCount
279 | * });
280 | * }
281 | *
282 | * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
283 | * transition may cause a state change, but the opposite is not true. If you
284 | * need it, you are probably looking for `componentWillUpdate`.
285 | *
286 | * @param {object} nextProps
287 | * @optional
288 | */
289 | componentWillReceiveProps: 'DEFINE_MANY',
290 |
291 | /**
292 | * Invoked while deciding if the component should be updated as a result of
293 | * receiving new props, state and/or context.
294 | *
295 | * Use this as an opportunity to `return false` when you're certain that the
296 | * transition to the new props/state/context will not require a component
297 | * update.
298 | *
299 | * shouldComponentUpdate: function(nextProps, nextState, nextContext) {
300 | * return !equal(nextProps, this.props) ||
301 | * !equal(nextState, this.state) ||
302 | * !equal(nextContext, this.context);
303 | * }
304 | *
305 | * @param {object} nextProps
306 | * @param {?object} nextState
307 | * @param {?object} nextContext
308 | * @return {boolean} True if the component should update.
309 | * @optional
310 | */
311 | shouldComponentUpdate: 'DEFINE_ONCE',
312 |
313 | /**
314 | * Invoked when the component is about to update due to a transition from
315 | * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
316 | * and `nextContext`.
317 | *
318 | * Use this as an opportunity to perform preparation before an update occurs.
319 | *
320 | * NOTE: You **cannot** use `this.setState()` in this method.
321 | *
322 | * @param {object} nextProps
323 | * @param {?object} nextState
324 | * @param {?object} nextContext
325 | * @param {ReactReconcileTransaction} transaction
326 | * @optional
327 | */
328 | componentWillUpdate: 'DEFINE_MANY',
329 |
330 | /**
331 | * Invoked when the component's DOM representation has been updated.
332 | *
333 | * Use this as an opportunity to operate on the DOM when the component has
334 | * been updated.
335 | *
336 | * @param {object} prevProps
337 | * @param {?object} prevState
338 | * @param {?object} prevContext
339 | * @param {DOMElement} rootNode DOM element representing the component.
340 | * @optional
341 | */
342 | componentDidUpdate: 'DEFINE_MANY',
343 |
344 | /**
345 | * Invoked when the component is about to be removed from its parent and have
346 | * its DOM representation destroyed.
347 | *
348 | * Use this as an opportunity to deallocate any external resources.
349 | *
350 | * NOTE: There is no `componentDidUnmount` since your component will have been
351 | * destroyed by that point.
352 | *
353 | * @optional
354 | */
355 | componentWillUnmount: 'DEFINE_MANY',
356 |
357 | // ==== Advanced methods ====
358 |
359 | /**
360 | * Updates the component's currently mounted DOM representation.
361 | *
362 | * By default, this implements React's rendering and reconciliation algorithm.
363 | * Sophisticated clients may wish to override this.
364 | *
365 | * @param {ReactReconcileTransaction} transaction
366 | * @internal
367 | * @overridable
368 | */
369 | updateComponent: 'OVERRIDE_BASE'
370 | };
371 |
372 | /**
373 | * Mapping from class specification keys to special processing functions.
374 | *
375 | * Although these are declared like instance properties in the specification
376 | * when defining classes using `React.createClass`, they are actually static
377 | * and are accessible on the constructor instead of the prototype. Despite
378 | * being static, they must be defined outside of the "statics" key under
379 | * which all other static methods are defined.
380 | */
381 | var RESERVED_SPEC_KEYS = {
382 | displayName: function(Constructor, displayName) {
383 | Constructor.displayName = displayName;
384 | },
385 | mixins: function(Constructor, mixins) {
386 | if (mixins) {
387 | for (var i = 0; i < mixins.length; i++) {
388 | mixSpecIntoComponent(Constructor, mixins[i]);
389 | }
390 | }
391 | },
392 | childContextTypes: function(Constructor, childContextTypes) {
393 | if ("production" !== 'production') {
394 | validateTypeDef(Constructor, childContextTypes, 'childContext');
395 | }
396 | Constructor.childContextTypes = _assign(
397 | {},
398 | Constructor.childContextTypes,
399 | childContextTypes
400 | );
401 | },
402 | contextTypes: function(Constructor, contextTypes) {
403 | if ("production" !== 'production') {
404 | validateTypeDef(Constructor, contextTypes, 'context');
405 | }
406 | Constructor.contextTypes = _assign(
407 | {},
408 | Constructor.contextTypes,
409 | contextTypes
410 | );
411 | },
412 | /**
413 | * Special case getDefaultProps which should move into statics but requires
414 | * automatic merging.
415 | */
416 | getDefaultProps: function(Constructor, getDefaultProps) {
417 | if (Constructor.getDefaultProps) {
418 | Constructor.getDefaultProps = createMergedResultFunction(
419 | Constructor.getDefaultProps,
420 | getDefaultProps
421 | );
422 | } else {
423 | Constructor.getDefaultProps = getDefaultProps;
424 | }
425 | },
426 | propTypes: function(Constructor, propTypes) {
427 | if ("production" !== 'production') {
428 | validateTypeDef(Constructor, propTypes, 'prop');
429 | }
430 | Constructor.propTypes = _assign({}, Constructor.propTypes, propTypes);
431 | },
432 | statics: function(Constructor, statics) {
433 | mixStaticSpecIntoComponent(Constructor, statics);
434 | },
435 | autobind: function() {}
436 | };
437 |
438 | function validateTypeDef(Constructor, typeDef, location) {
439 | for (var propName in typeDef) {
440 | if (typeDef.hasOwnProperty(propName)) {
441 | // use a warning instead of an _invariant so components
442 | // don't show up in prod but only in __DEV__
443 | if ("production" !== 'production') {
444 | warning(
445 | typeof typeDef[propName] === 'function',
446 | '%s: %s type `%s` is invalid; it must be a function, usually from ' +
447 | 'React.PropTypes.',
448 | Constructor.displayName || 'ReactClass',
449 | ReactPropTypeLocationNames[location],
450 | propName
451 | );
452 | }
453 | }
454 | }
455 | }
456 |
457 | function validateMethodOverride(isAlreadyDefined, name) {
458 | var specPolicy = ReactClassInterface.hasOwnProperty(name)
459 | ? ReactClassInterface[name]
460 | : null;
461 |
462 | // Disallow overriding of base class methods unless explicitly allowed.
463 | if (ReactClassMixin.hasOwnProperty(name)) {
464 | _invariant(
465 | specPolicy === 'OVERRIDE_BASE',
466 | 'ReactClassInterface: You are attempting to override ' +
467 | '`%s` from your class specification. Ensure that your method names ' +
468 | 'do not overlap with React methods.',
469 | name
470 | );
471 | }
472 |
473 | // Disallow defining methods more than once unless explicitly allowed.
474 | if (isAlreadyDefined) {
475 | _invariant(
476 | specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED',
477 | 'ReactClassInterface: You are attempting to define ' +
478 | '`%s` on your component more than once. This conflict may be due ' +
479 | 'to a mixin.',
480 | name
481 | );
482 | }
483 | }
484 |
485 | /**
486 | * Mixin helper which handles policy validation and reserved
487 | * specification keys when building React classes.
488 | */
489 | function mixSpecIntoComponent(Constructor, spec) {
490 | if (!spec) {
491 | if ("production" !== 'production') {
492 | var typeofSpec = typeof spec;
493 | var isMixinValid = typeofSpec === 'object' && spec !== null;
494 |
495 | if ("production" !== 'production') {
496 | warning(
497 | isMixinValid,
498 | "%s: You're attempting to include a mixin that is either null " +
499 | 'or not an object. Check the mixins included by the component, ' +
500 | 'as well as any mixins they include themselves. ' +
501 | 'Expected object but got %s.',
502 | Constructor.displayName || 'ReactClass',
503 | spec === null ? null : typeofSpec
504 | );
505 | }
506 | }
507 |
508 | return;
509 | }
510 |
511 | _invariant(
512 | typeof spec !== 'function',
513 | "ReactClass: You're attempting to " +
514 | 'use a component class or function as a mixin. Instead, just use a ' +
515 | 'regular object.'
516 | );
517 | _invariant(
518 | !isValidElement(spec),
519 | "ReactClass: You're attempting to " +
520 | 'use a component as a mixin. Instead, just use a regular object.'
521 | );
522 |
523 | var proto = Constructor.prototype;
524 | var autoBindPairs = proto.__reactAutoBindPairs;
525 |
526 | // By handling mixins before any other properties, we ensure the same
527 | // chaining order is applied to methods with DEFINE_MANY policy, whether
528 | // mixins are listed before or after these methods in the spec.
529 | if (spec.hasOwnProperty(MIXINS_KEY)) {
530 | RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
531 | }
532 |
533 | for (var name in spec) {
534 | if (!spec.hasOwnProperty(name)) {
535 | continue;
536 | }
537 |
538 | if (name === MIXINS_KEY) {
539 | // We have already handled mixins in a special case above.
540 | continue;
541 | }
542 |
543 | var property = spec[name];
544 | var isAlreadyDefined = proto.hasOwnProperty(name);
545 | validateMethodOverride(isAlreadyDefined, name);
546 |
547 | if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
548 | RESERVED_SPEC_KEYS[name](Constructor, property);
549 | } else {
550 | // Setup methods on prototype:
551 | // The following member methods should not be automatically bound:
552 | // 1. Expected ReactClass methods (in the "interface").
553 | // 2. Overridden methods (that were mixed in).
554 | var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
555 | var isFunction = typeof property === 'function';
556 | var shouldAutoBind =
557 | isFunction &&
558 | !isReactClassMethod &&
559 | !isAlreadyDefined &&
560 | spec.autobind !== false;
561 |
562 | if (shouldAutoBind) {
563 | autoBindPairs.push(name, property);
564 | proto[name] = property;
565 | } else {
566 | if (isAlreadyDefined) {
567 | var specPolicy = ReactClassInterface[name];
568 |
569 | // These cases should already be caught by validateMethodOverride.
570 | _invariant(
571 | isReactClassMethod &&
572 | (specPolicy === 'DEFINE_MANY_MERGED' ||
573 | specPolicy === 'DEFINE_MANY'),
574 | 'ReactClass: Unexpected spec policy %s for key %s ' +
575 | 'when mixing in component specs.',
576 | specPolicy,
577 | name
578 | );
579 |
580 | // For methods which are defined more than once, call the existing
581 | // methods before calling the new property, merging if appropriate.
582 | if (specPolicy === 'DEFINE_MANY_MERGED') {
583 | proto[name] = createMergedResultFunction(proto[name], property);
584 | } else if (specPolicy === 'DEFINE_MANY') {
585 | proto[name] = createChainedFunction(proto[name], property);
586 | }
587 | } else {
588 | proto[name] = property;
589 | if ("production" !== 'production') {
590 | // Add verbose displayName to the function, which helps when looking
591 | // at profiling tools.
592 | if (typeof property === 'function' && spec.displayName) {
593 | proto[name].displayName = spec.displayName + '_' + name;
594 | }
595 | }
596 | }
597 | }
598 | }
599 | }
600 | }
601 |
602 | function mixStaticSpecIntoComponent(Constructor, statics) {
603 | if (!statics) {
604 | return;
605 | }
606 | for (var name in statics) {
607 | var property = statics[name];
608 | if (!statics.hasOwnProperty(name)) {
609 | continue;
610 | }
611 |
612 | var isReserved = name in RESERVED_SPEC_KEYS;
613 | _invariant(
614 | !isReserved,
615 | 'ReactClass: You are attempting to define a reserved ' +
616 | 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' +
617 | 'as an instance property instead; it will still be accessible on the ' +
618 | 'constructor.',
619 | name
620 | );
621 |
622 | var isInherited = name in Constructor;
623 | _invariant(
624 | !isInherited,
625 | 'ReactClass: You are attempting to define ' +
626 | '`%s` on your component more than once. This conflict may be ' +
627 | 'due to a mixin.',
628 | name
629 | );
630 | Constructor[name] = property;
631 | }
632 | }
633 |
634 | /**
635 | * Merge two objects, but throw if both contain the same key.
636 | *
637 | * @param {object} one The first object, which is mutated.
638 | * @param {object} two The second object
639 | * @return {object} one after it has been mutated to contain everything in two.
640 | */
641 | function mergeIntoWithNoDuplicateKeys(one, two) {
642 | _invariant(
643 | one && two && typeof one === 'object' && typeof two === 'object',
644 | 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'
645 | );
646 |
647 | for (var key in two) {
648 | if (two.hasOwnProperty(key)) {
649 | _invariant(
650 | one[key] === undefined,
651 | 'mergeIntoWithNoDuplicateKeys(): ' +
652 | 'Tried to merge two objects with the same key: `%s`. This conflict ' +
653 | 'may be due to a mixin; in particular, this may be caused by two ' +
654 | 'getInitialState() or getDefaultProps() methods returning objects ' +
655 | 'with clashing keys.',
656 | key
657 | );
658 | one[key] = two[key];
659 | }
660 | }
661 | return one;
662 | }
663 |
664 | /**
665 | * Creates a function that invokes two functions and merges their return values.
666 | *
667 | * @param {function} one Function to invoke first.
668 | * @param {function} two Function to invoke second.
669 | * @return {function} Function that invokes the two argument functions.
670 | * @private
671 | */
672 | function createMergedResultFunction(one, two) {
673 | return function mergedResult() {
674 | var a = one.apply(this, arguments);
675 | var b = two.apply(this, arguments);
676 | if (a == null) {
677 | return b;
678 | } else if (b == null) {
679 | return a;
680 | }
681 | var c = {};
682 | mergeIntoWithNoDuplicateKeys(c, a);
683 | mergeIntoWithNoDuplicateKeys(c, b);
684 | return c;
685 | };
686 | }
687 |
688 | /**
689 | * Creates a function that invokes two functions and ignores their return vales.
690 | *
691 | * @param {function} one Function to invoke first.
692 | * @param {function} two Function to invoke second.
693 | * @return {function} Function that invokes the two argument functions.
694 | * @private
695 | */
696 | function createChainedFunction(one, two) {
697 | return function chainedFunction() {
698 | one.apply(this, arguments);
699 | two.apply(this, arguments);
700 | };
701 | }
702 |
703 | /**
704 | * Binds a method to the component.
705 | *
706 | * @param {object} component Component whose method is going to be bound.
707 | * @param {function} method Method to be bound.
708 | * @return {function} The bound method.
709 | */
710 | function bindAutoBindMethod(component, method) {
711 | var boundMethod = method.bind(component);
712 | if ("production" !== 'production') {
713 | boundMethod.__reactBoundContext = component;
714 | boundMethod.__reactBoundMethod = method;
715 | boundMethod.__reactBoundArguments = null;
716 | var componentName = component.constructor.displayName;
717 | var _bind = boundMethod.bind;
718 | boundMethod.bind = function(newThis) {
719 | for (
720 | var _len = arguments.length,
721 | args = Array(_len > 1 ? _len - 1 : 0),
722 | _key = 1;
723 | _key < _len;
724 | _key++
725 | ) {
726 | args[_key - 1] = arguments[_key];
727 | }
728 |
729 | // User is trying to bind() an autobound method; we effectively will
730 | // ignore the value of "this" that the user is trying to use, so
731 | // let's warn.
732 | if (newThis !== component && newThis !== null) {
733 | if ("production" !== 'production') {
734 | warning(
735 | false,
736 | 'bind(): React component methods may only be bound to the ' +
737 | 'component instance. See %s',
738 | componentName
739 | );
740 | }
741 | } else if (!args.length) {
742 | if ("production" !== 'production') {
743 | warning(
744 | false,
745 | 'bind(): You are binding a component method to the component. ' +
746 | 'React does this for you automatically in a high-performance ' +
747 | 'way, so you can safely remove this call. See %s',
748 | componentName
749 | );
750 | }
751 | return boundMethod;
752 | }
753 | var reboundMethod = _bind.apply(boundMethod, arguments);
754 | reboundMethod.__reactBoundContext = component;
755 | reboundMethod.__reactBoundMethod = method;
756 | reboundMethod.__reactBoundArguments = args;
757 | return reboundMethod;
758 | };
759 | }
760 | return boundMethod;
761 | }
762 |
763 | /**
764 | * Binds all auto-bound methods in a component.
765 | *
766 | * @param {object} component Component whose method is going to be bound.
767 | */
768 | function bindAutoBindMethods(component) {
769 | var pairs = component.__reactAutoBindPairs;
770 | for (var i = 0; i < pairs.length; i += 2) {
771 | var autoBindKey = pairs[i];
772 | var method = pairs[i + 1];
773 | component[autoBindKey] = bindAutoBindMethod(component, method);
774 | }
775 | }
776 |
777 | var IsMountedPreMixin = {
778 | componentDidMount: function() {
779 | this.__isMounted = true;
780 | }
781 | };
782 |
783 | var IsMountedPostMixin = {
784 | componentWillUnmount: function() {
785 | this.__isMounted = false;
786 | }
787 | };
788 |
789 | /**
790 | * Add more to the ReactClass base class. These are all legacy features and
791 | * therefore not already part of the modern ReactComponent.
792 | */
793 | var ReactClassMixin = {
794 | /**
795 | * TODO: This will be deprecated because state should always keep a consistent
796 | * type signature and the only use case for this, is to avoid that.
797 | */
798 | replaceState: function(newState, callback) {
799 | this.updater.enqueueReplaceState(this, newState, callback);
800 | },
801 |
802 | /**
803 | * Checks whether or not this composite component is mounted.
804 | * @return {boolean} True if mounted, false otherwise.
805 | * @protected
806 | * @final
807 | */
808 | isMounted: function() {
809 | if ("production" !== 'production') {
810 | warning(
811 | this.__didWarnIsMounted,
812 | '%s: isMounted is deprecated. Instead, make sure to clean up ' +
813 | 'subscriptions and pending requests in componentWillUnmount to ' +
814 | 'prevent memory leaks.',
815 | (this.constructor && this.constructor.displayName) ||
816 | this.name ||
817 | 'Component'
818 | );
819 | this.__didWarnIsMounted = true;
820 | }
821 | return !!this.__isMounted;
822 | }
823 | };
824 |
825 | var ReactClassComponent = function() {};
826 | _assign(
827 | ReactClassComponent.prototype,
828 | ReactComponent.prototype,
829 | ReactClassMixin
830 | );
831 |
832 | /**
833 | * Creates a composite component class given a class specification.
834 | * See https://facebook.github.io/react/docs/top-level-api.html#react.createclass
835 | *
836 | * @param {object} spec Class specification (which must define `render`).
837 | * @return {function} Component constructor function.
838 | * @public
839 | */
840 | function createClass(spec) {
841 | // To keep our warnings more understandable, we'll use a little hack here to
842 | // ensure that Constructor.name !== 'Constructor'. This makes sure we don't
843 | // unnecessarily identify a class without displayName as 'Constructor'.
844 | var Constructor = identity(function(props, context, updater) {
845 | // This constructor gets overridden by mocks. The argument is used
846 | // by mocks to assert on what gets mounted.
847 |
848 | if ("production" !== 'production') {
849 | warning(
850 | this instanceof Constructor,
851 | 'Something is calling a React component directly. Use a factory or ' +
852 | 'JSX instead. See: https://fb.me/react-legacyfactory'
853 | );
854 | }
855 |
856 | // Wire up auto-binding
857 | if (this.__reactAutoBindPairs.length) {
858 | bindAutoBindMethods(this);
859 | }
860 |
861 | this.props = props;
862 | this.context = context;
863 | this.refs = emptyObject;
864 | this.updater = updater || ReactNoopUpdateQueue;
865 |
866 | this.state = null;
867 |
868 | // ReactClasses doesn't have constructors. Instead, they use the
869 | // getInitialState and componentWillMount methods for initialization.
870 |
871 | var initialState = this.getInitialState ? this.getInitialState() : null;
872 | if ("production" !== 'production') {
873 | // We allow auto-mocks to proceed as if they're returning null.
874 | if (
875 | initialState === undefined &&
876 | this.getInitialState._isMockFunction
877 | ) {
878 | // This is probably bad practice. Consider warning here and
879 | // deprecating this convenience.
880 | initialState = null;
881 | }
882 | }
883 | _invariant(
884 | typeof initialState === 'object' && !Array.isArray(initialState),
885 | '%s.getInitialState(): must return an object or null',
886 | Constructor.displayName || 'ReactCompositeComponent'
887 | );
888 |
889 | this.state = initialState;
890 | });
891 | Constructor.prototype = new ReactClassComponent();
892 | Constructor.prototype.constructor = Constructor;
893 | Constructor.prototype.__reactAutoBindPairs = [];
894 |
895 | injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));
896 |
897 | mixSpecIntoComponent(Constructor, IsMountedPreMixin);
898 | mixSpecIntoComponent(Constructor, spec);
899 | mixSpecIntoComponent(Constructor, IsMountedPostMixin);
900 |
901 | // Initialize the defaultProps property after all mixins have been merged.
902 | if (Constructor.getDefaultProps) {
903 | Constructor.defaultProps = Constructor.getDefaultProps();
904 | }
905 |
906 | if ("production" !== 'production') {
907 | // This is a tag to indicate that the use of these method names is ok,
908 | // since it's used with createClass. If it's not, then it's likely a
909 | // mistake so we'll warn you to use the static property, property
910 | // initializer or constructor respectively.
911 | if (Constructor.getDefaultProps) {
912 | Constructor.getDefaultProps.isReactClassApproved = {};
913 | }
914 | if (Constructor.prototype.getInitialState) {
915 | Constructor.prototype.getInitialState.isReactClassApproved = {};
916 | }
917 | }
918 |
919 | _invariant(
920 | Constructor.prototype.render,
921 | 'createClass(...): Class specification must implement a `render` method.'
922 | );
923 |
924 | if ("production" !== 'production') {
925 | warning(
926 | !Constructor.prototype.componentShouldUpdate,
927 | '%s has a method called ' +
928 | 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
929 | 'The name is phrased as a question because the function is ' +
930 | 'expected to return a value.',
931 | spec.displayName || 'A component'
932 | );
933 | warning(
934 | !Constructor.prototype.componentWillRecieveProps,
935 | '%s has a method called ' +
936 | 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
937 | spec.displayName || 'A component'
938 | );
939 | }
940 |
941 | // Reduce time spent doing lookups by setting these on the prototype.
942 | for (var methodName in ReactClassInterface) {
943 | if (!Constructor.prototype[methodName]) {
944 | Constructor.prototype[methodName] = null;
945 | }
946 | }
947 |
948 | return Constructor;
949 | }
950 |
951 | return createClass;
952 | }
953 |
954 | module.exports = factory;
955 |
956 | },{"fbjs/lib/emptyObject":5,"fbjs/lib/invariant":6,"fbjs/lib/warning":7,"object-assign":8}],3:[function(require,module,exports){
957 | /**
958 | * Copyright 2013-present, Facebook, Inc.
959 | * All rights reserved.
960 | *
961 | * This source code is licensed under the BSD-style license found in the
962 | * LICENSE file in the root directory of this source tree. An additional grant
963 | * of patent rights can be found in the PATENTS file in the same directory.
964 | *
965 | */
966 |
967 | 'use strict';
968 |
969 | var React = require('react');
970 | var factory = require('./factory');
971 |
972 | if (typeof React === 'undefined') {
973 | throw Error(
974 | 'create-react-class could not find the React object. If you are using script tags, ' +
975 | 'make sure that React is being loaded before create-react-class.'
976 | );
977 | }
978 |
979 | // Hack to grab NoopUpdateQueue from isomorphic React
980 | var ReactNoopUpdateQueue = new React.Component().updater;
981 |
982 | module.exports = factory(
983 | React.Component,
984 | React.isValidElement,
985 | ReactNoopUpdateQueue
986 | );
987 |
988 | },{"./factory":2,"react":undefined}],4:[function(require,module,exports){
989 | "use strict";
990 |
991 | /**
992 | * Copyright (c) 2013-present, Facebook, Inc.
993 | * All rights reserved.
994 | *
995 | * This source code is licensed under the BSD-style license found in the
996 | * LICENSE file in the root directory of this source tree. An additional grant
997 | * of patent rights can be found in the PATENTS file in the same directory.
998 | *
999 | *
1000 | */
1001 |
1002 | function makeEmptyFunction(arg) {
1003 | return function () {
1004 | return arg;
1005 | };
1006 | }
1007 |
1008 | /**
1009 | * This function accepts and discards inputs; it has no side effects. This is
1010 | * primarily useful idiomatically for overridable function endpoints which
1011 | * always need to be callable, since JS lacks a null-call idiom ala Cocoa.
1012 | */
1013 | var emptyFunction = function emptyFunction() {};
1014 |
1015 | emptyFunction.thatReturns = makeEmptyFunction;
1016 | emptyFunction.thatReturnsFalse = makeEmptyFunction(false);
1017 | emptyFunction.thatReturnsTrue = makeEmptyFunction(true);
1018 | emptyFunction.thatReturnsNull = makeEmptyFunction(null);
1019 | emptyFunction.thatReturnsThis = function () {
1020 | return this;
1021 | };
1022 | emptyFunction.thatReturnsArgument = function (arg) {
1023 | return arg;
1024 | };
1025 |
1026 | module.exports = emptyFunction;
1027 | },{}],5:[function(require,module,exports){
1028 | /**
1029 | * Copyright (c) 2013-present, Facebook, Inc.
1030 | * All rights reserved.
1031 | *
1032 | * This source code is licensed under the BSD-style license found in the
1033 | * LICENSE file in the root directory of this source tree. An additional grant
1034 | * of patent rights can be found in the PATENTS file in the same directory.
1035 | *
1036 | */
1037 |
1038 | 'use strict';
1039 |
1040 | var emptyObject = {};
1041 |
1042 | if ("production" !== 'production') {
1043 | Object.freeze(emptyObject);
1044 | }
1045 |
1046 | module.exports = emptyObject;
1047 | },{}],6:[function(require,module,exports){
1048 | /**
1049 | * Copyright (c) 2013-present, Facebook, Inc.
1050 | * All rights reserved.
1051 | *
1052 | * This source code is licensed under the BSD-style license found in the
1053 | * LICENSE file in the root directory of this source tree. An additional grant
1054 | * of patent rights can be found in the PATENTS file in the same directory.
1055 | *
1056 | */
1057 |
1058 | 'use strict';
1059 |
1060 | /**
1061 | * Use invariant() to assert state which your program assumes to be true.
1062 | *
1063 | * Provide sprintf-style format (only %s is supported) and arguments
1064 | * to provide information about what broke and what you were
1065 | * expecting.
1066 | *
1067 | * The invariant message will be stripped in production, but the invariant
1068 | * will remain to ensure logic does not differ in production.
1069 | */
1070 |
1071 | var validateFormat = function validateFormat(format) {};
1072 |
1073 | if ("production" !== 'production') {
1074 | validateFormat = function validateFormat(format) {
1075 | if (format === undefined) {
1076 | throw new Error('invariant requires an error message argument');
1077 | }
1078 | };
1079 | }
1080 |
1081 | function invariant(condition, format, a, b, c, d, e, f) {
1082 | validateFormat(format);
1083 |
1084 | if (!condition) {
1085 | var error;
1086 | if (format === undefined) {
1087 | error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.');
1088 | } else {
1089 | var args = [a, b, c, d, e, f];
1090 | var argIndex = 0;
1091 | error = new Error(format.replace(/%s/g, function () {
1092 | return args[argIndex++];
1093 | }));
1094 | error.name = 'Invariant Violation';
1095 | }
1096 |
1097 | error.framesToPop = 1; // we don't care about invariant's own frame
1098 | throw error;
1099 | }
1100 | }
1101 |
1102 | module.exports = invariant;
1103 | },{}],7:[function(require,module,exports){
1104 | /**
1105 | * Copyright 2014-2015, Facebook, Inc.
1106 | * All rights reserved.
1107 | *
1108 | * This source code is licensed under the BSD-style license found in the
1109 | * LICENSE file in the root directory of this source tree. An additional grant
1110 | * of patent rights can be found in the PATENTS file in the same directory.
1111 | *
1112 | */
1113 |
1114 | 'use strict';
1115 |
1116 | var emptyFunction = require('./emptyFunction');
1117 |
1118 | /**
1119 | * Similar to invariant but only logs a warning if the condition is not met.
1120 | * This can be used to log issues in development environments in critical
1121 | * paths. Removing the logging code for production environments will keep the
1122 | * same logic and follow the same code paths.
1123 | */
1124 |
1125 | var warning = emptyFunction;
1126 |
1127 | if ("production" !== 'production') {
1128 | var printWarning = function printWarning(format) {
1129 | for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
1130 | args[_key - 1] = arguments[_key];
1131 | }
1132 |
1133 | var argIndex = 0;
1134 | var message = 'Warning: ' + format.replace(/%s/g, function () {
1135 | return args[argIndex++];
1136 | });
1137 | if (typeof console !== 'undefined') {
1138 | console.error(message);
1139 | }
1140 | try {
1141 | // --- Welcome to debugging React ---
1142 | // This error was thrown as a convenience so that you can use this stack
1143 | // to find the callsite that caused this warning to fire.
1144 | throw new Error(message);
1145 | } catch (x) {}
1146 | };
1147 |
1148 | warning = function warning(condition, format) {
1149 | if (format === undefined) {
1150 | throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument');
1151 | }
1152 |
1153 | if (format.indexOf('Failed Composite propType: ') === 0) {
1154 | return; // Ignore CompositeComponent proptype check.
1155 | }
1156 |
1157 | if (!condition) {
1158 | for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
1159 | args[_key2 - 2] = arguments[_key2];
1160 | }
1161 |
1162 | printWarning.apply(undefined, [format].concat(args));
1163 | }
1164 | };
1165 | }
1166 |
1167 | module.exports = warning;
1168 | },{"./emptyFunction":4}],8:[function(require,module,exports){
1169 | /*
1170 | object-assign
1171 | (c) Sindre Sorhus
1172 | @license MIT
1173 | */
1174 |
1175 | 'use strict';
1176 | /* eslint-disable no-unused-vars */
1177 | var getOwnPropertySymbols = Object.getOwnPropertySymbols;
1178 | var hasOwnProperty = Object.prototype.hasOwnProperty;
1179 | var propIsEnumerable = Object.prototype.propertyIsEnumerable;
1180 |
1181 | function toObject(val) {
1182 | if (val === null || val === undefined) {
1183 | throw new TypeError('Object.assign cannot be called with null or undefined');
1184 | }
1185 |
1186 | return Object(val);
1187 | }
1188 |
1189 | function shouldUseNative() {
1190 | try {
1191 | if (!Object.assign) {
1192 | return false;
1193 | }
1194 |
1195 | // Detect buggy property enumeration order in older V8 versions.
1196 |
1197 | // https://bugs.chromium.org/p/v8/issues/detail?id=4118
1198 | var test1 = new String('abc'); // eslint-disable-line no-new-wrappers
1199 | test1[5] = 'de';
1200 | if (Object.getOwnPropertyNames(test1)[0] === '5') {
1201 | return false;
1202 | }
1203 |
1204 | // https://bugs.chromium.org/p/v8/issues/detail?id=3056
1205 | var test2 = {};
1206 | for (var i = 0; i < 10; i++) {
1207 | test2['_' + String.fromCharCode(i)] = i;
1208 | }
1209 | var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
1210 | return test2[n];
1211 | });
1212 | if (order2.join('') !== '0123456789') {
1213 | return false;
1214 | }
1215 |
1216 | // https://bugs.chromium.org/p/v8/issues/detail?id=3056
1217 | var test3 = {};
1218 | 'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
1219 | test3[letter] = letter;
1220 | });
1221 | if (Object.keys(Object.assign({}, test3)).join('') !==
1222 | 'abcdefghijklmnopqrst') {
1223 | return false;
1224 | }
1225 |
1226 | return true;
1227 | } catch (err) {
1228 | // We don't expect any of the above to throw, but better to be safe.
1229 | return false;
1230 | }
1231 | }
1232 |
1233 | module.exports = shouldUseNative() ? Object.assign : function (target, source) {
1234 | var from;
1235 | var to = toObject(target);
1236 | var symbols;
1237 |
1238 | for (var s = 1; s < arguments.length; s++) {
1239 | from = Object(arguments[s]);
1240 |
1241 | for (var key in from) {
1242 | if (hasOwnProperty.call(from, key)) {
1243 | to[key] = from[key];
1244 | }
1245 | }
1246 |
1247 | if (getOwnPropertySymbols) {
1248 | symbols = getOwnPropertySymbols(from);
1249 | for (var i = 0; i < symbols.length; i++) {
1250 | if (propIsEnumerable.call(from, symbols[i])) {
1251 | to[symbols[i]] = from[symbols[i]];
1252 | }
1253 | }
1254 | }
1255 | }
1256 |
1257 | return to;
1258 | };
1259 |
1260 | },{}]},{},[1]);
1261 |
--------------------------------------------------------------------------------
/example/dist/example.js:
--------------------------------------------------------------------------------
1 | require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;oHello World;
180 | * }
181 | * });
182 | *
183 | * The class specification supports a specific protocol of methods that have
184 | * special meaning (e.g. `render`). See `ReactClassInterface` for
185 | * more the comprehensive protocol. Any other properties and methods in the
186 | * class specification will be available on the prototype.
187 | *
188 | * @interface ReactClassInterface
189 | * @internal
190 | */
191 | var ReactClassInterface = {
192 | /**
193 | * An array of Mixin objects to include when defining your component.
194 | *
195 | * @type {array}
196 | * @optional
197 | */
198 | mixins: 'DEFINE_MANY',
199 |
200 | /**
201 | * An object containing properties and methods that should be defined on
202 | * the component's constructor instead of its prototype (static methods).
203 | *
204 | * @type {object}
205 | * @optional
206 | */
207 | statics: 'DEFINE_MANY',
208 |
209 | /**
210 | * Definition of prop types for this component.
211 | *
212 | * @type {object}
213 | * @optional
214 | */
215 | propTypes: 'DEFINE_MANY',
216 |
217 | /**
218 | * Definition of context types for this component.
219 | *
220 | * @type {object}
221 | * @optional
222 | */
223 | contextTypes: 'DEFINE_MANY',
224 |
225 | /**
226 | * Definition of context types this component sets for its children.
227 | *
228 | * @type {object}
229 | * @optional
230 | */
231 | childContextTypes: 'DEFINE_MANY',
232 |
233 | // ==== Definition methods ====
234 |
235 | /**
236 | * Invoked when the component is mounted. Values in the mapping will be set on
237 | * `this.props` if that prop is not specified (i.e. using an `in` check).
238 | *
239 | * This method is invoked before `getInitialState` and therefore cannot rely
240 | * on `this.state` or use `this.setState`.
241 | *
242 | * @return {object}
243 | * @optional
244 | */
245 | getDefaultProps: 'DEFINE_MANY_MERGED',
246 |
247 | /**
248 | * Invoked once before the component is mounted. The return value will be used
249 | * as the initial value of `this.state`.
250 | *
251 | * getInitialState: function() {
252 | * return {
253 | * isOn: false,
254 | * fooBaz: new BazFoo()
255 | * }
256 | * }
257 | *
258 | * @return {object}
259 | * @optional
260 | */
261 | getInitialState: 'DEFINE_MANY_MERGED',
262 |
263 | /**
264 | * @return {object}
265 | * @optional
266 | */
267 | getChildContext: 'DEFINE_MANY_MERGED',
268 |
269 | /**
270 | * Uses props from `this.props` and state from `this.state` to render the
271 | * structure of the component.
272 | *
273 | * No guarantees are made about when or how often this method is invoked, so
274 | * it must not have side effects.
275 | *
276 | * render: function() {
277 | * var name = this.props.name;
278 | * return Hello, {name}!
;
279 | * }
280 | *
281 | * @return {ReactComponent}
282 | * @required
283 | */
284 | render: 'DEFINE_ONCE',
285 |
286 | // ==== Delegate methods ====
287 |
288 | /**
289 | * Invoked when the component is initially created and about to be mounted.
290 | * This may have side effects, but any external subscriptions or data created
291 | * by this method must be cleaned up in `componentWillUnmount`.
292 | *
293 | * @optional
294 | */
295 | componentWillMount: 'DEFINE_MANY',
296 |
297 | /**
298 | * Invoked when the component has been mounted and has a DOM representation.
299 | * However, there is no guarantee that the DOM node is in the document.
300 | *
301 | * Use this as an opportunity to operate on the DOM when the component has
302 | * been mounted (initialized and rendered) for the first time.
303 | *
304 | * @param {DOMElement} rootNode DOM element representing the component.
305 | * @optional
306 | */
307 | componentDidMount: 'DEFINE_MANY',
308 |
309 | /**
310 | * Invoked before the component receives new props.
311 | *
312 | * Use this as an opportunity to react to a prop transition by updating the
313 | * state using `this.setState`. Current props are accessed via `this.props`.
314 | *
315 | * componentWillReceiveProps: function(nextProps, nextContext) {
316 | * this.setState({
317 | * likesIncreasing: nextProps.likeCount > this.props.likeCount
318 | * });
319 | * }
320 | *
321 | * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
322 | * transition may cause a state change, but the opposite is not true. If you
323 | * need it, you are probably looking for `componentWillUpdate`.
324 | *
325 | * @param {object} nextProps
326 | * @optional
327 | */
328 | componentWillReceiveProps: 'DEFINE_MANY',
329 |
330 | /**
331 | * Invoked while deciding if the component should be updated as a result of
332 | * receiving new props, state and/or context.
333 | *
334 | * Use this as an opportunity to `return false` when you're certain that the
335 | * transition to the new props/state/context will not require a component
336 | * update.
337 | *
338 | * shouldComponentUpdate: function(nextProps, nextState, nextContext) {
339 | * return !equal(nextProps, this.props) ||
340 | * !equal(nextState, this.state) ||
341 | * !equal(nextContext, this.context);
342 | * }
343 | *
344 | * @param {object} nextProps
345 | * @param {?object} nextState
346 | * @param {?object} nextContext
347 | * @return {boolean} True if the component should update.
348 | * @optional
349 | */
350 | shouldComponentUpdate: 'DEFINE_ONCE',
351 |
352 | /**
353 | * Invoked when the component is about to update due to a transition from
354 | * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
355 | * and `nextContext`.
356 | *
357 | * Use this as an opportunity to perform preparation before an update occurs.
358 | *
359 | * NOTE: You **cannot** use `this.setState()` in this method.
360 | *
361 | * @param {object} nextProps
362 | * @param {?object} nextState
363 | * @param {?object} nextContext
364 | * @param {ReactReconcileTransaction} transaction
365 | * @optional
366 | */
367 | componentWillUpdate: 'DEFINE_MANY',
368 |
369 | /**
370 | * Invoked when the component's DOM representation has been updated.
371 | *
372 | * Use this as an opportunity to operate on the DOM when the component has
373 | * been updated.
374 | *
375 | * @param {object} prevProps
376 | * @param {?object} prevState
377 | * @param {?object} prevContext
378 | * @param {DOMElement} rootNode DOM element representing the component.
379 | * @optional
380 | */
381 | componentDidUpdate: 'DEFINE_MANY',
382 |
383 | /**
384 | * Invoked when the component is about to be removed from its parent and have
385 | * its DOM representation destroyed.
386 | *
387 | * Use this as an opportunity to deallocate any external resources.
388 | *
389 | * NOTE: There is no `componentDidUnmount` since your component will have been
390 | * destroyed by that point.
391 | *
392 | * @optional
393 | */
394 | componentWillUnmount: 'DEFINE_MANY',
395 |
396 | // ==== Advanced methods ====
397 |
398 | /**
399 | * Updates the component's currently mounted DOM representation.
400 | *
401 | * By default, this implements React's rendering and reconciliation algorithm.
402 | * Sophisticated clients may wish to override this.
403 | *
404 | * @param {ReactReconcileTransaction} transaction
405 | * @internal
406 | * @overridable
407 | */
408 | updateComponent: 'OVERRIDE_BASE'
409 | };
410 |
411 | /**
412 | * Mapping from class specification keys to special processing functions.
413 | *
414 | * Although these are declared like instance properties in the specification
415 | * when defining classes using `React.createClass`, they are actually static
416 | * and are accessible on the constructor instead of the prototype. Despite
417 | * being static, they must be defined outside of the "statics" key under
418 | * which all other static methods are defined.
419 | */
420 | var RESERVED_SPEC_KEYS = {
421 | displayName: function(Constructor, displayName) {
422 | Constructor.displayName = displayName;
423 | },
424 | mixins: function(Constructor, mixins) {
425 | if (mixins) {
426 | for (var i = 0; i < mixins.length; i++) {
427 | mixSpecIntoComponent(Constructor, mixins[i]);
428 | }
429 | }
430 | },
431 | childContextTypes: function(Constructor, childContextTypes) {
432 | if ("production" !== 'production') {
433 | validateTypeDef(Constructor, childContextTypes, 'childContext');
434 | }
435 | Constructor.childContextTypes = _assign(
436 | {},
437 | Constructor.childContextTypes,
438 | childContextTypes
439 | );
440 | },
441 | contextTypes: function(Constructor, contextTypes) {
442 | if ("production" !== 'production') {
443 | validateTypeDef(Constructor, contextTypes, 'context');
444 | }
445 | Constructor.contextTypes = _assign(
446 | {},
447 | Constructor.contextTypes,
448 | contextTypes
449 | );
450 | },
451 | /**
452 | * Special case getDefaultProps which should move into statics but requires
453 | * automatic merging.
454 | */
455 | getDefaultProps: function(Constructor, getDefaultProps) {
456 | if (Constructor.getDefaultProps) {
457 | Constructor.getDefaultProps = createMergedResultFunction(
458 | Constructor.getDefaultProps,
459 | getDefaultProps
460 | );
461 | } else {
462 | Constructor.getDefaultProps = getDefaultProps;
463 | }
464 | },
465 | propTypes: function(Constructor, propTypes) {
466 | if ("production" !== 'production') {
467 | validateTypeDef(Constructor, propTypes, 'prop');
468 | }
469 | Constructor.propTypes = _assign({}, Constructor.propTypes, propTypes);
470 | },
471 | statics: function(Constructor, statics) {
472 | mixStaticSpecIntoComponent(Constructor, statics);
473 | },
474 | autobind: function() {}
475 | };
476 |
477 | function validateTypeDef(Constructor, typeDef, location) {
478 | for (var propName in typeDef) {
479 | if (typeDef.hasOwnProperty(propName)) {
480 | // use a warning instead of an _invariant so components
481 | // don't show up in prod but only in __DEV__
482 | if ("production" !== 'production') {
483 | warning(
484 | typeof typeDef[propName] === 'function',
485 | '%s: %s type `%s` is invalid; it must be a function, usually from ' +
486 | 'React.PropTypes.',
487 | Constructor.displayName || 'ReactClass',
488 | ReactPropTypeLocationNames[location],
489 | propName
490 | );
491 | }
492 | }
493 | }
494 | }
495 |
496 | function validateMethodOverride(isAlreadyDefined, name) {
497 | var specPolicy = ReactClassInterface.hasOwnProperty(name)
498 | ? ReactClassInterface[name]
499 | : null;
500 |
501 | // Disallow overriding of base class methods unless explicitly allowed.
502 | if (ReactClassMixin.hasOwnProperty(name)) {
503 | _invariant(
504 | specPolicy === 'OVERRIDE_BASE',
505 | 'ReactClassInterface: You are attempting to override ' +
506 | '`%s` from your class specification. Ensure that your method names ' +
507 | 'do not overlap with React methods.',
508 | name
509 | );
510 | }
511 |
512 | // Disallow defining methods more than once unless explicitly allowed.
513 | if (isAlreadyDefined) {
514 | _invariant(
515 | specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED',
516 | 'ReactClassInterface: You are attempting to define ' +
517 | '`%s` on your component more than once. This conflict may be due ' +
518 | 'to a mixin.',
519 | name
520 | );
521 | }
522 | }
523 |
524 | /**
525 | * Mixin helper which handles policy validation and reserved
526 | * specification keys when building React classes.
527 | */
528 | function mixSpecIntoComponent(Constructor, spec) {
529 | if (!spec) {
530 | if ("production" !== 'production') {
531 | var typeofSpec = typeof spec;
532 | var isMixinValid = typeofSpec === 'object' && spec !== null;
533 |
534 | if ("production" !== 'production') {
535 | warning(
536 | isMixinValid,
537 | "%s: You're attempting to include a mixin that is either null " +
538 | 'or not an object. Check the mixins included by the component, ' +
539 | 'as well as any mixins they include themselves. ' +
540 | 'Expected object but got %s.',
541 | Constructor.displayName || 'ReactClass',
542 | spec === null ? null : typeofSpec
543 | );
544 | }
545 | }
546 |
547 | return;
548 | }
549 |
550 | _invariant(
551 | typeof spec !== 'function',
552 | "ReactClass: You're attempting to " +
553 | 'use a component class or function as a mixin. Instead, just use a ' +
554 | 'regular object.'
555 | );
556 | _invariant(
557 | !isValidElement(spec),
558 | "ReactClass: You're attempting to " +
559 | 'use a component as a mixin. Instead, just use a regular object.'
560 | );
561 |
562 | var proto = Constructor.prototype;
563 | var autoBindPairs = proto.__reactAutoBindPairs;
564 |
565 | // By handling mixins before any other properties, we ensure the same
566 | // chaining order is applied to methods with DEFINE_MANY policy, whether
567 | // mixins are listed before or after these methods in the spec.
568 | if (spec.hasOwnProperty(MIXINS_KEY)) {
569 | RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
570 | }
571 |
572 | for (var name in spec) {
573 | if (!spec.hasOwnProperty(name)) {
574 | continue;
575 | }
576 |
577 | if (name === MIXINS_KEY) {
578 | // We have already handled mixins in a special case above.
579 | continue;
580 | }
581 |
582 | var property = spec[name];
583 | var isAlreadyDefined = proto.hasOwnProperty(name);
584 | validateMethodOverride(isAlreadyDefined, name);
585 |
586 | if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
587 | RESERVED_SPEC_KEYS[name](Constructor, property);
588 | } else {
589 | // Setup methods on prototype:
590 | // The following member methods should not be automatically bound:
591 | // 1. Expected ReactClass methods (in the "interface").
592 | // 2. Overridden methods (that were mixed in).
593 | var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
594 | var isFunction = typeof property === 'function';
595 | var shouldAutoBind =
596 | isFunction &&
597 | !isReactClassMethod &&
598 | !isAlreadyDefined &&
599 | spec.autobind !== false;
600 |
601 | if (shouldAutoBind) {
602 | autoBindPairs.push(name, property);
603 | proto[name] = property;
604 | } else {
605 | if (isAlreadyDefined) {
606 | var specPolicy = ReactClassInterface[name];
607 |
608 | // These cases should already be caught by validateMethodOverride.
609 | _invariant(
610 | isReactClassMethod &&
611 | (specPolicy === 'DEFINE_MANY_MERGED' ||
612 | specPolicy === 'DEFINE_MANY'),
613 | 'ReactClass: Unexpected spec policy %s for key %s ' +
614 | 'when mixing in component specs.',
615 | specPolicy,
616 | name
617 | );
618 |
619 | // For methods which are defined more than once, call the existing
620 | // methods before calling the new property, merging if appropriate.
621 | if (specPolicy === 'DEFINE_MANY_MERGED') {
622 | proto[name] = createMergedResultFunction(proto[name], property);
623 | } else if (specPolicy === 'DEFINE_MANY') {
624 | proto[name] = createChainedFunction(proto[name], property);
625 | }
626 | } else {
627 | proto[name] = property;
628 | if ("production" !== 'production') {
629 | // Add verbose displayName to the function, which helps when looking
630 | // at profiling tools.
631 | if (typeof property === 'function' && spec.displayName) {
632 | proto[name].displayName = spec.displayName + '_' + name;
633 | }
634 | }
635 | }
636 | }
637 | }
638 | }
639 | }
640 |
641 | function mixStaticSpecIntoComponent(Constructor, statics) {
642 | if (!statics) {
643 | return;
644 | }
645 | for (var name in statics) {
646 | var property = statics[name];
647 | if (!statics.hasOwnProperty(name)) {
648 | continue;
649 | }
650 |
651 | var isReserved = name in RESERVED_SPEC_KEYS;
652 | _invariant(
653 | !isReserved,
654 | 'ReactClass: You are attempting to define a reserved ' +
655 | 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' +
656 | 'as an instance property instead; it will still be accessible on the ' +
657 | 'constructor.',
658 | name
659 | );
660 |
661 | var isInherited = name in Constructor;
662 | _invariant(
663 | !isInherited,
664 | 'ReactClass: You are attempting to define ' +
665 | '`%s` on your component more than once. This conflict may be ' +
666 | 'due to a mixin.',
667 | name
668 | );
669 | Constructor[name] = property;
670 | }
671 | }
672 |
673 | /**
674 | * Merge two objects, but throw if both contain the same key.
675 | *
676 | * @param {object} one The first object, which is mutated.
677 | * @param {object} two The second object
678 | * @return {object} one after it has been mutated to contain everything in two.
679 | */
680 | function mergeIntoWithNoDuplicateKeys(one, two) {
681 | _invariant(
682 | one && two && typeof one === 'object' && typeof two === 'object',
683 | 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'
684 | );
685 |
686 | for (var key in two) {
687 | if (two.hasOwnProperty(key)) {
688 | _invariant(
689 | one[key] === undefined,
690 | 'mergeIntoWithNoDuplicateKeys(): ' +
691 | 'Tried to merge two objects with the same key: `%s`. This conflict ' +
692 | 'may be due to a mixin; in particular, this may be caused by two ' +
693 | 'getInitialState() or getDefaultProps() methods returning objects ' +
694 | 'with clashing keys.',
695 | key
696 | );
697 | one[key] = two[key];
698 | }
699 | }
700 | return one;
701 | }
702 |
703 | /**
704 | * Creates a function that invokes two functions and merges their return values.
705 | *
706 | * @param {function} one Function to invoke first.
707 | * @param {function} two Function to invoke second.
708 | * @return {function} Function that invokes the two argument functions.
709 | * @private
710 | */
711 | function createMergedResultFunction(one, two) {
712 | return function mergedResult() {
713 | var a = one.apply(this, arguments);
714 | var b = two.apply(this, arguments);
715 | if (a == null) {
716 | return b;
717 | } else if (b == null) {
718 | return a;
719 | }
720 | var c = {};
721 | mergeIntoWithNoDuplicateKeys(c, a);
722 | mergeIntoWithNoDuplicateKeys(c, b);
723 | return c;
724 | };
725 | }
726 |
727 | /**
728 | * Creates a function that invokes two functions and ignores their return vales.
729 | *
730 | * @param {function} one Function to invoke first.
731 | * @param {function} two Function to invoke second.
732 | * @return {function} Function that invokes the two argument functions.
733 | * @private
734 | */
735 | function createChainedFunction(one, two) {
736 | return function chainedFunction() {
737 | one.apply(this, arguments);
738 | two.apply(this, arguments);
739 | };
740 | }
741 |
742 | /**
743 | * Binds a method to the component.
744 | *
745 | * @param {object} component Component whose method is going to be bound.
746 | * @param {function} method Method to be bound.
747 | * @return {function} The bound method.
748 | */
749 | function bindAutoBindMethod(component, method) {
750 | var boundMethod = method.bind(component);
751 | if ("production" !== 'production') {
752 | boundMethod.__reactBoundContext = component;
753 | boundMethod.__reactBoundMethod = method;
754 | boundMethod.__reactBoundArguments = null;
755 | var componentName = component.constructor.displayName;
756 | var _bind = boundMethod.bind;
757 | boundMethod.bind = function(newThis) {
758 | for (
759 | var _len = arguments.length,
760 | args = Array(_len > 1 ? _len - 1 : 0),
761 | _key = 1;
762 | _key < _len;
763 | _key++
764 | ) {
765 | args[_key - 1] = arguments[_key];
766 | }
767 |
768 | // User is trying to bind() an autobound method; we effectively will
769 | // ignore the value of "this" that the user is trying to use, so
770 | // let's warn.
771 | if (newThis !== component && newThis !== null) {
772 | if ("production" !== 'production') {
773 | warning(
774 | false,
775 | 'bind(): React component methods may only be bound to the ' +
776 | 'component instance. See %s',
777 | componentName
778 | );
779 | }
780 | } else if (!args.length) {
781 | if ("production" !== 'production') {
782 | warning(
783 | false,
784 | 'bind(): You are binding a component method to the component. ' +
785 | 'React does this for you automatically in a high-performance ' +
786 | 'way, so you can safely remove this call. See %s',
787 | componentName
788 | );
789 | }
790 | return boundMethod;
791 | }
792 | var reboundMethod = _bind.apply(boundMethod, arguments);
793 | reboundMethod.__reactBoundContext = component;
794 | reboundMethod.__reactBoundMethod = method;
795 | reboundMethod.__reactBoundArguments = args;
796 | return reboundMethod;
797 | };
798 | }
799 | return boundMethod;
800 | }
801 |
802 | /**
803 | * Binds all auto-bound methods in a component.
804 | *
805 | * @param {object} component Component whose method is going to be bound.
806 | */
807 | function bindAutoBindMethods(component) {
808 | var pairs = component.__reactAutoBindPairs;
809 | for (var i = 0; i < pairs.length; i += 2) {
810 | var autoBindKey = pairs[i];
811 | var method = pairs[i + 1];
812 | component[autoBindKey] = bindAutoBindMethod(component, method);
813 | }
814 | }
815 |
816 | var IsMountedPreMixin = {
817 | componentDidMount: function() {
818 | this.__isMounted = true;
819 | }
820 | };
821 |
822 | var IsMountedPostMixin = {
823 | componentWillUnmount: function() {
824 | this.__isMounted = false;
825 | }
826 | };
827 |
828 | /**
829 | * Add more to the ReactClass base class. These are all legacy features and
830 | * therefore not already part of the modern ReactComponent.
831 | */
832 | var ReactClassMixin = {
833 | /**
834 | * TODO: This will be deprecated because state should always keep a consistent
835 | * type signature and the only use case for this, is to avoid that.
836 | */
837 | replaceState: function(newState, callback) {
838 | this.updater.enqueueReplaceState(this, newState, callback);
839 | },
840 |
841 | /**
842 | * Checks whether or not this composite component is mounted.
843 | * @return {boolean} True if mounted, false otherwise.
844 | * @protected
845 | * @final
846 | */
847 | isMounted: function() {
848 | if ("production" !== 'production') {
849 | warning(
850 | this.__didWarnIsMounted,
851 | '%s: isMounted is deprecated. Instead, make sure to clean up ' +
852 | 'subscriptions and pending requests in componentWillUnmount to ' +
853 | 'prevent memory leaks.',
854 | (this.constructor && this.constructor.displayName) ||
855 | this.name ||
856 | 'Component'
857 | );
858 | this.__didWarnIsMounted = true;
859 | }
860 | return !!this.__isMounted;
861 | }
862 | };
863 |
864 | var ReactClassComponent = function() {};
865 | _assign(
866 | ReactClassComponent.prototype,
867 | ReactComponent.prototype,
868 | ReactClassMixin
869 | );
870 |
871 | /**
872 | * Creates a composite component class given a class specification.
873 | * See https://facebook.github.io/react/docs/top-level-api.html#react.createclass
874 | *
875 | * @param {object} spec Class specification (which must define `render`).
876 | * @return {function} Component constructor function.
877 | * @public
878 | */
879 | function createClass(spec) {
880 | // To keep our warnings more understandable, we'll use a little hack here to
881 | // ensure that Constructor.name !== 'Constructor'. This makes sure we don't
882 | // unnecessarily identify a class without displayName as 'Constructor'.
883 | var Constructor = identity(function(props, context, updater) {
884 | // This constructor gets overridden by mocks. The argument is used
885 | // by mocks to assert on what gets mounted.
886 |
887 | if ("production" !== 'production') {
888 | warning(
889 | this instanceof Constructor,
890 | 'Something is calling a React component directly. Use a factory or ' +
891 | 'JSX instead. See: https://fb.me/react-legacyfactory'
892 | );
893 | }
894 |
895 | // Wire up auto-binding
896 | if (this.__reactAutoBindPairs.length) {
897 | bindAutoBindMethods(this);
898 | }
899 |
900 | this.props = props;
901 | this.context = context;
902 | this.refs = emptyObject;
903 | this.updater = updater || ReactNoopUpdateQueue;
904 |
905 | this.state = null;
906 |
907 | // ReactClasses doesn't have constructors. Instead, they use the
908 | // getInitialState and componentWillMount methods for initialization.
909 |
910 | var initialState = this.getInitialState ? this.getInitialState() : null;
911 | if ("production" !== 'production') {
912 | // We allow auto-mocks to proceed as if they're returning null.
913 | if (
914 | initialState === undefined &&
915 | this.getInitialState._isMockFunction
916 | ) {
917 | // This is probably bad practice. Consider warning here and
918 | // deprecating this convenience.
919 | initialState = null;
920 | }
921 | }
922 | _invariant(
923 | typeof initialState === 'object' && !Array.isArray(initialState),
924 | '%s.getInitialState(): must return an object or null',
925 | Constructor.displayName || 'ReactCompositeComponent'
926 | );
927 |
928 | this.state = initialState;
929 | });
930 | Constructor.prototype = new ReactClassComponent();
931 | Constructor.prototype.constructor = Constructor;
932 | Constructor.prototype.__reactAutoBindPairs = [];
933 |
934 | injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));
935 |
936 | mixSpecIntoComponent(Constructor, IsMountedPreMixin);
937 | mixSpecIntoComponent(Constructor, spec);
938 | mixSpecIntoComponent(Constructor, IsMountedPostMixin);
939 |
940 | // Initialize the defaultProps property after all mixins have been merged.
941 | if (Constructor.getDefaultProps) {
942 | Constructor.defaultProps = Constructor.getDefaultProps();
943 | }
944 |
945 | if ("production" !== 'production') {
946 | // This is a tag to indicate that the use of these method names is ok,
947 | // since it's used with createClass. If it's not, then it's likely a
948 | // mistake so we'll warn you to use the static property, property
949 | // initializer or constructor respectively.
950 | if (Constructor.getDefaultProps) {
951 | Constructor.getDefaultProps.isReactClassApproved = {};
952 | }
953 | if (Constructor.prototype.getInitialState) {
954 | Constructor.prototype.getInitialState.isReactClassApproved = {};
955 | }
956 | }
957 |
958 | _invariant(
959 | Constructor.prototype.render,
960 | 'createClass(...): Class specification must implement a `render` method.'
961 | );
962 |
963 | if ("production" !== 'production') {
964 | warning(
965 | !Constructor.prototype.componentShouldUpdate,
966 | '%s has a method called ' +
967 | 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
968 | 'The name is phrased as a question because the function is ' +
969 | 'expected to return a value.',
970 | spec.displayName || 'A component'
971 | );
972 | warning(
973 | !Constructor.prototype.componentWillRecieveProps,
974 | '%s has a method called ' +
975 | 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
976 | spec.displayName || 'A component'
977 | );
978 | }
979 |
980 | // Reduce time spent doing lookups by setting these on the prototype.
981 | for (var methodName in ReactClassInterface) {
982 | if (!Constructor.prototype[methodName]) {
983 | Constructor.prototype[methodName] = null;
984 | }
985 | }
986 |
987 | return Constructor;
988 | }
989 |
990 | return createClass;
991 | }
992 |
993 | module.exports = factory;
994 |
995 | },{"fbjs/lib/emptyObject":5,"fbjs/lib/invariant":6,"fbjs/lib/warning":7,"object-assign":8}],3:[function(require,module,exports){
996 | /**
997 | * Copyright 2013-present, Facebook, Inc.
998 | * All rights reserved.
999 | *
1000 | * This source code is licensed under the BSD-style license found in the
1001 | * LICENSE file in the root directory of this source tree. An additional grant
1002 | * of patent rights can be found in the PATENTS file in the same directory.
1003 | *
1004 | */
1005 |
1006 | 'use strict';
1007 |
1008 | var React = require('react');
1009 | var factory = require('./factory');
1010 |
1011 | if (typeof React === 'undefined') {
1012 | throw Error(
1013 | 'create-react-class could not find the React object. If you are using script tags, ' +
1014 | 'make sure that React is being loaded before create-react-class.'
1015 | );
1016 | }
1017 |
1018 | // Hack to grab NoopUpdateQueue from isomorphic React
1019 | var ReactNoopUpdateQueue = new React.Component().updater;
1020 |
1021 | module.exports = factory(
1022 | React.Component,
1023 | React.isValidElement,
1024 | ReactNoopUpdateQueue
1025 | );
1026 |
1027 | },{"./factory":2,"react":undefined}],4:[function(require,module,exports){
1028 | "use strict";
1029 |
1030 | /**
1031 | * Copyright (c) 2013-present, Facebook, Inc.
1032 | * All rights reserved.
1033 | *
1034 | * This source code is licensed under the BSD-style license found in the
1035 | * LICENSE file in the root directory of this source tree. An additional grant
1036 | * of patent rights can be found in the PATENTS file in the same directory.
1037 | *
1038 | *
1039 | */
1040 |
1041 | function makeEmptyFunction(arg) {
1042 | return function () {
1043 | return arg;
1044 | };
1045 | }
1046 |
1047 | /**
1048 | * This function accepts and discards inputs; it has no side effects. This is
1049 | * primarily useful idiomatically for overridable function endpoints which
1050 | * always need to be callable, since JS lacks a null-call idiom ala Cocoa.
1051 | */
1052 | var emptyFunction = function emptyFunction() {};
1053 |
1054 | emptyFunction.thatReturns = makeEmptyFunction;
1055 | emptyFunction.thatReturnsFalse = makeEmptyFunction(false);
1056 | emptyFunction.thatReturnsTrue = makeEmptyFunction(true);
1057 | emptyFunction.thatReturnsNull = makeEmptyFunction(null);
1058 | emptyFunction.thatReturnsThis = function () {
1059 | return this;
1060 | };
1061 | emptyFunction.thatReturnsArgument = function (arg) {
1062 | return arg;
1063 | };
1064 |
1065 | module.exports = emptyFunction;
1066 | },{}],5:[function(require,module,exports){
1067 | /**
1068 | * Copyright (c) 2013-present, Facebook, Inc.
1069 | * All rights reserved.
1070 | *
1071 | * This source code is licensed under the BSD-style license found in the
1072 | * LICENSE file in the root directory of this source tree. An additional grant
1073 | * of patent rights can be found in the PATENTS file in the same directory.
1074 | *
1075 | */
1076 |
1077 | 'use strict';
1078 |
1079 | var emptyObject = {};
1080 |
1081 | if ("production" !== 'production') {
1082 | Object.freeze(emptyObject);
1083 | }
1084 |
1085 | module.exports = emptyObject;
1086 | },{}],6:[function(require,module,exports){
1087 | /**
1088 | * Copyright (c) 2013-present, Facebook, Inc.
1089 | * All rights reserved.
1090 | *
1091 | * This source code is licensed under the BSD-style license found in the
1092 | * LICENSE file in the root directory of this source tree. An additional grant
1093 | * of patent rights can be found in the PATENTS file in the same directory.
1094 | *
1095 | */
1096 |
1097 | 'use strict';
1098 |
1099 | /**
1100 | * Use invariant() to assert state which your program assumes to be true.
1101 | *
1102 | * Provide sprintf-style format (only %s is supported) and arguments
1103 | * to provide information about what broke and what you were
1104 | * expecting.
1105 | *
1106 | * The invariant message will be stripped in production, but the invariant
1107 | * will remain to ensure logic does not differ in production.
1108 | */
1109 |
1110 | var validateFormat = function validateFormat(format) {};
1111 |
1112 | if ("production" !== 'production') {
1113 | validateFormat = function validateFormat(format) {
1114 | if (format === undefined) {
1115 | throw new Error('invariant requires an error message argument');
1116 | }
1117 | };
1118 | }
1119 |
1120 | function invariant(condition, format, a, b, c, d, e, f) {
1121 | validateFormat(format);
1122 |
1123 | if (!condition) {
1124 | var error;
1125 | if (format === undefined) {
1126 | error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.');
1127 | } else {
1128 | var args = [a, b, c, d, e, f];
1129 | var argIndex = 0;
1130 | error = new Error(format.replace(/%s/g, function () {
1131 | return args[argIndex++];
1132 | }));
1133 | error.name = 'Invariant Violation';
1134 | }
1135 |
1136 | error.framesToPop = 1; // we don't care about invariant's own frame
1137 | throw error;
1138 | }
1139 | }
1140 |
1141 | module.exports = invariant;
1142 | },{}],7:[function(require,module,exports){
1143 | /**
1144 | * Copyright 2014-2015, Facebook, Inc.
1145 | * All rights reserved.
1146 | *
1147 | * This source code is licensed under the BSD-style license found in the
1148 | * LICENSE file in the root directory of this source tree. An additional grant
1149 | * of patent rights can be found in the PATENTS file in the same directory.
1150 | *
1151 | */
1152 |
1153 | 'use strict';
1154 |
1155 | var emptyFunction = require('./emptyFunction');
1156 |
1157 | /**
1158 | * Similar to invariant but only logs a warning if the condition is not met.
1159 | * This can be used to log issues in development environments in critical
1160 | * paths. Removing the logging code for production environments will keep the
1161 | * same logic and follow the same code paths.
1162 | */
1163 |
1164 | var warning = emptyFunction;
1165 |
1166 | if ("production" !== 'production') {
1167 | var printWarning = function printWarning(format) {
1168 | for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
1169 | args[_key - 1] = arguments[_key];
1170 | }
1171 |
1172 | var argIndex = 0;
1173 | var message = 'Warning: ' + format.replace(/%s/g, function () {
1174 | return args[argIndex++];
1175 | });
1176 | if (typeof console !== 'undefined') {
1177 | console.error(message);
1178 | }
1179 | try {
1180 | // --- Welcome to debugging React ---
1181 | // This error was thrown as a convenience so that you can use this stack
1182 | // to find the callsite that caused this warning to fire.
1183 | throw new Error(message);
1184 | } catch (x) {}
1185 | };
1186 |
1187 | warning = function warning(condition, format) {
1188 | if (format === undefined) {
1189 | throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument');
1190 | }
1191 |
1192 | if (format.indexOf('Failed Composite propType: ') === 0) {
1193 | return; // Ignore CompositeComponent proptype check.
1194 | }
1195 |
1196 | if (!condition) {
1197 | for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
1198 | args[_key2 - 2] = arguments[_key2];
1199 | }
1200 |
1201 | printWarning.apply(undefined, [format].concat(args));
1202 | }
1203 | };
1204 | }
1205 |
1206 | module.exports = warning;
1207 | },{"./emptyFunction":4}],8:[function(require,module,exports){
1208 | /*
1209 | object-assign
1210 | (c) Sindre Sorhus
1211 | @license MIT
1212 | */
1213 |
1214 | 'use strict';
1215 | /* eslint-disable no-unused-vars */
1216 | var getOwnPropertySymbols = Object.getOwnPropertySymbols;
1217 | var hasOwnProperty = Object.prototype.hasOwnProperty;
1218 | var propIsEnumerable = Object.prototype.propertyIsEnumerable;
1219 |
1220 | function toObject(val) {
1221 | if (val === null || val === undefined) {
1222 | throw new TypeError('Object.assign cannot be called with null or undefined');
1223 | }
1224 |
1225 | return Object(val);
1226 | }
1227 |
1228 | function shouldUseNative() {
1229 | try {
1230 | if (!Object.assign) {
1231 | return false;
1232 | }
1233 |
1234 | // Detect buggy property enumeration order in older V8 versions.
1235 |
1236 | // https://bugs.chromium.org/p/v8/issues/detail?id=4118
1237 | var test1 = new String('abc'); // eslint-disable-line no-new-wrappers
1238 | test1[5] = 'de';
1239 | if (Object.getOwnPropertyNames(test1)[0] === '5') {
1240 | return false;
1241 | }
1242 |
1243 | // https://bugs.chromium.org/p/v8/issues/detail?id=3056
1244 | var test2 = {};
1245 | for (var i = 0; i < 10; i++) {
1246 | test2['_' + String.fromCharCode(i)] = i;
1247 | }
1248 | var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
1249 | return test2[n];
1250 | });
1251 | if (order2.join('') !== '0123456789') {
1252 | return false;
1253 | }
1254 |
1255 | // https://bugs.chromium.org/p/v8/issues/detail?id=3056
1256 | var test3 = {};
1257 | 'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
1258 | test3[letter] = letter;
1259 | });
1260 | if (Object.keys(Object.assign({}, test3)).join('') !==
1261 | 'abcdefghijklmnopqrst') {
1262 | return false;
1263 | }
1264 |
1265 | return true;
1266 | } catch (err) {
1267 | // We don't expect any of the above to throw, but better to be safe.
1268 | return false;
1269 | }
1270 | }
1271 |
1272 | module.exports = shouldUseNative() ? Object.assign : function (target, source) {
1273 | var from;
1274 | var to = toObject(target);
1275 | var symbols;
1276 |
1277 | for (var s = 1; s < arguments.length; s++) {
1278 | from = Object(arguments[s]);
1279 |
1280 | for (var key in from) {
1281 | if (hasOwnProperty.call(from, key)) {
1282 | to[key] = from[key];
1283 | }
1284 | }
1285 |
1286 | if (getOwnPropertySymbols) {
1287 | symbols = getOwnPropertySymbols(from);
1288 | for (var i = 0; i < symbols.length; i++) {
1289 | if (propIsEnumerable.call(from, symbols[i])) {
1290 | to[symbols[i]] = from[symbols[i]];
1291 | }
1292 | }
1293 | }
1294 | }
1295 |
1296 | return to;
1297 | };
1298 |
1299 | },{}]},{},[1]);
1300 |
--------------------------------------------------------------------------------