Compare commits

..

23 Commits

Author SHA1 Message Date
Nicolas Gallagher
88d13f06f8 0.0.45 2016-09-06 12:42:36 -07:00
Nicolas Gallagher
3dd94880e0 [fix] ActivtyIndicator accessibility props
Markup the 'ActivityIndicator' as an indeterminate ARIA 'progressbar'
2016-09-06 12:41:07 -07:00
Nicolas Gallagher
e1080d72d7 [fix] ScrollView style issues
* Fix contentContainer not expanding to contain all child elements
* Add momentum scrolling on iOS

Fix #197
2016-09-06 12:33:53 -07:00
Nicolas Gallagher
55849cdd0d Add display names 2016-09-06 12:33:14 -07:00
Nicolas Gallagher
0aef117733 [add] ProgressBar component
Ref #91
2016-09-01 16:49:42 -07:00
Nicolas Gallagher
977d8729f5 Fix style prop validation in development 2016-09-01 16:37:45 -07:00
Nicolas Gallagher
9222cbf4bd [add] support numeric ActivityIndicator size value 2016-09-01 16:36:49 -07:00
Nicolas Gallagher
103560fc11 Add i18n docs and update README 2016-08-31 15:19:49 -07:00
Nicolas Gallagher
3a2daf386d [change] Hide more 'input' pseudo-elements
Removes the webkit spin-buttons from numeric inputs.

Fix #194
2016-08-31 14:07:08 -07:00
Nicolas Gallagher
6640b61b3e Change appearance of ActivityIndicator
This looks more like a traditional OS-level activity indicator.

The design of the Android activity indicator hasn't worked very well for
us on Web. The main problem is that if the main thread is locked, the
indicator (even if it's a gif) will stop animating and can look really
bad. This implementation looks like an activity indicator even when it
isn't animating.
2016-08-31 13:54:00 -07:00
Nicolas Gallagher
07d1124d60 Update runtime dependencies 2016-08-26 11:30:45 -07:00
Nicolas Gallagher
c7b3a8e60b Update react-storybook dependency 2016-08-26 11:30:44 -07:00
Nicolas Gallagher
d32eec7239 Update build and test dependencies 2016-08-26 11:30:38 -07:00
Nicolas Gallagher
f8f2898095 Use Twitter JavaScript style 2016-08-26 11:29:11 -07:00
Nicolas Gallagher
201bfd2e4d [change] remove 'label' element from Switch 2016-08-22 16:32:18 -07:00
Nicolas Gallagher
496839d19a Add tests for Switch 2016-08-20 20:33:55 -07:00
Nicolas Gallagher
6fe3f1f533 Don't persist RTL across examples 2016-08-19 16:46:07 -07:00
Nicolas Gallagher
aa53d931d5 Add I18nManager example 2016-08-19 14:04:10 -07:00
Nicolas Gallagher
88b184d540 Reorganize examples 2016-08-19 14:03:55 -07:00
Nicolas Gallagher
011affb110 0.0.44 2016-08-18 16:27:39 -07:00
Nicolas Gallagher
87a4f56a89 [add] Switch component
'Switch' on Web can support custom sizes and colors. To do so,
Web-specific propTypes are introduced: `trackColor`, `thumbColor`,
`activeTrackColor`, and `activeThumbColor`.
2016-08-18 16:25:16 -07:00
Nathan Tran
2f94295739 [fix] TextInput 'keyboardType' propType 2016-08-17 15:51:55 -07:00
Nicolas Gallagher
fdf4a88251 Refactor DOM element helper 2016-08-17 15:46:10 -07:00
124 changed files with 3025 additions and 1910 deletions

215
.eslintrc
View File

@@ -1,14 +1,213 @@
{
// babel parser to support ES features
// babel parser to support ES6/7 features
"parser": "babel-eslint",
// based on https://github.com/feross/standard
"extends": [ "standard", "standard-react" ],
"parserOptions": {
"ecmaVersion": 7,
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
},
"plugins": [
"jsx-a11y",
"promise",
"react"
],
"env": {
"es6": true,
"node": true
},
"globals": {
"document": false,
"navigator": false,
"window": false
},
"rules": {
// overrides of the standard style
"space-before-function-paren": [ 2, { "anonymous": "always", "named": "never" } ],
"wrap-iife": [ 2, "outside" ],
// overrides of the standard-react style
"accessor-pairs": 2,
"array-bracket-spacing": ["error", "always"],
"arrow-parens": [2, "always"],
"arrow-spacing": [2, { "before": true, "after": true }],
"block-spacing": [2, "always"],
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"camelcase": 0,
"comma-dangle": [2, "never"],
"comma-spacing": [2, { "before": false, "after": true }],
"comma-style": [2, "last"],
"computed-property-spacing": ["error", "never"],
"constructor-super": 2,
"curly": [2, "all"],
"default-case": [2, { commentPattern: '^no default$' }],
"dot-location": [2, "property"],
"eol-last": 2,
"eqeqeq": [2, "allow-null"],
"generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [2, "^(err|error)$" ],
"indent": [2, 2, { "SwitchCase": 1 }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"keyword-spacing": [2, { "before": true, "after": true }],
"max-len": [2, 120, 4],
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
"new-parens": 2,
"no-alert": 1,
"no-array-constructor": 2,
"no-caller": 2,
"no-case-declarations": 2,
"no-class-assign": 2,
"no-cond-assign": 2,
"no-const-assign": 2,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-dupe-args": 2,
"no-dupe-class-members": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-duplicate-imports": 2,
"no-empty-character-class": 2,
"no-empty-pattern": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": [2, "functions"],
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": [2, { "allowLoop": false, "allowSwitch": false }],
"no-lone-blocks": 2,
"no-loop-func": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-multiple-empty-lines": [2, { "max": 1 }],
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-require": 2,
"no-new-symbol": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-path-concat": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-return-assign": [2, "except-parens"],
"no-script-url": 2,
"no-self-assign": 2,
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-sparse-arrays": 2,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undef-init": 2,
"no-unexpected-multiline": 2,
"no-unmodified-loop-condition": 2,
"no-unneeded-ternary": [2, { "defaultAssignment": false }],
"no-unreachable": 2,
"no-unsafe-finally": 2,
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
"no-useless-call": 2,
"no-useless-computed-key": 2,
"no-useless-concat": 2,
"no-useless-constructor": 2,
"no-useless-escape": 2,
"no-var": 2,
"no-whitespace-before-property": 2,
"no-with": 2,
"object-curly-spacing": ["error", "always"],
"operator-linebreak": [2, "after"],
"padded-blocks": [2, "never"],
"prefer-const": 2,
"prefer-rest-params": 2,
"prefer-template": 2,
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"rest-spread-spacing": ["error"],
"semi": [2, "always"],
"semi-spacing": [2, { "before": false, "after": true }],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, { "anonymous": "always", "named": "never" }],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }],
"template-curly-spacing": [2, "never"],
"use-isnan": 2,
"valid-typeof": 2,
"wrap-iife": [2, "outside"],
"yield-star-spacing": [2, "both"],
"yoda": [2, "never"],
// promise
"promise/param-names": 2,
// jsx accessibility
"jsx-a11y/aria-props": 2,
"jsx-a11y/aria-proptypes": 2,
"jsx-a11y/aria-role": 2,
"jsx-a11y/aria-unsupported-elements": 2,
"jsx-a11y/heading-has-content": 2,
"jsx-a11y/href-no-hash": 2,
"jsx-a11y/html-has-lang": 2,
"jsx-a11y/img-has-alt": 2,
"jsx-a11y/img-redundant-alt": 2,
"jsx-a11y/label-has-for": 2,
"jsx-a11y/mouse-events-have-key-events": 2,
"jsx-a11y/no-access-key": 2,
"jsx-a11y/no-marquee": 2,
"jsx-a11y/no-onchange": 0,
"jsx-a11y/onclick-has-focus": 2,
"jsx-a11y/onclick-has-role": 2,
"jsx-a11y/role-has-required-aria-props": 2,
"jsx-a11y/role-supports-aria-props": 2,
"jsx-a11y/scope": 2,
"jsx-a11y/tabindex-no-positive": 2,
// react
"jsx-quotes": [2, "prefer-single"],
"react/display-name": 0,
"react/jsx-boolean-value": 2,
"react/jsx-handler-names": [2, {
"eventHandlerPrefix": "_handle"
}],
"react/jsx-indent": [2, 2],
"react/jsx-indent-props": [2, 2],
"react/jsx-no-bind": 2,
"react/jsx-no-duplicate-props": 2,
"react/jsx-no-undef": 2,
"react/jsx-pascal-case": 2,
"react/jsx-sort-props": 2,
"react/jsx-sort-prop-types": 2
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/no-did-mount-set-state": 0,
"react/no-did-update-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/no-multi-comp": 0,
"react/no-string-refs": 2,
"react/no-unknown-property": 2,
"react/prefer-es6-class": 2,
"react/prop-types": 2,
"react/react-in-jsx-scope": 0,
"react/self-closing-comp": 2,
"react/sort-comp": 0,
"react/sort-prop-types": 2,
"react/wrap-multilines": 0
}
}

138
README.md
View File

@@ -18,6 +18,10 @@ Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
"React Native for Web" is a project to bring React Native's building blocks and
touch handling to the Web. [Read more](#why).
Browse the UI Explorer to see React Native [examples running on
Web](https://necolas.github.io/react-native-web/storybook/). Or try it out
online with [React Native for Web: Playground](http://codepen.io/necolas/pen/PZzwBR).
## Quick start
To install in your app:
@@ -28,15 +32,71 @@ npm install --save react react-native-web
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
You can also bootstrap a standard React Native project structure for web by
using [react-native-web-starter](https://github.com/grabcode/react-native-web-starter).
Alternatively, you can quickly setup a local project
using [create-react-app](https://github.com/facebookincubator/create-react-app)
(which supports `react-native-web` out-of-the-box once installed) and
[react-native-web-starter](https://github.com/grabcode/react-native-web-starter).
## Examples
## Documentation
* React Native [examples running on Web](https://necolas.github.io/react-native-web/storybook/)
* [React Native for Web: Playground](http://codepen.io/necolas/pen/PZzwBR).
Guides:
Sample:
* [Accessibility](docs/guides/accessibility.md)
* [Client and server rendering](docs/guides/rendering.md)
* [Direct manipulation](docs/guides/direct-manipulation.md)
* [Internationalization](docs/guides/internationalization.md)
* [Known issues](docs/guides/known-issues.md)
* [React Native](docs/guides/react-native.md)
* [Style](docs/guides/style.md)
Exported modules:
* Components
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
* [`Image`](docs/components/Image.md)
* [`ListView`](docs/components/ListView.md)
* [`ScrollView`](docs/components/ScrollView.md)
* [`Switch`](docs/components/Switch.md)
* [`Text`](docs/components/Text.md)
* [`TextInput`](docs/components/TextInput.md)
* [`TouchableHighlight`](http://facebook.github.io/react-native/releases/0.22/docs/touchablehighlight.html) (mirrors React Native)
* [`TouchableOpacity`](http://facebook.github.io/react-native/releases/0.22/docs/touchableopacity.html) (mirrors React Native)
* [`TouchableWithoutFeedback`](docs/components/TouchableWithoutFeedback.md)
* [`View`](docs/components/View.md)
* APIs
* [`Animated`](http://facebook.github.io/react-native/releases/0.20/docs/animated.html) (mirrors React Native)
* [`AppRegistry`](docs/apis/AppRegistry.md)
* [`AppState`](docs/apis/AppState.md)
* [`AsyncStorage`](docs/apis/AsyncStorage.md)
* [`Dimensions`](docs/apis/Dimensions.md)
* [`I18nManager`](docs/apis/I18nManager.md)
* [`NativeMethods`](docs/apis/NativeMethods.md)
* [`NetInfo`](docs/apis/NetInfo.md)
* [`PanResponder`](http://facebook.github.io/react-native/releases/0.20/docs/panresponder.html#content) (mirrors React Native)
* [`PixelRatio`](docs/apis/PixelRatio.md)
* [`Platform`](docs/apis/Platform.md)
* [`StyleSheet`](docs/apis/StyleSheet.md)
* [`Vibration`](docs/apis/Vibration.md)
<span id="#why"></span>
## Why?
There are many different teams at Twitter building web applications with React.
We want to share React components, libraries, and APIs between teams…much like
the OSS community tries to do. At our scale, this involves dealing with
multiple, inter-related problems including: a common way to handle style,
animation, touch, viewport adaptation, accessibility, themes, RTL layout, and
server-rendering.
This is hard to do with React DOM, as the components are essentially the same
low-level building blocks that the browser provides. However, React Native
avoids, solves, or can solve almost all these problems facing Web teams.
Central to this is React Native's JavaScript style API (not strictly
"CSS-in-JS") which avoids the key [problems with
CSS](https://speakerdeck.com/vjeux/react-css-in-js) by giving up some of the
complexity of CSS.
## Example code
```js
import React from 'react'
@@ -75,72 +135,6 @@ AppRegistry.registerComponent('MyApp', () => App)
AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-root') })
```
## Documentation
Guides:
* [Accessibility](docs/guides/accessibility.md)
* [Client and server rendering](docs/guides/rendering.md)
* [Direct manipulation](docs/guides/direct-manipulation.md)
* [Known issues](docs/guides/known-issues.md)
* [React Native](docs/guides/react-native.md)
* [Style](docs/guides/style.md)
Exported modules:
* Components
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
* [`Image`](docs/components/Image.md)
* [`ListView`](docs/components/ListView.md)
* [`ScrollView`](docs/components/ScrollView.md)
* [`Text`](docs/components/Text.md)
* [`TextInput`](docs/components/TextInput.md)
* [`TouchableHighlight`](http://facebook.github.io/react-native/releases/0.22/docs/touchablehighlight.html) (mirrors React Native)
* [`TouchableOpacity`](http://facebook.github.io/react-native/releases/0.22/docs/touchableopacity.html) (mirrors React Native)
* [`TouchableWithoutFeedback`](docs/components/TouchableWithoutFeedback.md)
* [`View`](docs/components/View.md)
* APIs
* [`Animated`](http://facebook.github.io/react-native/releases/0.20/docs/animated.html) (mirrors React Native)
* [`AppRegistry`](docs/apis/AppRegistry.md)
* [`AppState`](docs/apis/AppState.md)
* [`AsyncStorage`](docs/apis/AsyncStorage.md)
* [`Dimensions`](docs/apis/Dimensions.md)
* [`I18nManager`](docs/apis/I18nManager.md)
* [`NativeMethods`](docs/apis/NativeMethods.md)
* [`NetInfo`](docs/apis/NetInfo.md)
* [`PanResponder`](http://facebook.github.io/react-native/releases/0.20/docs/panresponder.html#content) (mirrors React Native)
* [`PixelRatio`](docs/apis/PixelRatio.md)
* [`Platform`](docs/apis/Platform.md)
* [`StyleSheet`](docs/apis/StyleSheet.md)
* [`Vibration`](docs/apis/Vibration.md)
<span id="#why"></span>
## Why?
React Native is a comprehensive JavaScript framework for building application
user interfaces. It provides high-level, platform-agnostic components and APIs
e.g., `Text`, `View`, `Touchable*`, `Animated`, `StyleSheet` - that simplify
working with layout, gestures, animations, and styles. The entire React Native
ecosystem can depend on these shared building blocks.
In contrast, the React DOM ecosystem is limited by the lack of a higher-level
framework. At Twitter, we want to seamlessly author and share React component
libraries between different Web applications (with increasing interest from
product teams for multi-platform solutions). This goal draws together multiple,
inter-related problems including: styling, animation, gestures, themes,
viewport adaptation, accessibility, diverse build processes, and RTL layouts.
Almost all these problems are avoided, solved, or can be solved in React
Native. Central to this is React Native's JavaScript style API (not strictly
"CSS-in-JS") which avoids the key [problems with
CSS](https://speakerdeck.com/vjeux/react-css-in-js). By giving up some of the
complexity of CSS it also provides a reliable surface for style composition,
animation, gestures, server-side rendering, RTL layout; and removes the
requirement for CSS-specific build tools.
Bringing the React Native APIs and components to the Web has the added benefit
of allowing teams to explore code-sharing between Native and Web platforms.
## Related projects
* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter)

View File

@@ -6,17 +6,17 @@
**animating**: bool = true
Whether to show the indicator (true, the default) or hide it (false).
Whether to show the indicator or hide it.
**color**: string = #999999
**color**: string = '#1976D2'
The foreground color of the spinner (default is gray).
The foreground color of the spinner.
**hidesWhenStopped**: bool = true
Whether the indicator should hide when not animating (true by default).
Whether the indicator should hide when not animating.
**size**: oneOf('small, 'large')
**size**: oneOf('small, 'large') | number = 'small'
Size of the indicator. Small has a height of `20`, large has a height of `36`.

View File

@@ -0,0 +1,23 @@
# ProgressBar
Display an activity progress bar.
## Props
[...View props](./View.md)
**color**: string = '#1976D2'
Color of the progress bar.
**indeterminate**: bool = true
Whether the progress bar will show indeterminate progress.
**progress**: number
The progress value (between 0 and 1).
(web) **trackColor**: string = 'transparent'
Color of the track bar.

76
docs/components/Switch.md Normal file
View File

@@ -0,0 +1,76 @@
# Switch
This is a controlled component that requires an `onValueChange` callback that
updates the value prop in order for the component to reflect user actions. If
the `value` prop is not updated, the component will continue to render the
supplied `value` prop instead of the expected result of any user actions.
## Props
[...View props](./View.md)
**disabled**: bool = false
If `true` the user won't be able to interact with the switch.
**onValueChange**: func
Invoked with the new value when the value changes.
**value**: bool = false
The value of the switch. If `true` the switch will be turned on.
(web) **activeThumbColor**: color = #009688
The color of the thumb grip when the switch is turned on.
(web) **activeTrackColor**: color = #A3D3CF
The color of the track when the switch is turned on.
(web) **thumbColor**: color = #FAFAFA
The color of the thumb grip when the switch is turned off.
(web) **trackColor**: color = #939393
The color of the track when the switch is turned off.
## Examples
```js
import React, { Component } from 'react'
import { Switch, View } from 'react-native'
class ColorSwitchExample extends Component {
constructor(props) {
super(props)
this.state = {
colorTrueSwitchIsOn: true,
colorFalseSwitchIsOn: false
}
}
render() {
return (
<View>
<Switch
activeThumbColor='#428BCA'
activeTrackColor='#A0C4E3'
onValueChange={(value) => this.setState({ colorFalseSwitchIsOn: value })}
value={this.state.colorFalseSwitchIsOn}
/>
<Switch
activeThumbColor='#5CB85C'
activeTrackColor='#ADDAAD'
onValueChange={(value) => this.setState({ colorTrueSwitchIsOn: value })}
thumbColor='#EBA9A7'
trackColor='#D9534F'
value={this.state.colorTrueSwitchIsOn}
/>
</View>
)
}
}
```

View File

@@ -0,0 +1,34 @@
# Internationalization
To support right-to-left languages, application layout can be automatically
flipped from LTR to RTL. The `I18nManager` API can be used to help with more
fine-grained control and testing of RTL layouts.
React Native for Web provides an experimental feature to support "true left"
and "true right" styles. For example, `left` will be flipped to `right` in RTL
mode, but `left$noI18n` will not. More information is available in the `Text`
and `View` documentation.
## Working with icons and images
Icons and images that must match the LTR or RTL layout of the app need to be manually flipped.
Either use a transform style:
```js
<Image
source={...}
style={{ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }] }}
/>
```
Or replace the source asset:
```js
import imageSourceLTR from './back.png';
import imageSourceRTL from './forward.png';
<Image
source={I18nManager.isRTL ? imageSourceRTL : imageSourceLTR}
/>
```

View File

@@ -0,0 +1,79 @@
import { I18nManager, StyleSheet, TouchableHighlight, Text, View } from 'react-native'
import React, { Component } from 'react';
import { storiesOf, action } from '@kadira/storybook';
class RTLExample extends Component {
componentWillUnmount() {
I18nManager.setPreferredLanguageRTL(false)
}
render() {
return (
<View style={styles.container}>
<Text accessibilityRole='heading' style={styles.welcome}>
LTR/RTL layout example!
</Text>
<Text style={styles.text}>
This is sample text. The writing direction can be changed by pressing the button below.
</Text>
<Text style={[ styles.text, styles.ltrText ]}>
This is text that will always display LTR.
</Text>
<Text style={[ styles.text, styles.rtlText ]}>
This is text that will always display RTL.
</Text>
<TouchableHighlight
onPress={this._handleToggle}
style={styles.toggle}
underlayColor='rgba(0,0,0,0.25)'
>
<Text>Toggle LTR/RTL</Text>
</TouchableHighlight>
</View>
)
}
_handleToggle = () => {
this._isRTL = !this._isRTL
I18nManager.setPreferredLanguageRTL(this._isRTL)
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#F5FCFF',
flex: 1,
justifyContent: 'center',
padding: 10
},
welcome: {
fontSize: 28,
marginVertical: 10
},
text: {
color: '#333333',
fontSize: 18,
marginBottom: 5
},
ltrText: {
textAlign$noI18n: 'left',
writingDirection$noI18n: 'ltr'
},
rtlText: {
textAlign$noI18n: 'right',
writingDirection$noI18n: 'rtl'
},
toggle: {
alignSelf: 'center',
borderColor: 'black',
borderStyle: 'solid',
borderWidth: 1,
marginTop: 10,
padding: 10
}
})
storiesOf('api: I18nManager', module)
.add('RTL layout', () => (
<RTLExample />
))

View File

@@ -113,5 +113,5 @@ var styles = StyleSheet.create({
});
storiesOf('PanResponder', module)
storiesOf('api: PanResponder', module)
.add('example', () => <PanResponderExample />)

View File

@@ -59,31 +59,15 @@ const ToggleAnimatingActivityIndicator = React.createClass({
const examples = [
{
title: 'Default (small, white)',
title: 'Default',
render() {
return (
<ActivityIndicator
style={[styles.centering, styles.gray]}
color="white"
style={[styles.centering]}
/>
);
}
},
{
title: 'Gray',
render() {
return (
<View>
<ActivityIndicator
style={[styles.centering]}
/>
<ActivityIndicator
style={[styles.centering, {backgroundColor: '#eeeeee'}]}
/>
</View>
);
}
},
{
title: 'Custom colors',
render() {
@@ -144,10 +128,13 @@ const examples = [
title: 'Custom size',
render() {
return (
<ActivityIndicator
style={[styles.centering, {transform: [{scale: 1.5}]}]}
size="large"
/>
<View style={[styles.horizontal, styles.centering]}>
<ActivityIndicator size="40" />
<ActivityIndicator
style={{ marginLeft: 20, transform: [ {scale: 1.5} ] }}
size="large"
/>
</View>
);
}
},
@@ -170,6 +157,6 @@ const styles = StyleSheet.create({
});
examples.forEach((example) => {
storiesOf('<ActivityIndicator>', module)
storiesOf('component: ActivityIndicator', module)
.add(example.title, () => example.render())
})

View File

@@ -651,7 +651,7 @@ var styles = StyleSheet.create({
});
examples.forEach((example) => {
storiesOf('<Image>', module)
storiesOf('component: Image', module)
.addDecorator((renderStory) => <View>{renderStory()}</View>)
.add(example.title, () => example.render())
})

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 850 B

After

Width:  |  Height:  |  Size: 850 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,96 @@
import { ProgressBar, StyleSheet, View } from 'react-native'
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import TimerMixin from 'react-timer-mixin';
/**
* Copyright (c) 2013-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.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
var ProgressBarExample = React.createClass({
mixins: [TimerMixin],
getInitialState() {
return {
progress: 0,
};
},
componentDidMount() {
this.updateProgress();
},
updateProgress() {
var progress = this.state.progress + 0.01;
this.setState({ progress });
this.requestAnimationFrame(() => this.updateProgress());
},
getProgress(offset) {
var progress = this.state.progress + offset;
return Math.sin(progress % Math.PI) % 1;
},
render() {
return (
<View style={styles.container}>
<ProgressBar style={styles.progressView} color="purple" progress={this.getProgress(0.2)} />
<ProgressBar style={styles.progressView} color="red" progress={this.getProgress(0.4)} />
<ProgressBar style={styles.progressView} color="orange" progress={this.getProgress(0.6)} />
<ProgressBar style={styles.progressView} color="yellow" progress={this.getProgress(0.8)} />
</View>
);
},
});
const examples = [{
title: 'progress',
render() {
return (
<ProgressBarExample />
);
},
}, {
title: 'indeterminate',
render() {
return (
<ProgressBar indeterminate style={styles.progressView} trackColor='#D1E3F6' />
);
}
}];
var styles = StyleSheet.create({
container: {
minWidth: 200,
marginTop: -20,
backgroundColor: 'transparent',
},
progressView: {
marginTop: 20,
minWidth: 200
}
});
examples.forEach((example) => {
storiesOf('component: ProgressBar', module)
.add(example.title, () => example.render())
})

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { ScrollView, StyleSheet, Text, View } from 'react-native'
storiesOf('<ScrollView>', module)
storiesOf('component: ScrollView', module)
.add('vertical', () => (
<View style={styles.scrollViewContainer}>
<ScrollView
@@ -52,6 +52,7 @@ const styles = StyleSheet.create({
borderWidth: '1px'
},
scrollViewContentContainerStyle: {
backgroundColor: '#eee',
padding: '10px'
}
})

View File

@@ -0,0 +1,190 @@
import { Platform, Switch, Text, View } from 'react-native'
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
/**
* Copyright (c) 2013-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.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
var BasicSwitchExample = React.createClass({
getInitialState() {
return {
trueSwitchIsOn: true,
falseSwitchIsOn: false,
};
},
render() {
return (
<View>
<Switch
onValueChange={(value) => this.setState({falseSwitchIsOn: value})}
style={{marginBottom: 10}}
value={this.state.falseSwitchIsOn}
/>
<Switch
onValueChange={(value) => this.setState({trueSwitchIsOn: value})}
value={this.state.trueSwitchIsOn}
/>
</View>
);
}
});
var DisabledSwitchExample = React.createClass({
render() {
return (
<View>
<Switch
disabled={true}
style={{marginBottom: 10}}
value={true} />
<Switch
disabled={true}
value={false} />
</View>
);
},
});
var ColorSwitchExample = React.createClass({
getInitialState() {
return {
colorTrueSwitchIsOn: true,
colorFalseSwitchIsOn: false,
};
},
render() {
return (
<View>
<Switch
activeThumbColor="#428bca"
activeTrackColor="#A0C4E3"
onValueChange={(value) => this.setState({colorFalseSwitchIsOn: value})}
style={{marginBottom: 10}}
value={this.state.colorFalseSwitchIsOn}
/>
<Switch
activeThumbColor="#5CB85C"
activeTrackColor="#ADDAAD"
onValueChange={(value) => this.setState({colorTrueSwitchIsOn: value})}
thumbColor="#EBA9A7"
trackColor="#D9534F"
value={this.state.colorTrueSwitchIsOn}
/>
</View>
);
},
});
var EventSwitchExample = React.createClass({
getInitialState() {
return {
eventSwitchIsOn: false,
eventSwitchRegressionIsOn: true,
};
},
render() {
return (
<View style={{ flexDirection: 'row', justifyContent: 'space-around' }}>
<View>
<Switch
onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventSwitchIsOn} />
<Switch
onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventSwitchIsOn} />
<Text>{this.state.eventSwitchIsOn ? 'On' : 'Off'}</Text>
</View>
<View>
<Switch
onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventSwitchRegressionIsOn} />
<Switch
onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventSwitchRegressionIsOn} />
<Text>{this.state.eventSwitchRegressionIsOn ? 'On' : 'Off'}</Text>
</View>
</View>
);
}
});
var SizeSwitchExample = React.createClass({
getInitialState() {
return {
trueSwitchIsOn: true,
falseSwitchIsOn: false,
};
},
render() {
return (
<View>
<Switch
onValueChange={(value) => this.setState({falseSwitchIsOn: value})}
style={{marginBottom: 10, height: '3rem' }}
value={this.state.falseSwitchIsOn}
/>
<Switch
onValueChange={(value) => this.setState({trueSwitchIsOn: value})}
style={{marginBottom: 10, width: 150 }}
value={this.state.trueSwitchIsOn}
/>
</View>
);
}
});
var examples = [
{
title: 'set to true or false',
render(): ReactElement<any> { return <BasicSwitchExample />; }
},
{
title: 'disabled',
render(): ReactElement<any> { return <DisabledSwitchExample />; }
},
{
title: 'change events',
render(): ReactElement<any> { return <EventSwitchExample />; }
},
{
title: 'custom colors',
render(): ReactElement<any> { return <ColorSwitchExample />; }
},
{
title: 'custom size',
render(): ReactElement<any> { return <SizeSwitchExample />; }
},
{
title: 'controlled component',
render(): ReactElement<any> { return <Switch />; }
}
];
examples.forEach((example) => {
storiesOf('component: Switch', module)
.add(example.title, () => example.render())
})

View File

@@ -466,7 +466,7 @@ var styles = StyleSheet.create({
});
examples.forEach((example) => {
storiesOf('<Text>', module)
storiesOf('component: Text', module)
.addDecorator((renderStory) => <View style={{ width: 320 }}>{renderStory()}</View>)
.add(example.title, () => example.render())
})

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { StyleSheet, TextInput, View } from 'react-native'
storiesOf('<TextInput>', module)
storiesOf('component: TextInput', module)
.add('tbd', () => (
<View>
<TextInput
@@ -14,6 +14,7 @@ storiesOf('<TextInput>', module)
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
/>
<TextInput keyboardType='search' style={styles.textInput} />
<TextInput secureTextEntry style={styles.textInput} />
<TextInput defaultValue='read only' editable={false} style={styles.textInput} />
<TextInput

View File

@@ -445,6 +445,6 @@ var styles = StyleSheet.create({
});
examples.forEach((example) => {
storiesOf('<Touchable*>', module)
storiesOf('component: Touchable*', module)
.add(example.title, () => example.render())
})

View File

@@ -245,6 +245,6 @@ const examples = [
];
examples.forEach((example) => {
storiesOf('<View>', module)
storiesOf('component: View', module)
.add(example.title, () => example.render())
})

View File

@@ -281,6 +281,6 @@ const examples = [
];
examples.forEach((example) => {
storiesOf('<View> transforms', module)
storiesOf('component: View (transforms)', module)
.add(example.title, () => example.render())
})

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import Game2048 from './Game2048'
storiesOf('Game2048', module)
storiesOf('demo: Game2048', module)
.add('the game', () => (
<Game2048 />
))

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import TicTacToe from './TicTacToe'
storiesOf('TicTacToe', module)
storiesOf('demo: TicTacToe', module)
.add('the game', () => (
<TicTacToe />
))

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.43",
"version": "0.0.45",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
@@ -11,7 +11,7 @@
"build:examples": "build-storybook -o dist-examples -c ./examples/.storybook",
"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",
"examples": "start-storybook -p 9001 -c ./examples/.storybook --dont-track",
"lint": "eslint src",
"prepublish": "npm run build && npm run build:umd",
"test": "karma start karma.config.js",
@@ -19,47 +19,45 @@
},
"dependencies": {
"animated": "^0.1.3",
"babel-runtime": "^6.9.2",
"fbjs": "^0.8.1",
"inline-style-prefixer": "^2.0.0",
"lodash": "^4.13.1",
"react-dom": "^15.1.0",
"react-textarea-autosize": "^4.0.2",
"babel-runtime": "^6.11.6",
"fbjs": "^0.8.4",
"inline-style-prefixer": "^2.0.1",
"lodash": "^4.15.0",
"react-dom": "^15.3.1",
"react-textarea-autosize": "^4.0.4",
"react-timer-mixin": "^0.13.3"
},
"devDependencies": {
"@kadira/storybook": "^1.38.0",
"babel-cli": "^6.10.1",
"babel-core": "^6.10.4",
"babel-eslint": "^6.1.0",
"babel-loader": "^6.2.4",
"@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-preset-react-native": "^1.9.0",
"del-cli": "^0.2.0",
"enzyme": "^2.3.0",
"eslint": "^2.12.0",
"eslint-config-standard": "^5.3.1",
"eslint-config-standard-react": "^2.4.0",
"eslint-plugin-promise": "^1.3.2",
"eslint-plugin-react": "^5.1.1",
"eslint-plugin-standard": "^1.3.2",
"enzyme": "^2.4.1",
"eslint": "^3.4.0",
"eslint-plugin-jsx-a11y": "^2.2.0",
"eslint-plugin-promise": "^2.0.1",
"eslint-plugin-react": "^6.1.2",
"file-loader": "^0.9.0",
"karma": "^0.13.22",
"karma": "^1.2.0",
"karma-browserstack-launcher": "^1.0.1",
"karma-chrome-launcher": "^1.0.1",
"karma-chrome-launcher": "^2.0.0",
"karma-firefox-launcher": "^1.0.0",
"karma-mocha": "^1.1.1",
"karma-mocha-reporter": "^2.0.4",
"karma-mocha-reporter": "^2.1.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.7.0",
"mocha": "^2.5.3",
"karma-webpack": "^1.8.0",
"mocha": "^3.0.2",
"node-libs-browser": "^0.5.3",
"react": "^15.2.0",
"react-addons-test-utils": "^15.2.0",
"react": "^15.3.1",
"react-addons-test-utils": "^15.3.1",
"url-loader": "^0.5.7",
"webpack": "^1.13.1"
"webpack": "^1.13.2"
},
"peerDependencies": {
"react": "^15.1.0"
"react": "^15.3.1"
},
"author": "Nicolas Gallagher",
"license": "BSD-3-Clause",

View File

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

View File

@@ -1,6 +1,6 @@
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../StyleSheet'
import View from '../../components/View'
import StyleSheet from '../StyleSheet';
import View from '../../components/View';
import React, { Component, PropTypes } from 'react';
class ReactNativeApp extends Component {
static propTypes = {
@@ -10,13 +10,13 @@ class ReactNativeApp extends Component {
};
render() {
const { initialProps, rootComponent: RootComponent, rootTag } = this.props
const { initialProps, rootComponent: RootComponent, rootTag } = this.props;
return (
<View style={styles.appContainer}>
<RootComponent {...initialProps} rootTag={rootTag} />
</View>
)
);
}
}
@@ -31,6 +31,6 @@ const styles = StyleSheet.create({
right: 0,
bottom: 0
}
})
});
module.exports = ReactNativeApp
module.exports = ReactNativeApp;

View File

@@ -1,16 +1,16 @@
/* eslint-env mocha */
import assert from 'assert'
import { prerenderApplication } from '../renderApplication'
import React from 'react'
import assert from 'assert';
import { prerenderApplication } from '../renderApplication';
import React from 'react';
const component = () => <div />
const component = () => <div />;
suite('apis/AppRegistry/renderApplication', () => {
test('prerenderApplication', () => {
const { html, styleElement } = prerenderApplication(component, {})
const { html, styleElement } = prerenderApplication(component, {});
assert.ok(html.indexOf('<div ') > -1)
assert.equal(styleElement.type, 'style')
})
})
assert.ok(html.indexOf('<div ') > -1);
assert.equal(styleElement.type, 'style');
});
});

View File

@@ -6,12 +6,12 @@
* @flow
*/
import { Component } from 'react'
import invariant from 'fbjs/lib/invariant'
import ReactDOM from 'react-dom'
import renderApplication, { prerenderApplication } from './renderApplication'
import { Component } from 'react';
import invariant from 'fbjs/lib/invariant';
import ReactDOM from 'react-dom';
import renderApplication, { prerenderApplication } from './renderApplication';
const runnables = {}
const runnables = {};
type ComponentProvider = () => Component<any, any, any>
@@ -26,7 +26,7 @@ type AppConfig = {
*/
class AppRegistry {
static getAppKeys(): Array<string> {
return Object.keys(runnables)
return Object.keys(runnables);
}
static prerenderApplication(appKey: string, appParameters?: Object): string {
@@ -34,59 +34,59 @@ class AppRegistry {
runnables[appKey] && runnables[appKey].prerender,
`Application ${appKey} has not been registered. ` +
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
)
);
return runnables[appKey].prerender(appParameters)
return runnables[appKey].prerender(appParameters);
}
static registerComponent(appKey: string, getComponentFunc: ComponentProvider): string {
runnables[appKey] = {
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag),
prerender: ({ initialProps } = {}) => prerenderApplication(getComponentFunc(), initialProps)
}
return appKey
};
return appKey;
}
static registerConfig(config: Array<AppConfig>) {
config.forEach(({ appKey, component, run }) => {
if (run) {
AppRegistry.registerRunnable(appKey, run)
AppRegistry.registerRunnable(appKey, run);
} else {
invariant(component, 'No component provider passed in')
AppRegistry.registerComponent(appKey, component)
invariant(component, 'No component provider passed in');
AppRegistry.registerComponent(appKey, component);
}
})
});
}
// TODO: fix style sheet creation when using this method
static registerRunnable(appKey: string, run: Function): string {
runnables[appKey] = { run }
return appKey
runnables[appKey] = { run };
return appKey;
}
static runApplication(appKey: string, appParameters?: Object): void {
const isDevelopment = process.env.NODE_ENV !== 'production'
const params = { ...appParameters }
params.rootTag = `#${params.rootTag.id}`
const isDevelopment = process.env.NODE_ENV !== 'production';
const params = { ...appParameters };
params.rootTag = `#${params.rootTag.id}`;
console.log(
`Running application "${appKey}" with appParams: ${JSON.stringify(params)}. ` +
`development-level warnings are ${isDevelopment ? 'ON' : 'OFF'}, ` +
`performance optimizations are ${isDevelopment ? 'OFF' : 'ON'}`
)
);
invariant(
runnables[appKey] && runnables[appKey].run,
`Application "${appKey}" has not been registered. ` +
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
)
);
runnables[appKey].run(appParameters)
runnables[appKey].run(appParameters);
}
static unmountApplicationComponentAtRootTag(rootTag) {
ReactDOM.unmountComponentAtNode(rootTag)
ReactDOM.unmountComponentAtNode(rootTag);
}
}
module.exports = AppRegistry
module.exports = AppRegistry;

View File

@@ -6,15 +6,15 @@
* @flow
*/
import invariant from 'fbjs/lib/invariant'
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import ReactNativeApp from './ReactNativeApp'
import StyleSheet from '../../apis/StyleSheet'
import invariant from 'fbjs/lib/invariant';
import ReactDOM from 'react-dom';
import ReactDOMServer from 'react-dom/server';
import ReactNativeApp from './ReactNativeApp';
import StyleSheet from '../../apis/StyleSheet';
import React, { Component } from 'react';
export default function renderApplication(RootComponent: Component, initialProps: Object, rootTag: any) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag)
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
const component = (
<ReactNativeApp
@@ -22,8 +22,8 @@ export default function renderApplication(RootComponent: Component, initialProps
rootComponent={RootComponent}
rootTag={rootTag}
/>
)
ReactDOM.render(component, rootTag)
);
ReactDOM.render(component, rootTag);
}
export function prerenderApplication(RootComponent: Component, initialProps: Object): string {
@@ -32,8 +32,8 @@ export function prerenderApplication(RootComponent: Component, initialProps: Obj
initialProps={initialProps}
rootComponent={RootComponent}
/>
)
const html = ReactDOMServer.renderToString(component)
const styleElement = StyleSheet.render()
return { html, styleElement }
);
const html = ReactDOMServer.renderToString(component);
const styleElement = StyleSheet.render();
return { html, styleElement };
}

View File

@@ -1,31 +1,31 @@
/* eslint-env mocha */
import AppState from '..'
import assert from 'assert'
import AppState from '..';
import assert from 'assert';
suite('apis/AppState', () => {
const handler = () => {}
const handler = () => {};
teardown(() => {
try { AppState.removeEventListener('change', handler) } catch (e) {}
})
try { AppState.removeEventListener('change', handler); } catch (e) {}
});
suite('addEventListener', () => {
test('throws if the provided "eventType" is not supported', () => {
assert.throws(() => AppState.addEventListener('foo', handler))
assert.doesNotThrow(() => AppState.addEventListener('change', handler))
})
})
assert.throws(() => AppState.addEventListener('foo', handler));
assert.doesNotThrow(() => AppState.addEventListener('change', handler));
});
});
suite('removeEventListener', () => {
test('throws if the handler is not registered', () => {
assert.throws(() => AppState.removeEventListener('change', handler))
})
assert.throws(() => AppState.removeEventListener('change', handler));
});
test('throws if the provided "eventType" is not supported', () => {
AppState.addEventListener('change', handler)
assert.throws(() => AppState.removeEventListener('foo', handler))
assert.doesNotThrow(() => AppState.removeEventListener('change', handler))
})
})
})
AppState.addEventListener('change', handler);
assert.throws(() => AppState.removeEventListener('foo', handler));
assert.doesNotThrow(() => AppState.removeEventListener('change', handler));
});
});
});

View File

@@ -1,54 +1,54 @@
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import findIndex from 'lodash/findIndex'
import invariant from 'fbjs/lib/invariant'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import findIndex from 'lodash/findIndex';
import invariant from 'fbjs/lib/invariant';
const EVENT_TYPES = [ 'change' ]
const VISIBILITY_CHANGE_EVENT = 'visibilitychange'
const EVENT_TYPES = [ 'change' ];
const VISIBILITY_CHANGE_EVENT = 'visibilitychange';
const AppStates = {
BACKGROUND: 'background',
ACTIVE: 'active'
}
};
const listeners = []
const listeners = [];
class AppState {
static isSupported = ExecutionEnvironment.canUseDOM && document.visibilityState
static get currentState() {
if (!AppState.isSupported) {
return AppState.ACTIVE
return AppState.ACTIVE;
}
switch (document.visibilityState) {
case 'hidden':
case 'prerender':
case 'unloaded':
return AppStates.BACKGROUND
return AppStates.BACKGROUND;
default:
return AppStates.ACTIVE
return AppStates.ACTIVE;
}
}
static addEventListener(type: string, handler: Function) {
if (AppState.isSupported) {
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
const callback = () => handler(AppState.currentState)
listeners.push([ handler, callback ])
document.addEventListener(VISIBILITY_CHANGE_EVENT, callback, false)
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
const callback = () => handler(AppState.currentState);
listeners.push([ handler, callback ]);
document.addEventListener(VISIBILITY_CHANGE_EVENT, callback, false);
}
}
static removeEventListener(type: string, handler: Function) {
if (AppState.isSupported) {
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type)
const listenerIndex = findIndex(listeners, (pair) => pair[0] === handler)
invariant(listenerIndex !== -1, 'Trying to remove AppState listener for unregistered handler')
const callback = listeners[listenerIndex][1]
document.removeEventListener(VISIBILITY_CHANGE_EVENT, callback, false)
listeners.splice(listenerIndex, 1)
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type);
const listenerIndex = findIndex(listeners, (pair) => pair[0] === handler);
invariant(listenerIndex !== -1, 'Trying to remove AppState listener for unregistered handler');
const callback = listeners[listenerIndex][1];
document.removeEventListener(VISIBILITY_CHANGE_EVENT, callback, false);
listeners.splice(listenerIndex, 1);
}
}
}
module.exports = AppState
module.exports = AppState;

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('apis/AsyncStorage', () => {
test.skip('NO TEST COVERAGE', () => {})
})
test.skip('NO TEST COVERAGE', () => {});
});

View File

@@ -5,12 +5,12 @@
*/
const mergeLocalStorageItem = (key, value) => {
const oldValue = window.localStorage.getItem(key)
const oldObject = JSON.parse(oldValue)
const newObject = JSON.parse(value)
const nextValue = JSON.stringify({ ...oldObject, ...newObject })
window.localStorage.setItem(key, nextValue)
}
const oldValue = window.localStorage.getItem(key);
const oldObject = JSON.parse(oldValue);
const newObject = JSON.parse(value);
const nextValue = JSON.stringify({ ...oldObject, ...newObject });
window.localStorage.setItem(key, nextValue);
};
class AsyncStorage {
/**
@@ -19,12 +19,12 @@ class AsyncStorage {
static clear() {
return new Promise((resolve, reject) => {
try {
window.localStorage.clear()
resolve(null)
window.localStorage.clear();
resolve(null);
} catch (err) {
reject(err)
reject(err);
}
})
});
}
/**
@@ -33,17 +33,17 @@ class AsyncStorage {
static getAllKeys() {
return new Promise((resolve, reject) => {
try {
const numberOfKeys = window.localStorage.length
const keys = []
const numberOfKeys = window.localStorage.length;
const keys = [];
for (let i = 0; i < numberOfKeys; i += 1) {
const key = window.localStorage.key(i)
keys.push(key)
const key = window.localStorage.key(i);
keys.push(key);
}
resolve(keys)
resolve(keys);
} catch (err) {
reject(err)
reject(err);
}
})
});
}
/**
@@ -52,12 +52,12 @@ class AsyncStorage {
static getItem(key: string) {
return new Promise((resolve, reject) => {
try {
const value = window.localStorage.getItem(key)
resolve(value)
const value = window.localStorage.getItem(key);
resolve(value);
} catch (err) {
reject(err)
reject(err);
}
})
});
}
/**
@@ -66,12 +66,12 @@ class AsyncStorage {
static mergeItem(key: string, value: string) {
return new Promise((resolve, reject) => {
try {
mergeLocalStorageItem(key, value)
resolve(null)
mergeLocalStorageItem(key, value);
resolve(null);
} catch (err) {
reject(err)
reject(err);
}
})
});
}
/**
@@ -81,12 +81,12 @@ class AsyncStorage {
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
*/
static multiGet(keys: Array<string>) {
const promises = keys.map((key) => AsyncStorage.getItem(key))
const promises = keys.map((key) => AsyncStorage.getItem(key));
return Promise.all(promises).then(
(result) => Promise.resolve(result.map((value, i) => [ keys[i], value ])),
(error) => Promise.reject(error)
)
);
}
/**
@@ -96,24 +96,24 @@ class AsyncStorage {
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
*/
static multiMerge(keyValuePairs: Array<Array<string>>) {
const promises = keyValuePairs.map((item) => AsyncStorage.mergeItem(item[0], item[1]))
const promises = keyValuePairs.map((item) => AsyncStorage.mergeItem(item[0], item[1]));
return Promise.all(promises).then(
() => Promise.resolve(null),
(error) => Promise.reject(error)
)
);
}
/**
* Delete all the keys in the `keys` array.
*/
static multiRemove(keys: Array<string>) {
const promises = keys.map((key) => AsyncStorage.removeItem(key))
const promises = keys.map((key) => AsyncStorage.removeItem(key));
return Promise.all(promises).then(
() => Promise.resolve(null),
(error) => Promise.reject(error)
)
);
}
/**
@@ -121,12 +121,12 @@ class AsyncStorage {
* multiSet([['k1', 'val1'], ['k2', 'val2']])
*/
static multiSet(keyValuePairs: Array<Array<string>>) {
const promises = keyValuePairs.map((item) => AsyncStorage.setItem(item[0], item[1]))
const promises = keyValuePairs.map((item) => AsyncStorage.setItem(item[0], item[1]));
return Promise.all(promises).then(
() => Promise.resolve(null),
(error) => Promise.reject(error)
)
);
}
/**
@@ -135,12 +135,12 @@ class AsyncStorage {
static removeItem(key: string) {
return new Promise((resolve, reject) => {
try {
window.localStorage.removeItem(key)
resolve(null)
window.localStorage.removeItem(key);
resolve(null);
} catch (err) {
reject(err)
reject(err);
}
})
});
}
/**
@@ -149,13 +149,13 @@ class AsyncStorage {
static setItem(key: string, value: string) {
return new Promise((resolve, reject) => {
try {
window.localStorage.setItem(key, value)
resolve(null)
window.localStorage.setItem(key, value);
resolve(null);
} catch (err) {
reject(err)
reject(err);
}
})
});
}
}
module.exports = AsyncStorage
module.exports = AsyncStorage;

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('apis/Dimensions', () => {
test.skip('NO TEST COVERAGE', () => {})
})
test.skip('NO TEST COVERAGE', () => {});
});

View File

@@ -6,18 +6,18 @@
* @flow
*/
import debounce from 'lodash/debounce'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'
import debounce from 'lodash/debounce';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import invariant from 'fbjs/lib/invariant';
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} }
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} };
const dimensions = {}
const dimensions = {};
class Dimensions {
static get(dimension: string): Object {
invariant(dimensions[dimension], 'No dimension set for key ' + dimension)
return dimensions[dimension]
invariant(dimensions[dimension], `No dimension set for key ${dimension}`);
return dimensions[dimension];
}
static set(): void {
@@ -26,18 +26,18 @@ class Dimensions {
height: win.innerHeight,
scale: win.devicePixelRatio || 1,
width: win.innerWidth
}
};
dimensions.screen = {
fontScale: 1,
height: win.screen.height,
scale: win.devicePixelRatio || 1,
width: win.screen.width
}
};
}
}
Dimensions.set()
ExecutionEnvironment.canUseDOM && window.addEventListener('resize', debounce(Dimensions.set, 50))
Dimensions.set();
ExecutionEnvironment.canUseDOM && window.addEventListener('resize', debounce(Dimensions.set, 50));
module.exports = Dimensions
module.exports = Dimensions;

View File

@@ -1,46 +1,46 @@
/* eslint-env mocha */
import assert from 'assert'
import I18nManager from '..'
import assert from 'assert';
import I18nManager from '..';
suite('apis/I18nManager', () => {
suite('when RTL not enabled', () => {
setup(() => {
I18nManager.setPreferredLanguageRTL(false)
})
I18nManager.setPreferredLanguageRTL(false);
});
test('is "false" by default', () => {
assert.equal(I18nManager.isRTL, false)
assert.equal(document.documentElement.getAttribute('dir'), 'ltr')
})
assert.equal(I18nManager.isRTL, false);
assert.equal(document.documentElement.getAttribute('dir'), 'ltr');
});
test('is "true" when forced', () => {
I18nManager.forceRTL(true)
assert.equal(I18nManager.isRTL, true)
assert.equal(document.documentElement.getAttribute('dir'), 'rtl')
I18nManager.forceRTL(false)
})
})
I18nManager.forceRTL(true);
assert.equal(I18nManager.isRTL, true);
assert.equal(document.documentElement.getAttribute('dir'), 'rtl');
I18nManager.forceRTL(false);
});
});
suite('when RTL is enabled', () => {
setup(() => {
I18nManager.setPreferredLanguageRTL(true)
})
I18nManager.setPreferredLanguageRTL(true);
});
teardown(() => {
I18nManager.setPreferredLanguageRTL(false)
})
I18nManager.setPreferredLanguageRTL(false);
});
test('is "true" by default', () => {
assert.equal(I18nManager.isRTL, true)
assert.equal(document.documentElement.getAttribute('dir'), 'rtl')
})
assert.equal(I18nManager.isRTL, true);
assert.equal(document.documentElement.getAttribute('dir'), 'rtl');
});
test('is "false" when not allowed', () => {
I18nManager.allowRTL(false)
assert.equal(I18nManager.isRTL, false)
assert.equal(document.documentElement.getAttribute('dir'), 'ltr')
I18nManager.allowRTL(true)
})
})
})
I18nManager.allowRTL(false);
assert.equal(I18nManager.isRTL, false);
assert.equal(document.documentElement.getAttribute('dir'), 'ltr');
I18nManager.allowRTL(true);
});
});
});

View File

@@ -1,4 +1,4 @@
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
type I18nManagerStatus = {
allowRTL: (allowRTL: boolean) => {},
@@ -7,39 +7,39 @@ type I18nManagerStatus = {
isRTL: boolean
}
let isPreferredLanguageRTL = false
let isRTLAllowed = true
let isRTLForced = false
let isPreferredLanguageRTL = false;
let isRTLAllowed = true;
let isRTLForced = false;
const isRTL = () => {
if (isRTLForced) {
return true
return true;
}
return isRTLAllowed && isPreferredLanguageRTL
}
return isRTLAllowed && isPreferredLanguageRTL;
};
const onChange = () => {
if (ExecutionEnvironment.canUseDOM) {
document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr')
document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr');
}
}
};
const I18nManager: I18nManagerStatus = {
allowRTL(bool) {
isRTLAllowed = bool
onChange()
isRTLAllowed = bool;
onChange();
},
forceRTL(bool) {
isRTLForced = bool
onChange()
isRTLForced = bool;
onChange();
},
setPreferredLanguageRTL(bool) {
isPreferredLanguageRTL = bool
onChange()
isPreferredLanguageRTL = bool;
onChange();
},
get isRTL() {
return isRTL()
return isRTL();
}
}
};
module.exports = I18nManager
module.exports = I18nManager;

View File

@@ -5,8 +5,8 @@
* @flow
*/
import keyMirror from 'fbjs/lib/keyMirror'
import invariant from 'fbjs/lib/invariant'
import invariant from 'fbjs/lib/invariant';
import keyMirror from 'fbjs/lib/keyMirror';
const InteractionManager = {
Events: keyMirror({
@@ -21,15 +21,15 @@ const InteractionManager = {
invariant(
typeof callback === 'function',
'Must specify a function to schedule.'
)
callback()
);
callback();
},
/**
* Notify manager that an interaction has started.
*/
createInteractionHandle() {
return 1
return 1;
},
/**
@@ -39,10 +39,10 @@ const InteractionManager = {
invariant(
!!handle,
'Must provide a handle to clear.'
)
);
},
addListener: () => {}
}
};
module.exports = InteractionManager
module.exports = InteractionManager;

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('apis/NetInfo', () => {
test.skip('NO TEST COVERAGE', () => {})
})
test.skip('NO TEST COVERAGE', () => {});
});

View File

@@ -6,16 +6,16 @@
* @flow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import invariant from 'fbjs/lib/invariant';
const connection = ExecutionEnvironment.canUseDOM && (
window.navigator.connection ||
window.navigator.mozConnection ||
window.navigator.webkitConnection
)
);
const eventTypes = [ 'change' ]
const eventTypes = [ 'change' ];
/**
* Navigator online: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine
@@ -23,63 +23,63 @@ const eventTypes = [ 'change' ]
*/
const NetInfo = {
addEventListener(type: string, handler: Function): { remove: () => void } {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
if (!connection) {
console.error('Network Connection API is not supported. Not listening for connection type changes.')
console.error('Network Connection API is not supported. Not listening for connection type changes.');
return {
remove: () => {}
}
};
}
connection.addEventListener(type, handler)
connection.addEventListener(type, handler);
return {
remove: () => NetInfo.removeEventListener(type, handler)
}
};
},
removeEventListener(type: string, handler: Function): void {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
if (!connection) { return }
connection.removeEventListener(type, handler)
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
if (!connection) { return; }
connection.removeEventListener(type, handler);
},
fetch(): Promise {
return new Promise((resolve, reject) => {
try {
resolve(connection.type)
resolve(connection.type);
} catch (err) {
resolve('unknown')
resolve('unknown');
}
})
});
},
isConnected: {
addEventListener(type: string, handler: Function): { remove: () => void } {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
window.addEventListener('online', handler.bind(null, true), false)
window.addEventListener('offline', handler.bind(null, false), false)
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
window.addEventListener('online', handler.bind(null, true), false);
window.addEventListener('offline', handler.bind(null, false), false);
return {
remove: () => NetInfo.isConnected.removeEventListener(type, handler)
}
};
},
removeEventListener(type: string, handler: Function): void {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
window.removeEventListener('online', handler.bind(null, true), false)
window.removeEventListener('offline', handler.bind(null, false), false)
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
window.removeEventListener('online', handler.bind(null, true), false);
window.removeEventListener('offline', handler.bind(null, false), false);
},
fetch(): Promise {
return new Promise((resolve, reject) => {
try {
resolve(window.navigator.onLine)
resolve(window.navigator.onLine);
} catch (err) {
resolve(true)
resolve(true);
}
})
});
}
}
}
};
module.exports = NetInfo
module.exports = NetInfo;

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('apis/PixelRatio', () => {
test.skip('NO TEST COVERAGE', () => {})
})
test.skip('NO TEST COVERAGE', () => {});
});

View File

@@ -6,7 +6,7 @@
* @flow
*/
import Dimensions from '../Dimensions'
import Dimensions from '../Dimensions';
/**
* PixelRatio gives access to the device pixel density.
@@ -16,14 +16,14 @@ class PixelRatio {
* Returns the device pixel density.
*/
static get(): number {
return Dimensions.get('window').scale
return Dimensions.get('window').scale;
}
/**
* No equivalent for Web
*/
static getFontScale(): number {
return Dimensions.get('window').fontScale || PixelRatio.get()
return Dimensions.get('window').fontScale || PixelRatio.get();
}
/**
@@ -31,7 +31,7 @@ class PixelRatio {
* Guaranteed to return an integer number.
*/
static getPixelSizeForLayoutSize(layoutSize: number): number {
return Math.round(layoutSize * PixelRatio.get())
return Math.round(layoutSize * PixelRatio.get());
}
/**
@@ -41,9 +41,9 @@ class PixelRatio {
* exactly (8.33 * 3) = 25 pixels.
*/
static roundToNearestPixel(layoutSize: number): number {
const ratio = PixelRatio.get()
return Math.round(layoutSize * ratio) / ratio
const ratio = PixelRatio.get();
return Math.round(layoutSize * ratio) / ratio;
}
}
module.exports = PixelRatio
module.exports = PixelRatio;

View File

@@ -1,6 +1,6 @@
const Platform = {
OS: 'web',
select: (obj: Object) => obj.web
}
};
module.exports = Platform
module.exports = Platform;

View File

@@ -23,17 +23,18 @@ class StyleSheetValidation {
var message2 = '\nValid style props: ' +
JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ');
styleError(message1, style, caller, message2);
}
var error = allStylePropTypes[prop](
style,
prop,
caller,
ReactPropTypeLocations.prop,
null,
ReactPropTypesSecret
);
if (error) {
styleError(error.message, style, caller);
} else {
var error = allStylePropTypes[prop](
style,
prop,
caller,
ReactPropTypeLocations.prop,
null,
ReactPropTypesSecret
);
if (error) {
styleError(error.message, style, caller);
}
}
}
}
@@ -73,7 +74,8 @@ StyleSheetValidation.addValidStylePropTypes({
display: PropTypes.string,
float: PropTypes.oneOf([ 'left', 'none', 'right' ]),
font: PropTypes.string, /* @private */
listStyle: PropTypes.string
listStyle: PropTypes.string,
WebkitOverflowScrolling: PropTypes.string /* @private */
})
module.exports = StyleSheetValidation

View File

@@ -1,13 +1,13 @@
/* eslint-env mocha */
import assert from 'assert'
import createReactStyleObject from '../createReactStyleObject'
import assert from 'assert';
import createReactStyleObject from '../createReactStyleObject';
suite('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 }
const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 };
const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 };
assert.deepEqual(createReactStyleObject(reactNativeStyle), expectedStyle)
})
})
assert.deepEqual(createReactStyleObject(reactNativeStyle), expectedStyle);
});
});

View File

@@ -1,7 +1,7 @@
/* eslint-env mocha */
import assert from 'assert'
import expandStyle from '../expandStyle'
import assert from 'assert';
import expandStyle from '../expandStyle';
suite('apis/StyleSheet/expandStyle', () => {
test('shortform -> longform', () => {
@@ -14,7 +14,7 @@ suite('apis/StyleSheet/expandStyle', () => {
marginTop: 50,
marginVertical: 25,
margin: 10
}
};
const expected = {
borderBottomStyle: 'solid',
@@ -31,36 +31,36 @@ suite('apis/StyleSheet/expandStyle', () => {
marginBottom: '25px',
marginLeft: '10px',
marginRight: '10px'
}
};
assert.deepEqual(expandStyle(initial), expected)
})
assert.deepEqual(expandStyle(initial), expected);
});
test('textAlignVertical', () => {
const initial = {
textAlignVertical: 'center'
}
};
const expected = {
verticalAlign: 'middle'
}
};
assert.deepEqual(expandStyle(initial), expected)
})
assert.deepEqual(expandStyle(initial), expected);
});
test('flex', () => {
const value = 10
const value = 10;
const initial = {
flex: value
}
};
const expected = {
flexGrow: value,
flexShrink: 1,
flexBasis: 'auto'
}
};
assert.deepEqual(expandStyle(initial), expected)
})
})
assert.deepEqual(expandStyle(initial), expected);
});
});

View File

@@ -1,8 +1,8 @@
/* eslint-env mocha */
import assert from 'assert'
import I18nManager from '../../I18nManager'
import i18nStyle from '../i18nStyle'
import assert from 'assert';
import I18nManager from '../../I18nManager';
import i18nStyle from '../i18nStyle';
const initial = {
borderLeftColor: 'red',
@@ -24,13 +24,13 @@ const initial = {
textAlign: 'left',
textShadowOffset: { width: '1rem', height: 10 },
writingDirection: 'ltr'
}
};
const initialNoI18n = Object.keys(initial).reduce((acc, prop) => {
const newProp = `${prop}$noI18n`
acc[newProp] = initial[prop]
return acc
}, {})
const newProp = `${prop}$noI18n`;
acc[newProp] = initial[prop];
return acc;
}, {});
const expected = {
borderLeftColor: 'blue',
@@ -52,40 +52,40 @@ const expected = {
textAlign: 'right',
textShadowOffset: { width: '-1rem', height: 10 },
writingDirection: 'rtl'
}
};
suite('apis/StyleSheet/i18nStyle', () => {
suite('LTR mode', () => {
setup(() => {
I18nManager.allowRTL(false)
})
I18nManager.allowRTL(false);
});
teardown(() => {
I18nManager.allowRTL(true)
})
I18nManager.allowRTL(true);
});
test('does not auto-flip', () => {
assert.deepEqual(i18nStyle(initial), initial)
})
assert.deepEqual(i18nStyle(initial), initial);
});
test('normalizes properties', () => {
assert.deepEqual(i18nStyle(initialNoI18n), initial)
})
})
assert.deepEqual(i18nStyle(initialNoI18n), initial);
});
});
suite('RTL mode', () => {
setup(() => {
I18nManager.forceRTL(true)
})
I18nManager.forceRTL(true);
});
teardown(() => {
I18nManager.forceRTL(false)
})
I18nManager.forceRTL(false);
});
test('does auto-flip', () => {
assert.deepEqual(i18nStyle(initial), expected)
})
assert.deepEqual(i18nStyle(initial), expected);
});
test('normalizes properties', () => {
assert.deepEqual(i18nStyle(initialNoI18n), initial)
})
})
})
assert.deepEqual(i18nStyle(initialNoI18n), initial);
});
});
});

View File

@@ -1,52 +1,52 @@
/* eslint-env mocha */
import assert from 'assert'
import { getDefaultStyleSheet } from '../css'
import isPlainObject from 'lodash/isPlainObject'
import StyleSheet from '..'
import assert from 'assert';
import { getDefaultStyleSheet } from '../css';
import isPlainObject from 'lodash/isPlainObject';
import StyleSheet from '..';
suite('apis/StyleSheet', () => {
setup(() => {
StyleSheet._reset()
})
StyleSheet._reset();
});
test('absoluteFill', () => {
assert(Number.isInteger(StyleSheet.absoluteFill) === true)
})
assert(Number.isInteger(StyleSheet.absoluteFill) === true);
});
test('absoluteFillObject', () => {
assert.ok(isPlainObject(StyleSheet.absoluteFillObject) === true)
})
assert.ok(isPlainObject(StyleSheet.absoluteFillObject) === true);
});
suite('create', () => {
test('replaces styles with numbers', () => {
const style = StyleSheet.create({ root: { opacity: 1 } })
assert(Number.isInteger(style.root) === true)
})
const style = StyleSheet.create({ root: { opacity: 1 } });
assert(Number.isInteger(style.root) === true);
});
test('renders a style sheet in the browser', () => {
StyleSheet.create({ root: { color: 'red' } })
StyleSheet.create({ root: { color: 'red' } });
assert.equal(
document.getElementById('__react-native-style').textContent,
getDefaultStyleSheet()
)
})
})
);
});
});
test('flatten', () => {
assert(typeof StyleSheet.flatten === 'function')
})
assert(typeof StyleSheet.flatten === 'function');
});
test('hairlineWidth', () => {
assert(Number.isInteger(StyleSheet.hairlineWidth) === true)
})
assert(Number.isInteger(StyleSheet.hairlineWidth) === true);
});
test('render', () => {
assert.equal(
StyleSheet.render().props.dangerouslySetInnerHTML.__html,
getDefaultStyleSheet()
)
})
);
});
test('resolve', () => {
assert.deepEqual(
@@ -66,6 +66,6 @@ suite('apis/StyleSheet', () => {
pointerEvents: null
}
}
)
})
})
);
});
});

View File

@@ -1,14 +1,14 @@
/* eslint-env mocha */
import assert from 'assert'
import normalizeValue from '../normalizeValue'
import assert from 'assert';
import normalizeValue from '../normalizeValue';
suite('apis/StyleSheet/normalizeValue', () => {
test('normalizes property values requiring units', () => {
assert.deepEqual(normalizeValue('margin', 0), '0px')
})
assert.deepEqual(normalizeValue('margin', 0), '0px');
});
test('ignores unitless property values', () => {
assert.deepEqual(normalizeValue('flexGrow', 1), 1)
assert.deepEqual(normalizeValue('scale', 2), 2)
})
})
assert.deepEqual(normalizeValue('flexGrow', 1), 1);
assert.deepEqual(normalizeValue('scale', 2), 2);
});
});

View File

@@ -1,7 +1,7 @@
/* eslint-env mocha */
import assert from 'assert'
import processTextShadow from '../processTextShadow'
import assert from 'assert';
import processTextShadow from '../processTextShadow';
suite('apis/StyleSheet/processTextShadow', () => {
test('textShadowOffset', () => {
@@ -9,7 +9,7 @@ suite('apis/StyleSheet/processTextShadow', () => {
textShadowColor: 'red',
textShadowOffset: { width: 2, height: 2 },
textShadowRadius: 5
}
};
assert.deepEqual(
processTextShadow(style),
@@ -19,6 +19,6 @@ suite('apis/StyleSheet/processTextShadow', () => {
textShadowOffset: null,
textShadowRadius: null
}
)
})
})
);
});
});

View File

@@ -1,7 +1,7 @@
/* eslint-env mocha */
import assert from 'assert'
import processTransform from '../processTransform'
import assert from 'assert';
import processTransform from '../processTransform';
suite('apis/StyleSheet/processTransform', () => {
test('transform', () => {
@@ -11,18 +11,18 @@ suite('apis/StyleSheet/processTransform', () => {
{ translateX: 20 },
{ rotate: '20deg' }
]
}
};
assert.deepEqual(
processTransform(style),
{ transform: 'scaleX(20) translateX(20px) rotate(20deg)' }
)
})
);
});
test('transformMatrix', () => {
const style = {
transformMatrix: [ 1, 2, 3, 4, 5, 6 ]
}
};
assert.deepEqual(
processTransform(style),
@@ -30,6 +30,6 @@ suite('apis/StyleSheet/processTransform', () => {
transform: 'matrix3d(1,2,3,4,5,6)',
transformMatrix: null
}
)
})
})
);
});
});

View File

@@ -1,17 +1,17 @@
/* eslint-env mocha */
import assert from 'assert'
import processVendorPrefixes from '../processVendorPrefixes'
import assert from 'assert';
import processVendorPrefixes from '../processVendorPrefixes';
suite('apis/StyleSheet/processVendorPrefixes', () => {
test('handles array values', () => {
const style = {
display: [ '-webkit-flex', 'flex' ]
}
};
assert.deepEqual(
processVendorPrefixes(style),
{ display: 'flex' }
)
})
})
);
});
});

View File

@@ -1,20 +1,20 @@
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'
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 plugins = [
const processors = [
processTextShadow,
processTransform,
processVendorPrefixes
]
];
const applyPlugins = (style) => {
return plugins.reduce((style, plugin) => plugin(style), style)
}
const applyProcessors = (style) => processors.reduce((style, processor) => processor(style), style);
const createReactDOMStyleObject = (reactNativeStyle) => applyPlugins(expandStyle(i18nStyle(flattenStyle(reactNativeStyle))))
const createReactDOMStyleObject = (reactNativeStyle) => applyProcessors(
expandStyle(i18nStyle(flattenStyle(reactNativeStyle)))
);
module.exports = createReactDOMStyleObject
module.exports = createReactDOMStyleObject;

View File

@@ -1,23 +1,25 @@
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'
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[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {display:none}'
'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 {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}`
`.${POINTER_EVENTS_NONE_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME} *, .${POINTER_EVENTS_NONE_CLASSNAME} {pointer-events:none}`;
/* eslint-enable max-len */
const styleAsClassName = {
display: {
@@ -29,10 +31,10 @@ const styleAsClassName = {
'box-only': POINTER_EVENTS_BOX_ONLY_CLASSNAME,
'none': POINTER_EVENTS_NONE_CLASSNAME
}
}
};
export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`
export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`;
export const getStyleAsHelperClassName = (prop, value) => {
return styleAsClassName[prop] && styleAsClassName[prop][value]
}
return styleAsClassName[prop] && styleAsClassName[prop][value];
};

View File

@@ -9,9 +9,9 @@
* longfrom equivalents.
*/
import normalizeValue from './normalizeValue'
import normalizeValue from './normalizeValue';
const emptyObject = {}
const emptyObject = {};
const styleShortFormProperties = {
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
@@ -26,48 +26,48 @@ const styleShortFormProperties = {
paddingVertical: [ 'paddingTop', 'paddingBottom' ],
textDecorationLine: [ 'textDecoration' ],
writingDirection: [ 'direction' ]
}
};
const alphaSort = (arr) => arr.sort((a, b) => {
if (a < b) { return -1 }
if (a > b) { return 1 }
return 0
})
if (a < b) { return -1; }
if (a > b) { return 1; }
return 0;
});
const createStyleReducer = (originalStyle) => {
const originalStyleProps = Object.keys(originalStyle)
const originalStyleProps = Object.keys(originalStyle);
return (style, prop) => {
const value = normalizeValue(prop, originalStyle[prop])
const longFormProperties = styleShortFormProperties[prop]
const value = normalizeValue(prop, originalStyle[prop]);
const longFormProperties = styleShortFormProperties[prop];
// React Native treats `flex:1` like `flex:1 1 auto`
if (prop === 'flex') {
style.flexGrow = value
style.flexShrink = 1
style.flexBasis = 'auto'
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)
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
style[longForm] = value;
}
})
});
} else {
style[prop] = value
style[prop] = value;
}
return style
}
}
return style;
};
};
const expandStyle = (style = emptyObject) => {
const sortedStyleProps = alphaSort(Object.keys(style))
const styleReducer = createStyleReducer(style)
return sortedStyleProps.reduce(styleReducer, {})
}
const sortedStyleProps = alphaSort(Object.keys(style));
const styleReducer = createStyleReducer(style);
return sortedStyleProps.reduce(styleReducer, {});
};
module.exports = expandStyle
module.exports = expandStyle;

View File

@@ -1,6 +1,5 @@
import I18nManager from '../I18nManager'
const CSS_UNIT_RE = /^[+-]?\d*(?:\.\d+)?(?:[Ee][+-]?\d+)?(\w*)/
import I18nManager from '../I18nManager';
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue';
/**
* Map of property names to their BiDi equivalent.
@@ -22,103 +21,86 @@ const PROPERTIES_TO_SWAP = {
'paddingLeft': 'paddingRight',
'paddingRight': 'paddingLeft',
'right': 'left'
}
};
const PROPERTIES_SWAP_LEFT_RIGHT = {
'clear': true,
'float': true,
'textAlign': true
}
};
const PROPERTIES_SWAP_LTR_RTL = {
'writingDirection': true
}
};
/**
* Invert the sign of a numeric-like value
*/
const additiveInverse = (value: String | Number) => {
if (typeof value === 'string') {
const number = parseFloat(value, 10) * -1
const unit = getUnit(value)
return `${number}${unit}`
} else if (isNumeric(value)) {
return value * -1
}
}
const additiveInverse = (value: String | Number) => multiplyStyleLengthValue(value, -1);
/**
* BiDi flip the given property.
*/
const flipProperty = (prop:String): String => {
return PROPERTIES_TO_SWAP.hasOwnProperty(prop) ? PROPERTIES_TO_SWAP[prop] : prop
}
return PROPERTIES_TO_SWAP.hasOwnProperty(prop) ? PROPERTIES_TO_SWAP[prop] : prop;
};
/**
* BiDi flip translateX
*/
const flipTransform = (transform: Object): Object => {
const translateX = transform.translateX
const translateX = transform.translateX;
if (translateX != null) {
transform.translateX = additiveInverse(translateX)
transform.translateX = additiveInverse(translateX);
}
return transform
}
/**
* Get the CSS unit for string values
*/
const getUnit = (str) => str.match(CSS_UNIT_RE)[1]
const isNumeric = (n) => {
return !isNaN(parseFloat(n)) && isFinite(n)
}
return transform;
};
const swapLeftRight = (value:String): String => {
return value === 'left' ? 'right' : value === 'right' ? 'left' : value
}
return value === 'left' ? 'right' : value === 'right' ? 'left' : value;
};
const swapLtrRtl = (value:String): String => {
return value === 'ltr' ? 'rtl' : value === 'rtl' ? 'ltr' : value
}
return value === 'ltr' ? 'rtl' : value === 'rtl' ? 'ltr' : value;
};
const i18nStyle = (style = {}) => {
const newStyle = {}
const newStyle = {};
for (const prop in style) {
if (style.hasOwnProperty(prop)) {
const indexOfNoFlip = prop.indexOf('$noI18n')
const indexOfNoFlip = prop.indexOf('$noI18n');
if (I18nManager.isRTL) {
if (PROPERTIES_TO_SWAP[prop]) {
const newProp = flipProperty(prop)
newStyle[newProp] = style[prop]
const newProp = flipProperty(prop);
newStyle[newProp] = style[prop];
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
newStyle[prop] = swapLeftRight(style[prop])
newStyle[prop] = swapLeftRight(style[prop]);
} else if (PROPERTIES_SWAP_LTR_RTL[prop]) {
newStyle[prop] = swapLtrRtl(style[prop])
newStyle[prop] = swapLtrRtl(style[prop]);
} else if (prop === 'textShadowOffset') {
newStyle[prop] = style[prop]
newStyle[prop].width = additiveInverse(style[prop].width)
newStyle[prop] = style[prop];
newStyle[prop].width = additiveInverse(style[prop].width);
} else if (prop === 'transform') {
newStyle[prop] = style[prop].map(flipTransform)
newStyle[prop] = style[prop].map(flipTransform);
} else if (indexOfNoFlip > -1) {
const newProp = prop.substring(0, indexOfNoFlip)
newStyle[newProp] = style[prop]
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]
const newProp = prop.substring(0, indexOfNoFlip);
newStyle[newProp] = style[prop];
} else {
newStyle[prop] = style[prop]
newStyle[prop] = style[prop];
}
}
}
}
return newStyle
}
return newStyle;
};
module.exports = i18nStyle
module.exports = i18nStyle;

View File

@@ -1,32 +1,32 @@
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 StyleSheetValidation from './StyleSheetValidation'
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 StyleSheetValidation from './StyleSheetValidation';
let styleElement
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM
let styleElement;
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM;
const STYLE_SHEET_ID = '__react-native-style'
const STYLE_SHEET_ID = '__react-native-style';
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 };
const defaultStyleSheet = css.getDefaultStyleSheet()
const defaultStyleSheet = css.getDefaultStyleSheet();
const insertStyleSheet = () => {
// check if the server rendered the style sheet
styleElement = document.getElementById(STYLE_SHEET_ID)
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
);
shouldInsertStyleSheet = false;
}
}
};
module.exports = {
/**
@@ -35,9 +35,9 @@ module.exports = {
*/
_reset() {
if (styleElement) {
document.head.removeChild(styleElement)
styleElement = null
shouldInsertStyleSheet = true
document.head.removeChild(styleElement);
styleElement = null;
shouldInsertStyleSheet = true;
}
},
@@ -47,15 +47,15 @@ module.exports = {
create(styles) {
if (shouldInsertStyleSheet) {
insertStyleSheet()
insertStyleSheet();
}
const result = {}
const result = {};
for (const key in styles) {
StyleSheetValidation.validateStyle(key, styles)
result[key] = ReactNativePropRegistry.register(styles[key])
StyleSheetValidation.validateStyle(key, styles);
result[key] = ReactNativePropRegistry.register(styles[key]);
}
return result
return result;
},
hairlineWidth: 1,
@@ -64,7 +64,7 @@ module.exports = {
/* @platform web */
render() {
return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />
return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />;
},
/**
@@ -72,17 +72,17 @@ module.exports = {
* @platform web
*/
resolve(props) {
let className = props.className || ''
let style = createReactStyleObject(props.style)
let className = props.className || '';
const style = createReactStyleObject(props.style);
for (const prop in style) {
const value = style[prop]
const replacementClassName = css.getStyleAsHelperClassName(prop, value)
const value = style[prop];
const replacementClassName = css.getStyleAsHelperClassName(prop, value);
if (replacementClassName) {
className += ` ${replacementClassName}`
style[prop] = null
className += ` ${replacementClassName}`;
style[prop] = null;
}
}
return { className, style }
return { className, style };
}
}
};

View File

@@ -25,13 +25,13 @@ const unitlessNumbers = {
scaleX: true,
scaleY: true,
scaleZ: true
}
};
const normalizeValue = (property, value) => {
if (!unitlessNumbers[property] && typeof value === 'number') {
value = `${value}px`
value = `${value}px`;
}
return value
}
return value;
};
module.exports = normalizeValue
module.exports = normalizeValue;

View File

@@ -1,19 +1,19 @@
import normalizeValue from './normalizeValue'
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'
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
style.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
style.textShadowColor = null;
style.textShadowOffset = null;
style.textShadowRadius = null;
}
return style
}
return style;
};
module.exports = processTextShadow
module.exports = processTextShadow;

View File

@@ -1,29 +1,29 @@
import normalizeValue from './normalizeValue'
import normalizeValue from './normalizeValue';
// { scale: 2 } => 'scale(2)'
// { translateX: 20 } => 'translateX(20px)'
const mapTransform = (transform) => {
const type = Object.keys(transform)[0]
const value = normalizeValue(type, transform[type])
return `${type}(${value})`
}
const type = Object.keys(transform)[0];
const value = normalizeValue(type, transform[type]);
return `${type}(${value})`;
};
// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)'
const convertTransformMatrix = (transformMatrix) => {
var matrix = transformMatrix.join(',')
return `matrix3d(${matrix})`
}
const matrix = transformMatrix.join(',');
return `matrix3d(${matrix})`;
};
const processTransform = (style) => {
if (style) {
if (style.transform) {
style.transform = style.transform.map(mapTransform).join(' ')
style.transform = style.transform.map(mapTransform).join(' ');
} else if (style.transformMatrix) {
style.transform = convertTransformMatrix(style.transformMatrix)
style.transformMatrix = null
style.transform = convertTransformMatrix(style.transformMatrix);
style.transformMatrix = null;
}
}
return style
}
return style;
};
module.exports = processTransform
module.exports = processTransform;

View File

@@ -1,16 +1,16 @@
import prefixAll from 'inline-style-prefixer/static'
import prefixAll from 'inline-style-prefixer/static';
const processVendorPrefixes = (style) => {
let prefixedStyles = prefixAll(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) {
const value = prefixedStyles[prop]
const value = prefixedStyles[prop];
if (Array.isArray(value)) {
prefixedStyles[prop] = value[value.length - 1]
prefixedStyles[prop] = value[value.length - 1];
}
}
return prefixedStyles
}
return prefixedStyles;
};
module.exports = processVendorPrefixes
module.exports = processVendorPrefixes;

View File

@@ -1,97 +1,97 @@
/* eslint-env mocha */
import assert from 'assert'
import UIManager from '..'
import assert from 'assert';
import UIManager from '..';
const createNode = (style = {}) => {
const root = document.createElement('div')
const root = document.createElement('div');
Object.keys(style).forEach((prop) => {
root.style[prop] = style[prop]
})
return root
}
root.style[prop] = style[prop];
});
return root;
};
let defaultBodyMargin
let defaultBodyMargin;
suite('apis/UIManager', () => {
setup(() => {
// remove default body margin so we can predict the measured offsets
defaultBodyMargin = document.body.style.margin
document.body.style.margin = 0
})
defaultBodyMargin = document.body.style.margin;
document.body.style.margin = 0;
});
teardown(() => {
document.body.style.margin = defaultBodyMargin
})
document.body.style.margin = defaultBodyMargin;
});
suite('measure', () => {
test('provides correct layout to callback', () => {
const node = createNode({ height: '5000px', left: '100px', position: 'relative', top: '100px', width: '5000px' })
document.body.appendChild(node)
const node = createNode({ height: '5000px', left: '100px', position: 'relative', top: '100px', width: '5000px' });
document.body.appendChild(node);
UIManager.measure(node, (x, y, width, height, pageX, pageY) => {
assert.equal(x, 100)
assert.equal(y, 100)
assert.equal(width, 5000)
assert.equal(height, 5000)
assert.equal(pageX, 100)
assert.equal(pageY, 100)
})
assert.equal(x, 100);
assert.equal(y, 100);
assert.equal(width, 5000);
assert.equal(height, 5000);
assert.equal(pageX, 100);
assert.equal(pageY, 100);
});
// test values account for scroll position
window.scrollTo(200, 200)
window.scrollTo(200, 200);
UIManager.measure(node, (x, y, width, height, pageX, pageY) => {
assert.equal(x, 100)
assert.equal(y, 100)
assert.equal(width, 5000)
assert.equal(height, 5000)
assert.equal(pageX, -100)
assert.equal(pageY, -100)
})
assert.equal(x, 100);
assert.equal(y, 100);
assert.equal(width, 5000);
assert.equal(height, 5000);
assert.equal(pageX, -100);
assert.equal(pageY, -100);
});
document.body.removeChild(node)
})
})
document.body.removeChild(node);
});
});
suite('measureLayout', () => {
test('provides correct layout to onSuccess callback', () => {
const node = createNode({ height: '10px', width: '10px' })
const middle = createNode({ padding: '20px' })
const context = createNode({ padding: '20px' })
middle.appendChild(node)
context.appendChild(middle)
document.body.appendChild(context)
const node = createNode({ height: '10px', width: '10px' });
const middle = createNode({ padding: '20px' });
const context = createNode({ padding: '20px' });
middle.appendChild(node);
context.appendChild(middle);
document.body.appendChild(context);
UIManager.measureLayout(node, context, () => {}, (x, y, width, height) => {
assert.equal(x, 40)
assert.equal(y, 40)
assert.equal(width, 10)
assert.equal(height, 10)
})
assert.equal(x, 40);
assert.equal(y, 40);
assert.equal(width, 10);
assert.equal(height, 10);
});
document.body.removeChild(context)
})
})
document.body.removeChild(context);
});
});
suite('measureInWindow', () => {
test('provides correct layout to callback', () => {
const node = createNode({ height: '10px', width: '10px' })
const middle = createNode({ padding: '20px' })
const context = createNode({ padding: '20px' })
middle.appendChild(node)
context.appendChild(middle)
document.body.appendChild(context)
const node = createNode({ height: '10px', width: '10px' });
const middle = createNode({ padding: '20px' });
const context = createNode({ padding: '20px' });
middle.appendChild(node);
context.appendChild(middle);
document.body.appendChild(context);
UIManager.measureInWindow(node, (x, y, width, height) => {
assert.equal(x, 40)
assert.equal(y, 40)
assert.equal(width, 10)
assert.equal(height, 10)
})
assert.equal(x, 40);
assert.equal(y, 40);
assert.equal(width, 10);
assert.equal(height, 10);
});
document.body.removeChild(context)
})
})
document.body.removeChild(context);
});
});
suite('updateView', () => {
const componentStub = {
@@ -99,42 +99,42 @@ suite('apis/UIManager', () => {
_currentElement: { _owner: {} },
_debugID: 1
}
}
};
test('add new className to existing className', () => {
const node = createNode()
node.className = 'existing'
const props = { className: 'extra' }
UIManager.updateView(node, props, componentStub)
assert.equal(node.getAttribute('class'), 'existing extra')
})
const node = createNode();
node.className = 'existing';
const props = { className: 'extra' };
UIManager.updateView(node, props, componentStub);
assert.equal(node.getAttribute('class'), 'existing extra');
});
test('adds correct DOM styles to existing style', () => {
const node = createNode({ color: 'red' })
const props = { style: { marginVertical: 0, opacity: 0 } }
UIManager.updateView(node, props, componentStub)
assert.equal(node.getAttribute('style'), 'color: red; margin-top: 0px; margin-bottom: 0px; opacity: 0;')
})
const node = createNode({ color: 'red' });
const props = { style: { marginVertical: 0, opacity: 0 } };
UIManager.updateView(node, props, componentStub);
assert.equal(node.getAttribute('style'), 'color: red; margin-top: 0px; margin-bottom: 0px; opacity: 0;');
});
test('replaces input and textarea text', () => {
const node = createNode()
node.value = 'initial'
const textProp = { text: 'expected-text' }
const valueProp = { value: 'expected-value' }
const node = createNode();
node.value = 'initial';
const textProp = { text: 'expected-text' };
const valueProp = { value: 'expected-value' };
UIManager.updateView(node, textProp)
assert.equal(node.value, 'expected-text')
UIManager.updateView(node, textProp);
assert.equal(node.value, 'expected-text');
UIManager.updateView(node, valueProp)
assert.equal(node.value, 'expected-value')
})
UIManager.updateView(node, valueProp);
assert.equal(node.value, 'expected-value');
});
test('sets other attribute values', () => {
const node = createNode()
const props = { 'aria-level': '4', 'data-of-type': 'string' }
UIManager.updateView(node, props)
assert.equal(node.getAttribute('aria-level'), '4')
assert.equal(node.getAttribute('data-of-type'), 'string')
})
})
})
const node = createNode();
const props = { 'aria-level': '4', 'data-of-type': 'string' };
UIManager.updateView(node, props);
assert.equal(node.getAttribute('aria-level'), '4');
assert.equal(node.getAttribute('data-of-type'), 'string');
});
});
});

View File

@@ -1,41 +1,41 @@
import createReactStyleObject from '../StyleSheet/createReactStyleObject'
import CSSPropertyOperations from 'react/lib/CSSPropertyOperations'
import createReactStyleObject from '../StyleSheet/createReactStyleObject';
import CSSPropertyOperations from 'react/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)
}
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 = {
blur(node) {
try { node.blur() } catch (err) {}
try { node.blur(); } catch (err) {}
},
focus(node) {
try { node.focus() } catch (err) {}
try { node.focus(); } catch (err) {}
},
measure(node, callback) {
_measureLayout(node, null, callback)
_measureLayout(node, null, callback);
},
measureInWindow(node, callback) {
const { height, left, top, width } = node.getBoundingClientRect()
callback(left, top, width, height)
const { height, left, top, width } = node.getBoundingClientRect();
callback(left, top, width, height);
},
measureLayout(node, relativeToNativeNode, onFail, onSuccess) {
const relativeTo = relativeToNativeNode || node.parentNode
_measureLayout(node, relativeTo, onSuccess)
const relativeTo = relativeToNativeNode || node.parentNode;
_measureLayout(node, relativeTo, onSuccess);
},
updateView(node, props, component /* only needed to surpress React errors in development */) {
for (const prop in props) {
const value = props[prop]
const value = props[prop];
switch (prop) {
case 'style':
@@ -44,26 +44,26 @@ const UIManager = {
node,
createReactStyleObject(value),
component._reactInternalInstance
)
break
);
break;
case 'class':
case 'className': {
const nativeProp = 'class'
const nativeProp = 'class';
// prevent class names managed by React Native from being replaced
const className = node.getAttribute(nativeProp) + ' ' + value
node.setAttribute(nativeProp, className)
break
const className = `${node.getAttribute(nativeProp)} ${value}`;
node.setAttribute(nativeProp, className);
break;
}
case 'text':
case 'value':
// native platforms use `text` prop to replace text input value
node.value = value
break
node.value = value;
break;
default:
node.setAttribute(prop, value)
node.setAttribute(prop, value);
}
}
}
}
};
module.exports = UIManager
module.exports = UIManager;

View File

@@ -1,20 +1,20 @@
const vibrate = (pattern) => {
if ('vibrate' in window.navigator) {
if (typeof pattern === 'number' || Array.isArray(pattern)) {
window.navigator.vibrate(pattern)
window.navigator.vibrate(pattern);
} else {
throw new Error('Vibration pattern should be a number or array')
throw new Error('Vibration pattern should be a number or array');
}
}
}
};
const Vibration = {
cancel() {
vibrate(0)
vibrate(0);
},
vibrate(pattern) {
vibrate(pattern)
vibrate(pattern);
}
}
};
module.exports = Vibration
module.exports = Vibration;

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('components/ActivityIndicator', () => {
test.skip('NO TEST COVERAGE', () => {})
})
test.skip('NO TEST COVERAGE', () => {});
});

View File

@@ -1,44 +1,43 @@
import Animated from '../../apis/Animated'
import applyNativeMethods from '../../modules/applyNativeMethods'
import Easing from 'animated/lib/Easing'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import View from '../View'
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 GRAY = '#999999'
const opacityInterpolation = { inputRange: [ 0, 1 ], outputRange: [ 0.5, 1 ] }
const scaleInterpolation = { inputRange: [ 0, 1 ], outputRange: [ 0.95, 1 ] }
const rotationInterpolation = { inputRange: [ 0, 1 ], outputRange: [ '0deg', '360deg' ] };
class ActivityIndicator extends Component {
static displayName = 'ActivityIndicator';
static propTypes = {
...View.propTypes,
animating: PropTypes.bool,
color: PropTypes.string,
hidesWhenStopped: PropTypes.bool,
size: PropTypes.oneOf([ 'small', 'large' ]),
style: View.propTypes.style
size: PropTypes.oneOfType([ PropTypes.oneOf([ 'small', 'large' ]), PropTypes.number ])
};
static defaultProps = {
animating: true,
color: GRAY,
color: '#1976D2',
hidesWhenStopped: true,
size: 'small',
style: {}
size: 'small'
};
constructor(props) {
super(props)
super(props);
this.state = {
animation: new Animated.Value(1)
}
animation: new Animated.Value(0)
};
}
componentDidMount() {
this._manageAnimation()
this._manageAnimation();
}
componentDidUpdate() {
this._manageAnimation()
this._manageAnimation();
}
render() {
@@ -49,53 +48,85 @@ class ActivityIndicator extends Component {
size,
style,
...other
} = this.props
} = this.props;
const { animation } = this.state
const { animation } = this.state;
const svg = (
<svg height='100%' viewBox='0 0 32 32' width='100%'>
<circle
cx='16'
cy='16'
fill='none'
r='14'
strokeWidth='4'
style={{
stroke: color,
opacity: 0.2
}}
/>
<circle
cx='16'
cy='16'
fill='none'
r='14'
strokeWidth='4'
style={{
stroke: color,
strokeDasharray: 80,
strokeDashoffset: 60
}}
/>
</svg>
);
return (
<View {...other} style={[ styles.container, style ]}>
<View {...other}
accessibilityRole='progressbar'
aria-valuemax='1'
aria-valuemin='0'
style={[
styles.container,
style,
size && { height: size, width: size }
]}
>
<Animated.View
children={svg}
style={[
indicatorStyles[size],
hidesWhenStopped && !animating && styles.hidesWhenStopped,
{
borderColor: color,
opacity: animation.interpolate(opacityInterpolation),
transform: [ { scale: animation.interpolate(scaleInterpolation) } ]
transform: [
{ rotate: animation.interpolate(rotationInterpolation) }
]
}
]}
/>
</View>
)
);
}
_manageAnimation() {
const { animation } = this.state
const { animation } = this.state;
const cycleAnimation = () => {
Animated.sequence([
Animated.timing(animation, {
duration: 600,
easing: Easing.inOut(Easing.ease),
toValue: 0
}),
Animated.timing(animation, {
duration: 600,
easing: Easing.inOut(Easing.ease),
toValue: 1
})
]).start((event) => {
animation.setValue(0);
Animated.timing(animation, {
duration: 750,
easing: Easing.inOut(Easing.linear),
toValue: 1
}).start((event) => {
if (event.finished) {
cycleAnimation()
cycleAnimation();
}
})
}
});
};
if (this.props.animating) {
cycleAnimation()
cycleAnimation();
} else {
animation.stopAnimation()
animation.stopAnimation();
}
}
}
@@ -108,21 +139,17 @@ const styles = StyleSheet.create({
hidesWhenStopped: {
visibility: 'hidden'
}
})
});
const indicatorStyles = StyleSheet.create({
small: {
borderRadius: 100,
borderWidth: 3,
width: 20,
height: 20
},
large: {
borderRadius: 100,
borderWidth: 4,
width: 36,
height: 36
}
})
});
module.exports = applyNativeMethods(ActivityIndicator)
module.exports = applyNativeMethods(ActivityIndicator);

View File

@@ -1,4 +1,4 @@
import keyMirror from 'fbjs/lib/keyMirror'
import keyMirror from 'fbjs/lib/keyMirror';
const ImageResizeMode = keyMirror({
center: null,
@@ -7,6 +7,6 @@ const ImageResizeMode = keyMirror({
none: null,
repeat: null,
stretch: null
})
});
module.exports = ImageResizeMode
module.exports = ImageResizeMode;

View File

@@ -1,11 +1,11 @@
import { PropTypes } from 'react'
import BorderPropTypes from '../../propTypes/BorderPropTypes'
import ColorPropType from '../../propTypes/ColorPropType'
import LayoutPropTypes from '../../propTypes/LayoutPropTypes'
import TransformPropTypes from '../../propTypes/TransformPropTypes'
import ImageResizeMode from './ImageResizeMode'
import BorderPropTypes from '../../propTypes/BorderPropTypes';
import ColorPropType from '../../propTypes/ColorPropType';
import ImageResizeMode from './ImageResizeMode';
import LayoutPropTypes from '../../propTypes/LayoutPropTypes';
import { PropTypes } from 'react';
import TransformPropTypes from '../../propTypes/TransformPropTypes';
const hiddenOrVisible = PropTypes.oneOf([ 'hidden', 'visible' ])
const hiddenOrVisible = PropTypes.oneOf([ 'hidden', 'visible' ]);
module.exports = {
...BorderPropTypes,
@@ -24,4 +24,4 @@ module.exports = {
* @platform web
*/
visibility: hiddenOrVisible
}
};

View File

@@ -1,162 +1,161 @@
/* eslint-env mocha */
import { mount, shallow } from 'enzyme'
import assert from 'assert'
import React from 'react'
import StyleSheet from '../../../apis/StyleSheet'
import Image from '../'
import assert from 'assert';
import Image from '../';
import React from 'react';
import StyleSheet from '../../../apis/StyleSheet';
import { mount, shallow } from 'enzyme';
suite('components/Image', () => {
test('sets correct accessibility role"', () => {
const image = shallow(<Image />)
assert.equal(image.prop('accessibilityRole'), 'img')
})
const image = shallow(<Image />);
assert.equal(image.prop('accessibilityRole'), 'img');
});
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const image = shallow(<Image accessibilityLabel={accessibilityLabel} />)
assert.equal(image.prop('accessibilityLabel'), accessibilityLabel)
})
const accessibilityLabel = 'accessibilityLabel';
const image = shallow(<Image accessibilityLabel={accessibilityLabel} />);
assert.equal(image.prop('accessibilityLabel'), accessibilityLabel);
});
test('prop "accessible"', () => {
const accessible = false
const image = shallow(<Image accessible={accessible} />)
assert.equal(image.prop('accessible'), accessible)
})
const accessible = false;
const image = shallow(<Image accessible={accessible} />);
assert.equal(image.prop('accessible'), accessible);
});
test('prop "children"', () => {
const children = <div className='unique' />
const wrapper = shallow(<Image>{children}</Image>)
assert.equal(wrapper.contains(children), true)
})
const children = <div className='unique' />;
const wrapper = shallow(<Image>{children}</Image>);
assert.equal(wrapper.contains(children), true);
});
suite('prop "defaultSource"', () => {
test('sets background image when value is an object', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' }
const image = shallow(<Image defaultSource={defaultSource} />)
const backgroundImage = StyleSheet.flatten(image.prop('style')).backgroundImage
assert(backgroundImage.indexOf(defaultSource.uri) > -1)
})
const defaultSource = { uri: 'https://google.com/favicon.ico' };
const image = shallow(<Image defaultSource={defaultSource} />);
const backgroundImage = StyleSheet.flatten(image.prop('style')).backgroundImage;
assert(backgroundImage.indexOf(defaultSource.uri) > -1);
});
test('sets background image when value is a string', () => {
// emulate require-ed asset
const defaultSource = 'https://google.com/favicon.ico'
const image = shallow(<Image defaultSource={defaultSource} />)
const backgroundImage = StyleSheet.flatten(image.prop('style')).backgroundImage
assert(backgroundImage.indexOf(defaultSource) > -1)
})
})
const defaultSource = 'https://google.com/favicon.ico';
const image = shallow(<Image defaultSource={defaultSource} />);
const backgroundImage = StyleSheet.flatten(image.prop('style')).backgroundImage;
assert(backgroundImage.indexOf(defaultSource) > -1);
});
});
test('prop "onError"', function (done) {
this.timeout(5000)
mount(<Image onError={onError} source={{ uri: 'https://google.com/favicon.icox' }} />)
this.timeout(5000);
mount(<Image onError={onError} source={{ uri: 'https://google.com/favicon.icox' }} />);
function onError(e) {
assert.equal(e.nativeEvent.type, 'error')
done()
assert.equal(e.nativeEvent.type, 'error');
done();
}
})
});
test('prop "onLoad"', function (done) {
this.timeout(5000)
const image = mount(<Image onLoad={onLoad} source={{ uri: 'https://google.com/favicon.ico' }} />)
this.timeout(5000);
const image = mount(<Image onLoad={onLoad} source={{ uri: 'https://google.com/favicon.ico' }} />);
function onLoad(e) {
assert.equal(e.nativeEvent.type, 'load')
const backgroundImage = StyleSheet.flatten(image.ref('root').prop('style')).backgroundImage
assert.notDeepEqual(backgroundImage, undefined)
done()
assert.equal(e.nativeEvent.type, 'load');
const hasBackgroundImage = (image.html()).indexOf('url(&quot;https://google.com/favicon.ico&quot;)') > -1;
assert.equal(hasBackgroundImage, true);
done();
}
})
});
test('prop "onLoadEnd"', function (done) {
this.timeout(5000)
const image = mount(<Image onLoadEnd={onLoadEnd} source={{ uri: 'https://google.com/favicon.ico' }} />)
this.timeout(5000);
const image = mount(<Image onLoadEnd={onLoadEnd} source={{ uri: 'https://google.com/favicon.ico' }} />);
function onLoadEnd() {
assert.ok(true)
const backgroundImage = StyleSheet.flatten(image.ref('root').prop('style')).backgroundImage
assert.notDeepEqual(backgroundImage, undefined)
done()
assert.ok(true);
const hasBackgroundImage = (image.html()).indexOf('url(&quot;https://google.com/favicon.ico&quot;)') > -1;
assert.equal(hasBackgroundImage, true);
done();
}
})
});
test('prop "onLoadStart"', function (done) {
this.timeout(5000)
mount(<Image onLoadStart={onLoadStart} source={{ uri: 'https://google.com/favicon.ico' }} />)
this.timeout(5000);
mount(<Image onLoadStart={onLoadStart} source={{ uri: 'https://google.com/favicon.ico' }} />);
function onLoadStart() {
assert.ok(true)
done()
assert.ok(true);
done();
}
})
});
suite('prop "resizeMode"', () => {
const getBackgroundSize = (image) => StyleSheet.flatten(image.prop('style')).backgroundSize
const getBackgroundSize = (image) => StyleSheet.flatten(image.prop('style')).backgroundSize;
test('value "contain"', () => {
const image = shallow(<Image resizeMode={Image.resizeMode.contain} />)
assert.equal(getBackgroundSize(image), 'contain')
})
const image = shallow(<Image resizeMode={Image.resizeMode.contain} />);
assert.equal(getBackgroundSize(image), 'contain');
});
test('value "cover"', () => {
const image = shallow(<Image resizeMode={Image.resizeMode.cover} />)
assert.equal(getBackgroundSize(image), 'cover')
})
const image = shallow(<Image resizeMode={Image.resizeMode.cover} />);
assert.equal(getBackgroundSize(image), 'cover');
});
test('value "none"', () => {
const image = shallow(<Image resizeMode={Image.resizeMode.none} />)
assert.equal(getBackgroundSize(image), 'auto')
})
const image = shallow(<Image resizeMode={Image.resizeMode.none} />);
assert.equal(getBackgroundSize(image), 'auto');
});
test('value "stretch"', () => {
const image = shallow(<Image resizeMode={Image.resizeMode.stretch} />)
assert.equal(getBackgroundSize(image), '100% 100%')
})
const image = shallow(<Image resizeMode={Image.resizeMode.stretch} />);
assert.equal(getBackgroundSize(image), '100% 100%');
});
test('no value', () => {
const image = shallow(<Image />)
assert.equal(getBackgroundSize(image), 'cover')
})
})
const image = shallow(<Image />);
assert.equal(getBackgroundSize(image), 'cover');
});
});
suite('prop "source"', function () {
this.timeout(5000)
this.timeout(5000);
test('sets background image when value is an object', (done) => {
const source = { uri: 'https://google.com/favicon.ico' }
mount(<Image onLoad={onLoad} source={source} />)
const source = { uri: 'https://google.com/favicon.ico' };
mount(<Image onLoad={onLoad} source={source} />);
function onLoad(e) {
const src = e.nativeEvent.target.src
assert.equal(src, source.uri)
done()
const src = e.nativeEvent.target.src;
assert.equal(src, source.uri);
done();
}
})
});
test('sets background image when value is a string', (done) => {
// emulate require-ed asset
const source = 'https://google.com/favicon.ico'
mount(<Image onLoad={onLoad} source={source} />)
const source = 'https://google.com/favicon.ico';
mount(<Image onLoad={onLoad} source={source} />);
function onLoad(e) {
const src = e.nativeEvent.target.src
assert.equal(src, source)
done()
const src = e.nativeEvent.target.src;
assert.equal(src, source);
done();
}
})
})
});
});
suite('prop "style"', () => {
test('converts "resizeMode" property', () => {
const image = shallow(<Image style={{ resizeMode: Image.resizeMode.contain }} />)
assert.equal(StyleSheet.flatten(image.prop('style')).backgroundSize, 'contain')
})
const image = shallow(<Image style={{ resizeMode: Image.resizeMode.contain }} />);
assert.equal(StyleSheet.flatten(image.prop('style')).backgroundSize, 'contain');
});
test('removes "resizeMode" property', () => {
const image = shallow(<Image style={{ resizeMode: Image.resizeMode.contain }} />)
assert.equal(StyleSheet.flatten(image.prop('style')).resizeMode, undefined)
})
})
const image = shallow(<Image style={{ resizeMode: Image.resizeMode.contain }} />);
assert.equal(StyleSheet.flatten(image.prop('style')).resizeMode, undefined);
});
});
test('prop "testID"', () => {
const testID = 'testID'
const image = shallow(<Image testID={testID} />)
assert.equal(image.prop('testID'), testID)
})
})
const testID = 'testID';
const image = shallow(<Image testID={testID} />);
assert.equal(image.prop('testID'), testID);
});
});

View File

@@ -1,33 +1,33 @@
/* global window */
import applyNativeMethods from '../../modules/applyNativeMethods'
import createReactDOMComponent from '../../modules/createReactDOMComponent'
import ImageResizeMode from './ImageResizeMode'
import ImageStylePropTypes from './ImageStylePropTypes'
import resolveAssetSource from './resolveAssetSource'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
import View from '../View'
import applyNativeMethods from '../../modules/applyNativeMethods';
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes';
import createDOMElement from '../../modules/createDOMElement';
import ImageResizeMode from './ImageResizeMode';
import ImageStylePropTypes from './ImageStylePropTypes';
import resolveAssetSource from './resolveAssetSource';
import StyleSheet from '../../apis/StyleSheet';
import StyleSheetPropType from '../../propTypes/StyleSheetPropType';
import View from '../View';
import React, { Component, PropTypes } from 'react';
const STATUS_ERRORED = 'ERRORED'
const STATUS_LOADED = 'LOADED'
const STATUS_LOADING = 'LOADING'
const STATUS_PENDING = 'PENDING'
const STATUS_IDLE = 'IDLE'
const STATUS_ERRORED = 'ERRORED';
const STATUS_LOADED = 'LOADED';
const STATUS_LOADING = 'LOADING';
const STATUS_PENDING = 'PENDING';
const STATUS_IDLE = 'IDLE';
const ImageSourcePropType = PropTypes.oneOfType([
PropTypes.shape({
uri: PropTypes.string.isRequired
}),
PropTypes.string
])
]);
class Image extends Component {
static displayName = 'Image'
static displayName = 'Image';
static propTypes = {
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
accessible: createReactDOMComponent.propTypes.accessible,
...BaseComponentPropTypes,
children: PropTypes.any,
defaultSource: ImageSourcePropType,
onError: PropTypes.func,
@@ -35,10 +35,9 @@ class Image extends Component {
onLoad: PropTypes.func,
onLoadEnd: PropTypes.func,
onLoadStart: PropTypes.func,
resizeMode: PropTypes.oneOf(['center', 'contain', 'cover', 'none', 'repeat', 'stretch']),
resizeMode: PropTypes.oneOf([ 'center', 'contain', 'cover', 'none', 'repeat', 'stretch' ]),
source: ImageSourcePropType,
style: StyleSheetPropType(ImageStylePropTypes),
testID: createReactDOMComponent.propTypes.testID
style: StyleSheetPropType(ImageStylePropTypes)
};
static defaultProps = {
@@ -49,37 +48,37 @@ class Image extends Component {
static resizeMode = ImageResizeMode;
constructor(props, context) {
super(props, context)
const uri = resolveAssetSource(props.source)
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE
this.state = { isLoaded: false }
super(props, context);
const uri = resolveAssetSource(props.source);
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE;
this.state = { isLoaded: false };
}
componentDidMount() {
if (this._imageState === STATUS_PENDING) {
this._createImageLoader()
this._createImageLoader();
}
}
componentDidUpdate() {
if (this._imageState === STATUS_PENDING && !this.image) {
this._createImageLoader()
this._createImageLoader();
}
}
componentWillReceiveProps(nextProps) {
const nextUri = resolveAssetSource(nextProps.source)
const nextUri = resolveAssetSource(nextProps.source);
if (resolveAssetSource(this.props.source) !== nextUri) {
this._updateImageState(nextUri ? STATUS_PENDING : STATUS_IDLE)
this._updateImageState(nextUri ? STATUS_PENDING : STATUS_IDLE);
}
}
componentWillUnmount() {
this._destroyImageLoader()
this._destroyImageLoader();
}
render() {
const { isLoaded } = this.state
const { isLoaded } = this.state;
const {
accessibilityLabel,
accessible,
@@ -88,16 +87,16 @@ class Image extends Component {
onLayout,
source,
testID
} = this.props
} = this.props;
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source)
const backgroundImage = displayImage ? `url("${displayImage}")` : null
let style = StyleSheet.flatten(this.props.style)
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source);
const backgroundImage = displayImage ? `url("${displayImage}")` : null;
let style = StyleSheet.flatten(this.props.style);
const resizeMode = this.props.resizeMode || style.resizeMode || ImageResizeMode.cover
const resizeMode = this.props.resizeMode || style.resizeMode || ImageResizeMode.cover;
// remove 'resizeMode' style, as it is not supported by View (N.B. styles are frozen in dev)
style = process.env.NODE_ENV !== 'production' ? { ...style } : style
delete style.resizeMode
style = process.env.NODE_ENV !== 'production' ? { ...style } : style;
delete style.resizeMode;
/**
* Image is a non-stretching View. The image is displayed as a background
@@ -112,7 +111,6 @@ class Image extends Component {
accessibilityRole='img'
accessible={accessible}
onLayout={onLayout}
ref='root'
style={[
styles.initial,
style,
@@ -121,75 +119,73 @@ class Image extends Component {
]}
testID={testID}
>
{createReactDOMComponent({ component: 'img', src: displayImage, style: styles.img })}
{createDOMElement('img', { src: displayImage, style: styles.img })}
{children ? (
<View children={children} pointerEvents='box-none' style={styles.children} />
) : null}
</View>
)
);
}
_createImageLoader() {
const uri = resolveAssetSource(this.props.source)
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
this._onLoadStart()
this._destroyImageLoader();
this.image = new window.Image();
this.image.onerror = this._onError;
this.image.onload = this._onLoad;
this.image.src = uri;
this._onLoadStart();
}
_destroyImageLoader() {
if (this.image) {
this.image.onerror = null
this.image.onload = null
this.image = null
this.image.onerror = null;
this.image.onload = null;
this.image = null;
}
}
_onError = (e) => {
const { onError } = this.props
const event = { nativeEvent: e }
const { onError } = this.props;
const event = { nativeEvent: e };
this._destroyImageLoader()
this._updateImageState(STATUS_ERRORED)
this._onLoadEnd()
if (onError) onError(event)
this._destroyImageLoader();
this._updateImageState(STATUS_ERRORED);
this._onLoadEnd();
if (onError) { onError(event); }
}
_onLoad = (e) => {
const { onLoad } = this.props
const event = { nativeEvent: e }
const { onLoad } = this.props;
const event = { nativeEvent: e };
this._destroyImageLoader()
this._updateImageState(STATUS_LOADED)
if (onLoad) onLoad(event)
this._onLoadEnd()
this._destroyImageLoader();
this._updateImageState(STATUS_LOADED);
if (onLoad) { onLoad(event); }
this._onLoadEnd();
}
_onLoadEnd() {
const { onLoadEnd } = this.props
if (onLoadEnd) onLoadEnd()
const { onLoadEnd } = this.props;
if (onLoadEnd) { onLoadEnd(); }
}
_onLoadStart() {
const { onLoadStart } = this.props
this._updateImageState(STATUS_LOADING)
if (onLoadStart) onLoadStart()
const { onLoadStart } = this.props;
this._updateImageState(STATUS_LOADING);
if (onLoadStart) { onLoadStart(); }
}
_updateImageState(status) {
this._imageState = status
const isLoaded = this._imageState === STATUS_LOADED
this._imageState = status;
const isLoaded = this._imageState === STATUS_LOADED;
if (isLoaded !== this.state.isLoaded) {
this.setState({ isLoaded })
this.setState({ isLoaded });
}
}
}
applyNativeMethods(Image)
const styles = StyleSheet.create({
initial: {
alignSelf: 'flex-start',
@@ -212,7 +208,7 @@ const styles = StyleSheet.create({
right: 0,
top: 0
}
})
});
const resizeModeStyles = StyleSheet.create({
center: {
@@ -235,6 +231,6 @@ const resizeModeStyles = StyleSheet.create({
stretch: {
backgroundSize: '100% 100%'
}
})
});
module.exports = Image
module.exports = applyNativeMethods(Image);

View File

@@ -1,5 +1,5 @@
function resolveAssetSource(source) {
return ((typeof source === 'object') ? source.uri : source) || null
return ((typeof source === 'object') ? source.uri : source) || null;
}
module.exports = resolveAssetSource
module.exports = resolveAssetSource;

View File

@@ -1,6 +1,6 @@
import { PropTypes } from 'react'
import ScrollView from '../ScrollView'
import ListViewDataSource from './ListViewDataSource'
import ListViewDataSource from './ListViewDataSource';
import { PropTypes } from 'react';
import ScrollView from '../ScrollView';
export default {
...ScrollView.propTypes,
@@ -19,4 +19,4 @@ export default {
onChangeVisibleRows: PropTypes.func,
removeClippedSubviews: PropTypes.bool,
stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number)
}
};

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('components/ListView', () => {
test('NO TEST COVERAGE')
})
test('NO TEST COVERAGE');
});

View File

@@ -1,12 +1,10 @@
import applyNativeMethods from '../../modules/applyNativeMethods'
import React, { Component } from 'react'
import ScrollView from '../ScrollView'
import ListViewDataSource from './ListViewDataSource'
import ListViewPropTypes from './ListViewPropTypes'
import View from '../View'
import pick from 'lodash/pick'
const SCROLLVIEW_REF = 'listviewscroll'
import applyNativeMethods from '../../modules/applyNativeMethods';
import ListViewDataSource from './ListViewDataSource';
import ListViewPropTypes from './ListViewPropTypes';
import pick from 'lodash/pick';
import ScrollView from '../ScrollView';
import View from '../View';
import React, { Component } from 'react';
class ListView extends Component {
static propTypes = ListViewPropTypes;
@@ -23,81 +21,83 @@ class ListView extends Component {
static DataSource = ListViewDataSource;
constructor(props) {
super(props)
super(props);
this.state = {
curRenderedRowsCount: this.props.initialListSize,
highlightedRow: {}
}
this.onRowHighlighted = (sectionId, rowId) => this._onRowHighlighted(sectionId, rowId)
};
this.onRowHighlighted = (sectionId, rowId) => this._onRowHighlighted(sectionId, rowId);
}
getScrollResponder() {
return this.refs[SCROLLVIEW_REF] && this.refs[SCROLLVIEW_REF].getScrollResponder()
return this._scrollViewRef && this._scrollViewRef.getScrollResponder();
}
scrollTo(...args) {
return this.refs[SCROLLVIEW_REF] && this.refs[SCROLLVIEW_REF].scrollTo(...args)
return this._scrollViewRef && this._scrollViewRef.scrollTo(...args);
}
setNativeProps(props) {
return this.refs[SCROLLVIEW_REF] && this.refs[SCROLLVIEW_REF].setNativeProps(props)
return this._scrollViewRef && this._scrollViewRef.setNativeProps(props);
}
_onRowHighlighted(sectionId, rowId) {
this.setState({highlightedRow: {sectionId, rowId}})
this.setState({ highlightedRow: { sectionId, rowId } });
}
render() {
const dataSource = this.props.dataSource
const header = this.props.renderHeader ? this.props.renderHeader() : undefined
const footer = this.props.renderFooter ? this.props.renderFooter() : undefined
const dataSource = this.props.dataSource;
const header = this.props.renderHeader ? this.props.renderHeader() : undefined;
const footer = this.props.renderFooter ? this.props.renderFooter() : undefined;
// render sections and rows
const children = []
const sections = dataSource.rowIdentities
const renderRow = this.props.renderRow
const renderSectionHeader = this.props.renderSectionHeader
const renderSeparator = this.props.renderSeparator
const children = [];
const sections = dataSource.rowIdentities;
const renderRow = this.props.renderRow;
const renderSectionHeader = this.props.renderSectionHeader;
const renderSeparator = this.props.renderSeparator;
for (let sectionIdx = 0, sectionCnt = sections.length; sectionIdx < sectionCnt; sectionIdx++) {
const rows = sections[sectionIdx]
const sectionId = dataSource.sectionIdentities[sectionIdx]
const rows = sections[sectionIdx];
const sectionId = dataSource.sectionIdentities[sectionIdx];
// render optional section header
if (renderSectionHeader) {
const section = dataSource.getSectionHeaderData(sectionIdx)
const key = 's_' + sectionId
const child = <View key={key}>{renderSectionHeader(section, sectionId)}</View>
children.push(child)
const section = dataSource.getSectionHeaderData(sectionIdx);
const key = `s_${sectionId}`;
const child = <View key={key}>{renderSectionHeader(section, sectionId)}</View>;
children.push(child);
}
// render rows
for (let rowIdx = 0, rowCnt = rows.length; rowIdx < rowCnt; rowIdx++) {
const rowId = rows[rowIdx]
const row = dataSource.getRowData(sectionIdx, rowIdx)
const key = 'r_' + sectionId + '_' + rowId
const child = <View key={key}>{renderRow(row, sectionId, rowId, this.onRowHighlighted)}</View>
children.push(child)
const rowId = rows[rowIdx];
const row = dataSource.getRowData(sectionIdx, rowIdx);
const key = `r_${sectionId}_${rowId}`;
const child = <View key={key}>{renderRow(row, sectionId, rowId, this.onRowHighlighted)}</View>;
children.push(child);
// render optional separator
if (renderSeparator && ((rowIdx !== rows.length - 1) || (sectionIdx === sections.length - 1))) {
const adjacentRowHighlighted =
this.state.highlightedRow.sectionID === sectionId && (
this.state.highlightedRow.rowID === rowId ||
this.state.highlightedRow.rowID === rows[rowIdx + 1])
const separator = renderSeparator(sectionId, rowId, adjacentRowHighlighted)
children.push(separator)
this.state.highlightedRow.rowID === rows[rowIdx + 1]);
const separator = renderSeparator(sectionId, rowId, adjacentRowHighlighted);
children.push(separator);
}
}
}
const props = pick(ScrollView.propTypes, this.props)
const props = pick(ScrollView.propTypes, this.props);
return React.cloneElement(this.props.renderScrollComponent(props), {
ref: SCROLLVIEW_REF
}, header, children, footer)
ref: this._setScrollViewRef
}, header, children, footer);
}
_setScrollViewRef(component) {
this._scrollViewRef = component;
}
}
applyNativeMethods(ListView)
module.exports = ListView
module.exports = applyNativeMethods(ListView);

View File

@@ -0,0 +1,20 @@
/* eslint-env mocha */
import assert from 'assert';
import React from 'react';
import { shallow } from 'enzyme';
import ProgressBar from '..';
suite('components/ProgressBar', () => {
suite('progress', () => {
test('value as percentage is set to "aria-valuenow"', () => {
const component = shallow(<ProgressBar progress={0.5} />);
assert(component.prop('aria-valuenow') === 50);
});
test('is ignored when "indeterminate" is "true"', () => {
const component = shallow(<ProgressBar indeterminate progress={0.5} />);
assert(component.prop('aria-valuenow') === null);
});
});
});

View File

@@ -0,0 +1,120 @@
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';
static propTypes = {
...View.propTypes,
color: ColorPropType,
indeterminate: PropTypes.bool,
progress: PropTypes.number,
trackColor: ColorPropType
};
static defaultProps = {
color: '#1976D2',
indeterminate: false,
progress: 0,
trackColor: 'transparent'
};
constructor(props) {
super(props);
this.state = {
animationTranslate: new Animated.Value(0)
};
}
componentDidMount() {
this._manageAnimation();
}
componentDidUpdate() {
this._manageAnimation();
}
render() {
const {
color,
indeterminate,
progress,
trackColor,
style,
...other
} = this.props;
const { animationTranslate } = this.state;
const percentageProgress = indeterminate ? 50 : progress * 100;
return (
<View {...other}
accessibilityRole='progressbar'
aria-valuemax='100'
aria-valuemin='0'
aria-valuenow={indeterminate ? null : percentageProgress}
style={[
styles.track,
style,
{ backgroundColor: trackColor }
]}
>
<Animated.View style={[
styles.progress,
{ backgroundColor: color },
indeterminate ? {
transform: [
{ translateX: animationTranslate.interpolate(translateInterpolation) }
],
width: indeterminateWidth
} : {
width: `${percentageProgress}%`
}
]} />
</View>
);
}
_manageAnimation() {
const { animationTranslate } = this.state;
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();
}
}
}
const styles = StyleSheet.create({
track: {
height: 5,
overflow: 'hidden',
userSelect: 'none'
},
progress: {
height: '100%'
}
});
module.exports = applyNativeMethods(ProgressBar);

View File

@@ -6,9 +6,9 @@
* @flow
*/
import debounce from 'lodash/debounce'
import React, { Component, PropTypes } from 'react'
import View from '../View'
import debounce from 'lodash/debounce';
import View from '../View';
import React, { Component, PropTypes } from 'react';
/**
* Encapsulates the Web-specific scroll throttling and disabling logic
@@ -32,63 +32,63 @@ export default class ScrollViewBase extends Component {
};
constructor(props) {
super(props)
this._debouncedOnScrollEnd = debounce(this._handleScrollEnd, 100)
this._state = { isScrolling: false }
super(props);
this._debouncedOnScrollEnd = debounce(this._handleScrollEnd, 100);
this._state = { isScrolling: false };
}
_handlePreventableScrollEvent = (handler) => {
return (e) => {
if (!this.props.scrollEnabled) {
e.preventDefault()
e.preventDefault();
} else {
if (handler) handler(e)
if (handler) { handler(e); }
}
}
};
}
_handleScroll = (e) => {
const { scrollEventThrottle } = this.props
const { scrollEventThrottle } = this.props;
// A scroll happened, so the scroll bumps the debounce.
this._debouncedOnScrollEnd(e)
this._debouncedOnScrollEnd(e);
if (this._state.isScrolling) {
// Scroll last tick may have changed, check if we need to notify
if (this._shouldEmitScrollEvent(this._state.scrollLastTick, scrollEventThrottle)) {
this._handleScrollTick(e)
this._handleScrollTick(e);
}
} else {
// Weren't scrolling, so we must have just started
this._handleScrollStart(e)
this._handleScrollStart(e);
}
}
_handleScrollStart(e) {
this._state.isScrolling = true
this._state.scrollLastTick = Date.now()
this._state.isScrolling = true;
this._state.scrollLastTick = Date.now();
}
_handleScrollTick(e) {
const { onScroll } = this.props
this._state.scrollLastTick = Date.now()
if (onScroll) onScroll(e)
const { onScroll } = this.props;
this._state.scrollLastTick = Date.now();
if (onScroll) { onScroll(e); }
}
_handleScrollEnd(e) {
const { onScroll } = this.props
this._state.isScrolling = false
if (onScroll) onScroll(e)
const { onScroll } = this.props;
this._state.isScrolling = false;
if (onScroll) { onScroll(e); }
}
_shouldEmitScrollEvent(lastTick, eventThrottle) {
const timeSinceLastTick = Date.now() - lastTick
return (eventThrottle > 0 && timeSinceLastTick >= (1000 / eventThrottle))
const timeSinceLastTick = Date.now() - lastTick;
return (eventThrottle > 0 && timeSinceLastTick >= (1000 / eventThrottle));
}
render() {
const {
onMomentumScrollBegin, onMomentumScrollEnd, onScrollBeginDrag, onScrollEndDrag, scrollEnabled, scrollEventThrottle, // eslint-disable-line
...other
} = this.props
} = this.props;
return (
<View
@@ -97,6 +97,6 @@ export default class ScrollViewBase extends Component {
onTouchMove={this._handlePreventableScrollEvent(this.props.onTouchMove)}
onWheel={this._handlePreventableScrollEvent(this.props.onWheel)}
/>
)
);
}
}

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('components/ScrollView', () => {
test('NO TEST COVERAGE')
})
test('NO TEST COVERAGE');
});

View File

@@ -6,20 +6,18 @@
* @flow
*/
import dismissKeyboard from '../../modules/dismissKeyboard'
import invariant from 'fbjs/lib/invariant'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import ScrollResponder from '../../modules/ScrollResponder'
import ScrollViewBase from './ScrollViewBase'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
import View from '../View'
import ViewStylePropTypes from '../View/ViewStylePropTypes'
const INNERVIEW = 'InnerScrollView'
const SCROLLVIEW = 'ScrollView'
import dismissKeyboard from '../../modules/dismissKeyboard';
import invariant from 'fbjs/lib/invariant';
import ReactDOM from 'react-dom';
import ScrollResponder from '../../modules/ScrollResponder';
import ScrollViewBase from './ScrollViewBase';
import StyleSheet from '../../apis/StyleSheet';
import StyleSheetPropType from '../../propTypes/StyleSheetPropType';
import View from '../View';
import ViewStylePropTypes from '../View/ViewStylePropTypes';
import React, { Component, PropTypes } from 'react';
/* eslint-disable react/prefer-es6-class */
const ScrollView = React.createClass({
propTypes: {
...View.propTypes,
@@ -35,14 +33,14 @@ const ScrollView = React.createClass({
style: StyleSheetPropType(ViewStylePropTypes)
},
mixins: [ScrollResponder.Mixin],
mixins: [ ScrollResponder.Mixin ],
getInitialState() {
return this.scrollResponderMixinGetInitialState()
return this.scrollResponderMixinGetInitialState();
},
setNativeProps(props: Object) {
this.refs[SCROLLVIEW].setNativeProps(props)
this._scrollViewRef.setNativeProps(props);
},
/**
@@ -52,15 +50,15 @@ const ScrollView = React.createClass({
* to the underlying scroll responder's methods.
*/
getScrollResponder(): Component {
return this
return this;
},
getScrollableNode(): any {
return ReactDOM.findDOMNode(this.refs[SCROLLVIEW])
return ReactDOM.findDOMNode(this._scrollViewRef);
},
getInnerViewNode(): any {
return ReactDOM.findDOMNode(this.refs[INNERVIEW])
return ReactDOM.findDOMNode(this._innerViewRef);
},
/**
@@ -79,45 +77,20 @@ const ScrollView = React.createClass({
animated?: boolean
) {
if (typeof y === 'number') {
console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, animated: true})` instead.')
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 || {});
}
this.getScrollResponder().scrollResponderScrollTo({x: x || 0, y: y || 0, animated: animated !== false})
this.getScrollResponder().scrollResponderScrollTo({ x: x || 0, y: y || 0, animated: animated !== false });
},
/**
* Deprecated, do not use.
*/
scrollWithoutAnimationTo(y: number = 0, x: number = 0) {
console.warn('`scrollWithoutAnimationTo` is deprecated. Use `scrollTo` instead')
this.scrollTo({x, y, animated: false})
},
handleScroll(e: Object) {
if (process.env.NODE_ENV !== 'production') {
if (this.props.onScroll && !this.props.scrollEventThrottle) {
console.log(
'You specified `onScroll` on a <ScrollView> but not ' +
'`scrollEventThrottle`. You will only receive one event. ' +
'Using `16` you get all the events but be aware that it may ' +
'cause frame drops, use a bigger number if you don\'t need as ' +
'much precision.'
)
}
}
if (this.props.keyboardDismissMode === 'on-drag') {
dismissKeyboard()
}
this.scrollResponderHandleScroll(e)
},
_handleContentOnLayout(e: Object) {
const { width, height } = e.nativeEvent.layout
this.props.onContentSizeChange(width, height)
console.warn('`scrollWithoutAnimationTo` is deprecated. Use `scrollTo` instead');
this.scrollTo({ x, y, animated: false });
},
render() {
@@ -129,23 +102,23 @@ const ScrollView = React.createClass({
onScroll, // eslint-disable-line
refreshControl,
...other
} = this.props
} = this.props;
if (process.env.NODE_ENV !== 'production' && this.props.style) {
const style = StyleSheet.flatten(this.props.style)
const childLayoutProps = ['alignItems', 'justifyContent'].filter((prop) => style && style[prop] !== undefined)
const style = StyleSheet.flatten(this.props.style);
const childLayoutProps = [ 'alignItems', 'justifyContent' ].filter((prop) => style && style[prop] !== undefined);
invariant(
childLayoutProps.length === 0,
'ScrollView child layout (' + JSON.stringify(childLayoutProps) +
') must be applied through the contentContainerStyle prop.'
)
`ScrollView child layout (${JSON.stringify(childLayoutProps)}) ` +
'must be applied through the contentContainerStyle prop.'
);
}
let contentSizeChangeProps = {}
let contentSizeChangeProps = {};
if (onContentSizeChange) {
contentSizeChangeProps = {
onLayout: this._handleContentOnLayout
}
};
}
const contentContainer = (
@@ -153,14 +126,14 @@ const ScrollView = React.createClass({
{...contentSizeChangeProps}
children={this.props.children}
collapsable={false}
ref={INNERVIEW}
ref={this._setInnerViewRef}
style={[
styles.contentContainer,
horizontal && styles.contentContainerHorizontal,
contentContainerStyle
]}
/>
)
);
const props = {
...other,
@@ -179,55 +152,90 @@ const ScrollView = React.createClass({
onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder,
onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture,
onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder,
onScroll: this.handleScroll,
onScroll: this._handleScroll,
onResponderGrant: this.scrollResponderHandleResponderGrant,
onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest,
onResponderTerminate: this.scrollResponderHandleTerminate,
onResponderRelease: this.scrollResponderHandleResponderRelease,
onResponderReject: this.scrollResponderHandleResponderReject
}
};
const ScrollViewClass = ScrollViewBase
const ScrollViewClass = ScrollViewBase;
invariant(
ScrollViewClass !== undefined,
'ScrollViewClass must not be undefined'
)
);
if (refreshControl) {
return React.cloneElement(
refreshControl,
{ style: props.style },
<ScrollViewClass {...props} ref={SCROLLVIEW} style={styles.base}>
<ScrollViewClass {...props} ref={this._setScrollViewRef} style={styles.base}>
{contentContainer}
</ScrollViewClass>
)
);
}
return (
<ScrollViewClass {...props} ref={SCROLLVIEW} style={props.style}>
<ScrollViewClass {...props} ref={this._setScrollViewRef} style={props.style}>
{contentContainer}
</ScrollViewClass>
)
);
},
_handleContentOnLayout(e: Object) {
const { width, height } = e.nativeEvent.layout;
this.props.onContentSizeChange(width, height);
},
_handleScroll(e: Object) {
if (process.env.NODE_ENV !== 'production') {
if (this.props.onScroll && !this.props.scrollEventThrottle) {
console.log(
'You specified `onScroll` on a <ScrollView> but not ' +
'`scrollEventThrottle`. You will only receive one event. ' +
'Using `16` you get all the events but be aware that it may ' +
'cause frame drops, use a bigger number if you don\'t need as ' +
'much precision.'
);
}
}
if (this.props.keyboardDismissMode === 'on-drag') {
dismissKeyboard();
}
this.scrollResponderHandleScroll(e);
},
_setInnerViewRef(component) {
this._innerViewRef = component;
},
_setScrollViewRef(component) {
this._scrollViewRef = component;
}
})
});
const styles = StyleSheet.create({
base: {
flex: 1,
overflowX: 'hidden',
overflowY: 'auto'
overflowY: 'auto',
WebkitOverflowScrolling: 'touch'
},
baseHorizontal: {
flexDirection: 'row',
overflowX: 'auto',
overflowY: 'hidden'
},
contentContainer: {
flex: 1
flexGrow: 1
},
contentContainerHorizontal: {
flexDirection: 'row'
}
})
});
module.exports = ScrollView
module.exports = ScrollView;

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('components/StaticContainer', () => {
test.skip('NO TEST COVERAGE', () => {})
})
test.skip('NO TEST COVERAGE', () => {});
});

View File

@@ -6,7 +6,7 @@
* @flow
*/
import React, { Component, PropTypes } from 'react'
import React, { Component, PropTypes } from 'react';
/**
* Renders static content efficiently by allowing React to short-circuit the
@@ -30,13 +30,13 @@ class StaticContainer extends Component {
};
shouldComponentUpdate(nextProps: { shouldUpdate: boolean }): boolean {
return nextProps.shouldUpdate
return nextProps.shouldUpdate;
}
render() {
const child = this.props.children
return (child === null || child === false) ? null : React.Children.only(child)
const child = this.props.children;
return (child === null || child === false) ? null : React.Children.only(child);
}
}
module.exports = StaticContainer
module.exports = StaticContainer;

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('components/StaticRenderer', () => {
test.skip('NO TEST COVERAGE', () => {})
})
test.skip('NO TEST COVERAGE', () => {});
});

View File

@@ -6,7 +6,7 @@
* @flow
*/
import { Component, PropTypes } from 'react'
import { Component, PropTypes } from 'react';
/**
* Renders static content efficiently by allowing React to short-circuit the
@@ -29,12 +29,12 @@ class StaticRenderer extends Component {
};
shouldComponentUpdate(nextProps: { shouldUpdate: boolean }): boolean {
return nextProps.shouldUpdate
return nextProps.shouldUpdate;
}
render() {
return this.props.render()
return this.props.render();
}
}
module.exports = StaticRenderer
module.exports = StaticRenderer;

View File

@@ -0,0 +1,46 @@
/* eslint-env mocha */
import assert from 'assert';
import React from 'react';
import { shallow } from 'enzyme';
import Switch from '..';
suite('components/Switch', () => {
suite('disabled', () => {
test('when "false" a default checkbox is rendered', () => {
const component = shallow(<Switch />);
assert(component.find('input').length === 1);
});
test('when "true" a disabled checkbox is rendered', () => {
const component = shallow(<Switch disabled />);
assert(component.find('input').prop('disabled') === true);
});
});
suite('onValueChange', () => {
test('when value is "false" it receives "true"', () => {
const handleValueChange = (value) => assert(value === true);
const component = shallow(<Switch onValueChange={handleValueChange} value={false} />);
component.find('input').simulate('click');
});
test('when value is "true" it receives "false"', () => {
const handleValueChange = (value) => assert(value === false);
const component = shallow(<Switch onValueChange={handleValueChange} value />);
component.find('input').simulate('click');
});
});
suite('value', () => {
test('when "false" an unchecked checkbox is rendered', () => {
const component = shallow(<Switch value={false} />);
assert(component.find('input').prop('checked') === false);
});
test('when "true" a checked checkbox is rendered', () => {
const component = shallow(<Switch value />);
assert(component.find('input').prop('checked') === true);
});
});
});

View File

@@ -0,0 +1,178 @@
import applyNativeMethods from '../../modules/applyNativeMethods';
import ColorPropType from '../../propTypes/ColorPropType';
import createDOMElement from '../../modules/createDOMElement';
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue';
import StyleSheet from '../../apis/StyleSheet';
import UIManager from '../../apis/UIManager';
import View from '../View';
import React, { Component, PropTypes } from 'react';
const thumbDefaultBoxShadow = '0px 1px 3px rgba(0,0,0,0.5)';
const thumbFocusedBoxShadow = `${thumbDefaultBoxShadow}, 0 0 0 10px rgba(0,0,0,0.1)`;
class Switch extends Component {
static displayName = 'Switch';
static propTypes = {
...View.propTypes,
activeThumbColor: ColorPropType,
activeTrackColor: ColorPropType,
disabled: PropTypes.bool,
onValueChange: PropTypes.func,
thumbColor: ColorPropType,
trackColor: ColorPropType,
value: PropTypes.bool
};
static defaultProps = {
activeThumbColor: '#009688',
activeTrackColor: '#A3D3CF',
disabled: false,
style: {},
thumbColor: '#FAFAFA',
trackColor: '#939393',
value: false
};
blur() {
UIManager.blur(this._checkbox);
}
focus() {
UIManager.focus(this._checkbox);
}
render() {
const {
activeThumbColor,
activeTrackColor,
disabled,
onValueChange, // eslint-disable-line
style,
thumbColor,
trackColor,
value,
// remove any iOS-only props
onTintColor, // eslint-disable-line
thumbTintColor, // eslint-disable-line
tintColor, // eslint-disable-line
...other
} = this.props;
const { height: styleHeight, width: styleWidth } = StyleSheet.flatten(style);
const height = styleHeight || 20;
const minWidth = multiplyStyleLengthValue(height, 2);
const width = styleWidth > minWidth ? styleWidth : minWidth;
const trackBorderRadius = multiplyStyleLengthValue(height, 0.5);
const trackCurrentColor = value ? activeTrackColor : trackColor;
const thumbCurrentColor = value ? activeThumbColor : thumbColor;
const thumbHeight = height;
const thumbWidth = thumbHeight;
const rootStyle = [
styles.root,
style,
{ height, width },
disabled && styles.cursorDefault
];
const trackStyle = [
styles.track,
{
backgroundColor: trackCurrentColor,
borderRadius: trackBorderRadius
},
disabled && styles.disabledTrack
];
const thumbStyle = [
styles.thumb,
{
alignSelf: value ? 'flex-end' : 'flex-start',
backgroundColor: thumbCurrentColor,
height: thumbHeight,
width: thumbWidth
},
disabled && styles.disabledThumb
];
const nativeControl = createDOMElement('input', {
checked: value,
disabled: disabled,
onBlur: this._handleFocusState,
onChange: this._handleChange,
onFocus: this._handleFocusState,
ref: this._setCheckboxRef,
style: [ styles.nativeControl, styles.cursorInherit ],
type: 'checkbox'
});
return (
<View {...other} style={rootStyle}>
<View style={trackStyle} />
<View ref={this._setThumbRef} style={thumbStyle} />
{nativeControl}
</View>
);
}
_handleChange = (event: Object) => {
const { onValueChange } = this.props;
onValueChange && onValueChange(event.nativeEvent.target.checked);
}
_handleFocusState = (event: Object) => {
const isFocused = event.nativeEvent.type === 'focus';
const boxShadow = isFocused ? thumbFocusedBoxShadow : thumbDefaultBoxShadow;
this._thumb.setNativeProps({ style: { boxShadow } });
}
_setCheckboxRef = (component) => {
this._checkbox = component;
}
_setThumbRef = (component) => {
this._thumb = component;
}
}
const styles = StyleSheet.create({
root: {
cursor: 'pointer',
userSelect: 'none'
},
cursorDefault: {
cursor: 'default'
},
cursorInherit: {
cursor: 'inherit'
},
track: {
...StyleSheet.absoluteFillObject,
height: '70%',
margin: 'auto',
transition: 'background-color 0.1s',
width: '90%'
},
disabledTrack: {
backgroundColor: '#D5D5D5'
},
thumb: {
borderRadius: '100%',
boxShadow: thumbDefaultBoxShadow,
transition: 'background-color 0.1s'
},
disabledThumb: {
backgroundColor: '#BDBDBD'
},
nativeControl: {
...StyleSheet.absoluteFillObject,
height: '100%',
margin: 0,
opacity: 0,
padding: 0,
width: '100%'
}
});
module.exports = applyNativeMethods(Switch);

View File

@@ -1,7 +1,7 @@
import TextPropTypes from '../../propTypes/TextPropTypes'
import ViewStylePropTypes from '../View/ViewStylePropTypes'
import TextPropTypes from '../../propTypes/TextPropTypes';
import ViewStylePropTypes from '../View/ViewStylePropTypes';
module.exports = {
...ViewStylePropTypes,
...TextPropTypes
}
};

View File

@@ -1,41 +1,41 @@
/* eslint-env mocha */
import assert from 'assert'
import React from 'react'
import Text from '../'
import { mount, shallow } from 'enzyme'
import assert from 'assert';
import React from 'react';
import Text from '../';
import { mount, shallow } from 'enzyme';
suite('components/Text', () => {
test('prop "children"', () => {
const children = 'children'
const text = shallow(<Text>{children}</Text>)
assert.equal(text.prop('children'), children)
})
const children = 'children';
const text = shallow(<Text>{children}</Text>);
assert.equal(text.prop('children'), children);
});
test('prop "numberOfLines"')
test('prop "numberOfLines"');
test('prop "onLayout"', (done) => {
mount(<Text onLayout={onLayout} />)
mount(<Text onLayout={onLayout} />);
function onLayout(e) {
const { layout } = e.nativeEvent
assert.deepEqual(layout, { x: 0, y: 0, width: 0, height: 0 })
done()
const { layout } = e.nativeEvent;
assert.deepEqual(layout, { x: 0, y: 0, width: 0, height: 0 });
done();
}
})
});
test('prop "onPress"', (done) => {
const text = mount(<Text onPress={onPress} />)
text.simulate('click')
const text = mount(<Text onPress={onPress} />);
text.simulate('click');
function onPress(e) {
assert.ok(e.nativeEvent)
done()
assert.ok(e.nativeEvent);
done();
}
})
});
test('prop "selectable"', () => {
let text = shallow(<Text />)
assert.equal(text.prop('style').userSelect, undefined)
text = shallow(<Text selectable={false} />)
assert.equal(text.prop('style').userSelect, 'none')
})
})
let text = shallow(<Text />);
assert.equal(text.prop('style').userSelect, undefined);
text = shallow(<Text selectable={false} />);
assert.equal(text.prop('style').userSelect, 'none');
});
});

View File

@@ -1,25 +1,24 @@
import applyLayout from '../../modules/applyLayout'
import applyNativeMethods from '../../modules/applyNativeMethods'
import createReactDOMComponent from '../../modules/createReactDOMComponent'
import { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
import TextStylePropTypes from './TextStylePropTypes'
import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes';
import createDOMElement from '../../modules/createDOMElement';
import StyleSheet from '../../apis/StyleSheet';
import StyleSheetPropType from '../../propTypes/StyleSheetPropType';
import TextStylePropTypes from './TextStylePropTypes';
import { Component, PropTypes } from 'react';
class Text extends Component {
static displayName = 'Text'
static displayName = 'Text';
static propTypes = {
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
...BaseComponentPropTypes,
accessibilityRole: PropTypes.oneOf([ 'heading', 'link' ]),
accessible: createReactDOMComponent.propTypes.accessible,
children: PropTypes.any,
numberOfLines: PropTypes.number,
onLayout: PropTypes.func,
onPress: PropTypes.func,
selectable: PropTypes.bool,
style: StyleSheetPropType(TextStylePropTypes),
testID: createReactDOMComponent.propTypes.testID
style: StyleSheetPropType(TextStylePropTypes)
};
static defaultProps = {
@@ -35,11 +34,10 @@ class Text extends Component {
selectable,
style,
...other
} = this.props
} = this.props;
return createReactDOMComponent({
return createDOMElement('span', {
...other,
component: 'span',
onClick: this._onPress,
style: [
styles.initial,
@@ -47,16 +45,14 @@ class Text extends Component {
!selectable && styles.notSelectable,
numberOfLines === 1 && styles.singleLineStyle
]
})
});
}
_onPress = (e) => {
if (this.props.onPress) this.props.onPress(e)
if (this.props.onPress) { this.props.onPress(e); }
}
}
applyLayout(applyNativeMethods(Text))
const styles = StyleSheet.create({
initial: {
color: 'inherit',
@@ -76,6 +72,6 @@ const styles = StyleSheet.create({
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
})
});
module.exports = Text
module.exports = applyLayout(applyNativeMethods(Text));

View File

@@ -6,7 +6,7 @@
* @flow
*/
import UIManager from '../../apis/UIManager'
import UIManager from '../../apis/UIManager';
/**
* This class is responsible for coordinating the "focused"
@@ -24,7 +24,7 @@ const TextInputState = {
* If no text field is focused it returns null
*/
currentlyFocusedField(): ?Object {
return this._currentlyFocusedNode
return this._currentlyFocusedNode;
},
/**
@@ -34,8 +34,8 @@ const TextInputState = {
*/
focusTextInput(textFieldNode: ?Object) {
if (this._currentlyFocusedNode !== textFieldNode && textFieldNode !== null) {
this._currentlyFocusedNode = textFieldNode
UIManager.focus(textFieldNode)
this._currentlyFocusedNode = textFieldNode;
UIManager.focus(textFieldNode);
}
},
@@ -46,10 +46,10 @@ const TextInputState = {
*/
blurTextInput(textFieldNode: ?Object) {
if (this._currentlyFocusedNode === textFieldNode && textFieldNode !== null) {
this._currentlyFocusedNode = null
UIManager.blur(textFieldNode)
this._currentlyFocusedNode = null;
UIManager.blur(textFieldNode);
}
}
}
};
module.exports = TextInputState
module.exports = TextInputState;

View File

@@ -1,104 +1,104 @@
/* eslint-env mocha */
import assert from 'assert'
import React from 'react'
import StyleSheet from '../../../apis/StyleSheet'
import TextareaAutosize from 'react-textarea-autosize'
import TextInput from '..'
import { mount, shallow } from 'enzyme'
import assert from 'assert';
import React from 'react';
import StyleSheet from '../../../apis/StyleSheet';
import TextareaAutosize from 'react-textarea-autosize';
import TextInput from '..';
import { mount, shallow } from 'enzyme';
const placeholderText = 'placeholderText'
const findNativeInput = (wrapper) => wrapper.find('input')
const findNativeTextarea = (wrapper) => wrapper.find(TextareaAutosize)
const findPlaceholder = (wrapper) => wrapper.find({ children: placeholderText })
const placeholderText = 'placeholderText';
const findNativeInput = (wrapper) => wrapper.find('input');
const findNativeTextarea = (wrapper) => wrapper.find(TextareaAutosize);
const findPlaceholder = (wrapper) => wrapper.find({ children: placeholderText });
const testIfDocumentIsFocused = (message, fn) => {
if (document.hasFocus && document.hasFocus()) {
test(message, fn)
test(message, fn);
} else {
test.skip(`${message} document is not focused`)
test.skip(`${message} document is not focused`);
}
}
};
suite('components/TextInput', () => {
test('prop "autoComplete"', () => {
// off
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.prop('autoComplete'), undefined)
let input = findNativeInput(shallow(<TextInput />));
assert.equal(input.prop('autoComplete'), undefined);
// on
input = findNativeInput(shallow(<TextInput autoComplete />))
assert.equal(input.prop('autoComplete'), 'on')
})
input = findNativeInput(shallow(<TextInput autoComplete />));
assert.equal(input.prop('autoComplete'), 'on');
});
test('prop "autoFocus"', () => {
// false
let input = findNativeInput(mount(<TextInput />))
assert.equal(input.prop('autoFocus'), undefined)
let input = findNativeInput(mount(<TextInput />));
assert.equal(input.prop('autoFocus'), undefined);
// true
input = findNativeInput(mount(<TextInput autoFocus />))
assert.equal(input.prop('autoFocus'), true)
})
input = findNativeInput(mount(<TextInput autoFocus />));
assert.equal(input.prop('autoFocus'), true);
});
testIfDocumentIsFocused('prop "clearTextOnFocus"', () => {
const defaultValue = 'defaultValue'
const defaultValue = 'defaultValue';
// false
let input = findNativeInput(mount(<TextInput defaultValue={defaultValue} />))
input.simulate('focus')
assert.equal(input.node.value, defaultValue)
let input = findNativeInput(mount(<TextInput defaultValue={defaultValue} />));
input.simulate('focus');
assert.equal(input.node.value, defaultValue);
// true
input = findNativeInput(mount(<TextInput clearTextOnFocus defaultValue={defaultValue} />))
input.simulate('focus')
assert.equal(input.node.value, '')
})
input = findNativeInput(mount(<TextInput clearTextOnFocus defaultValue={defaultValue} />));
input.simulate('focus');
assert.equal(input.node.value, '');
});
test('prop "defaultValue"', () => {
const defaultValue = 'defaultValue'
const input = findNativeInput(shallow(<TextInput defaultValue={defaultValue} />))
assert.equal(input.prop('defaultValue'), defaultValue)
})
const defaultValue = 'defaultValue';
const input = findNativeInput(shallow(<TextInput defaultValue={defaultValue} />));
assert.equal(input.prop('defaultValue'), defaultValue);
});
test('prop "editable"', () => {
// true
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.prop('readOnly'), false)
let input = findNativeInput(shallow(<TextInput />));
assert.equal(input.prop('readOnly'), false);
// false
input = findNativeInput(shallow(<TextInput editable={false} />))
assert.equal(input.prop('readOnly'), true)
})
input = findNativeInput(shallow(<TextInput editable={false} />));
assert.equal(input.prop('readOnly'), true);
});
test('prop "keyboardType"', () => {
// default
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.prop('type'), undefined)
input = findNativeInput(shallow(<TextInput keyboardType='default' />))
assert.equal(input.prop('type'), undefined)
let input = findNativeInput(shallow(<TextInput />));
assert.equal(input.prop('type'), 'text');
input = findNativeInput(shallow(<TextInput keyboardType='default' />));
assert.equal(input.prop('type'), 'text');
// email-address
input = findNativeInput(shallow(<TextInput keyboardType='email-address' />))
assert.equal(input.prop('type'), 'email')
input = findNativeInput(shallow(<TextInput keyboardType='email-address' />));
assert.equal(input.prop('type'), 'email');
// numeric
input = findNativeInput(shallow(<TextInput keyboardType='numeric' />))
assert.equal(input.prop('type'), 'number')
input = findNativeInput(shallow(<TextInput keyboardType='numeric' />));
assert.equal(input.prop('type'), 'number');
// phone-pad
input = findNativeInput(shallow(<TextInput keyboardType='phone-pad' />))
assert.equal(input.prop('type'), 'tel')
input = findNativeInput(shallow(<TextInput keyboardType='phone-pad' />));
assert.equal(input.prop('type'), 'tel');
// url
input = findNativeInput(shallow(<TextInput keyboardType='url' />))
assert.equal(input.prop('type'), 'url')
})
input = findNativeInput(shallow(<TextInput keyboardType='url' />));
assert.equal(input.prop('type'), 'url');
});
test('prop "maxLength"', () => {
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.prop('maxLength'), undefined)
input = findNativeInput(shallow(<TextInput maxLength={10} />))
assert.equal(input.prop('maxLength'), '10')
})
let input = findNativeInput(shallow(<TextInput />));
assert.equal(input.prop('maxLength'), undefined);
input = findNativeInput(shallow(<TextInput maxLength={10} />));
assert.equal(input.prop('maxLength'), '10');
});
test('prop "maxNumberOfLines"', () => {
const generateValue = () => {
let str = ''
while (str.length < 100) str += 'x'
return str
}
let str = '';
while (str.length < 100) { str += 'x'; }
return str;
};
const input = findNativeTextarea(shallow(
<TextInput
@@ -106,122 +106,124 @@ suite('components/TextInput', () => {
multiline
value={generateValue()}
/>
))
assert.equal(input.prop('maxRows'), 3)
})
));
assert.equal(input.prop('maxRows'), 3);
});
test('prop "multiline"', () => {
// false
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.length, 1)
let input = findNativeInput(shallow(<TextInput />));
assert.equal(input.length, 1);
// true
input = findNativeTextarea(shallow(<TextInput multiline />))
assert.equal(input.length, 1)
})
input = findNativeTextarea(shallow(<TextInput multiline />));
assert.equal(input.length, 1);
});
test('prop "numberOfLines"', () => {
// missing multiline
let input = findNativeInput(shallow(<TextInput numberOfLines={2} />))
assert.equal(input.length, 1)
let input = findNativeInput(shallow(<TextInput numberOfLines={2} />));
assert.equal(input.length, 1);
// with multiline
input = findNativeTextarea(shallow(<TextInput multiline numberOfLines={2} />))
assert.equal(input.length, 1)
input = findNativeTextarea(shallow(<TextInput multiline numberOfLines={2} />));
assert.equal(input.length, 1);
input = findNativeTextarea(shallow(
<TextInput
multiline
numberOfLines={3}
/>
))
assert.equal(input.prop('minRows'), 3)
})
));
assert.equal(input.prop('minRows'), 3);
});
test('prop "onBlur"', (done) => {
const input = findNativeInput(mount(<TextInput onBlur={onBlur} />))
input.simulate('blur')
const input = findNativeInput(mount(<TextInput onBlur={onBlur} />));
input.simulate('blur');
function onBlur(e) {
assert.ok(e)
done()
assert.ok(e);
done();
}
})
});
test('prop "onChange"', (done) => {
const input = findNativeInput(mount(<TextInput onChange={onChange} />))
input.simulate('change')
const input = findNativeInput(mount(<TextInput onChange={onChange} />));
input.simulate('change');
function onChange(e) {
assert.ok(e)
done()
assert.ok(e);
done();
}
})
});
test('prop "onChangeText"', (done) => {
const newText = 'newText'
const input = findNativeInput(mount(<TextInput onChangeText={onChangeText} />))
input.simulate('change', { target: { value: newText } })
const newText = 'newText';
const input = findNativeInput(mount(<TextInput onChangeText={onChangeText} />));
input.simulate('change', { target: { value: newText } });
function onChangeText(text) {
assert.equal(text, newText)
done()
assert.equal(text, newText);
done();
}
})
});
test('prop "onFocus"', (done) => {
const input = findNativeInput(mount(<TextInput onFocus={onFocus} />))
input.simulate('focus')
const input = findNativeInput(mount(<TextInput onFocus={onFocus} />));
input.simulate('focus');
function onFocus(e) {
assert.ok(e)
done()
assert.ok(e);
done();
}
})
});
test('prop "onLayout"')
test('prop "onLayout"');
test('prop "onSelectionChange"', (done) => {
const input = findNativeInput(mount(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />))
input.simulate('select', { target: { selectionStart: 0, selectionEnd: 3 } })
const input = findNativeInput(mount(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />));
input.simulate('select', { target: { selectionStart: 0, selectionEnd: 3 } });
function onSelectionChange(e) {
assert.equal(e.selectionEnd, 3)
assert.equal(e.selectionStart, 0)
done()
assert.equal(e.selectionEnd, 3);
assert.equal(e.selectionStart, 0);
done();
}
})
});
test('prop "placeholder"', () => {
let textInput = shallow(<TextInput />)
assert.equal(findPlaceholder(textInput).length, 0)
let textInput = shallow(<TextInput />);
assert.equal(findPlaceholder(textInput).length, 0);
textInput = shallow(<TextInput placeholder={placeholderText} />)
assert.equal(findPlaceholder(textInput).length, 1)
})
textInput = shallow(<TextInput placeholder={placeholderText} />);
assert.equal(findPlaceholder(textInput).length, 1);
});
test('prop "placeholderTextColor"', () => {
let placeholderElement = findPlaceholder(shallow(<TextInput placeholder={placeholderText} />))
assert.equal(StyleSheet.flatten(placeholderElement.prop('style')).color, 'darkgray')
let placeholderElement = findPlaceholder(shallow(<TextInput placeholder={placeholderText} />));
assert.equal(StyleSheet.flatten(placeholderElement.prop('style')).color, 'darkgray');
placeholderElement = findPlaceholder(shallow(<TextInput placeholder={placeholderText} placeholderTextColor='red' />))
assert.equal(StyleSheet.flatten(placeholderElement.prop('style')).color, 'red')
})
placeholderElement = findPlaceholder(
shallow(<TextInput placeholder={placeholderText} placeholderTextColor='red' />)
);
assert.equal(StyleSheet.flatten(placeholderElement.prop('style')).color, 'red');
});
test('prop "secureTextEntry"', () => {
let input = findNativeInput(shallow(<TextInput secureTextEntry />))
assert.equal(input.prop('type'), 'password')
let input = findNativeInput(shallow(<TextInput secureTextEntry />));
assert.equal(input.prop('type'), 'password');
// ignored for multiline
input = findNativeTextarea(shallow(<TextInput multiline secureTextEntry />))
assert.equal(input.prop('type'), undefined)
})
input = findNativeTextarea(shallow(<TextInput multiline secureTextEntry />));
assert.equal(input.prop('type'), undefined);
});
testIfDocumentIsFocused('prop "selectTextOnFocus"', () => {
const text = 'Text'
const text = 'Text';
// false
let input = findNativeInput(mount(<TextInput defaultValue={text} />))
input.node.focus()
assert.equal(input.node.selectionEnd, 4)
assert.equal(input.node.selectionStart, 4)
let input = findNativeInput(mount(<TextInput defaultValue={text} />));
input.node.focus();
assert.equal(input.node.selectionEnd, 4);
assert.equal(input.node.selectionStart, 4);
// true
// input = findNativeInput(mount(<TextInput defaultValue={text} selectTextOnFocus />))
input = findNativeInput(mount(<TextInput defaultValue={text} selectTextOnFocus />));
// input.node.focus()
// assert.equal(input.node.selectionEnd, 4)
// assert.equal(input.node.selectionStart, 0)
})
});
test('prop "style"', () => {
const styles = StyleSheet.create({
@@ -229,16 +231,17 @@ suite('components/TextInput', () => {
borderWidth: 1,
textAlign: 'center'
}
})
const textInput = shallow(<TextInput style={styles.root} />)
const input = findNativeInput(textInput)
assert.equal(StyleSheet.flatten(textInput.prop('style')).borderWidth, 1, 'expected View styles to be applied to the "View"')
assert.equal(input.prop('style').textAlign, 'center', 'expected Text styles to be applied to the "input"')
})
});
const textInput = shallow(<TextInput style={styles.root} />);
const input = findNativeInput(textInput);
const borderWidth = StyleSheet.flatten(textInput.prop('style')).borderWidth;
assert.equal(borderWidth, 1, 'expected View styles to be applied to the "View"');
assert.equal(input.prop('style').textAlign, 'center', 'expected Text styles to be applied to the "input"');
});
test('prop "value"', () => {
const value = 'value'
const input = findNativeInput(shallow(<TextInput value={value} />))
assert.equal(input.prop('value'), value)
})
})
const value = 'value';
const input = findNativeInput(shallow(<TextInput value={value} />));
assert.equal(input.prop('value'), value);
});
});

View File

@@ -1,20 +1,22 @@
import applyNativeMethods from '../../modules/applyNativeMethods'
import createReactDOMComponent from '../../modules/createReactDOMComponent'
import omit from 'lodash/omit'
import pick from 'lodash/pick'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
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 ViewStylePropTypes from '../View/ViewStylePropTypes'
import applyNativeMethods from '../../modules/applyNativeMethods';
import createDOMElement from '../../modules/createDOMElement';
import omit from 'lodash/omit';
import pick from 'lodash/pick';
import ReactDOM from 'react-dom';
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 ViewStylePropTypes from '../View/ViewStylePropTypes';
import React, { Component, PropTypes } from 'react';
const viewStyleProps = Object.keys(ViewStylePropTypes)
const viewStyleProps = Object.keys(ViewStylePropTypes);
class TextInput extends Component {
static displayName = 'TextInput';
static propTypes = {
...View.propTypes,
autoComplete: PropTypes.bool,
@@ -22,7 +24,9 @@ class TextInput extends Component {
clearTextOnFocus: PropTypes.bool,
defaultValue: PropTypes.string,
editable: PropTypes.bool,
keyboardType: PropTypes.oneOf(['default', 'email-address', 'numeric', 'phone-pad', 'url']),
keyboardType: PropTypes.oneOf([
'default', 'email-address', 'numeric', 'phone-pad', 'search', 'url', 'web-search'
]),
maxLength: PropTypes.number,
maxNumberOfLines: PropTypes.number,
multiline: PropTypes.bool,
@@ -51,24 +55,24 @@ class TextInput extends Component {
};
constructor(props, context) {
super(props, context)
this.state = { showPlaceholder: !props.value && !props.defaultValue }
super(props, context);
this.state = { showPlaceholder: !props.value && !props.defaultValue };
}
blur() {
TextInputState.blurTextInput(ReactDOM.findDOMNode(this.refs.input))
TextInputState.blurTextInput(ReactDOM.findDOMNode(this._inputRef));
}
clear() {
this.setNativeProps({ text: '' })
this.setNativeProps({ text: '' });
}
focus() {
TextInputState.focusTextInput(ReactDOM.findDOMNode(this.refs.input))
TextInputState.focusTextInput(ReactDOM.findDOMNode(this._inputRef));
}
setNativeProps(props) {
UIManager.updateView(this.refs.input, props, this)
UIManager.updateView(this._inputRef, props, this);
}
render() {
@@ -91,39 +95,41 @@ class TextInput extends Component {
style,
testID,
value
} = this.props
} = this.props;
let type
let type;
switch (keyboardType) {
case 'email-address':
type = 'email'
break
type = 'email';
break;
case 'numeric':
type = 'number'
break
type = 'number';
break;
case 'phone-pad':
type = 'tel'
break
type = 'tel';
break;
case 'search':
case 'web-search':
type = 'search'
break
type = 'search';
break;
case 'url':
type = 'url'
break
type = 'url';
break;
default:
type = 'text';
}
if (secureTextEntry) {
type = 'password'
type = 'password';
}
// In order to support 'Text' styles on 'TextInput', we split the 'Text'
// and 'View' styles and apply them to the 'Text' and 'View' components
// used in the implementation
const flattenedStyle = StyleSheet.flatten(style)
const rootStyles = pick(flattenedStyle, viewStyleProps)
const textStyles = omit(flattenedStyle, viewStyleProps)
const flattenedStyle = StyleSheet.flatten(style);
const rootStyles = pick(flattenedStyle, viewStyleProps);
const textStyles = omit(flattenedStyle, viewStyleProps);
const propsCommon = {
autoComplete: autoComplete && 'on',
@@ -135,24 +141,24 @@ class TextInput extends Component {
onFocus: this._handleFocus,
onSelect: onSelectionChange && this._handleSelectionChange,
readOnly: !editable,
ref: this._setInputRef,
style: [ styles.input, textStyles, { outline: style.outline } ],
value
}
};
const propsMultiline = {
...propsCommon,
component: TextareaAutosize,
maxRows: maxNumberOfLines || numberOfLines,
minRows: numberOfLines
}
};
const propsSingleline = {
...propsCommon,
component: 'input',
type
}
};
const props = multiline ? propsMultiline : propsSingleline
const component = multiline ? TextareaAutosize : 'input';
const props = multiline ? propsMultiline : propsSingleline;
const optionalPlaceholder = placeholder && this.state.showPlaceholder && (
<View pointerEvents='none' style={styles.placeholder}>
@@ -165,7 +171,7 @@ class TextInput extends Component {
]}
/>
</View>
)
);
return (
<View
@@ -176,64 +182,68 @@ class TextInput extends Component {
testID={testID}
>
<View style={styles.wrapper}>
{createReactDOMComponent({ ...props, ref: 'input' })}
{createDOMElement(component, props)}
{optionalPlaceholder}
</View>
</View>
)
);
}
_handleBlur = (e) => {
const { onBlur } = this.props
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
this.blur()
if (onBlur) onBlur(e)
const { onBlur } = this.props;
const text = e.target.value;
this.setState({ showPlaceholder: text === '' });
this.blur();
if (onBlur) { onBlur(e); }
}
_handleChange = (e) => {
const { onChange, onChangeText } = this.props
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
if (onChange) onChange(e)
if (onChangeText) onChangeText(text)
if (!this.refs.input) {
const { onChange, onChangeText } = this.props;
const text = e.target.value;
this.setState({ showPlaceholder: text === '' });
if (onChange) { onChange(e); }
if (onChangeText) { onChangeText(text); }
if (!this._inputRef) {
// calling `this.props.onChange` or `this.props.onChangeText`
// may clean up the input itself. Exits here.
return
return;
}
}
_handleClick = (e) => {
this.focus()
this.focus();
}
_handleFocus = (e) => {
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props
const node = ReactDOM.findDOMNode(this.refs.input)
const text = e.target.value
if (onFocus) onFocus(e)
if (clearTextOnFocus) this.clear()
if (selectTextOnFocus) node.select()
this.setState({ showPlaceholder: text === '' })
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props;
const node = ReactDOM.findDOMNode(this._inputRef);
const text = e.target.value;
if (onFocus) { onFocus(e); }
if (clearTextOnFocus) { this.clear(); }
if (selectTextOnFocus) { node.select(); }
this.setState({ showPlaceholder: text === '' });
}
_handleSelectionChange = (e) => {
const { onSelectionChange } = this.props
const { onSelectionChange } = this.props;
try {
const { selectionDirection, selectionEnd, selectionStart } = e.target
const { selectionDirection, selectionEnd, selectionStart } = e.target;
const event = {
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent: e.nativeEvent
}
if (onSelectionChange) onSelectionChange(event)
};
if (onSelectionChange) { onSelectionChange(event); }
} catch (e) {}
}
_setInputRef = (component) => {
this._inputRef = component;
}
}
applyNativeMethods(TextInput)
applyNativeMethods(TextInput);
const styles = StyleSheet.create({
initial: {
@@ -267,6 +277,6 @@ const styles = StyleSheet.create({
overflow: 'hidden',
whiteSpace: 'pre'
}
})
});
module.exports = TextInput
module.exports = TextInput;

View File

@@ -1,5 +1,5 @@
/* eslint-env mocha */
suite('components/Touchable', () => {
test.skip('NO TEST COVERAGE', () => {})
})
test.skip('NO TEST COVERAGE', () => {});
});

View File

@@ -1,12 +1,12 @@
import { PropTypes } from 'react'
import BorderPropTypes from '../../propTypes/BorderPropTypes'
import ColorPropType from '../../propTypes/ColorPropType'
import LayoutPropTypes from '../../propTypes/LayoutPropTypes'
import TransformPropTypes from '../../propTypes/TransformPropTypes'
import BorderPropTypes from '../../propTypes/BorderPropTypes';
import ColorPropType from '../../propTypes/ColorPropType';
import LayoutPropTypes from '../../propTypes/LayoutPropTypes';
import { PropTypes } from 'react';
import TransformPropTypes from '../../propTypes/TransformPropTypes';
const { number, oneOf, string } = PropTypes
const autoOrHiddenOrVisible = oneOf([ 'auto', 'hidden', 'visible' ])
const hiddenOrVisible = oneOf([ 'hidden', 'visible' ])
const { number, oneOf, string } = PropTypes;
const autoOrHiddenOrVisible = oneOf([ 'auto', 'hidden', 'visible' ]);
const hiddenOrVisible = oneOf([ 'hidden', 'visible' ]);
module.exports = {
...BorderPropTypes,
@@ -16,6 +16,7 @@ module.exports = {
backgroundColor: ColorPropType,
opacity: number,
overflow: autoOrHiddenOrVisible,
zIndex: number,
/*
* @platform web
*/
@@ -34,5 +35,5 @@ module.exports = {
transition: string,
userSelect: string,
visibility: hiddenOrVisible,
zIndex: number
}
WebkitOverflowScrolling: oneOf([ 'auto', 'touch' ])
};

View File

@@ -1,55 +1,55 @@
/* eslint-env mocha */
import assert from 'assert'
import includes from 'lodash/includes'
import React from 'react'
import View from '../'
import { mount, shallow } from 'enzyme'
import assert from 'assert';
import includes from 'lodash/includes';
import React from 'react';
import View from '../';
import { mount, shallow } from 'enzyme';
suite('components/View', () => {
suite('rendered element', () => {
test('is a "div" by default', () => {
const view = shallow(<View />)
assert.equal(view.is('div'), true)
})
const view = shallow(<View />);
assert.equal(view.is('div'), true);
});
test('is a "span" when inside <View accessibilityRole="button" />', () => {
const view = mount(<View accessibilityRole='button'><View /></View>)
assert.equal(view.find('span').length, 1)
})
})
const view = mount(<View accessibilityRole='button'><View /></View>);
assert.equal(view.find('span').length, 1);
});
});
test('prop "children"', () => {
const children = <View testID='1' />
const view = shallow(<View>{children}</View>)
assert.equal(view.prop('children'), children)
})
const children = <View testID='1' />;
const view = shallow(<View>{children}</View>);
assert.equal(view.prop('children'), children);
});
test('prop "onLayout"', (done) => {
mount(<View onLayout={onLayout} />)
mount(<View onLayout={onLayout} />);
function onLayout(e) {
const { layout } = e.nativeEvent
assert.deepEqual(layout, { x: 0, y: 0, width: 0, height: 0 })
done()
const { layout } = e.nativeEvent;
assert.deepEqual(layout, { x: 0, y: 0, width: 0, height: 0 });
done();
}
})
});
test('prop "pointerEvents"', () => {
const view = shallow(<View pointerEvents='box-only' />)
assert.ok(includes(view.prop('className'), '__style_pebo') === true)
})
const view = shallow(<View pointerEvents='box-only' />);
assert.ok(includes(view.prop('className'), '__style_pebo') === true);
});
test('prop "style"', () => {
const view = shallow(<View />)
assert.equal(view.prop('style').flexShrink, 0)
const view = shallow(<View />);
assert.equal(view.prop('style').flexShrink, 0);
const flexView = shallow(<View style={{ flex: 1 }} />)
assert.equal(flexView.prop('style').flexShrink, 1)
const flexView = shallow(<View style={{ flex: 1 }} />);
assert.equal(flexView.prop('style').flexShrink, 1);
const flexShrinkView = shallow(<View style={{ flexShrink: 1 }} />)
assert.equal(flexShrinkView.prop('style').flexShrink, 1)
const flexShrinkView = shallow(<View style={{ flexShrink: 1 }} />);
assert.equal(flexShrinkView.prop('style').flexShrink, 1);
const flexAndShrinkView = shallow(<View style={{ flex: 1, flexShrink: 2 }} />)
assert.equal(flexAndShrinkView.prop('style').flexShrink, 2)
})
})
const flexAndShrinkView = shallow(<View style={{ flex: 1, flexShrink: 2 }} />);
assert.equal(flexAndShrinkView.prop('style').flexShrink, 2);
});
});

View File

@@ -1,12 +1,13 @@
import applyLayout from '../../modules/applyLayout'
import applyNativeMethods from '../../modules/applyNativeMethods'
import createReactDOMComponent from '../../modules/createReactDOMComponent'
import EdgeInsetsPropType from '../../propTypes/EdgeInsetsPropType'
import normalizeNativeEvent from '../../modules/normalizeNativeEvent'
import { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
import ViewStylePropTypes from './ViewStylePropTypes'
import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes';
import createDOMElement from '../../modules/createDOMElement';
import EdgeInsetsPropType from '../../propTypes/EdgeInsetsPropType';
import normalizeNativeEvent from '../../modules/normalizeNativeEvent';
import StyleSheet from '../../apis/StyleSheet';
import StyleSheetPropType from '../../propTypes/StyleSheetPropType';
import ViewStylePropTypes from './ViewStylePropTypes';
import { Component, PropTypes } from 'react';
const eventHandlerNames = [
'onClick',
@@ -29,16 +30,13 @@ const eventHandlerNames = [
'onTouchMoveCapture',
'onTouchStart',
'onTouchStartCapture'
]
];
class View extends Component {
static displayName = 'View'
static displayName = 'View';
static propTypes = {
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
accessibilityLiveRegion: createReactDOMComponent.propTypes.accessibilityLiveRegion,
accessibilityRole: createReactDOMComponent.propTypes.accessibilityRole,
accessible: createReactDOMComponent.propTypes.accessible,
...BaseComponentPropTypes,
children: PropTypes.any,
collapsable: PropTypes.bool,
hitSlop: EdgeInsetsPropType,
@@ -63,9 +61,8 @@ class View extends Component {
onTouchMoveCapture: PropTypes.func,
onTouchStart: PropTypes.func,
onTouchStartCapture: PropTypes.func,
pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']),
style: StyleSheetPropType(ViewStylePropTypes),
testID: createReactDOMComponent.propTypes.testID
pointerEvents: PropTypes.oneOf([ 'auto', 'box-none', 'box-only', 'none' ]),
style: StyleSheetPropType(ViewStylePropTypes)
};
static defaultProps = {
@@ -84,7 +81,7 @@ class View extends Component {
getChildContext() {
return {
isInAButtonView: this.props.accessibilityRole === 'button'
}
};
}
render() {
@@ -95,34 +92,34 @@ class View extends Component {
pointerEvents,
style,
...other
} = this.props
} = this.props;
const flattenedStyle = StyleSheet.flatten(style)
const pointerEventsStyle = pointerEvents && { pointerEvents }
const flattenedStyle = StyleSheet.flatten(style);
const pointerEventsStyle = pointerEvents && { pointerEvents };
// 'View' needs to set 'flexShrink:0' only when there is no 'flex' or 'flexShrink' style provided
const needsFlexReset = flattenedStyle.flex == null && flattenedStyle.flexShrink == null
const needsFlexReset = flattenedStyle.flex == null && flattenedStyle.flexShrink == null;
const normalizedEventHandlers = eventHandlerNames.reduce((handlerProps, handlerName) => {
const handler = this.props[handlerName]
const handler = this.props[handlerName];
if (typeof handler === 'function') {
handlerProps[handlerName] = this._normalizeEventForHandler(handler, handlerName)
handlerProps[handlerName] = this._normalizeEventForHandler(handler, handlerName);
}
return handlerProps
}, {})
return handlerProps;
}, {});
const component = this.context.isInAButtonView ? 'span' : 'div';
const props = {
...other,
...normalizedEventHandlers,
component: this.context.isInAButtonView ? 'span' : 'div',
style: [
styles.initial,
style,
needsFlexReset && styles.flexReset,
pointerEventsStyle
]
}
};
return createReactDOMComponent(props)
return createDOMElement(component, props);
}
_normalizeEventForHandler(handler, handlerName) {
@@ -130,16 +127,16 @@ class View extends Component {
// ResponderEvents and their handlers to fire twice for Touchables.
// Auto-fix this issue by calling 'preventDefault' to cancel the mouse
// events.
const shouldCancelEvent = handlerName.indexOf('onResponder') === 0
const shouldCancelEvent = handlerName.indexOf('onResponder') === 0;
return (e) => {
e.nativeEvent = normalizeNativeEvent(e.nativeEvent)
const returnValue = handler(e)
e.nativeEvent = normalizeNativeEvent(e.nativeEvent);
const returnValue = handler(e);
if (shouldCancelEvent && e.cancelable) {
e.preventDefault()
e.preventDefault();
}
return returnValue
}
return returnValue;
};
}
}
@@ -171,6 +168,6 @@ const styles = StyleSheet.create({
flexReset: {
flexShrink: 0
}
})
});
module.exports = applyLayout(applyNativeMethods(View))
module.exports = applyLayout(applyNativeMethods(View));

View File

@@ -1,14 +1,18 @@
import './modules/injectResponderEventPlugin'
import './modules/injectResponderEventPlugin';
import findNodeHandle from './modules/findNodeHandle'
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import I18nManager from './apis/I18nManager'
import StyleSheet from './apis/StyleSheet'
import Image from './components/Image'
import Text from './components/Text'
import TextInput from './components/TextInput'
import View from './components/View'
import findNodeHandle from './modules/findNodeHandle';
import ReactDOM from 'react-dom';
import ReactDOMServer from 'react-dom/server';
// APIs
import I18nManager from './apis/I18nManager';
import StyleSheet from './apis/StyleSheet';
// components
import Image from './components/Image';
import Text from './components/Text';
import TextInput from './components/TextInput';
import View from './components/View';
const ReactNativeCore = {
findNodeHandle,
@@ -16,12 +20,14 @@ const ReactNativeCore = {
renderToStaticMarkup: ReactDOMServer.renderToStaticMarkup,
renderToString: ReactDOMServer.renderToString,
unmountComponentAtNode: ReactDOM.unmountComponentAtNode,
// APIs
I18nManager,
StyleSheet,
// components
Image,
Text,
TextInput,
View
}
};
module.exports = ReactNativeCore
module.exports = ReactNativeCore;

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