Compare commits

..

56 Commits

Author SHA1 Message Date
Nicolas Gallagher
a94367bdcb 0.0.68 2017-01-11 13:12:25 -08:00
Nicolas Gallagher
65febbbc52 [fix] error in setNativeProps when no style
Close #325
Close #327
2017-01-11 13:08:10 -08:00
Nicolas Gallagher
b14d2e5bd8 [fix] CSS pointer event selectors 2017-01-11 12:47:24 -08:00
Nicolas Gallagher
7c83ba162d 0.0.67 2017-01-08 18:40:02 -08:00
Nicolas Gallagher
3ffc005a7b [fix] setNativeProps resolving logic
Since styles are set using both class names and inline styles,
'setNativeProps' needs an additional resolving step that accounts for
the pre-existing state of RN-managed styles on the DOM node.

Fix #321
2017-01-08 18:25:39 -08:00
Nicolas Gallagher
50a70ad02f 0.0.66 2017-01-07 19:05:54 -08:00
Nicolas Gallagher
768e895701 [fix] View transforms; add perspective styles
Fixes a regression introduced by
5db300df35

The `perspective` function is distinct from the `perspective` property.
This patch reverts the regression and adds support for `perspective`,
`perspectiveOrigin`, and `transformOrigin`.

Fix #208
2017-01-07 19:02:57 -08:00
Nicolas Gallagher
af5fde994d Fix yarn.lock 2017-01-07 18:25:54 -08:00
Paul Le Cam
c3d0763944 [fix] ListView imports and 'getRowAndSectionCount'
Close #316
2017-01-07 18:22:47 -08:00
Nicolas Gallagher
0aba506725 Fix UIManager tests 2017-01-07 18:18:56 -08:00
Nicolas Gallagher
91032d8565 [change] allow 'display' in ViewStylePropTypes
Fix #296
2017-01-07 18:15:16 -08:00
Nicolas Gallagher
0696721488 Document transition and animation View styles 2017-01-07 18:12:26 -08:00
Nicolas Gallagher
fe18830ce6 [change] use classList in UIManager
Prevent setting the same class multiple times
2017-01-07 18:03:51 -08:00
Nicolas Gallagher
1b86d02300 [change] wrap layout measurement in 'asap' 2017-01-07 18:03:03 -08:00
Nicolas Gallagher
c56b472258 [change] depend on normalize-css-color
Fix #308
2017-01-07 18:00:17 -08:00
Nicolas Gallagher
b00132f007 [fix] TouchableOpacity transition duration 2017-01-07 17:50:59 -08:00
Nicolas Gallagher
8b8f8f0374 [fix] CSS properties that support unitless numbers 2017-01-05 14:47:10 -08:00
Nicolas Gallagher
8e94af34e1 Remove use of 'keyOf' 2017-01-05 14:47:10 -08:00
Nicolas Gallagher
7ffaf592d5 [fix] Text automatic writing direction detection 2017-01-05 14:47:08 -08:00
Nicolas Gallagher
a1017fa785 0.0.65 2017-01-04 18:25:39 -08:00
Nicolas Gallagher
5db300df35 [fix] transform perspective resolution 2017-01-04 18:04:15 -08:00
Nicolas Gallagher
214d862e61 [add] ScrollView to Animated 2017-01-04 18:04:05 -08:00
Nicolas Gallagher
4ef5453b33 0.0.64 2017-01-04 10:58:15 -08:00
Nicolas Gallagher
a27671d7cf [fix] passing on RN style props in createDOMElement
The 'createDOMElement' function wasn't pulling 'style' out of the
props. A change to the logic that sets DOM props meant that if
'StyleRegistry.resolve' didn't return a 'style' object, the React Native
styles would be passed through to the underlying DOM node.

Fix #315
2017-01-04 10:43:19 -08:00
Nicolas Gallagher
8d2a650670 [fix] StyleSheet selector escaping
Values that contain '*' (e.g. 'calc(2 * 2)') were not properly escaped,
resulting in broken selectors.
2017-01-03 14:25:39 -08:00
Nicolas Gallagher
4cc1c983e8 0.0.63 2017-01-02 23:44:10 -08:00
Nicolas Gallagher
37413fd55f Update Text snapshot 2017-01-02 23:43:39 -08:00
Nicolas Gallagher
07ff0ea104 Use setNativeProps to update ProgressBar 2017-01-02 23:28:14 -08:00
Nicolas Gallagher
1a87657500 [fix] Switch thumb positioning 2017-01-02 23:25:15 -08:00
Nicolas Gallagher
5e4c8e520a [fix] StyleSheet registry key check 2017-01-02 23:24:24 -08:00
Nicolas Gallagher
236121e32c Add unregistered styles benchmark 2017-01-02 22:46:38 -08:00
Nicolas Gallagher
39c76ca50c Minor StyleSheet/injector refactor; small fixes 2017-01-02 15:07:05 -08:00
Nicolas Gallagher
e65f91d849 [change] simplify CSS generation 2017-01-02 13:15:38 -08:00
Nicolas Gallagher
a535c558d8 [change] Move 'onResponderRelease' cancel to 'Touchable'
Moves a fix for double-firing Touchables into 'Touchable'
2017-01-02 13:12:13 -08:00
Nicolas Gallagher
a74be91b7c Minor documentation changes 2017-01-02 11:27:46 -08:00
Nicolas Gallagher
8eaaf28a32 0.0.62 2017-01-01 20:45:45 -08:00
Nicolas Gallagher
16d448dc5b Update various dependencies 2017-01-01 20:45:14 -08:00
Nicolas Gallagher
ea75cced13 [change] ProgressBar using CSS animations
Fix #299
2017-01-01 20:22:42 -08:00
Nicolas Gallagher
cfc56a1354 [change] ActivityIndicator using CSS animations
Ref #299
2017-01-01 19:48:50 -08:00
Nicolas Gallagher
b1cd92a65d [change] update Animated
Fix #309
2017-01-01 18:35:18 -08:00
Nicolas Gallagher
d87f71ebc1 [change] StyleSheet performance rewrite
Improves StyleSheet benchmark performance by 13x. Removes undocumented
`StyleSheet.resolve` API.

Typical mean (and first) task duration.

[benchmark] DeepTree: depth=3, breadth=10, wrap=4)
  -master  2809.76ms (3117.64ms)
  -patch     211.2ms  (364.28ms)

[benchmark] DeepTree: depth=5, breadth=3, wrap=1)
  -master   421.25ms (428.15ms)
  -patch     32.46ms  (47.36ms)

This patch adds memoization of DOM prop resolution (~3-4x faster), and
re-introduces a `className`-based styling strategy (~3-4x faster).
Styles map to "atomic css" rules.

Fix #307
2017-01-01 17:42:25 -08:00
Nicolas Gallagher
a2cafe56fc Add initial performance benchmarks
Simple render tree benchmarks originally developed by @lelandrichardson

Fix #306
2017-01-01 14:43:47 -08:00
Nicolas Gallagher
351c0ac3d4 Minor changes 2016-12-30 22:19:51 -08:00
Nicolas Gallagher
877c0d2818 Simplify StyleSheet's expandStyle 2016-12-30 22:14:39 -08:00
Nicolas Gallagher
3afc5d5de6 Rename style processors to resolvers 2016-12-29 19:17:12 -08:00
Nicolas Gallagher
edf3b9b7ff Move 'flattenStyle' into 'StyleSheet' 2016-12-29 16:31:14 -08:00
Nicolas Gallagher
518a85bf1b Rename 'createReactStyleObject' to 'createReactDOMStyle' 2016-12-29 16:26:31 -08:00
vaukalak
ba75acb66a [add] BackAndroid API stub 2016-12-27 18:14:31 +00:00
Nicolas Gallagher
bc68b0b6f4 0.0.61 2016-12-27 18:09:11 +00:00
Nicolas Gallagher
44ecbc072e [change] update React and Touchables
Update to React@15.4. The 'EventConstants' module no longer exports a
key-mirror, which was preventing the 'ResponderEventPlugin' from working
as it did with React@15.3.

Close #255
2016-12-27 17:57:27 +00:00
Nicolas Gallagher
4cf4905fc2 [change] add support for ShadowPropTypes
Fix #44
2016-12-26 13:57:19 +00:00
Nicolas Gallagher
509920be4b [add] Image 'prefetch' and 'getSize' statics
Fix #160
2016-12-26 13:31:40 +00:00
Gethin Webster
04e3c23e67 [fix] ListView updates rows when dataSource changes
Close #295
2016-12-23 12:51:40 +00:00
Nicolas Gallagher
32f454de66 [change] add Platform and Touchable to 'core' module 2016-12-23 12:45:54 +00:00
Nicolas Gallagher
1273bfc7cf Address avoidable object creation 2016-12-23 12:22:32 +00:00
Nicolas Gallagher
dc7f526f6b [fix] TextInput props
- Add missing 'onSubmitEditing' propType and test
- Add 'dir=auto' DOM attribute to allow browser to switch writing
  direction for RTL languages
2016-12-17 23:38:36 +00:00
120 changed files with 4212 additions and 3597 deletions

View File

@@ -13,5 +13,6 @@ https://github.com/necolas/react-native-web/CONTRIBUTING.md
- [ ] includes documentation
- [ ] includes tests
- [ ] includes benchmark reports
- [ ] includes an interactive example
- [ ] includes screenshots/videos

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/dist
/dist-examples
/dist-performance
/node_modules

View File

@@ -1,7 +1,5 @@
# Contributing
We are open to, and grateful for, any contributions made by the community.
## Reporting Issues and Asking Questions
Before opening an issue, please search the [issue
@@ -31,6 +29,12 @@ Run the examples:
npm run examples
```
Run the benchmarks in a browser by opening `./performance/index.html` after running:
```
npm run build:performance
```
### Building
```

View File

@@ -27,7 +27,7 @@ online with [React Native for Web: Playground](http://codepen.io/necolas/pen/PZz
To install in your app:
```
npm install --save react@15.3 react-native-web
npm install --save react@15.4 react-native-web
```
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
@@ -82,6 +82,7 @@ Exported modules:
* [`Vibration`](docs/apis/Vibration.md)
<span id="#why"></span>
## Why?
There are many different teams at Twitter building web applications with React.

View File

@@ -15,9 +15,9 @@ Each key of the object passed to `create` must define a style object.
Flattens an array of styles into a single style object.
**render**: function
**renderToString**: function
Returns a React `<style>` element for use in server-side rendering.
Returns a string of the stylesheet for use in server-side rendering.
## Properties

View File

@@ -75,6 +75,23 @@ Example usage:
<Image resizeMode={Image.resizeMode.contain} />
```
## Methods
static **getSize**(uri: string, success: (width, height) => {}, failure: function)
Retrieve the width and height (in pixels) of an image prior to displaying it.
This method can fail if the image cannot be found, or fails to download.
(In order to retrieve the image dimensions, the image may first need to be
loaded or downloaded, after which it will be cached. This means that in
principle you could use this method to preload images, however it is not
optimized for that purpose, and may in future be implemented in a way that does
not fully load/download the image data.)
static **prefetch**(url: string): Promise
Prefetches a remote image for later use by downloading it.
## Examples
```js

View File

@@ -99,6 +99,14 @@ from `style`.
+ `alignContent`
+ `alignItems`
+ `alignSelf`
+ `animationDelay`
+ `animationDirection`
+ `animationDuration`
+ `animationFillMode`
+ `animationIterationCount`
+ `animationName`
+ `animationPlayState`
+ `animationTimingFunction`
+ `backfaceVisibility`
+ `backgroundAttachment`
+ `backgroundClip`
@@ -164,10 +172,17 @@ from `style`.
+ `paddingRight`
+ `paddingTop`
+ `paddingVertical`
+ `perspective`
+ `perspectiveOrigin`
+ `position`
+ `right`
+ `top`
+ `transform`
+ `transformOrigin`
+ `transitionDelay`
+ `transitionDuration`
+ `transitionProperty`
+ `transitionTimingFunction`
+ `userSelect`
+ `visibility`
+ `width`

View File

@@ -4,7 +4,7 @@ It is sometimes necessary to make changes directly to a component without using
state/props to trigger a re-render of the entire subtree in the browser, this
is done by directly modifying a DOM node. `setNativeProps` is the React Native
equivalent to setting properties directly on a DOM node. Use direct
manipulation when frequent re-rendering creates a performance bottleneck Direct
manipulation when frequent re-rendering creates a performance bottleneck. Direct
manipulation will not be a tool that you reach for frequently.
## `setNativeProps` and `shouldComponentUpdate`

View File

@@ -27,14 +27,48 @@ the `url-loader` to the webpack config:
module.exports = {
// ...
module: {
loaders: {
test: /\.(gif|jpe?g|png|svg)$/,
loader: 'url-loader',
query: { name: '[name].[hash:16].[ext]' }
}
loaders: [
{
test: /\.(gif|jpe?g|png|svg)$/,
loader: 'url-loader',
query: { name: '[name].[hash:16].[ext]' }
}
]
}
};
```
## Dependencies
Many OSS React Native packages are not compiled to ES5 before being published.
This can result in webpack build errors. To avoid this issue you should
configure webpack (or your bundler of choice) to run
`babel-preset-react-native` over the necessary `node_module`. For example:
```js
// webpack.config.js
module.exports = {
// ...
module: {
loaders: [
{
// transpile to ES5
test: /\.jsx?$/,
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules/react-native-something')
],
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
}
};
```
Please refer to the webpack documentation for more information.
## Web-specific code
Minor platform differences can use the `Platform` module.

View File

@@ -16,8 +16,9 @@ module.exports = {
}
```
The `react-native-web` package also includes a `core` module that exports only
`ReactNative`, `Image`, `StyleSheet`, `Text`, `TextInput`, and `View`.
The `react-native-web` package also includes a `core` module that exports a
subset of modules: `ReactNative`, `I18nManager`, `Platform`, `StyleSheet`,
`Image`, `Text`, `TextInput`, `Touchable`, and `View`.
```js
// webpack.config.js
@@ -64,6 +65,10 @@ AppRegistry.runApplication('App', {
})
```
Setting `process.env.__REACT_NATIVE_DEBUG_ENABLED__` to `true` will expose some
debugging logs. This can help track down when you're rendering without the
performance benefit of cached styles.
## Server-side rendering
Rendering using the `AppRegistry`:

View File

@@ -31,10 +31,9 @@ const styles = StyleSheet.create({
})
```
Using `StyleSheet.create` is optional but provides some key advantages: styles
are immutable in development, certain declarations are automatically converted
to CSS rather than applied as inline styles, and styles are only created once
for the application and not on every render.
Using `StyleSheet.create` is optional but provides the best performance
(`style` is resolved to CSS stylesheets). Avoid creating unregistered style
objects.
The attribute names and values are a subset of CSS. See the `style`
documentation of individual components.
@@ -56,12 +55,6 @@ A common pattern is to conditionally add style based on a condition:
styles.base,
this.state.active && styles.active
]} />
// or
<View style={{
...styles.base,
...(this.state.active && styles.active)
}} />
```
## Composing styles

View File

@@ -1,6 +1,8 @@
const path = require('path')
const webpack = require('webpack')
const DEV = process.env.NODE_ENV !== 'production';
module.exports = {
module: {
loaders: [
@@ -19,13 +21,9 @@ module.exports = {
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.__REACT_NATIVE_DEBUG_ENABLED__': DEV
}),
// https://github.com/animatedjs/animated/issues/40
new webpack.NormalModuleReplacementPlugin(
/es6-set/,
path.join(__dirname, '../../src/modules/polyfills/Set.js')
),
new webpack.optimize.OccurenceOrderPlugin()
],
resolve: {

View File

@@ -50,7 +50,8 @@ const ToggleAnimatingActivityIndicator = React.createClass({
return (
<ActivityIndicator
animating={this.state.animating}
style={[styles.centering, {height: 80}]}
style={styles.centering}
hidesWhenStopped={this.props.hidesWhenStopped}
size="large"
/>
);
@@ -121,7 +122,12 @@ const examples = [
{
title: 'Start/stop',
render() {
return <ToggleAnimatingActivityIndicator />;
return (
<View style={[styles.horizontal, styles.centering]}>
<ToggleAnimatingActivityIndicator />
<ToggleAnimatingActivityIndicator hidesWhenStopped={false} />
</View>
);
}
},
{
@@ -129,7 +135,7 @@ const examples = [
render() {
return (
<View style={[styles.horizontal, styles.centering]}>
<ActivityIndicator size="40" />
<ActivityIndicator size={40} />
<ActivityIndicator
style={{ marginLeft: 20, transform: [ {scale: 1.5} ] }}
size="large"

View File

@@ -28,10 +28,9 @@ import { ActivityIndicator, Image, Platform, StyleSheet, Text, View } from 'reac
var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
//var ImageCapInsetsExample = require('./ImageCapInsetsExample');
//const IMAGE_PREFETCH_URL = 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now();
//var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
const IMAGE_PREFETCH_URL = 'http://origami.design/public/images/bird-logo.png?r=1&t=' + Date.now();
var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
/*
var NetworkImageCallbackExample = React.createClass({
getInitialState: function() {
return {
@@ -88,7 +87,6 @@ var NetworkImageCallbackExample = React.createClass({
});
}
});
*/
var NetworkImageExample = React.createClass({
getInitialState: function() {
@@ -118,7 +116,6 @@ var NetworkImageExample = React.createClass({
}
});
/*
var ImageSizeExample = React.createClass({
getInitialState: function() {
return {
@@ -133,24 +130,25 @@ var ImageSizeExample = React.createClass({
},
render: function() {
return (
<View style={{flexDirection: 'row'}}>
<Image
style={{
width: 60,
height: 60,
backgroundColor: 'transparent',
marginRight: 10,
}}
source={this.props.source} />
<View>
<Text>
Actual dimensions:{'\n'}
Width: {this.state.width}, Height: {this.state.height}
width: {this.state.width}, height: {this.state.height}
</Text>
<Image
source={this.props.source}
style={{
backgroundColor: '#eee',
height: 227,
marginTop: 10,
width: 323
}}
/>
</View>
);
},
});
*/
/*
var MultipleSourcesExample = React.createClass({
getInitialState: function() {
@@ -239,17 +237,17 @@ const examples = [
);
},
},
/*
{
title: 'Image Loading Events',
render: function() {
return (
<NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now()}}
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}/>
<NetworkImageCallbackExample
source={{uri: 'http://origami.design/public/images/bird-logo.png?r=1&t=' + Date.now()}}
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}
/>
);
},
},
*/
{
title: 'Error Handler',
render: function() {
@@ -263,7 +261,7 @@ const examples = [
title: 'Image Download Progress',
render: function() {
return (
<NetworkImageExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
<NetworkImageExample source={{uri: 'http://origami.design/public/images/bird-logo.png?r=1'}}/>
);
},
platform: 'ios',
@@ -567,14 +565,12 @@ const examples = [
platform: 'ios',
},
*/
/*
{
title: 'Image Size',
render: function() {
return <ImageSizeExample source={fullImage} />;
return <ImageSizeExample source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/d/d7/Chestnut-mandibled_Toucan.jpg' }} />;
},
},
*/
/*
{
title: 'MultipleSourcesExample',

View File

@@ -307,11 +307,11 @@ var TouchableDisabled = React.createClass({
render: function() {
return (
<View>
<TouchableOpacity disabled={true} style={[styles.row, styles.block]}>
<TouchableOpacity disabled={true} style={[styles.row, styles.block]} onPress={action('TouchableOpacity')}>
<Text style={styles.disabledButton}>Disabled TouchableOpacity</Text>
</TouchableOpacity>
<TouchableOpacity disabled={false} style={[styles.row, styles.block]}>
<TouchableOpacity disabled={false} style={[styles.row, styles.block]} onPress={action('TouchableOpacity')}>
<Text style={styles.button}>Enabled TouchableOpacity</Text>
</TouchableOpacity>
@@ -321,7 +321,7 @@ var TouchableDisabled = React.createClass({
animationVelocity={0}
underlayColor="rgb(210, 230, 255)"
style={[styles.row, styles.block]}
onPress={() => console.log('custom THW text - highlight')}>
onPress={action('TouchableHighlight')}>
<Text style={styles.disabledButton}>
Disabled TouchableHighlight
</Text>
@@ -332,7 +332,7 @@ var TouchableDisabled = React.createClass({
animationVelocity={0}
underlayColor="rgb(210, 230, 255)"
style={[styles.row, styles.block]}
onPress={() => console.log('custom THW text - highlight')}>
onPress={action('TouchableHighlight')}>
<Text style={styles.button}>
Enabled TouchableHighlight
</Text>

View File

@@ -31,6 +31,17 @@ var styles = StyleSheet.create({
borderColor: '#000033',
borderWidth: 1,
},
shadowBox: {
width: 100,
height: 100,
borderWidth: 2,
},
shadow: {
shadowOpacity: 0.5,
shadowColor: 'red',
shadowRadius: 3,
shadowOffset: { width: 3, height: 3 },
},
zIndex: {
justifyContent: 'space-around',
width: 100,
@@ -242,6 +253,19 @@ const examples = [
return <ZIndexExample />;
},
},
{
title: 'Basic shadow',
render() {
return <View style={[ styles.shadowBox, styles.shadow ]} />;
}
},
{
title: 'Shaped shadow',
description: 'borderRadius: 50',
render() {
return <View style={[ styles.shadowBox, styles.shadow, {borderRadius: 50} ]} />;
}
}
];
examples.forEach((example) => {

View File

@@ -113,7 +113,7 @@ var Cell = React.createClass({
case 2:
return styles.cellTextO;
default:
return {};
return null;
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.60",
"version": "0.0.68",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
@@ -11,36 +11,39 @@
"scripts": {
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
"build:examples": "build-storybook -o dist-examples -c ./examples/.storybook",
"build:performance": "cd performance && webpack",
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
"deploy:examples": "git checkout gh-pages && rm -rf ./storybook && mv dist-examples storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -",
"examples": "start-storybook -p 9001 -c ./examples/.storybook --dont-track",
"lint": "eslint src",
"lint": "eslint performance src",
"prepublish": "npm run build && npm run build:umd",
"test": "npm run lint && npm run test:jest",
"test:jest": "jest",
"test:watch": "npm run test:jest -- --watch"
},
"dependencies": {
"animated": "^0.1.3",
"animated": "^0.1.5",
"array-find-index": "^1.0.2",
"babel-runtime": "^6.11.6",
"asap": "^2.0.5",
"babel-runtime": "^6.20.0",
"debounce": "^1.0.0",
"deep-assign": "^2.0.0",
"fbjs": "^0.8.4",
"inline-style-prefixer": "^2.0.1",
"react-dom": "~15.3.2",
"fbjs": "^0.8.8",
"inline-style-prefixer": "^2.0.5",
"normalize-css-color": "^1.0.2",
"react-dom": "~15.4.1",
"react-textarea-autosize": "^4.0.4",
"react-timer-mixin": "^0.13.3"
},
"devDependencies": {
"@kadira/storybook": "^2.5.1",
"babel-cli": "^6.14.0",
"babel-core": "^6.14.0",
"babel-eslint": "^6.1.2",
"babel-loader": "^6.2.5",
"babel-core": "^6.21.0",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.10",
"babel-plugin-transform-react-remove-prop-types": "^0.2.11",
"babel-preset-react-native": "^1.9.0",
"del-cli": "^0.2.0",
"babel-preset-react-native": "^1.9.1",
"del-cli": "^0.2.1",
"enzyme": "^2.4.1",
"eslint": "^3.4.0",
"eslint-plugin-jsx-a11y": "^2.2.0",
@@ -48,16 +51,17 @@
"eslint-plugin-react": "^6.1.2",
"file-loader": "^0.9.0",
"jest": "^16.0.2",
"marky": "^1.1.1",
"node-libs-browser": "^0.5.3",
"react": "~15.3.2",
"react-addons-test-utils": "~15.3.2",
"react-test-renderer": "~15.3.2",
"react": "~15.4.1",
"react-addons-test-utils": "~15.4.1",
"react-test-renderer": "~15.4.1",
"url-loader": "^0.5.7",
"webpack": "^1.13.2",
"webpack-bundle-analyzer": "^1.5.3"
},
"peerDependencies": {
"react": "~15.3.2"
"react": "~15.4.1"
},
"author": "Nicolas Gallagher",
"license": "BSD-3-Clause",

65
performance/benchmark.js Normal file
View File

@@ -0,0 +1,65 @@
import * as marky from 'marky';
const fmt = (time) => `${Math.round(time * 100) / 100}ms`;
const measure = (name, fn) => {
marky.mark(name);
fn();
const performanceMeasure = marky.stop(name);
return performanceMeasure;
};
const benchmark = ({ name, description, setup, teardown, task, runs }) => {
return new Promise((resolve) => {
const performanceMeasures = [];
let i = 0;
setup();
const first = measure('first', task);
teardown();
const done = () => {
const mean = performanceMeasures.reduce((sum, performanceMeasure) => {
return sum + performanceMeasure.duration;
}, 0) / runs;
const firstDuration = fmt(first.duration);
const meanDuration = fmt(mean);
console.log(`First: ${firstDuration}`);
console.log(`Mean: ${meanDuration}`);
console.groupEnd();
resolve(mean);
};
const a = () => {
setup();
window.requestAnimationFrame(b);
};
const b = () => {
performanceMeasures.push(measure('mean', task));
window.requestAnimationFrame(c);
};
const c = () => {
teardown();
window.requestAnimationFrame(d);
};
const d = () => {
i += 1;
if (i < runs) {
window.requestAnimationFrame(a);
} else {
window.requestAnimationFrame(done);
}
};
console.group(`[benchmark] ${name}`);
console.log(description);
window.requestAnimationFrame(a);
});
};
module.exports = benchmark;

View File

@@ -0,0 +1,88 @@
import React, { Component, PropTypes } from 'react';
const createDeepTree = ({ StyleSheet, View }, options = {}) => {
class DeepTree extends Component {
static propTypes = {
breadth: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired,
id: PropTypes.number.isRequired,
wrap: PropTypes.number.isRequired
};
render() {
const { breadth, depth, id, wrap } = this.props;
let result = (
<View
style={[
styles.outer,
depth % 2 === 0 ? styles.even : styles.odd,
styles[`custom${id % 3}`]
]}
>
{depth === 0 && (
<View
style={[
styles.terminal,
styles[`terminal${id % 3}`]
]}
/>
)}
{depth !== 0 && Array.from({ length: breadth }).map((el, i) => (
<DeepTree
breadth={breadth}
depth={depth - 1}
id={i}
key={i}
wrap={wrap}
/>
))}
</View>
);
for (let i = 0; i < wrap; i++) {
result = <View>{result}</View>;
}
return result;
}
}
const stylesObject = {
outer: {
padding: 4
},
odd: {
flexDirection: 'row'
},
even: {
flexDirection: 'column'
},
custom0: {
backgroundColor: '#222'
},
custom1: {
backgroundColor: '#666'
},
custom2: {
backgroundColor: '#999'
},
terminal: {
width: 20,
height: 20
},
terminal0: {
backgroundColor: 'blue'
},
terminal1: {
backgroundColor: 'orange'
},
terminal2: {
backgroundColor: 'red'
}
};
const styles = options.registerStyles ? StyleSheet.create(stylesObject) : stylesObject;
return DeepTree;
};
module.exports = createDeepTree;

View File

@@ -0,0 +1,24 @@
import benchmark from '../../benchmark';
import createDeepTree from './createDeepTree';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactNative from 'react-native';
const deepTreeBenchmark = ({ breadth, depth, registerStyles = true, runs, wrap }, node) => () => {
// React Native for Web implementation of the tree
const DeepTree = createDeepTree(ReactNative, { registerStyles });
const setup = () => {};
const teardown = () => ReactDOM.unmountComponentAtNode(node);
return benchmark({
name: `DeepTree (${registerStyles ? 'registered' : 'unregistered'}) styles)`,
description: `depth=${depth}, breadth=${breadth}, wrap=${wrap}`,
runs,
setup,
teardown,
task: () => ReactDOM.render(<DeepTree breadth={breadth} depth={depth} id={0} wrap={wrap} />, node)
});
};
module.exports = deepTreeBenchmark;

11
performance/index.html Normal file
View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="root"></div>
<script src="../dist-performance/performance.bundle.js"></script>
</body>
</html>

16
performance/index.js Normal file
View File

@@ -0,0 +1,16 @@
import createDeepTree from './benchmarks/deepTree/createDeepTree';
import deepTree from './benchmarks/deepTree';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactNative from 'react-native';
const node = document.querySelector('.root');
const DeepTree = createDeepTree(ReactNative);
Promise.resolve()
.then(deepTree({ wrap: 4, depth: 3, breadth: 10, runs: 10, registerStyles: false }, node))
.then(deepTree({ wrap: 1, depth: 5, breadth: 3, runs: 20, registerStyles: false }, node))
.then(deepTree({ wrap: 4, depth: 3, breadth: 10, runs: 10 }, node))
.then(deepTree({ wrap: 1, depth: 5, breadth: 3, runs: 20 }, node))
.then(() => ReactDOM.render(<DeepTree breadth={3} depth={5} id={0} wrap={1} />, node));

View File

@@ -0,0 +1,45 @@
const path = require('path');
const webpack = require('webpack');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
entry: {
performance: './index'
},
output: {
path: path.resolve(__dirname, '../dist-performance'),
filename: 'performance.bundle.js'
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
plugins: [
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
new webpack.optimize.DedupePlugin(),
// https://github.com/animatedjs/animated/issues/40
new webpack.NormalModuleReplacementPlugin(
/es6-set/,
path.join(__dirname, '../src/modules/polyfills/Set.js')
),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
dead_code: true,
screw_ie8: true,
warnings: true
}
})
],
resolve: {
alias: {
'react-native': path.join(__dirname, '../src')
}
}
};

View File

@@ -1,5 +1,6 @@
import Animated from 'animated';
import Image from '../../components/Image';
import ScrollView from '../../components/ScrollView';
import StyleSheet from '../StyleSheet';
import Text from '../../components/Text';
import View from '../../components/View';
@@ -9,6 +10,7 @@ Animated.inject.FlattenStyle(StyleSheet.flatten);
module.exports = {
...Animated,
Image: Animated.createAnimatedComponent(Image),
ScrollView: Animated.createAnimatedComponent(ScrollView),
Text: Animated.createAnimatedComponent(Text),
View: Animated.createAnimatedComponent(View)
};

View File

@@ -0,0 +1,48 @@
exports[`apis/AppRegistry/renderApplication getApplication 1`] = `
"<style id=\"react-native-stylesheet\">
/* React Native StyleSheet*/
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}
body{margin:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}
@keyframes rn-ActivityIndicator-animation{0%{-webkit-transform: rotate(0deg); transform: rotate(0deg);}100%{-webkit-transform: rotate(360deg); transform: rotate(360deg);}}
@keyframes rn-ProgressBar-animation{0%{-webkit-transform: translateX(-100%); transform: translateX(-100%);}100%{-webkit-transform: translateX(400%); transform: translateX(400%);}}
.rn-pointerEvents\\:auto,.rn_pointerEvents\\:box-only,.rn-pointerEvents\\:box-none *{pointer-events:auto}.rn-pointerEvents\\:none,.rn_pointerEvents\\:box-only *,.rn-pointerEvents\\:box-none{pointer-events:none}
.rn-bottom\\:0px{bottom:0px}
.rn-left\\:0px{left:0px}
.rn-position\\:absolute{position:absolute}
.rn-right\\:0px{right:0px}
.rn-top\\:0px{top:0px}
.rn-alignItems\\:stretch{-ms-flex-align:stretch;-webkit-align-items:stretch;-webkit-box-align:stretch;align-items:stretch}
.rn-backgroundColor\\:transparent{background-color:transparent}
.rn-borderTopStyle\\:solid{border-top-style:solid}
.rn-borderRightStyle\\:solid{border-right-style:solid}
.rn-borderBottomStyle\\:solid{border-bottom-style:solid}
.rn-borderLeftStyle\\:solid{border-left-style:solid}
.rn-borderTopWidth\\:0px{border-top-width:0px}
.rn-borderRightWidth\\:0px{border-right-width:0px}
.rn-borderBottomWidth\\:0px{border-bottom-width:0px}
.rn-borderLeftWidth\\:0px{border-left-width:0px}
.rn-boxSizing\\:border-box{-moz-box-sizing:border-box;box-sizing:border-box}
.rn-color\\:inherit{color:inherit}
.rn-display\\:flex{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}
.rn-flexBasis\\:auto{-ms-preferred-size:auto;-webkit-flex-basis:auto;flex-basis:auto}
.rn-flexDirection\\:column{-ms-flex-direction:column;-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column}
.rn-font\\:inherit{font:inherit}
.rn-listStyle\\:none{list-style:none}
.rn-marginTop\\:0px{margin-top:0px}
.rn-marginRight\\:0px{margin-right:0px}
.rn-marginBottom\\:0px{margin-bottom:0px}
.rn-marginLeft\\:0px{margin-left:0px}
.rn-minHeight\\:0px{min-height:0px}
.rn-minWidth\\:0px{min-width:0px}
.rn-paddingTop\\:0px{padding-top:0px}
.rn-paddingRight\\:0px{padding-right:0px}
.rn-paddingBottom\\:0px{padding-bottom:0px}
.rn-paddingLeft\\:0px{padding-left:0px}
.rn-position\\:relative{position:relative}
.rn-textAlign\\:inherit{text-align:inherit}
.rn-textDecoration\\:none{text-decoration:none}
.rn-flexShrink\\:0{-ms-flex-negative:0px;-webkit-flex-shrink:0px;flex-shrink:0}
</style>"
`;

View File

@@ -10,6 +10,6 @@ describe('apis/AppRegistry/renderApplication', () => {
const { element, stylesheet } = getApplication(component, {});
expect(element).toBeTruthy();
expect(stylesheet.type).toEqual('style');
expect(stylesheet).toMatchSnapshot();
});
});

View File

@@ -8,9 +8,10 @@
import { Component } from 'react';
import invariant from 'fbjs/lib/invariant';
import { unmountComponentAtNode } from 'react/lib/ReactMount';
import { unmountComponentAtNode } from 'react-dom/lib/ReactMount';
import renderApplication, { getApplication } from './renderApplication';
const emptyObject = {};
const runnables = {};
type ComponentProvider = () => Component<any, any, any>
@@ -41,8 +42,8 @@ class AppRegistry {
static registerComponent(appKey: string, getComponentFunc: ComponentProvider): string {
runnables[appKey] = {
getApplication: ({ initialProps } = {}) => getApplication(getComponentFunc(), initialProps),
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag)
getApplication: ({ initialProps } = emptyObject) => getApplication(getComponentFunc(), initialProps),
run: ({ initialProps = emptyObject, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag)
};
return appKey;
}

View File

@@ -7,7 +7,7 @@
*/
import invariant from 'fbjs/lib/invariant';
import { render } from 'react/lib/ReactMount';
import { render } from 'react-dom/lib/ReactMount';
import ReactNativeApp from './ReactNativeApp';
import StyleSheet from '../../apis/StyleSheet';
import React, { Component } from 'react';
@@ -32,6 +32,6 @@ export function getApplication(RootComponent: Component, initialProps: Object):
rootComponent={RootComponent}
/>
);
const stylesheet = StyleSheet.render();
const stylesheet = StyleSheet.renderToString();
return { element, stylesheet };
}

View File

@@ -0,0 +1,28 @@
/**
* 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.
*
* web stub for BackAndroid.android.js
*
* @providesModule BackAndroid
*/
'use strict';
function emptyFunction() {}
const BackAndroid = {
exitApp: emptyFunction,
addEventListener() {
return {
remove: emptyFunction
};
},
removeEventListener: emptyFunction
};
module.exports = BackAndroid;

View File

@@ -6,7 +6,7 @@
"use strict";
var TouchHistoryMath = require('react/lib/TouchHistoryMath');
var TouchHistoryMath = require('react-dom/lib/TouchHistoryMath');
var currentCentroidXOfTouchesChangedAfter =
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;

View File

@@ -9,8 +9,8 @@
import { PropTypes } from 'react'
import ImageStylePropTypes from '../../components/Image/ImageStylePropTypes'
import ReactPropTypeLocations from 'react/lib/ReactPropTypeLocations'
import ReactPropTypesSecret from 'react/lib/ReactPropTypesSecret'
import ReactPropTypeLocations from 'react-dom/lib/ReactPropTypeLocations'
import ReactPropTypesSecret from 'react-dom/lib/ReactPropTypesSecret'
import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
import warning from 'fbjs/lib/warning'

View File

@@ -0,0 +1,15 @@
exports[`apis/StyleSheet/createReactDOMStyle converts ReactNative style to ReactDOM style 1`] = `
Object {
"borderBottomWidth": "1px",
"borderLeftWidth": "1px",
"borderRightWidth": "1px",
"borderTopWidth": "1px",
"borderWidthLeft": "2px",
"borderWidthRight": "3px",
"boxShadow": "1px 1px 1px 1px #000, 1px 2px 0px rgba(255,0,0,1)",
"display": "flex",
"marginBottom": "0px",
"marginTop": "0px",
"opacity": 0,
}
`;

View File

@@ -0,0 +1,26 @@
exports[`apis/StyleSheet/flattenStyle should merge style objects 1`] = `
Object {
"opacity": 1,
"order": 2,
}
`;
exports[`apis/StyleSheet/flattenStyle should override style properties 1`] = `
Object {
"backgroundColor": "#023c69",
"order": null,
}
`;
exports[`apis/StyleSheet/flattenStyle should overwrite properties with \`undefined\` 1`] = `
Object {
"backgroundColor": undefined,
}
`;
exports[`apis/StyleSheet/flattenStyle should recursively flatten arrays 1`] = `
Object {
"opacity": 1,
"order": 3,
}
`;

View File

@@ -0,0 +1 @@
exports[`apis/StyleSheet/generateCss generates correct css 1`] = `"-webkit-transition-duration:0.1s;border-width-left:2px;border-width-right:3px;box-shadow:1px 1px 1px 1px #000;position:absolute;transition-duration:0.1s"`;

View File

@@ -1,10 +1,14 @@
exports[`apis/StyleSheet resolve 1`] = `
Object {
"className": "test __style_df __style_pebn",
"style": Object {
"display": null,
"opacity": 1,
"pointerEvents": null,
},
}
exports[`apis/StyleSheet renderToString 1`] = `
"<style id=\"react-native-stylesheet\">
.rn-borderTopColor\\:red{border-top-color:red}
.rn-borderRightColor\\:red{border-right-color:red}
.rn-borderBottomColor\\:red{border-bottom-color:red}
.rn-borderLeftColor\\:red{border-left-color:red}
.rn-borderTopWidth\\:0px{border-top-width:0px}
.rn-borderRightWidth\\:0px{border-right-width:0px}
.rn-borderBottomWidth\\:0px{border-bottom-width:0px}
.rn-borderLeftWidth\\:0px{border-left-width:0px}
.rn-left\\:50px{left:50px}
.rn-position\\:absolute{position:absolute}
</style>"
`;

View File

@@ -0,0 +1,179 @@
exports[`apis/StyleSheet/registry resolve with register, resolves to className 1`] = `
Object {
"className": "
rn-borderTopColor:red
rn-borderRightColor:red
rn-borderBottomColor:red
rn-borderLeftColor:red
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:100px",
"style": Object {},
}
`;
exports[`apis/StyleSheet/registry resolve with register, resolves to className 2`] = `
Object {
"className": "
rn-borderTopColor:red
rn-borderRightColor:red
rn-borderBottomColor:red
rn-borderLeftColor:red
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:200px",
"style": Object {},
}
`;
exports[`apis/StyleSheet/registry resolve with register, resolves to className 3`] = `
Object {
"className": "
rn-borderTopColor:red
rn-borderRightColor:red
rn-borderBottomColor:red
rn-borderLeftColor:red
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:100px",
"style": Object {},
}
`;
exports[`apis/StyleSheet/registry resolve with register, resolves to mixed 1`] = `
Object {
"className": "
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"width": "100px",
},
}
`;
exports[`apis/StyleSheet/registry resolve with register, resolves to mixed 2`] = `
Object {
"className": "
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute
rn-width:200px",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
},
}
`;
exports[`apis/StyleSheet/registry resolve with register, resolves to mixed 3`] = `
Object {
"className": "
rn-left:50px
rn-pointerEvents:box-only
rn-position:absolute",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"width": "100px",
},
}
`;
exports[`apis/StyleSheet/registry resolve without register, resolves to inline styles 1`] = `
Object {
"className": "
",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"left": "50px",
"pointerEvents": "box-only",
"position": "absolute",
"width": "100px",
},
}
`;
exports[`apis/StyleSheet/registry resolve without register, resolves to inline styles 2`] = `
Object {
"className": "
",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"left": "50px",
"pointerEvents": "box-only",
"position": "absolute",
"width": "200px",
},
}
`;
exports[`apis/StyleSheet/registry resolve without register, resolves to inline styles 3`] = `
Object {
"className": "
",
"style": Object {
"borderBottomColor": "red",
"borderBottomWidth": "0px",
"borderLeftColor": "red",
"borderLeftWidth": "0px",
"borderRightColor": "red",
"borderRightWidth": "0px",
"borderTopColor": "red",
"borderTopWidth": "0px",
"left": "50px",
"pointerEvents": "box-only",
"position": "absolute",
"width": "100px",
},
}
`;

View File

@@ -0,0 +1,28 @@
/* eslint-env jasmine, jest */
import createReactDOMStyle from '../createReactDOMStyle';
const reactNativeStyle = {
boxShadow: '1px 1px 1px 1px #000',
borderWidthLeft: 2,
borderWidth: 1,
borderWidthRight: 3,
display: 'flex',
marginVertical: 0,
opacity: 0,
shadowColor: 'red',
shadowOffset: { width: 1, height: 2 },
resizeMode: 'contain'
};
describe('apis/StyleSheet/createReactDOMStyle', () => {
test('converts ReactNative style to ReactDOM style', () => {
expect(createReactDOMStyle(reactNativeStyle)).toMatchSnapshot();
});
test('noop on DOM styles', () => {
const firstStyle = createReactDOMStyle(reactNativeStyle);
const secondStyle = createReactDOMStyle(firstStyle);
expect(firstStyle).toEqual(secondStyle);
});
});

View File

@@ -1,12 +0,0 @@
/* eslint-env jasmine, jest */
import createReactStyleObject from '../createReactStyleObject';
describe('apis/StyleSheet/createReactStyleObject', () => {
test('converts ReactNative style to ReactDOM style', () => {
const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 };
const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 };
expect(createReactStyleObject(reactNativeStyle)).toEqual(expectedStyle);
});
});

View File

@@ -9,9 +9,9 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
import flattenStyle from '..';
import flattenStyle from '../flattenStyle';
describe('modules/flattenStyle', () => {
describe('apis/StyleSheet/flattenStyle', () => {
test('should merge style objects', () => {
const style = flattenStyle([
{ opacity: 1 },

View File

@@ -0,0 +1,16 @@
/* eslint-env jasmine, jest */
import generateCss from '../generateCss';
describe('apis/StyleSheet/generateCss', () => {
test('generates correct css', () => {
const style = {
boxShadow: '1px 1px 1px 1px #000',
borderWidthLeft: 2,
borderWidthRight: 3,
position: 'absolute',
transitionDuration: '0.1s'
};
expect(generateCss(style)).toMatchSnapshot();
});
});

View File

@@ -1,7 +1,7 @@
/* eslint-env jasmine, jest */
import { getDefaultStyleSheet } from '../css';
import StyleSheet from '..';
import StyleRegistry from '../registry';
const isPlainObject = (x) => {
const toString = Object.prototype.toString;
@@ -16,7 +16,7 @@ const isPlainObject = (x) => {
describe('apis/StyleSheet', () => {
beforeEach(() => {
StyleSheet._reset();
StyleRegistry.reset();
});
test('absoluteFill', () => {
@@ -32,11 +32,6 @@ describe('apis/StyleSheet', () => {
const style = StyleSheet.create({ root: { opacity: 1 } });
expect(Number.isInteger(style.root) === true).toBeTruthy();
});
test('renders a style sheet in the browser', () => {
StyleSheet.create({ root: { color: 'red' } });
expect(document.getElementById('react-native-style__').textContent).toEqual(getDefaultStyleSheet());
});
});
test('flatten', () => {
@@ -47,18 +42,17 @@ describe('apis/StyleSheet', () => {
expect(Number.isInteger(StyleSheet.hairlineWidth) === true).toBeTruthy();
});
test('render', () => {
expect(StyleSheet.render().props.dangerouslySetInnerHTML.__html).toEqual(getDefaultStyleSheet());
});
test('resolve', () => {
expect(StyleSheet.resolve({
className: 'test',
style: {
display: 'flex',
opacity: 1,
pointerEvents: 'box-none'
test('renderToString', () => {
StyleSheet.create({
a: {
borderWidth: 0,
borderColor: 'red'
},
b: {
position: 'absolute',
left: 50
}
})).toMatchSnapshot();
});
expect(StyleSheet.renderToString()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,22 @@
/* eslint-env jasmine, jest */
import injector from '../injector';
describe('apis/StyleSheet/injector', () => {
beforeEach(() => {
document.head.insertAdjacentHTML('afterbegin', `
<style id="react-native-stylesheet">
.rn-alignItems\\:stretch{align-items:stretch;}
.rn-position\\:top{position:top;}
</style>
`);
});
test('hydrates from SSR', () => {
const classList = injector.getClassNames();
expect(classList).toEqual({
'rn-alignItems\\:stretch': true,
'rn-position\\:top': true
});
});
});

View File

@@ -0,0 +1,13 @@
/* eslint-env jasmine, jest */
import prefixInlineStyles from '../prefixInlineStyles';
describe('apis/StyleSheet/prefixInlineStyles', () => {
test('handles array values', () => {
const style = {
display: [ '-webkit-flex', 'flex' ]
};
expect(prefixInlineStyles(style)).toEqual({ display: 'flex' });
});
});

View File

@@ -1,20 +0,0 @@
/* eslint-env jasmine, jest */
import processTextShadow from '../processTextShadow';
describe('apis/StyleSheet/processTextShadow', () => {
test('textShadowOffset', () => {
const style = {
textShadowColor: 'red',
textShadowOffset: { width: 2, height: 2 },
textShadowRadius: 5
};
expect(processTextShadow(style)).toEqual({
textShadow: '2px 2px 5px red',
textShadowColor: null,
textShadowOffset: null,
textShadowRadius: null
});
});
});

View File

@@ -1,28 +0,0 @@
/* eslint-env jasmine, jest */
import processTransform from '../processTransform';
describe('apis/StyleSheet/processTransform', () => {
test('transform', () => {
const style = {
transform: [
{ scaleX: 20 },
{ translateX: 20 },
{ rotate: '20deg' }
]
};
expect(processTransform(style)).toEqual({ transform: 'scaleX(20) translateX(20px) rotate(20deg)' });
});
test('transformMatrix', () => {
const style = {
transformMatrix: [ 1, 2, 3, 4, 5, 6 ]
};
expect(processTransform(style)).toEqual({
transform: 'matrix3d(1,2,3,4,5,6)',
transformMatrix: null
});
});
});

View File

@@ -1,13 +0,0 @@
/* eslint-env jasmine, jest */
import processVendorPrefixes from '../processVendorPrefixes';
describe('apis/StyleSheet/processVendorPrefixes', () => {
test('handles array values', () => {
const style = {
display: [ '-webkit-flex', 'flex' ]
};
expect(processVendorPrefixes(style)).toEqual({ display: 'flex' });
});
});

View File

@@ -0,0 +1,54 @@
/* eslint-env jasmine, jest */
import StyleRegistry from '../registry';
describe('apis/StyleSheet/registry', () => {
beforeEach(() => {
StyleRegistry.reset();
});
test('register', () => {
const style = { opacity: 0 };
const id = StyleRegistry.register(style);
expect(typeof id === 'number').toBe(true);
});
describe('resolve', () => {
const styleA = { borderWidth: 0, borderColor: 'red', width: 100 };
const styleB = { position: 'absolute', left: 50, pointerEvents: 'box-only' };
const styleC = { width: 200 };
const testResolve = (a, b, c) => {
// no common properties, different resolving order, same result
const resolve1 = StyleRegistry.resolve([ a, b ]);
expect(resolve1).toMatchSnapshot();
const resolve2 = StyleRegistry.resolve([ b, a ]);
expect(resolve1).toEqual(resolve2);
// common properties, different resolving order, different result
const resolve3 = StyleRegistry.resolve([ a, b, c ]);
expect(resolve3).toMatchSnapshot();
const resolve4 = StyleRegistry.resolve([ c, a, b ]);
expect(resolve4).toMatchSnapshot();
expect(resolve3).not.toEqual(resolve4);
};
test('with register, resolves to className', () => {
const a = StyleRegistry.register(styleA);
const b = StyleRegistry.register(styleB);
const c = StyleRegistry.register(styleC);
testResolve(a, b, c);
});
test('with register, resolves to mixed', () => {
const a = styleA;
const b = StyleRegistry.register(styleB);
const c = StyleRegistry.register(styleC);
testResolve(a, b, c);
});
test('without register, resolves to inline styles', () => {
testResolve(styleA, styleB, styleC);
});
});
});

View File

@@ -0,0 +1,60 @@
/* eslint-env jasmine, jest */
import resolveBoxShadow from '../resolveBoxShadow';
describe('apis/StyleSheet/resolveBoxShadow', () => {
test('shadowColor only', () => {
const resolvedStyle = {};
const style = { shadowColor: 'red' };
resolveBoxShadow(resolvedStyle, style);
expect(resolvedStyle).toEqual({
boxShadow: '0px 0px 0px rgba(255,0,0,1)'
});
});
test('shadowColor and shadowOpacity only', () => {
const resolvedStyle = {};
const style = { shadowColor: 'red', shadowOpacity: 0.5 };
resolveBoxShadow(resolvedStyle, style);
expect(resolvedStyle).toEqual({
boxShadow: '0px 0px 0px rgba(255,0,0,0.5)'
});
});
test('shadowOffset only', () => {
const resolvedStyle = {};
const style = { shadowOffset: { width: 1, height: 2 } };
resolveBoxShadow(resolvedStyle, style);
expect(resolvedStyle).toEqual({
boxShadow: '1px 2px 0px rgba(0,0,0,0)'
});
});
test('shadowRadius only', () => {
const resolvedStyle = {};
const style = { shadowRadius: 5 };
resolveBoxShadow(resolvedStyle, style);
expect(resolvedStyle).toEqual({
boxShadow: '0px 0px 5px rgba(0,0,0,0)'
});
});
test('shadowOffset, shadowRadius, shadowColor', () => {
const resolvedStyle = {};
const style = {
shadowColor: 'rgba(50,60,70,0.5)',
shadowOffset: { width: 1, height: 2 },
shadowOpacity: 0.5,
shadowRadius: 3
};
resolveBoxShadow(resolvedStyle, style);
expect(resolvedStyle).toEqual({
boxShadow: '1px 2px 3px rgba(50,60,70,0.25)'
});
});
});

View File

@@ -0,0 +1,19 @@
/* eslint-env jasmine, jest */
import resolveTextShadow from '../resolveTextShadow';
describe('apis/StyleSheet/resolveTextShadow', () => {
test('textShadowOffset', () => {
const resolvedStyle = {};
const style = {
textShadowColor: 'red',
textShadowOffset: { width: 1, height: 2 },
textShadowRadius: 5
};
resolveTextShadow(resolvedStyle, style);
expect(resolvedStyle).toEqual({
textShadow: '1px 2px 5px red'
});
});
});

View File

@@ -0,0 +1,32 @@
/* eslint-env jasmine, jest */
import resolveTransform from '../resolveTransform';
describe('apis/StyleSheet/resolveTransform', () => {
test('transform', () => {
const resolvedStyle = {};
const style = {
transform: [
{ perspective: 50 },
{ scaleX: 20 },
{ translateX: 20 },
{ rotate: '20deg' }
]
};
resolveTransform(resolvedStyle, style);
expect(resolvedStyle).toEqual({
transform: 'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)'
});
});
test('transformMatrix', () => {
const resolvedStyle = {};
const style = { transformMatrix: [ 1, 2, 3, 4, 5, 6 ] };
resolveTransform(resolvedStyle, style);
expect(resolvedStyle).toEqual({
transform: 'matrix3d(1,2,3,4,5,6)'
});
});
});

View File

@@ -0,0 +1,6 @@
import expandStyle from './expandStyle';
import i18nStyle from './i18nStyle';
const createReactDOMStyle = (flattenedReactNativeStyle) => expandStyle(i18nStyle(flattenedReactNativeStyle));
module.exports = createReactDOMStyle;

View File

@@ -1,20 +0,0 @@
import expandStyle from './expandStyle';
import flattenStyle from '../../modules/flattenStyle';
import i18nStyle from './i18nStyle';
import processTextShadow from './processTextShadow';
import processTransform from './processTransform';
import processVendorPrefixes from './processVendorPrefixes';
const processors = [
processTextShadow,
processTransform,
processVendorPrefixes
];
const applyProcessors = (style) => processors.reduce((style, processor) => processor(style), style);
const createReactDOMStyleObject = (reactNativeStyle) => applyProcessors(
expandStyle(i18nStyle(flattenStyle(reactNativeStyle)))
);
module.exports = createReactDOMStyleObject;

View File

@@ -1,42 +0,0 @@
const DISPLAY_FLEX_CLASSNAME = '__style_df';
const POINTER_EVENTS_AUTO_CLASSNAME = '__style_pea';
const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn';
const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo';
const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen';
/* eslint-disable max-len */
const CSS_RESET =
// reset unwanted styles
'/* React Native */\n' +
'html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' +
'body{margin:0}\n' +
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' +
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}';
const CSS_HELPERS =
// vendor prefix 'display:flex' until React supports fallback values for inline styles
`.${DISPLAY_FLEX_CLASSNAME} {display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}\n` +
// implement React Native's pointer event values
`.${POINTER_EVENTS_AUTO_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME}, .${POINTER_EVENTS_BOX_NONE_CLASSNAME} * {pointer-events:auto}\n` +
`.${POINTER_EVENTS_NONE_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME} *, .${POINTER_EVENTS_NONE_CLASSNAME} {pointer-events:none}`;
/* eslint-enable max-len */
const styleAsClassName = {
display: {
'flex': DISPLAY_FLEX_CLASSNAME
},
pointerEvents: {
'auto': POINTER_EVENTS_AUTO_CLASSNAME,
'box-none': POINTER_EVENTS_BOX_NONE_CLASSNAME,
'box-only': POINTER_EVENTS_BOX_ONLY_CLASSNAME,
'none': POINTER_EVENTS_NONE_CLASSNAME
}
};
export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`;
export const getStyleAsHelperClassName = (prop, value) => {
return styleAsClassName[prop] && styleAsClassName[prop][value];
};

View File

@@ -10,6 +10,9 @@
*/
import normalizeValue from './normalizeValue';
import resolveBoxShadow from './resolveBoxShadow';
import resolveTextShadow from './resolveTextShadow';
import resolveTransform from './resolveTransform';
const emptyObject = {};
const styleShortFormProperties = {
@@ -28,46 +31,83 @@ const styleShortFormProperties = {
writingDirection: [ 'direction' ]
};
const alphaSort = (arr) => arr.sort((a, b) => {
const alphaSortProps = (propsArray) => propsArray.sort((a, b) => {
if (a < b) { return -1; }
if (a > b) { return 1; }
return 0;
});
const createStyleReducer = (originalStyle) => {
const originalStyleProps = Object.keys(originalStyle);
const expandStyle = (style) => {
if (!style) { return emptyObject; }
const styleProps = Object.keys(style);
const sortedStyleProps = alphaSortProps(styleProps);
let hasResolvedBoxShadow = false;
let hasResolvedTextShadow = false;
return (style, prop) => {
const value = normalizeValue(prop, originalStyle[prop]);
const longFormProperties = styleShortFormProperties[prop];
const reducer = (resolvedStyle, prop) => {
const value = normalizeValue(prop, style[prop]);
if (value == null) { return resolvedStyle; }
// React Native treats `flex:1` like `flex:1 1 auto`
if (prop === 'flex') {
style.flexGrow = value;
style.flexShrink = 1;
style.flexBasis = 'auto';
// React Native accepts 'center' as a value
} else if (prop === 'textAlignVertical') {
style.verticalAlign = (value === 'center' ? 'middle' : value);
} else if (longFormProperties) {
longFormProperties.forEach((longForm, i) => {
// the value of any longform property in the original styles takes
// precedence over the shortform's value
if (originalStyleProps.indexOf(longForm) === -1) {
style[longForm] = value;
switch (prop) {
// ignore React Native styles
case 'elevation':
case 'resizeMode': {
break;
}
case 'flex': {
resolvedStyle.flexGrow = value;
resolvedStyle.flexShrink = 1;
resolvedStyle.flexBasis = 'auto';
break;
}
case 'shadowColor':
case 'shadowOffset':
case 'shadowOpacity':
case 'shadowRadius': {
if (!hasResolvedBoxShadow) {
resolveBoxShadow(resolvedStyle, style);
}
});
} else {
style[prop] = value;
hasResolvedBoxShadow = true;
break;
}
case 'textAlignVertical': {
resolvedStyle.verticalAlign = (value === 'center' ? 'middle' : value);
break;
}
case 'textShadowColor':
case 'textShadowOffset':
case 'textShadowRadius': {
if (!hasResolvedTextShadow) {
resolveTextShadow(resolvedStyle, style);
}
hasResolvedTextShadow = true;
break;
}
case 'transform': {
resolveTransform(resolvedStyle, style);
break;
}
default: {
const longFormProperties = styleShortFormProperties[prop];
if (longFormProperties) {
longFormProperties.forEach((longForm, i) => {
// the value of any longform property in the original styles takes
// precedence over the shortform's value
if (styleProps.indexOf(longForm) === -1) {
resolvedStyle[longForm] = value;
}
});
} else {
resolvedStyle[prop] = value;
}
}
}
return style;
};
};
const expandStyle = (style = emptyObject) => {
const sortedStyleProps = alphaSort(Object.keys(style));
const styleReducer = createStyleReducer(style);
return sortedStyleProps.reduce(styleReducer, {});
return resolvedStyle;
};
const resolvedStyle = sortedStyleProps.reduce(reducer, {});
return resolvedStyle;
};
module.exports = expandStyle;

View File

@@ -1,4 +1,3 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
@@ -10,10 +9,8 @@
* @providesModule flattenStyle
* @flow
*/
'use strict';
var ReactNativePropRegistry = require('../ReactNativePropRegistry');
var invariant = require('fbjs/lib/invariant');
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
import invariant from 'fbjs/lib/invariant';
function getStyle(style) {
if (typeof style === 'number') {
@@ -22,22 +19,26 @@ function getStyle(style) {
return style;
}
function flattenStyle(style: ?StyleObj): ?Object {
function flattenStyle(style) {
if (!style) {
return undefined;
}
invariant(style !== true, 'style may be false but not true');
if (process.env.NODE !== 'production') {
invariant(style !== true, 'style may be false but not true');
}
if (!Array.isArray(style)) {
return getStyle(style);
}
var result = {};
for (var i = 0, styleLength = style.length; i < styleLength; ++i) {
var computedStyle = flattenStyle(style[i]);
const result = {};
for (let i = 0, styleLength = style.length; i < styleLength; ++i) {
const computedStyle = flattenStyle(style[i]);
if (computedStyle) {
for (var key in computedStyle) {
result[key] = computedStyle[key];
for (const key in computedStyle) {
const value = computedStyle[key];
result[key] = value;
}
}
}

View File

@@ -0,0 +1,23 @@
import hyphenate from './hyphenate';
import mapKeyValue from '../../modules/mapKeyValue';
import normalizeValue from './normalizeValue';
import prefixAll from 'inline-style-prefixer/static';
const createDeclarationString = (prop, val) => {
const name = hyphenate(prop);
const value = normalizeValue(prop, val);
if (Array.isArray(val)) {
return val.map((v) => `${name}:${v}`).join(';');
}
return `${name}:${value}`;
};
/**
* Generates valid CSS rule body from a JS object
*
* generateCss({ width: 20, color: 'blue' });
* // => 'color:blue;width:20px'
*/
const generateCss = (style) => mapKeyValue(prefixAll(style), createDeclarationString).sort().join(';');
module.exports = generateCss;

View File

@@ -0,0 +1,3 @@
const RE_1 = /([A-Z])/g;
const RE_2 = /^ms-/;
module.exports = (s) => s.replace(RE_1, '-$1').toLowerCase().replace(RE_2, '-ms-');

View File

@@ -1,6 +1,8 @@
import I18nManager from '../I18nManager';
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue';
const emptyObject = {};
/**
* Map of property names to their BiDi equivalent.
*/
@@ -64,38 +66,40 @@ const swapLtrRtl = (value:String): String => {
return value === 'ltr' ? 'rtl' : value === 'rtl' ? 'ltr' : value;
};
const i18nStyle = (style = {}) => {
const i18nStyle = (style = emptyObject) => {
const newStyle = {};
for (const prop in style) {
if (style.hasOwnProperty(prop)) {
const indexOfNoFlip = prop.indexOf('$noI18n');
if (!Object.prototype.hasOwnProperty.call(style, prop)) {
continue;
}
if (I18nManager.isRTL) {
if (PROPERTIES_TO_SWAP[prop]) {
const newProp = flipProperty(prop);
newStyle[newProp] = style[prop];
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
newStyle[prop] = swapLeftRight(style[prop]);
} else if (PROPERTIES_SWAP_LTR_RTL[prop]) {
newStyle[prop] = swapLtrRtl(style[prop]);
} else if (prop === 'textShadowOffset') {
newStyle[prop] = style[prop];
newStyle[prop].width = additiveInverse(style[prop].width);
} else if (prop === 'transform') {
newStyle[prop] = style[prop].map(flipTransform);
} else if (indexOfNoFlip > -1) {
const newProp = prop.substring(0, indexOfNoFlip);
newStyle[newProp] = style[prop];
} else {
newStyle[prop] = style[prop];
}
const indexOfNoFlip = prop.indexOf('$noI18n');
if (I18nManager.isRTL) {
if (PROPERTIES_TO_SWAP[prop]) {
const newProp = flipProperty(prop);
newStyle[newProp] = style[prop];
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
newStyle[prop] = swapLeftRight(style[prop]);
} else if (PROPERTIES_SWAP_LTR_RTL[prop]) {
newStyle[prop] = swapLtrRtl(style[prop]);
} else if (prop === 'textShadowOffset') {
newStyle[prop] = style[prop];
newStyle[prop].width = additiveInverse(style[prop].width);
} else if (prop === 'transform') {
newStyle[prop] = style[prop].map(flipTransform);
} else if (indexOfNoFlip > -1) {
const newProp = prop.substring(0, indexOfNoFlip);
newStyle[newProp] = style[prop];
} else {
if (indexOfNoFlip > -1) {
const newProp = prop.substring(0, indexOfNoFlip);
newStyle[newProp] = style[prop];
} else {
newStyle[prop] = style[prop];
}
newStyle[prop] = style[prop];
}
} else {
if (indexOfNoFlip > -1) {
const newProp = prop.substring(0, indexOfNoFlip);
newStyle[newProp] = style[prop];
} else {
newStyle[prop] = style[prop];
}
}
}

View File

@@ -1,89 +1,26 @@
import * as css from './css';
import createReactStyleObject from './createReactStyleObject';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import flattenStyle from '../../modules/flattenStyle';
import React from 'react';
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
import flattenStyle from './flattenStyle';
import initialize from './initialize';
import injector from './injector';
import StyleRegistry from './registry';
let styleElement;
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM;
const STYLE_SHEET_ID = 'react-native-style__';
initialize();
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 };
const defaultStyleSheet = css.getDefaultStyleSheet();
const insertStyleSheet = () => {
// check if the server rendered the style sheet
styleElement = document.getElementById(STYLE_SHEET_ID);
// if not, inject the style sheet
if (!styleElement) {
document.head.insertAdjacentHTML(
'afterbegin',
`<style id="${STYLE_SHEET_ID}">${defaultStyleSheet}</style>`
);
shouldInsertStyleSheet = false;
}
};
module.exports = {
/**
* For testing
* @private
*/
_reset() {
if (styleElement) {
document.head.removeChild(styleElement);
styleElement = null;
shouldInsertStyleSheet = true;
}
},
absoluteFill: ReactNativePropRegistry.register(absoluteFillObject),
absoluteFill: StyleRegistry.register(absoluteFillObject),
absoluteFillObject,
create(styles) {
if (shouldInsertStyleSheet) {
insertStyleSheet();
}
const result = {};
for (const key in styles) {
Object.keys(styles).forEach((key) => {
if (process.env.NODE_ENV !== 'production') {
require('./StyleSheetValidation').validateStyle(key, styles);
}
result[key] = ReactNativePropRegistry.register(styles[key]);
}
result[key] = StyleRegistry.register(styles[key]);
});
return result;
},
hairlineWidth: 1,
flatten: flattenStyle,
/* @platform web */
render() {
return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />;
},
/**
* Accepts React props and converts style declarations to classNames when necessary
* @platform web
*/
resolve(props) {
let className = props.className || '';
const style = createReactStyleObject(props.style);
for (const prop in style) {
const value = style[prop];
const replacementClassName = css.getStyleAsHelperClassName(prop, value);
if (replacementClassName) {
className += ` ${replacementClassName}`;
style[prop] = null;
}
}
return { className, style };
}
renderToString: injector.getStyleSheetHtml
};

View File

@@ -0,0 +1,39 @@
import injector from './injector';
import StyleRegistry from './registry';
const initialize = () => {
injector.addRule(
'reset',
'/* React Native StyleSheet*/\n' +
'html{' +
'font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;' +
'-webkit-tap-highlight-color:rgba(0,0,0,0)' +
'}\n' +
'body{margin:0}\n' +
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' +
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}'
);
injector.addRule(
'keyframes',
'@keyframes rn-ActivityIndicator-animation{' +
'0%{-webkit-transform: rotate(0deg); transform: rotate(0deg);}' +
'100%{-webkit-transform: rotate(360deg); transform: rotate(360deg);}' +
'}\n' +
'@keyframes rn-ProgressBar-animation{' +
'0%{-webkit-transform: translateX(-100%); transform: translateX(-100%);}' +
'100%{-webkit-transform: translateX(400%); transform: translateX(400%);}' +
'}'
);
injector.addRule(
'pointer-events',
'.rn-pointerEvents\\:auto,.rn-pointerEvents\\:box-only,.rn-pointerEvents\\:box-none *{pointer-events:auto}' +
'.rn-pointerEvents\\:none,.rn-pointerEvents\\:box-only *,.rn-pointerEvents\\:box-none{pointer-events:none}'
);
const classNames = injector.getClassNames();
StyleRegistry.initialize(classNames);
};
export default initialize;

View File

@@ -0,0 +1,106 @@
/**
* Based on
* https://github.com/lelandrichardson/react-primitives/blob/master/src/StyleSheet/injector.js
*/
import asap from 'asap';
const emptyObject = {};
const hasOwnProperty = Object.prototype.hasOwnProperty;
const CLASSNAME_REXEP = /\.rn-([^{;\s]+)/g;
const STYLE_ELEMENT_ID = 'react-native-stylesheet';
let registry = {};
let isDirty = false;
/**
* Registers a rule and requests an update to the style sheet
*/
const addRule = (key, rule) => {
if (!registry[key]) {
registry[key] = rule;
isDirty = true;
if (global.document) {
asap(frame);
}
}
};
/**
* Returns a string of the registered rules
*/
const getStyleText = () => {
/* eslint prefer-template:0 */
let result = '\n';
for (const key in registry) {
if (hasOwnProperty.call(registry, key)) {
result += registry[key] + '\n';
}
}
return result;
};
/**
* Returns an HTML string for server rendering
*/
const getStyleSheetHtml = () => `<style id="${STYLE_ELEMENT_ID}">${getStyleText()}</style>`;
const reset = () => { registry = {}; };
/**
* Finds or injects the style sheet when in a browser environment
*/
let styleNode = null;
const getStyleNode = () => {
if (global.document) {
if (!styleNode) {
// look for existing style sheet (could also be server-rendered)
styleNode = document.getElementById(STYLE_ELEMENT_ID);
if (!styleNode) {
// if there is no existing stylesheet, inject it style sheet
document.head.insertAdjacentHTML('afterbegin', getStyleSheetHtml());
styleNode = document.getElementById(STYLE_ELEMENT_ID);
}
}
return styleNode;
}
};
/**
* Determines which classes are available in the existing document. Doesn't
* rely on the registry so it can be used to read class names from a SSR style
* sheet.
*/
const getClassNames = () => {
const styleNode = getStyleNode();
if (styleNode) {
const text = styleNode.textContent;
const matches = text.match(CLASSNAME_REXEP);
if (matches) {
return matches.map((name) => name.slice(1)).reduce((classMap, className) => {
classMap[className] = true;
return classMap;
}, {});
}
}
return emptyObject;
};
const frame = () => {
if (!isDirty || !global.document) { return; }
isDirty = false;
const styleNode = getStyleNode();
if (styleNode) {
const css = getStyleText();
styleNode.textContent = css;
}
};
module.exports = {
addRule,
getClassNames,
getStyleSheetHtml,
reset
};

View File

@@ -1,30 +1,45 @@
const unitlessNumbers = {
animationIterationCount: true,
borderImageOutset: true,
borderImageSlice: true,
borderImageWidth: true,
boxFlex: true,
boxFlexGroup: true,
boxOrdinalGroup: true,
columnCount: true,
flex: true,
flexGrow: true,
flexOrder: true,
flexPositive: true,
flexShrink: true,
flexNegative: true,
fontWeight: true,
gridRow: true,
gridColumn: true,
lineClamp: true,
opacity: true,
order: true,
orphans: true,
tabSize: true,
widows: true,
zIndex: true,
zoom: true,
// SVG-related
fillOpacity: true,
floodOpacity: true,
stopOpacity: true,
strokeDasharray: true,
strokeDashoffset: true,
strokeMiterlimit: true,
strokeOpacity: true,
strokeWidth: true,
// transform types
scale: true,
scaleX: true,
scaleY: true,
scaleZ: true
scaleZ: true,
// RN properties
shadowOpacity: true
};
const normalizeValue = (property, value) => {

View File

@@ -1,16 +1,18 @@
import prefixAll from 'inline-style-prefixer/static';
const processVendorPrefixes = (style) => {
const prefixInlineStyles = (style) => {
const prefixedStyles = prefixAll(style);
// React@15 removed undocumented support for fallback values in
// inline-styles. Revert array values to the standard CSS value
for (const prop in prefixedStyles) {
Object.keys(prefixedStyles).forEach((prop) => {
const value = prefixedStyles[prop];
if (Array.isArray(value)) {
prefixedStyles[prop] = value[value.length - 1];
}
}
});
return prefixedStyles;
};
module.exports = processVendorPrefixes;
module.exports = prefixInlineStyles;

View File

@@ -1,19 +0,0 @@
import normalizeValue from './normalizeValue';
const processTextShadow = (style) => {
if (style && style.textShadowOffset) {
const { height, width } = style.textShadowOffset;
const offsetX = normalizeValue(null, height || 0);
const offsetY = normalizeValue(null, width || 0);
const blurRadius = normalizeValue(null, style.textShadowRadius || 0);
const color = style.textShadowColor || 'currentcolor';
style.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
style.textShadowColor = null;
style.textShadowOffset = null;
style.textShadowRadius = null;
}
return style;
};
module.exports = processTextShadow;

View File

@@ -0,0 +1,219 @@
/**
* WARNING: changes to this file in particular can cause significant changes to
* the results of render performance benchmarks.
*/
import createReactDOMStyle from './createReactDOMStyle';
import flattenArray from '../../modules/flattenArray';
import flattenStyle from './flattenStyle';
import generateCss from './generateCss';
import injector from './injector';
import mapKeyValue from '../../modules/mapKeyValue';
import prefixInlineStyles from './prefixInlineStyles';
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
const prefix = 'r';
const SPACE_REGEXP = /\s/g;
const ESCAPE_SELECTOR_CHARS_REGEXP = /[(),":?.%\\$#*]/g;
/**
* Creates an HTML class name for use on elements
*/
const createClassName = (prop, value) => {
const val = `${value}`.replace(SPACE_REGEXP, '-');
return `rn-${prop}:${val}`;
};
/**
* Formatting improves debugging in devtools and snapshot
*/
const mapDeclarationsToClassName = (style, fn) => {
const result = mapKeyValue(style, fn).join('\n').trim();
return `\n${result}`;
};
/**
* Inject a CSS rule for a given declaration and record the availability of the
* resulting class name.
*/
let injectedClassNames = {};
const injectClassNameIfNeeded = (prop, value) => {
const className = createClassName(prop, value);
if (!injectedClassNames[className]) {
// create a valid CSS selector for a given HTML class name
const selector = className.replace(ESCAPE_SELECTOR_CHARS_REGEXP, '\\$&');
const body = generateCss({ [prop]: value });
const css = `.${selector}{${body}}`;
// this adds the rule to the buffer to be injected into the document
injector.addRule(className, css);
injectedClassNames[className] = true;
}
return className;
};
/**
* Converts a React Native style object to HTML class names
*/
let resolvedPropsCache = {};
const registerStyle = (id, flatStyle) => {
const style = createReactDOMStyle(flatStyle);
const className = mapDeclarationsToClassName(style, (prop, value) => {
if (value != null) {
return injectClassNameIfNeeded(prop, value);
}
});
const key = `${prefix}-${id}`;
resolvedPropsCache[key] = { className };
return id;
};
/**
* Resolves a React Native style object to DOM props
*/
const resolveProps = (reactNativeStyle) => {
const flatStyle = flattenStyle(reactNativeStyle);
const domStyle = createReactDOMStyle(flatStyle);
const style = {};
const className = mapDeclarationsToClassName(domStyle, (prop, value) => {
if (value != null) {
const singleClassName = createClassName(prop, value);
if (injectedClassNames[singleClassName]) {
return singleClassName;
} else {
// 4x slower render
style[prop] = value;
}
}
});
const props = {
className,
style: prefixInlineStyles(style)
};
/*
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
console.groupCollapsed('[StyleSheet] resolving uncached styles');
console.log(
'Slow operation. Resolving style objects (uncached result). ' +
'Occurs on first render and when using styles not registered with "StyleSheet.create"'
);
console.log('source => \n', reactNativeStyle);
console.log('flatten => \n', flatStyle);
console.log('resolve => \n', props);
console.groupEnd();
}
*/
return props;
};
/**
* Caching layer over 'resolveProps'
*/
const resolvePropsIfNeeded = (key, style) => {
if (key) {
if (!resolvedPropsCache[key]) {
// slow: convert style object to props and cache
resolvedPropsCache[key] = resolveProps(style);
}
return resolvedPropsCache[key];
}
return resolveProps(style);
};
/**
* Web style registry
*/
const StyleRegistry = {
initialize(classNames) {
injectedClassNames = classNames;
/*
if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
if (global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer) {
clearInterval(global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer);
}
global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer = setInterval(() => {
const entryCount = Object.keys(resolvedPropsCache).length;
console.groupCollapsed('[StyleSheet] resolved props cache snapshot:', entryCount, 'entries');
console.log(resolvedPropsCache);
console.groupEnd();
}, 30000);
}
*/
},
reset() {
injectedClassNames = {};
resolvedPropsCache = {};
injector.reset();
},
register(style) {
const id = ReactNativePropRegistry.register(style);
return registerStyle(id, style);
},
resolve(reactNativeStyle) {
if (!reactNativeStyle) {
return undefined;
}
// fast and cachable
if (typeof reactNativeStyle === 'number') {
const key = `${prefix}${reactNativeStyle}`;
return resolvePropsIfNeeded(key, reactNativeStyle);
}
// convert a RN style object to DOM props
if (!Array.isArray(reactNativeStyle)) {
return resolveProps(reactNativeStyle);
}
// flatten the array
// [ 1, [ 2, 3 ], { prop: value }, 4, 5 ] => [ 1, 2, 3, { prop: value }, 4, 5 ];
const flatArray = flattenArray(reactNativeStyle);
let isArrayOfNumbers = true;
for (let i = 0; i < flatArray.length; i++) {
if (typeof flatArray[i] !== 'number') {
isArrayOfNumbers = false;
break;
}
}
// TODO: determine when/if to cache unregistered styles. This produces 2x
// faster benchmark results for unregistered styles. However, the cache
// could be filled with props that are never used again.
//
// let hasValidKey = true;
// let key = flatArray.reduce((keyParts, element) => {
// if (typeof element === 'number') {
// keyParts.push(element);
// } else {
// if (element.transform) {
// hasValidKey = false;
// } else {
// const objectAsKey = Object.keys(element).map((prop) => `${prop}:${element[prop]}`).join(';');
// if (objectAsKey !== '') {
// keyParts.push(objectAsKey);
// }
// }
// }
// return keyParts;
// }, [ prefix ]).join('-');
// if (!hasValidKey) { key = null; }
// cache resolved props when all styles are registered
const key = isArrayOfNumbers ? `${prefix}-${flatArray.join('-')}` : null;
return resolvePropsIfNeeded(key, flatArray);
}
};
module.exports = StyleRegistry;

View File

@@ -0,0 +1,26 @@
import normalizeColor from 'normalize-css-color';
import normalizeValue from './normalizeValue';
const defaultOffset = { height: 0, width: 0 };
const applyOpacity = (color, opacity = 1) => {
const nullableColor = normalizeColor(color);
const colorInt = nullableColor === null ? 0x00000000 : nullableColor;
const { r, g, b, a } = normalizeColor.rgba(colorInt);
const alpha = a.toFixed(2);
return `rgba(${r},${g},${b},${alpha * opacity})`;
};
// TODO: add inset and spread support
const resolveBoxShadow = (resolvedStyle, style) => {
const { height, width } = style.shadowOffset || defaultOffset;
const offsetX = normalizeValue(null, width);
const offsetY = normalizeValue(null, height);
const blurRadius = normalizeValue(null, style.shadowRadius || 0);
const color = applyOpacity(style.shadowColor, style.shadowOpacity);
const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
resolvedStyle.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow;
};
module.exports = resolveBoxShadow;

View File

@@ -0,0 +1,15 @@
import normalizeValue from './normalizeValue';
const defaultOffset = { height: 0, width: 0 };
const resolveTextShadow = (resolvedStyle, style) => {
const { height, width } = style.textShadowOffset || defaultOffset;
const offsetX = normalizeValue(null, width);
const offsetY = normalizeValue(null, height);
const blurRadius = normalizeValue(null, style.textShadowRadius || 0);
const color = style.textShadowColor || 'currentcolor';
resolvedStyle.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
};
module.exports = resolveTextShadow;

View File

@@ -1,7 +1,7 @@
import normalizeValue from './normalizeValue';
// { scale: 2 } => 'scale(2)'
// { translateX: 20 } => 'translateX(20px)'
// { translateX: 20 } => 'translateX(20px)'
const mapTransform = (transform) => {
const type = Object.keys(transform)[0];
const value = normalizeValue(type, transform[type]);
@@ -14,16 +14,14 @@ const convertTransformMatrix = (transformMatrix) => {
return `matrix3d(${matrix})`;
};
const processTransform = (style) => {
if (style) {
if (style.transform && Array.isArray(style.transform)) {
style.transform = style.transform.map(mapTransform).join(' ');
} else if (style.transformMatrix) {
style.transform = convertTransformMatrix(style.transformMatrix);
style.transformMatrix = null;
}
const resolveTransform = (resolvedStyle, style) => {
if (Array.isArray(style.transform)) {
const transform = style.transform.map(mapTransform).join(' ');
resolvedStyle.transform = transform;
} else if (style.transformMatrix) {
const transform = convertTransformMatrix(style.transformMatrix);
resolvedStyle.transform = transform;
}
return style;
};
module.exports = processTransform;
module.exports = resolveTransform;

View File

@@ -24,7 +24,7 @@ describe('apis/UIManager', () => {
});
describe('measure', () => {
test('provides correct layout to callback', () => {
test('provides correct layout to callback', (done) => {
const node = createNode({ height: '5000px', left: '100px', position: 'relative', top: '100px', width: '5000px' });
document.body.appendChild(node);
@@ -37,7 +37,14 @@ describe('apis/UIManager', () => {
expect(height).toEqual(5000);
expect(pageX).toEqual(100);
expect(pageY).toEqual(100);
done();
document.body.removeChild(node);
});
});
test('provides correct layout to callback', (done) => {
const node = createNode({ height: '5000px', left: '100px', position: 'relative', top: '100px', width: '5000px' });
document.body.appendChild(node);
// test values account for scroll position
window.scrollTo(200, 200);
@@ -51,14 +58,14 @@ describe('apis/UIManager', () => {
expect(height).toEqual(5000);
expect(pageX).toEqual(-100);
expect(pageY).toEqual(-100);
done();
document.body.removeChild(node);
});
document.body.removeChild(node);
});
});
describe('measureLayout', () => {
test('provides correct layout to onSuccess callback', () => {
test('provides correct layout to onSuccess callback', (done) => {
const node = createNode({ height: '10px', width: '10px' });
const middle = createNode({ padding: '20px' });
const context = createNode({ padding: '20px' });
@@ -78,14 +85,14 @@ describe('apis/UIManager', () => {
expect(y).toEqual(40);
expect(width).toEqual(10);
expect(height).toEqual(10);
done();
document.body.removeChild(context);
});
document.body.removeChild(context);
});
});
describe('measureInWindow', () => {
test('provides correct layout to callback', () => {
test('provides correct layout to callback', (done) => {
const node = createNode({ height: '10px', width: '10px' });
const middle = createNode({ padding: '20px' });
const context = createNode({ padding: '20px' });
@@ -105,9 +112,9 @@ describe('apis/UIManager', () => {
expect(y).toEqual(40);
expect(width).toEqual(10);
expect(height).toEqual(10);
done();
document.body.removeChild(context);
});
document.body.removeChild(context);
});
});
@@ -119,17 +126,16 @@ describe('apis/UIManager', () => {
}
};
test('add new className to existing className', () => {
test('supports className alias for class', () => {
const node = createNode();
node.className = 'existing';
const props = { className: 'extra' };
UIManager.updateView(node, props, componentStub);
expect(node.getAttribute('class')).toEqual('existing extra');
expect(node.getAttribute('class')).toEqual('extra');
});
test('adds correct DOM styles to existing style', () => {
const node = createNode({ color: 'red' });
const props = { style: { marginVertical: 0, opacity: 0 } };
const props = { style: { marginTop: 0, marginBottom: 0, opacity: 0 } };
UIManager.updateView(node, props, componentStub);
expect(node.getAttribute('style')).toEqual('color: red; margin-top: 0px; margin-bottom: 0px; opacity: 0;');
});

View File

@@ -1,13 +1,15 @@
import createReactStyleObject from '../StyleSheet/createReactStyleObject';
import CSSPropertyOperations from 'react/lib/CSSPropertyOperations';
import asap from 'asap';
import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations';
const _measureLayout = (node, relativeToNativeNode, callback) => {
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);
asap(() => {
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);
});
};
const UIManager = {
@@ -35,23 +37,19 @@ const UIManager = {
updateView(node, props, component /* only needed to surpress React errors in development */) {
for (const prop in props) {
const value = props[prop];
if (!Object.prototype.hasOwnProperty.call(props, prop)) {
continue;
}
const value = props[prop];
switch (prop) {
case 'style':
// convert styles to DOM-styles
CSSPropertyOperations.setValueForStyles(
node,
createReactStyleObject(value),
component._reactInternalInstance
);
case 'style': {
CSSPropertyOperations.setValueForStyles(node, value, component._reactInternalInstance);
break;
}
case 'class':
case 'className': {
const nativeProp = 'class';
// prevent class names managed by React Native from being replaced
const className = `${node.getAttribute(nativeProp)} ${value}`;
node.setAttribute(nativeProp, className);
node.setAttribute('class', value);
break;
}
case 'text':

View File

@@ -0,0 +1,226 @@
exports[`components/ActivityIndicator default render 1`] = `
<div
aria-valuemax="1"
aria-valuemin="0"
className="
rn-alignItems:center
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-justifyContent:center
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
role="progressbar"
style={Object {}}>
<div
className="
rn-alignItems:stretch
rn-animationDuration:0.75s
rn-animationIterationCount:infinite
rn-animationName:rn-ActivityIndicator-animation
rn-animationTimingFunction:linear
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-height:20px
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none
rn-width:20px"
style={Object {}}>
<svg
height="100%"
viewBox="0 0 32 32"
width="100%">
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={
Object {
"opacity": 0.2,
"stroke": "#1976D2",
}
} />
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={
Object {
"stroke": "#1976D2",
"strokeDasharray": 80,
"strokeDashoffset": 60,
}
} />
</svg>
</div>
</div>
`;
exports[`components/ActivityIndicator other render 1`] = `
<div
aria-valuemax="1"
aria-valuemin="0"
className="
rn-alignItems:center
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-justifyContent:center
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
role="progressbar"
style={Object {}}>
<div
className="
rn-alignItems:stretch
rn-animationDuration:0.75s
rn-animationIterationCount:infinite
rn-animationName:rn-ActivityIndicator-animation
rn-animationPlayState:paused
rn-animationTimingFunction:linear
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-height:36px
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none
rn-width:36px"
style={Object {}}>
<svg
height="100%"
viewBox="0 0 32 32"
width="100%">
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={
Object {
"opacity": 0.2,
"stroke": "#1976D2",
}
} />
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={
Object {
"stroke": "#1976D2",
"strokeDasharray": 80,
"strokeDashoffset": 60,
}
} />
</svg>
</div>
</div>
`;

View File

@@ -1,5 +1,17 @@
/* eslint-env jasmine, jest */
import ActivityIndicator from '..';
import React from 'react';
import renderer from 'react-test-renderer';
describe('components/ActivityIndicator', () => {
test.skip('NO TEST COVERAGE', () => {});
test('default render', () => {
const component = renderer.create(<ActivityIndicator />);
expect(component.toJSON()).toMatchSnapshot();
});
test('other render', () => {
const component = renderer.create(<ActivityIndicator animating={false} hidesWhenStopped={false} size='large' />);
expect(component.toJSON()).toMatchSnapshot();
});
});

View File

@@ -1,12 +1,8 @@
import Animated from '../../apis/Animated';
import applyNativeMethods from '../../modules/applyNativeMethods';
import Easing from 'animated/lib/Easing';
import StyleSheet from '../../apis/StyleSheet';
import View from '../View';
import React, { Component, PropTypes } from 'react';
const rotationInterpolation = { inputRange: [ 0, 1 ], outputRange: [ '0deg', '360deg' ] };
class ActivityIndicator extends Component {
static displayName = 'ActivityIndicator';
@@ -25,21 +21,6 @@ class ActivityIndicator extends Component {
size: 'small'
};
constructor(props) {
super(props);
this.state = {
animation: new Animated.Value(0)
};
}
componentDidMount() {
this._manageAnimation();
}
componentDidUpdate() {
this._manageAnimation();
}
render() {
const {
animating,
@@ -50,8 +31,6 @@ class ActivityIndicator extends Component {
...other
} = this.props;
const { animation } = this.state;
const svg = (
<svg height='100%' viewBox='0 0 32 32' width='100%'>
<circle
@@ -88,47 +67,21 @@ class ActivityIndicator extends Component {
style={[
styles.container,
style,
size && { height: size, width: size }
typeof size === 'number' && { height: size, width: size }
]}
>
<Animated.View
<View
children={svg}
style={[
indicatorStyles[size],
hidesWhenStopped && !animating && styles.hidesWhenStopped,
{
transform: [
{ rotate: animation.interpolate(rotationInterpolation) }
]
}
indicatorSizes[size],
styles.animation,
!animating && styles.animationPause,
!animating && hidesWhenStopped && styles.hidesWhenStopped
]}
/>
</View>
);
}
_manageAnimation() {
const { animation } = this.state;
const cycleAnimation = () => {
animation.setValue(0);
Animated.timing(animation, {
duration: 750,
easing: Easing.inOut(Easing.linear),
toValue: 1
}).start((event) => {
if (event.finished) {
cycleAnimation();
}
});
};
if (this.props.animating) {
cycleAnimation();
} else {
animation.stopAnimation();
}
}
}
const styles = StyleSheet.create({
@@ -138,10 +91,19 @@ const styles = StyleSheet.create({
},
hidesWhenStopped: {
visibility: 'hidden'
},
animation: {
animationDuration: '0.75s',
animationName: 'rn-ActivityIndicator-animation',
animationTimingFunction: 'linear',
animationIterationCount: 'infinite'
},
animationPause: {
animationPlayState: 'paused'
}
});
const indicatorStyles = StyleSheet.create({
const indicatorSizes = StyleSheet.create({
small: {
width: 20,
height: 20

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
/* global window */
import applyNativeMethods from '../../modules/applyNativeMethods';
import ImageResizeMode from './ImageResizeMode';
import ImageLoader from '../../modules/ImageLoader';
import ImageStylePropTypes from './ImageStylePropTypes';
import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame';
import StyleSheet from '../../apis/StyleSheet';
@@ -8,6 +9,8 @@ import StyleSheetPropType from '../../propTypes/StyleSheetPropType';
import View from '../View';
import React, { Component, PropTypes } from 'react';
const emptyObject = {};
const STATUS_ERRORED = 'ERRORED';
const STATUS_LOADED = 'LOADED';
const STATUS_LOADING = 'LOADING';
@@ -52,14 +55,22 @@ class Image extends Component {
};
static defaultProps = {
style: {}
style: emptyObject
};
static getSize(uri, success, failure) {
ImageLoader.getSize(uri, success, failure);
}
static prefetch(uri) {
return ImageLoader.prefetch(uri);
}
static resizeMode = ImageResizeMode;
constructor(props, context) {
super(props, context);
this.state = { isLoaded: false };
this.state = { shouldDisplaySource: false };
const uri = resolveAssetSource(props.source);
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE;
this._isMounted = false;
@@ -73,7 +84,7 @@ class Image extends Component {
}
componentDidUpdate() {
if (this._imageState === STATUS_PENDING && !this.image) {
if (this._imageState === STATUS_PENDING) {
this._createImageLoader();
}
}
@@ -91,7 +102,7 @@ class Image extends Component {
}
render() {
const { isLoaded } = this.state;
const { shouldDisplaySource } = this.state;
const {
accessibilityLabel,
accessible,
@@ -101,13 +112,17 @@ class Image extends Component {
source,
testID,
/* eslint-disable */
onError,
onLoad,
onLoadEnd,
onLoadStart,
resizeMode,
/* eslint-enable */
...other
} = this.props;
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source);
const imageSizeStyle = resolveAssetDimensions(!isLoaded ? defaultSource : source);
const displayImage = resolveAssetSource(shouldDisplaySource ? source : defaultSource);
const imageSizeStyle = resolveAssetDimensions(shouldDisplaySource ? source : defaultSource);
const backgroundImage = displayImage ? `url("${displayImage}")` : null;
const originalStyle = StyleSheet.flatten(this.props.style);
const finalResizeMode = resizeMode || originalStyle.resizeMode || ImageResizeMode.cover;
@@ -116,8 +131,8 @@ class Image extends Component {
styles.initial,
imageSizeStyle,
originalStyle,
backgroundImage && { backgroundImage },
resizeModeStyles[finalResizeMode]
resizeModeStyles[finalResizeMode],
backgroundImage && { backgroundImage }
]);
// View doesn't support 'resizeMode' as a style
delete style.resizeMode;
@@ -137,28 +152,21 @@ class Image extends Component {
}
_createImageLoader() {
const uri = resolveAssetSource(this.props.source);
this._destroyImageLoader();
this.image = new window.Image();
this.image.onerror = this._onError;
this.image.onload = this._onLoad;
this.image.src = uri;
const uri = resolveAssetSource(this.props.source);
this._imageRequestId = ImageLoader.load(uri, this._onLoad, this._onError);
this._onLoadStart();
}
_destroyImageLoader() {
if (this.image) {
this.image.onerror = null;
this.image.onload = null;
this.image = null;
if (this._imageRequestId) {
ImageLoader.abort(this._imageRequestId);
this._imageRequestId = null;
}
}
_onError = () => {
const { onError, source } = this.props;
this._destroyImageLoader();
this._onLoadEnd();
this._updateImageState(STATUS_ERRORED);
if (onError) {
onError({
@@ -167,13 +175,13 @@ class Image extends Component {
}
});
}
this._onLoadEnd();
}
_onLoad = (e) => {
const { onLoad } = this.props;
const event = { nativeEvent: e };
this._destroyImageLoader();
this._updateImageState(STATUS_LOADED);
if (onLoad) { onLoad(event); }
this._onLoadEnd();
@@ -192,11 +200,12 @@ class Image extends Component {
_updateImageState(status) {
this._imageState = status;
const isLoaded = this._imageState === STATUS_LOADED;
if (isLoaded !== this.state.isLoaded) {
const shouldDisplaySource = this._imageState === STATUS_LOADED || this._imageState === STATUS_LOADING;
// only triggers a re-render when the image is loading (to support PJEG), loaded, or failed
if (shouldDisplaySource !== this.state.shouldDisplaySource) {
requestAnimationFrame(() => {
if (this._isMounted) {
this.setState({ isLoaded });
this.setState({ shouldDisplaySource });
}
});
}

View File

@@ -222,6 +222,10 @@ class ListViewDataSource {
return this._cachedRowCount;
}
getRowAndSectionCount(): number {
return (this._cachedRowCount + this.sectionIdentities.length);
}
/**
* Returns if the row is dirtied and needs to be rerendered
*/

View File

@@ -3,7 +3,8 @@ import ListViewDataSource from './ListViewDataSource';
import ListViewPropTypes from './ListViewPropTypes';
import ScrollView from '../ScrollView';
import StaticRenderer from '../StaticRenderer';
import React, { Component, isEmpty, merge } from 'react';
import React, { Component } from 'react';
import isEmpty from 'fbjs/lib/isEmpty';
import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame';
const DEFAULT_PAGE_SIZE = 1;
@@ -59,6 +60,20 @@ class ListView extends Component {
});
}
componentWillReceiveProps(nextProps: Object) {
if (this.props.dataSource !== nextProps.dataSource || this.props.initialListSize !== nextProps.initialListSize) {
this.setState((state, props) => {
this._prevRenderedRowsCount = 0;
return {
curRenderedRowsCount: Math.min(
Math.max(state.curRenderedRowsCount, props.initialListSize),
props.enableEmptySections ? props.dataSource.getRowAndSectionCount() : props.dataSource.getRowCount()
)
};
}, () => this._renderMoreRowsIfNeeded());
}
}
componentDidUpdate() {
requestAnimationFrame(() => {
this._measureAndUpdateScrollProps();
@@ -231,7 +246,7 @@ class ListView extends Component {
}
if (updatedFrames) {
updatedFrames.forEach((newFrame) => {
this._childFrames[newFrame.index] = merge(newFrame);
this._childFrames[newFrame.index] = Object.assign({}, newFrame);
});
}
const isVertical = !this.props.horizontal;

View File

@@ -1,13 +1,9 @@
import Animated from '../../apis/Animated';
import applyNativeMethods from '../../modules/applyNativeMethods';
import ColorPropType from '../../propTypes/ColorPropType';
import StyleSheet from '../../apis/StyleSheet';
import View from '../View';
import React, { Component, PropTypes } from 'react';
const indeterminateWidth = '25%';
const translateInterpolation = { inputRange: [ 0, 1 ], outputRange: [ '-100%', '400%' ] };
class ProgressBar extends Component {
static displayName = 'ProgressBar';
@@ -26,19 +22,12 @@ class ProgressBar extends Component {
trackColor: 'transparent'
};
constructor(props) {
super(props);
this.state = {
animationTranslate: new Animated.Value(0)
};
}
componentDidMount() {
this._manageAnimation();
this._updateProgressWidth();
}
componentDidUpdate() {
this._manageAnimation();
this._updateProgressWidth();
}
render() {
@@ -51,9 +40,7 @@ class ProgressBar extends Component {
...other
} = this.props;
const { animationTranslate } = this.state;
const percentageProgress = indeterminate ? 50 : progress * 100;
const percentageProgress = progress * 100;
return (
<View {...other}
@@ -67,42 +54,29 @@ class ProgressBar extends Component {
{ backgroundColor: trackColor }
]}
>
<Animated.View style={[
styles.progress,
{ backgroundColor: color },
indeterminate ? {
transform: [
{ translateX: animationTranslate.interpolate(translateInterpolation) }
],
width: indeterminateWidth
} : {
width: `${percentageProgress}%`
}
]} />
<View
ref={this._setProgressRef}
style={[
styles.progress,
indeterminate && styles.animation,
{ backgroundColor: color }
]}
/>
</View>
);
}
_manageAnimation() {
const { animationTranslate } = this.state;
_setProgressRef = (component) => {
this._progressRef = component;
}
const cycleAnimation = (animation) => {
animation.setValue(0);
Animated.timing(animation, {
duration: 1000,
toValue: 1
}).start((event) => {
if (event.finished) {
cycleAnimation(animation);
}
});
};
if (this.props.indeterminate) {
cycleAnimation(animationTranslate);
} else {
animationTranslate.stopAnimation();
}
_updateProgressWidth = () => {
const { indeterminate, progress } = this.props;
const percentageProgress = indeterminate ? 50 : progress * 100;
const width = indeterminate ? '25%' : `${percentageProgress}%`;
this._progressRef.setNativeProps({
style: { width }
});
}
}
@@ -114,6 +88,12 @@ const styles = StyleSheet.create({
},
progress: {
height: '100%'
},
animation: {
animationDuration: '1s',
animationName: 'rn-ProgressBar-animation',
animationTimingFunction: 'linear',
animationIterationCount: 'infinite'
}
});

View File

@@ -17,6 +17,8 @@ import View from '../View';
import ViewStylePropTypes from '../View/ViewStylePropTypes';
import React, { Component, PropTypes } from 'react';
const emptyObject = {};
/* eslint-disable react/prefer-es6-class */
const ScrollView = React.createClass({
propTypes: {
@@ -79,7 +81,7 @@ const ScrollView = React.createClass({
if (typeof y === 'number') {
console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, animated: true})` instead.');
} else {
({ x, y, animated } = y || {});
({ x, y, animated } = y || emptyObject);
}
this.getScrollResponder().scrollResponderScrollTo({ x: x || 0, y: y || 0, animated: animated !== false });

View File

@@ -7,6 +7,7 @@ import UIManager from '../../apis/UIManager';
import View from '../View';
import React, { Component, PropTypes } from 'react';
const emptyObject = {};
const thumbDefaultBoxShadow = '0px 1px 3px rgba(0,0,0,0.5)';
const thumbFocusedBoxShadow = `${thumbDefaultBoxShadow}, 0 0 0 10px rgba(0,0,0,0.1)`;
@@ -28,7 +29,7 @@ class Switch extends Component {
activeThumbColor: '#009688',
activeTrackColor: '#A3D3CF',
disabled: false,
style: {},
style: emptyObject,
thumbColor: '#FAFAFA',
trackColor: '#939393',
value: false
@@ -90,7 +91,6 @@ class Switch extends Component {
{
backgroundColor: thumbCurrentColor,
height: thumbHeight,
transform: [ { translateX: value ? '100%' : '0%' } ],
width: thumbWidth
},
disabled && styles.disabledThumb
@@ -110,7 +110,16 @@ class Switch extends Component {
return (
<View {...other} style={rootStyle}>
<View style={trackStyle} />
<View ref={this._setThumbRef} style={thumbStyle} />
<View
ref={this._setThumbRef}
style={[
thumbStyle,
value && styles.thumbOn,
{
marginLeft: value ? multiplyStyleLengthValue(thumbWidth, -1) : 0
}
]}
/>
{nativeControl}
</View>
);
@@ -151,7 +160,7 @@ const styles = StyleSheet.create({
...StyleSheet.absoluteFillObject,
height: '70%',
margin: 'auto',
transition: '0.1s',
transitionDuration: '0.1s',
width: '90%'
},
disabledTrack: {
@@ -161,7 +170,14 @@ const styles = StyleSheet.create({
alignSelf: 'flex-start',
borderRadius: '100%',
boxShadow: thumbDefaultBoxShadow,
transition: '0.1s'
left: '0%',
transform: [
{ translateZ: 0 }
],
transitionDuration: '0.1s'
},
thumbOn: {
left: '100%'
},
disabledThumb: {
backgroundColor: '#BDBDBD'

View File

@@ -1,113 +1,104 @@
exports[`components/Text prop "children" 1`] = `
<span
className=""
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"color": "inherit",
"display": "inline",
"font": "inherit",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"textDecoration": "none",
"wordWrap": "break-word",
}
}>
className="
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-color:inherit
rn-display:inline
rn-font:inherit
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-textDecoration:none
rn-whiteSpace:pre-wrap
rn-wordWrap:break-word"
dir="auto">
children
</span>
`;
exports[`components/Text prop "onPress" 1`] = `
<span
className=""
className="
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-color:inherit
rn-cursor:pointer
rn-display:inline
rn-font:inherit
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-textDecoration:none
rn-whiteSpace:pre-wrap
rn-wordWrap:break-word"
dir="auto"
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"color": "inherit",
"cursor": "pointer",
"display": "inline",
"font": "inherit",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"textDecoration": "none",
"wordWrap": "break-word",
}
}
style={Object {}}
tabIndex={0} />
`;
exports[`components/Text prop "selectable" 1`] = `
<span
className=""
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"color": "inherit",
"display": "inline",
"font": "inherit",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"textDecoration": "none",
"wordWrap": "break-word",
}
} />
className="
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-color:inherit
rn-display:inline
rn-font:inherit
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-textDecoration:none
rn-whiteSpace:pre-wrap
rn-wordWrap:break-word"
dir="auto" />
`;
exports[`components/Text prop "selectable" 2`] = `
<span
className=""
style={
Object {
"MozUserSelect": "none",
"WebkitUserSelect": "none",
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"color": "inherit",
"display": "inline",
"font": "inherit",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"msUserSelect": "none",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"textDecoration": "none",
"userSelect": "none",
"wordWrap": "break-word",
}
} />
className="
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-color:inherit
rn-display:inline
rn-font:inherit
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-textDecoration:none
rn-userSelect:none
rn-whiteSpace:pre-wrap
rn-wordWrap:break-word"
dir="auto"
style={Object {}} />
`;

View File

@@ -40,25 +40,26 @@ class Text extends Component {
onLayout,
suppressHighlighting,
/* eslint-enable */
...other
...otherProps
} = this.props;
if (onPress) {
other.onClick = onPress;
other.onKeyDown = this._createEnterHandler(onPress);
other.tabIndex = 0;
otherProps.onClick = onPress;
otherProps.onKeyDown = this._createEnterHandler(onPress);
otherProps.tabIndex = 0;
}
return createDOMElement('span', {
...other,
style: [
styles.initial,
style,
!selectable && styles.notSelectable,
numberOfLines === 1 && styles.singleLineStyle,
onPress && styles.pressable
]
});
otherProps.style = [
styles.initial,
style,
!selectable && styles.notSelectable,
numberOfLines === 1 && styles.singleLineStyle,
onPress && styles.pressable
];
// allow browsers to automatically infer the language writing direction
otherProps.dir = 'auto';
return createDOMElement('span', otherProps);
}
_createEnterHandler(fn) {
@@ -79,6 +80,7 @@ const styles = StyleSheet.create({
margin: 0,
padding: 0,
textDecorationLine: 'none',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word'
},
notSelectable: {

View File

@@ -169,8 +169,6 @@ describe('components/TextInput', () => {
}
});
test('prop "onLayout"');
test('prop "onSelectionChange"', (done) => {
const input = findNativeInput(mount(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />));
input.simulate('select', { target: { selectionStart: 0, selectionEnd: 3 } });
@@ -181,6 +179,16 @@ describe('components/TextInput', () => {
}
});
describe('prop "onSubmitEditing"', () => {
test('single-line input', (done) => {
const input = findNativeInput(mount(<TextInput defaultValue='12345' onSubmitEditing={onSubmitEditing} />));
input.simulate('keyPress', { which: 13 });
function onSubmitEditing(e) {
done();
}
});
});
test('prop "secureTextEntry"', () => {
let input = findNativeInput(shallow(<TextInput secureTextEntry />));
expect(input.prop('type')).toEqual('password');

View File

@@ -1,15 +1,17 @@
import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import NativeMethodsMixin from '../../modules/NativeMethodsMixin';
import createDOMElement from '../../modules/createDOMElement';
import findNodeHandle from '../../modules/findNodeHandle';
import StyleSheet from '../../apis/StyleSheet';
import Text from '../Text';
import TextareaAutosize from 'react-textarea-autosize';
import TextInputState from './TextInputState';
import UIManager from '../../apis/UIManager';
import View from '../View';
import { Component, PropTypes } from 'react';
const emptyObject = {};
/**
* React Native events differ from W3C events.
*/
@@ -72,6 +74,7 @@ class TextInput extends Component {
onFocus: PropTypes.func,
onKeyPress: PropTypes.func,
onSelectionChange: PropTypes.func,
onSubmitEditing: PropTypes.func,
placeholder: PropTypes.string,
placeholderTextColor: PropTypes.string,
secureTextEntry: PropTypes.bool,
@@ -93,7 +96,7 @@ class TextInput extends Component {
multiline: false,
numberOfLines: 2,
secureTextEntry: false,
style: {}
style: emptyObject
};
blur() {
@@ -113,7 +116,7 @@ class TextInput extends Component {
}
setNativeProps(props) {
UIManager.updateView(this._node, props, this);
NativeMethodsMixin.setNativeProps.call(this, props);
}
componentDidMount() {
@@ -152,7 +155,7 @@ class TextInput extends Component {
selectionColor,
selectTextOnFocus,
/* eslint-enable */
...other
...otherProps
} = this.props;
let type;
@@ -185,9 +188,9 @@ class TextInput extends Component {
const component = multiline ? TextareaAutosize : 'input';
const props = {
...other,
Object.assign(otherProps, {
autoCorrect: autoCorrect ? 'on' : 'off',
dir: 'auto',
onBlur: normalizeEventHandler(this._handleBlur),
onChange: normalizeEventHandler(this._handleChange),
onFocus: normalizeEventHandler(this._handleFocus),
@@ -199,16 +202,16 @@ class TextInput extends Component {
styles.initial,
style
]
};
});
if (multiline) {
props.maxRows = maxNumberOfLines || numberOfLines;
props.minRows = numberOfLines;
otherProps.maxRows = maxNumberOfLines || numberOfLines;
otherProps.minRows = numberOfLines;
} else {
props.type = type;
otherProps.type = type;
}
return createDOMElement(component, props);
return createDOMElement(component, otherProps);
}
_handleBlur = (e) => {
@@ -243,7 +246,7 @@ class TextInput extends Component {
}
_handleSelectionChange = (e) => {
const { onSelectionChange, selection = {} } = this.props;
const { onSelectionChange, selection = emptyObject } = this.props;
if (onSelectionChange) {
try {
const node = e.target;

View File

@@ -6,7 +6,7 @@
'use strict';
var PooledClass = require('react/lib/PooledClass');
var PooledClass = require('react-dom/lib/PooledClass');
var twoArgumentPooler = PooledClass.twoArgumentPooler;

View File

@@ -6,7 +6,7 @@
'use strict';
var PooledClass = require('react/lib/PooledClass');
var PooledClass = require('react-dom/lib/PooledClass');
var twoArgumentPooler = PooledClass.twoArgumentPooler;

View File

@@ -14,7 +14,6 @@
/* @edit start */
const BoundingDimensions = require('./BoundingDimensions');
const normalizeColor = require('../../modules/normalizeColor');
const Position = require('./Position');
const React = require('react');
const TouchEventUtils = require('fbjs/lib/TouchEventUtils');
@@ -404,6 +403,13 @@ var TouchableMixin = {
*/
touchableHandleResponderRelease: function(e) {
this._receiveSignal(Signals.RESPONDER_RELEASE, e);
// Browsers fire mouse events after touch events. This causes the
// 'onResponderRelease' handler to be called twice for Touchables.
// Auto-fix this issue by calling 'preventDefault' to cancel the mouse
// events.
if (e.cancelable && !e.isDefaultPrevented()) {
e.preventDefault();
}
},
/**
@@ -566,13 +572,13 @@ var TouchableMixin = {
* @sideeffects
* @private
*/
_remeasureMetricsOnActivation: function(e) {
/* @edit begin */
UIManager.measure(
e.nativeEvent.target,
this._handleQueryLayout
);
/* @edit end */
_remeasureMetricsOnActivation: function() {
const tag = this.state.touchable.responderID;
if (tag == null) {
return;
}
UIManager.measure(tag, this._handleQueryLayout);
},
_handleQueryLayout: function(l, t, w, h, globalX, globalY) {
@@ -684,7 +690,7 @@ var TouchableMixin = {
}
if (!IsActive[curState] && IsActive[nextState]) {
this._remeasureMetricsOnActivation(e);
this._remeasureMetricsOnActivation();
}
if (IsPressingIn[curState] && signal === Signals.LONG_PRESS_DETECTED) {
@@ -692,16 +698,9 @@ var TouchableMixin = {
}
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);
}
this._startHighlight(e);
} else if (!newIsHighlight && curIsHighlight) {
this._endHighlight(e);
}
if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) {
@@ -714,49 +713,40 @@ var TouchableMixin = {
var shouldInvokePress = !IsLongPressingIn[curState] || pressIsLongButStillCallOnPress;
if (shouldInvokePress && this.touchableHandlePress) {
if (!newIsHighlight && !curIsHighlight) {
// we never highlighted because of delay, but we should highlight now
this._startHighlight(e);
this._endHighlight(e);
}
this.touchableHandlePress(e);
}
}
this.touchableDelayTimeout && clearTimeout(this.touchableDelayTimeout);
this.touchableDelayTimeout = null;
}
},
_startHighlight: function(e) {
this._savePressInLocation(e);
this.touchableHandleActivePressIn && this.touchableHandleActivePressIn(e);
},
_endHighlight: function(e) {
if (this.touchableHandleActivePressOut) {
if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) {
this.pressOutDelayTimeout = setTimeout(() => {
this.touchableHandleActivePressOut(e);
}, this.touchableGetPressOutDelayMS());
} else {
this.touchableHandleActivePressOut(e);
}
}
},
};
var Touchable = {
Mixin: TouchableMixin,
TOUCH_TARGET_DEBUG: false, // Highlights all touchable targets. Toggle with Inspector.
/**
* Renders a debugging overlay to visualize touch target with hitSlop (might not work on Android).
*/
renderDebugView: ({color, hitSlop}) => {
if (!Touchable.TOUCH_TARGET_DEBUG) {
return null;
}
if (process.env.NODE_ENV === 'production') {
throw Error('Touchable.TOUCH_TARGET_DEBUG should not be enabled in prod!');
}
const debugHitSlopStyle = {};
hitSlop = hitSlop || {top: 0, bottom: 0, left: 0, right: 0};
for (const key in hitSlop) {
debugHitSlopStyle[key] = -hitSlop[key];
}
const hexColor = '#' + ('00000000' + normalizeColor(color).toString(16)).substr(-8);
return (
<View
pointerEvents="none"
style={{
position: 'absolute',
borderColor: hexColor.slice(0, -2) + '55', // More opaque
borderWidth: 1,
borderStyle: 'dashed',
backgroundColor: hexColor.slice(0, -2) + '0F', // Less opaque
...debugHitSlopStyle
}}
/>
);
}
Mixin: TouchableMixin
};
module.exports = Touchable;

View File

@@ -27,13 +27,12 @@ var ViewStylePropTypes = require('../View/ViewStylePropTypes');
var ensureComponentIsNative = require('./ensureComponentIsNative');
var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
var keyOf = require('fbjs/lib/keyOf');
type Event = Object;
var DEFAULT_PROPS = {
accessibilityRole: 'button',
activeOpacity: 0.8,
activeOpacity: 0.85,
underlayColor: 'black'
};
@@ -55,13 +54,13 @@ var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
* <TouchableHighlight onPress={this._onPressButton}>
* <Image
* style={styles.button}
* source={require('image!myButton')}
* source={require('./myButton')}
* />
* </TouchableHighlight>
* );
* },
* ```
* > **NOTE**: TouchableHighlight supports only one child
* > **NOTE**: TouchableHighlight must have one child (not zero or more than one)
* >
* > If you wish to have several child components, wrap them in a View.
*/
@@ -268,8 +267,9 @@ var TouchableHighlight = React.createClass({
}
});
var CHILD_REF = keyOf({childRef: null});
var UNDERLAY_REF = keyOf({underlayRef: null});
var CHILD_REF = 'childRef';
var UNDERLAY_REF = 'underlayRef';
var INACTIVE_CHILD_PROPS = {
style: StyleSheet.create({x: {opacity: 1.0}}).x,
};

View File

@@ -43,7 +43,7 @@ var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
* <TouchableOpacity onPress={this._onPressButton}>
* <Image
* style={styles.button}
* source={require('image!myButton')}
* source={require('./myButton')}
* />
* </TouchableOpacity>
* );
@@ -60,12 +60,14 @@ var TouchableOpacity = React.createClass({
* active.
*/
activeOpacity: React.PropTypes.number,
focusedOpacity: React.PropTypes.number
},
getDefaultProps: function() {
return {
accessibilityRole: 'button',
activeOpacity: 0.2,
focusedOpacity: 0.7
};
},
@@ -81,10 +83,11 @@ var TouchableOpacity = React.createClass({
ensurePositiveDelayProps(nextProps);
},
setOpacityTo: function(value) {
setOpacityTo: function(value: number, duration: number) {
this.setNativeProps({
style: {
opacity: value
opacity: value,
transitionDuration: `${duration / 1000}s`
}
});
},
@@ -94,26 +97,20 @@ var TouchableOpacity = React.createClass({
* defined on your component.
*/
touchableHandleActivePressIn: function(e: Event) {
this.clearTimeout(this._hideTimeout);
this._hideTimeout = null;
this._opacityActive();
if (e.dispatchConfig.registrationName === 'onResponderGrant') {
this._opacityActive(0);
} else {
this._opacityActive(150);
}
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: Event) {
if (!this._hideTimeout) {
this._opacityInactive();
}
this._opacityInactive(250);
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandlePress: function(e: Event) {
this.clearTimeout(this._hideTimeout);
this._opacityActive();
this._hideTimeout = this.setTimeout(
this._opacityInactive,
this.props.delayPressOut || 100
);
this.props.onPress && this.props.onPress(e);
},
@@ -142,19 +139,22 @@ var TouchableOpacity = React.createClass({
return this.props.delayPressOut;
},
_opacityActive: function() {
this.setOpacityTo(this.props.activeOpacity);
_opacityActive: function(duration: number) {
this.setOpacityTo(this.props.activeOpacity, duration);
},
_opacityInactive: function() {
this.clearTimeout(this._hideTimeout);
this._hideTimeout = null;
_opacityInactive: function(duration: number) {
var childStyle = flattenStyle(this.props.style) || {};
this.setOpacityTo(
childStyle.opacity === undefined ? 1 : childStyle.opacity
childStyle.opacity === undefined ? 1 : childStyle.opacity,
duration
);
},
_opacityFocused: function() {
this.setOpacityTo(this.props.focusedOpacity);
},
_onKeyEnter(e, callback) {
var ENTER = 13
if (e.keyCode === ENTER) {
@@ -197,7 +197,8 @@ var TouchableOpacity = React.createClass({
var styles = StyleSheet.create({
root: {
cursor: 'pointer',
transition: 'opacity 0.15s',
transitionProperty: 'opacity',
transitionDuration: '0.15s',
userSelect: 'none'
},
disabled: {

View File

@@ -22,12 +22,12 @@ var StyleSheet = require('../../apis/StyleSheet');
type Event = Object;
var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
/**
* Do not use unless you have a very good reason. All the elements that
* respond to press should have a visual feedback when touched. This is
* one of the primary reason a "web" app doesn't feel "native".
* one of the primary reasons a "web" app doesn't feel "native".
*
* > **NOTE**: TouchableWithoutFeedback supports only one child
* >
@@ -144,7 +144,7 @@ const TouchableWithoutFeedback = React.createClass({
return this.props.delayPressOut || 0;
},
render: function(): ReactElement<any> {
render: function(): React.Element<any> {
// Note(avik): remove dynamic typecast once Flow has been upgraded
const child = React.Children.only(this.props.children);
let children = child.props.children;
@@ -153,12 +153,6 @@ const TouchableWithoutFeedback = React.createClass({
'TouchableWithoutFeedback does not work well with Text children. Wrap children in a View instead. See ' +
((child._owner && child._owner.getName && child._owner.getName()) || '<unknown>')
);
if (Touchable.TOUCH_TARGET_DEBUG && child.type && child.type.displayName === 'View') {
if (!Array.isArray(children)) {
children = [children];
}
children.push(Touchable.renderDebugView({color: 'red', hitSlop: this.props.hitSlop}));
}
const style = (Touchable.TOUCH_TARGET_DEBUG && child.type && child.type.displayName === 'Text') ?
[
styles.root,

View File

@@ -1,7 +1,9 @@
import AnimationPropTypes from '../../propTypes/AnimationPropTypes';
import BorderPropTypes from '../../propTypes/BorderPropTypes';
import ColorPropType from '../../propTypes/ColorPropType';
import LayoutPropTypes from '../../propTypes/LayoutPropTypes';
import { PropTypes } from 'react';
import ShadowPropTypes from '../../propTypes/ShadowPropTypes';
import TransformPropTypes from '../../propTypes/TransformPropTypes';
const { number, oneOf, string } = PropTypes;
@@ -9,8 +11,10 @@ const autoOrHiddenOrVisible = oneOf([ 'auto', 'hidden', 'visible' ]);
const hiddenOrVisible = oneOf([ 'hidden', 'visible' ]);
module.exports = process.env.NODE_ENV !== 'production' ? {
...AnimationPropTypes,
...BorderPropTypes,
...LayoutPropTypes,
...ShadowPropTypes,
...TransformPropTypes,
backfaceVisibility: hiddenOrVisible,
backgroundColor: ColorPropType,
@@ -23,16 +27,22 @@ module.exports = process.env.NODE_ENV !== 'production' ? {
backgroundAttachment: string,
backgroundClip: string,
backgroundImage: string,
backgroundPosition: string,
backgroundOrigin: oneOf([ 'border-box', 'content-box', 'padding-box' ]),
backgroundPosition: string,
backgroundRepeat: string,
backgroundSize: string,
boxShadow: string,
cursor: string,
display: string,
outline: string,
overflowX: autoOrHiddenOrVisible,
overflowY: autoOrHiddenOrVisible,
transition: string,
perspective: PropTypes.oneOfType([ number, string ]),
perspectiveOrigin: string,
transitionDelay: string,
transitionDuration: string,
transitionProperty: string,
transitionTimingFunction: string,
userSelect: string,
visibility: hiddenOrVisible,
WebkitOverflowScrolling: oneOf([ 'auto', 'touch' ])

View File

@@ -1,525 +1,399 @@
exports[`components/View prop "children" 1`] = `
<div
className=" __style_df"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
}>
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={Object {}}>
<div
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
data-testid="1"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
style={Object {}} />
</div>
`;
exports[`components/View prop "pointerEvents" 1`] = `
<div
className=" __style_df __style_pebo"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"pointerEvents": null,
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
"pointerEvents": "box-only",
}
} />
`;
exports[`components/View prop "style" 1`] = `
<div
className=" __style_df"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={Object {}} />
`;
exports[`components/View prop "style" 2`] = `
<div
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexGrow": 1,
"WebkitFlexShrink": 1,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexGrow": 1,
"flexShrink": 1,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 1,
"msFlexPositive": 1,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/View prop "style" 3`] = `
<div
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 1,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 1,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 1,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/View prop "style" 4`] = `
<div
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexGrow": 1,
"WebkitFlexShrink": 2,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexGrow": 1,
"flexShrink": 2,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 2,
"msFlexPositive": 1,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
`;
exports[`components/View rendered element is a "div" by default 1`] = `
<div
className=" __style_df"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={Object {}} />
`;
exports[`components/View rendered element is a "span" when inside <View accessibilityRole="button" /> 1`] = `
<button
className=" __style_df"
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
role="button"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
}
style={Object {}}
type="button">
<span
className=" __style_df"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"position": "relative",
"textAlign": "inherit",
"textDecoration": "none",
}
} />
className="
rn-alignItems:stretch
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
style={Object {}} />
</button>
`;

View File

@@ -99,7 +99,7 @@ class View extends Component {
onMagicTap,
removeClippedSubviews,
/* eslint-enable */
...other
...otherProps
} = this.props;
const flattenedStyle = StyleSheet.flatten(style);
@@ -107,43 +107,30 @@ class View extends Component {
// 'View' needs to set 'flexShrink:0' only when there is no 'flex' or 'flexShrink' style provided
const needsFlexReset = !flattenedStyle || (flattenedStyle.flex == null && flattenedStyle.flexShrink == null);
const normalizedEventHandlers = eventHandlerNames.reduce((handlerProps, handlerName) => {
const component = this.context.isInAButtonView ? 'span' : 'div';
eventHandlerNames.reduce((props, handlerName) => {
const handler = this.props[handlerName];
if (typeof handler === 'function') {
handlerProps[handlerName] = this._normalizeEventForHandler(handler, handlerName);
props[handlerName] = this._normalizeEventForHandler(handler);
}
return handlerProps;
}, {});
return props;
}, otherProps);
const component = this.context.isInAButtonView ? 'span' : 'div';
const props = {
...other,
...normalizedEventHandlers,
style: [
styles.initial,
style,
needsFlexReset && styles.flexReset,
pointerEventsStyle
]
};
otherProps.style = [
styles.initial,
style,
needsFlexReset && styles.flexReset,
pointerEventsStyle
];
return createDOMElement(component, props);
return createDOMElement(component, otherProps);
}
_normalizeEventForHandler(handler, handlerName) {
// Browsers fire mouse events after touch events. This causes the
// 'onResponderRelease' handler to be called twice for Touchables.
// Auto-fix this issue by calling 'preventDefault' to cancel the mouse
// events.
const shouldCancelEvent = handlerName === 'onResponderRelease';
_normalizeEventForHandler(handler) {
return (e) => {
e.nativeEvent = normalizeNativeEvent(e.nativeEvent);
const returnValue = handler(e);
if (shouldCancelEvent && e.cancelable) {
e.preventDefault();
}
return returnValue;
return handler(e);
};
}
}

View File

@@ -1,17 +1,22 @@
import findNodeHandle from './modules/findNodeHandle';
import ReactDefaultInjection from 'react/lib/ReactDefaultInjection';
import { render, unmountComponentAtNode } from 'react/lib/ReactMount';
import ReactDefaultInjection from 'react-dom/lib/ReactDefaultInjection';
import { render, unmountComponentAtNode } from 'react-dom/lib/ReactMount';
ReactDefaultInjection.inject();
// APIs
import I18nManager from './apis/I18nManager';
import Platform from './apis/Platform';
import StyleSheet from './apis/StyleSheet';
// components
import Image from './components/Image';
import Text from './components/Text';
import TextInput from './components/TextInput';
import Touchable from './components/Touchable/Touchable';
import TouchableHighlight from './components/Touchable/TouchableHighlight';
import TouchableOpacity from './components/Touchable/TouchableOpacity';
import TouchableWithoutFeedback from './components/Touchable/TouchableWithoutFeedback';
import View from './components/View';
// modules
@@ -24,11 +29,16 @@ const ReactNativeCore = {
unmountComponentAtNode,
// APIs
I18nManager,
Platform,
StyleSheet,
// components
Image,
Text,
TextInput,
Touchable,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
View
};

View File

@@ -1,6 +1,6 @@
import findNodeHandle from './modules/findNodeHandle';
import ReactDefaultInjection from 'react/lib/ReactDefaultInjection';
import { render, unmountComponentAtNode } from 'react/lib/ReactMount';
import ReactDefaultInjection from 'react-dom/lib/ReactDefaultInjection';
import { render, unmountComponentAtNode } from 'react-dom/lib/ReactMount';
ReactDefaultInjection.inject();
@@ -9,6 +9,7 @@ import Animated from './apis/Animated';
import AppRegistry from './apis/AppRegistry';
import AppState from './apis/AppState';
import AsyncStorage from './apis/AsyncStorage';
import BackAndroid from './apis/BackAndroid';
import Clipboard from './apis/Clipboard';
import Dimensions from './apis/Dimensions';
import Easing from 'animated/lib/Easing';
@@ -59,6 +60,7 @@ const ReactNative = {
AppRegistry,
AppState,
AsyncStorage,
BackAndroid,
Clipboard,
Dimensions,
Easing,

View File

@@ -0,0 +1,48 @@
let id = 0;
const requests = {};
const ImageLoader = {
abort(requestId: number) {
let image = requests[`${requestId}`];
if (image) {
image.onerror = image.onload = image = null;
delete requests[`${requestId}`];
}
},
getSize(uri, success, failure) {
let complete = false;
const interval = setInterval(callback, 16);
const requestId = ImageLoader.load(uri, callback, callback);
function callback() {
const image = requests[`${requestId}`];
if (image) {
const { naturalHeight, naturalWidth } = image;
if (naturalHeight && naturalWidth) {
success(naturalWidth, naturalHeight);
complete = true;
}
}
if (complete) {
ImageLoader.abort(requestId);
clearInterval(interval);
}
}
},
load(uri, onLoad, onError): number {
id += 1;
const image = new window.Image();
image.onerror = onError;
image.onload = onLoad;
image.src = uri;
requests[`${id}`] = image;
return id;
},
prefetch(uri): Promise {
return new Promise((resolve, reject) => {
ImageLoader.load(uri, resolve, reject);
});
}
};
export default ImageLoader;

View File

@@ -6,32 +6,22 @@
* @flow
*/
import { Component } from 'react';
import findNodeHandle from '../findNodeHandle';
import StyleRegistry from '../../apis/StyleSheet/registry';
import UIManager from '../../apis/UIManager';
type MeasureInWindowOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
) => void
const emptyObject = {};
const REGEX_CLASSNAME_SPLIT = /\s+/;
const REGEX_STYLE_PROP = /rn-(.*):.*/;
type MeasureLayoutOnSuccessCallback = (
left: number,
top: number,
width: number,
height: number
) => void
type MeasureOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number
) => void
const classNameFilter = (className) => { return className !== ''; };
const classNameToList = (className = '') => className.split(REGEX_CLASSNAME_SPLIT).filter(classNameFilter);
const getStyleProp = (className) => {
const match = className.match(REGEX_STYLE_PROP);
if (match) {
return match[1];
}
};
const NativeMethodsMixin = {
/**
@@ -52,11 +42,8 @@ const NativeMethodsMixin = {
/**
* Determines the position and dimensions of the view
*/
measure(callback: MeasureOnSuccessCallback) {
UIManager.measure(
findNodeHandle(this),
mountSafeCallback(this, callback)
);
measure(callback) {
UIManager.measure(findNodeHandle(this), callback);
},
/**
@@ -72,52 +59,66 @@ const NativeMethodsMixin = {
* - height
*
* Note that these measurements are not available until after the rendering
* has been completed in native.
* has been completed.
*/
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
UIManager.measureInWindow(
findNodeHandle(this),
mountSafeCallback(this, callback)
);
measureInWindow(callback) {
UIManager.measureInWindow(findNodeHandle(this), callback);
},
/**
* Measures the view relative to another view (usually an ancestor)
*/
measureLayout(
relativeToNativeNode: Object,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */
) {
UIManager.measureLayout(
findNodeHandle(this),
relativeToNativeNode,
mountSafeCallback(this, onFail),
mountSafeCallback(this, onSuccess)
);
measureLayout(relativeToNativeNode, onSuccess, onFail) {
UIManager.measureLayout(findNodeHandle(this), relativeToNativeNode, onFail, onSuccess);
},
/**
* This function sends props straight to the underlying DOM node.
* This works as if all styles were set as inline styles. Since a DOM node
* may aleady be styled with class names and inline styles, we need to get
* the initial styles from the DOM node and merge them with incoming props.
*/
setNativeProps(nativeProps: Object) {
UIManager.updateView(
findNodeHandle(this),
nativeProps,
this
);
}
};
// DOM state
const node = findNodeHandle(this);
const domClassList = [ ...node.classList ];
/**
* In the future, we should cleanup callbacks by cancelling them instead of
* using this.
*/
const mountSafeCallback = (context: Component, callback: ?Function) => (...args) => {
if (!callback) {
return undefined;
// Resolved state
const resolvedProps = StyleRegistry.resolve(nativeProps.style) || emptyObject;
const resolvedClassList = classNameToList(resolvedProps.className);
// Merged state
const classList = [];
const style = { ...resolvedProps.style };
// The node has class names that we need to override.
// Only pass on a class name when the style is unchanged.
domClassList.forEach((c) => {
const prop = getStyleProp(c);
const className = resolvedProps.className;
if (!className || className.indexOf(prop) === -1) {
classList.push(c);
}
});
// The node has styles that we need to override.
// Remove any inline style that may collide with a new class name.
resolvedClassList.forEach((c) => {
const prop = getStyleProp(c);
classList.push(c);
style[prop] = null;
});
const className = `\n${classList.sort().join('\n')}`;
const props = {
...nativeProps,
className,
style
};
UIManager.updateView(node, props, this);
}
return callback.apply(context, args);
};
module.exports = NativeMethodsMixin;

View File

@@ -14,15 +14,19 @@
const emptyObject = {};
const objects = {};
const prefix = 'r';
let uniqueID = 1;
const createKey = (id) => `${prefix}-${id}`;
class ReactNativePropRegistry {
static register(object: Object): number {
let id = ++uniqueID;
let id = uniqueID++;
if (process.env.NODE_ENV !== 'production') {
Object.freeze(object);
}
objects[id] = object;
const key = createKey(id);
objects[key] = object;
return id;
}
@@ -32,8 +36,8 @@ class ReactNativePropRegistry {
// we want it to be a no-op when the value is false or null
return emptyObject;
}
const object = objects[id];
const key = createKey(id);
const object = objects[key];
if (!object) {
console.warn('Invalid style with id `' + id + '`. Skipping ...');
return emptyObject;

Some files were not shown because too many files have changed in this diff Show More