Compare commits

...

50 Commits

Author SHA1 Message Date
Nicolas Gallagher
dbd607ce47 0.0.46 2016-10-10 17:12:28 -07:00
Nicolas Gallagher
373cb38ca9 Fix lint error 2016-10-10 09:19:20 -07:00
Nicolas Gallagher
4576b42365 Correct the Image docs 2016-10-09 16:53:41 -07:00
Nicolas Gallagher
5a5707855b [fix] CSS reset 2016-10-04 16:26:03 -07:00
Paul Le Cam
0c76cc5d80 [fix] how ListView uses ScrollView
* Bind `_setScrollViewRef` to instance
* Fix getting ScrollView props for ListView
2016-09-19 16:24:25 -07:00
Nicolas Gallagher
d64df129b2 [fix] remove default 'input' border-radius 2016-09-12 11:46:47 -07:00
Nicolas Gallagher
763c5444ce Add 'ProgressBar' to README 2016-09-06 12:51:59 -07:00
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
Nicolas Gallagher
acc7032d60 0.0.43 2016-08-16 14:05:48 -07:00
Nicolas Gallagher
0fc5644959 Add more details to README
Fix #57
2016-08-16 14:04:22 -07:00
Nicolas Gallagher
be923ec453 [fix] disabled Touchable 2016-08-16 10:29:26 -07:00
Nicolas Gallagher
248c2e1258 [fix] ActivityIndicator animation
Use 'Animated' to animate the 'ActivityIndicator'

Fix #182
2016-08-15 16:58:20 -07:00
Nicolas Gallagher
2e822c068d [fix] Image render thrashing
This patch removes several avoidable uses of `setState`, only
re-rendering the `Image` when necessary.

It also fixes a style prop warning for `resizeMode`, which was not being
removed in development due to the use of `Object.freeze` when styles are
registered.

Close #116
2016-08-15 15:00:47 -07:00
Nicolas Gallagher
fb5406e4ec [change] I18nManager manages global writing direction 2016-08-15 11:36:59 -07:00
Vitor Balocco
638479991e Fix broken link and typo in StyleSheet API docs (#189) 2016-08-14 19:42:42 -07:00
Nicolas Gallagher
fb41be1bf7 0.0.42 2016-08-12 14:59:59 -07:00
Nicolas Gallagher
2291544373 [fix] React@15.3 propType warnings 2016-08-12 14:59:28 -07:00
Nicolas Gallagher
6416166bc3 [add] support for RTL layout
Add `I18nManager` API from React Native. This can be used to control
when the app displays in RTL mode.

Add `$noI18n` property suffix for properties that StyleSheet will
automatically flip. This can be used to opt-out of automatic flipping on
a per-declaration basis.
2016-08-12 14:58:53 -07:00
Nicolas Gallagher
f1e221e51e Add Github issue and pull request templates
Fix #180
Close #185
2016-08-05 10:59:00 -07:00
Nicolas Gallagher
27edfd9d88 Update guide for styling 2016-08-03 15:56:04 -07:00
Nicolas Gallagher
6cadc22ad5 0.0.41 2016-08-03 13:16:50 -07:00
Nicolas Gallagher
5c8cdd7742 [add] export a 'core' module 2016-08-03 13:16:09 -07:00
Nicolas Gallagher
e3450ed26c 0.0.40 2016-07-29 14:06:21 -07:00
Nicolas Gallagher
d54a84701a [fix] ScrollView scrolling
Scrolling is broken by the patch that adds ResponderEvent support for
multi-input devices: 6a9212df40

By calling 'preventDefault' on every touch event, scroll events were
cancelled. This patch shifts the responsibility for calling
'preventDefault' to the 'View' event handler normalizer, and only on
touch events within the Responder system.

Fix #175
2016-07-29 14:05:52 -07:00
Nicolas Gallagher
8201906703 Fix lint error 2016-07-27 15:08:10 -07:00
Nicolas Gallagher
10f88670ed [add] support for textShadow* and transition style props
Close #170
2016-07-23 19:16:48 -07:00
Nicolas Gallagher
1be2c810d1 Minor refactor of StyleSheet helpers 2016-07-23 18:54:41 -07:00
Nicolas Gallagher
6f75fb3e0d Fix gh-pages build 2016-07-22 16:10:14 -07:00
141 changed files with 3699 additions and 1982 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
}
}

30
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,30 @@
<!--
React Native for Web is an implementation of React Native. If you have feature
requests, you should post them to https://productpains.com/product/react-native/.
GitHub issues should only be used for bugs or Web-specific features you believe
React Native requires.
Make sure to add ALL the information needed to understand the bug so that
someone can help. If the info is missing we'll add the 'needs more information'
label and close the issue until there is enough information.
-->
**What is the current behavior?**
Link to minimal test case: (template: [codepen](https://codepen.io/necolas/pen/PZzwBR?editors=0010))
**What is the expected behaviour?**
**Steps to reproduce**
1.
2.
**Environment (include versions)**
OS:
Device:
Browser:
React Native for Web (version):
React (version):

17
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,17 @@
<!--
Thanks for submitting a pull request! Make sure the PR does only one thing.
Please provide enough information so that others can review your pull
request. Make sure you have read the Contributing Guidelines -
https://github.com/necolas/react-native-web/CONTRIBUTING.md
-->
**This patch solves the following problem**
**Test plan**
**This pull request**
- [ ] includes documentation
- [ ] includes tests
- [ ] includes an interactive example
- [ ] includes screenshots/videos

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
/dist
/dist-examples
/node_modules
/storybook

140
README.md
View File

@@ -2,28 +2,25 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
![gzipped size](https://img.shields.io/badge/gzipped-~48.6k-blue.svg)
[React Native][react-native-url] components and APIs for the Web.
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
[npm-image]: https://badge.fury.io/js/react-native-web.svg
[npm-url]: https://npmjs.org/package/react-native-web
[react-native-url]: https://facebook.github.io/react-native/
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
[travis-url]: https://travis-ci.org/necolas/react-native-web
## Overview
"React Native for Web" is a project to bring React Native's building blocks and
touch handling to the Web.
touch handling to the Web. [Read more](#why).
React Native provides a foundational layer to support interoperable,
zero-configuration React component development. This is missing from React's
web ecosystem where OSS components rely on inline styles (usually without
vendor prefixes), or require build tool configuration. This project allows
components built upon React Native to be run on the Web, and it manages all
component styling out-of-the-box.
For example, the [`View`](docs/components/View.md) component makes it easy to build
cross-browser layouts with flexbox, such as stacked and nested boxes with
margin and padding. And the [`StyleSheet`](docs/guides/style.md) API converts
styles defined in JavaScript into "Atomic CSS".
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
@@ -35,18 +32,72 @@ 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
Demos:
Guides:
* [React Native for Web: Playground](http://codepen.io/necolas/pen/PZzwBR).
* [TicTacToe](http://codepen.io/necolas/full/eJaLZd/)
* [2048](http://codepen.io/necolas/full/wMVvxj/)
* [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)
Sample:
Exported modules:
* Components
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
* [`Image`](docs/components/Image.md)
* [`ListView`](docs/components/ListView.md)
* [`ProgressBar`](docs/components/ProgressBar.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'
@@ -85,50 +136,13 @@ AppRegistry.registerComponent('MyApp', () => App)
AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-root') })
```
## Documentation
## Related projects
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)
* [`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)
* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter)
* [react-native-web-player](https://github.com/dabbott/react-native-web-player)
* [react-web](https://github.com/taobaofed/react-web)
* [react-native-for-web](https://github.com/KodersLab/react-native-for-web)
## License
React Native for Web is [BSD licensed](LICENSE).
[npm-image]: https://badge.fury.io/js/react-native-web.svg
[npm-url]: https://npmjs.org/package/react-native-web
[react-native-url]: https://facebook.github.io/react-native/
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
[travis-url]: https://travis-ci.org/necolas/react-native-web

1
core.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./dist/core')

25
docs/apis/I18nManager.md Normal file
View File

@@ -0,0 +1,25 @@
# I18nManager
Control and set the layout and writing direction of the application.
## Properties
**isRTL**: bool = false
Whether the application is currently in RTL mode.
## Methods
static **allowRTL**(allowRTL: bool)
Allow the application to display in RTL mode.
static **forceRTL**(forceRTL: bool)
Force the application to display in RTL mode.
static **setPreferredLanguageRTL**(isRTL: bool)
Set the application's preferred writing direction to RTL. You will need to
determine the user's preferred locale server-side (from HTTP headers) and
decide whether it's an RTL language.

View File

@@ -29,7 +29,7 @@ static **removeEventListener**(eventName: ChangeEventName, handler: Function)
## Properties
**isConnected**
**isConnected**: bool = true
Available on all user agents. Asynchronously fetch a boolean to determine
internet connectivity.

View File

@@ -2,8 +2,8 @@
The `StyleSheet` abstraction converts predefined styles to (vendor-prefixed)
CSS without requiring a compile-time step. Some styles cannot be resolved
outside of the render loop and are applied as inline styles. Read more about to
[how style your application](docs/guides/style).
outside of the render loop and are applied as inline styles. Read more about
[how to style your application](../guides/style.md).
## Methods

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

@@ -46,7 +46,7 @@ Invoked when load either succeeds or fails,
Invoked on load start.
**resizeMode**: oneOf('center', 'contain', 'cover', 'none', 'repeat', 'stretch') = 'stretch'
**resizeMode**: oneOf('center', 'contain', 'cover', 'none', 'repeat', 'stretch') = 'cover'
Determines how to resize the image when the frame doesn't match the raw image
dimensions.

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

@@ -68,14 +68,22 @@ Lets the user select the text.
+ `fontWeight`
+ `letterSpacing`
+ `lineHeight`
+ `textAlign`
+ `textAlign`
+ `textAlignVertical`
+ `textDecorationLine`
+ `textShadow`
+ `textOverflow`
+ `textRendering`
+ `textShadowColor`
+ `textShadowOffset`
+ `textShadowRadius`
+ `textTransform`
+ `unicodeBidi`
+ `whiteSpace`
+ `wordWrap`
+ `writingDirection`
+ `writingDirection`
‡ This property can be suffixed with `$noI18n` to prevent automatic
bidi-flipping in RTL mode. This is only supported if `Platform.OS === 'web'`.
**testID**: string

View File

@@ -108,10 +108,26 @@ from `style`.
+ `backgroundPosition`
+ `backgroundRepeat`
+ `backgroundSize`
+ `borderColor`
+ `borderRadius`
+ `borderStyle`
+ `borderWidth`
+ `borderColor` (single value)
+ `borderTopColor`
+ `borderBottomColor`
+ `borderRightColor`
+ `borderLeftColor`
+ `borderRadius` (single value)
+ `borderTopLeftRadius`
+ `borderTopRightRadius`
+ `borderBottomLeftRadius`
+ `borderBottomRightRadius`
+ `borderStyle` (single value)
+ `borderTopStyle`
+ `borderRightStyle`
+ `borderBottomStyle`
+ `borderLeftStyle`
+ `borderWidth` (single value)
+ `borderBottomWidth`
+ `borderLeftWidth`
+ `borderRightWidth`
+ `borderTopWidth`
+ `bottom`
+ `boxShadow`
+ `boxSizing`
@@ -124,12 +140,12 @@ from `style`.
+ `flexWrap`
+ `height`
+ `justifyContent`
+ `left`
+ `left`
+ `margin` (single value)
+ `marginBottom`
+ `marginHorizontal`
+ `marginLeft`
+ `marginRight`
+ `marginLeft`
+ `marginRight`
+ `marginTop`
+ `marginVertical`
+ `maxHeight`
@@ -144,20 +160,22 @@ from `style`.
+ `padding` (single value)
+ `paddingBottom`
+ `paddingHorizontal`
+ `paddingLeft`
+ `paddingRight`
+ `paddingLeft`
+ `paddingRight`
+ `paddingTop`
+ `paddingVertical`
+ `position`
+ `right`
+ `right`
+ `top`
+ `transform`
+ `transformMatrix`
+ `userSelect`
+ `visibility`
+ `width`
+ `zIndex`
‡ This property can be suffixed with `$noI18n` to prevent automatic
bidi-flipping in RTL mode. This is only supported if `Platform.OS === 'web'`.
Default:
```js

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

@@ -16,6 +16,22 @@ module.exports = {
}
```
The `react-native-web` package also includes a `core` module that exports only
`ReactNative`, `Image`, `StyleSheet`, `Text`, `TextInput`, and `View`.
```js
// webpack.config.js
module.exports = {
// ...other configuration
resolve: {
alias: {
'react-native': 'react-native-web/core'
}
}
}
```
## Client-side rendering
Rendering without using the `AppRegistry`:

View File

@@ -1,8 +1,7 @@
# Style
React Native for Web relies on JavaScript to define styles for your
application. Along with a novel JS-to-CSS conversion strategy, this allows you
to avoid issues arising from the [7 deadly sins of
application. This allows you to avoid issues arising from the [7 deadly sins of
CSS](https://speakerdeck.com/vjeux/react-css-in-js):
1. Global namespace
@@ -33,9 +32,9 @@ const styles = StyleSheet.create({
```
Using `StyleSheet.create` is optional but provides some key advantages: styles
are immutable in development, styles are converted to CSS rather than applied
as inline styles, and styles are only created once for the application and not
on every render.
are immutable in development, certain declarations are automatically converted
to CSS rather than applied as inline styles, and styles are only created once
for the application and not on every render.
The attribute names and values are a subset of CSS. See the `style`
documentation of individual components.
@@ -140,51 +139,8 @@ benefit of co-locating breakpoint-specific DOM and style changes.
## Pseudo-classes and pseudo-elements
Pseudo-classes like `:hover` and `:focus` can be implemented with the events
(e.g. `onFocus`). Pseudo-elements are not supported; elements should be used
instead.
## How it works
Every call to `StyleSheet.create` extracts the unique _declarations_ and
converts them to a unique CSS rule. This is sometimes referred to as "atomic
CSS". All the core components map their `style` property-value pairs to the
corresponding `className`'s.
By doing this, the total size of the generated CSS is determined by the
total number of unique declarations (rather than the total number of rules in
the application), making it viable to inline the style sheet when pre-rendering
on the server. Styles are updated if new module bundle are loaded asynchronously.
JavaScript definition:
```js
const styles = StyleSheet.create({
heading: {
color: 'gray',
fontSize: '2rem'
},
text: {
color: 'gray',
fontSize: '1.25rem'
}
})
```
CSS output:
```css
.__style1 { color: gray; }
.__style2 { font-size: 2rem; }
.__style3 { font-size: 1.25rem; }
```
Rendered HTML:
```html
<span className="__style1 __style2">Heading</span>
<span className="__style1 __style3">Text</span>
```
Pseudo-classes like `:hover` and `:focus` can be implemented with events (e.g.
`onFocus`). Pseudo-elements are not supported; elements should be used instead.
### Reset
@@ -194,27 +150,3 @@ You **do not** need to include a CSS reset or
React Native for Web includes a very small CSS reset taken from normalize.css.
It removes unwanted User Agent styles from (pseudo-)elements beyond the reach
of React (e.g., `html`, `body`) or inline styles (e.g., `::-moz-focus-inner`).
```css
html {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color:rgba(0,0,0,0)
}
body {
margin: 0;
}
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
display: none;
}
```

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

@@ -406,6 +406,7 @@ const examples = [
);
},
},
/*
{
title: 'Tint Color',
description: 'The `tintColor` style prop changes all the non-alpha ' +
@@ -456,6 +457,7 @@ const examples = [
);
},
},
*/
{
title: 'Resize Mode',
description: 'The `resizeMode` style prop controls how the image is ' +
@@ -649,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

@@ -271,7 +271,7 @@ const examples = [
<Text>
auto (default) - english LTR
</Text>
<Text style={{ writingDirection: 'rtl' }}>
<Text style={{ writingDirection$noI18n: 'rtl' }}>
أحب اللغة العربية auto (default) - arabic RTL
</Text>
<Text style={{textAlign: 'left'}}>
@@ -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.39",
"version": "0.0.46",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
@@ -8,58 +8,56 @@
],
"scripts": {
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
"build:storybook": "build-storybook -o storybook -c ./examples/.storybook",
"deploy:storybook": "git checkout gh-pages && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -",
"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 --dont-track",
"lint": "eslint src",
"prepublish": "npm run build && npm run build:umd",
"storybook": "start-storybook -p 9001 -c ./examples/.storybook",
"test": "karma start karma.config.js",
"test:watch": "npm run test -- --no-single-run"
},
"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

@@ -0,0 +1,46 @@
/* eslint-env mocha */
import assert from 'assert';
import I18nManager from '..';
suite('apis/I18nManager', () => {
suite('when RTL not enabled', () => {
setup(() => {
I18nManager.setPreferredLanguageRTL(false);
});
test('is "false" by default', () => {
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);
});
});
suite('when RTL is enabled', () => {
setup(() => {
I18nManager.setPreferredLanguageRTL(true);
});
teardown(() => {
I18nManager.setPreferredLanguageRTL(false);
});
test('is "true" by default', () => {
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);
});
});
});

View File

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

@@ -1,3 +1,4 @@
/* eslint-disable */
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
@@ -8,6 +9,8 @@
import { PropTypes } from 'react'
import ImageStylePropTypes from '../../components/Image/ImageStylePropTypes'
import ReactPropTypeLocations from 'react/lib/ReactPropTypeLocations'
import ReactPropTypesSecret from 'react/lib/ReactPropTypesSecret'
import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
import warning from 'fbjs/lib/warning'
@@ -16,13 +19,21 @@ class StyleSheetValidation {
static validateStyleProp(prop, style, caller) {
if (process.env.NODE_ENV !== 'production') {
if (allStylePropTypes[prop] === undefined) {
const message1 = `"${prop}" is not a valid style property.`
const message2 = '\nValid style props: ' + JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ')
styleError(message1, style, caller, message2)
var message1 = '"' + prop + '" is not a valid style property.';
var message2 = '\nValid style props: ' +
JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ');
styleError(message1, style, caller, message2);
} else {
const error = allStylePropTypes[prop](style, prop, caller, 'prop')
var error = allStylePropTypes[prop](
style,
prop,
caller,
ReactPropTypeLocations.prop,
null,
ReactPropTypesSecret
);
if (error) {
styleError(error.message, style, caller)
styleError(error.message, style, caller);
}
}
}
@@ -30,28 +41,28 @@ class StyleSheetValidation {
static validateStyle(name, styles) {
if (process.env.NODE_ENV !== 'production') {
for (const prop in styles[name]) {
StyleSheetValidation.validateStyleProp(prop, styles[name], 'StyleSheet ' + name)
for (var prop in styles[name]) {
StyleSheetValidation.validateStyleProp(prop, styles[name], 'StyleSheet ' + name);
}
}
}
static addValidStylePropTypes(stylePropTypes) {
for (const key in stylePropTypes) {
allStylePropTypes[key] = stylePropTypes[key]
for (var key in stylePropTypes) {
allStylePropTypes[key] = stylePropTypes[key];
}
}
}
const styleError = (message1, style, caller, message2) => {
var styleError = function(message1, style, caller?, message2?) {
warning(
false,
message1 + '\n' + (caller || '<<unknown>>') + ': ' +
JSON.stringify(style, null, ' ') + (message2 || '')
)
}
);
};
const allStylePropTypes = {}
var allStylePropTypes = {};
StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes)
@@ -61,10 +72,10 @@ StyleSheetValidation.addValidStylePropTypes({
clear: PropTypes.string,
cursor: PropTypes.string,
display: PropTypes.string,
direction: PropTypes.string, /* @private */
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

@@ -0,0 +1,91 @@
/* eslint-env mocha */
import assert from 'assert';
import I18nManager from '../../I18nManager';
import i18nStyle from '../i18nStyle';
const initial = {
borderLeftColor: 'red',
borderRightColor: 'blue',
borderTopLeftRadius: 10,
borderTopRightRadius: '1rem',
borderBottomLeftRadius: 20,
borderBottomRightRadius: '2rem',
borderLeftStyle: 'solid',
borderRightStyle: 'dotted',
borderLeftWidth: 5,
borderRightWidth: 6,
left: 1,
marginLeft: 7,
marginRight: 8,
paddingLeft: 9,
paddingRight: 10,
right: 2,
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 expected = {
borderLeftColor: 'blue',
borderRightColor: 'red',
borderTopLeftRadius: '1rem',
borderTopRightRadius: 10,
borderBottomLeftRadius: '2rem',
borderBottomRightRadius: 20,
borderLeftStyle: 'dotted',
borderRightStyle: 'solid',
borderLeftWidth: 6,
borderRightWidth: 5,
left: 2,
marginLeft: 8,
marginRight: 7,
paddingLeft: 10,
paddingRight: 9,
right: 1,
textAlign: 'right',
textShadowOffset: { width: '-1rem', height: 10 },
writingDirection: 'rtl'
};
suite('apis/StyleSheet/i18nStyle', () => {
suite('LTR mode', () => {
setup(() => {
I18nManager.allowRTL(false);
});
teardown(() => {
I18nManager.allowRTL(true);
});
test('does not auto-flip', () => {
assert.deepEqual(i18nStyle(initial), initial);
});
test('normalizes properties', () => {
assert.deepEqual(i18nStyle(initialNoI18n), initial);
});
});
suite('RTL mode', () => {
setup(() => {
I18nManager.forceRTL(true);
});
teardown(() => {
I18nManager.forceRTL(false);
});
test('does auto-flip', () => {
assert.deepEqual(i18nStyle(initial), expected);
});
test('normalizes properties', () => {
assert.deepEqual(i18nStyle(initialNoI18n), initial);
});
});
});

View File

@@ -1,52 +1,52 @@
/* eslint-env mocha */
import assert from 'assert'
import { defaultStyles } from '../predefs'
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,
defaultStyles
)
})
})
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,
defaultStyles
)
})
getDefaultStyleSheet()
);
});
test('resolve', () => {
assert.deepEqual(
@@ -61,11 +61,11 @@ suite('apis/StyleSheet', () => {
{
className: 'test __style_df __style_pebn',
style: {
display: 'flex',
display: null,
opacity: 1,
pointerEvents: 'box-none'
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

@@ -0,0 +1,24 @@
/* eslint-env mocha */
import assert from 'assert';
import processTextShadow from '../processTextShadow';
suite('apis/StyleSheet/processTextShadow', () => {
test('textShadowOffset', () => {
const style = {
textShadowColor: 'red',
textShadowOffset: { width: 2, height: 2 },
textShadowRadius: 5
};
assert.deepEqual(
processTextShadow(style),
{
textShadow: '2px 2px 5px red',
textShadowColor: null,
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,22 +11,25 @@ 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),
{ transform: 'matrix3d(1,2,3,4,5,6)' }
)
})
})
{
transform: 'matrix3d(1,2,3,4,5,6)',
transformMatrix: null
}
);
});
});

View File

@@ -0,0 +1,17 @@
/* eslint-env mocha */
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,22 +1,20 @@
import expandStyle from './expandStyle'
import flattenStyle from '../../modules/flattenStyle'
import prefixAll from 'inline-style-prefixer/static'
import processTransform from './processTransform'
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 addVendorPrefixes = (style) => {
let 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]
if (Array.isArray(value)) {
prefixedStyles[prop] = value[value.length - 1]
}
}
return prefixedStyles
}
const processors = [
processTextShadow,
processTransform,
processVendorPrefixes
];
const _createReactDOMStyleObject = (reactNativeStyle) => processTransform(expandStyle(flattenStyle(reactNativeStyle)))
const createReactDOMStyleObject = (reactNativeStyle) => addVendorPrefixes(_createReactDOMStyleObject(reactNativeStyle))
const applyProcessors = (style) => processors.reduce((style, processor) => processor(style), style);
module.exports = createReactDOMStyleObject
const createReactDOMStyleObject = (reactNativeStyle) => applyProcessors(
expandStyle(i18nStyle(flattenStyle(reactNativeStyle)))
);
module.exports = createReactDOMStyleObject;

View File

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

View File

@@ -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

@@ -0,0 +1,106 @@
import I18nManager from '../I18nManager';
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue';
/**
* Map of property names to their BiDi equivalent.
*/
const PROPERTIES_TO_SWAP = {
'borderTopLeftRadius': 'borderTopRightRadius',
'borderTopRightRadius': 'borderTopLeftRadius',
'borderBottomLeftRadius': 'borderBottomRightRadius',
'borderBottomRightRadius': 'borderBottomLeftRadius',
'borderLeftColor': 'borderRightColor',
'borderLeftStyle': 'borderRightStyle',
'borderLeftWidth': 'borderRightWidth',
'borderRightColor': 'borderLeftColor',
'borderRightWidth': 'borderLeftWidth',
'borderRightStyle': 'borderLeftStyle',
'left': 'right',
'marginLeft': 'marginRight',
'marginRight': 'marginLeft',
'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) => multiplyStyleLengthValue(value, -1);
/**
* BiDi flip the given property.
*/
const flipProperty = (prop:String): String => {
return PROPERTIES_TO_SWAP.hasOwnProperty(prop) ? PROPERTIES_TO_SWAP[prop] : prop;
};
/**
* BiDi flip translateX
*/
const flipTransform = (transform: Object): Object => {
const translateX = transform.translateX;
if (translateX != null) {
transform.translateX = additiveInverse(translateX);
}
return transform;
};
const swapLeftRight = (value:String): String => {
return value === 'left' ? 'right' : value === 'right' ? 'left' : value;
};
const swapLtrRtl = (value:String): String => {
return value === 'ltr' ? 'rtl' : value === 'rtl' ? 'ltr' : value;
};
const i18nStyle = (style = {}) => {
const newStyle = {};
for (const prop in style) {
if (style.hasOwnProperty(prop)) {
const indexOfNoFlip = prop.indexOf('$noI18n');
if (I18nManager.isRTL) {
if (PROPERTIES_TO_SWAP[prop]) {
const newProp = flipProperty(prop);
newStyle[newProp] = style[prop];
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
newStyle[prop] = swapLeftRight(style[prop]);
} else if (PROPERTIES_SWAP_LTR_RTL[prop]) {
newStyle[prop] = swapLtrRtl(style[prop]);
} else if (prop === 'textShadowOffset') {
newStyle[prop] = style[prop];
newStyle[prop].width = additiveInverse(style[prop].width);
} else if (prop === 'transform') {
newStyle[prop] = style[prop].map(flipTransform);
} else if (indexOfNoFlip > -1) {
const newProp = prop.substring(0, indexOfNoFlip);
newStyle[newProp] = style[prop];
} else {
newStyle[prop] = style[prop];
}
} else {
if (indexOfNoFlip > -1) {
const newProp = prop.substring(0, indexOfNoFlip);
newStyle[newProp] = style[prop];
} else {
newStyle[prop] = style[prop];
}
}
}
}
return newStyle;
};
module.exports = i18nStyle;

View File

@@ -1,76 +1,88 @@
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 { defaultStyles, mapStyleToClassName } from './predefs'
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 isRendered = false
let styleElement
const STYLE_SHEET_ID = '__react-native-style'
let styleElement;
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM;
const _injectStyleSheet = () => {
const STYLE_SHEET_ID = '__react-native-style';
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 };
const defaultStyleSheet = css.getDefaultStyleSheet();
const insertStyleSheet = () => {
// check if the server rendered the style sheet
styleElement = document.getElementById(STYLE_SHEET_ID)
styleElement = document.getElementById(STYLE_SHEET_ID);
// if not, inject the style sheet
if (!styleElement) { document.head.insertAdjacentHTML('afterbegin', renderToString()) }
isRendered = true
}
const _reset = () => {
if (styleElement) { document.head.removeChild(styleElement) }
styleElement = null
isRendered = false
}
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }
const absoluteFill = ReactNativePropRegistry.register(absoluteFillObject)
const create = (styles: Object): Object => {
if (!isRendered && ExecutionEnvironment.canUseDOM) {
_injectStyleSheet()
if (!styleElement) {
document.head.insertAdjacentHTML(
'afterbegin',
`<style id="${STYLE_SHEET_ID}">${defaultStyleSheet}</style>`
);
shouldInsertStyleSheet = false;
}
const result = {}
for (let key in styles) {
StyleSheetValidation.validateStyle(key, styles)
result[key] = ReactNativePropRegistry.register(styles[key])
}
return result
}
const render = () => <style dangerouslySetInnerHTML={{ __html: defaultStyles }} id={STYLE_SHEET_ID} />
const renderToString = () => `<style id="${STYLE_SHEET_ID}">${defaultStyles}</style>`
/**
* Accepts React props and converts style declarations to classNames when necessary
*/
const resolve = (props) => {
let className = props.className || ''
let style = createReactStyleObject(props.style)
for (const prop in style) {
const value = style[prop]
const replacementClassName = mapStyleToClassName(prop, value)
if (replacementClassName) {
className += ` ${replacementClassName}`
// delete style[prop]
}
}
return { className, style }
}
};
module.exports = {
_reset,
absoluteFill,
/**
* For testing
* @private
*/
_reset() {
if (styleElement) {
document.head.removeChild(styleElement);
styleElement = null;
shouldInsertStyleSheet = true;
}
},
absoluteFill: ReactNativePropRegistry.register(absoluteFillObject),
absoluteFillObject,
create,
create(styles) {
if (shouldInsertStyleSheet) {
insertStyleSheet();
}
const result = {};
for (const key in styles) {
StyleSheetValidation.validateStyle(key, styles);
result[key] = ReactNativePropRegistry.register(styles[key]);
}
return result;
},
hairlineWidth: 1,
flatten: flattenStyle,
/* @platform web */
render,
/* @platform web */
resolve
}
render() {
return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />;
},
/**
* Accepts React props and converts style declarations to classNames when necessary
* @platform web
*/
resolve(props) {
let className = props.className || '';
const style = createReactStyleObject(props.style);
for (const prop in style) {
const value = style[prop];
const replacementClassName = css.getStyleAsHelperClassName(prop, value);
if (replacementClassName) {
className += ` ${replacementClassName}`;
style[prop] = null;
}
}
return { className, style };
}
};

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,38 +0,0 @@
const DISPLAY_FLEX_CLASSNAME = '__style_df'
const POINTER_EVENTS_AUTO_CLASSNAME = '__style_pea'
const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn'
const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo'
const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen'
const styleAsClassName = {
display: {
'flex': DISPLAY_FLEX_CLASSNAME
},
pointerEvents: {
'auto': POINTER_EVENTS_AUTO_CLASSNAME,
'box-none': POINTER_EVENTS_BOX_NONE_CLASSNAME,
'box-only': POINTER_EVENTS_BOX_ONLY_CLASSNAME,
'none': POINTER_EVENTS_NONE_CLASSNAME
}
}
export const mapStyleToClassName = (prop, value) => {
return styleAsClassName[prop] && styleAsClassName[prop][value]
}
// reset unwanted styles beyond the control of React inline styles
const resetCSS =
'/* 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}'
const helperCSS =
// 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}`
export const defaultStyles = `${resetCSS}\n${helperCSS}`

View File

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

View File

@@ -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)
delete style.transformMatrix
style.transform = convertTransformMatrix(style.transformMatrix);
style.transformMatrix = null;
}
}
return style
}
return style;
};
module.exports = processTransform
module.exports = processTransform;

View File

@@ -0,0 +1,16 @@
import prefixAll from 'inline-style-prefixer/static';
const processVendorPrefixes = (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];
if (Array.isArray(value)) {
prefixedStyles[prop] = value[value.length - 1];
}
}
return prefixedStyles;
};
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,50 +1,43 @@
import applyNativeMethods from '../../modules/applyNativeMethods'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
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 animationEffectTimingProperties = {
direction: 'alternate',
duration: 700,
easing: 'ease-in-out',
fill: 'forwards',
iterations: Infinity
}
const keyframeEffects = [
{ transform: 'scale(1)', opacity: 1.0 },
{ transform: 'scale(0.95)', opacity: 0.5 }
]
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);
this.state = {
animation: new Animated.Value(0)
};
}
componentDidMount() {
if (document.documentElement.animate) {
this._player = ReactDOM.findDOMNode(this._indicatorRef).animate(keyframeEffects, animationEffectTimingProperties)
}
this._manageAnimation()
this._manageAnimation();
}
componentDidUpdate() {
this._manageAnimation()
this._manageAnimation();
}
render() {
@@ -55,39 +48,89 @@ class ActivityIndicator extends Component {
size,
style,
...other
} = this.props
} = this.props;
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
ref={this._createIndicatorRef}
<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 }
{
transform: [
{ rotate: animation.interpolate(rotationInterpolation) }
]
}
]}
/>
</View>
)
}
_createIndicatorRef = (component) => {
this._indicatorRef = component
);
}
_manageAnimation() {
if (this._player) {
if (this.props.animating) {
this._player.play()
} else {
this._player.cancel()
}
const { animation } = this.state;
const cycleAnimation = () => {
animation.setValue(0);
Animated.timing(animation, {
duration: 750,
easing: Easing.inOut(Easing.linear),
toValue: 1
}).start((event) => {
if (event.finished) {
cycleAnimation();
}
});
};
if (this.props.animating) {
cycleAnimation();
} else {
animation.stopAnimation();
}
}
}
applyNativeMethods(ActivityIndicator)
const styles = StyleSheet.create({
container: {
alignItems: 'center',
@@ -96,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 = ActivityIndicator
module.exports = applyNativeMethods(ActivityIndicator);

View File

@@ -1,12 +1,10 @@
import keyMirror from 'fbjs/lib/keyMirror'
const ImageResizeMode = {
center: 'center',
contain: 'contain',
cover: 'cover',
none: 'none',
repeat: 'repeat',
stretch: 'stretch'
};
const ImageResizeMode = keyMirror({
center: null,
contain: null,
cover: null,
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,36 @@
/* 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 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
])
]);
const resolveAssetSource = (source) => {
return ((typeof source === 'object') ? source.uri : source) || null;
};
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 +38,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(Object.keys(ImageResizeMode)),
source: ImageSourcePropType,
style: StyleSheetPropType(ImageStylePropTypes),
testID: createReactDOMComponent.propTypes.testID
style: StyleSheetPropType(ImageStylePropTypes)
};
static defaultProps = {
@@ -49,37 +51,37 @@ class Image extends Component {
static resizeMode = ImageResizeMode;
constructor(props, context) {
super(props, context)
const uri = resolveAssetSource(props.source)
this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE }
super(props, context);
this.state = { isLoaded: false };
const uri = resolveAssetSource(props.source);
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE;
}
componentDidMount() {
if (this.state.status === STATUS_PENDING) {
this._createImageLoader()
if (this._imageState === STATUS_PENDING) {
this._createImageLoader();
}
}
componentDidUpdate() {
if (this.state.status === STATUS_PENDING && !this.image) {
this._createImageLoader()
if (this._imageState === STATUS_PENDING && !this.image) {
this._createImageLoader();
}
}
componentWillReceiveProps(nextProps) {
const nextUri = resolveAssetSource(nextProps.source)
const nextUri = resolveAssetSource(nextProps.source);
if (resolveAssetSource(this.props.source) !== nextUri) {
this.setState({
status: nextUri ? STATUS_PENDING : STATUS_IDLE
})
this._updateImageState(nextUri ? STATUS_PENDING : STATUS_IDLE);
}
}
componentWillUnmount() {
this._destroyImageLoader()
this._destroyImageLoader();
}
render() {
const { isLoaded } = this.state;
const {
accessibilityLabel,
accessible,
@@ -88,16 +90,16 @@ class Image extends Component {
onLayout,
source,
testID
} = this.props
} = this.props;
const isLoaded = this.state.status === STATUS_LOADED
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source)
const backgroundImage = displayImage ? `url("${displayImage}")` : null
const 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
// remove resizeMode style, as it is not supported by View
delete style.resizeMode
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;
/**
* Image is a non-stretching View. The image is displayed as a background
@@ -112,7 +114,6 @@ class Image extends Component {
accessibilityRole='img'
accessible={accessible}
onLayout={onLayout}
ref='root'
style={[
styles.initial,
style,
@@ -121,67 +122,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.setState({ status: 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.setState({ status: 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.setState({ status: 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;
if (isLoaded !== this.state.isLoaded) {
this.setState({ isLoaded });
}
}
}
applyNativeMethods(Image)
const styles = StyleSheet.create({
initial: {
alignSelf: 'flex-start',
@@ -204,7 +211,7 @@ const styles = StyleSheet.create({
right: 0,
top: 0
}
})
});
const resizeModeStyles = StyleSheet.create({
center: {
@@ -227,6 +234,6 @@ const resizeModeStyles = StyleSheet.create({
stretch: {
backgroundSize: '100% 100%'
}
})
});
module.exports = Image
module.exports = applyNativeMethods(Image);

View File

@@ -1,5 +0,0 @@
function resolveAssetSource(source) {
return ((typeof source === 'object') ? source.uri : source) || null
}
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,12 @@
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'
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';
const SCROLLVIEW_REF = 'listviewscroll'
const scrollViewProps = Object.keys(ScrollView.propTypes);
class ListView extends Component {
static propTypes = ListViewPropTypes;
@@ -23,81 +23,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(this.props, scrollViewProps);
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 && 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,92 @@ 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}>
{contentContainer}
</ScrollViewClass>
)
(
<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;

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