mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-31 18:21:38 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd9232201d | ||
|
|
29f6bd363c | ||
|
|
4845de5cb5 | ||
|
|
267a9b55bf | ||
|
|
7add5c524a | ||
|
|
8e0d94e092 | ||
|
|
25f96ba8ae | ||
|
|
2b90bd736f | ||
|
|
791ede06dd | ||
|
|
0567232942 |
3
.babelrc
3
.babelrc
@@ -3,5 +3,8 @@
|
||||
"es2015",
|
||||
"stage-1",
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-decorators-legacy"
|
||||
]
|
||||
}
|
||||
|
||||
19
README.md
19
README.md
@@ -2,10 +2,12 @@
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![npm version][npm-image]][npm-url]
|
||||

|
||||

|
||||
|
||||
[React Native][react-native-url] components and APIs for the Web.
|
||||
|
||||
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
|
||||
|
||||
## Quick start
|
||||
|
||||
To install in your app:
|
||||
@@ -14,9 +16,7 @@ To install in your app:
|
||||
npm install --save react@0.14 react-dom@0.14 react-native-web
|
||||
```
|
||||
|
||||
Or [try it on CodePen](http://codepen.io/necolas/pen/PZzwBR).
|
||||
|
||||
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
|
||||
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -29,9 +29,14 @@ common layouts with flexbox, such as stacked and nested boxes with margin and
|
||||
padding. And the [`StyleSheet`](docs/guides/style.md) API converts styles
|
||||
defined in JavaScript to "atomic" CSS.
|
||||
|
||||
## Example
|
||||
## Examples
|
||||
|
||||
More examples can be found in the [`examples` directory](examples).
|
||||
Demos:
|
||||
|
||||
* [React Native for Web: Playground](http://codepen.io/necolas/pen/PZzwBR).
|
||||
* [TicTacToe](http://codepen.io/necolas/full/eJaLZd/)
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
import React, { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
|
||||
@@ -95,12 +100,14 @@ Exported modules:
|
||||
* [`TouchableWithoutFeedback`](docs/components/TouchableWithoutFeedback.md)
|
||||
* [`View`](docs/components/View.md)
|
||||
* APIs
|
||||
* [`Animated`](http://facebook.github.io/react-native/releases/0.20/docs/animated.html) (mirrors React Native)
|
||||
* [`AppRegistry`](docs/apis/AppRegistry.md)
|
||||
* [`AppState`](docs/apis/AppState.md)
|
||||
* [`AsyncStorage`](docs/apis/AsyncStorage.md)
|
||||
* [`Dimensions`](docs/apis/Dimensions.md)
|
||||
* [`NativeMethods`](docs/apis/NativeMethods.md)
|
||||
* [`NetInfo`](docs/apis/NetInfo.md)
|
||||
* [`PanResponder`](http://facebook.github.io/react-native/releases/0.20/docs/panresponder.html#content) (mirrors React Native)
|
||||
* [`PixelRatio`](docs/apis/PixelRatio.md)
|
||||
* [`Platform`](docs/apis/Platform.md)
|
||||
* [`StyleSheet`](docs/apis/StyleSheet.md)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var constants = require('./constants')
|
||||
var webpack = require('webpack')
|
||||
const constants = require('./constants')
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
devServer: {
|
||||
@@ -30,7 +31,7 @@ module.exports = {
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'react-native': '../../src'
|
||||
'react-native': path.join(__dirname, '../src')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# Client and Server rendering
|
||||
|
||||
It's recommended that you use a module loader that supports package aliases
|
||||
(e.g., webpack), and alias `react-native` to `react-native-web`.
|
||||
|
||||
```js
|
||||
// webpack.config.js
|
||||
|
||||
module.exports = {
|
||||
// ...other configuration
|
||||
resolve: {
|
||||
alias: {
|
||||
'react-native': 'react-native-web'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Client-side rendering
|
||||
|
||||
```js
|
||||
|
||||
@@ -1,18 +1,4 @@
|
||||
import React, { Component, PropTypes, StyleSheet, View } from '../../src'
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
contentContainer: {
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1
|
||||
},
|
||||
// distribute all space (rather than extra space)
|
||||
column: {
|
||||
flexBasis: '0%'
|
||||
}
|
||||
})
|
||||
import React, { Component, PropTypes, StyleSheet, View } from 'react-native'
|
||||
|
||||
export default class GridView extends Component {
|
||||
static propTypes = {
|
||||
@@ -63,3 +49,17 @@ export default class GridView extends Component {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
contentContainer: {
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1
|
||||
},
|
||||
// distribute all space (rather than extra space)
|
||||
column: {
|
||||
flexBasis: '0%'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import React, { StyleSheet, Text } from '../../src'
|
||||
import React, { StyleSheet, Text } from 'react-native'
|
||||
|
||||
const Heading = ({ children, size = 'normal' }) => (
|
||||
<Text
|
||||
accessibilityRole='heading'
|
||||
children={children}
|
||||
style={{ ...styles.root, ...sizeStyles[size] }}
|
||||
/>
|
||||
)
|
||||
|
||||
const sizeStyles = StyleSheet.create({
|
||||
xlarge: {
|
||||
@@ -23,12 +31,4 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
const Heading = ({ children, size = 'normal' }) => (
|
||||
<Text
|
||||
accessibilityRole='heading'
|
||||
children={children}
|
||||
style={{ ...styles.root, ...sizeStyles[size] }}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Heading
|
||||
|
||||
@@ -1,21 +1,4 @@
|
||||
import React, { StyleSheet, Text, View } from '../../src'
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
marginVertical: 10,
|
||||
padding: 10
|
||||
},
|
||||
heading: {
|
||||
fontWeight: 'bold',
|
||||
padding: 5,
|
||||
textAlign: 'center'
|
||||
},
|
||||
text: {
|
||||
textAlign: 'center'
|
||||
}
|
||||
})
|
||||
import React, { StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
const MediaQueryWidget = ({ mediaQuery = {} }) => {
|
||||
const active = Object.keys(mediaQuery).reduce((active, alias) => {
|
||||
@@ -36,4 +19,21 @@ const MediaQueryWidget = ({ mediaQuery = {} }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
marginVertical: 10,
|
||||
padding: 10
|
||||
},
|
||||
heading: {
|
||||
fontWeight: 'bold',
|
||||
padding: 5,
|
||||
textAlign: 'center'
|
||||
},
|
||||
text: {
|
||||
textAlign: 'center'
|
||||
}
|
||||
})
|
||||
|
||||
export default MediaQueryWidget
|
||||
|
||||
@@ -2,7 +2,7 @@ import { MediaProvider, matchMedia } from 'react-media-queries'
|
||||
import App from './components/App'
|
||||
import createGetter from 'react-media-queries/lib/createMediaQueryGetter'
|
||||
import createListener from 'react-media-queries/lib/createMediaQueryListener'
|
||||
import React, { AppRegistry } from '../src'
|
||||
import React, { AppRegistry } from 'react-native'
|
||||
|
||||
const mediaQueries = {
|
||||
small: '(min-width: 300px)',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.14",
|
||||
"version": "0.0.16",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
@@ -16,9 +16,8 @@
|
||||
"test:watch": "npm run test:unit -- --no-single-run"
|
||||
},
|
||||
"dependencies": {
|
||||
"exenv": "^1.2.0",
|
||||
"inline-style-prefixer": "^0.5.3",
|
||||
"invariant": "^2.2.0",
|
||||
"fbjs": "^0.7.2",
|
||||
"inline-style-prefixer": "^0.6.7",
|
||||
"lodash.debounce": "^3.1.1",
|
||||
"react-tappable": "^0.7.1",
|
||||
"react-textarea-autosize": "^3.1.0"
|
||||
@@ -28,6 +27,7 @@
|
||||
"babel-core": "^6.3.13",
|
||||
"babel-eslint": "^4.1.6",
|
||||
"babel-loader": "^6.2.0",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"babel-preset-stage-1": "^6.3.13",
|
||||
|
||||
1654
src/apis/Animated/AnimatedImplementation.js
Normal file
1654
src/apis/Animated/AnimatedImplementation.js
Normal file
File diff suppressed because it is too large
Load Diff
288
src/apis/Animated/Interpolation.js
Normal file
288
src/apis/Animated/Interpolation.js
Normal file
@@ -0,0 +1,288 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule Interpolation
|
||||
* @flow
|
||||
*/
|
||||
/* eslint no-bitwise: 0 */
|
||||
'use strict';
|
||||
|
||||
/* @edit start */
|
||||
var normalizeColor = require('../StyleSheet/normalizeColor');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
/* @edit end */
|
||||
|
||||
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
|
||||
|
||||
export type InterpolationConfigType = {
|
||||
inputRange: Array<number>;
|
||||
outputRange: (Array<number> | Array<string>);
|
||||
easing?: ((input: number) => number);
|
||||
extrapolate?: ExtrapolateType;
|
||||
extrapolateLeft?: ExtrapolateType;
|
||||
extrapolateRight?: ExtrapolateType;
|
||||
};
|
||||
|
||||
var linear = (t) => t;
|
||||
|
||||
/**
|
||||
* Very handy helper to map input ranges to output ranges with an easing
|
||||
* function and custom behavior outside of the ranges.
|
||||
*/
|
||||
class Interpolation {
|
||||
static create(config: InterpolationConfigType): (input: number) => number | string {
|
||||
|
||||
if (config.outputRange && typeof config.outputRange[0] === 'string') {
|
||||
return createInterpolationFromStringOutputRange(config);
|
||||
}
|
||||
|
||||
var outputRange: Array<number> = (config.outputRange: any);
|
||||
checkInfiniteRange('outputRange', outputRange);
|
||||
|
||||
var inputRange = config.inputRange;
|
||||
checkInfiniteRange('inputRange', inputRange);
|
||||
checkValidInputRange(inputRange);
|
||||
|
||||
invariant(
|
||||
inputRange.length === outputRange.length,
|
||||
'inputRange (' + inputRange.length + ') and outputRange (' +
|
||||
outputRange.length + ') must have the same length'
|
||||
);
|
||||
|
||||
var easing = config.easing || linear;
|
||||
|
||||
var extrapolateLeft: ExtrapolateType = 'extend';
|
||||
if (config.extrapolateLeft !== undefined) {
|
||||
extrapolateLeft = config.extrapolateLeft;
|
||||
} else if (config.extrapolate !== undefined) {
|
||||
extrapolateLeft = config.extrapolate;
|
||||
}
|
||||
|
||||
var extrapolateRight: ExtrapolateType = 'extend';
|
||||
if (config.extrapolateRight !== undefined) {
|
||||
extrapolateRight = config.extrapolateRight;
|
||||
} else if (config.extrapolate !== undefined) {
|
||||
extrapolateRight = config.extrapolate;
|
||||
}
|
||||
|
||||
return (input) => {
|
||||
invariant(
|
||||
typeof input === 'number',
|
||||
'Cannot interpolation an input which is not a number'
|
||||
);
|
||||
|
||||
var range = findRange(input, inputRange);
|
||||
return interpolate(
|
||||
input,
|
||||
inputRange[range],
|
||||
inputRange[range + 1],
|
||||
outputRange[range],
|
||||
outputRange[range + 1],
|
||||
easing,
|
||||
extrapolateLeft,
|
||||
extrapolateRight,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function interpolate(
|
||||
input: number,
|
||||
inputMin: number,
|
||||
inputMax: number,
|
||||
outputMin: number,
|
||||
outputMax: number,
|
||||
easing: ((input: number) => number),
|
||||
extrapolateLeft: ExtrapolateType,
|
||||
extrapolateRight: ExtrapolateType,
|
||||
) {
|
||||
var result = input;
|
||||
|
||||
// Extrapolate
|
||||
if (result < inputMin) {
|
||||
if (extrapolateLeft === 'identity') {
|
||||
return result;
|
||||
} else if (extrapolateLeft === 'clamp') {
|
||||
result = inputMin;
|
||||
} else if (extrapolateLeft === 'extend') {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
if (result > inputMax) {
|
||||
if (extrapolateRight === 'identity') {
|
||||
return result;
|
||||
} else if (extrapolateRight === 'clamp') {
|
||||
result = inputMax;
|
||||
} else if (extrapolateRight === 'extend') {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
if (outputMin === outputMax) {
|
||||
return outputMin;
|
||||
}
|
||||
|
||||
if (inputMin === inputMax) {
|
||||
if (input <= inputMin) {
|
||||
return outputMin;
|
||||
}
|
||||
return outputMax;
|
||||
}
|
||||
|
||||
// Input Range
|
||||
if (inputMin === -Infinity) {
|
||||
result = -result;
|
||||
} else if (inputMax === Infinity) {
|
||||
result = result - inputMin;
|
||||
} else {
|
||||
result = (result - inputMin) / (inputMax - inputMin);
|
||||
}
|
||||
|
||||
// Easing
|
||||
result = easing(result);
|
||||
|
||||
// Output Range
|
||||
if (outputMin === -Infinity) {
|
||||
result = -result;
|
||||
} else if (outputMax === Infinity) {
|
||||
result = result + outputMin;
|
||||
} else {
|
||||
result = result * (outputMax - outputMin) + outputMin;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function colorToRgba(input: string): string {
|
||||
var int32Color = normalizeColor(input);
|
||||
if (int32Color === null) {
|
||||
return input;
|
||||
}
|
||||
|
||||
int32Color = int32Color || 0; // $FlowIssue
|
||||
|
||||
var r = (int32Color & 0xff000000) >>> 24;
|
||||
var g = (int32Color & 0x00ff0000) >>> 16;
|
||||
var b = (int32Color & 0x0000ff00) >>> 8;
|
||||
var a = (int32Color & 0x000000ff) / 255;
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||
}
|
||||
|
||||
var stringShapeRegex = /[0-9\.-]+/g;
|
||||
|
||||
/**
|
||||
* Supports string shapes by extracting numbers so new values can be computed,
|
||||
* and recombines those values into new strings of the same shape. Supports
|
||||
* things like:
|
||||
*
|
||||
* rgba(123, 42, 99, 0.36) // colors
|
||||
* -45deg // values with units
|
||||
*/
|
||||
function createInterpolationFromStringOutputRange(
|
||||
config: InterpolationConfigType,
|
||||
): (input: number) => string {
|
||||
var outputRange: Array<string> = (config.outputRange: any);
|
||||
invariant(outputRange.length >= 2, 'Bad output range');
|
||||
outputRange = outputRange.map(colorToRgba);
|
||||
checkPattern(outputRange);
|
||||
|
||||
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
|
||||
// ->
|
||||
// [
|
||||
// [0, 50],
|
||||
// [100, 150],
|
||||
// [200, 250],
|
||||
// [0, 0.5],
|
||||
// ]
|
||||
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
|
||||
* guard against this possibility.
|
||||
*/
|
||||
var outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
|
||||
outputRange.forEach(value => {
|
||||
/* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard
|
||||
* against this possibility.
|
||||
*/
|
||||
value.match(stringShapeRegex).forEach((number, i) => {
|
||||
outputRanges[i].push(+number);
|
||||
});
|
||||
});
|
||||
|
||||
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
|
||||
* guard against this possibility.
|
||||
*/
|
||||
var interpolations = outputRange[0].match(stringShapeRegex).map((value, i) => {
|
||||
return Interpolation.create({
|
||||
...config,
|
||||
outputRange: outputRanges[i],
|
||||
});
|
||||
});
|
||||
|
||||
return (input) => {
|
||||
var i = 0;
|
||||
// 'rgba(0, 100, 200, 0)'
|
||||
// ->
|
||||
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
|
||||
return outputRange[0].replace(stringShapeRegex, () => {
|
||||
return String(interpolations[i++](input));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function checkPattern(arr: Array<string>) {
|
||||
var pattern = arr[0].replace(stringShapeRegex, '');
|
||||
for (var i = 1; i < arr.length; ++i) {
|
||||
invariant(
|
||||
pattern === arr[i].replace(stringShapeRegex, ''),
|
||||
'invalid pattern ' + arr[0] + ' and ' + arr[i],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function findRange(input: number, inputRange: Array<number>) {
|
||||
for (var i = 1; i < inputRange.length - 1; ++i) {
|
||||
if (inputRange[i] >= input) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
function checkValidInputRange(arr: Array<number>) {
|
||||
invariant(arr.length >= 2, 'inputRange must have at least 2 elements');
|
||||
for (var i = 1; i < arr.length; ++i) {
|
||||
invariant(
|
||||
arr[i] >= arr[i - 1],
|
||||
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
|
||||
* one or both of the operands may be something that doesn't cleanly
|
||||
* convert to a string, like undefined, null, and object, etc. If you really
|
||||
* mean this implicit string conversion, you can do something like
|
||||
* String(myThing)
|
||||
*/
|
||||
'inputRange must be monotonically increasing ' + arr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function checkInfiniteRange(name: string, arr: Array<number>) {
|
||||
invariant(arr.length >= 2, name + ' must have at least 2 elements');
|
||||
invariant(
|
||||
arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity,
|
||||
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
|
||||
* one or both of the operands may be something that doesn't cleanly convert
|
||||
* to a string, like undefined, null, and object, etc. If you really mean
|
||||
* this implicit string conversion, you can do something like
|
||||
* String(myThing)
|
||||
*/
|
||||
name + 'cannot be ]-infinity;+infinity[ ' + arr
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = Interpolation;
|
||||
103
src/apis/Animated/SpringConfig.js
Normal file
103
src/apis/Animated/SpringConfig.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule SpringConfig
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
type SpringConfigType = {
|
||||
tension: number,
|
||||
friction: number,
|
||||
};
|
||||
|
||||
function tensionFromOrigamiValue(oValue) {
|
||||
return (oValue - 30) * 3.62 + 194;
|
||||
}
|
||||
|
||||
function frictionFromOrigamiValue(oValue) {
|
||||
return (oValue - 8) * 3 + 25;
|
||||
}
|
||||
|
||||
function fromOrigamiTensionAndFriction(
|
||||
tension: number,
|
||||
friction: number,
|
||||
): SpringConfigType {
|
||||
return {
|
||||
tension: tensionFromOrigamiValue(tension),
|
||||
friction: frictionFromOrigamiValue(friction)
|
||||
};
|
||||
}
|
||||
|
||||
function fromBouncinessAndSpeed(
|
||||
bounciness: number,
|
||||
speed: number,
|
||||
): SpringConfigType {
|
||||
function normalize(value, startValue, endValue) {
|
||||
return (value - startValue) / (endValue - startValue);
|
||||
}
|
||||
|
||||
function projectNormal(n, start, end) {
|
||||
return start + (n * (end - start));
|
||||
}
|
||||
|
||||
function linearInterpolation(t, start, end) {
|
||||
return t * end + (1 - t) * start;
|
||||
}
|
||||
|
||||
function quadraticOutInterpolation(t, start, end) {
|
||||
return linearInterpolation(2 * t - t * t, start, end);
|
||||
}
|
||||
|
||||
function b3Friction1(x) {
|
||||
return (0.0007 * Math.pow(x, 3)) -
|
||||
(0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28;
|
||||
}
|
||||
|
||||
function b3Friction2(x) {
|
||||
return (0.000044 * Math.pow(x, 3)) -
|
||||
(0.006 * Math.pow(x, 2)) + 0.36 * x + 2;
|
||||
}
|
||||
|
||||
function b3Friction3(x) {
|
||||
return (0.00000045 * Math.pow(x, 3)) -
|
||||
(0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84;
|
||||
}
|
||||
|
||||
function b3Nobounce(tension) {
|
||||
if (tension <= 18) {
|
||||
return b3Friction1(tension);
|
||||
} else if (tension > 18 && tension <= 44) {
|
||||
return b3Friction2(tension);
|
||||
} else {
|
||||
return b3Friction3(tension);
|
||||
}
|
||||
}
|
||||
|
||||
var b = normalize(bounciness / 1.7, 0, 20);
|
||||
b = projectNormal(b, 0, 0.8);
|
||||
var s = normalize(speed / 1.7, 0, 20);
|
||||
var bouncyTension = projectNormal(s, 0.5, 200);
|
||||
var bouncyFriction = quadraticOutInterpolation(
|
||||
b,
|
||||
b3Nobounce(bouncyTension),
|
||||
0.01
|
||||
);
|
||||
|
||||
return {
|
||||
tension: tensionFromOrigamiValue(bouncyTension),
|
||||
friction: frictionFromOrigamiValue(bouncyFriction)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fromOrigamiTensionAndFriction,
|
||||
fromBouncinessAndSpeed,
|
||||
};
|
||||
19
src/apis/Animated/index.js
Normal file
19
src/apis/Animated/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import AnimatedImplementation from './AnimatedImplementation'
|
||||
import Image from '../../components/Image'
|
||||
import Text from '../../components/Text'
|
||||
import View from '../../components/View'
|
||||
|
||||
export default {
|
||||
...AnimatedImplementation,
|
||||
View: AnimatedImplementation.createAnimatedComponent(View),
|
||||
Text: AnimatedImplementation.createAnimatedComponent(Text),
|
||||
Image: AnimatedImplementation.createAnimatedComponent(Image)
|
||||
}
|
||||
15
src/apis/Animated/polyfills/Set.js
Normal file
15
src/apis/Animated/polyfills/Set.js
Normal file
@@ -0,0 +1,15 @@
|
||||
function SetPolyfill() {
|
||||
this._cache = []
|
||||
}
|
||||
|
||||
SetPolyfill.prototype.add = function (e) {
|
||||
if (this._cache.indexOf(e) === -1) {
|
||||
this._cache.push(e)
|
||||
}
|
||||
}
|
||||
|
||||
SetPolyfill.prototype.forEach = function (cb) {
|
||||
this._cache.forEach(cb)
|
||||
}
|
||||
|
||||
export default SetPolyfill
|
||||
@@ -1,7 +1,7 @@
|
||||
import Portal from '../../components/Portal'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import StyleSheet from '../StyleSheet'
|
||||
import View from '../../components/View'
|
||||
|
||||
export default class ReactNativeApp extends Component {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { Component } from 'react'
|
||||
import invariant from 'invariant'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import ReactDOM from 'react-dom'
|
||||
import renderApplication, { prerenderApplication } from './renderApplication'
|
||||
|
||||
@@ -65,7 +65,7 @@ export default class AppRegistry {
|
||||
}
|
||||
|
||||
static runApplication(appKey: string, appParameters?: Object): void {
|
||||
const isDevelopment = process.env.NODE_ENV === 'development'
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
const params = { ...appParameters }
|
||||
params.rootTag = `#${params.rootTag.id}`
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'invariant'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import React, { Component } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import invariant from 'invariant'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
|
||||
const listeners = {}
|
||||
const eventTypes = [ 'change' ]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'invariant'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
|
||||
const dimensions = {
|
||||
screen: {
|
||||
|
||||
83
src/apis/Easing/bezier.js
Normal file
83
src/apis/Easing/bezier.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* https://github.com/arian/cubic-bezier
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2013 Arian Stolwijk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @providesModule bezier
|
||||
* @nolint
|
||||
*/
|
||||
|
||||
module.exports = function(x1, y1, x2, y2, epsilon){
|
||||
|
||||
var curveX = function(t){
|
||||
var v = 1 - t;
|
||||
return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
|
||||
};
|
||||
|
||||
var curveY = function(t){
|
||||
var v = 1 - t;
|
||||
return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
|
||||
};
|
||||
|
||||
var derivativeCurveX = function(t){
|
||||
var v = 1 - t;
|
||||
return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) * x2;
|
||||
};
|
||||
|
||||
return function(t){
|
||||
|
||||
var x = t, t0, t1, t2, x2, d2, i;
|
||||
|
||||
// First try a few iterations of Newton's method -- normally very fast.
|
||||
for (t2 = x, i = 0; i < 8; i++){
|
||||
x2 = curveX(t2) - x;
|
||||
if (Math.abs(x2) < epsilon) { return curveY(t2); }
|
||||
d2 = derivativeCurveX(t2);
|
||||
if (Math.abs(d2) < 1e-6) { break; }
|
||||
t2 = t2 - x2 / d2;
|
||||
}
|
||||
|
||||
t0 = 0;
|
||||
t1 = 1;
|
||||
t2 = x;
|
||||
|
||||
if (t2 < t0) { return curveY(t0); }
|
||||
if (t2 > t1) { return curveY(t1); }
|
||||
|
||||
// Fallback to the bisection method for reliability.
|
||||
while (t0 < t1){
|
||||
x2 = curveX(t2);
|
||||
if (Math.abs(x2 - x) < epsilon) { return curveY(t2); }
|
||||
if (x > x2) { t0 = t2; }
|
||||
else { t1 = t2; }
|
||||
t2 = (t1 - t0) * 0.5 + t0;
|
||||
}
|
||||
|
||||
// Failure
|
||||
return curveY(t2);
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
155
src/apis/Easing/index.js
Normal file
155
src/apis/Easing/index.js
Normal file
@@ -0,0 +1,155 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule Easing
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var _bezier = require('./bezier');
|
||||
|
||||
/**
|
||||
* This class implements common easing functions. The math is pretty obscure,
|
||||
* but this cool website has nice visual illustrations of what they represent:
|
||||
* http://xaedes.de/dev/transitions/
|
||||
*/
|
||||
class Easing {
|
||||
static step0(n) {
|
||||
return n > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
static step1(n) {
|
||||
return n >= 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
static linear(t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
static ease(t: number): number {
|
||||
return ease(t);
|
||||
}
|
||||
|
||||
static quad(t) {
|
||||
return t * t;
|
||||
}
|
||||
|
||||
static cubic(t) {
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
static poly(n) {
|
||||
return (t) => Math.pow(t, n);
|
||||
}
|
||||
|
||||
static sin(t) {
|
||||
return 1 - Math.cos(t * Math.PI / 2);
|
||||
}
|
||||
|
||||
static circle(t) {
|
||||
return 1 - Math.sqrt(1 - t * t);
|
||||
}
|
||||
|
||||
static exp(t) {
|
||||
return Math.pow(2, 10 * (t - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple elastic interaction, similar to a spring. Default bounciness
|
||||
* is 1, which overshoots a little bit once. 0 bounciness doesn't overshoot
|
||||
* at all, and bounciness of N > 1 will overshoot about N times.
|
||||
*
|
||||
* Wolfram Plots:
|
||||
*
|
||||
* http://tiny.cc/elastic_b_1 (default bounciness = 1)
|
||||
* http://tiny.cc/elastic_b_3 (bounciness = 3)
|
||||
*/
|
||||
static elastic(bounciness: number = 1): (t: number) => number {
|
||||
var p = bounciness * Math.PI;
|
||||
return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
|
||||
}
|
||||
|
||||
static back(s: number): (t: number) => number {
|
||||
if (s === undefined) {
|
||||
s = 1.70158;
|
||||
}
|
||||
return (t) => t * t * ((s + 1) * t - s);
|
||||
}
|
||||
|
||||
static bounce(t: number): number {
|
||||
if (t < 1 / 2.75) {
|
||||
return 7.5625 * t * t;
|
||||
}
|
||||
|
||||
if (t < 2 / 2.75) {
|
||||
t -= 1.5 / 2.75;
|
||||
return 7.5625 * t * t + 0.75;
|
||||
}
|
||||
|
||||
if (t < 2.5 / 2.75) {
|
||||
t -= 2.25 / 2.75;
|
||||
return 7.5625 * t * t + 0.9375;
|
||||
}
|
||||
|
||||
t -= 2.625 / 2.75;
|
||||
return 7.5625 * t * t + 0.984375;
|
||||
}
|
||||
|
||||
static bezier(
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
epsilon?: ?number,
|
||||
): (t: number) => number {
|
||||
if (epsilon === undefined) {
|
||||
// epsilon determines the precision of the solved values
|
||||
// a good approximation is:
|
||||
var duration = 500; // duration of animation in milliseconds.
|
||||
epsilon = (1000 / 60 / duration) / 4;
|
||||
}
|
||||
|
||||
return _bezier(x1, y1, x2, y2, epsilon);
|
||||
}
|
||||
|
||||
static in(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
return easing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an easing function backwards.
|
||||
*/
|
||||
static out(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
return (t) => 1 - easing(1 - t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes any easing function symmetrical.
|
||||
*/
|
||||
static inOut(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
return (t) => {
|
||||
if (t < 0.5) {
|
||||
return easing(t * 2) / 2;
|
||||
}
|
||||
return 1 - easing((1 - t) * 2) / 2;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var ease = Easing.bezier(0.42, 0, 1, 1);
|
||||
|
||||
|
||||
|
||||
module.exports = Easing;
|
||||
48
src/apis/InteractionManager/index.js
Normal file
48
src/apis/InteractionManager/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import keyMirror from 'fbjs/lib/keyMirror'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
|
||||
const InteractionManager = {
|
||||
Events: keyMirror({
|
||||
interactionStart: true,
|
||||
interactionComplete: true
|
||||
}),
|
||||
|
||||
/**
|
||||
* Schedule a function to run after all interactions have completed.
|
||||
*/
|
||||
runAfterInteractions(callback: Function) {
|
||||
invariant(
|
||||
typeof callback === 'function',
|
||||
'Must specify a function to schedule.'
|
||||
)
|
||||
callback()
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify manager that an interaction has started.
|
||||
*/
|
||||
createInteractionHandle() {
|
||||
return 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify manager that an interaction has completed.
|
||||
*/
|
||||
clearInteractionHandle(handle) {
|
||||
invariant(
|
||||
!!handle,
|
||||
'Must provide a handle to clear.'
|
||||
)
|
||||
},
|
||||
|
||||
addListener: () => {}
|
||||
}
|
||||
|
||||
export default InteractionManager
|
||||
@@ -6,7 +6,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'invariant'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
|
||||
const connection = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection
|
||||
const eventTypes = [ 'change' ]
|
||||
@@ -75,3 +75,5 @@ const NetInfo = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NetInfo
|
||||
|
||||
124
src/apis/PanResponder/TouchHistoryMath.js
Normal file
124
src/apis/PanResponder/TouchHistoryMath.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var TouchHistoryMath = {
|
||||
/**
|
||||
* This code is optimized and not intended to look beautiful. This allows
|
||||
* computing of touch centroids that have moved after `touchesChangedAfter`
|
||||
* timeStamp. You can compute the current centroid involving all touches
|
||||
* moves after `touchesChangedAfter`, or you can compute the previous
|
||||
* centroid of all touches that were moved after `touchesChangedAfter`.
|
||||
*
|
||||
* @param {TouchHistoryMath} touchHistory Standard Responder touch track
|
||||
* data.
|
||||
* @param {number} touchesChangedAfter timeStamp after which moved touches
|
||||
* are considered "actively moving" - not just "active".
|
||||
* @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension.
|
||||
* @param {boolean} ofCurrent Compute current centroid for actively moving
|
||||
* touches vs. previous centroid of now actively moving touches.
|
||||
* @return {number} value of centroid in specified dimension.
|
||||
*/
|
||||
centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
|
||||
var touchBank = touchHistory.touchBank;
|
||||
var total = 0;
|
||||
var count = 0;
|
||||
|
||||
var oneTouchData = touchHistory.numberActiveTouches === 1 ?
|
||||
touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null;
|
||||
|
||||
if (oneTouchData !== null) {
|
||||
if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) {
|
||||
total += ofCurrent && isXAxis ? oneTouchData.currentPageX :
|
||||
ofCurrent && !isXAxis ? oneTouchData.currentPageY :
|
||||
!ofCurrent && isXAxis ? oneTouchData.previousPageX :
|
||||
oneTouchData.previousPageY;
|
||||
count = 1;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < touchBank.length; i++) {
|
||||
var touchTrack = touchBank[i];
|
||||
if (touchTrack !== null &&
|
||||
touchTrack !== undefined &&
|
||||
touchTrack.touchActive &&
|
||||
touchTrack.currentTimeStamp >= touchesChangedAfter) {
|
||||
var toAdd; // Yuck, program temporarily in invalid state.
|
||||
if (ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.currentPageX;
|
||||
} else if (ofCurrent && !isXAxis) {
|
||||
toAdd = touchTrack.currentPageY;
|
||||
} else if (!ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.previousPageX;
|
||||
} else {
|
||||
toAdd = touchTrack.previousPageY;
|
||||
}
|
||||
total += toAdd;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count > 0 ? total / count : TouchHistoryMath.noCentroid;
|
||||
},
|
||||
|
||||
currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidX: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidY: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
noCentroid: -1,
|
||||
};
|
||||
|
||||
module.exports = TouchHistoryMath;
|
||||
380
src/apis/PanResponder/index.js
Normal file
380
src/apis/PanResponder/index.js
Normal file
@@ -0,0 +1,380 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var TouchHistoryMath = require('./TouchHistoryMath');
|
||||
|
||||
var currentCentroidXOfTouchesChangedAfter =
|
||||
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
|
||||
var currentCentroidYOfTouchesChangedAfter =
|
||||
TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
|
||||
var previousCentroidXOfTouchesChangedAfter =
|
||||
TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
|
||||
var previousCentroidYOfTouchesChangedAfter =
|
||||
TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
|
||||
var currentCentroidX = TouchHistoryMath.currentCentroidX;
|
||||
var currentCentroidY = TouchHistoryMath.currentCentroidY;
|
||||
|
||||
/**
|
||||
* `PanResponder` reconciles several touches into a single gesture. It makes
|
||||
* single-touch gestures resilient to extra touches, and can be used to
|
||||
* recognize simple multi-touch gestures.
|
||||
*
|
||||
* It provides a predictable wrapper of the responder handlers provided by the
|
||||
* [gesture responder system](docs/gesture-responder-system.html).
|
||||
* For each handler, it provides a new `gestureState` object alongside the
|
||||
* native event object:
|
||||
*
|
||||
* ```
|
||||
* onPanResponderMove: (event, gestureState) => {}
|
||||
* ```
|
||||
*
|
||||
* A native event is a synthetic touch event with the following form:
|
||||
*
|
||||
* - `nativeEvent`
|
||||
* + `changedTouches` - Array of all touch events that have changed since the last event
|
||||
* + `identifier` - The ID of the touch
|
||||
* + `locationX` - The X position of the touch, relative to the element
|
||||
* + `locationY` - The Y position of the touch, relative to the element
|
||||
* + `pageX` - The X position of the touch, relative to the root element
|
||||
* + `pageY` - The Y position of the touch, relative to the root element
|
||||
* + `target` - The node id of the element receiving the touch event
|
||||
* + `timestamp` - A time identifier for the touch, useful for velocity calculation
|
||||
* + `touches` - Array of all current touches on the screen
|
||||
*
|
||||
* A `gestureState` object has the following:
|
||||
*
|
||||
* - `stateID` - ID of the gestureState- persisted as long as there at least
|
||||
* one touch on screen
|
||||
* - `moveX` - the latest screen coordinates of the recently-moved touch
|
||||
* - `moveY` - the latest screen coordinates of the recently-moved touch
|
||||
* - `x0` - the screen coordinates of the responder grant
|
||||
* - `y0` - the screen coordinates of the responder grant
|
||||
* - `dx` - accumulated distance of the gesture since the touch started
|
||||
* - `dy` - accumulated distance of the gesture since the touch started
|
||||
* - `vx` - current velocity of the gesture
|
||||
* - `vy` - current velocity of the gesture
|
||||
* - `numberActiveTouches` - Number of touches currently on screen
|
||||
*
|
||||
* ### Basic Usage
|
||||
*
|
||||
* ```
|
||||
* componentWillMount: function() {
|
||||
* this._panResponder = PanResponder.create({
|
||||
* // Ask to be the responder:
|
||||
* onStartShouldSetPanResponder: (evt, gestureState) => true,
|
||||
* onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
|
||||
* onMoveShouldSetPanResponder: (evt, gestureState) => true,
|
||||
* onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
|
||||
*
|
||||
* onPanResponderGrant: (evt, gestureState) => {
|
||||
* // The guesture has started. Show visual feedback so the user knows
|
||||
* // what is happening!
|
||||
*
|
||||
* // gestureState.{x,y}0 will be set to zero now
|
||||
* },
|
||||
* onPanResponderMove: (evt, gestureState) => {
|
||||
* // The most recent move distance is gestureState.move{X,Y}
|
||||
*
|
||||
* // The accumulated gesture distance since becoming responder is
|
||||
* // gestureState.d{x,y}
|
||||
* },
|
||||
* onPanResponderTerminationRequest: (evt, gestureState) => true,
|
||||
* onPanResponderRelease: (evt, gestureState) => {
|
||||
* // The user has released all touches while this view is the
|
||||
* // responder. This typically means a gesture has succeeded
|
||||
* },
|
||||
* onPanResponderTerminate: (evt, gestureState) => {
|
||||
* // Another component has become the responder, so this gesture
|
||||
* // should be cancelled
|
||||
* },
|
||||
* onShouldBlockNativeResponder: (evt, gestureState) => {
|
||||
* // Returns whether this component should block native components from becoming the JS
|
||||
* // responder. Returns true by default. Is currently only supported on android.
|
||||
* return true;
|
||||
* },
|
||||
* });
|
||||
* },
|
||||
*
|
||||
* render: function() {
|
||||
* return (
|
||||
* <View {...this._panResponder.panHandlers} />
|
||||
* );
|
||||
* },
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ### Working Example
|
||||
*
|
||||
* To see it in action, try the
|
||||
* [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js)
|
||||
*/
|
||||
|
||||
var PanResponder = {
|
||||
|
||||
/**
|
||||
*
|
||||
* A graphical explanation of the touch data flow:
|
||||
*
|
||||
* +----------------------------+ +--------------------------------+
|
||||
* | ResponderTouchHistoryStore | |TouchHistoryMath |
|
||||
* +----------------------------+ +----------+---------------------+
|
||||
* |Global store of touchHistory| |Allocation-less math util |
|
||||
* |including activeness, start | |on touch history (centroids |
|
||||
* |position, prev/cur position.| |and multitouch movement etc) |
|
||||
* | | | |
|
||||
* +----^-----------------------+ +----^---------------------------+
|
||||
* | |
|
||||
* | (records relevant history |
|
||||
* | of touches relevant for |
|
||||
* | implementing higher level |
|
||||
* | gestures) |
|
||||
* | |
|
||||
* +----+-----------------------+ +----|---------------------------+
|
||||
* | ResponderEventPlugin | | | Your App/Component |
|
||||
* +----------------------------+ +----|---------------------------+
|
||||
* |Negotiates which view gets | Low level | | High level |
|
||||
* |onResponderMove events. | events w/ | +-+-------+ events w/ |
|
||||
* |Also records history into | touchHistory| | Pan | multitouch + |
|
||||
* |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
|
||||
* +----------------------------+ attached to | | | distance and |
|
||||
* each event | +---------+ velocity. |
|
||||
* | |
|
||||
* | |
|
||||
* +--------------------------------+
|
||||
*
|
||||
*
|
||||
*
|
||||
* Gesture that calculates cumulative movement over time in a way that just
|
||||
* "does the right thing" for multiple touches. The "right thing" is very
|
||||
* nuanced. When moving two touches in opposite directions, the cumulative
|
||||
* distance is zero in each dimension. When two touches move in parallel five
|
||||
* pixels in the same direction, the cumulative distance is five, not ten. If
|
||||
* two touches start, one moves five in a direction, then stops and the other
|
||||
* touch moves fives in the same direction, the cumulative distance is ten.
|
||||
*
|
||||
* This logic requires a kind of processing of time "clusters" of touch events
|
||||
* so that two touch moves that essentially occur in parallel but move every
|
||||
* other frame respectively, are considered part of the same movement.
|
||||
*
|
||||
* Explanation of some of the non-obvious fields:
|
||||
*
|
||||
* - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
|
||||
* invalid. If a move event has been observed, `(moveX, moveY)` is the
|
||||
* centroid of the most recently moved "cluster" of active touches.
|
||||
* (Currently all move have the same timeStamp, but later we should add some
|
||||
* threshold for what is considered to be "moving"). If a palm is
|
||||
* accidentally counted as a touch, but a finger is moving greatly, the palm
|
||||
* will move slightly, but we only want to count the single moving touch.
|
||||
* - x0/y0: Centroid location (non-cumulative) at the time of becoming
|
||||
* responder.
|
||||
* - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
|
||||
* distance. Accounts for touch moves that are clustered together in time,
|
||||
* moving the same direction. Only valid when currently responder (otherwise,
|
||||
* it only represents the drag distance below the threshold).
|
||||
* - vx/vy: Velocity.
|
||||
*/
|
||||
|
||||
_initializeGestureState: function(gestureState) {
|
||||
gestureState.moveX = 0;
|
||||
gestureState.moveY = 0;
|
||||
gestureState.x0 = 0;
|
||||
gestureState.y0 = 0;
|
||||
gestureState.dx = 0;
|
||||
gestureState.dy = 0;
|
||||
gestureState.vx = 0;
|
||||
gestureState.vy = 0;
|
||||
gestureState.numberActiveTouches = 0;
|
||||
// All `gestureState` accounts for timeStamps up until:
|
||||
gestureState._accountsForMovesUpTo = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is nuanced and is necessary. It is incorrect to continuously take all
|
||||
* active *and* recently moved touches, find the centroid, and track how that
|
||||
* result changes over time. Instead, we must take all recently moved
|
||||
* touches, and calculate how the centroid has changed just for those
|
||||
* recently moved touches, and append that change to an accumulator. This is
|
||||
* to (at least) handle the case where the user is moving three fingers, and
|
||||
* then one of the fingers stops but the other two continue.
|
||||
*
|
||||
* This is very different than taking all of the recently moved touches and
|
||||
* storing their centroid as `dx/dy`. For correctness, we must *accumulate
|
||||
* changes* in the centroid of recently moved touches.
|
||||
*
|
||||
* There is also some nuance with how we handle multiple moved touches in a
|
||||
* single event. With the way `ReactNativeEventEmitter` dispatches touches as
|
||||
* individual events, multiple touches generate two 'move' events, each of
|
||||
* them triggering `onResponderMove`. But with the way `PanResponder` works,
|
||||
* all of the gesture inference is performed on the first dispatch, since it
|
||||
* looks at all of the touches (even the ones for which there hasn't been a
|
||||
* native dispatch yet). Therefore, `PanResponder` does not call
|
||||
* `onResponderMove` passed the first dispatch. This diverges from the
|
||||
* typical responder callback pattern (without using `PanResponder`), but
|
||||
* avoids more dispatches than necessary.
|
||||
*/
|
||||
_updateGestureStateOnMove: function(gestureState, touchHistory) {
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(
|
||||
touchHistory,
|
||||
gestureState._accountsForMovesUpTo
|
||||
);
|
||||
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(
|
||||
touchHistory,
|
||||
gestureState._accountsForMovesUpTo
|
||||
);
|
||||
var movedAfter = gestureState._accountsForMovesUpTo;
|
||||
var prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var nextDX = gestureState.dx + (x - prevX);
|
||||
var nextDY = gestureState.dy + (y - prevY);
|
||||
|
||||
// TODO: This must be filtered intelligently.
|
||||
var dt =
|
||||
(touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo);
|
||||
gestureState.vx = (nextDX - gestureState.dx) / dt;
|
||||
gestureState.vy = (nextDY - gestureState.dy) / dt;
|
||||
|
||||
gestureState.dx = nextDX;
|
||||
gestureState.dy = nextDY;
|
||||
gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} config Enhanced versions of all of the responder callbacks
|
||||
* that provide not only the typical `ResponderSyntheticEvent`, but also the
|
||||
* `PanResponder` gesture state. Simply replace the word `Responder` with
|
||||
* `PanResponder` in each of the typical `onResponder*` callbacks. For
|
||||
* example, the `config` object would look like:
|
||||
*
|
||||
* - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
|
||||
* - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
|
||||
* - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
|
||||
* - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
|
||||
* - `onPanResponderReject: (e, gestureState) => {...}`
|
||||
* - `onPanResponderGrant: (e, gestureState) => {...}`
|
||||
* - `onPanResponderStart: (e, gestureState) => {...}`
|
||||
* - `onPanResponderEnd: (e, gestureState) => {...}`
|
||||
* - `onPanResponderRelease: (e, gestureState) => {...}`
|
||||
* - `onPanResponderMove: (e, gestureState) => {...}`
|
||||
* - `onPanResponderTerminate: (e, gestureState) => {...}`
|
||||
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
|
||||
* - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
|
||||
*
|
||||
* In general, for events that have capture equivalents, we update the
|
||||
* gestureState once in the capture phase and can use it in the bubble phase
|
||||
* as well.
|
||||
*
|
||||
* Be careful with onStartShould* callbacks. They only reflect updated
|
||||
* `gestureState` for start/end events that bubble/capture to the Node.
|
||||
* Once the node is the responder, you can rely on every start/end event
|
||||
* being processed by the gesture and `gestureState` being updated
|
||||
* accordingly. (numberActiveTouches) may not be totally accurate unless you
|
||||
* are the responder.
|
||||
*/
|
||||
create: function(config) {
|
||||
var gestureState = {
|
||||
// Useful for debugging
|
||||
stateID: Math.random(),
|
||||
};
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
var panHandlers = {
|
||||
onStartShouldSetResponder: function(e) {
|
||||
return config.onStartShouldSetPanResponder === undefined ? false :
|
||||
config.onStartShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onMoveShouldSetResponder: function(e) {
|
||||
return config.onMoveShouldSetPanResponder === undefined ? false :
|
||||
config.onMoveShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onStartShouldSetResponderCapture: function(e) {
|
||||
// TODO: Actually, we should reinitialize the state any time
|
||||
// touches.length increases from 0 active to > 0 active.
|
||||
if (e.nativeEvent.touches.length === 1) {
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
}
|
||||
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
|
||||
return config.onStartShouldSetPanResponderCapture !== undefined ?
|
||||
config.onStartShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onMoveShouldSetResponderCapture: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
// Responder system incorrectly dispatches should* to current responder
|
||||
// Filter out any touch moves past the first one - we would have
|
||||
// already processed multi-touch geometry during the first event.
|
||||
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
||||
return false;
|
||||
}
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
return config.onMoveShouldSetPanResponderCapture ?
|
||||
config.onMoveShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onResponderGrant: function(e) {
|
||||
gestureState.x0 = currentCentroidX(e.touchHistory);
|
||||
gestureState.y0 = currentCentroidY(e.touchHistory);
|
||||
gestureState.dx = 0;
|
||||
gestureState.dy = 0;
|
||||
config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState);
|
||||
// TODO: t7467124 investigate if this can be removed
|
||||
return config.onShouldBlockNativeResponder === undefined ? true :
|
||||
config.onShouldBlockNativeResponder();
|
||||
},
|
||||
|
||||
onResponderReject: function(e) {
|
||||
config.onPanResponderReject && config.onPanResponderReject(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderRelease: function(e) {
|
||||
config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState);
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
},
|
||||
|
||||
onResponderStart: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
config.onPanResponderStart && config.onPanResponderStart(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderMove: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
// Guard against the dispatch of two touch moves when there are two
|
||||
// simultaneously changed touches.
|
||||
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
||||
return;
|
||||
}
|
||||
// Filter out any touch moves past the first one - we would have
|
||||
// already processed multi-touch geometry during the first event.
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
config.onPanResponderMove && config.onPanResponderMove(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderEnd: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderTerminate: function(e) {
|
||||
config.onPanResponderTerminate &&
|
||||
config.onPanResponderTerminate(e, gestureState);
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
},
|
||||
|
||||
onResponderTerminationRequest: function(e) {
|
||||
return config.onPanResponderTerminationRequest === undefined ? true :
|
||||
config.onPanResponderTerminationRequest(e, gestureState);
|
||||
},
|
||||
};
|
||||
return {panHandlers: panHandlers};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = PanResponder;
|
||||
92
src/apis/PanResponder/injectResponderEventPlugin.js
Normal file
92
src/apis/PanResponder/injectResponderEventPlugin.js
Normal file
@@ -0,0 +1,92 @@
|
||||
// based on https://github.com/facebook/react/pull/4303/files
|
||||
|
||||
import EventConstants from 'react/lib/EventConstants'
|
||||
import EventPluginRegistry from 'react/lib/EventPluginRegistry'
|
||||
import ResponderEventPlugin from 'react/lib/ResponderEventPlugin'
|
||||
import ResponderTouchHistoryStore from 'react/lib/ResponderTouchHistoryStore'
|
||||
|
||||
const {
|
||||
topMouseDown,
|
||||
topMouseMove,
|
||||
topMouseUp,
|
||||
topScroll,
|
||||
topSelectionChange,
|
||||
topTouchCancel,
|
||||
topTouchEnd,
|
||||
topTouchMove,
|
||||
topTouchStart
|
||||
} = EventConstants.topLevelTypes
|
||||
|
||||
const endDependencies = [ topMouseUp, topTouchCancel, topTouchEnd ]
|
||||
const moveDependencies = [ topMouseMove, topTouchMove ]
|
||||
const startDependencies = [ topMouseDown, topTouchStart ]
|
||||
|
||||
/**
|
||||
* Setup ResponderEventPlugin dependencies
|
||||
*/
|
||||
ResponderEventPlugin.eventTypes.responderMove.dependencies = moveDependencies
|
||||
ResponderEventPlugin.eventTypes.responderEnd.dependencies = endDependencies
|
||||
ResponderEventPlugin.eventTypes.responderStart.dependencies = startDependencies
|
||||
ResponderEventPlugin.eventTypes.responderRelease.dependencies = []
|
||||
ResponderEventPlugin.eventTypes.responderTerminationRequest.dependencies = []
|
||||
ResponderEventPlugin.eventTypes.responderGrant.dependencies = []
|
||||
ResponderEventPlugin.eventTypes.responderReject.dependencies = []
|
||||
ResponderEventPlugin.eventTypes.responderTerminate.dependencies = []
|
||||
ResponderEventPlugin.eventTypes.moveShouldSetResponder.dependencies = moveDependencies
|
||||
ResponderEventPlugin.eventTypes.selectionChangeShouldSetResponder.dependencies = [ topSelectionChange ]
|
||||
ResponderEventPlugin.eventTypes.scrollShouldSetResponder.dependencies = [ topScroll ]
|
||||
ResponderEventPlugin.eventTypes.startShouldSetResponder.dependencies = startDependencies
|
||||
|
||||
// Mobile Safari re-uses touch objects, so we copy the properties we want and normalize the identifier
|
||||
const normalizeTouches = (touches = []) => Array.prototype.slice.call(touches).map((touch) => {
|
||||
const identifier = touch.identifier > 20 ? (touch.identifier % 20) : touch.identifier
|
||||
|
||||
return {
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY,
|
||||
force: touch.force,
|
||||
identifier: identifier,
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY,
|
||||
radiusX: touch.radiusX,
|
||||
radiusY: touch.radiusY,
|
||||
rotationAngle: touch.rotationAngle,
|
||||
screenX: touch.screenX,
|
||||
screenY: touch.screenY,
|
||||
target: touch.target
|
||||
}
|
||||
})
|
||||
|
||||
const normalizeNativeEvent = (nativeEvent) => {
|
||||
const changedTouches = normalizeTouches(nativeEvent.changedTouches)
|
||||
const touches = normalizeTouches(nativeEvent.touches)
|
||||
|
||||
const event = {
|
||||
changedTouches,
|
||||
pageX: nativeEvent.pageX,
|
||||
pageY: nativeEvent.pageY,
|
||||
target: nativeEvent.target,
|
||||
// normalize the timestamp
|
||||
// https://stackoverflow.com/questions/26177087/ios-8-mobile-safari-wrong-timestamp-on-touch-events
|
||||
timestamp: Date.now(),
|
||||
touches
|
||||
}
|
||||
|
||||
if (changedTouches[0]) {
|
||||
event.identifier = changedTouches[0].identifier
|
||||
event.pageX = changedTouches[0].pageX
|
||||
event.pageY = changedTouches[0].pageY
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
const originalRecordTouchTrack = ResponderTouchHistoryStore.recordTouchTrack
|
||||
|
||||
ResponderTouchHistoryStore.recordTouchTrack = (topLevelType, nativeEvent) => {
|
||||
originalRecordTouchTrack.call(ResponderTouchHistoryStore, topLevelType, normalizeNativeEvent(nativeEvent))
|
||||
}
|
||||
|
||||
EventPluginRegistry.injectEventPluginsByName({
|
||||
ResponderEventPlugin
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { canUseDOM } from 'exenv'
|
||||
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'
|
||||
|
||||
const Platform = {
|
||||
OS: 'web',
|
||||
|
||||
@@ -1,5 +1,62 @@
|
||||
import { PropTypes } from 'react'
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ColorPropType
|
||||
*/
|
||||
|
||||
const ColorPropType = PropTypes.string
|
||||
import { PropTypes } from 'react'
|
||||
import ReactPropTypeLocationNames from 'react/lib/ReactPropTypeLocationNames'
|
||||
|
||||
var normalizeColor = require('./normalizeColor');
|
||||
|
||||
var colorPropType = function(isRequired, props, propName, componentName, location, propFullName) {
|
||||
var color = props[propName];
|
||||
if (color === undefined || color === null) {
|
||||
if (isRequired) {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
return new Error(
|
||||
'Required ' + locationName + ' `' + (propFullName || propName) +
|
||||
'` was not specified in `' + componentName + '`.'
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof color === 'number') {
|
||||
// Developers should not use a number, but we are using the prop type
|
||||
// both for user provided colors and for transformed ones. This isn't ideal
|
||||
// and should be fixed but will do for now...
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizeColor(color) === null) {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
return new Error(
|
||||
'Invalid ' + locationName + ' `' + (propFullName || propName) +
|
||||
'` supplied to `' + componentName + '`: ' + color + '\n' +
|
||||
`Valid color formats are
|
||||
- '#f0f' (#rgb)
|
||||
- '#f0fc' (#rgba)
|
||||
- '#ff00ff' (#rrggbb)
|
||||
- '#ff00ff00' (#rrggbbaa)
|
||||
- 'rgb(255, 255, 255)'
|
||||
- 'rgba(255, 255, 255, 1.0)'
|
||||
- 'hsl(360, 100%, 100%)'
|
||||
- 'hsla(360, 100%, 100%, 1.0)'
|
||||
- 'transparent'
|
||||
- 'red'
|
||||
- 0xff00ff00 (0xrrggbbaa)
|
||||
`);
|
||||
}
|
||||
};
|
||||
|
||||
var ColorPropType = colorPropType.bind(null, false /* isRequired */);
|
||||
ColorPropType.isRequired = colorPropType.bind(null, true /* isRequired */);
|
||||
|
||||
export default ColorPropType
|
||||
|
||||
@@ -46,8 +46,9 @@ export default class Store {
|
||||
// transform the declarations into CSS rules with vendor-prefixes
|
||||
const buildCSSRules = (property, values) => {
|
||||
return values.reduce((cssRules, value) => {
|
||||
const declarations = prefixer.prefix({ [property]: value })
|
||||
const declarations = prefixer({ [property]: value })
|
||||
const cssDeclarations = Object.keys(declarations).reduce((str, prop) => {
|
||||
const value = declarations[prop]
|
||||
str += `${hyphenate(prop)}:${value};`
|
||||
return str
|
||||
}, '')
|
||||
|
||||
@@ -39,7 +39,7 @@ export default class StyleSheetRegistry {
|
||||
}
|
||||
|
||||
_className = classList.join(' ')
|
||||
_style = prefixer.prefix(_style)
|
||||
_style = prefixer(_style)
|
||||
|
||||
return { className: _className, style: _style }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PropTypes } from 'react'
|
||||
import ImageStylePropTypes from '../../components/Image/ImageStylePropTypes'
|
||||
import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
|
||||
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
|
||||
import invariant from 'invariant'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
|
||||
export default class StyleSheetValidation {
|
||||
static validateStyleProp(prop, style, caller) {
|
||||
|
||||
@@ -6,9 +6,9 @@ import Store from '../Store'
|
||||
suite('apis/StyleSheet/Store', () => {
|
||||
suite('the constructor', () => {
|
||||
test('initialState', () => {
|
||||
const initialState = { classNames: { 'alignItems:center': '__classname__' } }
|
||||
const initialState = { classNames: { 'textAlign:center': '__classname__' } }
|
||||
const store = new Store(initialState)
|
||||
assert.deepEqual(store._classNames['alignItems:center'], '__classname__')
|
||||
assert.deepEqual(store._classNames['textAlign:center'], '__classname__')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,80 +16,81 @@ suite('apis/StyleSheet/Store', () => {
|
||||
test('returns a declaration-specific className', () => {
|
||||
const initialState = {
|
||||
classNames: {
|
||||
'alignItems:center': '__expected__',
|
||||
'alignItems:flex-start': '__error__'
|
||||
'textAlign:center': '__expected__',
|
||||
'textAlign:left': '__error__'
|
||||
}
|
||||
}
|
||||
const store = new Store(initialState)
|
||||
assert.deepEqual(store.get('alignItems', 'center'), '__expected__')
|
||||
assert.deepEqual(store.get('textAlign', 'center'), '__expected__')
|
||||
})
|
||||
})
|
||||
|
||||
suite('#set', () => {
|
||||
test('stores declarations', () => {
|
||||
const store = new Store()
|
||||
store.set('alignItems', 'center')
|
||||
store.set('flexGrow', 0)
|
||||
store.set('flexGrow', 1)
|
||||
store.set('flexGrow', 2)
|
||||
store.set('textAlign', 'center')
|
||||
store.set('marginTop', 0)
|
||||
store.set('marginTop', 1)
|
||||
store.set('marginTop', 2)
|
||||
assert.deepEqual(store._declarations, {
|
||||
alignItems: [ 'center' ],
|
||||
flexGrow: [ 0, 1, 2 ]
|
||||
textAlign: [ 'center' ],
|
||||
marginTop: [ 0, 1, 2 ]
|
||||
})
|
||||
})
|
||||
|
||||
test('human-readable classNames', () => {
|
||||
const store = new Store()
|
||||
store.set('alignItems', 'center')
|
||||
store.set('flexGrow', 0)
|
||||
store.set('flexGrow', 1)
|
||||
store.set('flexGrow', 2)
|
||||
store.set('textAlign', 'center')
|
||||
store.set('marginTop', 0)
|
||||
store.set('marginTop', 1)
|
||||
store.set('marginTop', 2)
|
||||
assert.deepEqual(store._classNames, {
|
||||
'alignItems:center': 'alignItems:center',
|
||||
'flexGrow:0': 'flexGrow:0',
|
||||
'flexGrow:1': 'flexGrow:1',
|
||||
'flexGrow:2': 'flexGrow:2'
|
||||
'textAlign:center': 'textAlign:center',
|
||||
'marginTop:0': 'marginTop:0',
|
||||
'marginTop:1': 'marginTop:1',
|
||||
'marginTop:2': 'marginTop:2'
|
||||
})
|
||||
})
|
||||
|
||||
test('obfuscated classNames', () => {
|
||||
const store = new Store({}, { obfuscateClassNames: true })
|
||||
store.set('alignItems', 'center')
|
||||
store.set('flexGrow', 0)
|
||||
store.set('flexGrow', 1)
|
||||
store.set('flexGrow', 2)
|
||||
store.set('textAlign', 'center')
|
||||
store.set('marginTop', 0)
|
||||
store.set('marginTop', 1)
|
||||
store.set('marginTop', 2)
|
||||
assert.deepEqual(store._classNames, {
|
||||
'alignItems:center': '_s_1',
|
||||
'flexGrow:0': '_s_2',
|
||||
'flexGrow:1': '_s_3',
|
||||
'flexGrow:2': '_s_4'
|
||||
'textAlign:center': '_s_1',
|
||||
'marginTop:0': '_s_2',
|
||||
'marginTop:1': '_s_3',
|
||||
'marginTop:2': '_s_4'
|
||||
})
|
||||
})
|
||||
|
||||
test('replaces space characters', () => {
|
||||
const store = new Store()
|
||||
|
||||
store.set('margin', '0 auto')
|
||||
assert.equal(store.get('margin', '0 auto'), 'margin\:0-auto')
|
||||
store.set('backgroundPosition', 'top left')
|
||||
assert.equal(store.get('backgroundPosition', 'top left'), 'backgroundPosition\:top-left')
|
||||
})
|
||||
})
|
||||
|
||||
suite('#toString', () => {
|
||||
test('human-readable style sheet', () => {
|
||||
const store = new Store()
|
||||
store.set('alignItems', 'center')
|
||||
store.set('textAlign', 'center')
|
||||
store.set('backgroundColor', 'rgba(0,0,0,0)')
|
||||
store.set('color', '#fff')
|
||||
store.set('fontFamily', '"Helvetica Neue", Arial, sans-serif')
|
||||
store.set('marginBottom', '0px')
|
||||
store.set('width', '100%')
|
||||
console.log(store.toString())
|
||||
|
||||
const expected = '/* 6 unique declarations */\n' +
|
||||
'.alignItems\\:center{align-items:center;}\n' +
|
||||
'.backgroundColor\\:rgba\\(0\\,0\\,0\\,0\\){background-color:rgba(0,0,0,0);}\n' +
|
||||
'.color\\:\\#fff{color:#fff;}\n' +
|
||||
'.fontFamily\\:\\"Helvetica-Neue\\"\\,-Arial\\,-sans-serif{font-family:"Helvetica Neue", Arial, sans-serif;}\n' +
|
||||
'.marginBottom\\:0px{margin-bottom:0px;}\n' +
|
||||
'.textAlign\\:center{text-align:center;}\n' +
|
||||
'.width\\:100\\%{width:100%;}'
|
||||
|
||||
assert.equal(store.toString(), expected)
|
||||
@@ -97,18 +98,18 @@ suite('apis/StyleSheet/Store', () => {
|
||||
|
||||
test('obfuscated style sheet', () => {
|
||||
const store = new Store({}, { obfuscateClassNames: true })
|
||||
store.set('alignItems', 'center')
|
||||
store.set('textAlign', 'center')
|
||||
store.set('marginBottom', '0px')
|
||||
store.set('margin', '1px')
|
||||
store.set('margin', '2px')
|
||||
store.set('margin', '3px')
|
||||
|
||||
const expected = '/* 5 unique declarations */\n' +
|
||||
'._s_1{align-items:center;}\n' +
|
||||
'._s_3{margin:1px;}\n' +
|
||||
'._s_4{margin:2px;}\n' +
|
||||
'._s_5{margin:3px;}\n' +
|
||||
'._s_2{margin-bottom:0px;}'
|
||||
'._s_2{margin-bottom:0px;}\n' +
|
||||
'._s_1{text-align:center;}'
|
||||
|
||||
assert.equal(store.toString(), expected)
|
||||
})
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import ReactPropTypeLocationNames from 'react/lib/ReactPropTypeLocationNames'
|
||||
import invariant from 'invariant'
|
||||
|
||||
export default function createStrictShapeTypeChecker(shapeTypes) {
|
||||
function checkType(isRequired, props, propName, componentName, location?) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
import invariant from 'invariant'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import expandStyle from './expandStyle'
|
||||
|
||||
export default function flattenStyle(style): ?Object {
|
||||
|
||||
352
src/apis/StyleSheet/normalizeColor.js
Normal file
352
src/apis/StyleSheet/normalizeColor.js
Normal file
@@ -0,0 +1,352 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule normalizeColor
|
||||
* @flow
|
||||
*/
|
||||
/* eslint no-bitwise: 0 */
|
||||
'use strict';
|
||||
|
||||
function normalizeColor(color: string | number): ?number {
|
||||
var match;
|
||||
|
||||
if (typeof color === 'number') {
|
||||
if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
|
||||
return color;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ordered based on occurrences on Facebook codebase
|
||||
if ((match = matchers.hex6.exec(color))) {
|
||||
return parseInt(match[1] + 'ff', 16) >>> 0;
|
||||
}
|
||||
|
||||
if (names.hasOwnProperty(color)) {
|
||||
return names[color];
|
||||
}
|
||||
|
||||
if ((match = matchers.rgb.exec(color))) {
|
||||
return (
|
||||
parse255(match[1]) << 24 | // r
|
||||
parse255(match[2]) << 16 | // g
|
||||
parse255(match[3]) << 8 | // b
|
||||
0x000000ff // a
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
if ((match = matchers.rgba.exec(color))) {
|
||||
return (
|
||||
parse255(match[1]) << 24 | // r
|
||||
parse255(match[2]) << 16 | // g
|
||||
parse255(match[3]) << 8 | // b
|
||||
parse1(match[4]) // a
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
if ((match = matchers.hex3.exec(color))) {
|
||||
return parseInt(
|
||||
match[1] + match[1] + // r
|
||||
match[2] + match[2] + // g
|
||||
match[3] + match[3] + // b
|
||||
'ff', // a
|
||||
16
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-color-4/#hex-notation
|
||||
if ((match = matchers.hex8.exec(color))) {
|
||||
return parseInt(match[1], 16) >>> 0;
|
||||
}
|
||||
|
||||
if ((match = matchers.hex4.exec(color))) {
|
||||
return parseInt(
|
||||
match[1] + match[1] + // r
|
||||
match[2] + match[2] + // g
|
||||
match[3] + match[3] + // b
|
||||
match[4] + match[4], // a
|
||||
16
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
if ((match = matchers.hsl.exec(color))) {
|
||||
return (
|
||||
hslToRgb(
|
||||
parse360(match[1]), // h
|
||||
parsePercentage(match[2]), // s
|
||||
parsePercentage(match[3]) // l
|
||||
) |
|
||||
0x000000ff // a
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
if ((match = matchers.hsla.exec(color))) {
|
||||
return (
|
||||
hslToRgb(
|
||||
parse360(match[1]), // h
|
||||
parsePercentage(match[2]), // s
|
||||
parsePercentage(match[3]) // l
|
||||
) |
|
||||
parse1(match[4]) // a
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function hue2rgb(p: number, q: number, t: number): number {
|
||||
if (t < 0) {
|
||||
t += 1;
|
||||
}
|
||||
if (t > 1) {
|
||||
t -= 1;
|
||||
}
|
||||
if (t < 1 / 6) {
|
||||
return p + (q - p) * 6 * t;
|
||||
}
|
||||
if (t < 1 / 2) {
|
||||
return q;
|
||||
}
|
||||
if (t < 2 / 3) {
|
||||
return p + (q - p) * (2 / 3 - t) * 6;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
function hslToRgb(h: number, s: number, l: number): number {
|
||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
var r = hue2rgb(p, q, h + 1 / 3);
|
||||
var g = hue2rgb(p, q, h);
|
||||
var b = hue2rgb(p, q, h - 1 / 3);
|
||||
|
||||
return (
|
||||
Math.round(r * 255) << 24 |
|
||||
Math.round(g * 255) << 16 |
|
||||
Math.round(b * 255) << 8
|
||||
);
|
||||
}
|
||||
|
||||
// var INTEGER = '[-+]?\\d+';
|
||||
var NUMBER = '[-+]?\\d*\\.?\\d+';
|
||||
var PERCENTAGE = NUMBER + '%';
|
||||
|
||||
function call(...args) {
|
||||
return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)';
|
||||
}
|
||||
|
||||
var matchers = {
|
||||
rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)),
|
||||
rgba: new RegExp('rgba' + call(NUMBER, NUMBER, NUMBER, NUMBER)),
|
||||
hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)),
|
||||
hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)),
|
||||
hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
||||
hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
|
||||
hex6: /^#([0-9a-fA-F]{6})$/,
|
||||
hex8: /^#([0-9a-fA-F]{8})$/,
|
||||
};
|
||||
|
||||
function parse255(str: string): number {
|
||||
var int = parseInt(str, 10);
|
||||
if (int < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (int > 255) {
|
||||
return 255;
|
||||
}
|
||||
return int;
|
||||
}
|
||||
|
||||
function parse360(str: string): number {
|
||||
var int = parseFloat(str);
|
||||
return (((int % 360) + 360) % 360) / 360;
|
||||
}
|
||||
|
||||
function parse1(str: string): number {
|
||||
var num = parseFloat(str);
|
||||
if (num < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (num > 1) {
|
||||
return 255;
|
||||
}
|
||||
return Math.round(num * 255);
|
||||
}
|
||||
|
||||
function parsePercentage(str: string): number {
|
||||
// parseFloat conveniently ignores the final %
|
||||
var int = parseFloat(str, 10);
|
||||
if (int < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (int > 100) {
|
||||
return 1;
|
||||
}
|
||||
return int / 100;
|
||||
}
|
||||
|
||||
var names = {
|
||||
/* @edit start */
|
||||
inherit: 'inherit',
|
||||
/* @edit end */
|
||||
transparent: 0x00000000,
|
||||
|
||||
// http://www.w3.org/TR/css3-color/#svg-color
|
||||
aliceblue: 0xf0f8ffff,
|
||||
antiquewhite: 0xfaebd7ff,
|
||||
aqua: 0x00ffffff,
|
||||
aquamarine: 0x7fffd4ff,
|
||||
azure: 0xf0ffffff,
|
||||
beige: 0xf5f5dcff,
|
||||
bisque: 0xffe4c4ff,
|
||||
black: 0x000000ff,
|
||||
blanchedalmond: 0xffebcdff,
|
||||
blue: 0x0000ffff,
|
||||
blueviolet: 0x8a2be2ff,
|
||||
brown: 0xa52a2aff,
|
||||
burlywood: 0xdeb887ff,
|
||||
burntsienna: 0xea7e5dff,
|
||||
cadetblue: 0x5f9ea0ff,
|
||||
chartreuse: 0x7fff00ff,
|
||||
chocolate: 0xd2691eff,
|
||||
coral: 0xff7f50ff,
|
||||
cornflowerblue: 0x6495edff,
|
||||
cornsilk: 0xfff8dcff,
|
||||
crimson: 0xdc143cff,
|
||||
cyan: 0x00ffffff,
|
||||
darkblue: 0x00008bff,
|
||||
darkcyan: 0x008b8bff,
|
||||
darkgoldenrod: 0xb8860bff,
|
||||
darkgray: 0xa9a9a9ff,
|
||||
darkgreen: 0x006400ff,
|
||||
darkgrey: 0xa9a9a9ff,
|
||||
darkkhaki: 0xbdb76bff,
|
||||
darkmagenta: 0x8b008bff,
|
||||
darkolivegreen: 0x556b2fff,
|
||||
darkorange: 0xff8c00ff,
|
||||
darkorchid: 0x9932ccff,
|
||||
darkred: 0x8b0000ff,
|
||||
darksalmon: 0xe9967aff,
|
||||
darkseagreen: 0x8fbc8fff,
|
||||
darkslateblue: 0x483d8bff,
|
||||
darkslategray: 0x2f4f4fff,
|
||||
darkslategrey: 0x2f4f4fff,
|
||||
darkturquoise: 0x00ced1ff,
|
||||
darkviolet: 0x9400d3ff,
|
||||
deeppink: 0xff1493ff,
|
||||
deepskyblue: 0x00bfffff,
|
||||
dimgray: 0x696969ff,
|
||||
dimgrey: 0x696969ff,
|
||||
dodgerblue: 0x1e90ffff,
|
||||
firebrick: 0xb22222ff,
|
||||
floralwhite: 0xfffaf0ff,
|
||||
forestgreen: 0x228b22ff,
|
||||
fuchsia: 0xff00ffff,
|
||||
gainsboro: 0xdcdcdcff,
|
||||
ghostwhite: 0xf8f8ffff,
|
||||
gold: 0xffd700ff,
|
||||
goldenrod: 0xdaa520ff,
|
||||
gray: 0x808080ff,
|
||||
green: 0x008000ff,
|
||||
greenyellow: 0xadff2fff,
|
||||
grey: 0x808080ff,
|
||||
honeydew: 0xf0fff0ff,
|
||||
hotpink: 0xff69b4ff,
|
||||
indianred: 0xcd5c5cff,
|
||||
indigo: 0x4b0082ff,
|
||||
ivory: 0xfffff0ff,
|
||||
khaki: 0xf0e68cff,
|
||||
lavender: 0xe6e6faff,
|
||||
lavenderblush: 0xfff0f5ff,
|
||||
lawngreen: 0x7cfc00ff,
|
||||
lemonchiffon: 0xfffacdff,
|
||||
lightblue: 0xadd8e6ff,
|
||||
lightcoral: 0xf08080ff,
|
||||
lightcyan: 0xe0ffffff,
|
||||
lightgoldenrodyellow: 0xfafad2ff,
|
||||
lightgray: 0xd3d3d3ff,
|
||||
lightgreen: 0x90ee90ff,
|
||||
lightgrey: 0xd3d3d3ff,
|
||||
lightpink: 0xffb6c1ff,
|
||||
lightsalmon: 0xffa07aff,
|
||||
lightseagreen: 0x20b2aaff,
|
||||
lightskyblue: 0x87cefaff,
|
||||
lightslategray: 0x778899ff,
|
||||
lightslategrey: 0x778899ff,
|
||||
lightsteelblue: 0xb0c4deff,
|
||||
lightyellow: 0xffffe0ff,
|
||||
lime: 0x00ff00ff,
|
||||
limegreen: 0x32cd32ff,
|
||||
linen: 0xfaf0e6ff,
|
||||
magenta: 0xff00ffff,
|
||||
maroon: 0x800000ff,
|
||||
mediumaquamarine: 0x66cdaaff,
|
||||
mediumblue: 0x0000cdff,
|
||||
mediumorchid: 0xba55d3ff,
|
||||
mediumpurple: 0x9370dbff,
|
||||
mediumseagreen: 0x3cb371ff,
|
||||
mediumslateblue: 0x7b68eeff,
|
||||
mediumspringgreen: 0x00fa9aff,
|
||||
mediumturquoise: 0x48d1ccff,
|
||||
mediumvioletred: 0xc71585ff,
|
||||
midnightblue: 0x191970ff,
|
||||
mintcream: 0xf5fffaff,
|
||||
mistyrose: 0xffe4e1ff,
|
||||
moccasin: 0xffe4b5ff,
|
||||
navajowhite: 0xffdeadff,
|
||||
navy: 0x000080ff,
|
||||
oldlace: 0xfdf5e6ff,
|
||||
olive: 0x808000ff,
|
||||
olivedrab: 0x6b8e23ff,
|
||||
orange: 0xffa500ff,
|
||||
orangered: 0xff4500ff,
|
||||
orchid: 0xda70d6ff,
|
||||
palegoldenrod: 0xeee8aaff,
|
||||
palegreen: 0x98fb98ff,
|
||||
paleturquoise: 0xafeeeeff,
|
||||
palevioletred: 0xdb7093ff,
|
||||
papayawhip: 0xffefd5ff,
|
||||
peachpuff: 0xffdab9ff,
|
||||
peru: 0xcd853fff,
|
||||
pink: 0xffc0cbff,
|
||||
plum: 0xdda0ddff,
|
||||
powderblue: 0xb0e0e6ff,
|
||||
purple: 0x800080ff,
|
||||
rebeccapurple: 0x663399ff,
|
||||
red: 0xff0000ff,
|
||||
rosybrown: 0xbc8f8fff,
|
||||
royalblue: 0x4169e1ff,
|
||||
saddlebrown: 0x8b4513ff,
|
||||
salmon: 0xfa8072ff,
|
||||
sandybrown: 0xf4a460ff,
|
||||
seagreen: 0x2e8b57ff,
|
||||
seashell: 0xfff5eeff,
|
||||
sienna: 0xa0522dff,
|
||||
silver: 0xc0c0c0ff,
|
||||
skyblue: 0x87ceebff,
|
||||
slateblue: 0x6a5acdff,
|
||||
slategray: 0x708090ff,
|
||||
slategrey: 0x708090ff,
|
||||
snow: 0xfffafaff,
|
||||
springgreen: 0x00ff7fff,
|
||||
steelblue: 0x4682b4ff,
|
||||
tan: 0xd2b48cff,
|
||||
teal: 0x008080ff,
|
||||
thistle: 0xd8bfd8ff,
|
||||
tomato: 0xff6347ff,
|
||||
turquoise: 0x40e0d0ff,
|
||||
violet: 0xee82eeff,
|
||||
wheat: 0xf5deb3ff,
|
||||
white: 0xffffffff,
|
||||
whitesmoke: 0xf5f5f5ff,
|
||||
yellow: 0xffff00ff,
|
||||
yellowgreen: 0x9acd32ff,
|
||||
};
|
||||
|
||||
module.exports = normalizeColor;
|
||||
@@ -1,3 +1,3 @@
|
||||
import Prefixer from 'inline-style-prefixer'
|
||||
const prefixer = new Prefixer()
|
||||
const prefixer = Prefixer.prefixAll
|
||||
export default prefixer
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import CSSPropertyOperations from 'react/lib/CSSPropertyOperations'
|
||||
|
||||
const measureAll = (node, callback, relativeToNativeNode) => {
|
||||
const { height, left, top, width } = node.getBoundingClientRect()
|
||||
const relativeNode = relativeToNativeNode || node.parentNode
|
||||
const relativeRect = relativeNode.getBoundingClientRect()
|
||||
const { height, left, top, width } = node.getBoundingClientRect()
|
||||
const x = left - relativeRect.left
|
||||
const y = top - relativeRect.top
|
||||
callback(x, y, width, height, left, top)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
@@ -18,6 +19,7 @@ const keyframeEffects = [
|
||||
{ transform: 'scale(0.95)', opacity: 0.5 }
|
||||
]
|
||||
|
||||
@NativeMethodsDecorator
|
||||
export default class ActivityIndicator extends Component {
|
||||
static propTypes = {
|
||||
animating: PropTypes.bool,
|
||||
@@ -39,19 +41,11 @@ export default class ActivityIndicator extends Component {
|
||||
if (document.documentElement.animate) {
|
||||
this._player = ReactDOM.findDOMNode(this._indicatorRef).animate(keyframeEffects, animationEffectTimingProperties)
|
||||
}
|
||||
if (this.props.animating) {
|
||||
this._player.play()
|
||||
} else {
|
||||
this._player.cancel()
|
||||
}
|
||||
this._manageAnimation()
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.animating) {
|
||||
this._player.play()
|
||||
} else {
|
||||
this._player.cancel()
|
||||
}
|
||||
this._manageAnimation()
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -77,6 +71,16 @@ export default class ActivityIndicator extends Component {
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
_manageAnimation() {
|
||||
if (this._player) {
|
||||
if (this.props.animating) {
|
||||
this._player.play()
|
||||
} else {
|
||||
this._player.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
|
||||
@@ -17,6 +18,7 @@ const roleComponents = {
|
||||
region: 'section'
|
||||
}
|
||||
|
||||
@NativeMethodsDecorator
|
||||
export default class CoreComponent extends Component {
|
||||
static propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
|
||||
10
src/components/Image/ImageResizeMode.js
Normal file
10
src/components/Image/ImageResizeMode.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import keyMirror from 'fbjs/lib/keyMirror'
|
||||
|
||||
const ImageResizeMode = keyMirror({
|
||||
contain: null,
|
||||
cover: null,
|
||||
none: null,
|
||||
stretch: null
|
||||
})
|
||||
|
||||
export default ImageResizeMode
|
||||
@@ -1,6 +1,8 @@
|
||||
/* global window */
|
||||
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import CoreComponent from '../CoreComponent'
|
||||
import ImageResizeMode from './ImageResizeMode'
|
||||
import ImageStylePropTypes from './ImageStylePropTypes'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
|
||||
@@ -12,17 +14,8 @@ const STATUS_LOADING = 'LOADING'
|
||||
const STATUS_PENDING = 'PENDING'
|
||||
const STATUS_IDLE = 'IDLE'
|
||||
|
||||
@NativeMethodsDecorator
|
||||
export default class Image extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
const { uri } = props.source
|
||||
// state
|
||||
this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE }
|
||||
// autobinding
|
||||
this._onError = this._onError.bind(this)
|
||||
this._onLoad = this._onLoad.bind(this)
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
|
||||
accessible: CoreComponent.propTypes.accessible,
|
||||
@@ -45,6 +38,18 @@ export default class Image extends Component {
|
||||
source: {}
|
||||
};
|
||||
|
||||
static resizeMode = ImageResizeMode;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
const { uri } = props.source
|
||||
// state
|
||||
this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE }
|
||||
// autobinding
|
||||
this._onError = this._onError.bind(this)
|
||||
this._onLoad = this._onLoad.bind(this)
|
||||
}
|
||||
|
||||
_createImageLoader() {
|
||||
const { source } = this.props
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ScrollView from '../ScrollView'
|
||||
|
||||
@NativeMethodsDecorator
|
||||
export default class ListView extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.any,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'invariant'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import Platform from '../../apis/Platform'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
|
||||
import debounce from 'lodash.debounce'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import View from '../View'
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
flex: 1,
|
||||
overflow: 'auto'
|
||||
},
|
||||
initialContentContainer: {
|
||||
flex: 1
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
})
|
||||
|
||||
@NativeMethodsDecorator
|
||||
export default class ScrollView extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.any,
|
||||
@@ -104,7 +93,6 @@ export default class ScrollView extends Component {
|
||||
|
||||
return (
|
||||
<View
|
||||
_className='ScrollView'
|
||||
onScroll={(e) => this._onScroll(e)}
|
||||
onTouchMove={(e) => this._maybePreventScroll(e)}
|
||||
onWheel={(e) => this._maybePreventScroll(e)}
|
||||
@@ -127,3 +115,16 @@ export default class ScrollView extends Component {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
flex: 1,
|
||||
overflow: 'auto'
|
||||
},
|
||||
initialContentContainer: {
|
||||
flex: 1
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,27 +1,11 @@
|
||||
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
|
||||
import CoreComponent from '../CoreComponent'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
|
||||
import TextStylePropTypes from './TextStylePropTypes'
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
color: 'inherit',
|
||||
display: 'inline',
|
||||
font: 'inherit',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
textDecoration: 'none',
|
||||
wordWrap: 'break-word'
|
||||
},
|
||||
singleLineStyle: {
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
})
|
||||
|
||||
@NativeMethodsDecorator
|
||||
export default class Text extends Component {
|
||||
static propTypes = {
|
||||
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
|
||||
@@ -64,3 +48,21 @@ export default class Text extends Component {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
color: 'inherit',
|
||||
display: 'inline',
|
||||
font: 'inherit',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
textDecoration: 'none',
|
||||
wordWrap: 'break-word'
|
||||
},
|
||||
singleLineStyle: {
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
|
||||
import CoreComponent from '../CoreComponent'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
@@ -6,12 +7,8 @@ import Text from '../Text'
|
||||
import TextareaAutosize from 'react-textarea-autosize'
|
||||
import View from '../View'
|
||||
|
||||
@NativeMethodsDecorator
|
||||
export default class TextInput extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = { showPlaceholder: !props.value && !props.defaultValue }
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
...View.propTypes,
|
||||
autoComplete: PropTypes.bool,
|
||||
@@ -47,6 +44,23 @@ export default class TextInput extends Component {
|
||||
style: {}
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = { showPlaceholder: !props.value && !props.defaultValue }
|
||||
}
|
||||
|
||||
blur() {
|
||||
this.refs.input.blur()
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.refs.input.focus()
|
||||
}
|
||||
|
||||
setNativeProps(props) {
|
||||
this.refs.input.setNativeProps(props)
|
||||
}
|
||||
|
||||
_onBlur(e) {
|
||||
const { onBlur } = this.props
|
||||
const value = e.target.value
|
||||
|
||||
43
src/components/Touchable/BoundingDimensions.js
Normal file
43
src/components/Touchable/BoundingDimensions.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var PooledClass = require('react/lib/PooledClass');
|
||||
|
||||
var twoArgumentPooler = PooledClass.twoArgumentPooler;
|
||||
|
||||
/**
|
||||
* PooledClass representing the bounding rectangle of a region.
|
||||
*
|
||||
* @param {number} width Width of bounding rectangle.
|
||||
* @param {number} height Height of bounding rectangle.
|
||||
* @constructor BoundingDimensions
|
||||
*/
|
||||
function BoundingDimensions(width, height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
BoundingDimensions.prototype.destructor = function() {
|
||||
this.width = null;
|
||||
this.height = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} element Element to return `BoundingDimensions` for.
|
||||
* @return {BoundingDimensions} Bounding dimensions of `element`.
|
||||
*/
|
||||
BoundingDimensions.getPooledFromElement = function(element) {
|
||||
return BoundingDimensions.getPooled(
|
||||
element.offsetWidth,
|
||||
element.offsetHeight
|
||||
);
|
||||
};
|
||||
|
||||
PooledClass.addPoolingTo(BoundingDimensions, twoArgumentPooler);
|
||||
|
||||
module.exports = BoundingDimensions;
|
||||
33
src/components/Touchable/Position.js
Normal file
33
src/components/Touchable/Position.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var PooledClass = require('react/lib/PooledClass');
|
||||
|
||||
var twoArgumentPooler = PooledClass.twoArgumentPooler;
|
||||
|
||||
/**
|
||||
* Position does not expose methods for construction via an `HTMLDOMElement`,
|
||||
* because it isn't meaningful to construct such a thing without first defining
|
||||
* a frame of reference.
|
||||
*
|
||||
* @param {number} windowStartKey Key that window starts at.
|
||||
* @param {number} windowEndKey Key that window ends at.
|
||||
*/
|
||||
function Position(left, top) {
|
||||
this.left = left;
|
||||
this.top = top;
|
||||
}
|
||||
|
||||
Position.prototype.destructor = function() {
|
||||
this.left = null;
|
||||
this.top = null;
|
||||
};
|
||||
|
||||
PooledClass.addPoolingTo(Position, twoArgumentPooler);
|
||||
|
||||
module.exports = Position;
|
||||
719
src/components/Touchable/Touchable.js
Normal file
719
src/components/Touchable/Touchable.js
Normal file
@@ -0,0 +1,719 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/* @edit start */
|
||||
var BoundingDimensions = require('./BoundingDimensions');
|
||||
var Position = require('./Position');
|
||||
var TouchEventUtils = require('fbjs/lib/TouchEventUtils');
|
||||
var keyMirror = require('fbjs/lib/keyMirror');
|
||||
var UIManager = require('../../apis/UIManager');
|
||||
/* @edit end */
|
||||
|
||||
/**
|
||||
* `Touchable`: Taps done right.
|
||||
*
|
||||
* You hook your `ResponderEventPlugin` events into `Touchable`. `Touchable`
|
||||
* will measure time/geometry and tells you when to give feedback to the user.
|
||||
*
|
||||
* ====================== Touchable Tutorial ===============================
|
||||
* The `Touchable` mixin helps you handle the "press" interaction. It analyzes
|
||||
* the geometry of elements, and observes when another responder (scroll view
|
||||
* etc) has stolen the touch lock. It notifies your component when it should
|
||||
* give feedback to the user. (bouncing/highlighting/unhighlighting).
|
||||
*
|
||||
* - When a touch was activated (typically you highlight)
|
||||
* - When a touch was deactivated (typically you unhighlight)
|
||||
* - When a touch was "pressed" - a touch ended while still within the geometry
|
||||
* of the element, and no other element (like scroller) has "stolen" touch
|
||||
* lock ("responder") (Typically you bounce the element).
|
||||
*
|
||||
* A good tap interaction isn't as simple as you might think. There should be a
|
||||
* slight delay before showing a highlight when starting a touch. If a
|
||||
* subsequent touch move exceeds the boundary of the element, it should
|
||||
* unhighlight, but if that same touch is brought back within the boundary, it
|
||||
* should rehighlight again. A touch can move in and out of that boundary
|
||||
* several times, each time toggling highlighting, but a "press" is only
|
||||
* triggered if that touch ends while within the element's boundary and no
|
||||
* scroller (or anything else) has stolen the lock on touches.
|
||||
*
|
||||
* To create a new type of component that handles interaction using the
|
||||
* `Touchable` mixin, do the following:
|
||||
*
|
||||
* - Initialize the `Touchable` state.
|
||||
*
|
||||
* getInitialState: function() {
|
||||
* return merge(this.touchableGetInitialState(), yourComponentState);
|
||||
* }
|
||||
*
|
||||
* - Choose the rendered component who's touches should start the interactive
|
||||
* sequence. On that rendered node, forward all `Touchable` responder
|
||||
* handlers. You can choose any rendered node you like. Choose a node whose
|
||||
* hit target you'd like to instigate the interaction sequence:
|
||||
*
|
||||
* // In render function:
|
||||
* return (
|
||||
* <View
|
||||
* onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
* onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
* onResponderGrant={this.touchableHandleResponderGrant}
|
||||
* onResponderMove={this.touchableHandleResponderMove}
|
||||
* onResponderRelease={this.touchableHandleResponderRelease}
|
||||
* onResponderTerminate={this.touchableHandleResponderTerminate}>
|
||||
* <View>
|
||||
* Even though the hit detection/interactions are triggered by the
|
||||
* wrapping (typically larger) node, we usually end up implementing
|
||||
* custom logic that highlights this inner one.
|
||||
* </View>
|
||||
* </View>
|
||||
* );
|
||||
*
|
||||
* - You may set up your own handlers for each of these events, so long as you
|
||||
* also invoke the `touchable*` handlers inside of your custom handler.
|
||||
*
|
||||
* - Implement the handlers on your component class in order to provide
|
||||
* feedback to the user. See documentation for each of these class methods
|
||||
* that you should implement.
|
||||
*
|
||||
* touchableHandlePress: function() {
|
||||
* this.performBounceAnimation(); // or whatever you want to do.
|
||||
* },
|
||||
* touchableHandleActivePressIn: function() {
|
||||
* this.beginHighlighting(...); // Whatever you like to convey activation
|
||||
* },
|
||||
* touchableHandleActivePressOut: function() {
|
||||
* this.endHighlighting(...); // Whatever you like to convey deactivation
|
||||
* },
|
||||
*
|
||||
* - There are more advanced methods you can implement (see documentation below):
|
||||
* touchableGetHighlightDelayMS: function() {
|
||||
* return 20;
|
||||
* }
|
||||
* // In practice, *always* use a predeclared constant (conserve memory).
|
||||
* touchableGetPressRectOffset: function() {
|
||||
* return {top: 20, left: 20, right: 20, bottom: 100};
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Touchable states.
|
||||
*/
|
||||
var States = keyMirror({
|
||||
NOT_RESPONDER: null, // Not the responder
|
||||
RESPONDER_INACTIVE_PRESS_IN: null, // Responder, inactive, in the `PressRect`
|
||||
RESPONDER_INACTIVE_PRESS_OUT: null, // Responder, inactive, out of `PressRect`
|
||||
RESPONDER_ACTIVE_PRESS_IN: null, // Responder, active, in the `PressRect`
|
||||
RESPONDER_ACTIVE_PRESS_OUT: null, // Responder, active, out of `PressRect`
|
||||
RESPONDER_ACTIVE_LONG_PRESS_IN: null, // Responder, active, in the `PressRect`, after long press threshold
|
||||
RESPONDER_ACTIVE_LONG_PRESS_OUT: null, // Responder, active, out of `PressRect`, after long press threshold
|
||||
ERROR: null
|
||||
});
|
||||
|
||||
/**
|
||||
* Quick lookup map for states that are considered to be "active"
|
||||
*/
|
||||
var IsActive = {
|
||||
RESPONDER_ACTIVE_PRESS_OUT: true,
|
||||
RESPONDER_ACTIVE_PRESS_IN: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Quick lookup for states that are considered to be "pressing" and are
|
||||
* therefore eligible to result in a "selection" if the press stops.
|
||||
*/
|
||||
var IsPressingIn = {
|
||||
RESPONDER_INACTIVE_PRESS_IN: true,
|
||||
RESPONDER_ACTIVE_PRESS_IN: true,
|
||||
RESPONDER_ACTIVE_LONG_PRESS_IN: true,
|
||||
};
|
||||
|
||||
var IsLongPressingIn = {
|
||||
RESPONDER_ACTIVE_LONG_PRESS_IN: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Inputs to the state machine.
|
||||
*/
|
||||
var Signals = keyMirror({
|
||||
DELAY: null,
|
||||
RESPONDER_GRANT: null,
|
||||
RESPONDER_RELEASE: null,
|
||||
RESPONDER_TERMINATED: null,
|
||||
ENTER_PRESS_RECT: null,
|
||||
LEAVE_PRESS_RECT: null,
|
||||
LONG_PRESS_DETECTED: null,
|
||||
});
|
||||
|
||||
/**
|
||||
* Mapping from States x Signals => States
|
||||
*/
|
||||
var Transitions = {
|
||||
NOT_RESPONDER: {
|
||||
DELAY: States.ERROR,
|
||||
RESPONDER_GRANT: States.RESPONDER_INACTIVE_PRESS_IN,
|
||||
RESPONDER_RELEASE: States.ERROR,
|
||||
RESPONDER_TERMINATED: States.ERROR,
|
||||
ENTER_PRESS_RECT: States.ERROR,
|
||||
LEAVE_PRESS_RECT: States.ERROR,
|
||||
LONG_PRESS_DETECTED: States.ERROR,
|
||||
},
|
||||
RESPONDER_INACTIVE_PRESS_IN: {
|
||||
DELAY: States.RESPONDER_ACTIVE_PRESS_IN,
|
||||
RESPONDER_GRANT: States.ERROR,
|
||||
RESPONDER_RELEASE: States.NOT_RESPONDER,
|
||||
RESPONDER_TERMINATED: States.NOT_RESPONDER,
|
||||
ENTER_PRESS_RECT: States.RESPONDER_INACTIVE_PRESS_IN,
|
||||
LEAVE_PRESS_RECT: States.RESPONDER_INACTIVE_PRESS_OUT,
|
||||
LONG_PRESS_DETECTED: States.ERROR,
|
||||
},
|
||||
RESPONDER_INACTIVE_PRESS_OUT: {
|
||||
DELAY: States.RESPONDER_ACTIVE_PRESS_OUT,
|
||||
RESPONDER_GRANT: States.ERROR,
|
||||
RESPONDER_RELEASE: States.NOT_RESPONDER,
|
||||
RESPONDER_TERMINATED: States.NOT_RESPONDER,
|
||||
ENTER_PRESS_RECT: States.RESPONDER_INACTIVE_PRESS_IN,
|
||||
LEAVE_PRESS_RECT: States.RESPONDER_INACTIVE_PRESS_OUT,
|
||||
LONG_PRESS_DETECTED: States.ERROR,
|
||||
},
|
||||
RESPONDER_ACTIVE_PRESS_IN: {
|
||||
DELAY: States.ERROR,
|
||||
RESPONDER_GRANT: States.ERROR,
|
||||
RESPONDER_RELEASE: States.NOT_RESPONDER,
|
||||
RESPONDER_TERMINATED: States.NOT_RESPONDER,
|
||||
ENTER_PRESS_RECT: States.RESPONDER_ACTIVE_PRESS_IN,
|
||||
LEAVE_PRESS_RECT: States.RESPONDER_ACTIVE_PRESS_OUT,
|
||||
LONG_PRESS_DETECTED: States.RESPONDER_ACTIVE_LONG_PRESS_IN,
|
||||
},
|
||||
RESPONDER_ACTIVE_PRESS_OUT: {
|
||||
DELAY: States.ERROR,
|
||||
RESPONDER_GRANT: States.ERROR,
|
||||
RESPONDER_RELEASE: States.NOT_RESPONDER,
|
||||
RESPONDER_TERMINATED: States.NOT_RESPONDER,
|
||||
ENTER_PRESS_RECT: States.RESPONDER_ACTIVE_PRESS_IN,
|
||||
LEAVE_PRESS_RECT: States.RESPONDER_ACTIVE_PRESS_OUT,
|
||||
LONG_PRESS_DETECTED: States.ERROR,
|
||||
},
|
||||
RESPONDER_ACTIVE_LONG_PRESS_IN: {
|
||||
DELAY: States.ERROR,
|
||||
RESPONDER_GRANT: States.ERROR,
|
||||
RESPONDER_RELEASE: States.NOT_RESPONDER,
|
||||
RESPONDER_TERMINATED: States.NOT_RESPONDER,
|
||||
ENTER_PRESS_RECT: States.RESPONDER_ACTIVE_LONG_PRESS_IN,
|
||||
LEAVE_PRESS_RECT: States.RESPONDER_ACTIVE_LONG_PRESS_OUT,
|
||||
LONG_PRESS_DETECTED: States.RESPONDER_ACTIVE_LONG_PRESS_IN,
|
||||
},
|
||||
RESPONDER_ACTIVE_LONG_PRESS_OUT: {
|
||||
DELAY: States.ERROR,
|
||||
RESPONDER_GRANT: States.ERROR,
|
||||
RESPONDER_RELEASE: States.NOT_RESPONDER,
|
||||
RESPONDER_TERMINATED: States.NOT_RESPONDER,
|
||||
ENTER_PRESS_RECT: States.RESPONDER_ACTIVE_LONG_PRESS_IN,
|
||||
LEAVE_PRESS_RECT: States.RESPONDER_ACTIVE_LONG_PRESS_OUT,
|
||||
LONG_PRESS_DETECTED: States.ERROR,
|
||||
},
|
||||
error: {
|
||||
DELAY: States.NOT_RESPONDER,
|
||||
RESPONDER_GRANT: States.RESPONDER_INACTIVE_PRESS_IN,
|
||||
RESPONDER_RELEASE: States.NOT_RESPONDER,
|
||||
RESPONDER_TERMINATED: States.NOT_RESPONDER,
|
||||
ENTER_PRESS_RECT: States.NOT_RESPONDER,
|
||||
LEAVE_PRESS_RECT: States.NOT_RESPONDER,
|
||||
LONG_PRESS_DETECTED: States.NOT_RESPONDER,
|
||||
}
|
||||
};
|
||||
|
||||
// ==== Typical Constants for integrating into UI components ====
|
||||
// var HIT_EXPAND_PX = 20;
|
||||
// var HIT_VERT_OFFSET_PX = 10;
|
||||
var HIGHLIGHT_DELAY_MS = 130;
|
||||
|
||||
var PRESS_EXPAND_PX = 20;
|
||||
|
||||
var LONG_PRESS_THRESHOLD = 500;
|
||||
|
||||
var LONG_PRESS_DELAY_MS = LONG_PRESS_THRESHOLD - HIGHLIGHT_DELAY_MS;
|
||||
|
||||
var LONG_PRESS_ALLOWED_MOVEMENT = 10;
|
||||
|
||||
// Default amount "active" region protrudes beyond box
|
||||
|
||||
/**
|
||||
* By convention, methods prefixed with underscores are meant to be @private,
|
||||
* and not @protected. Mixers shouldn't access them - not even to provide them
|
||||
* as callback handlers.
|
||||
*
|
||||
*
|
||||
* ========== Geometry =========
|
||||
* `Touchable` only assumes that there exists a `HitRect` node. The `PressRect`
|
||||
* is an abstract box that is extended beyond the `HitRect`.
|
||||
*
|
||||
* +--------------------------+
|
||||
* | | - "Start" events in `HitRect` cause `HitRect`
|
||||
* | +--------------------+ | to become the responder.
|
||||
* | | +--------------+ | | - `HitRect` is typically expanded around
|
||||
* | | | | | | the `VisualRect`, but shifted downward.
|
||||
* | | | VisualRect | | | - After pressing down, after some delay,
|
||||
* | | | | | | and before letting up, the Visual React
|
||||
* | | +--------------+ | | will become "active". This makes it eligible
|
||||
* | | HitRect | | for being highlighted (so long as the
|
||||
* | +--------------------+ | press remains in the `PressRect`).
|
||||
* | PressRect o |
|
||||
* +----------------------|---+
|
||||
* Out Region |
|
||||
* +-----+ This gap between the `HitRect` and
|
||||
* `PressRect` allows a touch to move far away
|
||||
* from the original hit rect, and remain
|
||||
* highlighted, and eligible for a "Press".
|
||||
* Customize this via
|
||||
* `touchableGetPressRectOffset()`.
|
||||
*
|
||||
*
|
||||
*
|
||||
* ======= State Machine =======
|
||||
*
|
||||
* +-------------+ <---+ RESPONDER_RELEASE
|
||||
* |NOT_RESPONDER|
|
||||
* +-------------+ <---+ RESPONDER_TERMINATED
|
||||
* +
|
||||
* | RESPONDER_GRANT (HitRect)
|
||||
* v
|
||||
* +---------------------------+ DELAY +-------------------------+ T + DELAY +------------------------------+
|
||||
* |RESPONDER_INACTIVE_PRESS_IN|+-------->|RESPONDER_ACTIVE_PRESS_IN| +------------> |RESPONDER_ACTIVE_LONG_PRESS_IN|
|
||||
* +---------------------------+ +-------------------------+ +------------------------------+
|
||||
* + ^ + ^ + ^
|
||||
* |LEAVE_ |ENTER_ |LEAVE_ |ENTER_ |LEAVE_ |ENTER_
|
||||
* |PRESS_RECT |PRESS_RECT |PRESS_RECT |PRESS_RECT |PRESS_RECT |PRESS_RECT
|
||||
* | | | | | |
|
||||
* v + v + v +
|
||||
* +----------------------------+ DELAY +--------------------------+ +-------------------------------+
|
||||
* |RESPONDER_INACTIVE_PRESS_OUT|+------->|RESPONDER_ACTIVE_PRESS_OUT| |RESPONDER_ACTIVE_LONG_PRESS_OUT|
|
||||
* +----------------------------+ +--------------------------+ +-------------------------------+
|
||||
*
|
||||
* T + DELAY => LONG_PRESS_DELAY_MS + DELAY
|
||||
*
|
||||
* Not drawn are the side effects of each transition. The most important side
|
||||
* effect is the `touchableHandlePress` abstract method invocation that occurs
|
||||
* when a responder is released while in either of the "Press" states.
|
||||
*
|
||||
* The other important side effects are the highlight abstract method
|
||||
* invocations (internal callbacks) to be implemented by the mixer.
|
||||
*
|
||||
*
|
||||
* @lends Touchable.prototype
|
||||
*/
|
||||
var TouchableMixin = {
|
||||
/**
|
||||
* Clear all timeouts on unmount
|
||||
*/
|
||||
componentWillUnmount: function() {
|
||||
this.touchableDelayTimeout && clearTimeout(this.touchableDelayTimeout);
|
||||
this.longPressDelayTimeout && clearTimeout(this.longPressDelayTimeout);
|
||||
this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout);
|
||||
},
|
||||
|
||||
/**
|
||||
* It's prefer that mixins determine state in this way, having the class
|
||||
* explicitly mix the state in the one and only `getInitialState` method.
|
||||
*
|
||||
* @return {object} State object to be placed inside of
|
||||
* `this.state.touchable`.
|
||||
*/
|
||||
touchableGetInitialState: function() {
|
||||
return {
|
||||
touchable: {touchState: undefined, responderID: null}
|
||||
};
|
||||
},
|
||||
|
||||
// ==== Hooks to Gesture Responder system ====
|
||||
/**
|
||||
* Must return true if embedded in a native platform scroll view.
|
||||
*/
|
||||
touchableHandleResponderTerminationRequest: function() {
|
||||
return !this.props.rejectResponderTermination;
|
||||
},
|
||||
|
||||
/**
|
||||
* Must return true to start the process of `Touchable`.
|
||||
*/
|
||||
touchableHandleStartShouldSetResponder: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true to cancel press on long press.
|
||||
*/
|
||||
touchableLongPressCancelsPress: function () {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Place as callback for a DOM element's `onResponderGrant` event.
|
||||
* @param {SyntheticEvent} e Synthetic event from event system.
|
||||
* @param {string} dispatchID ID of node that e was dispatched to.
|
||||
*
|
||||
*/
|
||||
touchableHandleResponderGrant: function(e, dispatchID) {
|
||||
// Since e is used in a callback invoked on another event loop
|
||||
// (as in setTimeout etc), we need to call e.persist() on the
|
||||
// event to make sure it doesn't get reused in the event object pool.
|
||||
e.persist();
|
||||
|
||||
this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout);
|
||||
this.pressOutDelayTimeout = null;
|
||||
|
||||
this.state.touchable.touchState = States.NOT_RESPONDER;
|
||||
this.state.touchable.responderID = dispatchID;
|
||||
this._receiveSignal(Signals.RESPONDER_GRANT, e);
|
||||
var delayMS =
|
||||
this.touchableGetHighlightDelayMS !== undefined ?
|
||||
Math.max(this.touchableGetHighlightDelayMS(), 0) : HIGHLIGHT_DELAY_MS;
|
||||
delayMS = isNaN(delayMS) ? HIGHLIGHT_DELAY_MS : delayMS;
|
||||
if (delayMS !== 0) {
|
||||
this.touchableDelayTimeout = setTimeout(
|
||||
this._handleDelay.bind(this, e),
|
||||
delayMS
|
||||
);
|
||||
} else {
|
||||
this._handleDelay(e);
|
||||
}
|
||||
|
||||
var longDelayMS =
|
||||
this.touchableGetLongPressDelayMS !== undefined ?
|
||||
Math.max(this.touchableGetLongPressDelayMS(), 10) : LONG_PRESS_DELAY_MS;
|
||||
longDelayMS = isNaN(longDelayMS) ? LONG_PRESS_DELAY_MS : longDelayMS;
|
||||
this.longPressDelayTimeout = setTimeout(
|
||||
this._handleLongDelay.bind(this, e),
|
||||
longDelayMS + delayMS
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Place as callback for a DOM element's `onResponderRelease` event.
|
||||
*/
|
||||
touchableHandleResponderRelease: function(e) {
|
||||
this._receiveSignal(Signals.RESPONDER_RELEASE, e);
|
||||
},
|
||||
|
||||
/**
|
||||
* Place as callback for a DOM element's `onResponderTerminate` event.
|
||||
*/
|
||||
touchableHandleResponderTerminate: function(e) {
|
||||
this._receiveSignal(Signals.RESPONDER_TERMINATED, e);
|
||||
},
|
||||
|
||||
/**
|
||||
* Place as callback for a DOM element's `onResponderMove` event.
|
||||
*/
|
||||
touchableHandleResponderMove: function(e) {
|
||||
// Not enough time elapsed yet, wait for highlight -
|
||||
// this is just a perf optimization.
|
||||
if (this.state.touchable.touchState === States.RESPONDER_INACTIVE_PRESS_IN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Measurement may not have returned yet.
|
||||
if (!this.state.touchable.positionOnActivate) {
|
||||
return;
|
||||
}
|
||||
|
||||
var positionOnActivate = this.state.touchable.positionOnActivate;
|
||||
var dimensionsOnActivate = this.state.touchable.dimensionsOnActivate;
|
||||
var pressRectOffset = this.touchableGetPressRectOffset ?
|
||||
this.touchableGetPressRectOffset() : {
|
||||
left: PRESS_EXPAND_PX,
|
||||
right: PRESS_EXPAND_PX,
|
||||
top: PRESS_EXPAND_PX,
|
||||
bottom: PRESS_EXPAND_PX
|
||||
};
|
||||
|
||||
var pressExpandLeft = pressRectOffset.left;
|
||||
var pressExpandTop = pressRectOffset.top;
|
||||
var pressExpandRight = pressRectOffset.right;
|
||||
var pressExpandBottom = pressRectOffset.bottom;
|
||||
|
||||
var hitSlop = this.touchableGetHitSlop ?
|
||||
this.touchableGetHitSlop() : null;
|
||||
|
||||
if (hitSlop) {
|
||||
pressExpandLeft += hitSlop.left;
|
||||
pressExpandTop += hitSlop.top;
|
||||
pressExpandRight += hitSlop.right;
|
||||
pressExpandBottom += hitSlop.bottom;
|
||||
}
|
||||
|
||||
var touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
|
||||
var pageX = touch && touch.pageX;
|
||||
var pageY = touch && touch.pageY;
|
||||
|
||||
if (this.pressInLocation) {
|
||||
var movedDistance = this._getDistanceBetweenPoints(pageX, pageY, this.pressInLocation.pageX, this.pressInLocation.pageY);
|
||||
if (movedDistance > LONG_PRESS_ALLOWED_MOVEMENT) {
|
||||
this._cancelLongPressDelayTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
var isTouchWithinActive =
|
||||
pageX > positionOnActivate.left - pressExpandLeft &&
|
||||
pageY > positionOnActivate.top - pressExpandTop &&
|
||||
pageX <
|
||||
positionOnActivate.left +
|
||||
dimensionsOnActivate.width +
|
||||
pressExpandRight &&
|
||||
pageY <
|
||||
positionOnActivate.top +
|
||||
dimensionsOnActivate.height +
|
||||
pressExpandBottom;
|
||||
if (isTouchWithinActive) {
|
||||
this._receiveSignal(Signals.ENTER_PRESS_RECT, e);
|
||||
var curState = this.state.touchable.touchState;
|
||||
if (curState === States.RESPONDER_INACTIVE_PRESS_IN) {
|
||||
// fix for t7967420
|
||||
this._cancelLongPressDelayTimeout();
|
||||
}
|
||||
} else {
|
||||
this._cancelLongPressDelayTimeout();
|
||||
this._receiveSignal(Signals.LEAVE_PRESS_RECT, e);
|
||||
}
|
||||
},
|
||||
|
||||
// ==== Abstract Application Callbacks ====
|
||||
|
||||
/**
|
||||
* Invoked when the item should be highlighted. Mixers should implement this
|
||||
* to visually distinguish the `VisualRect` so that the user knows that
|
||||
* releasing a touch will result in a "selection" (analog to click).
|
||||
*
|
||||
* @abstract
|
||||
* touchableHandleActivePressIn: function,
|
||||
*/
|
||||
|
||||
/**
|
||||
* Invoked when the item is "active" (in that it is still eligible to become
|
||||
* a "select") but the touch has left the `PressRect`. Usually the mixer will
|
||||
* want to unhighlight the `VisualRect`. If the user (while pressing) moves
|
||||
* back into the `PressRect` `touchableHandleActivePressIn` will be invoked
|
||||
* again and the mixer should probably highlight the `VisualRect` again. This
|
||||
* event will not fire on an `touchEnd/mouseUp` event, only move events while
|
||||
* the user is depressing the mouse/touch.
|
||||
*
|
||||
* @abstract
|
||||
* touchableHandleActivePressOut: function
|
||||
*/
|
||||
|
||||
/**
|
||||
* Invoked when the item is "selected" - meaning the interaction ended by
|
||||
* letting up while the item was either in the state
|
||||
* `RESPONDER_ACTIVE_PRESS_IN` or `RESPONDER_INACTIVE_PRESS_IN`.
|
||||
*
|
||||
* @abstract
|
||||
* touchableHandlePress: function
|
||||
*/
|
||||
|
||||
/**
|
||||
* Invoked when the item is long pressed - meaning the interaction ended by
|
||||
* letting up while the item was in `RESPONDER_ACTIVE_LONG_PRESS_IN`. If
|
||||
* `touchableHandleLongPress` is *not* provided, `touchableHandlePress` will
|
||||
* be called as it normally is. If `touchableHandleLongPress` is provided, by
|
||||
* default any `touchableHandlePress` callback will not be invoked. To
|
||||
* override this default behavior, override `touchableLongPressCancelsPress`
|
||||
* to return false. As a result, `touchableHandlePress` will be called when
|
||||
* lifting up, even if `touchableHandleLongPress` has also been called.
|
||||
*
|
||||
* @abstract
|
||||
* touchableHandleLongPress: function
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the number of millis to wait before triggering a highlight.
|
||||
*
|
||||
* @abstract
|
||||
* touchableGetHighlightDelayMS: function
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the amount to extend the `HitRect` into the `PressRect`. Positive
|
||||
* numbers mean the size expands outwards.
|
||||
*
|
||||
* @abstract
|
||||
* touchableGetPressRectOffset: function
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// ==== Internal Logic ====
|
||||
|
||||
/**
|
||||
* Measures the `HitRect` node on activation. The Bounding rectangle is with
|
||||
* respect to viewport - not page, so adding the `pageXOffset/pageYOffset`
|
||||
* should result in points that are in the same coordinate system as an
|
||||
* event's `globalX/globalY` data values.
|
||||
*
|
||||
* - Consider caching this for the lifetime of the component, or possibly
|
||||
* being able to share this cache between any `ScrollMap` view.
|
||||
*
|
||||
* @sideeffects
|
||||
* @private
|
||||
*/
|
||||
_remeasureMetricsOnActivation: function() {
|
||||
/* @edit begin */
|
||||
UIManager.measure(
|
||||
this.state.touchable.responderID,
|
||||
this._handleQueryLayout
|
||||
);
|
||||
/* @edit end */
|
||||
},
|
||||
|
||||
_handleQueryLayout: function(l, t, w, h, globalX, globalY) {
|
||||
this.state.touchable.positionOnActivate &&
|
||||
Position.release(this.state.touchable.positionOnActivate);
|
||||
this.state.touchable.dimensionsOnActivate &&
|
||||
BoundingDimensions.release(this.state.touchable.dimensionsOnActivate);
|
||||
this.state.touchable.positionOnActivate = Position.getPooled(globalX, globalY);
|
||||
this.state.touchable.dimensionsOnActivate = BoundingDimensions.getPooled(w, h);
|
||||
},
|
||||
|
||||
_handleDelay: function(e) {
|
||||
this.touchableDelayTimeout = null;
|
||||
this._receiveSignal(Signals.DELAY, e);
|
||||
},
|
||||
|
||||
_handleLongDelay: function(e) {
|
||||
this.longPressDelayTimeout = null;
|
||||
var curState = this.state.touchable.touchState;
|
||||
if (curState !== States.RESPONDER_ACTIVE_PRESS_IN &&
|
||||
curState !== States.RESPONDER_ACTIVE_LONG_PRESS_IN) {
|
||||
console.error('Attempted to transition from state `' + curState + '` to `' +
|
||||
States.RESPONDER_ACTIVE_LONG_PRESS_IN + '`, which is not supported. This is ' +
|
||||
'most likely due to `Touchable.longPressDelayTimeout` not being cancelled.');
|
||||
} else {
|
||||
this._receiveSignal(Signals.LONG_PRESS_DETECTED, e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Receives a state machine signal, performs side effects of the transition
|
||||
* and stores the new state. Validates the transition as well.
|
||||
*
|
||||
* @param {Signals} signal State machine signal.
|
||||
* @throws Error if invalid state transition or unrecognized signal.
|
||||
* @sideeffects
|
||||
*/
|
||||
_receiveSignal: function(signal, e) {
|
||||
var curState = this.state.touchable.touchState;
|
||||
var nextState = Transitions[curState] && Transitions[curState][signal];
|
||||
if (!nextState) {
|
||||
throw new Error(
|
||||
'Unrecognized signal `' + signal + '` or state `' + curState +
|
||||
'` for Touchable responder `' + this.state.touchable.responderID + '`'
|
||||
);
|
||||
}
|
||||
if (nextState === States.ERROR) {
|
||||
throw new Error(
|
||||
'Touchable cannot transition from `' + curState + '` to `' + signal +
|
||||
'` for responder `' + this.state.touchable.responderID + '`'
|
||||
);
|
||||
}
|
||||
if (curState !== nextState) {
|
||||
this._performSideEffectsForTransition(curState, nextState, signal, e);
|
||||
this.state.touchable.touchState = nextState;
|
||||
}
|
||||
},
|
||||
|
||||
_cancelLongPressDelayTimeout: function () {
|
||||
this.longPressDelayTimeout && clearTimeout(this.longPressDelayTimeout);
|
||||
this.longPressDelayTimeout = null;
|
||||
},
|
||||
|
||||
_isHighlight: function (state) {
|
||||
return state === States.RESPONDER_ACTIVE_PRESS_IN ||
|
||||
state === States.RESPONDER_ACTIVE_LONG_PRESS_IN;
|
||||
},
|
||||
|
||||
_savePressInLocation: function(e) {
|
||||
var touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
|
||||
var pageX = touch && touch.pageX;
|
||||
var pageY = touch && touch.pageY;
|
||||
var locationX = touch && touch.locationX;
|
||||
var locationY = touch && touch.locationY;
|
||||
this.pressInLocation = {pageX, pageY, locationX, locationY};
|
||||
},
|
||||
|
||||
_getDistanceBetweenPoints: function (aX, aY, bX, bY) {
|
||||
var deltaX = aX - bX;
|
||||
var deltaY = aY - bY;
|
||||
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
},
|
||||
|
||||
/**
|
||||
* Will perform a transition between touchable states, and identify any
|
||||
* highlighting or unhighlighting that must be performed for this particular
|
||||
* transition.
|
||||
*
|
||||
* @param {States} curState Current Touchable state.
|
||||
* @param {States} nextState Next Touchable state.
|
||||
* @param {Signal} signal Signal that triggered the transition.
|
||||
* @param {Event} e Native event.
|
||||
* @sideeffects
|
||||
*/
|
||||
_performSideEffectsForTransition: function(curState, nextState, signal, e) {
|
||||
var curIsHighlight = this._isHighlight(curState);
|
||||
var newIsHighlight = this._isHighlight(nextState);
|
||||
|
||||
var isFinalSignal =
|
||||
signal === Signals.RESPONDER_TERMINATED ||
|
||||
signal === Signals.RESPONDER_RELEASE;
|
||||
|
||||
if (isFinalSignal) {
|
||||
this._cancelLongPressDelayTimeout();
|
||||
}
|
||||
|
||||
if (!IsActive[curState] && IsActive[nextState]) {
|
||||
this._remeasureMetricsOnActivation();
|
||||
}
|
||||
|
||||
if (IsPressingIn[curState] && signal === Signals.LONG_PRESS_DETECTED) {
|
||||
this.touchableHandleLongPress && this.touchableHandleLongPress(e);
|
||||
}
|
||||
|
||||
if (newIsHighlight && !curIsHighlight) {
|
||||
this._savePressInLocation(e);
|
||||
this.touchableHandleActivePressIn && this.touchableHandleActivePressIn(e);
|
||||
} else if (!newIsHighlight && curIsHighlight && this.touchableHandleActivePressOut) {
|
||||
if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) {
|
||||
this.pressOutDelayTimeout = setTimeout(() => {
|
||||
this.touchableHandleActivePressOut(e);
|
||||
}, this.touchableGetPressOutDelayMS());
|
||||
} else {
|
||||
this.touchableHandleActivePressOut(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) {
|
||||
var hasLongPressHandler = !!this.props.onLongPress;
|
||||
var pressIsLongButStillCallOnPress =
|
||||
IsLongPressingIn[curState] && ( // We *are* long pressing..
|
||||
!hasLongPressHandler || // But either has no long handler
|
||||
!this.touchableLongPressCancelsPress() // or we're told to ignore it.
|
||||
);
|
||||
|
||||
var shouldInvokePress = !IsLongPressingIn[curState] || pressIsLongButStillCallOnPress;
|
||||
if (shouldInvokePress && this.touchableHandlePress) {
|
||||
this.touchableHandlePress(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.touchableDelayTimeout && clearTimeout(this.touchableDelayTimeout);
|
||||
this.touchableDelayTimeout = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var Touchable = {
|
||||
Mixin: TouchableMixin
|
||||
};
|
||||
|
||||
module.exports = Touchable;
|
||||
@@ -1,9 +1,11 @@
|
||||
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
|
||||
import CoreComponent from '../CoreComponent'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
|
||||
import ViewStylePropTypes from './ViewStylePropTypes'
|
||||
|
||||
@NativeMethodsDecorator
|
||||
export default class View extends Component {
|
||||
static propTypes = {
|
||||
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
|
||||
@@ -11,6 +13,26 @@ export default class View extends Component {
|
||||
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
|
||||
accessible: CoreComponent.propTypes.accessible,
|
||||
children: PropTypes.any,
|
||||
onClick: PropTypes.func,
|
||||
onClickCapture: PropTypes.func,
|
||||
onMoveShouldSetResponder: PropTypes.func,
|
||||
onMoveShouldSetResponderCapture: PropTypes.func,
|
||||
onResponderGrant: PropTypes.func,
|
||||
onResponderMove: PropTypes.func,
|
||||
onResponderReject: PropTypes.func,
|
||||
onResponderRelease: PropTypes.func,
|
||||
onResponderTerminate: PropTypes.func,
|
||||
onResponderTerminationRequest: PropTypes.func,
|
||||
onStartShouldSetResponder: PropTypes.func,
|
||||
onStartShouldSetResponderCapture: PropTypes.func,
|
||||
onTouchCancel: PropTypes.func,
|
||||
onTouchCancelCapture: PropTypes.func,
|
||||
onTouchEnd: PropTypes.func,
|
||||
onTouchEndCapture: PropTypes.func,
|
||||
onTouchMove: PropTypes.func,
|
||||
onTouchMoveCapture: PropTypes.func,
|
||||
onTouchStart: PropTypes.func,
|
||||
onTouchStartCapture: PropTypes.func,
|
||||
pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']),
|
||||
style: StyleSheetPropType(ViewStylePropTypes),
|
||||
testID: CoreComponent.propTypes.testID
|
||||
@@ -20,6 +42,20 @@ export default class View extends Component {
|
||||
accessible: true
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this._handleClick = this._handleClick.bind(this)
|
||||
this._handleClickCapture = this._handleClickCapture.bind(this)
|
||||
this._handleTouchCancel = this._handleTouchCancel.bind(this)
|
||||
this._handleTouchCancelCapture = this._handleTouchCancelCapture.bind(this)
|
||||
this._handleTouchEnd = this._handleTouchEnd.bind(this)
|
||||
this._handleTouchEndCapture = this._handleTouchEndCapture.bind(this)
|
||||
this._handleTouchMove = this._handleTouchMove.bind(this)
|
||||
this._handleTouchMoveCapture = this._handleTouchMoveCapture.bind(this)
|
||||
this._handleTouchStart = this._handleTouchStart.bind(this)
|
||||
this._handleTouchStartCapture = this._handleTouchStartCapture.bind(this)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
pointerEvents,
|
||||
@@ -32,6 +68,16 @@ export default class View extends Component {
|
||||
return (
|
||||
<CoreComponent
|
||||
{...other}
|
||||
onClick={this._handleClick}
|
||||
onClickCapture={this._handleClickCapture}
|
||||
onTouchCancel={this._handleTouchCancel}
|
||||
onTouchCancelCapture={this._handleTouchCancelCapture}
|
||||
onTouchEnd={this._handleTouchEnd}
|
||||
onTouchEndCapture={this._handleTouchEndCapture}
|
||||
onTouchMove={this._handleTouchMove}
|
||||
onTouchMoveCapture={this._handleTouchMoveCapture}
|
||||
onTouchStart={this._handleTouchStart}
|
||||
onTouchStartCapture={this._handleTouchStartCapture}
|
||||
style={[
|
||||
styles.initial,
|
||||
style,
|
||||
@@ -40,6 +86,80 @@ export default class View extends Component {
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* React Native expects `pageX` and `pageY` to be on the `nativeEvent`, but
|
||||
* React doesn't include them for touch events.
|
||||
*/
|
||||
_normalizeTouchEvent(event) {
|
||||
const { pageX, changedTouches } = event.nativeEvent
|
||||
if (pageX === undefined) {
|
||||
const { pageX, pageY } = changedTouches[0]
|
||||
event.nativeEvent.pageX = pageX
|
||||
event.nativeEvent.pageY = pageY
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
_handleClick(e) {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
|
||||
_handleClickCapture(e) {
|
||||
if (this.props.onClickCapture) {
|
||||
this.props.onClickCapture(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
|
||||
_handleTouchCancel(e) {
|
||||
if (this.props.onTouchCancel) {
|
||||
this.props.onTouchCancel(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
|
||||
_handleTouchCancelCapture(e) {
|
||||
if (this.props.onTouchCancelCapture) {
|
||||
this.props.onTouchCancelCapture(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
|
||||
_handleTouchEnd(e) {
|
||||
if (this.props.onTouchEnd) {
|
||||
this.props.onTouchEnd(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
|
||||
_handleTouchEndCapture(e) {
|
||||
if (this.props.onTouchEndCapture) {
|
||||
this.props.onTouchEndCapture(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
|
||||
_handleTouchMove(e) {
|
||||
if (this.props.onTouchMove) {
|
||||
this.props.onTouchMove(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
|
||||
_handleTouchMoveCapture(e) {
|
||||
if (this.props.onTouchMoveCapture) {
|
||||
this.props.onTouchMoveCapture(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
|
||||
_handleTouchStart(e) {
|
||||
if (this.props.onTouchStart) {
|
||||
this.props.onTouchStart(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
|
||||
_handleTouchStartCapture(e) {
|
||||
if (this.props.onTouchStartCapture) {
|
||||
this.props.onTouchStartCapture(this._normalizeTouchEvent(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
11
src/index.js
11
src/index.js
@@ -2,12 +2,18 @@ import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
|
||||
import './apis/PanResponder/injectResponderEventPlugin'
|
||||
|
||||
// apis
|
||||
import Animated from './apis/Animated'
|
||||
import AppRegistry from './apis/AppRegistry'
|
||||
import AppState from './apis/AppState'
|
||||
import AsyncStorage from './apis/AsyncStorage'
|
||||
import Dimensions from './apis/Dimensions'
|
||||
import Easing from './apis/Easing'
|
||||
import InteractionManager from './apis/InteractionManager'
|
||||
import NetInfo from './apis/NetInfo'
|
||||
import PanResponder from './apis/PanResponder'
|
||||
import PixelRatio from './apis/PixelRatio'
|
||||
import Platform from './apis/Platform'
|
||||
import StyleSheet from './apis/StyleSheet'
|
||||
@@ -25,11 +31,15 @@ import View from './components/View'
|
||||
|
||||
const ReactNative = {
|
||||
// apis
|
||||
Animated,
|
||||
AppRegistry,
|
||||
AppState,
|
||||
AsyncStorage,
|
||||
Dimensions,
|
||||
Easing,
|
||||
InteractionManager,
|
||||
NetInfo,
|
||||
PanResponder,
|
||||
PixelRatio,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
@@ -42,6 +52,7 @@ const ReactNative = {
|
||||
ScrollView,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableBounce: Touchable,
|
||||
TouchableHighlight: Touchable,
|
||||
TouchableOpacity: Touchable,
|
||||
TouchableWithoutFeedback: Touchable,
|
||||
|
||||
@@ -90,11 +90,11 @@ const mountSafeCallback = (context: Component, callback: ?Function) => () => {
|
||||
return callback.apply(context, arguments)
|
||||
}
|
||||
|
||||
export default NativeMethodsMixin
|
||||
|
||||
export const nativeMethodsDecorator = (Component) => {
|
||||
export const NativeMethodsDecorator = (Component) => {
|
||||
Object.keys(NativeMethodsMixin).forEach((method) => {
|
||||
Component.prototype[method] = NativeMethodsMixin[method]
|
||||
})
|
||||
return Component
|
||||
}
|
||||
|
||||
export default NativeMethodsMixin
|
||||
|
||||
Reference in New Issue
Block a user